From 6f02fb729569879b55d1c4f2dec613b30fdf3c0c Mon Sep 17 00:00:00 2001 From: Leon Liang Date: Tue, 3 Nov 2020 14:54:19 -0800 Subject: [PATCH] Make Tab an unsealed runtimeclass (and rename it to TabBase) In preparation for the Settings UI, we needed to make some changes to Tab to abstract out shared, common functionality between different types of tab. This is the result of that work. All code references to the settings have been removed or reverted. Contains changes from #8053, #7802. The messages below only make sense in the context of the Settings UI, which this pull request does not bring in. They do, however, provide valuable information. From #7802 (@leonMSFT): > This PR's goal was to add an option to the `OpenSettings` keybinding to > open the Settings UI in a tab. In order to implement that, a couple of > changes had to be made to `Tab`, specifically: > > - Introduce a tab interface named `ITab` > - Create/Rename two new Tab classes that implement `ITab` called > `SettingsTab` and `TerminalTab` > From #8053: > `TerminalTab` and `SettingsTab` share some implementation details. The > close submenu introduced in #7728 is a good example of functionality > that is consistent across all tabs. This PR transforms `ITab` from an > interface, into an [unsealed runtime class] to de-duplicate some > functionality. Most of the logic from `SettingsTab` was moved there > because I expect the default behavior of a tab to resemble the > `SettingsTab` over a `TerminalTab`. > > ## References > Verified that Close submenu work was transferred over (#7728, #7961, #8010). > > ## Validation Steps Performed > Check close submenu on first/last tab when multiple tabs are open. > > Closes #7969 > > [unsealed runtime class]: https://docs.microsoft.com/en-us/uwp/midl-3/intro#base-classes Co-authored-by: Carlos Zamora --- .../LocalTests_TerminalApp/TabTests.cpp | 32 +- .../TerminalApp/AppActionHandlers.cpp | 100 ++--- src/cascadia/TerminalApp/AppLogic.h | 2 - src/cascadia/TerminalApp/Tab.idl | 18 - src/cascadia/TerminalApp/TabBase.cpp | 148 ++++++++ src/cascadia/TerminalApp/TabBase.h | 57 +++ src/cascadia/TerminalApp/TabBase.idl | 24 ++ .../TerminalApp/TerminalAppLib.vcxproj | 21 +- .../TerminalAppLib.vcxproj.filters | 20 +- src/cascadia/TerminalApp/TerminalPage.cpp | 356 ++++++++++-------- src/cascadia/TerminalApp/TerminalPage.h | 22 +- .../TerminalApp/{Tab.cpp => TerminalTab.cpp} | 331 +++++----------- .../TerminalApp/{Tab.h => TerminalTab.h} | 48 +-- src/cascadia/TerminalApp/TerminalTab.idl | 11 + .../TerminalApp/dll/TerminalApp.vcxproj | 3 +- src/cascadia/inc/cppwinrt_utils.h | 4 +- 16 files changed, 676 insertions(+), 521 deletions(-) delete mode 100644 src/cascadia/TerminalApp/Tab.idl create mode 100644 src/cascadia/TerminalApp/TabBase.cpp create mode 100644 src/cascadia/TerminalApp/TabBase.h create mode 100644 src/cascadia/TerminalApp/TabBase.idl rename src/cascadia/TerminalApp/{Tab.cpp => TerminalTab.cpp} (74%) rename src/cascadia/TerminalApp/{Tab.h => TerminalTab.h} (64%) create mode 100644 src/cascadia/TerminalApp/TerminalTab.idl diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp index d32a41aee94f..deff987ba7fc 100644 --- a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp @@ -7,7 +7,7 @@ #include "../TerminalApp/MinMaxCloseControl.h" #include "../TerminalApp/TabRowControl.h" #include "../TerminalApp/ShortcutActionDispatch.h" -#include "../TerminalApp/Tab.h" +#include "../TerminalApp/TerminalTab.h" #include "../CppWinrtTailored.h" using namespace Microsoft::Console; @@ -250,8 +250,8 @@ namespace TerminalAppLocalTests // In the real app, this isn't a problem, but doesn't happen // reliably in the unit tests. Log::Comment(L"Ensure we set the first tab as the selected one."); - auto tab{ page->_GetStrongTabImpl(0) }; - page->_tabView.SelectedItem(tab->GetTabViewItem()); + auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); + page->_tabView.SelectedItem(tab->TabViewItem()); page->_UpdatedSelectedTab(0); }); VERIFY_SUCCEEDED(result); @@ -453,7 +453,7 @@ namespace TerminalAppLocalTests result = RunOnUIThread([&page]() { VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); - auto tab = page->_GetStrongTabImpl(0); + auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(1, tab->GetLeafPaneCount()); }); VERIFY_SUCCEEDED(result); @@ -463,7 +463,7 @@ namespace TerminalAppLocalTests page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr); VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); - auto tab = page->_GetStrongTabImpl(0); + auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, tab->GetLeafPaneCount()); }); VERIFY_SUCCEEDED(result); @@ -481,7 +481,7 @@ namespace TerminalAppLocalTests page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, nullptr); VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); - auto tab = page->_GetStrongTabImpl(0); + auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, tab->GetLeafPaneCount(), L"We should gracefully do nothing here - the profile no longer exists."); @@ -562,7 +562,7 @@ namespace TerminalAppLocalTests ActionEventArgs eventArgs{ args }; // eventArgs.Args(args); page->_HandleSplitPane(nullptr, eventArgs); - auto firstTab = page->_GetStrongTabImpl(0); + auto firstTab = page->_GetTerminalTabImpl(0); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); @@ -573,7 +573,7 @@ namespace TerminalAppLocalTests result = RunOnUIThread([&page]() { ActionEventArgs eventArgs{}; page->_HandleTogglePaneZoom(nullptr, eventArgs); - auto firstTab = page->_GetStrongTabImpl(0); + auto firstTab = page->_GetTerminalTabImpl(0); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_TRUE(firstTab->IsZoomed()); }); @@ -583,7 +583,7 @@ namespace TerminalAppLocalTests result = RunOnUIThread([&page]() { ActionEventArgs eventArgs{}; page->_HandleTogglePaneZoom(nullptr, eventArgs); - auto firstTab = page->_GetStrongTabImpl(0); + auto firstTab = page->_GetTerminalTabImpl(0); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); }); @@ -600,7 +600,7 @@ namespace TerminalAppLocalTests SplitPaneArgs args{ SplitType::Duplicate }; ActionEventArgs eventArgs{ args }; page->_HandleSplitPane(nullptr, eventArgs); - auto firstTab = page->_GetStrongTabImpl(0); + auto firstTab = page->_GetTerminalTabImpl(0); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); @@ -614,7 +614,7 @@ namespace TerminalAppLocalTests page->_HandleTogglePaneZoom(nullptr, eventArgs); - auto firstTab = page->_GetStrongTabImpl(0); + auto firstTab = page->_GetTerminalTabImpl(0); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_TRUE(firstTab->IsZoomed()); }); @@ -628,7 +628,7 @@ namespace TerminalAppLocalTests page->_HandleMoveFocus(nullptr, eventArgs); - auto firstTab = page->_GetStrongTabImpl(0); + auto firstTab = page->_GetTerminalTabImpl(0); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); }); @@ -645,7 +645,7 @@ namespace TerminalAppLocalTests SplitPaneArgs args{ SplitType::Duplicate }; ActionEventArgs eventArgs{ args }; page->_HandleSplitPane(nullptr, eventArgs); - auto firstTab = page->_GetStrongTabImpl(0); + auto firstTab = page->_GetTerminalTabImpl(0); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); @@ -659,7 +659,7 @@ namespace TerminalAppLocalTests page->_HandleTogglePaneZoom(nullptr, eventArgs); - auto firstTab = page->_GetStrongTabImpl(0); + auto firstTab = page->_GetTerminalTabImpl(0); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_TRUE(firstTab->IsZoomed()); }); @@ -672,7 +672,7 @@ namespace TerminalAppLocalTests page->_HandleClosePane(nullptr, eventArgs); - auto firstTab = page->_GetStrongTabImpl(0); + auto firstTab = page->_GetTerminalTabImpl(0); VERIFY_IS_FALSE(firstTab->IsZoomed()); }); VERIFY_SUCCEEDED(result); @@ -683,7 +683,7 @@ namespace TerminalAppLocalTests Log::Comment(L"Check to ensure there's only one pane left."); result = RunOnUIThread([&page]() { - auto firstTab = page->_GetStrongTabImpl(0); + auto firstTab = page->_GetTerminalTabImpl(0); VERIFY_ARE_EQUAL(1, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); }); diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index e2ca19453b4b..47339208cf7b 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -130,21 +130,26 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleTogglePaneZoom(const IInspectable& /*sender*/, const ActionEventArgs& args) { - auto activeTab = _GetFocusedTab(); - - // Don't do anything if there's only one pane. It's already zoomed. - if (activeTab && activeTab->GetLeafPaneCount() > 1) + if (auto focusedTab = _GetFocusedTab()) { - // 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(); - - // Togging the zoom on the tab will cause the tab to inform us of - // the new root Content for this tab. - activeTab->ToggleZoom(); + if (auto activeTab = _GetTerminalTabImpl(focusedTab)) + { + // Don't do anything if there's only one pane. It's already zoomed. + if (activeTab && activeTab->GetLeafPaneCount() > 1) + { + // 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(); + + // Togging the zoom on the tab will cause the tab to inform us of + // the new root Content for this tab. + activeTab->ToggleZoom(); + } + } } + args.Handled(true); } @@ -323,16 +328,19 @@ namespace winrt::TerminalApp::implementation args.Handled(false); if (const auto& realArgs = args.ActionArgs().try_as()) { - if (auto activeTab = _GetFocusedTab()) + if (auto focusedTab = _GetFocusedTab()) { - if (auto activeControl = activeTab->GetActiveTerminalControl()) + if (auto activeTab = _GetTerminalTabImpl(focusedTab)) { - if (const auto scheme = _settings.GlobalSettings().ColorSchemes().TryLookup(realArgs.SchemeName())) + if (auto activeControl = activeTab->GetActiveTerminalControl()) { - auto controlSettings = activeControl.Settings().as(); - controlSettings->ApplyColorScheme(scheme); - activeControl.UpdateSettings(*controlSettings); - args.Handled(true); + if (const auto scheme = _settings.GlobalSettings().ColorSchemes().TryLookup(realArgs.SchemeName())) + { + auto controlSettings = activeControl.Settings().as(); + controlSettings->ApplyColorScheme(scheme); + activeControl.UpdateSettings(*controlSettings); + args.Handled(true); + } } } } @@ -352,16 +360,18 @@ namespace winrt::TerminalApp::implementation } } - auto activeTab = _GetFocusedTab(); - if (activeTab) + if (auto focusedTab = _GetFocusedTab()) { - if (tabColor.has_value()) + if (auto activeTab = _GetTerminalTabImpl(focusedTab)) { - activeTab->SetRuntimeTabColor(tabColor.value()); - } - else - { - activeTab->ResetRuntimeTabColor(); + if (tabColor.has_value()) + { + activeTab->SetRuntimeTabColor(tabColor.value()); + } + else + { + activeTab->ResetRuntimeTabColor(); + } } } args.Handled(true); @@ -370,10 +380,12 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleOpenTabColorPicker(const IInspectable& /*sender*/, const ActionEventArgs& args) { - auto activeTab = _GetFocusedTab(); - if (activeTab) + if (auto focusedTab = _GetFocusedTab()) { - activeTab->ActivateColorPicker(); + if (auto activeTab = _GetTerminalTabImpl(focusedTab)) + { + activeTab->ActivateColorPicker(); + } } args.Handled(true); } @@ -388,16 +400,18 @@ namespace winrt::TerminalApp::implementation title = realArgs.Title(); } - auto activeTab = _GetFocusedTab(); - if (activeTab) + if (auto focusedTab = _GetFocusedTab()) { - if (title.has_value()) - { - activeTab->SetTabText(title.value()); - } - else + if (auto activeTab = _GetTerminalTabImpl(focusedTab)) { - activeTab->ResetTabText(); + if (title.has_value()) + { + activeTab->SetTabText(title.value()); + } + else + { + activeTab->ResetTabText(); + } } } args.Handled(true); @@ -406,10 +420,12 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleOpenTabRenamer(const IInspectable& /*sender*/, const ActionEventArgs& args) { - auto activeTab = _GetFocusedTab(); - if (activeTab) + if (auto focusedTab = _GetFocusedTab()) { - activeTab->ActivateTabRenamer(); + if (auto activeTab = _GetTerminalTabImpl(focusedTab)) + { + activeTab->ActivateTabRenamer(); + } } args.Handled(true); } diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 077982d8a583..348755826853 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -4,8 +4,6 @@ #pragma once #include "AppLogic.g.h" - -#include "Tab.h" #include "TerminalPage.h" #include "Jumplist.h" #include "../../cascadia/inc/cppwinrt_utils.h" diff --git a/src/cascadia/TerminalApp/Tab.idl b/src/cascadia/TerminalApp/Tab.idl deleted file mode 100644 index 7e88c587816e..000000000000 --- a/src/cascadia/TerminalApp/Tab.idl +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -import "ShortcutActionDispatch.idl"; - -namespace TerminalApp -{ - [default_interface] runtimeclass Tab : Windows.UI.Xaml.Data.INotifyPropertyChanged - { - String Title { get; }; - Windows.UI.Xaml.Controls.IconSource IconSource { get; }; - Microsoft.Terminal.Settings.Model.Command SwitchToTabCommand { get; }; - UInt32 TabViewIndex { get; }; - - Windows.UI.Xaml.UIElement Content { get; }; - - void SetDispatch(ShortcutActionDispatch dispatch); - } -} diff --git a/src/cascadia/TerminalApp/TabBase.cpp b/src/cascadia/TerminalApp/TabBase.cpp new file mode 100644 index 000000000000..84d6a8b4c4de --- /dev/null +++ b/src/cascadia/TerminalApp/TabBase.cpp @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include +#include "TabBase.h" +#include "TabBase.g.cpp" + +using namespace winrt; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Core; +using namespace winrt::Microsoft::Terminal::TerminalControl; +using namespace winrt::Microsoft::Terminal::Settings::Model; +using namespace winrt::Windows::System; + +namespace winrt +{ + namespace MUX = Microsoft::UI::Xaml; + namespace WUX = Windows::UI::Xaml; +} + +namespace winrt::TerminalApp::implementation +{ + WUX::FocusState TabBase::FocusState() const noexcept + { + return _focusState; + } + + // Method Description: + // - Prepares this tab for being removed from the UI hierarchy + void TabBase::Shutdown() + { + Content(nullptr); + _ClosedHandlers(nullptr, nullptr); + } + + // Method Description: + // - Creates a context menu attached to the tab. + // Currently contains elements allowing the user to close the selected tab + // Arguments: + // - + // Return Value: + // - + void TabBase::_CreateContextMenu() + { + auto weakThis{ get_weak() }; + + // Close + Controls::MenuFlyoutItem closeTabMenuItem; + Controls::FontIcon closeSymbol; + closeSymbol.FontFamily(Media::FontFamily{ L"Segoe MDL2 Assets" }); + closeSymbol.Glyph(L"\xE8BB"); + + closeTabMenuItem.Click([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + tab->_ClosedHandlers(nullptr, nullptr); + } + }); + closeTabMenuItem.Text(RS_(L"TabClose")); + closeTabMenuItem.Icon(closeSymbol); + + // Build the menu + Controls::MenuFlyout newTabFlyout; + newTabFlyout.Items().Append(_CreateCloseSubMenu()); + newTabFlyout.Items().Append(closeTabMenuItem); + TabViewItem().ContextFlyout(newTabFlyout); + } + + // Method Description: + // - Creates a sub-menu containing menu items to close multiple tabs + // Arguments: + // - + // Return Value: + // - the created MenuFlyoutSubItem + Controls::MenuFlyoutSubItem TabBase::_CreateCloseSubMenu() + { + auto weakThis{ get_weak() }; + + // Close tabs after + _closeTabsAfterMenuItem.Click([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + tab->_CloseTabsAfter(); + } + }); + _closeTabsAfterMenuItem.Text(RS_(L"TabCloseAfter")); + + // Close other tabs + _closeOtherTabsMenuItem.Click([weakThis](auto&&, auto&&) { + if (auto tab{ weakThis.get() }) + { + tab->_CloseOtherTabs(); + } + }); + _closeOtherTabsMenuItem.Text(RS_(L"TabCloseOther")); + + Controls::MenuFlyoutSubItem closeSubMenu; + closeSubMenu.Text(RS_(L"TabCloseSubMenu")); + closeSubMenu.Items().Append(_closeTabsAfterMenuItem); + closeSubMenu.Items().Append(_closeOtherTabsMenuItem); + + return closeSubMenu; + } + + // Method Description: + // - Enable the Close menu items based on tab index and total number of tabs + // Arguments: + // - + // Return Value: + // - + void TabBase::_EnableCloseMenuItems() + { + // close other tabs is enabled only if there are other tabs + _closeOtherTabsMenuItem.IsEnabled(TabViewNumTabs() > 1); + // close tabs after is enabled only if there are other tabs on the right + _closeTabsAfterMenuItem.IsEnabled(TabViewIndex() < TabViewNumTabs() - 1); + } + + void TabBase::_CloseTabsAfter() + { + CloseTabsAfterArgs args{ _TabViewIndex }; + ActionAndArgs closeTabsAfter{ ShortcutAction::CloseTabsAfter, args }; + + _dispatch.DoAction(closeTabsAfter); + } + + void TabBase::_CloseOtherTabs() + { + CloseOtherTabsArgs args{ _TabViewIndex }; + ActionAndArgs closeOtherTabs{ ShortcutAction::CloseOtherTabs, args }; + + _dispatch.DoAction(closeOtherTabs); + } + + void TabBase::UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs) + { + TabViewIndex(idx); + TabViewNumTabs(numTabs); + _EnableCloseMenuItems(); + SwitchToTabCommand().Action().Args().as().TabIndex(idx); + } + + void TabBase::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch) + { + _dispatch = dispatch; + } +} diff --git a/src/cascadia/TerminalApp/TabBase.h b/src/cascadia/TerminalApp/TabBase.h new file mode 100644 index 000000000000..d4697fadcd61 --- /dev/null +++ b/src/cascadia/TerminalApp/TabBase.h @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once +#include "inc/cppwinrt_utils.h" +#include "TabBase.g.h" + +// fwdecl unittest classes +namespace TerminalAppLocalTests +{ + class TabTests; +}; + +namespace winrt::TerminalApp::implementation +{ + struct TabBase : TabBaseT + { + public: + virtual void Focus(winrt::Windows::UI::Xaml::FocusState focusState) = 0; + winrt::Windows::UI::Xaml::FocusState FocusState() const noexcept; + + virtual void Shutdown(); + void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch); + + void UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs); + + WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); + WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); + + // The TabViewIndex is the index this Tab object resides in TerminalPage's _tabs vector. + // This is needed since Tab is going to be managing its own SwitchToTab command. + GETSET_PROPERTY(uint32_t, TabViewIndex, 0); + // The TabViewNumTabs is the number of Tab objects in TerminalPage's _tabs vector. + GETSET_PROPERTY(uint32_t, TabViewNumTabs, 0); + + OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Title, _PropertyChangedHandlers); + OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Icon, _PropertyChangedHandlers); + OBSERVABLE_GETSET_PROPERTY(winrt::Microsoft::Terminal::Settings::Model::Command, SwitchToTabCommand, _PropertyChangedHandlers, nullptr); + GETSET_PROPERTY(winrt::Microsoft::UI::Xaml::Controls::TabViewItem, TabViewItem, nullptr); + + OBSERVABLE_GETSET_PROPERTY(winrt::Windows::UI::Xaml::FrameworkElement, Content, _PropertyChangedHandlers, nullptr); + + protected: + winrt::Windows::UI::Xaml::FocusState _focusState{ winrt::Windows::UI::Xaml::FocusState::Unfocused }; + winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeOtherTabsMenuItem{}; + winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeTabsAfterMenuItem{}; + winrt::TerminalApp::ShortcutActionDispatch _dispatch; + + virtual void _CreateContextMenu(); + winrt::Windows::UI::Xaml::Controls::MenuFlyoutSubItem _CreateCloseSubMenu(); + void _EnableCloseMenuItems(); + void _CloseTabsAfter(); + void _CloseOtherTabs(); + + friend class ::TerminalAppLocalTests::TabTests; + }; +} diff --git a/src/cascadia/TerminalApp/TabBase.idl b/src/cascadia/TerminalApp/TabBase.idl new file mode 100644 index 000000000000..040a25591205 --- /dev/null +++ b/src/cascadia/TerminalApp/TabBase.idl @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +import "ShortcutActionDispatch.idl"; + +namespace TerminalApp +{ + unsealed runtimeclass TabBase : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + String Title { get; }; + String Icon { get; }; + Microsoft.Terminal.Settings.Model.Command SwitchToTabCommand; + Microsoft.UI.Xaml.Controls.TabViewItem TabViewItem { get; }; + Windows.UI.Xaml.FrameworkElement Content { get; }; + Windows.UI.Xaml.FocusState FocusState { get; }; + + UInt32 TabViewIndex; + UInt32 TabViewNumTabs; + + overridable void Focus(Windows.UI.Xaml.FocusState focusState); + overridable void Shutdown(); + + void SetDispatch(ShortcutActionDispatch dispatch); + } +} diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index ca38ca66b136..d8dae687c4c6 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -70,6 +70,12 @@ MinMaxCloseControl.xaml + + TabBase.idl + + + TerminalTab.idl + TerminalPage.xaml Code @@ -95,9 +101,6 @@ IconPathConverter.idl - - Tab.idl - @@ -127,6 +130,12 @@ MinMaxCloseControl.xaml + + TabBase.idl + + + TerminalTab.idl + TerminalPage.xaml Code @@ -152,9 +161,6 @@ IconPathConverter.idl - - Tab.idl - @@ -197,6 +203,8 @@ MinMaxCloseControl.xaml Code + + TerminalPage.xaml Code @@ -220,7 +228,6 @@ - diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters index 33a85d5e1123..b35aba8da2d7 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters @@ -27,6 +27,10 @@ settings + + + tab + @@ -48,6 +52,10 @@ settings + + + tab + @@ -62,14 +70,16 @@ settings - - tab - - settings + + tab + + + tab + @@ -119,4 +129,4 @@ app - + \ No newline at end of file diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 8c16c74cfdb3..d61a9a9ef818 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -42,7 +42,7 @@ namespace winrt namespace winrt::TerminalApp::implementation { TerminalPage::TerminalPage() : - _tabs{ winrt::single_threaded_observable_vector() }, + _tabs{ winrt::single_threaded_observable_vector() }, _mruTabActions{ winrt::single_threaded_vector() }, _startupActions{ winrt::single_threaded_vector() } { @@ -668,8 +668,10 @@ namespace winrt::TerminalApp::implementation TermControl term{ settings, connection }; + auto newTabImpl = winrt::make_self(profileGuid, term); + _MakeSwitchToTabCommand(*newTabImpl, _tabs.Size()); + // Add the new tab to the list of our tabs. - auto newTabImpl = winrt::make_self(profileGuid, term); _tabs.Append(*newTabImpl); _mruTabActions.Append(newTabImpl->SwitchToTabCommand()); @@ -686,7 +688,8 @@ namespace winrt::TerminalApp::implementation auto weakTab = make_weak(newTabImpl); // When the tab's active pane changes, we'll want to lookup a new icon - // for it, and possibly propagate the title up to the window. + // for it. The Title change will be propagated upwards through the tab's + // PropertyChanged event handler. newTabImpl->ActivePaneChanged([weakTab, weakThis{ get_weak() }]() { auto page{ weakThis.get() }; auto tab{ weakTab.get() }; @@ -695,24 +698,12 @@ namespace winrt::TerminalApp::implementation { // Possibly update the icon of the tab. page->_UpdateTabIcon(*tab); - // Possibly update the title of the tab, window to match the newly - // focused pane. - page->_UpdateTitle(*tab); } }); - auto tabViewItem = newTabImpl->GetTabViewItem(); + auto tabViewItem = newTabImpl->TabViewItem(); _tabView.TabItems().Append(tabViewItem); - // GH#6570 - // The TabView does not apply compact sizing to items added after Compact is enabled. - // By forcibly reapplying compact sizing every time we add a new tab, we'll make sure - // that it works. - // Workaround from https://github.com/microsoft/microsoft-ui-xaml/issues/2711 - if (_tabView.TabWidthMode() == MUX::Controls::TabViewWidthMode::Compact) - { - _tabView.UpdateLayout(); - _tabView.TabWidthMode(MUX::Controls::TabViewWidthMode::Compact); - } + _ReapplyCompactTabSize(); // Set this tab's icon to the icon from the user's profile const auto profile = _settings.FindProfile(profileGuid); @@ -923,12 +914,12 @@ namespace winrt::TerminalApp::implementation // TitleChanged event. // Arguments: // - tab: the Tab to update the title for. - void TerminalPage::_UpdateTitle(const Tab& tab) + void TerminalPage::_UpdateTitle(const TerminalTab& tab) { - auto newTabTitle = tab.GetActiveTitle(); + auto newTabTitle = tab.Title(); if (_settings.GlobalSettings().ShowTitleInTitlebar() && - tab.IsFocused()) + tab.FocusState() != FocusState::Unfocused) { _titleChangeHandlers(*this, newTabTitle); } @@ -939,7 +930,7 @@ namespace winrt::TerminalApp::implementation // tab's icon to that icon. // Arguments: // - tab: the Tab to update the title for. - void TerminalPage::_UpdateTabIcon(Tab& tab) + void TerminalPage::_UpdateTabIcon(TerminalTab& tab) { const auto lastFocusedProfileOpt = tab.GetFocusedProfile(); if (lastFocusedProfileOpt.has_value()) @@ -990,30 +981,32 @@ namespace winrt::TerminalApp::implementation { if (auto index{ _GetFocusedTabIndex() }) { - try + if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index))) { - auto focusedTab = _GetStrongTabImpl(*index); - // TODO: GH#5047 - In the future, we should get the Profile of - // the focused pane, and use that to build a new instance of the - // settings so we can duplicate this tab/pane. - // - // Currently, if the profile doesn't exist anymore in our - // settings, we'll silently do nothing. - // - // In the future, it will be preferable to just duplicate the - // current control's settings, but we can't do that currently, - // because we won't be able to create a new instance of the - // connection without keeping an instance of the original Profile - // object around. - - const auto& profileGuid = focusedTab->GetFocusedProfile(); - if (profileGuid.has_value()) + try { - const auto settings{ winrt::make(_settings, profileGuid.value(), *_bindings) }; - _CreateNewTabFromSettings(profileGuid.value(), settings); + // TODO: GH#5047 - In the future, we should get the Profile of + // the focused pane, and use that to build a new instance of the + // settings so we can duplicate this tab/pane. + // + // Currently, if the profile doesn't exist anymore in our + // settings, we'll silently do nothing. + // + // In the future, it will be preferable to just duplicate the + // current control's settings, but we can't do that currently, + // because we won't be able to create a new instance of the + // connection without keeping an instance of the original Profile + // object around. + + const auto& profileGuid = terminalTab->GetFocusedProfile(); + if (profileGuid.has_value()) + { + const auto settings{ winrt::make(_settings, profileGuid.value(), *_bindings) }; + _CreateNewTabFromSettings(profileGuid.value(), settings); + } } + CATCH_LOG(); } - CATCH_LOG(); } } @@ -1040,8 +1033,8 @@ namespace winrt::TerminalApp::implementation { // Removing the tab from the collection should destroy its control and disconnect its connection, // but it doesn't always do so. The UI tree may still be holding the control and preventing its destruction. - auto tab{ _GetStrongTabImpl(tabIndex) }; - tab->Shutdown(); + auto tab{ _tabs.GetAt(tabIndex) }; + tab.Shutdown(); uint32_t mruIndex; if (_mruTabActions.IndexOf(_tabs.GetAt(tabIndex).SwitchToTabCommand(), mruIndex)) @@ -1091,8 +1084,8 @@ namespace winrt::TerminalApp::implementation // here. If we don't, then the TabView will technically not have a // selected item at all, which can make things like ClosePane not // work correctly. - auto newSelectedTab{ _GetStrongTabImpl(newSelectedIndex) }; - _tabView.SelectedItem(newSelectedTab->GetTabViewItem()); + auto newSelectedTab{ _tabs.GetAt(newSelectedIndex) }; + _tabView.SelectedItem(newSelectedTab.TabViewItem()); } // GH#5559 - If we were in the middle of a drag/drop, end it by clearing @@ -1114,7 +1107,7 @@ namespace winrt::TerminalApp::implementation // Arguments: // - term: The newly created TermControl to connect the events for // - hostingTab: The Tab that's hosting this TermControl instance - void TerminalPage::_RegisterTerminalEvents(TermControl term, Tab& hostingTab) + void TerminalPage::_RegisterTerminalEvents(TermControl term, TerminalTab& hostingTab) { // Add an event handler when the terminal's selection wants to be copied. // When the text buffer data is retrieved, we'll copy the data into the Clipboard @@ -1144,12 +1137,12 @@ namespace winrt::TerminalApp::implementation } else if (args.PropertyName() == L"Content") { - if (tab == page->_GetFocusedTab()) + if (*tab == page->_GetFocusedTab()) { page->_tabContent.Children().Clear(); page->_tabContent.Children().Append(tab->Content()); - tab->SetFocused(true); + tab->Focus(FocusState::Programmatic); } } } @@ -1160,7 +1153,7 @@ namespace winrt::TerminalApp::implementation auto page{ weakThis.get() }; auto tab{ weakTab.get() }; - if (page && tab && tab->IsFocused()) + if (page && tab && (tab->FocusState() != FocusState::Unfocused)) { page->_SetNonClientAreaColors(color); } @@ -1170,7 +1163,7 @@ namespace winrt::TerminalApp::implementation auto page{ weakThis.get() }; auto tab{ weakTab.get() }; - if (page && tab && tab->IsFocused()) + if (page && tab && (tab->FocusState() != FocusState::Unfocused)) { page->_ClearNonClientAreaColors(); } @@ -1234,8 +1227,8 @@ namespace winrt::TerminalApp::implementation { if (_startupState == StartupState::InStartup) { - auto tab{ _GetStrongTabImpl(tabIndex) }; - _tabView.SelectedItem(tab->GetTabViewItem()); + auto tab{ _tabs.GetAt(tabIndex) }; + _tabView.SelectedItem(tab.TabViewItem()); _UpdatedSelectedTab(tabIndex); } else @@ -1263,16 +1256,21 @@ namespace winrt::TerminalApp::implementation // - void TerminalPage::_UnZoomIfNeeded() { - auto activeTab = _GetFocusedTab(); - if (activeTab && activeTab->IsZoomed()) + if (auto focusedTab = _GetFocusedTab()) { - // 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(); - // In ExitZoom, we'll change the Tab's Content(), triggering the - // content changed event, which will re-attach the tab's new content - // root to the tree. - activeTab->ExitZoom(); + if (auto activeTab = _GetTerminalTabImpl(focusedTab)) + { + if (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(); + // In ExitZoom, we'll change the Tab's Content(), triggering the + // content changed event, which will re-attach the tab's new content + // root to the tree. + activeTab->ExitZoom(); + } + } } } @@ -1288,9 +1286,11 @@ namespace winrt::TerminalApp::implementation { if (auto index{ _GetFocusedTabIndex() }) { - auto focusedTab{ _GetStrongTabImpl(*index) }; - _UnZoomIfNeeded(); - focusedTab->NavigateFocus(direction); + if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index))) + { + _UnZoomIfNeeded(); + terminalTab->NavigateFocus(direction); + } } } @@ -1298,13 +1298,12 @@ namespace winrt::TerminalApp::implementation { if (auto index{ _GetFocusedTabIndex() }) { - auto focusedTab{ _GetStrongTabImpl(*index) }; - return focusedTab->GetActiveTerminalControl(); - } - else - { - return nullptr; + if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index))) + { + return terminalTab->GetActiveTerminalControl(); + } } + return nullptr; } // Method Description: @@ -1327,11 +1326,11 @@ namespace winrt::TerminalApp::implementation // Method Description: // - returns a com_ptr to the currently focused tab. This might return null, // so make sure to check the result! - winrt::com_ptr TerminalPage::_GetFocusedTab() + winrt::TerminalApp::TabBase TerminalPage::_GetFocusedTab() { if (auto index{ _GetFocusedTabIndex() }) { - return _GetStrongTabImpl(*index); + return _tabs.GetAt(*index); } return nullptr; } @@ -1356,8 +1355,8 @@ namespace winrt::TerminalApp::implementation if (auto page{ weakThis.get() }) { - auto tab{ _GetStrongTabImpl(tabIndex) }; - _tabView.SelectedItem(tab->GetTabViewItem()); + auto tabToFocus = page->_tabs.GetAt(tabIndex); + _tabView.SelectedItem(tabToFocus.TabViewItem()); } } @@ -1379,9 +1378,11 @@ namespace winrt::TerminalApp::implementation { if (auto index{ _GetFocusedTabIndex() }) { - auto focusedTab{ _GetStrongTabImpl(*index) }; - _UnZoomIfNeeded(); - focusedTab->ClosePane(); + if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index))) + { + _UnZoomIfNeeded(); + terminalTab->ClosePane(); + } } } @@ -1422,22 +1423,24 @@ namespace winrt::TerminalApp::implementation { if (auto index{ _GetFocusedTabIndex() }) { - auto focusedTab{ _GetStrongTabImpl(*index) }; - uint32_t realRowsToScroll; - if (rowsToScroll == nullptr) - { - // The magic value of WHEEL_PAGESCROLL indicates that we need to scroll the entire page - realRowsToScroll = _systemRowsToScroll == WHEEL_PAGESCROLL ? - focusedTab->GetActiveTerminalControl().GetViewHeight() : - _systemRowsToScroll; - } - else + if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index))) { - // use the custom value specified in the command - realRowsToScroll = rowsToScroll.Value(); + uint32_t realRowsToScroll; + if (rowsToScroll == nullptr) + { + // The magic value of WHEEL_PAGESCROLL indicates that we need to scroll the entire page + realRowsToScroll = _systemRowsToScroll == WHEEL_PAGESCROLL ? + terminalTab->GetActiveTerminalControl().GetViewHeight() : + _systemRowsToScroll; + } + else + { + // use the custom value specified in the command + realRowsToScroll = rowsToScroll.Value(); + } + auto scrollDelta = _ComputeScrollDelta(scrollDirection, realRowsToScroll); + terminalTab->Scroll(scrollDelta); } - auto scrollDelta = _ComputeScrollDelta(scrollDirection, realRowsToScroll); - focusedTab->Scroll(scrollDelta); } } @@ -1470,9 +1473,16 @@ namespace winrt::TerminalApp::implementation return; } + auto focusedTab = _GetTerminalTabImpl(_tabs.GetAt(*indexOpt)); + + // Do nothing if the focused tab isn't a TerminalTab + if (!focusedTab) + { + return; + } + try { - auto focusedTab = _GetStrongTabImpl(*indexOpt); TerminalApp::TerminalSettings controlSettings; GUID realGuid; bool profileFound = false; @@ -1546,9 +1556,11 @@ namespace winrt::TerminalApp::implementation { if (auto index{ _GetFocusedTabIndex() }) { - auto focusedTab{ _GetStrongTabImpl(*index) }; - _UnZoomIfNeeded(); - focusedTab->ResizePane(direction); + if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index))) + { + _UnZoomIfNeeded(); + terminalTab->ResizePane(direction); + } } } @@ -1566,11 +1578,13 @@ namespace winrt::TerminalApp::implementation return; } - const auto control = _GetActiveControl(); - const auto termHeight = control.GetViewHeight(); - auto focusedTab{ _GetStrongTabImpl(*indexOpt) }; - auto scrollDelta = _ComputeScrollDelta(scrollDirection, termHeight); - focusedTab->Scroll(scrollDelta); + if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*indexOpt))) + { + const auto control = _GetActiveControl(); + const auto termHeight = control.GetViewHeight(); + auto scrollDelta = _ComputeScrollDelta(scrollDirection, termHeight); + terminalTab->Scroll(scrollDelta); + } } // Method Description: @@ -1681,8 +1695,10 @@ namespace winrt::TerminalApp::implementation { if (auto index{ _GetFocusedTabIndex() }) { - auto focusedTab{ _GetStrongTabImpl(*index) }; - return focusedTab->CalcSnappedDimension(widthOrHeight, dimension); + if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index))) + { + return terminalTab->CalcSnappedDimension(widthOrHeight, dimension); + } } } return dimension; @@ -1960,18 +1976,17 @@ namespace winrt::TerminalApp::implementation // Unfocus all the tabs. for (auto tab : _tabs) { - auto tabImpl{ _GetStrongTabImpl(tab) }; - tabImpl->SetFocused(false); + tab.Focus(FocusState::Unfocused); } if (index >= 0) { try { - auto tab{ _GetStrongTabImpl(index) }; + auto tab{ _tabs.GetAt(index) }; _tabContent.Children().Clear(); - _tabContent.Children().Append(tab->Content()); + _tabContent.Children().Append(tab.Content()); // GH#7409: If the tab switcher is open, then we _don't_ want to // automatically focus the new tab here. The tab switcher wants @@ -1985,12 +2000,12 @@ namespace winrt::TerminalApp::implementation // need to worry about focus getting lost. if (CommandPalette().Visibility() != Visibility::Visible) { - tab->SetFocused(true); + tab.Focus(FocusState::Programmatic); _UpdateMRUTab(index); } // Raise an event that our title changed - _titleChangeHandlers(*this, tab->GetActiveTitle()); + _titleChangeHandlers(*this, tab.Title()); } CATCH_LOG(); } @@ -2025,8 +2040,10 @@ namespace winrt::TerminalApp::implementation const auto newSize = e.NewSize(); for (auto tab : _tabs) { - auto tabImpl{ _GetStrongTabImpl(tab) }; - tabImpl->ResizeContent(newSize); + if (auto terminalTab = _GetTerminalTabImpl(tab)) + { + terminalTab->ResizeContent(newSize); + } } } @@ -2097,9 +2114,10 @@ namespace winrt::TerminalApp::implementation for (auto tab : _tabs) { - // Attempt to reload the settings of any panes with this profile - auto tabImpl{ _GetStrongTabImpl(tab) }; - tabImpl->UpdateSettings(settings, profileGuid); + if (auto terminalTab = _GetTerminalTabImpl(tab)) + { + terminalTab->UpdateSettings(settings, profileGuid); + } } } CATCH_LOG(); @@ -2111,11 +2129,17 @@ namespace winrt::TerminalApp::implementation // anymore, so we can't possibly update its settings. // Update the icon of the tab for the currently focused profile in that tab. + // Only do this for TerminalTabs. Other types of tabs won't have multiple panes + // and profiles so the Title and Icon will be set once and only once on init. for (auto tab : _tabs) { - auto tabImpl{ _GetStrongTabImpl(tab) }; - _UpdateTabIcon(*tabImpl); - _UpdateTitle(*tabImpl); + if (auto terminalTab = _GetTerminalTabImpl(tab)) + { + _UpdateTabIcon(*terminalTab); + + // Force the TerminalTab to re-grab its currently active control's title. + terminalTab->UpdateTitle(); + } } auto weakThis{ get_weak() }; @@ -2255,10 +2279,9 @@ namespace winrt::TerminalApp::implementation for (const auto& tab : _tabs) { - auto tabImpl{ _GetStrongTabImpl(tab) }; - if (tabImpl->GetTabViewItem().ContextFlyout()) + if (tab.TabViewItem().ContextFlyout()) { - tabImpl->GetTabViewItem().ContextFlyout().Hide(); + tab.TabViewItem().ContextFlyout().Hide(); } } } @@ -2317,32 +2340,6 @@ namespace winrt::TerminalApp::implementation _alwaysOnTopChangedHandlers(*this, nullptr); } - // Method Description: - // - Returns a com_ptr to the implementation type of the tab at the given index - // Arguments: - // - index: an unsigned integer index to a tab in _tabs - // Return Value: - // - a com_ptr to the implementation type of the Tab - winrt::com_ptr TerminalPage::_GetStrongTabImpl(const uint32_t index) const - { - winrt::com_ptr tabImpl; - tabImpl.copy_from(winrt::get_self(_tabs.GetAt(index))); - return tabImpl; - } - - // Method Description: - // - Returns a com_ptr to the implementation type of the given projected Tab - // Arguments: - // - tab: the projected type of a Tab - // Return Value: - // - a com_ptr to the implementation type of the Tab - winrt::com_ptr TerminalPage::_GetStrongTabImpl(const ::winrt::TerminalApp::Tab& tab) const - { - winrt::com_ptr tabImpl; - tabImpl.copy_from(winrt::get_self(tab)); - return tabImpl; - } - // Method Description: // - Sets the tab split button color when a new tab color is selected // Arguments: @@ -2544,7 +2541,7 @@ namespace winrt::TerminalApp::implementation // Return focus to the active control if (auto index{ _GetFocusedTabIndex() }) { - _GetStrongTabImpl(index.value())->SetFocused(true); + _tabs.GetAt(*index).Focus(FocusState::Programmatic); _UpdateMRUTab(index.value()); } } @@ -2583,10 +2580,75 @@ namespace winrt::TerminalApp::implementation const uint32_t size = _tabs.Size(); for (uint32_t i = 0; i < size; ++i) { - _GetStrongTabImpl(i)->UpdateTabViewIndex(i, size); + auto tab{ _tabs.GetAt(i) }; + auto tabImpl{ winrt::get_self(tab) }; + tabImpl->UpdateTabViewIndex(i, size); } } + // Method Description: + // - Returns a com_ptr to the implementation type of the given tab if it's a TerminalTab. + // If the tab is not a TerminalTab, returns nullptr. + // Arguments: + // - tab: the projected type of a Tab + // Return Value: + // - If the tab is a TerminalTab, a com_ptr to the implementation type. + // If the tab is not a TerminalTab, nullptr + winrt::com_ptr TerminalPage::_GetTerminalTabImpl(const TerminalApp::TabBase& tab) const + { + if (auto terminalTab = tab.try_as()) + { + winrt::com_ptr tabImpl; + tabImpl.copy_from(winrt::get_self(terminalTab)); + return tabImpl; + } + else + { + return nullptr; + } + } + + // Method Description: + // - The TabView does not apply compact sizing to items added after Compact is enabled. + // By forcibly reapplying compact sizing every time we add a new tab, we'll make sure + // that it works. + // Workaround from https://github.com/microsoft/microsoft-ui-xaml/issues/2711 + // TODO: Remove this function and its calls when ingesting the above changes. + // Arguments: + // - + // Return Value: + // - + void TerminalPage::_ReapplyCompactTabSize() + { + if (_tabView.TabWidthMode() == MUX::Controls::TabViewWidthMode::Compact) + { + _tabView.UpdateLayout(); + _tabView.TabWidthMode(MUX::Controls::TabViewWidthMode::Compact); + } + } + + // Method Description: + // - Initializes a SwitchToTab command object for this Tab instance. + // This should be done before the tab is added to the _tabs vector so that + // controls like the CmdPal that observe the vector changes can always expect + // a SwitchToTab command to be available. + // Arguments: + // - + // Return Value: + // - + void TerminalPage::_MakeSwitchToTabCommand(const TerminalApp::TabBase& tab, const uint32_t index) + { + SwitchToTabArgs args{ index }; + ActionAndArgs focusTabAction{ ShortcutAction::SwitchToTab, args }; + + Command command; + command.Action(focusTabAction); + command.Name(tab.Title()); + command.Icon(tab.Icon()); + + tab.SwitchToTabCommand(command); + } + // Method Description: // - Computes the delta for scrolling the tab's viewport. // Arguments: diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 7d29e77c928a..97d87d4f78ca 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -4,7 +4,7 @@ #pragma once #include "TerminalPage.g.h" -#include "Tab.h" +#include "TerminalTab.h" #include "AppKeyBindings.h" #include "TerminalSettings.h" @@ -96,10 +96,10 @@ namespace winrt::TerminalApp::implementation Microsoft::Terminal::Settings::Model::CascadiaSettings _settings{ nullptr }; - Windows::Foundation::Collections::IObservableVector _tabs; + Windows::Foundation::Collections::IObservableVector _tabs; Windows::Foundation::Collections::IVector _mruTabActions; - winrt::com_ptr _GetStrongTabImpl(const uint32_t index) const; - winrt::com_ptr _GetStrongTabImpl(const ::winrt::TerminalApp::Tab& tab) const; + winrt::com_ptr _GetTerminalTabImpl(const TerminalApp::TabBase& tab) const; + void _UpdateTabIndices(); bool _isInFocusMode{ false }; @@ -146,8 +146,8 @@ namespace winrt::TerminalApp::implementation void _HookupKeyBindings(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap) noexcept; void _RegisterActionCallbacks(); - void _UpdateTitle(const Tab& tab); - void _UpdateTabIcon(Tab& tab); + void _UpdateTitle(const TerminalTab& tab); + void _UpdateTabIcon(TerminalTab& tab); void _UpdateTabView(); void _UpdateTabWidthMode(); void _UpdateCommandsForPalette(); @@ -159,7 +159,7 @@ namespace winrt::TerminalApp::implementation void _RemoveTabViewItem(const Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem); void _RemoveTabViewItemByIndex(uint32_t tabIndex); - void _RegisterTerminalEvents(Microsoft::Terminal::TerminalControl::TermControl term, Tab& hostingTab); + void _RegisterTerminalEvents(Microsoft::Terminal::TerminalControl::TermControl term, TerminalTab& hostingTab); void _SelectNextTab(const bool bMoveRight); bool _SelectTab(const uint32_t tabIndex); @@ -167,7 +167,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::TerminalControl::TermControl _GetActiveControl(); std::optional _GetFocusedTabIndex() const noexcept; - winrt::com_ptr _GetFocusedTab(); + TerminalApp::TabBase _GetFocusedTab(); winrt::fire_and_forget _SetFocusedTabIndex(const uint32_t tabIndex); void _CloseFocusedTab(); void _CloseFocusedPane(); @@ -218,6 +218,12 @@ namespace winrt::TerminalApp::implementation void _UnZoomIfNeeded(); + void _OpenSettingsUI(); + + void _ReapplyCompactTabSize(); + + void _MakeSwitchToTabCommand(const TerminalApp::TabBase& tab, const uint32_t index); + static int _ComputeScrollDelta(ScrollDirection scrollDirection, const uint32_t rowsToScroll); static uint32_t _ReadSystemRowsToScroll(); diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp similarity index 74% rename from src/cascadia/TerminalApp/Tab.cpp rename to src/cascadia/TerminalApp/TerminalTab.cpp index 6ca57fa902dc..567d48a7ba14 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/TerminalTab.cpp @@ -4,8 +4,8 @@ #include "pch.h" #include #include "ColorPickupFlyout.h" -#include "Tab.h" -#include "Tab.g.cpp" +#include "TerminalTab.h" +#include "TerminalTab.g.cpp" #include "Utils.h" #include "ColorHelper.h" @@ -24,7 +24,7 @@ namespace winrt namespace winrt::TerminalApp::implementation { - Tab::Tab(const GUID& profile, const TermControl& control) + TerminalTab::TerminalTab(const GUID& profile, const TermControl& control) { _rootPane = std::make_shared(profile, control, true); @@ -36,7 +36,6 @@ namespace winrt::TerminalApp::implementation Content(_rootPane->GetRootElement()); _MakeTabViewItem(); - _MakeSwitchToTabCommand(); _CreateContextMenu(); } @@ -46,18 +45,18 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void Tab::_MakeTabViewItem() + void TerminalTab::_MakeTabViewItem() { - _tabViewItem = ::winrt::MUX::Controls::TabViewItem{}; + TabViewItem(::winrt::MUX::Controls::TabViewItem{}); - _tabViewItem.DoubleTapped([weakThis = get_weak()](auto&& /*s*/, auto&& /*e*/) { + TabViewItem().DoubleTapped([weakThis = get_weak()](auto&& /*s*/, auto&& /*e*/) { if (auto tab{ weakThis.get() }) { tab->ActivateTabRenamer(); } }); - _UpdateTitle(); + UpdateTitle(); _RecalculateAndApplyTabColor(); } @@ -72,22 +71,11 @@ namespace winrt::TerminalApp::implementation // Return Value: // - nullptr if no children were marked `_lastFocused`, else the TermControl // that was last focused. - TermControl Tab::GetActiveTerminalControl() const + TermControl TerminalTab::GetActiveTerminalControl() const { return _activePane->GetTerminalControl(); } - // Method Description: - // - Gets the TabViewItem that represents this Tab - // Arguments: - // - - // Return Value: - // - The TabViewItem that represents this Tab - winrt::MUX::Controls::TabViewItem Tab::GetTabViewItem() - { - return _tabViewItem; - } - // Method Description: // - Called after construction of a Tab object to bind event handlers to its // associated Pane and TermControl object @@ -95,39 +83,29 @@ namespace winrt::TerminalApp::implementation // - control: reference to the TermControl object to bind event to // Return Value: // - - void Tab::Initialize(const TermControl& control) + void TerminalTab::Initialize(const TermControl& control) { _BindEventHandlers(control); } - // Method Description: - // - Returns true if this is the currently focused tab. For any set of tabs, - // there should only be one tab that is marked as focused, though each tab has - // no control over the other tabs in the set. - // Arguments: - // - - // Return Value: - // - true iff this tab is focused. - bool Tab::IsFocused() const noexcept - { - return _focused; - } - // Method Description: // - Updates our focus state. If we're gaining focus, make sure to transfer // focus to the last focused terminal control in our tree of controls. // Arguments: - // - focused: our new focus state. If true, we should be focused. If false, we - // should be unfocused. + // - focused: our new focus state // Return Value: // - - void Tab::SetFocused(const bool focused) + void TerminalTab::Focus(WUX::FocusState focusState) { - _focused = focused; + _focusState = focusState; - if (_focused) + if (_focusState != FocusState::Unfocused) { - _Focus(); + auto lastFocusedControl = GetActiveTerminalControl(); + if (lastFocusedControl) + { + lastFocusedControl.Focus(_focusState); + } } } @@ -140,7 +118,7 @@ namespace winrt::TerminalApp::implementation // Return Value: // - nullopt if no children of this tab were the last control to be // focused, else the GUID of the profile of the last control to be focused - std::optional Tab::GetFocusedProfile() const noexcept + std::optional TerminalTab::GetFocusedProfile() const noexcept { return _activePane->GetFocusedProfile(); } @@ -152,7 +130,7 @@ namespace winrt::TerminalApp::implementation // - control: reference to the TermControl object to bind event to // Return Value: // - - void Tab::_BindEventHandlers(const TermControl& control) noexcept + void TerminalTab::_BindEventHandlers(const TermControl& control) noexcept { _AttachEventHandlersToPane(_rootPane); _AttachEventHandlersToControl(control); @@ -165,35 +143,18 @@ namespace winrt::TerminalApp::implementation // - profile: The GUID of the profile these settings should apply to. // Return Value: // - - void Tab::UpdateSettings(const TerminalSettings& settings, const GUID& profile) + void TerminalTab::UpdateSettings(const TerminalSettings& settings, const GUID& profile) { _rootPane->UpdateSettings(settings, profile); } - // Method Description: - // - Focus the last focused control in our tree of panes. - // Arguments: - // - - // Return Value: - // - - void Tab::_Focus() - { - _focused = true; - - auto lastFocusedControl = GetActiveTerminalControl(); - if (lastFocusedControl) - { - lastFocusedControl.Focus(FocusState::Programmatic); - } - } - // Method Description: // - Set the icon on the TabViewItem for this tab. // Arguments: // - iconPath: The new path string to use as the IconPath for our TabViewItem // Return Value: // - - winrt::fire_and_forget Tab::UpdateIcon(const winrt::hstring iconPath) + winrt::fire_and_forget TerminalTab::UpdateIcon(const winrt::hstring iconPath) { // Don't reload our icon if it hasn't changed. if (iconPath == _lastIconPath) @@ -205,13 +166,13 @@ namespace winrt::TerminalApp::implementation auto weakThis{ get_weak() }; - co_await winrt::resume_foreground(_tabViewItem.Dispatcher()); + co_await winrt::resume_foreground(TabViewItem().Dispatcher()); if (auto tab{ weakThis.get() }) { // The TabViewItem Icon needs MUX while the IconSourceElement in the CommandPalette needs WUX... - IconSource(IconPathConverter::IconSourceWUX(_lastIconPath)); - _tabViewItem.IconSource(IconPathConverter::IconSourceMUX(_lastIconPath)); + Icon(_lastIconPath); + TabViewItem().IconSource(IconPathConverter::IconSourceMUX(_lastIconPath)); // Update SwitchToTab command's icon SwitchToTabCommand().Icon(_lastIconPath); @@ -225,7 +186,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - the title string of the last focused terminal control in our tree. - winrt::hstring Tab::GetActiveTitle() const + winrt::hstring TerminalTab::_GetActiveTitle() const { if (!_runtimeTabText.empty()) { @@ -243,14 +204,14 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - winrt::fire_and_forget Tab::_UpdateTitle() + winrt::fire_and_forget TerminalTab::UpdateTitle() { auto weakThis{ get_weak() }; - co_await winrt::resume_foreground(_tabViewItem.Dispatcher()); + co_await winrt::resume_foreground(TabViewItem().Dispatcher()); if (auto tab{ weakThis.get() }) { // Bubble our current tab text to anyone who's listening for changes. - Title(GetActiveTitle()); + Title(_GetActiveTitle()); // Update SwitchToTab command's name SwitchToTabCommand().Name(Title()); @@ -268,7 +229,7 @@ namespace winrt::TerminalApp::implementation // - delta: a number of lines to move the viewport relative to the current viewport. // Return Value: // - - winrt::fire_and_forget Tab::Scroll(const int delta) + winrt::fire_and_forget TerminalTab::Scroll(const int delta) { auto control = GetActiveTerminalControl(); @@ -284,7 +245,7 @@ namespace winrt::TerminalApp::implementation // - splitType: The type of split we want to create. // Return Value: // - True if the focused pane can be split. False otherwise. - bool Tab::CanSplitPane(SplitState splitType) + bool TerminalTab::CanSplitPane(SplitState splitType) { return _activePane->CanSplit(splitType); } @@ -298,7 +259,7 @@ namespace winrt::TerminalApp::implementation // - control: A TermControl to use in the new pane. // Return Value: // - - void Tab::SplitPane(SplitState splitType, const GUID& profile, TermControl& control) + void TerminalTab::SplitPane(SplitState splitType, const GUID& profile, TermControl& control) { auto [first, second] = _activePane->Split(splitType, profile, control); _activePane = first; @@ -318,7 +279,7 @@ namespace winrt::TerminalApp::implementation // Method Description: // - See Pane::CalcSnappedDimension - float Tab::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const + float TerminalTab::CalcSnappedDimension(const bool widthOrHeight, const float dimension) const { return _rootPane->CalcSnappedDimension(widthOrHeight, dimension); } @@ -330,7 +291,7 @@ namespace winrt::TerminalApp::implementation // - newSize: the amount of space that the panes have to fill now. // Return Value: // - - void Tab::ResizeContent(const winrt::Windows::Foundation::Size& newSize) + void TerminalTab::ResizeContent(const winrt::Windows::Foundation::Size& newSize) { // NOTE: This _must_ be called on the root pane, so that it can propagate // throughout the entire tree. @@ -344,7 +305,7 @@ namespace winrt::TerminalApp::implementation // - direction: The direction to move the separator in. // Return Value: // - - void Tab::ResizePane(const Direction& direction) + void TerminalTab::ResizePane(const Direction& direction) { // NOTE: This _must_ be called on the root pane, so that it can propagate // throughout the entire tree. @@ -358,7 +319,7 @@ namespace winrt::TerminalApp::implementation // - direction: The direction to move the focus in. // Return Value: // - - void Tab::NavigateFocus(const Direction& direction) + void TerminalTab::NavigateFocus(const Direction& direction) { // NOTE: This _must_ be called on the root pane, so that it can propagate // throughout the entire tree. @@ -367,7 +328,7 @@ namespace winrt::TerminalApp::implementation // Method Description: // - Prepares this tab for being removed from the UI hierarchy by shutting down all active connections. - void Tab::Shutdown() + void TerminalTab::Shutdown() { _rootPane->Shutdown(); } @@ -380,21 +341,21 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void Tab::ClosePane() + void TerminalTab::ClosePane() { _activePane->Close(); } - void Tab::SetTabText(winrt::hstring title) + void TerminalTab::SetTabText(winrt::hstring title) { _runtimeTabText = title; - _UpdateTitle(); + UpdateTitle(); } - void Tab::ResetTabText() + void TerminalTab::ResetTabText() { _runtimeTabText = L""; - _UpdateTitle(); + UpdateTitle(); } // Method Description: @@ -404,7 +365,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void Tab::ActivateTabRenamer() + void TerminalTab::ActivateTabRenamer() { _inRename = true; _receivedKeyDown = false; @@ -421,7 +382,7 @@ namespace winrt::TerminalApp::implementation // - control: the TermControl to add events to. // Return Value: // - - void Tab::_AttachEventHandlersToControl(const TermControl& control) + void TerminalTab::_AttachEventHandlersToControl(const TermControl& control) { auto weakThis{ get_weak() }; @@ -431,7 +392,7 @@ namespace winrt::TerminalApp::implementation { // The title of the control changed, but not necessarily the title of the tab. // Set the tab's text to the active panes' text. - tab->_UpdateTitle(); + tab->UpdateTitle(); } }); @@ -468,7 +429,7 @@ namespace winrt::TerminalApp::implementation // - pane: a Pane to mark as active. // Return Value: // - - void Tab::_UpdateActivePane(std::shared_ptr pane) + void TerminalTab::_UpdateActivePane(std::shared_ptr pane) { // Clear the active state of the entire tree, and mark only the pane as active. _rootPane->ClearActive(); @@ -476,7 +437,7 @@ namespace winrt::TerminalApp::implementation _activePane->SetActive(); // Update our own title text to match the newly-active pane. - _UpdateTitle(); + UpdateTitle(); // Raise our own ActivePaneChanged event. _ActivePaneChangedHandlers(); @@ -491,7 +452,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void Tab::_AttachEventHandlersToPane(std::shared_ptr pane) + void TerminalTab::_AttachEventHandlersToPane(std::shared_ptr pane) { auto weakThis{ get_weak() }; @@ -531,7 +492,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void Tab::_CreateContextMenu() + void TerminalTab::_CreateContextMenu() { auto weakThis{ get_weak() }; @@ -605,57 +566,7 @@ namespace winrt::TerminalApp::implementation newTabFlyout.Items().Append(menuSeparator); newTabFlyout.Items().Append(_CreateCloseSubMenu()); newTabFlyout.Items().Append(closeTabMenuItem); - _tabViewItem.ContextFlyout(newTabFlyout); - } - - // Method Description: - // - Creates a sub-menu containing menu items to close multiple tabs - // Arguments: - // - - // Return Value: - // - the created MenuFlyoutSubItem - Controls::MenuFlyoutSubItem Tab::_CreateCloseSubMenu() - { - auto weakThis{ get_weak() }; - - // Close tabs after - _closeTabsAfterMenuItem.Click([weakThis](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - tab->_CloseTabsAfter(); - } - }); - _closeTabsAfterMenuItem.Text(RS_(L"TabCloseAfter")); - - // Close other tabs - _closeOtherTabsMenuItem.Click([weakThis](auto&&, auto&&) { - if (auto tab{ weakThis.get() }) - { - tab->_CloseOtherTabs(); - } - }); - _closeOtherTabsMenuItem.Text(RS_(L"TabCloseOther")); - - Controls::MenuFlyoutSubItem closeSubMenu; - closeSubMenu.Text(RS_(L"TabCloseSubMenu")); - closeSubMenu.Items().Append(_closeTabsAfterMenuItem); - closeSubMenu.Items().Append(_closeOtherTabsMenuItem); - - return closeSubMenu; - } - - // Method Description: - // - Enable the Close menu items based on tab index and total number of tabs - // Arguments: - // - - // Return Value: - // - - void Tab::_EnableCloseMenuItems() - { - // close other tabs is enabled only if there are other tabs - _closeOtherTabsMenuItem.IsEnabled(TabViewNumTabs() > 1); - // close tabs after is enabled only if there are other tabs on the right - _closeTabsAfterMenuItem.IsEnabled(TabViewIndex() < TabViewNumTabs() - 1); + TabViewItem().ContextFlyout(newTabFlyout); } // Method Description: @@ -670,9 +581,9 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void Tab::_UpdateTabHeader() + void TerminalTab::_UpdateTabHeader() { - winrt::hstring tabText{ GetActiveTitle() }; + winrt::hstring tabText{ Title() }; if (!_inRename) { @@ -690,13 +601,13 @@ namespace winrt::TerminalApp::implementation tb.Text(tabText); sp.Children().Append(tb); - _tabViewItem.Header(sp); + 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)); + TabViewItem().Header(winrt::box_value(tabText)); } } else @@ -713,9 +624,9 @@ namespace winrt::TerminalApp::implementation // - tabText: This should be the text to initialize the rename text box with. // Return Value: // - - void Tab::_ConstructTabRenameBox(const winrt::hstring& tabText) + void TerminalTab::_ConstructTabRenameBox(const winrt::hstring& tabText) { - if (_tabViewItem.Header().try_as()) + if (TabViewItem().Header().try_as()) { return; } @@ -763,7 +674,7 @@ namespace winrt::TerminalApp::implementation { tab->_runtimeTabText = textBox.Text(); tab->_inRename = false; - tab->_UpdateTitle(); + tab->UpdateTitle(); } }); @@ -793,7 +704,7 @@ namespace winrt::TerminalApp::implementation e.Handled(true); textBox.Text(tab->_runtimeTabText); tab->_inRename = false; - tab->_UpdateTitle(); + tab->UpdateTitle(); break; } } @@ -803,7 +714,7 @@ namespace winrt::TerminalApp::implementation _tabRenameBoxLayoutUpdatedRevoker = tabTextBox.LayoutUpdated(winrt::auto_revoke, [this](auto&&, auto&&) { // Curiously, the sender for this event is null, so we have to // get the TextBox from the Tab's Header(). - auto textBox{ _tabViewItem.Header().try_as() }; + auto textBox{ TabViewItem().Header().try_as() }; if (textBox) { textBox.SelectAll(); @@ -813,7 +724,7 @@ namespace winrt::TerminalApp::implementation _tabRenameBoxLayoutUpdatedRevoker.revoke(); }); - _tabViewItem.Header(tabTextBox); + TabViewItem().Header(tabTextBox); } // Method Description: @@ -822,7 +733,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - The tab's color, if any - std::optional Tab::GetTabColor() + std::optional TerminalTab::GetTabColor() { const auto currControlColor{ GetActiveTerminalControl().TabColor() }; std::optional controlTabColor; @@ -859,7 +770,7 @@ namespace winrt::TerminalApp::implementation // - color: the color the user picked for their tab // Return Value: // - - void Tab::SetRuntimeTabColor(const winrt::Windows::UI::Color& color) + void TerminalTab::SetRuntimeTabColor(const winrt::Windows::UI::Color& color) { _runtimeTabColor.emplace(color); _RecalculateAndApplyTabColor(); @@ -874,11 +785,11 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void Tab::_RecalculateAndApplyTabColor() + void TerminalTab::_RecalculateAndApplyTabColor() { auto weakThis{ get_weak() }; - _tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis]() { + TabViewItem().Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis]() { auto ptrTab = weakThis.get(); if (!ptrTab) return; @@ -906,7 +817,7 @@ namespace winrt::TerminalApp::implementation // - color: the color the user picked for their tab // Return Value: // - - void Tab::_ApplyTabColor(const winrt::Windows::UI::Color& color) + void TerminalTab::_ApplyTabColor(const winrt::Windows::UI::Color& color) { Media::SolidColorBrush selectedTabBrush{}; Media::SolidColorBrush deselectedTabBrush{}; @@ -935,15 +846,15 @@ namespace winrt::TerminalApp::implementation // currently if a tab has a custom color, a deselected state is // signified by using the same color with a bit ot transparency - _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush); - _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush); - _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), hoverTabBrush); - _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush); - _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForeground"), fontBrush); - _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush); - _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), fontBrush); - _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPressed"), fontBrush); - _tabViewItem.Resources().Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush); + TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush); + TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush); + TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), hoverTabBrush); + TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush); + TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForeground"), fontBrush); + TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush); + TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), fontBrush); + TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPressed"), fontBrush); + TabViewItem().Resources().Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush); _RefreshVisualState(); @@ -958,7 +869,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void Tab::ResetRuntimeTabColor() + void TerminalTab::ResetRuntimeTabColor() { _runtimeTabColor.reset(); _RecalculateAndApplyTabColor(); @@ -971,7 +882,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void Tab::_ClearTabBackgroundColor() + void TerminalTab::_ClearTabBackgroundColor() { winrt::hstring keys[] = { L"TabViewItemHeaderBackground", @@ -989,9 +900,9 @@ namespace winrt::TerminalApp::implementation for (auto keyString : keys) { auto key = winrt::box_value(keyString); - if (_tabViewItem.Resources().HasKey(key)) + if (TabViewItem().Resources().HasKey(key)) { - _tabViewItem.Resources().Remove(key); + TabViewItem().Resources().Remove(key); } } @@ -1005,9 +916,9 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void Tab::ActivateColorPicker() + void TerminalTab::ActivateColorPicker() { - _tabColorPickup.ShowAt(_tabViewItem); + _tabColorPickup.ShowAt(TabViewItem()); } // Method Description: @@ -1017,17 +928,17 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void Tab::_RefreshVisualState() + void TerminalTab::_RefreshVisualState() { - if (_focused) + if (_focusState != FocusState::Unfocused) { - VisualStateManager::GoToState(_tabViewItem, L"Normal", true); - VisualStateManager::GoToState(_tabViewItem, L"Selected", true); + VisualStateManager::GoToState(TabViewItem(), L"Normal", true); + VisualStateManager::GoToState(TabViewItem(), L"Selected", true); } else { - VisualStateManager::GoToState(_tabViewItem, L"Selected", true); - VisualStateManager::GoToState(_tabViewItem, L"Normal", true); + VisualStateManager::GoToState(TabViewItem(), L"Selected", true); + VisualStateManager::GoToState(TabViewItem(), L"Normal", true); } } @@ -1037,7 +948,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - The total number of leaf panes hosted by this tab. - int Tab::GetLeafPaneCount() const noexcept + int TerminalTab::GetLeafPaneCount() const noexcept { return _rootPane->GetLeafPaneCount(); } @@ -1052,12 +963,12 @@ namespace winrt::TerminalApp::implementation // Return Value: // - The SplitState that we should use for an `Automatic` split given // `availableSpace` - SplitState Tab::PreCalculateAutoSplit(winrt::Windows::Foundation::Size availableSpace) const + SplitState TerminalTab::PreCalculateAutoSplit(winrt::Windows::Foundation::Size availableSpace) const { return _rootPane->PreCalculateAutoSplit(_activePane, availableSpace).value_or(SplitState::Vertical); } - bool Tab::PreCalculateCanSplit(SplitState splitType, winrt::Windows::Foundation::Size availableSpace) const + bool TerminalTab::PreCalculateCanSplit(SplitState splitType, winrt::Windows::Foundation::Size availableSpace) const { return _rootPane->PreCalculateCanSplit(_activePane, splitType, availableSpace).value_or(false); } @@ -1066,13 +977,13 @@ namespace winrt::TerminalApp::implementation // - 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. + // Tab::GetTabContent, 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() + void TerminalTab::ToggleZoom() { if (_zoomedPane) { @@ -1083,7 +994,7 @@ namespace winrt::TerminalApp::implementation EnterZoom(); } } - void Tab::EnterZoom() + void TerminalTab::EnterZoom() { _zoomedPane = _activePane; _rootPane->Maximize(_zoomedPane); @@ -1091,7 +1002,7 @@ namespace winrt::TerminalApp::implementation _UpdateTabHeader(); Content(_zoomedPane->GetRootElement()); } - void Tab::ExitZoom() + void TerminalTab::ExitZoom() { _rootPane->Restore(_zoomedPane); _zoomedPane = nullptr; @@ -1100,60 +1011,12 @@ namespace winrt::TerminalApp::implementation Content(_rootPane->GetRootElement()); } - bool Tab::IsZoomed() + bool TerminalTab::IsZoomed() { return _zoomedPane != nullptr; } - // Method Description: - // - Initializes a SwitchToTab command object for this Tab instance. - // Arguments: - // - - // Return Value: - // - - void Tab::_MakeSwitchToTabCommand() - { - SwitchToTabArgs args{ _TabViewIndex }; - ActionAndArgs focusTabAction{ ShortcutAction::SwitchToTab, args }; - - Command command; - command.Action(focusTabAction); - command.Name(Title()); - command.Icon(_lastIconPath); - - SwitchToTabCommand(command); - } - - void Tab::_CloseTabsAfter() - { - CloseTabsAfterArgs args{ _TabViewIndex }; - ActionAndArgs closeTabsAfter{ ShortcutAction::CloseTabsAfter, args }; - - _dispatch.DoAction(closeTabsAfter); - } - - void Tab::_CloseOtherTabs() - { - CloseOtherTabsArgs args{ _TabViewIndex }; - ActionAndArgs closeOtherTabs{ ShortcutAction::CloseOtherTabs, args }; - - _dispatch.DoAction(closeOtherTabs); - } - - void Tab::UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs) - { - TabViewIndex(idx); - TabViewNumTabs(numTabs); - _EnableCloseMenuItems(); - SwitchToTabCommand().Action().Args().as().TabIndex(idx); - } - - void Tab::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch) - { - _dispatch = dispatch; - } - - DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>); - DEFINE_EVENT(Tab, ColorSelected, _colorSelected, winrt::delegate); - DEFINE_EVENT(Tab, ColorCleared, _colorCleared, winrt::delegate<>); + DEFINE_EVENT(TerminalTab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>); + DEFINE_EVENT(TerminalTab, ColorSelected, _colorSelected, winrt::delegate); + DEFINE_EVENT(TerminalTab, ColorCleared, _colorCleared, winrt::delegate<>); } diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/TerminalTab.h similarity index 64% rename from src/cascadia/TerminalApp/Tab.h rename to src/cascadia/TerminalApp/TerminalTab.h index 1eb4f85a9e37..7144a961fa46 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/TerminalTab.h @@ -4,7 +4,8 @@ #pragma once #include "Pane.h" #include "ColorPickupFlyout.h" -#include "Tab.g.h" +#include "TabBase.h" +#include "TerminalTab.g.h" // fwdecl unittest classes namespace TerminalAppLocalTests @@ -14,21 +15,18 @@ namespace TerminalAppLocalTests namespace winrt::TerminalApp::implementation { - struct Tab : public TabT + struct TerminalTab : TerminalTabT { public: - Tab() = delete; - Tab(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); + TerminalTab(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); // Called after construction to perform the necessary setup, which relies on weak_ptr void Initialize(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); - winrt::Microsoft::UI::Xaml::Controls::TabViewItem GetTabViewItem(); winrt::Microsoft::Terminal::TerminalControl::TermControl GetActiveTerminalControl() const; std::optional GetFocusedProfile() const noexcept; - bool IsFocused() const noexcept; - void SetFocused(const bool focused); + void Focus(winrt::Windows::UI::Xaml::FocusState focusState) override; winrt::fire_and_forget Scroll(const int delta); @@ -46,9 +44,9 @@ namespace winrt::TerminalApp::implementation void NavigateFocus(const winrt::Microsoft::Terminal::Settings::Model::Direction& direction); void UpdateSettings(const winrt::TerminalApp::TerminalSettings& settings, const GUID& profile); - winrt::hstring GetActiveTitle() const; + winrt::fire_and_forget UpdateTitle(); - void Shutdown(); + void Shutdown() override; void ClosePane(); void SetTabText(winrt::hstring title); @@ -68,28 +66,10 @@ namespace winrt::TerminalApp::implementation int GetLeafPaneCount() const noexcept; - void UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs); - - void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch); - - WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); - WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>); DECLARE_EVENT(ColorSelected, _colorSelected, winrt::delegate); DECLARE_EVENT(ColorCleared, _colorCleared, winrt::delegate<>); - OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Title, _PropertyChangedHandlers); - OBSERVABLE_GETSET_PROPERTY(winrt::Windows::UI::Xaml::Controls::IconSource, IconSource, _PropertyChangedHandlers, nullptr); - OBSERVABLE_GETSET_PROPERTY(winrt::Microsoft::Terminal::Settings::Model::Command, SwitchToTabCommand, _PropertyChangedHandlers, nullptr); - - // The TabViewIndex is the index this Tab object resides in TerminalPage's _tabs vector. - // This is needed since Tab is going to be managing its own SwitchToTab command. - OBSERVABLE_GETSET_PROPERTY(uint32_t, TabViewIndex, _PropertyChangedHandlers, 0); - // The TabViewNumTabs is the number of Tab objects in TerminalPage's _tabs vector. - OBSERVABLE_GETSET_PROPERTY(uint32_t, TabViewNumTabs, _PropertyChangedHandlers, 0); - - OBSERVABLE_GETSET_PROPERTY(winrt::Windows::UI::Xaml::UIElement, Content, _PropertyChangedHandlers, nullptr); - private: std::shared_ptr _rootPane{ nullptr }; std::shared_ptr _activePane{ nullptr }; @@ -101,9 +81,7 @@ namespace winrt::TerminalApp::implementation winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeOtherTabsMenuItem{}; winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeTabsAfterMenuItem{}; - bool _focused{ false }; bool _receivedKeyDown{ false }; - winrt::Microsoft::UI::Xaml::Controls::TabViewItem _tabViewItem{ nullptr }; winrt::hstring _runtimeTabText{}; bool _inRename{ false }; @@ -112,11 +90,8 @@ namespace winrt::TerminalApp::implementation winrt::TerminalApp::ShortcutActionDispatch _dispatch; void _MakeTabViewItem(); - void _Focus(); - void _CreateContextMenu(); - winrt::Windows::UI::Xaml::Controls::MenuFlyoutSubItem _CreateCloseSubMenu(); - void _EnableCloseMenuItems(); + void _CreateContextMenu() override; void _RefreshVisualState(); @@ -127,19 +102,14 @@ namespace winrt::TerminalApp::implementation void _UpdateActivePane(std::shared_ptr pane); + winrt::hstring _GetActiveTitle() const; void _UpdateTabHeader(); - winrt::fire_and_forget _UpdateTitle(); void _ConstructTabRenameBox(const winrt::hstring& tabText); void _RecalculateAndApplyTabColor(); void _ApplyTabColor(const winrt::Windows::UI::Color& color); void _ClearTabBackgroundColor(); - void _MakeSwitchToTabCommand(); - - void _CloseTabsAfter(); - void _CloseOtherTabs(); - friend class ::TerminalAppLocalTests::TabTests; }; } diff --git a/src/cascadia/TerminalApp/TerminalTab.idl b/src/cascadia/TerminalApp/TerminalTab.idl new file mode 100644 index 000000000000..7485acbf27da --- /dev/null +++ b/src/cascadia/TerminalApp/TerminalTab.idl @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "TabBase.idl"; + +namespace TerminalApp +{ + [default_interface] runtimeclass TerminalTab : TabBase + { + } +} diff --git a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj index 003a977aae69..a85b1a9ae233 100644 --- a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj +++ b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj @@ -30,7 +30,8 @@ - + + diff --git a/src/cascadia/inc/cppwinrt_utils.h b/src/cascadia/inc/cppwinrt_utils.h index a1ac58fb80ab..e20dcca82db2 100644 --- a/src/cascadia/inc/cppwinrt_utils.h +++ b/src/cascadia/inc/cppwinrt_utils.h @@ -82,7 +82,7 @@ public: winrt::event_token name(args const& handler) { return _##name##Handlers.add(handler); } \ void name(winrt::event_token const& token) { _##name##Handlers.remove(token); } \ \ -private: \ +protected: \ winrt::event _##name##Handlers; // This is a helper macro for both declaring the signature and body of an event @@ -128,7 +128,7 @@ private: // (like when the class is being initialized). #define OBSERVABLE_GETSET_PROPERTY(type, name, event, ...) \ public: \ - type name() { return _##name; }; \ + type name() const noexcept { return _##name; }; \ void name(const type& value) \ { \ if (_##name != value) \