Files
trombon_ip_browser/lib/widgets/connection_painter.dart
2026-05-10 02:54:27 +03:00

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;
}
}