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.
This commit is contained in:
Georgi Baychev 2020-05-04 22:57:12 +02:00 committed by GitHub
parent 6ce3357bab
commit 624a553d23
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 1202 additions and 6 deletions

View file

@ -7,4 +7,5 @@ NCLBUTTONDBLCLK
NCRBUTTONDBLCLK
NOREDIRECTIONBITMAP
rfind
roundf
SIZENS

View file

@ -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}\}

View file

@ -2,3 +2,4 @@ http
td
www
ecma
rapidtables

View file

@ -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
{

View file

@ -39,6 +39,8 @@ Author(s):
#include "../../types/inc/utils.hpp"
#include "../../inc/DefaultSettings.h"
#include <winrt/Windows.ApplicationModel.Resources.Core.h>
#include "winrt/Windows.UI.Xaml.Markup.h"
#include <winrt/Windows.system.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>

View file

@ -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;
}

View file

@ -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);
};
}

View file

@ -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 <LibraryResources.h>
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:
// - <none>
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:
// - <none>
void ColorPickupFlyout::ColorButton_Click(IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const&)
{
auto button{ sender.as<Windows::UI::Xaml::Controls::Button>() };
auto rectangle{ button.Content().as<Windows::UI::Xaml::Shapes::Rectangle>() };
auto rectClr{ rectangle.Fill().as<Windows::UI::Xaml::Media::SolidColorBrush>() };
_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:
// - <none>
// Return Value:
// - <none>
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:
// - <none>
// Return Value:
// - <none>
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:
// - <none>
// Return Value:
// - <none>
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());
}
}

View file

@ -0,0 +1,27 @@
#pragma once
#include "ColorPickupFlyout.g.h"
#include "../cascadia/inc/cppwinrt_utils.h"
namespace winrt::TerminalApp::implementation
{
struct ColorPickupFlyout : ColorPickupFlyoutT<ColorPickupFlyout>
{
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<ColorPickupFlyout, implementation::ColorPickupFlyout>
{
};
}

View file

@ -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;
}
}

View file

