Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for per-profile tab colors #7162

Merged
merged 10 commits into from
Aug 7, 2020
4 changes: 2 additions & 2 deletions src/cascadia/TerminalApp/AppActionHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,11 +289,11 @@ namespace winrt::TerminalApp::implementation
{
if (tabColor.has_value())
{
activeTab->SetTabColor(tabColor.value());
activeTab->SetRuntimeTabColor(tabColor.value());
}
else
{
activeTab->ResetTabColor();
activeTab->ResetRuntimeTabColor();
}
}
args.Handled(true);
Expand Down
4 changes: 2 additions & 2 deletions src/cascadia/TerminalApp/CascadiaSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ void CascadiaSettings::_ResolveDefaultProfile()
{
const auto unparsedDefaultProfile{ GlobalSettings().UnparsedDefaultProfile() };
auto maybeParsedDefaultProfile{ _GetProfileGuidByName(unparsedDefaultProfile) };
auto defaultProfileGuid{ Utils::CoalesceOptionals(maybeParsedDefaultProfile, GUID{}) };
auto defaultProfileGuid{ til::coalesce_value(maybeParsedDefaultProfile, GUID{}) };
GlobalSettings().DefaultProfile(defaultProfileGuid);
}

Expand Down Expand Up @@ -565,7 +565,7 @@ GUID CascadiaSettings::_GetProfileForArgs(const NewTerminalArgs& newTerminalArgs
profileByName = _GetProfileGuidByName(newTerminalArgs.Profile());
}

return Utils::CoalesceOptionals(profileByName, profileByIndex, _globals.DefaultProfile());
return til::coalesce_value(profileByName, profileByIndex, _globals.DefaultProfile());
}

// Method Description:
Expand Down
9 changes: 9 additions & 0 deletions src/cascadia/TerminalApp/Profile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ static constexpr std::string_view BackgroundImageStretchModeKey{ "backgroundImag
static constexpr std::string_view BackgroundImageAlignmentKey{ "backgroundImageAlignment" };
static constexpr std::string_view RetroTerminalEffectKey{ "experimental.retroTerminalEffect" };
static constexpr std::string_view AntialiasingModeKey{ "antialiasingMode" };
static constexpr std::string_view TabColorKey{ "tabColor" };

Profile::Profile() :
Profile(std::nullopt)
Expand Down Expand Up @@ -232,6 +233,12 @@ TerminalSettings Profile::CreateTerminalSettings(const std::unordered_map<std::w

terminalSettings.AntialiasingMode(_antialiasingMode);

if (_tabColor)
{
winrt::Windows::Foundation::IReference<uint32_t> colorRef{ _tabColor.value() };
terminalSettings.TabColor(colorRef);
}

return terminalSettings;
}

Expand Down Expand Up @@ -405,6 +412,8 @@ void Profile::LayerJson(const Json::Value& json)
JsonUtils::GetValueForKey(json, BackgroundImageAlignmentKey, _backgroundImageAlignment);
JsonUtils::GetValueForKey(json, RetroTerminalEffectKey, _retroTerminalEffect);
JsonUtils::GetValueForKey(json, AntialiasingModeKey, _antialiasingMode);

JsonUtils::GetValueForKey(json, TabColorKey, _tabColor);
}

void Profile::SetFontFace(std::wstring fontFace) noexcept
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/Profile.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ class TerminalApp::Profile final
std::optional<til::color> _selectionBackground;
std::optional<til::color> _cursorColor;
std::optional<std::wstring> _tabTitle;
std::optional<til::color> _tabColor;
bool _suppressApplicationTitle;
int32_t _historySize;
bool _snapOnInput;
Expand Down
225 changes: 151 additions & 74 deletions src/cascadia/TerminalApp/Tab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ namespace winrt::TerminalApp::implementation
});

_UpdateTitle();
_RecalculateAndApplyTabColor();
}

// Method Description:
Expand Down Expand Up @@ -435,6 +436,16 @@ namespace winrt::TerminalApp::implementation
_rootPane->Relayout();
}
});

control.TabColorChanged([weakThis](auto&&, auto&&) {
if (auto tab{ weakThis.get() })
{
// The control's tabColor changed, but it is not necessarily the
// active control in this tab. We'll just recalculate the
// current color anyways.
tab->_RecalculateAndApplyTabColor();
}
});
}

// Method Description:
Expand Down Expand Up @@ -479,6 +490,7 @@ namespace winrt::TerminalApp::implementation
if (tab && sender != tab->_activePane)
{
tab->_UpdateActivePane(sender);
tab->_RecalculateAndApplyTabColor();
}
});
}
Expand Down Expand Up @@ -529,14 +541,14 @@ namespace winrt::TerminalApp::implementation
_tabColorPickup.ColorSelected([weakThis](auto newTabColor) {
if (auto tab{ weakThis.get() })
{
tab->SetTabColor(newTabColor);
tab->SetRuntimeTabColor(newTabColor);
}
});

_tabColorPickup.ColorCleared([weakThis]() {
if (auto tab{ weakThis.get() })
{
tab->ResetTabColor();
tab->ResetRuntimeTabColor();
}
});

