初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
using System.Windows.Media;
|
||||
using PCL.Core.App.IoC;
|
||||
|
||||
namespace PCL.Core.UI.Theme;
|
||||
|
||||
public class CatColorResource
|
||||
{
|
||||
private const string ColorPrefix = "ColorObject";
|
||||
private const string BrushPrefix = "ColorBrush";
|
||||
|
||||
/// <summary>
|
||||
/// 透明度值。
|
||||
/// </summary>
|
||||
public float Alpha { get; init; } = 255;
|
||||
|
||||
/// <summary>
|
||||
/// 红色值。
|
||||
/// </summary>
|
||||
public required float Red { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 绿色值。
|
||||
/// </summary>
|
||||
public required float Green { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 蓝色值。
|
||||
/// </summary>
|
||||
public required float Blue { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 资源后缀。
|
||||
/// </summary>
|
||||
public required string Suffix { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否应用到 <see cref="SolidColorBrush"/> 资源。
|
||||
/// </summary>
|
||||
public bool ApplyToBrush { get; init; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 是否应用到 <see cref="Color"/> 资源。
|
||||
/// </summary>
|
||||
public bool ApplyToColor { get; init; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 应用该颜色到 WPF 资源字典中,仅可在 <see cref="LifecycleState.Loaded"/> 及后面的阶段调用。
|
||||
/// </summary>
|
||||
public void Apply()
|
||||
{
|
||||
var res = Lifecycle.CurrentApplication.Resources;
|
||||
var color = Color.FromScRgb(Alpha, Red, Green, Blue);
|
||||
if (ApplyToColor) res[$"{ColorPrefix}{Suffix}"] = color;
|
||||
if (ApplyToBrush) res[$"{BrushPrefix}{Suffix}"] = new SolidColorBrush(color);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
using System;
|
||||
using Wacton.Unicolour;
|
||||
|
||||
namespace PCL.Core.UI.Theme;
|
||||
|
||||
/// <summary>
|
||||
/// Color definition, cast and other utilities based on OKLAB color space.
|
||||
/// </summary>
|
||||
public class LabColor
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="Unicolour"/> instance for extended use.<br/>
|
||||
/// Please avoid using it. Use <see cref="LabColor"/> implementations instead.
|
||||
/// </summary>
|
||||
internal Unicolour UnicolourInstance { get; }
|
||||
|
||||
private Oklab _Oklab { get => field ??= UnicolourInstance.Oklab; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Light channel.
|
||||
/// </summary>
|
||||
public double L => _Oklab.L;
|
||||
|
||||
/// <summary>
|
||||
/// A channel.
|
||||
/// </summary>
|
||||
public double A => _Oklab.A;
|
||||
|
||||
/// <summary>
|
||||
/// B channel.
|
||||
/// </summary>
|
||||
public double B => _Oklab.B;
|
||||
|
||||
/// <summary>
|
||||
/// Alpha channel.
|
||||
/// </summary>
|
||||
public double Alpha { get; }
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(L, A, B, Alpha);
|
||||
|
||||
// ReSharper disable CompareOfFloatsByEqualityOperator
|
||||
public override bool Equals(object? obj) => (obj is LabColor c) && c.L == L && c.A == A && c.B == B && c.Alpha == Alpha;
|
||||
|
||||
private LabColor(double l, double a, double b, double alpha)
|
||||
{
|
||||
Alpha = alpha;
|
||||
UnicolourInstance = new Unicolour(ColourSpace.Oklab, l, a, b, alpha);
|
||||
}
|
||||
|
||||
private LabColor(Unicolour instance)
|
||||
{
|
||||
Alpha = instance.Alpha.A;
|
||||
UnicolourInstance = instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="LabColor"/> instance from <see cref="Unicolour"/> instance.<br/>
|
||||
/// Please avoid using it. Use <see cref="LabColor.Create(double, double, double, double)"/> instead.
|
||||
/// </summary>
|
||||
internal static LabColor CreateInternal(Unicolour instance) => new(instance);
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="LabColor"/> instance.
|
||||
/// </summary>
|
||||
public static LabColor Create(double l, double a, double b, double alpha = 1.0) => new(l, a, b, alpha);
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="LabColor"/> instance from OKLCH values.
|
||||
/// </summary>
|
||||
public static LabColor FromLch(double l, double c = 0, double h = 0, double alpha = 1.0)
|
||||
{
|
||||
var unicolour = new Unicolour(ColourSpace.Oklch, l, c, h, alpha);
|
||||
return CreateInternal(unicolour);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="LabColor"/> instance from RGB values.<br/>
|
||||
/// <b>NOTE</b>: The alpha value is a number typed double, not byte.
|
||||
/// </summary>
|
||||
public static LabColor FromRgb(byte r, byte g, byte b, double alpha = 1.0)
|
||||
{
|
||||
var unicolour = new Unicolour(ColourSpace.Rgb255, r, g, b, alpha);
|
||||
return CreateInternal(unicolour);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="LabColor"/> instance from ScRGB values.
|
||||
/// </summary>
|
||||
public static LabColor FromRgb(double r, double g, double b, double alpha = 1.0)
|
||||
{
|
||||
var unicolour = new Unicolour(ColourSpace.RgbLinear, r, g, b, alpha);
|
||||
return CreateInternal(unicolour);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="LabColor"/> instance from <see cref="System.Windows.Media.Color"/>.
|
||||
/// </summary>
|
||||
public static LabColor FromWpfColor(System.Windows.Media.Color color)
|
||||
=> FromRgb(color.ScR, color.ScG, color.ScB, color.ScA);
|
||||
|
||||
public static implicit operator LabColor(System.Windows.Media.Color color) => FromWpfColor(color);
|
||||
|
||||
/// <summary>
|
||||
/// ScRGB 映射模式.
|
||||
/// </summary>
|
||||
public enum ScRgbMappingMode
|
||||
{
|
||||
/// <summary>
|
||||
/// 禁用,可能导致色彩不准确
|
||||
/// </summary>
|
||||
Disable = -1,
|
||||
/// <summary>
|
||||
/// 截断到 sRGB 范围内,可能导致某些较为鲜艳的颜色变灰
|
||||
/// </summary>
|
||||
Clip = GamutMap.RgbClipping,
|
||||
/// <summary>
|
||||
/// 在 OKLCH 色彩定义下降低色度 (Chroma) 到 sRGB 范围内
|
||||
/// </summary>
|
||||
ReduceChroma = GamutMap.OklchChromaReduction,
|
||||
/// <summary>
|
||||
/// 在 WXY 色彩定义下降低纯度 (Purity) 到 sRGB 范围内
|
||||
/// </summary>
|
||||
ReducePurity = GamutMap.WxyPurityReduction,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set conversion mapping of <see cref="ToScRgb"/> method and <see cref="ScRgb"/> property.
|
||||
/// </summary>
|
||||
public static ScRgbMappingMode ScRgbMapping { get; set; } = ScRgbMappingMode.ReduceChroma;
|
||||
|
||||
/// <summary>
|
||||
/// Convert to ScRGB values.<br/>
|
||||
/// <b>NOTE</b>: Use <see cref="ScRgb"/> property for better performance.
|
||||
/// </summary>
|
||||
/// <seealso cref="ScRgbMapping"/>
|
||||
public (float Alpha, float Red, float Green, float Blue) ToScRgb()
|
||||
{
|
||||
var a = UnicolourInstance.Alpha.A;
|
||||
var instance = (ScRgbMapping == ScRgbMappingMode.Disable)
|
||||
? UnicolourInstance
|
||||
: UnicolourInstance.MapToRgbGamut((GamutMap)ScRgbMapping);
|
||||
var (r, g, b) = instance.RgbLinear;
|
||||
return ((float)a, (float)r, (float)g, (float)b);
|
||||
}
|
||||
|
||||
private bool _isScRgbInitialized = false;
|
||||
|
||||
/// <summary>
|
||||
/// Cached ScRGB values.
|
||||
/// </summary>
|
||||
public (float Alpha, float Red, float Green, float Blue) ScRgb
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isScRgbInitialized) return field;
|
||||
_isScRgbInitialized = true;
|
||||
return field = ToScRgb();
|
||||
}
|
||||
} = default;
|
||||
|
||||
/// <summary>
|
||||
/// Convert to <see cref="System.Windows.Media.Color"/>.
|
||||
/// </summary>
|
||||
public System.Windows.Media.Color ToWpfColor()
|
||||
{
|
||||
var (a, r, g, b) = ScRgb;
|
||||
return System.Windows.Media.Color.FromScRgb(a, r, g, b);
|
||||
}
|
||||
|
||||
public static implicit operator System.Windows.Media.Color(LabColor lab) => lab.ToWpfColor();
|
||||
|
||||
/// <summary>
|
||||
/// Convert to <see cref="CatColorResource"/>.
|
||||
/// </summary>
|
||||
/// <param name="suffix">See <see cref="CatColorResource.Suffix"/></param>
|
||||
/// <param name="applyToColor">See <see cref="CatColorResource.ApplyToColor"/></param>
|
||||
/// <param name="applyToBrush">See <see cref="CatColorResource.ApplyToBrush"/></param>
|
||||
public CatColorResource ToCatColor(string suffix, bool applyToColor = true, bool applyToBrush = true)
|
||||
{
|
||||
var (a, r, g, b) = ScRgb;
|
||||
return new CatColorResource
|
||||
{
|
||||
Red = r,
|
||||
Green = g,
|
||||
Blue = b,
|
||||
Alpha = a,
|
||||
ApplyToBrush = applyToBrush,
|
||||
ApplyToColor = applyToColor,
|
||||
Suffix = suffix
|
||||
};
|
||||
}
|
||||
|
||||
internal static Unicolour MixInternal(LabColor c1, LabColor c2, double c1Amount = 0.5)
|
||||
=> c1.UnicolourInstance.Mix(c2.UnicolourInstance, ColourSpace.Oklab, c1Amount);
|
||||
|
||||
/// <summary>
|
||||
/// Mix two colors.
|
||||
/// </summary>
|
||||
/// <param name="c1">Color 1</param>
|
||||
/// <param name="c2">Color 2</param>
|
||||
/// <param name="c1Amount">The amount of color 1 in mixture (0.0~1.0), as color 2 takes the rest</param>
|
||||
/// <returns></returns>
|
||||
public static LabColor Mix(LabColor c1, LabColor c2, double c1Amount = 0.5) => CreateInternal(MixInternal(c1, c2, c1Amount));
|
||||
|
||||
/// <summary>
|
||||
/// Mix two colors, see <see cref="Mix"/>.
|
||||
/// </summary>
|
||||
public static LabColor operator +(LabColor c1, LabColor c2) => Mix(c1, c2);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security;
|
||||
using Microsoft.Win32;
|
||||
using PCL.Core.Logging;
|
||||
|
||||
namespace PCL.Core.UI.Theme;
|
||||
|
||||
public static class SystemThemeHelper {
|
||||
private const string ThemeRegistryPath = @"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
|
||||
private const string AppsUseLightThemeKey = "AppsUseLightTheme";
|
||||
|
||||
/// <summary>
|
||||
/// 检查系统是否处于深色模式。
|
||||
/// </summary>
|
||||
/// <returns>如果系统使用深色模式,则返回 true;否则返回 false(包括注册表不可访问的情况)。</returns>
|
||||
public static bool IsSystemInDarkMode() {
|
||||
try {
|
||||
using var registryKey = Registry.CurrentUser.OpenSubKey(ThemeRegistryPath);
|
||||
if (registryKey is null) {
|
||||
LogWrapper.Warn($"注册表键 {ThemeRegistryPath} 不存在");
|
||||
return false;
|
||||
}
|
||||
|
||||
var value = registryKey.GetValue(AppsUseLightThemeKey) as int?;
|
||||
return value == 0; // 0 表示深色模式(AppsUseLightTheme = false)
|
||||
} catch (Exception ex) when (ex is SecurityException or IOException) {
|
||||
LogWrapper.Warn(ex, $"无法访问注册表键 {ThemeRegistryPath}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Use ThemeService.IsDarkMode instead")]
|
||||
public static bool IsDarkMode() => ThemeService.IsDarkMode;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace PCL.Core.UI.Theme;
|
||||
|
||||
public record ToneProfile(
|
||||
double L1 = 0.35,
|
||||
double L2 = 0.5,
|
||||
double L3 = 0.575,
|
||||
double L4 = 0.65,
|
||||
double L5 = 0.8,
|
||||
double L6 = 0.92,
|
||||
double L7 = 0.94,
|
||||
double L8 = 0.96,
|
||||
double LWhite = 1,
|
||||
double LForeground = 0,
|
||||
double LBackground = 0.995,
|
||||
double C1 = 0.025,
|
||||
double C2 = 0.188,
|
||||
double C3 = 0.213,
|
||||
double C4 = 0.168,
|
||||
double C5 = 0.093,
|
||||
double C6 = 0.036,
|
||||
double C7 = 0.028,
|
||||
double C8 = 0.018,
|
||||
double ASemiWhite = 0.733,
|
||||
double AHalfWhite = 0.333,
|
||||
double ASemiTransparent = 0.004,
|
||||
double AHalfTransparent = 0.5,
|
||||
double ATransparent = 0,
|
||||
double ABackground = 0.824,
|
||||
double AToolTip = 0.9
|
||||
);
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace PCL.Core.UI.Theme;
|
||||
|
||||
public record ToneProfileConfig
|
||||
{
|
||||
public static readonly ToneProfile DefaultLight = new();
|
||||
|
||||
public static readonly ToneProfile DefaultDark = new(
|
||||
L1: 0.96, L2: 0.75, L3: 0.6, L4: 0.65,
|
||||
L5: 0.45, L6: 0.25, L7: 0.225, L8: 0.2,
|
||||
LBackground: 0.3, LForeground: 1, LWhite: 0.275
|
||||
);
|
||||
|
||||
public ToneProfile Light { get => field ?? DefaultLight; init; } = null!;
|
||||
|
||||
public ToneProfile Dark { get => field ?? DefaultDark; init; } = null!;
|
||||
}
|
||||
Reference in New Issue
Block a user