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