using System; using Microsoft.UI; using Microsoft.UI.Xaml; using Windows.UI; using Windows.UI.ViewManagement; namespace TeamsISO.App.WinUI.Services; /// /// Owns the active theme for the WinUI 3 host. Three preferences: /// System follows the Windows app-mode setting (default for new /// users); Dark and Light pin one regardless of the OS choice. /// The persistence path will land alongside the existing UIPreferences in /// the next commit — for now state lives in-process. /// /// All public mutations push to subscribers so the /// host (MainWindow) can update the AppWindow title-bar button colors /// (system buttons aren't part of the visual tree and need a separate /// poke when ElementTheme changes). /// public sealed class ThemeManager { public static ThemeManager Current { get; } = new(); private ThemeManager() { _uiSettings = new UISettings(); _uiSettings.ColorValuesChanged += OnSystemColorsChanged; // Hydrate the preference from disk so the operator's choice // survives across launches. Defaults to "System" if the prefs // file is missing or unreadable (Load() catches its own errors). try { var prefs = UIPreferences.Load(); if (prefs.Theme == "System" || prefs.Theme == "Dark" || prefs.Theme == "Light") { _preference = prefs.Theme; } } catch { // Defensive — ThemeManager.Current is a static singleton; a // throw here would prevent the app from getting any theme. } } private readonly UISettings _uiSettings; private string _preference = "System"; public string Preference => _preference; public event EventHandler? Themed; /// /// Resolve the preference to an absolute /// suitable for . /// System resolves to the OS app-mode. /// public ElementTheme ResolveTheme() => _preference switch { "Dark" => ElementTheme.Dark, "Light" => ElementTheme.Light, _ => IsSystemDark() ? ElementTheme.Dark : ElementTheme.Light, }; public bool PreferenceMatches(string value) => string.Equals(_preference, value, StringComparison.Ordinal); /// /// Cycle dark ↔ light from the title-bar toggle. If the current /// preference is System, the cycle pins to the opposite of the /// currently-resolved theme so the click has a visible effect. /// public ElementTheme Toggle() { var current = ResolveTheme(); Set(current == ElementTheme.Dark ? "Light" : "Dark"); return ResolveTheme(); } /// Set the preference, persist to disk, broadcast the resolved theme. public void Set(string preference) { if (preference != "System" && preference != "Dark" && preference != "Light") { throw new ArgumentException("Preference must be System, Dark, or Light.", nameof(preference)); } _preference = preference; try { UIPreferences.SetTheme(preference); } catch { /* persistence is best-effort */ } Themed?.Invoke(this, ResolveTheme()); } private bool IsSystemDark() { // UISettings.GetColorValue(UIColorType.Background) returns // black-ish in dark mode, white-ish in light mode — the most // reliable cross-version check for app mode on desktop WinUI 3. var bg = _uiSettings.GetColorValue(UIColorType.Background); return ((5 * bg.G) + (2 * bg.R) + bg.B) < 8 * 128; } private void OnSystemColorsChanged(UISettings sender, object args) { // Only re-broadcast if the operator hasn't pinned a preference — // otherwise the explicit choice wins regardless of what the OS does. if (_preference == "System") { Themed?.Invoke(this, ResolveTheme()); } } /// /// Compute the AppWindow title-bar foreground for the given resolved /// theme so the system min/max/close buttons stay readable. /// public static Color TitleBarForegroundFor(ElementTheme theme) => theme == ElementTheme.Dark ? Color.FromArgb(0xFF, 0xF4, 0xF4, 0xF6) : Color.FromArgb(0xFF, 0x0A, 0x0A, 0x0A); public static Color TitleBarHoverBgFor(ElementTheme theme) => theme == ElementTheme.Dark ? Color.FromArgb(0xFF, 0x33, 0x34, 0x3A) : Color.FromArgb(0xFF, 0xEC, 0xEE, 0xF1); }