145 lines
3.8 KiB
Dart
145 lines
3.8 KiB
Dart
import 'dart:math' as math;
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
class ConnectionPaintData {
|
|
const ConnectionPaintData({
|
|
required this.from,
|
|
required this.to,
|
|
required this.color,
|
|
this.animated = false,
|
|
this.label,
|
|
});
|
|
|
|
final Offset from;
|
|
final Offset to;
|
|
final Color color;
|
|
final bool animated;
|
|
final String? label;
|
|
}
|
|
|
|
class ConnectionPainter extends CustomPainter {
|
|
const ConnectionPainter({
|
|
required this.connections,
|
|
this.animationPhase = 0,
|
|
required this.labelTextColor,
|
|
required this.labelBgColor,
|
|
required this.labelBorderColor,
|
|
});
|
|
|
|
final List<ConnectionPaintData> connections;
|
|
final double animationPhase;
|
|
final Color labelTextColor;
|
|
final Color labelBgColor;
|
|
final Color labelBorderColor;
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
for (final connection in connections) {
|
|
final paint = Paint()
|
|
..color = connection.color
|
|
..strokeWidth = 3
|
|
..style = PaintingStyle.stroke
|
|
..strokeCap = StrokeCap.round;
|
|
|
|
if (connection.animated) {
|
|
_drawAnimatedDashedLine(canvas, paint, connection.from, connection.to);
|
|
} else {
|
|
canvas.drawLine(connection.from, connection.to, paint);
|
|
}
|
|
|
|
if (connection.label != null && connection.label!.isNotEmpty) {
|
|
_drawLineLabel(canvas, connection.from, connection.to, connection.label!);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _drawAnimatedDashedLine(
|
|
Canvas canvas,
|
|
Paint paint,
|
|
Offset from,
|
|
Offset to,
|
|
) {
|
|
const dashLength = 12.0;
|
|
const gapLength = 8.0;
|
|
const cycle = dashLength + gapLength;
|
|
|
|
final dx = to.dx - from.dx;
|
|
final dy = to.dy - from.dy;
|
|
final distance = math.sqrt(dx * dx + dy * dy);
|
|
if (distance == 0) {
|
|
return;
|
|
}
|
|
|
|
final ux = dx / distance;
|
|
final uy = dy / distance;
|
|
final offsetShift = animationPhase * cycle;
|
|
var position = -offsetShift;
|
|
|
|
while (position < distance) {
|
|
final start = position.clamp(0.0, distance);
|
|
final end = (position + dashLength).clamp(0.0, distance);
|
|
if (end > start) {
|
|
final p1 = Offset(from.dx + ux * start, from.dy + uy * start);
|
|
final p2 = Offset(from.dx + ux * end, from.dy + uy * end);
|
|
canvas.drawLine(p1, p2, paint);
|
|
}
|
|
position += cycle;
|
|
}
|
|
}
|
|
|
|
void _drawLineLabel(Canvas canvas, Offset from, Offset to, String label) {
|
|
final textPainter = TextPainter(
|
|
text: TextSpan(
|
|
text: label,
|
|
style: TextStyle(
|
|
color: labelTextColor,
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
textDirection: TextDirection.ltr,
|
|
)..layout();
|
|
|
|
final mid = Offset((from.dx + to.dx) / 2, (from.dy + to.dy) / 2);
|
|
final dx = to.dx - from.dx;
|
|
final dy = to.dy - from.dy;
|
|
final length = math.sqrt(dx * dx + dy * dy);
|
|
final normal = length == 0 ? const Offset(0, -1) : Offset(-dy / length, dx / length);
|
|
final labelOffset = mid + normal * 10;
|
|
|
|
final rect = RRect.fromRectAndRadius(
|
|
Rect.fromLTWH(
|
|
labelOffset.dx - textPainter.width / 2 - 6,
|
|
labelOffset.dy - textPainter.height / 2 - 3,
|
|
textPainter.width + 12,
|
|
textPainter.height + 6,
|
|
),
|
|
const Radius.circular(6),
|
|
);
|
|
|
|
final backgroundPaint = Paint()..color = labelBgColor;
|
|
canvas.drawRRect(rect, backgroundPaint);
|
|
canvas.drawRRect(
|
|
rect,
|
|
Paint()
|
|
..color = labelBorderColor
|
|
..style = PaintingStyle.stroke,
|
|
);
|
|
|
|
textPainter.paint(
|
|
canvas,
|
|
Offset(
|
|
labelOffset.dx - textPainter.width / 2,
|
|
labelOffset.dy - textPainter.height / 2,
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant ConnectionPainter oldDelegate) {
|
|
return oldDelegate.connections != connections ||
|
|
oldDelegate.animationPhase != animationPhase;
|
|
}
|
|
}
|