// EdgePainter — draws every connection on the canvas as a // cubic-bezier curve. Pure render layer: receives a flat list // of port-to-port positions (already resolved against node // positions) and paints them in one go. // // Connections are painted in a single CustomPaint so we don't // pay the widget-tree cost of one painter per edge. Hit // testing is delegated to port hit boxes drawn over the // canvas; the painter itself is wrapped in IgnorePointer in // the canvas so nothing here eats taps meant for nodes. import 'package:flutter/material.dart'; class EdgeSegment { final Offset from; final Offset to; final EdgeAccent accent; const EdgeSegment({ required this.from, required this.to, this.accent = EdgeAccent.normal, }); } enum EdgeAccent { normal, highlight, draftDrag } class EdgePainter extends CustomPainter { final List segments; final Color baseColor; final Color highlightColor; final Color draftColor; EdgePainter({ required this.segments, required this.baseColor, required this.highlightColor, required this.draftColor, }); @override void paint(Canvas canvas, Size size) { for (final seg in segments) { final color = switch (seg.accent) { EdgeAccent.normal => baseColor, EdgeAccent.highlight => highlightColor, EdgeAccent.draftDrag => draftColor, }; final paint = Paint() ..color = color ..style = PaintingStyle.stroke ..strokeWidth = seg.accent == EdgeAccent.highlight ? 2.5 : 1.8 ..strokeCap = StrokeCap.round; final path = _bezier(seg.from, seg.to); canvas.drawPath(path, paint); // End-cap arrow: small filled triangle pointing at the // target port so the reader can tell direction at a // glance without inspecting the geometry. _drawArrowHead(canvas, seg.to, seg.from, color); } } Path _bezier(Offset from, Offset to) { // Cubic with horizontal handles — typical "flow diagram" // smooth curve. The handle length scales with the // horizontal distance so steep verticals still look // graceful. final dx = (to.dx - from.dx).abs(); final handleLen = (dx / 2).clamp(40.0, 220.0); return Path() ..moveTo(from.dx, from.dy) ..cubicTo( from.dx + handleLen, from.dy, to.dx - handleLen, to.dy, to.dx, to.dy, ); } void _drawArrowHead(Canvas canvas, Offset tip, Offset from, Color color) { // Approximate the incoming direction by sampling a // little before the tip. Good enough since the curve is // nearly horizontal near the end. final dir = tip.dx - from.dx; final orient = dir >= 0 ? 1.0 : -1.0; const size = 6.0; final p1 = tip; final p2 = Offset(tip.dx - size * orient, tip.dy - size * 0.7); final p3 = Offset(tip.dx - size * orient, tip.dy + size * 0.7); final path = Path() ..moveTo(p1.dx, p1.dy) ..lineTo(p2.dx, p2.dy) ..lineTo(p3.dx, p3.dy) ..close(); canvas.drawPath(path, Paint()..color = color); } @override bool shouldRepaint(EdgePainter old) => old.segments != segments || old.baseColor != baseColor || old.highlightColor != highlightColor || old.draftColor != draftColor; }