diff --git a/.github/actions/spell-check/dictionary/apis.txt b/.github/actions/spell-check/dictionary/apis.txt index 969b90c3460..092ed76889f 100644 --- a/.github/actions/spell-check/dictionary/apis.txt +++ b/.github/actions/spell-check/dictionary/apis.txt @@ -24,6 +24,7 @@ IExplorer IMap IObject IStorage +ITab llabs LCID lround diff --git a/OpenConsole.sln b/OpenConsole.sln index c033e78249d..54f0b9c95f5 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -2010,7 +2010,8 @@ Global {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.AuditMode|x86.Build.0 = Release|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.AuditMode|x86.Deploy.0 = Release|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Debug|ARM64.ActiveCfg = Debug|Win32 + {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Debug|ARM64.Build.0 = Debug|ARM64 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Debug|x64.ActiveCfg = Debug|x64 @@ -2020,7 +2021,8 @@ Global {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Debug|x86.Build.0 = Debug|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Debug|x86.Deploy.0 = Debug|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Release|Any CPU.ActiveCfg = Release|Win32 - {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Release|ARM64.ActiveCfg = Release|Win32 + {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Release|ARM64.ActiveCfg = Release|ARM64 + {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Release|ARM64.Build.0 = Release|ARM64 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Release|DotNet_x64Test.ActiveCfg = Release|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Release|DotNet_x86Test.ActiveCfg = Release|Win32 {CA5CAD1A-0B5E-45C3-96A8-BB496BFE4E32}.Release|x64.ActiveCfg = Release|x64 diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp index 4b98b1a9552..9707269e828 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" #include "JsonTestClass.h" @@ -246,8 +246,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); @@ -469,7 +469,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); @@ -479,7 +479,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); @@ -497,7 +497,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."); diff --git a/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj b/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj index 67952867d7d..eded639c75e 100644 --- a/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj +++ b/src/cascadia/LocalTests_TerminalApp/TerminalApp.LocalTests.vcxproj @@ -79,6 +79,7 @@ + diff --git a/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj b/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj index b6d85e9ae04..b9c3b869689 100644 --- a/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj +++ b/src/cascadia/LocalTests_TerminalApp/TestHostApp/TestHostApp.vcxproj @@ -94,6 +94,8 @@ {CA5CAD1A-C46D-4588-B1C0-40F31AE9100B} + + diff --git a/src/cascadia/TerminalApp/ActionArgs.idl b/src/cascadia/TerminalApp/ActionArgs.idl index 51ecf282941..3bb355aa4ca 100644 --- a/src/cascadia/TerminalApp/ActionArgs.idl +++ b/src/cascadia/TerminalApp/ActionArgs.idl @@ -42,7 +42,8 @@ namespace TerminalApp { SettingsFile = 0, DefaultsFile, - AllFiles + AllFiles, + SettingsUI }; [default_interface] runtimeclass NewTerminalArgs { diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 6d2e765c3a1..74bcdddceda 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -121,22 +121,27 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleTogglePaneZoom(const IInspectable& /*sender*/, const TerminalApp::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(); + 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(); - activeTab->ToggleZoom(); + activeTab->ToggleZoom(); - // Update the selected tab, to trigger us to re-add the tab's GetRootElement to the UI tree - _UpdatedSelectedTab(_tabView.SelectedIndex()); + // Update the selected tab, to trigger us to re-add the tab's tab content to the UI tree + _UpdatedSelectedTab(_tabView.SelectedIndex()); + } + } } + args.Handled(true); } @@ -315,16 +320,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); + } } } } @@ -344,16 +352,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); @@ -362,10 +372,12 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_HandleOpenTabColorPicker(const IInspectable& /*sender*/, const TerminalApp::ActionEventArgs& args) { - auto activeTab = _GetFocusedTab(); - if (activeTab) + if (auto focusedTab = _GetFocusedTab()) { - activeTab->ActivateColorPicker(); + if (auto activeTab = _GetTerminalTabImpl(focusedTab)) + { + activeTab->ActivateColorPicker(); + } } args.Handled(true); } @@ -380,16 +392,18 @@ namespace winrt::TerminalApp::implementation title = realArgs.Title(); } - auto activeTab = _GetFocusedTab(); - if (activeTab) + if (auto focusedTab = _GetFocusedTab()) { - if (title.has_value()) + if (auto activeTab = _GetTerminalTabImpl(focusedTab)) { - activeTab->SetTabText(title.value()); - } - else - { - activeTab->ResetTabText(); + if (title.has_value()) + { + activeTab->SetTabText(title.value()); + } + else + { + activeTab->ResetTabText(); + } } } args.Handled(true); diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 192f7330ce3..aaf2425e602 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 "CascadiaSettings.h" #include "TerminalPage.h" #include "Jumplist.h" diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index 82fb48ed9a7..111b6c066d0 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -966,7 +966,7 @@ namespace winrt::TerminalApp::implementation // - void CommandPalette::OnTabsChanged(const IInspectable& s, const IVectorChangedEventArgs& e) { - if (auto tabList = s.try_as>()) + if (auto tabList = s.try_as>()) { auto idx = e.Index(); auto changedEvent = e.CollectionChange(); diff --git a/src/cascadia/TerminalApp/ITab.idl b/src/cascadia/TerminalApp/ITab.idl new file mode 100644 index 00000000000..9087b20110e --- /dev/null +++ b/src/cascadia/TerminalApp/ITab.idl @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "Command.idl"; + +namespace TerminalApp +{ + interface ITab + { + String Title { get; }; + Windows.UI.Xaml.Controls.IconSource IconSource { get; }; + Command SwitchToTabCommand; + Microsoft.UI.Xaml.Controls.TabViewItem TabViewItem { get; }; + Windows.UI.Xaml.FrameworkElement Content { get; }; + Windows.UI.Xaml.FocusState FocusState { get; }; + + void Focus(Windows.UI.Xaml.FocusState focusState); + void Shutdown(); + } +} diff --git a/src/cascadia/TerminalApp/SettingsTab.cpp b/src/cascadia/TerminalApp/SettingsTab.cpp new file mode 100644 index 00000000000..864e0be7b81 --- /dev/null +++ b/src/cascadia/TerminalApp/SettingsTab.cpp @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include +#include "SettingsTab.h" +#include "SettingsTab.g.cpp" +#include "Utils.h" +#include "ActionAndArgs.h" +#include "ActionArgs.h" + +using namespace winrt; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Core; +using namespace winrt::Microsoft::Terminal::TerminalControl; +using namespace winrt::Windows::System; + +namespace winrt +{ + namespace MUX = Microsoft::UI::Xaml; + namespace WUX = Windows::UI::Xaml; +} + +namespace winrt::TerminalApp::implementation +{ + SettingsTab::SettingsTab() + { + Content(winrt::Microsoft::Terminal::Settings::Editor::MainPage()); + + _MakeTabViewItem(); + _CreateContextMenu(); + _CreateIcon(); + } + + // Method Description: + // - Initializes a TabViewItem for this Tab instance. + // Arguments: + // - + // Return Value: + // - + void SettingsTab::_MakeTabViewItem() + { + TabViewItem(::winrt::MUX::Controls::TabViewItem{}); + TabViewItem().Header(winrt::box_value(Title())); + } + + // Method Description: + // - Focus the settings UI + // Arguments: + // - focusState: The FocusState mode by which focus is to be obtained. + // Return Value: + // - + void SettingsTab::Focus(WUX::FocusState focusState) + { + _focusState = focusState; + + if (_focusState != FocusState::Unfocused) + { + Content().Focus(focusState); + } + } + + WUX::FocusState SettingsTab::FocusState() const noexcept + { + return _focusState; + } + + // Method Description: + // - Set the icon on the TabViewItem for this tab. + // Arguments: + // - + // Return Value: + // - + winrt::fire_and_forget SettingsTab::_CreateIcon() + { + auto weakThis{ get_weak() }; + + co_await winrt::resume_foreground(TabViewItem().Dispatcher()); + + if (auto tab{ weakThis.get() }) + { + auto fontFamily = winrt::WUX::Media::FontFamily(L"Segoe MDL2 Assets"); + auto glyph = L"\xE713"; // This is the Setting icon (looks like a gear) + + // The TabViewItem Icon needs MUX while the IconSourceElement in the CommandPalette needs WUX... + IconSource(GetFontIcon(fontFamily, 12, glyph)); + TabViewItem().IconSource(GetFontIcon(fontFamily, 12, glyph)); + + // Update SwitchToTab command's icon + SwitchToTabCommand().IconSource(GetFontIcon(fontFamily, 12, glyph)); + } + } + + // Method Description: + // - Prepares this tab for being removed from the UI hierarchy + void SettingsTab::Shutdown() + { + // TODO: Does/Will the settings UI need some shutdown procedures? + 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 SettingsTab::_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; + Controls::MenuFlyoutSeparator menuSeparator; + newTabFlyout.Items().Append(closeTabMenuItem); + TabViewItem().ContextFlyout(newTabFlyout); + } +} diff --git a/src/cascadia/TerminalApp/SettingsTab.h b/src/cascadia/TerminalApp/SettingsTab.h new file mode 100644 index 00000000000..678b6bd71ac --- /dev/null +++ b/src/cascadia/TerminalApp/SettingsTab.h @@ -0,0 +1,51 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- SettingsTab.h + +Abstract: +- The SettingsTab is a tab whose content is a Settings UI control. They can + coexist in a TabView with all other types of tabs, like the TerminalTab. + There should only be at most one SettingsTab open at any given time. + +Author(s): +- Leon Liang - October 2020 + +--*/ + +#pragma once +#include "SettingsTab.g.h" +#include +#include +#include "../../cascadia/inc/cppwinrt_utils.h" + +namespace winrt::TerminalApp::implementation +{ + struct SettingsTab : SettingsTabT + { + public: + SettingsTab(); + + void Focus(winrt::Windows::UI::Xaml::FocusState focusState); + winrt::Windows::UI::Xaml::FocusState FocusState() const noexcept; + + void Shutdown(); + + WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); + + GETSET_PROPERTY(winrt::hstring, Title, L"Settings"); + GETSET_PROPERTY(winrt::Windows::UI::Xaml::Controls::IconSource, IconSource, nullptr); + GETSET_PROPERTY(winrt::TerminalApp::Command, SwitchToTabCommand, nullptr); + GETSET_PROPERTY(winrt::Microsoft::UI::Xaml::Controls::TabViewItem, TabViewItem, nullptr); + GETSET_PROPERTY(winrt::Windows::UI::Xaml::Controls::Page, Content, nullptr); + + private: + winrt::Windows::UI::Xaml::FocusState _focusState{ winrt::Windows::UI::Xaml::FocusState::Unfocused }; + + void _MakeTabViewItem(); + void _CreateContextMenu(); + winrt::fire_and_forget _CreateIcon(); + }; +} diff --git a/src/cascadia/TerminalApp/SettingsTab.idl b/src/cascadia/TerminalApp/SettingsTab.idl new file mode 100644 index 00000000000..bea4f29ef65 --- /dev/null +++ b/src/cascadia/TerminalApp/SettingsTab.idl @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "ITab.idl"; + +namespace TerminalApp +{ + [default_interface] runtimeclass SettingsTab : ITab + { + } +} diff --git a/src/cascadia/TerminalApp/Tab.idl b/src/cascadia/TerminalApp/Tab.idl deleted file mode 100644 index ceb52a55f8f..00000000000 --- a/src/cascadia/TerminalApp/Tab.idl +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -import "Command.idl"; - -namespace TerminalApp -{ - [default_interface] runtimeclass Tab : Windows.UI.Xaml.Data.INotifyPropertyChanged - { - String Title { get; }; - Windows.UI.Xaml.Controls.IconSource IconSource { get; }; - Command SwitchToTabCommand { get; }; - UInt32 TabViewIndex { get; }; - } -} diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index ade5c951528..0d65f30d1aa 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -76,6 +76,12 @@ MinMaxCloseControl.xaml + + SettingsTab.idl + + + TerminalTab.idl + TerminalPage.xaml Code @@ -101,9 +107,6 @@ HasNestedCommandsVisibilityConverter.idl - - Tab.idl - ColorScheme.idl @@ -167,6 +170,12 @@ MinMaxCloseControl.xaml + + SettingsTab.idl + + + TerminalTab.idl + TerminalPage.xaml Code @@ -192,9 +201,6 @@ HasNestedCommandsVisibilityConverter.idl - - Tab.idl - ColorScheme.idl @@ -268,6 +274,7 @@ App.xaml + @@ -277,6 +284,8 @@ MinMaxCloseControl.xaml Code + + TerminalPage.xaml Code @@ -300,7 +309,6 @@ - @@ -425,4 +433,4 @@ - + \ No newline at end of file diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters index e8949cbdfe6..1ee756f16ad 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj.filters @@ -63,6 +63,13 @@ settings + + + tab + + + tab + @@ -123,6 +130,13 @@ settings + + + tab + + + tab + @@ -140,14 +154,10 @@ settings - - tab - commandPalette - settings @@ -160,6 +170,19 @@ settings + + + + + + tab + + + tab + + + tab + @@ -215,4 +238,4 @@ app - + \ No newline at end of file diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 8dc7142a3f0..7e914ef33ac 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -20,6 +20,7 @@ #include "TabRowControl.h" #include "ColorHelper.h" #include "DebugTapConnection.h" +#include "SettingsTab.h" using namespace winrt; using namespace winrt::Windows::Foundation::Collections; @@ -45,7 +46,7 @@ namespace winrt namespace winrt::TerminalApp::implementation { TerminalPage::TerminalPage() : - _tabs{ winrt::single_threaded_observable_vector() }, + _tabs{ winrt::single_threaded_observable_vector() }, _startupActions{ winrt::single_threaded_vector() } { InitializeComponent(); @@ -684,13 +685,12 @@ 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); - // Give the tab its index in the _tabs vector so it can manage its own SwitchToTab command. - newTabImpl->UpdateTabViewIndex(_tabs.Size() - 1); - // Hookup our event handlers to the new terminal _RegisterTerminalEvents(term, *newTabImpl); @@ -699,7 +699,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() }; @@ -708,24 +709,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); @@ -948,12 +937,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); } @@ -964,7 +953,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()) @@ -1015,30 +1004,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(); } } @@ -1065,8 +1056,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(); _tabs.RemoveAt(tabIndex); _tabView.TabItems().RemoveAt(tabIndex); @@ -1109,8 +1100,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 @@ -1132,7 +1123,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 @@ -1168,7 +1159,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); } @@ -1178,7 +1169,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(); } @@ -1237,8 +1228,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 @@ -1266,15 +1257,20 @@ 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(); - activeTab->ExitZoom(); - // Re-attach the tab's content to the UI tree. - _tabContent.Children().Append(activeTab->GetRootElement()); + 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(); + activeTab->ExitZoom(); + // Re-attach the tab's content to the UI tree. + _tabContent.Children().Append(activeTab->Content()); + } + } } } @@ -1290,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); + } } } @@ -1300,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: @@ -1329,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() + ITab TerminalPage::_GetFocusedTab() { if (auto index{ _GetFocusedTabIndex() }) { - return _GetStrongTabImpl(*index); + return _tabs.GetAt(*index); } return nullptr; } @@ -1358,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()); } } @@ -1381,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(); + } } } @@ -1423,8 +1422,10 @@ namespace winrt::TerminalApp::implementation { if (auto index{ _GetFocusedTabIndex() }) { - auto focusedTab{ _GetStrongTabImpl(*index) }; - focusedTab->Scroll(delta); + if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*index))) + { + terminalTab->Scroll(delta); + } } } @@ -1457,9 +1458,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; @@ -1533,9 +1541,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); + } } } @@ -1556,11 +1566,13 @@ namespace winrt::TerminalApp::implementation return; } - delta = std::clamp(delta, -1, 1); - const auto control = _GetActiveControl(); - const auto termHeight = control.GetViewHeight(); - auto focusedTab{ _GetStrongTabImpl(*indexOpt) }; - focusedTab->Scroll(termHeight * delta); + if (auto terminalTab = _GetTerminalTabImpl(_tabs.GetAt(*indexOpt))) + { + delta = std::clamp(delta, -1, 1); + const auto control = _GetActiveControl(); + const auto termHeight = control.GetViewHeight(); + terminalTab->Scroll(termHeight * delta); + } } // Method Description: @@ -1671,8 +1683,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; @@ -1870,32 +1884,39 @@ namespace winrt::TerminalApp::implementation // a background thread, as to not hang/crash the UI thread. fire_and_forget TerminalPage::_LaunchSettings(const SettingsTarget target) { - // This will switch the execution of the function to a background (not - // UI) thread. This is IMPORTANT, because the Windows.Storage API's - // (used for retrieving the path to the file) will crash on the UI - // thread, because the main thread is a STA. - co_await winrt::resume_background(); + if (target == SettingsTarget::SettingsUI) + { + _OpenSettingsUI(); + } + else + { + // This will switch the execution of the function to a background (not + // UI) thread. This is IMPORTANT, because the Windows.Storage API's + // (used for retrieving the path to the file) will crash on the UI + // thread, because the main thread is a STA. + co_await winrt::resume_background(); + + auto openFile = [](const auto& filePath) { + HINSTANCE res = ShellExecute(nullptr, nullptr, filePath.c_str(), nullptr, nullptr, SW_SHOW); + if (static_cast(reinterpret_cast(res)) <= 32) + { + ShellExecute(nullptr, nullptr, L"notepad", filePath.c_str(), nullptr, SW_SHOW); + } + }; - auto openFile = [](const auto& filePath) { - HINSTANCE res = ShellExecute(nullptr, nullptr, filePath.c_str(), nullptr, nullptr, SW_SHOW); - if (static_cast(reinterpret_cast(res)) <= 32) + switch (target) { - ShellExecute(nullptr, nullptr, L"notepad", filePath.c_str(), nullptr, SW_SHOW); + case SettingsTarget::DefaultsFile: + openFile(CascadiaSettings::GetDefaultSettingsPath()); + break; + case SettingsTarget::SettingsFile: + openFile(CascadiaSettings::GetSettingsPath()); + break; + case SettingsTarget::AllFiles: + openFile(CascadiaSettings::GetDefaultSettingsPath()); + openFile(CascadiaSettings::GetSettingsPath()); + break; } - }; - - switch (target) - { - case SettingsTarget::DefaultsFile: - openFile(CascadiaSettings::GetDefaultSettingsPath()); - break; - case SettingsTarget::SettingsFile: - openFile(CascadiaSettings::GetSettingsPath()); - break; - case SettingsTarget::AllFiles: - openFile(CascadiaSettings::GetDefaultSettingsPath()); - openFile(CascadiaSettings::GetSettingsPath()); - break; } } @@ -1954,23 +1975,22 @@ 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->GetRootElement()); + _tabContent.Children().Append(tab.Content()); - tab->SetFocused(true); + tab.Focus(FocusState::Programmatic); // Raise an event that our title changed - _titleChangeHandlers(*this, tab->GetActiveTitle()); + _titleChangeHandlers(*this, tab.Title()); } CATCH_LOG(); } @@ -2005,8 +2025,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); + } } } @@ -2063,9 +2085,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(); @@ -2077,11 +2100,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() }; @@ -2270,32 +2299,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: @@ -2497,7 +2500,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); } } @@ -2534,10 +2537,128 @@ namespace winrt::TerminalApp::implementation { for (uint32_t i = 0; i < _tabs.Size(); ++i) { - _GetStrongTabImpl(i)->UpdateTabViewIndex(i); + auto command = _tabs.GetAt(i).SwitchToTabCommand(); + command.Action().Args().as()->TabIndex(i); + } + } + + // Method Description: + // - Creates a settings UI tab and focuses it. If there's already a settings UI tab open, + // just focus the existing one. + // Arguments: + // - + // Return Value: + // - + void TerminalPage::_OpenSettingsUI() + { + // If we're holding the settings tab's switch command, don't create a new one, switch to the existing one. + if (!_switchToSettingsCommand) + { + auto newTabImpl = winrt::make_self(); + _MakeSwitchToTabCommand(*newTabImpl, _tabs.Size()); + + // Add the new tab to the list of our tabs. + _tabs.Append(*newTabImpl); + + // Don't capture a strong ref to the tab. If the tab is removed as this + // is called, we don't really care anymore about handling the event. + auto weakTab = make_weak(newTabImpl); + + auto tabViewItem = newTabImpl->TabViewItem(); + _tabView.TabItems().Append(tabViewItem); + + _ReapplyCompactTabSize(); + + tabViewItem.PointerPressed({ this, &TerminalPage::_OnTabClick }); + + // When the tab is closed, remove it from our list of tabs. + newTabImpl->Closed([tabViewItem, weakThis{ get_weak() }](auto&& /*s*/, auto&& /*e*/) { + if (auto page{ weakThis.get() }) + { + page->_switchToSettingsCommand = nullptr; + page->_RemoveOnCloseRoutine(tabViewItem, page); + } + }); + + _switchToSettingsCommand = newTabImpl->SwitchToTabCommand(); + + // This kicks off TabView::SelectionChanged, in response to which + // we'll attach the terminal's Xaml control to the Xaml root. + _tabView.SelectedItem(tabViewItem); + } + else + { + _actionDispatch->DoAction(_switchToSettingsCommand.Action()); + } + } + + // 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 ITab& 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 ITab& tab, const uint32_t index) + { + auto focusTabAction = winrt::make_self(); + auto args = winrt::make_self(); + args->TabIndex(index); + + focusTabAction->Action(ShortcutAction::SwitchToTab); + focusTabAction->Args(*args); + + winrt::TerminalApp::Command command; + command.Action(*focusTabAction); + command.Name(tab.Title()); + command.IconSource(tab.IconSource()); + + tab.SwitchToTabCommand(command); + } + // -------------------------------- WinRT Events --------------------------------- // Winrt events need a method for adding a callback to the event and removing the callback. // These macros will define them both for you. diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 540921c0c23..5a7d69dc87c 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 "CascadiaSettings.h" #include "Profile.h" #include "AppKeyBindings.h" @@ -90,11 +90,12 @@ namespace winrt::TerminalApp::implementation TerminalApp::CascadiaSettings _settings{ nullptr }; - Windows::Foundation::Collections::IObservableVector _tabs; - winrt::com_ptr _GetStrongTabImpl(const uint32_t index) const; - winrt::com_ptr _GetStrongTabImpl(const ::winrt::TerminalApp::Tab& tab) const; + Windows::Foundation::Collections::IObservableVector _tabs; + winrt::com_ptr _GetTerminalTabImpl(const ITab& tab) const; void _UpdateTabIndices(); + winrt::TerminalApp::Command _switchToSettingsCommand{ nullptr }; + bool _isInFocusMode{ false }; bool _isFullscreen{ false }; bool _isAlwaysOnTop{ false }; @@ -135,8 +136,8 @@ namespace winrt::TerminalApp::implementation void _HookupKeyBindings(const TerminalApp::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(); @@ -148,7 +149,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); @@ -156,7 +157,7 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::TerminalControl::TermControl _GetActiveControl(); std::optional _GetFocusedTabIndex() const noexcept; - winrt::com_ptr _GetFocusedTab(); + ITab _GetFocusedTab(); winrt::fire_and_forget _SetFocusedTabIndex(const uint32_t tabIndex); void _CloseFocusedTab(); void _CloseFocusedPane(); @@ -207,6 +208,12 @@ namespace winrt::TerminalApp::implementation void _UnZoomIfNeeded(); + void _OpenSettingsUI(); + + void _ReapplyCompactTabSize(); + + void _MakeSwitchToTabCommand(const ITab& tab, const uint32_t index); + #pragma region ActionHandlers // These are all defined in AppActionHandlers.cpp void _HandleOpenNewTabDropdown(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); diff --git a/src/cascadia/TerminalApp/TerminalSettingsSerializationHelpers.h b/src/cascadia/TerminalApp/TerminalSettingsSerializationHelpers.h index 3a3eb4f5ffb..5992ff560b8 100644 --- a/src/cascadia/TerminalApp/TerminalSettingsSerializationHelpers.h +++ b/src/cascadia/TerminalApp/TerminalSettingsSerializationHelpers.h @@ -320,10 +320,11 @@ JSON_ENUM_MAPPER(::winrt::TerminalApp::SplitType) JSON_ENUM_MAPPER(::winrt::TerminalApp::SettingsTarget) { - JSON_MAPPINGS(3) = { + JSON_MAPPINGS(4) = { pair_type{ "settingsFile", ValueType::SettingsFile }, pair_type{ "defaultsFile", ValueType::DefaultsFile }, pair_type{ "allFiles", ValueType::AllFiles }, + pair_type{ "settingsUI", ValueType::SettingsUI }, }; }; diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/TerminalTab.cpp similarity index 78% rename from src/cascadia/TerminalApp/Tab.cpp rename to src/cascadia/TerminalApp/TerminalTab.cpp index f7d33efd754..903d17edb28 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" #include "ActionAndArgs.h" @@ -25,7 +25,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 _activePane = _rootPane; _MakeTabViewItem(); - _MakeSwitchToTabCommand(); } // Method Description: @@ -45,11 +44,11 @@ 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->_inRename = true; @@ -57,7 +56,7 @@ namespace winrt::TerminalApp::implementation } }); - _UpdateTitle(); + UpdateTitle(); _RecalculateAndApplyTabColor(); } @@ -67,7 +66,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - The UIElement acting as root of the Tab's root pane. - UIElement Tab::GetRootElement() + FrameworkElement TerminalTab::Content() { if (_zoomedPane) { @@ -90,22 +89,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 and to create the context menu of @@ -114,40 +102,44 @@ 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); _CreateContextMenu(); } // Method Description: - // - Returns true if this is the currently focused tab. For any set of tabs, + // - Returns the focus state of this Tab. Unfocused means this tab is not focused, + // and any other FocusState means that this tab is focused. 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 + // - A FocusState enum value + WUX::FocusState TerminalTab::FocusState() const noexcept { - return _focused; + return _focusState; } // 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); + } } } @@ -160,7 +152,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(); } @@ -172,7 +164,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); @@ -185,35 +177,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) @@ -225,13 +200,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(GetColoredIcon(_lastIconPath)); - _tabViewItem.IconSource(GetColoredIcon(_lastIconPath)); + TabViewItem().IconSource(GetColoredIcon(_lastIconPath)); // Update SwitchToTab command's icon SwitchToTabCommand().IconSource(IconSource()); @@ -245,7 +220,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()) { @@ -263,14 +238,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()); @@ -288,7 +263,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(); @@ -304,7 +279,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(winrt::TerminalApp::SplitState splitType) + bool TerminalTab::CanSplitPane(winrt::TerminalApp::SplitState splitType) { return _activePane->CanSplit(splitType); } @@ -318,7 +293,7 @@ namespace winrt::TerminalApp::implementation // - control: A TermControl to use in the new pane. // Return Value: // - - void Tab::SplitPane(winrt::TerminalApp::SplitState splitType, const GUID& profile, TermControl& control) + void TerminalTab::SplitPane(winrt::TerminalApp::SplitState splitType, const GUID& profile, TermControl& control) { auto [first, second] = _activePane->Split(splitType, profile, control); _activePane = first; @@ -338,7 +313,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); } @@ -350,7 +325,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. @@ -364,7 +339,7 @@ namespace winrt::TerminalApp::implementation // - direction: The direction to move the separator in. // Return Value: // - - void Tab::ResizePane(const winrt::TerminalApp::Direction& direction) + void TerminalTab::ResizePane(const winrt::TerminalApp::Direction& direction) { // NOTE: This _must_ be called on the root pane, so that it can propagate // throughout the entire tree. @@ -378,7 +353,7 @@ namespace winrt::TerminalApp::implementation // - direction: The direction to move the focus in. // Return Value: // - - void Tab::NavigateFocus(const winrt::TerminalApp::Direction& direction) + void TerminalTab::NavigateFocus(const winrt::TerminalApp::Direction& direction) { // NOTE: This _must_ be called on the root pane, so that it can propagate // throughout the entire tree. @@ -387,7 +362,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(); } @@ -400,21 +375,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: @@ -427,7 +402,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() }; @@ -437,7 +412,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(); } }); @@ -474,7 +449,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(); @@ -482,7 +457,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(); @@ -497,7 +472,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() }; @@ -521,7 +496,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void Tab::_CreateContextMenu() + void TerminalTab::_CreateContextMenu() { auto weakThis{ get_weak() }; @@ -595,7 +570,7 @@ namespace winrt::TerminalApp::implementation newTabFlyout.Items().Append(renameTabMenuItem); newTabFlyout.Items().Append(menuSeparator); newTabFlyout.Items().Append(closeTabMenuItem); - _tabViewItem.ContextFlyout(newTabFlyout); + TabViewItem().ContextFlyout(newTabFlyout); } // Method Description: @@ -610,9 +585,9 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void Tab::_UpdateTabHeader() + void TerminalTab::_UpdateTabHeader() { - winrt::hstring tabText{ GetActiveTitle() }; + winrt::hstring tabText{ Title() }; if (!_inRename) { @@ -630,13 +605,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 @@ -653,9 +628,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; } @@ -703,7 +678,7 @@ namespace winrt::TerminalApp::implementation { tab->_runtimeTabText = textBox.Text(); tab->_inRename = false; - tab->_UpdateTitle(); + tab->UpdateTitle(); } }); @@ -725,7 +700,7 @@ namespace winrt::TerminalApp::implementation e.Handled(true); textBox.Text(tab->_runtimeTabText); tab->_inRename = false; - tab->_UpdateTitle(); + tab->UpdateTitle(); break; } } @@ -735,7 +710,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(); @@ -745,7 +720,7 @@ namespace winrt::TerminalApp::implementation _tabRenameBoxLayoutUpdatedRevoker.revoke(); }); - _tabViewItem.Header(tabTextBox); + TabViewItem().Header(tabTextBox); } // Method Description: @@ -754,7 +729,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; @@ -791,7 +766,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(); @@ -806,11 +781,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; @@ -838,7 +813,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{}; @@ -867,15 +842,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(); @@ -890,7 +865,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void Tab::ResetRuntimeTabColor() + void TerminalTab::ResetRuntimeTabColor() { _runtimeTabColor.reset(); _RecalculateAndApplyTabColor(); @@ -903,7 +878,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void Tab::_ClearTabBackgroundColor() + void TerminalTab::_ClearTabBackgroundColor() { winrt::hstring keys[] = { L"TabViewItemHeaderBackground", @@ -921,9 +896,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); } } @@ -937,9 +912,9 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - - void Tab::ActivateColorPicker() + void TerminalTab::ActivateColorPicker() { - _tabColorPickup.ShowAt(_tabViewItem); + _tabColorPickup.ShowAt(TabViewItem()); } // Method Description: @@ -949,17 +924,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); } } @@ -969,7 +944,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(); } @@ -984,12 +959,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); } @@ -998,13 +973,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) { @@ -1015,14 +990,14 @@ namespace winrt::TerminalApp::implementation EnterZoom(); } } - void Tab::EnterZoom() + void TerminalTab::EnterZoom() { _zoomedPane = _activePane; _rootPane->Maximize(_zoomedPane); // Update the tab header to show the magnifying glass _UpdateTabHeader(); } - void Tab::ExitZoom() + void TerminalTab::ExitZoom() { _rootPane->Restore(_zoomedPane); _zoomedPane = nullptr; @@ -1030,41 +1005,12 @@ namespace winrt::TerminalApp::implementation _UpdateTabHeader(); } - 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() - { - auto focusTabAction = winrt::make_self(); - auto args = winrt::make_self(); - args->TabIndex(_TabViewIndex); - - focusTabAction->Action(ShortcutAction::SwitchToTab); - focusTabAction->Args(*args); - - winrt::TerminalApp::Command command; - command.Action(*focusTabAction); - command.Name(Title()); - command.IconSource(IconSource()); - - SwitchToTabCommand(command); - } - - void Tab::UpdateTabViewIndex(const uint32_t idx) - { - TabViewIndex(idx); - SwitchToTabCommand().Action().Args().as()->TabIndex(idx); - } - - 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 81% rename from src/cascadia/TerminalApp/Tab.h rename to src/cascadia/TerminalApp/TerminalTab.h index 3b52488f5d0..e63c5d0e15b 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/TerminalTab.h @@ -4,7 +4,7 @@ #pragma once #include "Pane.h" #include "ColorPickupFlyout.h" -#include "Tab.g.h" +#include "TerminalTab.g.h" // fwdecl unittest classes namespace TerminalAppLocalTests @@ -14,22 +14,21 @@ 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() = delete; + 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::Windows::UI::Xaml::UIElement GetRootElement(); + winrt::Windows::UI::Xaml::FrameworkElement Content(); 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); + winrt::Windows::UI::Xaml::FocusState FocusState() const noexcept; winrt::fire_and_forget Scroll(const int delta); @@ -47,7 +46,7 @@ namespace winrt::TerminalApp::implementation void NavigateFocus(const winrt::TerminalApp::Direction& direction); void UpdateSettings(const winrt::TerminalApp::TerminalSettings& settings, const GUID& profile); - winrt::hstring GetActiveTitle() const; + winrt::fire_and_forget UpdateTitle(); void Shutdown(); void ClosePane(); @@ -68,8 +67,6 @@ namespace winrt::TerminalApp::implementation int GetLeafPaneCount() const noexcept; - void UpdateTabViewIndex(const uint32_t idx); - WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>); @@ -80,9 +77,7 @@ namespace winrt::TerminalApp::implementation OBSERVABLE_GETSET_PROPERTY(winrt::Windows::UI::Xaml::Controls::IconSource, IconSource, _PropertyChangedHandlers, nullptr); OBSERVABLE_GETSET_PROPERTY(winrt::TerminalApp::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); + GETSET_PROPERTY(winrt::Microsoft::UI::Xaml::Controls::TabViewItem, TabViewItem, nullptr); private: std::shared_ptr _rootPane{ nullptr }; @@ -93,7 +88,7 @@ namespace winrt::TerminalApp::implementation std::optional _themeTabColor{}; std::optional _runtimeTabColor{}; - bool _focused{ false }; + winrt::Windows::UI::Xaml::FocusState _focusState{ winrt::Windows::UI::Xaml::FocusState::Unfocused }; winrt::Microsoft::UI::Xaml::Controls::TabViewItem _tabViewItem{ nullptr }; winrt::hstring _runtimeTabText{}; @@ -113,16 +108,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(); - friend class ::TerminalAppLocalTests::TabTests; }; } diff --git a/src/cascadia/TerminalApp/TerminalTab.idl b/src/cascadia/TerminalApp/TerminalTab.idl new file mode 100644 index 00000000000..a721365683f --- /dev/null +++ b/src/cascadia/TerminalApp/TerminalTab.idl @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "ITab.idl"; + +namespace TerminalApp +{ + [default_interface] runtimeclass TerminalTab : ITab + { + } +} diff --git a/src/cascadia/TerminalApp/Utils.h b/src/cascadia/TerminalApp/Utils.h index 91915804139..64d560af2d7 100644 --- a/src/cascadia/TerminalApp/Utils.h +++ b/src/cascadia/TerminalApp/Utils.h @@ -82,6 +82,23 @@ namespace Microsoft::TerminalApp::details { using type = winrt::Windows::UI::Xaml::Controls::BitmapIconSource; }; + + template + struct FontIconSource + { + }; + + template<> + struct FontIconSource + { + using type = winrt::Microsoft::UI::Xaml::Controls::FontIconSource; + }; + + template<> + struct FontIconSource + { + using type = winrt::Windows::UI::Xaml::Controls::FontIconSource; + }; } // Method Description: @@ -116,6 +133,28 @@ TIconSource GetColoredIcon(const winrt::hstring& path) return nullptr; } +// TODO: GH#1564 SUI polish - Dedupe with Command's icon handler +template +TIconSource GetFontIcon(const winrt::Windows::UI::Xaml::Media::FontFamily& fontFamily, + const double fontSize, + const winrt::hstring glyph) +{ + if (!glyph.empty()) + { + try + { + ::Microsoft::TerminalApp::details::FontIconSource::type iconSource; + iconSource.FontFamily(fontFamily); + iconSource.FontSize(fontSize); + iconSource.Glyph(glyph); + return iconSource; + } + CATCH_LOG(); + } + + return nullptr; +} + std::wstring VisualizeControlCodes(std::wstring str) noexcept; inline std::wstring VisualizeControlCodes(std::wstring_view str) noexcept diff --git a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj index 6cf1ed00dee..32ebc347566 100644 --- a/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj +++ b/src/cascadia/TerminalApp/dll/TerminalApp.vcxproj @@ -36,7 +36,8 @@ - + + diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.xaml b/src/cascadia/TerminalSettingsEditor/MainPage.xaml index 120028331da..bb9ad8d6ea3 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.xaml +++ b/src/cascadia/TerminalSettingsEditor/MainPage.xaml @@ -1,6 +1,6 @@  - Margin="20"> - + diff --git a/src/cascadia/inc/cppwinrt_utils.h b/src/cascadia/inc/cppwinrt_utils.h index 72f0526d3ca..32e1783efc8 100644 --- a/src/cascadia/inc/cppwinrt_utils.h +++ b/src/cascadia/inc/cppwinrt_utils.h @@ -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) \