@weasel-js/core 1.13.0-rc.0 → 1.14.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 +408 -0
- package/README.md +8 -12
- package/dist/affordances/hitAffordanceRegions.js +1 -1
- package/dist/affordances/hitAffordanceRegions.js.map +1 -1
- package/dist/affordances/pathAnchors.js.map +1 -1
- package/dist/animation/clipTargets.js +1 -1
- package/dist/animation/clipTargets.js.map +1 -1
- package/dist/animation/poseTarget.js +5 -32
- package/dist/animation/poseTarget.js.map +1 -1
- package/dist/{autoPoseDescriptor-CZ1eXbzY.d.ts → autoPoseDescriptor-C25QVb0W.d.ts} +1 -1
- package/dist/barrel/canvas.js +8 -8
- package/dist/barrel/chrome.js +9 -9
- package/dist/barrel/input.js +1 -1
- package/dist/barrel/interactions.js +3 -3
- package/dist/barrel/paint.js +2 -2
- package/dist/barrel/paths.js +19 -17
- package/dist/barrel/renderer.js +11 -7
- package/dist/barrel/routing.js +4 -1
- package/dist/barrel/scene.js +16 -12
- package/dist/barrel/text.js +5 -5
- package/dist/barrel/viewport.js +8 -8
- package/dist/canvas/Canvas.js +2 -2
- package/dist/canvas/Canvas.js.map +1 -1
- package/dist/canvas/CanvasView.js +20 -14
- package/dist/canvas/CanvasView.js.map +1 -1
- package/dist/canvas/GestureReason.js +64 -0
- package/dist/canvas/GestureReason.js.map +1 -0
- package/dist/canvas/GestureReason.module.js +7 -0
- package/dist/canvas/GestureReason.module.js.map +1 -0
- package/dist/canvas/LeanSceneCanvas.js +3 -1
- package/dist/canvas/LeanSceneCanvas.js.map +1 -1
- package/dist/canvas/NodeShape.js +2 -0
- package/dist/canvas/NodeShape.js.map +1 -1
- package/dist/canvas/PickHud.js +7 -3
- package/dist/canvas/PickHud.js.map +1 -1
- package/dist/canvas/SceneCanvas/CanvasKitProviders.js +1 -1
- package/dist/canvas/SceneCanvas/CanvasKitProviders.js.map +1 -1
- package/dist/canvas/SceneCanvas/DevHuds.js +2 -2
- package/dist/canvas/SceneCanvas/DevHuds.js.map +1 -1
- package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +4 -2
- package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js.map +1 -1
- package/dist/canvas/SceneCanvas/SceneCanvasInput.js +3 -1
- package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
- package/dist/canvas/SceneCanvas/brushOverlay.js.map +1 -1
- package/dist/canvas/SceneCanvas/builtinToolSets.js +17 -9
- package/dist/canvas/SceneCanvas/builtinToolSets.js.map +1 -1
- package/dist/canvas/SceneCanvas/canvasPassThrough.js +2 -1
- package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js +75 -19
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
- package/dist/canvas/SceneCanvas/defaultNodeRouting.js +7 -1
- package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
- package/dist/canvas/SceneCanvas/defaultTextEditor.js +108 -0
- package/dist/canvas/SceneCanvas/defaultTextEditor.js.map +1 -0
- package/dist/canvas/SceneCanvas/describePresets.js +47 -0
- package/dist/canvas/SceneCanvas/describePresets.js.map +1 -0
- package/dist/canvas/SceneCanvas/features.js +57 -16
- package/dist/canvas/SceneCanvas/features.js.map +1 -1
- package/dist/canvas/SceneCanvas/mergeLayers.js +10 -1
- package/dist/canvas/SceneCanvas/mergeLayers.js.map +1 -1
- package/dist/canvas/SceneCanvas/namedPresets.js +52 -24
- package/dist/canvas/SceneCanvas/namedPresets.js.map +1 -1
- package/dist/canvas/SceneCanvas/presets/arrange.js +35 -10
- package/dist/canvas/SceneCanvas/presets/arrange.js.map +1 -1
- package/dist/canvas/SceneCanvas/presets/baseline.js +9 -0
- package/dist/canvas/SceneCanvas/presets/baseline.js.map +1 -0
- package/dist/canvas/SceneCanvas/presets/chrome.js.map +1 -1
- package/dist/canvas/SceneCanvas/presets/composites.js +43 -0
- package/dist/canvas/SceneCanvas/presets/composites.js.map +1 -0
- package/dist/canvas/SceneCanvas/presets/edit.js +79 -26
- package/dist/canvas/SceneCanvas/presets/edit.js.map +1 -1
- package/dist/canvas/SceneCanvas/presets/ingest.js +1 -0
- package/dist/canvas/SceneCanvas/presets/ingest.js.map +1 -1
- package/dist/canvas/SceneCanvas/presets/paths.js +58 -16
- package/dist/canvas/SceneCanvas/presets/paths.js.map +1 -1
- package/dist/canvas/SceneCanvas/presets/select.js +4 -2
- package/dist/canvas/SceneCanvas/presets/select.js.map +1 -1
- package/dist/canvas/SceneCanvas/presets/tools.js +24 -0
- package/dist/canvas/SceneCanvas/presets/tools.js.map +1 -0
- package/dist/canvas/SceneCanvas/presets/transform.js +4 -0
- package/dist/canvas/SceneCanvas/presets/transform.js.map +1 -1
- package/dist/canvas/SceneCanvas/presets/view.js +3 -1
- package/dist/canvas/SceneCanvas/presets/view.js.map +1 -1
- package/dist/canvas/SceneCanvas/props.js.map +1 -1
- package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js +1 -1
- package/dist/canvas/SceneCanvas/shapeGeneratorProperties.js.map +1 -1
- package/dist/canvas/SceneCanvas/toolRegistry.js +5 -4
- package/dist/canvas/SceneCanvas/toolRegistry.js.map +1 -1
- package/dist/canvas/SceneCanvas/useCanvasTools.js +26 -10
- package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useContributionRoles.js +17 -2
- package/dist/canvas/SceneCanvas/useContributionRoles.js.map +1 -1
- package/dist/canvas/SceneCanvas/useDepartureGhostLayer.js +5 -4
- package/dist/canvas/SceneCanvas/useDepartureGhostLayer.js.map +1 -1
- package/dist/canvas/SceneCanvas/useDispatcherOverlayLayer.js +1 -1
- package/dist/canvas/SceneCanvas/useNodeAtPoint.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +9 -8
- package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasPathEdit.js +2 -2
- package/dist/canvas/SceneCanvas/useSceneCanvasPathEdit.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +4 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneChrome.js +7 -4
- package/dist/canvas/SceneCanvas/useSceneChrome.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js +9 -9
- package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js.map +1 -1
- package/dist/canvas/SceneCanvas/useScenePicking.js +4 -7
- package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneRedrawSources.js +2 -1
- package/dist/canvas/SceneCanvas/useSceneRedrawSources.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js +6 -6
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js +2 -2
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSelectionOverlayLayer.js +1 -1
- package/dist/canvas/SceneCanvas/useSelectionOverlayLayer.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSurfaceViewInputs.js.map +1 -1
- package/dist/canvas/SceneCanvas/useToolKeybindings.js +2 -2
- package/dist/canvas/SceneCanvas/useToolKeybindings.js.map +1 -1
- package/dist/canvas/SceneCanvas/useViewportActions.js +5 -2
- package/dist/canvas/SceneCanvas/useViewportActions.js.map +1 -1
- package/dist/canvas/SceneCanvas/worldPointAt.js.map +1 -1
- package/dist/canvas/SceneCanvas.js +22 -11
- package/dist/canvas/SceneCanvas.js.map +1 -1
- package/dist/canvas/SceneViewCanvas.js +1 -0
- package/dist/canvas/SceneViewCanvas.js.map +1 -1
- package/dist/canvas/ViewMarker.js.map +1 -1
- package/dist/canvas/accentColor.js +14 -1
- package/dist/canvas/accentColor.js.map +1 -1
- package/dist/canvas/affordanceAt.js +2 -2
- package/dist/canvas/affordanceAt.js.map +1 -1
- package/dist/canvas/buildSceneLayer.js +1 -1
- package/dist/canvas/buildSceneLayer.js.map +1 -1
- package/dist/canvas/buildSceneTree.js +5 -3
- package/dist/canvas/buildSceneTree.js.map +1 -1
- package/dist/canvas/canvasRenderer.js +6 -2
- package/dist/canvas/canvasRenderer.js.map +1 -1
- package/dist/canvas/deps/editAnchors.js +4 -4
- package/dist/canvas/deps/editAnchors.js.map +1 -1
- package/dist/canvas/deps/enteredGroup.js +14 -5
- package/dist/canvas/deps/enteredGroup.js.map +1 -1
- package/dist/canvas/deps/ingestion.js +5 -1
- package/dist/canvas/deps/ingestion.js.map +1 -1
- package/dist/canvas/deps/insert.js.map +1 -1
- package/dist/canvas/deps/insertSite.js +5 -3
- package/dist/canvas/deps/insertSite.js.map +1 -1
- package/dist/canvas/deps/nodeAtPoint.js +1 -12
- package/dist/canvas/deps/nodeAtPoint.js.map +1 -1
- package/dist/canvas/deps/pathEditCommits.js +3 -3
- package/dist/canvas/deps/pathEditCommits.js.map +1 -1
- package/dist/canvas/deps/slice.js +16 -2
- package/dist/canvas/deps/slice.js.map +1 -1
- package/dist/canvas/deps/snap.js +5 -5
- package/dist/canvas/deps/snap.js.map +1 -1
- package/dist/canvas/deps/textEdit.js +5 -6
- package/dist/canvas/deps/textEdit.js.map +1 -1
- package/dist/canvas/derivedPath.js +8 -3
- package/dist/canvas/derivedPath.js.map +1 -1
- package/dist/canvas/getNodeAtPoint.js +2 -2
- package/dist/canvas/getNodeAtPoint.js.map +1 -1
- package/dist/canvas/insertPreviewExtent.js.map +1 -1
- package/dist/canvas/layerSlots.js.map +1 -1
- package/dist/canvas/nodeShapes/measurePainter.js +1 -1
- package/dist/canvas/nodeShapes/measurePainter.js.map +1 -1
- package/dist/canvas/nodeShapes/strokedBounds.js +5 -3
- package/dist/canvas/nodeShapes/strokedBounds.js.map +1 -1
- package/dist/canvas/overlayRoleStrokes.js +6 -0
- package/dist/canvas/overlayRoleStrokes.js.map +1 -1
- package/dist/canvas/pickIndex/scenePickIndex.js +10 -12
- package/dist/canvas/pickIndex/scenePickIndex.js.map +1 -1
- package/dist/canvas/sceneAdapter.js +10 -19
- package/dist/canvas/sceneAdapter.js.map +1 -1
- package/dist/canvas/sceneClipboard.js +17 -4
- package/dist/canvas/sceneClipboard.js.map +1 -1
- package/dist/canvas/sceneViewRender.js +7 -5
- package/dist/canvas/sceneViewRender.js.map +1 -1
- package/dist/canvas/surfaceContribution.js.map +1 -1
- package/dist/canvas/useCanvasPaint.js +12 -4
- package/dist/canvas/useCanvasPaint.js.map +1 -1
- package/dist/canvas/useHitObserver.js +1 -1
- package/dist/canvas/useHitObserver.js.map +1 -1
- package/dist/canvas/useViewAim.js +1 -1
- package/dist/canvas/useViewAim.js.map +1 -1
- package/dist/canvas/useViewHelpers.js +8 -5
- package/dist/canvas/useViewHelpers.js.map +1 -1
- package/dist/canvas/useViewNesting.js +39 -12
- package/dist/canvas/useViewNesting.js.map +1 -1
- package/dist/canvas/viewInputs.js.map +1 -1
- package/dist/canvas/viewMarkers.js +2 -2
- package/dist/canvas/viewMarkers.js.map +1 -1
- package/dist/canvas/viewRegistry.js +2 -2
- package/dist/canvas/viewRegistry.js.map +1 -1
- package/dist/canvas/viewSnap.js +67 -0
- package/dist/canvas/viewSnap.js.map +1 -0
- package/dist/clipboard.d.ts +6 -12
- package/dist/{clone-qb2e1F5p.d.ts → clone-Bz4iqGfE.d.ts} +10 -5
- package/dist/clone.d.ts +5 -5
- package/dist/core/geometry/booleanOfPaths.js +23 -0
- package/dist/core/geometry/booleanOfPaths.js.map +1 -0
- package/dist/core/geometry/pathPoseDescriptor.js +12 -9
- package/dist/core/geometry/pathPoseDescriptor.js.map +1 -1
- package/dist/core/geometry/poseDescriptor.js +12 -3
- package/dist/core/geometry/poseDescriptor.js.map +1 -1
- package/dist/core/geometry/targetFrame.js +2 -13
- package/dist/core/geometry/targetFrame.js.map +1 -1
- package/dist/core/layers/render.js +2 -1
- package/dist/core/layers/render.js.map +1 -1
- package/dist/core/layers/retain.js +5 -3
- package/dist/core/layers/retain.js.map +1 -1
- package/dist/core/ops/setPath.js +3 -0
- package/dist/core/ops/setPath.js.map +1 -1
- package/dist/core/paint/meshKindLazy.js.map +1 -1
- package/dist/core/paint/paintKindEntry.js.map +1 -1
- package/dist/core/paint/paintKinds.js +8 -1
- package/dist/core/paint/paintKinds.js.map +1 -1
- package/dist/core/scene/NodeProperties.js.map +1 -1
- package/dist/core/scene/NodeRouting.js.map +1 -1
- package/dist/core/scene/booleanGroup.js +33 -0
- package/dist/core/scene/booleanGroup.js.map +1 -0
- package/dist/core/scene/booleanOfChildren.js +53 -0
- package/dist/core/scene/booleanOfChildren.js.map +1 -0
- package/dist/core/scene/carryDescendants.js +18 -6
- package/dist/core/scene/carryDescendants.js.map +1 -1
- package/dist/core/scene/composeFrame.js +42 -5
- package/dist/core/scene/composeFrame.js.map +1 -1
- package/dist/core/scene/composePose.js +35 -16
- package/dist/core/scene/composePose.js.map +1 -1
- package/dist/core/scene/depMemo.js +36 -18
- package/dist/core/scene/depMemo.js.map +1 -1
- package/dist/core/scene/derivedParts.js +2 -23
- package/dist/core/scene/derivedParts.js.map +1 -1
- package/dist/core/scene/effectivePose.js +10 -0
- package/dist/core/scene/effectivePose.js.map +1 -1
- package/dist/core/scene/historyForm.js +5 -4
- package/dist/core/scene/historyForm.js.map +1 -1
- package/dist/core/scene/kitOps.js +5 -71
- 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/layerOps.js +82 -0
- package/dist/core/scene/layerOps.js.map +1 -0
- package/dist/core/scene/layoutPass.js +14 -12
- package/dist/core/scene/layoutPass.js.map +1 -1
- package/dist/core/scene/lockGuard.js +9 -3
- package/dist/core/scene/lockGuard.js.map +1 -1
- package/dist/core/scene/nestedHit.js +5 -5
- package/dist/core/scene/nestedHit.js.map +1 -1
- package/dist/core/scene/nodeFlags.js +8 -2
- package/dist/core/scene/nodeFlags.js.map +1 -1
- package/dist/core/scene/nodeGates.js +31 -2
- package/dist/core/scene/nodeGates.js.map +1 -1
- package/dist/core/scene/pathEffects/effects.js +22 -3
- package/dist/core/scene/pathEffects/effects.js.map +1 -1
- package/dist/core/scene/pathEffects/textAlong.js +16 -3
- package/dist/core/scene/pathEffects/textAlong.js.map +1 -1
- package/dist/core/scene/pathEffects/textAlongBands.js +65 -0
- package/dist/core/scene/pathEffects/textAlongBands.js.map +1 -0
- package/dist/core/scene/reconcileSpecs.js +1 -1
- package/dist/core/scene/reconcileSpecs.js.map +1 -1
- package/dist/core/scene/scene.js +11 -5
- package/dist/core/scene/scene.js.map +1 -1
- package/dist/core/scene/sceneEdits.js +5 -2
- package/dist/core/scene/sceneEdits.js.map +1 -1
- package/dist/core/scene/scenePoseAdapter.js +10 -1
- package/dist/core/scene/scenePoseAdapter.js.map +1 -1
- package/dist/core/scene/scrollOffsets.js +93 -0
- package/dist/core/scene/scrollOffsets.js.map +1 -0
- package/dist/core/scene/serialization.js +15 -5
- package/dist/core/scene/serialization.js.map +1 -1
- package/dist/core/scene/statedFields.js +2 -1
- package/dist/core/scene/statedFields.js.map +1 -1
- package/dist/core/scene/strategyView.js +35 -0
- package/dist/core/scene/strategyView.js.map +1 -0
- package/dist/core/scene/types.js.map +1 -1
- package/dist/core/scene/useScene.js +2 -0
- package/dist/core/scene/useScene.js.map +1 -1
- package/dist/core/scene/worldSave.js +87 -0
- package/dist/core/scene/worldSave.js.map +1 -0
- package/dist/core/selection/selectionTarget.js +1 -1
- package/dist/core/selection/selectionTarget.js.map +1 -1
- package/dist/{cornerHandles-CNCb7aTY.d.ts → cornerHandles-DpmQM_z5.d.ts} +1 -1
- package/dist/{depSchema-B1RS-IJx.d.ts → depSchema-C3BwxitB.d.ts} +130 -104
- package/dist/devtools-protocol.d.ts +2 -2
- package/dist/devtools.d.ts +2 -2
- package/dist/{features → domains}/chrome-caps/buildChromeCtx.js +1 -1
- package/dist/domains/chrome-caps/buildChromeCtx.js.map +1 -0
- package/dist/{features → domains}/chrome-caps/conditions.js +1 -1
- package/dist/domains/chrome-caps/conditions.js.map +1 -0
- package/dist/{features → domains}/chrome-caps/defaults.js +1 -1
- package/dist/domains/chrome-caps/defaults.js.map +1 -0
- package/dist/{features → domains}/chrome-caps/resolve.js +1 -1
- package/dist/domains/chrome-caps/resolve.js.map +1 -0
- package/dist/{features → domains}/chrome-caps/useChromeCaps.js +1 -1
- package/dist/domains/chrome-caps/useChromeCaps.js.map +1 -0
- package/dist/{features → domains}/chrome-caps/useHoverTracking.js +1 -1
- package/dist/domains/chrome-caps/useHoverTracking.js.map +1 -0
- package/dist/{features → domains}/ingestion/contentHandlers.js +20 -3
- package/dist/domains/ingestion/contentHandlers.js.map +1 -0
- package/dist/domains/ingestion/ghost.js +56 -0
- package/dist/domains/ingestion/ghost.js.map +1 -0
- package/dist/{features → domains}/ingestion/imageHandler.js +1 -1
- package/dist/domains/ingestion/imageHandler.js.map +1 -0
- package/dist/domains/ingestion/kitHandlers.js +22 -0
- package/dist/domains/ingestion/kitHandlers.js.map +1 -0
- package/dist/{features → domains}/ingestion/openFilePicker.js +1 -1
- package/dist/domains/ingestion/openFilePicker.js.map +1 -0
- package/dist/{features → domains}/ingestion/placement.js +1 -1
- package/dist/domains/ingestion/placement.js.map +1 -0
- package/dist/{features → domains}/ingestion/svgHandler.js +1 -1
- package/dist/domains/ingestion/svgHandler.js.map +1 -0
- package/dist/{features → domains}/ingestion/textHandler.js +9 -7
- package/dist/domains/ingestion/textHandler.js.map +1 -0
- package/dist/{features → domains}/ingestion/useCanvasIngestion.js +13 -15
- package/dist/domains/ingestion/useCanvasIngestion.js.map +1 -0
- package/dist/{features → domains}/ingestion/weaselJsonHandler.js +1 -1
- package/dist/domains/ingestion/weaselJsonHandler.js.map +1 -0
- package/dist/{features → domains}/longPress/longPressFeedback.js +1 -1
- package/dist/domains/longPress/longPressFeedback.js.map +1 -0
- package/dist/{features → domains}/measure/actionIds.js +1 -1
- package/dist/domains/measure/actionIds.js.map +1 -0
- package/dist/{features → domains}/measure/measureCommands.js +1 -1
- package/dist/domains/measure/measureCommands.js.map +1 -0
- package/dist/{features → domains}/measure/measureContribution.js +1 -1
- package/dist/domains/measure/measureContribution.js.map +1 -0
- package/dist/{features → domains}/measure/measureEdits.js +1 -1
- package/dist/domains/measure/measureEdits.js.map +1 -0
- package/dist/{features → domains}/measure/measureGestures.js +4 -7
- package/dist/domains/measure/measureGestures.js.map +1 -0
- package/dist/{features → domains}/measure/measureHit.js +1 -1
- package/dist/domains/measure/measureHit.js.map +1 -0
- package/dist/{features → domains}/measure/measureLayer.js +1 -1
- package/dist/domains/measure/measureLayer.js.map +1 -0
- package/dist/{features → domains}/measure/measurement.js +1 -1
- package/dist/domains/measure/measurement.js.map +1 -0
- package/dist/{features → domains}/measure/measurements.js +2 -2
- package/dist/domains/measure/measurements.js.map +1 -0
- package/dist/{features → domains}/modes/modeLayers.js +1 -1
- package/dist/domains/modes/modeLayers.js.map +1 -0
- package/dist/{features → domains}/modes/modeTriggers.js +3 -6
- package/dist/domains/modes/modeTriggers.js.map +1 -0
- package/dist/{features → domains}/pathEffects/effectSources.js +1 -1
- package/dist/domains/pathEffects/effectSources.js.map +1 -0
- package/dist/{features → domains}/pathEffects/index.js +1 -1
- package/dist/domains/pathEffects/index.js.map +1 -0
- package/dist/{features → domains}/pathEffects/readSource.js +1 -1
- package/dist/domains/pathEffects/readSource.js.map +1 -0
- package/dist/{features → domains}/pathEffects/textItems.js +11 -2
- package/dist/domains/pathEffects/textItems.js.map +1 -0
- package/dist/{features → domains}/pathEffects/useEffectSourcesLayer.js +1 -1
- package/dist/domains/pathEffects/useEffectSourcesLayer.js.map +1 -0
- package/dist/{features → domains}/paths/anchorData.js +1 -1
- package/dist/domains/paths/anchorData.js.map +1 -0
- package/dist/{features → domains}/paths/anchorMarkers.js +9 -7
- package/dist/domains/paths/anchorMarkers.js.map +1 -0
- package/dist/{features → domains}/paths/editablePath.js +1 -1
- package/dist/domains/paths/editablePath.js.map +1 -0
- package/dist/{features → domains}/paths/index.js +3 -3
- package/dist/{features → domains}/paths/originProjection.js +1 -1
- package/dist/domains/paths/originProjection.js.map +1 -0
- package/dist/{features → domains}/paths/pathEditExtension.js +1 -1
- package/dist/domains/paths/pathEditExtension.js.map +1 -0
- package/dist/{features → domains}/paths/pathEditState.js +1 -1
- package/dist/domains/paths/pathEditState.js.map +1 -0
- package/dist/{features → domains}/paths/pathEditingOverlayLayer.js +1 -1
- package/dist/domains/paths/pathEditingOverlayLayer.js.map +1 -0
- package/dist/{features → domains}/paths/pathLayer.js +1 -1
- package/dist/domains/paths/pathLayer.js.map +1 -0
- package/dist/{features → domains}/paths/pathRepresentation.js +1 -1
- package/dist/domains/paths/pathRepresentation.js.map +1 -0
- package/dist/{features → domains}/paths/pathSnap/glyphs.js +1 -1
- package/dist/domains/paths/pathSnap/glyphs.js.map +1 -0
- package/dist/{features → domains}/paths/pathSnap/pathSnapSource.js +1 -1
- package/dist/domains/paths/pathSnap/pathSnapSource.js.map +1 -0
- package/dist/{features → domains}/paths/pathSnap/sceneIndex.js +1 -1
- package/dist/domains/paths/pathSnap/sceneIndex.js.map +1 -0
- package/dist/{features → domains}/paths/pathSnap/snapGeometry.js +3 -2
- package/dist/domains/paths/pathSnap/snapGeometry.js.map +1 -0
- package/dist/{features → domains}/paths/penPreviewLayer.js +1 -1
- package/dist/domains/paths/penPreviewLayer.js.map +1 -0
- package/dist/{features → domains}/paths/shapeGenerator.js +6 -4
- package/dist/domains/paths/shapeGenerator.js.map +1 -0
- package/dist/{features → domains}/paths/strokeOutline.js +1 -1
- package/dist/domains/paths/strokeOutline.js.map +1 -0
- package/dist/{features → domains}/paths/useAnchorFields.js +1 -1
- package/dist/domains/paths/useAnchorFields.js.map +1 -0
- package/dist/{features → domains}/paths/usePathEdit.js +1 -1
- package/dist/domains/paths/usePathEdit.js.map +1 -0
- package/dist/{features → domains}/poseRun/usePoseRun.js +1 -1
- package/dist/domains/poseRun/usePoseRun.js.map +1 -0
- package/dist/{features → domains}/selection/enteredGroupFrame.js +49 -10
- package/dist/domains/selection/enteredGroupFrame.js.map +1 -0
- package/dist/{features → domains}/selection/enteredGroupScope.js +1 -1
- package/dist/domains/selection/enteredGroupScope.js.map +1 -0
- package/dist/domains/selection/enteredOperands.js +29 -0
- package/dist/domains/selection/enteredOperands.js.map +1 -0
- package/dist/{features → domains}/selection/handleCommands.js +1 -1
- package/dist/domains/selection/handleCommands.js.map +1 -0
- package/dist/{features → domains}/selection/overlay.js +1 -1
- package/dist/domains/selection/overlay.js.map +1 -0
- package/dist/{features → domains}/selection/selectionPose.js +1 -1
- package/dist/domains/selection/selectionPose.js.map +1 -0
- package/dist/{features → domains}/snap/angle2d.js +1 -1
- package/dist/domains/snap/angle2d.js.map +1 -0
- package/dist/{features → domains}/snap/debugCandidates.js +1 -1
- package/dist/domains/snap/debugCandidates.js.map +1 -0
- package/dist/{features → domains}/snap/gather.js +1 -1
- package/dist/domains/snap/gather.js.map +1 -0
- package/dist/{features → domains}/snap/inputSubspace.js +1 -1
- package/dist/domains/snap/inputSubspace.js.map +1 -0
- package/dist/{features → domains}/snap/lazySession.js +1 -1
- package/dist/domains/snap/lazySession.js.map +1 -0
- package/dist/{features → domains}/snap/marks.js +1 -1
- package/dist/domains/snap/marks.js.map +1 -0
- package/dist/{features → domains}/snap/outcomeStore.js +1 -1
- package/dist/domains/snap/outcomeStore.js.map +1 -0
- package/dist/{features → domains}/snap/pointSnap.js +1 -1
- package/dist/domains/snap/pointSnap.js.map +1 -0
- package/dist/{features → domains}/snap/probes2d.js +1 -1
- package/dist/domains/snap/probes2d.js.map +1 -0
- package/dist/{features → domains}/snap/readoutDraw.js +1 -1
- package/dist/domains/snap/readoutDraw.js.map +1 -0
- package/dist/{features → domains}/snap/readoutLayer.js +1 -1
- package/dist/domains/snap/readoutLayer.js.map +1 -0
- package/dist/{features → domains}/snap/registry.js +1 -1
- package/dist/domains/snap/registry.js.map +1 -0
- package/dist/{features → domains}/snap/resizeBox.js +1 -1
- package/dist/domains/snap/resizeBox.js.map +1 -0
- package/dist/{features → domains}/snap/roles.js +1 -1
- package/dist/domains/snap/roles.js.map +1 -0
- package/dist/{features → domains}/snap/rotationSource.js +1 -1
- package/dist/domains/snap/rotationSource.js.map +1 -0
- package/dist/{features → domains}/snap/sceneReads.js +24 -3
- package/dist/domains/snap/sceneReads.js.map +1 -0
- package/dist/{features → domains}/snap/sceneSession2d.js +3 -2
- package/dist/domains/snap/sceneSession2d.js.map +1 -0
- package/dist/{features → domains}/snap/screen.js +1 -1
- package/dist/domains/snap/screen.js.map +1 -0
- package/dist/{features → domains}/snap/session2d.js +3 -2
- package/dist/domains/snap/session2d.js.map +1 -0
- package/dist/{features → domains}/snap/spanSnap.js +1 -1
- package/dist/domains/snap/spanSnap.js.map +1 -0
- package/dist/{features → domains}/snap/stepAngles.js +1 -1
- package/dist/domains/snap/stepAngles.js.map +1 -0
- package/dist/{features → domains}/text/editingDrawOne.js +4 -4
- package/dist/domains/text/editingDrawOne.js.map +1 -0
- package/dist/domains/text/hostTextEditors.js +24 -0
- package/dist/domains/text/hostTextEditors.js.map +1 -0
- package/dist/{features → domains}/text/index.js +1 -1
- package/dist/{features → domains}/text/sceneTextDrafts.js +1 -1
- package/dist/domains/text/sceneTextDrafts.js.map +1 -0
- package/dist/{features → domains}/text/textHitAt.js +18 -2
- package/dist/domains/text/textHitAt.js.map +1 -0
- package/dist/{features → domains}/text/textLayer.js +1 -1
- package/dist/domains/text/textLayer.js.map +1 -0
- package/dist/{features → domains}/text/useSceneTextEdit.js +11 -3
- package/dist/domains/text/useSceneTextEdit.js.map +1 -0
- package/dist/{features → domains}/viewports/aim.js +1 -1
- package/dist/domains/viewports/aim.js.map +1 -0
- package/dist/{features → domains}/viewports/viewMarker.js +23 -5
- package/dist/domains/viewports/viewMarker.js.map +1 -0
- package/dist/{features → domains}/viewports/viewMarkerDrag.js +3 -3
- package/dist/domains/viewports/viewMarkerDrag.js.map +1 -0
- package/dist/{features → domains}/viewports/viewResolver.js +1 -1
- package/dist/domains/viewports/viewResolver.js.map +1 -0
- package/dist/{features → domains}/viewports/viewportLayer.js +3 -2
- package/dist/domains/viewports/viewportLayer.js.map +1 -0
- package/dist/icons.table.js +1 -1
- package/dist/icons.table.js.map +1 -1
- package/dist/icons.table.json +106 -6
- package/dist/index.d.ts +1445 -1053
- package/dist/index.js +90 -77
- package/dist/interactions/actions/admitsChild.js +6 -2
- package/dist/interactions/actions/admitsChild.js.map +1 -1
- package/dist/interactions/actions/align/align.js +1 -4
- package/dist/interactions/actions/align/align.js.map +1 -1
- package/dist/interactions/actions/booleans/booleanGroupAction.js +66 -0
- package/dist/interactions/actions/booleans/booleanGroupAction.js.map +1 -0
- package/dist/interactions/actions/booleans/keymap.js +52 -0
- package/dist/interactions/actions/booleans/keymap.js.map +1 -0
- package/dist/interactions/actions/booleans/operands.js +1 -1
- package/dist/interactions/actions/booleans/pieces.js +4 -11
- package/dist/interactions/actions/booleans/pieces.js.map +1 -1
- package/dist/interactions/actions/clipboard/action.js +3 -7
- package/dist/interactions/actions/clipboard/action.js.map +1 -1
- package/dist/interactions/actions/clipboard/clipboardOps.js +1 -1
- package/dist/interactions/actions/clipboard/clipboardOps.js.map +1 -1
- package/dist/interactions/actions/clipboard/materializePaste.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorEditing.js +6 -6
- package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
- package/dist/interactions/actions/defaults/bendSegment.js +1 -1
- package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
- package/dist/interactions/actions/defaults/containerScroll.js +170 -0
- package/dist/interactions/actions/defaults/containerScroll.js.map +1 -0
- package/dist/interactions/actions/defaults/contextMenu.js +2 -5
- package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
- package/dist/interactions/actions/defaults/delete.js +1 -1
- package/dist/interactions/actions/defaults/delete.js.map +1 -1
- package/dist/interactions/actions/defaults/duplicate.js +1 -3
- package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
- package/dist/interactions/actions/defaults/enterGroup.js +7 -12
- package/dist/interactions/actions/defaults/enterGroup.js.map +1 -1
- package/dist/interactions/actions/defaults/enterPathEdit.js +4 -8
- package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/group.js +12 -24
- package/dist/interactions/actions/defaults/group.js.map +1 -1
- package/dist/interactions/actions/defaults/ingest.js +18 -10
- package/dist/interactions/actions/defaults/ingest.js.map +1 -1
- package/dist/interactions/actions/defaults/insertPathAnchor.js +3 -3
- package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
- package/dist/interactions/actions/defaults/nudgeSteps.js +3 -21
- package/dist/interactions/actions/defaults/nudgeSteps.js.map +1 -1
- package/dist/interactions/actions/defaults/pathEditMarks.js.map +1 -1
- package/dist/interactions/actions/defaults/selectDeep.js +2 -2
- package/dist/interactions/actions/defaults/selectDeep.js.map +1 -1
- package/dist/interactions/actions/distribute/distribute.js.map +1 -1
- package/dist/interactions/actions/dropVerdict.js +9 -0
- package/dist/interactions/actions/dropVerdict.js.map +1 -0
- package/dist/interactions/actions/edit-anchors/action.js +2 -2
- package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/extensionDrags.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/geometry.js +8 -6
- package/dist/interactions/actions/edit-anchors/geometry.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/handleDrag.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/preview.js +1 -1
- package/dist/interactions/actions/edit-anchors/preview.js.map +1 -1
- package/dist/interactions/actions/flip/action.js +3 -2
- package/dist/interactions/actions/flip/action.js.map +1 -1
- package/dist/interactions/actions/geometryProjection.js +8 -3
- package/dist/interactions/actions/geometryProjection.js.map +1 -1
- package/dist/interactions/actions/gestureFrames.js +1 -0
- package/dist/interactions/actions/gestureFrames.js.map +1 -1
- package/dist/interactions/actions/insert/action.js +6 -2
- package/dist/interactions/actions/insert/action.js.map +1 -1
- package/dist/interactions/actions/insert/frame.js +1 -1
- package/dist/interactions/actions/insert/frame.js.map +1 -1
- package/dist/interactions/actions/insert/placeAction.js +6 -8
- package/dist/interactions/actions/insert/placeAction.js.map +1 -1
- package/dist/interactions/actions/insert/session.js +2 -2
- package/dist/interactions/actions/insert/session.js.map +1 -1
- package/dist/interactions/actions/move/action.js +6 -15
- package/dist/interactions/actions/move/action.js.map +1 -1
- package/dist/interactions/actions/move/behaviors/layoutDrop.js +12 -2
- package/dist/interactions/actions/move/behaviors/layoutDrop.js.map +1 -1
- package/dist/interactions/actions/move/behaviors/reparentOnDrop.js +29 -10
- package/dist/interactions/actions/move/behaviors/reparentOnDrop.js.map +1 -1
- package/dist/interactions/actions/move/behaviors/snapToContainer.js +42 -6
- package/dist/interactions/actions/move/behaviors/snapToContainer.js.map +1 -1
- package/dist/interactions/actions/move/capture.js +4 -8
- package/dist/interactions/actions/move/capture.js.map +1 -1
- package/dist/interactions/actions/move/commit/index.js.map +1 -1
- package/dist/interactions/actions/move/commit/layoutDrop.js +4 -3
- package/dist/interactions/actions/move/commit/layoutDrop.js.map +1 -1
- package/dist/interactions/actions/move/commit/layoutRelease.js +15 -9
- package/dist/interactions/actions/move/commit/layoutRelease.js.map +1 -1
- package/dist/interactions/actions/move/commit/reparent.js +19 -13
- package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
- package/dist/interactions/actions/move/delta.js +51 -5
- package/dist/interactions/actions/move/delta.js.map +1 -1
- package/dist/interactions/actions/move/frame.js +8 -4
- package/dist/interactions/actions/move/frame.js.map +1 -1
- package/dist/interactions/actions/move/layout/container.js +19 -9
- package/dist/interactions/actions/move/layout/container.js.map +1 -1
- package/dist/interactions/actions/move/layout/dragged.js +2 -2
- package/dist/interactions/actions/move/layout/dragged.js.map +1 -1
- package/dist/interactions/actions/move/layout/dropPass.js +36 -18
- package/dist/interactions/actions/move/layout/dropPass.js.map +1 -1
- package/dist/interactions/actions/move/layout/exits.js +2 -2
- package/dist/interactions/actions/move/layout/exits.js.map +1 -1
- package/dist/interactions/actions/move/layout/glide.js +5 -5
- package/dist/interactions/actions/move/layout/glide.js.map +1 -1
- package/dist/interactions/actions/move/layout/view.js +55 -0
- package/dist/interactions/actions/move/layout/view.js.map +1 -0
- package/dist/interactions/actions/move/refusal.js +52 -0
- package/dist/interactions/actions/move/refusal.js.map +1 -0
- package/dist/interactions/actions/move/session.js +9 -8
- package/dist/interactions/actions/move/session.js.map +1 -1
- package/dist/interactions/actions/move/translate.js +10 -4
- package/dist/interactions/actions/move/translate.js.map +1 -1
- package/dist/interactions/actions/moveNodes.js +21 -5
- package/dist/interactions/actions/moveNodes.js.map +1 -1
- package/dist/interactions/actions/pathCommands/pathNodes.js +3 -3
- package/dist/interactions/actions/pathCommands/pathNodes.js.map +1 -1
- package/dist/interactions/actions/planeInput.js +14 -13
- package/dist/interactions/actions/planeInput.js.map +1 -1
- package/dist/interactions/actions/poseFrame.js +21 -8
- package/dist/interactions/actions/poseFrame.js.map +1 -1
- package/dist/interactions/actions/resize/action.js +1 -1
- package/dist/interactions/actions/resize/action.js.map +1 -1
- package/dist/interactions/actions/resize/aspect.js +1 -1
- package/dist/interactions/actions/resize/aspect.js.map +1 -1
- package/dist/interactions/actions/resize/commit.js +10 -1
- package/dist/interactions/actions/resize/commit.js.map +1 -1
- package/dist/interactions/actions/resize/frame.js +3 -2
- package/dist/interactions/actions/resize/frame.js.map +1 -1
- package/dist/interactions/actions/resize/held.js +44 -0
- package/dist/interactions/actions/resize/held.js.map +1 -0
- package/dist/interactions/actions/resize/session.js +3 -1
- package/dist/interactions/actions/resize/session.js.map +1 -1
- package/dist/interactions/actions/resolveOverlays.js +9 -0
- package/dist/interactions/actions/resolveOverlays.js.map +1 -1
- package/dist/interactions/actions/resolvePreviews.js +23 -7
- package/dist/interactions/actions/resolvePreviews.js.map +1 -1
- package/dist/interactions/actions/rotate/action.js +1 -1
- package/dist/interactions/actions/rotate/action.js.map +1 -1
- package/dist/interactions/actions/rotate/frame.js +1 -1
- package/dist/interactions/actions/rotate/frame.js.map +1 -1
- package/dist/interactions/actions/rotate/session.js +2 -2
- package/dist/interactions/actions/rotate/session.js.map +1 -1
- package/dist/interactions/actions/selectionSubtree.js +2 -4
- package/dist/interactions/actions/selectionSubtree.js.map +1 -1
- package/dist/interactions/actions/spatialReference.js +4 -9
- package/dist/interactions/actions/spatialReference.js.map +1 -1
- package/dist/interactions/gestures/dragRadial/dragRadial.js.map +1 -1
- package/dist/interactions/gestures/dragRect/dragRect.js.map +1 -1
- package/dist/kitSettingsSchema.js +2 -2
- package/dist/kitSettingsSchema.js.map +1 -1
- package/dist/{math-C_elShP_.d.ts → math-B_unOnF4.d.ts} +2 -2
- package/dist/math.d.ts +3 -3
- package/dist/move.d.ts +6 -6
- package/dist/{options-ClEm3SEl.d.ts → options-DAjkhbcs.d.ts} +5 -5
- package/dist/{poseDescriptor-DN2yfnq_.d.ts → poseDescriptor-CvSxVhCd.d.ts} +17 -4
- package/dist/resize.d.ts +6 -6
- package/dist/style.css +14 -0
- package/dist/test-seams.d.ts +11 -15
- package/dist/test-seams.js +1 -1
- package/dist/tools/builtin/eyedropper/useEyedropperTool.js +3 -6
- package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
- package/dist/tools/builtin/line/useLineTool.js.map +1 -1
- package/dist/tools/builtin/measure/useMeasureTool.js +2 -2
- package/dist/tools/builtin/measure/useMeasureTool.js.map +1 -1
- package/dist/tools/builtin/pen/penHandle.js +2 -2
- package/dist/tools/builtin/pen/penHandle.js.map +1 -1
- package/dist/tools/builtin/pen/usePenTool.js +13 -19
- package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
- package/dist/tools/builtin/polygon/usePolygonTool.js +1 -1
- package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
- package/dist/tools/builtin/select/groupEntry.js +1 -1
- package/dist/tools/builtin/select/groupEntry.js.map +1 -1
- package/dist/tools/builtin/select/selectionContributions.js +1 -1
- package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
- package/dist/tools/builtin/select/useSelectTool.js +7 -7
- package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
- package/dist/tools/builtin/slice/useSliceTool.js +5 -8
- package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
- package/dist/tools/builtin/star/useStarTool.js +1 -1
- package/dist/tools/builtin/star/useStarTool.js.map +1 -1
- package/dist/tools/index.js +2 -1
- package/dist/{types-49uYJN3j.d.ts → types-A_sNJYRd.d.ts} +8 -0
- package/dist/{types-BEdRIhNc.d.ts → types-DW14eu9B.d.ts} +131 -36
- package/dist/{types-p2alu-R1.d.ts → types-VCtfchp-.d.ts} +4 -6
- package/dist/util/color.js +12 -14
- package/dist/util/color.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +25 -24
- package/dist/canvas/poseCascade.js +0 -65
- package/dist/canvas/poseCascade.js.map +0 -1
- package/dist/features/chrome-caps/buildChromeCtx.js.map +0 -1
- package/dist/features/chrome-caps/conditions.js.map +0 -1
- package/dist/features/chrome-caps/defaults.js.map +0 -1
- package/dist/features/chrome-caps/resolve.js.map +0 -1
- package/dist/features/chrome-caps/useChromeCaps.js.map +0 -1
- package/dist/features/chrome-caps/useHoverTracking.js.map +0 -1
- package/dist/features/ingestion/contentHandlers.js.map +0 -1
- package/dist/features/ingestion/imageHandler.js.map +0 -1
- package/dist/features/ingestion/openFilePicker.js.map +0 -1
- package/dist/features/ingestion/placement.js.map +0 -1
- package/dist/features/ingestion/registerKitHandlers.js +0 -43
- package/dist/features/ingestion/registerKitHandlers.js.map +0 -1
- package/dist/features/ingestion/svgHandler.js.map +0 -1
- package/dist/features/ingestion/textHandler.js.map +0 -1
- package/dist/features/ingestion/useCanvasIngestion.js.map +0 -1
- package/dist/features/ingestion/weaselJsonHandler.js.map +0 -1
- package/dist/features/longPress/longPressFeedback.js.map +0 -1
- package/dist/features/measure/actionIds.js.map +0 -1
- package/dist/features/measure/measureCommands.js.map +0 -1
- package/dist/features/measure/measureContribution.js.map +0 -1
- package/dist/features/measure/measureEdits.js.map +0 -1
- package/dist/features/measure/measureGestures.js.map +0 -1
- package/dist/features/measure/measureHit.js.map +0 -1
- package/dist/features/measure/measureLayer.js.map +0 -1
- package/dist/features/measure/measurement.js.map +0 -1
- package/dist/features/measure/measurements.js.map +0 -1
- package/dist/features/modes/modeLayers.js.map +0 -1
- package/dist/features/modes/modeTriggers.js.map +0 -1
- package/dist/features/pathEffects/effectSources.js.map +0 -1
- package/dist/features/pathEffects/index.js.map +0 -1
- package/dist/features/pathEffects/readSource.js.map +0 -1
- package/dist/features/pathEffects/textItems.js.map +0 -1
- package/dist/features/pathEffects/useEffectSourcesLayer.js.map +0 -1
- package/dist/features/paths/anchorData.js.map +0 -1
- package/dist/features/paths/anchorMarkers.js.map +0 -1
- package/dist/features/paths/editablePath.js.map +0 -1
- package/dist/features/paths/originProjection.js.map +0 -1
- package/dist/features/paths/pathEditExtension.js.map +0 -1
- package/dist/features/paths/pathEditState.js.map +0 -1
- package/dist/features/paths/pathEditingOverlayLayer.js.map +0 -1
- package/dist/features/paths/pathLayer.js.map +0 -1
- package/dist/features/paths/pathRepresentation.js.map +0 -1
- package/dist/features/paths/pathSnap/glyphs.js.map +0 -1
- package/dist/features/paths/pathSnap/pathSnapSource.js.map +0 -1
- package/dist/features/paths/pathSnap/sceneIndex.js.map +0 -1
- package/dist/features/paths/pathSnap/snapGeometry.js.map +0 -1
- package/dist/features/paths/penPreviewLayer.js.map +0 -1
- package/dist/features/paths/shapeGenerator.js.map +0 -1
- package/dist/features/paths/strokeOutline.js.map +0 -1
- package/dist/features/paths/useAnchorFields.js.map +0 -1
- package/dist/features/paths/usePathEdit.js.map +0 -1
- package/dist/features/poseRun/usePoseRun.js.map +0 -1
- package/dist/features/selection/enteredGroupFrame.js.map +0 -1
- package/dist/features/selection/enteredGroupScope.js.map +0 -1
- package/dist/features/selection/handleCommands.js.map +0 -1
- package/dist/features/selection/overlay.js.map +0 -1
- package/dist/features/selection/selectionPose.js.map +0 -1
- package/dist/features/snap/angle2d.js.map +0 -1
- package/dist/features/snap/debugCandidates.js.map +0 -1
- package/dist/features/snap/gather.js.map +0 -1
- package/dist/features/snap/inputSubspace.js.map +0 -1
- package/dist/features/snap/lazySession.js.map +0 -1
- package/dist/features/snap/marks.js.map +0 -1
- package/dist/features/snap/outcomeStore.js.map +0 -1
- package/dist/features/snap/pointSnap.js.map +0 -1
- package/dist/features/snap/probes2d.js.map +0 -1
- package/dist/features/snap/readoutDraw.js.map +0 -1
- package/dist/features/snap/readoutLayer.js.map +0 -1
- package/dist/features/snap/registry.js.map +0 -1
- package/dist/features/snap/resizeBox.js.map +0 -1
- package/dist/features/snap/roles.js.map +0 -1
- package/dist/features/snap/rotationSource.js.map +0 -1
- package/dist/features/snap/sceneReads.js.map +0 -1
- package/dist/features/snap/sceneSession2d.js.map +0 -1
- package/dist/features/snap/screen.js.map +0 -1
- package/dist/features/snap/session2d.js.map +0 -1
- package/dist/features/snap/spanSnap.js.map +0 -1
- package/dist/features/snap/stepAngles.js.map +0 -1
- package/dist/features/text/editingDrawOne.js.map +0 -1
- package/dist/features/text/sceneTextDrafts.js.map +0 -1
- package/dist/features/text/textHitAt.js.map +0 -1
- package/dist/features/text/textLayer.js.map +0 -1
- package/dist/features/text/useSceneTextEdit.js.map +0 -1
- package/dist/features/viewports/aim.js.map +0 -1
- package/dist/features/viewports/viewMarker.js.map +0 -1
- package/dist/features/viewports/viewMarkerDrag.js.map +0 -1
- package/dist/features/viewports/viewResolver.js.map +0 -1
- package/dist/features/viewports/viewportLayer.js.map +0 -1
- /package/dist/{features → domains}/chrome-caps/index.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathEditExtension.js","names":[],"sources":["../../../src/domains/paths/pathEditExtension.ts"],"sourcesContent":["/**\n * What a feature adds to path edit without path edit knowing it: marks drawn\n * on the open path, parts of it to press, marks a click selects and Delete\n * removes, a share in shaping an anchor or handle drag, what an edit must keep\n * true when it commits, and how a path drawn from another form is edited. A\n * preset carries its extensions as `FeaturePreset.pathEdit`; core consults\n * them in preset order.\n */\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { AnchorSet, PointOrigin, PolygonPath, View } from '@weasel-js/geom';\nimport type { OngoingOverlay, WorldPoint } from '@weasel-js/routing';\nimport type { HandleLocks } from 'interactions/actions/edit-anchors/params';\nimport type { SnapDep } from 'interactions/actions/snapDep';\nimport type { PathRepresentation } from './pathRepresentation';\n\n/** A mark an extension drew that is selected in place of anchors: Delete\n * acts on it, and selecting anchors clears it. */\nexport interface PathEditMark {\n /** The extension that drew it. */\n owner: string;\n /** Which of its marks, in the extension's own numbering. */\n index: number;\n}\n\n/** The open path as an extension's marks and parts read it. */\nexport interface PathEditTarget {\n /** The node being edited. */\n id: string;\n /** Its committed pose and data. */\n node: { pose: unknown; data: unknown };\n /** The path as drawn now, in-flight edits included, in world space. */\n path: PolygonPath;\n /** `path`'s anchors. */\n anchors: AnchorSet;\n}\n\n/** What {@link PathEditExtension.marks} draws with. */\nexport interface PathEditMarksCtx extends PathEditTarget {\n view: View;\n /** World space to the screen space the marks are drawn in. */\n toScreen(p: { x: number; y: number }): { x: number; y: number };\n /** Chrome-caps' answer for a chrome id; marks gated by one ask it. */\n isVisible(chromeId: string): boolean;\n /** The selected mark's index when this extension owns it, else null. */\n selected: number | null;\n}\n\n/** A grabbable point an extension adds to the open path. A press on it\n * routes as an affordance hit of kind `hitKind`. */\nexport interface PathEditPart {\n x: number;\n y: number;\n hitKind: string;\n /** Chrome-caps id it is hit-tested under, beside `path-edit.anchors`. */\n chromeId?: string;\n /** Hit radius in screen px; path edit's anchor radius when left out. */\n hitRadiusPx?: number;\n /** Pushes the part `px` screen pixels from (x, y) along the world-space\n * `normal`, so it keeps its distance at any zoom. */\n standoff?: { px: number; normal: { x: number; y: number } };\n}\n\n/** An anchor or handle drag on the open path, as it starts. */\nexport interface PathEditDragStart extends Omit<PathEditTarget, 'node'> {\n /** The node's committed pose and data; null when it cannot be read. */\n node: PathEditTarget['node'] | null;\n /** What the press grabbed: an anchor, or one of its handles. */\n part: 'anchor' | 'controlIn' | 'controlOut';\n /** The pressed anchor, as a flat index. */\n anchorIndex: number;\n /** The anchors an anchor drag moves, as flat indices; empty for a handle\n * drag, or when Shift took the pressed anchor out of the selection. */\n dragging: readonly number[];\n /** The view's world-to-screen scale at the press, to measure in screen\n * pixels by; absent without a view. */\n scale?: { x: number; y: number };\n /** The canvas's `snap` dep, to begin a session on; absent without one. */\n snap?: SnapDep;\n}\n\n/** A handle drag's move, as {@link PathEditDrag.handle} reads it. */\nexport interface PathEditHandleMove {\n /** The handle locks in force this move, modifiers included. */\n locks: HandleLocks;\n}\n\n/** One extension's share of a drag in flight. Every member is optional. */\nexport interface PathEditDrag {\n /**\n * For an anchor drag, anchors that follow it and the share of its delta\n * each moves by, keyed by flat index. Read every move. When any extension\n * gives one, the drag moves its dragged anchors in full and every anchor\n * named by the largest share given, and moves no symmetric images.\n */\n weights?(): ReadonlyMap<number, number>;\n /** For a handle drag, where the handle goes, given `to`: where the pointer\n * and the locks in force put it. Extensions answer in turn, each from the\n * last one's answer. */\n handle?(to: WorldPoint, move: PathEditHandleMove): WorldPoint;\n /** A wheel turned while the drag is in flight. Return true to claim it;\n * the drag then redoes its last move. */\n wheel?(wheel: { deltaX: number; deltaY: number; deltaZ: number }): boolean;\n /** Overlays to draw while the drag is in flight, for `current`, the path\n * as the drag has made it. */\n overlays?(current: AnchorSet): readonly OngoingOverlay[];\n /** The drag is over, committed or canceled. */\n end?(): void;\n}\n\n/** Another node an edit changes along with the edited one. */\nexport interface PathEditOther {\n id: string;\n /** Its new path, in world space, stored where the node keeps its own. */\n path?: PolygonPath;\n /** Data fields to set on it. */\n data?: Record<string, unknown>;\n /** Delete it: what it kept true no longer has anything to hold. */\n remove?: boolean;\n}\n\n/** What an edit commits once an extension has finished it. */\nexport interface FinishedPathEdit {\n path: PolygonPath;\n /** Data fields to set beside the path. */\n data?: Record<string, unknown>;\n /** Other nodes the edit changes, committed in the same history entry and previewed with it. */\n others?: readonly PathEditOther[];\n}\n\n/** The scene as {@link PathEditExtension.finishEdit} reads it, for an edit that reaches past one node. */\nexport interface PathEditFinishCtx {\n /** The edited node's id. */\n id: string;\n /** A node's committed pose, data and dependencies; null when there is none. */\n node(id: string): { pose: unknown; data: unknown; dependsOn?: readonly string[] | 'children' } | null;\n /** A node's committed editable path, in world space; null when it has none. */\n path(id: string): PolygonPath | null;\n /** The nodes that list `id` in their `dependsOn`, directly or through others. */\n dependents(id: string): readonly string[];\n}\n\n/**\n * The scene as {@link PathEditExtension.finishTransform} reads it: `node` and\n * `path` answer each node as the transform leaves it, `before` as it was.\n */\nexport interface PathTransformFinishCtx extends Omit<PathEditFinishCtx, 'id'> {\n /** The nodes the transform moves, resizes or turns. */\n ids: readonly string[];\n /** A node's committed editable path before the transform, in world space; null when it has none. */\n before(id: string): PolygonPath | null;\n}\n\n/** One feature's share of path edit. Every member is optional. */\nexport interface PathEditExtension {\n /** Names it as a {@link PathEditMark}'s owner. Unique per canvas. */\n readonly id: string;\n /** Draw commands for the open path, in screen space, under its anchor\n * markers. Called every frame the overlay paints. */\n marks?(ctx: PathEditMarksCtx): DrawCommand[];\n /** Grabbable points on the open path, pressed ahead of its segments and\n * behind its anchors. */\n parts?(target: PathEditTarget): readonly PathEditPart[];\n /** The index of this extension's mark a click at `screen` (in the\n * overlay's screen space) lands on, or -1. Asked when a click misses\n * every anchor. */\n markAt?(target: PathEditTarget, toScreen: PathEditMarksCtx['toScreen'], screen: { x: number; y: number }): number;\n /** Remove mark `index` of the node `target` names, committing through\n * `store`. Delete in path edit calls it on the selected mark. */\n deleteMark?(target: PathEditTarget, index: number, store: PathEditStore): void;\n /** Its share of an anchor or handle drag starting on the open path, or\n * null to sit it out. Asked once, at the press. */\n drag?(start: PathEditDragStart): PathEditDrag | null;\n /**\n * `worldPath`, an edit of `node`'s committed path, finished so whatever\n * this extension keeps true holds — the path, and any data fields that\n * change with it, and any other nodes it has to move to keep it. Null when\n * it cannot hold, and the edit should then not happen. Runs on every commit\n * of an edit and every frame of one in flight. `ctx` reads the rest of the\n * scene; it is absent where an edit has no scene behind it.\n */\n finishEdit?(\n node: { pose: unknown; data: unknown },\n worldPath: PolygonPath,\n origins?: readonly (PointOrigin | null)[],\n ctx?: PathEditFinishCtx,\n ): FinishedPathEdit | null;\n /**\n * The other nodes a whole-node transform — a move, resize, rotate, flip or\n * nudge — changes along with the nodes it transforms, so whatever this\n * extension keeps true across nodes holds; committed in the transform's\n * history entry. Null when the transform should not happen. An entry\n * naming a node the transform itself moves is ignored.\n */\n finishTransform?(ctx: PathTransformFinishCtx): readonly PathEditOther[] | null;\n /** How path edit reads and writes the nodes whose `data.representation`\n * is of its kind. */\n representation?: PathRepresentation;\n}\n\n/** Commits a node's new path and data as one history entry, with no\n * extension finishing it. */\nexport type PathEditStore = (id: string, worldPath: PolygonPath, label: string, data?: Record<string, unknown>) => void;\n\n/**\n * Data fields that describe how a node's geometry was made, or name its\n * anchors, and so mean nothing once the geometry is replaced some other way.\n * A writer replacing a node's path drops them: a held warp, path\n * constraints, a skin, the form a path was drawn from.\n */\nexport const GEOMETRY_BOUND_DATA: readonly string[] = ['skin', 'constraints', 'warp', 'representation'];\n\n/** `data` without the fields in {@link GEOMETRY_BOUND_DATA}, other than `keep`. */\nexport function withoutGeometryBoundData<T>(data: T, keep: readonly string[] = []): T {\n if (data === null || typeof data !== 'object') return data;\n const drop = GEOMETRY_BOUND_DATA.filter((k) => k in (data as object) && !keep.includes(k));\n if (drop.length === 0) return data;\n const out = { ...(data as Record<string, unknown>) };\n for (const k of drop) delete out[k];\n return out as T;\n}\n"],"mappings":";;;;;;;AAiNA,IAAa,sBAAyC;CAAC;CAAQ;CAAe;CAAQ;AAAgB;;AAGtG,SAAgB,yBAA4B,MAAS,OAA0B,CAAC,GAAM;CACpF,IAAI,SAAS,QAAQ,OAAO,SAAS,UAAU,OAAO;CACtD,MAAM,OAAO,oBAAoB,QAAQ,MAAM,KAAM,QAAmB,CAAC,KAAK,SAAS,CAAC,CAAC;CACzF,IAAI,KAAK,WAAW,GAAG,OAAO;CAC9B,MAAM,MAAM,EAAE,GAAI,KAAiC;CACnD,KAAK,MAAM,KAAK,MAAM,OAAO,IAAI;CACjC,OAAO;AACT"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { effectSourceFor } from "../pathEffects/effectSources.js";
|
|
2
2
|
import { createEmitter } from "@weasel-js/registry";
|
|
3
3
|
|
|
4
|
-
//#region src/
|
|
4
|
+
//#region src/domains/paths/pathEditState.ts
|
|
5
5
|
/**
|
|
6
6
|
* Which path is being anchor-edited, which of its anchors are selected, and
|
|
7
7
|
* the anchor marquee in flight: one object every reader shares — the
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathEditState.js","names":[],"sources":["../../../src/domains/paths/pathEditState.ts"],"sourcesContent":["/**\n * Which path is being anchor-edited, which of its anchors are selected, and\n * the anchor marquee in flight: one object every reader shares — the\n * `editAnchors` dep, the anchor overlay, the selection overlay's suppressed\n * set, the chrome-caps rule context, and the select tool's shift-click lock.\n */\nimport { createEmitter } from '@weasel-js/registry';\nimport type { PathEditHost } from 'canvas/SceneCanvas/pathEditHost';\nimport type { HandleLocks } from 'interactions/actions/edit-anchors/params';\nimport type { PathEditExtension, PathEditMark } from './pathEditExtension';\nimport type { NodeId, Scene } from 'core/scene/types';\nimport { effectSourceFor } from 'domains/pathEffects/effectSources';\n\n/** A world-coord rect. */\nexport interface AnchorMarquee {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * What decides whether a stored edit target is still one: its node exists and\n * is selected, and the mode permits anchor editing. Each is read at the\n * moment of the question.\n */\nexport interface PathEditSource {\n /** Whether `id` names a node that exists and is selected. Absent means\n * every id does. */\n isSelectedNode?(id: string): boolean;\n /** A host that owns the id; the state then keeps none of its own. */\n host?(): PathEditHost | undefined;\n /**\n * Whether anchor editing is permitted right now — whether the active mode\n * allows the `edits-anchors` capability.\n *\n * **Omit this when no mode registry is wired.** Absent means \"no modality,\n * so nothing revokes edit mode\"; a predicate built from an empty capability\n * set would revoke edit mode instantly.\n */\n isAllowed?(): boolean;\n /** The extensions it starts with; `setExtensions` replaces them. */\n extensions?: readonly PathEditExtension[];\n}\n\nexport interface PathEditState {\n /**\n * The node whose anchors are editable now, or `''`: the stored id, or the\n * host's, while `PathEditSource` still lets it stand. Masked rather than\n * cleared: a host's mode machine can leave path-edit mode without running\n * `exitPathEdit`, and nothing re-renders when it does, so there is no\n * moment to write the id back.\n */\n getEditingId(): string;\n /** `getEditingId()` as the set the selection overlay skips. */\n getEditingIds(): ReadonlySet<string>;\n setEditingId(id: string | null): void;\n /** Selected anchors (flat indices) of the path being edited. */\n getSelectedAnchors(): ReadonlySet<number>;\n setSelectedAnchors(next: ReadonlySet<number>): void;\n /** The in-flight anchor-marquee rect, in the edited path's world. */\n getMarquee(): AnchorMarquee | null;\n setMarquee(rect: AnchorMarquee | null): void;\n /** The extension mark selected in place of anchors, or null. */\n getSelectedMark(): PathEditMark | null;\n setSelectedMark(mark: PathEditMark | null): void;\n /** The canvas's path-edit extensions, in order. */\n getExtensions(): readonly PathEditExtension[];\n /** Replace the extensions; the overlay repaints when they change. */\n setExtensions(next: readonly PathEditExtension[]): void;\n /** The handle locks the `handleLock.*` toggles set; null until one is\n * pressed. Not tied to the edited path, so they outlast a change of it. */\n getHandleLocks(): HandleLocks | null;\n setHandleLocks(next: HandleLocks): void;\n /**\n * The path-edit tool in use, or null for plain anchor editing. Path edit has\n * a set of named tools, one active at a time, each changing what a plain\n * drag does: `'simplifyBrush'` brushes the path simpler. A tool belongs to\n * the open path; with none open, or a different one, there is none.\n */\n getTool(): string | null;\n /** Switch tool, or pass null to go back to plain anchor editing. Ignored\n * with no path open. */\n setTool(id: string | null): void;\n /** Notified when a write changes what the anchor overlay paints. A host\n * that owns the id announces its own changes. */\n subscribe(listener: () => void): () => void;\n}\n\nconst NO_IDS: ReadonlySet<string> = new Set();\nconst NO_EXTENSIONS: readonly PathEditExtension[] = [];\nconst NO_ANCHORS: ReadonlySet<number> = new Set();\n\n/** Whether `id` names a node `scene` holds and `selection` includes, or the path a selected effect hides and is edited\n * through. */\nexport function isSelectedNode(\n scene: Pick<Scene<unknown, string, unknown>, 'get'>,\n selection: { readonly current: readonly string[] },\n id: string,\n): boolean {\n if (scene.get(id as NodeId) == null) return false;\n return selection.current.includes(id)\n || selection.current.some((sel) => effectSourceFor(scene, sel as NodeId, 'path') === id);\n}\n\nexport function createPathEditState(source: PathEditSource = {}): PathEditState {\n let ownId = '';\n let selectedAnchors = NO_ANCHORS;\n let marquee: AnchorMarquee | null = null;\n let handleLocks: HandleLocks | null = null;\n let ownTool: { id: string; on: string } | null = null;\n let idSet = NO_IDS;\n let selectedMark: PathEditMark | null = null;\n let extensions = source.extensions ?? NO_EXTENSIONS;\n const changed = createEmitter();\n const notify = changed.emit;\n\n const next = (id: string | null): string => id ?? '';\n const getEditingId = (): string => {\n const host = source.host?.();\n const id = host ? host.editingId() ?? '' : ownId;\n if (!id) return '';\n if (source.isAllowed && !source.isAllowed()) return '';\n if (source.isSelectedNode && !source.isSelectedNode(id)) return '';\n return id;\n };\n\n return {\n getEditingId,\n getEditingIds() {\n const id = getEditingId();\n if (!id) return NO_IDS;\n if (!idSet.has(id)) idSet = new Set([id]);\n return idSet;\n },\n setEditingId(id) {\n const host = source.host?.();\n if (next(id) !== getEditingId()) ownTool = null;\n if (host) { host.setEditingId(id); return; }\n if (next(id) === ownId) return;\n ownId = next(id);\n notify();\n },\n getSelectedAnchors: () => {\n const host = source.host?.();\n return host?.selectedAnchors && host.setSelectedAnchors ? host.selectedAnchors() : selectedAnchors;\n },\n setSelectedAnchors(next) {\n const host = source.host?.();\n if (host?.selectedAnchors && host.setSelectedAnchors) { host.setSelectedAnchors(next); return; }\n selectedAnchors = next;\n notify();\n },\n getMarquee: () => marquee,\n setMarquee(rect) {\n marquee = rect;\n notify();\n },\n getSelectedMark: () => selectedMark,\n setSelectedMark(mark) {\n if (mark?.owner === selectedMark?.owner && mark?.index === selectedMark?.index) return;\n selectedMark = mark;\n notify();\n },\n getExtensions: () => extensions,\n setExtensions(next) {\n if (next === extensions) return;\n extensions = next;\n notify();\n },\n getHandleLocks: () => handleLocks,\n setHandleLocks(next) {\n handleLocks = { angle: next.angle, length: next.length };\n notify();\n },\n getTool() {\n const on = getEditingId();\n const host = source.host?.();\n if (host?.tool) return on ? host.tool() : null;\n // Dropped on the read that finds its path gone: a host can close the\n // path without telling this state.\n if (ownTool && ownTool.on !== on) ownTool = null;\n return ownTool?.id ?? null;\n },\n setTool(id) {\n const host = source.host?.();\n if (host?.tool && host.setTool) { host.setTool(id); return; }\n const on = getEditingId();\n const tool = id && on ? { id, on } : null;\n if (tool?.id === ownTool?.id && tool?.on === ownTool?.on) return;\n ownTool = tool;\n notify();\n },\n subscribe: changed.subscribe,\n };\n}\n"],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { chromeStateFrom, isVisibleFrom, previewSourcesFrom } from "../../canvas
|
|
|
3
3
|
import { DEFAULT_OVERLAY_STYLE, anchorMarkerCommands, worldToScreen } from "./anchorMarkers.js";
|
|
4
4
|
import { pathToAnchors, rectMarkerPath } from "@weasel-js/geom";
|
|
5
5
|
|
|
6
|
-
//#region src/
|
|
6
|
+
//#region src/domains/paths/pathEditingOverlayLayer.ts
|
|
7
7
|
var EMPTY_SELECTION = /* @__PURE__ */ new Set();
|
|
8
8
|
var ALWAYS = () => true;
|
|
9
9
|
/** Render layer that draws the outline and the anchors and control handles
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathEditingOverlayLayer.js","names":[],"sources":["../../../src/domains/paths/pathEditingOverlayLayer.ts"],"sourcesContent":["/**\n * Screen-space render layer for path-editing chrome — the open path's\n * outline, anchor markers, tangent lines, and control-point dots for a\n * polygon path that is the current edit target, under them whatever marks\n * its path-edit extensions draw. Marker sizes stay constant regardless of zoom.\n */\n\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { RenderLayer } from 'core/layers/render';\nimport {\n type AnchorSet, type Path, type PolygonPath, type View, pathToAnchors, rectMarkerPath,\n} from '@weasel-js/geom';\nimport type { GesturePreviewSource } from 'canvas/gestureBounds';\nimport { chromeStateFrom, previewSourcesFrom, isVisibleFrom } from 'canvas/drawEnvelope';\nimport { paintsLayer } from 'core/selection/chromeState';\nimport {\n anchorMarkerCommands, DEFAULT_OVERLAY_STYLE, worldToScreen, type PathEditingOverlayStyle,\n} from './anchorMarkers';\nimport type { PathEditExtension, PathEditMark } from './pathEditExtension';\nimport type { AnchorMarkerShape } from './pathRepresentation';\n\nexport type { PathEditingOverlayStyle } from './anchorMarkers';\n\n/** Options for `createPathEditingOverlayLayer`. Everything is read per frame,\n * so the layer follows a changing edit target without being rebuilt. */\nexport interface CreatePathEditingOverlayLayerOptions {\n /** Returns the id of the polygon currently in anchor-edit mode, or null\n * when no node is being edited. */\n getEditingId(): string | null;\n /**\n * Returns the pose for an id, or null if the node has been deleted.\n * Non-polygon poses are tolerated (the layer no-ops on them).\n *\n * `previews` are the drawing view's in-flight preview surfaces, so an\n * anchor dragged in one panel does not move in the others. Resolve against\n * them before falling back to the committed pose.\n *\n * `view` is the drawing view's camera. Answer in its world: a node whose\n * layer is a parallax plane stores its path in the plane's.\n */\n getPose(id: string, previews: readonly GesturePreviewSource[], view: View): Path | null;\n /** The marker of each anchor, by flat index, for a node whose path was\n * drawn from a form (`PathRepresentation.anchorMarkers`); null, or an\n * anchor with none, draws a square. Resolve against `previews` as\n * `getPose` does. */\n getAnchorMarkers?(id: string, previews: readonly GesturePreviewSource[]): readonly (AnchorMarkerShape | null)[] | null;\n /** Flat indices of the selected anchors. Selected anchors render\n * filled; unselected ones hollow. */\n getSelectedAnchors?(): ReadonlySet<number>;\n /** In-flight anchor-marquee rect in world coords, or null. Drawn as a\n * rubber band while `marqueeAnchorsAction` is dragging. */\n getMarquee?(view: View): { x: number; y: number; width: number; height: number } | null;\n /** The edited node's committed pose and data, which extensions' marks read. */\n getNode?(id: string): { pose: unknown; data: unknown; layer?: string } | null;\n /** The path-edit extensions whose marks are drawn, in order. */\n getExtensions?(): readonly PathEditExtension[];\n /** The selected extension mark, or null. */\n getSelectedMark?(): PathEditMark | null;\n /** Optional styling overrides. */\n style?: PathEditingOverlayStyle;\n}\n\nconst EMPTY_SELECTION: ReadonlySet<number> = new Set();\nconst ALWAYS = (): boolean => true;\n\n/** Render layer that draws the outline and the anchors and control handles\n * of the path currently being anchor-edited. Draws nothing when no path is\n * being edited. */\nexport function createPathEditingOverlayLayer(\n opts: CreatePathEditingOverlayLayerOptions,\n): RenderLayer<unknown> {\n const style = { ...DEFAULT_OVERLAY_STYLE, ...(opts.style ?? {}) };\n\n return {\n id: 'path-editing-overlay',\n label: 'Path editing',\n space: 'screen',\n draw: (data, view) => {\n const id = opts.getEditingId();\n if (!id) return [];\n // Chrome-caps gate off the envelope, so paint and the anchor hit-test in\n // `affordanceAt` consult the same rule — otherwise a consumer that hides\n // the chrome still gets grabbable invisible anchors, or vice versa.\n const isVisible = isVisibleFrom(data);\n if (isVisible && !isVisible('path-edit.anchors')) return [];\n const node = opts.getNode?.(id);\n if (!paintsLayer(chromeStateFrom(data), node?.layer)) return [];\n const previews = previewSourcesFrom(data);\n const pose = opts.getPose(id, previews, view);\n if (!pose || pose.kind !== 'polygon') return [];\n const markers = opts.getAnchorMarkers?.(id, previews) ?? null;\n\n const selected = opts.getSelectedAnchors?.() ?? EMPTY_SELECTION;\n // Extensions' marks under the markers, which are what a press reaches first.\n const out: DrawCommand[] = [];\n if (node) {\n const mark = opts.getSelectedMark?.() ?? null;\n const anchors = pathToAnchors(pose as PolygonPath) as AnchorSet;\n const toScreen = (p: { x: number; y: number }) => worldToScreen(p, view);\n for (const ext of opts.getExtensions?.() ?? []) {\n if (!ext.marks) continue;\n out.push(...ext.marks({\n id, node, path: pose as PolygonPath, anchors, view, toScreen,\n isVisible: isVisible ?? ALWAYS,\n selected: mark?.owner === ext.id ? mark.index : null,\n }));\n }\n }\n out.push(...anchorMarkerCommands(pose as PolygonPath, view, selected, style, markers));\n\n // Marquee rubber band, on top of everything.\n const marquee = opts.getMarquee?.(view);\n if (marquee) {\n const m = worldToScreen(marquee, view);\n const m2 = worldToScreen({ x: marquee.x + marquee.width, y: marquee.y + marquee.height }, view);\n out.push({\n kind: 'path',\n path: rectMarkerPath(m.x, m.y, m2.x - m.x, m2.y - m.y),\n fill: { paint: { kind: 'solid', color: style.marqueeFill } },\n stroke: { paint: { kind: 'solid', color: style.marqueeStroke }, width: 1 },\n });\n }\n\n return out;\n },\n };\n}\n"],"mappings":";;;;;;AA8DA,IAAM,kCAAuC,IAAI,IAAI;AACrD,IAAM,eAAwB;;;;AAK9B,SAAgB,8BACd,MACsB;CACtB,MAAM,QAAQ;EAAE,GAAG;EAAuB,GAAI,KAAK,SAAS,CAAC;CAAG;CAEhE,OAAO;EACL,IAAI;EACJ,OAAO;EACP,OAAO;EACP,OAAO,MAAM,SAAS;GACpB,MAAM,KAAK,KAAK,aAAa;GAC7B,IAAI,CAAC,IAAI,OAAO,CAAC;GAIjB,MAAM,YAAY,cAAc,IAAI;GACpC,IAAI,aAAa,CAAC,UAAU,mBAAmB,GAAG,OAAO,CAAC;GAC1D,MAAM,OAAO,KAAK,UAAU,EAAE;GAC9B,IAAI,CAAC,YAAY,gBAAgB,IAAI,GAAG,MAAM,KAAK,GAAG,OAAO,CAAC;GAC9D,MAAM,WAAW,mBAAmB,IAAI;GACxC,MAAM,OAAO,KAAK,QAAQ,IAAI,UAAU,IAAI;GAC5C,IAAI,CAAC,QAAQ,KAAK,SAAS,WAAW,OAAO,CAAC;GAC9C,MAAM,UAAU,KAAK,mBAAmB,IAAI,QAAQ,KAAK;GAEzD,MAAM,WAAW,KAAK,qBAAqB,KAAK;GAEhD,MAAM,MAAqB,CAAC;GAC5B,IAAI,MAAM;IACR,MAAM,OAAO,KAAK,kBAAkB,KAAK;IACzC,MAAM,UAAU,cAAc,IAAmB;IACjD,MAAM,YAAY,MAAgC,cAAc,GAAG,IAAI;IACvE,KAAK,MAAM,OAAO,KAAK,gBAAgB,KAAK,CAAC,GAAG;KAC9C,IAAI,CAAC,IAAI,OAAO;KAChB,IAAI,KAAK,GAAG,IAAI,MAAM;MACpB;MAAI;MAAM,MAAM;MAAqB;MAAS;MAAM;MACpD,WAAW,aAAa;MACxB,UAAU,MAAM,UAAU,IAAI,KAAK,KAAK,QAAQ;KAClD,CAAC,CAAC;IACJ;GACF;GACA,IAAI,KAAK,GAAG,qBAAqB,MAAqB,MAAM,UAAU,OAAO,OAAO,CAAC;GAGrF,MAAM,UAAU,KAAK,aAAa,IAAI;GACtC,IAAI,SAAS;IACX,MAAM,IAAI,cAAc,SAAS,IAAI;IACrC,MAAM,KAAK,cAAc;KAAE,GAAG,QAAQ,IAAI,QAAQ;KAAO,GAAG,QAAQ,IAAI,QAAQ;IAAO,GAAG,IAAI;IAC9F,IAAI,KAAK;KACP,MAAM;KACN,MAAM,eAAe,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,EAAE,GAAG,GAAG,IAAI,EAAE,CAAC;KACrD,MAAM,EAAE,OAAO;MAAE,MAAM;MAAS,OAAO,MAAM;KAAY,EAAE;KAC3D,QAAQ;MAAE,OAAO;OAAE,MAAM;OAAS,OAAO,MAAM;MAAc;MAAG,OAAO;KAAE;IAC3E,CAAC;GACH;GAEA,OAAO;EACT;CACF;AACF"}
|
|
@@ -3,7 +3,7 @@ import { countPathAnchors } from "@weasel-js/geom";
|
|
|
3
3
|
import { isDev } from "@weasel-js/react";
|
|
4
4
|
import "@weasel-js/paint";
|
|
5
5
|
|
|
6
|
-
//#region src/
|
|
6
|
+
//#region src/domains/paths/pathLayer.ts
|
|
7
7
|
var PLACEHOLDER_FILL = { paint: { color: "#ffffff" } };
|
|
8
8
|
var PLACEHOLDER_STROKE = {
|
|
9
9
|
paint: { color: "#ffffff" },
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathLayer.js","names":[],"sources":["../../../src/domains/paths/pathLayer.ts"],"sourcesContent":["/**\n * `RenderLayer` that fills/strokes `Path` instances. Mirrors the shape of\n * `createTextLayer`: caller hands over a `getNodes()` enumerator and a\n * `getPath(node)` lookup; the layer iterates and renders. Fill and\n * stroke are looked up per node so consumers can keep the path geometry\n * separate from the visual style record (the typical scene-graph layout).\n */\n\nimport { type DrawCommand } from '@weasel-js/renderer';\nimport { type CommandStroke, type CommandFill, type VertexWidth } from '@weasel-js/paint';\nimport type { RenderLayer } from 'core/layers/render';\nimport { type Path, countPathAnchors } from '@weasel-js/geom';\nimport type { ColorOverrideRegistry } from '@weasel-js/animator';\nimport { isDev } from '@weasel-js/react';\n\nconst PLACEHOLDER_FILL: CommandFill = { paint: { color: '#ffffff' } };\nconst PLACEHOLDER_STROKE: CommandStroke = { paint: { color: '#ffffff' }, width: 1 };\n\n\n/** Options for `createPathLayer`. */\nexport interface CreatePathLayerOpts<T> {\n id?: string;\n label?: string;\n getNodes: () => readonly T[];\n getPath: (node: T) => Path;\n /** Per-node fill. Return `null`/`undefined` to skip filling. */\n getFill?: (node: T) => CommandFill | null | undefined;\n /** Per-node stroke. Return `null`/`undefined` to skip stroking. */\n getStroke?: (node: T) => CommandStroke | null | undefined;\n /** Optional per-node hide hook (e.g., suppress while editing). */\n isHidden?: (node: T) => boolean;\n /**\n * Per-node fill vertex-color array, flat RGBA-per-path-anchor, in\n * 0..1 floats (same color space as `PathDrawCommand.vertexColors`).\n * Length must be `4 × countPathAnchors(getPath(node))`. When set and\n * `getFill` returns null/undefined, a white placeholder fill is\n * synthesized so the renderer's per-vertex shader path activates.\n */\n getVertexColors?: (node: T) => number[] | null | undefined;\n /**\n * Per-node stroke vertex-color array, flat RGBA-per-path-anchor, in\n * 0..1 floats (same color space as `Stroke.vertexColors`).\n * Length must be `4 × countPathAnchors(getPath(node))`. When set and\n * `getStroke` returns null/undefined, a white 1px placeholder stroke\n * is synthesized.\n */\n getStrokeVertexColors?: (node: T) => number[] | null | undefined;\n /**\n * Per-node stroke vertex-width array (one `VertexWidth` per anchor: a\n * width, or a `{ left, right }` pair).\n * Length must be `countPathAnchors(getPath(node))`. The tessellator\n * interpolates half-widths along each segment for tapered strokes.\n * When set and `getStroke` returns null/undefined, a 1px placeholder\n * stroke is synthesized so the per-anchor widths take effect.\n */\n getStrokeVertexWidths?: (node: T) => VertexWidth[] | null | undefined;\n /**\n * Optional color override registry, typically `animator.colorOverrides`.\n * When set, the renderer consults it before falling back to\n * `getVertexColors` / `getStrokeVertexColors`. Function-form overrides\n * receive the base color array and the current animation timestamp.\n */\n colorOverrides?: ColorOverrideRegistry;\n /** Clock used to timestamp function-form color overrides. Defaults to\n * `performance.now`. Override in tests. */\n now?: () => number;\n}\n\n/** Build a `RenderLayer` that fills/strokes `Path` instances enumerated from a node list. */\nexport function createPathLayer<T>(opts: CreatePathLayerOpts<T>): RenderLayer<unknown> {\n const {\n id = 'paths', label = 'Paths',\n getNodes, getPath, getFill, getStroke, isHidden,\n getVertexColors, getStrokeVertexColors, getStrokeVertexWidths,\n colorOverrides,\n now = (): number => (typeof performance !== 'undefined' ? performance.now() : Date.now()),\n } = opts;\n const warned = new Set<string>();\n const dev = isDev();\n\n return {\n id,\n label,\n draw: () => {\n const children: DrawCommand[] = [];\n const nodes = getNodes();\n for (let idx = 0; idx < nodes.length; idx++) {\n const node = nodes[idx];\n if (isHidden?.(node)) continue;\n const path = getPath(node);\n const fillFromHook = getFill?.(node);\n const strokeFromHook = getStroke?.(node);\n const baseVColors = getVertexColors?.(node);\n const baseStrokeVColors = getStrokeVertexColors?.(node);\n const strokeVWidths = getStrokeVertexWidths?.(node);\n\n const nodeId = (node as { id?: string }).id ?? String(idx);\n const tMs = colorOverrides ? now() : 0;\n\n const vColors = colorOverrides\n ? colorOverrides.resolve(nodeId, 'fill', baseVColors ?? undefined, tMs)\n : baseVColors;\n const strokeVColors = colorOverrides\n ? colorOverrides.resolve(nodeId, 'stroke', baseStrokeVColors ?? undefined, tMs)\n : baseStrokeVColors;\n\n const nodeKey = nodeId;\n const anchorCount = countPathAnchors(path);\n const expectedLen = 4 * anchorCount;\n\n let useVColors: readonly number[] | null = null;\n if (vColors != null) {\n if (vColors.length === expectedLen) {\n useVColors = vColors;\n } else if (dev) {\n const key = `${id}:${nodeKey}:fill`;\n if (!warned.has(key)) {\n warned.add(key);\n console.warn(\n `[createPathLayer ${id}] node ${nodeKey}: fill vertexColors length ${vColors.length}, expected ${expectedLen}; dropping`,\n );\n }\n }\n }\n\n let useStrokeVColors: readonly number[] | null = null;\n if (strokeVColors != null) {\n if (strokeVColors.length === expectedLen) {\n useStrokeVColors = strokeVColors;\n } else if (dev) {\n const key = `${id}:${nodeKey}:stroke`;\n if (!warned.has(key)) {\n warned.add(key);\n console.warn(\n `[createPathLayer ${id}] node ${nodeKey}: stroke vertexColors length ${strokeVColors.length}, expected ${expectedLen}; dropping`,\n );\n }\n }\n }\n\n // Synthesize placeholder only when colors validate (useVColors != null),\n // not on any raw hook return. Mismatched-length arrays drop both the\n // colors and placeholder so the dev signal (console warning) isn't duplicated.\n const fill: CommandFill | undefined =\n fillFromHook != null ? fillFromHook\n : (useVColors != null ? PLACEHOLDER_FILL : undefined);\n\n let useStrokeVWidths: VertexWidth[] | null = null;\n if (strokeVWidths != null) {\n if (strokeVWidths.length === anchorCount) {\n useStrokeVWidths = strokeVWidths;\n } else if (dev) {\n const key = `${id}:${nodeKey}:strokeWidths`;\n if (!warned.has(key)) {\n warned.add(key);\n console.warn(\n `[createPathLayer ${id}] node ${nodeKey}: stroke vertexWidths length ${strokeVWidths.length}, expected ${anchorCount}; dropping`,\n );\n }\n }\n }\n\n const baseStroke: CommandStroke | undefined =\n strokeFromHook != null ? strokeFromHook\n : (useStrokeVColors != null || useStrokeVWidths != null ? PLACEHOLDER_STROKE : undefined);\n\n const stroke: CommandStroke | undefined =\n baseStroke != null\n ? {\n ...baseStroke,\n ...(useStrokeVColors != null ? { vertexColors: useStrokeVColors as number[] } : {}),\n ...(useStrokeVWidths != null ? { vertexWidths: useStrokeVWidths } : {}),\n }\n : baseStroke;\n\n if (fill == null && stroke == null) continue;\n\n children.push({\n kind: 'path',\n path,\n ...(fill != null ? { fill } : {}),\n ...(stroke != null ? { stroke } : {}),\n ...(useVColors != null ? { vertexColors: useVColors as number[] } : {}),\n });\n }\n // World-space commands; drawLayers wraps in viewToMat3 automatically.\n return children;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathRepresentation.js","names":[],"sources":["../../../src/domains/paths/pathRepresentation.ts"],"sourcesContent":["/**\n * A path drawn from another form the node keeps beside it, such as a curve\n * solved through the points it passes. The node's path stays the geometry\n * every reader draws and hit-tests; `data.representation` holds the form,\n * named by its `kind`. A preset that knows a kind brings a\n * {@link PathRepresentation} for it (`PathEditExtension.representation`),\n * through which path edit shows and edits the form. Where none is on, path\n * edit edits the drawn path, and the edit drops the form.\n */\nimport type { ActionDeps, OngoingHandle, WorldPoint } from '@weasel-js/routing';\nimport type { PolygonPath, Point } from '@weasel-js/geom';\nimport type { PathEditExtension } from './pathEditExtension';\n\n/** What `data.representation` holds: a form named by `kind`, with whatever\n * that kind keeps. */\nexport interface PathForm {\n readonly kind: string;\n readonly [field: string]: unknown;\n}\n\n/** The outline of an anchor's marker, in screen space: `size` px across at\n * `at`, with the anchor's neighbors along its subpath, null past an open\n * end. */\nexport type AnchorMarkerShape = (at: Point, size: number, prev: Point | null, next: Point | null) => PolygonPath;\n\n/** How path edit reads and writes one kind of {@link PathForm}. */\nexport interface PathRepresentation {\n /** The `kind` of the forms it reads. */\n readonly kind: string;\n /** What path edit shows and moves for a node holding `form`, whose drawn\n * path in world space is `path`. Null when `form` does not describe\n * `path`: path edit then edits `path`, and the edit drops the form. */\n editPath(path: PolygonPath, form: PathForm): PolygonPath | null;\n /**\n * The drawn path an edit makes, and the form to keep beside it: `edited` is\n * an edit of what {@link editPath} gave, `path` the drawn path it was given\n * from. Null when `form` does not describe `path`; the edit is then stored\n * as drawn, without the form. Runs on every commit of an edit and every\n * frame of one in flight.\n */\n store(path: PolygonPath, form: PathForm, edited: PolygonPath): { path: PolygonPath; form: PathForm } | null;\n /** The marker of each anchor of the edit path, by flat index. An anchor\n * with none draws path edit's square. */\n anchorMarkers?(form: PathForm): readonly (AnchorMarkerShape | null)[];\n /**\n * A press at world point `at` on the drawn outline of the node in path\n * edit: the drag it starts, or null when it starts none there. A\n * representation that has this takes every segment press, in place of\n * Bend segment.\n */\n segmentPress?(deps: ActionDeps, at: WorldPoint): (() => OngoingHandle) | null;\n}\n\n/** The form `data` holds, or null. */\nexport function pathFormOf(data: unknown): PathForm | null {\n const form = (data as { representation?: unknown } | null | undefined)?.representation;\n return form && typeof form === 'object' && typeof (form as { kind?: unknown }).kind === 'string'\n ? (form as PathForm)\n : null;\n}\n\n/** `data` without its form: its path as plain geometry. */\nexport function withoutPathForm<T>(data: T): T {\n if (data === null || typeof data !== 'object' || !('representation' in data)) return data;\n const { representation: _dropped, ...rest } = data as Record<string, unknown>;\n void _dropped;\n return rest as T;\n}\n\n/** The form `data` holds, and the representation among `extensions` that\n * reads it; null when either is missing. */\nexport function representationOf(\n extensions: readonly PathEditExtension[] | undefined,\n data: unknown,\n): { representation: PathRepresentation; form: PathForm } | null {\n const form = pathFormOf(data);\n if (!form || !extensions) return null;\n const representation = extensions.find((e) => e.representation?.kind === form.kind)?.representation;\n return representation ? { representation, form } : null;\n}\n"],"mappings":";;AAsDA,SAAgB,WAAW,MAAgC;CACzD,MAAM,OAAQ,MAA0D;CACxE,OAAO,QAAQ,OAAO,SAAS,YAAY,OAAQ,KAA4B,SAAS,WACnF,OACD;AACN;;AAGA,SAAgB,gBAAmB,MAAY;CAC7C,IAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,EAAE,oBAAoB,OAAO,OAAO;CACrF,MAAM,EAAE,gBAAgB,UAAU,GAAG,SAAS;CAE9C,OAAO;AACT;;;AAIA,SAAgB,iBACd,YACA,MAC+D;CAC/D,MAAM,OAAO,WAAW,IAAI;CAC5B,IAAI,CAAC,QAAQ,CAAC,YAAY,OAAO;CACjC,MAAM,iBAAiB,WAAW,MAAM,MAAM,EAAE,gBAAgB,SAAS,KAAK,IAAI,CAAC,EAAE;CACrF,OAAO,iBAAiB;EAAE;EAAgB;CAAK,IAAI;AACrD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glyphs.js","names":[],"sources":["../../../../src/domains/paths/pathSnap/glyphs.ts"],"sourcesContent":["import type { PathSnapKind } from '@weasel-js/geom';\n\n/** What the snap readout calls each kind of path target. */\nexport const PATH_SNAP_LABELS: Readonly<Record<PathSnapKind, string>> = {\n anchor: 'anchor',\n center: 'center',\n intersection: 'intersection',\n tangent: 'tangent',\n perpendicular: 'perpendicular',\n midpoint: 'midpoint',\n extremum: 'extremum',\n quadrant: 'quadrant',\n nearest: 'on path',\n};\n\n/** The glyph for `kind` centered on (x, y), `r` from center to edge, as SVG path data. */\nexport function pathSnapGlyph(kind: PathSnapKind, x: number, y: number, r: number): string {\n const L = x - r, R = x + r, T = y - r, B = y + r;\n const ring = (cx: number, cy: number, rc: number) => {\n const k = rc * 0.5523;\n return `M${cx} ${cy - rc} C${cx + k} ${cy - rc} ${cx + rc} ${cy - k} ${cx + rc} ${cy}`\n + ` C${cx + rc} ${cy + k} ${cx + k} ${cy + rc} ${cx} ${cy + rc}`\n + ` C${cx - k} ${cy + rc} ${cx - rc} ${cy + k} ${cx - rc} ${cy}`\n + ` C${cx - rc} ${cy - k} ${cx - k} ${cy - rc} ${cx} ${cy - rc} Z`;\n };\n switch (kind) {\n case 'anchor': return `M${L} ${T} H${R} V${B} H${L} Z`;\n case 'center': return `${ring(x, y, r)} M${L} ${y} H${R} M${x} ${T} V${B}`;\n case 'intersection': return `M${L} ${T} L${R} ${B} M${R} ${T} L${L} ${B}`;\n case 'midpoint': return `M${x} ${T} L${R} ${B} L${L} ${B} Z`;\n case 'extremum': return `M${x} ${T} L${R} ${y} L${x} ${B} L${L} ${y} Z`;\n case 'quadrant': return ring(x, y, r);\n case 'perpendicular': return `M${L} ${T} V${B} H${R} M${L} ${y} H${x} V${B}`;\n case 'nearest': return `M${L} ${T} H${R} L${L} ${B} H${R} Z`;\n case 'tangent': {\n const rc = (3 * r) / 4;\n return `${ring(x, y + r / 4, rc)} M${L} ${y + r / 4 - rc} H${R}`;\n }\n }\n}\n"],"mappings":";;AAGA,IAAa,mBAA2D;CACtE,QAAQ;CACR,QAAQ;CACR,cAAc;CACd,SAAS;CACT,eAAe;CACf,UAAU;CACV,UAAU;CACV,UAAU;CACV,SAAS;AACX;;AAGA,SAAgB,cAAc,MAAoB,GAAW,GAAW,GAAmB;CACzF,MAAM,IAAI,IAAI,GAAG,IAAI,IAAI,GAAG,IAAI,IAAI,GAAG,IAAI,IAAI;CAC/C,MAAM,QAAQ,IAAY,IAAY,OAAe;EACnD,MAAM,IAAI,KAAK;EACf,OAAO,IAAI,GAAG,GAAG,KAAK,GAAG,IAAI,KAAK,EAAE,GAAG,KAAK,GAAG,GAAG,KAAK,GAAG,GAAG,KAAK,EAAE,GAAG,KAAK,GAAG,GAAG,OACzE,KAAK,GAAG,GAAG,KAAK,EAAE,GAAG,KAAK,EAAE,GAAG,KAAK,GAAG,GAAG,GAAG,GAAG,KAAK,OACrD,KAAK,EAAE,GAAG,KAAK,GAAG,GAAG,KAAK,GAAG,GAAG,KAAK,EAAE,GAAG,KAAK,GAAG,GAAG,OACrD,KAAK,GAAG,GAAG,KAAK,EAAE,GAAG,KAAK,EAAE,GAAG,KAAK,GAAG,GAAG,GAAG,GAAG,KAAK,GAAG;CACnE;CACA,QAAQ,MAAR;EACE,KAAK,UAAU,OAAO,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE;EACnD,KAAK,UAAU,OAAO,GAAG,KAAK,GAAG,GAAG,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI;EACvE,KAAK,gBAAgB,OAAO,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG;EACtE,KAAK,YAAY,OAAO,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE;EACzD,KAAK,YAAY,OAAO,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE;EACpE,KAAK,YAAY,OAAO,KAAK,GAAG,GAAG,CAAC;EACpC,KAAK,iBAAiB,OAAO,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI;EACzE,KAAK,WAAW,OAAO,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE;EACzD,KAAK,WAAW;GACd,MAAM,KAAM,IAAI,IAAK;GACrB,OAAO,GAAG,KAAK,GAAG,IAAI,IAAI,GAAG,EAAE,EAAE,IAAI,EAAE,GAAG,IAAI,IAAI,IAAI,GAAG,IAAI;EAC/D;CACF;AACF"}
|
|
@@ -3,7 +3,7 @@ import { PATH_SNAP_LABELS } from "./glyphs.js";
|
|
|
3
3
|
import { sceneSnapIndex } from "./sceneIndex.js";
|
|
4
4
|
import { PATH_SNAP_KINDS, findPathSnap, findPathSnaps } from "@weasel-js/geom";
|
|
5
5
|
|
|
6
|
-
//#region src/
|
|
6
|
+
//#region src/domains/paths/pathSnap/pathSnapSource.ts
|
|
7
7
|
/**
|
|
8
8
|
* The scene's paths as snap targets: a point for every enabled kind of
|
|
9
9
|
* target in range of a pointer or geometry probe. `nearest` is offered only
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathSnapSource.js","names":[],"sources":["../../../../src/domains/paths/pathSnap/pathSnapSource.ts"],"sourcesContent":["import { findPathSnap, findPathSnaps, PATH_SNAP_KINDS, type PathSnapKind } from '@weasel-js/geom';\nimport type { WorldPoint } from '@weasel-js/routing';\nimport type { Scene } from 'core/scene/types';\nimport { DEFAULT_SNAP_PX } from 'domains/snap/session2d';\nimport type { SnapSource } from 'domains/snap/types';\nimport { PATH_SNAP_LABELS } from './glyphs';\nimport { sceneSnapIndex } from './sceneIndex';\n\n/** Options for `pathSnapSource`. */\nexport interface PathSnapSourceOptions {\n /** The kinds offered. Default: every kind. */\n only?: readonly PathSnapKind[];\n /** Kinds to turn on or off. A kind not named is on. */\n kinds?: Partial<Record<PathSnapKind, boolean>>;\n /** Overrides the canvas's reach for path targets, in screen px. */\n reachPx?: number;\n}\n\n/**\n * The scene's paths as snap targets: a point for every enabled kind of\n * target in range of a pointer or geometry probe. `nearest` is offered only\n * when no other kind is in range of that probe. Tangent and perpendicular\n * targets need the query's `origin`.\n */\nexport function pathSnapSource(opts: PathSnapSourceOptions = {}): SnapSource {\n const kinds = (opts.only ?? PATH_SNAP_KINDS).filter((k) => opts.kinds?.[k] !== false);\n return {\n id: 'path-snap',\n roles: ['pointer', 'geometry'],\n ...(opts.reachPx !== undefined ? { reachPx: opts.reachPx } : {}),\n *collect(q) {\n if (!q.scene) return;\n const { ids, index } = sceneSnapIndex(q.scene, q.also);\n const exclude = new Set(q.exclude);\n const include = q.include ? new Set(q.include) : null;\n const radius = q.reachWorld(opts.reachPx ?? q.reachPx);\n const skip = (i: number) => {\n const id = ids[i];\n if (id === null) return false;\n return exclude.has(id) || (include ? !include.has(id) : false);\n };\n for (const p of q.probes) {\n const hits = findPathSnaps(index, p.at, { radius, kinds, skip, ...(q.origin ? { origin: q.origin } : {}) });\n for (const h of hits) {\n const at = { x: h.x, y: h.y };\n yield {\n probe: p.id,\n locus: { at, dirs: [] },\n mark: { kind: 'point', at, glyph: h.kind, label: PATH_SNAP_LABELS[h.kind] },\n };\n }\n }\n },\n };\n}\n\n/** The point of the nodes `ids`' own geometry nearest `press`, within\n * `reachPx` screen px, or null: where a move's `geometry` probe sits. Leaves\n * no mark. */\nexport function ownGeometryAt(\n scene: Pick<Scene<unknown, string, unknown>, 'nodes' | 'layers' | 'getVersion'>,\n press: WorldPoint,\n ids: readonly string[],\n scale: { x: number; y: number },\n reachPx = DEFAULT_SNAP_PX,\n): WorldPoint | null {\n const { ids: owners, index } = sceneSnapIndex(scene);\n const own = new Set(ids);\n const hit = findPathSnap(index, press, {\n radius: reachPx,\n scale,\n skip: (i) => {\n const id = owners[i];\n return id !== null && !own.has(id);\n },\n });\n return hit ? { x: hit.x, y: hit.y } : null;\n}\n"],"mappings":";;;;;;;;;;;;AAwBA,SAAgB,eAAe,OAA8B,CAAC,GAAe;CAC3E,MAAM,SAAS,KAAK,QAAQ,gBAAA,CAAiB,QAAQ,MAAM,KAAK,QAAQ,OAAO,KAAK;CACpF,OAAO;EACL,IAAI;EACJ,OAAO,CAAC,WAAW,UAAU;EAC7B,GAAI,KAAK,YAAY,SAAY,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;EAC9D,CAAC,QAAQ,GAAG;GACV,IAAI,CAAC,EAAE,OAAO;GACd,MAAM,EAAE,KAAK,UAAU,eAAe,EAAE,OAAO,EAAE,IAAI;GACrD,MAAM,UAAU,IAAI,IAAI,EAAE,OAAO;GACjC,MAAM,UAAU,EAAE,UAAU,IAAI,IAAI,EAAE,OAAO,IAAI;GACjD,MAAM,SAAS,EAAE,WAAW,KAAK,WAAW,EAAE,OAAO;GACrD,MAAM,QAAQ,MAAc;IAC1B,MAAM,KAAK,IAAI;IACf,IAAI,OAAO,MAAM,OAAO;IACxB,OAAO,QAAQ,IAAI,EAAE,MAAM,UAAU,CAAC,QAAQ,IAAI,EAAE,IAAI;GAC1D;GACA,KAAK,MAAM,KAAK,EAAE,QAAQ;IACxB,MAAM,OAAO,cAAc,OAAO,EAAE,IAAI;KAAE;KAAQ;KAAO;KAAM,GAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,IAAI,CAAC;IAAG,CAAC;IAC1G,KAAK,MAAM,KAAK,MAAM;KACpB,MAAM,KAAK;MAAE,GAAG,EAAE;MAAG,GAAG,EAAE;KAAE;KAC5B,MAAM;MACJ,OAAO,EAAE;MACT,OAAO;OAAE;OAAI,MAAM,CAAC;MAAE;MACtB,MAAM;OAAE,MAAM;OAAS;OAAI,OAAO,EAAE;OAAM,OAAO,iBAAiB,EAAE;MAAM;KAC5E;IACF;GACF;EACF;CACF;AACF;;;;AAKA,SAAgB,cACd,OACA,OACA,KACA,OACA,UAAU,iBACS;CACnB,MAAM,EAAE,KAAK,QAAQ,UAAU,eAAe,KAAK;CACnD,MAAM,MAAM,IAAI,IAAI,GAAG;CACvB,MAAM,MAAM,aAAa,OAAO,OAAO;EACrC,QAAQ;EACR;EACA,OAAO,MAAM;GACX,MAAM,KAAK,OAAO;GAClB,OAAO,OAAO,QAAQ,CAAC,IAAI,IAAI,EAAE;EACnC;CACF,CAAC;CACD,OAAO,MAAM;EAAE,GAAG,IAAI;EAAG,GAAG,IAAI;CAAE,IAAI;AACxC"}
|
|
@@ -3,7 +3,7 @@ import { nodeGates } from "../../../core/scene/nodeGates.js";
|
|
|
3
3
|
import { nodeSnapGeometry } from "./snapGeometry.js";
|
|
4
4
|
import { createPathSnapIndex } from "@weasel-js/geom";
|
|
5
5
|
|
|
6
|
-
//#region src/
|
|
6
|
+
//#region src/domains/paths/pathSnap/sceneIndex.ts
|
|
7
7
|
/** Indexes every leaf of `scene` with geometry (`nodeSnapGeometry`), but
|
|
8
8
|
* hidden ones (`nodeGates`) and those on an annotation layer, once per scene
|
|
9
9
|
* version, and again when a query brings geometry of its own. The scene
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sceneIndex.js","names":[],"sources":["../../../../src/domains/paths/pathSnap/sceneIndex.ts"],"sourcesContent":["import {\n createPathSnapIndex, type Path, type PathSnapIndex, type PathSnapTarget,\n} from '@weasel-js/geom';\nimport type { Scene } from 'core/scene/types';\nimport { isAnnotationLayer } from 'core/scene/annotation';\nimport { nodeGates } from 'core/scene/nodeGates';\nimport type { SnapGeometry } from 'interactions/actions/snapDep';\nimport { nodeSnapGeometry } from './snapGeometry';\n\ntype SnapScene = Pick<Scene<unknown, string, unknown>, 'nodes' | 'layers' | 'getVersion'>;\n\n/** A scene's paths indexed for snapping; `ids[i]` is the node path `i` came\n * from, or null for a path of the query's own (`also`). */\nexport interface SceneSnapIndex {\n ids: (string | null)[];\n index: PathSnapIndex;\n}\n\n/** Indexes every leaf of `scene` with geometry (`nodeSnapGeometry`), but\n * hidden ones (`nodeGates`) and those on an annotation layer, once per scene\n * version, and again when a query brings geometry of its own. The scene\n * alone and the scene with the last `also` are cached apart, so a query\n * without geometry does not evict a gesture's. */\nexport function createSceneSnapIndexer(scene: SnapScene): (also?: SnapGeometry) => SceneSnapIndex {\n type Slot = { version: number; layers: unknown; also: SnapGeometry | undefined; built: SceneSnapIndex };\n const slots: { bare: Slot | null; also: Slot | null } = { bare: null, also: null };\n return (also) => {\n const version = scene.getVersion();\n const layers = scene.layers;\n const key = also ? 'also' : 'bare';\n const cached = slots[key];\n if (cached?.version === version && cached.layers === layers && cached.also === also) return cached.built;\n const notes = new Set(layers.filter(isAnnotationLayer).map((l) => l.id));\n const gates = nodeGates({ layers, get: (id) => scene.nodes.get(id) });\n const ids: (string | null)[] = [];\n const paths: Path[] = [];\n const extra: PathSnapTarget[] = [];\n for (const node of scene.nodes.values()) {\n if (node.kind !== 'mark' || notes.has(node.layer) || gates.hidden(node.id)) continue;\n const g = nodeSnapGeometry(node);\n if (!g) continue;\n for (const p of g.points) extra.push({ ...p, paths: [paths.length] });\n ids.push(node.id);\n paths.push(g.path);\n }\n for (const path of also?.paths ?? []) { ids.push(null); paths.push(path); }\n if (also?.anchors) {\n ids.push(null);\n for (const p of also.anchors) extra.push({ kind: 'anchor', x: p.x, y: p.y, paths: [paths.length] });\n paths.push({ kind: 'polygon', commands: new Uint8Array(), coords: new Float32Array(), fillRule: 'nonzero' });\n }\n const built = { ids, index: createPathSnapIndex(paths, extra) };\n slots[key] = { version, layers, also, built };\n return built;\n };\n}\n\nconst indexers = new WeakMap<object, ReturnType<typeof createSceneSnapIndexer>>();\n\n/** {@link createSceneSnapIndexer}, one per scene, so every source over a\n * scene shares its index. */\nexport function sceneSnapIndex(scene: SnapScene, also?: SnapGeometry): SceneSnapIndex {\n let indexer = indexers.get(scene);\n if (!indexer) indexers.set(scene, (indexer = createSceneSnapIndexer(scene)));\n return indexer(also);\n}\n"],"mappings":";;;;;;;;;;;AAuBA,SAAgB,uBAAuB,OAA2D;CAEhG,MAAM,QAAkD;EAAE,MAAM;EAAM,MAAM;CAAK;CACjF,QAAQ,SAAS;EACf,MAAM,UAAU,MAAM,WAAW;EACjC,MAAM,SAAS,MAAM;EACrB,MAAM,MAAM,OAAO,SAAS;EAC5B,MAAM,SAAS,MAAM;EACrB,IAAI,QAAQ,YAAY,WAAW,OAAO,WAAW,UAAU,OAAO,SAAS,MAAM,OAAO,OAAO;EACnG,MAAM,QAAQ,IAAI,IAAI,OAAO,OAAO,iBAAiB,CAAC,CAAC,KAAK,MAAM,EAAE,EAAE,CAAC;EACvE,MAAM,QAAQ,UAAU;GAAE;GAAQ,MAAM,OAAO,MAAM,MAAM,IAAI,EAAE;EAAE,CAAC;EACpE,MAAM,MAAyB,CAAC;EAChC,MAAM,QAAgB,CAAC;EACvB,MAAM,QAA0B,CAAC;EACjC,KAAK,MAAM,QAAQ,MAAM,MAAM,OAAO,GAAG;GACvC,IAAI,KAAK,SAAS,UAAU,MAAM,IAAI,KAAK,KAAK,KAAK,MAAM,OAAO,KAAK,EAAE,GAAG;GAC5E,MAAM,IAAI,iBAAiB,IAAI;GAC/B,IAAI,CAAC,GAAG;GACR,KAAK,MAAM,KAAK,EAAE,QAAQ,MAAM,KAAK;IAAE,GAAG;IAAG,OAAO,CAAC,MAAM,MAAM;GAAE,CAAC;GACpE,IAAI,KAAK,KAAK,EAAE;GAChB,MAAM,KAAK,EAAE,IAAI;EACnB;EACA,KAAK,MAAM,QAAQ,MAAM,SAAS,CAAC,GAAG;GAAE,IAAI,KAAK,IAAI;GAAG,MAAM,KAAK,IAAI;EAAG;EAC1E,IAAI,MAAM,SAAS;GACjB,IAAI,KAAK,IAAI;GACb,KAAK,MAAM,KAAK,KAAK,SAAS,MAAM,KAAK;IAAE,MAAM;IAAU,GAAG,EAAE;IAAG,GAAG,EAAE;IAAG,OAAO,CAAC,MAAM,MAAM;GAAE,CAAC;GAClG,MAAM,KAAK;IAAE,MAAM;IAAW,0BAAU,IAAI,WAAW;IAAG,wBAAQ,IAAI,aAAa;IAAG,UAAU;GAAU,CAAC;EAC7G;EACA,MAAM,QAAQ;GAAE;GAAK,OAAO,oBAAoB,OAAO,KAAK;EAAE;EAC9D,MAAM,OAAO;GAAE;GAAS;GAAQ;GAAM;EAAM;EAC5C,OAAO;CACT;AACF;AAEA,IAAM,2BAAW,IAAI,QAA2D;;;AAIhF,SAAgB,eAAe,OAAkB,MAAqC;CACpF,IAAI,UAAU,SAAS,IAAI,KAAK;CAChC,IAAI,CAAC,SAAS,SAAS,IAAI,OAAQ,UAAU,uBAAuB,KAAK,CAAE;CAC3E,OAAO,QAAQ,IAAI;AACrB"}
|
|
@@ -2,8 +2,9 @@ import { isRectPose } from "../../../core/geometry/autoPoseDescriptor.js";
|
|
|
2
2
|
import { findNodeShape } from "../../../canvas/NodeShape.js";
|
|
3
3
|
import { poseOutline } from "../../../core/geometry/regionHit.js";
|
|
4
4
|
import { nodeOutline } from "../strokeOutline.js";
|
|
5
|
+
import { worldCoords } from "@weasel-js/geom";
|
|
5
6
|
|
|
6
|
-
//#region src/
|
|
7
|
+
//#region src/domains/paths/pathSnap/snapGeometry.ts
|
|
7
8
|
/**
|
|
8
9
|
* The geometry `node` snaps to. A painter that `snapsToBox` — text, an image
|
|
9
10
|
* — gives its pose box through the pose's frame, and the box's center. Any
|
|
@@ -34,7 +35,7 @@ function boxCenter(path) {
|
|
|
34
35
|
x: path.x + path.width / 2,
|
|
35
36
|
y: path.y + path.height / 2
|
|
36
37
|
};
|
|
37
|
-
const c = path
|
|
38
|
+
const c = worldCoords(path);
|
|
38
39
|
if (c.length < 8) return null;
|
|
39
40
|
return {
|
|
40
41
|
x: (c[0] + c[4]) / 2,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapGeometry.js","names":[],"sources":["../../../../src/domains/paths/pathSnap/snapGeometry.ts"],"sourcesContent":["import { worldCoords, type Path, type PathSnapTarget } from '@weasel-js/geom';\nimport { isRectPose } from 'core/geometry/autoPoseDescriptor';\nimport { poseOutline } from 'core/geometry/regionHit';\nimport type { Node } from 'core/scene/types';\nimport { findNodeShape } from 'canvas/NodeShape';\nimport { nodeOutline } from '../strokeOutline';\n\n/** What a node offers path snapping: the outline it is drawn with, in world\n * coords, and any point of its own that the outline does not carry. */\nexport interface NodeSnapGeometry {\n path: Path;\n /** Targets beside the outline's, with `paths` left for the index to fill. */\n points: Omit<PathSnapTarget, 'paths'>[];\n}\n\n/**\n * The geometry `node` snaps to. A painter that `snapsToBox` — text, an image\n * — gives its pose box through the pose's frame, and the box's center. Any\n * other node gives its `nodeOutline`. Null for a node with neither.\n */\nexport function nodeSnapGeometry(node: Node<unknown, string, unknown>): NodeSnapGeometry | null {\n const pose = node.pose;\n if (findNodeShape(node)?.snapsToBox && isRectPose(pose)) {\n const path = poseOutline(pose, pose);\n const c = boxCenter(path);\n return { path, points: c ? [{ kind: 'center', ...c }] : [] };\n }\n const path = nodeOutline(node);\n return path ? { path, points: [] } : null;\n}\n\n/** The center of a box outline: a rect, or the four corners a turned one has. */\nfunction boxCenter(path: Path): { x: number; y: number } | null {\n if (path.kind === 'rect') return { x: path.x + path.width / 2, y: path.y + path.height / 2 };\n const c = worldCoords(path);\n if (c.length < 8) return null;\n return { x: (c[0] + c[4]) / 2, y: (c[1] + c[5]) / 2 };\n}\n"],"mappings":";;;;;;;;;;;;AAoBA,SAAgB,iBAAiB,MAA+D;CAC9F,MAAM,OAAO,KAAK;CAClB,IAAI,cAAc,IAAI,CAAC,EAAE,cAAc,WAAW,IAAI,GAAG;EACvD,MAAM,OAAO,YAAY,MAAM,IAAI;EACnC,MAAM,IAAI,UAAU,IAAI;EACxB,OAAO;GAAE;GAAM,QAAQ,IAAI,CAAC;IAAE,MAAM;IAAU,GAAG;GAAE,CAAC,IAAI,CAAC;EAAE;CAC7D;CACA,MAAM,OAAO,YAAY,IAAI;CAC7B,OAAO,OAAO;EAAE;EAAM,QAAQ,CAAC;CAAE,IAAI;AACvC;;AAGA,SAAS,UAAU,MAA6C;CAC9D,IAAI,KAAK,SAAS,QAAQ,OAAO;EAAE,GAAG,KAAK,IAAI,KAAK,QAAQ;EAAG,GAAG,KAAK,IAAI,KAAK,SAAS;CAAE;CAC3F,MAAM,IAAI,YAAY,IAAI;CAC1B,IAAI,EAAE,SAAS,GAAG,OAAO;CACzB,OAAO;EAAE,IAAI,EAAE,KAAK,EAAE,MAAM;EAAG,IAAI,EAAE,KAAK,EAAE,MAAM;CAAE;AACtD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"penPreviewLayer.js","names":[],"sources":["../../../src/domains/paths/penPreviewLayer.ts"],"sourcesContent":["/**\n * Screen-space preview layer for `usePenTool`. Reads the pen's state each\n * frame and renders the in-progress path: finished\n * subpaths (faded), the current subpath (bright + anchor dots + handles),\n * a rubber-band segment from the latest anchor to the cursor, and a\n * close-hint ring on the first anchor when the tool flags it as hot.\n *\n * Runs in screen space so anchor/handle sizes stay constant under zoom.\n * All world-space coords go through `worldToScreen` via the layer's `view`\n * argument.\n */\n\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { RenderLayer } from 'core/layers/render';\nimport type { KitSettingsStore } from '@weasel-js/react';\nimport type { PenScratch, PenAnchor, PenSubpath } from 'tools/builtin/pen';\nimport { PATH_C, PATH_L, PATH_M, type PolygonPath, anchorsToPath, circlePath } from '@weasel-js/geom';\n\n/** Appearance of the pen tool's in-progress path. */\nexport interface PenPreviewStyle {\n anchorFill?: string;\n anchorStroke?: string;\n handleStroke?: string;\n rubberBandStroke?: string;\n closeHintFill?: string;\n finishedSubpathStroke?: string;\n}\n\n/** Options for `createPenPreviewLayer`. */\nexport interface CreatePenPreviewLayerOptions {\n /** The pen's state. One object for the pen's life, changed in place, so\n * the layer reads the same one on every frame. */\n state: PenScratch;\n style?: PenPreviewStyle;\n /** The kit settings whose `targets.pen*RadiusPx` size the dots, read each frame. */\n kitSettings?: () => KitSettingsStore;\n}\n\nconst DEFAULT_STYLE: Required<PenPreviewStyle> = {\n anchorFill: '#d4c4a8',\n anchorStroke: '#d4c4a8',\n handleStroke: '#d4c4a8',\n rubberBandStroke: '#d4c4a8',\n closeHintFill: '#d4c4a8',\n finishedSubpathStroke: '#8a7a5e',\n};\n\nexport const ANCHOR_RADIUS_PX = 4;\nexport const HANDLE_DOT_RADIUS_PX = 3;\nexport const CLOSE_HINT_RADIUS_PX = 8;\n\n/** How near the pen's first anchor, in screen px, a click closes the subpath. */\nexport const PEN_CLOSE_HIT_PX = 8;\n\n/** The pen's reach in screen px: its own option, else the kit setting, else the constant. */\nexport const penReachOf = (o: { closeHitRadius?: number; kitSettings?: KitSettingsStore }) => ({\n close: o.closeHitRadius ?? o.kitSettings?.get('targets.penCloseHitPx') ?? PEN_CLOSE_HIT_PX,\n});\n\nconst radiiOf = (kit: KitSettingsStore | undefined) => ({\n anchor: kit?.get('targets.penAnchorRadiusPx') ?? ANCHOR_RADIUS_PX,\n handleDot: kit?.get('targets.penHandleDotRadiusPx') ?? HANDLE_DOT_RADIUS_PX,\n closeHint: kit?.get('targets.penCloseHintRadiusPx') ?? CLOSE_HINT_RADIUS_PX,\n});\n\nfunction w2s(wx: number, wy: number, view: { x: number; y: number; scale: { x: number; y: number } }): [number, number] {\n return [(wx - view.x) * view.scale.x, (wy - view.y) * view.scale.y];\n}\n\n// approximateCircle has moved to `./markers.ts` as `circlePath`. Re-exported\n// locally so we don't rename callers in this file.\nfunction approximateCircle(cx: number, cy: number, r: number): PolygonPath {\n return circlePath(cx, cy, r);\n}\n\n/** A pen subpath as a screen-space `PolygonPath`, segmented by the same rule\n * the pen commits with. */\nfunction subpathToPath(\n sp: PenSubpath,\n view: { x: number; y: number; scale: { x: number; y: number } },\n): PolygonPath | null {\n if (sp.anchors.length === 0) return null;\n const pt = (p: { x: number; y: number }) => {\n const [x, y] = w2s(p.x, p.y, view);\n return { x, y };\n };\n const anchors = sp.anchors.map((a: PenAnchor) => ({\n ...pt(a),\n ...(a.inHandle ? { inHandle: pt(a.inHandle) } : {}),\n ...(a.outHandle ? { outHandle: pt(a.outHandle) } : {}),\n }));\n return anchorsToPath([anchors], [sp.closed]);\n}\n\n/** Render layer that draws the pen tool's path as it is being placed —\n * committed anchors, the rubber band to the cursor, and the close hint. Draws\n * nothing when the pen is idle. */\nexport function createPenPreviewLayer(\n opts: CreatePenPreviewLayerOptions,\n): RenderLayer<unknown> {\n const style = { ...DEFAULT_STYLE, ...(opts.style ?? {}) };\n const s = opts.state;\n\n return {\n id: 'penPreview',\n label: 'Pen preview',\n space: 'screen',\n draw: (_data, view) => {\n const out: DrawCommand[] = [];\n const r = radiiOf(opts.kitSettings?.());\n // (1) Finished subpaths — faded outline.\n for (const sp of s.finishedSubpaths) {\n const path = subpathToPath(sp, view);\n if (path === null) continue;\n out.push({\n kind: 'path',\n path,\n stroke: { paint: { kind: 'solid', color: style.finishedSubpathStroke }, width: 1 },\n });\n }\n\n const cur = s.current;\n if (!cur && s.cursor === null) return out;\n\n // (2) Current subpath — bright outline.\n if (cur) {\n const path = subpathToPath(cur, view);\n if (path !== null) {\n out.push({\n kind: 'path',\n path,\n stroke: { paint: { kind: 'solid', color: style.rubberBandStroke }, width: 1 },\n });\n }\n }\n\n // (3) Rubber-band from latest anchor to cursor.\n if (cur && cur.anchors.length > 0 && s.cursor && s.draggingHandleAt === null) {\n const last = cur.anchors[cur.anchors.length - 1];\n const out2 = last.outHandle;\n const [sx, sy] = w2s(last.x, last.y, view);\n const [cx, cy] = w2s(s.cursor.x, s.cursor.y, view);\n let path: PolygonPath;\n if (out2) {\n const [c1x, c1y] = w2s(out2.x, out2.y, view);\n path = {\n kind: 'polygon',\n commands: new Uint8Array([PATH_M, PATH_C]),\n coords: new Float32Array([sx, sy, c1x, c1y, cx, cy, cx, cy]),\n fillRule: 'nonzero',\n };\n } else {\n path = {\n kind: 'polygon',\n commands: new Uint8Array([PATH_M, PATH_L]),\n coords: new Float32Array([sx, sy, cx, cy]),\n fillRule: 'nonzero',\n };\n }\n out.push({\n kind: 'path',\n path,\n stroke: { paint: { kind: 'solid', color: style.rubberBandStroke }, width: 1 },\n });\n }\n\n // (4) Anchor dots + handles.\n if (cur) {\n for (let i = 0; i < cur.anchors.length; i++) {\n const a = cur.anchors[i];\n const [ax, ay] = w2s(a.x, a.y, view);\n out.push({\n kind: 'path',\n path: approximateCircle(ax, ay, r.anchor),\n fill: { paint: { kind: 'solid', color: style.anchorFill } },\n stroke: { paint: { kind: 'solid', color: style.anchorStroke }, width: 1 },\n });\n // Handle line + dot (only the latest anchor, per spec).\n if (i === cur.anchors.length - 1 && a.outHandle) {\n const [hx, hy] = w2s(a.outHandle.x, a.outHandle.y, view);\n const linePath: PolygonPath = {\n kind: 'polygon',\n commands: new Uint8Array([PATH_M, PATH_L]),\n coords: new Float32Array([ax, ay, hx, hy]),\n fillRule: 'nonzero',\n };\n out.push({\n kind: 'path',\n path: linePath,\n stroke: { paint: { kind: 'solid', color: style.handleStroke }, width: 1 },\n });\n out.push({\n kind: 'path',\n path: approximateCircle(hx, hy, r.handleDot),\n fill: { paint: { kind: 'solid', color: style.handleStroke } },\n });\n const inSide = a.inHandle;\n if (inSide) {\n const [mx, my] = w2s(inSide.x, inSide.y, view);\n const inLine: PolygonPath = {\n kind: 'polygon',\n commands: new Uint8Array([PATH_M, PATH_L]),\n coords: new Float32Array([ax, ay, mx, my]),\n fillRule: 'nonzero',\n };\n out.push({\n kind: 'path',\n path: inLine,\n stroke: { paint: { kind: 'solid', color: style.handleStroke }, width: 1 },\n });\n out.push({\n kind: 'path',\n path: approximateCircle(mx, my, r.handleDot),\n fill: { paint: { kind: 'solid', color: style.handleStroke } },\n });\n }\n }\n }\n\n // (5) Close-hint ring on first anchor.\n if (s.closeHintActive && cur.anchors.length >= 3) {\n const first = cur.anchors[0];\n const [fx, fy] = w2s(first.x, first.y, view);\n out.push({\n kind: 'path',\n path: approximateCircle(fx, fy, r.closeHint),\n stroke: { paint: { kind: 'solid', color: style.closeHintFill }, width: 1.5 },\n });\n }\n }\n\n return out;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { boundsOfPath, boxToBox, poseFrameOf, regularPolygonPath, starPath, transformPath, worldEditToStorage } from "@weasel-js/geom";
|
|
1
|
+
import { boundsOfPath, boxToBox, originOf, poseFrameOf, regularPolygonPath, starPath, transformPath, worldCoords, worldEditToStorage } from "@weasel-js/geom";
|
|
2
2
|
|
|
3
|
-
//#region src/
|
|
3
|
+
//#region src/domains/paths/shapeGenerator.ts
|
|
4
4
|
/**
|
|
5
5
|
* The parameters a polygon or star was drawn with, kept on the node beside the
|
|
6
6
|
* geometry they made (`data.generator` beside `data.path`), so the shape stays
|
|
@@ -76,11 +76,13 @@ function pathMatches(path, g) {
|
|
|
76
76
|
const u = boundsOfPath(unit);
|
|
77
77
|
const b = boundsOfPath(p);
|
|
78
78
|
const m = boxToBox(u.x, u.y, u.width, u.height, b.x, b.y, b.width, b.height);
|
|
79
|
-
const
|
|
79
|
+
const o = originOf(p);
|
|
80
|
+
const tol = 1e-4 * Math.max(1, Math.abs(b.x - o.x) + b.width, Math.abs(b.y - o.y) + b.height);
|
|
81
|
+
const at = worldCoords(p);
|
|
80
82
|
for (let i = 0; i + 1 < unit.coords.length; i += 2) {
|
|
81
83
|
const x = m[0] * unit.coords[i] + m[4];
|
|
82
84
|
const y = m[3] * unit.coords[i + 1] + m[5];
|
|
83
|
-
if (Math.abs(x -
|
|
85
|
+
if (Math.abs(x - at[i]) > tol || Math.abs(y - at[i + 1]) > tol) return false;
|
|
84
86
|
}
|
|
85
87
|
return true;
|
|
86
88
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shapeGenerator.js","names":[],"sources":["../../../src/domains/paths/shapeGenerator.ts"],"sourcesContent":["/**\n * The parameters a polygon or star was drawn with, kept on the node beside the\n * geometry they made (`data.generator` beside `data.path`), so the shape stays\n * editable as a polygon or a star after it is drawn.\n *\n * `data.path` stays the geometry every reader uses; the generator only says how\n * to make it again. Editing the path itself turns the shape into plain\n * geometry: the generator is dropped, since it could no longer reproduce what\n * is drawn. A generator whose path no longer matches it (a writer that changed\n * the path without dropping it) is treated the same way.\n */\nimport {\n boundsOfPath,\n boxToBox,\n poseFrameOf,\n regularPolygonPath,\n starPath,\n transformPath,\n originOf,\n worldCoords,\n worldEditToStorage,\n type PathInWorldPose,\n type PolygonPath,\n} from '@weasel-js/geom';\n\n/** A regular polygon's parameters. `rotation` is the angle of its first\n * vertex, in radians, inside the pose's own rotation. */\nexport interface PolygonGenerator {\n kind: 'polygon';\n sides: number;\n rotation: number;\n}\n\n/** A star's parameters. `innerRadiusRatio` is the inner radius over the outer\n * one; `rotation` is the angle of its first point, in radians, inside the\n * pose's own rotation. */\nexport interface StarGenerator {\n kind: 'star';\n points: number;\n innerRadiusRatio: number;\n rotation: number;\n}\n\n/** The parameters a node's `data.path` was generated from. */\nexport type ShapeGenerator = PolygonGenerator | StarGenerator;\n\n/** The range of a polygon's side count and a star's point count. */\nexport const SHAPE_COUNT_RANGE = { min: 3, max: 32 } as const;\n\n/** The range of a star's `innerRadiusRatio`. */\nexport const STAR_RATIO_RANGE = { min: 0.01, max: 1 } as const;\n\nconst finite = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);\n\n/** Whether `v` is a well-formed {@link ShapeGenerator}. */\nexport function isShapeGenerator(v: unknown): v is ShapeGenerator {\n if (v === null || typeof v !== 'object') return false;\n const g = v as Record<string, unknown>;\n if (!finite(g.rotation)) return false;\n if (g.kind === 'polygon') return finite(g.sides);\n if (g.kind === 'star') return finite(g.points) && finite(g.innerRadiusRatio);\n return false;\n}\n\n/** The generator a node's `data` holds, or `null`. */\nexport function shapeGeneratorOf(data: unknown): ShapeGenerator | null {\n const g = (data as { generator?: unknown } | null | undefined)?.generator;\n return isShapeGenerator(g) ? g : null;\n}\n\nconst clamp = (v: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, v));\nconst count = (v: number): number => clamp(Math.round(v), SHAPE_COUNT_RANGE.min, SHAPE_COUNT_RANGE.max);\n\n/** `g` with its counts rounded and every field held in range. */\nexport function clampShapeGenerator(g: ShapeGenerator): ShapeGenerator {\n if (g.kind === 'polygon') return { kind: 'polygon', sides: count(g.sides), rotation: g.rotation };\n return {\n kind: 'star',\n points: count(g.points),\n innerRadiusRatio: clamp(g.innerRadiusRatio, STAR_RATIO_RANGE.min, STAR_RATIO_RANGE.max),\n rotation: g.rotation,\n };\n}\n\n/** The shape `g` makes, on a circle of `radius` around `center`. */\nexport function shapeGeneratorPath(\n g: ShapeGenerator,\n center: { x: number; y: number } = { x: 0, y: 0 },\n radius = 1,\n): PolygonPath {\n const c = clampShapeGenerator(g);\n return c.kind === 'polygon'\n ? regularPolygonPath(center, radius, c.sides, c.rotation)\n : starPath(center, radius, c.points, radius * c.innerRadiusRatio, c.rotation);\n}\n\n/** `data` without its generator: the shape as plain geometry. */\nexport function withoutShapeGenerator<T>(data: T): T {\n if (data === null || typeof data !== 'object' || !('generator' in data)) return data;\n const { generator: _dropped, ...rest } = data as Record<string, unknown>;\n void _dropped;\n return rest as T;\n}\n\n/** Whether `path` is what `g` makes, up to the box-to-box map storage applies. */\nfunction pathMatches(path: unknown, g: ShapeGenerator): boolean {\n const p = path as PolygonPath | undefined;\n if (p?.kind !== 'polygon') return false;\n const unit = shapeGeneratorPath(g);\n if (p.coords.length !== unit.coords.length) return false;\n const u = boundsOfPath(unit);\n const b = boundsOfPath(p);\n const m = boxToBox(u.x, u.y, u.width, u.height, b.x, b.y, b.width, b.height);\n // Paths store float32 coordinates, so the slack scales with how far they are from the point they are measured from.\n const o = originOf(p);\n const tol = 1e-4 * Math.max(1, Math.abs(b.x - o.x) + b.width, Math.abs(b.y - o.y) + b.height);\n const at = worldCoords(p);\n for (let i = 0; i + 1 < unit.coords.length; i += 2) {\n const x = m[0] * unit.coords[i] + m[4];\n const y = m[3] * unit.coords[i + 1] + m[5];\n if (Math.abs(x - at[i]) > tol || Math.abs(y - at[i + 1]) > tol) return false;\n }\n return true;\n}\n\n/** A node's two halves, as a regeneration reads and writes them. */\nexport interface GeneratedShape<P extends PathInWorldPose = PathInWorldPose> {\n pose: P;\n data: unknown;\n}\n\n/**\n * `node` made again from `next`, in place: the new shape takes the box the old\n * one's circle had, under the same rotation and transform, so a star given\n * more points keeps its center and its size. Returns `null` when the node holds\n * no generator, or its path no longer matches the one it holds.\n */\nexport function regenerateShape<P extends PathInWorldPose>(\n node: GeneratedShape<P>,\n next: ShapeGenerator,\n): GeneratedShape<P> | null {\n const held = shapeGeneratorOf(node.data);\n if (!held || !pathMatches((node.data as { path?: unknown }).path, held)) return null;\n const g = clampShapeGenerator(next);\n const was = boundsOfPath(shapeGeneratorPath(held));\n const { pose } = node;\n // Where the unit circle of the held generator lands in the pose's local frame.\n const toLocal = boxToBox(was.x, was.y, was.width, was.height, pose.x, pose.y, pose.width, pose.height);\n const local = transformPath(shapeGeneratorPath(g), toLocal) as PolygonPath;\n const frame = poseFrameOf(pose);\n const world = frame ? (transformPath(local, frame) as PolygonPath) : local;\n const stored = worldEditToStorage(pose, world);\n return { pose: stored.pose, data: { ...(node.data as object), path: stored.path, generator: g } };\n}\n\n/**\n * What a write that left a node as `next`, from `prev`, stores: when the write\n * changed the generator, the shape made again from it. A generator written\n * onto a node that held none, or of another kind, or onto one whose path no\n * longer matches, is dropped, leaving the shape as plain geometry. Any other\n * write is stored as written. Fits `NodePropertiesEntry.settle`.\n */\nexport function settleShapeGenerator<N extends { pose: unknown; data: unknown }>(next: N, prev: N): N {\n const data = next.data as Record<string, unknown> | null;\n if (data === null || typeof data !== 'object' || !('generator' in data)) return next;\n const raw = data.generator;\n const held = shapeGeneratorOf(prev.data);\n if (held !== null && JSON.stringify(raw) === JSON.stringify(held)) return next;\n const regenerated = held !== null && isShapeGenerator(raw) && raw.kind === held.kind\n ? regenerateShape({ pose: next.pose as PathInWorldPose, data: { ...data, generator: held } }, raw)\n : null;\n return { ...next, ...(regenerated ?? { data: withoutShapeGenerator(next.data) }) };\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { findShapeSilhouette } from "../../canvas/NodeShape.js";
|
|
2
2
|
import { isSilhouettePose } from "../../core/geometry/regionHit.js";
|
|
3
3
|
|
|
4
|
-
//#region src/
|
|
4
|
+
//#region src/domains/paths/strokeOutline.ts
|
|
5
5
|
/** The outline `node` is drawn with, in world coords: its pose when the pose
|
|
6
6
|
* is a polygon, else its painter's silhouette. Null for a node with neither. */
|
|
7
7
|
function nodeOutline(node) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strokeOutline.js","names":[],"sources":["../../../src/domains/paths/strokeOutline.ts"],"sourcesContent":["import type { Path } from '@weasel-js/geom';\nimport { isSilhouettePose } from 'core/geometry/regionHit';\nimport type { Node } from 'core/scene/types';\nimport { findShapeSilhouette } from 'canvas/NodeShape';\n\n/** The outline `node` is drawn with, in world coords: its pose when the pose\n * is a polygon, else its painter's silhouette. Null for a node with neither. */\nexport function nodeOutline(node: Node<unknown, string, unknown>): Path | null {\n const pose = node.pose;\n if (isSilhouettePose(pose)) return pose;\n return findShapeSilhouette(node, pose);\n}\n"],"mappings":";;;;;;AAOA,SAAgB,YAAY,MAAmD;CAC7E,MAAM,OAAO,KAAK;CAClB,IAAI,iBAAiB,IAAI,GAAG,OAAO;CACnC,OAAO,oBAAoB,MAAM,IAAI;AACvC"}
|
|
@@ -2,7 +2,7 @@ import { anchorFieldsOf } from "../../interactions/actions/defaults/anchorFields
|
|
|
2
2
|
import { useMemo, useSyncExternalStore } from "react";
|
|
3
3
|
import { useActionsRegistry, useOptionalDepRegistry } from "@weasel-js/routing/react";
|
|
4
4
|
|
|
5
|
-
//#region src/
|
|
5
|
+
//#region src/domains/paths/useAnchorFields.ts
|
|
6
6
|
/**
|
|
7
7
|
* The anchor fields of the path being edited, for chrome beside the canvas:
|
|
8
8
|
* a panel showing the selected anchors' numbers and writing typed ones back.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAnchorFields.js","names":[],"sources":["../../../src/domains/paths/useAnchorFields.ts"],"sourcesContent":["/**\n * The anchor fields of the path being edited, for chrome beside the canvas:\n * a panel showing the selected anchors' numbers and writing typed ones back.\n */\nimport { useMemo, useSyncExternalStore } from 'react';\nimport type { AnchorField, AnchorFieldValues, NumberEntry } from '@weasel-js/geom';\nimport { useActionsRegistry, useOptionalDepRegistry } from '@weasel-js/routing/react';\nimport { anchorFieldsOf } from 'interactions/actions/defaults/anchorFields';\n\n/** What {@link useAnchorFields} answers. */\nexport interface AnchorFieldsApi {\n /** Each field's value: a number, `null` where the selected anchors differ,\n * no key where the field does not apply. Empty outside path edit or with no\n * anchor selected. */\n values: AnchorFieldValues;\n /** Write `entry` into `field` of every selected anchor, as one undo step,\n * through the `setAnchorField` action. */\n write(field: AnchorField, entry: NumberEntry): void;\n}\n\nconst noSubscribe = (): (() => void) => () => {};\n\n/**\n * The selected anchors' fields in the canvas in scope — the one last pressed\n * in, from chrome above several — kept current as the path, the anchor\n * selection, or the edited path changes. Outside any canvas it answers `{}`\n * and writes nothing.\n */\nexport function useAnchorFields(): AnchorFieldsApi {\n const deps = useOptionalDepRegistry();\n const actions = useActionsRegistry();\n const snapshot = (): string => JSON.stringify(anchorFieldsOf(deps ? { editAnchors: deps.get('editAnchors') } : undefined));\n const json = useSyncExternalStore(deps?.subscribe ?? noSubscribe, snapshot, snapshot);\n return useMemo(() => ({\n values: JSON.parse(json) as AnchorFieldValues,\n write: (field, entry) => { actions?.trigger('setAnchorField', { field, ...entry }); },\n }), [json, actions]);\n}\n"],"mappings":";;;;;;;;;AAoBA,IAAM,0BAAwC,CAAC;;;;;;;AAQ/C,SAAgB,kBAAmC;CACjD,MAAM,OAAO,uBAAuB;CACpC,MAAM,UAAU,mBAAmB;CACnC,MAAM,iBAAyB,KAAK,UAAU,eAAe,OAAO,EAAE,aAAa,KAAK,IAAI,aAAa,EAAE,IAAI,MAAS,CAAC;CACzH,MAAM,OAAO,qBAAqB,MAAM,aAAa,aAAa,UAAU,QAAQ;CACpF,OAAO,eAAe;EACpB,QAAQ,KAAK,MAAM,IAAI;EACvB,QAAQ,OAAO,UAAU;GAAE,SAAS,QAAQ,kBAAkB;IAAE;IAAO,GAAG;GAAM,CAAC;EAAG;CACtF,IAAI,CAAC,MAAM,OAAO,CAAC;AACrB"}
|
|
@@ -5,7 +5,7 @@ import { createPathEditingOverlayLayer } from "./pathEditingOverlayLayer.js";
|
|
|
5
5
|
import { useEffect, useMemo, useState } from "react";
|
|
6
6
|
import { useLatest } from "@weasel-js/react";
|
|
7
7
|
|
|
8
|
-
//#region src/
|
|
8
|
+
//#region src/domains/paths/usePathEdit.ts
|
|
9
9
|
/**
|
|
10
10
|
* The path-edit state and its anchor overlay for one canvas surface.
|
|
11
11
|
*/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePathEdit.js","names":[],"sources":["../../../src/domains/paths/usePathEdit.ts"],"sourcesContent":["/**\n * The path-edit state and its anchor overlay for one canvas surface.\n */\nimport { useLatest } from '@weasel-js/react';\nimport { useEffect, useMemo, useState } from 'react';\nimport type { PathEditHost, PathEditOpening } from 'canvas/SceneCanvas/pathEditHost';\nimport type { RenderLayer } from 'core/layers/render';\nimport { rectFromPlane, type PlaneMap } from 'core/viewport/parallax';\nimport type { View } from 'core/viewport/view';\nimport { liveAnchorMarkers, liveEditablePath, type EditableShape } from './editablePath';\nimport { createPathEditingOverlayLayer } from './pathEditingOverlayLayer';\nimport type { PathEditingOverlaySlotConfig } from 'canvas/layerSlots';\nimport { createPathEditState, type PathEditState } from './pathEditState';\nimport type { PathEditExtension } from './pathEditExtension';\n\nexport interface UsePathEditStateOptions {\n /** `<SceneCanvas pathEdit>`. */\n host: PathEditHost | undefined;\n /** `<SceneCanvas defaultPathEdit>`: what the state starts holding. */\n opening?: PathEditOpening;\n /** See `PathEditSource.isAllowed`; absent when no mode registry is wired. */\n isAllowed: (() => boolean) | undefined;\n isSelectedNode: (id: string) => boolean;\n /** The path-edit extensions the canvas's presets bring. */\n extensions: readonly PathEditExtension[];\n}\n\n/** One state object for the life of the component, answering from the\n * options of the last committed render. */\nexport function usePathEditState(options: UsePathEditStateOptions): PathEditState {\n const live = useLatest(options);\n const [state] = useState(() => {\n const made = createPathEditState({\n host: () => live.current.host,\n isAllowed: () => live.current.isAllowed?.() ?? true,\n isSelectedNode: (id) => live.current.isSelectedNode(id),\n extensions: options.extensions,\n });\n const { opening } = options;\n if (opening) {\n made.setEditingId(opening.id);\n made.setSelectedAnchors(new Set(opening.anchors ?? []));\n }\n return made;\n });\n useEffect(() => state.setExtensions(options.extensions), [state, options.extensions]);\n return state;\n}\n\nexport interface PathEditOverlayInputs {\n /** The committed node for an id, or nothing once it is gone. */\n nodeOf: (id: string) => EditableShape | null | undefined;\n /** How `view`'s world maps into the parallax plane `id` is drawn through. */\n planeOfNode: (id: string, view: View) => PlaneMap | null;\n}\n\n/** The anchor overlay for `state`, drawn as `config` says. Rebuilt when\n * `config` changes, so pass a stable one; otherwise it reads `inputs` as of\n * the last commit and repaints itself when the state changes. */\nexport function usePathEditOverlayLayer(\n state: PathEditState,\n inputs: PathEditOverlayInputs,\n config?: PathEditingOverlaySlotConfig,\n): RenderLayer<unknown> {\n const live = useLatest(inputs);\n return useMemo<RenderLayer<unknown>>(() => ({\n ...createPathEditingOverlayLayer({\n getEditingId: () => state.getEditingId() || null,\n getPose: (id, previews, view) =>\n liveEditablePath(id, live.current.nodeOf(id), previews, live.current.planeOfNode(id, view), state.getExtensions()),\n getAnchorMarkers: (id, previews) => liveAnchorMarkers(id, live.current.nodeOf(id), previews, state.getExtensions()),\n getSelectedAnchors: state.getSelectedAnchors,\n getSelectedMark: state.getSelectedMark,\n getExtensions: state.getExtensions,\n getNode: (id) => live.current.nodeOf(id) ?? null,\n // Drawn by an action that ran in the edited path's plane.\n getMarquee: (view) => {\n const rect = state.getMarquee();\n const plane = rect ? live.current.planeOfNode(state.getEditingId(), view) : null;\n return rect && plane ? rectFromPlane(plane, rect) : rect;\n },\n ...(config?.style ? { style: config.style } : {}),\n }),\n subscribe: state.subscribe,\n }), [state, live, config]);\n}\n"],"mappings":";;;;;;;;;;;;;AA6BA,SAAgB,iBAAiB,SAAiD;CAChF,MAAM,OAAO,UAAU,OAAO;CAC9B,MAAM,CAAC,SAAS,eAAe;EAC7B,MAAM,OAAO,oBAAoB;GAC/B,YAAY,KAAK,QAAQ;GACzB,iBAAiB,KAAK,QAAQ,YAAY,KAAK;GAC/C,iBAAiB,OAAO,KAAK,QAAQ,eAAe,EAAE;GACtD,YAAY,QAAQ;EACtB,CAAC;EACD,MAAM,EAAE,YAAY;EACpB,IAAI,SAAS;GACX,KAAK,aAAa,QAAQ,EAAE;GAC5B,KAAK,mBAAmB,IAAI,IAAI,QAAQ,WAAW,CAAC,CAAC,CAAC;EACxD;EACA,OAAO;CACT,CAAC;CACD,gBAAgB,MAAM,cAAc,QAAQ,UAAU,GAAG,CAAC,OAAO,QAAQ,UAAU,CAAC;CACpF,OAAO;AACT;;;;AAYA,SAAgB,wBACd,OACA,QACA,QACsB;CACtB,MAAM,OAAO,UAAU,MAAM;CAC7B,OAAO,eAAqC;EAC1C,GAAG,8BAA8B;GAC/B,oBAAoB,MAAM,aAAa,KAAK;GAC5C,UAAU,IAAI,UAAU,SACtB,iBAAiB,IAAI,KAAK,QAAQ,OAAO,EAAE,GAAG,UAAU,KAAK,QAAQ,YAAY,IAAI,IAAI,GAAG,MAAM,cAAc,CAAC;GACnH,mBAAmB,IAAI,aAAa,kBAAkB,IAAI,KAAK,QAAQ,OAAO,EAAE,GAAG,UAAU,MAAM,cAAc,CAAC;GAClH,oBAAoB,MAAM;GAC1B,iBAAiB,MAAM;GACvB,eAAe,MAAM;GACrB,UAAU,OAAO,KAAK,QAAQ,OAAO,EAAE,KAAK;GAE5C,aAAa,SAAS;IACpB,MAAM,OAAO,MAAM,WAAW;IAC9B,MAAM,QAAQ,OAAO,KAAK,QAAQ,YAAY,MAAM,aAAa,GAAG,IAAI,IAAI;IAC5E,OAAO,QAAQ,QAAQ,cAAc,OAAO,IAAI,IAAI;GACtD;GACA,GAAI,QAAQ,QAAQ,EAAE,OAAO,OAAO,MAAM,IAAI,CAAC;EACjD,CAAC;EACD,WAAW,MAAM;CACnB,IAAI;EAAC;EAAO;EAAM;CAAM,CAAC;AAC3B"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
2
|
import { useLatest, useVisibleRaf } from "@weasel-js/react";
|
|
3
3
|
|
|
4
|
-
//#region src/
|
|
4
|
+
//#region src/domains/poseRun/usePoseRun.ts
|
|
5
5
|
/**
|
|
6
6
|
* A pose run: poses published per frame, committed once.
|
|
7
7
|
*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePoseRun.js","names":[],"sources":["../../../src/domains/poseRun/usePoseRun.ts"],"sourcesContent":["/**\n * A pose run: poses published per frame, committed once.\n *\n * The frames go to `scene.overrides` — the ephemeral table a drag publishes\n * to — so an edge follows a node the run is moving, and the whole run lands as\n * one undo entry when it settles.\n *\n * **A node another gesture owns is pinned, not driven.** The run publishes\n * through a hold of its own, so an override it did not put there belongs to\n * somebody else — a drag, a resize. It never writes that id, never commits it,\n * and hands it to `step` as pinned so the producer can hold its neighbors\n * against it.\n */\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { useLatest, useVisibleRaf, type VisibleRaf } from '@weasel-js/react';\nimport type { NodeId, PoseHold, Scene } from '../../core/scene/types';\n\n/** What a producer is told before it is asked for a frame. */\nexport interface PoseRunCtx<TPose> {\n /** Nodes another gesture is moving, and the pose each is drawn at. The run\n * will not write these, so a producer holds them fixed. */\n pinned: ReadonlyMap<NodeId, TPose>;\n /** Frames this run has produced, starting at 0. */\n frame: number;\n}\n\n/** One frame of a run. */\nexport interface PoseRunStep<TPose> {\n /** Where the nodes it drives are this frame — the whole picture, not a\n * delta. An id the frame omits stops being published, which is how a node\n * leaves the run's hands. */\n poses: Iterable<readonly [NodeId, TPose]>;\n /** Whether the run is finished. The frame is published either way, and a\n * finished run commits. */\n done: boolean;\n}\n\n/** Options for `usePoseRun`. Read fresh each frame, so they may change mid-run. */\nexport interface UsePoseRunOptions<TPose> {\n scene: Scene<unknown, string, TPose>;\n /** Called once per frame while running, for that frame's poses. */\n step: (ctx: PoseRunCtx<TPose>) => PoseRunStep<TPose>;\n /** The undo entry's name. Default `'Layout'`. */\n label?: string;\n /** How many nodes the commit wrote. Fires after the ops land. */\n onCommit?: (moved: number) => void;\n /** Writes of the caller's own, made inside the commit's batch so they\n * share its undo entry. `finished` is false when `stop` cut the run short\n * of where `step` was taking it. */\n commitWith?: (finished: boolean) => void;\n /** Clock injection, for tests. Defaults to `requestAnimationFrame`. */\n requestFrame?: (cb: (t: number) => void) => number;\n cancelFrame?: (handle: number) => void;\n}\n\n/** Controls for a pose run. Stable for the life of the component. */\nexport interface PoseRun {\n /** Begin a new run, counting frames from 0. Idempotent while running. */\n start(): void;\n /** Finish now: commit what is published, then drop the overrides. */\n stop(): void;\n /** Abandon: drop the overrides and write nothing, like a canceled drag. */\n cancel(): void;\n isRunning(): boolean;\n}\n\n/**\n * Drive node poses frame by frame through `scene.overrides`, committing them as\n * one undo entry when `step` reports done or `stop` is called. Idle until\n * `start`; unmounting abandons the run without writing.\n */\nexport function usePoseRun<TPose>(opts: UsePoseRunOptions<TPose>): PoseRun {\n const optsRef = useLatest(opts);\n\n /** This frame's poses. */\n const previews = useRef(new Map<NodeId, TPose>());\n /** The hold the run publishes through, and the scene it was taken from. */\n const held = useRef<{ scene: Scene<unknown, string, TPose>; hold: PoseHold<TPose> } | null>(null);\n const running = useRef(false);\n const frame = useRef(0);\n const mounted = useRef(true);\n\n /** The hold on the scene the options name now. A run handed a different\n * scene mid-flight lets go of the old one's nodes. */\n const holdNow = useCallback((): PoseHold<TPose> => {\n const { scene } = optsRef.current;\n if (held.current?.scene !== scene) {\n held.current?.hold.drop();\n held.current = { scene, hold: scene.overrides.hold() };\n }\n return held.current.hold;\n }, [optsRef]);\n\n const release = useCallback((): void => {\n held.current?.hold.drop();\n previews.current.clear();\n }, []);\n\n const commit = (finished: boolean): void => {\n const scene = optsRef.current.scene;\n const also = optsRef.current.commitWith;\n let moved = 0;\n try {\n if (previews.current.size > 0 || also !== undefined) {\n scene.batch(optsRef.current.label ?? 'Layout', () => {\n for (const [id, pose] of previews.current) {\n if (scene.get(id) === undefined || scene.isLocked(id)) continue;\n scene.setPose(id, pose);\n moved++;\n }\n also?.(finished);\n });\n }\n } finally {\n release();\n }\n optsRef.current.onCommit?.(moved);\n };\n\n const halt = (loop: VisibleRaf): void => {\n running.current = false;\n loop.cancel();\n };\n\n const tick = (loop: VisibleRaf): void => {\n if (!mounted.current || !running.current) return;\n const hold = holdNow();\n const pinned = hold.others();\n const { poses, done } = optsRef.current.step({ pinned, frame: frame.current });\n frame.current++;\n previews.current.clear();\n for (const [id, pose] of poses) {\n if (pinned.has(id)) continue;\n previews.current.set(id, pose);\n }\n hold.set(previews.current);\n if (done) {\n halt(loop);\n commit(true);\n return;\n }\n loop.request();\n };\n\n // The gate defaults an absent clock to `requestAnimationFrame` itself, so\n // these pass straight through rather than being defaulted twice.\n const loop = useVisibleRaf((_time, loop) => { tick(loop); }, {\n requestFrame: opts.requestFrame,\n cancelFrame: opts.cancelFrame,\n });\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n loop.cancel();\n // Teardown is not a decision: an unmounted run abandons its frames\n // rather than writing them.\n release();\n };\n }, [loop, release]);\n\n return useMemo<PoseRun>(() => ({\n start() {\n if (running.current || !mounted.current) return;\n running.current = true;\n frame.current = 0;\n loop.request();\n },\n stop() {\n if (!running.current) return;\n halt(loop);\n commit(false);\n },\n cancel() {\n halt(loop);\n release();\n },\n isRunning: () => running.current,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }), [loop]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAuEA,SAAgB,WAAkB,MAAyC;CACzE,MAAM,UAAU,UAAU,IAAI;;CAG9B,MAAM,WAAW,uBAAO,IAAI,IAAmB,CAAC;;CAEhD,MAAM,OAAO,OAA+E,IAAI;CAChG,MAAM,UAAU,OAAO,KAAK;CAC5B,MAAM,QAAQ,OAAO,CAAC;CACtB,MAAM,UAAU,OAAO,IAAI;;;CAI3B,MAAM,UAAU,kBAAmC;EACjD,MAAM,EAAE,UAAU,QAAQ;EAC1B,IAAI,KAAK,SAAS,UAAU,OAAO;GACjC,KAAK,SAAS,KAAK,KAAK;GACxB,KAAK,UAAU;IAAE;IAAO,MAAM,MAAM,UAAU,KAAK;GAAE;EACvD;EACA,OAAO,KAAK,QAAQ;CACtB,GAAG,CAAC,OAAO,CAAC;CAEZ,MAAM,UAAU,kBAAwB;EACtC,KAAK,SAAS,KAAK,KAAK;EACxB,SAAS,QAAQ,MAAM;CACzB,GAAG,CAAC,CAAC;CAEL,MAAM,UAAU,aAA4B;EAC1C,MAAM,QAAQ,QAAQ,QAAQ;EAC9B,MAAM,OAAO,QAAQ,QAAQ;EAC7B,IAAI,QAAQ;EACZ,IAAI;GACF,IAAI,SAAS,QAAQ,OAAO,KAAK,SAAS,QACxC,MAAM,MAAM,QAAQ,QAAQ,SAAS,gBAAgB;IACnD,KAAK,MAAM,CAAC,IAAI,SAAS,SAAS,SAAS;KACzC,IAAI,MAAM,IAAI,EAAE,MAAM,UAAa,MAAM,SAAS,EAAE,GAAG;KACvD,MAAM,QAAQ,IAAI,IAAI;KACtB;IACF;IACA,OAAO,QAAQ;GACjB,CAAC;EAEL,UAAU;GACR,QAAQ;EACV;EACA,QAAQ,QAAQ,WAAW,KAAK;CAClC;CAEA,MAAM,QAAQ,SAA2B;EACvC,QAAQ,UAAU;EAClB,KAAK,OAAO;CACd;CAEA,MAAM,QAAQ,SAA2B;EACvC,IAAI,CAAC,QAAQ,WAAW,CAAC,QAAQ,SAAS;EAC1C,MAAM,OAAO,QAAQ;EACrB,MAAM,SAAS,KAAK,OAAO;EAC3B,MAAM,EAAE,OAAO,SAAS,QAAQ,QAAQ,KAAK;GAAE;GAAQ,OAAO,MAAM;EAAQ,CAAC;EAC7E,MAAM;EACN,SAAS,QAAQ,MAAM;EACvB,KAAK,MAAM,CAAC,IAAI,SAAS,OAAO;GAC9B,IAAI,OAAO,IAAI,EAAE,GAAG;GACpB,SAAS,QAAQ,IAAI,IAAI,IAAI;EAC/B;EACA,KAAK,IAAI,SAAS,OAAO;EACzB,IAAI,MAAM;GACR,KAAK,IAAI;GACT,OAAO,IAAI;GACX;EACF;EACA,KAAK,QAAQ;CACf;CAIA,MAAM,OAAO,eAAe,OAAO,SAAS;EAAE,KAAK,IAAI;CAAG,GAAG;EAC3D,cAAc,KAAK;EACnB,aAAa,KAAK;CACpB,CAAC;CAED,gBAAgB;EACd,QAAQ,UAAU;EAClB,aAAa;GACX,QAAQ,UAAU;GAClB,KAAK,OAAO;GAGZ,QAAQ;EACV;CACF,GAAG,CAAC,MAAM,OAAO,CAAC;CAElB,OAAO,eAAwB;EAC7B,QAAQ;GACN,IAAI,QAAQ,WAAW,CAAC,QAAQ,SAAS;GACzC,QAAQ,UAAU;GAClB,MAAM,UAAU;GAChB,KAAK,QAAQ;EACf;EACA,OAAO;GACL,IAAI,CAAC,QAAQ,SAAS;GACtB,KAAK,IAAI;GACT,OAAO,KAAK;EACd;EACA,SAAS;GACP,KAAK,IAAI;GACT,QAAQ;EACV;EACA,iBAAiB,QAAQ;CAE3B,IAAI,CAAC,IAAI,CAAC;AACZ"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { viewToTransform } from "../../core/viewport/view.js";
|
|
2
2
|
import { worldToScreen } from "../../core/viewport/viewTransform.js";
|
|
3
3
|
import { rectPathFor } from "./handleCommands.js";
|
|
4
|
+
import { enteredOperandOutlines } from "./enteredOperands.js";
|
|
5
|
+
import { transformPath } from "@weasel-js/geom";
|
|
4
6
|
import { useMemo } from "react";
|
|
5
7
|
|
|
6
|
-
//#region src/
|
|
8
|
+
//#region src/domains/selection/enteredGroupFrame.ts
|
|
7
9
|
/** The theme token the frame reads its color from, set on the canvas or any element above it. */
|
|
8
10
|
var ENTERED_FRAME_TOKEN = "--wzl-entered-group";
|
|
9
11
|
/** The frame's color where nothing sets {@link ENTERED_FRAME_TOKEN}. */
|
|
@@ -13,6 +15,8 @@ var FRAME_DASH = [6, 4];
|
|
|
13
15
|
/**
|
|
14
16
|
* The frame around the group a canvas is working inside: one dashed outline on the group's
|
|
15
17
|
* box, in the selection chrome's own screen space, repainting when the entered group changes.
|
|
18
|
+
* Inside a boolean group, whose children are drawn only through its result, each of them is
|
|
19
|
+
* outlined too.
|
|
16
20
|
*/
|
|
17
21
|
function createEnteredGroupFrameLayer(opts) {
|
|
18
22
|
return {
|
|
@@ -22,24 +26,43 @@ function createEnteredGroupFrameLayer(opts) {
|
|
|
22
26
|
subscribe: (repaint) => {
|
|
23
27
|
const offGroup = opts.entered.subscribe(repaint);
|
|
24
28
|
const offColor = opts.colorChanged?.(repaint);
|
|
29
|
+
const offOutlines = opts.outlinesChanged?.(repaint);
|
|
25
30
|
return () => {
|
|
26
31
|
offGroup();
|
|
27
32
|
offColor?.();
|
|
33
|
+
offOutlines?.();
|
|
28
34
|
};
|
|
29
35
|
},
|
|
30
36
|
draw: (_data, view) => {
|
|
31
37
|
const id = opts.entered.get();
|
|
32
38
|
const b = id === null ? null : opts.boundsOf(id);
|
|
33
39
|
if (b === null) return [];
|
|
34
|
-
const
|
|
35
|
-
|
|
40
|
+
const t = viewToTransform(view);
|
|
41
|
+
const [x, y] = worldToScreen(b.x, b.y, t);
|
|
42
|
+
const paint = {
|
|
43
|
+
kind: "solid",
|
|
44
|
+
color: opts.color?.() ?? ENTERED_FRAME_COLOR
|
|
45
|
+
};
|
|
46
|
+
const toScreen = [
|
|
47
|
+
t.zoom.x,
|
|
48
|
+
0,
|
|
49
|
+
0,
|
|
50
|
+
t.zoom.y,
|
|
51
|
+
t.panX,
|
|
52
|
+
t.panY
|
|
53
|
+
];
|
|
54
|
+
return [...(opts.outlinesOf?.(id) ?? []).map((p) => ({
|
|
55
|
+
kind: "path",
|
|
56
|
+
path: transformPath(p, toScreen),
|
|
57
|
+
stroke: {
|
|
58
|
+
paint,
|
|
59
|
+
width: FRAME_WIDTH
|
|
60
|
+
}
|
|
61
|
+
})), {
|
|
36
62
|
kind: "path",
|
|
37
63
|
path: rectPathFor(x, y, b.width * view.scale.x, b.height * view.scale.y),
|
|
38
64
|
stroke: {
|
|
39
|
-
paint
|
|
40
|
-
kind: "solid",
|
|
41
|
-
color: opts.color?.() ?? ENTERED_FRAME_COLOR
|
|
42
|
-
},
|
|
65
|
+
paint,
|
|
43
66
|
width: FRAME_WIDTH,
|
|
44
67
|
dash: FRAME_DASH
|
|
45
68
|
}
|
|
@@ -47,16 +70,32 @@ function createEnteredGroupFrameLayer(opts) {
|
|
|
47
70
|
}
|
|
48
71
|
};
|
|
49
72
|
}
|
|
50
|
-
|
|
73
|
+
/** The frame layer over `scene`, outlining a boolean group's children while it is entered. */
|
|
74
|
+
function useEnteredGroupFrameLayer(entered, boundsOf, color, scene) {
|
|
51
75
|
return useMemo(() => createEnteredGroupFrameLayer({
|
|
52
76
|
entered,
|
|
53
77
|
boundsOf,
|
|
54
78
|
color: color.read,
|
|
55
|
-
colorChanged: color.changed
|
|
79
|
+
colorChanged: color.changed,
|
|
80
|
+
...scene ? {
|
|
81
|
+
outlinesOf: (id) => enteredOperandOutlines(scene, id),
|
|
82
|
+
outlinesChanged: (fn) => {
|
|
83
|
+
const whileEntered = () => {
|
|
84
|
+
if (entered.get() !== null) fn();
|
|
85
|
+
};
|
|
86
|
+
const offScene = scene.subscribe(whileEntered);
|
|
87
|
+
const offOverrides = scene.overrides.subscribe(whileEntered);
|
|
88
|
+
return () => {
|
|
89
|
+
offScene();
|
|
90
|
+
offOverrides();
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
} : {}
|
|
56
94
|
}), [
|
|
57
95
|
entered,
|
|
58
96
|
boundsOf,
|
|
59
|
-
color
|
|
97
|
+
color,
|
|
98
|
+
scene
|
|
60
99
|
]);
|
|
61
100
|
}
|
|
62
101
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enteredGroupFrame.js","names":[],"sources":["../../../src/domains/selection/enteredGroupFrame.ts"],"sourcesContent":["import { useMemo } from 'react';\nimport type { RenderLayer } from 'core/layers/render';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { viewToTransform } from 'core/viewport/view';\nimport { worldToScreen } from 'core/viewport/viewTransform';\nimport type { EnteredGroupApi } from 'canvas/deps/enteredGroup';\nimport { rectPathFor } from './handleCommands';\nimport { transformPath, type Path } from '@weasel-js/geom';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { NodeId, Scene } from 'core/scene/types';\nimport { enteredOperandOutlines } from './enteredOperands';\n\n/** The theme token the frame reads its color from, set on the canvas or any element above it. */\nexport const ENTERED_FRAME_TOKEN = '--wzl-entered-group';\n/** The frame's color where nothing sets {@link ENTERED_FRAME_TOKEN}. */\nexport const ENTERED_FRAME_COLOR = '#a48bd4';\nconst FRAME_WIDTH = 1;\nconst FRAME_DASH = [6, 4];\n\n/**\n * The frame around the group a canvas is working inside: one dashed outline on the group's\n * box, in the selection chrome's own screen space, repainting when the entered group changes.\n * Inside a boolean group, whose children are drawn only through its result, each of them is\n * outlined too.\n */\nexport function createEnteredGroupFrameLayer(opts: {\n entered: EnteredGroupApi;\n /** The box to frame, in world units. */\n boundsOf: (id: string) => Bounds | null;\n /** The frame's color, read at each paint. Default {@link ENTERED_FRAME_COLOR}. */\n color?: () => string;\n /** Hears that `color` may answer differently: a theme or mode switch. */\n colorChanged?: (fn: () => void) => () => void;\n /** The world outlines to draw while `id` is entered: `enteredOperandOutlines`. */\n outlinesOf?: (id: string) => readonly Path[];\n /** Hears that `outlinesOf` may answer differently: a scene edit or a pose override. */\n outlinesChanged?: (fn: () => void) => () => void;\n}): RenderLayer<unknown> {\n return {\n id: 'entered-group-frame',\n label: 'Entered group',\n space: 'screen',\n subscribe: (repaint) => {\n const offGroup = opts.entered.subscribe(repaint);\n const offColor = opts.colorChanged?.(repaint);\n const offOutlines = opts.outlinesChanged?.(repaint);\n return () => { offGroup(); offColor?.(); offOutlines?.(); };\n },\n draw: (_data, view) => {\n const id = opts.entered.get();\n const b = id === null ? null : opts.boundsOf(id);\n if (b === null) return [];\n const t = viewToTransform(view);\n const [x, y] = worldToScreen(b.x, b.y, t);\n const paint = { kind: 'solid', color: opts.color?.() ?? ENTERED_FRAME_COLOR } as const;\n const toScreen = [t.zoom.x, 0, 0, t.zoom.y, t.panX, t.panY] as const;\n const outlines: DrawCommand[] = (opts.outlinesOf?.(id!) ?? []).map((p) => ({\n kind: 'path', path: transformPath(p, toScreen), stroke: { paint, width: FRAME_WIDTH },\n }));\n return [...outlines, {\n kind: 'path',\n path: rectPathFor(x, y, b.width * view.scale.x, b.height * view.scale.y),\n stroke: { paint, width: FRAME_WIDTH, dash: FRAME_DASH },\n }];\n },\n };\n}\n\n/** The frame layer over `scene`, outlining a boolean group's children while it is entered. */\nexport function useEnteredGroupFrameLayer(\n entered: EnteredGroupApi,\n boundsOf: (id: string) => Bounds | null,\n color: { read: () => string; changed: (fn: () => void) => () => void },\n scene?: Scene<unknown, string, unknown>,\n): RenderLayer<unknown> {\n return useMemo(\n () => createEnteredGroupFrameLayer({\n entered, boundsOf, color: color.read, colorChanged: color.changed,\n ...(scene ? {\n outlinesOf: (id: string) => enteredOperandOutlines(scene, id as NodeId),\n outlinesChanged: (fn: () => void) => {\n const whileEntered = () => { if (entered.get() !== null) fn(); };\n const offScene = scene.subscribe(whileEntered);\n const offOverrides = scene.overrides.subscribe(whileEntered);\n return () => { offScene(); offOverrides(); };\n },\n } : {}),\n }),\n [entered, boundsOf, color, scene],\n );\n}\n"],"mappings":";;;;;;;;;AAaA,IAAa,sBAAsB;;AAEnC,IAAa,sBAAsB;AACnC,IAAM,cAAc;AACpB,IAAM,aAAa,CAAC,GAAG,CAAC;;;;;;;AAQxB,SAAgB,6BAA6B,MAYpB;CACvB,OAAO;EACL,IAAI;EACJ,OAAO;EACP,OAAO;EACP,YAAY,YAAY;GACtB,MAAM,WAAW,KAAK,QAAQ,UAAU,OAAO;GAC/C,MAAM,WAAW,KAAK,eAAe,OAAO;GAC5C,MAAM,cAAc,KAAK,kBAAkB,OAAO;GAClD,aAAa;IAAE,SAAS;IAAG,WAAW;IAAG,cAAc;GAAG;EAC5D;EACA,OAAO,OAAO,SAAS;GACrB,MAAM,KAAK,KAAK,QAAQ,IAAI;GAC5B,MAAM,IAAI,OAAO,OAAO,OAAO,KAAK,SAAS,EAAE;GAC/C,IAAI,MAAM,MAAM,OAAO,CAAC;GACxB,MAAM,IAAI,gBAAgB,IAAI;GAC9B,MAAM,CAAC,GAAG,KAAK,cAAc,EAAE,GAAG,EAAE,GAAG,CAAC;GACxC,MAAM,QAAQ;IAAE,MAAM;IAAS,OAAO,KAAK,QAAQ,KAAK;GAAoB;GAC5E,MAAM,WAAW;IAAC,EAAE,KAAK;IAAG;IAAG;IAAG,EAAE,KAAK;IAAG,EAAE;IAAM,EAAE;GAAI;GAI1D,OAAO,CAAC,IAHyB,KAAK,aAAa,EAAG,KAAK,CAAC,EAAA,CAAG,KAAK,OAAO;IACzE,MAAM;IAAQ,MAAM,cAAc,GAAG,QAAQ;IAAG,QAAQ;KAAE;KAAO,OAAO;IAAY;GACtF,EACW,GAAU;IACnB,MAAM;IACN,MAAM,YAAY,GAAG,GAAG,EAAE,QAAQ,KAAK,MAAM,GAAG,EAAE,SAAS,KAAK,MAAM,CAAC;IACvE,QAAQ;KAAE;KAAO,OAAO;KAAa,MAAM;IAAW;GACxD,CAAC;EACH;CACF;AACF;;AAGA,SAAgB,0BACd,SACA,UACA,OACA,OACsB;CACtB,OAAO,cACC,6BAA6B;EACjC;EAAS;EAAU,OAAO,MAAM;EAAM,cAAc,MAAM;EAC1D,GAAI,QAAQ;GACV,aAAa,OAAe,uBAAuB,OAAO,EAAY;GACtE,kBAAkB,OAAmB;IACnC,MAAM,qBAAqB;KAAE,IAAI,QAAQ,IAAI,MAAM,MAAM,GAAG;IAAG;IAC/D,MAAM,WAAW,MAAM,UAAU,YAAY;IAC7C,MAAM,eAAe,MAAM,UAAU,UAAU,YAAY;IAC3D,aAAa;KAAE,SAAS;KAAG,aAAa;IAAG;GAC7C;EACF,IAAI,CAAC;CACP,CAAC,GACD;EAAC;EAAS;EAAU;EAAO;CAAK,CAClC;AACF"}
|
|
@@ -2,7 +2,7 @@ import { reachableInGroup } from "../../canvas/deps/enteredGroup.js";
|
|
|
2
2
|
import { createEmitter } from "@weasel-js/registry";
|
|
3
3
|
import { OUT_OF_SCOPE_ALPHA } from "@weasel-js/modes";
|
|
4
4
|
|
|
5
|
-
//#region src/
|
|
5
|
+
//#region src/domains/selection/enteredGroupScope.ts
|
|
6
6
|
/**
|
|
7
7
|
* The entered group as a scope: everything outside its subtree dims and stops taking hits, and
|
|
8
8
|
* so does the group itself, whose empty box is empty canvas while it is entered. With nothing
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enteredGroupScope.js","names":[],"sources":["../../../src/domains/selection/enteredGroupScope.ts"],"sourcesContent":["import { OUT_OF_SCOPE_ALPHA, type NodeScope } from '@weasel-js/modes';\nimport { createEmitter } from '@weasel-js/registry';\nimport { reachableInGroup, type EnteredGroupApi } from 'canvas/deps/enteredGroup';\nimport type { Scene } from 'core/scene/types';\n\n/**\n * The entered group as a scope: everything outside its subtree dims and stops taking hits, and\n * so does the group itself, whose empty box is empty canvas while it is entered. With nothing\n * entered every node is lit and live. Compose `alphaFor` into `<SceneCanvas alphaFor>` and the\n * scope into `redrawOn` to show it.\n */\nexport function createEnteredGroupScope(opts: {\n entered: EnteredGroupApi;\n scene: Pick<Scene<unknown, string, unknown>, 'ancestorsOf' | 'get'>;\n /** Alpha for a node outside the entered group. Default `OUT_OF_SCOPE_ALPHA`. */\n dimAlpha?: number;\n}): NodeScope {\n const { entered, scene } = opts;\n const dim = opts.dimAlpha ?? OUT_OF_SCOPE_ALPHA;\n const listeners = createEmitter();\n let version = 0;\n let stop: (() => void) | null = null;\n\n return {\n alphaFor: (id) => {\n const group = entered.get();\n return id === group || reachableInGroup(scene, group, id) ? 1 : dim;\n },\n isPointerInteractive: (id) => reachableInGroup(scene, entered.get(), id),\n getVersion: () => version,\n subscribe(fn) {\n if (stop === null) stop = entered.subscribe(() => { version++; listeners.emit(); });\n const off = listeners.subscribe(fn);\n return () => {\n off();\n if (listeners.size === 0) { stop?.(); stop = null; }\n };\n },\n };\n}\n"],"mappings":";;;;;;;;;;;AAWA,SAAgB,wBAAwB,MAK1B;CACZ,MAAM,EAAE,SAAS,UAAU;CAC3B,MAAM,MAAM,KAAK,YAAY;CAC7B,MAAM,YAAY,cAAc;CAChC,IAAI,UAAU;CACd,IAAI,OAA4B;CAEhC,OAAO;EACL,WAAW,OAAO;GAChB,MAAM,QAAQ,QAAQ,IAAI;GAC1B,OAAO,OAAO,SAAS,iBAAiB,OAAO,OAAO,EAAE,IAAI,IAAI;EAClE;EACA,uBAAuB,OAAO,iBAAiB,OAAO,QAAQ,IAAI,GAAG,EAAE;EACvE,kBAAkB;EAClB,UAAU,IAAI;GACZ,IAAI,SAAS,MAAM,OAAO,QAAQ,gBAAgB;IAAE;IAAW,UAAU,KAAK;GAAG,CAAC;GAClF,MAAM,MAAM,UAAU,UAAU,EAAE;GAClC,aAAa;IACX,IAAI;IACJ,IAAI,UAAU,SAAS,GAAG;KAAE,OAAO;KAAG,OAAO;IAAM;GACrD;EACF;CACF;AACF"}
|