From fe46f70cf11f85756872106a3c2c17498498e209 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 7 Dec 2021 19:25:38 -0500 Subject: [PATCH] Apply MVVM for profiles in SUI (#11877) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cleans up `ProfileViewModel`, `Profiles`, and `ProfilePageNavigationState` to move all of the view model responsibilities over to `ProfileViewModel`. We don't actually store the `ProfilePageNavigationState` anymore. We only use it as a way to transfer information to the new page. - I pulled out `ProfileViewModel` into its own file to keep things cleaner. It was getting pretty big. - The font lists are now stored in a static location in `ProfileViewModel`, which means that we can reuse the same list between pages. - the profile pivot was also moved to the `ProfileViewModel` and stored as a static value. ✅ pivot behavior is the same ✅ font list is still populated --- .../TerminalSettingsEditor/Appearances.cpp | 4 +- .../TerminalSettingsEditor/Appearances.h | 6 +- .../TerminalSettingsEditor/GlobalAppearance.h | 4 +- .../TerminalSettingsEditor/Interaction.h | 4 +- src/cascadia/TerminalSettingsEditor/Launch.h | 6 +- .../TerminalSettingsEditor/MainPage.cpp | 20 +- .../TerminalSettingsEditor/MainPage.h | 1 - ...Microsoft.Terminal.Settings.Editor.vcxproj | 9 + ...t.Terminal.Settings.Editor.vcxproj.filters | 1 + .../ProfileViewModel.cpp | 417 +++++++++++++++++ .../TerminalSettingsEditor/ProfileViewModel.h | 151 ++++++ .../ProfileViewModel.idl | 97 ++++ .../TerminalSettingsEditor/Profiles.cpp | 443 +----------------- .../TerminalSettingsEditor/Profiles.h | 155 +----- .../TerminalSettingsEditor/Profiles.idl | 99 +--- .../TerminalSettingsEditor/Profiles.xaml | 167 +++---- src/cascadia/TerminalSettingsEditor/Utils.h | 46 +- 17 files changed, 832 insertions(+), 798 deletions(-) create mode 100644 src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp create mode 100644 src/cascadia/TerminalSettingsEditor/ProfileViewModel.h create mode 100644 src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.cpp b/src/cascadia/TerminalSettingsEditor/Appearances.cpp index 0eb24bb054f..a9add91b5c4 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.cpp +++ b/src/cascadia/TerminalSettingsEditor/Appearances.cpp @@ -142,7 +142,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { bool result{ false }; const auto currentFont{ Appearance().FontFace() }; - for (const auto& font : SourceProfile().MonospaceFontList()) + for (const auto& font : ProfileViewModel::MonospaceFontList()) { if (font.LocalizedName() == currentFont) { @@ -175,7 +175,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // look for the current font in our shown list of fonts const auto& appearanceVM{ Appearance() }; const auto appearanceFontFace{ appearanceVM.FontFace() }; - const auto& currentFontList{ ShowAllFonts() ? SourceProfile().CompleteFontList() : SourceProfile().MonospaceFontList() }; + const auto& currentFontList{ ShowAllFonts() ? ProfileViewModel::CompleteFontList() : ProfileViewModel::MonospaceFontList() }; IInspectable fallbackFont; for (const auto& font : currentFontList) { diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.h b/src/cascadia/TerminalSettingsEditor/Appearances.h index f6fa4b3d644..3ef5532db3b 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.h +++ b/src/cascadia/TerminalSettingsEditor/Appearances.h @@ -128,16 +128,16 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation bool IsCustomFontWeight(); WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, FontWeightList); - GETSET_BINDABLE_ENUM_SETTING(CursorShape, Microsoft::Terminal::Core::CursorStyle, Appearance, CursorShape); + GETSET_BINDABLE_ENUM_SETTING(CursorShape, Microsoft::Terminal::Core::CursorStyle, Appearance(), CursorShape); WINRT_PROPERTY(Windows::Foundation::Collections::IObservableVector, ColorSchemeList, nullptr); WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); DEPENDENCY_PROPERTY(Editor::AppearanceViewModel, Appearance); WINRT_PROPERTY(Editor::ProfileViewModel, SourceProfile, nullptr); - GETSET_BINDABLE_ENUM_SETTING(BackgroundImageStretchMode, Windows::UI::Xaml::Media::Stretch, Appearance, BackgroundImageStretchMode); + GETSET_BINDABLE_ENUM_SETTING(BackgroundImageStretchMode, Windows::UI::Xaml::Media::Stretch, Appearance(), BackgroundImageStretchMode); - GETSET_BINDABLE_ENUM_SETTING(IntenseTextStyle, Microsoft::Terminal::Settings::Model::IntenseStyle, Appearance, IntenseTextStyle); + GETSET_BINDABLE_ENUM_SETTING(IntenseTextStyle, Microsoft::Terminal::Settings::Model::IntenseStyle, Appearance(), IntenseTextStyle); private: bool _ShowAllFonts; diff --git a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.h b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.h index 8b0082a95ee..42822847640 100644 --- a/src/cascadia/TerminalSettingsEditor/GlobalAppearance.h +++ b/src/cascadia/TerminalSettingsEditor/GlobalAppearance.h @@ -28,8 +28,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation bool FeatureNotificationIconEnabled() const noexcept; WINRT_PROPERTY(Editor::GlobalAppearancePageNavigationState, State, nullptr); - GETSET_BINDABLE_ENUM_SETTING(Theme, winrt::Windows::UI::Xaml::ElementTheme, State().Globals, Theme); - GETSET_BINDABLE_ENUM_SETTING(TabWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, State().Globals, TabWidthMode); + GETSET_BINDABLE_ENUM_SETTING(Theme, winrt::Windows::UI::Xaml::ElementTheme, State().Globals(), Theme); + GETSET_BINDABLE_ENUM_SETTING(TabWidthMode, winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, State().Globals(), TabWidthMode); public: // LanguageDisplayConverter maps the given BCP 47 tag to a localized string. diff --git a/src/cascadia/TerminalSettingsEditor/Interaction.h b/src/cascadia/TerminalSettingsEditor/Interaction.h index bf6dc598b26..b06cf7ede0c 100644 --- a/src/cascadia/TerminalSettingsEditor/Interaction.h +++ b/src/cascadia/TerminalSettingsEditor/Interaction.h @@ -26,8 +26,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation WINRT_PROPERTY(Editor::InteractionPageNavigationState, State, nullptr); - GETSET_BINDABLE_ENUM_SETTING(TabSwitcherMode, Model::TabSwitcherMode, State().Globals, TabSwitcherMode); - GETSET_BINDABLE_ENUM_SETTING(CopyFormat, winrt::Microsoft::Terminal::Control::CopyFormat, State().Globals, CopyFormatting); + GETSET_BINDABLE_ENUM_SETTING(TabSwitcherMode, Model::TabSwitcherMode, State().Globals(), TabSwitcherMode); + GETSET_BINDABLE_ENUM_SETTING(CopyFormat, winrt::Microsoft::Terminal::Control::CopyFormat, State().Globals(), CopyFormatting); }; } diff --git a/src/cascadia/TerminalSettingsEditor/Launch.h b/src/cascadia/TerminalSettingsEditor/Launch.h index baf110c52e4..7d688a8822e 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.h +++ b/src/cascadia/TerminalSettingsEditor/Launch.h @@ -33,9 +33,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation WINRT_PROPERTY(Editor::LaunchPageNavigationState, State, nullptr); - GETSET_BINDABLE_ENUM_SETTING(FirstWindowPreference, Model::FirstWindowPreference, State().Settings().GlobalSettings, FirstWindowPreference); - GETSET_BINDABLE_ENUM_SETTING(LaunchMode, Model::LaunchMode, State().Settings().GlobalSettings, LaunchMode); - GETSET_BINDABLE_ENUM_SETTING(WindowingBehavior, Model::WindowingMode, State().Settings().GlobalSettings, WindowingBehavior); + GETSET_BINDABLE_ENUM_SETTING(FirstWindowPreference, Model::FirstWindowPreference, State().Settings().GlobalSettings(), FirstWindowPreference); + GETSET_BINDABLE_ENUM_SETTING(LaunchMode, Model::LaunchMode, State().Settings().GlobalSettings(), LaunchMode); + GETSET_BINDABLE_ENUM_SETTING(WindowingBehavior, Model::WindowingMode, State().Settings().GlobalSettings(), WindowingBehavior); }; } diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.cpp b/src/cascadia/TerminalSettingsEditor/MainPage.cpp index 414bd4f0d2a..2714a5cd88c 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.cpp +++ b/src/cascadia/TerminalSettingsEditor/MainPage.cpp @@ -313,12 +313,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { auto profileVM{ _viewModelForProfile(_settingsClone.ProfileDefaults(), _settingsClone) }; profileVM.IsBaseLayer(true); - _lastProfilesNavState = winrt::make(profileVM, - _settingsClone.GlobalSettings().ColorSchemes(), - _lastProfilesNavState, - *this); + auto state{ winrt::make(profileVM, + _settingsClone.GlobalSettings().ColorSchemes(), + *this) }; - contentFrame().Navigate(xaml_typename(), _lastProfilesNavState); + contentFrame().Navigate(xaml_typename(), state); } else if (clickedItemTag == colorSchemesTag) { @@ -343,15 +342,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // - profile - the profile object we are getting a view of void MainPage::_Navigate(const Editor::ProfileViewModel& profile) { - _lastProfilesNavState = winrt::make(profile, - _settingsClone.GlobalSettings().ColorSchemes(), - _lastProfilesNavState, - *this); + auto state{ winrt::make(profile, + _settingsClone.GlobalSettings().ColorSchemes(), + *this) }; // Add an event handler for when the user wants to delete a profile. - _lastProfilesNavState.DeleteProfile({ this, &MainPage::_DeleteProfile }); + profile.DeleteProfile({ this, &MainPage::_DeleteProfile }); - contentFrame().Navigate(xaml_typename(), _lastProfilesNavState); + contentFrame().Navigate(xaml_typename(), state); } void MainPage::OpenJsonTapped(IInspectable const& /*sender*/, Windows::UI::Xaml::Input::TappedRoutedEventArgs const& /*args*/) diff --git a/src/cascadia/TerminalSettingsEditor/MainPage.h b/src/cascadia/TerminalSettingsEditor/MainPage.h index 24c2c9cd503..1c0152ce0b6 100644 --- a/src/cascadia/TerminalSettingsEditor/MainPage.h +++ b/src/cascadia/TerminalSettingsEditor/MainPage.h @@ -46,7 +46,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void _Navigate(const Editor::ProfileViewModel& profile); winrt::Microsoft::Terminal::Settings::Editor::ColorSchemesPageNavigationState _colorSchemesNavState{ nullptr }; - winrt::Microsoft::Terminal::Settings::Editor::ProfilePageNavigationState _lastProfilesNavState{ nullptr }; }; } diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj index e38d6383c5d..e3d66548482 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj @@ -73,6 +73,10 @@ Profiles.xaml Code + + ProfileViewModel.idl + Code + Appearances.xaml Code @@ -173,6 +177,10 @@ Profiles.xaml Code + + ProfileViewModel.idl + Code + Appearances.xaml Code @@ -238,6 +246,7 @@ Profiles.xaml Code + Appearances.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 3b0b9186e6a..9ce0ebd1436 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj.filters @@ -17,6 +17,7 @@ + Converters diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp new file mode 100644 index 00000000000..7076b093197 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp @@ -0,0 +1,417 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "ProfileViewModel.h" +#include "ProfileViewModel.g.cpp" +#include "EnumEntry.h" + +#include +#include "..\WinRTUtils\inc\Utils.h" + +// This function is a copy of DxFontInfo::_NearbyCollection() with +// * the call to DxFontInfo::s_GetNearbyFonts() inlined +// * checkForUpdates for GetSystemFontCollection() set to true +static wil::com_ptr NearbyCollection(IDWriteFactory* dwriteFactory) +{ + // The convenience interfaces for loading fonts from files + // are only available on Windows 10+. + wil::com_ptr factory6; + // wil's query() facilities don't work inside WinRT land at the moment. + // They produce a compilation error due to IUnknown and winrt::Windows::Foundation::IUnknown being ambiguous. + if (!SUCCEEDED(dwriteFactory->QueryInterface(__uuidof(IDWriteFactory6), factory6.put_void()))) + { + return nullptr; + } + + wil::com_ptr systemFontCollection; + THROW_IF_FAILED(factory6->GetSystemFontCollection(false, systemFontCollection.addressof(), true)); + + wil::com_ptr systemFontSet; + THROW_IF_FAILED(systemFontCollection->GetFontSet(systemFontSet.addressof())); + + wil::com_ptr fontSetBuilder2; + THROW_IF_FAILED(factory6->CreateFontSetBuilder(fontSetBuilder2.addressof())); + + THROW_IF_FAILED(fontSetBuilder2->AddFontSet(systemFontSet.get())); + + { + const std::filesystem::path module{ wil::GetModuleFileNameW(nullptr) }; + const auto folder{ module.parent_path() }; + + for (const auto& p : std::filesystem::directory_iterator(folder)) + { + if (til::ends_with(p.path().native(), L".ttf")) + { + fontSetBuilder2->AddFontFile(p.path().c_str()); + } + } + } + + wil::com_ptr fontSet; + THROW_IF_FAILED(fontSetBuilder2->CreateFontSet(fontSet.addressof())); + + wil::com_ptr fontCollection; + THROW_IF_FAILED(factory6->CreateFontCollectionFromFontSet(fontSet.get(), &fontCollection)); + + return fontCollection; +} + +using namespace winrt::Windows::UI::Text; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Xaml::Controls; +using namespace winrt::Windows::UI::Xaml::Data; +using namespace winrt::Windows::UI::Xaml::Navigation; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Microsoft::Terminal::Settings::Model; + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + Windows::Foundation::Collections::IObservableVector ProfileViewModel::_MonospaceFontList{ nullptr }; + Windows::Foundation::Collections::IObservableVector ProfileViewModel::_FontList{ nullptr }; + ProfilesPivots ProfileViewModel::_LastActivePivot{ ProfilesPivots::General }; + + ProfileViewModel::ProfileViewModel(const Model::Profile& profile, const Model::CascadiaSettings& appSettings) : + _profile{ profile }, + _defaultAppearanceViewModel{ winrt::make(profile.DefaultAppearance().try_as()) }, + _originalProfileGuid{ profile.Guid() }, + _appSettings{ appSettings }, + _unfocusedAppearanceViewModel{ nullptr } + { + INITIALIZE_BINDABLE_ENUM_SETTING(AntiAliasingMode, TextAntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode, L"Profile_AntialiasingMode", L"Content"); + INITIALIZE_BINDABLE_ENUM_SETTING_REVERSE_ORDER(CloseOnExitMode, CloseOnExitMode, winrt::Microsoft::Terminal::Settings::Model::CloseOnExitMode, L"Profile_CloseOnExit", L"Content"); + INITIALIZE_BINDABLE_ENUM_SETTING(ScrollState, ScrollbarState, winrt::Microsoft::Terminal::Control::ScrollbarState, L"Profile_ScrollbarVisibility", L"Content"); + + // Add a property changed handler to our own property changed event. + // This propagates changes from the settings model to anybody listening to our + // unique view model members. + PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) { + const auto viewModelProperty{ args.PropertyName() }; + if (viewModelProperty == L"IsBaseLayer") + { + // we _always_ want to show the background image settings in base layer + _NotifyChanges(L"BackgroundImageSettingsVisible"); + } + else if (viewModelProperty == L"StartingDirectory") + { + // notify listener that all starting directory related values might have changed + // NOTE: this is similar to what is done with BackgroundImagePath above + _NotifyChanges(L"UseParentProcessDirectory", L"UseCustomStartingDirectory"); + } + else if (viewModelProperty == L"UseAcrylic") + { + // GH#11372: If we're on Windows 10, and someone turns off + // acrylic, we're going to disable opacity for them. Opacity + // doesn't work without acrylic on Windows 10. + // + // BODGY: CascadiaSettings's function IsDefaultTerminalAvailable + // is basically a "are we on Windows 11" check, because defterm + // only works on Win11. So we'll use that. + // + // Remove when we can remove the rest of GH#11285 + if (!UseAcrylic() && !CascadiaSettings::IsDefaultTerminalAvailable()) + { + Opacity(1.0); + } + } + }); + + // Do the same for the starting directory + if (!StartingDirectory().empty()) + { + _lastStartingDirectoryPath = StartingDirectory(); + } + + // generate the font list, if we don't have one + if (!_FontList || !_MonospaceFontList) + { + UpdateFontList(); + } + + if (profile.HasUnfocusedAppearance()) + { + _unfocusedAppearanceViewModel = winrt::make(profile.UnfocusedAppearance().try_as()); + } + + _defaultAppearanceViewModel.IsDefault(true); + } + + Model::TerminalSettings ProfileViewModel::TermSettings() const + { + return Model::TerminalSettings::CreateWithProfile(_appSettings, _profile, nullptr).DefaultSettings(); + } + + // Method Description: + // - Updates the lists of fonts and sorts them alphabetically + void ProfileViewModel::UpdateFontList() noexcept + try + { + // initialize font list + std::vector fontList; + std::vector monospaceFontList; + + // get a DWriteFactory + com_ptr factory; + THROW_IF_FAILED(DWriteCreateFactory( + DWRITE_FACTORY_TYPE_SHARED, + __uuidof(IDWriteFactory), + reinterpret_cast<::IUnknown**>(factory.put()))); + + // get the font collection; subscribe to updates + const auto fontCollection = NearbyCollection(factory.get()); + + for (UINT32 i = 0; i < fontCollection->GetFontFamilyCount(); ++i) + { + try + { + // get the font family + com_ptr fontFamily; + THROW_IF_FAILED(fontCollection->GetFontFamily(i, fontFamily.put())); + + // get the font's localized names + com_ptr localizedFamilyNames; + THROW_IF_FAILED(fontFamily->GetFamilyNames(localizedFamilyNames.put())); + + // construct a font entry for tracking + if (const auto fontEntry{ _GetFont(localizedFamilyNames) }) + { + // check if the font is monospaced + try + { + com_ptr font; + THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(DWRITE_FONT_WEIGHT::DWRITE_FONT_WEIGHT_NORMAL, + DWRITE_FONT_STRETCH::DWRITE_FONT_STRETCH_NORMAL, + DWRITE_FONT_STYLE::DWRITE_FONT_STYLE_NORMAL, + font.put())); + + // add the font name to our list of monospace fonts + const auto castedFont{ font.try_as() }; + if (castedFont && castedFont->IsMonospacedFont()) + { + monospaceFontList.emplace_back(fontEntry); + } + } + CATCH_LOG(); + + // add the font name to our list of all fonts + fontList.emplace_back(std::move(fontEntry)); + } + } + CATCH_LOG(); + } + + // sort and save the lists + std::sort(begin(fontList), end(fontList), FontComparator()); + _FontList = single_threaded_observable_vector(std::move(fontList)); + + std::sort(begin(monospaceFontList), end(monospaceFontList), FontComparator()); + _MonospaceFontList = single_threaded_observable_vector(std::move(monospaceFontList)); + } + CATCH_LOG(); + + Editor::Font ProfileViewModel::_GetFont(com_ptr localizedFamilyNames) + { + // used for the font's name as an identifier (i.e. text block's font family property) + std::wstring nameID; + UINT32 nameIDIndex; + + // used for the font's localized name + std::wstring localizedName; + UINT32 localizedNameIndex; + + // use our current locale to find the localized name + BOOL exists{ FALSE }; + HRESULT hr; + wchar_t localeName[LOCALE_NAME_MAX_LENGTH]; + if (GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH)) + { + hr = localizedFamilyNames->FindLocaleName(localeName, &localizedNameIndex, &exists); + } + if (SUCCEEDED(hr) && !exists) + { + // if we can't find the font for our locale, fallback to the en-us one + // Source: https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-findlocalename + hr = localizedFamilyNames->FindLocaleName(L"en-us", &localizedNameIndex, &exists); + } + if (!exists) + { + // failed to find the correct locale, using the first one + localizedNameIndex = 0; + } + + // get the localized name + UINT32 nameLength; + THROW_IF_FAILED(localizedFamilyNames->GetStringLength(localizedNameIndex, &nameLength)); + + localizedName.resize(nameLength); + THROW_IF_FAILED(localizedFamilyNames->GetString(localizedNameIndex, localizedName.data(), nameLength + 1)); + + // now get the nameID + hr = localizedFamilyNames->FindLocaleName(L"en-us", &nameIDIndex, &exists); + if (FAILED(hr) || !exists) + { + // failed to find it, using the first one + nameIDIndex = 0; + } + + // get the nameID + THROW_IF_FAILED(localizedFamilyNames->GetStringLength(nameIDIndex, &nameLength)); + nameID.resize(nameLength); + THROW_IF_FAILED(localizedFamilyNames->GetString(nameIDIndex, nameID.data(), nameLength + 1)); + + if (!nameID.empty() && !localizedName.empty()) + { + return make(nameID, localizedName); + } + return nullptr; + } + + Windows::Foundation::Collections::IMapView ProfileViewModel::Schemes() const noexcept + { + return _Schemes; + } + + void ProfileViewModel::Schemes(const Windows::Foundation::Collections::IMapView& val) noexcept + { + _Schemes = val; + } + + winrt::guid ProfileViewModel::OriginalProfileGuid() const noexcept + { + return _originalProfileGuid; + } + + bool ProfileViewModel::CanDeleteProfile() const + { + return !IsBaseLayer(); + } + + Editor::AppearanceViewModel ProfileViewModel::DefaultAppearance() + { + return _defaultAppearanceViewModel; + } + + bool ProfileViewModel::HasUnfocusedAppearance() + { + return _profile.HasUnfocusedAppearance(); + } + + bool ProfileViewModel::EditableUnfocusedAppearance() const noexcept + { + return Feature_EditableUnfocusedAppearance::IsEnabled(); + } + + bool ProfileViewModel::ShowUnfocusedAppearance() + { + return EditableUnfocusedAppearance() && HasUnfocusedAppearance(); + } + + void ProfileViewModel::CreateUnfocusedAppearance() + { + _profile.CreateUnfocusedAppearance(); + + _unfocusedAppearanceViewModel = winrt::make(_profile.UnfocusedAppearance().try_as()); + _unfocusedAppearanceViewModel.Schemes(_Schemes); + _unfocusedAppearanceViewModel.WindowRoot(_WindowRoot); + + _NotifyChanges(L"UnfocusedAppearance", L"HasUnfocusedAppearance", L"ShowUnfocusedAppearance"); + } + + void ProfileViewModel::DeleteUnfocusedAppearance() + { + _profile.DeleteUnfocusedAppearance(); + + _unfocusedAppearanceViewModel = nullptr; + + _NotifyChanges(L"UnfocusedAppearance", L"HasUnfocusedAppearance", L"ShowUnfocusedAppearance"); + } + + Editor::AppearanceViewModel ProfileViewModel::UnfocusedAppearance() + { + return _unfocusedAppearanceViewModel; + } + + bool ProfileViewModel::AtlasEngineAvailable() const noexcept + { + return Feature_AtlasEngine::IsEnabled(); + } + + bool ProfileViewModel::UseParentProcessDirectory() + { + return StartingDirectory().empty(); + } + + // This function simply returns the opposite of UseParentProcessDirectory. + // We bind the 'IsEnabled' parameters of the textbox and browse button + // to this because it needs to be the reverse of UseParentProcessDirectory + // but we don't want to create a whole new converter for inverting a boolean + bool ProfileViewModel::UseCustomStartingDirectory() + { + return !UseParentProcessDirectory(); + } + + void ProfileViewModel::UseParentProcessDirectory(const bool useParent) + { + if (useParent) + { + // Stash the current value of StartingDirectory. If the user + // checks and un-checks the "Use parent process directory" button, we want + // the path that we display in the text box to remain unchanged. + // + // Only stash this value if it's not empty + if (!StartingDirectory().empty()) + { + _lastStartingDirectoryPath = StartingDirectory(); + } + StartingDirectory(L""); + } + else + { + // Restore the path we had previously cached as long as it wasn't empty + // If it was empty, set the starting directory to %USERPROFILE% + // (we need to set it to something non-empty otherwise we will automatically + // disable the text box) + if (_lastStartingDirectoryPath.empty()) + { + StartingDirectory(L"%USERPROFILE%"); + } + else + { + StartingDirectory(_lastStartingDirectoryPath); + } + } + } + + bool ProfileViewModel::IsBellStyleFlagSet(const uint32_t flag) + { + return (WI_EnumValue(BellStyle()) & flag) == flag; + } + + void ProfileViewModel::SetBellStyleAudible(winrt::Windows::Foundation::IReference on) + { + auto currentStyle = BellStyle(); + WI_UpdateFlag(currentStyle, Model::BellStyle::Audible, winrt::unbox_value(on)); + BellStyle(currentStyle); + } + + void ProfileViewModel::SetBellStyleWindow(winrt::Windows::Foundation::IReference on) + { + auto currentStyle = BellStyle(); + WI_UpdateFlag(currentStyle, Model::BellStyle::Window, winrt::unbox_value(on)); + BellStyle(currentStyle); + } + + void ProfileViewModel::SetBellStyleTaskbar(winrt::Windows::Foundation::IReference on) + { + auto currentStyle = BellStyle(); + WI_UpdateFlag(currentStyle, Model::BellStyle::Taskbar, winrt::unbox_value(on)); + BellStyle(currentStyle); + } + + void ProfileViewModel::DeleteProfile() + { + auto deleteProfileArgs{ winrt::make_self(Guid()) }; + _DeleteProfileHandlers(*this, *deleteProfileArgs); + } +} diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h new file mode 100644 index 00000000000..0cce255e8c2 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h @@ -0,0 +1,151 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "DeleteProfileEventArgs.g.h" +#include "ProfileViewModel.g.h" +#include "Utils.h" +#include "ViewModelHelpers.h" +#include "Appearances.h" + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + struct ProfileViewModel : ProfileViewModelT, ViewModelHelper + { + public: + // font face + static void UpdateFontList() noexcept; + static Windows::Foundation::Collections::IObservableVector CompleteFontList() noexcept { return _FontList; }; + static Windows::Foundation::Collections::IObservableVector MonospaceFontList() noexcept { return _MonospaceFontList; }; + + static ProfilesPivots LastActivePivot() noexcept { return _LastActivePivot; }; + static void LastActivePivot(Editor::ProfilesPivots val) noexcept { _LastActivePivot = val; }; + + ProfileViewModel(const Model::Profile& profile, const Model::CascadiaSettings& settings); + Model::TerminalSettings TermSettings() const; + void DeleteProfile(); + + Windows::Foundation::Collections::IMapView Schemes() const noexcept; + void Schemes(const Windows::Foundation::Collections::IMapView& val) noexcept; + + // bell style bits + bool IsBellStyleFlagSet(const uint32_t flag); + void SetBellStyleAudible(winrt::Windows::Foundation::IReference on); + void SetBellStyleWindow(winrt::Windows::Foundation::IReference on); + void SetBellStyleTaskbar(winrt::Windows::Foundation::IReference on); + + void SetAcrylicOpacityPercentageValue(double value) + { + Opacity(winrt::Microsoft::Terminal::Settings::Editor::Converters::PercentageValueToPercentage(value)); + + // GH#11372: If we're on Windows 10, and someone wants opacity, then + // we'll turn acrylic on for them. Opacity doesn't work without + // acrylic on Windows 10. + // + // BODGY: CascadiaSettings's function IsDefaultTerminalAvailable + // is basically a "are we on Windows 11" check, because defterm + // only works on Win11. So we'll use that. + // + // Remove when we can remove the rest of GH#11285 + if (value < 100.0 && + !winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings::IsDefaultTerminalAvailable()) + { + UseAcrylic(true); + } + }; + + void SetPadding(double value) + { + Padding(to_hstring(value)); + } + + // starting directory + bool UseParentProcessDirectory(); + void UseParentProcessDirectory(const bool useParent); + bool UseCustomStartingDirectory(); + + // general profile knowledge + winrt::guid OriginalProfileGuid() const noexcept; + bool CanDeleteProfile() const; + Editor::AppearanceViewModel DefaultAppearance(); + Editor::AppearanceViewModel UnfocusedAppearance(); + bool HasUnfocusedAppearance(); + bool EditableUnfocusedAppearance() const noexcept; + bool ShowUnfocusedAppearance(); + void CreateUnfocusedAppearance(); + void DeleteUnfocusedAppearance(); + bool AtlasEngineAvailable() const noexcept; + + WINRT_PROPERTY(bool, IsBaseLayer, false); + WINRT_PROPERTY(IHostedInWindow, WindowRoot, nullptr); + GETSET_BINDABLE_ENUM_SETTING(AntiAliasingMode, Microsoft::Terminal::Control::TextAntialiasingMode, _profile, AntialiasingMode); + GETSET_BINDABLE_ENUM_SETTING(CloseOnExitMode, Microsoft::Terminal::Settings::Model::CloseOnExitMode, _profile, CloseOnExit); + GETSET_BINDABLE_ENUM_SETTING(ScrollState, Microsoft::Terminal::Control::ScrollbarState, _profile, ScrollState); + + PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, Guid); + PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, ConnectionType); + OBSERVABLE_PROJECTED_SETTING(_profile, Name); + OBSERVABLE_PROJECTED_SETTING(_profile, Source); + OBSERVABLE_PROJECTED_SETTING(_profile, Hidden); + OBSERVABLE_PROJECTED_SETTING(_profile, Icon); + OBSERVABLE_PROJECTED_SETTING(_profile, CloseOnExit); + OBSERVABLE_PROJECTED_SETTING(_profile, TabTitle); + OBSERVABLE_PROJECTED_SETTING(_profile, TabColor); + OBSERVABLE_PROJECTED_SETTING(_profile, SuppressApplicationTitle); + OBSERVABLE_PROJECTED_SETTING(_profile, UseAcrylic); + OBSERVABLE_PROJECTED_SETTING(_profile, ScrollState); + OBSERVABLE_PROJECTED_SETTING(_profile, Padding); + OBSERVABLE_PROJECTED_SETTING(_profile, Commandline); + OBSERVABLE_PROJECTED_SETTING(_profile, StartingDirectory); + OBSERVABLE_PROJECTED_SETTING(_profile, AntialiasingMode); + OBSERVABLE_PROJECTED_SETTING(_profile.DefaultAppearance(), Foreground); + OBSERVABLE_PROJECTED_SETTING(_profile.DefaultAppearance(), Background); + OBSERVABLE_PROJECTED_SETTING(_profile.DefaultAppearance(), SelectionBackground); + OBSERVABLE_PROJECTED_SETTING(_profile.DefaultAppearance(), CursorColor); + OBSERVABLE_PROJECTED_SETTING(_profile.DefaultAppearance(), Opacity); + OBSERVABLE_PROJECTED_SETTING(_profile, HistorySize); + OBSERVABLE_PROJECTED_SETTING(_profile, SnapOnInput); + OBSERVABLE_PROJECTED_SETTING(_profile, AltGrAliasing); + OBSERVABLE_PROJECTED_SETTING(_profile, BellStyle); + OBSERVABLE_PROJECTED_SETTING(_profile, UseAtlasEngine); + + TYPED_EVENT(DeleteProfile, Editor::ProfileViewModel, Editor::DeleteProfileEventArgs); + + private: + Model::Profile _profile; + winrt::guid _originalProfileGuid; + winrt::hstring _lastBgImagePath; + winrt::hstring _lastStartingDirectoryPath; + Editor::AppearanceViewModel _defaultAppearanceViewModel; + Windows::Foundation::Collections::IMapView _Schemes; + + static Windows::Foundation::Collections::IObservableVector _MonospaceFontList; + static Windows::Foundation::Collections::IObservableVector _FontList; + static ProfilesPivots _LastActivePivot; + + static Editor::Font _GetFont(com_ptr localizedFamilyNames); + + Model::CascadiaSettings _appSettings; + Editor::AppearanceViewModel _unfocusedAppearanceViewModel; + }; + + struct DeleteProfileEventArgs : + public DeleteProfileEventArgsT + { + public: + DeleteProfileEventArgs(guid profileGuid) : + _ProfileGuid(profileGuid) {} + + guid ProfileGuid() const noexcept { return _ProfileGuid; } + + private: + guid _ProfileGuid{}; + }; +}; + +namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation +{ + // Since we have static functions, we need a factory. + BASIC_FACTORY(ProfileViewModel); +} diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl new file mode 100644 index 00000000000..7215989d626 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "EnumEntry.idl"; +import "MainPage.idl"; +import "Appearances.idl"; + +#include "ViewModelHelpers.idl.h" + +#define OBSERVABLE_PROJECTED_PROFILE_SETTING(Type, Name) \ + OBSERVABLE_PROJECTED_SETTING(Type, Name); \ + Object Name##OverrideSource { get; } + +namespace Microsoft.Terminal.Settings.Editor +{ + runtimeclass DeleteProfileEventArgs + { + Guid ProfileGuid { get; }; + } + + enum ProfilesPivots + { + General = 0, + Appearance = 1, + Advanced = 2 + }; + + runtimeclass ProfileViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged + { + static Windows.Foundation.Collections.IObservableVector CompleteFontList { get; }; + static Windows.Foundation.Collections.IObservableVector MonospaceFontList { get; }; + static ProfilesPivots LastActivePivot; + + Microsoft.Terminal.Settings.Model.TerminalSettings TermSettings { get; }; + + event Windows.Foundation.TypedEventHandler DeleteProfile; + + void SetAcrylicOpacityPercentageValue(Double value); + void SetPadding(Double value); + + Boolean IsBellStyleFlagSet(UInt32 flag); + void SetBellStyleAudible(Windows.Foundation.IReference on); + void SetBellStyleWindow(Windows.Foundation.IReference on); + void SetBellStyleTaskbar(Windows.Foundation.IReference on); + + IInspectable CurrentAntiAliasingMode; + Windows.Foundation.Collections.IObservableVector AntiAliasingModeList { get; }; + + IInspectable CurrentCloseOnExitMode; + Windows.Foundation.Collections.IObservableVector CloseOnExitModeList { get; }; + + IInspectable CurrentScrollState; + Windows.Foundation.Collections.IObservableVector ScrollStateList { get; }; + + Boolean CanDeleteProfile { get; }; + Boolean IsBaseLayer; + Boolean UseParentProcessDirectory; + Boolean UseCustomStartingDirectory { get; }; + AppearanceViewModel DefaultAppearance { get; }; + Guid OriginalProfileGuid { get; }; + Boolean HasUnfocusedAppearance { get; }; + Boolean EditableUnfocusedAppearance { get; }; + Boolean ShowUnfocusedAppearance { get; }; + AppearanceViewModel UnfocusedAppearance { get; }; + Boolean AtlasEngineAvailable { get; }; + + void CreateUnfocusedAppearance(); + void DeleteUnfocusedAppearance(); + + OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Name); + PERMANENT_OBSERVABLE_PROJECTED_SETTING(Guid, Guid); + OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Source); + PERMANENT_OBSERVABLE_PROJECTED_SETTING(Guid, ConnectionType); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, Hidden); + OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Icon); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Settings.Model.CloseOnExitMode, CloseOnExit); + OBSERVABLE_PROJECTED_PROFILE_SETTING(String, TabTitle); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Windows.Foundation.IReference, TabColor); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, SuppressApplicationTitle); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, UseAcrylic); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Double, Opacity); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.ScrollbarState, ScrollState); + OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Padding); + OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Commandline); + OBSERVABLE_PROJECTED_PROFILE_SETTING(String, StartingDirectory); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.TextAntialiasingMode, AntialiasingMode); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Windows.Foundation.IReference, Foreground); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Windows.Foundation.IReference, Background); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Windows.Foundation.IReference, SelectionBackground); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Windows.Foundation.IReference, CursorColor); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Int32, HistorySize); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, SnapOnInput); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AltGrAliasing); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Settings.Model.BellStyle, BellStyle); + OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, UseAtlasEngine); + } +} diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.cpp b/src/cascadia/TerminalSettingsEditor/Profiles.cpp index bb429a454d0..3b01f7cbfc4 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles.cpp +++ b/src/cascadia/TerminalSettingsEditor/Profiles.cpp @@ -6,59 +6,10 @@ #include "PreviewConnection.h" #include "Profiles.g.cpp" -#include "EnumEntry.h" #include #include "..\WinRTUtils\inc\Utils.h" -// This function is a copy of DxFontInfo::_NearbyCollection() with -// * the call to DxFontInfo::s_GetNearbyFonts() inlined -// * checkForUpdates for GetSystemFontCollection() set to true -static wil::com_ptr NearbyCollection(IDWriteFactory* dwriteFactory) -{ - // The convenience interfaces for loading fonts from files - // are only available on Windows 10+. - wil::com_ptr factory6; - // wil's query() facilities don't work inside WinRT land at the moment. - // They produce a compilation error due to IUnknown and winrt::Windows::Foundation::IUnknown being ambiguous. - if (!SUCCEEDED(dwriteFactory->QueryInterface(__uuidof(IDWriteFactory6), factory6.put_void()))) - { - return nullptr; - } - - wil::com_ptr systemFontCollection; - THROW_IF_FAILED(factory6->GetSystemFontCollection(false, systemFontCollection.addressof(), true)); - - wil::com_ptr systemFontSet; - THROW_IF_FAILED(systemFontCollection->GetFontSet(systemFontSet.addressof())); - - wil::com_ptr fontSetBuilder2; - THROW_IF_FAILED(factory6->CreateFontSetBuilder(fontSetBuilder2.addressof())); - - THROW_IF_FAILED(fontSetBuilder2->AddFontSet(systemFontSet.get())); - - { - const std::filesystem::path module{ wil::GetModuleFileNameW(nullptr) }; - const auto folder{ module.parent_path() }; - - for (const auto& p : std::filesystem::directory_iterator(folder)) - { - if (til::ends_with(p.path().native(), L".ttf")) - { - fontSetBuilder2->AddFontFile(p.path().c_str()); - } - } - } - - wil::com_ptr fontSet; - THROW_IF_FAILED(fontSetBuilder2->CreateFontSet(fontSet.addressof())); - - wil::com_ptr fontCollection; - THROW_IF_FAILED(factory6->CreateFontCollectionFromFontSet(fontSet.get(), &fontCollection)); - - return fontCollection; -} - using namespace winrt::Windows::UI::Text; using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Xaml::Controls; @@ -70,341 +21,11 @@ using namespace winrt::Microsoft::Terminal::Settings::Model; namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { - Windows::Foundation::Collections::IObservableVector ProfileViewModel::_MonospaceFontList{ nullptr }; - Windows::Foundation::Collections::IObservableVector ProfileViewModel::_FontList{ nullptr }; - - ProfileViewModel::ProfileViewModel(const Model::Profile& profile, const Model::CascadiaSettings& appSettings) : - _profile{ profile }, - _defaultAppearanceViewModel{ winrt::make(profile.DefaultAppearance().try_as()) }, - _originalProfileGuid{ profile.Guid() }, - _appSettings{ appSettings }, - _unfocusedAppearanceViewModel{ nullptr } - { - // Add a property changed handler to our own property changed event. - // This propagates changes from the settings model to anybody listening to our - // unique view model members. - PropertyChanged([this](auto&&, const PropertyChangedEventArgs& args) { - const auto viewModelProperty{ args.PropertyName() }; - if (viewModelProperty == L"IsBaseLayer") - { - // we _always_ want to show the background image settings in base layer - _NotifyChanges(L"BackgroundImageSettingsVisible"); - } - else if (viewModelProperty == L"StartingDirectory") - { - // notify listener that all starting directory related values might have changed - // NOTE: this is similar to what is done with BackgroundImagePath above - _NotifyChanges(L"UseParentProcessDirectory", L"UseCustomStartingDirectory"); - } - else if (viewModelProperty == L"UseAcrylic") - { - // GH#11372: If we're on Windows 10, and someone turns off - // acrylic, we're going to disable opacity for them. Opacity - // doesn't work without acrylic on Windows 10. - // - // BODGY: CascadiaSettings's function IsDefaultTerminalAvailable - // is basically a "are we on Windows 11" check, because defterm - // only works on Win11. So we'll use that. - // - // Remove when we can remove the rest of GH#11285 - if (!UseAcrylic() && !CascadiaSettings::IsDefaultTerminalAvailable()) - { - Opacity(1.0); - } - } - }); - - // Do the same for the starting directory - if (!StartingDirectory().empty()) - { - _lastStartingDirectoryPath = StartingDirectory(); - } - - // generate the font list, if we don't have one - if (!_FontList || !_MonospaceFontList) - { - UpdateFontList(); - } - - if (profile.HasUnfocusedAppearance()) - { - _unfocusedAppearanceViewModel = winrt::make(profile.UnfocusedAppearance().try_as()); - } - - _defaultAppearanceViewModel.IsDefault(true); - } - - Model::TerminalSettings ProfileViewModel::TermSettings() const - { - return Model::TerminalSettings::CreateWithProfile(_appSettings, _profile, nullptr).DefaultSettings(); - } - - // Method Description: - // - Updates the lists of fonts and sorts them alphabetically - void ProfileViewModel::UpdateFontList() noexcept - try - { - // initialize font list - std::vector fontList; - std::vector monospaceFontList; - - // get a DWriteFactory - com_ptr factory; - THROW_IF_FAILED(DWriteCreateFactory( - DWRITE_FACTORY_TYPE_SHARED, - __uuidof(IDWriteFactory), - reinterpret_cast<::IUnknown**>(factory.put()))); - - // get the font collection; subscribe to updates - const auto fontCollection = NearbyCollection(factory.get()); - - for (UINT32 i = 0; i < fontCollection->GetFontFamilyCount(); ++i) - { - try - { - // get the font family - com_ptr fontFamily; - THROW_IF_FAILED(fontCollection->GetFontFamily(i, fontFamily.put())); - - // get the font's localized names - com_ptr localizedFamilyNames; - THROW_IF_FAILED(fontFamily->GetFamilyNames(localizedFamilyNames.put())); - - // construct a font entry for tracking - if (const auto fontEntry{ _GetFont(localizedFamilyNames) }) - { - // check if the font is monospaced - try - { - com_ptr font; - THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(DWRITE_FONT_WEIGHT::DWRITE_FONT_WEIGHT_NORMAL, - DWRITE_FONT_STRETCH::DWRITE_FONT_STRETCH_NORMAL, - DWRITE_FONT_STYLE::DWRITE_FONT_STYLE_NORMAL, - font.put())); - - // add the font name to our list of monospace fonts - const auto castedFont{ font.try_as() }; - if (castedFont && castedFont->IsMonospacedFont()) - { - monospaceFontList.emplace_back(fontEntry); - } - } - CATCH_LOG(); - - // add the font name to our list of all fonts - fontList.emplace_back(std::move(fontEntry)); - } - } - CATCH_LOG(); - } - - // sort and save the lists - std::sort(begin(fontList), end(fontList), FontComparator()); - _FontList = single_threaded_observable_vector(std::move(fontList)); - - std::sort(begin(monospaceFontList), end(monospaceFontList), FontComparator()); - _MonospaceFontList = single_threaded_observable_vector(std::move(monospaceFontList)); - } - CATCH_LOG(); - - Editor::Font ProfileViewModel::_GetFont(com_ptr localizedFamilyNames) - { - // used for the font's name as an identifier (i.e. text block's font family property) - std::wstring nameID; - UINT32 nameIDIndex; - - // used for the font's localized name - std::wstring localizedName; - UINT32 localizedNameIndex; - - // use our current locale to find the localized name - BOOL exists{ FALSE }; - HRESULT hr; - wchar_t localeName[LOCALE_NAME_MAX_LENGTH]; - if (GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH)) - { - hr = localizedFamilyNames->FindLocaleName(localeName, &localizedNameIndex, &exists); - } - if (SUCCEEDED(hr) && !exists) - { - // if we can't find the font for our locale, fallback to the en-us one - // Source: https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-findlocalename - hr = localizedFamilyNames->FindLocaleName(L"en-us", &localizedNameIndex, &exists); - } - if (!exists) - { - // failed to find the correct locale, using the first one - localizedNameIndex = 0; - } - - // get the localized name - UINT32 nameLength; - THROW_IF_FAILED(localizedFamilyNames->GetStringLength(localizedNameIndex, &nameLength)); - - localizedName.resize(nameLength); - THROW_IF_FAILED(localizedFamilyNames->GetString(localizedNameIndex, localizedName.data(), nameLength + 1)); - - // now get the nameID - hr = localizedFamilyNames->FindLocaleName(L"en-us", &nameIDIndex, &exists); - if (FAILED(hr) || !exists) - { - // failed to find it, using the first one - nameIDIndex = 0; - } - - // get the nameID - THROW_IF_FAILED(localizedFamilyNames->GetStringLength(nameIDIndex, &nameLength)); - nameID.resize(nameLength); - THROW_IF_FAILED(localizedFamilyNames->GetString(nameIDIndex, nameID.data(), nameLength + 1)); - - if (!nameID.empty() && !localizedName.empty()) - { - return make(nameID, localizedName); - } - return nullptr; - } - - IObservableVector ProfileViewModel::CompleteFontList() const noexcept - { - return _FontList; - } - - IObservableVector ProfileViewModel::MonospaceFontList() const noexcept - { - return _MonospaceFontList; - } - - winrt::guid ProfileViewModel::OriginalProfileGuid() const noexcept - { - return _originalProfileGuid; - } - - bool ProfileViewModel::CanDeleteProfile() const - { - return !IsBaseLayer(); - } - - Editor::AppearanceViewModel ProfileViewModel::DefaultAppearance() - { - return _defaultAppearanceViewModel; - } - - bool ProfileViewModel::HasUnfocusedAppearance() - { - return _profile.HasUnfocusedAppearance(); - } - - bool ProfileViewModel::EditableUnfocusedAppearance() - { - if constexpr (Feature_EditableUnfocusedAppearance::IsEnabled()) - { - return true; - } - return false; - } - - bool ProfileViewModel::ShowUnfocusedAppearance() - { - return EditableUnfocusedAppearance() && HasUnfocusedAppearance(); - } - - void ProfileViewModel::CreateUnfocusedAppearance(const Windows::Foundation::Collections::IMapView& schemes, - const IHostedInWindow& windowRoot) - { - _profile.CreateUnfocusedAppearance(); - - _unfocusedAppearanceViewModel = winrt::make(_profile.UnfocusedAppearance().try_as()); - _unfocusedAppearanceViewModel.Schemes(schemes); - _unfocusedAppearanceViewModel.WindowRoot(windowRoot); - - _NotifyChanges(L"UnfocusedAppearance", L"HasUnfocusedAppearance", L"ShowUnfocusedAppearance"); - } - - void ProfileViewModel::DeleteUnfocusedAppearance() - { - _profile.DeleteUnfocusedAppearance(); - - _unfocusedAppearanceViewModel = nullptr; - - _NotifyChanges(L"UnfocusedAppearance", L"HasUnfocusedAppearance", L"ShowUnfocusedAppearance"); - } - - Editor::AppearanceViewModel ProfileViewModel::UnfocusedAppearance() - { - return _unfocusedAppearanceViewModel; - } - - bool ProfileViewModel::UseParentProcessDirectory() - { - return StartingDirectory().empty(); - } - - // This function simply returns the opposite of UseParentProcessDirectory. - // We bind the 'IsEnabled' parameters of the textbox and browse button - // to this because it needs to be the reverse of UseParentProcessDirectory - // but we don't want to create a whole new converter for inverting a boolean - bool ProfileViewModel::UseCustomStartingDirectory() - { - return !UseParentProcessDirectory(); - } - - void ProfileViewModel::UseParentProcessDirectory(const bool useParent) - { - if (useParent) - { - // Stash the current value of StartingDirectory. If the user - // checks and un-checks the "Use parent process directory" button, we want - // the path that we display in the text box to remain unchanged. - // - // Only stash this value if it's not empty - if (!StartingDirectory().empty()) - { - _lastStartingDirectoryPath = StartingDirectory(); - } - StartingDirectory(L""); - } - else - { - // Restore the path we had previously cached as long as it wasn't empty - // If it was empty, set the starting directory to %USERPROFILE% - // (we need to set it to something non-empty otherwise we will automatically - // disable the text box) - if (_lastStartingDirectoryPath.empty()) - { - StartingDirectory(L"%USERPROFILE%"); - } - else - { - StartingDirectory(_lastStartingDirectoryPath); - } - } - } - - void ProfilePageNavigationState::DeleteProfile() - { - auto deleteProfileArgs{ winrt::make_self(_Profile.Guid()) }; - _DeleteProfileHandlers(*this, *deleteProfileArgs); - } - - void ProfilePageNavigationState::CreateUnfocusedAppearance() - { - _Profile.CreateUnfocusedAppearance(_Schemes, _WindowRoot); - } - - void ProfilePageNavigationState::DeleteUnfocusedAppearance() - { - _Profile.DeleteUnfocusedAppearance(); - } - Profiles::Profiles() : _previewControl{ Control::TermControl(Model::TerminalSettings{}, make()) } { InitializeComponent(); - INITIALIZE_BINDABLE_ENUM_SETTING(AntiAliasingMode, TextAntialiasingMode, winrt::Microsoft::Terminal::Control::TextAntialiasingMode, L"Profile_AntialiasingMode", L"Content"); - INITIALIZE_BINDABLE_ENUM_SETTING_REVERSE_ORDER(CloseOnExitMode, CloseOnExitMode, winrt::Microsoft::Terminal::Settings::Model::CloseOnExitMode, L"Profile_CloseOnExit", L"Content"); - INITIALIZE_BINDABLE_ENUM_SETTING(ScrollState, ScrollbarState, winrt::Microsoft::Terminal::Control::ScrollbarState, L"Profile_ScrollbarVisibility", L"Content"); - const auto startingDirCheckboxTooltip{ ToolTipService::GetToolTip(StartingDirectoryUseParentCheckbox()) }; Automation::AutomationProperties::SetFullDescription(StartingDirectoryUseParentCheckbox(), unbox_value(startingDirCheckboxTooltip)); @@ -417,16 +38,17 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void Profiles::OnNavigatedTo(const NavigationEventArgs& e) { - _State = e.Parameter().as(); + auto state{ e.Parameter().as() }; + _Profile = state.Profile(); // generate the font list, if we don't have one - if (!_State.Profile().CompleteFontList() || !_State.Profile().MonospaceFontList()) + if (!ProfileViewModel::CompleteFontList() || !ProfileViewModel::MonospaceFontList()) { ProfileViewModel::UpdateFontList(); } // Check the use parent directory box if the starting directory is empty - if (_State.Profile().StartingDirectory().empty()) + if (_Profile.StartingDirectory().empty()) { StartingDirectoryUseParentCheckbox().IsChecked(true); } @@ -434,7 +56,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // Subscribe to some changes in the view model // These changes should force us to update our own set of "Current" members, // and propagate those changes to the UI - _ViewModelChangedRevoker = _State.Profile().PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) { + _ViewModelChangedRevoker = _Profile.PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) { const auto settingName{ args.PropertyName() }; if (settingName == L"AntialiasingMode") { @@ -452,19 +74,19 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { _PropertyChangedHandlers(*this, PropertyChangedEventArgs{ L"CurrentScrollState" }); } - _previewControl.Settings(_State.Profile().TermSettings()); + _previewControl.Settings(_Profile.TermSettings()); _previewControl.UpdateSettings(); }); // The Appearances object handles updating the values in the settings UI, but // we still need to listen to the changes here just to update the preview control - _AppearanceViewModelChangedRevoker = _State.Profile().DefaultAppearance().PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& /*args*/) { - _previewControl.Settings(_State.Profile().TermSettings()); + _AppearanceViewModelChangedRevoker = _Profile.DefaultAppearance().PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& /*args*/) { + _previewControl.Settings(_Profile.TermSettings()); _previewControl.UpdateSettings(); }); // Navigate to the pivot in the provided navigation state - ProfilesPivot().SelectedIndex(static_cast(_State.LastActivePivot())); + ProfilesPivot().SelectedIndex(static_cast(ProfileViewModel::LastActivePivot())); _previewControl.Settings(_State.Profile().TermSettings()); // There is a possibility that the control has not fully initialized yet, @@ -481,57 +103,30 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _AppearanceViewModelChangedRevoker.revoke(); } - bool Profiles::IsBellStyleFlagSet(const uint32_t flag) - { - return (WI_EnumValue(_State.Profile().BellStyle()) & flag) == flag; - } - - void Profiles::SetBellStyleAudible(winrt::Windows::Foundation::IReference on) - { - auto currentStyle = State().Profile().BellStyle(); - WI_UpdateFlag(currentStyle, Model::BellStyle::Audible, winrt::unbox_value(on)); - State().Profile().BellStyle(currentStyle); - } - - void Profiles::SetBellStyleWindow(winrt::Windows::Foundation::IReference on) - { - auto currentStyle = State().Profile().BellStyle(); - WI_UpdateFlag(currentStyle, Model::BellStyle::Window, winrt::unbox_value(on)); - State().Profile().BellStyle(currentStyle); - } - - void Profiles::SetBellStyleTaskbar(winrt::Windows::Foundation::IReference on) - { - auto currentStyle = State().Profile().BellStyle(); - WI_UpdateFlag(currentStyle, Model::BellStyle::Taskbar, winrt::unbox_value(on)); - State().Profile().BellStyle(currentStyle); - } - void Profiles::DeleteConfirmation_Click(IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/) { - auto state{ winrt::get_self(_State) }; - state->DeleteProfile(); + winrt::get_self(_Profile)->DeleteProfile(); } void Profiles::CreateUnfocusedAppearance_Click(IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/) { - _State.CreateUnfocusedAppearance(); + _Profile.CreateUnfocusedAppearance(); } void Profiles::DeleteUnfocusedAppearance_Click(IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/) { - _State.DeleteUnfocusedAppearance(); + _Profile.DeleteUnfocusedAppearance(); } fire_and_forget Profiles::Icon_Click(IInspectable const&, RoutedEventArgs const&) { auto lifetime = get_strong(); - const auto parentHwnd{ reinterpret_cast(_State.WindowRoot().GetHostingWindow()) }; + const auto parentHwnd{ reinterpret_cast(winrt::get_self(_Profile)->WindowRoot().GetHostingWindow()) }; auto file = co_await OpenImagePicker(parentHwnd); if (!file.empty()) { - _State.Profile().Icon(file); + _Profile.Icon(file); } } @@ -545,7 +140,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation }; static constexpr winrt::guid clientGuidExecutables{ 0x2E7E4331, 0x0800, 0x48E6, { 0xB0, 0x17, 0xA1, 0x4C, 0xD8, 0x73, 0xDD, 0x58 } }; - const auto parentHwnd{ reinterpret_cast(_State.WindowRoot().GetHostingWindow()) }; + const auto parentHwnd{ reinterpret_cast(winrt::get_self(_Profile)->WindowRoot().GetHostingWindow()) }; auto path = co_await OpenFilePicker(parentHwnd, [](auto&& dialog) { THROW_IF_FAILED(dialog->SetClientGuid(clientGuidExecutables)); try @@ -561,14 +156,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation if (!path.empty()) { - _State.Profile().Commandline(path); + _Profile.Commandline(path); } } fire_and_forget Profiles::StartingDirectory_Click(IInspectable const&, RoutedEventArgs const&) { auto lifetime = get_strong(); - const auto parentHwnd{ reinterpret_cast(_State.WindowRoot().GetHostingWindow()) }; + const auto parentHwnd{ reinterpret_cast(winrt::get_self(_Profile)->WindowRoot().GetHostingWindow()) }; auto folder = co_await OpenFilePicker(parentHwnd, [](auto&& dialog) { static constexpr winrt::guid clientGuidFolderPicker{ 0xAADAA433, 0xB04D, 0x4BAE, { 0xB1, 0xEA, 0x1E, 0x6C, 0xD1, 0xCD, 0xA6, 0x8B } }; THROW_IF_FAILED(dialog->SetClientGuid(clientGuidFolderPicker)); @@ -586,13 +181,13 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation if (!folder.empty()) { - _State.Profile().StartingDirectory(folder); + _Profile.StartingDirectory(folder); } } void Profiles::Pivot_SelectionChanged(Windows::Foundation::IInspectable const& /*sender*/, RoutedEventArgs const& /*e*/) { - _State.LastActivePivot(static_cast(ProfilesPivot().SelectedIndex())); + ProfileViewModel::LastActivePivot(static_cast(ProfilesPivot().SelectedIndex())); } } diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.h b/src/cascadia/TerminalSettingsEditor/Profiles.h index 2bcbb1ca37b..6213bcbe69d 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles.h +++ b/src/cascadia/TerminalSettingsEditor/Profiles.h @@ -5,144 +5,25 @@ #include "Profiles.g.h" #include "ProfilePageNavigationState.g.h" -#include "DeleteProfileEventArgs.g.h" -#include "ProfileViewModel.g.h" +#include "ProfileViewModel.h" #include "Utils.h" #include "ViewModelHelpers.h" #include "Appearances.h" namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { - struct ProfileViewModel : ProfileViewModelT, ViewModelHelper - { - public: - ProfileViewModel(const Model::Profile& profile, const Model::CascadiaSettings& settings); - - Model::TerminalSettings TermSettings() const; - - void SetAcrylicOpacityPercentageValue(double value) - { - Opacity(winrt::Microsoft::Terminal::Settings::Editor::Converters::PercentageValueToPercentage(value)); - - // GH#11372: If we're on Windows 10, and someone wants opacity, then - // we'll turn acrylic on for them. Opacity doesn't work without - // acrylic on Windows 10. - // - // BODGY: CascadiaSettings's function IsDefaultTerminalAvailable - // is basically a "are we on Windows 11" check, because defterm - // only works on Win11. So we'll use that. - // - // Remove when we can remove the rest of GH#11285 - if (value < 100.0 && - !winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings::IsDefaultTerminalAvailable()) - { - UseAcrylic(true); - } - }; - - void SetPadding(double value) - { - Padding(to_hstring(value)); - } - - // starting directory - bool UseParentProcessDirectory(); - void UseParentProcessDirectory(const bool useParent); - bool UseCustomStartingDirectory(); - - // font face - static void UpdateFontList() noexcept; - Windows::Foundation::Collections::IObservableVector CompleteFontList() const noexcept; - Windows::Foundation::Collections::IObservableVector MonospaceFontList() const noexcept; - - // general profile knowledge - winrt::guid OriginalProfileGuid() const noexcept; - bool CanDeleteProfile() const; - Editor::AppearanceViewModel DefaultAppearance(); - Editor::AppearanceViewModel UnfocusedAppearance(); - bool HasUnfocusedAppearance(); - bool EditableUnfocusedAppearance(); - bool ShowUnfocusedAppearance(); - - void CreateUnfocusedAppearance(const Windows::Foundation::Collections::IMapView& schemes, - const IHostedInWindow& windowRoot); - void DeleteUnfocusedAppearance(); - - WINRT_PROPERTY(bool, IsBaseLayer, false); - - PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, Guid); - PERMANENT_OBSERVABLE_PROJECTED_SETTING(_profile, ConnectionType); - OBSERVABLE_PROJECTED_SETTING(_profile, Name); - OBSERVABLE_PROJECTED_SETTING(_profile, Source); - OBSERVABLE_PROJECTED_SETTING(_profile, Hidden); - OBSERVABLE_PROJECTED_SETTING(_profile, Icon); - OBSERVABLE_PROJECTED_SETTING(_profile, CloseOnExit); - OBSERVABLE_PROJECTED_SETTING(_profile, TabTitle); - OBSERVABLE_PROJECTED_SETTING(_profile, TabColor); - OBSERVABLE_PROJECTED_SETTING(_profile, SuppressApplicationTitle); - OBSERVABLE_PROJECTED_SETTING(_profile, UseAcrylic); - OBSERVABLE_PROJECTED_SETTING(_profile, ScrollState); - OBSERVABLE_PROJECTED_SETTING(_profile, Padding); - OBSERVABLE_PROJECTED_SETTING(_profile, Commandline); - OBSERVABLE_PROJECTED_SETTING(_profile, StartingDirectory); - OBSERVABLE_PROJECTED_SETTING(_profile, AntialiasingMode); - OBSERVABLE_PROJECTED_SETTING(_profile, ForceFullRepaintRendering); - OBSERVABLE_PROJECTED_SETTING(_profile, SoftwareRendering); - OBSERVABLE_PROJECTED_SETTING(_profile.DefaultAppearance(), Foreground); - OBSERVABLE_PROJECTED_SETTING(_profile.DefaultAppearance(), Background); - OBSERVABLE_PROJECTED_SETTING(_profile.DefaultAppearance(), SelectionBackground); - OBSERVABLE_PROJECTED_SETTING(_profile.DefaultAppearance(), CursorColor); - OBSERVABLE_PROJECTED_SETTING(_profile.DefaultAppearance(), Opacity); - OBSERVABLE_PROJECTED_SETTING(_profile, HistorySize); - OBSERVABLE_PROJECTED_SETTING(_profile, SnapOnInput); - OBSERVABLE_PROJECTED_SETTING(_profile, AltGrAliasing); - OBSERVABLE_PROJECTED_SETTING(_profile, BellStyle); - - private: - Model::Profile _profile; - winrt::guid _originalProfileGuid; - winrt::hstring _lastBgImagePath; - winrt::hstring _lastStartingDirectoryPath; - Editor::AppearanceViewModel _defaultAppearanceViewModel; - - static Windows::Foundation::Collections::IObservableVector _MonospaceFontList; - static Windows::Foundation::Collections::IObservableVector _FontList; - - static Editor::Font _GetFont(com_ptr localizedFamilyNames); - - Model::CascadiaSettings _appSettings; - Editor::AppearanceViewModel _unfocusedAppearanceViewModel; - }; - - struct DeleteProfileEventArgs : - public DeleteProfileEventArgsT - { - public: - DeleteProfileEventArgs(guid profileGuid) : - _ProfileGuid(profileGuid) {} - - guid ProfileGuid() const noexcept { return _ProfileGuid; } - - private: - guid _ProfileGuid{}; - }; - struct ProfilePageNavigationState : ProfilePageNavigationStateT { public: ProfilePageNavigationState(const Editor::ProfileViewModel& viewModel, const Windows::Foundation::Collections::IMapView& schemes, - const Editor::ProfilePageNavigationState& lastState, const IHostedInWindow& windowRoot) : - _Profile{ viewModel }, - _Schemes{ schemes }, - _WindowRoot{ windowRoot } + _Profile{ viewModel } { - // If there was a previous nav state copy the selected pivot from it. - if (lastState) - { - _LastActivePivot = lastState.LastActivePivot(); - } + auto profile{ winrt::get_self(viewModel) }; + profile->Schemes(schemes); + profile->WindowRoot(windowRoot); + viewModel.DefaultAppearance().Schemes(schemes); viewModel.DefaultAppearance().WindowRoot(windowRoot); @@ -152,20 +33,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation viewModel.UnfocusedAppearance().WindowRoot(windowRoot); } } - - void DeleteProfile(); - void CreateUnfocusedAppearance(); - void DeleteUnfocusedAppearance(); - - Windows::Foundation::Collections::IMapView Schemes() { return _Schemes; } - void Schemes(const Windows::Foundation::Collections::IMapView& val) { _Schemes = val; } - TYPED_EVENT(DeleteProfile, Editor::ProfilePageNavigationState, Editor::DeleteProfileEventArgs); - WINRT_PROPERTY(IHostedInWindow, WindowRoot, nullptr); - WINRT_PROPERTY(Editor::ProfilesPivots, LastActivePivot, Editor::ProfilesPivots::General); WINRT_PROPERTY(Editor::ProfileViewModel, Profile, nullptr); - - private: - Windows::Foundation::Collections::IMapView _Schemes; }; struct Profiles : public HasScrollViewer, ProfilesT @@ -176,12 +44,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void OnNavigatedTo(const Windows::UI::Xaml::Navigation::NavigationEventArgs& e); void OnNavigatedFrom(const Windows::UI::Xaml::Navigation::NavigationEventArgs& e); - // bell style bits - bool IsBellStyleFlagSet(const uint32_t flag); - void SetBellStyleAudible(winrt::Windows::Foundation::IReference on); - void SetBellStyleWindow(winrt::Windows::Foundation::IReference on); - void SetBellStyleTaskbar(winrt::Windows::Foundation::IReference on); - fire_and_forget Commandline_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e); fire_and_forget StartingDirectory_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e); fire_and_forget Icon_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e); @@ -192,10 +54,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); - WINRT_PROPERTY(Editor::ProfilePageNavigationState, State, nullptr); - GETSET_BINDABLE_ENUM_SETTING(AntiAliasingMode, Microsoft::Terminal::Control::TextAntialiasingMode, State().Profile, AntialiasingMode); - GETSET_BINDABLE_ENUM_SETTING(CloseOnExitMode, Microsoft::Terminal::Settings::Model::CloseOnExitMode, State().Profile, CloseOnExit); - GETSET_BINDABLE_ENUM_SETTING(ScrollState, Microsoft::Terminal::Control::ScrollbarState, State().Profile, ScrollState); + WINRT_PROPERTY(Editor::ProfileViewModel, Profile, nullptr); private: void _UpdateBIAlignmentControl(const int32_t val); diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.idl b/src/cascadia/TerminalSettingsEditor/Profiles.idl index 482a1967174..545579f2365 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles.idl +++ b/src/cascadia/TerminalSettingsEditor/Profiles.idl @@ -1,113 +1,20 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import "EnumEntry.idl"; import "MainPage.idl"; -import "Appearances.idl"; - -#include "ViewModelHelpers.idl.h" - -#define OBSERVABLE_PROJECTED_PROFILE_SETTING(Type, Name) \ - OBSERVABLE_PROJECTED_SETTING(Type, Name); \ - Object Name##OverrideSource { get; } +import "ProfileViewModel.idl"; namespace Microsoft.Terminal.Settings.Editor { - runtimeclass ProfileViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged - { - Windows.Foundation.Collections.IObservableVector CompleteFontList { get; }; - Windows.Foundation.Collections.IObservableVector MonospaceFontList { get; }; - Microsoft.Terminal.Settings.Model.TerminalSettings TermSettings { get; }; - - void SetAcrylicOpacityPercentageValue(Double value); - void SetPadding(Double value); - - Boolean CanDeleteProfile { get; }; - Boolean IsBaseLayer; - Boolean UseParentProcessDirectory; - Boolean UseCustomStartingDirectory { get; }; - AppearanceViewModel DefaultAppearance { get; }; - Guid OriginalProfileGuid { get; }; - Boolean HasUnfocusedAppearance { get; }; - Boolean EditableUnfocusedAppearance { get; }; - Boolean ShowUnfocusedAppearance { get; }; - AppearanceViewModel UnfocusedAppearance { get; }; - - void CreateUnfocusedAppearance(Windows.Foundation.Collections.IMapView Schemes, IHostedInWindow WindowRoot); - void DeleteUnfocusedAppearance(); - - OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Name); - PERMANENT_OBSERVABLE_PROJECTED_SETTING(Guid, Guid); - OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Source); - PERMANENT_OBSERVABLE_PROJECTED_SETTING(Guid, ConnectionType); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, Hidden); - OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Icon); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Settings.Model.CloseOnExitMode, CloseOnExit); - OBSERVABLE_PROJECTED_PROFILE_SETTING(String, TabTitle); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Windows.Foundation.IReference, TabColor); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, SuppressApplicationTitle); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, UseAcrylic); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Double, Opacity); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.ScrollbarState, ScrollState); - OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Padding); - OBSERVABLE_PROJECTED_PROFILE_SETTING(String, Commandline); - OBSERVABLE_PROJECTED_PROFILE_SETTING(String, StartingDirectory); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.TextAntialiasingMode, AntialiasingMode); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, ForceFullRepaintRendering); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, SoftwareRendering); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Windows.Foundation.IReference, Foreground); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Windows.Foundation.IReference, Background); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Windows.Foundation.IReference, SelectionBackground); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Windows.Foundation.IReference, CursorColor); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Int32, HistorySize); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, SnapOnInput); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AltGrAliasing); - OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Settings.Model.BellStyle, BellStyle); - } - - runtimeclass DeleteProfileEventArgs - { - Guid ProfileGuid { get; }; - } - - enum ProfilesPivots - { - General = 0, - Appearance = 1, - Advanced = 2 - }; - runtimeclass ProfilePageNavigationState { - IHostedInWindow WindowRoot; // necessary to send the right HWND into the file picker dialogs. - - ProfileViewModel Profile; - ProfilesPivots LastActivePivot; - - void CreateUnfocusedAppearance(); - void DeleteUnfocusedAppearance(); - - event Windows.Foundation.TypedEventHandler DeleteProfile; + ProfileViewModel Profile { get; }; }; [default_interface] runtimeclass Profiles : Windows.UI.Xaml.Controls.Page, Windows.UI.Xaml.Data.INotifyPropertyChanged { Profiles(); - ProfilePageNavigationState State { get; }; - - Boolean IsBellStyleFlagSet(UInt32 flag); - void SetBellStyleAudible(Windows.Foundation.IReference on); - void SetBellStyleWindow(Windows.Foundation.IReference on); - void SetBellStyleTaskbar(Windows.Foundation.IReference on); - - IInspectable CurrentAntiAliasingMode; - Windows.Foundation.Collections.IObservableVector AntiAliasingModeList { get; }; - - IInspectable CurrentCloseOnExitMode; - Windows.Foundation.Collections.IObservableVector CloseOnExitModeList { get; }; - - IInspectable CurrentScrollState; - Windows.Foundation.Collections.IObservableVector ScrollStateList { get; }; + ProfileViewModel Profile { get; }; Windows.UI.Xaml.Controls.Slider OpacitySlider { get; }; } diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.xaml b/src/cascadia/TerminalSettingsEditor/Profiles.xaml index dcbe1d084c6..9fb71b8f6b1 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles.xaml @@ -47,7 +47,7 @@ Grid.Row="0" Margin="{StaticResource StandardIndentMargin}" Style="{StaticResource DisclaimerStyle}" - Visibility="{x:Bind State.Profile.IsBaseLayer}" /> + Visibility="{x:Bind Profile.IsBaseLayer}" /> + Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(Profile.IsBaseLayer), Mode=OneWay}"> + Text="{x:Bind Profile.Name, Mode=TwoWay}" /> + ClearSettingValue="{x:Bind Profile.ClearCommandline}" + HasSettingValue="{x:Bind Profile.HasCommandline, Mode=OneWay}" + SettingOverrideSource="{x:Bind Profile.CommandlineOverrideSource, Mode=OneWay}" + Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(Profile.IsBaseLayer), Mode=OneWay}"> + Text="{x:Bind Profile.Commandline, Mode=TwoWay}" /> - + @@ -408,77 +408,78 @@ - + ClearSettingValue="{x:Bind Profile.ClearSuppressApplicationTitle}" + HasSettingValue="{x:Bind Profile.HasSuppressApplicationTitle, Mode=OneWay}" + SettingOverrideSource="{x:Bind Profile.SuppressApplicationTitleOverrideSource, Mode=OneWay}"> + + ClearSettingValue="{x:Bind Profile.ClearAntialiasingMode}" + HasSettingValue="{x:Bind Profile.HasAntialiasingMode, Mode=OneWay}" + SettingOverrideSource="{x:Bind Profile.AntialiasingModeOverrideSource, Mode=OneWay}"> + ItemsSource="{x:Bind Profile.AntiAliasingModeList, Mode=OneWay}" + SelectedItem="{x:Bind Profile.CurrentAntiAliasingMode, Mode=TwoWay}" /> - + ClearSettingValue="{x:Bind Profile.ClearAltGrAliasing}" + HasSettingValue="{x:Bind Profile.HasAltGrAliasing, Mode=OneWay}" + SettingOverrideSource="{x:Bind Profile.AltGrAliasingOverrideSource, Mode=OneWay}"> + - + ClearSettingValue="{x:Bind Profile.ClearSnapOnInput}" + HasSettingValue="{x:Bind Profile.HasSnapOnInput, Mode=OneWay}" + SettingOverrideSource="{x:Bind Profile.SnapOnInputOverrideSource, Mode=OneWay}"> + + ClearSettingValue="{x:Bind Profile.ClearHistorySize}" + HasSettingValue="{x:Bind Profile.HasHistorySize, Mode=OneWay}" + SettingOverrideSource="{x:Bind Profile.HistorySizeOverrideSource, Mode=OneWay}"> + Value="{x:Bind Profile.HistorySize, Mode=TwoWay}" /> + ClearSettingValue="{x:Bind Profile.ClearCloseOnExit}" + HasSettingValue="{x:Bind Profile.HasCloseOnExit, Mode=OneWay}" + SettingOverrideSource="{x:Bind Profile.CloseOnExitOverrideSource, Mode=OneWay}"> + ItemsSource="{x:Bind Profile.CloseOnExitModeList, Mode=OneWay}" + SelectedItem="{x:Bind Profile.CurrentCloseOnExitMode, Mode=TwoWay}" /> + ClearSettingValue="{x:Bind Profile.ClearBellStyle}" + HasSettingValue="{x:Bind Profile.HasBellStyle, Mode=OneWay}" + SettingOverrideSource="{x:Bind Profile.BellStyleOverrideSource, Mode=OneWay}"> + IsChecked="{x:Bind Profile.IsBellStyleFlagSet(1), BindBack=Profile.SetBellStyleAudible, Mode=TwoWay}" /> + IsChecked="{x:Bind Profile.IsBellStyleFlagSet(2), BindBack=Profile.SetBellStyleWindow, Mode=TwoWay}" /> + IsChecked="{x:Bind Profile.IsBellStyleFlagSet(4), BindBack=Profile.SetBellStyleTaskbar, Mode=TwoWay}" /> + diff --git a/src/cascadia/TerminalSettingsEditor/Utils.h b/src/cascadia/TerminalSettingsEditor/Utils.h index ec73f41c71f..b0be99d658d 100644 --- a/src/cascadia/TerminalSettingsEditor/Utils.h +++ b/src/cascadia/TerminalSettingsEditor/Utils.h @@ -51,29 +51,29 @@ // of EnumEntries so that we may display all possible values of the given // enum type and its localized names. It also provides a getter and setter // for the setting we wish to bind to. -#define GETSET_BINDABLE_ENUM_SETTING(name, enumType, settingsModelName, settingNameInModel) \ -public: \ - winrt::Windows::Foundation::Collections::IObservableVector name##List() \ - { \ - return _##name##List; \ - } \ - \ - winrt::Windows::Foundation::IInspectable Current##name() \ - { \ - return winrt::box_value(_##name##Map.Lookup(settingsModelName().settingNameInModel())); \ - } \ - \ - void Current##name(const winrt::Windows::Foundation::IInspectable& enumEntry) \ - { \ - if (auto ee = enumEntry.try_as()) \ - { \ - auto setting = winrt::unbox_value(ee.EnumValue()); \ - settingsModelName().settingNameInModel(setting); \ - } \ - } \ - \ -private: \ - winrt::Windows::Foundation::Collections::IObservableVector _##name##List; \ +#define GETSET_BINDABLE_ENUM_SETTING(name, enumType, settingsModelName, settingNameInModel) \ +public: \ + winrt::Windows::Foundation::Collections::IObservableVector name##List() \ + { \ + return _##name##List; \ + } \ + \ + winrt::Windows::Foundation::IInspectable Current##name() \ + { \ + return winrt::box_value(_##name##Map.Lookup(settingsModelName.settingNameInModel())); \ + } \ + \ + void Current##name(const winrt::Windows::Foundation::IInspectable& enumEntry) \ + { \ + if (auto ee = enumEntry.try_as()) \ + { \ + auto setting = winrt::unbox_value(ee.EnumValue()); \ + settingsModelName.settingNameInModel(setting); \ + } \ + } \ + \ +private: \ + winrt::Windows::Foundation::Collections::IObservableVector _##name##List; \ winrt::Windows::Foundation::Collections::IMap _##name##Map; // This macro defines a dependency property for a WinRT class.