Files
ShrlAlgoToolkit/NeuWPF/NeoUI/Appearance/RThemeManager.cs
ShrlAlgo 955a01f564 整理
2025-08-20 12:10:35 +08:00

322 lines
11 KiB
C#

using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using Microsoft.Win32;
using NeoUI.Appearance;
namespace Rubyer
{
/// <summary>
/// 主题管理
/// </summary>
public class RThemeManager
{
/// <summary>
/// 所有颜色 key
/// </summary>
private static List<string> ColorKeys =
[
"DefaultForeground",
"DefaultBackground",
"Primary",
"Secondary",
"SecondaryForeground",
"Accent",
"Light",
"Dark",
"Border",
"BorderLight",
"BorderLighter",
"SeconarydText",
"WatermarkText",
"Mask",
"MaskDark",
"DialogBackground",
"HeaderBackground",
"FloatBackground",
"AllDirectionEffect",
"AllDirectionEffect2",
"AllDirectionEffect3",
"AllDirectionEffect4",
"AllDirectionEffect5",
"RightTopEffect",
"LeftTopEffect",
"RightBottomEffect",
"LeftBottomEffect",
"TopEffect",
"BottomEffect",
"RightEffect",
"LeftEffect",
"ControlBackground",
"ContainerBackground",
"ScrollBarBrush",
"WindowTitleBackground",
"StatusBarBackground",
"PanelBackground",
];
private static bool themeApplying = false;
/// <summary>
/// 主题模式改变事件
/// </summary>
/// <param name="sender">发生对象</param>
/// <param name="e">参数</param>
public delegate void ThemeModeChangedEventHandler(object sender, ThemeModeChangedArgs e);
/// <summary>
/// 主题改变事件
/// </summary>
public static ThemeModeChangedEventHandler? ThemeModeChanged;
/// <summary>
/// 获取当前应用模式是否为暗色
/// </summary>
/// <returns>是否为暗色</returns>
public static bool GetIsAppDarkMode()
{
const string RegistryKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
const string RegistryValueName = "AppsUseLightTheme";
// 这里也可能是LocalMachine(HKEY_LOCAL_MACHINE)
// see "https://www.addictivetips.com/windows-tips/how-to-enable-the-dark-theme-in-windows-10/"
object registryValueObject = Registry.CurrentUser.OpenSubKey(RegistryKeyPath)?.GetValue(RegistryValueName);
if (registryValueObject is null) return false;
return (int)registryValueObject <= 0;
}
private static ColorAnimation GetColorAnimation(bool isDark, Color lightColor, Color darkColor)
{
themeApplying = true;
var animation = new ColorAnimation
{
Duration = TimeSpan.FromMilliseconds(600),
To = isDark ? darkColor : lightColor,
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut }
};
animation.Completed += (sender, e) => themeApplying = false;
return animation;
}
private static void ApplyColor(bool isDark, string colorName)
{
var application = Application.Current;
Color lightColor;
Color darkColor;
if (colorName.Contains("Effect"))
{
lightColor = (Color)application.FindResource($"LightEffectColor");
darkColor = (Color)application.FindResource($"DarkEffectColor");
}
else
{
lightColor = (Color)application.FindResource($"Light{colorName}Color");
darkColor = (Color)application.FindResource($"Dark{colorName}Color");
}
if (application.Resources[colorName] is Brush brush)
{
ColorAnimation animation = GetColorAnimation(isDark, lightColor, darkColor);
if (brush.IsFrozen)
{
brush = brush.CloneCurrentValue();
}
brush.BeginAnimation(SolidColorBrush.ColorProperty, animation);
application.Resources[colorName] = brush;
}
else if (application.Resources[colorName] is DropShadowEffect effect)
{
if (effect.IsFrozen)
{
effect = effect.CloneCurrentValue();
}
effect.Color = isDark ? darkColor : lightColor;
application.Resources[colorName] = effect;
}
}
private static ThemeColor GetCurrentThemeColor(Application application)
{
return new ThemeColor
{
Primary = (Brush)application.Resources["Primary"],
Light = (Brush)application.Resources["Light"],
Dark = (Brush)application.Resources["Dark"],
Accent = (Brush)application.Resources["Accent"],
};
}
private static void ApplyTheme(bool isDark)
{
foreach (var colorKey in ColorKeys)
{
ApplyColor(isDark, colorKey);
}
var currentThemeColor = GetCurrentThemeColor(Application.Current);
var args = new ThemeModeChangedArgs(currentThemeColor, isDark);
ThemeModeChanged?.Invoke(Application.Current, args);
}
/// <summary>
/// 切换主题模式
/// </summary>
/// <param name="mode">模式</param>
public static void SwitchThemeMode(ThemeMode mode)
{
if (mode != ThemeMode.System)
{
SystemEvents.UserPreferenceChanged -= SystemEvents_UserPreferenceChanged;
}
bool isDark = false;
switch (mode)
{
case ThemeMode.Light:
default:
break;
case ThemeMode.Dark:
isDark = true;
break;
case ThemeMode.System:
isDark = GetIsAppDarkMode();
SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged;
break;
}
ApplyTheme(isDark);
}
private static void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
{
if (!themeApplying)
{
var isDark = GetIsAppDarkMode();
ApplyTheme(isDark);
}
}
/// <summary>
/// 切换容器圆角半径
/// </summary>
/// <param name="cornerRadius">圆角半径</param>
public static void SwitchContainerCornerRadius(double cornerRadius)
{
Application.Current.Resources["AllContainerCornerRadius"] = new CornerRadius(cornerRadius);
Application.Current.Resources["LeftContainerCornerRadius"] = new CornerRadius(cornerRadius, 0, 0, cornerRadius);
Application.Current.Resources["RightContainerCornerRadius"] = new CornerRadius(0, cornerRadius, cornerRadius, 0);
Application.Current.Resources["TopContainerCornerRadius"] = new CornerRadius(cornerRadius, cornerRadius, 0, 0);
Application.Current.Resources["BottomContainerCornerRadius"] = new CornerRadius(0, 0, cornerRadius, cornerRadius);
}
/// <summary>
/// 切换控件圆角半径
/// </summary>
/// <param name="cornerRadius">圆角半径</param>
public static void SwitchControlCornerRadius(double cornerRadius)
{
Application.Current.Resources["AllControlCornerRadius"] = new CornerRadius(cornerRadius);
Application.Current.Resources["LeftControlCornerRadius"] = new CornerRadius(cornerRadius, 0, 0, cornerRadius);
Application.Current.Resources["RightControlCornerRadius"] = new CornerRadius(0, cornerRadius, cornerRadius, 0);
Application.Current.Resources["TopControlCornerRadius"] = new CornerRadius(cornerRadius, cornerRadius, 0, 0);
Application.Current.Resources["BottomControlCornerRadius"] = new CornerRadius(0, 0, cornerRadius, cornerRadius);
}
private static bool CheckIsDark(Brush brush)
{
if (brush is SolidColorBrush color)
{
return (color.Color.R * 0.299 + color.Color.G * 0.578 + color.Color.B * 0.114) < 192;
}
return false;
}
/// <summary>
/// 应用主题配色
/// </summary>
/// <param name="colorUrl">颜色配置文件路径</param>
public static void ApplyThemeColor(string colorUrl)
{
var resourceDictionaries = Application.Current.Resources.MergedDictionaries;
var resourceDictionary = new ResourceDictionary
{
Source = new Uri(colorUrl, UriKind.RelativeOrAbsolute)
};
if (resourceDictionaries.Any(x => x.Source.AbsoluteUri == resourceDictionary.Source.AbsoluteUri))
{
var oldColorResource = resourceDictionaries.First(x => x.Source.AbsoluteUri == resourceDictionary.Source.AbsoluteUri);
resourceDictionaries.Remove(oldColorResource);
}
resourceDictionaries.Add(resourceDictionary);
var currentBackground = (Brush)Application.Current.Resources["DefaultBackground"];
var currentThemeColor = GetCurrentThemeColor(Application.Current);
var isDarkMode = CheckIsDark(currentBackground);
ApplyTheme(isDarkMode);
var args = new ThemeModeChangedArgs(currentThemeColor, isDarkMode);
ThemeModeChanged?.Invoke(Application.Current, args);
}
/// <summary>
/// 添加颜色
/// </summary>
/// <param name="keys"></param>
public static void AddColorKeys(params string[] keys)
{
for (int i = 0; i < keys.Length; i++)
{
ColorKeys.Add(keys[i]);
}
}
}
/// <summary>
/// 主题模式改变参数
/// </summary>
public class ThemeModeChangedArgs : EventArgs
{
/// <summary>
/// 主题配色
/// </summary>
public ThemeColor ThemeColor { get; set; }
/// <summary>
/// 是否未暗色模式
/// </summary>
public bool IsDarkMode { get; set; }
/// <summary>
///
/// </summary>
/// <param name="themeColor">主题配色</param>
/// <param name="isDarkMode">是否未暗色模式</param>
public ThemeModeChangedArgs(ThemeColor themeColor, bool isDarkMode)
{
ThemeColor = themeColor;
IsDarkMode = isDarkMode;
}
}
public class ThemeColor
{
public Brush Primary { get; internal set; }
public Brush Light { get; internal set; }
public Brush Dark { get; internal set; }
public Brush Accent { get; internal set; }
}
}