diff --git a/frontend-svelte/src/components/panels/NodeGraph.svelte b/frontend-svelte/src/components/panels/NodeGraph.svelte index 60855ba6..53444085 100644 --- a/frontend-svelte/src/components/panels/NodeGraph.svelte +++ b/frontend-svelte/src/components/panels/NodeGraph.svelte @@ -175,11 +175,21 @@ } function scroll(e: WheelEvent) { - const scrollX = e.deltaX; - const scrollY = e.deltaY; + const [scrollX, scrollY] = [e.deltaX, e.deltaY]; + + // If zoom with scroll is enabled: horizontal pan with Ctrl, vertical pan with Shift + const zoomWithScroll = $nodeGraph.zoomWithScroll; + const zoom = zoomWithScroll ? !e.ctrlKey && !e.shiftKey : e.ctrlKey; + const horizontalPan = zoomWithScroll ? e.ctrlKey : !e.ctrlKey && e.shiftKey; + + // Prevent the web page from being zoomed + if (e.ctrlKey) e.preventDefault(); + + // Always pan horizontally in response to a horizontal scroll wheel movement + transform.x -= scrollX / transform.scale; // Zoom - if (e.ctrlKey) { + if (zoom) { let zoomFactor = 1 + Math.abs(scrollY) * WHEEL_RATE; if (scrollY > 0) zoomFactor = 1 / zoomFactor; @@ -199,15 +209,14 @@ transform.x -= (deltaX / transform.scale) * zoomFactor; transform.y -= (deltaY / transform.scale) * zoomFactor; - // Prevent actually zooming into the page when pinch-zooming on laptop trackpads - e.preventDefault(); + return; } + // Pan - else if (!e.shiftKey) { - transform.x -= scrollX / transform.scale; - transform.y -= scrollY / transform.scale; - } else { + if (horizontalPan) { transform.x -= scrollY / transform.scale; + } else { + transform.y -= scrollY / transform.scale; } } @@ -218,13 +227,17 @@ } } - // TODO: Move the event listener from the graph to the window so dragging outside the graph area (or even the browser window) works + // TODO: Move the event listener from the graph to the window so dragging outside the graph area (or even the whole browser window) works function pointerDown(e: PointerEvent) { - // Exit the add node popup by clicking elsewhere in the graph - if (nodeListLocation && !(e.target as HTMLElement).closest("[data-node-list]")) nodeListLocation = undefined; + const [lmb, rmb] = [e.button === 0, e.button === 2]; - // Handle the add node popup on right click - if (e.button === 2) { + const port = (e.target as HTMLDivElement).closest("[data-port]") as HTMLDivElement; + const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined; + const nodeId = node?.getAttribute("data-node") || undefined; + const nodeList = (e.target as HTMLElement).closest("[data-node-list]") as HTMLElement | undefined; + + // Create the add node popup on right click, then exit + if (rmb) { const graphBounds = graph.div().getBoundingClientRect(); nodeListLocation = { x: Math.round(((e.clientX - graphBounds.x) / transform.scale - transform.x) / GRID_SIZE), @@ -239,20 +252,19 @@ return; } - const port = (e.target as HTMLDivElement).closest("[data-port]") as HTMLDivElement; - const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined; - const nodeId = node?.getAttribute("data-node") || undefined; - const nodeList = (e.target as HTMLElement).closest("[data-node-list]") as HTMLElement | undefined; - // If the user is clicking on the add nodes list, exit here - if (nodeList) return; + if (lmb && nodeList) return; - if (e.altKey && nodeId) { + // Since the user is clicking elsewhere in the graph, ensure the add nodes list is closed + if (lmb) nodeListLocation = undefined; + + // Alt-click sets the clicked node as previewed + if (lmb && e.altKey && nodeId) { editor.instance.togglePreview(BigInt(nodeId)); } // Clicked on a port dot - if (port && node) { + if (lmb && port && node) { const isOutput = Boolean(port.getAttribute("data-port") === "output"); if (isOutput) linkInProgressFromConnector = port; @@ -279,7 +291,7 @@ } // Clicked on a node - if (nodeId) { + if (lmb && nodeId) { let modifiedSelected = false; const id = BigInt(nodeId); @@ -306,11 +318,13 @@ } // Clicked on the graph background - panning = true; - if (selected.length !== 0) { + if (lmb && selected.length !== 0) { selected = []; - editor.instance.selectNodes(new BigUint64Array(selected)); + editor.instance.selectNodes(new BigUint64Array([])); } + + // LMB clicked on the graph background or MMB clicked anywhere + panning = true; } function doubleClick(e: MouseEvent) { diff --git a/frontend-svelte/src/state-providers/node-graph.ts b/frontend-svelte/src/state-providers/node-graph.ts index f76ce30b..4778651e 100644 --- a/frontend-svelte/src/state-providers/node-graph.ts +++ b/frontend-svelte/src/state-providers/node-graph.ts @@ -9,6 +9,7 @@ import { UpdateNodeGraph, UpdateNodeTypes, UpdateNodeGraphBarLayout, + UpdateZoomWithScroll, defaultWidgetLayout, patchWidgetLayout, } from "@/wasm-communication/messages"; @@ -20,6 +21,7 @@ export function createNodeGraphState(editor: Editor) { links: [] as FrontendNodeLink[], nodeTypes: [] as FrontendNodeType[], nodeGraphBarLayout: defaultWidgetLayout(), + zoomWithScroll: false as boolean, }); // Set up message subscriptions on creation @@ -42,6 +44,12 @@ export function createNodeGraphState(editor: Editor) { return state; }); }); + editor.subscriptions.subscribeJsMessage(UpdateZoomWithScroll, (updateZoomWithScroll) => { + update((state) => { + state.zoomWithScroll = updateZoomWithScroll.zoomWithScroll; + return state; + }); + }); return { subscribe, diff --git a/frontend/src/components/panels/NodeGraph.vue b/frontend/src/components/panels/NodeGraph.vue index 6a58e90d..e42d2efe 100644 --- a/frontend/src/components/panels/NodeGraph.vue +++ b/frontend/src/components/panels/NodeGraph.vue @@ -511,19 +511,18 @@ export default defineComponent({ return [pathString, dataType]; }, scroll(e: WheelEvent) { - const scrollX = e.deltaX; - const scrollY = e.deltaY; - const zoomWithScroll = this.nodeGraph.state.zoomWithScroll; + const [scrollX, scrollY] = [e.deltaX, e.deltaY]; - let zoom; - let horizontalPan; - if (zoomWithScroll) { - zoom = !(e.ctrlKey || e.shiftKey); - horizontalPan = e.ctrlKey; - } else { - zoom = e.ctrlKey; - horizontalPan = !(e.ctrlKey || e.shiftKey); - } + // If zoom with scroll is enabled: horizontal pan with Ctrl, vertical pan with Shift + const zoomWithScroll = this.nodeGraph.state.zoomWithScroll; + const zoom = zoomWithScroll ? !e.ctrlKey && !e.shiftKey : e.ctrlKey; + const horizontalPan = zoomWithScroll ? e.ctrlKey : !e.ctrlKey && e.shiftKey; + + // Prevent the web page from being zoomed + if (e.ctrlKey) e.preventDefault(); + + // Always pan horizontally in response to a horizontal scroll wheel movement + this.transform.x -= scrollX / this.transform.scale; // Zoom if (zoom) { @@ -548,14 +547,13 @@ export default defineComponent({ this.transform.x -= (deltaX / this.transform.scale) * zoomFactor; this.transform.y -= (deltaY / this.transform.scale) * zoomFactor; - // Prevent actually zooming into the page when pinch-zooming on laptop trackpads - e.preventDefault(); + return; } + // Pan - else if (horizontalPan) { + if (horizontalPan) { this.transform.x -= scrollY / this.transform.scale; } else { - this.transform.x -= scrollX / this.transform.scale; this.transform.y -= scrollY / this.transform.scale; } }, @@ -565,13 +563,19 @@ export default defineComponent({ document.removeEventListener("keydown", this.keydown); } }, - // TODO: Move the event listener from the graph to the window so dragging outside the graph area (or even the browser window) works + // TODO: Move the event listener from the graph to the window so dragging outside the graph area (or even the whole browser window) works pointerDown(e: PointerEvent) { - // Exit the add node popup by clicking elsewhere in the graph - if (this.nodeListLocation && !(e.target as HTMLElement).closest("[data-node-list]")) this.nodeListLocation = undefined; + const [lmb, rmb] = [e.button === 0, e.button === 2]; - // Handle the add node popup on right click - if (e.button === 2) { + const port = (e.target as HTMLDivElement).closest("[data-port]") as HTMLDivElement; + const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined; + const nodeId = node?.getAttribute("data-node") || undefined; + const nodeList = (e.target as HTMLElement).closest("[data-node-list]") as HTMLElement | undefined; + const containerBounds = this.$refs.nodesContainer as HTMLDivElement | undefined; + if (!containerBounds) return; + + // Create the add node popup on right click, then exit + if (rmb) { const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el; const graph = graphDiv?.getBoundingClientRect() || new DOMRect(); this.nodeListLocation = { @@ -583,22 +587,19 @@ export default defineComponent({ return; } - const port = (e.target as HTMLDivElement).closest("[data-port]") as HTMLDivElement; - const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined; - const nodeId = node?.getAttribute("data-node") || undefined; - const nodeList = (e.target as HTMLElement).closest("[data-node-list]") as HTMLElement | undefined; - const containerBounds = this.$refs.nodesContainer as HTMLDivElement | undefined; - if (!containerBounds) return; - // If the user is clicking on the add nodes list, exit here - if (nodeList) return; + if (lmb && nodeList) return; - if (e.altKey && nodeId) { + // Since the user is clicking elsewhere in the graph, ensure the add nodes list is closed + if (lmb) this.nodeListLocation = undefined; + + // Alt-click sets the clicked node as previewed + if (lmb && e.altKey && nodeId) { this.editor.instance.togglePreview(BigInt(nodeId)); } // Clicked on a port dot - if (port && node) { + if (lmb && port && node) { const isOutput = Boolean(port.getAttribute("data-port") === "output"); if (isOutput) this.linkInProgressFromConnector = port; @@ -625,7 +626,7 @@ export default defineComponent({ } // Clicked on a node - if (nodeId) { + if (lmb && nodeId) { let modifiedSelected = false; const id = BigInt(nodeId); @@ -652,11 +653,13 @@ export default defineComponent({ } // Clicked on the graph background - this.panning = true; - if (this.selected.length !== 0) { + if (lmb && this.selected.length !== 0) { this.selected = []; - this.editor.instance.selectNodes(new BigUint64Array(this.selected)); + this.editor.instance.selectNodes(new BigUint64Array([])); } + + // LMB clicked on the graph background or MMB clicked anywhere + this.panning = true; }, doubleClick(e: MouseEvent) { const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined; diff --git a/node-graph/gstd/src/raster.rs b/node-graph/gstd/src/raster.rs index 4ccd99c0..d543a00d 100644 --- a/node-graph/gstd/src/raster.rs +++ b/node-graph/gstd/src/raster.rs @@ -117,7 +117,6 @@ fn map_image(mut image_frame: ImageFrame, map_fn: &'any_input MapFn) -> I where MapFn: for<'any_input> Node<'any_input, Color, Output = Color> + 'input, { - let mut image_frame = image_frame; for pixel in &mut image_frame.image.data { *pixel = map_fn.eval(*pixel); }