diff --git a/lib/src/widgets/flow_canvas.dart b/lib/src/widgets/flow_canvas.dart index ce00cd5..2292e47 100644 --- a/lib/src/widgets/flow_canvas.dart +++ b/lib/src/widgets/flow_canvas.dart @@ -483,7 +483,15 @@ class _FlowCanvasState extends State { void Function(Offset cursor)? onDropTarget, }) { final theme = Theme.of(context); - const size = 16.0; + // During a connection drag, highlight every input port + // so the operator sees the landing options; the closest + // one gets a larger halo so they know which port the + // drop will snap to if they release now. + final dragging = _draft != null; + final isInputDuringDrag = dragging && !isSource; + final isClosest = + isInputDuringDrag && _isClosestDropTarget(center); + final size = isClosest ? 22.0 : 16.0; return Positioned( left: center.dx - size / 2, top: center.dy - size / 2, @@ -517,10 +525,27 @@ class _FlowCanvasState extends State { child: Container( decoration: BoxDecoration( shape: BoxShape.circle, - color: isSource + color: isClosest ? theme.colorScheme.primary - : theme.colorScheme.surfaceContainerHighest, - border: Border.all(color: theme.colorScheme.primary, width: 1.5), + : isSource + ? theme.colorScheme.primary + : isInputDuringDrag + ? theme.colorScheme.primary + .withValues(alpha: 0.35) + : theme.colorScheme.surfaceContainerHighest, + border: Border.all( + color: theme.colorScheme.primary, + width: isClosest ? 2.5 : 1.5, + ), + boxShadow: isClosest + ? [ + BoxShadow( + color: theme.colorScheme.primary + .withValues(alpha: 0.5), + blurRadius: 10, + ), + ] + : null, ), ), ), @@ -528,6 +553,43 @@ class _FlowCanvasState extends State { ); } + /// True if this input port is the nearest valid drop + /// target to the current draft cursor (within snap + /// distance). Used to paint the highlight halo so the + /// operator sees which port will accept the connection. + bool _isClosestDropTarget(Offset portCenter) { + final draft = _draft; + if (draft == null) return false; + final graph = widget.controller.graph; + final layout = widget.controller.layout; + final outputsX = _outputsX(graph, layout); + const maxDist = 32.0; + double bestDist = double.infinity; + Offset? best; + for (final step in graph.steps) { + final keys = step.with_.keys.toList(); + for (var i = 0; i < keys.length; i++) { + final p = _inputPortPosition(step.id, i, layout, outputsX); + final d = (draft.cursor - p).distance; + if (d < bestDist && d <= maxDist) { + bestDist = d; + best = p; + } + } + } + final outs = graph.outputs.keys.toList(); + for (var i = 0; i < outs.length; i++) { + final p = _inputPortPosition('__outputs__', i, layout, outputsX); + final d = (draft.cursor - p).distance; + if (d < bestDist && d <= maxDist) { + bestDist = d; + best = p; + } + } + if (best == null) return false; + return (best - portCenter).distance < 0.5; + } + void Function(Offset) _dropTargetFor( String targetId, String targetField,