Files
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

58 lines
2.2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { type LabelBox, stackLabels, type WantedLabel } from '../../src/viewport/labelRows';
const H = 12;
const STEP = 13;
function label(id: string, x: number, y: number, w = 40): WantedLabel {
return { id, x, y, w, h: H };
}
function overlap(a: LabelBox, b: LabelBox): boolean {
return a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;
}
describe('stackLabels', () => {
it('leaves names that do not touch on their own row', () => {
const tops = stackLabels([label('a', 0, 0), label('b', 100, 0), label('c', 0, 20)], [], STEP);
expect([...tops]).toEqual([
['a', 0],
['b', 0],
['c', 20],
]);
});
it('moves the lower of two colliding names a row down', () => {
const tops = stackLabels([label('low', 10, 4), label('high', 0, 0)], [], STEP);
expect(tops.get('high')).toBe(0);
expect(tops.get('low')).toBe(4 + STEP);
});
it('stacks names on one spot row by row: down, up, two down, two up', () => {
const names = ['a', 'b', 'c', 'd', 'e'].map((id) => label(id, 0, 0));
const tops = stackLabels(names, [], STEP);
expect(names.map((n) => tops.get(n.id))).toEqual([0, STEP, -STEP, 2 * STEP, -2 * STEP]);
});
it('keeps names off icons they would cover', () => {
// Covers the row above, the name's own and the next one down: two rows down is the first free.
const icon = { x: 20, y: -6, w: 18, h: 30 };
const tops = stackLabels([label('a', 0, 0)], [icon], STEP);
const box = { ...label('a', 0, 0), y: tops.get('a')! };
expect(overlap(box, icon)).toBe(false);
expect(tops.get('a')).toBe(2 * STEP);
});
it('leaves no two names overlapping in a crowd', () => {
const names = Array.from({ length: 30 }, (_, k) =>
label(`n${k}`, (k * 7) % 50, (k * 5) % 30, 30 + (k % 4) * 10),
);
const tops = stackLabels(names, [], STEP);
const boxes = names.map((n) => ({ ...n, y: tops.get(n.id)! }));
for (let i = 0; i < boxes.length; i++)
for (let j = i + 1; j < boxes.length; j++) expect(overlap(boxes[i]!, boxes[j]!)).toBe(false);
// Only up or down: every name stays where its icon wants it across.
for (const box of boxes) expect(box.x).toBe(names.find((n) => n.id === box.id)!.x);
});
});