Skip to content

8. Share verbs and shortcuts ​

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

A verb is a named edit that a button, a shortcut, the inspector and an agent all perform the same way. Add this import:

dart
import 'package:flutter/services.dart';

Declare the verb on the review node, and bind a shortcut to it in the review extension. The shape and its shortcut travel together in one DiagramFlutterExtension, installed in the diagram's configuration:

dart
late final controller = DiagramController(
  configuration: DiagramConfiguration(
    extensions: [
      EditorExtension(inspector: InspectorPlacement.none, toolbar: false),
      reviewExtension(),
    ],
  ),
);

DiagramFlutterExtension reviewExtension() => DiagramFlutterExtension(
  id: 'tutorial.review',
  shapes: [reviewNode()],
  shortcuts: const [
    DiagramKeyBinding(
      verb: 'grow',
      key: LogicalKeyboardKey.keyG,
      modifiers: {DiagramKeyModifier.command, DiagramKeyModifier.shift},
      scope: DiagramKeyScope.selection,
    ),
  ],
);

/// Grows a review node by 40 × 20. It derives a new size, so `resize` with
/// the previous size expresses its reverse.
final growReview = DiagramVerb(
  id: 'grow',
  label: 'Grow',
  symmetry: const DiagramVerbSymmetry.derived(reversedBy: 'resize'),
  target: DiagramVerbTarget.element,
  perform: (call) {
    final frame = call.document.shapeById(call.id)!.frame;
    return call.controller.edits.perform(
      [call.id],
      'resize',
      {'size': DiagramSize(frame.width + 40, frame.height + 20)},
    );
  },
);

ShapeDefinition reviewNode() => ShapeDefinition(
  type: 'tutorial.review',
  verbs: [growReview],
  // ...the rest of the review node from step 7
);

Add this method and include buildGrowButton() in the reactive toolbar:

dart
Widget buildGrowButton() {
  final selection = controller.selection;
  final ids = selection is ElementSelection
      ? selection.elementIds : <String>{};
  final id = ids.length == 1 ? ids.single : null;
  return TextButton(
    onPressed: id != null && controller.canPerform([id], 'grow').allowed
        ? () => showDecision(controller.edits.perform([id], 'grow'))
        : null,
    child: const Text('Grow review'),
  );
}

The verb belongs to the review node, so only review nodes offer it: canPerform denies it on a diamond, and on a locked review node. Availability is checked again when the verb runs. The verb performs the core resize verb, so it is one admitted edit and one Undo entry; a denial changes nothing.

Every verb is two-way. grow computes a new size, and resize with the previous size is its reverse, so it declares DiagramVerbSymmetry.derived(reversedBy: 'resize'). A verb that sets a value, such as a count, is a setter instead: a button that increments passes the next value rather than calling a one-way increment.

Here command maps to Meta on macOS and Control elsewhere. Selection-scoped bindings belong to object selection, not active text editing or a focused host text field. Choose a chord that does not collide with your application's shortcuts.

Checkpoint: select a review node and use Grow review, then Command/Ctrl+Shift+G with canvas focus. Both grow it and Undo reverses each operation. Select a diamond: Grow review disables. Verbs and shortcuts covers canvas/text scopes and painted regions.