diff --git a/src/TeamsISO.App.WinUI/Services/ThemeManager.cs b/src/TeamsISO.App.WinUI/Services/ThemeManager.cs new file mode 100644 index 0000000..7cc683a --- /dev/null +++ b/src/TeamsISO.App.WinUI/Services/ThemeManager.cs @@ -0,0 +1,108 @@ +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; + 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); +} diff --git a/src/TeamsISO.App.WinUI/Views/AboutDialog.xaml b/src/TeamsISO.App.WinUI/Views/AboutDialog.xaml new file mode 100644 index 0000000..143430d --- /dev/null +++ b/src/TeamsISO.App.WinUI/Views/AboutDialog.xaml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +