outsource line drawing in paint controller
This commit is contained in:
		@@ -1,17 +1,15 @@
 | 
				
			|||||||
import 'dart:math';
 | 
					import 'dart:math';
 | 
				
			||||||
import 'dart:ui';
 | 
					import 'dart:ui';
 | 
				
			||||||
import 'package:adwaita_icons/adwaita_icons.dart';
 | 
					 | 
				
			||||||
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
 | 
					import 'package:fluentui_system_icons/fluentui_system_icons.dart';
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:iconify_flutter/iconify_flutter.dart';
 | 
					import 'my_painter.dart';
 | 
				
			||||||
import 'package:iconify_flutter/icons/emojione_monotone.dart';
 | 
					import 'paint_controller.dart';
 | 
				
			||||||
import 'package:iconify_flutter/icons/jam.dart';
 | 
					import 'screen_document_mapping.dart';
 | 
				
			||||||
import 'package:notes/canvas/my_painter.dart';
 | 
					 | 
				
			||||||
import 'package:notes/canvas/screen_document_mapping.dart';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import '../icon_material_button.dart';
 | 
					import '../icon_material_button.dart';
 | 
				
			||||||
import 'document_types.dart';
 | 
					import '../tool_bar.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Handles input events and draws canvas element
 | 
				
			||||||
class DrawingPage extends StatefulWidget {
 | 
					class DrawingPage extends StatefulWidget {
 | 
				
