@weasel-js/hud 1.0.0 → 1.0.2

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.
@@ -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;AAOvF,OAAO,EAA6B,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjF,MAAM,WAAW,gBAAgB;IAC/B;2EACuE;IACvE,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAChC;AAED,wBAAgB,SAAS,CACvB,GAAG,EAAE,kBAAkB,EACvB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,IAAI,CAuHZ"}
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;AAOvF,OAAO,EAA6B,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjF,MAAM,WAAW,gBAAgB;IAC/B;2EACuE;IACvE,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAChC;AAED,wBAAgB,SAAS,CACvB,GAAG,EAAE,kBAAkB,EACvB,GAAG,EAAE,GAAG,EACR,OAAO,GAAE,gBAAqB,GAC7B,MAAM,IAAI,CAwHZ"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, c as t, i as n, l as r, n as i, o as a, r as o, s, t as c, u as l } from "./tool-Cvy1MwFJ.js";
1
+ import { a as e, c as t, i as n, l as r, n as i, o as a, r as o, s, t as c, u as l } from "./tool-DyPy2h0T.js";
2
2
  import { createViewportLayer as u, screenToWorld as d, viewToTransform as f } from "@weasel-js/core";
3
3
  //#region src/loupe/innerView.ts
4
4
  function p(e, t, n, r) {
@@ -33,25 +33,25 @@ function g(e) {
33
33
  let { hud: t, element: n, source: r, requestRedraw: i } = e, a = e.mode ?? "vector", o = e.factor ?? 8, s = {
34
34
  x: 0,
35
35
  y: 0
36
- }, c = null, l = null, h = !1, g = e.bounds ?? {
36
+ }, c = null, l = null, h = !1, g = !1, _ = !1, v = e.bounds ?? {
37
37
  x: 24,
38
38
  y: 24,
39
39
  w: 220,
40
40
  h: 200
41
- }, _ = (e) => a === "vector" ? v(e) : y(e), v = (t) => {
41
+ }, y = (e) => a === "vector" ? b(e) : x(e), b = (t) => {
42
42
  let n = d(s.x, s.y, f(t.view)), i = p({
43
43
  x: n[0],
44
44
  y: n[1]
45
45
  }, t.view, t.rect, o);
46
46
  return u({
47
- id: `${S.id}:lens`,
47
+ id: `${w.id}:lens`,
48
48
  label: "Loupe content",
49
49
  source: r,
50
50
  view: i,
51
51
  bounds: () => t.rect,
52
52
  background: e.background ?? t.tokens["--wzl-surface"]
53
53
  }).draw(t.data, t.view, t.dims);
54
- }, y = (e) => l ? [{
54
+ }, x = (e) => l ? [{
55
55
  kind: "image",
56
56
  image: l,
57
57
  x: e.rect.x,
@@ -59,22 +59,30 @@ function g(e) {
59
59
  w: e.rect.w,
60
60
  h: e.rect.h,
61
61
  sampling: "nearest"
62
- }] : [], b = () => {
63
- if (a !== "pixel" || h) return;
62
+ }] : [], S = () => {
63
+ if (_ || a !== "pixel") return;
64
+ if (h) {
65
+ g = !0;
66
+ return;
67
+ }
64
68
  let e = n.getContext("webgl2");
65
69
  if (!e) return;
66
70
  let t = n.getBoundingClientRect();
67
71
  if (t.width === 0) return;
68
- let r = n.width / t.width, c = S.contentRect, u = Math.max(1, Math.round(c.w * r / o)), d = Math.max(1, Math.round(c.h * r / o)), f = m(e, {
72
+ let r = n.width / t.width, c = w.contentRect, u = Math.max(1, Math.round(c.w * r / o)), d = Math.max(1, Math.round(c.h * r / o)), f = m(e, {
69
73
  width: n.width,
70
74
  height: n.height
71
75
  }, s, r, u, d);
72
- h = !0, createImageBitmap(f).then((e) => {
73
- l?.close(), l = e, h = !1, i();
76
+ h = !0, g = !1, createImageBitmap(f).then((e) => {
77
+ if (_) {
78
+ e.close();
79
+ return;
80
+ }
81
+ l?.close(), l = e, h = !1, i(), g && S();
74
82
  }).catch(() => {
75
- h = !1;
83
+ h = !1, g && S();
76
84
  });
77
- }, x = () => {
85
+ }, C = () => {
78
86
  let t = n.getContext("webgl2");
79
87
  if (!t) return;
80
88
  let r = n.getBoundingClientRect();
@@ -88,30 +96,30 @@ function g(e) {
88
96
  a.data[2]
89
97
  ].map((e) => e.toString(16).padStart(2, "0")).join("");
90
98
  o !== c && (c = o, e.onColorChange?.(o));
91
- }, S = t.window({
99
+ }, w = t.window({
92
100
  id: "weasel-loupe",
93
- x: g.x,
94
- y: g.y,
95
- w: g.w,
96
- h: g.h,
101
+ x: v.x,
102
+ y: v.y,
103
+ w: v.w,
104
+ h: v.h,
97
105
  title: e.title ?? "Loupe",
98
106
  titlebar: e.titlebar,
99
- content: _,
107
+ content: y,
100
108
  onResize: () => {
101
- b();
109
+ S();
102
110
  },
103
111
  ...e.onClose ? { onClose: e.onClose } : {}
104
- }), C = (e) => {
112
+ }), T = (e) => {
105
113
  let t = n.getBoundingClientRect();
106
- w({
114
+ E({
107
115
  x: e.clientX - t.left,
108
116
  y: e.clientY - t.top
109
117
  });
110
- }, w = (e) => {
111
- S.hidden || S.hitTest(e.x, e.y) || (s = e, x(), a === "pixel" && b(), i());
118
+ }, E = (e) => {
119
+ w.hidden || w.hitTest(e.x, e.y) || (s = e, C(), a === "pixel" && S(), i());
112
120
  };
113
- return n.addEventListener("pointermove", C), {
114
- window: S,
121
+ return n.addEventListener("pointermove", T), {
122
+ window: w,
115
123
  get mode() {
116
124
  return a;
117
125
  },
@@ -125,14 +133,14 @@ function g(e) {
125
133
  return c;
126
134
  },
127
135
  setMode(e) {
128
- e !== a && (a = e, a === "pixel" && b(), i());
136
+ e !== a && (a = e, a === "pixel" && S(), i());
129
137
  },
130
138
  setFactor(e) {
131
- o = e, a === "pixel" && b(), i();
139
+ o = e, a === "pixel" && S(), i();
132
140
  },
133
- aimAt: w,
141
+ aimAt: E,
134
142
  dispose() {
135
- n.removeEventListener("pointermove", C), l?.close(), l = null, S.dispose();
143
+ _ = !0, n.removeEventListener("pointermove", T), l?.close(), l = null, w.dispose();
136
144
  }
137
145
  };
138
146
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/loupe/innerView.ts","../src/loupe/readback.ts","../src/loupe/createLoupe.ts"],"sourcesContent":["import type { View } from '@weasel-js/core';\nimport type { WidgetBounds } from '../widget';\n\n/**\n * The inner `View` a loupe renders its content through: the outer view's\n * magnification times `factor`, positioned so `target` sits at the center of\n * a viewport the size of `rect`.\n */\nexport function loupeInnerView(\n target: { x: number; y: number },\n outer: View,\n rect: WidgetBounds,\n factor: number,\n): View {\n const scale = { x: outer.scale.x * factor, y: outer.scale.y * factor };\n return {\n x: target.x - rect.w / 2 / scale.x,\n y: target.y - rect.h / 2 / scale.y,\n scale,\n };\n}\n","/**\n * Read a `rw × rh` device-pixel region of the drawing buffer centered on\n * `pointer` (CSS px relative to the canvas), returned top-down.\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 buffer: { width: number; height: number },\n pointer: { x: number; y: number },\n dpr: number,\n rw: number,\n rh: number,\n): ImageData {\n const cx = Math.round(pointer.x * dpr);\n const cy = Math.round(pointer.y * dpr);\n const gx = clamp(cx - Math.floor(rw / 2), 0, Math.max(0, buffer.width - rw));\n const gyTop = clamp(cy - Math.floor(rh / 2), 0, Math.max(0, buffer.height - rh));\n const gy = buffer.height - 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 RenderLayer, type View,\n} from '@weasel-js/core';\nimport type { DrawCommand } from '@weasel-js/core/renderer';\nimport type { Hud } from '../hud';\nimport type { WindowWidget } from '../widgets/window/window';\nimport type { HudContentCtx, WidgetBounds } from '../widget';\nimport { loupeInnerView } from './innerView';\nimport { readbackRegion } from './readback';\n\nexport type LoupeMode = 'vector' | 'pixel';\n\nexport interface LoupeOptions {\n hud: Hud;\n /** The live canvas. Supplies the GL context for pixel mode and the pointer\n * feed for aiming. */\n element: HTMLCanvasElement;\n /** Layers re-rendered through the magnified inner view in vector mode. */\n source: RenderLayer<unknown>[];\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 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 /** Opaque backdrop behind vector content, so the outer canvas does not\n * show through where the inner view is empty. */\n background?: string;\n}\n\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. `null` before\n * the first aim, or when the GL context 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 dispose(): void;\n}\n\nexport function createLoupe(opts: LoupeOptions): LoupeHandle {\n const { hud, element, source, requestRedraw } = opts;\n let mode: LoupeMode = opts.mode ?? 'vector';\n let factor = opts.factor ?? 8;\n let aim = { x: 0, y: 0 };\n let color: string | null = null;\n let pixels: ImageBitmap | null = null;\n let pixelsPending = false;\n\n const b = opts.bounds ?? { x: 24, y: 24, w: 220, h: 200 };\n\n const content = (ctx: HudContentCtx): DrawCommand[] =>\n mode === 'vector' ? drawVector(ctx) : drawPixels(ctx);\n\n const drawVector = (ctx: HudContentCtx): DrawCommand[] => {\n const world = screenToWorld(aim.x, aim.y, viewToTransform(ctx.view));\n const inner: View = loupeInnerView(\n { x: world[0], y: world[1] }, ctx.view, ctx.rect, factor,\n );\n // A fresh lens per frame: CreateViewportLayerOpts.view is static, and the\n // inner view moves with the pointer.\n const lens = createViewportLayer<unknown>({\n id: `${win.id}:lens`,\n label: 'Loupe content',\n source,\n view: inner,\n bounds: () => ctx.rect,\n background: opts.background ?? ctx.tokens['--wzl-surface'],\n });\n return lens.draw(ctx.data, ctx.view, ctx.dims);\n };\n\n const drawPixels = (ctx: HudContentCtx): DrawCommand[] => {\n if (!pixels) return [];\n return [{\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\n const refreshPixels = () => {\n if (mode !== 'pixel' || pixelsPending) return;\n const gl = element.getContext('webgl2');\n if (!gl) return;\n const cssRect = element.getBoundingClientRect();\n if (cssRect.width === 0) return;\n const dpr = element.width / cssRect.width;\n const rect = win.contentRect;\n const rw = Math.max(1, Math.round((rect.w * dpr) / factor));\n const rh = Math.max(1, Math.round((rect.h * dpr) / factor));\n const data = readbackRegion(\n gl, { width: element.width, height: element.height }, aim, dpr, rw, rh,\n );\n pixelsPending = true;\n createImageBitmap(data)\n .then((bmp) => {\n pixels?.close();\n pixels = bmp;\n pixelsPending = false;\n requestRedraw();\n })\n .catch(() => { pixelsPending = false; });\n };\n\n const sampleColor = () => {\n const gl = element.getContext('webgl2');\n if (!gl) return;\n const cssRect = element.getBoundingClientRect();\n if (cssRect.width === 0) return;\n const dpr = element.width / cssRect.width;\n const px = readbackRegion(\n gl, { width: element.width, height: element.height }, aim, dpr, 1, 1,\n );\n const hex = '#' + [px.data[0], px.data[1], px.data[2]]\n .map((c) => c.toString(16).padStart(2, '0')).join('');\n if (hex !== color) {\n color = hex;\n opts.onColorChange?.(hex);\n }\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 onResize: () => { refreshPixels(); },\n ...(opts.onClose ? { onClose: opts.onClose } : {}),\n });\n\n const onPointerMove = (evt: PointerEvent) => {\n const r = element.getBoundingClientRect();\n handleAim({ x: evt.clientX - r.left, y: evt.clientY - r.top });\n };\n\n const handleAim = (p: { x: number; y: number }) => {\n if (win.hidden) return;\n if (win.hitTest(p.x, p.y)) return; // freeze rule\n aim = p;\n sampleColor();\n if (mode === 'pixel') refreshPixels();\n requestRedraw();\n };\n\n element.addEventListener('pointermove', onPointerMove);\n\n return {\n window: win,\n get mode() { return mode; },\n get factor() { return factor; },\n get aim() { return aim; },\n get color() { return color; },\n setMode(next) {\n if (next === mode) return;\n mode = next;\n if (mode === 'pixel') refreshPixels();\n requestRedraw();\n },\n setFactor(next) {\n factor = next;\n if (mode === 'pixel') refreshPixels();\n requestRedraw();\n },\n aimAt: handleAim,\n dispose() {\n element.removeEventListener('pointermove', onPointerMove);\n pixels?.close();\n pixels = null;\n win.dispose();\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/loupe/innerView.ts","../src/loupe/readback.ts","../src/loupe/createLoupe.ts"],"sourcesContent":["import type { View } from '@weasel-js/core';\nimport type { WidgetBounds } from '../widget';\n\n/**\n * The inner `View` a loupe renders its content through: the outer view's\n * magnification times `factor`, positioned so `target` sits at the center of\n * a viewport the size of `rect`.\n */\nexport function loupeInnerView(\n target: { x: number; y: number },\n outer: View,\n rect: WidgetBounds,\n factor: number,\n): View {\n const scale = { x: outer.scale.x * factor, y: outer.scale.y * factor };\n return {\n x: target.x - rect.w / 2 / scale.x,\n y: target.y - rect.h / 2 / scale.y,\n scale,\n };\n}\n","/**\n * Read a `rw × rh` device-pixel region of the drawing buffer centered on\n * `pointer` (CSS px relative to the canvas), returned top-down.\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 buffer: { width: number; height: number },\n pointer: { x: number; y: number },\n dpr: number,\n rw: number,\n rh: number,\n): ImageData {\n const cx = Math.round(pointer.x * dpr);\n const cy = Math.round(pointer.y * dpr);\n const gx = clamp(cx - Math.floor(rw / 2), 0, Math.max(0, buffer.width - rw));\n const gyTop = clamp(cy - Math.floor(rh / 2), 0, Math.max(0, buffer.height - rh));\n const gy = buffer.height - 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 RenderLayer, type View,\n} from '@weasel-js/core';\nimport type { DrawCommand } from '@weasel-js/core/renderer';\nimport type { Hud } from '../hud';\nimport type { WindowWidget } from '../widgets/window/window';\nimport type { HudContentCtx, WidgetBounds } from '../widget';\nimport { loupeInnerView } from './innerView';\nimport { readbackRegion } from './readback';\n\nexport type LoupeMode = 'vector' | 'pixel';\n\nexport interface LoupeOptions {\n hud: Hud;\n /** The live canvas. Supplies the GL context for pixel mode and the pointer\n * feed for aiming. */\n element: HTMLCanvasElement;\n /** Layers re-rendered through the magnified inner view in vector mode. */\n source: RenderLayer<unknown>[];\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 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 /** Opaque backdrop behind vector content, so the outer canvas does not\n * show through where the inner view is empty. */\n background?: string;\n}\n\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. `null` before\n * the first aim, or when the GL context 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 dispose(): void;\n}\n\nexport function createLoupe(opts: LoupeOptions): LoupeHandle {\n const { hud, element, source, requestRedraw } = opts;\n let mode: LoupeMode = opts.mode ?? 'vector';\n let factor = opts.factor ?? 8;\n let aim = { x: 0, y: 0 };\n let color: string | null = null;\n let pixels: ImageBitmap | null = null;\n let pixelsPending = false;\n let pixelsStale = false;\n let disposed = false;\n\n const b = opts.bounds ?? { x: 24, y: 24, w: 220, h: 200 };\n\n const content = (ctx: HudContentCtx): DrawCommand[] =>\n mode === 'vector' ? drawVector(ctx) : drawPixels(ctx);\n\n const drawVector = (ctx: HudContentCtx): DrawCommand[] => {\n const world = screenToWorld(aim.x, aim.y, viewToTransform(ctx.view));\n const inner: View = loupeInnerView(\n { x: world[0], y: world[1] }, ctx.view, ctx.rect, factor,\n );\n // A fresh lens per frame: CreateViewportLayerOpts.view is static, and the\n // inner view moves with the pointer.\n const lens = createViewportLayer<unknown>({\n id: `${win.id}:lens`,\n label: 'Loupe content',\n source,\n view: inner,\n bounds: () => ctx.rect,\n background: opts.background ?? ctx.tokens['--wzl-surface'],\n });\n return lens.draw(ctx.data, ctx.view, ctx.dims);\n };\n\n const drawPixels = (ctx: HudContentCtx): DrawCommand[] => {\n if (!pixels) return [];\n return [{\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\n const refreshPixels = () => {\n if (disposed || mode !== 'pixel') return;\n // A readback requested mid-flight is remembered rather than dropped: the\n // aim that arrives during a fast drag is the one the user ends on.\n if (pixelsPending) { pixelsStale = true; return; }\n const gl = element.getContext('webgl2');\n if (!gl) return;\n const cssRect = element.getBoundingClientRect();\n if (cssRect.width === 0) return;\n const dpr = element.width / cssRect.width;\n const rect = win.contentRect;\n const rw = Math.max(1, Math.round((rect.w * dpr) / factor));\n const rh = Math.max(1, Math.round((rect.h * dpr) / factor));\n const data = readbackRegion(\n gl, { width: element.width, height: element.height }, aim, dpr, rw, rh,\n );\n pixelsPending = true;\n pixelsStale = false;\n createImageBitmap(data)\n .then((bmp) => {\n if (disposed) { bmp.close(); return; }\n pixels?.close();\n pixels = bmp;\n pixelsPending = false;\n requestRedraw();\n if (pixelsStale) refreshPixels();\n })\n .catch(() => {\n pixelsPending = false;\n if (pixelsStale) refreshPixels();\n });\n };\n\n const sampleColor = () => {\n const gl = element.getContext('webgl2');\n if (!gl) return;\n const cssRect = element.getBoundingClientRect();\n if (cssRect.width === 0) return;\n const dpr = element.width / cssRect.width;\n const px = readbackRegion(\n gl, { width: element.width, height: element.height }, aim, dpr, 1, 1,\n );\n const hex = '#' + [px.data[0], px.data[1], px.data[2]]\n .map((c) => c.toString(16).padStart(2, '0')).join('');\n if (hex !== color) {\n color = hex;\n opts.onColorChange?.(hex);\n }\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 onResize: () => { refreshPixels(); },\n ...(opts.onClose ? { onClose: opts.onClose } : {}),\n });\n\n const onPointerMove = (evt: PointerEvent) => {\n const r = element.getBoundingClientRect();\n handleAim({ x: evt.clientX - r.left, y: evt.clientY - r.top });\n };\n\n const handleAim = (p: { x: number; y: number }) => {\n if (win.hidden) return;\n if (win.hitTest(p.x, p.y)) return; // freeze rule\n aim = p;\n sampleColor();\n if (mode === 'pixel') refreshPixels();\n requestRedraw();\n };\n\n element.addEventListener('pointermove', onPointerMove);\n\n return {\n window: win,\n get mode() { return mode; },\n get factor() { return factor; },\n get aim() { return aim; },\n get color() { return color; },\n setMode(next) {\n if (next === mode) return;\n mode = next;\n if (mode === 'pixel') refreshPixels();\n requestRedraw();\n },\n setFactor(next) {\n factor = next;\n if (mode === 'pixel') refreshPixels();\n requestRedraw();\n },\n aimAt: handleAim,\n dispose() {\n disposed = true;\n element.removeEventListener('pointermove', onPointerMove);\n pixels?.close();\n pixels = null;\n win.dispose();\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"createLoupe.d.ts","sourceRoot":"","sources":["../../src/loupe/createLoupe.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,iBAAiB,CAAC;AAEzB,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;AAI7D,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE3C,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,GAAG,CAAC;IACT;2BACuB;IACvB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,0EAA0E;IAC1E,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;IAC/B,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;yEACqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;mEAG+D;IAC/D,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC;0EACsE;IACtE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;sDACkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uEAAuE;IACvE,QAAQ,CAAC,GAAG,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC;gEAC4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;oEACgE;IAChE,KAAK,CAAC,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,WAAW,CAsI3D"}
1
+ {"version":3,"file":"createLoupe.d.ts","sourceRoot":"","sources":["../../src/loupe/createLoupe.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EACjB,MAAM,iBAAiB,CAAC;AAEzB,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;AAI7D,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE3C,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,GAAG,CAAC;IACT;2BACuB;IACvB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,0EAA0E;IAC1E,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;IAC/B,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;yEACqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;mEAG+D;IAC/D,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC;0EACsE;IACtE,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;sDACkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,uEAAuE;IACvE,QAAQ,CAAC,GAAG,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC;gEAC4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IAC/B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;oEACgE;IAChE,KAAK,CAAC,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACzC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,WAAW,CAkJ3D"}
@@ -1,4 +1,4 @@
1
- import { c as e, n as t, r as n } from "../tool-Cvy1MwFJ.js";
1
+ import { c as e, n as t, r as n } from "../tool-DyPy2h0T.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";
@@ -238,6 +238,9 @@ function d(e) {
238
238
  hitTest(e, t) {
239
239
  return i ? !1 : d(e, t);
240
240
  },
241
+ cursorAt(t, n) {
242
+ return i || !d(t, n) ? "default" : e.cursor ?? "pointer";
243
+ },
241
244
  onPointer(t) {
242
245
  switch (t.type) {
243
246
  case "down":
@@ -598,9 +601,12 @@ var C = [
598
601
  function w(e) {
599
602
  return e.claims ?? C;
600
603
  }
604
+ function T(e, t, n) {
605
+ return e.cursorAt?.(t, n) ?? (w(e).includes("pointer") ? "pointer" : void 0);
606
+ }
601
607
  //#endregion
602
608
  //#region src/attach.ts
603
- function T(e, t, i = {}) {
609
+ function E(e, t, i = {}) {
604
610
  let s = i.theme ?? a(o, o.defaultMode);
605
611
  if (t.attached) throw Error("weasel-hud: this HUD is already attached to a canvas.");
606
612
  S().then(() => e.requestRedraw()).catch((e) => {
@@ -652,12 +658,14 @@ function T(e, t, i = {}) {
652
658
  },
653
659
  hitTest: (e, t, i, a, o) => {
654
660
  let [s, c] = r(e, t, n(a)), u = l(s, c);
655
- return u ? {
661
+ if (!u) return null;
662
+ let d = T(u, s, c);
663
+ return {
656
664
  initialScratch: { widget: u },
657
665
  strength: "exclusive",
658
666
  claimedKinds: w(u),
659
- ...u.cursorAt ? { cursor: u.cursorAt(s, c) } : {}
660
- } : null;
667
+ ...d === void 0 ? {} : { cursor: d }
668
+ };
661
669
  },
662
670
  onUncapturedMove: (t, i, a, o) => {
663
671
  let [s, u] = r(t, i, n(o)), d = l(s, u);
@@ -687,30 +695,30 @@ function T(e, t, i = {}) {
687
695
  }
688
696
  //#endregion
689
697
  //#region src/tool.ts
690
- var E = "layer:weasel-hud", D = {
698
+ var D = "layer:weasel-hud", O = {
691
699
  shift: "optional",
692
700
  alt: "optional",
693
701
  meta: "optional",
694
702
  ctrl: "optional"
695
703
  };
696
- function O(e) {
697
- return e?.kind === E;
698
- }
699
704
  function k(e) {
700
- return e?.payload?.widget ?? null;
705
+ return e?.kind === D;
701
706
  }
702
707
  function A(e) {
708
+ return e?.payload?.widget ?? null;
709
+ }
710
+ function j(e) {
703
711
  return Object.assign((t) => {
704
- if (!O(t)) return !1;
705
- let n = k(t);
712
+ if (!k(t)) return !1;
713
+ let n = A(t);
706
714
  return n !== null && w(n).includes(e);
707
715
  }, { readsAffordance: !0 });
708
716
  }
709
- function j(e, t, i) {
717
+ function M(e, t, i) {
710
718
  let a = e.view?.get();
711
719
  return a ? r(t, i, n(a)) : [t, i];
712
720
  }
713
- function M() {
721
+ function N() {
714
722
  return {
715
723
  id: "hud.press",
716
724
  label: "HUD — press widget",
@@ -718,9 +726,9 @@ function M() {
718
726
  invoker: {
719
727
  timing: "immediate",
720
728
  run: (e, t) => {
721
- let n = t, r = k(n?.affordance);
729
+ let n = t, r = A(n?.affordance);
722
730
  if (!r || n?.worldX === void 0 || n.worldY === void 0) return;
723
- let [i, a] = j(e, n.worldX, n.worldY);
731
+ let [i, a] = M(e, n.worldX, n.worldY);
724
732
  r.onPointer({
725
733
  type: "down",
726
734
  x: i,
@@ -731,7 +739,7 @@ function M() {
731
739
  }
732
740
  };
733
741
  }
734
- function N() {
742
+ function P() {
735
743
  return {
736
744
  id: "hud.release",
737
745
  label: "HUD — release widget",
@@ -739,9 +747,9 @@ function N() {
739
747
  invoker: {
740
748
  timing: "immediate",
741
749
  run: (e, t) => {
742
- let n = t, r = k(n?.affordance);
750
+ let n = t, r = A(n?.affordance);
743
751
  if (!r || n?.worldX === void 0 || n.worldY === void 0) return;
744
- let [i, a] = j(e, n.worldX, n.worldY);
752
+ let [i, a] = M(e, n.worldX, n.worldY);
745
753
  r.onPointer({
746
754
  type: "up",
747
755
  x: i,
@@ -752,7 +760,7 @@ function N() {
752
760
  }
753
761
  };
754
762
  }
755
- function P() {
763
+ function F() {
756
764
  return {
757
765
  id: "hud.drag",
758
766
  label: "HUD — drag widget",
@@ -760,12 +768,12 @@ function P() {
760
768
  invoker: {
761
769
  timing: "ongoing",
762
770
  start: (e) => {
763
- let t = k(e.drag?.affordance);
771
+ let t = A(e.drag?.affordance);
764
772
  if (!t) return {};
765
773
  let n = e.deps;
766
774
  return {
767
775
  onMove: (e) => {
768
- let [r, i] = j(n, e.world.x, e.world.y);
776
+ let [r, i] = M(n, e.world.x, e.world.y);
769
777
  t.onPointer({
770
778
  type: "move",
771
779
  x: r,
@@ -781,7 +789,7 @@ function P() {
781
789
  });
782
790
  return;
783
791
  }
784
- let [i, a] = j(n, e.world.x, e.world.y);
792
+ let [i, a] = M(n, e.world.x, e.world.y);
785
793
  t.onPointer({
786
794
  type: "up",
787
795
  x: i,
@@ -794,7 +802,7 @@ function P() {
794
802
  }
795
803
  };
796
804
  }
797
- function F(e, t, n) {
805
+ function I(e, t, n) {
798
806
  return {
799
807
  id: e,
800
808
  label: t,
@@ -802,9 +810,9 @@ function F(e, t, n) {
802
810
  invoker: {
803
811
  timing: "immediate",
804
812
  run: (e, t) => {
805
- let r = t, i = k(r?.affordance);
813
+ let r = t, i = A(r?.affordance);
806
814
  if (!i || r?.worldX === void 0 || r.worldY === void 0) return;
807
- let [a, o] = j(e, r.worldX, r.worldY);
815
+ let [a, o] = M(e, r.worldX, r.worldY);
808
816
  i.onPointer({
809
817
  type: n,
810
818
  x: a,
@@ -815,14 +823,14 @@ function F(e, t, n) {
815
823
  }
816
824
  };
817
825
  }
818
- function I() {
826
+ function L() {
819
827
  return {
820
828
  id: "hud.wheel",
821
829
  label: "HUD — wheel over widget",
822
830
  invoker: {
823
831
  timing: "immediate",
824
832
  run: (e, t) => {
825
- let n = t, r = k(n?.affordance);
833
+ let n = t, r = A(n?.affordance);
826
834
  !r || n?.clientX === void 0 || n.clientY === void 0 || r.onPointer({
827
835
  type: "wheel",
828
836
  x: n.clientX,
@@ -835,73 +843,73 @@ function I() {
835
843
  }
836
844
  };
837
845
  }
838
- function L() {
846
+ function R() {
839
847
  return {
840
848
  id: "weasel-hud",
841
849
  eligibility: { claimed: !0 },
842
850
  actions: [
843
- M(),
844
851
  N(),
845
852
  P(),
846
- F("hud.doubleClick", "HUD — double-click widget", "doubleclick"),
847
- F("hud.contextMenu", "HUD — right-click widget", "contextmenu"),
848
- F("hud.longPress", "HUD — long-press widget", "longpress"),
849
- I()
853
+ F(),
854
+ I("hud.doubleClick", "HUD — double-click widget", "doubleclick"),
855
+ I("hud.contextMenu", "HUD — right-click widget", "contextmenu"),
856
+ I("hud.longPress", "HUD — long-press widget", "longpress"),
857
+ L()
850
858
  ],
851
859
  bindings: [
852
860
  {
853
861
  spec: {
854
862
  kind: "pointerDown",
855
- target: { kindOf: A("pointer") },
856
- mods: D
863
+ target: { kindOf: j("pointer") },
864
+ mods: O
857
865
  },
858
866
  actionId: "hud.press"
859
867
  },
860
868
  {
861
869
  spec: {
862
870
  kind: "click",
863
- target: { kindOf: A("pointer") },
864
- mods: D
871
+ target: { kindOf: j("pointer") },
872
+ mods: O
865
873
  },
866
874
  actionId: "hud.release"
867
875
  },
868
876
  {
869
877
  spec: {
870
878
  kind: "drag",
871
- target: { kindOf: A("pointer") },
872
- mods: D
879
+ target: { kindOf: j("pointer") },
880
+ mods: O
873
881
  },
874
882
  actionId: "hud.drag"
875
883
  },
876
884
  {
877
885
  spec: {
878
886
  kind: "doubleClick",
879
- target: { kindOf: A("doubleClick") },
880
- mods: D
887
+ target: { kindOf: j("doubleClick") },
888
+ mods: O
881
889
  },
882
890
  actionId: "hud.doubleClick"
883
891
  },
884
892
  {
885
893
  spec: {
886
894
  kind: "contextMenu",
887
- target: { kindOf: A("contextMenu") },
888
- mods: D
895
+ target: { kindOf: j("contextMenu") },
896
+ mods: O
889
897
  },
890
898
  actionId: "hud.contextMenu"
891
899
  },
892
900
  {
893
901
  spec: {
894
902
  kind: "longPress",
895
- target: { kindOf: A("longPress") },
896
- mods: D
903
+ target: { kindOf: j("longPress") },
904
+ mods: O
897
905
  },
898
906
  actionId: "hud.longPress"
899
907
  },
900
908
  {
901
909
  spec: {
902
910
  kind: "wheel",
903
- target: { kindOf: A("wheel") },
904
- mods: D
911
+ target: { kindOf: j("wheel") },
912
+ mods: O
905
913
  },
906
914
  actionId: "hud.wheel"
907
915
  }
@@ -909,6 +917,6 @@ function L() {
909
917
  };
910
918
  }
911
919
  //#endregion
912
- export { w as a, v as c, C as i, f as l, L as n, x as o, T as r, S as s, E as t, g as u };
920
+ export { w as a, v as c, C as i, f as l, R as n, x as o, E as r, S as s, D as t, g as u };
913
921
 
914
- //# sourceMappingURL=tool-Cvy1MwFJ.js.map
922
+ //# sourceMappingURL=tool-DyPy2h0T.js.map