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
This commit is contained in:
William Zhang
2026-01-20 15:18:07 -05:00
committed by GitHub
parent e8971c80ae
commit 8d94edd7b0
9 changed files with 96 additions and 15 deletions

View File

@@ -1064,6 +1064,7 @@ Mypair
Myval Myval
NAMELENGTH NAMELENGTH
namestream namestream
NCACTIVATE
NCCALCSIZE NCCALCSIZE
NCCREATE NCCREATE
NCLBUTTONDOWN NCLBUTTONDOWN

View File

@@ -88,6 +88,18 @@ namespace winrt::TerminalApp::implementation
CloseClick.raise(*this, e); CloseClick.raise(*this, e);
} }
bool MinMaxCloseControl::Focused() const
{
return _focused;
}
void MinMaxCloseControl::Focused(bool focused)
{
_focused = focused;
ReleaseButtons();
}
void MinMaxCloseControl::SetWindowVisualState(WindowVisualState visualState) void MinMaxCloseControl::SetWindowVisualState(WindowVisualState visualState)
{ {
// Look up the heights we should use for the caption buttons from our // Look up the heights we should use for the caption buttons from our
@@ -169,25 +181,25 @@ namespace winrt::TerminalApp::implementation
// animate the fade in/out transition between colors. // animate the fade in/out transition between colors.
case CaptionButton::Minimize: case CaptionButton::Minimize:
VisualStateManager::GoToState(MinimizeButton(), L"PointerOver", true); VisualStateManager::GoToState(MinimizeButton(), L"PointerOver", true);
VisualStateManager::GoToState(MaximizeButton(), L"Normal", true); VisualStateManager::GoToState(MaximizeButton(), _normalState(), true);
VisualStateManager::GoToState(CloseButton(), L"Normal", true); VisualStateManager::GoToState(CloseButton(), _normalState(), true);
_displayToolTip->Run(MinimizeButton()); _displayToolTip->Run(MinimizeButton());
closeToolTipForButton(MaximizeButton()); closeToolTipForButton(MaximizeButton());
closeToolTipForButton(CloseButton()); closeToolTipForButton(CloseButton());
break; break;
case CaptionButton::Maximize: case CaptionButton::Maximize:
VisualStateManager::GoToState(MinimizeButton(), L"Normal", true); VisualStateManager::GoToState(MinimizeButton(), _normalState(), true);
VisualStateManager::GoToState(MaximizeButton(), L"PointerOver", true); VisualStateManager::GoToState(MaximizeButton(), L"PointerOver", true);
VisualStateManager::GoToState(CloseButton(), L"Normal", true); VisualStateManager::GoToState(CloseButton(), _normalState(), true);
closeToolTipForButton(MinimizeButton()); closeToolTipForButton(MinimizeButton());
_displayToolTip->Run(MaximizeButton()); _displayToolTip->Run(MaximizeButton());
closeToolTipForButton(CloseButton()); closeToolTipForButton(CloseButton());
break; break;
case CaptionButton::Close: case CaptionButton::Close:
VisualStateManager::GoToState(MinimizeButton(), L"Normal", true); VisualStateManager::GoToState(MinimizeButton(), _normalState(), true);
VisualStateManager::GoToState(MaximizeButton(), L"Normal", true); VisualStateManager::GoToState(MaximizeButton(), _normalState(), true);
VisualStateManager::GoToState(CloseButton(), L"PointerOver", true); VisualStateManager::GoToState(CloseButton(), L"PointerOver", true);
closeToolTipForButton(MinimizeButton()); closeToolTipForButton(MinimizeButton());
@@ -210,17 +222,17 @@ namespace winrt::TerminalApp::implementation
{ {
case CaptionButton::Minimize: case CaptionButton::Minimize:
VisualStateManager::GoToState(MinimizeButton(), L"Pressed", true); VisualStateManager::GoToState(MinimizeButton(), L"Pressed", true);
VisualStateManager::GoToState(MaximizeButton(), L"Normal", true); VisualStateManager::GoToState(MaximizeButton(), _normalState(), true);
VisualStateManager::GoToState(CloseButton(), L"Normal", true); VisualStateManager::GoToState(CloseButton(), _normalState(), true);
break; break;
case CaptionButton::Maximize: case CaptionButton::Maximize:
VisualStateManager::GoToState(MinimizeButton(), L"Normal", true); VisualStateManager::GoToState(MinimizeButton(), _normalState(), true);
VisualStateManager::GoToState(MaximizeButton(), L"Pressed", true); VisualStateManager::GoToState(MaximizeButton(), L"Pressed", true);
VisualStateManager::GoToState(CloseButton(), L"Normal", true); VisualStateManager::GoToState(CloseButton(), _normalState(), true);
break; break;
case CaptionButton::Close: case CaptionButton::Close:
VisualStateManager::GoToState(MinimizeButton(), L"Normal", true); VisualStateManager::GoToState(MinimizeButton(), _normalState(), true);
VisualStateManager::GoToState(MaximizeButton(), L"Normal", true); VisualStateManager::GoToState(MaximizeButton(), _normalState(), true);
VisualStateManager::GoToState(CloseButton(), L"Pressed", true); VisualStateManager::GoToState(CloseButton(), L"Pressed", true);
break; break;
} }
@@ -233,9 +245,9 @@ namespace winrt::TerminalApp::implementation
void MinMaxCloseControl::ReleaseButtons() void MinMaxCloseControl::ReleaseButtons()
{ {
_displayToolTip->Run(nullptr); _displayToolTip->Run(nullptr);
VisualStateManager::GoToState(MinimizeButton(), L"Normal", true); VisualStateManager::GoToState(MinimizeButton(), _normalState(), true);
VisualStateManager::GoToState(MaximizeButton(), L"Normal", true); VisualStateManager::GoToState(MaximizeButton(), _normalState(), true);
VisualStateManager::GoToState(CloseButton(), L"Normal", true); VisualStateManager::GoToState(CloseButton(), _normalState(), true);
closeToolTipForButton(MinimizeButton()); closeToolTipForButton(MinimizeButton());
closeToolTipForButton(MaximizeButton()); closeToolTipForButton(MaximizeButton());
@@ -243,4 +255,11 @@ namespace winrt::TerminalApp::implementation
_lastPressedButton = std::nullopt; _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);
}
} }

