From 0077ed72800e360bdf1500754c0c0257a10f57f5 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 1 May 2024 01:33:15 +0200 Subject: [PATCH 1/5] Fix font axes/features settings UI --- doc/cascadia/profiles.schema.json | 4 +- src/cascadia/TerminalControl/ControlCore.cpp | 31 +- .../TerminalControl/ControlSettings.h | 2 +- .../TerminalControl/IControlSettings.idl | 2 +- .../TerminalSettingsEditor/Appearances.cpp | 1195 ++++++++--------- .../TerminalSettingsEditor/Appearances.h | 164 +-- .../TerminalSettingsEditor/Appearances.idl | 61 +- .../TerminalSettingsEditor/Appearances.xaml | 152 +-- .../ProfileViewModel.cpp | 34 +- .../TerminalSettingsEditor/ProfileViewModel.h | 7 - .../ProfileViewModel.idl | 4 - .../Resources/en-US/Resources.resw | 6 + .../TerminalSettingsModel/FontConfig.cpp | 21 +- .../TerminalSettingsModel/FontConfig.h | 2 +- .../TerminalSettingsModel/FontConfig.idl | 2 +- .../TerminalSettingsModel/JsonUtils.h | 20 + .../TerminalSettingsModel/TerminalSettings.h | 2 +- .../UnitTests_Control/MockControlSettings.h | 2 +- src/renderer/atlas/AtlasEngine.api.cpp | 13 +- src/renderer/atlas/AtlasEngine.h | 4 +- 20 files changed, 785 insertions(+), 943 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 34945728c03..9a7d1d84593 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -349,7 +349,7 @@ "description": "Sets the DWrite font features for the given font. For example, { \"ss01\": 1, \"liga\":0 } will enable ss01 and disable ligatures.", "type": "object", "patternProperties": { - "^(([A-Za-z0-9]){4})$": { + "^[\\x00-\\x7E]{4}$": { "type": "integer" } }, @@ -359,7 +359,7 @@ "description": "Sets the DWrite font axes for the given font. For example, { \"wght\": 200 } will set the font weight to 200.", "type": "object", "patternProperties": { - "^([A-Za-z]{4})$": { + "^[\\x00-\\x7E]{4}$": { "type": "number" } }, diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 02e1af5802e..9698c659d42 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -959,26 +959,23 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (_renderEngine) { - std::unordered_map featureMap; - if (const auto fontFeatures = _settings->FontFeatures()) - { - featureMap.reserve(fontFeatures.Size()); - - for (const auto& [tag, param] : fontFeatures) + static constexpr auto cloneMap = [](const IFontFeatureMap& map) { + std::unordered_map clone; + if (map) { - featureMap.emplace(tag, param); + clone.reserve(map.Size()); + for (const auto& [tag, param] : map) + { + clone.emplace(tag, param); + } } - } - std::unordered_map axesMap; - if (const auto fontAxes = _settings->FontAxes()) - { - axesMap.reserve(fontAxes.Size()); + return clone; + }; - for (const auto& [axis, value] : fontAxes) - { - axesMap.emplace(axis, value); - } - } + const auto fontFeatures = _settings->FontFeatures(); + const auto fontAxes = _settings->FontAxes(); + const auto featureMap = cloneMap(fontFeatures); + const auto axesMap = cloneMap(fontAxes); // TODO: MSFT:20895307 If the font doesn't exist, this doesn't // actually fail. We need a way to gracefully fallback. diff --git a/src/cascadia/TerminalControl/ControlSettings.h b/src/cascadia/TerminalControl/ControlSettings.h index 0423b36f7b7..75d12b97070 100644 --- a/src/cascadia/TerminalControl/ControlSettings.h +++ b/src/cascadia/TerminalControl/ControlSettings.h @@ -10,7 +10,7 @@ Licensed under the MIT license. #include #include "ControlAppearance.h" -using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap; +using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap; using IFontAxesMap = winrt::Windows::Foundation::Collections::IMap; namespace winrt::Microsoft::Terminal::Control::implementation diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 29cd96b6f8e..51eaf34f90d 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -40,7 +40,7 @@ namespace Microsoft.Terminal.Control Single FontSize { get; }; Windows.UI.Text.FontWeight FontWeight { get; }; String Padding { get; }; - Windows.Foundation.Collections.IMap FontFeatures { get; }; + Windows.Foundation.Collections.IMap FontFeatures { get; }; Windows.Foundation.Collections.IMap FontAxes { get; }; Boolean EnableBuiltinGlyphs { get; }; Boolean EnableColorGlyphs { get; }; diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.cpp b/src/cascadia/TerminalSettingsEditor/Appearances.cpp index 036b045ca5c..c2823b955bc 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.cpp +++ b/src/cascadia/TerminalSettingsEditor/Appearances.cpp @@ -3,13 +3,14 @@ #include "pch.h" #include "Appearances.h" -#include "Appearances.g.cpp" -#include "AxisKeyValuePair.g.cpp" -#include "FeatureKeyValuePair.g.cpp" -#include "EnumEntry.h" #include -#include "..\WinRTUtils\inc\Utils.h" +#include "../WinRTUtils/inc/Utils.h" + +#include "EnumEntry.h" +#include "ProfileViewModel.h" + +#include "Appearances.g.cpp" using namespace winrt::Windows::UI::Text; using namespace winrt::Windows::UI::Xaml; @@ -20,299 +21,186 @@ using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Microsoft::Terminal::Settings::Model; -static constexpr std::array DefaultFeatures{ - L"rlig", - L"locl", - L"ccmp", - L"calt", - L"liga", - L"clig", - L"rnrn", - L"kern", - L"mark", - L"mkmk", - L"dist" +// These features are enabled by default by DWrite, so if a user adds them, +// we initialize the setting to a value of 1 instead of 0. +static constexpr std::array s_defaultFeatures{ + DWRITE_MAKE_FONT_FEATURE_TAG('c', 'a', 'l', 't'), + DWRITE_MAKE_FONT_FEATURE_TAG('c', 'c', 'm', 'p'), + DWRITE_MAKE_FONT_FEATURE_TAG('c', 'l', 'i', 'g'), + DWRITE_MAKE_FONT_FEATURE_TAG('d', 'i', 's', 't'), + DWRITE_MAKE_FONT_FEATURE_TAG('k', 'e', 'r', 'n'), + DWRITE_MAKE_FONT_FEATURE_TAG('l', 'i', 'g', 'a'), + DWRITE_MAKE_FONT_FEATURE_TAG('l', 'o', 'c', 'l'), + DWRITE_MAKE_FONT_FEATURE_TAG('m', 'a', 'r', 'k'), + DWRITE_MAKE_FONT_FEATURE_TAG('m', 'k', 'm', 'k'), + DWRITE_MAKE_FONT_FEATURE_TAG('r', 'l', 'i', 'g'), + DWRITE_MAKE_FONT_FEATURE_TAG('r', 'n', 'r', 'n'), }; namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { - Windows::Foundation::Collections::IMap Font::FontAxesTagsAndNames() + struct TagToStringImpl { - if (!_fontAxesTagsAndNames) + explicit TagToStringImpl(uint32_t tag) noexcept { - wil::com_ptr font; - THROW_IF_FAILED(_family->GetFont(0, font.put())); - wil::com_ptr fontFace; - THROW_IF_FAILED(font->CreateFontFace(fontFace.put())); - wil::com_ptr fontFace5; - if (fontFace5 = fontFace.try_query()) - { - wil::com_ptr fontResource; - THROW_IF_FAILED(fontFace5->GetFontResource(fontResource.put())); + _buffer[0] = static_cast((tag >> 0) & 0xFF); + _buffer[1] = static_cast((tag >> 8) & 0xFF); + _buffer[2] = static_cast((tag >> 16) & 0xFF); + _buffer[3] = static_cast((tag >> 24) & 0xFF); + _buffer[4] = 0; + } - const auto axesCount = fontFace5->GetFontAxisValueCount(); - if (axesCount > 0) - { - std::vector axesVector(axesCount); - fontFace5->GetFontAxisValues(axesVector.data(), axesCount); + operator std::wstring_view() const noexcept + { + return { &_buffer[0], 4 }; + } - uint32_t localeIndex; - BOOL localeExists; - wchar_t localeName[LOCALE_NAME_MAX_LENGTH]; - const auto localeToTry = GetUserDefaultLocaleName(localeName, LOCALE_NAME_MAX_LENGTH) ? localeName : L"en-US"; + private: + wchar_t _buffer[5]; + }; - std::unordered_map fontAxesTagsAndNames; - for (uint32_t i = 0; i < axesCount; ++i) - { - wil::com_ptr names; - THROW_IF_FAILED(fontResource->GetAxisNames(i, names.put())); - - if (!SUCCEEDED(names->FindLocaleName(localeToTry, &localeIndex, &localeExists)) || !localeExists) - { - // default to the first locale in the list - localeIndex = 0; - } - - UINT32 length = 0; - if (SUCCEEDED(names->GetStringLength(localeIndex, &length))) - { - winrt::impl::hstring_builder builder{ length }; - if (SUCCEEDED(names->GetString(localeIndex, builder.data(), length + 1))) - { - fontAxesTagsAndNames.insert(std::pair(_tagToString(axesVector[i].axisTag), builder.to_hstring())); - continue; - } - } - // if there was no name found, it means the font does not actually support this axis - // don't insert anything into the vector in this case - } - _fontAxesTagsAndNames = winrt::single_threaded_map(std::move(fontAxesTagsAndNames)); - } - } - } - return _fontAxesTagsAndNames; + // Turns a DWRITE_MAKE_OPENTYPE_TAG into a string_view... + // (...buffer holder because someone needs to hold onto the data the view refers to.) + static TagToStringImpl tagToString(uint32_t tag) noexcept + { + return TagToStringImpl{ tag }; } - IMap Font::FontFeaturesTagsAndNames() + // Turns a string to a DWRITE_MAKE_OPENTYPE_TAG. Returns 0 on failure. + static uint32_t tagFromString(std::wstring_view str) noexcept { - if (!_fontFeaturesTagsAndNames) + if (str.size() != 4) { - wil::com_ptr font; - THROW_IF_FAILED(_family->GetFont(0, font.put())); - wil::com_ptr fontFace; - THROW_IF_FAILED(font->CreateFontFace(fontFace.put())); - - wil::com_ptr factory; - THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(factory), reinterpret_cast<::IUnknown**>(factory.addressof()))); - wil::com_ptr textAnalyzer; - factory->CreateTextAnalyzer(textAnalyzer.addressof()); - wil::com_ptr textAnalyzer2 = textAnalyzer.query(); - - DWRITE_SCRIPT_ANALYSIS scriptAnalysis{}; - UINT32 tagCount; - // we have to call GetTypographicFeatures twice, first to get the actual count then to get the features - std::ignore = textAnalyzer2->GetTypographicFeatures(fontFace.get(), scriptAnalysis, L"en-us", 0, &tagCount, nullptr); - std::vector tags{ tagCount }; - textAnalyzer2->GetTypographicFeatures(fontFace.get(), scriptAnalysis, L"en-us", tagCount, &tagCount, tags.data()); - - std::unordered_map fontFeaturesTagsAndNames; - for (auto tag : tags) - { - const auto tagString = _tagToString(tag); - hstring formattedResourceString{ fmt::format(L"Profile_FontFeature_{}", tagString) }; - hstring localizedName{ tagString }; - // we have resource strings for common font features, see if one for this feature exists - if (HasLibraryResourceWithName(formattedResourceString)) - { - localizedName = GetLibraryResourceString(formattedResourceString); - } - fontFeaturesTagsAndNames.insert(std::pair(tagString, localizedName)); - } - _fontFeaturesTagsAndNames = winrt::single_threaded_map(std::move(fontFeaturesTagsAndNames)); + return 0; } - return _fontFeaturesTagsAndNames; - } - winrt::hstring Font::_tagToString(DWRITE_FONT_AXIS_TAG tag) - { - std::wstring result; + // Check if all 4 characters are printable ASCII. for (int i = 0; i < 4; ++i) { - result.push_back((tag >> (i * 8)) & 0xFF); + const auto ch = str[i]; + if (ch < 0x20 || ch > 0x7E) + { + return 0; + } } - return winrt::hstring{ result }; + + return DWRITE_MAKE_OPENTYPE_TAG(str[0], str[1], str[2], str[3]); } - hstring Font::_tagToString(DWRITE_FONT_FEATURE_TAG tag) + static winrt::hstring getLocalizedStringByIndex(IDWriteLocalizedStrings* strings, UINT32 index) { - std::wstring result; - for (int i = 0; i < 4; ++i) - { - result.push_back((tag >> (i * 8)) & 0xFF); - } - return hstring{ result }; + UINT32 length = 0; + THROW_IF_FAILED(strings->GetStringLength(index, &length)); + + winrt::impl::hstring_builder builder{ length }; + THROW_IF_FAILED(strings->GetString(index, builder.data(), length + 1)); + + return builder.to_hstring(); } - AxisKeyValuePair::AxisKeyValuePair(winrt::hstring axisKey, float axisValue, const Windows::Foundation::Collections::IMap& baseMap, const Windows::Foundation::Collections::IMap& tagToNameMap) : - _AxisKey{ axisKey }, - _AxisValue{ axisValue }, - _baseMap{ baseMap }, - _tagToNameMap{ tagToNameMap } + static UINT32 getLocalizedStringIndex(IDWriteLocalizedStrings* strings, const wchar_t* locale, UINT32 fallback) { - if (_tagToNameMap.HasKey(_AxisKey)) + UINT32 index; + BOOL exists; + if (FAILED(strings->FindLocaleName(locale, &index, &exists)) || !exists) { - int32_t i{ 0 }; - // IMap guarantees that the iteration order is the same every time - // so this conversion of key to index is safe - for (const auto tagAndName : _tagToNameMap) - { - if (tagAndName.Key() == _AxisKey) - { - _AxisIndex = i; - break; - } - ++i; - } + index = fallback; } + return index; } - winrt::hstring AxisKeyValuePair::AxisKey() + Font::Font(winrt::hstring name, winrt::hstring localizedName) : + _Name{ std::move(name) }, + _LocalizedName{ std::move(localizedName) } { - return _AxisKey; } - float AxisKeyValuePair::AxisValue() + bool FontKeyValuePair::SortAscending(const Editor::FontKeyValuePair& lhs, const Editor::FontKeyValuePair& rhs) { - return _AxisValue; + const auto& a = winrt::get_self(lhs)->KeyDisplayStringRef(); + const auto& b = winrt::get_self(rhs)->KeyDisplayStringRef(); + return til::compare_linguistic_insensitive(a, b) < 0; } - int32_t AxisKeyValuePair::AxisIndex() + FontKeyValuePair::FontKeyValuePair(winrt::weak_ref vm, winrt::hstring keyDisplayString, uint32_t key, float value, bool isFontFeature) : + _vm{ std::move(vm) }, + _keyDisplayString{ std::move(keyDisplayString) }, + _key{ key }, + _value{ value }, + _isFontFeature{ isFontFeature } { - return _AxisIndex; } - void AxisKeyValuePair::AxisValue(float axisValue) + uint32_t FontKeyValuePair::Key() const noexcept { - if (axisValue != _AxisValue) - { - _baseMap.Remove(_AxisKey); - _AxisValue = axisValue; - _baseMap.Insert(_AxisKey, _AxisValue); - PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"AxisValue" }); - } + return _key; } - void AxisKeyValuePair::AxisKey(winrt::hstring axisKey) + winrt::hstring FontKeyValuePair::KeyDisplayString() { - if (axisKey != _AxisKey) - { - _baseMap.Remove(_AxisKey); - _AxisKey = axisKey; - _baseMap.Insert(_AxisKey, _AxisValue); - PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"AxisKey" }); - } + return KeyDisplayStringRef(); } - void AxisKeyValuePair::AxisIndex(int32_t axisIndex) + // You can't return a const-ref from a WinRT function, because the cppwinrt generated wrapper chokes on it. + // So, now we got two KeyDisplayString() functions, because I refuse to AddRef/Release this for no reason. + // I mean, really it makes no perf. difference, but I'm not kneeling down for an incompetent code generator. + const winrt::hstring& FontKeyValuePair::KeyDisplayStringRef() { - if (axisIndex != _AxisIndex) + if (!_keyDisplayString.empty()) { - _AxisIndex = axisIndex; - - int32_t i{ 0 }; - // same as in the constructor, iterating through IMap - // gives us the same order every time - for (const auto tagAndName : _tagToNameMap) - { - if (i == _AxisIndex) - { - AxisKey(tagAndName.Key()); - break; - } - ++i; - } + return _keyDisplayString; } - } - FeatureKeyValuePair::FeatureKeyValuePair(hstring featureKey, uint32_t featureValue, const IMap& baseMap, const IMap& tagToNameMap) : - _FeatureKey{ featureKey }, - _FeatureValue{ featureValue }, - _baseMap{ baseMap }, - _tagToNameMap{ tagToNameMap } - { - if (_tagToNameMap.HasKey(_FeatureKey)) + const auto tagString = tagToString(_key); + hstring displayString; + + if (_isFontFeature) { - int32_t i{ 0 }; - // this loop assumes that every time we iterate through the map - // we get the same ordering - for (const auto tagAndName : _tagToNameMap) + const auto key = fmt::format(FMT_COMPILE(L"Profile_FontFeature_{}"), std::wstring_view{ tagString }); + if (HasLibraryResourceWithName(key)) { - if (tagAndName.Key() == _FeatureKey) - { - _FeatureIndex = i; - break; - } - ++i; + displayString = GetLibraryResourceString(key); + displayString = hstring{ fmt::format(FMT_COMPILE(L"{} ({})"), displayString, std::wstring_view{ tagString }) }; } } - } - hstring FeatureKeyValuePair::FeatureKey() - { - return _FeatureKey; - } + if (displayString.empty()) + { + displayString = hstring{ tagString }; + } - uint32_t FeatureKeyValuePair::FeatureValue() - { - return _FeatureValue; + _keyDisplayString = displayString; + return _keyDisplayString; } - int32_t FeatureKeyValuePair::FeatureIndex() + float FontKeyValuePair::Value() const noexcept { - return _FeatureIndex; + return _value; } - void FeatureKeyValuePair::FeatureValue(uint32_t featureValue) + void FontKeyValuePair::Value(float v) { - if (featureValue != _FeatureValue) + if (_value == v) { - _baseMap.Remove(_FeatureKey); - _FeatureValue = featureValue; - _baseMap.Insert(_FeatureKey, _FeatureValue); - PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"FeatureValue" }); + return; } - } - void FeatureKeyValuePair::FeatureKey(hstring featureKey) - { - if (featureKey != _FeatureKey) + _value = v; + + if (const auto vm = _vm.get()) { - _baseMap.Remove(_FeatureKey); - _FeatureKey = featureKey; - _baseMap.Insert(_FeatureKey, _FeatureValue); - PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"FeatureKey" }); + vm->UpdateFontSetting(this); } } - void FeatureKeyValuePair::FeatureIndex(int32_t featureIndex) + void FontKeyValuePair::SetValueDirect(float v) { - if (featureIndex != _FeatureIndex) - { - _FeatureIndex = featureIndex; + _value = v; + } - int32_t i{ 0 }; - // same as in the constructor, this assumes that iterating through the map - // gives us the same order every time - for (const auto tagAndName : _tagToNameMap) - { - if (i == _FeatureIndex) - { - FeatureKey(tagAndName.Key()); - break; - } - ++i; - } - } + bool FontKeyValuePair::IsFontFeature() const noexcept + { + return _isFontFeature; } AppearanceViewModel::AppearanceViewModel(const Model::AppearanceConfig& appearance) : @@ -333,25 +221,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // box, prevent it from ever being changed again. _NotifyChanges(L"UseDesktopBGImage", L"BackgroundImageSettingsVisible"); } - else if (viewModelProperty == L"FontAxes") - { - // this is a weird one - // we manually make the observable vector based on the map in the settings model - // (this is due to xaml being unable to bind a list view to a map) - // so when the FontAxes change (say from the reset button), reinitialize the observable vector - InitializeFontAxesVector(); - } - else if (viewModelProperty == L"FontFeatures") - { - // same as the FontAxes one - InitializeFontFeaturesVector(); - } }); - _refreshFontFaceDependents(); - InitializeFontAxesVector(); - InitializeFontFeaturesVector(); - // Cache the original BG image path. If the user clicks "Use desktop // wallpaper", then un-checks it, this is the string we'll restore to // them. @@ -375,7 +246,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } fontInfo.FontFace(value); - _refreshFontFaceDependents(); + _invalidateFontFaceDependents(); _NotifyChanges(L"HasFontFace", L"FontFace"); } @@ -388,15 +259,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void AppearanceViewModel::ClearFontFace() { const auto fontInfo = _appearance.SourceProfile().FontInfo(); - const auto hadValue = fontInfo.HasFontFace(); fontInfo.ClearFontFace(); - _refreshFontFaceDependents(); + _invalidateFontFaceDependents(); - if (hadValue) - { - _NotifyChanges(L"HasFontFace", L"FontFace"); - } + _NotifyChanges(L"HasFontFace", L"FontFace"); } Model::FontConfig AppearanceViewModel::FontFaceOverrideSource() const @@ -412,50 +279,62 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation wil::com_ptr fontCollection; THROW_IF_FAILED(factory->GetSystemFontCollection(fontCollection.addressof(), FALSE)); - const auto fontFace = FontFace(); - std::wstring primaryFontName; + const auto fontFaceSpec = FontFace(); std::wstring missingFonts; std::wstring proportionalFonts; + std::array, 2> fontSettingsRemaining; BOOL hasPowerlineCharacters = FALSE; - til::iterate_font_families(fontFace, [&](wil::zwstring_view name) { - if (primaryFontName.empty()) - { - primaryFontName = name; - } + wchar_t localeNameBuffer[LOCALE_NAME_MAX_LENGTH]; + const auto localeName = GetUserDefaultLocaleName(localeNameBuffer, LOCALE_NAME_MAX_LENGTH) ? localeNameBuffer : L"en-US"; + til::iterate_font_families(fontFaceSpec, [&](wil::zwstring_view name) { std::wstring* accumulator = nullptr; - UINT32 index = 0; - BOOL exists = FALSE; - THROW_IF_FAILED(fontCollection->FindFamilyName(name.c_str(), &index, &exists)); - - // Look ma, no goto! - do + try { - if (!exists) + UINT32 index = 0; + BOOL exists = FALSE; + THROW_IF_FAILED(fontCollection->FindFamilyName(name.c_str(), &index, &exists)); + + // Look ma, no goto! + do { - accumulator = &missingFonts; - break; - } + if (!exists) + { + accumulator = &missingFonts; + break; + } - wil::com_ptr fontFamily; - THROW_IF_FAILED(fontCollection->GetFontFamily(index, fontFamily.addressof())); + wil::com_ptr fontFamily; + THROW_IF_FAILED(fontCollection->GetFontFamily(index, fontFamily.addressof())); - wil::com_ptr font; - THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, font.addressof())); + wil::com_ptr font; + THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL, DWRITE_FONT_STYLE_NORMAL, font.addressof())); - if (!font.query()->IsMonospacedFont()) - { - accumulator = &proportionalFonts; - } + if (!font.query()->IsMonospacedFont()) + { + accumulator = &proportionalFonts; + } + + // We're actually checking for the "Extended" PowerLine glyph set. + // They're more fun. + BOOL hasE0B6 = FALSE; + std::ignore = font->HasCharacter(0xE0B6, &hasE0B6); + hasPowerlineCharacters |= hasE0B6; - // We're actually checking for the "Extended" PowerLine glyph set. - // They're more fun. - BOOL hasE0B6 = FALSE; - std::ignore = font->HasCharacter(0xE0B6, &hasE0B6); - hasPowerlineCharacters |= hasE0B6; - } while (false); + wil::com_ptr fontFace; + THROW_IF_FAILED(font->CreateFontFace(fontFace.addressof())); + + _generateFontAxes(fontFace.get(), localeName, fontSettingsRemaining[FontAxesIndex]); + _generateFontFeatures(fontFace.get(), fontSettingsRemaining[FontFeaturesIndex]); + } while (false); + } + catch (...) + { + accumulator = &missingFonts; + LOG_CAUGHT_EXCEPTION(); + } if (accumulator) { @@ -467,10 +346,212 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } }); - _primaryFontName = std::move(primaryFontName); - MissingFontFaces(winrt::hstring{ missingFonts }); - ProportionalFontFaces(winrt::hstring{ proportionalFonts }); - HasPowerlineCharacters(hasPowerlineCharacters); + // Up to this point, our two vectors are sorted by tag value. We want to sort them by display string now, + // because this will result in sorted fontSettingsUsed/Unused lists below. + for (auto& v : fontSettingsRemaining) + { + std::sort(v.begin(), v.end(), FontKeyValuePair::SortAscending); + } + + std::array, 2> fontSettingsUsed; + const std::array fontSettingsUser{ + _appearance.SourceProfile().FontInfo().FontAxes(), + _appearance.SourceProfile().FontInfo().FontFeatures(), + }; + + // Find all axes and features that are in the user settings, and move them to the used list. + // They'll be displayed as a list in the UI. + for (int i = FontAxesIndex; i <= FontFeaturesIndex; i++) + { + const auto& map = fontSettingsUser[i]; + if (!map) + { + continue; + } + + for (const auto& [tagString, value] : fontSettingsUser[i]) + { + const auto tag = tagFromString(tagString); + if (!tag) + { + continue; + } + + auto& remaining = fontSettingsRemaining[i]; + const auto it = std::ranges::find_if(remaining, [&](const Editor::FontKeyValuePair& kv) { + return winrt::get_self(kv)->Key() == tag; + }); + + Editor::FontKeyValuePair kv{ nullptr }; + if (it != remaining.end()) + { + kv = std::move(*it); + remaining.erase(it); + + const auto kvImpl = winrt::get_self(kv); + kvImpl->SetValueDirect(value); + } + else + { + kv = winrt::make(get_weak(), hstring{}, tag, value, i == FontFeaturesIndex); + } + + fontSettingsUsed[i].emplace_back(std::move(kv)); + } + } + + std::array, 2> fontSettingsUnused; + + // All remaining (= unused) axes and features are turned into menu items. + // They'll be displayed as a flyout when clicking the "add item" button. + for (int i = FontAxesIndex; i <= FontFeaturesIndex; i++) + { + for (const auto& kv : fontSettingsRemaining[i]) + { + fontSettingsUnused[i].emplace_back(_createFontSettingMenuItem(kv)); + } + } + + auto& d = _fontFaceDependents.emplace(); + d.missingFontFaces = winrt::hstring{ missingFonts }; + d.proportionalFontFaces = winrt::hstring{ proportionalFonts }; + d.hasPowerlineCharacters = hasPowerlineCharacters; + + d.fontSettingsUsed[FontAxesIndex] = winrt::single_threaded_observable_vector(std::move(fontSettingsUsed[FontAxesIndex])); + d.fontSettingsUsed[FontFeaturesIndex] = winrt::single_threaded_observable_vector(std::move(fontSettingsUsed[FontFeaturesIndex])); + d.fontSettingsUnused = std::move(fontSettingsUnused); + + _notifyChangesForFontSettings(); + } + + std::pair::const_iterator, bool> AppearanceViewModel::_fontSettingSortedByKeyInsertPosition(const std::vector& vec, uint32_t key) + { + const auto it = std::lower_bound(vec.begin(), vec.end(), key, [](const Editor::FontKeyValuePair& lhs, uint32_t rhs) { + return winrt::get_self(lhs)->Key() < rhs; + }); + const auto exists = it != vec.end() && winrt::get_self(*it)->Key() == key; + return { it, exists }; + } + + void AppearanceViewModel::_generateFontAxes(IDWriteFontFace* fontFace, const wchar_t* localeName, std::vector& list) + { + const auto fontFace5 = wil::try_com_query(fontFace); + if (!fontFace5) + { + return; + } + + const auto axesCount = fontFace5->GetFontAxisValueCount(); + if (axesCount == 0) + { + return; + } + + std::vector axesVector(axesCount); + THROW_IF_FAILED(fontFace5->GetFontAxisValues(axesVector.data(), axesCount)); + + wil::com_ptr fontResource; + THROW_IF_FAILED(fontFace5->GetFontResource(fontResource.addressof())); + + for (UINT32 i = 0; i < axesCount; ++i) + { + wil::com_ptr names; + THROW_IF_FAILED(fontResource->GetAxisNames(i, names.addressof())); + + // As per MSDN: + // > The font author may not have supplied names for some font axes. + // > The localized strings will be empty in that case. + if (names->GetCount() == 0) + { + continue; + } + + const auto tag = axesVector[i].axisTag; + const auto [it, tagExists] = _fontSettingSortedByKeyInsertPosition(list, tag); + if (tagExists) + { + continue; + } + + UINT32 index; + BOOL exists; + if (FAILED(names->FindLocaleName(localeName, &index, &exists)) || !exists) + { + index = 0; + } + + const auto idx = getLocalizedStringIndex(names.get(), localeName, 0); + const auto localizedName = getLocalizedStringByIndex(names.get(), idx); + const auto tagString = tagToString(tag); + hstring displayString{ fmt::format(FMT_COMPILE(L"{} ({})"), localizedName, std::wstring_view{ tagString }) }; + + const auto value = axesVector[i].value; + + list.emplace(it, winrt::make(get_weak(), std::move(displayString), tag, value, false)); + } + } + + void AppearanceViewModel::_generateFontFeatures(IDWriteFontFace* fontFace, std::vector& list) + { + wil::com_ptr factory; + THROW_IF_FAILED(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(factory), reinterpret_cast<::IUnknown**>(factory.addressof()))); + + wil::com_ptr textAnalyzer; + THROW_IF_FAILED(factory->CreateTextAnalyzer(textAnalyzer.addressof())); + const auto textAnalyzer2 = textAnalyzer.query(); + + static constexpr DWRITE_SCRIPT_ANALYSIS scriptAnalysis{}; + UINT32 tagCount; + if (textAnalyzer2->GetTypographicFeatures(fontFace, scriptAnalysis, L"en-US", 0, &tagCount, nullptr) != E_NOT_SUFFICIENT_BUFFER) + { + return; + } + std::vector tags{ tagCount }; + if (FAILED(textAnalyzer2->GetTypographicFeatures(fontFace, scriptAnalysis, L"en-US", tagCount, &tagCount, tags.data()))) + { + return; + } + + for (const auto& tag : tags) + { + const auto [it, tagExists] = _fontSettingSortedByKeyInsertPosition(list, tag); + if (tagExists) + { + continue; + } + + const auto dfBeg = s_defaultFeatures.begin(); + const auto dfEnd = s_defaultFeatures.end(); + const auto isDefaultFeature = std::find(dfBeg, dfEnd, tag) != dfEnd; + const auto value = isDefaultFeature ? 1.0f : 0.0f; + + list.emplace(it, winrt::make(get_weak(), hstring{}, tag, value, true)); + } + } + + MenuFlyoutItemBase AppearanceViewModel::_createFontSettingMenuItem(const Editor::FontKeyValuePair& kv) + { + const auto kvImpl = winrt::get_self(kv); + + MenuFlyoutItem item; + item.Text(kvImpl->KeyDisplayStringRef()); + item.Click([weakSelf = get_weak(), kv](const IInspectable& sender, const RoutedEventArgs&) { + if (const auto self = weakSelf.get()) + { + self->AddFontKeyValuePair(sender, kv); + } + }); + return item; + } + + void AppearanceViewModel::_notifyChangesForFontSettings() + { + _NotifyChanges( + L"FontFaceDependents", + L"FontAxes", + L"FontFeatures", + L"HasFontAxes", + L"HasFontFeatures"); } double AppearanceViewModel::LineHeight() const @@ -535,333 +616,250 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation FontWeight(winrt::Microsoft::Terminal::UI::Converters::DoubleToFontWeight(fontWeight)); } - void AppearanceViewModel::SetBackgroundImageOpacityFromPercentageValue(double percentageValue) + IObservableVector AppearanceViewModel::FontAxes() { - BackgroundImageOpacity(static_cast(percentageValue) / 100.0f); + return FontFaceDependents().fontSettingsUsed[FontAxesIndex]; } - void AppearanceViewModel::SetBackgroundImagePath(winrt::hstring path) + bool AppearanceViewModel::HasFontAxes() const { - BackgroundImagePath(path); + return _appearance.SourceProfile().FontInfo().HasFontAxes(); } - bool AppearanceViewModel::UseDesktopBGImage() + void AppearanceViewModel::ClearFontAxes() { - return BackgroundImagePath() == L"desktopWallpaper"; + _deleteAllFontSettings(FontAxesIndex); } - void AppearanceViewModel::UseDesktopBGImage(const bool useDesktop) + Model::FontConfig AppearanceViewModel::FontAxesOverrideSource() const { - if (useDesktop) - { - // Stash the current value of BackgroundImagePath. If the user - // checks and un-checks the "Use desktop wallpaper" button, we want - // the path that we display in the text box to remain unchanged. - // - // Only stash this value if it's not the special "desktopWallpaper" - // value. - if (BackgroundImagePath() != L"desktopWallpaper") - { - _lastBgImagePath = BackgroundImagePath(); - } - BackgroundImagePath(L"desktopWallpaper"); - } - else - { - // Restore the path we had previously cached. This might be the - // empty string. - BackgroundImagePath(_lastBgImagePath); - } + return _appearance.SourceProfile().FontInfo().FontAxesOverrideSource(); } - bool AppearanceViewModel::BackgroundImageSettingsVisible() + IObservableVector AppearanceViewModel::FontFeatures() { - return BackgroundImagePath() != L""; + return FontFaceDependents().fontSettingsUsed[FontFeaturesIndex]; } - void AppearanceViewModel::ClearColorScheme() + bool AppearanceViewModel::HasFontFeatures() const { - ClearDarkColorSchemeName(); - _NotifyChanges(L"CurrentColorScheme"); + return _appearance.SourceProfile().FontInfo().HasFontFeatures(); } - Editor::ColorSchemeViewModel AppearanceViewModel::CurrentColorScheme() + void AppearanceViewModel::ClearFontFeatures() { - const auto schemeName{ DarkColorSchemeName() }; - const auto allSchemes{ SchemesList() }; - for (const auto& scheme : allSchemes) - { - if (scheme.Name() == schemeName) - { - return scheme; - } - } - // This Appearance points to a color scheme that was renamed or deleted. - // Fallback to the first one in the list. - return allSchemes.GetAt(0); + _deleteAllFontSettings(FontFeaturesIndex); } - void AppearanceViewModel::CurrentColorScheme(const ColorSchemeViewModel& val) + Model::FontConfig AppearanceViewModel::FontFeaturesOverrideSource() const { - DarkColorSchemeName(val.Name()); - LightColorSchemeName(val.Name()); + return _appearance.SourceProfile().FontInfo().FontFeaturesOverrideSource(); } - void AppearanceViewModel::AddNewAxisKeyValuePair() + void AppearanceViewModel::AddFontKeyValuePair(const IInspectable& sender, const Editor::FontKeyValuePair& kv) { - if (!_appearance.SourceProfile().FontInfo().FontAxes()) + if (!_fontFaceDependents) { - _appearance.SourceProfile().FontInfo().FontAxes(winrt::single_threaded_map()); + return; } - auto fontAxesMap = _appearance.SourceProfile().FontInfo().FontAxes(); - // find one axis that does not already exist, and add that - // if there are no more possible axes to add, the button is disabled so there shouldn't be a way to get here - const auto possibleAxesTagsAndNames = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontAxesTagsAndNames(); - for (const auto tagAndName : possibleAxesTagsAndNames) + const auto kvImpl = winrt::get_self(kv); + const auto fontSettingsIndex = kvImpl->IsFontFeature() ? 1 : 0; + auto& d = *_fontFaceDependents; + auto& used = d.fontSettingsUsed[fontSettingsIndex]; + auto& unused = d.fontSettingsUnused[fontSettingsIndex]; + + const auto it = std::ranges::find(unused, sender); + if (it == unused.end()) { - if (!fontAxesMap.HasKey(tagAndName.Key())) - { - fontAxesMap.Insert(tagAndName.Key(), gsl::narrow(0)); - FontAxesVector().Append(_CreateAxisKeyValuePairHelper(tagAndName.Key(), gsl::narrow(0), fontAxesMap, possibleAxesTagsAndNames)); - break; - } + return; } - _NotifyChanges(L"CanFontAxesBeAdded"); - } - void AppearanceViewModel::DeleteAxisKeyValuePair(winrt::hstring key) - { - for (uint32_t i = 0; i < _FontAxesVector.Size(); i++) + // Sync the added value into the user settings model. + UpdateFontSetting(kvImpl); + + // Insert the item into the used list, keeping it sorted by the display text. { - if (_FontAxesVector.GetAt(i).AxisKey() == key) - { - FontAxesVector().RemoveAt(i); - _appearance.SourceProfile().FontInfo().FontAxes().Remove(key); - if (_FontAxesVector.Size() == 0) - { - _appearance.SourceProfile().FontInfo().ClearFontAxes(); - } - break; - } + const auto it = std::lower_bound(used.begin(), used.end(), kv, FontKeyValuePair::SortAscending); + used.InsertAt(gsl::narrow(it - used.begin()), kv); } - _NotifyChanges(L"CanFontAxesBeAdded"); + + unused.erase(it); + + _notifyChangesForFontSettings(); } - void AppearanceViewModel::InitializeFontAxesVector() + void AppearanceViewModel::DeleteFontKeyValuePair(const Editor::FontKeyValuePair& kv) { - if (!_FontAxesVector) + if (!_fontFaceDependents) { - _FontAxesVector = winrt::single_threaded_observable_vector(); + return; } - _FontAxesVector.Clear(); - if (const auto fontAxesMap = _appearance.SourceProfile().FontInfo().FontAxes()) + const auto kvImpl = winrt::get_self(kv); + const auto tag = kvImpl->Key(); + const auto tagString = tagToString(tag); + const auto fontSettingsIndex = kvImpl->IsFontFeature() ? 1 : 0; + auto& d = *_fontFaceDependents; + auto& used = d.fontSettingsUsed[fontSettingsIndex]; + auto& unused = d.fontSettingsUnused[fontSettingsIndex]; + + const auto fontInfo = _appearance.SourceProfile().FontInfo(); + auto fontSettingsUser = kvImpl->IsFontFeature() ? fontInfo.FontFeatures() : fontInfo.FontAxes(); + if (!fontSettingsUser) { - const auto fontAxesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontAxesTagsAndNames(); - for (const auto axis : fontAxesMap) - { - // only show the axes that the font supports - // any axes that the font doesn't support continue to be stored in the json, we just don't show them in the UI - if (fontAxesTagToNameMap.HasKey(axis.Key())) - { - _FontAxesVector.Append(_CreateAxisKeyValuePairHelper(axis.Key(), axis.Value(), fontAxesMap, fontAxesTagToNameMap)); - } - } + return; } - _NotifyChanges(L"AreFontAxesAvailable", L"CanFontAxesBeAdded"); - } - // Method Description: - // - Determines whether the currently selected font has any variable font axes - bool AppearanceViewModel::AreFontAxesAvailable() - { - return ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontAxesTagsAndNames().Size() > 0; - } + const auto it = std::ranges::find(used, kv); + if (it == used.end()) + { + return; + } - // Method Description: - // - Determines whether the currently selected font has any variable font axes that have not already been set - bool AppearanceViewModel::CanFontAxesBeAdded() - { - if (const auto fontAxesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontAxesTagsAndNames(); fontAxesTagToNameMap.Size() > 0) + fontSettingsUser.Remove(std::wstring_view{ tagString }); + + // Insert the item into the unused list, keeping it sorted by the display text. { - if (const auto fontAxesMap = _appearance.SourceProfile().FontInfo().FontAxes()) - { - for (const auto tagAndName : fontAxesTagToNameMap) - { - if (!fontAxesMap.HasKey(tagAndName.Key())) - { - // we found an axis that has not been set - return true; - } - } - // all possible axes have been set already - return false; - } - // the font supports font axes but the profile has none set - return true; + const auto item = _createFontSettingMenuItem(*it); + const auto it = std::lower_bound(unused.begin(), unused.end(), item, [](const MenuFlyoutItemBase& lhs, const MenuFlyoutItemBase& rhs) { + const auto& a = lhs.as().Text(); + const auto& b = rhs.as().Text(); + return til::compare_linguistic_insensitive(a, b) < 0; + }); + unused.insert(it, item); } - // the font does not support any font axes - return false; - } - // Method Description: - // - Creates an AxisKeyValuePair and sets up an event handler for it - Editor::AxisKeyValuePair AppearanceViewModel::_CreateAxisKeyValuePairHelper(winrt::hstring axisKey, float axisValue, const Windows::Foundation::Collections::IMap& baseMap, const Windows::Foundation::Collections::IMap& tagToNameMap) - { - const auto axisKeyValuePair = winrt::make(axisKey, axisValue, baseMap, tagToNameMap); - // when either the key or the value changes, send an event for the preview control to catch - axisKeyValuePair.PropertyChanged([weakThis = get_weak()](auto& /*sender*/, auto& /*e*/) { - if (auto appVM{ weakThis.get() }) - { - appVM->_NotifyChanges(L"AxisKeyValuePair"); - } - }); - return axisKeyValuePair; + used.RemoveAt(gsl::narrow(it - used.begin())); + + _notifyChangesForFontSettings(); } - void AppearanceViewModel::AddNewFeatureKeyValuePair() + void AppearanceViewModel::UpdateFontSetting(const FontKeyValuePair* kvImpl) { + const auto tag = kvImpl->Key(); + const auto value = kvImpl->Value(); + const auto tagString = tagToString(tag); const auto fontInfo = _appearance.SourceProfile().FontInfo(); - auto fontFeaturesMap = fontInfo.FontFeatures(); - if (!fontFeaturesMap) - { - fontFeaturesMap = winrt::single_threaded_map(); - fontInfo.FontFeatures(fontFeaturesMap); - } + auto fontSettingsUser = kvImpl->IsFontFeature() ? fontInfo.FontFeatures() : fontInfo.FontAxes(); - // find one feature that does not already exist, and add that - // if there are no more possible features to add, the button is disabled so there shouldn't be a way to get here - const auto possibleFeaturesTagsAndNames = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontFeaturesTagsAndNames(); - for (const auto tagAndName : possibleFeaturesTagsAndNames) + if (!fontSettingsUser) { - const auto featureKey = tagAndName.Key(); - if (!fontFeaturesMap.HasKey(featureKey)) + fontSettingsUser = winrt::single_threaded_map(); + if (kvImpl->IsFontFeature()) + { + fontInfo.FontFeatures(fontSettingsUser); + } + else { - const auto featureDefaultValue = _IsDefaultFeature(featureKey) ? 1 : 0; - fontFeaturesMap.Insert(featureKey, featureDefaultValue); - FontFeaturesVector().Append(_CreateFeatureKeyValuePairHelper(featureKey, featureDefaultValue, fontFeaturesMap, possibleFeaturesTagsAndNames)); - break; + fontInfo.FontAxes(fontSettingsUser); } } - _NotifyChanges(L"CanFontFeaturesBeAdded"); + + std::ignore = fontSettingsUser.Insert(std::wstring_view{ tagString }, value); } - void AppearanceViewModel::DeleteFeatureKeyValuePair(hstring key) + void AppearanceViewModel::_deleteAllFontSettings(FontSettingIndex fontSettingsIndex) { - for (uint32_t i = 0; i < _FontFeaturesVector.Size(); i++) + const auto fontInfo = _appearance.SourceProfile().FontInfo(); + if (fontSettingsIndex == FontFeaturesIndex) { - if (_FontFeaturesVector.GetAt(i).FeatureKey() == key) - { - FontFeaturesVector().RemoveAt(i); - _appearance.SourceProfile().FontInfo().FontFeatures().Remove(key); - if (_FontFeaturesVector.Size() == 0) - { - _appearance.SourceProfile().FontInfo().ClearFontFeatures(); - } - break; - } + fontInfo.ClearFontFeatures(); + } + else + { + fontInfo.ClearFontAxes(); } - _NotifyChanges(L"CanFontAxesBeAdded"); - } - void AppearanceViewModel::InitializeFontFeaturesVector() - { - if (!_FontFeaturesVector) + if (!_fontFaceDependents) { - _FontFeaturesVector = single_threaded_observable_vector(); + return; } - _FontFeaturesVector.Clear(); - if (const auto fontFeaturesMap = _appearance.SourceProfile().FontInfo().FontFeatures()) + auto& d = *_fontFaceDependents; + auto& used = d.fontSettingsUsed[fontSettingsIndex]; + auto& unused = d.fontSettingsUnused[fontSettingsIndex]; + + for (const auto& kv : used) { - const auto fontFeaturesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontFeaturesTagsAndNames(); - for (const auto feature : fontFeaturesMap) - { - const auto featureKey = feature.Key(); - // only show the features that the font supports - // any features that the font doesn't support continue to be stored in the json, we just don't show them in the UI - if (fontFeaturesTagToNameMap.HasKey(featureKey)) - { - _FontFeaturesVector.Append(_CreateFeatureKeyValuePairHelper(featureKey, feature.Value(), fontFeaturesMap, fontFeaturesTagToNameMap)); - } - } + unused.emplace_back(_createFontSettingMenuItem(kv)); } - _NotifyChanges(L"AreFontFeaturesAvailable", L"CanFontFeaturesBeAdded"); + + used.Clear(); + + _notifyChangesForFontSettings(); } - // Method Description: - // - Determines whether the currently selected font has any font features - bool AppearanceViewModel::AreFontFeaturesAvailable() + void AppearanceViewModel::SetBackgroundImageOpacityFromPercentageValue(double percentageValue) + { + BackgroundImageOpacity(static_cast(percentageValue) / 100.0f); + } + + void AppearanceViewModel::SetBackgroundImagePath(winrt::hstring path) { - return ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontFeaturesTagsAndNames().Size() > 0; + BackgroundImagePath(path); } - // Method Description: - // - Determines whether the currently selected font has any font features that have not already been set - bool AppearanceViewModel::CanFontFeaturesBeAdded() + bool AppearanceViewModel::UseDesktopBGImage() { - if (const auto fontFeaturesTagToNameMap = ProfileViewModel::FindFontWithLocalizedName(_primaryFontName).FontFeaturesTagsAndNames(); fontFeaturesTagToNameMap.Size() > 0) + return BackgroundImagePath() == L"desktopWallpaper"; + } + + void AppearanceViewModel::UseDesktopBGImage(const bool useDesktop) + { + if (useDesktop) { - if (const auto fontFeaturesMap = _appearance.SourceProfile().FontInfo().FontFeatures()) + // Stash the current value of BackgroundImagePath. If the user + // checks and un-checks the "Use desktop wallpaper" button, we want + // the path that we display in the text box to remain unchanged. + // + // Only stash this value if it's not the special "desktopWallpaper" + // value. + if (BackgroundImagePath() != L"desktopWallpaper") { - for (const auto tagAndName : fontFeaturesTagToNameMap) - { - if (!fontFeaturesMap.HasKey(tagAndName.Key())) - { - // we found a feature that has not been set - return true; - } - } - // all possible features have been set already - return false; + _lastBgImagePath = BackgroundImagePath(); } - // the font supports font features but the profile has none set - return true; + BackgroundImagePath(L"desktopWallpaper"); + } + else + { + // Restore the path we had previously cached. This might be the + // empty string. + BackgroundImagePath(_lastBgImagePath); } - // the font does not support any font features - return false; } - // Method Description: - // - Creates a FeatureKeyValuePair and sets up an event handler for it - Editor::FeatureKeyValuePair AppearanceViewModel::_CreateFeatureKeyValuePairHelper(hstring featureKey, uint32_t featureValue, const IMap& baseMap, const IMap& tagToNameMap) + bool AppearanceViewModel::BackgroundImageSettingsVisible() { - const auto featureKeyValuePair = winrt::make(featureKey, featureValue, baseMap, tagToNameMap); - // when either the key or the value changes, send an event for the preview control to catch - featureKeyValuePair.PropertyChanged([weakThis = get_weak()](auto& sender, const PropertyChangedEventArgs& args) { - if (auto appVM{ weakThis.get() }) - { - appVM->_NotifyChanges(L"FeatureKeyValuePair"); - const auto settingName{ args.PropertyName() }; - if (settingName == L"FeatureKey") - { - const auto senderPair = sender.as(); - const auto senderKey = senderPair->FeatureKey(); - if (appVM->_IsDefaultFeature(senderKey)) - { - senderPair->FeatureValue(1); - } - else - { - senderPair->FeatureValue(0); - } - } - } - }); - return featureKeyValuePair; + return BackgroundImagePath() != L""; } - bool AppearanceViewModel::_IsDefaultFeature(winrt::hstring featureKey) + void AppearanceViewModel::ClearColorScheme() { - for (const auto defaultFeature : DefaultFeatures) + ClearDarkColorSchemeName(); + _NotifyChanges(L"CurrentColorScheme"); + } + + Editor::ColorSchemeViewModel AppearanceViewModel::CurrentColorScheme() + { + const auto schemeName{ DarkColorSchemeName() }; + const auto allSchemes{ SchemesList() }; + for (const auto& scheme : allSchemes) { - if (defaultFeature == featureKey) + if (scheme.Name() == schemeName) { - return true; + return scheme; } } - return false; + // This Appearance points to a color scheme that was renamed or deleted. + // Fallback to the first one in the list. + return allSchemes.GetAt(0); + } + + void AppearanceViewModel::CurrentColorScheme(const ColorSchemeViewModel& val) + { + DarkColorSchemeName(val.Name()); + LightColorSchemeName(val.Name()); } DependencyProperty Appearances::_AppearanceProperty{ nullptr }; @@ -928,12 +926,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation const auto backgroundImgCheckboxTooltip{ ToolTipService::GetToolTip(UseDesktopImageCheckBox()) }; Automation::AutomationProperties::SetFullDescription(UseDesktopImageCheckBox(), unbox_value(backgroundImgCheckboxTooltip)); - _FontAxesNames = winrt::single_threaded_observable_vector(); - FontAxesNamesCVS().Source(_FontAxesNames); - - _FontFeaturesNames = winrt::single_threaded_observable_vector(); - FontFeaturesNamesCVS().Source(_FontFeaturesNames); - INITIALIZE_BINDABLE_ENUM_SETTING(IntenseTextStyle, IntenseTextStyle, winrt::Microsoft::Terminal::Settings::Model::IntenseStyle, L"Appearance_IntenseTextStyle", L"Content"); } @@ -983,55 +975,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { appearance.FontFace(fontSpec); } - - // TODO: Any use of FindFontWithLocalizedName is broken and requires refactoring in time for version 1.21. - const auto newFontFace = ProfileViewModel::FindFontWithLocalizedName(fontSpec); - if (!newFontFace) - { - return; - } - - _FontAxesNames.Clear(); - const auto axesTagsAndNames = newFontFace.FontAxesTagsAndNames(); - for (const auto tagAndName : axesTagsAndNames) - { - _FontAxesNames.Append(tagAndName.Value()); - } - - _FontFeaturesNames.Clear(); - const auto featuresTagsAndNames = newFontFace.FontFeaturesTagsAndNames(); - for (const auto tagAndName : featuresTagsAndNames) - { - _FontFeaturesNames.Append(tagAndName.Value()); - } - - // when the font face changes, we have to tell the view model to update the font axes/features vectors - // since the new font may not have the same possible axes as the previous one - Appearance().InitializeFontAxesVector(); - if (!Appearance().AreFontAxesAvailable()) - { - // if the previous font had available font axes and the expander was expanded, - // at this point the expander would be set to disabled so manually collapse it - FontAxesContainer().SetExpanded(false); - FontAxesContainer().HelpText(RS_(L"Profile_FontAxesUnavailable/Text")); - } - else - { - FontAxesContainer().HelpText(RS_(L"Profile_FontAxesAvailable/Text")); - } - - Appearance().InitializeFontFeaturesVector(); - if (!Appearance().AreFontFeaturesAvailable()) - { - // if the previous font had available font features and the expander was expanded, - // at this point the expander would be set to disabled so manually collapse it - FontFeaturesContainer().SetExpanded(false); - FontFeaturesContainer().HelpText(RS_(L"Profile_FontFeaturesUnavailable/Text")); - } - else - { - FontFeaturesContainer().HelpText(RS_(L"Profile_FontFeaturesAvailable/Text")); - } } void Appearances::FontFaceBox_SuggestionChosen(const AutoSuggestBox& sender, const AutoSuggestBoxSuggestionChosenEventArgs& args) @@ -1127,17 +1070,30 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { if (const auto appearance = Appearance()) { - const auto& biAlignmentVal{ static_cast(appearance.BackgroundImageAlignment()) }; + const auto appearanceImpl = winrt::get_self(appearance); + const auto& biAlignmentVal{ static_cast(appearanceImpl->BackgroundImageAlignment()) }; for (const auto& biButton : _BIAlignmentButtons) { biButton.IsChecked(biButton.Tag().as() == biAlignmentVal); } - FontAxesCVS().Source(Appearance().FontAxesVector()); - FontAxesContainer().HelpText(appearance.AreFontAxesAvailable() ? RS_(L"Profile_FontAxesAvailable/Text") : RS_(L"Profile_FontAxesUnavailable/Text")); + { + const auto& d = appearanceImpl->FontFaceDependents(); + + const std::array buttons{ + AddFontAxisButton(), + AddFontFeatureButton(), + }; - FontFeaturesCVS().Source(Appearance().FontFeaturesVector()); - FontFeaturesContainer().HelpText(appearance.AreFontFeaturesAvailable() ? RS_(L"Profile_FontFeaturesAvailable/Text") : RS_(L"Profile_FontFeaturesUnavailable/Text")); + for (int i = 0; i < 2; ++i) + { + const auto& button = buttons[i]; + const auto& data = d.fontSettingsUnused[i]; + const auto items = button.Flyout().as().Items(); + items.ReplaceAll(data); + button.IsEnabled(!data.empty()); + } + } _ViewModelChangedRevoker = appearance.PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& args) { const auto settingName{ args.PropertyName() }; @@ -1156,13 +1112,36 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } else if (settingName == L"BackgroundImageAlignment") { - _UpdateBIAlignmentControl(static_cast(appearance.BackgroundImageAlignment())); + _UpdateBIAlignmentControl(static_cast(appearanceImpl->BackgroundImageAlignment())); } else if (settingName == L"FontWeight") { PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentFontWeight" }); PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"IsCustomFontWeight" }); } + else if (settingName == L"FontFaceDependents") + { + const auto& d = appearanceImpl->FontFaceDependents(); + + const std::array buttons{ + AddFontAxisButton(), + AddFontFeatureButton(), + }; + + for (int i = 0; i < 2; ++i) + { + const auto& button = buttons[i]; + const auto& data = d.fontSettingsUnused[i]; + const auto flyout = button.Flyout().as(); + const auto items = flyout.Items(); + items.ReplaceAll(data); + button.IsEnabled(!data.empty()); + // WinUI doesn't hide the flyout when it's currently open and the items are now empty. + // In fact it doesn't close it, period. You click an item? Flyout stays open! + // This gets called whenever an item is selected, so it's the "perfect" time to close it. + flyout.Hide(); + } + } else if (settingName == L"IntenseTextStyle") { PropertyChanged.raise(*this, PropertyChangedEventArgs{ L"CurrentIntenseTextStyle" }); @@ -1248,36 +1227,12 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } } - void Appearances::DeleteAxisKeyValuePair_Click(const IInspectable& sender, const RoutedEventArgs& /*e*/) - { - if (const auto& button{ sender.try_as() }) - { - if (const auto& tag{ button.Tag().try_as() }) - { - Appearance().DeleteAxisKeyValuePair(tag.value()); - } - } - } - - void Appearances::AddNewAxisKeyValuePair_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/) - { - Appearance().AddNewAxisKeyValuePair(); - } - - void Appearances::DeleteFeatureKeyValuePair_Click(const IInspectable& sender, const RoutedEventArgs& /*e*/) - { - if (const auto& button{ sender.try_as() }) - { - if (const auto& tag{ button.Tag().try_as() }) - { - Appearance().DeleteFeatureKeyValuePair(tag.value()); - } - } - } - - void Appearances::AddNewFeatureKeyValuePair_Click(const IInspectable& /*sender*/, const RoutedEventArgs& /*e*/) + void Appearances::DeleteFontKeyValuePair_Click(const IInspectable& sender, const RoutedEventArgs& /*e*/) { - Appearance().AddNewFeatureKeyValuePair(); + const auto element = sender.as(); + const auto tag = element.Tag(); + const auto kv = tag.as(); + winrt::get_self(Appearance())->DeleteFontKeyValuePair(kv); } bool Appearances::IsVintageCursor() const diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.h b/src/cascadia/TerminalSettingsEditor/Appearances.h index d01819b24b5..572b6240984 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.h +++ b/src/cascadia/TerminalSettingsEditor/Appearances.h @@ -17,8 +17,7 @@ Author(s): #pragma once #include "Font.g.h" -#include "AxisKeyValuePair.g.h" -#include "FeatureKeyValuePair.g.h" +#include "FontKeyValuePair.g.h" #include "Appearances.g.h" #include "AppearanceViewModel.g.h" #include "Utils.h" @@ -28,81 +27,57 @@ Author(s): namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { + struct AppearanceViewModel; + struct Font : FontT { - public: - Font(winrt::hstring name, winrt::hstring localizedName, wil::com_ptr family) : - _Name{ std::move(name) }, - _LocalizedName{ std::move(localizedName) }, - _family{ std::move(family) } - { - } - - hstring ToString() { return _LocalizedName; } - Windows::Foundation::Collections::IMap FontAxesTagsAndNames(); - Windows::Foundation::Collections::IMap FontFeaturesTagsAndNames(); + Font(winrt::hstring name, winrt::hstring localizedName); WINRT_PROPERTY(hstring, Name); WINRT_PROPERTY(hstring, LocalizedName); - - private: - winrt::hstring _tagToString(DWRITE_FONT_AXIS_TAG tag); - winrt::hstring _tagToString(DWRITE_FONT_FEATURE_TAG tag); - - Windows::Foundation::Collections::IMap _fontAxesTagsAndNames; - Windows::Foundation::Collections::IMap _fontFeaturesTagsAndNames; - wil::com_ptr _family; }; - struct AxisKeyValuePair : AxisKeyValuePairT, ViewModelHelper + struct FontKeyValuePair : FontKeyValuePairT { - AxisKeyValuePair(winrt::hstring axisKey, float axisValue, const Windows::Foundation::Collections::IMap& baseMap, const Windows::Foundation::Collections::IMap& tagToNameMap); + static bool SortAscending(const Editor::FontKeyValuePair& lhs, const Editor::FontKeyValuePair& rhs); - winrt::hstring AxisKey(); - void AxisKey(winrt::hstring axisKey); + FontKeyValuePair(winrt::weak_ref vm, winrt::hstring keyDisplayString, uint32_t key, float value, bool isFontFeature); - float AxisValue(); - void AxisValue(float axisValue); + winrt::hstring KeyDisplayString(); + const winrt::hstring& KeyDisplayStringRef(); + uint32_t Key() const noexcept; + float Value() const noexcept; + void Value(float v); - int32_t AxisIndex(); - void AxisIndex(int32_t axisIndex); - - til::property_changed_event PropertyChanged; + void SetValueDirect(float v); + bool IsFontFeature() const noexcept; private: - winrt::hstring _AxisKey; - float _AxisValue; - int32_t _AxisIndex; - Windows::Foundation::Collections::IMap _baseMap{ nullptr }; - Windows::Foundation::Collections::IMap _tagToNameMap{ nullptr }; + winrt::weak_ref _vm; + winrt::hstring _keyDisplayString; + uint32_t _key; + float _value; + bool _isFontFeature; }; - struct FeatureKeyValuePair : FeatureKeyValuePairT, ViewModelHelper + struct AppearanceViewModel : AppearanceViewModelT, ViewModelHelper { - FeatureKeyValuePair(winrt::hstring featureKey, uint32_t featureValue, const Windows::Foundation::Collections::IMap& baseMap, const Windows::Foundation::Collections::IMap& tagToNameMap); - - winrt::hstring FeatureKey(); - void FeatureKey(winrt::hstring featureKey); - - uint32_t FeatureValue(); - void FeatureValue(uint32_t featureValue); - - int32_t FeatureIndex(); - void FeatureIndex(int32_t featureIndex); + enum FontSettingIndex + { + FontAxesIndex, + FontFeaturesIndex, + }; - til::property_changed_event PropertyChanged; + struct FontFaceDependentsData + { + winrt::hstring missingFontFaces; + winrt::hstring proportionalFontFaces; + bool hasPowerlineCharacters = false; - private: - winrt::hstring _FeatureKey; - uint32_t _FeatureValue; - int32_t _FeatureIndex; - Windows::Foundation::Collections::IMap _baseMap{ nullptr }; - Windows::Foundation::Collections::IMap _tagToNameMap{ nullptr }; - }; + std::array, 2> fontSettingsUsed; + std::array, 2> fontSettingsUnused; + }; - struct AppearanceViewModel : AppearanceViewModelT, ViewModelHelper - { - public: AppearanceViewModel(const Model::AppearanceConfig& appearance); winrt::hstring FontFace() const; @@ -118,32 +93,46 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Model::FontConfig LineHeightOverrideSource() const; void SetFontWeightFromDouble(double fontWeight); - void SetBackgroundImageOpacityFromPercentageValue(double percentageValue); - void SetBackgroundImagePath(winrt::hstring path); + + const FontFaceDependentsData& FontFaceDependents() + { + if (!_fontFaceDependents) + { + _refreshFontFaceDependents(); + } + return *_fontFaceDependents; + } + + winrt::hstring MissingFontFaces() { return FontFaceDependents().missingFontFaces; } + winrt::hstring ProportionalFontFaces() { return FontFaceDependents().proportionalFontFaces; } + bool HasPowerlineCharacters() { return FontFaceDependents().hasPowerlineCharacters; } + + Windows::Foundation::Collections::IObservableVector FontAxes(); + bool HasFontAxes() const; + void ClearFontAxes(); + Model::FontConfig FontAxesOverrideSource() const; + + Windows::Foundation::Collections::IObservableVector FontFeatures(); + bool HasFontFeatures() const; + void ClearFontFeatures(); + Model::FontConfig FontFeaturesOverrideSource() const; + + void AddFontKeyValuePair(const IInspectable& sender, const Editor::FontKeyValuePair& kv); + void DeleteFontKeyValuePair(const Editor::FontKeyValuePair& kv); + void UpdateFontSetting(const FontKeyValuePair* kv); // background image bool UseDesktopBGImage(); void UseDesktopBGImage(const bool useDesktop); bool BackgroundImageSettingsVisible(); + void SetBackgroundImageOpacityFromPercentageValue(double percentageValue); + void SetBackgroundImagePath(winrt::hstring path); void ClearColorScheme(); Editor::ColorSchemeViewModel CurrentColorScheme(); void CurrentColorScheme(const Editor::ColorSchemeViewModel& val); - void AddNewAxisKeyValuePair(); - void DeleteAxisKeyValuePair(winrt::hstring key); - void InitializeFontAxesVector(); - bool AreFontAxesAvailable(); - bool CanFontAxesBeAdded(); - - void AddNewFeatureKeyValuePair(); - void DeleteFeatureKeyValuePair(winrt::hstring key); - void InitializeFontFeaturesVector(); - bool AreFontFeaturesAvailable(); - bool CanFontFeaturesBeAdded(); - WINRT_PROPERTY(bool, IsDefault, false); - WINRT_PROPERTY(bool, HasPowerlineCharacters, false); // These settings are not defined in AppearanceConfig, so we grab them // from the source profile itself. The reason we still want them in the @@ -152,8 +141,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // are defined in AppearanceConfig and some that are not. OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontSize); OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontWeight); - OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontAxes); - OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), FontFeatures); OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), EnableBuiltinGlyphs); OBSERVABLE_PROJECTED_SETTING(_appearance.SourceProfile().FontInfo(), EnableColorGlyphs); @@ -169,28 +156,24 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation OBSERVABLE_PROJECTED_SETTING(_appearance, IntenseTextStyle); OBSERVABLE_PROJECTED_SETTING(_appearance, AdjustIndistinguishableColors); WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector, SchemesList, _propertyChangedHandlers, nullptr); - WINRT_OBSERVABLE_PROPERTY(winrt::hstring, MissingFontFaces, _propertyChangedHandlers); - WINRT_OBSERVABLE_PROPERTY(winrt::hstring, ProportionalFontFaces, _propertyChangedHandlers); - WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector, FontAxesVector, _propertyChangedHandlers, nullptr); - WINRT_OBSERVABLE_PROPERTY(Windows::Foundation::Collections::IObservableVector, FontFeaturesVector, _propertyChangedHandlers, nullptr); private: + void _invalidateFontFaceDependents() { _fontFaceDependents.reset(); } void _refreshFontFaceDependents(); - Editor::AxisKeyValuePair _CreateAxisKeyValuePairHelper(winrt::hstring axisKey, float axisValue, const Windows::Foundation::Collections::IMap& baseMap, const Windows::Foundation::Collections::IMap& tagToNameMap); - Editor::FeatureKeyValuePair _CreateFeatureKeyValuePairHelper(winrt::hstring axisKey, uint32_t axisValue, const Windows::Foundation::Collections::IMap& baseMap, const Windows::Foundation::Collections::IMap& tagToNameMap); - bool _IsDefaultFeature(winrt::hstring featureTag); + static std::pair::const_iterator, bool> _fontSettingSortedByKeyInsertPosition(const std::vector& vec, uint32_t key); + void _generateFontAxes(IDWriteFontFace* fontFace, const wchar_t* localeName, std::vector& list); + void _generateFontFeatures(IDWriteFontFace* fontFace, std::vector& list); + Windows::UI::Xaml::Controls::MenuFlyoutItemBase _createFontSettingMenuItem(const Editor::FontKeyValuePair& kv); + void _notifyChangesForFontSettings(); + void _deleteAllFontSettings(FontSettingIndex index); Model::AppearanceConfig _appearance; winrt::hstring _lastBgImagePath; - std::wstring _primaryFontName; + std::optional _fontFaceDependents; }; struct Appearances : AppearancesT { - public: - static winrt::hstring FontAxisName(const winrt::hstring& key); - static winrt::hstring FontFeatureName(const winrt::hstring& key); - Appearances(); // CursorShape visibility logic @@ -204,12 +187,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void FontFaceBox_LostFocus(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); void FontFaceBox_SuggestionChosen(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox&, const winrt::Windows::UI::Xaml::Controls::AutoSuggestBoxSuggestionChosenEventArgs&); void FontFaceBox_TextChanged(const winrt::Windows::UI::Xaml::Controls::AutoSuggestBox&, const winrt::Windows::UI::Xaml::Controls::AutoSuggestBoxTextChangedEventArgs&); + void DeleteFontKeyValuePair_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); fire_and_forget BackgroundImage_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); void BIAlignment_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); - void DeleteAxisKeyValuePair_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); - void AddNewAxisKeyValuePair_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); - void DeleteFeatureKeyValuePair_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); - void AddNewFeatureKeyValuePair_Click(const Windows::Foundation::IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& e); // manually bind FontWeight Windows::Foundation::IInspectable CurrentFontWeight() const; @@ -253,6 +233,4 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation namespace winrt::Microsoft::Terminal::Settings::Editor::factory_implementation { BASIC_FACTORY(Appearances); - BASIC_FACTORY(AxisKeyValuePair); - BASIC_FACTORY(FeatureKeyValuePair); } diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.idl b/src/cascadia/TerminalSettingsEditor/Appearances.idl index 2855fa21b72..0907e5f8f3d 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.idl +++ b/src/cascadia/TerminalSettingsEditor/Appearances.idl @@ -17,30 +17,17 @@ import "ColorSchemesPageViewModel.idl"; namespace Microsoft.Terminal.Settings.Editor { - runtimeclass Font : Windows.Foundation.IStringable + runtimeclass Font { String Name { get; }; String LocalizedName { get; }; - Windows.Foundation.Collections.IMap FontAxesTagsAndNames { get; }; - Windows.Foundation.Collections.IMap FontFeaturesTagsAndNames { get; }; } - // We have to make this because we cannot bind an IObservableMap to a ListView in XAML (in c++) - // So instead we make an IObservableVector of these AxisKeyValuePair objects - runtimeclass AxisKeyValuePair : Windows.UI.Xaml.Data.INotifyPropertyChanged + runtimeclass FontKeyValuePair { - AxisKeyValuePair(String axisKey, Single axisValue, Windows.Foundation.Collections.IMap baseMap, Windows.Foundation.Collections.IMap tagToNameMap); - String AxisKey; - Single AxisValue; - Int32 AxisIndex; - } - - runtimeclass FeatureKeyValuePair : Windows.UI.Xaml.Data.INotifyPropertyChanged - { - FeatureKeyValuePair(String featureKey, UInt32 featureValue, Windows.Foundation.Collections.IMap baseMap, Windows.Foundation.Collections.IMap tagToNameMap); - String FeatureKey; - UInt32 FeatureValue; - Int32 FeatureIndex; + UInt32 Key { get; }; + String KeyDisplayString { get; }; + Single Value; } runtimeclass AppearanceViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged @@ -56,27 +43,21 @@ namespace Microsoft.Terminal.Settings.Editor void ClearColorScheme(); ColorSchemeViewModel CurrentColorScheme; - Windows.Foundation.Collections.IObservableVector SchemesList; + IObservableVector SchemesList; String MissingFontFaces { get; }; String ProportionalFontFaces { get; }; Boolean HasPowerlineCharacters { get; }; - void AddNewAxisKeyValuePair(); - void DeleteAxisKeyValuePair(String key); - void InitializeFontAxesVector(); - Boolean AreFontAxesAvailable { get; }; - Boolean CanFontAxesBeAdded { get; }; - Windows.Foundation.Collections.IObservableVector FontAxesVector; - OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Windows.Foundation.Collections.IMap, FontAxes); - - void AddNewFeatureKeyValuePair(); - void DeleteFeatureKeyValuePair(String key); - void InitializeFontFeaturesVector(); - Boolean AreFontFeaturesAvailable { get; }; - Boolean CanFontFeaturesBeAdded { get; }; - Windows.Foundation.Collections.IObservableVector FontFeaturesVector; - OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Windows.Foundation.Collections.IMap, FontFeatures); + IObservableVector FontAxes { get; }; + Boolean HasFontAxes { get; }; + void ClearFontAxes(); + Object FontAxesOverrideSource { get; }; + + IObservableVector FontFeatures { get; }; + Boolean HasFontFeatures { get; }; + void ClearFontFeatures(); + Object FontFeaturesOverrideSource { get; }; OBSERVABLE_PROJECTED_APPEARANCE_SETTING(String, FontFace); OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Single, FontSize); @@ -106,24 +87,24 @@ namespace Microsoft.Terminal.Settings.Editor IHostedInWindow WindowRoot; static Windows.UI.Xaml.DependencyProperty AppearanceProperty { get; }; - Windows.Foundation.Collections.IObservableVector FilteredFontList { get; }; + IObservableVector FilteredFontList { get; }; Boolean ShowAllFonts; IInspectable CurrentCursorShape; Boolean IsVintageCursor { get; }; - Windows.Foundation.Collections.IObservableVector CursorShapeList { get; }; + IObservableVector CursorShapeList { get; }; IInspectable CurrentAdjustIndistinguishableColors; - Windows.Foundation.Collections.IObservableVector AdjustIndistinguishableColorsList { get; }; + IObservableVector AdjustIndistinguishableColorsList { get; }; IInspectable CurrentBackgroundImageStretchMode; - Windows.Foundation.Collections.IObservableVector BackgroundImageStretchModeList { get; }; + IObservableVector BackgroundImageStretchModeList { get; }; IInspectable CurrentFontWeight; Boolean IsCustomFontWeight { get; }; - Windows.Foundation.Collections.IObservableVector FontWeightList { get; }; + IObservableVector FontWeightList { get; }; IInspectable CurrentIntenseTextStyle; - Windows.Foundation.Collections.IObservableVector IntenseTextStyleList { get; }; + IObservableVector IntenseTextStyleList { get; }; } } diff --git a/src/cascadia/TerminalSettingsEditor/Appearances.xaml b/src/cascadia/TerminalSettingsEditor/Appearances.xaml index 7d065dc4c92..2f94cf4473b 100644 --- a/src/cascadia/TerminalSettingsEditor/Appearances.xaml +++ b/src/cascadia/TerminalSettingsEditor/Appearances.xaml @@ -38,10 +38,29 @@ Background="{x:Bind mtu:Converters.ColorToBrush(Color)}" CornerRadius="1" /> - - + + + + + + + + + + + + + @@ -190,8 +209,7 @@ Visibility="{x:Bind Appearance.IsDefault, Mode=OneWay}"> - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + @@ -721,8 +670,7 @@ - GetFamilyNames(familyNames.addressof())); // If en-us is missing we fall back to whatever is at index 0. - const auto ci = getLocalizedStringIndex(familyNames.get(), L"en-us", 0); + const auto ci = getLocalizedStringIndex(familyNames.get(), L"en-US", 0); // If our locale is missing we fall back to en-us. const auto li = getLocalizedStringIndex(familyNames.get(), locale, ci); @@ -242,7 +212,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // If the canonical/localized indices are the same, there's no need to get the other string. auto localized = ci == li ? canonical : getLocalizedStringByIndex(familyNames.get(), li); - return make(std::move(canonical), std::move(localized), family); + return make(std::move(canonical), std::move(localized)); } winrt::guid ProfileViewModel::OriginalProfileGuid() const noexcept diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h index 04225a46efa..f0ce84f1f05 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h @@ -33,7 +33,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation static void UpdateFontList() noexcept; static Windows::Foundation::Collections::IObservableVector CompleteFontList() noexcept { return _FontList; }; static Windows::Foundation::Collections::IObservableVector MonospaceFontList() noexcept { return _MonospaceFontList; }; - static Editor::Font FindFontWithLocalizedName(winrt::hstring const& name) noexcept; ProfileViewModel(const Model::Profile& profile, const Model::CascadiaSettings& settings); Model::TerminalSettings TermSettings() const; @@ -156,9 +155,3 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation 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 index 315afdfb4fe..c0bc407c7ae 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.idl @@ -34,10 +34,6 @@ namespace Microsoft.Terminal.Settings.Editor runtimeclass ProfileViewModel : Windows.UI.Xaml.Data.INotifyPropertyChanged { - static Windows.Foundation.Collections.IObservableVector CompleteFontList { get; }; - static Windows.Foundation.Collections.IObservableVector MonospaceFontList { get; }; - static Font FindFontWithLocalizedName(String name); - Microsoft.Terminal.Settings.Model.TerminalSettings TermSettings { get; }; event Windows.Foundation.TypedEventHandler DeleteProfileRequested; diff --git a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw index 405734f241b..f27f774c55c 100644 --- a/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsEditor/Resources/en-US/Resources.resw @@ -733,10 +733,16 @@ Browse... Button label that opens a file picker in a new window. The "..." is standard to mean it will open a new window. + + Add new font axis + Add new Button label that adds a new font axis for the current font. + + Add new font feature + Add new Button label that adds a new font feature for the current font. diff --git a/src/cascadia/TerminalSettingsModel/FontConfig.cpp b/src/cascadia/TerminalSettingsModel/FontConfig.cpp index fd9a181ae30..746ceee649d 100644 --- a/src/cascadia/TerminalSettingsModel/FontConfig.cpp +++ b/src/cascadia/TerminalSettingsModel/FontConfig.cpp @@ -32,24 +32,21 @@ winrt::com_ptr FontConfig::CopyFontInfo(const FontConfig* source, wi // We cannot simply copy the font axes and features with `fontInfo->_FontAxes = source->_FontAxes;` // since that'll just create a reference; we have to manually copy the values. - if (source->_FontAxes) - { + static constexpr auto cloneFontMap = [](const IFontFeatureMap& map) { std::map fontAxes; - for (const auto keyValuePair : source->_FontAxes.value()) + for (const auto& [k, v] : map) { - fontAxes.insert(std::pair(keyValuePair.Key(), keyValuePair.Value())); + fontAxes.emplace(k, v); } - fontInfo->_FontAxes = winrt::single_threaded_map(std::move(fontAxes)); + return winrt::single_threaded_map(std::move(fontAxes)); + }; + if (source->_FontAxes) + { + fontInfo->_FontAxes = cloneFontMap(*source->_FontAxes); } - if (source->_FontFeatures) { - std::map fontFeatures; - for (const auto keyValuePair : source->_FontFeatures.value()) - { - fontFeatures.insert(std::pair(keyValuePair.Key(), keyValuePair.Value())); - } - fontInfo->_FontFeatures = winrt::single_threaded_map(std::move(fontFeatures)); + fontInfo->_FontFeatures = cloneFontMap(*source->_FontFeatures); } return fontInfo; diff --git a/src/cascadia/TerminalSettingsModel/FontConfig.h b/src/cascadia/TerminalSettingsModel/FontConfig.h index 8a789278eb7..c249f5a1b46 100644 --- a/src/cascadia/TerminalSettingsModel/FontConfig.h +++ b/src/cascadia/TerminalSettingsModel/FontConfig.h @@ -24,7 +24,7 @@ Author(s): #include using IFontAxesMap = winrt::Windows::Foundation::Collections::IMap; -using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap; +using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap; namespace winrt::Microsoft::Terminal::Settings::Model::implementation { diff --git a/src/cascadia/TerminalSettingsModel/FontConfig.idl b/src/cascadia/TerminalSettingsModel/FontConfig.idl index 9145919c438..0303b0c27e5 100644 --- a/src/cascadia/TerminalSettingsModel/FontConfig.idl +++ b/src/cascadia/TerminalSettingsModel/FontConfig.idl @@ -18,7 +18,7 @@ namespace Microsoft.Terminal.Settings.Model INHERITABLE_FONT_SETTING(String, FontFace); INHERITABLE_FONT_SETTING(Single, FontSize); INHERITABLE_FONT_SETTING(Windows.UI.Text.FontWeight, FontWeight); - INHERITABLE_FONT_SETTING(Windows.Foundation.Collections.IMap, FontFeatures); + INHERITABLE_FONT_SETTING(Windows.Foundation.Collections.IMap, FontFeatures); INHERITABLE_FONT_SETTING(Windows.Foundation.Collections.IMap, FontAxes); INHERITABLE_FONT_SETTING(Boolean, EnableBuiltinGlyphs); INHERITABLE_FONT_SETTING(Boolean, EnableColorGlyphs); diff --git a/src/cascadia/TerminalSettingsModel/JsonUtils.h b/src/cascadia/TerminalSettingsModel/JsonUtils.h index ec94d23c92e..e08047bf0e9 100644 --- a/src/cascadia/TerminalSettingsModel/JsonUtils.h +++ b/src/cascadia/TerminalSettingsModel/JsonUtils.h @@ -731,6 +731,16 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils Json::Value ToJson(const float val) { + // Convert floats that are almost integers to proper integers, because that looks way neater in JSON. + if (val >= static_cast(Json::Value::minInt) && val <= static_cast(Json::Value::maxInt)) + { + const auto i = static_cast(std::lround(val)); + const auto f = static_cast(i); + if (std::fabs(f - val) < 1e-6f) + { + return i; + } + } return val; } @@ -755,6 +765,16 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils Json::Value ToJson(const double val) { + // Convert floats that are almost integers to proper integers, because that looks way neater in JSON. + if (val >= static_cast(Json::Value::minInt) && val <= static_cast(Json::Value::maxInt)) + { + const auto i = static_cast(std::lround(val)); + const auto f = static_cast(i); + if (std::fabs(f - val) < 1e-6) + { + return i; + } + } return val; } diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index 45b4db2aa4d..33658427f94 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -21,7 +21,7 @@ Author(s): #include using IFontAxesMap = winrt::Windows::Foundation::Collections::IMap; -using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap; +using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap; using IEnvironmentVariableMap = winrt::Windows::Foundation::Collections::IMap; // fwdecl unittest classes diff --git a/src/cascadia/UnitTests_Control/MockControlSettings.h b/src/cascadia/UnitTests_Control/MockControlSettings.h index 9a9ebcda745..50b71687e8a 100644 --- a/src/cascadia/UnitTests_Control/MockControlSettings.h +++ b/src/cascadia/UnitTests_Control/MockControlSettings.h @@ -8,7 +8,7 @@ Licensed under the MIT license. #include #include "../../inc/ControlProperties.h" -using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap; +using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap; using IFontAxesMap = winrt::Windows::Foundation::Collections::IMap; namespace ControlUnitTests diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index 674dc3baf7e..2fbaf7cd52a 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -493,7 +493,7 @@ void AtlasEngine::SetWarningCallback(std::function& features, const std::unordered_map& axes) noexcept +[[nodiscard]] HRESULT AtlasEngine::UpdateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept { // We're currently faced with a font caching bug that we're unable to reproduce locally. See GH#9375. // But it occurs often enough and has no proper workarounds, so we're forced to fix it. @@ -544,7 +544,7 @@ void AtlasEngine::_resolveTransparencySettings() noexcept } } -[[nodiscard]] HRESULT AtlasEngine::_updateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept +[[nodiscard]] HRESULT AtlasEngine::_updateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept try { std::vector fontFeatures; @@ -567,19 +567,20 @@ try if (p.first.size() == 4) { const auto s = p.first.data(); + const auto v = static_cast(std::max(0l, lrintf(p.second))); switch (const auto tag = DWRITE_MAKE_FONT_FEATURE_TAG(s[0], s[1], s[2], s[3])) { case DWRITE_FONT_FEATURE_TAG_STANDARD_LIGATURES: - fontFeatures[0].parameter = p.second; + fontFeatures[0].parameter = v; break; case DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_LIGATURES: - fontFeatures[1].parameter = p.second; + fontFeatures[1].parameter = v; break; case DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_ALTERNATES: - fontFeatures[2].parameter = p.second; + fontFeatures[2].parameter = v; break; default: - fontFeatures.emplace_back(tag, p.second); + fontFeatures.emplace_back(tag, v); break; } } diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h index 9ad4425beae..1f26644ebe6 100644 --- a/src/renderer/atlas/AtlasEngine.h +++ b/src/renderer/atlas/AtlasEngine.h @@ -78,7 +78,7 @@ namespace Microsoft::Console::Render::Atlas void SetGraphicsAPI(GraphicsAPI graphicsAPI) noexcept; void SetWarningCallback(std::function pfn) noexcept; [[nodiscard]] HRESULT SetWindowSize(til::size pixels) noexcept; - [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept; + [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept; private: // AtlasEngine.cpp @@ -96,7 +96,7 @@ namespace Microsoft::Console::Render::Atlas // AtlasEngine.api.cpp void _resolveTransparencySettings() noexcept; - [[nodiscard]] HRESULT _updateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept; + [[nodiscard]] HRESULT _updateFont(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map& features, const std::unordered_map& axes) noexcept; void _resolveFontMetrics(const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontSettings* fontMetrics = nullptr); [[nodiscard]] bool _updateWithNearbyFontCollection() noexcept; From 7bb3b4dec128f4fe0e092a169787ba6b83310812 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 1 May 2024 03:17:12 +0200 Subject: [PATCH 2/5] Fix tests --- .../SerializationTests.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index 82f9f924de3..20357656638 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -151,7 +151,7 @@ namespace SettingsModelUnitTests "font": { "face": "Cascadia Mono", - "size": 12.0, + "size": 12, "weight": "normal" }, "padding": "8, 8, 8, 8", @@ -175,7 +175,7 @@ namespace SettingsModelUnitTests "backgroundImage": "made_you_look.jpeg", "backgroundImageStretchMode": "uniformToFill", "backgroundImageAlignment": "center", - "backgroundImageOpacity": 1.0, + "backgroundImageOpacity": 1, "scrollbarState": "visible", "snapOnInput": true, @@ -298,8 +298,8 @@ namespace SettingsModelUnitTests // complex command with key chords static constexpr std::string_view actionsString4A{ R"([ - { "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+c" }, - { "command": { "action": "adjustFontSize", "delta": 1.0 }, "keys": "ctrl+d" } + { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+c" }, + { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+d" } ])" }; // command with name and icon and multiple key chords @@ -323,8 +323,8 @@ namespace SettingsModelUnitTests { "name": "Change font size...", "commands": [ - { "command": { "action": "adjustFontSize", "delta": 1.0 } }, - { "command": { "action": "adjustFontSize", "delta": -1.0 } }, + { "command": { "action": "adjustFontSize", "delta": 1 } }, + { "command": { "action": "adjustFontSize", "delta": -1 } }, { "command": "resetFontSize" }, ] } @@ -523,7 +523,7 @@ namespace SettingsModelUnitTests "name": "Profile with legacy font settings", "fontFace": "Cascadia Mono", - "fontSize": 12.0, + "fontSize": 12, "fontWeight": "normal" })" }; @@ -533,7 +533,7 @@ namespace SettingsModelUnitTests "font": { "face": "Cascadia Mono", - "size": 12.0, + "size": 12, "weight": "normal" } })" }; @@ -1007,8 +1007,8 @@ namespace SettingsModelUnitTests { "name": "Change font size...", "commands": [ - { "command": { "action": "adjustFontSize", "delta": 1.0 } }, - { "command": { "action": "adjustFontSize", "delta": -1.0 } }, + { "command": { "action": "adjustFontSize", "delta": 1 } }, + { "command": { "action": "adjustFontSize", "delta": -1 } }, { "command": "resetFontSize" }, ] } From d284d057ab6a1df992c0bbe3ad7c0966cadbe2a7 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 1 May 2024 13:19:43 +0200 Subject: [PATCH 3/5] Address feedback --- doc/cascadia/profiles.schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 9a7d1d84593..d354bf0bcc0 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -349,7 +349,7 @@ "description": "Sets the DWrite font features for the given font. For example, { \"ss01\": 1, \"liga\":0 } will enable ss01 and disable ligatures.", "type": "object", "patternProperties": { - "^[\\x00-\\x7E]{4}$": { + "^[\\x20-\\x7E]{4}$": { "type": "integer" } }, @@ -359,7 +359,7 @@ "description": "Sets the DWrite font axes for the given font. For example, { \"wght\": 200 } will set the font weight to 200.", "type": "object", "patternProperties": { - "^[\\x00-\\x7E]{4}$": { + "^[\\x20-\\x7E]{4}$": { "type": "number" } }, From 3e594531fd65714fdc794e7a16657ff7271854fc Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 1 May 2024 13:21:52 +0200 Subject: [PATCH 4/5] Fix tests --- src/cascadia/ut_app/JsonUtilsTests.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cascadia/ut_app/JsonUtilsTests.cpp b/src/cascadia/ut_app/JsonUtilsTests.cpp index 475e76abb5f..38dc57959c8 100644 --- a/src/cascadia/ut_app/JsonUtilsTests.cpp +++ b/src/cascadia/ut_app/JsonUtilsTests.cpp @@ -354,16 +354,16 @@ namespace TerminalAppUnitTests TryBasicType(int{ -1024 }, -1024); TryBasicType(std::numeric_limits::max(), std::numeric_limits::max()); TryBasicType(false, false); - TryBasicType(1.0f, 1.0f); + TryBasicType(1.1f, 1.1f); // string -> wstring TryBasicType(std::wstring{ L"hello" }, "hello"); // float -> double - TryBasicType(1.0, 1.0f); + TryBasicType(1.1, 1.1f); // double -> float - TryBasicType(1.0f, 1.0); + TryBasicType(1.1f, 1.1); TryBasicType(til::color{ 0xab, 0xcd, 0xef }, "#ABCDEF"); TryBasicType(til::color{ 0xcc, 0xcc, 0xcc }, "#CCC", "#CCCCCC"); From fe463af70cc5605e9db291c0028237cde0262b73 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Wed, 1 May 2024 14:28:30 +0200 Subject: [PATCH 5/5] Gosh darn float precision --- src/cascadia/ut_app/JsonUtilsTests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cascadia/ut_app/JsonUtilsTests.cpp b/src/cascadia/ut_app/JsonUtilsTests.cpp index 38dc57959c8..29404fc1a96 100644 --- a/src/cascadia/ut_app/JsonUtilsTests.cpp +++ b/src/cascadia/ut_app/JsonUtilsTests.cpp @@ -360,10 +360,10 @@ namespace TerminalAppUnitTests TryBasicType(std::wstring{ L"hello" }, "hello"); // float -> double - TryBasicType(1.1, 1.1f); + TryBasicType(static_cast(1.1f), 1.1f); // double -> float - TryBasicType(1.1f, 1.1); + TryBasicType(1.1f, static_cast(1.1f)); TryBasicType(til::color{ 0xab, 0xcd, 0xef }, "#ABCDEF"); TryBasicType(til::color{ 0xcc, 0xcc, 0xcc }, "#CCC", "#CCCCCC");