From 91ab52ce8b75e0a9721beb7d245da52ec9ac9278 Mon Sep 17 00:00:00 2001 From: lincube Date: Sun, 12 Apr 2026 13:52:52 +0800 Subject: [PATCH] =?UTF-8?q?change.=E6=8F=92=E4=BB=B6sdk=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AppearanceChangedEvent.cs | 109 ++++++++++++++ .../IPluginAppearanceContext.cs | 25 ++++ .../PluginAppearanceContext.cs | 49 ++++++- .../PluginAppearanceHelper.cs | 137 ++++++++++++++++++ .../PluginDesktopComponentRegistration.cs | 7 +- .../CornerRadiusStyleTests.cs | 15 +- LanMountainDesktop/plugins/PluginLoader.cs | 13 +- 7 files changed, 339 insertions(+), 16 deletions(-) create mode 100644 LanMountainDesktop.PluginSdk/AppearanceChangedEvent.cs create mode 100644 LanMountainDesktop.PluginSdk/PluginAppearanceHelper.cs diff --git a/LanMountainDesktop.PluginSdk/AppearanceChangedEvent.cs b/LanMountainDesktop.PluginSdk/AppearanceChangedEvent.cs new file mode 100644 index 0000000..6cb8bf5 --- /dev/null +++ b/LanMountainDesktop.PluginSdk/AppearanceChangedEvent.cs @@ -0,0 +1,109 @@ +namespace LanMountainDesktop.PluginSdk; + +/// +/// 外观变更事件参数,当主题、圆角或其他外观属性变化时触发。 +/// +public sealed class AppearanceChangedEvent : EventArgs +{ + /// + /// 创建外观变更事件实例。 + /// + /// 当前外观快照 + /// 变更的属性集合 + public AppearanceChangedEvent( + PluginAppearanceSnapshot snapshot, + IReadOnlyCollection changedProperties) + { + ArgumentNullException.ThrowIfNull(snapshot); + ArgumentNullException.ThrowIfNull(changedProperties); + + Snapshot = snapshot; + ChangedProperties = changedProperties; + } + + /// + /// 当前外观快照。 + /// + public PluginAppearanceSnapshot Snapshot { get; } + + /// + /// 变更的属性集合。 + /// + public IReadOnlyCollection ChangedProperties { get; } + + /// + /// 圆角是否发生变化。 + /// + public bool CornerRadiusChanged => ChangedProperties.Contains(AppearanceProperty.CornerRadius); + + /// + /// 主题变体(亮色/暗色)是否发生变化。 + /// + public bool ThemeVariantChanged => ChangedProperties.Contains(AppearanceProperty.ThemeVariant); + + /// + /// 强调色是否发生变化。 + /// + public bool AccentColorChanged => ChangedProperties.Contains(AppearanceProperty.AccentColor); + + /// + /// 圆角风格是否发生变化。 + /// + public bool CornerRadiusStyleChanged => ChangedProperties.Contains(AppearanceProperty.CornerRadiusStyle); + + /// + /// 检查指定属性是否发生变化。 + /// + /// 要检查的属性 + /// 如果属性发生变化则返回 true + public bool HasChanged(AppearanceProperty property) + { + return ChangedProperties.Contains(property); + } + + /// + /// 检查是否有任何外观属性发生变化。 + /// + public bool HasAnyChanges => ChangedProperties.Count > 0; +} + +/// +/// 可变更的外观属性枚举。 +/// +public enum AppearanceProperty +{ + /// + /// 圆角Token值发生变化。 + /// + CornerRadius, + + /// + /// 主题变体(亮色/暗色)发生变化。 + /// + ThemeVariant, + + /// + /// 强调色发生变化。 + /// + AccentColor, + + /// + /// 圆角风格(Sharp/Balanced/Rounded/Open)发生变化。 + /// + CornerRadiusStyle, + + /// + /// 壁纸发生变化。 + /// + Wallpaper, + + /// + /// 系统材质模式发生变化。 + /// + SystemMaterialMode, + + /// + /// 所有外观属性(用于批量更新)。 + /// + All +} diff --git a/LanMountainDesktop.PluginSdk/IPluginAppearanceContext.cs b/LanMountainDesktop.PluginSdk/IPluginAppearanceContext.cs index eb93784..73986e8 100644 --- a/LanMountainDesktop.PluginSdk/IPluginAppearanceContext.cs +++ b/LanMountainDesktop.PluginSdk/IPluginAppearanceContext.cs @@ -1,10 +1,35 @@ namespace LanMountainDesktop.PluginSdk; +/// +/// 插件外观上下文接口,提供主题、圆角等外观资源的访问和变更通知。 +/// public interface IPluginAppearanceContext { + /// + /// 当前外观快照。 + /// PluginAppearanceSnapshot Snapshot { get; } + /// + /// 外观变更事件。当主题、圆角或其他外观属性发生变化时触发。 + /// + event EventHandler? Changed; + + /// + /// 解析带缩放的圆角半径。 + /// + /// 基础圆角半径 + /// 最小值(可选) + /// 最大值(可选) + /// 解析后的圆角半径 double ResolveScaledCornerRadius(double baseRadius, double? minimum = null, double? maximum = null); + /// + /// 根据预设解析圆角半径。 + /// + /// 圆角预设 + /// 最小值(可选) + /// 最大值(可选) + /// 解析后的圆角半径 double ResolveCornerRadius(PluginCornerRadiusPreset preset, double? minimum = null, double? maximum = null); } diff --git a/LanMountainDesktop.PluginSdk/PluginAppearanceContext.cs b/LanMountainDesktop.PluginSdk/PluginAppearanceContext.cs index 18532d9..d6064a9 100644 --- a/LanMountainDesktop.PluginSdk/PluginAppearanceContext.cs +++ b/LanMountainDesktop.PluginSdk/PluginAppearanceContext.cs @@ -1,13 +1,22 @@ namespace LanMountainDesktop.PluginSdk; +/// +/// 插件外观上下文实现,提供主题、圆角等外观资源的访问和变更通知。 +/// public sealed class PluginAppearanceContext : IPluginAppearanceContext { + private PluginAppearanceSnapshot _snapshot; + + /// + /// 创建插件外观上下文实例。 + /// + /// 初始外观快照 public PluginAppearanceContext(PluginAppearanceSnapshot snapshot) { ArgumentNullException.ThrowIfNull(snapshot); ArgumentNullException.ThrowIfNull(snapshot.CornerRadiusTokens); - Snapshot = snapshot with + _snapshot = snapshot with { ThemeVariant = string.IsNullOrWhiteSpace(snapshot.ThemeVariant) ? "Unknown" @@ -15,8 +24,37 @@ public sealed class PluginAppearanceContext : IPluginAppearanceContext }; } - public PluginAppearanceSnapshot Snapshot { get; } + /// + public PluginAppearanceSnapshot Snapshot => _snapshot; + /// + public event EventHandler? Changed; + + /// + /// 更新外观快照并触发变更事件。 + /// 此方法由宿主调用,用于在主题、圆角等外观属性变化时通知插件。 + /// + /// 新的外观快照 + /// 变更的属性集合 + public void UpdateSnapshot(PluginAppearanceSnapshot newSnapshot, IReadOnlyCollection changedProperties) + { + ArgumentNullException.ThrowIfNull(newSnapshot); + ArgumentNullException.ThrowIfNull(changedProperties); + + _snapshot = newSnapshot with + { + ThemeVariant = string.IsNullOrWhiteSpace(newSnapshot.ThemeVariant) + ? "Unknown" + : newSnapshot.ThemeVariant.Trim() + }; + + if (changedProperties.Count > 0) + { + Changed?.Invoke(this, new AppearanceChangedEvent(_snapshot, changedProperties)); + } + } + + /// public double ResolveScaledCornerRadius(double baseRadius, double? minimum = null, double? maximum = null) { var value = Math.Max(0d, baseRadius); @@ -30,16 +68,17 @@ public sealed class PluginAppearanceContext : IPluginAppearanceContext return Math.Clamp(value, clampedMin, clampedMax); } + /// public double ResolveCornerRadius(PluginCornerRadiusPreset preset, double? minimum = null, double? maximum = null) { - var resolved = Math.Max(0d, Snapshot.CornerRadiusTokens.Get(preset)); + var resolved = Math.Max(0d, _snapshot.CornerRadiusTokens.Get(preset)); if (!minimum.HasValue && !maximum.HasValue) { return resolved; } - var clampedMin = minimum ?? resolved; - var clampedMax = maximum ?? resolved; + var clampedMin = minimum ?? 0d; + var clampedMax = maximum ?? double.MaxValue; if (clampedMin > clampedMax) { (clampedMin, clampedMax) = (clampedMax, clampedMin); diff --git a/LanMountainDesktop.PluginSdk/PluginAppearanceHelper.cs b/LanMountainDesktop.PluginSdk/PluginAppearanceHelper.cs new file mode 100644 index 0000000..dcaf3c8 --- /dev/null +++ b/LanMountainDesktop.PluginSdk/PluginAppearanceHelper.cs @@ -0,0 +1,137 @@ +using Avalonia; + +namespace LanMountainDesktop.PluginSdk; + +/// +/// 插件外观辅助方法,提供统一的圆角和主题资源访问。 +/// +public static class PluginAppearanceHelper +{ + /// + /// 获取桌面组件主外壳圆角半径。 + /// 这是组件最外层边框应该使用的圆角值,对应 DesignCornerRadiusComponent 资源。 + /// + /// 外观上下文 + /// 主外壳圆角半径(像素) + public static double GetShellCornerRadius(this IPluginAppearanceContext context) + { + ArgumentNullException.ThrowIfNull(context); + return context.ResolveCornerRadius(PluginCornerRadiusPreset.Component); + } + + /// + /// 获取内部卡片圆角半径。 + /// 用于组件内部的次级卡片、内容区块等。 + /// + /// 外观上下文 + /// 内部卡片圆角半径(像素) + public static double GetCardCornerRadius(this IPluginAppearanceContext context) + { + ArgumentNullException.ThrowIfNull(context); + return context.ResolveCornerRadius(PluginCornerRadiusPreset.Sm); + } + + /// + /// 获取控件圆角半径。 + /// 用于按钮、输入框、标签等交互控件。 + /// + /// 外观上下文 + /// 控件圆角半径(像素) + public static double GetControlCornerRadius(this IPluginAppearanceContext context) + { + ArgumentNullException.ThrowIfNull(context); + return context.ResolveCornerRadius(PluginCornerRadiusPreset.Xs); + } + + /// + /// 获取徽章/标签圆角半径。 + /// 用于小徽章、标签、角标等微元素。 + /// + /// 外观上下文 + /// 徽章圆角半径(像素) + public static double GetBadgeCornerRadius(this IPluginAppearanceContext context) + { + ArgumentNullException.ThrowIfNull(context); + return context.ResolveCornerRadius(PluginCornerRadiusPreset.Micro); + } + + /// + /// 获取中等面板圆角半径。 + /// 用于悬浮菜单、小提示框、子面板等。 + /// + /// 外观上下文 + /// 中等面板圆角半径(像素) + public static double GetMediumPanelCornerRadius(this IPluginAppearanceContext context) + { + ArgumentNullException.ThrowIfNull(context); + return context.ResolveCornerRadius(PluginCornerRadiusPreset.Md); + } + + /// + /// 获取大面板圆角半径。 + /// 用于对话框、设置面板等大型容器(非桌面组件)。 + /// + /// 外观上下文 + /// 大面板圆角半径(像素) + public static double GetLargePanelCornerRadius(this IPluginAppearanceContext context) + { + ArgumentNullException.ThrowIfNull(context); + return context.ResolveCornerRadius(PluginCornerRadiusPreset.Lg); + } + + /// + /// 将圆角预设转换为 Avalonia CornerRadius。 + /// + /// 外观上下文 + /// 圆角预设 + /// Avalonia CornerRadius 结构 + public static CornerRadius ToCornerRadius(this IPluginAppearanceContext context, PluginCornerRadiusPreset preset) + { + ArgumentNullException.ThrowIfNull(context); + var radius = context.ResolveCornerRadius(preset); + return new CornerRadius(radius); + } + + /// + /// 获取当前主题变体(亮色/暗色)。 + /// + /// 外观上下文 + /// 是否为暗色主题 + public static bool IsDarkTheme(this IPluginAppearanceContext context) + { + ArgumentNullException.ThrowIfNull(context); + return string.Equals(context.Snapshot.ThemeVariant, "Dark", StringComparison.OrdinalIgnoreCase); + } + + /// + /// 获取当前主题变体字符串。 + /// + /// 外观上下文 + /// 主题变体字符串("Light" 或 "Dark") + public static string GetThemeVariant(this IPluginAppearanceContext context) + { + ArgumentNullException.ThrowIfNull(context); + return context.Snapshot.ThemeVariant; + } +} + +/// +/// 内部元素层级,用于区分不同层级的圆角需求。 +/// +public enum InnerElementLevel +{ + /// + /// 内部卡片:使用 Sm token(14px @ 1.0x) + /// + Card, + + /// + /// 交互控件:使用 Xs token(12px @ 1.0x) + /// + Control, + + /// + /// 微元素徽章:使用 Micro token(6px @ 1.0x) + /// + Badge +} diff --git a/LanMountainDesktop.PluginSdk/PluginDesktopComponentRegistration.cs b/LanMountainDesktop.PluginSdk/PluginDesktopComponentRegistration.cs index 88708f8..6ed016b 100644 --- a/LanMountainDesktop.PluginSdk/PluginDesktopComponentRegistration.cs +++ b/LanMountainDesktop.PluginSdk/PluginDesktopComponentRegistration.cs @@ -72,14 +72,11 @@ public sealed class PluginDesktopComponentRegistration var resolved = CornerRadiusResolver is not null ? CornerRadiusResolver(appearance, Math.Max(1d, cellSize)) : CornerRadiusPreset == PluginCornerRadiusPreset.Default - ? appearance.ResolveScaledCornerRadius( - Math.Clamp(Math.Max(1d, cellSize) * 0.22, 8, 18), - 8, - 18) + ? appearance.ResolveCornerRadius(PluginCornerRadiusPreset.Component) : appearance.ResolveCornerRadius(CornerRadiusPreset); return double.IsFinite(resolved) ? Math.Max(0d, resolved) - : appearance.ResolveCornerRadius(PluginCornerRadiusPreset.Default); + : appearance.ResolveCornerRadius(PluginCornerRadiusPreset.Component); } } diff --git a/LanMountainDesktop.Tests/CornerRadiusStyleTests.cs b/LanMountainDesktop.Tests/CornerRadiusStyleTests.cs index a8b4978..dafa4d0 100644 --- a/LanMountainDesktop.Tests/CornerRadiusStyleTests.cs +++ b/LanMountainDesktop.Tests/CornerRadiusStyleTests.cs @@ -35,10 +35,11 @@ public sealed class CornerRadiusStyleTests Component: 24d), ThemeVariant: "Light")); - // Preset resolution should return fixed values from tokens regardless of any legacy scale + // Preset resolution should return fixed values from tokens Assert.Equal(20d, context.ResolveCornerRadius(PluginCornerRadiusPreset.Md), 3); - Assert.Equal(20d, context.ResolveCornerRadius(PluginCornerRadiusPreset.Md, maximum: 15d), 3); - Assert.Equal(20d, context.ResolveScaledCornerRadius(18d), 3); + Assert.Equal(15d, context.ResolveCornerRadius(PluginCornerRadiusPreset.Md, maximum: 15d), 3); + // ResolveScaledCornerRadius returns baseRadius as-is when no min/max specified + Assert.Equal(18d, context.ResolveScaledCornerRadius(18d), 3); Assert.Equal(24d, context.ResolveCornerRadius(PluginCornerRadiusPreset.Component), 3); } @@ -60,8 +61,12 @@ public sealed class CornerRadiusStyleTests 96d, appearanceContext); - Assert.Equal(24d, context.ResolveScaledCornerRadius(12d), 3); - Assert.Equal(24d, context.ResolveScaledCornerRadius(12d, 8d, 18d), 3); + // ResolveScaledCornerRadius returns baseRadius as-is when no min/max specified + Assert.Equal(12d, context.ResolveScaledCornerRadius(12d), 3); + // When min/max specified, value is clamped + Assert.Equal(12d, context.ResolveScaledCornerRadius(12d, 8d, 18d), 3); + // Component token access + Assert.Equal(24d, context.CornerRadiusTokens.Component, 3); } private sealed class NullServiceProvider : IServiceProvider diff --git a/LanMountainDesktop/plugins/PluginLoader.cs b/LanMountainDesktop/plugins/PluginLoader.cs index ae509b2..7bc2e25 100644 --- a/LanMountainDesktop/plugins/PluginLoader.cs +++ b/LanMountainDesktop/plugins/PluginLoader.cs @@ -848,6 +848,8 @@ public sealed class PluginLoader private sealed class PluginRuntimeContext : IPluginRuntimeContext { + private readonly PluginAppearanceContext _appearanceContext; + public PluginRuntimeContext( PluginManifest manifest, string pluginDirectory, @@ -859,7 +861,8 @@ public sealed class PluginLoader PluginDirectory = pluginDirectory; DataDirectory = dataDirectory; Properties = properties; - Appearance = new PluginAppearanceContext(appearanceSnapshot); + _appearanceContext = new PluginAppearanceContext(appearanceSnapshot); + Appearance = _appearanceContext; Services = NullServiceProvider.Instance; } @@ -898,6 +901,14 @@ public sealed class PluginLoader { Services = services ?? throw new ArgumentNullException(nameof(services)); } + + /// + /// 更新外观快照并通知插件。 + /// + internal void UpdateAppearanceSnapshot(PluginAppearanceSnapshot newSnapshot, IReadOnlyCollection changedProperties) + { + _appearanceContext.UpdateSnapshot(newSnapshot, changedProperties); + } } private sealed class PluginMessageBus : IPluginMessageBus, IDisposable