mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +08:00
Manage lifecycle for Input and SpineWebComponentOverlay. Missing SpineWebComponentWidget.
This commit is contained in:
parent
e26034426a
commit
5c42b6fa2d
@ -27,7 +27,8 @@
|
|||||||
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
export class Input {
|
import { Disposable } from "./index.js"
|
||||||
|
export class Input implements Disposable {
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
mouseX = 0;
|
mouseX = 0;
|
||||||
mouseY = 0;
|
mouseY = 0;
|
||||||
@ -37,28 +38,34 @@ export class Input {
|
|||||||
initialPinchDistance = 0;
|
initialPinchDistance = 0;
|
||||||
private listeners = new Array<InputListener>();
|
private listeners = new Array<InputListener>();
|
||||||
private autoPreventDefault: boolean;
|
private autoPreventDefault: boolean;
|
||||||
|
private callbacks: {
|
||||||
|
mouseDown: (ev: UIEvent) => void;
|
||||||
|
mouseMove: (ev: UIEvent) => void;
|
||||||
|
mouseUp: (ev: UIEvent) => void;
|
||||||
|
mouseWheel: (ev: WheelEvent) => void;
|
||||||
|
touchStart: (ev: TouchEvent) => void;
|
||||||
|
touchMove: (ev: TouchEvent) => void;
|
||||||
|
touchEnd: (ev: TouchEvent) => void;
|
||||||
|
};
|
||||||
|
|
||||||
constructor (element: HTMLElement, autoPreventDefault = true) {
|
constructor (element: HTMLElement, autoPreventDefault = true) {
|
||||||
this.element = element;
|
this.element = element;
|
||||||
this.autoPreventDefault = autoPreventDefault;
|
this.autoPreventDefault = autoPreventDefault;
|
||||||
this.setupCallbacks(element);
|
this.callbacks = this.setupCallbacks(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupCallbacks (element: HTMLElement) {
|
private setupCallbacks (element: HTMLElement) {
|
||||||
let mouseDown = (ev: UIEvent) => {
|
const mouseDown = (ev: UIEvent) => {
|
||||||
if (ev instanceof MouseEvent) {
|
if (ev instanceof MouseEvent) {
|
||||||
let rect = element.getBoundingClientRect();
|
let rect = element.getBoundingClientRect();
|
||||||
this.mouseX = ev.clientX - rect.left;
|
this.mouseX = ev.clientX - rect.left;
|
||||||
this.mouseY = ev.clientY - rect.top;
|
this.mouseY = ev.clientY - rect.top;
|
||||||
this.buttonDown = true;
|
this.buttonDown = true;
|
||||||
this.listeners.map((listener) => { if (listener.down) listener.down(this.mouseX, this.mouseY, ev); });
|
this.listeners.map((listener) => { if (listener.down) listener.down(this.mouseX, this.mouseY, ev); });
|
||||||
|
|
||||||
document.addEventListener("mousemove", mouseMove);
|
|
||||||
document.addEventListener("mouseup", mouseUp);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mouseMove = (ev: UIEvent) => {
|
const mouseMove = (ev: UIEvent) => {
|
||||||
if (ev instanceof MouseEvent) {
|
if (ev instanceof MouseEvent) {
|
||||||
let rect = element.getBoundingClientRect();
|
let rect = element.getBoundingClientRect();
|
||||||
this.mouseX = ev.clientX - rect.left;
|
this.mouseX = ev.clientX - rect.left;
|
||||||
@ -74,20 +81,17 @@ export class Input {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mouseUp = (ev: UIEvent) => {
|
const mouseUp = (ev: UIEvent) => {
|
||||||
if (ev instanceof MouseEvent) {
|
if (ev instanceof MouseEvent) {
|
||||||
let rect = element.getBoundingClientRect();
|
let rect = element.getBoundingClientRect();
|
||||||
this.mouseX = ev.clientX - rect.left;;
|
this.mouseX = ev.clientX - rect.left;;
|
||||||
this.mouseY = ev.clientY - rect.top;
|
this.mouseY = ev.clientY - rect.top;
|
||||||
this.buttonDown = false;
|
this.buttonDown = false;
|
||||||
this.listeners.map((listener) => { if (listener.up) listener.up(this.mouseX, this.mouseY, ev); });
|
this.listeners.map((listener) => { if (listener.up) listener.up(this.mouseX, this.mouseY, ev); });
|
||||||
|
|
||||||
document.removeEventListener("mousemove", mouseMove);
|
|
||||||
document.removeEventListener("mouseup", mouseUp);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mouseWheel = (ev: WheelEvent) => {
|
const mouseWheel = (ev: WheelEvent) => {
|
||||||
if (this.autoPreventDefault) ev.preventDefault();
|
if (this.autoPreventDefault) ev.preventDefault();
|
||||||
let deltaY = ev.deltaY;
|
let deltaY = ev.deltaY;
|
||||||
if (ev.deltaMode == WheelEvent.DOM_DELTA_LINE) deltaY *= 8;
|
if (ev.deltaMode == WheelEvent.DOM_DELTA_LINE) deltaY *= 8;
|
||||||
@ -95,13 +99,7 @@ export class Input {
|
|||||||
this.listeners.map((listener) => { if (listener.wheel) listener.wheel(ev.deltaY, ev); });
|
this.listeners.map((listener) => { if (listener.wheel) listener.wheel(ev.deltaY, ev); });
|
||||||
};
|
};
|
||||||
|
|
||||||
element.addEventListener("mousedown", mouseDown, true);
|
const touchStart = (ev: TouchEvent) => {
|
||||||
element.addEventListener("mousemove", mouseMove, true);
|
|
||||||
element.addEventListener("mouseup", mouseUp, true);
|
|
||||||
element.addEventListener("wheel", mouseWheel, true);
|
|
||||||
|
|
||||||
|
|
||||||
element.addEventListener("touchstart", (ev: TouchEvent) => {
|
|
||||||
if (!this.touch0 || !this.touch1) {
|
if (!this.touch0 || !this.touch1) {
|
||||||
var touches = ev.changedTouches;
|
var touches = ev.changedTouches;
|
||||||
let nativeTouch = touches.item(0);
|
let nativeTouch = touches.item(0);
|
||||||
@ -126,9 +124,9 @@ export class Input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.autoPreventDefault) ev.preventDefault();
|
if (this.autoPreventDefault) ev.preventDefault();
|
||||||
}, { passive: false, capture: false });
|
}
|
||||||
|
|
||||||
element.addEventListener("touchmove", (ev: TouchEvent) => {
|
const touchMove = (ev: TouchEvent) => {
|
||||||
if (this.touch0) {
|
if (this.touch0) {
|
||||||
var touches = ev.changedTouches;
|
var touches = ev.changedTouches;
|
||||||
let rect = element.getBoundingClientRect();
|
let rect = element.getBoundingClientRect();
|
||||||
@ -155,9 +153,9 @@ export class Input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.autoPreventDefault) ev.preventDefault();
|
if (this.autoPreventDefault) ev.preventDefault();
|
||||||
}, { passive: false, capture: false });
|
}
|
||||||
|
|
||||||
let touchEnd = (ev: TouchEvent) => {
|
const touchEnd = (ev: TouchEvent) => {
|
||||||
if (this.touch0) {
|
if (this.touch0) {
|
||||||
var touches = ev.changedTouches;
|
var touches = ev.changedTouches;
|
||||||
let rect = element.getBoundingClientRect();
|
let rect = element.getBoundingClientRect();
|
||||||
@ -193,8 +191,38 @@ export class Input {
|
|||||||
}
|
}
|
||||||
if (this.autoPreventDefault) ev.preventDefault();
|
if (this.autoPreventDefault) ev.preventDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
element.addEventListener("mousedown", mouseDown, true);
|
||||||
|
element.addEventListener("mousemove", mouseMove, true);
|
||||||
|
element.addEventListener("mouseup", mouseUp, true);
|
||||||
|
element.addEventListener("wheel", mouseWheel, true);
|
||||||
|
element.addEventListener("touchstart", touchStart, { passive: false, capture: false });
|
||||||
|
element.addEventListener("touchmove", touchMove, { passive: false, capture: false });
|
||||||
element.addEventListener("touchend", touchEnd, { passive: false, capture: false });
|
element.addEventListener("touchend", touchEnd, { passive: false, capture: false });
|
||||||
element.addEventListener("touchcancel", touchEnd);
|
element.addEventListener("touchcancel", touchEnd);
|
||||||
|
|
||||||
|
return {
|
||||||
|
mouseDown,
|
||||||
|
mouseMove,
|
||||||
|
mouseUp,
|
||||||
|
mouseWheel,
|
||||||
|
touchStart,
|
||||||
|
touchMove,
|
||||||
|
touchEnd,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
const element = this.element;
|
||||||
|
element.addEventListener("mousedown", this.callbacks.mouseDown, true);
|
||||||
|
element.addEventListener("mousemove", this.callbacks.mouseMove, true);
|
||||||
|
element.addEventListener("mouseup", this.callbacks.mouseUp, true);
|
||||||
|
element.addEventListener("wheel", this.callbacks.mouseWheel, true);
|
||||||
|
element.addEventListener("touchstart", this.callbacks.touchStart, { passive: false, capture: false });
|
||||||
|
element.addEventListener("touchmove", this.callbacks.touchMove, { passive: false, capture: false });
|
||||||
|
element.addEventListener("touchend", this.callbacks.touchEnd, { passive: false, capture: false });
|
||||||
|
element.addEventListener("touchcancel", this.callbacks.touchEnd);
|
||||||
|
this.listeners.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
addListener (listener: InputListener) {
|
addListener (listener: InputListener) {
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import {
|
|||||||
AtlasAttachmentLoader,
|
AtlasAttachmentLoader,
|
||||||
AssetManager,
|
AssetManager,
|
||||||
Color,
|
Color,
|
||||||
|
Disposable,
|
||||||
Input,
|
Input,
|
||||||
LoadingScreen,
|
LoadingScreen,
|
||||||
ManagedWebGLRenderingContext,
|
ManagedWebGLRenderingContext,
|
||||||
@ -810,7 +811,12 @@ export class SpineWebComponentWidget extends HTMLElement implements WidgetAttrib
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SpineWebComponentOverlay extends HTMLElement {
|
class SpineWebComponentOverlay extends HTMLElement implements Disposable {
|
||||||
|
|
||||||
|
public skeletonList = new Array<SpineWebComponentWidget>();
|
||||||
|
public renderer: SceneRenderer;
|
||||||
|
public assetManager: AssetManager;
|
||||||
|
|
||||||
private root: ShadowRoot;
|
private root: ShadowRoot;
|
||||||
|
|
||||||
private div: HTMLDivElement;
|
private div: HTMLDivElement;
|
||||||
@ -818,10 +824,8 @@ class SpineWebComponentOverlay extends HTMLElement {
|
|||||||
private fps: HTMLSpanElement;
|
private fps: HTMLSpanElement;
|
||||||
private fpsAppended = false;
|
private fpsAppended = false;
|
||||||
|
|
||||||
public skeletonList = new Array<SpineWebComponentWidget>();
|
|
||||||
|
|
||||||
private intersectionObserver? : IntersectionObserver;
|
private intersectionObserver? : IntersectionObserver;
|
||||||
private input: Input;
|
private input?: Input;
|
||||||
|
|
||||||
// how many pixels to add to the edges to prevent "edge cuttin" on fast scrolling
|
// how many pixels to add to the edges to prevent "edge cuttin" on fast scrolling
|
||||||
// be aware that the canvas is already big as the display size
|
// be aware that the canvas is already big as the display size
|
||||||
@ -836,9 +840,8 @@ class SpineWebComponentOverlay extends HTMLElement {
|
|||||||
private currentCanvasBaseWidth = 0;
|
private currentCanvasBaseWidth = 0;
|
||||||
private currentCanvasBaseHeight = 0;
|
private currentCanvasBaseHeight = 0;
|
||||||
|
|
||||||
public renderer: SceneRenderer;
|
|
||||||
public assetManager: AssetManager;
|
|
||||||
private disposed = false;
|
private disposed = false;
|
||||||
|
private detached = true;
|
||||||
readonly time = new TimeKeeper();
|
readonly time = new TimeKeeper();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -873,40 +876,40 @@ class SpineWebComponentOverlay extends HTMLElement {
|
|||||||
const context = new ManagedWebGLRenderingContext(this.canvas, { alpha: true });
|
const context = new ManagedWebGLRenderingContext(this.canvas, { alpha: true });
|
||||||
this.renderer = new SceneRenderer(this.canvas, context);
|
this.renderer = new SceneRenderer(this.canvas, context);
|
||||||
this.assetManager = new AssetManager(context);
|
this.assetManager = new AssetManager(context);
|
||||||
this.input = new Input(this.canvas, false);
|
|
||||||
this.setupRenderingElements();
|
|
||||||
|
|
||||||
this.overflowLeftSize = this.overflowLeft * document.documentElement.clientWidth;
|
this.overflowLeftSize = this.overflowLeft * document.documentElement.clientWidth;
|
||||||
this.overflowTopSize = this.overflowTop * document.documentElement.clientHeight;
|
this.overflowTopSize = this.overflowTop * document.documentElement.clientHeight;
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
this.updateCanvasSize();
|
|
||||||
this.zoomHandler();
|
|
||||||
});
|
|
||||||
|
|
||||||
window.screen.orientation.onchange = () => {
|
|
||||||
this.updateCanvasSize();
|
|
||||||
// after an orientation change the scrolling changes, but the scroll event does not fire
|
|
||||||
this.scrollHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("scroll", this.scrollHandler);
|
|
||||||
|
|
||||||
window.onload = () => {
|
|
||||||
this.updateCanvasSize();
|
|
||||||
this.zoomHandler();
|
|
||||||
|
|
||||||
// translateCanvas starts a requestAnimationFrame loop
|
|
||||||
this.translateCanvas();
|
|
||||||
|
|
||||||
this.scrollHandler();
|
|
||||||
};
|
|
||||||
|
|
||||||
this.input = new Input(document.body, false);
|
|
||||||
this.setupDragUtility();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private resizeCallback = () => {
|
||||||
|
this.updateCanvasSize();
|
||||||
|
this.zoomHandler();
|
||||||
|
}
|
||||||
|
private orientationChangeCallback = () => {
|
||||||
|
this.updateCanvasSize();
|
||||||
|
// after an orientation change the scrolling changes, but the scroll event does not fire
|
||||||
|
this.scrollHandler();
|
||||||
|
}
|
||||||
|
// right now, we scroll the canvas each frame, that makes scrolling on mobile waaay more smoother
|
||||||
|
// this is way scroll handler do nothing
|
||||||
|
private scrollHandler = () => {
|
||||||
|
// this.translateCanvas();
|
||||||
|
}
|
||||||
|
private onLoadCallback = () => {
|
||||||
|
this.updateCanvasSize();
|
||||||
|
this.zoomHandler();
|
||||||
|
|
||||||
|
// translateCanvas starts a requestAnimationFrame loop
|
||||||
|
this.translateCanvas();
|
||||||
|
|
||||||
|
this.scrollHandler();
|
||||||
|
}
|
||||||
connectedCallback(): void {
|
connectedCallback(): void {
|
||||||
|
window.addEventListener("resize", this.resizeCallback);
|
||||||
|
window.addEventListener("scroll", this.scrollHandler);
|
||||||
|
window.addEventListener("load", this.onLoadCallback);
|
||||||
|
window.screen.orientation.addEventListener('change', this.orientationChangeCallback);
|
||||||
|
|
||||||
this.intersectionObserver = new IntersectionObserver((widgets) => {
|
this.intersectionObserver = new IntersectionObserver((widgets) => {
|
||||||
widgets.forEach(({ isIntersecting, target, intersectionRatio }) => {
|
widgets.forEach(({ isIntersecting, target, intersectionRatio }) => {
|
||||||
const widget = this.skeletonList.find(w => w.getHTMLElementReference() == target);
|
const widget = this.skeletonList.find(w => w.getHTMLElementReference() == target);
|
||||||
@ -923,17 +926,40 @@ class SpineWebComponentOverlay extends HTMLElement {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, { rootMargin: "30px 20px 30px 20px" });
|
}, { rootMargin: "30px 20px 30px 20px" });
|
||||||
|
this.skeletonList.forEach((widget) => {
|
||||||
|
this.intersectionObserver?.observe(widget.getHTMLElementReference());
|
||||||
|
})
|
||||||
|
this.input = this.setupDragUtility();
|
||||||
|
|
||||||
|
this.detached = false;
|
||||||
|
|
||||||
|
this.startRenderingLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback(): void {
|
disconnectedCallback(): void {
|
||||||
|
window.removeEventListener("resize", this.resizeCallback);
|
||||||
|
window.removeEventListener("scroll", this.scrollHandler);
|
||||||
|
window.removeEventListener("load", this.onLoadCallback);
|
||||||
|
window.screen.orientation.removeEventListener('change', this.orientationChangeCallback);
|
||||||
|
this.intersectionObserver?.disconnect();
|
||||||
|
this.input?.dispose();
|
||||||
|
this.detached = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
document.body.removeChild(this);
|
||||||
|
this.skeletonList.length = 0;
|
||||||
|
this.renderer.dispose();
|
||||||
|
this.disposed = true;
|
||||||
|
this.detached = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addWidget(widget: SpineWebComponentWidget) {
|
addWidget(widget: SpineWebComponentWidget) {
|
||||||
this.skeletonList.push(widget);
|
this.skeletonList.push(widget);
|
||||||
this.intersectionObserver!.observe(widget.getHTMLElementReference());
|
this.intersectionObserver?.observe(widget.getHTMLElementReference());
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupRenderingElements() {
|
private startRenderingLoop() {
|
||||||
const updateWidgets = () => {
|
const updateWidgets = () => {
|
||||||
const delta = this.time.delta;
|
const delta = this.time.delta;
|
||||||
this.skeletonList.forEach(({ skeleton, state, update, onScreen, offScreenUpdateBehaviour, beforeUpdateWorldTransforms, afterUpdateWorldTransforms }) => {
|
this.skeletonList.forEach(({ skeleton, state, update, onScreen, offScreenUpdateBehaviour, beforeUpdateWorldTransforms, afterUpdateWorldTransforms }) => {
|
||||||
@ -1195,7 +1221,7 @@ class SpineWebComponentOverlay extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loop = () => {
|
const loop = () => {
|
||||||
if (this.disposed) return;
|
if (this.disposed || this.detached) return;
|
||||||
requestAnimationFrame(loop);
|
requestAnimationFrame(loop);
|
||||||
this.time.update();
|
this.time.update();
|
||||||
updateWidgets();
|
updateWidgets();
|
||||||
@ -1210,8 +1236,9 @@ class SpineWebComponentOverlay extends HTMLElement {
|
|||||||
const transparentWhite = new Color(1, 1, 1, .3);
|
const transparentWhite = new Color(1, 1, 1, .3);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupDragUtility() {
|
private setupDragUtility(): Input {
|
||||||
// TODO: we should use document - body might have some margin that offset the click events - Meanwhile I take event pageX/Y
|
// TODO: we should use document - body might have some margin that offset the click events - Meanwhile I take event pageX/Y
|
||||||
|
const inputManager = new Input(document.body, false)
|
||||||
const point: Point = { x: 0, y: 0 };
|
const point: Point = { x: 0, y: 0 };
|
||||||
|
|
||||||
const getInput = (ev?: MouseEvent | TouchEvent): Point => {
|
const getInput = (ev?: MouseEvent | TouchEvent): Point => {
|
||||||
@ -1223,7 +1250,7 @@ class SpineWebComponentOverlay extends HTMLElement {
|
|||||||
|
|
||||||
let prevX = 0;
|
let prevX = 0;
|
||||||
let prevY = 0;
|
let prevY = 0;
|
||||||
this.input.addListener({
|
inputManager.addListener({
|
||||||
down: (x, y, ev) => {
|
down: (x, y, ev) => {
|
||||||
const input = getInput(ev);
|
const input = getInput(ev);
|
||||||
this.skeletonList.forEach(widget => {
|
this.skeletonList.forEach(widget => {
|
||||||
@ -1257,7 +1284,9 @@ class SpineWebComponentOverlay extends HTMLElement {
|
|||||||
widget.dragging = false;
|
widget.dragging = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
return inputManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1306,12 +1335,6 @@ class SpineWebComponentOverlay extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// right now, we scroll the canvas each frame, that makes scrolling on mobile waaay more smoother
|
|
||||||
// this is way scroll handler do nothing
|
|
||||||
private scrollHandler = () => {
|
|
||||||
// this.translateCanvas();
|
|
||||||
}
|
|
||||||
|
|
||||||
private translateCanvas() {
|
private translateCanvas() {
|
||||||
const scrollPositionX = window.scrollX - this.overflowLeftSize;
|
const scrollPositionX = window.scrollX - this.overflowLeftSize;
|
||||||
const scrollPositionY = window.scrollY - this.overflowTopSize;
|
const scrollPositionY = window.scrollY - this.overflowTopSize;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user