初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user