diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 5d260ef94c4..71bea71051f 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -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); diff --git a/src/cascadia/TerminalApp/CascadiaSettings.cpp b/src/cascadia/TerminalApp/CascadiaSettings.cpp index 29c8d750d02..1206437256f 100644 --- a/src/cascadia/TerminalApp/CascadiaSettings.cpp +++ b/src/cascadia/TerminalApp/CascadiaSettings.cpp @@ -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: diff --git a/src/cascadia/TerminalApp/Profile.cpp b/src/cascadia/TerminalApp/Profile.cpp index 9829474297a..22e627ad896 100644 --- a/src/cascadia/TerminalApp/Profile.cpp +++ b/src/cascadia/TerminalApp/Profile.cpp @@ -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 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 diff --git a/src/cascadia/TerminalApp/Profile.h b/src/cascadia/TerminalApp/Profile.h index 422b139883e..a6794bbcc07 100644 --- a/src/cascadia/TerminalApp/Profile.h +++ b/src/cascadia/TerminalApp/Profile.h @@ -117,6 +117,7 @@ class TerminalApp::Profile final std::optional _selectionBackground; std::optional _cursorColor; std::optional _tabTitle; + std::optional _tabColor; bool _suppressApplicationTitle; int32_t _historySize; bool _snapOnInput; diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 5e7521bbdfe..77e3371104f 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -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,114 +718,179 @@ namespace winrt::TerminalApp::implementation // - The tab's color, if any std::optional Tab::GetTabColor() { - return _tabColor; + const auto currControlColor{ GetActiveTerminalControl().TabColor() }; + std::optional 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(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: // - - 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: + // - + // Return Value: + // - + 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 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: + // - + 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: // - // Return Value: // - - 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: + // - + // Return Value: + // - + 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: diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index 055d631c421..a53326b04f6 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -57,8 +57,8 @@ namespace winrt::TerminalApp::implementation std::optional 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); @@ -75,7 +75,8 @@ namespace winrt::TerminalApp::implementation std::shared_ptr _activePane{ nullptr }; winrt::hstring _lastIconPath{}; winrt::TerminalApp::ColorPickupFlyout _tabColorPickup{}; - std::optional _tabColor{}; + std::optional _themeTabColor{}; + std::optional _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; }; } diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index b2c859f3218..b19710a8194 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -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 color = tab->GetTabColor(); - if (color.has_value()) - { - _SetNonClientAreaColors(color.value()); - } - else - { - _ClearNonClientAreaColors(); - } } CATCH_LOG(); } diff --git a/src/cascadia/TerminalApp/TerminalSettings.h b/src/cascadia/TerminalApp/TerminalSettings.h index e3e2aec0bfc..53a4ab3201d 100644 --- a/src/cascadia/TerminalApp/TerminalSettings.h +++ b/src/cascadia/TerminalApp/TerminalSettings.h @@ -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, TabColor, nullptr); + // ------------------------ End of Core Settings ----------------------- GETSET_PROPERTY(hstring, ProfileName); diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index e99e0d2153d..4b7b64a7b15 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -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 /*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 TermControl::TabColor() noexcept + { + auto coreColor = _terminal->GetTabColor(); + return coreColor.has_value() ? Windows::Foundation::IReference(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. diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index ce9a8a49d2d..5b159fea38e 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -107,6 +107,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation const winrt::hstring& padding, const uint32_t dpi); + Windows::Foundation::IReference 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 color); void _CopyToClipboard(const std::wstring_view& wstr); void _TerminalScrollPositionChanged(const int viewTop, const int viewHeight, const int bufferSize); void _TerminalCursorPositionChanged(); diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index e729c38659c..89e4634e968 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -70,5 +70,8 @@ namespace Microsoft.Terminal.TerminalControl void ResetFontSize(); void ToggleRetroEffect(); + + Windows.Foundation.IReference TabColor { get; }; + event Windows.Foundation.TypedEventHandler TabColorChanged; } } diff --git a/src/cascadia/TerminalCore/ICoreSettings.idl b/src/cascadia/TerminalCore/ICoreSettings.idl index 99fe24e7930..de11ec04b05 100644 --- a/src/cascadia/TerminalCore/ICoreSettings.idl +++ b/src/cascadia/TerminalCore/ICoreSettings.idl @@ -34,6 +34,8 @@ namespace Microsoft.Terminal.TerminalControl String WordDelimiters; Boolean ForceVTInput; + + Windows.Foundation.IReference TabColor; }; } diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 6d4a4208567..f4a847cc884 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -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)> pfn) noexcept +{ + _pfnTabColorChanged.swap(pfn); +} + void Terminal::SetCopyToClipboardCallback(std::function 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 Terminal::GetTabColor() const noexcept +{ + return _tabColor; +} diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 351b7c7f8a5..78e0ead8952 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -168,6 +168,7 @@ class Microsoft::Terminal::Core::Terminal final : void SetWriteInputCallback(std::function pfn) noexcept; void SetTitleChangedCallback(std::function pfn) noexcept; + void SetTabColorChangedCallback(std::function)> pfn) noexcept; void SetCopyToClipboardCallback(std::function pfn) noexcept; void SetScrollPositionChangedCallback(std::function pfn) noexcept; void SetCursorPositionChangedCallback(std::function pfn) noexcept; @@ -176,6 +177,8 @@ class Microsoft::Terminal::Core::Terminal final : void SetCursorOn(const bool isOn); bool IsCursorBlinkingAllowed() const noexcept; + const std::optional GetTabColor() const noexcept; + #pragma region TextSelection // These methods are defined in TerminalSelection.cpp enum class SelectionExpansionMode @@ -199,12 +202,14 @@ class Microsoft::Terminal::Core::Terminal final : std::function _pfnScrollPositionChanged; std::function _pfnBackgroundColorChanged; std::function _pfnCursorPositionChanged; + std::function)> _pfnTabColorChanged; std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine; std::unique_ptr<::Microsoft::Console::VirtualTerminal::TerminalInput> _terminalInput; std::optional _title; std::wstring _startingTitle; + std::optional _tabColor; std::array _colorTable; COLORREF _defaultFg; diff --git a/src/cascadia/TerminalCore/pch.h b/src/cascadia/TerminalCore/pch.h index e03399daf91..58fe68df1da 100644 --- a/src/cascadia/TerminalCore/pch.h +++ b/src/cascadia/TerminalCore/pch.h @@ -4,3 +4,4 @@ #pragma once #include +#include "winrt/Windows.Foundation.h" diff --git a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp index 95812c957a8..a68aa213db1 100644 --- a/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp +++ b/src/cascadia/UnitTests_TerminalCore/ConptyRoundtripTests.cpp @@ -9,8 +9,6 @@ // output, and then flush the output of the VtEngine straight to the Terminal. #include "pch.h" -#include -#include "../../inc/consoletaeftemplates.hpp" #include "../../types/inc/Viewport.hpp" #include "../../types/inc/convert.hpp" diff --git a/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h b/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h index 8ddc223ee36..1a328c9d015 100644 --- a/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h +++ b/src/cascadia/UnitTests_TerminalCore/MockTermSettings.h @@ -6,6 +6,7 @@ #include "DefaultSettings.h" #include +#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, TabColor, nullptr); + private: int32_t _historySize; int32_t _initialRows; diff --git a/src/cascadia/UnitTests_TerminalCore/pch.h b/src/cascadia/UnitTests_TerminalCore/pch.h index b63aeadb8a0..c33ba50c236 100644 --- a/src/cascadia/UnitTests_TerminalCore/pch.h +++ b/src/cascadia/UnitTests_TerminalCore/pch.h @@ -17,31 +17,40 @@ Author(s): #pragma once -// -// 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 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" -// - +#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 +#include +#include +#include -// Comment to build against the private SDK. -#define CON_BUILD_PUBLIC +#include +#include "consoletaeftemplates.hpp" -#ifdef CON_BUILD_PUBLIC -#define CON_USERPRIVAPI_INDIRECT -#define CON_DPIAPI_INDIRECT -#endif +#include +#include +#include + +// Manually include til after we include Windows.Foundation to give it winrt superpowers +#include "til.h" + +// +// 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) +// diff --git a/src/inc/test/CommonState.hpp b/src/inc/test/CommonState.hpp index 2a4ddeab47e..03abae9a221 100644 --- a/src/inc/test/CommonState.hpp +++ b/src/inc/test/CommonState.hpp @@ -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" diff --git a/src/inc/til.h b/src/inc/til.h index e9504535708..36e10ad0e6f 100644 --- a/src/inc/til.h +++ b/src/inc/til.h @@ -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" { diff --git a/src/inc/til/coalesce.h b/src/inc/til/coalesce.h new file mode 100644 index 00000000000..24153869a67 --- /dev/null +++ b/src/inc/til/coalesce.h @@ -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() + template + 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 + T coalesce_value(const std::optional& 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 + T coalesce_value(const std::optional& 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(t2)...)); + } + + // Method Description: + // - Base case provided to handle the last argument to coalesce_value() + template + std::optional coalesce(const std::optional& base) + { + return base; + } + + // Method Description: + // - Base case provided to handle the last argument to coalesce_value(..., nullopt) + template + std::optional 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 + std::optional coalesce(const std::optional& t1, Ts&&... t2) + { + return t1.has_value() ? t1 : coalesce(std::forward(t2)...); + } + +} diff --git a/src/til/ut_til/CoalesceTests.cpp b/src/til/ut_til/CoalesceTests.cpp new file mode 100644 index 00000000000..76c2e9c31ab --- /dev/null +++ b/src/til/ut_til/CoalesceTests.cpp @@ -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(1), + std::optional(2), + std::optional(3), + 4); + VERIFY_ARE_EQUAL(1, result); +} +void CoalesceTests::CoalesceMiddleValue() +{ + int result = til::coalesce_value(std::optional(std::nullopt), + std::optional(2), + std::optional(3), + 4); + VERIFY_ARE_EQUAL(2, result); +} +void CoalesceTests::CoalesceDefaultValue() +{ + int result = til::coalesce_value(std::optional(std::nullopt), + std::optional(std::nullopt), + std::optional(std::nullopt), + 4); + VERIFY_ARE_EQUAL(4, result); +} + +void CoalesceTests::CoalesceOrNotFirstValue() +{ + std::optional result = til::coalesce(std::optional(1), + std::optional(2), + std::optional(3), + std::optional(4)); + VERIFY_IS_TRUE(result.has_value()); + VERIFY_ARE_EQUAL(1, result.value()); +} +void CoalesceTests::CoalesceOrNotMiddleValue() +{ + std::optional result = til::coalesce(std::optional(std::nullopt), + std::optional(2), + std::optional(3), + std::optional(4)); + VERIFY_IS_TRUE(result.has_value()); + VERIFY_ARE_EQUAL(2, result.value()); +} +void CoalesceTests::CoalesceOrNotDefaultValue() +{ + std::optional result = til::coalesce(std::optional(std::nullopt), + std::optional(std::nullopt), + std::optional(std::nullopt), + std::optional(4)); + VERIFY_IS_TRUE(result.has_value()); + VERIFY_ARE_EQUAL(4, result.value()); +} +void CoalesceTests::CoalesceOrNotDefaultIsNullopt() +{ + std::optional result = til::coalesce(std::optional(std::nullopt), + std::optional(std::nullopt), + std::optional(std::nullopt), + std::optional(std::nullopt)); + VERIFY_IS_FALSE(result.has_value()); +} diff --git a/src/til/ut_til/til.unit.tests.vcxproj b/src/til/ut_til/til.unit.tests.vcxproj index 82b7908bb7c..52a07246b81 100644 --- a/src/til/ut_til/til.unit.tests.vcxproj +++ b/src/til/ut_til/til.unit.tests.vcxproj @@ -18,6 +18,7 @@ + Create @@ -36,4 +37,4 @@ - \ No newline at end of file + diff --git a/src/types/inc/utils.hpp b/src/types/inc/utils.hpp index 5e22a25127e..05ba610e9d5 100644 --- a/src/types/inc/utils.hpp +++ b/src/types/inc/utils.hpp @@ -96,32 +96,4 @@ namespace Microsoft::Console::Utils GUID CreateV5Uuid(const GUID& namespaceGuid, const gsl::span name); - // Method Description: - // - Base case provided to handle the last argument to CoalesceOptionals() - template - T CoalesceOptionals(const T& base) - { - return base; - } - - // Method Description: - // - Base case provided to throw an assertion if you call CoalesceOptionals(opt, opt, opt) - template - T CoalesceOptionals(const std::optional& 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 - T CoalesceOptionals(const std::optional& 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(t2)...)); - } }