A selected vector shape has its own inspector rows (#129)
Stage 4 of #129. Until now the inspector showed the rows of the asset when a shape was selected, so there was no way to give a shape a colour, an outline or a place in the drawing order without editing the project file. The rows for a shape now sit where every other selection puts its rows. - SHAPES lists every shape of the asset, topmost row drawn last, the same way a layer list reads. Selecting the asset itself shows the same list below its own rows, with no shape selected. - Each row paints its shape with the commands the rasterizer uses (shapeDrawing plus paintDrawing) into a 22 px swatch, the whole canvas scaled in, so the row also says where on the canvas the shape sits. - The four order buttons from the issue (forward, to front, backward, to back) sit in two rows of two, and a row can be dragged to another place with a marker line. They are disabled when there is nothing to move. - VECTOR SHAPE carries name, fill, opacity and an outline checkbox, the checkbox adding and removing the stroke instead of hiding it; line colour and width appear with it. A dragged colour slider is one undo step, the style rows going through previewingHandlers like every other row. - A line width of 0 is no stroke, which the command rejects, so the row clamps at 0.1. Findings: - locate('Outline') aims beside the control, because it gives the centre of the label text and a wider label pushes a fixed offset out of the field. __gm.inspectorField(label) knows the centre of the control. - A click into a text field sets the cursor and selects nothing, so a typed hex landed appended to the old one and parseHex returned null without anything failing - typeText only checks that the text is contained. A Ctrl+A goes first now. - Four buttons side by side cut "Backward" to "Backw...", which only the screenshot showed. Two rows of two now, as in the tilemap panel. Tests: ./check.sh 128 files, 1897 tests (was 127/1883). Full e2e suite locally instead of only CI because the change sits in the inspector and touches every selection: ALL PASSED - 1179 checks (was 1172), module 47 at 24 checks (was 17). Three counter-checks hold: not reversing the list fails 1 unit test and 2 e2e checks, dropping the gap correction when dropping a row fails 2 unit tests and the drag check, and a constant signature kills module 47 because the outline rows never appear. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3a68067079
commit
78595ec80f
9 files changed
+958
-8
No files matched your search
+108
-2
@@ -69,8 +69,23 @@ const SHAPES = [
|
||||
];
|
||||
|
||||
export async function vectorArt(t) {
|
||||
const { check, click, doubleClick, drag, clickText, json, evaluate, mutate, settle, sleep, shot, showTab } =
|
||||
t;
|
||||
const {
|
||||
check,
|
||||
click,
|
||||
doubleClick,
|
||||
drag,
|
||||
clickText,
|
||||
json,
|
||||
evaluate,
|
||||
locate,
|
||||
mutate,
|
||||
press,
|
||||
settle,
|
||||
sleep,
|
||||
shot,
|
||||
showTab,
|
||||
typeText,
|
||||
} = t;
|
||||
const Y = LANES.vectorArt;
|
||||
|
||||
// 47a. A drawing on a sprite: what the rasteriser put on the GPU, read off the screen.
|
||||
@@ -235,4 +250,95 @@ export async function vectorArt(t) {
|
||||
Boolean(undrawn) && undrawn.file !== after?.file && undrawn.kept,
|
||||
JSON.stringify({ was: after?.file, now: undrawn?.file }),
|
||||
);
|
||||
|
||||
// 47e. The inspector (stage 4): the shapes are listed, picked, coloured and reordered there.
|
||||
const shapeIds = async () => (await json("__gm.vectorArt.shapes('va-art')")).map((s) => s.id);
|
||||
const shapeOf = async (id) =>
|
||||
(await json("__gm.vectorArt.shapes('va-art')")).find((shape) => shape.id === id);
|
||||
const rows = await json('__gm.vectorArt.shapeRows()');
|
||||
const model = await shapeIds();
|
||||
check(
|
||||
'the inspector lists the shapes, the one drawn last on top',
|
||||
Array.isArray(rows) && rows.length === model.length && rows.join() === [...model].reverse().join(),
|
||||
JSON.stringify({ rows, model }),
|
||||
);
|
||||
|
||||
// A row is picked by where it sits: its name also stands in the tab, so text would be ambiguous.
|
||||
const fieldRow = await json("__gm.vectorArt.shapeRowAt('va-field')");
|
||||
await click(fieldRow.x + fieldRow.w / 2, fieldRow.y + fieldRow.h / 2);
|
||||
await settle();
|
||||
check(
|
||||
'clicking a row selects that shape',
|
||||
(await json('__gm.vectorArt.selected()')) === 'va-field',
|
||||
JSON.stringify({ selected: await json('__gm.vectorArt.selected()'), row: fieldRow }),
|
||||
);
|
||||
|
||||
// The control of a row, not an offset from its label: a wider label pushes its control right.
|
||||
const fillField = await json("__gm.inspectorField('Fill')");
|
||||
await click(fillField.x, fillField.y);
|
||||
// The click puts a caret in the hex text rather than selecting it, so the old value goes first.
|
||||
await press('a', { ctrl: true });
|
||||
await typeText('20c0e8');
|
||||
await press('Enter');
|
||||
await settle();
|
||||
const recoloured = await shapeOf('va-field');
|
||||
// Read off the tab as well: a row that only writes the model would show nothing to the eye.
|
||||
const spot = await json('__gm.vectorArt.pointAt(48, 200)');
|
||||
const painted = await json(`__gm.pixelAt(${Math.round(spot.x)}, ${Math.round(spot.y)})`);
|
||||
check(
|
||||
'the Fill row recolours the shape, and the tab shows it',
|
||||
recoloured?.fill === 0x20c0e8 && colourDistance(painted, '#20c0e8') < 14,
|
||||
JSON.stringify({ fill: recoloured?.fill?.toString(16), painted }),
|
||||
);
|
||||
|
||||
const outlineField = await json("__gm.inspectorField('Outline')");
|
||||
await click(outlineField.x, outlineField.y);
|
||||
await settle();
|
||||
const outlined = await shapeOf('va-field');
|
||||
check(
|
||||
'the Outline switch gives the shape a line, and its rows appear with it',
|
||||
Boolean(outlined?.stroke) && outlined.stroke.width === 2 && Boolean(await locate('Line width')),
|
||||
JSON.stringify(outlined?.stroke),
|
||||
);
|
||||
|
||||
// The badge was drawn over the field; "To front" puts the field over the badge instead.
|
||||
const covered = await json('__gm.vectorArt.pointAt(204, 60)');
|
||||
const wasBadge = await json(`__gm.pixelAt(${Math.round(covered.x)}, ${Math.round(covered.y)})`);
|
||||
await clickText('To front');
|
||||
await settle();
|
||||
const nowField = await json(`__gm.pixelAt(${Math.round(covered.x)}, ${Math.round(covered.y)})`);
|
||||
check(
|
||||
'To front draws the shape over the one that covered it',
|
||||
(await shapeIds()).at(-1) === 'va-field' &&
|
||||
colourDistance(wasBadge, BADGE) < 14 &&
|
||||
colourDistance(nowField, '#20c0e8') < 14,
|
||||
JSON.stringify({ order: await shapeIds(), wasBadge, nowField }),
|
||||
);
|
||||
|
||||
await shot('47-shape-rows');
|
||||
|
||||
// Dragging the top row to the bottom of the list is the same move the other way round.
|
||||
const top = await json("__gm.vectorArt.shapeRowAt('va-field')");
|
||||
await drag(
|
||||
top.x + top.w / 2,
|
||||
top.y + top.h / 2,
|
||||
top.x + top.w / 2,
|
||||
top.y + top.h * (await shapeIds()).length,
|
||||
);
|
||||
await settle();
|
||||
check(
|
||||
'dragging a row to the bottom draws that shape first',
|
||||
(await shapeIds())[0] === 'va-field',
|
||||
JSON.stringify(await shapeIds()),
|
||||
);
|
||||
|
||||
const before47e = (await shapeIds()).length;
|
||||
await clickText('Add shape');
|
||||
await settle();
|
||||
const added = await shapeIds();
|
||||
check(
|
||||
'Add shape puts an empty shape on top and selects it',
|
||||
added.length === before47e + 1 && (await json('__gm.vectorArt.selected()')) === added.at(-1),
|
||||
JSON.stringify({ added: added.length, selected: await json('__gm.vectorArt.selected()') }),
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user