Add support for per-profile tab colors (#7162)
This PR adds support for per-profile tab colors, in accordance with #7134. This adds a single `tabColor` property, that when set, specifies the background color for profile's tab. This color can be overridden by the color picker, and clearing the color with the color picker will revert to this default color set for the tab. * Full theming is covered in #3327 & #5772 Validation: Played with setting this color, both on launch and via hot-reload Specified in #7134 Closes #1337
This commit is contained in:
parent
60b44c856e
commit
4e0f31337d
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -117,6 +117,7 @@ private:
|
|||
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;
|
||||
|
|
|
@ -54,6 +54,7 @@ namespace winrt::TerminalApp::implementation
|
|||
});
|
||||
|
||||
_UpdateTitle();
|
||||
_RecalculateAndApplyTabColor();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -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:
|
||||
|
@ -479,6 +490,7 @@ namespace winrt::TerminalApp::implementation
|
|||
if (tab && sender != tab->_activePane)
|
||||
{
|
||||
tab->_UpdateActivePane(sender);
|
||||
tab->_RecalculateAndApplyTabColor();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -706,79 +718,57 @@ 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
|
||||
// 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)
|
||||
{
|
||||
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->_tabViewItem.Resources().Insert(winrt::box_value(L"TabViewButtonForegroundActiveTab"), fontBrush);
|
||||
|
||||
tab->_RefreshVisualState();
|
||||
|
||||
tab->_tabColor.emplace(color);
|
||||
tab->_colorSelected(color);
|
||||
});
|
||||
_runtimeTabColor.emplace(color);
|
||||
_RecalculateAndApplyTabColor();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// Clear the custom color of the tab, if any
|
||||
// the background color
|
||||
// - 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::ResetTabColor()
|
||||
void Tab::_RecalculateAndApplyTabColor()
|
||||
{
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
|
@ -788,34 +778,121 @@ namespace winrt::TerminalApp::implementation
|
|||
return;
|
||||
|
||||
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)
|
||||
std::optional<winrt::Windows::UI::Color> currentColor = tab->GetTabColor();
|
||||
if (currentColor.has_value())
|
||||
{
|
||||
auto key = winrt::box_value(keyString);
|
||||
if (tab->_tabViewItem.Resources().HasKey(key))
|
||||
{
|
||||
tab->_tabViewItem.Resources().Remove(key);
|
||||
}
|
||||
tab->_ApplyTabColor(currentColor.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
tab->_ClearTabBackgroundColor();
|
||||
}
|
||||
|
||||
tab->_RefreshVisualState();
|
||||
tab->_tabColor.reset();
|
||||
tab->_colorCleared();
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - 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::ResetRuntimeTabColor()
|
||||
{
|
||||
_runtimeTabColor.reset();
|
||||
_RecalculateAndApplyTabColor();
|
||||
}
|
||||
|
||||
// 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"
|
||||
};
|
||||
|
||||
// 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))
|
||||
{
|
||||
_tabViewItem.Resources().Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
_RefreshVisualState();
|
||||
_colorCleared();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Display the tab color picker at the location of the TabViewItem for this tab.
|
||||
// Arguments:
|
||||
|
|
|
@ -57,8 +57,8 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
std::optional<winrt::Windows::UI::Color> GetTabColor();
|
||||
|
||||
void SetTabColor(const winrt::Windows::UI::Color& color);
|
||||
void ResetTabColor();
|
||||
void SetRuntimeTabColor(const winrt::Windows::UI::Color& color);
|
||||
void ResetRuntimeTabColor();
|
||||
void ActivateColorPicker();
|
||||
|
||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||
|
@ -75,7 +75,8 @@ namespace winrt::TerminalApp::implementation
|
|||
std::shared_ptr<Pane> _activePane{ nullptr };
|
||||
winrt::hstring _lastIconPath{};
|
||||
winrt::TerminalApp::ColorPickupFlyout _tabColorPickup{};
|
||||
std::optional<winrt::Windows::UI::Color> _tabColor{};
|
||||
std::optional<winrt::Windows::UI::Color> _themeTabColor{};
|
||||
std::optional<winrt::Windows::UI::Color> _runtimeTabColor{};
|
||||
|
||||
bool _focused{ false };
|
||||
winrt::Microsoft::UI::Xaml::Controls::TabViewItem _tabViewItem{ nullptr };
|
||||
|
@ -102,6 +103,10 @@ namespace winrt::TerminalApp::implementation
|
|||
winrt::fire_and_forget _UpdateTitle();
|
||||
void _ConstructTabRenameBox(const winrt::hstring& tabText);
|
||||
|
||||
void _RecalculateAndApplyTabColor();
|
||||
void _ApplyTabColor(const winrt::Windows::UI::Color& color);
|
||||
void _ClearTabBackgroundColor();
|
||||
|
||||
friend class ::TerminalAppLocalTests::TabTests;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1818,17 +1818,6 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
// Raise an event that our title changed
|
||||
_titleChangeHandlers(*this, tab->GetActiveTitle());
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
|
|
@ -54,6 +54,8 @@ namespace winrt::TerminalApp::implementation
|
|||
GETSET_PROPERTY(hstring, WordDelimiters, DEFAULT_WORD_DELIMITERS);
|
||||
GETSET_PROPERTY(bool, CopyOnSelect, false);
|
||||
|
||||
GETSET_PROPERTY(Windows::Foundation::IReference<uint32_t>, TabColor, nullptr);
|
||||
|
||||
// ------------------------ End of Core Settings -----------------------
|
||||
|
||||
GETSET_PROPERTY(hstring, ProfileName);
|
||||
|
|
|
@ -82,6 +82,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||
auto pfnTitleChanged = std::bind(&TermControl::_TerminalTitleChanged, this, std::placeholders::_1);
|
||||
_terminal->SetTitleChangedCallback(pfnTitleChanged);
|
||||
|
||||
auto pfnTabColorChanged = std::bind(&TermControl::_TerminalTabColorChanged, this, std::placeholders::_1);
|
||||
_terminal->SetTabColorChangedCallback(pfnTabColorChanged);
|
||||
|
||||
auto pfnBackgroundColorChanged = std::bind(&TermControl::_BackgroundColorChanged, this, std::placeholders::_1);
|
||||
_terminal->SetBackgroundCallback(pfnBackgroundColorChanged);
|
||||
|
||||
|
@ -2051,6 +2054,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||
{
|
||||
_titleChangedHandlers(winrt::hstring{ wstr });
|
||||
}
|
||||
void TermControl::_TerminalTabColorChanged(const std::optional<til::color> /*color*/)
|
||||
{
|
||||
_TabColorChangedHandlers(*this, nullptr);
|
||||
}
|
||||
|
||||
void TermControl::_CopyToClipboard(const std::wstring_view& wstr)
|
||||
{
|
||||
|
@ -2821,6 +2828,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||
_renderer->ResetErrorStateAndResume();
|
||||
}
|
||||
|
||||
Windows::Foundation::IReference<winrt::Windows::UI::Color> TermControl::TabColor() noexcept
|
||||
{
|
||||
auto coreColor = _terminal->GetTabColor();
|
||||
return coreColor.has_value() ? Windows::Foundation::IReference<winrt::Windows::UI::Color>(coreColor.value()) : nullptr;
|
||||
}
|
||||
|
||||
// -------------------------------- 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.
|
||||
|
|
|
@ -107,6 +107,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||
const winrt::hstring& padding,
|
||||
const uint32_t dpi);
|
||||
|
||||
Windows::Foundation::IReference<winrt::Windows::UI::Color> TabColor() noexcept;
|
||||
|
||||
// clang-format off
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
DECLARE_EVENT(TitleChanged, _titleChangedHandlers, TerminalControl::TitleChangedEventArgs);
|
||||
|
@ -118,6 +120,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||
|
||||
TYPED_EVENT(ConnectionStateChanged, TerminalControl::TermControl, IInspectable);
|
||||
TYPED_EVENT(Initialized, TerminalControl::TermControl, Windows::UI::Xaml::RoutedEventArgs);
|
||||
TYPED_EVENT(TabColorChanged, IInspectable, IInspectable);
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
|
@ -219,6 +222,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||
void _DoResizeUnderLock(const double newWidth, const double newHeight);
|
||||
void _RefreshSizeUnderLock();
|
||||
void _TerminalTitleChanged(const std::wstring_view& wstr);
|
||||
void _TerminalTabColorChanged(const std::optional<til::color> color);
|
||||
void _CopyToClipboard(const std::wstring_view& wstr);
|
||||
void _TerminalScrollPositionChanged(const int viewTop, const int viewHeight, const int bufferSize);
|
||||
void _TerminalCursorPositionChanged();
|
||||
|
|
|
@ -70,5 +70,8 @@ namespace Microsoft.Terminal.TerminalControl
|
|||
void ResetFontSize();
|
||||
|
||||
void ToggleRetroEffect();
|
||||
|
||||
Windows.Foundation.IReference<Windows.UI.Color> TabColor { get; };
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> TabColorChanged;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ namespace Microsoft.Terminal.TerminalControl
|
|||
String WordDelimiters;
|
||||
|
||||
Boolean ForceVTInput;
|
||||
|
||||
Windows.Foundation.IReference<UInt32> TabColor;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -147,6 +147,19 @@ void Terminal::UpdateSettings(ICoreSettings settings)
|
|||
|
||||
_terminalInput->ForceDisableWin32InputMode(settings.ForceVTInput());
|
||||
|
||||
if (settings.TabColor() == nullptr)
|
||||
{
|
||||
_tabColor = std::nullopt;
|
||||
}
|
||||
else
|
||||
{
|
||||
_tabColor = til::color(settings.TabColor().Value() | 0xff000000);
|
||||
}
|
||||
if (_pfnTabColorChanged)
|
||||
{
|
||||
_pfnTabColorChanged(_tabColor);
|
||||
}
|
||||
|
||||
// TODO:MSFT:21327402 - if HistorySize has changed, resize the buffer so we
|
||||
// have a smaller scrollback. We should do this carefully - if the new buffer
|
||||
// size is smaller than where the mutable viewport currently is, we'll want
|
||||
|
@ -913,6 +926,11 @@ void Terminal::SetTitleChangedCallback(std::function<void(const std::wstring_vie
|
|||
_pfnTitleChanged.swap(pfn);
|
||||
}
|
||||
|
||||
void Terminal::SetTabColorChangedCallback(std::function<void(const std::optional<til::color>)> pfn) noexcept
|
||||
{
|
||||
_pfnTabColorChanged.swap(pfn);
|
||||
}
|
||||
|
||||
void Terminal::SetCopyToClipboardCallback(std::function<void(const std::wstring_view&)> pfn) noexcept
|
||||
{
|
||||
_pfnCopyToClipboard.swap(pfn);
|
||||
|
@ -969,3 +987,8 @@ bool Terminal::IsCursorBlinkingAllowed() const noexcept
|
|||
const auto& cursor = _buffer->GetCursor();
|
||||
return cursor.IsBlinkingAllowed();
|
||||
}
|
||||
|
||||
const std::optional<til::color> Terminal::GetTabColor() const noexcept
|
||||
{
|
||||
return _tabColor;
|
||||
}
|
||||
|
|
|
@ -168,6 +168,7 @@ public:
|
|||
|
||||
void SetWriteInputCallback(std::function<void(std::wstring&)> pfn) noexcept;
|
||||
void SetTitleChangedCallback(std::function<void(const std::wstring_view&)> pfn) noexcept;
|
||||
void SetTabColorChangedCallback(std::function<void(const std::optional<til::color>)> pfn) noexcept;
|
||||
void SetCopyToClipboardCallback(std::function<void(const std::wstring_view&)> pfn) noexcept;
|
||||
void SetScrollPositionChangedCallback(std::function<void(const int, const int, const int)> pfn) noexcept;
|
||||
void SetCursorPositionChangedCallback(std::function<void()> pfn) noexcept;
|
||||
|
@ -176,6 +177,8 @@ public:
|
|||
void SetCursorOn(const bool isOn);
|
||||
bool IsCursorBlinkingAllowed() const noexcept;
|
||||
|
||||
const std::optional<til::color> GetTabColor() const noexcept;
|
||||
|
||||
#pragma region TextSelection
|
||||
// These methods are defined in TerminalSelection.cpp
|
||||
enum class SelectionExpansionMode
|
||||
|
@ -199,12 +202,14 @@ private:
|
|||
std::function<void(const int, const int, const int)> _pfnScrollPositionChanged;
|
||||
std::function<void(const COLORREF)> _pfnBackgroundColorChanged;
|
||||
std::function<void()> _pfnCursorPositionChanged;
|
||||
std::function<void(const std::optional<til::color>)> _pfnTabColorChanged;
|
||||
|
||||
std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine;
|
||||
std::unique_ptr<::Microsoft::Console::VirtualTerminal::TerminalInput> _terminalInput;
|
||||
|
||||
std::optional<std::wstring> _title;
|
||||
std::wstring _startingTitle;
|
||||
std::optional<til::color> _tabColor;
|
||||
|
||||
std::array<COLORREF, XTERM_COLOR_TABLE_SIZE> _colorTable;
|
||||
COLORREF _defaultFg;
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
#pragma once
|
||||
|
||||
#include <LibraryIncludes.h>
|
||||
#include "winrt/Windows.Foundation.h"
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
// output, and then flush the output of the VtEngine straight to the Terminal.
|
||||
|
||||
#include "pch.h"
|
||||
#include <wextestclass.h>
|
||||
#include "../../inc/consoletaeftemplates.hpp"
|
||||
#include "../../types/inc/Viewport.hpp"
|
||||
#include "../../types/inc/convert.hpp"
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "DefaultSettings.h"
|
||||
|
||||
#include <winrt/Microsoft.Terminal.TerminalControl.h>
|
||||
#include "../inc/cppwinrt_utils.h"
|
||||
|
||||
using namespace winrt::Microsoft::Terminal::TerminalControl;
|
||||
|
||||
|
@ -63,6 +64,8 @@ namespace TerminalCoreUnitTests
|
|||
// other unimplemented methods
|
||||
void SetColorTableEntry(int32_t /* index */, uint32_t /* value */) {}
|
||||
|
||||
GETSET_PROPERTY(winrt::Windows::Foundation::IReference<uint32_t>, TabColor, nullptr);
|
||||
|
||||
private:
|
||||
int32_t _historySize;
|
||||
int32_t _initialRows;
|
||||
|
|
|
@ -17,31 +17,40 @@ Author(s):
|
|||
|
||||
#pragma once
|
||||
|
||||
// <Conhost includes>
|
||||
// This header and define are needed so that the console host code can build in
|
||||
// this test binary.
|
||||
|
||||
// Block minwindef.h min/max macros to prevent <algorithm> conflict
|
||||
#define NOMINMAX
|
||||
|
||||
// This includes a lot of common headers needed by both the host and the propsheet
|
||||
// including: windows.h, winuser, ntstatus, assert, and the DDK
|
||||
#include "HostAndPropsheetIncludes.h"
|
||||
// </Conhost Includes>
|
||||
|
||||
#define BLOCK_TIL
|
||||
// This includes support libraries from the CRT, STL, WIL, and GSL
|
||||
#include "LibraryIncludes.h"
|
||||
|
||||
#ifdef BUILDING_INSIDE_WINIDE
|
||||
#define DbgRaiseAssertionFailure() __int2c()
|
||||
// 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 <ShellScalingApi.h>
|
||||
#include <wil/cppwinrt.h>
|
||||
#include <unknwn.h>
|
||||
#include <hstring.h>
|
||||
|
||||
// Comment to build against the private SDK.
|
||||
#define CON_BUILD_PUBLIC
|
||||
#include <WexTestClass.h>
|
||||
#include "consoletaeftemplates.hpp"
|
||||
|
||||
#ifdef CON_BUILD_PUBLIC
|
||||
#define CON_USERPRIVAPI_INDIRECT
|
||||
#define CON_DPIAPI_INDIRECT
|
||||
#endif
|
||||
#include <winrt/Windows.system.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
|
||||
// Manually include til after we include Windows.Foundation to give it winrt superpowers
|
||||
#include "til.h"
|
||||
|
||||
// <Conhost includes>
|
||||
// These are needed because the roundtrip tests included in this library also
|
||||
// re-use some conhost code that depends on these.
|
||||
|
||||
#include "conddkrefs.h"
|
||||
// From ntdef.h, but that can't be included or it'll fight over PROBE_ALIGNMENT and other such arch specific defs
|
||||
typedef _Return_type_success_(return >= 0) LONG NTSTATUS;
|
||||
/*lint -save -e624 */ // Don't complain about different typedefs.
|
||||
typedef NTSTATUS* PNTSTATUS;
|
||||
/*lint -restore */ // Resume checking for different typedefs.
|
||||
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
|
||||
// </Conhost Includes>
|
||||
|
|
|
@ -22,7 +22,6 @@ unit testing projects in the codebase without a bunch of overhead.
|
|||
|
||||
#define VERIFY_SUCCESS_NTSTATUS(x) VERIFY_IS_TRUE(NT_SUCCESS(x))
|
||||
|
||||
#include "precomp.h"
|
||||
#include "../host/globals.h"
|
||||
#include "../host/inputReadHandleData.h"
|
||||
#include "../buffer/out/CharRow.hpp"
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "til/bitmap.h"
|
||||
#include "til/u8u16convert.h"
|
||||
#include "til/spsc.h"
|
||||
#include "til/coalesce.h"
|
||||
|
||||
namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
||||
{
|
||||
|
|
61
src/inc/til/coalesce.h
Normal file
61
src/inc/til/coalesce.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace til
|
||||
{
|
||||
// Method Description:
|
||||
// - Base case provided to handle the last argument to coalesce_value<T...>()
|
||||
template<typename T>
|
||||
T coalesce_value(const T& base)
|
||||
{
|
||||
return base;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Base case provided to throw an assertion if you call coalesce_value(opt, opt, opt)
|
||||
template<typename T>
|
||||
T coalesce_value(const std::optional<T>& base)
|
||||
{
|
||||
static_assert(false, "coalesce_value must be passed a base non-optional value to be used if all optionals are empty");
|
||||
return T{};
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns the value from the first populated optional, or a base value if none were populated.
|
||||
template<typename T, typename... Ts>
|
||||
T coalesce_value(const std::optional<T>& t1, Ts&&... t2)
|
||||
{
|
||||
// Initially, I wanted to check "has_value" and short-circuit out so that we didn't
|
||||
// evaluate value_or for every single optional, but has_value/value emits exception handling
|
||||
// code that value_or doesn't. Less exception handling is cheaper than calling value_or a
|
||||
// few more times.
|
||||
return t1.value_or(coalesce_value(std::forward<Ts>(t2)...));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Base case provided to handle the last argument to coalesce_value<T...>()
|
||||
template<typename T>
|
||||
std::optional<T> coalesce(const std::optional<T>& base)
|
||||
{
|
||||
return base;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Base case provided to handle the last argument to coalesce_value<T...>(..., nullopt)
|
||||
template<typename T>
|
||||
std::optional<T> coalesce(const std::nullopt_t& base)
|
||||
{
|
||||
return base;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns the value from the first populated optional, or the last one (if none of the previous had a value)
|
||||
template<typename T, typename... Ts>
|
||||
std::optional<T> coalesce(const std::optional<T>& t1, Ts&&... t2)
|
||||
{
|
||||
return t1.has_value() ? t1 : coalesce(std::forward<Ts>(t2)...);
|
||||
}
|
||||
|
||||
}
|
84
src/til/ut_til/CoalesceTests.cpp
Normal file
84
src/til/ut_til/CoalesceTests.cpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "WexTestClass.h"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
|
||||
class CoalesceTests
|
||||
{
|
||||
TEST_CLASS(CoalesceTests);
|
||||
|
||||
TEST_METHOD(CoalesceFirstValue);
|
||||
TEST_METHOD(CoalesceMiddleValue);
|
||||
TEST_METHOD(CoalesceDefaultValue);
|
||||
|
||||
TEST_METHOD(CoalesceOrNotFirstValue);
|
||||
TEST_METHOD(CoalesceOrNotMiddleValue);
|
||||
TEST_METHOD(CoalesceOrNotDefaultValue);
|
||||
TEST_METHOD(CoalesceOrNotDefaultIsNullopt);
|
||||
};
|
||||
|
||||
void CoalesceTests::CoalesceFirstValue()
|
||||
{
|
||||
int result = til::coalesce_value(std::optional<int>(1),
|
||||
std::optional<int>(2),
|
||||
std::optional<int>(3),
|
||||
4);
|
||||
VERIFY_ARE_EQUAL(1, result);
|
||||
}
|
||||
void CoalesceTests::CoalesceMiddleValue()
|
||||
{
|
||||
int result = til::coalesce_value(std::optional<int>(std::nullopt),
|
||||
std::optional<int>(2),
|
||||
std::optional<int>(3),
|
||||
4);
|
||||
VERIFY_ARE_EQUAL(2, result);
|
||||
}
|
||||
void CoalesceTests::CoalesceDefaultValue()
|
||||
{
|
||||
int result = til::coalesce_value(std::optional<int>(std::nullopt),
|
||||
std::optional<int>(std::nullopt),
|
||||
std::optional<int>(std::nullopt),
|
||||
4);
|
||||
VERIFY_ARE_EQUAL(4, result);
|
||||
}
|
||||
|
||||
void CoalesceTests::CoalesceOrNotFirstValue()
|
||||
{
|
||||
std::optional<int> result = til::coalesce(std::optional<int>(1),
|
||||
std::optional<int>(2),
|
||||
std::optional<int>(3),
|
||||
std::optional<int>(4));
|
||||
VERIFY_IS_TRUE(result.has_value());
|
||||
VERIFY_ARE_EQUAL(1, result.value());
|
||||
}
|
||||
void CoalesceTests::CoalesceOrNotMiddleValue()
|
||||
{
|
||||
std::optional<int> result = til::coalesce(std::optional<int>(std::nullopt),
|
||||
std::optional<int>(2),
|
||||
std::optional<int>(3),
|
||||
std::optional<int>(4));
|
||||
VERIFY_IS_TRUE(result.has_value());
|
||||
VERIFY_ARE_EQUAL(2, result.value());
|
||||
}
|
||||
void CoalesceTests::CoalesceOrNotDefaultValue()
|
||||
{
|
||||
std::optional<int> result = til::coalesce(std::optional<int>(std::nullopt),
|
||||
std::optional<int>(std::nullopt),
|
||||
std::optional<int>(std::nullopt),
|
||||
std::optional<int>(4));
|
||||
VERIFY_IS_TRUE(result.has_value());
|
||||
VERIFY_ARE_EQUAL(4, result.value());
|
||||
}
|
||||
void CoalesceTests::CoalesceOrNotDefaultIsNullopt()
|
||||
{
|
||||
std::optional<int> result = til::coalesce(std::optional<int>(std::nullopt),
|
||||
std::optional<int>(std::nullopt),
|
||||
std::optional<int>(std::nullopt),
|
||||
std::optional<int>(std::nullopt));
|
||||
VERIFY_IS_FALSE(result.has_value());
|
||||
}
|
|
@ -18,6 +18,7 @@
|
|||
<ClCompile Include="RectangleTests.cpp" />
|
||||
<ClCompile Include="SizeTests.cpp" />
|
||||
<ClCompile Include="ColorTests.cpp" />
|
||||
<ClCompile Include="CoalesceTests.cpp" />
|
||||
<ClCompile Include="SomeTests.cpp" />
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
|
@ -36,4 +37,4 @@
|
|||
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
|
||||
<Import Project="$(SolutionDir)src\common.build.post.props" />
|
||||
<Import Project="$(SolutionDir)src\common.build.tests.props" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -96,32 +96,4 @@ namespace Microsoft::Console::Utils
|
|||
|
||||
GUID CreateV5Uuid(const GUID& namespaceGuid, const gsl::span<const gsl::byte> name);
|
||||
|
||||
// Method Description:
|
||||
// - Base case provided to handle the last argument to CoalesceOptionals<T...>()
|
||||
template<typename T>
|
||||
T CoalesceOptionals(const T& base)
|
||||
{
|
||||
return base;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Base case provided to throw an assertion if you call CoalesceOptionals(opt, opt, opt)
|
||||
template<typename T>
|
||||
T CoalesceOptionals(const std::optional<T>& base)
|
||||
{
|
||||
static_assert(false, "CoalesceOptionals must be passed a base non-optional value to be used if all optionals are empty");
|
||||
return T{};
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns the value from the first populated optional, or a base value if none were populated.
|
||||
template<typename T, typename... Ts>
|
||||
T CoalesceOptionals(const std::optional<T>& t1, Ts&&... t2)
|
||||
{
|
||||
// Initially, I wanted to check "has_value" and short-circuit out so that we didn't
|
||||
// evaluate value_or for every single optional, but has_value/value emits exception handling
|
||||
// code that value_or doesn't. Less exception handling is cheaper than calling value_or a
|
||||
// few more times.
|
||||
return t1.value_or(CoalesceOptionals(std::forward<Ts>(t2)...));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue