Files
feuerwerk/src/viewport/labelRows.ts
T
adminandClaude Opus 5.5 50bddf68d4
game-maker / gate (push) Successful in 2m57s
game-maker / suite (push) Failing after 8m37s
Keep scene view names readable on plates and in rows of their own
Symbol names in the scene view were plain grey text. On a background
of a similar colour they could not be read, and names of symbols close
together were drawn over each other.

- Each name stands on a rounded, nearly black plate at 70 % opacity,
  3 px padding left and right, 1 px top and bottom.
- The text is light (viewportLabel) instead of muted grey.
- The plate of a selected object gets an accent border.
- New rule stackLabels in src/viewport/labelRows.ts. A name that
  overlaps another name or an icon moves one row down, then one up,
  then two down and so on, until a row is free.
- Names are placed top to bottom, so the upper one keeps its row.
  A name only moves vertically and stays on the side of its icon.
- Names outside the view still claim their row. Otherwise the visible
  names would jump while panning.
- New hook __gm.sceneLabels() for the e2e check.

The frame names in the texture editor already had a plate, so they
are unchanged.

Not covered by tests: the accent border on selection. Readability on a
light background was only judged from a screenshot on a dark one.

Tests:
- 5 unit tests in tests/viewport/label-rows.test.ts: free names stay,
  the lower of two colliding names moves, the stacking order, names
  avoid icons, 30 crowded names never overlap.
- e2e 5c-ii: 8 symbols on one spot give 8 plates, no overlap with each
  other or with icons, each name on the side of its icon.
- Counter-checks: without the overlap test in the rule 4 of 5 unit
  tests fail. With the scene view ignoring the rows exactly 5c-ii
  fails.
- ./check.sh: 161 files, 2369 tests, CHECK EXIT 0.
- e2e 1-5: ALL PASSED, 140 checks. The e2e sum is now 1382 (computed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 08:05:39 +02:00

51 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Where the scene view's symbol names stand. Each wants the row beside its icon; a name that would
* cover one already placed, or another icon, moves a row down, up, two down and so on, until it
* finds a free row. Names are placed top to bottom, so of two that collide the upper one keeps its
* row, and a name only ever moves up or down: it stays on its icon's side.
*/
export interface LabelBox {
x: number;
y: number;
w: number;
h: number;
}
export interface WantedLabel extends LabelBox {
id: string;
}
/** Tries per name before it gives up and keeps its own row – far more rows than a scene fills. */
const MAX_TRIES = 4096;
function overlaps(a: LabelBox, b: LabelBox): boolean {
// A hair of slack: boxes that just touch are fine, and rounding must not push one away.
const e = 1e-6;
return a.x < b.x + b.w - e && b.x < a.x + a.w - e && a.y < b.y + b.h - e && b.y < a.y + a.h - e;
}
/** The top of every label, `step` apart from its wanted row: 0, +1, −1, +2, −2 … rows. */
export function stackLabels(
labels: readonly WantedLabel[],
obstacles: readonly LabelBox[],
step: number,
): Map<string, number> {
const order = labels.map((label, k) => ({ label, k }));
order.sort((a, b) => a.label.y - b.label.y || a.label.x - b.label.x || a.k - b.k);
const placed: LabelBox[] = [...obstacles];
const tops = new Map<string, number>();
for (const { label } of order) {
let top = label.y;
for (let k = 0; k < MAX_TRIES; k++) {
const rows = k % 2 === 0 ? -k / 2 : (k + 1) / 2;
const box = { ...label, y: label.y + rows * step };
if (placed.some((other) => overlaps(box, other))) continue;
top = box.y;
break;
}
tops.set(label.id, top);
placed.push({ x: label.x, y: top, w: label.w, h: label.h });
}
return tops;
}