diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index 26823366401..174039e6775 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -211,6 +211,45 @@ Split pane + + Right click for split directions - right/down/up/left + + + 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..841447ae979 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -5034,9 +5034,48 @@ namespace winrt::TerminalApp::implementation }; }; - auto makeItem = [&menu, &makeCallback](const winrt::hstring& label, + auto makeItem = [&makeCallback](const winrt::hstring& label, + const winrt::hstring& icon, + const auto& action, + 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.Click(makeCallback(action)); + 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); + }; + + auto makeContextItem = [&makeCallback](const winrt::hstring& label, const winrt::hstring& icon, - const auto& action) { + const winrt::hstring& tooltip, + const auto& action, + const auto& subMenu, + auto& targetMenu) { AppBarButton button{}; if (!icon.empty()) @@ -5048,34 +5087,122 @@ namespace winrt::TerminalApp::implementation button.Label(label); button.Click(makeCallback(action)); - menu.SecondaryCommands().Append(button); + WUX::Controls::ToolTipService::SetToolTip(button, box_value(tooltip)); + button.ContextFlyout(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 splitPaneMenu{}; + // 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(); + const auto splitPaneDuplicateText = RS_(L"SplitPaneDuplicateText") + L" " + focusedProfileName; // SplitPaneDuplicateText + + const auto splitPaneRightText = RS_(L"SplitPaneRightText"); + const auto splitPaneDownText = RS_(L"SplitPaneDownText"); + const auto splitPaneUpText = RS_(L"SplitPaneUpText"); + const auto splitPaneLeftText = RS_(L"SplitPaneLeftText"); + const auto splitPaneToolTipText = RS_(L"SplitPaneToolTipText"); + + MUX::Controls::CommandBarFlyout splitPaneContextMenu{}; + makeItem(splitPaneRightText, focusedProfileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate, SplitDirection::Right, .5, nullptr } }, splitPaneContextMenu); + makeItem(splitPaneDownText, focusedProfileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate, SplitDirection::Down, .5, nullptr } }, splitPaneContextMenu); + makeItem(splitPaneUpText, focusedProfileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate, SplitDirection::Up, .5, nullptr } }, splitPaneContextMenu); + makeItem(splitPaneLeftText, focusedProfileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate, SplitDirection::Left, .5, nullptr } }, splitPaneContextMenu); + + makeContextItem(splitPaneDuplicateText, focusedProfileIcon, splitPaneToolTipText, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Duplicate, SplitDirection::Automatic, .5, nullptr } }, splitPaneContextMenu, splitPaneMenu); + + // add menu separator + const auto separatorAutoItem = AppBarSeparator{}; + + splitPaneMenu.SecondaryCommands().Append(separatorAutoItem); + + 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); + + MUX::Controls::CommandBarFlyout splitPaneContextMenu{}; + makeItem(splitPaneRightText, profileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Manual, SplitDirection::Right, .5, args } }, splitPaneContextMenu); + makeItem(splitPaneDownText, profileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Manual, SplitDirection::Down, .5, args } }, splitPaneContextMenu); + makeItem(splitPaneUpText, profileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Manual, SplitDirection::Up, .5, args } }, splitPaneContextMenu); + makeItem(splitPaneLeftText, profileIcon, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Manual, SplitDirection::Left, .5, args } }, splitPaneContextMenu); + + makeContextItem(profileName, profileIcon, splitPaneToolTipText, ActionAndArgs{ ShortcutAction::SplitPane, SplitPaneArgs{ SplitType::Manual, SplitDirection::Automatic, .5, args } }, splitPaneContextMenu, 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/Resources/en-US/Resources.resw b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw index 90648381e1a..85446823b58 100644 --- a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw @@ -262,10 +262,18 @@ Please either install the missing font or choose another one. The tooltip for a paste button + Find... + The label of a button for searching for the text + + Find... The label of a button for searching for the selected text + Find + The tooltip for a button for searching for the text + + Find The tooltip for a button for searching for the selected text @@ -334,4 +342,4 @@ Please either install the missing font or choose another one. Suggested input: {0} {Locked="{0}"} {0} will be replaced with a string of input that is suggested for the user to input - \ No newline at end of file + diff --git a/src/cascadia/TerminalControl/TermControl.xaml b/src/cascadia/TerminalControl/TermControl.xaml index b0b24c47ced..16b85c138ce 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" /> - @@ -81,6 +81,10 @@ x:Uid="SelectOutputWithSelectionButton" Click="_SelectOutputHandler" Icon="AlignLeft" /> + diff --git a/src/cascadia/TerminalControl/TerminalControlLib.vcxproj b/src/cascadia/TerminalControl/TerminalControlLib.vcxproj index f585755f209..90d053424f9 100644 --- a/src/cascadia/TerminalControl/TerminalControlLib.vcxproj +++ b/src/cascadia/TerminalControl/TerminalControlLib.vcxproj @@ -9,7 +9,6 @@ StaticLibrary Console true - 3 nested - - true true - - @@ -143,7 +138,9 @@ - + + Designer + @@ -183,9 +180,7 @@ - - - + \ No newline at end of file