@ -0,0 +1,146 @@
<Flyout
x:Class="TerminalApp.ColorPickupFlyout"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TerminalApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Flyout.FlyoutPresenterStyle>
<Style TargetType="FlyoutPresenter">
<Setter Property="MinWidth" Value="0"/>
</Style>
</Flyout.FlyoutPresenterStyle>
<StackPanel Orientation="Horizontal">
<StackPanel>
<VariableSizedWrapGrid Orientation="Horizontal" MaximumRowsOrColumns="4" HorizontalAlignment="Center" Margin="0, 3, 0, 0">
<VariableSizedWrapGrid.Resources>
<Style TargetType="Rectangle">
<Setter Property="Width" Value="30"/>
<Setter Property="Height" Value="30"/>
</Style>
<Style TargetType="Button">
<Setter Property="Padding" Value="0"/>
<Setter Property="Margin" Value="2"/>
</Style>
</VariableSizedWrapGrid.Resources>
<Button Click="ColorButton_Click" AutomationProperties.Name="Crimson">
<Button.Content>
<Rectangle Fill="Crimson"/>
</Button.Content>
</Button>
<Button Click="ColorButton_Click" AutomationProperties.Name="SteelBlue">
<Button.Content>
<Rectangle Fill="SteelBlue"/>
</Button.Content>
</Button>
<Button Click="ColorButton_Click" AutomationProperties.Name="MediumSeaGreen">
<Button.Content>
<Rectangle Fill="MediumSeaGreen"/>
</Button.Content>
</Button>
<Button Click="ColorButton_Click" AutomationProperties.Name="DarkOrange">
<Button.Content>
<Rectangle Fill="DarkOrange"/>
</Button.Content>
</Button>
<Button Click="ColorButton_Click" AutomationProperties.Name="MediumVioletRed">
<Button.Content>
<Rectangle Fill="MediumVioletRed"/>
</Button.Content>
</Button>
<Button Click="ColorButton_Click" AutomationProperties.Name="DodgerBlue">
<Button.Content>
<Rectangle Fill="DodgerBlue"/>
</Button.Content>
</Button>
<Button Click="ColorButton_Click" AutomationProperties.Name="LimeGreen">
<Button.Content>
<Rectangle Fill="LimeGreen"/>
</Button.Content>
</Button>
<Button Click="ColorButton_Click" AutomationProperties.Name="Yellow">
<Button.Content>
<Rectangle Fill="Yellow"/>
</Button.Content>
</Button>
<Button Click="ColorButton_Click" AutomationProperties.Name="BlueViolet">
<Button.Content>
<Rectangle Fill="BlueViolet"/>
</Button.Content>
</Button>
<Button Click="ColorButton_Click" AutomationProperties.Name="SlateBlue">
<Button.Content>
<Rectangle Fill="SlateBlue"/>
</Button.Content>
</Button>
<Button Click="ColorButton_Click" AutomationProperties.Name="Lime">
<Button.Content>
<Rectangle Fill="Lime"/>
</Button.Content>
</Button>
<Button Click="ColorButton_Click" AutomationProperties.Name="Tan">
<Button.Content>
<Rectangle Fill="Tan"/>
</Button.Content>
</Button>
<Button Click="ColorButton_Click" AutomationProperties.Name="Magenta">
<Button.Content>
<Rectangle Fill="Magenta"/>
</Button.Content>
</Button>
<Button Click="ColorButton_Click" AutomationProperties.Name="Cyan">
<Button.Content>
<Rectangle Fill="Cyan"/>
</Button.Content>
</Button>
<Button Click="ColorButton_Click" AutomationProperties.Name="SkyBlue">
<Button.Content>
<Rectangle Fill="SkyBlue"/>
</Button.Content>
</Button>
<Button Click="ColorButton_Click" AutomationProperties.Name="DarkGray">
<Button.Content>
<Rectangle Fill="DarkGray"/>
</Button.Content>
</Button>
</VariableSizedWrapGrid>
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="Button">
<Setter Property="Margin" Value="2" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
</StackPanel.Resources>
<Button Padding="5"
Click="ClearColorButton_Click"
x:Name="ClearColorButton" x:Uid="TabColorClearButton" Content="Reset">
</Button>
<Button Padding="5"
Click="ShowColorPickerButton_Click"
x:Name="CustomColorButton" x:Uid="TabColorCustomButton" Content="Custom...">
</Button>
</StackPanel>
</StackPanel>
<Grid Visibility="Collapsed" x:Name="customColorPanel" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ColorPicker x:Name="customColorPicker"
IsMoreButtonVisible="True"
IsColorSliderVisible="False"
IsColorChannelTextInputVisible="True"
IsHexInputVisible="True"
IsAlphaEnabled="False"
IsAlphaSliderVisible="False"
IsAlphaTextInputVisible="False"
FontSize="10"
Grid.Row="0"
ColorChanged="ColorPicker_ColorChanged"
>
</ColorPicker>
<Button x:Name="OkButton" Click="CustomColorButton_Click" Grid.Row="1" HorizontalAlignment="Center" MinWidth="130" MinHeight="12" Margin="0, 5, 0, 0" x:Uid="OkButton" Content="**OK**"/>
</Grid>
</StackPanel>
</Flyout>

View file

@ -168,6 +168,27 @@
<data name="SettingsMenuItem" xml:space="preserve">
<value>Settings</value>
</data>
<data name="Cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="CloseAll" xml:space="preserve">
<value>Close all</value>
</data>
<data name="CloseWindowWarningTitle" xml:space="preserve">
<value>Do you want to close all tabs?</value>
</data>
<data name="TabClose" xml:space="preserve">
<value>Close</value>
</data>
<data name="TabColorChoose" xml:space="preserve">
<value>Color...</value>
</data>
<data name="TabColorCustomButton.Content" xml:space="preserve">
<value>Custom...</value>
</data>
<data name="TabColorClearButton.Content" xml:space="preserve">
<value>Reset</value>
</data>
<data name="InvalidBackgroundImage" xml:space="preserve">
<value>Found a profile with an invalid "backgroundImage". Defaulting that profile to have no background image. Make sure that when setting a "backgroundImage", the value is a valid file path to an image.</value>
<comment>{Locked="\"backgroundImage\""}</comment>

View file

