@weasel-js/core 1.9.2 → 1.9.4
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 +393 -0
- package/README.md +2 -2
- package/dist/WeaselProvider.js +5 -5
- package/dist/affordances/composeAffordanceLayer.js +10 -20
- package/dist/affordances/composeAffordanceLayer.js.map +1 -1
- package/dist/affordances/cornerResize.js +25 -17
- package/dist/affordances/cornerResize.js.map +1 -1
- package/dist/affordances/hitAffordanceRegions.js +27 -20
- package/dist/affordances/hitAffordanceRegions.js.map +1 -1
- package/dist/affordances/rotationHandle.js +18 -18
- package/dist/affordances/rotationHandle.js.map +1 -1
- package/dist/animation/behaviors/momentum.js +93 -55
- package/dist/animation/behaviors/momentum.js.map +1 -1
- package/dist/animation/loop.js +10 -12
- package/dist/animation/loop.js.map +1 -1
- package/dist/animation/reflow.js +8 -8
- package/dist/animation/reflow.js.map +1 -1
- package/dist/animation/stagger.js +64 -56
- package/dist/animation/stagger.js.map +1 -1
- package/dist/animation/timeline/booking.js +62 -54
- package/dist/animation/timeline/booking.js.map +1 -1
- package/dist/animation/timeline/createTimeline.js +85 -76
- package/dist/animation/timeline/createTimeline.js.map +1 -1
- package/dist/animation/timeline/sampleTrack.js +13 -5
- package/dist/animation/timeline/sampleTrack.js.map +1 -1
- package/dist/animation/useAnimator.js +45 -35
- package/dist/animation/useAnimator.js.map +1 -1
- package/dist/animation/wrappers/animateLifecycle.js +31 -24
- package/dist/animation/wrappers/animateLifecycle.js.map +1 -1
- package/dist/animation/wrappers/animateOnSetPose.js +28 -26
- package/dist/animation/wrappers/animateOnSetPose.js.map +1 -1
- package/dist/animation/wrappers/instantPose.js +13 -0
- package/dist/animation/wrappers/instantPose.js.map +1 -0
- package/dist/{poseDescriptor-D-OH-tu5.d.ts → autoPoseDescriptor-Ct7yqivU.d.ts} +28 -3
- package/dist/barrel/chrome.js +16 -0
- package/dist/barrel/input.js +19 -0
- package/dist/barrel/interactions.js +42 -0
- package/dist/barrel/paint.js +18 -0
- package/dist/barrel/paths.js +19 -0
- package/dist/barrel/renderer.js +20 -0
- package/dist/barrel/routing.js +51 -0
- package/dist/barrel/text.js +18 -0
- package/dist/barrel/viewport.js +52 -0
- package/dist/builtins-DPcdghtB.d.ts +1223 -0
- package/dist/canvas/Canvas.js +363 -448
- package/dist/canvas/Canvas.js.map +1 -1
- package/dist/canvas/CanvasView.js +164 -165
- package/dist/canvas/CanvasView.js.map +1 -1
- package/dist/canvas/CursorCoordsHud.js +37 -32
- package/dist/canvas/CursorCoordsHud.js.map +1 -1
- package/dist/canvas/DrawCanvas.js +49 -43
- package/dist/canvas/DrawCanvas.js.map +1 -1
- package/dist/canvas/MinimapCanvas.js +26 -25
- package/dist/canvas/MinimapCanvas.js.map +1 -1
- package/dist/canvas/NodeShape.js +59 -377
- package/dist/canvas/NodeShape.js.map +1 -1
- package/dist/canvas/PickHud.js +50 -47
- package/dist/canvas/PickHud.js.map +1 -1
- package/dist/canvas/SceneCanvas/GestureDispatcherMounter.js +167 -0
- package/dist/canvas/SceneCanvas/GestureDispatcherMounter.js.map +1 -0
- package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +13 -13
- package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js +67 -0
- package/dist/canvas/SceneCanvas/StandardActionsRegistrar.js.map +1 -0
- package/dist/canvas/SceneCanvas/ToolKeybindingsMounter.js +16 -0
- package/dist/canvas/SceneCanvas/ToolKeybindingsMounter.js.map +1 -0
- package/dist/canvas/SceneCanvas/coordTrace.js +10 -0
- package/dist/canvas/SceneCanvas/coordTrace.js.map +1 -0
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js +146 -92
- package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
- package/dist/canvas/SceneCanvas/defaultNodeRouting.js +9 -8
- package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
- package/dist/canvas/SceneCanvas/features.js +1 -0
- package/dist/canvas/SceneCanvas/features.js.map +1 -1
- package/dist/canvas/SceneCanvas/isDev.js +12 -0
- package/dist/canvas/SceneCanvas/isDev.js.map +1 -0
- package/dist/canvas/SceneCanvas/poseGeometry.js +29 -28
- package/dist/canvas/SceneCanvas/poseGeometry.js.map +1 -1
- package/dist/canvas/SceneCanvas/poseRegistrars.js +13 -0
- package/dist/canvas/SceneCanvas/poseRegistrars.js.map +1 -0
- package/dist/canvas/SceneCanvas/props.js +38 -0
- package/dist/canvas/SceneCanvas/props.js.map +1 -0
- package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js +21 -62
- package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useDispatcherOverlayLayer.js.map +1 -1
- package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +46 -48
- package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js +33 -33
- package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
- package/dist/canvas/SceneCanvas.js +534 -788
- package/dist/canvas/SceneCanvas.js.map +1 -1
- package/dist/canvas/SceneViewCanvas.js +45 -43
- package/dist/canvas/SceneViewCanvas.js.map +1 -1
- package/dist/canvas/buildSceneLayer.js +91 -0
- package/dist/canvas/buildSceneLayer.js.map +1 -0
- package/dist/canvas/buildSceneTree.js +16 -19
- package/dist/canvas/buildSceneTree.js.map +1 -1
- package/dist/canvas/canvasRenderer.js +68 -52
- package/dist/canvas/canvasRenderer.js.map +1 -1
- package/dist/canvas/deps/changes.js +13 -0
- package/dist/canvas/deps/changes.js.map +1 -0
- package/dist/canvas/deps/editAnchors.js +28 -28
- package/dist/canvas/deps/insert.js +119 -91
- package/dist/canvas/deps/insert.js.map +1 -1
- package/dist/canvas/deps/slice.js +46 -45
- package/dist/canvas/deps/slice.js.map +1 -1
- package/dist/canvas/deps/textEdit.js +10 -9
- package/dist/canvas/deps/textEdit.js.map +1 -1
- package/dist/canvas/derivedPath.js +46 -23
- package/dist/canvas/derivedPath.js.map +1 -1
- package/dist/canvas/getNodeAtPoint.js +7 -7
- package/dist/canvas/getNodeAtPoint.js.map +1 -1
- package/dist/canvas/hud.module.js +1 -1
- package/dist/canvas/hud.module.js.map +1 -1
- package/dist/canvas/kitRequirements.js +2 -2
- package/dist/canvas/kitRequirements.js.map +1 -1
- package/dist/canvas/layerOrder.js +13 -6
- package/dist/canvas/layerOrder.js.map +1 -1
- package/dist/canvas/minimapMath.js +12 -10
- package/dist/canvas/minimapMath.js.map +1 -1
- package/dist/canvas/nodeShapes/derivedPainter.js +34 -0
- package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -0
- package/dist/canvas/nodeShapes/imagePainter.js +87 -0
- package/dist/canvas/nodeShapes/imagePainter.js.map +1 -0
- package/dist/canvas/nodeShapes/memo.js +9 -0
- package/dist/canvas/nodeShapes/memo.js.map +1 -0
- package/dist/canvas/nodeShapes/nodePaint.js +57 -0
- package/dist/canvas/nodeShapes/nodePaint.js.map +1 -0
- package/dist/canvas/nodeShapes/pathPainter.js +38 -0
- package/dist/canvas/nodeShapes/pathPainter.js.map +1 -0
- package/dist/canvas/nodeShapes/poseBox.js +20 -0
- package/dist/canvas/nodeShapes/poseBox.js.map +1 -0
- package/dist/canvas/nodeShapes/rectFallbackPainter.js +38 -0
- package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -0
- package/dist/canvas/nodeShapes/shapePainter.js +72 -0
- package/dist/canvas/nodeShapes/shapePainter.js.map +1 -0
- package/dist/canvas/nodeShapes/textPainter.js +55 -0
- package/dist/canvas/nodeShapes/textPainter.js.map +1 -0
- package/dist/canvas/overlayRoleStrokes.js.map +1 -1
- package/dist/canvas/paintCull.js +10 -11
- package/dist/canvas/paintCull.js.map +1 -1
- package/dist/canvas/pickWalk.js +52 -63
- package/dist/canvas/pickWalk.js.map +1 -1
- package/dist/canvas/poseRotation.js +11 -23
- package/dist/canvas/poseRotation.js.map +1 -1
- package/dist/canvas/renderDebugSnapshot.js +5 -5
- package/dist/canvas/renderSceneToPixels.js +3 -1
- package/dist/canvas/renderSceneToPixels.js.map +1 -1
- package/dist/canvas/sceneAdapter.js +66 -148
- package/dist/canvas/sceneAdapter.js.map +1 -1
- package/dist/canvas/sceneClipboard.js +112 -0
- package/dist/canvas/sceneClipboard.js.map +1 -0
- package/dist/canvas/sceneSlotWiring.js +4 -4
- package/dist/canvas/sceneSlotWiring.js.map +1 -1
- package/dist/canvas/sceneViewRender.js +39 -40
- package/dist/canvas/sceneViewRender.js.map +1 -1
- package/dist/canvas/useCanvasRenderer.js +14 -6
- package/dist/canvas/useCanvasRenderer.js.map +1 -1
- package/dist/canvas/useCanvasView.js +58 -0
- package/dist/canvas/useCanvasView.js.map +1 -0
- package/dist/canvas/useFrameLoop.js +35 -26
- package/dist/canvas/useFrameLoop.js.map +1 -1
- package/dist/canvas/useSceneLayerList.js +21 -0
- package/dist/canvas/useSceneLayerList.js.map +1 -0
- package/dist/clipboard.d.ts +10 -6
- package/dist/clone.d.ts +4 -3
- package/dist/core/adapters/arrayAdapter.js +48 -39
- package/dist/core/adapters/arrayAdapter.js.map +1 -1
- package/dist/core/device/index.js +5 -4
- package/dist/core/device/useDeviceProfile.js +38 -44
- package/dist/core/device/useDeviceProfile.js.map +1 -1
- package/dist/core/device/watchDevicePixelRatio.js +17 -0
- package/dist/core/device/watchDevicePixelRatio.js.map +1 -0
- package/dist/core/fillInPoseFrame.js.map +1 -1
- package/dist/core/geometry/poseDescriptor.js +21 -19
- package/dist/core/geometry/poseDescriptor.js.map +1 -1
- package/dist/core/geometry/poseFrame.js +44 -0
- package/dist/core/geometry/poseFrame.js.map +1 -0
- package/dist/core/geometry/poseRotation.js.map +1 -1
- package/dist/core/geometry/regionHit.js +9 -10
- package/dist/core/geometry/regionHit.js.map +1 -1
- package/dist/core/geometry/unionBounds.js +31 -29
- package/dist/core/geometry/unionBounds.js.map +1 -1
- package/dist/core/isPlainObject.js +2 -11
- package/dist/core/ops/arrange.js +1 -0
- package/dist/core/ops/arrange.js.map +1 -1
- package/dist/core/ops/create.js +10 -5
- package/dist/core/ops/create.js.map +1 -1
- package/dist/core/ops/delete.js +12 -24
- package/dist/core/ops/delete.js.map +1 -1
- package/dist/core/ops/reorder/index.js +43 -40
- package/dist/core/ops/reorder/index.js.map +1 -1
- package/dist/core/ops/reorder/setChildOrder.js +34 -0
- package/dist/core/ops/reorder/setChildOrder.js.map +1 -0
- package/dist/core/paintKinds.js +5 -5
- package/dist/core/paintKinds.js.map +1 -1
- package/dist/core/scene/NodeProperties.js.map +1 -1
- package/dist/core/scene/appliedLog.js +37 -0
- package/dist/core/scene/appliedLog.js.map +1 -0
- package/dist/core/scene/arrivalWindow.js +101 -0
- package/dist/core/scene/arrivalWindow.js.map +1 -0
- package/dist/core/scene/derivedPath.js.map +1 -1
- package/dist/core/scene/historyFacade.js +84 -0
- package/dist/core/scene/historyFacade.js.map +1 -0
- package/dist/core/scene/kitOps.js +173 -0
- package/dist/core/scene/kitOps.js.map +1 -0
- package/dist/core/scene/layerStack.js +38 -0
- package/dist/core/scene/layerStack.js.map +1 -0
- package/dist/core/scene/lockGuard.js +37 -0
- package/dist/core/scene/lockGuard.js.map +1 -0
- package/dist/core/scene/nodeFnFields.js +5 -6
- package/dist/core/scene/nodeFnFields.js.map +1 -1
- package/dist/core/scene/nodeMemo.js +45 -19
- package/dist/core/scene/nodeMemo.js.map +1 -1
- package/dist/core/scene/nodeStore.js +126 -0
- package/dist/core/scene/nodeStore.js.map +1 -0
- package/dist/core/scene/pickability.js +6 -0
- package/dist/core/scene/pickability.js.map +1 -0
- package/dist/core/scene/poseFeed.js +38 -38
- package/dist/core/scene/poseFeed.js.map +1 -1
- package/dist/core/scene/poseOverrides.js +34 -38
- package/dist/core/scene/poseOverrides.js.map +1 -1
- package/dist/core/scene/renderOrder.js +84 -0
- package/dist/core/scene/renderOrder.js.map +1 -0
- package/dist/core/scene/scene.js +289 -866
- package/dist/core/scene/scene.js.map +1 -1
- package/dist/core/scene/serialization.js +68 -0
- package/dist/core/scene/serialization.js.map +1 -0
- package/dist/core/scene/treeOrder.js +13 -0
- package/dist/core/scene/treeOrder.js.map +1 -0
- package/dist/core/scene/types.js.map +1 -1
- package/dist/core/viewport/meanScale.js +1 -1
- package/dist/core/viewport/meanScale.js.map +1 -1
- package/dist/core/viewport/view.js +12 -12
- package/dist/core/viewport/view.js.map +1 -1
- package/dist/core/viewport/zoomBounds.js +10 -8
- package/dist/core/viewport/zoomBounds.js.map +1 -1
- package/dist/{cornerHandles-BCrDp_mH.d.ts → cornerHandles-CtCb8a3G.d.ts} +1 -1
- package/dist/debug/createDebugOverlayLayer.js +14 -14
- package/dist/debug/createDebugOverlayLayer.js.map +1 -1
- package/dist/debug/flag.js +2 -41
- package/dist/features/chrome-caps/useHoverTracking.js +22 -17
- package/dist/features/chrome-caps/useHoverTracking.js.map +1 -1
- package/dist/features/focus/gateLayer.js +3 -4
- package/dist/features/focus/gateLayer.js.map +1 -1
- package/dist/features/images/imageCache.js +19 -9
- package/dist/features/images/imageCache.js.map +1 -1
- package/dist/features/ingestion/contentHandlers.js +14 -11
- package/dist/features/ingestion/contentHandlers.js.map +1 -1
- package/dist/features/ingestion/index.js +1 -1
- package/dist/features/ingestion/registerKitHandlers.js +7 -7
- package/dist/features/ingestion/weaselJsonHandler.js +17 -14
- package/dist/features/ingestion/weaselJsonHandler.js.map +1 -1
- package/dist/features/meshPaint/lazyKind.js +201 -35
- package/dist/features/meshPaint/lazyKind.js.map +1 -1
- package/dist/features/meshPaint/meshPaint.js +82 -79
- package/dist/features/meshPaint/meshPaint.js.map +1 -1
- package/dist/features/minimap/createMinimapContribution.js +42 -42
- package/dist/features/minimap/layers.js +44 -62
- package/dist/features/minimap/layers.js.map +1 -1
- package/dist/features/parallax/createParallaxPlane.js +10 -14
- package/dist/features/parallax/createParallaxPlane.js.map +1 -1
- package/dist/features/paths/anchorEdits.js +19 -8
- package/dist/features/paths/anchorEdits.js.map +1 -1
- package/dist/features/paths/anchors.js +31 -26
- package/dist/features/paths/anchors.js.map +1 -1
- package/dist/features/paths/booleans.js +2 -2
- package/dist/features/paths/index.js +13 -11
- package/dist/features/paths/markerCommands.js +37 -36
- package/dist/features/paths/markerCommands.js.map +1 -1
- package/dist/features/paths/pathInWorld.js +19 -19
- package/dist/features/paths/pathInWorld.js.map +1 -1
- package/dist/features/paths/pathLayer.js.map +1 -1
- package/dist/features/paths/strokeToPath.js +40 -0
- package/dist/features/paths/strokeToPath.js.map +1 -0
- package/dist/features/paths/tessellate/stroke.js.map +1 -1
- package/dist/features/patterns/commandPaint.js +18 -0
- package/dist/features/patterns/commandPaint.js.map +1 -0
- package/dist/features/patterns/index.js +5 -17
- package/dist/features/patterns/index.js.map +1 -1
- package/dist/features/patterns/rasterizeTile.js +17 -0
- package/dist/features/patterns/rasterizeTile.js.map +1 -0
- package/dist/features/patterns/resolveSpec.js +52 -44
- package/dist/features/patterns/resolveSpec.js.map +1 -1
- package/dist/features/poseRun/usePoseRun.js +8 -3
- package/dist/features/poseRun/usePoseRun.js.map +1 -1
- package/dist/features/selection/SelectionContext.js +5 -0
- package/dist/features/selection/SelectionContext.js.map +1 -1
- package/dist/features/selection/overlay.js +18 -18
- package/dist/features/selection/overlay.js.map +1 -1
- package/dist/features/text/domOffsets.js +82 -0
- package/dist/features/text/domOffsets.js.map +1 -0
- package/dist/features/text/domRuns.js +103 -131
- package/dist/features/text/domRuns.js.map +1 -1
- package/dist/features/text/hitTest.js +5 -2
- package/dist/features/text/hitTest.js.map +1 -1
- package/dist/features/text/isTextData.js +8 -0
- package/dist/features/text/isTextData.js.map +1 -0
- package/dist/features/text/overlayDirection.js +15 -0
- package/dist/features/text/overlayDirection.js.map +1 -0
- package/dist/features/text/runs/rangeStyle.js +32 -34
- package/dist/features/text/runs/rangeStyle.js.map +1 -1
- package/dist/features/text/textCommand.js +34 -33
- package/dist/features/text/textCommand.js.map +1 -1
- package/dist/features/text/textToPath.js +40 -40
- package/dist/features/text/textToPath.js.map +1 -1
- package/dist/features/text/useSceneTextEdit.js +76 -82
- package/dist/features/text/useSceneTextEdit.js.map +1 -1
- package/dist/features/text/useTextEdit.js +184 -160
- package/dist/features/text/useTextEdit.js.map +1 -1
- package/dist/features/tiling/createTiledLayer.js +24 -22
- package/dist/features/tiling/createTiledLayer.js.map +1 -1
- package/dist/index.d.ts +11010 -7700
- package/dist/index.js +316 -309
- package/dist/input/keyState.js +42 -43
- package/dist/input/keyState.js.map +1 -1
- package/dist/input/useKeyModifiers.js +11 -0
- package/dist/input/useKeyModifiers.js.map +1 -0
- package/dist/interactions/actions/align/align.js +8 -8
- package/dist/interactions/actions/clipboard/clipboardOps.js +34 -32
- package/dist/interactions/actions/clipboard/clipboardOps.js.map +1 -1
- package/dist/interactions/actions/clipboard/materializePaste.js +10 -7
- package/dist/interactions/actions/clipboard/materializePaste.js.map +1 -1
- package/dist/interactions/actions/clipboard/wireFormat.js.map +1 -1
- package/dist/interactions/actions/clone/behaviors/cloneByAltDrag.js.map +1 -1
- package/dist/interactions/actions/defaultCommitAdapter.js +20 -18
- package/dist/interactions/actions/defaultCommitAdapter.js.map +1 -1
- package/dist/interactions/actions/defaults/align.js +9 -10
- package/dist/interactions/actions/defaults/align.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorEditing.js +59 -81
- package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
- package/dist/interactions/actions/defaults/areaSelect.js +35 -38
- package/dist/interactions/actions/defaults/areaSelect.js.map +1 -1
- package/dist/interactions/actions/defaults/clipboard.js +26 -10
- package/dist/interactions/actions/defaults/clipboard.js.map +1 -1
- package/dist/interactions/actions/defaults/clone.js +44 -60
- package/dist/interactions/actions/defaults/clone.js.map +1 -1
- package/dist/interactions/actions/defaults/copySubtree.js +40 -0
- package/dist/interactions/actions/defaults/copySubtree.js.map +1 -0
- package/dist/interactions/actions/defaults/distribute.js +14 -13
- package/dist/interactions/actions/defaults/distribute.js.map +1 -1
- package/dist/interactions/actions/defaults/duplicate.js +17 -57
- package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
- package/dist/interactions/actions/defaults/editAnchors.js +18 -18
- package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/flip.js +52 -46
- package/dist/interactions/actions/defaults/flip.js.map +1 -1
- package/dist/interactions/actions/defaults/group.js +43 -48
- package/dist/interactions/actions/defaults/group.js.map +1 -1
- package/dist/interactions/actions/defaults/ingest.js +35 -33
- package/dist/interactions/actions/defaults/ingest.js.map +1 -1
- package/dist/interactions/actions/defaults/insert.js +50 -51
- package/dist/interactions/actions/defaults/insert.js.map +1 -1
- package/dist/interactions/actions/defaults/insertPathAnchor.js +7 -7
- package/dist/interactions/actions/defaults/lassoSelect.js +67 -70
- package/dist/interactions/actions/defaults/lassoSelect.js.map +1 -1
- package/dist/interactions/actions/defaults/move.js +267 -249
- package/dist/interactions/actions/defaults/move.js.map +1 -1
- package/dist/interactions/actions/defaults/nudge.js +40 -68
- package/dist/interactions/actions/defaults/nudge.js.map +1 -1
- package/dist/interactions/actions/defaults/nudgeSteps.js +37 -0
- package/dist/interactions/actions/defaults/nudgeSteps.js.map +1 -0
- package/dist/interactions/actions/defaults/reorder.js +2 -0
- package/dist/interactions/actions/defaults/reorder.js.map +1 -1
- package/dist/interactions/actions/defaults/resize.js +104 -98
- package/dist/interactions/actions/defaults/resize.js.map +1 -1
- package/dist/interactions/actions/defaults/rotate.js +111 -110
- package/dist/interactions/actions/defaults/rotate.js.map +1 -1
- package/dist/interactions/actions/defaults/setFill.js +7 -7
- package/dist/interactions/actions/defaults/setFillOpacity.js +4 -4
- package/dist/interactions/actions/defaults/setStroke.js +12 -12
- package/dist/interactions/actions/defaults/setStrokeOpacity.js +6 -6
- package/dist/interactions/actions/defaults/sliceOps.js +19 -19
- package/dist/interactions/actions/defaults/viewportDragPan.js +37 -26
- package/dist/interactions/actions/defaults/viewportDragPan.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/geometry.js +28 -23
- package/dist/interactions/actions/edit-anchors/geometry.js.map +1 -1
- package/dist/interactions/actions/flip/helpers.js +30 -17
- package/dist/interactions/actions/flip/helpers.js.map +1 -1
- package/dist/interactions/actions/hitSelection.js +8 -0
- package/dist/interactions/actions/hitSelection.js.map +1 -0
- package/dist/interactions/actions/lasso-select/behaviors/selectFromLasso.js +14 -18
- package/dist/interactions/actions/lasso-select/behaviors/selectFromLasso.js.map +1 -1
- package/dist/interactions/actions/move/behaviors/snapBackOrDelete.js +5 -5
- package/dist/interactions/actions/move/behaviors/snapBackOrDelete.js.map +1 -1
- package/dist/interactions/actions/move/behaviors/snapToContainer.js +32 -21
- package/dist/interactions/actions/move/behaviors/snapToContainer.js.map +1 -1
- package/dist/interactions/actions/move/gestureAdapter.js +32 -15
- package/dist/interactions/actions/move/gestureAdapter.js.map +1 -1
- package/dist/interactions/actions/move/landedDescendants.js +33 -0
- package/dist/interactions/actions/move/landedDescendants.js.map +1 -0
- package/dist/interactions/actions/outlines/createOutlines.js +18 -18
- package/dist/interactions/actions/poseDescriptorDep.js +16 -4
- package/dist/interactions/actions/poseDescriptorDep.js.map +1 -1
- package/dist/interactions/actions/poseFrame.js +11 -6
- package/dist/interactions/actions/poseFrame.js.map +1 -1
- package/dist/interactions/actions/previewOverrides.js +5 -5
- package/dist/interactions/actions/previewOverrides.js.map +1 -1
- package/dist/interactions/actions/selectionSubtree.js +69 -0
- package/dist/interactions/actions/selectionSubtree.js.map +1 -0
- package/dist/interactions/actions/useStandardActions.js +66 -62
- package/dist/interactions/actions/useStandardActions.js.map +1 -1
- package/dist/interactions/gestures/dragGesture/dragGesture.js +4 -7
- package/dist/interactions/gestures/dragGesture/dragGesture.js.map +1 -1
- package/dist/interactions/gestures/dragGesture/dragPointOf.js +23 -0
- package/dist/interactions/gestures/dragGesture/dragPointOf.js.map +1 -0
- package/dist/interactions/gestures/handleDrag/handleDrag.js +21 -26
- package/dist/interactions/gestures/handleDrag/handleDrag.js.map +1 -1
- package/dist/interactions/gestures/pointerDrag/pointerDrag.js +8 -6
- package/dist/interactions/gestures/pointerDrag/pointerDrag.js.map +1 -1
- package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js +8 -4
- package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js.map +1 -1
- package/dist/{math-Drrw4ewZ.d.ts → math-J-ACHyEg.d.ts} +1 -2
- package/dist/math.d.ts +2 -3
- package/dist/move.d.ts +12 -11
- package/dist/{originProjection-DEklpDc_.d.ts → originProjection-gHnOup4e.d.ts} +1 -1
- package/dist/{registerProgram-BMZRDgrh.d.ts → registerProgram-T9S_PfAX.d.ts} +18 -7
- package/dist/renderer/DrawCommand.js.map +1 -1
- package/dist/renderer/WeaselRenderer.js +75 -73
- package/dist/renderer/WeaselRenderer.js.map +1 -1
- package/dist/renderer/cache/GLImageCache.js +15 -5
- package/dist/renderer/cache/GLImageCache.js.map +1 -1
- package/dist/renderer/cache/GLMeshCache.js +23 -23
- package/dist/renderer/cache/GLMeshCache.js.map +1 -1
- package/dist/renderer/cache/GLTextureCache.js +18 -1
- package/dist/renderer/cache/GLTextureCache.js.map +1 -1
- package/dist/renderer/cache/cache.js.map +1 -1
- package/dist/renderer/cache/markerCommandCache.js.map +1 -1
- package/dist/renderer/cache/strokeMeshCache.js.map +1 -1
- package/dist/renderer/cullDrawCommands.js +11 -11
- package/dist/renderer/draw/batch.js +133 -0
- package/dist/renderer/draw/batch.js.map +1 -0
- package/dist/renderer/draw/clip.js +89 -0
- package/dist/renderer/draw/clip.js.map +1 -0
- package/dist/renderer/draw/customShader.js +230 -0
- package/dist/renderer/draw/customShader.js.map +1 -0
- package/dist/renderer/draw/drawTriangles.js +8 -0
- package/dist/renderer/draw/drawTriangles.js.map +1 -0
- package/dist/renderer/draw/effects.js +28 -0
- package/dist/renderer/draw/effects.js.map +1 -0
- package/dist/renderer/draw/flushBatch.js +35 -0
- package/dist/renderer/draw/flushBatch.js.map +1 -0
- package/dist/renderer/draw/gradient.js +90 -0
- package/dist/renderer/draw/gradient.js.map +1 -0
- package/dist/renderer/draw/image.js +38 -0
- package/dist/renderer/draw/image.js.map +1 -0
- package/dist/renderer/draw/mesh.js +16 -0
- package/dist/renderer/draw/mesh.js.map +1 -0
- package/dist/renderer/draw/paintBind.js +70 -0
- package/dist/renderer/draw/paintBind.js.map +1 -0
- package/dist/renderer/draw/pathPaint.js +142 -0
- package/dist/renderer/draw/pathPaint.js.map +1 -0
- package/dist/renderer/draw/scratchPass.js +83 -0
- package/dist/renderer/draw/scratchPass.js.map +1 -0
- package/dist/renderer/draw/stencil.js +28 -0
- package/dist/renderer/draw/stencil.js.map +1 -0
- package/dist/renderer/draw/text.js +167 -0
- package/dist/renderer/draw/text.js.map +1 -0
- package/dist/renderer/draw/uniforms.js +77 -0
- package/dist/renderer/draw/uniforms.js.map +1 -0
- package/dist/renderer/draw.js +60 -1116
- package/dist/renderer/draw.js.map +1 -1
- package/dist/renderer/effects/EffectTargets.js +3 -0
- package/dist/renderer/effects/EffectTargets.js.map +1 -1
- package/dist/renderer/effects/composite.js +7 -5
- package/dist/renderer/effects/composite.js.map +1 -1
- package/dist/renderer/index.js +18 -17
- package/dist/renderer/math/color.js +63 -83
- package/dist/renderer/math/color.js.map +1 -1
- package/dist/renderer/math/mat3.js +15 -11
- package/dist/renderer/math/mat3.js.map +1 -1
- package/dist/renderer/math/viewToMat3.js +1 -1
- package/dist/renderer/math/viewToMat3.js.map +1 -1
- package/dist/renderer/shaders/ShaderProgram.js +13 -13
- package/dist/renderer/shaders/ShaderProgram.js.map +1 -1
- package/dist/renderer/shaders/glyphPaint.js +3 -3
- package/dist/renderer/shaders/glyphPaint.js.map +1 -1
- package/dist/renderer/shaders/gradFill.js +2 -1
- package/dist/renderer/shaders/gradFill.js.map +1 -1
- package/dist/renderer/shaders/pathFill.js +2 -1
- package/dist/renderer/shaders/pathFill.js.map +1 -1
- package/dist/renderer/shaders/patternFill.js +2 -1
- package/dist/renderer/shaders/patternFill.js.map +1 -1
- package/dist/renderer/shaders/registerProgram.js.map +1 -1
- package/dist/renderer/strokeLengths.js.map +1 -1
- package/dist/renderer/textures/registerTexture.js +25 -5
- package/dist/renderer/textures/registerTexture.js.map +1 -1
- package/dist/renderer.d.ts +16 -15
- package/dist/renderer.js +17 -16
- package/dist/resize.d.ts +5 -6
- package/dist/resize.js +5 -5
- package/dist/style.css +1 -1
- package/dist/test-hook/install.js +12 -6
- package/dist/test-hook/install.js.map +1 -1
- package/dist/{test-seams-CuIxe0xY.d.ts → test-seams-DM2B87-g.d.ts} +4 -4
- package/dist/test-seams.d.ts +2 -2
- package/dist/tools/builtin/ellipse/useEllipseTool.js +1 -0
- package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
- package/dist/tools/builtin/hand/useHandTool.js +1 -0
- package/dist/tools/builtin/hand/useHandTool.js.map +1 -1
- package/dist/tools/builtin/lasso/useLassoTool.js +11 -11
- package/dist/tools/builtin/lasso/useLassoTool.js.map +1 -1
- package/dist/tools/builtin/line/useLineTool.js +1 -0
- package/dist/tools/builtin/line/useLineTool.js.map +1 -1
- package/dist/tools/builtin/pen/usePenTool.js +153 -103
- package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
- package/dist/tools/builtin/pencil/usePencilTool.js +1 -0
- package/dist/tools/builtin/pencil/usePencilTool.js.map +1 -1
- package/dist/tools/builtin/polygon/usePolygonTool.js +13 -8
- package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
- package/dist/tools/builtin/rect/useRectTool.js +1 -0
- package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
- package/dist/tools/builtin/select/selectionContributions.js +24 -23
- package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
- package/dist/tools/builtin/select/useSelectTool.js +51 -48
- package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
- package/dist/tools/builtin/shared/pointerFollower.js +17 -0
- package/dist/tools/builtin/shared/pointerFollower.js.map +1 -0
- package/dist/tools/builtin/slice/useSliceTool.js +49 -54
- package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
- package/dist/tools/builtin/star/useStarTool.js +22 -16
- package/dist/tools/builtin/star/useStarTool.js.map +1 -1
- package/dist/tools/builtin/text/useTextTool.js +13 -2
- package/dist/tools/builtin/text/useTextTool.js.map +1 -1
- package/dist/tools/useKeybindings.js +14 -30
- package/dist/tools/useKeybindings.js.map +1 -1
- package/dist/{types-B_9g6hMM.d.ts → types-B4iJRHC3.d.ts} +5 -1
- package/dist/{types-A37hYkeR.d.ts → types-k-5Oqh-U.d.ts} +31 -7
- package/dist/util/color.js +3 -3
- package/dist/util/color.js.map +1 -1
- package/dist/util/paint.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +20 -18
- package/dist/autoPoseDescriptor-vCdt55xm.d.ts +0 -27
- package/dist/builtins-C-VgUlWI.d.ts +0 -4514
- package/dist/core/isPlainObject.js.map +0 -1
- package/dist/debug/flag.js.map +0 -1
- package/dist/interactions/actions/defaults/freshNodeId.js +0 -10
- package/dist/interactions/actions/defaults/freshNodeId.js.map +0 -1
- package/dist/renderer/shaders/imageFill.js +0 -14
- package/dist/renderer/shaders/imageFill.js.map +0 -1
- package/dist/tools/prefs.js +0 -47
- package/dist/tools/prefs.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composeAffordanceLayer.js","names":["composeAffordanceLayer","state","asChromeState","data","debug","asDebugSink","isVisible","asIsVisible","out","t","viewToTransform","view","a","affordances","region","xf","transformOf","paintRegion","recordRegionHitbox","cmd","hit","hitAffordanceRegions","wx","wy","cursor","strength","claimedKinds","maybe","f","pointRegionFrame","quad","pointRegionScreenQuad","x","y","affordanceId","q","s","annulusSemiAxes","angleOf","pts","localToWorld","rx","ry","minX","p","minY","maxX","maxY","r","w","paint","sx","sy","worldToScreen","viewT","half","fill","stroke","c","sn","coords","u","v","i","PATH_M","PATH_L","PATH_Z","insetLocal","pxExtent","annulusCommand","ctx","worldOf","semiAxes","cx","shape","cy","rectHW","rectHH","haveTopBottom","haveLeftRight","xT","yR","cmds","localPts","appendPetal","t0","t1","pointRegionAnchor","b"],"sources":["../../src/affordances/composeAffordanceLayer.ts"],"sourcesContent":["import type { RenderLayer } from 'core/layers/render';\nimport type { DrawCommand } from '../renderer';\nimport type { ChromeState } from 'core/selection/chromeState';\nimport type { View } from 'core/viewport/view';\nimport { viewToTransform } from 'core/viewport/view';\nimport { worldToScreen } from 'core/viewport/viewTransform';\nimport { pxExtent } from 'core/viewport/pxExtent';\nimport type { DebugSink } from '../debug/types';\nimport type { FillStyle, Stroke } from '@weasel-js/paint';\nimport { PATH_M, PATH_L, PATH_Z } from '@weasel-js/geom';\nimport {\n angleOf,\n annulusSemiAxes,\n hitAffordanceRegions,\n localToWorld,\n pointRegionAnchor,\n pointRegionFrame,\n pointRegionScreenQuad,\n transformOf,\n type TargetTransform,\n} from './hitAffordanceRegions';\nimport type {\n Affordance,\n AffordanceRegion,\n CustomPaintContext,\n LayerHit,\n} from './types';\n\n/**\n * @experimental\n * Bundle a list of Affordances into a single RenderLayer.\n *\n * Each affordance declares interactive regions in target-local coordinates;\n * this layer composes each target's bounds transform (rotation around the\n * AABB center, when present) once per region for both paint and hit-test.\n *\n * FillStyle order matches array order (first → last = bottom → top). Hit-test\n * walks regions in reverse (top → bottom) and returns the first non-null\n * binding.\n *\n * `decorate(state, view)` runs *after* a given affordance's regions for\n * paint only — useful for leader lines and other purely-visual chrome that\n * don't fit the region model.\n */\nexport function composeAffordanceLayer(\n id: string,\n label: string,\n affordances: readonly Affordance[],\n): RenderLayer<ChromeState> & {\n hitTest(\n wx: number,\n wy: number,\n state: unknown,\n view: View,\n dims: { width: number; height: number },\n isVisible?: (id: string) => boolean,\n ): LayerHit | null;\n} {\n return {\n id,\n label,\n space: 'screen',\n draw: (data, view, _dims): DrawCommand[] => {\n const state = asChromeState(data);\n const debug = asDebugSink(data);\n const isVisible = asIsVisible(data);\n const out: DrawCommand[] = [];\n const t = viewToTransform(view);\n for (const a of affordances) {\n if (isVisible && !isVisible(a.id)) continue;\n for (const region of a.regions(state)) {\n const xf = transformOf(state, region.targetId);\n if (region.paint) paintRegion(region, xf, view, state, t, out);\n if (debug) recordRegionHitbox(debug, a.id, region, xf, view);\n }\n if (a.decorate) {\n for (const cmd of a.decorate(state, view)) out.push(cmd);\n }\n }\n return out;\n },\n // The walk itself lives in `hitAffordanceRegions` so this layer and\n // `buildAffordanceAt` share one implementation rather than two that can\n // drift. This wrapper lifts the region's declared cursor and claim onto\n // the binding, the way `buildAffordanceAt` lifts them onto `AffordanceHit`.\n hitTest: (wx, wy, data, view, _dims, isVisible): LayerHit | null => {\n const state = asChromeState(data);\n if (!state) return null;\n const hit = hitAffordanceRegions(\n affordances, wx, wy, state, view, isVisible ?? asIsVisible(data) ?? undefined,\n );\n if (!hit) return null;\n const { cursor, strength, claimedKinds } = hit.region;\n return {\n ...hit.binding,\n ...(cursor !== undefined ? { cursor } : {}),\n ...(strength !== undefined ? { strength } : {}),\n ...(claimedKinds !== undefined ? { claimedKinds } : {}),\n };\n },\n };\n}\n\n// ─── Data unwrappers ────────────────────────────────────────────────────────\n\n// `draw` is called by Canvas with `CanvasHelpers` and by tests with a bare\n// `ChromeState`. Tests stay terse this way, and production gets the live\n// debug sink via the same channel.\nfunction asChromeState(data: unknown): ChromeState {\n const maybe = data as { getChromeState?: () => ChromeState };\n if (typeof maybe?.getChromeState === 'function') return maybe.getChromeState();\n return data as ChromeState;\n}\n\nfunction asDebugSink(data: unknown): DebugSink | null {\n const maybe = data as { getDebug?: () => DebugSink | null };\n if (typeof maybe?.getDebug === 'function') return maybe.getDebug();\n return null;\n}\n\n/** Pull a chrome-caps visibility predicate off the data envelope when\n * present. When absent (legacy callers / tests passing bare state),\n * every affordance renders — preserves pre-chrome-caps behavior. */\nfunction asIsVisible(data: unknown): ((id: string) => boolean) | null {\n const maybe = data as { getIsVisible?: () => (id: string) => boolean };\n if (typeof maybe?.getIsVisible === 'function') return maybe.getIsVisible();\n return null;\n}\n\nfunction recordRegionHitbox(\n debug: DebugSink,\n affordanceId: string,\n region: AffordanceRegion,\n xf: TargetTransform,\n view: View,\n): void {\n if (region.shape.kind === 'point') {\n // The hit square is a square on *screen* — turned with the target when the\n // region asks — so in world it is a parallelogram, recorded as one.\n const f = pointRegionFrame(region.shape, xf, view);\n const quad = pointRegionScreenQuad(region.shape, xf, view, (x, y) => [x, y]);\n debug.recordHitbox(affordanceId, 'handle', {\n kind: 'polygon',\n points: quad.map((q) => ({\n x: f.x + (q.x - f.x) / view.scale.x,\n y: f.y + (q.y - f.y) / view.scale.y,\n })),\n });\n return;\n }\n if (region.shape.kind === 'annulus') {\n // Approximate the annulus visualization as the outer ellipse's AABB.\n // The DebugSink's hitbox primitives don't model ring shapes; the\n // always-visible dev paint (`paint.kind === 'annulus'`) draws the\n // actual ring. Consumers that want a precise debug overlay can match\n // against the affordance id and render the annulus themselves.\n const s = region.shape;\n const { rx, ry } = annulusSemiAxes(s, view, angleOf(xf));\n const pts = [\n localToWorld(xf, s.cx + rx, s.cy),\n localToWorld(xf, s.cx - rx, s.cy),\n localToWorld(xf, s.cx, s.cy + ry),\n localToWorld(xf, s.cx, s.cy - ry),\n ];\n const minX = Math.min(...pts.map((p) => p.x));\n const minY = Math.min(...pts.map((p) => p.y));\n const maxX = Math.max(...pts.map((p) => p.x));\n const maxY = Math.max(...pts.map((p) => p.y));\n debug.recordHitbox(affordanceId, 'handle', {\n kind: 'rect',\n x: minX,\n y: minY,\n width: maxX - minX,\n height: maxY - minY,\n ...(xf.identity ? {} : { rotation: Math.atan2(xf.sin, xf.cos) }),\n });\n return;\n }\n // rect: axis-aligned in local frame; apply target rotation around the\n // AABB pivot when present.\n const r = region.shape;\n const w = localToWorld(xf, r.x + r.width / 2, r.y + r.height / 2);\n debug.recordHitbox(affordanceId, 'handle', {\n kind: 'rect',\n x: w.x - r.width / 2,\n y: w.y - r.height / 2,\n width: r.width,\n height: r.height,\n ...(xf.identity ? {} : { rotation: Math.atan2(xf.sin, xf.cos) }),\n });\n}\n\n// ─── FillStyle ──────────────────────────────────────────────────────────────────\n\nfunction paintRegion(\n region: AffordanceRegion,\n xf: TargetTransform,\n view: View,\n state: ChromeState,\n viewT: ReturnType<typeof viewToTransform>,\n out: DrawCommand[],\n): void {\n const paint = region.paint!;\n if (paint.kind === 'square') {\n if (region.shape.kind !== 'point') {\n // 'square' only makes sense over a point shape. Silently skip — a\n // future paint variant can cover rect outlines.\n return;\n }\n // Same frame the hit-test reads, at the paint's own size.\n const f = pointRegionFrame(region.shape, xf, view);\n const [sx, sy] = worldToScreen(f.x, f.y, viewT);\n const half = paint.sizePx / 2;\n const fill = paint.fill ? { fill: paint.fill } : {};\n const stroke = paint.stroke ? { stroke: paint.stroke } : {};\n if (f.angle === 0) {\n out.push({\n kind: 'path',\n path: { kind: 'rect', x: sx - half, y: sy - half, width: paint.sizePx, height: paint.sizePx },\n ...fill, ...stroke,\n });\n return;\n }\n const c = Math.cos(f.angle) * half, sn = Math.sin(f.angle) * half;\n const coords = new Float32Array(8);\n [[-1, -1], [1, -1], [1, 1], [-1, 1]].forEach(([u, v], i) => {\n coords[i * 2] = sx + c * u - sn * v;\n coords[i * 2 + 1] = sy + sn * u + c * v;\n });\n out.push({\n kind: 'path',\n path: { kind: 'polygon', commands: new Uint8Array([PATH_M, PATH_L, PATH_L, PATH_L, PATH_Z]), coords, fillRule: 'nonzero' },\n ...fill, ...stroke,\n });\n return;\n }\n if (paint.kind === 'annulus') {\n if (region.shape.kind !== 'annulus') return; // type mismatch — silent skip\n const s = region.shape;\n // Convert the screen-px inset to target-local units so it visually\n // matches at any zoom. `localToWorld` is rotation+translation (no\n // scale), so target-local units equal world units for ring math.\n const insetLocal = pxExtent(paint.insetPx ?? 0, view.scale, angleOf(xf));\n // Same `minBandPx` clamp the hit-test applies, so the ring you can see is\n // the ring you can grab.\n const cmd = annulusCommand(s, annulusSemiAxes(s, view, angleOf(xf)), insetLocal, xf, viewT, paint.fill, paint.stroke);\n if (cmd) out.push(cmd);\n return;\n }\n if (paint.kind === 'custom') {\n const ctx: CustomPaintContext = {\n world: worldOf(region, xf, view),\n local: region.shape,\n view,\n state,\n };\n for (const cmd of paint.draw(ctx)) out.push(cmd);\n return;\n }\n}\n\n/** Build a screen-space DrawCommand for an annulus region: the area\n * inside the (contracted) outer ellipse but outside the (expanded)\n * inner rect. Renders as four disjoint petal subpaths — one per rect\n * edge — so the visible fill is true `ellipse \\ rect`. A naive\n * ellipse + rect even-odd path would also fill the rect's corner\n * triangles where they poke past the contracted ellipse. Returns null\n * when the band collapses on both axes. */\nfunction annulusCommand(\n shape: Extract<AffordanceRegion['shape'], { kind: 'annulus' }>,\n semiAxes: { rx: number; ry: number },\n insetLocal: { x: number; y: number },\n xf: TargetTransform,\n viewT: ReturnType<typeof viewToTransform>,\n fill: FillStyle | undefined,\n stroke: Stroke | undefined,\n): DrawCommand | null {\n const rx = semiAxes.rx - insetLocal.x;\n const ry = semiAxes.ry - insetLocal.y;\n if (rx <= 0 || ry <= 0) return null;\n const cx = shape.cx;\n const cy = shape.cy;\n // Expanded inner rect (still in target-local coords).\n const rectHW = shape.innerWidth / 2 + insetLocal.x;\n const rectHH = shape.innerHeight / 2 + insetLocal.y;\n // Each petal exists only when the corresponding rect edge actually\n // intersects the ellipse interior — otherwise the rect already\n // extends past the ellipse on that axis and there's no band to show.\n const haveTopBottom = rectHH < ry;\n const haveLeftRight = rectHW < rx;\n if (!haveTopBottom && !haveLeftRight) return null;\n const xT = haveTopBottom ? rx * Math.sqrt(1 - (rectHH / ry) ** 2) : 0;\n const yR = haveLeftRight ? ry * Math.sqrt(1 - (rectHW / rx) ** 2) : 0;\n\n // Sampling density per petal arc — line segments are visually clean\n // for the rotate-band band thickness and avoid the cubic-Bézier\n // sub-arc bookkeeping. 24 is smooth at typical zooms.\n const SAMPLES = 24;\n const cmds: number[] = [];\n const localPts: { x: number; y: number }[] = [];\n\n function appendPetal(t0: number, t1: number): void {\n cmds.push(PATH_M);\n localPts.push({ x: cx + rx * Math.cos(t0), y: cy + ry * Math.sin(t0) });\n for (let i = 1; i <= SAMPLES; i++) {\n const t = t0 + (t1 - t0) * (i / SAMPLES);\n cmds.push(PATH_L);\n localPts.push({ x: cx + rx * Math.cos(t), y: cy + ry * Math.sin(t) });\n }\n // PATH_Z closes back to the move-to point along the rect edge — both\n // endpoints share the rect-edge coord by construction, so the\n // closing segment IS the chord.\n cmds.push(PATH_Z);\n }\n\n if (haveTopBottom) {\n // Top petal: y < cy. Arc from right endpoint over t = -π/2 to left.\n appendPetal(\n Math.atan2(-rectHH / ry, xT / rx), // in (-π/2, 0)\n Math.atan2(-rectHH / ry, -xT / rx), // in (-π, -π/2), decreasing through -π/2\n );\n // Bottom petal: y > cy. Arc through t = +π/2.\n appendPetal(\n Math.atan2(rectHH / ry, xT / rx), // in (0, π/2)\n Math.atan2(rectHH / ry, -xT / rx), // in (π/2, π), increasing through π/2\n );\n }\n if (haveLeftRight) {\n // Right petal: x > cx. Arc through t = 0.\n appendPetal(\n Math.atan2(-yR / ry, rectHW / rx), // in (-π/2, 0)\n Math.atan2(yR / ry, rectHW / rx), // in (0, π/2)\n );\n // Left petal: x < cx. Arc through t = ±π. Shift the end angle by\n // -2π so the parametric trace passes through the leftmost point\n // monotonically instead of crossing the right side.\n appendPetal(\n Math.atan2(-yR / ry, -rectHW / rx), // in (-π, -π/2)\n Math.atan2(yR / ry, -rectHW / rx) - 2 * Math.PI, // in (-3π/2, -π), decreasing through -π\n );\n }\n\n const coords = new Float32Array(localPts.length * 2);\n for (let i = 0; i < localPts.length; i++) {\n const w = localToWorld(xf, localPts[i].x, localPts[i].y);\n const [sx, sy] = worldToScreen(w.x, w.y, viewT);\n coords[i * 2] = sx;\n coords[i * 2 + 1] = sy;\n }\n return {\n kind: 'path',\n path: { kind: 'polygon', commands: new Uint8Array(cmds), coords, fillRule: 'nonzero' },\n ...(fill ? { fill } : {}),\n ...(stroke ? { stroke } : {}),\n };\n}\n\nfunction worldOf(region: AffordanceRegion, xf: TargetTransform, view: View): CustomPaintContext['world'] {\n if (region.shape.kind === 'point') {\n const w = pointRegionAnchor(region.shape, xf, view);\n return { x: w.x, y: w.y };\n }\n if (region.shape.kind === 'annulus') {\n // Outer ellipse bounding box, in world coords. Map the four cardinal\n // extrema through the target transform and AABB the result. (Rotated\n // ellipses are still ellipses; their AABB needs all four points.)\n const s = region.shape;\n const { rx, ry } = annulusSemiAxes(s, view, angleOf(xf));\n const pts = [\n localToWorld(xf, s.cx + rx, s.cy),\n localToWorld(xf, s.cx - rx, s.cy),\n localToWorld(xf, s.cx, s.cy + ry),\n localToWorld(xf, s.cx, s.cy - ry),\n ];\n const minX = Math.min(...pts.map((p) => p.x));\n const minY = Math.min(...pts.map((p) => p.y));\n const maxX = Math.max(...pts.map((p) => p.x));\n const maxY = Math.max(...pts.map((p) => p.y));\n return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n }\n // Rect: map the rect's origin and far corner; width/height under rotation\n // describe the AABB of the rotated rect, which is what custom-paint\n // consumers most often want. Affordances needing exact rotated geometry\n // can read `local` + reapply rotation themselves via `view`/`state`.\n const r = region.shape;\n const a = localToWorld(xf, r.x, r.y);\n const b = localToWorld(xf, r.x + r.width, r.y);\n const c = localToWorld(xf, r.x, r.y + r.height);\n const d = localToWorld(xf, r.x + r.width, r.y + r.height);\n const minX = Math.min(a.x, b.x, c.x, d.x);\n const minY = Math.min(a.y, b.y, c.y, d.y);\n const maxX = Math.max(a.x, b.x, c.x, d.x);\n const maxY = Math.max(a.y, b.y, c.y, d.y);\n return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n}\n\n// Hit-testing lives in `hitAffordanceRegions.ts` — see `hitRegion` there.\n"],"mappings":";;;;;;AA4CA,SAAgBA,EACd,GACA,GACA,GAUA;CACA,OAAO;EACL;EACA;EACA,OAAO;EACP,OAAO,GAAM,GAAM,MAAyB;GAC1C,IAAMC,IAAQC,EAAcC,CAAI,GAC1BC,IAAQC,EAAYF,CAAI,GACxBG,IAAYC,EAAYJ,CAAI,GAC5BK,IAAqB,CAAC,GACtBC,IAAIC,EAAgBC,CAAI;GAC9B,KAAK,IAAMC,KAAKC,GACd,IAAI,MAAcP,EAAUM,EAAE,EAAE,GAChC;SAAK,IAAME,KAAUF,EAAE,QAAQX,CAAK,GAAG;KACrC,IAAMc,IAAKC,EAAYf,GAAOa,EAAO,QAAQ;KAE7C,AADIA,EAAO,SAAOG,EAAYH,GAAQC,GAAIJ,GAAMV,GAAOQ,GAAGD,CAAG,GACzDJ,KAAOc,EAAmBd,GAAOQ,EAAE,IAAIE,GAAQC,GAAIJ,CAAI;IAC7D;IACA,IAAIC,EAAE,UACJ,KAAK,IAAMO,KAAOP,EAAE,SAASX,GAAOU,CAAI,GAAG,EAAI,KAAKQ,CAAG;GAFzD;GAKF,OAAOX;EACT;EAKA,UAAU,GAAI,GAAI,GAAM,GAAM,GAAO,MAA+B;GAClE,IAAMP,IAAQC,EAAcC,CAAI;GAChC,IAAI,CAACF,GAAO,OAAO;GACnB,IAAMmB,IAAMC,EACVR,GAAaS,GAAIC,GAAItB,GAAOU,GAAML,KAAaC,EAAYJ,CAAI,KAAK,KAAA,CACtE;GACA,IAAI,CAACiB,GAAK,OAAO;GACjB,IAAM,EAAE,WAAQ,aAAU,oBAAiBA,EAAI;GAC/C,OAAO;IACL,GAAGA,EAAI;IACP,GAAII,MAAW,KAAA,IAAyB,CAAC,IAAd,EAAE,UAAO;IACpC,GAAIC,MAAa,KAAA,IAA2B,CAAC,IAAhB,EAAE,YAAS;IACxC,GAAIC,MAAiB,KAAA,IAA+B,CAAC,IAApB,EAAE,gBAAa;GAClD;EACF;CACF;AACF;AAOA,SAASxB,EAAc,GAA4B;CACjD,IAAMyB,IAAQxB;CAEd,OADI,OAAOwB,GAAO,kBAAmB,aAAmBA,EAAM,eAAe,IACtExB;AACT;AAEA,SAASE,EAAY,GAAiC;CACpD,IAAMsB,IAAQxB;CAEd,OADI,OAAOwB,GAAO,YAAa,aAAmBA,EAAM,SAAS,IAC1D;AACT;AAKA,SAASpB,EAAY,GAAiD;CACpE,IAAMoB,IAAQxB;CAEd,OADI,OAAOwB,GAAO,gBAAiB,aAAmBA,EAAM,aAAa,IAClE;AACT;AAEA,SAAST,EACP,GACA,GACA,GACA,GACA,GACM;CACN,IAAIJ,EAAO,MAAM,SAAS,SAAS;EAGjC,IAAMc,IAAIC,EAAiBf,EAAO,OAAOC,GAAIJ,CAAI,GAC3CmB,IAAOC,EAAsBjB,EAAO,OAAOC,GAAIJ,IAAO,GAAG,MAAM,CAACqB,GAAGC,CAAC,CAAC;EAC3E,EAAM,aAAaC,GAAc,UAAU;GACzC,MAAM;GACN,QAAQJ,EAAK,KAAK,OAAO;IACvB,GAAGF,EAAE,KAAKO,EAAE,IAAIP,EAAE,KAAKjB,EAAK,MAAM;IAClC,GAAGiB,EAAE,KAAKO,EAAE,IAAIP,EAAE,KAAKjB,EAAK,MAAM;GACpC,EAAE;EACJ,CAAC;EACD;CACF;CACA,IAAIG,EAAO,MAAM,SAAS,WAAW;EAMnC,IAAMsB,IAAItB,EAAO,OACX,EAAE,OAAI,UAAOuB,EAAgBD,GAAGzB,GAAM2B,EAAQvB,CAAE,CAAC,GACjDwB,IAAM;GACVC,EAAazB,GAAIqB,EAAE,KAAKK,GAAIL,EAAE,EAAE;GAChCI,EAAazB,GAAIqB,EAAE,KAAKK,GAAIL,EAAE,EAAE;GAChCI,EAAazB,GAAIqB,EAAE,IAAIA,EAAE,KAAKM,CAAE;GAChCF,EAAazB,GAAIqB,EAAE,IAAIA,EAAE,KAAKM,CAAE;EAClC,GACMC,IAAO,KAAK,IAAI,GAAGJ,EAAI,KAAK,MAAMK,EAAE,CAAC,CAAC,GACtCC,IAAO,KAAK,IAAI,GAAGN,EAAI,KAAK,MAAMK,EAAE,CAAC,CAAC,GACtCE,IAAO,KAAK,IAAI,GAAGP,EAAI,KAAK,MAAMK,EAAE,CAAC,CAAC,GACtCG,IAAO,KAAK,IAAI,GAAGR,EAAI,KAAK,MAAMK,EAAE,CAAC,CAAC;EAC5C,EAAM,aAAaV,GAAc,UAAU;GACzC,MAAM;GACN,GAAGS;GACH,GAAGE;GACH,OAAOC,IAAOH;GACd,QAAQI,IAAOF;GACf,GAAI9B,EAAG,WAAW,CAAC,IAAI,EAAE,UAAU,KAAK,MAAMA,EAAG,KAAKA,EAAG,GAAG,EAAE;EAChE,CAAC;EACD;CACF;CAGA,IAAMiC,IAAIlC,EAAO,OACXmC,IAAIT,EAAazB,GAAIiC,EAAE,IAAIA,EAAE,QAAQ,GAAGA,EAAE,IAAIA,EAAE,SAAS,CAAC;CAChE,EAAM,aAAad,GAAc,UAAU;EACzC,MAAM;EACN,GAAGe,EAAE,IAAID,EAAE,QAAQ;EACnB,GAAGC,EAAE,IAAID,EAAE,SAAS;EACpB,OAAOA,EAAE;EACT,QAAQA,EAAE;EACV,GAAIjC,EAAG,WAAW,CAAC,IAAI,EAAE,UAAU,KAAK,MAAMA,EAAG,KAAKA,EAAG,GAAG,EAAE;CAChE,CAAC;AACH;AAIA,SAASE,EACP,GACA,GACA,GACA,GACA,GACA,GACM;CACN,IAAMiC,IAAQpC,EAAO;CACrB,IAAIoC,EAAM,SAAS,UAAU;EAC3B,IAAIpC,EAAO,MAAM,SAAS,SAGxB;EAGF,IAAMc,IAAIC,EAAiBf,EAAO,OAAOC,GAAIJ,CAAI,GAC3C,CAACwC,GAAIC,KAAMC,EAAczB,EAAE,GAAGA,EAAE,GAAG0B,CAAK,GACxCC,IAAOL,EAAM,SAAS,GACtBM,IAAON,EAAM,OAAO,EAAE,MAAMA,EAAM,KAAK,IAAI,CAAC,GAC5CO,IAASP,EAAM,SAAS,EAAE,QAAQA,EAAM,OAAO,IAAI,CAAC;EAC1D,IAAItB,EAAE,UAAU,GAAG;GACjB,EAAI,KAAK;IACP,MAAM;IACN,MAAM;KAAE,MAAM;KAAQ,GAAGuB,IAAKI;KAAM,GAAGH,IAAKG;KAAM,OAAOL,EAAM;KAAQ,QAAQA,EAAM;IAAO;IAC5F,GAAGM;IAAM,GAAGC;GACd,CAAC;GACD;EACF;EACA,IAAMC,IAAI,KAAK,IAAI9B,EAAE,KAAK,IAAI2B,GAAMI,IAAK,KAAK,IAAI/B,EAAE,KAAK,IAAI2B,GACvDK,oBAAS,IAAI,aAAa,CAAC;EAKjC,AAJA;GAAC,CAAC,IAAI,EAAE;GAAG,CAAC,GAAG,EAAE;GAAG,CAAC,GAAG,CAAC;GAAG,CAAC,IAAI,CAAC;EAAC,CAAC,CAAC,SAAS,CAACC,GAAGC,IAAI,MAAM;GAE1D,AADA,EAAOC,IAAI,KAAKZ,IAAKO,IAAIG,IAAIF,IAAKG,GAClC,EAAOC,IAAI,IAAI,KAAKX,IAAKO,IAAKE,IAAIH,IAAII;EACxC,CAAC,GACDtD,EAAI,KAAK;GACP,MAAM;GACN,MAAM;IAAE,MAAM;IAAW,UAAU,IAAI,WAAW;KAACwD;KAAQC;KAAQA;KAAQA;KAAQC;IAAM,CAAC;IAAG;IAAQ,UAAU;GAAU;GACzH,GAAGV;GAAM,GAAGC;EACd,CAAC;EACD;CACF;CACA,IAAIP,EAAM,SAAS,WAAW;EAC5B,IAAIpC,EAAO,MAAM,SAAS,WAAW;EACrC,IAAMsB,IAAItB,EAAO,OAIXqD,IAAaC,EAASlB,EAAM,WAAW,GAAGvC,EAAK,OAAO2B,EAAQvB,CAAE,CAAC,GAGjEI,IAAMkD,EAAejC,GAAGC,EAAgBD,GAAGzB,GAAM2B,EAAQvB,CAAE,CAAC,GAAGoD,GAAYpD,GAAIuC,GAAOJ,EAAM,MAAMA,EAAM,MAAM;EACpH,AAAI/B,KAAKX,EAAI,KAAKW,CAAG;EACrB;CACF;CACA,IAAI+B,EAAM,SAAS,UAAU;EAC3B,IAAMoB,IAA0B;GAC9B,OAAOC,EAAQzD,GAAQC,GAAIJ,CAAI;GAC/B,OAAOG,EAAO;GACd;GACA;EACF;EACA,KAAK,IAAMK,KAAO+B,EAAM,KAAKoB,CAAG,GAAG,EAAI,KAAKnD,CAAG;EAC/C;CACF;AACF;AASA,SAASkD,EACP,GACA,GACA,GACA,GACA,GACA,GACA,GACoB;CACpB,IAAM5B,IAAK+B,EAAS,KAAKL,EAAW,GAC9BzB,IAAK8B,EAAS,KAAKL,EAAW;CACpC,IAAI1B,KAAM,KAAKC,KAAM,GAAG,OAAO;CAC/B,IAAM+B,IAAKC,EAAM,IACXC,IAAKD,EAAM,IAEXE,IAASF,EAAM,aAAa,IAAIP,EAAW,GAC3CU,IAASH,EAAM,cAAc,IAAIP,EAAW,GAI5CW,IAAgBD,IAASnC,GACzBqC,IAAgBH,IAASnC;CAC/B,IAAI,CAACqC,KAAiB,CAACC,GAAe,OAAO;CAC7C,IAAMC,IAAKF,IAAgBrC,IAAK,KAAK,KAAK,KAAKoC,IAASnC,MAAO,CAAC,IAAI,GAC9DuC,IAAKF,IAAgBrC,IAAK,KAAK,KAAK,KAAKkC,IAASnC,MAAO,CAAC,IAAI,GAM9DyC,IAAiB,CAAC,GAClBC,IAAuC,CAAC;CAE9C,SAASC,EAAY,GAAY,GAAkB;EAEjD,AADAF,EAAK,KAAKlB,CAAM,GAChBmB,EAAS,KAAK;GAAE,GAAGV,IAAKhC,IAAK,KAAK,IAAI4C,CAAE;GAAG,GAAGV,IAAKjC,IAAK,KAAK,IAAI2C,CAAE;EAAE,CAAC;EACtE,KAAK,IAAItB,IAAI,GAAGA,KAAK,IAAS,KAAK;GACjC,IAAMtD,IAAI4E,KAAMC,IAAKD,MAAOtB,IAAI;GAEhC,AADAmB,EAAK,KAAKjB,CAAM,GAChBkB,EAAS,KAAK;IAAE,GAAGV,IAAKhC,IAAK,KAAK,IAAIhC,CAAC;IAAG,GAAGkE,IAAKjC,IAAK,KAAK,IAAIjC,CAAC;GAAE,CAAC;EACtE;EAIA,EAAK,KAAKyD,CAAM;CAClB;CAcA,AAZIY,MAEFM,EACE,KAAK,MAAM,CAACP,IAASnC,GAAIsC,IAAKvC,CAAE,GAChC,KAAK,MAAM,CAACoC,IAASnC,GAAI,CAACsC,IAAKvC,CAAE,CACnC,GAEA2C,EACE,KAAK,MAAMP,IAASnC,GAAIsC,IAAKvC,CAAE,GAC/B,KAAK,MAAMoC,IAASnC,GAAI,CAACsC,IAAKvC,CAAE,CAClC,IAEEsC,MAEFK,EACE,KAAK,MAAM,CAACH,IAAKvC,GAAIkC,IAASnC,CAAE,GAChC,KAAK,MAAMwC,IAAKvC,GAAIkC,IAASnC,CAAE,CACjC,GAIA2C,EACE,KAAK,MAAM,CAACH,IAAKvC,GAAI,CAACkC,IAASnC,CAAE,GACjC,KAAK,MAAMwC,IAAKvC,GAAI,CAACkC,IAASnC,CAAE,IAAI,IAAI,KAAK,EAC/C;CAGF,IAAMmB,IAAS,IAAI,aAAauB,EAAS,SAAS,CAAC;CACnD,KAAK,IAAIpB,IAAI,GAAGA,IAAIoB,EAAS,QAAQ,KAAK;EACxC,IAAMlC,IAAIT,EAAazB,GAAIoE,EAASpB,EAAE,CAAC,GAAGoB,EAASpB,EAAE,CAAC,CAAC,GACjD,CAACZ,GAAIC,KAAMC,EAAcJ,EAAE,GAAGA,EAAE,GAAGK,CAAK;EAE9C,AADA,EAAOS,IAAI,KAAKZ,GAChB,EAAOY,IAAI,IAAI,KAAKX;CACtB;CACA,OAAO;EACL,MAAM;EACN,MAAM;GAAE,MAAM;GAAW,UAAU,IAAI,WAAW8B,CAAI;GAAG;GAAQ,UAAU;EAAU;EACrF,GAAI1B,IAAO,EAAE,QAAK,IAAI,CAAC;EACvB,GAAIC,IAAS,EAAE,UAAO,IAAI,CAAC;CAC7B;AACF;AAEA,SAASc,EAAQ,GAA0B,GAAqB,GAAyC;CACvG,IAAIzD,EAAO,MAAM,SAAS,SAAS;EACjC,IAAMmC,IAAIsC,EAAkBzE,EAAO,OAAOC,GAAIJ,CAAI;EAClD,OAAO;GAAE,GAAGsC,EAAE;GAAG,GAAGA,EAAE;EAAE;CAC1B;CACA,IAAInC,EAAO,MAAM,SAAS,WAAW;EAInC,IAAMsB,IAAItB,EAAO,OACX,EAAE,OAAI,UAAOuB,EAAgBD,GAAGzB,GAAM2B,EAAQvB,CAAE,CAAC,GACjDwB,IAAM;GACVC,EAAazB,GAAIqB,EAAE,KAAKK,GAAIL,EAAE,EAAE;GAChCI,EAAazB,GAAIqB,EAAE,KAAKK,GAAIL,EAAE,EAAE;GAChCI,EAAazB,GAAIqB,EAAE,IAAIA,EAAE,KAAKM,CAAE;GAChCF,EAAazB,GAAIqB,EAAE,IAAIA,EAAE,KAAKM,CAAE;EAClC,GACMC,IAAO,KAAK,IAAI,GAAGJ,EAAI,KAAK,MAAMK,EAAE,CAAC,CAAC,GACtCC,IAAO,KAAK,IAAI,GAAGN,EAAI,KAAK,MAAMK,EAAE,CAAC,CAAC,GACtCE,IAAO,KAAK,IAAI,GAAGP,EAAI,KAAK,MAAMK,EAAE,CAAC,CAAC,GACtCG,IAAO,KAAK,IAAI,GAAGR,EAAI,KAAK,MAAMK,EAAE,CAAC,CAAC;EAC5C,OAAO;GAAE,GAAGD;GAAM,GAAGE;GAAM,OAAOC,IAAOH;GAAM,QAAQI,IAAOF;EAAK;CACrE;CAKA,IAAMG,IAAIlC,EAAO,OACXF,IAAI4B,EAAazB,GAAIiC,EAAE,GAAGA,EAAE,CAAC,GAC7BwC,IAAIhD,EAAazB,GAAIiC,EAAE,IAAIA,EAAE,OAAOA,EAAE,CAAC,GACvCU,IAAIlB,EAAazB,GAAIiC,EAAE,GAAGA,EAAE,IAAIA,EAAE,MAAM,GACxC,IAAIR,EAAazB,GAAIiC,EAAE,IAAIA,EAAE,OAAOA,EAAE,IAAIA,EAAE,MAAM,GAClDL,IAAO,KAAK,IAAI/B,EAAE,GAAG4E,EAAE,GAAG9B,EAAE,GAAG,EAAE,CAAC,GAClCb,IAAO,KAAK,IAAIjC,EAAE,GAAG4E,EAAE,GAAG9B,EAAE,GAAG,EAAE,CAAC,GAClCZ,IAAO,KAAK,IAAIlC,EAAE,GAAG4E,EAAE,GAAG9B,EAAE,GAAG,EAAE,CAAC,GAClCX,IAAO,KAAK,IAAInC,EAAE,GAAG4E,EAAE,GAAG9B,EAAE,GAAG,EAAE,CAAC;CACxC,OAAO;EAAE,GAAGf;EAAM,GAAGE;EAAM,OAAOC,IAAOH;EAAM,QAAQI,IAAOF;CAAK;AACrE"}
|
|
1
|
+
{"version":3,"file":"composeAffordanceLayer.js","names":["composeAffordanceLayer","state","asChromeState","data","debug","asDebugSink","isVisible","asIsVisible","out","t","viewToTransform","view","a","affordances","region","xf","transformOf","paintRegion","recordRegionHitbox","cmd","hit","hitAffordanceRegions","wx","wy","cursor","strength","claimedKinds","maybe","f","pointRegionFrame","quad","pointRegionScreenQuad","x","y","affordanceId","q","s","annulusSemiAxes","angleOf","c","localToWorld","rx","ry","r","w","paint","sx","sy","worldToScreen","viewT","half","fill","stroke","sn","coords","u","v","i","PATH_M","PATH_L","PATH_Z","insetLocal","pxExtent","annulusCommand","ctx","worldOf","semiAxes","cx","shape","cy","rectHW","rectHH","haveTopBottom","haveLeftRight","xT","yR","cmds","localPts","appendPetal","t0","t1","pointRegionAnchor","hw","hh","b","minX","minY","maxX","maxY"],"sources":["../../src/affordances/composeAffordanceLayer.ts"],"sourcesContent":["import type { RenderLayer } from 'core/layers/render';\nimport type { DrawCommand } from '../renderer';\nimport type { ChromeState } from 'core/selection/chromeState';\nimport type { View } from 'core/viewport/view';\nimport { viewToTransform } from 'core/viewport/view';\nimport { worldToScreen } from 'core/viewport/viewTransform';\nimport { pxExtent } from 'core/viewport/pxExtent';\nimport type { DebugSink } from '../debug/types';\nimport type { CommandFill, CommandStroke } from '@weasel-js/paint';\nimport { PATH_M, PATH_L, PATH_Z } from '@weasel-js/geom';\nimport {\n angleOf,\n annulusSemiAxes,\n hitAffordanceRegions,\n localToWorld,\n pointRegionAnchor,\n pointRegionFrame,\n pointRegionScreenQuad,\n transformOf,\n type TargetTransform,\n} from './hitAffordanceRegions';\nimport type {\n Affordance,\n AffordanceRegion,\n CustomPaintContext,\n LayerHit,\n} from './types';\n\n/**\n * @experimental\n * Bundle a list of Affordances into a single RenderLayer.\n *\n * Each affordance declares interactive regions in target-local coordinates;\n * this layer composes each target's bounds transform (rotation around the\n * AABB center, when present) once per region for both paint and hit-test.\n *\n * FillStyle order matches array order (first → last = bottom → top). Hit-test\n * walks affordances in reverse (top → bottom) and stops at the first that\n * claims the point; within it the nearest region wins (see\n * `hitAffordanceRegions`).\n *\n * `decorate(state, view)` runs *after* a given affordance's regions for\n * paint only — useful for leader lines and other purely-visual chrome that\n * don't fit the region model.\n */\nexport function composeAffordanceLayer(\n id: string,\n label: string,\n affordances: readonly Affordance[],\n): RenderLayer<ChromeState> & {\n hitTest(\n wx: number,\n wy: number,\n state: unknown,\n view: View,\n dims: { width: number; height: number },\n isVisible?: (id: string) => boolean,\n ): LayerHit | null;\n} {\n return {\n id,\n label,\n space: 'screen',\n draw: (data, view, _dims): DrawCommand[] => {\n const state = asChromeState(data);\n const debug = asDebugSink(data);\n const isVisible = asIsVisible(data);\n const out: DrawCommand[] = [];\n const t = viewToTransform(view);\n for (const a of affordances) {\n if (isVisible && !isVisible(a.id)) continue;\n for (const region of a.regions(state)) {\n const xf = transformOf(state, region.targetId);\n if (region.paint) paintRegion(region, xf, view, state, t, out);\n if (debug) recordRegionHitbox(debug, a.id, region, xf, view);\n }\n if (a.decorate) {\n for (const cmd of a.decorate(state, view)) out.push(cmd);\n }\n }\n return out;\n },\n // The walk itself lives in `hitAffordanceRegions` so this layer and\n // `buildAffordanceAt` share one implementation rather than two that can\n // drift. This wrapper lifts the region's declared cursor and claim onto\n // the binding, the way `buildAffordanceAt` lifts them onto `AffordanceHit`.\n hitTest: (wx, wy, data, view, _dims, isVisible): LayerHit | null => {\n const state = asChromeState(data);\n if (!state) return null;\n const hit = hitAffordanceRegions(\n affordances, wx, wy, state, view, isVisible ?? asIsVisible(data) ?? undefined,\n );\n if (!hit) return null;\n const { cursor, strength, claimedKinds } = hit.region;\n return {\n ...hit.binding,\n ...(cursor !== undefined ? { cursor } : {}),\n ...(strength !== undefined ? { strength } : {}),\n ...(claimedKinds !== undefined ? { claimedKinds } : {}),\n };\n },\n };\n}\n\n// ─── Data unwrappers ────────────────────────────────────────────────────────\n\n// `draw` is called by Canvas with `CanvasHelpers` and by tests with a bare\n// `ChromeState`. Tests stay terse this way, and production gets the live\n// debug sink via the same channel.\nfunction asChromeState(data: unknown): ChromeState {\n const maybe = data as { getChromeState?: () => ChromeState };\n if (typeof maybe?.getChromeState === 'function') return maybe.getChromeState();\n return data as ChromeState;\n}\n\nfunction asDebugSink(data: unknown): DebugSink | null {\n const maybe = data as { getDebug?: () => DebugSink | null };\n if (typeof maybe?.getDebug === 'function') return maybe.getDebug();\n return null;\n}\n\n/** Pull a chrome-caps visibility predicate off the data envelope when\n * present. When absent (legacy callers / tests passing bare state),\n * every affordance renders — preserves pre-chrome-caps behavior. */\nfunction asIsVisible(data: unknown): ((id: string) => boolean) | null {\n const maybe = data as { getIsVisible?: () => (id: string) => boolean };\n if (typeof maybe?.getIsVisible === 'function') return maybe.getIsVisible();\n return null;\n}\n\nfunction recordRegionHitbox(\n debug: DebugSink,\n affordanceId: string,\n region: AffordanceRegion,\n xf: TargetTransform,\n view: View,\n): void {\n if (region.shape.kind === 'point') {\n // The hit square is a square on *screen* — turned with the target when the\n // region asks — so in world it is a parallelogram, recorded as one.\n const f = pointRegionFrame(region.shape, xf, view);\n const quad = pointRegionScreenQuad(region.shape, xf, view, (x, y) => [x, y]);\n debug.recordHitbox(affordanceId, 'handle', {\n kind: 'polygon',\n points: quad.map((q) => ({\n x: f.x + (q.x - f.x) / view.scale.x,\n y: f.y + (q.y - f.y) / view.scale.y,\n })),\n });\n return;\n }\n if (region.shape.kind === 'annulus') {\n // Approximate the annulus visualization as the outer ellipse's AABB.\n // The DebugSink's hitbox primitives don't model ring shapes; the\n // always-visible dev paint (`paint.kind === 'annulus'`) draws the\n // actual ring. Consumers that want a precise debug overlay can match\n // against the affordance id and render the annulus themselves.\n const s = region.shape;\n const { rx, ry } = annulusSemiAxes(s, view, angleOf(xf));\n const c = localToWorld(xf, s.cx, s.cy);\n debug.recordHitbox(affordanceId, 'handle', {\n kind: 'rect',\n x: c.x - rx,\n y: c.y - ry,\n width: 2 * rx,\n height: 2 * ry,\n ...(xf.identity ? {} : { rotation: Math.atan2(xf.sin, xf.cos) }),\n });\n return;\n }\n // rect: axis-aligned in local frame; apply target rotation around the\n // AABB pivot when present.\n const r = region.shape;\n const w = localToWorld(xf, r.x + r.width / 2, r.y + r.height / 2);\n debug.recordHitbox(affordanceId, 'handle', {\n kind: 'rect',\n x: w.x - r.width / 2,\n y: w.y - r.height / 2,\n width: r.width,\n height: r.height,\n ...(xf.identity ? {} : { rotation: Math.atan2(xf.sin, xf.cos) }),\n });\n}\n\n// ─── FillStyle ──────────────────────────────────────────────────────────────────\n\nfunction paintRegion(\n region: AffordanceRegion,\n xf: TargetTransform,\n view: View,\n state: ChromeState,\n viewT: ReturnType<typeof viewToTransform>,\n out: DrawCommand[],\n): void {\n const paint = region.paint!;\n if (paint.kind === 'square') {\n if (region.shape.kind !== 'point') {\n // 'square' only makes sense over a point shape. Silently skip — a\n // future paint variant can cover rect outlines.\n return;\n }\n // Same frame the hit-test reads, at the paint's own size.\n const f = pointRegionFrame(region.shape, xf, view);\n const [sx, sy] = worldToScreen(f.x, f.y, viewT);\n const half = paint.sizePx / 2;\n const fill = paint.fill ? { fill: paint.fill } : {};\n const stroke = paint.stroke ? { stroke: paint.stroke } : {};\n if (f.angle === 0) {\n out.push({\n kind: 'path',\n path: { kind: 'rect', x: sx - half, y: sy - half, width: paint.sizePx, height: paint.sizePx },\n ...fill, ...stroke,\n });\n return;\n }\n const c = Math.cos(f.angle) * half, sn = Math.sin(f.angle) * half;\n const coords = new Float32Array(8);\n [[-1, -1], [1, -1], [1, 1], [-1, 1]].forEach(([u, v], i) => {\n coords[i * 2] = sx + c * u - sn * v;\n coords[i * 2 + 1] = sy + sn * u + c * v;\n });\n out.push({\n kind: 'path',\n path: { kind: 'polygon', commands: new Uint8Array([PATH_M, PATH_L, PATH_L, PATH_L, PATH_Z]), coords, fillRule: 'nonzero' },\n ...fill, ...stroke,\n });\n return;\n }\n if (paint.kind === 'annulus') {\n if (region.shape.kind !== 'annulus') return; // type mismatch — silent skip\n const s = region.shape;\n // Convert the screen-px inset to target-local units so it visually\n // matches at any zoom. `localToWorld` is rotation+translation (no\n // scale), so target-local units equal world units for ring math.\n const insetLocal = pxExtent(paint.insetPx ?? 0, view.scale, angleOf(xf));\n // Same `minBandPx` clamp the hit-test applies, so the ring you can see is\n // the ring you can grab.\n const cmd = annulusCommand(s, annulusSemiAxes(s, view, angleOf(xf)), insetLocal, xf, viewT, paint.fill, paint.stroke);\n if (cmd) out.push(cmd);\n return;\n }\n if (paint.kind === 'custom') {\n const ctx: CustomPaintContext = {\n world: worldOf(region, xf, view),\n local: region.shape,\n view,\n state,\n };\n for (const cmd of paint.draw(ctx)) out.push(cmd);\n return;\n }\n}\n\n/** Build a screen-space DrawCommand for an annulus region: the area\n * inside the (contracted) outer ellipse but outside the (expanded)\n * inner rect. Renders as four disjoint petal subpaths — one per rect\n * edge — so the visible fill is true `ellipse \\ rect`. A naive\n * ellipse + rect even-odd path would also fill the rect's corner\n * triangles where they poke past the contracted ellipse. Returns null\n * when the band collapses on both axes. */\nfunction annulusCommand(\n shape: Extract<AffordanceRegion['shape'], { kind: 'annulus' }>,\n semiAxes: { rx: number; ry: number },\n insetLocal: { x: number; y: number },\n xf: TargetTransform,\n viewT: ReturnType<typeof viewToTransform>,\n fill: CommandFill | undefined,\n stroke: CommandStroke | undefined,\n): DrawCommand | null {\n const rx = semiAxes.rx - insetLocal.x;\n const ry = semiAxes.ry - insetLocal.y;\n if (rx <= 0 || ry <= 0) return null;\n const cx = shape.cx;\n const cy = shape.cy;\n // Expanded inner rect (still in target-local coords).\n const rectHW = shape.innerWidth / 2 + insetLocal.x;\n const rectHH = shape.innerHeight / 2 + insetLocal.y;\n // Each petal exists only when the corresponding rect edge actually\n // intersects the ellipse interior — otherwise the rect already\n // extends past the ellipse on that axis and there's no band to show.\n const haveTopBottom = rectHH < ry;\n const haveLeftRight = rectHW < rx;\n if (!haveTopBottom && !haveLeftRight) return null;\n const xT = haveTopBottom ? rx * Math.sqrt(1 - (rectHH / ry) ** 2) : 0;\n const yR = haveLeftRight ? ry * Math.sqrt(1 - (rectHW / rx) ** 2) : 0;\n\n // Sampling density per petal arc — line segments are visually clean\n // for the rotate-band band thickness and avoid the cubic-Bézier\n // sub-arc bookkeeping. 24 is smooth at typical zooms.\n const SAMPLES = 24;\n const cmds: number[] = [];\n const localPts: { x: number; y: number }[] = [];\n\n function appendPetal(t0: number, t1: number): void {\n cmds.push(PATH_M);\n localPts.push({ x: cx + rx * Math.cos(t0), y: cy + ry * Math.sin(t0) });\n for (let i = 1; i <= SAMPLES; i++) {\n const t = t0 + (t1 - t0) * (i / SAMPLES);\n cmds.push(PATH_L);\n localPts.push({ x: cx + rx * Math.cos(t), y: cy + ry * Math.sin(t) });\n }\n // PATH_Z closes back to the move-to point along the rect edge — both\n // endpoints share the rect-edge coord by construction, so the\n // closing segment IS the chord.\n cmds.push(PATH_Z);\n }\n\n if (haveTopBottom) {\n // Top petal: y < cy. Arc from right endpoint over t = -π/2 to left.\n appendPetal(\n Math.atan2(-rectHH / ry, xT / rx), // in (-π/2, 0)\n Math.atan2(-rectHH / ry, -xT / rx), // in (-π, -π/2), decreasing through -π/2\n );\n // Bottom petal: y > cy. Arc through t = +π/2.\n appendPetal(\n Math.atan2(rectHH / ry, xT / rx), // in (0, π/2)\n Math.atan2(rectHH / ry, -xT / rx), // in (π/2, π), increasing through π/2\n );\n }\n if (haveLeftRight) {\n // Right petal: x > cx. Arc through t = 0.\n appendPetal(\n Math.atan2(-yR / ry, rectHW / rx), // in (-π/2, 0)\n Math.atan2(yR / ry, rectHW / rx), // in (0, π/2)\n );\n // Left petal: x < cx. Arc through t = ±π. Shift the end angle by\n // -2π so the parametric trace passes through the leftmost point\n // monotonically instead of crossing the right side.\n appendPetal(\n Math.atan2(-yR / ry, -rectHW / rx), // in (-π, -π/2)\n Math.atan2(yR / ry, -rectHW / rx) - 2 * Math.PI, // in (-3π/2, -π), decreasing through -π\n );\n }\n\n const coords = new Float32Array(localPts.length * 2);\n for (let i = 0; i < localPts.length; i++) {\n const w = localToWorld(xf, localPts[i].x, localPts[i].y);\n const [sx, sy] = worldToScreen(w.x, w.y, viewT);\n coords[i * 2] = sx;\n coords[i * 2 + 1] = sy;\n }\n return {\n kind: 'path',\n path: { kind: 'polygon', commands: new Uint8Array(cmds), coords, fillRule: 'nonzero' },\n ...(fill ? { fill } : {}),\n ...(stroke ? { stroke } : {}),\n };\n}\n\nfunction worldOf(region: AffordanceRegion, xf: TargetTransform, view: View): CustomPaintContext['world'] {\n if (region.shape.kind === 'point') {\n const w = pointRegionAnchor(region.shape, xf, view);\n return { x: w.x, y: w.y };\n }\n if (region.shape.kind === 'annulus') {\n // The outer ellipse's bounding box in world coords. Turned by θ, its\n // half-extents are √(rx²cos²θ + ry²sin²θ) and √(rx²sin²θ + ry²cos²θ).\n const s = region.shape;\n const { rx, ry } = annulusSemiAxes(s, view, angleOf(xf));\n const c = localToWorld(xf, s.cx, s.cy);\n const hw = Math.hypot(rx * xf.cos, ry * xf.sin);\n const hh = Math.hypot(rx * xf.sin, ry * xf.cos);\n return { x: c.x - hw, y: c.y - hh, width: 2 * hw, height: 2 * hh };\n }\n // Rect: map the rect's origin and far corner; width/height under rotation\n // describe the AABB of the rotated rect, which is what custom-paint\n // consumers most often want. Affordances needing exact rotated geometry\n // can read `local` + reapply rotation themselves via `view`/`state`.\n const r = region.shape;\n const a = localToWorld(xf, r.x, r.y);\n const b = localToWorld(xf, r.x + r.width, r.y);\n const c = localToWorld(xf, r.x, r.y + r.height);\n const d = localToWorld(xf, r.x + r.width, r.y + r.height);\n const minX = Math.min(a.x, b.x, c.x, d.x);\n const minY = Math.min(a.y, b.y, c.y, d.y);\n const maxX = Math.max(a.x, b.x, c.x, d.x);\n const maxY = Math.max(a.y, b.y, c.y, d.y);\n return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n}\n\n// Hit-testing lives in `hitAffordanceRegions.ts` — see `hitRegion` there.\n"],"mappings":"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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { HANDLE_BASE_PX as e } from "../core/device/targets.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { angleOf as t, localToWorld as n, transformOf as r } from "./hitAffordanceRegions.js";
|
|
3
|
+
import { CORNER_ANCHORS as i, cornerPoint as a, fixedCornerOf as o } from "../interactions/actions/resize/cornerHandles.js";
|
|
4
|
+
import { MULTI_RESIZE_TARGET_ID as s } from "../core/selection/selectionTarget.js";
|
|
5
5
|
//#region src/affordances/cornerResize.ts
|
|
6
6
|
var c = "#d4c4a8", l = "#1a130d";
|
|
7
|
-
function u(
|
|
8
|
-
let { handleHitRadius: u = e, handleSize: f = e, fill: p = c, stroke: m = l } =
|
|
7
|
+
function u(s = {}) {
|
|
8
|
+
let { handleHitRadius: u = e, handleSize: f = e, fill: p = c, stroke: m = l } = s, h = {
|
|
9
9
|
kind: "square",
|
|
10
10
|
sizePx: f,
|
|
11
11
|
fill: { color: p },
|
|
@@ -17,31 +17,39 @@ function u(i = {}) {
|
|
|
17
17
|
return {
|
|
18
18
|
id: "selection.resize-handles",
|
|
19
19
|
regions(e) {
|
|
20
|
-
let
|
|
21
|
-
if (!
|
|
22
|
-
let c =
|
|
23
|
-
return
|
|
24
|
-
let
|
|
20
|
+
let s = d(e);
|
|
21
|
+
if (!s) return [];
|
|
22
|
+
let c = s.bounds, l = r(e, s.id);
|
|
23
|
+
return i.map((e) => {
|
|
24
|
+
let r = a(c, e), i = o(c, e.anchor), d = n(l, i.x, i.y);
|
|
25
25
|
return {
|
|
26
26
|
id: `corner-${e.tag}`,
|
|
27
|
-
targetId:
|
|
27
|
+
targetId: s.id,
|
|
28
28
|
shape: {
|
|
29
29
|
kind: "point",
|
|
30
|
-
x:
|
|
31
|
-
y:
|
|
30
|
+
x: r.x,
|
|
31
|
+
y: r.y,
|
|
32
32
|
hitRadiusPx: u,
|
|
33
|
-
turned: !0
|
|
33
|
+
turned: !0,
|
|
34
|
+
cornerOf: {
|
|
35
|
+
width: c.width,
|
|
36
|
+
height: c.height,
|
|
37
|
+
outward: {
|
|
38
|
+
x: e.xEdge === "min" ? -1 : 1,
|
|
39
|
+
y: e.yEdge === "min" ? -1 : 1
|
|
40
|
+
}
|
|
41
|
+
}
|
|
34
42
|
},
|
|
35
43
|
paint: h,
|
|
36
44
|
hitKind: e.kind,
|
|
37
45
|
cursor: {
|
|
38
46
|
glyph: "resize",
|
|
39
|
-
angle: (e.anchor.x === e.anchor.y ? Math.PI / 4 : -Math.PI / 4) +
|
|
47
|
+
angle: (e.anchor.x === e.anchor.y ? Math.PI / 4 : -Math.PI / 4) + t(l),
|
|
40
48
|
fallback: e.anchor.x === e.anchor.y ? "nwse-resize" : "nesw-resize"
|
|
41
49
|
},
|
|
42
50
|
bind: () => ({ initialScratch: {
|
|
43
51
|
anchor: e.anchor,
|
|
44
|
-
targetId:
|
|
52
|
+
targetId: s.id,
|
|
45
53
|
fixedPoint: d
|
|
46
54
|
} })
|
|
47
55
|
};
|
|
@@ -51,7 +59,7 @@ function u(i = {}) {
|
|
|
51
59
|
}
|
|
52
60
|
function d(e) {
|
|
53
61
|
if (e.multiActive && e.unionBounds) return {
|
|
54
|
-
id:
|
|
62
|
+
id: s,
|
|
55
63
|
bounds: e.unionBounds
|
|
56
64
|
};
|
|
57
65
|
if (e.selection.length === 1) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cornerResize.js","names":["DEFAULT_FILL","DEFAULT_STROKE","createCornerResizeAffordance","HANDLE_BASE_PX","opts","paint","handleSize","fill","stroke","target","pickRenderTarget","state","b","xf","transformOf","CORNER_ANCHORS","corner","cornerPoint","c","fixedLocal","fixedCornerOf","fixedPoint","localToWorld","handleHitRadius","angleOf","MULTI_RESIZE_TARGET_ID","id"],"sources":["../../src/affordances/cornerResize.ts"],"sourcesContent":["import type { Affordance, AffordanceBinding, AffordanceRegion, CommonAffordanceScratch } from './types';\nimport type { ChromeState, Bounds } from 'core/selection/chromeState';\nimport type { ResizeAnchor } from 'interactions/gestures/types';\nimport { CORNER_ANCHORS, cornerPoint, fixedCornerOf } from 'interactions/actions/resize/cornerHandles';\nimport { MULTI_RESIZE_TARGET_ID } from 'core/selection/selectionTarget';\nimport { angleOf, localToWorld, transformOf } from './hitAffordanceRegions';\nimport { HANDLE_BASE_PX } from 'core/device/targets';\n\n/** Options for `createCornerResizeAffordance` — the size and appearance of\n * the corner handles. */\nexport interface CornerResizeAffordanceOptions {\n /** Hit radius (screen-px) for the corner handles. Default 8. */\n handleHitRadius?: number;\n /** Visual handle size (screen-px). Default 8. */\n handleSize?: number;\n fill?: string;\n stroke?: string;\n}\n\n/** What a corner-resize hit hands to the resize action: which corner stays\n * put, what is being resized, and the world-space pivot to scale from. */\nexport interface CornerResizeScratch extends CommonAffordanceScratch {\n /** Resize anchor identifying the OPPOSITE corner (the one that stays\n * fixed). Matches the kit's existing ResizeAnchor convention. */\n anchor: ResizeAnchor;\n /** Id of the resize target. In multi-mode this is `MULTI_RESIZE_TARGET_ID`. */\n targetId: string;\n /** The fixed corner in **world** coords, with the target's rotation already\n * applied. `resizeAction` scales from this point, and it has no access to\n * the target transform, so the affordance resolves it here. */\n fixedPoint: { x: number; y: number };\n}\n\nconst DEFAULT_FILL = '#d4c4a8';\nconst DEFAULT_STROKE = '#1a130d';\n\n/**\n * @experimental\n * Corner-handle affordance for resizing the active selection (single\n * member in single-mode; the union AABB in multi-mode).\n *\n * Declares four point regions in the target's local frame; the framework\n * applies the target's bounds rotation for both paint and hit-test.
|
|
1
|
+
{"version":3,"file":"cornerResize.js","names":["DEFAULT_FILL","DEFAULT_STROKE","createCornerResizeAffordance","HANDLE_BASE_PX","opts","paint","handleSize","fill","stroke","target","pickRenderTarget","state","b","xf","transformOf","CORNER_ANCHORS","corner","cornerPoint","c","fixedLocal","fixedCornerOf","fixedPoint","localToWorld","handleHitRadius","angleOf","MULTI_RESIZE_TARGET_ID","id"],"sources":["../../src/affordances/cornerResize.ts"],"sourcesContent":["import type { Affordance, AffordanceBinding, AffordanceRegion, CommonAffordanceScratch } from './types';\nimport type { ChromeState, Bounds } from 'core/selection/chromeState';\nimport type { ResizeAnchor } from 'interactions/gestures/types';\nimport { CORNER_ANCHORS, cornerPoint, fixedCornerOf } from 'interactions/actions/resize/cornerHandles';\nimport { MULTI_RESIZE_TARGET_ID } from 'core/selection/selectionTarget';\nimport { angleOf, localToWorld, transformOf } from './hitAffordanceRegions';\nimport { HANDLE_BASE_PX } from 'core/device/targets';\n\n/** Options for `createCornerResizeAffordance` — the size and appearance of\n * the corner handles. */\nexport interface CornerResizeAffordanceOptions {\n /** Hit radius (screen-px) for the corner handles. Default 8. */\n handleHitRadius?: number;\n /** Visual handle size (screen-px). Default 8. */\n handleSize?: number;\n fill?: string;\n stroke?: string;\n}\n\n/** What a corner-resize hit hands to the resize action: which corner stays\n * put, what is being resized, and the world-space pivot to scale from. */\nexport interface CornerResizeScratch extends CommonAffordanceScratch {\n /** Resize anchor identifying the OPPOSITE corner (the one that stays\n * fixed). Matches the kit's existing ResizeAnchor convention. */\n anchor: ResizeAnchor;\n /** Id of the resize target. In multi-mode this is `MULTI_RESIZE_TARGET_ID`. */\n targetId: string;\n /** The fixed corner in **world** coords, with the target's rotation already\n * applied. `resizeAction` scales from this point, and it has no access to\n * the target transform, so the affordance resolves it here. */\n fixedPoint: { x: number; y: number };\n}\n\nconst DEFAULT_FILL = '#d4c4a8';\nconst DEFAULT_STROKE = '#1a130d';\n\n/**\n * @experimental\n * Corner-handle affordance for resizing the active selection (single\n * member in single-mode; the union AABB in multi-mode).\n *\n * Declares four point regions in the target's local frame; the framework\n * applies the target's bounds rotation for both paint and hit-test. A\n * region's `bind()` carries only the scratch a hit hands to the resize\n * action: the anchor, the target, and the world-space fixed point.\n */\nexport function createCornerResizeAffordance(\n opts: CornerResizeAffordanceOptions = {},\n): Affordance {\n const {\n handleHitRadius = HANDLE_BASE_PX,\n handleSize = HANDLE_BASE_PX,\n fill = DEFAULT_FILL,\n stroke = DEFAULT_STROKE,\n } = opts;\n\n const paint = {\n kind: 'square' as const,\n sizePx: handleSize,\n fill: { color: fill },\n stroke: { paint: { color: stroke }, width: 1 },\n };\n\n return {\n id: 'selection.resize-handles',\n regions(state: ChromeState): readonly AffordanceRegion[] {\n const target = pickRenderTarget(state);\n if (!target) return [];\n const b = target.bounds;\n // The transform the framework applies to these regions. Needed here too,\n // because `fixedPoint` leaves the local frame — it travels to\n // `resizeAction`, which works in world coords.\n const xf = transformOf(state, target.id);\n return CORNER_ANCHORS.map((c) => {\n const corner = cornerPoint(b, c);\n const fixedLocal = fixedCornerOf(b, c.anchor);\n const fixedPoint = localToWorld(xf, fixedLocal.x, fixedLocal.y);\n return {\n id: `corner-${c.tag}`,\n targetId: target.id,\n shape: {\n kind: 'point' as const,\n x: corner.x,\n y: corner.y,\n hitRadiusPx: handleHitRadius,\n turned: true,\n cornerOf: {\n width: b.width,\n height: b.height,\n outward: { x: c.xEdge === 'min' ? -1 : 1, y: c.yEdge === 'min' ? -1 : 1 },\n },\n },\n paint,\n hitKind: c.kind,\n // Diagonal by fixed-corner parity: a matched-axis anchor (min-min /\n // max-max fixed) means the dragged corner sits on the ↘ diagonal;\n // mixed axes sit on the ↗ diagonal. The glyph is horizontal at angle\n // 0, so the diagonal is a half-quarter-turn either way, plus the\n // target's own rotation. The keyword is the fallback rather than the\n // answer: CSS has four of them, which cannot express a target\n // rotated to anything but a multiple of 45°.\n cursor: {\n glyph: 'resize',\n angle: (c.anchor.x === c.anchor.y ? Math.PI / 4 : -Math.PI / 4) + angleOf(xf),\n fallback: c.anchor.x === c.anchor.y ? 'nwse-resize' : 'nesw-resize',\n },\n bind: (): AffordanceBinding => ({\n initialScratch: {\n anchor: c.anchor,\n targetId: target.id,\n fixedPoint,\n } satisfies CornerResizeScratch,\n }),\n } satisfies AffordanceRegion;\n });\n },\n };\n}\n\nfunction pickRenderTarget(state: ChromeState): { id: string; bounds: Bounds } | null {\n if (state.multiActive && state.unionBounds) {\n return { id: MULTI_RESIZE_TARGET_ID, bounds: state.unionBounds };\n }\n if (state.selection.length === 1) {\n const id = state.selection[0];\n const b = state.boundsOf(id);\n return b ? { id, bounds: b } : null;\n }\n return null;\n}\n"],"mappings":";;;;;AAiCA,IAAMA,IAAe,WACfC,IAAiB;AAYvB,SAAgBC,EACd,IAAsC,CAAC,GAC3B;CACZ,IAAM,EACJ,qBAAkBC,GAClB,gBAAaA,GACb,UAAOH,GACP,YAASC,MACPG,GAEEC,IAAQ;EACZ,MAAM;EACN,QAAQC;EACR,MAAM,EAAE,OAAOC,EAAK;EACpB,QAAQ;GAAE,OAAO,EAAE,OAAOC,EAAO;GAAG,OAAO;EAAE;CAC/C;CAEA,OAAO;EACL,IAAI;EACJ,QAAQ,GAAiD;GACvD,IAAMC,IAASC,EAAiBC,CAAK;GACrC,IAAI,CAACF,GAAQ,OAAO,CAAC;GACrB,IAAMG,IAAIH,EAAO,QAIXI,IAAKC,EAAYH,GAAOF,EAAO,EAAE;GACvC,OAAOM,EAAe,KAAK,MAAM;IAC/B,IAAMC,IAASC,EAAYL,GAAGM,CAAC,GACzBC,IAAaC,EAAcR,GAAGM,EAAE,MAAM,GACtCG,IAAaC,EAAaT,GAAIM,EAAW,GAAGA,EAAW,CAAC;IAC9D,OAAO;KACL,IAAI,UAAUD,EAAE;KAChB,UAAUT,EAAO;KACjB,OAAO;MACL,MAAM;MACN,GAAGO,EAAO;MACV,GAAGA,EAAO;MACV,aAAaO;MACb,QAAQ;MACR,UAAU;OACR,OAAOX,EAAE;OACT,QAAQA,EAAE;OACV,SAAS;QAAE,GAAGM,EAAE,UAAU,QAAQ,KAAK;QAAG,GAAGA,EAAE,UAAU,QAAQ,KAAK;OAAE;MAC1E;KACF;KACA;KACA,SAASA,EAAE;KAQX,QAAQ;MACN,OAAO;MACP,QAAQA,EAAE,OAAO,MAAMA,EAAE,OAAO,IAAI,KAAK,KAAK,IAAI,CAAC,KAAK,KAAK,KAAKM,EAAQX,CAAE;MAC5E,UAAUK,EAAE,OAAO,MAAMA,EAAE,OAAO,IAAI,gBAAgB;KACxD;KACA,aAAgC,EAC9B,gBAAgB;MACd,QAAQA,EAAE;MACV,UAAUT,EAAO;MACjB;KACF,EACF;IACF;GACF,CAAC;EACH;CACF;AACF;AAEA,SAASC,EAAiB,GAA2D;CACnF,IAAIC,EAAM,eAAeA,EAAM,aAC7B,OAAO;EAAE,IAAIc;EAAwB,QAAQd,EAAM;CAAY;CAEjE,IAAIA,EAAM,UAAU,WAAW,GAAG;EAChC,IAAMe,IAAKf,EAAM,UAAU,IACrBC,IAAID,EAAM,SAASe,CAAE;EAC3B,OAAOd,IAAI;GAAE;GAAI,QAAQA;EAAE,IAAI;CACjC;CACA,OAAO;AACT"}
|
|
@@ -8,7 +8,7 @@ function a(e, t, n, r, i, a) {
|
|
|
8
8
|
let u = l.regions(r), d = null;
|
|
9
9
|
for (let e = 0; e < u.length; e++) {
|
|
10
10
|
let a = u[e], s = c(r, a.targetId);
|
|
11
|
-
if (!
|
|
11
|
+
if (!_(a, t, n, s, i)) continue;
|
|
12
12
|
let l = o(a, t, n, s, i);
|
|
13
13
|
(d === null || l <= d.d2) && (d = {
|
|
14
14
|
region: a,
|
|
@@ -25,7 +25,7 @@ function a(e, t, n, r, i, a) {
|
|
|
25
25
|
return null;
|
|
26
26
|
}
|
|
27
27
|
function o(e, t, n, r, i) {
|
|
28
|
-
let a = e.shape, o = a.kind === "point" ?
|
|
28
|
+
let a = e.shape, o = a.kind === "point" ? h(a, r, i) : a.kind === "annulus" ? u(r, a.cx, a.cy) : u(r, a.x + a.width / 2, a.y + a.height / 2), s = o.x - t, c = o.y - n;
|
|
29
29
|
return s * s + c * c;
|
|
30
30
|
}
|
|
31
31
|
var s = {
|
|
@@ -73,28 +73,35 @@ function d(e, t, n) {
|
|
|
73
73
|
y: e.cy - e.sin * r + e.cos * i
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
|
-
function f(e,
|
|
77
|
-
let
|
|
76
|
+
function f(e, n, i) {
|
|
77
|
+
let a = h(e, n, i), o = e.turned ? r(l(n), i.scale) : 0, s = e.hitRadiusPx, c = [-s, s], u = [-s, s], d = e.cornerOf;
|
|
78
|
+
if (d) {
|
|
79
|
+
let e = t(p * s, i.scale, l(n)), r = d.outward.y * Math.sign(i.scale.x * i.scale.y);
|
|
80
|
+
d.width < e.x && (c[d.outward.x > 0 ? 0 : 1] = 0), d.height < e.y && (u[r > 0 ? 0 : 1] = 0);
|
|
81
|
+
}
|
|
78
82
|
return {
|
|
79
|
-
x:
|
|
80
|
-
y:
|
|
81
|
-
angle:
|
|
82
|
-
half:
|
|
83
|
+
x: a.x,
|
|
84
|
+
y: a.y,
|
|
85
|
+
angle: o,
|
|
86
|
+
half: s,
|
|
87
|
+
u: c,
|
|
88
|
+
v: u
|
|
83
89
|
};
|
|
84
90
|
}
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
var p = 4;
|
|
92
|
+
function m(e, t, n, r) {
|
|
93
|
+
let i = f(e, t, n), [a, o] = r(i.x, i.y), s = Math.cos(i.angle), c = Math.sin(i.angle);
|
|
87
94
|
return [
|
|
88
|
-
[
|
|
89
|
-
[1,
|
|
90
|
-
[1, 1],
|
|
91
|
-
[
|
|
95
|
+
[i.u[0], i.v[0]],
|
|
96
|
+
[i.u[1], i.v[0]],
|
|
97
|
+
[i.u[1], i.v[1]],
|
|
98
|
+
[i.u[0], i.v[1]]
|
|
92
99
|
].map(([e, t]) => ({
|
|
93
100
|
x: a + s * e - c * t,
|
|
94
101
|
y: o + c * e + s * t
|
|
95
102
|
}));
|
|
96
103
|
}
|
|
97
|
-
function
|
|
104
|
+
function h(e, t, n) {
|
|
98
105
|
let r = u(t, e.x, e.y);
|
|
99
106
|
if (!e.standoff) return r;
|
|
100
107
|
let a = e.standoff.normal, o = t.identity ? a : {
|
|
@@ -103,7 +110,7 @@ function m(e, t, n) {
|
|
|
103
110
|
};
|
|
104
111
|
return i(r, o, e.standoff.px, n.scale);
|
|
105
112
|
}
|
|
106
|
-
function
|
|
113
|
+
function g(e, n, r = 0) {
|
|
107
114
|
let i = e.minBandPx ?? 0;
|
|
108
115
|
if (i === 0) return {
|
|
109
116
|
rx: e.rx,
|
|
@@ -115,16 +122,16 @@ function h(e, n, r = 0) {
|
|
|
115
122
|
ry: Math.max(e.ry, e.innerHeight / 2 + a.y)
|
|
116
123
|
};
|
|
117
124
|
}
|
|
118
|
-
function
|
|
125
|
+
function _(e, t, r, i, a) {
|
|
119
126
|
let o = d(i, t, r);
|
|
120
127
|
if (e.shape.kind === "point") {
|
|
121
128
|
let o = f(e.shape, i, a), s = n(t - o.x, r - o.y, a.scale), c = Math.cos(o.angle), l = Math.sin(o.angle), u = c * s.x + l * s.y, d = -l * s.x + c * s.y;
|
|
122
|
-
return
|
|
129
|
+
return u >= o.u[0] && u <= o.u[1] && d >= o.v[0] && d <= o.v[1];
|
|
123
130
|
}
|
|
124
131
|
if (e.shape.kind === "annulus") {
|
|
125
132
|
let t = e.shape;
|
|
126
133
|
if (o.x >= t.innerX && o.x <= t.innerX + t.innerWidth && o.y >= t.innerY && o.y <= t.innerY + t.innerHeight) return !1;
|
|
127
|
-
let { rx: n, ry: r } =
|
|
134
|
+
let { rx: n, ry: r } = g(t, a, l(i));
|
|
128
135
|
if (n <= 0 || r <= 0) return !1;
|
|
129
136
|
let s = (o.x - t.cx) / n, c = (o.y - t.cy) / r;
|
|
130
137
|
return s * s + c * c <= 1;
|
|
@@ -133,6 +140,6 @@ function g(e, t, r, i, a) {
|
|
|
133
140
|
return o.x >= s.x && o.x <= s.x + s.width && o.y >= s.y && o.y <= s.y + s.height;
|
|
134
141
|
}
|
|
135
142
|
//#endregion
|
|
136
|
-
export { l as angleOf,
|
|
143
|
+
export { l as angleOf, g as annulusSemiAxes, a as hitAffordanceRegions, u as localToWorld, h as pointRegionAnchor, f as pointRegionFrame, m as pointRegionScreenQuad, c as transformOf };
|
|
137
144
|
|
|
138
145
|
//# sourceMappingURL=hitAffordanceRegions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hitAffordanceRegions.js","names":["hitAffordanceRegions","i","affordances","a","isVisible","regions","state","best","j","region","xf","transformOf","hitRegion","wx","wy","view","d2","regionDistance2","s","w","pointRegionAnchor","localToWorld","dx","dy","IDENTITY_XF","targetId","b","r","poseRotationOf","angleOf","lx","ly","worldToLocal","pointRegionFrame","shape","angle","screenAngleOf","pointRegionScreenQuad","f","cx","cy","toScreen","c","sn","u","v","n","wn","standoff","annulusSemiAxes","bandPx","band","pxExtent","rotation","local","d","scaleDelta","rx","ry","ex","ey"],"sources":["../../src/affordances/hitAffordanceRegions.ts"],"sourcesContent":["/**\n * The one affordance hit-test walk.\n *\n * The kit used to hit-test selection chrome two ways: this region walk\n * (reached through `composeAffordanceLayer`'s `RenderLayer.hitTest`) and a\n * hand-written classifier in `canvas/affordanceAt.ts` that re-derived corner\n * positions, the rotate ring, and anchor points from `ChromeState` itself.\n * Both read the same state and answered the same question, so the geometry\n * lived twice and could disagree — and only the hand-written side ran for the\n * kit's own chrome, which is why `AffordanceRegion.cursor` was declared, set,\n * and never consumed.\n *\n * Now there is one walk. `composeAffordanceLayer.hitTest` delegates here, and\n * so does `buildAffordanceAt`, so an affordance's `regions()` is the single\n * source of truth for where its chrome is and what a press on it means.\n */\n\nimport type { View } from 'core/viewport/view';\nimport { pxExtent, scaleDelta, screenAngleOf, standoff } from 'core/viewport/pxExtent';\nimport type { ChromeState, Bounds } from 'core/selection/chromeState';\nimport { poseRotationOf } from 'core/geometry/poseRotation';\nimport type { Affordance, AffordanceBinding, AffordanceRegion } from './types';\n\n/** What the walk found: the region, plus the ids needed to describe it. */\nexport interface AffordanceRegionHit {\n /** `Affordance.id` — also the chrome-caps visibility id. */\n affordanceId: string;\n /** `AffordanceRegion.id`. */\n regionId: string;\n /** The region itself, so callers can read `hitKind` / `cursor`. */\n region: AffordanceRegion;\n /** Result of the region's `bind()`, called exactly once. */\n binding: AffordanceBinding;\n}\n\n/**\n * Walk affordances for a world-space hit, topmost first.\n *\n * **Across affordances**: order mirrors paint. Affordances are drawn first →\n * last (bottom → top), so the walk runs last → first and stops at the first\n * one that claims the point. Layering wins outright — a nearer region on a\n * lower affordance does not beat a farther one on top of it.\n *\n * **Within an affordance**: the *nearest* hit region wins, ties going to the\n * later-declared one. Both hit-testers this replaced took the first region\n * they found instead, in opposite orders, which is only invisible while\n * regions don't overlap. They do: the four corner handles of a selection\n * smaller than the hit radius all contain the same click, and \"first in the\n * table\" silently answered top-left (or bottom-right) no matter which handle\n * the pointer was actually on.\n *\n * `isVisible` (when supplied) gates by the same chrome id used at paint time,\n * so chrome that isn't drawn can't be hit either.\n */\nexport function hitAffordanceRegions(\n affordances: readonly Affordance[],\n wx: number,\n wy: number,\n state: ChromeState,\n view: View,\n isVisible?: (id: string) => boolean,\n): AffordanceRegionHit | null {\n for (let i = affordances.length - 1; i >= 0; i--) {\n const a = affordances[i];\n if (isVisible && !isVisible(a.id)) continue;\n const regions = a.regions(state);\n let best: { region: AffordanceRegion; d2: number } | null = null;\n for (let j = 0; j < regions.length; j++) {\n const region = regions[j];\n const xf = transformOf(state, region.targetId);\n if (!hitRegion(region, wx, wy, xf, view)) continue;\n const d2 = regionDistance2(region, wx, wy, xf, view);\n // `<=` so a later-declared region wins an exact tie, preserving the\n // \"later is on top\" reading of declaration order.\n if (best === null || d2 <= best.d2) best = { region, d2 };\n }\n if (best) {\n return {\n affordanceId: a.id,\n regionId: best.region.id,\n region: best.region,\n binding: best.region.bind(),\n };\n }\n }\n return null;\n}\n\n/** Squared world distance from the point to a region's representative center,\n * used only to rank regions that all already contain the point. */\nfunction regionDistance2(\n region: AffordanceRegion,\n wx: number,\n wy: number,\n xf: TargetTransform,\n view: View,\n): number {\n const s = region.shape;\n const w = s.kind === 'point'\n ? pointRegionAnchor(s, xf, view)\n : s.kind === 'annulus'\n ? localToWorld(xf, s.cx, s.cy)\n : localToWorld(xf, s.x + s.width / 2, s.y + s.height / 2);\n const dx = w.x - wx;\n const dy = w.y - wy;\n return dx * dx + dy * dy;\n}\n\n// ─── Transform helpers ──────────────────────────────────────────────────────\n\n/** Local↔world transform for an affordance target. Translation is the AABB\n * origin; rotation is around the AABB center. `identity` short-circuits the\n * math when the target isn't rotated. */\nexport interface TargetTransform {\n /** Rotation pivot (AABB center) in world coords. */\n cx: number;\n cy: number;\n /** cos(rotation) — 1 when identity. */\n cos: number;\n /** sin(rotation) — 0 when identity. */\n sin: number;\n identity: boolean;\n}\n\nconst IDENTITY_XF: TargetTransform = { cx: 0, cy: 0, cos: 1, sin: 0, identity: true };\n\nexport function transformOf(state: ChromeState, targetId: string | null): TargetTransform {\n if (targetId === null) return IDENTITY_XF;\n const b: Bounds | null = state.boundsOf(targetId);\n if (!b) return IDENTITY_XF;\n // Pivot + angle come from the kit's one rotation convention.\n const r = poseRotationOf(b);\n if (!r) return IDENTITY_XF;\n return {\n cx: r.cx,\n cy: r.cy,\n cos: Math.cos(r.rotation),\n sin: Math.sin(r.rotation),\n identity: false,\n };\n}\n\n/** A target transform's rotation in radians, for cursors baked at an angle. */\nexport function angleOf(xf: TargetTransform): number {\n return xf.identity ? 0 : Math.atan2(xf.sin, xf.cos);\n}\n\n/** local point → world point. */\nexport function localToWorld(xf: TargetTransform, lx: number, ly: number): { x: number; y: number } {\n if (xf.identity) return { x: lx, y: ly };\n const dx = lx - xf.cx;\n const dy = ly - xf.cy;\n return {\n x: xf.cx + xf.cos * dx - xf.sin * dy,\n y: xf.cy + xf.sin * dx + xf.cos * dy,\n };\n}\n\n/** world point → local point. */\nfunction worldToLocal(xf: TargetTransform, wx: number, wy: number): { x: number; y: number } {\n if (xf.identity) return { x: wx, y: wy };\n const dx = wx - xf.cx;\n const dy = wy - xf.cy;\n // Inverse rotation: cos stays, sin negates.\n return {\n x: xf.cx + xf.cos * dx + xf.sin * dy,\n y: xf.cy - xf.sin * dx + xf.cos * dy,\n };\n}\n\n// ─── Shape resolution ───────────────────────────────────────────────────────\n\ntype PointShape = Extract<AffordanceRegion['shape'], { kind: 'point' }>;\n\n/** Where a point region's hit square sits: its center in world coords, its\n * turn on screen, and its screen half-extent. Hit-test, paint, the debug\n * hitbox and the slops overlay all read it from here. */\nexport function pointRegionFrame(\n shape: PointShape,\n xf: TargetTransform,\n view: View,\n): { x: number; y: number; angle: number; half: number } {\n const a = pointRegionAnchor(shape, xf, view);\n const angle = shape.turned ? screenAngleOf(angleOf(xf), view.scale) : 0;\n return { x: a.x, y: a.y, angle, half: shape.hitRadiusPx };\n}\n\n/** A point region's hit square as four screen-space corners, relative to its\n * world center carried to the screen by `toScreen`. */\nexport function pointRegionScreenQuad(\n shape: PointShape,\n xf: TargetTransform,\n view: View,\n toScreen: (x: number, y: number) => [number, number],\n): { x: number; y: number }[] {\n const f = pointRegionFrame(shape, xf, view);\n const [cx, cy] = toScreen(f.x, f.y);\n const c = Math.cos(f.angle) * f.half, sn = Math.sin(f.angle) * f.half;\n return [[-1, -1], [1, -1], [1, 1], [-1, 1]].map(([u, v]) => ({\n x: cx + c * u - sn * v,\n y: cy + sn * u + c * v,\n }));\n}\n\n/** A point region's anchor in world coords, its `standoff` applied. */\nexport function pointRegionAnchor(\n shape: PointShape,\n xf: TargetTransform,\n view: View,\n): { x: number; y: number } {\n const a = localToWorld(xf, shape.x, shape.y);\n if (!shape.standoff) return a;\n const n = shape.standoff.normal;\n const wn = xf.identity\n ? n\n : { x: xf.cos * n.x - xf.sin * n.y, y: xf.sin * n.x + xf.cos * n.y };\n return standoff(a, wn, shape.standoff.px, view.scale);\n}\n\n/**\n * An annulus region's effective outer semi-axes, with `minBandPx` applied.\n *\n * The affordance declares the natural ellipse (typically the smallest one\n * containing the inner rect) plus a screen-space floor on band thickness;\n * this is where the floor becomes world units, because this is where the view\n * is known. Paint calls it too, so the visible ring and the hoverable ring\n * are the same ring.\n */\nexport function annulusSemiAxes(\n shape: Extract<AffordanceRegion['shape'], { kind: 'annulus' }>,\n view: View,\n /** The target's rotation, which decides what a screen pixel is along each\n * of its axes. */\n rotation = 0,\n): { rx: number; ry: number } {\n const bandPx = shape.minBandPx ?? 0;\n if (bandPx === 0) return { rx: shape.rx, ry: shape.ry };\n const band = pxExtent(bandPx, view.scale, rotation);\n return {\n rx: Math.max(shape.rx, shape.innerWidth / 2 + band.x),\n ry: Math.max(shape.ry, shape.innerHeight / 2 + band.y),\n };\n}\n\n// ─── Per-shape hit-test ─────────────────────────────────────────────────────\n\nfunction hitRegion(\n region: AffordanceRegion,\n wx: number,\n wy: number,\n xf: TargetTransform,\n view: View,\n): boolean {\n const local = worldToLocal(xf, wx, wy);\n if (region.shape.kind === 'point') {\n // Square hit, not circular: the handle a `point` region paints is a\n // square, and the hit zone should match what the user sees. (The old\n // hand-written classifier used a circle here, which made the corners of\n // an 8px handle unclickable.)\n //\n // Compared in screen space, where that square is axis-aligned. Testing it\n // in the target's local frame instead would tilt it under a rotated\n // target and stretch it under non-uniform zoom.\n const f = pointRegionFrame(region.shape, xf, view);\n const d = scaleDelta(wx - f.x, wy - f.y, view.scale);\n const c = Math.cos(f.angle), sn = Math.sin(f.angle);\n const u = c * d.x + sn * d.y, v = -sn * d.x + c * d.y;\n return Math.abs(u) <= f.half && Math.abs(v) <= f.half;\n }\n if (region.shape.kind === 'annulus') {\n const s = region.shape;\n // Outside the inner-rect cutout?\n const insideInner =\n local.x >= s.innerX && local.x <= s.innerX + s.innerWidth &&\n local.y >= s.innerY && local.y <= s.innerY + s.innerHeight;\n if (insideInner) return false;\n // Inside the outer ellipse? `((x-cx)/rx)² + ((y-cy)/ry)² ≤ 1`.\n const { rx, ry } = annulusSemiAxes(s, view, angleOf(xf));\n if (rx <= 0 || ry <= 0) return false;\n const ex = (local.x - s.cx) / rx;\n const ey = (local.y - s.cy) / ry;\n return ex * ex + ey * ey <= 1;\n }\n // rect: axis-aligned in local frame.\n const r = region.shape;\n return local.x >= r.x && local.x <= r.x + r.width\n && local.y >= r.y && local.y <= r.y + r.height;\n}\n"],"mappings":";;;AAsDA,SAAgBA,EACd,GACA,GACA,GACA,GACA,GACA,GAC4B;CAC5B,KAAK,IAAIC,IAAIC,EAAY,SAAS,GAAGD,KAAK,GAAG,KAAK;EAChD,IAAME,IAAID,EAAYD;EACtB,IAAIG,KAAa,CAACA,EAAUD,EAAE,EAAE,GAAG;EACnC,IAAME,IAAUF,EAAE,QAAQG,CAAK,GAC3BC,IAAwD;EAC5D,KAAK,IAAIC,IAAI,GAAGA,IAAIH,EAAQ,QAAQ,KAAK;GACvC,IAAMI,IAASJ,EAAQG,IACjBE,IAAKC,EAAYL,GAAOG,EAAO,QAAQ;GAC7C,IAAI,CAACG,EAAUH,GAAQI,GAAIC,GAAIJ,GAAIK,CAAI,GAAG;GAC1C,IAAMC,IAAKC,EAAgBR,GAAQI,GAAIC,GAAIJ,GAAIK,CAAI;GAGnD,CAAIR,MAAS,QAAQS,KAAMT,EAAK,QAAI,IAAO;IAAE;IAAQ;GAAG;EAC1D;EACA,IAAIA,GACF,OAAO;GACL,cAAcJ,EAAE;GAChB,UAAUI,EAAK,OAAO;GACtB,QAAQA,EAAK;GACb,SAASA,EAAK,OAAO,KAAK;EAC5B;CAEJ;CACA,OAAO;AACT;AAIA,SAASU,EACP,GACA,GACA,GACA,GACA,GACQ;CACR,IAAMC,IAAIT,EAAO,OACXU,IAAID,EAAE,SAAS,UACjBE,EAAkBF,GAAGR,GAAIK,CAAI,IAC7BG,EAAE,SAAS,YACTG,EAAaX,GAAIQ,EAAE,IAAIA,EAAE,EAAE,IAC3BG,EAAaX,GAAIQ,EAAE,IAAIA,EAAE,QAAQ,GAAGA,EAAE,IAAIA,EAAE,SAAS,CAAC,GACtDI,IAAKH,EAAE,IAAIN,GACXU,IAAKJ,EAAE,IAAIL;CACjB,OAAOQ,IAAKA,IAAKC,IAAKA;AACxB;AAkBA,IAAMC,IAA+B;CAAE,IAAI;CAAG,IAAI;CAAG,KAAK;CAAG,KAAK;CAAG,UAAU;AAAK;AAEpF,SAAgBb,EAAY,GAAoB,GAA0C;CACxF,IAAIc,MAAa,MAAM,OAAOD;CAC9B,IAAME,IAAmBpB,EAAM,SAASmB,CAAQ;CAChD,IAAI,CAACC,GAAG,OAAOF;CAEf,IAAMG,IAAIC,EAAeF,CAAC;CAE1B,OADKC,IACE;EACL,IAAIA,EAAE;EACN,IAAIA,EAAE;EACN,KAAK,KAAK,IAAIA,EAAE,QAAQ;EACxB,KAAK,KAAK,IAAIA,EAAE,QAAQ;EACxB,UAAU;CACZ,IAPeH;AAQjB;AAGA,SAAgBK,EAAQ,GAA6B;CACnD,OAAOnB,EAAG,WAAW,IAAI,KAAK,MAAMA,EAAG,KAAKA,EAAG,GAAG;AACpD;AAGA,SAAgBW,EAAa,GAAqB,GAAY,GAAsC;CAClG,IAAIX,EAAG,UAAU,OAAO;EAAE,GAAGoB;EAAI,GAAGC;CAAG;CACvC,IAAMT,IAAKQ,IAAKpB,EAAG,IACba,IAAKQ,IAAKrB,EAAG;CACnB,OAAO;EACL,GAAGA,EAAG,KAAKA,EAAG,MAAMY,IAAKZ,EAAG,MAAMa;EAClC,GAAGb,EAAG,KAAKA,EAAG,MAAMY,IAAKZ,EAAG,MAAMa;CACpC;AACF;AAGA,SAASS,EAAa,GAAqB,GAAY,GAAsC;CAC3F,IAAItB,EAAG,UAAU,OAAO;EAAE,GAAGG;EAAI,GAAGC;CAAG;CACvC,IAAMQ,IAAKT,IAAKH,EAAG,IACba,IAAKT,IAAKJ,EAAG;CAEnB,OAAO;EACL,GAAGA,EAAG,KAAKA,EAAG,MAAMY,IAAKZ,EAAG,MAAMa;EAClC,GAAGb,EAAG,KAAKA,EAAG,MAAMY,IAAKZ,EAAG,MAAMa;CACpC;AACF;AASA,SAAgBU,EACd,GACA,GACA,GACuD;CACvD,IAAM9B,IAAIiB,EAAkBc,GAAOxB,GAAIK,CAAI,GACrCoB,IAAQD,EAAM,SAASE,EAAcP,EAAQnB,CAAE,GAAGK,EAAK,KAAK,IAAI;CACtE,OAAO;EAAE,GAAGZ,EAAE;EAAG,GAAGA,EAAE;EAAG;EAAO,MAAM+B,EAAM;CAAY;AAC1D;AAIA,SAAgBG,EACd,GACA,GACA,GACA,GAC4B;CAC5B,IAAMC,IAAIL,EAAiBC,GAAOxB,GAAIK,CAAI,GACpC,CAACwB,GAAIC,KAAMC,EAASH,EAAE,GAAGA,EAAE,CAAC,GAC5BI,IAAI,KAAK,IAAIJ,EAAE,KAAK,IAAIA,EAAE,MAAMK,IAAK,KAAK,IAAIL,EAAE,KAAK,IAAIA,EAAE;CACjE,OAAO;EAAC,CAAC,IAAI,EAAE;EAAG,CAAC,GAAG,EAAE;EAAG,CAAC,GAAG,CAAC;EAAG,CAAC,IAAI,CAAC;CAAC,CAAC,CAAC,KAAK,CAACM,GAAGC,QAAQ;EAC3D,GAAGN,IAAKG,IAAIE,IAAID,IAAKE;EACrB,GAAGL,IAAKG,IAAKC,IAAIF,IAAIG;CACvB,EAAE;AACJ;AAGA,SAAgBzB,EACd,GACA,GACA,GAC0B;CAC1B,IAAMjB,IAAIkB,EAAaX,GAAIwB,EAAM,GAAGA,EAAM,CAAC;CAC3C,IAAI,CAACA,EAAM,UAAU,OAAO/B;CAC5B,IAAM2C,IAAIZ,EAAM,SAAS,QACnBa,IAAKrC,EAAG,WACVoC,IACA;EAAE,GAAGpC,EAAG,MAAMoC,EAAE,IAAIpC,EAAG,MAAMoC,EAAE;EAAG,GAAGpC,EAAG,MAAMoC,EAAE,IAAIpC,EAAG,MAAMoC,EAAE;CAAE;CACrE,OAAOE,EAAS7C,GAAG4C,GAAIb,EAAM,SAAS,IAAInB,EAAK,KAAK;AACtD;AAWA,SAAgBkC,EACd,GACA,GAGA,IAAW,GACiB;CAC5B,IAAMC,IAAShB,EAAM,aAAa;CAClC,IAAIgB,MAAW,GAAG,OAAO;EAAE,IAAIhB,EAAM;EAAI,IAAIA,EAAM;CAAG;CACtD,IAAMiB,IAAOC,EAASF,GAAQnC,EAAK,OAAOsC,CAAQ;CAClD,OAAO;EACL,IAAI,KAAK,IAAInB,EAAM,IAAIA,EAAM,aAAa,IAAIiB,EAAK,CAAC;EACpD,IAAI,KAAK,IAAIjB,EAAM,IAAIA,EAAM,cAAc,IAAIiB,EAAK,CAAC;CACvD;AACF;AAIA,SAASvC,EACP,GACA,GACA,GACA,GACA,GACS;CACT,IAAM0C,IAAQtB,EAAatB,GAAIG,GAAIC,CAAE;CACrC,IAAIL,EAAO,MAAM,SAAS,SAAS;EASjC,IAAM6B,IAAIL,EAAiBxB,EAAO,OAAOC,GAAIK,CAAI,GAC3CwC,IAAIC,EAAW3C,IAAKyB,EAAE,GAAGxB,IAAKwB,EAAE,GAAGvB,EAAK,KAAK,GAC7C,IAAI,KAAK,IAAIuB,EAAE,KAAK,GAAGK,IAAK,KAAK,IAAIL,EAAE,KAAK,GAC5C,IAAI,IAAIiB,EAAE,IAAIZ,IAAKY,EAAE,GAAGV,IAAI,CAACF,IAAKY,EAAE,IAAI,IAAIA,EAAE;EACpD,OAAO,KAAK,IAAI,CAAC,KAAKjB,EAAE,QAAQ,KAAK,IAAIO,CAAC,KAAKP,EAAE;CACnD;CACA,IAAI7B,EAAO,MAAM,SAAS,WAAW;EACnC,IAAMS,IAAIT,EAAO;EAKjB,IAFE6C,EAAM,KAAKpC,EAAE,UAAUoC,EAAM,KAAKpC,EAAE,SAASA,EAAE,cAC/CoC,EAAM,KAAKpC,EAAE,UAAUoC,EAAM,KAAKpC,EAAE,SAASA,EAAE,aAChC,OAAO;EAExB,IAAM,EAAE,OAAI,UAAO+B,EAAgB/B,GAAGH,GAAMc,EAAQnB,CAAE,CAAC;EACvD,IAAI+C,KAAM,KAAKC,KAAM,GAAG,OAAO;EAC/B,IAAMC,KAAML,EAAM,IAAIpC,EAAE,MAAMuC,GACxBG,KAAMN,EAAM,IAAIpC,EAAE,MAAMwC;EAC9B,OAAOC,IAAKA,IAAKC,IAAKA,KAAM;CAC9B;CAEA,IAAMjC,IAAIlB,EAAO;CACjB,OAAO6C,EAAM,KAAK3B,EAAE,KAAK2B,EAAM,KAAK3B,EAAE,IAAIA,EAAE,SACrC2B,EAAM,KAAK3B,EAAE,KAAK2B,EAAM,KAAK3B,EAAE,IAAIA,EAAE;AAC9C"}
|
|
1
|
+
{"version":3,"file":"hitAffordanceRegions.js","names":["hitAffordanceRegions","i","affordances","a","isVisible","regions","state","best","j","region","xf","transformOf","hitRegion","wx","wy","view","d2","regionDistance2","s","w","pointRegionAnchor","localToWorld","dx","dy","IDENTITY_XF","targetId","b","r","poseRotationOf","angleOf","lx","ly","worldToLocal","pointRegionFrame","shape","angle","screenAngleOf","half","u","v","body","floor","pxExtent","SMALL_BODY_RADII","vOut","pointRegionScreenQuad","f","cx","cy","toScreen","c","sn","n","wn","standoff","annulusSemiAxes","bandPx","band","rotation","local","d","scaleDelta","rx","ry","ex","ey"],"sources":["../../src/affordances/hitAffordanceRegions.ts"],"sourcesContent":["/**\n * The one affordance hit-test walk.\n *\n * The kit used to hit-test selection chrome two ways: this region walk\n * (reached through `composeAffordanceLayer`'s `RenderLayer.hitTest`) and a\n * hand-written classifier in `canvas/affordanceAt.ts` that re-derived corner\n * positions, the rotate ring, and anchor points from `ChromeState` itself.\n * Both read the same state and answered the same question, so the geometry\n * lived twice and could disagree — and only the hand-written side ran for the\n * kit's own chrome, which is why `AffordanceRegion.cursor` was declared, set,\n * and never consumed.\n *\n * Now there is one walk. `composeAffordanceLayer.hitTest` delegates here, and\n * so does `buildAffordanceAt`, so an affordance's `regions()` is the single\n * source of truth for where its chrome is and what a press on it means.\n */\n\nimport type { View } from 'core/viewport/view';\nimport { pxExtent, scaleDelta, screenAngleOf, standoff } from 'core/viewport/pxExtent';\nimport type { ChromeState, Bounds } from 'core/selection/chromeState';\nimport { poseRotationOf } from 'core/geometry/poseRotation';\nimport type { Affordance, AffordanceBinding, AffordanceRegion } from './types';\n\n/** What the walk found: the region, plus the ids needed to describe it. */\nexport interface AffordanceRegionHit {\n /** `Affordance.id` — also the chrome-caps visibility id. */\n affordanceId: string;\n /** `AffordanceRegion.id`. */\n regionId: string;\n /** The region itself, so callers can read `hitKind` / `cursor`. */\n region: AffordanceRegion;\n /** Result of the region's `bind()`, called exactly once. */\n binding: AffordanceBinding;\n}\n\n/**\n * Walk affordances for a world-space hit, topmost first.\n *\n * **Across affordances**: order mirrors paint. Affordances are drawn first →\n * last (bottom → top), so the walk runs last → first and stops at the first\n * one that claims the point. Layering wins outright — a nearer region on a\n * lower affordance does not beat a farther one on top of it.\n *\n * **Within an affordance**: the *nearest* hit region wins, ties going to the\n * later-declared one. Both hit-testers this replaced took the first region\n * they found instead, in opposite orders, which is only invisible while\n * regions don't overlap. They do: the four corner handles of a selection\n * smaller than the hit radius all contain the same click, and \"first in the\n * table\" silently answered top-left (or bottom-right) no matter which handle\n * the pointer was actually on.\n *\n * `isVisible` (when supplied) gates by the same chrome id used at paint time,\n * so chrome that isn't drawn can't be hit either.\n */\nexport function hitAffordanceRegions(\n affordances: readonly Affordance[],\n wx: number,\n wy: number,\n state: ChromeState,\n view: View,\n isVisible?: (id: string) => boolean,\n): AffordanceRegionHit | null {\n for (let i = affordances.length - 1; i >= 0; i--) {\n const a = affordances[i];\n if (isVisible && !isVisible(a.id)) continue;\n const regions = a.regions(state);\n let best: { region: AffordanceRegion; d2: number } | null = null;\n for (let j = 0; j < regions.length; j++) {\n const region = regions[j];\n const xf = transformOf(state, region.targetId);\n if (!hitRegion(region, wx, wy, xf, view)) continue;\n const d2 = regionDistance2(region, wx, wy, xf, view);\n // `<=` so a later-declared region wins an exact tie, preserving the\n // \"later is on top\" reading of declaration order.\n if (best === null || d2 <= best.d2) best = { region, d2 };\n }\n if (best) {\n return {\n affordanceId: a.id,\n regionId: best.region.id,\n region: best.region,\n binding: best.region.bind(),\n };\n }\n }\n return null;\n}\n\n/** Squared world distance from the point to a region's representative center,\n * used only to rank regions that all already contain the point. */\nfunction regionDistance2(\n region: AffordanceRegion,\n wx: number,\n wy: number,\n xf: TargetTransform,\n view: View,\n): number {\n const s = region.shape;\n const w = s.kind === 'point'\n ? pointRegionAnchor(s, xf, view)\n : s.kind === 'annulus'\n ? localToWorld(xf, s.cx, s.cy)\n : localToWorld(xf, s.x + s.width / 2, s.y + s.height / 2);\n const dx = w.x - wx;\n const dy = w.y - wy;\n return dx * dx + dy * dy;\n}\n\n// ─── Transform helpers ──────────────────────────────────────────────────────\n\n/** Local↔world transform for an affordance target. Translation is the AABB\n * origin; rotation is around the AABB center. `identity` short-circuits the\n * math when the target isn't rotated. */\nexport interface TargetTransform {\n /** Rotation pivot (AABB center) in world coords. */\n cx: number;\n cy: number;\n /** cos(rotation) — 1 when identity. */\n cos: number;\n /** sin(rotation) — 0 when identity. */\n sin: number;\n identity: boolean;\n}\n\nconst IDENTITY_XF: TargetTransform = { cx: 0, cy: 0, cos: 1, sin: 0, identity: true };\n\nexport function transformOf(state: ChromeState, targetId: string | null): TargetTransform {\n if (targetId === null) return IDENTITY_XF;\n const b: Bounds | null = state.boundsOf(targetId);\n if (!b) return IDENTITY_XF;\n // Pivot + angle come from the kit's one rotation convention.\n const r = poseRotationOf(b);\n if (!r) return IDENTITY_XF;\n return {\n cx: r.cx,\n cy: r.cy,\n cos: Math.cos(r.rotation),\n sin: Math.sin(r.rotation),\n identity: false,\n };\n}\n\n/** A target transform's rotation in radians, for cursors baked at an angle. */\nexport function angleOf(xf: TargetTransform): number {\n return xf.identity ? 0 : Math.atan2(xf.sin, xf.cos);\n}\n\n/** local point → world point. */\nexport function localToWorld(xf: TargetTransform, lx: number, ly: number): { x: number; y: number } {\n if (xf.identity) return { x: lx, y: ly };\n const dx = lx - xf.cx;\n const dy = ly - xf.cy;\n return {\n x: xf.cx + xf.cos * dx - xf.sin * dy,\n y: xf.cy + xf.sin * dx + xf.cos * dy,\n };\n}\n\n/** world point → local point. */\nfunction worldToLocal(xf: TargetTransform, wx: number, wy: number): { x: number; y: number } {\n if (xf.identity) return { x: wx, y: wy };\n const dx = wx - xf.cx;\n const dy = wy - xf.cy;\n // Inverse rotation: cos stays, sin negates.\n return {\n x: xf.cx + xf.cos * dx + xf.sin * dy,\n y: xf.cy - xf.sin * dx + xf.cos * dy,\n };\n}\n\n// ─── Shape resolution ───────────────────────────────────────────────────────\n\ntype PointShape = Extract<AffordanceRegion['shape'], { kind: 'point' }>;\n\n/** Where a point region's hit square sits: its center in world coords, its\n * turn on screen, its screen half-extent, and the span of the square that\n * takes a press along each of its own axes (`[-half, half]` unless\n * `cornerOf` cuts it). Hit-test, paint, the debug hitbox and the slops\n * overlay all read it from here. */\nexport function pointRegionFrame(\n shape: PointShape,\n xf: TargetTransform,\n view: View,\n): { x: number; y: number; angle: number; half: number; u: [number, number]; v: [number, number] } {\n const a = pointRegionAnchor(shape, xf, view);\n const angle = shape.turned ? screenAngleOf(angleOf(xf), view.scale) : 0;\n const half = shape.hitRadiusPx;\n const u: [number, number] = [-half, half];\n const v: [number, number] = [-half, half];\n const body = shape.cornerOf;\n if (body) {\n // The body in world units that spans four hit radii on screen, per axis.\n const floor = pxExtent(SMALL_BODY_RADII * half, view.scale, angleOf(xf));\n // `v` runs a quarter turn on from `u` on screen, which is the target's +y\n // only while the view keeps its handedness.\n const vOut = body.outward.y * Math.sign(view.scale.x * view.scale.y);\n if (body.width < floor.x) u[body.outward.x > 0 ? 0 : 1] = 0;\n if (body.height < floor.y) v[vOut > 0 ? 0 : 1] = 0;\n }\n return { x: a.x, y: a.y, angle, half, u, v };\n}\n\n/** A body under this many hit radii across gives up the inward half of each\n * corner's hit square, so there is somewhere on it that is not a handle. */\nconst SMALL_BODY_RADII = 4;\n\n/** A point region's hit square as four screen-space corners, relative to its\n * world center carried to the screen by `toScreen`. */\nexport function pointRegionScreenQuad(\n shape: PointShape,\n xf: TargetTransform,\n view: View,\n toScreen: (x: number, y: number) => [number, number],\n): { x: number; y: number }[] {\n const f = pointRegionFrame(shape, xf, view);\n const [cx, cy] = toScreen(f.x, f.y);\n const c = Math.cos(f.angle), sn = Math.sin(f.angle);\n return [[f.u[0], f.v[0]], [f.u[1], f.v[0]], [f.u[1], f.v[1]], [f.u[0], f.v[1]]].map(([u, v]) => ({\n x: cx + c * u - sn * v,\n y: cy + sn * u + c * v,\n }));\n}\n\n/** A point region's anchor in world coords, its `standoff` applied. */\nexport function pointRegionAnchor(\n shape: PointShape,\n xf: TargetTransform,\n view: View,\n): { x: number; y: number } {\n const a = localToWorld(xf, shape.x, shape.y);\n if (!shape.standoff) return a;\n const n = shape.standoff.normal;\n const wn = xf.identity\n ? n\n : { x: xf.cos * n.x - xf.sin * n.y, y: xf.sin * n.x + xf.cos * n.y };\n return standoff(a, wn, shape.standoff.px, view.scale);\n}\n\n/**\n * An annulus region's effective outer semi-axes, with `minBandPx` applied.\n *\n * The affordance declares the natural ellipse (typically the smallest one\n * containing the inner rect) plus a screen-space floor on band thickness;\n * this is where the floor becomes world units, because this is where the view\n * is known. Paint calls it too, so the visible ring and the hoverable ring\n * are the same ring.\n */\nexport function annulusSemiAxes(\n shape: Extract<AffordanceRegion['shape'], { kind: 'annulus' }>,\n view: View,\n /** The target's rotation, which decides what a screen pixel is along each\n * of its axes. */\n rotation = 0,\n): { rx: number; ry: number } {\n const bandPx = shape.minBandPx ?? 0;\n if (bandPx === 0) return { rx: shape.rx, ry: shape.ry };\n const band = pxExtent(bandPx, view.scale, rotation);\n return {\n rx: Math.max(shape.rx, shape.innerWidth / 2 + band.x),\n ry: Math.max(shape.ry, shape.innerHeight / 2 + band.y),\n };\n}\n\n// ─── Per-shape hit-test ─────────────────────────────────────────────────────\n\nfunction hitRegion(\n region: AffordanceRegion,\n wx: number,\n wy: number,\n xf: TargetTransform,\n view: View,\n): boolean {\n const local = worldToLocal(xf, wx, wy);\n if (region.shape.kind === 'point') {\n // Square hit, not circular: the handle a `point` region paints is a\n // square, and the hit zone should match what the user sees. (The old\n // hand-written classifier used a circle here, which made the corners of\n // an 8px handle unclickable.)\n //\n // Compared in screen space, where that square is axis-aligned. Testing it\n // in the target's local frame instead would tilt it under a rotated\n // target and stretch it under non-uniform zoom.\n const f = pointRegionFrame(region.shape, xf, view);\n const d = scaleDelta(wx - f.x, wy - f.y, view.scale);\n const c = Math.cos(f.angle), sn = Math.sin(f.angle);\n const u = c * d.x + sn * d.y, v = -sn * d.x + c * d.y;\n return u >= f.u[0] && u <= f.u[1] && v >= f.v[0] && v <= f.v[1];\n }\n if (region.shape.kind === 'annulus') {\n const s = region.shape;\n // Outside the inner-rect cutout?\n const insideInner =\n local.x >= s.innerX && local.x <= s.innerX + s.innerWidth &&\n local.y >= s.innerY && local.y <= s.innerY + s.innerHeight;\n if (insideInner) return false;\n // Inside the outer ellipse? `((x-cx)/rx)² + ((y-cy)/ry)² ≤ 1`.\n const { rx, ry } = annulusSemiAxes(s, view, angleOf(xf));\n if (rx <= 0 || ry <= 0) return false;\n const ex = (local.x - s.cx) / rx;\n const ey = (local.y - s.cy) / ry;\n return ex * ex + ey * ey <= 1;\n }\n // rect: axis-aligned in local frame.\n const r = region.shape;\n return local.x >= r.x && local.x <= r.x + r.width\n && local.y >= r.y && local.y <= r.y + r.height;\n}\n"],"mappings":";;;AAsDA,SAAgBA,EACd,GACA,GACA,GACA,GACA,GACA,GAC4B;CAC5B,KAAK,IAAIC,IAAIC,EAAY,SAAS,GAAGD,KAAK,GAAG,KAAK;EAChD,IAAME,IAAID,EAAYD;EACtB,IAAIG,KAAa,CAACA,EAAUD,EAAE,EAAE,GAAG;EACnC,IAAME,IAAUF,EAAE,QAAQG,CAAK,GAC3BC,IAAwD;EAC5D,KAAK,IAAIC,IAAI,GAAGA,IAAIH,EAAQ,QAAQ,KAAK;GACvC,IAAMI,IAASJ,EAAQG,IACjBE,IAAKC,EAAYL,GAAOG,EAAO,QAAQ;GAC7C,IAAI,CAACG,EAAUH,GAAQI,GAAIC,GAAIJ,GAAIK,CAAI,GAAG;GAC1C,IAAMC,IAAKC,EAAgBR,GAAQI,GAAIC,GAAIJ,GAAIK,CAAI;GAGnD,CAAIR,MAAS,QAAQS,KAAMT,EAAK,QAAI,IAAO;IAAE;IAAQ;GAAG;EAC1D;EACA,IAAIA,GACF,OAAO;GACL,cAAcJ,EAAE;GAChB,UAAUI,EAAK,OAAO;GACtB,QAAQA,EAAK;GACb,SAASA,EAAK,OAAO,KAAK;EAC5B;CAEJ;CACA,OAAO;AACT;AAIA,SAASU,EACP,GACA,GACA,GACA,GACA,GACQ;CACR,IAAMC,IAAIT,EAAO,OACXU,IAAID,EAAE,SAAS,UACjBE,EAAkBF,GAAGR,GAAIK,CAAI,IAC7BG,EAAE,SAAS,YACTG,EAAaX,GAAIQ,EAAE,IAAIA,EAAE,EAAE,IAC3BG,EAAaX,GAAIQ,EAAE,IAAIA,EAAE,QAAQ,GAAGA,EAAE,IAAIA,EAAE,SAAS,CAAC,GACtDI,IAAKH,EAAE,IAAIN,GACXU,IAAKJ,EAAE,IAAIL;CACjB,OAAOQ,IAAKA,IAAKC,IAAKA;AACxB;AAkBA,IAAMC,IAA+B;CAAE,IAAI;CAAG,IAAI;CAAG,KAAK;CAAG,KAAK;CAAG,UAAU;AAAK;AAEpF,SAAgBb,EAAY,GAAoB,GAA0C;CACxF,IAAIc,MAAa,MAAM,OAAOD;CAC9B,IAAME,IAAmBpB,EAAM,SAASmB,CAAQ;CAChD,IAAI,CAACC,GAAG,OAAOF;CAEf,IAAMG,IAAIC,EAAeF,CAAC;CAE1B,OADKC,IACE;EACL,IAAIA,EAAE;EACN,IAAIA,EAAE;EACN,KAAK,KAAK,IAAIA,EAAE,QAAQ;EACxB,KAAK,KAAK,IAAIA,EAAE,QAAQ;EACxB,UAAU;CACZ,IAPeH;AAQjB;AAGA,SAAgBK,EAAQ,GAA6B;CACnD,OAAOnB,EAAG,WAAW,IAAI,KAAK,MAAMA,EAAG,KAAKA,EAAG,GAAG;AACpD;AAGA,SAAgBW,EAAa,GAAqB,GAAY,GAAsC;CAClG,IAAIX,EAAG,UAAU,OAAO;EAAE,GAAGoB;EAAI,GAAGC;CAAG;CACvC,IAAMT,IAAKQ,IAAKpB,EAAG,IACba,IAAKQ,IAAKrB,EAAG;CACnB,OAAO;EACL,GAAGA,EAAG,KAAKA,EAAG,MAAMY,IAAKZ,EAAG,MAAMa;EAClC,GAAGb,EAAG,KAAKA,EAAG,MAAMY,IAAKZ,EAAG,MAAMa;CACpC;AACF;AAGA,SAASS,EAAa,GAAqB,GAAY,GAAsC;CAC3F,IAAItB,EAAG,UAAU,OAAO;EAAE,GAAGG;EAAI,GAAGC;CAAG;CACvC,IAAMQ,IAAKT,IAAKH,EAAG,IACba,IAAKT,IAAKJ,EAAG;CAEnB,OAAO;EACL,GAAGA,EAAG,KAAKA,EAAG,MAAMY,IAAKZ,EAAG,MAAMa;EAClC,GAAGb,EAAG,KAAKA,EAAG,MAAMY,IAAKZ,EAAG,MAAMa;CACpC;AACF;AAWA,SAAgBU,EACd,GACA,GACA,GACiG;CACjG,IAAM,IAAIb,EAAkBc,GAAOxB,GAAIK,CAAI,GACrCoB,IAAQD,EAAM,SAASE,EAAcP,EAAQnB,CAAE,GAAGK,EAAK,KAAK,IAAI,GAChEsB,IAAOH,EAAM,aACbI,IAAsB,CAAC,CAACD,GAAMA,CAAI,GAClCE,IAAsB,CAAC,CAACF,GAAMA,CAAI,GAClCG,IAAON,EAAM;CACnB,IAAIM,GAAM;EAER,IAAMC,IAAQC,EAASC,IAAmBN,GAAMtB,EAAK,OAAOc,EAAQnB,CAAE,CAAC,GAGjEkC,IAAOJ,EAAK,QAAQ,IAAI,KAAK,KAAKzB,EAAK,MAAM,IAAIA,EAAK,MAAM,CAAC;EAEnE,AADIyB,EAAK,QAAQC,EAAM,MAAG,EAAED,EAAK,QAAQ,IAAI,IAAI,IAAI,KAAK,IACtDA,EAAK,SAASC,EAAM,MAAG,EAAEG,IAAO,IAAI,IAAI,KAAK;CACnD;CACA,OAAO;EAAE,GAAG,EAAE;EAAG,GAAG,EAAE;EAAG;EAAO;EAAM;EAAG;CAAE;AAC7C;AAIA,IAAMD,IAAmB;AAIzB,SAAgBE,EACd,GACA,GACA,GACA,GAC4B;CAC5B,IAAMC,IAAIb,EAAiBC,GAAOxB,GAAIK,CAAI,GACpC,CAACgC,GAAIC,KAAMC,EAASH,EAAE,GAAGA,EAAE,CAAC,GAC5BI,IAAI,KAAK,IAAIJ,EAAE,KAAK,GAAGK,IAAK,KAAK,IAAIL,EAAE,KAAK;CAClD,OAAO;EAAC,CAACA,EAAE,EAAE,IAAIA,EAAE,EAAE,EAAE;EAAG,CAACA,EAAE,EAAE,IAAIA,EAAE,EAAE,EAAE;EAAG,CAACA,EAAE,EAAE,IAAIA,EAAE,EAAE,EAAE;EAAG,CAACA,EAAE,EAAE,IAAIA,EAAE,EAAE,EAAE;CAAC,CAAC,CAAC,KAAK,CAACR,GAAGC,QAAQ;EAC/F,GAAGQ,IAAKG,IAAIZ,IAAIa,IAAKZ;EACrB,GAAGS,IAAKG,IAAKb,IAAIY,IAAIX;CACvB,EAAE;AACJ;AAGA,SAAgBnB,EACd,GACA,GACA,GAC0B;CAC1B,IAAMjB,IAAIkB,EAAaX,GAAIwB,EAAM,GAAGA,EAAM,CAAC;CAC3C,IAAI,CAACA,EAAM,UAAU,OAAO/B;CAC5B,IAAMiD,IAAIlB,EAAM,SAAS,QACnBmB,IAAK3C,EAAG,WACV0C,IACA;EAAE,GAAG1C,EAAG,MAAM0C,EAAE,IAAI1C,EAAG,MAAM0C,EAAE;EAAG,GAAG1C,EAAG,MAAM0C,EAAE,IAAI1C,EAAG,MAAM0C,EAAE;CAAE;CACrE,OAAOE,EAASnD,GAAGkD,GAAInB,EAAM,SAAS,IAAInB,EAAK,KAAK;AACtD;AAWA,SAAgBwC,EACd,GACA,GAGA,IAAW,GACiB;CAC5B,IAAMC,IAAStB,EAAM,aAAa;CAClC,IAAIsB,MAAW,GAAG,OAAO;EAAE,IAAItB,EAAM;EAAI,IAAIA,EAAM;CAAG;CACtD,IAAMuB,IAAOf,EAASc,GAAQzC,EAAK,OAAO2C,CAAQ;CAClD,OAAO;EACL,IAAI,KAAK,IAAIxB,EAAM,IAAIA,EAAM,aAAa,IAAIuB,EAAK,CAAC;EACpD,IAAI,KAAK,IAAIvB,EAAM,IAAIA,EAAM,cAAc,IAAIuB,EAAK,CAAC;CACvD;AACF;AAIA,SAAS7C,EACP,GACA,GACA,GACA,GACA,GACS;CACT,IAAM+C,IAAQ3B,EAAatB,GAAIG,GAAIC,CAAE;CACrC,IAAIL,EAAO,MAAM,SAAS,SAAS;EASjC,IAAMqC,IAAIb,EAAiBxB,EAAO,OAAOC,GAAIK,CAAI,GAC3C6C,IAAIC,EAAWhD,IAAKiC,EAAE,GAAGhC,IAAKgC,EAAE,GAAG/B,EAAK,KAAK,GAC7C,IAAI,KAAK,IAAI+B,EAAE,KAAK,GAAGK,IAAK,KAAK,IAAIL,EAAE,KAAK,GAC5C,IAAI,IAAIc,EAAE,IAAIT,IAAKS,EAAE,GAAGrB,IAAI,CAACY,IAAKS,EAAE,IAAI,IAAIA,EAAE;EACpD,OAAO,KAAKd,EAAE,EAAE,MAAM,KAAKA,EAAE,EAAE,MAAMP,KAAKO,EAAE,EAAE,MAAMP,KAAKO,EAAE,EAAE;CAC/D;CACA,IAAIrC,EAAO,MAAM,SAAS,WAAW;EACnC,IAAMS,IAAIT,EAAO;EAKjB,IAFEkD,EAAM,KAAKzC,EAAE,UAAUyC,EAAM,KAAKzC,EAAE,SAASA,EAAE,cAC/CyC,EAAM,KAAKzC,EAAE,UAAUyC,EAAM,KAAKzC,EAAE,SAASA,EAAE,aAChC,OAAO;EAExB,IAAM,EAAE,OAAI,UAAOqC,EAAgBrC,GAAGH,GAAMc,EAAQnB,CAAE,CAAC;EACvD,IAAIoD,KAAM,KAAKC,KAAM,GAAG,OAAO;EAC/B,IAAMC,KAAML,EAAM,IAAIzC,EAAE,MAAM4C,GACxBG,KAAMN,EAAM,IAAIzC,EAAE,MAAM6C;EAC9B,OAAOC,IAAKA,IAAKC,IAAKA,KAAM;CAC9B;CAEA,IAAMtC,IAAIlB,EAAO;CACjB,OAAOkD,EAAM,KAAKhC,EAAE,KAAKgC,EAAM,KAAKhC,EAAE,IAAIA,EAAE,SACrCgC,EAAM,KAAKhC,EAAE,KAAKgC,EAAM,KAAKhC,EAAE,IAAIA,EAAE;AAC9C"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { angleOf as e, transformOf as t } from "./hitAffordanceRegions.js";
|
|
2
|
+
import { MULTI_RESIZE_TARGET_ID as n } from "../core/selection/selectionTarget.js";
|
|
3
3
|
//#region src/affordances/rotationHandle.ts
|
|
4
4
|
var r = (e) => "fill" in e || "stroke" in e || "insetPx" in e, i = {
|
|
5
5
|
fill: {
|
|
@@ -8,8 +8,8 @@ var r = (e) => "fill" in e || "stroke" in e || "insetPx" in e, i = {
|
|
|
8
8
|
},
|
|
9
9
|
insetPx: 10
|
|
10
10
|
};
|
|
11
|
-
function a(
|
|
12
|
-
let { bandPx: a = 24, paint: s = i, cursor: c, handle: l } =
|
|
11
|
+
function a(n = {}) {
|
|
12
|
+
let { bandPx: a = 24, paint: s = i, cursor: c, handle: l } = n, u = s ? r(s) ? {
|
|
13
13
|
kind: "annulus",
|
|
14
14
|
...s.fill ? { fill: s.fill } : {},
|
|
15
15
|
...s.stroke ? { stroke: s.stroke } : {},
|
|
@@ -20,12 +20,12 @@ function a(e = {}) {
|
|
|
20
20
|
} : null;
|
|
21
21
|
return {
|
|
22
22
|
id: "selection.rotation-handle",
|
|
23
|
-
regions(
|
|
24
|
-
let r = o(
|
|
23
|
+
regions(n) {
|
|
24
|
+
let r = o(n);
|
|
25
25
|
if (!r) return [];
|
|
26
26
|
let i = r.bounds, s = i.width / 2, d = i.height / 2, f = c ?? {
|
|
27
27
|
glyph: "rotate",
|
|
28
|
-
angle: t(n
|
|
28
|
+
angle: e(t(n, r.id)),
|
|
29
29
|
fallback: "grab"
|
|
30
30
|
}, p = () => ({ initialScratch: {
|
|
31
31
|
targetId: r.id,
|
|
@@ -76,23 +76,23 @@ function a(e = {}) {
|
|
|
76
76
|
}
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
|
-
function o(
|
|
80
|
-
if (
|
|
81
|
-
let
|
|
82
|
-
if (
|
|
83
|
-
let n =
|
|
79
|
+
function o(e) {
|
|
80
|
+
if (e.selection.length === 1) {
|
|
81
|
+
let t = e.selection[0];
|
|
82
|
+
if (e.canRotate && !e.canRotate(t)) return null;
|
|
83
|
+
let n = e.boundsOf(t);
|
|
84
84
|
return n ? {
|
|
85
|
-
id:
|
|
85
|
+
id: t,
|
|
86
86
|
bounds: n
|
|
87
87
|
} : null;
|
|
88
88
|
}
|
|
89
|
-
if (
|
|
90
|
-
if (
|
|
91
|
-
for (let
|
|
89
|
+
if (e.multiActive && e.unionBounds) {
|
|
90
|
+
if (e.canRotate) {
|
|
91
|
+
for (let t of e.selection) if (!e.canRotate(t)) return null;
|
|
92
92
|
}
|
|
93
93
|
return {
|
|
94
|
-
id:
|
|
95
|
-
bounds:
|
|
94
|
+
id: n,
|
|
95
|
+
bounds: e.unionBounds
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
98
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rotationHandle.js","names":["isAnnulusForm","paint","DEFAULT_PAINT","createRotationAffordance","opts","annulusPaint","target","pickTarget","state","b","halfW","halfH","regionCursor","cursor","angleOf","transformOf","bind","region","bandPx","handle","id","MULTI_RESIZE_TARGET_ID"],"sources":["../../src/affordances/rotationHandle.ts"],"sourcesContent":["import type { Affordance, AffordanceBinding, AffordanceRegion, CommonAffordanceScratch } from './types';\nimport type { ChromeState, Bounds } from 'core/selection/chromeState';\nimport type { FillStyle, Stroke } from '@weasel-js/paint';\nimport { MULTI_RESIZE_TARGET_ID } from 'core/selection/selectionTarget';\nimport { angleOf, transformOf } from './hitAffordanceRegions';\nimport type { CursorSpec } from '@weasel-js/cursor';\n\n/** Options for `createRotationAffordance` — how thick the rotate band is\n * outside the selection, and how (or whether) it is painted. */\nexport interface RotationAffordanceOptions {\n /** Minimum screen-space thickness of the rotate-zone band outside the\n * selection AABB. The outer ellipse semi-axes are clamped to\n * `max(w/√2, w/2 + bandPx/scale)` so the zone stays hoverable at any\n * zoom and any selection size. Default 24. */\n bandPx?: number;\n /** Paint for the ring. `null` / `false` = invisible. Default is a\n * semi-transparent fill of the band, contracted by `insetPx` screen\n * pixels on the outer edge so the visible band sits inside the\n * hoverable zone with a clear margin. Pass a `Stroke` (or\n * `{ fill, stroke, insetPx }`) to override. */\n paint?:\n | Stroke\n | { fill?: FillStyle; stroke?: Stroke; insetPx?: number }\n | null\n | false;\n /** Cursor while hovering the ring. Defaults to the `rotate` glyph turned\n * with the target, falling back to `'grab'` if the browser rejects the\n * image. */\n cursor?: CursorSpec;\n /** A grab region for a rotate badge painted off the top edge — the one\n * the selection overlay's `rotationHandle` draws. `distancePx` is how far\n * off the edge it sits and `hitRadiusPx` the half-extent of its square,\n * both in screen pixels. Placed by the same `standoff` the painted badge\n * is, so the two cannot drift apart. Omitted, only the ring rotates. */\n handle?: { distancePx: number; hitRadiusPx: number };\n}\n\n/** What a rotation-handle hit hands to the rotate action: what is being\n * rotated, and the world-space pivot it turns around. */\nexport interface RotationScratch extends CommonAffordanceScratch {\n /** Id of the rotation target — single selection id, or\n * `MULTI_RESIZE_TARGET_ID` for multi-selection. */\n targetId: string;\n /** Rotation pivot in world coords — the AABB (or union AABB) center. */\n fixedPoint: { x: number; y: number };\n}\n\ntype AnnulusPaintForm = { fill?: FillStyle; stroke?: Stroke; insetPx?: number };\n\nconst isAnnulusForm = (paint: Stroke | AnnulusPaintForm): paint is AnnulusPaintForm =>\n 'fill' in paint || 'stroke' in paint || 'insetPx' in paint;\n\nconst DEFAULT_PAINT: AnnulusPaintForm = {\n fill: { fill: 'solid', color: 'rgba(26, 19, 13, 0.12)' },\n insetPx: 10,\n};\n\n/**\n * @experimental\n * Rotation affordance for the active selection. Replaces the legacy\n * \"small dot above the AABB\" handle with an invisible elliptical ring\n * that wraps the selection: the smallest ellipse containing the AABB,\n * minus the AABB itself. Hovering the ring sets the cursor (default\n * `'grab'`) and drag-from-ring triggers `rotateAction` via the same\n * binding path the old point-handle used (id stays\n * `'selection.rotation-handle'`).\n *\n * The ring is target-local — under target rotation the ellipse rotates\n * with the AABB so the hoverable band always wraps the rendered shape.\n *\n * Visibility: gated by chrome-caps' `'selection.rotation-handle'` rule.\n * The `paint` option drives optional dev-time ring stroke; production\n * defaults to invisible (the cursor change is the only on-hover cue).\n */\nexport function createRotationAffordance(\n opts: RotationAffordanceOptions = {},\n): Affordance {\n const { bandPx = 24, paint = DEFAULT_PAINT, cursor, handle } = opts;\n\n // Normalize `paint` into the AnnulusPaint shape understood by\n // `composeAffordanceLayer`. Three input forms are accepted: a bare\n // `Stroke` (legacy outline form), the `{ fill, stroke, insetPx }`\n // object form, or `null`/`false` for invisible. The object form is what\n // carries these three keys; `Stroke` carries none of them, and cannot be\n // told apart by its own `paint` since that is optional.\n const annulusPaint:\n | { kind: 'annulus'; fill?: FillStyle; stroke?: Stroke; insetPx?: number }\n | null = (() => {\n if (!paint) return null;\n if (!isAnnulusForm(paint)) {\n return { kind: 'annulus' as const, stroke: paint };\n }\n return {\n kind: 'annulus' as const,\n ...(paint.fill ? { fill: paint.fill } : {}),\n ...(paint.stroke ? { stroke: paint.stroke } : {}),\n ...(paint.insetPx !== undefined ? { insetPx: paint.insetPx } : {}),\n };\n })();\n\n return {\n id: 'selection.rotation-handle',\n regions(state: ChromeState): readonly AffordanceRegion[] {\n const target = pickTarget(state);\n if (!target) return [];\n const b = target.bounds;\n\n // The natural ring: the smallest ellipse containing the AABB. For a\n // small or thin selection that band is too tight to hover, so\n // `minBandPx` asks the framework to widen it to at least `bandPx`\n // screen pixels outside the rect. The floor is declared in screen units\n // rather than applied here because `ChromeState` carries no view scale\n // — the framework applies it at paint and hit time, where the view is\n // known, so the ring holds its apparent thickness at any zoom.\n const halfW = b.width / 2;\n const halfH = b.height / 2;\n\n // Turned with the target, so the arc follows the selection it rotates.\n // Resolved per region rather than defaulted in the option list because\n // the angle is only known once a target is picked.\n const regionCursor: CursorSpec = cursor ?? {\n glyph: 'rotate',\n angle: angleOf(transformOf(state, target.id)),\n fallback: 'grab',\n };\n const bind = (): AffordanceBinding => ({\n initialScratch: {\n targetId: target.id,\n // Pivot. `rotateAction` recomputes its own union pivot for\n // multi-selection, but the single-target case reads this.\n fixedPoint: { x: b.x + halfW, y: b.y + halfH },\n } satisfies RotationScratch,\n });\n\n const region: AffordanceRegion = {\n id: 'selection.rotation-handle',\n targetId: target.id,\n shape: {\n kind: 'annulus',\n cx: b.x + halfW,\n cy: b.y + halfH,\n rx: halfW * Math.SQRT2,\n ry: halfH * Math.SQRT2,\n innerX: b.x,\n innerY: b.y,\n innerWidth: b.width,\n innerHeight: b.height,\n minBandPx: bandPx,\n },\n ...(annulusPaint ? { paint: annulusPaint } : {}),\n hitKind: 'rotate-handle',\n cursor: regionCursor,\n bind,\n };\n if (!handle) return [region];\n const badge: AffordanceRegion = {\n id: 'rotation-badge',\n targetId: target.id,\n shape: {\n kind: 'point',\n x: b.x + halfW,\n y: b.y,\n hitRadiusPx: handle.hitRadiusPx,\n standoff: { px: handle.distancePx, normal: { x: 0, y: -1 } },\n },\n hitKind: 'rotate-handle',\n cursor: regionCursor,\n bind,\n };\n return [region, badge];\n },\n };\n}\n\nfunction pickTarget(state: ChromeState): { id: string; bounds: Bounds } | null {\n if (state.selection.length === 1) {\n const id = state.selection[0];\n // Skip when the selected node's pose-descriptor declares it can't\n // carry a rotation. Without this the rotation affordance would expose\n // a rotate cursor over (e.g.) polygon-Path selections whose\n // `rotateAction` write is silently dropped at paint time.\n if (state.canRotate && !state.canRotate(id)) return null;\n const b = state.boundsOf(id);\n return b ? { id, bounds: b } : null;\n }\n // Multi-selection: anchor the ring to the union AABB. `rotateAction`\n // ignores the synthetic id and pivots at its own union center\n // (`useUnionPivot: ids.length > 1` branch in `defaults/rotate.ts`).\n // Require every selected id to support rotation — a partial multi-rotate\n // (some ids rotate, others silently no-op) would feel broken.\n if (state.multiActive && state.unionBounds) {\n if (state.canRotate) {\n for (const id of state.selection) {\n if (!state.canRotate(id)) return null;\n }\n }\n return { id: MULTI_RESIZE_TARGET_ID, bounds: state.unionBounds };\n }\n return null;\n}\n"],"mappings":";;;AAiDA,IAAMA,KAAiB,MACrB,UAAUC,KAAS,YAAYA,KAAS,aAAaA,GAEjDC,IAAkC;CACtC,MAAM;EAAE,MAAM;EAAS,OAAO;CAAyB;CACvD,SAAS;AACX;AAmBA,SAAgBC,EACd,IAAkC,CAAC,GACvB;CACZ,IAAM,EAAE,YAAS,IAAI,WAAQD,GAAe,WAAQ,cAAWE,GAQzDC,IAGGJ,IACAD,EAAcC,CAAK,IAGjB;EACL,MAAM;EACN,GAAIA,EAAM,OAAO,EAAE,MAAMA,EAAM,KAAK,IAAI,CAAC;EACzC,GAAIA,EAAM,SAAS,EAAE,QAAQA,EAAM,OAAO,IAAI,CAAC;EAC/C,GAAIA,EAAM,YAAY,KAAA,IAAyC,CAAC,IAA9B,EAAE,SAASA,EAAM,QAAQ;CAC7D,IAPS;EAAE,MAAM;EAAoB,QAAQA;CAAM,IAFhC;CAYvB,OAAO;EACL,IAAI;EACJ,QAAQ,GAAiD;GACvD,IAAMK,IAASC,EAAWC,CAAK;GAC/B,IAAI,CAACF,GAAQ,OAAO,CAAC;GACrB,IAAMG,IAAIH,EAAO,QASXI,IAAQD,EAAE,QAAQ,GAClBE,IAAQF,EAAE,SAAS,GAKnBG,IAA2BC,KAAU;IACzC,OAAO;IACP,OAAOC,EAAQC,EAAYP,GAAOF,EAAO,EAAE,CAAC;IAC5C,UAAU;GACZ,GACMU,WAAiC,EACrC,gBAAgB;IACd,UAAUV,EAAO;IAGjB,YAAY;KAAE,GAAGG,EAAE,IAAIC;KAAO,GAAGD,EAAE,IAAIE;IAAM;GAC/C,EACF,IAEMM,IAA2B;IAC/B,IAAI;IACJ,UAAUX,EAAO;IACjB,OAAO;KACL,MAAM;KACN,IAAIG,EAAE,IAAIC;KACV,IAAID,EAAE,IAAIE;KACV,IAAID,IAAQ,KAAK;KACjB,IAAIC,IAAQ,KAAK;KACjB,QAAQF,EAAE;KACV,QAAQA,EAAE;KACV,YAAYA,EAAE;KACd,aAAaA,EAAE;KACf,WAAWS;IACb;IACA,GAAIb,IAAe,EAAE,OAAOA,EAAa,IAAI,CAAC;IAC9C,SAAS;IACT,QAAQO;IACR;GACF;GAgBA,OAfKO,IAeE,CAACF,GAAQ;IAbd,IAAI;IACJ,UAAUX,EAAO;IACjB,OAAO;KACL,MAAM;KACN,GAAGG,EAAE,IAAIC;KACT,GAAGD,EAAE;KACL,aAAaU,EAAO;KACpB,UAAU;MAAE,IAAIA,EAAO;MAAY,QAAQ;OAAE,GAAG;OAAG,GAAG;MAAG;KAAE;IAC7D;IACA,SAAS;IACT,QAAQP;IACR;GAEc,CAAK,IAfD,CAACK,CAAM;EAgB7B;CACF;AACF;AAEA,SAASV,EAAW,GAA2D;CAC7E,IAAIC,EAAM,UAAU,WAAW,GAAG;EAChC,IAAMY,IAAKZ,EAAM,UAAU;EAK3B,IAAIA,EAAM,aAAa,CAACA,EAAM,UAAUY,CAAE,GAAG,OAAO;EACpD,IAAMX,IAAID,EAAM,SAASY,CAAE;EAC3B,OAAOX,IAAI;GAAE;GAAI,QAAQA;EAAE,IAAI;CACjC;CAMA,IAAID,EAAM,eAAeA,EAAM,aAAa;EAC1C,IAAIA,EAAM,WACH;QAAA,IAAMY,KAAMZ,EAAM,WACrB,IAAI,CAACA,EAAM,UAAUY,CAAE,GAAG,OAAO;EAAA;EAGrC,OAAO;GAAE,IAAIC;GAAwB,QAAQb,EAAM;EAAY;CACjE;CACA,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"rotationHandle.js","names":["isAnnulusForm","paint","DEFAULT_PAINT","createRotationAffordance","opts","annulusPaint","target","pickTarget","state","b","halfW","halfH","regionCursor","cursor","angleOf","transformOf","bind","region","bandPx","handle","id","MULTI_RESIZE_TARGET_ID"],"sources":["../../src/affordances/rotationHandle.ts"],"sourcesContent":["import type { Affordance, AffordanceBinding, AffordanceRegion, CommonAffordanceScratch } from './types';\nimport type { ChromeState, Bounds } from 'core/selection/chromeState';\nimport type { CommandFill, CommandStroke } from '@weasel-js/paint';\nimport { MULTI_RESIZE_TARGET_ID } from 'core/selection/selectionTarget';\nimport { angleOf, transformOf } from './hitAffordanceRegions';\nimport type { CursorSpec } from '@weasel-js/cursor';\n\n/** Options for `createRotationAffordance` — how thick the rotate band is\n * outside the selection, and how (or whether) it is painted. */\nexport interface RotationAffordanceOptions {\n /** Minimum screen-space thickness of the rotate-zone band outside the\n * selection AABB. The outer ellipse semi-axes are clamped to\n * `max(w/√2, w/2 + bandPx/scale)` so the zone stays hoverable at any\n * zoom and any selection size. Default 24. */\n bandPx?: number;\n /** Paint for the ring. `null` / `false` = invisible. Default is a\n * semi-transparent fill of the band, contracted by `insetPx` screen\n * pixels on the outer edge so the visible band sits inside the\n * hoverable zone with a clear margin. Pass a `Stroke` (or\n * `{ fill, stroke, insetPx }`) to override. */\n paint?:\n | CommandStroke\n | { fill?: CommandFill; stroke?: CommandStroke; insetPx?: number }\n | null\n | false;\n /** Cursor while hovering the ring. Defaults to the `rotate` glyph turned\n * with the target, falling back to `'grab'` if the browser rejects the\n * image. */\n cursor?: CursorSpec;\n /** A grab region for a rotate badge painted off the top edge — the one\n * the selection overlay's `rotationHandle` draws. `distancePx` is how far\n * off the edge it sits and `hitRadiusPx` the half-extent of its square,\n * both in screen pixels. Placed by the same `standoff` the painted badge\n * is, so the two cannot drift apart. Omitted, only the ring rotates. */\n handle?: { distancePx: number; hitRadiusPx: number };\n}\n\n/** What a rotation-handle hit hands to the rotate action: what is being\n * rotated, and the world-space pivot it turns around. */\nexport interface RotationScratch extends CommonAffordanceScratch {\n /** Id of the rotation target — single selection id, or\n * `MULTI_RESIZE_TARGET_ID` for multi-selection. */\n targetId: string;\n /** Rotation pivot in world coords — the AABB (or union AABB) center. */\n fixedPoint: { x: number; y: number };\n}\n\ntype AnnulusPaintForm = { fill?: CommandFill; stroke?: CommandStroke; insetPx?: number };\n\nconst isAnnulusForm = (paint: CommandStroke | AnnulusPaintForm): paint is AnnulusPaintForm =>\n 'fill' in paint || 'stroke' in paint || 'insetPx' in paint;\n\nconst DEFAULT_PAINT: AnnulusPaintForm = {\n fill: { fill: 'solid', color: 'rgba(26, 19, 13, 0.12)' },\n insetPx: 10,\n};\n\n/**\n * @experimental\n * Rotation affordance for the active selection. Replaces the legacy\n * \"small dot above the AABB\" handle with an elliptical ring that wraps\n * the selection: the smallest ellipse containing the AABB, minus the AABB\n * itself. Hovering the ring sets the cursor (default the `rotate` glyph)\n * and drag-from-ring triggers `rotateAction` via the same\n * binding path the old point-handle used (id stays\n * `'selection.rotation-handle'`).\n *\n * The ring is target-local — under target rotation the ellipse rotates\n * with the AABB so the hoverable band always wraps the rendered shape.\n *\n * Visibility: gated by chrome-caps' `'selection.rotation-handle'` rule.\n * The ring paints as a faint band by default; `paint: null` leaves the\n * cursor change as the only cue.\n */\nexport function createRotationAffordance(\n opts: RotationAffordanceOptions = {},\n): Affordance {\n const { bandPx = 24, paint = DEFAULT_PAINT, cursor, handle } = opts;\n\n // Normalize `paint` into the AnnulusPaint shape understood by\n // `composeAffordanceLayer`. Three input forms are accepted: a bare\n // `Stroke` (legacy outline form), the `{ fill, stroke, insetPx }`\n // object form, or `null`/`false` for invisible. The object form is what\n // carries these three keys; `Stroke` carries none of them, and cannot be\n // told apart by its own `paint` since that is optional.\n const annulusPaint:\n | { kind: 'annulus'; fill?: CommandFill; stroke?: CommandStroke; insetPx?: number }\n | null = (() => {\n if (!paint) return null;\n if (!isAnnulusForm(paint)) {\n return { kind: 'annulus' as const, stroke: paint };\n }\n return {\n kind: 'annulus' as const,\n ...(paint.fill ? { fill: paint.fill } : {}),\n ...(paint.stroke ? { stroke: paint.stroke } : {}),\n ...(paint.insetPx !== undefined ? { insetPx: paint.insetPx } : {}),\n };\n })();\n\n return {\n id: 'selection.rotation-handle',\n regions(state: ChromeState): readonly AffordanceRegion[] {\n const target = pickTarget(state);\n if (!target) return [];\n const b = target.bounds;\n\n // The natural ring: the smallest ellipse containing the AABB. For a\n // small or thin selection that band is too tight to hover, so\n // `minBandPx` asks the framework to widen it to at least `bandPx`\n // screen pixels outside the rect. The floor is declared in screen units\n // rather than applied here because `ChromeState` carries no view scale\n // — the framework applies it at paint and hit time, where the view is\n // known, so the ring holds its apparent thickness at any zoom.\n const halfW = b.width / 2;\n const halfH = b.height / 2;\n\n // Turned with the target, so the arc follows the selection it rotates.\n // Resolved per region rather than defaulted in the option list because\n // the angle is only known once a target is picked.\n const regionCursor: CursorSpec = cursor ?? {\n glyph: 'rotate',\n angle: angleOf(transformOf(state, target.id)),\n fallback: 'grab',\n };\n const bind = (): AffordanceBinding => ({\n initialScratch: {\n targetId: target.id,\n // Pivot. `rotateAction` recomputes its own union pivot for\n // multi-selection, but the single-target case reads this.\n fixedPoint: { x: b.x + halfW, y: b.y + halfH },\n } satisfies RotationScratch,\n });\n\n const region: AffordanceRegion = {\n id: 'selection.rotation-handle',\n targetId: target.id,\n shape: {\n kind: 'annulus',\n cx: b.x + halfW,\n cy: b.y + halfH,\n rx: halfW * Math.SQRT2,\n ry: halfH * Math.SQRT2,\n innerX: b.x,\n innerY: b.y,\n innerWidth: b.width,\n innerHeight: b.height,\n minBandPx: bandPx,\n },\n ...(annulusPaint ? { paint: annulusPaint } : {}),\n hitKind: 'rotate-handle',\n cursor: regionCursor,\n bind,\n };\n if (!handle) return [region];\n const badge: AffordanceRegion = {\n id: 'rotation-badge',\n targetId: target.id,\n shape: {\n kind: 'point',\n x: b.x + halfW,\n y: b.y,\n hitRadiusPx: handle.hitRadiusPx,\n standoff: { px: handle.distancePx, normal: { x: 0, y: -1 } },\n },\n hitKind: 'rotate-handle',\n cursor: regionCursor,\n bind,\n };\n return [region, badge];\n },\n };\n}\n\nfunction pickTarget(state: ChromeState): { id: string; bounds: Bounds } | null {\n if (state.selection.length === 1) {\n const id = state.selection[0];\n // Skip when the selected node's pose-descriptor declares it can't\n // carry a rotation. Without this the rotation affordance would expose\n // a rotate cursor over (e.g.) polygon-Path selections whose\n // `rotateAction` write is silently dropped at paint time.\n if (state.canRotate && !state.canRotate(id)) return null;\n const b = state.boundsOf(id);\n return b ? { id, bounds: b } : null;\n }\n // Multi-selection: anchor the ring to the union AABB. `rotateAction`\n // ignores the synthetic id and pivots at its own union center\n // (`useUnionPivot: ids.length > 1` branch in `defaults/rotate.ts`).\n // Require every selected id to support rotation — a partial multi-rotate\n // (some ids rotate, others silently no-op) would feel broken.\n if (state.multiActive && state.unionBounds) {\n if (state.canRotate) {\n for (const id of state.selection) {\n if (!state.canRotate(id)) return null;\n }\n }\n return { id: MULTI_RESIZE_TARGET_ID, bounds: state.unionBounds };\n }\n return null;\n}\n"],"mappings":";;;AAiDA,IAAMA,KAAiB,MACrB,UAAUC,KAAS,YAAYA,KAAS,aAAaA,GAEjDC,IAAkC;CACtC,MAAM;EAAE,MAAM;EAAS,OAAO;CAAyB;CACvD,SAAS;AACX;AAmBA,SAAgBC,EACd,IAAkC,CAAC,GACvB;CACZ,IAAM,EAAE,YAAS,IAAI,WAAQD,GAAe,WAAQ,cAAWE,GAQzDC,IAGGJ,IACAD,EAAcC,CAAK,IAGjB;EACL,MAAM;EACN,GAAIA,EAAM,OAAO,EAAE,MAAMA,EAAM,KAAK,IAAI,CAAC;EACzC,GAAIA,EAAM,SAAS,EAAE,QAAQA,EAAM,OAAO,IAAI,CAAC;EAC/C,GAAIA,EAAM,YAAY,KAAA,IAAyC,CAAC,IAA9B,EAAE,SAASA,EAAM,QAAQ;CAC7D,IAPS;EAAE,MAAM;EAAoB,QAAQA;CAAM,IAFhC;CAYvB,OAAO;EACL,IAAI;EACJ,QAAQ,GAAiD;GACvD,IAAMK,IAASC,EAAWC,CAAK;GAC/B,IAAI,CAACF,GAAQ,OAAO,CAAC;GACrB,IAAMG,IAAIH,EAAO,QASXI,IAAQD,EAAE,QAAQ,GAClBE,IAAQF,EAAE,SAAS,GAKnBG,IAA2BC,KAAU;IACzC,OAAO;IACP,OAAOC,EAAQC,EAAYP,GAAOF,EAAO,EAAE,CAAC;IAC5C,UAAU;GACZ,GACMU,WAAiC,EACrC,gBAAgB;IACd,UAAUV,EAAO;IAGjB,YAAY;KAAE,GAAGG,EAAE,IAAIC;KAAO,GAAGD,EAAE,IAAIE;IAAM;GAC/C,EACF,IAEMM,IAA2B;IAC/B,IAAI;IACJ,UAAUX,EAAO;IACjB,OAAO;KACL,MAAM;KACN,IAAIG,EAAE,IAAIC;KACV,IAAID,EAAE,IAAIE;KACV,IAAID,IAAQ,KAAK;KACjB,IAAIC,IAAQ,KAAK;KACjB,QAAQF,EAAE;KACV,QAAQA,EAAE;KACV,YAAYA,EAAE;KACd,aAAaA,EAAE;KACf,WAAWS;IACb;IACA,GAAIb,IAAe,EAAE,OAAOA,EAAa,IAAI,CAAC;IAC9C,SAAS;IACT,QAAQO;IACR;GACF;GAgBA,OAfKO,IAeE,CAACF,GAAQ;IAbd,IAAI;IACJ,UAAUX,EAAO;IACjB,OAAO;KACL,MAAM;KACN,GAAGG,EAAE,IAAIC;KACT,GAAGD,EAAE;KACL,aAAaU,EAAO;KACpB,UAAU;MAAE,IAAIA,EAAO;MAAY,QAAQ;OAAE,GAAG;OAAG,GAAG;MAAG;KAAE;IAC7D;IACA,SAAS;IACT,QAAQP;IACR;GAEc,CAAK,IAfD,CAACK,CAAM;EAgB7B;CACF;AACF;AAEA,SAASV,EAAW,GAA2D;CAC7E,IAAIC,EAAM,UAAU,WAAW,GAAG;EAChC,IAAMY,IAAKZ,EAAM,UAAU;EAK3B,IAAIA,EAAM,aAAa,CAACA,EAAM,UAAUY,CAAE,GAAG,OAAO;EACpD,IAAMX,IAAID,EAAM,SAASY,CAAE;EAC3B,OAAOX,IAAI;GAAE;GAAI,QAAQA;EAAE,IAAI;CACjC;CAMA,IAAID,EAAM,eAAeA,EAAM,aAAa;EAC1C,IAAIA,EAAM,WACH;QAAA,IAAMY,KAAMZ,EAAM,WACrB,IAAI,CAACA,EAAM,UAAUY,CAAE,GAAG,OAAO;EAAA;EAGrC,OAAO;GAAE,IAAIC;GAAwB,QAAQb,EAAM;EAAY;CACjE;CACA,OAAO;AACT"}
|