@weasel-js/core 1.11.1 → 1.12.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 +184 -0
- package/dist/WeaselProvider.js +1 -1
- package/dist/affordances/pathAnchors.js +10 -6
- package/dist/affordances/pathAnchors.js.map +1 -1
- package/dist/barrel/canvas.js +3 -1
- package/dist/barrel/paint.js +4 -4
- package/dist/barrel/paths.js +8 -4
- package/dist/barrel/renderer.js +3 -2
- package/dist/barrel/routing.js +4 -4
- package/dist/barrel/scene.js +2 -1
- package/dist/barrel/text.js +2 -2
- package/dist/barrel/viewport.js +6 -2
- package/dist/canvas/Canvas.js +2 -1
- package/dist/canvas/Canvas.js.map +1 -1
- package/dist/canvas/CanvasView.js +23 -34
- package/dist/canvas/CanvasView.js.map +1 -1
- package/dist/canvas/ModalityHud.js +1 -1
- package/dist/canvas/NodeShape.js +4 -1
- package/dist/canvas/NodeShape.js.map +1 -1
- package/dist/canvas/SceneCanvas/ActionsProviderIfRoot.js +1 -1
- package/dist/canvas/SceneCanvas/DepRegistryProviderIfRoot.js +1 -1
- package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +1 -1
- package/dist/canvas/SceneCanvas/SurfaceDeclarations.js +23 -0
- package/dist/canvas/SceneCanvas/SurfaceDeclarations.js.map +1 -0
- package/dist/canvas/SceneCanvas/canvasPassThrough.js +1 -0
- package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
- package/dist/canvas/SceneCanvas/props.js.map +1 -1
- package/dist/canvas/SceneCanvas/scenePointQuery.js +7 -3
- package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -1
- package/dist/canvas/SceneCanvas/useActionTunings.js +6 -1
- package/dist/canvas/SceneCanvas/useActionTunings.js.map +1 -1
- package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js +1 -0
- package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js.map +1 -1
- package/dist/canvas/SceneCanvas/useCanvasTools.js +7 -2
- package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js +9 -3
- package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +10 -2
- package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +1 -0
- package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js +19 -4
- package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js +2 -1
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
- package/dist/canvas/SceneCanvas.js +11 -10
- package/dist/canvas/SceneCanvas.js.map +1 -1
- package/dist/canvas/ViewMarker.js +35 -0
- package/dist/canvas/ViewMarker.js.map +1 -0
- package/dist/canvas/defaultDrawOne.js +2 -0
- package/dist/canvas/defaultDrawOne.js.map +1 -1
- package/dist/canvas/deps/booleans.js +2 -1
- package/dist/canvas/deps/booleans.js.map +1 -1
- package/dist/canvas/deps/hitTestArea.js +2 -2
- package/dist/canvas/deps/hitTestArea.js.map +1 -1
- package/dist/canvas/deps/insert.js +6 -2
- package/dist/canvas/deps/insert.js.map +1 -1
- package/dist/canvas/deps/pathEditCommits.js +3 -7
- package/dist/canvas/deps/pathEditCommits.js.map +1 -1
- package/dist/canvas/drawnBounds.js +11 -3
- package/dist/canvas/drawnBounds.js.map +1 -1
- package/dist/canvas/inheritedPaints.js +44 -0
- package/dist/canvas/inheritedPaints.js.map +1 -0
- package/dist/canvas/nodeShapes/derivedPainter.js +15 -16
- package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/nodePaint.js +13 -34
- package/dist/canvas/nodeShapes/nodePaint.js.map +1 -1
- package/dist/canvas/nodeShapes/pathPainter.js +9 -8
- package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/rectFallbackPainter.js +4 -5
- package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/shapePainter.js +11 -10
- package/dist/canvas/nodeShapes/shapePainter.js.map +1 -1
- package/dist/canvas/nodeShapes/textPainter.js +13 -12
- package/dist/canvas/nodeShapes/textPainter.js.map +1 -1
- package/dist/canvas/pickReach.js +3 -3
- package/dist/canvas/pickReach.js.map +1 -1
- package/dist/canvas/pickWalk.js +3 -1
- package/dist/canvas/pickWalk.js.map +1 -1
- package/dist/canvas/renderSceneToPixels.js +1 -1
- package/dist/canvas/renderSceneToPixels.js.map +1 -1
- package/dist/canvas/sceneSlotWiring.js +2 -1
- package/dist/canvas/sceneSlotWiring.js.map +1 -1
- package/dist/canvas/sceneViewRender.js +2 -1
- package/dist/canvas/sceneViewRender.js.map +1 -1
- package/dist/canvas/surfaceContribution.js +12 -8
- package/dist/canvas/surfaceContribution.js.map +1 -1
- package/dist/canvas/useCanvasView.js +4 -2
- package/dist/canvas/useCanvasView.js.map +1 -1
- package/dist/canvas/useLayerStack.js +6 -2
- package/dist/canvas/useLayerStack.js.map +1 -1
- package/dist/canvas/useViewAim.js +121 -0
- package/dist/canvas/useViewAim.js.map +1 -0
- package/dist/canvas/useViewNesting.js +3 -2
- package/dist/canvas/useViewNesting.js.map +1 -1
- package/dist/canvas/useViewRegistrySurface.js +4 -2
- package/dist/canvas/useViewRegistrySurface.js.map +1 -1
- package/dist/canvas/viewMarkers.js +63 -0
- package/dist/canvas/viewMarkers.js.map +1 -0
- package/dist/canvas/viewRegistry.js +27 -1
- package/dist/canvas/viewRegistry.js.map +1 -1
- package/dist/core/pages/pageEdits.js +2 -3
- package/dist/core/pages/pageEdits.js.map +1 -1
- package/dist/core/paint/meshKindLazy.js +59 -2
- package/dist/core/paint/meshKindLazy.js.map +1 -1
- package/dist/core/scene/clipNothing.js +13 -0
- package/dist/core/scene/clipNothing.js.map +1 -0
- package/dist/core/scene/depMemo.js +11 -3
- package/dist/core/scene/depMemo.js.map +1 -1
- package/dist/core/scene/dependents.js +7 -1
- package/dist/core/scene/dependents.js.map +1 -1
- package/dist/core/scene/derivedParts.js +10 -11
- package/dist/core/scene/derivedParts.js.map +1 -1
- package/dist/core/scene/effectivePose.js +7 -2
- package/dist/core/scene/effectivePose.js.map +1 -1
- package/dist/core/scene/fontReads.js +37 -0
- package/dist/core/scene/fontReads.js.map +1 -0
- package/dist/core/scene/inheritedPaint.js +75 -0
- package/dist/core/scene/inheritedPaint.js.map +1 -0
- package/dist/core/scene/kitRegistry.js +5 -0
- package/dist/core/scene/kitRegistry.js.map +1 -1
- package/dist/core/scene/nodeMemo.js +15 -7
- package/dist/core/scene/nodeMemo.js.map +1 -1
- package/dist/core/scene/pathEffects/blend.js.map +1 -1
- package/dist/core/scene/pathEffects/derive.js +7 -2
- package/dist/core/scene/pathEffects/derive.js.map +1 -1
- package/dist/core/scene/pathEffects/effects.js +3 -1
- package/dist/core/scene/pathEffects/effects.js.map +1 -1
- package/dist/core/scene/pathEffects/textAlong.js +95 -0
- package/dist/core/scene/pathEffects/textAlong.js.map +1 -0
- package/dist/{cornerHandles-BHuoMs1o.d.ts → cornerHandles-p8fDIAKt.d.ts} +2 -1
- package/dist/{depSchema-BpZNOv7q.d.ts → depSchema-mG19aEdD.d.ts} +61 -13
- package/dist/devtools/registry.js +4 -4
- package/dist/devtools/registry.js.map +1 -1
- package/dist/features/measure/measureContribution.js +4 -2
- package/dist/features/measure/measureContribution.js.map +1 -1
- package/dist/features/modes/modeTriggers.js +7 -3
- package/dist/features/modes/modeTriggers.js.map +1 -1
- package/dist/features/pathEffects/effectSources.js +60 -0
- package/dist/features/pathEffects/effectSources.js.map +1 -0
- package/dist/features/pathEffects/readSource.js +17 -1
- package/dist/features/pathEffects/readSource.js.map +1 -1
- package/dist/features/pathEffects/textItems.js +53 -0
- package/dist/features/pathEffects/textItems.js.map +1 -0
- package/dist/features/pathEffects/useEffectSourcesLayer.js +57 -0
- package/dist/features/pathEffects/useEffectSourcesLayer.js.map +1 -0
- package/dist/features/paths/pathEditState.js +5 -2
- package/dist/features/paths/pathEditState.js.map +1 -1
- package/dist/features/text/sceneTextDrafts.js +65 -0
- package/dist/features/text/sceneTextDrafts.js.map +1 -0
- package/dist/features/text/textHitAt.js +31 -2
- package/dist/features/text/textHitAt.js.map +1 -1
- package/dist/features/text/useSceneTextEdit.js +22 -18
- package/dist/features/text/useSceneTextEdit.js.map +1 -1
- package/dist/features/viewports/aim.js +105 -0
- package/dist/features/viewports/aim.js.map +1 -0
- package/dist/features/viewports/viewMarker.js +82 -0
- package/dist/features/viewports/viewMarker.js.map +1 -0
- package/dist/features/viewports/viewMarkerDrag.js +55 -0
- package/dist/features/viewports/viewMarkerDrag.js.map +1 -0
- package/dist/index.d.ts +500 -201
- package/dist/index.js +23 -12
- package/dist/interactions/actions/booleans/booleans.js +2 -1
- package/dist/interactions/actions/booleans/booleans.js.map +1 -1
- package/dist/interactions/actions/booleans/operands.js +6 -5
- package/dist/interactions/actions/booleans/operands.js.map +1 -1
- package/dist/interactions/actions/clipboard/action.js +27 -17
- package/dist/interactions/actions/clipboard/action.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorEditing.js +44 -23
- package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorJoin.js +12 -6
- package/dist/interactions/actions/defaults/anchorJoin.js.map +1 -1
- package/dist/interactions/actions/defaults/bendSegment.js +7 -4
- package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
- package/dist/interactions/actions/defaults/cancelGesture.js +11 -8
- package/dist/interactions/actions/defaults/cancelGesture.js.map +1 -1
- package/dist/interactions/actions/defaults/contextMenu.js +17 -8
- package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
- package/dist/interactions/actions/defaults/createPaintAction.js +1 -1
- package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
- package/dist/interactions/actions/defaults/delete.js +28 -14
- package/dist/interactions/actions/defaults/delete.js.map +1 -1
- package/dist/interactions/actions/defaults/duplicate.js +11 -8
- package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
- package/dist/interactions/actions/defaults/enterPathEdit.js +19 -9
- package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/escape.js +11 -8
- package/dist/interactions/actions/defaults/escape.js.map +1 -1
- package/dist/interactions/actions/defaults/exitPathEdit.js +7 -4
- package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/group.js +19 -13
- package/dist/interactions/actions/defaults/group.js.map +1 -1
- package/dist/interactions/actions/defaults/ingest.js +10 -4
- package/dist/interactions/actions/defaults/ingest.js.map +1 -1
- package/dist/interactions/actions/defaults/insertPathAnchor.js +7 -4
- package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
- package/dist/interactions/actions/defaults/nudge.js +8 -2
- package/dist/interactions/actions/defaults/nudge.js.map +1 -1
- package/dist/interactions/actions/defaults/pathEditTools.js +11 -8
- package/dist/interactions/actions/defaults/pathEditTools.js.map +1 -1
- package/dist/interactions/actions/defaults/pinchZoom.js +7 -4
- package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
- package/dist/interactions/actions/defaults/reorder.js +24 -6
- package/dist/interactions/actions/defaults/reorder.js.map +1 -1
- package/dist/interactions/actions/defaults/selectAll.js +8 -5
- package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
- package/dist/interactions/actions/defaults/setFill.js +7 -3
- package/dist/interactions/actions/defaults/setFill.js.map +1 -1
- package/dist/interactions/actions/defaults/setFillOpacity.js +1 -1
- package/dist/interactions/actions/defaults/setStroke.js +12 -11
- package/dist/interactions/actions/defaults/setStroke.js.map +1 -1
- package/dist/interactions/actions/defaults/setStrokeOpacity.js +1 -1
- package/dist/interactions/actions/defaults/toolActivate.js +7 -1
- package/dist/interactions/actions/defaults/toolActivate.js.map +1 -1
- package/dist/interactions/actions/defaults/toolResetToDefault.js +7 -4
- package/dist/interactions/actions/defaults/toolResetToDefault.js.map +1 -1
- package/dist/interactions/actions/defaults/undoRedo.js +19 -13
- package/dist/interactions/actions/defaults/undoRedo.js.map +1 -1
- package/dist/interactions/actions/defaults/viewportWheelPan.js +16 -7
- package/dist/interactions/actions/defaults/viewportWheelPan.js.map +1 -1
- package/dist/interactions/actions/defaults/viewportZoom.js +24 -6
- package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/action.js +7 -4
- package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
- package/dist/interactions/actions/flip/action.js +8 -2
- package/dist/interactions/actions/flip/action.js.map +1 -1
- package/dist/interactions/actions/insert/repositionAction.js +2 -1
- package/dist/interactions/actions/insert/repositionAction.js.map +1 -1
- package/dist/interactions/actions/move/action.js +7 -4
- package/dist/interactions/actions/move/action.js.map +1 -1
- package/dist/interactions/actions/outlines/createOutlinesAction.js +8 -5
- package/dist/interactions/actions/outlines/createOutlinesAction.js.map +1 -1
- package/dist/interactions/actions/outlines/outlineStroke.js +5 -3
- package/dist/interactions/actions/outlines/outlineStroke.js.map +1 -1
- package/dist/interactions/actions/pathCommands/cutJoin.js +8 -5
- package/dist/interactions/actions/pathCommands/cutJoin.js.map +1 -1
- package/dist/interactions/actions/resize/cornerHandles.js +2 -1
- package/dist/interactions/actions/resize/cornerHandles.js.map +1 -1
- package/dist/interactions/actions/selectionSubtree.js +12 -7
- package/dist/interactions/actions/selectionSubtree.js.map +1 -1
- package/dist/interactions/actions/transform-again/action.js +8 -5
- package/dist/interactions/actions/transform-again/action.js.map +1 -1
- package/dist/json.d.ts +4 -0
- package/dist/json.js +3 -0
- package/dist/kitSettingsSchema.js +8 -1
- package/dist/kitSettingsSchema.js.map +1 -1
- package/dist/{math-CrJULaZI.d.ts → math-B56zxVs5.d.ts} +10 -1
- package/dist/math.d.ts +1 -1
- package/dist/math.js +2 -1
- package/dist/resize.d.ts +1 -1
- package/dist/test-seams.d.ts +2 -2
- package/dist/tools/builtin/ellipse/useEllipseTool.js +1 -0
- package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
- package/dist/tools/builtin/eyedropper/useEyedropperTool.js +2 -1
- package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
- package/dist/tools/builtin/hand/dragPanContribution.js +4 -1
- package/dist/tools/builtin/hand/dragPanContribution.js.map +1 -1
- package/dist/tools/builtin/hand/useHandTool.js +7 -4
- package/dist/tools/builtin/hand/useHandTool.js.map +1 -1
- package/dist/tools/builtin/image/useImageTool.js +1 -0
- package/dist/tools/builtin/image/useImageTool.js.map +1 -1
- package/dist/tools/builtin/lasso/useLassoTool.js +1 -0
- package/dist/tools/builtin/lasso/useLassoTool.js.map +1 -1
- package/dist/tools/builtin/line/useLineTool.js +1 -0
- package/dist/tools/builtin/line/useLineTool.js.map +1 -1
- package/dist/tools/builtin/measure/useMeasureTool.js +12 -5
- package/dist/tools/builtin/measure/useMeasureTool.js.map +1 -1
- package/dist/tools/builtin/pen/usePenTool.js +14 -6
- package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
- package/dist/tools/builtin/pencil/usePencilTool.js +1 -0
- package/dist/tools/builtin/pencil/usePencilTool.js.map +1 -1
- package/dist/tools/builtin/polygon/usePolygonTool.js +13 -4
- package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
- package/dist/tools/builtin/rect/useRectTool.js +1 -0
- package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
- package/dist/tools/builtin/select/adapterPointQuery.js +3 -2
- package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
- package/dist/tools/builtin/select/pressDecision.js +32 -0
- package/dist/tools/builtin/select/pressDecision.js.map +1 -0
- package/dist/tools/builtin/select/selectionContributions.js +22 -13
- package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
- package/dist/tools/builtin/select/useSelectTool.js +13 -10
- package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
- package/dist/tools/builtin/slice/useSliceTool.js +22 -8
- package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
- package/dist/tools/builtin/star/useStarTool.js +13 -4
- package/dist/tools/builtin/star/useStarTool.js.map +1 -1
- package/dist/tools/builtin/sticky/stickyNode.js +4 -1
- package/dist/tools/builtin/sticky/stickyNode.js.map +1 -1
- package/dist/tools/builtin/sticky/useStickyTool.js +10 -5
- package/dist/tools/builtin/sticky/useStickyTool.js.map +1 -1
- package/dist/tools/builtin/text/useTextTool.js +3 -1
- package/dist/tools/builtin/text/useTextTool.js.map +1 -1
- package/dist/util/paint.js +1 -3
- package/dist/util/paint.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +28 -24
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"depMemo.js","names":[],"sources":["../../../src/core/scene/depMemo.ts"],"sourcesContent":["/**\n * What a memoized derivation was derived *from*, so a hit can re-check it.\n *\n * A derivation keyed on the deriving node's own authored pose only notices a\n * change someone pushed. A dependency that changed with nothing pushed behind\n * it — an ancestor's frame moving, a removal, a dependency appearing, a lookup\n * answering nodes no scene edits — is invisible to that key. Recording what\n * the derivation was handed and comparing it on the hit path is what notices\n * it without a trigger.\n *\n * What is recorded is what a derivation may read of a dependency: its node (an\n * identity), its pose (by value), its `data` (a reference, which every edit\n * replaces), its `layer`, and its path where the derivation read it. Anything\n * else on the node — its parent, its children — still needs a push.\n */\nimport type { Path } from '@weasel-js/geom';\nimport { samePoseValue, snapshotPose } from './poseSnapshot';\nimport type { DerivedDep, Node, NodeId } from './types';\n\n/** Each dependency's node — an identity, so a restored clone is a different\n * one — with a copy of the pose it resolved to, its `data` and `layer`, and\n * the path it answered, `undefined` where the derivation never asked. */\nexport interface DepRecord<TPose> {\n nodes: (object | undefined)[];\n poses: (TPose | undefined)[];\n data: unknown[];\n layers: (string | undefined)[];\n paths: (Path | null | undefined)[];\n}\n\nexport function emptyDepRecord<TPose>(): DepRecord<TPose> {\n return { nodes: [], poses: [], data: [], layers: [], paths: [] };\n}\n\n/** A dependency as the derivation is handed it, noting a read of its path. A\n * class for the reason `SceneDep` is one: a prototype accessor is cheap. */\nclass RecordedDep<TPose> implements DerivedDep<TPose> {\n readonly node: Node<unknown, string, TPose>;\n readonly pose: TPose;\n\n constructor(\n private readonly dep: DerivedDep<TPose>,\n private readonly paths: (Path | null | undefined)[],\n private readonly index: number,\n ) {\n this.node = dep.node;\n this.pose = dep.pose;\n }\n\n get path(): Path | null {\n return (this.paths[this.index] = this.dep.path);\n }\n}\n\n/** Run `derive` against `deps`, recording in place what it was run against.\n * Mutating the record is what updates the memo slot, which holds this object. */\nexport function deriveRecorded<TPose, T>(\n into: DepRecord<TPose>,\n deps: readonly (DerivedDep<TPose> | undefined)[],\n derive: (deps: readonly (DerivedDep<TPose> | undefined)[]) => T,\n): T {\n into.nodes.length = 0;\n into.poses.length = 0;\n into.data.length = 0;\n into.layers.length = 0;\n into.paths.length = 0;\n const handed = deps.map((dep, i) => {\n into.nodes.push(dep?.node);\n into.poses.push(dep === undefined ? undefined : snapshotPose(dep.pose));\n into.data.push(dep?.node.data);\n into.layers.push(dep?.node.layer);\n into.paths.push(undefined);\n return dep === undefined ? undefined : new RecordedDep(dep, into.paths, i);\n });\n
|
|
1
|
+
{"version":3,"file":"depMemo.js","names":[],"sources":["../../../src/core/scene/depMemo.ts"],"sourcesContent":["/**\n * What a memoized derivation was derived *from*, so a hit can re-check it.\n *\n * A derivation keyed on the deriving node's own authored pose only notices a\n * change someone pushed. A dependency that changed with nothing pushed behind\n * it — an ancestor's frame moving, a removal, a dependency appearing, a lookup\n * answering nodes no scene edits — is invisible to that key. Recording what\n * the derivation was handed and comparing it on the hit path is what notices\n * it without a trigger.\n *\n * What is recorded is what a derivation may read of a dependency: its node (an\n * identity), its pose (by value), its `data` (a reference, which every edit\n * replaces), its `layer`, and its path where the derivation read it. Anything\n * else on the node — its parent, its children — still needs a push. A\n * derivation that read glyph outlines also records the face generation, since\n * a face landing changes its result with nothing in the scene changed.\n */\nimport type { Path } from '@weasel-js/geom';\nimport { fontGeneration, noteFontRead, withFontReads } from './fontReads';\nimport { samePoseValue, snapshotPose } from './poseSnapshot';\nimport type { DerivedDep, Node, NodeId } from './types';\n\n/** Each dependency's node — an identity, so a restored clone is a different\n * one — with a copy of the pose it resolved to, its `data` and `layer`, and\n * the path it answered, `undefined` where the derivation never asked. */\nexport interface DepRecord<TPose> {\n nodes: (object | undefined)[];\n poses: (TPose | undefined)[];\n data: unknown[];\n layers: (string | undefined)[];\n paths: (Path | null | undefined)[];\n /** The face generation it was derived under, or `null` when it read no\n * glyph outlines. */\n faces: number | null;\n}\n\nexport function emptyDepRecord<TPose>(): DepRecord<TPose> {\n return { nodes: [], poses: [], data: [], layers: [], paths: [], faces: null };\n}\n\n/** A dependency as the derivation is handed it, noting a read of its path. A\n * class for the reason `SceneDep` is one: a prototype accessor is cheap. */\nclass RecordedDep<TPose> implements DerivedDep<TPose> {\n readonly node: Node<unknown, string, TPose>;\n readonly pose: TPose;\n\n constructor(\n private readonly dep: DerivedDep<TPose>,\n private readonly paths: (Path | null | undefined)[],\n private readonly index: number,\n ) {\n this.node = dep.node;\n this.pose = dep.pose;\n }\n\n get path(): Path | null {\n return (this.paths[this.index] = this.dep.path);\n }\n}\n\n/** Run `derive` against `deps`, recording in place what it was run against.\n * Mutating the record is what updates the memo slot, which holds this object. */\nexport function deriveRecorded<TPose, T>(\n into: DepRecord<TPose>,\n deps: readonly (DerivedDep<TPose> | undefined)[],\n derive: (deps: readonly (DerivedDep<TPose> | undefined)[]) => T,\n): T {\n into.nodes.length = 0;\n into.poses.length = 0;\n into.data.length = 0;\n into.layers.length = 0;\n into.paths.length = 0;\n const handed = deps.map((dep, i) => {\n into.nodes.push(dep?.node);\n into.poses.push(dep === undefined ? undefined : snapshotPose(dep.pose));\n into.data.push(dep?.node.data);\n into.layers.push(dep?.node.layer);\n into.paths.push(undefined);\n return dep === undefined ? undefined : new RecordedDep(dep, into.paths, i);\n });\n const generation = fontGeneration();\n const { value, fonts } = withFontReads(() => derive(handed));\n into.faces = fonts ? generation : null;\n return value;\n}\n\n/** Whether the dependencies still resolve to what the record was taken from.\n * Resolves them one at a time rather than taking an array: this is the hit\n * path, and it walks every derived node's every dependency on every frame. */\nexport function sameDeps<TPose>(\n record: DepRecord<TPose>,\n ids: readonly NodeId[],\n depOf: (id: NodeId) => DerivedDep<TPose> | undefined,\n): boolean {\n if (record.nodes.length !== ids.length) return false;\n if (record.faces !== null && record.faces !== fontGeneration()) return false;\n for (let i = 0; i < ids.length; i++) {\n const dep = depOf(ids[i]);\n if (record.nodes[i] !== (dep === undefined ? undefined : dep.node)) return false;\n if (dep === undefined) continue;\n if (record.data[i] !== dep.node.data || record.layers[i] !== dep.node.layer) return false;\n if (!samePoseValue(record.poses[i], dep.pose)) return false;\n const path = record.paths[i];\n if (path !== undefined && path !== dep.path) return false;\n }\n // A hit is still a read, for any derivation enclosing this one.\n if (record.faces !== null) noteFontRead();\n return true;\n}\n"],"mappings":";;;;AAoCA,SAAgB,iBAA0C;CACxD,OAAO;EAAE,OAAO,CAAC;EAAG,OAAO,CAAC;EAAG,MAAM,CAAC;EAAG,QAAQ,CAAC;EAAG,OAAO,CAAC;EAAG,OAAO;CAAK;AAC9E;;;AAIA,IAAM,cAAN,MAAsD;CAKjC;CACA;CACA;CANnB;CACA;CAEA,YACE,KACA,OACA,OACA;EAHiB,KAAA,MAAA;EACA,KAAA,QAAA;EACA,KAAA,QAAA;EAEjB,KAAK,OAAO,IAAI;EAChB,KAAK,OAAO,IAAI;CAClB;CAEA,IAAI,OAAoB;EACtB,OAAQ,KAAK,MAAM,KAAK,SAAS,KAAK,IAAI;CAC5C;AACF;;;AAIA,SAAgB,eACd,MACA,MACA,QACG;CACH,KAAK,MAAM,SAAS;CACpB,KAAK,MAAM,SAAS;CACpB,KAAK,KAAK,SAAS;CACnB,KAAK,OAAO,SAAS;CACrB,KAAK,MAAM,SAAS;CACpB,MAAM,SAAS,KAAK,KAAK,KAAK,MAAM;EAClC,KAAK,MAAM,KAAK,KAAK,IAAI;EACzB,KAAK,MAAM,KAAK,QAAQ,SAAY,SAAY,aAAa,IAAI,IAAI,CAAC;EACtE,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI;EAC7B,KAAK,OAAO,KAAK,KAAK,KAAK,KAAK;EAChC,KAAK,MAAM,KAAK,MAAS;EACzB,OAAO,QAAQ,SAAY,SAAY,IAAI,YAAY,KAAK,KAAK,OAAO,CAAC;CAC3E,CAAC;CACD,MAAM,aAAa,eAAe;CAClC,MAAM,EAAE,OAAO,UAAU,oBAAoB,OAAO,MAAM,CAAC;CAC3D,KAAK,QAAQ,QAAQ,aAAa;CAClC,OAAO;AACT;;;;AAKA,SAAgB,SACd,QACA,KACA,OACS;CACT,IAAI,OAAO,MAAM,WAAW,IAAI,QAAQ,OAAO;CAC/C,IAAI,OAAO,UAAU,QAAQ,OAAO,UAAU,eAAe,GAAG,OAAO;CACvE,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;EACnC,MAAM,MAAM,MAAM,IAAI,EAAE;EACxB,IAAI,OAAO,MAAM,QAAQ,QAAQ,SAAY,SAAY,IAAI,OAAO,OAAO;EAC3E,IAAI,QAAQ,QAAW;EACvB,IAAI,OAAO,KAAK,OAAO,IAAI,KAAK,QAAQ,OAAO,OAAO,OAAO,IAAI,KAAK,OAAO,OAAO;EACpF,IAAI,CAAC,cAAc,OAAO,MAAM,IAAI,IAAI,IAAI,GAAG,OAAO;EACtD,MAAM,OAAO,OAAO,MAAM;EAC1B,IAAI,SAAS,UAAa,SAAS,IAAI,MAAM,OAAO;CACtD;CAEA,IAAI,OAAO,UAAU,MAAM,aAAa;CACxC,OAAO;AACT"}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
//#region src/core/scene/dependents.ts
|
|
2
2
|
var NONE = Object.freeze([]);
|
|
3
|
+
/** The nodes in `scene` that list `id` in their `dependsOn`, directly or through others, less any inside `id`'s own
|
|
4
|
+
* subtree. */
|
|
5
|
+
function sceneDependentsOf(scene, id) {
|
|
6
|
+
if (!scene.get(id)) return NONE;
|
|
7
|
+
return scene.removalClosure([id]).filter((d) => d !== id && !scene.ancestorsOf(d).includes(id));
|
|
8
|
+
}
|
|
3
9
|
/**
|
|
4
10
|
* The nodes `node`'s geometry derives from: the ids it named, or its own
|
|
5
11
|
* children when it named `'children'`.
|
|
@@ -90,5 +96,5 @@ function sameDependsOn(a, b) {
|
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
//#endregion
|
|
93
|
-
export { createDependentsIndex, dependencyIdsOf, sameDependsOn };
|
|
99
|
+
export { createDependentsIndex, dependencyIdsOf, sameDependsOn, sceneDependentsOf };
|
|
94
100
|
//# sourceMappingURL=dependents.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dependents.js","names":[],"sources":["../../../src/core/scene/dependents.ts"],"sourcesContent":["/**\n * Which nodes must recompute when a given node's pose changes — the reverse\n * index, plus {@link dependencyIdsOf}, the forward read it is built from.\n */\nimport type { NodeId } from './types';\n\nconst NONE: readonly NodeId[] = Object.freeze([]);\n\n/**\n * The nodes `node`'s geometry derives from: the ids it named, or its own\n * children when it named `'children'`.\n *\n * The `'children'` form is deliberately absent from the reverse index — a\n * container is not a dependent of the children it hugs, or deleting one child\n * would cascade the whole group away. Its invalidation is the ancestor walk in\n * `scene.ts` instead.\n */\nexport function dependencyIdsOf(\n node: { id: NodeId; dependsOn?: readonly NodeId[] | 'children' },\n childrenOf: (id: NodeId) => readonly NodeId[],\n): readonly NodeId[] {\n const deps = node.dependsOn;\n if (deps === undefined) return NONE;\n return deps === 'children' ? childrenOf(node.id) : deps;\n}\n\nexport interface DependentsIndex {\n add(id: NodeId, deps: readonly NodeId[]): void;\n /** Forget what `id` declared. Deliberately not symmetric: who depends on\n * `id` is those nodes' declaration, not `id`'s, and outlives its removal. */\n remove(id: NodeId): void;\n /** Forget every registration. */\n clear(): void;\n /** True when nothing derives from anything. */\n isEmpty(): boolean;\n dependentsOf(id: NodeId): Iterable<NodeId>;\n /** Nodes that derive from `id` directly or through a chain. Excludes `id`\n * itself unless a cycle leads back to it. */\n transitiveDependentsOf(id: NodeId): Iterable<NodeId>;\n}\n\nexport function createDependentsIndex(): DependentsIndex {\n /** dependency -> nodes deriving from it */\n const forward = new Map<NodeId, Set<NodeId>>();\n /** dependent -> the dependencies it registered against */\n const reverse = new Map<NodeId, readonly NodeId[]>();\n\n /** Drop `id`'s registrations against its current dependencies. Shared by\n * `add` (which re-registers after) and `remove` (which does not). */\n function detachOwnDeps(id: NodeId): void {\n const old = reverse.get(id);\n if (old === undefined) return;\n for (const dep of old) {\n const set = forward.get(dep);\n if (set === undefined) continue;\n set.delete(id);\n if (set.size === 0) forward.delete(dep);\n }\n reverse.delete(id);\n }\n\n return {\n add(id, deps) {\n detachOwnDeps(id);\n if (deps.length === 0) return;\n reverse.set(id, [...deps]);\n for (const dep of deps) {\n let set = forward.get(dep);\n if (set === undefined) { set = new Set(); forward.set(dep, set); }\n set.add(id);\n }\n },\n\n remove(id) {\n detachOwnDeps(id);\n },\n\n clear() {\n forward.clear();\n reverse.clear();\n },\n\n isEmpty() {\n return forward.size === 0;\n },\n\n dependentsOf(id) {\n const set = forward.get(id);\n return set === undefined ? NONE : [...set];\n },\n\n transitiveDependentsOf(id) {\n // Runs per node per frame; the miss must not allocate.\n const direct = forward.get(id);\n if (direct === undefined) return NONE;\n const out = new Set<NodeId>();\n const stack: NodeId[] = [...direct];\n while (stack.length > 0) {\n const next = stack.pop()!;\n if (out.has(next)) continue; // also what stops a cycle\n out.add(next);\n for (const d of forward.get(next) ?? []) stack.push(d);\n }\n return out;\n },\n };\n}\n\n/**\n * Whether two `dependsOn` values name the same thing, so a retarget to what a\n * node already declares can be elided. Order is part of it: a router reads its\n * ends positionally, and swapping them reverses the edge.\n */\nexport function sameDependsOn(\n a: readonly NodeId[] | 'children' | undefined,\n b: readonly NodeId[] | 'children' | undefined,\n): boolean {\n if (a === b) return true;\n if (a === undefined || b === undefined) return false;\n if (a === 'children' || b === 'children') return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;\n return true;\n}\n"],"mappings":";AAMA,IAAM,OAA0B,OAAO,OAAO,CAAC,CAAC;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"dependents.js","names":[],"sources":["../../../src/core/scene/dependents.ts"],"sourcesContent":["/**\n * Which nodes must recompute when a given node's pose changes — the reverse\n * index, plus {@link dependencyIdsOf}, the forward read it is built from.\n */\nimport type { NodeId, Scene } from './types';\n\nconst NONE: readonly NodeId[] = Object.freeze([]);\n\n/** The nodes in `scene` that list `id` in their `dependsOn`, directly or through others, less any inside `id`'s own\n * subtree. */\nexport function sceneDependentsOf<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n id: NodeId,\n): readonly NodeId[] {\n if (!scene.get(id)) return NONE;\n return scene.removalClosure([id]).filter((d) => d !== id && !scene.ancestorsOf(d).includes(id));\n}\n\n/**\n * The nodes `node`'s geometry derives from: the ids it named, or its own\n * children when it named `'children'`.\n *\n * The `'children'` form is deliberately absent from the reverse index — a\n * container is not a dependent of the children it hugs, or deleting one child\n * would cascade the whole group away. Its invalidation is the ancestor walk in\n * `scene.ts` instead.\n */\nexport function dependencyIdsOf(\n node: { id: NodeId; dependsOn?: readonly NodeId[] | 'children' },\n childrenOf: (id: NodeId) => readonly NodeId[],\n): readonly NodeId[] {\n const deps = node.dependsOn;\n if (deps === undefined) return NONE;\n return deps === 'children' ? childrenOf(node.id) : deps;\n}\n\nexport interface DependentsIndex {\n add(id: NodeId, deps: readonly NodeId[]): void;\n /** Forget what `id` declared. Deliberately not symmetric: who depends on\n * `id` is those nodes' declaration, not `id`'s, and outlives its removal. */\n remove(id: NodeId): void;\n /** Forget every registration. */\n clear(): void;\n /** True when nothing derives from anything. */\n isEmpty(): boolean;\n dependentsOf(id: NodeId): Iterable<NodeId>;\n /** Nodes that derive from `id` directly or through a chain. Excludes `id`\n * itself unless a cycle leads back to it. */\n transitiveDependentsOf(id: NodeId): Iterable<NodeId>;\n}\n\nexport function createDependentsIndex(): DependentsIndex {\n /** dependency -> nodes deriving from it */\n const forward = new Map<NodeId, Set<NodeId>>();\n /** dependent -> the dependencies it registered against */\n const reverse = new Map<NodeId, readonly NodeId[]>();\n\n /** Drop `id`'s registrations against its current dependencies. Shared by\n * `add` (which re-registers after) and `remove` (which does not). */\n function detachOwnDeps(id: NodeId): void {\n const old = reverse.get(id);\n if (old === undefined) return;\n for (const dep of old) {\n const set = forward.get(dep);\n if (set === undefined) continue;\n set.delete(id);\n if (set.size === 0) forward.delete(dep);\n }\n reverse.delete(id);\n }\n\n return {\n add(id, deps) {\n detachOwnDeps(id);\n if (deps.length === 0) return;\n reverse.set(id, [...deps]);\n for (const dep of deps) {\n let set = forward.get(dep);\n if (set === undefined) { set = new Set(); forward.set(dep, set); }\n set.add(id);\n }\n },\n\n remove(id) {\n detachOwnDeps(id);\n },\n\n clear() {\n forward.clear();\n reverse.clear();\n },\n\n isEmpty() {\n return forward.size === 0;\n },\n\n dependentsOf(id) {\n const set = forward.get(id);\n return set === undefined ? NONE : [...set];\n },\n\n transitiveDependentsOf(id) {\n // Runs per node per frame; the miss must not allocate.\n const direct = forward.get(id);\n if (direct === undefined) return NONE;\n const out = new Set<NodeId>();\n const stack: NodeId[] = [...direct];\n while (stack.length > 0) {\n const next = stack.pop()!;\n if (out.has(next)) continue; // also what stops a cycle\n out.add(next);\n for (const d of forward.get(next) ?? []) stack.push(d);\n }\n return out;\n },\n };\n}\n\n/**\n * Whether two `dependsOn` values name the same thing, so a retarget to what a\n * node already declares can be elided. Order is part of it: a router reads its\n * ends positionally, and swapping them reverses the edge.\n */\nexport function sameDependsOn(\n a: readonly NodeId[] | 'children' | undefined,\n b: readonly NodeId[] | 'children' | undefined,\n): boolean {\n if (a === b) return true;\n if (a === undefined || b === undefined) return false;\n if (a === 'children' || b === 'children') return false;\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;\n return true;\n}\n"],"mappings":";AAMA,IAAM,OAA0B,OAAO,OAAO,CAAC,CAAC;;;AAIhD,SAAgB,kBACd,OACA,IACmB;CACnB,IAAI,CAAC,MAAM,IAAI,EAAE,GAAG,OAAO;CAC3B,OAAO,MAAM,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,MAAM,MAAM,MAAM,CAAC,MAAM,YAAY,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;AAChG;;;;;;;;;;AAWA,SAAgB,gBACd,MACA,YACmB;CACnB,MAAM,OAAO,KAAK;CAClB,IAAI,SAAS,QAAW,OAAO;CAC/B,OAAO,SAAS,aAAa,WAAW,KAAK,EAAE,IAAI;AACrD;AAiBA,SAAgB,wBAAyC;;CAEvD,MAAM,0BAAU,IAAI,IAAyB;;CAE7C,MAAM,0BAAU,IAAI,IAA+B;;;CAInD,SAAS,cAAc,IAAkB;EACvC,MAAM,MAAM,QAAQ,IAAI,EAAE;EAC1B,IAAI,QAAQ,QAAW;EACvB,KAAK,MAAM,OAAO,KAAK;GACrB,MAAM,MAAM,QAAQ,IAAI,GAAG;GAC3B,IAAI,QAAQ,QAAW;GACvB,IAAI,OAAO,EAAE;GACb,IAAI,IAAI,SAAS,GAAG,QAAQ,OAAO,GAAG;EACxC;EACA,QAAQ,OAAO,EAAE;CACnB;CAEA,OAAO;EACL,IAAI,IAAI,MAAM;GACZ,cAAc,EAAE;GAChB,IAAI,KAAK,WAAW,GAAG;GACvB,QAAQ,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC;GACzB,KAAK,MAAM,OAAO,MAAM;IACtB,IAAI,MAAM,QAAQ,IAAI,GAAG;IACzB,IAAI,QAAQ,QAAW;KAAE,sBAAM,IAAI,IAAI;KAAG,QAAQ,IAAI,KAAK,GAAG;IAAG;IACjE,IAAI,IAAI,EAAE;GACZ;EACF;EAEA,OAAO,IAAI;GACT,cAAc,EAAE;EAClB;EAEA,QAAQ;GACN,QAAQ,MAAM;GACd,QAAQ,MAAM;EAChB;EAEA,UAAU;GACR,OAAO,QAAQ,SAAS;EAC1B;EAEA,aAAa,IAAI;GACf,MAAM,MAAM,QAAQ,IAAI,EAAE;GAC1B,OAAO,QAAQ,SAAY,OAAO,CAAC,GAAG,GAAG;EAC3C;EAEA,uBAAuB,IAAI;GAEzB,MAAM,SAAS,QAAQ,IAAI,EAAE;GAC7B,IAAI,WAAW,QAAW,OAAO;GACjC,MAAM,sBAAM,IAAI,IAAY;GAC5B,MAAM,QAAkB,CAAC,GAAG,MAAM;GAClC,OAAO,MAAM,SAAS,GAAG;IACvB,MAAM,OAAO,MAAM,IAAI;IACvB,IAAI,IAAI,IAAI,IAAI,GAAG;IACnB,IAAI,IAAI,IAAI;IACZ,KAAK,MAAM,KAAK,QAAQ,IAAI,IAAI,KAAK,CAAC,GAAG,MAAM,KAAK,CAAC;GACvD;GACA,OAAO;EACT;CACF;AACF;;;;;;AAOA,SAAgB,cACd,GACA,GACS;CACT,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,MAAM,UAAa,MAAM,QAAW,OAAO;CAC/C,IAAI,MAAM,cAAc,MAAM,YAAY,OAAO;CACjD,IAAI,EAAE,WAAW,EAAE,QAAQ,OAAO;CAClC,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK,IAAI,EAAE,OAAO,EAAE,IAAI,OAAO;CAC7D,OAAO;AACT"}
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { cubicContoursToPath, pathToCubicContours } from "@weasel-js/geom";
|
|
2
|
+
import { resolveFill, resolveStroke } from "@weasel-js/paint";
|
|
2
3
|
|
|
3
4
|
//#region src/core/scene/derivedParts.ts
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* long as the path does — in the derivation's memo.
|
|
13
|
-
*/
|
|
5
|
+
/** What `part` paints, its `INHERIT` taken from `own`, the deriving node's
|
|
6
|
+
* data — whose own `INHERIT` takes `inherited`, its container's. */
|
|
7
|
+
function resolvePartPaint(part, own, inherited) {
|
|
8
|
+
return {
|
|
9
|
+
fill: resolveFill(part.fill, resolveFill(own?.fill, inherited?.fill)),
|
|
10
|
+
stroke: resolveStroke(part.stroke, resolveStroke(own?.stroke, inherited?.stroke))
|
|
11
|
+
};
|
|
12
|
+
}
|
|
14
13
|
var PARTS = /* @__PURE__ */ new WeakMap();
|
|
15
14
|
/**
|
|
16
15
|
* One path holding every part's outline, with the parts remembered against
|
|
@@ -54,5 +53,5 @@ function concatPaths(paths) {
|
|
|
54
53
|
}
|
|
55
54
|
|
|
56
55
|
//#endregion
|
|
57
|
-
export { derivedPartsOf, pathFromParts };
|
|
56
|
+
export { derivedPartsOf, pathFromParts, resolvePartPaint };
|
|
58
57
|
//# sourceMappingURL=derivedParts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derivedParts.js","names":[],"sources":["../../../src/core/scene/derivedParts.ts"],"sourcesContent":["/**\n * A derived path that paints in several parts, each with its own fill and\n * stroke: the steps of a blend, the copies of a repeat.\n *\n * A derivation returns one `Path`, which is what picking, bounds and every\n * reader of `DerivedDep.path` see. {@link pathFromParts} makes that path out\n * of the parts and remembers the parts against it, so the derived-node\n * painter, handed the path, can paint each part as itself. The parts live as\n * long as the path does — in the derivation's memo.\n */\nimport { pathToCubicContours, cubicContoursToPath, type Path, type PolygonPath } from '@weasel-js/geom';\nimport type
|
|
1
|
+
{"version":3,"file":"derivedParts.js","names":[],"sources":["../../../src/core/scene/derivedParts.ts"],"sourcesContent":["/**\n * A derived path that paints in several parts, each with its own fill and\n * stroke: the steps of a blend, the copies of a repeat.\n *\n * A derivation returns one `Path`, which is what picking, bounds and every\n * reader of `DerivedDep.path` see. {@link pathFromParts} makes that path out\n * of the parts and remembers the parts against it, so the derived-node\n * painter, handed the path, can paint each part as itself. The parts live as\n * long as the path does — in the derivation's memo.\n */\nimport type { InheritedPaint } from './inheritedPaint';\nimport { pathToCubicContours, cubicContoursToPath, type Path, type PolygonPath } from '@weasel-js/geom';\nimport {\n resolveFill, resolveStroke, type Fill, type Inherit, type PaintedFill, type PaintedStroke, type Stroke,\n} from '@weasel-js/paint';\n\n/** One painted piece of a derived path. */\nexport interface DerivedPart {\n path: Path;\n /** The part's fill, read as a node's is: absent paints none, and\n * `INHERIT` takes the node's own `data.fill`. */\n fill?: Fill | Inherit | null;\n /** The part's stroke, on the same terms as `fill`. */\n stroke?: Stroke | Inherit | null;\n}\n\n/** What `part` paints, its `INHERIT` taken from `own`, the deriving node's\n * data — whose own `INHERIT` takes `inherited`, its container's. */\nexport function resolvePartPaint(\n part: DerivedPart,\n own: { fill?: Fill | Inherit | null; stroke?: Stroke | Inherit | null } | null | undefined,\n inherited?: InheritedPaint,\n): { fill: PaintedFill | null; stroke: PaintedStroke | null } {\n return {\n fill: resolveFill(part.fill, resolveFill(own?.fill, inherited?.fill)),\n stroke: resolveStroke(part.stroke, resolveStroke(own?.stroke, inherited?.stroke)),\n };\n}\n\nconst PARTS = new WeakMap<Path, readonly DerivedPart[]>();\n\n/**\n * One path holding every part's outline, with the parts remembered against\n * it for the painter. Return it from a `derivePath`. `null` for no parts.\n */\nexport function pathFromParts(parts: readonly DerivedPart[]): Path | null {\n if (parts.length === 0) return null;\n const whole = parts.length === 1 ? parts[0]!.path : concatPaths(parts.map((p) => p.path));\n // A one-part path is the part's own, which another reader may hold; the\n // painter must not mistake that path for a painted one elsewhere.\n const key = whole === parts[0]!.path ? { ...whole } as Path : whole;\n PARTS.set(key, parts);\n return key;\n}\n\n/** The parts `path` was made from by {@link pathFromParts}, or `undefined`\n * for any other path. */\nexport function derivedPartsOf(path: Path | null | undefined): readonly DerivedPart[] | undefined {\n return path == null ? undefined : PARTS.get(path);\n}\n\nfunction concatPaths(paths: readonly Path[]): PolygonPath {\n const polys = paths.map((p) => (p.kind === 'rect' ? cubicContoursToPath(pathToCubicContours(p)) : p));\n let commands = 0;\n let coords = 0;\n for (const p of polys) {\n commands += p.commands.length;\n coords += p.coords.length;\n }\n const out: PolygonPath = {\n kind: 'polygon',\n commands: new Uint8Array(commands),\n coords: new Float32Array(coords),\n fillRule: 'nonzero',\n };\n let ci = 0;\n let xi = 0;\n for (const p of polys) {\n out.commands.set(p.commands, ci);\n out.coords.set(p.coords, xi);\n ci += p.commands.length;\n xi += p.coords.length;\n }\n return out;\n}\n"],"mappings":";;;;;;AA4BA,SAAgB,iBACd,MACA,KACA,WAC4D;CAC5D,OAAO;EACL,MAAM,YAAY,KAAK,MAAM,YAAY,KAAK,MAAM,WAAW,IAAI,CAAC;EACpE,QAAQ,cAAc,KAAK,QAAQ,cAAc,KAAK,QAAQ,WAAW,MAAM,CAAC;CAClF;AACF;AAEA,IAAM,wBAAQ,IAAI,QAAsC;;;;;AAMxD,SAAgB,cAAc,OAA4C;CACxE,IAAI,MAAM,WAAW,GAAG,OAAO;CAC/B,MAAM,QAAQ,MAAM,WAAW,IAAI,MAAM,EAAE,CAAE,OAAO,YAAY,MAAM,KAAK,MAAM,EAAE,IAAI,CAAC;CAGxF,MAAM,MAAM,UAAU,MAAM,EAAE,CAAE,OAAO,EAAE,GAAG,MAAM,IAAY;CAC9D,MAAM,IAAI,KAAK,KAAK;CACpB,OAAO;AACT;;;AAIA,SAAgB,eAAe,MAAmE;CAChG,OAAO,QAAQ,OAAO,SAAY,MAAM,IAAI,IAAI;AAClD;AAEA,SAAS,YAAY,OAAqC;CACxD,MAAM,QAAQ,MAAM,KAAK,MAAO,EAAE,SAAS,SAAS,oBAAoB,oBAAoB,CAAC,CAAC,IAAI,CAAE;CACpG,IAAI,WAAW;CACf,IAAI,SAAS;CACb,KAAK,MAAM,KAAK,OAAO;EACrB,YAAY,EAAE,SAAS;EACvB,UAAU,EAAE,OAAO;CACrB;CACA,MAAM,MAAmB;EACvB,MAAM;EACN,UAAU,IAAI,WAAW,QAAQ;EACjC,QAAQ,IAAI,aAAa,MAAM;EAC/B,UAAU;CACZ;CACA,IAAI,KAAK;CACT,IAAI,KAAK;CACT,KAAK,MAAM,KAAK,OAAO;EACrB,IAAI,SAAS,IAAI,EAAE,UAAU,EAAE;EAC/B,IAAI,OAAO,IAAI,EAAE,QAAQ,EAAE;EAC3B,MAAM,EAAE,SAAS;EACjB,MAAM,EAAE,OAAO;CACjB;CACA,OAAO;AACT"}
|
|
@@ -195,9 +195,14 @@ function effectivePose(source, node) {
|
|
|
195
195
|
* frame — and the composition fold passes over it.
|
|
196
196
|
*/
|
|
197
197
|
function definesFrame(node) {
|
|
198
|
-
return !(node
|
|
198
|
+
return !isEnvelope(node);
|
|
199
|
+
}
|
|
200
|
+
/** Whether `node` derives its pose from its children: a group that is its
|
|
201
|
+
* members, so moving them moves it. */
|
|
202
|
+
function isEnvelope(node) {
|
|
203
|
+
return node.dependsOn === "children" && node.derivePose !== void 0;
|
|
199
204
|
}
|
|
200
205
|
|
|
201
206
|
//#endregion
|
|
202
|
-
export { definesFrame, derivedDepOf, derivedPose, documentDepOf, documentPose, effectivePose };
|
|
207
|
+
export { definesFrame, derivedDepOf, derivedPose, documentDepOf, documentPose, effectivePose, isEnvelope };
|
|
203
208
|
//# sourceMappingURL=effectivePose.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effectivePose.js","names":[],"sources":["../../../src/core/scene/effectivePose.ts"],"sourcesContent":["/**\n * `effectivePose` — the pose to draw, pick and measure a node at.\n *\n * Three answers, in order: the ephemeral override a gesture published, the\n * pose the node derives from its dependencies, or the pose the document\n * stores. A reader of `node.pose` disagrees with the render about where a\n * dragged or derived node is.\n */\nimport { deriveRecorded, emptyDepRecord, sameDeps, type DepRecord } from './depMemo';\nimport type { PoseComposition } from './composeFrame';\nimport { carryDepPath, depFrameRebase, pathAsPainted } from './depFrame';\nimport { dependencyIdsOf } from './dependents';\nimport { resolveDerivedPath } from './derivedPath';\nimport { dropPoseKeyedMemoSlots, nodeMemo } from './nodeMemo';\nimport type { Path } from '@weasel-js/geom';\nimport type { DerivedDep, DerivePathFn, DerivePoseFn, Node, NodeId, PoseOverrides } from './types';\n\n/**\n * Which pose a resolution answers: `effective` honors overrides all the way\n * down the dependency chain, `document` ignores them all the way down. Each\n * reading memoizes in its own slots — the memo key is the node and its\n * authored pose, which is the same for both.\n */\ntype Reading = 'effective' | 'document';\n\nconst POSE_SLOT: Record<Reading, string> = {\n effective: 'kit:derivedPose',\n document: 'kit:derivedPose:document',\n};\nconst PATH_SLOT: Record<Reading, string | undefined> = {\n effective: undefined,\n document: 'kit:derivedPath:document',\n};\n\n/** The subset of a node this module reads. Structural so a `Node` satisfies\n * it without importing the full generic shape. */\nexport interface PosedNode<TPose> {\n id: NodeId;\n kind?: 'leaf' | 'container';\n pose: TPose;\n /** Read only by the memo, which keys on its reference alongside the pose. */\n data?: unknown;\n dependsOn?: readonly NodeId[] | 'children';\n /** Read under a composing strategy, to find the frame its pose is in. */\n parent?: NodeId | null;\n derivePose?: DerivePoseFn<TPose>;\n /** Read only when something asks a dependency for its `path`. */\n derivePath?: DerivePathFn<TPose>;\n}\n\n/** What resolving a pose needs: the overrides, and enough of the scene to\n * reach a node's dependencies. A `Scene` satisfies it. */\nexport interface PoseSource<TPose> {\n readonly overrides: Pick<PoseOverrides<TPose>, 'read'>;\n get(id: NodeId): PosedNode<TPose> | undefined;\n childrenOf(id: NodeId): readonly NodeId[];\n /** When it composes frames, a derivation reads each dependency in the\n * frame of the node deriving. */\n readonly composition?: PoseComposition<TPose>;\n}\n\n/** A cached derived pose, with what it was derived from. */\ninterface PoseMemo<TPose> extends DepRecord<TPose> {\n value: TPose | null;\n}\n\n/** Ids whose derivation is on the stack. A dependency graph is meant to be\n * acyclic and `dependsOn` is unvalidated, so a cycle resolves to the authored\n * pose at whichever node closes it rather than overflowing the stack. */\nconst resolving = new Set<NodeId>();\n\n/** Bumped each time the guard above fires. A derivation that closed a cycle\n * answered from a fallback rather than from its dependencies, so its value is\n * an artifact of which node was asked first and must not be cached. */\nlet cycleHits = 0;\n\n/**\n * The pose `node` computes from its dependencies, or `null` when it derives\n * nothing — it has no `derivePose`, nothing to derive from, or its\n * `derivePose` returned `null`.\n *\n * A dependency is read through {@link effectivePose}, so a group of groups\n * resolves bottom-up and a dragged dependency's override reaches the\n * derivation the same frame it is published.\n */\nexport function derivedPose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose | null {\n return derivedPoseIn(source, node, 'effective');\n}\n\nfunction derivedPoseIn<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n reading: Reading,\n): TPose | null {\n const derive = node.derivePose;\n if (derive === undefined) return null;\n const ids = dependencyIdsOf(node, source.childrenOf);\n if (ids.length === 0) return null;\n if (resolving.has(node.id)) {\n cycleHits++;\n return null;\n }\n resolving.add(node.id);\n const hitsBefore = cycleHits;\n try {\n const depOf = depsAsRead(source, node, reading);\n // Keyed on the authored pose, which only sees a change someone pushed, so\n // the record also carries what the derivation read and re-checks it by\n // value on a hit — see `depMemo.ts`.\n let computed = false;\n const record = nodeMemo<PoseMemo<TPose>>(node, POSE_SLOT[reading], node.pose, () => {\n computed = true;\n return run(node, derive, ids.map(depOf), { ...emptyDepRecord<TPose>(), value: null });\n });\n // Mutating the record in place is what updates the memo slot, which holds\n // this object.\n if (!computed && !sameDeps(record, ids, depOf)) {\n run(node, derive, ids.map(depOf), record);\n }\n if (cycleHits !== hitsBefore) dropPoseKeyedMemoSlots(node);\n return record.value;\n } finally {\n resolving.delete(node.id);\n }\n}\n\n/** Run `derivePose` against `deps` and record what it was run against. */\nfunction run<TPose>(\n node: PosedNode<TPose>,\n derive: NonNullable<PosedNode<TPose>['derivePose']>,\n deps: readonly (DerivedDep<TPose> | undefined)[],\n into: PoseMemo<TPose>,\n): PoseMemo<TPose> {\n into.value = deriveRecorded(into, deps, (handed) => derive(node as never, handed));\n return into;\n}\n\n/**\n * `id` as a derivation sees it: the node, the pose it is painted at, and the\n * path it derives.\n *\n * The pose is resolved eagerly — every derivation reads it — and the path\n * lazily, because most do not and resolving a route costs a router run. Both\n * resolve *through* this function, so a dependency that is itself derived is\n * resolved before it is read.\n */\nexport function derivedDepOf<TPose>(\n source: PoseSource<TPose>,\n id: NodeId,\n reader?: PosedNode<TPose>,\n): DerivedDep<TPose> | undefined {\n return reader === undefined ? depIn(source, id, 'effective') : depsAsRead(source, reader, 'effective')(id);\n}\n\n/** `id` as {@link derivedDepOf} answers it, but at the pose and path the\n * document holds: no override anywhere down its dependencies counts. For a\n * reader that must not see an in-flight gesture, such as an edit pass. */\nexport function documentDepOf<TPose>(source: PoseSource<TPose>, id: NodeId): DerivedDep<TPose> | undefined {\n return depIn(source, id, 'document');\n}\n\n/** `(id) => that dependency as `reader`'s derivation is handed it`: its pose\n * in `reader`'s frame where the scene composes frames. A `'children'`\n * dependent shares its children's frame, so it reads them as they are. */\nfunction depsAsRead<TPose>(\n source: PoseSource<TPose>,\n reader: PosedNode<TPose>,\n reading: Reading,\n): (id: NodeId) => DerivedDep<TPose> | undefined {\n const plain = (id: NodeId) => depIn(source, id, reading);\n if (reader.dependsOn === 'children') return plain;\n const rebase = depFrameRebase(\n source.composition,\n reader,\n (id) => source.get(id),\n (id) => poseIn(source, source.get(id)!, reading),\n (id) => {\n const n = source.get(id);\n return n === undefined || definesFrame(n);\n },\n );\n if (rebase === null) return plain;\n return (id) => {\n const dep = plain(id);\n if (dep === undefined) return undefined;\n const pose = rebase(id, dep.pose);\n return pose === dep.pose ? dep : new RebasedDep(dep, pose);\n };\n}\n\n/** A dependency read from another frame: its pose rebased, and its path\n * carried by the same frame change when asked for. */\nclass RebasedDep<TPose> implements DerivedDep<TPose> {\n readonly node: Node<unknown, string, TPose>;\n\n constructor(private readonly dep: DerivedDep<TPose>, readonly pose: TPose) {\n this.node = dep.node;\n }\n\n get path(): Path | null {\n const path = this.dep.path;\n return path === null ? null : carryDepPath(path, this.dep.pose, this.pose);\n }\n}\n\nfunction depIn<TPose>(\n source: PoseSource<TPose>,\n id: NodeId,\n reading: Reading,\n): DerivedDep<TPose> | undefined {\n const node = source.get(id);\n if (node === undefined) return undefined;\n return new SceneDep(source, node, reading);\n}\n\n/**\n * A class rather than an object literal with a `get path()`: one of these is\n * built per dependency per derived node per frame, and an own accessor costs\n * an order of magnitude more to define than a prototype one does to inherit.\n */\nclass SceneDep<TPose> implements DerivedDep<TPose> {\n readonly node: Node<unknown, string, TPose>;\n readonly pose: TPose;\n private readonly shown: Path | undefined;\n\n constructor(\n private readonly source: PoseSource<TPose>,\n private readonly posed: PosedNode<TPose>,\n private readonly reading: Reading,\n ) {\n this.node = posed as unknown as Node<unknown, string, TPose>;\n const override = reading === 'effective' ? source.overrides.read(posed.id) : undefined;\n this.shown = override?.path;\n this.pose = override?.pose ?? derivedPoseIn(source, posed, reading) ?? posed.pose;\n }\n\n get path(): Path | null {\n if (this.shown !== undefined) return pathAsPainted(this.shown, this.pose, this.pose);\n const { source, reading } = this;\n return resolveDerivedPath(\n this.posed,\n depsAsRead(source, this.posed, reading),\n (depId) => source.childrenOf(depId),\n PATH_SLOT[reading],\n );\n }\n}\n\nfunction poseIn<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n reading: Reading,\n): TPose {\n if (reading === 'effective') {\n const override = source.overrides.read(node.id)?.pose;\n if (override !== undefined) return override;\n }\n return derivedPoseIn(source, node, reading) ?? node.pose;\n}\n\n/**\n * The pose the *document* says `node` is at: derived when it derives, else\n * authored. `effectivePose` minus the override step, at every link of the\n * dependency chain — a derived node's document pose holds still while\n * something it derives from is being dragged.\n *\n * For a reader that must not see an in-flight gesture, such as an action\n * capturing the `from` of a transform op.\n */\nexport function documentPose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose {\n return poseIn(source, node, 'document');\n}\n\n/**\n * The pose to draw, pick and measure `node` at. Every pose a reader acts on\n * comes from here.\n */\nexport function effectivePose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose {\n return poseIn(source, node, 'effective');\n}\n\n/**\n * Whether `node`'s pose is a frame its children are expressed in, under a\n * composing strategy.\n *\n * A container that derives its pose from its children cannot be one: moving\n * the frame to cover the children would move the children, and a `RectPose`\n * frame's origin is its corner, so no envelope leaves them in place. Such a\n * container is an envelope instead — its children are stored in its own\n * frame — and the composition fold passes over it.\n */\nexport function definesFrame(node: { dependsOn?: unknown; derivePose?: unknown }): boolean {\n return !(node.dependsOn === 'children' && node.derivePose !== undefined);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"effectivePose.js","names":[],"sources":["../../../src/core/scene/effectivePose.ts"],"sourcesContent":["/**\n * `effectivePose` — the pose to draw, pick and measure a node at.\n *\n * Three answers, in order: the ephemeral override a gesture published, the\n * pose the node derives from its dependencies, or the pose the document\n * stores. A reader of `node.pose` disagrees with the render about where a\n * dragged or derived node is.\n */\nimport { deriveRecorded, emptyDepRecord, sameDeps, type DepRecord } from './depMemo';\nimport type { PoseComposition } from './composeFrame';\nimport { carryDepPath, depFrameRebase, pathAsPainted } from './depFrame';\nimport { dependencyIdsOf } from './dependents';\nimport { resolveDerivedPath } from './derivedPath';\nimport { dropPoseKeyedMemoSlots, nodeMemo } from './nodeMemo';\nimport type { Path } from '@weasel-js/geom';\nimport type { DerivedDep, DerivePathFn, DerivePoseFn, Node, NodeId, PoseOverrides } from './types';\n\n/**\n * Which pose a resolution answers: `effective` honors overrides all the way\n * down the dependency chain, `document` ignores them all the way down. Each\n * reading memoizes in its own slots — the memo key is the node and its\n * authored pose, which is the same for both.\n */\ntype Reading = 'effective' | 'document';\n\nconst POSE_SLOT: Record<Reading, string> = {\n effective: 'kit:derivedPose',\n document: 'kit:derivedPose:document',\n};\nconst PATH_SLOT: Record<Reading, string | undefined> = {\n effective: undefined,\n document: 'kit:derivedPath:document',\n};\n\n/** The subset of a node this module reads. Structural so a `Node` satisfies\n * it without importing the full generic shape. */\nexport interface PosedNode<TPose> {\n id: NodeId;\n kind?: 'leaf' | 'container';\n pose: TPose;\n /** Read only by the memo, which keys on its reference alongside the pose. */\n data?: unknown;\n dependsOn?: readonly NodeId[] | 'children';\n /** Read under a composing strategy, to find the frame its pose is in. */\n parent?: NodeId | null;\n derivePose?: DerivePoseFn<TPose>;\n /** Read only when something asks a dependency for its `path`. */\n derivePath?: DerivePathFn<TPose>;\n}\n\n/** What resolving a pose needs: the overrides, and enough of the scene to\n * reach a node's dependencies. A `Scene` satisfies it. */\nexport interface PoseSource<TPose> {\n readonly overrides: Pick<PoseOverrides<TPose>, 'read'>;\n get(id: NodeId): PosedNode<TPose> | undefined;\n childrenOf(id: NodeId): readonly NodeId[];\n /** When it composes frames, a derivation reads each dependency in the\n * frame of the node deriving. */\n readonly composition?: PoseComposition<TPose>;\n}\n\n/** A cached derived pose, with what it was derived from. */\ninterface PoseMemo<TPose> extends DepRecord<TPose> {\n value: TPose | null;\n}\n\n/** Ids whose derivation is on the stack. A dependency graph is meant to be\n * acyclic and `dependsOn` is unvalidated, so a cycle resolves to the authored\n * pose at whichever node closes it rather than overflowing the stack. */\nconst resolving = new Set<NodeId>();\n\n/** Bumped each time the guard above fires. A derivation that closed a cycle\n * answered from a fallback rather than from its dependencies, so its value is\n * an artifact of which node was asked first and must not be cached. */\nlet cycleHits = 0;\n\n/**\n * The pose `node` computes from its dependencies, or `null` when it derives\n * nothing — it has no `derivePose`, nothing to derive from, or its\n * `derivePose` returned `null`.\n *\n * A dependency is read through {@link effectivePose}, so a group of groups\n * resolves bottom-up and a dragged dependency's override reaches the\n * derivation the same frame it is published.\n */\nexport function derivedPose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose | null {\n return derivedPoseIn(source, node, 'effective');\n}\n\nfunction derivedPoseIn<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n reading: Reading,\n): TPose | null {\n const derive = node.derivePose;\n if (derive === undefined) return null;\n const ids = dependencyIdsOf(node, source.childrenOf);\n if (ids.length === 0) return null;\n if (resolving.has(node.id)) {\n cycleHits++;\n return null;\n }\n resolving.add(node.id);\n const hitsBefore = cycleHits;\n try {\n const depOf = depsAsRead(source, node, reading);\n // Keyed on the authored pose, which only sees a change someone pushed, so\n // the record also carries what the derivation read and re-checks it by\n // value on a hit — see `depMemo.ts`.\n let computed = false;\n const record = nodeMemo<PoseMemo<TPose>>(node, POSE_SLOT[reading], node.pose, () => {\n computed = true;\n return run(node, derive, ids.map(depOf), { ...emptyDepRecord<TPose>(), value: null });\n });\n // Mutating the record in place is what updates the memo slot, which holds\n // this object.\n if (!computed && !sameDeps(record, ids, depOf)) {\n run(node, derive, ids.map(depOf), record);\n }\n if (cycleHits !== hitsBefore) dropPoseKeyedMemoSlots(node);\n return record.value;\n } finally {\n resolving.delete(node.id);\n }\n}\n\n/** Run `derivePose` against `deps` and record what it was run against. */\nfunction run<TPose>(\n node: PosedNode<TPose>,\n derive: NonNullable<PosedNode<TPose>['derivePose']>,\n deps: readonly (DerivedDep<TPose> | undefined)[],\n into: PoseMemo<TPose>,\n): PoseMemo<TPose> {\n into.value = deriveRecorded(into, deps, (handed) => derive(node as never, handed));\n return into;\n}\n\n/**\n * `id` as a derivation sees it: the node, the pose it is painted at, and the\n * path it derives.\n *\n * The pose is resolved eagerly — every derivation reads it — and the path\n * lazily, because most do not and resolving a route costs a router run. Both\n * resolve *through* this function, so a dependency that is itself derived is\n * resolved before it is read.\n */\nexport function derivedDepOf<TPose>(\n source: PoseSource<TPose>,\n id: NodeId,\n reader?: PosedNode<TPose>,\n): DerivedDep<TPose> | undefined {\n return reader === undefined ? depIn(source, id, 'effective') : depsAsRead(source, reader, 'effective')(id);\n}\n\n/** `id` as {@link derivedDepOf} answers it, but at the pose and path the\n * document holds: no override anywhere down its dependencies counts. For a\n * reader that must not see an in-flight gesture, such as an edit pass. */\nexport function documentDepOf<TPose>(source: PoseSource<TPose>, id: NodeId): DerivedDep<TPose> | undefined {\n return depIn(source, id, 'document');\n}\n\n/** `(id) => that dependency as `reader`'s derivation is handed it`: its pose\n * in `reader`'s frame where the scene composes frames. A `'children'`\n * dependent shares its children's frame, so it reads them as they are. */\nfunction depsAsRead<TPose>(\n source: PoseSource<TPose>,\n reader: PosedNode<TPose>,\n reading: Reading,\n): (id: NodeId) => DerivedDep<TPose> | undefined {\n const plain = (id: NodeId) => depIn(source, id, reading);\n if (reader.dependsOn === 'children') return plain;\n const rebase = depFrameRebase(\n source.composition,\n reader,\n (id) => source.get(id),\n (id) => poseIn(source, source.get(id)!, reading),\n (id) => {\n const n = source.get(id);\n return n === undefined || definesFrame(n);\n },\n );\n if (rebase === null) return plain;\n return (id) => {\n const dep = plain(id);\n if (dep === undefined) return undefined;\n const pose = rebase(id, dep.pose);\n return pose === dep.pose ? dep : new RebasedDep(dep, pose);\n };\n}\n\n/** A dependency read from another frame: its pose rebased, and its path\n * carried by the same frame change when asked for. */\nclass RebasedDep<TPose> implements DerivedDep<TPose> {\n readonly node: Node<unknown, string, TPose>;\n\n constructor(private readonly dep: DerivedDep<TPose>, readonly pose: TPose) {\n this.node = dep.node;\n }\n\n get path(): Path | null {\n const path = this.dep.path;\n return path === null ? null : carryDepPath(path, this.dep.pose, this.pose);\n }\n}\n\nfunction depIn<TPose>(\n source: PoseSource<TPose>,\n id: NodeId,\n reading: Reading,\n): DerivedDep<TPose> | undefined {\n const node = source.get(id);\n if (node === undefined) return undefined;\n return new SceneDep(source, node, reading);\n}\n\n/**\n * A class rather than an object literal with a `get path()`: one of these is\n * built per dependency per derived node per frame, and an own accessor costs\n * an order of magnitude more to define than a prototype one does to inherit.\n */\nclass SceneDep<TPose> implements DerivedDep<TPose> {\n readonly node: Node<unknown, string, TPose>;\n readonly pose: TPose;\n private readonly shown: Path | undefined;\n\n constructor(\n private readonly source: PoseSource<TPose>,\n private readonly posed: PosedNode<TPose>,\n private readonly reading: Reading,\n ) {\n this.node = posed as unknown as Node<unknown, string, TPose>;\n const override = reading === 'effective' ? source.overrides.read(posed.id) : undefined;\n this.shown = override?.path;\n this.pose = override?.pose ?? derivedPoseIn(source, posed, reading) ?? posed.pose;\n }\n\n get path(): Path | null {\n if (this.shown !== undefined) return pathAsPainted(this.shown, this.pose, this.pose);\n const { source, reading } = this;\n return resolveDerivedPath(\n this.posed,\n depsAsRead(source, this.posed, reading),\n (depId) => source.childrenOf(depId),\n PATH_SLOT[reading],\n );\n }\n}\n\nfunction poseIn<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n reading: Reading,\n): TPose {\n if (reading === 'effective') {\n const override = source.overrides.read(node.id)?.pose;\n if (override !== undefined) return override;\n }\n return derivedPoseIn(source, node, reading) ?? node.pose;\n}\n\n/**\n * The pose the *document* says `node` is at: derived when it derives, else\n * authored. `effectivePose` minus the override step, at every link of the\n * dependency chain — a derived node's document pose holds still while\n * something it derives from is being dragged.\n *\n * For a reader that must not see an in-flight gesture, such as an action\n * capturing the `from` of a transform op.\n */\nexport function documentPose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose {\n return poseIn(source, node, 'document');\n}\n\n/**\n * The pose to draw, pick and measure `node` at. Every pose a reader acts on\n * comes from here.\n */\nexport function effectivePose<TPose>(\n source: PoseSource<TPose>,\n node: PosedNode<TPose>,\n): TPose {\n return poseIn(source, node, 'effective');\n}\n\n/**\n * Whether `node`'s pose is a frame its children are expressed in, under a\n * composing strategy.\n *\n * A container that derives its pose from its children cannot be one: moving\n * the frame to cover the children would move the children, and a `RectPose`\n * frame's origin is its corner, so no envelope leaves them in place. Such a\n * container is an envelope instead — its children are stored in its own\n * frame — and the composition fold passes over it.\n */\nexport function definesFrame(node: { dependsOn?: unknown; derivePose?: unknown }): boolean {\n return !isEnvelope(node);\n}\n\n/** Whether `node` derives its pose from its children: a group that is its\n * members, so moving them moves it. */\nexport function isEnvelope(node: { dependsOn?: unknown; derivePose?: unknown }): boolean {\n return node.dependsOn === 'children' && node.derivePose !== undefined;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { faceGeneration } from "@weasel-js/font";
|
|
2
|
+
|
|
3
|
+
//#region src/core/scene/fontReads.ts
|
|
4
|
+
/**
|
|
5
|
+
* Whether a derivation read glyph outlines. Outlines land asynchronously, so a
|
|
6
|
+
* derivation that read them while a face was loading is stale once it lands,
|
|
7
|
+
* with nothing in the scene changed. A derivation that noted a read records
|
|
8
|
+
* {@link fontGeneration} and re-derives when it moves.
|
|
9
|
+
*/
|
|
10
|
+
var noted = false;
|
|
11
|
+
/** Note that the derivation running now read glyph outlines. */
|
|
12
|
+
function noteFontRead() {
|
|
13
|
+
noted = true;
|
|
14
|
+
}
|
|
15
|
+
/** Run `fn`, reporting whether it — or anything it ran — noted a font read.
|
|
16
|
+
* A read inside counts toward any enclosing `withFontReads` too. */
|
|
17
|
+
function withFontReads(fn) {
|
|
18
|
+
const outer = noted;
|
|
19
|
+
noted = false;
|
|
20
|
+
try {
|
|
21
|
+
return {
|
|
22
|
+
value: fn(),
|
|
23
|
+
fonts: noted
|
|
24
|
+
};
|
|
25
|
+
} finally {
|
|
26
|
+
noted = outer || noted;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** The face generation a font-reading derivation is checked against: it
|
|
30
|
+
* moves when a face lands, not on every glyph bake. */
|
|
31
|
+
function fontGeneration() {
|
|
32
|
+
return faceGeneration();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//#endregion
|
|
36
|
+
export { fontGeneration, noteFontRead, withFontReads };
|
|
37
|
+
//# sourceMappingURL=fontReads.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fontReads.js","names":[],"sources":["../../../src/core/scene/fontReads.ts"],"sourcesContent":["/**\n * Whether a derivation read glyph outlines. Outlines land asynchronously, so a\n * derivation that read them while a face was loading is stale once it lands,\n * with nothing in the scene changed. A derivation that noted a read records\n * {@link fontGeneration} and re-derives when it moves.\n */\nimport { faceGeneration } from '@weasel-js/font';\n\nlet noted = false;\n\n/** Note that the derivation running now read glyph outlines. */\nexport function noteFontRead(): void {\n noted = true;\n}\n\n/** Run `fn`, reporting whether it — or anything it ran — noted a font read.\n * A read inside counts toward any enclosing `withFontReads` too. */\nexport function withFontReads<T>(fn: () => T): { value: T; fonts: boolean } {\n const outer = noted;\n noted = false;\n try {\n const value = fn();\n return { value, fonts: noted };\n } finally {\n noted = outer || noted;\n }\n}\n\n/** The face generation a font-reading derivation is checked against: it\n * moves when a face lands, not on every glyph bake. */\nexport function fontGeneration(): number {\n return faceGeneration();\n}\n"],"mappings":";;;;;;;;;AAQA,IAAI,QAAQ;;AAGZ,SAAgB,eAAqB;CACnC,QAAQ;AACV;;;AAIA,SAAgB,cAAiB,IAA2C;CAC1E,MAAM,QAAQ;CACd,QAAQ;CACR,IAAI;EAEF,OAAO;GAAE,OADK,GACL;GAAO,OAAO;EAAM;CAC/B,UAAU;EACR,QAAQ,SAAS;CACnB;AACF;;;AAIA,SAAgB,iBAAyB;CACvC,OAAO,eAAe;AACxB"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { INHERIT, resolveFill, resolveStroke } from "@weasel-js/paint";
|
|
2
|
+
|
|
3
|
+
//#region src/core/scene/inheritedPaint.ts
|
|
4
|
+
/** Whether `node`'s fill or stroke is {@link INHERIT}, so painting it needs
|
|
5
|
+
* its containers' paint. */
|
|
6
|
+
function inheritsPaint(node) {
|
|
7
|
+
const d = node.data;
|
|
8
|
+
return d?.fill === INHERIT || d?.stroke === INHERIT;
|
|
9
|
+
}
|
|
10
|
+
var LAST = /* @__PURE__ */ new WeakMap();
|
|
11
|
+
/**
|
|
12
|
+
* {@link InheritedPaint} for `node`, given its ancestors innermost first. Only
|
|
13
|
+
* containers are sources; one whose own field is `'inherit'` passes the
|
|
14
|
+
* question on to the next, and running out of containers is none.
|
|
15
|
+
*
|
|
16
|
+
* Hands back the object it returned last time for `node` while both values
|
|
17
|
+
* are the same references, so a memo can key on it.
|
|
18
|
+
*/
|
|
19
|
+
function inheritedPaintOf(node, ancestors) {
|
|
20
|
+
const d = node.data;
|
|
21
|
+
let wantFill = d?.fill === INHERIT;
|
|
22
|
+
let wantStroke = d?.stroke === INHERIT;
|
|
23
|
+
let fill = null;
|
|
24
|
+
let stroke = null;
|
|
25
|
+
for (const a of ancestors) {
|
|
26
|
+
if (!wantFill && !wantStroke) break;
|
|
27
|
+
if (a.kind !== "container") continue;
|
|
28
|
+
const ad = a.data;
|
|
29
|
+
if (wantFill && ad?.fill !== INHERIT) {
|
|
30
|
+
fill = resolveFill(ad?.fill);
|
|
31
|
+
wantFill = false;
|
|
32
|
+
}
|
|
33
|
+
if (wantStroke && ad?.stroke !== INHERIT) {
|
|
34
|
+
stroke = resolveStroke(ad?.stroke);
|
|
35
|
+
wantStroke = false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const last = LAST.get(node);
|
|
39
|
+
if (last !== void 0 && last.fill === fill && last.stroke === stroke) return last;
|
|
40
|
+
const next = {
|
|
41
|
+
fill,
|
|
42
|
+
stroke
|
|
43
|
+
};
|
|
44
|
+
LAST.set(node, next);
|
|
45
|
+
return next;
|
|
46
|
+
}
|
|
47
|
+
/** `node`'s ancestors in `scene`, innermost first, read lazily off `parent`. */
|
|
48
|
+
function* sceneAncestors(get, node) {
|
|
49
|
+
const seen = /* @__PURE__ */ new Set();
|
|
50
|
+
let id = node.parent ?? null;
|
|
51
|
+
while (id !== null && !seen.has(id)) {
|
|
52
|
+
seen.add(id);
|
|
53
|
+
const p = get(id);
|
|
54
|
+
if (p === void 0) return;
|
|
55
|
+
yield p;
|
|
56
|
+
id = p.parent ?? null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* `data` with an `'inherit'` fill or stroke replaced by what `inherited` says
|
|
61
|
+
* it resolves to, for a reader that wants the paint a node shows rather than
|
|
62
|
+
* the one it declares. Hands `data` back as is when nothing inherits.
|
|
63
|
+
*/
|
|
64
|
+
function withInheritedPaint(data, inherited) {
|
|
65
|
+
if (data.fill !== INHERIT && data.stroke !== INHERIT) return data;
|
|
66
|
+
return {
|
|
67
|
+
...data,
|
|
68
|
+
...data.fill === INHERIT ? { fill: inherited?.fill ?? null } : {},
|
|
69
|
+
...data.stroke === INHERIT ? { stroke: inherited?.stroke ?? null } : {}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
export { inheritedPaintOf, inheritsPaint, sceneAncestors, withInheritedPaint };
|
|
75
|
+
//# sourceMappingURL=inheritedPaint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inheritedPaint.js","names":[],"sources":["../../../src/core/scene/inheritedPaint.ts"],"sourcesContent":["import {\n INHERIT, resolveFill, resolveStroke,\n type Fill, type Inherit, type PaintedFill, type PaintedStroke, type Stroke,\n} from '@weasel-js/paint';\n\n/**\n * What a node's `'inherit'` fill and stroke resolve to: its nearest enclosing\n * container's, `null` for none. A field the node does not inherit is `null`\n * here whatever its containers hold.\n */\nexport interface InheritedPaint {\n readonly fill: PaintedFill | null;\n readonly stroke: PaintedStroke | null;\n}\n\ntype PaintData = { fill?: Fill | Inherit | null; stroke?: Stroke | Inherit | null } | null | undefined;\n\n/** Whether `node`'s fill or stroke is {@link INHERIT}, so painting it needs\n * its containers' paint. */\nexport function inheritsPaint(node: { data?: unknown }): boolean {\n const d = node.data as PaintData;\n return d?.fill === INHERIT || d?.stroke === INHERIT;\n}\n\nconst LAST = new WeakMap<object, InheritedPaint>();\n\n/**\n * {@link InheritedPaint} for `node`, given its ancestors innermost first. Only\n * containers are sources; one whose own field is `'inherit'` passes the\n * question on to the next, and running out of containers is none.\n *\n * Hands back the object it returned last time for `node` while both values\n * are the same references, so a memo can key on it.\n */\nexport function inheritedPaintOf(\n node: { data?: unknown },\n ancestors: Iterable<{ kind?: string; data?: unknown }>,\n): InheritedPaint {\n const d = node.data as PaintData;\n let wantFill = d?.fill === INHERIT;\n let wantStroke = d?.stroke === INHERIT;\n let fill: PaintedFill | null = null;\n let stroke: PaintedStroke | null = null;\n for (const a of ancestors) {\n if (!wantFill && !wantStroke) break;\n if (a.kind !== 'container') continue;\n const ad = a.data as PaintData;\n if (wantFill && ad?.fill !== INHERIT) {\n fill = resolveFill(ad?.fill);\n wantFill = false;\n }\n if (wantStroke && ad?.stroke !== INHERIT) {\n stroke = resolveStroke(ad?.stroke);\n wantStroke = false;\n }\n }\n const last = LAST.get(node);\n if (last !== undefined && last.fill === fill && last.stroke === stroke) return last;\n const next: InheritedPaint = { fill, stroke };\n LAST.set(node, next);\n return next;\n}\n\n/** `node`'s ancestors in `scene`, innermost first, read lazily off `parent`. */\nexport function* sceneAncestors<TNode extends { parent?: string | null }>(\n get: (id: never) => TNode | undefined,\n node: TNode,\n): Generator<TNode> {\n const seen = new Set<string>();\n let id = node.parent ?? null;\n while (id !== null && !seen.has(id)) {\n seen.add(id);\n const p = get(id as never);\n if (p === undefined) return;\n yield p;\n id = p.parent ?? null;\n }\n}\n\n/** The fields {@link withInheritedPaint} resolves. */\ntype ResolvedPaintFields<T> = Omit<T, 'fill' | 'stroke'> & {\n fill?: Exclude<T extends { fill?: infer F } ? F : never, Inherit>;\n stroke?: Exclude<T extends { stroke?: infer S } ? S : never, Inherit>;\n};\n\n/**\n * `data` with an `'inherit'` fill or stroke replaced by what `inherited` says\n * it resolves to, for a reader that wants the paint a node shows rather than\n * the one it declares. Hands `data` back as is when nothing inherits.\n */\nexport function withInheritedPaint<T extends { fill?: unknown; stroke?: unknown }>(\n data: T,\n inherited: InheritedPaint | undefined,\n): ResolvedPaintFields<T> {\n if (data.fill !== INHERIT && data.stroke !== INHERIT) return data as ResolvedPaintFields<T>;\n return {\n ...data,\n ...(data.fill === INHERIT ? { fill: inherited?.fill ?? null } : {}),\n ...(data.stroke === INHERIT ? { stroke: inherited?.stroke ?? null } : {}),\n } as ResolvedPaintFields<T>;\n}\n"],"mappings":";;;;;AAmBA,SAAgB,cAAc,MAAmC;CAC/D,MAAM,IAAI,KAAK;CACf,OAAO,GAAG,SAAS,WAAW,GAAG,WAAW;AAC9C;AAEA,IAAM,uBAAO,IAAI,QAAgC;;;;;;;;;AAUjD,SAAgB,iBACd,MACA,WACgB;CAChB,MAAM,IAAI,KAAK;CACf,IAAI,WAAW,GAAG,SAAS;CAC3B,IAAI,aAAa,GAAG,WAAW;CAC/B,IAAI,OAA2B;CAC/B,IAAI,SAA+B;CACnC,KAAK,MAAM,KAAK,WAAW;EACzB,IAAI,CAAC,YAAY,CAAC,YAAY;EAC9B,IAAI,EAAE,SAAS,aAAa;EAC5B,MAAM,KAAK,EAAE;EACb,IAAI,YAAY,IAAI,SAAS,SAAS;GACpC,OAAO,YAAY,IAAI,IAAI;GAC3B,WAAW;EACb;EACA,IAAI,cAAc,IAAI,WAAW,SAAS;GACxC,SAAS,cAAc,IAAI,MAAM;GACjC,aAAa;EACf;CACF;CACA,MAAM,OAAO,KAAK,IAAI,IAAI;CAC1B,IAAI,SAAS,UAAa,KAAK,SAAS,QAAQ,KAAK,WAAW,QAAQ,OAAO;CAC/E,MAAM,OAAuB;EAAE;EAAM;CAAO;CAC5C,KAAK,IAAI,MAAM,IAAI;CACnB,OAAO;AACT;;AAGA,UAAiB,eACf,KACA,MACkB;CAClB,MAAM,uBAAO,IAAI,IAAY;CAC7B,IAAI,KAAK,KAAK,UAAU;CACxB,OAAO,OAAO,QAAQ,CAAC,KAAK,IAAI,EAAE,GAAG;EACnC,KAAK,IAAI,EAAE;EACX,MAAM,IAAI,IAAI,EAAW;EACzB,IAAI,MAAM,QAAW;EACrB,MAAM;EACN,KAAK,EAAE,UAAU;CACnB;AACF;;;;;;AAaA,SAAgB,mBACd,MACA,WACwB;CACxB,IAAI,KAAK,SAAS,WAAW,KAAK,WAAW,SAAS,OAAO;CAC7D,OAAO;EACL,GAAG;EACH,GAAI,KAAK,SAAS,UAAU,EAAE,MAAM,WAAW,QAAQ,KAAK,IAAI,CAAC;EACjE,GAAI,KAAK,WAAW,UAAU,EAAE,QAAQ,WAAW,UAAU,KAAK,IAAI,CAAC;CACzE;AACF"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { unionAABB } from "../geometry/unionBounds.js";
|
|
2
|
+
import { CLIP_NOTHING, clipNothing } from "./clipNothing.js";
|
|
2
3
|
|
|
3
4
|
//#region src/core/scene/kitRegistry.ts
|
|
4
5
|
/**
|
|
@@ -48,6 +49,10 @@ function asRectPose(pose) {
|
|
|
48
49
|
function withKitRegistry(registry) {
|
|
49
50
|
return {
|
|
50
51
|
...registry,
|
|
52
|
+
clipFromPose: {
|
|
53
|
+
[CLIP_NOTHING]: clipNothing,
|
|
54
|
+
...registry.clipFromPose
|
|
55
|
+
},
|
|
51
56
|
derivePose: {
|
|
52
57
|
[UNION_OF_CHILDREN]: unionOfChildren,
|
|
53
58
|
...registry.derivePose
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kitRegistry.js","names":[],"sources":["../../../src/core/scene/kitRegistry.ts"],"sourcesContent":["/**\n * The `SceneRegistry` entries the kit owns.\n *\n * A registry-keyed function has to be resolvable by key in whatever scene\n * loads a snapshot, so a function the *kit* attaches to a node cannot live in\n * the consumer's registry alone — a document saved with a grouped container\n * would come back with a dead key. `createScene` merges these under the\n * consumer's, which stays the winner on a key collision.\n */\nimport { unionAABB } from '../geometry/unionBounds';\nimport type { RectPose, SceneRegistry } from './types';\n\n/** Registry key for {@link unionOfChildren}. */\nexport const UNION_OF_CHILDREN = 'kit:unionOfChildren';\n\n/**\n * A container's pose as the envelope of what it holds — `unionAABB`, so a\n * rotated member contributes the extent of its ink rather than its unrotated\n * box. Paired with `dependsOn: 'children'`, this is what makes a group's\n * bounds track its members.\n *\n * Returns `null` for an emptied container, which falls back to its authored\n * pose.\n *\n * Reads members as rect poses only: a scene whose poses are shaped otherwise\n * gets `null` here and its container keeps its authored pose. To make such a\n * container track its members, register `unionOfChildrenVia(itsDescriptor)`\n * under {@link UNION_OF_CHILDREN} — the consumer's entry wins the collision.\n */\nexport function unionOfChildren<TPose>(\n _node: unknown,\n deps: readonly ({ pose: TPose } | undefined)[],\n): TPose | null {\n const poses: RectPose[] = [];\n for (const d of deps) {\n if (d === undefined) continue;\n const rect = asRectPose(d.pose);\n if (rect === null) return null;\n poses.push(rect);\n }\n if (poses.length === 0) return null;\n const union = unionAABB(poses);\n // Sound by the check above: every member read as a rect, so the union is one\n // too, and a scene holding rect poses is a scene whose `TPose` is one.\n return union === null ? null : (union as unknown as TPose);\n}\n\n/** `pose` as a rect, or `null` when it is some other shape entirely. */\nexport function asRectPose(pose: unknown): RectPose | null {\n if (pose === null || typeof pose !== 'object') return null;\n const p = pose as Partial<RectPose>;\n const rect = typeof p.x === 'number' && typeof p.y === 'number'\n && typeof p.width === 'number' && typeof p.height === 'number';\n return rect ? (pose as RectPose) : null;\n}\n\n/** Merge the kit's own entries under a consumer registry. */\nexport function withKitRegistry<TPose, TData = unknown, TLayer extends string = string>(\n registry: SceneRegistry<TPose, TData, TLayer>,\n): SceneRegistry<TPose, TData, TLayer> {\n return {\n ...registry,\n derivePose: {\n [UNION_OF_CHILDREN]: unionOfChildren as NonNullable<\n SceneRegistry<TPose, TData, TLayer>['derivePose']\n >[string],\n ...registry.derivePose,\n },\n };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"kitRegistry.js","names":[],"sources":["../../../src/core/scene/kitRegistry.ts"],"sourcesContent":["/**\n * The `SceneRegistry` entries the kit owns.\n *\n * A registry-keyed function has to be resolvable by key in whatever scene\n * loads a snapshot, so a function the *kit* attaches to a node cannot live in\n * the consumer's registry alone — a document saved with a grouped container\n * would come back with a dead key. `createScene` merges these under the\n * consumer's, which stays the winner on a key collision.\n */\nimport { unionAABB } from '../geometry/unionBounds';\nimport type { RectPose, SceneRegistry } from './types';\nimport { CLIP_NOTHING, clipNothing } from './clipNothing';\n\n/** Registry key for {@link unionOfChildren}. */\nexport const UNION_OF_CHILDREN = 'kit:unionOfChildren';\n\n/**\n * A container's pose as the envelope of what it holds — `unionAABB`, so a\n * rotated member contributes the extent of its ink rather than its unrotated\n * box. Paired with `dependsOn: 'children'`, this is what makes a group's\n * bounds track its members.\n *\n * Returns `null` for an emptied container, which falls back to its authored\n * pose.\n *\n * Reads members as rect poses only: a scene whose poses are shaped otherwise\n * gets `null` here and its container keeps its authored pose. To make such a\n * container track its members, register `unionOfChildrenVia(itsDescriptor)`\n * under {@link UNION_OF_CHILDREN} — the consumer's entry wins the collision.\n */\nexport function unionOfChildren<TPose>(\n _node: unknown,\n deps: readonly ({ pose: TPose } | undefined)[],\n): TPose | null {\n const poses: RectPose[] = [];\n for (const d of deps) {\n if (d === undefined) continue;\n const rect = asRectPose(d.pose);\n if (rect === null) return null;\n poses.push(rect);\n }\n if (poses.length === 0) return null;\n const union = unionAABB(poses);\n // Sound by the check above: every member read as a rect, so the union is one\n // too, and a scene holding rect poses is a scene whose `TPose` is one.\n return union === null ? null : (union as unknown as TPose);\n}\n\n/** `pose` as a rect, or `null` when it is some other shape entirely. */\nexport function asRectPose(pose: unknown): RectPose | null {\n if (pose === null || typeof pose !== 'object') return null;\n const p = pose as Partial<RectPose>;\n const rect = typeof p.x === 'number' && typeof p.y === 'number'\n && typeof p.width === 'number' && typeof p.height === 'number';\n return rect ? (pose as RectPose) : null;\n}\n\n/** Merge the kit's own entries under a consumer registry. */\nexport function withKitRegistry<TPose, TData = unknown, TLayer extends string = string>(\n registry: SceneRegistry<TPose, TData, TLayer>,\n): SceneRegistry<TPose, TData, TLayer> {\n return {\n ...registry,\n clipFromPose: {\n [CLIP_NOTHING]: clipNothing,\n ...registry.clipFromPose,\n },\n derivePose: {\n [UNION_OF_CHILDREN]: unionOfChildren as NonNullable<\n SceneRegistry<TPose, TData, TLayer>['derivePose']\n >[string],\n ...registry.derivePose,\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,IAAa,oBAAoB;;;;;;;;;;;;;;;AAgBjC,SAAgB,gBACd,OACA,MACc;CACd,MAAM,QAAoB,CAAC;CAC3B,KAAK,MAAM,KAAK,MAAM;EACpB,IAAI,MAAM,QAAW;EACrB,MAAM,OAAO,WAAW,EAAE,IAAI;EAC9B,IAAI,SAAS,MAAM,OAAO;EAC1B,MAAM,KAAK,IAAI;CACjB;CACA,IAAI,MAAM,WAAW,GAAG,OAAO;CAC/B,MAAM,QAAQ,UAAU,KAAK;CAG7B,OAAO,UAAU,OAAO,OAAQ;AAClC;;AAGA,SAAgB,WAAW,MAAgC;CACzD,IAAI,SAAS,QAAQ,OAAO,SAAS,UAAU,OAAO;CACtD,MAAM,IAAI;CAGV,OAFa,OAAO,EAAE,MAAM,YAAY,OAAO,EAAE,MAAM,YAClD,OAAO,EAAE,UAAU,YAAY,OAAO,EAAE,WAAW,WACzC,OAAoB;AACrC;;AAGA,SAAgB,gBACd,UACqC;CACrC,OAAO;EACL,GAAG;EACH,cAAc;IACX,eAAe;GAChB,GAAG,SAAS;EACd;EACA,YAAY;IACT,oBAAoB;GAGrB,GAAG,SAAS;EACd;CACF;AACF"}
|
|
@@ -8,8 +8,8 @@ var MEMO = /* @__PURE__ */ new WeakMap();
|
|
|
8
8
|
var generation = 0;
|
|
9
9
|
/**
|
|
10
10
|
* Return the value `derive` produces for `node`, recomputing only when the
|
|
11
|
-
* node's `data` reference, the given `pose` reference,
|
|
12
|
-
* generation has changed since the last call for this `slot`.
|
|
11
|
+
* node's `data` reference, the given `pose` reference, the `also` reference,
|
|
12
|
+
* or the global generation has changed since the last call for this `slot`.
|
|
13
13
|
*
|
|
14
14
|
* `slot` names the derivation, so a node's several derived values do not
|
|
15
15
|
* overwrite each other.
|
|
@@ -18,9 +18,13 @@ var generation = 0;
|
|
|
18
18
|
* legitimately derive against a pose that isn't `node.pose`: a preview or
|
|
19
19
|
* ghost pose mid-drag. Pass `undefined` for derivations that don't depend on
|
|
20
20
|
* the pose at all, leaving `data` as the only key.
|
|
21
|
+
*
|
|
22
|
+
* `also` is for an input that lives outside the node, such as the paint a
|
|
23
|
+
* node inherits from its container. It must be a reference its owner keeps
|
|
24
|
+
* while the input is unchanged.
|
|
21
25
|
*/
|
|
22
|
-
function nodeMemo(node, slot, pose, derive) {
|
|
23
|
-
if (typeof slot !== "string") return scopedNodeMemo(node, slot, pose, derive);
|
|
26
|
+
function nodeMemo(node, slot, pose, derive, also) {
|
|
27
|
+
if (typeof slot !== "string") return scopedNodeMemo(node, slot, pose, derive, also);
|
|
24
28
|
let record = MEMO.get(node);
|
|
25
29
|
if (record === void 0) {
|
|
26
30
|
record = {
|
|
@@ -35,36 +39,40 @@ function nodeMemo(node, slot, pose, derive) {
|
|
|
35
39
|
}
|
|
36
40
|
const data = node.data;
|
|
37
41
|
const hit = record.slots.get(slot);
|
|
38
|
-
if (hit !== void 0 && hit.pose === pose && hit.data === data) return hit.value;
|
|
42
|
+
if (hit !== void 0 && hit.pose === pose && hit.data === data && hit.also === also) return hit.value;
|
|
39
43
|
const value = derive();
|
|
40
44
|
if (hit !== void 0) {
|
|
41
45
|
hit.pose = pose;
|
|
42
46
|
hit.data = data;
|
|
47
|
+
hit.also = also;
|
|
43
48
|
hit.value = value;
|
|
44
49
|
} else record.slots.set(slot, {
|
|
45
50
|
pose,
|
|
46
51
|
data,
|
|
52
|
+
also,
|
|
47
53
|
value
|
|
48
54
|
});
|
|
49
55
|
return value;
|
|
50
56
|
}
|
|
51
|
-
function scopedNodeMemo(node, scope, pose, derive) {
|
|
57
|
+
function scopedNodeMemo(node, scope, pose, derive, also) {
|
|
52
58
|
const entries = scope;
|
|
53
59
|
const data = node.data;
|
|
54
60
|
const drops = MEMO.get(node)?.drops ?? 0;
|
|
55
61
|
const hit = entries.get(node);
|
|
56
|
-
if (hit !== void 0 && hit.generation === generation && hit.pose === pose && hit.data === data && (pose === void 0 || hit.drops === drops)) return hit.value;
|
|
62
|
+
if (hit !== void 0 && hit.generation === generation && hit.pose === pose && hit.data === data && hit.also === also && (pose === void 0 || hit.drops === drops)) return hit.value;
|
|
57
63
|
const value = derive();
|
|
58
64
|
const dropsNow = MEMO.get(node)?.drops ?? 0;
|
|
59
65
|
if (hit !== void 0) {
|
|
60
66
|
hit.pose = pose;
|
|
61
67
|
hit.data = data;
|
|
68
|
+
hit.also = also;
|
|
62
69
|
hit.value = value;
|
|
63
70
|
hit.generation = generation;
|
|
64
71
|
hit.drops = dropsNow;
|
|
65
72
|
} else entries.set(node, {
|
|
66
73
|
pose,
|
|
67
74
|
data,
|
|
75
|
+
also,
|
|
68
76
|
value,
|
|
69
77
|
generation,
|
|
70
78
|
drops: dropsNow
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nodeMemo.js","names":[],"sources":["../../../src/core/scene/nodeMemo.ts"],"sourcesContent":["/**\n * `nodeMemo` — memoize anything derived from a node, keyed on the node's\n * identity plus the `pose` / `data` references the scene swaps on every edit.\n *\n * The scene mutates node objects in place but *replaces* `pose` and `data`\n * with new references, so those two references are an exact freshness signal.\n * The assumption it rests on: nobody mutates `node.data` (or `node.pose`) in\n * place without replacing the reference. A consumer who violates it sees a\n * stale derivation until the next real edit.\n *\n * **What the key does not cover** is ambient state that `derive` reads — a\n * registry, a font set, a decode cache. Whoever owns the ambient state calls\n * {@link bumpNodeMemoGeneration}. Derivations that read ambient state with\n * *no* such signal (an async image decode landing, say) must not be memoized\n * here at all.\n */\n\n/** One memoized derivation: the references it was derived against, plus the\n * value. Presence in the slot map is what \"computed\" means — a `null` or\n * `undefined` value is a real answer and stays cached. */\ninterface MemoSlot {\n /** Reference-compared against the caller's `pose` argument. */\n pose: unknown;\n /** Reference-compared against `node.data` at call time. */\n data: unknown;\n value: unknown;\n}\n\ninterface NodeMemoRecord {\n /** `generation` at the time the slots were filled. */\n generation: number;\n slots: Map<string, MemoSlot>;\n /** How many times {@link dropPoseKeyedMemoSlots} has run for this node,\n * which is how a drop reaches the scopes it cannot enumerate. */\n drops: number;\n}\n\n/** A scoped entry also carries what a named slot's record holds for it. */\ninterface ScopedSlot extends MemoSlot {\n generation: number;\n drops: number;\n}\n\ndeclare const scopeBrand: unique symbol;\n\n/**\n * A private place to memoize one derivation, in place of a slot name.\n *\n * A named slot lives on the node, so it lasts as long as the node does. That\n * is wrong for a derivation that belongs to some shorter-lived reader — one\n * `toPose` function among many over the life of a scene. A scope is held by\n * that reader, and its entries go when the reader does.\n */\nexport type NodeMemoScope = { readonly [scopeBrand]: true };\n\nexport function createNodeMemoScope(): NodeMemoScope {\n return new WeakMap<object, ScopedSlot>() as unknown as NodeMemoScope;\n}\n\nconst MEMO = new WeakMap<object, NodeMemoRecord>();\n\n/** Bumped whenever ambient state a derivation reads has changed. Entries\n * stamped with an older value are dropped rather than served stale. */\nlet generation = 0;\n\n/** A node, as far as the memo is concerned: an object identity that may carry\n * a `data` reference. */\ntype MemoizableNode = { data?: unknown };\n\n/**\n * Return the value `derive` produces for `node`, recomputing only when the\n * node's `data` reference, the given `pose` reference, or the global
|
|
1
|
+
{"version":3,"file":"nodeMemo.js","names":[],"sources":["../../../src/core/scene/nodeMemo.ts"],"sourcesContent":["/**\n * `nodeMemo` — memoize anything derived from a node, keyed on the node's\n * identity plus the `pose` / `data` references the scene swaps on every edit.\n *\n * The scene mutates node objects in place but *replaces* `pose` and `data`\n * with new references, so those two references are an exact freshness signal.\n * The assumption it rests on: nobody mutates `node.data` (or `node.pose`) in\n * place without replacing the reference. A consumer who violates it sees a\n * stale derivation until the next real edit.\n *\n * **What the key does not cover** is ambient state that `derive` reads — a\n * registry, a font set, a decode cache. Whoever owns the ambient state calls\n * {@link bumpNodeMemoGeneration}. Derivations that read ambient state with\n * *no* such signal (an async image decode landing, say) must not be memoized\n * here at all.\n */\n\n/** One memoized derivation: the references it was derived against, plus the\n * value. Presence in the slot map is what \"computed\" means — a `null` or\n * `undefined` value is a real answer and stays cached. */\ninterface MemoSlot {\n /** Reference-compared against the caller's `pose` argument. */\n pose: unknown;\n /** Reference-compared against `node.data` at call time. */\n data: unknown;\n /** Reference-compared against the caller's `also` argument. */\n also: unknown;\n value: unknown;\n}\n\ninterface NodeMemoRecord {\n /** `generation` at the time the slots were filled. */\n generation: number;\n slots: Map<string, MemoSlot>;\n /** How many times {@link dropPoseKeyedMemoSlots} has run for this node,\n * which is how a drop reaches the scopes it cannot enumerate. */\n drops: number;\n}\n\n/** A scoped entry also carries what a named slot's record holds for it. */\ninterface ScopedSlot extends MemoSlot {\n generation: number;\n drops: number;\n}\n\ndeclare const scopeBrand: unique symbol;\n\n/**\n * A private place to memoize one derivation, in place of a slot name.\n *\n * A named slot lives on the node, so it lasts as long as the node does. That\n * is wrong for a derivation that belongs to some shorter-lived reader — one\n * `toPose` function among many over the life of a scene. A scope is held by\n * that reader, and its entries go when the reader does.\n */\nexport type NodeMemoScope = { readonly [scopeBrand]: true };\n\nexport function createNodeMemoScope(): NodeMemoScope {\n return new WeakMap<object, ScopedSlot>() as unknown as NodeMemoScope;\n}\n\nconst MEMO = new WeakMap<object, NodeMemoRecord>();\n\n/** Bumped whenever ambient state a derivation reads has changed. Entries\n * stamped with an older value are dropped rather than served stale. */\nlet generation = 0;\n\n/** A node, as far as the memo is concerned: an object identity that may carry\n * a `data` reference. */\ntype MemoizableNode = { data?: unknown };\n\n/**\n * Return the value `derive` produces for `node`, recomputing only when the\n * node's `data` reference, the given `pose` reference, the `also` reference,\n * or the global generation has changed since the last call for this `slot`.\n *\n * `slot` names the derivation, so a node's several derived values do not\n * overwrite each other.\n *\n * `pose` is a **parameter rather than a read off the node**, because callers\n * legitimately derive against a pose that isn't `node.pose`: a preview or\n * ghost pose mid-drag. Pass `undefined` for derivations that don't depend on\n * the pose at all, leaving `data` as the only key.\n *\n * `also` is for an input that lives outside the node, such as the paint a\n * node inherits from its container. It must be a reference its owner keeps\n * while the input is unchanged.\n */\nexport function nodeMemo<T>(\n node: MemoizableNode,\n slot: string | NodeMemoScope,\n pose: unknown,\n derive: () => T,\n also?: unknown,\n): T {\n if (typeof slot !== 'string') return scopedNodeMemo(node, slot, pose, derive, also);\n let record = MEMO.get(node);\n if (record === undefined) {\n record = { generation, slots: new Map(), drops: 0 };\n MEMO.set(node, record);\n } else if (record.generation !== generation) {\n record.generation = generation;\n record.slots.clear();\n }\n\n const data = node.data;\n const hit = record.slots.get(slot);\n if (hit !== undefined && hit.pose === pose && hit.data === data && hit.also === also) {\n return hit.value as T;\n }\n\n const value = derive();\n // Reuse the slot object when there is one: this runs per node per frame, and\n // the entry churn is the only allocation the memo would otherwise add.\n if (hit !== undefined) {\n hit.pose = pose;\n hit.data = data;\n hit.also = also;\n hit.value = value;\n } else {\n record.slots.set(slot, { pose, data, also, value });\n }\n return value;\n}\n\nfunction scopedNodeMemo<T>(\n node: MemoizableNode,\n scope: NodeMemoScope,\n pose: unknown,\n derive: () => T,\n also: unknown,\n): T {\n const entries = scope as unknown as WeakMap<object, ScopedSlot>;\n const data = node.data;\n const drops = MEMO.get(node)?.drops ?? 0;\n const hit = entries.get(node);\n if (\n hit !== undefined && hit.generation === generation && hit.pose === pose && hit.data === data\n && hit.also === also && (pose === undefined || hit.drops === drops)\n ) {\n return hit.value as T;\n }\n const value = derive();\n // Read again: a derivation that hit a cycle drops this node's slots itself.\n const dropsNow = MEMO.get(node)?.drops ?? 0;\n if (hit !== undefined) {\n hit.pose = pose;\n hit.data = data;\n hit.also = also;\n hit.value = value;\n hit.generation = generation;\n hit.drops = dropsNow;\n } else {\n entries.set(node, { pose, data, also, value, generation, drops: dropsNow });\n }\n return value;\n}\n\n/** How many named slots `node` holds. For tests of what is kept on a node. */\nexport function nodeMemoSlotCount(node: MemoizableNode): number {\n const record = MEMO.get(node);\n return record === undefined || record.generation !== generation ? 0 : record.slots.size;\n}\n\n/**\n * Drop every memoized slot on every node. For owners of ambient state that\n * changes what a `derive` would return. Cheap — it bumps a counter.\n */\nexport function bumpNodeMemoGeneration(): void {\n generation++;\n}\n\n/**\n * Drop the slots of `node` that were keyed on a pose, keeping the ones that\n * weren't.\n *\n * For per-frame pose overrides, which mutate one buffer in place rather than\n * replacing the reference, which the `(pose, data)` key cannot see.\n * {@link bumpNodeMemoGeneration} is the wrong tool there — it would drop every\n * static node's slots too, every frame.\n */\nexport function dropPoseKeyedMemoSlots(node: MemoizableNode): void {\n const record = MEMO.get(node);\n if (record === undefined) {\n MEMO.set(node, { generation, slots: new Map(), drops: 1 });\n return;\n }\n record.drops++;\n for (const [slot, entry] of record.slots) {\n if (entry.pose !== undefined) record.slots.delete(slot);\n }\n}\n"],"mappings":";AAyDA,SAAgB,sBAAqC;CACnD,uBAAO,IAAI,QAA4B;AACzC;AAEA,IAAM,uBAAO,IAAI,QAAgC;;;AAIjD,IAAI,aAAa;;;;;;;;;;;;;;;;;;AAuBjB,SAAgB,SACd,MACA,MACA,MACA,QACA,MACG;CACH,IAAI,OAAO,SAAS,UAAU,OAAO,eAAe,MAAM,MAAM,MAAM,QAAQ,IAAI;CAClF,IAAI,SAAS,KAAK,IAAI,IAAI;CAC1B,IAAI,WAAW,QAAW;EACxB,SAAS;GAAE;GAAY,uBAAO,IAAI,IAAI;GAAG,OAAO;EAAE;EAClD,KAAK,IAAI,MAAM,MAAM;CACvB,OAAO,IAAI,OAAO,eAAe,YAAY;EAC3C,OAAO,aAAa;EACpB,OAAO,MAAM,MAAM;CACrB;CAEA,MAAM,OAAO,KAAK;CAClB,MAAM,MAAM,OAAO,MAAM,IAAI,IAAI;CACjC,IAAI,QAAQ,UAAa,IAAI,SAAS,QAAQ,IAAI,SAAS,QAAQ,IAAI,SAAS,MAC9E,OAAO,IAAI;CAGb,MAAM,QAAQ,OAAO;CAGrB,IAAI,QAAQ,QAAW;EACrB,IAAI,OAAO;EACX,IAAI,OAAO;EACX,IAAI,OAAO;EACX,IAAI,QAAQ;CACd,OACE,OAAO,MAAM,IAAI,MAAM;EAAE;EAAM;EAAM;EAAM;CAAM,CAAC;CAEpD,OAAO;AACT;AAEA,SAAS,eACP,MACA,OACA,MACA,QACA,MACG;CACH,MAAM,UAAU;CAChB,MAAM,OAAO,KAAK;CAClB,MAAM,QAAQ,KAAK,IAAI,IAAI,CAAC,EAAE,SAAS;CACvC,MAAM,MAAM,QAAQ,IAAI,IAAI;CAC5B,IACE,QAAQ,UAAa,IAAI,eAAe,cAAc,IAAI,SAAS,QAAQ,IAAI,SAAS,QACrF,IAAI,SAAS,SAAY,SAAS,UAAa,IAAI,UAAU,QAEhE,OAAO,IAAI;CAEb,MAAM,QAAQ,OAAO;CAErB,MAAM,WAAW,KAAK,IAAI,IAAI,CAAC,EAAE,SAAS;CAC1C,IAAI,QAAQ,QAAW;EACrB,IAAI,OAAO;EACX,IAAI,OAAO;EACX,IAAI,OAAO;EACX,IAAI,QAAQ;EACZ,IAAI,aAAa;EACjB,IAAI,QAAQ;CACd,OACE,QAAQ,IAAI,MAAM;EAAE;EAAM;EAAM;EAAM;EAAO;EAAY,OAAO;CAAS,CAAC;CAE5E,OAAO;AACT;;;;;AAYA,SAAgB,yBAA+B;CAC7C;AACF;;;;;;;;;;AAWA,SAAgB,uBAAuB,MAA4B;CACjE,MAAM,SAAS,KAAK,IAAI,IAAI;CAC5B,IAAI,WAAW,QAAW;EACxB,KAAK,IAAI,MAAM;GAAE;GAAY,uBAAO,IAAI,IAAI;GAAG,OAAO;EAAE,CAAC;EACzD;CACF;CACA,OAAO;CACP,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,OACjC,IAAI,MAAM,SAAS,QAAW,OAAO,MAAM,OAAO,IAAI;AAE1D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blend.js","names":[],"sources":["../../../../src/core/scene/pathEffects/blend.ts"],"sourcesContent":["/**\n * A blend's steps: where each sits between its two sources — evenly by\n * count, or a fixed distance apart — and, with a spine, laid along it.\n */\nimport {\n boundsOfPath, measurePath, multiply, prepareBlend, rotate, rotatePathAround, transformPath, translate,\n type BlendPathsOptions, type Path, type PathMeasure,\n} from '@weasel-js/geom';\nimport { lerpFill, lerpStroke } from '@weasel-js/paint';\nimport type { DerivedPart } from '../derivedParts';\nimport type { EffectSource } from './source';\n\n/** Steps between two shapes. Its sources are `[from, to]`, or\n * `[from, to, spine]` to lay the steps along a third path. */\nexport interface BlendEffect extends BlendPathsOptions {\n kind: 'blend';\n /** How many shapes stand between the two sources, spacing by count. */\n steps: number;\n /**\n * How the steps are spaced. `'count'` puts `steps` of them evenly between\n * the sources; `'distance'` puts one every `distance` along the run from\n * the first source, as many as fit short of the second, so the last gap is\n * whatever is left. Default `'count'`.\n */\n spaceBy?: 'count' | 'distance';\n /** Distance from one shape's middle to the next, spacing by distance —\n * along the spine where there is one. Spacing by count when unset. */\n distance?: number;\n /**\n * Interpolate each source's rotation as a turn about its middle, rather\n * than moving every point in a straight line — so a shape turned 90° at\n * one end turns through 45° halfway instead of shrinking there. Default\n * `true`; it applies where both sources are turned boxes.\n */\n rotate?: boolean;\n /** With a spine, turn each step by how far the spine's heading where it\n * sits differs from the straight run between the sources. Default\n * `false`, which only moves them. */\n orient?: boolean;\n}\n\nconst EPS = 1e-9;\n\n/** The fractions of the way from the first source to the second that the\n * steps sit at, over a run `length` long. */\nexport function blendStations(effect: BlendEffect, length: number): number[] {\n const out: number[] = [];\n const d = effect.distance;\n if (effect.spaceBy === 'distance' && d !== undefined && d > 0) {\n if (!(length > 0)) return out;\n for (let s = d; s < length - EPS * Math.max(length, 1); s += d) out.push(s / length);\n return out;\n }\n const steps = Math.max(Math.floor(effect.steps), 0);\n for (let i = 1; i <= steps; i++) out.push(i / (steps + 1));\n return out;\n}\n\n/** The middle a source's steps are interpolated about: its turn's center\n * where it is a turned box, else its outline's bounds' middle. */\nfunction middleOf(source: EffectSource): { x: number; y: number } {\n if (source.framed) return { x: source.framed.cx, y: source.framed.cy };\n const b = boundsOfPath(source.world);\n return { x: b.x + b.width / 2, y: b.y + b.height / 2 };\n}\n\n/** The parts of `effect` between `a` and `b`, along `spine` when given.\n * `null` when the spine has no length to measure. */\nexport function blendParts(effect: BlendEffect, a: EffectSource, b: EffectSource, spine?: Path): DerivedPart[] | null {\n const from = middleOf(a);\n const to = middleOf(b);\n const along: PathMeasure | null = spine === undefined ? null : measurePath(spine);\n if (spine !== undefined && along === null) return null;\n const stations = blendStations(effect, along ? along.length : Math.hypot(to.x - from.x, to.y - from.y));\n if (stations.length === 0) return [];\n\n const turned = (effect.rotate ?? true) && a.framed !== undefined && b.framed !== undefined\n ? { a: a.framed, b: b.framed }\n : null;\n const blend = turned\n ? prepareBlend(turned.a.path, turned.b.path, effect)\n : prepareBlend(a.world, b.world, effect);\n // The shorter way round, so a turn of 350° reads as -10°.\n const turn = turned\n ? Math.atan2(Math.sin(turned.b.rotation - turned.a.rotation), Math.cos(turned.b.rotation - turned.a.rotation))\n : 0;\n const chord = Math.atan2(to.y - from.y, to.x - from.x);\n return stations.map((t) => {\n let path: Path = blend.at(t);\n if (turned) {\n const cx = turned.a.cx + (turned.b.cx - turned.a.cx) * t;\n const cy = turned.a.cy + (turned.b.cy - turned.a.cy) * t;\n const rotation = turned.a.rotation + turn * t;\n if (rotation !== 0) path = rotatePathAround(path, cx, cy, rotation);\n }\n if (along) {\n const { point, tangent } = along.at(t * along.length);\n const angle = effect.orient ? Math.atan2(tangent.y, tangent.x) - chord : 0;\n const middle = { x: from.x + (to.x - from.x) * t, y: from.y + (to.y - from.y) * t };\n path = transformPath(path, multiply(translate(point.x, point.y), multiply(rotate(angle), translate(-middle.x, -middle.y))));\n }\n return { path, fill: lerpFill(a.fill, b.fill, t) ?? null, stroke: lerpStroke(a.stroke, b.stroke, t) ?? null };\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"blend.js","names":[],"sources":["../../../../src/core/scene/pathEffects/blend.ts"],"sourcesContent":["/**\n * A blend's steps: where each sits between its two sources — evenly by\n * count, or a fixed distance apart — and, with a spine, laid along it.\n */\nimport {\n boundsOfPath, measurePath, multiply, prepareBlend, rotate, rotatePathAround, transformPath, translate,\n type BlendPathsOptions, type Path, type PathMeasure,\n} from '@weasel-js/geom';\nimport { lerpFill, lerpStroke } from '@weasel-js/paint';\nimport type { DerivedPart } from '../derivedParts';\nimport type { EffectSource, PathEffectOptions } from './source';\n\n/** Steps between two shapes. Its sources are `[from, to]`, or\n * `[from, to, spine]` to lay the steps along a third path. */\nexport interface BlendEffect extends BlendPathsOptions, PathEffectOptions {\n kind: 'blend';\n /** How many shapes stand between the two sources, spacing by count. */\n steps: number;\n /**\n * How the steps are spaced. `'count'` puts `steps` of them evenly between\n * the sources; `'distance'` puts one every `distance` along the run from\n * the first source, as many as fit short of the second, so the last gap is\n * whatever is left. Default `'count'`.\n */\n spaceBy?: 'count' | 'distance';\n /** Distance from one shape's middle to the next, spacing by distance —\n * along the spine where there is one. Spacing by count when unset. */\n distance?: number;\n /**\n * Interpolate each source's rotation as a turn about its middle, rather\n * than moving every point in a straight line — so a shape turned 90° at\n * one end turns through 45° halfway instead of shrinking there. Default\n * `true`; it applies where both sources are turned boxes.\n */\n rotate?: boolean;\n /** With a spine, turn each step by how far the spine's heading where it\n * sits differs from the straight run between the sources. Default\n * `false`, which only moves them. */\n orient?: boolean;\n}\n\nconst EPS = 1e-9;\n\n/** The fractions of the way from the first source to the second that the\n * steps sit at, over a run `length` long. */\nexport function blendStations(effect: BlendEffect, length: number): number[] {\n const out: number[] = [];\n const d = effect.distance;\n if (effect.spaceBy === 'distance' && d !== undefined && d > 0) {\n if (!(length > 0)) return out;\n for (let s = d; s < length - EPS * Math.max(length, 1); s += d) out.push(s / length);\n return out;\n }\n const steps = Math.max(Math.floor(effect.steps), 0);\n for (let i = 1; i <= steps; i++) out.push(i / (steps + 1));\n return out;\n}\n\n/** The middle a source's steps are interpolated about: its turn's center\n * where it is a turned box, else its outline's bounds' middle. */\nfunction middleOf(source: EffectSource): { x: number; y: number } {\n if (source.framed) return { x: source.framed.cx, y: source.framed.cy };\n const b = boundsOfPath(source.world);\n return { x: b.x + b.width / 2, y: b.y + b.height / 2 };\n}\n\n/** The parts of `effect` between `a` and `b`, along `spine` when given.\n * `null` when the spine has no length to measure. */\nexport function blendParts(effect: BlendEffect, a: EffectSource, b: EffectSource, spine?: Path): DerivedPart[] | null {\n const from = middleOf(a);\n const to = middleOf(b);\n const along: PathMeasure | null = spine === undefined ? null : measurePath(spine);\n if (spine !== undefined && along === null) return null;\n const stations = blendStations(effect, along ? along.length : Math.hypot(to.x - from.x, to.y - from.y));\n if (stations.length === 0) return [];\n\n const turned = (effect.rotate ?? true) && a.framed !== undefined && b.framed !== undefined\n ? { a: a.framed, b: b.framed }\n : null;\n const blend = turned\n ? prepareBlend(turned.a.path, turned.b.path, effect)\n : prepareBlend(a.world, b.world, effect);\n // The shorter way round, so a turn of 350° reads as -10°.\n const turn = turned\n ? Math.atan2(Math.sin(turned.b.rotation - turned.a.rotation), Math.cos(turned.b.rotation - turned.a.rotation))\n : 0;\n const chord = Math.atan2(to.y - from.y, to.x - from.x);\n return stations.map((t) => {\n let path: Path = blend.at(t);\n if (turned) {\n const cx = turned.a.cx + (turned.b.cx - turned.a.cx) * t;\n const cy = turned.a.cy + (turned.b.cy - turned.a.cy) * t;\n const rotation = turned.a.rotation + turn * t;\n if (rotation !== 0) path = rotatePathAround(path, cx, cy, rotation);\n }\n if (along) {\n const { point, tangent } = along.at(t * along.length);\n const angle = effect.orient ? Math.atan2(tangent.y, tangent.x) - chord : 0;\n const middle = { x: from.x + (to.x - from.x) * t, y: from.y + (to.y - from.y) * t };\n path = transformPath(path, multiply(translate(point.x, point.y), multiply(rotate(angle), translate(-middle.x, -middle.y))));\n }\n return { path, fill: lerpFill(a.fill, b.fill, t) ?? null, stroke: lerpStroke(a.stroke, b.stroke, t) ?? null };\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { fontGeneration, noteFontRead, withFontReads } from "../fontReads.js";
|
|
1
2
|
import { samePoseValue, snapshotPose } from "../poseSnapshot.js";
|
|
2
3
|
import { derivedPartsOf, pathFromParts } from "../derivedParts.js";
|
|
3
4
|
import { effectParts } from "./effects.js";
|
|
@@ -33,14 +34,16 @@ function createPathEffectDerivations(read) {
|
|
|
33
34
|
const held = results.get(node);
|
|
34
35
|
if (held !== void 0 && current(held, node, deps)) return held;
|
|
35
36
|
const effect = pathEffectOf(node);
|
|
36
|
-
const
|
|
37
|
+
const generation = fontGeneration();
|
|
38
|
+
const { value: parts, fonts } = withFontReads(() => effect === null ? null : effectParts(effect, deps.map((d) => d === void 0 ? null : read(d))));
|
|
37
39
|
const result = {
|
|
38
40
|
data: node.data,
|
|
39
41
|
nodes: deps.map((d) => d?.node),
|
|
40
42
|
depData: deps.map((d) => d?.node.data),
|
|
41
43
|
poses: deps.map((d) => d === void 0 ? void 0 : snapshotPose(d.pose)),
|
|
42
44
|
paths: deps.map((d) => d?.path ?? null),
|
|
43
|
-
path: parts === null ? null : pathFromParts(parts)
|
|
45
|
+
path: parts === null ? null : pathFromParts(parts),
|
|
46
|
+
faces: fonts ? generation : null
|
|
44
47
|
};
|
|
45
48
|
results.set(node, result);
|
|
46
49
|
return result;
|
|
@@ -62,6 +65,7 @@ function createPathEffectDerivations(read) {
|
|
|
62
65
|
}
|
|
63
66
|
function current(held, node, deps) {
|
|
64
67
|
if (held.data !== node.data || held.nodes.length !== deps.length) return false;
|
|
68
|
+
if (held.faces !== null && held.faces !== fontGeneration()) return false;
|
|
65
69
|
for (let i = 0; i < deps.length; i++) {
|
|
66
70
|
const d = deps[i];
|
|
67
71
|
if (held.nodes[i] !== d?.node) return false;
|
|
@@ -69,6 +73,7 @@ function current(held, node, deps) {
|
|
|
69
73
|
if (held.depData[i] !== d.node.data || !samePoseValue(held.poses[i], d.pose)) return false;
|
|
70
74
|
if (held.paths[i] !== d.path) return false;
|
|
71
75
|
}
|
|
76
|
+
if (held.faces !== null) noteFontRead();
|
|
72
77
|
return true;
|
|
73
78
|
}
|
|
74
79
|
/** The parts a path-effect node paints, as its sources stand, or `null` when
|