@weasel-js/core 1.13.0-rc.0 → 1.13.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/CHANGELOG.md +108 -0
- package/dist/barrel/text.js +2 -2
- package/dist/canvas/SceneCanvas/useContributionRoles.js +2 -0
- package/dist/canvas/SceneCanvas/useContributionRoles.js.map +1 -1
- package/dist/canvas/buildSceneLayer.js +1 -1
- package/dist/canvas/buildSceneLayer.js.map +1 -1
- package/dist/canvas/canvasRenderer.js +6 -2
- package/dist/canvas/canvasRenderer.js.map +1 -1
- package/dist/canvas/sceneViewRender.js +4 -3
- package/dist/canvas/sceneViewRender.js.map +1 -1
- package/dist/canvas/useCanvasPaint.js +3 -1
- package/dist/canvas/useCanvasPaint.js.map +1 -1
- package/dist/core/layers/render.js.map +1 -1
- package/dist/{depSchema-B1RS-IJx.d.ts → depSchema-CpIQzKWQ.d.ts} +12 -6
- package/dist/features/viewports/viewportLayer.js +2 -1
- package/dist/features/viewports/viewportLayer.js.map +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.js +2 -2
- package/dist/test-seams.d.ts +2 -2
- package/dist/version.js +1 -1
- package/package.json +24 -24
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,113 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.13.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b1c691a: A contribution can declare `watch(changed)` beside its `deps`. A dep source is a thunk nothing else can watch, so an `ActionBar` button showing an action's `active` or `enabled` state kept whatever it last read when the source changed without the scene or selection changing. A host subscribes `watch` while the entry is installed and tells the dep registry whenever it fires, and the chrome re-reads.
|
|
8
|
+
- 16464c5: `cullDrawCommands` now culls a group with effects: it drops one whose children's bound, grown by
|
|
9
|
+
how far its effects reach and cut by its clip, misses the cull rect, and keeps it whole otherwise. A
|
|
10
|
+
group whose effects do not say how far they reach is still always kept. Effect reach is in device
|
|
11
|
+
pixels, so `cullDrawCommands` takes the density as a new, required fourth argument, `dpr`.
|
|
12
|
+
|
|
13
|
+
That density reaches a layer through a new `LayerDims` type, `Dims` plus `dpr`, which is what
|
|
14
|
+
`RenderLayer.draw` and `deps`, `drawLayers`, `drawOneLayer`, `CanvasViewHandle.draw` and hud's
|
|
15
|
+
`HudContentCtx.dims` now take. A layer written against `Dims` still type-checks; code that calls
|
|
16
|
+
`draw` itself must pass `dpr`. `SceneViewCull` gains `dpr` too, which `sceneViewFrame` and
|
|
17
|
+
`renderSceneToCanvas` fill from their `dpr` argument. These are breaking for direct callers.
|
|
18
|
+
- 9a76fb5: A text layout holds its code points in columns of numbers, and painting it reads them there, so text re-laid out every frame — a HUD readout — no longer leaves a cell, a placed glyph and a quad object per glyph for the garbage collector. A line's `cells` and `glyphs` and a group's `quads` read as before, built the first time something reads them. A `LaidOutGroup` gains `quadData`, its quads packed `QUAD_STRIDE` (exported) numbers each, which is what the renderer draws from, and a color group gains `colorGlyphs`.
|
|
19
|
+
- 38d5ad1: Depend on `@msb235/blits` 0.12.0, which has no breaking changes from 0.11.0. Its fixes make a single `fn` voice over many subjects, the shape weasel's animator uses, cheaper per frame.
|
|
20
|
+
- 7edd3be: Canceling an in-place edit of text something derives from, such as the hidden text of a Text Along Path, now leaves no undo entry and keeps the redo stack. Its keystrokes still reach the curved copy as you type, but they are shown through a data override rather than written into the node, so the document holds none of them; a commit records one entry from the text before the edit to the text after it. A write made to the node during the edit, such as a fill change, shows through the draft and is kept, and undoing it after a cancel brings back no draft text.
|
|
21
|
+
- cbb16ca: `kit:shape` paints a container that has a `shape` again, and its interior is grabbable by its fill. The previous release stopped it, so a node that draws itself and has children, such as a skeleton bone with child bones, drew nothing. A container whose `path` is its clip, as an SVG `<g>` imports, still paints nothing of its own.
|
|
22
|
+
- 5f28e9c: A path effect that hides its sources no longer treats a source that was already hidden when the effect was made as one it hid. Release leaves that source hidden, and Expand to Paths, Delete and Cut leave it in the scene; Set Path moving off it leaves it hidden too. The effect records those sources by position in a new `foundHidden` field beside `hideSources`, and a saved effect without one reads as before: it hid them all. `sourcesHiddenBy(scene, id)` in core answers which sources an effect holds hidden, and `sourcesOf(node)` the ids a node names in its `dependsOn`.
|
|
23
|
+
- e70d2cd: A node is a mark or a group, and the two now behave differently:
|
|
24
|
+
|
|
25
|
+
- A group never paints, whatever its data: a `shape` or `path` on a group draws nothing, and only its members draw. A mark with children paints itself and its children. A group is hit anywhere in its box, even with no member under the press or after its last member is deleted.
|
|
26
|
+
- Ungroup dissolves only groups; a mark with children is left whole.
|
|
27
|
+
- The area hit test's `includeContainers` option is now `includeGroups` (breaking for a custom `hitTestArea` dep). Marquee and lasso take a mark and its children, and a group only when asked for.
|
|
28
|
+
- A mark's drawn bounds include its own ink as well as its children's.
|
|
29
|
+
- Any node may clip, repeat (`copies`), lay out and set units for its children, and `'inherit'` fills and strokes read from any parent.
|
|
30
|
+
- A drop or an insert lands in a mark only when the `canDropInto` dep admits it; with no dep, a group admits and a mark does not. An insert asks `canDropInto` with an empty list, since the new node is not minted yet.
|
|
31
|
+
- Diagram bodies, containers, pools and lanes are marks, so they paint again and take drops through `diagramCanDropInto`. A stencil body still takes nothing, and a press inside an unfilled lane hits the lane.
|
|
32
|
+
- In a diagram scene, `kind === 'group'` no longer identifies a body or a region. A region is a node where `diagramContainerOf(node)` is not null. A diagram group box is one where `diagramGroupOf(node)` is not null. A body is a node where `diagramNodeOf(node)` is not null and neither of those is. `isDiagramBody(node)` asks that, and `isBodyOrRegion(node)` asks for either.
|
|
33
|
+
- ed89d42: A node clips its children only when it declares a clip. Breaking: a group no longer clips its children to its outline by default, and nothing else does either. A node clips its children to its `clipFromPose` when it has one, or to its outline when it sets the new `clip: 'outline'` field. Its own paint, under its children or over them, is not clipped by its own clip. That outline is the silhouette of whichever painter claims the node, the rect fallback's box included, read by the new `clipOutline(node, pose)`. `clip` is saved and loaded, copied, pasted and undone like the node's other fields, and `NodeClip` names its type.
|
|
34
|
+
|
|
35
|
+
Scenes saved at version 1 keep their look: a version 1 container that loads as a group, as it does by default, gets `clip: 'outline'` when it has no clip function or a load skips its clip key. One that `classifyV1` makes a mark gets no implicit clip, since the loader cannot know which painter will claim it; set `clip` on it where it should clip. Version 2 scenes saved by builds between the version 2 format and this change are read as written, so a group there that relied on the old default clip draws its children unclipped.
|
|
36
|
+
|
|
37
|
+
- `clipNothing` and symmetry's `SYMMETRY_CLIP_KEY` stay registered so saves naming them load; diagram bodies and symmetry groups no longer set a clip, and `@weasel-js/diagram`'s `BodyNodeSpec` drops its `clipFromPose` field.
|
|
38
|
+
- Baking a symmetry group carries each copied node's pose through the copy's transform, its clip with it, and copies a mark's children and every node's stated fields (`pickable`, `hidden`, `units`, `clip`). A mirrored copy of a clipping group used to clip its content away.
|
|
39
|
+
- `statedFields(node)` and `StatedNodeFields` are exported: the plain optional fields a copy of a node carries.
|
|
40
|
+
- `@weasel-js/svg`: an imported `<g clip-path>` is a group with `clip: 'outline'` (`SvgSceneDraft` carries `clip` on a group). The export writes a clip only for a node that declares one, clipped to the same `clipOutline` the canvas uses; a group holding `data.path` without `clip` exports unclipped. A mark with children now exports as a `<g>` holding its own element and then its children, which were left out before; its declared clip and copies apply to its children only. `SvgKitTreeNode` takes `clip`.
|
|
41
|
+
- `@weasel-js/diagram-formats`: a page export writes a diagram box as its outline followed by its rows, with no special case for diagram nodes.
|
|
42
|
+
- `specsFromSerialized` is exported, with `SpecsFromSerializedOptions`. Its `unknownKeys: 'skip'` loads a node whose registry key is unknown without that function instead of throwing, and `onSkip` hears each key left out as a `SkippedKey`. `sceneFromJSON` takes both options, and `scene.loadState(json, opts)` takes them as a second argument.
|
|
43
|
+
- 04b862d: Scene node kinds are now `mark` and `group`, replacing `leaf` and `container`, and any node may have children. `ContainerNode` and `LeafNode` are now `GroupNode` and `MarkNode`; `AddNodeSpec.kind` is `'mark' | 'group'`, and `isLeaf(node)` and `hasChildren(node)` ask whether a node has children. This breaks code that names the old kinds. Scenes save as version 2, and a version 1 save still loads: its leaves become marks and its containers become groups, unless the `classifyV1` option of `sceneFromJSON(json, { classifyV1 })` says otherwise. `V1Classifier` and `nodeKindOf` are exported from `@weasel-js/core`. `@weasel-js/svg`'s `SvgSceneDraft.kind` changed from `'container' | 'leaf'` to `'group' | 'mark'`.
|
|
44
|
+
- 7e7549c: A group now selects as one item:
|
|
45
|
+
|
|
46
|
+
- A press on a group's member selects the group, and so does a right-click, whose menu is the group's own. A drag from a member moves the group.
|
|
47
|
+
- A double-click on a member enters the group, drawn with a dashed frame; a press then selects among its members, and a further double-click goes one level deeper. Escape leaves one level. Both work under any tool. While a group is entered only what is inside it can be pressed, marqueed, lassoed or selected with Select All. Under the select tool a press outside the entered group's box, with or without modifiers, leaves every group that box is outside of and selects nothing; that leave is its own binding, `group.leave.press`, which `bindingOverrides` can rebind or remove. Under a drawing tool the press draws, and what is drawn or pasted lands in the entered group: core now sources the `insertInto` dep from it, under any source registered after it, and `sceneToAdapter`'s new `pasteInto` option says where a paste lands. A double-click on a member that is not a group still reaches text and path editing, and an open mode takes Escape before the group does.
|
|
48
|
+
- Cmd-click (Ctrl on Windows and Linux) selects the node under the pointer past every group. It no longer extends the selection: Shift-click does that, and Cmd-Shift-click adds the node under the pointer past every group.
|
|
49
|
+
- The entered group is session state beside the selection: undo does not move it. `<SceneCanvas>` publishes it as the optional `enteredGroup` dep, and an absent dep means no group is entered. `<SceneCanvas enteredGroup>` takes an `EnteredGroupApi` made with `useEnteredGroup(scene)`, as `selection` is passed, for an app that reads or drives it; without it the canvas keeps its own. `leaveAll()` leaves every level at once.
|
|
50
|
+
- `createEnteredGroupScope({ entered, scene })` returns a `NodeScope` (from `@weasel-js/modes`, which `ScopingDim` now extends) for the entered group, everything outside it dimmed to `OUT_OF_SCOPE_ALPHA`. Compose its `alphaFor` into `<SceneCanvas alphaFor>` and pass it in `redrawOn` to dim what is outside the group.
|
|
51
|
+
- The entered-group frame takes its color from the `--wzl-entered-group` override hook on the canvas or any element above it, and repaints when a theme, mode or class attribute changes there or the system color scheme flips.
|
|
52
|
+
- A contribution's dep source may read the source it displaces: `ContributionDeps` entries take the `below` argument `DepSource` does. The symmetry contribution's `insertInto` falls through to it when no symmetry group is active.
|
|
53
|
+
- `workspaceTintLayer` takes a `fallback` tint, shown when the mode has none.
|
|
54
|
+
|
|
55
|
+
Breaking: isolation mode is gone. `ISOLATION` is no longer exported from `@weasel-js/modes` and `DEFAULT_MODES` no longer holds it, and `<SceneCanvas isolatedGroup>` is removed: the entered group is the one way of working inside a group. In WeaselDraw a double-click on a group's member now enters the group, dims everything outside it, tints the workspace, and names the group in the breadcrumb. Edits made inside it are each their own undo step, where isolation by default collected them into one entry on leaving, and path and text editing open inside the group without leaving it.
|
|
56
|
+
- d5a9a80: A path effect whose source has an `'inherit'` fill or stroke paints that source in what it inherits from its containers, and follows a container's paint when it changes; it used to read as none. `DerivedDep` gains an optional `inherited`, which every dependency the scene builds carries, so any derivation can read the paint a dependency shows. `diagram:mark` resolves an `'inherit'` fill and stroke too.
|
|
57
|
+
- 3519c96: Two more readers resolve an `'inherit'` fill or stroke against a node's containers. The preview ghost of a gesture paints a member with the paint its containers are taking mid-gesture, so a container's fill or stroke scrubbed in a paint control reaches its inheriting members at once rather than at the commit: the paint actions preview those members with the container, and the ghost reads every previewed container above a member. And culling measures an inherited stroke as the stroke it takes instead of declining to bound the node: `defaultPaintBounds` and `PaintBoundsFn` take an optional fourth `ctx` with `inherited`, which `<SceneCanvas>` and the headless render fill in from the scene; `withInheritedPaintBounds` does the same for a consumer's own cull. A `drawOne` wrapped by `withInheritedPaints` now keeps a `ctx.inherited` its caller passes.
|
|
58
|
+
- 773f9c8: Several writers can now override one node at once. Each entry set on `scene.overrides` is a ply over the ones set before it: for each field the most recently claimed ply that gives it wins, `alpha` multiplies through every ply, and `data` runs each ply's function on what the plies under it show. Releasing a ply shows what lies under it. A ply's `path` is drawn in the box of the `pose` it gives beside it, so a pose laid over it by another writer, such as a move over a skinned node, carries the path along; a path given with no pose is drawn where it is. So a gesture's published preview no longer gives way where another writer holds the node: the data a move previews for a transform follower shows while the move holds the follower's pose, and an edge whose live route is eased shows the drag's reroute beside the route, so what derives from either follows every frame.
|
|
59
|
+
|
|
60
|
+
`has` and `clear` take an optional `entry`, to check or release one writer's ply: `has(id, entry)`, `clear(id, entry)`. `clear(id)` still drops every ply of the node; each keeps its place, so a writer that sets its entry again goes back where it stood. `get(id)` answers the top ply. A hand-written `PoseOverrides` must honor `entry` on both: a writer releasing its own ply must not clear another's. This is a behavior change for a writer that set an entry over another's to replace it, or that checked `get(id) === entry` to know its own was still installed: the earlier entry now stays and shows again when the later one goes, and `get` answers only the top one. A hold's `show` no longer leaves an id another writer holds to that writer; it lays its frame over it. `PoseHold.others()` answers each node's topmost pose that is not the hold's own, where it answered the pose of whichever single entry the node held.
|
|
61
|
+
|
|
62
|
+
`PoseTransitionScene.overrides` now needs `read` where it needed `has`: the undo glide skips only a node something else poses, not one showing a data draft. A container's descendants likewise ride along with its pose animation unless another writer poses them. Rigs and pose targets put their override back when the table drops it, and a pose target retargeted mid-run keeps running when its own write reflows a layout. Labkit's `LensActionApi` gains `unfollow`, which Pinned and Close call to let go of the follow hold alone.
|
|
63
|
+
- 39c72a4: What derives from a node now follows an anchor drag on it every frame, rather than at the release: a Text Along Path laid along a path or a blend from it, and the same for every other action that previews through its handle. `<SceneCanvas>` publishes each in-flight handle's `previewPose` and `previewData` to the scene's overrides through the new `publishPreviews(scene, dispatcher)`, and releases them once the handle's commit has landed, so a cancel leaves the document and its history untouched. A consumer driving a dispatcher of its own, a 3D host among them, can call it too.
|
|
64
|
+
|
|
65
|
+
A pose override can now carry `data(stored)`, the data a node is shown with, worked out from what it stores. Painting, picking, drawn bounds and the effective reading of every derivation read it through the new `nodeAsShown(overrides, node)`; the document reading, `scene.get`, history and export do not. `PoseOverride` and `PoseOverrides` take the scene's data type as a second parameter, and `PoseHold` gains `show(frame)`, which publishes data beside poses and leaves an id another writer holds to that writer; a hold now puts back an entry the scene dropped when its node was removed, once the node is back.
|
|
66
|
+
|
|
67
|
+
Commands and exports read the document through the new `documentOf(scene)`, the scene with its overrides empty: Expand to Paths, Slice, `drawnBoxes` and what it serves (Align, Flip, resize and rotate, snap targets), and the diagram page export no longer bake in an in-flight drag's pose or an open edit's draft, and `documentDepOf` takes the reading node as `derivedDepOf` does. `withPath` keeps returning the same object for the same node and path, so a gesture previewing through it keeps its previews' identity between frames.
|
|
68
|
+
- 8c39126: Picks consult a BVH index kept beside the scene, so a marquee, a lasso or a click visits the nodes near the query instead of every node. `@weasel-js/geom` adds the tree. Three contracts tighten for the index to be sound. `NodeInk.outset` must not depend on the view scale: screen-space reach goes in `outsetPx`. In kernel3d, `world.bounds` must be a function of the node alone. Two picks change behavior: marquee and lasso now test a derived node at its derived path, and the point pick now tests a node carrying an override `path` against that path, which is what the renderer paints, instead of its pose box.
|
|
69
|
+
- 1322310: A node's declared clip can now be changed after it is added: `scene.setClip(id, 'outline')` declares it and `scene.setClip(id, null)` clears it, as one undo step, refused on a locked node like `setUnits`. `reconcileSpecs` writes a changed `clip` in place through it, where before it removed the node and added it again. The two setters are typed together as `SceneDeclaredFieldSetters`, which `Scene` extends. `setUnits` no longer records an entry when given a scope equal to the one declared. This adds API.
|
|
70
|
+
- bbee4df: `startThresholdDrag` takes `captureAt: 'threshold'`, which leaves the pointer uncaptured until the press is a drag, so a press released short of the threshold still clicks what it was on; the capture it takes is released when the drag ends or is canceled. `modifierStateOf`, which reads an event's modifier keys as a `ModifierState`, is exported from routing and core, for `keysHeld` to read a press.
|
|
71
|
+
- Updated dependencies [b1c691a]
|
|
72
|
+
- Updated dependencies [16464c5]
|
|
73
|
+
- Updated dependencies [7c1134c]
|
|
74
|
+
- Updated dependencies [f1c1528]
|
|
75
|
+
- Updated dependencies [9a76fb5]
|
|
76
|
+
- Updated dependencies [7e5f9e3]
|
|
77
|
+
- Updated dependencies [38d5ad1]
|
|
78
|
+
- Updated dependencies [04b862d]
|
|
79
|
+
- Updated dependencies [7e7549c]
|
|
80
|
+
- Updated dependencies [c7516cd]
|
|
81
|
+
- Updated dependencies [1f2a0b3]
|
|
82
|
+
- Updated dependencies [8c39126]
|
|
83
|
+
- Updated dependencies [3eaceb1]
|
|
84
|
+
- Updated dependencies [86f67ad]
|
|
85
|
+
- Updated dependencies [769eef8]
|
|
86
|
+
- Updated dependencies [352502d]
|
|
87
|
+
- Updated dependencies [bbee4df]
|
|
88
|
+
- Updated dependencies [aba2016]
|
|
89
|
+
- Updated dependencies [4c113da]
|
|
90
|
+
- @weasel-js/routing@1.13.0
|
|
91
|
+
- @weasel-js/renderer@1.13.0
|
|
92
|
+
- @weasel-js/prefs@1.13.0
|
|
93
|
+
- @weasel-js/text@1.13.0
|
|
94
|
+
- @weasel-js/geom@1.13.0
|
|
95
|
+
- @weasel-js/animator@1.13.0
|
|
96
|
+
- @weasel-js/quantity@1.13.0
|
|
97
|
+
- @weasel-js/modes@1.13.0
|
|
98
|
+
- @weasel-js/text-dom@1.13.0
|
|
99
|
+
- @weasel-js/stroke@1.13.0
|
|
100
|
+
- @weasel-js/gestures@1.13.0
|
|
101
|
+
- @weasel-js/cursor@1.13.0
|
|
102
|
+
- @weasel-js/font@1.13.0
|
|
103
|
+
- @weasel-js/history@1.13.0
|
|
104
|
+
- @weasel-js/icons@1.13.0
|
|
105
|
+
- @weasel-js/paint@1.13.0
|
|
106
|
+
- @weasel-js/react@1.13.0
|
|
107
|
+
- @weasel-js/registry@1.13.0
|
|
108
|
+
- @weasel-js/select@1.13.0
|
|
109
|
+
- @weasel-js/simulation@1.13.0
|
|
110
|
+
|
|
3
111
|
## 1.13.0-rc.0
|
|
4
112
|
|
|
5
113
|
### Patch Changes
|
package/dist/barrel/text.js
CHANGED
|
@@ -5,6 +5,6 @@ import { createTextLayer } from "../features/text/textLayer.js";
|
|
|
5
5
|
import { useSceneTextEdit } from "../features/text/useSceneTextEdit.js";
|
|
6
6
|
import { TEXT_EDIT_FONT_HOLD, TextOutlinesError as TextOutlinesError$1, charOffsetToDomPosition, domPositionToCharOffset, domToRuns, loadTextOutlines as loadTextOutlines$1, runsToDom, textCommand, textCommandFromPose, textCommandFromRuns, textToPath as textToPath$1, textToPathsByPaint as textToPathsByPaint$1, useTextEdit } from "../features/text/index.js";
|
|
7
7
|
import { useCreateOutlinesAdapter } from "../interactions/actions/outlines/useCreateOutlinesAdapter.js";
|
|
8
|
-
import { DEFAULT_DECORATION_METRICS, DEFAULT_TEXT_FILL as DEFAULT_TEXT_FILL$1, DEFAULT_TEXT_STYLE, MARKDOWN_RUN_GRAMMAR, SCRIPT_METRICS, SMALL_CAPS_SCALE, applyStyleToRange, cachedLayoutRuns, cachedPaintRuns, caretIndexAt as caretIndexAt$1, createMarkdownRenderer, decorationMetrics, defaultLabelTextRenderer, effectiveRangeStyle, fitTextPose, fontString, isBoldWeight, isSmallCapsLetter, isTextData as isTextData$1, layoutMarkdown, layoutRuns, layoutTextPose, markdownToRuns, measureText, measureTextBounds as measureTextBounds$1, measuredWidth, nodeHasFlag, numericWeight, paintRuns, paragraphDirection, patchRangeStyle, pointInTextPose as pointInTextPose$1, rangeWeight, registerBidi, renderLabel, resolveAlign as resolveAlign$1, resolveRunFace, resolveRuns, resolveTextStyle, runsToMarkdown, runsToPlainText as runsToPlainText$1, scriptMetrics, scriptMetricsFor, setFlagOverRange, smallCapsScale, smallCapsScaleFor, smallCapsText, styleAtRange, textLineBoxes as textLineBoxes$1, textPoseLayoutInput, toRuns, transformRunTexts, unboldPatch, verticalAlignOffset } from "@weasel-js/text";
|
|
8
|
+
import { DEFAULT_DECORATION_METRICS, DEFAULT_TEXT_FILL as DEFAULT_TEXT_FILL$1, DEFAULT_TEXT_STYLE, MARKDOWN_RUN_GRAMMAR, QUAD_STRIDE, SCRIPT_METRICS, SMALL_CAPS_SCALE, applyStyleToRange, cachedLayoutRuns, cachedPaintRuns, caretIndexAt as caretIndexAt$1, createMarkdownRenderer, decorationMetrics, defaultLabelTextRenderer, effectiveRangeStyle, fitTextPose, fontString, isBoldWeight, isSmallCapsLetter, isTextData as isTextData$1, layoutMarkdown, layoutRuns, layoutTextPose, markdownToRuns, measureText, measureTextBounds as measureTextBounds$1, measuredWidth, nodeHasFlag, numericWeight, paintRuns, paragraphDirection, patchRangeStyle, pointInTextPose as pointInTextPose$1, rangeWeight, registerBidi, renderLabel, resolveAlign as resolveAlign$1, resolveRunFace, resolveRuns, resolveTextStyle, runsToMarkdown, runsToPlainText as runsToPlainText$1, scriptMetrics, scriptMetricsFor, setFlagOverRange, smallCapsScale, smallCapsScaleFor, smallCapsText, styleAtRange, textLineBoxes as textLineBoxes$1, textPoseLayoutInput, toRuns, transformRunTexts, unboldPatch, verticalAlignOffset } from "@weasel-js/text";
|
|
9
9
|
|
|
10
|
-
export { DEFAULT_DECORATION_METRICS, DEFAULT_TEXT_FILL$1 as DEFAULT_TEXT_FILL, DEFAULT_TEXT_STYLE, MARKDOWN_RUN_GRAMMAR, SCRIPT_METRICS, SMALL_CAPS_SCALE, TEXT_EDIT_FONT_HOLD, TextOutlinesError$1 as TextOutlinesError, applyCreateOutlines, applyOutlineStroke, applyStyleToRange, cachedLayoutRuns, cachedPaintRuns, caretIndexAt$1 as caretIndexAt, charOffsetToDomPosition, createMarkdownRenderer, createTextLayer, decorationMetrics, defaultLabelTextRenderer, domPositionToCharOffset, domToRuns, effectiveRangeStyle, fitTextPose, fontString, isBoldWeight, isSmallCapsLetter, isTextData$1 as isTextData, layoutMarkdown, layoutRuns, layoutTextPose, loadTextOutlines$1 as loadTextOutlines, markdownToRuns, measureText, measureTextBounds$1 as measureTextBounds, measuredWidth, nodeHasFlag, numericWeight, paintRuns, paragraphDirection, patchRangeStyle, pointInTextPose$1 as pointInTextPose, rangeWeight, registerBidi, renderLabel, resolveAlign$1 as resolveAlign, resolveRunFace, resolveRuns, resolveTextStyle, runsToDom, runsToMarkdown, runsToPlainText$1 as runsToPlainText, scriptMetrics, scriptMetricsFor, setFlagOverRange, smallCapsScale, smallCapsScaleFor, smallCapsText, strokeRegion, styleAtRange, textCommand, textCommandFromPose, textCommandFromRuns, textLineBoxes$1 as textLineBoxes, textPoseLayoutInput, textToPath$1 as textToPath, textToPathsByPaint$1 as textToPathsByPaint, toRuns, transformRunTexts, unboldPatch, useCreateOutlinesAdapter, useSceneTextEdit, useTextEdit, verticalAlignOffset };
|
|
10
|
+
export { DEFAULT_DECORATION_METRICS, DEFAULT_TEXT_FILL$1 as DEFAULT_TEXT_FILL, DEFAULT_TEXT_STYLE, MARKDOWN_RUN_GRAMMAR, QUAD_STRIDE, SCRIPT_METRICS, SMALL_CAPS_SCALE, TEXT_EDIT_FONT_HOLD, TextOutlinesError$1 as TextOutlinesError, applyCreateOutlines, applyOutlineStroke, applyStyleToRange, cachedLayoutRuns, cachedPaintRuns, caretIndexAt$1 as caretIndexAt, charOffsetToDomPosition, createMarkdownRenderer, createTextLayer, decorationMetrics, defaultLabelTextRenderer, domPositionToCharOffset, domToRuns, effectiveRangeStyle, fitTextPose, fontString, isBoldWeight, isSmallCapsLetter, isTextData$1 as isTextData, layoutMarkdown, layoutRuns, layoutTextPose, loadTextOutlines$1 as loadTextOutlines, markdownToRuns, measureText, measureTextBounds$1 as measureTextBounds, measuredWidth, nodeHasFlag, numericWeight, paintRuns, paragraphDirection, patchRangeStyle, pointInTextPose$1 as pointInTextPose, rangeWeight, registerBidi, renderLabel, resolveAlign$1 as resolveAlign, resolveRunFace, resolveRuns, resolveTextStyle, runsToDom, runsToMarkdown, runsToPlainText$1 as runsToPlainText, scriptMetrics, scriptMetricsFor, setFlagOverRange, smallCapsScale, smallCapsScaleFor, smallCapsText, strokeRegion, styleAtRange, textCommand, textCommandFromPose, textCommandFromRuns, textLineBoxes$1 as textLineBoxes, textPoseLayoutInput, textToPath$1 as textToPath, textToPathsByPaint$1 as textToPathsByPaint, toRuns, transformRunTexts, unboldPatch, useCreateOutlinesAdapter, useSceneTextEdit, useTextEdit, verticalAlignOffset };
|
|
@@ -107,6 +107,8 @@ function useContributionRoles(tools, api, versionCheck = "warn") {
|
|
|
107
107
|
for (const entry of entries) {
|
|
108
108
|
const deps = entry.deps;
|
|
109
109
|
if (deps && depRegistry) installs.set(deps, () => registerDeps(depRegistry, deps));
|
|
110
|
+
const watch = entry.watch;
|
|
111
|
+
if (watch && depRegistry) installs.set(watch, () => watch(() => depRegistry.changed?.()));
|
|
110
112
|
const attach = entry.attach;
|
|
111
113
|
if (attach && api) installs.set(attach, () => attach(api, reader));
|
|
112
114
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useContributionRoles.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useContributionRoles.ts"],"sourcesContent":["/**\n * `useContributionRoles` — installs the roles of every entry in the tools\n * registry that a surface owns rather than the dispatcher: its actions into the\n * `<ActionsProvider>`, its deps into the dep registry, and its `attach` against\n * the mounted canvas. Views are the fourth such role; `<SceneCanvas>` renders\n * those itself, because a view is a component.\n *\n * Tool hooks run wherever the consumer calls them, which may be outside any\n * registry. Declaring roles on the entry and installing them from in here keeps\n * the tool hook callable from anywhere.\n */\nimport { useEffect, useRef } from 'react';\nimport { useActionsRegistry, useMenuHost, useOffhandAction, useOptionalDepRegistry } from '@weasel-js/routing/react';\nimport type { DepRegistry } from '@weasel-js/routing/react';\nimport type { DepName } from 'interactions/actions/depSchema';\nimport type { ToolsApi } from '../../tools/overlayBinding';\nimport type { CanvasExtensionApi } from '../canvasExtension';\nimport type { SurfaceContribution } from '../surfaceContribution';\nimport { checkRequirements, type RequirementMismatch } from '../kitRequirements';\nimport { useContributionFrameHooks } from './contributionFrameHooks';\nimport { isDev } from '@weasel-js/react';\n\n/** How a surface treats an entry whose `requires` the running kit does not\n * meet: warn in development (the default), throw, or not check. */\nexport type VersionCheck = 'warn' | 'strict' | 'off';\n\nconst NO_DEPS = { get: () => undefined };\nconst warnedRequirements = new WeakSet<object>();\n\nfunction describeMismatch(m: RequirementMismatch): string {\n switch (m.reason) {\n case 'unsatisfied': return `${m.package} ${m.range} (running ${m.running})`;\n case 'invalid-range': return `${m.package} \"${m.range}\", which is not a range this check reads`;\n case 'unknown-package': return `${m.package} ${m.range}, but ${m.package} reports no version`;\n }\n}\n\nfunction checkEntryRequirements(entries: readonly SurfaceContribution[], mode: VersionCheck): void {\n if (mode === 'off') return;\n for (const entry of entries) {\n const requires = entry.requires;\n if (!requires) continue;\n if (mode === 'warn' && warnedRequirements.has(requires)) continue;\n const mismatches = checkRequirements(requires);\n if (mismatches.length === 0) continue;\n const message = `[weasel] contribution \"${entry.id}\" requires ${mismatches.map(describeMismatch).join('; ')}.`;\n if (mode === 'strict') throw new Error(message);\n warnedRequirements.add(requires);\n console.warn(message);\n }\n}\n\n/** Every entry the registry holds, registry and ambient alike. */\nexport function contributionEntries(tools: Pick<ToolsApi, 'registry' | 'ambient'>): SurfaceContribution[] {\n return [...Object.values(tools.registry), ...tools.ambient] as SurfaceContribution[];\n}\n\n/**\n * Keep one installation per key alive: install keys that appeared, tear down\n * keys that went. Keys are role objects the consumer owns, so an entry the\n * host re-wraps every render — `useTools` restates eligibility on a copy —\n * still installs once.\n */\nfunction useKeyedInstall(\n items: ReadonlyMap<object, () => () => void>,\n): void {\n const live = useRef(new Map<object, () => void>());\n useEffect(() => {\n for (const [key, teardown] of live.current) {\n if (items.has(key)) continue;\n teardown();\n live.current.delete(key);\n }\n for (const [key, install] of items) {\n if (live.current.has(key)) continue;\n live.current.set(key, install());\n }\n });\n useEffect(() => () => {\n for (const teardown of live.current.values()) teardown();\n live.current.clear();\n }, []);\n}\n\nexport function useContributionRoles(\n tools: ToolsApi,\n api: CanvasExtensionApi | null,\n versionCheck: VersionCheck = 'warn',\n): void {\n const registry = useActionsRegistry();\n const depRegistry = useOptionalDepRegistry();\n const { registry: toolRegistry, ambient } = tools;\n\n useEffect(() => {\n if (!registry) return;\n const unregisters: Array<() => void> = [];\n // Ambient entries included: an always-on entry owns actions its own\n // bindings reference exactly as a registry tool does, and leaving them out\n // gives the same silent failure this hook exists to prevent — a binding\n // pointing at an id nothing registered.\n for (const entry of contributionEntries({ registry: toolRegistry, ambient })) {\n for (const action of entry.actions ?? []) {\n unregisters.push(registry.register(action));\n }\n }\n return () => { for (const u of unregisters) u(); };\n }, [registry, toolRegistry, ambient]);\n\n const menuHost = useMenuHost();\n useEffect(() => {\n if (!menuHost) return;\n const releases: Array<() => void> = [];\n for (const [id, entry] of Object.entries(toolRegistry)) {\n for (const def of (entry as SurfaceContribution).menus ?? []) releases.push(menuHost.define(def, id));\n }\n for (const entry of ambient) {\n for (const def of (entry as SurfaceContribution).menus ?? []) releases.push(menuHost.define(def, null));\n }\n return () => { for (const r of releases) r(); };\n }, [menuHost, toolRegistry, ambient]);\n\n const entries = contributionEntries(tools);\n // Development only for a warning; a strict surface asked to be stopped, so\n // it throws in every build.\n if (versionCheck === 'strict' || (versionCheck === 'warn' && isDev())) {\n checkEntryRequirements(entries, versionCheck);\n }\n\n const reader = depRegistry ?? NO_DEPS;\n const installs = new Map<object, () => () => void>();\n for (const entry of entries) {\n const deps = entry.deps;\n if (deps && depRegistry) installs.set(deps, () => registerDeps(depRegistry, deps));\n const attach = entry.attach;\n if (attach && api) installs.set(attach, () => attach(api, reader));\n }\n useKeyedInstall(installs);\n useContributionFrameHooks(entries, api, reader);\n\n // Tool assembly runs above the provider too, so its own registration of\n // held-key engagement finds no registry unless the consumer mounted one.\n useOffhandAction(contributionEntries(tools));\n}\n\nfunction registerDeps(\n depRegistry: DepRegistry,\n deps: NonNullable<SurfaceContribution['deps']>,\n): () => void {\n const unregisters = (Object.keys(deps) as DepName[]).map((name) =>\n depRegistry.register(name, deps[name] as never));\n return () => { for (const u of unregisters) u(); };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useContributionRoles.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useContributionRoles.ts"],"sourcesContent":["/**\n * `useContributionRoles` — installs the roles of every entry in the tools\n * registry that a surface owns rather than the dispatcher: its actions into the\n * `<ActionsProvider>`, its deps into the dep registry, and its `attach` against\n * the mounted canvas. Views are the fourth such role; `<SceneCanvas>` renders\n * those itself, because a view is a component.\n *\n * Tool hooks run wherever the consumer calls them, which may be outside any\n * registry. Declaring roles on the entry and installing them from in here keeps\n * the tool hook callable from anywhere.\n */\nimport { useEffect, useRef } from 'react';\nimport { useActionsRegistry, useMenuHost, useOffhandAction, useOptionalDepRegistry } from '@weasel-js/routing/react';\nimport type { DepRegistry } from '@weasel-js/routing/react';\nimport type { DepName } from 'interactions/actions/depSchema';\nimport type { ToolsApi } from '../../tools/overlayBinding';\nimport type { CanvasExtensionApi } from '../canvasExtension';\nimport type { SurfaceContribution } from '../surfaceContribution';\nimport { checkRequirements, type RequirementMismatch } from '../kitRequirements';\nimport { useContributionFrameHooks } from './contributionFrameHooks';\nimport { isDev } from '@weasel-js/react';\n\n/** How a surface treats an entry whose `requires` the running kit does not\n * meet: warn in development (the default), throw, or not check. */\nexport type VersionCheck = 'warn' | 'strict' | 'off';\n\nconst NO_DEPS = { get: () => undefined };\nconst warnedRequirements = new WeakSet<object>();\n\nfunction describeMismatch(m: RequirementMismatch): string {\n switch (m.reason) {\n case 'unsatisfied': return `${m.package} ${m.range} (running ${m.running})`;\n case 'invalid-range': return `${m.package} \"${m.range}\", which is not a range this check reads`;\n case 'unknown-package': return `${m.package} ${m.range}, but ${m.package} reports no version`;\n }\n}\n\nfunction checkEntryRequirements(entries: readonly SurfaceContribution[], mode: VersionCheck): void {\n if (mode === 'off') return;\n for (const entry of entries) {\n const requires = entry.requires;\n if (!requires) continue;\n if (mode === 'warn' && warnedRequirements.has(requires)) continue;\n const mismatches = checkRequirements(requires);\n if (mismatches.length === 0) continue;\n const message = `[weasel] contribution \"${entry.id}\" requires ${mismatches.map(describeMismatch).join('; ')}.`;\n if (mode === 'strict') throw new Error(message);\n warnedRequirements.add(requires);\n console.warn(message);\n }\n}\n\n/** Every entry the registry holds, registry and ambient alike. */\nexport function contributionEntries(tools: Pick<ToolsApi, 'registry' | 'ambient'>): SurfaceContribution[] {\n return [...Object.values(tools.registry), ...tools.ambient] as SurfaceContribution[];\n}\n\n/**\n * Keep one installation per key alive: install keys that appeared, tear down\n * keys that went. Keys are role objects the consumer owns, so an entry the\n * host re-wraps every render — `useTools` restates eligibility on a copy —\n * still installs once.\n */\nfunction useKeyedInstall(\n items: ReadonlyMap<object, () => () => void>,\n): void {\n const live = useRef(new Map<object, () => void>());\n useEffect(() => {\n for (const [key, teardown] of live.current) {\n if (items.has(key)) continue;\n teardown();\n live.current.delete(key);\n }\n for (const [key, install] of items) {\n if (live.current.has(key)) continue;\n live.current.set(key, install());\n }\n });\n useEffect(() => () => {\n for (const teardown of live.current.values()) teardown();\n live.current.clear();\n }, []);\n}\n\nexport function useContributionRoles(\n tools: ToolsApi,\n api: CanvasExtensionApi | null,\n versionCheck: VersionCheck = 'warn',\n): void {\n const registry = useActionsRegistry();\n const depRegistry = useOptionalDepRegistry();\n const { registry: toolRegistry, ambient } = tools;\n\n useEffect(() => {\n if (!registry) return;\n const unregisters: Array<() => void> = [];\n // Ambient entries included: an always-on entry owns actions its own\n // bindings reference exactly as a registry tool does, and leaving them out\n // gives the same silent failure this hook exists to prevent — a binding\n // pointing at an id nothing registered.\n for (const entry of contributionEntries({ registry: toolRegistry, ambient })) {\n for (const action of entry.actions ?? []) {\n unregisters.push(registry.register(action));\n }\n }\n return () => { for (const u of unregisters) u(); };\n }, [registry, toolRegistry, ambient]);\n\n const menuHost = useMenuHost();\n useEffect(() => {\n if (!menuHost) return;\n const releases: Array<() => void> = [];\n for (const [id, entry] of Object.entries(toolRegistry)) {\n for (const def of (entry as SurfaceContribution).menus ?? []) releases.push(menuHost.define(def, id));\n }\n for (const entry of ambient) {\n for (const def of (entry as SurfaceContribution).menus ?? []) releases.push(menuHost.define(def, null));\n }\n return () => { for (const r of releases) r(); };\n }, [menuHost, toolRegistry, ambient]);\n\n const entries = contributionEntries(tools);\n // Development only for a warning; a strict surface asked to be stopped, so\n // it throws in every build.\n if (versionCheck === 'strict' || (versionCheck === 'warn' && isDev())) {\n checkEntryRequirements(entries, versionCheck);\n }\n\n const reader = depRegistry ?? NO_DEPS;\n const installs = new Map<object, () => () => void>();\n for (const entry of entries) {\n const deps = entry.deps;\n if (deps && depRegistry) installs.set(deps, () => registerDeps(depRegistry, deps));\n const watch = entry.watch;\n if (watch && depRegistry) installs.set(watch, () => watch(() => depRegistry.changed?.()));\n const attach = entry.attach;\n if (attach && api) installs.set(attach, () => attach(api, reader));\n }\n useKeyedInstall(installs);\n useContributionFrameHooks(entries, api, reader);\n\n // Tool assembly runs above the provider too, so its own registration of\n // held-key engagement finds no registry unless the consumer mounted one.\n useOffhandAction(contributionEntries(tools));\n}\n\nfunction registerDeps(\n depRegistry: DepRegistry,\n deps: NonNullable<SurfaceContribution['deps']>,\n): () => void {\n const unregisters = (Object.keys(deps) as DepName[]).map((name) =>\n depRegistry.register(name, deps[name] as never));\n return () => { for (const u of unregisters) u(); };\n}\n"],"mappings":"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"}
|
|
@@ -44,7 +44,7 @@ function buildSceneLayer(cfg, adapter, boundsOfFn, hideIds, slot) {
|
|
|
44
44
|
y: 0,
|
|
45
45
|
width: dims.width,
|
|
46
46
|
height: dims.height
|
|
47
|
-
}) : (cmds) => cmds;
|
|
47
|
+
}, dims.dpr) : (cmds) => cmds;
|
|
48
48
|
const paintBounds = cfg.cull ? cfg.paintBounds : void 0;
|
|
49
49
|
const culledIn = (view, dims) => paintBounds ? paintMissesView(paintBounds, view, {
|
|
50
50
|
x: 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildSceneLayer.js","names":[],"sources":["../../src/canvas/buildSceneLayer.ts"],"sourcesContent":["/**\n * The scene slot: an adapter's tree as render layers, one per scene layer.\n */\nimport type { Path } from '@weasel-js/geom';\nimport type { Dims, RenderLayer } from 'core/layers/render';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport type { View } from 'core/viewport/view';\nimport { cullDrawCommands, viewToMat3, type DrawCommand } from '@weasel-js/renderer';\nimport { adapterDerivedPathOf } from './adapterDerivedPath';\nimport { buildSceneTree, type HierarchicalAdapter } from './buildSceneTree';\nimport { debugSinkFrom } from './drawEnvelope';\nimport type { NodePaintCtx } from './NodeShape';\nimport { paintMissesView, type PaintBoundsFn } from './paintCull';\nimport { sceneLayerKey } from './sceneLayerPaint';\nimport { wrapNodeOutput } from './wrapNodeOutput';\n\n/** Scene slot config — describes how to draw one object with its effective pose. */\nexport interface SceneSlotConfig<TNode extends { id: string }, TPose> {\n /** Paint these, in this order, in place of the adapter's tree: one flat run with no\n * scene layers, no container clips, and no pose composition. */\n objects?: TNode[];\n /** The pose an object is painted at. Defaults to `adapter.getPose(obj.id)`. `<SceneCanvas>`\n * hands it each node as the scene shows it, its `pose` an override's or a derived one,\n * and carries an override's `path` into the box it answers; return a pose placed from\n * `obj.pose`, or a drag in flight and a rig's motion do not show in this view. */\n toPose?: (obj: TNode) => TPose;\n /** Draw a single object as a `DrawCommand` tree. `ctx` carries a derived\n * node's path: `<SceneCanvas>` resolves it from the scene, and\n * `buildSceneLayer` called without one from its adapter. */\n drawOne: (obj: TNode, pose: TPose, view: View, ctx?: NodePaintCtx) => DrawCommand[];\n /** The path a container derives from its dependencies' poses, for the clip\n * it imposes on its subtree — `drawOne`'s `ctx` carries the derived path a\n * node *paints*, this one the clip it *imposes*. A scene-aware caller\n * supplies it; left out, both are resolved off the adapter. */\n derivedPathOf?: (obj: TNode, pose: TPose) => Path | null;\n /** Default ghost alpha for the move-overlay slot. Default 0.85. */\n ghostAlpha?: number;\n /** Per-id alpha multiplier. A node's emitted `DrawCommand[]` is wrapped in a\n * `{ kind: 'group', alpha }` when the returned value is not 1. */\n alphaFor?: (id: string) => number;\n /**\n * Post-processor for the scene slot's emitted commands: called with the final\n * world-space `DrawCommand[]` each time a scene canvas layer draws (after per-node\n * rotation wrapping and `alphaFor`), once per layer when the slot is split into\n * `scene:<layerId>` layers. Its return value replaces the array handed to the renderer.\n *\n * Commands are world-space — `drawLayers` applies the view transform afterward. Clip\n * nesting is capped at 7 levels; a wrapping group with `clip` consumes one. The\n * move-overlay ghost (drag preview) is not post-processed.\n */\n postProcess?: (cmds: DrawCommand[], view: View, dims: Dims) => DrawCommand[];\n /**\n * Skip what cannot reach the view. A node `paintBounds` places outside the\n * `dims` rectangle under `view` is not painted at all; then, after\n * `postProcess`, `cullDrawCommands` drops any remaining command that cannot\n * reach it — see there for what it can and cannot bound. So `postProcess`\n * sees an empty group where a culled node would have painted.\n *\n * Off by default because it makes the layer's world-space output depend on\n * the view: anything that draws this layer once and shows the result under\n * another view — a cache keyed without `view`, a consumer re-wrapping the\n * commands — would show the first view's cull.\n */\n cull?: boolean;\n /**\n * Where `drawOne`'s output for a node can reach, in the frame it paints in\n * — before the pose rotation the slot wraps it in. `null` for a node it\n * cannot bound cheaply. Read only under `cull`, which skips `drawOne` for a\n * node whose box misses the view, so it must enclose everything `drawOne`\n * paints: a box too small makes a node vanish before it leaves the screen.\n *\n * `<SceneCanvas>` supplies `defaultPaintBounds` while `drawOne` is\n * `defaultDrawOne`. Omit it with any other `drawOne` and culling only\n * trims commands after painting.\n */\n paintBounds?: PaintBoundsFn<TNode, TPose>;\n}\n\n/**\n * `cfg`'s `drawOne` and clip lookup, resolving derived paths off the adapter\n * when nothing scene-aware filled `derivedPathOf` in.\n */\nfunction withAdapterDerivedPaths<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n toPose: (obj: TNode) => TPose,\n): Pick<SceneSlotConfig<TNode, TPose>, 'drawOne' | 'derivedPathOf'> {\n const drawOne = cfg.drawOne;\n if (cfg.derivedPathOf !== undefined || !drawOne || !adapter) return cfg;\n const pathOf = adapterDerivedPathOf<TPose>(cfg.toPose === undefined ? adapter : {\n getNode: (id) => adapter.getNode(id),\n getPose: (id) => {\n const obj = adapter.getNode(id);\n return obj ? toPose(obj) : adapter.getPose(id);\n },\n getChildren: (id) => adapter.getChildren(id),\n });\n return {\n derivedPathOf: (obj) => pathOf(obj.id),\n drawOne: (obj, pose, view, ctx) => ((obj as { dependsOn?: unknown }).dependsOn === undefined\n ? drawOne(obj, pose, view, ctx)\n : drawOne(obj, pose, view, { ...ctx, derivedPath: pathOf(obj.id) })),\n };\n}\n\n/**\n * Build the scene layer, which draws committed poses. `hideIds()` skips the committed paint\n * of ids the active tool is currently ghosting so the source doesn't show through. With a\n * debug sink on its draw envelope, it records each node's bounds and origin there.\n */\nexport function buildSceneLayer<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n boundsOfFn: ((id: string) => Bounds | null) | undefined,\n hideIds: () => Set<string> | null,\n /** When set, the returned layer carries this `id` and paints ONLY the named\n * scene layer (the rest are walked for ancestor clips). Omit for the single\n * bundled `scene` layer. */\n slot?: { id: string; forLayer: string },\n): RenderLayer<unknown> {\n const toPose =\n cfg.toPose ??\n ((obj: TNode) => (adapter ? adapter.getPose(obj.id) : (obj as unknown as TPose)));\n const { drawOne, derivedPathOf } = withAdapterDerivedPaths(cfg, adapter, toPose);\n const postProcess = cfg.postProcess;\n const finish = cfg.cull\n ? (cmds: DrawCommand[], view: View, dims: Dims): DrawCommand[] =>\n cullDrawCommands(cmds, viewToMat3(view), { x: 0, y: 0, width: dims.width, height: dims.height })\n : (cmds: DrawCommand[]): DrawCommand[] => cmds;\n const paintBounds = cfg.cull ? cfg.paintBounds : undefined;\n const culledIn = (view: View, dims: Dims) => paintBounds\n ? paintMissesView(paintBounds, view, { x: 0, y: 0, width: dims.width, height: dims.height })\n : undefined;\n return {\n id: slot?.id ?? 'scene',\n label: slot ? `Scene: ${slot.forLayer}` : 'Scene',\n draw: (data, view, dims) => {\n const debugSink = debugSinkFrom(data);\n const hidden = hideIds();\n const a = adapter;\n if (a && cfg.objects === undefined && drawOne) {\n const filteredDrawOne = (obj: TNode, pose: TPose, v: View): DrawCommand[] => {\n if (hidden && hidden.has(obj.id)) return [];\n const cmds = drawOne(obj, pose, v);\n if (debugSink) {\n const b = boundsOfFn ? boundsOfFn(obj.id) : null;\n if (b) debugSink.recordBounds(obj.id, b);\n const ox = (pose as { x?: number }).x ?? (b ? b.x : 0);\n const oy = (pose as { y?: number }).y ?? (b ? b.y : 0);\n debugSink.recordOrigin(obj.id, { x: ox, y: oy });\n }\n return wrapNodeOutput(cmds, pose, cfg.alphaFor ? cfg.alphaFor(obj.id) : 1);\n };\n // Honor cfg.toPose on the hierarchical path by shimming getPose on the\n // adapter so buildSceneTree routes through it instead of the raw adapter.\n const hierarchicalAdapter = cfg.toPose\n ? {\n ...a,\n getPose: (id: string) => {\n const obj = a.getNode(id);\n return obj ? toPose(obj) : a.getPose(id);\n },\n }\n : a;\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n const tree = buildSceneTree(\n hierarchicalAdapter as unknown as Parameters<typeof buildSceneTree>[0],\n filteredDrawOne as unknown as Parameters<typeof buildSceneTree>[1],\n view,\n slot?.forLayer,\n derivedPathOf as Parameters<typeof buildSceneTree>[4],\n culledIn(view, dims) as Parameters<typeof buildSceneTree>[5],\n );\n return finish(postProcess ? postProcess(tree, view, dims) : tree, view, dims);\n }\n // `cfg.objects`: the caller's own list, which has no scene layers to split by.\n const objects = cfg.objects ?? [];\n const children: DrawCommand[] = [];\n const culled = culledIn(view, dims);\n for (const obj of objects) {\n if (hidden && hidden.has(obj.id)) continue;\n const pose: TPose = toPose(obj);\n if (drawOne && !culled?.(obj, pose)) {\n const cmds = drawOne(obj, pose, view);\n const wrapped = wrapNodeOutput(cmds, pose, cfg.alphaFor ? cfg.alphaFor(obj.id) : 1);\n for (const cmd of wrapped) children.push(cmd);\n }\n if (debugSink) {\n const b = boundsOfFn ? boundsOfFn(obj.id) : null;\n if (b) debugSink.recordBounds(obj.id, { x: b.x, y: b.y, width: b.width, height: b.height });\n const ox = (pose as { x?: number }).x ?? (b ? b.x : 0);\n const oy = (pose as { y?: number }).y ?? (b ? b.y : 0);\n debugSink.recordOrigin(obj.id, { x: ox, y: oy });\n }\n }\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n return finish(postProcess ? postProcess(children, view, dims) : children, view, dims);\n },\n };\n}\n\n/**\n * The scene slot as one canvas layer **per scene layer** — keyed `scene:<layerId>` — so\n * consumers can interleave custom layers *between* scene layers via the standard slot\n * anchoring (`before: 'scene:plantings'`). A single `scene` layer when `cfg.objects` replaces\n * the tree, or the adapter declares no layers.\n */\nexport function buildSceneLayers<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n boundsOfFn: ((id: string) => Bounds | null) | undefined,\n hideIds: () => Set<string> | null,\n): Array<{ key: string; layer: RenderLayer<unknown> }> {\n const sceneLayerIds =\n cfg.objects === undefined && adapter ? adapter.getLayers().map((l) => l.id) : null;\n if (!sceneLayerIds || sceneLayerIds.length === 0) {\n return [{ key: 'scene', layer: buildSceneLayer(cfg, adapter, boundsOfFn, hideIds) }];\n }\n return sceneLayerIds.map((id) => ({\n key: sceneLayerKey(id),\n layer: buildSceneLayer(cfg, adapter, boundsOfFn, hideIds, {\n id: sceneLayerKey(id),\n forLayer: id,\n }),\n }));\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"buildSceneLayer.js","names":[],"sources":["../../src/canvas/buildSceneLayer.ts"],"sourcesContent":["/**\n * The scene slot: an adapter's tree as render layers, one per scene layer.\n */\nimport type { Path } from '@weasel-js/geom';\nimport type { Dims, LayerDims, RenderLayer } from 'core/layers/render';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport type { View } from 'core/viewport/view';\nimport { cullDrawCommands, viewToMat3, type DrawCommand } from '@weasel-js/renderer';\nimport { adapterDerivedPathOf } from './adapterDerivedPath';\nimport { buildSceneTree, type HierarchicalAdapter } from './buildSceneTree';\nimport { debugSinkFrom } from './drawEnvelope';\nimport type { NodePaintCtx } from './NodeShape';\nimport { paintMissesView, type PaintBoundsFn } from './paintCull';\nimport { sceneLayerKey } from './sceneLayerPaint';\nimport { wrapNodeOutput } from './wrapNodeOutput';\n\n/** Scene slot config — describes how to draw one object with its effective pose. */\nexport interface SceneSlotConfig<TNode extends { id: string }, TPose> {\n /** Paint these, in this order, in place of the adapter's tree: one flat run with no\n * scene layers, no container clips, and no pose composition. */\n objects?: TNode[];\n /** The pose an object is painted at. Defaults to `adapter.getPose(obj.id)`. `<SceneCanvas>`\n * hands it each node as the scene shows it, its `pose` an override's or a derived one,\n * and carries an override's `path` into the box it answers; return a pose placed from\n * `obj.pose`, or a drag in flight and a rig's motion do not show in this view. */\n toPose?: (obj: TNode) => TPose;\n /** Draw a single object as a `DrawCommand` tree. `ctx` carries a derived\n * node's path: `<SceneCanvas>` resolves it from the scene, and\n * `buildSceneLayer` called without one from its adapter. */\n drawOne: (obj: TNode, pose: TPose, view: View, ctx?: NodePaintCtx) => DrawCommand[];\n /** The path a container derives from its dependencies' poses, for the clip\n * it imposes on its subtree — `drawOne`'s `ctx` carries the derived path a\n * node *paints*, this one the clip it *imposes*. A scene-aware caller\n * supplies it; left out, both are resolved off the adapter. */\n derivedPathOf?: (obj: TNode, pose: TPose) => Path | null;\n /** Default ghost alpha for the move-overlay slot. Default 0.85. */\n ghostAlpha?: number;\n /** Per-id alpha multiplier. A node's emitted `DrawCommand[]` is wrapped in a\n * `{ kind: 'group', alpha }` when the returned value is not 1. */\n alphaFor?: (id: string) => number;\n /**\n * Post-processor for the scene slot's emitted commands: called with the final\n * world-space `DrawCommand[]` each time a scene canvas layer draws (after per-node\n * rotation wrapping and `alphaFor`), once per layer when the slot is split into\n * `scene:<layerId>` layers. Its return value replaces the array handed to the renderer.\n *\n * Commands are world-space — `drawLayers` applies the view transform afterward. Clip\n * nesting is capped at 7 levels; a wrapping group with `clip` consumes one. The\n * move-overlay ghost (drag preview) is not post-processed.\n */\n postProcess?: (cmds: DrawCommand[], view: View, dims: Dims) => DrawCommand[];\n /**\n * Skip what cannot reach the view. A node `paintBounds` places outside the\n * `dims` rectangle under `view` is not painted at all; then, after\n * `postProcess`, `cullDrawCommands` drops any remaining command that cannot\n * reach it — see there for what it can and cannot bound. So `postProcess`\n * sees an empty group where a culled node would have painted.\n *\n * Off by default because it makes the layer's world-space output depend on\n * the view: anything that draws this layer once and shows the result under\n * another view — a cache keyed without `view`, a consumer re-wrapping the\n * commands — would show the first view's cull.\n */\n cull?: boolean;\n /**\n * Where `drawOne`'s output for a node can reach, in the frame it paints in\n * — before the pose rotation the slot wraps it in. `null` for a node it\n * cannot bound cheaply. Read only under `cull`, which skips `drawOne` for a\n * node whose box misses the view, so it must enclose everything `drawOne`\n * paints: a box too small makes a node vanish before it leaves the screen.\n *\n * `<SceneCanvas>` supplies `defaultPaintBounds` while `drawOne` is\n * `defaultDrawOne`. Omit it with any other `drawOne` and culling only\n * trims commands after painting.\n */\n paintBounds?: PaintBoundsFn<TNode, TPose>;\n}\n\n/**\n * `cfg`'s `drawOne` and clip lookup, resolving derived paths off the adapter\n * when nothing scene-aware filled `derivedPathOf` in.\n */\nfunction withAdapterDerivedPaths<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n toPose: (obj: TNode) => TPose,\n): Pick<SceneSlotConfig<TNode, TPose>, 'drawOne' | 'derivedPathOf'> {\n const drawOne = cfg.drawOne;\n if (cfg.derivedPathOf !== undefined || !drawOne || !adapter) return cfg;\n const pathOf = adapterDerivedPathOf<TPose>(cfg.toPose === undefined ? adapter : {\n getNode: (id) => adapter.getNode(id),\n getPose: (id) => {\n const obj = adapter.getNode(id);\n return obj ? toPose(obj) : adapter.getPose(id);\n },\n getChildren: (id) => adapter.getChildren(id),\n });\n return {\n derivedPathOf: (obj) => pathOf(obj.id),\n drawOne: (obj, pose, view, ctx) => ((obj as { dependsOn?: unknown }).dependsOn === undefined\n ? drawOne(obj, pose, view, ctx)\n : drawOne(obj, pose, view, { ...ctx, derivedPath: pathOf(obj.id) })),\n };\n}\n\n/**\n * Build the scene layer, which draws committed poses. `hideIds()` skips the committed paint\n * of ids the active tool is currently ghosting so the source doesn't show through. With a\n * debug sink on its draw envelope, it records each node's bounds and origin there.\n */\nexport function buildSceneLayer<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n boundsOfFn: ((id: string) => Bounds | null) | undefined,\n hideIds: () => Set<string> | null,\n /** When set, the returned layer carries this `id` and paints ONLY the named\n * scene layer (the rest are walked for ancestor clips). Omit for the single\n * bundled `scene` layer. */\n slot?: { id: string; forLayer: string },\n): RenderLayer<unknown> {\n const toPose =\n cfg.toPose ??\n ((obj: TNode) => (adapter ? adapter.getPose(obj.id) : (obj as unknown as TPose)));\n const { drawOne, derivedPathOf } = withAdapterDerivedPaths(cfg, adapter, toPose);\n const postProcess = cfg.postProcess;\n const finish = cfg.cull\n ? (cmds: DrawCommand[], view: View, dims: LayerDims): DrawCommand[] =>\n cullDrawCommands(cmds, viewToMat3(view), { x: 0, y: 0, width: dims.width, height: dims.height }, dims.dpr)\n : (cmds: DrawCommand[]): DrawCommand[] => cmds;\n const paintBounds = cfg.cull ? cfg.paintBounds : undefined;\n const culledIn = (view: View, dims: Dims) => paintBounds\n ? paintMissesView(paintBounds, view, { x: 0, y: 0, width: dims.width, height: dims.height })\n : undefined;\n return {\n id: slot?.id ?? 'scene',\n label: slot ? `Scene: ${slot.forLayer}` : 'Scene',\n draw: (data, view, dims) => {\n const debugSink = debugSinkFrom(data);\n const hidden = hideIds();\n const a = adapter;\n if (a && cfg.objects === undefined && drawOne) {\n const filteredDrawOne = (obj: TNode, pose: TPose, v: View): DrawCommand[] => {\n if (hidden && hidden.has(obj.id)) return [];\n const cmds = drawOne(obj, pose, v);\n if (debugSink) {\n const b = boundsOfFn ? boundsOfFn(obj.id) : null;\n if (b) debugSink.recordBounds(obj.id, b);\n const ox = (pose as { x?: number }).x ?? (b ? b.x : 0);\n const oy = (pose as { y?: number }).y ?? (b ? b.y : 0);\n debugSink.recordOrigin(obj.id, { x: ox, y: oy });\n }\n return wrapNodeOutput(cmds, pose, cfg.alphaFor ? cfg.alphaFor(obj.id) : 1);\n };\n // Honor cfg.toPose on the hierarchical path by shimming getPose on the\n // adapter so buildSceneTree routes through it instead of the raw adapter.\n const hierarchicalAdapter = cfg.toPose\n ? {\n ...a,\n getPose: (id: string) => {\n const obj = a.getNode(id);\n return obj ? toPose(obj) : a.getPose(id);\n },\n }\n : a;\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n const tree = buildSceneTree(\n hierarchicalAdapter as unknown as Parameters<typeof buildSceneTree>[0],\n filteredDrawOne as unknown as Parameters<typeof buildSceneTree>[1],\n view,\n slot?.forLayer,\n derivedPathOf as Parameters<typeof buildSceneTree>[4],\n culledIn(view, dims) as Parameters<typeof buildSceneTree>[5],\n );\n return finish(postProcess ? postProcess(tree, view, dims) : tree, view, dims);\n }\n // `cfg.objects`: the caller's own list, which has no scene layers to split by.\n const objects = cfg.objects ?? [];\n const children: DrawCommand[] = [];\n const culled = culledIn(view, dims);\n for (const obj of objects) {\n if (hidden && hidden.has(obj.id)) continue;\n const pose: TPose = toPose(obj);\n if (drawOne && !culled?.(obj, pose)) {\n const cmds = drawOne(obj, pose, view);\n const wrapped = wrapNodeOutput(cmds, pose, cfg.alphaFor ? cfg.alphaFor(obj.id) : 1);\n for (const cmd of wrapped) children.push(cmd);\n }\n if (debugSink) {\n const b = boundsOfFn ? boundsOfFn(obj.id) : null;\n if (b) debugSink.recordBounds(obj.id, { x: b.x, y: b.y, width: b.width, height: b.height });\n const ox = (pose as { x?: number }).x ?? (b ? b.x : 0);\n const oy = (pose as { y?: number }).y ?? (b ? b.y : 0);\n debugSink.recordOrigin(obj.id, { x: ox, y: oy });\n }\n }\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n return finish(postProcess ? postProcess(children, view, dims) : children, view, dims);\n },\n };\n}\n\n/**\n * The scene slot as one canvas layer **per scene layer** — keyed `scene:<layerId>` — so\n * consumers can interleave custom layers *between* scene layers via the standard slot\n * anchoring (`before: 'scene:plantings'`). A single `scene` layer when `cfg.objects` replaces\n * the tree, or the adapter declares no layers.\n */\nexport function buildSceneLayers<TNode extends { id: string }, TPose>(\n cfg: SceneSlotConfig<TNode, TPose>,\n adapter: HierarchicalAdapter<TNode, TPose> | undefined,\n boundsOfFn: ((id: string) => Bounds | null) | undefined,\n hideIds: () => Set<string> | null,\n): Array<{ key: string; layer: RenderLayer<unknown> }> {\n const sceneLayerIds =\n cfg.objects === undefined && adapter ? adapter.getLayers().map((l) => l.id) : null;\n if (!sceneLayerIds || sceneLayerIds.length === 0) {\n return [{ key: 'scene', layer: buildSceneLayer(cfg, adapter, boundsOfFn, hideIds) }];\n }\n return sceneLayerIds.map((id) => ({\n key: sceneLayerKey(id),\n layer: buildSceneLayer(cfg, adapter, boundsOfFn, hideIds, {\n id: sceneLayerKey(id),\n forLayer: id,\n }),\n }));\n}\n"],"mappings":"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"}
|
|
@@ -18,6 +18,10 @@ var TENANTS = /* @__PURE__ */ new WeakMap();
|
|
|
18
18
|
/** Canvases whose context the renderer refused, so a redraw does not rebuild
|
|
19
19
|
* and fail again, and the reason is reported once. */
|
|
20
20
|
var REFUSED = /* @__PURE__ */ new WeakSet();
|
|
21
|
+
/** The density a canvas given `dpr` paints at. */
|
|
22
|
+
function resolveDpr(dpr) {
|
|
23
|
+
return dpr ?? (typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1);
|
|
24
|
+
}
|
|
21
25
|
/** Identifies a set of custom programs by their ids, in order. */
|
|
22
26
|
var shaderKeyOf = (shaders) => shaders?.map((h) => h.id).join("|") ?? "";
|
|
23
27
|
function registerShaders(renderer, shaders) {
|
|
@@ -42,7 +46,7 @@ function leaseCanvasRenderer() {
|
|
|
42
46
|
const restored = createEmitter();
|
|
43
47
|
const bind = (canvas, size, options = {}) => {
|
|
44
48
|
const { width, height } = size;
|
|
45
|
-
const dpr = size.dpr
|
|
49
|
+
const dpr = resolveDpr(size.dpr);
|
|
46
50
|
const tile = options.tile ?? null;
|
|
47
51
|
const shared = tile !== null;
|
|
48
52
|
if (held && (held.canvas !== canvas || held.shared !== shared)) {
|
|
@@ -186,5 +190,5 @@ function releaseCanvasRenderer(canvas) {
|
|
|
186
190
|
}
|
|
187
191
|
|
|
188
192
|
//#endregion
|
|
189
|
-
export { leaseCanvasRenderer, paintCanvas, releaseCanvasRenderer, shaderKeyOf };
|
|
193
|
+
export { leaseCanvasRenderer, paintCanvas, releaseCanvasRenderer, resolveDpr, shaderKeyOf };
|
|
190
194
|
//# sourceMappingURL=canvasRenderer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"canvasRenderer.js","names":["viewToMat3"],"sources":["../../src/canvas/canvasRenderer.ts"],"sourcesContent":["/**\n * The one renderer lifetime in core. Every on-screen surface holds its\n * `WeaselRenderer` through a `leaseCanvasRenderer` lease.\n *\n * A lease holds its own renderer, so several can share one element: each\n * tenant painting a `tile` of a shared buffer gets a renderer of its own on\n * the element's one context. Release frees only what that renderer created;\n * the context itself is never lost.\n */\nimport { WeaselRenderer, type FlattenTolerance, type ImageMinification, type RenderOptions } from '@weasel-js/renderer';\nimport { viewToMat3 } from '@weasel-js/geom';\nimport type { ShaderProgramHandle } from '@weasel-js/renderer';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { View } from '../core/viewport/view';\nimport { createEmitter } from '@weasel-js/registry';\nimport { CANVAS_CONTEXT_ATTRIBUTES } from './contextAttributes';\n\n/** The live renderers painting into each element, across every lease. */\nconst TENANTS: WeakMap<HTMLCanvasElement, Set<WeaselRenderer>> = new WeakMap();\n\n/** Canvases whose context the renderer refused, so a redraw does not rebuild\n * and fail again, and the reason is reported once. */\nconst REFUSED: WeakSet<HTMLCanvasElement> = new WeakSet();\n\n/** A renderer painting into the canvas, if any lease holds one. Test seam. */\nexport function cachedCanvasRenderer(canvas: HTMLCanvasElement): WeaselRenderer | undefined {\n const set = TENANTS.get(canvas);\n return set ? set.values().next().value : undefined;\n}\n\n/** The size and density to paint a canvas at. `dpr` defaults to\n * `window.devicePixelRatio`, read per paint. */\nexport interface CanvasPaintSize {\n width: number;\n height: number;\n dpr?: number;\n}\n\n/** How one paint reaches the canvas, beyond its size. */\nexport interface CanvasPaintOptions {\n /** Paint a `width × height` tile of a buffer other surfaces share, at this\n * origin in CSS pixels. The buffer is then the caller's to size: the\n * renderer never resizes the element, which would have every co-tenant\n * resize it to its own pane. Omit to own the whole element. */\n tile?: { x: number; y: number } | null;\n /** `WeaselRendererOptions.flattenTolerance`, applied from this paint on. */\n flattenTolerance?: FlattenTolerance;\n /** `WeaselRendererOptions.retainBudget`, applied from this paint on. */\n retainBudget?: number;\n /** `WeaselRendererOptions.imageMinification`, applied from this paint on. */\n imageMinification?: ImageMinification;\n /** Custom programs to compile on the renderer. Compiled when the renderer\n * is created, and again when the set of ids changes. */\n shaders?: readonly ShaderProgramHandle[];\n}\n\n/** A paint's options, plus what `paint` hands `WeaselRenderer.render`. */\nexport interface CanvasRenderOptions extends CanvasPaintOptions {\n render?: RenderOptions;\n}\n\n/** A canvas renderer held for a surface's lifetime. */\nexport interface CanvasRendererLease {\n /** Draw `commands` into `canvas` — screen-space, one unit per CSS pixel,\n * with `view` passed on as the frame's world→screen transform. `bind`,\n * then render. Returns `false` without painting where `bind` finds no\n * renderer. */\n paint(\n canvas: HTMLCanvasElement,\n commands: DrawCommand[],\n view: View,\n size: CanvasPaintSize,\n options?: CanvasRenderOptions,\n ): boolean;\n /** The renderer that paints `canvas` at `size`, ready to render: created on\n * first use, resized, and given this paint's tile, tolerance, retain\n * budget, and shaders.\n * Frees the previous renderer first if the surface moved to another\n * element, or between owning it whole and painting a tile of it. `null`\n * where no WebGL2 context is available (jsdom, a failed context creation,\n * or a context that is lost for now). */\n bind(canvas: HTMLCanvasElement, size: CanvasPaintSize, options?: CanvasPaintOptions): WeaselRenderer | null;\n /** Run `fn` when the canvas gets a lost context back, after the renderer\n * has rebuilt itself on it. Nothing repaints a restored buffer on its own.\n * Returns an unsubscribe. */\n subscribeRestored(fn: () => void): () => void;\n /** Free the held renderer and every GL object it owns. A later `paint`\n * creates a fresh one. */\n release(): void;\n}\n\ninterface Held {\n canvas: HTMLCanvasElement;\n renderer: WeaselRenderer;\n shared: boolean;\n lost: boolean;\n onLost: () => void;\n onRestored: () => void;\n width: number;\n height: number;\n dpr: number;\n tileX: number | null;\n tileY: number | null;\n flattenTolerance: FlattenTolerance | undefined;\n retainBudget: number | undefined;\n imageMinification: ImageMinification | undefined;\n shaderKey: string;\n}\n\n/** Identifies a set of custom programs by their ids, in order. */\nexport const shaderKeyOf = (shaders: readonly ShaderProgramHandle[] | undefined): string =>\n shaders?.map((h) => h.id).join('|') ?? '';\n\nfunction registerShaders(renderer: WeaselRenderer, shaders: readonly ShaderProgramHandle[] | undefined): void {\n if (!shaders) return;\n for (const handle of shaders) {\n try {\n renderer.registerProgram(handle);\n } catch (e) {\n console.warn(`weasel: failed to register shader \"${handle.id}\":`, e);\n }\n }\n}\n\nfunction free(held: Held): void {\n const set = TENANTS.get(held.canvas);\n set?.delete(held.renderer);\n held.canvas.removeEventListener('webglcontextlost', held.onLost);\n held.canvas.removeEventListener('webglcontextrestored', held.onRestored);\n if (set && set.size === 0) TENANTS.delete(held.canvas);\n held.renderer.dispose();\n}\n\n/** Hold the renderer of the canvas a surface paints into, until released. */\nexport function leaseCanvasRenderer(): CanvasRendererLease {\n let held: Held | null = null;\n const restored = createEmitter();\n const bind = (canvas: HTMLCanvasElement, size: CanvasPaintSize, options: CanvasPaintOptions = {}): WeaselRenderer | null => {\n const { width, height } = size;\n const dpr = size.dpr ?? (typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1);\n const tile = options.tile ?? null;\n const shared = tile !== null;\n\n if (held && (held.canvas !== canvas || held.shared !== shared)) {\n free(held);\n held = null;\n }\n if (!held) {\n if (REFUSED.has(canvas)) return null;\n const gl = canvas.getContext('webgl2', CANVAS_CONTEXT_ATTRIBUTES);\n if (!gl || typeof (gl as Partial<WebGL2RenderingContext>).enable !== 'function') return null;\n let renderer: WeaselRenderer;\n try {\n renderer = new WeaselRenderer({\n gl: gl as WebGL2RenderingContext,\n ...(shared ? {} : { canvas }),\n width, height, dpr,\n flattenTolerance: options.flattenTolerance,\n retainBudget: options.retainBudget,\n imageMinification: options.imageMinification,\n });\n } catch (e) {\n // A lost context fails to compile and may yet come back; anything\n // else fails the same way next time.\n if (!(gl as Partial<WebGL2RenderingContext>).isContextLost?.()) {\n REFUSED.add(canvas);\n console.warn('weasel: this canvas cannot be painted:', e);\n }\n return null;\n }\n // Added after the renderer's own listeners, so a subscriber runs against\n // a renderer that has already recompiled.\n const mine: Held = held = {\n canvas, renderer, shared, width, height, dpr, tileX: null, tileY: null,\n flattenTolerance: options.flattenTolerance, retainBudget: options.retainBudget,\n imageMinification: options.imageMinification, shaderKey: '',\n lost: false,\n onLost: () => { mine.lost = true; },\n onRestored: () => {\n mine.lost = false;\n restored.emit();\n },\n };\n canvas.addEventListener('webglcontextlost', held.onLost);\n canvas.addEventListener('webglcontextrestored', held.onRestored);\n let set = TENANTS.get(canvas);\n if (!set) { set = new Set(); TENANTS.set(canvas, set); }\n set.add(renderer);\n } else {\n // React writes the element's `width` and `height` attributes in CSS\n // pixels on every size change, so a size that went away and came back\n // matches the record while the buffer does not.\n const stale = !shared\n && (canvas.width !== Math.round(width * dpr) || canvas.height !== Math.round(height * dpr));\n if (stale || held.width !== width || held.height !== height || held.dpr !== dpr) {\n held.renderer.resize({ width, height, dpr });\n held.width = width;\n held.height = height;\n held.dpr = dpr;\n }\n if (held.flattenTolerance !== options.flattenTolerance) {\n held.renderer.setFlattenTolerance(options.flattenTolerance);\n held.flattenTolerance = options.flattenTolerance;\n }\n if (held.retainBudget !== options.retainBudget) {\n held.renderer.setRetainBudget(options.retainBudget);\n held.retainBudget = options.retainBudget;\n }\n if (held.imageMinification !== options.imageMinification) {\n held.renderer.setImageMinification(options.imageMinification);\n held.imageMinification = options.imageMinification;\n }\n }\n\n if (held.lost) return null;\n const { renderer } = held;\n const shaderKey = shaderKeyOf(options.shaders);\n if (shaderKey !== held.shaderKey) {\n registerShaders(renderer, options.shaders);\n held.shaderKey = shaderKey;\n }\n const tileX = tile?.x ?? null;\n const tileY = tile?.y ?? null;\n if (tileX !== held.tileX || tileY !== held.tileY) {\n renderer.setTarget(tile ? { origin: { x: tile.x, y: tile.y } } : null);\n held.tileX = tileX;\n held.tileY = tileY;\n }\n return renderer;\n };\n return {\n bind,\n paint(canvas, commands, view, size, options) {\n const renderer = bind(canvas, size, options);\n if (!renderer) return false;\n renderer.render(commands, viewToMat3(view), options?.render);\n return true;\n },\n subscribeRestored: restored.subscribe,\n release() {\n if (held) free(held);\n held = null;\n },\n };\n}\n\n/** Leases held on a canvas's behalf by `paintCanvas`, one per element. */\nconst CANVAS_LEASES: WeakMap<HTMLCanvasElement, CanvasRendererLease> = new WeakMap();\n\n/**\n * Paint `canvas` through a lease held for the element itself, for callers\n * with no component lifetime of their own. The renderer lives until\n * `releaseCanvasRenderer(canvas)`.\n */\nexport function paintCanvas(\n canvas: HTMLCanvasElement,\n commands: DrawCommand[],\n view: View,\n size: CanvasPaintSize,\n): boolean {\n let lease = CANVAS_LEASES.get(canvas);\n if (!lease) {\n lease = leaseCanvasRenderer();\n CANVAS_LEASES.set(canvas, lease);\n }\n return lease.paint(canvas, commands, view, size);\n}\n\n/** Free the renderer `paintCanvas` holds for the canvas, and every GL object\n * it owns. The next paint creates a fresh one. Renderers a surface component\n * holds are that component's to free, on unmount. */\nexport function releaseCanvasRenderer(canvas: HTMLCanvasElement): void {\n const lease = CANVAS_LEASES.get(canvas);\n if (!lease) return;\n CANVAS_LEASES.delete(canvas);\n lease.release();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"canvasRenderer.js","names":["viewToMat3"],"sources":["../../src/canvas/canvasRenderer.ts"],"sourcesContent":["/**\n * The one renderer lifetime in core. Every on-screen surface holds its\n * `WeaselRenderer` through a `leaseCanvasRenderer` lease.\n *\n * A lease holds its own renderer, so several can share one element: each\n * tenant painting a `tile` of a shared buffer gets a renderer of its own on\n * the element's one context. Release frees only what that renderer created;\n * the context itself is never lost.\n */\nimport { WeaselRenderer, type FlattenTolerance, type ImageMinification, type RenderOptions } from '@weasel-js/renderer';\nimport { viewToMat3 } from '@weasel-js/geom';\nimport type { ShaderProgramHandle } from '@weasel-js/renderer';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { View } from '../core/viewport/view';\nimport { createEmitter } from '@weasel-js/registry';\nimport { CANVAS_CONTEXT_ATTRIBUTES } from './contextAttributes';\n\n/** The live renderers painting into each element, across every lease. */\nconst TENANTS: WeakMap<HTMLCanvasElement, Set<WeaselRenderer>> = new WeakMap();\n\n/** Canvases whose context the renderer refused, so a redraw does not rebuild\n * and fail again, and the reason is reported once. */\nconst REFUSED: WeakSet<HTMLCanvasElement> = new WeakSet();\n\n/** A renderer painting into the canvas, if any lease holds one. Test seam. */\nexport function cachedCanvasRenderer(canvas: HTMLCanvasElement): WeaselRenderer | undefined {\n const set = TENANTS.get(canvas);\n return set ? set.values().next().value : undefined;\n}\n\n/** The size and density to paint a canvas at. `dpr` defaults to\n * `window.devicePixelRatio`, read per paint. */\nexport interface CanvasPaintSize {\n width: number;\n height: number;\n dpr?: number;\n}\n\n/** The density a canvas given `dpr` paints at. */\nexport function resolveDpr(dpr: number | undefined): number {\n return dpr ?? (typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1);\n}\n\n/** How one paint reaches the canvas, beyond its size. */\nexport interface CanvasPaintOptions {\n /** Paint a `width × height` tile of a buffer other surfaces share, at this\n * origin in CSS pixels. The buffer is then the caller's to size: the\n * renderer never resizes the element, which would have every co-tenant\n * resize it to its own pane. Omit to own the whole element. */\n tile?: { x: number; y: number } | null;\n /** `WeaselRendererOptions.flattenTolerance`, applied from this paint on. */\n flattenTolerance?: FlattenTolerance;\n /** `WeaselRendererOptions.retainBudget`, applied from this paint on. */\n retainBudget?: number;\n /** `WeaselRendererOptions.imageMinification`, applied from this paint on. */\n imageMinification?: ImageMinification;\n /** Custom programs to compile on the renderer. Compiled when the renderer\n * is created, and again when the set of ids changes. */\n shaders?: readonly ShaderProgramHandle[];\n}\n\n/** A paint's options, plus what `paint` hands `WeaselRenderer.render`. */\nexport interface CanvasRenderOptions extends CanvasPaintOptions {\n render?: RenderOptions;\n}\n\n/** A canvas renderer held for a surface's lifetime. */\nexport interface CanvasRendererLease {\n /** Draw `commands` into `canvas` — screen-space, one unit per CSS pixel,\n * with `view` passed on as the frame's world→screen transform. `bind`,\n * then render. Returns `false` without painting where `bind` finds no\n * renderer. */\n paint(\n canvas: HTMLCanvasElement,\n commands: DrawCommand[],\n view: View,\n size: CanvasPaintSize,\n options?: CanvasRenderOptions,\n ): boolean;\n /** The renderer that paints `canvas` at `size`, ready to render: created on\n * first use, resized, and given this paint's tile, tolerance, retain\n * budget, and shaders.\n * Frees the previous renderer first if the surface moved to another\n * element, or between owning it whole and painting a tile of it. `null`\n * where no WebGL2 context is available (jsdom, a failed context creation,\n * or a context that is lost for now). */\n bind(canvas: HTMLCanvasElement, size: CanvasPaintSize, options?: CanvasPaintOptions): WeaselRenderer | null;\n /** Run `fn` when the canvas gets a lost context back, after the renderer\n * has rebuilt itself on it. Nothing repaints a restored buffer on its own.\n * Returns an unsubscribe. */\n subscribeRestored(fn: () => void): () => void;\n /** Free the held renderer and every GL object it owns. A later `paint`\n * creates a fresh one. */\n release(): void;\n}\n\ninterface Held {\n canvas: HTMLCanvasElement;\n renderer: WeaselRenderer;\n shared: boolean;\n lost: boolean;\n onLost: () => void;\n onRestored: () => void;\n width: number;\n height: number;\n dpr: number;\n tileX: number | null;\n tileY: number | null;\n flattenTolerance: FlattenTolerance | undefined;\n retainBudget: number | undefined;\n imageMinification: ImageMinification | undefined;\n shaderKey: string;\n}\n\n/** Identifies a set of custom programs by their ids, in order. */\nexport const shaderKeyOf = (shaders: readonly ShaderProgramHandle[] | undefined): string =>\n shaders?.map((h) => h.id).join('|') ?? '';\n\nfunction registerShaders(renderer: WeaselRenderer, shaders: readonly ShaderProgramHandle[] | undefined): void {\n if (!shaders) return;\n for (const handle of shaders) {\n try {\n renderer.registerProgram(handle);\n } catch (e) {\n console.warn(`weasel: failed to register shader \"${handle.id}\":`, e);\n }\n }\n}\n\nfunction free(held: Held): void {\n const set = TENANTS.get(held.canvas);\n set?.delete(held.renderer);\n held.canvas.removeEventListener('webglcontextlost', held.onLost);\n held.canvas.removeEventListener('webglcontextrestored', held.onRestored);\n if (set && set.size === 0) TENANTS.delete(held.canvas);\n held.renderer.dispose();\n}\n\n/** Hold the renderer of the canvas a surface paints into, until released. */\nexport function leaseCanvasRenderer(): CanvasRendererLease {\n let held: Held | null = null;\n const restored = createEmitter();\n const bind = (canvas: HTMLCanvasElement, size: CanvasPaintSize, options: CanvasPaintOptions = {}): WeaselRenderer | null => {\n const { width, height } = size;\n const dpr = resolveDpr(size.dpr);\n const tile = options.tile ?? null;\n const shared = tile !== null;\n\n if (held && (held.canvas !== canvas || held.shared !== shared)) {\n free(held);\n held = null;\n }\n if (!held) {\n if (REFUSED.has(canvas)) return null;\n const gl = canvas.getContext('webgl2', CANVAS_CONTEXT_ATTRIBUTES);\n if (!gl || typeof (gl as Partial<WebGL2RenderingContext>).enable !== 'function') return null;\n let renderer: WeaselRenderer;\n try {\n renderer = new WeaselRenderer({\n gl: gl as WebGL2RenderingContext,\n ...(shared ? {} : { canvas }),\n width, height, dpr,\n flattenTolerance: options.flattenTolerance,\n retainBudget: options.retainBudget,\n imageMinification: options.imageMinification,\n });\n } catch (e) {\n // A lost context fails to compile and may yet come back; anything\n // else fails the same way next time.\n if (!(gl as Partial<WebGL2RenderingContext>).isContextLost?.()) {\n REFUSED.add(canvas);\n console.warn('weasel: this canvas cannot be painted:', e);\n }\n return null;\n }\n // Added after the renderer's own listeners, so a subscriber runs against\n // a renderer that has already recompiled.\n const mine: Held = held = {\n canvas, renderer, shared, width, height, dpr, tileX: null, tileY: null,\n flattenTolerance: options.flattenTolerance, retainBudget: options.retainBudget,\n imageMinification: options.imageMinification, shaderKey: '',\n lost: false,\n onLost: () => { mine.lost = true; },\n onRestored: () => {\n mine.lost = false;\n restored.emit();\n },\n };\n canvas.addEventListener('webglcontextlost', held.onLost);\n canvas.addEventListener('webglcontextrestored', held.onRestored);\n let set = TENANTS.get(canvas);\n if (!set) { set = new Set(); TENANTS.set(canvas, set); }\n set.add(renderer);\n } else {\n // React writes the element's `width` and `height` attributes in CSS\n // pixels on every size change, so a size that went away and came back\n // matches the record while the buffer does not.\n const stale = !shared\n && (canvas.width !== Math.round(width * dpr) || canvas.height !== Math.round(height * dpr));\n if (stale || held.width !== width || held.height !== height || held.dpr !== dpr) {\n held.renderer.resize({ width, height, dpr });\n held.width = width;\n held.height = height;\n held.dpr = dpr;\n }\n if (held.flattenTolerance !== options.flattenTolerance) {\n held.renderer.setFlattenTolerance(options.flattenTolerance);\n held.flattenTolerance = options.flattenTolerance;\n }\n if (held.retainBudget !== options.retainBudget) {\n held.renderer.setRetainBudget(options.retainBudget);\n held.retainBudget = options.retainBudget;\n }\n if (held.imageMinification !== options.imageMinification) {\n held.renderer.setImageMinification(options.imageMinification);\n held.imageMinification = options.imageMinification;\n }\n }\n\n if (held.lost) return null;\n const { renderer } = held;\n const shaderKey = shaderKeyOf(options.shaders);\n if (shaderKey !== held.shaderKey) {\n registerShaders(renderer, options.shaders);\n held.shaderKey = shaderKey;\n }\n const tileX = tile?.x ?? null;\n const tileY = tile?.y ?? null;\n if (tileX !== held.tileX || tileY !== held.tileY) {\n renderer.setTarget(tile ? { origin: { x: tile.x, y: tile.y } } : null);\n held.tileX = tileX;\n held.tileY = tileY;\n }\n return renderer;\n };\n return {\n bind,\n paint(canvas, commands, view, size, options) {\n const renderer = bind(canvas, size, options);\n if (!renderer) return false;\n renderer.render(commands, viewToMat3(view), options?.render);\n return true;\n },\n subscribeRestored: restored.subscribe,\n release() {\n if (held) free(held);\n held = null;\n },\n };\n}\n\n/** Leases held on a canvas's behalf by `paintCanvas`, one per element. */\nconst CANVAS_LEASES: WeakMap<HTMLCanvasElement, CanvasRendererLease> = new WeakMap();\n\n/**\n * Paint `canvas` through a lease held for the element itself, for callers\n * with no component lifetime of their own. The renderer lives until\n * `releaseCanvasRenderer(canvas)`.\n */\nexport function paintCanvas(\n canvas: HTMLCanvasElement,\n commands: DrawCommand[],\n view: View,\n size: CanvasPaintSize,\n): boolean {\n let lease = CANVAS_LEASES.get(canvas);\n if (!lease) {\n lease = leaseCanvasRenderer();\n CANVAS_LEASES.set(canvas, lease);\n }\n return lease.paint(canvas, commands, view, size);\n}\n\n/** Free the renderer `paintCanvas` holds for the canvas, and every GL object\n * it owns. The next paint creates a fresh one. Renderers a surface component\n * holds are that component's to free, on unmount. */\nexport function releaseCanvasRenderer(canvas: HTMLCanvasElement): void {\n const lease = CANVAS_LEASES.get(canvas);\n if (!lease) return;\n CANVAS_LEASES.delete(canvas);\n lease.release();\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { nodeAsShown, shownLookup } from "../core/scene/nodeAsShown.js";
|
|
2
2
|
import { withInheritedPaintBounds, withInheritedPaints } from "./inheritedPaints.js";
|
|
3
3
|
import { paintedSceneLayers } from "./sceneLayerPaint.js";
|
|
4
|
-
import { paintCanvas } from "./canvasRenderer.js";
|
|
4
|
+
import { paintCanvas, resolveDpr } from "./canvasRenderer.js";
|
|
5
5
|
import { wrapNodeOutput } from "./wrapNodeOutput.js";
|
|
6
6
|
import { sceneDerivedPathOf, withDerivedPaths } from "./derivedPath.js";
|
|
7
7
|
import { buildSceneTree } from "./buildSceneTree.js";
|
|
@@ -64,7 +64,7 @@ function buildSceneViewCommands(scene, view, drawOne, extraCommands, alphaFor, l
|
|
|
64
64
|
} : void 0;
|
|
65
65
|
const culled = cullRect && cull?.paintBounds ? paintMissesView(withShownNode(scene.overrides, withInheritedPaintBounds(shown, cull.paintBounds)), view, cullRect) : void 0;
|
|
66
66
|
const tree = buildSceneTree(hierarchy, wrappedDrawOne, view, void 0, derivedPathOf, culled);
|
|
67
|
-
const children = cullRect ? cullDrawCommands(tree, viewToMat3$1(view), cullRect) : tree;
|
|
67
|
+
const children = cull && cullRect ? cullDrawCommands(tree, viewToMat3$1(view), cullRect, cull.dpr) : tree;
|
|
68
68
|
if (extraCommands && extraCommands.length > 0) for (const cmd of extraCommands) children.push(cmd);
|
|
69
69
|
return [{
|
|
70
70
|
kind: "group",
|
|
@@ -78,13 +78,14 @@ function buildSceneViewCommands(scene, view, drawOne, extraCommands, alphaFor, l
|
|
|
78
78
|
* renderer lease.
|
|
79
79
|
*/
|
|
80
80
|
function sceneViewFrame(args) {
|
|
81
|
-
const { scene, view, width, height, drawOne, extraCommands, alphaFor, layerVisibility, layerOrder, colorOverrides, cull } = args;
|
|
81
|
+
const { scene, view, width, height, drawOne, extraCommands, alphaFor, layerVisibility, layerOrder, colorOverrides, cull, dpr } = args;
|
|
82
82
|
return buildSceneViewCommands(scene, view, drawOne, extraCommands, alphaFor, {
|
|
83
83
|
layerVisibility,
|
|
84
84
|
layerOrder
|
|
85
85
|
}, colorOverrides, cull ? {
|
|
86
86
|
width,
|
|
87
87
|
height,
|
|
88
|
+
dpr: resolveDpr(dpr),
|
|
88
89
|
paintBounds: args.paintBounds ?? paintBoundsFor(drawOne)
|
|
89
90
|
} : void 0);
|
|
90
91
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sceneViewRender.js","names":["viewToMat3"],"sources":["../../src/canvas/sceneViewRender.ts"],"sourcesContent":["/**\n * `renderSceneToCanvas` — substrate for detached, read-only scene views\n * (`<SceneViewCanvas>` / `<MinimapCanvas>`). Given a `<canvas>`, a `Scene`,\n * a `View`, and a per-node `drawOne`, render one frame.\n */\n\nimport type { WeaselRenderer } from '@weasel-js/renderer';\nimport { cachedCanvasRenderer, paintCanvas } from './canvasRenderer';\nimport { viewToMat3 } from '@weasel-js/geom';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { View } from '../core/viewport/view';\nimport type { Node, NodeId, Scene } from '../core/scene/types';\nimport { wrapNodeOutput } from './wrapNodeOutput';\nimport { buildSceneTree, type HierarchicalAdapter } from './buildSceneTree';\nimport { sceneToAdapter } from './sceneAdapter';\nimport { withInheritedPaintBounds, withInheritedPaints } from './inheritedPaints';\nimport { withDerivedPaths, sceneDerivedPathOf } from './derivedPath';\nimport { withColorOverrides } from './colorOverrides';\nimport type { ColorOverrideRegistry } from '@weasel-js/animator';\nimport type { SceneViewDrawOne } from './NodeShape';\nimport { paintedSceneLayers } from './sceneLayerPaint';\nimport { cullDrawCommands } from '@weasel-js/renderer';\nimport { paintMissesView, type PaintBoundsFn } from './paintCull';\nimport { paintBoundsFor } from './defaultDrawOne';\nimport { nodeAsShown, shownLookup } from 'core/scene/nodeAsShown';\nimport { withShownNode } from './shownNodes';\n\nexport type { SceneViewDrawOne } from './NodeShape';\n\n/**\n * Which scene layers a detached view paints, on top of each layer's own\n * `visible` flag. Keyed the way `<SceneCanvas>` keys its scene layers —\n * `scene:<layerId>` — so the map a main canvas takes serves its minimap too.\n */\nexport interface SceneViewLayers {\n /** `false` hides that layer in this view. It cannot show one the scene hides. */\n layerVisibility?: Record<string, boolean>;\n /** Paint order, bottom first. A listed order is the whole list: a scene\n * layer left out of it is not painted. */\n layerOrder?: readonly string[];\n}\n\n/** The view size `buildSceneViewCommands` culls to, and how it bounds a\n * node's paint before painting it. */\nexport interface SceneViewCull<TData, TLayer extends string, TPose> {\n width: number;\n height: number;\n paintBounds?: PaintBoundsFn<Node<TData, TLayer, TPose>, TPose>;\n}\n\n/** What to draw into an existing canvas: the scene, the view, and the same\n * painting hooks `<SceneCanvas>` takes. */\nexport interface RenderSceneToCanvasArgs<TData, TLayer extends string, TPose> {\n /** The DOM canvas to paint into. The first call on a given canvas creates\n * its renderer and later calls reuse it;\n * `releaseCanvasRenderer(canvas)` frees it. */\n canvas: HTMLCanvasElement;\n scene: Scene<TData, TLayer, TPose>;\n /** Camera view applied to scene and `extraCommands` alike. */\n view: View;\n /** CSS-pixel size. The drawing buffer is set to `width × dpr` and\n * `height × dpr`; the element's CSS size is the caller's to set. */\n width: number;\n height: number;\n drawOne: SceneViewDrawOne<TData, TLayer, TPose>;\n /** Commands appended after the scene-node output, wrapped in the same view\n * transform as scene commands. */\n extraCommands?: DrawCommand[];\n /** Per-id alpha multiplier, mirroring `<SceneCanvas>`'s scene-slot\n * `alphaFor`. Defaults to `() => 1`. */\n alphaFor?: (id: string) => number;\n /** Per-view layer visibility. See {@link SceneViewLayers}. */\n layerVisibility?: SceneViewLayers['layerVisibility'];\n /** Per-view layer order. See {@link SceneViewLayers}. */\n layerOrder?: SceneViewLayers['layerOrder'];\n /** Animated vertex colors to paint, typically an animator's\n * `colorOverrides` — see `NodePaintCtx.vertexColors`. */\n colorOverrides?: ColorOverrideRegistry;\n /** Skip what cannot reach the `width × height` view: nodes `paintBounds`\n * places outside it are not painted, and scene commands that cannot reach\n * it are dropped before the renderer — the scene slot's `cull`. Off by\n * default. `extraCommands` are never culled. */\n cull?: boolean;\n /** Where `drawOne`'s output for a node can reach, as on the scene slot.\n * Defaults to `defaultPaintBounds` when `drawOne` is `defaultDrawOne`. */\n paintBounds?: PaintBoundsFn<Node<TData, TLayer, TPose>, TPose>;\n /** Device-pixel ratio. Defaults to `window.devicePixelRatio || 1` when\n * available, otherwise 1. */\n dpr?: number;\n}\n\n/** Test seam: the renderer `renderSceneToCanvas` created for a canvas. Not\n * re-exported from the barrel. */\nexport function __getCachedRendererForTest(\n canvas: HTMLCanvasElement,\n): WeaselRenderer | undefined {\n return cachedCanvasRenderer(canvas);\n}\n\n/**\n * Present a `Scene` as the reading surface `buildSceneTree` walks: the scene\n * adapter's own reads, under this view's layer list.\n */\nfunction sceneAsHierarchy<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n layers: SceneViewLayers = {},\n): HierarchicalAdapter<Node<TData, TLayer, TPose>, TPose> {\n const { getNode, getChildren, getPose, composePose } = sceneToAdapter(scene);\n return {\n getLayers: () =>\n paintedSceneLayers(scene.layers, layers)\n .map((l) => ({ id: l.id, visible: l.visible, parallax: l.parallax })),\n getNode,\n getChildren,\n getPose,\n ...(composePose ? { composePose } : {}),\n };\n}\n\n/**\n * Build the DrawCommand list that `renderSceneToCanvas` would dispatch, in\n * screen space. Pure — no GL, no DOM. Walks the scene through `buildSceneTree`,\n * the same walk `<SceneCanvas>` uses, which is what keeps hidden layers\n * unpainted and container clips applied here.\n *\n * Each node's commands go through `wrapNodeOutput` — pose rotation, then the\n * `alphaFor` multiplier — so a `drawOne` that rotates its own output rotates\n * twice; emit unrotated geometry and let the pose drive it.\n *\n * Output is one group per **visible** layer, in scene-layer order (or\n * `layers.layerOrder`), each holding one group per node on that layer.\n */\nexport function buildSceneViewCommands<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n view: View,\n drawOne: SceneViewDrawOne<TData, TLayer, TPose>,\n extraCommands?: ReadonlyArray<DrawCommand>,\n alphaFor?: (id: string) => number,\n /** This view's own layer visibility and order. */\n layers?: SceneViewLayers,\n /** Animated vertex colors to paint, typically an animator's `colorOverrides`. */\n colorOverrides?: ColorOverrideRegistry,\n /** Cull the scene to this CSS-pixel view size — see\n * `RenderSceneToCanvasArgs.cull`. Omit to paint every node. */\n cull?: SceneViewCull<TData, TLayer, TPose>,\n): DrawCommand[] {\n const shown = shownLookup(scene);\n const derivedOnly = withDerivedPaths(scene, withInheritedPaints(shown, drawOne));\n const derived = colorOverrides ? withColorOverrides(colorOverrides, derivedOnly) : derivedOnly;\n const derivedPathAt = sceneDerivedPathOf(scene);\n const derivedPathOf = (node: Node<TData, TLayer, TPose>) => derivedPathAt(nodeAsShown(scene.overrides, node));\n const wrappedDrawOne = (\n node: Node<TData, TLayer, TPose>,\n pose: TPose,\n v: View,\n ): DrawCommand[] => {\n const overrideAlpha = scene.overrides.read(node.id as NodeId)?.alpha ?? 1;\n return wrapNodeOutput(\n derived(nodeAsShown(scene.overrides, node), pose, v),\n pose,\n (alphaFor ? alphaFor(node.id) : 1) * overrideAlpha,\n );\n };\n\n const hierarchy = sceneAsHierarchy(scene, layers);\n\n const cullRect = cull ? { x: 0, y: 0, width: cull.width, height: cull.height } : undefined;\n const culled = cullRect && cull?.paintBounds\n ? paintMissesView(withShownNode(scene.overrides, withInheritedPaintBounds(shown, cull.paintBounds)), view, cullRect)\n : undefined;\n const tree: DrawCommand[] = buildSceneTree(\n hierarchy as Parameters<typeof buildSceneTree>[0],\n wrappedDrawOne as unknown as Parameters<typeof buildSceneTree>[1],\n view,\n undefined,\n derivedPathOf as unknown as Parameters<typeof buildSceneTree>[4],\n culled as Parameters<typeof buildSceneTree>[5],\n );\n const children = cullRect ? cullDrawCommands(tree, viewToMat3(view), cullRect) : tree;\n if (extraCommands && extraCommands.length > 0) {\n for (const cmd of extraCommands) children.push(cmd);\n }\n return [{\n kind: 'group',\n transform: viewToMat3(view),\n children,\n }];\n}\n\n/**\n * The commands `renderSceneToCanvas` paints for one frame — everything in\n * `args` but the canvas. `<SceneViewCanvas>` paints these through its own\n * renderer lease.\n */\nexport function sceneViewFrame<TData, TLayer extends string, TPose>(\n args: Omit<RenderSceneToCanvasArgs<TData, TLayer, TPose>, 'canvas'>,\n): DrawCommand[] {\n const {\n scene, view, width, height, drawOne, extraCommands, alphaFor, layerVisibility, layerOrder,\n colorOverrides, cull,\n } = args;\n return buildSceneViewCommands(\n scene, view, drawOne, extraCommands, alphaFor, { layerVisibility, layerOrder }, colorOverrides,\n cull ? { width, height, paintBounds: args.paintBounds ?? paintBoundsFor(drawOne) } : undefined,\n );\n}\n\n/**\n * Render one frame of `scene` at `view` into `canvas`. Where no WebGL2\n * context is available (jsdom) the call paints nothing. The canvas's renderer\n * lives until `releaseCanvasRenderer(canvas)`.\n */\nexport function renderSceneToCanvas<TData, TLayer extends string, TPose>(\n args: RenderSceneToCanvasArgs<TData, TLayer, TPose>,\n): void {\n const { canvas, view, width, height, dpr } = args;\n paintCanvas(canvas, sceneViewFrame(args), view, { width, height, dpr });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuGA,SAAS,iBACP,OACA,SAA0B,CAAC,GAC6B;CACxD,MAAM,EAAE,SAAS,aAAa,SAAS,gBAAgB,eAAe,KAAK;CAC3E,OAAO;EACL,iBACE,mBAAmB,MAAM,QAAQ,MAAM,CAAC,CACrC,KAAK,OAAO;GAAE,IAAI,EAAE;GAAI,SAAS,EAAE;GAAS,UAAU,EAAE;EAAS,EAAE;EACxE;EACA;EACA;EACA,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;CACvC;AACF;;;;;;;;;;;;;;AAeA,SAAgB,uBACd,OACA,MACA,SACA,eACA,UAEA,QAEA,gBAGA,MACe;CACf,MAAM,QAAQ,YAAY,KAAK;CAC/B,MAAM,cAAc,iBAAiB,OAAO,oBAAoB,OAAO,OAAO,CAAC;CAC/E,MAAM,UAAU,iBAAiB,mBAAmB,gBAAgB,WAAW,IAAI;CACnF,MAAM,gBAAgB,mBAAmB,KAAK;CAC9C,MAAM,iBAAiB,SAAqC,cAAc,YAAY,MAAM,WAAW,IAAI,CAAC;CAC5G,MAAM,kBACJ,MACA,MACA,MACkB;EAClB,MAAM,gBAAgB,MAAM,UAAU,KAAK,KAAK,EAAY,CAAC,EAAE,SAAS;EACxE,OAAO,eACL,QAAQ,YAAY,MAAM,WAAW,IAAI,GAAG,MAAM,CAAC,GACnD,OACC,WAAW,SAAS,KAAK,EAAE,IAAI,KAAK,aACvC;CACF;CAEA,MAAM,YAAY,iBAAiB,OAAO,MAAM;CAEhD,MAAM,WAAW,OAAO;EAAE,GAAG;EAAG,GAAG;EAAG,OAAO,KAAK;EAAO,QAAQ,KAAK;CAAO,IAAI;CACjF,MAAM,SAAS,YAAY,MAAM,cAC7B,gBAAgB,cAAc,MAAM,WAAW,yBAAyB,OAAO,KAAK,WAAW,CAAC,GAAG,MAAM,QAAQ,IACjH;CACJ,MAAM,OAAsB,eAC1B,WACA,gBACA,MACA,QACA,eACA,MACF;CACA,MAAM,WAAW,WAAW,iBAAiB,MAAMA,aAAW,IAAI,GAAG,QAAQ,IAAI;CACjF,IAAI,iBAAiB,cAAc,SAAS,GAC1C,KAAK,MAAM,OAAO,eAAe,SAAS,KAAK,GAAG;CAEpD,OAAO,CAAC;EACN,MAAM;EACN,WAAWA,aAAW,IAAI;EAC1B;CACF,CAAC;AACH;;;;;;AAOA,SAAgB,eACd,MACe;CACf,MAAM,EACJ,OAAO,MAAM,OAAO,QAAQ,SAAS,eAAe,UAAU,iBAAiB,YAC/E,gBAAgB,SACd;CACJ,OAAO,uBACL,OAAO,MAAM,SAAS,eAAe,UAAU;EAAE;EAAiB;CAAW,GAAG,gBAChF,OAAO;EAAE;EAAO;EAAQ,aAAa,KAAK,eAAe,eAAe,OAAO;CAAE,IAAI,MACvF;AACF;;;;;;AAOA,SAAgB,oBACd,MACM;CACN,MAAM,EAAE,QAAQ,MAAM,OAAO,QAAQ,QAAQ;CAC7C,YAAY,QAAQ,eAAe,IAAI,GAAG,MAAM;EAAE;EAAO;EAAQ;CAAI,CAAC;AACxE"}
|
|
1
|
+
{"version":3,"file":"sceneViewRender.js","names":["viewToMat3"],"sources":["../../src/canvas/sceneViewRender.ts"],"sourcesContent":["/**\n * `renderSceneToCanvas` — substrate for detached, read-only scene views\n * (`<SceneViewCanvas>` / `<MinimapCanvas>`). Given a `<canvas>`, a `Scene`,\n * a `View`, and a per-node `drawOne`, render one frame.\n */\n\nimport type { WeaselRenderer } from '@weasel-js/renderer';\nimport { cachedCanvasRenderer, paintCanvas, resolveDpr } from './canvasRenderer';\nimport { viewToMat3 } from '@weasel-js/geom';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { View } from '../core/viewport/view';\nimport type { Node, NodeId, Scene } from '../core/scene/types';\nimport { wrapNodeOutput } from './wrapNodeOutput';\nimport { buildSceneTree, type HierarchicalAdapter } from './buildSceneTree';\nimport { sceneToAdapter } from './sceneAdapter';\nimport { withInheritedPaintBounds, withInheritedPaints } from './inheritedPaints';\nimport { withDerivedPaths, sceneDerivedPathOf } from './derivedPath';\nimport { withColorOverrides } from './colorOverrides';\nimport type { ColorOverrideRegistry } from '@weasel-js/animator';\nimport type { SceneViewDrawOne } from './NodeShape';\nimport { paintedSceneLayers } from './sceneLayerPaint';\nimport { cullDrawCommands } from '@weasel-js/renderer';\nimport { paintMissesView, type PaintBoundsFn } from './paintCull';\nimport { paintBoundsFor } from './defaultDrawOne';\nimport { nodeAsShown, shownLookup } from 'core/scene/nodeAsShown';\nimport { withShownNode } from './shownNodes';\n\nexport type { SceneViewDrawOne } from './NodeShape';\n\n/**\n * Which scene layers a detached view paints, on top of each layer's own\n * `visible` flag. Keyed the way `<SceneCanvas>` keys its scene layers —\n * `scene:<layerId>` — so the map a main canvas takes serves its minimap too.\n */\nexport interface SceneViewLayers {\n /** `false` hides that layer in this view. It cannot show one the scene hides. */\n layerVisibility?: Record<string, boolean>;\n /** Paint order, bottom first. A listed order is the whole list: a scene\n * layer left out of it is not painted. */\n layerOrder?: readonly string[];\n}\n\n/** The view size `buildSceneViewCommands` culls to, the density it will be\n * painted at, and how it bounds a node's paint before painting it. */\nexport interface SceneViewCull<TData, TLayer extends string, TPose> {\n width: number;\n height: number;\n dpr: number;\n paintBounds?: PaintBoundsFn<Node<TData, TLayer, TPose>, TPose>;\n}\n\n/** What to draw into an existing canvas: the scene, the view, and the same\n * painting hooks `<SceneCanvas>` takes. */\nexport interface RenderSceneToCanvasArgs<TData, TLayer extends string, TPose> {\n /** The DOM canvas to paint into. The first call on a given canvas creates\n * its renderer and later calls reuse it;\n * `releaseCanvasRenderer(canvas)` frees it. */\n canvas: HTMLCanvasElement;\n scene: Scene<TData, TLayer, TPose>;\n /** Camera view applied to scene and `extraCommands` alike. */\n view: View;\n /** CSS-pixel size. The drawing buffer is set to `width × dpr` and\n * `height × dpr`; the element's CSS size is the caller's to set. */\n width: number;\n height: number;\n drawOne: SceneViewDrawOne<TData, TLayer, TPose>;\n /** Commands appended after the scene-node output, wrapped in the same view\n * transform as scene commands. */\n extraCommands?: DrawCommand[];\n /** Per-id alpha multiplier, mirroring `<SceneCanvas>`'s scene-slot\n * `alphaFor`. Defaults to `() => 1`. */\n alphaFor?: (id: string) => number;\n /** Per-view layer visibility. See {@link SceneViewLayers}. */\n layerVisibility?: SceneViewLayers['layerVisibility'];\n /** Per-view layer order. See {@link SceneViewLayers}. */\n layerOrder?: SceneViewLayers['layerOrder'];\n /** Animated vertex colors to paint, typically an animator's\n * `colorOverrides` — see `NodePaintCtx.vertexColors`. */\n colorOverrides?: ColorOverrideRegistry;\n /** Skip what cannot reach the `width × height` view: nodes `paintBounds`\n * places outside it are not painted, and scene commands that cannot reach\n * it are dropped before the renderer — the scene slot's `cull`. Off by\n * default. `extraCommands` are never culled. */\n cull?: boolean;\n /** Where `drawOne`'s output for a node can reach, as on the scene slot.\n * Defaults to `defaultPaintBounds` when `drawOne` is `defaultDrawOne`. */\n paintBounds?: PaintBoundsFn<Node<TData, TLayer, TPose>, TPose>;\n /** Device-pixel ratio. Defaults to `window.devicePixelRatio || 1` when\n * available, otherwise 1. */\n dpr?: number;\n}\n\n/** Test seam: the renderer `renderSceneToCanvas` created for a canvas. Not\n * re-exported from the barrel. */\nexport function __getCachedRendererForTest(\n canvas: HTMLCanvasElement,\n): WeaselRenderer | undefined {\n return cachedCanvasRenderer(canvas);\n}\n\n/**\n * Present a `Scene` as the reading surface `buildSceneTree` walks: the scene\n * adapter's own reads, under this view's layer list.\n */\nfunction sceneAsHierarchy<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n layers: SceneViewLayers = {},\n): HierarchicalAdapter<Node<TData, TLayer, TPose>, TPose> {\n const { getNode, getChildren, getPose, composePose } = sceneToAdapter(scene);\n return {\n getLayers: () =>\n paintedSceneLayers(scene.layers, layers)\n .map((l) => ({ id: l.id, visible: l.visible, parallax: l.parallax })),\n getNode,\n getChildren,\n getPose,\n ...(composePose ? { composePose } : {}),\n };\n}\n\n/**\n * Build the DrawCommand list that `renderSceneToCanvas` would dispatch, in\n * screen space. Pure — no GL, no DOM. Walks the scene through `buildSceneTree`,\n * the same walk `<SceneCanvas>` uses, which is what keeps hidden layers\n * unpainted and container clips applied here.\n *\n * Each node's commands go through `wrapNodeOutput` — pose rotation, then the\n * `alphaFor` multiplier — so a `drawOne` that rotates its own output rotates\n * twice; emit unrotated geometry and let the pose drive it.\n *\n * Output is one group per **visible** layer, in scene-layer order (or\n * `layers.layerOrder`), each holding one group per node on that layer.\n */\nexport function buildSceneViewCommands<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n view: View,\n drawOne: SceneViewDrawOne<TData, TLayer, TPose>,\n extraCommands?: ReadonlyArray<DrawCommand>,\n alphaFor?: (id: string) => number,\n /** This view's own layer visibility and order. */\n layers?: SceneViewLayers,\n /** Animated vertex colors to paint, typically an animator's `colorOverrides`. */\n colorOverrides?: ColorOverrideRegistry,\n /** Cull the scene to this CSS-pixel view size — see\n * `RenderSceneToCanvasArgs.cull`. Omit to paint every node. */\n cull?: SceneViewCull<TData, TLayer, TPose>,\n): DrawCommand[] {\n const shown = shownLookup(scene);\n const derivedOnly = withDerivedPaths(scene, withInheritedPaints(shown, drawOne));\n const derived = colorOverrides ? withColorOverrides(colorOverrides, derivedOnly) : derivedOnly;\n const derivedPathAt = sceneDerivedPathOf(scene);\n const derivedPathOf = (node: Node<TData, TLayer, TPose>) => derivedPathAt(nodeAsShown(scene.overrides, node));\n const wrappedDrawOne = (\n node: Node<TData, TLayer, TPose>,\n pose: TPose,\n v: View,\n ): DrawCommand[] => {\n const overrideAlpha = scene.overrides.read(node.id as NodeId)?.alpha ?? 1;\n return wrapNodeOutput(\n derived(nodeAsShown(scene.overrides, node), pose, v),\n pose,\n (alphaFor ? alphaFor(node.id) : 1) * overrideAlpha,\n );\n };\n\n const hierarchy = sceneAsHierarchy(scene, layers);\n\n const cullRect = cull ? { x: 0, y: 0, width: cull.width, height: cull.height } : undefined;\n const culled = cullRect && cull?.paintBounds\n ? paintMissesView(withShownNode(scene.overrides, withInheritedPaintBounds(shown, cull.paintBounds)), view, cullRect)\n : undefined;\n const tree: DrawCommand[] = buildSceneTree(\n hierarchy as Parameters<typeof buildSceneTree>[0],\n wrappedDrawOne as unknown as Parameters<typeof buildSceneTree>[1],\n view,\n undefined,\n derivedPathOf as unknown as Parameters<typeof buildSceneTree>[4],\n culled as Parameters<typeof buildSceneTree>[5],\n );\n const children = cull && cullRect ? cullDrawCommands(tree, viewToMat3(view), cullRect, cull.dpr) : tree;\n if (extraCommands && extraCommands.length > 0) {\n for (const cmd of extraCommands) children.push(cmd);\n }\n return [{\n kind: 'group',\n transform: viewToMat3(view),\n children,\n }];\n}\n\n/**\n * The commands `renderSceneToCanvas` paints for one frame — everything in\n * `args` but the canvas. `<SceneViewCanvas>` paints these through its own\n * renderer lease.\n */\nexport function sceneViewFrame<TData, TLayer extends string, TPose>(\n args: Omit<RenderSceneToCanvasArgs<TData, TLayer, TPose>, 'canvas'>,\n): DrawCommand[] {\n const {\n scene, view, width, height, drawOne, extraCommands, alphaFor, layerVisibility, layerOrder,\n colorOverrides, cull, dpr,\n } = args;\n return buildSceneViewCommands(\n scene, view, drawOne, extraCommands, alphaFor, { layerVisibility, layerOrder }, colorOverrides,\n cull ? { width, height, dpr: resolveDpr(dpr), paintBounds: args.paintBounds ?? paintBoundsFor(drawOne) } : undefined,\n );\n}\n\n/**\n * Render one frame of `scene` at `view` into `canvas`. Where no WebGL2\n * context is available (jsdom) the call paints nothing. The canvas's renderer\n * lives until `releaseCanvasRenderer(canvas)`.\n */\nexport function renderSceneToCanvas<TData, TLayer extends string, TPose>(\n args: RenderSceneToCanvasArgs<TData, TLayer, TPose>,\n): void {\n const { canvas, view, width, height, dpr } = args;\n paintCanvas(canvas, sceneViewFrame(args), view, { width, height, dpr });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAwGA,SAAS,iBACP,OACA,SAA0B,CAAC,GAC6B;CACxD,MAAM,EAAE,SAAS,aAAa,SAAS,gBAAgB,eAAe,KAAK;CAC3E,OAAO;EACL,iBACE,mBAAmB,MAAM,QAAQ,MAAM,CAAC,CACrC,KAAK,OAAO;GAAE,IAAI,EAAE;GAAI,SAAS,EAAE;GAAS,UAAU,EAAE;EAAS,EAAE;EACxE;EACA;EACA;EACA,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;CACvC;AACF;;;;;;;;;;;;;;AAeA,SAAgB,uBACd,OACA,MACA,SACA,eACA,UAEA,QAEA,gBAGA,MACe;CACf,MAAM,QAAQ,YAAY,KAAK;CAC/B,MAAM,cAAc,iBAAiB,OAAO,oBAAoB,OAAO,OAAO,CAAC;CAC/E,MAAM,UAAU,iBAAiB,mBAAmB,gBAAgB,WAAW,IAAI;CACnF,MAAM,gBAAgB,mBAAmB,KAAK;CAC9C,MAAM,iBAAiB,SAAqC,cAAc,YAAY,MAAM,WAAW,IAAI,CAAC;CAC5G,MAAM,kBACJ,MACA,MACA,MACkB;EAClB,MAAM,gBAAgB,MAAM,UAAU,KAAK,KAAK,EAAY,CAAC,EAAE,SAAS;EACxE,OAAO,eACL,QAAQ,YAAY,MAAM,WAAW,IAAI,GAAG,MAAM,CAAC,GACnD,OACC,WAAW,SAAS,KAAK,EAAE,IAAI,KAAK,aACvC;CACF;CAEA,MAAM,YAAY,iBAAiB,OAAO,MAAM;CAEhD,MAAM,WAAW,OAAO;EAAE,GAAG;EAAG,GAAG;EAAG,OAAO,KAAK;EAAO,QAAQ,KAAK;CAAO,IAAI;CACjF,MAAM,SAAS,YAAY,MAAM,cAC7B,gBAAgB,cAAc,MAAM,WAAW,yBAAyB,OAAO,KAAK,WAAW,CAAC,GAAG,MAAM,QAAQ,IACjH;CACJ,MAAM,OAAsB,eAC1B,WACA,gBACA,MACA,QACA,eACA,MACF;CACA,MAAM,WAAW,QAAQ,WAAW,iBAAiB,MAAMA,aAAW,IAAI,GAAG,UAAU,KAAK,GAAG,IAAI;CACnG,IAAI,iBAAiB,cAAc,SAAS,GAC1C,KAAK,MAAM,OAAO,eAAe,SAAS,KAAK,GAAG;CAEpD,OAAO,CAAC;EACN,MAAM;EACN,WAAWA,aAAW,IAAI;EAC1B;CACF,CAAC;AACH;;;;;;AAOA,SAAgB,eACd,MACe;CACf,MAAM,EACJ,OAAO,MAAM,OAAO,QAAQ,SAAS,eAAe,UAAU,iBAAiB,YAC/E,gBAAgB,MAAM,QACpB;CACJ,OAAO,uBACL,OAAO,MAAM,SAAS,eAAe,UAAU;EAAE;EAAiB;CAAW,GAAG,gBAChF,OAAO;EAAE;EAAO;EAAQ,KAAK,WAAW,GAAG;EAAG,aAAa,KAAK,eAAe,eAAe,OAAO;CAAE,IAAI,MAC7G;AACF;;;;;;AAOA,SAAgB,oBACd,MACM;CACN,MAAM,EAAE,QAAQ,MAAM,OAAO,QAAQ,QAAQ;CAC7C,YAAY,QAAQ,eAAe,IAAI,GAAG,MAAM;EAAE;EAAO;EAAQ;CAAI,CAAC;AACxE"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_LAYER_RETAIN } from "../core/layers/retain.js";
|
|
2
2
|
import { drawLayers } from "../core/layers/render.js";
|
|
3
|
+
import { resolveDpr } from "./canvasRenderer.js";
|
|
3
4
|
import { beginDebugFrame } from "../debug/useDebugSink.js";
|
|
4
5
|
import { NO_LAYER_VISIBILITY } from "./useExtraLayers.js";
|
|
5
6
|
import { viewToMat3 } from "@weasel-js/renderer";
|
|
@@ -59,7 +60,8 @@ function useCanvasPaint(paintRef, lease, target, frame, markPainted) {
|
|
|
59
60
|
const spans = debugFrame?.time();
|
|
60
61
|
const commands = drawLayers(paintLayers, helpersRef.current, vis ?? NO_LAYER_VISIBILITY, order, view, {
|
|
61
62
|
width: w,
|
|
62
|
-
height: h
|
|
63
|
+
height: h,
|
|
64
|
+
dpr: resolveDpr(dprIn)
|
|
63
65
|
}, layerCacheRef.current, void 0, groups, spans, retainPolicy(retain));
|
|
64
66
|
renderer.render(commands, viewToMat3(view), spans ? { spans } : void 0);
|
|
65
67
|
debugFrame?.end(renderer.lastFrameStats());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCanvasPaint.js","names":[],"sources":["../../src/canvas/useCanvasPaint.ts"],"sourcesContent":["import { useCallback, useEffect, useInsertionEffect, useRef } from 'react';\nimport { useLatest } from '@weasel-js/react';\nimport { viewToMat3 } from '@weasel-js/renderer';\nimport type { View } from 'core/viewport/view';\nimport {\n DEFAULT_LAYER_RETAIN, drawLayers, type LayerCommandCache, type LayerRetainPolicy, type RenderLayer,\n} from 'core/layers/render';\nimport type { CanvasDebugSink } from '../debug/types';\nimport { beginDebugFrame } from '../debug/useDebugSink';\nimport type
|
|
1
|
+
{"version":3,"file":"useCanvasPaint.js","names":[],"sources":["../../src/canvas/useCanvasPaint.ts"],"sourcesContent":["import { useCallback, useEffect, useInsertionEffect, useRef } from 'react';\nimport { useLatest } from '@weasel-js/react';\nimport { viewToMat3 } from '@weasel-js/renderer';\nimport type { View } from 'core/viewport/view';\nimport {\n DEFAULT_LAYER_RETAIN, drawLayers, type LayerCommandCache, type LayerRetainPolicy, type RenderLayer,\n} from 'core/layers/render';\nimport type { CanvasDebugSink } from '../debug/types';\nimport { beginDebugFrame } from '../debug/useDebugSink';\nimport { resolveDpr, type CanvasRendererLease } from './canvasRenderer';\nimport type { CanvasProps } from './canvasProps';\nimport type { CanvasElements } from './useCanvasElements';\nimport { NO_LAYER_VISIBILITY, type LayerGate } from './useExtraLayers';\n\n/** What a frame's paint reads that a React render owns. */\nexport interface PaintInputs extends Pick<CanvasProps<unknown>, 'dpr' | 'layerGroups' | 'shaders' | 'flattenTolerance' | 'imageMinification' | 'retain'> {\n layers: RenderLayer<unknown>[];\n width: number;\n height: number;\n debugSink: CanvasDebugSink | null;\n frameStats: boolean;\n}\n\n/** What a frame's paint reads that changes outside a render. */\nexport interface PaintFrame {\n inputsRef: { readonly current: PaintInputs };\n viewRef: { readonly current: View };\n gateRef: { readonly current: LayerGate };\n helpersRef: { readonly current: unknown };\n}\n\n/**\n * Which content version the last paint showed. `markPainted` records the current one, read\n * from `contentVersion`'s latest committed value.\n */\nexport function usePaintedVersion(contentVersion: (() => number) | undefined): {\n getPaintedVersion: () => number;\n markPainted: () => void;\n} {\n const contentVersionRef = useLatest(contentVersion);\n const paintedVersionRef = useRef(0);\n const getPaintedVersion = useCallback(() => paintedVersionRef.current, []);\n const markPainted = useCallback(() => {\n paintedVersionRef.current = contentVersionRef.current?.() ?? 0;\n }, [contentVersionRef]);\n return { getPaintedVersion, markPainted };\n}\n\nfunction retainPolicy(retain: PaintInputs['retain']): LayerRetainPolicy | false {\n if (retain === false) return false;\n const minCommands = retain?.minCommands;\n return minCommands === undefined ? DEFAULT_LAYER_RETAIN : { minCommands };\n}\n\n/**\n * Publish the canvas's paint into `paintRef`, which the frame loop calls. The paint binds the\n * leased renderer to whichever canvas pixels go to and draws the layer stack through the\n * current view. The layer command cache lives as long as the canvas.\n */\nexport function useCanvasPaint(\n paintRef: { current: () => boolean },\n lease: CanvasRendererLease,\n target: Pick<CanvasElements, 'paintTargetRef' | 'ownCanvasRef' | 'paintRectRef'>,\n frame: PaintFrame,\n markPainted: () => void,\n): void {\n const { paintTargetRef, ownCanvasRef, paintRectRef } = target;\n const { inputsRef, viewRef, gateRef, helpersRef } = frame;\n const layerCacheRef = useRef<LayerCommandCache>(new Map());\n\n const paint = useCallback((): boolean => {\n const c = paintTargetRef.current ?? ownCanvasRef.current;\n if (!c) return false;\n const rect = paintRectRef.current;\n const {\n layers: paintLayers, width: w, height: h, debugSink: sink, frameStats: timed,\n dpr: dprIn,\n flattenTolerance: paintFlattenTolerance,\n layerGroups: groups, shaders: paintShaders, retain, imageMinification,\n } = inputsRef.current;\n if (w === 0 || h === 0) return false;\n\n const debugFrame = beginDebugFrame(sink, timed, paintLayers);\n\n const renderer = lease.bind(c, { width: w, height: h, dpr: dprIn }, {\n tile: rect,\n flattenTolerance: paintFlattenTolerance,\n retainBudget: retain ? retain.budget : undefined,\n imageMinification,\n shaders: paintShaders,\n });\n // No WebGL2 (jsdom), or a context that is lost for now: nothing to paint.\n if (!renderer) return false;\n\n const view = viewRef.current;\n const { layerVisibility: vis, layerOrder: order } = gateRef.current;\n const spans = debugFrame?.time();\n const commands = drawLayers(\n paintLayers,\n helpersRef.current,\n vis ?? NO_LAYER_VISIBILITY,\n order,\n view,\n { width: w, height: h, dpr: resolveDpr(dprIn) },\n layerCacheRef.current,\n undefined,\n groups,\n spans,\n retainPolicy(retain),\n );\n renderer.render(commands, viewToMat3(view), spans ? { spans } : undefined);\n debugFrame?.end(renderer.lastFrameStats());\n markPainted();\n return true;\n }, [gateRef, helpersRef, inputsRef, lease, markPainted, ownCanvasRef, paintRectRef, paintTargetRef, viewRef]);\n useInsertionEffect(() => {\n paintRef.current = paint;\n });\n\n useEffect(() => {\n const layerCache = layerCacheRef.current;\n return () => { layerCache.clear(); };\n }, []);\n}\n"],"mappings":";;;;;;;;;;;;;;AAmCA,SAAgB,kBAAkB,gBAGhC;CACA,MAAM,oBAAoB,UAAU,cAAc;CAClD,MAAM,oBAAoB,OAAO,CAAC;CAKlC,OAAO;EAAE,mBAJiB,kBAAkB,kBAAkB,SAAS,CAAC,CAI/D;EAAmB,aAHR,kBAAkB;GACpC,kBAAkB,UAAU,kBAAkB,UAAU,KAAK;EAC/D,GAAG,CAAC,iBAAiB,CACO;CAAY;AAC1C;AAEA,SAAS,aAAa,QAA0D;CAC9E,IAAI,WAAW,OAAO,OAAO;CAC7B,MAAM,cAAc,QAAQ;CAC5B,OAAO,gBAAgB,SAAY,uBAAuB,EAAE,YAAY;AAC1E;;;;;;AAOA,SAAgB,eACd,UACA,OACA,QACA,OACA,aACM;CACN,MAAM,EAAE,gBAAgB,cAAc,iBAAiB;CACvD,MAAM,EAAE,WAAW,SAAS,SAAS,eAAe;CACpD,MAAM,gBAAgB,uBAA0B,IAAI,IAAI,CAAC;CAEzD,MAAM,QAAQ,kBAA2B;EACvC,MAAM,IAAI,eAAe,WAAW,aAAa;EACjD,IAAI,CAAC,GAAG,OAAO;EACf,MAAM,OAAO,aAAa;EAC1B,MAAM,EACJ,QAAQ,aAAa,OAAO,GAAG,QAAQ,GAAG,WAAW,MAAM,YAAY,OACvE,KAAK,OACL,kBAAkB,uBAClB,aAAa,QAAQ,SAAS,cAAc,QAAQ,sBAClD,UAAU;EACd,IAAI,MAAM,KAAK,MAAM,GAAG,OAAO;EAE/B,MAAM,aAAa,gBAAgB,MAAM,OAAO,WAAW;EAE3D,MAAM,WAAW,MAAM,KAAK,GAAG;GAAE,OAAO;GAAG,QAAQ;GAAG,KAAK;EAAM,GAAG;GAClE,MAAM;GACN,kBAAkB;GAClB,cAAc,SAAS,OAAO,SAAS;GACvC;GACA,SAAS;EACX,CAAC;EAED,IAAI,CAAC,UAAU,OAAO;EAEtB,MAAM,OAAO,QAAQ;EACrB,MAAM,EAAE,iBAAiB,KAAK,YAAY,UAAU,QAAQ;EAC5D,MAAM,QAAQ,YAAY,KAAK;EAC/B,MAAM,WAAW,WACf,aACA,WAAW,SACX,OAAO,qBACP,OACA,MACA;GAAE,OAAO;GAAG,QAAQ;GAAG,KAAK,WAAW,KAAK;EAAE,GAC9C,cAAc,SACd,QACA,QACA,OACA,aAAa,MAAM,CACrB;EACA,SAAS,OAAO,UAAU,WAAW,IAAI,GAAG,QAAQ,EAAE,MAAM,IAAI,MAAS;EACzE,YAAY,IAAI,SAAS,eAAe,CAAC;EACzC,YAAY;EACZ,OAAO;CACT,GAAG;EAAC;EAAS;EAAY;EAAW;EAAO;EAAa;EAAc;EAAc;EAAgB;CAAO,CAAC;CAC5G,yBAAyB;EACvB,SAAS,UAAU;CACrB,CAAC;CAED,gBAAgB;EACd,MAAM,aAAa,cAAc;EACjC,aAAa;GAAE,WAAW,MAAM;EAAG;CACrC,GAAG,CAAC,CAAC;AACP"}
|