177 lines
4.8 KiB
Dart
177 lines
4.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import 'drawing_controller.dart';
|
|
import 'drawing_line.dart';
|
|
|
|
/// Custom painter that renders drawing lines on a canvas.
|
|
///
|
|
/// Converts normalized coordinates (0-1) to actual canvas coordinates
|
|
/// based on the provided canvas size.
|
|
class DrawingPainter extends CustomPainter {
|
|
final List<DrawingLine> lines;
|
|
final DrawingLine? currentLine;
|
|
final Size canvasSize;
|
|
|
|
DrawingPainter({
|
|
required this.lines,
|
|
required this.currentLine,
|
|
required this.canvasSize,
|
|
});
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
// Draw all completed lines
|
|
for (final line in lines) {
|
|
_drawLine(canvas, line);
|
|
}
|
|
|
|
// Draw the current line being drawn
|
|
if (currentLine != null) {
|
|
_drawLine(canvas, currentLine!);
|
|
}
|
|
}
|
|
|
|
void _drawLine(Canvas canvas, DrawingLine line) {
|
|
if (line.points.length < 2) return;
|
|
|
|
final paint = Paint()
|
|
..color = line.color
|
|
..strokeWidth = line.strokeWidth * canvasSize.width
|
|
..strokeCap = StrokeCap.round
|
|
..strokeJoin = StrokeJoin.round
|
|
..style = PaintingStyle.stroke
|
|
..isAntiAlias = true;
|
|
|
|
// Create path from normalized points
|
|
final path = Path();
|
|
final firstPoint = _toCanvasPoint(line.points.first);
|
|
path.moveTo(firstPoint.dx, firstPoint.dy);
|
|
|
|
// Use quadratic bezier curves for smooth lines
|
|
if (line.points.length == 2) {
|
|
final endPoint = _toCanvasPoint(line.points.last);
|
|
path.lineTo(endPoint.dx, endPoint.dy);
|
|
} else {
|
|
for (int i = 1; i < line.points.length - 1; i++) {
|
|
final p0 = _toCanvasPoint(line.points[i]);
|
|
final p1 = _toCanvasPoint(line.points[i + 1]);
|
|
final midPoint = Offset(
|
|
(p0.dx + p1.dx) / 2,
|
|
(p0.dy + p1.dy) / 2,
|
|
);
|
|
path.quadraticBezierTo(p0.dx, p0.dy, midPoint.dx, midPoint.dy);
|
|
}
|
|
// Draw to the last point
|
|
final lastPoint = _toCanvasPoint(line.points.last);
|
|
path.lineTo(lastPoint.dx, lastPoint.dy);
|
|
}
|
|
|
|
canvas.drawPath(path, paint);
|
|
}
|
|
|
|
/// Converts a normalized point (0-1) to canvas coordinates.
|
|
Offset _toCanvasPoint(Offset normalizedPoint) {
|
|
return Offset(
|
|
normalizedPoint.dx * canvasSize.width,
|
|
normalizedPoint.dy * canvasSize.height,
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant DrawingPainter oldDelegate) {
|
|
return lines != oldDelegate.lines ||
|
|
currentLine != oldDelegate.currentLine ||
|
|
canvasSize != oldDelegate.canvasSize;
|
|
}
|
|
}
|
|
|
|
/// A widget that displays drawing lines on a transparent canvas.
|
|
///
|
|
/// This widget only shows the drawings, it doesn't handle input.
|
|
/// Use [DrawingCanvas] or [DrawingBoard] for input handling.
|
|
class DrawingOverlay extends StatelessWidget {
|
|
final DrawingController controller;
|
|
final Size canvasSize;
|
|
|
|
const DrawingOverlay({
|
|
super.key,
|
|
required this.controller,
|
|
required this.canvasSize,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ListenableBuilder(
|
|
listenable: controller,
|
|
builder: (context, _) {
|
|
return CustomPaint(
|
|
size: canvasSize,
|
|
painter: DrawingPainter(
|
|
lines: controller.lines,
|
|
currentLine: controller.currentLine,
|
|
canvasSize: canvasSize,
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
/// A widget that handles drawing input and renders lines.
|
|
///
|
|
/// Converts touch/pointer events to normalized coordinates and
|
|
/// passes them to the [DrawingController].
|
|
class DrawingCanvas extends StatelessWidget {
|
|
final DrawingController controller;
|
|
final Size canvasSize;
|
|
final bool enabled;
|
|
|
|
const DrawingCanvas({
|
|
super.key,
|
|
required this.controller,
|
|
required this.canvasSize,
|
|
this.enabled = true,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Listener(
|
|
onPointerDown: enabled ? _onPointerDown : null,
|
|
onPointerMove: enabled ? _onPointerMove : null,
|
|
onPointerUp: enabled ? _onPointerUp : null,
|
|
onPointerCancel: enabled ? _onPointerCancel : null,
|
|
behavior: HitTestBehavior.opaque,
|
|
child: DrawingOverlay(
|
|
controller: controller,
|
|
canvasSize: canvasSize,
|
|
),
|
|
);
|
|
}
|
|
|
|
void _onPointerDown(PointerDownEvent event) {
|
|
final normalized = _toNormalized(event.localPosition);
|
|
controller.startLine(normalized);
|
|
}
|
|
|
|
void _onPointerMove(PointerMoveEvent event) {
|
|
final normalized = _toNormalized(event.localPosition);
|
|
controller.addPoint(normalized);
|
|
}
|
|
|
|
void _onPointerUp(PointerUpEvent event) {
|
|
controller.endLine();
|
|
}
|
|
|
|
void _onPointerCancel(PointerCancelEvent event) {
|
|
controller.endLine();
|
|
}
|
|
|
|
/// Converts canvas coordinates to normalized coordinates (0-1).
|
|
Offset _toNormalized(Offset canvasPoint) {
|
|
return Offset(
|
|
(canvasPoint.dx / canvasSize.width).clamp(0.0, 1.0),
|
|
(canvasPoint.dy / canvasSize.height).clamp(0.0, 1.0),
|
|
);
|
|
}
|
|
}
|