mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
0.7.1
This commit is contained in:
10
LanMountainDesktop.PluginSdk/IPluginAppearanceContext.cs
Normal file
10
LanMountainDesktop.PluginSdk/IPluginAppearanceContext.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
|
||||
public interface IPluginAppearanceContext
|
||||
{
|
||||
PluginAppearanceSnapshot Snapshot { get; }
|
||||
|
||||
double ResolveScaledCornerRadius(double baseRadius, double? minimum = null, double? maximum = null);
|
||||
|
||||
double ResolveCornerRadius(PluginCornerRadiusPreset preset, double? minimum = null, double? maximum = null);
|
||||
}
|
||||
@@ -12,6 +12,8 @@ public interface IPluginRuntimeContext
|
||||
|
||||
IReadOnlyDictionary<string, object?> Properties { get; }
|
||||
|
||||
IPluginAppearanceContext Appearance { get; }
|
||||
|
||||
T? GetService<T>();
|
||||
|
||||
bool TryGetProperty<T>(string key, out T? value);
|
||||
|
||||
49
LanMountainDesktop.PluginSdk/PluginAppearanceContext.cs
Normal file
49
LanMountainDesktop.PluginSdk/PluginAppearanceContext.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
|
||||
public sealed class PluginAppearanceContext : IPluginAppearanceContext
|
||||
{
|
||||
public PluginAppearanceContext(PluginAppearanceSnapshot snapshot)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(snapshot);
|
||||
ArgumentNullException.ThrowIfNull(snapshot.CornerRadiusTokens);
|
||||
|
||||
Snapshot = snapshot with
|
||||
{
|
||||
GlobalCornerRadiusScale = Math.Max(0d, snapshot.GlobalCornerRadiusScale),
|
||||
ThemeVariant = string.IsNullOrWhiteSpace(snapshot.ThemeVariant)
|
||||
? "Unknown"
|
||||
: snapshot.ThemeVariant.Trim()
|
||||
};
|
||||
}
|
||||
|
||||
public PluginAppearanceSnapshot Snapshot { get; }
|
||||
|
||||
public double ResolveScaledCornerRadius(double baseRadius, double? minimum = null, double? maximum = null)
|
||||
{
|
||||
var scale = Snapshot.GlobalCornerRadiusScale;
|
||||
var scaled = Math.Max(0d, baseRadius) * scale;
|
||||
var scaledMin = minimum.HasValue ? minimum.Value * scale : scaled;
|
||||
var scaledMax = maximum.HasValue ? maximum.Value * scale : scaled;
|
||||
return minimum.HasValue || maximum.HasValue
|
||||
? Math.Clamp(scaled, scaledMin, scaledMax)
|
||||
: scaled;
|
||||
}
|
||||
|
||||
public double ResolveCornerRadius(PluginCornerRadiusPreset preset, double? minimum = null, double? maximum = null)
|
||||
{
|
||||
var resolved = Math.Max(0d, Snapshot.CornerRadiusTokens.Get(preset));
|
||||
if (!minimum.HasValue && !maximum.HasValue)
|
||||
{
|
||||
return resolved;
|
||||
}
|
||||
|
||||
var clampedMin = minimum ?? resolved;
|
||||
var clampedMax = maximum ?? resolved;
|
||||
if (clampedMin > clampedMax)
|
||||
{
|
||||
(clampedMin, clampedMax) = (clampedMax, clampedMin);
|
||||
}
|
||||
|
||||
return Math.Clamp(resolved, clampedMin, clampedMax);
|
||||
}
|
||||
}
|
||||
6
LanMountainDesktop.PluginSdk/PluginAppearanceSnapshot.cs
Normal file
6
LanMountainDesktop.PluginSdk/PluginAppearanceSnapshot.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
|
||||
public sealed record PluginAppearanceSnapshot(
|
||||
double GlobalCornerRadiusScale,
|
||||
PluginCornerRadiusTokens CornerRadiusTokens,
|
||||
string ThemeVariant);
|
||||
13
LanMountainDesktop.PluginSdk/PluginCornerRadiusPreset.cs
Normal file
13
LanMountainDesktop.PluginSdk/PluginCornerRadiusPreset.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
|
||||
public enum PluginCornerRadiusPreset
|
||||
{
|
||||
Default = 0,
|
||||
Micro = 1,
|
||||
Xs = 2,
|
||||
Sm = 3,
|
||||
Md = 4,
|
||||
Lg = 5,
|
||||
Xl = 6,
|
||||
Island = 7
|
||||
}
|
||||
49
LanMountainDesktop.PluginSdk/PluginCornerRadiusTokens.cs
Normal file
49
LanMountainDesktop.PluginSdk/PluginCornerRadiusTokens.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using Avalonia;
|
||||
using LanMountainDesktop.Shared.Contracts;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
|
||||
public sealed record PluginCornerRadiusTokens(
|
||||
double Micro,
|
||||
double Xs,
|
||||
double Sm,
|
||||
double Md,
|
||||
double Lg,
|
||||
double Xl,
|
||||
double Island)
|
||||
{
|
||||
public double Get(PluginCornerRadiusPreset preset)
|
||||
{
|
||||
return preset switch
|
||||
{
|
||||
PluginCornerRadiusPreset.Default => Md,
|
||||
PluginCornerRadiusPreset.Micro => Micro,
|
||||
PluginCornerRadiusPreset.Xs => Xs,
|
||||
PluginCornerRadiusPreset.Sm => Sm,
|
||||
PluginCornerRadiusPreset.Md => Md,
|
||||
PluginCornerRadiusPreset.Lg => Lg,
|
||||
PluginCornerRadiusPreset.Xl => Xl,
|
||||
PluginCornerRadiusPreset.Island => Island,
|
||||
_ => Md
|
||||
};
|
||||
}
|
||||
|
||||
public CornerRadius ToCornerRadius(PluginCornerRadiusPreset preset)
|
||||
{
|
||||
return new CornerRadius(Get(preset));
|
||||
}
|
||||
|
||||
public static PluginCornerRadiusTokens FromShared(AppearanceCornerRadiusTokens tokens)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(tokens);
|
||||
|
||||
return new PluginCornerRadiusTokens(
|
||||
tokens.Micro.TopLeft,
|
||||
tokens.Xs.TopLeft,
|
||||
tokens.Sm.TopLeft,
|
||||
tokens.Md.TopLeft,
|
||||
tokens.Lg.TopLeft,
|
||||
tokens.Xl.TopLeft,
|
||||
tokens.Island.TopLeft);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
using LanMountainDesktop.Shared.Contracts;
|
||||
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
|
||||
public sealed class PluginDesktopComponentContext
|
||||
@@ -13,8 +11,7 @@ public sealed class PluginDesktopComponentContext
|
||||
string componentId,
|
||||
string? placementId,
|
||||
double cellSize,
|
||||
double globalCornerRadiusScale,
|
||||
AppearanceCornerRadiusTokens cornerRadiusTokens,
|
||||
IPluginAppearanceContext appearance,
|
||||
IPluginSettingsService? pluginSettings = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(manifest);
|
||||
@@ -23,7 +20,7 @@ public sealed class PluginDesktopComponentContext
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(componentId);
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(properties);
|
||||
ArgumentNullException.ThrowIfNull(cornerRadiusTokens);
|
||||
ArgumentNullException.ThrowIfNull(appearance);
|
||||
|
||||
Manifest = manifest;
|
||||
PluginDirectory = pluginDirectory;
|
||||
@@ -33,8 +30,7 @@ public sealed class PluginDesktopComponentContext
|
||||
ComponentId = componentId.Trim();
|
||||
PlacementId = string.IsNullOrWhiteSpace(placementId) ? null : placementId.Trim();
|
||||
CellSize = Math.Max(1, cellSize);
|
||||
GlobalCornerRadiusScale = Math.Max(0d, globalCornerRadiusScale);
|
||||
CornerRadiusTokens = cornerRadiusTokens;
|
||||
Appearance = appearance;
|
||||
PluginSettings = pluginSettings;
|
||||
}
|
||||
|
||||
@@ -54,20 +50,22 @@ public sealed class PluginDesktopComponentContext
|
||||
|
||||
public double CellSize { get; }
|
||||
|
||||
public double GlobalCornerRadiusScale { get; }
|
||||
public IPluginAppearanceContext Appearance { get; }
|
||||
|
||||
public AppearanceCornerRadiusTokens CornerRadiusTokens { get; }
|
||||
public double GlobalCornerRadiusScale => Appearance.Snapshot.GlobalCornerRadiusScale;
|
||||
|
||||
public PluginCornerRadiusTokens CornerRadiusTokens => Appearance.Snapshot.CornerRadiusTokens;
|
||||
|
||||
public IPluginSettingsService? PluginSettings { get; }
|
||||
|
||||
public double ResolveScaledCornerRadius(double baseRadius, double? minimum = null, double? maximum = null)
|
||||
{
|
||||
var scaled = Math.Max(0d, baseRadius) * GlobalCornerRadiusScale;
|
||||
var scaledMin = minimum.HasValue ? minimum.Value * GlobalCornerRadiusScale : scaled;
|
||||
var scaledMax = maximum.HasValue ? maximum.Value * GlobalCornerRadiusScale : scaled;
|
||||
return minimum.HasValue || maximum.HasValue
|
||||
? Math.Clamp(scaled, scaledMin, scaledMax)
|
||||
: scaled;
|
||||
return Appearance.ResolveScaledCornerRadius(baseRadius, minimum, maximum);
|
||||
}
|
||||
|
||||
public double ResolveCornerRadius(PluginCornerRadiusPreset preset, double? minimum = null, double? maximum = null)
|
||||
{
|
||||
return Appearance.ResolveCornerRadius(preset, minimum, maximum);
|
||||
}
|
||||
|
||||
public T? GetService<T>()
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace LanMountainDesktop.PluginSdk;
|
||||
|
||||
public sealed class PluginDesktopComponentOptions
|
||||
{
|
||||
public required string ComponentId { get; init; }
|
||||
|
||||
public required string DisplayName { get; init; }
|
||||
|
||||
public string IconKey { get; init; } = "PuzzlePiece";
|
||||
|
||||
public string Category { get; init; } = "Plugins";
|
||||
|
||||
public int MinWidthCells { get; init; } = 2;
|
||||
|
||||
public int MinHeightCells { get; init; } = 2;
|
||||
|
||||
public bool AllowDesktopPlacement { get; init; } = true;
|
||||
|
||||
public bool AllowStatusBarPlacement { get; init; }
|
||||
|
||||
public PluginDesktopComponentResizeMode ResizeMode { get; init; } = PluginDesktopComponentResizeMode.Proportional;
|
||||
|
||||
public string? DisplayNameLocalizationKey { get; init; }
|
||||
|
||||
public PluginCornerRadiusPreset CornerRadiusPreset { get; init; } = PluginCornerRadiusPreset.Default;
|
||||
|
||||
public Func<IPluginAppearanceContext, double, double>? CornerRadiusResolver { get; init; }
|
||||
}
|
||||
@@ -5,67 +5,37 @@ namespace LanMountainDesktop.PluginSdk;
|
||||
public sealed class PluginDesktopComponentRegistration
|
||||
{
|
||||
public PluginDesktopComponentRegistration(
|
||||
string componentId,
|
||||
string displayName,
|
||||
Func<IServiceProvider, PluginDesktopComponentContext, Control> controlFactory,
|
||||
string iconKey = "PuzzlePiece",
|
||||
string category = "Plugins",
|
||||
int minWidthCells = 2,
|
||||
int minHeightCells = 2,
|
||||
bool allowDesktopPlacement = true,
|
||||
bool allowStatusBarPlacement = false,
|
||||
PluginDesktopComponentResizeMode resizeMode = PluginDesktopComponentResizeMode.Proportional,
|
||||
string? displayNameLocalizationKey = null,
|
||||
Func<double, double>? cornerRadiusResolver = null)
|
||||
PluginDesktopComponentOptions options)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(componentId);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(displayName);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(iconKey);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(category);
|
||||
ArgumentNullException.ThrowIfNull(controlFactory);
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(options.ComponentId);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(options.DisplayName);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(options.IconKey);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(options.Category);
|
||||
|
||||
ComponentId = componentId.Trim();
|
||||
DisplayName = displayName.Trim();
|
||||
DisplayNameLocalizationKey = string.IsNullOrWhiteSpace(displayNameLocalizationKey)
|
||||
ComponentId = options.ComponentId.Trim();
|
||||
DisplayName = options.DisplayName.Trim();
|
||||
DisplayNameLocalizationKey = string.IsNullOrWhiteSpace(options.DisplayNameLocalizationKey)
|
||||
? null
|
||||
: displayNameLocalizationKey.Trim();
|
||||
: options.DisplayNameLocalizationKey.Trim();
|
||||
ControlFactory = controlFactory;
|
||||
IconKey = iconKey.Trim();
|
||||
Category = category.Trim();
|
||||
MinWidthCells = Math.Max(1, minWidthCells);
|
||||
MinHeightCells = Math.Max(1, minHeightCells);
|
||||
AllowDesktopPlacement = allowDesktopPlacement;
|
||||
AllowStatusBarPlacement = allowStatusBarPlacement;
|
||||
ResizeMode = resizeMode;
|
||||
CornerRadiusResolver = cornerRadiusResolver;
|
||||
IconKey = options.IconKey.Trim();
|
||||
Category = options.Category.Trim();
|
||||
MinWidthCells = Math.Max(1, options.MinWidthCells);
|
||||
MinHeightCells = Math.Max(1, options.MinHeightCells);
|
||||
AllowDesktopPlacement = options.AllowDesktopPlacement;
|
||||
AllowStatusBarPlacement = options.AllowStatusBarPlacement;
|
||||
ResizeMode = options.ResizeMode;
|
||||
CornerRadiusPreset = options.CornerRadiusPreset;
|
||||
CornerRadiusResolver = options.CornerRadiusResolver;
|
||||
}
|
||||
|
||||
public PluginDesktopComponentRegistration(
|
||||
string componentId,
|
||||
string displayName,
|
||||
Func<PluginDesktopComponentContext, Control> controlFactory,
|
||||
string iconKey = "PuzzlePiece",
|
||||
string category = "Plugins",
|
||||
int minWidthCells = 2,
|
||||
int minHeightCells = 2,
|
||||
bool allowDesktopPlacement = true,
|
||||
bool allowStatusBarPlacement = false,
|
||||
PluginDesktopComponentResizeMode resizeMode = PluginDesktopComponentResizeMode.Proportional,
|
||||
string? displayNameLocalizationKey = null,
|
||||
Func<double, double>? cornerRadiusResolver = null)
|
||||
: this(
|
||||
componentId,
|
||||
displayName,
|
||||
(_, context) => controlFactory(context),
|
||||
iconKey,
|
||||
category,
|
||||
minWidthCells,
|
||||
minHeightCells,
|
||||
allowDesktopPlacement,
|
||||
allowStatusBarPlacement,
|
||||
resizeMode,
|
||||
displayNameLocalizationKey,
|
||||
cornerRadiusResolver)
|
||||
PluginDesktopComponentOptions options)
|
||||
: this((_, context) => controlFactory(context), options)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -91,5 +61,25 @@ public sealed class PluginDesktopComponentRegistration
|
||||
|
||||
public PluginDesktopComponentResizeMode ResizeMode { get; }
|
||||
|
||||
public Func<double, double>? CornerRadiusResolver { get; }
|
||||
public PluginCornerRadiusPreset CornerRadiusPreset { get; }
|
||||
|
||||
public Func<IPluginAppearanceContext, double, double>? CornerRadiusResolver { get; }
|
||||
|
||||
public double ResolveCornerRadius(IPluginAppearanceContext appearance, double cellSize)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(appearance);
|
||||
|
||||
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(CornerRadiusPreset);
|
||||
|
||||
return double.IsFinite(resolved)
|
||||
? Math.Max(0d, resolved)
|
||||
: appearance.ResolveCornerRadius(PluginCornerRadiusPreset.Default);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +87,8 @@ public sealed record PluginManifest(
|
||||
throw new InvalidOperationException(
|
||||
$"Plugin '{normalized.Id}' targets API version '{normalized.ApiVersion}' (major {requestedVersion.Major}), " +
|
||||
$"but the host provides '{PluginSdkInfo.ApiVersion}' (major {currentVersion.Major}). " +
|
||||
$"This host only supports v{currentVersion.Major}.x plugins. " +
|
||||
$"Migrate the plugin to API {PluginSdkInfo.ApiVersion} and rebuild the package.");
|
||||
$"This host only supports v{currentVersion.Major}.x plugins and rejects v{requestedVersion.Major}.x packages by default. " +
|
||||
$"Migrate the plugin manifest and code to API {PluginSdkInfo.ApiVersion}, then rebuild and republish the package.");
|
||||
}
|
||||
|
||||
return normalized;
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace LanMountainDesktop.PluginSdk;
|
||||
|
||||
public static class PluginSdkInfo
|
||||
{
|
||||
public const string ApiVersion = "3.0.0";
|
||||
public const string ApiVersion = "4.0.0";
|
||||
public const string ManifestFileName = "plugin.json";
|
||||
public const string PackageFileExtension = ".laapp";
|
||||
public const string DataDirectoryName = "Data";
|
||||
|
||||
@@ -30,34 +30,15 @@ public static class PluginServiceCollectionExtensions
|
||||
|
||||
public static IServiceCollection AddPluginDesktopComponent<TControl>(
|
||||
this IServiceCollection services,
|
||||
string componentId,
|
||||
string displayName,
|
||||
string iconKey = "PuzzlePiece",
|
||||
string category = "Plugins",
|
||||
int minWidthCells = 2,
|
||||
int minHeightCells = 2,
|
||||
bool allowDesktopPlacement = true,
|
||||
bool allowStatusBarPlacement = false,
|
||||
PluginDesktopComponentResizeMode resizeMode = PluginDesktopComponentResizeMode.Proportional,
|
||||
string? displayNameLocalizationKey = null,
|
||||
Func<double, double>? cornerRadiusResolver = null)
|
||||
PluginDesktopComponentOptions options)
|
||||
where TControl : Control
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
services.AddSingleton(new PluginDesktopComponentRegistration(
|
||||
componentId,
|
||||
displayName,
|
||||
(provider, context) => ActivatorUtilities.CreateInstance<TControl>(provider, context),
|
||||
iconKey,
|
||||
category,
|
||||
minWidthCells,
|
||||
minHeightCells,
|
||||
allowDesktopPlacement,
|
||||
allowStatusBarPlacement,
|
||||
resizeMode,
|
||||
displayNameLocalizationKey,
|
||||
cornerRadiusResolver));
|
||||
options));
|
||||
return services;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user