diff --git a/lib/components/canvas/_stroke.dart b/lib/components/canvas/_stroke.dart index 10e1ffd71a..d8e29386f7 100644 --- a/lib/components/canvas/_stroke.dart +++ b/lib/components/canvas/_stroke.dart @@ -277,6 +277,9 @@ class Stroke { double get maxY { return points.isEmpty ? 0 : points.map((point) => point.y).reduce(max); } + double get minY { + return points.isEmpty ? 0 : points.map((point) => point.y).reduce(min); + } RecognizedUnistroke? detectShape() { if (points.length < 3) return null; @@ -360,6 +363,14 @@ class Stroke { } } + Offset get firstPoint { + return(Offset(points[0].x,points[0].y)); + } + + Offset get lastPoint { + return(Offset(points.last.x,points.last.y)); + } + Stroke copy() => Stroke( color: color, pressureEnabled: pressureEnabled, diff --git a/lib/components/toolbar/pen_modal.dart b/lib/components/toolbar/pen_modal.dart index 946ea7c4ed..ddaf0d8cd1 100644 --- a/lib/components/toolbar/pen_modal.dart +++ b/lib/components/toolbar/pen_modal.dart @@ -125,6 +125,26 @@ class _PenModalState extends State { tooltip: t.editor.pens.shapePen, icon: const FaIcon(ShapePen.shapePenIcon), ), + const SizedBox.square(dimension: 8), + IconButton( + onPressed: () => setState(() { + widget.setTool(Pen.insertPen()); + }), + style: TextButton.styleFrom( + foregroundColor: Pen.currentPen.icon == Pen.insertPenIcon + ? Theme.of(context).colorScheme.secondary + : Theme.of(context).colorScheme.onSurface, + backgroundColor: Pen.currentPen.icon == Pen.insertPenIcon + ? Theme.of(context) + .colorScheme + .secondary + .withValues(alpha: 0.1) + : Colors.transparent, + shape: const CircleBorder(), + ), + tooltip: t.editor.pens.insertPen, + icon: const FaIcon(Pen.insertPenIcon), + ), ], ], ); diff --git a/lib/data/editor/page.dart b/lib/data/editor/page.dart index a8c1da5db2..846f5aed2f 100644 --- a/lib/data/editor/page.dart +++ b/lib/data/editor/page.dart @@ -25,7 +25,7 @@ class EditorPage extends Listenable implements HasSize { static const Size defaultSize = Size(defaultWidth, defaultHeight); @override - final Size size; + Size size; late final CanvasKey innerCanvasKey = CanvasKey(); RenderBox? _renderBox; diff --git a/lib/data/tools/_tool.dart b/lib/data/tools/_tool.dart index 723bae5ed4..c620d30117 100644 --- a/lib/data/tools/_tool.dart +++ b/lib/data/tools/_tool.dart @@ -24,6 +24,7 @@ class _TextEditingTool extends Tool { enum ToolId { fountainPen('fountainPen'), ballpointPen('ballpointPen'), + insertPen('insertPen'), highlighter('Highlighter'), pencil('Pencil'), shapePen('ShapePen'), diff --git a/lib/data/tools/pen.dart b/lib/data/tools/pen.dart index 07a8339e62..07d86fe1e1 100644 --- a/lib/data/tools/pen.dart +++ b/lib/data/tools/pen.dart @@ -46,6 +46,18 @@ class Pen extends Tool { color = Color(stows.lastBallpointPenColor.value), toolId = ToolId.ballpointPen; + Pen.insertPen() + : name = t.editor.pens.insertPen, + sizeMin = 1, + sizeMax = 25, + sizeStep = 1, + icon = insertPenIcon, + options = Prefs.lastBallpointPenOptions.value, + pressureEnabled = false, + color = Color(Prefs.lastBallpointPenColor.value), + toolId = ToolId.insertPen; + + final String name; final double sizeMin, sizeMax, sizeStep; late final int sizeStepsBetweenMinAndMax = @@ -57,6 +69,7 @@ class Pen extends Tool { static const IconData fountainPenIcon = FontAwesomeIcons.penFancy; static const IconData ballpointPenIcon = FontAwesomeIcons.pen; + static const IconData insertPenIcon = FontAwesomeIcons.arrowsUpDown; static Stroke? currentStroke; Color color; diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index fae5ccef55..01e99f7f07 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -624,6 +624,7 @@ class TranslationsEditorPensEn { // Translations String get fountainPen => 'Fountain pen'; String get ballpointPen => 'Ballpoint pen'; + String get insertPen => 'Insert space pen'; String get highlighter => 'Highlighter'; String get pencil => 'Pencil'; String get shapePen => 'Shape pen'; diff --git a/lib/pages/editor/editor.dart b/lib/pages/editor/editor.dart index 77c0fb017a..3b1706d708 100644 --- a/lib/pages/editor/editor.dart +++ b/lib/pages/editor/editor.dart @@ -136,6 +136,11 @@ class EditorState extends State { Pen.currentPen = Pen.ballpointPen(); } return Pen.currentPen; + case ToolId.insertPen: + if (Pen.currentPen.toolId != Prefs.lastTool.value) { + Pen.currentPen = Pen.insertPen(); + } + return Pen.currentPen; case ToolId.shapePen: if (Pen.currentPen.toolId != stows.lastTool.value) { Pen.currentPen = ShapePen(); @@ -628,6 +633,7 @@ class EditorState extends State { moveOffset += offset; } + void onDrawEnd(ScaleEndDetails details) { final page = coreInfo.pages[dragPageIndex!]; bool shouldSave = true; @@ -643,14 +649,23 @@ class EditorState extends State { newStroke.convertToLine(); } - createPage(newStroke.pageIndex); - page.insertStroke(newStroke); - history.recordChange(EditorHistoryItem( - type: EditorHistoryItemType.draw, - pageIndex: dragPageIndex!, - strokes: [newStroke], - images: [], - )); + if (currentTool.toolId != ToolId.insertPen) { + // normal pen + createPage(newStroke.pageIndex); + page.insertStroke(newStroke); + history.recordChange(EditorHistoryItem( + type: EditorHistoryItemType.draw, + pageIndex: dragPageIndex!, + strokes: [newStroke], + images: [], + )); + } + else { + // is insert pen, I must insert free space of the vertical length of stroke + double yFirst=newStroke.firstPoint.dy; + double yLast=newStroke.lastPoint.dy; + moveItemsOnPageUpDown(page,dragPageIndex!,yFirst,yLast); // move all items below up/down + } } else if (currentTool is Eraser) { final erased = (currentTool as Eraser).onDragEnd(); if (tmpTool != null && @@ -706,6 +721,82 @@ class EditorState extends State { if (shouldSave) autosaveAfterDelay(); } + // move all items below maxY down by maxY-minY + void moveItemsOnPageUpDown(EditorPage page,int pageIndex, double yFirst,double yLast){ + if ((yFirst-yLast).abs()<1.0){ + return; // too small move + } + + final double maxY; + maxY=yFirst; // all items below first point + List strokesBelow=[]; + double maxStrokesY; + maxStrokesY=0.0; + for (int i = 0; i < page.strokes.length; i++) { + final stroke = page.strokes[i]; + if (stroke.minY>=maxY) { + // stroke is below inserted place + strokesBelow.add(stroke); // add stroke to list of interest + if (stroke.maxY>maxStrokesY) { + maxStrokesY=stroke.maxY; // the lowest possible point of strokes + } + } + } + + List imagesBelow=[]; + + for (int i = 0; i < page.images.length; i++) { + final EditorImage image = page.images[i]; + if (image.dstRect.top>=maxY){ + //image is below inserted place + imagesBelow.add(image); // add stroke to list of interest + if (image.dstRect.bottom>maxStrokesY) { + maxStrokesY=image.dstRect.bottom; // the lowest possible point of images + } + } + } + if (strokesBelow.length==0 && imagesBelow.length==0) { + return; // nothing to move + } + final double shiftY=yLast-yFirst; + if (maxStrokesY+shiftY>page.size.height){ + // must enlarge page + page.size=Size(page.size.width,maxStrokesY+shiftY); + } + if (maxStrokesY+shiftY