Skip to content

Commit 64a298a

Browse files
0HyperCubeKeavon
authored andcommitted
Migrate to using MoveSelectedLayersTo (#469)
* migrate to using MoveSelectedLayersTo * Fix dragging a selected layer with multiple selected layers * Fix CreatedLayer overriding selection * Fix MoveSelectedLayersTo behaviour * Squashed commit of the following: commit 095d577 Author: Keavon Chambers <[email protected]> Date: Mon Jan 10 18:06:12 2022 -0800 Fix NumberInput clamping regression with undefined bounds commit 9f54a37 Author: mfish33 <[email protected]> Date: Sun Jan 9 15:52:55 2022 -0800 Fix bounds with artboards for zoom-to-fit and scrollbar scaling (#473) * - document load keeps postition - zoom to fit - scrollbars use artboard dimensions * - review comments - svg export uses all artboard bounds Co-authored-by: Keavon Chambers <[email protected]> commit 61432de Author: 0HyperCube <[email protected]> Date: Sat Jan 8 21:06:15 2022 +0000 Fix rotation input (#472) * Fix insert with no nesting at end of panel * Deselect other layers on paste * Resolve logging
1 parent df99771 commit 64a298a

File tree

4 files changed

+83
-104
lines changed

4 files changed

+83
-104
lines changed

editor/src/document/document_file.rs

Lines changed: 27 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,6 @@ pub enum DocumentMessage {
174174
insert_index: isize,
175175
},
176176
ReorderSelectedLayers(i32), // relative_position,
177-
MoveLayerInTree {
178-
layer: Vec<LayerId>,
179-
insert_above: bool,
180-
neighbor: Vec<LayerId>,
181-
},
182177
SetSnapping(bool),
183178
ZoomCanvasToFitAll,
184179
}
@@ -537,6 +532,16 @@ impl DocumentMessageHandler {
537532
Some(layer_panel_entry(layer_metadata, transform, layer, path.to_vec()))
538533
}
539534

535+
/// When working with an insert index, deleting the layers may cause the insert index to point to a different location (if the layer being deleted was located before the insert index).
536+
///
537+
/// This function updates the insert index so that it points to the same place after the specified `layers` are deleted.
538+
fn update_insert_index<'a>(&self, layers: &[&'a [LayerId]], path: &[LayerId], insert_index: isize) -> Result<isize, DocumentError> {
539+
let folder = self.graphene_document.folder(path)?;
540+
let layer_ids_above = if insert_index < 0 { &folder.layer_ids } else { &folder.layer_ids[..(insert_index as usize)] };
541+
542+
Ok(insert_index - layer_ids_above.iter().filter(|layer_id| layers.iter().any(|x| *x == [path, &[**layer_id]].concat())).count() as isize)
543+
}
544+
540545
pub fn document_bounds(&self) -> Option<[DVec2; 2]> {
541546
if self.artboard_message_handler.is_infinite_canvas() {
542547
self.graphene_document.viewport_bounding_box(&[]).ok().flatten()
@@ -842,10 +847,14 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
842847
}
843848
DocumentResponse::LayerChanged { path } => responses.push_back(LayerChanged(path.clone()).into()),
844849
DocumentResponse::CreatedLayer { path } => {
850+
if self.layer_metadata.contains_key(path) {
851+
log::warn!("CreatedLayer overrides existing layer metadata.");
852+
}
845853
self.layer_metadata.insert(path.clone(), LayerMetadata::new(false));
854+
846855
responses.push_back(LayerChanged(path.clone()).into());
847856
self.layer_range_selection_reference = path.clone();
848-
responses.push_back(SetSelectedLayers(vec![path.clone()]).into());
857+
responses.push_back(AddSelectedLayers(vec![path.clone()]).into());
849858
}
850859
DocumentResponse::DocumentChanged => responses.push_back(RenderDocument.into()),
851860
};
@@ -923,6 +932,13 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
923932
responses.push_back(ToolMessage::DocumentIsDirty.into());
924933
}
925934
MoveSelectedLayersTo { path, insert_index } => {
935+
let layers = self.selected_layers().collect::<Vec<_>>();
936+
937+
// Trying to insert into self.
938+
if layers.iter().any(|layer| path.starts_with(layer)) {
939+
return;
940+
}
941+
let insert_index = self.update_insert_index(&layers, &path, insert_index).unwrap();
926942
responses.push_back(DocumentsMessage::Copy(Clipboard::System).into());
927943
responses.push_back(DocumentMessage::DeleteSelectedLayers.into());
928944
responses.push_back(
@@ -954,15 +970,11 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
954970
if let Some(insert_path) = insert {
955971
let (id, path) = insert_path.split_last().expect("Can't move the root folder");
956972
if let Some(folder) = self.graphene_document.layer(path).ok().and_then(|layer| layer.as_folder().ok()) {
957-
let selected: Vec<_> = selected_layers
958-
.iter()
959-
.filter(|layer| layer.starts_with(path) && layer.len() == path.len() + 1)
960-
.map(|x| x.last().unwrap())
961-
.collect();
962-
let non_selected: Vec<_> = folder.layer_ids.iter().filter(|id| selected.iter().all(|x| x != id)).collect();
963-
let offset = if relative_position < 0 || non_selected.is_empty() { 0 } else { 1 };
964-
let fallback = offset * (non_selected.len());
965-
let insert_index = non_selected.iter().position(|x| *x == id).map(|x| x + offset).unwrap_or(fallback) as isize;
973+
let layer_index = folder.layer_ids.iter().position(|comparison_id| comparison_id == id).unwrap() as isize;
974+
975+
// If moving down, insert below this layer, if moving up, insert above this layer
976+
let insert_index = if relative_position < 0 { layer_index } else { layer_index + 1 };
977+
966978
responses.push_back(DocumentMessage::MoveSelectedLayersTo { path: path.to_vec(), insert_index }.into());
967979
}
968980
}
@@ -1029,42 +1041,6 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
10291041
}
10301042
}
10311043
RenameLayer(path, name) => responses.push_back(DocumentOperation::RenameLayer { path, name }.into()),
1032-
MoveLayerInTree {
1033-
layer: target_layer,
1034-
insert_above,
1035-
neighbor,
1036-
} => {
1037-
let neighbor_id = neighbor.last().expect("Tried to move next to root");
1038-
let neighbor_path = &neighbor[..neighbor.len() - 1];
1039-
1040-
if !neighbor.starts_with(&target_layer) {
1041-
let containing_folder = self.graphene_document.folder(neighbor_path).expect("Neighbor does not exist");
1042-
let neighbor_index = containing_folder.position_of_layer(*neighbor_id).expect("Neighbor layer does not exist");
1043-
1044-
let layer = self.graphene_document.layer(&target_layer).expect("Layer moving does not exist.").to_owned();
1045-
let destination_path = [neighbor_path.to_vec(), vec![generate_uuid()]].concat();
1046-
let insert_index = if insert_above { neighbor_index } else { neighbor_index + 1 } as isize;
1047-
1048-
responses.push_back(DocumentMessage::StartTransaction.into());
1049-
responses.push_back(
1050-
DocumentOperation::InsertLayer {
1051-
layer,
1052-
destination_path: destination_path.clone(),
1053-
insert_index,
1054-
}
1055-
.into(),
1056-
);
1057-
responses.push_back(
1058-
DocumentMessage::UpdateLayerMetadata {
1059-
layer_path: destination_path,
1060-
layer_metadata: *self.layer_metadata(&target_layer),
1061-
}
1062-
.into(),
1063-
);
1064-
responses.push_back(DocumentOperation::DeleteLayer { path: target_layer }.into());
1065-
responses.push_back(DocumentMessage::CommitTransaction.into());
1066-
}
1067-
}
10681044
SetSnapping(new_status) => {
10691045
self.snapping_enabled = new_status;
10701046
}
@@ -1094,7 +1070,6 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
10941070
SaveDocument,
10951071
SetSnapping,
10961072
DebugPrintDocument,
1097-
MoveLayerInTree,
10981073
ZoomCanvasToFitAll,
10991074
);
11001075

