Files
ShrlAlgoToolkit/AntDesignWPF/Appearance/ThemeManager.cs
2025-07-31 20:12:24 +08:00

295 lines
9.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Reflection;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using AntDesignWPF.Extensions;
namespace AntDesignWPF.Appearance;
/// <summary>
/// 允许通过交换包含动态资源的资源字典来管理应用程序主题。
/// </summary>
/// <example>
/// <code lang="csharp"> ThemeManager.ChangeAppTheme( ThemeMode.Light );</code> <code lang="csharp"> if
/// (ThemeManager.GetAppTheme() == ThemeMode.Dark) { ThemeManager.ChangeAppTheme(
/// ThemeMode.Light ); }</code> <code> ThemeManager.Changed += (theme, accent) => {
/// Debug.WriteLine($"Application theme changed to {theme.ToString()}"); };</code>
/// </example>
public class ThemeManager
{
internal static string LibraryNamespace => Assembly.GetExecutingAssembly().GetName().Name;
public static string ThemesDictionaryPath => $"pack://application:,,,/{LibraryNamespace};component/Themes/";
private static ThemeMode _activeThemeMode = ThemeMode.Light;
private static ThemePalette _activeThemePalette = ThemePalette.DaybreakBlue;
public static event Action<ThemeMode>? ThemeModeChanged;
public static event Action<ThemePalette>? ThemePaletteChanged;
#region Animation
private static Task AnimateBrushColorAsync(SolidColorBrush brush, Color toColor, int durationMs = 500)
{
if (brush.IsFrozen)
return Task.CompletedTask; // 冻结的Brush无法动画直接返回完成的Task
var tcs = new TaskCompletionSource<bool>();
var anim = new ColorAnimation
{
To = toColor,
Duration = TimeSpan.FromMilliseconds(durationMs),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut },
FillBehavior = FillBehavior.HoldEnd
};
// 在动画完成时标记任务完成
anim.Completed += (s, e) => tcs.SetResult(true);
brush.BeginAnimation(SolidColorBrush.ColorProperty, anim);
return tcs.Task;
}
/// <summary>
/// 动画过渡
/// </summary>
/// <param name="themeUri"></param>
public void ChangeTheme(Uri themeUri)
{
// 1. 加载目标主题的资源字典,但先不合并
ResourceDictionary newTheme = new ResourceDictionary { Source = themeUri };
// 2. 遍历新主题中的画刷资源
foreach (var key in newTheme.Keys)
{
// 只处理 SolidColorBrush
if (newTheme[key] is SolidColorBrush newBrush)
{
// 3. 在当前的应用程序资源中找到同名的画刷
var currentBrush = Application.Current.Resources[key] as SolidColorBrush;
if (currentBrush != null)
{
// 4. 创建颜色动画
var colorAnimation = new ColorAnimation
{
From = currentBrush.Color,
To = newBrush.Color,
Duration = new Duration(TimeSpan.FromMilliseconds(300)), // 动画时长
EasingFunction = new QuarticEase { EasingMode = EasingMode.EaseInOut } // 缓动函数,使动画更自然
};
// 5. 对当前画刷的 Color 属性应用动画
currentBrush.BeginAnimation(SolidColorBrush.ColorProperty, colorAnimation);
}
}
}
}
#endregion
/// <summary>
/// 将资源应用于 <paramref name="frameworkElement"/>.
/// </summary>
public static void ApplyResourcesToElement(FrameworkElement frameworkElement)
{
if (frameworkElement.Resources.MergedDictionaries.Count <
ThemeManager.Current.Resources.MergedDictionaries.Count)
{
foreach (var dictionary in ThemeManager.Current.Resources.MergedDictionaries)
{
frameworkElement.Resources.MergedDictionaries.Add(dictionary);
}
}
}
public static void SwitchThemePalette(ThemePalette themePalette)
{
if (themePalette == ThemePalette.DaybreakBlue)
{
return;
}
}
/// <summary>
/// 更改当前应用程序主题。
/// </summary>
/// <param name="themeMode">要设置的主题</param>
/// <param name="backgroundEffect">是否应用自定义背景效果。</param>
/// <param name="updateAccent">是否应更改颜色强调。</param>
public static void SwitchThemeMode(ThemeMode themeMode)
{
if (themeMode == _activeThemeMode)
{
return;
}
ResourceDictionaryManager appDictionaries = new();
var themeDictionaryName = themeMode switch
{
ThemeMode.Light => "Light",
ThemeMode.Dark => "Dark",
_ => throw new NotImplementedException(),
};
//ApplyThemeWithAnimation(applicationTheme);
if (!appDictionaries.UpdateDictionary(
"themes",
new Uri($"{ThemesDictionaryPath}{themeDictionaryName}.xaml", UriKind.Absolute)))
{
return;
}
_activeThemeMode = themeMode;
ThemeModeChanged?.Invoke(themeMode);
}
/// <summary>
/// 获取当前设置的应用程序主题。
/// </summary>
/// <returns><see cref="ThemeMode.Unknow"/> if something goes wrong.</returns>
public static ThemeMode GetAppTheme()
{
if (_activeThemeMode == ThemeMode.Light)
{
ResourceDictionaryManager appDictionaries = new();
var themeDictionary = appDictionaries.LookupDictionary("themes");
if (themeDictionary == null)
{
return ThemeMode.Light;
}
var themeUri = themeDictionary.Source.ToString();
if (themeUri.Contains("light", StringComparison.OrdinalIgnoreCase))
{
_activeThemeMode = ThemeMode.Light;
}
if (themeUri.Contains("dark", StringComparison.OrdinalIgnoreCase))
{
_activeThemeMode = ThemeMode.Dark;
}
}
return _activeThemeMode;
}
#region UiApplication
private static ThemeManager? _themeManagerInstance;
private readonly Application? application;
private Window? mainWindow;
private ResourceDictionary? resources;
/// <summary>
/// 初始化 <see cref="ThemeManager"/> 实例.
/// </summary>
private ThemeManager(Application? application)
{
if (application is null)
{
return;
}
if (!ApplicationHasResources(application))
{
return;
}
this.application = application;
System.Diagnostics.Debug
.WriteLine($"INFO | {typeof(ThemeManager)} application is {this.application}", "AntDesignWPF");
}
private static bool ApplicationHasResources(Application? application)
{
return application!
.Resources.MergedDictionaries
.Where(e => e.Source is not null)
.Any(
e => e.Source
.ToString()
.Contains(ThemeManager.LibraryNamespace, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// 将应用程序的 变为 shutdown 模式。
/// </summary>
public void Shutdown() { application?.Shutdown(); }
/// <summary>
/// 获取或设置应用程序的资源。
/// </summary>
public object TryFindResource(object resourceKey) { return Resources[resourceKey]; }
/// <summary>
/// 获取当前UI应用程序。
/// </summary>
public static ThemeManager Current
{
get
{
_themeManagerInstance ??= new ThemeManager(Application.Current);
return _themeManagerInstance;
}
}
/// <summary>
/// 获取或设置应用程序的主窗口。
/// </summary>
public Window? MainWindow
{
get => application?.MainWindow ?? mainWindow;
set
{
if (application != null)
{
application.MainWindow = value;
}
mainWindow = value;
}
}
/// <summary>
/// 获取或设置应用程序的资源。
/// </summary>
public ResourceDictionary Resources
{
get
{
if (resources is null)
{
resources = [];
try
{
var themesDictionary = new ThemesDictionary();
var controlsDictionary = new ControlsDictionary();
resources.MergedDictionaries.Add(themesDictionary);
resources.MergedDictionaries.Add(controlsDictionary);
}
catch
{
}
}
return application?.Resources ?? resources;
}
set
{
if (application is not null)
{
application.Resources = value;
}
resources = value;
}
}
#endregion
}