Skip to content

Commit

Permalink
Merge pull request #19216 from calixteman/extract_pinch
Browse files Browse the repository at this point in the history
Move the pinch stuff in its own file in order to use for editors
  • Loading branch information
calixteman authored Dec 12, 2024
2 parents a9c5bb2 + e0b63ec commit 8fa4398
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 123 deletions.
192 changes: 192 additions & 0 deletions src/display/touch_manager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/* Copyright 2024 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { shadow } from "../shared/util.js";

class TouchManager {
#container;

#isPinching = false;

#isPinchingStopped = null;

#isPinchingDisabled;

#onPinching;

#onPinchEnd;

#signal;

#touchInfo = null;

#touchManagerAC;

#touchMoveAC = null;

constructor({
container,
isPinchingDisabled = null,
isPinchingStopped = null,
onPinching = null,
onPinchEnd = null,
signal,
}) {
this.#container = container;
this.#isPinchingStopped = isPinchingStopped;
this.#isPinchingDisabled = isPinchingDisabled;
this.#onPinching = onPinching;
this.#onPinchEnd = onPinchEnd;
this.#touchManagerAC = new AbortController();
this.#signal = AbortSignal.any([signal, this.#touchManagerAC.signal]);

container.addEventListener("touchstart", this.#onTouchStart.bind(this), {
passive: false,
signal: this.#signal,
});
}

get MIN_TOUCH_DISTANCE_TO_PINCH() {
// The 35 is coming from:
// https://searchfox.org/mozilla-central/source/gfx/layers/apz/src/GestureEventListener.cpp#36
//
// The properties TouchEvent::screenX/Y are in screen CSS pixels:
// https://developer.mozilla.org/en-US/docs/Web/API/Touch/screenX#examples
// MIN_TOUCH_DISTANCE_TO_PINCH is in CSS pixels.
return shadow(
this,
"MIN_TOUCH_DISTANCE_TO_PINCH",
35 / (window.devicePixelRatio || 1)
);
}

#onTouchStart(evt) {
if (this.#isPinchingDisabled?.() || evt.touches.length < 2) {
return;
}

if (!this.#touchMoveAC) {
this.#touchMoveAC = new AbortController();
const signal = AbortSignal.any([this.#signal, this.#touchMoveAC.signal]);
const container = this.#container;
const opt = { signal, 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
);
}

evt.preventDefault();

if (evt.touches.length !== 2 || this.#isPinchingStopped?.()) {
this.#touchInfo = null;
return;
}

let [touch0, touch1] = evt.touches;
if (touch0.identifier > touch1.identifier) {
[touch0, touch1] = [touch1, touch0];
}
this.#touchInfo = {
touch0X: touch0.screenX,
touch0Y: touch0.screenY,
touch1X: touch1.screenX,
touch1Y: touch1.screenY,
};
}

#onTouchMove(evt) {
if (!this.#touchInfo || evt.touches.length !== 2) {
return;
}

let [touch0, touch1] = evt.touches;
if (touch0.identifier > touch1.identifier) {
[touch0, touch1] = [touch1, touch0];
}
const { screenX: screen0X, screenY: screen0Y } = touch0;
const { screenX: screen1X, screenY: screen1Y } = touch1;
const touchInfo = this.#touchInfo;
const {
touch0X: pTouch0X,
touch0Y: pTouch0Y,
touch1X: pTouch1X,
touch1Y: pTouch1Y,
} = touchInfo;

const prevGapX = pTouch1X - pTouch0X;
const prevGapY = pTouch1Y - pTouch0Y;
const currGapX = screen1X - screen0X;
const currGapY = screen1Y - screen0Y;

const distance = Math.hypot(currGapX, currGapY) || 1;
const pDistance = Math.hypot(prevGapX, prevGapY) || 1;
if (
!this.#isPinching &&
Math.abs(pDistance - distance) <= TouchManager.MIN_TOUCH_DISTANCE_TO_PINCH
) {
return;
}

touchInfo.touch0X = screen0X;
touchInfo.touch0Y = screen0Y;
touchInfo.touch1X = screen1X;
touchInfo.touch1Y = screen1Y;

evt.preventDefault();

if (!this.#isPinching) {
// Start pinching.
this.#isPinching = true;

// We return here else the first pinch is a bit too much
return;
}

const origin = [(screen0X + screen1X) / 2, (screen0Y + screen1Y) / 2];
this.#onPinching?.(origin, pDistance, distance);
}

#onTouchEnd(evt) {
this.#touchMoveAC.abort();
this.#touchMoveAC = null;

if (!this.#touchInfo) {
return;
}

if (this.#isPinching) {
this.#onPinchEnd?.();
this.#isPinching = false;
}

evt.preventDefault();
this.#touchInfo = null;
}

destroy() {
this.#touchManagerAC?.abort();
this.#touchManagerAC = null;
}
}

export { TouchManager };
2 changes: 2 additions & 0 deletions src/pdf.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import { DrawLayer } from "./display/draw_layer.js";
import { GlobalWorkerOptions } from "./display/worker_options.js";
import { HighlightOutliner } from "./display/editor/drawers/highlight.js";
import { TextLayer } from "./display/text_layer.js";
import { TouchManager } from "./display/touch_manager.js";
import { XfaLayer } from "./display/xfa_layer.js";

/* eslint-disable-next-line no-unused-vars */
Expand Down Expand Up @@ -127,6 +128,7 @@ export {
shadow,
stopEvent,
TextLayer,
TouchManager,
UnexpectedResponseException,
Util,
VerbosityLevel,
Expand Down
2 changes: 2 additions & 0 deletions test/unit/pdf_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ import { DOMSVGFactory } from "../../src/display/svg_factory.js";
import { DrawLayer } from "../../src/display/draw_layer.js";
import { GlobalWorkerOptions } from "../../src/display/worker_options.js";
import { TextLayer } from "../../src/display/text_layer.js";
import { TouchManager } from "../../src/display/touch_manager.js";
import { XfaLayer } from "../../src/display/xfa_layer.js";

const expectedAPI = Object.freeze({
Expand Down Expand Up @@ -105,6 +106,7 @@ const expectedAPI = Object.freeze({
shadow,
stopEvent,
TextLayer,
TouchManager,
UnexpectedResponseException,
Util,
VerbosityLevel,
Expand Down
Loading

0 comments on commit 8fa4398

Please sign in to comment.