diff --git a/.github/actions/spell-check/dictionary/apis.txt b/.github/actions/spell-check/dictionary/apis.txt index 1f6a441001b..c8248141b74 100644 --- a/.github/actions/spell-check/dictionary/apis.txt +++ b/.github/actions/spell-check/dictionary/apis.txt @@ -7,4 +7,5 @@ NCLBUTTONDBLCLK NCRBUTTONDBLCLK NOREDIRECTIONBITMAP rfind +roundf SIZENS diff --git a/.github/actions/spell-check/patterns/patterns.txt b/.github/actions/spell-check/patterns/patterns.txt index ea01453d959..0a2ee40b769 100644 --- a/.github/actions/spell-check/patterns/patterns.txt +++ b/.github/actions/spell-check/patterns/patterns.txt @@ -1,5 +1,6 @@ https://(?:(?:[-a-zA-Z0-9?&=]*\.|)microsoft\.com)/[-a-zA-Z0-9?&=_\/.]* https://aka\.ms/[-a-zA-Z0-9?&=\/_]* +http://www.w3.org/[-a-zA-Z0-9?&=\/_]* https://(?:(?:www\.|)youtube\.com|youtu.be)/[-a-zA-Z0-9?&=]* (?:0[Xx]|U\+|#)[a-f0-9A-FGgRr]{2,}[Uu]?[Ll]?\b \{[0-9A-FA-F]{8}-(?:[0-9A-FA-F]{4}-){3}[0-9A-FA-F]{12}\} diff --git a/.github/actions/spell-check/whitelist/web.txt b/.github/actions/spell-check/whitelist/web.txt index 81a62e3dcf4..ce07ecac172 100644 --- a/.github/actions/spell-check/whitelist/web.txt +++ b/.github/actions/spell-check/whitelist/web.txt @@ -2,3 +2,4 @@ http td www ecma +rapidtables diff --git a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp index 9fa20427767..b515eacd93e 100644 --- a/src/cascadia/LocalTests_TerminalApp/TabTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/TabTests.cpp @@ -17,6 +17,7 @@ using namespace winrt::TerminalApp; using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace WEX::Common; +using namespace winrt::Windows::ApplicationModel::DataTransfer; namespace TerminalAppLocalTests { diff --git a/src/cascadia/LocalTests_TerminalApp/pch.h b/src/cascadia/LocalTests_TerminalApp/pch.h index dcbe6c911b2..6f134f00e4d 100644 --- a/src/cascadia/LocalTests_TerminalApp/pch.h +++ b/src/cascadia/LocalTests_TerminalApp/pch.h @@ -39,6 +39,8 @@ Author(s): #include "../../types/inc/utils.hpp" #include "../../inc/DefaultSettings.h" +#include +#include "winrt/Windows.UI.Xaml.Markup.h" #include #include #include diff --git a/src/cascadia/TerminalApp/ColorHelper.cpp b/src/cascadia/TerminalApp/ColorHelper.cpp new file mode 100644 index 00000000000..9917b2d6767 --- /dev/null +++ b/src/cascadia/TerminalApp/ColorHelper.cpp @@ -0,0 +1,269 @@ +#include "pch.h" +#include "ColorHelper.h" +#include + +using namespace winrt::TerminalApp; + +// Method Description: +// Determines whether or not a given color is light +// Arguments: +// - color: this color is going to be examined whether it +// is light or not +// Return Value: +// - true of light, false if dark +bool ColorHelper::IsBrightColor(const winrt::Windows::UI::Color& color) +{ + // http://www.w3.org/TR/AERT#color-contrast + auto brightness = (color.R * 299 + color.G * 587 + color.B * 114) / 1000.f; + return brightness > 128.f; +} + +// Method Description: +// Converts a rgb color to an hsl one +// Arguments: +// - color: the rgb color, which is going to be converted +// Return Value: +// - a hsl color with the following ranges +// - H: [0.f -360.f] +// - L: [0.f - 1.f] (rounded to the third decimal place) +// - S: [0.f - 1.f] (rounded to the third decimal place) +HSL ColorHelper::RgbToHsl(const winrt::Windows::UI::Color& color) +{ + // https://www.rapidtables.com/convert/color/rgb-to-hsl.html + auto epsilon = std::numeric_limits::epsilon(); + auto r = color.R / 255.f; + auto g = color.G / 255.f; + auto b = color.B / 255.f; + + auto max = std::max(r, std::max(g, b)); + auto min = std::min(r, std::min(g, b)); + + auto delta = max - min; + + auto h = 0.f; + auto s = 0.f; + auto l = (max + min) / 2; + + if (delta < epsilon || max < epsilon) /* delta == 0 || max == 0*/ + { + l = std::roundf(l * 1000) / 1000; + return HSL{ h, s, l }; + } + + s = l > 0.5 ? delta / (2 - max - min) : delta / (max + min); + + if (max - r < epsilon) // max == r + { + h = (g - b) / delta + (g < b ? 6 : 0); + } + else if (max - g < epsilon) // max == g + { + h = (b - r) / delta + 2; + } + else if (max - b < epsilon) // max == b + { + h = (r - g) / delta + 4; + } + + // three decimal places after the comma ought + // to be enough for everybody - Bill Gates, 1981 + float finalH = std::roundf(h * 60); + float finalS = std::roundf(s * 1000) / 1000; + float finalL = std::roundf(l * 1000) / 1000; + + return HSL{ finalH, finalS, finalL }; +} + +// Method Description: +// Converts a hsl color to rgb one +// Arguments: +// - color: the hsl color, which is going to be converted +// Return Value: +// - the rgb color (r,g,b - [0, 255] range) +winrt::Windows::UI::Color ColorHelper::HslToRgb(const HSL& color) +{ + auto epsilon = std::numeric_limits::epsilon(); + + auto h = (color.H - 1.f > epsilon) ? color.H / 360.f : color.H; + auto s = (color.S - 1.f > epsilon) ? color.S / 100.f : color.S; + auto l = (color.L - 1.f > epsilon) ? color.L / 100.f : color.L; + + auto r = l; + auto g = l; + auto b = l; + + if (s > epsilon) + { + auto q = l < 0.5 ? l * (1 + s) : l + s - l * s; + auto p = 2 * l - q; + r = HueToRgb(p, q, h + 1.f / 3.f); + g = HueToRgb(p, q, h); + b = HueToRgb(p, q, h - 1.f / 3.f); + } + + auto finalR = static_cast(std::roundf(r * 255)); + auto finalG = static_cast(std::roundf(g * 255)); + auto finalB = static_cast(std::roundf(b * 255)); + uint8_t finalA = 255; //opaque + + return winrt::Windows::UI::ColorHelper::FromArgb(finalA, finalR, finalG, finalB); +} + +float ColorHelper::HueToRgb(float p, float q, float t) +{ + auto epsilon = std::numeric_limits::epsilon(); + + if (t < 0) + t += 1; + if (t > 1) + t -= 1; + if (t - (1.f / 6.f) < epsilon) + return p + (q - p) * 6 * t; + if (t - .5f < epsilon) + return q; + if (t - 2.f / 3.f < epsilon) + return p + (q - p) * (2.f / 3.f - t) * 6; + return p; +} + +// Method Description: +// Lightens a color by a given amount +// Arguments: +// - color: the color which is going to be lightened +// - amount: the lighten amount (0-100) +// Return Value: +// - the lightened color in RGB format +winrt::Windows::UI::Color ColorHelper::Lighten(const winrt::Windows::UI::Color& color, float amount /* = 10.f*/) +{ + auto hsl = RgbToHsl(color); + hsl.L += amount / 100; + hsl.L = std::clamp(hsl.L, 0.f, 1.f); + return HslToRgb(hsl); +} + +// Method Description: +// Darkens a color by a given amount +// Arguments: +// - color: the color which is going to be darkened +// - amount: the darken amount (0-100) +// Return Value: +// - the darkened color in RGB format +winrt::Windows::UI::Color ColorHelper::Darken(const winrt::Windows::UI::Color& color, float amount /* = 10.f*/) +{ + auto hsl = RgbToHsl(color); + hsl.L -= amount / 100; + hsl.L = std::clamp(hsl.L, 0.f, 1.f); + return HslToRgb(hsl); +} + +// Method Description: +// Gets an accent color to a given color. Basically, generates +// 16 shades of the color and finds the first which has a good +// contrast according to http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef (WCAG Version 2) +// Readability ratio of 3.5 seems to look quite nicely +// Arguments: +// - color: the color for which we need an accent +// Return Value: +// - the accent color in RGB format +winrt::Windows::UI::Color ColorHelper::GetAccentColor(const winrt::Windows::UI::Color& color) +{ + auto accentColor = RgbToHsl(color); + + if (accentColor.S < 0.15) + { + accentColor.S = 0.15f; + } + + constexpr auto shadeCount = 16; + constexpr auto shadeStep = 1.f / shadeCount; + auto shades = std::map(); + for (auto i = 0; i < 15; i++) + { + auto shade = HSL{ accentColor.H, accentColor.S, i * shadeStep }; + auto contrast = GetReadability(shade, accentColor); + shades.insert(std::make_pair(contrast, shade)); + } + + // 3f is quite nice if the whole non-client area is painted + constexpr auto readability = 1.75f; + for (auto shade : shades) + { + if (shade.first >= readability) + { + return HslToRgb(shade.second); + } + } + return HslToRgb(shades.end()->second); +} + +// Method Description: +// Gets the readability of two colors according to +// http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef (WCAG Version 2) +// Arguments: +// - firstColor: the first color for the readability check (hsl) +// - secondColor: the second color for the readability check (hsl) +// Return Value: +// - the readability of the colors according to (WCAG Version 2) +float ColorHelper::GetReadability(const HSL& first, const HSL& second) +{ + return GetReadability(HslToRgb(first), HslToRgb(second)); +} + +// Method Description: +// Gets the readability of two colors according to +// http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef (WCAG Version 2) +// Arguments: +// - firstColor: the first color for the readability check (rgb) +// - secondColor: the second color for the readability check (rgb) +// Return Value: +// - the readability of the colors according to (WCAG Version 2) +float ColorHelper::GetReadability(const winrt::Windows::UI::Color& first, const winrt::Windows::UI::Color& second) +{ + auto l1 = GetLuminance(first); + auto l2 = GetLuminance(second); + + return (std::max(l1, l2) + 0.05f) / std::min(l1, l2) + 0.05f; +} + +// Method Description: +// Calculates the luminance of a given color according to +// http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef +// Arguments: +// - color: its luminance is going to be calculated +// Return Value: +// - the luminance of the color +float ColorHelper::GetLuminance(const winrt::Windows::UI::Color& color) +{ + auto epsilon = std::numeric_limits::epsilon(); + float R, G, B; + auto RsRGB = color.R / 255.f; + auto GsRGB = color.G / 255.f; + auto BsRGB = color.B / 255.f; + + if (RsRGB - 0.03928f <= epsilon) + { + R = RsRGB / 12.92f; + } + else + { + R = std::pow(((RsRGB + 0.055f) / 1.055f), 2.4f); + } + if (GsRGB - 0.03928f <= epsilon) + { + G = GsRGB / 12.92f; + } + else + { + G = std::pow(((GsRGB + 0.055f) / 1.055f), 2.4f); + } + if (BsRGB - 0.03928f <= epsilon) + { + B = BsRGB / 12.92f; + } + else + { + B = std::pow(((BsRGB + 0.055f) / 1.055f), 2.4f); + } + float luminance = (0.2126f * R) + (0.7152f * G) + (0.0722f * B); + return std::roundf(luminance * 10000) / 10000.f; +} diff --git a/src/cascadia/TerminalApp/ColorHelper.h b/src/cascadia/TerminalApp/ColorHelper.h new file mode 100644 index 00000000000..cbe6582c8d1 --- /dev/null +++ b/src/cascadia/TerminalApp/ColorHelper.h @@ -0,0 +1,32 @@ +#pragma once +#include "pch.h" + +#include + +namespace winrt::TerminalApp +{ + class HSL + { + public: + float H; + float S; + float L; + }; + + class ColorHelper + { + public: + static bool IsBrightColor(const Windows::UI::Color& color); + static HSL RgbToHsl(const Windows::UI::Color& color); + static Windows::UI::Color HslToRgb(const HSL& color); + static Windows::UI::Color Lighten(const Windows::UI::Color& color, float amount = 10.f); + static Windows::UI::Color Darken(const Windows::UI::Color& color, float amount = 10.f); + static Windows::UI::Color GetAccentColor(const Windows::UI::Color& color); + static float GetLuminance(const Windows::UI::Color& color); + static float GetReadability(const Windows::UI::Color& first, const Windows::UI::Color& second); + static float GetReadability(const HSL& first, const HSL& second); + + private: + static float HueToRgb(float p, float q, float t); + }; +} diff --git a/src/cascadia/TerminalApp/ColorPickupFlyout.cpp b/src/cascadia/TerminalApp/ColorPickupFlyout.cpp new file mode 100644 index 00000000000..b1c15be4818 --- /dev/null +++ b/src/cascadia/TerminalApp/ColorPickupFlyout.cpp @@ -0,0 +1,103 @@ +#include "pch.h" +#include "ColorPickupFlyout.h" +#include "ColorPickupFlyout.g.cpp" +#include "winrt/Windows.UI.Xaml.Media.h" +#include "winrt/Windows.UI.Xaml.Shapes.h" +#include "winrt/Windows.UI.Xaml.Interop.h" +#include + +namespace winrt::TerminalApp::implementation +{ + // Method Description: + // - Default constructor, localizes the buttons and hooks + // up the event fired by the custom color picker, so that + // the tab color is set on the fly when selecting a non-preset color + // Arguments: + // - + ColorPickupFlyout::ColorPickupFlyout() + { + InitializeComponent(); + + OkButton().Content(winrt::box_value(RS_(L"Ok"))); + CustomColorButton().Content(winrt::box_value(RS_(L"TabColorCustomButton/Content"))); + ClearColorButton().Content(winrt::box_value(RS_(L"TabColorClearButton/Content"))); + } + + // Method Description: + // - Handler of the click event for the preset color swatches. + // Reads the color from the clicked rectangle and fires an event + // with the selected color. After that hides the flyout + // Arguments: + // - sender: the rectangle that got clicked + // Return Value: + // - + void ColorPickupFlyout::ColorButton_Click(IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const&) + { + auto button{ sender.as() }; + auto rectangle{ button.Content().as() }; + auto rectClr{ rectangle.Fill().as() }; + _ColorSelectedHandlers(rectClr.Color()); + Hide(); + } + + // Method Description: + // - Handler of the clear color button. Clears the current + // color of the tab, if any. Hides the flyout after that + // Arguments: + // - + // Return Value: + // - + void ColorPickupFlyout::ClearColorButton_Click(IInspectable const&, Windows::UI::Xaml::RoutedEventArgs const&) + { + _ColorClearedHandlers(); + Hide(); + } + + // Method Description: + // - Handler of the select custom color button. Expands or collapses the flyout + // to show the color picker. In order to accomplish this a FlyoutPresenterStyle is used, + // in which a Style is embedded, containing the desired width + // Arguments: + // - + // Return Value: + // - + void ColorPickupFlyout::ShowColorPickerButton_Click(Windows::Foundation::IInspectable const&, Windows::UI::Xaml::RoutedEventArgs const&) + { + auto targetType = this->FlyoutPresenterStyle().TargetType(); + auto s = Windows::UI::Xaml::Style{}; + s.TargetType(targetType); + auto visibility = customColorPanel().Visibility(); + if (visibility == winrt::Windows::UI::Xaml::Visibility::Collapsed) + { + customColorPanel().Visibility(winrt::Windows::UI::Xaml::Visibility::Visible); + auto setter = Windows::UI::Xaml::Setter(Windows::UI::Xaml::FrameworkElement::MinWidthProperty(), winrt::box_value(540)); + s.Setters().Append(setter); + } + else + { + customColorPanel().Visibility(winrt::Windows::UI::Xaml::Visibility::Collapsed); + auto setter = Windows::UI::Xaml::Setter(Windows::UI::Xaml::FrameworkElement::MinWidthProperty(), winrt::box_value(0)); + s.Setters().Append(setter); + } + this->FlyoutPresenterStyle(s); + } + + // Method Description: + // - Handles the color selection of the color pickup. Gets + // the currently selected color and fires an event with it + // Arguments: + // - + // Return Value: + // - + void ColorPickupFlyout::CustomColorButton_Click(Windows::Foundation::IInspectable const&, Windows::UI::Xaml::RoutedEventArgs const&) + { + auto color = customColorPicker().Color(); + _ColorSelectedHandlers(color); + Hide(); + } + + void ColorPickupFlyout::ColorPicker_ColorChanged(const Windows::UI::Xaml::Controls::ColorPicker&, const Windows::UI::Xaml::Controls::ColorChangedEventArgs& args) + { + _ColorSelectedHandlers(args.NewColor()); + } +} diff --git a/src/cascadia/TerminalApp/ColorPickupFlyout.h b/src/cascadia/TerminalApp/ColorPickupFlyout.h new file mode 100644 index 00000000000..c73f688813a --- /dev/null +++ b/src/cascadia/TerminalApp/ColorPickupFlyout.h @@ -0,0 +1,27 @@ +#pragma once +#include "ColorPickupFlyout.g.h" +#include "../cascadia/inc/cppwinrt_utils.h" + +namespace winrt::TerminalApp::implementation +{ + struct ColorPickupFlyout : ColorPickupFlyoutT + { + ColorPickupFlyout(); + + void ColorButton_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + void ShowColorPickerButton_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + void CustomColorButton_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + void ClearColorButton_Click(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + void ColorPicker_ColorChanged(const Windows::UI::Xaml::Controls::ColorPicker&, const Windows::UI::Xaml::Controls::ColorChangedEventArgs& args); + + WINRT_CALLBACK(ColorCleared, TerminalApp::ColorClearedArgs); + WINRT_CALLBACK(ColorSelected, TerminalApp::ColorSelectedArgs); + }; +} + +namespace winrt::TerminalApp::factory_implementation +{ + struct ColorPickupFlyout : ColorPickupFlyoutT + { + }; +} diff --git a/src/cascadia/TerminalApp/ColorPickupFlyout.idl b/src/cascadia/TerminalApp/ColorPickupFlyout.idl new file mode 100644 index 00000000000..e0217361033 --- /dev/null +++ b/src/cascadia/TerminalApp/ColorPickupFlyout.idl @@ -0,0 +1,12 @@ +namespace TerminalApp +{ + delegate void ColorSelectedArgs(Windows.UI.Color color); + delegate void ColorClearedArgs(); + + [default_interface] runtimeclass ColorPickupFlyout : Windows.UI.Xaml.Controls.Flyout + { + ColorPickupFlyout(); + event ColorSelectedArgs ColorSelected; + event ColorClearedArgs ColorCleared; + } +} diff --git a/src/cascadia/TerminalApp/ColorPickupFlyout.xaml b/src/cascadia/TerminalApp/ColorPickupFlyout.xaml new file mode 100644 index 00000000000..7045de9d5b2 --- /dev/null +++ b/src/cascadia/TerminalApp/ColorPickupFlyout.xaml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +