From 9c31d708a21cc23209da10d6dd6de586d3161afd Mon Sep 17 00:00:00 2001 From: filtered <176114999+webfiltered@users.noreply.github.com> Date: Wed, 13 Aug 2025 03:15:32 +1000 Subject: [PATCH] Add automatic trackpad / mouse sensing (#4913) --- src/lib/litegraph/src/CanvasPointer.ts | 45 +++++++++++++++++++ src/lib/litegraph/src/LGraphCanvas.ts | 55 +++++++++++------------- src/lib/litegraph/src/LiteGraphGlobal.ts | 2 + 3 files changed, 72 insertions(+), 30 deletions(-) diff --git a/src/lib/litegraph/src/CanvasPointer.ts b/src/lib/litegraph/src/CanvasPointer.ts index 0e1af314d1..380e50f41b 100644 --- a/src/lib/litegraph/src/CanvasPointer.ts +++ b/src/lib/litegraph/src/CanvasPointer.ts @@ -43,6 +43,19 @@ export class CanvasPointer { /** {@link maxClickDrift} squared. Used to calculate click drift without `sqrt`. */ static #maxClickDrift2 = this.#maxClickDrift ** 2 + /** Assume that "wheel" events with both deltaX and deltaY less than this value are trackpad gestures. */ + static trackpadThreshold = 60 + + /** + * The minimum time between "wheel" events to allow switching between trackpad + * and mouse modes. + * + * This prevents trackpad "flick" panning from registering as regular mouse wheel. + * After a flick gesture is complete, the automatic wheel events are sent with + * reduced frequency, but much higher deltaX and deltaY values. + */ + static trackpadMaxGap = 200 + /** The element this PointerState should capture input against when dragging. */ element: Element /** Pointer ID used by drag capture. */ @@ -77,6 +90,9 @@ export class CanvasPointer { /** The last pointerup event for the primary button */ eUp?: CanvasPointerEvent + /** The last pointermove event that was treated as a trackpad gesture. */ + lastTrackpadEvent?: WheelEvent + /** * If set, as soon as the mouse moves outside the click drift threshold, this action is run once. * @param pointer [DEPRECATED] This parameter will be removed in a future release. @@ -257,6 +273,35 @@ export class CanvasPointer { delete this.onDragStart } + /** + * Checks if the given wheel event is part of a continued trackpad gesture. + * @param e The wheel event to check + * @returns `true` if the event is part of a continued trackpad gesture, otherwise `false` + */ + #isContinuationOfGesture(e: WheelEvent): boolean { + const { lastTrackpadEvent } = this + if (!lastTrackpadEvent) return false + + return ( + e.timeStamp - lastTrackpadEvent.timeStamp < CanvasPointer.trackpadMaxGap + ) + } + + /** + * Checks if the given wheel event is part of a trackpad gesture. + * @param e The wheel event to check + * @returns `true` if the event is part of a trackpad gesture, otherwise `false` + */ + isTrackpadGesture(e: WheelEvent): boolean { + if (this.#isContinuationOfGesture(e)) { + this.lastTrackpadEvent = e + return true + } + + const threshold = CanvasPointer.trackpadThreshold + return Math.abs(e.deltaX) < threshold && Math.abs(e.deltaY) < threshold + } + /** * Resets the state of this {@link CanvasPointer} instance. * diff --git a/src/lib/litegraph/src/LGraphCanvas.ts b/src/lib/litegraph/src/LGraphCanvas.ts index 27f0f43b8f..a4ff279221 100644 --- a/src/lib/litegraph/src/LGraphCanvas.ts +++ b/src/lib/litegraph/src/LGraphCanvas.ts @@ -3456,10 +3456,6 @@ export class LGraphCanvas processMouseWheel(e: WheelEvent): void { if (!this.graph || !this.allow_dragcanvas) return - // TODO: Mouse wheel zoom rewrite - // @ts-expect-error wheelDeltaY is non-standard property on WheelEvent - const delta = e.wheelDeltaY ?? e.detail * -60 - this.adjustMouseEvent(e) const pos: Point = [e.clientX, e.clientY] @@ -3467,35 +3463,34 @@ export class LGraphCanvas let { scale } = this.ds - if ( - LiteGraph.canvasNavigationMode === 'legacy' || - (LiteGraph.canvasNavigationMode === 'standard' && e.ctrlKey) - ) { - if (delta > 0) { - scale *= this.zoom_speed - } else if (delta < 0) { - scale *= 1 / this.zoom_speed - } - this.ds.changeScale(scale, [e.clientX, e.clientY]) - } else if ( - LiteGraph.macTrackpadGestures && - (!LiteGraph.macGesturesRequireMac || navigator.userAgent.includes('Mac')) - ) { - if (e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey) { - if (e.deltaY > 0) { - scale *= 1 / this.zoom_speed - } else if (e.deltaY < 0) { - scale *= this.zoom_speed - } - this.ds.changeScale(scale, [e.clientX, e.clientY]) - } else if (e.ctrlKey) { + // Detect if this is a trackpad gesture or mouse wheel + const isTrackpad = this.pointer.isTrackpadGesture(e) + + if (e.ctrlKey || LiteGraph.canvasNavigationMode === 'legacy') { + // Legacy mode or standard mode with ctrl - use wheel for zoom + if (isTrackpad) { + // Trackpad gesture - use smooth scaling scale *= 1 + e.deltaY * (1 - this.zoom_speed) * 0.18 this.ds.changeScale(scale, [e.clientX, e.clientY], false) - } else if (e.shiftKey) { - this.ds.offset[0] -= e.deltaY * 1.18 * (1 / scale) } else { - this.ds.offset[0] -= e.deltaX * 1.18 * (1 / scale) - this.ds.offset[1] -= e.deltaY * 1.18 * (1 / scale) + // Mouse wheel - use stepped scaling + if (e.deltaY < 0) { + scale *= this.zoom_speed + } else if (e.deltaY > 0) { + scale *= 1 / this.zoom_speed + } + this.ds.changeScale(scale, [e.clientX, e.clientY]) + } + } else { + // Standard mode without ctrl - use wheel / gestures to pan + // Trackpads and mice work on significantly different scales + const factor = isTrackpad ? 0.18 : 0.008_333 + + if (!isTrackpad && e.shiftKey && e.deltaX === 0) { + this.ds.offset[0] -= e.deltaY * (1 + factor) * (1 / scale) + } else { + this.ds.offset[0] -= e.deltaX * (1 + factor) * (1 / scale) + this.ds.offset[1] -= e.deltaY * (1 + factor) * (1 / scale) } } diff --git a/src/lib/litegraph/src/LiteGraphGlobal.ts b/src/lib/litegraph/src/LiteGraphGlobal.ts index e27377fedf..42ad95877d 100644 --- a/src/lib/litegraph/src/LiteGraphGlobal.ts +++ b/src/lib/litegraph/src/LiteGraphGlobal.ts @@ -284,6 +284,7 @@ export class LiteGraphGlobal { ] /** + * @deprecated Removed; has no effect. * If `true`, mouse wheel events will be interpreted as trackpad gestures. * Tested on MacBook M4 Pro. * @default false @@ -292,6 +293,7 @@ export class LiteGraphGlobal { macTrackpadGestures: boolean = false /** + * @deprecated Removed; has no effect. * If both this setting and {@link macTrackpadGestures} are `true`, trackpad gestures will * only be enabled when the browser user agent includes "Mac". * @default true