@weasel-js/labkit 1.1.0 → 1.3.0-pre.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 +27 -0
- package/dist/_dts/{DrawCommand-BkZztJsW.d.ts → DrawCommand-DBB45NfN.d.ts} +244 -58
- package/dist/_dts/PrefsForm-BYa6cWnO.d.ts +201 -0
- package/dist/_dts/index-DgcNdEdh.d.ts +314 -0
- package/dist/_dts/types-1Sdxy_Pv.d.ts +90 -0
- package/dist/_dts/types-AHlQxBNN.d.ts +162 -0
- package/dist/_dts/types-DJ79Tg5J.d.ts +56 -0
- package/dist/_dts/{useTrialState-DYe2vUwN.d.ts → useTrialState-CsGMjhu9.d.ts} +6 -92
- package/dist/canvas/index.d.ts +8 -3
- package/dist/canvas/index.js +2 -1
- package/dist/chrome/index.d.ts +86 -0
- package/dist/chrome/index.js +8 -0
- package/dist/chrome/index.js.map +1 -0
- package/dist/{chunk-3TYUJR7Z.js → chunk-4TMMVIDM.js} +9 -3
- package/dist/chunk-4TMMVIDM.js.map +1 -0
- package/dist/chunk-HFDVGF4X.js +170 -0
- package/dist/chunk-HFDVGF4X.js.map +1 -0
- package/dist/chunk-LN6JDUGB.js +106 -0
- package/dist/chunk-LN6JDUGB.js.map +1 -0
- package/dist/{chunk-574LJAV4.js → chunk-NS54R52R.js} +31 -21
- package/dist/chunk-NS54R52R.js.map +1 -0
- package/dist/{chunk-NRKWVTVT.js → chunk-O2BULFHX.js} +5 -14
- package/dist/chunk-O2BULFHX.js.map +1 -0
- package/dist/chunk-O7NVSCLN.js +266 -0
- package/dist/chunk-O7NVSCLN.js.map +1 -0
- package/dist/chunk-PO6L3NSH.js +366 -0
- package/dist/chunk-PO6L3NSH.js.map +1 -0
- package/dist/chunk-PQJ5B2U2.js +27 -0
- package/dist/chunk-PQJ5B2U2.js.map +1 -0
- package/dist/chunk-RVNN4CHQ.js +212 -0
- package/dist/chunk-RVNN4CHQ.js.map +1 -0
- package/dist/chunk-TLGRYALP.js +6151 -0
- package/dist/chunk-TLGRYALP.js.map +1 -0
- package/dist/{chunk-SFL7NFKN.js → chunk-V7PRSIRQ.js} +15 -7
- package/dist/chunk-V7PRSIRQ.js.map +1 -0
- package/dist/{chunk-DJLDIRFN.js → chunk-Z6HJ5FEM.js} +9626 -6050
- package/dist/chunk-Z6HJ5FEM.js.map +1 -0
- package/dist/chunk-ZZAYVX4X.js +505 -0
- package/dist/chunk-ZZAYVX4X.js.map +1 -0
- package/dist/controls/index.d.ts +19 -5
- package/dist/controls/index.js +3 -1
- package/dist/dragdrop/index.d.ts +5 -2
- package/dist/index.d.ts +307 -107
- package/dist/index.js +797 -540
- package/dist/index.js.map +1 -1
- package/dist/job/index.d.ts +13 -0
- package/dist/job/index.js +3 -0
- package/dist/job/index.js.map +1 -0
- package/dist/layers/index.d.ts +6 -3
- package/dist/layers/index.js +2 -1
- package/dist/passthrough/weasel-canvas.d.ts +69 -13
- package/dist/passthrough/weasel-canvas.js +1 -1
- package/dist/passthrough/weasel-ui.d.ts +3804 -3661
- package/dist/passthrough/weasel-ui.js +2 -2
- package/dist/primitives/index.d.ts +102 -8
- package/dist/primitives/index.js +4 -1
- package/dist/state/index.d.ts +3 -2
- package/dist/state/index.js +2 -2
- package/dist/styles.css +543 -144
- package/dist/surface/index.d.ts +77 -0
- package/dist/surface/index.js +4 -0
- package/dist/surface/index.js.map +1 -0
- package/dist/ui/layers/index.js +4 -3
- package/dist/undo/index.d.ts +5 -2
- package/package.json +24 -7
- package/src/canvas/AGENTS.md +12 -0
- package/src/canvas/CanvasStack.tsx +6 -2
- package/src/canvas/useLayerScheduler.ts +22 -15
- package/src/canvas/useOrbit.test.ts +71 -0
- package/src/canvas/useOrbit.ts +149 -0
- package/src/canvas/usePanZoom.test.ts +80 -0
- package/src/canvas/usePanZoom.ts +16 -2
- package/src/chrome/ChromeRegions.stories.tsx +106 -0
- package/src/chrome/builtins.test.ts +153 -0
- package/src/chrome/builtins.tsx +205 -0
- package/src/chrome/index.ts +25 -0
- package/src/chrome/merge.test.ts +51 -0
- package/src/chrome/merge.ts +52 -0
- package/src/chrome/regions/PaletteRegion.less +14 -0
- package/src/chrome/regions/PaletteRegion.test.tsx +36 -0
- package/src/chrome/regions/PaletteRegion.tsx +41 -0
- package/src/chrome/regions/SidebarRegion.tsx +56 -0
- package/src/chrome/regions/StatusRegion.tsx +31 -0
- package/src/chrome/regions/TitleBarRegion.tsx +41 -0
- package/src/chrome/regions/ToolbarRegion.test.tsx +82 -0
- package/src/chrome/regions/ToolbarRegion.tsx +71 -0
- package/src/chrome/regions/ViewportRegion.less +42 -0
- package/src/chrome/regions/ViewportRegion.tsx +37 -0
- package/src/chrome/regions/regions.test.tsx +81 -0
- package/src/chrome/types.ts +127 -0
- package/src/config/builder.test.ts +87 -0
- package/src/config/builder.ts +224 -0
- package/src/config/fromConfigField.test.ts +93 -0
- package/src/config/fromConfigField.ts +74 -0
- package/src/config/index.ts +22 -0
- package/src/config/resolve.test.ts +120 -0
- package/src/config/resolve.ts +67 -0
- package/src/config/rules.ts +68 -0
- package/src/config/types.ts +108 -0
- package/src/config/useConfigSchema.ts +30 -0
- package/src/config/visible.ts +20 -0
- package/src/controls/ControlPanel.less +4 -58
- package/src/controls/ControlPanel.stories.tsx +86 -0
- package/src/controls/ControlPanel.test.tsx +223 -10
- package/src/controls/ControlPanel.tsx +202 -203
- package/src/dragdrop/DragDropRuntime.tsx +36 -18
- package/src/dragdrop/DragGhost.less +1 -1
- package/src/dragdrop/Palette.less +1 -1
- package/src/index.test.ts +18 -0
- package/src/index.ts +38 -3
- package/src/instrument/SineWave.smoke.test.tsx +9 -1
- package/src/instrument/defineInstrument.test.ts +38 -1
- package/src/instrument/defineInstrument.ts +31 -4
- package/src/instrument/index.ts +1 -0
- package/src/instrument/types.ts +38 -3
- package/src/instrument/validateConfigSchema.test.ts +13 -3
- package/src/instrument/validateConfigSchema.ts +7 -10
- package/src/job/index.ts +3 -0
- package/src/job/types.ts +47 -0
- package/src/job/useJob.test.tsx +210 -0
- package/src/job/useJob.ts +134 -0
- package/src/lab/Lab.chrome.test.tsx +65 -0
- package/src/lab/Lab.stories.tsx +1 -2
- package/src/lab/Lab.tsx +58 -15
- package/src/lab/LabContext.ts +6 -0
- package/src/lab/LabFullChrome.stories.tsx +308 -0
- package/src/lab/LabHeader.test.tsx +42 -0
- package/src/lab/LabHeader.tsx +57 -0
- package/src/lab/LabPalette.test.tsx +35 -0
- package/src/lab/LabPalette.tsx +34 -0
- package/src/lab/LabShell.less +50 -3
- package/src/lab/Workspace.less +1 -30
- package/src/lab/Workspace.surface.test.tsx +49 -0
- package/src/lab/Workspace.test.tsx +20 -10
- package/src/lab/Workspace.tsx +19 -9
- package/src/lab/index.ts +2 -0
- package/src/layers/AGENTS.md +1 -1
- package/src/layers/LayerList.less +31 -7
- package/src/layers/LayerList.tsx +17 -4
- package/src/passthrough/weasel-ui.ts +23 -0
- package/src/primitives/DragHandleGlyph.tsx +29 -0
- package/src/primitives/FloatingPanel.less +28 -0
- package/src/primitives/FloatingPanel.stories.tsx +46 -0
- package/src/primitives/FloatingPanel.test.tsx +240 -0
- package/src/primitives/FloatingPanel.tsx +190 -0
- package/src/primitives/FpsMeter.test.tsx +44 -0
- package/src/primitives/FpsMeter.tsx +20 -9
- package/src/primitives/JobProgress.less +69 -0
- package/src/primitives/JobProgress.stories.tsx +48 -0
- package/src/primitives/JobProgress.tsx +65 -0
- package/src/primitives/Legend.less +47 -0
- package/src/primitives/Legend.stories.tsx +32 -0
- package/src/primitives/Legend.test.tsx +57 -0
- package/src/primitives/Legend.tsx +40 -0
- package/src/primitives/Sidebar.less +32 -2
- package/src/primitives/StatusBar.less +7 -0
- package/src/primitives/StatusBar.tsx +10 -3
- package/src/primitives/Toolbar.less +50 -3
- package/src/primitives/Toolbar.test.tsx +67 -1
- package/src/primitives/Toolbar.tsx +72 -6
- package/src/primitives/ZoomControl.less +31 -0
- package/src/primitives/ZoomControl.tsx +76 -0
- package/src/primitives/floating.entry.test.tsx +14 -0
- package/src/primitives/index.ts +10 -2
- package/src/primitives/useRovingTabIndex.test.ts +27 -0
- package/src/primitives/useRovingTabIndex.ts +79 -0
- package/src/state/store.ts +16 -2
- package/src/state/toolSlot.test.ts +36 -0
- package/src/state/types.ts +8 -2
- package/src/state/view.test.ts +127 -0
- package/src/state/view.ts +18 -0
- package/src/styles.less +6 -0
- package/src/surface/AGENTS.md +64 -0
- package/src/surface/SurfaceContext.ts +5 -0
- package/src/surface/composeRects.test.ts +50 -0
- package/src/surface/composeRects.ts +19 -0
- package/src/surface/deviceRect.test.ts +40 -0
- package/src/surface/deviceRect.ts +19 -0
- package/src/surface/index.ts +7 -0
- package/src/surface/rect.ts +16 -0
- package/src/surface/useSurfaceTile.test.tsx +67 -0
- package/src/surface/useSurfaceTile.ts +32 -0
- package/src/surface/useTiledSurface.test.tsx +231 -0
- package/src/surface/useTiledSurface.ts +161 -0
- package/src/test-setup.ts +26 -0
- package/src/theme/base.less +19 -2
- package/src/theme/interstellar.tokens.json +6 -2
- package/src/tools/types.ts +24 -0
- package/src/trial/Trial.config.test.tsx +113 -0
- package/src/trial/Trial.job.test.tsx +73 -0
- package/src/trial/Trial.less +121 -4
- package/src/trial/Trial.stories.tsx +9 -1
- package/src/trial/Trial.test.tsx +67 -96
- package/src/trial/Trial.tsx +112 -45
- package/src/trial/TrialChrome.tsx +110 -64
- package/src/trial/TrialDragContext.ts +10 -0
- package/src/trial/TrialTitleBar.tsx +41 -0
- package/src/trial/index.ts +1 -15
- package/src/trial/trialChrome.borders.test.ts +38 -0
- package/src/ui/layers/LayerStack.less +9 -9
- package/src/ui/layers/LayerStack.tsx +1 -13
- package/src/ui/properties/CurveField.less +2 -2
- package/src/ui/properties/CurveField.test.tsx +11 -11
- package/src/ui/properties/PropertyGroup.less +2 -2
- package/src/ui/properties/PropertyPanel.less +55 -41
- package/src/ui/properties/SpeechBalloonPanels.stories.tsx +4 -2
- package/src/ui/properties/storyLayouts.tsx +2 -2
- package/dist/_dts/index-iAP6XbH3.d.ts +0 -132
- package/dist/_dts/types-x92Kfeme.d.ts +0 -62
- package/dist/chunk-3TYUJR7Z.js.map +0 -1
- package/dist/chunk-574LJAV4.js.map +0 -1
- package/dist/chunk-5R2ATYPJ.js +0 -201
- package/dist/chunk-5R2ATYPJ.js.map +0 -1
- package/dist/chunk-C6GJKPUI.js +0 -5986
- package/dist/chunk-C6GJKPUI.js.map +0 -1
- package/dist/chunk-DJLDIRFN.js.map +0 -1
- package/dist/chunk-NRKWVTVT.js.map +0 -1
- package/dist/chunk-PMAU3SEE.js +0 -126
- package/dist/chunk-PMAU3SEE.js.map +0 -1
- package/dist/chunk-SFL7NFKN.js.map +0 -1
- package/src/instrument/capabilityDetector.test.ts +0 -64
- package/src/instrument/capabilityDetector.ts +0 -20
- package/src/trial/DefaultSidebar.tsx +0 -29
- package/src/trial/DefaultStatusBar.tsx +0 -19
- package/src/trial/DefaultToolbar.tsx +0 -92
- package/src/trial/slotTypes.ts +0 -53
package/README.md
CHANGED
|
@@ -59,6 +59,33 @@ import { ThemeProvider } from '@weasel-js/theme/react';
|
|
|
59
59
|
`"light"` or `"dark"`. Only `styles.css` needs importing — the token values
|
|
60
60
|
arrive through the provider.
|
|
61
61
|
|
|
62
|
+
## Driving your own renderer
|
|
63
|
+
|
|
64
|
+
`CanvasStack` is 2D. For three.js or raw WebGL, take rects and dirtiness from
|
|
65
|
+
labkit and keep the GL yourself:
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import { toDeviceRect, useSurfaceTile, useTiledSurface } from '@weasel-js/labkit/surface';
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
A trial's `view` is opaque to labkit — it is persisted, restored on Reset and
|
|
72
|
+
handed to the instrument without being read into — so a 3D lab stores an orbit
|
|
73
|
+
there and gets all three. `useOrbit` is the 3D peer of `usePanZoom`.
|
|
74
|
+
|
|
75
|
+
See `src/surface/AGENTS.md` for the contract and the traps.
|
|
76
|
+
|
|
77
|
+
## Long-running work
|
|
78
|
+
|
|
79
|
+
An instrument with work too slow for a render declares a `job`. labkit starts it,
|
|
80
|
+
aborts it on unmount and on a key change, discards results from a superseded run,
|
|
81
|
+
and renders progress and a cancel control into the trial chrome. Per-item failure
|
|
82
|
+
is an event rather than a thrown error, so a run with two failed items is a
|
|
83
|
+
partial success.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import type { JobCapability } from '@weasel-js/labkit/job';
|
|
87
|
+
```
|
|
88
|
+
|
|
62
89
|
## Development
|
|
63
90
|
|
|
64
91
|
```bash
|
|
@@ -88,6 +88,8 @@ interface SerializedHistoryEntry {
|
|
|
88
88
|
label: string;
|
|
89
89
|
forwardOps: SerializedOp[];
|
|
90
90
|
baseOps: SerializedOp[];
|
|
91
|
+
selectionBefore?: readonly string[];
|
|
92
|
+
selectionAfter?: readonly string[];
|
|
91
93
|
}
|
|
92
94
|
/** Snapshot of an entire `History` instance. Designed to live alongside the
|
|
93
95
|
* scene snapshot in IDB so a reload restores the undo / redo stacks to
|
|
@@ -119,6 +121,10 @@ interface HistoryEntry {
|
|
|
119
121
|
* contribute nothing. May be `undefined` for deserialized entries
|
|
120
122
|
* restored from an older snapshot that predates this field. */
|
|
121
123
|
touchedIds?: ReadonlySet<string>;
|
|
124
|
+
/** Selection restored when this entry is undone. */
|
|
125
|
+
selectionBefore?: readonly string[];
|
|
126
|
+
/** Selection restored when this entry is redone. */
|
|
127
|
+
selectionAfter?: readonly string[];
|
|
122
128
|
}
|
|
123
129
|
/** Op-batched undo/redo controller returned by `createHistory`. */
|
|
124
130
|
interface History {
|
|
@@ -170,7 +176,7 @@ interface History {
|
|
|
170
176
|
* Unlike `applyOps`, does NOT call `op.apply()`. Used by Journal.commit
|
|
171
177
|
* to flush a session's net forward ops to the parent as one entry without
|
|
172
178
|
* re-mutating the scene. */
|
|
173
|
-
recordEntry(ops: Op[], label: string): void;
|
|
179
|
+
recordEntry(ops: Op[], label: string, options?: RecordEntryOptions): void;
|
|
174
180
|
/** Concatenated forwardOps of every undo-stack entry, in order. Snapshot
|
|
175
181
|
* of "what changes are currently applied via this history" — useful for
|
|
176
182
|
* Journal.commit to flush to a parent, and for any caller that wants to
|
|
@@ -193,6 +199,32 @@ interface History {
|
|
|
193
199
|
* to resume or discard before calling this. */
|
|
194
200
|
resumeJournal(journal: Journal): void;
|
|
195
201
|
}
|
|
202
|
+
/** Options for `recordEntry`. */
|
|
203
|
+
interface RecordEntryOptions {
|
|
204
|
+
/** Selection as of before the already-applied ops ran. `recordEntry` is
|
|
205
|
+
* called after the fact, so the live selection has moved on by then and
|
|
206
|
+
* the engine cannot sample it — a caller that wants undo to restore the
|
|
207
|
+
* selection captures it when the batch opens and passes it here. */
|
|
208
|
+
selectionBefore?: readonly string[];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Path data model. Vector-graphics primitive used kit-wide as the canonical
|
|
213
|
+
* shape (replacing per-shape ad-hoc poses). SVG-style command stream so we
|
|
214
|
+
* cover lines, polygons, beziers, and multi-contour shapes (think the inner
|
|
215
|
+
* hole of an "O") under one type.
|
|
216
|
+
*
|
|
217
|
+
* Storage: command codes in a `Uint8Array`, parameters in a `Float32Array`.
|
|
218
|
+
* Each command consumes a fixed number of float coords from the parameter
|
|
219
|
+
* array; the parser walks both arrays in lockstep. This keeps interaction
|
|
220
|
+
* hot loops monomorphic and keeps GC pressure low under heavy edits.
|
|
221
|
+
*
|
|
222
|
+
* Two path subtypes are distinguished at the type level so common-case
|
|
223
|
+
* machinery (selection AABBs, area-select intersection, hit-testing rect
|
|
224
|
+
* silhouettes) can short-circuit on `RectPath` without paying the polygon
|
|
225
|
+
* kernel cost. Polymorphic kernels accept either via the `Path` union and
|
|
226
|
+
* dispatch on `kind`.
|
|
227
|
+
*/
|
|
196
228
|
|
|
197
229
|
/** Fill rule used by polygon path hit-testing and `ctx.fill()`. */
|
|
198
230
|
type PathFillRule = 'nonzero' | 'evenodd';
|
|
@@ -298,6 +330,20 @@ interface NodeBase<TData, TLayer extends string, TPose> {
|
|
|
298
330
|
pose: TPose;
|
|
299
331
|
data: TData;
|
|
300
332
|
parent: NodeId | null;
|
|
333
|
+
/** Nodes whose poses this node's geometry is computed from. Fixed at add
|
|
334
|
+
* time. Absent or empty means the node's geometry is authored, which is the
|
|
335
|
+
* normal case. */
|
|
336
|
+
dependsOn?: readonly NodeId[];
|
|
337
|
+
/** Computes this node's path from its dependencies' poses, in `dependsOn`
|
|
338
|
+
* order. A dependency that has been removed arrives as `undefined`.
|
|
339
|
+
* Returning `null` means "nothing to draw right now". Re-evaluated when a
|
|
340
|
+
* dependency's world pose changes, never authored. Absolute-pose `Scene`
|
|
341
|
+
* makes that the dependency's own pose, and an ancestor's move reaches it as
|
|
342
|
+
* a `setPose` of its own from the container cascade.
|
|
343
|
+
* `node` is deliberately widened: naming `TData`/`TLayer` here puts them in
|
|
344
|
+
* a contravariant position, making `Scene` invariant in both and breaking
|
|
345
|
+
* assignment kit-wide. The cost is that a `derivePath` casts to read `node.data`. */
|
|
346
|
+
derivePath?: (node: Node<unknown, string, TPose>, deps: readonly (TPose | undefined)[]) => Path | null;
|
|
301
347
|
}
|
|
302
348
|
/** A node with no children — a shape, a label, an image. */
|
|
303
349
|
interface LeafNode<TData, TLayer extends string, TPose = RectPose> extends NodeBase<TData, TLayer, TPose> {
|
|
@@ -352,6 +398,11 @@ interface AddNodeSpec<TData, TLayer extends string, TPose = RectPose> {
|
|
|
352
398
|
/** Only meaningful when `kind === 'container'`. Attach a clip-path function
|
|
353
399
|
* to the node; ignored for leaves. Mirrors `ContainerNode.clipFromPose`. */
|
|
354
400
|
clipFromPose?: (pose: TPose) => Path | null;
|
|
401
|
+
/** Mirrors `SceneNode.dependsOn`. */
|
|
402
|
+
dependsOn?: readonly NodeId[];
|
|
403
|
+
/** Mirrors `SceneNode.derivePath`. Taken as a live function; its registry key is
|
|
404
|
+
* looked up from it, never passed in. */
|
|
405
|
+
derivePath?: (node: Node<unknown, string, TPose>, deps: readonly (TPose | undefined)[]) => Path | null;
|
|
355
406
|
}
|
|
356
407
|
/** A custom scene mutation registered with `Scene.registerOp`: how to apply
|
|
357
408
|
* it and how to undo it. The pair is what makes it participate in history. */
|
|
@@ -399,6 +450,57 @@ interface SerializedNode<TData, TLayer extends string, TPose> {
|
|
|
399
450
|
/** Registry key for the container's clip-path factory.
|
|
400
451
|
* Containers only; omitted when the container has no clip. */
|
|
401
452
|
clipFromPoseKey?: string;
|
|
453
|
+
/** Ids this node's geometry derives from. Omitted when it derives from nothing. */
|
|
454
|
+
dependsOn?: readonly string[];
|
|
455
|
+
/** Registry key for the node's `derivePath` function. Omitted when it has none. */
|
|
456
|
+
derivePathKey?: string;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* One node's ephemeral presentation override — what a frame loop wants to say
|
|
460
|
+
* about a node without saying it about the document.
|
|
461
|
+
*
|
|
462
|
+
* Not document content: never recorded in history, never in `toJSON`, and
|
|
463
|
+
* writing one does not bump `Scene.getVersion()`. Hoist one entry per node and
|
|
464
|
+
* mutate it in place on a frame loop; `PoseOverrides.commit()` is what makes a
|
|
465
|
+
* mutation visible.
|
|
466
|
+
*/
|
|
467
|
+
interface PoseOverride<TPose> {
|
|
468
|
+
/** Replaces the node's document pose everywhere the render and hit-test
|
|
469
|
+
* paths read one, including the clip a container derives from its pose.
|
|
470
|
+
* Resolved by `effectivePose` — reading `node.pose` directly is how those
|
|
471
|
+
* paths came to disagree about where a node is. */
|
|
472
|
+
pose?: TPose;
|
|
473
|
+
/** Multiplied into the node's painted alpha, on top of any `alphaFor`. */
|
|
474
|
+
alpha?: number;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* The scene's ephemeral per-node overrides — see {@link PoseOverride}.
|
|
478
|
+
*
|
|
479
|
+
* The intended shape of a frame is: `set` each node once, mutate the entries
|
|
480
|
+
* in place per frame, `commit()` once. `commit` is not optional bookkeeping —
|
|
481
|
+
* the painter memo keys on pose *reference*, so a mutation without a commit
|
|
482
|
+
* paints the previous frame with no error.
|
|
483
|
+
*
|
|
484
|
+
* To promote a frame to document state (dropping a drag, baking an animation),
|
|
485
|
+
* write it once through `Scene.setPose` and `clear` the override.
|
|
486
|
+
*/
|
|
487
|
+
interface PoseOverrides<TPose> {
|
|
488
|
+
/** Store `entry` for `id` **by reference**; the caller keeps mutating it. */
|
|
489
|
+
set(id: NodeId, entry: PoseOverride<TPose>): void;
|
|
490
|
+
get(id: NodeId): PoseOverride<TPose> | undefined;
|
|
491
|
+
has(id: NodeId): boolean;
|
|
492
|
+
/** The overridden ids, as a snapshot array. */
|
|
493
|
+
ids(): readonly NodeId[];
|
|
494
|
+
clear(id: NodeId): void;
|
|
495
|
+
clearAll(): void;
|
|
496
|
+
/** Publish this frame's in-place mutations: invalidate the painter memo for
|
|
497
|
+
* every overridden node, then notify subscribers. */
|
|
498
|
+
commit(): void;
|
|
499
|
+
/** Notified after every write. The canvas uses this to repaint without a
|
|
500
|
+
* scene version bump. */
|
|
501
|
+
subscribe(fn: () => void): () => void;
|
|
502
|
+
/** Monotonic write counter. A snapshot for observers that poll. */
|
|
503
|
+
getGeneration(): number;
|
|
402
504
|
}
|
|
403
505
|
/**
|
|
404
506
|
* The kit-owned scene tree: nodes, layers, and the undo history over both.
|
|
@@ -428,10 +530,19 @@ interface Scene<TData, TLayer extends string, TPose = RectPose> {
|
|
|
428
530
|
* not a live view, and not yours to mutate. */
|
|
429
531
|
renderOrderNodes(): readonly Node<TData, TLayer, TPose>[];
|
|
430
532
|
add(spec: AddNodeSpec<TData, TLayer, TPose>): NodeId;
|
|
431
|
-
/** Delete `id
|
|
432
|
-
*
|
|
433
|
-
*
|
|
533
|
+
/** Delete `id`, its **entire subtree**, and **everything that derives from**
|
|
534
|
+
* any of those nodes — a node listing one of them in `dependsOn` goes too,
|
|
535
|
+
* along with its own subtree, transitively. A dependent can live anywhere in
|
|
536
|
+
* the tree, so this deletes nodes the caller never named and may unlink
|
|
537
|
+
* several disjoint subtrees at once. Recorded as one undoable step; `undo()`
|
|
538
|
+
* restores every one of them where it was, child order intact. */
|
|
434
539
|
remove(id: NodeId): void;
|
|
540
|
+
/** {@link remove} over several roots at once, as a **single** undoable step.
|
|
541
|
+
* Ids resolve against the tree as it stands at the call, so an id that
|
|
542
|
+
* another one would cascade away is absorbed rather than removed twice —
|
|
543
|
+
* which is what makes it safe to pass a whole selection. Throws if any id is
|
|
544
|
+
* not in the scene; an empty list does nothing and records no step. */
|
|
545
|
+
removeMany(ids: readonly NodeId[]): void;
|
|
435
546
|
update(id: NodeId, patch: {
|
|
436
547
|
data: TData;
|
|
437
548
|
}): void;
|
|
@@ -467,6 +578,9 @@ interface Scene<TData, TLayer extends string, TPose = RectPose> {
|
|
|
467
578
|
setLayerVisible(layer: TLayer, visible: boolean): void;
|
|
468
579
|
setLayerLocked(layer: TLayer, locked: boolean): void;
|
|
469
580
|
addLayer(spec: AddLayerSpec<TLayer>): void;
|
|
581
|
+
/** Drop a user layer and every node tagged to it, as one undoable step.
|
|
582
|
+
* Removal cascades, so this also deletes nodes **on other layers** that
|
|
583
|
+
* derive from a node on this one. */
|
|
470
584
|
removeLayer(layer: TLayer): void;
|
|
471
585
|
renameLayer(layer: TLayer, name: string): void;
|
|
472
586
|
moveLayer(layer: TLayer, index: number): void;
|
|
@@ -500,6 +614,19 @@ interface Scene<TData, TLayer extends string, TPose = RectPose> {
|
|
|
500
614
|
* `SceneCanvasAdapter`). Pass `this` from `sceneToAdapter` or a compatible
|
|
501
615
|
* adapter. */
|
|
502
616
|
applyBatch(ops: Op[], label: string, adapter: unknown): void;
|
|
617
|
+
/** The transient set of active ids — "operate on these N as a unit".
|
|
618
|
+
* Shared by every view over this scene unless a view supplies its own
|
|
619
|
+
* (see `CanvasView.selection`). Not document content: it never appears
|
|
620
|
+
* in `toJSON`. It does ride on history entries, so undo and redo put
|
|
621
|
+
* back the selection an edit was made under; changing it is never an
|
|
622
|
+
* undo step of its own. */
|
|
623
|
+
getSelection(): readonly NodeId[];
|
|
624
|
+
setSelection(ids: readonly NodeId[]): void;
|
|
625
|
+
/** Per-node pose / alpha overrides the render and hit-test paths read
|
|
626
|
+
* through. Like {@link Scene.getSelection} this is not document content:
|
|
627
|
+
* writes are never recorded, never serialized, and do not bump
|
|
628
|
+
* {@link Scene.getVersion}. See {@link PoseOverrides}. */
|
|
629
|
+
readonly overrides: PoseOverrides<TPose>;
|
|
503
630
|
undo(): boolean;
|
|
504
631
|
redo(): boolean;
|
|
505
632
|
canUndo(): boolean;
|
|
@@ -609,29 +736,11 @@ interface ViewportDims {
|
|
|
609
736
|
}
|
|
610
737
|
|
|
611
738
|
/**
|
|
612
|
-
*
|
|
613
|
-
* `WebGL2RenderingContext.uniformMatrix3fv` byte order so we can pass the
|
|
614
|
-
* array directly without a transpose flag.
|
|
739
|
+
* A registered texture, named by the registry that holds it.
|
|
615
740
|
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
* tx, ty, 1]
|
|
620
|
-
*
|
|
621
|
-
* `apply(m, x, y)` returns `[m * (x, y, 1)] = [m00*x + m01*y + tx,
|
|
622
|
-
* m10*x + m11*y + ty]`.
|
|
623
|
-
*/
|
|
624
|
-
type Mat3 = Float32Array;
|
|
625
|
-
|
|
626
|
-
/**
|
|
627
|
-
* registerTexture — accepts an image source, assigns an opaque id, stores in
|
|
628
|
-
* a module-level registry. Actual GL upload happens lazily at draw time in
|
|
629
|
-
* drawShader() via GLTextureCache.upload (which is idempotent).
|
|
630
|
-
*
|
|
631
|
-
* Lifecycle: textures live for the renderer's lifetime. No unregister in v1.
|
|
632
|
-
*
|
|
633
|
-
* Convention §9: this registry stores image data only — no per-renderer state.
|
|
634
|
-
* Each WeaselRenderer's GLTextureCache does its own dedup via has(id).
|
|
741
|
+
* The handle is here rather than beside the registry because `FillStyle`'s
|
|
742
|
+
* pattern variant names it, and paint is a leaf: the registry lives in the
|
|
743
|
+
* renderer and imports this back.
|
|
635
744
|
*/
|
|
636
745
|
interface TextureHandle {
|
|
637
746
|
readonly id: string;
|
|
@@ -789,20 +898,53 @@ interface GradStop {
|
|
|
789
898
|
* typically use a stencil mask of the stroked path against the geometry.
|
|
790
899
|
*/
|
|
791
900
|
type StrokeAlign = 'center' | 'inner' | 'outer';
|
|
901
|
+
/**
|
|
902
|
+
* The kit's built-in marker vocabulary, open on the string the way
|
|
903
|
+
* `PaintKind` is so a consumer's registered key typechecks.
|
|
904
|
+
*/
|
|
905
|
+
type KitMarkerKey = 'arrow' | 'arrow-open' | 'arrow-concave' | 'diamond' | 'diamond-hollow' | 'circle' | 'square' | 'bar';
|
|
906
|
+
type MarkerKey = KitMarkerKey | (string & {});
|
|
907
|
+
/**
|
|
908
|
+
* A marker on one end (or every interior vertex) of a stroke.
|
|
909
|
+
*
|
|
910
|
+
* `size` reuses `Stroke.width`'s unit system: a bare number scales with the
|
|
911
|
+
* resolved stroke width, `{ px }` pins screen pixels. That is SVG's
|
|
912
|
+
* `markerUnits` in the idiom this codebase already resolves at draw time.
|
|
913
|
+
* Omitted, the marker is one stroke width per unit.
|
|
914
|
+
*/
|
|
915
|
+
type MarkerRef = MarkerKey | {
|
|
916
|
+
key: MarkerKey;
|
|
917
|
+
size?: number | {
|
|
918
|
+
px: number;
|
|
919
|
+
};
|
|
920
|
+
};
|
|
792
921
|
/** Stroke style: a FillStyle plus structural line parameters. */
|
|
793
922
|
interface Stroke {
|
|
794
923
|
paint: FillStyle;
|
|
795
|
-
|
|
924
|
+
/** World units, or `{ px }` for screen pixels — resolved against the
|
|
925
|
+
* accumulated transform scale at draw time, so it holds its on-screen
|
|
926
|
+
* thickness as the view zooms. */
|
|
927
|
+
width?: number | {
|
|
928
|
+
px: number;
|
|
929
|
+
};
|
|
796
930
|
/** Per `CanvasRenderingContext2D.setLineDash` — empty/omitted = solid. */
|
|
797
931
|
dash?: number[];
|
|
932
|
+
/** Marker at the first vertex of each open subpath, rotated to point back
|
|
933
|
+
* along the line (SVG's `auto-start-reverse`, as the only behavior). */
|
|
934
|
+
markerStart?: MarkerRef;
|
|
935
|
+
/** Marker at every interior authored vertex, on the bisector of the
|
|
936
|
+
* incoming and outgoing directions. Never insets the stroke. */
|
|
937
|
+
markerMid?: MarkerRef;
|
|
938
|
+
/** Marker at the last vertex of each open subpath. */
|
|
939
|
+
markerEnd?: MarkerRef;
|
|
798
940
|
cap?: 'butt' | 'round' | 'square';
|
|
799
941
|
join?: 'miter' | 'round' | 'bevel';
|
|
800
942
|
/**
|
|
801
943
|
* Miter join fallback threshold. When the miter length exceeds
|
|
802
|
-
* `miterLimit * width / 2`, the join falls back to a bevel. Default
|
|
803
|
-
*
|
|
804
|
-
*
|
|
805
|
-
*
|
|
944
|
+
* `miterLimit * width / 2`, the join falls back to a bevel. Default 4,
|
|
945
|
+
* matching SVG — which is also what the kit's own serializer implies when
|
|
946
|
+
* it omits the attribute for an unset field. Canvas2D's 10 lets an acute
|
|
947
|
+
* corner throw a spike four times the half-width.
|
|
806
948
|
*/
|
|
807
949
|
miterLimit?: number;
|
|
808
950
|
/** Where the stroke sits relative to the geometric edge. Default `'center'`. */
|
|
@@ -837,17 +979,41 @@ interface Stroke {
|
|
|
837
979
|
varyingWidthJoinThreshold?: number;
|
|
838
980
|
}
|
|
839
981
|
|
|
982
|
+
/**
|
|
983
|
+
* 2D affine matrix utilities. Column-major 9-element Float32Array, matching
|
|
984
|
+
* `WebGL2RenderingContext.uniformMatrix3fv` byte order so we can pass the
|
|
985
|
+
* array directly without a transpose flag.
|
|
986
|
+
*
|
|
987
|
+
* Layout (column-major):
|
|
988
|
+
* [m00, m10, 0,
|
|
989
|
+
* m01, m11, 0,
|
|
990
|
+
* tx, ty, 1]
|
|
991
|
+
*
|
|
992
|
+
* `apply(m, x, y)` returns `[m * (x, y, 1)] = [m00*x + m01*y + tx,
|
|
993
|
+
* m10*x + m11*y + ty]`.
|
|
994
|
+
*/
|
|
995
|
+
type Mat3 = Float32Array;
|
|
996
|
+
|
|
840
997
|
/**
|
|
841
998
|
* Typography for `TextPose` and friends. Every field is optional; consumers
|
|
842
999
|
* pass `{}` or override the few they care about. Defaults live in
|
|
843
1000
|
* `DEFAULT_TEXT_STYLE` and are applied at render/measure time, never written
|
|
844
1001
|
* back to the pose.
|
|
845
1002
|
*
|
|
846
|
-
*
|
|
847
|
-
*
|
|
848
|
-
*
|
|
1003
|
+
* Paint is not typography and does not live here. A text node carries its
|
|
1004
|
+
* fill and stroke in `data.fill` / `data.stroke`, the slots every other node
|
|
1005
|
+
* kind uses; `resolveTextStyle` takes them as its second argument and
|
|
1006
|
+
* `StyledRun.fill` / `.stroke` override them per range.
|
|
849
1007
|
*/
|
|
850
1008
|
|
|
1009
|
+
/**
|
|
1010
|
+
* Horizontal alignment. `start` / `end` resolve against the reading direction;
|
|
1011
|
+
* `left` / `right` are absolute. Same five values as CSS `text-align`, with
|
|
1012
|
+
* the same split between the relative pair and the absolute pair.
|
|
1013
|
+
*/
|
|
1014
|
+
type TextAlign = 'left' | 'center' | 'right' | 'start' | 'end';
|
|
1015
|
+
/** Reading direction, which is what gives `start` / `end` their meaning. */
|
|
1016
|
+
type TextDirection = 'ltr' | 'rtl';
|
|
851
1017
|
/** User-facing text style. All fields optional; defaults applied at render time via `resolveTextStyle`. */
|
|
852
1018
|
interface TextStyle {
|
|
853
1019
|
/** Font size in world units. Default 16. */
|
|
@@ -858,15 +1024,15 @@ interface TextStyle {
|
|
|
858
1024
|
fontWeight?: number | string;
|
|
859
1025
|
/** Default `'normal'`. */
|
|
860
1026
|
fontStyle?: 'normal' | 'italic';
|
|
861
|
-
/** Default `'left'`. */
|
|
862
|
-
align?:
|
|
1027
|
+
/** Default `'left'`. Pass `'start'` to align by reading order instead. */
|
|
1028
|
+
align?: TextAlign;
|
|
1029
|
+
/** Reading direction, resolving `align: 'start' | 'end'`. Default `'ltr'`. */
|
|
1030
|
+
direction?: TextDirection;
|
|
863
1031
|
/** Multiplier applied to `fontSize`. Default 1.2. */
|
|
864
1032
|
lineHeight?: number;
|
|
865
|
-
/** Default `{ fill: 'solid', color: '#000' }`. */
|
|
866
|
-
fill?: FillStyle;
|
|
867
1033
|
/**
|
|
868
|
-
* Caret color used by the edit overlay. Defaults to the
|
|
869
|
-
*
|
|
1034
|
+
* Caret color used by the edit overlay. Defaults to the node's fill when
|
|
1035
|
+
* that fill is solid; falls back to `#000` for non-solid paints.
|
|
870
1036
|
*/
|
|
871
1037
|
caretColor?: string;
|
|
872
1038
|
/**
|
|
@@ -883,19 +1049,8 @@ interface TextStyle {
|
|
|
883
1049
|
underline?: boolean;
|
|
884
1050
|
/** Default `false`. */
|
|
885
1051
|
strikethrough?: boolean;
|
|
886
|
-
/**
|
|
887
|
-
|
|
888
|
-
* outline — there is no such thing as a default text stroke.
|
|
889
|
-
*
|
|
890
|
-
* Only glyphs on the outline tier are stroked: above
|
|
891
|
-
* `textOutlineMinScreenSize` a glyph is a real `PolygonPath`, so it gets
|
|
892
|
-
* the ordinary tessellated ribbon with real joins, caps and miters, in any
|
|
893
|
-
* paint. Below it a glyph is a sampled distance field with no geometry to
|
|
894
|
-
* stroke, and it renders unstroked rather than approximated. `width` is in
|
|
895
|
-
* world units, like every other stroke in the kit — it does not scale with
|
|
896
|
-
* `fontSize`.
|
|
897
|
-
*/
|
|
898
|
-
stroke?: Stroke;
|
|
1052
|
+
/** Default `false`. */
|
|
1053
|
+
overline?: boolean;
|
|
899
1054
|
}
|
|
900
1055
|
|
|
901
1056
|
/**
|
|
@@ -910,10 +1065,15 @@ interface TextStyle {
|
|
|
910
1065
|
* override the node-level value (`letterSpacing: 0` on a run is an override,
|
|
911
1066
|
* not an absence — it zeroes inherited tracking).
|
|
912
1067
|
*
|
|
913
|
-
* `underline` / `strikethrough` are *additive*, like
|
|
914
|
-
* can turn a decoration on but never off, so they
|
|
915
|
-
* `run.x || style.x` and not `run.x ?? style.x`. See the header of
|
|
1068
|
+
* `underline` / `strikethrough` / `overline` are *additive*, like
|
|
1069
|
+
* `bold`/`italic`: a run can turn a decoration on but never off, so they
|
|
1070
|
+
* resolve as `run.x || style.x` and not `run.x ?? style.x`. See the header of
|
|
916
1071
|
* `runs/rangeStyle.ts` for why the model collapses the tri-state.
|
|
1072
|
+
*
|
|
1073
|
+
* `script` is folded the same way the toggles are, into the two primitives it
|
|
1074
|
+
* is a preset over: `baselineShift` and `fontScale`. Both come out as one
|
|
1075
|
+
* world-unit `baselineShift` and a final `fontSize`, so layout never learns
|
|
1076
|
+
* that superscripts exist — it places a run against a baseline and an offset.
|
|
917
1077
|
*/
|
|
918
1078
|
|
|
919
1079
|
/** A run with every style resolved against the node's text style — no
|
|
@@ -934,6 +1094,17 @@ interface ResolvedRun {
|
|
|
934
1094
|
underline: boolean;
|
|
935
1095
|
/** Draw a rule through this run's x-height. Additive over the node style. */
|
|
936
1096
|
strikethrough: boolean;
|
|
1097
|
+
/** Draw a rule above this run's ascent. Additive over the node style. */
|
|
1098
|
+
overline: boolean;
|
|
1099
|
+
/**
|
|
1100
|
+
* How far this run sits off the line's shared baseline, in world units;
|
|
1101
|
+
* positive raises. 0 for ordinary text.
|
|
1102
|
+
*
|
|
1103
|
+
* Already multiplied out against the inherited font size, and already
|
|
1104
|
+
* carrying whatever `script` asked for — layout adds it to a baseline and
|
|
1105
|
+
* asks nothing about where it came from.
|
|
1106
|
+
*/
|
|
1107
|
+
baselineShift: number;
|
|
937
1108
|
}
|
|
938
1109
|
|
|
939
1110
|
/**
|
|
@@ -952,7 +1123,7 @@ type TextVerticalAlign = 'top' | 'center' | 'bottom';
|
|
|
952
1123
|
* calls getProgramSource() and compiles the result. This keeps registerProgram
|
|
953
1124
|
* GL-context-agnostic — identical pattern to registerFont storing ImageBitmap.
|
|
954
1125
|
*
|
|
955
|
-
*
|
|
1126
|
+
* Module-level state = source strings only; compiled GL
|
|
956
1127
|
* programs live on each renderer's programRegistry (Map<id, ShaderProgram>).
|
|
957
1128
|
*
|
|
958
1129
|
* Lifecycle: program sources live for the module lifetime. No unregister in v1.
|
|
@@ -1063,6 +1234,21 @@ interface ImageDrawCommand {
|
|
|
1063
1234
|
* device pixels as hard squares — required by anything magnifying a
|
|
1064
1235
|
* framebuffer readback, where blur destroys the point of the readback. */
|
|
1065
1236
|
sampling?: 'linear' | 'nearest';
|
|
1237
|
+
/** Sub-rectangle of `image` to draw, in bitmap pixels from the top-left.
|
|
1238
|
+
* Omitted draws the whole bitmap. Not range-checked: a rect past the edge
|
|
1239
|
+
* samples outside [0..1], which CLAMP_TO_EDGE smears. With
|
|
1240
|
+
* `sampling: 'linear'` the filter reaches half a texel beyond `source`, so
|
|
1241
|
+
* atlas frames need a gutter (see `SpriteSheet.spacing`) or `'nearest'`. */
|
|
1242
|
+
source?: {
|
|
1243
|
+
x: number;
|
|
1244
|
+
y: number;
|
|
1245
|
+
w: number;
|
|
1246
|
+
h: number;
|
|
1247
|
+
};
|
|
1248
|
+
/** Mirror the sampled region within the destination rect. The quad does not
|
|
1249
|
+
* move — a flipped draw covers exactly the pixels an unflipped one does. */
|
|
1250
|
+
flipX?: boolean;
|
|
1251
|
+
flipY?: boolean;
|
|
1066
1252
|
}
|
|
1067
1253
|
/**
|
|
1068
1254
|
* Custom shader draw command. The renderer generates a quad over `bounds`
|
|
@@ -1087,4 +1273,4 @@ interface ShaderDrawCommand {
|
|
|
1087
1273
|
};
|
|
1088
1274
|
}
|
|
1089
1275
|
|
|
1090
|
-
export type { Bounds as B, DrawCommand as D, History as H, Node as N, Op as O,
|
|
1276
|
+
export type { Bounds as B, DrawCommand as D, History as H, Node as N, Op as O, Path as P, Scene as S, View as V, ViewportDims as a, PathDrawCommand as b, NodeId as c };
|