feat(editor): highlight drop targets during connection drag
While the operator is dragging from an output port, every
input port now lights up with a tint so they can see at a
glance where the connection can land. The closest port
within the snap distance gets a larger halo + brighter
border so the operator knows which port will accept the
drop if they release now.
Visual states:
- input port, no drag: muted background
- input port, drag in flight: primary @ 35% alpha
- input port, closest target: primary fill + 2.5 px border
+ soft halo glow
The closest-target computation runs once per port per
frame during a drag — O(n) over visible input ports.
Negligible at flow sizes operators care about; revisit if
flows exceed a few dozen steps.
Signed-off-by: flemming-it <sf@flemming.it>
This commit is contained in:
parent
307eaccc3e
commit
fb8730903c
1 changed files with 66 additions and 4 deletions
|
|
@ -483,7 +483,15 @@ class _FlowCanvasState extends State<FlowCanvas> {
|
||||||
void Function(Offset cursor)? onDropTarget,
|
void Function(Offset cursor)? onDropTarget,
|
||||||
}) {
|
}) {
|
||||||
final theme = Theme.of(context);
|
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(
|
return Positioned(
|
||||||
left: center.dx - size / 2,
|
left: center.dx - size / 2,
|
||||||
top: center.dy - size / 2,
|
top: center.dy - size / 2,
|
||||||
|
|
@ -517,10 +525,27 @@ class _FlowCanvasState extends State<FlowCanvas> {
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: isSource
|
color: isClosest
|
||||||
? theme.colorScheme.primary
|
? theme.colorScheme.primary
|
||||||
: theme.colorScheme.surfaceContainerHighest,
|
: isSource
|
||||||
border: Border.all(color: theme.colorScheme.primary, width: 1.5),
|
? 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<FlowCanvas> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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(
|
void Function(Offset) _dropTargetFor(
|
||||||
String targetId,
|
String targetId,
|
||||||
String targetField,
|
String targetField,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue