Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions docs/pane-handle-snap-docking.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,15 @@ thread-affine view; its retained `ControlInteractivity`/`ControlCore` owns the
buffer, renderer state, and `ITerminalConnection`. Elevated windows and another
launched winTerm instance use another process.

The native pane header currently renders and focuses correctly, and its handle
and overflow button share the terminal's unified context flyout. The remaining
runtime pointer/overlay adapter is still readiness-gated. Model or source tests
must not be reported as proof that the live XAML drag interaction passed.
The native pane header renders and focuses correctly, and its handle and
overflow button share the terminal's unified context flyout. Its runtime adapter
captures pointer input only from the grip, feeds the existing
`PaneHandleDragSource` and `DockTargetResolver`, renders the model-produced Snap
zones and proposed-layout preview, and commits same-tab edge drops through the
layout transaction coordinator. Center drop moves the same live pane into a new
tab. Cross-window and outside-window handle drops remain readiness-gated. Model,
source, and compiled tests must not be reported as proof that the live XAML drag
interaction passed.

## Reused v0.5 components

Expand Down
29 changes: 28 additions & 1 deletion scripts/winterm/test-pane-controls.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,12 @@ try
'SetPaneHeadersVisible',
'ContextFlyout',
'Move ',
'Open pane menu'
'Open pane menu',
'_paneGrip.CapturePointer',
'PaneDragPressed.raise',
'PaneDragUpdated.raise',
'PaneDragCompleted.raise',
'PaneDragCancelled.raise'
))
{
if (-not $runtimeHeader.Contains($required))
Expand All @@ -148,6 +153,28 @@ try
}
}

$runtimeTab = Get-Content -Raw -LiteralPath (Join-Path $root 'src\cascadia\TerminalApp\Tab.cpp')
foreach ($required in @(
'_ResolvePaneDockTarget',
'_ShowPaneDockingOverlay',
'_DockPane',
'PaneHandleDragSource',
'DockTargetResolver',
'LayoutTransactionCoordinator',
'PaneSnapLayoutOverlay::Build',
'PaneMoveToNewTabRequested.raise',
'SplitDirection::Up',
'SplitDirection::Down',
'SplitDirection::Left',
'SplitDirection::Right'
))
{
if (-not $runtimeTab.Contains($required))
{
throw "Runtime Pane Handle docking boundary '$required' is missing."
}
}

