Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/SharpFM.Plugin.Sample/ClipInspectorPanel.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
<TextBlock Classes="Fluent2Body" Text="{Binding ClipType}" />
</StackPanel>

<StackPanel Spacing="2">
<StackPanel Spacing="2" IsVisible="{Binding ShowElementCount}">
<TextBlock Classes="Fluent2Caption" Opacity="0.7" Text="XML Elements" />
<TextBlock Classes="Fluent2Body" Text="{Binding ElementCount}" />
</StackPanel>

<StackPanel Spacing="2">
<StackPanel Spacing="2" IsVisible="{Binding ShowXmlSize}">
<TextBlock Classes="Fluent2Caption" Opacity="0.7" Text="Approx. Size" />
<TextBlock Classes="Fluent2Body" Text="{Binding XmlSize}" />
</StackPanel>
Expand Down
35 changes: 34 additions & 1 deletion src/SharpFM.Plugin.Sample/ClipInspectorPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,37 @@ public class ClipInspectorPlugin : IPanelPlugin
public IReadOnlyList<PluginKeyBinding> KeyBindings => [];
public IReadOnlyList<PluginMenuAction> MenuActions => [];

public PluginConfigSchema ConfigSchema { get; } = new(new[]
{
new PluginConfigField(
Key: "ShowElementCount",
Label: "Show XML element count",
Type: PluginConfigFieldType.Bool,
DefaultValue: true,
Description: "Display the number of XML elements in the selected clip."),
new PluginConfigField(
Key: "ShowXmlSize",
Label: "Show approximate size",
Type: PluginConfigFieldType.Bool,
DefaultValue: true,
Description: "Display the approximate byte size of the clip's XML."),
});

public void OnConfigChanged(IReadOnlyDictionary<string, object?> values)
{
_showElementCount = values.TryGetValue("ShowElementCount", out var a) && a is bool ba ? ba : true;
_showXmlSize = values.TryGetValue("ShowXmlSize", out var b) && b is bool bb ? bb : true;
if (_viewModel is not null)
{
_viewModel.ShowElementCount = _showElementCount;
_viewModel.ShowXmlSize = _showXmlSize;
}
}

private IPluginHost? _host;
private ClipInspectorViewModel? _viewModel;
private bool _showElementCount = true;
private bool _showXmlSize = true;

