Skip to content

Commit

Permalink
feat: delete and duplicate selection (#937)
Browse files Browse the repository at this point in the history
* feat: added duplication and deletion of selection

* fix: added tooltips to selection options

* refactor: shifting of duplicated selection

* fix: typo in comment

Co-authored-by: Adil Hanney <[email protected]>

* fix: match duplication button in EditorPageManager

Co-authored-by: Adil Hanney <[email protected]>

* fix: match delete button in EditorPageManager

Co-authored-by: Adil Hanney <[email protected]>

* refactor: use conditional operator

* refactor: use guard clauses and prevent unnecessary setState

* refactor: rename SelectModal to SelectionBar

* refactor: rename selectionOptions to selectionBar

* fix: error in guard clause

* fix: replace currentPageIndex with selectResult.pageIndex

* fix: changed back to strokes.first.pageIndex becuase select.unselect() sets pageIndex to -1

* feat: only show selection options  when selection is done

* feat: unselect selection if selectionResullt is empty

* ref: convert SelectionBar to StatelessWidget

* chore: revert _missing_translations

* ref: formatting

* fix: assign new id to duplicated image

* chore: revert timestamp to make merging easier

---------

Co-authored-by: Adil Hanney <[email protected]>
  • Loading branch information
AraxTheCoder and adil192 authored Oct 4, 2023
1 parent 1a5a14f commit 6c098bc
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 0 deletions.
50 changes: 50 additions & 0 deletions lib/components/toolbar/selection_bar.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:saber/components/theming/adaptive_icon.dart';
import 'package:saber/i18n/strings.g.dart';

class SelectionBar extends StatelessWidget {
final VoidCallback duplicateSelection;
final VoidCallback deleteSelection;

const SelectionBar({
super.key,
required this.duplicateSelection,
required this.deleteSelection,
});

@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: duplicateSelection,
style: TextButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.secondary,
backgroundColor: Colors.transparent,
shape: const CircleBorder(),
),
tooltip: t.editor.selectionBar.duplicate,
icon: const AdaptiveIcon(
icon: Icons.content_copy,
cupertinoIcon: CupertinoIcons.doc_on_clipboard,
),
),
IconButton(
onPressed: deleteSelection,
style: TextButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.secondary,
backgroundColor: Colors.transparent,
shape: const CircleBorder(),
),
tooltip: t.editor.selectionBar.delete,
icon: const AdaptiveIcon(
icon: Icons.delete,
cupertinoIcon: CupertinoIcons.delete,
),
),
],
);
}
}
19 changes: 19 additions & 0 deletions lib/components/toolbar/toolbar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:saber/components/theming/dynamic_material_app.dart';
import 'package:saber/components/toolbar/color_bar.dart';
import 'package:saber/components/toolbar/export_bar.dart';
import 'package:saber/components/toolbar/pen_modal.dart';
import 'package:saber/components/toolbar/selection_bar.dart';
import 'package:saber/components/toolbar/toolbar_button.dart';
import 'package:saber/data/editor/page.dart';
import 'package:saber/data/extensions/color_extensions.dart';
Expand Down Expand Up @@ -47,6 +48,9 @@ class Toolbar extends StatefulWidget {

required this.paste,

required this.duplicateSelection,
required this.deleteSelection,

required this.exportAsSbn,
required this.exportAsPdf,
required this.exportAsPng,
Expand All @@ -73,6 +77,9 @@ class Toolbar extends StatefulWidget {

final VoidCallback paste;

final VoidCallback duplicateSelection;
final VoidCallback deleteSelection;

final Future Function()? exportAsSbn;
final Future Function()? exportAsPdf;
final Future Function()? exportAsPng;
Expand Down Expand Up @@ -165,6 +172,13 @@ class _ToolbarState extends State<Toolbar> {
_ => null,
};

if (widget.currentTool == Select.currentSelect) {
// Enable selection bar only when selection is done
toolOptionsType.value = Select.currentSelect.doneSelecting
? ToolOptions.select
: ToolOptions.hide;
}

final children = <Widget>[
ValueListenableBuilder(
valueListenable: showExportOptions,
Expand Down Expand Up @@ -201,6 +215,10 @@ class _ToolbarState extends State<Toolbar> {
getTool: () => Highlighter.currentHighlighter,
setTool: widget.setTool,
),
ToolOptions.select => SelectionBar(
duplicateSelection: widget.duplicateSelection,
deleteSelection: widget.deleteSelection,
),
},
);
},
Expand Down Expand Up @@ -483,4 +501,5 @@ enum ToolOptions {
hide,
pen,
highlighter,
select,
}
18 changes: 18 additions & 0 deletions lib/data/tools/select.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,22 @@ class SelectResult {
required this.images,
required this.path,
});

