A frame that keeps its corners can be stretched to any size (#41)
game-maker / suite (push) Successful in 6m21s
game-maker / suite (push) Successful in 6m21s
TextureFrame carries optional `border` (left/right/top/bottom) and `fill` (stretch | repeat); absent keys mean "not sliced", so no format bump and undo restores the plain frame instead of four zeroes. The sliced sprite builds its own geometry instead of the shared UNIT_QUAD: a 4x4 grid, and for `repeat` one quad per tile carrying the frame cell's real UVs -- tiling in the geometry, not in the sampler, so a frame out of a sheet cannot pull its neighbour in. Above 64 tiles per axis it stretches, to bound the buffer. A sprite narrower than left+right scales both borders down together until the middle is exactly 0, so the corners meet at a seam and never overlap. The texture editor gets four draggable border lines plus the same four numbers in the inspector. Hit order is resize handles, then the lines, then the frame body -- handleAt reports 'move' anywhere inside a frame, so lines placed after it were unreachable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b9f1a9d319
commit
be8f2331de
17 files changed
+1091
-31
No files matched your search
@@ -54,6 +54,57 @@ describe('serialize', () => {
|
||||
expect(machine.scope).toBeUndefined();
|
||||
});
|
||||
|
||||
/** #41: the borders are part of the frame, and a frame nobody sliced still saves without them. */
|
||||
it('round-trips the nine-slice of a frame and repairs an impossible one', () => {
|
||||
const file = `${'ab'.repeat(32)}.png`;
|
||||
const loaded = deserializeProject({
|
||||
assets: [
|
||||
{
|
||||
id: 't1',
|
||||
type: 'texture',
|
||||
name: 'Sheet',
|
||||
file,
|
||||
width: 64,
|
||||
height: 64,
|
||||
frames: [
|
||||
{
|
||||
id: 'f1',
|
||||
name: 'Button',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 24,
|
||||
h: 24,
|
||||
border: { left: 8, right: 8 },
|
||||
fill: 'repeat',
|
||||
},
|
||||
{ id: 'f2', name: 'Plain', x: 24, y: 0, w: 24, h: 24 },
|
||||
{
|
||||
id: 'f3',
|
||||
name: 'Greedy',
|
||||
x: 0,
|
||||
y: 24,
|
||||
w: 8,
|
||||
h: 8,
|
||||
border: { top: 99, bottom: 99 },
|
||||
fill: 'wat',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
const texture = loaded.assets[0]!;
|
||||
if (texture.type !== 'texture') throw new Error('wrong types');
|
||||
const [sliced, plain, greedy] = texture.frames;
|
||||
expect(sliced?.border).toEqual({ left: 8, top: 0, right: 8, bottom: 0 });
|
||||
expect(sliced?.fill).toBe('repeat');
|
||||
expect(plain?.border).toBeUndefined();
|
||||
expect(plain?.fill).toBeUndefined();
|
||||
// Measured against the frame, not the image, and never leaving it without a middle.
|
||||
expect(greedy?.border).toEqual({ left: 0, top: 4, right: 0, bottom: 3 });
|
||||
expect(greedy?.fill).toBeUndefined();
|
||||
expect(roundTrip(loaded)).toEqual(loaded);
|
||||
});
|
||||
|
||||
it('repairs broken documents', () => {
|
||||
const project = deserializeProject(
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user