@weasel-js/core 1.11.1 → 1.13.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +275 -0
- package/dist/WeaselProvider.js +1 -1
- package/dist/affordances/pathAnchors.js +10 -6
- package/dist/affordances/pathAnchors.js.map +1 -1
- 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 -1
- package/dist/barrel/chrome.js +3 -1
- package/dist/barrel/paint.js +4 -4
- package/dist/barrel/paths.js +9 -4
- package/dist/barrel/renderer.js +3 -2
- package/dist/barrel/routing.js +4 -4
- package/dist/barrel/scene.js +7 -1
- package/dist/barrel/text.js +2 -2
- package/dist/barrel/viewport.js +6 -2
- package/dist/canvas/Canvas.js +2 -1
- package/dist/canvas/Canvas.js.map +1 -1
- package/dist/canvas/CanvasView.js +23 -34
- package/dist/canvas/CanvasView.js.map +1 -1
- package/dist/canvas/ModalityHud.js +1 -1
- package/dist/canvas/NodeShape.js +5 -1
- package/dist/canvas/NodeShape.js.map +1 -1
- package/dist/canvas/SceneCanvas/ActionsProviderIfRoot.js +1 -1
- package/dist/canvas/SceneCanvas/DepRegistryProviderIfRoot.js +1 -1
- package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +1 -1
- package/dist/canvas/SceneCanvas/SceneCanvasInput.js +2 -0
- package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
- package/dist/canvas/SceneCanvas/SurfaceDeclarations.js +23 -0
- package/dist/canvas/SceneCanvas/SurfaceDeclarations.js.map +1 -0
- package/dist/canvas/SceneCanvas/canvasPassThrough.js +2 -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/props.js.map +1 -1
- package/dist/canvas/SceneCanvas/scenePointQuery.js +27 -5
- package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -1
- package/dist/canvas/SceneCanvas/useActionTunings.js +6 -1
- package/dist/canvas/SceneCanvas/useActionTunings.js.map +1 -1
- package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js +1 -0
- package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js.map +1 -1
- package/dist/canvas/SceneCanvas/useCanvasTools.js +10 -2
- package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +25 -19
- package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js +9 -3
- package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +17 -2
- package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +2 -0
- package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js +19 -4
- package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js.map +1 -1
- package/dist/canvas/SceneCanvas/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 +7 -4
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
- package/dist/canvas/SceneCanvas.js +30 -12
- package/dist/canvas/SceneCanvas.js.map +1 -1
- package/dist/canvas/ViewMarker.js +35 -0
- package/dist/canvas/ViewMarker.js.map +1 -0
- package/dist/canvas/accentColor.js +26 -3
- package/dist/canvas/accentColor.js.map +1 -1
- package/dist/canvas/buildSceneTree.js +14 -20
- package/dist/canvas/buildSceneTree.js.map +1 -1
- package/dist/canvas/copiesAbove.js +1 -1
- package/dist/canvas/copiesAbove.js.map +1 -1
- package/dist/canvas/defaultDrawOne.js +11 -6
- 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 +3 -2
- 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 +28 -16
- package/dist/canvas/deps/hitTestArea.js.map +1 -1
- package/dist/canvas/deps/insert.js +10 -38
- 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/pathEditCommits.js +3 -7
- package/dist/canvas/deps/pathEditCommits.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 +26 -9
- package/dist/canvas/drawnBounds.js.map +1 -1
- package/dist/canvas/inheritedPaints.js +55 -0
- package/dist/canvas/inheritedPaints.js.map +1 -0
- package/dist/canvas/nodeShapes/derivedPainter.js +15 -16
- package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/nodePaint.js +7 -34
- package/dist/canvas/nodeShapes/nodePaint.js.map +1 -1
- package/dist/canvas/nodeShapes/pathPainter.js +7 -7
- package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/rectFallbackPainter.js +4 -5
- package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/shapePainter.js +9 -9
- package/dist/canvas/nodeShapes/shapePainter.js.map +1 -1
- package/dist/canvas/nodeShapes/textPainter.js +13 -12
- package/dist/canvas/nodeShapes/textPainter.js.map +1 -1
- package/dist/canvas/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/pickReach.js +3 -3
- package/dist/canvas/pickReach.js.map +1 -1
- package/dist/canvas/pickWalk.js +17 -91
- 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/renderSceneToPixels.js +1 -1
- package/dist/canvas/renderSceneToPixels.js.map +1 -1
- 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 -3
- package/dist/canvas/sceneSlotWiring.js.map +1 -1
- package/dist/canvas/sceneViewRender.js +9 -4
- 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/surfaceContribution.js +12 -8
- package/dist/canvas/surfaceContribution.js.map +1 -1
- package/dist/canvas/useCanvasView.js +4 -2
- package/dist/canvas/useCanvasView.js.map +1 -1
- package/dist/canvas/useLayerStack.js +6 -2
- package/dist/canvas/useLayerStack.js.map +1 -1
- package/dist/canvas/useSceneAdapter.js +5 -3
- package/dist/canvas/useSceneAdapter.js.map +1 -1
- package/dist/canvas/useViewAim.js +121 -0
- package/dist/canvas/useViewAim.js.map +1 -0
- package/dist/canvas/useViewNesting.js +3 -2
- package/dist/canvas/useViewNesting.js.map +1 -1
- package/dist/canvas/useViewRegistrySurface.js +4 -2
- package/dist/canvas/useViewRegistrySurface.js.map +1 -1
- package/dist/canvas/viewMarkers.js +63 -0
- package/dist/canvas/viewMarkers.js.map +1 -0
- package/dist/canvas/viewRegistry.js +27 -1
- package/dist/canvas/viewRegistry.js.map +1 -1
- package/dist/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/pages/pageEdits.js +2 -3
- package/dist/core/pages/pageEdits.js.map +1 -1
- package/dist/core/paint/meshKindLazy.js +59 -2
- package/dist/core/paint/meshKindLazy.js.map +1 -1
- package/dist/core/scene/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 +10 -0
- package/dist/core/scene/clipNothing.js.map +1 -0
- package/dist/core/scene/declaredFieldSetters.js +44 -0
- package/dist/core/scene/declaredFieldSetters.js.map +1 -0
- package/dist/core/scene/depMemo.js +14 -3
- package/dist/core/scene/depMemo.js.map +1 -1
- package/dist/core/scene/dependents.js +9 -1
- package/dist/core/scene/dependents.js.map +1 -1
- package/dist/core/scene/derivedParts.js +10 -11
- package/dist/core/scene/derivedParts.js.map +1 -1
- package/dist/core/scene/documentOf.js +43 -0
- package/dist/core/scene/documentOf.js.map +1 -0
- package/dist/core/scene/effectivePose.js +44 -15
- 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/fontReads.js +37 -0
- package/dist/core/scene/fontReads.js.map +1 -0
- package/dist/core/scene/historyForm.js +4 -4
- package/dist/core/scene/historyForm.js.map +1 -1
- package/dist/core/scene/inheritedPaint.js +97 -0
- package/dist/core/scene/inheritedPaint.js.map +1 -0
- package/dist/core/scene/kitOps.js +3 -23
- package/dist/core/scene/kitOps.js.map +1 -1
- package/dist/core/scene/kitRegistry.js +5 -0
- package/dist/core/scene/kitRegistry.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 +21 -8
- 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/blend.js.map +1 -1
- package/dist/core/scene/pathEffects/derive.js +10 -4
- package/dist/core/scene/pathEffects/derive.js.map +1 -1
- package/dist/core/scene/pathEffects/effects.js +3 -1
- package/dist/core/scene/pathEffects/effects.js.map +1 -1
- package/dist/core/scene/pathEffects/foundHidden.js +35 -0
- package/dist/core/scene/pathEffects/foundHidden.js.map +1 -0
- package/dist/core/scene/pathEffects/textAlong.js +95 -0
- package/dist/core/scene/pathEffects/textAlong.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-BHuoMs1o.d.ts → cornerHandles-CNCb7aTY.d.ts} +3 -2
- package/dist/{depSchema-BpZNOv7q.d.ts → depSchema-B1RS-IJx.d.ts} +39 -18
- 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/registry.js +4 -4
- package/dist/devtools/registry.js.map +1 -1
- package/dist/devtools-protocol.d.ts +4 -3
- package/dist/devtools.d.ts +3 -2
- package/dist/features/measure/measureContribution.js +4 -2
- package/dist/features/measure/measureContribution.js.map +1 -1
- 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 +10 -6
- package/dist/features/modes/modeTriggers.js.map +1 -1
- package/dist/features/pathEffects/effectSources.js +69 -0
- package/dist/features/pathEffects/effectSources.js.map +1 -0
- package/dist/features/pathEffects/readSource.js +18 -2
- package/dist/features/pathEffects/readSource.js.map +1 -1
- package/dist/features/pathEffects/textItems.js +53 -0
- package/dist/features/pathEffects/textItems.js.map +1 -0
- package/dist/features/pathEffects/useEffectSourcesLayer.js +53 -0
- package/dist/features/pathEffects/useEffectSourcesLayer.js.map +1 -0
- package/dist/features/paths/editablePath.js +1 -1
- package/dist/features/paths/editablePath.js.map +1 -1
- package/dist/features/paths/pathEditState.js +5 -2
- package/dist/features/paths/pathEditState.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 +64 -0
- package/dist/features/text/sceneTextDrafts.js.map +1 -0
- package/dist/features/text/textHitAt.js +34 -3
- package/dist/features/text/textHitAt.js.map +1 -1
- package/dist/features/text/useSceneTextEdit.js +23 -19
- package/dist/features/text/useSceneTextEdit.js.map +1 -1
- package/dist/features/viewports/aim.js +105 -0
- package/dist/features/viewports/aim.js.map +1 -0
- package/dist/features/viewports/viewMarker.js +82 -0
- package/dist/features/viewports/viewMarker.js.map +1 -0
- package/dist/features/viewports/viewMarkerDrag.js +55 -0
- package/dist/features/viewports/viewMarkerDrag.js.map +1 -0
- package/dist/index.d.ts +709 -258
- package/dist/index.js +33 -12
- package/dist/interactions/actions/admitsChild.js +11 -0
- package/dist/interactions/actions/admitsChild.js.map +1 -0
- package/dist/interactions/actions/booleans/booleans.js +2 -1
- package/dist/interactions/actions/booleans/booleans.js.map +1 -1
- package/dist/interactions/actions/booleans/operands.js +6 -5
- package/dist/interactions/actions/booleans/operands.js.map +1 -1
- package/dist/interactions/actions/clipboard/action.js +27 -17
- package/dist/interactions/actions/clipboard/action.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorEditing.js +44 -23
- package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorJoin.js +12 -6
- package/dist/interactions/actions/defaults/anchorJoin.js.map +1 -1
- package/dist/interactions/actions/defaults/bendSegment.js +7 -4
- package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
- package/dist/interactions/actions/defaults/cancelGesture.js +11 -8
- package/dist/interactions/actions/defaults/cancelGesture.js.map +1 -1
- package/dist/interactions/actions/defaults/contextMenu.js +33 -11
- package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
- package/dist/interactions/actions/defaults/createPaintAction.js +5 -2
- package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
- package/dist/interactions/actions/defaults/delete.js +28 -14
- package/dist/interactions/actions/defaults/delete.js.map +1 -1
- package/dist/interactions/actions/defaults/duplicate.js +11 -8
- package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
- package/dist/interactions/actions/defaults/enterGroup.js +88 -0
- package/dist/interactions/actions/defaults/enterGroup.js.map +1 -0
- package/dist/interactions/actions/defaults/enterPathEdit.js +20 -10
- 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/escape.js +11 -8
- package/dist/interactions/actions/defaults/escape.js.map +1 -1
- package/dist/interactions/actions/defaults/exitPathEdit.js +7 -4
- package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/group.js +26 -20
- package/dist/interactions/actions/defaults/group.js.map +1 -1
- package/dist/interactions/actions/defaults/ingest.js +10 -4
- package/dist/interactions/actions/defaults/ingest.js.map +1 -1
- package/dist/interactions/actions/defaults/insertPathAnchor.js +7 -4
- package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
- package/dist/interactions/actions/defaults/nudge.js +8 -2
- package/dist/interactions/actions/defaults/nudge.js.map +1 -1
- package/dist/interactions/actions/defaults/pathEditTools.js +11 -8
- package/dist/interactions/actions/defaults/pathEditTools.js.map +1 -1
- package/dist/interactions/actions/defaults/pinchZoom.js +7 -4
- package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
- package/dist/interactions/actions/defaults/reorder.js +24 -6
- package/dist/interactions/actions/defaults/reorder.js.map +1 -1
- package/dist/interactions/actions/defaults/selectAll.js +15 -8
- 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/defaults/setFill.js +7 -3
- package/dist/interactions/actions/defaults/setFill.js.map +1 -1
- package/dist/interactions/actions/defaults/setFillOpacity.js +1 -1
- package/dist/interactions/actions/defaults/setStroke.js +12 -11
- package/dist/interactions/actions/defaults/setStroke.js.map +1 -1
- package/dist/interactions/actions/defaults/setStrokeOpacity.js +1 -1
- package/dist/interactions/actions/defaults/toolActivate.js +7 -1
- package/dist/interactions/actions/defaults/toolActivate.js.map +1 -1
- package/dist/interactions/actions/defaults/toolResetToDefault.js +7 -4
- package/dist/interactions/actions/defaults/toolResetToDefault.js.map +1 -1
- package/dist/interactions/actions/defaults/undoRedo.js +19 -13
- package/dist/interactions/actions/defaults/undoRedo.js.map +1 -1
- package/dist/interactions/actions/defaults/viewportWheelPan.js +16 -7
- package/dist/interactions/actions/defaults/viewportWheelPan.js.map +1 -1
- package/dist/interactions/actions/defaults/viewportZoom.js +24 -6
- package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
- package/dist/interactions/actions/drawnBoxes.js +2 -1
- package/dist/interactions/actions/drawnBoxes.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/action.js +7 -4
- package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
- package/dist/interactions/actions/flip/action.js +8 -2
- package/dist/interactions/actions/flip/action.js.map +1 -1
- package/dist/interactions/actions/insert/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/repositionAction.js +2 -1
- package/dist/interactions/actions/insert/repositionAction.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/action.js +7 -4
- package/dist/interactions/actions/move/action.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/outlines/createOutlinesAction.js +8 -5
- package/dist/interactions/actions/outlines/createOutlinesAction.js.map +1 -1
- package/dist/interactions/actions/outlines/outlineStroke.js +5 -3
- package/dist/interactions/actions/outlines/outlineStroke.js.map +1 -1
- package/dist/interactions/actions/pathCommands/cutJoin.js +8 -5
- package/dist/interactions/actions/pathCommands/cutJoin.js.map +1 -1
- package/dist/interactions/actions/pathCommands/pathNodes.js +14 -5
- package/dist/interactions/actions/pathCommands/pathNodes.js.map +1 -1
- package/dist/interactions/actions/resize/cornerHandles.js +2 -1
- package/dist/interactions/actions/resize/cornerHandles.js.map +1 -1
- package/dist/interactions/actions/selectionSubtree.js +12 -7
- package/dist/interactions/actions/selectionSubtree.js.map +1 -1
- package/dist/interactions/actions/transform-again/action.js +8 -5
- package/dist/interactions/actions/transform-again/action.js.map +1 -1
- package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js +14 -2
- package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js.map +1 -1
- package/dist/json.d.ts +4 -0
- package/dist/json.js +3 -0
- package/dist/kitSettingsSchema.js +8 -1
- package/dist/kitSettingsSchema.js.map +1 -1
- package/dist/{math-CrJULaZI.d.ts → math-C_elShP_.d.ts} +7 -1
- package/dist/math.d.ts +1 -1
- package/dist/math.js +2 -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/ellipse/useEllipseTool.js +1 -0
- package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
- package/dist/tools/builtin/eyedropper/useEyedropperTool.js +2 -1
- package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
- package/dist/tools/builtin/hand/dragPanContribution.js +4 -1
- package/dist/tools/builtin/hand/dragPanContribution.js.map +1 -1
- package/dist/tools/builtin/hand/useHandTool.js +7 -4
- package/dist/tools/builtin/hand/useHandTool.js.map +1 -1
- package/dist/tools/builtin/image/useImageTool.js +1 -0
- package/dist/tools/builtin/image/useImageTool.js.map +1 -1
- package/dist/tools/builtin/lasso/useLassoTool.js +1 -0
- package/dist/tools/builtin/lasso/useLassoTool.js.map +1 -1
- package/dist/tools/builtin/line/useLineTool.js +1 -0
- package/dist/tools/builtin/line/useLineTool.js.map +1 -1
- package/dist/tools/builtin/measure/useMeasureTool.js +12 -5
- package/dist/tools/builtin/measure/useMeasureTool.js.map +1 -1
- package/dist/tools/builtin/pen/usePenTool.js +14 -6
- package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
- package/dist/tools/builtin/pencil/usePencilTool.js +1 -0
- package/dist/tools/builtin/pencil/usePencilTool.js.map +1 -1
- package/dist/tools/builtin/polygon/usePolygonTool.js +13 -4
- package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
- package/dist/tools/builtin/pressTarget.js +22 -0
- package/dist/tools/builtin/pressTarget.js.map +1 -0
- package/dist/tools/builtin/rect/useRectTool.js +1 -0
- package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
- package/dist/tools/builtin/select/adapterPointQuery.js +6 -5
- 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 +32 -0
- package/dist/tools/builtin/select/pressDecision.js.map +1 -0
- package/dist/tools/builtin/select/selectionContributions.js +22 -13
- package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
- package/dist/tools/builtin/select/useSelectTool.js +74 -55
- package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
- package/dist/tools/builtin/slice/useSliceTool.js +22 -8
- package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
- package/dist/tools/builtin/star/useStarTool.js +13 -4
- package/dist/tools/builtin/star/useStarTool.js.map +1 -1
- package/dist/tools/builtin/sticky/stickyNode.js +4 -1
- package/dist/tools/builtin/sticky/stickyNode.js.map +1 -1
- package/dist/tools/builtin/sticky/useStickyTool.js +10 -5
- package/dist/tools/builtin/sticky/useStickyTool.js.map +1 -1
- package/dist/tools/builtin/text/useTextTool.js +3 -1
- package/dist/tools/builtin/text/useTextTool.js.map +1 -1
- package/dist/{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/util/paint.js +1 -3
- package/dist/util/paint.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +29 -25
|
@@ -9,9 +9,11 @@ import { mergeLayersWithDefaults } from "./mergeLayers.js";
|
|
|
9
9
|
import { useDispatcherOverlayLayer } from "./useDispatcherOverlayLayer.js";
|
|
10
10
|
import { usePreviewGhostLayer } from "./usePreviewGhostLayer.js";
|
|
11
11
|
import { useDepartureGhostLayer } from "./useDepartureGhostLayer.js";
|
|
12
|
+
import { useEffectSourcesLayer } from "../../features/pathEffects/useEffectSourcesLayer.js";
|
|
12
13
|
import { usePoseTransition } from "../../animation/poseTransition.js";
|
|
13
14
|
import { useSceneSlotLayers } from "./useSceneSlotLayers.js";
|
|
14
15
|
import { useSelectionOverlayLayer } from "./useSelectionOverlayLayer.js";
|
|
16
|
+
import { useEnteredGroupFrameLayer } from "../../features/selection/enteredGroupFrame.js";
|
|
15
17
|
import { createSnapReadoutLayer } from "../../features/snap/readoutLayer.js";
|
|
16
18
|
import { useMemo, useRef, useState } from "react";
|
|
17
19
|
|
|
@@ -20,7 +22,8 @@ import { useMemo, useRef, useState } from "react";
|
|
|
20
22
|
* The layer map `<SceneCanvas>` hands `<Canvas>`: the caller's `layers` over
|
|
21
23
|
* the kit defaults, with the scene and selection-overlay slots built, and the
|
|
22
24
|
* canvas's own layers anchored around them. Removed nodes leave just above
|
|
23
|
-
* the scene, the preview ghost above them,
|
|
25
|
+
* the scene, the preview ghost above them, a selected path effect's hidden sources above that, the dispatcher's
|
|
26
|
+
* overlay (marquee, lasso) above those, and the
|
|
24
27
|
* anchor overlay above the selection chrome.
|
|
25
28
|
*/
|
|
26
29
|
function useSceneCanvasLayers(args) {
|
|
@@ -51,6 +54,7 @@ function useSceneCanvasLayers(args) {
|
|
|
51
54
|
sceneSlot,
|
|
52
55
|
departures
|
|
53
56
|
});
|
|
57
|
+
const effectSourcesLayer = useEffectSourcesLayer(scene);
|
|
54
58
|
const dispatcherOverlay = useDispatcherOverlayLayer({ dispatcher });
|
|
55
59
|
const hitRadiusPx = args.selectTool?.handleHitRadius ?? targetSizesPx(targetScale, baseSizes).handle;
|
|
56
60
|
const edges = args.selectTool?.resize?.edgeHandles ?? true;
|
|
@@ -80,11 +84,16 @@ function useSceneCanvasLayers(args) {
|
|
|
80
84
|
planeOfNode: args.planeOfNode
|
|
81
85
|
}, overlayConfig);
|
|
82
86
|
const { layer: selectionOverlayLayer, rotationBadge } = useSelectionOverlayLayer(merged.selectionOverlay, pathEditState.getEditingIds, poseDescriptor, args.boundsIncludeStroke ? drawnBoundsScene(scene) : void 0, resizeHandles);
|
|
87
|
+
const enteredFrameLayer = useEnteredGroupFrameLayer(args.enteredGroup, args.boundsOf, args.frameColor);
|
|
83
88
|
return {
|
|
84
89
|
layers: useMemo(() => ({
|
|
85
90
|
...builtLayersOf(merged),
|
|
86
91
|
scene: sceneLayers,
|
|
87
92
|
selectionOverlay: selectionOverlayLayer ? { layer: selectionOverlayLayer } : null,
|
|
93
|
+
enteredGroupFrame: {
|
|
94
|
+
layer: enteredFrameLayer,
|
|
95
|
+
before: "selectionOverlay"
|
|
96
|
+
},
|
|
88
97
|
departureGhost: {
|
|
89
98
|
layer: departureLayer,
|
|
90
99
|
after: "scene"
|
|
@@ -93,9 +102,13 @@ function useSceneCanvasLayers(args) {
|
|
|
93
102
|
layer: previewLayer,
|
|
94
103
|
after: "departureGhost"
|
|
95
104
|
},
|
|
105
|
+
effectSources: {
|
|
106
|
+
layer: effectSourcesLayer,
|
|
107
|
+
after: "previewGhost"
|
|
108
|
+
},
|
|
96
109
|
dispatcherOverlay: {
|
|
97
110
|
layer: dispatcherOverlay,
|
|
98
|
-
after: "
|
|
111
|
+
after: "effectSources"
|
|
99
112
|
},
|
|
100
113
|
pathEditingOverlay: overlaySlot === null || isCustomEntry(overlaySlot) ? overlaySlot : {
|
|
101
114
|
layer: pathEditingOverlayLayer,
|
|
@@ -113,8 +126,10 @@ function useSceneCanvasLayers(args) {
|
|
|
113
126
|
merged,
|
|
114
127
|
sceneLayers,
|
|
115
128
|
selectionOverlayLayer,
|
|
129
|
+
enteredFrameLayer,
|
|
116
130
|
departureLayer,
|
|
117
131
|
previewLayer,
|
|
132
|
+
effectSourcesLayer,
|
|
118
133
|
dispatcherOverlay,
|
|
119
134
|
pathEditingOverlayLayer,
|
|
120
135
|
overlaySlot,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSceneCanvasLayers.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneCanvasLayers.ts"],"sourcesContent":["import { useMemo, useRef, useState, type MutableRefObject } from 'react';\nimport type { ColorOverrideRegistry } from '@weasel-js/animator';\nimport type { Dispatcher } from '@weasel-js/routing';\nimport type { Affordance } from 'affordances/types';\nimport type { Node, NodeId, Scene } from 'core/scene/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport type { PlaneMap } from 'core/viewport/parallax';\nimport type { View } from 'core/viewport/view';\nimport type { PathEditState } from 'features/paths/pathEditState';\nimport { usePathEditOverlayLayer } from 'features/paths/usePathEdit';\nimport type { rotationBadgeOf } from 'features/selection/overlay';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport type { HierarchicalAdapter } from '../buildSceneTree';\nimport { builtLayersOf, isCustomEntry, type CanvasLayers } from '../layerSlots';\nimport { createSlopsDebugLayer } from '../slopsDebugLayer';\nimport { useTargetBasePx } from 'core/device/useTargetBasePx';\nimport { targetSizesPx } from 'core/device/targets';\nimport type { ResizeHandleLayout } from 'interactions/actions/resize/cornerHandles';\nimport { mergeLayersWithDefaults } from './mergeLayers';\nimport type { SceneCanvasLayers } from './props';\nimport { useDispatcherOverlayLayer } from './useDispatcherOverlayLayer';\nimport { usePreviewGhostLayer } from './usePreviewGhostLayer';\nimport { useDepartureGhostLayer } from './useDepartureGhostLayer';\nimport type { Departures } from '../../animation/departures';\nimport { usePoseTransition, type PoseTransitionSeat } from '../../animation/poseTransition';\nimport { useSceneSlotLayers } from './useSceneSlotLayers';\nimport { useSelectionOverlayLayer } from './useSelectionOverlayLayer';\nimport { drawnBoundsScene } from '../derivedPath';\nimport { createSnapOutcomeStore, type SnapOutcomeStore } from 'features/snap/outcomeStore';\nimport { createSnapReadoutLayer } from 'features/snap/readoutLayer';\nimport type { SnapReadoutStyle } from 'features/snap/types';\n\n/**\n * The layer map `<SceneCanvas>` hands `<Canvas>`: the caller's `layers` over\n * the kit defaults, with the scene and selection-overlay slots built, and the\n * canvas's own layers anchored around them. Removed nodes leave just above\n * the scene, the preview ghost above them, the dispatcher's overlay (marquee, lasso) above the ghost, and the\n * anchor overlay above the selection chrome.\n */\nexport function useSceneCanvasLayers<TData, TLayer extends string, TPose>(args: {\n /** `<SceneCanvas layers>`. Omitted, the result is the full default set. */\n layers: SceneCanvasLayers<Node<TData, TLayer, TPose>, TPose> | undefined;\n /** The device profile's, which sizes the default handles. */\n targetScale: number;\n scene: Scene<TData, TLayer, TPose>;\n adapter: HierarchicalAdapter<Node<TData, TLayer, TPose>, TPose>;\n poseDescriptor: PoseDescriptor<TPose>;\n alphaFor: ((id: string) => number) | undefined;\n colorOverrides: ColorOverrideRegistry | undefined;\n boundsOf: (id: string) => Bounds | null;\n planeOfNode: (id: string, view: View) => PlaneMap | null;\n /** `<SceneCanvas poseTransition>`, whose ghosts of removed nodes this draws. */\n poseTransition: PoseTransitionSeat<TPose> | null | undefined;\n /** Ids an in-flight gesture is ghosting. */\n previewIds: () => Iterable<string> | null;\n dispatcher: Dispatcher;\n /** Whether to mount the snap readout layer. */\n snapReadout: boolean;\n snapReadoutStyle?: SnapReadoutStyle;\n pathEditState: PathEditState;\n /** `debug.slops`: paint the hit regions of the chrome affordances. */\n slops: boolean;\n /** `geometry.boundsIncludeStroke`: whether chrome sits on the stroke. */\n boundsIncludeStroke: boolean;\n /** `selectTool`'s `handleHitRadius` and `resize.edgeHandles`, resolved here into\n * the one {@link ResizeHandleLayout} the overlay paints and the hit-test reads. */\n selectTool?: { handleHitRadius?: number; resize?: { edgeHandles?: boolean } };\n}): {\n layers: CanvasLayers;\n /** Where the selection overlay paints its rotate badge, or null. */\n rotationBadge: ReturnType<typeof rotationBadgeOf>;\n /** Which resize handles exist and their hit radius, for every hit-test of this canvas. */\n resizeHandles: ResizeHandleLayout;\n /** The affordances the gesture mounter hit-tests, which it publishes here\n * so the slops halos are the hit regions rather than a copy of them. */\n chromeAffordancesRef: MutableRefObject<readonly Affordance[]>;\n /** The marks the readout layer draws; the canvas's snap dep writes them. */\n snapStore: SnapOutcomeStore;\n /** The removed nodes the seat is seeing off. */\n departures: Departures<TPose, Node<TData, TLayer, TPose>>;\n} {\n const { scene, adapter, poseDescriptor, alphaFor, dispatcher, pathEditState, slops, targetScale } = args;\n\n const baseSizes = useTargetBasePx();\n const merged = useMemo(\n () => mergeLayersWithDefaults(args.layers, targetScale, baseSizes),\n [args.layers, targetScale, baseSizes],\n );\n\n const { config: sceneSlot, layers: sceneLayers } = useSceneSlotLayers({\n slot: merged.scene,\n scene,\n adapter,\n alphaFor,\n colorOverrides: args.colorOverrides,\n boundsOf: args.boundsOf,\n previewIds: args.previewIds,\n });\n // Takes the scene-wired slot, so a ghosted derived node still resolves its path.\n const previewLayer = usePreviewGhostLayer<TData, TLayer, TPose>({ scene, sceneSlot, dispatcher });\n const departures = usePoseTransition(scene, args.poseTransition);\n const departureLayer = useDepartureGhostLayer({ scene, sceneSlot, departures });\n const dispatcherOverlay = useDispatcherOverlayLayer({ dispatcher });\n\n const hitRadiusPx = args.selectTool?.handleHitRadius ?? targetSizesPx(targetScale, baseSizes).handle;\n const edges = args.selectTool?.resize?.edgeHandles ?? true;\n const resizeHandles = useMemo<ResizeHandleLayout>(() => ({ edges, hitRadiusPx }), [edges, hitRadiusPx]);\n\n const chromeAffordancesRef = useRef<readonly Affordance[]>([]);\n const slopsLayer = useMemo(\n () => createSlopsDebugLayer({ getAffordances: () => chromeAffordancesRef.current }),\n [],\n );\n\n const [snapStore] = useState(() => createSnapOutcomeStore());\n // Keyed on the style's values, so an inline `readoutStyle` literal keeps the layer.\n const { color, gapColor, lineWidth } = args.snapReadoutStyle ?? {};\n const snapReadoutLayer = useMemo(() => createSnapReadoutLayer(snapStore, {\n ...(color !== undefined ? { color } : {}),\n ...(gapColor !== undefined ? { gapColor } : {}),\n ...(lineWidth !== undefined ? { lineWidth } : {}),\n }), [snapStore, color, gapColor, lineWidth]);\n const overlaySlot = merged.pathEditingOverlay;\n const overlayConfig = overlaySlot && !isCustomEntry(overlaySlot) ? overlaySlot : undefined;\n const overlayAnchor = overlaySlot === null ? 'dispatcherOverlay' : 'pathEditingOverlay';\n const pathEditingOverlayLayer = usePathEditOverlayLayer(pathEditState, {\n nodeOf: (id) => scene.get(id as NodeId),\n planeOfNode: args.planeOfNode,\n }, overlayConfig);\n\n const { layer: selectionOverlayLayer, rotationBadge } = useSelectionOverlayLayer<TPose>(\n merged.selectionOverlay,\n pathEditState.getEditingIds,\n poseDescriptor,\n args.boundsIncludeStroke ? drawnBoundsScene(scene) : undefined,\n resizeHandles,\n );\n\n const layers = useMemo<CanvasLayers>(() => ({\n ...builtLayersOf(merged),\n scene: sceneLayers,\n selectionOverlay: selectionOverlayLayer ? { layer: selectionOverlayLayer } : null,\n departureGhost: { layer: departureLayer, after: 'scene' },\n previewGhost: { layer: previewLayer, after: 'departureGhost' },\n dispatcherOverlay: { layer: dispatcherOverlay, after: 'previewGhost' },\n pathEditingOverlay: overlaySlot === null || isCustomEntry(overlaySlot)\n ? overlaySlot\n : { layer: pathEditingOverlayLayer, after: 'selectionOverlay' },\n ...(args.snapReadout ? { snapReadout: { layer: snapReadoutLayer, after: overlayAnchor } } : {}),\n ...(slops ? { slopsDebug: { layer: slopsLayer, after: args.snapReadout ? 'snapReadout' : overlayAnchor } } : {}),\n }), [\n merged, sceneLayers, selectionOverlayLayer, departureLayer, previewLayer, dispatcherOverlay, pathEditingOverlayLayer, overlaySlot, overlayAnchor,\n snapReadoutLayer, args.snapReadout, slops, slopsLayer,\n ]);\n\n return { layers, rotationBadge, resizeHandles, chromeAffordancesRef, snapStore, departures };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,qBAA0D,MAyCxE;CACA,MAAM,EAAE,OAAO,SAAS,gBAAgB,UAAU,YAAY,eAAe,OAAO,gBAAgB;CAEpG,MAAM,YAAY,gBAAgB;CAClC,MAAM,SAAS,cACP,wBAAwB,KAAK,QAAQ,aAAa,SAAS,GACjE;EAAC,KAAK;EAAQ;EAAa;CAAS,CACtC;CAEA,MAAM,EAAE,QAAQ,WAAW,QAAQ,gBAAgB,mBAAmB;EACpE,MAAM,OAAO;EACb;EACA;EACA;EACA,gBAAgB,KAAK;EACrB,UAAU,KAAK;EACf,YAAY,KAAK;CACnB,CAAC;CAED,MAAM,eAAe,qBAA2C;EAAE;EAAO;EAAW;CAAW,CAAC;CAChG,MAAM,aAAa,kBAAkB,OAAO,KAAK,cAAc;CAC/D,MAAM,iBAAiB,uBAAuB;EAAE;EAAO;EAAW;CAAW,CAAC;CAC9E,MAAM,oBAAoB,0BAA0B,EAAE,WAAW,CAAC;CAElE,MAAM,cAAc,KAAK,YAAY,mBAAmB,cAAc,aAAa,SAAS,CAAC,CAAC;CAC9F,MAAM,QAAQ,KAAK,YAAY,QAAQ,eAAe;CACtD,MAAM,gBAAgB,eAAmC;EAAE;EAAO;CAAY,IAAI,CAAC,OAAO,WAAW,CAAC;CAEtG,MAAM,uBAAuB,OAA8B,CAAC,CAAC;CAC7D,MAAM,aAAa,cACX,sBAAsB,EAAE,sBAAsB,qBAAqB,QAAQ,CAAC,GAClF,CAAC,CACH;CAEA,MAAM,CAAC,aAAa,eAAe,uBAAuB,CAAC;CAE3D,MAAM,EAAE,OAAO,UAAU,cAAc,KAAK,oBAAoB,CAAC;CACjE,MAAM,mBAAmB,cAAc,uBAAuB,WAAW;EACvE,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;EACvC,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;EAC7C,GAAI,cAAc,SAAY,EAAE,UAAU,IAAI,CAAC;CACjD,CAAC,GAAG;EAAC;EAAW;EAAO;EAAU;CAAS,CAAC;CAC3C,MAAM,cAAc,OAAO;CAC3B,MAAM,gBAAgB,eAAe,CAAC,cAAc,WAAW,IAAI,cAAc;CACjF,MAAM,gBAAgB,gBAAgB,OAAO,sBAAsB;CACnE,MAAM,0BAA0B,wBAAwB,eAAe;EACrE,SAAS,OAAO,MAAM,IAAI,EAAY;EACtC,aAAa,KAAK;CACpB,GAAG,aAAa;CAEhB,MAAM,EAAE,OAAO,uBAAuB,kBAAkB,yBACtD,OAAO,kBACP,cAAc,eACd,gBACA,KAAK,sBAAsB,iBAAiB,KAAK,IAAI,QACrD,aACF;CAmBA,OAAO;EAAE,QAjBM,eAA6B;GAC1C,GAAG,cAAc,MAAM;GACvB,OAAO;GACP,kBAAkB,wBAAwB,EAAE,OAAO,sBAAsB,IAAI;GAC7E,gBAAgB;IAAE,OAAO;IAAgB,OAAO;GAAQ;GACxD,cAAc;IAAE,OAAO;IAAc,OAAO;GAAiB;GAC7D,mBAAmB;IAAE,OAAO;IAAmB,OAAO;GAAe;GACrE,oBAAoB,gBAAgB,QAAQ,cAAc,WAAW,IACjE,cACA;IAAE,OAAO;IAAyB,OAAO;GAAmB;GAChE,GAAI,KAAK,cAAc,EAAE,aAAa;IAAE,OAAO;IAAkB,OAAO;GAAc,EAAE,IAAI,CAAC;GAC7F,GAAI,QAAQ,EAAE,YAAY;IAAE,OAAO;IAAY,OAAO,KAAK,cAAc,gBAAgB;GAAc,EAAE,IAAI,CAAC;EAChH,IAAI;GACF;GAAQ;GAAa;GAAuB;GAAgB;GAAc;GAAmB;GAAyB;GAAa;GACnI;GAAkB,KAAK;GAAa;GAAO;EAC7C,CAES;EAAQ;EAAe;EAAe;EAAsB;EAAW;CAAW;AAC7F"}
|
|
1
|
+
{"version":3,"file":"useSceneCanvasLayers.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneCanvasLayers.ts"],"sourcesContent":["import { useMemo, useRef, useState, type MutableRefObject } from 'react';\nimport type { ColorOverrideRegistry } from '@weasel-js/animator';\nimport type { Dispatcher } from '@weasel-js/routing';\nimport type { Affordance } from 'affordances/types';\nimport type { Node, NodeId, Scene } from 'core/scene/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport type { PlaneMap } from 'core/viewport/parallax';\nimport type { View } from 'core/viewport/view';\nimport type { PathEditState } from 'features/paths/pathEditState';\nimport { usePathEditOverlayLayer } from 'features/paths/usePathEdit';\nimport type { rotationBadgeOf } from 'features/selection/overlay';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport type { HierarchicalAdapter } from '../buildSceneTree';\nimport { builtLayersOf, isCustomEntry, type CanvasLayers } from '../layerSlots';\nimport { createSlopsDebugLayer } from '../slopsDebugLayer';\nimport { useTargetBasePx } from 'core/device/useTargetBasePx';\nimport { targetSizesPx } from 'core/device/targets';\nimport type { ResizeHandleLayout } from 'interactions/actions/resize/cornerHandles';\nimport { mergeLayersWithDefaults } from './mergeLayers';\nimport type { SceneCanvasLayers } from './props';\nimport { useDispatcherOverlayLayer } from './useDispatcherOverlayLayer';\nimport { usePreviewGhostLayer } from './usePreviewGhostLayer';\nimport { useDepartureGhostLayer } from './useDepartureGhostLayer';\nimport { useEffectSourcesLayer } from 'features/pathEffects/useEffectSourcesLayer';\nimport type { Departures } from '../../animation/departures';\nimport { usePoseTransition, type PoseTransitionSeat } from '../../animation/poseTransition';\nimport { useSceneSlotLayers } from './useSceneSlotLayers';\nimport { useSelectionOverlayLayer } from './useSelectionOverlayLayer';\nimport { drawnBoundsScene } from '../derivedPath';\nimport { useEnteredGroupFrameLayer } from 'features/selection/enteredGroupFrame';\nimport type { EnteredGroupApi } from '../deps/enteredGroup';\nimport { createSnapOutcomeStore, type SnapOutcomeStore } from 'features/snap/outcomeStore';\nimport { createSnapReadoutLayer } from 'features/snap/readoutLayer';\nimport type { SnapReadoutStyle } from 'features/snap/types';\n\n/**\n * The layer map `<SceneCanvas>` hands `<Canvas>`: the caller's `layers` over\n * the kit defaults, with the scene and selection-overlay slots built, and the\n * canvas's own layers anchored around them. Removed nodes leave just above\n * the scene, the preview ghost above them, a selected path effect's hidden sources above that, the dispatcher's\n * overlay (marquee, lasso) above those, and the\n * anchor overlay above the selection chrome.\n */\nexport function useSceneCanvasLayers<TData, TLayer extends string, TPose>(args: {\n /** `<SceneCanvas layers>`. Omitted, the result is the full default set. */\n layers: SceneCanvasLayers<Node<TData, TLayer, TPose>, TPose> | undefined;\n /** The device profile's, which sizes the default handles. */\n targetScale: number;\n scene: Scene<TData, TLayer, TPose>;\n adapter: HierarchicalAdapter<Node<TData, TLayer, TPose>, TPose>;\n poseDescriptor: PoseDescriptor<TPose>;\n alphaFor: ((id: string) => number) | undefined;\n colorOverrides: ColorOverrideRegistry | undefined;\n boundsOf: (id: string) => Bounds | null; enteredGroup: EnteredGroupApi; frameColor: { read: () => string; changed: (fn: () => void) => () => void };\n planeOfNode: (id: string, view: View) => PlaneMap | null;\n /** `<SceneCanvas poseTransition>`, whose ghosts of removed nodes this draws. */\n poseTransition: PoseTransitionSeat<TPose> | null | undefined;\n /** Ids an in-flight gesture is ghosting. */\n previewIds: () => Iterable<string> | null;\n dispatcher: Dispatcher;\n /** Whether to mount the snap readout layer. */\n snapReadout: boolean;\n snapReadoutStyle?: SnapReadoutStyle;\n pathEditState: PathEditState;\n /** `debug.slops`: paint the hit regions of the chrome affordances. */\n slops: boolean;\n /** `geometry.boundsIncludeStroke`: whether chrome sits on the stroke. */\n boundsIncludeStroke: boolean;\n /** `selectTool`'s `handleHitRadius` and `resize.edgeHandles`, resolved here into\n * the one {@link ResizeHandleLayout} the overlay paints and the hit-test reads. */\n selectTool?: { handleHitRadius?: number; resize?: { edgeHandles?: boolean } };\n}): {\n layers: CanvasLayers;\n /** Where the selection overlay paints its rotate badge, or null. */\n rotationBadge: ReturnType<typeof rotationBadgeOf>;\n /** Which resize handles exist and their hit radius, for every hit-test of this canvas. */\n resizeHandles: ResizeHandleLayout;\n /** The affordances the gesture mounter hit-tests, which it publishes here\n * so the slops halos are the hit regions rather than a copy of them. */\n chromeAffordancesRef: MutableRefObject<readonly Affordance[]>;\n /** The marks the readout layer draws; the canvas's snap dep writes them. */\n snapStore: SnapOutcomeStore;\n /** The removed nodes the seat is seeing off. */\n departures: Departures<TPose, Node<TData, TLayer, TPose>>;\n} {\n const { scene, adapter, poseDescriptor, alphaFor, dispatcher, pathEditState, slops, targetScale } = args;\n\n const baseSizes = useTargetBasePx();\n const merged = useMemo(\n () => mergeLayersWithDefaults(args.layers, targetScale, baseSizes),\n [args.layers, targetScale, baseSizes],\n );\n\n const { config: sceneSlot, layers: sceneLayers } = useSceneSlotLayers({\n slot: merged.scene,\n scene,\n adapter,\n alphaFor,\n colorOverrides: args.colorOverrides,\n boundsOf: args.boundsOf,\n previewIds: args.previewIds,\n });\n // Takes the scene-wired slot, so a ghosted derived node still resolves its path.\n const previewLayer = usePreviewGhostLayer<TData, TLayer, TPose>({ scene, sceneSlot, dispatcher });\n const departures = usePoseTransition(scene, args.poseTransition);\n const departureLayer = useDepartureGhostLayer({ scene, sceneSlot, departures });\n const effectSourcesLayer = useEffectSourcesLayer(scene);\n const dispatcherOverlay = useDispatcherOverlayLayer({ dispatcher });\n\n const hitRadiusPx = args.selectTool?.handleHitRadius ?? targetSizesPx(targetScale, baseSizes).handle;\n const edges = args.selectTool?.resize?.edgeHandles ?? true;\n const resizeHandles = useMemo<ResizeHandleLayout>(() => ({ edges, hitRadiusPx }), [edges, hitRadiusPx]);\n\n const chromeAffordancesRef = useRef<readonly Affordance[]>([]);\n const slopsLayer = useMemo(\n () => createSlopsDebugLayer({ getAffordances: () => chromeAffordancesRef.current }),\n [],\n );\n\n const [snapStore] = useState(() => createSnapOutcomeStore());\n // Keyed on the style's values, so an inline `readoutStyle` literal keeps the layer.\n const { color, gapColor, lineWidth } = args.snapReadoutStyle ?? {};\n const snapReadoutLayer = useMemo(() => createSnapReadoutLayer(snapStore, {\n ...(color !== undefined ? { color } : {}),\n ...(gapColor !== undefined ? { gapColor } : {}),\n ...(lineWidth !== undefined ? { lineWidth } : {}),\n }), [snapStore, color, gapColor, lineWidth]);\n const overlaySlot = merged.pathEditingOverlay;\n const overlayConfig = overlaySlot && !isCustomEntry(overlaySlot) ? overlaySlot : undefined;\n const overlayAnchor = overlaySlot === null ? 'dispatcherOverlay' : 'pathEditingOverlay';\n const pathEditingOverlayLayer = usePathEditOverlayLayer(pathEditState, {\n nodeOf: (id) => scene.get(id as NodeId),\n planeOfNode: args.planeOfNode,\n }, overlayConfig);\n\n const { layer: selectionOverlayLayer, rotationBadge } = useSelectionOverlayLayer<TPose>(\n merged.selectionOverlay,\n pathEditState.getEditingIds,\n poseDescriptor,\n args.boundsIncludeStroke ? drawnBoundsScene(scene) : undefined,\n resizeHandles,\n );\n\n const enteredFrameLayer = useEnteredGroupFrameLayer(args.enteredGroup, args.boundsOf, args.frameColor);\n const layers = useMemo<CanvasLayers>(() => ({\n ...builtLayersOf(merged),\n scene: sceneLayers,\n selectionOverlay: selectionOverlayLayer ? { layer: selectionOverlayLayer } : null,\n enteredGroupFrame: { layer: enteredFrameLayer, before: 'selectionOverlay' },\n departureGhost: { layer: departureLayer, after: 'scene' },\n previewGhost: { layer: previewLayer, after: 'departureGhost' },\n effectSources: { layer: effectSourcesLayer, after: 'previewGhost' },\n dispatcherOverlay: { layer: dispatcherOverlay, after: 'effectSources' },\n pathEditingOverlay: overlaySlot === null || isCustomEntry(overlaySlot)\n ? overlaySlot\n : { layer: pathEditingOverlayLayer, after: 'selectionOverlay' },\n ...(args.snapReadout ? { snapReadout: { layer: snapReadoutLayer, after: overlayAnchor } } : {}),\n ...(slops ? { slopsDebug: { layer: slopsLayer, after: args.snapReadout ? 'snapReadout' : overlayAnchor } } : {}),\n }), [merged, sceneLayers, selectionOverlayLayer, enteredFrameLayer, departureLayer, previewLayer, effectSourcesLayer, dispatcherOverlay, pathEditingOverlayLayer, overlaySlot, overlayAnchor, snapReadoutLayer, args.snapReadout, slops, slopsLayer,\n ]);\n\n return { layers, rotationBadge, resizeHandles, chromeAffordancesRef, snapStore, departures };\n}\n"],"mappings":"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"}
|
|
@@ -6,12 +6,14 @@ function useSceneCanvasTools(args) {
|
|
|
6
6
|
const { props, select, enabled } = args;
|
|
7
7
|
const { tools, internal, takeover } = useCanvasTools({
|
|
8
8
|
select,
|
|
9
|
+
groupEntry: enabled.has("select") ? args.groupEntry : null,
|
|
9
10
|
enabled,
|
|
10
11
|
tools: props.tools,
|
|
11
12
|
defaultTools: props.defaultTools,
|
|
12
13
|
toolOptions: props.toolOptions,
|
|
13
14
|
inertia: props.viewport?.inertia,
|
|
14
15
|
ambient: props.ambient,
|
|
16
|
+
actions: props.actions,
|
|
15
17
|
pressFeedback: props.longPress?.feedback,
|
|
16
18
|
initialActiveTool: props.initialActiveTool,
|
|
17
19
|
modes: props.modes,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSceneCanvasTools.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneCanvasTools.ts"],"sourcesContent":["import type { Tool } from '../../tools/overlayBinding';\nimport type { EnabledPresets, FeatureBindingOptions, PresetSource } from './features';\nimport type { SceneCanvasProps } from './props';\nimport { useCanvasTools, useFeatureBindings } from './useCanvasTools';\n\ntype Props = SceneCanvasProps<unknown, string, unknown>;\n\n/** The `<SceneCanvas>` props its tools and preset bindings read. */\nexport type SceneCanvasToolProps = Pick<\n Props,\n | 'tools' | 'defaultTools' | 'toolOptions' | 'viewport' | 'ambient' | 'longPress'\n | 'initialActiveTool' | 'modes' | 'onToolsCreated' | 'selectTool'\n>;\n\n/** The canvas's tools, and the contributions and standard actions its presets bring. */\nexport function useSceneCanvasTools(args: {\n props: SceneCanvasToolProps;\n select: Tool;\n /** The select tool's move options, which the selection's `move` binding takes. */\n move: unknown;\n enabled: EnabledPresets;\n presetSource: PresetSource;\n}) {\n const { props, select, enabled } = args;\n const { tools, internal, takeover } = useCanvasTools({\n select,\n enabled,\n tools: props.tools,\n defaultTools: props.defaultTools,\n toolOptions: props.toolOptions,\n inertia: props.viewport?.inertia,\n ambient: props.ambient,\n pressFeedback: props.longPress?.feedback,\n initialActiveTool: props.initialActiveTool,\n modes: props.modes,\n onToolsCreated: props.onToolsCreated,\n });\n const { contributions, standardActions } = useFeatureBindings({\n enabled, pool: args.presetSource.pool,\n tools,\n move: args.move as FeatureBindingOptions['move'],\n rotate: (props.selectTool?.rotate || undefined) as FeatureBindingOptions['rotate'],\n });\n return { tools, internal, takeover, contributions, standardActions };\n}\n"],"mappings":";;;;AAeA,SAAgB,oBAAoB,
|
|
1
|
+
{"version":3,"file":"useSceneCanvasTools.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneCanvasTools.ts"],"sourcesContent":["import type { Contribution, Tool } from '../../tools/overlayBinding';\nimport type { EnabledPresets, FeatureBindingOptions, PresetSource } from './features';\nimport type { SceneCanvasProps } from './props';\nimport { useCanvasTools, useFeatureBindings } from './useCanvasTools';\n\ntype Props = SceneCanvasProps<unknown, string, unknown>;\n\n/** The `<SceneCanvas>` props its tools and preset bindings read. */\nexport type SceneCanvasToolProps = Pick<\n Props,\n | 'tools' | 'defaultTools' | 'toolOptions' | 'viewport' | 'ambient' | 'longPress'\n | 'initialActiveTool' | 'modes' | 'onToolsCreated' | 'selectTool' | 'actions'\n>;\n\n/** The canvas's tools, and the contributions and standard actions its presets bring. */\nexport function useSceneCanvasTools(args: {\n props: SceneCanvasToolProps;\n select: Tool;\n /** The select tool's move options, which the selection's `move` binding takes. */\n move: unknown;\n enabled: EnabledPresets;\n presetSource: PresetSource;\n /** Entering and leaving groups, installed with the `select` preset. */\n groupEntry: Contribution;\n}) {\n const { props, select, enabled } = args;\n const { tools, internal, takeover } = useCanvasTools({\n select,\n groupEntry: enabled.has('select') ? args.groupEntry : null,\n enabled,\n tools: props.tools,\n defaultTools: props.defaultTools,\n toolOptions: props.toolOptions,\n inertia: props.viewport?.inertia,\n ambient: props.ambient,\n actions: props.actions,\n pressFeedback: props.longPress?.feedback,\n initialActiveTool: props.initialActiveTool,\n modes: props.modes,\n onToolsCreated: props.onToolsCreated,\n });\n const { contributions, standardActions } = useFeatureBindings({\n enabled, pool: args.presetSource.pool,\n tools,\n move: args.move as FeatureBindingOptions['move'],\n rotate: (props.selectTool?.rotate || undefined) as FeatureBindingOptions['rotate'],\n });\n return { tools, internal, takeover, contributions, standardActions };\n}\n"],"mappings":";;;;AAeA,SAAgB,oBAAoB,MASjC;CACD,MAAM,EAAE,OAAO,QAAQ,YAAY;CACnC,MAAM,EAAE,OAAO,UAAU,aAAa,eAAe;EACnD;EACA,YAAY,QAAQ,IAAI,QAAQ,IAAI,KAAK,aAAa;EACtD;EACA,OAAO,MAAM;EACb,cAAc,MAAM;EACpB,aAAa,MAAM;EACnB,SAAS,MAAM,UAAU;EACzB,SAAS,MAAM;EACf,SAAS,MAAM;EACf,eAAe,MAAM,WAAW;EAChC,mBAAmB,MAAM;EACzB,OAAO,MAAM;EACb,gBAAgB,MAAM;CACxB,CAAC;CACD,MAAM,EAAE,eAAe,oBAAoB,mBAAmB;EAC5D;EAAS,MAAM,KAAK,aAAa;EACjC;EACA,MAAM,KAAK;EACX,QAAS,MAAM,YAAY,UAAU;CACvC,CAAC;CACD,OAAO;EAAE;EAAO;EAAU;EAAU;EAAe;CAAgB;AACrE"}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { EMPTY_CHROME_STATE } from "../../core/selection/chromeState.js";
|
|
2
|
-
import { useDispatchDepSources } from "../deps/dispatchSources.js";
|
|
3
2
|
import { anchorStateFrom, buildAffordanceAt, buildClassifyTarget, layerAffordance } from "../affordanceAt.js";
|
|
4
|
-
import { pathFromPlane } from "../planeClips.js";
|
|
5
3
|
import { useOptionalViewRegistry } from "../viewRegistry.js";
|
|
4
|
+
import { useDispatchDepSources } from "../deps/dispatchSources.js";
|
|
5
|
+
import { pathFromPlane } from "../planeClips.js";
|
|
6
|
+
import { VIEW_MARKER_ENTRY_ID } from "../../features/viewports/viewMarkerDrag.js";
|
|
6
7
|
import { useKitSettingsDepSource } from "../deps/kitSettings.js";
|
|
7
8
|
import { useChromeAffordances } from "./useChromeAffordances.js";
|
|
8
9
|
import { useSurfaceRouting } from "./useSurfaceRouting.js";
|
|
9
10
|
import { traceConversion } from "./coordTrace.js";
|
|
10
|
-
import { useCallback, useInsertionEffect, useMemo, useState } from "react";
|
|
11
|
+
import { useCallback, useEffect, useInsertionEffect, useMemo, useState } from "react";
|
|
11
12
|
import { useLatest } from "@weasel-js/react";
|
|
12
13
|
import { createLongPressStore } from "@weasel-js/routing";
|
|
13
14
|
import { useActionsRegistry, useDepRegistry, useGestureDispatcher } from "@weasel-js/routing/react";
|
|
@@ -57,9 +58,22 @@ function useAffordanceAt(inputs, depRegistry, viewRegistry) {
|
|
|
57
58
|
return (worldPoint) => {
|
|
58
59
|
const extra = canvasApiRef.current?.hitTestExtras?.(worldPoint.x, worldPoint.y);
|
|
59
60
|
if (extra) return layerAffordance(extra.layerId, extra.hit);
|
|
61
|
+
const marker = viewRegistry?.markerAt(null, worldPoint);
|
|
62
|
+
if (marker) return marker;
|
|
60
63
|
return affordanceAt ? affordanceAt(worldPoint) : null;
|
|
61
64
|
};
|
|
62
|
-
}, [
|
|
65
|
+
}, [
|
|
66
|
+
affordanceAt,
|
|
67
|
+
canvasApiRef,
|
|
68
|
+
viewRegistry
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
/** Markers take presses only while the routed tools carry the marker drag:
|
|
72
|
+
* `actions={null}` and a tools takeover both leave it out. */
|
|
73
|
+
function useMarkerGate(viewRegistry, ambient) {
|
|
74
|
+
const installed = ambient.some((entry) => entry.id === VIEW_MARKER_ENTRY_ID);
|
|
75
|
+
const installedRef = useLatest(installed);
|
|
76
|
+
useEffect(() => viewRegistry?.gateMarkers(() => installedRef.current), [viewRegistry, installedRef]);
|
|
63
77
|
}
|
|
64
78
|
/** Which body a world point lands on, its selection state, and its routing kind. */
|
|
65
79
|
function useClassifyTarget(inputs, kindOfNodeRef) {
|
|
@@ -102,6 +116,7 @@ function useSceneGestureDispatcher(inputs) {
|
|
|
102
116
|
contributions
|
|
103
117
|
]);
|
|
104
118
|
const kindOfNodeRef = useLatest(kindOfNode);
|
|
119
|
+
useMarkerGate(viewRegistry, tools.ambient);
|
|
105
120
|
const affordanceWithLayers = useAffordanceAt(inputs, depRegistry, viewRegistry);
|
|
106
121
|
const classifyTarget = useClassifyTarget(inputs, kindOfNodeRef);
|
|
107
122
|
const clientToWorld = useCallback((clientX, clientY) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSceneGestureDispatcher.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneGestureDispatcher.ts"],"sourcesContent":["import { useLatest } from '@weasel-js/react';\nimport { createLongPressStore, type Dispatcher, type DispatchTrace, type TraceRing } from '@weasel-js/routing';\nimport { useActionsRegistry, useDepRegistry, useGestureDispatcher, type DepRegistry } from '@weasel-js/routing/react';\nimport type { Affordance } from 'affordances/types';\nimport { EMPTY_CHROME_STATE } from 'core/selection/chromeState';\nimport type { View } from 'core/viewport/view';\nimport type { RuleCtx } from 'features/chrome-caps';\nimport type React from 'react';\nimport { useCallback, useInsertionEffect, useMemo, useState } from 'react';\nimport type { Contribution } from '../../tools/overlayBinding';\nimport type { ToolsApi } from '../../tools/overlayBinding';\nimport { useDispatchDepSources } from '../deps/dispatchSources';\nimport { anchorStateFrom, buildAffordanceAt, buildClassifyTarget, layerAffordance } from '../affordanceAt';\nimport { useChromeAffordances } from './useChromeAffordances';\nimport type { CanvasExtensionApi } from '../canvasExtension';\nimport { useKitSettingsDepSource } from '../deps/kitSettings';\nimport { pathFromPlane } from '../planeClips';\nimport { useOptionalViewRegistry, type ViewRegistry } from '../viewRegistry';\nimport { useSurfaceRouting } from './useSurfaceRouting';\nimport { traceConversion, type CoordTraceEntry } from './coordTrace';\nimport type { SceneCanvasLongPress } from './props';\n\n/** What `<SceneCanvas>` hands {@link useSceneGestureDispatcher}. */\nexport interface SceneGestureDispatcherInputs {\n canvasRef: React.RefObject<HTMLElement | null>;\n /** Holds the full `CanvasExtensionApi` so the gesture dispatcher can call\n * `requestRedraw()` between pointer events. */\n canvasApiRef: React.RefObject<CanvasExtensionApi | null>;\n tools: ToolsApi;\n /** Always-live entries the canvas installs beside `tools` — the selection's\n * `move` / `transform` bindings. Ahead of the tools' ambient entries, so a\n * tie at one specificity goes to the kit's. */\n contributions: readonly Contribution[];\n enabled: boolean;\n /** When false, the dispatcher leaves keyboard listeners unattached so\n * keyboard-bound actions never fire. Wired to `enableKeybindings`. */\n keyboard: boolean;\n /** Take the keyboard on mount. Wired to `claimKeys`. */\n claimKeys: boolean;\n selectionRef?: React.RefObject<import('core/selection/useSelection').SelectionApi>;\n boundsOf?: (id: string) => import('core/viewport/fitViewToBounds').Bounds | null;\n /** The parallax plane a node is drawn through, under the surface's camera —\n * so path anchors are hit where the plane draws them. */\n planeOfNode?: (id: string) => import('core/viewport/parallax').PlaneMap | null;\n pickEvery?: (worldX: number, worldY: number) => string[];\n /** Single topmost hit (collapses parent/child via `pickTopMostHit`), used to\n * classify the body under the pointer. Must match the select tool's own\n * resolution so a child node isn't misclassified by its parent's selection.\n * Falls back to `pickEvery`'s last id when absent. */\n pickBest?: (worldX: number, worldY: number) => string | null;\n /** Resolves a hit node id to its routing-trait kind, so the body\n * classification carries `kind` alongside `bodyTarget` and the\n * `kind:<k>` / `kind:<k>:selected` target forms can match. Absent when\n * the consumer opted out of routing (`routing={[]}`). */\n kindOfNode?: (id: string) => string | undefined;\n /** The node a double-click on a hit names (`doubleClickTarget`), for the classification. */\n doubleClickNames?: (hitId: string) => string;\n viewRef?: React.RefObject<View>;\n /** Pre-created dispatcher to pump events into. When omitted,\n * `useGestureDispatcher` creates one internally (legacy path). */\n dispatcher?: Dispatcher;\n /** Where the dispatcher records its decisions and `clientToWorld` its conversions. */\n traces: { dispatch: DispatchTrace; coords: TraceRing<CoordTraceEntry> };\n /** Chrome-caps visibility resolver factory. Threaded into\n * `buildAffordanceAt` so the hit-test pipeline gates corner / rotate /\n * anchor affordances on the same chrome ids the renderer uses. */\n getIsVisibleForCanvas?: () => (id: string) => boolean;\n /** Live RuleCtx factory. Threaded into `useGestureDispatcher` so the\n * dispatcher's eligibility filter sees the same mode/capabilities/selection\n * view of the world that chrome-caps does. */\n getRuleCtx?: () => RuleCtx | undefined;\n /** `DeviceProfile.targetScale`, so the affordance grab zones resolve to the\n * same sizes the chrome paints at. */\n targetScale?: number;\n /** Consumer override for the corner-handle grab radius\n * (`selectTool.handleHitRadius`). Undefined takes the device-scaled size. */\n handleHitRadius?: number;\n /** `selectTool.resize.edgeHandles`: resize handles at the edge midpoints. Default true. */\n edgeHandles?: boolean;\n /** The rotate badge the selection overlay paints, grabbable where drawn. */\n rotationBadge?: { distancePx: number; sizePx: number } | null;\n /** Receives the affordances this mounter hit-tests, for the slops overlay. */\n chromeAffordancesRef?: React.MutableRefObject<readonly Affordance[]>;\n /** Fires on every synthesized double click, in world coords. Backs the\n * `onDoubleClick` prop — see the option's doc on\n * `UseGestureDispatcherOptions` for why it's an observer, not a binding. */\n onDoubleClick?: (world: { x: number; y: number }) => void;\n /** Fires after every synthesized click. Backs the `onClick` prop. */\n onClick?: (world: { x: number; y: number }) => void;\n /** Backs `<SceneCanvas longPress>`. */\n longPress?: SceneCanvasLongPress;\n}\n\ntype AffordanceInputs = Pick<\n SceneGestureDispatcherInputs,\n | 'canvasApiRef' | 'selectionRef' | 'boundsOf' | 'planeOfNode' | 'viewRef' | 'getIsVisibleForCanvas'\n | 'targetScale' | 'handleHitRadius' | 'edgeHandles' | 'rotationBadge' | 'chromeAffordancesRef'\n>;\n\n/** What a press on the canvas grabs: a registered layer's widget, else a piece of the\n * selection chrome. */\nfunction useAffordanceAt(inputs: AffordanceInputs, depRegistry: DepRegistry, viewRegistry: ViewRegistry | null) {\n const {\n canvasApiRef, selectionRef, boundsOf, planeOfNode, viewRef, getIsVisibleForCanvas,\n targetScale, handleHitRadius, edgeHandles, rotationBadge, chromeAffordancesRef,\n } = inputs;\n // `getAnchorState` thunk for `buildAffordanceAt` — reads the live\n // `editAnchors` dep from the registry at call time.\n const depRegistryRef = useLatest(depRegistry);\n const planeOfNodeRef = useLatest(planeOfNode);\n const getAnchorState = useMemo(() => anchorStateFrom(\n () => depRegistryRef.current,\n (id, path) => pathFromPlane(path, planeOfNodeRef.current?.(id) ?? null),\n ), [depRegistryRef, planeOfNodeRef]);\n\n // Build the `affordanceAt` thunk. Takes world coords and delegates to\n // `buildAffordanceAt` for handle hit-testing.\n const affordances = useChromeAffordances({ targetScale, handleHitRadius, edgeHandles, rotationBadge, getAnchorState });\n useInsertionEffect(() => {\n if (chromeAffordancesRef) chromeAffordancesRef.current = affordances;\n });\n const affordanceAt = useMemo(() => {\n if (!selectionRef || !boundsOf || !viewRef) return undefined;\n return buildAffordanceAt({\n // The canvas's own view chrome, built once by its helpers — not a\n // second construction here that has to agree with the painted one.\n getChromeState: () => viewRegistry?.surface()?.chromeState() ?? EMPTY_CHROME_STATE,\n // Radii are declared in screen pixels and converted against this view.\n getView: () => viewRef.current ?? { x: 0, y: 0, scale: { x: 1, y: 1 } },\n affordances,\n // Chrome-caps resolver: keep the affordance hit-test in sync with what\n // the renderer is actually painting. Without this, a click on a (no\n // longer visible) resize handle position still classifies as a resize\n // handle — e.g. an anchor drag in path-edit mode resizes the path's\n // bounding box instead of moving the anchor.\n ...(getIsVisibleForCanvas ? { getIsVisible: () => getIsVisibleForCanvas() } : {}),\n });\n }, [selectionRef, boundsOf, viewRef, affordances, getIsVisibleForCanvas, viewRegistry]);\n\n return useMemo(() => {\n // Note this is NOT gated on `affordanceAt` being built: registered layers\n // produce affordances of their own, and a consumer with no selection\n // chrome (a canvas that is nothing but a HUD, say) still needs those.\n return (worldPoint: { x: number; y: number }) => {\n // Registered layers first: they draw on top of the kit's own chrome, so\n // they get first refusal on the point. A hit becomes an `AffordanceHit`\n // whose kind names the layer, carrying whatever the layer's hit-test\n // resolved — which is how `@weasel-js/hud` routes a press on one of its\n // widgets to its own action instead of the active tool.\n const extra = canvasApiRef.current?.hitTestExtras?.(worldPoint.x, worldPoint.y);\n if (extra) return layerAffordance(extra.layerId, extra.hit);\n return affordanceAt ? affordanceAt(worldPoint) : null;\n };\n }, [affordanceAt, canvasApiRef]);\n}\n\n/** Which body a world point lands on, its selection state, and its routing kind. */\nfunction useClassifyTarget(\n inputs: Pick<SceneGestureDispatcherInputs, 'selectionRef' | 'pickEvery' | 'pickBest' | 'viewRef' | 'doubleClickNames'>,\n kindOfNodeRef: { readonly current: SceneGestureDispatcherInputs['kindOfNode'] },\n) {\n const { selectionRef, pickEvery, pickBest, viewRef } = inputs;\n // Stable refs for the optional thunk inputs so the thunks themselves are\n // stable function identities across renders (no need to pass them as deps).\n const pickEveryRef = useLatest(pickEvery);\n const pickBestRef = useLatest(pickBest);\n const doubleClickNamesRef = useLatest(inputs.doubleClickNames);\n // Build the `classifyTarget` thunk. Takes world coords and delegates to\n // `buildClassifyTarget`.\n return useMemo(() => {\n if (!selectionRef || !pickEvery || !viewRef) return undefined;\n return buildClassifyTarget(\n () => selectionRef.current?.current ?? [],\n // Use the select tool's own topmost-hit resolution (`pickTopMostHit`,\n // which collapses parent/child) so a child body is classified by ITS\n // OWN selection — not its parent's.\n (wx: number, wy: number) => {\n if (pickBestRef.current) return pickBestRef.current(wx, wy);\n const ids = pickEveryRef.current?.(wx, wy) ?? [];\n return ids.length > 0 ? ids[ids.length - 1] : null;\n },\n // Node kind comes from the routing trait — the same classifier that\n // names `Hit.kind` — so `target: 'kind:text'` on a binding speaks the\n // vocabulary the consumer already declared in `routing`, rather than a\n // second one invented for bindings. `routing={[]}` opts out and leaves\n // every `kind:` target unmatchable.\n (id: string) => kindOfNodeRef.current?.(id),\n (id: string) => doubleClickNamesRef.current?.(id) ?? id,\n );\n }, [selectionRef, pickEvery, viewRef, pickBestRef, pickEveryRef, kindOfNodeRef, doubleClickNamesRef]);\n}\n\n/** Runs this canvas's gesture dispatcher over its tools, contributions, and chrome. */\nexport function useSceneGestureDispatcher(inputs: SceneGestureDispatcherInputs): void {\n const {\n canvasRef, canvasApiRef, tools, contributions, enabled, keyboard, claimKeys, viewRef,\n kindOfNode, dispatcher, traces, getRuleCtx, onDoubleClick, onClick,\n longPress: { duration: longPressDuration, haptics: longPressHaptics } = {},\n } = inputs;\n const registry = useActionsRegistry();\n const depRegistry = useDepRegistry();\n const viewRegistry = useOptionalViewRegistry();\n const entriesById = useMemo<ReadonlyMap<string, Contribution>>(() => {\n const m = new Map<string, Contribution>();\n for (const [id, tool] of Object.entries(tools.registry)) {\n m.set(id, tool);\n }\n for (const entry of contributions) m.set(entry.id, entry);\n // Ambient entries too — their bindings assemble at ambient scope, and the\n // dispatcher resolves them through this same map.\n for (const entry of tools.ambient) m.set(entry.id, entry);\n return m;\n }, [tools.registry, tools.ambient, contributions]);\n\n const kindOfNodeRef = useLatest(kindOfNode);\n\n const affordanceWithLayers = useAffordanceAt(inputs, depRegistry, viewRegistry);\n const classifyTarget = useClassifyTarget(inputs, kindOfNodeRef);\n\n // Through the handle, so a `clientToWorld` override on the canvas applies.\n const clientToWorld = useCallback((clientX: number, clientY: number): { x: number; y: number } => {\n const api = canvasApiRef.current;\n const [x, y] = api ? api.clientToWorld(clientX, clientY) : [clientX, clientY];\n const world = { x, y };\n traceConversion(traces.coords, clientX, clientY, api ? canvasRef.current : null, viewRef?.current, world);\n return world;\n }, [canvasRef, canvasApiRef, viewRef, traces.coords]);\n\n // Stable callback that asks the canvas to redraw. Reads via ref so the\n // identity stays stable while the underlying API binds after mount.\n const requestRedraw = useCallback(() => {\n canvasApiRef.current?.requestRedraw?.();\n }, [canvasApiRef]);\n\n // The painted tier's channel, read live for the same reason — the handle it\n // lives on binds after mount.\n const paintedCursor = useCallback(() => canvasApiRef.current?.paintedCursor, [canvasApiRef]);\n const cursorSink = useCallback(() => canvasApiRef.current?.cursorSink, [canvasApiRef]);\n\n // The pending long-press, published as the `longPress` dep for feedback.\n const [longPressState] = useState(createLongPressStore);\n const longPress = useMemo(() => ({\n state: longPressState,\n ...(longPressDuration !== undefined ? { duration: longPressDuration } : {}),\n ...(longPressHaptics !== undefined ? { haptics: longPressHaptics } : {}),\n }), [longPressState, longPressDuration, longPressHaptics]);\n\n const routing = useSurfaceRouting(viewRegistry);\n\n // Hover cursors live in `useGestureDispatcher`'s hover-cursor pump:\n // affordance hits carry `AffordanceHit.cursor` (set by `buildAffordanceAt`),\n // and everything else is predicted via `Dispatcher.resolveOnly` +\n // `Action.cursor`. Nothing SceneCanvas-specific remains here.\n useGestureDispatcher({\n canvasRef,\n actions: registry!,\n entriesById,\n enabled,\n keyboard,\n claimKeys,\n affordanceAt: affordanceWithLayers,\n classifyTarget,\n dispatcher,\n trace: traces.dispatch,\n clientToWorld,\n requestRedraw,\n paintedCursor,\n cursorSink,\n getRuleCtx,\n onClick,\n onDoubleClick,\n longPress,\n ...routing,\n });\n useDispatchDepSources(longPressState, classifyTarget, kindOfNodeRef);\n useKitSettingsDepSource();\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useSceneGestureDispatcher.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneGestureDispatcher.ts"],"sourcesContent":["import { useLatest } from '@weasel-js/react';\nimport { createLongPressStore, type Dispatcher, type DispatchTrace, type TraceRing } from '@weasel-js/routing';\nimport { useActionsRegistry, useDepRegistry, useGestureDispatcher, type DepRegistry } from '@weasel-js/routing/react';\nimport type { Affordance } from 'affordances/types';\nimport { EMPTY_CHROME_STATE } from 'core/selection/chromeState';\nimport type { View } from 'core/viewport/view';\nimport type { RuleCtx } from 'features/chrome-caps';\nimport type React from 'react';\nimport { useCallback, useEffect, useInsertionEffect, useMemo, useState } from 'react';\nimport type { Contribution } from '../../tools/overlayBinding';\nimport type { ToolsApi } from '../../tools/overlayBinding';\nimport { useDispatchDepSources } from '../deps/dispatchSources';\nimport { anchorStateFrom, buildAffordanceAt, buildClassifyTarget, layerAffordance } from '../affordanceAt';\nimport { useChromeAffordances } from './useChromeAffordances';\nimport type { CanvasExtensionApi } from '../canvasExtension';\nimport { useKitSettingsDepSource } from '../deps/kitSettings';\nimport { pathFromPlane } from '../planeClips';\nimport { useOptionalViewRegistry, type ViewRegistry } from '../viewRegistry';\nimport { useSurfaceRouting } from './useSurfaceRouting';\nimport { traceConversion, type CoordTraceEntry } from './coordTrace';\nimport type { SceneCanvasLongPress } from './props';\nimport { VIEW_MARKER_ENTRY_ID } from '../../features/viewports/viewMarkerDrag';\n\n/** What `<SceneCanvas>` hands {@link useSceneGestureDispatcher}. */\nexport interface SceneGestureDispatcherInputs {\n canvasRef: React.RefObject<HTMLElement | null>;\n /** Holds the full `CanvasExtensionApi` so the gesture dispatcher can call\n * `requestRedraw()` between pointer events. */\n canvasApiRef: React.RefObject<CanvasExtensionApi | null>;\n tools: ToolsApi;\n /** Always-live entries the canvas installs beside `tools` — the selection's\n * `move` / `transform` bindings. Ahead of the tools' ambient entries, so a\n * tie at one specificity goes to the kit's. */\n contributions: readonly Contribution[];\n enabled: boolean;\n /** When false, the dispatcher leaves keyboard listeners unattached so\n * keyboard-bound actions never fire. Wired to `enableKeybindings`. */\n keyboard: boolean;\n /** Take the keyboard on mount. Wired to `claimKeys`. */\n claimKeys: boolean;\n selectionRef?: React.RefObject<import('core/selection/useSelection').SelectionApi>;\n boundsOf?: (id: string) => import('core/viewport/fitViewToBounds').Bounds | null;\n /** The parallax plane a node is drawn through, under the surface's camera —\n * so path anchors are hit where the plane draws them. */\n planeOfNode?: (id: string) => import('core/viewport/parallax').PlaneMap | null;\n pickEvery?: (worldX: number, worldY: number) => string[];\n /** Single topmost hit (collapses parent/child via `pickTopMostHit`), used to\n * classify the body under the pointer. Must match the select tool's own\n * resolution so a child node isn't misclassified by its parent's selection.\n * Falls back to `pickEvery`'s last id when absent. */\n pickBest?: (worldX: number, worldY: number) => string | null;\n /** Resolves a hit node id to its routing-trait kind, so the body\n * classification carries `kind` alongside `bodyTarget` and the\n * `kind:<k>` / `kind:<k>:selected` target forms can match. Absent when\n * the consumer opted out of routing (`routing={[]}`). */\n kindOfNode?: (id: string) => string | undefined;\n /** The node a double-click on a hit names (`doubleClickTarget`), for the classification. */\n doubleClickNames?: (hitId: string) => string;\n viewRef?: React.RefObject<View>;\n /** Pre-created dispatcher to pump events into. When omitted,\n * `useGestureDispatcher` creates one internally (legacy path). */\n dispatcher?: Dispatcher;\n /** Where the dispatcher records its decisions and `clientToWorld` its conversions. */\n traces: { dispatch: DispatchTrace; coords: TraceRing<CoordTraceEntry> };\n /** Chrome-caps visibility resolver factory. Threaded into\n * `buildAffordanceAt` so the hit-test pipeline gates corner / rotate /\n * anchor affordances on the same chrome ids the renderer uses. */\n getIsVisibleForCanvas?: () => (id: string) => boolean;\n /** Live RuleCtx factory. Threaded into `useGestureDispatcher` so the\n * dispatcher's eligibility filter sees the same mode/capabilities/selection\n * view of the world that chrome-caps does. */\n getRuleCtx?: () => RuleCtx | undefined;\n /** `DeviceProfile.targetScale`, so the affordance grab zones resolve to the\n * same sizes the chrome paints at. */\n targetScale?: number;\n /** Consumer override for the corner-handle grab radius\n * (`selectTool.handleHitRadius`). Undefined takes the device-scaled size. */\n handleHitRadius?: number;\n /** `selectTool.resize.edgeHandles`: resize handles at the edge midpoints. Default true. */\n edgeHandles?: boolean;\n /** The rotate badge the selection overlay paints, grabbable where drawn. */\n rotationBadge?: { distancePx: number; sizePx: number } | null;\n /** Receives the affordances this mounter hit-tests, for the slops overlay. */\n chromeAffordancesRef?: React.MutableRefObject<readonly Affordance[]>;\n /** Fires on every synthesized double click, in world coords. Backs the\n * `onDoubleClick` prop — see the option's doc on\n * `UseGestureDispatcherOptions` for why it's an observer, not a binding. */\n onDoubleClick?: (world: { x: number; y: number }) => void;\n /** Fires after every synthesized click. Backs the `onClick` prop. */\n onClick?: (world: { x: number; y: number }) => void;\n /** Backs `<SceneCanvas longPress>`. */\n longPress?: SceneCanvasLongPress;\n}\n\ntype AffordanceInputs = Pick<\n SceneGestureDispatcherInputs,\n | 'canvasApiRef' | 'selectionRef' | 'boundsOf' | 'planeOfNode' | 'viewRef' | 'getIsVisibleForCanvas'\n | 'targetScale' | 'handleHitRadius' | 'edgeHandles' | 'rotationBadge' | 'chromeAffordancesRef'\n>;\n\n/** What a press on the canvas grabs: a registered layer's widget, else a piece of the\n * selection chrome. */\nfunction useAffordanceAt(inputs: AffordanceInputs, depRegistry: DepRegistry, viewRegistry: ViewRegistry | null) {\n const {\n canvasApiRef, selectionRef, boundsOf, planeOfNode, viewRef, getIsVisibleForCanvas,\n targetScale, handleHitRadius, edgeHandles, rotationBadge, chromeAffordancesRef,\n } = inputs;\n // `getAnchorState` thunk for `buildAffordanceAt` — reads the live\n // `editAnchors` dep from the registry at call time.\n const depRegistryRef = useLatest(depRegistry);\n const planeOfNodeRef = useLatest(planeOfNode);\n const getAnchorState = useMemo(() => anchorStateFrom(\n () => depRegistryRef.current,\n (id, path) => pathFromPlane(path, planeOfNodeRef.current?.(id) ?? null),\n ), [depRegistryRef, planeOfNodeRef]);\n\n // Build the `affordanceAt` thunk. Takes world coords and delegates to\n // `buildAffordanceAt` for handle hit-testing.\n const affordances = useChromeAffordances({ targetScale, handleHitRadius, edgeHandles, rotationBadge, getAnchorState });\n useInsertionEffect(() => {\n if (chromeAffordancesRef) chromeAffordancesRef.current = affordances;\n });\n const affordanceAt = useMemo(() => {\n if (!selectionRef || !boundsOf || !viewRef) return undefined;\n return buildAffordanceAt({\n // The canvas's own view chrome, built once by its helpers — not a\n // second construction here that has to agree with the painted one.\n getChromeState: () => viewRegistry?.surface()?.chromeState() ?? EMPTY_CHROME_STATE,\n // Radii are declared in screen pixels and converted against this view.\n getView: () => viewRef.current ?? { x: 0, y: 0, scale: { x: 1, y: 1 } },\n affordances,\n // Chrome-caps resolver: keep the affordance hit-test in sync with what\n // the renderer is actually painting. Without this, a click on a (no\n // longer visible) resize handle position still classifies as a resize\n // handle — e.g. an anchor drag in path-edit mode resizes the path's\n // bounding box instead of moving the anchor.\n ...(getIsVisibleForCanvas ? { getIsVisible: () => getIsVisibleForCanvas() } : {}),\n });\n }, [selectionRef, boundsOf, viewRef, affordances, getIsVisibleForCanvas, viewRegistry]);\n\n return useMemo(() => {\n // Note this is NOT gated on `affordanceAt` being built: registered layers\n // produce affordances of their own, and a consumer with no selection\n // chrome (a canvas that is nothing but a HUD, say) still needs those.\n return (worldPoint: { x: number; y: number }) => {\n // Registered layers first: they draw on top of the kit's own chrome, so\n // they get first refusal on the point. A hit becomes an `AffordanceHit`\n // whose kind names the layer, carrying whatever the layer's hit-test\n // resolved — which is how `@weasel-js/hud` routes a press on one of its\n // widgets to its own action instead of the active tool.\n const extra = canvasApiRef.current?.hitTestExtras?.(worldPoint.x, worldPoint.y);\n if (extra) return layerAffordance(extra.layerId, extra.hit);\n const marker = viewRegistry?.markerAt(null, worldPoint);\n if (marker) return marker;\n return affordanceAt ? affordanceAt(worldPoint) : null;\n };\n }, [affordanceAt, canvasApiRef, viewRegistry]);\n}\n\n/** Markers take presses only while the routed tools carry the marker drag:\n * `actions={null}` and a tools takeover both leave it out. */\nfunction useMarkerGate(viewRegistry: ViewRegistry | null, ambient: readonly Contribution[]): void {\n const installed = ambient.some((entry) => entry.id === VIEW_MARKER_ENTRY_ID);\n const installedRef = useLatest(installed);\n useEffect(() => viewRegistry?.gateMarkers(() => installedRef.current), [viewRegistry, installedRef]);\n}\n\n/** Which body a world point lands on, its selection state, and its routing kind. */\nfunction useClassifyTarget(\n inputs: Pick<SceneGestureDispatcherInputs, 'selectionRef' | 'pickEvery' | 'pickBest' | 'viewRef' | 'doubleClickNames'>,\n kindOfNodeRef: { readonly current: SceneGestureDispatcherInputs['kindOfNode'] },\n) {\n const { selectionRef, pickEvery, pickBest, viewRef } = inputs;\n // Stable refs for the optional thunk inputs so the thunks themselves are\n // stable function identities across renders (no need to pass them as deps).\n const pickEveryRef = useLatest(pickEvery);\n const pickBestRef = useLatest(pickBest);\n const doubleClickNamesRef = useLatest(inputs.doubleClickNames);\n // Build the `classifyTarget` thunk. Takes world coords and delegates to\n // `buildClassifyTarget`.\n return useMemo(() => {\n if (!selectionRef || !pickEvery || !viewRef) return undefined;\n return buildClassifyTarget(\n () => selectionRef.current?.current ?? [],\n // Use the select tool's own topmost-hit resolution (`pickTopMostHit`,\n // which collapses parent/child) so a child body is classified by ITS\n // OWN selection — not its parent's.\n (wx: number, wy: number) => {\n if (pickBestRef.current) return pickBestRef.current(wx, wy);\n const ids = pickEveryRef.current?.(wx, wy) ?? [];\n return ids.length > 0 ? ids[ids.length - 1] : null;\n },\n // Node kind comes from the routing trait — the same classifier that\n // names `Hit.kind` — so `target: 'kind:text'` on a binding speaks the\n // vocabulary the consumer already declared in `routing`, rather than a\n // second one invented for bindings. `routing={[]}` opts out and leaves\n // every `kind:` target unmatchable.\n (id: string) => kindOfNodeRef.current?.(id),\n (id: string) => doubleClickNamesRef.current?.(id) ?? id,\n );\n }, [selectionRef, pickEvery, viewRef, pickBestRef, pickEveryRef, kindOfNodeRef, doubleClickNamesRef]);\n}\n\n/** Runs this canvas's gesture dispatcher over its tools, contributions, and chrome. */\nexport function useSceneGestureDispatcher(inputs: SceneGestureDispatcherInputs): void {\n const {\n canvasRef, canvasApiRef, tools, contributions, enabled, keyboard, claimKeys, viewRef,\n kindOfNode, dispatcher, traces, getRuleCtx, onDoubleClick, onClick,\n longPress: { duration: longPressDuration, haptics: longPressHaptics } = {},\n } = inputs;\n const registry = useActionsRegistry();\n const depRegistry = useDepRegistry();\n const viewRegistry = useOptionalViewRegistry();\n const entriesById = useMemo<ReadonlyMap<string, Contribution>>(() => {\n const m = new Map<string, Contribution>();\n for (const [id, tool] of Object.entries(tools.registry)) {\n m.set(id, tool);\n }\n for (const entry of contributions) m.set(entry.id, entry);\n // Ambient entries too — their bindings assemble at ambient scope, and the\n // dispatcher resolves them through this same map.\n for (const entry of tools.ambient) m.set(entry.id, entry);\n return m;\n }, [tools.registry, tools.ambient, contributions]);\n\n const kindOfNodeRef = useLatest(kindOfNode);\n useMarkerGate(viewRegistry, tools.ambient);\n\n const affordanceWithLayers = useAffordanceAt(inputs, depRegistry, viewRegistry);\n const classifyTarget = useClassifyTarget(inputs, kindOfNodeRef);\n\n // Through the handle, so a `clientToWorld` override on the canvas applies.\n const clientToWorld = useCallback((clientX: number, clientY: number): { x: number; y: number } => {\n const api = canvasApiRef.current;\n const [x, y] = api ? api.clientToWorld(clientX, clientY) : [clientX, clientY];\n const world = { x, y };\n traceConversion(traces.coords, clientX, clientY, api ? canvasRef.current : null, viewRef?.current, world);\n return world;\n }, [canvasRef, canvasApiRef, viewRef, traces.coords]);\n\n // Stable callback that asks the canvas to redraw. Reads via ref so the\n // identity stays stable while the underlying API binds after mount.\n const requestRedraw = useCallback(() => {\n canvasApiRef.current?.requestRedraw?.();\n }, [canvasApiRef]);\n\n // The painted tier's channel, read live for the same reason — the handle it\n // lives on binds after mount.\n const paintedCursor = useCallback(() => canvasApiRef.current?.paintedCursor, [canvasApiRef]);\n const cursorSink = useCallback(() => canvasApiRef.current?.cursorSink, [canvasApiRef]);\n\n // The pending long-press, published as the `longPress` dep for feedback.\n const [longPressState] = useState(createLongPressStore);\n const longPress = useMemo(() => ({\n state: longPressState,\n ...(longPressDuration !== undefined ? { duration: longPressDuration } : {}),\n ...(longPressHaptics !== undefined ? { haptics: longPressHaptics } : {}),\n }), [longPressState, longPressDuration, longPressHaptics]);\n\n const routing = useSurfaceRouting(viewRegistry);\n\n // Hover cursors live in `useGestureDispatcher`'s hover-cursor pump:\n // affordance hits carry `AffordanceHit.cursor` (set by `buildAffordanceAt`),\n // and everything else is predicted via `Dispatcher.resolveOnly` +\n // `Action.cursor`. Nothing SceneCanvas-specific remains here.\n useGestureDispatcher({\n canvasRef,\n actions: registry!,\n entriesById,\n enabled,\n keyboard,\n claimKeys,\n affordanceAt: affordanceWithLayers,\n classifyTarget,\n dispatcher,\n trace: traces.dispatch,\n clientToWorld,\n requestRedraw,\n paintedCursor,\n cursorSink,\n getRuleCtx,\n onClick,\n onDoubleClick,\n longPress,\n ...routing,\n });\n useDispatchDepSources(longPressState, classifyTarget, kindOfNodeRef);\n useKitSettingsDepSource();\n}\n"],"mappings":"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"}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { useOptionalViewRegistry } from "../viewRegistry.js";
|
|
2
2
|
import { asNodeId } from "../../core/scene/types.js";
|
|
3
|
+
import { useEnteredGroup } from "../deps/enteredGroup.js";
|
|
3
4
|
import { useViewLayerGate } from "../useViewLayerGate.js";
|
|
4
5
|
import { composeAlphaFor } from "../sceneSlotWiring.js";
|
|
5
6
|
import { doubleClickTarget } from "../doubleClickTarget.js";
|
|
7
|
+
import { createEnteredGroupScope } from "../../features/selection/enteredGroupScope.js";
|
|
6
8
|
import { useSceneAdapter } from "../useSceneAdapter.js";
|
|
7
9
|
import { useNodeAtPoint } from "./useNodeAtPoint.js";
|
|
8
10
|
import { useSceneSelectTool } from "./useSceneSelectTool.js";
|
|
9
|
-
import { useMemo } from "react";
|
|
11
|
+
import { useCallback, useMemo, useSyncExternalStore } from "react";
|
|
10
12
|
import { useLatest } from "@weasel-js/react";
|
|
13
|
+
import { useOptionalDepRegistry } from "@weasel-js/routing/react";
|
|
11
14
|
|
|
12
15
|
//#region src/canvas/SceneCanvas/useScenePicking.ts
|
|
13
16
|
/**
|
|
@@ -19,11 +22,31 @@ function useScenePicking(args) {
|
|
|
19
22
|
const { geometry, alphaFor, layerVisibility, layerOrder, isPointerInteractive } = args.props;
|
|
20
23
|
const { kindOf, kindOfNode } = args.kinds;
|
|
21
24
|
const composedAlphaFor = useMemo(() => composeAlphaFor(scene, alphaFor), [scene, alphaFor]);
|
|
25
|
+
const ownEntered = useEnteredGroup(scene);
|
|
26
|
+
const enteredGroup = args.props.enteredGroup ?? ownEntered;
|
|
27
|
+
const consumerInteractive = useLatest(isPointerInteractive);
|
|
28
|
+
const filtering = useSyncExternalStore(enteredGroup.subscribe, () => enteredGroup.get() !== null) || isPointerInteractive !== void 0;
|
|
29
|
+
const reachable = useMemo(() => {
|
|
30
|
+
if (!filtering) return void 0;
|
|
31
|
+
const scope = createEnteredGroupScope({
|
|
32
|
+
entered: enteredGroup,
|
|
33
|
+
scene
|
|
34
|
+
});
|
|
35
|
+
return (id) => scope.isPointerInteractive(id) && consumerInteractive.current?.(id) !== false;
|
|
36
|
+
}, [
|
|
37
|
+
filtering,
|
|
38
|
+
enteredGroup,
|
|
39
|
+
scene,
|
|
40
|
+
consumerInteractive
|
|
41
|
+
]);
|
|
22
42
|
const surfaceViewRegistry = useOptionalViewRegistry();
|
|
23
43
|
const viewLayerGate = useViewLayerGate(layerVisibility, layerOrder, () => surfaceViewRegistry?.surface()?.layers() ?? []);
|
|
44
|
+
const depRegistry = useOptionalDepRegistry();
|
|
45
|
+
const pasteInto = useCallback(() => depRegistry?.get("insertInto")?.get() ?? null, [depRegistry]);
|
|
24
46
|
const adapter = useSceneAdapter(scene, {
|
|
25
47
|
selection: selection.adapterMethods,
|
|
26
|
-
poseDescriptor: descriptor
|
|
48
|
+
poseDescriptor: descriptor,
|
|
49
|
+
pasteInto
|
|
27
50
|
});
|
|
28
51
|
const selectTool = useSceneSelectTool({
|
|
29
52
|
scene,
|
|
@@ -33,7 +56,7 @@ function useScenePicking(args) {
|
|
|
33
56
|
getView: () => viewRef.current,
|
|
34
57
|
...alphaFor ? { alphaOf: composedAlphaFor } : {},
|
|
35
58
|
...viewLayerGate ? { layerIsPainted: viewLayerGate } : {},
|
|
36
|
-
...
|
|
59
|
+
...reachable ? { isPointerInteractive: reachable } : {},
|
|
37
60
|
selectTool: args.selectTool
|
|
38
61
|
});
|
|
39
62
|
const getNodeAtPoint = useNodeAtPoint({
|
|
@@ -41,15 +64,14 @@ function useScenePicking(args) {
|
|
|
41
64
|
nodes: adapter,
|
|
42
65
|
kindOf
|
|
43
66
|
});
|
|
44
|
-
const isolatedGroup = useLatest(args.props.isolatedGroup);
|
|
45
67
|
const doubleClickNames = useMemo(() => kindOfNode && ((hitId) => doubleClickTarget(hitId, {
|
|
46
68
|
parentOf: (id) => scene.get(asNodeId(id))?.parent ?? null,
|
|
47
69
|
kindOf: kindOfNode,
|
|
48
|
-
scope:
|
|
70
|
+
scope: enteredGroup.get()
|
|
49
71
|
})), [
|
|
50
72
|
scene,
|
|
51
73
|
kindOfNode,
|
|
52
|
-
|
|
74
|
+
enteredGroup
|
|
53
75
|
]);
|
|
54
76
|
return {
|
|
55
77
|
...selectTool,
|
|
@@ -58,6 +80,9 @@ function useScenePicking(args) {
|
|
|
58
80
|
doubleClickNames,
|
|
59
81
|
getNodeAtPoint,
|
|
60
82
|
viewLayerGate,
|
|
83
|
+
enteredGroup,
|
|
84
|
+
/** What a press or a region select can reach, or undefined when that is every node. */
|
|
85
|
+
reachable,
|
|
61
86
|
/** The composed paint alpha, only when the consumer fades anything. */
|
|
62
87
|
alphaOf: alphaFor ? composedAlphaFor : void 0
|
|
63
88
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useScenePicking.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useScenePicking.ts"],"sourcesContent":["import { useMemo, type RefObject } from 'react';\nimport { asNodeId, type Scene } from 'core/scene/types';\nimport { useLatest } from '@weasel-js/react';\nimport { doubleClickTarget } from '../doubleClickTarget';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport type { View } from 'core/viewport/view';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { composeAlphaFor } from '../sceneSlotWiring';\nimport { useSceneAdapter } from '../useSceneAdapter';\nimport type { SceneCanvasProps } from './props';\nimport { useNodeAtPoint } from './useNodeAtPoint';\nimport type { NodeKinds } from './useNodeKinds';\nimport { useSceneSelectTool, type UseSceneSelectToolArgs } from './useSceneSelectTool';\nimport { useOptionalViewRegistry } from '../viewRegistry';\nimport { useViewLayerGate } from '../useViewLayerGate';\n\ntype PickingProps<TData, TLayer extends string, TPose> = Pick<\n SceneCanvasProps<TData, TLayer, TPose>,\n 'geometry' | 'alphaFor' | 'layerVisibility' | 'layerOrder' | 'isPointerInteractive' | '
|
|
1
|
+
{"version":3,"file":"useScenePicking.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useScenePicking.ts"],"sourcesContent":["import { useCallback, useMemo, useSyncExternalStore, type RefObject } from 'react';\nimport { useOptionalDepRegistry } from '@weasel-js/routing/react';\nimport { asNodeId, type Scene } from 'core/scene/types';\nimport { useLatest } from '@weasel-js/react';\nimport { doubleClickTarget } from '../doubleClickTarget';\nimport { useEnteredGroup } from '../deps/enteredGroup';\nimport { createEnteredGroupScope } from 'features/selection/enteredGroupScope';\nimport type { SelectionApi } from 'core/selection/useSelection';\nimport type { View } from 'core/viewport/view';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { composeAlphaFor } from '../sceneSlotWiring';\nimport { useSceneAdapter } from '../useSceneAdapter';\nimport type { SceneCanvasProps } from './props';\nimport { useNodeAtPoint } from './useNodeAtPoint';\nimport type { NodeKinds } from './useNodeKinds';\nimport { useSceneSelectTool, type UseSceneSelectToolArgs } from './useSceneSelectTool';\nimport { useOptionalViewRegistry } from '../viewRegistry';\nimport { useViewLayerGate } from '../useViewLayerGate';\n\ntype PickingProps<TData, TLayer extends string, TPose> = Pick<\n SceneCanvasProps<TData, TLayer, TPose>,\n 'geometry' | 'alphaFor' | 'layerVisibility' | 'layerOrder' | 'isPointerInteractive' | 'enteredGroup'\n>;\n\n/**\n * The canvas's adapter and everything that answers \"what is under this point\": the select\n * tool's pickers and bounds, the node kinds, and the paint gates picking shares with the painter.\n */\nexport function useScenePicking<TData, TLayer extends string, TPose>(args: {\n scene: Scene<TData, TLayer, TPose>;\n selection: SelectionApi;\n descriptor: PoseDescriptor<TPose>;\n props: PickingProps<TData, TLayer, TPose>;\n viewRef: RefObject<View>;\n selectTool: UseSceneSelectToolArgs<TData, TLayer, TPose>['selectTool'];\n kinds: NodeKinds<TData>;\n}) {\n const { scene, selection, descriptor, viewRef } = args;\n const { geometry, alphaFor, layerVisibility, layerOrder, isPointerInteractive } = args.props;\n const { kindOf, kindOfNode } = args.kinds;\n\n // The painted alpha of a node in this view: the caller's `alphaFor` times any per-node\n // override alpha. The pick path reads the same number the painter does, or a node faded\n // to nothing stays clickable.\n const composedAlphaFor = useMemo(\n () => composeAlphaFor(scene, alphaFor),\n [scene, alphaFor],\n );\n const ownEntered = useEnteredGroup(scene);\n const enteredGroup = args.props.enteredGroup ?? ownEntered;\n // A press resolves inside the entered group, so nothing outside it takes a hit. With nothing\n // entered and no consumer predicate there is nothing to filter, and the walk runs bare.\n const consumerInteractive = useLatest(isPointerInteractive);\n const anyEntered = useSyncExternalStore(enteredGroup.subscribe, () => enteredGroup.get() !== null);\n const filtering = anyEntered || isPointerInteractive !== undefined;\n const reachable = useMemo(() => {\n if (!filtering) return undefined;\n const scope = createEnteredGroupScope({ entered: enteredGroup, scene });\n return (id: string) => scope.isPointerInteractive(id) && consumerInteractive.current?.(id) !== false;\n }, [filtering, enteredGroup, scene, consumerInteractive]);\n\n const surfaceViewRegistry = useOptionalViewRegistry();\n const viewLayerGate = useViewLayerGate(\n layerVisibility, layerOrder, () => surfaceViewRegistry?.surface()?.layers() ?? [],\n );\n\n const depRegistry = useOptionalDepRegistry();\n const pasteInto = useCallback(() => depRegistry?.get('insertInto')?.get() ?? null, [depRegistry]);\n const adapter = useSceneAdapter(scene, {\n // `adapterMethods` is memoized; `selection` itself is a fresh object each render.\n selection: selection.adapterMethods,\n poseDescriptor: descriptor,\n pasteInto,\n });\n\n const selectTool = useSceneSelectTool({\n scene,\n adapter,\n poseDescriptor: descriptor,\n geometry,\n // The pick tolerance is declared in screen pixels; this is what converts it.\n getView: () => viewRef.current,\n // Only when the consumer actually fades something. Without a prop the\n // composed function is just the override lookup, and the source resolves\n // that once per walk instead of once per candidate.\n ...(alphaFor ? { alphaOf: composedAlphaFor } : {}),\n ...(viewLayerGate ? { layerIsPainted: viewLayerGate } : {}),\n ...(reachable ? { isPointerInteractive: reachable } : {}),\n selectTool: args.selectTool,\n });\n\n const getNodeAtPoint = useNodeAtPoint({\n pickBest: selectTool.pickBest, nodes: adapter, kindOf,\n });\n\n const doubleClickNames = useMemo(() => kindOfNode && ((hitId: string) => doubleClickTarget(hitId, {\n parentOf: (id) => scene.get(asNodeId(id))?.parent ?? null,\n kindOf: kindOfNode,\n scope: enteredGroup.get(),\n })), [scene, kindOfNode, enteredGroup]);\n\n return {\n ...selectTool, adapter, kindOfNode, doubleClickNames, getNodeAtPoint, viewLayerGate, enteredGroup,\n /** What a press or a region select can reach, or undefined when that is every node. */\n reachable,\n /** The composed paint alpha, only when the consumer fades anything. */\n alphaOf: alphaFor ? composedAlphaFor : undefined,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA4BA,SAAgB,gBAAqD,MAQlE;CACD,MAAM,EAAE,OAAO,WAAW,YAAY,YAAY;CAClD,MAAM,EAAE,UAAU,UAAU,iBAAiB,YAAY,yBAAyB,KAAK;CACvF,MAAM,EAAE,QAAQ,eAAe,KAAK;CAKpC,MAAM,mBAAmB,cACjB,gBAAgB,OAAO,QAAQ,GACrC,CAAC,OAAO,QAAQ,CAClB;CACA,MAAM,aAAa,gBAAgB,KAAK;CACxC,MAAM,eAAe,KAAK,MAAM,gBAAgB;CAGhD,MAAM,sBAAsB,UAAU,oBAAoB;CAE1D,MAAM,YADa,qBAAqB,aAAa,iBAAiB,aAAa,IAAI,MAAM,IAC3E,KAAc,yBAAyB;CACzD,MAAM,YAAY,cAAc;EAC9B,IAAI,CAAC,WAAW,OAAO;EACvB,MAAM,QAAQ,wBAAwB;GAAE,SAAS;GAAc;EAAM,CAAC;EACtE,QAAQ,OAAe,MAAM,qBAAqB,EAAE,KAAK,oBAAoB,UAAU,EAAE,MAAM;CACjG,GAAG;EAAC;EAAW;EAAc;EAAO;CAAmB,CAAC;CAExD,MAAM,sBAAsB,wBAAwB;CACpD,MAAM,gBAAgB,iBACpB,iBAAiB,kBAAkB,qBAAqB,QAAQ,CAAC,EAAE,OAAO,KAAK,CAAC,CAClF;CAEA,MAAM,cAAc,uBAAuB;CAC3C,MAAM,YAAY,kBAAkB,aAAa,IAAI,YAAY,CAAC,EAAE,IAAI,KAAK,MAAM,CAAC,WAAW,CAAC;CAChG,MAAM,UAAU,gBAAgB,OAAO;EAErC,WAAW,UAAU;EACrB,gBAAgB;EAChB;CACF,CAAC;CAED,MAAM,aAAa,mBAAmB;EACpC;EACA;EACA,gBAAgB;EAChB;EAEA,eAAe,QAAQ;EAIvB,GAAI,WAAW,EAAE,SAAS,iBAAiB,IAAI,CAAC;EAChD,GAAI,gBAAgB,EAAE,gBAAgB,cAAc,IAAI,CAAC;EACzD,GAAI,YAAY,EAAE,sBAAsB,UAAU,IAAI,CAAC;EACvD,YAAY,KAAK;CACnB,CAAC;CAED,MAAM,iBAAiB,eAAe;EACpC,UAAU,WAAW;EAAU,OAAO;EAAS;CACjD,CAAC;CAED,MAAM,mBAAmB,cAAc,gBAAgB,UAAkB,kBAAkB,OAAO;EAChG,WAAW,OAAO,MAAM,IAAI,SAAS,EAAE,CAAC,CAAC,EAAE,UAAU;EACrD,QAAQ;EACR,OAAO,aAAa,IAAI;CAC1B,CAAC,IAAI;EAAC;EAAO;EAAY;CAAY,CAAC;CAEtC,OAAO;EACL,GAAG;EAAY;EAAS;EAAY;EAAkB;EAAgB;EAAe;;EAErF;;EAEA,SAAS,WAAW,mBAAmB;CACzC;AACF"}
|
|
@@ -3,7 +3,8 @@ import { AUTO_POSE_DESCRIPTOR } from "../../core/geometry/autoPoseDescriptor.js"
|
|
|
3
3
|
import { drawnBoundsOf } from "../drawnBounds.js";
|
|
4
4
|
import { asNodeId } from "../../core/scene/types.js";
|
|
5
5
|
import { drawnBoundsScene } from "../derivedPath.js";
|
|
6
|
-
import {
|
|
6
|
+
import { scenePickSource, scenePlaneOf } from "../scenePickSource.js";
|
|
7
|
+
import { pickWalk } from "../pickWalk.js";
|
|
7
8
|
import { pickTopMostHit } from "../../tools/builtin/pickTopMostHit.js";
|
|
8
9
|
import { useSelectTool } from "../../tools/builtin/select/useSelectTool.js";
|
|
9
10
|
import { scenePointQuery } from "./scenePointQuery.js";
|
|
@@ -57,7 +58,6 @@ function useSceneSelectTool(args) {
|
|
|
57
58
|
const wiredMoveOptions = useMemo(() => moveOptions ?? {}, [moveOptions]);
|
|
58
59
|
const wiredHitBody = usePointPick({
|
|
59
60
|
scene,
|
|
60
|
-
adapter,
|
|
61
61
|
d,
|
|
62
62
|
pickEveryProp,
|
|
63
63
|
shapePicking,
|
|
@@ -126,7 +126,7 @@ function useSceneSelectTool(args) {
|
|
|
126
126
|
/** Every id under a world point, back-to-front: the caller's `geometry.pickEvery` or the pose
|
|
127
127
|
* walk, less whatever `isPointerInteractive` rejects. */
|
|
128
128
|
function usePointPick(args) {
|
|
129
|
-
const { scene,
|
|
129
|
+
const { scene, d, pickEveryProp, shapePicking, pickTolerancePx, getView, alphaOf, layerIsPainted, isPointerInteractive } = args;
|
|
130
130
|
const walk = useMemo(() => {
|
|
131
131
|
return (wx, wy, view) => {
|
|
132
132
|
if (pickEveryProp) {
|
|
@@ -139,7 +139,7 @@ function usePointPick(args) {
|
|
|
139
139
|
const viewLayers = view?.layerIsPainted ?? layerIsPainted;
|
|
140
140
|
const camera = cameraOf(asked);
|
|
141
141
|
const src = scenePickSource(scene, {
|
|
142
|
-
|
|
142
|
+
index: { descriptor: d },
|
|
143
143
|
...viewAlpha ? { alphaOf: viewAlpha } : {},
|
|
144
144
|
...viewLayers ? { layerIsPainted: viewLayers } : {},
|
|
145
145
|
...camera ? { camera } : {}
|
|
@@ -152,7 +152,6 @@ function usePointPick(args) {
|
|
|
152
152
|
};
|
|
153
153
|
}, [
|
|
154
154
|
scene,
|
|
155
|
-
adapter,
|
|
156
155
|
pickEveryProp,
|
|
157
156
|
shapePicking,
|
|
158
157
|
pickTolerancePx,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSceneSelectTool.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneSelectTool.ts"],"sourcesContent":["/**\n * Scene-aware select-tool synthesis for `<SceneCanvas>`: a `useSelectTool`\n * over the caller's scene adapter, configured with kit-default pickEvery /\n * boundsOf derived from pose shape. Caller-supplied `pickEvery` / `boundsOf`\n * overrides via the `geometry` arg take precedence.\n */\nimport { drawnBoundsScene } from '../derivedPath';\nimport { useMemo } from 'react';\nimport { useKitSetting, useLatest } from '@weasel-js/react';\nimport type { SceneCanvasAdapter } from '../sceneAdapter';\nimport { pickWalk, scenePickSource, scenePlaneOf, type ViewPickGates } from 'canvas/pickWalk';\nimport { rectFromPlane, type PlaneMap } from 'core/viewport/parallax';\nimport type { View } from 'core/viewport/view';\nimport { useSelectTool, type Bounds } from 'tools/builtin/select';\nimport { pickTopMostHit, type PickTopMostHitAdapter } from 'tools/builtin/pickTopMostHit';\nimport type { Node, Scene } from 'core/scene/types';\nimport { asNodeId } from 'core/scene/types';\nimport type { UseMoveOptions } from 'interactions/actions/move/options';\nimport type { UseResizeOptions } from 'interactions/actions/resize/options';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { drawnBoundsOf } from 'canvas/drawnBounds';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport { scenePointQuery } from './scenePointQuery';\nimport type { SceneCanvasGeometry } from './geometryProps';\n\n/**\n * Default grab slop around a shape's outline, in screen pixels.\n *\n * Four is the same number the path-pose stroke test has always used, and it's\n * the difference between a hairline being clickable and not: a 1px stroke is\n * half a world unit of reach at scale 1, which no pointing device can hit\n * reliably. It also lets a filled shape be grabbed a few pixels outside its\n * edge, which is what makes edge-adjacent dragging feel possible.\n */\nexport const DEFAULT_PICK_TOLERANCE_PX = 4;\n\n/** The view a pick is asked for. A world point carries neither the scale it\n * was produced under nor what that view paints, so a caller picking for a\n * view other than the surface's says both. A gate left out is the one this\n * hook was built with. */\nexport interface PickView extends ViewPickGates {\n scale: { x: number; y: number };\n /** The rest of the asking camera. Supplied, it lets a parallax layer be\n * picked through its plane; left out, every layer is taken to move with\n * the camera. */\n x?: number;\n y?: number;\n}\n\n/** A pick view that names a whole camera, or null. */\nfunction cameraOf(v: Pick<PickView, 'scale' | 'x' | 'y'> | null): View | null {\n return v && typeof v.x === 'number' && typeof v.y === 'number'\n ? { x: v.x, y: v.y, scale: v.scale }\n : null;\n}\n\nexport interface UseSceneSelectToolArgs<TData, TLayer extends string, TPose> {\n scene: Scene<TData, TLayer, TPose>;\n /** The adapter the select tool drives, built over `scene` with the same\n * `poseDescriptor`. Its world poses are what picking and bounds read. */\n adapter: SceneCanvasAdapter<TData, TLayer, TPose>;\n /** How to read and rewrite this scene's poses. Default `AUTO_POSE_DESCRIPTOR`. */\n poseDescriptor?: PoseDescriptor<TPose>;\n geometry?: SceneCanvasGeometry;\n /** The surface's camera, for converting the screen-pixel pick tolerance into\n * world units. A caller picking for another view passes that view's camera\n * to `pickEvery` / `pickBest` instead. Omitted in tests and non-viewport\n * hosts, where scale is 1. */\n getView?: () => Pick<PickView, 'scale' | 'x' | 'y'> | null;\n /** The asking view's painted alpha per node — its `alphaFor` times any\n * per-node override alpha. A node the view paints at alpha 0 is not on\n * screen, so picking must not answer for it. */\n alphaOf?: (id: string) => number;\n /** The asking view's answer to \"does this scene layer reach the screen\".\n * `layerVisibility` and `layerOrder` both gate a layer out of the paint\n * (`drawLayers` drops any layer missing from a supplied order), and an\n * undrawn layer must not claim clicks. */\n layerIsPainted?: (layer: string) => boolean;\n /** `<SceneCanvas isPointerInteractive>`: a node it rejects is left out of every hit, so\n * whatever is beneath it answers instead. */\n isPointerInteractive?: (id: string) => boolean;\n selectTool?: {\n move?: UseMoveOptions<TPose>;\n resize?: UseResizeOptions<TPose>;\n /** Override the body-pick used on click/pointerdown. Alt-aware: receives\n * the live alt state + current selection so consumers can implement\n * alt-cycling through an overlapping stack. Default: top-most hit\n * (alt ignored). Forwarded verbatim to `useSelectTool`. */\n pickBest?: (worldX: number, worldY: number, alt: boolean, sel: readonly string[]) => string | null;\n /** Forwarded verbatim to `useSelectTool`. See\n * `UseSelectToolOptions.extendClickLocked`. */\n extendClickLocked?: () => boolean;\n };\n}\n\n/** `id` drawn at `pose`, in world, as the chrome boxes it. */\nfunction chromeBox<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n adapter: SceneCanvasAdapter<TData, TLayer, TPose>,\n descriptor: PoseDescriptor<TPose>,\n includeStroke: boolean,\n id: string,\n pose: TPose,\n scale: { x: number; y: number } | undefined,\n): Bounds {\n return drawnBoundsOf(drawnBoundsScene(scene), asNodeId(id), pose, {\n descriptor: descriptor as PoseDescriptor<unknown>,\n worldPoseOf: (m) => adapter.getWorldPose(m),\n includeStroke,\n ...(scale ? { scale } : {}),\n });\n}\n\nexport interface UseSceneSelectToolReturn<TData, TLayer extends string, TPose> {\n selectTool: ReturnType<typeof useSelectTool<Node<TData, TLayer, TPose>, TPose>>;\n /** Every id under the world point, back-to-front (topmost last), from the\n * caller's `geometry.pickEvery` or the pose-walk default. */\n pickEvery: (worldX: number, worldY: number, view?: PickView | null) => string[];\n /** Single-best hit under the world point, or null. Runs `pickEvery` then\n * collapses parent/child overlap via `pickTopMostHit` — matches the id\n * the select tool's pointerdown classifier would settle on for a bare\n * click. The canvas's `getNodeAtPoint` resolves through it. */\n pickBest: (worldX: number, worldY: number, view?: PickView | null) => string | null;\n /** World-space AABB of `id`, or null. Same as what the selection overlay +\n * affordance hit-test need. Exposed so SceneCanvas can pass it to the\n * `affordanceAt` thunk without re-deriving it. `view` is the camera the\n * box is drawn under — a plane node sits somewhere else in each — and\n * defaults to the surface's. */\n boundsOf: (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) =>\n import('core/viewport/fitViewToBounds').Bounds | null;\n /** How `view`'s world (the surface's camera by default) maps into the plane\n * `id` is drawn through, or null when its layer moves with the camera. */\n planeOfNode: (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) => PlaneMap | null;\n /** `boundsOf` for `id` drawn at `pose` rather than its own — how chrome\n * boxes an in-flight preview. Ignores a `geometry.boundsOf` override, which\n * answers only for a node's own pose. */\n boundsOfPose: (id: string, pose: TPose, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) =>\n import('core/viewport/fitViewToBounds').Bounds | null;\n /** `selectTool.move` — what the `move` preset's bindings carry. */\n moveOptions: UseMoveOptions<TPose>;\n}\n\nexport function useSceneSelectTool<TData, TLayer extends string, TPose>(\n args: UseSceneSelectToolArgs<TData, TLayer, TPose>,\n): UseSceneSelectToolReturn<TData, TLayer, TPose> {\n const { scene, adapter, geometry, selectTool: opts, getView, alphaOf, layerIsPainted, isPointerInteractive } = args;\n const d = (args.poseDescriptor ?? AUTO_POSE_DESCRIPTOR) as PoseDescriptor<TPose>;\n\n const pickEveryProp = geometry?.pickEvery;\n const boundsOfProp = geometry?.boundsOf;\n // Default. The pose rect is the wrong answer for anything that isn't a rectangle, so `'pose'`\n // is the opt-out rather than the baseline. Cheap to leave on: the rect pre-filter rejects every\n // node the pointer isn't over first, and the survivors' silhouettes are memoized per node.\n const shapePicking = (geometry?.picking ?? 'shape') === 'shape';\n const kitPickTolerancePx = useKitSetting('targets.pickTolerancePx');\n const pickTolerancePx = geometry?.pickTolerancePx ?? kitPickTolerancePx ?? DEFAULT_PICK_TOLERANCE_PX;\n const includeStroke = geometry?.boundsIncludeStroke ?? true;\n const moveOptions = opts?.move;\n const wiredMoveOptions = useMemo<UseMoveOptions<TPose>>(() => moveOptions ?? {}, [moveOptions]);\n\n const wiredHitBody = usePointPick({\n scene, adapter, d, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, isPointerInteractive,\n });\n\n // Read through a ref: this is the chrome's bounds source, and a caller's\n // inline `getView` would otherwise rebuild it, and everything keyed on it,\n // every render.\n const getViewRef = useLatest(getView);\n const wiredPlaneOfNode = useMemo(() => {\n return (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): PlaneMap | null => {\n const n = scene.get(asNodeId(id));\n const camera = cameraOf(view ?? getViewRef.current?.() ?? null);\n if (!n || !camera) return null;\n return scenePlaneOf(scene.layers, camera)?.(n.layer) ?? null;\n };\n }, [scene, getViewRef]);\n\n const wiredBoundsOfPose = useMemo(() => {\n return (id: string, pose: TPose, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): Bounds | null => {\n if (!scene.get(asNodeId(id))) return null;\n const scale = (view ?? getViewRef.current?.() ?? null)?.scale;\n const own = chromeBox(scene, adapter, d, includeStroke, id, pose, scale);\n // A plane node is drawn where its plane puts it, and the chrome has to\n // follow it there.\n const plane = wiredPlaneOfNode(id, view);\n return plane ? rectFromPlane(plane, own) : own;\n };\n }, [scene, adapter, d, includeStroke, getViewRef, wiredPlaneOfNode]);\n\n const wiredBoundsOf = useMemo(() => {\n return (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): Bounds | null => {\n if (boundsOfProp) return boundsOfProp(id);\n if (!scene.get(asNodeId(id))) return null;\n // World, not local: a framed child is drawn in its parent's frame, and\n // the chrome has to land on the ink.\n return wiredBoundsOfPose(id, adapter.getWorldPose(id), view);\n };\n }, [scene, adapter, boundsOfProp, wiredBoundsOfPose]);\n\n const selectTool = useSelectTool<Node<TData, TLayer, TPose>, TPose>(adapter, {\n pickEvery: wiredHitBody,\n ...(opts?.pickBest ? { pickBest: opts.pickBest } : {}),\n ...(opts?.extendClickLocked ? { extendClickLocked: opts.extendClickLocked } : {}),\n });\n\n\n const wiredPickBest = useMemo(() => {\n return (wx: number, wy: number, view?: PickView | null): string | null => {\n const ids = wiredHitBody(wx, wy, view);\n return pickTopMostHit(ids, adapter as unknown as PickTopMostHitAdapter);\n };\n }, [wiredHitBody, adapter]);\n\n return {\n selectTool,\n pickEvery: wiredHitBody,\n pickBest: wiredPickBest,\n boundsOf: wiredBoundsOf,\n boundsOfPose: wiredBoundsOfPose,\n planeOfNode: wiredPlaneOfNode,\n moveOptions: wiredMoveOptions,\n };\n}\n\n/** Every id under a world point, back-to-front: the caller's `geometry.pickEvery` or the pose\n * walk, less whatever `isPointerInteractive` rejects. */\nfunction usePointPick<TData, TLayer extends string, TPose>(args: {\n scene: Scene<TData, TLayer, TPose>;\n adapter: SceneCanvasAdapter<TData, TLayer, TPose>;\n d: PoseDescriptor<TPose>;\n pickEveryProp: SceneCanvasGeometry['pickEvery'];\n shapePicking: boolean;\n pickTolerancePx: number;\n getView: UseSceneSelectToolArgs<TData, TLayer, TPose>['getView'];\n alphaOf: ((id: string) => number) | undefined;\n layerIsPainted: ((layer: string) => boolean) | undefined;\n isPointerInteractive: ((id: string) => boolean) | undefined;\n}): (wx: number, wy: number, view?: PickView | null) => string[] {\n const {\n scene, adapter, d, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, isPointerInteractive,\n } = args;\n // Default pickEvery: walk renderOrder() forward (back-to-front) and collect\n // every node whose pose contains the world point. Order matches the\n // `useSelectTool` contract — last element is the topmost — so `pickTopMostHit`\n // picks correctly. Wraps the caller's `pickEvery` (string-or-null) into the\n // array form `useSelectTool` expects. `poseContainsRotated` reads\n // `pose.rotation` directly (the kit's one rotation convention), so rotated\n // shapes pick against their rendered, rotated body without a per-demo override.\n const walk = useMemo(() => {\n return (wx: number, wy: number, view?: PickView | null): string[] => {\n if (pickEveryProp) {\n const r = pickEveryProp(wx, wy);\n if (r == null) return [];\n return Array.isArray(r) ? r : [r];\n }\n const asked = view ?? getView?.() ?? null;\n const viewAlpha = view?.alphaOf ?? alphaOf;\n const viewLayers = view?.layerIsPainted ?? layerIsPainted;\n const camera = cameraOf(asked);\n // Through the adapter, not `n.pose`: an ephemeral override is the pose\n // the renderer draws, so it has to be the one picking tests. World, not\n // local, for the same reason — a framed child is drawn in its parent's\n // frame, not where its own pose says.\n const src = scenePickSource<TData, TLayer, TPose>(scene, {\n getPose: (id) => adapter.getWorldPose(id),\n ...(viewAlpha ? { alphaOf: viewAlpha } : {}),\n ...(viewLayers ? { layerIsPainted: viewLayers } : {}),\n ...(camera ? { camera } : {}),\n });\n const pointQuery = scenePointQuery<TPose>(d as PoseDescriptor<unknown>, shapePicking, pickTolerancePx);\n return pickWalk<TPose>(src, pointQuery(wx, wy, asked?.scale ?? { x: 1, y: 1 }));\n };\n }, [scene, adapter, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, d]);\n const wiredHitBody = useMemo(() => {\n if (!isPointerInteractive) return walk;\n return (wx: number, wy: number, view?: PickView | null): string[] =>\n walk(wx, wy, view).filter((id) => isPointerInteractive(id) !== false);\n }, [walk, isPointerInteractive]);\n return wiredHitBody;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,IAAa,4BAA4B;;AAgBzC,SAAS,SAAS,GAA4D;CAC5E,OAAO,KAAK,OAAO,EAAE,MAAM,YAAY,OAAO,EAAE,MAAM,WAClD;EAAE,GAAG,EAAE;EAAG,GAAG,EAAE;EAAG,OAAO,EAAE;CAAM,IACjC;AACN;;AA0CA,SAAS,UACP,OACA,SACA,YACA,eACA,IACA,MACA,OACQ;CACR,OAAO,cAAc,iBAAiB,KAAK,GAAG,SAAS,EAAE,GAAG,MAAM;EACpD;EACZ,cAAc,MAAM,QAAQ,aAAa,CAAC;EAC1C;EACA,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC3B,CAAC;AACH;AA+BA,SAAgB,mBACd,MACgD;CAChD,MAAM,EAAE,OAAO,SAAS,UAAU,YAAY,MAAM,SAAS,SAAS,gBAAgB,yBAAyB;CAC/G,MAAM,IAAK,KAAK,kBAAkB;CAElC,MAAM,gBAAgB,UAAU;CAChC,MAAM,eAAe,UAAU;CAI/B,MAAM,gBAAgB,UAAU,WAAW,aAAa;CACxD,MAAM,qBAAqB,cAAc,yBAAyB;CAClE,MAAM,kBAAkB,UAAU,mBAAmB,sBAAsB;CAC3E,MAAM,gBAAgB,UAAU,uBAAuB;CACvD,MAAM,cAAc,MAAM;CAC1B,MAAM,mBAAmB,cAAqC,eAAe,CAAC,GAAG,CAAC,WAAW,CAAC;CAE9F,MAAM,eAAe,aAAa;EAChC;EAAO;EAAS;EAAG;EAAe;EAAc;EAAiB;EACjE;EAAS;EAAgB;CAC3B,CAAC;CAKD,MAAM,aAAa,UAAU,OAAO;CACpC,MAAM,mBAAmB,cAAc;EACrC,QAAQ,IAAY,SAAuE;GACzF,MAAM,IAAI,MAAM,IAAI,SAAS,EAAE,CAAC;GAChC,MAAM,SAAS,SAAS,QAAQ,WAAW,UAAU,KAAK,IAAI;GAC9D,IAAI,CAAC,KAAK,CAAC,QAAQ,OAAO;GAC1B,OAAO,aAAa,MAAM,QAAQ,MAAM,CAAC,GAAG,EAAE,KAAK,KAAK;EAC1D;CACF,GAAG,CAAC,OAAO,UAAU,CAAC;CAEtB,MAAM,oBAAoB,cAAc;EACtC,QAAQ,IAAY,MAAa,SAAqE;GACpG,IAAI,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC,GAAG,OAAO;GACrC,MAAM,SAAS,QAAQ,WAAW,UAAU,KAAK,KAAA,EAAO;GACxD,MAAM,MAAM,UAAU,OAAO,SAAS,GAAG,eAAe,IAAI,MAAM,KAAK;GAGvE,MAAM,QAAQ,iBAAiB,IAAI,IAAI;GACvC,OAAO,QAAQ,cAAc,OAAO,GAAG,IAAI;EAC7C;CACF,GAAG;EAAC;EAAO;EAAS;EAAG;EAAe;EAAY;CAAgB,CAAC;CAEnE,MAAM,gBAAgB,cAAc;EAClC,QAAQ,IAAY,SAAqE;GACvF,IAAI,cAAc,OAAO,aAAa,EAAE;GACxC,IAAI,CAAC,MAAM,IAAI,SAAS,EAAE,CAAC,GAAG,OAAO;GAGrC,OAAO,kBAAkB,IAAI,QAAQ,aAAa,EAAE,GAAG,IAAI;EAC7D;CACF,GAAG;EAAC;EAAO;EAAS;EAAc;CAAiB,CAAC;CAgBpD,OAAO;EACL,YAfiB,cAAiD,SAAS;GAC3E,WAAW;GACX,GAAI,MAAM,WAAW,EAAE,UAAU,KAAK,SAAS,IAAI,CAAC;GACpD,GAAI,MAAM,oBAAoB,EAAE,mBAAmB,KAAK,kBAAkB,IAAI,CAAC;EACjF,CAWE;EACA,WAAW;EACX,UAVoB,cAAc;GAClC,QAAQ,IAAY,IAAY,SAA0C;IACxE,MAAM,MAAM,aAAa,IAAI,IAAI,IAAI;IACrC,OAAO,eAAe,KAAK,OAA2C;GACxE;EACF,GAAG,CAAC,cAAc,OAAO,CAKb;EACV,UAAU;EACV,cAAc;EACd,aAAa;EACb,aAAa;CACf;AACF;;;AAIA,SAAS,aAAkD,MAWM;CAC/D,MAAM,EACJ,OAAO,SAAS,GAAG,eAAe,cAAc,iBAAiB,SACjE,SAAS,gBAAgB,yBACvB;CAQJ,MAAM,OAAO,cAAc;EACzB,QAAQ,IAAY,IAAY,SAAqC;GACnE,IAAI,eAAe;IACjB,MAAM,IAAI,cAAc,IAAI,EAAE;IAC9B,IAAI,KAAK,MAAM,OAAO,CAAC;IACvB,OAAO,MAAM,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAC;GAClC;GACA,MAAM,QAAQ,QAAQ,UAAU,KAAK;GACrC,MAAM,YAAY,MAAM,WAAW;GACnC,MAAM,aAAa,MAAM,kBAAkB;GAC3C,MAAM,SAAS,SAAS,KAAK;GAK7B,MAAM,MAAM,gBAAsC,OAAO;IACvD,UAAU,OAAO,QAAQ,aAAa,EAAE;IACxC,GAAI,YAAY,EAAE,SAAS,UAAU,IAAI,CAAC;IAC1C,GAAI,aAAa,EAAE,gBAAgB,WAAW,IAAI,CAAC;IACnD,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;GAC7B,CAAC;GACD,MAAM,aAAa,gBAAuB,GAA8B,cAAc,eAAe;GACrG,OAAO,SAAgB,KAAK,WAAW,IAAI,IAAI,OAAO,SAAS;IAAE,GAAG;IAAG,GAAG;GAAE,CAAC,CAAC;EAChF;CACF,GAAG;EAAC;EAAO;EAAS;EAAe;EAAc;EAAiB;EAC9D;EAAS;EAAgB;CAAC,CAAC;CAM/B,OALqB,cAAc;EACjC,IAAI,CAAC,sBAAsB,OAAO;EAClC,QAAQ,IAAY,IAAY,SAC9B,KAAK,IAAI,IAAI,IAAI,CAAC,CAAC,QAAQ,OAAO,qBAAqB,EAAE,MAAM,KAAK;CACxE,GAAG,CAAC,MAAM,oBAAoB,CACvB;AACT"}
|
|
1
|
+
{"version":3,"file":"useSceneSelectTool.js","names":[],"sources":["../../../src/canvas/SceneCanvas/useSceneSelectTool.ts"],"sourcesContent":["/**\n * Scene-aware select-tool synthesis for `<SceneCanvas>`: a `useSelectTool`\n * over the caller's scene adapter, configured with kit-default pickEvery /\n * boundsOf derived from pose shape. Caller-supplied `pickEvery` / `boundsOf`\n * overrides via the `geometry` arg take precedence.\n */\nimport { drawnBoundsScene } from '../derivedPath';\nimport { useMemo } from 'react';\nimport { useKitSetting, useLatest } from '@weasel-js/react';\nimport type { SceneCanvasAdapter } from '../sceneAdapter';\nimport { pickWalk, scenePickSource, scenePlaneOf, type ViewPickGates } from 'canvas/pickWalk';\nimport { rectFromPlane, type PlaneMap } from 'core/viewport/parallax';\nimport type { View } from 'core/viewport/view';\nimport { useSelectTool, type Bounds } from 'tools/builtin/select';\nimport { pickTopMostHit, type PickTopMostHitAdapter } from 'tools/builtin/pickTopMostHit';\nimport type { Node, Scene } from 'core/scene/types';\nimport { asNodeId } from 'core/scene/types';\nimport type { UseMoveOptions } from 'interactions/actions/move/options';\nimport type { UseResizeOptions } from 'interactions/actions/resize/options';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { drawnBoundsOf } from 'canvas/drawnBounds';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport { scenePointQuery } from './scenePointQuery';\nimport type { SceneCanvasGeometry } from './geometryProps';\n\n/**\n * Default grab slop around a shape's outline, in screen pixels.\n *\n * Four is the same number the path-pose stroke test has always used, and it's\n * the difference between a hairline being clickable and not: a 1px stroke is\n * half a world unit of reach at scale 1, which no pointing device can hit\n * reliably. It also lets a filled shape be grabbed a few pixels outside its\n * edge, which is what makes edge-adjacent dragging feel possible.\n */\nexport const DEFAULT_PICK_TOLERANCE_PX = 4;\n\n/** The view a pick is asked for. A world point carries neither the scale it\n * was produced under nor what that view paints, so a caller picking for a\n * view other than the surface's says both. A gate left out is the one this\n * hook was built with. */\nexport interface PickView extends ViewPickGates {\n scale: { x: number; y: number };\n /** The rest of the asking camera. Supplied, it lets a parallax layer be\n * picked through its plane; left out, every layer is taken to move with\n * the camera. */\n x?: number;\n y?: number;\n}\n\n/** A pick view that names a whole camera, or null. */\nfunction cameraOf(v: Pick<PickView, 'scale' | 'x' | 'y'> | null): View | null {\n return v && typeof v.x === 'number' && typeof v.y === 'number'\n ? { x: v.x, y: v.y, scale: v.scale }\n : null;\n}\n\nexport interface UseSceneSelectToolArgs<TData, TLayer extends string, TPose> {\n scene: Scene<TData, TLayer, TPose>;\n /** The adapter the select tool drives, built over `scene` with the same\n * `poseDescriptor`. Its world poses are what bounds read, and equal the\n * scene's painted poses, which picking reads. */\n adapter: SceneCanvasAdapter<TData, TLayer, TPose>;\n /** How to read and rewrite this scene's poses. Default `AUTO_POSE_DESCRIPTOR`. */\n poseDescriptor?: PoseDescriptor<TPose>;\n geometry?: SceneCanvasGeometry;\n /** The surface's camera, for converting the screen-pixel pick tolerance into\n * world units. A caller picking for another view passes that view's camera\n * to `pickEvery` / `pickBest` instead. Omitted in tests and non-viewport\n * hosts, where scale is 1. */\n getView?: () => Pick<PickView, 'scale' | 'x' | 'y'> | null;\n /** The asking view's painted alpha per node — its `alphaFor` times any\n * per-node override alpha. A node the view paints at alpha 0 is not on\n * screen, so picking must not answer for it. */\n alphaOf?: (id: string) => number;\n /** The asking view's answer to \"does this scene layer reach the screen\".\n * `layerVisibility` and `layerOrder` both gate a layer out of the paint\n * (`drawLayers` drops any layer missing from a supplied order), and an\n * undrawn layer must not claim clicks. */\n layerIsPainted?: (layer: string) => boolean;\n /** `<SceneCanvas isPointerInteractive>`: a node it rejects is left out of every hit, so\n * whatever is beneath it answers instead. */\n isPointerInteractive?: (id: string) => boolean;\n selectTool?: {\n move?: UseMoveOptions<TPose>;\n resize?: UseResizeOptions<TPose>;\n /** Override the body-pick used on click/pointerdown. Alt-aware: receives\n * the live alt state + current selection so consumers can implement\n * alt-cycling through an overlapping stack. Default: top-most hit\n * (alt ignored). Forwarded verbatim to `useSelectTool`. */\n pickBest?: (worldX: number, worldY: number, alt: boolean, sel: readonly string[]) => string | null;\n /** Forwarded verbatim to `useSelectTool`. See\n * `UseSelectToolOptions.extendClickLocked`. */\n extendClickLocked?: () => boolean;\n };\n}\n\n/** `id` drawn at `pose`, in world, as the chrome boxes it. */\nfunction chromeBox<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n adapter: SceneCanvasAdapter<TData, TLayer, TPose>,\n descriptor: PoseDescriptor<TPose>,\n includeStroke: boolean,\n id: string,\n pose: TPose,\n scale: { x: number; y: number } | undefined,\n): Bounds {\n return drawnBoundsOf(drawnBoundsScene(scene), asNodeId(id), pose, {\n descriptor: descriptor as PoseDescriptor<unknown>,\n worldPoseOf: (m) => adapter.getWorldPose(m),\n includeStroke,\n ...(scale ? { scale } : {}),\n });\n}\n\nexport interface UseSceneSelectToolReturn<TData, TLayer extends string, TPose> {\n selectTool: ReturnType<typeof useSelectTool<Node<TData, TLayer, TPose>, TPose>>;\n /** Every id under the world point, back-to-front (topmost last), from the\n * caller's `geometry.pickEvery` or the pose-walk default. */\n pickEvery: (worldX: number, worldY: number, view?: PickView | null) => string[];\n /** Single-best hit under the world point, or null. Runs `pickEvery` then\n * collapses parent/child overlap via `pickTopMostHit` — matches the id\n * the select tool's pointerdown classifier would settle on for a bare\n * click. The canvas's `getNodeAtPoint` resolves through it. */\n pickBest: (worldX: number, worldY: number, view?: PickView | null) => string | null;\n /** World-space AABB of `id`, or null. Same as what the selection overlay +\n * affordance hit-test need. Exposed so SceneCanvas can pass it to the\n * `affordanceAt` thunk without re-deriving it. `view` is the camera the\n * box is drawn under — a plane node sits somewhere else in each — and\n * defaults to the surface's. */\n boundsOf: (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) =>\n import('core/viewport/fitViewToBounds').Bounds | null;\n /** How `view`'s world (the surface's camera by default) maps into the plane\n * `id` is drawn through, or null when its layer moves with the camera. */\n planeOfNode: (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) => PlaneMap | null;\n /** `boundsOf` for `id` drawn at `pose` rather than its own — how chrome\n * boxes an in-flight preview. Ignores a `geometry.boundsOf` override, which\n * answers only for a node's own pose. */\n boundsOfPose: (id: string, pose: TPose, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null) =>\n import('core/viewport/fitViewToBounds').Bounds | null;\n /** `selectTool.move` — what the `move` preset's bindings carry. */\n moveOptions: UseMoveOptions<TPose>;\n}\n\nexport function useSceneSelectTool<TData, TLayer extends string, TPose>(\n args: UseSceneSelectToolArgs<TData, TLayer, TPose>,\n): UseSceneSelectToolReturn<TData, TLayer, TPose> {\n const { scene, adapter, geometry, selectTool: opts, getView, alphaOf, layerIsPainted, isPointerInteractive } = args;\n const d = (args.poseDescriptor ?? AUTO_POSE_DESCRIPTOR) as PoseDescriptor<TPose>;\n\n const pickEveryProp = geometry?.pickEvery;\n const boundsOfProp = geometry?.boundsOf;\n // Default. The pose rect is the wrong answer for anything that isn't a rectangle, so `'pose'`\n // is the opt-out rather than the baseline. Cheap to leave on: the rect pre-filter rejects every\n // node the pointer isn't over first, and the survivors' silhouettes are memoized per node.\n const shapePicking = (geometry?.picking ?? 'shape') === 'shape';\n const kitPickTolerancePx = useKitSetting('targets.pickTolerancePx');\n const pickTolerancePx = geometry?.pickTolerancePx ?? kitPickTolerancePx ?? DEFAULT_PICK_TOLERANCE_PX;\n const includeStroke = geometry?.boundsIncludeStroke ?? true;\n const moveOptions = opts?.move;\n const wiredMoveOptions = useMemo<UseMoveOptions<TPose>>(() => moveOptions ?? {}, [moveOptions]);\n\n const wiredHitBody = usePointPick({\n scene, d, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, isPointerInteractive,\n });\n\n // Read through a ref: this is the chrome's bounds source, and a caller's\n // inline `getView` would otherwise rebuild it, and everything keyed on it,\n // every render.\n const getViewRef = useLatest(getView);\n const wiredPlaneOfNode = useMemo(() => {\n return (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): PlaneMap | null => {\n const n = scene.get(asNodeId(id));\n const camera = cameraOf(view ?? getViewRef.current?.() ?? null);\n if (!n || !camera) return null;\n return scenePlaneOf(scene.layers, camera)?.(n.layer) ?? null;\n };\n }, [scene, getViewRef]);\n\n const wiredBoundsOfPose = useMemo(() => {\n return (id: string, pose: TPose, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): Bounds | null => {\n if (!scene.get(asNodeId(id))) return null;\n const scale = (view ?? getViewRef.current?.() ?? null)?.scale;\n const own = chromeBox(scene, adapter, d, includeStroke, id, pose, scale);\n // A plane node is drawn where its plane puts it, and the chrome has to\n // follow it there.\n const plane = wiredPlaneOfNode(id, view);\n return plane ? rectFromPlane(plane, own) : own;\n };\n }, [scene, adapter, d, includeStroke, getViewRef, wiredPlaneOfNode]);\n\n const wiredBoundsOf = useMemo(() => {\n return (id: string, view?: Pick<PickView, 'scale' | 'x' | 'y'> | null): Bounds | null => {\n if (boundsOfProp) return boundsOfProp(id);\n if (!scene.get(asNodeId(id))) return null;\n // World, not local: a framed child is drawn in its parent's frame, and\n // the chrome has to land on the ink.\n return wiredBoundsOfPose(id, adapter.getWorldPose(id), view);\n };\n }, [scene, adapter, boundsOfProp, wiredBoundsOfPose]);\n\n const selectTool = useSelectTool<Node<TData, TLayer, TPose>, TPose>(adapter, {\n pickEvery: wiredHitBody,\n ...(opts?.pickBest ? { pickBest: opts.pickBest } : {}),\n ...(opts?.extendClickLocked ? { extendClickLocked: opts.extendClickLocked } : {}),\n });\n\n\n const wiredPickBest = useMemo(() => {\n return (wx: number, wy: number, view?: PickView | null): string | null => {\n const ids = wiredHitBody(wx, wy, view);\n return pickTopMostHit(ids, adapter as unknown as PickTopMostHitAdapter);\n };\n }, [wiredHitBody, adapter]);\n\n return {\n selectTool,\n pickEvery: wiredHitBody,\n pickBest: wiredPickBest,\n boundsOf: wiredBoundsOf,\n boundsOfPose: wiredBoundsOfPose,\n planeOfNode: wiredPlaneOfNode,\n moveOptions: wiredMoveOptions,\n };\n}\n\n/** Every id under a world point, back-to-front: the caller's `geometry.pickEvery` or the pose\n * walk, less whatever `isPointerInteractive` rejects. */\nfunction usePointPick<TData, TLayer extends string, TPose>(args: {\n scene: Scene<TData, TLayer, TPose>;\n d: PoseDescriptor<TPose>;\n pickEveryProp: SceneCanvasGeometry['pickEvery'];\n shapePicking: boolean;\n pickTolerancePx: number;\n getView: UseSceneSelectToolArgs<TData, TLayer, TPose>['getView'];\n alphaOf: ((id: string) => number) | undefined;\n layerIsPainted: ((layer: string) => boolean) | undefined;\n isPointerInteractive: ((id: string) => boolean) | undefined;\n}): (wx: number, wy: number, view?: PickView | null) => string[] {\n const {\n scene, d, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, isPointerInteractive,\n } = args;\n // Default pickEvery: walk renderOrder() forward (back-to-front) and collect\n // every node whose pose contains the world point. Order matches the\n // `useSelectTool` contract — last element is the topmost — so `pickTopMostHit`\n // picks correctly. Wraps the caller's `pickEvery` (string-or-null) into the\n // array form `useSelectTool` expects. `poseContainsRotated` reads\n // `pose.rotation` directly (the kit's one rotation convention), so rotated\n // shapes pick against their rendered, rotated body without a per-demo override.\n const walk = useMemo(() => {\n return (wx: number, wy: number, view?: PickView | null): string[] => {\n if (pickEveryProp) {\n const r = pickEveryProp(wx, wy);\n if (r == null) return [];\n return Array.isArray(r) ? r : [r];\n }\n const asked = view ?? getView?.() ?? null;\n const viewAlpha = view?.alphaOf ?? alphaOf;\n const viewLayers = view?.layerIsPainted ?? layerIsPainted;\n const camera = cameraOf(asked);\n // No `getPose`: the scene's painted poses are the adapter's world poses\n // (`useSceneSelectTool.index.test.tsx`), and they let the index serve the walk.\n const src = scenePickSource<TData, TLayer, TPose>(scene, {\n index: { descriptor: d as PoseDescriptor<unknown> },\n ...(viewAlpha ? { alphaOf: viewAlpha } : {}),\n ...(viewLayers ? { layerIsPainted: viewLayers } : {}),\n ...(camera ? { camera } : {}),\n });\n const pointQuery = scenePointQuery<TPose>(d as PoseDescriptor<unknown>, shapePicking, pickTolerancePx);\n return pickWalk<TPose>(src, pointQuery(wx, wy, asked?.scale ?? { x: 1, y: 1 }));\n };\n }, [scene, pickEveryProp, shapePicking, pickTolerancePx, getView,\n alphaOf, layerIsPainted, d]);\n const wiredHitBody = useMemo(() => {\n if (!isPointerInteractive) return walk;\n return (wx: number, wy: number, view?: PickView | null): string[] =>\n walk(wx, wy, view).filter((id) => isPointerInteractive(id) !== false);\n }, [walk, isPointerInteractive]);\n return wiredHitBody;\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useDecayLoop } from "../../core/viewport/useDecayLoop.js";
|
|
2
2
|
import { drawnBoxes } from "../../interactions/actions/drawnBoxes.js";
|
|
3
|
+
import { useEnteredGroupApiSource } from "../deps/enteredGroup.js";
|
|
3
4
|
import { useActionSet, useStandardDeps } from "../../interactions/actions/useActionSet.js";
|
|
4
5
|
import { usePoseDescriptorDepSource } from "../deps/poseDescriptor.js";
|
|
5
6
|
import { useViewDepSource } from "../deps/view.js";
|
|
@@ -19,7 +20,7 @@ import { useDispatcherDepSource } from "../deps/dispatcher.js";
|
|
|
19
20
|
import { useLayoutDepSource } from "../deps/layout.js";
|
|
20
21
|
import { useIngestionDepSource } from "../deps/ingestion.js";
|
|
21
22
|
import { useActionsPropResolver } from "./useActionsPropResolver.js";
|
|
22
|
-
import { useActionTunings } from "./useActionTunings.js";
|
|
23
|
+
import { useActionTunings, useBindingOverrides } from "./useActionTunings.js";
|
|
23
24
|
import { useViewportActions, viewportZoomOption } from "./useViewportActions.js";
|
|
24
25
|
import { useEffect, useMemo } from "react";
|
|
25
26
|
import { useActionsRegistry, useDepSource } from "@weasel-js/routing/react";
|
|
@@ -34,7 +35,7 @@ import { useActionsRegistry, useDepSource } from "@weasel-js/routing/react";
|
|
|
34
35
|
* parent's effects run after its children's, so a child's override would lose.
|
|
35
36
|
*/
|
|
36
37
|
function useSceneStandardActions(inputs) {
|
|
37
|
-
const { props, viewOn, selection, scene, adapter, standardActions, currentViewRef, onViewChange, poseDescriptor, dispatcher, pickEvery, layerIsPainted, alphaOf, viewAnimation, editAnchorsExternalState, snap, canvasRef, canvasApiRef, ingestionClipboard } = inputs;
|
|
38
|
+
const { props, viewOn, selection, enteredGroup, scene, adapter, standardActions, currentViewRef, onViewChange, poseDescriptor, dispatcher, pickEvery, layerIsPainted, alphaOf, reachable, viewAnimation, editAnchorsExternalState, snap, canvasRef, canvasApiRef, ingestionClipboard } = inputs;
|
|
38
39
|
const { tune, viewport, ingestion } = props;
|
|
39
40
|
const boundsIncludeStroke = props.geometry?.boundsIncludeStroke ?? true;
|
|
40
41
|
const reflowTransition = props.reflowTransition;
|
|
@@ -67,9 +68,10 @@ function useSceneStandardActions(inputs) {
|
|
|
67
68
|
zoom: viewOn ? viewportZoom : false,
|
|
68
69
|
pinchZoom: viewOn ? viewport?.pinchZoom ?? true : false
|
|
69
70
|
});
|
|
70
|
-
useAreaSelectDepSource(scene, selection, poseDescriptor, alphaOf);
|
|
71
|
+
useAreaSelectDepSource(scene, selection, poseDescriptor, alphaOf, reachable);
|
|
71
72
|
useNodeAtPointDepSource(pickEvery);
|
|
72
73
|
useLayoutDepSource(scene, props.layoutDropTarget, reflowTransition);
|
|
74
|
+
useEnteredGroupApiSource(enteredGroup);
|
|
73
75
|
useInsertDepSource(scene, adapter, props.insertNodeFactories, props.insertPaint);
|
|
74
76
|
usePathLeafDepSources(scene, selection, adapter, poseDescriptor);
|
|
75
77
|
useSnapDepSource(currentViewRef, {
|
|
@@ -92,7 +94,7 @@ function useSceneStandardActions(inputs) {
|
|
|
92
94
|
usePreviewPathsDepSource(canvasApiRef);
|
|
93
95
|
useDepChangeNotices(scene, selection.current, editAnchorsExternalState);
|
|
94
96
|
useIngestionDepSource(canvasRef, () => currentViewRef.current, ingestion?.resolveSrc, ingestion?.svg, ingestionClipboard);
|
|
95
|
-
useLassoSelectDepSource(scene, selection, poseDescriptor, alphaOf);
|
|
97
|
+
useLassoSelectDepSource(scene, selection, poseDescriptor, alphaOf, reachable);
|
|
96
98
|
useTextEditDepSource(scene);
|
|
97
99
|
useEditAnchorsDepSource(scene, selection, adapter, editAnchorsExternalState, tune);
|
|
98
100
|
useDispatcherDepSource(dispatcher);
|
|
@@ -100,6 +102,7 @@ function useSceneStandardActions(inputs) {
|
|
|
100
102
|
useDepSource("boundsIncludeStroke", () => boundsIncludeStroke);
|
|
101
103
|
useActionsPropResolver(props.actions, standardActions.map((a) => a.id).join("|"));
|
|
102
104
|
useActionTunings(tune);
|
|
105
|
+
useBindingOverrides(props.bindingOverrides);
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
//#endregion
|