Expand Down Expand Up @@ -706,114 +718,179 @@ namespace winrt::TerminalApp::implementation
// - The tab's color, if any
std::optional<winrt::Windows::UI::Color> Tab::GetTabColor()
{
return _tabColor;
const auto currControlColor{ GetActiveTerminalControl().TabColor() };
std::optional<winrt::Windows::UI::Color> controlTabColor;
if (currControlColor != nullptr)
{
controlTabColor = currControlColor.Value();
}

// A Tab's color will be the result of layering a variety of sources,
// from the bottom up:
//
// Color | | Set by
// -------------------- | -- | --
// Runtime Color | _optional_ | Color Picker / `setTabColor` action
// Control Tab Color | _optional_ | Profile's `tabColor`, or a color set by VT
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"or a color set by VT"

Shouldn't this be in the line above with "Runtime Color"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh so I'm thinking that this operates more like the tab title, which can be set by VT, but the VT title can also be overridden temporarily at runtime the tab renamer.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also note that I'm not sure that there is a VT sequence for setting the tab color, that's purely a hypothetical

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also note that I'm not sure that there is a VT sequence for setting the tab color, that's purely a hypothetical

One could argue setting the title to a text string with a VT-sequence to set the background color could reasonably be interpreted as a request / intent to set the tab (or titlebar if tabs are disabled )color.

// Theme Tab Background | _optional_ | `tab.backgroundColor` in the theme
// Tab Default Color | **default** | TabView in XAML
//
// coalesce will get us the first of these values that's
// actually set, with nullopt being our sentinel for "use the default
// tabview color" (and clear out any colors we've set).

return til::coalesce(_runtimeTabColor,
controlTabColor,
_themeTabColor,
std::optional<Windows::UI::Color>(std::nullopt));
}

// Method Description:
// - Sets the tab background color to the color chosen by the user
// - Sets the runtime 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
// - color: the color the user picked for their tab
// Return Value:
// - <none>
void Tab::SetTabColor(const winrt::Windows::UI::Color& color)
void Tab::SetRuntimeTabColor(const winrt::Windows::UI::Color& color)
{
_runtimeTabColor.emplace(color);
_RecalculateAndApplyTabColor();
}

// Method Description:
// - This function dispatches a function to the UI thread to recalculate
// what this tab's current background color should be. If a color is set,
// it will apply the given color to the tab's background. Otherwise, it
// will clear the tab's background color.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Tab::_RecalculateAndApplyTabColor()
{
auto weakThis{ get_weak() };

_tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis, color]() {
_tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis]() {
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))

std::optional<winrt::Windows::UI::Color> currentColor = tab->GetTabColor();
if (currentColor.has_value())
{
fontBrush.Color(winrt::Windows::UI::Colors::Black());
tab->_ApplyTabColor(currentColor.value());
}
else
{
fontBrush.Color(winrt::Windows::UI::Colors::White());
tab->_ClearTabBackgroundColor();
}

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->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush);

tab->_RefreshVisualState();

tab->_tabColor.emplace(color);
tab->_colorSelected(color);
});
}

// Method Description:
// Clear the custom color of the tab, if any
// - Applies the given color to the background of this tab's TabViewItem.
// - Sets the tab foreground color depending on the luminance of
// the background color
// - This method should only be called on the UI thread.
// Arguments:
// - color: the color the user picked for their tab
// Return Value:
// - <none>
void Tab::_ApplyTabColor(const winrt::Windows::UI::Color& color)
{
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
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush);
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush);
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), hoverTabBrush);
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush);
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForeground"), fontBrush);
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundSelected"), fontBrush);
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPointerOver"), fontBrush);
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderForegroundPressed"), fontBrush);
_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush);

_RefreshVisualState();

_colorSelected(color);
}

// Method Description:
// - Clear the custom runtime color of the tab, if any color is set. This
// will re-apply whatever the tab's base color should be (either the color
// from the control, the theme, or the default tab color.)
// Arguments:
// - <none>
// Return Value:
// - <none>
void Tab::ResetTabColor()
void Tab::ResetRuntimeTabColor()
{
auto weakThis{ get_weak() };
_runtimeTabColor.reset();
_RecalculateAndApplyTabColor();
}

_tabViewItem.Dispatcher().RunAsync(CoreDispatcherPriority::Normal, [weakThis]() {
auto ptrTab = weakThis.get();
if (!ptrTab)
return;
// Method Description:
// - Clear out any color we've set for the TabViewItem.
// - This method should only be called on the UI thread.
// Arguments:
// - <none>
// Return Value:
// - <none>
void Tab::_ClearTabBackgroundColor()
{
winrt::hstring keys[] = {
L"TabViewItemHeaderBackground",
L"TabViewItemHeaderBackgroundSelected",
L"TabViewItemHeaderBackgroundPointerOver",
L"TabViewItemHeaderForeground",
L"TabViewItemHeaderForegroundSelected",
L"TabViewItemHeaderForegroundPointerOver",
L"TabViewItemHeaderBackgroundPressed",
L"TabViewItemHeaderForegroundPressed",
L"TabViewButtonForegroundActiveTab"
};

auto tab{ ptrTab };
winrt::hstring keys[] = {
L"TabViewItemHeaderBackground",
L"TabViewItemHeaderBackgroundSelected",
L"TabViewItemHeaderBackgroundPointerOver",
L"TabViewItemHeaderForeground",
L"TabViewItemHeaderForegroundSelected",
L"TabViewItemHeaderForegroundPointerOver",
L"TabViewItemHeaderBackgroundPressed",
L"TabViewItemHeaderForegroundPressed",
L"TabViewButtonForegroundActiveTab"
};

// simply clear any of the colors in the tab's dict
for (auto keyString : keys)
// simply clear any of the colors in the tab's dict
for (auto keyString : keys)
{
auto key = winrt::box_value(keyString);
if (_tabViewItem.Resources().HasKey(key))
{
auto key = winrt::box_value(keyString);
if (tab->_tabViewItem.Resources().HasKey(key))
{
tab->_tabViewItem.Resources().Remove(key);
}
_tabViewItem.Resources().Remove(key);
}
}

tab->_RefreshVisualState();
tab->_tabColor.reset();
tab->_colorCleared();
});
_RefreshVisualState();
_colorCleared();
}

// Method Description:
Expand Down
Loading