diff --git a/frontend/src/components/panels/Layers.svelte b/frontend/src/components/panels/Layers.svelte index c65691fd67..8c24b94403 100644 --- a/frontend/src/components/panels/Layers.svelte +++ b/frontend/src/components/panels/Layers.svelte @@ -66,6 +66,11 @@ let justFinishedDrag = false; // Used to prevent click events after a drag let dragInPanel = false; + // Alt+Drag duplication + let altKeyPressedDuringDrag = false; + let originalLayersBeforeDuplication: bigint[] | undefined = undefined; + let duplicatedLayerIds: bigint[] | undefined = undefined; + // Interactive clipping let layerToClipUponClick: LayerListingInfo | undefined = undefined; let layerToClipAltKeyPressed = false; @@ -107,6 +112,8 @@ addEventListener("pointermove", draggingPointerMove); addEventListener("mousedown", draggingMouseDown); addEventListener("keydown", draggingKeyDown); + addEventListener("keyup", draggingKeyUp); + addEventListener("blur", () => internalDragState?.active && abortDrag()); addEventListener("pointermove", clippingHover); addEventListener("keydown", clippingKeyPress); @@ -124,6 +131,7 @@ removeEventListener("pointermove", draggingPointerMove); removeEventListener("mousedown", draggingMouseDown); removeEventListener("keydown", draggingKeyDown); + removeEventListener("keyup", draggingKeyUp); removeEventListener("pointermove", clippingHover); removeEventListener("keydown", clippingKeyPress); @@ -469,11 +477,43 @@ abortDrag(); } + async function startDuplicates() { + originalLayersBeforeDuplication = [...$nodeGraph.selected]; + editor.handle.duplicateSelectedLayers(); + + await tick(); + duplicatedLayerIds = [...$nodeGraph.selected]; + } + + function stopDuplicates() { + if (!originalLayersBeforeDuplication || !duplicatedLayerIds) return; + + duplicatedLayerIds.forEach(layerId => { + editor.handle.deleteNode(layerId); + }); + + editor.handle.deselectAllLayers(); + originalLayersBeforeDuplication.forEach((layerId, index) => { + const ctrl = index > 0; + editor.handle.selectLayer(layerId, ctrl, false); + }); + + originalLayersBeforeDuplication = undefined; + duplicatedLayerIds = undefined; + } + function abortDrag() { + if (altKeyPressedDuringDrag && originalLayersBeforeDuplication) { + stopDuplicates(); + } + internalDragState = undefined; draggingData = undefined; fakeHighlightOfNotYetSelectedLayerBeingDragged = undefined; dragInPanel = false; + altKeyPressedDuringDrag = false; + originalLayersBeforeDuplication = undefined; + duplicatedLayerIds = undefined; } function draggingMouseDown(e: MouseEvent) { @@ -489,6 +529,18 @@ justFinishedDrag = true; abortDrag(); } + + if (e.key === "Alt" && !altKeyPressedDuringDrag) { + altKeyPressedDuringDrag = true; + startDuplicates(); + } + } + + function draggingKeyUp(e: KeyboardEvent) { + if (e.key === "Alt" && altKeyPressedDuringDrag) { + altKeyPressedDuringDrag = false; + stopDuplicates(); + } } function fileDragOver(e: DragEvent) { diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 6d637f0d1b..b789200c2f 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -665,6 +665,13 @@ impl EditorHandle { self.dispatch(message); } + /// Duplicate the currently selected layers + #[wasm_bindgen(js_name = duplicateSelectedLayers)] + pub fn duplicate_selected_layers(&self) { + let message = DocumentMessage::DuplicateSelectedLayers; + self.dispatch(message); + } + /// Move a layer to within a folder and placed down at the given index. /// If the folder is `None`, it is inserted into the document root. /// If the insert index is `None`, it is inserted at the start of the folder.