From 8483ae0f88dd3c4eb6e39a80066cfdfc93f18546 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Thu, 9 Jan 2025 22:21:45 +0100 Subject: [PATCH] [Editor] Improve zooming with a pinch gesture while drawing It lets the user make a pinch gesture with a finger on page with a drawing and the second finger on an other page. On mobile, it's pretty easy to be in such a situation. --- src/display/touch_manager.js | 68 +++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 12 deletions(-) diff --git a/src/display/touch_manager.js b/src/display/touch_manager.js index 2d5db615d5ca7..69f6a9f6d2022 100644 --- a/src/display/touch_manager.js +++ b/src/display/touch_manager.js @@ -31,6 +31,8 @@ class TouchManager { #onPinchEnd; + #pointerDownAC = null; + #signal; #touchInfo = null; @@ -78,7 +80,41 @@ class TouchManager { } #onTouchStart(evt) { - if (this.#isPinchingDisabled?.() || evt.touches.length < 2) { + if (this.#isPinchingDisabled?.()) { + return; + } + + if (evt.touches.length === 1) { + if (this.#pointerDownAC) { + return; + } + const pointerDownAC = (this.#pointerDownAC = new AbortController()); + const signal = AbortSignal.any([this.#signal, pointerDownAC.signal]); + const container = this.#container; + + // We want to have the events at the capture phase to make sure we can + // cancel them. + const opts = { capture: true, signal, passive: false }; + const cancelPointerDown = e => { + if (e.pointerType === "touch") { + this.#pointerDownAC?.abort(); + this.#pointerDownAC = null; + } + }; + container.addEventListener( + "pointerdown", + e => { + if (e.pointerType === "touch") { + // This is the second finger so we don't want it select something + // or whatever. + stopEvent(e); + cancelPointerDown(e); + } + }, + opts + ); + container.addEventListener("pointerup", cancelPointerDown, opts); + container.addEventListener("pointercancel", cancelPointerDown, opts); return; } @@ -86,18 +122,22 @@ class TouchManager { this.#touchMoveAC = new AbortController(); const signal = AbortSignal.any([this.#signal, this.#touchMoveAC.signal]); const container = this.#container; - const opt = { signal, passive: false }; + + const opt = { signal, capture: false, passive: false }; container.addEventListener( "touchmove", this.#onTouchMove.bind(this), opt ); - container.addEventListener("touchend", this.#onTouchEnd.bind(this), opt); - container.addEventListener( - "touchcancel", - this.#onTouchEnd.bind(this), - opt - ); + const onTouchEnd = this.#onTouchEnd.bind(this); + container.addEventListener("touchend", onTouchEnd, opt); + container.addEventListener("touchcancel", onTouchEnd, opt); + + opt.capture = true; + container.addEventListener("pointerdown", stopEvent, opt); + container.addEventListener("pointermove", stopEvent, opt); + container.addEventListener("pointercancel", stopEvent, opt); + container.addEventListener("pointerup", stopEvent, opt); this.#onPinchStart?.(); } @@ -125,6 +165,8 @@ class TouchManager { return; } + stopEvent(evt); + let [touch0, touch1] = evt.touches; if (touch0.identifier > touch1.identifier) { [touch0, touch1] = [touch1, touch0]; @@ -158,8 +200,6 @@ class TouchManager { touchInfo.touch1X = screen1X; touchInfo.touch1Y = screen1Y; - evt.preventDefault(); - if (!this.#isPinching) { // Start pinching. this.#isPinching = true; @@ -173,6 +213,9 @@ class TouchManager { } #onTouchEnd(evt) { + if (evt.touches.length >= 2) { + return; + } this.#touchMoveAC.abort(); this.#touchMoveAC = null; this.#onPinchEnd?.(); @@ -180,8 +223,7 @@ class TouchManager { if (!this.#touchInfo) { return; } - - evt.preventDefault(); + stopEvent(evt); this.#touchInfo = null; this.#isPinching = false; } @@ -189,6 +231,8 @@ class TouchManager { destroy() { this.#touchManagerAC?.abort(); this.#touchManagerAC = null; + this.#pointerDownAC?.abort(); + this.#pointerDownAC = null; } }