Add padding attributes/properties.

This commit is contained in:
Davide Tantillo 2024-10-03 13:05:12 +02:00
parent bedce14780
commit f4837ad8eb
2 changed files with 173 additions and 78 deletions

View File

@ -407,6 +407,59 @@
<div id="section5" class="section vertical-split">
<div class="split-top split">
<div class="split-left">
<spine-widget
atlas="assets/goblins-pma.atlas"
skeleton="assets/goblins-pro.skel"
skin="goblingirl"
animation="walk"
pad-left=".25"
pad-right=".25"
pad-top=".25"
pad-bottom=".25"
></spine-widget>
</div>
<div class="split-right">
You can virtually add a padding to the element container by using <code>pad-left</code>, <code>pad-right</code>, <code>pad-top</code>, <code>pad-bottom</code>.
<br>
<br>
As a value you can use a percentage of the width for left and right, and of the height for top and bottom.
</div>
</div>
<div class="split-bottom">
<pre><code id="code-display">
<script>escapeHTMLandInject(`
<spine-widget
atlas="assets/goblins-pma.atlas"
skeleton="assets/goblins-pro.skel"
skin="goblingirl"
animation="walk"
pad-left=".25"
pad-right=".25"
pad-top=".25"
pad-bottom=".25"
></spine-widget>`);</script>
</code></pre>
</div>
</div>
<!--
/////////////////////
// end section 5 //
/////////////////////
-->
<!--
/////////////////////
// start section 6 //
/////////////////////
-->
<div id="section6" class="section vertical-split">
<div class="split-top split">
<div class="split-left">
Give an <code>identifier</code> to your widget to get it by using the <code>spine.getSpineWidget</code> function.
@ -473,23 +526,26 @@
<!--
/////////////////////
// end section 5 //
// end section 6 //
/////////////////////
-->
<!--
/////////////////////
// start section 6 //
// start section 7 //
/////////////////////
-->
<div id="section6" class="section vertical-split">
<div id="section7" class="section vertical-split">
<div class="split high-page" style="flex-direction: column;">
<div class="split-nosize full-width" style="width: 80%; padding: 1em;">
Moving the div will move the skeleton origin. <br>
Moving the div will move the skeleton origin.
<br>
Resizing the div will resize the skeleton in <code>inside</code> mode, but not in <code>origin</code> mode.
<br>
Interact with the example above dragging the div and resizing it
</div>
<div class="split-nosize full-width" style="width: 80%; height: 50%; margin: 1em; padding: 1em;" id="section6-element">
<div class="split-nosize full-width" style="width: 80%; height: 50%; margin: 1em; padding: 1em;" id="section7-element">
<spine-widget
atlas="assets/cloud-pot-pma.atlas"
skeleton="assets/cloud-pot.skel"
@ -514,17 +570,17 @@
<!--
/////////////////////
// end section 6 //
// end section 7 //
/////////////////////
-->
<!--
/////////////////////
// start section 7 //
// start section 8 //
/////////////////////
-->
<div id="section7" class="section vertical-split">
<div id="section8" class="section vertical-split">
<div class="split-top split">
<div class="split-left">
@ -557,7 +613,7 @@
<!--
/////////////////////
// end section 7 //
// end section 8 //
/////////////////////
-->
@ -565,10 +621,10 @@
<!--
/////////////////////
// start section 8 //
// start section 9 //
/////////////////////
-->
<div id="section8" class="section vertical-split">
<div id="section9" class="section vertical-split">
<div class="split-top split">
<div class="split-left">
@ -630,16 +686,16 @@
</div>
<!--
/////////////////////
// end section 8 //
// end section 9 //
/////////////////////
-->
<!--
/////////////////////
// start section 9 //
// start section 10 //
/////////////////////
-->
<div id="section9" class="section vertical-split">
<div id="section10" class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
@ -868,16 +924,16 @@
</div>
<!--
/////////////////////
// end section 9 //
// end section 10 //
/////////////////////
-->
<!--
/////////////////////
// start section 10 //
// start section 11 //
/////////////////////
-->
<div id="section10" class="section vertical-split">
<div id="section11" class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
@ -1131,16 +1187,16 @@
</div>
<!--
/////////////////////
// end section 10 //
// end section 11 //
/////////////////////
-->
<!--
/////////////////////
// start section 11 //
// start section 12 //
/////////////////////
-->
<div id="section11" class="section vertical-split">
<div id="section12" class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
@ -1253,16 +1309,16 @@ skins.forEach((skin, i) => {
</div>
<!--
/////////////////////
// end section 11 //
// end section 12 //
/////////////////////
-->
<!--
/////////////////////
// start section 12 //
// start section 13 //
/////////////////////
-->
<div id="section12" class="section vertical-split">
<div id="section13" class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
@ -1346,16 +1402,16 @@ skins.forEach((skin, i) => {
</div>
<!--
/////////////////////
// end section 12 //
// end section 13 //
/////////////////////
-->
<!--
/////////////////////
// start section 13 //
// start section 14 //
/////////////////////
-->
<div id="section13" class="section vertical-split">
<div id="section14" class="section vertical-split">
<div class="split-top split">
<div class="split-left">
@ -1441,16 +1497,16 @@ function loadPageDragon(pageIndex) {
</div>
<!--
/////////////////////
// end section 13 //
// end section 14 //
/////////////////////
-->
<!--
/////////////////////
// start section 14 //
// start section 15 //
/////////////////////
-->
<div id="section14" class="section vertical-split">
<div id="section15" class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
@ -1544,16 +1600,16 @@ stretchyman.update = (canvas, delta, skeleton, state) => {
<!--
/////////////////////
// end section 14 //
// end section 15 //
/////////////////////
-->
<!--
/////////////////////
// start section 15 //
// start section 16 //
/////////////////////
-->
<div id="section15" class="section vertical-split">
<div id="section16" class="section vertical-split">
<div class="split" style="width: 100%; flex-direction: column;">
@ -1649,16 +1705,16 @@ stretchyman.update = (canvas, delta, skeleton, state) => {
<!--
/////////////////////
// end section 15 //
// end section 16 //
/////////////////////
-->
<!--
/////////////////////
// start section 16 //
// start section 17 //
/////////////////////
-->
<div id="section16" class="section vertical-split">
<div id="section17" class="section vertical-split">
<div class="split-left" style="width: 80%; box-sizing: border-box;">
More examples for <code>clip</code> attribute.
@ -1798,20 +1854,17 @@ stretchyman.update = (canvas, delta, skeleton, state) => {
<!--
/////////////////////
// end section 16 //
// end section 17 //
/////////////////////
-->
<!--
/////////////////////
// start section 17 //
// start section 18 //
/////////////////////
-->
<div id="section17" class="section vertical-split">
<div id="section18" class="section vertical-split">
<div class="split-top split">
<div class="split-left">
@ -1844,7 +1897,7 @@ stretchyman.update = (canvas, delta, skeleton, state) => {
<!--
/////////////////////
// end section 17 //
// end section 18 //
/////////////////////
-->
@ -1947,8 +2000,8 @@ stretchyman.update = (canvas, delta, skeleton, state) => {
}
}
makeDraggable(document.getElementById(`section6-element`));
makeResizable(document.getElementById(`section6-element`));
makeDraggable(document.getElementById(`section7-element`));
makeResizable(document.getElementById(`section7-element`));
</script>
</body>
</html>

View File

@ -110,6 +110,10 @@ interface WidgetAttributes {
yAxis: number
offsetX: number
offsetY: number
padLeft: number
padRight: number
padTop: number
padBottom: number
width: number
height: number
isDraggable: boolean
@ -220,22 +224,22 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
public fit: FitType = "contain";
/**
* Specify the way the skeleton is centered within the div:
* - `inside`: the skeleton bounds center is centered with the div container (Default)
* - `origin`: the skeleton origin is centered with the div container regardless of the bounds.
* 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 in div width units
* The x offset of the skeleton world origin x axis as a percentage of the element container width
* Connected to `x-axis` attribute.
*/
public xAxis = 0;
/**
* The y offset of the skeleton world origin x axis in div width units
* The y offset of the skeleton world origin x axis as a percentage of the element container height
* Connected to `y-axis` attribute.
*/
public yAxis = 0;
@ -252,9 +256,33 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
*/
public offsetY = 0;
/**
* A padding that shrink the element container virtually from left as a percentage of the element container width
* Connected to `pad-left` attribute.
*/
public padLeft = 0;
/**
* A padding that shrink the element container virtually from right as a percentage of the element container width
* Connected to `pad-right` attribute.
*/
public padRight = 0;
/**
* A padding that shrink the element container virtually from the top as a percentage of the element container height
* Connected to `pad-top` attribute.
*/
public padTop = 0;
/**
* A padding that shrink the element container virtually from the bottom as a percentage of the element container height
* Connected to `pad-bottom` attribute.
*/
public padBottom = 0;
/**
* Specify a fixed width for the widget. If at least one of `width` and `height` is > 0,
* the widget will have an actual size and the div reference is the widget itself, not the div parent.
* the widget will have an actual size and the element container reference is the widget itself, not the element container parent.
* Connected to `width` attribute.
*/
public get width (): number {
@ -268,7 +296,7 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
/**
* Specify a fixed height for the widget. If at least one of `width` and `height` is > 0,
* the widget will have an actual size and the div reference is the widget itself, not the div parent.
* the widget will have an actual size and the element container reference is the widget itself, not the element container parent.
* Connected to `height` attribute.
*/
public get height (): number {
@ -316,14 +344,14 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
public pages?: Array<number>;
/**
* If `true`, the skeleton is clipped to the container div bounds.
* If `true`, the skeleton is clipped to the element container bounds.
* Be careful on using this feature because it breaks batching!
* Connected to `clip` attribute.
*/
public clip = false;
/**
* The widget update/apply behaviour when the skeleton div container is offscreen:
* The widget update/apply behaviour when the skeleton element container is offscreen:
* - `pause`: the state is not updated, neither applied (Default)
* - `update`: the state is updated, but not applied
* - `pose`: the state is updated and applied
@ -356,7 +384,7 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
public afterUpdateWorldTransforms: BeforeAfterUpdateSpineWidgetFunction = () => { };
/**
* A callback invoked each time div hosting the widget enters the screen viewport.
* A callback invoked each time the element container enters the screen viewport.
* By default, the callback call the {@link start} method the first time the widget
* enters the screen viewport.
*/
@ -423,12 +451,12 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
public started = false;
/**
* True, when the div hosting the widget enters the screen viewport. It uses an IntersectionObserver internally.
* True, when the element container enters the screen viewport. It uses an IntersectionObserver internally.
*/
public onScreen = false;
/**
* True, when the div hosting the widget enters the screen viewport at least once.
* True, when the element container enters the screen viewport at least once.
* It uses an IntersectionObserver internally.
*/
public onScreenAtLeastOnce = false;
@ -499,6 +527,10 @@ export class SpineWebComponentWidget extends HTMLElement implements Disposable,
"y-axis": { propertyName: "yAxis", type: "number" },
"offset-x": { propertyName: "offsetX", type: "number" },
"offset-y": { propertyName: "offsetY", type: "number" },
"pad-left": { propertyName: "padLeft", type: "number" },
"pad-right": { propertyName: "padRight", type: "number" },
"pad-top": { propertyName: "padTop", type: "number" },
"pad-bottom": { propertyName: "padBottom", type: "number" },
identifier: { propertyName: "identifier", type: "string" },
debug: { propertyName: "debug", type: "boolean" },
"manual-start": { propertyName: "manualStart", type: "boolean" },
@ -1029,11 +1061,11 @@ class SpineWebComponentOverlay extends HTMLElement implements Disposable {
this.renderer.end();
// set the new viewport to the div bound
const viewportWidth = divBounds.width * window.devicePixelRatio;
const viewporthHeight = divBounds.height * window.devicePixelRatio;
const viewportWidth = this.screenToWorldLength(divBounds.width);
const viewporthHeight = this.screenToWorldLength(divBounds.height);
this.renderer.context.gl.viewport(
divBounds.x * window.devicePixelRatio,
this.canvas.height - (divBounds.y + divBounds.height) * window.devicePixelRatio,
this.screenToWorldLength(divBounds.x),
this.canvas.height - this.screenToWorldLength(divBounds.y + divBounds.height),
viewportWidth,
viewporthHeight
);
@ -1073,7 +1105,6 @@ class SpineWebComponentOverlay extends HTMLElement implements Disposable {
let renderer = this.renderer;
renderer.begin();
const devicePixelRatio = window.devicePixelRatio;
const tempVector = new Vector3();
this.skeletonList.forEach((widget) => {
const { skeleton, bounds, mode, debug, offsetX, offsetY, xAxis, yAxis, dragX, dragY, fit, loadingSpinner, onScreen, loading, clip, isDraggable } = widget;
@ -1084,24 +1115,30 @@ class SpineWebComponentOverlay extends HTMLElement implements Disposable {
divBounds.x = divBounds.left + this.overflowLeftSize;
divBounds.y = divBounds.top + this.overflowTopSize;
const { padLeft, padRight, padTop, padBottom } = widget
const paddingShiftHorizontal = (padLeft - padRight) / 2;
const paddingShiftVertical = (padTop - padBottom) / 2;
let divOriginX = 0;
let divOriginY = 0;
if (clip) {
// in clip mode, the world origin is the div center (divBounds center)
clipToBoundStart(divBounds);
divOriginX = divBounds.width * (xAxis * window.devicePixelRatio);
divOriginY = divBounds.height * (yAxis * window.devicePixelRatio);
divOriginX = this.screenToWorldLength(divBounds.width * (xAxis + paddingShiftHorizontal));
divOriginY = this.screenToWorldLength(divBounds.height * (yAxis - paddingShiftVertical));
} else {
// get the desired point into the the div (center by default) in world coordinate
const divX = divBounds.x + divBounds.width * (xAxis + .5);
const divY = divBounds.y + divBounds.height * (-yAxis + .5) - 1;
const divX = divBounds.x + divBounds.width * ((xAxis + .5) + paddingShiftHorizontal);
const divY = divBounds.y + divBounds.height * ((-yAxis + .5) + paddingShiftVertical) - 1;
this.screenToWorld(tempVector, divX, divY);
divOriginX = tempVector.x;
divOriginY = tempVector.y;
}
const divWidthWorld = divBounds.width * devicePixelRatio;
const divHeightWorld = divBounds.height * devicePixelRatio;
const paddingShrinkWidth = 1 - (padLeft + padRight);
const paddingShrinkHeight = 1 - (padTop + padBottom);
const divWidthWorld = this.screenToWorldLength(divBounds.width * paddingShrinkWidth);
const divHeightWorld = this.screenToWorldLength(divBounds.height * paddingShrinkHeight);
if (loading) {
if (loadingSpinner) {
@ -1193,9 +1230,9 @@ class SpineWebComponentOverlay extends HTMLElement implements Disposable {
let { x: ax, y: ay, width: aw, height: ah } = bounds!;
this.worldToScreen(tempVector, ax * skeleton.scaleX + worldOffsetX, ay * skeleton.scaleY + worldOffsetY);
widget.dragBoundsRectangle.x = tempVector.x + window.scrollX;
widget.dragBoundsRectangle.y = tempVector.y - ah * skeleton.scaleY / window.devicePixelRatio + window.scrollY;
widget.dragBoundsRectangle.width = aw * skeleton.scaleX / window.devicePixelRatio;
widget.dragBoundsRectangle.height = ah * skeleton.scaleY / window.devicePixelRatio;
widget.dragBoundsRectangle.y = tempVector.y - this.worldToScreenLength(ah * skeleton.scaleY) + window.scrollY;
widget.dragBoundsRectangle.width = this.worldToScreenLength(aw * skeleton.scaleX);
widget.dragBoundsRectangle.height = this.worldToScreenLength(ah * skeleton.scaleY);
if (clip) {
widget.dragBoundsRectangle.x += divBounds.x;
@ -1298,8 +1335,8 @@ class SpineWebComponentOverlay extends HTMLElement implements Disposable {
this.skeletonList.forEach(widget => {
if (!widget.dragging || (!widget.onScreen && widget.dragX === 0 && widget.dragY === 0)) return;
const skeleton = widget.skeleton!;
widget.dragX += dragX * window.devicePixelRatio;
widget.dragY -= dragY * window.devicePixelRatio;
widget.dragX += this.screenToWorldLength(dragX);
widget.dragY -= this.screenToWorldLength(dragY);
skeleton.physicsTranslate(dragX, dragY);
ev?.preventDefault();
ev?.stopPropagation();
@ -1387,9 +1424,8 @@ class SpineWebComponentOverlay extends HTMLElement implements Disposable {
private resize (width: number, height: number) {
let canvas = this.canvas;
const dpr = window.devicePixelRatio;
this.canvas.width = Math.round(width * dpr);
this.canvas.height = Math.round(height * dpr);
this.canvas.width = Math.round(this.screenToWorldLength(width));
this.canvas.height = Math.round(this.screenToWorldLength(height));
this.renderer.context.gl.viewport(0, 0, canvas.width, canvas.height);
this.renderer.camera.setViewport(canvas.width, canvas.height);
this.renderer.camera.update();
@ -1417,16 +1453,22 @@ class SpineWebComponentOverlay extends HTMLElement implements Disposable {
/*
* Other utilities
*/
private screenToWorld (vec: Vector3, x: number, y: number) {
public screenToWorld (vec: Vector3, x: number, y: number) {
vec.set(x, y, 0);
// pay attention that clientWidth/Height rounds the size - if we don't like it, we should use getBoundingClientRect as in getPagSize
this.renderer.camera.screenToWorld(vec, this.canvas.clientWidth, this.canvas.clientHeight);
}
private worldToScreen (vec: Vector3, x: number, y: number) {
public worldToScreen (vec: Vector3, x: number, y: number) {
vec.set(x, -y, 0);
// pay attention that clientWidth/Height rounds the size - if we don't like it, we should use getBoundingClientRect as in getPagSize
// this.renderer.camera.worldToScreen(vec, this.canvas.clientWidth, this.canvas.clientHeight);
this.renderer.camera.worldToScreen(vec, this.renderer.camera.viewportWidth / window.devicePixelRatio, this.renderer.camera.viewportHeight / window.devicePixelRatio);
this.renderer.camera.worldToScreen(vec, this.worldToScreenLength(this.renderer.camera.viewportWidth), this.worldToScreenLength(this.renderer.camera.viewportHeight));
}
public screenToWorldLength (length: number) {
return length * window.devicePixelRatio;
}
public worldToScreenLength (length: number) {
return length / window.devicePixelRatio;
}
}