@weasel-js/core 1.11.0 → 1.11.1
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 +427 -0
- package/README.md +1 -1
- package/dist/animation/index.js +3 -3
- package/dist/animation/rig/bindRig.js +1 -1
- package/dist/animation/rig/rigNode.js.map +1 -1
- package/dist/barrel/canvas.js +7 -3
- package/dist/barrel/interactions.js +5 -4
- package/dist/barrel/paths.js +3 -3
- package/dist/barrel/renderer.js +3 -2
- package/dist/barrel/routing.js +6 -11
- package/dist/barrel/scene.js +8 -5
- package/dist/barrel/viewport.js +4 -3
- package/dist/canvas/Canvas.js +3 -1
- package/dist/canvas/Canvas.js.map +1 -1
- package/dist/canvas/SceneCanvas/SceneCanvasInput.js +17 -3
- package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
- package/dist/canvas/SceneCanvas/builtinToolSets.js +30 -0
- package/dist/canvas/SceneCanvas/builtinToolSets.js.map +1 -0
- package/dist/canvas/SceneCanvas/canvasPassThrough.js +1 -0
- package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
- package/dist/canvas/SceneCanvas/coordTrace.js +5 -16
- package/dist/canvas/SceneCanvas/coordTrace.js.map +1 -1
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js +5 -0
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
- package/dist/canvas/SceneCanvas/features.js +45 -5
- package/dist/canvas/SceneCanvas/features.js.map +1 -1
- package/dist/canvas/SceneCanvas/props.js +1 -11
- package/dist/canvas/SceneCanvas/props.js.map +1 -1
- package/dist/canvas/SceneCanvas/toolRegistry.js +8 -4
- package/dist/canvas/SceneCanvas/toolRegistry.js.map +1 -1
- package/dist/canvas/SceneCanvas/useActionsPropResolver.js +1 -11
- package/dist/canvas/SceneCanvas/useActionsPropResolver.js.map +1 -1
- package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useBuiltinTools.js +2 -9
- package/dist/canvas/SceneCanvas/useBuiltinTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js +93 -0
- package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js.map +1 -0
- package/dist/canvas/SceneCanvas/useCanvasSelection.js +7 -2
- package/dist/canvas/SceneCanvas/useCanvasSelection.js.map +1 -1
- package/dist/canvas/SceneCanvas/useCanvasTools.js +8 -8
- package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useNodeKinds.js +1 -1
- package/dist/canvas/SceneCanvas/useNodeKinds.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +13 -2
- package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +1 -3
- package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneChrome.js +6 -7
- package/dist/canvas/SceneCanvas/useSceneChrome.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js +5 -3
- package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js.map +1 -1
- package/dist/canvas/SceneCanvas/useScenePicking.js +2 -3
- package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js +2 -1
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js +3 -4
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
- package/dist/canvas/SceneCanvas.js +16 -14
- package/dist/canvas/SceneCanvas.js.map +1 -1
- package/dist/canvas/SurfaceCanvas.js +5 -22
- package/dist/canvas/SurfaceCanvas.js.map +1 -1
- package/dist/canvas/canvasRenderer.js +9 -5
- package/dist/canvas/canvasRenderer.js.map +1 -1
- package/dist/canvas/contextAttributes.js +14 -0
- package/dist/canvas/contextAttributes.js.map +1 -0
- package/dist/canvas/deps/editAnchors.js +9 -113
- package/dist/canvas/deps/editAnchors.js.map +1 -1
- package/dist/canvas/deps/insert.js +4 -2
- package/dist/canvas/deps/insert.js.map +1 -1
- package/dist/canvas/deps/pathEditCommits.js +193 -0
- package/dist/canvas/deps/pathEditCommits.js.map +1 -0
- package/dist/canvas/deps/snap.js +12 -9
- package/dist/canvas/deps/snap.js.map +1 -1
- package/dist/canvas/deps/transformFollowers.js +20 -0
- package/dist/canvas/deps/transformFollowers.js.map +1 -0
- package/dist/canvas/derivedPath.js +20 -1
- package/dist/canvas/derivedPath.js.map +1 -1
- package/dist/canvas/drawnBounds.js +29 -3
- package/dist/canvas/drawnBounds.js.map +1 -1
- package/dist/canvas/nodeShapes/derivedPainter.js +9 -1
- package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/pathPainter.js +3 -2
- package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/shapePainter.js +1 -1
- package/dist/canvas/overlayRoleStrokes.js +12 -0
- package/dist/canvas/overlayRoleStrokes.js.map +1 -1
- package/dist/canvas/pathOverlay.js +27 -7
- package/dist/canvas/pathOverlay.js.map +1 -1
- package/dist/canvas/renderDebugSnapshot.js +1 -0
- package/dist/canvas/renderDebugSnapshot.js.map +1 -1
- package/dist/canvas/renderSceneToPixels.js +6 -5
- package/dist/canvas/renderSceneToPixels.js.map +1 -1
- package/dist/canvas/sceneAdapter.js.map +1 -1
- package/dist/canvas/useCanvasPaint.js +2 -1
- package/dist/canvas/useCanvasPaint.js.map +1 -1
- package/dist/canvas/useInFlightBounds.js +52 -0
- package/dist/canvas/useInFlightBounds.js.map +1 -0
- package/dist/canvas/useViewHelpers.js +7 -9
- package/dist/canvas/useViewHelpers.js.map +1 -1
- package/dist/clipboard.d.ts +3 -2
- package/dist/{clone-DZDrfIlU.d.ts → clone-C3e0OTuF.d.ts} +8 -21
- package/dist/clone.d.ts +4 -3
- package/dist/core/geometry/autoPoseDescriptor.js +1 -1
- package/dist/core/ops/setDependsOn.js +37 -0
- package/dist/core/ops/setDependsOn.js.map +1 -0
- package/dist/core/ops/writtenBy.js +53 -0
- package/dist/core/ops/writtenBy.js.map +1 -0
- package/dist/core/pages/fitViewToPage.js +12 -1
- package/dist/core/pages/fitViewToPage.js.map +1 -1
- package/dist/core/pages/pageEdits.js +164 -0
- package/dist/core/pages/pageEdits.js.map +1 -0
- package/dist/core/pages/pages.js +30 -1
- package/dist/core/pages/pages.js.map +1 -1
- package/dist/core/scene/NodeRouting.js +8 -3
- package/dist/core/scene/NodeRouting.js.map +1 -1
- package/dist/core/scene/arrivalWindow.js.map +1 -1
- package/dist/core/scene/carryDescendants.js +51 -0
- package/dist/core/scene/carryDescendants.js.map +1 -0
- package/dist/core/scene/docValues.js +57 -0
- package/dist/core/scene/docValues.js.map +1 -0
- package/dist/core/scene/editPass.js +111 -0
- package/dist/core/scene/editPass.js.map +1 -0
- package/dist/core/scene/effectivePose.js +7 -1
- package/dist/core/scene/effectivePose.js.map +1 -1
- package/dist/core/scene/fieldEdits.js +4 -1
- package/dist/core/scene/fieldEdits.js.map +1 -1
- package/dist/core/scene/historyFacade.js +1 -0
- package/dist/core/scene/historyFacade.js.map +1 -1
- package/dist/core/scene/layoutPass.js +28 -3
- package/dist/core/scene/layoutPass.js.map +1 -1
- package/dist/core/scene/recorder.js +28 -19
- package/dist/core/scene/recorder.js.map +1 -1
- package/dist/core/scene/scene.js +15 -9
- package/dist/core/scene/scene.js.map +1 -1
- package/dist/core/scene/sceneChanges.js +13 -0
- package/dist/core/scene/sceneChanges.js.map +1 -0
- package/dist/core/scene/sceneEdits.js +8 -1
- package/dist/core/scene/sceneEdits.js.map +1 -1
- package/dist/core/scene/sceneMutators.js +1 -0
- package/dist/core/scene/sceneMutators.js.map +1 -1
- package/dist/core/scene/sceneRecording.js +20 -3
- package/dist/core/scene/sceneRecording.js.map +1 -1
- package/dist/core/scene/sceneSelection.js +4 -1
- package/dist/core/scene/sceneSelection.js.map +1 -1
- package/dist/core/scene/sceneState.js +11 -5
- package/dist/core/scene/sceneState.js.map +1 -1
- package/dist/core/scene/serialization.js +5 -3
- package/dist/core/scene/serialization.js.map +1 -1
- package/dist/core/scene/types.js.map +1 -1
- package/dist/core/scene/unitScopes.js +19 -1
- package/dist/core/scene/unitScopes.js.map +1 -1
- package/dist/core/selection/usePublishSceneSelection.js +6 -12
- package/dist/core/selection/usePublishSceneSelection.js.map +1 -1
- package/dist/core/shapeKinds.js +25 -11
- package/dist/core/shapeKinds.js.map +1 -1
- package/dist/{cornerHandles-Camw_yeu.d.ts → cornerHandles-BHuoMs1o.d.ts} +1 -1
- package/dist/debug/createDebugOverlayLayer.js +16 -111
- package/dist/debug/createDebugOverlayLayer.js.map +1 -1
- package/dist/debug/createDebugSink.js +2 -1
- package/dist/debug/createDebugSink.js.map +1 -1
- package/dist/debug/defaultTheme.js +4 -2
- package/dist/debug/defaultTheme.js.map +1 -1
- package/dist/debug/features.js +17 -0
- package/dist/debug/features.js.map +1 -0
- package/dist/debug/overlayPanels.js +117 -0
- package/dist/debug/overlayPanels.js.map +1 -0
- package/dist/debug/parseDebugFlags.js +4 -13
- package/dist/debug/parseDebugFlags.js.map +1 -1
- package/dist/{depSchema-Cgfu_G9s.d.ts → depSchema-BpZNOv7q.d.ts} +114 -892
- package/dist/devtools/handlers.js +112 -0
- package/dist/devtools/handlers.js.map +1 -0
- package/dist/devtools/history.js +71 -0
- package/dist/devtools/history.js.map +1 -0
- package/dist/devtools/hook.js +74 -0
- package/dist/devtools/hook.js.map +1 -0
- package/dist/devtools/label.js +25 -0
- package/dist/devtools/label.js.map +1 -0
- package/dist/devtools/notices.js +58 -0
- package/dist/devtools/notices.js.map +1 -0
- package/dist/devtools/protocol.js +7 -0
- package/dist/devtools/protocol.js.map +1 -0
- package/dist/devtools/registry.js +90 -0
- package/dist/devtools/registry.js.map +1 -0
- package/dist/devtools/root.js +80 -0
- package/dist/devtools/root.js.map +1 -0
- package/dist/devtools/serialize.js +86 -0
- package/dist/devtools/serialize.js.map +1 -0
- package/dist/devtools/session.js +111 -0
- package/dist/devtools/session.js.map +1 -0
- package/dist/devtools/transport.js +67 -0
- package/dist/devtools/transport.js.map +1 -0
- package/dist/devtools/useDevtoolsRoot.js +81 -0
- package/dist/devtools/useDevtoolsRoot.js.map +1 -0
- package/dist/devtools-protocol.d.ts +435 -0
- package/dist/devtools-protocol.js +7 -0
- package/dist/devtools.d.ts +8 -0
- package/dist/devtools.js +12 -0
- package/dist/devtools.js.map +1 -0
- package/dist/features/chrome-caps/useHoverTracking.js +16 -10
- package/dist/features/chrome-caps/useHoverTracking.js.map +1 -1
- package/dist/features/ingestion/contentHandlers.js +25 -2
- package/dist/features/ingestion/contentHandlers.js.map +1 -1
- package/dist/features/measure/measureCommands.js +7 -5
- package/dist/features/measure/measureCommands.js.map +1 -1
- package/dist/features/paths/index.js +2 -2
- package/dist/features/paths/pathEditExtension.js.map +1 -1
- package/dist/features/paths/pathSnap/pathSnapSource.js +5 -5
- package/dist/features/paths/pathSnap/pathSnapSource.js.map +1 -1
- package/dist/features/paths/pathSnap/sceneIndex.js +10 -3
- package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
- package/dist/features/selection/selectionPose.js +4 -1
- package/dist/features/selection/selectionPose.js.map +1 -1
- package/dist/features/snap/gather.js +9 -1
- package/dist/features/snap/gather.js.map +1 -1
- package/dist/features/snap/lazySession.js +17 -0
- package/dist/features/snap/lazySession.js.map +1 -0
- package/dist/features/snap/marks.js.map +1 -1
- package/dist/features/snap/probes2d.js +27 -19
- package/dist/features/snap/probes2d.js.map +1 -1
- package/dist/features/snap/readoutLayer.js.map +1 -1
- package/dist/features/snap/roles.js +2 -1
- package/dist/features/snap/roles.js.map +1 -1
- package/dist/features/snap/rotationSource.js +25 -8
- package/dist/features/snap/rotationSource.js.map +1 -1
- package/dist/features/snap/sceneSession2d.js +6 -4
- package/dist/features/snap/sceneSession2d.js.map +1 -1
- package/dist/features/snap/session2d.js +37 -23
- package/dist/features/snap/session2d.js.map +1 -1
- package/dist/features/snap/spanSnap.js +5 -2
- package/dist/features/snap/spanSnap.js.map +1 -1
- package/dist/icons.table.js +1 -1
- package/dist/icons.table.js.map +1 -1
- package/dist/icons.table.json +0 -10
- package/dist/index.d.ts +701 -696
- package/dist/index.js +35 -29
- package/dist/interactions/actions/align/action.js +6 -4
- package/dist/interactions/actions/align/action.js.map +1 -1
- package/dist/interactions/actions/align/align.js.map +1 -1
- package/dist/interactions/actions/booleans/action.js +7 -1
- package/dist/interactions/actions/booleans/action.js.map +1 -1
- package/dist/interactions/actions/clone/action.js +1 -1
- package/dist/interactions/actions/clone/behaviors.js +4 -4
- package/dist/interactions/actions/clone/behaviors.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/createPaintAction.js +1 -1
- package/dist/interactions/actions/defaults/delete.js +48 -39
- package/dist/interactions/actions/defaults/delete.js.map +1 -1
- package/dist/interactions/actions/defaults/duplicate.js +1 -1
- package/dist/interactions/actions/defaults/group.js +2 -2
- package/dist/interactions/actions/defaults/ingest.js +7 -5
- package/dist/interactions/actions/defaults/ingest.js.map +1 -1
- package/dist/interactions/actions/defaults/nudge.js +13 -38
- package/dist/interactions/actions/defaults/nudge.js.map +1 -1
- package/dist/interactions/actions/distribute/action.js +6 -4
- package/dist/interactions/actions/distribute/action.js.map +1 -1
- package/dist/interactions/actions/drawnBoxes.js +2 -1
- package/dist/interactions/actions/drawnBoxes.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/action.js +3 -6
- package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/preview.js +41 -8
- package/dist/interactions/actions/edit-anchors/preview.js.map +1 -1
- package/dist/interactions/actions/flip/action.js +6 -4
- package/dist/interactions/actions/flip/action.js.map +1 -1
- package/dist/interactions/actions/gestureLifecycle.js +5 -2
- package/dist/interactions/actions/gestureLifecycle.js.map +1 -1
- package/dist/interactions/actions/insert/action.js +1 -2
- package/dist/interactions/actions/insert/action.js.map +1 -1
- package/dist/interactions/actions/insert/commit.js +1 -1
- package/dist/interactions/actions/insert/parent.js +11 -0
- package/dist/interactions/actions/insert/parent.js.map +1 -0
- package/dist/interactions/actions/insert/placeAction.js +7 -2
- package/dist/interactions/actions/insert/placeAction.js.map +1 -1
- package/dist/interactions/actions/insert/session.js +5 -1
- package/dist/interactions/actions/insert/session.js.map +1 -1
- package/dist/interactions/actions/move/action.js +8 -4
- package/dist/interactions/actions/move/action.js.map +1 -1
- package/dist/interactions/actions/move/behaviors/layoutDrop.js +0 -1
- package/dist/interactions/actions/move/behaviors/layoutDrop.js.map +1 -1
- package/dist/interactions/actions/move/behaviors/snapToContainer.js +1 -1
- package/dist/interactions/actions/move/capture.js +1 -1
- package/dist/interactions/actions/move/commit/index.js +0 -2
- package/dist/interactions/actions/move/commit/index.js.map +1 -1
- package/dist/interactions/actions/move/commit/layoutDrop.js +2 -2
- package/dist/interactions/actions/move/commit/layoutRelease.js +2 -2
- package/dist/interactions/actions/move/commit/reparent.js +11 -5
- package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
- package/dist/interactions/actions/move/frame.js +4 -10
- package/dist/interactions/actions/move/frame.js.map +1 -1
- package/dist/interactions/actions/move/landedDescendants.js +20 -17
- package/dist/interactions/actions/move/landedDescendants.js.map +1 -1
- package/dist/interactions/actions/move/layout/container.js +2 -1
- package/dist/interactions/actions/move/layout/container.js.map +1 -1
- package/dist/interactions/actions/move/layout/dragged.js +1 -1
- package/dist/interactions/actions/move/layout/dropPass.js +18 -1
- package/dist/interactions/actions/move/layout/dropPass.js.map +1 -1
- package/dist/interactions/actions/move/layout/exits.js +5 -5
- package/dist/interactions/actions/move/layout/exits.js.map +1 -1
- package/dist/interactions/actions/move/session.js +14 -3
- package/dist/interactions/actions/move/session.js.map +1 -1
- package/dist/interactions/actions/move/translate.js +8 -37
- package/dist/interactions/actions/move/translate.js.map +1 -1
- package/dist/interactions/actions/moveNodes.js +87 -0
- package/dist/interactions/actions/moveNodes.js.map +1 -0
- package/dist/interactions/actions/planeInput.js.map +1 -1
- package/dist/interactions/actions/resize/action.js +83 -72
- package/dist/interactions/actions/resize/action.js.map +1 -1
- package/dist/interactions/actions/resize/commit.js +2 -1
- package/dist/interactions/actions/resize/commit.js.map +1 -1
- package/dist/interactions/actions/resize/session.js +2 -0
- package/dist/interactions/actions/resize/session.js.map +1 -1
- package/dist/interactions/actions/resolvePreviews.js +3 -3
- package/dist/interactions/actions/resolvePreviews.js.map +1 -1
- package/dist/interactions/actions/rotate/action.js +7 -4
- package/dist/interactions/actions/rotate/action.js.map +1 -1
- package/dist/interactions/actions/rotate/commit.js +2 -1
- package/dist/interactions/actions/rotate/commit.js.map +1 -1
- package/dist/interactions/actions/rotate/session.js +2 -0
- package/dist/interactions/actions/rotate/session.js.map +1 -1
- package/dist/interactions/actions/selectionSubtree.js +13 -2
- package/dist/interactions/actions/selectionSubtree.js.map +1 -1
- package/dist/interactions/actions/testUtils.js.map +1 -1
- package/dist/interactions/actions/transform-again/action.js +5 -6
- package/dist/interactions/actions/transform-again/action.js.map +1 -1
- package/dist/interactions/actions/transformFinish.js +106 -0
- package/dist/interactions/actions/transformFinish.js.map +1 -0
- package/dist/kitSettingsSchema.js +0 -4
- package/dist/kitSettingsSchema.js.map +1 -1
- package/dist/move.d.ts +5 -4
- package/dist/{options-p9GZMXbY.d.ts → options-DBxEryYq.d.ts} +1 -1
- package/dist/resize.d.ts +4 -3
- package/dist/routing.d.ts +1 -1
- package/dist/routing.js +2 -2
- package/dist/test-hook/createTestHook.js +1 -1
- package/dist/test-hook/createTestHook.js.map +1 -1
- package/dist/test-hook/useTestHook.js +2 -1
- package/dist/test-hook/useTestHook.js.map +1 -1
- package/dist/test-seams.d.ts +9 -5
- package/dist/tools/builtin/pen/penHandle.js +3 -5
- package/dist/tools/builtin/pen/penHandle.js.map +1 -1
- package/dist/tools/index.js +26 -0
- package/dist/{types-CHVXGfYm.d.ts → types-7puAuVD2.d.ts} +3 -262
- package/dist/types-BN9hbdMI.d.ts +184 -0
- package/dist/types-CniVctmk.d.ts +949 -0
- package/dist/types-oFeQ5VjV.d.ts +264 -0
- package/dist/version.js +1 -1
- package/package.json +35 -25
- package/dist/canvas/SceneCanvas/useBuiltinRigTools.js +0 -65
- package/dist/canvas/SceneCanvas/useBuiltinRigTools.js.map +0 -1
- package/dist/tools/builtin/bone/boneActions.js +0 -156
- package/dist/tools/builtin/bone/boneActions.js.map +0 -1
- package/dist/tools/builtin/bone/boneOverlay.js +0 -71
- package/dist/tools/builtin/bone/boneOverlay.js.map +0 -1
- package/dist/tools/builtin/bone/placedRig.js +0 -28
- package/dist/tools/builtin/bone/placedRig.js.map +0 -1
- package/dist/tools/builtin/bone/rigEditState.js +0 -30
- package/dist/tools/builtin/bone/rigEditState.js.map +0 -1
- package/dist/tools/builtin/bone/useBoneTool.js +0 -125
- package/dist/tools/builtin/bone/useBoneTool.js.map +0 -1
- package/dist/tools/builtin/weight/useWeightTool.js +0 -101
- package/dist/tools/builtin/weight/useWeightTool.js.map +0 -1
- package/dist/tools/builtin/weight/weightActions.js +0 -140
- package/dist/tools/builtin/weight/weightActions.js.map +0 -1
- package/dist/tools/builtin/weight/weightOverlay.js +0 -78
- package/dist/tools/builtin/weight/weightOverlay.js.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { measurementOf } from "./measurement.js";
|
|
2
2
|
import { poseDescriptorOf } from "../../interactions/actions/poseDescriptorDep.js";
|
|
3
3
|
import { commitOps } from "../../interactions/actions/defaultCommitAdapter.js";
|
|
4
|
+
import { commitTransformOps, transformApplyOps } from "../../interactions/actions/transformFinish.js";
|
|
4
5
|
import { recordTransform } from "../../interactions/actions/transform-again/memory.js";
|
|
5
6
|
import { requiresSelection } from "../../interactions/actions/defaults/requiresSelection.js";
|
|
6
7
|
import { translateSelection } from "../../interactions/actions/defaults/nudge.js";
|
|
@@ -42,7 +43,8 @@ function moveBy(ms) {
|
|
|
42
43
|
"scene",
|
|
43
44
|
"applyOps",
|
|
44
45
|
"geometryProjection",
|
|
45
|
-
"poseDescriptor"
|
|
46
|
+
"poseDescriptor",
|
|
47
|
+
"transformFollowers"
|
|
46
48
|
],
|
|
47
49
|
enabled: transformable(ms),
|
|
48
50
|
invoker: {
|
|
@@ -52,7 +54,7 @@ function moveBy(ms) {
|
|
|
52
54
|
const { selection, scene } = deps;
|
|
53
55
|
if (!c || !selection || !scene) return;
|
|
54
56
|
const { dx, dy } = c.m;
|
|
55
|
-
if (translateSelection(selection, scene, dx, dy, deps
|
|
57
|
+
if (translateSelection(selection, scene, dx, dy, transformApplyOps(deps), deps.geometryProjection, poseDescriptorOf(deps.poseDescriptor), MOVE_LABEL)) recordTransform(scene, {
|
|
56
58
|
kind: "translate",
|
|
57
59
|
dx,
|
|
58
60
|
dy
|
|
@@ -72,7 +74,8 @@ function rotateBy(ms) {
|
|
|
72
74
|
"scene",
|
|
73
75
|
"applyOps",
|
|
74
76
|
"poseDescriptor",
|
|
75
|
-
"view"
|
|
77
|
+
"view",
|
|
78
|
+
"transformFollowers"
|
|
76
79
|
],
|
|
77
80
|
enabled: transformable(ms),
|
|
78
81
|
invoker: {
|
|
@@ -85,8 +88,7 @@ function rotateBy(ms) {
|
|
|
85
88
|
const selected = selection.get();
|
|
86
89
|
const turn = -c.m.angle;
|
|
87
90
|
const ops = rotateSelectionOps(scene, selected, deps, turn, "union", ROTATE_LABEL);
|
|
88
|
-
if (ops
|
|
89
|
-
commitOps(deps, ops, ROTATE_LABEL);
|
|
91
|
+
if (!commitTransformOps(deps, ops, ROTATE_LABEL)) return;
|
|
90
92
|
recordTransform(scene, {
|
|
91
93
|
kind: "rotate",
|
|
92
94
|
angle: turn,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"measureCommands.js","names":[],"sources":["../../../src/features/measure/measureCommands.ts"],"sourcesContent":["import type { Action, ActionDeps } from '@weasel-js/routing';\nimport { ActionDisabledReason } from '@weasel-js/routing';\nimport type { NodeId, Scene } from 'core/scene/types';\nimport { translateSelection } from 'interactions/actions/defaults/nudge';\nimport { requiresSelection } from 'interactions/actions/defaults/requiresSelection';\nimport { commitOps } from 'interactions/actions/defaultCommitAdapter';\nimport { poseDescriptorOf } from 'interactions/actions/poseDescriptorDep';\nimport { rotateSelectionOps } from 'interactions/actions/rotate/replay';\nimport { recordTransform } from 'interactions/actions/transform-again/memory';\nimport { measurementOf, type Measurement } from './measurement';\nimport { currentMeasureId, measureIds, type Measurements } from './measurements';\nimport { deleteMeasureOps, MEASURE_LABELS } from './measureEdits';\nimport { MEASURE_ACTION_IDS } from './actionIds';\n\nconst MOVE_LABEL = 'Move by Measurement';\nconst ROTATE_LABEL = 'Rotate by Measurement';\n\ntype AnyScene = Scene<unknown, string, unknown>;\n\nfunction current(deps: ActionDeps | undefined, ms: Measurements): { id: NodeId; m: Measurement } | null {\n const scene = deps?.scene as AnyScene | undefined;\n if (!scene) return null;\n const id = currentMeasureId(scene, ms);\n const m = id === null ? null : measurementOf(scene.get(id));\n return id !== null && m ? { id, m } : null;\n}\n\n/** Enabled with a measurement to apply and a selection to apply it to. */\nfunction transformable(ms: Measurements) {\n return (deps?: ActionDeps) => {\n const c = current(deps, ms);\n if (!c || c.m.distance === 0) return ActionDisabledReason.NotApplicable;\n return requiresSelection(deps);\n };\n}\n\nfunction moveBy(ms: Measurements): Action {\n return {\n id: MEASURE_ACTION_IDS.moveBy,\n label: MOVE_LABEL,\n group: 'measure',\n eligible: { capability: 'transforms-selection' },\n requires: ['selection', 'scene', 'applyOps', 'geometryProjection', 'poseDescriptor'],\n enabled: transformable(ms),\n invoker: {\n timing: 'immediate',\n run: (deps) => {\n const c = current(deps, ms);\n const { selection, scene } = deps;\n if (!c || !selection || !scene) return;\n const { dx, dy } = c.m;\n const moved = translateSelection(\n selection, scene, dx, dy, deps
|
|
1
|
+
{"version":3,"file":"measureCommands.js","names":[],"sources":["../../../src/features/measure/measureCommands.ts"],"sourcesContent":["import type { Action, ActionDeps } from '@weasel-js/routing';\nimport { ActionDisabledReason } from '@weasel-js/routing';\nimport type { NodeId, Scene } from 'core/scene/types';\nimport { translateSelection } from 'interactions/actions/defaults/nudge';\nimport { requiresSelection } from 'interactions/actions/defaults/requiresSelection';\nimport { commitOps } from 'interactions/actions/defaultCommitAdapter';\nimport { commitTransformOps, transformApplyOps } from 'interactions/actions/transformFinish';\nimport { poseDescriptorOf } from 'interactions/actions/poseDescriptorDep';\nimport { rotateSelectionOps } from 'interactions/actions/rotate/replay';\nimport { recordTransform } from 'interactions/actions/transform-again/memory';\nimport { measurementOf, type Measurement } from './measurement';\nimport { currentMeasureId, measureIds, type Measurements } from './measurements';\nimport { deleteMeasureOps, MEASURE_LABELS } from './measureEdits';\nimport { MEASURE_ACTION_IDS } from './actionIds';\n\nconst MOVE_LABEL = 'Move by Measurement';\nconst ROTATE_LABEL = 'Rotate by Measurement';\n\ntype AnyScene = Scene<unknown, string, unknown>;\n\nfunction current(deps: ActionDeps | undefined, ms: Measurements): { id: NodeId; m: Measurement } | null {\n const scene = deps?.scene as AnyScene | undefined;\n if (!scene) return null;\n const id = currentMeasureId(scene, ms);\n const m = id === null ? null : measurementOf(scene.get(id));\n return id !== null && m ? { id, m } : null;\n}\n\n/** Enabled with a measurement to apply and a selection to apply it to. */\nfunction transformable(ms: Measurements) {\n return (deps?: ActionDeps) => {\n const c = current(deps, ms);\n if (!c || c.m.distance === 0) return ActionDisabledReason.NotApplicable;\n return requiresSelection(deps);\n };\n}\n\nfunction moveBy(ms: Measurements): Action {\n return {\n id: MEASURE_ACTION_IDS.moveBy,\n label: MOVE_LABEL,\n group: 'measure',\n eligible: { capability: 'transforms-selection' },\n requires: ['selection', 'scene', 'applyOps', 'geometryProjection', 'poseDescriptor', 'transformFollowers'],\n enabled: transformable(ms),\n invoker: {\n timing: 'immediate',\n run: (deps) => {\n const c = current(deps, ms);\n const { selection, scene } = deps;\n if (!c || !selection || !scene) return;\n const { dx, dy } = c.m;\n const moved = translateSelection(\n selection, scene, dx, dy, transformApplyOps(deps), deps.geometryProjection,\n poseDescriptorOf(deps.poseDescriptor), MOVE_LABEL,\n );\n if (moved) recordTransform(scene, { kind: 'translate', dx, dy }, selection.get());\n },\n },\n };\n}\n\nfunction rotateBy(ms: Measurements): Action {\n return {\n id: MEASURE_ACTION_IDS.rotateBy,\n label: ROTATE_LABEL,\n group: 'measure',\n eligible: { capability: 'transforms-selection' },\n requires: ['selection', 'scene', 'applyOps', 'poseDescriptor', 'view', 'transformFollowers'],\n enabled: transformable(ms),\n invoker: {\n timing: 'immediate',\n run: (deps) => {\n const c = current(deps, ms);\n const { selection } = deps;\n const scene = deps.scene as AnyScene | undefined;\n if (!c || !selection || !scene) return;\n const selected = selection.get() as string[];\n // The measurement reads counterclockwise-positive; the canvas turns clockwise-positive.\n const turn = -c.m.angle;\n const ops = rotateSelectionOps(\n scene, selected, deps, turn, 'union', ROTATE_LABEL,\n );\n if (!commitTransformOps(deps, ops, ROTATE_LABEL)) return;\n recordTransform(scene, { kind: 'rotate', angle: turn, pivot: 'union' }, selected);\n },\n },\n };\n}\n\nfunction remove(ms: Measurements): Action {\n return {\n id: MEASURE_ACTION_IDS.delete,\n label: MEASURE_LABELS.delete,\n group: 'measure',\n requires: ['scene', 'applyOps', 'selection'],\n // Declining with nothing measured leaves Delete to the ambient `delete`.\n enabled: (deps) => (current(deps, ms) ? true : ActionDisabledReason.NotApplicable),\n invoker: {\n timing: 'immediate',\n run: (deps) => {\n const c = current(deps, ms);\n if (!c) return;\n commitOps(deps, deleteMeasureOps(deps.scene as AnyScene, [c.id], MEASURE_LABELS.delete), MEASURE_LABELS.delete);\n if (ms.picked.get() === c.id) ms.picked.set(null);\n },\n },\n };\n}\n\nfunction clearAll(ms: Measurements): Action {\n const all = (deps: ActionDeps | undefined) => {\n const scene = deps?.scene as AnyScene | undefined;\n return scene ? measureIds(scene, ms) : [];\n };\n return {\n id: MEASURE_ACTION_IDS.clear,\n label: MEASURE_LABELS.clear,\n group: 'measure',\n requires: ['scene', 'applyOps', 'selection'],\n enabled: (deps) => (all(deps).length > 0 ? true : ActionDisabledReason.NotApplicable),\n invoker: {\n timing: 'immediate',\n run: (deps) => {\n const ids = all(deps);\n if (ids.length === 0) return;\n commitOps(deps, deleteMeasureOps(deps.scene as AnyScene, ids, MEASURE_LABELS.clear), MEASURE_LABELS.clear);\n ms.picked.set(null);\n },\n },\n };\n}\n\nfunction unpick(ms: Measurements): Action {\n return {\n id: MEASURE_ACTION_IDS.unpick,\n label: 'Deselect Measurement',\n // Declining with nothing picked leaves Escape to the ambient `escape`.\n enabled: () => (ms.picked.get() !== null ? true : ActionDisabledReason.NotApplicable),\n invoker: { timing: 'immediate', run: () => ms.picked.set(null) },\n };\n}\n\n/**\n * The commands over a scene's measurements: move the content selection by\n * the current measurement's offset, or turn it about its center by the\n * measured angle — each one undo step Transform Again can repeat — delete the\n * current measurement, clear them all, and drop the pick. The current\n * measurement is the picked one, else the newest. All but the pick's are in\n * the `measure` group.\n */\nexport function measureCommands(ms: Measurements): Action[] {\n return [moveBy(ms), rotateBy(ms), remove(ms), clearAll(ms), unpick(ms)];\n}\n"],"mappings":";;;;;;;;;;;;;;AAeA,IAAM,aAAa;AACnB,IAAM,eAAe;AAIrB,SAAS,QAAQ,MAA8B,IAAyD;CACtG,MAAM,QAAQ,MAAM;CACpB,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,KAAK,iBAAiB,OAAO,EAAE;CACrC,MAAM,IAAI,OAAO,OAAO,OAAO,cAAc,MAAM,IAAI,EAAE,CAAC;CAC1D,OAAO,OAAO,QAAQ,IAAI;EAAE;EAAI;CAAE,IAAI;AACxC;;AAGA,SAAS,cAAc,IAAkB;CACvC,QAAQ,SAAsB;EAC5B,MAAM,IAAI,QAAQ,MAAM,EAAE;EAC1B,IAAI,CAAC,KAAK,EAAE,EAAE,aAAa,GAAG,OAAO,qBAAqB;EAC1D,OAAO,kBAAkB,IAAI;CAC/B;AACF;AAEA,SAAS,OAAO,IAA0B;CACxC,OAAO;EACL,IAAI,mBAAmB;EACvB,OAAO;EACP,OAAO;EACP,UAAU,EAAE,YAAY,uBAAuB;EAC/C,UAAU;GAAC;GAAa;GAAS;GAAY;GAAsB;GAAkB;EAAoB;EACzG,SAAS,cAAc,EAAE;EACzB,SAAS;GACP,QAAQ;GACR,MAAM,SAAS;IACb,MAAM,IAAI,QAAQ,MAAM,EAAE;IAC1B,MAAM,EAAE,WAAW,UAAU;IAC7B,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO;IAChC,MAAM,EAAE,IAAI,OAAO,EAAE;IAKrB,IAJc,mBACZ,WAAW,OAAO,IAAI,IAAI,kBAAkB,IAAI,GAAG,KAAK,oBACxD,iBAAiB,KAAK,cAAc,GAAG,UAErC,GAAO,gBAAgB,OAAO;KAAE,MAAM;KAAa;KAAI;IAAG,GAAG,UAAU,IAAI,CAAC;GAClF;EACF;CACF;AACF;AAEA,SAAS,SAAS,IAA0B;CAC1C,OAAO;EACL,IAAI,mBAAmB;EACvB,OAAO;EACP,OAAO;EACP,UAAU,EAAE,YAAY,uBAAuB;EAC/C,UAAU;GAAC;GAAa;GAAS;GAAY;GAAkB;GAAQ;EAAoB;EAC3F,SAAS,cAAc,EAAE;EACzB,SAAS;GACP,QAAQ;GACR,MAAM,SAAS;IACb,MAAM,IAAI,QAAQ,MAAM,EAAE;IAC1B,MAAM,EAAE,cAAc;IACtB,MAAM,QAAQ,KAAK;IACnB,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO;IAChC,MAAM,WAAW,UAAU,IAAI;IAE/B,MAAM,OAAO,CAAC,EAAE,EAAE;IAClB,MAAM,MAAM,mBACV,OAAO,UAAU,MAAM,MAAM,SAAS,YACxC;IACA,IAAI,CAAC,mBAAmB,MAAM,KAAK,YAAY,GAAG;IAClD,gBAAgB,OAAO;KAAE,MAAM;KAAU,OAAO;KAAM,OAAO;IAAQ,GAAG,QAAQ;GAClF;EACF;CACF;AACF;AAEA,SAAS,OAAO,IAA0B;CACxC,OAAO;EACL,IAAI,mBAAmB;EACvB,OAAO,eAAe;EACtB,OAAO;EACP,UAAU;GAAC;GAAS;GAAY;EAAW;EAE3C,UAAU,SAAU,QAAQ,MAAM,EAAE,IAAI,OAAO,qBAAqB;EACpE,SAAS;GACP,QAAQ;GACR,MAAM,SAAS;IACb,MAAM,IAAI,QAAQ,MAAM,EAAE;IAC1B,IAAI,CAAC,GAAG;IACR,UAAU,MAAM,iBAAiB,KAAK,OAAmB,CAAC,EAAE,EAAE,GAAG,eAAe,MAAM,GAAG,eAAe,MAAM;IAC9G,IAAI,GAAG,OAAO,IAAI,MAAM,EAAE,IAAI,GAAG,OAAO,IAAI,IAAI;GAClD;EACF;CACF;AACF;AAEA,SAAS,SAAS,IAA0B;CAC1C,MAAM,OAAO,SAAiC;EAC5C,MAAM,QAAQ,MAAM;EACpB,OAAO,QAAQ,WAAW,OAAO,EAAE,IAAI,CAAC;CAC1C;CACA,OAAO;EACL,IAAI,mBAAmB;EACvB,OAAO,eAAe;EACtB,OAAO;EACP,UAAU;GAAC;GAAS;GAAY;EAAW;EAC3C,UAAU,SAAU,IAAI,IAAI,CAAC,CAAC,SAAS,IAAI,OAAO,qBAAqB;EACvE,SAAS;GACP,QAAQ;GACR,MAAM,SAAS;IACb,MAAM,MAAM,IAAI,IAAI;IACpB,IAAI,IAAI,WAAW,GAAG;IACtB,UAAU,MAAM,iBAAiB,KAAK,OAAmB,KAAK,eAAe,KAAK,GAAG,eAAe,KAAK;IACzG,GAAG,OAAO,IAAI,IAAI;GACpB;EACF;CACF;AACF;AAEA,SAAS,OAAO,IAA0B;CACxC,OAAO;EACL,IAAI,mBAAmB;EACvB,OAAO;EAEP,eAAgB,GAAG,OAAO,IAAI,MAAM,OAAO,OAAO,qBAAqB;EACvE,SAAS;GAAE,QAAQ;GAAa,WAAW,GAAG,OAAO,IAAI,IAAI;EAAE;CACjE;AACF;;;;;;;;;AAUA,SAAgB,gBAAgB,IAA4B;CAC1D,OAAO;EAAC,OAAO,EAAE;EAAG,SAAS,EAAE;EAAG,OAAO,EAAE;EAAG,SAAS,EAAE;EAAG,OAAO,EAAE;CAAC;AACxE"}
|
|
@@ -5,9 +5,9 @@ import { PATH_SNAP_LABELS, pathSnapGlyph } from "./pathSnap/glyphs.js";
|
|
|
5
5
|
import { createPathEditingOverlayLayer } from "./pathEditingOverlayLayer.js";
|
|
6
6
|
import { createPathLayer } from "./pathLayer.js";
|
|
7
7
|
import { pathOriginProjection } from "./originProjection.js";
|
|
8
|
-
import { CORNER_FIT_TOLERANCE, CORNER_KEYWORDS, DEFAULT_FLATTEN_TOLERANCE, PATH_C as PATH_C$1, PATH_CMD_LENGTHS, PATH_L as PATH_L$1, PATH_M as PATH_M$1, PATH_Q as PATH_Q$1, PATH_SNAP_KINDS as PATH_SNAP_KINDS$1, PATH_Z as PATH_Z$1, PathBuilder, asPolygonPath as asPolygonPath$1, averageAnchorsAt as averageAnchorsAt$1, bendAlongPath as bendAlongPath$1, blendPaths, boundsOfPath as boundsOfPath$1, circlePath as circlePath$1, composePath, cornerAt, cornerFrameAt, cornerFrames, cornerKeywordOf, cornerShapeK, cornerShapePieces, cornerSpecAt, countPathAnchors as countPathAnchors$1, createPathSnapIndex as createPathSnapIndex$1, decomposePath, ellipsePath as ellipsePath$1, extendToMeet, findPathSnap as findPathSnap$1, flattenCubic, flattenCubicWithArcLen, flattenQuadratic, flattenQuadraticWithArcLen, hasCorners, hasOpenSubpath as hasOpenSubpath$1, intersectSegments, isCornerShape, joinAnchorsAt as joinAnchorsAt$1, lengthenOpenEnd, linePath as linePath$1, measurePath as measurePath$1, nextCornerShape, openEndRay, openPathEnds, pathCommandCoordCount, pathContainsPoint as pathContainsPoint$1, pathContainsPolygon, pathContainsRect, pathCrossings, pathDistanceToPoint, pathFromD as pathFromD$1, pathInPoseFrame as pathInPoseFrame$1, pathInWorld as pathInWorld$1, pathIntersectsPolygon, pathIntersectsRect as pathIntersectsRect$1, pathSegments, pathSignedArea, pathThroughPoseFrame as pathThroughPoseFrame$1, pathToAnchors as pathToAnchors$1, pointAlongPath, pointInPath as pointInPath$1, polygonFromPoints as polygonFromPoints$1, polylineFromPoints as polylineFromPoints$1, poseFrameInverseOf as poseFrameInverseOf$1, poseFrameOf as poseFrameOf$1, poseLinearOf as poseLinearOf$1, poseRotationOf, prepareBlend as prepareBlend$1, rectMarkerPath as rectMarkerPath$1, rectPath as rectPath$1, regularPolygonPath as regularPolygonPath$1, remapPerAnchor, repeatAlongPath as repeatAlongPath$1, resolveCorners as resolveCorners$1, resolveCornersWithSources, reversePath, rotatePathAround as rotatePathAround$1, roundRectPath, scalePathToBounds, simplifyAnchorsAt, simplifyPath as simplifyPath$1, smudgePath, splitByClosure, splitSubpaths, squarePath as squarePath$1, starPath as starPath$1, stationsAlongPath, transformPath as transformPath$1, translatePath as translatePath$1, trimPathAt, unionBoundsPath, withCorner, withoutCorners, worldEditToStorage as worldEditToStorage$1 } from "@weasel-js/geom";
|
|
8
|
+
import { CORNER_FIT_TOLERANCE, CORNER_KEYWORDS, DEFAULT_FLATTEN_TOLERANCE, PATH_C as PATH_C$1, PATH_CMD_LENGTHS, PATH_L as PATH_L$1, PATH_M as PATH_M$1, PATH_Q as PATH_Q$1, PATH_SNAP_KINDS as PATH_SNAP_KINDS$1, PATH_Z as PATH_Z$1, PathBuilder, asPolygonPath as asPolygonPath$1, averageAnchorsAt as averageAnchorsAt$1, bendAlongPath as bendAlongPath$1, blendPaths, boundsOfPath as boundsOfPath$1, circlePath as circlePath$1, composePath, cornerAt, cornerFrameAt, cornerFrames, cornerKeywordOf, cornerShapeK, cornerShapePieces, cornerSpecAt, countPathAnchors as countPathAnchors$1, createPathSnapIndex as createPathSnapIndex$1, decomposePath, ellipsePath as ellipsePath$1, extendToMeet, findPathSnap as findPathSnap$1, flattenCubic, flattenCubicWithArcLen, flattenQuadratic, flattenQuadraticWithArcLen, hasCorners, hasOpenSubpath as hasOpenSubpath$1, intersectSegments, isCornerShape, joinAnchorsAt as joinAnchorsAt$1, lengthenOpenEnd, linePath as linePath$1, measurePath as measurePath$1, nextCornerShape, openEndRay, openPathEnds, pathCommandCoordCount, pathContainsPoint as pathContainsPoint$1, pathContainsPolygon, pathContainsRect, pathCrossings, pathDistanceToPoint, pathFromD as pathFromD$1, pathInPoseFrame as pathInPoseFrame$1, pathInWorld as pathInWorld$1, pathIntersectsPolygon, pathIntersectsRect as pathIntersectsRect$1, pathSegments, pathSignedArea, pathThroughPoseFrame as pathThroughPoseFrame$1, pathToAnchors as pathToAnchors$1, pathToD, pointAlongPath, pointInPath as pointInPath$1, polygonFromPoints as polygonFromPoints$1, polylineFromPoints as polylineFromPoints$1, poseFrameInverseOf as poseFrameInverseOf$1, poseFrameOf as poseFrameOf$1, poseLinearOf as poseLinearOf$1, poseRotationOf, prepareBlend as prepareBlend$1, rectMarkerPath as rectMarkerPath$1, rectPath as rectPath$1, regularPolygonPath as regularPolygonPath$1, remapPerAnchor, repeatAlongPath as repeatAlongPath$1, resolveCorners as resolveCorners$1, resolveCornersWithSources, reversePath, rotatePathAround as rotatePathAround$1, roundRectPath, scalePathToBounds, simplifyAnchorsAt, simplifyPath as simplifyPath$1, smudgePath, splitByClosure, splitSubpaths, squarePath as squarePath$1, starPath as starPath$1, stationsAlongPath, transformPath as transformPath$1, translatePath as translatePath$1, trimPathAt, unionBoundsPath, withCorner, withoutCorners, worldEditToStorage as worldEditToStorage$1 } from "@weasel-js/geom";
|
|
9
9
|
import { offsetPath as offsetPath$1, strokeToPath as strokeToPath$1 } from "@weasel-js/stroke";
|
|
10
10
|
import { pathDivide, pathExclude as pathExclude$1, pathIntersect as pathIntersect$1, pathSubtract as pathSubtract$1, pathUnion as pathUnion$1, snipPathByPolyline as snipPathByPolyline$1, snipPathsAtCrossings as snipPathsAtCrossings$1, splitPathByPolyline as splitPathByPolyline$1, splitPathBySegment, trianglesToPath } from "@weasel-js/geom/booleans";
|
|
11
11
|
import "@weasel-js/geom/curves";
|
|
12
12
|
|
|
13
|
-
export { CORNER_FIT_TOLERANCE, CORNER_KEYWORDS, DEFAULT_FLATTEN_TOLERANCE, GEOMETRY_BOUND_DATA, PATH_C$1 as PATH_C, PATH_CMD_LENGTHS, PATH_L$1 as PATH_L, PATH_M$1 as PATH_M, PATH_Q$1 as PATH_Q, PATH_SNAP_KINDS$1 as PATH_SNAP_KINDS, PATH_SNAP_LABELS, PATH_Z$1 as PATH_Z, PathBuilder, asPolygonPath$1 as asPolygonPath, averageAnchorsAt$1 as averageAnchorsAt, bendAlongPath$1 as bendAlongPath, blendPaths, boundsOfPath$1 as boundsOfPath, circlePath$1 as circlePath, composePath, cornerAt, cornerFrameAt, cornerFrames, cornerKeywordOf, cornerShapeK, cornerShapePieces, cornerSpecAt, countPathAnchors$1 as countPathAnchors, createPathEditingOverlayLayer, createPathLayer, createPathSnapIndex$1 as createPathSnapIndex, createPenPreviewLayer, decomposePath, ellipsePath$1 as ellipsePath, extendToMeet, findPathSnap$1 as findPathSnap, flattenCubic, flattenCubicWithArcLen, flattenQuadratic, flattenQuadraticWithArcLen, hasCorners, hasOpenSubpath$1 as hasOpenSubpath, intersectSegments, isCornerShape, joinAnchorsAt$1 as joinAnchorsAt, lengthenOpenEnd, linePath$1 as linePath, measurePath$1 as measurePath, nextCornerShape, offsetPath$1 as offsetPath, openEndRay, openPathEnds, pathCommandCoordCount, pathContainsPoint$1 as pathContainsPoint, pathContainsPolygon, pathContainsRect, pathCrossings, pathDistanceToPoint, pathDivide, pathExclude$1 as pathExclude, pathFromD$1 as pathFromD, pathInPoseFrame$1 as pathInPoseFrame, pathInWorld$1 as pathInWorld, pathIntersect$1 as pathIntersect, pathIntersectsPolygon, pathIntersectsRect$1 as pathIntersectsRect, pathOriginProjection, pathPoseDescriptor, pathSegments, pathSignedArea, pathSnapGlyph, pathSubtract$1 as pathSubtract, pathThroughPoseFrame$1 as pathThroughPoseFrame, pathToAnchors$1 as pathToAnchors, pathUnion$1 as pathUnion, pointAlongPath, pointInPath$1 as pointInPath, polygonFromPoints$1 as polygonFromPoints, polylineFromPoints$1 as polylineFromPoints, poseFrameInverseOf$1 as poseFrameInverseOf, poseFrameOf$1 as poseFrameOf, poseLinearOf$1 as poseLinearOf, poseRotationOf, prepareBlend$1 as prepareBlend, rectMarkerPath$1 as rectMarkerPath, rectPath$1 as rectPath, regularPolygonPath$1 as regularPolygonPath, remapPerAnchor, repeatAlongPath$1 as repeatAlongPath, resolveCorners$1 as resolveCorners, resolveCornersWithSources, reversePath, rotatePathAround$1 as rotatePathAround, roundRectPath, scalePathToBounds, simplifyAnchorsAt, simplifyPath$1 as simplifyPath, smudgePath, snipPathByPolyline$1 as snipPathByPolyline, snipPathsAtCrossings$1 as snipPathsAtCrossings, splitByClosure, splitPathByPolyline$1 as splitPathByPolyline, splitPathBySegment, splitSubpaths, squarePath$1 as squarePath, starPath$1 as starPath, stationsAlongPath, strokeToPath$1 as strokeToPath, transformPath$1 as transformPath, translatePath$1 as translatePath, trianglesToPath, trimPathAt, unionBoundsPath, withCorner, withoutCorners, withoutGeometryBoundData, worldEditToStorage$1 as worldEditToStorage };
|
|
13
|
+
export { CORNER_FIT_TOLERANCE, CORNER_KEYWORDS, DEFAULT_FLATTEN_TOLERANCE, GEOMETRY_BOUND_DATA, PATH_C$1 as PATH_C, PATH_CMD_LENGTHS, PATH_L$1 as PATH_L, PATH_M$1 as PATH_M, PATH_Q$1 as PATH_Q, PATH_SNAP_KINDS$1 as PATH_SNAP_KINDS, PATH_SNAP_LABELS, PATH_Z$1 as PATH_Z, PathBuilder, asPolygonPath$1 as asPolygonPath, averageAnchorsAt$1 as averageAnchorsAt, bendAlongPath$1 as bendAlongPath, blendPaths, boundsOfPath$1 as boundsOfPath, circlePath$1 as circlePath, composePath, cornerAt, cornerFrameAt, cornerFrames, cornerKeywordOf, cornerShapeK, cornerShapePieces, cornerSpecAt, countPathAnchors$1 as countPathAnchors, createPathEditingOverlayLayer, createPathLayer, createPathSnapIndex$1 as createPathSnapIndex, createPenPreviewLayer, decomposePath, ellipsePath$1 as ellipsePath, extendToMeet, findPathSnap$1 as findPathSnap, flattenCubic, flattenCubicWithArcLen, flattenQuadratic, flattenQuadraticWithArcLen, hasCorners, hasOpenSubpath$1 as hasOpenSubpath, intersectSegments, isCornerShape, joinAnchorsAt$1 as joinAnchorsAt, lengthenOpenEnd, linePath$1 as linePath, measurePath$1 as measurePath, nextCornerShape, offsetPath$1 as offsetPath, openEndRay, openPathEnds, pathCommandCoordCount, pathContainsPoint$1 as pathContainsPoint, pathContainsPolygon, pathContainsRect, pathCrossings, pathDistanceToPoint, pathDivide, pathExclude$1 as pathExclude, pathFromD$1 as pathFromD, pathInPoseFrame$1 as pathInPoseFrame, pathInWorld$1 as pathInWorld, pathIntersect$1 as pathIntersect, pathIntersectsPolygon, pathIntersectsRect$1 as pathIntersectsRect, pathOriginProjection, pathPoseDescriptor, pathSegments, pathSignedArea, pathSnapGlyph, pathSubtract$1 as pathSubtract, pathThroughPoseFrame$1 as pathThroughPoseFrame, pathToAnchors$1 as pathToAnchors, pathToD, pathUnion$1 as pathUnion, pointAlongPath, pointInPath$1 as pointInPath, polygonFromPoints$1 as polygonFromPoints, polylineFromPoints$1 as polylineFromPoints, poseFrameInverseOf$1 as poseFrameInverseOf, poseFrameOf$1 as poseFrameOf, poseLinearOf$1 as poseLinearOf, poseRotationOf, prepareBlend$1 as prepareBlend, rectMarkerPath$1 as rectMarkerPath, rectPath$1 as rectPath, regularPolygonPath$1 as regularPolygonPath, remapPerAnchor, repeatAlongPath$1 as repeatAlongPath, resolveCorners$1 as resolveCorners, resolveCornersWithSources, reversePath, rotatePathAround$1 as rotatePathAround, roundRectPath, scalePathToBounds, simplifyAnchorsAt, simplifyPath$1 as simplifyPath, smudgePath, snipPathByPolyline$1 as snipPathByPolyline, snipPathsAtCrossings$1 as snipPathsAtCrossings, splitByClosure, splitPathByPolyline$1 as splitPathByPolyline, splitPathBySegment, splitSubpaths, squarePath$1 as squarePath, starPath$1 as starPath, stationsAlongPath, strokeToPath$1 as strokeToPath, transformPath$1 as transformPath, translatePath$1 as translatePath, trianglesToPath, trimPathAt, unionBoundsPath, withCorner, withoutCorners, withoutGeometryBoundData, worldEditToStorage$1 as worldEditToStorage };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pathEditExtension.js","names":[],"sources":["../../../src/features/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 } 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\ntype Point = { x: number; y: number };\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: Point, move: PathEditHandleMove): Point;\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/** 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 /** 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":";;;;;;;AAgMA,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
|
+
{"version":3,"file":"pathEditExtension.js","names":[],"sources":["../../../src/features/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 } 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\ntype Point = { x: number; y: number };\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: Point, move: PathEditHandleMove): Point;\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":";;;;;;;AAmNA,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"}
|
|
@@ -57,14 +57,14 @@ function pathSnapSource(opts = {}) {
|
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
|
-
/** The point of the nodes `ids`' own geometry nearest `press`, within
|
|
61
|
-
*
|
|
62
|
-
* mark. */
|
|
63
|
-
function ownGeometryAt(scene, press, ids, scale) {
|
|
60
|
+
/** The point of the nodes `ids`' own geometry nearest `press`, within
|
|
61
|
+
* `reachPx` screen px, or null: where a move's `geometry` probe sits. Leaves
|
|
62
|
+
* no mark. */
|
|
63
|
+
function ownGeometryAt(scene, press, ids, scale, reachPx = DEFAULT_SNAP_PX) {
|
|
64
64
|
const { ids: owners, index } = sceneSnapIndex(scene);
|
|
65
65
|
const own = new Set(ids);
|
|
66
66
|
const hit = findPathSnap(index, press, {
|
|
67
|
-
radius:
|
|
67
|
+
radius: reachPx,
|
|
68
68
|
scale,
|
|
69
69
|
skip: (i) => {
|
|
70
70
|
const id = owners[i];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pathSnapSource.js","names":[],"sources":["../../../../src/features/paths/pathSnap/pathSnapSource.ts"],"sourcesContent":["import { findPathSnap, findPathSnaps, PATH_SNAP_KINDS, type PathSnapKind } from '@weasel-js/geom';\nimport type { Scene } from 'core/scene/types';\nimport { DEFAULT_SNAP_PX } from 'features/snap/session2d';\nimport type { SnapSource } from 'features/snap/types';\nimport { PATH_SNAP_LABELS } from './glyphs';\nimport { sceneSnapIndex } from './sceneIndex';\n\ntype Point = { x: number; y: number };\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
|
|
1
|
+
{"version":3,"file":"pathSnapSource.js","names":[],"sources":["../../../../src/features/paths/pathSnap/pathSnapSource.ts"],"sourcesContent":["import { findPathSnap, findPathSnaps, PATH_SNAP_KINDS, type PathSnapKind } from '@weasel-js/geom';\nimport type { Scene } from 'core/scene/types';\nimport { DEFAULT_SNAP_PX } from 'features/snap/session2d';\nimport type { SnapSource } from 'features/snap/types';\nimport { PATH_SNAP_LABELS } from './glyphs';\nimport { sceneSnapIndex } from './sceneIndex';\n\ntype Point = { x: number; y: number };\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: Point,\n ids: readonly string[],\n scale: { x: number; y: number },\n reachPx = DEFAULT_SNAP_PX,\n): Point | 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":";;;;;;;;;;;;AAyBA,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,iBACI;CACd,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"}
|
|
@@ -6,12 +6,19 @@ import { createPathSnapIndex } from "@weasel-js/geom";
|
|
|
6
6
|
//#region src/features/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
|
-
* version, and again when a query brings geometry of its own.
|
|
9
|
+
* version, and again when a query brings geometry of its own. The scene
|
|
10
|
+
* alone and the scene with the last `also` are cached apart, so a query
|
|
11
|
+
* without geometry does not evict a gesture's. */
|
|
10
12
|
function createSceneSnapIndexer(scene) {
|
|
11
|
-
|
|
13
|
+
const slots = {
|
|
14
|
+
bare: null,
|
|
15
|
+
also: null
|
|
16
|
+
};
|
|
12
17
|
return (also) => {
|
|
13
18
|
const version = scene.getVersion();
|
|
14
19
|
const layers = scene.layers;
|
|
20
|
+
const key = also ? "also" : "bare";
|
|
21
|
+
const cached = slots[key];
|
|
15
22
|
if (cached?.version === version && cached.layers === layers && cached.also === also) return cached.built;
|
|
16
23
|
const notes = new Set(layers.filter(isAnnotationLayer).map((l) => l.id));
|
|
17
24
|
const gates = nodeGates({
|
|
@@ -55,7 +62,7 @@ function createSceneSnapIndexer(scene) {
|
|
|
55
62
|
ids,
|
|
56
63
|
index: createPathSnapIndex(paths, extra)
|
|
57
64
|
};
|
|
58
|
-
|
|
65
|
+
slots[key] = {
|
|
59
66
|
version,
|
|
60
67
|
layers,
|
|
61
68
|
also,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sceneIndex.js","names":[],"sources":["../../../../src/features/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. */\nexport function createSceneSnapIndexer(scene: SnapScene): (also?: SnapGeometry) => SceneSnapIndex {\n
|
|
1
|
+
{"version":3,"file":"sceneIndex.js","names":[],"sources":["../../../../src/features/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 !== 'leaf' || 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"}
|
|
@@ -31,7 +31,10 @@ function boxReader(d, scene) {
|
|
|
31
31
|
framed: (_id, pose) => framedBounds(pose, d),
|
|
32
32
|
aabb: (_id, pose) => visualBoundsViaDescriptor(pose, d)
|
|
33
33
|
};
|
|
34
|
-
const leaves = {
|
|
34
|
+
const leaves = {
|
|
35
|
+
get: (id) => scene.get(id),
|
|
36
|
+
...scene.derivedPathOf ? { derivedPathOf: (id) => scene.derivedPathOf(id) } : {}
|
|
37
|
+
};
|
|
35
38
|
const opts = (scale) => ({
|
|
36
39
|
descriptor: d,
|
|
37
40
|
...scale !== void 0 ? { scale } : {}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"selectionPose.js","names":[],"sources":["../../../src/features/selection/selectionPose.ts"],"sourcesContent":["/**\n * The pose and box a selection's chrome is drawn around: precedence between\n * the in-flight overlays and the stored pose, a container's union over its\n * leaves, and the drawn box a scene supplies.\n */\nimport type { Scale2 } from '@weasel-js/geom';\nimport { drawnAABBOf, drawnBoundsOf, type DrawnBoundsScene } from 'canvas/drawnBounds';\nimport { unionAABB } from 'core/geometry/unionBounds';\nimport { framedBounds, visualBoundsViaDescriptor, type PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport type { NodeId } from 'core/scene/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\n\n/**\n * Transitive leaf ids under `id`. A childless node is a leaf only if it is not\n * itself a container: an empty container contributes no bounds, and its own\n * stored pose is at its most stale when no children are left to have moved it.\n */\nfunction leavesOf(\n id: string,\n getChildren: (id: string) => readonly string[],\n isContainer: (id: string) => boolean,\n): string[] {\n if (!isContainer(id)) return [id];\n const out: string[] = [];\n const visit = (nid: string) => {\n const kids = getChildren(nid);\n if (kids.length === 0) { if (!isContainer(nid)) out.push(nid); return; }\n for (const k of kids) visit(k);\n };\n visit(id);\n return out;\n}\n\n/** How one id's pose becomes a box: the pose box, or with a scene the box the\n * node is drawn with. Each id is read as a leaf — the pose already says where\n * a container is, so its members' stored poses are not consulted again. */\nfunction boxReader<TPose>(\n d: PoseDescriptor<TPose>,\n scene: DrawnBoundsScene | undefined,\n): { framed(id: string, pose: TPose, scale?: number | Scale2): Bounds; aabb(id: string, pose: TPose, scale?: number | Scale2): Bounds } {\n if (!scene) {\n return {\n framed: (_id, pose) => framedBounds(pose, d),\n aabb: (_id, pose) => visualBoundsViaDescriptor(pose, d),\n };\n }\n const leaves: DrawnBoundsScene = {
|
|
1
|
+
{"version":3,"file":"selectionPose.js","names":[],"sources":["../../../src/features/selection/selectionPose.ts"],"sourcesContent":["/**\n * The pose and box a selection's chrome is drawn around: precedence between\n * the in-flight overlays and the stored pose, a container's union over its\n * leaves, and the drawn box a scene supplies.\n */\nimport type { Scale2 } from '@weasel-js/geom';\nimport { drawnAABBOf, drawnBoundsOf, type DrawnBoundsScene } from 'canvas/drawnBounds';\nimport { unionAABB } from 'core/geometry/unionBounds';\nimport { framedBounds, visualBoundsViaDescriptor, type PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport type { NodeId } from 'core/scene/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\n\n/**\n * Transitive leaf ids under `id`. A childless node is a leaf only if it is not\n * itself a container: an empty container contributes no bounds, and its own\n * stored pose is at its most stale when no children are left to have moved it.\n */\nfunction leavesOf(\n id: string,\n getChildren: (id: string) => readonly string[],\n isContainer: (id: string) => boolean,\n): string[] {\n if (!isContainer(id)) return [id];\n const out: string[] = [];\n const visit = (nid: string) => {\n const kids = getChildren(nid);\n if (kids.length === 0) { if (!isContainer(nid)) out.push(nid); return; }\n for (const k of kids) visit(k);\n };\n visit(id);\n return out;\n}\n\n/** How one id's pose becomes a box: the pose box, or with a scene the box the\n * node is drawn with. Each id is read as a leaf — the pose already says where\n * a container is, so its members' stored poses are not consulted again. */\nfunction boxReader<TPose>(\n d: PoseDescriptor<TPose>,\n scene: DrawnBoundsScene | undefined,\n): { framed(id: string, pose: TPose, scale?: number | Scale2): Bounds; aabb(id: string, pose: TPose, scale?: number | Scale2): Bounds } {\n if (!scene) {\n return {\n framed: (_id, pose) => framedBounds(pose, d),\n aabb: (_id, pose) => visualBoundsViaDescriptor(pose, d),\n };\n }\n const leaves: DrawnBoundsScene = {\n get: (id) => scene.get(id),\n ...(scene.derivedPathOf ? { derivedPathOf: (id: NodeId) => scene.derivedPathOf!(id) } : {}),\n };\n const opts = (scale: number | Scale2 | undefined) => ({\n descriptor: d as PoseDescriptor<unknown>,\n ...(scale !== undefined ? { scale } : {}),\n });\n return {\n framed: (id, pose, scale) => drawnBoundsOf(leaves, id as NodeId, pose, opts(scale)),\n aabb: (id, pose, scale) => drawnAABBOf(leaves, id as NodeId, pose, opts(scale)),\n };\n}\n\n/** Options for `composeSelectionPose`. */\nexport interface ComposeSelectionPoseOpts<TPose> {\n /** Move overlay; when present its `poses` map wins over everything else. */\n moveOverlay?: { poses: Map<string, TPose> } | null;\n /**\n * Resize overlay; consulted only when move overlay does not own the id.\n * For container resize, `leafPoses` (when present) maps each leaf id under\n * the container to its overlay pose. If absent the container falls back to\n * stored leaf poses.\n */\n resizeOverlay?: {\n id: string;\n currentPose: TPose;\n leafPoses?: Map<string, TPose>;\n } | null;\n /** Fallback pose lookup (typically the stored/committed pose). */\n getStoredPose: (id: string) => TPose;\n /** How to read poses. Default `AUTO_POSE_DESCRIPTOR`. */\n poseDescriptor?: PoseDescriptor<TPose>;\n /** Walk a container's direct children (e.g. `scene.childrenOf`). With\n * `isContainer`, a selected container resolves to the union AABB of its\n * transitive leaf poses instead of its own stored pose. */\n getChildren?: (id: string) => readonly string[];\n /** True when `id` is a structural container. */\n isContainer?: (id: string) => boolean;\n /** The nodes behind the ids. With it, a container's union takes each leaf\n * as drawn, stroke included (`drawnBoundsOf`); without, its pose box. */\n scene?: DrawnBoundsScene;\n /** View scale a screen-pixel stroke is resolved at. Default 1. */\n viewScale?: () => number | Scale2;\n}\n\n/**\n * Build a pose resolver for a selection. Precedence per id:\n * move overlay > resize overlay > stored. When `getChildren`/`isContainer`\n * are supplied and the id resolves to a container, the resolver returns the\n * union AABB of all transitive leaf poses (each leaf still subject to the\n * precedence rules). Containers with no leaves resolve to `null`.\n */\nexport function composeSelectionPose<TPose>(\n opts: ComposeSelectionPoseOpts<TPose>,\n): (id: string) => TPose | null {\n const { moveOverlay, resizeOverlay, getStoredPose, getChildren, isContainer } = opts;\n const d = (opts.poseDescriptor ?? AUTO_POSE_DESCRIPTOR) as PoseDescriptor<TPose>;\n const boxes = boxReader(d, opts.scene);\n\n const selectionLeaves = (id: string): string[] =>\n getChildren && isContainer ? leavesOf(id, getChildren, isContainer) : [id];\n\n const resolveLeaf = (id: string): TPose => {\n const moved = moveOverlay?.poses.get(id);\n if (moved !== undefined) return moved;\n if (resizeOverlay && resizeOverlay.id === id) return resizeOverlay.currentPose;\n return getStoredPose(id);\n };\n\n return (id: string): TPose | null => {\n if (isContainer?.(id)) {\n const leaves = selectionLeaves(id);\n if (leaves.length === 0) return null;\n const containerResizeLeafPoses =\n resizeOverlay && resizeOverlay.id === id ? resizeOverlay.leafPoses : undefined;\n const scale = opts.viewScale?.();\n const leafBounds: Bounds[] = [];\n for (const leafId of leaves) {\n const pose = moveOverlay?.poses.get(leafId)\n ?? containerResizeLeafPoses?.get(leafId)\n ?? getStoredPose(leafId);\n leafBounds.push(boxes.aabb(leafId, pose, scale));\n }\n const u = unionAABB(leafBounds);\n if (u === null) return null;\n return d.fromBounds(u, getStoredPose(leaves[0]!));\n }\n return resolveLeaf(id);\n };\n}\n\n/**\n * Build a bounds resolver that handles container ids by computing the union\n * AABB of every leaf's bounds. Non-container ids pass through directly. When\n * `getChildren`/`isContainer` are omitted, every id is treated as a leaf.\n * With a scene, each leaf is boxed as drawn at the view's scale.\n */\nexport function containerAwareBoundsResolver<TPose>(\n getPose: (id: string) => TPose | null,\n d: PoseDescriptor<TPose>,\n getChildren: ((id: string) => readonly string[]) | undefined,\n isContainer: ((id: string) => boolean) | undefined,\n scene: DrawnBoundsScene | undefined,\n): (id: string, scale: Scale2) => Bounds | null {\n const boxes = boxReader(d, scene);\n const leaf = (id: string, scale: Scale2): Bounds | null => {\n const p = getPose(id);\n return p === null ? null : boxes.framed(id, p, scale);\n };\n if (getChildren === undefined || isContainer === undefined) return leaf;\n return (id, scale) => {\n if (!isContainer(id)) return leaf(id, scale);\n const leaves = leavesOf(id, getChildren, isContainer);\n if (leaves.length === 0) return null;\n const leafBounds: Bounds[] = [];\n for (const leafId of leaves) {\n const p = getPose(leafId);\n if (p !== null) leafBounds.push(boxes.aabb(leafId, p, scale));\n }\n if (leafBounds.length === 0) return null;\n return unionAABB(leafBounds);\n };\n}\n"],"mappings":";;;;;;;;;;;AAkBA,SAAS,SACP,IACA,aACA,aACU;CACV,IAAI,CAAC,YAAY,EAAE,GAAG,OAAO,CAAC,EAAE;CAChC,MAAM,MAAgB,CAAC;CACvB,MAAM,SAAS,QAAgB;EAC7B,MAAM,OAAO,YAAY,GAAG;EAC5B,IAAI,KAAK,WAAW,GAAG;GAAE,IAAI,CAAC,YAAY,GAAG,GAAG,IAAI,KAAK,GAAG;GAAG;EAAQ;EACvE,KAAK,MAAM,KAAK,MAAM,MAAM,CAAC;CAC/B;CACA,MAAM,EAAE;CACR,OAAO;AACT;;;;AAKA,SAAS,UACP,GACA,OACsI;CACtI,IAAI,CAAC,OACH,OAAO;EACL,SAAS,KAAK,SAAS,aAAa,MAAM,CAAC;EAC3C,OAAO,KAAK,SAAS,0BAA0B,MAAM,CAAC;CACxD;CAEF,MAAM,SAA2B;EAC/B,MAAM,OAAO,MAAM,IAAI,EAAE;EACzB,GAAI,MAAM,gBAAgB,EAAE,gBAAgB,OAAe,MAAM,cAAe,EAAE,EAAE,IAAI,CAAC;CAC3F;CACA,MAAM,QAAQ,WAAwC;EACpD,YAAY;EACZ,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC;CACzC;CACA,OAAO;EACL,SAAS,IAAI,MAAM,UAAU,cAAc,QAAQ,IAAc,MAAM,KAAK,KAAK,CAAC;EAClF,OAAO,IAAI,MAAM,UAAU,YAAY,QAAQ,IAAc,MAAM,KAAK,KAAK,CAAC;CAChF;AACF;;;;;;;;AAyCA,SAAgB,qBACd,MAC8B;CAC9B,MAAM,EAAE,aAAa,eAAe,eAAe,aAAa,gBAAgB;CAChF,MAAM,IAAK,KAAK,kBAAkB;CAClC,MAAM,QAAQ,UAAU,GAAG,KAAK,KAAK;CAErC,MAAM,mBAAmB,OACvB,eAAe,cAAc,SAAS,IAAI,aAAa,WAAW,IAAI,CAAC,EAAE;CAE3E,MAAM,eAAe,OAAsB;EACzC,MAAM,QAAQ,aAAa,MAAM,IAAI,EAAE;EACvC,IAAI,UAAU,QAAW,OAAO;EAChC,IAAI,iBAAiB,cAAc,OAAO,IAAI,OAAO,cAAc;EACnE,OAAO,cAAc,EAAE;CACzB;CAEA,QAAQ,OAA6B;EACnC,IAAI,cAAc,EAAE,GAAG;GACrB,MAAM,SAAS,gBAAgB,EAAE;GACjC,IAAI,OAAO,WAAW,GAAG,OAAO;GAChC,MAAM,2BACJ,iBAAiB,cAAc,OAAO,KAAK,cAAc,YAAY;GACvE,MAAM,QAAQ,KAAK,YAAY;GAC/B,MAAM,aAAuB,CAAC;GAC9B,KAAK,MAAM,UAAU,QAAQ;IAC3B,MAAM,OAAO,aAAa,MAAM,IAAI,MAAM,KACrC,0BAA0B,IAAI,MAAM,KACpC,cAAc,MAAM;IACzB,WAAW,KAAK,MAAM,KAAK,QAAQ,MAAM,KAAK,CAAC;GACjD;GACA,MAAM,IAAI,UAAU,UAAU;GAC9B,IAAI,MAAM,MAAM,OAAO;GACvB,OAAO,EAAE,WAAW,GAAG,cAAc,OAAO,EAAG,CAAC;EAClD;EACA,OAAO,YAAY,EAAE;CACvB;AACF;;;;;;;AAQA,SAAgB,6BACd,SACA,GACA,aACA,aACA,OAC8C;CAC9C,MAAM,QAAQ,UAAU,GAAG,KAAK;CAChC,MAAM,QAAQ,IAAY,UAAiC;EACzD,MAAM,IAAI,QAAQ,EAAE;EACpB,OAAO,MAAM,OAAO,OAAO,MAAM,OAAO,IAAI,GAAG,KAAK;CACtD;CACA,IAAI,gBAAgB,UAAa,gBAAgB,QAAW,OAAO;CACnE,QAAQ,IAAI,UAAU;EACpB,IAAI,CAAC,YAAY,EAAE,GAAG,OAAO,KAAK,IAAI,KAAK;EAC3C,MAAM,SAAS,SAAS,IAAI,aAAa,WAAW;EACpD,IAAI,OAAO,WAAW,GAAG,OAAO;EAChC,MAAM,aAAuB,CAAC;EAC9B,KAAK,MAAM,UAAU,QAAQ;GAC3B,MAAM,IAAI,QAAQ,MAAM;GACxB,IAAI,MAAM,MAAM,WAAW,KAAK,MAAM,KAAK,QAAQ,GAAG,KAAK,CAAC;EAC9D;EACA,IAAI,WAAW,WAAW,GAAG,OAAO;EACpC,OAAO,UAAU,UAAU;CAC7B;AACF"}
|
|
@@ -10,6 +10,14 @@ var DEFAULT_GESTURES = [
|
|
|
10
10
|
function snapSourcesFor(sources, gesture) {
|
|
11
11
|
return sources.filter((s) => (s.gestures ?? DEFAULT_GESTURES).includes(gesture));
|
|
12
12
|
}
|
|
13
|
+
/** Throws if two of `sources` share an id: one would silently replace the other. */
|
|
14
|
+
function assertSnapSourceIds(sources) {
|
|
15
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16
|
+
for (const { id } of sources) {
|
|
17
|
+
if (seen.has(id)) throw new Error(`[snap] two snap sources share the id '${id}'. Give one its own id, or drop one.`);
|
|
18
|
+
seen.add(id);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
13
21
|
/** How near `origin` a point target counts as on it, relative to its magnitude. */
|
|
14
22
|
var ORIGIN_EPS = 1e-9;
|
|
15
23
|
/**
|
|
@@ -67,5 +75,5 @@ function marksOf(mark, delta) {
|
|
|
67
75
|
}
|
|
68
76
|
|
|
69
77
|
//#endregion
|
|
70
|
-
export { gatherSnapCandidates, resolvedSnapMarks, snapSourcesFor };
|
|
78
|
+
export { assertSnapSourceIds, gatherSnapCandidates, resolvedSnapMarks, snapSourcesFor };
|
|
71
79
|
//# sourceMappingURL=gather.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gather.js","names":[],"sources":["../../../src/features/snap/gather.ts"],"sourcesContent":["import type { SnapCandidate, Vec } from '@weasel-js/geom/nd';\nimport { distinct } from './marks';\nimport type { SnapCollect, SnapGesture, SnapMark, SnapProbeAt, SnapSource, SourceCandidate } from './types';\n\nconst DEFAULT_GESTURES: readonly SnapGesture[] = ['point', 'move', 'resize'];\n\n/** The sources that answer `gesture`: those naming it, and, for one naming none, point, move and resize. */\nexport function snapSourcesFor<S extends Pick<SnapSource<unknown>, 'gestures'>>(sources: readonly S[], gesture: SnapGesture): S[] {\n return sources.filter((s) => (s.gestures ?? DEFAULT_GESTURES).includes(gesture));\n}\n\n/** What a candidate draws when the result meets it. */\nexport type SnapCandidateMark<P> = NonNullable<SourceCandidate<P>['mark']>;\n\n/** How near `origin` a point target counts as on it, relative to its magnitude. */\nconst ORIGIN_EPS = 1e-9;\n\n/** How `gatherSnapCandidates` reads a host's points. */\nexport interface GatherSnapOptions<P> {\n /** `p` as a resolver tuple. */\n toVec(p: P): Vec;\n /** The reach of a source that sets none, in screen px. */\n reachPx: number;\n /** Where the geometry being drawn starts: no point target on it is offered. */\n origin?: P;\n}\n\n/**\n * One frame's resolver candidates: each source is asked, through `ask`, about\n * the probes whose role it matches and the reach that applies to it (its\n * own `reachPx`, else `opts.reachPx`), and what it answers for a probe it was\n * not offered, or a point on `origin`, is dropped. Sources run in order, and\n * an earlier source, then an earlier yield, wins a full tie. The building\n * block of a host's own snap session over `resolveSnap`.\n */\nexport function gatherSnapCandidates<P>(\n sources: readonly SnapSource<P>[],\n probes: readonly SnapProbeAt<P>[],\n ask: (source: SnapSource<P>, offered: SnapProbeAt<P>[], reachPx: number) => SnapCollect<P>,\n opts: GatherSnapOptions<P>,\n): SnapCandidate<SnapCandidateMark<P>>[] {\n const origin = opts.origin === undefined ? null : opts.toVec(opts.origin);\n const out: SnapCandidate<SnapCandidateMark<P>>[] = [];\n let order = 0;\n for (const source of sources) {\n const offered = probes.filter((q) => source.roles.includes(q.role));\n if (offered.length === 0) continue;\n const ids = new Set(offered.map((q) => q.id));\n const reachPx = source.reachPx ?? opts.reachPx;\n for (const c of source.collect(ask(source, offered, reachPx))) {\n if (!ids.has(c.probe)) continue;\n const at = opts.toVec(c.locus.at);\n if (origin && c.locus.dirs.length === 0 && same(at, origin)) continue;\n out.push({\n probe: c.probe, locus: { at, dirs: c.locus.dirs.map(opts.toVec) },\n ...(c.locus.span ? { span: c.locus.span } : {}),\n source: source.id, order: order++, tier: source.tier ?? 'target', reachPx,\n ...(c.required ? { required: true } : {}),\n ...(c.mark ? { mark: c.mark } : {}),\n });\n }\n }\n return out;\n}\n\nfunction same(a: Vec, b: Vec): boolean {\n const eps = ORIGIN_EPS * Math.max(1, ...b.map(Math.abs));\n return a.every((v, i) => Math.abs(v - b[i]) <= eps);\n}\n\n/** The marks of the candidates a result meets, with `delta` the resolved\n * move, and repeats dropped so a target several probes meet is drawn once. */\nexport function resolvedSnapMarks<P>(\n satisfied: readonly { mark?: SnapCandidateMark<P> }[], delta: P,\n): SnapMark<P>[] {\n return distinct(satisfied.flatMap((c) => marksOf(c.mark, delta)));\n}\n\nfunction marksOf<P>(mark: SnapCandidateMark<P> | undefined, delta: P): readonly SnapMark<P>[] {\n if (!mark) return [];\n const m = typeof mark === 'function' ? mark(delta) : mark;\n return 'kind' in m ? [m as SnapMark<P>] : (m as readonly SnapMark<P>[]);\n}\n"],"mappings":";;;AAIA,IAAM,mBAA2C;CAAC;CAAS;CAAQ;AAAQ;;AAG3E,SAAgB,eAAgE,SAAuB,SAA2B;CAChI,OAAO,QAAQ,QAAQ,OAAO,EAAE,YAAY,iBAAA,CAAkB,SAAS,OAAO,CAAC;AACjF;;AAMA,IAAM,aAAa;;;;;;;;;AAoBnB,SAAgB,qBACd,SACA,QACA,KACA,MACuC;CACvC,MAAM,SAAS,KAAK,WAAW,SAAY,OAAO,KAAK,MAAM,KAAK,MAAM;CACxE,MAAM,MAA6C,CAAC;CACpD,IAAI,QAAQ;CACZ,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,UAAU,OAAO,QAAQ,MAAM,OAAO,MAAM,SAAS,EAAE,IAAI,CAAC;EAClE,IAAI,QAAQ,WAAW,GAAG;EAC1B,MAAM,MAAM,IAAI,IAAI,QAAQ,KAAK,MAAM,EAAE,EAAE,CAAC;EAC5C,MAAM,UAAU,OAAO,WAAW,KAAK;EACvC,KAAK,MAAM,KAAK,OAAO,QAAQ,IAAI,QAAQ,SAAS,OAAO,CAAC,GAAG;GAC7D,IAAI,CAAC,IAAI,IAAI,EAAE,KAAK,GAAG;GACvB,MAAM,KAAK,KAAK,MAAM,EAAE,MAAM,EAAE;GAChC,IAAI,UAAU,EAAE,MAAM,KAAK,WAAW,KAAK,KAAK,IAAI,MAAM,GAAG;GAC7D,IAAI,KAAK;IACP,OAAO,EAAE;IAAO,OAAO;KAAE;KAAI,MAAM,EAAE,MAAM,KAAK,IAAI,KAAK,KAAK;IAAE;IAChE,GAAI,EAAE,MAAM,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,QAAQ,OAAO;IAAI,OAAO;IAAS,MAAM,OAAO,QAAQ;IAAU;IAClE,GAAI,EAAE,WAAW,EAAE,UAAU,KAAK,IAAI,CAAC;IACvC,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;GACnC,CAAC;EACH;CACF;CACA,OAAO;AACT;AAEA,SAAS,KAAK,GAAQ,GAAiB;CACrC,MAAM,MAAM,aAAa,KAAK,IAAI,GAAG,GAAG,EAAE,IAAI,KAAK,GAAG,CAAC;CACvD,OAAO,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,IAAI,EAAE,EAAE,KAAK,GAAG;AACpD;;;AAIA,SAAgB,kBACd,WAAuD,OACxC;CACf,OAAO,SAAS,UAAU,SAAS,MAAM,QAAQ,EAAE,MAAM,KAAK,CAAC,CAAC;AAClE;AAEA,SAAS,QAAW,MAAwC,OAAkC;CAC5F,IAAI,CAAC,MAAM,OAAO,CAAC;CACnB,MAAM,IAAI,OAAO,SAAS,aAAa,KAAK,KAAK,IAAI;CACrD,OAAO,UAAU,IAAI,CAAC,CAAgB,IAAK;AAC7C"}
|
|
1
|
+
{"version":3,"file":"gather.js","names":[],"sources":["../../../src/features/snap/gather.ts"],"sourcesContent":["import type { SnapCandidate, Vec } from '@weasel-js/geom/nd';\nimport { distinct } from './marks';\nimport type { SnapCollect, SnapGesture, SnapMark, SnapProbeAt, SnapSource, SourceCandidate } from './types';\n\nconst DEFAULT_GESTURES: readonly SnapGesture[] = ['point', 'move', 'resize'];\n\n/** The sources that answer `gesture`: those naming it, and, for one naming none, point, move and resize. */\nexport function snapSourcesFor<S extends Pick<SnapSource<unknown>, 'gestures'>>(sources: readonly S[], gesture: SnapGesture): S[] {\n return sources.filter((s) => (s.gestures ?? DEFAULT_GESTURES).includes(gesture));\n}\n\n/** Throws if two of `sources` share an id: one would silently replace the other. */\nexport function assertSnapSourceIds(sources: readonly Pick<SnapSource<unknown>, 'id'>[]): void {\n const seen = new Set<string>();\n for (const { id } of sources) {\n if (seen.has(id)) {\n throw new Error(`[snap] two snap sources share the id '${id}'. Give one its own id, or drop one.`);\n }\n seen.add(id);\n }\n}\n\n/** What a candidate draws when the result meets it. */\nexport type SnapCandidateMark<P> = NonNullable<SourceCandidate<P>['mark']>;\n\n/** How near `origin` a point target counts as on it, relative to its magnitude. */\nconst ORIGIN_EPS = 1e-9;\n\n/** How `gatherSnapCandidates` reads a host's points. */\nexport interface GatherSnapOptions<P> {\n /** `p` as a resolver tuple. */\n toVec(p: P): Vec;\n /** The reach of a source that sets none, in screen px. */\n reachPx: number;\n /** Where the geometry being drawn starts: no point target on it is offered. */\n origin?: P;\n}\n\n/**\n * One frame's resolver candidates: each source is asked, through `ask`, about\n * the probes whose role it matches and the reach that applies to it (its\n * own `reachPx`, else `opts.reachPx`), and what it answers for a probe it was\n * not offered, or a point on `origin`, is dropped. Sources run in order, and\n * an earlier source, then an earlier yield, wins a full tie. The building\n * block of a host's own snap session over `resolveSnap`.\n */\nexport function gatherSnapCandidates<P>(\n sources: readonly SnapSource<P>[],\n probes: readonly SnapProbeAt<P>[],\n ask: (source: SnapSource<P>, offered: SnapProbeAt<P>[], reachPx: number) => SnapCollect<P>,\n opts: GatherSnapOptions<P>,\n): SnapCandidate<SnapCandidateMark<P>>[] {\n const origin = opts.origin === undefined ? null : opts.toVec(opts.origin);\n const out: SnapCandidate<SnapCandidateMark<P>>[] = [];\n let order = 0;\n for (const source of sources) {\n const offered = probes.filter((q) => source.roles.includes(q.role));\n if (offered.length === 0) continue;\n const ids = new Set(offered.map((q) => q.id));\n const reachPx = source.reachPx ?? opts.reachPx;\n for (const c of source.collect(ask(source, offered, reachPx))) {\n if (!ids.has(c.probe)) continue;\n const at = opts.toVec(c.locus.at);\n if (origin && c.locus.dirs.length === 0 && same(at, origin)) continue;\n out.push({\n probe: c.probe, locus: { at, dirs: c.locus.dirs.map(opts.toVec) },\n ...(c.locus.span ? { span: c.locus.span } : {}),\n source: source.id, order: order++, tier: source.tier ?? 'target', reachPx,\n ...(c.required ? { required: true } : {}),\n ...(c.mark ? { mark: c.mark } : {}),\n });\n }\n }\n return out;\n}\n\nfunction same(a: Vec, b: Vec): boolean {\n const eps = ORIGIN_EPS * Math.max(1, ...b.map(Math.abs));\n return a.every((v, i) => Math.abs(v - b[i]) <= eps);\n}\n\n/** The marks of the candidates a result meets, with `delta` the resolved\n * move, and repeats dropped so a target several probes meet is drawn once. */\nexport function resolvedSnapMarks<P>(\n satisfied: readonly { mark?: SnapCandidateMark<P> }[], delta: P,\n): SnapMark<P>[] {\n return distinct(satisfied.flatMap((c) => marksOf(c.mark, delta)));\n}\n\nfunction marksOf<P>(mark: SnapCandidateMark<P> | undefined, delta: P): readonly SnapMark<P>[] {\n if (!mark) return [];\n const m = typeof mark === 'function' ? mark(delta) : mark;\n return 'kind' in m ? [m as SnapMark<P>] : (m as readonly SnapMark<P>[]);\n}\n"],"mappings":";;;AAIA,IAAM,mBAA2C;CAAC;CAAS;CAAQ;AAAQ;;AAG3E,SAAgB,eAAgE,SAAuB,SAA2B;CAChI,OAAO,QAAQ,QAAQ,OAAO,EAAE,YAAY,iBAAA,CAAkB,SAAS,OAAO,CAAC;AACjF;;AAGA,SAAgB,oBAAoB,SAA2D;CAC7F,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,EAAE,QAAQ,SAAS;EAC5B,IAAI,KAAK,IAAI,EAAE,GACb,MAAM,IAAI,MAAM,yCAAyC,GAAG,qCAAqC;EAEnG,KAAK,IAAI,EAAE;CACb;AACF;;AAMA,IAAM,aAAa;;;;;;;;;AAoBnB,SAAgB,qBACd,SACA,QACA,KACA,MACuC;CACvC,MAAM,SAAS,KAAK,WAAW,SAAY,OAAO,KAAK,MAAM,KAAK,MAAM;CACxE,MAAM,MAA6C,CAAC;CACpD,IAAI,QAAQ;CACZ,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,UAAU,OAAO,QAAQ,MAAM,OAAO,MAAM,SAAS,EAAE,IAAI,CAAC;EAClE,IAAI,QAAQ,WAAW,GAAG;EAC1B,MAAM,MAAM,IAAI,IAAI,QAAQ,KAAK,MAAM,EAAE,EAAE,CAAC;EAC5C,MAAM,UAAU,OAAO,WAAW,KAAK;EACvC,KAAK,MAAM,KAAK,OAAO,QAAQ,IAAI,QAAQ,SAAS,OAAO,CAAC,GAAG;GAC7D,IAAI,CAAC,IAAI,IAAI,EAAE,KAAK,GAAG;GACvB,MAAM,KAAK,KAAK,MAAM,EAAE,MAAM,EAAE;GAChC,IAAI,UAAU,EAAE,MAAM,KAAK,WAAW,KAAK,KAAK,IAAI,MAAM,GAAG;GAC7D,IAAI,KAAK;IACP,OAAO,EAAE;IAAO,OAAO;KAAE;KAAI,MAAM,EAAE,MAAM,KAAK,IAAI,KAAK,KAAK;IAAE;IAChE,GAAI,EAAE,MAAM,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,QAAQ,OAAO;IAAI,OAAO;IAAS,MAAM,OAAO,QAAQ;IAAU;IAClE,GAAI,EAAE,WAAW,EAAE,UAAU,KAAK,IAAI,CAAC;IACvC,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;GACnC,CAAC;EACH;CACF;CACA,OAAO;AACT;AAEA,SAAS,KAAK,GAAQ,GAAiB;CACrC,MAAM,MAAM,aAAa,KAAK,IAAI,GAAG,GAAG,EAAE,IAAI,KAAK,GAAG,CAAC;CACvD,OAAO,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,IAAI,EAAE,EAAE,KAAK,GAAG;AACpD;;;AAIA,SAAgB,kBACd,WAAuD,OACxC;CACf,OAAO,SAAS,UAAU,SAAS,MAAM,QAAQ,EAAE,MAAM,KAAK,CAAC,CAAC;AAClE;AAEA,SAAS,QAAW,MAAwC,OAAkC;CAC5F,IAAI,CAAC,MAAM,OAAO,CAAC;CACnB,MAAM,IAAI,OAAO,SAAS,aAAa,KAAK,KAAK,IAAI;CACrD,OAAO,UAAU,IAAI,CAAC,CAAgB,IAAK;AAC7C"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/features/snap/lazySession.ts
|
|
2
|
+
/** A session of `dep` that begins on its first `at` or `angle`, so a press
|
|
3
|
+
* that never moves reads nothing of the scene. Null without a dep. */
|
|
4
|
+
function lazySnapSession(dep, req) {
|
|
5
|
+
if (!dep) return null;
|
|
6
|
+
let begun = null;
|
|
7
|
+
const session = () => begun ??= dep.begin(req);
|
|
8
|
+
return {
|
|
9
|
+
at: (p, mods) => session().at(p, mods),
|
|
10
|
+
angle: (theta, mods) => session().angle(theta, mods),
|
|
11
|
+
end: () => begun?.end()
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
export { lazySnapSession };
|
|
17
|
+
//# sourceMappingURL=lazySession.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lazySession.js","names":[],"sources":["../../../src/features/snap/lazySession.ts"],"sourcesContent":["import type { SnapBegin, SnapDep, SnapSession } from 'interactions/actions/snapDep';\n\n/** A session of `dep` that begins on its first `at` or `angle`, so a press\n * that never moves reads nothing of the scene. Null without a dep. */\nexport function lazySnapSession(dep: SnapDep | undefined, req: SnapBegin): SnapSession | null {\n if (!dep) return null;\n let begun: SnapSession | null = null;\n const session = () => (begun ??= dep.begin(req));\n return {\n at: (p, mods) => session().at(p, mods),\n angle: (theta, mods) => session().angle(theta, mods),\n end: () => begun?.end(),\n };\n}\n"],"mappings":";;;AAIA,SAAgB,gBAAgB,KAA0B,KAAoC;CAC5F,IAAI,CAAC,KAAK,OAAO;CACjB,IAAI,QAA4B;CAChC,MAAM,gBAAiB,UAAU,IAAI,MAAM,GAAG;CAC9C,OAAO;EACL,KAAK,GAAG,SAAS,QAAQ,CAAC,CAAC,GAAG,GAAG,IAAI;EACrC,QAAQ,OAAO,SAAS,QAAQ,CAAC,CAAC,MAAM,OAAO,IAAI;EACnD,WAAW,OAAO,IAAI;CACxB;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"marks.js","names":[],"sources":["../../../src/features/snap/marks.ts"],"sourcesContent":["import { fromPlane, type PlaneMap } from 'core/viewport/parallax';\nimport type { SnapMark } from './types';\n\ntype Point = { x: number; y: number };\n\n/** `marks` with repeats dropped, so a target several probes meet is drawn once. */\nexport function distinct<P>(marks: readonly SnapMark<P>[]): SnapMark<P>[] {\n const seen = new Set<string>();\n return marks.filter((m) => {\n const key = JSON.stringify(m);\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n\n/** `m` carried out of a plane into the camera's world, where the readout draws. */\nexport function markFromPlane(m: SnapMark, plane: PlaneMap): SnapMark {\n const pt = (p: Point) => fromPlane(plane, p);\n return m.kind === 'point' ? { ...m, at: pt(m.at) } : { ...m, from: pt(m.from), to: pt(m.to) };\n}\n"],"mappings":";;;;AAMA,SAAgB,SAAY,OAA8C;
|
|
1
|
+
{"version":3,"file":"marks.js","names":[],"sources":["../../../src/features/snap/marks.ts"],"sourcesContent":["import { fromPlane, type PlaneMap } from 'core/viewport/parallax';\nimport type { SnapMark } from './types';\n\ntype Point = { x: number; y: number };\n\n/** `marks` with repeats dropped, so a target several probes meet is drawn once. */\nexport function distinct<P>(marks: readonly SnapMark<P>[]): SnapMark<P>[] {\n // A string key keeps this linear: comparing every pair field by field more\n // than doubled a crowded row's frame time (culling.test.ts, 2026-10-18).\n const seen = new Set<string>();\n return marks.filter((m) => {\n const key = JSON.stringify(m);\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n\n/** `m` carried out of a plane into the camera's world, where the readout draws. */\nexport function markFromPlane(m: SnapMark, plane: PlaneMap): SnapMark {\n const pt = (p: Point) => fromPlane(plane, p);\n return m.kind === 'point' ? { ...m, at: pt(m.at) } : { ...m, from: pt(m.from), to: pt(m.to) };\n}\n"],"mappings":";;;;AAMA,SAAgB,SAAY,OAA8C;CAGxE,MAAM,uBAAO,IAAI,IAAY;CAC7B,OAAO,MAAM,QAAQ,MAAM;EACzB,MAAM,MAAM,KAAK,UAAU,CAAC;EAC5B,IAAI,KAAK,IAAI,GAAG,GAAG,OAAO;EAC1B,KAAK,IAAI,GAAG;EACZ,OAAO;CACT,CAAC;AACH;;AAGA,SAAgB,cAAc,GAAa,OAA2B;CACpE,MAAM,MAAM,MAAa,UAAU,OAAO,CAAC;CAC3C,OAAO,EAAE,SAAS,UAAU;EAAE,GAAG;EAAG,IAAI,GAAG,EAAE,EAAE;CAAE,IAAI;EAAE,GAAG;EAAG,MAAM,GAAG,EAAE,IAAI;EAAG,IAAI,GAAG,EAAE,EAAE;CAAE;AAC9F"}
|
|
@@ -11,16 +11,28 @@ function subjectBox(ids, reads) {
|
|
|
11
11
|
}
|
|
12
12
|
return unionBounds(boxes);
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
/** The subject of `req`, null where the gesture has none or nothing is left. */
|
|
15
|
+
function subjectOf(req, reads) {
|
|
16
|
+
if (req.gesture === "move") return {
|
|
17
|
+
box: subjectBox(req.ids ?? [], reads),
|
|
18
|
+
resize: null
|
|
19
|
+
};
|
|
20
|
+
if (req.gesture !== "resize" || !req.handle) return {
|
|
21
|
+
box: null,
|
|
22
|
+
resize: null
|
|
23
|
+
};
|
|
24
|
+
if (req.box) return {
|
|
25
|
+
box: null,
|
|
26
|
+
resize: resizeSubject(req.box, req.handle)
|
|
27
|
+
};
|
|
19
28
|
const bounds = subjectBox(req.ids ?? [], reads);
|
|
20
|
-
return
|
|
29
|
+
return {
|
|
30
|
+
box: null,
|
|
31
|
+
resize: bounds ? resizeSubject({ bounds }, req.handle) : null
|
|
32
|
+
};
|
|
21
33
|
}
|
|
22
34
|
/** The probes a gesture snaps, where they are at press. */
|
|
23
|
-
function buildProbes(req, reads) {
|
|
35
|
+
function buildProbes(req, reads, subject = subjectOf(req, reads)) {
|
|
24
36
|
const ids = req.ids ?? [];
|
|
25
37
|
switch (req.gesture) {
|
|
26
38
|
case "point": return [{
|
|
@@ -28,20 +40,17 @@ function buildProbes(req, reads) {
|
|
|
28
40
|
at: req.press,
|
|
29
41
|
role: "pointer"
|
|
30
42
|
}];
|
|
31
|
-
case "resize": {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}];
|
|
38
|
-
}
|
|
39
|
-
case "move": return moveProbes(req.press, ids, reads);
|
|
43
|
+
case "resize": return subject.resize ? resizeProbes(subject.resize) : [{
|
|
44
|
+
id: "pointer",
|
|
45
|
+
at: req.press,
|
|
46
|
+
role: "pointer"
|
|
47
|
+
}];
|
|
48
|
+
case "move": return moveProbes(req.press, ids, reads, subject.box);
|
|
40
49
|
case "rotate":
|
|
41
50
|
case "handle": return [];
|
|
42
51
|
}
|
|
43
52
|
}
|
|
44
|
-
function moveProbes(press, ids, reads) {
|
|
53
|
+
function moveProbes(press, ids, reads, box) {
|
|
45
54
|
const out = [];
|
|
46
55
|
for (const id of ids) {
|
|
47
56
|
const pose = reads.pose(id);
|
|
@@ -51,7 +60,6 @@ function moveProbes(press, ids, reads) {
|
|
|
51
60
|
role: "origin"
|
|
52
61
|
});
|
|
53
62
|
}
|
|
54
|
-
const box = subjectBox(ids, reads);
|
|
55
63
|
if (box) out.push({
|
|
56
64
|
id: "box.min",
|
|
57
65
|
at: {
|
|
@@ -87,5 +95,5 @@ function moveProbes(press, ids, reads) {
|
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
//#endregion
|
|
90
|
-
export { buildProbes,
|
|
98
|
+
export { buildProbes, subjectBox, subjectOf };
|
|
91
99
|
//# sourceMappingURL=probes2d.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"probes2d.js","names":[],"sources":["../../../src/features/snap/probes2d.ts"],"sourcesContent":["import { unionBounds } from 'core/geometry/unionBounds';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport type { SnapBegin } from 'interactions/actions/snapDep';\nimport type { SnapProbeAt } from './types';\nimport { resizeProbes, resizeSubject, type ResizeSubject } from './resizeBox';\n\ntype Point = { x: number; y: number };\n\n/** How a 2D session reads its subject at press. */\nexport interface ProbeReads {\n /** `id`'s world pose now, or undefined when it is gone. */\n pose(id: string): unknown;\n /** The axis-aligned box `id` is drawn with at `pose`, stroke included. */\n drawnBox(id: string, pose: unknown): Bounds;\n /** The point of `pose` a grid rounds: its `OriginProjection` origin. */\n origin(pose: unknown): Point;\n /** The point of the subject's own geometry nearest `press`, or null. */\n geometryAt?(press: Point, ids: readonly string[]): Point | null;\n}\n\n/** The union drawn box of the subjects, or null when none is left. */\nexport function subjectBox(ids: readonly string[], reads: ProbeReads): Bounds | null {\n const boxes: Bounds[] = [];\n for (const id of ids) {\n const pose = reads.pose(id);\n if (pose !== undefined) boxes.push(reads.drawnBox(id, pose));\n }\n return unionBounds(boxes);\n}\n\n/** A resize's box and frame: the one it names, else the subjects' drawn
|
|
1
|
+
{"version":3,"file":"probes2d.js","names":[],"sources":["../../../src/features/snap/probes2d.ts"],"sourcesContent":["import { unionBounds } from 'core/geometry/unionBounds';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport type { SnapBegin } from 'interactions/actions/snapDep';\nimport type { SnapProbeAt } from './types';\nimport { resizeProbes, resizeSubject, type ResizeSubject } from './resizeBox';\n\ntype Point = { x: number; y: number };\n\n/** How a 2D session reads its subject at press. */\nexport interface ProbeReads {\n /** `id`'s world pose now, or undefined when it is gone. */\n pose(id: string): unknown;\n /** The axis-aligned box `id` is drawn with at `pose`, stroke included. */\n drawnBox(id: string, pose: unknown): Bounds;\n /** The point of `pose` a grid rounds: its `OriginProjection` origin. */\n origin(pose: unknown): Point;\n /** The point of the subject's own geometry nearest `press`, or null. */\n geometryAt?(press: Point, ids: readonly string[]): Point | null;\n}\n\n/** The union drawn box of the subjects, or null when none is left. */\nexport function subjectBox(ids: readonly string[], reads: ProbeReads): Bounds | null {\n const boxes: Bounds[] = [];\n for (const id of ids) {\n const pose = reads.pose(id);\n if (pose !== undefined) boxes.push(reads.drawnBox(id, pose));\n }\n return unionBounds(boxes);\n}\n\n/** What a gesture drags, read once at press. */\nexport interface Subject2d {\n /** A move's subjects' union drawn box. */\n box: Bounds | null;\n /** A resize's box and frame: the one it names, else the subjects' drawn\n * box, axis-aligned. */\n resize: ResizeSubject | null;\n}\n\n/** The subject of `req`, null where the gesture has none or nothing is left. */\nexport function subjectOf(req: SnapBegin, reads: ProbeReads): Subject2d {\n if (req.gesture === 'move') return { box: subjectBox(req.ids ?? [], reads), resize: null };\n if (req.gesture !== 'resize' || !req.handle) return { box: null, resize: null };\n if (req.box) return { box: null, resize: resizeSubject(req.box, req.handle) };\n const bounds = subjectBox(req.ids ?? [], reads);\n return { box: null, resize: bounds ? resizeSubject({ bounds }, req.handle) : null };\n}\n\n/** The probes a gesture snaps, where they are at press. */\nexport function buildProbes(req: SnapBegin, reads: ProbeReads, subject = subjectOf(req, reads)): SnapProbeAt[] {\n const ids = req.ids ?? [];\n switch (req.gesture) {\n case 'point':\n return [{ id: 'pointer', at: req.press, role: 'pointer' }];\n case 'resize':\n return subject.resize ? resizeProbes(subject.resize) : [{ id: 'pointer', at: req.press, role: 'pointer' }];\n case 'move':\n return moveProbes(req.press, ids, reads, subject.box);\n case 'rotate':\n case 'handle':\n return [];\n }\n}\n\nfunction moveProbes(press: Point, ids: readonly string[], reads: ProbeReads, box: Bounds | null): SnapProbeAt[] {\n const out: SnapProbeAt[] = [];\n for (const id of ids) {\n const pose = reads.pose(id);\n if (pose !== undefined) out.push({ id: `origin:${id}`, at: reads.origin(pose), role: 'origin' });\n }\n if (box) {\n out.push(\n { id: 'box.min', at: { x: box.x, y: box.y }, role: 'box' },\n { id: 'box.center', at: { x: box.x + box.width / 2, y: box.y + box.height / 2 }, role: 'box' },\n { id: 'box.max', at: { x: box.x + box.width, y: box.y + box.height }, role: 'box' },\n );\n }\n const geometry = reads.geometryAt?.(press, ids) ?? press;\n out.push({ id: 'geometry', at: { x: geometry.x, y: geometry.y }, role: 'geometry' });\n return out;\n}\n"],"mappings":";;;;;AAqBA,SAAgB,WAAW,KAAwB,OAAkC;CACnF,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,OAAO,MAAM,KAAK,EAAE;EAC1B,IAAI,SAAS,QAAW,MAAM,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC;CAC7D;CACA,OAAO,YAAY,KAAK;AAC1B;;AAYA,SAAgB,UAAU,KAAgB,OAA8B;CACtE,IAAI,IAAI,YAAY,QAAQ,OAAO;EAAE,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,KAAK;EAAG,QAAQ;CAAK;CACzF,IAAI,IAAI,YAAY,YAAY,CAAC,IAAI,QAAQ,OAAO;EAAE,KAAK;EAAM,QAAQ;CAAK;CAC9E,IAAI,IAAI,KAAK,OAAO;EAAE,KAAK;EAAM,QAAQ,cAAc,IAAI,KAAK,IAAI,MAAM;CAAE;CAC5E,MAAM,SAAS,WAAW,IAAI,OAAO,CAAC,GAAG,KAAK;CAC9C,OAAO;EAAE,KAAK;EAAM,QAAQ,SAAS,cAAc,EAAE,OAAO,GAAG,IAAI,MAAM,IAAI;CAAK;AACpF;;AAGA,SAAgB,YAAY,KAAgB,OAAmB,UAAU,UAAU,KAAK,KAAK,GAAkB;CAC7G,MAAM,MAAM,IAAI,OAAO,CAAC;CACxB,QAAQ,IAAI,SAAZ;EACE,KAAK,SACH,OAAO,CAAC;GAAE,IAAI;GAAW,IAAI,IAAI;GAAO,MAAM;EAAU,CAAC;EAC3D,KAAK,UACH,OAAO,QAAQ,SAAS,aAAa,QAAQ,MAAM,IAAI,CAAC;GAAE,IAAI;GAAW,IAAI,IAAI;GAAO,MAAM;EAAU,CAAC;EAC3G,KAAK,QACH,OAAO,WAAW,IAAI,OAAO,KAAK,OAAO,QAAQ,GAAG;EACtD,KAAK;EACL,KAAK,UACH,OAAO,CAAC;CACZ;AACF;AAEA,SAAS,WAAW,OAAc,KAAwB,OAAmB,KAAmC;CAC9G,MAAM,MAAqB,CAAC;CAC5B,KAAK,MAAM,MAAM,KAAK;EACpB,MAAM,OAAO,MAAM,KAAK,EAAE;EAC1B,IAAI,SAAS,QAAW,IAAI,KAAK;GAAE,IAAI,UAAU;GAAM,IAAI,MAAM,OAAO,IAAI;GAAG,MAAM;EAAS,CAAC;CACjG;CACA,IAAI,KACF,IAAI,KACF;EAAE,IAAI;EAAW,IAAI;GAAE,GAAG,IAAI;GAAG,GAAG,IAAI;EAAE;EAAG,MAAM;CAAM,GACzD;EAAE,IAAI;EAAc,IAAI;GAAE,GAAG,IAAI,IAAI,IAAI,QAAQ;GAAG,GAAG,IAAI,IAAI,IAAI,SAAS;EAAE;EAAG,MAAM;CAAM,GAC7F;EAAE,IAAI;EAAW,IAAI;GAAE,GAAG,IAAI,IAAI,IAAI;GAAO,GAAG,IAAI,IAAI,IAAI;EAAO;EAAG,MAAM;CAAM,CACpF;CAEF,MAAM,WAAW,MAAM,aAAa,OAAO,GAAG,KAAK;CACnD,IAAI,KAAK;EAAE,IAAI;EAAY,IAAI;GAAE,GAAG,SAAS;GAAG,GAAG,SAAS;EAAE;EAAG,MAAM;CAAW,CAAC;CACnF,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"readoutLayer.js","names":[],"sources":["../../../src/features/snap/readoutLayer.ts"],"sourcesContent":["/** The one layer that draws what a snap won: every mark in the outcome store. */\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { RenderLayer } from 'core/layers/render';\nimport { viewToTransform } from 'core/viewport/view';\nimport { worldToScreen } from 'core/viewport/viewTransform';\nimport type { SnapOutcomeStore } from './outcomeStore';\nimport { glyphCommands, measuredSegmentCommands } from './readoutDraw';\
|
|
1
|
+
{"version":3,"file":"readoutLayer.js","names":[],"sources":["../../../src/features/snap/readoutLayer.ts"],"sourcesContent":["/** The one layer that draws what a snap won: every mark in the outcome store. */\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { RenderLayer } from 'core/layers/render';\nimport { viewToTransform } from 'core/viewport/view';\nimport { worldToScreen } from 'core/viewport/viewTransform';\nimport type { SnapOutcomeStore } from './outcomeStore';\nimport { glyphCommands, measuredSegmentCommands } from './readoutDraw';\nimport type { SnapReadoutStyle } from './types';\n\nconst TICK = 6;\n\n/** A screen-space layer drawing the marks in `store`, redrawn when they change. */\nexport function createSnapReadoutLayer(\n store: SnapOutcomeStore,\n style: SnapReadoutStyle = {},\n): RenderLayer<unknown> {\n const color = style.color ?? '#e8742c';\n const gapColor = style.gapColor ?? '#3aa0ff';\n const width = style.lineWidth ?? 1;\n const strokeOf = (c: string) => ({ paint: { kind: 'solid' as const, color: c }, width });\n return {\n id: 'snap-readout',\n label: 'Snap readout',\n space: 'screen',\n alwaysOn: true,\n subscribe: (repaint) => store.subscribe(repaint),\n draw: (_data, view) => {\n const marks = store.get();\n if (marks.length === 0) return [];\n const t = viewToTransform(view);\n const out: DrawCommand[] = [];\n for (const m of marks) {\n const measured = m.kind === 'gap' || m.kind === 'distance';\n const c = m.color ?? (measured ? gapColor : color);\n if (m.kind === 'point') {\n const [x, y] = worldToScreen(m.at.x, m.at.y, t);\n out.push(...glyphCommands(m.glyph, x, y, m.label, c));\n continue;\n }\n const [x0, y0] = worldToScreen(m.from.x, m.from.y, t);\n const [x1, y1] = worldToScreen(m.to.x, m.to.y, t);\n out.push(...measuredSegmentCommands(\n x0, y0, x1, y1, measured ? TICK : 0, strokeOf(c), measured ? String(Math.round(m.value)) : null, c,\n ));\n }\n return out;\n },\n };\n}\n"],"mappings":";;;;;AASA,IAAM,OAAO;;AAGb,SAAgB,uBACd,OACA,QAA0B,CAAC,GACL;CACtB,MAAM,QAAQ,MAAM,SAAS;CAC7B,MAAM,WAAW,MAAM,YAAY;CACnC,MAAM,QAAQ,MAAM,aAAa;CACjC,MAAM,YAAY,OAAe;EAAE,OAAO;GAAE,MAAM;GAAkB,OAAO;EAAE;EAAG;CAAM;CACtF,OAAO;EACL,IAAI;EACJ,OAAO;EACP,OAAO;EACP,UAAU;EACV,YAAY,YAAY,MAAM,UAAU,OAAO;EAC/C,OAAO,OAAO,SAAS;GACrB,MAAM,QAAQ,MAAM,IAAI;GACxB,IAAI,MAAM,WAAW,GAAG,OAAO,CAAC;GAChC,MAAM,IAAI,gBAAgB,IAAI;GAC9B,MAAM,MAAqB,CAAC;GAC5B,KAAK,MAAM,KAAK,OAAO;IACrB,MAAM,WAAW,EAAE,SAAS,SAAS,EAAE,SAAS;IAChD,MAAM,IAAI,EAAE,UAAU,WAAW,WAAW;IAC5C,IAAI,EAAE,SAAS,SAAS;KACtB,MAAM,CAAC,GAAG,KAAK,cAAc,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,CAAC;KAC9C,IAAI,KAAK,GAAG,cAAc,EAAE,OAAO,GAAG,GAAG,EAAE,OAAO,CAAC,CAAC;KACpD;IACF;IACA,MAAM,CAAC,IAAI,MAAM,cAAc,EAAE,KAAK,GAAG,EAAE,KAAK,GAAG,CAAC;IACpD,MAAM,CAAC,IAAI,MAAM,cAAc,EAAE,GAAG,GAAG,EAAE,GAAG,GAAG,CAAC;IAChD,IAAI,KAAK,GAAG,wBACV,IAAI,IAAI,IAAI,IAAI,WAAW,OAAO,GAAG,SAAS,CAAC,GAAG,WAAW,OAAO,KAAK,MAAM,EAAE,KAAK,CAAC,IAAI,MAAM,CACnG,CAAC;GACH;GACA,OAAO;EACT;CACF;AACF"}
|
|
@@ -15,7 +15,8 @@ var SNAP_ROLES = {
|
|
|
15
15
|
/** The angle step `snap.constrain` (Shift) holds a direction to: a line, a
|
|
16
16
|
* measurement, a rotate, a symmetry axis. 15 degrees. */
|
|
17
17
|
var SNAP_ANGLE_STEP = Math.PI / 12;
|
|
18
|
-
/** The step an `'axis'` lock holds a move
|
|
18
|
+
/** The step an `'axis'` lock holds a move or an anchor drag to, and Shift a
|
|
19
|
+
* pen handle: the axes and the diagonals. */
|
|
19
20
|
var SNAP_AXIS_STEP = Math.PI / 4;
|
|
20
21
|
var held = roleReader(SNAP_ROLES);
|
|
21
22
|
/** The snap roles `ctx` holds. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roles.js","names":[],"sources":["../../../src/features/snap/roles.ts"],"sourcesContent":["import { roleReader, type ModifierState } from '@weasel-js/routing';\nimport type { SnapMods } from 'interactions/actions/snapDep';\n\n/** The modifier roles a snapping action declares. */\nexport const SNAP_ROLES = {\n 'snap.suspend': { label: 'Suspend snapping', keys: ['ctrl'] },\n 'snap.constrain': { label: 'Constrain', keys: ['shift'] },\n} as const;\n\n/** The angle step `snap.constrain` (Shift) holds a direction to: a line, a\n * measurement, a rotate, a symmetry axis. 15 degrees. */\nexport const SNAP_ANGLE_STEP = Math.PI / 12;\n\n/** The step an `'axis'` lock holds a move to: the axes and the diagonals. */\nexport const SNAP_AXIS_STEP = Math.PI / 4;\n\nconst held = roleReader(SNAP_ROLES);\n\n/** The snap roles `ctx` holds. */\nexport function snapMods(ctx: { roles?: Readonly<Record<string, boolean>>; modifiers: ModifierState }): SnapMods {\n return { suspend: held(ctx, 'snap.suspend'), constrain: held(ctx, 'snap.constrain') };\n}\n"],"mappings":";;;;AAIA,IAAa,aAAa;CACxB,gBAAgB;EAAE,OAAO;EAAoB,MAAM,CAAC,MAAM;CAAE;CAC5D,kBAAkB;EAAE,OAAO;EAAa,MAAM,CAAC,OAAO;CAAE;AAC1D;;;AAIA,IAAa,kBAAkB,KAAK,KAAK
|
|
1
|
+
{"version":3,"file":"roles.js","names":[],"sources":["../../../src/features/snap/roles.ts"],"sourcesContent":["import { roleReader, type ModifierRoles, type ModifierState } from '@weasel-js/routing';\nimport type { SnapMods } from 'interactions/actions/snapDep';\n\n/** The modifier roles a snapping action declares. */\nexport const SNAP_ROLES = {\n 'snap.suspend': { label: 'Suspend snapping', keys: ['ctrl'] },\n 'snap.constrain': { label: 'Constrain', keys: ['shift'] },\n} as const satisfies ModifierRoles;\n\n/** The angle step `snap.constrain` (Shift) holds a direction to: a line, a\n * measurement, a rotate, a symmetry axis. 15 degrees. */\nexport const SNAP_ANGLE_STEP = Math.PI / 12;\n\n/** The step an `'axis'` lock holds a move or an anchor drag to, and Shift a\n * pen handle: the axes and the diagonals. */\nexport const SNAP_AXIS_STEP = Math.PI / 4;\n\nconst held = roleReader(SNAP_ROLES);\n\n/** The snap roles `ctx` holds. */\nexport function snapMods(ctx: { roles?: Readonly<Record<string, boolean>>; modifiers: ModifierState }): SnapMods {\n return { suspend: held(ctx, 'snap.suspend'), constrain: held(ctx, 'snap.constrain') };\n}\n"],"mappings":";;;;AAIA,IAAa,aAAa;CACxB,gBAAgB;EAAE,OAAO;EAAoB,MAAM,CAAC,MAAM;CAAE;CAC5D,kBAAkB;EAAE,OAAO;EAAa,MAAM,CAAC,OAAO;CAAE;AAC1D;;;AAIA,IAAa,kBAAkB,KAAK,KAAK;;;AAIzC,IAAa,iBAAiB,KAAK,KAAK;AAExC,IAAM,OAAO,WAAW,UAAU;;AAGlC,SAAgB,SAAS,KAAwF;CAC/G,OAAO;EAAE,SAAS,KAAK,KAAK,cAAc;EAAG,WAAW,KAAK,KAAK,gBAAgB;CAAE;AACtF"}
|