mirror of
https://github.com/LinwoodDev/Butterfly.git
synced 2025-08-14 09:26:29 +08:00
Implement arrangement
This commit is contained in:
@ -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.
|
||||||
|
3
api/lib/butterfly_helpers.dart
Normal file
3
api/lib/butterfly_helpers.dart
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export 'src/helpers/asset_helper.dart';
|
||||||
|
export 'src/helpers/point_helper.dart';
|
||||||
|
export 'src/helpers/search_helper.dart';
|
22
api/lib/butterfly_models.dart
Normal file
22
api/lib/butterfly_models.dart
Normal 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';
|
@ -1,3 +0,0 @@
|
|||||||
export 'helpers/asset_helper.dart';
|
|
||||||
export 'helpers/point_helper.dart';
|
|
||||||
export 'helpers/search_helper.dart';
|
|
@ -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';
|
|
@ -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';
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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)),
|
||||||
|
@ -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>()
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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()));
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user