Skip to content

Commit

Permalink
Manage lifecycle for Input and SpineWebComponentOverlay. Missing Spin…
Browse files Browse the repository at this point in the history
…eWebComponentWidget.
  • Loading branch information
davidetan committed Sep 27, 2024
1 parent e260344 commit 5c42b6f
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 68 deletions.
74 changes: 51 additions & 23 deletions spine-ts/spine-webgl/src/Input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -74,34 +81,25 @@ 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;
if (ev.deltaMode == WheelEvent.DOM_DELTA_PAGE) deltaY *= 24;
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);
Expand All @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down
113 changes: 68 additions & 45 deletions spine-ts/spine-webgl/src/SpineWebComponentWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
AtlasAttachmentLoader,
AssetManager,
Color,
Disposable,
Input,
LoadingScreen,
ManagedWebGLRenderingContext,
Expand Down Expand Up @@ -810,18 +811,21 @@ 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;
private canvas:HTMLCanvasElement;
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
Expand All @@ -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() {
Expand Down Expand Up @@ -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();
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();

this.scrollHandler();
};
// translateCanvas starts a requestAnimationFrame loop
this.translateCanvas();

this.input = new Input(document.body, false);
this.setupDragUtility();
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);
Expand All @@ -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 }) => {
Expand Down Expand Up @@ -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();
Expand All @@ -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 => {
Expand All @@ -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 => {
Expand Down Expand Up @@ -1257,7 +1284,9 @@ class SpineWebComponentOverlay extends HTMLElement {
widget.dragging = false;
});
}
})
});

return inputManager;
}

/*
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 5c42b6f

Please sign in to comment.