mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-19 22:58:07 +02:00
[Editor] Add the ability to resize an editor in using a pinch gesture
This commit is contained in:
parent
8fa4398266
commit
4ed7f7f1ee
5 changed files with 130 additions and 22 deletions
|
@ -26,6 +26,7 @@ import { FeatureTest, shadow, unreachable } from "../../shared/util.js";
|
|||
import { noContextMenu, stopEvent } from "../display_utils.js";
|
||||
import { AltText } from "./alt_text.js";
|
||||
import { EditorToolbar } from "./toolbar.js";
|
||||
import { TouchManager } from "../touch_manager.js";
|
||||
|
||||
/**
|
||||
* @typedef {Object} AnnotationEditorParameters
|
||||
|
@ -82,6 +83,8 @@ class AnnotationEditor {
|
|||
|
||||
#telemetryTimeouts = null;
|
||||
|
||||
#touchManager = null;
|
||||
|
||||
_editToolbar = null;
|
||||
|
||||
_initialOptions = Object.create(null);
|
||||
|
@ -864,6 +867,13 @@ class AnnotationEditor {
|
|||
});
|
||||
}
|
||||
|
||||
static _round(x) {
|
||||
// 10000 because we multiply by 100 and use toFixed(2) in fixAndSetPosition.
|
||||
// Without rounding, the positions of the corners other than the top left
|
||||
// one can be slightly wrong.
|
||||
return Math.round(x * 10000) / 10000;
|
||||
}
|
||||
|
||||
#resizerPointermove(name, event) {
|
||||
const [parentWidth, parentHeight] = this.parentDimensions;
|
||||
const savedX = this.x;
|
||||
|
@ -873,10 +883,6 @@ class AnnotationEditor {
|
|||
const minWidth = AnnotationEditor.MIN_SIZE / parentWidth;
|
||||
const minHeight = AnnotationEditor.MIN_SIZE / parentHeight;
|
||||
|
||||
// 10000 because we multiply by 100 and use toFixed(2) in fixAndSetPosition.
|
||||
// Without rounding, the positions of the corners other than the top left
|
||||
// one can be slightly wrong.
|
||||
const round = x => Math.round(x * 10000) / 10000;
|
||||
const rotationMatrix = this.#getRotationMatrix(this.rotation);
|
||||
const transf = (x, y) => [
|
||||
rotationMatrix[0] * x + rotationMatrix[2] * y,
|
||||
|
@ -936,8 +942,8 @@ class AnnotationEditor {
|
|||
const point = getPoint(savedWidth, savedHeight);
|
||||
const oppositePoint = getOpposite(savedWidth, savedHeight);
|
||||
let transfOppositePoint = transf(...oppositePoint);
|
||||
const oppositeX = round(savedX + transfOppositePoint[0]);
|
||||
const oppositeY = round(savedY + transfOppositePoint[1]);
|
||||
const oppositeX = AnnotationEditor._round(savedX + transfOppositePoint[0]);
|
||||
const oppositeY = AnnotationEditor._round(savedY + transfOppositePoint[1]);
|
||||
let ratioX = 1;
|
||||
let ratioY = 1;
|
||||
|
||||
|
@ -990,8 +996,8 @@ class AnnotationEditor {
|
|||
) / savedHeight;
|
||||
}
|
||||
|
||||
const newWidth = round(savedWidth * ratioX);
|
||||
const newHeight = round(savedHeight * ratioY);
|
||||
const newWidth = AnnotationEditor._round(savedWidth * ratioX);
|
||||
const newHeight = AnnotationEditor._round(savedHeight * ratioY);
|
||||
transfOppositePoint = transf(...getOpposite(newWidth, newHeight));
|
||||
const newX = oppositeX - transfOppositePoint[0];
|
||||
const newY = oppositeY - transfOppositePoint[1];
|
||||
|
@ -1142,11 +1148,92 @@ class AnnotationEditor {
|
|||
|
||||
bindEvents(this, this.div, ["pointerdown"]);
|
||||
|
||||
if (this.isResizable && this._uiManager._supportsPinchToZoom) {
|
||||
this.#touchManager ||= new TouchManager({
|
||||
container: this.div,
|
||||
isPinchingDisabled: () => !this.isSelected,
|
||||
onPinchStart: this.#touchPinchStartCallback.bind(this),
|
||||
onPinching: this.#touchPinchCallback.bind(this),
|
||||
onPinchEnd: this.#touchPinchEndCallback.bind(this),
|
||||
signal: this._uiManager._signal,
|
||||
});
|
||||
}
|
||||
|
||||
this._uiManager._editorUndoBar?.hide();
|
||||
|
||||
return this.div;
|
||||
}
|
||||
|
||||
#touchPinchStartCallback() {
|
||||
this.#savedDimensions = {
|
||||
savedX: this.x,
|
||||
savedY: this.y,
|
||||
savedWidth: this.width,
|
||||
savedHeight: this.height,
|
||||
};
|
||||
this.#altText?.toggle(false);
|
||||
this.parent.togglePointerEvents(false);
|
||||
}
|
||||
|
||||
#touchPinchCallback(_origin, prevDistance, distance) {
|
||||
// Slightly slow down the zooming because the editor could be small and the
|
||||
// user could have difficulties to rescale it as they want.
|
||||
const slowDownFactor = 0.7;
|
||||
let factor =
|
||||
slowDownFactor * (distance / prevDistance) + 1 - slowDownFactor;
|
||||
if (factor === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rotationMatrix = this.#getRotationMatrix(this.rotation);
|
||||
const transf = (x, y) => [
|
||||
rotationMatrix[0] * x + rotationMatrix[2] * y,
|
||||
rotationMatrix[1] * x + rotationMatrix[3] * y,
|
||||
];
|
||||
|
||||
// The center of the editor is the fixed point.
|
||||
const [parentWidth, parentHeight] = this.parentDimensions;
|
||||
const savedX = this.x;
|
||||
const savedY = this.y;
|
||||
const savedWidth = this.width;
|
||||
const savedHeight = this.height;
|
||||
|
||||
const minWidth = AnnotationEditor.MIN_SIZE / parentWidth;
|
||||
const minHeight = AnnotationEditor.MIN_SIZE / parentHeight;
|
||||
factor = Math.max(
|
||||
Math.min(factor, 1 / savedWidth, 1 / savedHeight),
|
||||
minWidth / savedWidth,
|
||||
minHeight / savedHeight
|
||||
);
|
||||
const newWidth = AnnotationEditor._round(savedWidth * factor);
|
||||
const newHeight = AnnotationEditor._round(savedHeight * factor);
|
||||
if (newWidth === savedWidth && newHeight === savedHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#initialRect ||= [savedX, savedY, savedWidth, savedHeight];
|
||||
const transfCenterPoint = transf(savedWidth / 2, savedHeight / 2);
|
||||
const centerX = AnnotationEditor._round(savedX + transfCenterPoint[0]);
|
||||
const centerY = AnnotationEditor._round(savedY + transfCenterPoint[1]);
|
||||
const newTransfCenterPoint = transf(newWidth / 2, newHeight / 2);
|
||||
|
||||
this.x = centerX - newTransfCenterPoint[0];
|
||||
this.y = centerY - newTransfCenterPoint[1];
|
||||
this.width = newWidth;
|
||||
this.height = newHeight;
|
||||
|
||||
this.setDims(parentWidth * newWidth, parentHeight * newHeight);
|
||||
this.fixAndSetPosition();
|
||||
|
||||
this._onResizing();
|
||||
}
|
||||
|
||||
#touchPinchEndCallback() {
|
||||
this.#altText?.toggle(true);
|
||||
this.parent.togglePointerEvents(true);
|
||||
this.#addResizeToUndoStack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Onpointerdown callback.
|
||||
* @param {PointerEvent} event
|
||||
|
@ -1158,7 +1245,6 @@ class AnnotationEditor {
|
|||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
this.#hasBeenClicked = true;
|
||||
|
||||
if (this._isDraggable) {
|
||||
|
@ -1189,6 +1275,7 @@ class AnnotationEditor {
|
|||
#setUpDragSession(event) {
|
||||
const { isSelected } = this;
|
||||
this._uiManager.setUpDragSession();
|
||||
let hasDraggingStarted = false;
|
||||
|
||||
const ac = new AbortController();
|
||||
const signal = this._uiManager.combinedSignal(ac);
|
||||
|
@ -1201,6 +1288,9 @@ class AnnotationEditor {
|
|||
if (!this._uiManager.endDragSession()) {
|
||||
this.#selectOnPointerEvent(e);
|
||||
}
|
||||
if (hasDraggingStarted) {
|
||||
this._onStopDragging();
|
||||
}
|
||||
};
|
||||
|
||||
if (isSelected) {
|
||||
|
@ -1211,6 +1301,10 @@ class AnnotationEditor {
|
|||
window.addEventListener(
|
||||
"pointermove",
|
||||
e => {
|
||||
if (!hasDraggingStarted) {
|
||||
hasDraggingStarted = true;
|
||||
this._onStartDragging();
|
||||
}
|
||||
const { clientX: x, clientY: y, pointerId } = e;
|
||||
if (pointerId !== this.#dragPointerId) {
|
||||
stopEvent(e);
|
||||
|
@ -1235,11 +1329,14 @@ class AnnotationEditor {
|
|||
"pointerdown",
|
||||
// If the user drags with one finger and then clicks with another.
|
||||
e => {
|
||||
if (e.isPrimary && e.pointerType === this.#dragPointerType) {
|
||||
if (e.pointerType === this.#dragPointerType) {
|
||||
// We've a pinch to zoom session.
|
||||
// We cannot have two primaries at the same time.
|
||||
// It's possible to be in this state with Firefox and Gnome when
|
||||
// trying to drag with three fingers (see bug 1933716).
|
||||
cancelDrag(e);
|
||||
if (this.#touchManager || e.isPrimary) {
|
||||
cancelDrag(e);
|
||||
}
|
||||
}
|
||||
stopEvent(e);
|
||||
},
|
||||
|
@ -1247,12 +1344,9 @@ class AnnotationEditor {
|
|||
);
|
||||
}
|
||||
|
||||
this._onStartDragging();
|
||||
|
||||
const pointerUpCallback = e => {
|
||||
if (!this.#dragPointerId || this.#dragPointerId === e.pointerId) {
|
||||
cancelDrag(e);
|
||||
this._onStopDragging();
|
||||
return;
|
||||
}
|
||||
stopEvent(e);
|
||||
|
@ -1557,6 +1651,8 @@ class AnnotationEditor {
|
|||
this.#telemetryTimeouts = null;
|
||||
}
|
||||
this.parent = null;
|
||||
this.#touchManager?.destroy();
|
||||
this.#touchManager = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -834,7 +834,8 @@ class AnnotationEditorUIManager {
|
|||
enableUpdatedAddImage,
|
||||
enableNewAltTextWhenAddingImage,
|
||||
mlManager,
|
||||
editorUndoBar
|
||||
editorUndoBar,
|
||||
supportsPinchToZoom
|
||||
) {
|
||||
const signal = (this._signal = this.#abortController.signal);
|
||||
this.#container = container;
|
||||
|
@ -870,6 +871,7 @@ class AnnotationEditorUIManager {
|
|||
};
|
||||
this.isShiftKeyDown = false;
|
||||
this._editorUndoBar = editorUndoBar || null;
|
||||
this._supportsPinchToZoom = supportsPinchToZoom !== false;
|
||||
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
||||
Object.defineProperty(this, "reset", {
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
*/
|
||||
|
||||
import { shadow } from "../shared/util.js";
|
||||
import { stopEvent } from "./display_utils.js";
|
||||
|
||||
class TouchManager {
|
||||
#container;
|
||||
|
@ -24,6 +25,8 @@ class TouchManager {
|
|||
|
||||
#isPinchingDisabled;
|
||||
|
||||
#onPinchStart;
|
||||
|
||||
#onPinching;
|
||||
|
||||
#onPinchEnd;
|
||||
|
@ -40,6 +43,7 @@ class TouchManager {
|
|||
container,
|
||||
isPinchingDisabled = null,
|
||||
isPinchingStopped = null,
|
||||
onPinchStart = null,
|
||||
onPinching = null,
|
||||
onPinchEnd = null,
|
||||
signal,
|
||||
|
@ -47,6 +51,7 @@ class TouchManager {
|
|||
this.#container = container;
|
||||
this.#isPinchingStopped = isPinchingStopped;
|
||||
this.#isPinchingDisabled = isPinchingDisabled;
|
||||
this.#onPinchStart = onPinchStart;
|
||||
this.#onPinching = onPinching;
|
||||
this.#onPinchEnd = onPinchEnd;
|
||||
this.#touchManagerAC = new AbortController();
|
||||
|
@ -93,9 +98,10 @@ class TouchManager {
|
|||
this.#onTouchEnd.bind(this),
|
||||
opt
|
||||
);
|
||||
this.#onPinchStart?.();
|
||||
}
|
||||
|
||||
evt.preventDefault();
|
||||
stopEvent(evt);
|
||||
|
||||
if (evt.touches.length !== 2 || this.#isPinchingStopped?.()) {
|
||||
this.#touchInfo = null;
|
||||
|
@ -169,18 +175,15 @@ class TouchManager {
|
|||
#onTouchEnd(evt) {
|
||||
this.#touchMoveAC.abort();
|
||||
this.#touchMoveAC = null;
|
||||
this.#onPinchEnd?.();
|
||||
|
||||
if (!this.#touchInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.#isPinching) {
|
||||
this.#onPinchEnd?.();
|
||||
this.#isPinching = false;
|
||||
}
|
||||
|
||||
evt.preventDefault();
|
||||
this.#touchInfo = null;
|
||||
this.#isPinching = false;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue