@@ -60,7 +60,7 @@ minimap, and more.
6060 - [ Custom Annotations] ( #custom-annotations )
6161 - [ Following Nodes] ( #following-nodes )
6262- [ Interactive Features] ( #interactive-features )
63- - [ Editor Callbacks ] ( #editor-callbacks )
63+ - [ Event System ] ( #event-system )
6464 - [ Keyboard Shortcuts] ( #keyboard-shortcuts )
6565 - [ Feature Toggles] ( #feature-toggles )
6666- [ Minimap] ( #minimap )
@@ -900,7 +900,7 @@ controller.addConnection(connection);
900900
901901### Connection Validation
902902
903- Validate connections before they're created:
903+ Validate connections before they're created using the event system :
904904
905905<details >
906906<summary ><strong >Connection Validation Example</strong ></summary >
@@ -911,42 +911,46 @@ NodeFlowEditor<MyData>(
911911 theme: theme,
912912 nodeBuilder: _buildNode,
913913
914- // Validate when starting a connection
915- onBeforeStartConnection: (context) {
916- // Don't allow connections from disabled nodes
917- if (context.sourceNode.data.isDisabled) {
918- return ConnectionValidationResult.invalid(
919- reason: 'Cannot connect from disabled node',
920- );
921- }
922- return ConnectionValidationResult.valid();
923- },
914+ events: NodeFlowEvents<MyData>(
915+ connection: ConnectionEvents<MyData>(
916+ // Validate when starting a connection
917+ onBeforeStart: (context) {
918+ // Don't allow connections from disabled nodes
919+ if (context.sourceNode.data.isDisabled) {
920+ return ConnectionValidationResult.invalid(
921+ reason: 'Cannot connect from disabled node',
922+ );
923+ }
924+ return ConnectionValidationResult.valid();
925+ },
924926
925- // Validate when completing a connection
926- onBeforeCompleteConnection : (context) {
927- // Don't allow self-connections
928- if (context.sourceNode.id == context.targetNode.id) {
929- return ConnectionValidationResult.invalid(
930- reason: 'Cannot connect node to itself',
931- );
932- }
927+ // Validate when completing a connection
928+ onBeforeComplete : (context) {
929+ // Don't allow self-connections
930+ if (context.sourceNode.id == context.targetNode.id) {
931+ return ConnectionValidationResult.invalid(
932+ reason: 'Cannot connect node to itself',
933+ );
934+ }
933935
934- // Check for circular dependencies
935- if (_wouldCreateCycle(context)) {
936- return ConnectionValidationResult.invalid(
937- reason: 'Would create circular dependency',
938- );
939- }
936+ // Check for circular dependencies
937+ if (_wouldCreateCycle(context)) {
938+ return ConnectionValidationResult.invalid(
939+ reason: 'Would create circular dependency',
940+ );
941+ }
940942
941- // Check port compatibility
942- if (!_arePortsCompatible(context.sourcePort, context.targetPort)) {
943- return ConnectionValidationResult.invalid(
944- reason: 'Incompatible port types',
945- );
946- }
943+ // Check port compatibility
944+ if (!_arePortsCompatible(context.sourcePort, context.targetPort)) {
945+ return ConnectionValidationResult.invalid(
946+ reason: 'Incompatible port types',
947+ );
948+ }
947949
948- return ConnectionValidationResult.valid();
949- },
950+ return ConnectionValidationResult.valid();
951+ },
952+ ),
953+ ),
950954)
951955```
952956
@@ -1555,94 +1559,122 @@ controller.addAnnotation(annotation);
15551559
15561560## Interactive Features
15571561
1558- ### Editor Callbacks
1562+ ### Event System
1563+
1564+ Vyuh Node Flow uses a structured event system organized into logical groups for better discoverability and maintainability.
15591565
15601566<details >
1561- <summary ><strong >All Available Callbacks </strong ></summary >
1567+ <summary ><strong >Complete Event System Example </strong ></summary >
15621568
15631569``` dart
15641570NodeFlowEditor<MyData>(
15651571 controller: controller,
15661572 theme: theme,
15671573 nodeBuilder: _buildNode,
15681574
1569- // Node events
1570- onNodeSelected: (node) {
1571- print('Node selected: ${node?.id}');
1572- },
1573- onNodeTap: (node) {
1574- print('Node tapped: ${node.id}');
1575- },
1576- onNodeDoubleTap: (node) {
1577- print('Node double-tapped: ${node.id}');
1578- _showNodeEditor(node);
1579- },
1580- onNodeCreated: (node) {
1581- print('Node created: ${node.id}');
1582- },
1583- onNodeDeleted: (node) {
1584- print('Node deleted: ${node.id}');
1585- },
1575+ events: NodeFlowEvents<MyData>(
1576+ // Node-related events
1577+ node: NodeEvents<MyData>(
1578+ onCreated: (node) => print('Node created: ${node.id}'),
1579+ onDeleted: (node) => print('Node deleted: ${node.id}'),
1580+ onSelected: (node) => print('Node selected: ${node?.id}'),
1581+ onTap: (node) => print('Node tapped: ${node.id}'),
1582+ onDoubleTap: (node) => _showNodeEditor(node),
1583+ onDragStart: (node) => print('Drag started: ${node.id}'),
1584+ onDrag: (node) => print('Dragging: ${node.id}'),
1585+ onDragStop: (node) => print('Drag stopped: ${node.id}'),
1586+ onMouseEnter: (node) => print('Mouse entered: ${node.id}'),
1587+ onMouseLeave: (node) => print('Mouse left: ${node.id}'),
1588+ onContextMenu: (node, position) => _showNodeContextMenu(node, position),
1589+ ),
15861590
1587- // Connection events
1588- onConnectionSelected: (connection) {
1589- print('Connection selected: ${connection?.id}');
1590- },
1591- onConnectionTap: (connection) {
1592- print('Connection tapped: ${connection.id}');
1593- },
1594- onConnectionDoubleTap: (connection) {
1595- print('Connection double-tapped: ${connection.id}');
1596- },
1597- onConnectionCreated: (connection) {
1598- print('Connection created: ${connection.id}');
1599- _notifyConnectionChange();
1600- },
1601- onConnectionDeleted: (connection) {
1602- print('Connection deleted: ${connection.id}');
1603- },
1591+ // Connection-related events
1592+ connection: ConnectionEvents<MyData>(
1593+ onCreated: (connection) {
1594+ print('Connection created: ${connection.id}');
1595+ _notifyConnectionChange();
1596+ },
1597+ onDeleted: (connection) => print('Connection deleted: ${connection.id}'),
1598+ onSelected: (connection) => print('Connection selected: ${connection?.id}'),
1599+ onTap: (connection) => print('Connection tapped: ${connection.id}'),
1600+ onDoubleTap: (connection) => _editConnection(connection),
1601+ onMouseEnter: (connection) => print('Mouse entered connection'),
1602+ onMouseLeave: (connection) => print('Mouse left connection'),
1603+ onContextMenu: (connection, position) => _showConnectionMenu(connection, position),
1604+
1605+ // Connection lifecycle events
1606+ onConnectStart: (nodeId, portId, isOutput) {
1607+ print('Started connecting from port $portId');
1608+ },
1609+ onConnectEnd: (success) {
1610+ print(success ? 'Connection completed' : 'Connection cancelled');
1611+ },
16041612
1605- // Connection validation callbacks
1606- onBeforeStartConnection: (context) {
1607- // Validate before starting a connection from a port
1608- if (context.existingConnections.isNotEmpty &&
1609- !context.sourcePort.multiConnections) {
1610- return ConnectionValidationResult(
1611- allowed: false,
1612- message: 'Port already has a connection',
1613- );
1614- }
1615- return ConnectionValidationResult.valid();
1616- },
1617- onBeforeCompleteConnection: (context) {
1618- // Validate before completing a connection to a target port
1619- if (_wouldCreateCycle(context.sourceNode, context.targetNode)) {
1620- return ConnectionValidationResult(
1621- allowed: false,
1622- message: 'This would create a cycle',
1623- );
1624- }
1625- return ConnectionValidationResult.valid();
1626- },
1613+ // Connection validation
1614+ onBeforeStart: (context) {
1615+ if (context.existingConnections.isNotEmpty &&
1616+ !context.sourcePort.multiConnections) {
1617+ return ConnectionValidationResult.invalid(
1618+ reason: 'Port already has a connection',
1619+ );
1620+ }
1621+ return ConnectionValidationResult.valid();
1622+ },
1623+ onBeforeComplete: (context) {
1624+ if (_wouldCreateCycle(context.sourceNode, context.targetNode)) {
1625+ return ConnectionValidationResult.invalid(
1626+ reason: 'This would create a cycle',
1627+ );
1628+ }
1629+ return ConnectionValidationResult.valid();
1630+ },
1631+ ),
16271632
1628- // Annotation events
1629- onAnnotationSelected: (annotation) {
1630- print('Annotation selected: ${annotation?.id}');
1631- },
1632- onAnnotationTap: (annotation) {
1633- print('Annotation tapped: ${annotation.id}');
1634- },
1635- onAnnotationCreated: (annotation) {
1636- print('Annotation created: ${annotation.id}');
1637- },
1638- onAnnotationDeleted: (annotation) {
1639- print('Annotation deleted: ${annotation.id}');
1640- },
1633+ // Viewport events (pan, zoom, canvas interaction)
1634+ viewport: ViewportEvents(
1635+ onMoveStart: (viewport) => print('Viewport move started'),
1636+ onMove: (viewport) => print('Viewport: ${viewport.x}, ${viewport.y}'),
1637+ onMoveEnd: (viewport) => print('Viewport move ended'),
1638+ onCanvasTap: (position) => _handleCanvasTap(position),
1639+ onCanvasContextMenu: (position) => _showCanvasMenu(position),
1640+ ),
1641+
1642+ // Annotation events
1643+ annotation: AnnotationEvents(
1644+ onCreated: (annotation) => print('Annotation created'),
1645+ onDeleted: (annotation) => print('Annotation deleted'),
1646+ onSelected: (annotation) => print('Annotation selected'),
1647+ onTap: (annotation) => print('Annotation tapped'),
1648+ ),
1649+
1650+ // Selection change tracking
1651+ onSelectionChange: (state) {
1652+ print('Selection: ${state.nodes.length} nodes, '
1653+ '${state.connections.length} connections');
1654+ },
1655+
1656+ // Lifecycle events
1657+ onInit: () {
1658+ print('Editor initialized');
1659+ controller.fitToView(); // Auto-fit on init
1660+ },
1661+ onError: (error) {
1662+ print('Error: ${error.message}');
1663+ },
1664+ ),
16411665)
16421666```
16431667
16441668</details >
16451669
1670+ ** Event Categories:**
1671+
1672+ - ** ` NodeEvents ` ** - Node lifecycle, interaction, drag, and hover events
1673+ - ** ` ConnectionEvents ` ** - Connection lifecycle, validation, and interaction events
1674+ - ** ` ViewportEvents ` ** - Pan, zoom, and canvas interaction events
1675+ - ** ` AnnotationEvents ` ** - Annotation lifecycle and interaction events
1676+ - ** Top-level** - Selection change tracking, initialization, and error handling
1677+
16461678### Keyboard Shortcuts
16471679
16481680Built-in keyboard shortcuts are available:
@@ -2050,30 +2082,34 @@ class _WorkflowBuilderState extends State<WorkflowBuilder> {
20502082 theme: _createWorkflowTheme(),
20512083 nodeBuilder: _buildWorkflowNode,
20522084
2053- // Prevent invalid connections
2054- onBeforeCompleteConnection: (context) {
2055- // Don't allow loops
2056- if (context.sourceNode.id == context.targetNode.id) {
2057- return ConnectionValidationResult.invalid(
2058- reason: 'Cannot connect to self',
2059- );
2060- }
2061-
2062- // Check for cycles
2063- if (_wouldCreateCycle(context)) {
2064- return ConnectionValidationResult.invalid(
2065- reason: 'Would create circular dependency',
2066- );
2067- }
2068-
2069- return ConnectionValidationResult.valid();
2070- },
2071-
2072- onConnectionCreated: (connection) {
2073- ScaffoldMessenger.of(context).showSnackBar(
2074- const SnackBar(content: Text('Connection created')),
2075- );
2076- },
2085+ events: NodeFlowEvents<WorkflowNodeData>(
2086+ connection: ConnectionEvents<WorkflowNodeData>(
2087+ // Prevent invalid connections
2088+ onBeforeComplete: (context) {
2089+ // Don't allow loops
2090+ if (context.sourceNode.id == context.targetNode.id) {
2091+ return ConnectionValidationResult.invalid(
2092+ reason: 'Cannot connect to self',
2093+ );
2094+ }
2095+
2096+ // Check for cycles
2097+ if (_wouldCreateCycle(context)) {
2098+ return ConnectionValidationResult.invalid(
2099+ reason: 'Would create circular dependency',
2100+ );
2101+ }
2102+
2103+ return ConnectionValidationResult.valid();
2104+ },
2105+
2106+ onCreated: (connection) {
2107+ ScaffoldMessenger.of(context).showSnackBar(
2108+ const SnackBar(content: Text('Connection created')),
2109+ );
2110+ },
2111+ ),
2112+ ),
20772113 ),
20782114 );
20792115 }
@@ -2302,6 +2338,9 @@ class ProcessViewer extends StatelessWidget {
23022338| ` zoomBy(double delta) ` | Adjust zoom by delta |
23032339| ` zoomTo(double zoom) ` | Set specific zoom level |
23042340| ` fitToView() ` | Fit all nodes in view |
2341+ | ` centerViewport() ` | Center viewport on all nodes |
2342+ | ` getViewportCenter() ` | Get viewport center in graph coordinates |
2343+ | ` centerOn(Offset point) ` | Center viewport on specific point |
23052344| ` centerOnNode(String id) ` | Center viewport on node |
23062345| ` exportGraph() ` | Export graph to JSON |
23072346| ` loadGraph(NodeGraph) ` | Load graph from data |
0 commit comments