diff --git a/src/cascadia/LocalTests_TerminalApp/pch.h.orig b/src/cascadia/LocalTests_TerminalApp/pch.h.orig deleted file mode 100644 index 067f82edad9..00000000000 --- a/src/cascadia/LocalTests_TerminalApp/pch.h.orig +++ /dev/null @@ -1,63 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- precomp.h - -Abstract: -- Contains external headers to include in the precompile phase of console build process. -- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building). - -Author(s): -- Carlos Zamora (cazamor) April 2019 ---*/ - -#pragma once - -// This includes support libraries from the CRT, STL, WIL, and GSL -#include "LibraryIncludes.h" -// This is inexplicable, but for whatever reason, cppwinrt conflicts with the -// SDK definition of this function, so the only fix is to undef it. -// from WinBase.h -// Windows::UI::Xaml::Media::Animation::IStoryboard::GetCurrentTime -#ifdef GetCurrentTime -#undef GetCurrentTime -#endif - -#include -#include -#include - -#include -#include -#include "consoletaeftemplates.hpp" - -// Common includes for most tests: -#include "../../inc/argb.h" -#include "../../inc/conattrs.hpp" -#include "../../types/inc/utils.hpp" -#include "../../inc/DefaultSettings.h" - -<<<<<<< HEAD:src/cascadia/LocalTests_TerminalApp/precomp.h -#include -#include "winrt/Windows.UI.Xaml.Markup.h" -======= -#include ->>>>>>> master:src/cascadia/LocalTests_TerminalApp/pch.h -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include - -#include diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index 48bd799db4e..649b1cfda08 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -91,6 +91,8 @@ namespace winrt::TerminalApp::implementation FORWARDED_TYPED_EVENT(TitleChanged, winrt::Windows::Foundation::IInspectable, winrt::hstring, _root, TitleChanged); FORWARDED_TYPED_EVENT(LastTabClosed, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::LastTabClosedEventArgs, _root, LastTabClosed); FORWARDED_TYPED_EVENT(ToggleFullscreen, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::ToggleFullscreenEventArgs, _root, ToggleFullscreen); + FORWARDED_TYPED_EVENT(SetTitleBarColor, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::TabColorChangedEventArgs, _root, SetTitleBarColor); + FORWARDED_TYPED_EVENT(ClearTitleBarColor, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Color, _root, ClearTitleBarColor); }; } diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index 39bfb38e5a2..de2e31ddaca 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -47,5 +47,7 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler LastTabClosed; event Windows.Foundation.TypedEventHandler RequestedThemeChanged; event Windows.Foundation.TypedEventHandler ToggleFullscreen; + event Windows.Foundation.TypedEventHandler SetTitleBarColor; + event Windows.Foundation.TypedEventHandler ClearTitleBarColor; } } diff --git a/src/cascadia/TerminalApp/ColorHelper.cpp b/src/cascadia/TerminalApp/ColorHelper.cpp new file mode 100644 index 00000000000..968235c7f79 --- /dev/null +++ b/src/cascadia/TerminalApp/ColorHelper.cpp @@ -0,0 +1,273 @@ +#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 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 +WUI::Color ColorHelper::Lighten(const WUI::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 +WUI::Color ColorHelper::Darken(const WUI::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 accemt color in RGB format +WUI::Color ColorHelper::GetAccentColor(const WUI::Color& color) +{ + auto accentColor = RgbToHsl(color); + + if (accentColor.S == 0.f) + { + accentColor.H = 60 * std::roundf(accentColor.L * 6); + } + + if (accentColor.S < 0.15) + { + accentColor.S = 0.5f; + } + + 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)); + } + + constexpr auto readability = 3.f; + 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 WUI::Color& first, const WUI::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 WUI::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..a088a46d3ad --- /dev/null +++ b/src/cascadia/TerminalApp/ColorHelper.h @@ -0,0 +1,34 @@ +#pragma once +#include "pch.h" + +#include + +namespace WUI = winrt::Windows::UI; + +namespace winrt::TerminalApp +{ + class HSL + { + public: + float H; + float S; + float L; + }; + + class ColorHelper + { + public: + static bool IsBrightColor(const WUI::Color& color); + static HSL RgbToHsl(const WUI::Color& color); + static WUI::Color HslToRgb(const HSL& color); + static WUI::Color Lighten(const WUI::Color& color, float amount = 10.f); + static WUI::Color Darken(const WUI::Color& color, float amount = 10.f); + static WUI::Color GetAccentColor(const WUI::Color& color); + static float GetLuminance(const WUI::Color& color); + static float GetReadability(const WUI::Color& first, const WUI::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/MinMaxCloseControl.cpp b/src/cascadia/TerminalApp/MinMaxCloseControl.cpp index 0be83621099..5af02bd3e64 100644 --- a/src/cascadia/TerminalApp/MinMaxCloseControl.cpp +++ b/src/cascadia/TerminalApp/MinMaxCloseControl.cpp @@ -53,6 +53,33 @@ namespace winrt::TerminalApp::implementation } } + void MinMaxCloseControl::RefreshButtons() + { + _RefreshButton(MinimizeButton()); + _RefreshButton(MaximizeButton()); + _RefreshButton(CloseButton()); + } + + void MinMaxCloseControl::_RefreshButton(winrt::Windows::UI::Xaml::Controls::Button const& button) + { + const winrt::hstring commonStates = L"CommonStates"; + const winrt::hstring normalState = L"Normal"; + VisualState currentState; + const auto states = winrt::Windows::UI::Xaml::VisualStateManager::GetVisualStateGroups(button); + for (unsigned int i = 0; i < states.Size(); i++) + { + auto stateGroup = states.GetAt(i); + if (stateGroup.Name() == commonStates) + currentState = stateGroup.CurrentState(); + } + // default to 'Normal' + winrt::hstring stateName = currentState.Name() == L"" ? normalState : currentState.Name(); + // since the button state is refreshed only when picking tab color, we + // can safely pick pointerover as the start state for the toggle + winrt::Windows::UI::Xaml::VisualStateManager::GoToState(button, L"PointerOver", false); + winrt::Windows::UI::Xaml::VisualStateManager::GoToState(button, stateName, false); + } + DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(MinMaxCloseControl, MinimizeClick, _minimizeClickHandlers, TerminalApp::MinMaxCloseControl, RoutedEventArgs); DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(MinMaxCloseControl, MaximizeClick, _maximizeClickHandlers, TerminalApp::MinMaxCloseControl, RoutedEventArgs); DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(MinMaxCloseControl, CloseClick, _closeClickHandlers, TerminalApp::MinMaxCloseControl, RoutedEventArgs); diff --git a/src/cascadia/TerminalApp/MinMaxCloseControl.h b/src/cascadia/TerminalApp/MinMaxCloseControl.h index 4ee7ced322c..7494baa581a 100644 --- a/src/cascadia/TerminalApp/MinMaxCloseControl.h +++ b/src/cascadia/TerminalApp/MinMaxCloseControl.h @@ -19,6 +19,7 @@ namespace winrt::TerminalApp::implementation MinMaxCloseControl(); void SetWindowVisualState(WindowVisualState visualState); + void RefreshButtons(); void _MinimizeClick(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e); @@ -26,6 +27,7 @@ namespace winrt::TerminalApp::implementation winrt::Windows::UI::Xaml::RoutedEventArgs const& e); void _CloseClick(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e); + void _RefreshButton(winrt::Windows::UI::Xaml::Controls::Button const& button); DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(MinimizeClick, _minimizeClickHandlers, TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs); DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(MaximizeClick, _maximizeClickHandlers, TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs); diff --git a/src/cascadia/TerminalApp/MinMaxCloseControl.idl b/src/cascadia/TerminalApp/MinMaxCloseControl.idl index c0ccb370f21..2f691917b17 100644 --- a/src/cascadia/TerminalApp/MinMaxCloseControl.idl +++ b/src/cascadia/TerminalApp/MinMaxCloseControl.idl @@ -10,6 +10,7 @@ namespace TerminalApp MinMaxCloseControl(); void SetWindowVisualState(WindowVisualState visualState); + void RefreshButtons(); event Windows.Foundation.TypedEventHandler MinimizeClick; event Windows.Foundation.TypedEventHandler MaximizeClick; diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index cb518420b0f..c22e58aad94 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -7,6 +7,7 @@ #include "Tab.h" #include "Tab.g.cpp" #include "Utils.h" +#include "ColorHelper.h" using namespace winrt; using namespace winrt::Windows::UI::Xaml; @@ -473,7 +474,7 @@ namespace winrt::TerminalApp::implementation closeTabMenuItem.Click([weakThis](auto&&, auto&&) { if (auto tab{ weakThis.get() }) { - tab->ClosePane(); + tab->_rootPane->Close(); } }); @@ -508,6 +509,17 @@ namespace winrt::TerminalApp::implementation _tabViewItem.ContextFlyout(newTabFlyout); } + // Method Description: + // Returns the tab color, if any + // Arguments: + // - + // Return Value: + // - The tab's color, if any + std::optional Tab::GetTabColor() + { + return _tabColor; + } + // Method Description: // - Sets the tab background color to the color chosen by the user // - Sets the tab foreground color depending on the luminance of @@ -529,25 +541,11 @@ namespace winrt::TerminalApp::implementation Media::SolidColorBrush selectedTabBrush{}; Media::SolidColorBrush deselectedTabBrush{}; Media::SolidColorBrush fontBrush{}; - - // see https://www.w3.org/TR/WCAG20/#relativeluminancedef - float c[] = { color.R / 255.f, color.G / 255.f, color.B / 255.f }; - for (int i = 0; i < 3; i++) - { - if (c[i] <= 0.03928) - { - c[i] = c[i] / 12.92f; - } - else - { - c[i] = std::pow((c[i] + 0.055f) / 1.055f, 2.4f); - } - } - + Media::SolidColorBrush hoverTabBrush{}; // calculate the luminance of the current color and select a font // color based on that - auto luminance = 0.2126f * c[0] + 0.7152f * c[1] + 0.0722f * c[2]; - if (luminance > 0.179) + // see https://www.w3.org/TR/WCAG20/#relativeluminancedef + if (TerminalApp::ColorHelper::IsBrightColor(color)) { fontBrush.Color(winrt::Windows::UI::Colors::Black()); } @@ -556,21 +554,24 @@ namespace winrt::TerminalApp::implementation fontBrush.Color(winrt::Windows::UI::Colors::White()); } + hoverTabBrush.Color(TerminalApp::ColorHelper::GetAccentColor(color)); selectedTabBrush.Color(color); // currently if a tab has a custom color, a deselected state is // signified by using the same color with a bit ot transparency - auto deselectedTabColor = color; - deselectedTabColor.A = 64; - deselectedTabBrush.Color(deselectedTabColor); tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush); - tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush); - tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), selectedTabBrush); + tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackground"), selectedTabBrush); + tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), hoverTabBrush); + tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush); tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForeground"), fontBrush); tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush); tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), fontBrush); + tab->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPressed"), fontBrush); tab->_RefreshVisualState(); + + tab->_tabColor.emplace(color); + tab->_colorSelected(color); }); } @@ -598,6 +599,8 @@ namespace winrt::TerminalApp::implementation L"TabViewItemHeaderForeground", L"TabViewItemHeaderForegroundSelected", L"TabViewItemHeaderForegroundPointerOver", + L"TabViewItemHeaderBackgroundPressed", + L"TabViewItemHeaderForegroundPressed" }; // simply clear any of the colors in the tab's dict @@ -611,6 +614,8 @@ namespace winrt::TerminalApp::implementation } tab->_RefreshVisualState(); + tab->_tabColor.reset(); + tab->_colorCleared(); }); } @@ -636,4 +641,6 @@ namespace winrt::TerminalApp::implementation } DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>); + DEFINE_EVENT(Tab, ColorSelected, _colorSelected, winrt::delegate); + DEFINE_EVENT(Tab, ColorCleared, _colorCleared, winrt::delegate<>); } diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index 9099d7befa5..e330491b512 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -45,9 +45,13 @@ namespace winrt::TerminalApp::implementation void Shutdown(); void ClosePane(); + std::optional GetTabColor(); + WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>); + DECLARE_EVENT(ColorSelected, _colorSelected, winrt::delegate); + DECLARE_EVENT(ColorCleared, _colorCleared, winrt::delegate<>); OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Title, _PropertyChangedHandlers); OBSERVABLE_GETSET_PROPERTY(winrt::hstring, IconPath, _PropertyChangedHandlers); @@ -57,6 +61,7 @@ namespace winrt::TerminalApp::implementation std::shared_ptr _activePane{ nullptr }; winrt::hstring _lastIconPath{}; winrt::TerminalApp::ColorPickupFlyout _tabColorPickup{}; + std::optional _tabColor{}; bool _focused{ false }; winrt::Microsoft::UI::Xaml::Controls::TabViewItem _tabViewItem{ nullptr }; diff --git a/src/cascadia/TerminalApp/TabRowControl.xaml.orig b/src/cascadia/TerminalApp/TabRowControl.xaml.orig deleted file mode 100644 index f6cf2d30728..00000000000 --- a/src/cascadia/TerminalApp/TabRowControl.xaml.orig +++ /dev/null @@ -1,90 +0,0 @@ -<<<<<<< HEAD - - - - - - - >>>>>> master - CanReorderTabs="True" - CanDragTabs="True" - AllowDropTabs="True"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 553ad0ad92e..229c9b91287 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -15,6 +15,7 @@ #include "AzureCloudShellGenerator.h" // For AzureConnectionType #include "TelnetGenerator.h" // For TelnetConnectionType #include "TabRowControl.h" +#include "ColorHelper.h" using namespace winrt; using namespace winrt::Windows::UI::Xaml; @@ -893,6 +894,30 @@ namespace winrt::TerminalApp::implementation page->_UpdateTitle(*tab); } }); + + // react on color changed events + hostingTab.ColorSelected([weakTab{ hostingTab.get_weak() }, weakThis{ get_weak() }](auto&& color) { + auto page{ weakThis.get() }; + auto tab{ weakTab.get() }; + + if (page && tab && tab->IsFocused()) + { + page->_SetNonClientAreaColors(color); + } + }); + + hostingTab.ColorCleared([weakTab{ hostingTab.get_weak() }, weakThis{ get_weak() }]() { + auto page{ weakThis.get() }; + auto tab{ weakTab.get() }; + + if (page && tab && tab->IsFocused()) + { + page->_ClearNonClientAreaColors(); + } + }); + + // remove any colors left by other colored tabs + _ClearTabRowColor(); } // Method Description: @@ -1445,6 +1470,15 @@ namespace winrt::TerminalApp::implementation _tabContent.Children().Append(tab->GetRootElement()); tab->SetFocused(true); + std::optional color = tab->GetTabColor(); + if (color.has_value()) + { + _SetNonClientAreaColors(color.value()); + } + else + { + _ClearNonClientAreaColors(); + } _titleChangeHandlers(*this, Title()); } CATCH_LOG(); @@ -1690,7 +1724,7 @@ namespace winrt::TerminalApp::implementation // Arguments: // - tab: the projected type of a Tab // Return Value: - // - a com_ptr to the implementation type of the Tab + // - a com_ptr to the implementation type of the Tab winrt::com_ptr TerminalPage::_GetStrongTabImpl(const ::winrt::TerminalApp::Tab& tab) const winrt::com_ptr TerminalPage::_GetStrongTabImpl(const ::winrt::TerminalApp::Tab& tab) const { winrt::com_ptr tabImpl; @@ -1698,6 +1732,173 @@ namespace winrt::TerminalApp::implementation return tabImpl; } + // Method Description: + // - Sets the tab split button color when a new tab color is selected + // - Sets the tab row color when a new tab color is selected + // Arguments: + // - color: The color of the newly selected tab, used to properly calculate + // the foreground color of the split button (to match the font + // color of the tab) + // - accentColor: the actual color we are going to use to paint the tab row and + // split button, so that there is some contrast between the tab + // and the non-client are behind it + // Return Value: + // - + void TerminalPage::_SetTabRowColor(const Windows::UI::Color& color, const Windows::UI::Color& accentColor) + { + bool IsBrightColor = ColorHelper::IsBrightColor(color); + bool isLightAccentColor = ColorHelper::IsBrightColor(accentColor); + winrt::Windows::UI::Color pressedColor{}; + winrt::Windows::UI::Color hoverColor{}; + winrt::Windows::UI::Color foregroundColor{}; + const float hoverColorAdjustment = 5.f; + const float pressedColorAdjustment = 7.f; + + if (IsBrightColor) + { + foregroundColor = winrt::Windows::UI::Colors::Black(); + } + else + { + foregroundColor = winrt::Windows::UI::Colors::White(); + } + + if (isLightAccentColor) + { + hoverColor = ColorHelper::Darken(accentColor, hoverColorAdjustment); + pressedColor = ColorHelper::Darken(accentColor, pressedColorAdjustment); + } + else + { + hoverColor = ColorHelper::Lighten(accentColor, hoverColorAdjustment); + pressedColor = ColorHelper::Lighten(accentColor, pressedColorAdjustment); + } + + Media::SolidColorBrush backgroundBrush{ accentColor }; + Media::SolidColorBrush backgroundHoverBrush{ hoverColor }; + Media::SolidColorBrush backgroundPressedBrush{ pressedColor }; + Media::SolidColorBrush foregroundBrush{ foregroundColor }; + + _newTabButton.Resources().Insert(winrt::box_value(L"SplitButtonBackground"), backgroundBrush); + _newTabButton.Resources().Insert(winrt::box_value(L"SplitButtonBackgroundPointerOver"), backgroundHoverBrush); + _newTabButton.Resources().Insert(winrt::box_value(L"SplitButtonBackgroundPressed"), backgroundPressedBrush); + + _newTabButton.Resources().Insert(winrt::box_value(L"SplitButtonForeground"), foregroundBrush); + _newTabButton.Resources().Insert(winrt::box_value(L"SplitButtonForegroundPointerOver"), foregroundBrush); + _newTabButton.Resources().Insert(winrt::box_value(L"SplitButtonForegroundPressed"), foregroundBrush); + + _newTabButton.Background(backgroundBrush); + _newTabButton.Foreground(foregroundBrush); + + TabRow().TabView().Background(backgroundBrush); + } + + // Method Description: + // - Clears the tab split button color to a system color + // (or white if none is found) when the tab's color is cleared + // - Clears the tab row color to a system color + // (or white if none is found) when the tab's color is cleared + // Arguments: + // - + // Return Value: + // - + void TerminalPage::_ClearTabRowColor() + { + winrt::hstring keys[] = { + L"SplitButtonBackground", + L"SplitButtonBackgroundPointerOver", + L"SplitButtonBackgroundPressed", + L"SplitButtonForeground", + L"SplitButtonForegroundPointerOver", + L"SplitButtonForegroundPressed" + }; + + // simply clear any of the colors in the split button's dict + for (auto keyString : keys) + { + auto key = winrt::box_value(keyString); + if (_newTabButton.Resources().HasKey(key)) + { + _newTabButton.Resources().Remove(key); + } + } + + const auto res = Application::Current().Resources(); + + const auto defaultBackgroundKey = winrt::box_value(L"TabViewItemHeaderBackground"); + const auto defaultForegroundKey = winrt::box_value(L"SystemControlForegroundBaseHighBrush"); + winrt::Windows::UI::Xaml::Media::SolidColorBrush backgroundBrush; + winrt::Windows::UI::Xaml::Media::SolidColorBrush foregroundBrush; + + if (res.HasKey(defaultBackgroundKey)) + { + winrt::Windows::Foundation::IInspectable obj = res.Lookup(defaultBackgroundKey); + backgroundBrush = obj.try_as(); + } + else + { + backgroundBrush = winrt::Windows::UI::Xaml::Media::SolidColorBrush{ winrt::Windows::UI::Colors::Black() }; + } + + if (res.HasKey(defaultForegroundKey)) + { + winrt::Windows::Foundation::IInspectable obj = res.Lookup(defaultForegroundKey); + foregroundBrush = obj.try_as(); + } + else + { + foregroundBrush = winrt::Windows::UI::Xaml::Media::SolidColorBrush{ winrt::Windows::UI::Colors::White() }; + } + + _newTabButton.Background(backgroundBrush); + _newTabButton.Foreground(foregroundBrush); + + TabRow().TabView().Background(backgroundBrush); + } + + // Method Description: + // - Sets the tab split button color when a new tab color is selected + // - Sets the tab row color when a new tab color is selected + // - Sets the title bar color when a new tab color is selected + // Arguments: + // - selectedTabColor: The color of the newly selected tab + // Return Value: + // - + void TerminalPage::_SetNonClientAreaColors(const Windows::UI::Color& selectedTabColor) + { + Windows::UI::Color accentColor{}, minMaxForegroundColor{}, minMaxHoverColor{}; + + if (ColorHelper::IsBrightColor(selectedTabColor)) + { + minMaxForegroundColor = winrt::Windows::UI::Colors::Black(); + minMaxHoverColor = winrt::Windows::UI::Colors::DarkGray(); + } + else + { + minMaxForegroundColor = winrt::Windows::UI::Colors::White(); + minMaxHoverColor = winrt::Windows::UI::Colors::LightGray(); + } + minMaxHoverColor.A = 128; + accentColor = ColorHelper::GetAccentColor(selectedTabColor); + auto colorArgs = winrt::make_self(accentColor, minMaxForegroundColor, minMaxHoverColor); + _SetTabRowColor(selectedTabColor, accentColor); + _setTitleBarColorHandlers(*this, *colorArgs); + } + + // Method Description: + // - Clears the tab split button color when the tab's color is cleared + // - Clears the tab row color when the tab's color is cleared + // - Clears the title bar color when when the tab's color is cleared + // Arguments: + // - + // Return Value: + // - + void TerminalPage::_ClearNonClientAreaColors() + { + _ClearTabRowColor(); + _clearTitleBarColorHandlers(*this, Windows::UI::Colors::White()); + } + // -------------------------------- WinRT Events --------------------------------- // Winrt events need a method for adding a callback to the event and removing the callback. // These macros will define them both for you. @@ -1706,4 +1907,6 @@ namespace winrt::TerminalApp::implementation DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TerminalPage, SetTitleBarContent, _setTitleBarContentHandlers, winrt::Windows::Foundation::IInspectable, UIElement); DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TerminalPage, ShowDialog, _showDialogHandlers, winrt::Windows::Foundation::IInspectable, WUX::Controls::ContentDialog); DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TerminalPage, ToggleFullscreen, _toggleFullscreenHandlers, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::ToggleFullscreenEventArgs); + DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TerminalPage, SetTitleBarColor, _setTitleBarColorHandlers, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::TabColorChangedEventArgs); + DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TerminalPage, ClearTitleBarColor, _clearTitleBarColorHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Color); } diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 3da62c5b03d..f7935bc4284 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -11,6 +11,7 @@ #include #include "AppCommandlineArgs.h" +#include "TabColorChangedEventArgs.g.h" // fwdecl unittest classes namespace TerminalAppLocalTests @@ -20,6 +21,25 @@ namespace TerminalAppLocalTests namespace winrt::TerminalApp::implementation { + struct TabColorChangedEventArgs : + public TabColorChangedEventArgsT + { + public: + TabColorChangedEventArgs(winrt::Windows::UI::Color titleBarColor, winrt::Windows::UI::Color minMaxForegroundColor, winrt::Windows::UI::Color minMaxHoverColor) : + _titleBarColor(titleBarColor), + _minMaxForegroundColor(minMaxForegroundColor), + _minMaxHoverColor(minMaxHoverColor) {} + + winrt::Windows::UI::Color TitleBarColor() { return _titleBarColor; }; + winrt::Windows::UI::Color MinMaxForegroundColor() { return _minMaxForegroundColor; }; + winrt::Windows::UI::Color MinMaxHoverColor() { return _minMaxHoverColor; }; + + private: + winrt::Windows::UI::Color _titleBarColor; + winrt::Windows::UI::Color _minMaxForegroundColor; + winrt::Windows::UI::Color _minMaxHoverColor; + }; + struct TerminalPage : TerminalPageT { public: @@ -48,6 +68,8 @@ namespace winrt::TerminalApp::implementation DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(SetTitleBarContent, _setTitleBarContentHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::UIElement); DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(ShowDialog, _showDialogHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Xaml::Controls::ContentDialog); DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(ToggleFullscreen, _toggleFullscreenHandlers, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::ToggleFullscreenEventArgs); + DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(SetTitleBarColor, _setTitleBarColorHandlers, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::TabColorChangedEventArgs); + DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(ClearTitleBarColor, _clearTitleBarColorHandlers, winrt::Windows::Foundation::IInspectable, winrt::Windows::UI::Color); private: // If you add controls here, but forget to null them either here or in @@ -148,6 +170,11 @@ namespace winrt::TerminalApp::implementation void _ToggleFullscreen(); + void _SetNonClientAreaColors(const Windows::UI::Color& selectedTabColor); + void _ClearNonClientAreaColors(); + void _SetTabRowColor(const Windows::UI::Color& color, const Windows::UI::Color& accentColor); + void _ClearTabRowColor(); + #pragma region ActionHandlers // These are all defined in AppActionHandlers.cpp void _HandleOpenNewTabDropdown(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); diff --git a/src/cascadia/TerminalApp/TerminalPage.idl b/src/cascadia/TerminalApp/TerminalPage.idl index a1bfcd98db2..00e316d59fb 100644 --- a/src/cascadia/TerminalApp/TerminalPage.idl +++ b/src/cascadia/TerminalApp/TerminalPage.idl @@ -6,6 +6,13 @@ namespace TerminalApp delegate void LastTabClosedEventArgs(); delegate void ToggleFullscreenEventArgs(); + runtimeclass TabColorChangedEventArgs + { + Windows.UI.Color TitleBarColor { get; }; + Windows.UI.Color MinMaxForegroundColor { get; }; + Windows.UI.Color MinMaxHoverColor { get; }; + }; + [default_interface] runtimeclass TerminalPage : Windows.UI.Xaml.Controls.Page { TerminalPage(); @@ -18,5 +25,7 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler SetTitleBarContent; event Windows.Foundation.TypedEventHandler ShowDialog; event Windows.Foundation.TypedEventHandler ToggleFullscreen; + event Windows.Foundation.TypedEventHandler SetTitleBarColor; + event Windows.Foundation.TypedEventHandler ClearTitleBarColor; } } diff --git a/src/cascadia/TerminalApp/TitlebarControl.cpp b/src/cascadia/TerminalApp/TitlebarControl.cpp index 0fb9aa11e19..dd71d426def 100644 --- a/src/cascadia/TerminalApp/TitlebarControl.cpp +++ b/src/cascadia/TerminalApp/TitlebarControl.cpp @@ -94,4 +94,38 @@ namespace winrt::TerminalApp::implementation MinMaxCloseControl().SetWindowVisualState(visualState); } + void TitlebarControl::SetMinMaxControlColors(const winrt::Windows::UI::Color& minMaxForegroundColor, const winrt::Windows::UI::Color& minMaxHoverColor) + { + winrt::Windows::UI::Xaml::Media::SolidColorBrush minMaxForegroundBrush{ minMaxForegroundColor }; + winrt::Windows::UI::Xaml::Media::SolidColorBrush minMaxHoverBrush{ minMaxHoverColor }; + MinMaxCloseControl().Resources().Insert(winrt::box_value(L"CaptionButtonStroke"), minMaxForegroundBrush); + MinMaxCloseControl().Resources().Insert(winrt::box_value(L"CaptionButtonStrokePressed"), minMaxForegroundBrush); + MinMaxCloseControl().Resources().Insert(winrt::box_value(L"CaptionButtonStrokePointerOver"), minMaxHoverBrush); + // this is not a mistake, it really looks better IMHO + MinMaxCloseControl().Resources().Insert(winrt::box_value(L"CaptionButtonBackgroundPointerOver"), minMaxHoverBrush); + + MinMaxCloseControl().RefreshButtons(); + } + + void TitlebarControl::ClearMinMaxControlColors() + { + winrt::hstring keys[] = { + L"CaptionButtonStroke", + L"CaptionButtonStrokePressed", + L"CaptionButtonStrokePointerOver", + L"CaptionButtonBackgroundPointerOver", + }; + + // simply clear any of the colors in the tab's dict + for (auto keyString : keys) + { + auto key = winrt::box_value(keyString); + if (MinMaxCloseControl().Resources().HasKey(key)) + { + MinMaxCloseControl().Resources().Remove(key); + } + } + + MinMaxCloseControl().RefreshButtons(); + } } diff --git a/src/cascadia/TerminalApp/TitlebarControl.h b/src/cascadia/TerminalApp/TitlebarControl.h index 8f0bb7d4d0d..f580d7a66db 100644 --- a/src/cascadia/TerminalApp/TitlebarControl.h +++ b/src/cascadia/TerminalApp/TitlebarControl.h @@ -21,6 +21,8 @@ namespace winrt::TerminalApp::implementation void Content(IInspectable content); void SetWindowVisualState(WindowVisualState visualState); + void SetMinMaxControlColors(const winrt::Windows::UI::Color& minMaxForegroundColor, const winrt::Windows::UI::Color& minMaxHoverColor); + void ClearMinMaxControlColors(); void Root_SizeChanged(const IInspectable& sender, Windows::UI::Xaml::SizeChangedEventArgs const& e); diff --git a/src/cascadia/TerminalApp/TitlebarControl.idl b/src/cascadia/TerminalApp/TitlebarControl.idl index 104f7a2f26f..a2a08d8e9f3 100644 --- a/src/cascadia/TerminalApp/TitlebarControl.idl +++ b/src/cascadia/TerminalApp/TitlebarControl.idl @@ -14,6 +14,8 @@ namespace TerminalApp { TitlebarControl(UInt64 parentWindowHandle); void SetWindowVisualState(WindowVisualState visualState); + void SetMinMaxControlColors(Windows.UI.Color minMaxForegroundColor, Windows.UI.Color minMaxHoverColor); + void ClearMinMaxControlColors(); IInspectable Content; Windows.UI.Xaml.Controls.Border DragBar { get; }; diff --git a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj index b062a8ec836..2811c86f19d 100644 --- a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj @@ -101,7 +101,8 @@ - + + ../ShortcutActionDispatch.idl @@ -160,6 +161,7 @@ + Create diff --git a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.filters b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.filters index 1a395f3ef59..e697e7c4c96 100644 --- a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.filters +++ b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.filters @@ -59,6 +59,7 @@ pane + @@ -107,6 +108,7 @@ tab + @@ -141,6 +143,9 @@ controls + + controls + diff --git a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.orig b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.orig deleted file mode 100644 index edf1b39d757..00000000000 --- a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj.orig +++ /dev/null @@ -1,356 +0,0 @@ - - - - {CA5CAD1A-9A12-429C-B551-8562EC954746} - Win32Proj - TerminalApp - TerminalAppLib - TerminalAppLib - 10.0.17763.0 - StaticLibrary - Console - true - - true - - false - - - - - - - - Designer - - - - - - Designer - - - Designer - - - Designer - - - Designer - - - Designer - - - - - - - ../MinMaxCloseControl.xaml - - - ../TerminalPage.xaml - Code - - - ../TitlebarControl.xaml - - - ../TabRowControl.xaml - - - ../ColorPickupFlyout.xaml - - - - - - - - - - - - - - - - -<<<<<<< HEAD - - -======= - - - ->>>>>>> master - ../ShortcutActionDispatch.idl - - - ../ActionArgs.idl - - - ../ActionArgs.idl - - - ../AppKeyBindings.idl - - - ../App.xaml - - - ../AppLogic.idl - - - - - - - ../MinMaxCloseControl.xaml - - - ../TerminalPage.xaml - Code - - - ../TitlebarControl.xaml - - - ../TabRowControl.xaml - - - ../ColorPickupFlyout.xaml - - - - - - - - - - - - - - - - - - Create - - - ../AppKeyBindings.idl - - - ../ShortcutActionDispatch.idl - - - ../ActionArgs.idl - - - ../ActionArgs.idl - - - ../App.xaml - - - ../App.xaml - - - ../AppLogic.idl - - - - NotUsing - - - - - - - - ../App.xaml - - - - - - - ../MinMaxCloseControl.xaml - Code - - - ../TerminalPage.xaml - Code - - - ../TitlebarControl.xaml - Code - - - ../TabRowControl.xaml - Code - - - ../ColorPickupFlyout.xaml - Code - - - - - - - - - - - - - {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE} - false - - - - - - <_BinRoot Condition="'$(Platform)' != 'Win32'">$(OpenConsoleDir)$(Platform)\$(Configuration)\ - <_BinRoot Condition="'$(Platform)' == 'Win32'">$(OpenConsoleDir)$(Configuration)\ - - - - Warning - - - - - $(_BinRoot)TerminalSettings\Microsoft.Terminal.Settings.winmd - true - false - false - - - $(_BinRoot)TerminalConnection\Microsoft.Terminal.TerminalConnection.winmd - true - false - false - - - $(_BinRoot)TerminalControl\Microsoft.Terminal.TerminalControl.winmd - true - false - false - - - - - - pch.h - ..;$(OpenConsoleDir)\dep\jsoncpp\json;%(AdditionalIncludeDirectories); - - 4702;%(DisableSpecificWarnings) - - - WindowsApp.lib;shell32.lib;%(AdditionalDependencies) - - - - - - - x86 - $(Platform) - <_MUXRoot>$(OpenConsoleDir)\packages\Microsoft.UI.Xaml.2.2.191203001-prerelease\ - <_MUXAppRoot>$(OpenConsoleDir)\packages\Microsoft.Toolkit.Win32.UI.XamlApplication.6.0.0\ - - - - - Microsoft.UI.Xaml.dll - true - false - true - - - - - - Microsoft.Toolkit.Win32.UI.XamlHost.dll - true - false - false - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - <_GenerateProjectPriFileDependsOn>OpenConsolePlaceAppXbfAtRootOfResourceTree;$(_GenerateProjectPriFileDependsOn) - - - - <_RelocatedAppXamlData Include="@(PackagingOutputs)" Condition="'%(Filename)' == 'App' and ('%(Extension)' == '.xaml' or '%(Extension)' == '.xbf')" /> - - - %(Filename)%(Extension) - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index c4f8f2b7a75..fcc64d39973 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -133,6 +133,8 @@ void AppHost::Initialize() // This has to be done _before_ App::Create, as the app might set the // content in Create. _logic.SetTitleBarContent({ this, &AppHost::_UpdateTitleBarContent }); + _logic.SetTitleBarColor({ this, &AppHost::_SetTitleBarColor }); + _logic.ClearTitleBarColor({ this, &AppHost::_ClearTitleBarColor }); } // Register the 'X' button of the window for a warning experience of multiple @@ -311,6 +313,40 @@ void AppHost::_UpdateTitleBarContent(const winrt::Windows::Foundation::IInspecta } } +// Method Description: +// - Called when the user has selected a custom color for a tab. +// Sets the background to a matching color +// Arguments: +// - sender: unused +// - color: the color to use as the new Titlebar background color. +// - minMaxForeground: the foreground color for the min max buttons +// - minMaxHover: the hover color for the min max buttons +// Return Value: +// - +void AppHost::_SetTitleBarColor(const winrt::Windows::Foundation::IInspectable&, const winrt::TerminalApp::TabColorChangedEventArgs& args) +{ + if (_useNonClientArea) + { + (static_cast(_window.get()))->SetTitlebarColor(args.TitleBarColor(), args.MinMaxForegroundColor(), args.MinMaxHoverColor()); + } +} + +// Method Description: +// - Called when the user has clears the custom tab color +// Sets the background to a matching color +// Arguments: +// - sender: unused +// - arg: unused +// Return Value: +// - +void AppHost::_ClearTitleBarColor(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::UI::Color& arg) +{ + if (_useNonClientArea) + { + (static_cast(_window.get()))->ClearTitlebarColor(arg); + } +} + // Method Description: // - Called when the app wants to change its theme. We'll forward this to the // IslandWindow, so it can update the root UI element of the entire XAML tree. diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index f65669dca5b..ac4d8f737a8 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -30,6 +30,10 @@ class AppHost void _HandleCreateWindow(const HWND hwnd, RECT proposedRect, winrt::TerminalApp::LaunchMode& launchMode); void _UpdateTitleBarContent(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::UIElement& arg); + void _SetTitleBarColor(const winrt::Windows::Foundation::IInspectable& sender, + const winrt::TerminalApp::TabColorChangedEventArgs& args); + void _ClearTitleBarColor(const winrt::Windows::Foundation::IInspectable& sender, + const winrt::Windows::UI::Color& arg); void _UpdateTheme(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::UI::Xaml::ElementTheme& arg); void _ToggleFullscreen(const winrt::Windows::Foundation::IInspectable& sender, diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index fc85e78f977..b72fc16c98e 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -7,6 +7,7 @@ #include "NonClientIslandWindow.h" #include "../types/inc/ThemeUtils.h" #include "../types/inc/utils.hpp" +#include extern "C" IMAGE_DOS_HEADER __ImageBase; @@ -111,6 +112,51 @@ void NonClientIslandWindow::SetTitlebarContent(winrt::Windows::UI::Xaml::UIEleme _titlebar.Content(content); } +// Method Description: +// - Set the background of the "titlebar area" of our window to the given color +// Arguments: +// - color: the new color to use as the titlebar background color +// - minMaxForeground: the foreground color for the min max buttons +// - minMaxHover: the hover color for the min max buttons +// Return Value: +// - +void NonClientIslandWindow::SetTitlebarColor(const winrt::Windows::UI::Color& color, const winrt::Windows::UI::Color& minMaxForeground, const winrt::Windows::UI::Color& minMaxHover) +{ + winrt::Windows::UI::Xaml::Media::SolidColorBrush brush{ color }; + _titlebar.Background(brush); + _titlebar.SetMinMaxControlColors(minMaxForeground, minMaxHover); + + // for whatever reason I need to re-invalidate the whole window, + // otherwise only the area underneath the tab row and the min/max control + // gets its color changed + ::RedrawWindow(_window.get(), NULL, NULL, RDW_FRAME | RDW_INVALIDATE); +} + +// Method Description: +// - Clears the background of the "titlebar area" of our window +// Arguments: +// - color: if no matching color is found in the resources, this one +// will be used instead +// Return Value: +// - +void NonClientIslandWindow::ClearTitlebarColor(const winrt::Windows::UI::Color& color) +{ + winrt::Windows::UI::Xaml::Media::SolidColorBrush brush{ color }; + const auto res = Application::Current().Resources(); + const auto tabViewBackgroundKey = winrt::box_value(L"TabViewBackground"); + if (res.HasKey(tabViewBackgroundKey)) + { + winrt::Windows::Foundation::IInspectable obj = res.Lookup(tabViewBackgroundKey); + brush = obj.try_as(); + } + _titlebar.Background(brush); + _titlebar.ClearMinMaxControlColors(); + // for whatever reason I need to re-invalidate the whole window, + // otherwise only the area underneath the tab row and the min/max control + // gets its color changed + ::RedrawWindow(_window.get(), NULL, NULL, RDW_FRAME | RDW_INVALIDATE); +} + // Method Description: // - This method computes the height of the little border above the title bar // and returns it. If the border is disabled, then this method will return 0. diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h index 6fdec25c827..ea51254489d 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h @@ -43,6 +43,8 @@ class NonClientIslandWindow : public IslandWindow void OnAppInitialized() override; void SetContent(winrt::Windows::UI::Xaml::UIElement content) override; void SetTitlebarContent(winrt::Windows::UI::Xaml::UIElement content); + void SetTitlebarColor(const winrt::Windows::UI::Color& color, const winrt::Windows::UI::Color& minMaxForeground, const winrt::Windows::UI::Color& minMaxHover); + void ClearTitlebarColor(const winrt::Windows::UI::Color& color); void OnApplicationThemeChanged(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) override; private: diff --git a/src/cascadia/ut_app/ColorHelperTests.cpp b/src/cascadia/ut_app/ColorHelperTests.cpp new file mode 100644 index 00000000000..dd686f59fd4 --- /dev/null +++ b/src/cascadia/ut_app/ColorHelperTests.cpp @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "precomp.h" + +#include "../TerminalApp/Profile.h" +#include "../TerminalApp/ColorHelper.h" + +using namespace Microsoft::Console; +using namespace winrt::TerminalApp; +using namespace WEX::Logging; +using namespace WEX::TestExecution; +using namespace WEX::Common; + +namespace TerminalAppUnitTests +{ + class ColorHelperTests + { + BEGIN_TEST_CLASS(ColorHelperTests) + TEST_CLASS_PROPERTY(L"ActivationContext", L"TerminalApp.Unit.Tests.manifest") + END_TEST_CLASS() + + TEST_METHOD(ConvertRgbToHsl); + TEST_METHOD(ConvertHslToRgb); + TEST_METHOD(LuminanceTests); + }; + + void ColorHelperTests::ConvertHslToRgb() + { + auto red = winrt::Windows::UI::Colors::Red(); + auto redHsl = ColorHelper::RgbToHsl(red); + VERIFY_ARE_EQUAL(0.f, redHsl.H); + VERIFY_ARE_EQUAL(1.f, redHsl.S); + VERIFY_ARE_EQUAL(0.5f, redHsl.L); + + auto green = winrt::Windows::UI::Colors::Lime(); + auto greenHsl = ColorHelper::RgbToHsl(green); + VERIFY_ARE_EQUAL(120.f, greenHsl.H); + VERIFY_ARE_EQUAL(1.f, greenHsl.S); + VERIFY_ARE_EQUAL(0.5f, greenHsl.L); + + auto blue = winrt::Windows::UI::Colors::Blue(); + auto blueHsl = ColorHelper::RgbToHsl(blue); + VERIFY_ARE_EQUAL(240.f, blueHsl.H); + VERIFY_ARE_EQUAL(1.f, blueHsl.S); + VERIFY_ARE_EQUAL(0.5f, blueHsl.L); + + auto darkTurquoise = winrt::Windows::UI::Colors::DarkTurquoise(); + auto darkTurquoiseHsl = ColorHelper::RgbToHsl(darkTurquoise); + VERIFY_ARE_EQUAL(181.f, darkTurquoiseHsl.H); + VERIFY_ARE_EQUAL(1.f, darkTurquoiseHsl.S); + VERIFY_ARE_EQUAL(0.41f, darkTurquoiseHsl.L); + + auto darkViolet = winrt::Windows::UI::Colors::DarkViolet(); + auto darkVioletHsl = ColorHelper::RgbToHsl(darkViolet); + VERIFY_ARE_EQUAL(282.f, darkVioletHsl.H); + VERIFY_ARE_EQUAL(1.f, darkVioletHsl.S); + VERIFY_ARE_EQUAL(0.414f, darkVioletHsl.L); + + auto white = winrt::Windows::UI::Colors::White(); + auto whiteHsl = ColorHelper::RgbToHsl(white); + VERIFY_ARE_EQUAL(0.f, whiteHsl.H); + VERIFY_ARE_EQUAL(0.f, whiteHsl.S); + VERIFY_ARE_EQUAL(1.f, whiteHsl.L); + + auto black = winrt::Windows::UI::Colors::Black(); + auto blackHsl = ColorHelper::RgbToHsl(black); + VERIFY_ARE_EQUAL(0.f, blackHsl.H); + VERIFY_ARE_EQUAL(0.f, blackHsl.S); + VERIFY_ARE_EQUAL(0.f, blackHsl.L); + } + + void ColorHelperTests::ConvertRgbToHsl() + { + auto redHsl = HSL{ 0.f, 100.f, 50.f }; + auto red = ColorHelper::HslToRgb(redHsl); + VERIFY_ARE_EQUAL(255, red.R); + VERIFY_ARE_EQUAL(0, red.G); + VERIFY_ARE_EQUAL(0, red.B); + + auto greenHsl = HSL{ 120.f, 100.f, 50.f }; + auto green = ColorHelper::HslToRgb(greenHsl); + VERIFY_ARE_EQUAL(0, green.R); + VERIFY_ARE_EQUAL(255, green.G); + VERIFY_ARE_EQUAL(0, green.B); + + auto blueHsl = HSL{ 240.f, 100.f, 50.f }; + auto blue = ColorHelper::HslToRgb(blueHsl); + VERIFY_ARE_EQUAL(0, blue.R); + VERIFY_ARE_EQUAL(0, blue.G); + VERIFY_ARE_EQUAL(255, blue.B); + + auto darkTurquoiseHsl = HSL{ 181.f, 100.f, 41.f }; + auto darkTurquoise = ColorHelper::HslToRgb(darkTurquoiseHsl); + VERIFY_ARE_EQUAL(0, darkTurquoise.R); + VERIFY_ARE_EQUAL(206, darkTurquoise.G); + VERIFY_ARE_EQUAL(209, darkTurquoise.B); + + auto darkVioletHsl = HSL{ 282.f, 100.f, 41.4f }; + auto darkViolet = ColorHelper::HslToRgb(darkVioletHsl); + VERIFY_ARE_EQUAL(148, darkViolet.R); + VERIFY_ARE_EQUAL(0, darkViolet.G); + VERIFY_ARE_EQUAL(211, darkViolet.B); + + auto whiteHsl = HSL{ 360.f, 100.f, 100.f }; + auto white = ColorHelper::HslToRgb(whiteHsl); + VERIFY_ARE_EQUAL(255, white.R); + VERIFY_ARE_EQUAL(255, white.G); + VERIFY_ARE_EQUAL(255, white.B); + + auto blackHsl = HSL{ 0.f, 0.f, 0.f }; + auto black = ColorHelper::HslToRgb(blackHsl); + VERIFY_ARE_EQUAL(0, black.R); + VERIFY_ARE_EQUAL(0, black.G); + VERIFY_ARE_EQUAL(0, black.B); + } + + void ColorHelperTests::LuminanceTests() + { + auto darkTurquoiseLuminance = ColorHelper::GetLuminance(winrt::Windows::UI::Colors::DarkTurquoise()); + VERIFY_ARE_EQUAL(48.75f, darkTurquoiseLuminance * 100); + auto darkVioletLuminance = ColorHelper::GetLuminance(winrt::Windows::UI::Colors::DarkViolet()); + VERIFY_ARE_EQUAL(11.f, darkVioletLuminance * 100); + auto magentaLuminance = ColorHelper::GetLuminance(winrt::Windows::UI::Colors::Magenta()); + VERIFY_ARE_EQUAL(28.48f, magentaLuminance * 100); + } +} diff --git a/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj b/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj index d8dd378417e..127200412e3 100644 --- a/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj +++ b/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj @@ -12,9 +12,8 @@ true - - - + @@ -44,22 +41,21 @@ NotUsing + + NotUsing + - - - ..;$(OpenConsoleDir)\dep\jsoncpp\json;$(OpenConsoleDir)src\inc;$(OpenConsoleDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalApp\lib\Generated Files";%(AdditionalIncludeDirectories) precomp.h - 4702;%(DisableSpecificWarnings) @@ -67,7 +63,6 @@ WindowsApp.lib;%(AdditionalDependencies) - true true @@ -75,25 +70,22 @@ - - - <_CppWinrtBinRoot>"$(OpenConsoleDir)$(Platform)\$(Configuration)\" + <_CppWinrtBinRoot>"$(OpenConsoleDir)$(Platform)\$(Configuration)\" x86 $(Platform) - <_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\runtimes\win10-$(Native-Platform)\native\" + <_MUXBinRoot>"$(OpenConsoleDir)packages\Microsoft.UI.Xaml.2.3.191217003-prerelease\runtimes\win10-$(Native-Platform)\native\" - - (xcopy /Y "$(OpenConsoleDir)src\cascadia\WindowsTerminal\WindowsTerminal.manifest" "$(OpenConsoleDir)src\cascadia\ut_app\TerminalApp.Unit.Tests.manifest*" ) + (xcopy /Y "$(OpenConsoleDir)src\cascadia\WindowsTerminal\WindowsTerminal.manifest" "$(OpenConsoleDir)src\cascadia\ut_app\TerminalApp.Unit.Tests.manifest*" ) @@ -111,14 +103,12 @@ --> echo OutDir=$(OutDir) - (xcopy /Y "$(OpenConsoleDir)src\cascadia\ut_app\TerminalApp.Unit.Tests.manifest" "$(OutDir)\TerminalApp.Unit.Tests.manifest*" ) - (xcopy /Y "$(OpenConsoleDir)src\cascadia\ut_app\TerminalApp.Unit.Tests.AppxManifest.xml" "$(OutDir)\TerminalApp.Unit.Tests.AppxManifest.xml*" ) + (xcopy /Y "$(OpenConsoleDir)src\cascadia\ut_app\TerminalApp.Unit.Tests.manifest" "$(OutDir)\TerminalApp.Unit.Tests.manifest*" ) + (xcopy /Y "$(OpenConsoleDir)src\cascadia\ut_app\TerminalApp.Unit.Tests.AppxManifest.xml" "$(OutDir)\TerminalApp.Unit.Tests.AppxManifest.xml*" ) - - diff --git a/src/cascadia/ut_app/precomp.h b/src/cascadia/ut_app/precomp.h index 53a18343367..9097a630411 100644 --- a/src/cascadia/ut_app/precomp.h +++ b/src/cascadia/ut_app/precomp.h @@ -46,3 +46,4 @@ Author(s): #include #include #include +#include