From d14747ff2db6935e04828bff19160daead11f486 Mon Sep 17 00:00:00 2001 From: William Zhang <72227515+Aliremu@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:18:07 -0500 Subject: [PATCH] Dim the caption buttons for unfocused windows (#19668) This PR closes #12632 by dimming the caption controls in the titlebar when unfocused. Leaves them intact when in high contrast mode. The color used is borrowed from PowerToys' unfocused color. ## Validation Steps Performed Tested by hovering and unhovering in focused and unfocused states, as well as in high contrast mode. States are consistent and applied correctly. I did notice that clicking on the titlebar wouldn't put it into focus unless I also moved my mouse though, but that is also present in the release branch. Closes #12632 (cherry picked from commit 8d94edd7b0445aa2221b1d1c7576b8b9dfd4797c) Service-Card-Id: PVTI_lADOAF3p4s4AxadtzgkC_KQ Service-Version: 1.23 --- .github/actions/spelling/expect/expect.txt | 1 + .../TerminalApp/MinMaxCloseControl.cpp | 49 +++++++++++++------ src/cascadia/TerminalApp/MinMaxCloseControl.h | 7 +++ .../TerminalApp/MinMaxCloseControl.idl | 1 + .../TerminalApp/MinMaxCloseControl.xaml | 33 +++++++++++++ src/cascadia/TerminalApp/TitlebarControl.cpp | 10 ++++ src/cascadia/TerminalApp/TitlebarControl.h | 3 ++ src/cascadia/TerminalApp/TitlebarControl.idl | 1 + .../WindowsTerminal/NonClientIslandWindow.cpp | 6 +++ 9 files changed, 96 insertions(+), 15 deletions(-) diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 1672346589..901b8b324a 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1121,6 +1121,7 @@ Mypair Myval NAMELENGTH namestream +NCACTIVATE NCCALCSIZE NCCREATE NCLBUTTONDOWN diff --git a/src/cascadia/TerminalApp/MinMaxCloseControl.cpp b/src/cascadia/TerminalApp/MinMaxCloseControl.cpp index 541f7351ae..4a984d2896 100644 --- a/src/cascadia/TerminalApp/MinMaxCloseControl.cpp +++ b/src/cascadia/TerminalApp/MinMaxCloseControl.cpp @@ -89,6 +89,18 @@ namespace winrt::TerminalApp::implementation CloseClick.raise(*this, e); } + bool MinMaxCloseControl::Focused() const + { + return _focused; + } + + void MinMaxCloseControl::Focused(bool focused) + { + _focused = focused; + + ReleaseButtons(); + } + void MinMaxCloseControl::SetWindowVisualState(WindowVisualState visualState) { // Look up the heights we should use for the caption buttons from our @@ -170,25 +182,25 @@ namespace winrt::TerminalApp::implementation // animate the fade in/out transition between colors. case CaptionButton::Minimize: VisualStateManager::GoToState(MinimizeButton(), L"PointerOver", true); - VisualStateManager::GoToState(MaximizeButton(), L"Normal", true); - VisualStateManager::GoToState(CloseButton(), L"Normal", true); + VisualStateManager::GoToState(MaximizeButton(), _normalState(), true); + VisualStateManager::GoToState(CloseButton(), _normalState(), true); _displayToolTip->Run(MinimizeButton()); closeToolTipForButton(MaximizeButton()); closeToolTipForButton(CloseButton()); break; case CaptionButton::Maximize: - VisualStateManager::GoToState(MinimizeButton(), L"Normal", true); + VisualStateManager::GoToState(MinimizeButton(), _normalState(), true); VisualStateManager::GoToState(MaximizeButton(), L"PointerOver", true); - VisualStateManager::GoToState(CloseButton(), L"Normal", true); + VisualStateManager::GoToState(CloseButton(), _normalState(), true); closeToolTipForButton(MinimizeButton()); _displayToolTip->Run(MaximizeButton()); closeToolTipForButton(CloseButton()); break; case CaptionButton::Close: - VisualStateManager::GoToState(MinimizeButton(), L"Normal", true); - VisualStateManager::GoToState(MaximizeButton(), L"Normal", true); + VisualStateManager::GoToState(MinimizeButton(), _normalState(), true); + VisualStateManager::GoToState(MaximizeButton(), _normalState(), true); VisualStateManager::GoToState(CloseButton(), L"PointerOver", true); closeToolTipForButton(MinimizeButton()); @@ -211,17 +223,17 @@ namespace winrt::TerminalApp::implementation { case CaptionButton::Minimize: VisualStateManager::GoToState(MinimizeButton(), L"Pressed", true); - VisualStateManager::GoToState(MaximizeButton(), L"Normal", true); - VisualStateManager::GoToState(CloseButton(), L"Normal", true); + VisualStateManager::GoToState(MaximizeButton(), _normalState(), true); + VisualStateManager::GoToState(CloseButton(), _normalState(), true); break; case CaptionButton::Maximize: - VisualStateManager::GoToState(MinimizeButton(), L"Normal", true); + VisualStateManager::GoToState(MinimizeButton(), _normalState(), true); VisualStateManager::GoToState(MaximizeButton(), L"Pressed", true); - VisualStateManager::GoToState(CloseButton(), L"Normal", true); + VisualStateManager::GoToState(CloseButton(), _normalState(), true); break; case CaptionButton::Close: - VisualStateManager::GoToState(MinimizeButton(), L"Normal", true); - VisualStateManager::GoToState(MaximizeButton(), L"Normal", true); + VisualStateManager::GoToState(MinimizeButton(), _normalState(), true); + VisualStateManager::GoToState(MaximizeButton(), _normalState(), true); VisualStateManager::GoToState(CloseButton(), L"Pressed", true); break; } @@ -234,9 +246,9 @@ namespace winrt::TerminalApp::implementation void MinMaxCloseControl::ReleaseButtons() { _displayToolTip->Run(nullptr); - VisualStateManager::GoToState(MinimizeButton(), L"Normal", true); - VisualStateManager::GoToState(MaximizeButton(), L"Normal", true); - VisualStateManager::GoToState(CloseButton(), L"Normal", true); + VisualStateManager::GoToState(MinimizeButton(), _normalState(), true); + VisualStateManager::GoToState(MaximizeButton(), _normalState(), true); + VisualStateManager::GoToState(CloseButton(), _normalState(), true); closeToolTipForButton(MinimizeButton()); closeToolTipForButton(MaximizeButton()); @@ -244,4 +256,11 @@ namespace winrt::TerminalApp::implementation _lastPressedButton = std::nullopt; } + + const winrt::param::hstring& MinMaxCloseControl::_normalState() const + { + static const winrt::param::hstring normal = L"Normal"; + static const winrt::param::hstring unfocused = L"Unfocused"; + return (_focused ? normal : unfocused); + } } diff --git a/src/cascadia/TerminalApp/MinMaxCloseControl.h b/src/cascadia/TerminalApp/MinMaxCloseControl.h index 4a53674174..007de27b18 100644 --- a/src/cascadia/TerminalApp/MinMaxCloseControl.h +++ b/src/cascadia/TerminalApp/MinMaxCloseControl.h @@ -21,6 +21,9 @@ namespace winrt::TerminalApp::implementation void PressButton(CaptionButton button); void ReleaseButtons(); + bool Focused() const; + void Focused(bool focused); + void _MinimizeClick(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::UI::Xaml::RoutedEventArgs& e); void _MaximizeClick(const winrt::Windows::Foundation::IInspectable& sender, @@ -32,8 +35,12 @@ namespace winrt::TerminalApp::implementation til::typed_event MaximizeClick; til::typed_event CloseClick; + bool _focused{ false }; std::shared_ptr> _displayToolTip{ nullptr }; std::optional _lastPressedButton{ std::nullopt }; + + private: + const winrt::param::hstring& _normalState() const; }; } diff --git a/src/cascadia/TerminalApp/MinMaxCloseControl.idl b/src/cascadia/TerminalApp/MinMaxCloseControl.idl index 5e3cc77fe2..82dedf1cc2 100644 --- a/src/cascadia/TerminalApp/MinMaxCloseControl.idl +++ b/src/cascadia/TerminalApp/MinMaxCloseControl.idl @@ -14,6 +14,7 @@ namespace TerminalApp void HoverButton(CaptionButton button); void PressButton(CaptionButton button); void ReleaseButtons(); + Boolean Focused { get; set; }; event Windows.Foundation.TypedEventHandler MinimizeClick; event Windows.Foundation.TypedEventHandler MaximizeClick; diff --git a/src/cascadia/TerminalApp/MinMaxCloseControl.xaml b/src/cascadia/TerminalApp/MinMaxCloseControl.xaml index 4bc516bc31..d5cee6b937 100644 --- a/src/cascadia/TerminalApp/MinMaxCloseControl.xaml +++ b/src/cascadia/TerminalApp/MinMaxCloseControl.xaml @@ -32,6 +32,10 @@ ResourceKey="SystemControlForegroundBaseHighBrush" /> + + Transparent @@ -66,6 +70,10 @@ ResourceKey="SystemControlForegroundBaseHighBrush" /> + + Transparent @@ -103,6 +111,10 @@ Color="{ThemeResource SystemColorHighlightTextColor}" /> + + + + + + + + + @@ -198,6 +224,13 @@ + + + + + + + diff --git a/src/cascadia/TerminalApp/TitlebarControl.cpp b/src/cascadia/TerminalApp/TitlebarControl.cpp index 1ed82f39ca..22247333fc 100644 --- a/src/cascadia/TerminalApp/TitlebarControl.cpp +++ b/src/cascadia/TerminalApp/TitlebarControl.cpp @@ -54,6 +54,16 @@ namespace winrt::TerminalApp::implementation return static_cast(minMaxCloseWidth) / 3.0f; } + bool TitlebarControl::Focused() + { + return MinMaxCloseControl().Focused(); + } + + void TitlebarControl::Focused(bool focused) + { + MinMaxCloseControl().Focused(focused); + } + IInspectable TitlebarControl::Content() { return ContentRoot().Content(); diff --git a/src/cascadia/TerminalApp/TitlebarControl.h b/src/cascadia/TerminalApp/TitlebarControl.h index 1008253ba8..1b0e2593fe 100644 --- a/src/cascadia/TerminalApp/TitlebarControl.h +++ b/src/cascadia/TerminalApp/TitlebarControl.h @@ -17,6 +17,9 @@ namespace winrt::TerminalApp::implementation void ReleaseButtons(); float CaptionButtonWidth(); + bool Focused(); + void Focused(bool focused); + IInspectable Content(); void Content(IInspectable content); diff --git a/src/cascadia/TerminalApp/TitlebarControl.idl b/src/cascadia/TerminalApp/TitlebarControl.idl index 8f934fae95..6cdae62dd5 100644 --- a/src/cascadia/TerminalApp/TitlebarControl.idl +++ b/src/cascadia/TerminalApp/TitlebarControl.idl @@ -29,6 +29,7 @@ namespace TerminalApp void ClickButton(CaptionButton button); void ReleaseButtons(); Single CaptionButtonWidth { get; }; + Boolean Focused { get; set; }; IInspectable Content; Windows.UI.Xaml.Controls.Border DragBar { get; }; diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index 414e4043f8..def2562742 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -958,6 +958,12 @@ void NonClientIslandWindow::_UpdateFrameMargins() const noexcept { switch (message) { + case WM_NCACTIVATE: + { + const bool activated = LOWORD(wParam) != 0; + _titlebar.Focused(activated); + break; + } case WM_SETCURSOR: return _OnSetCursor(wParam, lParam); case WM_DISPLAYCHANGE: