Update bounds and slot interaction method names.

This commit is contained in:
Davide Tantillo 2025-04-30 12:27:29 +02:00
parent 0c90eed036
commit 15375fb20c
2 changed files with 57 additions and 38 deletions

View File

@ -2938,7 +2938,7 @@ const darkPicker = document.getElementById("dark-picker");
[0, 1].forEach(async (i) => { [0, 1].forEach(async (i) => {
const widget = await spine.getSpineWidget(`interactive${i}`).loadingPromise; 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 === "enter") widget.state.setAnimation(0, "emotes/hooray", true).mixDuration = .15;
if (event === "leave") widget.state.setAnimation(0, "emotes/wave", true).mixDuration = .25; 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 tempColor = new spine.Color();
const slot = widget.skeleton.findSlot("head-base"); const slot = widget.skeleton.findSlot("head-base");
slot.darkColor = new spine.Color(0, 0, 0, 1); slot.darkColor = new spine.Color(0, 0, 0, 1);
widget.addCursorSlotEventCallbacks(slot, (slot, event) => { widget.addCursorSlotEventCallback(slot, (slot, event) => {
if (event === "down") { if (event === "down") {
slot.darkColor.setFromColor(spine.Color.fromString(darkPicker.value, tempColor)); slot.darkColor.setFromColor(spine.Color.fromString(darkPicker.value, tempColor));
slot.color.setFromColor(spine.Color.fromString(colorPicker.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) => { [0, 1].forEach(async (i) => {
const widget = await spine.getSpineWidget(\`interactive\${i}\`).loadingPromise; 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 === "enter") widget.state.setAnimation(0, "emotes/hooray", true).mixDuration = .15;
if (event === "leave") widget.state.setAnimation(0, "emotes/wave", true).mixDuration = .25; 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 tempColor = new spine.Color();
const slot = widget.skeleton.findSlot("head-base"); const slot = widget.skeleton.findSlot("head-base");
slot.darkColor = new spine.Color(0, 0, 0, 1); slot.darkColor = new spine.Color(0, 0, 0, 1);
widget.addCursorSlotEventCallbacks(slot, (slot, event) => { widget.addCursorSlotEventCallback(slot, (slot, event) => {
if (event === "down") { if (event === "down") {
slot.darkColor.setFromColor(spine.Color.fromString(darkPicker.value, tempColor)); slot.darkColor.setFromColor(spine.Color.fromString(darkPicker.value, tempColor));
slot.color.setFromColor(spine.Color.fromString(colorPicker.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, []); const emotes = widget.skeleton.data.animations.reduce((acc, { name }) => name.startsWith("emotes") ? [...acc, name] : acc, []);
let leaveAnimation = "emotes/wave"; let leaveAnimation = "emotes/wave";
widget.cursorBoundsEventCallback = (event) => { widget.cursorEventCallback = (event) => {
if (event === "enter") widget.state.setAnimation(0, "emotes/hooray", true).mixDuration = .15; 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 === "leave") widget.state.setAnimation(0, leaveAnimation, true).mixDuration = .25;
else if (event === "down") { else if (event === "down") {
@ -3398,7 +3398,7 @@ TODO`
await widgetButton.loadingPromise; await widgetButton.loadingPromise;
widgetButton.skeleton.color.set(.85, .85, .85, 1); widgetButton.skeleton.color.set(.85, .85, .85, 1);
widgetButton.cursorBoundsEventCallback = (event, originalEvent) => { widgetButton.cursorEventCallback = (event, originalEvent) => {
if (event === "enter") { if (event === "enter") {
widgetButton.state.setAnimation(0, "enhance-in", false); widgetButton.state.setAnimation(0, "enhance-in", false);
widgetButton.state.setAnimation(1, "shadow-in", false); widgetButton.state.setAnimation(1, "shadow-in", false);
@ -3448,7 +3448,7 @@ TODO`
skinSelect.addEventListener('change', (e) => widget.skin = e.target.value); skinSelect.addEventListener('change', (e) => widget.skin = e.target.value);
// click on spineboy // click on spineboy
widget.cursorBoundsEventCallback = (event) => { widget.cursorEventCallback = (event) => {
if (event === "down") { if (event === "down") {
widget.state.setAnimation(0, "interactive/pwd/touch", false); widget.state.setAnimation(0, "interactive/pwd/touch", false);
widget.state.addAnimation(0, "interactive/head/idle", true); widget.state.addAnimation(0, "interactive/head/idle", true);
@ -4027,7 +4027,7 @@ TODO`
const itemSlot = widget1.skeleton.findSlot(`bubble-base-${itemName.charAt(0)}`); const itemSlot = widget1.skeleton.findSlot(`bubble-base-${itemName.charAt(0)}`);
let onItem = false; let onItem = false;
widget1.addCursorSlotEventCallbacks(itemSlot, (slot, event) => { widget1.addCursorSlotEventCallback(itemSlot, (slot, event) => {
if (event === "enter") { if (event === "enter") {
console.log("focus"); console.log("focus");
@ -4191,7 +4191,7 @@ TODO`
const widget4 = await spine.getSpineWidget("ready").loadingPromise; const widget4 = await spine.getSpineWidget("ready").loadingPromise;
const slot4Bread = widget4.skeleton.findSlot("salad"); const slot4Bread = widget4.skeleton.findSlot("salad");
widget4.addCursorSlotEventCallbacks(slot4Bread, (slot, event) => { widget4.addCursorSlotEventCallback(slot4Bread, (slot, event) => {
if (event === "enter") { if (event === "enter") {
widget4.state.setAnimation(1, "bread-opening", false); widget4.state.setAnimation(1, "bread-opening", false);
@ -4205,7 +4205,7 @@ TODO`
}); });
const slot4Bottle = widget4.skeleton.findSlot("bottle-base"); const slot4Bottle = widget4.skeleton.findSlot("bottle-base");
widget4.addCursorSlotEventCallbacks(slot4Bottle, (slot, event) => { widget4.addCursorSlotEventCallback(slot4Bottle, (slot, event) => {
if (event === "enter") { if (event === "enter") {
widget4.state.setAnimation(2, "bottle-opening", false); widget4.state.setAnimation(2, "bottle-opening", false);
@ -4219,7 +4219,7 @@ TODO`
}); });
const slot4Fries = widget4.skeleton.findSlot("fries-case-back"); const slot4Fries = widget4.skeleton.findSlot("fries-case-back");
widget4.addCursorSlotEventCallbacks(slot4Fries, (slot, event) => { widget4.addCursorSlotEventCallback(slot4Fries, (slot, event) => {
if (event === "enter") { if (event === "enter") {
widget4.state.setAnimation(3, "fries", true); widget4.state.setAnimation(3, "fries", true);
@ -4334,7 +4334,7 @@ TODO`
spineboy.skeleton.slots.forEach(slot => { spineboy.skeleton.slots.forEach(slot => {
if (slot.data.name === "gun") { if (slot.data.name === "gun") {
spineboy.addCursorSlotEventCallbacks(slot, (slot,event) => { spineboy.addCursorSlotEventCallback(slot, (slot,event) => {
if (event === "down") { if (event === "down") {
spineboy.state.setAnimation(1, "shoot", false); spineboy.state.setAnimation(1, "shoot", false);
} }
@ -4342,7 +4342,7 @@ TODO`
} }
if (slot.data.name === "torso") { if (slot.data.name === "torso") {
spineboy.addCursorSlotEventCallbacks(slot, (slot,event) => { spineboy.addCursorSlotEventCallback(slot, (slot,event) => {
if (event === "down") { if (event === "down") {
spineboy.state.setAnimation(0, "jump", false).mixDuration = 0.2; spineboy.state.setAnimation(0, "jump", false).mixDuration = 0.2;
spineboy.state.addAnimation(0, "walk", true).mixDuration = 0.2; spineboy.state.addAnimation(0, "walk", true).mixDuration = 0.2;
@ -4351,7 +4351,7 @@ TODO`
} }
if (slot.data.name === "head") { if (slot.data.name === "head") {
spineboy.addCursorSlotEventCallbacks(slot, (slot,event) => { spineboy.addCursorSlotEventCallback(slot, (slot,event) => {
if (event === "down") { if (event === "down") {
spineboy.state.setAnimation(1, "run", true).mixDuration = 0.2; spineboy.state.setAnimation(1, "run", true).mixDuration = 0.2;
} else { } else {
@ -4390,7 +4390,7 @@ TODO`
flowers.push(slot); flowers.push(slot);
windmill.addCursorSlotEventCallbacks(slot, (slot, event) => { windmill.addCursorSlotEventCallback(slot, (slot, event) => {
if (ammo === 0) return; if (ammo === 0) return;
if (event !== "down") return; if (event !== "down") return;

View File

@ -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 AttributeTypes = "string" | "number" | "boolean" | "array-number" | "array-string" | "object" | "fitType" | "modeType" | "offScreenUpdateBehaviourType" | "animationsInfo";
export type CursorEventTypes = "down" | "up" | "enter" | "leave" | "move" | "drag"; export type CursorEventType = "down" | "up" | "enter" | "leave" | "move" | "drag";
export type CursorEventTypesInput = Exclude<CursorEventTypes, "enter" | "leave">; export type CursorEventTypesInput = Exclude<CursorEventType, "enter" | "leave">;
// The properties that map to widget attributes // The properties that map to widget attributes
interface WidgetAttributes { interface WidgetAttributes {
@ -539,22 +539,42 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
public isInteractive = false; 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). * performs actions within the widget bounds (for example, it enter or leaves the bounds).
* By default, the function does nothing. * By default, the function does nothing.
* This is an experimental property and might be removed in the future. * 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 // 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, * 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 * 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. * This is an experimental property and might be removed in the future.
*/ */
public addCursorSlotEventCallbacks (slot: Slot, slotFunction: (slot: Slot, event: CursorEventTypes) => void) { public addCursorSlotEventCallback (slot: number | string | Slot, slotFunction: (slot: Slot, event: CursorEventType) => void) {
this.cursorSlotEventCallbacks.set(slot, { slotFunction, inside: false }); 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 * @internal
*/ */
public cursorSlotEventCallbacks: Map<Slot, { public cursorSlotEventCallbacks: Map<Slot, {
slotFunction: (slot: Slot, event: CursorEventTypes, originalEvent?: UIEvent) => void, slotFunction: (slot: Slot, event: CursorEventType, originalEvent?: UIEvent) => void,
inside: boolean, inside: boolean,
}> = new Map(); }> = new Map();
@ -1151,19 +1171,19 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
} }
private checkBoundsInteraction (type: CursorEventTypesInput, originalEvent?: UIEvent) { private checkBoundsInteraction (type: CursorEventTypesInput, originalEvent?: UIEvent) {
if (this.checkCursorInsideBounds()) { if (this.isCursorInsideBounds()) {
if (!this.cursorInsideBounds) { if (!this.cursorInsideBounds) {
this.cursorBoundsEventCallback("enter", originalEvent); this.cursorEventCallback("enter", originalEvent);
} }
this.cursorInsideBounds = true; this.cursorInsideBounds = true;
this.cursorBoundsEventCallback(type, originalEvent); this.cursorEventCallback(type, originalEvent);
} else { } else {
if (this.cursorInsideBounds) { if (this.cursorInsideBounds) {
this.cursorBoundsEventCallback("leave", originalEvent); this.cursorEventCallback("leave", originalEvent);
} }
this.cursorInsideBounds = false; this.cursorInsideBounds = false;
@ -1173,7 +1193,7 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
/** /**
* @internal * @internal
*/ */
public checkCursorInsideBounds (): boolean { public isCursorInsideBounds (): boolean {
if (!this.onScreen || !this.skeleton) return false; if (!this.onScreen || !this.skeleton) return false;
this.pointTemp.set( this.pointTemp.set(
@ -1976,7 +1996,7 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
public cursorWorldY = 1; public cursorWorldY = 1;
private tempVector = new Vector3(); private tempVector = new Vector3();
private cursorUpdate (input: Point) { private updateCursor (input: Point) {
this.cursorCanvasX = input.x - window.scrollX; this.cursorCanvasX = input.x - window.scrollX;
this.cursorCanvasY = input.y - window.scrollY; this.cursorCanvasY = input.y - window.scrollY;
@ -1995,8 +2015,7 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
this.cursorWorldY = tempVector.y; this.cursorWorldY = tempVector.y;
} }
// return true if updated private updateWidgetCursor (widget: SpineWebComponentWidget): boolean {
private cursorWidgetUpdate (widget: SpineWebComponentWidget): boolean {
if (widget.worldX === Infinity) return false; if (widget.worldX === Infinity) return false;
widget.cursorWorldX = this.cursorWorldX - widget.worldX; 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 is used to pass cursor position wrt to canvas and widget position and currently is EXPERIMENTAL
moved: (x, y, ev) => { moved: (x, y, ev) => {
const input = getInput(ev); const input = getInput(ev);
this.cursorUpdate(input); this.updateCursor(input);
this.skeletonList.forEach(widget => { this.skeletonList.forEach(widget => {
if (!this.cursorWidgetUpdate(widget) || !widget.onScreen) return; if (!this.updateWidgetCursor(widget) || !widget.onScreen) return;
widget.cursorEventUpdate("move", ev); widget.cursorEventUpdate("move", ev);
}); });
@ -2034,14 +2053,14 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
down: (x, y, ev) => { down: (x, y, ev) => {
const input = getInput(ev); const input = getInput(ev);
this.cursorUpdate(input); this.updateCursor(input);
this.skeletonList.forEach(widget => { 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); 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; if (!widget.isDraggable) return;
widget.dragging = true; widget.dragging = true;
@ -2059,10 +2078,10 @@ class SpineWebComponentOverlay extends HTMLElement implements OverlayAttributes,
let dragX = input.x - prevX; let dragX = input.x - prevX;
let dragY = input.y - prevY; let dragY = input.y - prevY;
this.cursorUpdate(input); this.updateCursor(input);
this.skeletonList.forEach(widget => { 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); widget.cursorEventUpdate("drag", ev);