A border drawn in pixels is as wide as the texture says a pixel is, so a small sheet can frame a large sprite (#96)

A sliced frame drew its borders one source pixel to one sprite unit. Pixel-art sheets are small:
a 16px corner on a 400-unit button is a thin line, and the only way around it was a larger image.

The texture now carries `Nine-slice scale` (default 1), next to `Filter`. It sets how many sprite
units one border pixel takes, for every sliced frame of that texture. The UVs stay as they are -
a corner shows the same pixels, only larger or smaller. When the sprite is too small for both
borders they are still scaled down together, now starting from the scaled size. With `Repeat`
the tile size scales too, otherwise a doubled border would sit beside a pattern of the old size.

The value is a schema row, so inspector field, saving and undo come without their own code.
Anything that is not a size above 0 reads as 1, and documents without it draw as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5 committed 2026-09-16 18:09:43 +02:00
1 parent cc6db5f350
commit 94a0a578ed
8 files changed
+105 -21

No files matched your search

+13 -3
View File
@@ -45,6 +45,16 @@ export interface NineSlice {
/** Size of the frame in image pixels; the borders are distances inside it. */
sourceW: number;
sourceH: number;
/** Sprite units one border pixel is drawn with – the texture's, shared by all its frames (#96). */
scale: number;
}
export const SLICE_SCALE_KEY = 'sliceScale';
/** The texture's nine-slice scale; anything that is not a size above zero reads as the default 1. */
export function sliceScaleOf(asset: TextureAsset): number {
const value = Number(asset.props[SLICE_SCALE_KEY] ?? 1);
return Number.isFinite(value) && value > 0 ? value : 1;
}
export interface FrameRef {
@@ -102,7 +112,7 @@ function refFor(asset: TextureAsset, frame: TextureFrame | null): TextureRef | n
rect: frame ? { x: frame.x, y: frame.y, w: frame.w, h: frame.h } : null,
sourceWidth: asset.width,
sourceHeight: asset.height,
slice: frame ? nineSliceOf(frame) : null,
slice: frame ? nineSliceOf(frame, sliceScaleOf(asset)) : null,
};
}
@@ -125,10 +135,10 @@ export function hasBorders(border: FrameBorders): boolean {
}
/** What a frame is sliced into, or null when nobody sliced it – then a sprite draws it as one piece. */
export function nineSliceOf(frame: TextureFrame): NineSlice | null {
export function nineSliceOf(frame: TextureFrame, scale = 1): NineSlice | null {
const border = normalizeBorders(frameBorders(frame), frame.w, frame.h);
if (!hasBorders(border)) return null;
return { border, fill: frameFill(frame), sourceW: frame.w, sourceH: frame.h };
return { border, fill: frameFill(frame), sourceW: frame.w, sourceH: frame.h, scale };
}
/**