Skip to content

Navigation and motion ​

Move the viewport with controller.view and play transient effects with controller.effects. Both act on the mounted canvas. Neither moves elements, changes the document, nor adds undo entries. Geometry verbs take the same Motion to animate elements to their new places.

dart
// Jump to the selection.
controller.view.show(const ViewTarget.selection());

// Travel to an element.
await controller.view.show(ViewTarget.element('review'), motion: Motion.smooth);

// Particles flowing along a connector.
final flow = controller.effects.play(const Effect.flow(), on: 'review-to-approval');
flow.stop();

On this page ​

CategoryWhat you can do
Show a targetJump or travel to a point, rectangle, element, region, the selection or everything.
MotionChoose how the viewport travels, and interrupt it.
Animated geometryAnimate moved and resized elements.
Pointer and keyboardConfigure edge auto-pan and Arrow-key movement.
CoordinatesConvert pointer positions into drawing coordinates.
Read the viewObserve camera position, zoom and viewport.
Observe changesReact to view and effect changes without tracking document edits.
EffectsPlay effects on shapes and connectors; see Animation & effects.

Show a target ​

dart
Future<bool> show(ViewTarget target, {Motion? motion})
void stop()

controller.view.show moves the viewport to one target. Without motion it jumps; with motion it travels. The future completes with true once the target is shown, and with false when the target names missing content or the move is interrupted.

TargetShows
ViewTarget.point(point, {zoom})Centers a world point. Keeps the current zoom unless zoom is given.
ViewTarget.rectangle(rectangle, {padding = 48})Fits a world rectangle.
ViewTarget.element(id, {padding = 48})Fits an element's bounds.
ViewTarget.region(id, regionId, {padding = 48})Fits a named region of a shape or text element.
ViewTarget.selection({padding = 48})Fits the current selection.
ViewTarget.all({padding = 48})Fits every element in the diagram.
QuantityMeaning
point, rectangleWorld coordinates in the document.
paddingLogical screen pixels kept free on each side of the viewport.
zoomScale factor: 1 means 100%; clamped to the camera's zoom limits.

regionId is a shape-layer definition ID or a text slot ID, such as a card's title. Rotated regions fit their axis-aligned world bounds. Fitting preserves aspect ratio and chooses the smaller of the horizontal and vertical scales within zoom limits. Finite canvas bounds may constrain the requested center. A target that names a missing element, region or an empty selection returns false and leaves the viewport where it is.

Mount DiagramCanvas(controller: controller), editable or view-only, before calling show; a detached or disposed controller throws StateError. For initial placement, wait until the canvas has completed its first layout, for example in a post-frame callback. The target is captured when the move starts; it does not follow an element that moves later.

Motion ​

dart
const Motion({
  Duration duration = const Duration(milliseconds: 450),
  MotionCurve curve = MotionCurve.easeInOutCubic,
})

Motion is a plain description in the core library, shared by the camera and the geometry verbs. A zero or negative duration presents the change at once.

PresetDurationCurve
Motion.none0 ms—
Motion.quick200 msMotionCurve.easeOut
Motion.smooth450 msMotionCurve.easeInOutCubic
Motion.slow900 msMotionCurve.easeInOutCubic

MotionCurve.cubic(x1, y1, x2, y2) is an easing curve as CSS cubic-bezier describes it: from (0, 0) to (1, 1) through two control points whose x lies within 0 and 1; y may overshoot. The named curves are linear, ease, easeIn, easeOut, easeInOut and easeInOutCubic.

dart
final shown = await controller.view.show(
  const ViewTarget.region('approval', 'title', padding: 64),
  motion: const Motion(
    duration: Duration(milliseconds: 600),
    curve: MotionCurve.cubic(0.215, 0.61, 0.355, 1),
  ),
);
if (!shown) {
  // The region is missing, or another move interrupted this one.
}

Only one move runs at a time. Starting another move, a jump, controller.view.stop(), a user pan or zoom, or any other camera change such as controller.setCamera interrupts the current move, which then completes with false. stop leaves the camera where it is and is safe to call when nothing is moving. Unmounting the canvas also interrupts a move. A zero duration, or a device with animations turned off, shows the target immediately.

Overshooting curves may overshoot position, but zoom stays within camera limits. Targets must have finite coordinates and zoom, and curves finite control points; an invalid target or curve completes with ArgumentError without changing the camera.

Animated geometry ​

move, resize, setElementBounds and layout accept a motion. The document takes the final geometry at once: undo, persistence, rules, agents and hit testing see the target. The mounted canvas then animates the rendered geometry from where it showed the elements to their committed place:

dart
controller.edits.setElementBounds(
  'charger-load',
  const DiagramRect.fromLTWH(0, 100, 126, 8),
  motion: Motion.smooth,
);
await controller.edits.layout(Layout.layered(), motion: Motion.slow);
AspectBehavior
What movesEvery element the verb moved or resized, with its contents; text and ports follow. Many elements animate at once.
ConnectorsAttached connectors re-route every frame. Only the moving elements and their connectors are resolved again.
SelectionSelection chrome follows the animated geometry.
Pointer inputHits the committed geometry, even mid-animation.
A new changeRetargets smoothly from the shown frame. A change without motion jumps.
BatchesVerbs inside a batch, including batch(history: false), animate when the batch commits.
Reduced motionWith the platform's reduce-motion setting (MediaQuery.disableAnimations), elements jump.
No canvasA headless controller ignores motion.