			||||||
  const DrawingPage({Key? key}) : super(key: key);
 | 
					  const DrawingPage({Key? key}) : super(key: key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -20,15 +18,11 @@ class DrawingPage extends StatefulWidget {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class _DrawingPageState extends State<DrawingPage> {
 | 
					class _DrawingPageState extends State<DrawingPage> {
 | 
				
			||||||
  List<Stroke> _strokes = [];
 | 
					 | 
				
			||||||
  bool allowDrawWithFinger = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  double zoom = .75;
 | 
					  double zoom = .75;
 | 
				
			||||||
  double basezoom = 1.0;
 | 
					  double basezoom = 1.0;
 | 
				
			||||||
  Offset offset = const Offset(.0, .0);
 | 
					  Offset offset = const Offset(.0, .0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // todo better pen system
 | 
					  PaintController controller = PaintController();
 | 
				
			||||||
  bool eraseractive = false;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  void initState() {
 | 
					  void initState() {
 | 
				
			||||||
@@ -37,7 +31,7 @@ class _DrawingPageState extends State<DrawingPage> {
 | 
				
			|||||||
    // todo might be weird behaviour if used with short side
 | 
					    // todo might be weird behaviour if used with short side
 | 
				
			||||||
    final screenWidth =
 | 
					    final screenWidth =
 | 
				
			||||||
        (window.physicalSize.longestSide / window.devicePixelRatio);
 | 
					        (window.physicalSize.longestSide / window.devicePixelRatio);
 | 
				
			||||||
    _calcNewPageOffset(const Offset(.0, .0), screenWidth);
 | 
					    _calcNewPageOffset(const Offset(.0, .0), screenWidth - 45);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
@@ -47,68 +41,39 @@ class _DrawingPageState extends State<DrawingPage> {
 | 
				
			|||||||
        IconMaterialButton(
 | 
					        IconMaterialButton(
 | 
				
			||||||
          icon: const Icon(FluentIcons.book_open_48_filled),
 | 
					          icon: const Icon(FluentIcons.book_open_48_filled),
 | 
				
			||||||
          color: const Color.fromRGBO(255, 255, 255, .85),
 | 
					          color: const Color.fromRGBO(255, 255, 255, .85),
 | 
				
			||||||
          onPressed: () {},
 | 
					          onPressed: () {
 | 
				
			||||||
 | 
					            // todo implement
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        IconMaterialButton(
 | 
					        IconMaterialButton(
 | 
				
			||||||
          icon: const Icon(FluentIcons.document_one_page_24_regular),
 | 
					          icon: const Icon(FluentIcons.document_one_page_24_regular),
 | 
				
			||||||
          color: const Color.fromRGBO(255, 255, 255, .85),
 | 
					          color: const Color.fromRGBO(255, 255, 255, .85),
 | 
				
			||||||
          onPressed: () {},
 | 
					          onPressed: () {
 | 
				
			||||||
 | 
					            // todo implement
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        IconMaterialButton(
 | 
					        IconMaterialButton(
 | 
				
			||||||
          icon: const Icon(Icons.attachment_outlined),
 | 
					          icon: const Icon(Icons.attachment_outlined),
 | 
				
			||||||
          color: const Color.fromRGBO(255, 255, 255, .85),
 | 
					          color: const Color.fromRGBO(255, 255, 255, .85),
 | 
				
			||||||
          onPressed: () {},
 | 
					          onPressed: () {
 | 
				
			||||||
 | 
					            // todo implement
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
          rotation: -pi / 4,
 | 
					          rotation: -pi / 4,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        IconMaterialButton(
 | 
					        IconMaterialButton(
 | 
				
			||||||
          icon: const Icon(Icons.more_vert),
 | 
					          icon: const Icon(Icons.more_vert),
 | 
				
			||||||
          color: const Color.fromRGBO(255, 255, 255, .85),
 | 
					          color: const Color.fromRGBO(255, 255, 255, .85),
 | 
				
			||||||
          onPressed: () {},
 | 
					          onPressed: () {
 | 
				
			||||||
 | 
					            // todo implement
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ]),
 | 
					      ]),
 | 
				
			||||||
      body: Row(
 | 
					      body: Row(
 | 
				
			||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
          Container(
 | 
					          ToolBar(
 | 
				
			||||||
            color: const Color(0xff3f3f3f),
 | 
					            onPenChange: (pen) {
 | 
				
			||||||
            width: 45,
 | 
					              controller.changePen(pen);
 | 
				
			||||||
            child: Column(
 | 
					            },
 | 
				
			||||||
              children: [
 | 
					 | 
				
			||||||
                const SizedBox(
 | 
					 | 
				
			||||||
                  height: 10,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                IconMaterialButton(
 | 
					 | 
				
			||||||
                  icon: const Iconify(EmojioneMonotone.fountain_pen, color: Color.fromRGBO(255, 255, 255, .85),),
 | 
					 | 
				
			||||||
                  color: const Color.fromRGBO(255, 255, 255, .85),
 | 
					 | 
				
			||||||
                  onPressed: () => setState(() => eraseractive = false),
 | 
					 | 
				
			||||||
                  selected: !eraseractive,
 | 
					 | 
				
			||||||
                  iconSize: 24,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                IconMaterialButton(
 | 
					 | 
				
			||||||
                  icon: const Iconify(Jam.highlighter, color: Color.fromRGBO(255, 255, 255, .85),),
 | 
					 | 
				
			||||||
                  color: const Color.fromRGBO(255, 255, 255, .85),
 | 
					 | 
				
			||||||
                  onPressed: () => setState(() => eraseractive = false),
 | 
					 | 
				
			||||||
                  selected: false,
 | 
					 | 
				
			||||||
                  iconSize: 24,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                IconMaterialButton(
 | 
					 | 
				
			||||||
                  icon: Transform.translate(
 | 
					 | 
				
			||||||
                    offset: const Offset(-2.0, .0),
 | 
					 | 
				
			||||||
                    child: const AdwaitaIcon(AdwaitaIcons.eraser2),
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                  color: const Color.fromRGBO(255, 255, 255, .85),
 | 
					 | 
				
			||||||
                  onPressed: () => setState(() => eraseractive = true),
 | 
					 | 
				
			||||||
                  iconSize: 24,
 | 
					 | 
				
			||||||
                  selected: eraseractive,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                IconMaterialButton(
 | 
					 | 
				
			||||||
                  icon: const Icon(FluentIcons.select_object_24_regular),
 | 
					 | 
				
			||||||
                  color: const Color.fromRGBO(255, 255, 255, .85),
 | 
					 | 
				
			||||||
                  onPressed: () => setState(() => eraseractive = false),
 | 
					 | 
				
			||||||
                  selected: false,
 | 
					 | 
				
			||||||
                  iconSize: 24,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
              ],
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
          Expanded(child: RepaintBoundary(child: _buildCanvas())),
 | 
					          Expanded(child: RepaintBoundary(child: _buildCanvas())),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
@@ -116,28 +81,6 @@ class _DrawingPageState extends State<DrawingPage> {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  double _calcTiltedWidth(double baseWidth, double tilt) {
 | 
					 | 
				
			||||||
    if (tilt == .0) return baseWidth;
 | 
					 | 
				
			||||||
    return baseWidth * tilt;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  double _calcAngleDependentWidth(Point pt1, Point pt2, double basetickness) {
 | 
					 | 
				
			||||||
    double dx = pt2.point.dx - pt1.point.dx;
 | 
					 | 
				
			||||||
    double dy = pt2.point.dy - pt1.point.dy;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // todo those deltas to small to get an accurate direction!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    double alpha = atan(dx / dy);
 | 
					 | 
				
			||||||
    // alpha has range from 0 - 2pi
 | 
					 | 
				
			||||||
    // we want 0.5 -1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    alpha /= (2 * pi * 2);
 | 
					 | 
				
			||||||
    alpha += .5;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    double thickness = basetickness * alpha;
 | 
					 | 
				
			||||||
    return thickness;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // calculate new page offset from mousepointer delta
 | 
					  // calculate new page offset from mousepointer delta
 | 
				
			||||||
  void _calcNewPageOffset(Offset delta, double canvasWidth) {
 | 
					  void _calcNewPageOffset(Offset delta, double canvasWidth) {
 | 
				
			||||||
    if (zoom > 1.0) {
 | 
					    if (zoom > 1.0) {
 | 
				
			||||||
@@ -175,96 +118,28 @@ class _DrawingPageState extends State<DrawingPage> {
 | 
				
			|||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // todo outsource this eraser pen
 | 
					    controller.pointMoveEvent(pos, event.kind, event.tilt);
 | 
				
			||||||
    if (eraseractive) {
 | 
					 | 
				
			||||||
      // todo dynamic eraser size
 | 
					 | 
				
			||||||
      final eraserrect = Rect.fromCircle(center: pos, radius: 3);
 | 
					 | 
				
			||||||
      for (final stroke in _strokes) {
 | 
					 | 
				
			||||||
        // check if delete action was within bounding rect of stroke
 | 
					 | 
				
			||||||
        if (stroke.getBoundingRect().contains(pos)) {
 | 
					 | 
				
			||||||
          // check if eraser hit an point within its range
 | 
					 | 
				
			||||||
          for (final pt in stroke.points) {
 | 
					 | 
				
			||||||
            if (eraserrect.contains(pt.point)) {
 | 
					 | 
				
			||||||
              setState(() {
 | 
					 | 
				
			||||||
                _strokes = List.from(_strokes)..remove(stroke);
 | 
					 | 
				
			||||||
              });
 | 
					 | 
				
			||||||
              return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (allowDrawWithFinger || event.kind != PointerDeviceKind.touch) {
 | 
					    if (event.kind == PointerDeviceKind.touch) {
 | 
				
			||||||
      final pts = _strokes.last.points;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (pts.last.point == pos) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      double newWidth = _calcTiltedWidth(5.0, event.tilt);
 | 
					 | 
				
			||||||
      if (_strokes.last.points.length > 1) {
 | 
					 | 
				
			||||||
        newWidth =
 | 
					 | 
				
			||||||
            _calcAngleDependentWidth(pts.last, pts[pts.length - 2], newWidth);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      setState(() {
 | 
					 | 
				
			||||||
        _strokes = List.from(_strokes, growable: false)
 | 
					 | 
				
			||||||
          ..last.addPoint(Point(pos, newWidth));
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      _calcNewPageOffset(event.delta, size.width);
 | 
					      _calcNewPageOffset(event.delta, size.width);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Widget _buildCanvas() {
 | 
					  Widget _buildCanvas() {
 | 
				
			||||||
    final size = MediaQuery.of(context).size;
 | 
					    final size = MediaQuery.of(context).size;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    final canvasSize = Size(size.width - 45, size.height);
 | 
					    final canvasSize = Size(size.width - 45, size.height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Listener(
 | 
					    return Listener(
 | 
				
			||||||
      behavior: HitTestBehavior.opaque,
 | 
					      behavior: HitTestBehavior.opaque,
 | 
				
			||||||
      onPointerMove: (e) => _onPointerMove(e, size),
 | 
					      onPointerMove: (e) => _onPointerMove(e, canvasSize),
 | 
				
			||||||
      onPointerSignal: (event) {
 | 
					 | 
				
			||||||
        print('Button: ${event.buttons}');
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      onPointerDown: (event) {
 | 
					      onPointerDown: (event) {
 | 
				
			||||||
        print('Button: ${event.buttons}');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (allowDrawWithFinger || event.kind != PointerDeviceKind.touch) {
 | 
					 | 
				
			||||||
        Offset pos = event.localPosition;
 | 
					        Offset pos = event.localPosition;
 | 
				
			||||||
          final scale = calcPageDependentScale(zoom, a4Page, size);
 | 
					        final scale = calcPageDependentScale(zoom, a4Page, canvasSize);
 | 
				
			||||||
        pos = translateScreenToDocumentPoint(pos, scale, offset);
 | 
					        pos = translateScreenToDocumentPoint(pos, scale, offset);
 | 
				
			||||||
 | 
					        controller.pointDownEvent(pos, event.kind, event.tilt);
 | 
				
			||||||
          // todo line drawn on edge where line left page
 | 
					 | 
				
			||||||
          if (!a4Page.contains(pos)) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (eraseractive) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          setState(() {
 | 
					 | 
				
			||||||
            _strokes = List.from(_strokes)
 | 
					 | 
				
			||||||
              ..add(Stroke.fromPoints(
 | 
					 | 
				
			||||||
                  [Point(pos, _calcTiltedWidth(3.0, event.tilt))]));
 | 
					 | 
				
			||||||
          });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      onPointerUp: (event) {
 | 
					      onPointerUp: (event) {
 | 
				
			||||||
        if (eraseractive) return;
 | 
					        controller.pointUpEvent(event.kind);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (allowDrawWithFinger || event.kind != PointerDeviceKind.touch) {
 | 
					 | 
				
			||||||
          if (_strokes.last.points.length <= 1) {
 | 
					 | 
				
			||||||
            // if the line consists only of one point (point) add endpoint as the same to allow drawing a line
 | 
					 | 
				
			||||||
            // todo maybe solve this in custompainter in future
 | 
					 | 
				
			||||||
            setState(() {
 | 
					 | 
				
			||||||
              _strokes = List.from(_strokes, growable: false)
 | 
					 | 
				
			||||||
                ..last.points.add(_strokes.last.points.last);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
          } else {
 | 
					 | 
				
			||||||
            setState(() {});
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          print(_strokes.length);
 | 
					 | 
				
			||||||
          print(_strokes.last.points.length);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      child: GestureDetector(
 | 
					      child: GestureDetector(
 | 
				
			||||||
        onScaleUpdate: (details) {
 | 
					        onScaleUpdate: (details) {
 | 
				
			||||||
@@ -277,18 +152,12 @@ class _DrawingPageState extends State<DrawingPage> {
 | 
				
			|||||||
        onScaleEnd: (details) {
 | 
					        onScaleEnd: (details) {
 | 
				
			||||||
          basezoom = zoom;
 | 
					          basezoom = zoom;
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        onSecondaryTap: () {
 | 
					 | 
				
			||||||
          print('secctab');
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        onTertiaryTapDown: (details) {
 | 
					 | 
				
			||||||
          print('tertiary button');
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        child: CustomPaint(
 | 
					        child: CustomPaint(
 | 
				
			||||||
          painter: MyPainter(
 | 
					          painter: MyPainter(
 | 
				
			||||||
              strokes: _strokes,
 | 
					 | 
				
			||||||
              offset: offset,
 | 
					              offset: offset,
 | 
				
			||||||
              zoom: zoom,
 | 
					              zoom: zoom,
 | 
				
			||||||
              canvasSize: canvasSize),
 | 
					              canvasSize: canvasSize,
 | 
				
			||||||
 | 
					              controller: controller),
 | 
				
			||||||
          size: canvasSize,
 | 
					          size: canvasSize,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,24 +1,32 @@
 | 
				
			|||||||
import 'dart:math';
 | 
					import 'dart:math';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:notes/canvas/paint_controller.dart';
 | 
				
			||||||
import 'package:notes/canvas/screen_document_mapping.dart';
 | 
					import 'package:notes/canvas/screen_document_mapping.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'document_types.dart';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
final Rect a4Page =
 | 
					final Rect a4Page =
 | 
				
			||||||
    Rect.fromPoints(const Offset(.0, .0), const Offset(210, 210 * sqrt2));
 | 
					    Rect.fromPoints(const Offset(.0, .0), const Offset(210, 210 * sqrt2));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MyPainter extends CustomPainter {
 | 
					class MyPainter extends CustomPainter {
 | 
				
			||||||
  List<Stroke> strokes;
 | 
					 | 
				
			||||||
  double zoom;
 | 
					  double zoom;
 | 
				
			||||||
  Offset offset;
 | 
					  Offset offset;
 | 
				
			||||||
  Size canvasSize;
 | 
					  Size canvasSize;
 | 
				
			||||||
 | 
					  PaintController controller;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  late Pen activePen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  MyPainter(
 | 
					  MyPainter(
 | 
				
			||||||
      {required this.strokes,
 | 
					      {required this.zoom,
 | 
				
			||||||
      required this.zoom,
 | 
					 | 
				
			||||||
      required this.offset,
 | 
					      required this.offset,
 | 
				
			||||||
      required this.canvasSize});
 | 
					      required this.canvasSize,
 | 
				
			||||||
 | 
					      required this.controller})
 | 
				
			||||||
 | 
					      : super(repaint: controller) {
 | 
				
			||||||
 | 
					    activePen = controller.activePen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    controller.addListener(() {
 | 
				
			||||||
 | 
					      activePen = controller.activePen;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Offset _translatept(Offset pt, Size canvasSize) {
 | 
					  Offset _translatept(Offset pt, Size canvasSize) {
 | 
				
			||||||
    final scale = calcPageDependentScale(zoom, a4Page, canvasSize);
 | 
					    final scale = calcPageDependentScale(zoom, a4Page, canvasSize);
 | 
				
			||||||
@@ -42,7 +50,7 @@ class MyPainter extends CustomPainter {
 | 
				
			|||||||
            _translatept(a4Page.bottomRight, size)),
 | 
					            _translatept(a4Page.bottomRight, size)),
 | 
				
			||||||
        backgroundPaint);
 | 
					        backgroundPaint);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (final stroke in strokes) {
 | 
					    for (final stroke in controller.strokes) {
 | 
				
			||||||
      for (int i = 0; i < stroke.points.length - 1; i++) {
 | 
					      for (int i = 0; i < stroke.points.length - 1; i++) {
 | 
				
			||||||
        Offset pt1 = stroke.points[i].point;
 | 
					        Offset pt1 = stroke.points[i].point;
 | 
				
			||||||
        pt1 = _translatept(pt1, size);
 | 
					        pt1 = _translatept(pt1, size);
 | 
				
			||||||
@@ -58,8 +66,6 @@ class MyPainter extends CustomPainter {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  bool shouldRepaint(MyPainter oldDelegate) {
 | 
					  bool shouldRepaint(MyPainter oldDelegate) {
 | 
				
			||||||
    return oldDelegate.strokes != strokes ||
 | 
					    return oldDelegate.zoom != zoom || oldDelegate.offset != offset;
 | 
				
			||||||
        oldDelegate.zoom != zoom ||
 | 
					 | 
				
			||||||
        oldDelegate.offset != offset;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										116
									
								
								lib/canvas/paint_controller.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								lib/canvas/paint_controller.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
				
			|||||||
 | 
					import 'dart:math';
 | 
				
			||||||
 | 
					import 'dart:ui';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:flutter/foundation.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'document_types.dart';
 | 
				
			||||||
 | 
					import 'my_painter.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum Pen { eraser, pen, highlighter, selector }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PaintController extends ChangeNotifier {
 | 
				
			||||||
 | 
					  Pen activePen = Pen.pen;
 | 
				
			||||||
 | 
					  List<Stroke> strokes = [];
 | 
				
			||||||
 | 
					  final bool _allowDrawWithFinger = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void changePen(Pen pen) {
 | 
				
			||||||
 | 
					    activePen = pen;
 | 
				
			||||||
 | 
					    notifyListeners();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double _calcTiltedWidth(double baseWidth, double tilt) {
 | 
				
			||||||
 | 
					    if (tilt == .0) return baseWidth;
 | 
				
			||||||
 | 
					    return baseWidth * tilt;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  double _calcAngleDependentWidth(Point pt1, Point pt2, double basetickness) {
 | 
				
			||||||
 | 
					    double dx = pt2.point.dx - pt1.point.dx;
 | 
				
			||||||
 | 
					    double dy = pt2.point.dy - pt1.point.dy;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // todo those deltas to small to get an accurate direction!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    double alpha = atan(dx / dy);
 | 
				
			||||||
 | 
					    // alpha has range from 0 - 2pi
 | 
				
			||||||
 | 
					    // we want 0.5 -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    alpha /= (2 * pi * 2);
 | 
				
			||||||
 | 
					    alpha += .5;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    double thickness = basetickness * alpha;
 | 
				
			||||||
 | 
					    return thickness;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void pointDownEvent(Offset offset, PointerDeviceKind pointer, double tilt) {
 | 
				
			||||||
 | 
					    if (_allowDrawWithFinger || pointer != PointerDeviceKind.touch) {
 | 
				
			||||||
 | 
					      // todo line drawn on edge where line left page
 | 
				
			||||||
 | 
					      if (!a4Page.contains(offset)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // todo handle other pens
 | 
				
			||||||
 | 
					      if (activePen != Pen.pen) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      strokes
 | 
				
			||||||
 | 
					          .add(Stroke.fromPoints([Point(offset, _calcTiltedWidth(3.0, tilt))]));
 | 
				
			||||||
 | 
					      notifyListeners();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void pointUpEvent(PointerDeviceKind pointer) {
 | 
				
			||||||
 | 
					    if (activePen == Pen.eraser) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (_allowDrawWithFinger || pointer != PointerDeviceKind.touch) {
 | 
				
			||||||
 | 
					      if (strokes.last.points.length <= 1) {
 | 
				
			||||||
 | 
					        // if the line consists only of one point (point) add endpoint as the same to allow drawing a line
 | 
				
			||||||
 | 
					        // todo maybe solve this in custompainter in future
 | 
				
			||||||
 | 
					        strokes.last.points.add(strokes.last.points.last);
 | 
				
			||||||
 | 
					        notifyListeners();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void pointMoveEvent(Offset offset, PointerDeviceKind pointer, double tilt) {
 | 
				
			||||||
 | 
					    if (!a4Page.contains(offset)) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (_allowDrawWithFinger || pointer != PointerDeviceKind.touch) {
 | 
				
			||||||
 | 
					      switch (activePen) {
 | 
				
			||||||
 | 
					        case Pen.eraser:
 | 
				
			||||||
 | 
					          // todo dynamic eraser size
 | 
				
			||||||
 | 
					          final eraserrect = Rect.fromCircle(center: offset, radius: 3);
 | 
				
			||||||
 | 
					          for (final stroke in strokes) {
 | 
				
			||||||
 | 
					            // check if delete action was within bounding rect of stroke
 | 
				
			||||||
 | 
					            if (stroke.getBoundingRect().contains(offset)) {
 | 
				
			||||||
 | 
					              // check if eraser hit an point within its range
 | 
				
			||||||
 | 
					              for (final pt in stroke.points) {
 | 
				
			||||||
 | 
					                if (eraserrect.contains(pt.point)) {
 | 
				
			||||||
 | 
					                  strokes.remove(stroke);
 | 
				
			||||||
 | 
					                  notifyListeners();
 | 
				
			||||||
 | 
					                  return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case Pen.pen:
 | 
				
			||||||
 | 
					          final pts = strokes.last.points;
 | 
				
			||||||
 | 
					          if (pts.last.point == offset) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          double newWidth = _calcTiltedWidth(5.0, tilt);
 | 
				
			||||||
 | 
					          if (strokes.last.points.length > 1) {
 | 
				
			||||||
 | 
					            newWidth = _calcAngleDependentWidth(
 | 
				
			||||||
 | 
					                pts.last, pts[pts.length - 2], newWidth);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          strokes.last.addPoint(Point(offset, newWidth));
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case Pen.highlighter:
 | 
				
			||||||
 | 
					          // TODO: Handle this case.
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					        case Pen.selector:
 | 
				
			||||||
 | 
					          // TODO: Handle this case.
 | 
				
			||||||
 | 
					          break;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      notifyListeners();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										86
									
								
								lib/tool_bar.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								lib/tool_bar.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
				
			|||||||
 | 
					import 'package:adwaita_icons/adwaita_icons.dart';
 | 
				
			||||||
 | 
					import 'package:fluentui_system_icons/fluentui_system_icons.dart';
 | 
				
			||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:iconify_flutter/iconify_flutter.dart';
 | 
				
			||||||
 | 
					import 'package:iconify_flutter/icons/emojione_monotone.dart';
 | 
				
			||||||
 | 
					import 'package:iconify_flutter/icons/jam.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'canvas/paint_controller.dart';
 | 
				
			||||||
 | 
					import 'icon_material_button.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ToolBar extends StatefulWidget {
 | 
				
			||||||
 | 
					  const ToolBar({Key? key, required this.onPenChange}) : super(key: key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  final void Function(Pen pen) onPenChange;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  State<ToolBar> createState() => _ToolBarState();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _ToolBarState extends State<ToolBar> {
 | 
				
			||||||
 | 
					  Pen activepen = Pen.pen;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return Container(
 | 
				
			||||||
 | 
					      color: const Color(0xff3f3f3f),
 | 
				
			||||||
 | 
					      width: 45,
 | 
				
			||||||
 | 
					      child: Column(
 | 
				
			||||||
 | 
					        children: [
 | 
				
			||||||
 | 
					          const SizedBox(
 | 
				
			||||||
 | 
					            height: 10,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          IconMaterialButton(
 | 
				
			||||||
 | 
					            icon: const Iconify(
 | 
				
			||||||
 | 
					              EmojioneMonotone.fountain_pen,
 | 
				
			||||||
 | 
					              color: Color.fromRGBO(255, 255, 255, .85),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            color: const Color.fromRGBO(255, 255, 255, .85),
 | 
				
			||||||
 | 
					            onPressed: () {
 | 
				
			||||||
 | 
					              setState(() => activepen = Pen.pen);
 | 
				
			||||||
 | 
					              widget.onPenChange(Pen.pen);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            selected: activepen == Pen.pen,
 | 
				
			||||||
 | 
					            iconSize: 24,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          IconMaterialButton(
 | 
				
			||||||
 | 
					            icon: const Iconify(
 | 
				
			||||||
 | 
					              Jam.highlighter,
 | 
				
			||||||
 | 
					              color: Color.fromRGBO(255, 255, 255, .85),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            color: const Color.fromRGBO(255, 255, 255, .85),
 | 
				
			||||||
 | 
					            onPressed: () {
 | 
				
			||||||
 | 
					              setState(() => activepen = Pen.highlighter);
 | 
				
			||||||
 | 
					              widget.onPenChange(Pen.highlighter);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            selected: activepen == Pen.highlighter,
 | 
				
			||||||
 | 
					            iconSize: 24,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          IconMaterialButton(
 | 
				
			||||||
 | 
					            icon: Transform.translate(
 | 
				
			||||||
 | 
					              offset: const Offset(-2.0, .0),
 | 
				
			||||||
 | 
					              child: const AdwaitaIcon(AdwaitaIcons.eraser2),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            color: const Color.fromRGBO(255, 255, 255, .85),
 | 
				
			||||||
 | 
					            onPressed: () {
 | 
				
			||||||
 | 
					              setState(() => activepen = Pen.eraser);
 | 
				
			||||||
 | 
					              widget.onPenChange(Pen.eraser);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            iconSize: 24,
 | 
				
			||||||
 | 
					            selected: activepen == Pen.eraser,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					          IconMaterialButton(
 | 
				
			||||||
 | 
					            icon: const Icon(FluentIcons.select_object_24_regular),
 | 
				
			||||||
 | 
					            color: const Color.fromRGBO(255, 255, 255, .85),
 | 
				
			||||||
 | 
					            onPressed: () {
 | 
				
			||||||
 | 
					              setState(() => activepen = Pen.selector);
 | 
				
			||||||
 | 
					              widget.onPenChange(Pen.selector);
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            selected: activepen == Pen.selector,
 | 
				
			||||||
 | 
					            iconSize: 24,
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user