@weasel-js/core 1.12.0 → 1.13.0-rc.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 +91 -0
- package/dist/animation/poseTarget.js +46 -15
- package/dist/animation/poseTarget.js.map +1 -1
- package/dist/animation/poseTransition.js +3 -3
- package/dist/animation/poseTransition.js.map +1 -1
- package/dist/animation/reflow.js +3 -6
- package/dist/animation/reflow.js.map +1 -1
- package/dist/animation/rig/bindRig.js +2 -2
- package/dist/animation/rig/bindRig.js.map +1 -1
- package/dist/animation/rig/skinBinding.js +2 -2
- package/dist/animation/rig/skinBinding.js.map +1 -1
- package/dist/barrel/canvas.js +5 -3
- package/dist/barrel/chrome.js +3 -1
- package/dist/barrel/paths.js +3 -2
- package/dist/barrel/routing.js +2 -2
- package/dist/barrel/scene.js +6 -1
- package/dist/canvas/NodeShape.js +1 -0
- package/dist/canvas/NodeShape.js.map +1 -1
- package/dist/canvas/SceneCanvas/SceneCanvasInput.js +2 -0
- package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
- package/dist/canvas/SceneCanvas/canvasPassThrough.js +1 -1
- package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
- package/dist/canvas/SceneCanvas/defaultNodeRouting.js +2 -2
- package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
- package/dist/canvas/SceneCanvas/scenePointQuery.js +20 -2
- package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -1
- package/dist/canvas/SceneCanvas/useCanvasTools.js +3 -0
- package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +25 -19
- package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +7 -0
- 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/useScenePicking.js +31 -6
- package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js +4 -5
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js +5 -3
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
- package/dist/canvas/SceneCanvas.js +19 -2
- package/dist/canvas/SceneCanvas.js.map +1 -1
- package/dist/canvas/accentColor.js +26 -3
- package/dist/canvas/accentColor.js.map +1 -1
- package/dist/canvas/buildSceneTree.js +14 -20
- package/dist/canvas/buildSceneTree.js.map +1 -1
- package/dist/canvas/copiesAbove.js +1 -1
- package/dist/canvas/copiesAbove.js.map +1 -1
- package/dist/canvas/defaultDrawOne.js +10 -7
- package/dist/canvas/defaultDrawOne.js.map +1 -1
- package/dist/canvas/deps/areaSelect.js +5 -2
- package/dist/canvas/deps/areaSelect.js.map +1 -1
- package/dist/canvas/deps/booleans.js +1 -1
- package/dist/canvas/deps/booleans.js.map +1 -1
- package/dist/canvas/deps/enteredGroup.js +81 -0
- package/dist/canvas/deps/enteredGroup.js.map +1 -0
- package/dist/canvas/deps/hitTestArea.js +27 -15
- package/dist/canvas/deps/hitTestArea.js.map +1 -1
- package/dist/canvas/deps/insert.js +4 -36
- package/dist/canvas/deps/insert.js.map +1 -1
- package/dist/canvas/deps/insertSite.js +39 -0
- package/dist/canvas/deps/insertSite.js.map +1 -0
- package/dist/canvas/deps/lassoSelect.js +6 -3
- package/dist/canvas/deps/lassoSelect.js.map +1 -1
- package/dist/canvas/deps/pathLeaf.js +1 -1
- package/dist/canvas/deps/pathLeaf.js.map +1 -1
- package/dist/canvas/deps/slice.js +2 -1
- package/dist/canvas/deps/slice.js.map +1 -1
- package/dist/canvas/derivedPath.js +14 -6
- package/dist/canvas/derivedPath.js.map +1 -1
- package/dist/canvas/doubleClickTarget.js +9 -11
- package/dist/canvas/doubleClickTarget.js.map +1 -1
- package/dist/canvas/drawnBounds.js +16 -7
- package/dist/canvas/drawnBounds.js.map +1 -1
- package/dist/canvas/inheritedPaints.js +22 -11
- package/dist/canvas/inheritedPaints.js.map +1 -1
- package/dist/canvas/nodeShapes/nodePaint.js +1 -7
- package/dist/canvas/nodeShapes/nodePaint.js.map +1 -1
- package/dist/canvas/nodeShapes/pathPainter.js +2 -3
- package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/rectFallbackPainter.js +1 -1
- package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/shapePainter.js +2 -3
- package/dist/canvas/nodeShapes/shapePainter.js.map +1 -1
- package/dist/canvas/ownClip.js +24 -0
- package/dist/canvas/ownClip.js.map +1 -0
- package/dist/canvas/paintCull.js.map +1 -1
- package/dist/canvas/pickIndex/nodeBroadBox.js +86 -0
- package/dist/canvas/pickIndex/nodeBroadBox.js.map +1 -0
- package/dist/canvas/pickIndex/scenePickIndex.js +208 -0
- package/dist/canvas/pickIndex/scenePickIndex.js.map +1 -0
- package/dist/canvas/pickWalk.js +14 -90
- package/dist/canvas/pickWalk.js.map +1 -1
- package/dist/canvas/poseCascade.js +1 -1
- package/dist/canvas/poseCascade.js.map +1 -1
- package/dist/canvas/publishPreviews.js +97 -0
- package/dist/canvas/publishPreviews.js.map +1 -0
- package/dist/canvas/sceneAdapter.js +7 -5
- package/dist/canvas/sceneAdapter.js.map +1 -1
- package/dist/canvas/sceneClipboard.js +17 -10
- package/dist/canvas/sceneClipboard.js.map +1 -1
- package/dist/canvas/scenePickReads.js +37 -0
- package/dist/canvas/scenePickReads.js.map +1 -0
- package/dist/canvas/scenePickSource.js +74 -0
- package/dist/canvas/scenePickSource.js.map +1 -0
- package/dist/canvas/sceneSlotWiring.js +9 -4
- package/dist/canvas/sceneSlotWiring.js.map +1 -1
- package/dist/canvas/sceneViewRender.js +9 -5
- package/dist/canvas/sceneViewRender.js.map +1 -1
- package/dist/canvas/shownNodes.js +15 -0
- package/dist/canvas/shownNodes.js.map +1 -0
- package/dist/canvas/useSceneAdapter.js +5 -3
- package/dist/canvas/useSceneAdapter.js.map +1 -1
- package/dist/clipboard.d.ts +2 -2
- package/dist/{clone-C3e0OTuF.d.ts → clone-qb2e1F5p.d.ts} +2 -2
- package/dist/clone.d.ts +3 -3
- package/dist/core/isPlainObject.js +2 -2
- package/dist/core/layers/layerGroup.js.map +1 -1
- package/dist/core/scene/NodeRouting.js.map +1 -1
- package/dist/core/scene/arrivalWindow.js +1 -1
- package/dist/core/scene/arrivalWindow.js.map +1 -1
- package/dist/core/scene/clipNothing.js +2 -5
- package/dist/core/scene/clipNothing.js.map +1 -1
- package/dist/core/scene/declaredFieldSetters.js +44 -0
- package/dist/core/scene/declaredFieldSetters.js.map +1 -0
- package/dist/core/scene/depMemo.js +3 -0
- package/dist/core/scene/depMemo.js.map +1 -1
- package/dist/core/scene/dependents.js +3 -1
- package/dist/core/scene/dependents.js.map +1 -1
- package/dist/core/scene/documentOf.js +43 -0
- package/dist/core/scene/documentOf.js.map +1 -0
- package/dist/core/scene/effectivePose.js +37 -13
- package/dist/core/scene/effectivePose.js.map +1 -1
- package/dist/core/scene/fieldEdits.js +17 -14
- package/dist/core/scene/fieldEdits.js.map +1 -1
- package/dist/core/scene/historyForm.js +4 -4
- package/dist/core/scene/historyForm.js.map +1 -1
- package/dist/core/scene/inheritedPaint.js +27 -5
- package/dist/core/scene/inheritedPaint.js.map +1 -1
- package/dist/core/scene/kitOps.js +3 -23
- package/dist/core/scene/kitOps.js.map +1 -1
- package/dist/core/scene/layoutPass.js +1 -2
- package/dist/core/scene/layoutPass.js.map +1 -1
- package/dist/core/scene/nodeAsShown.js +72 -0
- package/dist/core/scene/nodeAsShown.js.map +1 -0
- package/dist/core/scene/nodeFnFields.js +2 -10
- package/dist/core/scene/nodeFnFields.js.map +1 -1
- package/dist/core/scene/nodeMemo.js +6 -1
- package/dist/core/scene/nodeMemo.js.map +1 -1
- package/dist/core/scene/nodeStore.js +2 -4
- package/dist/core/scene/nodeStore.js.map +1 -1
- package/dist/core/scene/pathEffects/derive.js +3 -2
- package/dist/core/scene/pathEffects/derive.js.map +1 -1
- package/dist/core/scene/pathEffects/foundHidden.js +35 -0
- package/dist/core/scene/pathEffects/foundHidden.js.map +1 -0
- package/dist/core/scene/plyStacks.js +89 -0
- package/dist/core/scene/plyStacks.js.map +1 -0
- package/dist/core/scene/poseHold.js +42 -24
- package/dist/core/scene/poseHold.js.map +1 -1
- package/dist/core/scene/poseOverrides.js +35 -38
- package/dist/core/scene/poseOverrides.js.map +1 -1
- package/dist/core/scene/posePlies.js +59 -0
- package/dist/core/scene/posePlies.js.map +1 -0
- package/dist/core/scene/reconcileSpecs.js +6 -15
- package/dist/core/scene/reconcileSpecs.js.map +1 -1
- package/dist/core/scene/renderOrder.js +10 -4
- package/dist/core/scene/renderOrder.js.map +1 -1
- package/dist/core/scene/sameValue.js +17 -0
- package/dist/core/scene/sameValue.js.map +1 -0
- package/dist/core/scene/scene.js +3 -3
- package/dist/core/scene/scene.js.map +1 -1
- package/dist/core/scene/sceneEdits.js +8 -0
- package/dist/core/scene/sceneEdits.js.map +1 -1
- package/dist/core/scene/sceneState.js +1 -2
- package/dist/core/scene/sceneState.js.map +1 -1
- package/dist/core/scene/serialization.js +45 -9
- package/dist/core/scene/serialization.js.map +1 -1
- package/dist/core/scene/staleReplay.js +5 -2
- package/dist/core/scene/staleReplay.js.map +1 -1
- package/dist/core/scene/statedFields.js +4 -2
- package/dist/core/scene/statedFields.js.map +1 -1
- package/dist/core/scene/treeEdits.js +2 -4
- package/dist/core/scene/treeEdits.js.map +1 -1
- package/dist/core/scene/treeOrder.js +3 -1
- package/dist/core/scene/treeOrder.js.map +1 -1
- package/dist/core/scene/treeReads.js +1 -2
- package/dist/core/scene/treeReads.js.map +1 -1
- package/dist/core/scene/treeShape.js +13 -0
- package/dist/core/scene/treeShape.js.map +1 -0
- package/dist/core/scene/types.js.map +1 -1
- package/dist/core/scene/useScene.js +1 -1
- package/dist/core/scene/useScene.js.map +1 -1
- package/dist/{cornerHandles-p8fDIAKt.d.ts → cornerHandles-CNCb7aTY.d.ts} +1 -1
- package/dist/{depSchema-mG19aEdD.d.ts → depSchema-B1RS-IJx.d.ts} +27 -54
- package/dist/devtools/handlers.js +1 -1
- package/dist/devtools/handlers.js.map +1 -1
- package/dist/devtools/protocol.js.map +1 -1
- package/dist/devtools-protocol.d.ts +4 -3
- package/dist/devtools.d.ts +3 -2
- package/dist/features/measure/measureHit.js +5 -3
- package/dist/features/measure/measureHit.js.map +1 -1
- package/dist/features/measure/measurement.js +1 -1
- package/dist/features/measure/measurement.js.map +1 -1
- package/dist/features/modes/modeLayers.js +14 -7
- package/dist/features/modes/modeLayers.js.map +1 -1
- package/dist/features/modes/modeTriggers.js +3 -3
- package/dist/features/modes/modeTriggers.js.map +1 -1
- package/dist/features/pathEffects/effectSources.js +25 -16
- package/dist/features/pathEffects/effectSources.js.map +1 -1
- package/dist/features/pathEffects/readSource.js +1 -1
- package/dist/features/pathEffects/readSource.js.map +1 -1
- package/dist/features/pathEffects/useEffectSourcesLayer.js +15 -19
- package/dist/features/pathEffects/useEffectSourcesLayer.js.map +1 -1
- package/dist/features/paths/editablePath.js +1 -1
- package/dist/features/paths/editablePath.js.map +1 -1
- package/dist/features/paths/pathSnap/sceneIndex.js +1 -1
- package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
- package/dist/features/selection/enteredGroupFrame.js +65 -0
- package/dist/features/selection/enteredGroupFrame.js.map +1 -0
- package/dist/features/selection/enteredGroupScope.js +44 -0
- package/dist/features/selection/enteredGroupScope.js.map +1 -0
- package/dist/features/text/sceneTextDrafts.js +26 -27
- package/dist/features/text/sceneTextDrafts.js.map +1 -1
- package/dist/features/text/textHitAt.js +4 -2
- package/dist/features/text/textHitAt.js.map +1 -1
- package/dist/features/text/useSceneTextEdit.js +3 -3
- package/dist/features/text/useSceneTextEdit.js.map +1 -1
- package/dist/index.d.ts +225 -73
- package/dist/index.js +15 -5
- package/dist/interactions/actions/admitsChild.js +11 -0
- package/dist/interactions/actions/admitsChild.js.map +1 -0
- package/dist/interactions/actions/defaults/contextMenu.js +16 -3
- package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
- package/dist/interactions/actions/defaults/createPaintAction.js +4 -1
- package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
- package/dist/interactions/actions/defaults/enterGroup.js +88 -0
- package/dist/interactions/actions/defaults/enterGroup.js.map +1 -0
- package/dist/interactions/actions/defaults/enterPathEdit.js +1 -1
- package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/enterTextEdit.js +1 -1
- package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/group.js +7 -7
- package/dist/interactions/actions/defaults/group.js.map +1 -1
- package/dist/interactions/actions/defaults/selectAll.js +7 -3
- package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
- package/dist/interactions/actions/defaults/selectDeep.js +35 -0
- package/dist/interactions/actions/defaults/selectDeep.js.map +1 -0
- package/dist/interactions/actions/drawnBoxes.js +2 -1
- package/dist/interactions/actions/drawnBoxes.js.map +1 -1
- package/dist/interactions/actions/insert/action.js +1 -0
- package/dist/interactions/actions/insert/action.js.map +1 -1
- package/dist/interactions/actions/insert/parent.js +7 -5
- package/dist/interactions/actions/insert/parent.js.map +1 -1
- package/dist/interactions/actions/insert/placeAction.js +3 -2
- package/dist/interactions/actions/insert/placeAction.js.map +1 -1
- package/dist/interactions/actions/insert/session.js +2 -2
- package/dist/interactions/actions/insert/session.js.map +1 -1
- package/dist/interactions/actions/move/commit/reparent.js +1 -1
- package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
- package/dist/interactions/actions/move/gestureAdapter.js +6 -7
- package/dist/interactions/actions/move/gestureAdapter.js.map +1 -1
- package/dist/interactions/actions/move/layout/exits.js +1 -1
- package/dist/interactions/actions/move/layout/exits.js.map +1 -1
- package/dist/interactions/actions/move/session.js +4 -4
- package/dist/interactions/actions/move/session.js.map +1 -1
- package/dist/interactions/actions/pathCommands/pathNodes.js +14 -5
- package/dist/interactions/actions/pathCommands/pathNodes.js.map +1 -1
- package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js +14 -2
- package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js.map +1 -1
- package/dist/{math-B56zxVs5.d.ts → math-C_elShP_.d.ts} +2 -5
- package/dist/math.d.ts +1 -1
- package/dist/move.d.ts +4 -4
- package/dist/{options-DBxEryYq.d.ts → options-ClEm3SEl.d.ts} +1 -1
- package/dist/resize.d.ts +4 -4
- package/dist/test-seams.d.ts +6 -6
- package/dist/tools/builtin/pressTarget.js +22 -0
- package/dist/tools/builtin/pressTarget.js.map +1 -0
- package/dist/tools/builtin/select/adapterPointQuery.js +3 -3
- package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
- package/dist/tools/builtin/select/collapseDeferred.js +40 -0
- package/dist/tools/builtin/select/collapseDeferred.js.map +1 -0
- package/dist/tools/builtin/select/groupEntry.js +82 -0
- package/dist/tools/builtin/select/groupEntry.js.map +1 -0
- package/dist/tools/builtin/select/pressDecision.js +1 -1
- package/dist/tools/builtin/select/pressDecision.js.map +1 -1
- package/dist/tools/builtin/select/useSelectTool.js +61 -45
- package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
- package/dist/{types-7puAuVD2.d.ts → types-49uYJN3j.d.ts} +1 -1
- package/dist/{types-CniVctmk.d.ts → types-BEdRIhNc.d.ts} +272 -109
- package/dist/{types-oFeQ5VjV.d.ts → types-p2alu-R1.d.ts} +1 -1
- package/dist/version.js +1 -1
- package/package.json +25 -25
|
@@ -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 !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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"}
|
|
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 { inheritedPaintIn, type InheritedPaint } from './inheritedPaint';\nimport { isDocument } from './documentOf';\nimport { nodeAsShown, shownLookup } from './nodeAsShown';\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. A\n * `documentOf` view is read as `document` by every entry point. 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?: 'mark' | 'group';\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, readingOf(source));\n}\n\n/** How `source` is read: as its document holds it when it is a `documentOf` view. */\nfunction readingOf(source: PoseSource<unknown>): Reading {\n return isDocument(source) ? 'document' : 'effective';\n}\n\nfunction derivedPoseIn<TPose>(\n source: PoseSource<TPose>,\n stored: PosedNode<TPose>,\n reading: Reading,\n): TPose | null {\n const node = shownIn(source, stored, reading);\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 const reading = readingOf(source);\n return reader === undefined ? depIn(source, id, reading) : depsAsRead(source, reader, reading)(id);\n}\n\n/** `id` as {@link derivedDepOf} answers it, but at the pose, path and data the\n * document holds: no override anywhere down its dependencies counts. The same\n * reading as `derivedDepOf(documentOf(source), …)`, for a reader that must not\n * see an in-flight gesture or an open edit, such as an edit pass. */\nexport function documentDepOf<TPose>(\n source: PoseSource<TPose>,\n id: NodeId,\n reader?: PosedNode<TPose>,\n): DerivedDep<TPose> | undefined {\n return reader === undefined ? depIn(source, id, 'document') : depsAsRead(source, reader, 'document')(id);\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 get inherited(): InheritedPaint | undefined {\n return this.dep.inherited;\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 private readonly posed: PosedNode<TPose>;\n\n constructor(\n private readonly source: PoseSource<TPose>,\n stored: PosedNode<TPose>,\n private readonly reading: Reading,\n ) {\n const posed = shownIn(source, stored, reading);\n this.posed = posed;\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 get inherited(): InheritedPaint | undefined {\n const { source, reading } = this;\n return inheritedPaintIn(reading === 'effective' ? shownLookup(source) : source, this.posed);\n }\n}\n\n/** `node` as `reading` reads it: with the data an override shows it with in the effective\n * reading, as stored in the document one. */\nfunction shownIn<TPose>(source: PoseSource<TPose>, node: PosedNode<TPose>, reading: Reading): PosedNode<TPose> {\n return reading === 'effective' ? nodeAsShown(source.overrides, node) : node;\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. The same reading as\n * `effectivePose(documentOf(source), node)`.\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, readingOf(source));\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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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sameDependsOn } from "./dependents.js";
|
|
2
|
+
import { foundHiddenRetargeted } from "./pathEffects/foundHidden.js";
|
|
2
3
|
import { resized } from "./layoutPass.js";
|
|
3
4
|
|
|
4
5
|
//#region src/core/scene/fieldEdits.ts
|
|
@@ -22,9 +23,9 @@ function createFieldEdits(env) {
|
|
|
22
23
|
setPose(id, pose) {
|
|
23
24
|
const node = requireNode(id);
|
|
24
25
|
refuse(id, "set the pose of");
|
|
25
|
-
const resizes = node.
|
|
26
|
+
const resizes = node.layout !== void 0 && resized(layoutFrame, id, node.pose, pose);
|
|
26
27
|
const parent = node.parent === null ? void 0 : requireNode(node.parent);
|
|
27
|
-
const arranger = parent?.
|
|
28
|
+
const arranger = parent?.layout !== void 0 ? parent.id : null;
|
|
28
29
|
edit("setPose", resizes || arranger !== null, () => {
|
|
29
30
|
if (resizes) noteChange(id);
|
|
30
31
|
noteChange(arranger);
|
|
@@ -69,15 +70,27 @@ function createFieldEdits(env) {
|
|
|
69
70
|
const node = requireNode(id);
|
|
70
71
|
refuse(id, "retarget");
|
|
71
72
|
if (sameDependsOn(node.dependsOn, dependsOn)) return;
|
|
72
|
-
|
|
73
|
+
const data = foundHiddenRetargeted(node.data, node.dependsOn, dependsOn);
|
|
74
|
+
if (data === null) return void record("kit:setDependsOn", {
|
|
73
75
|
id,
|
|
74
76
|
from: node.dependsOn,
|
|
75
77
|
to: dependsOn
|
|
76
78
|
}, "setDependsOn");
|
|
79
|
+
batch("setDependsOn", () => {
|
|
80
|
+
record("kit:setDependsOn", {
|
|
81
|
+
id,
|
|
82
|
+
from: node.dependsOn,
|
|
83
|
+
to: dependsOn
|
|
84
|
+
}, "setDependsOn");
|
|
85
|
+
record("kit:setData", {
|
|
86
|
+
id,
|
|
87
|
+
from: node.data,
|
|
88
|
+
to: data
|
|
89
|
+
}, "setDependsOn");
|
|
90
|
+
});
|
|
77
91
|
},
|
|
78
92
|
setLayout(id, layout) {
|
|
79
93
|
const node = requireNode(id);
|
|
80
|
-
if (node.kind !== "container") throw new Error(`Scene: "${id}" is not a container`);
|
|
81
94
|
refuse(id, "set the layout of");
|
|
82
95
|
let to;
|
|
83
96
|
if (typeof layout === "string") {
|
|
@@ -102,16 +115,6 @@ function createFieldEdits(env) {
|
|
|
102
115
|
noteChange(id);
|
|
103
116
|
record("kit:setLayout", payload, "setLayout");
|
|
104
117
|
});
|
|
105
|
-
},
|
|
106
|
-
setUnits(id, units) {
|
|
107
|
-
const node = requireNode(id);
|
|
108
|
-
if (node.kind !== "container") throw new Error(`Scene: "${id}" is not a container`);
|
|
109
|
-
refuse(id, "set the units of");
|
|
110
|
-
record("kit:setUnits", {
|
|
111
|
-
id,
|
|
112
|
-
from: node.units,
|
|
113
|
-
to: units ?? void 0
|
|
114
|
-
}, "setUnits");
|
|
115
118
|
}
|
|
116
119
|
};
|
|
117
120
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fieldEdits.js","names":[],"sources":["../../../src/core/scene/fieldEdits.ts"],"sourcesContent":["/**\n * The scene's edits to one node's fields: its data, pose, layer, dependency\n * list,
|
|
1
|
+
{"version":3,"file":"fieldEdits.js","names":[],"sources":["../../../src/core/scene/fieldEdits.ts"],"sourcesContent":["/**\n * The scene's edits to one node's fields: its data, pose, layer, dependency\n * list, and layout. Each checks its arguments, then records.\n */\nimport type { LayoutStrategy } from '../../layout/types';\nimport type { ArrivalWindow } from './arrivalWindow';\nimport { sameDependsOn } from './dependents';\nimport { foundHiddenRetargeted } from './pathEffects/foundHidden';\nimport type { SetLayoutPayload } from './kitOps';\nimport type { createLayerStack } from './layerStack';\nimport { resized, type ResolvedLayoutFrame } from './layoutPass';\nimport type { LockGuard } from './lockGuard';\nimport type { NodeFnField } from './nodeFnFields';\nimport type { createNodeStore } from './nodeStore';\nimport type { Recorder } from './recorder';\nimport type { NodeId, Scene } from './types';\n\nexport interface FieldEditsEnv<TData, TLayer extends string, TPose> {\n store: Pick<ReturnType<typeof createNodeStore<TData, TLayer, TPose>>, 'requireNode' | 'descendants'>;\n locks: Pick<LockGuard<TLayer>, 'refuse' | 'refuseLayer'>;\n layers: Pick<ReturnType<typeof createLayerStack<TLayer>>, 'requireLayerIndex' | 'assertSubtreeLayer'>;\n recorder: Pick<Recorder, 'record' | 'edit' | 'batch'>;\n arrivals: Pick<ArrivalWindow<TPose>, 'noteChange' | 'layoutDeclared'>;\n layoutFrame: ResolvedLayoutFrame<TPose>;\n /** The registry's layouts, by key and back. */\n layoutField: NodeFnField;\n /** Where a live `kit:setLayout` payload's strategies are kept. */\n layoutRefs: WeakMap<object, { from: LayoutStrategy<TPose> | null; to: LayoutStrategy<TPose> | null }>;\n}\n\nexport type FieldEdits<TData, TLayer extends string, TPose> = Pick<\n Scene<TData, TLayer, TPose>,\n 'update' | 'setPose' | 'setLayer' | 'setDependsOn' | 'setLayout'\n>;\n\nexport function createFieldEdits<TData, TLayer extends string, TPose>(\n env: FieldEditsEnv<TData, TLayer, TPose>,\n): FieldEdits<TData, TLayer, TPose> {\n const { layoutFrame, layoutField, layoutRefs } = env;\n const { requireNode, descendants } = env.store;\n const { refuse, refuseLayer } = env.locks;\n const { requireLayerIndex, assertSubtreeLayer } = env.layers;\n const { record, edit, batch } = env.recorder;\n const { noteChange, layoutDeclared } = env.arrivals;\n\n return {\n update(id, patch) {\n const node = requireNode(id);\n refuse(id, 'update');\n record('kit:setData', { id, from: node.data, to: patch.data }, 'update');\n },\n\n setPose(id, pose) {\n const node = requireNode(id);\n refuse(id, 'set the pose of');\n const resizes = node.layout !== undefined && resized(layoutFrame, id, node.pose, pose);\n // A container with a layout owns where its children go, however one is moved.\n const parent = node.parent === null ? undefined : requireNode(node.parent);\n const arranger = parent?.layout !== undefined ? parent.id : null;\n edit('setPose', resizes || arranger !== null, () => {\n if (resizes) noteChange(id);\n noteChange(arranger);\n record('kit:setPose', { id, from: node.pose, to: pose }, 'setPose');\n });\n },\n\n setLayer(id, layer) {\n requireLayerIndex(layer);\n const node = requireNode(id);\n refuse(id, 'relayer');\n if (node.layer !== layer) refuseLayer(layer, 'move nodes onto');\n if (node.parent !== null) {\n const parentNode = requireNode(node.parent);\n assertSubtreeLayer(id, layer, node.parent, parentNode.layer, 'relayer');\n }\n if (node.layer === layer) return;\n const subtree: NodeId[] = [id];\n descendants(id, subtree);\n for (const sid of subtree) refuse(sid, 'relayer');\n if (subtree.length === 1) {\n record('kit:setLayer', { id, from: node.layer, to: layer }, 'setLayer');\n } else {\n batch('setLayer', () => {\n for (const sid of subtree) {\n const cur = requireNode(sid);\n if (cur.layer === layer) continue;\n record('kit:setLayer', { id: sid, from: cur.layer, to: layer }, 'setLayer');\n }\n });\n }\n },\n\n setDependsOn(id, dependsOn) {\n const node = requireNode(id);\n refuse(id, 'retarget');\n if (sameDependsOn(node.dependsOn, dependsOn)) return;\n const data = foundHiddenRetargeted(node.data, node.dependsOn, dependsOn);\n if (data === null) return void record('kit:setDependsOn', { id, from: node.dependsOn, to: dependsOn }, 'setDependsOn');\n batch('setDependsOn', () => {\n record('kit:setDependsOn', { id, from: node.dependsOn, to: dependsOn }, 'setDependsOn');\n record('kit:setData', { id, from: node.data, to: data }, 'setDependsOn');\n });\n },\n\n setLayout(id, layout) {\n const node = requireNode(id);\n refuse(id, 'set the layout of');\n let to: LayoutStrategy<TPose> | null;\n if (typeof layout === 'string') {\n to = (layoutField.fnOf(layout) as LayoutStrategy<TPose> | undefined) ?? null;\n if (to === null) throw new Error(`Scene: no layout \"${layout}\" in this scene's registry`);\n } else {\n to = layout;\n }\n const from = node.layout ?? null;\n if (from === to) return;\n if (to !== null) layoutDeclared();\n const fromKey = from === null ? undefined : layoutField.keyOf(from);\n const toKey = to === null ? undefined : layoutField.keyOf(to);\n const payload: SetLayoutPayload = {\n id,\n ...(fromKey !== undefined ? { fromKey } : {}),\n ...(toKey !== undefined ? { toKey } : {}),\n };\n layoutRefs.set(payload, { from, to });\n edit('setLayout', true, () => {\n noteChange(id);\n record('kit:setLayout', payload, 'setLayout');\n });\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
//#region src/core/scene/historyForm.ts
|
|
2
2
|
/** The form a scene's undo entries are recorded in. A serialized history
|
|
3
|
-
* carries it, and one carrying another is not restored. Form
|
|
4
|
-
* scene's own ops for every edit
|
|
5
|
-
* ops it was given. */
|
|
6
|
-
var SCENE_HISTORY_FORM =
|
|
3
|
+
* carries it, and one carrying another is not restored. Form 3 names
|
|
4
|
+
* nodes `mark` and `group`; form 2 held the scene's own ops for every edit,
|
|
5
|
+
* and before it an `applyBatch` entry held the ops it was given. */
|
|
6
|
+
var SCENE_HISTORY_FORM = 3;
|
|
7
7
|
|
|
8
8
|
//#endregion
|
|
9
9
|
export { SCENE_HISTORY_FORM };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"historyForm.js","names":[],"sources":["../../../src/core/scene/historyForm.ts"],"sourcesContent":["import type { SerializedHistory } from '@weasel-js/history';\n\n/** The form a scene's undo entries are recorded in. A serialized history\n * carries it, and one carrying another is not restored. Form
|
|
1
|
+
{"version":3,"file":"historyForm.js","names":[],"sources":["../../../src/core/scene/historyForm.ts"],"sourcesContent":["import type { SerializedHistory } from '@weasel-js/history';\n\n/** The form a scene's undo entries are recorded in. A serialized history\n * carries it, and one carrying another is not restored. Form 3 names\n * nodes `mark` and `group`; form 2 held the scene's own ops for every edit,\n * and before it an `applyBatch` entry held the ops it was given. */\nexport const SCENE_HISTORY_FORM = 3;\n\n/** What `Scene.serializeHistory` answers with. */\nexport interface SerializedSceneHistory extends SerializedHistory {\n form: typeof SCENE_HISTORY_FORM;\n}\n"],"mappings":";;;;;AAMA,IAAa,qBAAqB"}
|
|
@@ -9,9 +9,9 @@ function inheritsPaint(node) {
|
|
|
9
9
|
}
|
|
10
10
|
var LAST = /* @__PURE__ */ new WeakMap();
|
|
11
11
|
/**
|
|
12
|
-
* {@link InheritedPaint} for `node`, given its ancestors innermost first.
|
|
13
|
-
*
|
|
14
|
-
* question on to the next, and running out of
|
|
12
|
+
* {@link InheritedPaint} for `node`, given its ancestors innermost first. Any
|
|
13
|
+
* ancestor is a source, mark or group; one whose own field is `'inherit'`
|
|
14
|
+
* passes the question on to the next, and running out of ancestors is none.
|
|
15
15
|
*
|
|
16
16
|
* Hands back the object it returned last time for `node` while both values
|
|
17
17
|
* are the same references, so a memo can key on it.
|
|
@@ -24,7 +24,6 @@ function inheritedPaintOf(node, ancestors) {
|
|
|
24
24
|
let stroke = null;
|
|
25
25
|
for (const a of ancestors) {
|
|
26
26
|
if (!wantFill && !wantStroke) break;
|
|
27
|
-
if (a.kind !== "container") continue;
|
|
28
27
|
const ad = a.data;
|
|
29
28
|
if (wantFill && ad?.fill !== INHERIT) {
|
|
30
29
|
fill = resolveFill(ad?.fill);
|
|
@@ -44,6 +43,29 @@ function inheritedPaintOf(node, ancestors) {
|
|
|
44
43
|
LAST.set(node, next);
|
|
45
44
|
return next;
|
|
46
45
|
}
|
|
46
|
+
/** What `node`'s `'inherit'` fields take from its containers in `scene`, or
|
|
47
|
+
* `undefined` for a node that inherits nothing. */
|
|
48
|
+
function inheritedPaintIn(scene, node) {
|
|
49
|
+
if (!inheritsPaint(node)) return void 0;
|
|
50
|
+
return inheritedPaintOf(node, sceneAncestors((id) => scene.get(id), node));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The descendants of `id` whose `key` is `'inherit'` all the way up to it:
|
|
54
|
+
* those that paint whatever `id`'s own `key` holds, so a preview of that paint
|
|
55
|
+
* reaches them too. Parents before children.
|
|
56
|
+
*/
|
|
57
|
+
function paintInheritors(scene, id, key) {
|
|
58
|
+
const out = [];
|
|
59
|
+
const walk = (parent) => {
|
|
60
|
+
for (const child of scene.childrenOf(parent)) {
|
|
61
|
+
if ((scene.get(child)?.data)?.[key] !== INHERIT) continue;
|
|
62
|
+
out.push(child);
|
|
63
|
+
walk(child);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
walk(id);
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
47
69
|
/** `node`'s ancestors in `scene`, innermost first, read lazily off `parent`. */
|
|
48
70
|
function* sceneAncestors(get, node) {
|
|
49
71
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -71,5 +93,5 @@ function withInheritedPaint(data, inherited) {
|
|
|
71
93
|
}
|
|
72
94
|
|
|
73
95
|
//#endregion
|
|
74
|
-
export { inheritedPaintOf, inheritsPaint, sceneAncestors, withInheritedPaint };
|
|
96
|
+
export { inheritedPaintIn, inheritedPaintOf, inheritsPaint, paintInheritors, sceneAncestors, withInheritedPaint };
|
|
75
97
|
//# sourceMappingURL=inheritedPaint.js.map
|
|
@@ -1 +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.
|
|
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. Any\n * ancestor is a source, mark or group; one whose own field is `'inherit'`\n * passes the question on to the next, and running out of ancestors 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 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/** What `node`'s `'inherit'` fields take from its containers in `scene`, or\n * `undefined` for a node that inherits nothing. */\nexport function inheritedPaintIn<TNode extends { data?: unknown; parent?: string | null }>(\n scene: { get(id: never): TNode | undefined },\n node: TNode,\n): InheritedPaint | undefined {\n if (!inheritsPaint(node)) return undefined;\n return inheritedPaintOf(node, sceneAncestors((id: never) => scene.get(id), node));\n}\n\n/**\n * The descendants of `id` whose `key` is `'inherit'` all the way up to it:\n * those that paint whatever `id`'s own `key` holds, so a preview of that paint\n * reaches them too. Parents before children.\n */\nexport function paintInheritors(\n scene: { get(id: never): { data?: unknown } | undefined; childrenOf(id: never): readonly string[] },\n id: string,\n key: 'fill' | 'stroke',\n): string[] {\n const out: string[] = [];\n const walk = (parent: string): void => {\n for (const child of scene.childrenOf(parent as never)) {\n if ((scene.get(child as never)?.data as PaintData)?.[key] !== INHERIT) continue;\n out.push(child);\n walk(child);\n }\n };\n walk(id);\n return out;\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,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;;;AAIA,SAAgB,iBACd,OACA,MAC4B;CAC5B,IAAI,CAAC,cAAc,IAAI,GAAG,OAAO;CACjC,OAAO,iBAAiB,MAAM,gBAAgB,OAAc,MAAM,IAAI,EAAE,GAAG,IAAI,CAAC;AAClF;;;;;;AAOA,SAAgB,gBACd,OACA,IACA,KACU;CACV,MAAM,MAAgB,CAAC;CACvB,MAAM,QAAQ,WAAyB;EACrC,KAAK,MAAM,SAAS,MAAM,WAAW,MAAe,GAAG;GACrD,KAAK,MAAM,IAAI,KAAc,CAAC,EAAE,KAAA,GAAqB,SAAS,SAAS;GACvE,IAAI,KAAK,KAAK;GACd,KAAK,KAAK;EACZ;CACF;CACA,KAAK,EAAE;CACP,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"}
|
|
@@ -7,10 +7,6 @@ function setParallax(record, to) {
|
|
|
7
7
|
if (to === void 0) delete record.parallax;
|
|
8
8
|
else record.parallax = to;
|
|
9
9
|
}
|
|
10
|
-
function setUnits(node, to) {
|
|
11
|
-
if (to === void 0) delete node.units;
|
|
12
|
-
else node.units = to;
|
|
13
|
-
}
|
|
14
10
|
/** Register every kit op kind. Answers with the table a live `kit:setLayout`
|
|
15
11
|
* payload's strategies are kept in, which `scene.setLayout` fills. */
|
|
16
12
|
function registerKitOps(env) {
|
|
@@ -22,16 +18,8 @@ function registerKitOps(env) {
|
|
|
22
18
|
registerKitOp("kit:add", {
|
|
23
19
|
apply: (p) => {
|
|
24
20
|
if (state.nodes.has(p.id)) throw new Error(`Scene: id collision on "${p.id}"`);
|
|
25
|
-
const node =
|
|
26
|
-
kind:
|
|
27
|
-
id: p.id,
|
|
28
|
-
layer: p.layer,
|
|
29
|
-
pose: p.pose,
|
|
30
|
-
data: p.data,
|
|
31
|
-
parent: p.parent,
|
|
32
|
-
children: []
|
|
33
|
-
} : {
|
|
34
|
-
kind: "leaf",
|
|
21
|
+
const node = {
|
|
22
|
+
kind: p.kind,
|
|
35
23
|
id: p.id,
|
|
36
24
|
layer: p.layer,
|
|
37
25
|
pose: p.pose,
|
|
@@ -82,7 +70,7 @@ function registerKitOps(env) {
|
|
|
82
70
|
revert: (p) => {
|
|
83
71
|
const keys = new Map((p.fnKeys ?? []).map((k) => [k.id, k]));
|
|
84
72
|
for (const n of p.nodes) {
|
|
85
|
-
const clone = n.
|
|
73
|
+
const clone = n.children ? {
|
|
86
74
|
...n,
|
|
87
75
|
children: [...n.children]
|
|
88
76
|
} : { ...n };
|
|
@@ -136,14 +124,6 @@ function registerKitOps(env) {
|
|
|
136
124
|
retarget(p.id, p.from);
|
|
137
125
|
}
|
|
138
126
|
});
|
|
139
|
-
registerKitOp("kit:setUnits", {
|
|
140
|
-
apply: (p) => {
|
|
141
|
-
setUnits(requireNode(p.id), p.to);
|
|
142
|
-
},
|
|
143
|
-
revert: (p) => {
|
|
144
|
-
setUnits(requireNode(p.id), p.from);
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
127
|
/** The strategies each live `kit:setLayout` payload swaps between. A payload
|
|
148
128
|
* replayed from a persisted history has none, and resolves its keys. */
|
|
149
129
|
const layoutRefs = /* @__PURE__ */ new WeakMap();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kitOps.js","names":[],"sources":["../../../src/core/scene/kitOps.ts"],"sourcesContent":["/**\n * The kit's own op kinds: `kit:add`, `kit:remove`, `kit:setPose`, and the\n * rest. They are registered like any other op; the scene's mutation methods\n * build serializable payloads and route through the same log and replay\n * machinery a consumer's ops do.\n */\nimport type { UnitScope } from '@weasel-js/quantity';\nimport type { ParallaxOpts } from 'core/viewport/parallax';\nimport { dwarn } from 'debug/flag';\nimport type { LayoutStrategy } from '../../layout/types';\nimport { restoreFn, type FnBearingNode, type NodeFnField } from './nodeFnFields';\nimport { statedFields } from './statedFields';\nimport type { createNodeStore } from './nodeStore';\nimport type {\n ContainerNode, LayerRecord, LeafNode, Node, NodeId, RegisteredOp, UserLayerRecord,\n} from './types';\n\n/**\n * Snapshot of every node a removal deletes, so revert restores it exactly.\n *\n * A cascade unlinks several disjoint subtrees, not one, so `detached` names\n * each node the tree has to re-link; everything else in `nodes` has a parent\n * that is also going and rides back in that parent's cloned `children`.\n * Entries are sorted by ascending sibling index — globally rather than\n * per-parent, because only same-parent order constrains re-insertion and\n * entries under different parents never interact.\n */\nexport interface RemoveSnapshot<TData, TLayer extends string, TPose> {\n nodes: Node<TData, TLayer, TPose>[];\n detached: { id: NodeId; parent: NodeId | null; index: number }[];\n /** Registry keys for the function-valued fields on `nodes`. The functions\n * themselves do not survive a persisted history, so revert re-resolves\n * them the way `kit:add` does. */\n fnKeys?: ({ id: NodeId } & Record<string, string | undefined>)[];\n}\n\n/** What a `kit:setLayout` op carries: the container, and each side's layout\n * by registry key when it has one. */\nexport interface SetLayoutPayload { id: NodeId; fromKey?: string; toKey?: string }\n\n/** What the op table needs from the scene it mutates. */\nexport interface KitOpsEnv<TData, TLayer extends string, TPose> {\n state: {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n layers: LayerRecord<TLayer>[];\n };\n register(kind: string, handler: RegisteredOp<unknown>): void;\n store: ReturnType<typeof createNodeStore<TData, TLayer, TPose>>;\n requireLayerIndex(layer: TLayer): number;\n rebuildLayerIndex(): void;\n invalidateOrder(): void;\n fnFields: readonly NodeFnField[];\n /** Drop from the selection whatever a lock or a hide just put out of\n * reach, and the ids in `gone`. */\n narrowSelection(gone?: ReadonlySet<NodeId>): void;\n /** Drop the pose override of a node that is about to leave the scene: ids\n * are reusable, and one left behind would attach itself to whatever is\n * added under that id next. */\n clearOverride(id: NodeId): void;\n}\n\nfunction setParallax(record: { parallax?: ParallaxOpts }, to: ParallaxOpts | undefined): void {\n if (to === undefined) delete record.parallax;\n else record.parallax = to;\n}\n\nfunction setUnits(node: { units?: UnitScope }, to: UnitScope | undefined): void {\n if (to === undefined) delete node.units;\n else node.units = to;\n}\n\n/** Register every kit op kind. Answers with the table a live `kit:setLayout`\n * payload's strategies are kept in, which `scene.setLayout` fills. */\nexport function registerKitOps<TData, TLayer extends string, TPose>(\n env: KitOpsEnv<TData, TLayer, TPose>,\n): {\n layoutRefs: WeakMap<object, { from: LayoutStrategy<TPose> | null; to: LayoutStrategy<TPose> | null }>;\n} {\n const { state, requireLayerIndex, rebuildLayerIndex, invalidateOrder, fnFields } = env;\n const {\n dependents, childDerived, invalidateDependents, invalidateChildDerivedFrom, trackChildDerived, retarget,\n requireNode, detach, attach, addedFns, restoreNodeFunctions,\n } = env.store;\n\n function registerKitOp<P>(kind: string, handler: RegisteredOp<P>): void {\n env.register(kind, handler as RegisteredOp<unknown>);\n }\n\n registerKitOp<{\n id: NodeId; kind: 'leaf' | 'container'; layer: TLayer; pose: TPose; data: TData;\n parent: NodeId | null; index: number;\n dependsOn?: readonly NodeId[] | 'children';\n pickable?: boolean; hidden?: boolean; locked?: boolean;\n units?: UnitScope;\n } & Record<string, unknown>>('kit:add', {\n apply: (p) => {\n if (state.nodes.has(p.id)) {\n throw new Error(`Scene: id collision on \"${p.id}\"`);\n }\n const node: Node<TData, TLayer, TPose> = p.kind === 'container'\n ? ({ kind: 'container', id: p.id, layer: p.layer, pose: p.pose, data: p.data, parent: p.parent, children: [] } as ContainerNode<TData, TLayer, TPose>)\n : ({ kind: 'leaf', id: p.id, layer: p.layer, pose: p.pose, data: p.data, parent: p.parent } as LeafNode<TData, TLayer, TPose>);\n state.nodes.set(p.id, node);\n attach(p.id, p.parent, p.index);\n if (p.dependsOn !== undefined) node.dependsOn = p.dependsOn;\n Object.assign(node, statedFields(p));\n dependents.add(p.id, p.dependsOn === 'children' ? [] : p.dependsOn ?? []);\n trackChildDerived(p.id, p.dependsOn);\n // Two ways a function field comes back. Redo of a live add reads\n // what `scene.add` attached, since it replays the op without a spec.\n // A restored history has only the registry key the payload carries.\n const target = node as unknown as FnBearingNode;\n const attached = addedFns.get(p);\n for (const field of fnFields) {\n const fn = attached?.get(field);\n if (fn !== undefined) {\n field.write(target, fn);\n continue;\n }\n const key = p[field.spec.opKey] as string | undefined;\n if (key !== undefined) restoreFn(field, target, key, 'kit:add', dwarn);\n }\n invalidateDependents(p.id);\n },\n revert: (p) => {\n env.clearOverride(p.id);\n detach(p.id);\n state.nodes.delete(p.id);\n dependents.remove(p.id);\n childDerived.delete(p.id);\n invalidateChildDerivedFrom(p.parent);\n invalidateDependents(p.id);\n },\n });\n\n // Neither half invalidates dependents: removal's closure takes every live one.\n registerKitOp<RemoveSnapshot<TData, TLayer, TPose>>('kit:remove', {\n apply: (p) => {\n for (const n of p.nodes) env.clearOverride(n.id);\n for (const d of p.detached) detach(d.id);\n for (const n of p.nodes) {\n state.nodes.delete(n.id);\n dependents.remove(n.id);\n childDerived.delete(n.id);\n }\n // A `dependsOn: 'children'` container the removal did not take is now\n // hugging a smaller set.\n for (const d of p.detached) invalidateChildDerivedFrom(d.parent);\n env.narrowSelection(new Set(p.nodes.map((n) => n.id)));\n },\n revert: (p) => {\n const keys = new Map((p.fnKeys ?? []).map((k) => [k.id, k]));\n // Order-independent: every clone carries its own parent and children.\n for (const n of p.nodes) {\n const clone: Node<TData, TLayer, TPose> = n.kind === 'container'\n ? { ...n, children: [...n.children] }\n : { ...n };\n restoreNodeFunctions(clone, keys.get(n.id));\n state.nodes.set(n.id, clone);\n dependents.add(n.id, clone.dependsOn === 'children' ? [] : clone.dependsOn ?? []);\n trackChildDerived(n.id, clone.dependsOn);\n }\n for (const d of p.detached) attach(d.id, d.parent, d.index);\n for (const d of p.detached) invalidateChildDerivedFrom(d.parent);\n },\n });\n\n registerKitOp<{ id: NodeId; from: TPose; to: TPose }>('kit:setPose', {\n apply: (p) => {\n (requireNode(p.id) as { pose: TPose }).pose = p.to;\n invalidateDependents(p.id);\n },\n revert: (p) => {\n (requireNode(p.id) as { pose: TPose }).pose = p.from;\n invalidateDependents(p.id);\n },\n });\n\n // A derivation is handed its dependencies' nodes, not only their poses, so a\n // connector reading `data` or `layer` goes stale on these two exactly as it\n // does on a move.\n registerKitOp<{ id: NodeId; from: TData; to: TData }>('kit:setData', {\n apply: (p) => {\n (requireNode(p.id) as { data: TData }).data = p.to;\n invalidateDependents(p.id);\n },\n revert: (p) => {\n (requireNode(p.id) as { data: TData }).data = p.from;\n invalidateDependents(p.id);\n },\n });\n\n registerKitOp<{ id: NodeId; from: TLayer; to: TLayer }>('kit:setLayer', {\n apply: (p) => {\n requireLayerIndex(p.to);\n (requireNode(p.id) as { layer: TLayer }).layer = p.to;\n invalidateOrder();\n invalidateDependents(p.id);\n },\n revert: (p) => {\n (requireNode(p.id) as { layer: TLayer }).layer = p.from;\n invalidateOrder();\n invalidateDependents(p.id);\n },\n });\n\n registerKitOp<{\n id: NodeId;\n from: Node<TData, TLayer, TPose>['dependsOn'];\n to: Node<TData, TLayer, TPose>['dependsOn'];\n }>('kit:setDependsOn', {\n apply: (p) => { retarget(p.id, p.to); },\n revert: (p) => { retarget(p.id, p.from); },\n });\n\n registerKitOp<{ id: NodeId; from?: UnitScope; to?: UnitScope }>('kit:setUnits', {\n apply: (p) => { setUnits(requireNode(p.id) as { units?: UnitScope }, p.to); },\n revert: (p) => { setUnits(requireNode(p.id) as { units?: UnitScope }, p.from); },\n });\n\n /** The strategies each live `kit:setLayout` payload swaps between. A payload\n * replayed from a persisted history has none, and resolves its keys. */\n const layoutRefs = new WeakMap<object, { from: LayoutStrategy<TPose> | null; to: LayoutStrategy<TPose> | null }>();\n function placeLayout(p: SetLayoutPayload, side: 'from' | 'to'): void {\n const node = requireNode(p.id) as unknown as FnBearingNode;\n const field = fnFields.find((f) => f.spec.field === 'layout')!;\n const live = layoutRefs.get(p);\n const key = side === 'from' ? p.fromKey : p.toKey;\n if (live === undefined && key !== undefined) {\n delete node.layout;\n restoreFn(field, node, key, 'kit:setLayout', dwarn);\n return;\n }\n const layout = live?.[side] ?? null;\n if (layout === null) delete node.layout;\n else field.write(node, layout);\n }\n registerKitOp<SetLayoutPayload>('kit:setLayout', {\n apply: (p) => { placeLayout(p, 'to'); },\n revert: (p) => { placeLayout(p, 'from'); },\n });\n\n registerKitOp<{\n id: NodeId; fromParent: NodeId | null; fromIndex: number;\n toParent: NodeId | null; toIndex: number;\n }>('kit:move', {\n apply: (p) => {\n detach(p.id);\n attach(p.id, p.toParent, p.toIndex);\n // Both ends: the container it left no longer hugs it, and the one it\n // joined now does.\n invalidateChildDerivedFrom(p.fromParent);\n invalidateDependents(p.id);\n },\n revert: (p) => {\n detach(p.id);\n attach(p.id, p.fromParent, p.fromIndex);\n invalidateChildDerivedFrom(p.toParent);\n invalidateDependents(p.id);\n },\n });\n\n registerKitOp<{ layer: TLayer; from: boolean; to: boolean }>('kit:setLayerVisible', {\n apply: (p) => { state.layers[requireLayerIndex(p.layer)].visible = p.to; if (!p.to) env.narrowSelection(); },\n revert: (p) => { state.layers[requireLayerIndex(p.layer)].visible = p.from; },\n });\n\n registerKitOp<{ layer: TLayer; from: boolean; to: boolean }>('kit:setLayerLocked', {\n apply: (p) => { state.layers[requireLayerIndex(p.layer)].locked = p.to; if (p.to) env.narrowSelection(); },\n revert: (p) => { state.layers[requireLayerIndex(p.layer)].locked = p.from; },\n });\n\n registerKitOp<{ layer: TLayer; from?: ParallaxOpts; to?: ParallaxOpts }>('kit:setLayerParallax', {\n apply: (p) => { setParallax(state.layers[requireLayerIndex(p.layer)], p.to); },\n revert: (p) => { setParallax(state.layers[requireLayerIndex(p.layer)], p.from); },\n });\n\n registerKitOp<{ record: UserLayerRecord<TLayer>; index: number }>('kit:addLayer', {\n apply: (p) => { state.layers.splice(p.index, 0, { ...p.record }); rebuildLayerIndex(); },\n revert: (p) => { state.layers.splice(p.index, 1); rebuildLayerIndex(); },\n });\n\n registerKitOp<{ record: LayerRecord<TLayer>; index: number }>('kit:removeLayer', {\n apply: (p) => { state.layers.splice(p.index, 1); rebuildLayerIndex(); },\n revert: (p) => { state.layers.splice(p.index, 0, { ...p.record } as LayerRecord<TLayer>); rebuildLayerIndex(); },\n });\n\n registerKitOp<{ layer: TLayer; from: string; to: string }>('kit:renameLayer', {\n apply: (p) => { (state.layers[requireLayerIndex(p.layer)] as UserLayerRecord<TLayer>).name = p.to; },\n revert: (p) => { (state.layers[requireLayerIndex(p.layer)] as UserLayerRecord<TLayer>).name = p.from; },\n });\n\n /** Finds the layer by id, not by where the payload last left it: moves of\n * one layer coalesce, and the entry then reverts the first payload from\n * wherever the last one put the layer. */\n function placeLayer(layer: TLayer, index: number): void {\n const [rec] = state.layers.splice(requireLayerIndex(layer), 1);\n state.layers.splice(index, 0, rec);\n rebuildLayerIndex();\n }\n\n registerKitOp<{ layer: TLayer; from: number; to: number }>('kit:moveLayer', {\n apply: (p) => placeLayer(p.layer, p.to),\n revert: (p) => placeLayer(p.layer, p.from),\n });\n\n return { layoutRefs };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"kitOps.js","names":[],"sources":["../../../src/core/scene/kitOps.ts"],"sourcesContent":["/**\n * The kit's own op kinds: `kit:add`, `kit:remove`, `kit:setPose`, and the\n * rest. They are registered like any other op; the scene's mutation methods\n * build serializable payloads and route through the same log and replay\n * machinery a consumer's ops do.\n */\nimport type { UnitScope } from '@weasel-js/quantity';\nimport type { ParallaxOpts } from 'core/viewport/parallax';\nimport { dwarn } from 'debug/flag';\nimport type { LayoutStrategy } from '../../layout/types';\nimport { restoreFn, type FnBearingNode, type NodeFnField } from './nodeFnFields';\nimport { statedFields } from './statedFields';\nimport type { createNodeStore } from './nodeStore';\nimport type {\n LayerRecord, Node, NodeClip, NodeId, RegisteredOp, UserLayerRecord,\n} from './types';\n\n/**\n * Snapshot of every node a removal deletes, so revert restores it exactly.\n *\n * A cascade unlinks several disjoint subtrees, not one, so `detached` names\n * each node the tree has to re-link; everything else in `nodes` has a parent\n * that is also going and rides back in that parent's cloned `children`.\n * Entries are sorted by ascending sibling index — globally rather than\n * per-parent, because only same-parent order constrains re-insertion and\n * entries under different parents never interact.\n */\nexport interface RemoveSnapshot<TData, TLayer extends string, TPose> {\n nodes: Node<TData, TLayer, TPose>[];\n detached: { id: NodeId; parent: NodeId | null; index: number }[];\n /** Registry keys for the function-valued fields on `nodes`. The functions\n * themselves do not survive a persisted history, so revert re-resolves\n * them the way `kit:add` does. */\n fnKeys?: ({ id: NodeId } & Record<string, string | undefined>)[];\n}\n\n/** What a `kit:setLayout` op carries: the container, and each side's layout\n * by registry key when it has one. */\nexport interface SetLayoutPayload { id: NodeId; fromKey?: string; toKey?: string }\n\n/** What the op table needs from the scene it mutates. */\nexport interface KitOpsEnv<TData, TLayer extends string, TPose> {\n state: {\n nodes: Map<NodeId, Node<TData, TLayer, TPose>>;\n layers: LayerRecord<TLayer>[];\n };\n register(kind: string, handler: RegisteredOp<unknown>): void;\n store: ReturnType<typeof createNodeStore<TData, TLayer, TPose>>;\n requireLayerIndex(layer: TLayer): number;\n rebuildLayerIndex(): void;\n invalidateOrder(): void;\n fnFields: readonly NodeFnField[];\n /** Drop from the selection whatever a lock or a hide just put out of\n * reach, and the ids in `gone`. */\n narrowSelection(gone?: ReadonlySet<NodeId>): void;\n /** Drop the pose override of a node that is about to leave the scene: ids\n * are reusable, and one left behind would attach itself to whatever is\n * added under that id next. */\n clearOverride(id: NodeId): void;\n}\n\nfunction setParallax(record: { parallax?: ParallaxOpts }, to: ParallaxOpts | undefined): void {\n if (to === undefined) delete record.parallax;\n else record.parallax = to;\n}\n\n/** Register every kit op kind. Answers with the table a live `kit:setLayout`\n * payload's strategies are kept in, which `scene.setLayout` fills. */\nexport function registerKitOps<TData, TLayer extends string, TPose>(\n env: KitOpsEnv<TData, TLayer, TPose>,\n): {\n layoutRefs: WeakMap<object, { from: LayoutStrategy<TPose> | null; to: LayoutStrategy<TPose> | null }>;\n} {\n const { state, requireLayerIndex, rebuildLayerIndex, invalidateOrder, fnFields } = env;\n const {\n dependents, childDerived, invalidateDependents, invalidateChildDerivedFrom, trackChildDerived, retarget,\n requireNode, detach, attach, addedFns, restoreNodeFunctions,\n } = env.store;\n\n function registerKitOp<P>(kind: string, handler: RegisteredOp<P>): void {\n env.register(kind, handler as RegisteredOp<unknown>);\n }\n\n registerKitOp<{\n id: NodeId; kind: 'mark' | 'group'; layer: TLayer; pose: TPose; data: TData;\n parent: NodeId | null; index: number;\n dependsOn?: readonly NodeId[] | 'children';\n pickable?: boolean; hidden?: boolean; locked?: boolean;\n units?: UnitScope; clip?: NodeClip;\n } & Record<string, unknown>>('kit:add', {\n apply: (p) => {\n if (state.nodes.has(p.id)) {\n throw new Error(`Scene: id collision on \"${p.id}\"`);\n }\n const node = {\n kind: p.kind, id: p.id, layer: p.layer, pose: p.pose, data: p.data, parent: p.parent,\n } as Node<TData, TLayer, TPose>;\n state.nodes.set(p.id, node);\n attach(p.id, p.parent, p.index);\n if (p.dependsOn !== undefined) node.dependsOn = p.dependsOn;\n Object.assign(node, statedFields(p));\n dependents.add(p.id, p.dependsOn === 'children' ? [] : p.dependsOn ?? []);\n trackChildDerived(p.id, p.dependsOn);\n // Two ways a function field comes back. Redo of a live add reads\n // what `scene.add` attached, since it replays the op without a spec.\n // A restored history has only the registry key the payload carries.\n const target = node as unknown as FnBearingNode;\n const attached = addedFns.get(p);\n for (const field of fnFields) {\n const fn = attached?.get(field);\n if (fn !== undefined) {\n field.write(target, fn);\n continue;\n }\n const key = p[field.spec.opKey] as string | undefined;\n if (key !== undefined) restoreFn(field, target, key, 'kit:add', dwarn);\n }\n invalidateDependents(p.id);\n },\n revert: (p) => {\n env.clearOverride(p.id);\n detach(p.id);\n state.nodes.delete(p.id);\n dependents.remove(p.id);\n childDerived.delete(p.id);\n invalidateChildDerivedFrom(p.parent);\n invalidateDependents(p.id);\n },\n });\n\n // Neither half invalidates dependents: removal's closure takes every live one.\n registerKitOp<RemoveSnapshot<TData, TLayer, TPose>>('kit:remove', {\n apply: (p) => {\n for (const n of p.nodes) env.clearOverride(n.id);\n for (const d of p.detached) detach(d.id);\n for (const n of p.nodes) {\n state.nodes.delete(n.id);\n dependents.remove(n.id);\n childDerived.delete(n.id);\n }\n // A `dependsOn: 'children'` container the removal did not take is now\n // hugging a smaller set.\n for (const d of p.detached) invalidateChildDerivedFrom(d.parent);\n env.narrowSelection(new Set(p.nodes.map((n) => n.id)));\n },\n revert: (p) => {\n const keys = new Map((p.fnKeys ?? []).map((k) => [k.id, k]));\n // Order-independent: every clone carries its own parent and children.\n for (const n of p.nodes) {\n const clone: Node<TData, TLayer, TPose> = n.children\n ? { ...n, children: [...n.children] }\n : { ...n };\n restoreNodeFunctions(clone, keys.get(n.id));\n state.nodes.set(n.id, clone);\n dependents.add(n.id, clone.dependsOn === 'children' ? [] : clone.dependsOn ?? []);\n trackChildDerived(n.id, clone.dependsOn);\n }\n for (const d of p.detached) attach(d.id, d.parent, d.index);\n for (const d of p.detached) invalidateChildDerivedFrom(d.parent);\n },\n });\n\n registerKitOp<{ id: NodeId; from: TPose; to: TPose }>('kit:setPose', {\n apply: (p) => {\n (requireNode(p.id) as { pose: TPose }).pose = p.to;\n invalidateDependents(p.id);\n },\n revert: (p) => {\n (requireNode(p.id) as { pose: TPose }).pose = p.from;\n invalidateDependents(p.id);\n },\n });\n\n // A derivation is handed its dependencies' nodes, not only their poses, so a\n // connector reading `data` or `layer` goes stale on these two exactly as it\n // does on a move.\n registerKitOp<{ id: NodeId; from: TData; to: TData }>('kit:setData', {\n apply: (p) => {\n (requireNode(p.id) as { data: TData }).data = p.to;\n invalidateDependents(p.id);\n },\n revert: (p) => {\n (requireNode(p.id) as { data: TData }).data = p.from;\n invalidateDependents(p.id);\n },\n });\n\n registerKitOp<{ id: NodeId; from: TLayer; to: TLayer }>('kit:setLayer', {\n apply: (p) => {\n requireLayerIndex(p.to);\n (requireNode(p.id) as { layer: TLayer }).layer = p.to;\n invalidateOrder();\n invalidateDependents(p.id);\n },\n revert: (p) => {\n (requireNode(p.id) as { layer: TLayer }).layer = p.from;\n invalidateOrder();\n invalidateDependents(p.id);\n },\n });\n\n registerKitOp<{\n id: NodeId;\n from: Node<TData, TLayer, TPose>['dependsOn'];\n to: Node<TData, TLayer, TPose>['dependsOn'];\n }>('kit:setDependsOn', {\n apply: (p) => { retarget(p.id, p.to); },\n revert: (p) => { retarget(p.id, p.from); },\n });\n\n /** The strategies each live `kit:setLayout` payload swaps between. A payload\n * replayed from a persisted history has none, and resolves its keys. */\n const layoutRefs = new WeakMap<object, { from: LayoutStrategy<TPose> | null; to: LayoutStrategy<TPose> | null }>();\n function placeLayout(p: SetLayoutPayload, side: 'from' | 'to'): void {\n const node = requireNode(p.id) as unknown as FnBearingNode;\n const field = fnFields.find((f) => f.spec.field === 'layout')!;\n const live = layoutRefs.get(p);\n const key = side === 'from' ? p.fromKey : p.toKey;\n if (live === undefined && key !== undefined) {\n delete node.layout;\n restoreFn(field, node, key, 'kit:setLayout', dwarn);\n return;\n }\n const layout = live?.[side] ?? null;\n if (layout === null) delete node.layout;\n else field.write(node, layout);\n }\n registerKitOp<SetLayoutPayload>('kit:setLayout', {\n apply: (p) => { placeLayout(p, 'to'); },\n revert: (p) => { placeLayout(p, 'from'); },\n });\n\n registerKitOp<{\n id: NodeId; fromParent: NodeId | null; fromIndex: number;\n toParent: NodeId | null; toIndex: number;\n }>('kit:move', {\n apply: (p) => {\n detach(p.id);\n attach(p.id, p.toParent, p.toIndex);\n // Both ends: the container it left no longer hugs it, and the one it\n // joined now does.\n invalidateChildDerivedFrom(p.fromParent);\n invalidateDependents(p.id);\n },\n revert: (p) => {\n detach(p.id);\n attach(p.id, p.fromParent, p.fromIndex);\n invalidateChildDerivedFrom(p.toParent);\n invalidateDependents(p.id);\n },\n });\n\n registerKitOp<{ layer: TLayer; from: boolean; to: boolean }>('kit:setLayerVisible', {\n apply: (p) => { state.layers[requireLayerIndex(p.layer)].visible = p.to; if (!p.to) env.narrowSelection(); },\n revert: (p) => { state.layers[requireLayerIndex(p.layer)].visible = p.from; },\n });\n\n registerKitOp<{ layer: TLayer; from: boolean; to: boolean }>('kit:setLayerLocked', {\n apply: (p) => { state.layers[requireLayerIndex(p.layer)].locked = p.to; if (p.to) env.narrowSelection(); },\n revert: (p) => { state.layers[requireLayerIndex(p.layer)].locked = p.from; },\n });\n\n registerKitOp<{ layer: TLayer; from?: ParallaxOpts; to?: ParallaxOpts }>('kit:setLayerParallax', {\n apply: (p) => { setParallax(state.layers[requireLayerIndex(p.layer)], p.to); },\n revert: (p) => { setParallax(state.layers[requireLayerIndex(p.layer)], p.from); },\n });\n\n registerKitOp<{ record: UserLayerRecord<TLayer>; index: number }>('kit:addLayer', {\n apply: (p) => { state.layers.splice(p.index, 0, { ...p.record }); rebuildLayerIndex(); },\n revert: (p) => { state.layers.splice(p.index, 1); rebuildLayerIndex(); },\n });\n\n registerKitOp<{ record: LayerRecord<TLayer>; index: number }>('kit:removeLayer', {\n apply: (p) => { state.layers.splice(p.index, 1); rebuildLayerIndex(); },\n revert: (p) => { state.layers.splice(p.index, 0, { ...p.record } as LayerRecord<TLayer>); rebuildLayerIndex(); },\n });\n\n registerKitOp<{ layer: TLayer; from: string; to: string }>('kit:renameLayer', {\n apply: (p) => { (state.layers[requireLayerIndex(p.layer)] as UserLayerRecord<TLayer>).name = p.to; },\n revert: (p) => { (state.layers[requireLayerIndex(p.layer)] as UserLayerRecord<TLayer>).name = p.from; },\n });\n\n /** Finds the layer by id, not by where the payload last left it: moves of\n * one layer coalesce, and the entry then reverts the first payload from\n * wherever the last one put the layer. */\n function placeLayer(layer: TLayer, index: number): void {\n const [rec] = state.layers.splice(requireLayerIndex(layer), 1);\n state.layers.splice(index, 0, rec);\n rebuildLayerIndex();\n }\n\n registerKitOp<{ layer: TLayer; from: number; to: number }>('kit:moveLayer', {\n apply: (p) => placeLayer(p.layer, p.to),\n revert: (p) => placeLayer(p.layer, p.from),\n });\n\n return { layoutRefs };\n}\n"],"mappings":"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"}
|
|
@@ -66,11 +66,10 @@ function runLayoutPass(source, layoutOf, arrivals, changed, frame, departures =
|
|
|
66
66
|
const queue = [.../* @__PURE__ */ new Set([...arrivals.keys(), ...changed])];
|
|
67
67
|
const passes = /* @__PURE__ */ new Map();
|
|
68
68
|
const write = (id, pose) => {
|
|
69
|
-
const node = source.get(id);
|
|
70
69
|
const before = poseOf(id);
|
|
71
70
|
if (samePoseValue(before, pose)) return;
|
|
72
71
|
out.set(id, pose);
|
|
73
|
-
if (id !== current &&
|
|
72
|
+
if (id !== current && layoutOf(id) !== null && resized(frame, id, before, pose)) queue.push(id);
|
|
74
73
|
};
|
|
75
74
|
let current = null;
|
|
76
75
|
/** The container and its children as the strategy sees them, in world,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layoutPass.js","names":[],"sources":["../../../src/core/scene/layoutPass.ts"],"sourcesContent":["/**\n * The one computation behind a scene's container layouts: given the\n * containers one edit changed, the pose writes their layouts ask for.\n *\n * A container that lost children and whose strategy has `depart` rearranges\n * the rest through it (and may shrink). One that gained children and whose\n * strategy has `arrive` then places them through it (and may refuse, or\n * grow). Any other changed container — one that reordered children, was\n * resized, had a child moved, or gained or lost children with neither hook —\n * is put back in its resting arrangement, `childPoses`.\n *\n * `LayoutStrategy` is world-framed, so each pose is composed to world before\n * a strategy sees it and each result is rebased into its node's parent frame\n * after. A child already where the strategy puts it is left out, so a pass\n * that changes nothing writes nothing. A child the pass moves takes its own\n * descendants along (`carriedDescendants`). A container this pass resizes is\n * arranged in turn, so nested layouts settle in the same edit.\n */\nimport {\n composeWorldPose,\n rebaseLocalPose,\n type PoseAdapter,\n type PoseComposition,\n} from './composeFrame';\nimport type { ContainerBounds, LayoutArrival, LayoutChild, LayoutStrategy } from '../../layout/types';\nimport { carriedDescendants } from './carryDescendants';\nimport { definesFrame, documentPose, type PoseSource, type PosedNode } from './effectivePose';\nimport { asRectPose } from './kitRegistry';\nimport { samePoseValue } from './poseSnapshot';\nimport type { LayoutFrame, NodeId } from './types';\n\n/** What the pass reads a scene through. */\nexport interface LayoutPassSource<TPose> extends PoseSource<TPose> {\n get(id: NodeId): (PosedNode<TPose> & { parent: NodeId | null }) | undefined;\n readonly composition: PoseComposition<TPose>;\n}\n\n/** How the pass measures and resizes poses, with every default filled. */\nexport interface ResolvedLayoutFrame<TPose> {\n bounds(pose: TPose, id: NodeId): ContainerBounds | null;\n remap(pose: TPose, from: ContainerBounds, to: ContainerBounds, id: NodeId): TPose;\n}\n\n/** The pose writes one edit's layouts ask for, or `null` when a strategy\n * refused the children that joined it. */\nexport type LayoutPassResult<TPose> = Map<NodeId, TPose> | null;\n\nexport function resolveLayoutFrame<TPose>(frame: LayoutFrame<TPose> | undefined): ResolvedLayoutFrame<TPose> {\n return {\n bounds: frame?.bounds ?? ((pose) => {\n const r = asRectPose(pose);\n return r === null ? null : { x: r.x, y: r.y, width: r.width, height: r.height };\n }),\n remap: frame?.remap ?? ((pose, _from, to) => ({ ...(pose as object), ...to }) as TPose),\n };\n}\n\n/** Whether a node going from `from` to `to` changed size. */\nexport function resized<TPose>(frame: ResolvedLayoutFrame<TPose>, id: NodeId, from: TPose, to: TPose): boolean {\n const a = frame.bounds(from, id);\n const b = frame.bounds(to, id);\n if (a === null || b === null) return a !== b;\n return a.width !== b.width || a.height !== b.height;\n}\n\n/** How many times one pass may arrange a container before giving up on it —\n * only nested layouts that keep resizing each other get close. */\nconst MAX_PASSES = 16;\n\nexport function runLayoutPass<TPose>(\n source: LayoutPassSource<TPose>,\n layoutOf: (id: NodeId) => LayoutStrategy<TPose> | null,\n arrivals: ReadonlyMap<NodeId, readonly NodeId[]>,\n changed: ReadonlySet<NodeId>,\n frame: ResolvedLayoutFrame<TPose>,\n departures: ReadonlyMap<NodeId, readonly NodeId[]> = new Map(),\n): LayoutPassResult<TPose> {\n // Poses already decided in this pass, so a container resized here is the\n // frame its children are rebased into.\n const out = new Map<NodeId, TPose>();\n const poseOf = (id: NodeId): TPose => out.get(id) ?? documentPose(source, source.get(id)!);\n const pa: PoseAdapter<TPose> = {\n getPose: (id) => poseOf(id as NodeId),\n getParent: (id) => source.get(id as NodeId)?.parent ?? null,\n definesFrame: (id) => {\n const node = source.get(id as NodeId);\n return node === undefined || definesFrame(node);\n },\n };\n const { compose, decompose } = source.composition;\n const world = (id: NodeId): TPose => composeWorldPose(pa, id, compose);\n\n const queue: NodeId[] = [...new Set<NodeId>([...arrivals.keys(), ...changed])];\n const passes = new Map<NodeId, number>();\n const write = (id: NodeId, pose: TPose): void => {\n const node = source.get(id)!;\n const before = poseOf(id);\n if (samePoseValue(before, pose)) return;\n out.set(id, pose);\n // A container its own `depart` or `arrive` resized is arranged already.\n if (id !== current && node.kind === 'container' && layoutOf(id) !== null\n && resized(frame, id, before, pose)) queue.push(id);\n };\n let current: NodeId | null = null;\n\n /** The container and its children as the strategy sees them, in world,\n * with every write this pass has made so far folded in. */\n const frameOf = (containerId: NodeId) => {\n const pose = world(containerId);\n const bounds = frame.bounds(pose, containerId);\n if (bounds === null) return null;\n const children: LayoutChild<TPose>[] = source.childrenOf(containerId).map((cid) => ({\n id: cid as string,\n pose: world(cid),\n }));\n return { world: pose, container: { id: containerId as string, bounds }, children };\n };\n const apply = (\n containerId: NodeId,\n at: NonNullable<ReturnType<typeof frameOf>>,\n result: LayoutArrival<TPose>,\n ): void => {\n if (result.bounds) {\n const node = source.get(containerId)!;\n const sized = frame.remap(at.world, at.container.bounds, result.bounds, containerId);\n write(containerId, rebaseLocalPose(pa, sized, node.parent, compose, decompose));\n }\n for (const [cid, pose] of result.poses) {\n const id = cid as NodeId;\n if (source.get(id)?.parent !== containerId) continue;\n const before = frame.bounds(world(id), id);\n write(id, rebaseLocalPose(pa, pose, containerId, compose, decompose));\n carry(id, before);\n }\n };\n /** Moves `id`'s descendants by however far its write just moved it from `before`. */\n const carry = (id: NodeId, before: ContainerBounds | null): void => {\n const after = frame.bounds(world(id), id);\n if (before === null || after === null) return;\n const dx = after.x - before.x;\n const dy = after.y - before.y;\n if (dx === 0 && dy === 0) return;\n const carried = carriedDescendants(\n { childrenOf: (cid) => source.childrenOf(cid), poses: pa, composition: source.composition },\n id,\n (pose, cid) => {\n const b = frame.bounds(pose, cid);\n return b === null ? null : frame.remap(pose, b, { ...b, x: b.x + dx, y: b.y + dy }, cid);\n },\n );\n for (const [cid, pose] of carried) write(cid, pose);\n };\n\n while (queue.length > 0) {\n const containerId = queue.shift()!;\n current = containerId;\n const n = (passes.get(containerId) ?? 0) + 1;\n passes.set(containerId, n);\n if (n > MAX_PASSES) continue;\n const layout = layoutOf(containerId);\n if (layout === null || source.get(containerId) === undefined) continue;\n const joined = n === 1 ? arrivals.get(containerId) : undefined;\n const left = n === 1 ? departures.get(containerId) : undefined;\n let arranged = false;\n if (left !== undefined && layout.depart) {\n const at = frameOf(containerId);\n if (at === null) continue;\n const joinedSet = new Set<string>(joined ?? []);\n const staying = at.children.filter((c) => !joinedSet.has(c.id));\n apply(containerId, at, layout.depart(at.container, staying, new Set<string>(left)));\n arranged = true;\n }\n if (joined !== undefined && layout.arrive) {\n const at = frameOf(containerId);\n if (at === null) continue;\n const result = layout.arrive(at.container, at.children, new Set<string>(joined));\n if (result === null) return null;\n apply(containerId, at, result);\n } else if (!arranged || joined !== undefined) {\n const at = frameOf(containerId);\n if (at === null) continue;\n apply(containerId, at, { poses: layout.childPoses(at.container, at.children) });\n }\n }\n return out;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"layoutPass.js","names":[],"sources":["../../../src/core/scene/layoutPass.ts"],"sourcesContent":["/**\n * The one computation behind a scene's container layouts: given the\n * containers one edit changed, the pose writes their layouts ask for.\n *\n * A container that lost children and whose strategy has `depart` rearranges\n * the rest through it (and may shrink). One that gained children and whose\n * strategy has `arrive` then places them through it (and may refuse, or\n * grow). Any other changed container — one that reordered children, was\n * resized, had a child moved, or gained or lost children with neither hook —\n * is put back in its resting arrangement, `childPoses`.\n *\n * `LayoutStrategy` is world-framed, so each pose is composed to world before\n * a strategy sees it and each result is rebased into its node's parent frame\n * after. A child already where the strategy puts it is left out, so a pass\n * that changes nothing writes nothing. A child the pass moves takes its own\n * descendants along (`carriedDescendants`). A container this pass resizes is\n * arranged in turn, so nested layouts settle in the same edit.\n */\nimport {\n composeWorldPose,\n rebaseLocalPose,\n type PoseAdapter,\n type PoseComposition,\n} from './composeFrame';\nimport type { ContainerBounds, LayoutArrival, LayoutChild, LayoutStrategy } from '../../layout/types';\nimport { carriedDescendants } from './carryDescendants';\nimport { definesFrame, documentPose, type PoseSource, type PosedNode } from './effectivePose';\nimport { asRectPose } from './kitRegistry';\nimport { samePoseValue } from './poseSnapshot';\nimport type { LayoutFrame, NodeId } from './types';\n\n/** What the pass reads a scene through. */\nexport interface LayoutPassSource<TPose> extends PoseSource<TPose> {\n get(id: NodeId): (PosedNode<TPose> & { parent: NodeId | null }) | undefined;\n readonly composition: PoseComposition<TPose>;\n}\n\n/** How the pass measures and resizes poses, with every default filled. */\nexport interface ResolvedLayoutFrame<TPose> {\n bounds(pose: TPose, id: NodeId): ContainerBounds | null;\n remap(pose: TPose, from: ContainerBounds, to: ContainerBounds, id: NodeId): TPose;\n}\n\n/** The pose writes one edit's layouts ask for, or `null` when a strategy\n * refused the children that joined it. */\nexport type LayoutPassResult<TPose> = Map<NodeId, TPose> | null;\n\nexport function resolveLayoutFrame<TPose>(frame: LayoutFrame<TPose> | undefined): ResolvedLayoutFrame<TPose> {\n return {\n bounds: frame?.bounds ?? ((pose) => {\n const r = asRectPose(pose);\n return r === null ? null : { x: r.x, y: r.y, width: r.width, height: r.height };\n }),\n remap: frame?.remap ?? ((pose, _from, to) => ({ ...(pose as object), ...to }) as TPose),\n };\n}\n\n/** Whether a node going from `from` to `to` changed size. */\nexport function resized<TPose>(frame: ResolvedLayoutFrame<TPose>, id: NodeId, from: TPose, to: TPose): boolean {\n const a = frame.bounds(from, id);\n const b = frame.bounds(to, id);\n if (a === null || b === null) return a !== b;\n return a.width !== b.width || a.height !== b.height;\n}\n\n/** How many times one pass may arrange a container before giving up on it —\n * only nested layouts that keep resizing each other get close. */\nconst MAX_PASSES = 16;\n\nexport function runLayoutPass<TPose>(\n source: LayoutPassSource<TPose>,\n layoutOf: (id: NodeId) => LayoutStrategy<TPose> | null,\n arrivals: ReadonlyMap<NodeId, readonly NodeId[]>,\n changed: ReadonlySet<NodeId>,\n frame: ResolvedLayoutFrame<TPose>,\n departures: ReadonlyMap<NodeId, readonly NodeId[]> = new Map(),\n): LayoutPassResult<TPose> {\n // Poses already decided in this pass, so a container resized here is the\n // frame its children are rebased into.\n const out = new Map<NodeId, TPose>();\n const poseOf = (id: NodeId): TPose => out.get(id) ?? documentPose(source, source.get(id)!);\n const pa: PoseAdapter<TPose> = {\n getPose: (id) => poseOf(id as NodeId),\n getParent: (id) => source.get(id as NodeId)?.parent ?? null,\n definesFrame: (id) => {\n const node = source.get(id as NodeId);\n return node === undefined || definesFrame(node);\n },\n };\n const { compose, decompose } = source.composition;\n const world = (id: NodeId): TPose => composeWorldPose(pa, id, compose);\n\n const queue: NodeId[] = [...new Set<NodeId>([...arrivals.keys(), ...changed])];\n const passes = new Map<NodeId, number>();\n const write = (id: NodeId, pose: TPose): void => {\n const before = poseOf(id);\n if (samePoseValue(before, pose)) return;\n out.set(id, pose);\n // A container its own `depart` or `arrive` resized is arranged already.\n if (id !== current && layoutOf(id) !== null\n && resized(frame, id, before, pose)) queue.push(id);\n };\n let current: NodeId | null = null;\n\n /** The container and its children as the strategy sees them, in world,\n * with every write this pass has made so far folded in. */\n const frameOf = (containerId: NodeId) => {\n const pose = world(containerId);\n const bounds = frame.bounds(pose, containerId);\n if (bounds === null) return null;\n const children: LayoutChild<TPose>[] = source.childrenOf(containerId).map((cid) => ({\n id: cid as string,\n pose: world(cid),\n }));\n return { world: pose, container: { id: containerId as string, bounds }, children };\n };\n const apply = (\n containerId: NodeId,\n at: NonNullable<ReturnType<typeof frameOf>>,\n result: LayoutArrival<TPose>,\n ): void => {\n if (result.bounds) {\n const node = source.get(containerId)!;\n const sized = frame.remap(at.world, at.container.bounds, result.bounds, containerId);\n write(containerId, rebaseLocalPose(pa, sized, node.parent, compose, decompose));\n }\n for (const [cid, pose] of result.poses) {\n const id = cid as NodeId;\n if (source.get(id)?.parent !== containerId) continue;\n const before = frame.bounds(world(id), id);\n write(id, rebaseLocalPose(pa, pose, containerId, compose, decompose));\n carry(id, before);\n }\n };\n /** Moves `id`'s descendants by however far its write just moved it from `before`. */\n const carry = (id: NodeId, before: ContainerBounds | null): void => {\n const after = frame.bounds(world(id), id);\n if (before === null || after === null) return;\n const dx = after.x - before.x;\n const dy = after.y - before.y;\n if (dx === 0 && dy === 0) return;\n const carried = carriedDescendants(\n { childrenOf: (cid) => source.childrenOf(cid), poses: pa, composition: source.composition },\n id,\n (pose, cid) => {\n const b = frame.bounds(pose, cid);\n return b === null ? null : frame.remap(pose, b, { ...b, x: b.x + dx, y: b.y + dy }, cid);\n },\n );\n for (const [cid, pose] of carried) write(cid, pose);\n };\n\n while (queue.length > 0) {\n const containerId = queue.shift()!;\n current = containerId;\n const n = (passes.get(containerId) ?? 0) + 1;\n passes.set(containerId, n);\n if (n > MAX_PASSES) continue;\n const layout = layoutOf(containerId);\n if (layout === null || source.get(containerId) === undefined) continue;\n const joined = n === 1 ? arrivals.get(containerId) : undefined;\n const left = n === 1 ? departures.get(containerId) : undefined;\n let arranged = false;\n if (left !== undefined && layout.depart) {\n const at = frameOf(containerId);\n if (at === null) continue;\n const joinedSet = new Set<string>(joined ?? []);\n const staying = at.children.filter((c) => !joinedSet.has(c.id));\n apply(containerId, at, layout.depart(at.container, staying, new Set<string>(left)));\n arranged = true;\n }\n if (joined !== undefined && layout.arrive) {\n const at = frameOf(containerId);\n if (at === null) continue;\n const result = layout.arrive(at.container, at.children, new Set<string>(joined));\n if (result === null) return null;\n apply(containerId, at, result);\n } else if (!arranged || joined !== undefined) {\n const at = frameOf(containerId);\n if (at === null) continue;\n apply(containerId, at, { poses: layout.childPoses(at.container, at.children) });\n }\n }\n return out;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
//#region src/core/scene/nodeAsShown.ts
|
|
2
|
+
/** Marks a node whose data is already shown. An own enumerable key, so a spread
|
|
3
|
+
* of the node (`{ ...node, pose }`) carries it, and applying an override to a
|
|
4
|
+
* copy of a shown node is a no-op however the copy was made. */
|
|
5
|
+
var SHOWN = Symbol("weasel.shownData");
|
|
6
|
+
/** Per stored node, the last shown copy and what it was made from: keeping it
|
|
7
|
+
* is what lets a memo keyed on the node, or on its data, hit frame to frame
|
|
8
|
+
* while neither changes. */
|
|
9
|
+
var shownCopies = /* @__PURE__ */ new WeakMap();
|
|
10
|
+
/**
|
|
11
|
+
* `node` with its `data` replaced by what its override's `data` answers from
|
|
12
|
+
* it, or `node` itself when nothing overrides its data or its data is already
|
|
13
|
+
* shown. The same object while the stored node, its data and the override's
|
|
14
|
+
* function are unchanged.
|
|
15
|
+
*/
|
|
16
|
+
function nodeAsShown(overrides, node) {
|
|
17
|
+
if (SHOWN in node) return node;
|
|
18
|
+
const data = overrides.read(node.id)?.data;
|
|
19
|
+
if (data === void 0) return node;
|
|
20
|
+
const held = shownCopies.get(node);
|
|
21
|
+
if (held !== void 0 && held.data === data && held.stored === node.data) return held.shown;
|
|
22
|
+
const shown = withShownData(node, data(node.data));
|
|
23
|
+
shownCopies.set(node, {
|
|
24
|
+
stored: node.data,
|
|
25
|
+
data,
|
|
26
|
+
shown
|
|
27
|
+
});
|
|
28
|
+
return shown;
|
|
29
|
+
}
|
|
30
|
+
/** `node` showing `data`, marked so that no override applies to it again: for
|
|
31
|
+
* a reader that already holds the data to show, such as a gesture's ghost. */
|
|
32
|
+
function withShownData(node, data) {
|
|
33
|
+
return {
|
|
34
|
+
...node,
|
|
35
|
+
data,
|
|
36
|
+
[SHOWN]: true
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** The last copy `nodeAsShown` made of `node`, if any — what a reader handed
|
|
40
|
+
* the shown node memoized against, for invalidating with the stored one. */
|
|
41
|
+
function shownCopyOf(node) {
|
|
42
|
+
return shownCopies.get(node)?.shown;
|
|
43
|
+
}
|
|
44
|
+
/** `source`'s `get`, answering each node as it shows. For a reader that walks
|
|
45
|
+
* up from a node, such as inherited paint looking through its containers. */
|
|
46
|
+
function shownLookup(source) {
|
|
47
|
+
return { get: (id) => {
|
|
48
|
+
const node = source.get(id);
|
|
49
|
+
return node === void 0 ? void 0 : nodeAsShown(source.overrides, node);
|
|
50
|
+
} };
|
|
51
|
+
}
|
|
52
|
+
/** Per overrides table, whether any entry shows data, as of a generation. */
|
|
53
|
+
var anyData = /* @__PURE__ */ new WeakMap();
|
|
54
|
+
/** `nodes`, each as it shows; the same array when nothing overrides any node's
|
|
55
|
+
* data. Answers that from the table's generation, so a pick with nothing
|
|
56
|
+
* overridden allocates nothing. */
|
|
57
|
+
function nodesAsShown(overrides, nodes) {
|
|
58
|
+
const at = overrides.getGeneration();
|
|
59
|
+
let held = anyData.get(overrides);
|
|
60
|
+
if (held === void 0 || held.at !== at) {
|
|
61
|
+
held = {
|
|
62
|
+
at,
|
|
63
|
+
any: overrides.ids().some((id) => overrides.read(id)?.data !== void 0)
|
|
64
|
+
};
|
|
65
|
+
anyData.set(overrides, held);
|
|
66
|
+
}
|
|
67
|
+
return held.any ? nodes.map((node) => nodeAsShown(overrides, node)) : nodes;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
//#endregion
|
|
71
|
+
export { nodeAsShown, nodesAsShown, shownCopyOf, shownLookup, withShownData };
|
|
72
|
+
//# sourceMappingURL=nodeAsShown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodeAsShown.js","names":[],"sources":["../../../src/core/scene/nodeAsShown.ts"],"sourcesContent":["/**\n * `nodeAsShown` — a node with the data its override shows it with.\n *\n * The one read of `PoseOverride.data`. Painting, picking, drawn bounds and the\n * effective reading of a derivation go through it; the document reading\n * (`documentOf`), history and `scene.get` never do.\n */\nimport type { NodeId } from './types';\n\n/** What `nodeAsShown` reads of the overrides: a `Scene`'s satisfy it. */\nexport interface ShownDataSource {\n read(id: NodeId): { data?(stored: never): unknown } | undefined;\n}\n\n/** Marks a node whose data is already shown. An own enumerable key, so a spread\n * of the node (`{ ...node, pose }`) carries it, and applying an override to a\n * copy of a shown node is a no-op however the copy was made. */\nconst SHOWN: unique symbol = Symbol('weasel.shownData');\n\ninterface Held {\n stored: unknown;\n data: (stored: never) => unknown;\n shown: object;\n}\n\n/** Per stored node, the last shown copy and what it was made from: keeping it\n * is what lets a memo keyed on the node, or on its data, hit frame to frame\n * while neither changes. */\nconst shownCopies = new WeakMap<object, Held>();\n\n/**\n * `node` with its `data` replaced by what its override's `data` answers from\n * it, or `node` itself when nothing overrides its data or its data is already\n * shown. The same object while the stored node, its data and the override's\n * function are unchanged.\n */\nexport function nodeAsShown<TNode extends { id: NodeId; data?: unknown }>(\n overrides: ShownDataSource,\n node: TNode,\n): TNode {\n if (SHOWN in node) return node;\n const data = overrides.read(node.id)?.data;\n if (data === undefined) return node;\n const held = shownCopies.get(node);\n if (held !== undefined && held.data === data && held.stored === node.data) return held.shown as TNode;\n const shown = withShownData(node, data(node.data as never));\n shownCopies.set(node, { stored: node.data, data, shown });\n return shown;\n}\n\n/** `node` showing `data`, marked so that no override applies to it again: for\n * a reader that already holds the data to show, such as a gesture's ghost. */\nexport function withShownData<TNode extends object>(node: TNode, data: unknown): TNode {\n return { ...node, data, [SHOWN]: true };\n}\n\n/** The last copy `nodeAsShown` made of `node`, if any — what a reader handed\n * the shown node memoized against, for invalidating with the stored one. */\nexport function shownCopyOf(node: object): object | undefined {\n return shownCopies.get(node)?.shown;\n}\n\n/** `source`'s `get`, answering each node as it shows. For a reader that walks\n * up from a node, such as inherited paint looking through its containers. */\nexport function shownLookup<TNode extends { id: NodeId; data?: unknown }>(\n source: { overrides: ShownDataSource; get(id: NodeId): TNode | undefined },\n): { get(id: NodeId): TNode | undefined } {\n return {\n get: (id) => {\n const node = source.get(id);\n return node === undefined ? undefined : nodeAsShown(source.overrides, node);\n },\n };\n}\n\ntype CountedSource = ShownDataSource & { ids(): readonly NodeId[]; getGeneration(): number };\n\n/** Per overrides table, whether any entry shows data, as of a generation. */\nconst anyData = new WeakMap<object, { at: number; any: boolean }>();\n\n/** `nodes`, each as it shows; the same array when nothing overrides any node's\n * data. Answers that from the table's generation, so a pick with nothing\n * overridden allocates nothing. */\nexport function nodesAsShown<TNode extends { id: NodeId; data?: unknown }>(\n overrides: CountedSource,\n nodes: readonly TNode[],\n): readonly TNode[] {\n const at = overrides.getGeneration();\n let held = anyData.get(overrides);\n if (held === undefined || held.at !== at) {\n held = { at, any: overrides.ids().some((id) => overrides.read(id)?.data !== undefined) };\n anyData.set(overrides, held);\n }\n return held.any ? nodes.map((node) => nodeAsShown(overrides, node)) : nodes;\n}\n"],"mappings":";;;;AAiBA,IAAM,QAAuB,OAAO,kBAAkB;;;;AAWtD,IAAM,8BAAc,IAAI,QAAsB;;;;;;;AAQ9C,SAAgB,YACd,WACA,MACO;CACP,IAAI,SAAS,MAAM,OAAO;CAC1B,MAAM,OAAO,UAAU,KAAK,KAAK,EAAE,CAAC,EAAE;CACtC,IAAI,SAAS,QAAW,OAAO;CAC/B,MAAM,OAAO,YAAY,IAAI,IAAI;CACjC,IAAI,SAAS,UAAa,KAAK,SAAS,QAAQ,KAAK,WAAW,KAAK,MAAM,OAAO,KAAK;CACvF,MAAM,QAAQ,cAAc,MAAM,KAAK,KAAK,IAAa,CAAC;CAC1D,YAAY,IAAI,MAAM;EAAE,QAAQ,KAAK;EAAM;EAAM;CAAM,CAAC;CACxD,OAAO;AACT;;;AAIA,SAAgB,cAAoC,MAAa,MAAsB;CACrF,OAAO;EAAE,GAAG;EAAM;GAAO,QAAQ;CAAK;AACxC;;;AAIA,SAAgB,YAAY,MAAkC;CAC5D,OAAO,YAAY,IAAI,IAAI,CAAC,EAAE;AAChC;;;AAIA,SAAgB,YACd,QACwC;CACxC,OAAO,EACL,MAAM,OAAO;EACX,MAAM,OAAO,OAAO,IAAI,EAAE;EAC1B,OAAO,SAAS,SAAY,SAAY,YAAY,OAAO,WAAW,IAAI;CAC5E,EACF;AACF;;AAKA,IAAM,0BAAU,IAAI,QAA8C;;;;AAKlE,SAAgB,aACd,WACA,OACkB;CAClB,MAAM,KAAK,UAAU,cAAc;CACnC,IAAI,OAAO,QAAQ,IAAI,SAAS;CAChC,IAAI,SAAS,UAAa,KAAK,OAAO,IAAI;EACxC,OAAO;GAAE;GAAI,KAAK,UAAU,IAAI,CAAC,CAAC,MAAM,OAAO,UAAU,KAAK,EAAE,CAAC,EAAE,SAAS,MAAS;EAAE;EACvF,QAAQ,IAAI,WAAW,IAAI;CAC7B;CACA,OAAO,KAAK,MAAM,MAAM,KAAK,SAAS,YAAY,WAAW,IAAI,CAAC,IAAI;AACxE"}
|