bool get isEmpty {
return strokes.isEmpty && images.isEmpty;
}

SelectResult copyWith({
int? pageIndex,
List<Stroke>? strokes,
List<EditorImage>? images,
Path? path,
}) {
return SelectResult(
pageIndex: pageIndex ?? this.pageIndex,
strokes: strokes ?? this.strokes,
images: images ?? this.images,
path: path ?? this.path,
);
}
}
12 changes: 12 additions & 0 deletions lib/i18n/strings.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ class _StringsEditorEn {
late final _StringsEditorPenOptionsEn penOptions = _StringsEditorPenOptionsEn._(_root);
late final _StringsEditorColorsEn colors = _StringsEditorColorsEn._(_root);
late final _StringsEditorImageOptionsEn imageOptions = _StringsEditorImageOptionsEn._(_root);
late final _StringsEditorSelectionBarEn selectionBar = _StringsEditorSelectionBarEn._(_root);
late final _StringsEditorMenuEn menu = _StringsEditorMenuEn._(_root);
late final _StringsEditorNewerFileFormatEn newerFileFormat = _StringsEditorNewerFileFormatEn._(_root);
late final _StringsEditorQuillEn quill = _StringsEditorQuillEn._(_root);
Expand Down Expand Up @@ -799,6 +800,17 @@ class _StringsEditorImageOptionsEn {
String get delete => 'Delete';
}

// Path: editor.selectionBar
class _StringsEditorSelectionBarEn {
_StringsEditorSelectionBarEn._(this._root);

final _StringsEn _root; // ignore: unused_field

// Translations
String get delete => 'Delete';
String get duplicate => 'Duplicate';
}

// Path: editor.menu
class _StringsEditorMenuEn {
_StringsEditorMenuEn._(this._root);
Expand Down
3 changes: 3 additions & 0 deletions lib/i18n/strings.i18n.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@ editor:
setAsBackground: Set as background
removeAsBackground: Remove as background
delete: Delete
selectionBar:
delete: Delete
duplicate: Duplicate
menu:
clearPage: Clear page $page/$totalPages
clearAllPages: Clear all pages
Expand Down
77 changes: 77 additions & 0 deletions lib/pages/editor/editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,10 @@ class EditorState extends State<Editor> {
));
} else {
select.onDragEnd(page.strokes, page.images);

if (select.selectResult.isEmpty) {
Select.currentSelect.unselect();
}
}
} else if (currentTool is LaserPointer) {
Stroke newStroke = (currentTool as LaserPointer).onDragEnd(
Expand Down Expand Up @@ -1245,6 +1249,79 @@ class EditorState extends State<Editor> {
});
},
currentTool: currentTool,
duplicateSelection: () {
final select = currentTool as Select;
if (!select.doneSelecting) return;

setState(() {
final page = coreInfo.pages[select.selectResult.pageIndex];
final strokes = select.selectResult.strokes;
final images = select.selectResult.images;

const duplicationFeedbackOffset = Offset(25, -25);

final duplicatedStrokes = strokes
.map((stroke) {
return stroke.copy()
..shift(duplicationFeedbackOffset);
})
.toList();

final duplicatedImages = images
.map((image) {
return image.copy()
..id = coreInfo.nextImageId++
..dstRect.shift(duplicationFeedbackOffset);
})
.toList();

page.strokes.addAll(duplicatedStrokes);
page.images.addAll(duplicatedImages);

select.selectResult = select.selectResult.copyWith(
strokes: duplicatedStrokes,
images: duplicatedImages,
path: select.selectResult.path.shift(duplicationFeedbackOffset),
);

history.recordChange(EditorHistoryItem(
type: EditorHistoryItemType.draw,
pageIndex: select.selectResult.pageIndex,
strokes: duplicatedStrokes,
images: duplicatedImages,
));
autosaveAfterDelay();
});
},
deleteSelection: () {
final select = currentTool as Select;
if (!select.doneSelecting) {
return;
}

setState(() {
final page = coreInfo.pages[select.selectResult.pageIndex];
final strokes = select.selectResult.strokes;
final images = select.selectResult.images;

for (Stroke stroke in strokes) {
page.strokes.remove(stroke);
}
for (EditorImage image in images) {
page.images.remove(image);
}

select.unselect();

history.recordChange(EditorHistoryItem(
type: EditorHistoryItemType.erase,
pageIndex: strokes.first.pageIndex,
strokes: strokes,
images: images,
));
autosaveAfterDelay();
});
},
setColor: (color) {
setState(() {
updateColorBar(color);
Expand Down

1 comment on commit 6c098bc

@adil192
Copy link
Member

@adil192 adil192 commented on 6c098bc Oct 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New translation alert!

Please sign in to comment.