notes/lib/canvas/paint_controller.dart
lukas-heiligenbrunner 8e0cd05ded allow zooming on desktop when holding ctrl and scrolling
add prototype of selection mode, span up dashed rectangle
2022-12-02 00:25:37 +01:00

193 lines
5.6 KiB
Dart

import 'dart:math';
import 'dart:ui';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import '../savesystem/line_loader.dart';
import '../savesystem/note_file.dart';
import 'document_types.dart';
import 'my_painter.dart';
enum Pen { eraser, pen, highlighter, selector }
class PaintController extends ChangeNotifier {
final bool _allowDrawWithFinger = false;
final NoteFile file;
Offset? _currentPointerPosition;
Pen activePen = Pen.pen;
List<Stroke> strokes = [];
Offset? _selectionStart;
PaintController(this.file);
void changePen(Pen pen) {
activePen = pen;
notifyListeners();
}
/// return current position of pointer
/// null if nowhere hovering or painting
Offset? getPointerPosition() {
return _currentPointerPosition;
}
void pointDownEvent(Offset offset, PointerDownEvent e) async {
if (_allowedToDraw(e.kind, e.buttons)) {
// todo line drawn on edge where line left page
if (!a4Page.contains(offset)) return;
switch (activePen) {
case Pen.pen:
case Pen.highlighter:
int strokeid = strokes.isNotEmpty ? strokes.last.id + 1 : 0;
final color = activePen == Pen.pen
? const Color(0xFF444444)
: Colors.yellow.withOpacity(.3);
strokes.add(Stroke.fromPoints(
[Point(offset, _calcTiltedWidth(3.0, e.tilt))], strokeid, color));
file.addStroke(strokeid, color);
notifyListeners();
break;
case Pen.selector:
_selectionStart = offset;
break;
default:
break;
}
}
}
Rect? getSelection() {
if (_currentPointerPosition == null || _selectionStart == null) return null;
return Rect.fromPoints(_selectionStart!, _currentPointerPosition!);
}
void pointUpEvent(PointerUpEvent e) {
_currentPointerPosition = null;
notifyListeners();
switch (activePen) {
case Pen.pen:
case Pen.highlighter:
// pointerupevent doesn't deliver correct event button
if (_allowedToDraw(e.kind, 1)) {
final lastStroke = strokes.last;
if (lastStroke.points.length <= 1) {
// if the line consists only of one point (point) add endpoint as the same to allow drawing a line
lastStroke.points.add(lastStroke.points.last);
file.addPoint(lastStroke.id, lastStroke.points.last);
notifyListeners();
} else {
debugPrint('adding points to db');
file.addPoints(lastStroke.id, lastStroke.points);
}
}
break;
case Pen.selector:
_selectionStart = null;
// todo highlight current selection
break;
default:
break;
}
}
void pointMoveEvent(Offset offset, PointerMoveEvent event) {
_currentPointerPosition = offset;
if (!a4Page.contains(offset)) {
return;
}
if (_allowedToDraw(event.kind, event.buttons)) {
switch (activePen) {
case Pen.eraser:
// todo dynamic eraser size
final eraserrect = Rect.fromCircle(center: offset, radius: 2.0);
for (final stroke in strokes) {
// check if delete action was within bounding rect of stroke
if (stroke.getBoundingRect().overlaps(eraserrect)) {
// check if eraser hit an point within its range
for (final pt in stroke.points) {
if (eraserrect.contains(pt.point)) {
file.removeStroke(stroke.id);
strokes.remove(stroke);
notifyListeners();
return;
}
}
}
}
break;
case Pen.pen:
final pts = strokes.last.points;
// avoid duplicates
if (pts.last.point == offset) return;
double newWidth = _calcTiltedWidth(4.0, event.tilt);
if (pts.length > 1) {
newWidth = _calcAngleDependentWidth(pts.last,
pts[pts.length - (pts.length > 5 ? 5 : pts.length)], newWidth);
}
strokes.last.addPoint(Point(offset, newWidth));
break;
case Pen.highlighter:
final pts = strokes.last.points;
// avoid duplicates
if (pts.last.point == offset) return;
strokes.last.addPoint(Point(offset, 15.0));
break;
case Pen.selector:
// TODO: Handle this case.
break;
}
notifyListeners();
}
}
Future<void> loadStrokesFromFile() async {
strokes = await file.loadStrokes();
debugPrint('finished loading strokes from file');
notifyListeners();
}
/// check if pointer event is allowed to draw points
bool _allowedToDraw(PointerDeviceKind kind, int button) {
return (_allowDrawWithFinger && kind == PointerDeviceKind.touch) ||
kind == PointerDeviceKind.stylus ||
(kind == PointerDeviceKind.mouse && button == kPrimaryMouseButton);
}
double _calcAngleDependentWidth(Point pt1, Point pt2, double basetickness) {
final delta = pt2.point - pt1.point;
final normalizedDelta =
delta / sqrt(delta.dx * delta.dx + delta.dy * delta.dy);
double alpha = asin(normalizedDelta.dy);
// range [-pi,pi]
alpha += (3 * pi / 4);
// range [0,inf]
alpha = alpha % (2 * pi);
// range [0,2pi]
alpha -= pi;
// range [-pi,pi]
alpha = alpha.abs();
// range [0,pi]
alpha /= pi;
// range [0,1]
alpha += .5;
// range [.5,1.5]
return basetickness * alpha;
}
double _calcTiltedWidth(double baseWidth, double tilt) {
if (tilt == .0) return baseWidth;
return baseWidth * tilt;
}
}