acdream/src/AcDream.Plugin.Abstractions/IUiRegistry.cs

224 lines
7.9 KiB
C#

namespace AcDream.Plugin.Abstractions;
/// <summary>
/// Stable, presentation-neutral description of one top-level plugin window.
/// The graphical host uses this metadata for its plugin sidepanel and retained
/// window registry; no App/UI type crosses the plugin boundary.
/// </summary>
/// <param name="WindowId">
/// Stable id within the owning plugin. It is part of the persisted window-layout
/// key, so it must not be localized or changed between releases.
/// </param>
/// <param name="Title">User-facing window title.</param>
public sealed record PluginPanelDescriptor(string WindowId, string Title)
{
/// <summary>
/// Optional one-to-three-character fallback drawn in the sidepanel button
/// when no DAT icon is supplied. The host derives initials from
/// <see cref="Title"/> when this is empty.
/// </summary>
public string? IconText { get; init; }
/// <summary>
/// Optional installed-client RenderSurface DID. Zero asks the host to draw
/// <see cref="IconText"/> instead. Plugins never receive the resulting GPU
/// resource and remain BCL-only.
/// </summary>
public uint IconSurfaceId { get; init; }
/// <summary>
/// Initial visibility used only when no per-character persisted layout is
/// available. Hiding the window never disables the plugin.
/// </summary>
public bool StartVisible { get; init; } = true;
/// <summary>Whether this window receives a button in the shared sidepanel.</summary>
public bool ShowInSidePanel { get; init; } = true;
}
/// <summary>
/// Host-authenticated plugin identity attached to registrations by the scoped
/// plugin lifetime. Plugins cannot choose or spoof this value.
/// </summary>
public readonly record struct PluginUiOwner(string Id, string DisplayName);
/// <summary>
/// Plugin-facing UI registration. A plugin ships a markup file (KSML-style) +
/// a binding object exposing the data properties the markup binds to, and
/// registers it from <c>Enable()</c>. Graphical hosts buffer registrations until
/// their retained UI exists. A host whose <see cref="IPluginHost.HasUi"/> is
/// <see langword="false"/> exposes <see cref="NoOpUiRegistry.Instance"/> and
/// intentionally discards registrations.
/// </summary>
public interface IUiRegistry
{
/// <param name="markupPath">Absolute path to the plugin's panel markup file.</param>
/// <param name="binding">Object whose properties the markup's {Bindings} resolve against.</param>
void AddMarkupPanel(string markupPath, object binding);
/// <summary>
/// Registers a first-class plugin window. The host keeps the plugin lifetime
/// independent from the window's visible/minimized state.
/// </summary>
/// <remarks>
/// Defaulting to the API-v1 method keeps older/custom hosts source-compatible;
/// acdream's graphical scoped host overrides this route and preserves all
/// descriptor metadata.
/// </remarks>
void AddPanel(
PluginPanelDescriptor descriptor,
string markupPath,
object binding)
=> AddMarkupPanel(markupPath, binding);
/// <summary>
/// Registers a window whose lifetime may be ended independently while the
/// plugin keeps running. Disposing the token removes the retained window
/// and its sidepanel entry.
/// </summary>
IDisposable RegisterPanel(
PluginPanelDescriptor descriptor,
string markupPath,
object binding)
{
AddPanel(descriptor, markupPath, binding);
return NoOpUiRegistration.Instance;
}
/// <summary>
/// Registers an independently removable window from in-memory KSML. This
/// is the BCL-only seam used by VTank-compatible Meta Create View actions;
/// plugins do not need to create temporary files or import App types.
/// </summary>
IDisposable RegisterPanelContent(
PluginPanelDescriptor descriptor,
string markupContent,
object binding) => NoOpUiRegistration.Instance;
/// <summary>Queries this plugin's own registered view by title or stable id.</summary>
bool ViewExists(string viewName) => false;
bool IsViewVisible(string viewName) => false;
bool ControlExists(string viewName, string controlName) => false;
bool SetControlLabel(string viewName, string controlName, string label) => false;
bool SetControlVisible(string viewName, string controlName, bool visible) => false;
}
/// <summary>
/// Host-infrastructure extension used to give each plugin a removable UI
/// registration lifetime. Plugins continue to call
/// <see cref="IUiRegistry.AddMarkupPanel"/>; the shared plugin host wraps that
/// call and owns the returned token so failed initialization, failed enable,
/// and shutdown can roll the registration back without trusting plugin code.
/// </summary>
public interface IScopedUiRegistry : IUiRegistry
{
IDisposable RegisterMarkupPanel(string markupPath, object binding);
/// <summary>
/// Host-only scoped registration carrying the manifest-derived owner.
/// Disposal removes both the retained window and its sidepanel entry.
/// </summary>
IDisposable RegisterPanel(
PluginUiOwner owner,
PluginPanelDescriptor descriptor,
string markupPath,
object binding)
=> RegisterMarkupPanel(markupPath, binding);
/// <summary>Host-owned registration for in-memory plugin markup.</summary>
IDisposable RegisterPanelContent(
PluginUiOwner owner,
PluginPanelDescriptor descriptor,
string markupContent,
object binding) => NoOpUiRegistration.Instance;
bool ViewExists(PluginUiOwner owner, string viewName) => false;
bool IsViewVisible(PluginUiOwner owner, string viewName) => false;
bool ControlExists(
PluginUiOwner owner,
string viewName,
string controlName) => false;
bool SetControlLabel(
PluginUiOwner owner,
string viewName,
string controlName,
string label) => false;
bool SetControlVisible(
PluginUiOwner owner,
string viewName,
string controlName,
bool visible) => false;
}
/// <summary>Shared empty registration returned by UI-less/legacy hosts.</summary>
public sealed class NoOpUiRegistration : IDisposable
{
public static NoOpUiRegistration Instance { get; } = new();
private NoOpUiRegistration()
{
}
public void Dispose()
{
}
}
/// <summary>
/// BCL-only UI sink for no-window plugin hosts. It intentionally retains
/// neither markup paths nor binding objects, so a UI registration cannot keep
/// a plugin assembly alive after its collectible load context is unloaded.
/// </summary>
public sealed class NoOpUiRegistry : IScopedUiRegistry
{
public static NoOpUiRegistry Instance { get; } = new();
private NoOpUiRegistry()
{
}
public void AddMarkupPanel(string markupPath, object binding)
{
}
public void AddPanel(
PluginPanelDescriptor descriptor,
string markupPath,
object binding)
{
}
public IDisposable RegisterPanel(
PluginPanelDescriptor descriptor,
string markupPath,
object binding) => NoOpUiRegistration.Instance;
public IDisposable RegisterPanelContent(
PluginPanelDescriptor descriptor,
string markupContent,
object binding) => NoOpUiRegistration.Instance;
public IDisposable RegisterMarkupPanel(string markupPath, object binding) =>
NoOpRegistration.Instance;
public IDisposable RegisterPanel(
PluginUiOwner owner,
PluginPanelDescriptor descriptor,
string markupPath,
object binding) => NoOpRegistration.Instance;
public IDisposable RegisterPanelContent(
PluginUiOwner owner,
PluginPanelDescriptor descriptor,
string markupContent,
object binding) => NoOpUiRegistration.Instance;
private sealed class NoOpRegistration : IDisposable
{
internal static NoOpRegistration Instance { get; } = new();
public void Dispose()
{
}
}
}