editor/src/document/document_message_handler.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ impl MessageHandler<DocumentsMessage, &InputPreprocessor> for DocumentsMessageHa
379379
.graphene_document
380380
.shallowest_common_folder(document.selected_layers())
381381
.expect("While pasting, the selected layers did not exist while attempting to find the appropriate folder path for insertion");
382+
responses.push_back(DeselectAllLayers.into());
382383
responses.push_back(StartTransaction.into());
383384
responses.push_back(
384385
PasteIntoFolder {

frontend/src/components/panels/LayerTree.vue

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
</PopoverButton>
3030
</LayoutRow>
3131
<LayoutRow :class="'layer-tree scrollable-y'">
32-
<LayoutCol :class="'list'" ref="layerTreeList" @click="() => deselectAllLayers()" @dragover="updateInsertLine($event)" @dragend="drop()">
33-
<div class="layer-row" v-for="(layer, index) in layers" :key="String(layer.path.slice(-1))">
32+
<LayoutCol :class="'list'" ref="layerTreeList" @click="() => deselectAllLayers()" @dragover="updateInsertLine($event)" @dragend="drop($event)">
33+
<div class="layer-row" v-for="({ entry: layer }, index) in layers" :key="String(layer.path.slice(-1))">
3434
<div class="visibility">
3535
<IconButton
3636
:action="(e) => (toggleLayerVisibility(layer.path), e && e.stopPropagation())"
@@ -307,12 +307,12 @@ export default defineComponent({
307307
opacityNumberInputDisabled: true,
308308
// TODO: replace with BigUint64Array as index
309309
layerCache: new Map() as Map<string, LayerPanelEntry>,
310-
layers: [] as LayerPanelEntry[],
310+
layers: [] as { folderIndex: number; entry: LayerPanelEntry }[],
311311
layerDepths: [] as number[],
312312
selectionRangeStartLayer: undefined as undefined | LayerPanelEntry,
313313
selectionRangeEndLayer: undefined as undefined | LayerPanelEntry,
314314
opacity: 100,
315-
draggingData: undefined as undefined | { path: BigUint64Array; above: boolean; nearestPath: BigUint64Array; insertLine: HTMLDivElement },
315+
draggingData: undefined as undefined | { insertFolder: BigUint64Array; insertIndex: number; insertLine: HTMLDivElement },
316316
};
317317
},
318318
methods: {
@@ -343,63 +343,64 @@ export default defineComponent({
343343
},
344344
async clearSelection() {
345345
this.layers.forEach((layer) => {
346-
layer.layer_metadata.selected = false;
346+
layer.entry.layer_metadata.selected = false;
347347
});
348348
},
349-
closest(tree: HTMLElement, clientY: number): [BigUint64Array, boolean, Node] {
349+
closest(tree: HTMLElement, clientY: number): { insertFolder: BigUint64Array; insertIndex: number; insertAboveNode: Node } {
350350
const treeChildren = tree.children;
351351
352352
// Closest distance to the middle of the row along the Y axis
353353
let closest = Infinity;
354354
355355
// The nearest row parent (element of the tree)
356-
let nearestElement = tree.lastChild as Node;
356+
let insertAboveNode = tree.lastChild as Node;
357357
358-
// The nearest element in the path to the mouse
359-
let nearestPath = new BigUint64Array();
358+
// Folder to insert into
359+
let insertFolder = new BigUint64Array();
360360
361-
// Item goes above or below the mouse
362-
let above = false;
361+
// Insert index
362+
let insertIndex = -1;
363363
364364
Array.from(treeChildren).forEach((treeChild) => {
365-
if (treeChild.childElementCount <= 2) return;
366-
367-
const child = treeChild.children[2] as HTMLElement;
365+
const layerComponents = treeChild.getElementsByClassName("layer");
366+
if (layerComponents.length !== 1) return;
367+
const child = layerComponents[0];
368368
369369
const indexAttribute = child.getAttribute("data-index");
370370
if (!indexAttribute) return;
371-
const layer = this.layers[parseInt(indexAttribute, 10)];
371+
const { folderIndex, entry: layer } = this.layers[parseInt(indexAttribute, 10)];
372372
373373
const rect = child.getBoundingClientRect();
374374
const position = rect.top + rect.height / 2;
375375
const distance = position - clientY;
376376
377377
// Inserting above current row
378378
if (distance > 0 && distance < closest) {
379+
insertAboveNode = treeChild;
380+
insertFolder = layer.path.slice(0, layer.path.length - 1);
381+
insertIndex = folderIndex;
379382
closest = distance;
380-
nearestPath = layer.path;
381-
above = true;
382-
if (child.parentNode) {
383-
nearestElement = child.parentNode;
384-
}
385383
}
386384
// Inserting below current row
387-
else if (distance > -closest && distance > -RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT && distance < 0 && layer.layer_type !== "Folder") {
388-
closest = -distance;
389-
nearestPath = layer.path;
385+
else if (distance > -closest && distance > -RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT && distance < 0) {
390386
if (child.parentNode && child.parentNode.nextSibling) {
391-
nearestElement = child.parentNode.nextSibling;
387+
insertAboveNode = child.parentNode.nextSibling;
392388
}
389+
insertFolder = layer.layer_type === "Folder" ? layer.path : layer.path.slice(0, layer.path.length - 1);
390+
insertIndex = layer.layer_type === "Folder" ? 0 : folderIndex + 1;
391+
closest = -distance;
393392
}
394393
// Inserting with no nesting at the end of the panel
395-
else if (closest === Infinity) {
396-
nearestPath = layer.path.slice(0, 1);
394+
else if (closest === Infinity && layer.path.length === 1) {
395+
insertIndex = folderIndex + 1;
397396
}
398397
});
399398
400-
return [nearestPath, above, nearestElement];
399+
return { insertFolder, insertIndex, insertAboveNode };
401400
},
402401
async dragStart(event: DragEvent, layer: LayerPanelEntry) {
402+
if (!layer.layer_metadata.selected) this.selectLayer(layer, event.ctrlKey, event.shiftKey);
403+
403404
// Set style of cursor for drag
404405
if (event.dataTransfer) {
405406
event.dataTransfer.dropEffect = "move";
@@ -413,31 +414,30 @@ export default defineComponent({
413414
insertLine.classList.add("insert-mark");
414415
tree.appendChild(insertLine);
415416
416-
const [nearestPath, above, nearestElement] = this.closest(tree, event.clientY);
417+
const { insertFolder, insertIndex, insertAboveNode } = this.closest(tree, event.clientY);
417418
418419
// Set the initial state of the insert line
419-
if (nearestElement.parentNode) {
420-
insertLine.style.marginLeft = `${LAYER_LEFT_MARGIN_OFFSET + LAYER_LEFT_INDENT_OFFSET * nearestPath.length}px`; // TODO: use layerIndent function to calculate this
421-
tree.insertBefore(insertLine, nearestElement);
420+
if (insertAboveNode.parentNode) {
421+
insertLine.style.marginLeft = `${LAYER_LEFT_MARGIN_OFFSET + LAYER_LEFT_INDENT_OFFSET * (insertFolder.length + 1)}px`; // TODO: use layerIndent function to calculate this
422+
tree.insertBefore(insertLine, insertAboveNode);
422423
}
423424
424-
this.draggingData = { path: layer.path, above, nearestPath, insertLine };
425+
this.draggingData = { insertFolder, insertIndex, insertLine };
425426
},
426427
updateInsertLine(event: DragEvent) {
427428
// Stop the drag from being shown as cancelled
428429
event.preventDefault();
429430
430431
const tree = (this.$refs.layerTreeList as typeof LayoutCol).$el as HTMLElement;
431-
432-
const [nearestPath, above, nearestElement] = this.closest(tree, event.clientY);
432+
const { insertFolder, insertIndex, insertAboveNode } = this.closest(tree, event.clientY);
433433
434434
if (this.draggingData) {
435-
this.draggingData.nearestPath = nearestPath;
436-
this.draggingData.above = above;
435+
this.draggingData.insertFolder = insertFolder;
436+
this.draggingData.insertIndex = insertIndex;
437437
438-
if (nearestElement.parentNode) {
439-
this.draggingData.insertLine.style.marginLeft = `${LAYER_LEFT_MARGIN_OFFSET + LAYER_LEFT_INDENT_OFFSET * nearestPath.length}px`;
440-
tree.insertBefore(this.draggingData.insertLine, nearestElement);
438+
if (insertAboveNode.parentNode) {
439+
this.draggingData.insertLine.style.marginLeft = `${LAYER_LEFT_MARGIN_OFFSET + LAYER_LEFT_INDENT_OFFSET * (insertFolder.length + 1)}px`;
440+
tree.insertBefore(this.draggingData.insertLine, insertAboveNode);
441441
}
442442
}
443443
},
@@ -448,12 +448,15 @@ export default defineComponent({
448448
},
449449
async drop() {
450450
this.removeLine();
451+
451452
if (this.draggingData) {
452-
this.editor.instance.move_layer_in_tree(this.draggingData.path, this.draggingData.above, this.draggingData.nearestPath);
453+
const { insertFolder, insertIndex } = this.draggingData;
454+
455+
this.editor.instance.move_layer_in_tree(insertFolder, insertIndex);
453456
}
454457
},
455458
setBlendModeForSelectedLayers() {
456-
const selected = this.layers.filter((layer) => layer.layer_metadata.selected);
459+
const selected = this.layers.filter((layer) => layer.entry.layer_metadata.selected);
457460
458461
if (selected.length < 1) {
459462
this.blendModeSelectedIndex = 0;
@@ -462,8 +465,8 @@ export default defineComponent({
462465
}
463466
this.blendModeDropdownDisabled = false;
464467
465-
const firstEncounteredBlendMode = selected[0].blend_mode;
466-
const allBlendModesAlike = !selected.find((layer) => layer.blend_mode !== firstEncounteredBlendMode);
468+
const firstEncounteredBlendMode = selected[0].entry.blend_mode;
469+
const allBlendModesAlike = !selected.find((layer) => layer.entry.blend_mode !== firstEncounteredBlendMode);
467470
468471
if (allBlendModesAlike) {
469472
this.blendModeSelectedIndex = this.blendModeEntries.flat().findIndex((entry) => entry.value === firstEncounteredBlendMode);
@@ -474,7 +477,7 @@ export default defineComponent({
474477
},
475478
setOpacityForSelectedLayers() {
476479
// todo figure out why this is here
477-
const selected = this.layers.filter((layer) => layer.layer_metadata.selected);
480+
const selected = this.layers.filter((layer) => layer.entry.layer_metadata.selected);
478481
479482
if (selected.length < 1) {
480483
this.opacity = 100;
@@ -483,8 +486,8 @@ export default defineComponent({
483486
}
484487
this.opacityNumberInputDisabled = false;
485488
486-
const firstEncounteredOpacity = selected[0].opacity;
487-
const allOpacitiesAlike = !selected.find((layer) => layer.opacity !== firstEncounteredOpacity);
489+
const firstEncounteredOpacity = selected[0].entry.opacity;
490+
const allOpacitiesAlike = !selected.find((layer) => layer.entry.opacity !== firstEncounteredOpacity);
488491
489492
if (allOpacitiesAlike) {
490493
this.opacity = firstEncounteredOpacity;
@@ -497,14 +500,14 @@ export default defineComponent({
497500
mounted() {
498501
this.editor.dispatcher.subscribeJsMessage(DisplayFolderTreeStructure, (displayFolderTreeStructure) => {
499502
const path = [] as bigint[];
500-
this.layers = [] as LayerPanelEntry[];
503+
this.layers = [] as { folderIndex: number; entry: LayerPanelEntry }[];
501504
502-
const recurse = (folder: DisplayFolderTreeStructure, layers: LayerPanelEntry[], cache: Map<string, LayerPanelEntry>): void => {
503-
folder.children.forEach((item) => {
505+
const recurse = (folder: DisplayFolderTreeStructure, layers: { folderIndex: number; entry: LayerPanelEntry }[], cache: Map<string, LayerPanelEntry>): void => {
506+
folder.children.forEach((item, index) => {
504507
// TODO: fix toString
505508
path.push(BigInt(item.layerId.toString()));
506509
const mapping = cache.get(path.toString());
507-
if (mapping) layers.push(mapping);
510+
if (mapping) layers.push({ folderIndex: index, entry: mapping });
508511
if (item.children.length >= 1) recurse(item, layers, cache);
509512
path.pop();
510513
});

frontend/wasm/src/api.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,8 +383,8 @@ impl JsEditorHandle {
383383
}
384384

385385
/// Move a layer to be next to the specified neighbor
386-
pub fn move_layer_in_tree(&self, layer: Vec<LayerId>, insert_above: bool, neighbor: Vec<LayerId>) {
387-
let message = DocumentMessage::MoveLayerInTree { layer, insert_above, neighbor };
386+
pub fn move_layer_in_tree(&self, path: Vec<LayerId>, insert_index: isize) {
387+
let message = DocumentMessage::MoveSelectedLayersTo { path, insert_index };
388388
self.dispatch(message);
389389
}
390390

0 commit comments

Comments
 (0)