diff --git a/api/lib/butterfly_api.dart b/api/lib/butterfly_api.dart index d9dd0bb2b1..48559b6c05 100644 --- a/api/lib/butterfly_api.dart +++ b/api/lib/butterfly_api.dart @@ -3,7 +3,7 @@ /// More dartdocs go here. library butterfly_api; -export 'src/butterfly_models.dart'; -export 'src/butterfly_helpers.dart'; +export 'butterfly_models.dart'; +export 'butterfly_helpers.dart'; // TODO: Export any libraries intended for clients of this package. diff --git a/api/lib/butterfly_helpers.dart b/api/lib/butterfly_helpers.dart new file mode 100644 index 0000000000..3a6c03fab3 --- /dev/null +++ b/api/lib/butterfly_helpers.dart @@ -0,0 +1,3 @@ +export 'src/helpers/asset_helper.dart'; +export 'src/helpers/point_helper.dart'; +export 'src/helpers/search_helper.dart'; diff --git a/api/lib/butterfly_models.dart b/api/lib/butterfly_models.dart new file mode 100644 index 0000000000..aafbe6a44a --- /dev/null +++ b/api/lib/butterfly_models.dart @@ -0,0 +1,22 @@ +export 'src/converter/core.dart'; +export 'src/converter/legacy.dart'; +export 'src/converter/note.dart'; +export 'src/models/animation.dart'; +export 'src/models/archive.dart'; +export 'src/models/area.dart'; +export 'src/models/asset.dart'; +export 'src/models/background.dart'; +export 'src/models/colors.dart'; +export 'src/models/data.dart'; +export 'src/models/element.dart'; +export 'src/models/export.dart'; +export 'src/models/info.dart'; +export 'src/models/meta.dart'; +export 'src/models/pack.dart'; +export 'src/models/page.dart'; +export 'src/models/painter.dart'; +export 'src/models/palette.dart'; +export 'src/models/point.dart'; +export 'src/models/property.dart'; +export 'src/models/tool.dart'; +export 'src/models/waypoint.dart'; diff --git a/api/lib/src/butterfly_helpers.dart b/api/lib/src/butterfly_helpers.dart deleted file mode 100644 index 89cd07d525..0000000000 --- a/api/lib/src/butterfly_helpers.dart +++ /dev/null @@ -1,3 +0,0 @@ -export 'helpers/asset_helper.dart'; -export 'helpers/point_helper.dart'; -export 'helpers/search_helper.dart'; diff --git a/api/lib/src/butterfly_models.dart b/api/lib/src/butterfly_models.dart deleted file mode 100644 index fb1ccb790a..0000000000 --- a/api/lib/src/butterfly_models.dart +++ /dev/null @@ -1,22 +0,0 @@ -export 'converter/core.dart'; -export 'converter/legacy.dart'; -export 'converter/note.dart'; -export 'models/animation.dart'; -export 'models/archive.dart'; -export 'models/area.dart'; -export 'models/asset.dart'; -export 'models/background.dart'; -export 'models/colors.dart'; -export 'models/data.dart'; -export 'models/element.dart'; -export 'models/export.dart'; -export 'models/info.dart'; -export 'models/meta.dart'; -export 'models/pack.dart'; -export 'models/page.dart'; -export 'models/painter.dart'; -export 'models/palette.dart'; -export 'models/point.dart'; -export 'models/property.dart'; -export 'models/tool.dart'; -export 'models/waypoint.dart'; diff --git a/api/lib/src/models/asset.dart b/api/lib/src/models/asset.dart index 00dda5da8c..715687487a 100644 --- a/api/lib/src/models/asset.dart +++ b/api/lib/src/models/asset.dart @@ -1,10 +1,11 @@ import 'dart:convert'; import 'dart:typed_data'; -import 'package:butterfly_api/src/butterfly_helpers.dart'; -import 'package:butterfly_api/src/models/data.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import '../helpers/asset_helper.dart'; +import 'data.dart'; + part 'asset.freezed.dart'; part 'asset.g.dart'; diff --git a/app/lib/bloc/document_bloc.dart b/app/lib/bloc/document_bloc.dart index 28d9ef5ee8..892e1e877d 100644 --- a/app/lib/bloc/document_bloc.dart +++ b/app/lib/bloc/document_bloc.dart @@ -174,41 +174,95 @@ class DocumentBloc extends ReplayBloc { null); } }, transformer: sequential()); - on((event, emit) async { - if (state is DocumentLoadSuccess) { - final current = state as DocumentLoadSuccess; - if (!(current.embedding?.editable ?? true)) return; - if (event.elements.isEmpty || - !current.page.content - .any((element) => event.elements.contains(element))) return; - final page = current.page; - final renderers = current.renderers; - current.currentIndexCubit.unbake( - unbakedElements: renderers.where((element) { - final remaining = !event.elements.contains( - element.element, - ); - if (!remaining) element.dispose(); - return remaining; - }).toList(), - ); - final newPage = page.copyWith( - content: List.from(page.content) - ..removeWhere((element) => event.elements.contains(element))); - // Remove unused assets - final unusedAssets = {}; - event.elements.whereType().forEach((element) { - final uri = Uri.tryParse(element.source); - if (uri?.scheme == '' && !newPage.usesSource(element.source)) { - unusedAssets.add(element.source); - } - }); - for (var asset in unusedAssets) { - current.data.removeAsset(asset); + on((event, emit) async { + final current = state; + if (current is! DocumentLoadSuccess) return; + final renderers = await Future.wait(event.elements.map((e) async { + final renderer = Renderer.fromInstance(e); + await renderer.setup(current.data, current.assetService, current.page); + return renderer; + }).toList()); + var content = List.from(current.page.content); + final transform = current.transformCubit.state; + for (var renderer in renderers) { + final index = content.indexOf(renderer.element); + if (index == -1) { + content.add(renderer.element); + continue; + } + content.removeAt(index); + var newIndex = index; + if (event.arrangement == Arrangement.front) { + newIndex = content.length - 1; + } else if (event.arrangement == Arrangement.back) { + newIndex = 0; + } else { + final rect = renderer.rect; + if (rect != null) { + final hits = (await rayCastRect(rect, this, transform)) + .map((e) => e.element) + .toList(); + final hitIndex = hits.indexOf(renderer.element); + if (hitIndex != -1) { + if (event.arrangement == Arrangement.backward && hitIndex != 0) { + newIndex = content.indexOf(hits[hitIndex - 1]); + } else if (event.arrangement == Arrangement.forward && + hitIndex != hits.length - 1) { + newIndex = content.indexOf(hits[hitIndex + 1]) + 1; + } + } + } + } + if (newIndex >= 0) { + content.insert(newIndex, renderer.element); + } else { + content.add(renderer.element); } - - await _saveState(emit, current.copyWith(page: newPage), null); } + final newPage = current.page.copyWith(content: content); + return _saveState( + emit, + current.copyWith( + page: newPage, + ), + null) + .whenComplete(() => current.currentIndexCubit + .loadElements(current.data, current.assetService, newPage)); + }); + on((event, emit) async { + final current = state; + if (current is! DocumentLoadSuccess) return; + if (!(current.embedding?.editable ?? true)) return; + if (event.elements.isEmpty || + !current.page.content + .any((element) => event.elements.contains(element))) return; + final page = current.page; + final renderers = current.renderers; + current.currentIndexCubit.unbake( + unbakedElements: renderers.where((element) { + final remaining = !event.elements.contains( + element.element, + ); + if (!remaining) element.dispose(); + return remaining; + }).toList(), + ); + final newPage = page.copyWith( + content: List.from(page.content) + ..removeWhere((element) => event.elements.contains(element))); + // Remove unused assets + final unusedAssets = {}; + event.elements.whereType().forEach((element) { + final uri = Uri.tryParse(element.source); + if (uri?.scheme == '' && !newPage.usesSource(element.source)) { + unusedAssets.add(element.source); + } + }); + for (var asset in unusedAssets) { + current.data.removeAsset(asset); + } + + await _saveState(emit, current.copyWith(page: newPage), null); }, transformer: sequential()); on((event, emit) async { if (state is DocumentLoadSuccess) { diff --git a/app/lib/bloc/document_event.dart b/app/lib/bloc/document_event.dart index 7bba0a5817..837686ac7b 100644 --- a/app/lib/bloc/document_event.dart +++ b/app/lib/bloc/document_event.dart @@ -64,15 +64,15 @@ class ElementsRemoved extends DocumentEvent { List get props => [elements]; } -enum Arangement { forward, backward, front, back } +enum Arrangement { forward, backward, front, back } class ElementsArranged extends DocumentEvent { final List elements; - final Arangement arangement; + final Arrangement arrangement; - const ElementsArranged(this.elements, this.arangement); + const ElementsArranged(this.elements, this.arrangement); @override - List get props => [elements, arangement]; + List get props => [elements, arrangement]; } class DocumentDescriptorChanged extends DocumentEvent { diff --git a/app/lib/cubits/current_index.dart b/app/lib/cubits/current_index.dart index 16dbd0291c..1b8c488c73 100644 --- a/app/lib/cubits/current_index.dart +++ b/app/lib/cubits/current_index.dart @@ -450,6 +450,12 @@ class CurrentIndexCubit extends Cubit { Future loadElements( NoteData document, AssetService assetService, DocumentPage page) async { + for (var e in state.cameraViewport.unbakedElements) { + e.dispose(); + } + for (var e in state.cameraViewport.bakedElements) { + e.dispose(); + } final renderers = page.content.map((e) => Renderer.fromInstance(e)).toList(); await Future.wait(renderers diff --git a/app/lib/dialogs/elements.dart b/app/lib/dialogs/elements.dart index 33dbd78e48..de9542d6a3 100644 --- a/app/lib/dialogs/elements.dart +++ b/app/lib/dialogs/elements.dart @@ -75,7 +75,7 @@ class ElementsDialog extends StatelessWidget { SubmenuButton( leadingIcon: const Icon(PhosphorIconsLight.layout), menuStyle: const MenuStyle(alignment: Alignment.centerRight), - menuChildren: Arangement.values + menuChildren: Arrangement.values .map((e) => MenuItemButton( leadingIcon: Icon(e.icon(PhosphorIconsStyle.light)), child: Text(e.getLocalizedName(context)), diff --git a/app/lib/handlers/eraser.dart b/app/lib/handlers/eraser.dart index 8f5d5e2f5e..3836186a2c 100644 --- a/app/lib/handlers/eraser.dart +++ b/app/lib/handlers/eraser.dart @@ -40,7 +40,8 @@ class EraserHandler extends Handler { if (!_currentlyErasing) { _currentlyErasing = true; // Raycast - final ray = await rayCast(globalPos, context.buildContext, size); + final ray = await rayCast(globalPos, context.getDocumentBloc(), + context.getCameraTransform(), size); final newElements = ray .map((e) => e.element) .whereType() diff --git a/app/lib/handlers/hand.dart b/app/lib/handlers/hand.dart index 37277ed7ee..78884f177c 100644 --- a/app/lib/handlers/hand.dart +++ b/app/lib/handlers/hand.dart @@ -285,7 +285,8 @@ class HandHandler extends Handler { } final settings = context.getSettings(); final radius = settings.selectSensitivity / transform.size; - final hits = await rayCast(globalPos, context.buildContext, radius); + final hits = await rayCast(globalPos, context.getDocumentBloc(), + context.getCameraTransform(), radius); if (hits.isEmpty) { if (!context.isCtrlPressed) { _selected.clear(); @@ -336,7 +337,8 @@ class HandHandler extends Handler { return; } final position = context.getCameraTransform().localToGlobal(localPosition); - final hits = await rayCast(position, context.buildContext, 0.0); + final hits = await rayCast( + position, context.getDocumentBloc(), context.getCameraTransform(), 0.0); final hit = hits.firstOrNull; final rect = hit?.rect; if ((rect != null && !(getSelectionRect()?.contains(position) ?? false)) && @@ -470,7 +472,8 @@ class HandHandler extends Handler { if (!context.isCtrlPressed) { _selected.clear(); } - final hits = await rayCastRect(freeSelection, context.buildContext); + final hits = await rayCastRect(freeSelection, context.getDocumentBloc(), + context.getCameraTransform()); _selected.addAll(hits); context.refresh(); } diff --git a/app/lib/handlers/handler.dart b/app/lib/handlers/handler.dart index 4c0c04432a..069bdb8cc0 100644 --- a/app/lib/handlers/handler.dart +++ b/app/lib/handlers/handler.dart @@ -267,21 +267,22 @@ class _RayCastParams { Future>> rayCast( Offset globalPosition, - BuildContext context, + DocumentBloc bloc, + CameraTransform transform, double radius, ) async { return rayCastRect( Rect.fromCircle(center: globalPosition, radius: radius), - context, + bloc, + transform, ); } Future>> rayCastRect( Rect rect, - BuildContext context, + DocumentBloc bloc, + CameraTransform transform, ) async { - final bloc = context.read(); - final transform = context.read().state; final state = bloc.state; if (state is! DocumentLoadSuccess) return {}; final renderers = state.cameraViewport.visibleElements; diff --git a/app/lib/handlers/layer.dart b/app/lib/handlers/layer.dart index 1f137c5569..dfb5dcdae7 100644 --- a/app/lib/handlers/layer.dart +++ b/app/lib/handlers/layer.dart @@ -9,8 +9,11 @@ class LayerHandler extends Handler { final transform = context.getCameraTransform(); final state = context.getState(); if (state == null) return; - final hits = await rayCast(transform.localToGlobal(event.localPosition), - context.buildContext, data.strokeWidth / transform.size); + final hits = await rayCast( + transform.localToGlobal(event.localPosition), + context.getDocumentBloc(), + context.getCameraTransform(), + data.strokeWidth / transform.size); context.addDocumentEvent(ElementsLayerChanged( state.currentLayer, hits.map((e) => e.element).toList())); } diff --git a/app/lib/handlers/path_eraser.dart b/app/lib/handlers/path_eraser.dart index b176d8316f..fe5d9d54a8 100644 --- a/app/lib/handlers/path_eraser.dart +++ b/app/lib/handlers/path_eraser.dart @@ -12,7 +12,8 @@ class PathEraserHandler extends Handler { _removeRunning = true; final hits = await rayCast( transform.localToGlobal(event.localPosition), - context.buildContext, + context.getDocumentBloc(), + context.getCameraTransform(), data.strokeWidth / transform.size, ); context diff --git a/app/lib/visualizer/event.dart b/app/lib/visualizer/event.dart index d16dcddd25..5dd175369a 100644 --- a/app/lib/visualizer/event.dart +++ b/app/lib/visualizer/event.dart @@ -4,30 +4,30 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:material_leap/material_leap.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart'; -extension ArangementVisualizer on Arangement { +extension ArangementVisualizer on Arrangement { String getLocalizedName(BuildContext context) { final loc = AppLocalizations.of(context); switch (this) { - case Arangement.back: + case Arrangement.back: return loc.sendToBack; - case Arangement.front: + case Arrangement.front: return loc.bringToFront; - case Arangement.backward: + case Arrangement.backward: return loc.sendBackward; - case Arangement.forward: + case Arrangement.forward: return loc.bringForward; } } IconGetter get icon { switch (this) { - case Arangement.back: + case Arrangement.back: return PhosphorIcons.arrowDown; - case Arangement.front: + case Arrangement.front: return PhosphorIcons.arrowUp; - case Arangement.backward: + case Arrangement.backward: return PhosphorIcons.arrowDownLeft; - case Arangement.forward: + case Arrangement.forward: return PhosphorIcons.arrowUpRight; } }