Skip to content

Commit 8190c6f

Browse files
committed
Add safearea on toolbar, fix performance on import
1 parent fd6f925 commit 8190c6f

File tree

12 files changed

+189
-117
lines changed

12 files changed

+189
-117
lines changed

api/lib/src/helpers/import.dart

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import 'package:butterfly_api/butterfly_api.dart';
22

3-
(NoteData, List<PadElement>) importAssets(
3+
typedef ImportAssetOptions = (
44
NoteData data,
55
List<PadElement> elements, {
66
void Function(String source)? onInvalidate,
77
Map<String, String>? alreadyImported,
8-
}) {
9-
final imported = alreadyImported ?? {};
8+
});
9+
10+
(NoteData, List<PadElement>) importAssets(ImportAssetOptions options) {
11+
final imported = options.alreadyImported ?? {};
12+
var data = options.$1;
13+
var elements = options.$2;
1014
String importAsset(SourcedElement element, String fileExtension) {
1115
final source = element.source;
12-
onInvalidate?.call(source);
16+
options.onInvalidate?.call(source);
1317
if (imported.containsKey(source)) {
1418
return imported[source]!;
1519
}

api/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: butterfly_api
22
description: The Linwood Butterfly API
3-
version: 2.4.0
3+
version: 2.4.0-rc.4
44
publish_to: none
55

66
environment:

app/lib/bloc/document_bloc.dart

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:async';
22
import 'dart:convert';
3+
import 'dart:isolate';
34

45
import 'package:bloc_concurrency/bloc_concurrency.dart';
56
import 'package:butterfly/api/file_system.dart';
@@ -29,6 +30,46 @@ part 'document_state.dart';
2930

3031
enum ConnectionStatus { none, server, client }
3132

33+
typedef ImportAssetSyncOptions = (
34+
NoteData data,
35+
List<PadElement> elements, {
36+
SendPort? onInvalidate,
37+
Map<String, String>? alreadyImported,
38+
});
39+
(NoteData, List<PadElement>) _importAssetsSync(ImportAssetSyncOptions options) {
40+
return importAssets((
41+
options.$1,
42+
options.$2,
43+
onInvalidate: options.onInvalidate != null
44+
? (source) => options.onInvalidate!.send(source)
45+
: null,
46+
alreadyImported: options.alreadyImported,
47+
));
48+
}
49+
50+
Future<(NoteData, List<PadElement>)> importAssetsAsync(
51+
NoteData data,
52+
List<PadElement> elements, {
53+
void Function(String source)? onInvalidate,
54+
Map<String, String>? alreadyImported,
55+
}) {
56+
ReceivePort? port;
57+
if (onInvalidate != null) {
58+
port = ReceivePort();
59+
port.listen((message) {
60+
if (message is String) {
61+
onInvalidate(message);
62+
}
63+
});
64+
}
65+
return compute(_importAssetsSync, (
66+
data,
67+
elements,
68+
onInvalidate: port?.sendPort,
69+
alreadyImported: alreadyImported,
70+
));
71+
}
72+
3273
class DocumentBloc extends ReplayBloc<DocumentEvent, DocumentState> {
3374
DocumentBloc(
3475
ButterflyFileSystem fileSystem,
@@ -82,7 +123,7 @@ class DocumentBloc extends ReplayBloc<DocumentEvent, DocumentState> {
82123
);
83124

84125
void _init() {
85-
on<PagesAdded>((event, emit) {
126+
on<PagesAdded>((event, emit) async {
86127
final current = state;
87128
if (current is! DocumentLoadSuccess) return;
88129
var data = current.data;
@@ -92,16 +133,18 @@ class DocumentBloc extends ReplayBloc<DocumentEvent, DocumentState> {
92133
for (final details in event.pages) {
93134
page =
94135
details.page ?? DocumentPage(backgrounds: current.page.backgrounds);
95-
final layers = page.layers.map((layer) {
96-
List<PadElement> elements;
97-
(data, elements) = importAssets(
98-
data,
99-
layer.content,
100-
alreadyImported: imported,
101-
onInvalidate: current.assetService.invalidate,
102-
);
103-
return layer.copyWith(content: elements);
104-
}).toList();
136+
final layers = await Future.wait(
137+
page.layers.map((layer) async {
138+
List<PadElement> elements;
139+
(data, elements) = await importAssetsAsync(
140+
data,
141+
layer.content,
142+
alreadyImported: imported,
143+
onInvalidate: current.assetService.invalidate,
144+
);
145+
return layer.copyWith(content: elements);
146+
}),
147+
);
105148
page = page.copyWith(layers: layers);
106149
(data, pageName) = data.addPage(
107150
page,
@@ -165,12 +208,12 @@ class DocumentBloc extends ReplayBloc<DocumentEvent, DocumentState> {
165208
final newData = current.data.setThumbnail(event.data);
166209
_saveState(emit, state: current.copyWith(data: newData));
167210
});
168-
on<ElementsCreated>((event, emit) {
211+
on<ElementsCreated>((event, emit) async {
169212
final current = state;
170213
if (current is! DocumentLoadSuccess) return;
171214
if (!(current.embedding?.editable ?? true)) return;
172215
final assetService = current.assetService;
173-
final (data, elements) = importAssets(
216+
final (data, elements) = await importAssetsAsync(
174217
current.data,
175218
event.elements.map((e) => e.copyWith(id: createUniqueId())).toList(),
176219
onInvalidate: assetService.invalidate,

app/lib/cubits/current_index.dart

Lines changed: 57 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,40 @@ class CurrentIndexCubit extends Cubit<CurrentIndex> {
839839
Renderer? getRenderer(PadElement element) =>
840840
renderers.firstWhereOrNull((renderer) => renderer.element == element);
841841

842+
Rect getViewportRect({Size? viewportSize}) {
843+
var size = viewportSize ?? state.cameraViewport.toSize();
844+
845+
var transform = state.transformCubit.state;
846+
final resolution = state.settingsCubit.state.renderResolution;
847+
848+
final friction = transform.friction;
849+
final realWidth = size.width / transform.size;
850+
final realHeight = size.height / transform.size;
851+
var rect = Rect.fromLTWH(
852+
transform.position.dx,
853+
transform.position.dy,
854+
realWidth / resolution.multiplier,
855+
realHeight / resolution.multiplier,
856+
);
857+
if (friction != null) {
858+
final topLeft = Offset(
859+
min(transform.position.dx, friction.beginPosition.dx),
860+
min(transform.position.dy, friction.beginPosition.dy),
861+
);
862+
final bottomRight = Offset(
863+
max(transform.position.dx, friction.beginPosition.dx),
864+
max(transform.position.dy, friction.beginPosition.dy),
865+
).translate(size.width, size.height);
866+
transform = transform.withPosition(topLeft);
867+
rect = Rect.fromPoints(topLeft, bottomRight);
868+
size =
869+
Size(bottomRight.dx - topLeft.dx, bottomRight.dy - topLeft.dy) *
870+
transform.size;
871+
}
872+
rect = resolution.getRect(rect);
873+
return rect;
874+
}
875+
842876
bool _isBaking = false;
843877
Function? _queuedBake;
844878
Completer<void>? _bakeCompleter;
@@ -879,37 +913,13 @@ class CurrentIndexCubit extends Cubit<CurrentIndex> {
879913
size *= resolution.multiplier;
880914
}
881915
var transform = state.transformCubit.state;
882-
final realWidth = size.width / transform.size;
883-
final realHeight = size.height / transform.size;
884-
var rect = Rect.fromLTWH(
885-
transform.position.dx,
886-
transform.position.dy,
887-
realWidth / resolution.multiplier,
888-
realHeight / resolution.multiplier,
889-
);
890916
var renderers = List<Renderer<PadElement>>.from(
891917
cameraViewport.unbakedElements,
892918
);
893919
final recorder = ui.PictureRecorder();
894920
final canvas = ui.Canvas(recorder);
895-
final friction = transform.friction;
896-
if (friction != null) {
897-
final topLeft = Offset(
898-
min(transform.position.dx, friction.beginPosition.dx),
899-
min(transform.position.dy, friction.beginPosition.dy),
900-
);
901-
final bottomRight = Offset(
902-
max(transform.position.dx, friction.beginPosition.dx),
903-
max(transform.position.dy, friction.beginPosition.dy),
904-
).translate(size.width, size.height);
905-
transform = transform.withPosition(topLeft);
906-
rect = Rect.fromPoints(topLeft, bottomRight);
907-
size =
908-
Size(bottomRight.dx - topLeft.dx, bottomRight.dy - topLeft.dy) *
909-
transform.size;
910-
}
911-
final renderTransform = transform.improve(resolution, rect.size);
912-
rect = resolution.getRect(rect);
921+
final rect = getViewportRect(viewportSize: size);
922+
final renderTransform = transform.improve(resolution, rect);
913923
final document = blocState.data;
914924
final page = blocState.page;
915925
final info = blocState.info;
@@ -932,7 +942,7 @@ class CurrentIndexCubit extends Cubit<CurrentIndex> {
932942
if (reset) {
933943
renderers = List<Renderer<PadElement>>.from(this.renderers);
934944
visibleElements = renderers
935-
.where((renderer) => renderer.expandedRect?.overlaps(rect) ?? true)
945+
.where((renderer) => renderer.isVisible(rect))
936946
.toList();
937947
} else {
938948
visibleElements = List.from(oldVisible)..addAll(renderers);
@@ -1170,6 +1180,9 @@ class CurrentIndexCubit extends Cubit<CurrentIndex> {
11701180
}) async {
11711181
final newViewport = state.cameraViewport.unbake(
11721182
unbakedElements: unbakedElements,
1183+
visibleElements: unbakedElements
1184+
?.where((e) => e.isVisible(getViewportRect()))
1185+
.toList(),
11731186
backgrounds: backgrounds,
11741187
);
11751188
await _updateOnVisible(newViewport, blocState);
@@ -1242,11 +1255,19 @@ class CurrentIndexCubit extends Cubit<CurrentIndex> {
12421255
);
12431256
}
12441257

1245-
Future<void> withUnbaked(
1258+
Future<void> addUnbaked(
12461259
DocumentLoaded blocState,
1247-
List<Renderer<PadElement>> unbakedElements,
1248-
) async {
1249-
final newViewport = state.cameraViewport.withUnbaked(unbakedElements);
1260+
List<Renderer<PadElement>> unbakedElements, [
1261+
List<Renderer<PadElement>>? visibleElements,
1262+
]) async {
1263+
final rect = getViewportRect();
1264+
visibleElements ??= unbakedElements
1265+
.where((e) => e.isVisible(rect))
1266+
.toList();
1267+
final newViewport = state.cameraViewport.withUnbaked(
1268+
[...state.cameraViewport.unbakedElements, ...unbakedElements],
1269+
[...state.cameraViewport.visibleElements, ...visibleElements],
1270+
);
12501271
await _updateOnVisible(newViewport, blocState);
12511272
emit(state.copyWith(cameraViewport: newViewport));
12521273
}
@@ -1547,9 +1568,6 @@ class CurrentIndexCubit extends Cubit<CurrentIndex> {
15471568
bool updateIndex = false,
15481569
}) async {
15491570
final cameraViewport = current.cameraViewport;
1550-
final elements = List<Renderer<PadElement>>.from(
1551-
cameraViewport.unbakedElements,
1552-
);
15531571
for (var renderer in {
15541572
...?backgrounds,
15551573
...?replacedElements,
@@ -1562,7 +1580,6 @@ class CurrentIndexCubit extends Cubit<CurrentIndex> {
15621580
current.page,
15631581
);
15641582
}
1565-
elements.addAll(addedElements);
15661583
final blocState = bloc.state;
15671584
if (blocState is! DocumentLoadSuccess) return;
15681585

@@ -1577,7 +1594,11 @@ class CurrentIndexCubit extends Cubit<CurrentIndex> {
15771594
} else if (backgrounds != null) {
15781595
await this.unbake(blocState, backgrounds: backgrounds);
15791596
} else {
1580-
await withUnbaked(blocState, elements);
1597+
final elements = List<Renderer<PadElement>>.from(
1598+
cameraViewport.unbakedElements,
1599+
);
1600+
elements.addAll(addedElements);
1601+
await addUnbaked(blocState, elements);
15811602
}
15821603

15831604
setSaveState(saved: SaveState.unsaved);

app/lib/cubits/transform.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,8 @@ class CameraTransform extends Equatable {
117117
);
118118
}
119119

120-
CameraTransform improve(RenderResolution resolution, Size size) {
121-
final rect = resolution.getRect(position & size);
122-
return CameraTransform(pixelRatio, rect.topLeft, this.size, friction);
120+
CameraTransform improve(RenderResolution resolution, Rect rect) {
121+
return CameraTransform(pixelRatio, rect.topLeft, size, friction);
123122
}
124123
}
125124

app/lib/models/viewport.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,8 @@ class CameraViewport extends Equatable {
116116
}
117117

118118
CameraViewport withUnbaked(
119-
List<Renderer<PadElement>> unbakedElements, [
119+
List<Renderer<PadElement>> unbakedElements,
120+
List<Renderer<PadElement>> visibleElements, [
120121
List<Renderer<Background>>? backgrounds,
121122
]) => CameraViewport.baked(
122123
backgrounds: backgrounds ?? this.backgrounds,
@@ -127,8 +128,7 @@ class CameraViewport extends Equatable {
127128
unbakedElements: unbakedElements,
128129
bakedElements: bakedElements,
129130
pixelRatio: pixelRatio,
130-
visibleElements: List<Renderer<PadElement>>.from(visibleElements)
131-
..addAll(unbakedElements),
131+
visibleElements: visibleElements,
132132
x: x,
133133
y: y,
134134
aboveLayerImage: aboveLayerImage,

app/lib/renderers/renderer.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,8 @@ abstract class Renderer<T> {
300300

301301
Rect? get rect => null;
302302

303+
bool isVisible(Rect rect) => expandedRect?.overlaps(rect) ?? true;
304+
303305
Rect? get expandedRect {
304306
final current = rect;
305307
if (current == null) return null;

0 commit comments

Comments
 (0)