@weasel-js/core 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +199 -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/barrel/text.js +2 -2
- 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/useContributionRoles.js +2 -0
- package/dist/canvas/SceneCanvas/useContributionRoles.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/buildSceneLayer.js +1 -1
- package/dist/canvas/buildSceneLayer.js.map +1 -1
- package/dist/canvas/buildSceneTree.js +14 -20
- package/dist/canvas/buildSceneTree.js.map +1 -1
- package/dist/canvas/canvasRenderer.js +6 -2
- package/dist/canvas/canvasRenderer.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 +13 -8
- 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/useCanvasPaint.js +3 -1
- package/dist/canvas/useCanvasPaint.js.map +1 -1
- 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/layers/render.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-CpIQzKWQ.d.ts} +38 -59
- 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/features/viewports/viewportLayer.js +2 -1
- package/dist/features/viewports/viewportLayer.js.map +1 -1
- package/dist/index.d.ts +230 -77
- package/dist/index.js +16 -6
- 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,8 +1,6 @@
|
|
|
1
|
-
import { composeWorldPose, composesFrames } from "../../core/scene/composeFrame.js";
|
|
2
|
-
import { definesFrame } from "../../core/scene/effectivePose.js";
|
|
3
1
|
import { insertPreviewExtent } from "../insertPreviewExtent.js";
|
|
2
|
+
import { insertSite } from "./insertSite.js";
|
|
4
3
|
import { minSizeOf } from "../../core/scene/minSize.js";
|
|
5
|
-
import { insertParent } from "../../interactions/actions/insert/parent.js";
|
|
6
4
|
import { createInsertOp } from "../../core/ops/create.js";
|
|
7
5
|
import { cycleFill, solid, strokeOf } from "../../util/paint.js";
|
|
8
6
|
import { stickyData } from "../../tools/builtin/sticky/stickyNode.js";
|
|
@@ -85,36 +83,6 @@ function boxKindData(kind, bounds, extras, painted) {
|
|
|
85
83
|
default: return null;
|
|
86
84
|
}
|
|
87
85
|
}
|
|
88
|
-
/**
|
|
89
|
-
* Where an insert lands: the container the `insertInto` dep names, on that container's
|
|
90
|
-
* layer, with the pose made local to it when the scene composes frames —
|
|
91
|
-
* or the top of the tree, on the first layer, when it names no container.
|
|
92
|
-
*/
|
|
93
|
-
function insertSite(sc, into) {
|
|
94
|
-
const parentId = insertParent(sc, into);
|
|
95
|
-
if (parentId === null) return {
|
|
96
|
-
parent: null,
|
|
97
|
-
layer: sc.layers[0]?.id ?? "default",
|
|
98
|
-
local: (p) => p
|
|
99
|
-
};
|
|
100
|
-
const parent = sc.get(parentId);
|
|
101
|
-
const composition = sc.composition;
|
|
102
|
-
if (composition === void 0 || !composesFrames(composition)) return {
|
|
103
|
-
parent: parent.id,
|
|
104
|
-
layer: parent.layer,
|
|
105
|
-
local: (p) => p
|
|
106
|
-
};
|
|
107
|
-
const frame = composeWorldPose({
|
|
108
|
-
getPose: (id) => sc.get(id).pose,
|
|
109
|
-
getParent: (id) => sc.get(id)?.parent ?? null,
|
|
110
|
-
definesFrame: (id) => definesFrame(sc.get(id) ?? {})
|
|
111
|
-
}, parent.id, composition.compose);
|
|
112
|
-
return {
|
|
113
|
-
parent: parent.id,
|
|
114
|
-
layer: parent.layer,
|
|
115
|
-
local: (p) => composition.decompose(frame, p)
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
86
|
function useInsertDepSource(scene, adapter, factories, paint = paletteInsertPaint) {
|
|
119
87
|
const sceneRef = useLatest(scene);
|
|
120
88
|
const adapterRef = useLatest(adapter);
|
|
@@ -127,7 +95,7 @@ function useInsertDepSource(scene, adapter, factories, paint = paletteInsertPain
|
|
|
127
95
|
useDepSource("insert", () => {
|
|
128
96
|
const sc = sceneRef.current;
|
|
129
97
|
const ad = adapterRef.current;
|
|
130
|
-
const site = () => insertSite(sc, registry.get("insertInto")?.get());
|
|
98
|
+
const site = () => insertSite(sc, registry.get("insertInto")?.get(), registry.get("canDropInto"));
|
|
131
99
|
const build = (bounds, extras) => {
|
|
132
100
|
const kind = extras.kind;
|
|
133
101
|
const { parent, layer, local } = site();
|
|
@@ -150,7 +118,7 @@ function useInsertDepSource(scene, adapter, factories, paint = paletteInsertPain
|
|
|
150
118
|
ops: [createInsertOp({
|
|
151
119
|
node: {
|
|
152
120
|
id,
|
|
153
|
-
kind: "
|
|
121
|
+
kind: "mark",
|
|
154
122
|
layer,
|
|
155
123
|
parent,
|
|
156
124
|
pose: local(built.pose ?? pose),
|
|
@@ -227,7 +195,7 @@ function useInsertDepSource(scene, adapter, factories, paint = paletteInsertPain
|
|
|
227
195
|
ops: [createInsertOp({
|
|
228
196
|
node: {
|
|
229
197
|
id,
|
|
230
|
-
kind: "
|
|
198
|
+
kind: "mark",
|
|
231
199
|
layer,
|
|
232
200
|
parent,
|
|
233
201
|
pose: local(pose),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"insert.js","names":["e"],"sources":["../../../src/canvas/deps/insert.ts"],"sourcesContent":["/**\n * `useInsertDepSource` — wires the `insert` dep consumed by `insertAction`.\n *\n * `line`, `polygon`, `star`, and `pencil` receive the drag AABB from the insertAction invoker,\n * but `extras` may carry richer geometry (endpoints, center+radius+rotation, point trail).\n * `insertPreviewExtent` resolves the two into the node's real extent — the same resolution the\n * live preview and the reported gesture bounds use.\n */\nimport { useRef } from 'react';\nimport { useDepRegistry, useDepSource } from '@weasel-js/routing/react';\nimport { useLatest } from '@weasel-js/react';\nimport type { InsertDep, InsertExtras, InsertLive } from 'interactions/actions/depSchema';\nimport { insertParent } from 'interactions/actions/insert/parent';\nimport type { Scene, NodeId } from 'core/scene/types';\nimport type { Op } from 'core/ops/types';\nimport { createInsertOp } from 'core/ops/create';\nimport {\n rectPath,\n ellipsePath,\n regularPolygonPath,\n starPath,\n linePath,\n polygonFromPoints,\n schneiderFit,\n type PathFillRule,\n type PolygonPath,\n} from '@weasel-js/geom';\nimport { insertPreviewExtent } from '../insertPreviewExtent';\nimport type { ShapeGenerator } from 'features/paths/shapeGenerator';\nimport type { Stroke, Fill } from '@weasel-js/paint';\nimport { DEFAULT_TEXT_FILL } from '@weasel-js/text';\nimport { cycleFill, solid, strokeOf } from '../../util/paint';\nimport { composesFrames, composeWorldPose } from 'core/scene/composeFrame';\nimport { definesFrame } from 'core/scene/effectivePose';\nimport { stickyData, type StickyStyle } from '../../tools/builtin/sticky/stickyNode';\nimport { minSizeOf } from 'core/scene/minSize';\n\ninterface OpsApplier {\n applyOps(ops: Op[], label?: string): void;\n}\n\n/**\n * Consumer-supplied node factory for one insert `kind`. Given the drag AABB\n * and the tool's `extras`, returns the node's `data` (in the consumer's own\n * data shape) plus an optional `pose` override — the dep supplies id, layer,\n * and the undoable insert op. Return `null` to reject the insert.\n *\n * A factory registered for a kit kind (`rect`, `line`, …) fully replaces the\n * kit's default `{ path, fill }` factory for that kind; a factory for a novel\n * kind adds support the kit doesn't ship (e.g. `text`). To change only the\n * paint of a kit shape, pass an {@link InsertPaint}. See\n * `SceneCanvas`'s `insertNodeFactories` prop.\n */\nexport type InsertNodeFactory = (\n bounds: { x: number; y: number; width: number; height: number },\n extras: InsertExtras,\n) => { data: unknown; pose?: unknown } | null;\n\n/**\n * The paint a kit-built shape is inserted with. `kind` is the shape being\n * built and `seq` counts this canvas's inserts from 1. A key left out stays\n * off the node, which paints none of it, the same as `null`. A pencil\n * trail too short to fit is built as a rect and asks as one. A line and an\n * open pen path are inserted unfilled: a fill given to either is dropped.\n * `fillRule` is the rule a path that carries one is built with, where the\n * kit's own is nonzero; a rect or an ellipse has none to set.\n *\n * It paints only the shapes the kit builds: an `InsertNodeFactory` owns its\n * node's whole `data`. See `SceneCanvas`'s `insertPaint` prop.\n */\nexport type InsertPaint = (kind: string, seq: number) => {\n fill?: Fill | null;\n stroke?: Stroke | null;\n fillRule?: PathFillRule;\n};\n\nfunction paintKeys({ fill, stroke }: ReturnType<InsertPaint>): { fill?: Fill | null; stroke?: Stroke | null } {\n return { ...(fill !== undefined ? { fill } : {}), ...(stroke !== undefined ? { stroke } : {}) };\n}\n\nfunction withFillRule(data: unknown, rule: PathFillRule | undefined): unknown {\n const path = (data as { path?: object } | undefined)?.path;\n if (rule === undefined || path === undefined || !('fillRule' in path)) return data;\n return { ...(data as object), path: { ...path, fillRule: rule } };\n}\n\n/** The default {@link InsertPaint}: the next color of the kit palette, as a\n * fill on a closed shape, a fill and a stroke on a pen path, and a stroke\n * alone on a line or a pencil trail. */\nexport const paletteInsertPaint: InsertPaint = (kind, seq) => {\n const color = cycleFill(seq);\n if (kind === 'line' || kind === 'pencil') return { stroke: strokeOf(color, 2) };\n if (kind === 'path') return { fill: { paint: solid(color) }, stroke: strokeOf(color, 2) };\n return { fill: { paint: solid(color) } };\n};\n\n/** The kit's default data for a kind drawn as a box, or `null` for a kind it has none for. */\nfunction boxKindData(\n kind: string,\n bounds: { x: number; y: number; width: number; height: number },\n extras: InsertExtras,\n painted: (as?: string) => ReturnType<typeof paintKeys>,\n): unknown {\n switch (kind) {\n case 'rect':\n // The rect path lives at the origin and the pose carries position:\n // `pathInPoseFrame` rebases a rect path onto the pose box, so don't\n // double-count the position.\n return { path: rectPath(0, 0, bounds.width, bounds.height), ...painted() };\n case 'ellipse':\n return { path: ellipsePath(bounds), ...painted() };\n case 'path': {\n const e = extras as { path: PolygonPath; closed: boolean; data?: Readonly<Record<string, unknown>> };\n return { ...e.data, path: e.path, ...painted(), ...(e.closed ? {} : { fill: null }) };\n }\n case 'text': {\n // The built-in text tool drags an empty text box you then type into, so\n // `extras` carries no content. The kit:text painter matches on\n // `data.text != null` and applies style defaults at render time.\n const e = extras as Partial<{ text: string }>;\n return { text: e.text ?? '', fill: DEFAULT_TEXT_FILL };\n }\n case 'sticky':\n return stickyData(extras as StickyStyle & { text?: string });\n case 'image': {\n // The bitmap is loaded + cached by `imageCache` keyed on `src`;\n // only the (serializable) `src` lives on the node.\n const e = extras as Partial<{ src: string; opacity: number }>;\n return { image: { src: e.src ?? '', ...(e.opacity !== undefined ? { opacity: e.opacity } : {}) } };\n }\n default:\n return null;\n }\n}\n\n/**\n * Where an insert lands: the container the `insertInto` dep names, on that container's\n * layer, with the pose made local to it when the scene composes frames —\n * or the top of the tree, on the first layer, when it names no container.\n */\nfunction insertSite(\n sc: Scene<unknown, string, unknown>,\n into: string | null | undefined,\n): { parent: NodeId | null; layer: string; local: (world: unknown) => unknown } {\n const parentId = insertParent(sc, into);\n if (parentId === null) {\n return { parent: null, layer: (sc.layers[0]?.id ?? 'default') as string, local: (p) => p };\n }\n const parent = sc.get(parentId)!;\n const composition = sc.composition;\n if (composition === undefined || !composesFrames(composition)) {\n return { parent: parent.id, layer: parent.layer, local: (p) => p };\n }\n const frame = composeWorldPose({\n getPose: (id) => sc.get(id as NodeId)!.pose,\n getParent: (id) => sc.get(id as NodeId)?.parent ?? null,\n definesFrame: (id) => definesFrame(sc.get(id as NodeId) ?? {}),\n }, parent.id, composition.compose);\n return { parent: parent.id, layer: parent.layer, local: (p) => composition.decompose(frame, p) };\n}\n\nexport function useInsertDepSource(\n scene: Scene<unknown, string, unknown>,\n adapter: OpsApplier,\n factories?: Record<string, InsertNodeFactory>,\n paint: InsertPaint = paletteInsertPaint,\n): void {\n const sceneRef = useLatest(scene);\n const adapterRef = useLatest(adapter);\n const factoriesRef = useLatest(factories);\n const paintRef = useLatest(paint);\n const registry = useDepRegistry();\n const insertSeqRef = useRef(0);\n const liveRef = useRef<InsertLive>({ gesture: null });\n useDepSource('insertLive', () => liveRef.current);\n\n useDepSource('insert', (): InsertDep => {\n const sc = sceneRef.current;\n const ad = adapterRef.current;\n\n const site = () => insertSite(sc, registry.get('insertInto')?.get());\n const build: NonNullable<InsertDep['build']> = (bounds, extras) => {\n const kind = extras.kind;\n const { parent, layer, local } = site();\n const seq = ++insertSeqRef.current;\n const id = sc.mintId();\n let asked: ReturnType<InsertPaint> | undefined;\n const painted = (as: string = kind) => paintKeys(asked = paintRef.current(as, seq));\n\n // The nascent node's extent — drag AABB reconciled with whatever\n // richer geometry `extras` carries.\n const extent = insertPreviewExtent({ shape: kind, bounds, extras });\n const pose: unknown = extent.bounds;\n\n // Consumer factory wins over the kit default for this kind. It owns the node's\n // data + optional pose; the dep still supplies id/layer/op.\n const factory = factoriesRef.current?.[kind];\n if (factory) {\n const built = factory(bounds, extras);\n if (built === null) return null;\n return {\n id,\n ops: [createInsertOp({ node: { id, kind: 'leaf', layer, parent, pose: local(built.pose ?? pose), data: built.data } as unknown as { id: string }, label: `Insert ${kind}` })],\n };\n }\n\n const geom = extent.geometry;\n let data: unknown;\n\n switch (geom.kind) {\n case 'line':\n data = { path: linePath(geom.a, geom.b), ...painted(), fill: null };\n break;\n case 'polygon':\n data = {\n path: regularPolygonPath(geom.center, geom.radius, geom.sides, geom.rotation),\n generator: { kind: 'polygon', sides: geom.sides, rotation: geom.rotation } satisfies ShapeGenerator,\n ...painted(),\n };\n break;\n case 'star':\n data = {\n path: starPath(geom.center, geom.outerRadius, geom.points, geom.innerRadius, geom.rotation),\n generator: {\n kind: 'star',\n points: geom.points,\n innerRadiusRatio: geom.outerRadius === 0 ? 0.5 : geom.innerRadius / geom.outerRadius,\n rotation: geom.rotation,\n } satisfies ShapeGenerator,\n ...painted(),\n };\n break;\n case 'pencil': {\n const samples = geom.samples as { x: number; y: number }[];\n if (samples.length >= 4) {\n data = { path: schneiderFit(samples, 2.0), ...painted() };\n } else if (samples.length >= 2) {\n data = { path: polygonFromPoints(samples), ...painted() };\n } else {\n data = { path: rectPath(0, 0, extent.bounds.width, extent.bounds.height), ...painted('rect') };\n }\n break;\n }\n case 'box':\n data = boxKindData(kind, extent.bounds, extras, painted);\n if (data === null) {\n console.warn(`weasel insertDep: no factory for kind=\"${kind}\". Skipping insert.`);\n return null;\n }\n break;\n }\n\n data = withFillRule(data, asked?.fillRule);\n const min = minSizeOf(extras);\n if (min) data = { ...(data as object), minSize: min };\n return {\n id,\n ops: [createInsertOp({ node: { id, kind: 'leaf', layer, parent, pose: local(pose), data } as unknown as { id: string }, label: `Insert ${kind}` })],\n };\n };\n return {\n layer: () => site().layer,\n build,\n commit(bounds, extras, ends): NodeId | null {\n const built = build(bounds, extras, ends);\n if (built === null) return null;\n ad.applyOps(built.ops, `Insert ${extras.kind}`);\n return built.id;\n },\n };\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"insert.js","names":["e"],"sources":["../../../src/canvas/deps/insert.ts"],"sourcesContent":["/**\n * `useInsertDepSource` — wires the `insert` dep consumed by `insertAction`.\n *\n * `line`, `polygon`, `star`, and `pencil` receive the drag AABB from the insertAction invoker,\n * but `extras` may carry richer geometry (endpoints, center+radius+rotation, point trail).\n * `insertPreviewExtent` resolves the two into the node's real extent — the same resolution the\n * live preview and the reported gesture bounds use.\n */\nimport { useRef } from 'react';\nimport { useDepRegistry, useDepSource } from '@weasel-js/routing/react';\nimport { useLatest } from '@weasel-js/react';\nimport type { InsertDep, InsertExtras, InsertLive } from 'interactions/actions/depSchema';\nimport { insertSite } from './insertSite';\nimport type { Scene, NodeId } from 'core/scene/types';\nimport type { Op } from 'core/ops/types';\nimport { createInsertOp } from 'core/ops/create';\nimport {\n rectPath,\n ellipsePath,\n regularPolygonPath,\n starPath,\n linePath,\n polygonFromPoints,\n schneiderFit,\n type PathFillRule,\n type PolygonPath,\n} from '@weasel-js/geom';\nimport { insertPreviewExtent } from '../insertPreviewExtent';\nimport type { ShapeGenerator } from 'features/paths/shapeGenerator';\nimport type { Stroke, Fill } from '@weasel-js/paint';\nimport { DEFAULT_TEXT_FILL } from '@weasel-js/text';\nimport { cycleFill, solid, strokeOf } from '../../util/paint';\nimport { stickyData, type StickyStyle } from '../../tools/builtin/sticky/stickyNode';\nimport { minSizeOf } from 'core/scene/minSize';\n\ninterface OpsApplier {\n applyOps(ops: Op[], label?: string): void;\n}\n\n/**\n * Consumer-supplied node factory for one insert `kind`. Given the drag AABB\n * and the tool's `extras`, returns the node's `data` (in the consumer's own\n * data shape) plus an optional `pose` override — the dep supplies id, layer,\n * and the undoable insert op. Return `null` to reject the insert.\n *\n * A factory registered for a kit kind (`rect`, `line`, …) fully replaces the\n * kit's default `{ path, fill }` factory for that kind; a factory for a novel\n * kind adds support the kit doesn't ship (e.g. `text`). To change only the\n * paint of a kit shape, pass an {@link InsertPaint}. See\n * `SceneCanvas`'s `insertNodeFactories` prop.\n */\nexport type InsertNodeFactory = (\n bounds: { x: number; y: number; width: number; height: number },\n extras: InsertExtras,\n) => { data: unknown; pose?: unknown } | null;\n\n/**\n * The paint a kit-built shape is inserted with. `kind` is the shape being\n * built and `seq` counts this canvas's inserts from 1. A key left out stays\n * off the node, which paints none of it, the same as `null`. A pencil\n * trail too short to fit is built as a rect and asks as one. A line and an\n * open pen path are inserted unfilled: a fill given to either is dropped.\n * `fillRule` is the rule a path that carries one is built with, where the\n * kit's own is nonzero; a rect or an ellipse has none to set.\n *\n * It paints only the shapes the kit builds: an `InsertNodeFactory` owns its\n * node's whole `data`. See `SceneCanvas`'s `insertPaint` prop.\n */\nexport type InsertPaint = (kind: string, seq: number) => {\n fill?: Fill | null;\n stroke?: Stroke | null;\n fillRule?: PathFillRule;\n};\n\nfunction paintKeys({ fill, stroke }: ReturnType<InsertPaint>): { fill?: Fill | null; stroke?: Stroke | null } {\n return { ...(fill !== undefined ? { fill } : {}), ...(stroke !== undefined ? { stroke } : {}) };\n}\n\nfunction withFillRule(data: unknown, rule: PathFillRule | undefined): unknown {\n const path = (data as { path?: object } | undefined)?.path;\n if (rule === undefined || path === undefined || !('fillRule' in path)) return data;\n return { ...(data as object), path: { ...path, fillRule: rule } };\n}\n\n/** The default {@link InsertPaint}: the next color of the kit palette, as a\n * fill on a closed shape, a fill and a stroke on a pen path, and a stroke\n * alone on a line or a pencil trail. */\nexport const paletteInsertPaint: InsertPaint = (kind, seq) => {\n const color = cycleFill(seq);\n if (kind === 'line' || kind === 'pencil') return { stroke: strokeOf(color, 2) };\n if (kind === 'path') return { fill: { paint: solid(color) }, stroke: strokeOf(color, 2) };\n return { fill: { paint: solid(color) } };\n};\n\n/** The kit's default data for a kind drawn as a box, or `null` for a kind it has none for. */\nfunction boxKindData(\n kind: string,\n bounds: { x: number; y: number; width: number; height: number },\n extras: InsertExtras,\n painted: (as?: string) => ReturnType<typeof paintKeys>,\n): unknown {\n switch (kind) {\n case 'rect':\n // The rect path lives at the origin and the pose carries position:\n // `pathInPoseFrame` rebases a rect path onto the pose box, so don't\n // double-count the position.\n return { path: rectPath(0, 0, bounds.width, bounds.height), ...painted() };\n case 'ellipse':\n return { path: ellipsePath(bounds), ...painted() };\n case 'path': {\n const e = extras as { path: PolygonPath; closed: boolean; data?: Readonly<Record<string, unknown>> };\n return { ...e.data, path: e.path, ...painted(), ...(e.closed ? {} : { fill: null }) };\n }\n case 'text': {\n // The built-in text tool drags an empty text box you then type into, so\n // `extras` carries no content. The kit:text painter matches on\n // `data.text != null` and applies style defaults at render time.\n const e = extras as Partial<{ text: string }>;\n return { text: e.text ?? '', fill: DEFAULT_TEXT_FILL };\n }\n case 'sticky':\n return stickyData(extras as StickyStyle & { text?: string });\n case 'image': {\n // The bitmap is loaded + cached by `imageCache` keyed on `src`;\n // only the (serializable) `src` lives on the node.\n const e = extras as Partial<{ src: string; opacity: number }>;\n return { image: { src: e.src ?? '', ...(e.opacity !== undefined ? { opacity: e.opacity } : {}) } };\n }\n default:\n return null;\n }\n}\n\nexport function useInsertDepSource(\n scene: Scene<unknown, string, unknown>,\n adapter: OpsApplier,\n factories?: Record<string, InsertNodeFactory>,\n paint: InsertPaint = paletteInsertPaint,\n): void {\n const sceneRef = useLatest(scene);\n const adapterRef = useLatest(adapter);\n const factoriesRef = useLatest(factories);\n const paintRef = useLatest(paint);\n const registry = useDepRegistry();\n const insertSeqRef = useRef(0);\n const liveRef = useRef<InsertLive>({ gesture: null });\n useDepSource('insertLive', () => liveRef.current);\n\n useDepSource('insert', (): InsertDep => {\n const sc = sceneRef.current;\n const ad = adapterRef.current;\n\n const site = () => insertSite(sc, registry.get('insertInto')?.get(), registry.get('canDropInto'));\n const build: NonNullable<InsertDep['build']> = (bounds, extras) => {\n const kind = extras.kind;\n const { parent, layer, local } = site();\n const seq = ++insertSeqRef.current;\n const id = sc.mintId();\n let asked: ReturnType<InsertPaint> | undefined;\n const painted = (as: string = kind) => paintKeys(asked = paintRef.current(as, seq));\n\n // The nascent node's extent — drag AABB reconciled with whatever\n // richer geometry `extras` carries.\n const extent = insertPreviewExtent({ shape: kind, bounds, extras });\n const pose: unknown = extent.bounds;\n\n // Consumer factory wins over the kit default for this kind. It owns the node's\n // data + optional pose; the dep still supplies id/layer/op.\n const factory = factoriesRef.current?.[kind];\n if (factory) {\n const built = factory(bounds, extras);\n if (built === null) return null;\n return {\n id,\n ops: [createInsertOp({ node: { id, kind: 'mark', layer, parent, pose: local(built.pose ?? pose), data: built.data } as unknown as { id: string }, label: `Insert ${kind}` })],\n };\n }\n\n const geom = extent.geometry;\n let data: unknown;\n\n switch (geom.kind) {\n case 'line':\n data = { path: linePath(geom.a, geom.b), ...painted(), fill: null };\n break;\n case 'polygon':\n data = {\n path: regularPolygonPath(geom.center, geom.radius, geom.sides, geom.rotation),\n generator: { kind: 'polygon', sides: geom.sides, rotation: geom.rotation } satisfies ShapeGenerator,\n ...painted(),\n };\n break;\n case 'star':\n data = {\n path: starPath(geom.center, geom.outerRadius, geom.points, geom.innerRadius, geom.rotation),\n generator: {\n kind: 'star',\n points: geom.points,\n innerRadiusRatio: geom.outerRadius === 0 ? 0.5 : geom.innerRadius / geom.outerRadius,\n rotation: geom.rotation,\n } satisfies ShapeGenerator,\n ...painted(),\n };\n break;\n case 'pencil': {\n const samples = geom.samples as { x: number; y: number }[];\n if (samples.length >= 4) {\n data = { path: schneiderFit(samples, 2.0), ...painted() };\n } else if (samples.length >= 2) {\n data = { path: polygonFromPoints(samples), ...painted() };\n } else {\n data = { path: rectPath(0, 0, extent.bounds.width, extent.bounds.height), ...painted('rect') };\n }\n break;\n }\n case 'box':\n data = boxKindData(kind, extent.bounds, extras, painted);\n if (data === null) {\n console.warn(`weasel insertDep: no factory for kind=\"${kind}\". Skipping insert.`);\n return null;\n }\n break;\n }\n\n data = withFillRule(data, asked?.fillRule);\n const min = minSizeOf(extras);\n if (min) data = { ...(data as object), minSize: min };\n return {\n id,\n ops: [createInsertOp({ node: { id, kind: 'mark', layer, parent, pose: local(pose), data } as unknown as { id: string }, label: `Insert ${kind}` })],\n };\n };\n return {\n layer: () => site().layer,\n build,\n commit(bounds, extras, ends): NodeId | null {\n const built = build(bounds, extras, ends);\n if (built === null) return null;\n ad.applyOps(built.ops, `Insert ${extras.kind}`);\n return built.id;\n },\n };\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { composeWorldPose, composesFrames } from "../../core/scene/composeFrame.js";
|
|
2
|
+
import { definesFrame } from "../../core/scene/effectivePose.js";
|
|
3
|
+
import { insertParent } from "../../interactions/actions/insert/parent.js";
|
|
4
|
+
|
|
5
|
+
//#region src/canvas/deps/insertSite.ts
|
|
6
|
+
/**
|
|
7
|
+
* Where an insert lands: the container the `insertInto` dep names, on that container's
|
|
8
|
+
* layer, with the pose made local to it when the scene composes frames —
|
|
9
|
+
* or the top of the tree, on the first layer, when it names no container.
|
|
10
|
+
*/
|
|
11
|
+
function insertSite(sc, into, canDropInto) {
|
|
12
|
+
const parentId = insertParent(sc, into, canDropInto);
|
|
13
|
+
if (parentId === null) return {
|
|
14
|
+
parent: null,
|
|
15
|
+
layer: sc.layers[0]?.id ?? "default",
|
|
16
|
+
local: (p) => p
|
|
17
|
+
};
|
|
18
|
+
const parent = sc.get(parentId);
|
|
19
|
+
const composition = sc.composition;
|
|
20
|
+
if (composition === void 0 || !composesFrames(composition)) return {
|
|
21
|
+
parent: parent.id,
|
|
22
|
+
layer: parent.layer,
|
|
23
|
+
local: (p) => p
|
|
24
|
+
};
|
|
25
|
+
const frame = composeWorldPose({
|
|
26
|
+
getPose: (id) => sc.get(id).pose,
|
|
27
|
+
getParent: (id) => sc.get(id)?.parent ?? null,
|
|
28
|
+
definesFrame: (id) => definesFrame(sc.get(id) ?? {})
|
|
29
|
+
}, parent.id, composition.compose);
|
|
30
|
+
return {
|
|
31
|
+
parent: parent.id,
|
|
32
|
+
layer: parent.layer,
|
|
33
|
+
local: (p) => composition.decompose(frame, p)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
export { insertSite };
|
|
39
|
+
//# sourceMappingURL=insertSite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"insertSite.js","names":[],"sources":["../../../src/canvas/deps/insertSite.ts"],"sourcesContent":["import type { Scene, NodeId } from 'core/scene/types';\nimport type { CanDropIntoDep } from 'interactions/actions/layoutDeps';\nimport { insertParent } from 'interactions/actions/insert/parent';\nimport { composesFrames, composeWorldPose } from 'core/scene/composeFrame';\nimport { definesFrame } from 'core/scene/effectivePose';\n\n/** Where a new node lands: its parent, its layer, and how a world pose becomes its stored one. */\nexport interface InsertSite {\n parent: NodeId | null;\n layer: string;\n local: (world: unknown) => unknown;\n}\n\n/**\n * Where an insert lands: the container the `insertInto` dep names, on that container's\n * layer, with the pose made local to it when the scene composes frames —\n * or the top of the tree, on the first layer, when it names no container.\n */\nexport function insertSite(\n sc: Pick<Scene<unknown, string, unknown>, 'get' | 'layers' | 'composition'>,\n into: string | null | undefined,\n canDropInto?: CanDropIntoDep,\n): InsertSite {\n const parentId = insertParent(sc, into, canDropInto);\n if (parentId === null) {\n return { parent: null, layer: (sc.layers[0]?.id ?? 'default') as string, local: (p) => p };\n }\n const parent = sc.get(parentId)!;\n const composition = sc.composition;\n if (composition === undefined || !composesFrames(composition)) {\n return { parent: parent.id, layer: parent.layer, local: (p) => p };\n }\n const frame = composeWorldPose({\n getPose: (id) => sc.get(id as NodeId)!.pose,\n getParent: (id) => sc.get(id as NodeId)?.parent ?? null,\n definesFrame: (id) => definesFrame(sc.get(id as NodeId) ?? {}),\n }, parent.id, composition.compose);\n return { parent: parent.id, layer: parent.layer, local: (p) => composition.decompose(frame, p) };\n}\n"],"mappings":";;;;;;;;;;AAkBA,SAAgB,WACd,IACA,MACA,aACY;CACZ,MAAM,WAAW,aAAa,IAAI,MAAM,WAAW;CACnD,IAAI,aAAa,MACf,OAAO;EAAE,QAAQ;EAAM,OAAQ,GAAG,OAAO,EAAE,EAAE,MAAM;EAAsB,QAAQ,MAAM;CAAE;CAE3F,MAAM,SAAS,GAAG,IAAI,QAAQ;CAC9B,MAAM,cAAc,GAAG;CACvB,IAAI,gBAAgB,UAAa,CAAC,eAAe,WAAW,GAC1D,OAAO;EAAE,QAAQ,OAAO;EAAI,OAAO,OAAO;EAAO,QAAQ,MAAM;CAAE;CAEnE,MAAM,QAAQ,iBAAiB;EAC7B,UAAU,OAAO,GAAG,IAAI,EAAY,CAAC,CAAE;EACvC,YAAY,OAAO,GAAG,IAAI,EAAY,CAAC,EAAE,UAAU;EACnD,eAAe,OAAO,aAAa,GAAG,IAAI,EAAY,KAAK,CAAC,CAAC;CAC/D,GAAG,OAAO,IAAI,YAAY,OAAO;CACjC,OAAO;EAAE,QAAQ,OAAO;EAAI,OAAO,OAAO;EAAO,QAAQ,MAAM,YAAY,UAAU,OAAO,CAAC;CAAE;AACjG"}
|
|
@@ -8,19 +8,22 @@ import { useDepSource } from "@weasel-js/routing/react";
|
|
|
8
8
|
* `lassoSelectAction`: `hitTestLasso` tests the lasso polygon itself, and
|
|
9
9
|
* `hitTestArea` is the same silhouette-aware rect test `areaSelect` uses.
|
|
10
10
|
*/
|
|
11
|
-
function useLassoSelectDepSource(scene, selection, descriptor, alphaOf) {
|
|
11
|
+
function useLassoSelectDepSource(scene, selection, descriptor, alphaOf, reachable) {
|
|
12
12
|
const sceneRef = useLatest(scene);
|
|
13
13
|
const selectionRef = useLatest(selection);
|
|
14
14
|
const descriptorRef = useLatest(descriptor);
|
|
15
15
|
const alphaOfRef = useLatest(alphaOf);
|
|
16
|
+
const reachableRef = useLatest(reachable);
|
|
16
17
|
useDepSource("lassoSelect", () => {
|
|
17
18
|
const sc = sceneRef.current;
|
|
18
19
|
const s = selectionRef.current;
|
|
19
20
|
const d = descriptorRef.current;
|
|
20
21
|
const a = alphaOfRef.current;
|
|
22
|
+
const r = reachableRef.current;
|
|
23
|
+
const keep = (ids) => r ? ids.filter((id) => r(id)) : ids;
|
|
21
24
|
return {
|
|
22
|
-
hitTestLasso: (polygon, mode, view) => hitTestLassoPolygon(sc, polygon, mode, regionPickOptions(view, a), d),
|
|
23
|
-
hitTestArea: (bounds, view) => hitTestArea(sc, bounds, regionPickOptions(view, a), d),
|
|
25
|
+
hitTestLasso: (polygon, mode, view) => keep(hitTestLassoPolygon(sc, polygon, mode, regionPickOptions(view, a), d)),
|
|
26
|
+
hitTestArea: (bounds, view) => keep(hitTestArea(sc, bounds, regionPickOptions(view, a), d)),
|
|
24
27
|
getSelection: () => s.current,
|
|
25
28
|
setSelection: (ids) => s.set(ids)
|
|
26
29
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lassoSelect.js","names":["hitTestAreaShared"],"sources":["../../../src/canvas/deps/lassoSelect.ts"],"sourcesContent":["/**\n * `useLassoSelectDepSource` — wires the `lassoSelect` dep consumed by\n * `lassoSelectAction`: `hitTestLasso` tests the lasso polygon itself, and\n * `hitTestArea` is the same silhouette-aware rect test `areaSelect` uses.\n */\nimport { useDepSource } from '@weasel-js/routing/react';\nimport { useLatest } from '@weasel-js/react';\nimport type { LassoSelectDep } from 'interactions/actions/depSchema';\nimport type { Scene, NodeId } from 'core/scene/types';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { hitTestArea as hitTestAreaShared, hitTestLassoPolygon, regionPickOptions } from './hitTestArea';\n\nexport function useLassoSelectDepSource(\n scene: Scene<unknown, string, unknown>,\n selection: SelectionApi,\n descriptor?: PoseDescriptor<unknown>,\n /** The painted alpha the surface's layers apply, which every view shares. */\n alphaOf?: (id: string) => number,\n): void {\n const sceneRef = useLatest(scene);\n const selectionRef = useLatest(selection);\n const descriptorRef = useLatest(descriptor);\n const alphaOfRef = useLatest(alphaOf);\n\n useDepSource('lassoSelect', (): LassoSelectDep => {\n const sc = sceneRef.current;\n const s = selectionRef.current;\n const d = descriptorRef.current;\n const a = alphaOfRef.current;\n return {\n hitTestLasso: (polygon, mode, view) =>\n hitTestLassoPolygon(sc, polygon, mode, regionPickOptions(view, a), d),\n hitTestArea: (bounds, view) =>\n hitTestAreaShared(sc, bounds, regionPickOptions(view, a), d),\n getSelection: () => s.current as NodeId[],\n setSelection: (ids) => s.set(ids as NodeId[]),\n };\n });\n}\n"],"mappings":";;;;;;;;;;AAaA,SAAgB,wBACd,OACA,WACA,YAEA,
|
|
1
|
+
{"version":3,"file":"lassoSelect.js","names":["hitTestAreaShared"],"sources":["../../../src/canvas/deps/lassoSelect.ts"],"sourcesContent":["/**\n * `useLassoSelectDepSource` — wires the `lassoSelect` dep consumed by\n * `lassoSelectAction`: `hitTestLasso` tests the lasso polygon itself, and\n * `hitTestArea` is the same silhouette-aware rect test `areaSelect` uses.\n */\nimport { useDepSource } from '@weasel-js/routing/react';\nimport { useLatest } from '@weasel-js/react';\nimport type { LassoSelectDep } from 'interactions/actions/depSchema';\nimport type { Scene, NodeId } from 'core/scene/types';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { hitTestArea as hitTestAreaShared, hitTestLassoPolygon, regionPickOptions } from './hitTestArea';\n\nexport function useLassoSelectDepSource(\n scene: Scene<unknown, string, unknown>,\n selection: SelectionApi,\n descriptor?: PoseDescriptor<unknown>,\n /** The painted alpha the surface's layers apply, which every view shares. */\n alphaOf?: (id: string) => number,\n /** Whether a node can be reached at all: the canvas's one answer, which picking reads too. */\n reachable?: (id: string) => boolean,\n): void {\n const sceneRef = useLatest(scene);\n const selectionRef = useLatest(selection);\n const descriptorRef = useLatest(descriptor);\n const alphaOfRef = useLatest(alphaOf);\n const reachableRef = useLatest(reachable);\n\n useDepSource('lassoSelect', (): LassoSelectDep => {\n const sc = sceneRef.current;\n const s = selectionRef.current;\n const d = descriptorRef.current;\n const a = alphaOfRef.current;\n const r = reachableRef.current;\n const keep = (ids: NodeId[]): NodeId[] => (r ? ids.filter((id) => r(id)) : ids);\n return {\n hitTestLasso: (polygon, mode, view) =>\n keep(hitTestLassoPolygon(sc, polygon, mode, regionPickOptions(view, a), d)),\n hitTestArea: (bounds, view) =>\n keep(hitTestAreaShared(sc, bounds, regionPickOptions(view, a), d)),\n getSelection: () => s.current as NodeId[],\n setSelection: (ids) => s.set(ids as NodeId[]),\n };\n });\n}\n"],"mappings":";;;;;;;;;;AAaA,SAAgB,wBACd,OACA,WACA,YAEA,SAEA,WACM;CACN,MAAM,WAAW,UAAU,KAAK;CAChC,MAAM,eAAe,UAAU,SAAS;CACxC,MAAM,gBAAgB,UAAU,UAAU;CAC1C,MAAM,aAAa,UAAU,OAAO;CACpC,MAAM,eAAe,UAAU,SAAS;CAExC,aAAa,qBAAqC;EAChD,MAAM,KAAK,SAAS;EACpB,MAAM,IAAI,aAAa;EACvB,MAAM,IAAI,cAAc;EACxB,MAAM,IAAI,WAAW;EACrB,MAAM,IAAI,aAAa;EACvB,MAAM,QAAQ,QAA6B,IAAI,IAAI,QAAQ,OAAO,EAAE,EAAE,CAAC,IAAI;EAC3E,OAAO;GACL,eAAe,SAAS,MAAM,SAC5B,KAAK,oBAAoB,IAAI,SAAS,MAAM,kBAAkB,MAAM,CAAC,GAAG,CAAC,CAAC;GAC5E,cAAc,QAAQ,SACpB,KAAKA,YAAkB,IAAI,QAAQ,kBAAkB,MAAM,CAAC,GAAG,CAAC,CAAC;GACnE,oBAAoB,EAAE;GACtB,eAAe,QAAQ,EAAE,IAAI,GAAe;EAC9C;CACF,CAAC;AACH"}
|
|
@@ -55,7 +55,7 @@ function storePiece({ world, g, box }, piece) {
|
|
|
55
55
|
/** The path a leaf stores, or null for one that is not a free path leaf: a
|
|
56
56
|
* group, a locked leaf, one whose geometry derives from other nodes. */
|
|
57
57
|
function leafPath(scene, node) {
|
|
58
|
-
if (node.kind !== "
|
|
58
|
+
if (node.kind !== "mark" || node.derivePath || node.derivePose) return null;
|
|
59
59
|
if (scene.isLocked(node.id)) return null;
|
|
60
60
|
const path = node.data?.path;
|
|
61
61
|
return path && (path.kind === "rect" || path.kind === "polygon") ? path : null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pathLeaf.js","names":[],"sources":["../../../src/canvas/deps/pathLeaf.ts"],"sourcesContent":["/**\n * Path leaves as the kit's own path deps read and write them: geometry in\n * `data.path`, drawn through the box its pose describes.\n */\nimport type { Path } from '@weasel-js/geom';\nimport { poseLinearOf, worldEditToStorage, type PathInWorldPose } from '@weasel-js/geom';\nimport type { PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport type { Node, Scene } from 'core/scene/types';\n\n/** A path leaf's world pose, and the box, rotation, and transform its path is drawn through. */\nexport interface DrawnPose {\n world: unknown;\n g: PoseDescriptor<unknown>;\n box: PathInWorldPose;\n}\n\n/** How `g` draws a leaf at `world`, or null for a pose it reads no finite box from. */\nexport function drawnPose(g: PoseDescriptor<unknown>, world: unknown): DrawnPose | null {\n const b = g.getBounds(world);\n if (!b || ![b.x, b.y, b.width, b.height].every(Number.isFinite)) return null;\n const rotation = g.getRotation?.(world) ?? 0;\n const transform = poseLinearOf(world) ?? undefined;\n return { world, g, box: { x: b.x, y: b.y, width: b.width, height: b.height, rotation, transform } };\n}\n\n/** A world-space piece as a world pose of the source's kind plus the path it draws. */\nexport function storePiece({ world, g, box }: DrawnPose, piece: Path): { pose: unknown; path: Path } {\n const keepsRotation = !!g.withRotation && g.supportsRotation?.(world) !== false;\n const rotation = box.rotation ?? 0;\n if (rotation !== 0 && !keepsRotation) {\n const baked = worldEditToStorage({ ...box, rotation: 0 }, piece);\n return { pose: g.fromBounds(baked.pose, world), path: baked.path };\n }\n const { pose: b, path } = worldEditToStorage(box, piece);\n const dst = { x: b.x, y: b.y, width: b.width, height: b.height };\n const pose = g.remapBounds(world, g.getBounds(world), dst);\n return { pose: rotation !== 0 ? g.withRotation!(pose, rotation) : pose, path };\n}\n\n/** The path a leaf stores, or null for one that is not a free path leaf: a\n * group, a locked leaf, one whose geometry derives from other nodes. */\nexport function leafPath(scene: Scene<unknown, string, unknown>, node: Node<unknown, string, unknown>): Path | null {\n if (node.kind !== '
|
|
1
|
+
{"version":3,"file":"pathLeaf.js","names":[],"sources":["../../../src/canvas/deps/pathLeaf.ts"],"sourcesContent":["/**\n * Path leaves as the kit's own path deps read and write them: geometry in\n * `data.path`, drawn through the box its pose describes.\n */\nimport type { Path } from '@weasel-js/geom';\nimport { poseLinearOf, worldEditToStorage, type PathInWorldPose } from '@weasel-js/geom';\nimport type { PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport type { Node, Scene } from 'core/scene/types';\n\n/** A path leaf's world pose, and the box, rotation, and transform its path is drawn through. */\nexport interface DrawnPose {\n world: unknown;\n g: PoseDescriptor<unknown>;\n box: PathInWorldPose;\n}\n\n/** How `g` draws a leaf at `world`, or null for a pose it reads no finite box from. */\nexport function drawnPose(g: PoseDescriptor<unknown>, world: unknown): DrawnPose | null {\n const b = g.getBounds(world);\n if (!b || ![b.x, b.y, b.width, b.height].every(Number.isFinite)) return null;\n const rotation = g.getRotation?.(world) ?? 0;\n const transform = poseLinearOf(world) ?? undefined;\n return { world, g, box: { x: b.x, y: b.y, width: b.width, height: b.height, rotation, transform } };\n}\n\n/** A world-space piece as a world pose of the source's kind plus the path it draws. */\nexport function storePiece({ world, g, box }: DrawnPose, piece: Path): { pose: unknown; path: Path } {\n const keepsRotation = !!g.withRotation && g.supportsRotation?.(world) !== false;\n const rotation = box.rotation ?? 0;\n if (rotation !== 0 && !keepsRotation) {\n const baked = worldEditToStorage({ ...box, rotation: 0 }, piece);\n return { pose: g.fromBounds(baked.pose, world), path: baked.path };\n }\n const { pose: b, path } = worldEditToStorage(box, piece);\n const dst = { x: b.x, y: b.y, width: b.width, height: b.height };\n const pose = g.remapBounds(world, g.getBounds(world), dst);\n return { pose: rotation !== 0 ? g.withRotation!(pose, rotation) : pose, path };\n}\n\n/** The path a leaf stores, or null for one that is not a free path leaf: a\n * group, a locked leaf, one whose geometry derives from other nodes. */\nexport function leafPath(scene: Scene<unknown, string, unknown>, node: Node<unknown, string, unknown>): Path | null {\n if (node.kind !== 'mark' || node.derivePath || node.derivePose) return null;\n if (scene.isLocked(node.id)) return null;\n const path = (node.data as { path?: unknown } | null)?.path as Path | undefined;\n return path && (path.kind === 'rect' || path.kind === 'polygon') ? path : null;\n}\n"],"mappings":";;;;AAiBA,SAAgB,UAAU,GAA4B,OAAkC;CACtF,MAAM,IAAI,EAAE,UAAU,KAAK;CAC3B,IAAI,CAAC,KAAK,CAAC;EAAC,EAAE;EAAG,EAAE;EAAG,EAAE;EAAO,EAAE;CAAM,CAAC,CAAC,MAAM,OAAO,QAAQ,GAAG,OAAO;CACxE,MAAM,WAAW,EAAE,cAAc,KAAK,KAAK;CAC3C,MAAM,YAAY,aAAa,KAAK,KAAK;CACzC,OAAO;EAAE;EAAO;EAAG,KAAK;GAAE,GAAG,EAAE;GAAG,GAAG,EAAE;GAAG,OAAO,EAAE;GAAO,QAAQ,EAAE;GAAQ;GAAU;EAAU;CAAE;AACpG;;AAGA,SAAgB,WAAW,EAAE,OAAO,GAAG,OAAkB,OAA4C;CACnG,MAAM,gBAAgB,CAAC,CAAC,EAAE,gBAAgB,EAAE,mBAAmB,KAAK,MAAM;CAC1E,MAAM,WAAW,IAAI,YAAY;CACjC,IAAI,aAAa,KAAK,CAAC,eAAe;EACpC,MAAM,QAAQ,mBAAmB;GAAE,GAAG;GAAK,UAAU;EAAE,GAAG,KAAK;EAC/D,OAAO;GAAE,MAAM,EAAE,WAAW,MAAM,MAAM,KAAK;GAAG,MAAM,MAAM;EAAK;CACnE;CACA,MAAM,EAAE,MAAM,GAAG,SAAS,mBAAmB,KAAK,KAAK;CACvD,MAAM,MAAM;EAAE,GAAG,EAAE;EAAG,GAAG,EAAE;EAAG,OAAO,EAAE;EAAO,QAAQ,EAAE;CAAO;CAC/D,MAAM,OAAO,EAAE,YAAY,OAAO,EAAE,UAAU,KAAK,GAAG,GAAG;CACzD,OAAO;EAAE,MAAM,aAAa,IAAI,EAAE,aAAc,MAAM,QAAQ,IAAI;EAAM;CAAK;AAC/E;;;AAIA,SAAgB,SAAS,OAAwC,MAAmD;CAClH,IAAI,KAAK,SAAS,UAAU,KAAK,cAAc,KAAK,YAAY,OAAO;CACvE,IAAI,MAAM,SAAS,KAAK,EAAE,GAAG,OAAO;CACpC,MAAM,OAAQ,KAAK,MAAoC;CACvD,OAAO,SAAS,KAAK,SAAS,UAAU,KAAK,SAAS,aAAa,OAAO;AAC5E"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { documentOf } from "../../core/scene/documentOf.js";
|
|
1
2
|
import { poseDescriptorForNode } from "../../core/geometry/poseDescriptor.js";
|
|
2
3
|
import { AUTO_POSE_DESCRIPTOR } from "../../core/geometry/autoPoseDescriptor.js";
|
|
3
4
|
import { scenePoseFrame } from "../../interactions/actions/poseFrame.js";
|
|
@@ -39,7 +40,7 @@ function useSliceDepSource(scene, selection, adapter, poseDescriptor) {
|
|
|
39
40
|
const adapterRef = useLatest(adapter);
|
|
40
41
|
const descriptorRef = useLatest(poseDescriptor);
|
|
41
42
|
useDepSource("slice", () => ({ commit(cut) {
|
|
42
|
-
const sc = sceneRef.current;
|
|
43
|
+
const sc = documentOf(sceneRef.current);
|
|
43
44
|
const frame = scenePoseFrame(sc);
|
|
44
45
|
const descriptor = descriptorRef.current ?? AUTO_POSE_DESCRIPTOR;
|
|
45
46
|
const leaves = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slice.js","names":[],"sources":["../../../src/canvas/deps/slice.ts"],"sourcesContent":["/**\n * The `slice` dep `sliceAction` and `useSliceTool` commit their cuts through.\n *\n * `<SceneCanvas>` publishes `useSliceDepSource` over its own scene. A consumer\n * replaces it with `useSliceDep` (or `useDepSource('slice', …)`) from under\n * the canvas; the newest registration wins, and a canvas child registers\n * after the canvas's own.\n */\nimport { useDepSource } from '@weasel-js/routing/react';\nimport { useLatest } from '@weasel-js/react';\nimport type { SliceDep } from 'interactions/actions/depSchema';\nimport type { Scene, NodeId, Node } from 'core/scene/types';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport type { Op } from 'core/ops/types';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport { poseDescriptorForNode, type PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport { pathInWorld } from '@weasel-js/geom';\nimport { drawnPose, leafPath, storePiece, type DrawnPose } from './pathLeaf';\nimport { scenePoseFrame } from 'interactions/actions/poseFrame';\nimport { computeSliceOps, type SliceLeaf } from 'interactions/actions/defaults/sliceOps';\n\n/** Publish how a slice (knife cut) is performed, so the `slice` action can\n * run against the consumer's geometry. */\nexport function useSliceDep(dep: SliceDep): void {\n const depRef = useLatest(dep);\n\n useDepSource('slice', (): SliceDep => depRef.current);\n}\n\n/** What the `slice` dep writes its batch through. An adapter satisfies it. */\nexport interface OpsApplier {\n applyOps(ops: Op[], label?: string): void;\n}\n\n/**\n * The kit `slice` dep: cut every leaf the cut crosses and swap it for its\n * pieces in one undoable batch.\n *\n * A leaf is cut when its `data.path` is a `Path` and `poseDescriptor` can read\n * its world pose as a box and a rotation; a leaf on a locked layer, or one\n * whose geometry derives from other nodes, is passed over. A piece keeps the\n * source's rotation in its pose when the descriptor can write one\n * (`withRotation`), and has the rotation baked into its path otherwise. The\n * pieces of a selected leaf take its place in the selection.\n */\nexport function useSliceDepSource(\n scene: Scene<unknown, string, unknown>,\n selection: SelectionApi,\n adapter: OpsApplier,\n poseDescriptor?: PoseDescriptor<unknown>,\n): void {\n const sceneRef = useLatest(scene);\n const selectionRef = useLatest(selection);\n const adapterRef = useLatest(adapter);\n const descriptorRef = useLatest(poseDescriptor);\n\n useDepSource('slice', (): SliceDep => ({\n commit(cut) {\n const sc = sceneRef.current;\n const frame = scenePoseFrame(sc);\n const descriptor = descriptorRef.current ?? AUTO_POSE_DESCRIPTOR;\n const leaves: SliceLeaf<Node<unknown, string, unknown>>[] = [];\n const drawnAt = new Map<string, DrawnPose>();\n for (const node of sc.renderOrderNodes()) {\n if (sc.isHidden(node.id)) continue;\n const path = leafPath(sc, node);\n if (!path) continue;\n const drawn = drawnPose(poseDescriptorForNode(descriptor, node), frame.world(node.id));\n if (!drawn) continue;\n drawnAt.set(node.id, drawn);\n leaves.push({ node, index: siblingIndex(sc, node), worldPath: pathInWorld(path, drawn.box) });\n }\n\n const sel = selectionRef.current;\n const { ops, nextSelection } = computeSliceOps({\n leaves,\n cut,\n nextId: () => sc.mintId(),\n selection: sel.get(),\n placePiece: (leaf, piece) => {\n const stored = storePiece(drawnAt.get(leaf.node.id)!, piece);\n return { pose: frame.local(leaf.node.id, stored.pose), path: stored.path };\n },\n });\n if (ops.length === 0) return;\n adapterRef.current.applyOps(ops, 'Slice');\n if (nextSelection) sel.set(nextSelection as NodeId[]);\n },\n }));\n}\n\nfunction siblingIndex(scene: Scene<unknown, string, unknown>, node: Node<unknown, string, unknown>): number {\n const siblings = node.parent == null ? scene.roots : scene.childrenOf(node.parent);\n return siblings.indexOf(node.id);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"slice.js","names":[],"sources":["../../../src/canvas/deps/slice.ts"],"sourcesContent":["/**\n * The `slice` dep `sliceAction` and `useSliceTool` commit their cuts through.\n *\n * `<SceneCanvas>` publishes `useSliceDepSource` over its own scene. A consumer\n * replaces it with `useSliceDep` (or `useDepSource('slice', …)`) from under\n * the canvas; the newest registration wins, and a canvas child registers\n * after the canvas's own.\n */\nimport { useDepSource } from '@weasel-js/routing/react';\nimport { useLatest } from '@weasel-js/react';\nimport type { SliceDep } from 'interactions/actions/depSchema';\nimport type { Scene, NodeId, Node } from 'core/scene/types';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport type { Op } from 'core/ops/types';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport { poseDescriptorForNode, type PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport { pathInWorld } from '@weasel-js/geom';\nimport { drawnPose, leafPath, storePiece, type DrawnPose } from './pathLeaf';\nimport { scenePoseFrame } from 'interactions/actions/poseFrame';\nimport { documentOf } from 'core/scene/documentOf';\nimport { computeSliceOps, type SliceLeaf } from 'interactions/actions/defaults/sliceOps';\n\n/** Publish how a slice (knife cut) is performed, so the `slice` action can\n * run against the consumer's geometry. */\nexport function useSliceDep(dep: SliceDep): void {\n const depRef = useLatest(dep);\n\n useDepSource('slice', (): SliceDep => depRef.current);\n}\n\n/** What the `slice` dep writes its batch through. An adapter satisfies it. */\nexport interface OpsApplier {\n applyOps(ops: Op[], label?: string): void;\n}\n\n/**\n * The kit `slice` dep: cut every leaf the cut crosses and swap it for its\n * pieces in one undoable batch.\n *\n * A leaf is cut when its `data.path` is a `Path` and `poseDescriptor` can read\n * its world pose as a box and a rotation; a leaf on a locked layer, or one\n * whose geometry derives from other nodes, is passed over. A piece keeps the\n * source's rotation in its pose when the descriptor can write one\n * (`withRotation`), and has the rotation baked into its path otherwise. The\n * pieces of a selected leaf take its place in the selection.\n */\nexport function useSliceDepSource(\n scene: Scene<unknown, string, unknown>,\n selection: SelectionApi,\n adapter: OpsApplier,\n poseDescriptor?: PoseDescriptor<unknown>,\n): void {\n const sceneRef = useLatest(scene);\n const selectionRef = useLatest(selection);\n const adapterRef = useLatest(adapter);\n const descriptorRef = useLatest(poseDescriptor);\n\n useDepSource('slice', (): SliceDep => ({\n commit(cut) {\n // A commit cuts what the document holds, not what an open gesture or edit is showing.\n const sc = documentOf(sceneRef.current);\n const frame = scenePoseFrame(sc);\n const descriptor = descriptorRef.current ?? AUTO_POSE_DESCRIPTOR;\n const leaves: SliceLeaf<Node<unknown, string, unknown>>[] = [];\n const drawnAt = new Map<string, DrawnPose>();\n for (const node of sc.renderOrderNodes()) {\n if (sc.isHidden(node.id)) continue;\n const path = leafPath(sc, node);\n if (!path) continue;\n const drawn = drawnPose(poseDescriptorForNode(descriptor, node), frame.world(node.id));\n if (!drawn) continue;\n drawnAt.set(node.id, drawn);\n leaves.push({ node, index: siblingIndex(sc, node), worldPath: pathInWorld(path, drawn.box) });\n }\n\n const sel = selectionRef.current;\n const { ops, nextSelection } = computeSliceOps({\n leaves,\n cut,\n nextId: () => sc.mintId(),\n selection: sel.get(),\n placePiece: (leaf, piece) => {\n const stored = storePiece(drawnAt.get(leaf.node.id)!, piece);\n return { pose: frame.local(leaf.node.id, stored.pose), path: stored.path };\n },\n });\n if (ops.length === 0) return;\n adapterRef.current.applyOps(ops, 'Slice');\n if (nextSelection) sel.set(nextSelection as NodeId[]);\n },\n }));\n}\n\nfunction siblingIndex(scene: Scene<unknown, string, unknown>, node: Node<unknown, string, unknown>): number {\n const siblings = node.parent == null ? scene.roots : scene.childrenOf(node.parent);\n return siblings.indexOf(node.id);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAwBA,SAAgB,YAAY,KAAqB;CAC/C,MAAM,SAAS,UAAU,GAAG;CAE5B,aAAa,eAAyB,OAAO,OAAO;AACtD;;;;;;;;;;;;AAkBA,SAAgB,kBACd,OACA,WACA,SACA,gBACM;CACN,MAAM,WAAW,UAAU,KAAK;CAChC,MAAM,eAAe,UAAU,SAAS;CACxC,MAAM,aAAa,UAAU,OAAO;CACpC,MAAM,gBAAgB,UAAU,cAAc;CAE9C,aAAa,gBAA0B,EACrC,OAAO,KAAK;EAEV,MAAM,KAAK,WAAW,SAAS,OAAO;EACtC,MAAM,QAAQ,eAAe,EAAE;EAC/B,MAAM,aAAa,cAAc,WAAW;EAC5C,MAAM,SAAsD,CAAC;EAC7D,MAAM,0BAAU,IAAI,IAAuB;EAC3C,KAAK,MAAM,QAAQ,GAAG,iBAAiB,GAAG;GACxC,IAAI,GAAG,SAAS,KAAK,EAAE,GAAG;GAC1B,MAAM,OAAO,SAAS,IAAI,IAAI;GAC9B,IAAI,CAAC,MAAM;GACX,MAAM,QAAQ,UAAU,sBAAsB,YAAY,IAAI,GAAG,MAAM,MAAM,KAAK,EAAE,CAAC;GACrF,IAAI,CAAC,OAAO;GACZ,QAAQ,IAAI,KAAK,IAAI,KAAK;GAC1B,OAAO,KAAK;IAAE;IAAM,OAAO,aAAa,IAAI,IAAI;IAAG,WAAW,YAAY,MAAM,MAAM,GAAG;GAAE,CAAC;EAC9F;EAEA,MAAM,MAAM,aAAa;EACzB,MAAM,EAAE,KAAK,kBAAkB,gBAAgB;GAC7C;GACA;GACA,cAAc,GAAG,OAAO;GACxB,WAAW,IAAI,IAAI;GACnB,aAAa,MAAM,UAAU;IAC3B,MAAM,SAAS,WAAW,QAAQ,IAAI,KAAK,KAAK,EAAE,GAAI,KAAK;IAC3D,OAAO;KAAE,MAAM,MAAM,MAAM,KAAK,KAAK,IAAI,OAAO,IAAI;KAAG,MAAM,OAAO;IAAK;GAC3E;EACF,CAAC;EACD,IAAI,IAAI,WAAW,GAAG;EACtB,WAAW,QAAQ,SAAS,KAAK,OAAO;EACxC,IAAI,eAAe,IAAI,IAAI,aAAyB;CACtD,EACF,EAAE;AACJ;AAEA,SAAS,aAAa,OAAwC,MAA8C;CAE1G,QADiB,KAAK,UAAU,OAAO,MAAM,QAAQ,MAAM,WAAW,KAAK,MAAM,EAC1E,CAAS,QAAQ,KAAK,EAAE;AACjC"}
|
|
@@ -2,15 +2,18 @@ import { composeWorldPose, composesFrames } from "../core/scene/composeFrame.js"
|
|
|
2
2
|
import { carryBoxPath, carryPathBy, pathAsPainted } from "../core/scene/depFrame.js";
|
|
3
3
|
import { createNodeMemoScope } from "../core/scene/nodeMemo.js";
|
|
4
4
|
import { resolveDerivedPath } from "../core/scene/derivedPath.js";
|
|
5
|
+
import { inheritedPaintIn } from "../core/scene/inheritedPaint.js";
|
|
6
|
+
import { nodeAsShown, shownLookup } from "../core/scene/nodeAsShown.js";
|
|
5
7
|
import { definesFrame, derivedDepOf, effectivePose } from "../core/scene/effectivePose.js";
|
|
6
8
|
import { asNodeId } from "../core/scene/types.js";
|
|
7
9
|
import { multiply, poseFrameInverseOf, poseFrameOf } from "@weasel-js/geom";
|
|
8
10
|
|
|
9
11
|
//#region src/canvas/derivedPath.ts
|
|
10
|
-
/** `node` as the scene shows it, its `pose` an override's or a derived one
|
|
11
|
-
* itself where
|
|
12
|
-
function shownNode(scene,
|
|
13
|
-
const
|
|
12
|
+
/** `node` as the scene shows it, its `pose` an override's or a derived one and its `data` an
|
|
13
|
+
* override's: the stored node itself where they agree. */
|
|
14
|
+
function shownNode(scene, stored) {
|
|
15
|
+
const node = nodeAsShown(scene.overrides, stored);
|
|
16
|
+
const pose = effectivePose(scene, stored);
|
|
14
17
|
return pose === node.pose ? node : {
|
|
15
18
|
...node,
|
|
16
19
|
pose
|
|
@@ -74,9 +77,10 @@ function sceneDepLookup(scene, toPose) {
|
|
|
74
77
|
slot,
|
|
75
78
|
shown: sceneOverridePathOf(scene, toPose),
|
|
76
79
|
childrenOf: (id) => scene.childrenOf(id),
|
|
80
|
+
scene: shownLookup(scene),
|
|
77
81
|
lookup: (id) => {
|
|
78
82
|
const node = scene.get(id);
|
|
79
|
-
return node === void 0 ? void 0 : new SlotDep(node, ctx);
|
|
83
|
+
return node === void 0 ? void 0 : new SlotDep(nodeAsShown(scene.overrides, node), ctx);
|
|
80
84
|
}
|
|
81
85
|
};
|
|
82
86
|
return ctx.lookup;
|
|
@@ -97,6 +101,9 @@ var SlotDep = class {
|
|
|
97
101
|
if (shown !== void 0) return pathAsPainted(shown, this.ctx.local(this.node), this.pose);
|
|
98
102
|
return resolveDerivedPath(this.node, this.ctx.lookup, this.ctx.childrenOf, this.ctx.slot);
|
|
99
103
|
}
|
|
104
|
+
get inherited() {
|
|
105
|
+
return inheritedPaintIn(this.ctx.scene, this.node);
|
|
106
|
+
}
|
|
100
107
|
};
|
|
101
108
|
/** Whether something outside `node`'s data says what path it shows: it derives one, or a
|
|
102
109
|
* pose override carries one. The gate both scene walks check before resolving a path. */
|
|
@@ -181,8 +188,9 @@ function drawnBoundsScene(scene) {
|
|
|
181
188
|
let held = drawnScenes.get(scene);
|
|
182
189
|
if (held === void 0) {
|
|
183
190
|
const pathOf = sceneDerivedPathOf(scene);
|
|
191
|
+
const shown = shownLookup(scene);
|
|
184
192
|
held = {
|
|
185
|
-
get: (id) =>
|
|
193
|
+
get: (id) => shown.get(id),
|
|
186
194
|
childrenOf: (id) => scene.childrenOf(id),
|
|
187
195
|
derivedPathOf: (id) => {
|
|
188
196
|
const node = scene.get(id);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derivedPath.js","names":[],"sources":["../../src/canvas/derivedPath.ts"],"sourcesContent":["/**\n * The render walk's half of derived paths: resolving one against a scene, and\n * handing it to the painter.\n *\n * The resolver itself lives in `core/scene/derivedPath.ts`, beside\n * `derivedPose` — a *pose* can derive from a dependency's path, so\n * `effectivePose` has to be able to reach it.\n */\nimport type { DerivedDep, Node, NodeId, Scene } from 'core/scene/types';\nimport { multiply, poseFrameInverseOf, poseFrameOf, type Path } from '@weasel-js/geom';\nimport { asNodeId } from 'core/scene/types';\nimport { definesFrame, derivedDepOf, effectivePose } from 'core/scene/effectivePose';\nimport { composesFrames, composeWorldPose, type PoseAdapter } from 'core/scene/composeFrame';\nimport { resolveDerivedPath } from 'core/scene/derivedPath';\nimport { createNodeMemoScope, type NodeMemoScope } from 'core/scene/nodeMemo';\nimport { carryBoxPath, carryPathBy, pathAsPainted } from 'core/scene/depFrame';\nimport type { SceneViewDrawOne } from './NodeShape';\nimport type { DrawnBoundsScene } from './drawnBounds';\n\nexport { resolveDerivedPath } from 'core/scene/derivedPath';\n\ntype ToPose<TData, TLayer extends string, TPose> = (node: Node<TData, TLayer, TPose>) => TPose;\n\n/** `node` as the scene shows it, its `pose` an override's or a derived one: the stored node\n * itself where the two agree. */\nfunction shownNode<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n node: Node<TData, TLayer, TPose>,\n): Node<TData, TLayer, TPose> {\n const pose = effectivePose(scene, node);\n return pose === node.pose ? node : { ...node, pose };\n}\n\n/** A slot's `toPose`, handed each node as the scene shows it, so a view drawn through one\n * shows a drag in flight and a rig's motion as every other view does. */\nexport function sceneShownToPose<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose: ToPose<TData, TLayer, TPose>,\n): ToPose<TData, TLayer, TPose> {\n return (node) => toPose(shownNode(scene, node));\n}\n\n/**\n * `(node) => the path its pose override shows it by`, in the frame the reader paints in: as\n * the override has it, or under a `toPose` carried from the box the scene shows the node at\n * to the box `toPose` answers.\n */\nfunction sceneOverridePathOf<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose?: ToPose<TData, TLayer, TPose>,\n): (node: Node<TData, TLayer, TPose>) => Path | undefined {\n if (toPose === undefined) return (node) => scene.overrides.read(node.id)?.path;\n return (node) => {\n const path = scene.overrides.read(node.id)?.path;\n if (path === undefined) return undefined;\n const shown = shownNode(scene, node);\n return carryBoxPath(path, shown.pose, toPose(shown));\n };\n}\n\n/**\n * `(node) => the pose it is painted at`: the slot's `toPose` of the node as the scene shows\n * it if it has one, else the scene's effective pose, folded through the parent chain when\n * the scene's composition makes a stored pose local to its container.\n */\nexport function scenePaintedPoseOf<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose?: (node: Node<TData, TLayer, TPose>) => TPose,\n): (node: Node<TData, TLayer, TPose>) => TPose {\n const local = toPose\n ? sceneShownToPose(scene, toPose)\n : (node: Node<TData, TLayer, TPose>) => effectivePose(scene, node);\n if (!composesFrames(scene.composition)) return local;\n const chain: PoseAdapter<TPose> = {\n getPose: (id) => {\n const n = scene.get(asNodeId(id));\n return n ? local(n) : (undefined as unknown as TPose);\n },\n getParent: (id) => scene.get(asNodeId(id))?.parent ?? null,\n definesFrame: (id) => {\n const n = scene.get(asNodeId(id));\n return n === undefined || definesFrame(n);\n },\n };\n return (node) => composeWorldPose(chain, node.id, scene.composition.compose);\n}\n\n/**\n * `(id) => that node, the pose it is painted at, and the path it derives` —\n * resolved against the scene, so a dependency that is itself derived resolves\n * before it is read.\n *\n * A derived edge is drawn from these coordinates and must meet the nodes it\n * connects, so this reads the pose each one is painted at: see\n * {@link scenePaintedPoseOf}.\n */\nexport function sceneDepLookup<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose?: (node: Node<TData, TLayer, TPose>) => TPose,\n): (id: NodeId) => DerivedDep<TPose> | undefined {\n const slot = memoSlotFor(toPose, composesFrames(scene.composition));\n if (slot === undefined) return (id) => derivedDepOf(scene, id);\n const ctx: SlotLookup<TPose> = {\n at: scenePaintedPoseOf(scene, toPose) as (node: unknown) => TPose,\n local: (toPose\n ? sceneShownToPose(scene, toPose)\n : (node: Node<TData, TLayer, TPose>) => effectivePose(scene, node)) as (node: unknown) => TPose,\n slot,\n shown: sceneOverridePathOf(scene, toPose) as (node: unknown) => Path | undefined,\n childrenOf: (id: NodeId) => scene.childrenOf(id),\n lookup: (id: NodeId) => {\n const node = scene.get(id);\n return node === undefined ? undefined : new SlotDep(node, ctx);\n },\n };\n return ctx.lookup;\n}\n\ninterface SlotLookup<TPose> {\n at: (node: unknown) => TPose;\n /** The pose the node is shown at in its own frame: the box its override path is drawn in. */\n local: (node: unknown) => TPose;\n slot: string | NodeMemoScope;\n /** The path a pose override shows a node by, where it has one. */\n shown: (node: unknown) => Path | undefined;\n childrenOf: (id: NodeId) => readonly NodeId[];\n lookup: (id: NodeId) => DerivedDep<TPose> | undefined;\n}\n\n/** A class, not an object literal with a `get path()` — see `SceneDep` in\n * `core/scene/effectivePose.ts` for why. */\nclass SlotDep<TPose> implements DerivedDep<TPose> {\n readonly node: Node<unknown, string, TPose>;\n readonly pose: TPose;\n\n constructor(posed: unknown, private readonly ctx: SlotLookup<TPose>) {\n this.node = posed as Node<unknown, string, TPose>;\n this.pose = ctx.at(posed);\n }\n\n get path(): Path | null {\n const shown = this.ctx.shown(this.node);\n if (shown !== undefined) return pathAsPainted(shown, this.ctx.local(this.node), this.pose);\n return resolveDerivedPath(\n this.node as never, this.ctx.lookup, this.ctx.childrenOf, this.ctx.slot,\n );\n }\n}\n\n/** Whether something outside `node`'s data says what path it shows: it derives one, or a\n * pose override carries one. The gate both scene walks check before resolving a path. */\nexport function showsComputedPath<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n node: { id: NodeId; dependsOn?: readonly NodeId[] | 'children' },\n): boolean {\n return node.dependsOn !== undefined || scene.overrides.read(node.id)?.path !== undefined;\n}\n\n/**\n * `(node) => the path it shows in place of its data's`: a pose override's `path`, else the\n * one it derives, read through {@link sceneDepLookup}. A reader painting through a `toPose`\n * is answered the override's path carried into the box its `toPose` answers.\n */\nexport function sceneDerivedPathOf<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose?: (node: Node<TData, TLayer, TPose>) => TPose,\n): (node: Node<TData, TLayer, TPose>) => Path | null {\n const depOf = sceneDepLookup(scene, toPose);\n const slot = memoSlotFor(toPose, composesFrames(scene.composition));\n const childrenOf = (id: NodeId): readonly NodeId[] => scene.childrenOf(id);\n const derived = (node: Node<TData, TLayer, TPose>): Path | null =>\n resolveDerivedPath(node, depOf, childrenOf, slot);\n const overridePathOf = sceneOverridePathOf(scene, toPose);\n return (node) => overridePathOf(node) ?? derived(node);\n}\n\nconst toPoseScopes = new WeakMap<object, { local?: NodeMemoScope; world?: NodeMemoScope }>();\n\n/** Where one way of reading poses memoizes its paths, or `undefined` for the\n * scene's own slot: two readers painting one scene at different poses must\n * not serve each other's paths. Every reader composing stored poses shares a\n * slot; a `toPose` gets a scope that is collected with the function. */\nfunction memoSlotFor(\n toPose: object | undefined,\n composes: boolean,\n): string | NodeMemoScope | undefined {\n const frame = composes ? 'world' : 'local';\n if (toPose === undefined) return frame === 'world' ? 'kit:derivedPath:world' : undefined;\n let scopes = toPoseScopes.get(toPose);\n if (scopes === undefined) {\n scopes = {};\n toPoseScopes.set(toPose, scopes);\n }\n return (scopes[frame] ??= createNodeMemoScope());\n}\n\n/**\n * Wrap a `drawOne` so a node that derives its geometry, or whose pose override\n * carries a path, arrives at the painter with the resolved path. The painter has no scene handle and so cannot read\n * the dependencies' poses itself; both scene walks wrap here instead.\n *\n * A node that derives nothing passes the caller's `ctx` through untouched —\n * this runs per node per frame, and an added `{ derivedPath: null }` would be\n * an allocation per node for a field no painter reads.\n */\nexport function withDerivedPaths<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n drawOne: SceneViewDrawOne<TData, TLayer, TPose>,\n toPose?: (node: Node<TData, TLayer, TPose>) => TPose,\n): SceneViewDrawOne<TData, TLayer, TPose> {\n const derivedPathOf = sceneDerivedPathOf(scene, toPose);\n const ownPoseOf = composesFrames(scene.composition)\n ? (toPose ? sceneShownToPose(scene, toPose) : (node: Node<TData, TLayer, TPose>) => effectivePose(scene, node))\n : null;\n return (node, pose, view, ctx) => {\n if (!showsComputedPath(scene, node)) return drawOne(node, pose, view, ctx);\n let path = derivedPathOf(node);\n if (path !== null && ownPoseOf !== null && scene.overrides.read(node.id)?.path === undefined) {\n path = withoutInheritedFrame(path, pose, ownPoseOf(node));\n }\n return drawOne(node, pose, view, { ...ctx, derivedPath: path });\n };\n}\n\n/**\n * A derived path is drawn from its dependencies' painted poses, so it is already where it\n * shows; but the pose rotation the walk wraps every node in is `painted`'s, which carries its\n * containers' turns as well as its own. Carried back through what the containers add, the\n * wrap turns it by its own frame alone, as it does a root's.\n */\nfunction withoutInheritedFrame(path: Path, painted: unknown, own: unknown): Path {\n const undo = poseFrameInverseOf(painted);\n if (undo === null) return path;\n const box = painted as { x: number; y: number; width: number; height: number };\n const o = own as { rotation?: number; transform?: unknown } | null;\n const redo = poseFrameOf({ x: box.x, y: box.y, width: box.width, height: box.height, rotation: o?.rotation, transform: o?.transform });\n return carryPathBy(path, redo === null ? undo : multiply(undo, redo));\n}\n\nconst drawnScenes = new WeakMap<object, DrawnBoundsScene>();\n\n/** `scene` read as `drawnBoundsOf` reads one, each node that shows a computed\n * path answering it. One object per scene, so it can key a memo. */\nexport function drawnBoundsScene<TData, TLayer extends string, TPose>(scene: Scene<TData, TLayer, TPose>): DrawnBoundsScene {\n let held = drawnScenes.get(scene);\n if (held === undefined) {\n const pathOf = sceneDerivedPathOf(scene);\n held = {\n get: (id) => scene.get(id) as never,\n childrenOf: (id) => scene.childrenOf(id),\n derivedPathOf: (id) => {\n const node = scene.get(id);\n return node !== undefined && showsComputedPath(scene, node) ? pathOf(node) : null;\n },\n };\n drawnScenes.set(scene, held);\n }\n return held;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"derivedPath.js","names":[],"sources":["../../src/canvas/derivedPath.ts"],"sourcesContent":["/**\n * The render walk's half of derived paths: resolving one against a scene, and\n * handing it to the painter.\n *\n * The resolver itself lives in `core/scene/derivedPath.ts`, beside\n * `derivedPose` — a *pose* can derive from a dependency's path, so\n * `effectivePose` has to be able to reach it.\n */\nimport type { DerivedDep, Node, NodeId, Scene } from 'core/scene/types';\nimport { multiply, poseFrameInverseOf, poseFrameOf, type Path } from '@weasel-js/geom';\nimport { asNodeId } from 'core/scene/types';\nimport { definesFrame, derivedDepOf, effectivePose } from 'core/scene/effectivePose';\nimport { composesFrames, composeWorldPose, type PoseAdapter } from 'core/scene/composeFrame';\nimport { resolveDerivedPath } from 'core/scene/derivedPath';\nimport { inheritedPaintIn, type InheritedPaint } from 'core/scene/inheritedPaint';\nimport { createNodeMemoScope, type NodeMemoScope } from 'core/scene/nodeMemo';\nimport { carryBoxPath, carryPathBy, pathAsPainted } from 'core/scene/depFrame';\nimport type { SceneViewDrawOne } from './NodeShape';\nimport type { DrawnBoundsScene } from './drawnBounds';\nimport { nodeAsShown, shownLookup } from 'core/scene/nodeAsShown';\n\nexport { resolveDerivedPath } from 'core/scene/derivedPath';\n\ntype ToPose<TData, TLayer extends string, TPose> = (node: Node<TData, TLayer, TPose>) => TPose;\n\n/** `node` as the scene shows it, its `pose` an override's or a derived one and its `data` an\n * override's: the stored node itself where they agree. */\nfunction shownNode<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n stored: Node<TData, TLayer, TPose>,\n): Node<TData, TLayer, TPose> {\n const node = nodeAsShown(scene.overrides, stored);\n const pose = effectivePose(scene, stored);\n return pose === node.pose ? node : { ...node, pose };\n}\n\n/** A slot's `toPose`, handed each node as the scene shows it, so a view drawn through one\n * shows a drag in flight and a rig's motion as every other view does. */\nexport function sceneShownToPose<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose: ToPose<TData, TLayer, TPose>,\n): ToPose<TData, TLayer, TPose> {\n return (node) => toPose(shownNode(scene, node));\n}\n\n/**\n * `(node) => the path its pose override shows it by`, in the frame the reader paints in: as\n * the override has it, or under a `toPose` carried from the box the scene shows the node at\n * to the box `toPose` answers.\n */\nfunction sceneOverridePathOf<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose?: ToPose<TData, TLayer, TPose>,\n): (node: Node<TData, TLayer, TPose>) => Path | undefined {\n if (toPose === undefined) return (node) => scene.overrides.read(node.id)?.path;\n return (node) => {\n const path = scene.overrides.read(node.id)?.path;\n if (path === undefined) return undefined;\n const shown = shownNode(scene, node);\n return carryBoxPath(path, shown.pose, toPose(shown));\n };\n}\n\n/**\n * `(node) => the pose it is painted at`: the slot's `toPose` of the node as the scene shows\n * it if it has one, else the scene's effective pose, folded through the parent chain when\n * the scene's composition makes a stored pose local to its container.\n */\nexport function scenePaintedPoseOf<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose?: (node: Node<TData, TLayer, TPose>) => TPose,\n): (node: Node<TData, TLayer, TPose>) => TPose {\n const local = toPose\n ? sceneShownToPose(scene, toPose)\n : (node: Node<TData, TLayer, TPose>) => effectivePose(scene, node);\n if (!composesFrames(scene.composition)) return local;\n const chain: PoseAdapter<TPose> = {\n getPose: (id) => {\n const n = scene.get(asNodeId(id));\n return n ? local(n) : (undefined as unknown as TPose);\n },\n getParent: (id) => scene.get(asNodeId(id))?.parent ?? null,\n definesFrame: (id) => {\n const n = scene.get(asNodeId(id));\n return n === undefined || definesFrame(n);\n },\n };\n return (node) => composeWorldPose(chain, node.id, scene.composition.compose);\n}\n\n/**\n * `(id) => that node, the pose it is painted at, and the path it derives` —\n * resolved against the scene, so a dependency that is itself derived resolves\n * before it is read.\n *\n * A derived edge is drawn from these coordinates and must meet the nodes it\n * connects, so this reads the pose each one is painted at: see\n * {@link scenePaintedPoseOf}.\n */\nexport function sceneDepLookup<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose?: (node: Node<TData, TLayer, TPose>) => TPose,\n): (id: NodeId) => DerivedDep<TPose> | undefined {\n const slot = memoSlotFor(toPose, composesFrames(scene.composition));\n if (slot === undefined) return (id) => derivedDepOf(scene, id);\n const ctx: SlotLookup<TPose> = {\n at: scenePaintedPoseOf(scene, toPose) as (node: unknown) => TPose,\n local: (toPose\n ? sceneShownToPose(scene, toPose)\n : (node: Node<TData, TLayer, TPose>) => effectivePose(scene, node)) as (node: unknown) => TPose,\n slot,\n shown: sceneOverridePathOf(scene, toPose) as (node: unknown) => Path | undefined,\n childrenOf: (id: NodeId) => scene.childrenOf(id),\n scene: shownLookup(scene) as never,\n lookup: (id: NodeId) => {\n const node = scene.get(id);\n return node === undefined ? undefined : new SlotDep(nodeAsShown(scene.overrides, node), ctx);\n },\n };\n return ctx.lookup;\n}\n\ninterface SlotLookup<TPose> {\n at: (node: unknown) => TPose;\n /** The pose the node is shown at in its own frame: the box its override path is drawn in. */\n local: (node: unknown) => TPose;\n slot: string | NodeMemoScope;\n /** The path a pose override shows a node by, where it has one. */\n shown: (node: unknown) => Path | undefined;\n childrenOf: (id: NodeId) => readonly NodeId[];\n lookup: (id: NodeId) => DerivedDep<TPose> | undefined;\n /** Where a node's `'inherit'` paint is looked up. */\n scene: { get(id: NodeId): Node<unknown, string, TPose> | undefined };\n}\n\n/** A class, not an object literal with a `get path()` — see `SceneDep` in\n * `core/scene/effectivePose.ts` for why. */\nclass SlotDep<TPose> implements DerivedDep<TPose> {\n readonly node: Node<unknown, string, TPose>;\n readonly pose: TPose;\n\n constructor(posed: unknown, private readonly ctx: SlotLookup<TPose>) {\n this.node = posed as Node<unknown, string, TPose>;\n this.pose = ctx.at(posed);\n }\n\n get path(): Path | null {\n const shown = this.ctx.shown(this.node);\n if (shown !== undefined) return pathAsPainted(shown, this.ctx.local(this.node), this.pose);\n return resolveDerivedPath(\n this.node as never, this.ctx.lookup, this.ctx.childrenOf, this.ctx.slot,\n );\n }\n\n get inherited(): InheritedPaint | undefined {\n return inheritedPaintIn(this.ctx.scene, this.node);\n }\n}\n\n/** Whether something outside `node`'s data says what path it shows: it derives one, or a\n * pose override carries one. The gate both scene walks check before resolving a path. */\nexport function showsComputedPath<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n node: { id: NodeId; dependsOn?: readonly NodeId[] | 'children' },\n): boolean {\n return node.dependsOn !== undefined || scene.overrides.read(node.id)?.path !== undefined;\n}\n\n/**\n * `(node) => the path it shows in place of its data's`: a pose override's `path`, else the\n * one it derives, read through {@link sceneDepLookup}. A reader painting through a `toPose`\n * is answered the override's path carried into the box its `toPose` answers.\n */\nexport function sceneDerivedPathOf<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n toPose?: (node: Node<TData, TLayer, TPose>) => TPose,\n): (node: Node<TData, TLayer, TPose>) => Path | null {\n const depOf = sceneDepLookup(scene, toPose);\n const slot = memoSlotFor(toPose, composesFrames(scene.composition));\n const childrenOf = (id: NodeId): readonly NodeId[] => scene.childrenOf(id);\n const derived = (node: Node<TData, TLayer, TPose>): Path | null =>\n resolveDerivedPath(node, depOf, childrenOf, slot);\n const overridePathOf = sceneOverridePathOf(scene, toPose);\n return (node) => overridePathOf(node) ?? derived(node);\n}\n\nconst toPoseScopes = new WeakMap<object, { local?: NodeMemoScope; world?: NodeMemoScope }>();\n\n/** Where one way of reading poses memoizes its paths, or `undefined` for the\n * scene's own slot: two readers painting one scene at different poses must\n * not serve each other's paths. Every reader composing stored poses shares a\n * slot; a `toPose` gets a scope that is collected with the function. */\nfunction memoSlotFor(\n toPose: object | undefined,\n composes: boolean,\n): string | NodeMemoScope | undefined {\n const frame = composes ? 'world' : 'local';\n if (toPose === undefined) return frame === 'world' ? 'kit:derivedPath:world' : undefined;\n let scopes = toPoseScopes.get(toPose);\n if (scopes === undefined) {\n scopes = {};\n toPoseScopes.set(toPose, scopes);\n }\n return (scopes[frame] ??= createNodeMemoScope());\n}\n\n/**\n * Wrap a `drawOne` so a node that derives its geometry, or whose pose override\n * carries a path, arrives at the painter with the resolved path. The painter has no scene handle and so cannot read\n * the dependencies' poses itself; both scene walks wrap here instead.\n *\n * A node that derives nothing passes the caller's `ctx` through untouched —\n * this runs per node per frame, and an added `{ derivedPath: null }` would be\n * an allocation per node for a field no painter reads.\n */\nexport function withDerivedPaths<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n drawOne: SceneViewDrawOne<TData, TLayer, TPose>,\n toPose?: (node: Node<TData, TLayer, TPose>) => TPose,\n): SceneViewDrawOne<TData, TLayer, TPose> {\n const derivedPathOf = sceneDerivedPathOf(scene, toPose);\n const ownPoseOf = composesFrames(scene.composition)\n ? (toPose ? sceneShownToPose(scene, toPose) : (node: Node<TData, TLayer, TPose>) => effectivePose(scene, node))\n : null;\n return (node, pose, view, ctx) => {\n if (!showsComputedPath(scene, node)) return drawOne(node, pose, view, ctx);\n let path = derivedPathOf(node);\n if (path !== null && ownPoseOf !== null && scene.overrides.read(node.id)?.path === undefined) {\n path = withoutInheritedFrame(path, pose, ownPoseOf(node));\n }\n return drawOne(node, pose, view, { ...ctx, derivedPath: path });\n };\n}\n\n/**\n * A derived path is drawn from its dependencies' painted poses, so it is already where it\n * shows; but the pose rotation the walk wraps every node in is `painted`'s, which carries its\n * containers' turns as well as its own. Carried back through what the containers add, the\n * wrap turns it by its own frame alone, as it does a root's.\n */\nfunction withoutInheritedFrame(path: Path, painted: unknown, own: unknown): Path {\n const undo = poseFrameInverseOf(painted);\n if (undo === null) return path;\n const box = painted as { x: number; y: number; width: number; height: number };\n const o = own as { rotation?: number; transform?: unknown } | null;\n const redo = poseFrameOf({ x: box.x, y: box.y, width: box.width, height: box.height, rotation: o?.rotation, transform: o?.transform });\n return carryPathBy(path, redo === null ? undo : multiply(undo, redo));\n}\n\nconst drawnScenes = new WeakMap<object, DrawnBoundsScene>();\n\n/** `scene` read as `drawnBoundsOf` reads one, each node that shows a computed\n * path answering it. One object per scene, so it can key a memo. */\nexport function drawnBoundsScene<TData, TLayer extends string, TPose>(scene: Scene<TData, TLayer, TPose>): DrawnBoundsScene {\n let held = drawnScenes.get(scene);\n if (held === undefined) {\n const pathOf = sceneDerivedPathOf(scene);\n const shown = shownLookup(scene);\n held = {\n get: (id) => shown.get(id) as never,\n childrenOf: (id) => scene.childrenOf(id),\n derivedPathOf: (id) => {\n const node = scene.get(id);\n return node !== undefined && showsComputedPath(scene, node) ? pathOf(node) : null;\n },\n };\n drawnScenes.set(scene, held);\n }\n return held;\n}\n"],"mappings":"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"}
|
|
@@ -1,18 +1,16 @@
|
|
|
1
|
+
import { pressTarget } from "../tools/builtin/pressTarget.js";
|
|
2
|
+
|
|
1
3
|
//#region src/canvas/doubleClickTarget.ts
|
|
2
4
|
/**
|
|
3
|
-
* The node a double-click on `hitId` names:
|
|
4
|
-
*
|
|
5
|
-
*
|
|
5
|
+
* The node a double-click on `hitId` names: what a press there would select, taken inside
|
|
6
|
+
* `scope` (`pressTarget`). So a double-click on a grouped member names the outermost group
|
|
7
|
+
* above it, and each one inside the entered group names the next group in.
|
|
6
8
|
*/
|
|
7
9
|
function doubleClickTarget(hitId, tree) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (kindOf(id) === "group") named = id;
|
|
13
|
-
id = parentOf(id);
|
|
14
|
-
}
|
|
15
|
-
return named;
|
|
10
|
+
return pressTarget({
|
|
11
|
+
getParent: tree.parentOf,
|
|
12
|
+
getNode: (id) => ({ kind: tree.kindOf(id) })
|
|
13
|
+
}, hitId, tree.scope);
|
|
16
14
|
}
|
|
17
15
|
|
|
18
16
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doubleClickTarget.js","names":[],"sources":["../../src/canvas/doubleClickTarget.ts"],"sourcesContent":["/** How {@link doubleClickTarget} reads the tree. */\nexport interface DoubleClickTree {\n parentOf: (id: string) => string | null;\n /** A node's routing kind; a node routed `'group'` is one a double-click can name. */\n kindOf: (id: string) => string | undefined;\n /** The group
|
|
1
|
+
{"version":3,"file":"doubleClickTarget.js","names":[],"sources":["../../src/canvas/doubleClickTarget.ts"],"sourcesContent":["import { pressTarget } from '../tools/builtin/pressTarget';\n\n/** How {@link doubleClickTarget} reads the tree. */\nexport interface DoubleClickTree {\n parentOf: (id: string) => string | null;\n /** A node's routing kind; a node routed `'group'` is one a double-click can name. */\n kindOf: (id: string) => string | undefined;\n /** The entered group, or null at the scene root. */\n scope: string | null;\n}\n\n/**\n * The node a double-click on `hitId` names: what a press there would select, taken inside\n * `scope` (`pressTarget`). So a double-click on a grouped member names the outermost group\n * above it, and each one inside the entered group names the next group in.\n */\nexport function doubleClickTarget(hitId: string, tree: DoubleClickTree): string {\n return pressTarget(\n { getParent: tree.parentOf, getNode: (id) => ({ kind: tree.kindOf(id) }) },\n hitId,\n tree.scope,\n );\n}\n"],"mappings":";;;;;;;;AAgBA,SAAgB,kBAAkB,OAAe,MAA+B;CAC9E,OAAO,YACL;EAAE,WAAW,KAAK;EAAU,UAAU,QAAQ,EAAE,MAAM,KAAK,OAAO,EAAE,EAAE;CAAG,GACzE,OACA,KAAK,KACP;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { inheritedPaintIn } from "../core/scene/inheritedPaint.js";
|
|
1
2
|
import { axisAlignedBounds, unionBounds } from "../core/geometry/unionBounds.js";
|
|
2
3
|
import { framedBounds, poseDescriptorForNode } from "../core/geometry/poseDescriptor.js";
|
|
3
4
|
import { AUTO_POSE_DESCRIPTOR } from "../core/geometry/autoPoseDescriptor.js";
|
|
4
|
-
import { inheritedPaintIn } from "./inheritedPaints.js";
|
|
5
5
|
import { targetCorners, targetTransformOf, worldToLocal } from "../core/geometry/targetFrame.js";
|
|
6
6
|
import { NO_INK_EXTENTS, inkExtentsBetween, outsetBox } from "../core/geometry/inkExtents.js";
|
|
7
7
|
import { findNodeShape } from "./NodeShape.js";
|
|
@@ -18,8 +18,9 @@ import { boundsOfPath, meanScale } from "@weasel-js/geom";
|
|
|
18
18
|
* The box of `id` drawn at `pose`, stroke included, in the frame the pose is
|
|
19
19
|
* drawn in: a turned node's box comes back turned with it. A node with no
|
|
20
20
|
* stroke, a painter that declares no {@link NodeShapeEntry.drawnBounds}, or
|
|
21
|
-
* `includeStroke: false` answers `framedBounds` unchanged. A
|
|
22
|
-
* reaches as far as its members' boxes do
|
|
21
|
+
* `includeStroke: false` answers `framedBounds` unchanged. A group's box
|
|
22
|
+
* reaches as far as its members' boxes do; a mark's, as far as its own ink and its
|
|
23
|
+
* children's do. A node showing a derived path is
|
|
23
24
|
* boxed around that path, and its placeholder pose plays no part.
|
|
24
25
|
*/
|
|
25
26
|
function drawnBoundsOf(scene, id, pose, opts = {}) {
|
|
@@ -40,10 +41,18 @@ function inkExtentsOf(scene, id, pose, opts = {}) {
|
|
|
40
41
|
width: own.width,
|
|
41
42
|
height: own.height
|
|
42
43
|
};
|
|
43
|
-
const members =
|
|
44
|
-
|
|
44
|
+
const members = scene.childrenOf?.(id) ?? [];
|
|
45
|
+
const reached = members.length > 0 ? membersExtents(scene, members, own, opts) : NO_INK_EXTENTS;
|
|
46
|
+
if (node.kind === "group" && members.length > 0) return reached;
|
|
45
47
|
const drawn = findShapeDrawnBounds(node, pose, withInherited(scene, node, inkCtx(opts.scale, own)));
|
|
46
|
-
|
|
48
|
+
if (drawn === null) return reached;
|
|
49
|
+
const ink = inkExtentsBetween(normalized(unframed), drawn);
|
|
50
|
+
return {
|
|
51
|
+
left: Math.max(ink.left, reached.left),
|
|
52
|
+
top: Math.max(ink.top, reached.top),
|
|
53
|
+
right: Math.max(ink.right, reached.right),
|
|
54
|
+
bottom: Math.max(ink.bottom, reached.bottom)
|
|
55
|
+
};
|
|
47
56
|
}
|
|
48
57
|
/** The box of a node drawn along `path`: the path's own, grown by the stroke
|
|
49
58
|
* its painter reports. */
|
|
@@ -68,7 +77,7 @@ function derivedBoundsOf(scene, id, pose, path, opts) {
|
|
|
68
77
|
});
|
|
69
78
|
return drawn === null ? own : unionBounds([own, drawn]) ?? own;
|
|
70
79
|
}
|
|
71
|
-
/** A
|
|
80
|
+
/** A parent's member extents: its members' drawn boxes, taken into its own frame,
|
|
72
81
|
* past its own box. */
|
|
73
82
|
function membersExtents(scene, members, own, opts) {
|
|
74
83
|
const xf = targetTransformOf(own);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drawnBounds.js","names":[],"sources":["../../src/canvas/drawnBounds.ts"],"sourcesContent":["/**\n * The box of a node as drawn — its pose box with the stroke included — which\n * is what selection chrome, resize, rotate, and alignment sit on. Places that\n * store geometry (a group's derived pose, booleans, export) keep reading the\n * pose box through `framedBounds`.\n */\nimport { inheritedPaintIn } from './inheritedPaints';\nimport { framedBounds, poseDescriptorForNode, type PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport {\n inkExtentsBetween,\n NO_INK_EXTENTS,\n outsetBox,\n type InkExtents,\n} from 'core/geometry/inkExtents';\nimport { targetCorners, targetTransformOf, worldToLocal } from 'core/geometry/targetFrame';\nimport { axisAlignedBounds, unionBounds } from 'core/geometry/unionBounds';\nimport type { Node, NodeId } from 'core/scene/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { boundsOfPath, meanScale, type Path, type Scale2 } from '@weasel-js/geom';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport { findNodeShape, type NodeInkCtx } from './NodeShape';\n\n/** The scene reads {@link drawnBoundsOf} makes. `childrenOf` absent: every\n * node is a leaf. `derivedPathOf` absent: every node is measured by its pose,\n * a path-derived one included — `drawnBoundsScene` supplies it for a `Scene`. */\nexport interface DrawnBoundsScene {\n get(id: NodeId): Node<unknown, string, unknown> | undefined;\n childrenOf?(id: NodeId): readonly NodeId[];\n /** The path a node shows in place of its pose's, or `null` for one that shows its own. */\n derivedPathOf?(id: NodeId): Path | null;\n}\n\n/** How {@link drawnBoundsOf} reads a node. */\nexport interface DrawnBoundsOptions {\n /** How poses are read. Default `AUTO_POSE_DESCRIPTOR`. */\n descriptor?: PoseDescriptor<unknown>;\n /** A member's world pose, for boxing a container around its members.\n * Default: its pose as stored, which is world unless containers compose. */\n worldPoseOf?: (id: NodeId) => unknown;\n /** `false` answers the pose box alone. Default `true`. */\n includeStroke?: boolean;\n /** View scale, which a screen-pixel stroke width is resolved at. Default 1. */\n scale?: number | Scale2;\n}\n\n/**\n * The box of `id` drawn at `pose`, stroke included, in the frame the pose is\n * drawn in: a turned node's box comes back turned with it. A node with no\n * stroke, a painter that declares no {@link NodeShapeEntry.drawnBounds}, or\n * `includeStroke: false` answers `framedBounds` unchanged. A container's box\n * reaches as far as its members' boxes do. A node showing a derived path is\n * boxed around that path, and its placeholder pose plays no part.\n */\nexport function drawnBoundsOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): Bounds {\n const derived = scene.derivedPathOf?.(id) ?? null;\n if (derived !== null) return derivedBoundsOf(scene, id, pose, derived, opts);\n return outsetBox(framedBounds(pose, descriptorFor(scene, id, opts)), inkExtentsOf(scene, id, pose, opts));\n}\n\n/** How far `id`'s ink at `pose` reaches past its pose box on each side, in\n * the box's own frame: what {@link drawnBoundsOf} grows the pose box by. */\nexport function inkExtentsOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): InkExtents {\n if (opts.includeStroke === false) return NO_INK_EXTENTS;\n const node = scene.get(id);\n if (!node) return NO_INK_EXTENTS;\n const own = framedBounds(pose, descriptorFor(scene, id, opts));\n const unframed = { x: own.x, y: own.y, width: own.width, height: own.height };\n const members = node.kind === 'container' ? scene.childrenOf?.(id) ?? [] : [];\n if (members.length > 0) return membersExtents(scene, members, own, opts);\n const drawn = findShapeDrawnBounds(node, pose, withInherited(scene, node, inkCtx(opts.scale, own)));\n return drawn === null ? NO_INK_EXTENTS : inkExtentsBetween(normalized(unframed), drawn);\n}\n\n/** The box of a node drawn along `path`: the path's own, grown by the stroke\n * its painter reports. */\nfunction derivedBoundsOf(scene: DrawnBoundsScene, id: NodeId, pose: unknown, path: Path, opts: DrawnBoundsOptions): Bounds {\n const { x, y, width, height } = boundsOfPath(path);\n const own = { x, y, width, height };\n const node = scene.get(id);\n if (opts.includeStroke === false || !node) return own;\n const ctx = { ...withInherited(scene, node, inkCtx(opts.scale, { x: 0, y: 0, width: 0, height: 0 })), derivedPath: path };\n const drawn = findShapeDrawnBounds(node, pose, ctx);\n return drawn === null ? own : unionBounds([own, drawn]) ?? own;\n}\n\n/** A container's extents: its members' drawn boxes, taken into its own frame,\n * past its own box. */\nfunction membersExtents(\n scene: DrawnBoundsScene,\n members: readonly NodeId[],\n own: Bounds,\n opts: DrawnBoundsOptions,\n): InkExtents {\n const xf = targetTransformOf(own);\n const worldPoseOf = opts.worldPoseOf ?? ((m: NodeId) => scene.get(m)?.pose);\n let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;\n for (const m of members) {\n const box = drawnBoundsOf(scene, m, worldPoseOf(m), opts);\n for (const c of targetCorners(box, targetTransformOf(box))) {\n const p = worldToLocal(xf, c.x, c.y);\n if (!p) continue;\n x0 = Math.min(x0, p.x); y0 = Math.min(y0, p.y);\n x1 = Math.max(x1, p.x); y1 = Math.max(y1, p.y);\n }\n }\n if (x0 === Infinity) return NO_INK_EXTENTS;\n return inkExtentsBetween(normalized(own), { x: x0, y: y0, width: x1 - x0, height: y1 - y0 });\n}\n\n/** {@link drawnBoundsOf} as an axis-aligned box: what a turned node covers. */\nexport function drawnAABBOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): Bounds {\n return axisAlignedBounds(drawnBoundsOf(scene, id, pose, opts));\n}\n\n/** The painter's {@link NodeShapeEntry.drawnBounds} for `node`, or `null`\n * when no painter matches or the one that does declares none. */\nexport function findShapeDrawnBounds<TData, TPose>(\n node: Node<TData, string, TPose>,\n pose: TPose,\n ctx?: NodeInkCtx,\n): Bounds | null {\n return findNodeShape(node)?.drawnBounds?.(node, pose, ctx) ?? null;\n}\n\nfunction descriptorFor(scene: DrawnBoundsScene, id: NodeId, opts: DrawnBoundsOptions): PoseDescriptor<unknown> {\n const d = opts.descriptor ?? AUTO_POSE_DESCRIPTOR;\n const node = scene.get(id);\n return node ? poseDescriptorForNode(d, node) : d;\n}\n\n/** A per-axis view scale also carries the map from `own`'s frame to screen,\n * so a `{ px }` stroke is measured there rather than at the mean. */\nfunction inkCtx(scale: number | Scale2 | undefined, own: Bounds): NodeInkCtx | undefined {\n if (scale === undefined) return undefined;\n if (typeof scale === 'number') return { scale };\n const xf = targetTransformOf(own);\n const [a, b, c, d] = xf.identity ? [1, 0, 0, 1] : xf.toWorld;\n return { scale: meanScale(scale), screen: [scale.x * a, scale.y * b, scale.x * c, scale.y * d] };\n}\n\nfunction withInherited(\n scene: DrawnBoundsScene,\n node: Node<unknown, string, unknown>,\n ctx: NodeInkCtx | undefined,\n): NodeInkCtx | undefined {\n const inherited = inheritedPaintIn(scene, node);\n return inherited === undefined ? ctx : { ...ctx, inherited };\n}\n\nfunction normalized(b: Bounds): Bounds {\n return {\n x: Math.min(b.x, b.x + b.width),\n y: Math.min(b.y, b.y + b.height),\n width: Math.abs(b.width),\n height: Math.abs(b.height),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"drawnBounds.js","names":[],"sources":["../../src/canvas/drawnBounds.ts"],"sourcesContent":["/**\n * The box of a node as drawn — its pose box with the stroke included — which\n * is what selection chrome, resize, rotate, and alignment sit on. Places that\n * store geometry (a group's derived pose, booleans, export) keep reading the\n * pose box through `framedBounds`.\n */\nimport { inheritedPaintIn } from './inheritedPaints';\nimport { framedBounds, poseDescriptorForNode, type PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport {\n inkExtentsBetween,\n NO_INK_EXTENTS,\n outsetBox,\n type InkExtents,\n} from 'core/geometry/inkExtents';\nimport { targetCorners, targetTransformOf, worldToLocal } from 'core/geometry/targetFrame';\nimport { axisAlignedBounds, unionBounds } from 'core/geometry/unionBounds';\nimport type { Node, NodeId } from 'core/scene/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { boundsOfPath, meanScale, type Path, type Scale2 } from '@weasel-js/geom';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport { findNodeShape, type NodeInkCtx } from './NodeShape';\n\n/** The scene reads {@link drawnBoundsOf} makes. `childrenOf` absent: every\n * node is a leaf. `derivedPathOf` absent: every node is measured by its pose,\n * a path-derived one included — `drawnBoundsScene` supplies it for a `Scene`. */\nexport interface DrawnBoundsScene {\n get(id: NodeId): Node<unknown, string, unknown> | undefined;\n childrenOf?(id: NodeId): readonly NodeId[];\n /** The path a node shows in place of its pose's, or `null` for one that shows its own. */\n derivedPathOf?(id: NodeId): Path | null;\n}\n\n/** How {@link drawnBoundsOf} reads a node. */\nexport interface DrawnBoundsOptions {\n /** How poses are read. Default `AUTO_POSE_DESCRIPTOR`. */\n descriptor?: PoseDescriptor<unknown>;\n /** A member's world pose, for boxing a container around its members.\n * Default: its pose as stored, which is world unless containers compose. */\n worldPoseOf?: (id: NodeId) => unknown;\n /** `false` answers the pose box alone. Default `true`. */\n includeStroke?: boolean;\n /** View scale, which a screen-pixel stroke width is resolved at. Default 1. */\n scale?: number | Scale2;\n}\n\n/**\n * The box of `id` drawn at `pose`, stroke included, in the frame the pose is\n * drawn in: a turned node's box comes back turned with it. A node with no\n * stroke, a painter that declares no {@link NodeShapeEntry.drawnBounds}, or\n * `includeStroke: false` answers `framedBounds` unchanged. A group's box\n * reaches as far as its members' boxes do; a mark's, as far as its own ink and its\n * children's do. A node showing a derived path is\n * boxed around that path, and its placeholder pose plays no part.\n */\nexport function drawnBoundsOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): Bounds {\n const derived = scene.derivedPathOf?.(id) ?? null;\n if (derived !== null) return derivedBoundsOf(scene, id, pose, derived, opts);\n return outsetBox(framedBounds(pose, descriptorFor(scene, id, opts)), inkExtentsOf(scene, id, pose, opts));\n}\n\n/** How far `id`'s ink at `pose` reaches past its pose box on each side, in\n * the box's own frame: what {@link drawnBoundsOf} grows the pose box by. */\nexport function inkExtentsOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): InkExtents {\n if (opts.includeStroke === false) return NO_INK_EXTENTS;\n const node = scene.get(id);\n if (!node) return NO_INK_EXTENTS;\n const own = framedBounds(pose, descriptorFor(scene, id, opts));\n const unframed = { x: own.x, y: own.y, width: own.width, height: own.height };\n const members = scene.childrenOf?.(id) ?? [];\n const reached = members.length > 0 ? membersExtents(scene, members, own, opts) : NO_INK_EXTENTS;\n if (node.kind === 'group' && members.length > 0) return reached;\n const drawn = findShapeDrawnBounds(node, pose, withInherited(scene, node, inkCtx(opts.scale, own)));\n if (drawn === null) return reached;\n const ink = inkExtentsBetween(normalized(unframed), drawn);\n return {\n left: Math.max(ink.left, reached.left),\n top: Math.max(ink.top, reached.top),\n right: Math.max(ink.right, reached.right),\n bottom: Math.max(ink.bottom, reached.bottom),\n };\n}\n\n/** The box of a node drawn along `path`: the path's own, grown by the stroke\n * its painter reports. */\nfunction derivedBoundsOf(scene: DrawnBoundsScene, id: NodeId, pose: unknown, path: Path, opts: DrawnBoundsOptions): Bounds {\n const { x, y, width, height } = boundsOfPath(path);\n const own = { x, y, width, height };\n const node = scene.get(id);\n if (opts.includeStroke === false || !node) return own;\n const ctx = { ...withInherited(scene, node, inkCtx(opts.scale, { x: 0, y: 0, width: 0, height: 0 })), derivedPath: path };\n const drawn = findShapeDrawnBounds(node, pose, ctx);\n return drawn === null ? own : unionBounds([own, drawn]) ?? own;\n}\n\n/** A parent's member extents: its members' drawn boxes, taken into its own frame,\n * past its own box. */\nfunction membersExtents(\n scene: DrawnBoundsScene,\n members: readonly NodeId[],\n own: Bounds,\n opts: DrawnBoundsOptions,\n): InkExtents {\n const xf = targetTransformOf(own);\n const worldPoseOf = opts.worldPoseOf ?? ((m: NodeId) => scene.get(m)?.pose);\n let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;\n for (const m of members) {\n const box = drawnBoundsOf(scene, m, worldPoseOf(m), opts);\n for (const c of targetCorners(box, targetTransformOf(box))) {\n const p = worldToLocal(xf, c.x, c.y);\n if (!p) continue;\n x0 = Math.min(x0, p.x); y0 = Math.min(y0, p.y);\n x1 = Math.max(x1, p.x); y1 = Math.max(y1, p.y);\n }\n }\n if (x0 === Infinity) return NO_INK_EXTENTS;\n return inkExtentsBetween(normalized(own), { x: x0, y: y0, width: x1 - x0, height: y1 - y0 });\n}\n\n/** {@link drawnBoundsOf} as an axis-aligned box: what a turned node covers. */\nexport function drawnAABBOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): Bounds {\n return axisAlignedBounds(drawnBoundsOf(scene, id, pose, opts));\n}\n\n/** The painter's {@link NodeShapeEntry.drawnBounds} for `node`, or `null`\n * when no painter matches or the one that does declares none. */\nexport function findShapeDrawnBounds<TData, TPose>(\n node: Node<TData, string, TPose>,\n pose: TPose,\n ctx?: NodeInkCtx,\n): Bounds | null {\n return findNodeShape(node)?.drawnBounds?.(node, pose, ctx) ?? null;\n}\n\nfunction descriptorFor(scene: DrawnBoundsScene, id: NodeId, opts: DrawnBoundsOptions): PoseDescriptor<unknown> {\n const d = opts.descriptor ?? AUTO_POSE_DESCRIPTOR;\n const node = scene.get(id);\n return node ? poseDescriptorForNode(d, node) : d;\n}\n\n/** A per-axis view scale also carries the map from `own`'s frame to screen,\n * so a `{ px }` stroke is measured there rather than at the mean. */\nfunction inkCtx(scale: number | Scale2 | undefined, own: Bounds): NodeInkCtx | undefined {\n if (scale === undefined) return undefined;\n if (typeof scale === 'number') return { scale };\n const xf = targetTransformOf(own);\n const [a, b, c, d] = xf.identity ? [1, 0, 0, 1] : xf.toWorld;\n return { scale: meanScale(scale), screen: [scale.x * a, scale.y * b, scale.x * c, scale.y * d] };\n}\n\nfunction withInherited(\n scene: DrawnBoundsScene,\n node: Node<unknown, string, unknown>,\n ctx: NodeInkCtx | undefined,\n): NodeInkCtx | undefined {\n const inherited = inheritedPaintIn(scene, node);\n return inherited === undefined ? ctx : { ...ctx, inherited };\n}\n\nfunction normalized(b: Bounds): Bounds {\n return {\n x: Math.min(b.x, b.x + b.width),\n y: Math.min(b.y, b.y + b.height),\n width: Math.abs(b.width),\n height: Math.abs(b.height),\n };\n}\n"],"mappings":"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"}
|