Discovery & persistence
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.
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.
[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.