Give texts, tilemaps, particles and containers the turn knob (#242)
The knob on its stem turned and scaled only sprites and colliders. Every other type with Scale and Rotation had to be typed into the inspector. - Text, tilemap and particle source get the knob above their box. Edge and corner grips stay with sprite and collider, the only types with a width and a height of their own. - A container gets a thin frame round all of its content, turned and scaled with it, and the knob sits above that frame. It turns and scales round the container's origin, so the content swings with it. A container with no content gets the knob above its symbol. - contentFrame in sizeGizmo.ts is the pure rule for that frame. sizeGizmoEditor.ts wires it in. - One pull is one undo step, as for sprites. Not clicked in the browser: scaling (as opposed to turning) of a container by the knob. Only the shared code path covers it. Tests: - ./check.sh: 178 files, 2573 tests, CHECK EXIT 0 - 2 new unit tests for contentFrame - e2e 29k, 4 checks: knob on text, tilemap and particles, frame round the container's content, a real pull turns the container by 20.9 degrees round its origin with the content on the computed pixel, one undo takes it back - 4 counter-probes over #242 and #243 bite (2 unit, 2 browser) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
98b9b300d9
commit
074bda2247
4 files changed
+196
-14
No files matched your search
@@ -341,6 +341,94 @@ export async function transform(t) {
|
||||
await evaluate('__gm.state.select(null)');
|
||||
await remove(['otf-size', 'otf-sizec']);
|
||||
|
||||
// 29k. The knob on everything else with a turn and a scale (#242): a text, a tilemap, a particle
|
||||
// source and a container get the knob, but no edges or corners. The container's knob stands
|
||||
// over a frame round its content – a 100×40 sprite 150 to the right of its origin – and
|
||||
// pulling it across turns the container round that origin, the content swinging with it.
|
||||
const OTHERS = ['otf-text', 'otf-map', 'otf-pt', 'otf-box'];
|
||||
const INSIDE = 'otf-box~tf-in';
|
||||
await mutate(`
|
||||
const p = __gm.state.project;
|
||||
const scene = __gm.state.activeScene;
|
||||
const make = (id, assetId, parentId, position) => ({ id, assetId, name: '', position, editorOffset: { x: 0, y: 0 }, overrides: {}, parentId, instanceEdits: {} });
|
||||
p.assets.push(
|
||||
{ id: 'tf-text', type: 'text', name: 'tf-text', props: { text: 'Knob', fontSize: 32 } },
|
||||
{ id: 'tf-map', type: 'tilemap', name: 'tf-map', props: { columns: 3, rows: 2, tileSize: 32 }, layers: [{ id: 'tf-l', name: 'Ground', cells: {} }] },
|
||||
{ id: 'tf-pt', type: 'particles', name: 'tf-pt', props: {}, graph: { nodes: [], edges: [] } },
|
||||
{ id: 'tf-box', type: 'container', name: 'tf-box', props: { enabled: true }, objects: [make('tf-in', ${JSON.stringify(SPRITE)}, '', { x: 150, y: 0, z: 0 })] },
|
||||
);
|
||||
scene.objects.push(
|
||||
make('otf-text', 'tf-text', '', { x: -250, y: ${LANE - 120}, z: 960 }),
|
||||
make('otf-map', 'tf-map', '', { x: -250, y: ${LANE + 120}, z: 960 }),
|
||||
make('otf-pt', 'tf-pt', '', { x: 250, y: ${LANE - 120}, z: 960 }),
|
||||
make('otf-box', 'tf-box', '', { x: 0, y: ${LANE + 120}, z: 960 }),
|
||||
)`);
|
||||
const knobs = {};
|
||||
for (const id of ['otf-text', 'otf-map', 'otf-pt']) {
|
||||
await selectObject(id);
|
||||
const g = await gizmo();
|
||||
const r = await rect(id);
|
||||
knobs[id] = { g, r };
|
||||
}
|
||||
check(
|
||||
'a text, a tilemap and a particle source get the knob above their box, without edges or corners',
|
||||
Object.entries(knobs).every(
|
||||
([id, { g, r }]) =>
|
||||
g?.objectId === id &&
|
||||
g.corners.length === 0 &&
|
||||
g.edges.length === 0 &&
|
||||
Math.abs(g.knob.x - r.x) < 2 &&
|
||||
g.knob.y < r.y - r.h / 2 - 10,
|
||||
),
|
||||
JSON.stringify(knobs),
|
||||
);
|
||||
|
||||
await selectObject('otf-box');
|
||||
const b0 = await gizmo();
|
||||
const in0 = await rect(INSIDE);
|
||||
const xs = (b0?.box ?? []).map((c) => c.x);
|
||||
const ys = (b0?.box ?? []).map((c) => c.y);
|
||||
check(
|
||||
"a container's knob stands over a frame round its content, not over its icon",
|
||||
b0?.objectId === 'otf-box' &&
|
||||
b0.corners.length === 0 &&
|
||||
b0.edges.length === 0 &&
|
||||
Math.abs(Math.min(...xs) - (in0.x - in0.w / 2)) < 2 &&
|
||||
Math.abs(Math.max(...xs) - (in0.x + in0.w / 2)) < 2 &&
|
||||
Math.abs(Math.min(...ys) - (in0.y - in0.h / 2)) < 2 &&
|
||||
Math.abs(Math.max(...ys) - (in0.y + in0.h / 2)) < 2 &&
|
||||
Math.abs(b0.knob.x - in0.x) < 2 &&
|
||||
b0.knob.y < in0.y - in0.h / 2 - 10,
|
||||
JSON.stringify({ b0, in0 }),
|
||||
);
|
||||
|
||||
if (!b0) return;
|
||||
const origin = { x: in0.x - 150 * s, y: in0.y };
|
||||
await path(line(b0.knob, { x: b0.knob.x + 60, y: b0.knob.y + 60 }));
|
||||
const turnedBox = (await object('otf-box')).overrides;
|
||||
const theta = (Number(turnedBox.rotation) * Math.PI) / 180;
|
||||
const in1 = await rect(INSIDE);
|
||||
const swung = { x: origin.x + 150 * s * Math.cos(theta), y: origin.y + 150 * s * Math.sin(theta) };
|
||||
check(
|
||||
'pulling the knob across turns the container round its origin, and its content swings with it',
|
||||
Math.abs(Number(turnedBox.rotation)) > 5 &&
|
||||
turnedBox.scale === undefined &&
|
||||
Math.abs(in1.x - swung.x) < 3 &&
|
||||
Math.abs(in1.y - swung.y) < 3,
|
||||
JSON.stringify({ turnedBox, in1, swung }),
|
||||
);
|
||||
await shot('29-container-knob');
|
||||
await undo();
|
||||
check(
|
||||
'one undo takes the turn of the container back',
|
||||
(await object('otf-box')).overrides.rotation === undefined,
|
||||
);
|
||||
await evaluate('__gm.state.select(null)');
|
||||
await remove(OTHERS);
|
||||
await mutate(`
|
||||
const gone = new Set(['tf-text', 'tf-map', 'tf-pt', 'tf-box']);
|
||||
__gm.state.project.assets = __gm.state.project.assets.filter((a) => !gone.has(a.id))`);
|
||||
|
||||
// 29i. Cleanup, and the view back where the earlier steps left the scene.
|
||||
await remove([BOX, CHILD, CAMERA]);
|
||||
await mutate(`
|
||||
|
||||
Reference in new issue
Block a user