This commit is contained in:
GG Z
2025-07-31 20:12:24 +08:00
parent 4f6cd2137c
commit f209e7d3ad
426 changed files with 15854 additions and 6612 deletions

View File

@@ -0,0 +1,295 @@
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
}