using System; using System.Collections.Generic; using System.Windows; using System.Windows.Media; namespace TeamAAS.Theme { /// /// 主题门面:主程序唯一的主题调用入口。 /// /// 职责边界: /// - 主程序(App/ViewModel)只调用本类,不感知 VmResourceGuard/ThemeOverlay/官方字典细节; /// - 配置读取(SystemConfig.Theme / AccentColor)由主程序完成后以参数传入, /// 本模块不反向依赖主程序配置类型(依赖方向:主程序 → Global)。 /// /// 内部结构(同程序集 internal): /// - VmResourceGuard:资源守护(切主题入口消毒 / Purge 第三方注入字典 / 污染清理 / 坏画刷整替) /// - ThemeOverlay:纯值覆盖字典(主色派生配色 + 官方基准遮蔽,免疫第三方污染) /// - ColorUtility:HSL 颜色派生 /// public static class ThemeManager { /// 明暗主题变化通知(主界面图标/预览刷新订阅)。 public static event Action ThemeChanged; private static bool _dark; private static Color _accent = Color.FromRgb(0x2D, 0x6C, 0xDF); /// 按配置应用完整主题(启动 / 设置页保存时调用)。themeSetting:"Dark"/"Light"。 public static void ApplyTheme(string themeSetting, string accentHex) { ApplyTheme(string.Equals(themeSetting, "Dark", StringComparison.OrdinalIgnoreCase), accentHex); } /// 按配置应用完整主题(bool 版本)。 public static void ApplyTheme(bool dark, string accentHex) { ApplyTheme(dark, ParseAccent(accentHex)); } /// 按配置应用完整主题(Color 版本)。 public static void ApplyTheme(bool dark, Color accent) { _dark = dark; _accent = accent; UpdateThemeInternal(dark, accent); } /// 切换明暗主题(保持当前主色)。 public static void UpdateTheme(bool dark) { _dark = dark; UpdateThemeInternal(dark, _accent); } /// 切换主色(保持当前明暗)。hex 形式如 "#2D6CDF"。 public static void UpdateAccent(string accentHex) { UpdateAccent(ParseAccent(accentHex)); } /// 切换主色(保持当前明暗)。 public static void UpdateAccent(Color color) { _accent = color; // 覆盖字典重建(毫秒级,主色系派生 + 文字对比适配); // 若该明暗的官方基准尚未捕获(异常时序),回退整替路径补齐 if (!ThemeOverlay.Apply(_dark, color)) { VmResourceGuard.ForceReplaceTheme(_dark); } } /// 安全解析颜色(非法/空值回退默认主色蓝)。 public static Color ParseAccent(string hex) { try { if (!string.IsNullOrWhiteSpace(hex)) return (Color)ColorConverter.ConvertFromString(hex); } catch { /* 非法颜色值回退默认 */ } return Color.FromRgb(0x2D, 0x6C, 0xDF); } /// 核心切换流程(原 App.UpdateTheme 逻辑)。 private static void UpdateThemeInternal(bool dark, Color accent) { var merged = Application.Current != null ? Application.Current.Resources.MergedDictionaries : null; if (merged == null) return; // 切主题前先消毒:移除第三方注入字典/字符串污染条目 VmResourceGuard.Sanitize(nameof(UpdateTheme)); // 结构损坏 / 存在坏画刷 / 该明暗的官方基准尚未捕获 → 整替官方字典 // (内部完成:摘覆盖→整替→捕获基准→重建覆盖) // 否则 → 纯覆盖重建(丝滑,毫秒级) if (!IsThemeStructureValid(merged) || VmResourceGuard.HasBrokenBrush() || !ThemeOverlay.HasBase(dark)) { VmResourceGuard.ForceReplaceTheme(dark); } else if (!IsTargetSkinAlready(merged, dark)) { if (!ThemeOverlay.Apply(dark, accent)) { VmResourceGuard.ForceReplaceTheme(dark); } } ThemeChanged?.Invoke(); } /// /// 判定合并字典是否包含有效的官方主题结构 /// (Skin 调色板字典 + Theme.xaml 画刷/样式字典)。 /// 覆盖层加入后索引会变化,因此按存在性遍历判定。 /// private static bool IsThemeStructureValid(IList merged) { bool hasSkin = false; bool hasTheme = false; foreach (var d in merged) { try { var s = d.Source != null ? d.Source.OriginalString : null; if (s == null) continue; if (s.Contains("SkinDark.xaml") || s.Contains("SkinDefault.xaml")) hasSkin = true; else if (s.Contains("Theme.xaml")) hasTheme = true; } catch { /* Source 读取异常时跳过 */ } } return hasSkin && hasTheme; } /// md 中是否已是目标明暗的调色板字典。 private static bool IsTargetSkinAlready(IList merged, bool dark) { foreach (var d in merged) { try { var s = d.Source != null ? d.Source.OriginalString : null; if (s == null) continue; if (dark && s.Contains("SkinDark.xaml")) return true; if (!dark && s.Contains("SkinDefault.xaml")) return true; } catch { } } return false; } } }