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