Shapes, elements and registries
A definition describes a reusable shape type. An element is one immutable record in the document. The registry connects its type to the definition. The session inserts and changes records through the canonical engine.
| Type | Package | Role |
|---|---|---|
DiagramElement | vyuh_diagram_types | Base authored record with identity, stacking and lock state. |
ShapeElement | vyuh_diagram_types | A shape instance: type, frame, appearance and content. |
TextElement | vyuh_diagram_types | Standalone text; shared rectangular mechanics without shape appearance. |
ConnectorElement | vyuh_diagram_types | Connection endpoints, route, markers and labels. |
ShapeDefinition | vyuh_diagram_layout | Reusable geometry, content slots, ports and interaction grammar. |
ShapeRegistry | vyuh_diagram_layout | Definition lookup and validation. |
DrawingSession | vyuh_diagram_kit | Host commands, state, configuration and lifetime. |
Import package:vyuh_diagram_kit/vyuh_diagram_kit.dart in a Flutter host; it exports these types. Pure Dart hosts can import the types, layout and engine packages directly.
session.addElement
Use an element directly when you already know its identity, frame and content:
dart
DiagramPolicyDecision addElement(
DiagramElement element, {
bool select = true,
})
final decision = session.addElement(ShapeElement(
id: 'review-card',
type: BuiltInShapeType.card,
frame: const DiagramRect.fromLTWH(200, 160, 240, 160),
));| Argument / result | Contract |
|---|---|
element | Required immutable record. Shapes, text and connectors use this same method. |
select | Defaults to true; selects the inserted record. false preserves selection. |
| Result | DiagramPolicyDecision; denied changes return a reason. Call requireAllowed() to throw on denial. |
| Defaults | Preserves the supplied record; does not invent an ID, story, style, size or parent. |
| Validation | Registry, identity, ownership, bindings and the store's beforeChange admission still apply. Invalid records throw; no partial insertion occurs. |
| History | One admitted transaction and undo entry. Works before mounting a canvas. |
The factory-specific shapeCreationPolicy and connectionPolicy are not called by raw insertion. Put permissions that must cover every mutation in the canonical beforeChange hook, which also covers transactions, imports and history.
session.createShape
Use named arguments when the registry should supply defaults. A host no longer needs to wrap the call in DiagramShapeCreation.
dart
final cardId = session.createShape(
type: BuiltInShapeType.card,
worldCenter: const DiagramPoint(320, 240),
size: const DiagramSize(240, 160),
);Returns String, the created element ID. These are all named arguments:
| Argument | Type | Default / behavior |
|---|---|---|
type | String | Required registered shape type. Standalone text uses createText. |
worldCenter | DiagramPoint | Required center in world coordinates. |
id | String? | Allocated when omitted; explicit IDs must be unique. |
size | DiagramSize? | Definition's defaultSize; requested dimensions are clamped up to its minimum. |
style | ShapeStyle? | Definition's defaultStyle. |
textStory | DiagramTextStory? | Materialized from the definition's declared text slots when omitted. |
textSizingMode | DiagramTextSizingMode | fixedWidth; autoWidth opts into intrinsic text width. |
ports | List<DiagramPortInstance> | Empty; admitted by the registered connection grammar. |
normalizedPath | List<DiagramPoint> | Empty; normalized authored-path geometry when supported. |
pathWidths | List<double> | Empty; per-point pressure multipliers when supported. |
polygonSides | int? | Omitted; only applicable to a supporting polygon definition. |
imageSource | DiagramImageSource? | Omitted; only applicable to a supporting image definition. |
parent | DiagramShapeParentPlacement | const AutoDiagramShapeParent(): deepest accepting owner beneath the center. |
select | bool | true: select the new shape. |
zIndex | int | 0. |
Use const RootDiagramShapeParent() for explicit root placement, or OwnedDiagramShapeParent(ownerId) for an explicit child-owning shape. Missing or unsuitable explicit owners throw. Registry admission, shapeCreationPolicy and beforeChange run before successful publication. Denial throws rather than returning a successful ID. The operation creates one undo entry and requires no mounted canvas.
session.createText
dart
final textId = session.createText(
worldCenter: const DiagramPoint(320, 240),
textStory: DiagramTextStory.paragraph(
id: 'notes-story', blockId: 'notes-body', text: 'Your notes',
),
);Returns the new String ID. Named arguments are worldCenter (required), id, size, textStory, textSizingMode, parent, select, and zIndex, with the same types/defaults as above. Omitted size and story come from ShapeRegistry.text. There are no fill, border, port, image or path arguments.
The engine still uses DiagramShapeCreation as an advanced factory/preview request, including in shapeCreationPolicy and atomic createConnectedShape. That request is not a document element and is not persisted. Ordinary host insertion does not require it. This distinction is intentional; the advanced request naming remains a candidate for further consolidation.
ShapeElement
| Constructor property | Type | Default / contract |
|---|---|---|
id, type | String | Required identity and registry type. |
frame | DiagramRect | Required authored frame. |
rotation | double | 0, radians around the frame center. |
style | ShapeStyle | const ShapeStyle(); raw records do not acquire definition defaults. |
textStory | DiagramTextStory? | null. |
textSizingMode | DiagramTextSizingMode | fixedWidth. |
ports, normalizedPath, pathWidths | Lists | Empty immutable snapshots; subject to the definition's grammar. |
polygonSides, imageSource | int?, DiagramImageSource? | null. |
contentScrollOffset | double | 0; text content scroll intent. |
parentId | String? | null; explicit owner identity when present. |
clipContent | bool | false; requires a child-clipping definition. |
isLocked | bool | false; per-instance editing lock. |
zIndex | int | 0; stacking order. |
Records are immutable. Use copyWith to construct a replacement, then submit it through a command or transaction. Geometry and story data are validated on admission.
ShapeDefinition
| Constructor property | Type | Default / contract |
|---|---|---|
type | String | Required open registry key. |
outline | ShapeOutlineDefinition | Required outline grammar. |
textBounds | ShapeBoundsDefinition | Required text-region bounds grammar. |
supportsText | bool | true. |
minimumSize | DiagramSize | 24 × 24. |
defaultSize | DiagramSize | 160 × 100. |
defaultStyle | ShapeStyle | const ShapeStyle(). |
shadow | ShapeShadowDefinition? | null. |
capabilities | ShapeCapabilities? | ShapeCapabilities() when omitted. |
layers | List<ShapeLayerDefinition> | Empty immutable snapshot; text slots, regions, dividers and visuals. |
connection | ShapeConnectionDefinition | const ShapeConnectionDefinition.outline(). |
DiagramTextStory? createTextStory(String elementId) materializes the declared text slots and initial content. It returns null when text is unsupported or not editable. It does not reset an existing element's story.
ShapeRegistry
| Construction | Behavior |
|---|---|
ShapeRegistry.standard(...) | Built-in shape definitions. |
ShapeRegistry.withStandard(definitions, overrides: ..., ...) | Add unique definitions; intentionally replace named built-ins through overrides. |
ShapeRegistry(definitions, ...) | Curated set supplied by the host. |
Duplicate IDs are rejected. Definitions are immutable. Register required types before opening a document; live replacement and migration of registered definitions remain unfinished. An already registered type can be instantiated at any time.
Shape registration does not install a toolbar button. DiagramToolRegistry separately declares creation tools, shortcuts and contributions. See custom shapes for a complete example.
Mutation and locking
| Operation | API | Behavior |
|---|---|---|
| Lock / unlock | session.setElementsLocked(ids, locked) | Returns DiagramPolicyDecision; one admitted reversible change. |
| Delete | session.removeElements(ids) | Removes the ownership/dependency closure; locks, deletion capabilities and admission apply. |
| Move | MoveShapeStep(elementId: id, delta: ...) | Relative framed-element translation. |
| Resize | ResizeShapeStep(...) | Framed-element resize; see transaction step arguments. |
| Rotate | RotateShapeStep(...) | Framed-element rotation. |
| Replace | ReplaceElementStep(before: oldElement, after: newElement) | Rejects a stale expected record. |
| Replace text | ReplaceTextStoryStep(...) | Canonical story replacement. |
Submit steps with session.dispatch(DiagramTransaction(steps: [...])); related steps publish and undo together. Raw steps are the advanced authoring interface. Capabilities and instance locks are editing declarations, not authorization for arbitrary host-authored transactions; use beforeChange for permissions.
Text elements and rectangular mechanics
TextElement is a distinct sibling of ShapeElement. Both extend FramedElement, so movement, resize, rotation, parent placement, selection, and history share the same mechanics. Text owns a required rich-text story; it has no shape style, fill, border, ports, image, path, or child-clipping fields.
Configure standalone text through ShapeRegistry.text, using the text argument on ShapeRegistry.standard, ShapeRegistry.withStandard, or the registry constructor. Do not register a ShapeDefinition with BuiltInShapeType.text or construct ShapeElement(type: BuiltInShapeType.text).
dart
final registry = ShapeRegistry.standard(
text: TextElementDefinition(
minimumSize: const DiagramSize(32, 24),
defaultSize: const DiagramSize(200, 48),
),
);TextElementDefinition declares text bounds, text layers, minimum/default sizes, and shared interaction capabilities. Its defaults grow text height and retain the selection outline during transforms. The definition rejects shape surfaces and child ownership/clipping. It does not introduce a second paragraph editor.
Text color belongs to DiagramTextRun.color. A null run color inherits the text region foreground. See standalone text for the record and persistence for legacy imports.
Transform geometry for custom commands
The pure Dart engine exposes geometry operations for custom commands and tools. They return immutable elements; they do not bypass publication, permissions, ownership closure, or history. Submit results through normal transactions.
| API | Arguments | Result |
|---|---|---|
DiagramElementTransform.translate | DiagramElement element, DiagramPoint delta | Translates a shape/text frame or connector free points. |
DiagramElementTransform.rotate | DiagramElement element, required DiagramPoint pivot, required double radians | Rotates placement about a world-space pivot; framed element dimensions remain unchanged. |
DiagramResizeTransform | Required DiagramRect fromBounds, DiagramRect toBounds; double rotation = 0 | Creates one immutable mapping for all elements in a resize selection. Bounds are expressed in its unrotated coordinate frame. |
resize.mapPoint | DiagramPoint point | Maps a world point through the resize coordinate frame. |
resize.apply | DiagramElement element; optional DiagramRect resolvedFrame, bool fixTextWidth = false | Maps frame or free connector geometry. A horizontal text resize can set fixed wrapping width. |
Use the resolved frame captured at the beginning of the gesture for auto-grown text. Use bounds from that same scene revision, and enforce registry minima when choosing toBounds. The mapping preserves text, styles, local paths, and bound endpoint identity. The resolver determines the final bound connector positions. Non-finite inputs and non-positive resize bounds are rejected.
Rounded outline geometry
Rounded rectangle radii fit to half the smaller frame dimension. The resolved outline uses those fitted arcs for containment and connector boundary anchors, as well as painting. A child clipped by a rounded parent cannot be hit through the parent's cut-off corners. Rotation is applied through the shared local/world transform; it does not change the local corner radius.
Content layer containment
Region fills, dividers and image visuals are clipped to their owning shape's resolved outline, including rounded corners and rotated outlines. Interactive regions use the same containment: clipped-away corners cannot activate a region. This is a rule of the layer grammar, independent of child-frame clipping.
Text continues to use ShapeCapabilities.clipTextToOutline. Ports and shadows are separate productions and can extend beyond the outline. On-screen drawing and PNG output share the scene painter and therefore the same content clip.