Drop the collider and delete-node buttons from the inspector
Adding a collider now lives only in the Scene objects context menu, deleting a node on the Delete key and the node context menu. The e2e checks that pinned the inspector button now drive the hierarchy menu instead, including that its entry is disabled on template content and on a placed container without raising a notice. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0dcb3b4432
commit
5f8a308d62
7 files changed
+48
-36
No files matched your search
@@ -351,8 +351,8 @@ inspector below X/Y/Z, undoable, with ↺ back to 0. It belongs to the placed ob
|
||||
|
||||
A **collider** is an asset (`shape` box/circle, `width`/`height` or `radius`) placed like any
|
||||
other object – typically as a child of the sprite it belongs to, so its position is the offset
|
||||
from that *body* and it moves with it. Right-click an object in **Scene objects** (or press
|
||||
**+ Add collider** in its inspector) to get one with one click: a box collider sized like the
|
||||
from that *body* and it moves with it. Right-click an object in **Scene objects** → *Add
|
||||
collider* to get one with one click: a box collider sized like the
|
||||
sprite, parented at its centre. Colliders can be reused across objects, overridden per instance
|
||||
and stacked (hitbox + hurtbox on one sprite; colliders of the same body never collide with each
|
||||
other). They are editor symbols: green outlines in edit mode, invisible in play mode.
|
||||
@@ -644,7 +644,7 @@ npm run check # typecheck + lint + format:check + tests, what CI would ru
|
||||
# code is lost (tail's is reported), so a red run reads as green
|
||||
npm run build # writes dist/ and regenerates index.html
|
||||
export GM_REMOTE=user@host # optional: run the browser elsewhere; see below
|
||||
node tools/seed-e2e.mjs && node tools/e2e.mjs # 535 editor checks, screenshots in /tmp/gm-e2e-*.png
|
||||
node tools/seed-e2e.mjs && node tools/e2e.mjs # 537 editor checks, screenshots in /tmp/gm-e2e-*.png
|
||||
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 – a subset while working on one step
|
||||
./e2e.sh start [GM_STEPS] # the same run detached, see below
|
||||
./e2e.sh status # live progress, or the last run's result
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { RemoveNodeCommand } from '../../core/commands/graphCommands';
|
||||
import {
|
||||
callProblem,
|
||||
declaresVariablePorts,
|
||||
@@ -13,9 +12,9 @@ import type { Asset } from '../../core/model/Asset';
|
||||
import { behaviorLookup } from '../../core/model/behaviorPrograms';
|
||||
import { findAssetOf, type Project } from '../../core/model/Project';
|
||||
import type { PropertyDef } from '../../core/properties/PropertySchema';
|
||||
import { Button, Label } from '../../ui';
|
||||
import { Label } from '../../ui';
|
||||
import { createFieldRow, type FieldHandlers } from './fieldFactory';
|
||||
import { addGap, addNote, addTitle, type InspectorHost } from './host';
|
||||
import { addNote, addTitle, type InspectorHost } from './host';
|
||||
import { buildPortSection, buildVariablePortSection } from './portSection';
|
||||
import { listOpener, previewingHandlers } from './previewHandlers';
|
||||
import { currentNode, nodeParamValue } from './valueSpecs';
|
||||
@@ -81,14 +80,6 @@ export function buildNodeSection(host: InspectorHost, assetId: string, nodeId: s
|
||||
host.fields.set(`input:${port.key}`, row);
|
||||
host.body.add(row.row);
|
||||
}
|
||||
|
||||
addGap(host);
|
||||
host.body.add(
|
||||
new Button('Delete node', {
|
||||
variant: 'danger',
|
||||
onClick: () => host.state.execute(new RemoveNodeCommand(assetId, nodeId)),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/** Pushes the node's current params into the rows. False when the node is gone (rebuild). */
|
||||
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
resolveSceneObjectProps,
|
||||
} from '../../core/properties/resolve';
|
||||
import { Button, TextField } from '../../ui';
|
||||
import { addCollider, canAddCollider, createContainer, removeObjects } from '../actions';
|
||||
import { createContainer, removeObjects } from '../actions';
|
||||
import { aspectActions, sizeCommand } from './aspectAction';
|
||||
import { defSignature } from './assetSection';
|
||||
import { createFieldRow, type FieldRow, labelledRow, type RowActions } from './fieldFactory';
|
||||
@@ -230,17 +230,6 @@ export function buildObjectSection(host: InspectorHost, holderId: string, object
|
||||
markObjectRows(host, object, asset, target);
|
||||
|
||||
if (asset.type === 'container') contentSection(host, holderId, object, asset.objects.length);
|
||||
|
||||
const stored = findObjectHolder(project, holderId);
|
||||
if (stored && canAddCollider(project, stored, object)) {
|
||||
addGap(host);
|
||||
host.body.add(
|
||||
new Button('+ Add collider', {
|
||||
variant: 'ghost',
|
||||
onClick: () => addCollider(host.state, holderId, objectId),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
- Scene: Add collider button aus insepctor entfernen
|
||||
- Graph: Delete node button aus inspector entfernen
|
||||
|
||||
Umgesetzt: beide Aktionen bleiben erreichbar, nur nicht mehr im Inspector.
|
||||
- Collider: Rechtsklick auf das Objekt in **Scene objects** → *Add collider*. Das ist jetzt der
|
||||
einzige Weg; seine Sperre (Template-Inhalt, platzierter Container) prüft e2e 09 über das Menü.
|
||||
- Node: `Delete` oder Rechtsklick auf den Node → *Delete node*.
|
||||
@@ -1,2 +0,0 @@
|
||||
- Scene: Add collider button aus insepctor entfernen
|
||||
- Graph: Delete node button aus inspector entfernen
|
||||
@@ -233,6 +233,8 @@ export async function graph(t) {
|
||||
|
||||
// 13e. Delete removes the selected node together with its edge; undo brings both back.
|
||||
await select({ kind: 'node', assetId: 'mover', nodeId: newNode.id });
|
||||
await sleep(200);
|
||||
check('the node inspector has no delete button of its own', !(await t.has('Delete node')));
|
||||
await press('Delete');
|
||||
await sleep(150);
|
||||
const deleted = await graphCounts();
|
||||
|
||||
@@ -296,12 +296,33 @@ export async function containers(t) {
|
||||
|
||||
// 16i. Structure is the template's business: resolved content cannot be removed, re-parented,
|
||||
// given a collider or turned into a container of its own.
|
||||
await selectObject(redA);
|
||||
check('the inspector offers no collider for resolved content', !(await has('+ Add collider')));
|
||||
await selectObject(instanceA.id);
|
||||
// The hierarchy menu is the only way to a collider, and a disabled entry must not even reach
|
||||
// the command: the command refuses too, but with a notice.
|
||||
const colliderFromMenu = async (id) => {
|
||||
await selectObject(id);
|
||||
const row = await json(`__gm.hierarchyRow(${JSON.stringify(id)})`);
|
||||
const before = (await objects()).length;
|
||||
const noticeNow = await notice();
|
||||
await click(row.x, row.y, 'right');
|
||||
await sleep(150);
|
||||
await clickText('Add collider');
|
||||
await sleep(200);
|
||||
await press('Escape');
|
||||
await sleep(100);
|
||||
return { added: (await objects()).length - before, notice: await noticeNow() };
|
||||
};
|
||||
const refused = JSON.stringify({ added: 0, notice: '' });
|
||||
const fromContent = await colliderFromMenu(redA);
|
||||
check(
|
||||
'nor for the placed container itself, which the command refuses as the hierarchy menu does',
|
||||
!(await has('+ Add collider')),
|
||||
'the menu offers no collider for resolved content',
|
||||
JSON.stringify(fromContent) === refused,
|
||||
JSON.stringify(fromContent),
|
||||
);
|
||||
const fromInstance = await colliderFromMenu(instanceA.id);
|
||||
check(
|
||||
'nor for the placed container itself',
|
||||
JSON.stringify(fromInstance) === refused,
|
||||
JSON.stringify(fromInstance),
|
||||
);
|
||||
const plainId = await json(`(() => {
|
||||
const p = __gm.state.project;
|
||||
@@ -311,11 +332,15 @@ export async function containers(t) {
|
||||
return sprite ? sprite.id : null;
|
||||
})()`);
|
||||
await selectObject(plainId);
|
||||
check('the inspector has no collider button of its own', !(await has('+ Add collider')));
|
||||
const fromPlain = await colliderFromMenu(plainId);
|
||||
check(
|
||||
'while a plain sprite still gets it',
|
||||
Boolean(plainId) && (await has('+ Add collider')),
|
||||
JSON.stringify({ plainId }),
|
||||
'while a plain sprite still gets one from the menu',
|
||||
fromPlain.added === 1,
|
||||
JSON.stringify(fromPlain),
|
||||
);
|
||||
await undo();
|
||||
await sleep(200);
|
||||
await selectObject(redA);
|
||||
let noticeAfter = await notice();
|
||||
await press('Delete');
|
||||
|
||||
Reference in new issue
Block a user