$actionHandlers = Get-Content -Raw -LiteralPath (Join-Path $root $requiredFiles[12])
$handlerStart = $actionHandlers.IndexOf('void TerminalPage::_HandleSplitPane')
$handlerEnd = $actionHandlers.IndexOf('void TerminalPage::_HandleToggleSplitOrientation', $handlerStart)
Expand Down
89 changes: 89 additions & 0 deletions src/cascadia/TerminalApp/Pane.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1885,6 +1885,28 @@ void Pane::_AttachLeafVisual()
_paneHeader.PointerPressed([this](auto&&, auto&&) { _Focus(); });
_paneHeader.Tapped([this](auto&&, auto&&) { _Focus(); });
_paneHeader.RightTapped([this](auto&&, auto&&) { _Focus(); });
_paneGrip.PointerPressed([this](auto&&, const winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs& e) {
_PaneGripPointerPressed(e);
});
_paneGrip.PointerMoved([this](auto&&, const winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs& e) {
_PaneGripPointerMoved(e);
});
_paneGrip.PointerReleased([this](auto&&, const winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs& e) {
_PaneGripPointerReleased(e);
});
_paneGrip.PointerCanceled([this](auto&&, auto&&) {
_CancelPaneDrag(true);
});
_paneGrip.PointerCaptureLost([this](auto&&, auto&&) {
_CancelPaneDrag(true);
});
_paneGrip.KeyDown([this](auto&&, const winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs& e) {
if (e.Key() == winrt::Windows::System::VirtualKey::Escape && _paneDragPointerId)
{
_CancelPaneDrag(true);
e.Handled(true);
}
});
_paneGrip.PointerEntered([](auto&&, auto&&) {
if (const auto window = Window::Current())
{
Expand All @@ -1909,6 +1931,73 @@ void Pane::_AttachLeafVisual()
_UpdatePaneHeader();
}

void Pane::_PaneGripPointerPressed(const winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs& e)
{
const auto point = e.GetCurrentPoint(_paneGrip);
if (_paneDragPointerId || !point.Properties().IsLeftButtonPressed())
{
return;
}

_Focus();
if (!_paneGrip.CapturePointer(e.Pointer()))
{
return;
}

_paneDragPointerId = e.Pointer().PointerId();
_paneDragStartPoint = point.Position();
_paneGrip.Focus(FocusState::Pointer);
PaneDragPressed.raise(shared_from_this(), e);
e.Handled(true);
}

void Pane::_PaneGripPointerMoved(const winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs& e)
{
if (!_paneDragPointerId || e.Pointer().PointerId() != *_paneDragPointerId)
{
return;
}

PaneDragUpdated.raise(shared_from_this(), e);
e.Handled(true);
}

void Pane::_PaneGripPointerReleased(const winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs& e)
{
if (!_paneDragPointerId || e.Pointer().PointerId() != *_paneDragPointerId)
{
return;
}

_paneDragPointerId.reset();
_paneGrip.ReleasePointerCapture(e.Pointer());

PaneDragCompleted.raise(shared_from_this(), e);
if (_content)
{
_content.Focus(FocusState::Programmatic);
}
e.Handled(true);
}

void Pane::_CancelPaneDrag(const bool restoreFocus)
{
if (!_paneDragPointerId)
{
return;
}

_paneDragPointerId.reset();
_paneGrip.ReleasePointerCaptures();

PaneDragCancelled.raise(shared_from_this());
if (restoreFocus && _content)
{
_content.Focus(FocusState::Programmatic);
}
}

void Pane::_UpdatePaneHeader()
{
if (!_paneHeader || !_paneTitle || !_paneGrip || !_paneStatus)
Expand Down
18 changes: 18 additions & 0 deletions src/cascadia/TerminalApp/Pane.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,18 @@ class Pane : public std::enable_shared_from_this<Pane>
til::event<winrt::delegate<std::shared_ptr<Pane>>> LostFocus;
til::event<winrt::delegate<std::shared_ptr<Pane>>> Detached;

using paneDragPointerArgs = winrt::delegate<
std::shared_ptr<Pane>,
winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs>;

// Pane dragging is deliberately exposed only by the dedicated header grip.
// Tab owns target resolution and layout changes so a hover never mutates the
// pane tree and a cancelled drag leaves the live session untouched.
til::event<paneDragPointerArgs> PaneDragPressed;
til::event<paneDragPointerArgs> PaneDragUpdated;
til::event<paneDragPointerArgs> PaneDragCompleted;
til::event<winrt::delegate<std::shared_ptr<Pane>>> PaneDragCancelled;

private:
struct PanePoint;
struct PaneNeighborSearch;
Expand Down Expand Up @@ -273,6 +285,8 @@ class Pane : public std::enable_shared_from_this<Pane>
bool _zoomed{ false };
bool _broadcastEnabled{ false };
bool _paneHeadersVisible{ false };
std::optional<uint32_t> _paneDragPointerId;
winrt::Windows::Foundation::Point _paneDragStartPoint{};

bool _IsLeaf() const noexcept;
bool _HasFocusedChild() const noexcept;
Expand All @@ -283,6 +297,10 @@ class Pane : public std::enable_shared_from_this<Pane>
void _UpdatePaneHeader();
winrt::hstring _PaneHeaderTitle() const;
winrt::hstring _PaneHeaderAccessibleTitle() const;
void _PaneGripPointerPressed(const winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs& e);
void _PaneGripPointerMoved(const winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs& e);
void _PaneGripPointerReleased(const winrt::Windows::UI::Xaml::Input::PointerRoutedEventArgs& e);
void _CancelPaneDrag(bool restoreFocus);
bool _HasChild(const std::shared_ptr<Pane> child);
winrt::TerminalApp::TerminalPaneContent _getTerminalContent() const;

Expand Down
Loading