Move the pinch stuff in its own file in order to use for editors
This commit is contained in:
parent
a9c5bb25b8
commit
e0b63ecd03
192
src/display/touch_manager.js
Normal file
192
src/display/touch_manager.js
Normal file
@ -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 };
|
||||||
@ -72,6 +72,7 @@ import { DrawLayer } from "./display/draw_layer.js";
|
|||||||
import { GlobalWorkerOptions } from "./display/worker_options.js";
|
import { GlobalWorkerOptions } from "./display/worker_options.js";
|
||||||
import { HighlightOutliner } from "./display/editor/drawers/highlight.js";
|
import { HighlightOutliner } from "./display/editor/drawers/highlight.js";
|
||||||
import { TextLayer } from "./display/text_layer.js";
|
import { TextLayer } from "./display/text_layer.js";
|
||||||
|
import { TouchManager } from "./display/touch_manager.js";
|
||||||
import { XfaLayer } from "./display/xfa_layer.js";
|
import { XfaLayer } from "./display/xfa_layer.js";
|
||||||
|
|
||||||
/* eslint-disable-next-line no-unused-vars */
|
/* eslint-disable-next-line no-unused-vars */
|
||||||
@ -127,6 +128,7 @@ export {
|
|||||||
shadow,
|
shadow,
|
||||||
stopEvent,
|
stopEvent,
|
||||||
TextLayer,
|
TextLayer,
|
||||||
|
TouchManager,
|
||||||
UnexpectedResponseException,
|
UnexpectedResponseException,
|
||||||
Util,
|
Util,
|
||||||
VerbosityLevel,
|
VerbosityLevel,
|
||||||
|
|||||||
@ -63,6 +63,7 @@ import { DOMSVGFactory } from "../../src/display/svg_factory.js";
|
|||||||
import { DrawLayer } from "../../src/display/draw_layer.js";
|
import { DrawLayer } from "../../src/display/draw_layer.js";
|
||||||
import { GlobalWorkerOptions } from "../../src/display/worker_options.js";
|
import { GlobalWorkerOptions } from "../../src/display/worker_options.js";
|
||||||
import { TextLayer } from "../../src/display/text_layer.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";
|
import { XfaLayer } from "../../src/display/xfa_layer.js";
|
||||||
|
|
||||||
const expectedAPI = Object.freeze({
|
const expectedAPI = Object.freeze({
|
||||||
@ -105,6 +106,7 @@ const expectedAPI = Object.freeze({
|
|||||||
shadow,
|
shadow,
|
||||||
stopEvent,
|
stopEvent,
|
||||||
TextLayer,
|
TextLayer,
|
||||||
|
TouchManager,
|
||||||
UnexpectedResponseException,
|
UnexpectedResponseException,
|
||||||
Util,
|
Util,
|
||||||
VerbosityLevel,
|
VerbosityLevel,
|
||||||
|
|||||||
158
web/app.js
158
web/app.js
@ -54,6 +54,7 @@ import {
|
|||||||
PDFWorker,
|
PDFWorker,
|
||||||
shadow,
|
shadow,
|
||||||
stopEvent,
|
stopEvent,
|
||||||
|
TouchManager,
|
||||||
UnexpectedResponseException,
|
UnexpectedResponseException,
|
||||||
version,
|
version,
|
||||||
} from "pdfjs-lib";
|
} from "pdfjs-lib";
|
||||||
@ -94,14 +95,6 @@ import { ViewHistory } from "./view_history.js";
|
|||||||
|
|
||||||
const FORCE_PAGES_LOADED_TIMEOUT = 10000; // ms
|
const FORCE_PAGES_LOADED_TIMEOUT = 10000; // ms
|
||||||
|
|
||||||
// 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.
|
|
||||||
const MIN_TOUCH_DISTANCE_TO_PINCH = 35 / (window.devicePixelRatio || 1);
|
|
||||||
|
|
||||||
const ViewOnLoad = {
|
const ViewOnLoad = {
|
||||||
UNKNOWN: -1,
|
UNKNOWN: -1,
|
||||||
PREVIOUS: 0, // Default value.
|
PREVIOUS: 0, // Default value.
|
||||||
@ -182,14 +175,13 @@ const PDFViewerApplication = {
|
|||||||
_saveInProgress: false,
|
_saveInProgress: false,
|
||||||
_wheelUnusedTicks: 0,
|
_wheelUnusedTicks: 0,
|
||||||
_wheelUnusedFactor: 1,
|
_wheelUnusedFactor: 1,
|
||||||
|
_touchManager: null,
|
||||||
_touchUnusedTicks: 0,
|
_touchUnusedTicks: 0,
|
||||||
_touchUnusedFactor: 1,
|
_touchUnusedFactor: 1,
|
||||||
_PDFBug: null,
|
_PDFBug: null,
|
||||||
_hasAnnotationEditors: false,
|
_hasAnnotationEditors: false,
|
||||||
_title: document.title,
|
_title: document.title,
|
||||||
_printAnnotationStoragePromise: null,
|
_printAnnotationStoragePromise: null,
|
||||||
_touchInfo: null,
|
|
||||||
_isPinching: false,
|
|
||||||
_isCtrlKeyDown: false,
|
_isCtrlKeyDown: false,
|
||||||
_caretBrowsing: null,
|
_caretBrowsing: null,
|
||||||
_isScrolling: false,
|
_isScrolling: false,
|
||||||
@ -825,6 +817,29 @@ const PDFViewerApplication = {
|
|||||||
this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
|
this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
touchPinchCallback(origin, prevDistance, distance) {
|
||||||
|
if (this.supportsPinchToZoom) {
|
||||||
|
const newScaleFactor = this._accumulateFactor(
|
||||||
|
this.pdfViewer.currentScale,
|
||||||
|
distance / prevDistance,
|
||||||
|
"_touchUnusedFactor"
|
||||||
|
);
|
||||||
|
this.updateZoom(null, newScaleFactor, origin);
|
||||||
|
} else {
|
||||||
|
const PIXELS_PER_LINE_SCALE = 30;
|
||||||
|
const ticks = this._accumulateTicks(
|
||||||
|
(distance - prevDistance) / PIXELS_PER_LINE_SCALE,
|
||||||
|
"_touchUnusedTicks"
|
||||||
|
);
|
||||||
|
this.updateZoom(ticks, null, origin);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
touchPinchEndCallback() {
|
||||||
|
this._touchUnusedTicks = 0;
|
||||||
|
this._touchUnusedFactor = 1;
|
||||||
|
},
|
||||||
|
|
||||||
get pagesCount() {
|
get pagesCount() {
|
||||||
return this.pdfDocument ? this.pdfDocument.numPages : 0;
|
return this.pdfDocument ? this.pdfDocument.numPages : 0;
|
||||||
},
|
},
|
||||||
@ -2029,6 +2044,15 @@ const PDFViewerApplication = {
|
|||||||
_windowAbortController: { signal },
|
_windowAbortController: { signal },
|
||||||
} = this;
|
} = this;
|
||||||
|
|
||||||
|
this._touchManager = new TouchManager({
|
||||||
|
container: window,
|
||||||
|
isPinchingDisabled: () => this.pdfViewer.isInPresentationMode,
|
||||||
|
isPinchingStopped: () => this.overlayManager?.active,
|
||||||
|
onPinching: this.touchPinchCallback.bind(this),
|
||||||
|
onPinchEnd: this.touchPinchEndCallback.bind(this),
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
function addWindowResolutionChange(evt = null) {
|
function addWindowResolutionChange(evt = null) {
|
||||||
if (evt) {
|
if (evt) {
|
||||||
pdfViewer.refresh();
|
pdfViewer.refresh();
|
||||||
@ -2047,18 +2071,6 @@ const PDFViewerApplication = {
|
|||||||
passive: false,
|
passive: false,
|
||||||
signal,
|
signal,
|
||||||
});
|
});
|
||||||
window.addEventListener("touchstart", onTouchStart.bind(this), {
|
|
||||||
passive: false,
|
|
||||||
signal,
|
|
||||||
});
|
|
||||||
window.addEventListener("touchmove", onTouchMove.bind(this), {
|
|
||||||
passive: false,
|
|
||||||
signal,
|
|
||||||
});
|
|
||||||
window.addEventListener("touchend", onTouchEnd.bind(this), {
|
|
||||||
passive: false,
|
|
||||||
signal,
|
|
||||||
});
|
|
||||||
window.addEventListener("click", onClick.bind(this), { signal });
|
window.addEventListener("click", onClick.bind(this), { signal });
|
||||||
window.addEventListener("keydown", onKeyDown.bind(this), { signal });
|
window.addEventListener("keydown", onKeyDown.bind(this), { signal });
|
||||||
window.addEventListener("keyup", onKeyUp.bind(this), { signal });
|
window.addEventListener("keyup", onKeyUp.bind(this), { signal });
|
||||||
@ -2157,6 +2169,7 @@ const PDFViewerApplication = {
|
|||||||
unbindWindowEvents() {
|
unbindWindowEvents() {
|
||||||
this._windowAbortController?.abort();
|
this._windowAbortController?.abort();
|
||||||
this._windowAbortController = null;
|
this._windowAbortController = null;
|
||||||
|
this._touchManager = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2638,107 +2651,6 @@ function onWheel(evt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onTouchStart(evt) {
|
|
||||||
if (this.pdfViewer.isInPresentationMode || evt.touches.length < 2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
evt.preventDefault();
|
|
||||||
|
|
||||||
if (evt.touches.length !== 2 || this.overlayManager.active) {
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function onTouchMove(evt) {
|
|
||||||
if (!this._touchInfo || evt.touches.length !== 2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { pdfViewer, _touchInfo, supportsPinchToZoom } = this;
|
|
||||||
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 {
|
|
||||||
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) <= 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];
|
|
||||||
if (supportsPinchToZoom) {
|
|
||||||
const newScaleFactor = this._accumulateFactor(
|
|
||||||
pdfViewer.currentScale,
|
|
||||||
distance / pDistance,
|
|
||||||
"_touchUnusedFactor"
|
|
||||||
);
|
|
||||||
this.updateZoom(null, newScaleFactor, origin);
|
|
||||||
} else {
|
|
||||||
const PIXELS_PER_LINE_SCALE = 30;
|
|
||||||
const ticks = this._accumulateTicks(
|
|
||||||
(distance - pDistance) / PIXELS_PER_LINE_SCALE,
|
|
||||||
"_touchUnusedTicks"
|
|
||||||
);
|
|
||||||
this.updateZoom(ticks, null, origin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onTouchEnd(evt) {
|
|
||||||
if (!this._touchInfo) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
evt.preventDefault();
|
|
||||||
this._touchInfo = null;
|
|
||||||
this._touchUnusedTicks = 0;
|
|
||||||
this._touchUnusedFactor = 1;
|
|
||||||
this._isPinching = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeSecondaryToolbar(evt) {
|
function closeSecondaryToolbar(evt) {
|
||||||
if (!this.secondaryToolbar?.isOpen) {
|
if (!this.secondaryToolbar?.isOpen) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -53,6 +53,7 @@ const {
|
|||||||
shadow,
|
shadow,
|
||||||
stopEvent,
|
stopEvent,
|
||||||
TextLayer,
|
TextLayer,
|
||||||
|
TouchManager,
|
||||||
UnexpectedResponseException,
|
UnexpectedResponseException,
|
||||||
Util,
|
Util,
|
||||||
VerbosityLevel,
|
VerbosityLevel,
|
||||||
@ -100,6 +101,7 @@ export {
|
|||||||
shadow,
|
shadow,
|
||||||
stopEvent,
|
stopEvent,
|
||||||
TextLayer,
|
TextLayer,
|
||||||
|
TouchManager,
|
||||||
UnexpectedResponseException,
|
UnexpectedResponseException,
|
||||||
Util,
|
Util,
|
||||||
VerbosityLevel,
|
VerbosityLevel,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user