Skip to content

Commit 8488329

Browse files
committed
feat: updated readme about structured events
1 parent a081d03 commit 8488329

File tree

1 file changed

+168
-129
lines changed

1 file changed

+168
-129
lines changed

packages/vyuh_node_flow/README.md

Lines changed: 168 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -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
15641570
NodeFlowEditor<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

16481680
Built-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

Comments
 (0)