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>
52 lines
1.6 KiB
TypeScript
52 lines
1.6 KiB
TypeScript
import type { FederatedPointerEvent } from 'pixi.js';
|
|
|
|
/** Longest gap between two presses that still reads as one double-click. */
|
|
const DOUBLE_MS = 400;
|
|
/** How far the pointer may travel between them. */
|
|
const SLOP = 6;
|
|
|
|
/** When and where a press happened. */
|
|
export interface Press {
|
|
time: number;
|
|
x: number;
|
|
y: number;
|
|
}
|
|
|
|
export const pressOf = (e: FederatedPointerEvent): Press => ({
|
|
time: e.timeStamp,
|
|
x: e.global.x,
|
|
y: e.global.y,
|
|
});
|
|
|
|
/** Whether `e` comes soon enough and close enough after `first` to make a double-click with it. */
|
|
export function followsPress(first: Press, e: FederatedPointerEvent): boolean {
|
|
const gap = e.timeStamp - first.time;
|
|
return gap >= 0 && gap <= DOUBLE_MS && Math.hypot(e.global.x - first.x, e.global.y - first.y) <= SLOP;
|
|
}
|
|
|
|
interface LastPress extends Press {
|
|
key: unknown;
|
|
button: number;
|
|
count: number;
|
|
}
|
|
|
|
let last: LastPress = { key: null, button: -1, time: 0, x: 0, y: 0, count: 0 };
|
|
|
|
/**
|
|
* Press count in a row (2 on a double-click), since Pixi leaves `detail` at 0 on `pointerdown`. `key`
|
|
* must survive a rebuild of its widget: the first click's selection can recreate the list rows.
|
|
*/
|
|
export function pressCount(e: FederatedPointerEvent, key: unknown): number {
|
|
const repeat = key === last.key && e.button === last.button && followsPress(last, e);
|
|
const count = repeat ? last.count + 1 : 1;
|
|
last = {
|
|
...pressOf(e),
|
|
key,
|
|
button: e.button,
|
|
// A finished double-click ends the run: folding a row open and shut again is two
|
|
// double-clicks, and the second must count as one rather than as clicks three and four.
|
|
count: count === 2 ? 0 : count,
|
|
};
|
|
return count;
|
|
}
|