Skip to content

3. Create the toolbar ​

Commercial extension tutorial · Built on the free Core + Flutter foundations. Start with the free Hello World example.

What belongs to the free foundations? ​

The toolbar in this step is ordinary Flutter UI. createShape(), undo(), redo(), EditResult and DiagramCanvas are available in the free Core + Flutter packages, so you can use this pattern in a foundation-only application.

The complete tutorial continues the setup from step 1, which installs the commercial EditorExtension for interactive editing and its supplied editor controls. Building your own buttons does not require that extension; using its ready-made palette, inspector and editing tools does. See Hello World for a complete foundation-only application, or Extending it with extensions for the next layer.

Build your toolbar ​

The host owns the toolbar layout; the controller owns its edits. Add these methods inside _DiagramSurfaceState:

dart
void showDecision(EditResult decision) {
  if (!decision.allowed && mounted) {
    ScaffoldMessenger.of(context).showSnackBar(SnackBar(
      content: Text(decision.reason ?? 'This edit is unavailable.'),
    ));
  }
}

void addProcess() {
  showDecision(controller.edits.createShape(
    Shapes.rectangle,
    at: const DiagramPoint(0, 180), text: 'New process',
  ));
}

Widget buildToolbar() => Wrap(
  spacing: 8,
  children: [
    TextButton(onPressed: addProcess, child: const Text('Add process')),
    TextButton(onPressed: () => showDecision(controller.edits.undo()),
      child: const Text('Undo')),
    TextButton(onPressed: () => showDecision(controller.edits.redo()),
      child: const Text('Redo')),
  ],
);

@override
Widget build(BuildContext context) => Column(
  children: [
    buildToolbar(),
    Expanded(child: DiagramCanvas(controller: controller)),
  ],
);

Replace the old build method with this one. New processes start at the same teaching position; move one aside before adding another.

Why does an edit return “allowed”? ​

An edit is a request. A shape may be locked, a capability may forbid resizing, a target may have disappeared, or your host may reject changes. The SDK checks the request before publishing it. Toolbar clicks, canvas gestures and API calls pass through shared admission and history.

Use controller.edits when controls need to inspect an EditResult. Direct creation returns the new ID; other direct edits complete or throw on refusal. Read allowed to handle refusal in your UI, as above. Use code for program logic and reason for a human-readable explanation.

dart
void moveProcess() {
  controller.move(const DiagramPoint(20, 0), ids: [processId]);
}

requireAllowed() checks the returned result and throws DiagramChangeDenied if denied. It neither asks permission nor forces the edit through. Use it when refusal should propagate as an exception, such as setup code or a test; handle the result for ordinary UI feedback. Creation verbs report the new ID in the result's id, which is null when the edit was denied.

An allowed result can be a no-op; it does not promise that content changed. Invalid verb arguments return a denial with DiagramDenialCode.unsupportedOperation.

Checkpoint: add a process, undo it and redo it. Next we will make history buttons reflect availability.