Skip to content

3. Create the toolbar

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

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

void addProcess() {
  try {
    session.createShape(
      type: Shapes.rectangle,
      worldCenter: const DiagramPoint(0, 180), text: 'New process',
    );
  } on DiagramChangeDenied catch (error) {
    showDecision(error.decision);
  }
}

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

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

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.

Methods such as undo(), moveElements() and invokeAction() return a DiagramPolicyDecision. 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() {
  session.moveElements([processId], const DiagramPoint(20, 0))
      .requireAllowed();
}

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 decision for ordinary UI feedback. Creation helpers return an ID and throw on denial, which is why addProcess catches that exception.

An allowed result can be a no-op; it does not promise that content changed. Invalid arguments and malformed data can throw separately.

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