@weasel-js/hud 1.9.4 → 1.10.0

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 CHANGED
@@ -24,7 +24,7 @@ import { /* … */ } from '@weasel-js/hud/react';
24
24
  `rect`, `text`, `image`, `label`, `button`, and `window` — a draggable,
25
25
  resizable frame whose interior is painted by an opt-in `content` callback. See
26
26
  [`src/widgets/window`](src/widgets/window/README.md) for how content composes
27
- with the frame, and `createLoupe` in [`src/loupe`](src/loupe/createLoupe.ts) for the first
27
+ with the frame, and `createLoupe` in [`src/loupe`](src/loupe/createLoupe.ts) for a
28
28
  consumer.
29
29
 
30
30
  Widgets draw from a data-free context (`{ dims, defaultFont, tokens }`), which
package/dist/attach.d.ts CHANGED
@@ -1,24 +1,21 @@
1
1
  import type { Hud } from './hud';
2
2
  import type { CanvasExtensionApi } from '@weasel-js/core';
3
3
  import { type FontAtlasUrls } from './fonts/registerDefaultFont';
4
- import { type ColorContext, type ColorList, type ResolvedTheme } from '@weasel-js/theme';
4
+ import { type ColorSourceContext, type ResolvedTheme } from '@weasel-js/theme';
5
+ /** Options for `attachHud`. */
5
6
  export interface AttachHudOptions {
6
7
  /** Resolved theme the widgets draw with. Defaults to the built-in theme's
7
8
  * default mode; pass `useTheme().resolved` to follow a live theme. */
8
9
  readonly theme?: ResolvedTheme;
9
- /** The tone list a widget's numeric `tone` indexes, and the theme it
10
- * resolves against — `useTheme()`'s value is one. Defaults to the built-in
11
- * theme's list, resolved against `theme`. */
12
- readonly tones?: ColorContext & {
13
- readonly tones: ColorList;
14
- };
10
+ /** What a widget's color sources resolve against: a theme, the tone list a
11
+ * numeric `tone` indexes, and a palette — `useTheme()`'s value is one.
12
+ * Defaults to the built-in theme and its list, resolved against `theme`. */
13
+ readonly tones?: ColorSourceContext;
15
14
  /**
16
15
  * Where the text a widget draws without naming a family comes from.
17
16
  *
18
17
  * - A family name: that family, already registered by the host. The HUD
19
- * fetches nothing, which is the point — an app that has registered Inter
20
- * as `'sans-serif'` was downloading the HUD's byte-identical copy of it a
21
- * second time.
18
+ * fetches nothing.
22
19
  * - A `{ metricsUrl, atlasUrl }` pair: the HUD's own family, registered from
23
20
  * the host's copy of the atlas rather than the bundled one.
24
21
  *
@@ -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;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"}
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,EAA6B,KAAK,kBAAkB,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAG1G,+BAA+B;AAC/B,MAAM,WAAW,gBAAgB;IAC/B;2EACuE;IACvE,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;IAC/B;;iFAE6E;IAC7E,QAAQ,CAAC,KAAK,CAAC,EAAE,kBAAkB,CAAC;IACpC;;;;;;;;;OASG;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,CA6JZ"}
@@ -0,0 +1,9 @@
1
+ import { type ColorSource } from '@weasel-js/paint';
2
+ import { type ColorSourceContext } from '@weasel-js/theme';
3
+ /**
4
+ * What a draw context's `color` is built from: a source resolved against
5
+ * `ctx` to a hex a canvas can draw. `undefined` for a source nothing answers,
6
+ * and for a custom property only a cascade could resolve.
7
+ */
8
+ export declare function hudColor(ctx: ColorSourceContext): (source: ColorSource) => string | undefined;
9
+ //# sourceMappingURL=colors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../src/colors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAsB,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE/E;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,kBAAkB,GAAG,CAAC,MAAM,EAAE,WAAW,KAAK,MAAM,GAAG,SAAS,CAK7F"}
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,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"}
1
+ {"version":3,"file":"hud.d.ts","sourceRoot":"","sources":["../src/hud.ts"],"names":[],"mappings":"AACA,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,CA2L/B"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { createHud, type Hud, type HudFocusOptions } from './hud';
2
- export { attachHud } from './attach';
2
+ export { attachHud, type AttachHudOptions } from './attach';
3
3
  export { createHudContribution, HUD_AFFORDANCE_KIND, type HudHitPayload } from './tool';
4
4
  export type { Widget, WidgetBounds, HudDrawCtx, HudPointerEvent, HudKeyEvent, } from './widget';
5
5
  export { DEFAULT_WIDGET_CLAIMS, claimsOf, isFocusable } from './widget';
@@ -8,13 +8,13 @@ export { DEFAULT_FONT_FAMILY, registerDefaultFont } from './fonts/registerDefaul
8
8
  export type { FontAtlasUrls } from './fonts/registerDefaultFont';
9
9
  export type { RectOptions, RectWidget } from './widgets/rect';
10
10
  export type { TextOptions, TextWidget } from './widgets/text';
11
- export type { ImageOptions, ImageWidget } from './widgets/image';
11
+ export type { ImageOptions, ImageWidget, SourceRect } from './widgets/image';
12
12
  export type { LabelOptions, LabelWidget } from './widgets/label';
13
13
  export type { ButtonOptions, ButtonWidget, ButtonEvent, ButtonHandler } from './widgets/button';
14
14
  export type { WindowOptions, WindowWidget } from './widgets/window/window';
15
15
  export { DEFAULT_WINDOW_METRICS, cursorForZone, type WindowZone, type WindowMetrics, } from './widgets/window/zones';
16
16
  export type { HudContentCtx } from './widget';
17
- export { createLoupe, type LoupeOptions, type LoupeHandle, type LoupeMode } from './loupe/createLoupe';
17
+ export { createLoupe, type LoupeOptions, type LoupeBaseOptions, type LoupeHandle, type LoupeMode } from './loupe/createLoupe';
18
18
  export { loupeInnerView } from '@weasel-js/loupe';
19
19
  export type { ResolvedTheme } from '@weasel-js/theme';
20
20
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
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"}
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,KAAK,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5D,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,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7E,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,gBAAgB,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAC9H,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js CHANGED
@@ -1,10 +1,55 @@
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-BLDCwvTF.js";
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-Dq_D9qpD.js";
2
2
  import { createViewportLayer as d, screenToWorld as f, viewToTransform as p } from "@weasel-js/core";
3
3
  import { clamp as m } from "@weasel-js/geom";
4
4
  import { releaseImageSource as h } from "@weasel-js/core/renderer";
5
5
  import { createLoupeModel as g, loupeInnerView as _, loupeInnerView as v } from "@weasel-js/loupe";
6
+ //#region src/loupe/contentWatch.ts
7
+ function y(e) {
8
+ let t = "", n = null;
9
+ return {
10
+ drew(e) {
11
+ t = `${e.x},${e.y},${e.scale.x},${e.scale.y}`;
12
+ },
13
+ changed() {
14
+ let r = {
15
+ version: e?.(),
16
+ camera: t
17
+ }, i = n !== null && (n.version !== r.version || n.camera !== r.camera);
18
+ return n = r, i;
19
+ }
20
+ };
21
+ }
22
+ //#endregion
23
+ //#region src/loupe/pointerAim.ts
24
+ var b = [
25
+ "pointerup",
26
+ "pointercancel",
27
+ "lostpointercapture"
28
+ ];
29
+ function x(e, t) {
30
+ let n = !1, r = (t) => {
31
+ let n = e.getBoundingClientRect();
32
+ return {
33
+ x: t.clientX - n.left,
34
+ y: t.clientY - n.top
35
+ };
36
+ }, i = (e) => {
37
+ n || t.aimAt(r(e));
38
+ }, a = (e) => {
39
+ t.holds(r(e)) && (n = !0);
40
+ }, o = () => {
41
+ n = !1;
42
+ };
43
+ e.addEventListener("pointermove", i), e.addEventListener("pointerdown", a);
44
+ for (let t of b) e.addEventListener(t, o);
45
+ return () => {
46
+ e.removeEventListener("pointermove", i), e.removeEventListener("pointerdown", a);
47
+ for (let t of b) e.removeEventListener(t, o);
48
+ };
49
+ }
50
+ //#endregion
6
51
  //#region src/loupe/readback.ts
7
- function y(e, t, n, r, i, a, o) {
52
+ function S(e, t, n, r, i, a, o) {
8
53
  let s = n.x + Math.round(r.x * i), c = n.y + Math.round(r.y * i), l = m(s - Math.floor(a / 2), n.x, n.x + Math.max(0, n.width - a)), u = t - m(c - Math.floor(o / 2), n.y, n.y + Math.max(0, n.height - o)) - o, d = new Uint8Array(a * o * 4);
9
54
  e.readPixels(l, u, a, o, e.RGBA, e.UNSIGNED_BYTE, d);
10
55
  let f = new Uint8ClampedArray(a * o * 4), p = a * 4;
@@ -16,61 +61,61 @@ function y(e, t, n, r, i, a, o) {
16
61
  }
17
62
  //#endregion
18
63
  //#region src/loupe/createLoupe.ts
19
- var b = {
64
+ var C = (e, t) => ({
65
+ kind: "path",
66
+ path: {
67
+ kind: "rect",
68
+ x: e.rect.x,
69
+ y: e.rect.y,
70
+ width: e.rect.w,
71
+ height: e.rect.h
72
+ },
73
+ fill: { paint: {
74
+ kind: "solid",
75
+ color: t
76
+ } }
77
+ }), w = {
20
78
  x: 0,
21
79
  y: 0,
22
80
  w: 0,
23
81
  h: 0
24
82
  };
25
- function x(e) {
26
- 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 = () => {
27
- l &&= (h(l), l.close(), null);
28
- }, m = !1, _ = !1, x = !1, S = !1, C = e.bounds ?? {
83
+ function T(e) {
84
+ 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 = null, l = () => {
85
+ c &&= (h(c), c.close(), null);
86
+ }, u = !1, m = !1, _ = !1, b = !1, T = e.bounds ?? {
29
87
  x: 24,
30
88
  y: 24,
31
89
  w: 220,
32
90
  h: 200
33
- }, w = (e) => L.mode === "vector" ? O(e) : k(e), T = (t) => e.background ?? t.tokens["--wzl-surface"], E = (e) => ({
34
- kind: "path",
35
- path: {
36
- kind: "rect",
37
- x: e.rect.x,
38
- y: e.rect.y,
39
- width: e.rect.w,
40
- height: e.rect.h
41
- },
42
- fill: {
43
- fill: "solid",
44
- color: T(e)
45
- }
46
- }), D = (e, t) => {
47
- let n = f(L.aim.x, L.aim.y, p(e));
91
+ }, E = y(e.contentVersion ?? i?.getPaintedVersion), D = !1, O = (e) => (E.drew(e.view), V.mode === "vector" ? M(e) : N(e)), k = (t) => e.background ?? t.tokens["--wzl-surface"], A = (e) => C(e, k(e)), j = (e, t) => {
92
+ let n = f(V.aim.x, V.aim.y, p(e));
48
93
  return v({
49
94
  x: n[0],
50
95
  y: n[1]
51
- }, e, t, L.factor);
52
- }, O = (t) => i ? s ? [E(t), ...s.draw(t.data, t.view, t.dims)] : [E(t)] : d({
53
- id: `${P.id}:lens`,
96
+ }, e, t, V.factor);
97
+ }, M = (t) => i ? s ? [A(t), ...s.draw(t.data, t.view, t.dims)] : [A(t)] : d({
98
+ id: `${R.id}:lens`,
54
99
  label: "Loupe content",
55
100
  source: e.source ?? [],
56
- view: D(t.view, t.rect),
101
+ view: j(t.view, t.rect),
57
102
  bounds: () => t.rect,
58
- background: T(t)
59
- }).draw(t.data, t.view, t.dims), k = (e) => {
60
- let t = [E(e)];
61
- return l && t.push({
103
+ background: k(t)
104
+ }).draw(t.data, t.view, t.dims), N = (e) => {
105
+ let t = [A(e)];
106
+ return c && t.push({
62
107
  kind: "image",
63
- image: l,
108
+ image: c,
64
109
  x: e.rect.x,
65
110
  y: e.rect.y,
66
111
  w: e.rect.w,
67
112
  h: e.rect.h,
68
113
  sampling: "nearest"
69
114
  }), t;
70
- }, A = () => {
71
- x || L.mode !== "pixel" || (_ = !0);
72
- }, j = (t, r, a) => {
73
- if (!S) return null;
115
+ }, P = () => {
116
+ _ || V.mode !== "pixel" || (m = !0);
117
+ }, F = (t, r, a) => {
118
+ if (!b) return null;
74
119
  let o = n.getContext("webgl2");
75
120
  if (!o) return null;
76
121
  let s = n.getBoundingClientRect();
@@ -86,57 +131,57 @@ function x(e) {
86
131
  width: n.width,
87
132
  height: n.height
88
133
  };
89
- return y(o, n.height, u, t, c, r(c), a(c));
90
- }, M = () => {
91
- if (x || L.mode !== "pixel") return;
92
- let e = P.contentRect, t = j(L.aim, (t) => Math.max(1, Math.round(e.w * t / L.factor)), (t) => Math.max(1, Math.round(e.h * t / L.factor)));
93
- t && (m = !0, createImageBitmap(t).then((e) => {
94
- if (x) {
134
+ return S(o, n.height, u, t, c, r(c), a(c));
135
+ }, I = () => {
136
+ if (_ || V.mode !== "pixel") return;
137
+ let e = R.contentRect, t = F(V.aim, (t) => Math.max(1, Math.round(e.w * t / V.factor)), (t) => Math.max(1, Math.round(e.h * t / V.factor)));
138
+ t && (u = !0, createImageBitmap(t).then((e) => {
139
+ if (_) {
95
140
  e.close();
96
141
  return;
97
142
  }
98
- u(), l = e, m = !1, r();
143
+ l(), c = e, u = !1, r();
99
144
  }).catch(() => {
100
- m = !1, r();
145
+ u = !1, r();
101
146
  }));
102
- }, N = (e) => {
103
- let t = j(e, () => 1, () => 1);
147
+ }, L = (e) => {
148
+ let t = F(e, () => 1, () => 1);
104
149
  return !t || t.data[3] === 0 ? null : "#" + [
105
150
  t.data[0],
106
151
  t.data[1],
107
152
  t.data[2]
108
153
  ].map((e) => e.toString(16).padStart(2, "0")).join("");
109
- }, P = t.window({
154
+ }, R = t.window({
110
155
  id: "weasel-loupe",
111
- x: C.x,
112
- y: C.y,
113
- w: C.w,
114
- h: C.h,
156
+ x: T.x,
157
+ y: T.y,
158
+ w: T.w,
159
+ h: T.h,
115
160
  title: e.title ?? "Loupe",
116
161
  titlebar: e.titlebar,
117
- content: w,
162
+ content: O,
118
163
  interior: a ? "pass" : "claim",
119
164
  onResize: () => {
120
- A();
165
+ P();
121
166
  },
122
167
  ...a ? {} : { onContentClick: (e) => {
123
- L.pick(e);
168
+ V.pick(e);
124
169
  } },
125
170
  ...e.onClose ? { onClose: e.onClose } : {}
126
- }), F = {
127
- lens: () => P.contentRect,
128
- covers: (e) => P.hitTest(e.x, e.y) || (P.passes?.(e.x, e.y) ?? !1),
129
- sample: N,
130
- hidden: () => P.hidden,
131
- gone: () => P.disposed === !0,
171
+ }), z = {
172
+ lens: () => R.contentRect,
173
+ covers: (e) => R.hitTest(e.x, e.y) || (R.passes?.(e.x, e.y) ?? !1),
174
+ sample: L,
175
+ hidden: () => R.hidden,
176
+ gone: () => R.disposed === !0,
132
177
  changed: () => {
133
- A(), r();
178
+ P(), r();
134
179
  },
135
180
  subscribeFrame: t.subscribeFrame
136
- }, I = t.subscribeFrame(() => {
137
- S = !0, _ && !m && (_ = !1, M());
138
- }), L = g({
139
- surface: F,
181
+ }, B = t.subscribeFrame(() => {
182
+ b = !0, (E.changed() || D) && (D = !1, P(), V.resample()), m && !u && (m = !1, I());
183
+ }), V = g({
184
+ surface: z,
140
185
  ...e.mode ? { mode: e.mode } : {},
141
186
  ...e.factor === void 0 ? {} : { factor: e.factor },
142
187
  ...e.onColorChange ? { onColorChange: e.onColorChange } : {},
@@ -144,61 +189,47 @@ function x(e) {
144
189
  onDispose: () => U()
145
190
  });
146
191
  i && (s = i.addView({
147
- id: `${P.id}:lens`,
192
+ id: `${R.id}:lens`,
148
193
  label: "Loupe",
149
- bounds: () => P.hidden ? b : P.contentRect,
150
- view: (e) => D(e, P.contentRect),
194
+ bounds: () => R.hidden ? w : R.contentRect,
195
+ view: (e) => j(e, R.contentRect),
151
196
  paint: !1,
152
197
  interactive: a,
153
198
  ...e.source ? { layers: () => e.source } : {}
154
199
  }));
155
- let R = (e) => {
156
- let t = o.getBoundingClientRect();
157
- return {
158
- x: e.clientX - t.left,
159
- y: e.clientY - t.top
160
- };
161
- }, z = (e) => {
162
- c || L.aimAt(R(e));
163
- }, B = (e) => {
164
- a && F.covers(R(e)) && (c = !0);
165
- }, V = () => {
166
- c = !1;
167
- }, H = [
168
- "pointerup",
169
- "pointercancel",
170
- "lostpointercapture"
171
- ], U = () => {
172
- x = !0, I(), o.removeEventListener("pointermove", z), o.removeEventListener("pointerdown", B);
173
- for (let e of H) o.removeEventListener(e, V);
174
- s?.remove(), s = null, u();
200
+ let H = x(o, {
201
+ aimAt: (e) => V.aimAt(e),
202
+ holds: (e) => a && z.covers(e)
203
+ }), U = () => {
204
+ _ = !0, B(), H(), s?.remove(), s = null, l();
175
205
  };
176
- o.addEventListener("pointermove", z), o.addEventListener("pointerdown", B);
177
- for (let e of H) o.addEventListener(e, V);
178
206
  return {
179
- window: P,
207
+ window: R,
180
208
  get mode() {
181
- return L.mode;
209
+ return V.mode;
182
210
  },
183
211
  get factor() {
184
- return L.factor;
212
+ return V.factor;
185
213
  },
186
214
  get aim() {
187
- return L.aim;
215
+ return V.aim;
188
216
  },
189
217
  get color() {
190
- return L.color;
218
+ return V.color;
219
+ },
220
+ setMode: V.setMode,
221
+ setFactor: V.setFactor,
222
+ aimAt: V.aimAt,
223
+ pick: V.pick,
224
+ refresh() {
225
+ _ || (D = !0, r());
191
226
  },
192
- setMode: L.setMode,
193
- setFactor: L.setFactor,
194
- aimAt: L.aimAt,
195
- pick: L.pick,
196
227
  dispose() {
197
- L.dispose(), P.dispose();
228
+ V.dispose(), R.dispose();
198
229
  }
199
230
  };
200
231
  }
201
232
  //#endregion
202
- 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, x as createLoupe, t as cursorForZone, n as isFocusable, _ as loupeInnerView, e as registerDefaultFont };
233
+ 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, T as createLoupe, t as cursorForZone, n as isFocusable, _ as loupeInnerView, e as registerDefaultFont };
203
234
 
204
235
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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","NO_RECT","createLoupe","element","opts","host","interactive","input","lens","holding","pixels","dropPixels","releaseImageSource","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":["import { clamp } from '@weasel-js/geom';\n\n/**\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","import {\n createViewportLayer, screenToWorld, viewToTransform,\n type CanvasViewHandle, type RenderLayer, type SceneCanvasApi, type View,\n} from '@weasel-js/core';\nimport { releaseImageSource, 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 const dropPixels = () => {\n if (!pixels) return;\n releaseImageSource(pixels);\n pixels.close();\n pixels = null;\n };\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 dropPixels();\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 || px.data[3] === 0) 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 dropPixels();\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","createContentWatch","camera","seen","view","now","version","moved","POINTER_ENDS","followPointer","holding","local","r","input","evt","onPointerMove","lens","onPointerDown","onPointerEnd","type","readbackRegion","cx","target","pointer","dpr","cy","gx","clamp","rw","gy","bufferHeight","rh","raw","gl","flipped","stride","row","src","backdropOf","ctx","NO_RECT","createLoupe","element","opts","host","interactive","pixels","dropPixels","releaseImageSource","pixelsPending","refreshWanted","disposed","landed","b","watch","refreshAsked","content","model","drawVector","drawPixels","backdropColor","backdrop","innerView","world","screenToWorld","viewToTransform","outer","rect","createViewportLayer","win","out","scheduleRefresh","read","cssRect","at","refreshPixels","data","bmp","requestRedraw","readHex","px","c","hud","p","surface","unsubscribeFrame","createLoupeModel","teardown","unfollow","claimsOf","createHud","loupeInnerView"],"sources":["../src/loupe/contentWatch.ts","../src/loupe/pointerAim.ts","../src/loupe/readback.ts","../src/loupe/createLoupe.ts"],"sourcesContent":["import type { View } from '@weasel-js/core';\n\n/** Says whether what a lens reads from changed between one landed frame and the next. */\nexport interface ContentWatch {\n /** The camera the lens was just drawn through. */\n drew(view: View): void;\n /** Whether the content version or the camera differs from the last time this was asked. The first ask only\n * takes its bearings, and answers `false`. */\n changed(): boolean;\n}\n\n/**\n * Watches the two things that change the pixels under an aim that holds still: the content, through `version`\n * when the lens is given one, and the camera it is drawn through. A lens redrawing itself moves neither, which is\n * what lets it read the framebuffer again on a change without that read's own redraw asking for another.\n */\nexport function createContentWatch(version: (() => number) | undefined): ContentWatch {\n let camera = '';\n let seen: { version: number | undefined; camera: string } | null = null;\n return {\n drew(view) {\n camera = `${view.x},${view.y},${view.scale.x},${view.scale.y}`;\n },\n changed() {\n const now = { version: version?.(), camera };\n const moved = seen !== null && (seen.version !== now.version || seen.camera !== now.camera);\n seen = now;\n return moved;\n },\n };\n}\n","interface Point { x: number; y: number }\n\nconst POINTER_ENDS = ['pointerup', 'pointercancel', 'lostpointercapture'] as const;\n\n/**\n * Aims a lens from the pointer moving over `input`, in CSS px relative to it. A gesture that begins where\n * `holds` answers true freezes the aim until it ends: the lens camera follows the aim, so re-aiming mid-drag\n * would move the world under the pointer. Returns a detach.\n */\nexport function followPointer(\n input: HTMLElement,\n lens: { aimAt(p: Point): void; holds(p: Point): boolean },\n): () => void {\n let holding = false;\n const local = (evt: PointerEvent): Point => {\n const r = input.getBoundingClientRect();\n return { x: evt.clientX - r.left, y: evt.clientY - r.top };\n };\n const onPointerMove = (evt: PointerEvent) => {\n if (!holding) lens.aimAt(local(evt));\n };\n const onPointerDown = (evt: PointerEvent) => {\n if (lens.holds(local(evt))) holding = true;\n };\n const onPointerEnd = () => { holding = false; };\n\n input.addEventListener('pointermove', onPointerMove);\n input.addEventListener('pointerdown', onPointerDown);\n for (const type of POINTER_ENDS) input.addEventListener(type, onPointerEnd);\n return () => {\n input.removeEventListener('pointermove', onPointerMove);\n input.removeEventListener('pointerdown', onPointerDown);\n for (const type of POINTER_ENDS) input.removeEventListener(type, onPointerEnd);\n };\n}\n","import { clamp } from '@weasel-js/geom';\n\n/**\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","import {\n createViewportLayer, screenToWorld, viewToTransform,\n type CanvasViewHandle, type RenderLayer, type SceneCanvasApi, type View,\n} from '@weasel-js/core';\nimport { releaseImageSource, 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 { createContentWatch } from './contentWatch';\nimport { followPointer } from './pointerAim';\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 /**\n * The version of what the canvas paints, read on every landed frame: when it moves, a lens whose aim has not\n * reads the framebuffer and the color under it again. Pass `CanvasExtensionApi.getPaintedVersion`. Defaults to\n * `views.getPaintedVersion` when the lens is a view. Without one the lens follows an edit under a parked aim\n * only in vector mode, or when told through `refresh()`; a camera move it follows either way.\n */\n contentVersion?: () => number;\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'> & Partial<Pick<SceneCanvasApi, 'getPaintedVersion'>>;\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\nconst backdropOf = (ctx: HudContentCtx, color: string): 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: { paint: { kind: 'solid', color } },\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 /** Ask for a paint, and on the frame that lands read the picture and the color under the aim again: what is\n * under the aim changed in a way no `contentVersion` reports. */\n refresh(): void;\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 let pixels: ImageBitmap | null = null;\n const dropPixels = () => {\n if (!pixels) return;\n releaseImageSource(pixels);\n pixels.close();\n pixels = null;\n };\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 watch = createContentWatch(opts.contentVersion ?? host?.getPaintedVersion);\n let refreshAsked = false;\n\n const content = (ctx: HudContentCtx): DrawCommand[] => {\n watch.drew(ctx.view);\n return model.mode === 'vector' ? drawVector(ctx) : drawPixels(ctx);\n };\n\n const backdropColor = (ctx: HudContentCtx): string =>\n opts.background ?? ctx.tokens['--wzl-surface'];\n const backdrop = (ctx: HudContentCtx): DrawCommand => backdropOf(ctx, backdropColor(ctx));\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 dropPixels();\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 || px.data[3] === 0) 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 if (watch.changed() || refreshAsked) {\n refreshAsked = false;\n scheduleRefresh();\n model.resample();\n }\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 unfollow = followPointer(input, {\n aimAt: (p) => model.aimAt(p),\n holds: (p) => interactive && surface.covers(p),\n });\n\n const teardown = () => {\n disposed = true;\n unsubscribeFrame();\n unfollow();\n lens?.remove();\n lens = null;\n dropPixels();\n };\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 refresh() {\n if (disposed) return;\n refreshAsked = true;\n requestRedraw();\n },\n dispose() {\n model.dispose();\n win.dispose();\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { View } from '@weasel-js/core';
2
+ /** Says whether what a lens reads from changed between one landed frame and the next. */
3
+ export interface ContentWatch {
4
+ /** The camera the lens was just drawn through. */
5
+ drew(view: View): void;
6
+ /** Whether the content version or the camera differs from the last time this was asked. The first ask only
7
+ * takes its bearings, and answers `false`. */
8
+ changed(): boolean;
9
+ }
10
+ /**
11
+ * Watches the two things that change the pixels under an aim that holds still: the content, through `version`
12
+ * when the lens is given one, and the camera it is drawn through. A lens redrawing itself moves neither, which is
13
+ * what lets it read the framebuffer again on a change without that read's own redraw asking for another.
14
+ */
15
+ export declare function createContentWatch(version: (() => number) | undefined): ContentWatch;
16
+ //# sourceMappingURL=contentWatch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contentWatch.d.ts","sourceRoot":"","sources":["../../src/loupe/contentWatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE5C,yFAAyF;AACzF,MAAM,WAAW,YAAY;IAC3B,kDAAkD;IAClD,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB;mDAC+C;IAC/C,OAAO,IAAI,OAAO,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,GAAG,YAAY,CAcpF"}
@@ -25,6 +25,13 @@ export interface LoupeBaseOptions {
25
25
  height: number;
26
26
  };
27
27
  requestRedraw: () => void;
28
+ /**
29
+ * The version of what the canvas paints, read on every landed frame: when it moves, a lens whose aim has not
30
+ * reads the framebuffer and the color under it again. Pass `CanvasExtensionApi.getPaintedVersion`. Defaults to
31
+ * `views.getPaintedVersion` when the lens is a view. Without one the lens follows an edit under a parked aim
32
+ * only in vector mode, or when told through `refresh()`; a camera move it follows either way.
33
+ */
34
+ contentVersion?: () => number;
28
35
  mode?: LoupeMode;
29
36
  factor?: number;
30
37
  bounds?: WidgetBounds;
@@ -64,7 +71,7 @@ export type LoupeOptions = LoupeBaseOptions & ({
64
71
  * the lens can take input. It magnifies the canvas's own camera; aimed
65
72
  * over another view, it shows the canvas's world, not that view's.
66
73
  */
67
- views: Pick<SceneCanvasApi, 'addView' | 'getSurfaceRect'>;
74
+ views: Pick<SceneCanvasApi, 'addView' | 'getSurfaceRect'> & Partial<Pick<SceneCanvasApi, 'getPaintedVersion'>>;
68
75
  /** Narrows what vector mode paints. Defaults to the canvas's stack. */
69
76
  source?: RenderLayer<unknown>[];
70
77
  /**
@@ -107,6 +114,9 @@ export interface LoupeHandle {
107
114
  x: number;
108
115
  y: number;
109
116
  }): string | null;
117
+ /** Ask for a paint, and on the frame that lands read the picture and the color under the aim again: what is
118
+ * under the aim changed in a way no `contentVersion` reports. */
119
+ refresh(): void;
110
120
  dispose(): void;
111
121
  }
112
122
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"createLoupe.d.ts","sourceRoot":"","sources":["../../src/loupe/createLoupe.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"createLoupe.d.ts","sourceRoot":"","sources":["../../src/loupe/createLoupe.ts"],"names":[],"mappings":"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"}