Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add arrange #404

Merged
merged 2 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions api/lib/butterfly_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
3 changes: 3 additions & 0 deletions api/lib/butterfly_helpers.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export 'src/helpers/asset_helper.dart';
export 'src/helpers/point_helper.dart';
export 'src/helpers/search_helper.dart';
22 changes: 22 additions & 0 deletions api/lib/butterfly_models.dart
Original file line number Diff line number Diff line change
@@ -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';
3 changes: 0 additions & 3 deletions api/lib/src/butterfly_helpers.dart

This file was deleted.

22 changes: 0 additions & 22 deletions api/lib/src/butterfly_models.dart

This file was deleted.

5 changes: 3 additions & 2 deletions api/lib/src/models/asset.dart
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
118 changes: 86 additions & 32 deletions app/lib/bloc/document_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -174,41 +174,95 @@ class DocumentBloc extends ReplayBloc<DocumentEvent, DocumentState> {
null);
}
}, transformer: sequential());
on<ElementsRemoved>((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 = <String>{};
event.elements.whereType<SourcedElement>().forEach((element) {
final uri = Uri.tryParse(element.source);
if (uri?.scheme == '' && !newPage.usesSource(element.source)) {
unusedAssets.add(element.source);
on<ElementsArranged>((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<PadElement>.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;
}
}
}
});
for (var asset in unusedAssets) {
current.data.removeAsset(asset);
}

await _saveState(emit, current.copyWith(page: newPage), null);
if (newIndex >= 0) {
content.insert(newIndex, renderer.element);
} else {
content.add(renderer.element);
}
}
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());
on<DocumentDescriptorChanged>((event, emit) async {
if (state is DocumentLoadSuccess) {
Expand Down
11 changes: 11 additions & 0 deletions app/lib/bloc/document_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@ class ElementsRemoved extends DocumentEvent {
List<Object?> get props => [elements];
}

enum Arrangement { forward, backward, front, back }

class ElementsArranged extends DocumentEvent {
final List<PadElement> elements;
final Arrangement arrangement;

const ElementsArranged(this.elements, this.arrangement);
@override
List<Object?> get props => [elements, arrangement];
}

class DocumentDescriptorChanged extends DocumentEvent {
final String? name, description;

Expand Down
6 changes: 6 additions & 0 deletions app/lib/cubits/current_index.dart
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,12 @@ class CurrentIndexCubit extends Cubit<CurrentIndex> {

Future<void> 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
Expand Down
17 changes: 17 additions & 0 deletions app/lib/dialogs/elements.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:butterfly/cubits/current_index.dart';
import 'package:butterfly/handlers/handler.dart';
import 'package:butterfly/visualizer/event.dart';
import 'package:butterfly_api/butterfly_api.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
Expand Down Expand Up @@ -71,6 +72,22 @@ class ElementsDialog extends StatelessWidget {
leadingIcon: const PhosphorIcon(PhosphorIconsLight.trash),
child: Text(AppLocalizations.of(context).delete),
),
SubmenuButton(
leadingIcon: const Icon(PhosphorIconsLight.layout),
menuStyle: const MenuStyle(alignment: Alignment.centerRight),
menuChildren: Arrangement.values
.map((e) => MenuItemButton(
leadingIcon: Icon(e.icon(PhosphorIconsStyle.light)),
child: Text(e.getLocalizedName(context)),
onPressed: () {
Navigator.of(context).pop(true);
context.read<DocumentBloc>().add(ElementsArranged(
renderers.map((r) => r.element).toList(), e));
},
))
.toList(),
child: Text(AppLocalizations.of(context).arrange),
),
MenuItemButton(
onPressed: () {
Navigator.of(context).pop(true);
Expand Down
3 changes: 2 additions & 1 deletion app/lib/handlers/eraser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ class EraserHandler extends Handler<EraserPainter> {
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<PenElement>()
Expand Down
9 changes: 6 additions & 3 deletions app/lib/handlers/hand.dart
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,8 @@ class HandHandler extends Handler<HandPainter> {
}
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();
Expand Down Expand Up @@ -336,7 +337,8 @@ class HandHandler extends Handler<HandPainter> {
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)) &&
Expand Down Expand Up @@ -470,7 +472,8 @@ class HandHandler extends Handler<HandPainter> {
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();
}
Expand Down
11 changes: 6 additions & 5 deletions app/lib/handlers/handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -267,21 +267,22 @@ class _RayCastParams {

Future<Set<Renderer<PadElement>>> rayCast(
Offset globalPosition,
BuildContext context,
DocumentBloc bloc,
CameraTransform transform,
double radius,
) async {
return rayCastRect(
Rect.fromCircle(center: globalPosition, radius: radius),
context,
bloc,
transform,
);
}

Future<Set<Renderer<PadElement>>> rayCastRect(
Rect rect,
BuildContext context,
DocumentBloc bloc,
CameraTransform transform,
) async {
final bloc = context.read<DocumentBloc>();
final transform = context.read<TransformCubit>().state;
final state = bloc.state;
if (state is! DocumentLoadSuccess) return {};
final renderers = state.cameraViewport.visibleElements;
Expand Down
7 changes: 5 additions & 2 deletions app/lib/handlers/layer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ class LayerHandler extends Handler<LayerPainter> {
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()));
}
Expand Down
3 changes: 2 additions & 1 deletion app/lib/handlers/path_eraser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ class PathEraserHandler extends Handler<PathEraserPainter> {
_removeRunning = true;
final hits = await rayCast(
transform.localToGlobal(event.localPosition),
context.buildContext,
context.getDocumentBloc(),
context.getCameraTransform(),
data.strokeWidth / transform.size,
);
context
Expand Down
7 changes: 6 additions & 1 deletion app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -501,5 +501,10 @@
"version": "Version",
"repository": "Repository",
"pages": "Pages",
"navigator": "Navigator"
"navigator": "Navigator",
"arrange": "Arrange",
"bringToFront": "Bring to front",
"sendToBack": "Send to back",
"bringForward": "Bring forward",
"sendBackward": "Send backward"
}
Loading