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>
58 lines
2.2 KiB
TypeScript
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);
|
|
});
|
|
});
|