Manage lifecycle for Input and SpineWebComponentOverlay. Missing SpineWebComponentWidget.

This commit is contained in:
Davide Tantillo 2024-09-27 16:06:23 +02:00
parent e26034426a
commit 5c42b6fa2d
2 changed files with 121 additions and 70 deletions

View File

@ -27,7 +27,8 @@
* 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;
mouseX = 0;
mouseY = 0;
@ -37,28 +38,34 @@ export class Input {
initialPinchDistance = 0;
private listeners = new Array<InputListener>();
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) {
this.element = element;
this.autoPreventDefault = autoPreventDefault;
this.setupCallbacks(element);
this.callbacks = this.setupCallbacks(element);
}
private setupCallbacks (element: HTMLElement) {
let mouseDown = (ev: UIEvent) => {
const mouseDown = (ev: UIEvent) => {
if (ev instanceof MouseEvent) {
let rect = element.getBoundingClientRect();
this.mouseX = ev.clientX - rect.left;
this.mouseY = ev.clientY - rect.top;
this.buttonDown = true;
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) {
let rect = element.getBoundingClientRect();
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) {
let rect = element.getBoundingClientRect();
this.mouseX = ev.clientX - rect.left;;
this.mouseY = ev.clientY - rect.top;
this.buttonDown = false;
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();
let deltaY = ev.deltaY;
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); });
};
element.addEventListener("mousedown", mouseDown, true);
element.addEventListener("mousemove", mouseMove, true);
element.addEventListener("mouseup", mouseUp, true);
element.addEventListener("wheel", mouseWheel, true);
element.addEventListener("touchstart", (ev: TouchEvent) => {
const touchStart = (ev: TouchEvent) => {
if (!this.touch0 || !this.touch1) {
var touches = ev.changedTouches;
let nativeTouch = touches.item(0);
@ -126,9 +124,9 @@ export class Input {
}
}
if (this.autoPreventDefault) ev.preventDefault();
}, { passive: false, capture: false });
}
element.addEventListener("touchmove", (ev: TouchEvent) => {
const touchMove = (ev: TouchEvent) => {
if (this.touch0) {
var touches = ev.changedTouches;
let rect = element.getBoundingClientRect();
@ -155,9 +153,9 @@ export class Input {
}
}
if (this.autoPreventDefault) ev.preventDefault();
}, { passive: false, capture: false });
}
let touchEnd = (ev: TouchEvent) => {
const touchEnd = (ev: TouchEvent) => {
if (this.touch0) {
var touches = ev.changedTouches;
let rect = element.getBoundingClientRect();
@ -193,8 +191,38 @@ export class Input {
}
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("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) {

View File

@ -34,6 +34,7 @@ import {
AtlasAttachmentLoader,
AssetManager,
Color,
Disposable,
Input,
LoadingScreen,
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 div: HTMLDivElement;
@ -818,10 +824,8 @@ class SpineWebComponentOverlay extends HTMLElement {
private fps: HTMLSpanElement;
private fpsAppended = false;
public skeletonList = new Array<SpineWebComponentWidget>();
private intersectionObserver? : IntersectionObserver;
private input: Input;
private input?: Input;
// 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
@ -836,9 +840,8 @@ class SpineWebComponentOverlay extends HTMLElement {
private currentCanvasBaseWidth = 0;
private currentCanvasBaseHeight = 0;
public renderer: SceneRenderer;
public assetManager: AssetManager;
private disposed = false;
private detached = true;
readonly time = new TimeKeeper();
constructor() {
@ -873,40 +876,40 @@ class SpineWebComponentOverlay extends HTMLElement {
const context = new ManagedWebGLRenderingContext(this.canvas, { alpha: true });
this.renderer = new SceneRenderer(this.canvas, context);
this.assetManager = new AssetManager(context);
this.input = new Input(this.canvas, false);
this.setupRenderingElements();
this.overflowLeftSize = this.overflowLeft * document.documentElement.clientWidth;
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 {
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) => {
widgets.forEach(({ isIntersecting, target, intersectionRatio }) => {
const widget = this.skeletonList.find(w => w.getHTMLElementReference() == target);
@ -923,17 +926,40 @@ class SpineWebComponentOverlay extends HTMLElement {
}
})
}, { 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 {
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) {
this.skeletonList.push(widget);
this.intersectionObserver!.observe(widget.getHTMLElementReference());
this.intersectionObserver?.observe(widget.getHTMLElementReference());
}
private setupRenderingElements() {
private startRenderingLoop() {
const updateWidgets = () => {
const delta = this.time.delta;
this.skeletonList.forEach(({ skeleton, state, update, onScreen, offScreenUpdateBehaviour, beforeUpdateWorldTransforms, afterUpdateWorldTransforms }) => {
@ -1195,7 +1221,7 @@ class SpineWebComponentOverlay extends HTMLElement {
}
const loop = () => {
if (this.disposed) return;
if (this.disposed || this.detached) return;
requestAnimationFrame(loop);
this.time.update();
updateWidgets();
@ -1210,8 +1236,9 @@ class SpineWebComponentOverlay extends HTMLElement {
const transparentWhite = new Color(1, 1, 1, .3);
}
private setupDragUtility() {
// TODO: we should use document - body might have some margin that offset the click events - Meanwhile I take event pageX/Y
private setupDragUtility(): Input {
// 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 getInput = (ev?: MouseEvent | TouchEvent): Point => {
@ -1223,7 +1250,7 @@ class SpineWebComponentOverlay extends HTMLElement {
let prevX = 0;
let prevY = 0;
this.input.addListener({
inputManager.addListener({
down: (x, y, ev) => {
const input = getInput(ev);
this.skeletonList.forEach(widget => {
@ -1257,7 +1284,9 @@ class SpineWebComponentOverlay extends HTMLElement {
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() {
const scrollPositionX = window.scrollX - this.overflowLeftSize;
const scrollPositionY = window.scrollY - this.overflowTopSize;