Compose shapes and libraries
Build a compound as one registered shape. Its parts share movement, rotation, selection, text editing, history and persistence. Rows and columns arrange the parts inside its frame; they are portable geometry declarations, not Flutter widgets.
dart
import 'package:vyuh_diagram_kit/drawing.dart';
import 'package:vyuh_diagram_shapes_uml/vyuh_diagram_shapes_uml.dart';
final approval = ShapeDefinition(
type: 'app.approval',
displayName: 'Approval',
defaultSize: const DiagramSize(240, 140),
composition: ShapePadding(12, child: ShapeColumn([
ShapeSized(extent: 32, child: ShapeRow([
ShapeSized(extent: 32, child: const ShapeSurface(
outline: ShapeOutlineDefinition.ellipse(),
style: ShapeStyle(fill: DiagramColor(0xFFDBEAFE)),
)),
const ShapeText('title', text: 'Approval', singleLine: true),
], gap: 8)),
const ShapeText('body', text: 'Describe the decision'),
], gap: 8)),
);
final library = ShapeLibrary(
id: 'app', name: 'My application', shapes: [approval],
);
final session = DrawingSession(shapeRegistry: ShapeRegistry.fromLibraries([
ShapeLibraries.standard,
library,
]));Small layout vocabulary
Geometry, layout and appearance
Geometry describes frames, contours and paths. Layout computes where content fits within those bounds: rows, columns, stacks, sizing, gaps and padding. Appearance describes how that geometry is painted: fills, stroke colors, widths, dash styles and text formatting.
An outline's path belongs to geometry because clipping, hit testing and connector attachment also use it. Its fill and stroke belong to appearance. Some appearance properties affect measured bounds: text size changes text metrics, and stroke width can affect visible bounds and connector clearance.
Text uses named story slots and the shared paragraph system for formatting. Single-line constraints, wrapping and growth describe text behavior and layout, not just paint. Surfaces and paths carry their appropriate appearance properties. Connections additionally own endpoint bindings, routing and arrowheads; these behaviors remain separate from content layout inside a shape.
Parts are nestable and reusable: a padded column may contain a title row, a stacked body and a footer. They compile into ordinary layers, using the existing geometry and text resolvers. A new composed shape does not need its own painter, hit-test implementation or text editor. Use ShapePath for normalized path artwork and ShapeVector for polyline or polygon artwork within a composition.
Call the outer frame and outline shape geometry. Call meaningful interior areas content regions, such as title, body and footer. Content layout arranges those regions and their parts inside the shape. Diagram layout arranges whole shapes on the canvas; it is independent of this composition.
Region names describe their purpose. They do not require separate title, body or footer classes. For example, a column can contain a fixed-height ShapeText('title'), flexible ShapeText('body') and fixed-height ShapeText('footer'), using ShapeSized for the fixed heights.
| Part | Behavior |
|---|---|
ShapeRow(children, gap: ...) | Left-to-right; children stretch vertically. |
ShapeColumn(children, gap: ...) | Top-to-bottom; children stretch horizontally. |
ShapeSized(child: ..., extent: 32) | Fixed size along the containing row or column. |
ShapeSized(child: ..., flex: 2) | Twice the remaining space of a default flexible child. |
ShapePadding(12, child: ...) | Equal inset on every side. |
ShapeStack(children) | Paint parts back-to-front in the same bounds. |
ShapeSurface(outline: ..., style: ..., child: ...) | Static rectangle, ellipse, polygon or line surface, with optional content. |
ShapeText('slot', text: ...) | Editable named text; horizontal padding defaults to 8, vertical padding to 0. |
ShapePath(path, style: ...) | Open or closed normalized Bezier artwork using canonical path geometry. |
ShapeVector(paths) | Portable normalized polyline/polygon artwork. |
ShapeDivider(edge: ...) | Straight separator attached to a slot edge. |
ShapeImage() | The owning shape's image resource. |
Unwrapped children have flex 1. Fixed extents and gaps are allocated first; flex children share the remainder. Child minimums constrain resizing, and the default size must meet them.
Composition does not infer intrinsic text size, wrap rows or scroll. The outer outline clips the parts; surfaces do not add clipping boundaries.
There is no dedicated ShapeGrid; nest rows and columns for fixed grid-like arrangements. Live Flutter content currently uses a separately declared ShapeWidgetLayerDefinition, rather than a composition part. Inside that slot, Flutter controls use Flutter's own layout widgets. See widgets inside shapes for how composition, bounds and widget slots relate.
A surface has its own declared style. The ordinary shape fill/border inspector edits the outer shape, not every internal surface. Vector artwork follows the owning shape's stroke. Image slots currently share the owner's one image resource.
Reuse parts
Keep any ShapePart in a variable, or return it from a function. Use part.named('sender') and part.named('recipient') when repeating a part with text slots or explicitly named surfaces. This prefixes those identities—for example, sender.title—so the two instances retain separate content.
Edit a slot using session.text.setPlainText(elementId: id, slotId: 'title', text: 'Approved').requireAllowed(). Initial text is applied only when creating an instance. Resizing or rebuilding the registry never resets edited text.
Give a ShapeSurface an id to target its bounds with a port's targetLayerId. The existing port resolver applies the compound's transform and keeps attached connectors in sync. Use the full kit import for port declarations and advanced layer/widget APIs.
Choose libraries
Specialist libraries are independent packages: vyuh_diagram_shapes_uml, vyuh_diagram_shapes_flowchart, vyuh_diagram_shapes_workflow, and vyuh_diagram_shapes_network. Add only those you need and import their matching Dart entry point. For example, import package:vyuh_diagram_shapes_uml/vyuh_diagram_shapes_uml.dart for umlShapes. These packages depend only on the engine and types. The workflow shape library does not bring in the separate workflow authoring/runtime package.
The default library remains in the engine so the standard registry and existing hosts retain their defaults without a dependency cycle.
| Library | Initial definitions |
|---|---|
ShapeLibraries.standard | Existing built-in shapes, paths, images and drawing shapes. |
umlShapes | Class, interface, object, enumeration, data type, state, use case, component. |
flowchartShapes | Process, decision, terminal, subprocess, preparation, junction, extract. |
workflowShapes | Operation, wait, review, start, end, decision, parallel branches, nested workflow. |
networkShapes | Server, client, cluster. |
These are starter libraries, not exhaustive notation catalogs or execution models. Standalone text and connector grammar remain registry-level productions. Existing ShapeRegistry.standard() and withStandard(...) remain supported and obtain their built-in definitions from the standard library.
Use ShapeRegistry.fromLibraries([...]) to choose your vocabulary. Select an individual definition with umlShapes.shape('uml.class'), or create a subset with umlShapes.select(['uml.class'], id: 'my-uml', name: 'My UML'). Libraries retain the original definitions; duplicate type IDs are rejected rather than silently overridden. Save and reopen documents with the same registry.
Try the library playground. Its representative shapes are assembled using this public API. Compound parts belong to one element; use groups or child-owning shapes for independently selectable document elements.
The playground's Shape libraries panel sits above Properties. Switch between UML, Flowchart, and Workflow, then click a preview to add a shape or drag it to a canvas position. Previews come from the registered definitions, and additions support Undo. Collapse the panel to leave more room for Properties.
Flowchart tiles place their names inside the icons. New flowchart shapes start with centered text, declared using ShapeText('label', alignment: ParagraphAlignment.center). This is an initial paragraph style; subsequent text alignment edits are preserved.
Resize behavior is part of the grammar
For ParagraphAlignment, ShapeTextSizing, and advanced text grammar options in this section, import package:vyuh_diagram_kit/vyuh_diagram_kit.dart instead of the smaller drawing.dart entry point.
Text uses its declared rectangular region for layout and clipping. It is not masked by the outer contour by default, so slanted or curved edges do not cut through letters. Set ShapeCapabilities(clipTextToOutline: true) only when contour masking is intentional. This does not introduce contour-aware wrapping; use an inset text region when labels must remain entirely inside an outline.
Rows and columns adapt to the container size. Use fixed ShapeSized(extent: ...) headers with a flexible body, or make every child flexible for proportional resizing. With only fixed children, extra space stays at the end.
Text has horizontalPadding (8 by default), verticalPadding (0 by default), and verticalAlignment (top, center, bottom). Use top alignment in expandable bodies so existing text stays put while room grows below it. Padding and alignment resolve into the same text region used by paint, caret, selection and SVG export. UML definitions share a 40-unit title and one top-aligned multiline body. ShapeTextSizing.growHeight lets the body grow downward as content is added, while the title retains its height.
Use ShapeText('kind', text: 'Review', editable: false) for a fixed type label. It still uses the shared text layout and export pipeline, but cannot activate an editor. Document admission preserves its declared paragraph, and text commands reject changes to that slot. Adjacent title and body slots remain editable.
Build your own library
Use named actions and scoped keyboard bindings to share edits between buttons, interactive regions, and shape-specific text shortcuts.
A library is a named collection of definitions. The registry is the combined catalog used to create, resolve, edit and reopen a document. Include a definition only once: duplicate type identifiers are rejected.
dart
final applicationShapes = ShapeLibrary(
id: 'application',
name: 'Application shapes',
shapes: [
umlShapes.shape('uml.class'),
ShapeDefinition(
type: 'application.service',
composition: ShapeColumn([
ShapeSized(
extent: 40,
child: const ShapeText('title', text: 'Service', singleLine: true),
),
const ShapeText('members', text: '+ execute()'),
]),
),
],
);
final registry = ShapeRegistry.fromLibraries([
ShapeLibraries.standard,
applicationShapes,
]);Import umlShapes from package:vyuh_diagram_shapes_uml/vyuh_diagram_shapes_uml.dart. Add more definitions to applicationShapes.shapes when constructing the library, or combine several libraries in the registry. Libraries and definitions are immutable after construction.
Multiline slots accept additional paragraphs through the editor or session.text.setPlainText(elementId: id, slotId: 'members', text: ...). The UML package also exposes umlShape(type: ..., name: ..., title: ..., body: ...) to create your own variants with the same title/body layout and connection behavior. Fixed children stay fixed; use a flexible body and ShapeCapabilities(textSizing: ShapeTextSizing.growHeight) for content that expands the container.
While editing, overflow text and the caret remain visible beyond the text region and owning shape. Single-line titles stay on one baseline. This editing overlay uses the existing shaped paragraphs; leaving edit mode restores normal clipping.
UML shapes declare ShapeConnectionDefinition.outline(): no visible ports are required. Connector creation and endpoint dragging use the shared outline binding and standard virtual snap points. Choose the connector tool to start a connection; dragging the shape in selection mode continues to move it.