diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
index 26823366401..4c0224b7e6e 100644
--- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
+++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw
@@ -211,6 +211,42 @@
Split pane
+
+ Split pane down
+
+
+ Split pane right
+
+
+ Split pane up
+
+
+ Split pane left
+
+
+ Duplicate
+
+
+ Swap pane
+
+
+ Swap pane down
+
+
+ Swap pane right
+
+
+ Swap pane up
+
+
+ Swap pane left
+
+
+ Toggle pane zoom
+
+
+ Close other panes
+
Web search
diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp
index 99064fef8bb..f3757a0343c 100644
--- a/src/cascadia/TerminalApp/TerminalPage.cpp
+++ b/src/cascadia/TerminalApp/TerminalPage.cpp
@@ -5034,9 +5034,10 @@ namespace winrt::TerminalApp::implementation
};
};
- auto makeItem = [&menu, &makeCallback](const winrt::hstring& label,
- const winrt::hstring& icon,
- const auto& action) {
+ auto makeItem = [&makeCallback](const winrt::hstring& label,
+ const winrt::hstring& icon,
+ const auto& action,
+ auto& targetMenu) {
AppBarButton button{};
if (!icon.empty())
@@ -5048,34 +5049,139 @@ namespace winrt::TerminalApp::implementation
button.Label(label);
button.Click(makeCallback(action));
- menu.SecondaryCommands().Append(button);
+ targetMenu.SecondaryCommands().Append(button);
};
+ auto makeMenuItem = [](const winrt::hstring& label,
+ const winrt::hstring& icon,
+ const auto& subMenu,
+ auto& targetMenu) {
+ AppBarButton button{};
+
+ if (!icon.empty())
+ {
+ auto iconElement = UI::IconPathConverter::IconWUX(icon);
+ Automation::AutomationProperties::SetAccessibilityView(iconElement, Automation::Peers::AccessibilityView::Raw);
+ button.Icon(iconElement);
+ }
+
+ button.Label(label);
+ button.Flyout(subMenu);
+ targetMenu.SecondaryCommands().Append(button);
+ };
+
+ const auto focusedProfile = _GetFocusedTabImpl()->GetFocusedProfile();
+ auto separatorItem = AppBarSeparator{};
+ auto activeProfiles = _settings.ActiveProfiles();
+ auto activeProfileCount = gsl::narrow_cast(activeProfiles.Size());
+ MUX::Controls::CommandBarFlyout splitPaneDownMenu{};
+ MUX::Controls::CommandBarFlyout splitPaneUpMenu{};
+ MUX::Controls::CommandBarFlyout splitPaneRightMenu{};
+ MUX::Controls::CommandBarFlyout splitPaneLeftMenu{};
+
// Wire up each item to the action that should be performed. By actually
// connecting these to actions, we ensure the implementation is
// consistent. This also leaves room for customizing this menu with
// actions in the future.
- makeItem(RS_(L"SplitPaneText"), L"\xF246", ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate } });
- makeItem(RS_(L"DuplicateTabText"), L"\xF5ED", ActionAndArgs{ ShortcutAction::DuplicateTab, nullptr });
+ makeItem(RS_(L"DuplicateTabText"), L"\xF5ED", ActionAndArgs{ ShortcutAction::DuplicateTab, nullptr }, menu);
+
+ const auto focusedProfileName = focusedProfile.Name();
+ const auto focusedProfileIcon = focusedProfile.Icon();
+
+ makeItem(RS_(L"SplitPaneDuplicateText") + L" " + focusedProfileName, focusedProfileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate, SplitDirection::Down, .5, nullptr } }, splitPaneDownMenu);
+ makeItem(RS_(L"SplitPaneDuplicateText") + L" " + focusedProfileName, focusedProfileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate, SplitDirection::Up, .5, nullptr } }, splitPaneUpMenu);
+ makeItem(RS_(L"SplitPaneDuplicateText") + L" " + focusedProfileName, focusedProfileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate, SplitDirection::Right, .5, nullptr } }, splitPaneRightMenu);
+ makeItem(RS_(L"SplitPaneDuplicateText") + L" " + focusedProfileName, focusedProfileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate, SplitDirection::Left, .5, nullptr } }, splitPaneLeftMenu);
+
+ // add menu separator
+ const auto separatorDownItem = AppBarSeparator{};
+ const auto separatorUpItem = AppBarSeparator{};
+ const auto separatorRightItem = AppBarSeparator{};
+ const auto separatorLeftItem = AppBarSeparator{};
+
+ splitPaneDownMenu.SecondaryCommands().Append(separatorDownItem);
+ splitPaneUpMenu.SecondaryCommands().Append(separatorUpItem);
+ splitPaneRightMenu.SecondaryCommands().Append(separatorRightItem);
+ splitPaneLeftMenu.SecondaryCommands().Append(separatorLeftItem);
+
+ for (auto profileIndex = 0; profileIndex < activeProfileCount; profileIndex++)
+ {
+ const auto profile = activeProfiles.GetAt(profileIndex);
+ const auto profileName = profile.Name();
+ const auto profileIcon = profile.Icon();
+
+ NewTerminalArgs args{};
+ args.Profile(profileName);
+
+ makeItem(profileName, profileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Manual, SplitDirection::Down, .5, args } }, splitPaneDownMenu);
+ makeItem(profileName, profileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Manual, SplitDirection::Up, .5, args } }, splitPaneUpMenu);
+ makeItem(profileName, profileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Manual, SplitDirection::Right, .5, args } }, splitPaneRightMenu);
+ makeItem(profileName, profileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Manual, SplitDirection::Left, .5, args } }, splitPaneLeftMenu);
+ }
+
+ MUX::Controls::CommandBarFlyout splitPaneMenu{};
+ makeMenuItem(RS_(L"SplitPaneDownText"), L"\xF246", splitPaneDownMenu, splitPaneMenu);
+ makeMenuItem(RS_(L"SplitPaneRightText"), L"\xF246", splitPaneRightMenu, splitPaneMenu);
+ makeMenuItem(RS_(L"SplitPaneUpText"), L"\xF246", splitPaneUpMenu, splitPaneMenu);
+ makeMenuItem(RS_(L"SplitPaneLeftText"), L"\xF246", splitPaneLeftMenu, splitPaneMenu);
+ makeMenuItem(RS_(L"SplitPaneText"), L"\xF246", splitPaneMenu, menu);
// Only wire up "Close Pane" if there's multiple panes.
if (_GetFocusedTabImpl()->GetLeafPaneCount() > 1)
{
- makeItem(RS_(L"PaneClose"), L"\xE89F", ActionAndArgs{ ShortcutAction::ClosePane, nullptr });
+ MUX::Controls::CommandBarFlyout swapPaneMenu{};
+ const auto rootPane = _GetFocusedTabImpl()->GetRootPane();
+ const auto mruPanes = _GetFocusedTabImpl()->GetMruPanes();
+ auto activePane = _GetFocusedTabImpl()->GetActivePane();
+ rootPane->WalkTree([&](auto p) {
+ if (const auto& c{ p->GetTerminalControl() })
+ {
+ if (c == control)
+ {
+ activePane = p;
+ }
+ }
+ });
+
+ if (auto neighbor = rootPane->NavigateDirection(activePane, FocusDirection::Down, mruPanes))
+ {
+ makeItem(RS_(L"SwapPaneDownText"), neighbor->GetProfile().Icon(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Down } }, swapPaneMenu);
+ }
+
+ if (auto neighbor = rootPane->NavigateDirection(activePane, FocusDirection::Right, mruPanes))
+ {
+ makeItem(RS_(L"SwapPaneRightText"), neighbor->GetProfile().Icon(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Right } }, swapPaneMenu);
+ }
+
+ if (auto neighbor = rootPane->NavigateDirection(activePane, FocusDirection::Up, mruPanes))
+ {
+ makeItem(RS_(L"SwapPaneUpText"), neighbor->GetProfile().Icon(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Up } }, swapPaneMenu);
+ }
+
+ if (auto neighbor = rootPane->NavigateDirection(activePane, FocusDirection::Left, mruPanes))
+ {
+ makeItem(RS_(L"SwapPaneLeftText"), neighbor->GetProfile().Icon(), ActionAndArgs{ ShortcutAction::SwapPane, SwapPaneArgs{ FocusDirection::Left } }, swapPaneMenu);
+ }
+
+ makeMenuItem(RS_(L"SwapPaneText"), L"\xF1CB", swapPaneMenu, menu);
+
+ makeItem(RS_(L"TogglePaneZoomText"), L"\xE8A3", ActionAndArgs{ ShortcutAction::TogglePaneZoom, nullptr }, menu);
+ makeItem(RS_(L"CloseOtherPanesText"), L"\xE89F", ActionAndArgs{ ShortcutAction::CloseOtherPanes, nullptr }, menu);
+ makeItem(RS_(L"PaneClose"), L"\xE89F", ActionAndArgs{ ShortcutAction::ClosePane, nullptr }, menu);
}
if (control.ConnectionState() >= ConnectionState::Closed)
{
- makeItem(RS_(L"RestartConnectionText"), L"\xE72C", ActionAndArgs{ ShortcutAction::RestartConnection, nullptr });
+ makeItem(RS_(L"RestartConnectionText"), L"\xE72C", ActionAndArgs{ ShortcutAction::RestartConnection, nullptr }, menu);
}
if (withSelection)
{
- makeItem(RS_(L"SearchWebText"), L"\xF6FA", ActionAndArgs{ ShortcutAction::SearchForText, nullptr });
+ makeItem(RS_(L"SearchWebText"), L"\xF6FA", ActionAndArgs{ ShortcutAction::SearchForText, nullptr }, menu);
}
- makeItem(RS_(L"TabClose"), L"\xE711", ActionAndArgs{ ShortcutAction::CloseTab, CloseTabArgs{ _GetFocusedTabIndex().value() } });
+ makeItem(RS_(L"TabClose"), L"\xE711", ActionAndArgs{ ShortcutAction::CloseTab, CloseTabArgs{ _GetFocusedTabIndex().value() } }, menu);
}
void TerminalPage::_PopulateQuickFixMenu(const TermControl& control,
diff --git a/src/cascadia/TerminalApp/TerminalTab.h b/src/cascadia/TerminalApp/TerminalTab.h
index f38895a3da6..723df78c5a3 100644
--- a/src/cascadia/TerminalApp/TerminalTab.h
+++ b/src/cascadia/TerminalApp/TerminalTab.h
@@ -91,6 +91,7 @@ namespace winrt::TerminalApp::implementation
winrt::TerminalApp::TaskbarState GetCombinedTaskbarState() const;
std::shared_ptr GetRootPane() const { return _rootPane; }
+ std::vector GetMruPanes() const { return _mruPanes; }
winrt::TerminalApp::TerminalTabStatus TabStatus()
{
diff --git a/src/cascadia/TerminalControl/TermControl.xaml b/src/cascadia/TerminalControl/TermControl.xaml
index b0b24c47ced..a45363d9973 100644
--- a/src/cascadia/TerminalControl/TermControl.xaml
+++ b/src/cascadia/TerminalControl/TermControl.xaml
@@ -51,6 +51,10 @@
x:Uid="SelectOutputButton"
Click="_SelectOutputHandler"
Icon="AlignLeft" />
+
@@ -64,10 +68,6 @@
Click="_PasteCommandHandler"
Icon="Paste" />
-
@@ -89,17 +89,17 @@
12dips. This is harder for folks to hit with the mouse, and isn't
consistent with the rest of the scrollbars on the platform (as much
as they can be).
-
+
To work around this, we have to entirely copy the template for the
ScrollBar into our XAML file. We're then also re-defining
ScrollBarSize here to 16, so that the new template will pick up on
the new value.
-
+
This is kinda a pain, and we have to be careful to be sure to ingest
an updated version of the template any time we update MUX. The
latest ControlsV2 version of the template can be found at:
https://github.com/microsoft/microsoft-ui-xaml/blob/main/dev/CommonStyles/ScrollBar_themeresources.xaml#L218
-
+
Additionally we have:
* removed the corner radius, because that should be flush
with the top of the window above the TermControl.
@@ -108,7 +108,7 @@
the Win11-style WinUI ScrollView. If you also have the "Always show scrollbars" setting enabled in
the settings app (do it if you haven't already), it avoids any and all animations during startup which
makes the app start feel noticeably better and also shaves off another ~167ms of our "busy time".
-
+
We're also planning on making this adjustable in the future
(GH#9218), where we might need this anyways.
-->