-
Notifications
You must be signed in to change notification settings - Fork 8.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for setting a tab's color at runtime w/ context menu (#3789)
This commit introduces a context menu for Tab and a new item, "Color...", which will display a color picker. A flyout menu, containing a custom flyout, is attached to each tab. The flyout displays a palette of 16 preset colors and includes a color picker. When the user selects or clears color, an event is fired, which is intercepted by the tab to which the flyout belongs. The changing of the color is achieved by putting the selected color in the resource dictionary of the tab, using well-defined dictionary keys (e.g. TabViewItemHeaderBackground). Afterwards the visual state of the tab is toggled, so that the color change is visible immediately. Custom-colored tabs will be desaturated (somewhat) by alpha blending them with the tab bar background. The flyout menu also contains a 'Close' flyout item. ## Validation Steps Performed I've validated the behavior manually: start the program via the start menu. Right click on the tab -> Choose a tab color. The color flyout is going to be shown. Click a color swatch or click 'Select a custom color' to use the color picker. Use the 'Clear the current color' to remove the custom color. Closes #2994. References #3327.
- Loading branch information
Showing
24 changed files
with
1,202 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,4 +7,5 @@ NCLBUTTONDBLCLK | |
NCRBUTTONDBLCLK | ||
NOREDIRECTIONBITMAP | ||
rfind | ||
roundf | ||
SIZENS |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ http | |
td | ||
www | ||
ecma | ||
rapidtables |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,269 @@ | ||
#include "pch.h" | ||
#include "ColorHelper.h" | ||
#include <limits> | ||
|
||
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<float>::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<float>::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<uint8_t>(std::roundf(r * 255)); | ||
auto finalG = static_cast<uint8_t>(std::roundf(g * 255)); | ||
auto finalB = static_cast<uint8_t>(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<float>::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<float, HSL>(); | ||
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<float>::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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#pragma once | ||
#include "pch.h" | ||
|
||
#include <winrt/windows.ui.core.h> | ||
|
||
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); | ||
}; | ||
} |
Oops, something went wrong.
624a553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New misspellings found, please review:
To accept these changes, run the following commands
✏️ Contributor please read this
.github/actions/spell-check/dictionary/names.txt
..github/actions/spell-check/dictionary/
..github/actions/spell-check/whitelist/
.add an item in an appropriate file in
.github/actions/spell-check/patterns/
.See the
README.md
in each directory for more information.At present, the action that triggered this message will not show its ❌ in this PR unless the branch is within this repository.
Thus, you should make sure that this comment has been addressed before encouraging the merge bot to merge this PR.