You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

301 lines
10 KiB

  1. using BPA.UIControl.Enums;
  2. using System;
  3. using System.Windows.Media.Animation;
  4. using System.Windows.Media;
  5. using System.Windows;
  6. using Microsoft.Win32;
  7. using BPA.UIControl.Models;
  8. using System.Linq;
  9. using System.Windows.Media.Effects;
  10. namespace BPA.UIControl
  11. {
  12. /// <summary>
  13. /// 主题管理
  14. /// </summary>
  15. public class ThemeManager
  16. {
  17. /// <summary>
  18. /// 所有颜色 key
  19. /// </summary>
  20. public static string[] ColorKeys = new string[]
  21. {
  22. "DefaultForeground",
  23. "DefaultBackground",
  24. "Primary",
  25. "Secondary",
  26. "SecondaryForeground",
  27. "Accent",
  28. "Light",
  29. "Dark",
  30. "Border",
  31. "BorderLight",
  32. "BorderLighter",
  33. "SeconarydText",
  34. "WatermarkText",
  35. "Mask",
  36. "MaskDark",
  37. "DialogBackground",
  38. "HeaderBackground",
  39. "FloatBackground",
  40. "AllDirectionEffect",
  41. "AllDirectionEffect2",
  42. "AllDirectionEffect3",
  43. "AllDirectionEffect4",
  44. "AllDirectionEffect5",
  45. "RightTopEffect",
  46. "LeftTopEffect",
  47. "RightBottomEffect",
  48. "LeftBottomEffect",
  49. "TopEffect",
  50. "BottomEffect",
  51. "RightEffect",
  52. "LeftEffect",
  53. "ControlBackground",
  54. "ContainerBackground",
  55. };
  56. private static bool themeApplying = false;
  57. /// <summary>
  58. /// 主题模式改变事件
  59. /// </summary>
  60. /// <param name="sender">发生对象</param>
  61. /// <param name="e">参数</param>
  62. public delegate void ThemeModeChangedEventHandler(object sender, ThemeModeChangedArgs e);
  63. /// <summary>
  64. /// 主题改变事件
  65. /// </summary>
  66. public static ThemeModeChangedEventHandler ThemeModeChanged;
  67. /// <summary>
  68. /// 获取当前应用模式是否为暗色
  69. /// </summary>
  70. /// <returns>是否为暗色</returns>
  71. public static bool GetIsAppDarkMode()
  72. {
  73. const string RegistryKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
  74. const string RegistryValueName = "AppsUseLightTheme";
  75. // 这里也可能是LocalMachine(HKEY_LOCAL_MACHINE)
  76. // see "https://www.addictivetips.com/windows-tips/how-to-enable-the-dark-theme-in-windows-10/"
  77. object registryValueObject = Registry.CurrentUser.OpenSubKey(RegistryKeyPath)?.GetValue(RegistryValueName);
  78. if (registryValueObject is null) return false;
  79. return (int)registryValueObject <= 0;
  80. }
  81. private static ColorAnimation GetColorAnimation(bool isDark, Color lightColor, Color darkColor)
  82. {
  83. themeApplying = true;
  84. var animation = new ColorAnimation
  85. {
  86. Duration = TimeSpan.FromMilliseconds(600),
  87. To = isDark ? darkColor : lightColor,
  88. EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut }
  89. };
  90. animation.Completed += (sender, e) => themeApplying = false;
  91. return animation;
  92. }
  93. private static void ApplyColor(bool isDark, string colorName)
  94. {
  95. var application = Application.Current;
  96. Color lightColor;
  97. Color darkColor;
  98. if (colorName.Contains("Effect"))
  99. {
  100. lightColor = (Color)application.FindResource($"LightEffectColor");
  101. darkColor = (Color)application.FindResource($"DarkEffectColor");
  102. }
  103. else
  104. {
  105. lightColor = (Color)application.FindResource($"Light{colorName}Color");
  106. darkColor = (Color)application.FindResource($"Dark{colorName}Color");
  107. }
  108. if (application.Resources[colorName] is Brush brush)
  109. {
  110. ColorAnimation animation = GetColorAnimation(isDark, lightColor, darkColor);
  111. if (brush.IsFrozen)
  112. {
  113. brush = brush.CloneCurrentValue();
  114. }
  115. brush.BeginAnimation(SolidColorBrush.ColorProperty, animation);
  116. application.Resources[colorName] = brush;
  117. }
  118. else if (application.Resources[colorName] is DropShadowEffect effect)
  119. {
  120. if (effect.IsFrozen)
  121. {
  122. effect = effect.CloneCurrentValue();
  123. }
  124. effect.Color = isDark ? darkColor : lightColor;
  125. application.Resources[colorName] = effect;
  126. }
  127. }
  128. private static ThemeColor GetCurrentThemeColor(Application application)
  129. {
  130. return new ThemeColor
  131. {
  132. Primary = (Brush)application.Resources["Primary"],
  133. Light = (Brush)application.Resources["Light"],
  134. Dark = (Brush)application.Resources["Dark"],
  135. Accent = (Brush)application.Resources["Accent"],
  136. };
  137. }
  138. private static void ApplyTheme(bool isDark)
  139. {
  140. foreach (var colorKey in ColorKeys)
  141. {
  142. ApplyColor(isDark, colorKey);
  143. }
  144. var currentThemeColor = GetCurrentThemeColor(Application.Current);
  145. var args = new ThemeModeChangedArgs(currentThemeColor, isDark);
  146. ThemeModeChanged?.Invoke(Application.Current, args);
  147. }
  148. /// <summary>
  149. /// 切换主题模式
  150. /// </summary>
  151. /// <param name="mode">模式</param>
  152. public static void SwitchThemeMode(ThemeMode mode)
  153. {
  154. if (mode != ThemeMode.System)
  155. {
  156. SystemEvents.UserPreferenceChanged -= SystemEvents_UserPreferenceChanged;
  157. }
  158. bool isDark = false;
  159. switch (mode)
  160. {
  161. case ThemeMode.Light:
  162. default:
  163. break;
  164. case ThemeMode.Dark:
  165. isDark = true;
  166. break;
  167. case ThemeMode.System:
  168. isDark = GetIsAppDarkMode();
  169. SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged;
  170. break;
  171. }
  172. ApplyTheme(isDark);
  173. }
  174. private static void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
  175. {
  176. if (!themeApplying)
  177. {
  178. var isDark = GetIsAppDarkMode();
  179. ApplyTheme(isDark);
  180. }
  181. }
  182. /// <summary>
  183. /// 切换容器圆角半径
  184. /// </summary>
  185. /// <param name="cornerRadius">圆角半径</param>
  186. public static void SwitchContainerCornerRadius(double cornerRadius)
  187. {
  188. Application.Current.Resources["AllContainerCornerRadius"] = new CornerRadius(cornerRadius);
  189. Application.Current.Resources["LeftContainerCornerRadius"] = new CornerRadius(cornerRadius, 0, 0, cornerRadius);
  190. Application.Current.Resources["RightContainerCornerRadius"] = new CornerRadius(0, cornerRadius, cornerRadius, 0);
  191. Application.Current.Resources["TopContainerCornerRadius"] = new CornerRadius(cornerRadius, cornerRadius, 0, 0);
  192. Application.Current.Resources["BottomContainerCornerRadius"] = new CornerRadius(0, 0, cornerRadius, cornerRadius);
  193. }
  194. /// <summary>
  195. /// 切换控件圆角半径
  196. /// </summary>
  197. /// <param name="cornerRadius">圆角半径</param>
  198. public static void SwitchControlCornerRadius(double cornerRadius)
  199. {
  200. Application.Current.Resources["AllControlCornerRadius"] = new CornerRadius(cornerRadius);
  201. Application.Current.Resources["LeftControlCornerRadius"] = new CornerRadius(cornerRadius, 0, 0, cornerRadius);
  202. Application.Current.Resources["RightControlCornerRadius"] = new CornerRadius(0, cornerRadius, cornerRadius, 0);
  203. Application.Current.Resources["TopControlCornerRadius"] = new CornerRadius(cornerRadius, cornerRadius, 0, 0);
  204. Application.Current.Resources["BottomControlCornerRadius"] = new CornerRadius(0, 0, cornerRadius, cornerRadius);
  205. }
  206. private static bool CheckIsDark(Brush brush)
  207. {
  208. if (brush is SolidColorBrush color)
  209. {
  210. return (color.Color.R * 0.299 + color.Color.G * 0.578 + color.Color.B * 0.114) < 192;
  211. }
  212. return false;
  213. }
  214. /// <summary>
  215. /// 应用主题配色
  216. /// </summary>
  217. /// <param name="colorUrl">颜色配置文件路径</param>
  218. public static void ApplyThemeColor(string colorUrl)
  219. {
  220. var resourceDictionaries = Application.Current.Resources.MergedDictionaries;
  221. var resourceDictionary = new ResourceDictionary
  222. {
  223. Source = new Uri(colorUrl, UriKind.RelativeOrAbsolute)
  224. };
  225. if (resourceDictionaries.Any(x => x.Source.AbsoluteUri == resourceDictionary.Source.AbsoluteUri))
  226. {
  227. var oldColorResource = resourceDictionaries.First(x => x.Source.AbsoluteUri == resourceDictionary.Source.AbsoluteUri);
  228. resourceDictionaries.Remove(oldColorResource);
  229. }
  230. resourceDictionaries.Add(resourceDictionary);
  231. var currentBackground = (Brush)Application.Current.Resources["DefaultBackground"];
  232. var currentThemeColor = GetCurrentThemeColor(Application.Current);
  233. var isDarkMode = CheckIsDark(currentBackground);
  234. ApplyTheme(isDarkMode);
  235. var args = new ThemeModeChangedArgs(currentThemeColor, isDarkMode);
  236. ThemeModeChanged?.Invoke(Application.Current, args);
  237. }
  238. }
  239. /// <summary>
  240. /// 主题模式改变参数
  241. /// </summary>
  242. public class ThemeModeChangedArgs : EventArgs
  243. {
  244. /// <summary>
  245. /// 主题配色
  246. /// </summary>
  247. public ThemeColor ThemeColor { get; set; }
  248. /// <summary>
  249. /// 是否未暗色模式
  250. /// </summary>
  251. public bool IsDarkMode { get; set; }
  252. /// <summary>
  253. ///
  254. /// </summary>
  255. /// <param name="themeColor">主题配色</param>
  256. /// <param name="isDarkMode">是否未暗色模式</param>
  257. public ThemeModeChangedArgs(ThemeColor themeColor, bool isDarkMode)
  258. {
  259. ThemeColor = themeColor;
  260. IsDarkMode = isDarkMode;
  261. }
  262. }
  263. }