Custom drawing implementation
This commit is contained in:
176
lib/features/sheet_viewer/drawing/drawing_canvas.dart
Normal file
176
lib/features/sheet_viewer/drawing/drawing_canvas.dart
Normal file
@@ -0,0 +1,176 @@
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user