Verbs performed by name take a Motion or a preset name: controller.edits.perform([id], 'move', {'delta': delta, 'motion': 'smooth'}).

Pointer and keyboard navigation ​

While dragging an element, handle, marquee or connection, entering the padding inside a canvas edge starts auto-pan. Speed increases toward the edge and continues while the pointer stays there. Corners pan on both axes. Leaving the edge area, releasing or cancelling the drag stops auto-pan. Hover alone does not move the canvas. The active edit follows the camera and remains one undo step.

Escape cancels the active gesture, including modifier combinations such as Space-drag panning, Shift rotation and Shift+Alt resizing. Document previews roll back without an undo entry; marquee selection returns to its starting selection. Panning stops at the current camera position. Remaining pointer movement and release cannot resume or commit the canceled gesture.

With canvas focus, Arrow keys move selected elements by one world unit in the pressed direction. Shift+Arrow moves them by ten world units; holding a key repeats movement. With no selection, neither shortcut moves the canvas. Text editing and focused widget controls retain their own arrow-key behavior. Selection movement supports undo and respects locks and host admission.

dart
final controller = DiagramController(
  configuration: DiagramConfiguration(
    extensions: [
      EditorExtension(),
      NavigationExtension(
        edgePanEnabled: true,
        edgePadding: 48,
        maximumPanSpeed: 600,
        keyboardStep: 1,
        shiftMultiplier: 10,
        fitOnAttach: true,
      ),
    ],
  ),
);

NavigationExtension (id vyuh.navigation) owns NavigationOptions, with the same fields; without it the canvas uses these defaults, except fitOnAttach, which defaults to false. fitOnAttach fits the content when the canvas first has a viewport. Change the values at runtime with controller.edits.setOptions(controller.options<NavigationOptions>().copyWith(...)). Distances use logical screen pixels; maximumPanSpeed uses pixels per second. Numeric values must be finite and positive. Set edgePanEnabled: false to disable pointer auto-pan. Fit canvas bounds still constrain navigation. Only move and resize drags auto-pan by default. Shape manipulators, custom controls, and other gestures leave the camera stationary. Custom tools can explicitly opt in with DiagramToolGesture(autoPan: true, ...); edgePanEnabled remains the global gate.

For touch pan and pinch, see DiagramCanvas.

Coordinates ​

dart
DiagramPoint screenToWorld(DiagramPoint screenPoint)
DiagramPoint globalToWorld(Offset globalPosition)
MethodArgumentReturn
screenToWorldPoint relative to the canvas's top-left, in logical pixels.World point under that canvas position.
globalToWorldFlutter global pointer position, such as details.globalPosition.World point under that pointer.

Both require a mounted canvas; globalToWorld also throws StateError before the canvas has been laid out. Do not pass a global pointer coordinate to screenToWorld when the canvas is embedded below a header or beside a panel.

camera ​

dart
DiagramCameraState get camera

Returns this controller's camera snapshot. It is never null; until a canvas is mounted and laid out, viewportSize is zero. Check isAttached to know whether a canvas is mounted. The immutable snapshot exposes:

PropertyTypeMeaning
centerDiagramPointWorld point at the viewport center.
zoomdoubleCurrent projection scale.
viewportSizeDiagramSizeLaid-out canvas size in logical pixels.
minZoomdoubleLower zoom limit; camera-state constructor default 0.25.
maxZoomdoubleUpper zoom limit; camera-state constructor default 4.

DiagramCameraState.copyWith(...) creates a snapshot; it does not update a controller. Use controller.view.show to change the mounted camera.

Observing the camera and effects ​

controller.camera and controller.effects.playing are observable. Read them inside an Observer to rebuild when they change. The camera changes after each new camera state, including layout, user navigation and motion; identical camera geometry does not notify. Effects change when they start, change or stop, not on every frame. These observations are separate from document events and undo/redo.

dart
Observer(
  builder: (_) => Text('${(controller.camera.zoom * 100).round()}%'),
);

Effects ​

controller.effects plays transient effects on the mounted canvas: flows and pulses along connectors, and pulses, halos, scaling and fades on shapes. See Animation & effects for the five kinds, their values, repeat counts, reduced motion and custom effects.

dart
final flow = controller.effects.play(const Effect.flow(), on: connectorId);
controller.effects.play(const Effect.halo(), on: shapeId);
flow.stop();

Selecting inside groups ​

Click an element in a group to select its group. Double-click it without modifiers to enter the group and select that child; subsequent clicks select children within that group. Press Escape to return to the containing group.

Cmd-click (macOS) or Ctrl-click selects a child without entering its group; Shift-click adds or removes selection. Then drag, delete or edit the child normally without affecting siblings. Select a child before clicking its text to edit.

On connection labels, Shift-click toggles the connector selection and Command/Ctrl-click selects the connector directly. These modified clicks do not start label editing. Use an unmodified double-click to edit the label.

Outside text editing, modifier-clicking a checklist marker selects its text owner without checking or unchecking the item. An ordinary click still toggles it.