View File

@@ -21,6 +21,9 @@ namespace winrt::TerminalApp::implementation
void PressButton(CaptionButton button); void PressButton(CaptionButton button);
void ReleaseButtons(); void ReleaseButtons();
bool Focused() const;
void Focused(bool focused);
void _MinimizeClick(const winrt::Windows::Foundation::IInspectable& sender, void _MinimizeClick(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::UI::Xaml::RoutedEventArgs& e); const winrt::Windows::UI::Xaml::RoutedEventArgs& e);
void _MaximizeClick(const winrt::Windows::Foundation::IInspectable& sender, void _MaximizeClick(const winrt::Windows::Foundation::IInspectable& sender,
@@ -32,8 +35,12 @@ namespace winrt::TerminalApp::implementation
til::typed_event<TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs> MaximizeClick; til::typed_event<TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs> MaximizeClick;
til::typed_event<TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs> CloseClick; til::typed_event<TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs> CloseClick;
bool _focused{ false };
std::shared_ptr<ThrottledFunc<winrt::Windows::UI::Xaml::Controls::Button>> _displayToolTip{ nullptr }; std::shared_ptr<ThrottledFunc<winrt::Windows::UI::Xaml::Controls::Button>> _displayToolTip{ nullptr };
std::optional<CaptionButton> _lastPressedButton{ std::nullopt }; std::optional<CaptionButton> _lastPressedButton{ std::nullopt };
private:
const winrt::param::hstring& _normalState() const;
}; };
} }

