@grida/hud 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +499 -36
- package/dist/core/index.d.mts +181 -0
- package/dist/core/index.d.ts +181 -0
- package/dist/core/index.js +301 -0
- package/dist/core/index.mjs +291 -0
- package/dist/cursors/index.d.mts +1 -1
- package/dist/cursors/index.d.ts +1 -1
- package/dist/cursors/index.js +1 -1
- package/dist/cursors/index.mjs +1 -1
- package/dist/index-BrfEdWbQ.d.ts +3140 -0
- package/dist/index-Cmbe2X5b.d.mts +3140 -0
- package/dist/index.d.mts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +55 -2
- package/dist/index.mjs +3 -3
- package/dist/overlay-CVV4s3IL.d.ts +241 -0
- package/dist/overlay-dsG32baA.d.mts +241 -0
- package/dist/primitives/bedrock.d.mts +47 -0
- package/dist/primitives/bedrock.d.ts +47 -0
- package/dist/primitives/bedrock.js +71 -0
- package/dist/primitives/bedrock.mjs +65 -0
- package/dist/react.d.mts +3 -2
- package/dist/react.d.ts +2 -1
- package/dist/react.js +1 -1
- package/dist/react.mjs +1 -1
- package/dist/surface-BHQVvRFC.js +7356 -0
- package/dist/surface-NHSzUR8r.mjs +6902 -0
- package/dist/types-3wwFisZs.d.mts +296 -0
- package/dist/types-3wwFisZs.d.ts +296 -0
- package/package.json +12 -2
- package/dist/index-Cp0X4SV7.d.ts +0 -947
- package/dist/index-DhGdcuQz.d.mts +0 -947
- package/dist/surface-BvMmXoEl.mjs +0 -2471
- package/dist/surface-ofSNTJ8H.js +0 -2607
- /package/dist/{cursor-BFGUuD2M.d.mts → cursor-CxS8EMvm.d.mts} +0 -0
- /package/dist/{cursor-CIYvFshz.d.ts → cursor-CxS8EMvm.d.ts} +0 -0
- /package/dist/{cursor-BieMVb71.mjs → cursor-DW-uAPVE.mjs} +0 -0
- /package/dist/{cursor-DsP9qtN2.js → cursor-FGiJBdU-.js} +0 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { i as HitShape, t as HUDObject } from "../overlay-dsG32baA.mjs";
|
|
2
|
+
import cmath from "@grida/cmath";
|
|
3
|
+
|
|
4
|
+
//#region core/event.d.ts
|
|
5
|
+
/** Modifier-key snapshot at the moment an event was produced. */
|
|
6
|
+
interface Modifiers {
|
|
7
|
+
readonly shift: boolean;
|
|
8
|
+
readonly alt: boolean;
|
|
9
|
+
readonly meta: boolean;
|
|
10
|
+
readonly ctrl: boolean;
|
|
11
|
+
}
|
|
12
|
+
declare const NO_MODS: Modifiers;
|
|
13
|
+
type PointerButton = "primary" | "secondary" | "middle";
|
|
14
|
+
/**
|
|
15
|
+
* Synthesized input event consumed by HUD bedrock.
|
|
16
|
+
*
|
|
17
|
+
* All coordinates are **screen-space CSS pixels relative to the
|
|
18
|
+
* canvas viewport**. The consumer holds the camera and converts to
|
|
19
|
+
* document-space via {@link Transform}.
|
|
20
|
+
*/
|
|
21
|
+
type HUDEvent = {
|
|
22
|
+
readonly kind: "pointer_move";
|
|
23
|
+
readonly x: number;
|
|
24
|
+
readonly y: number;
|
|
25
|
+
readonly mods: Modifiers;
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: "pointer_down";
|
|
28
|
+
readonly x: number;
|
|
29
|
+
readonly y: number;
|
|
30
|
+
readonly button: PointerButton;
|
|
31
|
+
readonly mods: Modifiers;
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: "pointer_up";
|
|
34
|
+
readonly x: number;
|
|
35
|
+
readonly y: number;
|
|
36
|
+
readonly button: PointerButton;
|
|
37
|
+
readonly mods: Modifiers;
|
|
38
|
+
} | {
|
|
39
|
+
readonly kind: "modifiers";
|
|
40
|
+
readonly mods: Modifiers;
|
|
41
|
+
} | {
|
|
42
|
+
readonly kind: "wheel";
|
|
43
|
+
readonly x: number;
|
|
44
|
+
readonly y: number;
|
|
45
|
+
readonly dx: number;
|
|
46
|
+
readonly dy: number;
|
|
47
|
+
readonly mods: Modifiers;
|
|
48
|
+
} | {
|
|
49
|
+
readonly kind: "key";
|
|
50
|
+
readonly phase: "down" | "up";
|
|
51
|
+
readonly code: string;
|
|
52
|
+
readonly mods: Modifiers;
|
|
53
|
+
} | {
|
|
54
|
+
readonly kind: "blur";
|
|
55
|
+
};
|
|
56
|
+
/** Convenience re-export. */
|
|
57
|
+
type Vector2 = cmath.Vector2;
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region core/click-tracker.d.ts
|
|
60
|
+
interface ClickTrackerOptions {
|
|
61
|
+
/** Max gap between clicks in ms. Default 250. */
|
|
62
|
+
readonly windowMs?: number;
|
|
63
|
+
/** Max distance between clicks in screen px. Default 5. */
|
|
64
|
+
readonly distancePx?: number;
|
|
65
|
+
}
|
|
66
|
+
declare class ClickTracker {
|
|
67
|
+
private window_ms;
|
|
68
|
+
private distance_px;
|
|
69
|
+
private last_time;
|
|
70
|
+
private last_x;
|
|
71
|
+
private last_y;
|
|
72
|
+
private count;
|
|
73
|
+
constructor(opts?: ClickTrackerOptions);
|
|
74
|
+
/**
|
|
75
|
+
* Register a click at `(x, y)` and return the current consecutive-
|
|
76
|
+
* click count (1 = single, 2 = double, etc.). `now` is in ms.
|
|
77
|
+
*/
|
|
78
|
+
register(x: number, y: number, now?: number): number;
|
|
79
|
+
reset(): void;
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region core/transform.d.ts
|
|
83
|
+
/**
|
|
84
|
+
* Axis-aligned 2×3 affine.
|
|
85
|
+
*
|
|
86
|
+
* Stored as a `cmath.Transform`:
|
|
87
|
+
* `[[sx, 0, tx], [0, sy, ty]]`
|
|
88
|
+
*
|
|
89
|
+
* Off-diagonal components are ignored by these helpers; HUD's camera
|
|
90
|
+
* is scale + translate only.
|
|
91
|
+
*/
|
|
92
|
+
type Transform = cmath.Transform;
|
|
93
|
+
declare const IDENTITY: Transform;
|
|
94
|
+
/** Project a screen-space point into document-space. */
|
|
95
|
+
declare function screenToDoc(t: Transform, x: number, y: number): cmath.Vector2;
|
|
96
|
+
/** Project a document-space point into screen-space. */
|
|
97
|
+
declare function docToScreen(t: Transform, x: number, y: number): cmath.Vector2;
|
|
98
|
+
/** Current uniform zoom. Reads `sx`. */
|
|
99
|
+
declare function zoomOf(t: Transform): number;
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region core/registry.d.ts
|
|
102
|
+
/**
|
|
103
|
+
* Thrown by `register` when an entity with the same `name` is already
|
|
104
|
+
* registered. Consumers should `unregister` the existing one first.
|
|
105
|
+
*/
|
|
106
|
+
declare class RegistrationError extends Error {
|
|
107
|
+
constructor(message: string);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Generic name-keyed registry.
|
|
111
|
+
*
|
|
112
|
+
* @typeParam K — key type, must extend `string`.
|
|
113
|
+
* @typeParam T — entity type. Must expose `readonly name: K` and an
|
|
114
|
+
* optional `detach()` method.
|
|
115
|
+
*/
|
|
116
|
+
declare class NamedRegistry<K extends string, T extends {
|
|
117
|
+
readonly name: K;
|
|
118
|
+
detach?(): void;
|
|
119
|
+
}> {
|
|
120
|
+
private label;
|
|
121
|
+
private byName;
|
|
122
|
+
/** Explicit insertion-ordered array for cheap reverse-walk on clear. */
|
|
123
|
+
private order;
|
|
124
|
+
constructor(label: string);
|
|
125
|
+
register(e: T): void;
|
|
126
|
+
unregister(e: T): void;
|
|
127
|
+
has(name: K): boolean;
|
|
128
|
+
get(name: K): T | undefined;
|
|
129
|
+
entries(): IterableIterator<T>;
|
|
130
|
+
size(): number;
|
|
131
|
+
clear(): void;
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region core/hit-registry.d.ts
|
|
135
|
+
/**
|
|
136
|
+
* Generic hit registry. One per frame (or persistent — bedrock is
|
|
137
|
+
* agnostic). Add objects with `add`, clear with `clear`, query with
|
|
138
|
+
* `queryPoint` / `queryAll`.
|
|
139
|
+
*/
|
|
140
|
+
declare class HitRegistry<I = unknown> {
|
|
141
|
+
private items;
|
|
142
|
+
/**
|
|
143
|
+
* Add an object. Objects without `hit` are accepted (they may carry
|
|
144
|
+
* paint information the consumer still wants in the registry for
|
|
145
|
+
* uniform iteration) but are filtered from point queries.
|
|
146
|
+
*/
|
|
147
|
+
add(obj: HUDObject<I>): void;
|
|
148
|
+
/** Drop all entries. */
|
|
149
|
+
clear(): void;
|
|
150
|
+
/** Total number of stored objects (paint-only + hit-testable). */
|
|
151
|
+
size(): number;
|
|
152
|
+
/** Iterate every stored object in insertion order. */
|
|
153
|
+
entries(): IterableIterator<HUDObject<I>>;
|
|
154
|
+
/**
|
|
155
|
+
* Return the hit-testable object whose hit shape contains the
|
|
156
|
+
* given screen-space point with the lowest `priority` value
|
|
157
|
+
* (lower wins). Paint-only objects (no `hit`) are skipped.
|
|
158
|
+
*/
|
|
159
|
+
queryPoint(point_screen: cmath.Vector2, transform: Transform): HUDObject<I> | null;
|
|
160
|
+
/**
|
|
161
|
+
* Return all hit-testable objects whose hit shape contains the point,
|
|
162
|
+
* winner first. Ordering is identical to `queryPoint`'s arbitration:
|
|
163
|
+
* priority ascending (lower wins), and on EQUAL priority the
|
|
164
|
+
* later-added object comes first ("later push wins on tie"). This makes
|
|
165
|
+
* `queryAll(p)[0]` always equal `queryPoint(p)` — the two query paths
|
|
166
|
+
* never disagree on the winner.
|
|
167
|
+
*/
|
|
168
|
+
queryAll(point_screen: cmath.Vector2, transform: Transform): HUDObject<I>[];
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Test whether a screen-space point lies inside a `HitShape`.
|
|
172
|
+
*
|
|
173
|
+
* The camera `transform` is needed for doc-anchored shapes
|
|
174
|
+
* (`screen_rect_at_doc`, `screen_circle_at_doc`); pre-projected
|
|
175
|
+
* shapes (`screen_aabb`, `screen_polygon`) ignore it. `screen_obb`
|
|
176
|
+
* applies its own `inverse_transform`, which maps screen → shadow
|
|
177
|
+
* (independent of the camera).
|
|
178
|
+
*/
|
|
179
|
+
declare function shapeContains(shape: HitShape, point_screen: cmath.Vector2, transform: Transform): boolean;
|
|
180
|
+
//#endregion
|
|
181
|
+
export { ClickTracker, type ClickTrackerOptions, type HUDEvent, HitRegistry, IDENTITY, type Modifiers, NO_MODS, NamedRegistry, type PointerButton, RegistrationError, type Transform, type Vector2, docToScreen, screenToDoc, shapeContains, zoomOf };
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { i as HitShape, t as HUDObject } from "../overlay-CVV4s3IL.js";
|
|
2
|
+
import cmath from "@grida/cmath";
|
|
3
|
+
|
|
4
|
+
//#region core/event.d.ts
|
|
5
|
+
/** Modifier-key snapshot at the moment an event was produced. */
|
|
6
|
+
interface Modifiers {
|
|
7
|
+
readonly shift: boolean;
|
|
8
|
+
readonly alt: boolean;
|
|
9
|
+
readonly meta: boolean;
|
|
10
|
+
readonly ctrl: boolean;
|
|
11
|
+
}
|
|
12
|
+
declare const NO_MODS: Modifiers;
|
|
13
|
+
type PointerButton = "primary" | "secondary" | "middle";
|
|
14
|
+
/**
|
|
15
|
+
* Synthesized input event consumed by HUD bedrock.
|
|
16
|
+
*
|
|
17
|
+
* All coordinates are **screen-space CSS pixels relative to the
|
|
18
|
+
* canvas viewport**. The consumer holds the camera and converts to
|
|
19
|
+
* document-space via {@link Transform}.
|
|
20
|
+
*/
|
|
21
|
+
type HUDEvent = {
|
|
22
|
+
readonly kind: "pointer_move";
|
|
23
|
+
readonly x: number;
|
|
24
|
+
readonly y: number;
|
|
25
|
+
readonly mods: Modifiers;
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: "pointer_down";
|
|
28
|
+
readonly x: number;
|
|
29
|
+
readonly y: number;
|
|
30
|
+
readonly button: PointerButton;
|
|
31
|
+
readonly mods: Modifiers;
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: "pointer_up";
|
|
34
|
+
readonly x: number;
|
|
35
|
+
readonly y: number;
|
|
36
|
+
readonly button: PointerButton;
|
|
37
|
+
readonly mods: Modifiers;
|
|
38
|
+
} | {
|
|
39
|
+
readonly kind: "modifiers";
|
|
40
|
+
readonly mods: Modifiers;
|
|
41
|
+
} | {
|
|
42
|
+
readonly kind: "wheel";
|
|
43
|
+
readonly x: number;
|
|
44
|
+
readonly y: number;
|
|
45
|
+
readonly dx: number;
|
|
46
|
+
readonly dy: number;
|
|
47
|
+
readonly mods: Modifiers;
|
|
48
|
+
} | {
|
|
49
|
+
readonly kind: "key";
|
|
50
|
+
readonly phase: "down" | "up";
|
|
51
|
+
readonly code: string;
|
|
52
|
+
readonly mods: Modifiers;
|
|
53
|
+
} | {
|
|
54
|
+
readonly kind: "blur";
|
|
55
|
+
};
|
|
56
|
+
/** Convenience re-export. */
|
|
57
|
+
type Vector2 = cmath.Vector2;
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region core/click-tracker.d.ts
|
|
60
|
+
interface ClickTrackerOptions {
|
|
61
|
+
/** Max gap between clicks in ms. Default 250. */
|
|
62
|
+
readonly windowMs?: number;
|
|
63
|
+
/** Max distance between clicks in screen px. Default 5. */
|
|
64
|
+
readonly distancePx?: number;
|
|
65
|
+
}
|
|
66
|
+
declare class ClickTracker {
|
|
67
|
+
private window_ms;
|
|
68
|
+
private distance_px;
|
|
69
|
+
private last_time;
|
|
70
|
+
private last_x;
|
|
71
|
+
private last_y;
|
|
72
|
+
private count;
|
|
73
|
+
constructor(opts?: ClickTrackerOptions);
|
|
74
|
+
/**
|
|
75
|
+
* Register a click at `(x, y)` and return the current consecutive-
|
|
76
|
+
* click count (1 = single, 2 = double, etc.). `now` is in ms.
|
|
77
|
+
*/
|
|
78
|
+
register(x: number, y: number, now?: number): number;
|
|
79
|
+
reset(): void;
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region core/transform.d.ts
|
|
83
|
+
/**
|
|
84
|
+
* Axis-aligned 2×3 affine.
|
|
85
|
+
*
|
|
86
|
+
* Stored as a `cmath.Transform`:
|
|
87
|
+
* `[[sx, 0, tx], [0, sy, ty]]`
|
|
88
|
+
*
|
|
89
|
+
* Off-diagonal components are ignored by these helpers; HUD's camera
|
|
90
|
+
* is scale + translate only.
|
|
91
|
+
*/
|
|
92
|
+
type Transform = cmath.Transform;
|
|
93
|
+
declare const IDENTITY: Transform;
|
|
94
|
+
/** Project a screen-space point into document-space. */
|
|
95
|
+
declare function screenToDoc(t: Transform, x: number, y: number): cmath.Vector2;
|
|
96
|
+
/** Project a document-space point into screen-space. */
|
|
97
|
+
declare function docToScreen(t: Transform, x: number, y: number): cmath.Vector2;
|
|
98
|
+
/** Current uniform zoom. Reads `sx`. */
|
|
99
|
+
declare function zoomOf(t: Transform): number;
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region core/registry.d.ts
|
|
102
|
+
/**
|
|
103
|
+
* Thrown by `register` when an entity with the same `name` is already
|
|
104
|
+
* registered. Consumers should `unregister` the existing one first.
|
|
105
|
+
*/
|
|
106
|
+
declare class RegistrationError extends Error {
|
|
107
|
+
constructor(message: string);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Generic name-keyed registry.
|
|
111
|
+
*
|
|
112
|
+
* @typeParam K — key type, must extend `string`.
|
|
113
|
+
* @typeParam T — entity type. Must expose `readonly name: K` and an
|
|
114
|
+
* optional `detach()` method.
|
|
115
|
+
*/
|
|
116
|
+
declare class NamedRegistry<K extends string, T extends {
|
|
117
|
+
readonly name: K;
|
|
118
|
+
detach?(): void;
|
|
119
|
+
}> {
|
|
120
|
+
private label;
|
|
121
|
+
private byName;
|
|
122
|
+
/** Explicit insertion-ordered array for cheap reverse-walk on clear. */
|
|
123
|
+
private order;
|
|
124
|
+
constructor(label: string);
|
|
125
|
+
register(e: T): void;
|
|
126
|
+
unregister(e: T): void;
|
|
127
|
+
has(name: K): boolean;
|
|
128
|
+
get(name: K): T | undefined;
|
|
129
|
+
entries(): IterableIterator<T>;
|
|
130
|
+
size(): number;
|
|
131
|
+
clear(): void;
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region core/hit-registry.d.ts
|
|
135
|
+
/**
|
|
136
|
+
* Generic hit registry. One per frame (or persistent — bedrock is
|
|
137
|
+
* agnostic). Add objects with `add`, clear with `clear`, query with
|
|
138
|
+
* `queryPoint` / `queryAll`.
|
|
139
|
+
*/
|
|
140
|
+
declare class HitRegistry<I = unknown> {
|
|
141
|
+
private items;
|
|
142
|
+
/**
|
|
143
|
+
* Add an object. Objects without `hit` are accepted (they may carry
|
|
144
|
+
* paint information the consumer still wants in the registry for
|
|
145
|
+
* uniform iteration) but are filtered from point queries.
|
|
146
|
+
*/
|
|
147
|
+
add(obj: HUDObject<I>): void;
|
|
148
|
+
/** Drop all entries. */
|
|
149
|
+
clear(): void;
|
|
150
|
+
/** Total number of stored objects (paint-only + hit-testable). */
|
|
151
|
+
size(): number;
|
|
152
|
+
/** Iterate every stored object in insertion order. */
|
|
153
|
+
entries(): IterableIterator<HUDObject<I>>;
|
|
154
|
+
/**
|
|
155
|
+
* Return the hit-testable object whose hit shape contains the
|
|
156
|
+
* given screen-space point with the lowest `priority` value
|
|
157
|
+
* (lower wins). Paint-only objects (no `hit`) are skipped.
|
|
158
|
+
*/
|
|
159
|
+
queryPoint(point_screen: cmath.Vector2, transform: Transform): HUDObject<I> | null;
|
|
160
|
+
/**
|
|
161
|
+
* Return all hit-testable objects whose hit shape contains the point,
|
|
162
|
+
* winner first. Ordering is identical to `queryPoint`'s arbitration:
|
|
163
|
+
* priority ascending (lower wins), and on EQUAL priority the
|
|
164
|
+
* later-added object comes first ("later push wins on tie"). This makes
|
|
165
|
+
* `queryAll(p)[0]` always equal `queryPoint(p)` — the two query paths
|
|
166
|
+
* never disagree on the winner.
|
|
167
|
+
*/
|
|
168
|
+
queryAll(point_screen: cmath.Vector2, transform: Transform): HUDObject<I>[];
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Test whether a screen-space point lies inside a `HitShape`.
|
|
172
|
+
*
|
|
173
|
+
* The camera `transform` is needed for doc-anchored shapes
|
|
174
|
+
* (`screen_rect_at_doc`, `screen_circle_at_doc`); pre-projected
|
|
175
|
+
* shapes (`screen_aabb`, `screen_polygon`) ignore it. `screen_obb`
|
|
176
|
+
* applies its own `inverse_transform`, which maps screen → shadow
|
|
177
|
+
* (independent of the camera).
|
|
178
|
+
*/
|
|
179
|
+
declare function shapeContains(shape: HitShape, point_screen: cmath.Vector2, transform: Transform): boolean;
|
|
180
|
+
//#endregion
|
|
181
|
+
export { ClickTracker, type ClickTrackerOptions, type HUDEvent, HitRegistry, IDENTITY, type Modifiers, NO_MODS, NamedRegistry, type PointerButton, RegistrationError, type Transform, type Vector2, docToScreen, screenToDoc, shapeContains, zoomOf };
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region core/event.ts
|
|
3
|
+
const NO_MODS = {
|
|
4
|
+
shift: false,
|
|
5
|
+
alt: false,
|
|
6
|
+
meta: false,
|
|
7
|
+
ctrl: false
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region core/click-tracker.ts
|
|
11
|
+
var ClickTracker = class {
|
|
12
|
+
constructor(opts = {}) {
|
|
13
|
+
this.last_time = 0;
|
|
14
|
+
this.last_x = 0;
|
|
15
|
+
this.last_y = 0;
|
|
16
|
+
this.count = 0;
|
|
17
|
+
this.window_ms = opts.windowMs ?? 250;
|
|
18
|
+
this.distance_px = opts.distancePx ?? 5;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Register a click at `(x, y)` and return the current consecutive-
|
|
22
|
+
* click count (1 = single, 2 = double, etc.). `now` is in ms.
|
|
23
|
+
*/
|
|
24
|
+
register(x, y, now = nowMs()) {
|
|
25
|
+
const dt = now - this.last_time;
|
|
26
|
+
const dx = x - this.last_x;
|
|
27
|
+
const dy = y - this.last_y;
|
|
28
|
+
const dist2 = dx * dx + dy * dy;
|
|
29
|
+
const max2 = this.distance_px * this.distance_px;
|
|
30
|
+
if (dt <= this.window_ms && dist2 <= max2 && this.count > 0) this.count += 1;
|
|
31
|
+
else this.count = 1;
|
|
32
|
+
this.last_time = now;
|
|
33
|
+
this.last_x = x;
|
|
34
|
+
this.last_y = y;
|
|
35
|
+
return this.count;
|
|
36
|
+
}
|
|
37
|
+
reset() {
|
|
38
|
+
this.count = 0;
|
|
39
|
+
this.last_time = 0;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
function nowMs() {
|
|
43
|
+
if (typeof performance !== "undefined" && performance.now) return performance.now();
|
|
44
|
+
return Date.now();
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region core/transform.ts
|
|
48
|
+
const IDENTITY = [[
|
|
49
|
+
1,
|
|
50
|
+
0,
|
|
51
|
+
0
|
|
52
|
+
], [
|
|
53
|
+
0,
|
|
54
|
+
1,
|
|
55
|
+
0
|
|
56
|
+
]];
|
|
57
|
+
/** Project a screen-space point into document-space. */
|
|
58
|
+
function screenToDoc(t, x, y) {
|
|
59
|
+
const [[sx, , tx], [, sy, ty]] = t;
|
|
60
|
+
return [(x - tx) / (sx || 1), (y - ty) / (sy || 1)];
|
|
61
|
+
}
|
|
62
|
+
/** Project a document-space point into screen-space. */
|
|
63
|
+
function docToScreen(t, x, y) {
|
|
64
|
+
const [[sx, , tx], [, sy, ty]] = t;
|
|
65
|
+
return [sx * x + tx, sy * y + ty];
|
|
66
|
+
}
|
|
67
|
+
/** Current uniform zoom. Reads `sx`. */
|
|
68
|
+
function zoomOf(t) {
|
|
69
|
+
return t[0][0];
|
|
70
|
+
}
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region core/registry.ts
|
|
73
|
+
/**
|
|
74
|
+
* Thrown by `register` when an entity with the same `name` is already
|
|
75
|
+
* registered. Consumers should `unregister` the existing one first.
|
|
76
|
+
*/
|
|
77
|
+
var RegistrationError = class extends Error {
|
|
78
|
+
constructor(message) {
|
|
79
|
+
super(message);
|
|
80
|
+
this.name = "RegistrationError";
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Generic name-keyed registry.
|
|
85
|
+
*
|
|
86
|
+
* @typeParam K — key type, must extend `string`.
|
|
87
|
+
* @typeParam T — entity type. Must expose `readonly name: K` and an
|
|
88
|
+
* optional `detach()` method.
|
|
89
|
+
*/
|
|
90
|
+
var NamedRegistry = class {
|
|
91
|
+
constructor(label) {
|
|
92
|
+
this.label = label;
|
|
93
|
+
this.byName = /* @__PURE__ */ new Map();
|
|
94
|
+
this.order = [];
|
|
95
|
+
}
|
|
96
|
+
register(e) {
|
|
97
|
+
if (this.byName.has(e.name)) throw new RegistrationError(`${this.label} "${e.name}" already registered; unregister it first.`);
|
|
98
|
+
this.byName.set(e.name, e);
|
|
99
|
+
this.order.push(e);
|
|
100
|
+
}
|
|
101
|
+
unregister(e) {
|
|
102
|
+
if (this.byName.get(e.name) !== e) return;
|
|
103
|
+
this.byName.delete(e.name);
|
|
104
|
+
const i = this.order.indexOf(e);
|
|
105
|
+
if (i >= 0) this.order.splice(i, 1);
|
|
106
|
+
try {
|
|
107
|
+
e.detach?.();
|
|
108
|
+
} catch (err) {
|
|
109
|
+
console.error(`[hud] ${this.label} "${e.name}" detach() threw:`, err);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
has(name) {
|
|
113
|
+
return this.byName.has(name);
|
|
114
|
+
}
|
|
115
|
+
get(name) {
|
|
116
|
+
return this.byName.get(name);
|
|
117
|
+
}
|
|
118
|
+
*entries() {
|
|
119
|
+
for (const e of this.order) yield e;
|
|
120
|
+
}
|
|
121
|
+
size() {
|
|
122
|
+
return this.order.length;
|
|
123
|
+
}
|
|
124
|
+
clear() {
|
|
125
|
+
for (let i = this.order.length - 1; i >= 0; i--) {
|
|
126
|
+
const e = this.order[i];
|
|
127
|
+
try {
|
|
128
|
+
e.detach?.();
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.error(`[hud] ${this.label} "${e.name}" detach() threw:`, err);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
this.order = [];
|
|
134
|
+
this.byName.clear();
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region core/hit-registry.ts
|
|
139
|
+
/**
|
|
140
|
+
* Generic hit registry. One per frame (or persistent — bedrock is
|
|
141
|
+
* agnostic). Add objects with `add`, clear with `clear`, query with
|
|
142
|
+
* `queryPoint` / `queryAll`.
|
|
143
|
+
*/
|
|
144
|
+
var HitRegistry = class {
|
|
145
|
+
constructor() {
|
|
146
|
+
this.items = [];
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Add an object. Objects without `hit` are accepted (they may carry
|
|
150
|
+
* paint information the consumer still wants in the registry for
|
|
151
|
+
* uniform iteration) but are filtered from point queries.
|
|
152
|
+
*/
|
|
153
|
+
add(obj) {
|
|
154
|
+
this.items.push(obj);
|
|
155
|
+
}
|
|
156
|
+
/** Drop all entries. */
|
|
157
|
+
clear() {
|
|
158
|
+
this.items = [];
|
|
159
|
+
}
|
|
160
|
+
/** Total number of stored objects (paint-only + hit-testable). */
|
|
161
|
+
size() {
|
|
162
|
+
return this.items.length;
|
|
163
|
+
}
|
|
164
|
+
/** Iterate every stored object in insertion order. */
|
|
165
|
+
*entries() {
|
|
166
|
+
for (const o of this.items) yield o;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Return the hit-testable object whose hit shape contains the
|
|
170
|
+
* given screen-space point with the lowest `priority` value
|
|
171
|
+
* (lower wins). Paint-only objects (no `hit`) are skipped.
|
|
172
|
+
*/
|
|
173
|
+
queryPoint(point_screen, transform) {
|
|
174
|
+
let best = null;
|
|
175
|
+
let best_priority = Number.POSITIVE_INFINITY;
|
|
176
|
+
for (const obj of this.items) {
|
|
177
|
+
if (!obj.hit) continue;
|
|
178
|
+
if (obj.priority > best_priority) continue;
|
|
179
|
+
if (!shapeContains(obj.hit, point_screen, transform)) continue;
|
|
180
|
+
if (obj.refine && !obj.refine(point_screen)) continue;
|
|
181
|
+
best = obj;
|
|
182
|
+
best_priority = obj.priority;
|
|
183
|
+
}
|
|
184
|
+
return best;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Return all hit-testable objects whose hit shape contains the point,
|
|
188
|
+
* winner first. Ordering is identical to `queryPoint`'s arbitration:
|
|
189
|
+
* priority ascending (lower wins), and on EQUAL priority the
|
|
190
|
+
* later-added object comes first ("later push wins on tie"). This makes
|
|
191
|
+
* `queryAll(p)[0]` always equal `queryPoint(p)` — the two query paths
|
|
192
|
+
* never disagree on the winner.
|
|
193
|
+
*/
|
|
194
|
+
queryAll(point_screen, transform) {
|
|
195
|
+
const matches = [];
|
|
196
|
+
for (let i = 0; i < this.items.length; i++) {
|
|
197
|
+
const obj = this.items[i];
|
|
198
|
+
if (!obj.hit) continue;
|
|
199
|
+
if (!shapeContains(obj.hit, point_screen, transform)) continue;
|
|
200
|
+
if (obj.refine && !obj.refine(point_screen)) continue;
|
|
201
|
+
matches.push({
|
|
202
|
+
obj,
|
|
203
|
+
i
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
matches.sort((a, b) => a.obj.priority - b.obj.priority || b.i - a.i);
|
|
207
|
+
return matches.map((m) => m.obj);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
/**
|
|
211
|
+
* Test whether a screen-space point lies inside a `HitShape`.
|
|
212
|
+
*
|
|
213
|
+
* The camera `transform` is needed for doc-anchored shapes
|
|
214
|
+
* (`screen_rect_at_doc`, `screen_circle_at_doc`); pre-projected
|
|
215
|
+
* shapes (`screen_aabb`, `screen_polygon`) ignore it. `screen_obb`
|
|
216
|
+
* applies its own `inverse_transform`, which maps screen → shadow
|
|
217
|
+
* (independent of the camera).
|
|
218
|
+
*/
|
|
219
|
+
function shapeContains(shape, point_screen, transform) {
|
|
220
|
+
const [px, py] = point_screen;
|
|
221
|
+
switch (shape.kind) {
|
|
222
|
+
case "screen_rect_at_doc": {
|
|
223
|
+
const [ax, ay] = docToScreen(transform, shape.anchor_doc[0], shape.anchor_doc[1]);
|
|
224
|
+
const { x, y } = anchorOrigin(ax, ay, shape.width, shape.height, shape.placement ?? "center");
|
|
225
|
+
return px >= x && px <= x + shape.width && py >= y && py <= y + shape.height;
|
|
226
|
+
}
|
|
227
|
+
case "screen_aabb": {
|
|
228
|
+
const r = shape.rect;
|
|
229
|
+
return px >= r.x && px <= r.x + r.width && py >= r.y && py <= r.y + r.height;
|
|
230
|
+
}
|
|
231
|
+
case "screen_obb": {
|
|
232
|
+
const [[a, b, e], [c, d, f]] = shape.inverse_transform;
|
|
233
|
+
const sx = a * px + b * py + e;
|
|
234
|
+
const sy = c * px + d * py + f;
|
|
235
|
+
const r = shape.rect;
|
|
236
|
+
return sx >= r.x && sx <= r.x + r.width && sy >= r.y && sy <= r.y + r.height;
|
|
237
|
+
}
|
|
238
|
+
case "screen_circle_at_doc": {
|
|
239
|
+
const [ax, ay] = docToScreen(transform, shape.anchor_doc[0], shape.anchor_doc[1]);
|
|
240
|
+
const dx = px - ax;
|
|
241
|
+
const dy = py - ay;
|
|
242
|
+
return dx * dx + dy * dy <= shape.radius * shape.radius;
|
|
243
|
+
}
|
|
244
|
+
case "screen_polygon": return pointInPolygon(point_screen, shape.points);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function anchorOrigin(ax, ay, w, h, placement) {
|
|
248
|
+
switch (placement) {
|
|
249
|
+
case "center": return {
|
|
250
|
+
x: ax - w / 2,
|
|
251
|
+
y: ay - h / 2
|
|
252
|
+
};
|
|
253
|
+
case "tl": return {
|
|
254
|
+
x: ax,
|
|
255
|
+
y: ay
|
|
256
|
+
};
|
|
257
|
+
case "tr": return {
|
|
258
|
+
x: ax - w,
|
|
259
|
+
y: ay
|
|
260
|
+
};
|
|
261
|
+
case "bl": return {
|
|
262
|
+
x: ax,
|
|
263
|
+
y: ay - h
|
|
264
|
+
};
|
|
265
|
+
case "br": return {
|
|
266
|
+
x: ax - w,
|
|
267
|
+
y: ay - h
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Standard even-odd ray-cast point-in-polygon.
|
|
273
|
+
*
|
|
274
|
+
* Horizontal edges (`yi === yj`) are skipped — the standard idiom for
|
|
275
|
+
* the even-odd algorithm. (The previous form used `(yj - yi ||
|
|
276
|
+
* Number.EPSILON)` as a divide-by-zero guard, but the guard was dead
|
|
277
|
+
* code: the `yi > py !== yj > py` clause already short-circuits to
|
|
278
|
+
* `false` when the edge is horizontal, so the division never runs.)
|
|
279
|
+
*/
|
|
280
|
+
function pointInPolygon(point, poly) {
|
|
281
|
+
const [px, py] = point;
|
|
282
|
+
let inside = false;
|
|
283
|
+
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
284
|
+
const [xi, yi] = poly[i];
|
|
285
|
+
const [xj, yj] = poly[j];
|
|
286
|
+
if (yi === yj) continue;
|
|
287
|
+
if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi) inside = !inside;
|
|
288
|
+
}
|
|
289
|
+
return inside;
|
|
290
|
+
}
|
|
291
|
+
//#endregion
|
|
292
|
+
exports.ClickTracker = ClickTracker;
|
|
293
|
+
exports.HitRegistry = HitRegistry;
|
|
294
|
+
exports.IDENTITY = IDENTITY;
|
|
295
|
+
exports.NO_MODS = NO_MODS;
|
|
296
|
+
exports.NamedRegistry = NamedRegistry;
|
|
297
|
+
exports.RegistrationError = RegistrationError;
|
|
298
|
+
exports.docToScreen = docToScreen;
|
|
299
|
+
exports.screenToDoc = screenToDoc;
|
|
300
|
+
exports.shapeContains = shapeContains;
|
|
301
|
+
exports.zoomOf = zoomOf;
|