Files
MRCC/launcher/PCL-CE/PCL.Core/UI/Theme/ThemeService.cs
T

247 lines
9.5 KiB
C#
Raw Normal View History

using System;
using PCL.Core.App;
using PCL.Core.App.Configuration;
using PCL.Core.App.IoC;
namespace PCL.Core.UI.Theme;
/// <summary>
/// 配色模式更改事件。
/// </summary>
/// <param name="isDarkMode">当前是否为深色模式</param>
/// <param name="theme">当前配色主题</param>
public delegate void ColorModeChangedEvent(bool isDarkMode, ColorTheme theme);
/// <summary>
/// 配色主题更改事件。
/// </summary>
/// <param name="theme">当前配色主题</param>
public delegate void ColorThemeChangedEvent(ColorTheme theme);
[LifecycleScope("theme", "主题", false)]
[LifecycleService(LifecycleState.WindowCreating)]
public sealed partial class ThemeService
{
[AnyConfigItem<ToneProfileConfig>("UiToneProfiles", ConfigSource.Local)]
public static partial ToneProfileConfig ToneProfiles { get; set; }
[RegisterConfigEvent]
public static ConfigEventRegistry OnColorModeConfigChanged => new(
scope: Config.Preference.Theme.ColorModeConfig,
trigger: ConfigEvent.Update,
handler: _ => RefreshColorMode()
);
[RegisterConfigEvent]
public static ConfigEventRegistry OnColorThemeConfigChanged => new(
scope: [Config.Preference.Theme.DarkColorConfig, Config.Preference.Theme.LightColorConfig],
trigger: ConfigEvent.Update,
handler: e =>
{
// ignore no change or non-current color theme change
if (e.OldValue == e.Value) return;
if (IsDarkMode) { if (e.Item == Config.Preference.Theme.LightColorConfig) return; }
else { if (e.Item == Config.Preference.Theme.DarkColorConfig) return; }
// trigger color refresh
if (Lifecycle.CurrentState > LifecycleState.Loading)
{
Lifecycle.CurrentApplication.Dispatcher.BeginInvoke(() =>
{
ApplyColorResources();
ColorThemeChanged?.Invoke(CurrentTheme);
_AprilFoolLogic();
});
}
}
);
[LifecycleStart]
private static void _Start()
{
IsDarkMode = _IsDarkMode();
_LogStatus();
_RefreshAll();
}
private static void _LogStatus()
{
Context.Debug($"当前状态: {(IsDarkMode ? "Dark" : "Light")}, {CurrentTheme}");
}
private static bool _IsDarkMode() => Config.Preference.Theme.ColorMode switch
{
ColorMode.Light => false,
ColorMode.Dark => true,
ColorMode.System => SystemThemeHelper.IsSystemInDarkMode(),
_ => false
};
/// <summary>
/// 当前是否为深色主题。
/// </summary>
public static bool IsDarkMode { get; private set; }
/// <summary>
/// 配色模式更改事件。
/// </summary>
public static event ColorModeChangedEvent? ColorModeChanged;
/// <summary>
/// 配色主题更改事件。
/// </summary>
public static event ColorThemeChangedEvent? ColorThemeChanged;
private static void _RefreshAll()
{
ApplyGrayResources();
ApplyColorResources();
ColorModeChanged?.Invoke(IsDarkMode, CurrentTheme);
_AprilFoolLogic();
}
private static void _AprilFoolLogic()
{
// for HMCL theme on April Fools' Day
if (Basics.IsAprilFool) Config.Preference.WindowTitleTypeConfig.TriggerEvent(ConfigEvent.Changed, null);
else if (CurrentTheme == ColorTheme.HmclBlue) CurrentTheme = ColorTheme.CatBlue;
}
/// <summary>
/// 刷新配色模式,若检测到当前配色模式有实际更改,则会触发主题刷新。
/// </summary>
public static void RefreshColorMode()
{
var isDarkMode = _IsDarkMode();
if (IsDarkMode == isDarkMode) return;
Context.Info("正在更改配色模式");
IsDarkMode = isDarkMode;
_LogStatus();
if (Lifecycle.CurrentState > LifecycleState.Loading)
{
Lifecycle.CurrentApplication.Dispatcher.BeginInvoke(_RefreshAll);
}
}
/// <summary>
/// 当前使用的色彩属性。
/// </summary>
public static ToneProfile CurrentTone => IsDarkMode ? ToneProfiles.Dark : ToneProfiles.Light;
/// <summary>
/// 当前使用的主题色。
/// </summary>
public static ColorTheme CurrentTheme
{
get
{
var theme = Config.Preference.Theme;
return IsDarkMode ? theme.DarkColor : theme.LightColor;
}
set
{
var theme = Config.Preference.Theme;
var config = IsDarkMode ? theme.DarkColorConfig : theme.LightColorConfig;
config.SetValue(value);
}
}
/// <summary>
/// 获取当前色彩主题对应的各种参数。
/// </summary>
public static (int Hue, double LightAdjust, double ChromaAdjust) GetCurrentThemeArgs()
{
var theme = CurrentTheme;
return theme switch
{
ColorTheme.SkyBlue => (235, 0.36, 0.2),
ColorTheme.CatBlue => (255, 0, -0.2),
ColorTheme.DeathBlue => (268, -0.05, -0.1),
ColorTheme.HmclBlue => (275, -0.03, -0.35),
#if DEBUG
_ => ((int)theme, 0, 0)
#else
_ => throw new IndexOutOfRangeException($"Invalid theme index: {(int)theme}")
#endif
};
}
private static double _AdjustLinear(double value, double adjustment)
{
if (adjustment == 0) return value;
// 确保输入在合理范围内
value = Math.Clamp(value, 0.0, 1.0);
adjustment = Math.Clamp(adjustment, -1.0, 1.0);
// 非对称线性插值
return adjustment switch
{
> 0 => value + (1.0 - value) * adjustment,
_ => value + value * adjustment
};
}
private static CatColorResource[] _CalculateGrays(ToneProfile tone) => [
LabColor.FromLch(tone.L1).ToCatColor("Gray1"),
LabColor.FromLch(tone.L2).ToCatColor("Gray2"),
LabColor.FromLch(tone.L3).ToCatColor("Gray3"),
LabColor.FromLch(tone.L4).ToCatColor("Gray4"),
LabColor.FromLch(tone.L5).ToCatColor("Gray5"),
LabColor.FromLch(tone.L6).ToCatColor("Gray6"),
LabColor.FromLch(tone.L7).ToCatColor("Gray7"),
LabColor.FromLch(tone.L8).ToCatColor("Gray8"),
LabColor.FromLch(tone.LWhite, alpha:tone.AHalfWhite).ToCatColor("HalfWhite", false),
LabColor.FromLch(tone.LWhite, alpha:tone.ASemiWhite).ToCatColor("SemiWhite", false),
LabColor.FromLch(tone.LWhite).ToCatColor("White", false),
LabColor.FromLch(tone.LWhite, alpha:tone.ATransparent).ToCatColor("Transparent", false),
LabColor.FromLch(tone.LBackground, alpha:tone.ABackground).ToCatColor("TransparentBackground", false),
LabColor.FromLch(tone.LBackground).ToCatColor("Background", false),
LabColor.FromLch(tone.LBackground, alpha:tone.AToolTip).ToCatColor("ToolTip", false),
LabColor.FromLch(tone.L7, 0.25, 30, tone.AHalfTransparent).ToCatColor("RedBack", false),
LabColor.FromLch(tone.LForeground).ToCatColor("Memory", false),
];
private static CatColorResource[] _CalculateColors(ToneProfile tone, (int hue, double lightAdj, double chromaAdj) args) => [
LabColor.FromLch(_AdjustLinear(tone.L1, args.lightAdj * 0.1), _AdjustLinear(tone.C1, args.chromaAdj * 0.25), args.hue).ToCatColor("1"),
LabColor.FromLch(_AdjustLinear(tone.L2, args.lightAdj), _AdjustLinear(tone.C2, args.chromaAdj), args.hue).ToCatColor("2"),
LabColor.FromLch(_AdjustLinear(tone.L3, args.lightAdj), _AdjustLinear(tone.C3, args.chromaAdj), args.hue).ToCatColor("3"),
LabColor.FromLch(_AdjustLinear(tone.L4, args.lightAdj), _AdjustLinear(tone.C4, args.chromaAdj), args.hue).ToCatColor("4"),
LabColor.FromLch(_AdjustLinear(tone.L5, args.lightAdj), _AdjustLinear(tone.C5, args.chromaAdj), args.hue).ToCatColor("5"),
LabColor.FromLch(_AdjustLinear(tone.L6, args.lightAdj), _AdjustLinear(tone.C6, args.chromaAdj), args.hue).ToCatColor("6"),
LabColor.FromLch(_AdjustLinear(tone.L7, args.lightAdj), _AdjustLinear(tone.C7, args.chromaAdj), args.hue).ToCatColor("7"),
LabColor.FromLch(_AdjustLinear(tone.L8, args.lightAdj), _AdjustLinear(tone.C8, args.chromaAdj), args.hue).ToCatColor("8"),
LabColor.FromLch(_AdjustLinear(tone.L8, args.lightAdj), _AdjustLinear(tone.C8, args.chromaAdj), args.hue, tone.ASemiTransparent).ToCatColor("SemiTransparent", false),
LabColor.FromLch(_AdjustLinear(tone.L5, args.lightAdj), _AdjustLinear(tone.C5, args.chromaAdj), args.hue).ToCatColor("Bg0"),
LabColor.FromLch(_AdjustLinear(tone.L7, args.lightAdj), _AdjustLinear(tone.C7, args.chromaAdj), args.hue, tone.ASemiWhite).ToCatColor("Bg1"),
];
private static CatColorResource[] LightGrayCache { get => field ??= _CalculateGrays(ToneProfiles.Light); set; } = null!;
private static CatColorResource[] DarkGrayCache { get => field ??= _CalculateGrays(ToneProfiles.Dark); set; } = null!;
/// <summary>
/// 清除灰度配色的计算缓存。
/// </summary>
public static void InvalidateGrayCache()
{
LightGrayCache = null!;
DarkGrayCache = null!;
}
/// <summary>
/// 应用灰度配色到 WPF 资源字典。
/// </summary>
public static void ApplyGrayResources()
{
var cache = IsDarkMode ? DarkGrayCache : LightGrayCache;
foreach (var c in cache) c.Apply();
}
/// <summary>
/// 应用彩色配色到 WPF 资源字典。
/// </summary>
public static void ApplyColorResources()
{
var colors = _CalculateColors(CurrentTone, GetCurrentThemeArgs());
foreach (var c in colors) c.Apply();
}
}