diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index e0570cc763d..b4cb87e8138 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1107,7 +1107,6 @@ NOSIZE NOSNAPSHOT NOTHOUSANDS NOTICKS -notif NOTIMEOUTIFNOTHUNG NOTIMPL NOTOPMOST diff --git a/src/cascadia/TerminalSettingsEditor/AddProfile.cpp b/src/cascadia/TerminalSettingsEditor/AddProfile.cpp deleted file mode 100644 index a71f09bf9a4..00000000000 --- a/src/cascadia/TerminalSettingsEditor/AddProfile.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "pch.h" -#include "AddProfile.h" -#include "AddProfile.g.cpp" -#include "AddProfilePageNavigationState.g.cpp" -#include "EnumEntry.h" - -using namespace winrt::Windows::Foundation; -using namespace winrt::Windows::System; -using namespace winrt::Windows::UI::Core; -using namespace winrt::Windows::UI::Xaml; -using namespace winrt::Windows::UI::Xaml::Navigation; -using namespace winrt::Microsoft::Terminal::Settings::Model; - -namespace winrt::Microsoft::Terminal::Settings::Editor::implementation -{ - AddProfile::AddProfile() - { - InitializeComponent(); - - Automation::AutomationProperties::SetName(AddNewButton(), RS_(L"AddProfile_AddNewTextBlock/Text")); - Automation::AutomationProperties::SetName(DuplicateButton(), RS_(L"AddProfile_DuplicateTextBlock/Text")); - } - - void AddProfile::OnNavigatedTo(const NavigationEventArgs& e) - { - const auto args = e.Parameter().as(); - _State = args.ViewModel().as(); - BringIntoViewWhenLoaded(args.ElementToFocus()); - - TraceLoggingWrite( - g_hTerminalSettingsEditorProvider, - "NavigatedToPage", - TraceLoggingDescription("Event emitted when the user navigates to a page in the settings UI"), - TraceLoggingValue("addProfile", "PageId", "The identifier of the page that was navigated to"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - } - - void AddProfile::AddNewClick(const IInspectable& /*sender*/, - const Windows::UI::Xaml::RoutedEventArgs& /*eventArgs*/) - { - TraceLoggingWrite( - g_hTerminalSettingsEditorProvider, - "AddNewProfile", - TraceLoggingDescription("Event emitted when the user adds a new profile"), - TraceLoggingValue("EmptyProfile", "Type", "The type of the creation method (i.e. empty profile, duplicate)"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - - _State.RequestAddNew(); - } - - void AddProfile::DuplicateClick(const IInspectable& /*sender*/, - const Windows::UI::Xaml::RoutedEventArgs& /*eventArgs*/) - { - if (const auto selected = Profiles().SelectedItem()) - { - const auto selectedProfile = selected.as(); - TraceLoggingWrite( - g_hTerminalSettingsEditorProvider, - "AddNewProfile", - TraceLoggingDescription("Event emitted when the user adds a new profile"), - TraceLoggingValue("Duplicate", "Type", "The type of the creation method (i.e. empty profile, duplicate)"), - TraceLoggingValue(!selectedProfile.Source().empty(), "SourceProfileHasSource", "True, if the source profile has a source (i.e. dynamic profile generator namespace, fragment). Otherwise, False, indicating it's based on a custom profile."), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - - _State.RequestDuplicate(selectedProfile.Guid()); - } - } - - void AddProfile::ProfilesSelectionChanged(const IInspectable& /*sender*/, - const Windows::UI::Xaml::RoutedEventArgs& /*eventArgs*/) - { - if (!_IsProfileSelected) - { - IsProfileSelected(true); - } - } -} diff --git a/src/cascadia/TerminalSettingsEditor/AddProfile.h b/src/cascadia/TerminalSettingsEditor/AddProfile.h deleted file mode 100644 index 7bfa0654f20..00000000000 --- a/src/cascadia/TerminalSettingsEditor/AddProfile.h +++ /dev/null @@ -1,67 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- AddProfile.h - -Abstract: -- This creates the 'add new profile' page in the settings UI and handles - user interaction with it, raising events to the main page as necessary - - -Author(s): -- Pankaj Bhojwani - March 2021 - ---*/ - -#pragma once - -#include "AddProfile.g.h" -#include "AddProfilePageNavigationState.g.h" -#include "Utils.h" - -namespace winrt::Microsoft::Terminal::Settings::Editor::implementation -{ - struct AddProfilePageNavigationState : AddProfilePageNavigationStateT - { - public: - AddProfilePageNavigationState(const Model::CascadiaSettings& settings) : - _Settings{ settings } {} - - void RequestAddNew() - { - AddNew.raise(winrt::guid{}); - } - - void RequestDuplicate(GUID profile) - { - AddNew.raise(profile); - } - - til::event AddNew; - - WINRT_PROPERTY(Model::CascadiaSettings, Settings, nullptr); - }; - - struct AddProfile : public HasScrollViewer, AddProfileT - { - public: - AddProfile(); - - void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e); - - void AddNewClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); - void DuplicateClick(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); - void ProfilesSelectionChanged(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs); - - til::property_changed_event PropertyChanged; - WINRT_PROPERTY(Editor::AddProfilePageNavigationState, State, nullptr); - WINRT_OBSERVABLE_PROPERTY(bool, IsProfileSelected, PropertyChanged.raise, nullptr); - }; -} - -namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation -{ - BASIC_FACTORY(AddProfile); -} diff --git a/src/cascadia/TerminalSettingsEditor/AddProfile.idl b/src/cascadia/TerminalSettingsEditor/AddProfile.idl deleted file mode 100644 index 480c69bcbe1..00000000000 --- a/src/cascadia/TerminalSettingsEditor/AddProfile.idl +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -namespace Microsoft.Terminal.Settings.Editor -{ - delegate void AddNewArgs(Guid profile); - - runtimeclass AddProfilePageNavigationState - { - Microsoft.Terminal.Settings.Model.CascadiaSettings Settings; - void RequestAddNew(); - void RequestDuplicate(Guid profile); - event AddNewArgs AddNew; - }; - - [default_interface] runtimeclass AddProfile : Windows.UI.Xaml.Controls.Page, Windows.UI.Xaml.Data.INotifyPropertyChanged - { - AddProfile(); - AddProfilePageNavigationState State { get; }; - Boolean IsProfileSelected { get; }; - } -} diff --git a/src/cascadia/TerminalSettingsEditor/AddProfile.xaml b/src/cascadia/TerminalSettingsEditor/AddProfile.xaml deleted file mode 100644 index 0bee1fd2b41..00000000000 --- a/src/cascadia/TerminalSettingsEditor/AddProfile.xaml +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 2f3cd75db0b..0c566330e22 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -16,7 +16,7 @@ #include "GlobalAppearanceViewModel.h" #include "ColorSchemes.h" #include "EditColorScheme.h" -#include "AddProfile.h" +#include "Profiles.h" #include "InteractionViewModel.h" #include "LaunchViewModel.h" #include "NewTabMenuViewModel.h" @@ -102,7 +102,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation MainPage::MainPage(const CascadiaSettings& settings) : _settingsSource{ settings }, _settingsClone{ settings.Copy() }, - _profileVMs{ single_threaded_observable_vector() } + _profilesPageVM{ winrt::make() } { InitializeComponent(); _UpdateBackgroundForMica(); @@ -156,8 +156,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } }); + _SetupProfilesPageEventHandling(); + // Make sure to initialize the profiles _after_ we have initialized the color schemes page VM, because we pass - // that VM into the appearance VMs within the profiles + // that VM into the appearance VMs within the profiles. The Profiles VM owns the per-profile list itself. _InitializeProfilesList(); // Apply icons and tooltips (GH#19688, long names may be truncated) to static nav items @@ -205,28 +207,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _UpdateBackgroundForMica(); - // Deduce information about the currently selected item - IInspectable lastBreadcrumb; - const auto size = _breadcrumbs.Size(); - if (size > 0) + // Capture data about where we are right now, so we can re-navigate to the same + // place after we rebuild all the settings. + IInspectable destination{ nullptr }; + auto subPage = BreadcrumbSubPage::None; + if (const auto size = _breadcrumbs.Size(); size > 0) { - lastBreadcrumb = _breadcrumbs.GetAt(size - 1); + const auto& crumb = _breadcrumbs.GetAt(size - 1).as(); + destination = crumb->Tag(); + subPage = crumb->SubPage(); } - // Collect only the first items out of the menu item source, the static - // ones that we don't want to regenerate. - // - // By manipulating a MenuItemsSource this way, rather than manipulating the - // MenuItems directly, we avoid a crash in WinUI. - // - // By making the vector only _originalNumItems big to start, GetMany - // will only fill that number of elements out of the current source. - std::vector menuItemsSTL(_originalNumItems, nullptr); - _menuItemSource.GetMany(0, menuItemsSTL); - // now, just stick them back in. - _menuItemSource.ReplaceAll(menuItemsSTL); - - // Repopulate profile-related menu items _InitializeProfilesList(); // Update the Nav State with the new version of the settings @@ -236,64 +227,41 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _extensionsVM.UpdateSettings(_settingsClone, _colorSchemesPageVM); _profileDefaultsVM = nullptr; // Lazy-loaded upon navigation - // Now that the menuItems are repopulated, - // refresh the current page using the breadcrumb data we collected before the refresh - if (const auto& crumb{ lastBreadcrumb.try_as() }; crumb && crumb->Tag()) + if (const auto& profileTag{ destination.try_as() }) { - bool foundNavigationParams = false; - auto destination = crumb->Tag(); - auto subPage = crumb->SubPage(); - for (const auto& item : _menuItemSource) + // Find the new profile VM by guid + if (const auto newProfileVM = _FindProfileViewModelByGuid(profileTag.OriginalProfileGuid())) { - const auto menuItem = item.try_as(); - if (!menuItem) - { - continue; - } - - const auto& tag = menuItem.Tag(); - if (const auto& stringTag{ tag.try_as() }) - { - if (const auto& destString{ destination.try_as() }) - { - foundNavigationParams = (*stringTag == *destString); - } - else if (destination.try_as() && *stringTag == newTabMenuTag) - { - foundNavigationParams = true; - subPage = BreadcrumbSubPage::NewTabMenu_Folder; - } - else if (destination.try_as() && *stringTag == extensionsTag) - { - foundNavigationParams = true; - subPage = BreadcrumbSubPage::Extensions_Extension; - } - } - else if (const auto& profileTag{ tag.try_as() }) - { - const auto destProfile = destination.try_as(); - if (destProfile && profileTag->OriginalProfileGuid() == destProfile->OriginalProfileGuid()) - { - // Use the new profile VM from the refreshed menu items - destination = tag; - foundNavigationParams = true; - } - } - - if (foundNavigationParams) - { - // found the one that was selected before the refresh - _Navigate(destination, subPage); - return; - } + destination = newProfileVM; + } + else + { + // Fall back to the Profiles landing page + destination = box_value(profilesTag); + subPage = BreadcrumbSubPage::None; + } + } + else if (destination.try_as()) + { + destination = box_value(newTabMenuTag); + subPage = BreadcrumbSubPage::NewTabMenu_Folder; + } + else if (destination.try_as()) + { + destination = box_value(extensionsTag); + subPage = BreadcrumbSubPage::Extensions_Extension; + } + else if (!destination.try_as()) + { + // Couldn't find a meaningful previous page. Fall back to the first menu item. + if (_menuItemSource && _menuItemSource.Size() > 0) + { + destination = _menuItemSource.GetAt(0).as().Tag(); + subPage = BreadcrumbSubPage::None; } } - // Couldn't find the selected item, fall back to first menu item - // This happens when the selected item was a profile which doesn't exist in the new configuration - // We can use menuItemsSTL here because the only things they miss are profile entries. - const auto& firstItem{ _menuItemSource.GetAt(0).as() }; - _Navigate(firstItem.Tag(), BreadcrumbSubPage::None); + _Navigate(destination, subPage); _UpdateSearchIndex(); } @@ -325,12 +293,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // can be empty to indicate that we should create a fresh profile void MainPage::_AddProfileHandler(winrt::guid profileGuid) { - uint32_t insertIndex; - auto selectedItem{ SettingsNav().SelectedItem() }; - if (_menuItemSource) - { - _menuItemSource.IndexOf(selectedItem, insertIndex); - } if (profileGuid != winrt::guid{}) { // if we were given a non-empty guid, we want to duplicate the corresponding profile @@ -338,13 +300,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation if (profile) { const auto duplicated = _settingsClone.DuplicateProfile(profile); - _CreateAndNavigateToNewProfile(insertIndex, duplicated); + _CreateAndNavigateToNewProfile(duplicated); } } else { // we were given an empty guid, create a new profile - _CreateAndNavigateToNewProfile(insertIndex, nullptr); + _CreateAndNavigateToNewProfile(nullptr); } } @@ -543,6 +505,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation if (currentPage == ProfileSubPage::Base) { _breadcrumbs.Clear(); + _AppendProfilesRootCrumb(); _breadcrumbs.Append(winrt::make(breadcrumbTag, breadcrumbText, BreadcrumbSubPage::None)); } _NavigateToProfileSubPage(profile, currentPage, breadcrumbTag, {}); @@ -638,8 +601,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _breadcrumbs.Append(winrt::make(vm, RS_(L"Nav_Extensions/Content"), BreadcrumbSubPage::None)); } } + else if (*clickedItemTag == profilesTag) + { + contentFrame().Navigate(xaml_typename(), winrt::make(_profilesPageVM, *this, elementToFocus)); + _breadcrumbs.Append(winrt::make(vm, RS_(L"Nav_Profiles/Content"), BreadcrumbSubPage::None)); + } else if (*clickedItemTag == globalProfileTag) { + _AppendProfilesRootCrumb(); + // lazy load profile defaults VM if (!_profileDefaultsVM) { @@ -658,9 +628,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // Register handler for future user-driven sub-page changes _SetupProfileEventHandling(_profileDefaultsVM); + + // Keep the Profiles nav item selected. + selectedNavTag = profilesTag; } else if (*clickedItemTag == colorSchemesTag) { + _AppendProfilesRootCrumb(); + selectedNavTag = profilesTag; + _breadcrumbs.Append(winrt::make(vm, RS_(L"Nav_ColorSchemes/Content"), BreadcrumbSubPage::None)); contentFrame().Navigate(xaml_typename(), winrt::make(_colorSchemesPageVM, *this, elementToFocus)); @@ -674,49 +650,43 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation contentFrame().Navigate(xaml_typename(), winrt::make(winrt::make(_settingsClone.GlobalSettings()), *this, elementToFocus)); _breadcrumbs.Append(winrt::make(vm, RS_(L"Nav_Appearance/Content"), BreadcrumbSubPage::None)); } - else if (*clickedItemTag == addProfileTag) - { - auto addProfileState{ winrt::make(_settingsClone) }; - addProfileState.AddNew({ get_weak(), &MainPage::_AddProfileHandler }); - contentFrame().Navigate(xaml_typename(), winrt::make(addProfileState, *this, elementToFocus)); - _breadcrumbs.Append(winrt::make(vm, RS_(L"Nav_AddNewProfile/Content"), BreadcrumbSubPage::None)); - } } else if (const auto& profile = vm.try_as()) { + _AppendProfilesRootCrumb(); + selectedNavTag = profilesTag; + if (profile.Orphaned()) { contentFrame().Navigate(xaml_typename(), winrt::make(profile, *this, elementToFocus)); _breadcrumbs.Append(winrt::make(vm, profile.Name(), BreadcrumbSubPage::None)); profile.CurrentPage(ProfileSubPage::Base); _SetupProfileEventHandling(profile); - return; } + else + { + // Set CurrentPage before registering the handler to avoid double-navigation + const ProfileSubPage profileSubPage = ProfileSubPageFromBreadcrumb(subPage); + profile.CurrentPage(profileSubPage); - // Set CurrentPage before registering the handler to avoid double-navigation - const ProfileSubPage profileSubPage = ProfileSubPageFromBreadcrumb(subPage); - profile.CurrentPage(profileSubPage); - - // Navigate directly to the correct sub-page - _breadcrumbs.Append(winrt::make(vm, profile.Name(), BreadcrumbSubPage::None)); - _NavigateToProfileSubPage(profile, profileSubPage, vm, elementToFocus); + // Navigate directly to the correct sub-page + _breadcrumbs.Append(winrt::make(vm, profile.Name(), BreadcrumbSubPage::None)); + _NavigateToProfileSubPage(profile, profileSubPage, vm, elementToFocus); - if (const auto profileNavItem = _FindProfileNavItem(profile.OriginalProfileGuid())) - { - SettingsNav().SelectedItem(profileNavItem); + // Register handler for future user-driven sub-page changes + _SetupProfileEventHandling(profile); } - - // Register handler for future user-driven sub-page changes - _SetupProfileEventHandling(profile); } else if (const auto& colorSchemeVM = vm.try_as()) { - selectedNavTag = colorSchemesTag; const auto boxedColorSchemesTag = box_value(colorSchemesTag); // Suppress the handler to avoid double-navigation _colorSchemesPageViewModelChangedRevoker.revoke(); + _AppendProfilesRootCrumb(); + selectedNavTag = profilesTag; + _breadcrumbs.Append(winrt::make(boxedColorSchemesTag, RS_(L"Nav_ColorSchemes/Content"), BreadcrumbSubPage::None)); if (subPage == BreadcrumbSubPage::None) @@ -835,26 +805,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } // Select the appropriate nav item - // NOTE: profiles are special in that they have their own nav item, so those are handled in the profile branch above if (!selectedNavTag.empty()) { - for (auto&& menuItem : _menuItemSource) - { - if (const auto& navViewItem{ menuItem.try_as() }) - { - if (const auto& tag{ navViewItem.Tag() }) - { - if (const auto& stringTag{ tag.try_as() }) - { - if (*stringTag == selectedNavTag) - { - SettingsNav().SelectedItem(navViewItem); - break; - } - } - } - } - } + _SelectNavItemByTag(selectedNavTag); } } @@ -894,35 +847,20 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _MoveXamlParsedNavItemsIntoItemSource(); } - // Manually create a NavigationViewItem and view model for each profile - // and keep a reference to them in a map so that we - // can easily modify the correct one when the associated - // profile changes. - _profileVMs.Clear(); + // Populate the per-profile view models on the Profiles VM. The Profiles landing + // page (and the search index) read this same list back through the VM. + const auto& profileVMs = _profilesPageVM.Profiles(); + profileVMs.Clear(); for (const auto& profile : _settingsClone.AllProfiles()) { if (!profile.Deleted()) { auto profileVM = _viewModelForProfile(profile, _settingsClone, Dispatcher()); profileVM.SetupAppearances(_colorSchemesPageVM.AllColorSchemes()); - auto navItem = _CreateProfileNavViewItem(profileVM); - _menuItemSource.Append(navItem); + profileVM.DeleteProfileRequested({ this, &MainPage::_DeleteProfile }); + profileVMs.Append(profileVM); } } - - // Top off (the end of the nav view) with the Add Profile item - MUX::Controls::NavigationViewItem addProfileItem; - const auto addProfileText = RS_(L"Nav_AddNewProfile/Content"); - addProfileItem.Content(box_value(addProfileText)); - addProfileItem.Tag(box_value(addProfileTag)); - WUX::Controls::ToolTipService::SetToolTip(addProfileItem, box_value(addProfileText)); - - FontIcon icon; - // This is the "Add" symbol - icon.Glyph(NavTagIconMap[addProfileTag]); - addProfileItem.Icon(icon); - - _menuItemSource.Append(addProfileItem); } // BODGY @@ -958,79 +896,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation SettingsNav().MenuItemsSource(_menuItemSource); } - void MainPage::_CreateAndNavigateToNewProfile(const uint32_t index, const Model::Profile& profile) + void MainPage::_CreateAndNavigateToNewProfile(const Model::Profile& profile) { const auto newProfile{ profile ? profile : _settingsClone.CreateNewProfile() }; const auto profileViewModel{ _viewModelForProfile(newProfile, _settingsClone, Dispatcher()) }; profileViewModel.SetupAppearances(_colorSchemesPageVM.AllColorSchemes()); - const auto navItem{ _CreateProfileNavViewItem(profileViewModel) }; + profileViewModel.DeleteProfileRequested({ this, &MainPage::_DeleteProfile }); - if (_menuItemSource) - { - _menuItemSource.InsertAt(index, navItem); - } + _profilesPageVM.Profiles().Append(profileViewModel); // Select and navigate to the new profile - _Navigate(profileViewModel, BreadcrumbSubPage::None); - } - - static MUX::Controls::InfoBadge _createGlyphIconBadge(wil::zwstring_view glyph) - { - MUX::Controls::InfoBadge badge; - MUX::Controls::FontIconSource icon; - icon.FontFamily(winrt::Windows::UI::Xaml::Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); - icon.FontSize(12); - icon.Glyph(glyph); - badge.IconSource(icon); - return badge; - } - - MUX::Controls::NavigationViewItem MainPage::_CreateProfileNavViewItem(const Editor::ProfileViewModel& profile) - { - MUX::Controls::NavigationViewItem profileNavItem; - profileNavItem.Content(box_value(profile.Name())); - profileNavItem.Tag(box_value(profile)); - profileNavItem.Icon(UI::IconPathConverter::IconWUX(profile.EvaluatedIcon())); - WUX::Controls::ToolTipService::SetToolTip(profileNavItem, box_value(profile.Name())); - - if (profile.Orphaned()) - { - profileNavItem.InfoBadge(_createGlyphIconBadge(L"\xE7BA") /* Warning Triangle */); - } - else if (profile.Hidden()) - { - profileNavItem.InfoBadge(_createGlyphIconBadge(L"\xED1A") /* Hide */); - } - - // Update the menu item when the icon/name changes - auto weakMenuItem{ make_weak(profileNavItem) }; - profile.PropertyChanged([weakMenuItem](const auto&, const WUX::Data::PropertyChangedEventArgs& args) { - if (auto menuItem{ weakMenuItem.get() }) - { - const auto& tag{ menuItem.Tag().as() }; - if (args.PropertyName() == L"Icon") - { - menuItem.Icon(UI::IconPathConverter::IconWUX(tag.EvaluatedIcon())); - } - else if (args.PropertyName() == L"Name") - { - menuItem.Content(box_value(tag.Name())); - WUX::Controls::ToolTipService::SetToolTip(menuItem, box_value(tag.Name())); - } - else if (args.PropertyName() == L"Hidden") - { - menuItem.InfoBadge(tag.Hidden() ? _createGlyphIconBadge(L"\xED1A") /* Hide */ : nullptr); - } - } - }); - - // Add an event handler for when the user wants to delete a profile. - profile.DeleteProfileRequested({ this, &MainPage::_DeleteProfile }); - - // Register the VM so that it appears in the search index - _profileVMs.Append(profile); - - return profileNavItem; + _Navigate(profileViewModel); } void MainPage::_DeleteProfile(const IInspectable /*sender*/, const Editor::DeleteProfileEventArgs& args) @@ -1047,33 +923,20 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } - // remove selected item - uint32_t index; - auto selectedItem{ SettingsNav().SelectedItem() }; - if (_menuItemSource) + // Remove the profile VM + const auto& profileVMs = _profilesPageVM.Profiles(); + for (uint32_t i = 0; i < profileVMs.Size(); ++i) { - _menuItemSource.IndexOf(selectedItem, index); - _menuItemSource.RemoveAt(index); - - // Remove it from the list of VMs - auto profileVM = selectedItem.as().Tag().as(); - uint32_t vmIndex; - if (_menuItemSource.IndexOf(profileVM, vmIndex)) + if (profileVMs.GetAt(i).OriginalProfileGuid() == guid) { - _profileVMs.RemoveAt(vmIndex); - } - - // navigate to the profile next to this one - const auto newSelectedItem{ _menuItemSource.GetAt(index < _menuItemSource.Size() - 1 ? index : index - 1) }; - const auto newTag = newSelectedItem.as().Tag(); - if (const auto profileViewModel = newTag.try_as()) - { - profileViewModel->FocusDeleteButton(true); + profileVMs.RemoveAt(i); + break; } - _Navigate(newTag, BreadcrumbSubPage::None); - // Since we are navigating to a new profile after deletion, scroll up to the top - SettingsMainPage_ScrollViewer().ChangeView(nullptr, 0.0, nullptr); } + + // Go back to the Profiles landing page + _Navigate(box_value(profilesTag)); + SettingsMainPage_ScrollViewer().ChangeView(nullptr, 0.0, nullptr); } IObservableVector MainPage::Breadcrumbs() noexcept @@ -1083,37 +946,74 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void MainPage::_NavigateToProfileHandler(const IInspectable& /*sender*/, winrt::guid profileGuid) { - if (const auto profileNavItem = _FindProfileNavItem(profileGuid)) + if (const auto profileVM = _FindProfileViewModelByGuid(profileGuid)) { - _Navigate(profileNavItem.Tag(), BreadcrumbSubPage::None); + _Navigate(profileVM); } // Silently fail if the profile wasn't found } - MUX::Controls::NavigationViewItem MainPage::_FindProfileNavItem(winrt::guid profileGuid) const + Editor::ProfileViewModel MainPage::_FindProfileViewModelByGuid(winrt::guid profileGuid) const + { + for (const auto& profileVM : _profilesPageVM.Profiles()) + { + if (profileVM.OriginalProfileGuid() == profileGuid) + { + return profileVM; + } + } + return nullptr; + } + + void MainPage::_NavigateToColorSchemeHandler(const IInspectable& /*sender*/, const IInspectable& /*args*/) { + _Navigate(box_value(hstring{ colorSchemesTag }), BreadcrumbSubPage::ColorSchemes_Edit); + } + + void MainPage::_AppendProfilesRootCrumb() + { + _breadcrumbs.Append(winrt::make(box_value(profilesTag), RS_(L"Nav_Profiles/Content"), BreadcrumbSubPage::None)); + } + + void MainPage::_SelectNavItemByTag(std::wstring_view tag) + { + if (!_menuItemSource) + { + return; + } for (auto&& menuItem : _menuItemSource) { if (const auto& navViewItem{ menuItem.try_as() }) { - if (const auto& tag{ navViewItem.Tag() }) + if (const auto& itemTag{ navViewItem.Tag() }) { - if (const auto& profileTag{ tag.try_as() }) + if (const auto& stringTag{ itemTag.try_as() }) { - if (profileTag->OriginalProfileGuid() == profileGuid) + if (*stringTag == tag) { - return navViewItem; + SettingsNav().SelectedItem(navViewItem); + return; } } } } } - return nullptr; } - void MainPage::_NavigateToColorSchemeHandler(const IInspectable& /*sender*/, const IInspectable& /*args*/) + void MainPage::_SetupProfilesPageEventHandling() { - _Navigate(box_value(hstring{ colorSchemesTag }), BreadcrumbSubPage::ColorSchemes_Edit); + _profilesPageVM.OpenDefaultsRequested([this](const auto&, const auto&) { + _Navigate(box_value(globalProfileTag)); + }); + _profilesPageVM.OpenColorSchemesRequested([this](const auto&, const auto&) { + _Navigate(box_value(colorSchemesTag)); + }); + _profilesPageVM.AddProfileRequested([this](const auto&, const winrt::guid& sourceProfile) { + _AddProfileHandler(sourceProfile); + }); + _profilesPageVM.OpenProfileRequested([this](const auto&, const Editor::ProfileViewModel& profile) { + _Navigate(profile); + }); } winrt::Windows::UI::Xaml::Media::Brush MainPage::BackgroundBrush() @@ -1218,7 +1118,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation co_return; } _currentSearch = SearchIndex::Instance().SearchAsync(sanitizedQuery, - _profileVMs.GetView(), + _profilesPageVM.Profiles().GetView(), get_self(_newTabMenuPageVM)->FolderTreeFlatList().GetView(), _colorSchemesPageVM.AllColorSchemes().GetView(), _extensionsVM.ExtensionPackages().GetView(), diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.h b/src/cascadia/TerminalSettingsEditor/MainPage.h index 60c11f9a900..60d0729068c 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.h +++ b/src/cascadia/TerminalSettingsEditor/MainPage.h @@ -86,21 +86,24 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation std::optional _hostingHwnd; void _InitializeProfilesList(); - void _CreateAndNavigateToNewProfile(const uint32_t index, const Model::Profile& profile); - winrt::Microsoft::UI::Xaml::Controls::NavigationViewItem _CreateProfileNavViewItem(const Editor::ProfileViewModel& profile); + void _CreateAndNavigateToNewProfile(const Model::Profile& profile); void _DeleteProfile(const Windows::Foundation::IInspectable sender, const Editor::DeleteProfileEventArgs& args); void _AddProfileHandler(const winrt::guid profileGuid); void _SetupProfileEventHandling(const winrt::Microsoft::Terminal::Settings::Editor::ProfileViewModel profile); void _SetupColorSchemesEventHandling(); void _SetupActionsEventHandling(); + void _SetupProfilesPageEventHandling(); void _NavigateToProfileSubPage(const Editor::ProfileViewModel& profile, ProfileSubPage page, const IInspectable& breadcrumbTag, const hstring& elementToFocus); void _PreNavigateHelper(); - void _Navigate(const IInspectable& vm, BreadcrumbSubPage subPage, hstring elementToFocus = {}); + void _Navigate(const IInspectable& vm, BreadcrumbSubPage subPage = BreadcrumbSubPage::None, hstring elementToFocus = {}); void _NavigateToProfileHandler(const IInspectable& sender, winrt::guid profileGuid); void _NavigateToColorSchemeHandler(const IInspectable& sender, const IInspectable& args); - Microsoft::UI::Xaml::Controls::NavigationViewItem _FindProfileNavItem(winrt::guid profileGuid) const; + Editor::ProfileViewModel _FindProfileViewModelByGuid(winrt::guid profileGuid) const; + + void _AppendProfilesRootCrumb(); + void _SelectNavItemByTag(std::wstring_view tag); void _AnnounceNavPaneState(bool opened); @@ -110,11 +113,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation safe_void_coroutine _UpdateSearchIndex(); winrt::Microsoft::Terminal::Settings::Editor::ProfileViewModel _profileDefaultsVM{ nullptr }; - Windows::Foundation::Collections::IVector _profileVMs{ nullptr }; winrt::Microsoft::Terminal::Settings::Editor::ColorSchemesPageViewModel _colorSchemesPageVM{ nullptr }; winrt::Microsoft::Terminal::Settings::Editor::ActionsViewModel _actionsVM{ nullptr }; winrt::Microsoft::Terminal::Settings::Editor::NewTabMenuViewModel _newTabMenuPageVM{ nullptr }; winrt::Microsoft::Terminal::Settings::Editor::ExtensionsViewModel _extensionsVM{ nullptr }; + winrt::Microsoft::Terminal::Settings::Editor::ProfilesPageViewModel _profilesPageVM{ nullptr }; Windows::Foundation::IAsyncOperation> _currentSearch{ nullptr }; diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.xaml b/src/cascadia/TerminalSettingsEditor/MainPage.xaml index 950295a10c7..f438b5d3fde 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.xaml +++ b/src/cascadia/TerminalSettingsEditor/MainPage.xaml @@ -164,11 +164,6 @@ x:Uid="Nav_Appearance" Tag="GlobalAppearance_Nav" /> - - - @@ -194,11 +189,9 @@ x:Uid="Nav_Extensions" Tag="Extensions_Nav" /> - - - + diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj index 3eea812f502..1018ec2197f 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj @@ -52,9 +52,6 @@ EditAction.xaml - - AddProfile.xaml - EnumEntry.idl @@ -119,6 +116,10 @@ ColorSchemesPageViewModel.idl Code + + Profiles.xaml + Code + RenderingViewModel.idl Code @@ -192,9 +193,6 @@ Designer - - Designer - Designer @@ -237,6 +235,9 @@ Designer + + Designer + Designer @@ -275,9 +276,6 @@ EditAction.xaml - - AddProfile.xaml - GlobalAppearance.xaml @@ -340,6 +338,10 @@ ColorSchemesPageViewModel.idl Code + + Profiles.xaml + Code + RenderingViewModel.idl Code @@ -413,10 +415,6 @@ Actions.xaml Code - - AddProfile.xaml - Code - GlobalAppearance.xaml @@ -471,6 +469,10 @@ + + Profiles.xaml + Code + diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters index 1dd9008d159..cd5ae17bb1d 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters @@ -55,10 +55,10 @@ - + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsEditor/NavConstants.h b/src/cascadia/TerminalSettingsEditor/NavConstants.h index f83097b80d0..f0822a1bf3b 100644 --- a/src/cascadia/TerminalSettingsEditor/NavConstants.h +++ b/src/cascadia/TerminalSettingsEditor/NavConstants.h @@ -17,8 +17,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation inline constexpr std::wstring_view actionsTag{ L"Actions_Nav" }; inline constexpr std::wstring_view newTabMenuTag{ L"NewTabMenu_Nav" }; inline constexpr std::wstring_view extensionsTag{ L"Extensions_Nav" }; + inline constexpr std::wstring_view profilesTag{ L"Profiles_Nav" }; inline constexpr std::wstring_view globalProfileTag{ L"GlobalProfile_Nav" }; - inline constexpr std::wstring_view addProfileTag{ L"AddProfile" }; inline constexpr std::wstring_view colorSchemesTag{ L"ColorSchemes_Nav" }; inline constexpr std::wstring_view globalAppearanceTag{ L"GlobalAppearance_Nav" }; @@ -33,8 +33,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation std::pair{ actionsTag, L"\xE765" }, /* Keyboard Classic */ std::pair{ newTabMenuTag, L"\xE71D" }, /* All Apps */ std::pair{ extensionsTag, L"\xEA86" }, /* Puzzle */ - std::pair{ globalProfileTag, L"\xE81E" }, /* Map Layers */ - std::pair{ addProfileTag, L"\xE710" }, /* Add */ + std::pair{ profilesTag, L"\xE7EE" }, /* Other User */ + std::pair{ globalProfileTag, L"\xE7EE" }, /* Other User */ std::pair{ openJsonTag, L"\xE713" }, /* Settings */ }; } diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp index 5ace5a5ffc1..0ffd010e268 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp @@ -128,6 +128,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { _NotifyChanges(L"TabColorPreview"); } + else if (viewModelProperty == L"Hidden") + { + _NotifyChanges(L"AccessibleStateDescription", L"ShowHiddenBadge"); + } }); // Do the same for the starting directory @@ -340,6 +344,36 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation return _profile.Orphaned(); } + hstring ProfileViewModel::AccessibleStateDescription() const + { + const auto hidden = Hidden(); + const auto orphaned = Orphaned(); + if (hidden && orphaned) + { + return til::hstring_format(FMT_COMPILE(L"{}, {}"), + RS_(L"Profile_HiddenBadge/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"), + RS_(L"Profile_OrphanedBadge/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip")); + } + if (hidden) + { + return RS_(L"Profile_HiddenBadge/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); + } + if (orphaned) + { + return RS_(L"Profile_OrphanedBadge/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip"); + } + return {}; + } + + // Whether the "hidden" badge should be shown for this profile. The orphaned + // badge takes precedence, so the hidden badge is suppressed when the profile + // is also orphaned. This keeps a single badge visible at a time on the + // Profiles landing page. + bool ProfileViewModel::ShowHiddenBadge() const + { + return Hidden() && !Orphaned(); + } + hstring ProfileViewModel::TabTitlePreview() const { if (const auto tabTitle{ TabTitle() }; !tabTitle.empty()) diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h index 5ac434c46da..b198335a4b6 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h @@ -101,6 +101,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation bool RepositionCursorWithMouseAvailable() const noexcept; bool Orphaned() const; + hstring AccessibleStateDescription() const; + bool ShowHiddenBadge() const; hstring TabTitlePreview() const; hstring AnswerbackMessagePreview() const; Windows::UI::Color TabColorPreview() const; diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl index 2d3c5c5d7b0..748a283176b 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl @@ -106,6 +106,8 @@ namespace Microsoft.Terminal.Settings.Editor void DeleteUnfocusedAppearance(); Boolean Orphaned { get; }; + String AccessibleStateDescription { get; }; + Boolean ShowHiddenBadge { get; }; OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Name); PERMANENT_OBSERVABLE_PROJECTED_SETTING(Guid, Guid); OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Source); diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.cpp b/src/cascadia/TerminalSettingsEditor/Profiles.cpp new file mode 100644 index 00000000000..950157d74eb --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Profiles.cpp @@ -0,0 +1,199 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "Profiles.h" +#include "Profiles.g.cpp" +#include "ProfilesPageViewModel.g.cpp" +#include "ProfileViewModel.h" + +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Xaml::Controls; +using namespace winrt::Windows::UI::Xaml::Navigation; + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + Profiles::Profiles() + { + InitializeComponent(); + + Automation::AutomationProperties::SetHelpText(DefaultsNavigator(), RS_(L"Profiles_DefaultsNavigator/Description")); + Automation::AutomationProperties::SetHelpText(ColorSchemesNavigator(), RS_(L"Profiles_ColorSchemesNavigator/Description")); + Automation::AutomationProperties::SetName(AddProfileButton(), RS_(L"Profiles_AddProfileButton/[using:Windows.UI.Xaml.Controls]ToolTipService/ToolTip")); + } + + void Profiles::OnNavigatedTo(const NavigationEventArgs& e) + { + const auto args = e.Parameter().as(); + _ViewModel = args.ViewModel().as(); + BringIntoViewWhenLoaded(args.ElementToFocus()); + + TraceLoggingWrite( + g_hTerminalSettingsEditorProvider, + "NavigatedToPage", + TraceLoggingDescription("Event emitted when the user navigates to a page in the settings UI"), + TraceLoggingValue("profilesLanding", "PageId", "The identifier of the page that was navigated to"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + } + + // The primary "+" half of the SplitButton — adds either a new empty + // profile or a duplicate of the currently selected source profile, + // depending on what the user picked from the flyout. + void Profiles::AddProfile_Click(const winrt::Microsoft::UI::Xaml::Controls::SplitButton& /*sender*/, + const winrt::Microsoft::UI::Xaml::Controls::SplitButtonClickEventArgs& /*args*/) + { + const auto selected = _ViewModel.SelectedSourceProfile(); + const auto sourceGuid = selected ? selected.OriginalProfileGuid() : winrt::guid{}; + + TraceLoggingWrite( + g_hTerminalSettingsEditorProvider, + "AddNewProfile", + TraceLoggingDescription("Event emitted when the user adds a new profile"), + TraceLoggingValue(selected ? "Duplicate" : "EmptyProfile", "Type", "The type of the creation method (i.e. empty profile, duplicate)"), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), + TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); + + _ViewModel.RequestAddProfile(sourceGuid); + } + + // Rebuilds the MenuFlyout each time it opens so newly added/removed + // profiles are reflected without having to subscribe to VectorChanged. + // Selecting a flyout item updates which profile the SplitButton's primary + // action will use as its source — it does NOT add a profile on its own. + void Profiles::AddProfileFlyout_Opening(const IInspectable& sender, const IInspectable& /*args*/) + { + const auto flyout = sender.try_as(); + if (!flyout) + { + return; + } + + auto items = flyout.Items(); + items.Clear(); + + constexpr std::wstring_view groupName{ L"ProfilesAddProfileSource" }; + const auto selected = _ViewModel.SelectedSourceProfile(); + const auto weakViewModel = winrt::make_weak(_ViewModel); + + // "New empty profile" item — picking this resets the SplitButton to its + // default state where the primary action creates a brand-new profile. + { + winrt::Microsoft::UI::Xaml::Controls::RadioMenuFlyoutItem newEmptyItem; + newEmptyItem.Text(RS_(L"Profiles_AddProfileMenu_NewEmptyProfile/Text")); + newEmptyItem.GroupName(groupName); + newEmptyItem.IsChecked(!selected); + + Controls::FontIcon plusIcon; + plusIcon.FontFamily(Media::FontFamily{ L"Segoe Fluent Icons, Segoe MDL2 Assets" }); + plusIcon.Glyph(L"\xE710"); + newEmptyItem.Icon(plusIcon); + + newEmptyItem.Click([weakViewModel](const auto&, const auto&) { + if (const auto vm = weakViewModel.get()) + { + vm.SelectSourceProfile(nullptr); + } + }); + + items.Append(newEmptyItem); + } + + items.Append(MenuFlyoutSeparator{}); + + // One RadioMenuFlyoutItem per existing profile — picking one swaps the + // SplitButton's primary action over to "duplicate this profile". + for (const auto& profileVM : _ViewModel.Profiles()) + { + winrt::Microsoft::UI::Xaml::Controls::RadioMenuFlyoutItem profileItem; + profileItem.Text(profileVM.Name()); + profileItem.Icon(profileVM.IconPreview()); + profileItem.GroupName(groupName); + profileItem.IsChecked(selected && selected.OriginalProfileGuid() == profileVM.OriginalProfileGuid()); + + const auto weakProfile = winrt::make_weak(profileVM); + profileItem.Click([weakViewModel, weakProfile](const auto&, const auto&) { + const auto vm = weakViewModel.get(); + const auto profile = weakProfile.get(); + if (vm && profile) + { + vm.SelectSourceProfile(profile); + } + }); + + items.Append(profileItem); + } + } + + void Profiles::Profile_Click(const IInspectable& sender, const RoutedEventArgs& /*args*/) + { + // Profile navigators are buttons whose DataContext is the bound ProfileViewModel. + if (const auto element = sender.try_as()) + { + if (const auto profile = element.DataContext().try_as()) + { + _ViewModel.RequestOpenProfile(profile); + } + } + } + + ProfilesPageViewModel::ProfilesPageViewModel() + { + _setProfiles(single_threaded_observable_vector()); + } + + void ProfilesPageViewModel::RequestOpenDefaults() + { + OpenDefaultsRequested.raise(*this, nullptr); + } + + void ProfilesPageViewModel::RequestOpenColorSchemes() + { + OpenColorSchemesRequested.raise(*this, nullptr); + } + + void ProfilesPageViewModel::RequestAddProfile(const winrt::guid& sourceProfile) + { + AddProfileRequested.raise(*this, sourceProfile); + } + + void ProfilesPageViewModel::SelectSourceProfile(const Editor::ProfileViewModel& profile) + { + if (_SelectedSourceProfile == profile) + { + return; + } + + _SelectedSourceProfile = profile; + _NotifyChanges(L"SelectedSourceProfile", + L"HasSelectedSourceProfile", + L"SelectedSourceProfileLabel", + L"SelectedSourceProfileIcon"); + } + + winrt::hstring ProfilesPageViewModel::SelectedSourceProfileLabel() const + { + if (_SelectedSourceProfile) + { + return _SelectedSourceProfile.Name(); + } + return RS_(L"Profiles_AddProfileButton_DefaultLabel/Text"); + } + + Windows::UI::Xaml::Controls::IconElement ProfilesPageViewModel::SelectedSourceProfileIcon() const + { + if (_SelectedSourceProfile) + { + return _SelectedSourceProfile.IconPreview(); + } + return nullptr; + } + + void ProfilesPageViewModel::RequestOpenProfile(const Editor::ProfileViewModel& profile) + { + OpenProfileRequested.raise(*this, profile); + } + +} diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.h b/src/cascadia/TerminalSettingsEditor/Profiles.h new file mode 100644 index 00000000000..0bc45f551e4 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Profiles.h @@ -0,0 +1,75 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- Profiles.h + +Abstract: +- The Profiles landing page in the Settings UI. The page hosts entry points to + the list of individual profiles among other profile-related settings. + +--*/ + +#pragma once + +#include "Profiles.g.h" +#include "ProfilesPageViewModel.g.h" +#include "ProfileViewModel.h" +#include "Utils.h" +#include "ViewModelHelpers.h" + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + struct ProfilesPageViewModel : ProfilesPageViewModelT, ViewModelHelper + { + public: + ProfilesPageViewModel(); + + void RequestOpenDefaults(); + void RequestOpenColorSchemes(); + void RequestAddProfile(const winrt::guid& sourceProfile); + void RequestOpenProfile(const Editor::ProfileViewModel& profile); + + Editor::ProfileViewModel SelectedSourceProfile() const noexcept { return _SelectedSourceProfile; } + void SelectSourceProfile(const Editor::ProfileViewModel& profile); + bool HasSelectedSourceProfile() const noexcept { return static_cast(_SelectedSourceProfile); } + winrt::hstring SelectedSourceProfileLabel() const; + Windows::UI::Xaml::Controls::IconElement SelectedSourceProfileIcon() const; + + // DON'T YOU DARE ADD A `WINRT_CALLBACK(PropertyChanged` TO A CLASS DERIVED FROM ViewModelHelper. Do this instead: + using ViewModelHelper::PropertyChanged; + + WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector, Profiles, _propertyChangedHandlers, nullptr); + + public: + til::typed_event OpenDefaultsRequested; + til::typed_event OpenColorSchemesRequested; + til::typed_event AddProfileRequested; + til::typed_event OpenProfileRequested; + + private: + Editor::ProfileViewModel _SelectedSourceProfile{ nullptr }; + }; + + struct Profiles : public HasScrollViewer, ProfilesT + { + public: + Profiles(); + + void OnNavigatedTo(const winrt::Windows::UI::Xaml::Navigation::NavigationEventArgs& e); + + void AddProfile_Click(const Microsoft::UI::Xaml::Controls::SplitButton& sender, const Microsoft::UI::Xaml::Controls::SplitButtonClickEventArgs& args); + void AddProfileFlyout_Opening(const Windows::Foundation::IInspectable& sender, const Windows::Foundation::IInspectable& args); + void Profile_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& args); + + til::property_changed_event PropertyChanged; + WINRT_PROPERTY(Editor::ProfilesPageViewModel, ViewModel, nullptr); + }; +} + +namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation +{ + BASIC_FACTORY(Profiles); + BASIC_FACTORY(ProfilesPageViewModel); +} diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.idl b/src/cascadia/TerminalSettingsEditor/Profiles.idl new file mode 100644 index 00000000000..d61b3f4f87f --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Profiles.idl @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "ProfileViewModel.idl"; +import "ColorSchemeViewModel.idl"; + +namespace Microsoft.Terminal.Settings.Editor +{ + runtimeclass ProfilesPageViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + ProfilesPageViewModel(); + + Windows.Foundation.Collections.IObservableVector Profiles { get; }; + + // The profile whose properties should be used as the source when the + // user clicks the SplitButton's primary "+" action. null means "create + // a new empty profile" (i.e. the default state). + ProfileViewModel SelectedSourceProfile { get; }; + String SelectedSourceProfileLabel { get; }; + Windows.UI.Xaml.Controls.IconElement SelectedSourceProfileIcon { get; }; + Boolean HasSelectedSourceProfile { get; }; + void SelectSourceProfile(ProfileViewModel profile); + + void RequestOpenDefaults(); + void RequestOpenColorSchemes(); + void RequestAddProfile(Guid sourceProfile); + void RequestOpenProfile(ProfileViewModel profile); + + event Windows.Foundation.TypedEventHandler OpenDefaultsRequested; + event Windows.Foundation.TypedEventHandler OpenColorSchemesRequested; + event Windows.Foundation.TypedEventHandler AddProfileRequested; + event Windows.Foundation.TypedEventHandler OpenProfileRequested; + } + + [default_interface] runtimeclass Profiles : Windows.UI.Xaml.Controls.Page + { + Profiles(); + ProfilesPageViewModel ViewModel { get; }; + } +} diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.xaml b/src/cascadia/TerminalSettingsEditor/Profiles.xaml new file mode 100644 index 00000000000..6e5487db499 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/Profiles.xaml @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw index 4142f40e610..75f0f633285 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/de-DE/Resources.resw @@ -117,18 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Neues leeres Profil - Button label that creates a new profile with default settings. - - - Ein Profil duplizieren - This is the header for a control that lets the user duplicate one of their existing profiles. - - - Duplikat - Button label that duplicates the selected profile and navigates to the duplicate's page. - Dieses Farbschema ist Teil der integrierten Einstellungen oder einer installierten Erweiterung. @@ -1293,10 +1281,6 @@ ⚠ Es sind nicht gespeicherte Änderungen vorhanden. {Locked="⚠"} A disclaimer that appears when the unsaved changes to the settings are present. - - Neues Profil hinzufügen - Header for the "add new" menu item. This navigates to the page where users can add a new profile. - Profile Header for the "profiles" menu items. This acts as a divider to introduce profile-related menu items. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index 0edc8c0fd65..02830d9114b 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -117,18 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - New empty profile - Button label that creates a new profile with default settings. - - - Duplicate a profile - This is the header for a control that lets the user duplicate one of their existing profiles. - - - Duplicate - Button label that duplicates the selected profile and navigates to the duplicate's page. - This color scheme is part of the built-in settings or an installed extension @@ -1109,6 +1097,10 @@ If enabled, the profile will not appear in the list of profiles. This can be used to hide default profiles and dynamically generated profiles, while leaving them in your settings file. A description for what the "hidden" setting does. Presented near "Profile_Hidden". + + Hidden from the dropdown menu + Tooltip shown when hovering the InfoBadge next to a profile name on the Profiles landing page when that profile is hidden from the dropdown menu. + Run this profile as Administrator Header for a control to toggle whether the profile should always open elevated (in an admin window) @@ -1293,13 +1285,45 @@ ⚠ You have unsaved changes. {Locked="⚠"} A disclaimer that appears when the unsaved changes to the settings are present. - - Add a new profile - Header for the "add new" menu item. This navigates to the page where users can add a new profile. - Profiles - Header for the "profiles" menu items. This acts as a divider to introduce profile-related menu items. + Header for the "profiles" menu item. Navigates to a landing page that lists profile-related settings (defaults, color schemes, the list of terminal profiles, and the add-profile button). + + + Profiles + Tooltip for the "profiles" menu item. Navigates to a landing page that lists profile-related settings. + + + Terminal profiles + Section header on the Profiles landing page. Introduces the list of individual terminal profiles configured by the user. + + + Defaults + Title for the "Defaults" navigator on the Profiles landing page. Clicking this opens the page where defaults shared by all profiles can be edited. + + + Changes made here apply to all profiles unless overridden by a specific profile. These settings do not affect the Windows Terminal appearance. + Help text describing the "Defaults" navigator on the Profiles landing page. Clarifies that the defaults apply to every profile but are not the same as the global Windows Terminal appearance settings. + + + Color schemes + Title for the "Color schemes" navigator on the Profiles landing page. Clicking this opens the the color schemes page. + + + Add, edit, or remove the color schemes that your profiles can use. + Help text describing the "Color schemes" navigator on the Profiles landing page. + + + Add a new profile + Tooltip (and accessible name) for the split button on the Profiles landing page whose primary "+" action creates a new empty profile and whose flyout lists existing profiles to duplicate. + + + Add new profile + Default label shown next to the "+" icon on the add-profile split button when no source profile has been selected from the flyout. Clicking the button in this state creates a brand-new empty profile. + + + New empty profile + Label for the first item in the add-profile split button's flyout. Clicking this creates a new profile with default settings. Focus @@ -2671,6 +2695,10 @@ This automatically-detected profile appears to have been uninstalled. Changes you have made to it are preserved, but it cannot be used until it has been reinstalled. + + Profile is no longer detected + Tooltip shown when hovering the InfoBadge next to a profile name on the Profiles landing page when that profile's source software is no longer installed. + Original Source diff --git a/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw index 0587064226a..21f5c6f7519 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/es-ES/Resources.resw @@ -117,18 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Nuevo perfil vacío - Button label that creates a new profile with default settings. - - - Duplicar un perfil - This is the header for a control that lets the user duplicate one of their existing profiles. - - - Duplicar - Button label that duplicates the selected profile and navigates to the duplicate's page. - Esta combinación de colores forma parte de la configuración integrada o de una extensión instalada @@ -1293,10 +1281,6 @@ ⚠ Tienes cambios sin guardar {Locked="⚠"} A disclaimer that appears when the unsaved changes to the settings are present. - - Agregar un nuevo perfil - Header for the "add new" menu item. This navigates to the page where users can add a new profile. - Perfiles Header for the "profiles" menu items. This acts as a divider to introduce profile-related menu items. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw index 866fb341cba..6c2b8bc145b 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/fr-FR/Resources.resw @@ -117,18 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Nouveau profil vide - Button label that creates a new profile with default settings. - - - Dupliquer un profil - This is the header for a control that lets the user duplicate one of their existing profiles. - - - Dupliquer - Button label that duplicates the selected profile and navigates to the duplicate's page. - Ce modèle de couleurs fait partie des paramètres intégrés ou d’une extension installée @@ -1293,10 +1281,6 @@ ⚠ Certaines modifications ne sont pas enregistrées. {Locked="⚠"} A disclaimer that appears when the unsaved changes to the settings are present. - - Ajouter un nouveau profil - Header for the "add new" menu item. This navigates to the page where users can add a new profile. - Profils Header for the "profiles" menu items. This acts as a divider to introduce profile-related menu items. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw index 181a1ca2e86..7b311964aeb 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/it-IT/Resources.resw @@ -117,18 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Nuovo profilo vuoto - Button label that creates a new profile with default settings. - - - Duplica un profilo - This is the header for a control that lets the user duplicate one of their existing profiles. - - - Duplica - Button label that duplicates the selected profile and navigates to the duplicate's page. - Questa combinazione di colori fa parte delle impostazioni predefinite o di un'estensione installata @@ -1293,10 +1281,6 @@ ⚠ Sono presenti modifiche non salvate {Locked="⚠"} A disclaimer that appears when the unsaved changes to the settings are present. - - Aggiungi un nuovo profilo - Header for the "add new" menu item. This navigates to the page where users can add a new profile. - Profili Header for the "profiles" menu items. This acts as a divider to introduce profile-related menu items. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw index 458352042f6..361633c8f6f 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ja-JP/Resources.resw @@ -117,18 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 新しい空のプロファイル - Button label that creates a new profile with default settings. - - - プロファイルを複製する - This is the header for a control that lets the user duplicate one of their existing profiles. - - - 複製 - Button label that duplicates the selected profile and navigates to the duplicate's page. - この配色は、組み込みの設定またはインストールされている拡張機能の一部です @@ -1293,10 +1281,6 @@ ⚠ 保存されていない変更があります。 {Locked="⚠"} A disclaimer that appears when the unsaved changes to the settings are present. - - 新しいプロファイルを追加します - Header for the "add new" menu item. This navigates to the page where users can add a new profile. - プロファイル Header for the "profiles" menu items. This acts as a divider to introduce profile-related menu items. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw index c12bd0adc93..b906302af69 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ko-KR/Resources.resw @@ -117,18 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 새 빈 프로필 - Button label that creates a new profile with default settings. - - - 프로필 복제 - This is the header for a control that lets the user duplicate one of their existing profiles. - - - 중복 - Button label that duplicates the selected profile and navigates to the duplicate's page. - 이 색 구성표는 기본 제공 설정 또는 설치된 확장의 일부입니다. @@ -1293,10 +1281,6 @@ ⚠ 저장되지 않은 변경 내용이 있습니다. {Locked="⚠"} A disclaimer that appears when the unsaved changes to the settings are present. - - 새 프로필 추가 - Header for the "add new" menu item. This navigates to the page where users can add a new profile. - 프로필 Header for the "profiles" menu items. This acts as a divider to introduce profile-related menu items. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw index 8b65316350f..fb6aab40fbd 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/pt-BR/Resources.resw @@ -117,18 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Novo perfil vazio - Button label that creates a new profile with default settings. - - - Duplicar um perfil - This is the header for a control that lets the user duplicate one of their existing profiles. - - - Duplicar - Button label that duplicates the selected profile and navigates to the duplicate's page. - Este esquema de cores faz parte das configurações internas ou de uma extensão instalada @@ -1293,10 +1281,6 @@ ⚠ Você tem alterações não salvas. {Locked="⚠"} A disclaimer that appears when the unsaved changes to the settings are present. - - Adicionar novo perfil - Header for the "add new" menu item. This navigates to the page where users can add a new profile. - Perfis Header for the "profiles" menu items. This acts as a divider to introduce profile-related menu items. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw index 61601dcfefd..a879a04757d 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploc/Resources.resw @@ -117,18 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Ŋ℮ω ĕmρŧý рŗőƒîļё !!! !! - Button label that creates a new profile with default settings. - - - Ďύφľίċåте ã φґǿƒïłē !!! !!! - This is the header for a control that lets the user duplicate one of their existing profiles. - - - Đŭρŀī¢ατê !!! - Button label that duplicates the selected profile and navigates to the duplicate's page. - Ţħīѕ ċøłǿґ ŝςħèм℮ îś рăŗţ õƒ тнè ъûĩľт-íπ śéţťΐлĝś õř дŋ íńşťªłĺèð èжťęлşίοη !!! !!! !!! !!! !!! !!! !!! ! @@ -1293,10 +1281,6 @@ ⚠ Υŏű нâνě цπśąνêδ ¢ђãпġēš. !!! !!! !! {Locked="⚠"} A disclaimer that appears when the unsaved changes to the settings are present. - - Ãđδ α ηèŵ ряоƒіľэ !!! !! - Header for the "add new" menu item. This navigates to the page where users can add a new profile. - Ρґőƒϊłеѕ !! Header for the "profiles" menu items. This acts as a divider to introduce profile-related menu items. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw index 61601dcfefd..a879a04757d 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-ploca/Resources.resw @@ -117,18 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Ŋ℮ω ĕmρŧý рŗőƒîļё !!! !! - Button label that creates a new profile with default settings. - - - Ďύφľίċåте ã φґǿƒïłē !!! !!! - This is the header for a control that lets the user duplicate one of their existing profiles. - - - Đŭρŀī¢ατê !!! - Button label that duplicates the selected profile and navigates to the duplicate's page. - Ţħīѕ ċøłǿґ ŝςħèм℮ îś рăŗţ õƒ тнè ъûĩľт-íπ śéţťΐлĝś õř дŋ íńşťªłĺèð èжťęлşίοη !!! !!! !!! !!! !!! !!! !!! ! @@ -1293,10 +1281,6 @@ ⚠ Υŏű нâνě цπśąνêδ ¢ђãпġēš. !!! !!! !! {Locked="⚠"} A disclaimer that appears when the unsaved changes to the settings are present. - - Ãđδ α ηèŵ ряоƒіľэ !!! !! - Header for the "add new" menu item. This navigates to the page where users can add a new profile. - Ρґőƒϊłеѕ !! Header for the "profiles" menu items. This acts as a divider to introduce profile-related menu items. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw index 61601dcfefd..a879a04757d 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/qps-plocm/Resources.resw @@ -117,18 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Ŋ℮ω ĕmρŧý рŗőƒîļё !!! !! - Button label that creates a new profile with default settings. - - - Ďύφľίċåте ã φґǿƒïłē !!! !!! - This is the header for a control that lets the user duplicate one of their existing profiles. - - - Đŭρŀī¢ατê !!! - Button label that duplicates the selected profile and navigates to the duplicate's page. - Ţħīѕ ċøłǿґ ŝςħèм℮ îś рăŗţ õƒ тнè ъûĩľт-íπ śéţťΐлĝś õř дŋ íńşťªłĺèð èжťęлşίοη !!! !!! !!! !!! !!! !!! !!! ! @@ -1293,10 +1281,6 @@ ⚠ Υŏű нâνě цπśąνêδ ¢ђãпġēš. !!! !!! !! {Locked="⚠"} A disclaimer that appears when the unsaved changes to the settings are present. - - Ãđδ α ηèŵ ряоƒіľэ !!! !! - Header for the "add new" menu item. This navigates to the page where users can add a new profile. - Ρґőƒϊłеѕ !! Header for the "profiles" menu items. This acts as a divider to introduce profile-related menu items. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw index 10b2c6ef4bd..deadca0d386 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/ru-RU/Resources.resw @@ -117,18 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Новый пустой профиль - Button label that creates a new profile with default settings. - - - Дублировать профиль - This is the header for a control that lets the user duplicate one of their existing profiles. - - - Дублировать - Button label that duplicates the selected profile and navigates to the duplicate's page. - Эта цветовая схема является частью встроенных параметров или установленным расширением @@ -1293,10 +1281,6 @@ ⚠ Есть несохраненные изменения {Locked="⚠"} A disclaimer that appears when the unsaved changes to the settings are present. - - Добавить новый профиль - Header for the "add new" menu item. This navigates to the page where users can add a new profile. - Профили Header for the "profiles" menu items. This acts as a divider to introduce profile-related menu items. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/sr-Cyrl-RS/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/sr-Cyrl-RS/Resources.resw index 3d1bdf21898..796aa2c1176 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/sr-Cyrl-RS/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/sr-Cyrl-RS/Resources.resw @@ -117,18 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Нови празан профил - Button label that creates a new profile with default settings. - - - Дуплирај профил - This is the header for a control that lets the user duplicate one of their existing profiles. - - - Дуплирај - Button label that duplicates the selected profile and navigates to the duplicate's page. - Ова шема боја је део уграђених подешавања или инсталираног проширења @@ -1258,10 +1246,6 @@ ⚠ Имате несачуване измене. {Locked="⚠"} A disclaimer that appears when the unsaved changes to the settings are present. - - Додај нови профил - Header for the "add new" menu item. This navigates to the page where users can add a new profile. - Профили Header for the "profiles" menu items. This acts as a divider to introduce profile-related menu items. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/uk-UA/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/uk-UA/Resources.resw index 2d2b16c847a..7a739438bb6 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/uk-UA/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/uk-UA/Resources.resw @@ -117,24 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - Створити нову кнопку - - - Новий порожній профіль - Button label that creates a new profile with default settings. - - - Дублювати профіль - This is the header for a control that lets the user duplicate one of their existing profiles. - - - Дублювати кнопку - - - Дублювати - Button label that duplicates the selected profile and navigates to the duplicate's page. - Ця колірна схема є частиною вбудованих налаштувань або встановленого розширення @@ -1293,10 +1275,6 @@ ⚠ У вас є незбережені зміни. {Locked="⚠"} A disclaimer that appears when the unsaved changes to the settings are present. - - Додати новий профіль - Header for the "add new" menu item. This navigates to the page where users can add a new profile. - Профілі Header for the "profiles" menu items. This acts as a divider to introduce profile-related menu items. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw index ef9e3d5995f..9bac3f8a210 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/zh-CN/Resources.resw @@ -117,18 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 新建空配置文件 - Button label that creates a new profile with default settings. - - - 复制配置文件 - This is the header for a control that lets the user duplicate one of their existing profiles. - - - 复制 - Button label that duplicates the selected profile and navigates to the duplicate's page. - 此配色方案是内置设置或已安装扩展的一部分 @@ -1293,10 +1281,6 @@ ⚠ 你有未保存的更改。 {Locked="⚠"} A disclaimer that appears when the unsaved changes to the settings are present. - - 添加新配置文件 - Header for the "add new" menu item. This navigates to the page where users can add a new profile. - 配置文件 Header for the "profiles" menu items. This acts as a divider to introduce profile-related menu items. diff --git a/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw index 132de0f73d1..15d2a3f4853 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/zh-TW/Resources.resw @@ -117,18 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 新的空白設定檔 - Button label that creates a new profile with default settings. - - - 複製設定檔 - This is the header for a control that lets the user duplicate one of their existing profiles. - - - 複製 - Button label that duplicates the selected profile and navigates to the duplicate's page. - 此色彩配置是內建設定或已安裝擴充功能的一部分 @@ -1293,10 +1281,6 @@ ⚠ 您有尚未儲存的變更。 {Locked="⚠"} A disclaimer that appears when the unsaved changes to the settings are present. - - 新增設定檔 - Header for the "add new" menu item. This navigates to the page where users can add a new profile. - 設定檔 Header for the "profiles" menu items. This acts as a divider to introduce profile-related menu items. diff --git a/src/cascadia/TerminalSettingsEditor/SearchIndex.cpp b/src/cascadia/TerminalSettingsEditor/SearchIndex.cpp index e8fdb533cf3..014d3a9456a 100644 --- a/src/cascadia/TerminalSettingsEditor/SearchIndex.cpp +++ b/src/cascadia/TerminalSettingsEditor/SearchIndex.cpp @@ -40,8 +40,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // This allows us to prioritize certain fields over others when scoring search results. std::array, int>, 2> LocalizedIndexEntry::GetSearchableFields() const { - // Profile Defaults entries (DisplayTextUid starts with "Profile_") get a higher weight - const auto weight = til::starts_with(std::wstring_view{ Entry->ResourceName }, L"Profile_") ? WeightProfileDefaults : WeightDisplayTextLocalized; + // Profile Defaults entries get a higher weight so they rank above per-profile matches. + const auto weight = (std::wstring_view{ Entry->NavigationArgTag } == globalProfileTag) ? WeightProfileDefaults : WeightDisplayTextLocalized; return { { { std::optional{ DisplayTextLocalized }, weight }, { DisplayTextNeutral, WeightDisplayTextNeutral } } }; } @@ -66,13 +66,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation hstring runtimeObjContext{}; if (const auto profileVM = runtimeObj.try_as()) { - // No runtimeObjContext: profile name and icon should be enough runtimeObjLabel = profileVM.Name(); + runtimeObjContext = RS_(L"Nav_Profiles/Content"); } else if (const auto colorSchemeVM = runtimeObj.try_as()) { - // No runtimeObjContext: scheme name and generic icon should be enough runtimeObjLabel = colorSchemeVM.Name(); + runtimeObjContext = RS_(L"Nav_ColorSchemes/Content"); } else if (const auto ntmFolderEntryVM = runtimeObj.try_as()) { @@ -258,6 +258,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { localizedEntry.DisplayTextNeutral = EnglishOnlyResourceLoader().GetLocalizedString(entry.ResourceName); } + if (!entry.SecondaryLabelResourceName.empty()) + { + localizedEntry.SecondaryLabelLocalized = GetLibraryResourceString(entry.SecondaryLabelResourceName); + } localizedIndex.emplace_back(std::move(localizedEntry)); } return localizedIndex; @@ -342,7 +346,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation if (bestScore >= MinimumMatchScore) { - scoredResults.emplace_back(bestScore, winrt::make(index, &entry)); + scoredResults.emplace_back(bestScore, winrt::make(index, &entry, nullptr, std::nullopt, entry.SecondaryLabelLocalized)); } } diff --git a/src/cascadia/TerminalSettingsEditor/SearchIndex.h b/src/cascadia/TerminalSettingsEditor/SearchIndex.h index 276102d7dca..46536b601d1 100644 --- a/src/cascadia/TerminalSettingsEditor/SearchIndex.h +++ b/src/cascadia/TerminalSettingsEditor/SearchIndex.h @@ -20,6 +20,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { winrt::hstring DisplayTextLocalized; std::optional DisplayTextNeutral = std::nullopt; + // No "Neutral" copy of SecondaryLabelLocalized: unlike DisplayText, the secondary + // label is display-only (rendered in the search result row's caption). It is never + // passed to the fuzzy matcher, so a single localized string is sufficient. + winrt::hstring SecondaryLabelLocalized; const IndexEntry* Entry = nullptr; std::array, int>, 2> GetSearchableFields() const; diff --git a/tools/GenerateSettingsIndex.ps1 b/tools/GenerateSettingsIndex.ps1 index 8e43134f36d..af31dba469b 100644 --- a/tools/GenerateSettingsIndex.ps1 +++ b/tools/GenerateSettingsIndex.ps1 @@ -25,7 +25,9 @@ $ProhibitedUids = @( "ColorScheme_ColorsHeader", "ColorScheme_Rename", "Profile_ResetProfile", - "Profile_DeleteProfile" + "Profile_DeleteProfile", + "Profiles_ColorSchemesNavigator", + "Profiles_DefaultsNavigator" ) # Prohibited XAML files (already limited to Page root elements) @@ -57,6 +59,7 @@ $ClassMap = @{ ResourceName = "Nav_ColorSchemes/Content" NavigationParam = "ColorSchemes_Nav" SubPage = "BreadcrumbSubPage::None" + SecondaryLabel = "Nav_Profiles/Content" } "Microsoft::Terminal::Settings::Editor::Rendering" = @{ ResourceName = "Nav_Rendering/Content" @@ -87,6 +90,7 @@ $ClassMap = @{ ResourceName = "Nav_ProfileDefaults/Content" NavigationParam = "GlobalProfile_Nav" SubPage = "BreadcrumbSubPage::None" + SecondaryLabel = "Nav_Profiles/Content" } "Microsoft::Terminal::Settings::Editor::Profiles_Appearance" = @{ ResourceName = "Nav_ProfileDefaults/Content" @@ -103,9 +107,9 @@ $ClassMap = @{ NavigationParam = "GlobalProfile_Nav" SubPage = "BreadcrumbSubPage::Profile_Advanced" } - "Microsoft::Terminal::Settings::Editor::AddProfile" = @{ - ResourceName = "Nav_AddNewProfile/Content" - NavigationParam = "AddProfile" + "Microsoft::Terminal::Settings::Editor::Profiles" = @{ + ResourceName = "Nav_Profiles/Content" + NavigationParam = "Profiles_Nav" SubPage = "BreadcrumbSubPage::None" } } @@ -158,6 +162,7 @@ foreach ($xamlFile in Get-ChildItem -Path $SourceDir -Filter *.xaml) NavigationParam = $ClassMap[$pageClass].NavigationParam SubPage = $ClassMap[$pageClass].SubPage ElementName = $null # No specific element to navigate to, for the page itself + SecondaryLabel = $ClassMap[$pageClass].SecondaryLabel # Resource name for the result's sub-text (i.e. parent page name); $null if none File = $filename } } @@ -182,18 +187,6 @@ foreach ($xamlFile in Get-ChildItem -Path $SourceDir -Filter *.xaml) File = $filename } } - elseif ($filename -eq "AddProfile.xaml") - { - # "add new" button - $entries += [pscustomobject]@{ - ResourceName = "AddProfile_AddNewTextBlock/Text" - ParentPage = $pageClass - NavigationParam = $ClassMap[$pageClass].NavigationParam - SubPage = $ClassMap[$pageClass].SubPage - ElementName = "AddNewButton" - File = $filename - } - } # Iterate over all local:SettingsCard and local:SettingsExpander nodes foreach ($settingContainer in ($xml.SelectNodes("//local:SettingsCard", $xm) + $xml.SelectNodes("//local:SettingsExpander", $xm))) @@ -258,12 +251,15 @@ foreach ($xamlFile in Get-ChildItem -Path $SourceDir -Filter *.xaml) if ($includeInBuildIndex) { + # Profiles > Defaults results should show "Profiles" as secondary label + $buildSecondaryLabel = $navigationParam -eq "GlobalProfile_Nav" ? "Nav_Profiles/Content" : $null $entries += [pscustomobject]@{ ResourceName = "$($settingContainer.Uid)/Header" ParentPage = $pageClass NavigationParam = $navigationParam SubPage = $subPage ElementName = $name + SecondaryLabel = $buildSecondaryLabel File = $filename } } @@ -287,8 +283,9 @@ function FormatEntry($e) $formattedResourceName = 'USES_RESOURCE(L"{0}")' -f $e.ResourceName $formattedNavigationParam = 'L"{0}"' -f $e.NavigationParam # null Navigation param resolves to empty string $formattedElementName = 'L"{0}"' -f $e.ElementName + $formattedSecondaryLabel = [string]::IsNullOrEmpty($e.SecondaryLabel) ? 'L""' : ('USES_RESOURCE(L"{0}")' -f $e.SecondaryLabel) - return " IndexEntry{{ {0}, {1}, {2}, {3} }}, // {4}" -f ($formattedResourceName, $formattedNavigationParam, $e.SubPage, $formattedElementName, $e.File) + return " IndexEntry{{ {0}, {1}, {2}, {3}, {4} }}, // {5}" -f ($formattedResourceName, $formattedNavigationParam, $e.SubPage, $formattedElementName, $formattedSecondaryLabel, $e.File) } function FormatEntries($es) { @@ -296,7 +293,7 @@ function FormatEntries($es) { } # Sort and remove duplicates -$entries = $entries | Sort-Object ResourceName, ParentPage, NavigationParam, SubPage, ElementName, File -Unique +$entries = $entries | Sort-Object ResourceName, ParentPage, NavigationParam, SubPage, ElementName, SecondaryLabel, File -Unique $buildTimeEntries = @() $profileEntries = @() @@ -352,6 +349,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // x:Name of the SettingContainer to navigate to on the page (i.e. "DefaultProfile") wil::zwstring_view ElementName; + + // Resource name of the search result's secondary label (i.e. parent page name like "Nav_Profiles/Content"). + // Empty if the entry has no secondary label. + // NOTE: wrapped in USES_RESOURCE() like ResourceName when non-empty. + wil::zwstring_view SecondaryLabelResourceName; }; const std::array& LoadBuildTimeIndex(); @@ -460,4 +462,4 @@ Set-Content -LiteralPath $cppPath -Value $cpp -NoNewline Write-Host "Generated:" Write-Host " $headerPath" -Write-Host " $cppPath" \ No newline at end of file +Write-Host " $cppPath"