mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-03-26 22:49:01 +08:00
Merge branch '4.2' of https://github.com/EsotericSoftware/spine-runtimes into 4.2
This commit is contained in:
commit
4ad52678b2
@ -231,11 +231,11 @@
|
|||||||
></spine-skeleton>
|
></spine-skeleton>
|
||||||
</div>
|
</div>
|
||||||
<div class="split-right">
|
<div class="split-right">
|
||||||
If you want to preserve the original scale, you can use <code>fit="none"</code>.
|
If you want to preserve the original scale, you can use <code>fit="none"</code> (center the bounds) or <code>fit="origin"</code> (center the skeleton origin).
|
||||||
In combination with that, you can use the <code>scale</code> attribute to set your desired scale.
|
In combination with that, you can use the <code>scale</code> attribute to set your desired scale.
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
Other fit modes are <code>width</code>, <code>height</code>, <code>cover</code>, and <code>scaleDown</code>.
|
Other fit modes are <code>width</code>, <code>height</code>, <code>cover</code>, <code>scaleDown</code>..
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -363,11 +363,6 @@
|
|||||||
|
|
||||||
<div class="split-top split">
|
<div class="split-top split">
|
||||||
<div class="split-left">
|
<div class="split-left">
|
||||||
The <code>origin</code> mode centers the animation's world origin with the center of the HTML element.
|
|
||||||
<br>
|
|
||||||
You are responsible for scaling the skeleton when using this mode.
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
Move the origin by a percentage of the div's width and height using the <code>x-axis</code> and <code>y-axis</code> attributes, respectively.
|
Move the origin by a percentage of the div's width and height using the <code>x-axis</code> and <code>y-axis</code> attributes, respectively.
|
||||||
</div>
|
</div>
|
||||||
<div class="split-right">
|
<div class="split-right">
|
||||||
@ -375,7 +370,7 @@
|
|||||||
atlas="/assets/vine-pma.atlas"
|
atlas="/assets/vine-pma.atlas"
|
||||||
skeleton="/assets/vine-pro.skel"
|
skeleton="/assets/vine-pro.skel"
|
||||||
animation="grow"
|
animation="grow"
|
||||||
mode="origin"
|
fit="origin"
|
||||||
scale=".5"
|
scale=".5"
|
||||||
y-axis="-.5"
|
y-axis="-.5"
|
||||||
></spine-skeleton>
|
></spine-skeleton>
|
||||||
@ -390,7 +385,7 @@
|
|||||||
atlas="/assets/vine-pma.atlas"
|
atlas="/assets/vine-pma.atlas"
|
||||||
skeleton="/assets/vine-pro.skel"
|
skeleton="/assets/vine-pro.skel"
|
||||||
animation="grow"
|
animation="grow"
|
||||||
mode="origin"
|
fit="origin"
|
||||||
scale=".5"
|
scale=".5"
|
||||||
y-axis="-.5"
|
y-axis="-.5"
|
||||||
></spine-skeleton>
|
></spine-skeleton>
|
||||||
@ -772,11 +767,11 @@
|
|||||||
<li><code>mixDuration</code>: the mix duration between this animation and the previous one (not used for the first animation on a track)</li>
|
<li><code>mixDuration</code>: the mix duration between this animation and the previous one (not used for the first animation on a track)</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<p>To loop a track once it reaches the end, add the special group <code>[loop, trackNumber, holdDurationLastAnimation]</code>, where:</p>
|
<p>To loop a track once it reaches the end, add the special group <code>[loop, trackNumber, repeatDelay]</code>, where:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><code>loop</code>: identifies this as a loop instruction</li>
|
<li><code>loop</code>: identifies this as a loop instruction</li>
|
||||||
<li><code>trackNumber</code>: the number of the track to loop</li>
|
<li><code>trackNumber</code>: the number of the track to loop</li>
|
||||||
<li><code>holdDurationLastAnimation</code>: the number of seconds to wait after the last animation is completed before repeating the loop</li>
|
<li><code>repeatDelay</code>: the number of seconds to wait after the last animation is completed before repeating the loop</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<p>The parameters of the first group on each track are passed to the <code>setAnimation</code> method, while the remaining groups use <code>addAnimation</code>.</p>
|
<p>The parameters of the first group on each track are passed to the <code>setAnimation</code> method, while the remaining groups use <code>addAnimation</code>.</p>
|
||||||
@ -1427,6 +1422,8 @@ function toggleSpinner(element) {
|
|||||||
|
|
||||||
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
<div class="split-left" style="width: 80%; box-sizing: border-box; min-height: 0;">
|
||||||
It's very easy to display your different skins and animations. Simply create a table and use the <code>skin</code> and <code>animation</code> attributes.
|
It's very easy to display your different skins and animations. Simply create a table and use the <code>skin</code> and <code>animation</code> attributes.
|
||||||
|
<br>
|
||||||
|
<code>skin</code> accepts a comma separated list of skin names. The skins will be combined in a new one, from the first to the last. If multiple skins set the same slot, the latest in the list will be used.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="skin-grid">
|
<div class="skin-grid">
|
||||||
@ -3279,7 +3276,7 @@ const darkPicker = document.getElementById("dark-picker");
|
|||||||
<li><code>followOpacity</code>: the element opacity is connected to the slot alpha</li>
|
<li><code>followOpacity</code>: the element opacity is connected to the slot alpha</li>
|
||||||
<li><code>followScale</code>: the element scale is connected to the slot scale</li>
|
<li><code>followScale</code>: the element scale is connected to the slot scale</li>
|
||||||
<li><code>followRotation</code>: the element rotation is connected to the slot rotation</li>
|
<li><code>followRotation</code>: the element rotation is connected to the slot rotation</li>
|
||||||
<li><code>followAttachmentAttach</code>: the element is shown/hidden depending if the slot contains an attachment or not</li>
|
<li><code>followVisibility</code>: the element is shown/hidden depending if the slot contains an attachment or not</li>
|
||||||
<li><code>hideAttachment</code>: the slot attachment is hidden as if the element replaced the attachment</li>
|
<li><code>hideAttachment</code>: the slot attachment is hidden as if the element replaced the attachment</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
@ -3304,10 +3301,10 @@ const darkPicker = document.getElementById("dark-picker");
|
|||||||
<script>
|
<script>
|
||||||
(async () => {
|
(async () => {
|
||||||
const widget = await spine.getSpineWidget("potty").whenReady;
|
const widget = await spine.getSpineWidget("potty").whenReady;
|
||||||
widget.followSlot("rain/rain-color", document.getElementById("rain/rain-color"), { followAttachmentAttach: false, hideAttachment: true });
|
widget.followSlot("rain/rain-color", document.getElementById("rain/rain-color"), { followVisibility: false, hideAttachment: true });
|
||||||
widget.followSlot("rain/rain-white", document.getElementById("rain/rain-white"), { followAttachmentAttach: false, hideAttachment: true });
|
widget.followSlot("rain/rain-white", document.getElementById("rain/rain-white"), { followVisibility: false, hideAttachment: true });
|
||||||
widget.followSlot("rain/rain-blue", document.getElementById("rain/rain-blue"), { followAttachmentAttach: false, hideAttachment: true });
|
widget.followSlot("rain/rain-blue", document.getElementById("rain/rain-blue"), { followVisibility: false, hideAttachment: true });
|
||||||
widget.followSlot("rain/rain-green", document.getElementById("rain/rain-green"), { followAttachmentAttach: false, hideAttachment: true });
|
widget.followSlot("rain/rain-green", document.getElementById("rain/rain-green"), { followVisibility: false, hideAttachment: true });
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -3331,10 +3328,10 @@ const darkPicker = document.getElementById("dark-picker");
|
|||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const widget = await spine.getSpineWidget("potty").whenReady;
|
const widget = await spine.getSpineWidget("potty").whenReady;
|
||||||
widget.followSlot("rain/rain-color", document.getElementById("rain/rain-color"), { followAttachmentAttach: false, hideAttachment: true });
|
widget.followSlot("rain/rain-color", document.getElementById("rain/rain-color"), { followVisibility: false, hideAttachment: true });
|
||||||
widget.followSlot("rain/rain-white", document.getElementById("rain/rain-white"), { followAttachmentAttach: false, hideAttachment: true });
|
widget.followSlot("rain/rain-white", document.getElementById("rain/rain-white"), { followVisibility: false, hideAttachment: true });
|
||||||
widget.followSlot("rain/rain-blue", document.getElementById("rain/rain-blue"), { followAttachmentAttach: false, hideAttachment: true });
|
widget.followSlot("rain/rain-blue", document.getElementById("rain/rain-blue"), { followVisibility: false, hideAttachment: true });
|
||||||
widget.followSlot("rain/rain-green", document.getElementById("rain/rain-green"), { followAttachmentAttach: false, hideAttachment: true });
|
widget.followSlot("rain/rain-green", document.getElementById("rain/rain-green"), { followVisibility: false, hideAttachment: true });
|
||||||
})();`);</script>
|
})();`);</script>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
</div>
|
</div>
|
||||||
@ -3378,10 +3375,10 @@ const darkPicker = document.getElementById("dark-picker");
|
|||||||
<script>
|
<script>
|
||||||
(async () => {
|
(async () => {
|
||||||
const widget = await spine.getSpineWidget("potty2").whenReady;
|
const widget = await spine.getSpineWidget("potty2").whenReady;
|
||||||
widget.followSlot("rain/rain-color", spine.getSpineWidget("potty2-1"), { followAttachmentAttach: false, hideAttachment: true });
|
widget.followSlot("rain/rain-color", spine.getSpineWidget("potty2-1"), { followVisibility: false, hideAttachment: true });
|
||||||
widget.followSlot("rain/rain-white", spine.getSpineWidget("potty2-2"), { followAttachmentAttach: false, hideAttachment: true });
|
widget.followSlot("rain/rain-white", spine.getSpineWidget("potty2-2"), { followVisibility: false, hideAttachment: true });
|
||||||
widget.followSlot("rain/rain-blue", spine.getSpineWidget("potty2-3"), { followAttachmentAttach: false, hideAttachment: true });
|
widget.followSlot("rain/rain-blue", spine.getSpineWidget("potty2-3"), { followVisibility: false, hideAttachment: true });
|
||||||
widget.followSlot("rain/rain-green", spine.getSpineWidget("potty2-4"), { followAttachmentAttach: false, hideAttachment: true });
|
widget.followSlot("rain/rain-green", spine.getSpineWidget("potty2-4"), { followVisibility: false, hideAttachment: true });
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -3405,10 +3402,10 @@ const darkPicker = document.getElementById("dark-picker");
|
|||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const widget = await spine.getSpineWidget("potty2").whenReady;
|
const widget = await spine.getSpineWidget("potty2").whenReady;
|
||||||
widget.followSlot("rain/rain-color", spine.getSpineWidget("potty2-1"), { followAttachmentAttach: false, hideAttachment: true });
|
widget.followSlot("rain/rain-color", spine.getSpineWidget("potty2-1"), { followVisibility: false, hideAttachment: true });
|
||||||
widget.followSlot("rain/rain-white", spine.getSpineWidget("potty2-2"), { followAttachmentAttach: false, hideAttachment: true });
|
widget.followSlot("rain/rain-white", spine.getSpineWidget("potty2-2"), { followVisibility: false, hideAttachment: true });
|
||||||
widget.followSlot("rain/rain-blue", spine.getSpineWidget("potty2-3"), { followAttachmentAttach: false, hideAttachment: true });
|
widget.followSlot("rain/rain-blue", spine.getSpineWidget("potty2-3"), { followVisibility: false, hideAttachment: true });
|
||||||
widget.followSlot("rain/rain-green", spine.getSpineWidget("potty2-4"), { followAttachmentAttach: false, hideAttachment: true });
|
widget.followSlot("rain/rain-green", spine.getSpineWidget("potty2-4"), { followVisibility: false, hideAttachment: true });
|
||||||
})();`);</script>
|
})();`);</script>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -483,7 +483,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
|||||||
|
|
||||||
const tempVector = new Vector3();
|
const tempVector = new Vector3();
|
||||||
for (const widget of this.widgets) {
|
for (const widget of this.widgets) {
|
||||||
const { skeleton, pma, bounds, mode, debug, offsetX, offsetY, xAxis, yAxis, dragX, dragY, fit, noSpinner, loading, clip, isDraggable } = widget;
|
const { skeleton, pma, bounds, debug, offsetX, offsetY, dragX, dragY, fit, noSpinner, loading, clip, isDraggable } = widget;
|
||||||
|
|
||||||
if (widget.isOffScreenAndWasMoved()) continue;
|
if (widget.isOffScreenAndWasMoved()) continue;
|
||||||
const elementRef = widget.getHostElement();
|
const elementRef = widget.getHostElement();
|
||||||
@ -497,7 +497,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
|||||||
divBounds.y -= offsetTopForOverlay;
|
divBounds.y -= offsetTopForOverlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { padLeft, padRight, padTop, padBottom } = widget
|
const { padLeft, padRight, padTop, padBottom, xAxis, yAxis } = widget
|
||||||
const paddingShiftHorizontal = (padLeft - padRight) / 2;
|
const paddingShiftHorizontal = (padLeft - padRight) / 2;
|
||||||
const paddingShiftVertical = (padTop - padBottom) / 2;
|
const paddingShiftVertical = (padTop - padBottom) / 2;
|
||||||
|
|
||||||
@ -525,7 +525,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (skeleton) {
|
if (skeleton) {
|
||||||
if (mode === "inside") {
|
if (fit !== "origin") {
|
||||||
let { x: ax, y: ay, width: aw, height: ah } = bounds;
|
let { x: ax, y: ay, width: aw, height: ah } = bounds;
|
||||||
if (aw <= 0 || ah <= 0) continue;
|
if (aw <= 0 || ah <= 0) continue;
|
||||||
|
|
||||||
@ -591,8 +591,9 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const worldOffsetX = divOriginX + offsetX + dragX;
|
// const worldOffsetX = divOriginX + offsetX + dragX;
|
||||||
const worldOffsetY = divOriginY + offsetY + dragY;
|
const worldOffsetX = divOriginX + offsetX * window.devicePixelRatio + dragX;
|
||||||
|
const worldOffsetY = divOriginY + offsetY * window.devicePixelRatio + dragY;
|
||||||
|
|
||||||
widget.worldX = worldOffsetX;
|
widget.worldX = worldOffsetX;
|
||||||
widget.worldY = worldOffsetY;
|
widget.worldY = worldOffsetY;
|
||||||
@ -634,12 +635,10 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
|||||||
renderer.circle(true, root.x + worldOffsetX, root.y + worldOffsetY, 10, red);
|
renderer.circle(true, root.x + worldOffsetX, root.y + worldOffsetY, 10, red);
|
||||||
|
|
||||||
// show shifted origin
|
// show shifted origin
|
||||||
const originX = worldOffsetX - dragX - offsetX;
|
renderer.circle(true, divOriginX, divOriginY, 10, green);
|
||||||
const originY = worldOffsetY - dragY - offsetY;
|
|
||||||
renderer.circle(true, originX, originY, 10, green);
|
|
||||||
|
|
||||||
// show line from origin to bounds center
|
// show line from origin to bounds center
|
||||||
renderer.line(originX, originY, bbCenterX, bbCenterY, green);
|
renderer.line(divOriginX, divOriginY, bbCenterX, bbCenterY, green);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clip) endScissor();
|
if (clip) endScissor();
|
||||||
@ -654,7 +653,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
|||||||
if (widget.isOffScreenAndWasMoved() || !widget.skeleton) continue;
|
if (widget.isOffScreenAndWasMoved() || !widget.skeleton) continue;
|
||||||
|
|
||||||
for (const boneFollower of widget.boneFollowerList) {
|
for (const boneFollower of widget.boneFollowerList) {
|
||||||
const { slot, bone, element, followAttachmentAttach, followRotation, followOpacity, followScale } = boneFollower;
|
const { slot, bone, element, followVisibility, followRotation, followOpacity, followScale } = boneFollower;
|
||||||
const { worldX, worldY } = widget;
|
const { worldX, worldY } = widget;
|
||||||
this.worldToScreen(this.tempFollowBoneVector, bone.worldX + worldX, bone.worldY + worldY);
|
this.worldToScreen(this.tempFollowBoneVector, bone.worldX + worldX, bone.worldY + worldY);
|
||||||
|
|
||||||
@ -675,7 +674,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
|||||||
|
|
||||||
element.style.display = ""
|
element.style.display = ""
|
||||||
|
|
||||||
if (followAttachmentAttach && !slot.attachment) {
|
if (followVisibility && !slot.attachment) {
|
||||||
element.style.opacity = "0";
|
element.style.opacity = "0";
|
||||||
} else if (followOpacity) {
|
} else if (followOpacity) {
|
||||||
element.style.opacity = `${slot.color.a}`;
|
element.style.opacity = `${slot.color.a}`;
|
||||||
@ -951,7 +950,7 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr
|
|||||||
private updateWidgetScales () {
|
private updateWidgetScales () {
|
||||||
for (const widget of this.widgets) {
|
for (const widget of this.widgets) {
|
||||||
// inside mode scale automatically to fit the skeleton within its parent
|
// inside mode scale automatically to fit the skeleton within its parent
|
||||||
if (widget.mode !== "origin" && widget.fit !== "none") continue;
|
if (widget.fit !== "origin" && widget.fit !== "none") continue;
|
||||||
|
|
||||||
const skeleton = widget.skeleton;
|
const skeleton = widget.skeleton;
|
||||||
if (!skeleton) continue;
|
if (!skeleton) continue;
|
||||||
|
|||||||
@ -49,6 +49,7 @@ import {
|
|||||||
RegionAttachment,
|
RegionAttachment,
|
||||||
MeshAttachment,
|
MeshAttachment,
|
||||||
Bone,
|
Bone,
|
||||||
|
Skin,
|
||||||
} from "@esotericsoftware/spine-webgl";
|
} from "@esotericsoftware/spine-webgl";
|
||||||
import { AttributeTypes, castValue, isBase64, Rectangle } from "./wcUtils.js";
|
import { AttributeTypes, castValue, isBase64, Rectangle } from "./wcUtils.js";
|
||||||
import { SpineWebComponentOverlay } from "./SpineWebComponentOverlay.js";
|
import { SpineWebComponentOverlay } from "./SpineWebComponentOverlay.js";
|
||||||
@ -56,11 +57,10 @@ import { SpineWebComponentOverlay } from "./SpineWebComponentOverlay.js";
|
|||||||
type UpdateSpineWidgetFunction = (delta: number, skeleton: Skeleton, state: AnimationState) => void;
|
type UpdateSpineWidgetFunction = (delta: number, skeleton: Skeleton, state: AnimationState) => void;
|
||||||
|
|
||||||
export type OffScreenUpdateBehaviourType = "pause" | "update" | "pose";
|
export type OffScreenUpdateBehaviourType = "pause" | "update" | "pose";
|
||||||
export type ModeType = "inside" | "origin";
|
export type FitType = "fill" | "width" | "height" | "contain" | "cover" | "none" | "scaleDown" | "origin";
|
||||||
export type FitType = "fill" | "width" | "height" | "contain" | "cover" | "none" | "scaleDown";
|
|
||||||
export type AnimationsInfo = Record<string, {
|
export type AnimationsInfo = Record<string, {
|
||||||
cycle?: boolean,
|
cycle?: boolean,
|
||||||
holdDurationLastAnimation?: number;
|
repeatDelay?: number;
|
||||||
animations: Array<AnimationsType>
|
animations: Array<AnimationsType>
|
||||||
}>;
|
}>;
|
||||||
export type AnimationsType = { animationName: string | "#EMPTY#", loop?: boolean, delay?: number, mixDuration?: number };
|
export type AnimationsType = { animationName: string | "#EMPTY#", loop?: boolean, delay?: number, mixDuration?: number };
|
||||||
@ -77,9 +77,8 @@ interface WidgetAttributes {
|
|||||||
animation?: string
|
animation?: string
|
||||||
animations?: AnimationsInfo
|
animations?: AnimationsInfo
|
||||||
defaultMix?: number
|
defaultMix?: number
|
||||||
skin?: string
|
skin?: string[]
|
||||||
fit: FitType
|
fit: FitType
|
||||||
mode: ModeType
|
|
||||||
xAxis: number
|
xAxis: number
|
||||||
yAxis: number
|
yAxis: number
|
||||||
offsetX: number
|
offsetX: number
|
||||||
@ -221,14 +220,14 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
|||||||
* Optional: The name of the skin to be set
|
* Optional: The name of the skin to be set
|
||||||
* Connected to `skin` attribute.
|
* Connected to `skin` attribute.
|
||||||
*/
|
*/
|
||||||
public get skin (): string | undefined {
|
public get skin (): string[] | undefined {
|
||||||
return this._skin;
|
return this._skin;
|
||||||
}
|
}
|
||||||
public set skin (value: string | undefined) {
|
public set skin (value: string[] | undefined) {
|
||||||
this._skin = value;
|
this._skin = value;
|
||||||
this.initWidget();
|
this.initWidget();
|
||||||
}
|
}
|
||||||
private _skin?: string
|
private _skin?: string[]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specify the way the skeleton is sized within the element automatically changing its `scaleX` and `scaleY`.
|
* Specify the way the skeleton is sized within the element automatically changing its `scaleX` and `scaleY`.
|
||||||
@ -240,19 +239,11 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
|||||||
* - `cover`: as small as possible while still covering the entire element container.
|
* - `cover`: as small as possible while still covering the entire element container.
|
||||||
* - `scaleDown`: scale the skeleton down to ensure that the skeleton fits within the element container.
|
* - `scaleDown`: scale the skeleton down to ensure that the skeleton fits within the element container.
|
||||||
* - `none`: display the skeleton without autoscaling it.
|
* - `none`: display the skeleton without autoscaling it.
|
||||||
|
* - `origin`: the skeleton origin is centered with the element container regardless of the bounds.
|
||||||
* Connected to `fit` attribute.
|
* Connected to `fit` attribute.
|
||||||
*/
|
*/
|
||||||
public fit: FitType = "contain";
|
public fit: FitType = "contain";
|
||||||
|
|
||||||
/**
|
|
||||||
* Specify the way the skeleton is centered within the element container:
|
|
||||||
* - `inside`: the skeleton bounds center is centered with the element container (Default)
|
|
||||||
* - `origin`: the skeleton origin is centered with the element container regardless of the bounds.
|
|
||||||
* Origin does not allow to specify any {@link fit} type and guarantee the skeleton to not be autoscaled.
|
|
||||||
* Connected to `mode` attribute.
|
|
||||||
*/
|
|
||||||
public mode: ModeType = "inside";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The x offset of the skeleton world origin x axis as a percentage of the element container width
|
* The x offset of the skeleton world origin x axis as a percentage of the element container width
|
||||||
* Connected to `x-axis` attribute.
|
* Connected to `x-axis` attribute.
|
||||||
@ -705,7 +696,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
|||||||
animations: { propertyName: "animations", type: "animationsInfo", defaultValue: undefined },
|
animations: { propertyName: "animations", type: "animationsInfo", defaultValue: undefined },
|
||||||
"animation-bounds": { propertyName: "animationsBound", type: "array-string", defaultValue: undefined },
|
"animation-bounds": { propertyName: "animationsBound", type: "array-string", defaultValue: undefined },
|
||||||
"default-mix": { propertyName: "defaultMix", type: "number", defaultValue: 0 },
|
"default-mix": { propertyName: "defaultMix", type: "number", defaultValue: 0 },
|
||||||
skin: { propertyName: "skin", type: "string" },
|
skin: { propertyName: "skin", type: "array-string" },
|
||||||
width: { propertyName: "width", type: "number", defaultValue: -1 },
|
width: { propertyName: "width", type: "number", defaultValue: -1 },
|
||||||
height: { propertyName: "height", type: "number", defaultValue: -1 },
|
height: { propertyName: "height", type: "number", defaultValue: -1 },
|
||||||
isdraggable: { propertyName: "isDraggable", type: "boolean" },
|
isdraggable: { propertyName: "isDraggable", type: "boolean" },
|
||||||
@ -731,7 +722,6 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
|||||||
clip: { propertyName: "clip", type: "boolean" },
|
clip: { propertyName: "clip", type: "boolean" },
|
||||||
pages: { propertyName: "pages", type: "array-number" },
|
pages: { propertyName: "pages", type: "array-number" },
|
||||||
fit: { propertyName: "fit", type: "fitType", defaultValue: "contain" },
|
fit: { propertyName: "fit", type: "fitType", defaultValue: "contain" },
|
||||||
mode: { propertyName: "mode", type: "modeType", defaultValue: "inside" },
|
|
||||||
offscreen: { propertyName: "offScreenUpdateBehaviour", type: "offScreenUpdateBehaviourType", defaultValue: "pause" },
|
offscreen: { propertyName: "offScreenUpdateBehaviour", type: "offScreenUpdateBehaviourType", defaultValue: "pause" },
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1008,18 +998,28 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
|||||||
// skeleton.scaleX = this.dprScale;
|
// skeleton.scaleX = this.dprScale;
|
||||||
// skeleton.scaleY = this.dprScale;
|
// skeleton.scaleY = this.dprScale;
|
||||||
|
|
||||||
|
this.loading = false;
|
||||||
|
|
||||||
// the bounds are calculated the first time, if no custom bound is provided
|
// the bounds are calculated the first time, if no custom bound is provided
|
||||||
this.initWidget(this.bounds.width <= 0 || this.bounds.height <= 0);
|
this.initWidget(this.bounds.width <= 0 || this.bounds.height <= 0);
|
||||||
|
|
||||||
this.loading = false;
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private initWidget (forceRecalculate = false) {
|
private initWidget (forceRecalculate = false) {
|
||||||
|
if (this.loading) return;
|
||||||
|
|
||||||
const { skeleton, state, animation, animations: animationsInfo, skin, defaultMix } = this;
|
const { skeleton, state, animation, animations: animationsInfo, skin, defaultMix } = this;
|
||||||
|
|
||||||
if (skin) {
|
if (skin) {
|
||||||
skeleton?.setSkinByName(skin);
|
if (skin.length === 1) {
|
||||||
|
skeleton?.setSkinByName(skin[0]);
|
||||||
|
} else {
|
||||||
|
const customSkin = new Skin("custom");
|
||||||
|
for (const s of skin) customSkin.addSkin(skeleton?.data.findSkin(s) as Skin);
|
||||||
|
skeleton?.setSkin(customSkin);
|
||||||
|
}
|
||||||
|
|
||||||
skeleton?.setSlotsToSetupPose();
|
skeleton?.setSlotsToSetupPose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1027,7 +1027,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
|||||||
state.data.defaultMix = defaultMix;
|
state.data.defaultMix = defaultMix;
|
||||||
|
|
||||||
if (animationsInfo) {
|
if (animationsInfo) {
|
||||||
for (const [trackIndexString, { cycle, animations, holdDurationLastAnimation }] of Object.entries(animationsInfo)) {
|
for (const [trackIndexString, { cycle, animations, repeatDelay }] of Object.entries(animationsInfo)) {
|
||||||
const cycleFn = () => {
|
const cycleFn = () => {
|
||||||
const trackIndex = Number(trackIndexString);
|
const trackIndex = Number(trackIndexString);
|
||||||
for (const [index, { animationName, delay, loop, mixDuration }] of animations.entries()) {
|
for (const [index, { animationName, delay, loop, mixDuration }] of animations.entries()) {
|
||||||
@ -1051,8 +1051,8 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
|||||||
if (cycle && index === animations.length - 1) {
|
if (cycle && index === animations.length - 1) {
|
||||||
track.listener = {
|
track.listener = {
|
||||||
complete: () => {
|
complete: () => {
|
||||||
if (holdDurationLastAnimation)
|
if (repeatDelay)
|
||||||
setTimeout(() => cycleFn(), 1000 * holdDurationLastAnimation);
|
setTimeout(() => cycleFn(), 1000 * repeatDelay);
|
||||||
else
|
else
|
||||||
cycleFn();
|
cycleFn();
|
||||||
delete track.listener?.complete;
|
delete track.listener?.complete;
|
||||||
@ -1231,10 +1231,10 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
|||||||
* Other utilities
|
* Other utilities
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public boneFollowerList: Array<{ slot: Slot, bone: Bone, element: HTMLElement, followAttachmentAttach: boolean, followRotation: boolean, followOpacity: boolean, followScale: boolean, hideAttachment: boolean }> = [];
|
public boneFollowerList: Array<{ slot: Slot, bone: Bone, element: HTMLElement, followVisibility: boolean, followRotation: boolean, followOpacity: boolean, followScale: boolean, hideAttachment: boolean }> = [];
|
||||||
public followSlot (slotName: string | Slot, element: HTMLElement, options: { followAttachmentAttach?: boolean, followRotation?: boolean, followOpacity?: boolean, followScale?: boolean, hideAttachment?: boolean } = {}) {
|
public followSlot (slotName: string | Slot, element: HTMLElement, options: { followVisibility?: boolean, followRotation?: boolean, followOpacity?: boolean, followScale?: boolean, hideAttachment?: boolean } = {}) {
|
||||||
const {
|
const {
|
||||||
followAttachmentAttach = false,
|
followVisibility = false,
|
||||||
followRotation = true,
|
followRotation = true,
|
||||||
followOpacity = true,
|
followOpacity = true,
|
||||||
followScale = true,
|
followScale = true,
|
||||||
@ -1253,7 +1253,7 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
|
|||||||
element.style.left = '0px';
|
element.style.left = '0px';
|
||||||
element.style.display = 'none';
|
element.style.display = 'none';
|
||||||
|
|
||||||
this.boneFollowerList.push({ slot, bone: slot.bone, element, followAttachmentAttach, followRotation, followOpacity, followScale, hideAttachment });
|
this.boneFollowerList.push({ slot, bone: slot.bone, element, followVisibility, followRotation, followOpacity, followScale, hideAttachment });
|
||||||
this.overlay.addSlotFollowerElement(element);
|
this.overlay.addSlotFollowerElement(element);
|
||||||
}
|
}
|
||||||
public unfollowSlot (element: HTMLElement): HTMLElement | undefined {
|
public unfollowSlot (element: HTMLElement): HTMLElement | undefined {
|
||||||
|
|||||||
@ -27,10 +27,10 @@
|
|||||||
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
import { AnimationsInfo, FitType, ModeType, OffScreenUpdateBehaviourType } from "./SpineWebComponentSkeleton.js";
|
import { AnimationsInfo, FitType, OffScreenUpdateBehaviourType } from "./SpineWebComponentSkeleton.js";
|
||||||
|
|
||||||
const animatonTypeRegExp = /\[([^\]]+)\]/g;
|
const animatonTypeRegExp = /\[([^\]]+)\]/g;
|
||||||
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" | "offScreenUpdateBehaviourType" | "animationsInfo";
|
||||||
|
|
||||||
export function castValue (type: AttributeTypes, value: string | null, defaultValue?: any) {
|
export function castValue (type: AttributeTypes, value: string | null, defaultValue?: any) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@ -48,8 +48,6 @@ export function castValue (type: AttributeTypes, value: string | null, defaultVa
|
|||||||
return castObject(value, defaultValue);
|
return castObject(value, defaultValue);
|
||||||
case "fitType":
|
case "fitType":
|
||||||
return isFitType(value) ? value : defaultValue;
|
return isFitType(value) ? value : defaultValue;
|
||||||
case "modeType":
|
|
||||||
return isModeType(value) ? value : defaultValue;
|
|
||||||
case "offScreenUpdateBehaviourType":
|
case "offScreenUpdateBehaviourType":
|
||||||
return isOffScreenUpdateBehaviourType(value) ? value : defaultValue;
|
return isOffScreenUpdateBehaviourType(value) ? value : defaultValue;
|
||||||
case "animationsInfo":
|
case "animationsInfo":
|
||||||
@ -104,7 +102,7 @@ function castToAnimationsInfo (value: string | null): AnimationsInfo | undefined
|
|||||||
if (!matches) return undefined;
|
if (!matches) return undefined;
|
||||||
|
|
||||||
return matches.reduce((obj, group) => {
|
return matches.reduce((obj, group) => {
|
||||||
const [trackIndexStringOrLoopDefinition, animationNameOrTrackIndexStringCycle, loopOrHoldDurationLastAnimation, delayString, mixDurationString] = group.slice(1, -1).split(',').map(v => v.trim());
|
const [trackIndexStringOrLoopDefinition, animationNameOrTrackIndexStringCycle, loopOrRepeatDelay, delayString, mixDurationString] = group.slice(1, -1).split(',').map(v => v.trim());
|
||||||
|
|
||||||
if (trackIndexStringOrLoopDefinition === "loop") {
|
if (trackIndexStringOrLoopDefinition === "loop") {
|
||||||
if (!Number.isInteger(Number(animationNameOrTrackIndexStringCycle))) {
|
if (!Number.isInteger(Number(animationNameOrTrackIndexStringCycle))) {
|
||||||
@ -113,12 +111,12 @@ function castToAnimationsInfo (value: string | null): AnimationsInfo | undefined
|
|||||||
const animationInfoObject = obj[animationNameOrTrackIndexStringCycle] ||= { animations: [] };
|
const animationInfoObject = obj[animationNameOrTrackIndexStringCycle] ||= { animations: [] };
|
||||||
animationInfoObject.cycle = true;
|
animationInfoObject.cycle = true;
|
||||||
|
|
||||||
if (loopOrHoldDurationLastAnimation !== undefined) {
|
if (loopOrRepeatDelay !== undefined) {
|
||||||
const holdDurationLastAnimation = Number(loopOrHoldDurationLastAnimation);
|
const repeatDelay = Number(loopOrRepeatDelay);
|
||||||
if (Number.isNaN(holdDurationLastAnimation)) {
|
if (Number.isNaN(repeatDelay)) {
|
||||||
throw new Error(`If present, duration of last animation of cycle in ${group} must be a positive integer number, instead it is ${loopOrHoldDurationLastAnimation}. Original value: ${value}`);
|
throw new Error(`If present, duration of last animation of cycle in ${group} must be a positive integer number, instead it is ${loopOrRepeatDelay}. Original value: ${value}`);
|
||||||
}
|
}
|
||||||
animationInfoObject.holdDurationLastAnimation = holdDurationLastAnimation;
|
animationInfoObject.repeatDelay = repeatDelay;
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
@ -148,7 +146,7 @@ function castToAnimationsInfo (value: string | null): AnimationsInfo | undefined
|
|||||||
const animationInfoObject = obj[trackIndexStringOrLoopDefinition] ||= { animations: [] };
|
const animationInfoObject = obj[trackIndexStringOrLoopDefinition] ||= { animations: [] };
|
||||||
animationInfoObject.animations.push({
|
animationInfoObject.animations.push({
|
||||||
animationName: animationNameOrTrackIndexStringCycle,
|
animationName: animationNameOrTrackIndexStringCycle,
|
||||||
loop: loopOrHoldDurationLastAnimation.trim().toLowerCase() === "true",
|
loop: (loopOrRepeatDelay || "").trim().toLowerCase() === "true",
|
||||||
delay,
|
delay,
|
||||||
mixDuration,
|
mixDuration,
|
||||||
});
|
});
|
||||||
@ -164,7 +162,8 @@ function isFitType (value: string | null): value is FitType {
|
|||||||
value === "contain" ||
|
value === "contain" ||
|
||||||
value === "cover" ||
|
value === "cover" ||
|
||||||
value === "none" ||
|
value === "none" ||
|
||||||
value === "scaleDown"
|
value === "scaleDown" ||
|
||||||
|
value === "origin"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,12 +175,6 @@ function isOffScreenUpdateBehaviourType (value: string | null): value is OffScre
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isModeType (value: string | null): value is ModeType {
|
|
||||||
return (
|
|
||||||
value === "inside" ||
|
|
||||||
value === "origin"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const base64RegExp = /^(([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==))$/;
|
const base64RegExp = /^(([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==))$/;
|
||||||
export function isBase64 (str: string) {
|
export function isBase64 (str: string) {
|
||||||
return base64RegExp.test(str);
|
return base64RegExp.test(str);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user