Implement arrangement

This commit is contained in:
CodeDoctorDE
2023-06-13 18:55:46 +02:00
parent 1a65a282c3
commit f39fdbbc80
16 changed files with 158 additions and 88 deletions

View File

@ -3,7 +3,7 @@
/// More dartdocs go here. /// More dartdocs go here.
library butterfly_api; library butterfly_api;
export 'src/butterfly_models.dart'; export 'butterfly_models.dart';
export 'src/butterfly_helpers.dart'; export 'butterfly_helpers.dart';
// TODO: Export any libraries intended for clients of this package. // TODO: Export any libraries intended for clients of this package.

View File

@ -0,0 +1,3 @@
export 'src/helpers/asset_helper.dart';
export 'src/helpers/point_helper.dart';
export 'src/helpers/search_helper.dart';

View File

@ -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';

View File

@ -1,3 +0,0 @@
export 'helpers/asset_helper.dart';
export 'helpers/point_helper.dart';
export 'helpers/search_helper.dart';

View File

@ -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';

View File

@ -1,10 +1,11 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data'; 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 'package:freezed_annotation/freezed_annotation.dart';
import '../helpers/asset_helper.dart';
import 'data.dart';
part 'asset.freezed.dart'; part 'asset.freezed.dart';
part 'asset.g.dart'; part 'asset.g.dart';

View File

