@niivue/uikit 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # @niivue/uikit
2
+
3
+ UIKit: a collection of controls and widgets (rulers, crosshairs, annotations,
4
+ and later buttons, sliders, panels) integrated into the
5
+ [NiiVue](https://github.com/niivue/niivue) rendering lifecycle.
6
+
7
+ > **Status: shipping widgets.** The rendering-lifecycle hook, UIKit's own
8
+ > line/text renderers (WebGL2 + WebGPU, with a text transform), and the first
9
+ > widgets (ruler, annotation, crosshair) are in. See `docs/ruler-port.md` in
10
+ > `@niivue/niivue` for the design.
11
+
12
+ ## Design in one paragraph
13
+
14
+ UIKit is a **separate module with privileged access to NiiVue's rendering
15
+ lifecycle**. NiiVue core exposes a small hook (an overlay draw phase + a backend
16
+ handle + a settled signal); UIKit registers into it and draws with **its own**
17
+ line/text renderers on both backends. UIKit carries a duplicated copy of the line
18
+ and text drawing so core stays untouched during a bake-in phase; once UIKit is
19
+ proven, core's overlays cut over onto it and the duplicate in core is removed.
20
+
21
+ ## Widgets
22
+
23
+ Each widget is a pure geometry builder (spec in, plain line + text draw data out,
24
+ unit-testable without a GPU) paired with an overlay that owns the GPU resources
25
+ and draws that data through the lifecycle hook.
26
+
27
+ | Builder | Overlay | Draws |
28
+ | --- | --- | --- |
29
+ | `buildRuler` | `UIKitRulerOverlay` | A measuring ruler with graduated ticks and a distance label |
30
+ | `buildCrosshair` | `UIKitCrosshairOverlay` | A screen-space cross marking a point, optionally graduated and numbered |
31
+ | `buildAnnotationGeometry` | `UIKitAnnotationOverlay` | Free-form annotation lines |
32
+
33
+ ```ts
34
+ import { loadDefaultFont, UIKitCrosshairOverlay } from '@niivue/uikit'
35
+
36
+ // One font fetch, shared by every widget on the pane.
37
+ const crosshair = new UIKitCrosshairOverlay(await loadDefaultFont())
38
+ renderer.overlayDraw = (frame) => crosshair.drawOverlay(frame)
39
+
40
+ crosshair.setCrosshair({
41
+ at: [x, y],
42
+ gapPx: 10, // leave the pixel being pointed at uncovered
43
+ showTicks: true,
44
+ showTickNumbers: true,
45
+ pxPerUnit: [devicePxPerUm, devicePxPerUm], // per axis: planes can be anisotropic
46
+ unitsPerTick: 100,
47
+ units: 'um',
48
+ })
49
+ ```
50
+
51
+ The builders are usable on their own if you want the geometry but not UIKit's
52
+ renderers:
53
+
54
+ ```ts
55
+ import { buildTerminatedLine, LineTerminator } from '@niivue/uikit'
56
+
57
+ // A line with an arrowhead at the end, as plain LineData segments.
58
+ const segments = buildTerminatedLine(0, 0, 100, 40, 2, [1, 0, 0, 1], {
59
+ end: LineTerminator.ARROW,
60
+ })
61
+ ```
@@ -0,0 +1,31 @@
1
+ import { AnnotationScreenShape, UIKitOverlayFrame, UIKitOverlayRenderer } from '@niivue/niivue';
2
+ import { LineData } from './line';
3
+ import { UIKitFont } from './text/font';
4
+ import { UIKitTextItem } from './textOverlay';
5
+ export interface AnnotationGeometryOptions {
6
+ /** Device-pixel ratio; scales stroke width + label size for crisp output. */
7
+ dpr?: number;
8
+ /** Label em size in CSS pixels (multiplied by dpr). */
9
+ labelCssPx?: number;
10
+ }
11
+ /**
12
+ * Build the line + text geometry for a set of projected annotation shapes.
13
+ * Closed shapes (ellipse/rect/circle/freehand) draw as outline loops; line/arrow
14
+ * draw start->end (arrow gets an arrowhead). Pure, so it is unit-testable.
15
+ */
16
+ export declare function buildAnnotationGeometry(shapes: readonly AnnotationScreenShape[], opts?: AnnotationGeometryOptions): {
17
+ lines: LineData[];
18
+ text: UIKitTextItem[];
19
+ };
20
+ export declare class UIKitAnnotationOverlay implements UIKitOverlayRenderer {
21
+ private readonly lines;
22
+ private readonly labels;
23
+ private shapes;
24
+ private options;
25
+ constructor(font: UIKitFont, options?: AnnotationGeometryOptions);
26
+ /** Set the shapes to draw next frame. Trigger a redraw via the controller. */
27
+ setShapes(shapes: readonly AnnotationScreenShape[], options?: AnnotationGeometryOptions): void;
28
+ clear(): void;
29
+ drawOverlay(frame: UIKitOverlayFrame): void;
30
+ destroy(): void;
31
+ }
@@ -0,0 +1,81 @@
1
+ import { LineData } from './line';
2
+ import { RGBA, Vec2 } from './ruler';
3
+ import { UIKitTextItem } from './textOverlay';
4
+ export interface CrosshairSpec {
5
+ /** Crosshair centre in screen pixels (the space the overlay draws in). */
6
+ at: Vec2;
7
+ /** Frame width in the same pixel space (the overlay frame's bounds). */
8
+ width: number;
9
+ /** Frame height in the same pixel space. */
10
+ height: number;
11
+ color?: RGBA;
12
+ /** Line thickness in pixels. Default 2. */
13
+ thickness?: number;
14
+ /**
15
+ * Radius of the hole left at the centre. Default 6. Set 0 for an unbroken
16
+ * cross.
17
+ */
18
+ gapPx?: number;
19
+ /**
20
+ * Arm length in pixels, measured outward from the edge of the gap. Omit for
21
+ * arms that run to the edge of the frame.
22
+ */
23
+ armPx?: number;
24
+ /**
25
+ * Draw graduation ticks along the arms, every unit outward from the centre,
26
+ * longer (and optionally numbered) every fifth. Needs `pxPerUnit`: without a
27
+ * scale there is nothing to graduate against and ticks are skipped. Default
28
+ * false.
29
+ */
30
+ showTicks?: boolean;
31
+ /**
32
+ * Screen pixels per graduation unit. Pass a pair to graduate the horizontal
33
+ * and vertical arms on their own scales -- these planes can be anisotropic
34
+ * (a slab that never downsamples one axis), and a single mean would read long
35
+ * on one arm and short on the other.
36
+ */
37
+ pxPerUnit?: number | readonly [number, number];
38
+ /**
39
+ * Units between minor ticks. Default 1. Set it when one unit is the wrong
40
+ * tick spacing at the current scale: a pane that zooms across three decades
41
+ * wants 100 um ticks at one end and half-micron ticks at the other, and the
42
+ * numbers then read the distance (`i * unitsPerTick`), not the tick count.
43
+ */
44
+ unitsPerTick?: number;
45
+ /** Half-length of a minor tick in pixels. Default 6 (majors are twice that). */
46
+ tickLength?: number;
47
+ /** Tick thickness in pixels. Default half the line thickness, minimum 1. */
48
+ tickThickness?: number;
49
+ /** Number the major ticks. Default false. Needs a font on the overlay. */
50
+ showTickNumbers?: boolean;
51
+ /**
52
+ * Unit suffix for the graduation numbers, e.g. 'um'. Omit for bare numbers.
53
+ * Worth setting on a pane whose scale changes with zoom, where a bare "10" is
54
+ * ambiguous.
55
+ */
56
+ units?: string;
57
+ /** Graduation number height in pixels. Default 18. */
58
+ sizePx?: number;
59
+ /** Number color. Defaults to the line color. */
60
+ textColor?: RGBA;
61
+ /**
62
+ * Outline width (px) for the numbers, for readability over busy backgrounds.
63
+ * Default 2; the outline color auto-contrasts the text color. Set 0 to
64
+ * disable.
65
+ */
66
+ textOutlineWidthPx?: number;
67
+ }
68
+ export interface CrosshairGeometry {
69
+ lines: LineData[];
70
+ text: UIKitTextItem[];
71
+ }
72
+ /**
73
+ * Build the cross as up to four clipped segments, plus graduation ticks and
74
+ * numbers when a scale is supplied.
75
+ *
76
+ * An axis is drawn only when the centre lies within the frame on the OTHER axis,
77
+ * so a crosshair panned off the left edge still shows its horizontal line (which
78
+ * says "the point is on this row, off to the left") but not a stray full-height
79
+ * vertical line at a column that is not on screen.
80
+ */
81
+ export declare function buildCrosshair(spec: CrosshairSpec): CrosshairGeometry;
@@ -0,0 +1,18 @@
1
+ import { UIKitOverlayFrame, UIKitOverlayRenderer } from '@niivue/niivue';
2
+ import { CrosshairSpec } from './crosshair';
3
+ import { UIKitFont } from './text/font';
4
+ export type CrosshairPlacement = Omit<CrosshairSpec, 'width' | 'height'>;
5
+ export declare class UIKitCrosshairOverlay implements UIKitOverlayRenderer {
6
+ private readonly lines;
7
+ private readonly labels;
8
+ private placement;
9
+ private builtFor;
10
+ constructor(font?: UIKitFont, placement?: CrosshairPlacement);
11
+ /** Set (or move) the crosshair. Trigger a redraw via the host. */
12
+ setCrosshair(placement: CrosshairPlacement): void;
13
+ /** Clear the crosshair so nothing draws. */
14
+ clear(): void;
15
+ drawOverlay(frame: UIKitOverlayFrame): void;
16
+ /** Release GPU resources on both backends. */
17
+ destroy(): void;
18
+ }