Files
Shrlalgo.RvKits/WPFluent/Appearance/ThemeManager.cs
2025-05-05 17:04:06 +08:00

332 lines
9.9 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.Animation;
using System.Windows.Shapes;
using WPFluent.Controls;
using WPFluent.Extensions;
using WPFluent.Markup;
namespace WPFluent.Appearance;
/// <summary>
/// 允许通过交换包含动态资源的资源字典来管理应用程序主题。
/// </summary>
/// <example>
/// <code lang="csharp"> ThemeManager.ChangeAppTheme( ThemeType.Light );</code> <code lang="csharp"> if
/// (ThemeManager.GetAppTheme() == ThemeType.Dark) { ThemeManager.ChangeAppTheme(
/// ThemeType.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 ThemeType _activeApplicationTheme = ThemeType.Unknow;
public static event Action<ThemeType, Color>? Changed;
#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>
private static async void ApplyThemeWithAnimation(ThemeType theme)
{
try
{
var window = Application.Current.MainWindow;
if (window == null) return;
// 确保内容容器支持 Children
if (!(window.Content is Panel panel))
throw new InvalidOperationException("ThemeManager requires Window.Content to be a Panel.");
// 创建遮罩
var overlay = new Rectangle
{
Fill = new SolidColorBrush(Colors.Black),
Opacity = 0,
IsHitTestVisible = false,
};
Panel.SetZIndex(overlay, int.MaxValue);
panel.Children.Add(overlay);
// 淡入
await AnimateOpacity(overlay, 0, 1, TimeSpan.FromMilliseconds(300));
// 切换资源字典
//ApplyThemeInternal(theme);
ChangeAppTheme(theme);
// 淡出
await AnimateOpacity(overlay, 1, 0, TimeSpan.FromMilliseconds(300));
panel.Children.Remove(overlay);
Changed?.Invoke(theme, AccentColorManager.SystemAccent);
}
catch (Exception ex)
{
}
}
private static Task AnimateOpacity(UIElement element, double from, double to, TimeSpan duration)
{
var tcs = new TaskCompletionSource<bool>();
var anim = new DoubleAnimation(from, to, new Duration(duration));
anim.Completed += (s, e) => tcs.SetResult(true);
element.BeginAnimation(UIElement.OpacityProperty, anim);
return tcs.Task;
}
#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);
}
}
}
/// <summary>
/// 更改当前应用程序主题。
/// </summary>
/// <param name="applicationTheme">要设置的主题</param>
/// <param name="backgroundEffect">是否应用自定义背景效果。</param>
/// <param name="updateAccent">是否应更改颜色强调。</param>
public static void ChangeAppTheme(ThemeType applicationTheme, WindowBackdropType backgroundEffect = WindowBackdropType.Mica,
bool updateAccent = true)
{
if (updateAccent)
{
//TODO
AccentColorManager.Apply(
Color.FromRgb(194, 115, 37),
applicationTheme,
false);
}
if (applicationTheme == ThemeType.Unknow)
{
return;
}
ResourceDictionaryManager appDictionaries = new();
var themeDictionaryName = applicationTheme switch
{
ThemeType.Light => "Light",
ThemeType.Dark => "Dark",
ThemeType.HcWhite => "HCWhite",
ThemeType.HcBlack => "HCBlack",
_ => "Light"
};
//ApplyThemeWithAnimation(applicationTheme);
if (!appDictionaries.UpdateDictionary(
"themes",
new Uri($"{ThemesDictionaryPath}{themeDictionaryName}.xaml", UriKind.Absolute)))
{
return;
}
_activeApplicationTheme = applicationTheme;
Changed?.Invoke(applicationTheme, AccentColorManager.SystemAccent);
if (Current.MainWindow is Window mainWindow)
{
WindowBackgroundManager.UpdateBackground(mainWindow, applicationTheme, backgroundEffect);
}
}
/// <summary>
/// 获取当前设置的应用程序主题。
/// </summary>
/// <returns><see cref="ThemeType.Unknow"/> if something goes wrong.</returns>
public static ThemeType GetAppTheme()
{
if (_activeApplicationTheme == ThemeType.Unknow)
{
ResourceDictionaryManager appDictionaries = new();
var themeDictionary = appDictionaries.LookupDictionary("themes");
if (themeDictionary == null)
{
return ThemeType.Unknow;
}
var themeUri = themeDictionary.Source.ToString();
if (themeUri.Contains("light", StringComparison.OrdinalIgnoreCase))
{
_activeApplicationTheme = ThemeType.Light;
}
if (themeUri.Contains("dark", StringComparison.OrdinalIgnoreCase))
{
_activeApplicationTheme = ThemeType.Dark;
}
if (themeUri.Contains("hcwhite", StringComparison.OrdinalIgnoreCase))
{
_activeApplicationTheme = ThemeType.HcWhite;
}
if (themeUri.Contains("hcblack", StringComparison.OrdinalIgnoreCase))
{
_activeApplicationTheme = ThemeType.HcBlack;
}
}
return _activeApplicationTheme;
}
#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}", "WPFluent");
}
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
}