@ -2,9 +2,12 @@
// Licensed under the MIT license.
#include "pch.h"
#include <LibraryResources.h>
#include "ColorPickupFlyout.h"
#include "Tab.h"
#include "Tab.g.cpp"
#include "Utils.h"
#include "ColorHelper.h"
using namespace winrt;
using namespace winrt::Windows::UI::Xaml;
@ -81,6 +84,20 @@ namespace winrt::TerminalApp::implementation
return _tabViewItem;
}
// Method Description:
// - Called after construction of a Tab object to bind event handlers to its
// associated Pane and TermControl object and to create the context menu of
// the tab item
// Arguments:
// - control: reference to the TermControl object to bind event to
// Return Value:
// - <none>
void Tab::Initialize(const TermControl& control)
{
_BindEventHandlers(control);
_CreateContextMenu();
}
// Method Description:
// - Returns true if this is the currently focused tab. For any set of tabs,
// there should only be one tab that is marked as focused, though each tab has
@ -133,7 +150,7 @@ namespace winrt::TerminalApp::implementation
// - control: reference to the TermControl object to bind event to
// Return Value:
// - <none>
void Tab::BindEventHandlers(const TermControl& control) noexcept
void Tab::_BindEventHandlers(const TermControl& control) noexcept
{
_AttachEventHandlersToPane(_rootPane);
_AttachEventHandlersToControl(control);
@ -440,6 +457,208 @@ namespace winrt::TerminalApp::implementation
}
// Method Description:
// - Creates a context menu attached to the tab.
// Currently contains elements allowing to select or
// to close the current tab
// Arguments:
// - <none>
// Return Value:
// - <none>
void Tab::_CreateContextMenu()
{
auto weakThis{ get_weak() };
// Close
Controls::MenuFlyoutItem closeTabMenuItem;
Controls::FontIcon closeSymbol;
closeSymbol.FontFamily(Media::FontFamily{ L"Segoe MDL2 Assets" });
closeSymbol.Glyph(L"\xE8BB");
closeTabMenuItem.Click([weakThis](auto&&, auto&&) {
if (auto tab{ weakThis.get() })
{
tab->_rootPane->Close();
}
});
closeTabMenuItem.Text(RS_(L"TabClose"));
closeTabMenuItem.Icon(closeSymbol);
// "Color..."
Controls::MenuFlyoutItem chooseColorMenuItem;
Controls::FontIcon colorPickSymbol;
colorPickSymbol.FontFamily(Media::FontFamily{ L"Segoe MDL2 Assets" });
colorPickSymbol.Glyph(L"\xE790");
chooseColorMenuItem.Click([weakThis](auto&&, auto&&) {
if (auto tab{ weakThis.get() })
{
tab->_tabColorPickup.ShowAt(tab->_tabViewItem);
}
});
chooseColorMenuItem.Text(RS_(L"TabColorChoose"));
chooseColorMenuItem.Icon(colorPickSymbol);
// Color Picker (it's convenient to have it here)
_tabColorPickup.ColorSelected([weakThis](auto newTabColor) {
if (auto tab{ weakThis.get() })
{
tab->_SetTabColor(newTabColor);
}
});
_tabColorPickup.ColorCleared([weakThis]() {
if (auto tab{ weakThis.get() })
{
tab->_ResetTabColor();
}
});
// Build the menu
Controls::MenuFlyout newTabFlyout;
Controls::MenuFlyoutSeparator menuSeparator;
newTabFlyout.Items().Append(chooseColorMenuItem);
newTabFlyout.Items().Append(menuSeparator);
newTabFlyout.Items().Append(closeTabMenuItem);
_tabViewItem.ContextFlyout(newTabFlyout);
}
// Method Description:
// Returns the tab color, if any
// Arguments:
// - <none>
// Return Value:
// - The tab's color, if any
std::optional<winrt::Windows::UI::Color> 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
// the background color
// Arguments:
// - color: the shiny color the user picked for their tab
// Return Value:
// - <none>
void Tab::_SetTabColor(const winrt::Windows::UI::Color& color)
{
auto weakThis{ get_weak() };
_tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis, color]() {
auto ptrTab = weakThis.get();
if (!ptrTab)
return;
auto tab{ ptrTab };
Media::SolidColorBrush selectedTabBrush{};
Media::SolidColorBrush deselectedTabBrush{};
Media::SolidColorBrush fontBrush{};
Media::SolidColorBrush hoverTabBrush{};
// calculate the luminance of the current color and select a font
// color based on that
// see https://www.w3.org/TR/WCAG20/#relativeluminancedef
if (TerminalApp::ColorHelper::IsBrightColor(color))
{
fontBrush.Color(winrt::Windows::UI::Colors::Black());
}
else
{
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);
// currently if a tab has a custom color, a deselected state is
// signified by using the same color with a bit ot transparency
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"), 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);
});
}
// Method Description:
// Clear the custom color of the tab, if any
// the background color
// Arguments:
// - <none>
// Return Value:
// - <none>
void Tab::_ResetTabColor()
{
auto weakThis{ get_weak() };
_tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis]() {
auto ptrTab = weakThis.get();
if (!ptrTab)
return;
auto tab{ ptrTab };
winrt::hstring keys[] = {
L"TabViewItemHeaderBackground",
L"TabViewItemHeaderBackgroundSelected",
L"TabViewItemHeaderBackgroundPointerOver",
L"TabViewItemHeaderForeground",
L"TabViewItemHeaderForegroundSelected",
L"TabViewItemHeaderForegroundPointerOver",
L"TabViewItemHeaderBackgroundPressed",
L"TabViewItemHeaderForegroundPressed"
};
// simply clear any of the colors in the tab's dict
for (auto keyString : keys)
{
auto key = winrt::box_value(keyString);
if (tab->_tabViewItem.Resources().HasKey(key))
{
tab->_tabViewItem.Resources().Remove(key);
}
}
tab->_RefreshVisualState();
tab->_tabColor.reset();
tab->_colorCleared();
});
}
// Method Description:
// Toggles the visual state of the tab view item,
// so that changes to the tab color are reflected immediately
// Arguments:
// - <none>
// Return Value:
// - <none>
void Tab::_RefreshVisualState()
{
if (_focused)
{
winrt::Windows::UI::Xaml::VisualStateManager::GoToState(_tabViewItem, L"Normal", true);
winrt::Windows::UI::Xaml::VisualStateManager::GoToState(_tabViewItem, L"Selected", true);
}
else
{
winrt::Windows::UI::Xaml::VisualStateManager::GoToState(_tabViewItem, L"Selected", true);
winrt::Windows::UI::Xaml::VisualStateManager::GoToState(_tabViewItem, L"Normal", true);
}
}
// - Get the total number of leaf panes in this tab. This will be the number
// of actual controls hosted by this tab.
// Arguments:
@ -467,4 +686,6 @@ namespace winrt::TerminalApp::implementation
}
DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
DEFINE_EVENT(Tab, ColorSelected, _colorSelected, winrt::delegate<winrt::Windows::UI::Color>);
DEFINE_EVENT(Tab, ColorCleared, _colorCleared, winrt::delegate<>);
}

