-
Notifications
You must be signed in to change notification settings - Fork 358
/
Copy pathThemeAwareWindow.cs
183 lines (155 loc) · 6.35 KB
/
ThemeAwareWindow.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.UI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Windows.UI;
using Windows.UI.ViewManagement;
using WinUIEx;
namespace DevHome.Common.Windows;
/* This class encapsulates the theme-awareness logic, including support for custom chrome buttons.
* The main BarWindow derives from ThemeAwareWindow. All internal tool windows and any other owned windows
* must follow the theme of the main BarWindow. To achieve this:
* 1. Derive the tool window from ThemeAwareWindow.
* 2. Define a grid or panel for the icon and title (because this window sets ExtendsContentIntoTitleBar
* and HideIconAndSystemMenu).
* 3. Add the tool window to the list of related windows for the BarWindow by calling AddRelatedWindow.
* 4. Remove the tool window from the list of related windows for the BarWindow when the tool window is closed.
* 5. See ClipboardMonitoringWindow for an example.
*/
public class ThemeAwareWindow : WindowEx
{
private readonly SolidColorBrush _darkModeActiveCaptionBrush;
private readonly SolidColorBrush _darkModeInactiveCaptionBrush;
private readonly SolidColorBrush _nonDarkModeActiveCaptionBrush;
private readonly SolidColorBrush _nonDarkModeInactiveCaptionBrush;
private readonly UISettings _uiSettings = new();
private readonly DispatcherQueue _dispatcher;
private WindowActivationState _currentActivationState = WindowActivationState.Deactivated;
private List<ThemeAwareWindow> RelatedWindows { get; set; } = [];
protected List<Button> CustomTitleBarButtons { get; private set; } = [];
protected ElementTheme Theme { get; set; }
public ThemeAwareWindow()
{
_dispatcher = DispatcherQueue.GetForCurrentThread();
// Precreate the brushes for the caption buttons.
// In Dark Mode, the active state is white, and the inactive state is translucent white.
// In Light Mode, the active state is black, and the inactive state is translucent black.
var color = Colors.White;
_darkModeActiveCaptionBrush = new SolidColorBrush(color);
color.A = 0x66;
_darkModeInactiveCaptionBrush = new SolidColorBrush(color);
color = Colors.Black;
_nonDarkModeActiveCaptionBrush = new SolidColorBrush(color);
color.A = 0x66;
_nonDarkModeInactiveCaptionBrush = new SolidColorBrush(color);
ExtendsContentIntoTitleBar = true;
AppWindow.TitleBar.IconShowOptions = IconShowOptions.HideIconAndSystemMenu;
Closed += ThemeAwareWindow_Closed;
Activated += Window_Activated;
_uiSettings.ColorValuesChanged += UiSettings_ColorValuesChanged;
}
// Invoked when the user changes system-wide personalization settings.
private void UiSettings_ColorValuesChanged(UISettings sender, object args)
{
_dispatcher.TryEnqueue(ApplySystemThemeToCaptionButtons);
}
private void ThemeAwareWindow_Closed(object sender, WindowEventArgs args)
{
Activated -= Window_Activated;
_uiSettings.ColorValuesChanged -= UiSettings_ColorValuesChanged;
}
protected void Window_Activated(object sender, WindowActivatedEventArgs args)
{
// This follows the design guidance of dimming our title bar elements when the window isn't activated.
// https://learn.microsoft.com/en-us/windows/apps/develop/title-bar#dim-the-title-bar-when-the-window-is-inactive
_currentActivationState = args.WindowActivationState;
if (CustomTitleBarButtons.Count > 0)
{
UpdateCustomTitleBarButtonsTextColor();
}
}
// Invoked when the user changes theme preference in this app's settings.
protected void SetRequestedTheme(ElementTheme theme)
{
Theme = theme;
if (Content is FrameworkElement rootElement)
{
rootElement.RequestedTheme = theme;
if (theme == ElementTheme.Dark)
{
SetCaptionButtonColors(Colors.White);
}
else if (theme == ElementTheme.Light)
{
SetCaptionButtonColors(Colors.Black);
}
else
{
ApplySystemThemeToCaptionButtons();
}
}
foreach (var window in RelatedWindows)
{
window.SetRequestedTheme(theme);
}
}
// AppWindow.TitleBar doesn't update caption button colors correctly when changed while app is running.
// https://task.ms/44172495
internal void ApplySystemThemeToCaptionButtons()
{
if (Content is FrameworkElement rootElement)
{
Color foregroundColor;
if (rootElement.ActualTheme == ElementTheme.Dark)
{
foregroundColor = Colors.White;
}
else
{
foregroundColor = Colors.Black;
}
SetCaptionButtonColors(foregroundColor);
}
}
internal void SetCaptionButtonColors(Color color)
{
AppWindow.TitleBar.ButtonForegroundColor = color;
if (CustomTitleBarButtons.Count > 0)
{
UpdateCustomTitleBarButtonsTextColor();
}
}
internal void UpdateCustomTitleBarButtonsTextColor()
{
var rootElement = Content as FrameworkElement;
Debug.Assert(rootElement != null, "Expected Content to be a FrameworkElement");
foreach (var button in CustomTitleBarButtons)
{
if (_currentActivationState == WindowActivationState.Deactivated)
{
var brush = (rootElement.ActualTheme == ElementTheme.Dark) ? _darkModeInactiveCaptionBrush : _nonDarkModeInactiveCaptionBrush;
button.Foreground = brush;
}
else
{
var brush = (rootElement.ActualTheme == ElementTheme.Dark) ? _darkModeActiveCaptionBrush : _nonDarkModeActiveCaptionBrush;
button.Foreground = brush;
}
}
}
public void AddRelatedWindow(ThemeAwareWindow window)
{
RelatedWindows.Add(window);
window.SetRequestedTheme(Theme);
}
public void RemoveRelatedWindow(ThemeAwareWindow window)
{
RelatedWindows.Remove(window);
}
}