Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/vyuh_node_flow/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.27.3+2

- **FIX**: wire default port hover events.

## 0.27.3+1

- **FIX**(ports): render a `PortType.both` port once instead of twice, which raised a duplicate `ValueKey` assertion. ([#36](https://github.com/vyuh-tech/vyuh_node_flow/pull/36))
Expand Down
11 changes: 11 additions & 0 deletions packages/vyuh_node_flow/lib/src/editor/layers/nodes_layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class NodesLayer<T> extends StatelessWidget {
this.onNodeContextMenu,
this.onNodeMouseEnter,
this.onNodeMouseLeave,
this.onPortHover,
this.onPortContextMenu,
this.portSnapDistance = 8.0,
this.thumbnailBuilder,
Expand All @@ -63,6 +64,7 @@ class NodesLayer<T> extends StatelessWidget {
onNodeContextMenu,
void Function(Node<T> node)? onNodeMouseEnter,
void Function(Node<T> node)? onNodeMouseLeave,
void Function(String nodeId, String portId, bool isHover)? onPortHover,
void Function(String nodeId, String portId, ScreenPosition screenPosition)?
onPortContextMenu,
double portSnapDistance = 8.0,
Expand All @@ -78,6 +80,7 @@ class NodesLayer<T> extends StatelessWidget {
onNodeContextMenu: onNodeContextMenu,
onNodeMouseEnter: onNodeMouseEnter,
onNodeMouseLeave: onNodeMouseLeave,
onPortHover: onPortHover,
onPortContextMenu: onPortContextMenu,
portSnapDistance: portSnapDistance,
);
Expand All @@ -98,6 +101,7 @@ class NodesLayer<T> extends StatelessWidget {
onNodeContextMenu,
void Function(Node<T> node)? onNodeMouseEnter,
void Function(Node<T> node)? onNodeMouseLeave,
void Function(String nodeId, String portId, bool isHover)? onPortHover,
void Function(String nodeId, String portId, ScreenPosition screenPosition)?
onPortContextMenu,
double portSnapDistance = 8.0,
Expand All @@ -113,6 +117,7 @@ class NodesLayer<T> extends StatelessWidget {
onNodeContextMenu: onNodeContextMenu,
onNodeMouseEnter: onNodeMouseEnter,
onNodeMouseLeave: onNodeMouseLeave,
onPortHover: onPortHover,
onPortContextMenu: onPortContextMenu,
portSnapDistance: portSnapDistance,
);
Expand All @@ -133,6 +138,7 @@ class NodesLayer<T> extends StatelessWidget {
onNodeContextMenu,
void Function(Node<T> node)? onNodeMouseEnter,
void Function(Node<T> node)? onNodeMouseLeave,
void Function(String nodeId, String portId, bool isHover)? onPortHover,
void Function(String nodeId, String portId, ScreenPosition screenPosition)?
onPortContextMenu,
double portSnapDistance = 8.0,
Expand All @@ -148,6 +154,7 @@ class NodesLayer<T> extends StatelessWidget {
onNodeContextMenu: onNodeContextMenu,
onNodeMouseEnter: onNodeMouseEnter,
onNodeMouseLeave: onNodeMouseLeave,
onPortHover: onPortHover,
onPortContextMenu: onPortContextMenu,
portSnapDistance: portSnapDistance,
);
Expand Down Expand Up @@ -186,6 +193,9 @@ class NodesLayer<T> extends StatelessWidget {
/// Callback invoked when mouse leaves a node.
final void Function(Node<T> node)? onNodeMouseLeave;

/// Callback invoked when a port hover state changes.
final void Function(String nodeId, String portId, bool isHover)? onPortHover;

/// Callback invoked when a port is right-clicked (context menu).
/// The [screenPosition] is in screen/global coordinates for menu positioning.
final void Function(
Expand Down Expand Up @@ -291,6 +301,7 @@ class NodesLayer<T> extends StatelessWidget {
onMouseLeave: onNodeMouseLeave != null
? () => onNodeMouseLeave!(node)
: null,
onPortHover: onPortHover,
onPortContextMenu: onPortContextMenu,
portSnapDistance: portSnapDistance,
child: NodeWidget<T>(
Expand Down
3 changes: 3 additions & 0 deletions packages/vyuh_node_flow/lib/src/editor/node_flow_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,7 @@ class _NodeFlowEditorState<T, C> extends State<NodeFlowEditor<T, C>>
onNodeContextMenu: _handleNodeContextMenu,
onNodeMouseEnter: _handleNodeMouseEnter,
onNodeMouseLeave: _handleNodeMouseLeave,
onPortHover: _handlePortHover,
onPortContextMenu: _handlePortContextMenu,
portSnapDistance: widget
.controller
Expand Down Expand Up @@ -706,6 +707,7 @@ class _NodeFlowEditorState<T, C> extends State<NodeFlowEditor<T, C>>
onNodeContextMenu: _handleNodeContextMenu,
onNodeMouseEnter: _handleNodeMouseEnter,
onNodeMouseLeave: _handleNodeMouseLeave,
onPortHover: _handlePortHover,
onPortContextMenu: _handlePortContextMenu,
portSnapDistance: widget
.controller
Expand Down Expand Up @@ -737,6 +739,7 @@ class _NodeFlowEditorState<T, C> extends State<NodeFlowEditor<T, C>>
onNodeContextMenu: _handleNodeContextMenu,
onNodeMouseEnter: _handleNodeMouseEnter,
onNodeMouseLeave: _handleNodeMouseLeave,
onPortHover: _handlePortHover,
onPortContextMenu: _handlePortContextMenu,
portSnapDistance: widget
.controller
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ extension _WidgetGestureHandlers<T, C> on _NodeFlowEditorState<T, C> {
// Port Gesture Handlers
// ============================================================

/// Handles port hover changes.
void _handlePortHover(String nodeId, String portId, bool isHover) {
final node = widget.controller.getNode(nodeId);
if (node == null) return;

final port = node.ports.where((p) => p.id == portId).firstOrNull;
if (port == null) return;

if (isHover) {
widget.controller.events.port?.onMouseEnter?.call(node, port);
} else {
widget.controller.events.port?.onMouseLeave?.call(node, port);
}
}

/// Handles port context menu (right-click).
///
/// The [screenPosition] is in screen/global coordinates, passed directly
Expand Down
2 changes: 1 addition & 1 deletion packages/vyuh_node_flow/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: vyuh_node_flow
description: A flexible, high-performance node-based flow editor for Flutter. Build visual programming interfaces, workflow editors, diagrams, and data pipelines.
version: 0.27.3+1
version: 0.27.3+2

homepage: https://flow.vyuh.tech
repository: https://github.com/vyuh-tech/vyuh_node_flow
Expand Down
62 changes: 62 additions & 0 deletions packages/vyuh_node_flow/test/widget/node_flow_editor_test.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@Tags(['widget'])
library;

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:vyuh_node_flow/vyuh_node_flow.dart';
Expand Down Expand Up @@ -339,6 +340,67 @@ void main() {

expect(initCalled, isTrue);
});

testWidgets('default ports fire mouse enter and leave callbacks', (
tester,
) async {
final node = createTestNodeWithOutputPort(
id: 'node-1',
portId: 'output-1',
);
controller.addNode(node);

Node<String>? enteredNode;
Port? enteredPort;
Node<String>? leftNode;
Port? leftPort;

await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: SizedBox(
width: 800,
height: 600,
child: NodeFlowEditor<String, dynamic>(
controller: controller,
nodeBuilder: (context, node) => const SizedBox.expand(),
theme: NodeFlowTheme.light,
events: NodeFlowEvents<String, dynamic>(
port: PortEvents<String>(
onMouseEnter: (node, port) {
enteredNode = node;
enteredPort = port;
},
onMouseLeave: (node, port) {
leftNode = node;
leftPort = port;
},
),
),
),
),
),
),
);
await tester.pumpAndSettle();

final portFinder = find.byType(PortWidget<String>);
expect(portFinder, findsOneWidget);

final mouse = await tester.createGesture(kind: PointerDeviceKind.mouse);
await mouse.addPointer(location: const Offset(700, 500));
await mouse.moveTo(tester.getCenter(portFinder));
await tester.pump();

expect(enteredNode, same(node));
expect(enteredPort, same(node.ports.single));

await mouse.moveTo(const Offset(700, 500));
await tester.pump();

expect(leftNode, same(node));
expect(leftPort, same(node.ports.single));
});
});

group('NodeFlowEditor - Layout and Sizing', () {
Expand Down
Loading