public void Initialize(IPluginHost host)
{
Expand All @@ -29,7 +58,11 @@ public void Initialize(IPluginHost host)

public Control CreatePanel()
{
_viewModel = new ClipInspectorViewModel();
_viewModel = new ClipInspectorViewModel
{
ShowElementCount = _showElementCount,
ShowXmlSize = _showXmlSize,
};
_viewModel.Update(_host?.SelectedClip);
return new ClipInspectorPanel { DataContext = _viewModel };
}
Expand Down
6 changes: 6 additions & 0 deletions src/SharpFM.Plugin.Sample/ClipInspectorViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ private void Notify([CallerMemberName] string name = "")
private bool _hasClip;
public bool HasClip { get => _hasClip; private set { _hasClip = value; Notify(); } }

private bool _showElementCount = true;
public bool ShowElementCount { get => _showElementCount; set { _showElementCount = value; Notify(); } }

private bool _showXmlSize = true;
public bool ShowXmlSize { get => _showXmlSize; set { _showXmlSize = value; Notify(); } }

public void Update(ClipData? clip)
{
if (clip is null)
Expand Down
2 changes: 2 additions & 0 deletions src/SharpFM.Plugin.XmlViewer/XmlViewerPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public class XmlViewerPlugin : IPanelPlugin
[new PluginKeyBinding("Ctrl+Shift+X", "Toggle XML Viewer", () => { })];

public IReadOnlyList<PluginMenuAction> MenuActions => [];
public PluginConfigSchema ConfigSchema => PluginConfigSchema.Empty;
public void OnConfigChanged(IReadOnlyDictionary<string, object?> values) { }

public void Initialize(IPluginHost host)
{
Expand Down
16 changes: 16 additions & 0 deletions src/SharpFM.Plugin/IPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,20 @@ public interface IPlugin : IDisposable
/// as a submenu with "Toggle Panel" plus these custom actions.
/// </summary>
IReadOnlyList<PluginMenuAction> MenuActions { get; }

/// <summary>
/// Schema for user-tunable configuration values. The host persists these on the
/// plugin's behalf and renders a generic settings UI from the schema. Return
/// <see cref="PluginConfigSchema.Empty"/> if the plugin has no configuration.
/// </summary>
PluginConfigSchema ConfigSchema { get; }

/// <summary>
/// Called by the host with the current values for fields declared in
/// <see cref="ConfigSchema"/>: once after <see cref="Initialize"/> with the
/// persisted (or default) values, and again whenever the user saves edits in the
/// Plugin Manager. Keys match <see cref="PluginConfigField.Key"/>; values are
/// coerced to the CLR type implied by <see cref="PluginConfigField.Type"/>.
/// </summary>
void OnConfigChanged(IReadOnlyDictionary<string, object?> values);
}
48 changes: 48 additions & 0 deletions src/SharpFM.Plugin/PluginConfigSchema.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;

namespace SharpFM.Plugin;

/// <summary>
/// Supported field types for a plugin configuration schema. The host renders a
/// different control per type and coerces persisted values to the matching CLR type.
/// </summary>
public enum PluginConfigFieldType
{
String,
MultilineString,
Bool,
Int,
Double,
Enum
}

/// <summary>
/// A single configurable value declared by a plugin.
/// </summary>
/// <param name="Key">Dictionary key used when values are passed back to the plugin.</param>
/// <param name="Label">Human-readable label shown in the settings UI.</param>
/// <param name="Type">Field type — determines the rendered control and value coercion.</param>
/// <param name="DefaultValue">
/// Value returned when the field has never been set or the persisted value is invalid.
/// Must be assignable to the CLR type implied by <paramref name="Type"/>.
/// </param>
/// <param name="Description">Optional caption shown beneath the control.</param>
/// <param name="EnumValues">Allowed values when <paramref name="Type"/> is <see cref="PluginConfigFieldType.Enum"/>.</param>
public sealed record PluginConfigField(
string Key,
string Label,
PluginConfigFieldType Type,
object? DefaultValue = null,
string? Description = null,
IReadOnlyList<string>? EnumValues = null);

/// <summary>
/// Describes a plugin's user-tunable configuration. The host uses this to
/// persist values, generate a settings UI, and push the current values into the plugin.
/// </summary>
public sealed record PluginConfigSchema(IReadOnlyList<PluginConfigField> Fields)
{
/// <summary>Schema for plugins with no configuration.</summary>
public static PluginConfigSchema Empty { get; } = new(Array.Empty<PluginConfigField>());
}
5 changes: 3 additions & 2 deletions src/SharpFM/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ public override void OnFrameworkInitializationCompleted()
// Load plugins
var pluginHost = new PluginHost(viewModel, loggerFactory);
var pluginUIHost = new PluginUIHost(pluginHost);
var pluginService = new PluginService(logger);
var pluginConfigService = new PluginConfigService(logger);
var pluginService = new PluginService(logger, pluginConfigService);
pluginService.LoadPlugins(pluginUIHost);

viewModel.AllPlugins = pluginService.AllPlugins;
Expand All @@ -62,7 +63,7 @@ public override void OnFrameworkInitializationCompleted()

// Give the window access to plugin services for the manager dialog
if (desktop.MainWindow is MainWindow mainWindow)
mainWindow.SetPluginServices(pluginService, pluginUIHost);
mainWindow.SetPluginServices(pluginService, pluginUIHost, pluginConfigService);

desktop.MainWindow.DataContext = viewModel;

Expand Down
8 changes: 5 additions & 3 deletions src/SharpFM/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public partial class MainWindow : Window
private TextMate.Installation? _scriptTextMateInstallation;
private PluginService? _pluginService;
private PluginUIHost? _pluginHost;
private PluginConfigService? _pluginConfigService;

public MainWindow()
{
Expand Down Expand Up @@ -58,10 +59,11 @@ public MainWindow()
DataContextChanged += OnDataContextChanged;
}

public void SetPluginServices(PluginService pluginService, PluginUIHost pluginHost)
public void SetPluginServices(PluginService pluginService, PluginUIHost pluginHost, PluginConfigService pluginConfigService)
{
_pluginService = pluginService;
_pluginHost = pluginHost;
_pluginConfigService = pluginConfigService;
}

private void OnDataContextChanged(object? sender, EventArgs e)
Expand Down Expand Up @@ -229,11 +231,11 @@ private void UpdatePluginPanelContent()

private void ShowPluginManager()
{
if (_pluginService is null || _pluginHost is null) return;
if (_pluginService is null || _pluginHost is null || _pluginConfigService is null) return;
if (DataContext is not MainWindowViewModel vm) return;

var window = new PluginManagerWindow();
window.Configure(_pluginService, _pluginHost, vm);
window.Configure(_pluginService, _pluginHost, vm, _pluginConfigService);
window.ShowDialog(this);
}

Expand Down
33 changes: 33 additions & 0 deletions src/SharpFM/PluginManager/PluginConfigDialog.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<Window
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="SharpFM.PluginManager.PluginConfigDialog"
Width="500"
Height="520"
CanResize="True"
WindowStartupLocation="CenterOwner">

<Grid Margin="16" RowDefinitions="Auto,*,Auto">
<TextBlock
Grid.Row="0"
x:Name="headerText"
Classes="Fluent2Subtitle"
Margin="0,0,0,12" />

<Border Grid.Row="1" Classes="Fluent2SurfaceElevated">
<ScrollViewer Padding="12">
<StackPanel x:Name="fieldsPanel" Spacing="12" />
</ScrollViewer>
</Border>

<StackPanel
Grid.Row="2"
Orientation="Horizontal"
HorizontalAlignment="Right"
Spacing="8"
Margin="0,12,0,0">
<Button x:Name="cancelButton" Classes="Fluent2" Content="Cancel" />
<Button x:Name="okButton" Classes="Fluent2Primary" Content="OK" />
</StackPanel>
</Grid>
</Window>
Loading
Loading