diff --git a/src/cascadia/TerminalApp/ActionAndArgs.cpp b/src/cascadia/TerminalApp/ActionAndArgs.cpp index 2f8966810fe..f784345ba5a 100644 --- a/src/cascadia/TerminalApp/ActionAndArgs.cpp +++ b/src/cascadia/TerminalApp/ActionAndArgs.cpp @@ -28,6 +28,7 @@ static constexpr std::string_view ScrolldownpageKey{ "scrollDownPage" }; static constexpr std::string_view SwitchToTabKey{ "switchToTab" }; static constexpr std::string_view OpenSettingsKey{ "openSettings" }; // TODO GH#2557: Add args for OpenSettings static constexpr std::string_view SplitPaneKey{ "splitPane" }; +static constexpr std::string_view TogglePaneZoomKey{ "togglePaneZoom" }; static constexpr std::string_view ResizePaneKey{ "resizePane" }; static constexpr std::string_view MoveFocusKey{ "moveFocus" }; static constexpr std::string_view FindKey{ "find" }; @@ -87,6 +88,7 @@ namespace winrt::TerminalApp::implementation { ToggleFullscreenKey, ShortcutAction::ToggleFullscreen }, { ToggleAlwaysOnTopKey, ShortcutAction::ToggleAlwaysOnTop }, { SplitPaneKey, ShortcutAction::SplitPane }, + { TogglePaneZoomKey, ShortcutAction::TogglePaneZoom }, { SetTabColorKey, ShortcutAction::SetTabColor }, { OpenTabColorPickerKey, ShortcutAction::OpenTabColorPicker }, { UnboundKey, ShortcutAction::Invalid }, @@ -275,6 +277,7 @@ namespace winrt::TerminalApp::implementation { ShortcutAction::ToggleFullscreen, RS_(L"ToggleFullscreenCommandKey") }, { ShortcutAction::ToggleAlwaysOnTop, RS_(L"ToggleAlwaysOnTopCommandKey") }, { ShortcutAction::SplitPane, RS_(L"SplitPaneCommandKey") }, + { ShortcutAction::TogglePaneZoom, RS_(L"TogglePaneZoomCommandKey") }, { ShortcutAction::Invalid, L"" }, { ShortcutAction::Find, RS_(L"FindCommandKey") }, { ShortcutAction::SetTabColor, RS_(L"ResetTabColorCommandKey") }, diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 71bea71051f..c58a95b248c 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -103,6 +103,26 @@ namespace winrt::TerminalApp::implementation } } + void TerminalPage::_HandleTogglePaneZoom(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& args) + { + auto activeTab = _GetFocusedTab(); + if (activeTab) + { + // First thing's first, remove the current content from the UI + // tree. This is important, because we might be leaving zoom, and if + // a pane is zoomed, then it's currently in the UI tree, and should + // be removed before it's re-added in Pane::Restore + _tabContent.Children().Clear(); + + activeTab->ToggleZoom(); + + // Update the selected tab, to trigger us to re-add the tab's GetRootElement to the UI tree + _UpdatedSelectedTab(_tabView.SelectedIndex()); + } + args.Handled(true); + } + void TerminalPage::_HandleScrollUpPage(const IInspectable& /*sender*/, const TerminalApp::ActionEventArgs& args) { diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index 40438f81f51..180100f4a97 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -840,21 +840,29 @@ void Pane::_UpdateBorders() double top = 0, bottom = 0, left = 0, right = 0; Thickness newBorders{ 0 }; - if (WI_IsFlagSet(_borders, Borders::Top)) + if (_zoomed) { - top = PaneBorderSize; + // When the pane is zoomed, manually show all the borders around the window. + top = bottom = right = left = PaneBorderSize; } - if (WI_IsFlagSet(_borders, Borders::Bottom)) - { - bottom = PaneBorderSize; - } - if (WI_IsFlagSet(_borders, Borders::Left)) - { - left = PaneBorderSize; - } - if (WI_IsFlagSet(_borders, Borders::Right)) + else { - right = PaneBorderSize; + if (WI_IsFlagSet(_borders, Borders::Top)) + { + top = PaneBorderSize; + } + if (WI_IsFlagSet(_borders, Borders::Bottom)) + { + bottom = PaneBorderSize; + } + if (WI_IsFlagSet(_borders, Borders::Left)) + { + left = PaneBorderSize; + } + if (WI_IsFlagSet(_borders, Borders::Right)) + { + right = PaneBorderSize; + } } _border.BorderThickness(ThicknessHelper::FromLengths(left, top, right, bottom)); } @@ -1172,6 +1180,76 @@ std::pair, std::shared_ptr> Pane::_Split(SplitState return { _firstChild, _secondChild }; } +// Method Description: +// - Recursively attempt to "zoom" the given pane. When the pane is zoomed, it +// won't be displayed as part of the tab tree, instead it'll take up the full +// content of the tab. When we find the given pane, we'll need to remove it +// from the UI tree, so that the caller can re-add it. We'll also set some +// internal state, so the pane can display all of its borders. +// Arguments: +// - zoomedPane: This is the pane which we're attempting to zoom on. +// Return Value: +// - +void Pane::Maximize(std::shared_ptr zoomedPane) +{ + if (_IsLeaf()) + { + _zoomed = (zoomedPane == shared_from_this()); + _UpdateBorders(); + } + else + { + if (zoomedPane == _firstChild || zoomedPane == _secondChild) + { + // When we're zooming the pane, we'll need to remove it from our UI + // tree. Easy way: just remove both children. We'll re-attach both + // when we un-zoom. + _root.Children().Clear(); + } + + // Always recurse into both children. If the (un)zoomed pane was one of + // our direct children, we'll still want to update it's borders. + _firstChild->Maximize(zoomedPane); + _secondChild->Maximize(zoomedPane); + } +} + +// Method Description: +// - Recursively attempt to "un-zoom" the given pane. This does the opposite of +// Pane::Maximize. When we find the given pane, we should return the pane to our +// UI tree. We'll also clear the internal state, so the pane can display its +// borders correctly. +// - The caller should make sure to have removed the zoomed pane from the UI +// tree _before_ calling this. +// Arguments: +// - zoomedPane: This is the pane which we're attempting to un-zoom. +// Return Value: +// - +void Pane::Restore(std::shared_ptr zoomedPane) +{ + if (_IsLeaf()) + { + _zoomed = false; + _UpdateBorders(); + } + else + { + if (zoomedPane == _firstChild || zoomedPane == _secondChild) + { + // When we're un-zooming the pane, we'll need to re-add it to our UI + // tree where it originally belonged. easy way: just re-add both. + _root.Children().Clear(); + _root.Children().Append(_firstChild->GetRootElement()); + _root.Children().Append(_secondChild->GetRootElement()); + } + + // Always recurse into both children. If the (un)zoomed pane was one of + // our direct children, we'll still want to update it's borders. + _firstChild->Restore(zoomedPane); + _secondChild->Restore(zoomedPane); + } +} + // Method Description: // - Gets the size in pixels of each of our children, given the full size they // should fill. Since these children own their own separators (borders), this diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index dfbcf1e7bc2..6d1ffa133f1 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -72,6 +72,9 @@ class Pane : public std::enable_shared_from_this int GetLeafPaneCount() const noexcept; + void Maximize(std::shared_ptr zoomedPane); + void Restore(std::shared_ptr zoomedPane); + WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate>); @@ -103,6 +106,8 @@ class Pane : public std::enable_shared_from_this Borders _borders{ Borders::None }; + bool _zoomed{ false }; + bool _IsLeaf() const noexcept; bool _HasFocusedChild() const noexcept; void _SetupChildCloseHandlers(); diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index d1cb5606c07..25fc712d10f 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -452,6 +452,9 @@ Split pane + + Toggle pane zoom + New window diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp index 1ea1bb7fae2..2f6a1a280c6 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp @@ -121,6 +121,12 @@ namespace winrt::TerminalApp::implementation break; } + case ShortcutAction::TogglePaneZoom: + { + _TogglePaneZoomHandlers(*this, *eventArgs); + break; + } + case ShortcutAction::SwitchToTab: { _SwitchToTabHandlers(*this, *eventArgs); diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.h b/src/cascadia/TerminalApp/ShortcutActionDispatch.h index 7ed978d9b80..963bfbea447 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.h +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.h @@ -36,6 +36,7 @@ namespace winrt::TerminalApp::implementation TYPED_EVENT(NextTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(PrevTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(SplitPane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); + TYPED_EVENT(TogglePaneZoom, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(AdjustFontSize, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(ResetFontSize, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(ScrollUp, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.idl b/src/cascadia/TerminalApp/ShortcutActionDispatch.idl index 026fa4aa2ba..4136e44a97e 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.idl +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.idl @@ -21,6 +21,7 @@ namespace TerminalApp SplitVertical, SplitHorizontal, SplitPane, + TogglePaneZoom, SwitchToTab, AdjustFontSize, ResetFontSize, @@ -69,6 +70,7 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler NextTab; event Windows.Foundation.TypedEventHandler PrevTab; event Windows.Foundation.TypedEventHandler SplitPane; + event Windows.Foundation.TypedEventHandler TogglePaneZoom; event Windows.Foundation.TypedEventHandler AdjustFontSize; event Windows.Foundation.TypedEventHandler ResetFontSize; event Windows.Foundation.TypedEventHandler ScrollUp; diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 77e3371104f..eb396275303 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -65,7 +65,14 @@ namespace winrt::TerminalApp::implementation // - The UIElement acting as root of the Tab's root pane. UIElement Tab::GetRootElement() { - return _rootPane->GetRootElement(); + if (_zoomedPane) + { + return _zoomedPane->GetRootElement(); + } + else + { + return _rootPane->GetRootElement(); + } } // Method Description: @@ -598,8 +605,28 @@ namespace winrt::TerminalApp::implementation if (!_inRename) { - // If we're not currently in the process of renaming the tab, then just set the tab's text to whatever our active title is. - _tabViewItem.Header(winrt::box_value(tabText)); + if (_zoomedPane) + { + Controls::StackPanel sp; + sp.Orientation(Controls::Orientation::Horizontal); + Controls::FontIcon ico; + ico.FontFamily(Media::FontFamily{ L"Segoe MDL2 Assets" }); + ico.Glyph(L"\xE8A3"); // "ZoomIn", a magnifying glass with a '+' in it. + ico.FontSize(12); + ico.Margin(ThicknessHelper::FromLengths(0, 0, 8, 0)); + sp.Children().Append(ico); + Controls::TextBlock tb; + tb.Text(tabText); + sp.Children().Append(tb); + + _tabViewItem.Header(sp); + } + else + { + // If we're not currently in the process of renaming the tab, + // then just set the tab's text to whatever our active title is. + _tabViewItem.Header(winrt::box_value(tabText)); + } } else { @@ -955,6 +982,48 @@ namespace winrt::TerminalApp::implementation { return _rootPane->PreCalculateCanSplit(_activePane, splitType, availableSpace).value_or(false); } + + // Method Description: + // - Toggle our zoom state. + // * If we're not zoomed, then zoom the active pane, making it take the + // full size of the tab. We'll achieve this by changing our response to + // Tab::GetRootElement, so that it'll return the zoomed pane only. + // * If we're currently zoomed on a pane, un-zoom that pane. + // Arguments: + // - + // Return Value: + // - + void Tab::ToggleZoom() + { + if (_zoomedPane) + { + ExitZoom(); + } + else + { + EnterZoom(); + } + } + void Tab::EnterZoom() + { + _zoomedPane = _activePane; + _rootPane->Maximize(_zoomedPane); + // Update the tab header to show the magnifying glass + _UpdateTabHeader(); + } + void Tab::ExitZoom() + { + _rootPane->Restore(_zoomedPane); + _zoomedPane = nullptr; + // Update the tab header to hide the magnifying glass + _UpdateTabHeader(); + } + + bool Tab::IsZoomed() + { + return _zoomedPane != nullptr; + } + DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>); DEFINE_EVENT(Tab, ColorSelected, _colorSelected, winrt::delegate); DEFINE_EVENT(Tab, ColorCleared, _colorCleared, winrt::delegate<>); diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index a53326b04f6..6e3e5cc5b95 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -61,6 +61,11 @@ namespace winrt::TerminalApp::implementation void ResetRuntimeTabColor(); void ActivateColorPicker(); + void ToggleZoom(); + bool IsZoomed(); + void EnterZoom(); + void ExitZoom(); + WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>); @@ -73,6 +78,7 @@ namespace winrt::TerminalApp::implementation private: std::shared_ptr _rootPane{ nullptr }; std::shared_ptr _activePane{ nullptr }; + std::shared_ptr _zoomedPane{ nullptr }; winrt::hstring _lastIconPath{}; winrt::TerminalApp::ColorPickupFlyout _tabColorPickup{}; std::optional _themeTabColor{}; diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index b19710a8194..309fb12fe09 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -872,6 +872,7 @@ namespace winrt::TerminalApp::implementation _actionDispatch->NextTab({ this, &TerminalPage::_HandleNextTab }); _actionDispatch->PrevTab({ this, &TerminalPage::_HandlePrevTab }); _actionDispatch->SplitPane({ this, &TerminalPage::_HandleSplitPane }); + _actionDispatch->TogglePaneZoom({ this, &TerminalPage::_HandleTogglePaneZoom }); _actionDispatch->ScrollUpPage({ this, &TerminalPage::_HandleScrollUpPage }); _actionDispatch->ScrollDownPage({ this, &TerminalPage::_HandleScrollDownPage }); _actionDispatch->OpenSettings({ this, &TerminalPage::_HandleOpenSettings }); @@ -1189,6 +1190,33 @@ namespace winrt::TerminalApp::implementation return false; } + // Method Description: + // - Helper to manually exit "zoom" when certain actions take place. + // Anything that modifies the state of the pane tree should probably + // un-zoom the focused pane first, so that the user can see the full pane + // tree again. These actions include: + // * Splitting a new pane + // * Closing a pane + // * Moving focus between panes + // * Resizing a pane + // Arguments: + // - + // Return Value: + // - + void TerminalPage::_UnZoomIfNeeded() + { + auto activeTab = _GetFocusedTab(); + if (activeTab && activeTab->IsZoomed()) + { + // Remove the content from the tab first, so Pane::UnZoom can + // re-attach the content to the tree w/in the pane + _tabContent.Children().Clear(); + activeTab->ExitZoom(); + // Re-attach the tab's content to the UI tree. + _tabContent.Children().Append(activeTab->GetRootElement()); + } + } + // Method Description: // - Attempt to move focus between panes, as to focus the child on // the other side of the separator. See Pane::NavigateFocus for details. @@ -1202,6 +1230,7 @@ namespace winrt::TerminalApp::implementation if (auto index{ _GetFocusedTabIndex() }) { auto focusedTab{ _GetStrongTabImpl(*index) }; + _UnZoomIfNeeded(); focusedTab->NavigateFocus(direction); } } @@ -1292,6 +1321,7 @@ namespace winrt::TerminalApp::implementation if (auto index{ _GetFocusedTabIndex() }) { auto focusedTab{ _GetStrongTabImpl(*index) }; + _UnZoomIfNeeded(); focusedTab->ClosePane(); } } @@ -1423,6 +1453,8 @@ namespace winrt::TerminalApp::implementation // Hookup our event handlers to the new terminal _RegisterTerminalEvents(newControl, *focusedTab); + _UnZoomIfNeeded(); + focusedTab->SplitPane(realSplitType, realGuid, newControl); } CATCH_LOG(); @@ -1441,6 +1473,7 @@ namespace winrt::TerminalApp::implementation if (auto index{ _GetFocusedTabIndex() }) { auto focusedTab{ _GetStrongTabImpl(*index) }; + _UnZoomIfNeeded(); focusedTab->ResizePane(direction); } } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 8eb8e5752a3..1228226f6c1 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -191,6 +191,8 @@ namespace winrt::TerminalApp::implementation void _CommandPaletteClosed(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); + void _UnZoomIfNeeded(); + #pragma region ActionHandlers // These are all defined in AppActionHandlers.cpp void _HandleOpenNewTabDropdown(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); @@ -202,6 +204,7 @@ namespace winrt::TerminalApp::implementation void _HandleNextTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); void _HandlePrevTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); void _HandleSplitPane(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleTogglePaneZoom(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); void _HandleScrollUpPage(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); void _HandleScrollDownPage(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); void _HandleOpenSettings(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);