ComponentPalette
Toolbox/palette for drag-and-drop component discovery via SkiaComponentRegistry
Namespace: Beep.Skia.Controls — Inherits: MaterialControl → SkiaComponent
Table of Contents
Overview
The ComponentPalette control is a drag-and-drop toolbox that displays all registered SkiaComponent types as a categorized, searchable grid of icon+label tiles. It reads its inventory from the SkiaComponentRegistry — the centralized registry that maps component type metadata to factory delegates — enabling dynamic component discovery without hard-coded type lists.
Users can browse components by category, filter by keyword, select a display mode (icon grid or compact list), and drag items onto a canvas to instantiate new components. The palette renders entirely with SkiaSharp and follows Material Design 3.0 styling with elevation, state layers, and smooth drag-feedback animations.
Registry-Driven
Categories and items are populated automatically from SkiaComponentRegistry. No manual configuration required.
Search & Filter
A built-in search field filters components in real-time by name, description, or category tags.
Drag Ghost
Dragging a palette item creates a semi-transparent "ghost" preview that follows the pointer.
Custom Extensions
Register custom components and categories at runtime via the SkiaComponentRegistry API.
Properties
| Property | Type | Description |
|---|---|---|
Categories |
ObservableCollection<ComponentCategory> |
The grouped categories and their component items, built by LoadComponentsFromRegistry(). Each ComponentCategory exposes Name, Icon and its own Components collection. |
SelectedCategory |
ComponentCategory |
The currently selected category, if any. |
ItemHeight |
float |
Height of a component item row. |
CategoryHeaderHeight |
float |
Height of a category header row. |
DraggedComponent |
ComponentItem |
Read-only. The item currently being dragged, or null. |
IsDragging |
bool |
Read-only. True while a component drag operation is in progress. |
| Event | Signature | Description |
|---|---|---|
ComponentDragStarted |
EventHandler<ComponentDragEventArgs> |
Raised when the user begins dragging a component from the palette. The event carries the ComponentItem and pointer position. |
ComponentDragging |
EventHandler<ComponentDragEventArgs> |
Raised continuously while a component is dragged, with the current pointer position. |
ComponentDragEnded |
EventHandler<ComponentDragEventArgs> |
Raised when the user releases the drag. The event carries the ComponentItem, the final pointer position and the ComponentDragState. |
ComponentSelected |
EventHandler<ComponentItem> |
Raised when a component is selected in the palette. |
Component Discovery
The ComponentPalette discovers components exclusively through the SkiaComponentRegistry. This registry holds metadata for every component type available in the system, organized by category. The palette reads this data at initialization and builds its internal category/item structure.
Registry Entries
Each registry entry is an AssemblyClassDefinition (the shared BeepDM metadata type). The palette and the registry use these members:
| Member | Type | Description |
|---|---|---|
className |
string |
The component class name; the palette converts it to a display label (for example ProcessNode becomes "Process Node"). |
dllname |
string |
Assembly that contains the component; used together with className as the registry's duplicate key. |
GuidID |
string |
Stable identifier used by the registry lookups (GetComponentByGuid). |
type |
Type |
Resolved CLR type used for instantiation and namespace grouping. |
Methods |
List<MethodsClass> |
Optional command methods surfaced as child items in the palette and exposed through GetComponentsWithMethods(). |
How the Palette Reads the Registry
The palette builds its UI by calling LoadComponentsFromRegistry(), which reads SkiaComponentRegistry.GetComponentsOrdered() and groups the results into Categories — an ObservableCollection<ComponentCategory>. Each category exposes a Name, an Icon and an ObservableCollection<ComponentItem>; each ComponentItem wraps an AssemblyClassDefinition and derives its display name from the class name.
Call LoadComponentsFromRegistry() again after registering components at runtime to rebuild the category structure.
Drag-Drop Workflow
The drag-and-drop workflow is a multi-step interaction handled cooperatively by the palette and the target canvas:
- Pointer Down on Tile: The palette identifies which
ComponentItemis under the pointer via hit testing. - Drag Threshold: Once the pointer moves beyond the drag threshold, the palette raises
ComponentDragStarted; a drag ghost follows the pointer whileComponentDraggingfires continuously. - Dragging Over Canvas: The canvas renders a drop indicator based on the pointer position carried in
ComponentDragEventArgs.MousePosition. - Pointer Up:
ComponentDragEndedfires with theComponentItemand the final pointer position. The host creates the instance throughSkiaComponentRegistry.CreateComponentInstance(), positions it and adds it to the drawing manager. - Cancel: Pressing Escape during a drag cancels the operation; the ghost is removed and
ComponentDragEndedreports the cancelled state.
Canvas-Side Drop Handler
palette.ComponentDragEnded += (s, args) =>
{
if (args.State != ComponentDragState.Ended) return;
var component = SkiaComponentRegistry.CreateComponentInstance(args.Component.ClassDefinition);
if (component == null) return;
component.X = args.MousePosition.X;
component.Y = args.MousePosition.Y;
drawingManager.AddComponent(component);
drawingManager.RequestRedraw();
};
Custom Category Registration
The supported way to add custom components is the extension SDK: an ISkiaExtension declares its component types, and the host registers them with an optional palette category and display name. See the Extension SDK guide for packaging and discovery.
Registering Custom Components
public sealed class WidgetsExtension : ISkiaExtension
{
public string Id => "com.acme.widgets";
public string Name => "Acme Widgets";
public string Version => "1.0.0";
public string Description => "Gauge and status indicator nodes";
public IEnumerable<Type> GetComponentTypes() => new[]
{
typeof(CustomGaugeNode),
typeof(StatusIndicator)
};
public void Initialize(ISkiaExtensionContext context)
{
context.RegisterComponent(typeof(CustomGaugeNode), "Custom Widgets", "Gauge");
context.RegisterComponent(typeof(StatusIndicator), "Custom Widgets", "Status Indicator");
}
}
Extending a Flowchart Node
public class PipelineStageNode : ProcessNode
{
public PipelineStageNode()
{
Label = "Pipeline Stage";
Width = 140;
Height = 60;
}
protected override void DrawFlowchartContent(SKCanvas canvas, DrawingContext context)
{
// Draw the base node, then add family-specific decoration
base.DrawFlowchartContent(canvas, context);
}
}
Code Example
Basic ComponentPalette Setup
var palette = new ComponentPalette
{
X = 10,
Y = 50,
Width = 320,
Height = 600,
ItemHeight = 28f,
CategoryHeaderHeight = 32f
};
palette.LoadComponentsFromRegistry();
palette.ComponentSelected += (s, item) =>
Console.WriteLine($"Selected: {item.DisplayName}");
palette.ComponentDragStarted += (s, args) =>
Console.WriteLine($"Dragging: {args.Component.DisplayName}");
palette.ComponentDragEnded += (s, args) =>
{
if (args.State != ComponentDragState.Ended)
{
Console.WriteLine("Drag cancelled");
return;
}
var component = SkiaComponentRegistry.CreateComponentInstance(args.Component.ClassDefinition);
if (component == null) return;
component.X = args.MousePosition.X;
component.Y = args.MousePosition.Y;
drawingManager.AddComponent(component);
drawingManager.RequestRedraw();
};
drawingManager.AddComponent(palette);
drawingManager.RequestRedraw();
Working with Categories Programmatically
// Iterate the grouped categories and their items
foreach (var category in palette.Categories)
{
Console.WriteLine($"{category.Name} ({category.Components.Count})");
foreach (var item in category.Components)
Console.WriteLine($" {item.DisplayName}");
}
// Select a category programmatically
palette.SelectedCategory = palette.Categories.FirstOrDefault();
// Track the active drag
if (palette.IsDragging)
Console.WriteLine(palette.DraggedComponent?.DisplayName);
Refreshing After Runtime Registration
// Register through an extension, then rebuild the palette
palette.LoadComponentsFromRegistry();
drawingManager.RequestRedraw();
Note
The ComponentPalette is a read-only view of the registry: it never modifies entries. Register components through the extension SDK (or SkiaComponentRegistry.AddComponent) and call LoadComponentsFromRegistry() again to refresh the palette.
Tip
For large component libraries, keep the registry tidy by grouping related nodes into one extension package. The palette derives its categories from the registry, so a clean package structure produces a clean palette.