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; } private readonly UISettings _uiSettings; // Default: System (follow OS app-mode). Override at runtime via Set(); // persistence to UIPreferences.Theme lands in the view-model commit. 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 and 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; 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); }