Fix graph scrolling bug from #1021

And replicate those changes to the Svelte version.
This commit is contained in:
Keavon Chambers 2023-03-03 02:28:52 -08:00
parent 2cf4ee0fab
commit 8fe8896c4a
4 changed files with 86 additions and 62 deletions

View file

@ -175,11 +175,21 @@
} }
function scroll(e: WheelEvent) { function scroll(e: WheelEvent) {
const scrollX = e.deltaX; const [scrollX, scrollY] = [e.deltaX, e.deltaY];
const scrollY = 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 // Zoom
if (e.ctrlKey) { if (zoom) {
let zoomFactor = 1 + Math.abs(scrollY) * WHEEL_RATE; let zoomFactor = 1 + Math.abs(scrollY) * WHEEL_RATE;
if (scrollY > 0) zoomFactor = 1 / zoomFactor; if (scrollY > 0) zoomFactor = 1 / zoomFactor;
@ -199,15 +209,14 @@
transform.x -= (deltaX / transform.scale) * zoomFactor; transform.x -= (deltaX / transform.scale) * zoomFactor;
transform.y -= (deltaY / transform.scale) * zoomFactor; transform.y -= (deltaY / transform.scale) * zoomFactor;
// Prevent actually zooming into the page when pinch-zooming on laptop trackpads return;
e.preventDefault();
} }
// Pan // Pan
else if (!e.shiftKey) { if (horizontalPan) {
transform.x -= scrollX / transform.scale;
transform.y -= scrollY / transform.scale;
} else {
transform.x -= scrollY / transform.scale; 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) { function pointerDown(e: PointerEvent) {
// Exit the add node popup by clicking elsewhere in the graph const [lmb, rmb] = [e.button === 0, e.button === 2];
if (nodeListLocation && !(e.target as HTMLElement).closest("[data-node-list]")) nodeListLocation = undefined;
// Handle the add node popup on right click const port = (e.target as HTMLDivElement).closest("[data-port]") as HTMLDivElement;
if (e.button === 2) { 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(); const graphBounds = graph.div().getBoundingClientRect();
nodeListLocation = { nodeListLocation = {
x: Math.round(((e.clientX - graphBounds.x) / transform.scale - transform.x) / GRID_SIZE), x: Math.round(((e.clientX - graphBounds.x) / transform.scale - transform.x) / GRID_SIZE),
@ -239,20 +252,19 @@
return; 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 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)); editor.instance.togglePreview(BigInt(nodeId));
} }
// Clicked on a port dot // Clicked on a port dot
if (port && node) { if (lmb && port && node) {
const isOutput = Boolean(port.getAttribute("data-port") === "output"); const isOutput = Boolean(port.getAttribute("data-port") === "output");
if (isOutput) linkInProgressFromConnector = port; if (isOutput) linkInProgressFromConnector = port;
@ -279,7 +291,7 @@
} }
// Clicked on a node // Clicked on a node
if (nodeId) { if (lmb && nodeId) {
let modifiedSelected = false; let modifiedSelected = false;
const id = BigInt(nodeId); const id = BigInt(nodeId);
@ -306,11 +318,13 @@
} }
// Clicked on the graph background // Clicked on the graph background
panning = true; if (lmb && selected.length !== 0) {
if (selected.length !== 0) {
selected = []; 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) { function doubleClick(e: MouseEvent) {

View file

@ -9,6 +9,7 @@ import {
UpdateNodeGraph, UpdateNodeGraph,
UpdateNodeTypes, UpdateNodeTypes,
UpdateNodeGraphBarLayout, UpdateNodeGraphBarLayout,
UpdateZoomWithScroll,
defaultWidgetLayout, defaultWidgetLayout,
patchWidgetLayout, patchWidgetLayout,
} from "@/wasm-communication/messages"; } from "@/wasm-communication/messages";
@ -20,6 +21,7 @@ export function createNodeGraphState(editor: Editor) {
links: [] as FrontendNodeLink[], links: [] as FrontendNodeLink[],
nodeTypes: [] as FrontendNodeType[], nodeTypes: [] as FrontendNodeType[],
nodeGraphBarLayout: defaultWidgetLayout(), nodeGraphBarLayout: defaultWidgetLayout(),
zoomWithScroll: false as boolean,
}); });
// Set up message subscriptions on creation // Set up message subscriptions on creation
@ -42,6 +44,12 @@ export function createNodeGraphState(editor: Editor) {
return state; return state;
}); });
}); });
editor.subscriptions.subscribeJsMessage(UpdateZoomWithScroll, (updateZoomWithScroll) => {
update((state) => {
state.zoomWithScroll = updateZoomWithScroll.zoomWithScroll;
return state;
});
});
return { return {
subscribe, subscribe,

View file

@ -511,19 +511,18 @@ export default defineComponent({
return [pathString, dataType]; return [pathString, dataType];
}, },
scroll(e: WheelEvent) { scroll(e: WheelEvent) {
const scrollX = e.deltaX; const [scrollX, scrollY] = [e.deltaX, e.deltaY];
const scrollY = e.deltaY;
const zoomWithScroll = this.nodeGraph.state.zoomWithScroll;
let zoom; // If zoom with scroll is enabled: horizontal pan with Ctrl, vertical pan with Shift
let horizontalPan; const zoomWithScroll = this.nodeGraph.state.zoomWithScroll;
if (zoomWithScroll) { const zoom = zoomWithScroll ? !e.ctrlKey && !e.shiftKey : e.ctrlKey;
zoom = !(e.ctrlKey || e.shiftKey); const horizontalPan = zoomWithScroll ? e.ctrlKey : !e.ctrlKey && e.shiftKey;
horizontalPan = e.ctrlKey;
} else { // Prevent the web page from being zoomed
zoom = e.ctrlKey; if (e.ctrlKey) e.preventDefault();
horizontalPan = !(e.ctrlKey || e.shiftKey);
} // Always pan horizontally in response to a horizontal scroll wheel movement
this.transform.x -= scrollX / this.transform.scale;
// Zoom // Zoom
if (zoom) { if (zoom) {
@ -548,14 +547,13 @@ export default defineComponent({
this.transform.x -= (deltaX / this.transform.scale) * zoomFactor; this.transform.x -= (deltaX / this.transform.scale) * zoomFactor;
this.transform.y -= (deltaY / this.transform.scale) * zoomFactor; this.transform.y -= (deltaY / this.transform.scale) * zoomFactor;
// Prevent actually zooming into the page when pinch-zooming on laptop trackpads return;
e.preventDefault();
} }
// Pan // Pan
else if (horizontalPan) { if (horizontalPan) {
this.transform.x -= scrollY / this.transform.scale; this.transform.x -= scrollY / this.transform.scale;
} else { } else {
this.transform.x -= scrollX / this.transform.scale;
this.transform.y -= scrollY / this.transform.scale; this.transform.y -= scrollY / this.transform.scale;
} }
}, },
@ -565,13 +563,19 @@ export default defineComponent({
document.removeEventListener("keydown", this.keydown); 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) { pointerDown(e: PointerEvent) {
// Exit the add node popup by clicking elsewhere in the graph const [lmb, rmb] = [e.button === 0, e.button === 2];
if (this.nodeListLocation && !(e.target as HTMLElement).closest("[data-node-list]")) this.nodeListLocation = undefined;
// Handle the add node popup on right click const port = (e.target as HTMLDivElement).closest("[data-port]") as HTMLDivElement;
if (e.button === 2) { 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 graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
const graph = graphDiv?.getBoundingClientRect() || new DOMRect(); const graph = graphDiv?.getBoundingClientRect() || new DOMRect();
this.nodeListLocation = { this.nodeListLocation = {
@ -583,22 +587,19 @@ export default defineComponent({
return; 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 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)); this.editor.instance.togglePreview(BigInt(nodeId));
} }
// Clicked on a port dot // Clicked on a port dot
if (port && node) { if (lmb && port && node) {
const isOutput = Boolean(port.getAttribute("data-port") === "output"); const isOutput = Boolean(port.getAttribute("data-port") === "output");
if (isOutput) this.linkInProgressFromConnector = port; if (isOutput) this.linkInProgressFromConnector = port;
@ -625,7 +626,7 @@ export default defineComponent({
} }
// Clicked on a node // Clicked on a node
if (nodeId) { if (lmb && nodeId) {
let modifiedSelected = false; let modifiedSelected = false;
const id = BigInt(nodeId); const id = BigInt(nodeId);
@ -652,11 +653,13 @@ export default defineComponent({
} }
// Clicked on the graph background // Clicked on the graph background
this.panning = true; if (lmb && this.selected.length !== 0) {
if (this.selected.length !== 0) {
this.selected = []; 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) { doubleClick(e: MouseEvent) {
const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined; const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined;

View file

@ -117,7 +117,6 @@ fn map_image<MapFn>(mut image_frame: ImageFrame, map_fn: &'any_input MapFn) -> I
where where
MapFn: for<'any_input> Node<'any_input, Color, Output = Color> + 'input, 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 { for pixel in &mut image_frame.image.data {
*pixel = map_fn.eval(*pixel); *pixel = map_fn.eval(*pixel);
} }