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:
1 parent
cc6db5f350
commit
94a0a578ed
8 files changed
+105
-21
No files matched your search
@@ -16,7 +16,7 @@ describe('CreateTextureCommand', () => {
|
||||
const asset = findAsset(project, command.asset.id);
|
||||
expect(asset).toMatchObject({ type: 'texture', name: 'Hero', file: FILE, width: 64, height: 32 });
|
||||
// The schema default comes along, so the new texture is a complete asset – nothing to fill in later.
|
||||
expect(asset?.props).toEqual({ filter: 'linear' });
|
||||
expect(asset?.props).toEqual({ filter: 'linear', sliceScale: 1 });
|
||||
|
||||
stack.undo();
|
||||
expect(project.assets).toHaveLength(0);
|
||||
|
||||
@@ -83,7 +83,14 @@ describe('resolveTexture', () => {
|
||||
fill: 'repeat',
|
||||
sourceW: 32,
|
||||
sourceH: 24,
|
||||
scale: 1,
|
||||
});
|
||||
// The scale is the texture's, not the frame's (#96); a value that is no size falls back to 1.
|
||||
texture.props.sliceScale = 2.5;
|
||||
expect(resolveTexture(fixture.project, sprite.props)?.slice?.scale).toBe(2.5);
|
||||
texture.props.sliceScale = 0;
|
||||
expect(resolveTexture(fixture.project, sprite.props)?.slice?.scale).toBe(1);
|
||||
delete texture.props.sliceScale;
|
||||
delete texture.frames[0]!.border;
|
||||
delete texture.frames[0]!.fill;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ const slice = (border: Partial<NineSlice['border']>, fill: NineSlice['fill'] = '
|
||||
fill,
|
||||
sourceW: 24,
|
||||
sourceH: 24,
|
||||
scale: 1,
|
||||
});
|
||||
|
||||
describe('sliceSpans', () => {
|
||||
@@ -78,6 +79,42 @@ describe('sliceSpans', () => {
|
||||
it('answers nothing for a box or a picture of no size', () => {
|
||||
expect(sliceSpans(0, 24, 8, 8, 'stretch')).toEqual([]);
|
||||
expect(sliceSpans(200, 0, 8, 8, 'stretch')).toEqual([]);
|
||||
expect(sliceSpans(200, 24, 8, 8, 'stretch', 0)).toEqual([]);
|
||||
});
|
||||
|
||||
it('draws the borders scaled, and samples the same pixels for them (#96)', () => {
|
||||
const spans = sliceSpans(200, 24, 8, 8, 'stretch', 2);
|
||||
expect(spans.map((s) => [s.p0, s.p1])).toEqual([
|
||||
[0, 16],
|
||||
[16, 184],
|
||||
[184, 200],
|
||||
]);
|
||||
// Bigger on screen, not more of the picture: the corner still shows its eight pixels.
|
||||
expect(spans.map((s) => [s.t0, s.t1])).toEqual([
|
||||
[0, 1 / 3],
|
||||
[1 / 3, 2 / 3],
|
||||
[2 / 3, 1],
|
||||
]);
|
||||
// Below 1 the borders shrink the same way.
|
||||
expect(sliceSpans(200, 24, 8, 8, 'stretch', 0.5)[0]).toMatchObject({ p0: 0, p1: 4 });
|
||||
});
|
||||
|
||||
it('scales the repeated tile with the borders, so the pattern keeps its proportion', () => {
|
||||
const middle = sliceSpans(80, 24, 8, 8, 'repeat', 2).slice(1, -1);
|
||||
// A middle of 48 units in tiles of 8 × 2 = 16.
|
||||
expect(middle.map((s) => [s.p0, s.p1])).toEqual([
|
||||
[16, 32],
|
||||
[32, 48],
|
||||
[48, 64],
|
||||
]);
|
||||
});
|
||||
|
||||
it('squeezes scaled borders like unscaled ones when the sprite is too small for them', () => {
|
||||
// 16 + 16 on a box of 20: both shrink to 10, the middle is gone.
|
||||
expect(sliceSpans(20, 24, 8, 8, 'stretch', 2).map((s) => [s.p0, s.p1])).toEqual([
|
||||
[0, 10],
|
||||
[10, 20],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -115,5 +152,6 @@ describe('sliceKey', () => {
|
||||
expect(sliceKey(base, 200, 100)).not.toBe(sliceKey(base, 201, 100));
|
||||
expect(sliceKey(base, 200, 100)).not.toBe(sliceKey(slice({ left: 8, right: 7 }), 200, 100));
|
||||
expect(sliceKey(base, 200, 100)).not.toBe(sliceKey(slice({ left: 8, right: 8 }, 'repeat'), 200, 100));
|
||||
expect(sliceKey(base, 200, 100)).not.toBe(sliceKey({ ...base, scale: 2 }, 200, 100));
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user