@weasel-js/hud 1.6.1 → 1.7.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 +8 -0
- package/dist/attach.d.ts +1 -1
- package/dist/attach.d.ts.map +1 -1
- package/dist/fonts/registerDefaultFont.d.ts +3 -2
- package/dist/fonts/registerDefaultFont.d.ts.map +1 -1
- package/dist/hud.d.ts +25 -1
- package/dist/hud.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -24
- package/dist/index.js.map +1 -1
- package/dist/loupe/createLoupe.d.ts +2 -1
- package/dist/loupe/createLoupe.d.ts.map +1 -1
- package/dist/react/index.js +9 -9
- package/dist/react/index.js.map +1 -1
- package/dist/tool-DtH159Hp.js +1226 -0
- package/dist/tool-DtH159Hp.js.map +1 -0
- package/dist/widget.d.ts +54 -0
- package/dist/widget.d.ts.map +1 -1
- package/dist/widgets/button.d.ts +5 -0
- package/dist/widgets/button.d.ts.map +1 -1
- package/dist/widgets/image.d.ts.map +1 -1
- package/dist/widgets/rect.d.ts.map +1 -1
- package/dist/widgets/text.d.ts.map +1 -1
- package/dist/widgets/window/window.d.ts +4 -2
- package/dist/widgets/window/window.d.ts.map +1 -1
- package/dist/widgets/window/zones.d.ts +3 -0
- package/dist/widgets/window/zones.d.ts.map +1 -1
- package/package.json +7 -7
- package/dist/tool-DThNTu9f.js +0 -1031
- package/dist/tool-DThNTu9f.js.map +0 -1
package/README.md
CHANGED
|
@@ -31,6 +31,14 @@ Widgets draw from a data-free context (`{ dims, defaultFont, tokens }`), which
|
|
|
31
31
|
is what lets a HUD render headlessly and identically. A window's `content`
|
|
32
32
|
painter is the single, explicit exception.
|
|
33
33
|
|
|
34
|
+
## HUD or DOM text?
|
|
35
|
+
|
|
36
|
+
Measured in `tests/perf/README.md` ("HUD text against a DOM overlay"): below
|
|
37
|
+
about 500 glyphs the choice costs nothing either way. Past that, static labels
|
|
38
|
+
are cheaper in a DOM layer over the canvas, because a HUD re-emits every
|
|
39
|
+
widget's text on each repaint; readouts that change every frame *and* follow the
|
|
40
|
+
camera are cheaper here.
|
|
41
|
+
|
|
34
42
|
## License
|
|
35
43
|
|
|
36
44
|
MIT
|
package/dist/attach.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export interface AttachHudOptions {
|
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* Bind a HUD to a canvas: register its render layer, route pointer input to
|
|
31
|
-
* the widget under the cursor, and
|
|
31
|
+
* the widget under the cursor, and register the default font. Returns
|
|
32
32
|
* the detach function.
|
|
33
33
|
*
|
|
34
34
|
* Throws if the HUD is already attached — a HUD belongs to one canvas at a
|
package/dist/attach.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attach.d.ts","sourceRoot":"","sources":["../src/attach.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,KAAK,EAAE,kBAAkB,EAA+B,MAAM,iBAAiB,CAAC;AAIvF,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"attach.d.ts","sourceRoot":"","sources":["../src/attach.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,KAAK,EAAE,kBAAkB,EAA+B,MAAM,iBAAiB,CAAC;AAIvF,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,6BAA6B,CAAC;AAMrC,OAAO,EAEL,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,aAAa,EACtD,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,gBAAgB;IAC/B;2EACuE;IACvE,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;IAC/B;;kDAE8C;IAC9C,QAAQ,CAAC,KAAK,CAAC,EAAE,YAAY,GAAG;QAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAC;IAC9D;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC;CACxC;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,kBAAkB,EACvB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,IAAI,CAqKZ"}
|
|
@@ -7,8 +7,9 @@ export interface FontAtlasUrls {
|
|
|
7
7
|
atlasUrl: string;
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
* Register an Inter-shaped atlas under {@link DEFAULT_FONT_FAMILY}
|
|
11
|
-
*
|
|
10
|
+
* Register an Inter-shaped atlas under {@link DEFAULT_FONT_FAMILY}, lazily: it
|
|
11
|
+
* is fetched the first time a widget lays out text, and the promise settles
|
|
12
|
+
* with that load. `attachHud` calls this; calling it again is a no-op.
|
|
12
13
|
*
|
|
13
14
|
* `urls` points the family at a copy the host already serves. The bundled atlas
|
|
14
15
|
* is the same Inter every weasel app ships in its own publicDir, and fetching
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registerDefaultFont.d.ts","sourceRoot":"","sources":["../../src/fonts/registerDefaultFont.ts"],"names":[],"mappings":"AAOA;2BAC2B;AAC3B,eAAO,MAAM,mBAAmB,uBAAuB,CAAC;AAExD,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED
|
|
1
|
+
{"version":3,"file":"registerDefaultFont.d.ts","sourceRoot":"","sources":["../../src/fonts/registerDefaultFont.ts"],"names":[],"mappings":"AAOA;2BAC2B;AAC3B,eAAO,MAAM,mBAAmB,uBAAuB,CAAC;AAExD,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ7E"}
|
package/dist/hud.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Widget } from './widget';
|
|
2
2
|
import type { HudHost } from './host';
|
|
3
3
|
import { type RectOptions, type RectWidget } from './widgets/rect';
|
|
4
4
|
import { type TextOptions, type TextWidget } from './widgets/text';
|
|
@@ -6,6 +6,13 @@ import { type ImageOptions, type ImageWidget } from './widgets/image';
|
|
|
6
6
|
import { type LabelOptions, type LabelWidget } from './widgets/label';
|
|
7
7
|
import { type ButtonOptions, type ButtonWidget } from './widgets/button';
|
|
8
8
|
import { type WindowOptions, type WindowWidget } from './widgets/window/window';
|
|
9
|
+
/** How a focus change came about. */
|
|
10
|
+
export interface HudFocusOptions {
|
|
11
|
+
/** Whether the focused widget should show a focus ring — the HUD's
|
|
12
|
+
* `:focus-visible`. Keyboard moves show one; a press does not. Default
|
|
13
|
+
* `false` for {@link Hud.focus}, `true` for {@link Hud.moveFocus}. */
|
|
14
|
+
readonly visible?: boolean;
|
|
15
|
+
}
|
|
9
16
|
/**
|
|
10
17
|
* A HUD: an ordered set of widgets drawn in screen space over the canvas, and
|
|
11
18
|
* the factories that create them. The factory methods (`rect`, `text`, …) also
|
|
@@ -28,6 +35,23 @@ export interface Hud {
|
|
|
28
35
|
subscribeFrame(fn: () => void): () => void;
|
|
29
36
|
/** True after bind() and before unbind(). */
|
|
30
37
|
readonly attached: boolean;
|
|
38
|
+
/** The widget holding keyboard focus, or null. One per HUD. */
|
|
39
|
+
readonly focused: Widget | null;
|
|
40
|
+
/** Whether the focused widget shows its focus ring. */
|
|
41
|
+
readonly focusVisible: boolean;
|
|
42
|
+
/** Focus `widget`, or blur with `null`. Returns whether `widget` now holds
|
|
43
|
+
* focus — false when it is not in this HUD or cannot take focus
|
|
44
|
+
* (see `isFocusable`), in which case focus is unchanged. */
|
|
45
|
+
focus(widget: Widget | null, options?: HudFocusOptions): boolean;
|
|
46
|
+
/** Move focus one step through {@link tabOrder}. From nothing, `'next'`
|
|
47
|
+
* lands on the first widget and `'prev'` on the last; stepping past either
|
|
48
|
+
* end blurs and returns null, which is where focus leaves the HUD. */
|
|
49
|
+
moveFocus(direction: 'next' | 'prev', options?: HudFocusOptions): Widget | null;
|
|
50
|
+
/** The widgets Tab visits, in order: those declaring `tabOrder` first,
|
|
51
|
+
* ascending, then the rest in the order they were added. */
|
|
52
|
+
tabOrder(): Widget[];
|
|
53
|
+
/** Run `fn` whenever the focused widget changes. Returns an unsubscribe. */
|
|
54
|
+
subscribeFocus(fn: (focused: Widget | null) => void): () => void;
|
|
31
55
|
/** Create a rect widget, add it to the HUD, and wire onChange → markDirty. */
|
|
32
56
|
rect(opts: RectOptions): RectWidget;
|
|
33
57
|
/** Create a text widget, add it to the HUD, and wire onChange → markDirty. */
|
package/dist/hud.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hud.d.ts","sourceRoot":"","sources":["../src/hud.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"hud.d.ts","sourceRoot":"","sources":["../src/hud.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAc,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EAAc,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC/E,OAAO,EAAe,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnF,OAAO,EAAe,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnF,OAAO,EAAgB,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACvF,OAAO,EAAgB,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE9F,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC9B;;2EAEuE;IACvE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,GAAG;IAClB,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,IAAI,SAAS,MAAM,EAAE,CAAC;IAC7B,SAAS,IAAI,IAAI,CAAC;IAClB,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,MAAM,IAAI,IAAI,CAAC;IACf;;kCAE8B;IAC9B,cAAc,CAAC,EAAE,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;IAC3C,6CAA6C;IAC7C,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,uDAAuD;IACvD,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;iEAE6D;IAC7D,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC;IACjE;;2EAEuE;IACvE,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAAC;IAChF;iEAC6D;IAC7D,QAAQ,IAAI,MAAM,EAAE,CAAC;IACrB,4EAA4E;IAC5E,cAAc,CAAC,EAAE,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACjE,8EAA8E;IAC9E,IAAI,CAAC,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC;IACpC,8EAA8E;IAC9E,IAAI,CAAC,IAAI,EAAE,WAAW,GAAG,UAAU,CAAC;IACpC,gFAAgF;IAChF,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,WAAW,CAAC;IACvC,+EAA+E;IAC/E,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,WAAW,CAAC;IACvC,gFAAgF;IAChF,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IAC1C,gFAAgF;IAChF,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;CAC3C;AAED,oCAAoC;AACpC,wBAAgB,SAAS,IAAI,GAAG,CAgM/B"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { createHud, type Hud } from './hud';
|
|
1
|
+
export { createHud, type Hud, type HudFocusOptions } from './hud';
|
|
2
2
|
export { attachHud } from './attach';
|
|
3
3
|
export { createHudContribution, HUD_AFFORDANCE_KIND, type HudHitPayload } from './tool';
|
|
4
|
-
export type { Widget, WidgetBounds, HudDrawCtx, HudPointerEvent, } from './widget';
|
|
5
|
-
export { DEFAULT_WIDGET_CLAIMS, claimsOf } from './widget';
|
|
4
|
+
export type { Widget, WidgetBounds, HudDrawCtx, HudPointerEvent, HudKeyEvent, } from './widget';
|
|
5
|
+
export { DEFAULT_WIDGET_CLAIMS, claimsOf, isFocusable } from './widget';
|
|
6
6
|
export type { HudHost } from './host';
|
|
7
7
|
export { DEFAULT_FONT_FAMILY, registerDefaultFont } from './fonts/registerDefaultFont';
|
|
8
8
|
export type { FontAtlasUrls } from './fonts/registerDefaultFont';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,GAAG,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,GAAG,EAAE,KAAK,eAAe,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AACxF,YAAY,EACV,MAAM,EACN,YAAY,EACZ,UAAU,EACV,eAAe,EACf,WAAW,GACZ,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,qBAAqB,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACxE,YAAY,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvF,YAAY,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC9D,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC9D,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACjE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACjE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAChG,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,EACL,sBAAsB,EAAE,aAAa,EACrC,KAAK,UAAU,EAAE,KAAK,aAAa,GACpC,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACvG,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { a as e, c as t,
|
|
2
|
-
import { createViewportLayer as
|
|
3
|
-
import { createLoupeModel as
|
|
1
|
+
import { a as e, c as t, d as n, i as r, l as i, n as a, o, r as s, s as c, t as l, u } from "./tool-DtH159Hp.js";
|
|
2
|
+
import { createViewportLayer as d, screenToWorld as f, viewToTransform as p } from "@weasel-js/core";
|
|
3
|
+
import { createLoupeModel as m, loupeInnerView as h, loupeInnerView as g } from "@weasel-js/loupe";
|
|
4
4
|
//#region src/loupe/readback.ts
|
|
5
|
-
function
|
|
6
|
-
let s = n.x + Math.round(r.x * i), c = n.y + Math.round(r.y * i), l =
|
|
5
|
+
function _(e, t, n, r, i, a, o) {
|
|
6
|
+
let s = n.x + Math.round(r.x * i), c = n.y + Math.round(r.y * i), l = v(s - Math.floor(a / 2), n.x, n.x + Math.max(0, n.width - a)), u = t - v(c - Math.floor(o / 2), n.y, n.y + Math.max(0, n.height - o)) - o, d = new Uint8Array(a * o * 4);
|
|
7
7
|
e.readPixels(l, u, a, o, e.RGBA, e.UNSIGNED_BYTE, d);
|
|
8
8
|
let f = new Uint8ClampedArray(a * o * 4), p = a * 4;
|
|
9
9
|
for (let e = 0; e < o; e++) {
|
|
@@ -12,19 +12,19 @@ function g(e, t, n, r, i, a, o) {
|
|
|
12
12
|
}
|
|
13
13
|
return new ImageData(f, a, o);
|
|
14
14
|
}
|
|
15
|
-
function
|
|
15
|
+
function v(e, t, n) {
|
|
16
16
|
return e < t ? t : e > n ? n : e;
|
|
17
17
|
}
|
|
18
18
|
//#endregion
|
|
19
19
|
//#region src/loupe/createLoupe.ts
|
|
20
|
-
var
|
|
20
|
+
var y = {
|
|
21
21
|
x: 0,
|
|
22
22
|
y: 0,
|
|
23
23
|
w: 0,
|
|
24
24
|
h: 0
|
|
25
25
|
};
|
|
26
|
-
function
|
|
27
|
-
let { hud: t, canvas: n, requestRedraw: r } = e, i = e.views, a = i !== void 0 && e.interactive === !0, o = e.input ?? n, s = null, c = !1, l = null,
|
|
26
|
+
function b(e) {
|
|
27
|
+
let { hud: t, canvas: n, requestRedraw: r } = e, i = e.views, a = i !== void 0 && e.interactive === !0, o = e.input ?? n, s = null, c = !1, l = null, u = !1, h = !1, v = !1, b = !1, x = e.bounds ?? {
|
|
28
28
|
x: 24,
|
|
29
29
|
y: 24,
|
|
30
30
|
w: 220,
|
|
@@ -43,12 +43,12 @@ function y(e) {
|
|
|
43
43
|
color: C(e)
|
|
44
44
|
}
|
|
45
45
|
}), T = (e, t) => {
|
|
46
|
-
let n =
|
|
47
|
-
return
|
|
46
|
+
let n = f(F.aim.x, F.aim.y, p(e));
|
|
47
|
+
return g({
|
|
48
48
|
x: n[0],
|
|
49
49
|
y: n[1]
|
|
50
50
|
}, e, t, F.factor);
|
|
51
|
-
}, E = (t) => i ? s ? [w(t), ...s.draw(t.data, t.view, t.dims)] : [w(t)] :
|
|
51
|
+
}, E = (t) => i ? s ? [w(t), ...s.draw(t.data, t.view, t.dims)] : [w(t)] : d({
|
|
52
52
|
id: `${M.id}:lens`,
|
|
53
53
|
label: "Loupe content",
|
|
54
54
|
source: e.source ?? [],
|
|
@@ -67,7 +67,7 @@ function y(e) {
|
|
|
67
67
|
sampling: "nearest"
|
|
68
68
|
}), t;
|
|
69
69
|
}, O = () => {
|
|
70
|
-
|
|
70
|
+
v || F.mode !== "pixel" || (h = !0);
|
|
71
71
|
}, k = (t, r, a) => {
|
|
72
72
|
if (!b) return null;
|
|
73
73
|
let o = n.getContext("webgl2");
|
|
@@ -85,18 +85,18 @@ function y(e) {
|
|
|
85
85
|
width: n.width,
|
|
86
86
|
height: n.height
|
|
87
87
|
};
|
|
88
|
-
return
|
|
88
|
+
return _(o, n.height, u, t, c, r(c), a(c));
|
|
89
89
|
}, A = () => {
|
|
90
|
-
if (
|
|
90
|
+
if (v || F.mode !== "pixel") return;
|
|
91
91
|
let e = M.contentRect, t = k(F.aim, (t) => Math.max(1, Math.round(e.w * t / F.factor)), (t) => Math.max(1, Math.round(e.h * t / F.factor)));
|
|
92
|
-
t && (
|
|
93
|
-
if (
|
|
92
|
+
t && (u = !0, createImageBitmap(t).then((e) => {
|
|
93
|
+
if (v) {
|
|
94
94
|
e.close();
|
|
95
95
|
return;
|
|
96
96
|
}
|
|
97
|
-
l?.close(), l = e,
|
|
97
|
+
l?.close(), l = e, u = !1, r();
|
|
98
98
|
}).catch(() => {
|
|
99
|
-
|
|
99
|
+
u = !1, r();
|
|
100
100
|
}));
|
|
101
101
|
}, j = (e) => {
|
|
102
102
|
let t = k(e, () => 1, () => 1);
|
|
@@ -133,8 +133,8 @@ function y(e) {
|
|
|
133
133
|
},
|
|
134
134
|
subscribeFrame: t.subscribeFrame
|
|
135
135
|
}, P = t.subscribeFrame(() => {
|
|
136
|
-
b = !0,
|
|
137
|
-
}), F =
|
|
136
|
+
b = !0, h && !u && (h = !1, A());
|
|
137
|
+
}), F = m({
|
|
138
138
|
surface: N,
|
|
139
139
|
...e.mode ? { mode: e.mode } : {},
|
|
140
140
|
...e.factor === void 0 ? {} : { factor: e.factor },
|
|
@@ -145,7 +145,7 @@ function y(e) {
|
|
|
145
145
|
i && (s = i.addView({
|
|
146
146
|
id: `${M.id}:lens`,
|
|
147
147
|
label: "Loupe",
|
|
148
|
-
bounds: () => M.hidden ?
|
|
148
|
+
bounds: () => M.hidden ? y : M.contentRect,
|
|
149
149
|
view: (e) => T(e, M.contentRect),
|
|
150
150
|
paint: !1,
|
|
151
151
|
interactive: a,
|
|
@@ -168,7 +168,7 @@ function y(e) {
|
|
|
168
168
|
"pointercancel",
|
|
169
169
|
"lostpointercapture"
|
|
170
170
|
], V = () => {
|
|
171
|
-
|
|
171
|
+
v = !0, P(), o.removeEventListener("pointermove", L), o.removeEventListener("pointerdown", R);
|
|
172
172
|
for (let e of B) o.removeEventListener(e, z);
|
|
173
173
|
s?.remove(), s = null, l?.close(), l = null;
|
|
174
174
|
};
|
|
@@ -198,6 +198,6 @@ function y(e) {
|
|
|
198
198
|
};
|
|
199
199
|
}
|
|
200
200
|
//#endregion
|
|
201
|
-
export {
|
|
201
|
+
export { r as DEFAULT_FONT_FAMILY, i as DEFAULT_WIDGET_CLAIMS, c as DEFAULT_WINDOW_METRICS, l as HUD_AFFORDANCE_KIND, s as attachHud, u as claimsOf, o as createHud, a as createHudContribution, b as createLoupe, t as cursorForZone, n as isFocusable, h as loupeInnerView, e as registerDefaultFont };
|
|
202
202
|
|
|
203
203
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["loupeInnerView","claimsOf","createHud","DEFAULT_WIDGET_CLAIMS","DEFAULT_WINDOW_METRICS","createHudContribution","DEFAULT_FONT_FAMILY","attachHud","HUD_AFFORDANCE_KIND","cursorForZone","loupeInnerView$1","readbackRegion","cx","target","pointer","dpr","cy","gx","clamp","rw","gy","bufferHeight","rh","raw","gl","flipped","stride","row","src","v","lo","hi","NO_RECT","createLoupe","element","opts","host","interactive","input","lens","holding","pixels","pixelsPending","refreshWanted","disposed","landed","b","content","model","drawVector","ctx","drawPixels","backdropColor","backdrop","innerView","world","screenToWorld","viewToTransform","outer","rect","createViewportLayer","win","out","scheduleRefresh","read","cssRect","r","at","refreshPixels","data","bmp","requestRedraw","readHex","px","c","hud","p","surface","unsubscribeFrame","createLoupeModel","teardown","local","evt","onPointerMove","onPointerDown","onPointerEnd","pointerEnds","type","loupeInnerView","registerDefaultFont"],"sources":["../src/loupe/readback.ts","../src/loupe/createLoupe.ts"],"sourcesContent":["/**\n * Read a `rw × rh` device-pixel region of the drawing buffer centered on\n * `pointer`, returned top-down. `pointer` is in CSS px relative to `target`,\n * the rect of the buffer the canvas paints into (device px, top-left origin),\n * and the read is clamped to stay inside that rect.\n *\n * GL reports rows bottom-up; every consumer here wants top-down, so both the\n * read origin and the returned rows are flipped.\n */\nexport function readbackRegion(\n gl: WebGL2RenderingContext,\n bufferHeight: number,\n target: { x: number; y: number; width: number; height: number },\n pointer: { x: number; y: number },\n dpr: number,\n rw: number,\n rh: number,\n): ImageData {\n const cx = target.x + Math.round(pointer.x * dpr);\n const cy = target.y + Math.round(pointer.y * dpr);\n const gx = clamp(cx - Math.floor(rw / 2), target.x, target.x + Math.max(0, target.width - rw));\n const gyTop = clamp(cy - Math.floor(rh / 2), target.y, target.y + Math.max(0, target.height - rh));\n const gy = bufferHeight - gyTop - rh;\n\n const raw = new Uint8Array(rw * rh * 4);\n gl.readPixels(gx, gy, rw, rh, gl.RGBA, gl.UNSIGNED_BYTE, raw);\n\n const flipped = new Uint8ClampedArray(rw * rh * 4);\n const stride = rw * 4;\n for (let row = 0; row < rh; row++) {\n const src = (rh - 1 - row) * stride;\n flipped.set(raw.subarray(src, src + stride), row * stride);\n }\n return new ImageData(flipped, rw, rh);\n}\n\nfunction clamp(v: number, lo: number, hi: number): number {\n return v < lo ? lo : v > hi ? hi : v;\n}\n","import {\n createViewportLayer, screenToWorld, viewToTransform,\n type CanvasViewHandle, type RenderLayer, type SceneCanvasApi, type View,\n} from '@weasel-js/core';\nimport type { DrawCommand } from '@weasel-js/core/renderer';\nimport {\n createLoupeModel, loupeInnerView,\n type LoupeMode, type LoupeModel, type LoupeSurface,\n} from '@weasel-js/loupe';\nimport type { Hud } from '../hud';\nimport type { WindowWidget } from '../widgets/window/window';\nimport type { HudContentCtx, WidgetBounds } from '../widget';\nimport { readbackRegion } from './readback';\n\nexport type { LoupeMode };\n\n/** Options for `createLoupe` shared by a plain lens and an interactive one. */\nexport interface LoupeBaseOptions {\n hud: Hud;\n /** The canvas pixel mode reads back from. Supplies the GL context and the\n * drawing-buffer dimensions. */\n canvas: HTMLCanvasElement;\n /** Where the aim comes from: the element pointer events are taken from and\n * the rect client coords are measured against. Defaults to `canvas`, which\n * is right whenever the canvas is also the thing under the pointer. */\n input?: HTMLElement;\n /** The rect of `canvas` the magnified picture occupies, in its CSS px — the\n * one the aim is relative to. Pass `CanvasExtensionApi.getSurfaceRect` for\n * a pane painted into a shared canvas. Defaults to `views.getSurfaceRect`\n * when the lens is a view, and to the whole canvas otherwise. */\n region?: () => { x: number; y: number; width: number; height: number };\n requestRedraw: () => void;\n mode?: LoupeMode;\n factor?: number;\n bounds?: WidgetBounds;\n title?: string;\n /** Draw the window's titlebar and close box. Default `true`. Pass `false`\n * for a bare lens; the interior then doubles as the move handle. */\n titlebar?: boolean;\n /** Called with the hex color under the aim point whenever it changes.\n * Sampled from the framebuffer in both modes — vector content is a\n * re-render whose edge colors differ from the screen's, so reading the\n * color off it would report something the user cannot see. */\n onColorChange?: (hex: string) => void;\n /** Fired when a click inside the lens picks a color — an eyedropper on the\n * magnified surface. Where the color goes is the consumer's: the loupe\n * reports the pick and nothing else. An `interactive` lens gives its clicks\n * to the canvas, so only `pick()` reaches this. */\n onPick?: (hex: string) => void;\n /** Fired by the window's close box. The window does not hide itself —\n * whoever owns the loupe's visibility decides what closing means. */\n onClose?: () => void;\n /** Backdrop painted behind the magnified content in both modes, so the\n * outer canvas does not show through where the lens is empty. Defaults to\n * the theme's surface color. */\n background?: string;\n}\n\n/** Options for `createLoupe`. */\nexport type LoupeOptions = LoupeBaseOptions & (\n | {\n /** Layers re-rendered through the magnified inner view in vector mode. */\n source: RenderLayer<unknown>[];\n views?: undefined;\n interactive?: false;\n }\n | {\n /**\n * The canvas, as a host for views. The lens becomes a view on it: vector\n * mode paints the canvas's own stack — scene and selection chrome — and\n * the lens can take input. It magnifies the canvas's own camera; aimed\n * over another view, it shows the canvas's world, not that view's.\n */\n views: Pick<SceneCanvasApi, 'addView' | 'getSurfaceRect'>;\n /** Narrows what vector mode paints. Defaults to the canvas's stack. */\n source?: RenderLayer<unknown>[];\n /**\n * Route input inside the lens to what it shows: a press selects the node\n * magnified under the pointer, and a drag moves it by the pointer travel\n * divided by the magnification. Default `false` — the window keeps its\n * interior, and a click there picks a color.\n */\n interactive?: boolean;\n }\n);\n\n/** Where an interactive lens routes while its window is hidden: nowhere. */\nconst NO_RECT = { x: 0, y: 0, w: 0, h: 0 };\n\n/** Control surface for a live loupe: where it is aimed, how it magnifies,\n * what color it is over, and the window it lives in. */\nexport interface LoupeHandle {\n readonly window: WindowWidget;\n readonly mode: LoupeMode;\n readonly factor: number;\n /** Last aimed point, in screen-space CSS px relative to the canvas. */\n readonly aim: { x: number; y: number };\n /** Hex color under the aim point, read off the framebuffer on the first\n * frame to land after the aim. `null` until then, or when the GL context\n * is unavailable. */\n readonly color: string | null;\n setMode(mode: LoupeMode): void;\n setFactor(factor: number): void;\n /** Aim at a screen point. Ignored while the point is over the window —\n * that is the freeze rule that makes the borders reachable. */\n aimAt(p: { x: number; y: number }): void;\n /** Sample the color the lens is showing at `p` — a screen point inside the\n * lens — and report it to `onPick`. Omit `p` to pick at the aim point.\n * Returns the hex, or `null` when the framebuffer cannot answer for that\n * point, including before any frame has landed. Leaves the aim, and\n * `color`, alone. */\n pick(p?: { x: number; y: number }): string | null;\n dispose(): void;\n}\n\n/**\n * Build a magnifier: a HUD window whose interior shows the canvas around an\n * aim point, enlarged. Adds the window to the HUD and starts following the\n * pointer; `dispose` removes it again.\n */\nexport function createLoupe(opts: LoupeOptions): LoupeHandle {\n const { hud, canvas: element, requestRedraw } = opts;\n const host = opts.views;\n const interactive = host !== undefined && opts.interactive === true;\n const input = opts.input ?? element;\n let lens: CanvasViewHandle | null = null;\n // Set while a gesture that began over the window is held: the lens camera\n // follows the aim, so re-aiming mid-drag would move the world under it.\n let holding = false;\n let pixels: ImageBitmap | null = null;\n let pixelsPending = false;\n let refreshWanted = false;\n let disposed = false;\n // Until a paint lands, the context on a shared canvas may not exist yet, and\n // opening it here would give every tenant one with no stencil buffer.\n let landed = false;\n\n const b = opts.bounds ?? { x: 24, y: 24, w: 220, h: 200 };\n\n const content = (ctx: HudContentCtx): DrawCommand[] =>\n model.mode === 'vector' ? drawVector(ctx) : drawPixels(ctx);\n\n const backdropColor = (ctx: HudContentCtx): string =>\n opts.background ?? ctx.tokens['--wzl-surface'];\n\n const backdrop = (ctx: HudContentCtx): DrawCommand => ({\n kind: 'path',\n path: { kind: 'rect', x: ctx.rect.x, y: ctx.rect.y, width: ctx.rect.w, height: ctx.rect.h },\n fill: { fill: 'solid', color: backdropColor(ctx) },\n });\n\n const innerView = (outer: View, rect: WidgetBounds): View => {\n const world = screenToWorld(model.aim.x, model.aim.y, viewToTransform(outer));\n return loupeInnerView({ x: world[0], y: world[1] }, outer, rect, model.factor);\n };\n\n const drawVector = (ctx: HudContentCtx): DrawCommand[] => {\n if (host) return lens ? [backdrop(ctx), ...lens.draw(ctx.data, ctx.view, ctx.dims)] : [backdrop(ctx)];\n // A fresh lens per frame: CreateViewportLayerOpts.view is static, and the\n // inner view moves with the pointer.\n const picture = createViewportLayer<unknown>({\n id: `${win.id}:lens`,\n label: 'Loupe content',\n source: opts.source ?? [],\n view: innerView(ctx.view, ctx.rect),\n bounds: () => ctx.rect,\n background: backdropColor(ctx),\n });\n return picture.draw(ctx.data, ctx.view, ctx.dims);\n };\n\n const drawPixels = (ctx: HudContentCtx): DrawCommand[] => {\n // The backdrop paints even with a bitmap in hand: a readback of a\n // transparent framebuffer is transparent, and before the first one\n // settles there is nothing at all, either of which leaves the lens a\n // hole onto the unmagnified canvas.\n const out: DrawCommand[] = [backdrop(ctx)];\n if (pixels) {\n out.push({\n kind: 'image',\n image: pixels,\n x: ctx.rect.x, y: ctx.rect.y, w: ctx.rect.w, h: ctx.rect.h,\n sampling: 'nearest',\n });\n }\n return out;\n };\n\n // `gl.readPixels` outside a landed paint returns the *previous* frame, so an\n // aim only marks the lens dirty; the read itself happens in the frame\n // subscriber below, on the frame that painted.\n const scheduleRefresh = () => {\n if (disposed || model.mode !== 'pixel') return;\n refreshWanted = true;\n };\n\n /** Read device px of the loupe's region around `at`, sized for the\n * canvas's pixel ratio, or `null` when the framebuffer cannot answer. */\n const read = (\n at: { x: number; y: number },\n rw: (dpr: number) => number,\n rh: (dpr: number) => number,\n ): ImageData | null => {\n if (!landed) return null;\n const gl = element.getContext('webgl2');\n if (!gl) return null;\n const cssRect = element.getBoundingClientRect();\n if (cssRect.width === 0) return null;\n const dpr = element.width / cssRect.width;\n // A view-backed lens knows its canvas, so over a detached pane it reads\n // that pane's pixels without being told where the pane is.\n const r = (opts.region ?? host?.getSurfaceRect)?.();\n const target = r\n ? {\n x: Math.round(r.x * dpr), y: Math.round(r.y * dpr),\n width: Math.round(r.width * dpr), height: Math.round(r.height * dpr),\n }\n : { x: 0, y: 0, width: element.width, height: element.height };\n return readbackRegion(gl, element.height, target, at, dpr, rw(dpr), rh(dpr));\n };\n\n const refreshPixels = () => {\n if (disposed || model.mode !== 'pixel') return;\n const rect = win.contentRect;\n const data = read(\n model.aim,\n (dpr) => Math.max(1, Math.round((rect.w * dpr) / model.factor)),\n (dpr) => Math.max(1, Math.round((rect.h * dpr) / model.factor)),\n );\n if (!data) return;\n pixelsPending = true;\n createImageBitmap(data)\n .then((bmp) => {\n if (disposed) { bmp.close(); return; }\n pixels?.close();\n pixels = bmp;\n pixelsPending = false;\n requestRedraw();\n })\n .catch(() => {\n pixelsPending = false;\n requestRedraw();\n });\n };\n\n const readHex = (at: { x: number; y: number }): string | null => {\n const px = read(at, () => 1, () => 1);\n if (!px) return null;\n return '#' + [px.data[0], px.data[1], px.data[2]]\n .map((c) => c.toString(16).padStart(2, '0')).join('');\n };\n\n const win = hud.window({\n id: 'weasel-loupe',\n x: b.x, y: b.y, w: b.w, h: b.h,\n title: opts.title ?? 'Loupe',\n titlebar: opts.titlebar,\n content,\n interior: interactive ? 'pass' : 'claim',\n onResize: () => { scheduleRefresh(); },\n ...(interactive ? {} : { onContentClick: (p: { x: number; y: number }) => { model.pick(p); } }),\n ...(opts.onClose ? { onClose: opts.onClose } : {}),\n });\n\n const surface: LoupeSurface = {\n lens: () => win.contentRect,\n covers: (p) => win.hitTest(p.x, p.y) || (win.passes?.(p.x, p.y) ?? false),\n sample: readHex,\n hidden: () => win.hidden,\n gone: () => win.disposed === true,\n changed: () => { scheduleRefresh(); requestRedraw(); },\n subscribeFrame: hud.subscribeFrame,\n };\n\n // Subscribed ahead of the model, so the frame that lands first is already\n // readable by the time the model samples on it.\n const unsubscribeFrame = hud.subscribeFrame(() => {\n landed = true;\n // A refresh wanted while a bitmap is in flight is remembered rather than\n // dropped: the flag survives until a frame finds it settled, so the aim\n // the user ends a fast drag on is the one that gets read.\n if (!refreshWanted || pixelsPending) return;\n refreshWanted = false;\n refreshPixels();\n });\n\n const model: LoupeModel = createLoupeModel({\n surface,\n ...(opts.mode ? { mode: opts.mode } : {}),\n ...(opts.factor !== undefined ? { factor: opts.factor } : {}),\n ...(opts.onColorChange ? { onColorChange: opts.onColorChange } : {}),\n ...(opts.onPick ? { onPick: opts.onPick } : {}),\n onDispose: () => teardown(),\n });\n\n if (host) {\n lens = host.addView({\n id: `${win.id}:lens`,\n label: 'Loupe',\n bounds: () => (win.hidden ? NO_RECT : win.contentRect),\n view: (outer) => innerView(outer, win.contentRect),\n paint: false,\n interactive,\n ...(opts.source ? { layers: () => opts.source! } : {}),\n });\n }\n\n const local = (evt: PointerEvent): { x: number; y: number } => {\n const r = input.getBoundingClientRect();\n return { x: evt.clientX - r.left, y: evt.clientY - r.top };\n };\n\n const onPointerMove = (evt: PointerEvent) => {\n if (holding) return;\n model.aimAt(local(evt));\n };\n const onPointerDown = (evt: PointerEvent) => {\n if (interactive && surface.covers(local(evt))) holding = true;\n };\n const onPointerEnd = () => { holding = false; };\n const pointerEnds = ['pointerup', 'pointercancel', 'lostpointercapture'] as const;\n\n const teardown = () => {\n disposed = true;\n unsubscribeFrame();\n input.removeEventListener('pointermove', onPointerMove);\n input.removeEventListener('pointerdown', onPointerDown);\n for (const type of pointerEnds) input.removeEventListener(type, onPointerEnd);\n lens?.remove();\n lens = null;\n pixels?.close();\n pixels = null;\n };\n\n input.addEventListener('pointermove', onPointerMove);\n input.addEventListener('pointerdown', onPointerDown);\n for (const type of pointerEnds) input.addEventListener(type, onPointerEnd);\n\n return {\n window: win,\n get mode() { return model.mode; },\n get factor() { return model.factor; },\n get aim() { return model.aim; },\n get color() { return model.color; },\n setMode: model.setMode,\n setFactor: model.setFactor,\n aimAt: model.aimAt,\n pick: model.pick,\n dispose() {\n model.dispose();\n win.dispose();\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["loupeInnerView","registerDefaultFont","cursorForZone","isFocusable","DEFAULT_FONT_FAMILY","DEFAULT_WIDGET_CLAIMS","createHudContribution","attachHud","DEFAULT_WINDOW_METRICS","HUD_AFFORDANCE_KIND","loupeInnerView$1","readbackRegion","cx","target","pointer","dpr","cy","gx","clamp","rw","gy","bufferHeight","rh","raw","gl","flipped","stride","row","src","v","lo","hi","NO_RECT","createLoupe","element","opts","host","interactive","input","lens","holding","pixels","pixelsPending","refreshWanted","disposed","landed","b","content","model","drawVector","ctx","drawPixels","backdropColor","backdrop","innerView","world","screenToWorld","viewToTransform","outer","rect","createViewportLayer","win","out","scheduleRefresh","read","cssRect","r","at","refreshPixels","data","bmp","requestRedraw","readHex","px","c","hud","p","surface","unsubscribeFrame","createLoupeModel","teardown","local","evt","onPointerMove","onPointerDown","onPointerEnd","pointerEnds","type","claimsOf","createHud","loupeInnerView"],"sources":["../src/loupe/readback.ts","../src/loupe/createLoupe.ts"],"sourcesContent":["/**\n * Read a `rw × rh` device-pixel region of the drawing buffer centered on\n * `pointer`, returned top-down. `pointer` is in CSS px relative to `target`,\n * the rect of the buffer the canvas paints into (device px, top-left origin),\n * and the read is clamped to stay inside that rect.\n *\n * GL reports rows bottom-up; every consumer here wants top-down, so both the\n * read origin and the returned rows are flipped.\n */\nexport function readbackRegion(\n gl: WebGL2RenderingContext,\n bufferHeight: number,\n target: { x: number; y: number; width: number; height: number },\n pointer: { x: number; y: number },\n dpr: number,\n rw: number,\n rh: number,\n): ImageData {\n const cx = target.x + Math.round(pointer.x * dpr);\n const cy = target.y + Math.round(pointer.y * dpr);\n const gx = clamp(cx - Math.floor(rw / 2), target.x, target.x + Math.max(0, target.width - rw));\n const gyTop = clamp(cy - Math.floor(rh / 2), target.y, target.y + Math.max(0, target.height - rh));\n const gy = bufferHeight - gyTop - rh;\n\n const raw = new Uint8Array(rw * rh * 4);\n gl.readPixels(gx, gy, rw, rh, gl.RGBA, gl.UNSIGNED_BYTE, raw);\n\n const flipped = new Uint8ClampedArray(rw * rh * 4);\n const stride = rw * 4;\n for (let row = 0; row < rh; row++) {\n const src = (rh - 1 - row) * stride;\n flipped.set(raw.subarray(src, src + stride), row * stride);\n }\n return new ImageData(flipped, rw, rh);\n}\n\nfunction clamp(v: number, lo: number, hi: number): number {\n return v < lo ? lo : v > hi ? hi : v;\n}\n","import {\n createViewportLayer, screenToWorld, viewToTransform,\n type CanvasViewHandle, type RenderLayer, type SceneCanvasApi, type View,\n} from '@weasel-js/core';\nimport type { DrawCommand } from '@weasel-js/core/renderer';\nimport {\n createLoupeModel, loupeInnerView,\n type LoupeMode, type LoupeModel, type LoupeSurface,\n} from '@weasel-js/loupe';\nimport type { Hud } from '../hud';\nimport type { WindowWidget } from '../widgets/window/window';\nimport type { HudContentCtx, WidgetBounds } from '../widget';\nimport { readbackRegion } from './readback';\n\nexport type { LoupeMode };\n\n/** Options for `createLoupe` shared by a plain lens and an interactive one. */\nexport interface LoupeBaseOptions {\n hud: Hud;\n /** The canvas pixel mode reads back from. Supplies the GL context and the\n * drawing-buffer dimensions. */\n canvas: HTMLCanvasElement;\n /** Where the aim comes from: the element pointer events are taken from and\n * the rect client coords are measured against. Defaults to `canvas`, which\n * is right whenever the canvas is also the thing under the pointer. */\n input?: HTMLElement;\n /** The rect of `canvas` the magnified picture occupies, in its CSS px — the\n * one the aim is relative to. Pass `CanvasExtensionApi.getSurfaceRect` for\n * a pane painted into a shared canvas. Defaults to `views.getSurfaceRect`\n * when the lens is a view, and to the whole canvas otherwise. */\n region?: () => { x: number; y: number; width: number; height: number };\n requestRedraw: () => void;\n mode?: LoupeMode;\n factor?: number;\n bounds?: WidgetBounds;\n title?: string;\n /** Draw the window's titlebar and close box. Default `true`. Pass `false`\n * for a bare lens; the interior then doubles as the move handle, or, for an\n * `interactive` lens, a grip strip across its top does. */\n titlebar?: boolean;\n /** Called with the hex color under the aim point whenever it changes.\n * Sampled from the framebuffer in both modes — vector content is a\n * re-render whose edge colors differ from the screen's, so reading the\n * color off it would report something the user cannot see. */\n onColorChange?: (hex: string) => void;\n /** Fired when a click inside the lens picks a color — an eyedropper on the\n * magnified surface. Where the color goes is the consumer's: the loupe\n * reports the pick and nothing else. An `interactive` lens gives its clicks\n * to the canvas, so only `pick()` reaches this. */\n onPick?: (hex: string) => void;\n /** Fired by the window's close box. The window does not hide itself —\n * whoever owns the loupe's visibility decides what closing means. */\n onClose?: () => void;\n /** Backdrop painted behind the magnified content in both modes, so the\n * outer canvas does not show through where the lens is empty. Defaults to\n * the theme's surface color. */\n background?: string;\n}\n\n/** Options for `createLoupe`. */\nexport type LoupeOptions = LoupeBaseOptions & (\n | {\n /** Layers re-rendered through the magnified inner view in vector mode. */\n source: RenderLayer<unknown>[];\n views?: undefined;\n interactive?: false;\n }\n | {\n /**\n * The canvas, as a host for views. The lens becomes a view on it: vector\n * mode paints the canvas's own stack — scene and selection chrome — and\n * the lens can take input. It magnifies the canvas's own camera; aimed\n * over another view, it shows the canvas's world, not that view's.\n */\n views: Pick<SceneCanvasApi, 'addView' | 'getSurfaceRect'>;\n /** Narrows what vector mode paints. Defaults to the canvas's stack. */\n source?: RenderLayer<unknown>[];\n /**\n * Route input inside the lens to what it shows: a press selects the node\n * magnified under the pointer, and a drag moves it by the pointer travel\n * divided by the magnification. Default `false` — the window keeps its\n * interior, and a click there picks a color.\n */\n interactive?: boolean;\n }\n);\n\n/** Where an interactive lens routes while its window is hidden: nowhere. */\nconst NO_RECT = { x: 0, y: 0, w: 0, h: 0 };\n\n/** Control surface for a live loupe: where it is aimed, how it magnifies,\n * what color it is over, and the window it lives in. */\nexport interface LoupeHandle {\n readonly window: WindowWidget;\n readonly mode: LoupeMode;\n readonly factor: number;\n /** Last aimed point, in screen-space CSS px relative to the canvas. */\n readonly aim: { x: number; y: number };\n /** Hex color under the aim point, read off the framebuffer on the first\n * frame to land after the aim. `null` until then, or when the GL context\n * is unavailable. */\n readonly color: string | null;\n setMode(mode: LoupeMode): void;\n setFactor(factor: number): void;\n /** Aim at a screen point. Ignored while the point is over the window —\n * that is the freeze rule that makes the borders reachable. */\n aimAt(p: { x: number; y: number }): void;\n /** Sample the color the lens is showing at `p` — a screen point inside the\n * lens — and report it to `onPick`. Omit `p` to pick at the aim point.\n * Returns the hex, or `null` when the framebuffer cannot answer for that\n * point, including before any frame has landed. Leaves the aim, and\n * `color`, alone. */\n pick(p?: { x: number; y: number }): string | null;\n dispose(): void;\n}\n\n/**\n * Build a magnifier: a HUD window whose interior shows the canvas around an\n * aim point, enlarged. Adds the window to the HUD and starts following the\n * pointer; `dispose` removes it again.\n */\nexport function createLoupe(opts: LoupeOptions): LoupeHandle {\n const { hud, canvas: element, requestRedraw } = opts;\n const host = opts.views;\n const interactive = host !== undefined && opts.interactive === true;\n const input = opts.input ?? element;\n let lens: CanvasViewHandle | null = null;\n // Set while a gesture that began over the window is held: the lens camera\n // follows the aim, so re-aiming mid-drag would move the world under it.\n let holding = false;\n let pixels: ImageBitmap | null = null;\n let pixelsPending = false;\n let refreshWanted = false;\n let disposed = false;\n // Until a paint lands, the context on a shared canvas may not exist yet, and\n // opening it here would give every tenant one with no stencil buffer.\n let landed = false;\n\n const b = opts.bounds ?? { x: 24, y: 24, w: 220, h: 200 };\n\n const content = (ctx: HudContentCtx): DrawCommand[] =>\n model.mode === 'vector' ? drawVector(ctx) : drawPixels(ctx);\n\n const backdropColor = (ctx: HudContentCtx): string =>\n opts.background ?? ctx.tokens['--wzl-surface'];\n\n const backdrop = (ctx: HudContentCtx): DrawCommand => ({\n kind: 'path',\n path: { kind: 'rect', x: ctx.rect.x, y: ctx.rect.y, width: ctx.rect.w, height: ctx.rect.h },\n fill: { fill: 'solid', color: backdropColor(ctx) },\n });\n\n const innerView = (outer: View, rect: WidgetBounds): View => {\n const world = screenToWorld(model.aim.x, model.aim.y, viewToTransform(outer));\n return loupeInnerView({ x: world[0], y: world[1] }, outer, rect, model.factor);\n };\n\n const drawVector = (ctx: HudContentCtx): DrawCommand[] => {\n if (host) return lens ? [backdrop(ctx), ...lens.draw(ctx.data, ctx.view, ctx.dims)] : [backdrop(ctx)];\n // A fresh lens per frame: CreateViewportLayerOpts.view is static, and the\n // inner view moves with the pointer.\n const picture = createViewportLayer<unknown>({\n id: `${win.id}:lens`,\n label: 'Loupe content',\n source: opts.source ?? [],\n view: innerView(ctx.view, ctx.rect),\n bounds: () => ctx.rect,\n background: backdropColor(ctx),\n });\n return picture.draw(ctx.data, ctx.view, ctx.dims);\n };\n\n const drawPixels = (ctx: HudContentCtx): DrawCommand[] => {\n // The backdrop paints even with a bitmap in hand: a readback of a\n // transparent framebuffer is transparent, and before the first one\n // settles there is nothing at all, either of which leaves the lens a\n // hole onto the unmagnified canvas.\n const out: DrawCommand[] = [backdrop(ctx)];\n if (pixels) {\n out.push({\n kind: 'image',\n image: pixels,\n x: ctx.rect.x, y: ctx.rect.y, w: ctx.rect.w, h: ctx.rect.h,\n sampling: 'nearest',\n });\n }\n return out;\n };\n\n // `gl.readPixels` outside a landed paint returns the *previous* frame, so an\n // aim only marks the lens dirty; the read itself happens in the frame\n // subscriber below, on the frame that painted.\n const scheduleRefresh = () => {\n if (disposed || model.mode !== 'pixel') return;\n refreshWanted = true;\n };\n\n /** Read device px of the loupe's region around `at`, sized for the\n * canvas's pixel ratio, or `null` when the framebuffer cannot answer. */\n const read = (\n at: { x: number; y: number },\n rw: (dpr: number) => number,\n rh: (dpr: number) => number,\n ): ImageData | null => {\n if (!landed) return null;\n const gl = element.getContext('webgl2');\n if (!gl) return null;\n const cssRect = element.getBoundingClientRect();\n if (cssRect.width === 0) return null;\n const dpr = element.width / cssRect.width;\n // A view-backed lens knows its canvas, so over a detached pane it reads\n // that pane's pixels without being told where the pane is.\n const r = (opts.region ?? host?.getSurfaceRect)?.();\n const target = r\n ? {\n x: Math.round(r.x * dpr), y: Math.round(r.y * dpr),\n width: Math.round(r.width * dpr), height: Math.round(r.height * dpr),\n }\n : { x: 0, y: 0, width: element.width, height: element.height };\n return readbackRegion(gl, element.height, target, at, dpr, rw(dpr), rh(dpr));\n };\n\n const refreshPixels = () => {\n if (disposed || model.mode !== 'pixel') return;\n const rect = win.contentRect;\n const data = read(\n model.aim,\n (dpr) => Math.max(1, Math.round((rect.w * dpr) / model.factor)),\n (dpr) => Math.max(1, Math.round((rect.h * dpr) / model.factor)),\n );\n if (!data) return;\n pixelsPending = true;\n createImageBitmap(data)\n .then((bmp) => {\n if (disposed) { bmp.close(); return; }\n pixels?.close();\n pixels = bmp;\n pixelsPending = false;\n requestRedraw();\n })\n .catch(() => {\n pixelsPending = false;\n requestRedraw();\n });\n };\n\n const readHex = (at: { x: number; y: number }): string | null => {\n const px = read(at, () => 1, () => 1);\n if (!px) return null;\n return '#' + [px.data[0], px.data[1], px.data[2]]\n .map((c) => c.toString(16).padStart(2, '0')).join('');\n };\n\n const win = hud.window({\n id: 'weasel-loupe',\n x: b.x, y: b.y, w: b.w, h: b.h,\n title: opts.title ?? 'Loupe',\n titlebar: opts.titlebar,\n content,\n interior: interactive ? 'pass' : 'claim',\n onResize: () => { scheduleRefresh(); },\n ...(interactive ? {} : { onContentClick: (p: { x: number; y: number }) => { model.pick(p); } }),\n ...(opts.onClose ? { onClose: opts.onClose } : {}),\n });\n\n const surface: LoupeSurface = {\n lens: () => win.contentRect,\n covers: (p) => win.hitTest(p.x, p.y) || (win.passes?.(p.x, p.y) ?? false),\n sample: readHex,\n hidden: () => win.hidden,\n gone: () => win.disposed === true,\n changed: () => { scheduleRefresh(); requestRedraw(); },\n subscribeFrame: hud.subscribeFrame,\n };\n\n // Subscribed ahead of the model, so the frame that lands first is already\n // readable by the time the model samples on it.\n const unsubscribeFrame = hud.subscribeFrame(() => {\n landed = true;\n // A refresh wanted while a bitmap is in flight is remembered rather than\n // dropped: the flag survives until a frame finds it settled, so the aim\n // the user ends a fast drag on is the one that gets read.\n if (!refreshWanted || pixelsPending) return;\n refreshWanted = false;\n refreshPixels();\n });\n\n const model: LoupeModel = createLoupeModel({\n surface,\n ...(opts.mode ? { mode: opts.mode } : {}),\n ...(opts.factor !== undefined ? { factor: opts.factor } : {}),\n ...(opts.onColorChange ? { onColorChange: opts.onColorChange } : {}),\n ...(opts.onPick ? { onPick: opts.onPick } : {}),\n onDispose: () => teardown(),\n });\n\n if (host) {\n lens = host.addView({\n id: `${win.id}:lens`,\n label: 'Loupe',\n bounds: () => (win.hidden ? NO_RECT : win.contentRect),\n view: (outer) => innerView(outer, win.contentRect),\n paint: false,\n interactive,\n ...(opts.source ? { layers: () => opts.source! } : {}),\n });\n }\n\n const local = (evt: PointerEvent): { x: number; y: number } => {\n const r = input.getBoundingClientRect();\n return { x: evt.clientX - r.left, y: evt.clientY - r.top };\n };\n\n const onPointerMove = (evt: PointerEvent) => {\n if (holding) return;\n model.aimAt(local(evt));\n };\n const onPointerDown = (evt: PointerEvent) => {\n if (interactive && surface.covers(local(evt))) holding = true;\n };\n const onPointerEnd = () => { holding = false; };\n const pointerEnds = ['pointerup', 'pointercancel', 'lostpointercapture'] as const;\n\n const teardown = () => {\n disposed = true;\n unsubscribeFrame();\n input.removeEventListener('pointermove', onPointerMove);\n input.removeEventListener('pointerdown', onPointerDown);\n for (const type of pointerEnds) input.removeEventListener(type, onPointerEnd);\n lens?.remove();\n lens = null;\n pixels?.close();\n pixels = null;\n };\n\n input.addEventListener('pointermove', onPointerMove);\n input.addEventListener('pointerdown', onPointerDown);\n for (const type of pointerEnds) input.addEventListener(type, onPointerEnd);\n\n return {\n window: win,\n get mode() { return model.mode; },\n get factor() { return model.factor; },\n get aim() { return model.aim; },\n get color() { return model.color; },\n setMode: model.setMode,\n setFactor: model.setFactor,\n aimAt: model.aimAt,\n pick: model.pick,\n dispose() {\n model.dispose();\n win.dispose();\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -30,7 +30,8 @@ export interface LoupeBaseOptions {
|
|
|
30
30
|
bounds?: WidgetBounds;
|
|
31
31
|
title?: string;
|
|
32
32
|
/** Draw the window's titlebar and close box. Default `true`. Pass `false`
|
|
33
|
-
* for a bare lens; the interior then doubles as the move handle
|
|
33
|
+
* for a bare lens; the interior then doubles as the move handle, or, for an
|
|
34
|
+
* `interactive` lens, a grip strip across its top does. */
|
|
34
35
|
titlebar?: boolean;
|
|
35
36
|
/** Called with the hex color under the aim point whenever it changes.
|
|
36
37
|
* Sampled from the framebuffer in both modes — vector content is a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createLoupe.d.ts","sourceRoot":"","sources":["../../src/loupe/createLoupe.ts"],"names":[],"mappings":"AAAA,OAAO,EAEkB,KAAK,WAAW,EAAE,KAAK,cAAc,EAC7D,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAEL,KAAK,SAAS,EACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAClC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,WAAW,CAAC;AAG7D,YAAY,EAAE,SAAS,EAAE,CAAC;AAE1B,+EAA+E;AAC/E,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,GAAG,CAAC;IACT;qCACiC;IACjC,MAAM,EAAE,iBAAiB,CAAC;IAC1B;;4EAEwE;IACxE,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB;;;sEAGkE;IAClE,MAAM,CAAC,EAAE,MAAM;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACvE,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;
|
|
1
|
+
{"version":3,"file":"createLoupe.d.ts","sourceRoot":"","sources":["../../src/loupe/createLoupe.ts"],"names":[],"mappings":"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"}
|
package/dist/react/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as e, o as t, r as n } from "../tool-DtH159Hp.js";
|
|
2
2
|
import { resolveTheme as r, weaselTheme as i } from "@weasel-js/theme";
|
|
3
3
|
import { useEffect as a, useMemo as o, useState as s } from "react";
|
|
4
4
|
import { useThemeOptional as c } from "@weasel-js/theme/react";
|
|
5
5
|
//#region src/react/useHud.ts
|
|
6
|
-
function l(
|
|
7
|
-
let [i] = s(() =>
|
|
6
|
+
function l(e, r = {}) {
|
|
7
|
+
let [i] = s(() => t()), o = u(r.font);
|
|
8
8
|
return a(() => {
|
|
9
|
-
let
|
|
10
|
-
if (!
|
|
11
|
-
let r = n(
|
|
12
|
-
return
|
|
9
|
+
let t = e?.current;
|
|
10
|
+
if (!t) return;
|
|
11
|
+
let r = n(t, i, o);
|
|
12
|
+
return t.requestRedraw(), r;
|
|
13
13
|
}, [i, o]), i;
|
|
14
14
|
}
|
|
15
15
|
function u(e) {
|
|
@@ -20,9 +20,9 @@ function u(e) {
|
|
|
20
20
|
...e === void 0 ? {} : { font: e }
|
|
21
21
|
}), [t, e]);
|
|
22
22
|
}
|
|
23
|
-
function d(
|
|
23
|
+
function d(t, n = {}) {
|
|
24
24
|
let r = u(n.font);
|
|
25
|
-
return o(() => t
|
|
25
|
+
return o(() => e(t, r), [t, r]);
|
|
26
26
|
}
|
|
27
27
|
//#endregion
|
|
28
28
|
export { l as useHud, d as useHudContribution };
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["
|
|
1
|
+
{"version":3,"file":"index.js","names":["createHudContribution","createHud","attachHud","useHud","hud","useState","attachOptions","useAttachOptions","options","useEffect","api","canvasRef","detach","provided","useThemeOptional","useMemo","resolveTheme","weaselTheme","font","useHudContribution"],"sources":["../../src/react/useHud.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { useThemeOptional } from '@weasel-js/theme/react';\nimport { resolveTheme, weaselTheme } from '@weasel-js/theme';\nimport { createHud, type Hud } from '../hud';\nimport { attachHud, type AttachHudOptions } from '../attach';\nimport type { CanvasExtensionApi, SurfaceContribution } from '@weasel-js/core';\nimport { createHudContribution } from '../tool';\n\n/**\n * Create a HUD, and — given the canvas's ref handle — attach it to that\n * canvas. Returns the Hud immediately (unbound); attaches in an effect after\n * the canvas ref populates. Cleans up on unmount.\n *\n * Omit the ref when the HUD installs through {@link useHudContribution}\n * instead, which attaches it as part of the canvas's `ambient` entry.\n *\n * An app that has already registered a bitmap family should say so —\n * `useHud(ref, { font: 'sans-serif' })` — or the HUD fetches its own copy of\n * the same atlas.\n */\nexport function useHud(\n canvasRef?: { current: CanvasExtensionApi | null },\n options: Pick<AttachHudOptions, 'font'> = {},\n): Hud {\n const [hud] = useState(() => createHud());\n const attachOptions = useAttachOptions(options.font);\n\n useEffect(() => {\n const api = canvasRef?.current;\n if (!api) return;\n const detach = attachHud(api, hud, attachOptions);\n api.requestRedraw();\n return detach;\n // canvasRef.current changing during component lifetime is unusual for\n // canvas refs; treat as effectively-stable in v1. The dep on `hud` is\n // also stable (it comes from useState's initializer, never changes).\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hud, attachOptions]);\n\n return hud;\n}\n\n/** The attach options that follow the app's theme. Widgets are drawn into the\n * canvas, so they can't inherit the cascade — handing them the resolved\n * record is what keeps HUD chrome in step with a mode switch. */\nfunction useAttachOptions(font: AttachHudOptions['font']): AttachHudOptions {\n const provided = useThemeOptional();\n return useMemo(() => ({\n theme: provided?.resolved ?? resolveTheme(weaselTheme),\n ...(provided ? { tones: provided } : {}),\n ...(font !== undefined ? { font } : {}),\n }), [provided, font]);\n}\n\n/**\n * The HUD's canvas entry — see {@link createHudContribution} for how the\n * routing works. Given a HUD, the entry also attaches it, so one `ambient`\n * entry installs the whole HUD:\n *\n * ```tsx\n * const hud = useHud();\n * const hudEntry = useHudContribution(hud, { font: 'sans-serif' });\n * <SceneCanvas ambient={[hudEntry]} … />\n * ```\n *\n * Without one it is input routing only, stateless (its actions resolve the hit\n * widget from the gesture's affordance payload), and one instance serves any\n * number of HUDs attached some other way.\n */\nexport function useHudContribution(\n hud?: Hud,\n options: Pick<AttachHudOptions, 'font'> = {},\n): SurfaceContribution {\n const attachOptions = useAttachOptions(options.font);\n return useMemo(\n () => createHudContribution(hud, attachOptions),\n [hud, attachOptions],\n );\n}\n"],"mappings":";;;;;AAoBA,SAAgBG,EACd,GACA,IAA0C,CAAC,GACtC;CACL,IAAM,CAACC,KAAOC,QAAeJ,EAAU,CAAC,GAClCK,IAAgBC,EAAiBC,EAAQ,IAAI;CAcnD,OAZAC,QAAgB;EACd,IAAMC,IAAMC,GAAW;EACvB,IAAI,CAACD,GAAK;EACV,IAAME,IAASV,EAAUQ,GAAKN,GAAKE,CAAa;EAEhD,OADAI,EAAI,cAAc,GACXE;CAKT,GAAG,CAACR,GAAKE,CAAa,CAAC,GAEhBF;AACT;AAKA,SAASG,EAAiB,GAAkD;CAC1E,IAAMM,IAAWC,EAAiB;CAClC,OAAOC,SAAe;EACpB,OAAOF,GAAU,YAAYG,EAAaC,CAAW;EACrD,GAAIJ,IAAW,EAAE,OAAOA,EAAS,IAAI,CAAC;EACtC,GAAIK,MAAS,KAAA,IAAuB,CAAC,IAAZ,EAAE,QAAK;CAClC,IAAI,CAACL,GAAUK,CAAI,CAAC;AACtB;AAiBA,SAAgBC,EACd,GACA,IAA0C,CAAC,GACtB;CACrB,IAAMb,IAAgBC,EAAiBC,EAAQ,IAAI;CACnD,OAAOO,QACCf,EAAsBI,GAAKE,CAAa,GAC9C,CAACF,GAAKE,CAAa,CACrB;AACF"}
|