View file

@ -3,6 +3,7 @@
#pragma once
#include "Pane.h"
#include "ColorPickupFlyout.h"
#include "Tab.g.h"
// fwdecl unittest classes
@ -19,8 +20,8 @@ namespace winrt::TerminalApp::implementation
Tab() = delete;
Tab(const GUID& profile, const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
// Called after construction to setup events with weak_ptr
void BindEventHandlers(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control) noexcept;
// Called after construction to perform the necessary setup, which relies on weak_ptr
void Initialize(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
winrt::Microsoft::UI::Xaml::Controls::TabViewItem GetTabViewItem();
winrt::Windows::UI::Xaml::UIElement GetRootElement();
@ -51,9 +52,13 @@ namespace winrt::TerminalApp::implementation
void Shutdown();
void ClosePane();
std::optional<winrt::Windows::UI::Color> GetTabColor();
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
DECLARE_EVENT(ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>);
DECLARE_EVENT(ColorSelected, _colorSelected, winrt::delegate<winrt::Windows::UI::Color>);
DECLARE_EVENT(ColorCleared, _colorCleared, winrt::delegate<>);
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Title, _PropertyChangedHandlers);
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, IconPath, _PropertyChangedHandlers);
@ -62,6 +67,8 @@ namespace winrt::TerminalApp::implementation
std::shared_ptr<Pane> _rootPane{ nullptr };
std::shared_ptr<Pane> _activePane{ nullptr };
winrt::hstring _lastIconPath{};
winrt::TerminalApp::ColorPickupFlyout _tabColorPickup{};
std::optional<winrt::Windows::UI::Color> _tabColor{};
bool _focused{ false };
winrt::Microsoft::UI::Xaml::Controls::TabViewItem _tabViewItem{ nullptr };
@ -69,6 +76,13 @@ namespace winrt::TerminalApp::implementation
void _MakeTabViewItem();
void _Focus();
void _CreateContextMenu();
void _SetTabColor(const winrt::Windows::UI::Color& color);
void _ResetTabColor();
void _RefreshVisualState();
void _BindEventHandlers(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control) noexcept;
void _AttachEventHandlersToControl(const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
void _AttachEventHandlersToPane(std::shared_ptr<Pane> pane);

View file

@ -16,6 +16,7 @@
#include "AzureCloudShellGenerator.h" // For AzureConnectionType
#include "TelnetGenerator.h" // For TelnetConnectionType
#include "TabRowControl.h"
#include "ColorHelper.h"
#include "DebugTapConnection.h"
using namespace winrt;
@ -872,7 +873,7 @@ namespace winrt::TerminalApp::implementation
term.PasteFromClipboard({ this, &TerminalPage::_PasteFromClipboardHandler });
// Bind Tab events to the TermControl and the Tab's Pane
hostingTab.BindEventHandlers(term);
hostingTab.Initialize(term);
// Don't capture a strong ref to the tab. If the tab is removed as this
// is called, we don't really care anymore about handling the event.
@ -888,6 +889,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
_ClearNewTabButtonColor();
}
// Method Description:
@ -1474,6 +1499,10 @@ namespace winrt::TerminalApp::implementation
_RemoveTabViewItem(sender.as<MUX::Controls::TabViewItem>());
eventArgs.Handled(true);
}
else if (eventArgs.GetCurrentPoint(*this).Properties().IsRightButtonPressed())
{
eventArgs.Handled(true);
}
}
void TerminalPage::_UpdatedSelectedTab(const int32_t index)
@ -1495,7 +1524,20 @@ namespace winrt::TerminalApp::implementation
_tabContent.Children().Append(tab->GetRootElement());
tab->SetFocused(true);
// Raise an event that our title changed
_titleChangeHandlers(*this, Title());
// Raise an event that our titlebar color changed
std::optional<Windows::UI::Color> color = tab->GetTabColor();
if (color.has_value())
{
_SetNonClientAreaColors(color.value());
}
else
{
_ClearNonClientAreaColors();
}
}
CATCH_LOG();
}
@ -1790,6 +1832,153 @@ namespace winrt::TerminalApp::implementation
return tabImpl;
}
// Method Description:
// - Sets the tab split button 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:
// - <none>
void TerminalPage::_SetNewTabButtonColor(const Windows::UI::Color& color, const Windows::UI::Color& accentColor)
{
// TODO GH#3327: Look at what to do with the tab button when we have XAML theming
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);
}
// 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:
// - <none>
// Return Value:
// - <none>
void TerminalPage::_ClearNewTabButtonColor()
{
// TODO GH#3327: Look at what to do with the tab button when we have XAML theming
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<winrt::Windows::UI::Xaml::Media::SolidColorBrush>();
}
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<winrt::Windows::UI::Xaml::Media::SolidColorBrush>();
}
else
{
foregroundBrush = winrt::Windows::UI::Xaml::Media::SolidColorBrush{ winrt::Windows::UI::Colors::White() };
}
_newTabButton.Background(backgroundBrush);
_newTabButton.Foreground(foregroundBrush);
}
// Method Description:
// - Sets the tab split button color when a new tab color is selected
// - This method could also set the color of the title bar and tab row
// in the future
// Arguments:
// - selectedTabColor: The color of the newly selected tab
// Return Value:
// - <none>
void TerminalPage::_SetNonClientAreaColors(const Windows::UI::Color& /*selectedTabColor*/)
{
// TODO GH#3327: Look at what to do with the NC area when we have XAML theming
}
// Method Description:
// - Clears the tab split button color when the tab's color is cleared
// - This method could also clear the color of the title bar and tab row
// in the future
// Arguments:
// - <none>
// Return Value:
// - <none>
void TerminalPage::_ClearNonClientAreaColors()
{
// TODO GH#3327: Look at what to do with the NC area when we have XAML theming
}
// -------------------------------- 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.

