diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 3724edd542c..88d88360fcf 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -967,7 +967,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation auto settings{ _core.Settings() }; // Apply padding as swapChainPanel's margin - const auto newMargin = ParseThicknessFromPadding(settings.Padding()); + const auto newMargin = StringToXamlThickness(settings.Padding()); SwapChainPanel().Margin(newMargin); // Apply settings for scrollbar @@ -2921,7 +2921,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation } float height = rows * static_cast(actualFontSize.height); - const auto thickness = ParseThicknessFromPadding(padding); + const auto thickness = StringToXamlThickness(padding); // GH#2061 - make sure to account for the size the padding _will be_ scaled to width += scale * static_cast(thickness.Left + thickness.Right); height += scale * static_cast(thickness.Top + thickness.Bottom); @@ -2957,7 +2957,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation width += scrollbarSize; } - const auto thickness = ParseThicknessFromPadding(padding); + const auto thickness = StringToXamlThickness(padding); // GH#2061 - make sure to account for the size the padding _will be_ scaled to width += scale * static_cast(thickness.Left + thickness.Right); height += scale * static_cast(thickness.Top + thickness.Bottom); @@ -3056,63 +3056,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation _core.WindowVisibilityChanged(showOrHide); } - // Method Description: - // - Create XAML Thickness object based on padding props provided. - // Used for controlling the TermControl XAML Grid container's Padding prop. - // Arguments: - // - padding: 2D padding values - // Single Double value provides uniform padding - // Two Double values provide isometric horizontal & vertical padding - // Four Double values provide independent padding for 4 sides of the bounding rectangle - // Return Value: - // - Windows::UI::Xaml::Thickness object - Windows::UI::Xaml::Thickness TermControl::ParseThicknessFromPadding(const hstring padding) - { - const auto singleCharDelim = L','; - std::wstringstream tokenStream(padding.c_str()); - std::wstring token; - uint8_t paddingPropIndex = 0; - std::array thicknessArr = {}; - size_t* idx = nullptr; - - // Get padding values till we run out of delimiter separated values in the stream - // or we hit max number of allowable values (= 4) for the bounding rectangle - // Non-numeral values detected will default to 0 - // std::getline will not throw exception unless flags are set on the wstringstream - // std::stod will throw invalid_argument exception if the input is an invalid double value - // std::stod will throw out_of_range exception if the input value is more than DBL_MAX - try - { - for (; std::getline(tokenStream, token, singleCharDelim) && (paddingPropIndex < thicknessArr.size()); paddingPropIndex++) - { - // std::stod internally calls wcstod which handles whitespace prefix (which is ignored) - // & stops the scan when first char outside the range of radix is encountered - // We'll be permissive till the extent that stod function allows us to be by default - // Ex. a value like 100.3#535w2 will be read as 100.3, but ;df25 will fail - thicknessArr[paddingPropIndex] = std::stod(token, idx); - } - } - catch (...) - { - // If something goes wrong, even if due to a single bad padding value, we'll reset the index & return default 0 padding - paddingPropIndex = 0; - LOG_CAUGHT_EXCEPTION(); - } - - switch (paddingPropIndex) - { - case 1: - return ThicknessHelper::FromUniformLength(thicknessArr[0]); - case 2: - return ThicknessHelper::FromLengths(thicknessArr[0], thicknessArr[1], thicknessArr[0], thicknessArr[1]); - // No case for paddingPropIndex = 3, since it's not a norm to provide just Left, Top & Right padding values leaving out Bottom - case 4: - return ThicknessHelper::FromLengths(thicknessArr[0], thicknessArr[1], thicknessArr[2], thicknessArr[3]); - default: - return Thickness(); - } - } - // Method Description: // - Get the modifier keys that are currently pressed. This can be used to // find out which modifiers (ctrl, alt, shift) are pressed in events that diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp index 1d0272a10db..3fe244d60b8 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.cpp @@ -136,6 +136,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation } else if (viewModelProperty == L"Padding") { + _parsedPadding = StringToXamlThickness(_profile.Padding()); _NotifyChanges(L"LeftPadding", L"TopPadding", L"RightPadding", L"BottomPadding"); } }); @@ -157,6 +158,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation _unfocusedAppearanceViewModel = winrt::make(profile.UnfocusedAppearance().try_as()); } + _parsedPadding = StringToXamlThickness(_profile.Padding()); _defaultAppearanceViewModel.IsDefault(true); } @@ -217,145 +219,59 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation void ProfileViewModel::LeftPadding(double value) noexcept { - const hstring& padding = _GetNewPadding(PaddingDirection::Left, value); - - Padding(padding); + if (std::abs(_parsedPadding.Left - value) >= .0001) + { + _parsedPadding.Left = value; + Padding(XamlThicknessToOptimalString(_parsedPadding)); + } } double ProfileViewModel::LeftPadding() const noexcept { - return _GetPaddingValue(PaddingDirection::Left); + return _parsedPadding.Left; } void ProfileViewModel::TopPadding(double value) noexcept { - const hstring& padding = _GetNewPadding(PaddingDirection::Top, value); - - Padding(padding); + if (std::abs(_parsedPadding.Top - value) >= .0001) + { + _parsedPadding.Top = value; + Padding(XamlThicknessToOptimalString(_parsedPadding)); + } } double ProfileViewModel::TopPadding() const noexcept { - return _GetPaddingValue(PaddingDirection::Top); + return _parsedPadding.Top; } void ProfileViewModel::RightPadding(double value) noexcept { - const hstring& padding = _GetNewPadding(PaddingDirection::Right, value); - - Padding(padding); + if (std::abs(_parsedPadding.Right - value) >= .0001) + { + _parsedPadding.Right = value; + Padding(XamlThicknessToOptimalString(_parsedPadding)); + } } double ProfileViewModel::RightPadding() const noexcept { - return _GetPaddingValue(PaddingDirection::Right); + return _parsedPadding.Right; } void ProfileViewModel::BottomPadding(double value) noexcept { - const hstring& padding = _GetNewPadding(PaddingDirection::Bottom, value); - - Padding(padding); - } - - double ProfileViewModel::BottomPadding() const noexcept - { - return _GetPaddingValue(PaddingDirection::Bottom); - } - - winrt::hstring ProfileViewModel::_GetNewPadding(PaddingDirection paddingDirection, double newPaddingValue) const - { - std::array values{}; - std::wstring_view padding{ Padding() }; - uint32_t paddingIndex = static_cast(paddingDirection); - - try - { - uint32_t index = 0; - for (const auto& token : til::split_iterator{ padding, L',' }) - { - auto curVal = std::stod(std::wstring{ token }); - - if (paddingIndex == index) - { - curVal = newPaddingValue; - } - - values[index++] = curVal; - - if (index >= values.size()) - { - break; - } - } - } - catch (...) + if (std::abs(_parsedPadding.Bottom - value) >= .0001) { - values.fill(0); - LOG_CAUGHT_EXCEPTION(); + _parsedPadding.Bottom = value; + Padding(XamlThicknessToOptimalString(_parsedPadding)); } - - const auto result = fmt::format(FMT_COMPILE(L"{:.6f}"), fmt::join(values, L",")); - - return winrt::hstring{ result }; } - double ProfileViewModel::_GetPaddingValue(PaddingDirection paddingDirection) const + double ProfileViewModel::BottomPadding() const noexcept { - std::wstring_view padding{ Padding() }; - uint32_t paddingIndex = static_cast(paddingDirection); - std::array paddingValues{}; - double paddingValue = 0.; - uint32_t index = 0; - - try - { - for (const auto& token : til::split_iterator{ padding, L',' }) - { - auto curVal = std::stod(std::wstring{ token }); - - paddingValues[index++] = curVal; - - if (index >= paddingValues.size()) - { - break; - } - } - } - catch (...) - { - paddingValue = 0.; - LOG_CAUGHT_EXCEPTION(); - } - - // Padding: 8 - if (index == 1) - { - paddingValue = paddingValues[0]; - } - // Padding: 8, 4 - else if (index == 2) - { - if (paddingDirection == PaddingDirection::Left || - paddingDirection == PaddingDirection::Right) - { - paddingValue = paddingValues[0]; - } - else if (paddingDirection == PaddingDirection::Top || - paddingDirection == PaddingDirection::Bottom) - { - paddingValue = paddingValues[1]; - } - } - // Padding: 8, 4, 8, 4 - else - { - paddingValue = paddingValues[paddingIndex]; - } - - return paddingValue; + return _parsedPadding.Bottom; } - Model::TerminalSettings ProfileViewModel::TermSettings() const { return Model::TerminalSettings::CreateForPreview(_appSettings, _profile); diff --git a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h index 4b70acaacc2..16906336f79 100644 --- a/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h +++ b/src/cascadia/TerminalSettingsEditor/ProfileViewModel.h @@ -175,6 +175,8 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Editor::AppearanceViewModel _defaultAppearanceViewModel; Windows::UI::Core::CoreDispatcher _dispatcher; + winrt::Windows::UI::Xaml::Thickness _parsedPadding; + void _InitializeCurrentBellSounds(); void _PrepareModelForBellSoundModification(); void _MarkDuplicateBellSoundDirectories(); @@ -185,17 +187,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation Model::CascadiaSettings _appSettings; Editor::AppearanceViewModel _unfocusedAppearanceViewModel; - - enum class PaddingDirection - { - Left = 0, - Top = 1, - Right = 2, - Bottom = 3 - }; - - winrt::hstring _GetNewPadding(PaddingDirection paddingDirection, double newPaddingValue) const; - double _GetPaddingValue(PaddingDirection paddingDirection) const; void _UpdateBuiltInIcons(); void _DeduceCurrentIconType(); void _DeduceCurrentBuiltInIcon(); diff --git a/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.xaml b/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.xaml index f7c9bd68797..df8c18d1913 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.xaml +++ b/src/cascadia/TerminalSettingsEditor/Profiles_Appearance.xaml @@ -124,6 +124,7 @@ diff --git a/src/cascadia/UIHelpers/Converters.cpp b/src/cascadia/UIHelpers/Converters.cpp index 3c364c0a1b6..8c0cffb8af8 100644 --- a/src/cascadia/UIHelpers/Converters.cpp +++ b/src/cascadia/UIHelpers/Converters.cpp @@ -77,36 +77,4 @@ namespace winrt::Microsoft::Terminal::UI::implementation { return fontWeight.Weight; } - - double Converters::MaxValueFromPaddingString(const winrt::hstring& paddingString) - { - std::wstring buffer; - double maxVal = 0; - - auto& errnoRef = errno; // Nonzero cost, pay it once - - // Get padding values till we run out of delimiter separated values in the stream - // Non-numeral values detected will default to 0 - // std::stod will throw invalid_argument exception if the input is an invalid double value - // std::stod will throw out_of_range exception if the input value is more than DBL_MAX - for (const auto& part : til::split_iterator{ std::wstring_view{ paddingString }, L',' }) - { - buffer.assign(part); - - // wcstod handles whitespace prefix (which is ignored) & stops the - // scan when first char outside the range of radix is encountered. - // We'll be permissive till the extent that stod function allows us to be by default - // Ex. a value like 100.3#535w2 will be read as 100.3, but ;df25 will fail - errnoRef = 0; - wchar_t* end; - const double val = wcstod(buffer.c_str(), &end); - - if (end != buffer.c_str() && errnoRef != ERANGE) - { - maxVal = std::max(maxVal, val); - } - } - - return maxVal; - } } diff --git a/src/cascadia/UIHelpers/Converters.h b/src/cascadia/UIHelpers/Converters.h index af8bcecb458..998daad41f2 100644 --- a/src/cascadia/UIHelpers/Converters.h +++ b/src/cascadia/UIHelpers/Converters.h @@ -28,7 +28,6 @@ namespace winrt::Microsoft::Terminal::UI::implementation static winrt::Windows::UI::Text::FontWeight DoubleToFontWeight(double value); static winrt::Windows::UI::Xaml::Media::SolidColorBrush ColorToBrush(winrt::Windows::UI::Color color); static double FontWeightToDouble(winrt::Windows::UI::Text::FontWeight fontWeight); - static double MaxValueFromPaddingString(const winrt::hstring& paddingString); }; } diff --git a/src/cascadia/UIHelpers/Converters.idl b/src/cascadia/UIHelpers/Converters.idl index baf8a97af65..5cd5b553257 100644 --- a/src/cascadia/UIHelpers/Converters.idl +++ b/src/cascadia/UIHelpers/Converters.idl @@ -26,6 +26,5 @@ namespace Microsoft.Terminal.UI static Windows.UI.Text.FontWeight DoubleToFontWeight(Double value); static Windows.UI.Xaml.Media.SolidColorBrush ColorToBrush(Windows.UI.Color color); static Double FontWeightToDouble(Windows.UI.Text.FontWeight fontWeight); - static Double MaxValueFromPaddingString(String paddingString); } } diff --git a/src/cascadia/inc/cppwinrt_utils.h b/src/cascadia/inc/cppwinrt_utils.h index 9840e9532ba..f54b307fdef 100644 --- a/src/cascadia/inc/cppwinrt_utils.h +++ b/src/cascadia/inc/cppwinrt_utils.h @@ -288,4 +288,79 @@ std::vector> SafeArrayToOwningVector(SAFEARRAY* safeArray) namespace nameSpace::factory_implementation \ { \ BASIC_FACTORY(className); \ - }\ + } + +#ifdef WINRT_Windows_UI_Xaml_H + +inline ::winrt::hstring XamlThicknessToOptimalString(const ::winrt::Windows::UI::Xaml::Thickness& t) +{ + if (t.Left == t.Right) + { + if (t.Top == t.Bottom) + { + if (t.Top == t.Left) + { + return ::winrt::hstring{ fmt::format(FMT_COMPILE(L"{}"), t.Left) }; + } + return ::winrt::hstring{ fmt::format(FMT_COMPILE(L"{},{}"), t.Left, t.Top) }; + } + // fall through + } + return ::winrt::hstring{ fmt::format(FMT_COMPILE(L"{},{},{},{}"), t.Left, t.Top, t.Right, t.Bottom) }; +} + +inline ::winrt::Windows::UI::Xaml::Thickness StringToXamlThickness(std::wstring_view padding) +try +{ + uintptr_t count{ 0 }; + double t[4]{ 0. }; // left, top, right, bottom + wchar_t buf[17]; + std::wstring buf; + auto& errnoRef = errno; // Nonzero cost, pay it once + for (const auto& token : til::split_iterator{ padding, L',' }) + { + buf.assign(token); + // wcstod handles whitespace prefix (which is ignored) & stops the + // scan when first char outside the range of radix is encountered. + // We'll be permissive till the extent that stod function allows us to be by default + // Ex. a value like 100.3#535w2 will be read as 100.3, but ;df25 will fail + errnoRef = 0; + wchar_t* end; + const auto val{ std::wcstod(buf.c_str(), &end) }; + if (end != buf.c_str() && errnoRef != ERANGE) + { + til::at(t, count) = val; + } + + if (++count >= 4) + { + break; + } + } + +#pragma warning(push) +#pragma warning(disable : 26446) // Prefer to use gsl::at() instead of unchecked subscript operator (bounds.4). + switch (count) + { + case 1: // one input = all 4 values are the same + t[1] = t[0]; // top = left + __fallthrough; + case 2: // two inputs = top/bottom and left/right are the same + t[2] = t[0]; // right = left + t[3] = t[1]; // bottom = top + __fallthrough; + case 4: // four inputs = fully specified + break; + default: + return {}; + } + return { t[0], t[1], t[2], t[3] }; +#pragma warning(pop) +} +catch (...) +{ + LOG_CAUGHT_EXCEPTION(); + return {}; +} + +#endif