بنقرة واحدة
beutl-tooltab-extension
// Implementation guide for Beutl's ToolTabExtension (tool-tab extension). Use when adding a custom tool tab to the editor. Triggers when ToolTabExtension, IToolContext, or docking-tab implementations are needed.
// Implementation guide for Beutl's ToolTabExtension (tool-tab extension). Use when adding a custom tool tab to the editor. Triggers when ToolTabExtension, IToolContext, or docking-tab implementations are needed.
| name | beutl-tooltab-extension |
| description | Implementation guide for Beutl's ToolTabExtension (tool-tab extension). Use when adding a custom tool tab to the editor. Triggers when ToolTabExtension, IToolContext, or docking-tab implementations are needed. |
ToolTabExtension is the extension point that adds a dockable tool tab to the Beutl editor. It follows the MVVM pattern with two classes: the Extension (metadata + factory) and the ViewModel (an IToolContext implementation).
Extension (base)
└─ ViewExtension
└─ ToolTabExtension (tool tab)
| Item | Core implementation | Extension implementation |
|---|---|---|
| Attribute | [PrimitiveImpl] | [Export] |
| Singleton | Define an Instance field | Not used (omit) |
| Registration | Add to LoadPrimitiveExtensionTask.PrimitiveExtensions | Registered automatically |
| Injecting Extension into ViewModel | Reference Instance | Inject via constructor |
ToolTabExtensionusing System.Diagnostics.CodeAnalysis;
using System.ComponentModel.DataAnnotations;
using Avalonia.Controls;
using Beutl.Extensibility;
using Beutl.Language;
using FluentAvalonia.UI.Controls;
namespace Beutl.Services.PrimitiveImpls;
[PrimitiveImpl]
[Display(Name = nameof(Strings.MyToolTab), ResourceType = typeof(Strings))]
public sealed class MyToolTabExtension : ToolTabExtension
{
public static readonly MyToolTabExtension Instance = new();
// Whether multiple instances are allowed
public override bool CanMultiple => false;
// Tab header (returning null hides it from the menu).
// Use null when the tab should only be opened from code.
public override string? Header => Strings.MyToolTab;
// Tab icon
public override IconSource GetIcon()
{
return new SymbolIconSource { Symbol = Symbol.Settings };
}
// Create the view (the UI control)
public override bool TryCreateContent(
IEditorContext editorContext,
[NotNullWhen(true)] out Control? control)
{
control = new MyToolTabView();
return true;
}
// Create the ViewModel (the IToolContext)
public override bool TryCreateContext(
IEditorContext editorContext,
[NotNullWhen(true)] out IToolContext? context)
{
context = new MyToolTabViewModel(editorContext);
return true;
}
}
IToolContext (core)using System.Text.Json.Nodes;
using Beutl.Extensibility;
using Beutl.Language;
using Reactive.Bindings;
namespace Beutl.ViewModels;
public sealed class MyToolTabViewModel : IToolContext
{
private readonly IEditorContext _editorContext;
private readonly CompositeDisposable _disposables = [];
public MyToolTabViewModel(IEditorContext editorContext)
{
_editorContext = editorContext;
}
// Reference the singleton
public ToolTabExtension Extension => MyToolTabExtension.Instance;
public IReactiveProperty<bool> IsSelected { get; } = new ReactivePropertySlim<bool>();
public string Header => Strings.MyToolTab;
public IReactiveProperty<ToolTabExtension.TabPlacement> Placement { get; } =
new ReactivePropertySlim<ToolTabExtension.TabPlacement>(
ToolTabExtension.TabPlacement.RightUpperBottom);
public IReactiveProperty<ToolTabExtension.TabDisplayMode> DisplayMode { get; } =
new ReactivePropertySlim<ToolTabExtension.TabDisplayMode>();
public void Dispose() => _disposables.Dispose();
public void ReadFromJson(JsonObject json) { }
public void WriteToJson(JsonObject json) { }
public object? GetService(Type serviceType)
=> _editorContext.GetService(serviceType);
}
PrimitiveExtensions// Add Instance to PrimitiveExtensions in LoadPrimitiveExtensionTask.cs
public static readonly Extension[] PrimitiveExtensions =
[
// ... existing extensions ...
MyToolTabExtension.Instance,
];
<!-- src/Beutl.Language/Strings.resx -->
<data name="MyToolTab" xml:space="preserve">
<value>My Tool Tab</value>
</data>
<!-- src/Beutl.Language/Strings.ja.resx -->
<data name="MyToolTab" xml:space="preserve">
<value>マイツールタブ</value>
</data>
ToolTabExtensionusing System.Diagnostics.CodeAnalysis;
using System.ComponentModel.DataAnnotations;
using Avalonia.Controls;
using Beutl.Extensibility;
using FluentAvalonia.UI.Controls;
namespace MyExtension;
[Export] // Extensions use the [Export] attribute
[Display(Name = nameof(Strings.MyToolTab), ResourceType = typeof(Strings))]
public sealed class MyToolTabExtension : ToolTabExtension
{
// No singleton (omit it)
public override bool CanMultiple => false;
public override string? Header => Strings.MyToolTab;
public override IconSource GetIcon()
{
return new SymbolIconSource { Symbol = Symbol.Settings };
}
public override bool TryCreateContent(
IEditorContext editorContext,
[NotNullWhen(true)] out Control? control)
{
control = new MyToolTabView();
return true;
}
public override bool TryCreateContext(
IEditorContext editorContext,
[NotNullWhen(true)] out IToolContext? context)
{
// Inject the Extension instance (this) via the constructor
context = new MyToolTabViewModel(this, editorContext);
return true;
}
}
IToolContext (extension)using System.Text.Json.Nodes;
using Beutl.Extensibility;
using Reactive.Bindings;
namespace MyExtension;
public sealed class MyToolTabViewModel : IToolContext
{
private readonly IEditorContext _editorContext;
private readonly CompositeDisposable _disposables = [];
// Accept the Extension in the constructor
public MyToolTabViewModel(ToolTabExtension extension, IEditorContext editorContext)
{
Extension = extension;
_editorContext = editorContext;
}
// Return the instance injected in the constructor
public ToolTabExtension Extension { get; }
public IReactiveProperty<bool> IsSelected { get; } = new ReactivePropertySlim<bool>();
public string Header => Strings.MyToolTab;
public IReactiveProperty<ToolTabExtension.TabPlacement> Placement { get; } =
new ReactivePropertySlim<ToolTabExtension.TabPlacement>(
ToolTabExtension.TabPlacement.RightUpperBottom);
public IReactiveProperty<ToolTabExtension.TabDisplayMode> DisplayMode { get; } =
new ReactivePropertySlim<ToolTabExtension.TabDisplayMode>();
public void Dispose() => _disposables.Dispose();
public void ReadFromJson(JsonObject json) { }
public void WriteToJson(JsonObject json) { }
public object? GetService(Type serviceType)
=> _editorContext.GetService(serviceType);
}
Add Strings.resx and Strings.ja.resx inside the extension project, generated via ResXFileCodeGenerator.
<!-- MyToolTabView.axaml -->
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyExtension.MyToolTabView">
<TextBlock Text="Hello from My Tool Tab!"
VerticalAlignment="Center"
HorizontalAlignment="Center"/>
</UserControl>
| Value | Meaning |
|---|---|
| LeftUpperTop | Left sidebar, upper area, top |
| LeftUpperBottom | Left sidebar, upper area, bottom |
| LeftLowerTop | Left sidebar, lower area, top |
| LeftLowerBottom | Left sidebar, lower area, bottom |
| RightUpperTop | Right sidebar, upper area, top |
| RightUpperBottom | Right sidebar, upper area, bottom |
| RightLowerTop | Right sidebar, lower area, top |
| RightLowerBottom | Right sidebar, lower area, bottom |
IEditorContext| Service | Description |
|---|---|
IEditorSelection | Observe the currently selected object |
IEditorClock | Observe the playback clock |
IPreviewPlayer | Control preview playback |
IElementAdder | Add elements |
IPropertyEditorFactory | Build property editors |
IPropertiesEditorFactory | Build property-list editors |
HistoryManager | Undo/redo history |
Scene | Current scene |
Beutl.ExtensibilityBeutl.EditorReactive.BindingsFluentAvalonia (for icons)For detailed implementation patterns, see references/implementation-patterns.md.
Implementation guide for Beutl's Drawable class. Use when adding a new Drawable (drawable object) to a Beutl project. Triggers on "create a Drawable", "implement a drawable object", "add an element like SourceImage/Shape/TextBlock". Covers both the Beutl core and extension packages.
Implementation guide for Beutl's FilterEffect. Use when authoring a new filter effect (blur, color correction, drop shadow, etc.). Triggers: "create a FilterEffect", "implement a new effect", "add a filter", "Beutl effect dev", "SKSL", "GLSL effect".