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.