View file

@ -168,6 +168,11 @@ namespace winrt::TerminalApp::implementation
void _ToggleFullscreen();
void _SetNonClientAreaColors(const Windows::UI::Color& selectedTabColor);
void _ClearNonClientAreaColors();
void _SetNewTabButtonColor(const Windows::UI::Color& color, const Windows::UI::Color& accentColor);
void _ClearNewTabButtonColor();
#pragma region ActionHandlers
// These are all defined in AppActionHandlers.cpp
void _HandleOpenNewTabDropdown(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);

View file

@ -93,5 +93,4 @@ namespace winrt::TerminalApp::implementation
{
MinMaxCloseControl().SetWindowVisualState(visualState);
}
}

View file

@ -21,7 +21,6 @@ namespace winrt::TerminalApp::implementation
void Content(IInspectable content);
void SetWindowVisualState(WindowVisualState visualState);
void Root_SizeChanged(const IInspectable& sender, Windows::UI::Xaml::SizeChangedEventArgs const& e);
void Minimize_Click(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::RoutedEventArgs const& e);

View file

@ -60,6 +60,9 @@
<Page Include="../TabRowControl.xaml">
<SubType>Designer</SubType>
</Page>
<Page Include="../ColorPickupFlyout.xaml">
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<!-- ========================= Headers ======================== -->
<ItemGroup>
@ -79,6 +82,9 @@
<ClInclude Include="../TabRowControl.h">
<DependentUpon>../TabRowControl.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="../ColorPickupFlyout.h">
<DependentUpon>../ColorPickupFlyout.xaml</DependentUpon>
</ClInclude>
<ClInclude Include="../Tab.h">
<DependentUpon>../Tab.idl</DependentUpon>
</ClInclude>
@ -97,6 +103,7 @@
<ClInclude Include="../WslDistroGenerator.h" />
<ClInclude Include="../AzureCloudShellGenerator.h" />
<ClInclude Include="../TelnetGenerator.h" />
<ClInclude Include="..\ColorHelper.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="../ShortcutActionDispatch.h">
<DependentUpon>../ShortcutActionDispatch.idl</DependentUpon>
@ -136,6 +143,9 @@
<ClCompile Include="../TabRowControl.cpp">
<DependentUpon>../TabRowControl.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="../ColorPickupFlyout.cpp">
<DependentUpon>../ColorPickupFlyout.xaml</DependentUpon>
</ClCompile>
<ClCompile Include="../Tab.cpp">
<DependentUpon>../Tab.idl</DependentUpon>
</ClCompile>
@ -154,6 +164,7 @@
<ClCompile Include="../WslDistroGenerator.cpp" />
<ClCompile Include="../AzureCloudShellGenerator.cpp" />
<ClCompile Include="../Pane.LayoutSizeNode.cpp" />
<ClCompile Include="../ColorHelper.cpp" />
<ClCompile Include="../DebugTapConnection.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
@ -214,6 +225,10 @@
<DependentUpon>../TabRowControl.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="../ColorPickupFlyout.idl">
<DependentUpon>../ColorPickupFlyout.xaml</DependentUpon>
<SubType>Code</SubType>
</Midl>
<Midl Include="../Tab.idl"/>
</ItemGroup>
<!-- ========================= Misc Files ======================== -->

