Create custom shapes
A custom type is a registry definition, not a new painter or a private drag implementation. Definitions and their parts are extension authoring types: import package:vyuh_diagram_flutter/extension_api.dart beside the main library. Declare its anatomy—outline, body, values, connection and behavior—and the shared runtime resolves its geometry and handles editing.
Declare a node
dart
import 'package:vyuh_diagram_flutter/extension_api.dart';
import 'package:vyuh_diagram_flutter/vyuh_diagram_flutter.dart';
ShapeDefinition reviewNode() => ShapeDefinition(
type: 'example.review',
outline: const ShapeOutline.rect(),
body: const ShapeText('body', horizontalPadding: 16, verticalPadding: 16),
minimumSize: const DiagramSize(140, 80),
size: const DiagramSize(220, 120),
style: const ShapeStyle(cornerRadius: 12),
behavior: const ShapeBehavior(
rotatable: false,
textSizing: ShapeTextSizing.growHeight,
),
connection: ShapeConnectionDefinition.fixedPorts([
PortDefinition.onSide(
id: 'input',
side: PortSide.left,
),
PortDefinition.onSide(
id: 'output',
side: PortSide.right,
),
]),
);
final reviewExtension = customShapesExtension();
DiagramExtension customShapesExtension() => DiagramExtension(
id: 'example.review',
shapes: [reviewNode()],
);The rounded rectangle outline gives the node editable fill, stroke and corner radius; its body text part makes its text editable.
Install reviewExtension in the configuration's extensions: DiagramController(configuration: DiagramConfiguration(extensions: [reviewExtension])). Before or after mounting, create an instance using the registered type:
dart
String insertReview(DiagramController controller) => controller.createShape('example.review', at: const DiagramPoint(320, 240));The createShape verb uses definition defaults and the canonical text-story factory. The instance participates in shared selection, text editing, history and routing.
Keep one definition per type
Extensions add unique types; two non-core extensions contributing the same type are rejected. The core extensions are always installed first as defaults: an installed contribution with the same key, such as a ShapeDefinition of type Shapes.rectangle, a text definition of type text or a DiagramConnectorDefinition() for connector, replaces the core one. To give a shape a palette button, add DiagramToolDefinition.forShapes([shape], group: ...) to the same extension's tools.
Declare behavior
ShapeBehavior declares selection, movement, deletion, resizing, rotation and how text sizes the shape. Containers add ShapeContainer for child ownership, clipping and child scaling. Everything else a person can edit—fill, stroke, corner radius, text and values—follows from the anatomy. See shape behavior. Per-instance locks use controller.edits.setLocked(locked, ids: ids) and inherit through owners; shared admission enforces them across commands.
The visible rotation handle is opt-in per definition. Keep rotation enabled while choosing whether to show its stem-and-circle control:
dart
const rotationBehavior = ShapeBehavior(rotationHandle: true);rotationHandle defaults to false. Desktop corner rotation remains available when rotatable is true. Setting rotatable: false suppresses every rotation control, including the visible handle.
Embed paragraph content
A text part uses the same Rich Text story and editor as a text box. Declare its slot and place it in the body; the engine owns input, selection, layout and undo. Several paragraph blocks can flow through one slot.
dart
final described = ShapeDefinition(
type: 'example.described',
behavior: const ShapeBehavior(textSizing: ShapeTextSizing.growHeight),
body: ShapePadding(
16,
child: const ShapeText(
'description',
horizontalPadding: 0,
verticalAlignment: DiagramTextVerticalAlignment.top,
placeholder: 'Add a description…',
),
),
);Multiline plain text is the default; textType: PlainTextDefinition(maxLines: 1) restricts a short title region to one line, and a RichTextDefinition allows formatting. ShapeTextSizing.growHeight grows the shape to accommodate content. Fixed-size regions retain their declared bounds. A title and body are two ShapeText parts in one column, not two text editors.
Text boxes created with a click start in automatic-width mode: Enter adds a new paragraph, while the box grows horizontally as you type. Drag to create a wrapping box, or resize its width afterward. Its height grows to fit the paragraphs.
Paragraphs retain canonical runs, marks, list kind, nesting and alignment. Shapes and connector labels share story ownership, layout and editing commands; a host should not add an independent text field with a second copy of the content.
Connections can be disabled, outline-bound, fixed-port, default-side-port, or instance-port based. ShapePortLayout.distribute spaces multiple ports evenly along a side.
Keep domain meaning above geometry
The names input and output in this example are stable port IDs. They do not automatically enforce workflow direction or payload compatibility. A domain package declares those meanings and supplies a canConnect rule in DiagramRules to enforce them.
Custom type IDs are open. Outlines are deliberately finite: rectangle, ellipse, polygon, fixed or computed path, regular polygon, editable path and ink. Body parts are a small closed set, with ShapeContentPart as the one extension point for content such as tables. Adding a genuinely new geometry or interaction primitive requires extending that shared grammar and resolution contract, not bypassing it with shape-specific painting.
Embed native controls with a ShapeWidget part and Flutter builders registered in DiagramFlutterExtension.widgets. Their values remain canonical shape content; the host supplies a portable fallback part for unmounted and output projections. See widgets inside shapes for the complete declaration, editing and lifecycle API.
See capability status before relying on embedded controls, port labels, or connector-label editing.
Shared text regions
Every text-capable shape uses the same story, block, run, and resolved-line pipeline. Define separate text regions for a card title and body rather than mounting private text editors.
A single-line region accepts one paragraph with inline formatting. Enter finishes editing that region. Multiline regions support paragraph blocks, ordered and unordered list items, nested lists, and checklist items. Typing [] or [ ] at the beginning of a multiline paragraph creates a checklist item.
The default line-height multiplier is 1.3. Lines reflow within the region width after accounting for list indentation. Empty paragraphs still resolve a full line and caret. The declared text sizing policy controls whether the owner grows or retains its bounds; rendering and editing consume the same line geometry.
Checklist markers use resolved geometry for painting and clicking. Clicking a checkbox toggles its canonical block state, preserves text selection, and can be undone.
Paragraph alignment applies only to ordinary paragraphs. Bulleted lists, numbered lists, and checklists remain left-aligned in their region, including nested items. Markers reserve their measured width plus a half-em gap before the item text.
Standalone text
Use TextElement for text that does not belong to a shape. Text elements are a core concept: every installed text type (plain text, or richText from RichTextExtension()) is a type of standalone text, whose element type is the text type's name, with the core's standard placeholder, sizes and behavior. An extension adds a text type through textTypes. It shares FramedElement transforms and the same rich-text story machinery as cards, but exposes no shape style, ports, image, or child ownership. The reserved text type is not a custom shape definition. See the text API for an insertion example.