Repository navigation
Expand file tree
/
Copy pathMainWindow.xaml.cs
More file actions
231 lines (199 loc) · 11.3 KB
/
Copy pathMainWindow.xaml.cs
File metadata and controls
231 lines (199 loc) · 11.3 KB
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Imaging;
using Reframe.Core;
using Reframe.Services;
using Reframe.UI;
namespace Reframe;
public sealed partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Modern title bar: extend content into the title-bar area (Mica shows through to the top), with
// the Row0 drag region AppTitleDragRegion acting as the draggable title bar.
// The right-side system buttons (minimize/maximize/close) are kept automatically by the system;
// dragging / double-click-to-maximize is handled by SetTitleBar.
// A true two-row layout: Row0 is the physical title bar (custom-drawn hamburger + icon + title +
// drag region), and Row1 is the NavigationView.
// The custom hamburger is outside the drag region, so it receives Click normally (interactive
// controls inside a SetTitleBar element don't get input).
ExtendsContentIntoTitleBar = true;
SetTitleBar(AppTitleDragRegion);
InitTitleBarIcon();
InitLocalizedAccessibility();
// The title text greys out when deactivated and restores when activated (matching the Win11 Settings app).
Activated += OnActivated;
// Background material: chosen by config (Mica / Mica Alt / Acrylic), natively supported by
// WinAppSDK 1.8 + Win11, no fallback needed.
// The NavigationView Pane/content background brushes are set transparent in App.xaml so the
// material shows through; after the title bar is extended, Mica shows through there too.
ApplyBackdrop();
// Theme (dark mode): chosen by config (follow system / light / dark), tracking the system light/dark in real time.
ApplyTheme();
// Config change (Settings page changes the material / external config.json hot reload) → marshal
// back to the UI thread and re-apply. MainWindow's lifetime = the whole app, so don't unsubscribe.
ConfigService.Instance.Changed += OnConfigChanged;
// Top-left window + taskbar icon. Unpackaged, ApplicationIcon doesn't flow to AppWindow
// automatically, so load it explicitly from the output directory (csproj already sets CopyToOutputDirectory).
TrySetWindowIcon();
// Window has no Width/Height, so use AppWindow.Resize (physical pixels).
AppWindow.Resize(new Windows.Graphics.SizeInt32(1280, 860));
// Default to the dashboard. Setting SelectedItem fires SelectionChanged → which does the Navigate uniformly.
Nav.SelectedItem = Nav.MenuItems[0];
// Fallback: if setting the selected item didn't trigger navigation (already selected, etc.), navigate once manually.
if (ContentFrame.Content is null)
ContentFrame.Navigate(typeof(DashboardPage));
}
/// <summary>Set the window background material from the current config. Takes effect immediately; call on the UI thread.</summary>
public void ApplyBackdrop()
{
SystemBackdrop = ConfigService.Instance.Config.Backdrop switch
{
BackdropKind.MicaAlt => new MicaBackdrop { Kind = MicaKind.BaseAlt },
BackdropKind.Acrylic => new DesktopAcrylicBackdrop(),
_ => new MicaBackdrop { Kind = MicaKind.Base },
};
}
/// <summary>
/// Set the app theme (dark mode) from the current config. Takes effect immediately; call on the UI thread.
/// System → ElementTheme.Default: follow the system light/dark automatically and respond to system switches in real time.
/// Under ExtendsContentIntoTitleBar, the top-right system buttons' (minimize/maximize/close) foreground
/// color doesn't adapt to ElementTheme automatically, so set an explicit set of TitleBar button colors
/// from the effective theme here, ensuring they're legible in both light and dark.
/// </summary>
public void ApplyTheme()
{
if (Content is not FrameworkElement root) return;
root.RequestedTheme = ConfigService.Instance.Config.Theme switch
{
AppTheme.Light => ElementTheme.Light,
AppTheme.Dark => ElementTheme.Dark,
_ => ElementTheme.Default, // System: follow the system, respond to switches in real time
};
ApplyTitleBarButtonColors(root);
}
/// <summary>
/// Color the title-bar system buttons from the current effective theme. For System (Default), read
/// the actual system light/dark via root.ActualTheme. Light theme uses dark text, dark theme uses
/// light text; hover/pressed backgrounds use translucent grays, matching the Win11 look.
/// </summary>
private void ApplyTitleBarButtonColors(FrameworkElement root)
{
var titleBar = AppWindow.TitleBar;
// With RequestedTheme=Default, ActualTheme reflects the actual system light/dark; for Light/Dark it's the chosen value.
bool dark = root.ActualTheme == ElementTheme.Dark;
var fg = dark ? Windows.UI.Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF)
: Windows.UI.Color.FromArgb(0xFF, 0x00, 0x00, 0x00);
var disabled = dark ? Windows.UI.Color.FromArgb(0xFF, 0x6E, 0x6E, 0x6E)
: Windows.UI.Color.FromArgb(0xFF, 0x9E, 0x9E, 0x9E);
// hover/pressed use a low-opacity overlay of the theme text color (light gray in dark mode, dark gray in light mode).
var hoverBg = dark ? Windows.UI.Color.FromArgb(0x20, 0xFF, 0xFF, 0xFF)
: Windows.UI.Color.FromArgb(0x14, 0x00, 0x00, 0x00);
var pressedBg = dark ? Windows.UI.Color.FromArgb(0x10, 0xFF, 0xFF, 0xFF)
: Windows.UI.Color.FromArgb(0x0A, 0x00, 0x00, 0x00);
var transparent = Windows.UI.Color.FromArgb(0x00, 0x00, 0x00, 0x00);
titleBar.ButtonForegroundColor = fg;
titleBar.ButtonHoverForegroundColor = fg;
titleBar.ButtonPressedForegroundColor = fg;
titleBar.ButtonInactiveForegroundColor = disabled;
titleBar.ButtonBackgroundColor = transparent;
titleBar.ButtonInactiveBackgroundColor = transparent;
titleBar.ButtonHoverBackgroundColor = hoverBg;
titleBar.ButtonPressedBackgroundColor = pressedBg;
}
/// <summary>Config-change callback (any thread). Marshal back to the UI thread and re-apply the material and theme.</summary>
private void OnConfigChanged()
=> DispatcherQueue.TryEnqueue(() => { ApplyBackdrop(); ApplyTheme(); });
/// <summary>
/// Set the localized tooltip and accessibility name on the custom-drawn hamburger button (fetched
/// from MainWindow.resw via Loc). Done in code rather than XAML x:Uid: resw x:Uid for attached
/// properties (ToolTipService/AutomationProperties) is brittle under MRT Core.
/// </summary>
private void InitLocalizedAccessibility()
{
string tip = Loc.T("MainWindow/NavButtonTooltip");
Microsoft.UI.Xaml.Automation.AutomationProperties.SetName(PaneToggleButton, tip);
ToolTipService.SetToolTip(PaneToggleButton, tip);
}
/// <summary>Load Assets\reframe.ico into the 16px icon at the left of the title bar; leave it blank silently if the file is missing.</summary>
private void InitTitleBarIcon()
{
try
{
string ico = System.IO.Path.Combine(AppContext.BaseDirectory, "Assets", "reframe.ico");
if (System.IO.File.Exists(ico))
TitleBarIcon.Source = new BitmapImage(new System.Uri(ico));
}
catch { /* the icon is non-critical, ignore */ }
}
/// <summary>The title text greys out when the window is deactivated and restores when re-activated (the standard Win11 look).</summary>
private void OnActivated(object sender, WindowActivatedEventArgs args)
{
AppTitleText.Foreground = args.WindowActivationState == WindowActivationState.Deactivated
? (Brush)Application.Current.Resources["TextFillColorDisabledBrush"]
: (Brush)Application.Current.Resources["TextFillColorPrimaryBrush"];
}
/// <summary>Row0 custom-drawn hamburger: open/close the NavigationView pane (replacing the hidden built-in PaneToggleButton).</summary>
private void PaneToggle_Click(object sender, RoutedEventArgs e)
=> Nav.IsPaneOpen = !Nav.IsPaneOpen;
/// <summary>
/// The version the update banner has already been raised for this session. Once a version has been
/// announced it is never announced again in the same run, so dismissing the bar makes it stay
/// dismissed (the check itself is once per launch anyway; this also covers a manual re-check).
/// </summary>
private string? _announcedUpdateVersion;
/// <summary>
/// Show the "a newer Reframe is available" bar. Called on the UI thread from the startup check.
/// Deliberately passive: it never shows, activates or raises the window, so an app sitting in the
/// tray stays in the tray and the user meets the bar the next time they open the window.
/// </summary>
public void ShowUpdateAvailable(string version)
{
if (string.IsNullOrWhiteSpace(version)) return;
if (string.Equals(_announcedUpdateVersion, version, StringComparison.Ordinal)) return;
_announcedUpdateVersion = version;
UpdateBar.Title = Loc.T("MainWindow/UpdateBarTitle");
UpdateBar.Message = Loc.T("MainWindow/UpdateBarMessage", version);
UpdateBarViewButton.Content = Loc.T("MainWindow/UpdateBarViewButton");
UpdateBar.IsOpen = true;
}
/// <summary>Banner "View" button: close the bar and go to Settings, where the update controls live.</summary>
private void UpdateBarView_Click(object sender, RoutedEventArgs e)
{
UpdateBar.IsOpen = false;
// Selecting the footer item drives the normal SelectionChanged → Navigate path and keeps the
// nav rail highlight in sync; the explicit Navigate covers "Settings was already selected".
if (Nav.FooterMenuItems.Count > 0)
Nav.SelectedItem = Nav.FooterMenuItems[0];
if (ContentFrame.CurrentSourcePageType != typeof(SettingsPage))
ContentFrame.Navigate(typeof(SettingsPage));
}
private void Nav_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
if (args.SelectedItem is not NavigationViewItem item) return;
var pageType = (item.Tag as string) switch
{
"dashboard" => typeof(DashboardPage),
"profiles" => typeof(ProfilesPage),
"layouts" => typeof(LayoutsPage),
"settings" => typeof(SettingsPage),
_ => null
};
if (pageType is not null && ContentFrame.CurrentSourcePageType != pageType)
ContentFrame.Navigate(pageType);
}
/// <summary>Set the window icon from Assets\reframe.ico in the output directory; skip silently if the file is missing (don't block startup).</summary>
private void TrySetWindowIcon()
{
try
{
string ico = System.IO.Path.Combine(AppContext.BaseDirectory, "Assets", "reframe.ico");
if (System.IO.File.Exists(ico))
AppWindow.SetIcon(ico);
}
catch { /* the icon is non-critical, ignore */ }
}
}