From 2218046738e64646d87779de1ffa64736e31a09c Mon Sep 17 00:00:00 2001 From: root Date: Thu, 10 Sep 2026 17:36:27 +0200 Subject: [PATCH] Scene: ausgeschaltete Objekte behalten ihren Auswahlrahmen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ein ausgeschaltetes Objekt zeichnet nichts, verschwand bisher aber auch aus der Auswahl - im Panel angeklickt blieb der Viewport leer, es gab kein Anzeichen, wo es liegt. Der Rahmen kommt jetzt, gedimmt auf 45 %, damit er sich nicht wie eine fehlgeschlagene Zeichnung liest. Ziehen bleibt aus: der Node ist weiterhin nicht interaktiv. Dazu __gm.gizmos() als Testhaken - Gizmos waren vorher gar nicht prüfbar. Es meldet das alpha, das wirklich in den Strich geht, kein daneben abgeleitetes Flag. --- README.md | 2 +- src/editor/debugHook.ts | 2 ++ src/editor/viewport/SceneViewport.ts | 31 ++++++++++++++++--- .../done/show-gizmo-of-disabled-objects.md | 23 ++++++++++++++ todo-notes/show-gizmo-of-disabled-objects.md | 2 -- tools/e2e/10-text-and-enabled.mjs | 28 +++++++++++++++++ 6 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 todo-notes/done/show-gizmo-of-disabled-objects.md delete mode 100644 todo-notes/show-gizmo-of-disabled-objects.md diff --git a/README.md b/README.md index e6df06a2..7be949a3 100644 --- a/README.md +++ b/README.md @@ -604,7 +604,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 # 491 editor checks, screenshots in /tmp/gm-e2e-*.png +node tools/seed-e2e.mjs && node tools/e2e.mjs # 496 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 diff --git a/src/editor/debugHook.ts b/src/editor/debugHook.ts index acca6066..a2a3158f 100644 --- a/src/editor/debugHook.ts +++ b/src/editor/debugHook.ts @@ -176,6 +176,8 @@ export function installDebugHook(app: Application, state: EditorState, editor: E assetRow: (id: string) => assets.rowGlobal(id), assetRowColor: (id: string) => assets.rowColor(id), gameInfo: () => gameView.debugInfo(), + /** Selection outlines in the Scene tab: a switched-off object is invisible but still framed. */ + gizmos: () => viewport.gizmoInfo(), layerOf: (id: string) => viewport.layerOf(id), drawOrder: () => viewport.drawOrder(), objectScreen: (id: string) => viewport.objectScreen(id), diff --git a/src/editor/viewport/SceneViewport.ts b/src/editor/viewport/SceneViewport.ts index 915fbcca..c807838c 100644 --- a/src/editor/viewport/SceneViewport.ts +++ b/src/editor/viewport/SceneViewport.ts @@ -63,6 +63,15 @@ interface Marquee { active: boolean; } +/** One selection outline that was drawn – `alpha` is the value the stroke really got. */ +export interface GizmoInfo { + id: string; + primary: boolean; + alpha: number; +} + +const OFF_GIZMO_ALPHA = 0.45; + export class SceneViewport extends WorldViewport { private readonly grid = new Graphics(); private readonly links = new Graphics(); @@ -74,6 +83,7 @@ export class SceneViewport extends WorldViewport { private dragging: ObjectDrag | null = null; private marquee: Marquee | null = null; private dropPreview: DropPreview | null = null; + private lastGizmos: GizmoInfo[] = []; constructor(state: EditorState) { super(state); @@ -348,6 +358,11 @@ export class SceneViewport extends WorldViewport { this.gizmos.clear(); } + /** The selection outlines currently drawn (test hook). */ + gizmoInfo(): GizmoInfo[] { + return this.lastGizmos; + } + protected override afterItems(items: DrawItem[], view: View): void { this.drawParentLinks(items, view.scale); this.drawGizmos(items, view); @@ -443,24 +458,32 @@ export class SceneViewport extends WorldViewport { /** Selection outlines – only the primary object gets corner handles – and the rubber band. */ private drawGizmos(items: DrawItem[], view: View): void { + this.lastGizmos = []; const s = this.state.selection; if (s?.kind === 'object') { for (const id of s.objectIds) { const item = items.find((i) => i.id === id); - if (!item || !item.enabled) continue; + if (!item) continue; const { w, h } = footprintOf(item.type, item.props); const tl = worldToScreen(view, item.x - w / 2, item.y - h / 2); const sw = w * view.scale; const sh = h * view.scale; - this.gizmos.rect(tl.x - 2, tl.y - 2, sw + 4, sh + 4).stroke({ width: 2, color: Theme.colors.gizmo }); - if (id !== s.objectId) continue; + // A switched-off object draws nothing, so its outline is the only way to see where it + // sits – dimmed, because the empty frame would otherwise read as a drawing that failed. + const alpha = item.enabled ? 1 : OFF_GIZMO_ALPHA; + const primary = id === s.objectId; + this.lastGizmos.push({ id, primary, alpha }); + this.gizmos + .rect(tl.x - 2, tl.y - 2, sw + 4, sh + 4) + .stroke({ width: 2, color: Theme.colors.gizmo, alpha }); + if (!primary) continue; for (const [cx, cy] of [ [tl.x, tl.y], [tl.x + sw, tl.y], [tl.x, tl.y + sh], [tl.x + sw, tl.y + sh], ] as const) { - this.gizmos.rect(cx - 3, cy - 3, 6, 6).fill(Theme.colors.gizmo); + this.gizmos.rect(cx - 3, cy - 3, 6, 6).fill({ color: Theme.colors.gizmo, alpha }); } } } diff --git a/todo-notes/done/show-gizmo-of-disabled-objects.md b/todo-notes/done/show-gizmo-of-disabled-objects.md new file mode 100644 index 00000000..f66a8e43 --- /dev/null +++ b/todo-notes/done/show-gizmo-of-disabled-objects.md @@ -0,0 +1,23 @@ +objects die nicht enabled sind, sind komplett unsichtbar, das passt +aber: wenn ich sie im panel linksunten selektiere, sollte zumindest das "gizmo" (also der selektions rahmen) im scene viewport sichtbar sein + +--- + +Gebaut. Drei Dinge, die weder oben stehen noch aus dem Commit hervorgehen: + +**Der Rahmen ist gedimmt (45 %).** Ein ausgeschaltetes Objekt zeichnet nichts, ein Rahmen um leeren +Raum in voller Stärke liest sich sonst wie eine Zeichnung, die fehlgeschlagen ist. Die Eckgriffe des +primären Objekts kommen mit – sie sind reine Kennzeichnung, es gibt keinen Hit-Test daran, sie +versprechen also keine Geste, die ein ausgeschaltetes Objekt nicht kann. + +**Ziehen bleibt aus.** Der Rahmen sagt nur, wo das Objekt liegt; sein Node ist weiterhin unsichtbar +und `eventMode: 'none'`, weil `onScreen` in `WorldViewport` "aus" und "außerhalb des Sichtfelds" in +einem Ausdruck bündelt. Bewegen geht also nur über den Inspector – oder nach dem Einschalten. + +**Es greift auch bei geerbtem Aus.** `DrawItem.enabled` ist `enabledAlongChain`, also das Objekt +*und* alle seine Eltern. Ein Kind, das nur deshalb unsichtbar ist, weil sein Elternteil aus ist, +bekommt denselben gedimmten Rahmen. + +Nebenbei entstanden: `__gm.gizmos()` – vorher waren Gizmos überhaupt nicht prüfbar. Es meldet das +`alpha`, das wirklich in den Strich geht, nicht ein daneben abgeleitetes Flag; sonst hätte der Test +grün bleiben können, während gar nichts gedimmt wird (als Gegenprobe nachgestellt). diff --git a/todo-notes/show-gizmo-of-disabled-objects.md b/todo-notes/show-gizmo-of-disabled-objects.md deleted file mode 100644 index 405248ff..00000000 --- a/todo-notes/show-gizmo-of-disabled-objects.md +++ /dev/null @@ -1,2 +0,0 @@ -objects die nicht enabled sind, sind komplett unsichtbar, das passt -aber: wenn ich sie im panel linksunten selektiere, sollte zumindest das "gizmo" (also der selektions rahmen) im scene viewport sichtbar sein diff --git a/tools/e2e/10-text-and-enabled.mjs b/tools/e2e/10-text-and-enabled.mjs index 615f6ee6..8e7db2dc 100644 --- a/tools/e2e/10-text-and-enabled.mjs +++ b/tools/e2e/10-text-and-enabled.mjs @@ -40,6 +40,9 @@ export async function textAndEnabled(t) { const enabledRow = await evaluate(`__gm.locate('Enabled')`); check('the inspector shows the Enabled switch for a scene object', Boolean(enabledRow)); const visibleBefore = (await json('__gm.viewportInfo()')).visibleItems; + const gizmoOf = async (id) => (await json('__gm.gizmos()')).find((g) => g.id === id) ?? null; + const gizmoOn = await gizmoOf('otext'); + check('a selected object is framed at full strength', gizmoOn?.alpha === 1, JSON.stringify(gizmoOn)); // The checkbox sits right of the 64 px label column; locate() gives the label's text centre. await click(enabledRow.x + 55, enabledRow.y); await sleep(100); @@ -62,6 +65,31 @@ export async function textAndEnabled(t) { check('it stays selectable in the hierarchy, so its switch stays reachable', await has('Enabled')); await shot('30-disabled'); + // 17a-2. It draws nothing, so its outline is the only thing left that says where it sits – which + // is the whole point of being able to pick it in the hierarchy. Selecting another object + // and coming back proves the outline follows the panel, not a leftover from before. + const gizmoOff = await gizmoOf('otext'); + check( + 'a switched-off object is still framed, dimmed', + gizmoOff?.primary === true && gizmoOff.alpha > 0 && gizmoOff.alpha < 1, + JSON.stringify(gizmoOff), + ); + const otherRow = await evaluate(`__gm.hierarchyRow('o3')`); + await click(otherRow.x, otherRow.y); + await sleep(100); + check('selecting something else drops the frame', (await gizmoOf('otext')) === null); + const offRow = await evaluate(`__gm.hierarchyRow('otext')`); + await click(offRow.x, offRow.y); + await sleep(100); + const gizmoBack = await gizmoOf('otext'); + check( + 'picking the switched-off object in the hierarchy frames it again', + gizmoBack?.alpha > 0 && gizmoBack.alpha < 1, + JSON.stringify(gizmoBack), + ); + check('and it is still not drawn', !(await json('__gm.drawOrder()')).includes('otext')); + await shot('30b-disabled-gizmo'); + // 17b. In the running game a switched-off object does not exist: it is not rendered, and // switching it back on brings it back. await play();