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