@ -174,41 +174,95 @@ class DocumentBloc extends ReplayBloc<DocumentEvent, DocumentState> {
null); null);
} }
}, transformer: sequential()); }, transformer: sequential());
on<ElementsRemoved>((event, emit) async { on<ElementsArranged>((event, emit) async {
if (state is DocumentLoadSuccess) { final current = state;
final current = state as DocumentLoadSuccess; if (current is! DocumentLoadSuccess) return;
if (!(current.embedding?.editable ?? true)) return; final renderers = await Future.wait(event.elements.map((e) async {
if (event.elements.isEmpty || final renderer = Renderer.fromInstance(e);
!current.page.content await renderer.setup(current.data, current.assetService, current.page);
.any((element) => event.elements.contains(element))) return; return renderer;
final page = current.page; }).toList());
final renderers = current.renderers; var content = List<PadElement>.from(current.page.content);
current.currentIndexCubit.unbake( final transform = current.transformCubit.state;
unbakedElements: renderers.where((element) { for (var renderer in renderers) {
final remaining = !event.elements.contains( final index = content.indexOf(renderer.element);
element.element, if (index == -1) {
); content.add(renderer.element);
if (!remaining) element.dispose(); continue;
return remaining; }
}).toList(), content.removeAt(index);
); var newIndex = index;
final newPage = page.copyWith( if (event.arrangement == Arrangement.front) {
content: List.from(page.content) newIndex = content.length - 1;
..removeWhere((element) => event.elements.contains(element))); } else if (event.arrangement == Arrangement.back) {
// Remove unused assets newIndex = 0;
final unusedAssets = <String>{}; } else {
event.elements.whereType<SourcedElement>().forEach((element) { final rect = renderer.rect;
final uri = Uri.tryParse(element.source); if (rect != null) {
if (uri?.scheme == '' && !newPage.usesSource(element.source)) { final hits = (await rayCastRect(rect, this, transform))
unusedAssets.add(element.source); .map((e) => e.element)
} .toList();
}); final hitIndex = hits.indexOf(renderer.element);
for (var asset in unusedAssets) { if (hitIndex != -1) {
current.data.removeAsset(asset); 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<ElementsRemoved>((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 = <String>{};
event.elements.whereType<SourcedElement>().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()); }, transformer: sequential());
on<DocumentDescriptorChanged>((event, emit) async { on<DocumentDescriptorChanged>((event, emit) async {
if (state is DocumentLoadSuccess) { if (state is DocumentLoadSuccess) {

View File

@ -64,15 +64,15 @@ class ElementsRemoved extends DocumentEvent {
List<Object?> get props => [elements]; List<Object?> get props => [elements];
} }
enum Arangement { forward, backward, front, back } enum Arrangement { forward, backward, front, back }
class ElementsArranged extends DocumentEvent { class ElementsArranged extends DocumentEvent {
final List<PadElement> elements; final List<PadElement> elements;
final Arangement arangement; final Arrangement arrangement;
const ElementsArranged(this.elements, this.arangement); const ElementsArranged(this.elements, this.arrangement);
@override @override
List<Object?> get props => [elements, arangement]; List<Object?> get props => [elements, arrangement];
} }
class DocumentDescriptorChanged extends DocumentEvent { class DocumentDescriptorChanged extends DocumentEvent {

View File

@ -450,6 +450,12 @@ class CurrentIndexCubit extends Cubit<CurrentIndex> {
Future<void> loadElements( Future<void> loadElements(
NoteData document, AssetService assetService, DocumentPage page) async { 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 = final renderers =
page.content.map((e) => Renderer.fromInstance(e)).toList(); page.content.map((e) => Renderer.fromInstance(e)).toList();
await Future.wait(renderers await Future.wait(renderers

View File

@ -75,7 +75,7 @@ class ElementsDialog extends StatelessWidget {
SubmenuButton( SubmenuButton(
leadingIcon: const Icon(PhosphorIconsLight.layout), leadingIcon: const Icon(PhosphorIconsLight.layout),
menuStyle: const MenuStyle(alignment: Alignment.centerRight), menuStyle: const MenuStyle(alignment: Alignment.centerRight),
menuChildren: Arangement.values menuChildren: Arrangement.values
.map((e) => MenuItemButton( .map((e) => MenuItemButton(
leadingIcon: Icon(e.icon(PhosphorIconsStyle.light)), leadingIcon: Icon(e.icon(PhosphorIconsStyle.light)),
child: Text(e.getLocalizedName(context)), child: Text(e.getLocalizedName(context)),

View File

@ -40,7 +40,8 @@ class EraserHandler extends Handler<EraserPainter> {
if (!_currentlyErasing) { if (!_currentlyErasing) {
_currentlyErasing = true; _currentlyErasing = true;
// Raycast // Raycast
final ray = await rayCast(globalPos, context.buildContext, size); final ray = await rayCast(globalPos, context.getDocumentBloc(),
context.getCameraTransform(), size);
final newElements = ray final newElements = ray
.map((e) => e.element) .map((e) => e.element)
.whereType<PenElement>() .whereType<PenElement>()

View File

@ -285,7 +285,8 @@ class HandHandler extends Handler<HandPainter> {
} }
final settings = context.getSettings(); final settings = context.getSettings();
final radius = settings.selectSensitivity / transform.size; 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 (hits.isEmpty) {
if (!context.isCtrlPressed) { if (!context.isCtrlPressed) {
_selected.clear(); _selected.clear();
@ -336,7 +337,8 @@ class HandHandler extends Handler<HandPainter> {
return; return;
} }
final position = context.getCameraTransform().localToGlobal(localPosition); 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 hit = hits.firstOrNull;
final rect = hit?.rect; final rect = hit?.rect;
if ((rect != null && !(getSelectionRect()?.contains(position) ?? false)) && if ((rect != null && !(getSelectionRect()?.contains(position) ?? false)) &&
@ -470,7 +472,8 @@ class HandHandler extends Handler<HandPainter> {
if (!context.isCtrlPressed) { if (!context.isCtrlPressed) {
_selected.clear(); _selected.clear();
} }
final hits = await rayCastRect(freeSelection, context.buildContext); final hits = await rayCastRect(freeSelection, context.getDocumentBloc(),
context.getCameraTransform());
_selected.addAll(hits); _selected.addAll(hits);
context.refresh(); context.refresh();
} }

View File

@ -267,21 +267,22 @@ class _RayCastParams {
Future<Set<Renderer<PadElement>>> rayCast( Future<Set<Renderer<PadElement>>> rayCast(
Offset globalPosition, Offset globalPosition,
BuildContext context, DocumentBloc bloc,
CameraTransform transform,
double radius, double radius,
) async { ) async {
return rayCastRect( return rayCastRect(
Rect.fromCircle(center: globalPosition, radius: radius), Rect.fromCircle(center: globalPosition, radius: radius),
context, bloc,
transform,
); );
} }
Future<Set<Renderer<PadElement>>> rayCastRect( Future<Set<Renderer<PadElement>>> rayCastRect(
Rect rect, Rect rect,
BuildContext context, DocumentBloc bloc,
CameraTransform transform,
) async { ) async {
final bloc = context.read<DocumentBloc>();
final transform = context.read<TransformCubit>().state;
final state = bloc.state; final state = bloc.state;
if (state is! DocumentLoadSuccess) return {}; if (state is! DocumentLoadSuccess) return {};
final renderers = state.cameraViewport.visibleElements; final renderers = state.cameraViewport.visibleElements;

View File

@ -9,8 +9,11 @@ class LayerHandler extends Handler<LayerPainter> {
final transform = context.getCameraTransform(); final transform = context.getCameraTransform();
final state = context.getState(); final state = context.getState();
if (state == null) return; if (state == null) return;
final hits = await rayCast(transform.localToGlobal(event.localPosition), final hits = await rayCast(
context.buildContext, data.strokeWidth / transform.size); transform.localToGlobal(event.localPosition),
context.getDocumentBloc(),
context.getCameraTransform(),
data.strokeWidth / transform.size);
context.addDocumentEvent(ElementsLayerChanged( context.addDocumentEvent(ElementsLayerChanged(
state.currentLayer, hits.map((e) => e.element).toList())); state.currentLayer, hits.map((e) => e.element).toList()));
} }

View File

@ -12,7 +12,8 @@ class PathEraserHandler extends Handler<PathEraserPainter> {
_removeRunning = true; _removeRunning = true;
final hits = await rayCast( final hits = await rayCast(
transform.localToGlobal(event.localPosition), transform.localToGlobal(event.localPosition),
context.buildContext, context.getDocumentBloc(),
context.getCameraTransform(),
data.strokeWidth / transform.size, data.strokeWidth / transform.size,
); );
context context

View File

@ -4,30 +4,30 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:material_leap/material_leap.dart'; import 'package:material_leap/material_leap.dart';
import 'package:phosphor_flutter/phosphor_flutter.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart';
extension ArangementVisualizer on Arangement { extension ArangementVisualizer on Arrangement {
String getLocalizedName(BuildContext context) { String getLocalizedName(BuildContext context) {
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
switch (this) { switch (this) {
case Arangement.back: case Arrangement.back:
return loc.sendToBack; return loc.sendToBack;
case Arangement.front: case Arrangement.front:
return loc.bringToFront; return loc.bringToFront;
case Arangement.backward: case Arrangement.backward:
return loc.sendBackward; return loc.sendBackward;
case Arangement.forward: case Arrangement.forward:
return loc.bringForward; return loc.bringForward;
} }
} }
IconGetter get icon { IconGetter get icon {
switch (this) { switch (this) {
case Arangement.back: case Arrangement.back:
return PhosphorIcons.arrowDown; return PhosphorIcons.arrowDown;
case Arangement.front: case Arrangement.front:
return PhosphorIcons.arrowUp; return PhosphorIcons.arrowUp;
case Arangement.backward: case Arrangement.backward:
return PhosphorIcons.arrowDownLeft; return PhosphorIcons.arrowDownLeft;
case Arangement.forward: case Arrangement.forward:
return PhosphorIcons.arrowUpRight; return PhosphorIcons.arrowUpRight;
} }
} }