diff --git a/spine-ts/spine-webgl/example/webcomponent-tutorial.html b/spine-ts/spine-webgl/example/webcomponent-tutorial.html index c52b484a2..0b2ed2ebb 100644 --- a/spine-ts/spine-webgl/example/webcomponent-tutorial.html +++ b/spine-ts/spine-webgl/example/webcomponent-tutorial.html @@ -2938,7 +2938,7 @@ const darkPicker = document.getElementById("dark-picker"); [0, 1].forEach(async (i) => { const widget = await spine.getSpineWidget(`interactive${i}`).loadingPromise; - widget.cursorBoundsEventCallback = (event) => { + widget.cursorEventCallback = (event) => { if (event === "enter") widget.state.setAnimation(0, "emotes/hooray", true).mixDuration = .15; if (event === "leave") widget.state.setAnimation(0, "emotes/wave", true).mixDuration = .25; } @@ -2946,7 +2946,7 @@ const darkPicker = document.getElementById("dark-picker"); const tempColor = new spine.Color(); const slot = widget.skeleton.findSlot("head-base"); slot.darkColor = new spine.Color(0, 0, 0, 1); - widget.addCursorSlotEventCallbacks(slot, (slot, event) => { + widget.addCursorSlotEventCallback(slot, (slot, event) => { if (event === "down") { slot.darkColor.setFromColor(spine.Color.fromString(darkPicker.value, tempColor)); slot.color.setFromColor(spine.Color.fromString(colorPicker.value, tempColor)); @@ -2981,7 +2981,7 @@ const darkPicker = document.getElementById("dark-picker"); [0, 1].forEach(async (i) => { const widget = await spine.getSpineWidget(\`interactive\${i}\`).loadingPromise; - widget.cursorBoundsEventCallback = (event) => { + widget.cursorEventCallback = (event) => { if (event === "enter") widget.state.setAnimation(0, "emotes/hooray", true).mixDuration = .15; if (event === "leave") widget.state.setAnimation(0, "emotes/wave", true).mixDuration = .25; } @@ -2989,7 +2989,7 @@ const darkPicker = document.getElementById("dark-picker"); const tempColor = new spine.Color(); const slot = widget.skeleton.findSlot("head-base"); slot.darkColor = new spine.Color(0, 0, 0, 1); - widget.addCursorSlotEventCallbacks(slot, (slot, event) => { + widget.addCursorSlotEventCallback(slot, (slot, event) => { if (event === "down") { slot.darkColor.setFromColor(spine.Color.fromString(darkPicker.value, tempColor)); slot.color.setFromColor(spine.Color.fromString(colorPicker.value, tempColor)); @@ -3276,7 +3276,7 @@ const darkPicker = document.getElementById("dark-picker"); const emotes = widget.skeleton.data.animations.reduce((acc, { name }) => name.startsWith("emotes") ? [...acc, name] : acc, []); let leaveAnimation = "emotes/wave"; - widget.cursorBoundsEventCallback = (event) => { + widget.cursorEventCallback = (event) => { if (event === "enter") widget.state.setAnimation(0, "emotes/hooray", true).mixDuration = .15; else if (event === "leave") widget.state.setAnimation(0, leaveAnimation, true).mixDuration = .25; else if (event === "down") { @@ -3398,7 +3398,7 @@ TODO` await widgetButton.loadingPromise; widgetButton.skeleton.color.set(.85, .85, .85, 1); - widgetButton.cursorBoundsEventCallback = (event, originalEvent) => { + widgetButton.cursorEventCallback = (event, originalEvent) => { if (event === "enter") { widgetButton.state.setAnimation(0, "enhance-in", false); widgetButton.state.setAnimation(1, "shadow-in", false); @@ -3448,7 +3448,7 @@ TODO` skinSelect.addEventListener('change', (e) => widget.skin = e.target.value); // click on spineboy - widget.cursorBoundsEventCallback = (event) => { + widget.cursorEventCallback = (event) => { if (event === "down") { widget.state.setAnimation(0, "interactive/pwd/touch", false); widget.state.addAnimation(0, "interactive/head/idle", true); @@ -4027,7 +4027,7 @@ TODO` const itemSlot = widget1.skeleton.findSlot(`bubble-base-${itemName.charAt(0)}`); let onItem = false; - widget1.addCursorSlotEventCallbacks(itemSlot, (slot, event) => { + widget1.addCursorSlotEventCallback(itemSlot, (slot, event) => { if (event === "enter") { console.log("focus"); @@ -4191,7 +4191,7 @@ TODO` const widget4 = await spine.getSpineWidget("ready").loadingPromise; const slot4Bread = widget4.skeleton.findSlot("salad"); - widget4.addCursorSlotEventCallbacks(slot4Bread, (slot, event) => { + widget4.addCursorSlotEventCallback(slot4Bread, (slot, event) => { if (event === "enter") { widget4.state.setAnimation(1, "bread-opening", false); @@ -4205,7 +4205,7 @@ TODO` }); const slot4Bottle = widget4.skeleton.findSlot("bottle-base"); - widget4.addCursorSlotEventCallbacks(slot4Bottle, (slot, event) => { + widget4.addCursorSlotEventCallback(slot4Bottle, (slot, event) => { if (event === "enter") { widget4.state.setAnimation(2, "bottle-opening", false); @@ -4219,7 +4219,7 @@ TODO` }); const slot4Fries = widget4.skeleton.findSlot("fries-case-back"); - widget4.addCursorSlotEventCallbacks(slot4Fries, (slot, event) => { + widget4.addCursorSlotEventCallback(slot4Fries, (slot, event) => { if (event === "enter") { widget4.state.setAnimation(3, "fries", true); @@ -4334,7 +4334,7 @@ TODO` spineboy.skeleton.slots.forEach(slot => { if (slot.data.name === "gun") { - spineboy.addCursorSlotEventCallbacks(slot, (slot,event) => { + spineboy.addCursorSlotEventCallback(slot, (slot,event) => { if (event === "down") { spineboy.state.setAnimation(1, "shoot", false); } @@ -4342,7 +4342,7 @@ TODO` } if (slot.data.name === "torso") { - spineboy.addCursorSlotEventCallbacks(slot, (slot,event) => { + spineboy.addCursorSlotEventCallback(slot, (slot,event) => { if (event === "down") { spineboy.state.setAnimation(0, "jump", false).mixDuration = 0.2; spineboy.state.addAnimation(0, "walk", true).mixDuration = 0.2; @@ -4351,7 +4351,7 @@ TODO` } if (slot.data.name === "head") { - spineboy.addCursorSlotEventCallbacks(slot, (slot,event) => { + spineboy.addCursorSlotEventCallback(slot, (slot,event) => { if (event === "down") { spineboy.state.setAnimation(1, "run", true).mixDuration = 0.2; } else { @@ -4390,7 +4390,7 @@ TODO` flowers.push(slot); - windmill.addCursorSlotEventCallbacks(slot, (slot, event) => { + windmill.addCursorSlotEventCallback(slot, (slot, event) => { if (ammo === 0) return; if (event !== "down") return; diff --git a/spine-ts/spine-webgl/src/SpineWebComponentWidget.ts b/spine-ts/spine-webgl/src/SpineWebComponentWidget.ts index a8198176b..4c2fd2b76 100644 --- a/spine-ts/spine-webgl/src/SpineWebComponentWidget.ts +++ b/spine-ts/spine-webgl/src/SpineWebComponentWidget.ts @@ -160,8 +160,8 @@ function castToAnimationsInfo (value: string | null): AnimationsInfo | undefined export type AttributeTypes = "string" | "number" | "boolean" | "array-number" | "array-string" | "object" | "fitType" | "modeType" | "offScreenUpdateBehaviourType" | "animationsInfo"; -export type CursorEventTypes = "down" | "up" | "enter" | "leave" | "move" | "drag"; -export type CursorEventTypesInput = Exclude; +export type CursorEventType = "down" | "up" | "enter" | "leave" | "move" | "drag"; +export type CursorEventTypesInput = Exclude; // The properties that map to widget attributes interface WidgetAttributes { @@ -539,22 +539,42 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable, public isInteractive = false; /** - * If the widget is interactive, this method is invoked with a {@link CursorEventTypes} when the cursor + * If the widget is interactive, this method is invoked with a {@link CursorEventType} when the cursor * performs actions within the widget bounds (for example, it enter or leaves the bounds). * By default, the function does nothing. * This is an experimental property and might be removed in the future. */ - public cursorBoundsEventCallback = (event: CursorEventTypes, originalEvent?: UIEvent) => { } + public cursorEventCallback = (event: CursorEventType, originalEvent?: UIEvent) => { } // TODO: probably it makes sense to associate a single callback to a groups of slots to avoid the same callback to be called for each slot of the group /** * This methods allows to associate to a Slot a callback. For these slots, if the widget is interactive, * when the cursor performs actions within the slot's attachment the associated callback is invoked with - * a {@link CursorEventTypes} (for example, it enter or leaves the slot's attachment bounds). + * a {@link CursorEventType} (for example, it enter or leaves the slot's attachment bounds). * This is an experimental property and might be removed in the future. */ - public addCursorSlotEventCallbacks (slot: Slot, slotFunction: (slot: Slot, event: CursorEventTypes) => void) { - this.cursorSlotEventCallbacks.set(slot, { slotFunction, inside: false }); + public addCursorSlotEventCallback (slot: number | string | Slot, slotFunction: (slot: Slot, event: CursorEventType) => void) { + this.cursorSlotEventCallbacks.set(this.getSlotFromRef(slot), { slotFunction, inside: false }); + } + + /** + * Remove callbacks added through {@link addCursorSlotEventCallback}. + * @param slot: the slot reference to which remove the associated callback + */ + public removeCursorSlotEventCallbacks(slot: number | string | Slot) { + this.cursorSlotEventCallbacks.delete(this.getSlotFromRef(slot)); + } + + private getSlotFromRef (slotRef: number | string | Slot): Slot { + let slot: Slot | null; + + if (typeof slotRef === 'number') slot = this.skeleton!.slots[slotRef]; + else if (typeof slotRef === 'string') slot = this.skeleton!.findSlot(slotRef); + else slot = slotRef; + + if (!slot) throw new Error(`No slot found with the given slot reference: ${slotRef}`); + + return slot; } /** @@ -1136,7 +1156,7 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable, * @internal */ public cursorSlotEventCallbacks: Map void, + slotFunction: (slot: Slot, event: CursorEventType, originalEvent?: UIEvent) => void, inside: boolean, }> = new Map(); @@ -1151,19 +1171,19 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable, } private checkBoundsInteraction (type: CursorEventTypesInput, originalEvent?: UIEvent) { - if (this.checkCursorInsideBounds()) { + if (this.isCursorInsideBounds()) { if (!this.cursorInsideBounds) { - this.cursorBoundsEventCallback("enter", originalEvent); + this.cursorEventCallback("enter", originalEvent); } this.cursorInsideBounds = true; - this.cursorBoundsEventCallback(type, originalEvent); + this.cursorEventCallback(type, originalEvent); } else { if (this.cursorInsideBounds) { - this.cursorBoundsEventCallback("leave", originalEvent); + this.cursorEventCallback("leave", originalEvent); } this.cursorInsideBounds = false; @@ -1173,7 +1193,7 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable, /** * @internal */ - public checkCursorInsideBounds (): boolean { + public isCursorInsideBounds (): boolean { if (!this.onScreen || !this.skeleton) return false; this.pointTemp.set( @@ -1976,7 +1996,7 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes, public cursorWorldY = 1; private tempVector = new Vector3(); - private cursorUpdate (input: Point) { + private updateCursor (input: Point) { this.cursorCanvasX = input.x - window.scrollX; this.cursorCanvasY = input.y - window.scrollY; @@ -1995,8 +2015,7 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes, this.cursorWorldY = tempVector.y; } - // return true if updated - private cursorWidgetUpdate (widget: SpineWebComponentWidget): boolean { + private updateWidgetCursor (widget: SpineWebComponentWidget): boolean { if (widget.worldX === Infinity) return false; widget.cursorWorldX = this.cursorWorldX - widget.worldX; @@ -2023,10 +2042,10 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes, // moved is used to pass cursor position wrt to canvas and widget position and currently is EXPERIMENTAL moved: (x, y, ev) => { const input = getInput(ev); - this.cursorUpdate(input); + this.updateCursor(input); this.skeletonList.forEach(widget => { - if (!this.cursorWidgetUpdate(widget) || !widget.onScreen) return; + if (!this.updateWidgetCursor(widget) || !widget.onScreen) return; widget.cursorEventUpdate("move", ev); }); @@ -2034,14 +2053,14 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes, down: (x, y, ev) => { const input = getInput(ev); - this.cursorUpdate(input); + this.updateCursor(input); this.skeletonList.forEach(widget => { - if (!this.cursorWidgetUpdate(widget) || !widget.onScreen && widget.dragX === 0 && widget.dragY === 0) return; + if (!this.updateWidgetCursor(widget) || !widget.onScreen && widget.dragX === 0 && widget.dragY === 0) return; widget.cursorEventUpdate("down", ev); - if ((widget.isInteractive && widget.cursorInsideBounds) || (!widget.isInteractive && widget.checkCursorInsideBounds())) { + if ((widget.isInteractive && widget.cursorInsideBounds) || (!widget.isInteractive && widget.isCursorInsideBounds())) { if (!widget.isDraggable) return; widget.dragging = true; @@ -2059,10 +2078,10 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes, let dragX = input.x - prevX; let dragY = input.y - prevY; - this.cursorUpdate(input); + this.updateCursor(input); this.skeletonList.forEach(widget => { - if (!this.cursorWidgetUpdate(widget) || !widget.onScreen && widget.dragX === 0 && widget.dragY === 0) return; + if (!this.updateWidgetCursor(widget) || !widget.onScreen && widget.dragX === 0 && widget.dragY === 0) return; widget.cursorEventUpdate("drag", ev);