A sprite's texture and shader were only reachable by hunting for them
in the asset panel. Every place that shows such a reference now leads
straight to it.
- Object menu (hierarchy and scene view): "Open texture" and "Open
shader" sit right below "Open asset" when the sprite has one. The
object's own values count, so an overridden shader opens the
override.
- Asset context menu: the same entries at the top.
- Inspector: every asset field with a value shows its label as a blue,
underlined link. One click selects the asset, a double click opens
it. An override stays gold and gets the underline too.
- The first click rebuilds the inspector, so the second one lands on a
different row. The inspector catches that second click before any
row sees it.
- New pure rule `references.ts`: the asset behind a reference field. A
frame leads to its sheet. Kinds without a tab (palette, font, sound)
get no menu entry.
- `followsPress` in `clicks.ts` keeps the double-click window and
tolerance in one place.
- `activateAsset` in `actions.ts`: opening means the same everywhere
as a double click in the asset panel, so a scene also becomes the
active one. The asset panel uses it too.
Tests:
- ./check.sh: 150 files, 2226 tests, CHECK EXIT 0 (before 148/2214).
- 12 new unit tests: 6 for the reference rule, 4 for the link in
createFieldRow, 2 for followsPress.
- 5 new e2e checks in 28l-ii: both menus, the link's look, click
selects, double click opens.
- e2e 1-16,26,28,35,39,40 on the final build: ALL PASSED, 732 checks.
- Counter-probes: no "Open ..." entries, no underline and a second
click never recognised each fail exactly at their checks.
- Check total 1341 (CI counted 1336 at fd6af84), computed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
68 lines
2.5 KiB
TypeScript
68 lines
2.5 KiB
TypeScript
import type { FederatedPointerEvent } from 'pixi.js';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { followsPress, pressCount, pressOf } from '../../src/ui/clicks';
|
|
|
|
interface Press {
|
|
time: number;
|
|
x?: number;
|
|
y?: number;
|
|
button?: number;
|
|
}
|
|
|
|
const press = (p: Press): FederatedPointerEvent =>
|
|
({
|
|
timeStamp: p.time,
|
|
button: p.button ?? 0,
|
|
global: { x: p.x ?? 100, y: p.y ?? 100 },
|
|
}) as unknown as FederatedPointerEvent;
|
|
|
|
/** Counts a run of presses on one key. Each test uses its own key, so no run reaches the next. */
|
|
const counts = (key: unknown, presses: Press[]): number[] => presses.map((p) => pressCount(press(p), key));
|
|
|
|
describe('pressCount', () => {
|
|
it('counts a second press on the same target as a double-click', () => {
|
|
expect(counts('row-a', [{ time: 0 }, { time: 120 }])).toEqual([1, 2]);
|
|
});
|
|
|
|
it('starts over when the presses are too far apart in time', () => {
|
|
expect(counts('row-b', [{ time: 0 }, { time: 900 }])).toEqual([1, 1]);
|
|
});
|
|
|
|
it('starts over when the pointer moved to another row', () => {
|
|
expect(counts('row-c', [{ time: 0 }, { time: 100, y: 140 }])).toEqual([1, 1]);
|
|
});
|
|
|
|
it('tolerates the wobble of a hand holding the mouse still', () => {
|
|
expect(counts('row-d', [{ time: 0 }, { time: 100, x: 103, y: 102 }])).toEqual([1, 2]);
|
|
});
|
|
|
|
it('does not join presses on different targets', () => {
|
|
const first = pressCount(press({ time: 0 }), 'row-e');
|
|
const second = pressCount(press({ time: 100 }), 'row-f');
|
|
expect([first, second]).toEqual([1, 1]);
|
|
});
|
|
|
|
it('does not join presses of different buttons', () => {
|
|
expect(counts('row-g', [{ time: 0 }, { time: 100, button: 2 }])).toEqual([1, 1]);
|
|
});
|
|
|
|
it('starts a new run after a double-click, so a second pair counts again', () => {
|
|
// Folding a row open and shut again is two double-clicks in a row, well inside the window.
|
|
expect(counts('row-h', [{ time: 0 }, { time: 100 }, { time: 200 }, { time: 300 }])).toEqual([1, 2, 1, 2]);
|
|
});
|
|
});
|
|
|
|
describe('followsPress', () => {
|
|
// The second press of a link's double-click lands on a rebuilt panel, so no key is compared (#221).
|
|
const first = pressOf(press({ time: 0 }));
|
|
|
|
it('joins a press soon after at the same spot', () => {
|
|
expect(followsPress(first, press({ time: 300, x: 104 }))).toBe(true);
|
|
});
|
|
|
|
it('does not join a late press or one elsewhere', () => {
|
|
expect(followsPress(first, press({ time: 500 }))).toBe(false);
|
|
expect(followsPress(first, press({ time: 100, y: 120 }))).toBe(false);
|
|
});
|
|
});
|