Files
sheetless/lib/features/sheet_viewer/drawing/drawing_canvas.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),
);
}
}