Add ↔ next to Width and ↕ next to Height in the inspector. Each sets its field so the sprite matches the ratio of its assigned texture or frame, reading the frame's own rect rather than the sheet's. The buttons reuse the existing FieldActions convention, so they simply do not appear when no texture is assigned, when the sprite is already proportional, or on assets whose schema has no texture property. Comparison is a cross product with a relative tolerance: 3135 of the 40000 size pairs up to 200x200 do not survive a round trip through a division, so an exact check would leave the button standing after its own click. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
114 lines
5.3 KiB
JavaScript
114 lines
5.3 KiB
JavaScript
/**
|
||
* The ↔ / ↕ buttons beside a sprite's Width and Height: fit that side to the proportions of the
|
||
* picture the sprite wears, and not exist at all while it wears none.
|
||
*
|
||
* Runs on the sheet the texture steps imported (64 × 48) and adds a frame with a deliberately
|
||
* different shape, which is what makes "sheet or frame?" a question the numbers can answer.
|
||
*/
|
||
|
||
/** The rows are read as numbers, so a float from a division needs a tolerance. */
|
||
const near = (value, expected) => Math.abs(value - expected) < 0.01;
|
||
|
||
const spriteProp = (key) => `__gm.state.project.assets.find((a) => a.id === 'tex-sprite').props.${key}`;
|
||
|
||
export async function aspect(t) {
|
||
const { check, sleep, json, evaluate, shot, clickText, press, undo, mutate, select } = t;
|
||
|
||
await press('Escape');
|
||
await evaluate('document.activeElement instanceof HTMLElement && document.activeElement.blur()');
|
||
const sheet = await json("(__gm.state.project.assets.find((a) => a.type === 'texture') ?? {}).id ?? ''");
|
||
check('the imported sheet is still there', sheet !== '', sheet);
|
||
|
||
// 26. A sprite wearing nothing has nothing to be proportional to, so the buttons are absent.
|
||
// Edited as an asset: one base value per row instead of an override, so the numbers below
|
||
// say what they mean.
|
||
await mutate(`
|
||
const props = __gm.state.project.assets.find((a) => a.id === 'tex-sprite').props;
|
||
props.texture = '';
|
||
props.width = 100;
|
||
props.height = 100`);
|
||
await select({ kind: 'asset', assetId: 'tex-sprite' });
|
||
await sleep(600);
|
||
check('the sprite asset is in the inspector', Boolean(await json("__gm.locate('Width')")));
|
||
check(
|
||
'no texture, no aspect buttons',
|
||
(await json("__gm.locate('↔')")) === null && (await json("__gm.locate('↕')")) === null,
|
||
);
|
||
await shot('40-aspect-without-a-texture');
|
||
|
||
// 27. Wearing the 64 x 48 sheet, a 100 x 100 sprite is out of proportion – both appear.
|
||
await mutate(`${spriteProp('texture')} = ${JSON.stringify(sheet)}`);
|
||
await sleep(500);
|
||
check(
|
||
'assigning a texture brings both buttons out',
|
||
Boolean(await json("__gm.locate('↔')")) && Boolean(await json("__gm.locate('↕')")),
|
||
);
|
||
await shot('41-aspect-buttons');
|
||
|
||
// 28. ↔ moves the width only, to the sheet's ratio: 100 x 64/48.
|
||
await clickText('↔');
|
||
await sleep(500);
|
||
const fitted = { w: await json(spriteProp('width')), h: await json(spriteProp('height')) };
|
||
check(
|
||
'↔ sets the width from the height and the sheet ratio',
|
||
near(fitted.w, (100 * 64) / 48) && fitted.h === 100,
|
||
JSON.stringify(fitted),
|
||
);
|
||
check(
|
||
'and both buttons go once the sprite is proportional',
|
||
(await json("__gm.locate('↔')")) === null && (await json("__gm.locate('↕')")) === null,
|
||
);
|
||
await shot('42-aspect-fitted');
|
||
|
||
// 28a. One command, so it is one undo step – and the buttons come back with the old shape.
|
||
await undo();
|
||
await sleep(500);
|
||
check('undo takes the width back', (await json(spriteProp('width'))) === 100);
|
||
check('and the buttons return with it', Boolean(await json("__gm.locate('↔')")));
|
||
await t.redo();
|
||
await sleep(500);
|
||
|
||
// 29. A frame is the picture the sprite wears, not the sheet it was cut from: 40 x 10 against
|
||
// the sheet's 64 x 48, so the two ratios cannot be confused for one another.
|
||
await mutate(`
|
||
const asset = __gm.state.project.assets.find((a) => a.id === ${JSON.stringify(sheet)});
|
||
asset.frames.push({ id: 'aspect-frame', name: 'Wide', x: 0, y: 0, w: 40, h: 10 });
|
||
${spriteProp('texture')} = 'aspect-frame'`);
|
||
await sleep(500);
|
||
check('a frame of another shape brings the buttons back', Boolean(await json("__gm.locate('↕')")));
|
||
|
||
const before = await json(spriteProp('width'));
|
||
await clickText('↕');
|
||
await sleep(500);
|
||
const framed = { w: await json(spriteProp('width')), h: await json(spriteProp('height')) };
|
||
check(
|
||
'↕ sets the height from the frame ratio, not the sheet it belongs to',
|
||
near(framed.h, (before * 10) / 40) && framed.w === before,
|
||
JSON.stringify({ framed, sheetWouldGive: (before * 48) / 64 }),
|
||
);
|
||
check(
|
||
'and the buttons go again',
|
||
(await json("__gm.locate('↔')")) === null && (await json("__gm.locate('↕')")) === null,
|
||
);
|
||
|
||
// 30. The same rows on a placed object write an override rather than the asset value.
|
||
await mutate(`${spriteProp('texture')} = ${JSON.stringify(sheet)}`);
|
||
await select({ kind: 'object', sceneId: 'scene1', objectIds: ['otex'], objectId: 'otex' });
|
||
await sleep(600);
|
||
await mutate("__gm.state.activeScene.objects.find((o) => o.id === 'otex').overrides.height = 90");
|
||
await sleep(400);
|
||
check('the object row offers the fit as well', Boolean(await json("__gm.locate('↔')")));
|
||
// The crowded case: an overridden, out-of-proportion row carries ↕ next to ↑ and ↺.
|
||
check('and shows it beside the override buttons', (await json('__gm.textOverflows()')).length === 0);
|
||
await shot('43-aspect-beside-the-override-buttons');
|
||
await clickText('↔');
|
||
await sleep(500);
|
||
const override = await json("__gm.state.activeScene.objects.find((o) => o.id === 'otex').overrides");
|
||
check(
|
||
'fitting a scene object writes an override, leaving the asset alone',
|
||
near(override.width, (90 * 64) / 48) && near(await json(spriteProp('width')), framed.w),
|
||
JSON.stringify({ override, asset: await json(spriteProp('width')) }),
|
||
);
|
||
await shot('44-aspect-on-an-object');
|
||
}
|