Discovery & persistence

Drawing tools

Discovery & persistence

How a tool goes from a DLL on disk to an entry in the drawing toolbar — via reflective catalog discovery — and what state a tool carries so a saved annotation can be restored.

Discovery: the catalog

There is no manual registration. Tools are found by DrawingToolCatalog, which scans one or more assemblies for classes that inherit DrawingToolBase, carry a [DrawingToolMetadata] attribute, and expose a public parameterless constructor. A tool with the attribute but no parameterless constructor is a hard error at scan time — the catalog throws so the mistake surfaces immediately.

The catalog is immutable after construction and thread-safe to read. The host's drawing toolbar reads it to build categories, icons and hotkeys.

DrawingToolCatalog.csusing System.Reflection;
using TradeStrike.Pipeline.Drawing;

// Build a catalog from your plugin assembly (and any others):
var catalog = DrawingToolCatalog.FromAssembly(Assembly.GetExecutingAssembly());

IReadOnlyList<DrawingToolDescriptor> all = catalog.All;                  // sorted (Category, Order, Name)
var groups = catalog.ByCategory;                                          // grouped per Category

DrawingToolDescriptor? trend = catalog.FindByName("Trend Line");
DrawingToolDescriptor? byKey = catalog.FindByHotkey("Alt+T");

// Each descriptor carries the metadata plus a factory that builds a fresh instance:
IDrawingTool tool = trend!.Factory();

Each entry is a frozen DrawingToolDescriptor: the tool Type, the metadata fields (Name, Category, Hotkey, IconKey, AnchorCount, Order, Description), and a Func<IDrawingTool> Factory that constructs a fresh instance on demand.

DrawingToolDescriptor.cspublic sealed record DrawingToolDescriptor(
    Type ToolType,
    string Name,
    string Category,
    string? Hotkey,
    string? IconKey,
    int AnchorCount,
    int Order,
    string? Description,
    Func<IDrawingTool> Factory);

Category ordering

The catalog sorts by a category rank first, then by Order, then by name. The built-in ranks live in DrawingToolCatalog.DefaultCategoryRanks (Lines, Measures, Text, Arrows, Shapes, Brushes, Anchored, Fibonacci, Gann, Chart Patterns, Elliott Waves, Cycles). A category your plugin introduces that isn't in that map sorts after the built-ins, alphabetically among other unknown categories. So a brand-new tool category lands in a predictable place without you touching the host.

Multiple assemblies & the shared catalog

Use FromAssemblies(params Assembly[]) to scan core plus plugin DLLs together. The host also exposes a lazily-built shared DrawingToolCatalog.Default. Because the built-in tools ship in a runtime plugin assembly rather than the engine assembly, the host calls DrawingToolCatalog.ConfigureDefault(...) once after its plugin scan to point Default at the loaded plugin assemblies; until then Default reads as an empty catalog rather than throwing. As a plugin author you don't call ConfigureDefault — the host owns it — but it's why your tools appear in the toolbar only after the host's plugin scan completes.

Safe scanning. The catalog tolerates a partially loadable assembly — a ReflectionTypeLoadException is caught and the loadable types are still discovered — so one broken dependency in an unrelated plugin won't blank out the whole toolbar.

What a tool carries

A placed tool's identity and state live on the instance: its Id (a process-unique Guid generated at construction), its PanelId, the Anchors list, and the standard style surface — Color, LineWidthPx, LineStyle. Those are the fields the host reads when restoring a placed annotation. Anchors are (Time, Price) pairs (see the overview), which is what lets a restored tool land on the right place even after the bar grid has changed underneath it.

Custom [DrawingToolProperty] values need your attention. The standard style surface and anchors are the portable state. If your tool adds an extra setting (a fill colour, a flag) that must survive beyond the editing session, fold it into state the host round-trips or give your tool its own serialization — don't assume an arbitrary annotated property is persisted for you. The Fibonacci family keeps its configuration in a single FibLevelSet Levels property by convention.