View file

@ -59,6 +59,7 @@
<ClCompile Include="../Pane.LayoutSizeNode.cpp">
<Filter>pane</Filter>
</ClCompile>
<ClCompile Include="..\ColorHelper.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="../Utils.h" />
@ -107,6 +108,7 @@
<Filter>tab</Filter>
</ClInclude>
<ClInclude Include="../TelnetGenerator.h" />
<ClInclude Include="..\ColorHelper.h" />
</ItemGroup>
<ItemGroup>
<Midl Include="../AppLogic.idl">
@ -141,6 +143,9 @@
<Page Include="../TitlebarControl.xaml">
<Filter>controls</Filter>
</Page>
<Page Include="../ColorPickupFlyout.xaml">
<Filter>controls</Filter>
</Page>
</ItemGroup>
<ItemGroup>
<Filter Include="app">

View file

@ -63,6 +63,7 @@ TRACELOGGING_DECLARE_PROVIDER(g_hTerminalAppProvider);
#include <shellapi.h>
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
#include <winrt/Windows.UI.Popups.h>
#include <CLI11/CLI11.hpp>

View file

@ -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);
}
}

View file

@ -34,6 +34,7 @@
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>
<ClCompile Include="ColorHelperTests.cpp" />
<ClCompile Include="JsonTests.cpp" />
<ClCompile Include="DynamicProfileTests.cpp" />
<ClCompile Include="precomp.cpp">
@ -44,6 +45,9 @@
<ClCompile Include="$(OpenConsoleDir)\dep\jsoncpp\jsoncpp.cpp">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="..\TerminalApp\ColorHelper.cpp">
<PrecompiledHeader>NotUsing</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<!-- ========================= Project References ======================== -->

View file

@ -46,3 +46,4 @@ Author(s):
#include <hstring.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/windows.ui.core.h>