View File

@@ -14,6 +14,7 @@ namespace TerminalApp
void HoverButton(CaptionButton button); void HoverButton(CaptionButton button);
void PressButton(CaptionButton button); void PressButton(CaptionButton button);
void ReleaseButtons(); void ReleaseButtons();
Boolean Focused { get; set; };
event Windows.Foundation.TypedEventHandler<MinMaxCloseControl, Windows.UI.Xaml.RoutedEventArgs> MinimizeClick; event Windows.Foundation.TypedEventHandler<MinMaxCloseControl, Windows.UI.Xaml.RoutedEventArgs> MinimizeClick;
event Windows.Foundation.TypedEventHandler<MinMaxCloseControl, Windows.UI.Xaml.RoutedEventArgs> MaximizeClick; event Windows.Foundation.TypedEventHandler<MinMaxCloseControl, Windows.UI.Xaml.RoutedEventArgs> MaximizeClick;

View File

@@ -32,6 +32,10 @@
ResourceKey="SystemControlForegroundBaseHighBrush" /> ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CaptionButtonForegroundPressed" <StaticResource x:Key="CaptionButtonForegroundPressed"
ResourceKey="SystemControlForegroundBaseHighBrush" /> ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CaptionButtonForegroundUnfocusedColor"
ResourceKey="TextFillColorDisabled" />
<SolidColorBrush x:Key="CaptionButtonForegroundUnfocused"
Color="{ThemeResource CaptionButtonForegroundUnfocusedColor}" />
<SolidColorBrush x:Key="CaptionButtonBackground" <SolidColorBrush x:Key="CaptionButtonBackground"
Color="Transparent" /> Color="Transparent" />
<Color x:Key="CaptionButtonBackgroundColor">Transparent</Color> <Color x:Key="CaptionButtonBackgroundColor">Transparent</Color>
@@ -66,6 +70,10 @@
ResourceKey="SystemControlForegroundBaseHighBrush" /> ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CaptionButtonForegroundPressed" <StaticResource x:Key="CaptionButtonForegroundPressed"
ResourceKey="SystemControlForegroundBaseHighBrush" /> ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="CaptionButtonForegroundUnfocusedColor"
ResourceKey="TextFillColorDisabled" />
<SolidColorBrush x:Key="CaptionButtonForegroundUnfocused"
Color="{ThemeResource CaptionButtonForegroundUnfocusedColor}" />
<SolidColorBrush x:Key="CaptionButtonBackground" <SolidColorBrush x:Key="CaptionButtonBackground"
Color="Transparent" /> Color="Transparent" />
<Color x:Key="CaptionButtonBackgroundColor">Transparent</Color> <Color x:Key="CaptionButtonBackgroundColor">Transparent</Color>
@@ -103,6 +111,10 @@
Color="{ThemeResource SystemColorHighlightTextColor}" /> Color="{ThemeResource SystemColorHighlightTextColor}" />
<SolidColorBrush x:Key="CaptionButtonForegroundPressed" <SolidColorBrush x:Key="CaptionButtonForegroundPressed"
Color="{ThemeResource SystemColorHighlightTextColor}" /> Color="{ThemeResource SystemColorHighlightTextColor}" />
<SolidColorBrush x:Key="CaptionButtonForegroundUnfocused"
Color="{ThemeResource SystemColorButtonTextColor}" />
<StaticResource x:Key="CaptionButtonForegroundUnfocusedColor"
ResourceKey="SystemColorButtonTextColor" />
<SolidColorBrush x:Key="CloseButtonBackgroundPointerOver" <SolidColorBrush x:Key="CloseButtonBackgroundPointerOver"
Color="{ThemeResource SystemColorHighlightColor}" /> Color="{ThemeResource SystemColorHighlightColor}" />
<SolidColorBrush x:Key="CloseButtonForegroundPointerOver" <SolidColorBrush x:Key="CloseButtonForegroundPointerOver"
@@ -189,6 +201,20 @@
Duration="0:0:0.1" /> Duration="0:0:0.1" />
</Storyboard> </Storyboard>
</VisualTransition> </VisualTransition>
<VisualTransition From="PointerOver"
To="Unfocused">
<Storyboard>
<ColorAnimation Storyboard.TargetName="ButtonBaseElement"
Storyboard.TargetProperty="(UIElement.Background).(SolidColorBrush.Color)"
To="{ThemeResource CaptionButtonBackgroundColor}"
Duration="0:0:0.15" />
<ColorAnimation Storyboard.TargetName="ButtonIcon"
Storyboard.TargetProperty="(UIElement.Foreground).(SolidColorBrush.Color)"
To="{ThemeResource CaptionButtonForegroundUnfocusedColor}"
Duration="0:0:0.1" />
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions> </VisualStateGroup.Transitions>
<VisualState x:Name="Normal"> <VisualState x:Name="Normal">
@@ -198,6 +224,13 @@
</VisualState.Setters> </VisualState.Setters>
</VisualState> </VisualState>
<VisualState x:Name="Unfocused">
<VisualState.Setters>
<Setter Target="ButtonBaseElement.Background" Value="{ThemeResource CaptionButtonBackground}" />
<Setter Target="ButtonIcon.Foreground" Value="{ThemeResource CaptionButtonForegroundUnfocused}" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOver"> <VisualState x:Name="PointerOver">
<VisualState.Setters> <VisualState.Setters>
<Setter Target="ButtonBaseElement.Background" Value="{ThemeResource CaptionButtonBackgroundPointerOver}" /> <Setter Target="ButtonBaseElement.Background" Value="{ThemeResource CaptionButtonBackgroundPointerOver}" />

