mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-22 09:14:25 +08:00
change.插件设置支持View
This commit is contained in:
@@ -28,6 +28,35 @@ public static class PluginServiceCollectionExtensions
|
|||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a plugin settings section with a custom AXAML view.
|
||||||
|
/// The host application will display <typeparamref name="TView"/> directly
|
||||||
|
/// in the settings window, allowing the plugin to use any Fluent Avalonia controls
|
||||||
|
/// and custom layouts — just like built-in settings pages.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TView">A <see cref="SettingsPageBase"/> subclass that defines the settings UI using AXAML.</typeparam>
|
||||||
|
public static IServiceCollection AddPluginSettingsSection<TView>(
|
||||||
|
this IServiceCollection services,
|
||||||
|
string id,
|
||||||
|
string titleLocalizationKey,
|
||||||
|
string? descriptionLocalizationKey = null,
|
||||||
|
string iconKey = "PuzzlePiece",
|
||||||
|
int sortOrder = 0)
|
||||||
|
where TView : SettingsPageBase
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(services);
|
||||||
|
|
||||||
|
var builder = new PluginSettingsSectionBuilder(
|
||||||
|
id,
|
||||||
|
titleLocalizationKey,
|
||||||
|
descriptionLocalizationKey,
|
||||||
|
iconKey,
|
||||||
|
sortOrder);
|
||||||
|
builder.SetCustomView<TView>();
|
||||||
|
services.AddSingleton(builder.Build());
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
public static IServiceCollection AddPluginDesktopComponent<TControl>(
|
public static IServiceCollection AddPluginDesktopComponent<TControl>(
|
||||||
this IServiceCollection services,
|
this IServiceCollection services,
|
||||||
PluginDesktopComponentOptions options)
|
PluginDesktopComponentOptions options)
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
namespace LanMountainDesktop.PluginSdk;
|
namespace LanMountainDesktop.PluginSdk;
|
||||||
|
|
||||||
public sealed class PluginSettingsSectionBuilder
|
public sealed class PluginSettingsSectionBuilder
|
||||||
{
|
{
|
||||||
private readonly List<SettingsOptionDefinition> _options = [];
|
private readonly List<SettingsOptionDefinition> _options = [];
|
||||||
|
private Type? _customViewType;
|
||||||
|
|
||||||
internal PluginSettingsSectionBuilder(
|
internal PluginSettingsSectionBuilder(
|
||||||
string id,
|
string id,
|
||||||
@@ -30,8 +33,46 @@ public sealed class PluginSettingsSectionBuilder
|
|||||||
|
|
||||||
public int SortOrder { get; }
|
public int SortOrder { get; }
|
||||||
|
|
||||||
|
public Type? CustomViewType => _customViewType;
|
||||||
|
|
||||||
public IReadOnlyList<SettingsOptionDefinition> Options => _options;
|
public IReadOnlyList<SettingsOptionDefinition> Options => _options;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a custom AXAML view for this settings section.
|
||||||
|
/// The view type must be a subclass of <see cref="SettingsPageBase"/>.
|
||||||
|
/// When a custom view is provided, the host application will use it directly
|
||||||
|
/// instead of generating a page from the declared options, allowing the plugin
|
||||||
|
/// to use any Fluent Avalonia controls and custom layouts.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TView">A <see cref="SettingsPageBase"/> subclass that defines the settings UI.</typeparam>
|
||||||
|
public PluginSettingsSectionBuilder SetCustomView<TView>() where TView : SettingsPageBase
|
||||||
|
{
|
||||||
|
_customViewType = typeof(TView);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a custom AXAML view for this settings section.
|
||||||
|
/// The view type must be a subclass of <see cref="SettingsPageBase"/>.
|
||||||
|
/// When a custom view is provided, the host application will use it directly
|
||||||
|
/// instead of generating a page from the declared options.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="viewType">A <see cref="SettingsPageBase"/> subclass type that defines the settings UI.</param>
|
||||||
|
public PluginSettingsSectionBuilder SetCustomView(Type viewType)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(viewType);
|
||||||
|
|
||||||
|
if (!typeof(SettingsPageBase).IsAssignableFrom(viewType))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"Custom view type must be a subclass of {nameof(SettingsPageBase)}.",
|
||||||
|
nameof(viewType));
|
||||||
|
}
|
||||||
|
|
||||||
|
_customViewType = viewType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public PluginSettingsSectionBuilder AddOption(SettingsOptionDefinition option)
|
public PluginSettingsSectionBuilder AddOption(SettingsOptionDefinition option)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(option);
|
ArgumentNullException.ThrowIfNull(option);
|
||||||
@@ -142,6 +183,7 @@ public sealed class PluginSettingsSectionBuilder
|
|||||||
_options.ToArray(),
|
_options.ToArray(),
|
||||||
DescriptionLocalizationKey,
|
DescriptionLocalizationKey,
|
||||||
IconKey,
|
IconKey,
|
||||||
SortOrder);
|
SortOrder,
|
||||||
|
_customViewType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace LanMountainDesktop.PluginSdk;
|
namespace LanMountainDesktop.PluginSdk;
|
||||||
@@ -10,7 +11,8 @@ public sealed class PluginSettingsSectionRegistration
|
|||||||
IReadOnlyList<SettingsOptionDefinition> options,
|
IReadOnlyList<SettingsOptionDefinition> options,
|
||||||
string? descriptionLocalizationKey = null,
|
string? descriptionLocalizationKey = null,
|
||||||
string iconKey = "PuzzlePiece",
|
string iconKey = "PuzzlePiece",
|
||||||
int sortOrder = 0)
|
int sortOrder = 0,
|
||||||
|
Type? customViewType = null)
|
||||||
{
|
{
|
||||||
ArgumentException.ThrowIfNullOrWhiteSpace(id);
|
ArgumentException.ThrowIfNullOrWhiteSpace(id);
|
||||||
ArgumentException.ThrowIfNullOrWhiteSpace(titleLocalizationKey);
|
ArgumentException.ThrowIfNullOrWhiteSpace(titleLocalizationKey);
|
||||||
@@ -24,6 +26,15 @@ public sealed class PluginSettingsSectionRegistration
|
|||||||
IconKey = iconKey.Trim();
|
IconKey = iconKey.Trim();
|
||||||
SortOrder = sortOrder;
|
SortOrder = sortOrder;
|
||||||
Options = options ?? [];
|
Options = options ?? [];
|
||||||
|
|
||||||
|
if (customViewType is not null && !typeof(SettingsPageBase).IsAssignableFrom(customViewType))
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"Custom view type must be a subclass of {nameof(SettingsPageBase)}.",
|
||||||
|
nameof(customViewType));
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomViewType = customViewType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Id { get; }
|
public string Id { get; }
|
||||||
@@ -37,4 +48,11 @@ public sealed class PluginSettingsSectionRegistration
|
|||||||
public int SortOrder { get; }
|
public int SortOrder { get; }
|
||||||
|
|
||||||
public IReadOnlyList<SettingsOptionDefinition> Options { get; }
|
public IReadOnlyList<SettingsOptionDefinition> Options { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When set, the host application will instantiate this <see cref="SettingsPageBase"/> subclass
|
||||||
|
/// instead of generating a page from <see cref="Options"/>.
|
||||||
|
/// This allows plugins to provide fully custom AXAML views with any Fluent Avalonia controls.
|
||||||
|
/// </summary>
|
||||||
|
public Type? CustomViewType { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,38 @@ public sealed class Plugin : PluginBase
|
|||||||
public override void Initialize(HostBuilderContext context, IServiceCollection services)
|
public override void Initialize(HostBuilderContext context, IServiceCollection services)
|
||||||
{
|
{
|
||||||
_ = context;
|
_ = context;
|
||||||
|
|
||||||
|
// ── Option 1: Declarative settings (simple key-value options) ──────────
|
||||||
|
// The host generates a settings page automatically from the declared options.
|
||||||
|
// Supported option types: Toggle, Text, Number, Select, Path, List.
|
||||||
|
//
|
||||||
|
// services.AddPluginSettingsSection(
|
||||||
|
// "my-plugin-settings",
|
||||||
|
// "My Plugin Settings",
|
||||||
|
// section => section
|
||||||
|
// .AddToggle("enable_feature", "Enable Feature", defaultValue: true)
|
||||||
|
// .AddNumber("refresh_interval", "Refresh Interval", defaultValue: 30, minimum: 5, maximum: 120),
|
||||||
|
// iconKey: "PuzzlePiece");
|
||||||
|
|
||||||
|
// ── Option 2: Custom AXAML view (full Fluent Avalonia controls) ────────
|
||||||
|
// Provide a SettingsPageBase subclass to use any Fluent Avalonia control
|
||||||
|
// (SettingsExpander, ColorPicker, Slider, etc.) — just like built-in pages.
|
||||||
|
//
|
||||||
|
// services.AddPluginSettingsSection<MyCustomSettingsPage>(
|
||||||
|
// "my-plugin-settings",
|
||||||
|
// "My Plugin Settings",
|
||||||
|
// iconKey: "PuzzlePiece");
|
||||||
|
//
|
||||||
|
// Or mix both: declare options AND set a custom view on the builder:
|
||||||
|
//
|
||||||
|
// services.AddPluginSettingsSection(
|
||||||
|
// "my-plugin-settings",
|
||||||
|
// "My Plugin Settings",
|
||||||
|
// section => section
|
||||||
|
// .SetCustomView<MyCustomSettingsPage>()
|
||||||
|
// .AddToggle("enable_feature", "Enable Feature"),
|
||||||
|
// iconKey: "PuzzlePiece");
|
||||||
|
|
||||||
_ = services;
|
_ = services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -256,6 +256,29 @@ internal sealed class SettingsPageRegistry : ISettingsPageRegistry, IDisposable
|
|||||||
? null
|
? null
|
||||||
: localizer.GetString(section.DescriptionLocalizationKey, section.DescriptionLocalizationKey);
|
: localizer.GetString(section.DescriptionLocalizationKey, section.DescriptionLocalizationKey);
|
||||||
|
|
||||||
|
Func<ISettingsPageHostContext, Control> factory;
|
||||||
|
|
||||||
|
if (section.CustomViewType is not null)
|
||||||
|
{
|
||||||
|
var customViewType = section.CustomViewType;
|
||||||
|
var pluginServices = loadedPlugin.Services;
|
||||||
|
factory = hostContext => CreatePage(pluginServices, customViewType, hostContext);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
factory = hostContext =>
|
||||||
|
{
|
||||||
|
var page = new GeneratedPluginSettingsPage(
|
||||||
|
new PluginGeneratedSettingsPageViewModel(
|
||||||
|
_settingsFacade.Settings,
|
||||||
|
loadedPlugin.Manifest.Id,
|
||||||
|
section,
|
||||||
|
localizer));
|
||||||
|
page.InitializeHostContext(hostContext);
|
||||||
|
return page;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
_pages.Add(new SettingsPageDescriptor(
|
_pages.Add(new SettingsPageDescriptor(
|
||||||
pageId,
|
pageId,
|
||||||
title,
|
title,
|
||||||
@@ -270,17 +293,7 @@ internal sealed class SettingsPageRegistry : ISettingsPageRegistry, IDisposable
|
|||||||
hidePageTitle: false,
|
hidePageTitle: false,
|
||||||
useFullWidth: false,
|
useFullWidth: false,
|
||||||
groupId: null,
|
groupId: null,
|
||||||
hostContext =>
|
factory));
|
||||||
{
|
|
||||||
var page = new GeneratedPluginSettingsPage(
|
|
||||||
new PluginGeneratedSettingsPageViewModel(
|
|
||||||
_settingsFacade.Settings,
|
|
||||||
loadedPlugin.Manifest.Id,
|
|
||||||
section,
|
|
||||||
localizer));
|
|
||||||
page.InitializeHostContext(hostContext);
|
|
||||||
return page;
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user