View File

@@ -53,6 +53,16 @@ namespace winrt::TerminalApp::implementation
return static_cast<float>(minMaxCloseWidth) / 3.0f; return static_cast<float>(minMaxCloseWidth) / 3.0f;
} }
bool TitlebarControl::Focused()
{
return MinMaxCloseControl().Focused();
}
void TitlebarControl::Focused(bool focused)
{
MinMaxCloseControl().Focused(focused);
}
IInspectable TitlebarControl::Content() IInspectable TitlebarControl::Content()
{ {
return ContentRoot().Content(); return ContentRoot().Content();

View File

@@ -17,6 +17,9 @@ namespace winrt::TerminalApp::implementation
void ReleaseButtons(); void ReleaseButtons();
float CaptionButtonWidth(); float CaptionButtonWidth();
bool Focused();
void Focused(bool focused);
IInspectable Content(); IInspectable Content();
void Content(IInspectable content); void Content(IInspectable content);

View File

@@ -29,6 +29,7 @@ namespace TerminalApp
void ClickButton(CaptionButton button); void ClickButton(CaptionButton button);
void ReleaseButtons(); void ReleaseButtons();
Single CaptionButtonWidth { get; }; Single CaptionButtonWidth { get; };
Boolean Focused { get; set; };
IInspectable Content; IInspectable Content;
Windows.UI.Xaml.Controls.Border DragBar { get; }; Windows.UI.Xaml.Controls.Border DragBar { get; };

View File

@@ -958,6 +958,12 @@ void NonClientIslandWindow::_UpdateFrameMargins() const noexcept
{ {
switch (message) switch (message)
{ {
case WM_NCACTIVATE:
{
const bool activated = LOWORD(wParam) != 0;
_titlebar.Focused(activated);
break;
}
case WM_SETCURSOR: case WM_SETCURSOR:
return _OnSetCursor(wParam, lParam); return _OnSetCursor(wParam, lParam);
case WM_DISPLAYCHANGE: case WM_DISPLAYCHANGE: