@weasel-js/core 1.11.1 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +184 -0
- package/dist/WeaselProvider.js +1 -1
- package/dist/affordances/pathAnchors.js +10 -6
- package/dist/affordances/pathAnchors.js.map +1 -1
- package/dist/barrel/canvas.js +3 -1
- package/dist/barrel/paint.js +4 -4
- package/dist/barrel/paths.js +8 -4
- package/dist/barrel/renderer.js +3 -2
- package/dist/barrel/routing.js +4 -4
- package/dist/barrel/scene.js +2 -1
- package/dist/barrel/text.js +2 -2
- package/dist/barrel/viewport.js +6 -2
- package/dist/canvas/Canvas.js +2 -1
- package/dist/canvas/Canvas.js.map +1 -1
- package/dist/canvas/CanvasView.js +23 -34
- package/dist/canvas/CanvasView.js.map +1 -1
- package/dist/canvas/ModalityHud.js +1 -1
- package/dist/canvas/NodeShape.js +4 -1
- package/dist/canvas/NodeShape.js.map +1 -1
- package/dist/canvas/SceneCanvas/ActionsProviderIfRoot.js +1 -1
- package/dist/canvas/SceneCanvas/DepRegistryProviderIfRoot.js +1 -1
- package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +1 -1
- package/dist/canvas/SceneCanvas/SurfaceDeclarations.js +23 -0
- package/dist/canvas/SceneCanvas/SurfaceDeclarations.js.map +1 -0
- package/dist/canvas/SceneCanvas/canvasPassThrough.js +1 -0
- package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
- package/dist/canvas/SceneCanvas/props.js.map +1 -1
- package/dist/canvas/SceneCanvas/scenePointQuery.js +7 -3
- package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -1
- package/dist/canvas/SceneCanvas/useActionTunings.js +6 -1
- package/dist/canvas/SceneCanvas/useActionTunings.js.map +1 -1
- package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js +1 -0
- package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js.map +1 -1
- package/dist/canvas/SceneCanvas/useCanvasTools.js +7 -2
- package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js +9 -3
- package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +10 -2
- package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +1 -0
- package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js +19 -4
- package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js.map +1 -1
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js +2 -1
- package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
- package/dist/canvas/SceneCanvas.js +11 -10
- package/dist/canvas/SceneCanvas.js.map +1 -1
- package/dist/canvas/ViewMarker.js +35 -0
- package/dist/canvas/ViewMarker.js.map +1 -0
- package/dist/canvas/defaultDrawOne.js +2 -0
- package/dist/canvas/defaultDrawOne.js.map +1 -1
- package/dist/canvas/deps/booleans.js +2 -1
- package/dist/canvas/deps/booleans.js.map +1 -1
- package/dist/canvas/deps/hitTestArea.js +2 -2
- package/dist/canvas/deps/hitTestArea.js.map +1 -1
- package/dist/canvas/deps/insert.js +6 -2
- package/dist/canvas/deps/insert.js.map +1 -1
- package/dist/canvas/deps/pathEditCommits.js +3 -7
- package/dist/canvas/deps/pathEditCommits.js.map +1 -1
- package/dist/canvas/drawnBounds.js +11 -3
- package/dist/canvas/drawnBounds.js.map +1 -1
- package/dist/canvas/inheritedPaints.js +44 -0
- package/dist/canvas/inheritedPaints.js.map +1 -0
- package/dist/canvas/nodeShapes/derivedPainter.js +15 -16
- package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/nodePaint.js +13 -34
- package/dist/canvas/nodeShapes/nodePaint.js.map +1 -1
- package/dist/canvas/nodeShapes/pathPainter.js +9 -8
- package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/rectFallbackPainter.js +4 -5
- package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -1
- package/dist/canvas/nodeShapes/shapePainter.js +11 -10
- package/dist/canvas/nodeShapes/shapePainter.js.map +1 -1
- package/dist/canvas/nodeShapes/textPainter.js +13 -12
- package/dist/canvas/nodeShapes/textPainter.js.map +1 -1
- package/dist/canvas/pickReach.js +3 -3
- package/dist/canvas/pickReach.js.map +1 -1
- package/dist/canvas/pickWalk.js +3 -1
- package/dist/canvas/pickWalk.js.map +1 -1
- package/dist/canvas/renderSceneToPixels.js +1 -1
- package/dist/canvas/renderSceneToPixels.js.map +1 -1
- package/dist/canvas/sceneSlotWiring.js +2 -1
- package/dist/canvas/sceneSlotWiring.js.map +1 -1
- package/dist/canvas/sceneViewRender.js +2 -1
- package/dist/canvas/sceneViewRender.js.map +1 -1
- package/dist/canvas/surfaceContribution.js +12 -8
- package/dist/canvas/surfaceContribution.js.map +1 -1
- package/dist/canvas/useCanvasView.js +4 -2
- package/dist/canvas/useCanvasView.js.map +1 -1
- package/dist/canvas/useLayerStack.js +6 -2
- package/dist/canvas/useLayerStack.js.map +1 -1
- package/dist/canvas/useViewAim.js +121 -0
- package/dist/canvas/useViewAim.js.map +1 -0
- package/dist/canvas/useViewNesting.js +3 -2
- package/dist/canvas/useViewNesting.js.map +1 -1
- package/dist/canvas/useViewRegistrySurface.js +4 -2
- package/dist/canvas/useViewRegistrySurface.js.map +1 -1
- package/dist/canvas/viewMarkers.js +63 -0
- package/dist/canvas/viewMarkers.js.map +1 -0
- package/dist/canvas/viewRegistry.js +27 -1
- package/dist/canvas/viewRegistry.js.map +1 -1
- package/dist/core/pages/pageEdits.js +2 -3
- package/dist/core/pages/pageEdits.js.map +1 -1
- package/dist/core/paint/meshKindLazy.js +59 -2
- package/dist/core/paint/meshKindLazy.js.map +1 -1
- package/dist/core/scene/clipNothing.js +13 -0
- package/dist/core/scene/clipNothing.js.map +1 -0
- package/dist/core/scene/depMemo.js +11 -3
- package/dist/core/scene/depMemo.js.map +1 -1
- package/dist/core/scene/dependents.js +7 -1
- package/dist/core/scene/dependents.js.map +1 -1
- package/dist/core/scene/derivedParts.js +10 -11
- package/dist/core/scene/derivedParts.js.map +1 -1
- package/dist/core/scene/effectivePose.js +7 -2
- package/dist/core/scene/effectivePose.js.map +1 -1
- package/dist/core/scene/fontReads.js +37 -0
- package/dist/core/scene/fontReads.js.map +1 -0
- package/dist/core/scene/inheritedPaint.js +75 -0
- package/dist/core/scene/inheritedPaint.js.map +1 -0
- package/dist/core/scene/kitRegistry.js +5 -0
- package/dist/core/scene/kitRegistry.js.map +1 -1
- package/dist/core/scene/nodeMemo.js +15 -7
- package/dist/core/scene/nodeMemo.js.map +1 -1
- package/dist/core/scene/pathEffects/blend.js.map +1 -1
- package/dist/core/scene/pathEffects/derive.js +7 -2
- package/dist/core/scene/pathEffects/derive.js.map +1 -1
- package/dist/core/scene/pathEffects/effects.js +3 -1
- package/dist/core/scene/pathEffects/effects.js.map +1 -1
- package/dist/core/scene/pathEffects/textAlong.js +95 -0
- package/dist/core/scene/pathEffects/textAlong.js.map +1 -0
- package/dist/{cornerHandles-BHuoMs1o.d.ts → cornerHandles-p8fDIAKt.d.ts} +2 -1
- package/dist/{depSchema-BpZNOv7q.d.ts → depSchema-mG19aEdD.d.ts} +61 -13
- package/dist/devtools/registry.js +4 -4
- package/dist/devtools/registry.js.map +1 -1
- package/dist/features/measure/measureContribution.js +4 -2
- package/dist/features/measure/measureContribution.js.map +1 -1
- package/dist/features/modes/modeTriggers.js +7 -3
- package/dist/features/modes/modeTriggers.js.map +1 -1
- package/dist/features/pathEffects/effectSources.js +60 -0
- package/dist/features/pathEffects/effectSources.js.map +1 -0
- package/dist/features/pathEffects/readSource.js +17 -1
- package/dist/features/pathEffects/readSource.js.map +1 -1
- package/dist/features/pathEffects/textItems.js +53 -0
- package/dist/features/pathEffects/textItems.js.map +1 -0
- package/dist/features/pathEffects/useEffectSourcesLayer.js +57 -0
- package/dist/features/pathEffects/useEffectSourcesLayer.js.map +1 -0
- package/dist/features/paths/pathEditState.js +5 -2
- package/dist/features/paths/pathEditState.js.map +1 -1
- package/dist/features/text/sceneTextDrafts.js +65 -0
- package/dist/features/text/sceneTextDrafts.js.map +1 -0
- package/dist/features/text/textHitAt.js +31 -2
- package/dist/features/text/textHitAt.js.map +1 -1
- package/dist/features/text/useSceneTextEdit.js +22 -18
- package/dist/features/text/useSceneTextEdit.js.map +1 -1
- package/dist/features/viewports/aim.js +105 -0
- package/dist/features/viewports/aim.js.map +1 -0
- package/dist/features/viewports/viewMarker.js +82 -0
- package/dist/features/viewports/viewMarker.js.map +1 -0
- package/dist/features/viewports/viewMarkerDrag.js +55 -0
- package/dist/features/viewports/viewMarkerDrag.js.map +1 -0
- package/dist/index.d.ts +500 -201
- package/dist/index.js +23 -12
- package/dist/interactions/actions/booleans/booleans.js +2 -1
- package/dist/interactions/actions/booleans/booleans.js.map +1 -1
- package/dist/interactions/actions/booleans/operands.js +6 -5
- package/dist/interactions/actions/booleans/operands.js.map +1 -1
- package/dist/interactions/actions/clipboard/action.js +27 -17
- package/dist/interactions/actions/clipboard/action.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorEditing.js +44 -23
- package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
- package/dist/interactions/actions/defaults/anchorJoin.js +12 -6
- package/dist/interactions/actions/defaults/anchorJoin.js.map +1 -1
- package/dist/interactions/actions/defaults/bendSegment.js +7 -4
- package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
- package/dist/interactions/actions/defaults/cancelGesture.js +11 -8
- package/dist/interactions/actions/defaults/cancelGesture.js.map +1 -1
- package/dist/interactions/actions/defaults/contextMenu.js +17 -8
- package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
- package/dist/interactions/actions/defaults/createPaintAction.js +1 -1
- package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
- package/dist/interactions/actions/defaults/delete.js +28 -14
- package/dist/interactions/actions/defaults/delete.js.map +1 -1
- package/dist/interactions/actions/defaults/duplicate.js +11 -8
- package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
- package/dist/interactions/actions/defaults/enterPathEdit.js +19 -9
- package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/escape.js +11 -8
- package/dist/interactions/actions/defaults/escape.js.map +1 -1
- package/dist/interactions/actions/defaults/exitPathEdit.js +7 -4
- package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
- package/dist/interactions/actions/defaults/group.js +19 -13
- package/dist/interactions/actions/defaults/group.js.map +1 -1
- package/dist/interactions/actions/defaults/ingest.js +10 -4
- package/dist/interactions/actions/defaults/ingest.js.map +1 -1
- package/dist/interactions/actions/defaults/insertPathAnchor.js +7 -4
- package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
- package/dist/interactions/actions/defaults/nudge.js +8 -2
- package/dist/interactions/actions/defaults/nudge.js.map +1 -1
- package/dist/interactions/actions/defaults/pathEditTools.js +11 -8
- package/dist/interactions/actions/defaults/pathEditTools.js.map +1 -1
- package/dist/interactions/actions/defaults/pinchZoom.js +7 -4
- package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
- package/dist/interactions/actions/defaults/reorder.js +24 -6
- package/dist/interactions/actions/defaults/reorder.js.map +1 -1
- package/dist/interactions/actions/defaults/selectAll.js +8 -5
- package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
- package/dist/interactions/actions/defaults/setFill.js +7 -3
- package/dist/interactions/actions/defaults/setFill.js.map +1 -1
- package/dist/interactions/actions/defaults/setFillOpacity.js +1 -1
- package/dist/interactions/actions/defaults/setStroke.js +12 -11
- package/dist/interactions/actions/defaults/setStroke.js.map +1 -1
- package/dist/interactions/actions/defaults/setStrokeOpacity.js +1 -1
- package/dist/interactions/actions/defaults/toolActivate.js +7 -1
- package/dist/interactions/actions/defaults/toolActivate.js.map +1 -1
- package/dist/interactions/actions/defaults/toolResetToDefault.js +7 -4
- package/dist/interactions/actions/defaults/toolResetToDefault.js.map +1 -1
- package/dist/interactions/actions/defaults/undoRedo.js +19 -13
- package/dist/interactions/actions/defaults/undoRedo.js.map +1 -1
- package/dist/interactions/actions/defaults/viewportWheelPan.js +16 -7
- package/dist/interactions/actions/defaults/viewportWheelPan.js.map +1 -1
- package/dist/interactions/actions/defaults/viewportZoom.js +24 -6
- package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
- package/dist/interactions/actions/edit-anchors/action.js +7 -4
- package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
- package/dist/interactions/actions/flip/action.js +8 -2
- package/dist/interactions/actions/flip/action.js.map +1 -1
- package/dist/interactions/actions/insert/repositionAction.js +2 -1
- package/dist/interactions/actions/insert/repositionAction.js.map +1 -1
- package/dist/interactions/actions/move/action.js +7 -4
- package/dist/interactions/actions/move/action.js.map +1 -1
- package/dist/interactions/actions/outlines/createOutlinesAction.js +8 -5
- package/dist/interactions/actions/outlines/createOutlinesAction.js.map +1 -1
- package/dist/interactions/actions/outlines/outlineStroke.js +5 -3
- package/dist/interactions/actions/outlines/outlineStroke.js.map +1 -1
- package/dist/interactions/actions/pathCommands/cutJoin.js +8 -5
- package/dist/interactions/actions/pathCommands/cutJoin.js.map +1 -1
- package/dist/interactions/actions/resize/cornerHandles.js +2 -1
- package/dist/interactions/actions/resize/cornerHandles.js.map +1 -1
- package/dist/interactions/actions/selectionSubtree.js +12 -7
- package/dist/interactions/actions/selectionSubtree.js.map +1 -1
- package/dist/interactions/actions/transform-again/action.js +8 -5
- package/dist/interactions/actions/transform-again/action.js.map +1 -1
- package/dist/json.d.ts +4 -0
- package/dist/json.js +3 -0
- package/dist/kitSettingsSchema.js +8 -1
- package/dist/kitSettingsSchema.js.map +1 -1
- package/dist/{math-CrJULaZI.d.ts → math-B56zxVs5.d.ts} +10 -1
- package/dist/math.d.ts +1 -1
- package/dist/math.js +2 -1
- package/dist/resize.d.ts +1 -1
- 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/eyedropper/useEyedropperTool.js +2 -1
- package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
- package/dist/tools/builtin/hand/dragPanContribution.js +4 -1
- package/dist/tools/builtin/hand/dragPanContribution.js.map +1 -1
- package/dist/tools/builtin/hand/useHandTool.js +7 -4
- package/dist/tools/builtin/hand/useHandTool.js.map +1 -1
- package/dist/tools/builtin/image/useImageTool.js +1 -0
- package/dist/tools/builtin/image/useImageTool.js.map +1 -1
- package/dist/tools/builtin/lasso/useLassoTool.js +1 -0
- package/dist/tools/builtin/lasso/useLassoTool.js.map +1 -1
- package/dist/tools/builtin/line/useLineTool.js +1 -0
- package/dist/tools/builtin/line/useLineTool.js.map +1 -1
- package/dist/tools/builtin/measure/useMeasureTool.js +12 -5
- package/dist/tools/builtin/measure/useMeasureTool.js.map +1 -1
- package/dist/tools/builtin/pen/usePenTool.js +14 -6
- package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
- package/dist/tools/builtin/pencil/usePencilTool.js +1 -0
- package/dist/tools/builtin/pencil/usePencilTool.js.map +1 -1
- package/dist/tools/builtin/polygon/usePolygonTool.js +13 -4
- package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
- package/dist/tools/builtin/rect/useRectTool.js +1 -0
- package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
- package/dist/tools/builtin/select/adapterPointQuery.js +3 -2
- package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
- package/dist/tools/builtin/select/pressDecision.js +32 -0
- package/dist/tools/builtin/select/pressDecision.js.map +1 -0
- package/dist/tools/builtin/select/selectionContributions.js +22 -13
- package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
- package/dist/tools/builtin/select/useSelectTool.js +13 -10
- package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
- package/dist/tools/builtin/slice/useSliceTool.js +22 -8
- package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
- package/dist/tools/builtin/star/useStarTool.js +13 -4
- package/dist/tools/builtin/star/useStarTool.js.map +1 -1
- package/dist/tools/builtin/sticky/stickyNode.js +4 -1
- package/dist/tools/builtin/sticky/stickyNode.js.map +1 -1
- package/dist/tools/builtin/sticky/useStickyTool.js +10 -5
- package/dist/tools/builtin/sticky/useStickyTool.js.map +1 -1
- package/dist/tools/builtin/text/useTextTool.js +3 -1
- package/dist/tools/builtin/text/useTextTool.js.map +1 -1
- package/dist/util/paint.js +1 -3
- package/dist/util/paint.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +28 -24
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derive.js","names":[],"sources":["../../../../src/core/scene/pathEffects/derive.ts"],"sourcesContent":["/**\n * A path effect as a scene node: a leaf whose `dependsOn` names its sources,\n * whose `data.pathEffect` says what to make of them, and whose path and pose\n * are both derived — the path to paint and pick, the pose as that path's\n * bounds, so selection chrome and marquee see where it is.\n *\n * Reading a source's outline and paint is its painter's business, which sits\n * above the scene, so the derivations are made from a reader:\n * `createPathEffectDerivations(read)`. The canvas layer makes the kit's, and\n * a scene that holds path effects registers them under {@link PATH_EFFECT}.\n */\nimport { boundsOfPath, type Path } from '@weasel-js/geom';\nimport { derivedPartsOf, pathFromParts, type DerivedPart } from '../derivedParts';\nimport { samePoseValue, snapshotPose } from '../poseSnapshot';\nimport type { DerivedDep, NodeId } from '../types';\nimport { effectParts, type PathEffect } from './effects';\nimport type { EffectSourceReader } from './source';\n\n/** The registry key both derivations go by, for `derivePath` and `derivePose`. */\nexport const PATH_EFFECT = 'kit:pathEffect';\n\n/** A node's path effect, or `null` when its data holds none. */\nexport function pathEffectOf(node: { data?: unknown } | undefined): PathEffect | null {\n const effect = (node?.data as { pathEffect?: PathEffect } | null | undefined)?.pathEffect;\n return effect && typeof effect === 'object' && typeof effect.kind === 'string' ? effect : null;\n}\n\n/** A path-effect node's two derivations. */\nexport interface PathEffectDerivations {\n /** What it paints and picks by. */\n derivePath(node: { data?: unknown }, deps: readonly (DerivedDep<unknown> | undefined)[]): Path | null;\n /** The bounds of what it paints, or `null` — its authored pose — while it\n * paints nothing. */\n derivePose(node: { data?: unknown }, deps: readonly (DerivedDep<unknown> | undefined)[]): { x: number; y: number; width: number; height: number } | null;\n}\n\ninterface Derived {\n data: unknown;\n nodes: unknown[];\n depData: unknown[];\n poses: unknown[];\n paths: (Path | null)[];\n path: Path | null;\n}\n\n/** The derivations of a path effect whose sources `read` reads. */\nexport function createPathEffectDerivations(read: EffectSourceReader): PathEffectDerivations {\n /** One result per effect node, shared by its two derivations: the pose is\n * the path's bounds, and working the effect out twice a frame would double\n * its cost. Checked against everything it read, by value for poses — a\n * drag's override mutates its pose in place. */\n const results = new WeakMap<object, Derived>();\n const derive = (node: { data?: unknown }, deps: readonly (DerivedDep<unknown> | undefined)[]): Derived => {\n const held = results.get(node);\n if (held !== undefined && current(held, node, deps)) return held;\n const effect = pathEffectOf(node);\n const parts = effect === null\n ? null\n : effectParts(effect, deps.map((d) => (d === undefined ? null : read(d))));\n const result: Derived = {\n data: node.data,\n nodes: deps.map((d) => d?.node),\n depData: deps.map((d) => d?.node.data),\n poses: deps.map((d) => (d === undefined ? undefined : snapshotPose(d.pose))),\n paths: deps.map((d) => d?.path ?? null),\n path: parts === null ? null : pathFromParts(parts),\n };\n results.set(node, result);\n return result;\n };\n return {\n derivePath: (node, deps) => derive(node, deps).path,\n derivePose: (node, deps) => {\n const path = derive(node, deps).path;\n if (path === null) return null;\n const { x, y, width, height } = boundsOfPath(path);\n return { x, y, width, height };\n },\n };\n}\n\nfunction current(held: Derived, node: { data?: unknown }, deps: readonly (DerivedDep<unknown> | undefined)[]): boolean {\n if (held.data !== node.data || held.nodes.length !== deps.length) return false;\n for (let i = 0; i < deps.length; i++) {\n const d = deps[i];\n if (held.nodes[i] !== d?.node) return false;\n if (d === undefined) continue;\n if (held.depData[i] !== d.node.data || !samePoseValue(held.poses[i], d.pose)) return false;\n if (held.paths[i] !== d.path) return false;\n }\n return true;\n}\n\n/** The parts a path-effect node paints, as its sources stand, or `null` when\n * it has no derivation or nothing to paint. */\nexport function pathEffectParts(\n node: { data?: unknown; derivePath?: unknown },\n deps: readonly (DerivedDep<unknown> | undefined)[],\n): readonly DerivedPart[] | null {\n if (typeof node.derivePath !== 'function') return null;\n return derivedPartsOf((node.derivePath as PathEffectDerivations['derivePath'])(node, deps)) ?? null;\n}\n\n/** What to hand `Scene.add` (or an insert op) for a path-effect node over\n * `sources`, in the order its kind reads them, deriving through\n * `derivations` — the ones the scene registers under {@link PATH_EFFECT}. */\nexport function pathEffectNode<TLayer extends string>(\n effect: PathEffect,\n sources: readonly NodeId[],\n layer: TLayer,\n derivations: PathEffectDerivations,\n): {\n kind: 'leaf';\n layer: TLayer;\n pose: { x: number; y: number; width: number; height: number };\n data: { pathEffect: PathEffect };\n dependsOn: readonly NodeId[];\n derivePath: PathEffectDerivations['derivePath'];\n derivePose: PathEffectDerivations['derivePose'];\n} {\n return {\n kind: 'leaf',\n layer,\n pose: { x: 0, y: 0, width: 0, height: 0 },\n data: { pathEffect: effect },\n dependsOn: [...sources],\n derivePath: derivations.derivePath,\n derivePose: derivations.derivePose,\n };\n}\n\n/** The path-effect derivations `registry` holds under {@link PATH_EFFECT},\n * or `null` when it holds none. */\nexport function registeredPathEffects(registry: {\n derivePath?: Readonly<Record<string, unknown>>;\n derivePose?: Readonly<Record<string, unknown>>;\n}): PathEffectDerivations | null {\n const derivePath = registry.derivePath?.[PATH_EFFECT];\n const derivePose = registry.derivePose?.[PATH_EFFECT];\n return typeof derivePath === 'function' && typeof derivePose === 'function'\n ? { derivePath, derivePose } as PathEffectDerivations\n : null;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"derive.js","names":[],"sources":["../../../../src/core/scene/pathEffects/derive.ts"],"sourcesContent":["/**\n * A path effect as a scene node: a leaf whose `dependsOn` names its sources,\n * whose `data.pathEffect` says what to make of them, and whose path and pose\n * are both derived — the path to paint and pick, the pose as that path's\n * bounds, so selection chrome and marquee see where it is.\n *\n * Reading a source's outline and paint is its painter's business, which sits\n * above the scene, so the derivations are made from a reader:\n * `createPathEffectDerivations(read)`. The canvas layer makes the kit's, and\n * a scene that holds path effects registers them under {@link PATH_EFFECT}.\n */\nimport { boundsOfPath, type Path } from '@weasel-js/geom';\nimport { derivedPartsOf, pathFromParts, type DerivedPart } from '../derivedParts';\nimport { fontGeneration, noteFontRead, withFontReads } from '../fontReads';\nimport { samePoseValue, snapshotPose } from '../poseSnapshot';\nimport type { DerivedDep, NodeId } from '../types';\nimport { effectParts, type PathEffect } from './effects';\nimport type { EffectSourceReader } from './source';\n\n/** The registry key both derivations go by, for `derivePath` and `derivePose`. */\nexport const PATH_EFFECT = 'kit:pathEffect';\n\n/** A node's path effect, or `null` when its data holds none. */\nexport function pathEffectOf(node: { data?: unknown } | undefined): PathEffect | null {\n const effect = (node?.data as { pathEffect?: PathEffect } | null | undefined)?.pathEffect;\n return effect && typeof effect === 'object' && typeof effect.kind === 'string' ? effect : null;\n}\n\n/** A path-effect node's two derivations. */\nexport interface PathEffectDerivations {\n /** What it paints and picks by. */\n derivePath(node: { data?: unknown }, deps: readonly (DerivedDep<unknown> | undefined)[]): Path | null;\n /** The bounds of what it paints, or `null` — its authored pose — while it\n * paints nothing. */\n derivePose(node: { data?: unknown }, deps: readonly (DerivedDep<unknown> | undefined)[]): { x: number; y: number; width: number; height: number } | null;\n}\n\ninterface Derived {\n data: unknown;\n nodes: unknown[];\n depData: unknown[];\n poses: unknown[];\n paths: (Path | null)[];\n path: Path | null;\n /** The face generation it was worked out under, or `null` when it read no\n * glyph outlines. */\n faces: number | null;\n}\n\n/** The derivations of a path effect whose sources `read` reads. */\nexport function createPathEffectDerivations(read: EffectSourceReader): PathEffectDerivations {\n /** One result per effect node, shared by its two derivations: the pose is\n * the path's bounds, and working the effect out twice a frame would double\n * its cost. Checked against everything it read, by value for poses — a\n * drag's override mutates its pose in place. */\n const results = new WeakMap<object, Derived>();\n const derive = (node: { data?: unknown }, deps: readonly (DerivedDep<unknown> | undefined)[]): Derived => {\n const held = results.get(node);\n if (held !== undefined && current(held, node, deps)) return held;\n const effect = pathEffectOf(node);\n const generation = fontGeneration();\n const { value: parts, fonts } = withFontReads(() => (effect === null\n ? null\n : effectParts(effect, deps.map((d) => (d === undefined ? null : read(d))))));\n const result: Derived = {\n data: node.data,\n nodes: deps.map((d) => d?.node),\n depData: deps.map((d) => d?.node.data),\n poses: deps.map((d) => (d === undefined ? undefined : snapshotPose(d.pose))),\n paths: deps.map((d) => d?.path ?? null),\n path: parts === null ? null : pathFromParts(parts),\n faces: fonts ? generation : null,\n };\n results.set(node, result);\n return result;\n };\n return {\n derivePath: (node, deps) => derive(node, deps).path,\n derivePose: (node, deps) => {\n const path = derive(node, deps).path;\n if (path === null) return null;\n const { x, y, width, height } = boundsOfPath(path);\n return { x, y, width, height };\n },\n };\n}\n\nfunction current(held: Derived, node: { data?: unknown }, deps: readonly (DerivedDep<unknown> | undefined)[]): boolean {\n if (held.data !== node.data || held.nodes.length !== deps.length) return false;\n if (held.faces !== null && held.faces !== fontGeneration()) return false;\n for (let i = 0; i < deps.length; i++) {\n const d = deps[i];\n if (held.nodes[i] !== d?.node) return false;\n if (d === undefined) continue;\n if (held.depData[i] !== d.node.data || !samePoseValue(held.poses[i], d.pose)) return false;\n if (held.paths[i] !== d.path) return false;\n }\n // A hit is still a read, for the scene memo enclosing this one.\n if (held.faces !== null) noteFontRead();\n return true;\n}\n\n/** The parts a path-effect node paints, as its sources stand, or `null` when\n * it has no derivation or nothing to paint. */\nexport function pathEffectParts(\n node: { data?: unknown; derivePath?: unknown },\n deps: readonly (DerivedDep<unknown> | undefined)[],\n): readonly DerivedPart[] | null {\n if (typeof node.derivePath !== 'function') return null;\n return derivedPartsOf((node.derivePath as PathEffectDerivations['derivePath'])(node, deps)) ?? null;\n}\n\n/** What to hand `Scene.add` (or an insert op) for a path-effect node over\n * `sources`, in the order its kind reads them, deriving through\n * `derivations` — the ones the scene registers under {@link PATH_EFFECT}. */\nexport function pathEffectNode<TLayer extends string>(\n effect: PathEffect,\n sources: readonly NodeId[],\n layer: TLayer,\n derivations: PathEffectDerivations,\n): {\n kind: 'leaf';\n layer: TLayer;\n pose: { x: number; y: number; width: number; height: number };\n data: { pathEffect: PathEffect };\n dependsOn: readonly NodeId[];\n derivePath: PathEffectDerivations['derivePath'];\n derivePose: PathEffectDerivations['derivePose'];\n} {\n return {\n kind: 'leaf',\n layer,\n pose: { x: 0, y: 0, width: 0, height: 0 },\n data: { pathEffect: effect },\n dependsOn: [...sources],\n derivePath: derivations.derivePath,\n derivePose: derivations.derivePose,\n };\n}\n\n/** The path-effect derivations `registry` holds under {@link PATH_EFFECT},\n * or `null` when it holds none. */\nexport function registeredPathEffects(registry: {\n derivePath?: Readonly<Record<string, unknown>>;\n derivePose?: Readonly<Record<string, unknown>>;\n}): PathEffectDerivations | null {\n const derivePath = registry.derivePath?.[PATH_EFFECT];\n const derivePose = registry.derivePose?.[PATH_EFFECT];\n return typeof derivePath === 'function' && typeof derivePose === 'function'\n ? { derivePath, derivePose } as PathEffectDerivations\n : null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoBA,IAAa,cAAc;;AAG3B,SAAgB,aAAa,MAAyD;CACpF,MAAM,UAAU,MAAM,KAAA,EAAyD;CAC/E,OAAO,UAAU,OAAO,WAAW,YAAY,OAAO,OAAO,SAAS,WAAW,SAAS;AAC5F;;AAwBA,SAAgB,4BAA4B,MAAiD;;;;;CAK3F,MAAM,0BAAU,IAAI,QAAyB;CAC7C,MAAM,UAAU,MAA0B,SAAgE;EACxG,MAAM,OAAO,QAAQ,IAAI,IAAI;EAC7B,IAAI,SAAS,UAAa,QAAQ,MAAM,MAAM,IAAI,GAAG,OAAO;EAC5D,MAAM,SAAS,aAAa,IAAI;EAChC,MAAM,aAAa,eAAe;EAClC,MAAM,EAAE,OAAO,OAAO,UAAU,oBAAqB,WAAW,OAC5D,OACA,YAAY,QAAQ,KAAK,KAAK,MAAO,MAAM,SAAY,OAAO,KAAK,CAAC,CAAE,CAAC,CAAE;EAC7E,MAAM,SAAkB;GACtB,MAAM,KAAK;GACX,OAAO,KAAK,KAAK,MAAM,GAAG,IAAI;GAC9B,SAAS,KAAK,KAAK,MAAM,GAAG,KAAK,IAAI;GACrC,OAAO,KAAK,KAAK,MAAO,MAAM,SAAY,SAAY,aAAa,EAAE,IAAI,CAAE;GAC3E,OAAO,KAAK,KAAK,MAAM,GAAG,QAAQ,IAAI;GACtC,MAAM,UAAU,OAAO,OAAO,cAAc,KAAK;GACjD,OAAO,QAAQ,aAAa;EAC9B;EACA,QAAQ,IAAI,MAAM,MAAM;EACxB,OAAO;CACT;CACA,OAAO;EACL,aAAa,MAAM,SAAS,OAAO,MAAM,IAAI,CAAC,CAAC;EAC/C,aAAa,MAAM,SAAS;GAC1B,MAAM,OAAO,OAAO,MAAM,IAAI,CAAC,CAAC;GAChC,IAAI,SAAS,MAAM,OAAO;GAC1B,MAAM,EAAE,GAAG,GAAG,OAAO,WAAW,aAAa,IAAI;GACjD,OAAO;IAAE;IAAG;IAAG;IAAO;GAAO;EAC/B;CACF;AACF;AAEA,SAAS,QAAQ,MAAe,MAA0B,MAA6D;CACrH,IAAI,KAAK,SAAS,KAAK,QAAQ,KAAK,MAAM,WAAW,KAAK,QAAQ,OAAO;CACzE,IAAI,KAAK,UAAU,QAAQ,KAAK,UAAU,eAAe,GAAG,OAAO;CACnE,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,IAAI,KAAK;EACf,IAAI,KAAK,MAAM,OAAO,GAAG,MAAM,OAAO;EACtC,IAAI,MAAM,QAAW;EACrB,IAAI,KAAK,QAAQ,OAAO,EAAE,KAAK,QAAQ,CAAC,cAAc,KAAK,MAAM,IAAI,EAAE,IAAI,GAAG,OAAO;EACrF,IAAI,KAAK,MAAM,OAAO,EAAE,MAAM,OAAO;CACvC;CAEA,IAAI,KAAK,UAAU,MAAM,aAAa;CACtC,OAAO;AACT;;;AAIA,SAAgB,gBACd,MACA,MAC+B;CAC/B,IAAI,OAAO,KAAK,eAAe,YAAY,OAAO;CAClD,OAAO,eAAgB,KAAK,WAAmD,MAAM,IAAI,CAAC,KAAK;AACjG;;;;AAKA,SAAgB,eACd,QACA,SACA,OACA,aASA;CACA,OAAO;EACL,MAAM;EACN;EACA,MAAM;GAAE,GAAG;GAAG,GAAG;GAAG,OAAO;GAAG,QAAQ;EAAE;EACxC,MAAM,EAAE,YAAY,OAAO;EAC3B,WAAW,CAAC,GAAG,OAAO;EACtB,YAAY,YAAY;EACxB,YAAY,YAAY;CAC1B;AACF;;;AAIA,SAAgB,sBAAsB,UAGL;CAC/B,MAAM,aAAa,SAAS,aAAa;CACzC,MAAM,aAAa,SAAS,aAAa;CACzC,OAAO,OAAO,eAAe,cAAc,OAAO,eAAe,aAC7D;EAAE;EAAY;CAAW,IACzB;AACN"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { blendParts } from "./blend.js";
|
|
2
|
+
import { textAlongParts, wholeItem } from "./textAlong.js";
|
|
2
3
|
import { bendAlongPath, repeatAlongPath } from "@weasel-js/geom";
|
|
3
4
|
|
|
4
5
|
//#region src/core/scene/pathEffects/effects.ts
|
|
5
6
|
/**
|
|
6
7
|
* The path effects, each as the parts it paints from its sources: a blend's
|
|
7
8
|
* steps between two shapes, a shape bent along a spine, copies of a shape
|
|
8
|
-
* along a path.
|
|
9
|
+
* along a path, text laid along a path.
|
|
9
10
|
*/
|
|
10
11
|
/** The parts `effect` paints from `sources`, in its sources' order, or
|
|
11
12
|
* `null` when a source it needs is missing — a blend's spine among them,
|
|
@@ -32,6 +33,7 @@ function effectParts(effect, sources) {
|
|
|
32
33
|
fill: a.fill,
|
|
33
34
|
stroke: a.stroke
|
|
34
35
|
}));
|
|
36
|
+
case "textAlong": return textAlongParts(effect, a.items?.() ?? [wholeItem(a)], b.world);
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"effects.js","names":[],"sources":["../../../../src/core/scene/pathEffects/effects.ts"],"sourcesContent":["/**\n * The path effects, each as the parts it paints from its sources: a blend's\n * steps between two shapes, a shape bent along a spine, copies of a shape\n * along a path.\n */\nimport {\n bendAlongPath, repeatAlongPath, type BendAlongPathOptions, type RepeatAlongPathOptions,\n} from '@weasel-js/geom';\nimport type { DerivedPart } from '../derivedParts';\nimport { blendParts, type BlendEffect } from './blend';\nimport type { EffectSource } from './source';\n\nexport type { BlendEffect };\n\n/** A shape bent along a spine. Its sources are `[shape, spine]`. */\nexport interface BendAlongEffect extends BendAlongPathOptions {\n kind: 'bendAlong';\n}\n\n/** Copies of a shape along a path. Its sources are `[shape, path]`. */\nexport interface RepeatAlongEffect extends Omit<RepeatAlongPathOptions, 'flattenTolerance'
|
|
1
|
+
{"version":3,"file":"effects.js","names":[],"sources":["../../../../src/core/scene/pathEffects/effects.ts"],"sourcesContent":["/**\n * The path effects, each as the parts it paints from its sources: a blend's\n * steps between two shapes, a shape bent along a spine, copies of a shape\n * along a path, text laid along a path.\n */\nimport {\n bendAlongPath, repeatAlongPath, type BendAlongPathOptions, type RepeatAlongPathOptions,\n} from '@weasel-js/geom';\nimport type { DerivedPart } from '../derivedParts';\nimport { blendParts, type BlendEffect } from './blend';\nimport type { EffectSource, PathEffectOptions } from './source';\nimport { textAlongParts, wholeItem, type TextAlongEffect } from './textAlong';\n\nexport type { BlendEffect, PathEffectOptions, TextAlongEffect };\n\n/** A shape bent along a spine. Its sources are `[shape, spine]`. */\nexport interface BendAlongEffect extends BendAlongPathOptions, PathEffectOptions {\n kind: 'bendAlong';\n}\n\n/** Copies of a shape along a path. Its sources are `[shape, path]`. */\nexport interface RepeatAlongEffect extends Omit<RepeatAlongPathOptions, 'flattenTolerance'>, PathEffectOptions {\n kind: 'repeatAlong';\n}\n\n/** A path effect, as a node's `data.pathEffect` holds it. */\nexport type PathEffect = BlendEffect | BendAlongEffect | RepeatAlongEffect | TextAlongEffect;\n\n/** The parts `effect` paints from `sources`, in its sources' order, or\n * `null` when a source it needs is missing — a blend's spine among them,\n * where it names one. */\nexport function effectParts(effect: PathEffect, sources: readonly (EffectSource | null)[]): DerivedPart[] | null {\n const [a, b] = sources;\n if (!a || !b) return null;\n switch (effect.kind) {\n case 'blend': {\n if (sources.length < 3) return blendParts(effect, a, b);\n const spine = sources[2];\n return spine ? blendParts(effect, a, b, spine.world) : null;\n }\n case 'bendAlong': {\n const path = bendAlongPath(a.world, b.world, effect);\n return path === null ? null : [{ path, fill: a.fill, stroke: a.stroke }];\n }\n case 'repeatAlong':\n return repeatAlongPath(a.world, b.world, effect).map((path) => ({ path, fill: a.fill, stroke: a.stroke }));\n case 'textAlong':\n return textAlongParts(effect, a.items?.() ?? [wholeItem(a)], b.world);\n }\n}\n"],"mappings":";;;;;;;;;;;;;AA+BA,SAAgB,YAAY,QAAoB,SAAiE;CAC/G,MAAM,CAAC,GAAG,KAAK;CACf,IAAI,CAAC,KAAK,CAAC,GAAG,OAAO;CACrB,QAAQ,OAAO,MAAf;EACE,KAAK,SAAS;GACZ,IAAI,QAAQ,SAAS,GAAG,OAAO,WAAW,QAAQ,GAAG,CAAC;GACtD,MAAM,QAAQ,QAAQ;GACtB,OAAO,QAAQ,WAAW,QAAQ,GAAG,GAAG,MAAM,KAAK,IAAI;EACzD;EACA,KAAK,aAAa;GAChB,MAAM,OAAO,cAAc,EAAE,OAAO,EAAE,OAAO,MAAM;GACnD,OAAO,SAAS,OAAO,OAAO,CAAC;IAAE;IAAM,MAAM,EAAE;IAAM,QAAQ,EAAE;GAAO,CAAC;EACzE;EACA,KAAK,eACH,OAAO,gBAAgB,EAAE,OAAO,EAAE,OAAO,MAAM,CAAC,CAAC,KAAK,UAAU;GAAE;GAAM,MAAM,EAAE;GAAM,QAAQ,EAAE;EAAO,EAAE;EAC3G,KAAK,aACH,OAAO,eAAe,QAAQ,EAAE,QAAQ,KAAK,CAAC,UAAU,CAAC,CAAC,GAAG,EAAE,KAAK;CACxE;AACF"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { boundsOfPath, multiply, pathShape, placeAlongRun, rotate, runLength, trackRun, transformPath, translate } from "@weasel-js/geom";
|
|
2
|
+
|
|
3
|
+
//#region src/core/scene/pathEffects/textAlong.ts
|
|
4
|
+
/**
|
|
5
|
+
* Text along a path: a source's items — a text node's grapheme clusters, or a
|
|
6
|
+
* whole shape as one item — laid end to end along a path, each turned to it.
|
|
7
|
+
*/
|
|
8
|
+
var LEAD = {
|
|
9
|
+
start: 0,
|
|
10
|
+
center: .5,
|
|
11
|
+
end: 1
|
|
12
|
+
};
|
|
13
|
+
/** The parts `items` paint laid along `path` as `effect` says; `[]` when there is nothing to lay or nowhere to lay it. */
|
|
14
|
+
function textAlongParts(effect, items, path) {
|
|
15
|
+
const shape = pathShape(path);
|
|
16
|
+
if (shape === null || !(shape.length > 0) || items.length === 0) return [];
|
|
17
|
+
const L = shape.length;
|
|
18
|
+
const closed = shape.closed;
|
|
19
|
+
const lengths = items.map((it) => it.length);
|
|
20
|
+
const k = LEAD[effect.align ?? "start"];
|
|
21
|
+
const at = clamp01(effect.start ?? 0) * L;
|
|
22
|
+
const gap = effect.overflow === "fit" ? fitGap(lengths, roomAt(at, L, k, closed)) : 0;
|
|
23
|
+
const total = runLength(lengths, gap);
|
|
24
|
+
const c = at + (.5 - k) * total;
|
|
25
|
+
const about = closed ? (c / L % 1 + 1) % 1 : clamp01(c / L);
|
|
26
|
+
const run = trackRun(shape, 0, 1, {
|
|
27
|
+
side: effect.side ?? "auto",
|
|
28
|
+
about
|
|
29
|
+
});
|
|
30
|
+
const places = placeAlongRun(run, lengths, {
|
|
31
|
+
gap,
|
|
32
|
+
align: "center",
|
|
33
|
+
at: run.reversed ? L - c : c,
|
|
34
|
+
shift: finiteOr(effect.shift, 0),
|
|
35
|
+
wrap: closed
|
|
36
|
+
});
|
|
37
|
+
const center = effect.anchor === "center";
|
|
38
|
+
const out = [];
|
|
39
|
+
const firstStart = places[0].center - lengths[0] / 2;
|
|
40
|
+
places.forEach((place, i) => {
|
|
41
|
+
if (dropped(place, closed ? firstStart : null, L)) return;
|
|
42
|
+
const item = items[i];
|
|
43
|
+
const m = itemFrame(place, item.length, center ? item.middle : 0);
|
|
44
|
+
for (const part of item.parts) out.push({
|
|
45
|
+
...part,
|
|
46
|
+
path: transformPath(part.path, m)
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
/** `source`'s whole outline as one item: its bounds' left at x = 0 and their vertical middle on the anchor line. */
|
|
52
|
+
function wholeItem(source) {
|
|
53
|
+
const b = boundsOfPath(source.world);
|
|
54
|
+
return {
|
|
55
|
+
length: b.width,
|
|
56
|
+
middle: 0,
|
|
57
|
+
parts: [{
|
|
58
|
+
path: transformPath(source.world, translate(-b.x, -(b.y + b.height / 2))),
|
|
59
|
+
fill: source.fill,
|
|
60
|
+
stroke: source.stroke
|
|
61
|
+
}]
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** SVG `textPath`'s rule: an item is not drawn once its middle is past an open path's ends, or on a closed path a whole
|
|
65
|
+
* lap or more on from the first item's start. Judged within a hair of rounding. */
|
|
66
|
+
function dropped(place, firstStart, L) {
|
|
67
|
+
const hair = 1e-9 * Math.max(1, L);
|
|
68
|
+
if (firstStart !== null) return place.center - firstStart >= L - hair;
|
|
69
|
+
return place.center < -hair || place.center > L + hair;
|
|
70
|
+
}
|
|
71
|
+
/** How much path the text has before it runs off, from `at`, with `k` of it laid back behind `at`. */
|
|
72
|
+
function roomAt(at, L, k, closed) {
|
|
73
|
+
if (closed) return L;
|
|
74
|
+
return k === 0 ? L - at : k === 1 ? at : 2 * Math.min(at, L - at);
|
|
75
|
+
}
|
|
76
|
+
/** The gap that makes `lengths` fill `room`, or 0 when they fit already. Never so negative that an item's step back
|
|
77
|
+
* from the one before it (its length plus the gap) goes below zero, so items keep their order in too short a room. */
|
|
78
|
+
function fitGap(lengths, room) {
|
|
79
|
+
const total = runLength(lengths, 0);
|
|
80
|
+
if (!(total > room) || lengths.length < 2) return 0;
|
|
81
|
+
let floor = -Infinity;
|
|
82
|
+
for (let i = 1; i < lengths.length; i++) floor = Math.max(floor, -(lengths[i - 1] + lengths[i]) / 2);
|
|
83
|
+
return Math.max((room - total) / (lengths.length - 1), floor);
|
|
84
|
+
}
|
|
85
|
+
/** Item frame to world: its middle on `place`, turned to it, its anchor line `lift` above y = 0 brought onto the path. */
|
|
86
|
+
function itemFrame(place, length, lift) {
|
|
87
|
+
const turn = multiply(rotate(place.angle * Math.PI / 180), translate(-length / 2, lift));
|
|
88
|
+
return multiply(translate(place.point.x, place.point.y), turn);
|
|
89
|
+
}
|
|
90
|
+
var clamp01 = (v) => v > 1 ? 1 : v > 0 ? v : 0;
|
|
91
|
+
var finiteOr = (v, d) => v !== void 0 && Number.isFinite(v) ? v : d;
|
|
92
|
+
|
|
93
|
+
//#endregion
|
|
94
|
+
export { textAlongParts, wholeItem };
|
|
95
|
+
//# sourceMappingURL=textAlong.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textAlong.js","names":[],"sources":["../../../../src/core/scene/pathEffects/textAlong.ts"],"sourcesContent":["/**\n * Text along a path: a source's items — a text node's grapheme clusters, or a\n * whole shape as one item — laid end to end along a path, each turned to it.\n */\nimport {\n boundsOfPath, multiply, pathShape, placeAlongRun, rotate, runLength, trackRun, transformPath, translate,\n type Affine, type Path, type RunAlign, type RunPlace, type TrackSide,\n} from '@weasel-js/geom';\nimport type { DerivedPart } from '../derivedParts';\nimport type { EffectItem, EffectSource, PathEffectOptions } from './source';\n\n/**\n * Text (or any shape) laid along a path, item by item. Its sources are `[text, path]`. Only the text's first laid line\n * follows the path; a color glyph keeps its advance and paints nothing; underlines and other decorations are not drawn.\n * On an open path an item whose middle falls past either end is not drawn; on a closed one the text wraps past the\n * seam, and an item a whole lap on from the first item's start is not drawn.\n */\nexport interface TextAlongEffect extends PathEffectOptions {\n kind: 'textAlong';\n /** Which part of the text sits at `start`. Default `'start'`. */\n align?: RunAlign;\n /** Where the text sits, as a fraction 0..1 of the path's length from its first point. Default 0. */\n start?: number;\n /** How far the anchor line sits off the path, toward the glyphs' up, in world units. Default 0. */\n shift?: number;\n /** What sits on the path: the glyphs' baseline, or (`'center'`) their x-height middle. Default `'baseline'`. */\n anchor?: 'baseline' | 'center';\n /** Text longer than its room is cut at the path's end (`'clip'`), or has the gaps between items squeezed until it\n * fits (`'fit'`). Default `'clip'`. */\n overflow?: 'clip' | 'fit';\n /** Which side of the path the glyphs stand on; see `TrackSide`. Default `'auto'`, whichever reads upright. Turned over,\n * the text keeps the stretch of path it had, and on an open path drops whichever letters fall off it. */\n side?: TrackSide;\n}\n\nconst LEAD: Record<RunAlign, number> = { start: 0, center: 0.5, end: 1 };\n\n/** The parts `items` paint laid along `path` as `effect` says; `[]` when there is nothing to lay or nowhere to lay it. */\nexport function textAlongParts(effect: TextAlongEffect, items: readonly EffectItem[], path: Path): DerivedPart[] {\n const shape = pathShape(path);\n if (shape === null || !(shape.length > 0) || items.length === 0) return [];\n const L = shape.length;\n const closed = shape.closed;\n const lengths = items.map((it) => it.length);\n const k = LEAD[effect.align ?? 'start'];\n const at = clamp01(effect.start ?? 0) * L;\n const gap = effect.overflow === 'fit' ? fitGap(lengths, roomAt(at, L, k, closed)) : 0;\n const total = runLength(lengths, gap);\n const c = at + (0.5 - k) * total;\n const about = closed ? ((c / L) % 1 + 1) % 1 : clamp01(c / L);\n const run = trackRun(shape, 0, 1, { side: effect.side ?? 'auto', about });\n const places = placeAlongRun(run, lengths, {\n gap, align: 'center', at: run.reversed ? L - c : c, shift: finiteOr(effect.shift, 0), wrap: closed,\n });\n const center = effect.anchor === 'center';\n const out: DerivedPart[] = [];\n const firstStart = places[0]!.center - lengths[0]! / 2;\n places.forEach((place, i) => {\n // By center (SVG textPath's rule), not by RunPlace.clipped, which judges by extent.\n if (dropped(place, closed ? firstStart : null, L)) return;\n const item = items[i]!;\n const m = itemFrame(place, item.length, center ? item.middle : 0);\n for (const part of item.parts) out.push({ ...part, path: transformPath(part.path, m) });\n });\n return out;\n}\n\n/** `source`'s whole outline as one item: its bounds' left at x = 0 and their vertical middle on the anchor line. */\nexport function wholeItem(source: EffectSource): EffectItem {\n const b = boundsOfPath(source.world);\n return {\n length: b.width,\n middle: 0,\n parts: [{\n path: transformPath(source.world, translate(-b.x, -(b.y + b.height / 2))),\n fill: source.fill,\n stroke: source.stroke,\n }],\n };\n}\n\n/** SVG `textPath`'s rule: an item is not drawn once its middle is past an open path's ends, or on a closed path a whole\n * lap or more on from the first item's start. Judged within a hair of rounding. */\nfunction dropped(place: RunPlace, firstStart: number | null, L: number): boolean {\n const hair = 1e-9 * Math.max(1, L);\n if (firstStart !== null) return place.center - firstStart >= L - hair;\n return place.center < -hair || place.center > L + hair;\n}\n\n/** How much path the text has before it runs off, from `at`, with `k` of it laid back behind `at`. */\nfunction roomAt(at: number, L: number, k: number, closed: boolean): number {\n if (closed) return L;\n return k === 0 ? L - at : k === 1 ? at : 2 * Math.min(at, L - at);\n}\n\n/** The gap that makes `lengths` fill `room`, or 0 when they fit already. Never so negative that an item's step back\n * from the one before it (its length plus the gap) goes below zero, so items keep their order in too short a room. */\nfunction fitGap(lengths: readonly number[], room: number): number {\n const total = runLength(lengths, 0);\n if (!(total > room) || lengths.length < 2) return 0;\n let floor = -Infinity;\n for (let i = 1; i < lengths.length; i++) floor = Math.max(floor, -(lengths[i - 1]! + lengths[i]!) / 2);\n return Math.max((room - total) / (lengths.length - 1), floor);\n}\n\n/** Item frame to world: its middle on `place`, turned to it, its anchor line `lift` above y = 0 brought onto the path. */\nfunction itemFrame(place: RunPlace, length: number, lift: number): Affine {\n const turn = multiply(rotate((place.angle * Math.PI) / 180), translate(-length / 2, lift));\n return multiply(translate(place.point.x, place.point.y), turn);\n}\n\nconst clamp01 = (v: number): number => (v > 1 ? 1 : v > 0 ? v : 0);\nconst finiteOr = (v: number | undefined, d: number): number => (v !== undefined && Number.isFinite(v) ? v : d);\n"],"mappings":";;;;;;;AAmCA,IAAM,OAAiC;CAAE,OAAO;CAAG,QAAQ;CAAK,KAAK;AAAE;;AAGvE,SAAgB,eAAe,QAAyB,OAA8B,MAA2B;CAC/G,MAAM,QAAQ,UAAU,IAAI;CAC5B,IAAI,UAAU,QAAQ,EAAE,MAAM,SAAS,MAAM,MAAM,WAAW,GAAG,OAAO,CAAC;CACzE,MAAM,IAAI,MAAM;CAChB,MAAM,SAAS,MAAM;CACrB,MAAM,UAAU,MAAM,KAAK,OAAO,GAAG,MAAM;CAC3C,MAAM,IAAI,KAAK,OAAO,SAAS;CAC/B,MAAM,KAAK,QAAQ,OAAO,SAAS,CAAC,IAAI;CACxC,MAAM,MAAM,OAAO,aAAa,QAAQ,OAAO,SAAS,OAAO,IAAI,GAAG,GAAG,MAAM,CAAC,IAAI;CACpF,MAAM,QAAQ,UAAU,SAAS,GAAG;CACpC,MAAM,IAAI,MAAM,KAAM,KAAK;CAC3B,MAAM,QAAQ,UAAW,IAAI,IAAK,IAAI,KAAK,IAAI,QAAQ,IAAI,CAAC;CAC5D,MAAM,MAAM,SAAS,OAAO,GAAG,GAAG;EAAE,MAAM,OAAO,QAAQ;EAAQ;CAAM,CAAC;CACxE,MAAM,SAAS,cAAc,KAAK,SAAS;EACzC;EAAK,OAAO;EAAU,IAAI,IAAI,WAAW,IAAI,IAAI;EAAG,OAAO,SAAS,OAAO,OAAO,CAAC;EAAG,MAAM;CAC9F,CAAC;CACD,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,MAAqB,CAAC;CAC5B,MAAM,aAAa,OAAO,EAAE,CAAE,SAAS,QAAQ,KAAM;CACrD,OAAO,SAAS,OAAO,MAAM;EAE3B,IAAI,QAAQ,OAAO,SAAS,aAAa,MAAM,CAAC,GAAG;EACnD,MAAM,OAAO,MAAM;EACnB,MAAM,IAAI,UAAU,OAAO,KAAK,QAAQ,SAAS,KAAK,SAAS,CAAC;EAChE,KAAK,MAAM,QAAQ,KAAK,OAAO,IAAI,KAAK;GAAE,GAAG;GAAM,MAAM,cAAc,KAAK,MAAM,CAAC;EAAE,CAAC;CACxF,CAAC;CACD,OAAO;AACT;;AAGA,SAAgB,UAAU,QAAkC;CAC1D,MAAM,IAAI,aAAa,OAAO,KAAK;CACnC,OAAO;EACL,QAAQ,EAAE;EACV,QAAQ;EACR,OAAO,CAAC;GACN,MAAM,cAAc,OAAO,OAAO,UAAU,CAAC,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;GACxE,MAAM,OAAO;GACb,QAAQ,OAAO;EACjB,CAAC;CACH;AACF;;;AAIA,SAAS,QAAQ,OAAiB,YAA2B,GAAoB;CAC/E,MAAM,OAAO,OAAO,KAAK,IAAI,GAAG,CAAC;CACjC,IAAI,eAAe,MAAM,OAAO,MAAM,SAAS,cAAc,IAAI;CACjE,OAAO,MAAM,SAAS,CAAC,QAAQ,MAAM,SAAS,IAAI;AACpD;;AAGA,SAAS,OAAO,IAAY,GAAW,GAAW,QAAyB;CACzE,IAAI,QAAQ,OAAO;CACnB,OAAO,MAAM,IAAI,IAAI,KAAK,MAAM,IAAI,KAAK,IAAI,KAAK,IAAI,IAAI,IAAI,EAAE;AAClE;;;AAIA,SAAS,OAAO,SAA4B,MAAsB;CAChE,MAAM,QAAQ,UAAU,SAAS,CAAC;CAClC,IAAI,EAAE,QAAQ,SAAS,QAAQ,SAAS,GAAG,OAAO;CAClD,IAAI,QAAQ;CACZ,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,QAAQ,KAAK,IAAI,OAAO,EAAE,QAAQ,IAAI,KAAM,QAAQ,MAAO,CAAC;CACrG,OAAO,KAAK,KAAK,OAAO,UAAU,QAAQ,SAAS,IAAI,KAAK;AAC9D;;AAGA,SAAS,UAAU,OAAiB,QAAgB,MAAsB;CACxE,MAAM,OAAO,SAAS,OAAQ,MAAM,QAAQ,KAAK,KAAM,GAAG,GAAG,UAAU,CAAC,SAAS,GAAG,IAAI,CAAC;CACzF,OAAO,SAAS,UAAU,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,GAAG,IAAI;AAC/D;AAEA,IAAM,WAAW,MAAuB,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI;AAChE,IAAM,YAAY,GAAuB,MAAuB,MAAM,UAAa,OAAO,SAAS,CAAC,IAAI,IAAI"}
|
|
@@ -139,7 +139,8 @@ declare function handlePoint(bounds: FramedBox, entry: Pick<ResizeHandleAnchor,
|
|
|
139
139
|
x: number;
|
|
140
140
|
y: number;
|
|
141
141
|
};
|
|
142
|
-
/**
|
|
142
|
+
/** A corner handle's position for the given bounds, in the bounds' frame.
|
|
143
|
+
* @deprecated Use {@link handlePoint}, which also places edge handles. */
|
|
143
144
|
declare const cornerPoint: (bounds: FramedBox, entry: Pick<CornerAnchor, 'xEdge' | 'yEdge'>) => {
|
|
144
145
|
x: number;
|
|
145
146
|
y: number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as _weasel_js_paint from '@weasel-js/paint';
|
|
2
|
+
import { TileKind, Paint, GradStop, TilePaint, PaintedFill, PaintedStroke, Inherit, Fill, Stroke, CommandFill, CommandStroke, CommandPaint } from '@weasel-js/paint';
|
|
2
3
|
import { Reflection } from '@weasel-js/registry';
|
|
3
4
|
import { ComponentType } from 'react';
|
|
4
5
|
import { IconRef } from '@weasel-js/icons';
|
|
@@ -200,6 +201,53 @@ declare function paintKindOf(fill: Paint): PaintKindEntry | undefined;
|
|
|
200
201
|
/** @internal Test helper — do not call from product code. */
|
|
201
202
|
declare function _resetPaintKindsForTests(): void;
|
|
202
203
|
|
|
204
|
+
/**
|
|
205
|
+
* What a node's `'inherit'` fill and stroke resolve to: its nearest enclosing
|
|
206
|
+
* container's, `null` for none. A field the node does not inherit is `null`
|
|
207
|
+
* here whatever its containers hold.
|
|
208
|
+
*/
|
|
209
|
+
interface InheritedPaint {
|
|
210
|
+
readonly fill: PaintedFill | null;
|
|
211
|
+
readonly stroke: PaintedStroke | null;
|
|
212
|
+
}
|
|
213
|
+
/** Whether `node`'s fill or stroke is {@link INHERIT}, so painting it needs
|
|
214
|
+
* its containers' paint. */
|
|
215
|
+
declare function inheritsPaint(node: {
|
|
216
|
+
data?: unknown;
|
|
217
|
+
}): boolean;
|
|
218
|
+
/**
|
|
219
|
+
* {@link InheritedPaint} for `node`, given its ancestors innermost first. Only
|
|
220
|
+
* containers are sources; one whose own field is `'inherit'` passes the
|
|
221
|
+
* question on to the next, and running out of containers is none.
|
|
222
|
+
*
|
|
223
|
+
* Hands back the object it returned last time for `node` while both values
|
|
224
|
+
* are the same references, so a memo can key on it.
|
|
225
|
+
*/
|
|
226
|
+
declare function inheritedPaintOf(node: {
|
|
227
|
+
data?: unknown;
|
|
228
|
+
}, ancestors: Iterable<{
|
|
229
|
+
kind?: string;
|
|
230
|
+
data?: unknown;
|
|
231
|
+
}>): InheritedPaint;
|
|
232
|
+
/** The fields {@link withInheritedPaint} resolves. */
|
|
233
|
+
type ResolvedPaintFields<T> = Omit<T, 'fill' | 'stroke'> & {
|
|
234
|
+
fill?: Exclude<T extends {
|
|
235
|
+
fill?: infer F;
|
|
236
|
+
} ? F : never, Inherit>;
|
|
237
|
+
stroke?: Exclude<T extends {
|
|
238
|
+
stroke?: infer S;
|
|
239
|
+
} ? S : never, Inherit>;
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* `data` with an `'inherit'` fill or stroke replaced by what `inherited` says
|
|
243
|
+
* it resolves to, for a reader that wants the paint a node shows rather than
|
|
244
|
+
* the one it declares. Hands `data` back as is when nothing inherits.
|
|
245
|
+
*/
|
|
246
|
+
declare function withInheritedPaint<T extends {
|
|
247
|
+
fill?: unknown;
|
|
248
|
+
stroke?: unknown;
|
|
249
|
+
}>(data: T, inherited: InheritedPaint | undefined): ResolvedPaintFields<T>;
|
|
250
|
+
|
|
203
251
|
/** The paint a path node wears, resolved: what an action that turns ink into
|
|
204
252
|
* geometry reads from a node and tells a consumer to put on a result. */
|
|
205
253
|
interface PathPaint {
|
|
@@ -209,13 +257,12 @@ interface PathPaint {
|
|
|
209
257
|
stroke?: Stroke;
|
|
210
258
|
}
|
|
211
259
|
/** {@link PathPaint} of a node that keeps its paint in `data.fill` and
|
|
212
|
-
* `data.stroke`, as the built-in path painter draws it: a stroke
|
|
213
|
-
* `paint` is none, and an
|
|
214
|
-
* the node is stroked. */
|
|
260
|
+
* `data.stroke`, as the built-in path painter draws it: a stroke or a fill
|
|
261
|
+
* with no `paint` is none, and an `'inherit'` one is `inherited`'s. */
|
|
215
262
|
declare function pathPaintOf(data: {
|
|
216
|
-
fill?: Fill | null;
|
|
217
|
-
stroke?: Stroke | null;
|
|
218
|
-
}): PathPaint;
|
|
263
|
+
fill?: Fill | Inherit | null;
|
|
264
|
+
stroke?: Stroke | Inherit | null;
|
|
265
|
+
}, inherited?: InheritedPaint): PathPaint;
|
|
219
266
|
|
|
220
267
|
/**
|
|
221
268
|
* Create Outlines: replace each selected text node with path nodes carrying
|
|
@@ -1450,7 +1497,7 @@ type InsertNodeFactory = (bounds: {
|
|
|
1450
1497
|
/**
|
|
1451
1498
|
* The paint a kit-built shape is inserted with. `kind` is the shape being
|
|
1452
1499
|
* built and `seq` counts this canvas's inserts from 1. A key left out stays
|
|
1453
|
-
* off the node,
|
|
1500
|
+
* off the node, which paints none of it, the same as `null`. A pencil
|
|
1454
1501
|
* trail too short to fit is built as a rect and asks as one. A line and an
|
|
1455
1502
|
* open pen path are inserted unfilled: a fill given to either is dropped.
|
|
1456
1503
|
* `fillRule` is the rule a path that carries one is built with, where the
|
|
@@ -1474,8 +1521,8 @@ interface TextNodeData {
|
|
|
1474
1521
|
text: string;
|
|
1475
1522
|
style?: TextStyle;
|
|
1476
1523
|
runs?: readonly StyledRun[];
|
|
1477
|
-
fill?: Fill | null;
|
|
1478
|
-
stroke?: Stroke | null;
|
|
1524
|
+
fill?: Fill | Inherit | null;
|
|
1525
|
+
stroke?: Stroke | Inherit | null;
|
|
1479
1526
|
verticalAlign?: TextVerticalAlign;
|
|
1480
1527
|
/** Paint for the whole pose box, drawn under the glyphs. `fill` is the
|
|
1481
1528
|
* glyphs' own paint, so a box behind them needs a key of its own. */
|
|
@@ -1508,11 +1555,11 @@ interface MinSize {
|
|
|
1508
1555
|
declare function minSizeOf(data: unknown): MinSize | null;
|
|
1509
1556
|
|
|
1510
1557
|
/** How a sticky note looks as it is made, named as the text node it becomes
|
|
1511
|
-
* names them. Every field
|
|
1558
|
+
* names them. Every field has a default, in {@link STICKY_DEFAULTS}. */
|
|
1512
1559
|
interface StickyStyle {
|
|
1513
1560
|
/** The note itself, painted over its whole box. */
|
|
1514
1561
|
background?: Fill;
|
|
1515
|
-
/** The text's paint.
|
|
1562
|
+
/** The text's paint. */
|
|
1516
1563
|
fill?: Fill;
|
|
1517
1564
|
/** World units from the note's edge to its text, on every side. */
|
|
1518
1565
|
padding?: number;
|
|
@@ -1538,6 +1585,7 @@ declare const STICKY_DEFAULTS: {
|
|
|
1538
1585
|
readonly color: "#fff1a6ff";
|
|
1539
1586
|
};
|
|
1540
1587
|
};
|
|
1588
|
+
readonly fill: _weasel_js_paint.PaintedFill;
|
|
1541
1589
|
readonly padding: 12;
|
|
1542
1590
|
readonly style: {
|
|
1543
1591
|
readonly fontSize: 18;
|
|
@@ -2800,4 +2848,4 @@ declare module '@weasel-js/routing' {
|
|
|
2800
2848
|
}
|
|
2801
2849
|
}
|
|
2802
2850
|
|
|
2803
|
-
export { type
|
|
2851
|
+
export { type SnapProbeAt as $, type AnchorMarkerShape as A, type PaintKindEntry as B, type ContentHandlerEntry as C, type Dims as D, type EditAnchorsDep as E, type FitViewToBoundsOptions as F, type GestureLifecycleOptions as G, type HandleLocks as H, type InheritedPaint as I, type PathEditTarget as J, type FinishedEditFrame as K, type LayerHit as L, type PathSymmetryDep as M, type BooleanOpOptions as N, type BooleansAdapter as O, type PathEditMark as P, type BooleanOp as Q, type RenderLayer as R, type SnapSource as S, type TransformFollower as T, type PathPaint as U, type ViewAnimationOptions as V, type BooleanOpResult as W, type BooleanStrokes as X, type SnapMods as Y, type SourceCandidate as Z, _resetPaintKindsForTests as _, type PathEditExtension as a, type SnapEdge as a$, type SnapCollect as a0, type SnapMark as a1, type SnapDep as a2, type SnapOutcome as a3, type SnapReadoutStyle as a4, type AngleCandidate as a5, type AngleCollect as a6, type AnimateToBoundsOptions as a7, type AreaSelectDep as a8, type BooleanOpPreview as a9, type MinSize as aA, type NodeAtPointDep as aB, type OutlinePathSpec as aC, PATH_OVERLAY_ROLES as aD, type PaintKind as aE, type PaintKindEditorProps as aF, type PaintKindFamily as aG, type PaintKindLoader as aH, type PanBounds as aI, type PathEditDrag as aJ, type PathEditDragStart as aK, type PathEditFinishCtx as aL, type PathEditHandleMove as aM, type PathEditMarksCtx as aN, type PathEditOther as aO, type PathEditPart as aP, type PathEditStore as aQ, type PathForm as aR, type PathOverlay as aS, type PathOverlayOptions as aT, type PathOverlayStyle as aU, type PathRepresentation as aV, type PathTransformFinishCtx as aW, type PreviewPathsDep as aX, type ResizePolicy as aY, STICKY_DEFAULTS as aZ, type SnapBegin as a_, type BooleanPreviewPiece as aa, type CanDropIntoDep as ab, type ClipboardDep as ac, type ClipboardIngestCtx as ad, type CreateOutlinesResult as ae, type CustomPaintContext as af, DEFAULT_LAYER_RETAIN as ag, type DecayLoopConfig as ah, type FinishedPathEdit as ai, GEOMETRY_BOUND_DATA as aj, type HandleLock as ak, type HitTestView as al, type IngestCtx as am, type IngestionDep as an, type InsertDep as ao, type InsertEnds as ap, type InsertExtras as aq, type InsertIntoDep as ar, type InsertLive as as, type LassoSelectDep as at, type LayerCommandCache as au, type LayerDrawFailure as av, type LayerDrawSpan as aw, type LayerRetainPolicy as ax, type LayoutDep as ay, type Locus as az, type SnapGesture as b, type SnapEdges as b0, type SnapMarks as b1, type SnapReadoutDep as b2, type SvgUnpacker as b3, type TextEditDep as b4, type TextNodeData as b5, type TransformPreviewNode as b6, type TransientPath as b7, VIEW_ANIMATION_KEY as b8, type ViewAnimationApi as b9, previewBooleanOp as bA, registerContentHandler as bB, registerPaintKind as bC, registerPaintKindLoader as bD, seedTile as bE, stickyData as bF, stickyNodeFactory as bG, subscribeToSources as bH, switchGradientKind as bI, textBoxOf as bJ, useDecayLoop as bK, useViewAnimation as bL, useViewAnimationOn as bM, viewAxes as bN, withInheritedPaint as bO, withoutGeometryBoundData as bP, type ViewApi as ba, type ViewChannel as bb, applyBooleanOp as bc, applyCreateOutlines as bd, asPaint as be, createPathOverlay as bf, createSnapOutcomeStore as bg, depsUnchanged as bh, drawLayers as bi, drawOneLayer as bj, fitViewToBounds as bk, getPaintKind as bl, inheritedPaintOf as bm, inheritsPaint as bn, isLayerPainted as bo, isLayerVisible as bp, listGradientKinds as bq, listPaintKinds as br, minSizeOf as bs, paintKindOf as bt, paintKindRegistry as bu, paintedLayers as bv, paintsLayer as bw, paletteInsertPaint as bx, pathPaintOf as by, patternFamily as bz, type SnapConstrain as c, type SnapResizeBox as d, type SnapGeometry as e, type SnapOutcomeStore as f, type SnapSession as g, type ViewportDims as h, type RedrawSource as i, type EditAnchorsParams as j, type InertiaConfig as k, type SliceDep as l, type StickyStyle as m, type ChromeState as n, type LayerGroup as o, type GeometryProjection as p, type InsertNodeFactory as q, type InsertPaint as r, type SvgIngestOptions as s, type SceneCanvasSnap as t, type Affordance as u, type AffordanceRegion as v, type AffordanceBinding as w, type CommonAffordanceScratch as x, type TextOverflow as y, type CreateOutlinesAdapter as z };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { serialize } from "./serialize.js";
|
|
2
|
-
import { actionBindings, buildDepsFromRequires, evaluateActive, evaluateEnabled, routesForSpec } from "@weasel-js/routing";
|
|
2
|
+
import { actionBindings, buildDepsFromRequires, evaluateActive, evaluateEnabled, overriddenBindings, routesForSpec } from "@weasel-js/routing";
|
|
3
3
|
|
|
4
4
|
//#region src/devtools/registry.ts
|
|
5
5
|
/** What one canvas has registered — its tools, actions, deps and node kinds — as plain data for `get-registry`. */
|
|
@@ -18,7 +18,7 @@ function bindingOf(binding) {
|
|
|
18
18
|
opts: serialize(binding.opts)
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
-
function toolOf(entry, slot) {
|
|
21
|
+
function toolOf(entry, slot, overrides) {
|
|
22
22
|
const p = entry.presentation;
|
|
23
23
|
return {
|
|
24
24
|
id: entry.id,
|
|
@@ -31,7 +31,7 @@ function toolOf(entry, slot) {
|
|
|
31
31
|
hidden: p?.hide === true,
|
|
32
32
|
cursor: serialize(entry.cursor),
|
|
33
33
|
eligibility: serialize(entry.eligibility),
|
|
34
|
-
bindings: (entry.bindings ?? []).map(bindingOf),
|
|
34
|
+
bindings: overriddenBindings(entry.bindings ?? [], entry.id, overrides).map(bindingOf),
|
|
35
35
|
actions: (entry.actions ?? []).map((a) => a.id),
|
|
36
36
|
deps: Object.keys(entry.deps ?? {}),
|
|
37
37
|
hasOverlay: entry.overlay !== void 0
|
|
@@ -71,7 +71,7 @@ function describeRegistry(inputs) {
|
|
|
71
71
|
for (const e of entries) {
|
|
72
72
|
if (seen.has(e.id)) continue;
|
|
73
73
|
seen.add(e.id);
|
|
74
|
-
tools.push(toolOf(e, slot));
|
|
74
|
+
tools.push(toolOf(e, slot, inputs.overrides));
|
|
75
75
|
}
|
|
76
76
|
};
|
|
77
77
|
add(Object.values(inputs.tools.registry), "tool");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","names":[],"sources":["../../src/devtools/registry.ts"],"sourcesContent":["/** What one canvas has registered — its tools, actions, deps and node kinds — as plain data for `get-registry`. */\nimport {\n actionBindings, buildDepsFromRequires, evaluateActive, evaluateEnabled, routesForSpec,\n type Action, type ActionSource, type Contribution, type DepRegistry, type GestureBinding,\n} from '@weasel-js/routing';\nimport type {\n DevtoolsActionEntry, DevtoolsBinding, DevtoolsRegistry, DevtoolsToolEntry,\n} from './protocol';\nimport { serialize } from './serialize';\n\ntype Entry = Contribution<unknown> & { cursor?: unknown };\n\n/** The live registries {@link describeRegistry} reads. */\nexport interface RegistryInputs {\n tools: { registry: Readonly<Record<string, Entry>>; ambient: readonly Entry[] };\n /** The entries a `features` preset brought beside the tools. */\n contributions: readonly Entry[];\n actions: ActionSource | null;\n deps: DepRegistry | null;\n routingKinds: readonly string[];\n}\n\nfunction routesOf(binding: GestureBinding): string[] {\n try {\n return [...routesForSpec(binding.spec)];\n } catch {\n return [];\n }\n}\n\nfunction bindingOf(binding: GestureBinding): DevtoolsBinding {\n return {\n actionId: binding.actionId,\n spec: serialize(binding.spec),\n routes: routesOf(binding),\n opts: serialize(binding.opts),\n };\n}\n\nfunction toolOf(entry: Entry, slot: DevtoolsToolEntry['slot']): DevtoolsToolEntry {\n const p = entry.presentation;\n return {\n id: entry.id,\n slot,\n label: p?.label ?? entry.id,\n icon: p?.icon ?? null,\n group: p?.group ?? null,\n shortcut: p?.shortcut ?? null,\n hotkey: entry.eligibility?.offhand ?? null,\n hidden: p?.hide === true,\n cursor: serialize(entry.cursor),\n eligibility: serialize(entry.eligibility),\n bindings: (entry.bindings ?? []).map(bindingOf),\n actions: (entry.actions ?? []).map((a) => a.id),\n deps: Object.keys(entry.deps ?? {}),\n hasOverlay: entry.overlay !== undefined,\n };\n}\n\nfunction actionOf(action: Action, deps: DepRegistry | null): DevtoolsActionEntry {\n const bag = deps ? buildDepsFromRequires(action, deps) : undefined;\n const enabled = evaluateEnabled(action, bag);\n const rule: unknown = action.eligible;\n return {\n id: action.id,\n label: action.label ?? action.id,\n icon: action.icon ?? null,\n group: action.group ?? null,\n tags: [...(action.tags ?? [])],\n shortcut: action.shortcut ?? null,\n scope: action.scope === 'hotkey' ? 'hotkey' : 'ambient',\n invoker: action.invoker?.timing ?? null,\n requires: [...(action.requires ?? [])],\n variants: (action.variants ?? []).map((v) => ({\n key: v.key, label: v.label, icon: v.icon ?? null, params: serialize(v.params),\n })),\n enabled: enabled.enabled ? true : (enabled.reason ?? 'disabled'),\n active: evaluateActive(action, bag) ?? null,\n hasPreview: action.preview !== undefined,\n // A fluent condition is callable and carries its rule tree on `.rule`.\n eligible: serialize(typeof rule === 'function' ? (rule as { rule?: unknown }).rule : (rule ?? null)),\n bindings: actionBindings(action).map(bindingOf),\n };\n}\n\nexport function describeRegistry(inputs: RegistryInputs): DevtoolsRegistry {\n const seen = new Set<string>();\n const tools: DevtoolsToolEntry[] = [];\n const add = (entries: readonly Entry[], slot: DevtoolsToolEntry['slot']): void => {\n for (const e of entries) {\n if (seen.has(e.id)) continue;\n seen.add(e.id);\n tools.push(toolOf(e, slot));\n }\n };\n add(Object.values(inputs.tools.registry), 'tool');\n add(inputs.tools.ambient, 'ambient');\n add(inputs.contributions, 'contribution');\n return {\n tools,\n actions: (inputs.actions?.list() ?? []).map((a) => actionOf(a, inputs.deps)),\n deps: (inputs.deps?.list?.() ?? []).map((d) => ({ ...d })),\n routingKinds: [...inputs.routingKinds],\n };\n}\n"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"registry.js","names":[],"sources":["../../src/devtools/registry.ts"],"sourcesContent":["/** What one canvas has registered — its tools, actions, deps and node kinds — as plain data for `get-registry`. */\nimport {\n actionBindings, buildDepsFromRequires, evaluateActive, evaluateEnabled, overriddenBindings, routesForSpec,\n type Action, type ActionSource, type Contribution, type DepRegistry, type GestureBinding, type ResolvedBindingOverrides,\n} from '@weasel-js/routing';\nimport type {\n DevtoolsActionEntry, DevtoolsBinding, DevtoolsRegistry, DevtoolsToolEntry,\n} from './protocol';\nimport { serialize } from './serialize';\n\ntype Entry = Contribution<unknown> & { cursor?: unknown };\n\n/** The live registries {@link describeRegistry} reads. */\nexport interface RegistryInputs {\n tools: { registry: Readonly<Record<string, Entry>>; ambient: readonly Entry[] };\n /** The entries a `features` preset brought beside the tools. */\n contributions: readonly Entry[];\n actions: ActionSource | null;\n /** Binding overrides in force, applied to the entries' bindings; `actions`\n * lists its own already overridden. */\n overrides?: ResolvedBindingOverrides;\n deps: DepRegistry | null;\n routingKinds: readonly string[];\n}\n\nfunction routesOf(binding: GestureBinding): string[] {\n try {\n return [...routesForSpec(binding.spec)];\n } catch {\n return [];\n }\n}\n\nfunction bindingOf(binding: GestureBinding): DevtoolsBinding {\n return {\n actionId: binding.actionId,\n spec: serialize(binding.spec),\n routes: routesOf(binding),\n opts: serialize(binding.opts),\n };\n}\n\nfunction toolOf(entry: Entry, slot: DevtoolsToolEntry['slot'], overrides: ResolvedBindingOverrides | undefined): DevtoolsToolEntry {\n const p = entry.presentation;\n return {\n id: entry.id,\n slot,\n label: p?.label ?? entry.id,\n icon: p?.icon ?? null,\n group: p?.group ?? null,\n shortcut: p?.shortcut ?? null,\n hotkey: entry.eligibility?.offhand ?? null,\n hidden: p?.hide === true,\n cursor: serialize(entry.cursor),\n eligibility: serialize(entry.eligibility),\n bindings: overriddenBindings(entry.bindings ?? [], entry.id, overrides).map(bindingOf),\n actions: (entry.actions ?? []).map((a) => a.id),\n deps: Object.keys(entry.deps ?? {}),\n hasOverlay: entry.overlay !== undefined,\n };\n}\n\nfunction actionOf(action: Action, deps: DepRegistry | null): DevtoolsActionEntry {\n const bag = deps ? buildDepsFromRequires(action, deps) : undefined;\n const enabled = evaluateEnabled(action, bag);\n const rule: unknown = action.eligible;\n return {\n id: action.id,\n label: action.label ?? action.id,\n icon: action.icon ?? null,\n group: action.group ?? null,\n tags: [...(action.tags ?? [])],\n shortcut: action.shortcut ?? null,\n scope: action.scope === 'hotkey' ? 'hotkey' : 'ambient',\n invoker: action.invoker?.timing ?? null,\n requires: [...(action.requires ?? [])],\n variants: (action.variants ?? []).map((v) => ({\n key: v.key, label: v.label, icon: v.icon ?? null, params: serialize(v.params),\n })),\n enabled: enabled.enabled ? true : (enabled.reason ?? 'disabled'),\n active: evaluateActive(action, bag) ?? null,\n hasPreview: action.preview !== undefined,\n // A fluent condition is callable and carries its rule tree on `.rule`.\n eligible: serialize(typeof rule === 'function' ? (rule as { rule?: unknown }).rule : (rule ?? null)),\n bindings: actionBindings(action).map(bindingOf),\n };\n}\n\nexport function describeRegistry(inputs: RegistryInputs): DevtoolsRegistry {\n const seen = new Set<string>();\n const tools: DevtoolsToolEntry[] = [];\n const add = (entries: readonly Entry[], slot: DevtoolsToolEntry['slot']): void => {\n for (const e of entries) {\n if (seen.has(e.id)) continue;\n seen.add(e.id);\n tools.push(toolOf(e, slot, inputs.overrides));\n }\n };\n add(Object.values(inputs.tools.registry), 'tool');\n add(inputs.tools.ambient, 'ambient');\n add(inputs.contributions, 'contribution');\n return {\n tools,\n actions: (inputs.actions?.list() ?? []).map((a) => actionOf(a, inputs.deps)),\n deps: (inputs.deps?.list?.() ?? []).map((d) => ({ ...d })),\n routingKinds: [...inputs.routingKinds],\n };\n}\n"],"mappings":";;;;;AAyBA,SAAS,SAAS,SAAmC;CACnD,IAAI;EACF,OAAO,CAAC,GAAG,cAAc,QAAQ,IAAI,CAAC;CACxC,QAAQ;EACN,OAAO,CAAC;CACV;AACF;AAEA,SAAS,UAAU,SAA0C;CAC3D,OAAO;EACL,UAAU,QAAQ;EAClB,MAAM,UAAU,QAAQ,IAAI;EAC5B,QAAQ,SAAS,OAAO;EACxB,MAAM,UAAU,QAAQ,IAAI;CAC9B;AACF;AAEA,SAAS,OAAO,OAAc,MAAiC,WAAoE;CACjI,MAAM,IAAI,MAAM;CAChB,OAAO;EACL,IAAI,MAAM;EACV;EACA,OAAO,GAAG,SAAS,MAAM;EACzB,MAAM,GAAG,QAAQ;EACjB,OAAO,GAAG,SAAS;EACnB,UAAU,GAAG,YAAY;EACzB,QAAQ,MAAM,aAAa,WAAW;EACtC,QAAQ,GAAG,SAAS;EACpB,QAAQ,UAAU,MAAM,MAAM;EAC9B,aAAa,UAAU,MAAM,WAAW;EACxC,UAAU,mBAAmB,MAAM,YAAY,CAAC,GAAG,MAAM,IAAI,SAAS,CAAC,CAAC,IAAI,SAAS;EACrF,UAAU,MAAM,WAAW,CAAC,EAAA,CAAG,KAAK,MAAM,EAAE,EAAE;EAC9C,MAAM,OAAO,KAAK,MAAM,QAAQ,CAAC,CAAC;EAClC,YAAY,MAAM,YAAY;CAChC;AACF;AAEA,SAAS,SAAS,QAAgB,MAA+C;CAC/E,MAAM,MAAM,OAAO,sBAAsB,QAAQ,IAAI,IAAI;CACzD,MAAM,UAAU,gBAAgB,QAAQ,GAAG;CAC3C,MAAM,OAAgB,OAAO;CAC7B,OAAO;EACL,IAAI,OAAO;EACX,OAAO,OAAO,SAAS,OAAO;EAC9B,MAAM,OAAO,QAAQ;EACrB,OAAO,OAAO,SAAS;EACvB,MAAM,CAAC,GAAI,OAAO,QAAQ,CAAC,CAAE;EAC7B,UAAU,OAAO,YAAY;EAC7B,OAAO,OAAO,UAAU,WAAW,WAAW;EAC9C,SAAS,OAAO,SAAS,UAAU;EACnC,UAAU,CAAC,GAAI,OAAO,YAAY,CAAC,CAAE;EACrC,WAAW,OAAO,YAAY,CAAC,EAAA,CAAG,KAAK,OAAO;GAC5C,KAAK,EAAE;GAAK,OAAO,EAAE;GAAO,MAAM,EAAE,QAAQ;GAAM,QAAQ,UAAU,EAAE,MAAM;EAC9E,EAAE;EACF,SAAS,QAAQ,UAAU,OAAQ,QAAQ,UAAU;EACrD,QAAQ,eAAe,QAAQ,GAAG,KAAK;EACvC,YAAY,OAAO,YAAY;EAE/B,UAAU,UAAU,OAAO,SAAS,aAAc,KAA4B,OAAQ,QAAQ,IAAK;EACnG,UAAU,eAAe,MAAM,CAAC,CAAC,IAAI,SAAS;CAChD;AACF;AAEA,SAAgB,iBAAiB,QAA0C;CACzE,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,QAA6B,CAAC;CACpC,MAAM,OAAO,SAA2B,SAA0C;EAChF,KAAK,MAAM,KAAK,SAAS;GACvB,IAAI,KAAK,IAAI,EAAE,EAAE,GAAG;GACpB,KAAK,IAAI,EAAE,EAAE;GACb,MAAM,KAAK,OAAO,GAAG,MAAM,OAAO,SAAS,CAAC;EAC9C;CACF;CACA,IAAI,OAAO,OAAO,OAAO,MAAM,QAAQ,GAAG,MAAM;CAChD,IAAI,OAAO,MAAM,SAAS,SAAS;CACnC,IAAI,OAAO,eAAe,cAAc;CACxC,OAAO;EACL;EACA,UAAU,OAAO,SAAS,KAAK,KAAK,CAAC,EAAA,CAAG,KAAK,MAAM,SAAS,GAAG,OAAO,IAAI,CAAC;EAC3E,OAAO,OAAO,MAAM,OAAO,KAAK,CAAC,EAAA,CAAG,KAAK,OAAO,EAAE,GAAG,EAAE,EAAE;EACzD,cAAc,CAAC,GAAG,OAAO,YAAY;CACvC;AACF"}
|
|
@@ -40,14 +40,16 @@ function measurementsContribution(scene, ms) {
|
|
|
40
40
|
target: { kindOf: onMeasurement },
|
|
41
41
|
mods: MODS_ANY
|
|
42
42
|
},
|
|
43
|
-
actionId: MEASURE_ACTION_IDS.pick
|
|
43
|
+
actionId: MEASURE_ACTION_IDS.pick,
|
|
44
|
+
opts: { id: "weasel-measure.click" }
|
|
44
45
|
}, {
|
|
45
46
|
spec: {
|
|
46
47
|
kind: "drag",
|
|
47
48
|
target: { kindOf: onMeasurement },
|
|
48
49
|
mods: MODS_ANY
|
|
49
50
|
},
|
|
50
|
-
actionId: MEASURE_ACTION_IDS.drag
|
|
51
|
+
actionId: MEASURE_ACTION_IDS.drag,
|
|
52
|
+
opts: { id: "weasel-measure.drag" }
|
|
51
53
|
}]
|
|
52
54
|
};
|
|
53
55
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"measureContribution.js","names":[],"sources":["../../../src/features/measure/measureContribution.ts"],"sourcesContent":["import { hitPredicate } from '@weasel-js/gestures';\nimport type { Scene } from 'core/scene/types';\nimport type { SurfaceContribution } from 'canvas/surfaceContribution';\nimport type { Measurements } from './measurements';\nimport { measureCommands } from './measureCommands';\nimport { measureDragAction, measurePickAction } from './measureGestures';\nimport { MEASURE_AFFORDANCE_KIND, measurementsLayer } from './measureLayer';\nimport { MEASURE_ACTION_IDS } from './actionIds';\n\nconst onMeasurement = hitPredicate((hit) => (hit as { kind?: string } | null | undefined)?.kind === MEASURE_AFFORDANCE_KIND);\n\nconst MODS_ANY = { shift: 'optional', alt: 'optional', meta: 'optional', ctrl: 'optional' } as const;\n\n/**\n * A scene's measurements on a canvas, whatever tool is active: the layer\n * that shows the picked one and claims presses on measurements, a click on\n * one to pick it and a drag to edit it, and the commands — Move by, Rotate\n * by, Delete, Clear — a readout binds buttons to. Install it with\n * `<SceneCanvas ambient={[…]}>`; `useMeasureTool` adds the measuring itself.\n */\nexport function measurementsContribution(\n scene: Scene<unknown, string, unknown>,\n ms: Measurements,\n): SurfaceContribution {\n const layer = measurementsLayer(scene, ms);\n return {\n id: 'weasel-measure',\n eligibility: { claimed: true },\n attach: (api) => {\n const off = api.registerLayer(layer);\n api.requestRedraw();\n return off;\n },\n actions: [...measureCommands(ms), measurePickAction(ms), measureDragAction(ms)],\n bindings: [\n { spec: { kind: 'click', target: { kindOf: onMeasurement }, mods: MODS_ANY }, actionId: MEASURE_ACTION_IDS.pick },\n { spec: { kind: 'drag', target: { kindOf: onMeasurement }, mods: MODS_ANY }, actionId: MEASURE_ACTION_IDS.drag },\n ],\n };\n}\n"],"mappings":";;;;;;;AASA,IAAM,gBAAgB,cAAc,QAAS,KAA8C,SAAS,uBAAuB;AAE3H,IAAM,WAAW;CAAE,OAAO;CAAY,KAAK;CAAY,MAAM;CAAY,MAAM;AAAW;;;;;;;;AAS1F,SAAgB,yBACd,OACA,IACqB;CACrB,MAAM,QAAQ,kBAAkB,OAAO,EAAE;CACzC,OAAO;EACL,IAAI;EACJ,aAAa,EAAE,SAAS,KAAK;EAC7B,SAAS,QAAQ;GACf,MAAM,MAAM,IAAI,cAAc,KAAK;GACnC,IAAI,cAAc;GAClB,OAAO;EACT;EACA,SAAS;GAAC,GAAG,gBAAgB,EAAE;GAAG,kBAAkB,EAAE;GAAG,kBAAkB,EAAE;EAAC;EAC9E,UAAU,CACR;GAAE,MAAM;IAAE,MAAM;IAAS,QAAQ,EAAE,QAAQ,cAAc;IAAG,MAAM;GAAS;GAAG,UAAU,mBAAmB;
|
|
1
|
+
{"version":3,"file":"measureContribution.js","names":[],"sources":["../../../src/features/measure/measureContribution.ts"],"sourcesContent":["import { hitPredicate } from '@weasel-js/gestures';\nimport type { Scene } from 'core/scene/types';\nimport type { SurfaceContribution } from 'canvas/surfaceContribution';\nimport type { Measurements } from './measurements';\nimport { measureCommands } from './measureCommands';\nimport { measureDragAction, measurePickAction } from './measureGestures';\nimport { MEASURE_AFFORDANCE_KIND, measurementsLayer } from './measureLayer';\nimport { MEASURE_ACTION_IDS } from './actionIds';\n\nconst onMeasurement = hitPredicate((hit) => (hit as { kind?: string } | null | undefined)?.kind === MEASURE_AFFORDANCE_KIND);\n\nconst MODS_ANY = { shift: 'optional', alt: 'optional', meta: 'optional', ctrl: 'optional' } as const;\n\n/**\n * A scene's measurements on a canvas, whatever tool is active: the layer\n * that shows the picked one and claims presses on measurements, a click on\n * one to pick it and a drag to edit it, and the commands — Move by, Rotate\n * by, Delete, Clear — a readout binds buttons to. Install it with\n * `<SceneCanvas ambient={[…]}>`; `useMeasureTool` adds the measuring itself.\n */\nexport function measurementsContribution(\n scene: Scene<unknown, string, unknown>,\n ms: Measurements,\n): SurfaceContribution {\n const layer = measurementsLayer(scene, ms);\n return {\n id: 'weasel-measure',\n eligibility: { claimed: true },\n attach: (api) => {\n const off = api.registerLayer(layer);\n api.requestRedraw();\n return off;\n },\n actions: [...measureCommands(ms), measurePickAction(ms), measureDragAction(ms)],\n bindings: [\n { spec: { kind: 'click', target: { kindOf: onMeasurement }, mods: MODS_ANY }, actionId: MEASURE_ACTION_IDS.pick, opts: { id: 'weasel-measure.click' } },\n { spec: { kind: 'drag', target: { kindOf: onMeasurement }, mods: MODS_ANY }, actionId: MEASURE_ACTION_IDS.drag, opts: { id: 'weasel-measure.drag' } },\n ],\n };\n}\n"],"mappings":";;;;;;;AASA,IAAM,gBAAgB,cAAc,QAAS,KAA8C,SAAS,uBAAuB;AAE3H,IAAM,WAAW;CAAE,OAAO;CAAY,KAAK;CAAY,MAAM;CAAY,MAAM;AAAW;;;;;;;;AAS1F,SAAgB,yBACd,OACA,IACqB;CACrB,MAAM,QAAQ,kBAAkB,OAAO,EAAE;CACzC,OAAO;EACL,IAAI;EACJ,aAAa,EAAE,SAAS,KAAK;EAC7B,SAAS,QAAQ;GACf,MAAM,MAAM,IAAI,cAAc,KAAK;GACnC,IAAI,cAAc;GAClB,OAAO;EACT;EACA,SAAS;GAAC,GAAG,gBAAgB,EAAE;GAAG,kBAAkB,EAAE;GAAG,kBAAkB,EAAE;EAAC;EAC9E,UAAU,CACR;GAAE,MAAM;IAAE,MAAM;IAAS,QAAQ,EAAE,QAAQ,cAAc;IAAG,MAAM;GAAS;GAAG,UAAU,mBAAmB;GAAM,MAAM,EAAE,IAAI,uBAAuB;EAAE,GACtJ;GAAE,MAAM;IAAE,MAAM;IAAQ,QAAQ,EAAE,QAAQ,cAAc;IAAG,MAAM;GAAS;GAAG,UAAU,mBAAmB;GAAM,MAAM,EAAE,IAAI,sBAAsB;EAAE,CACtJ;CACF;AACF"}
|
|
@@ -38,12 +38,16 @@ function triggerAction(mode, targets, enter, canEnter) {
|
|
|
38
38
|
if (id === void 0) return null;
|
|
39
39
|
return canEnter?.(mode.id, id) === false ? null : id;
|
|
40
40
|
};
|
|
41
|
+
const id = `mode.${mode.id}.trigger`;
|
|
41
42
|
return {
|
|
42
|
-
id
|
|
43
|
+
id,
|
|
43
44
|
label: `Enter ${modeLabel(mode)}`,
|
|
44
45
|
defaultBinding: targets.map((kind) => ({
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
spec: {
|
|
47
|
+
kind: "doubleClick",
|
|
48
|
+
target: `kind:${kind}`
|
|
49
|
+
},
|
|
50
|
+
opts: { id: `${id}.doubleClick.${kind}` }
|
|
47
51
|
})),
|
|
48
52
|
requires: ["hitAt"],
|
|
49
53
|
enabled: (deps, at) => targetAt(deps, at) !== null ? true : ActionDisabledReason.NotApplicable,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modeTriggers.js","names":[],"sources":["../../../src/features/modes/modeTriggers.ts"],"sourcesContent":["import { modeLabel, type ModeDefinition, type ModeRegistry } from '@weasel-js/modes';\nimport { ActionDisabledReason, type Action, type ActionDeps, type Contribution } from '@weasel-js/routing';\n\n/** What a mode's double-click trigger does. */\nexport interface ModeTriggerHandlers {\n /** Enter mode `id` on the node `targetId`. Without it nothing is bound. */\n enter?: (id: string, targetId: string) => void;\n /**\n * Whether mode `id` can be entered on `targetId` now. Where it says no the double-click falls\n * through to the next binding on it, so a canvas action that already enters the mode on some\n * of those nodes keeps them: `enterPathEdit` on a path with anchors, say. Default yes.\n */\n canEnter?: (id: string, targetId: string) => boolean;\n}\n\n/**\n * The registry's double-click entries as an always-on contribution: for each mode whose\n * `entry.trigger` is `'double-click-target'`, one action bound to a double-click on each routing\n * kind its `entry.targets` names, entering the mode on the node double-clicked. Install it with\n * `<SceneCanvas ambient>`, beside `modeShortcuts`.\n *\n * The target is the node the dispatcher classified the double-click on (the `hitAt` dep's\n * `doubleClick`, else its hit), the one the binding's `kind:` target matched: a double-click on\n * a grouped member names its outermost group inside `<SceneCanvas isolatedGroup>`. The action is live in every mode, the target mode\n * included, so a double-click on another node moves a mode onto it; `canEnter` and `enter`\n * decide what a switch from the current mode means.\n *\n * The bindings ride the ambient tier: a tool that claims the double-click itself, the pen\n * finishing a stroke, keeps it.\n */\nexport function modeTriggers(registry: ModeRegistry, handlers: ModeTriggerHandlers): Contribution<never> {\n const actions: Action[] = [];\n const { enter } = handlers;\n if (enter) {\n for (const mode of registry.list()) {\n const targets = mode.entry?.trigger === 'double-click-target' ? mode.entry.targets ?? [] : [];\n if (targets.length > 0) actions.push(triggerAction(mode, targets, enter, handlers.canEnter));\n }\n }\n return { id: 'modes.triggers', eligibility: { always: true }, actions };\n}\n\nfunction triggerAction(\n mode: ModeDefinition,\n targets: readonly string[],\n enter: (id: string, targetId: string) => void,\n canEnter: ModeTriggerHandlers['canEnter'],\n): Action {\n const targetAt = (deps: ActionDeps | undefined, at: { x: number; y: number } | undefined): string | null => {\n if (!at) return null;\n const hit = deps?.hitAt?.(at);\n const id = (hit?.doubleClick ?? hit)?.id;\n if (id === undefined) return null;\n return canEnter?.(mode.id, id) === false ? null : id;\n };\n
|
|
1
|
+
{"version":3,"file":"modeTriggers.js","names":[],"sources":["../../../src/features/modes/modeTriggers.ts"],"sourcesContent":["import { modeLabel, type ModeDefinition, type ModeRegistry } from '@weasel-js/modes';\nimport { ActionDisabledReason, type Action, type ActionDeps, type Contribution } from '@weasel-js/routing';\n\n/** What a mode's double-click trigger does. */\nexport interface ModeTriggerHandlers {\n /** Enter mode `id` on the node `targetId`. Without it nothing is bound. */\n enter?: (id: string, targetId: string) => void;\n /**\n * Whether mode `id` can be entered on `targetId` now. Where it says no the double-click falls\n * through to the next binding on it, so a canvas action that already enters the mode on some\n * of those nodes keeps them: `enterPathEdit` on a path with anchors, say. Default yes.\n */\n canEnter?: (id: string, targetId: string) => boolean;\n}\n\n/**\n * The registry's double-click entries as an always-on contribution: for each mode whose\n * `entry.trigger` is `'double-click-target'`, one action bound to a double-click on each routing\n * kind its `entry.targets` names, entering the mode on the node double-clicked. Install it with\n * `<SceneCanvas ambient>`, beside `modeShortcuts`.\n *\n * The target is the node the dispatcher classified the double-click on (the `hitAt` dep's\n * `doubleClick`, else its hit), the one the binding's `kind:` target matched: a double-click on\n * a grouped member names its outermost group inside `<SceneCanvas isolatedGroup>`. The action is live in every mode, the target mode\n * included, so a double-click on another node moves a mode onto it; `canEnter` and `enter`\n * decide what a switch from the current mode means.\n *\n * The bindings ride the ambient tier: a tool that claims the double-click itself, the pen\n * finishing a stroke, keeps it.\n */\nexport function modeTriggers(registry: ModeRegistry, handlers: ModeTriggerHandlers): Contribution<never> {\n const actions: Action[] = [];\n const { enter } = handlers;\n if (enter) {\n for (const mode of registry.list()) {\n const targets = mode.entry?.trigger === 'double-click-target' ? mode.entry.targets ?? [] : [];\n if (targets.length > 0) actions.push(triggerAction(mode, targets, enter, handlers.canEnter));\n }\n }\n return { id: 'modes.triggers', eligibility: { always: true }, actions };\n}\n\nfunction triggerAction(\n mode: ModeDefinition,\n targets: readonly string[],\n enter: (id: string, targetId: string) => void,\n canEnter: ModeTriggerHandlers['canEnter'],\n): Action {\n const targetAt = (deps: ActionDeps | undefined, at: { x: number; y: number } | undefined): string | null => {\n if (!at) return null;\n const hit = deps?.hitAt?.(at);\n const id = (hit?.doubleClick ?? hit)?.id;\n if (id === undefined) return null;\n return canEnter?.(mode.id, id) === false ? null : id;\n };\n const id = `mode.${mode.id}.trigger`;\n return {\n id,\n label: `Enter ${modeLabel(mode)}`,\n defaultBinding: targets.map((kind) => ({\n spec: { kind: 'doubleClick' as const, target: `kind:${kind}` as const },\n opts: { id: `${id}.doubleClick.${kind}` },\n })),\n requires: ['hitAt'],\n enabled: (deps, at) => (targetAt(deps, at) !== null ? true : ActionDisabledReason.NotApplicable),\n invoker: {\n timing: 'immediate',\n run: (deps, params) => {\n const x = params?.worldX, y = params?.worldY;\n const id = typeof x === 'number' && typeof y === 'number' ? targetAt(deps, { x, y }) : null;\n if (id !== null) enter(mode.id, id);\n },\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,aAAa,UAAwB,UAAoD;CACvG,MAAM,UAAoB,CAAC;CAC3B,MAAM,EAAE,UAAU;CAClB,IAAI,OACF,KAAK,MAAM,QAAQ,SAAS,KAAK,GAAG;EAClC,MAAM,UAAU,KAAK,OAAO,YAAY,wBAAwB,KAAK,MAAM,WAAW,CAAC,IAAI,CAAC;EAC5F,IAAI,QAAQ,SAAS,GAAG,QAAQ,KAAK,cAAc,MAAM,SAAS,OAAO,SAAS,QAAQ,CAAC;CAC7F;CAEF,OAAO;EAAE,IAAI;EAAkB,aAAa,EAAE,QAAQ,KAAK;EAAG;CAAQ;AACxE;AAEA,SAAS,cACP,MACA,SACA,OACA,UACQ;CACR,MAAM,YAAY,MAA8B,OAA4D;EAC1G,IAAI,CAAC,IAAI,OAAO;EAChB,MAAM,MAAM,MAAM,QAAQ,EAAE;EAC5B,MAAM,MAAM,KAAK,eAAe,IAAA,EAAM;EACtC,IAAI,OAAO,QAAW,OAAO;EAC7B,OAAO,WAAW,KAAK,IAAI,EAAE,MAAM,QAAQ,OAAO;CACpD;CACA,MAAM,KAAK,QAAQ,KAAK,GAAG;CAC3B,OAAO;EACL;EACA,OAAO,SAAS,UAAU,IAAI;EAC9B,gBAAgB,QAAQ,KAAK,UAAU;GACrC,MAAM;IAAE,MAAM;IAAwB,QAAQ,QAAQ;GAAgB;GACtE,MAAM,EAAE,IAAI,GAAG,GAAG,eAAe,OAAO;EAC1C,EAAE;EACF,UAAU,CAAC,OAAO;EAClB,UAAU,MAAM,OAAQ,SAAS,MAAM,EAAE,MAAM,OAAO,OAAO,qBAAqB;EAClF,SAAS;GACP,QAAQ;GACR,MAAM,MAAM,WAAW;IACrB,MAAM,IAAI,QAAQ,QAAQ,IAAI,QAAQ;IACtC,MAAM,KAAK,OAAO,MAAM,YAAY,OAAO,MAAM,WAAW,SAAS,MAAM;KAAE;KAAG;IAAE,CAAC,IAAI;IACvF,IAAI,OAAO,MAAM,MAAM,KAAK,IAAI,EAAE;GACpC;EACF;CACF;AACF"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { pathEffectOf } from "../../core/scene/pathEffects/derive.js";
|
|
2
|
+
import { isTextData } from "@weasel-js/text";
|
|
3
|
+
|
|
4
|
+
//#region src/features/pathEffects/effectSources.ts
|
|
5
|
+
/**
|
|
6
|
+
* A path effect that hides its sources is edited through itself: its text and
|
|
7
|
+
* its path are reached from the effect node.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* For a path-effect node with `hideSources`, the source an edit of `role`
|
|
11
|
+
* goes to: `'text'`, its first source when that is a text node; `'path'`, the
|
|
12
|
+
* path it lays along — the second source of an along kind, a blend's spine.
|
|
13
|
+
* `null` otherwise. The one answer every edit route asks.
|
|
14
|
+
*/
|
|
15
|
+
function effectSourceFor(scene, id, role) {
|
|
16
|
+
const node = scene.get(id);
|
|
17
|
+
const effect = pathEffectOf(node);
|
|
18
|
+
const sources = node?.dependsOn;
|
|
19
|
+
if (effect === null || effect.hideSources !== true || !Array.isArray(sources)) return null;
|
|
20
|
+
const source = sources[role === "text" ? 0 : effect.kind === "blend" ? 2 : 1];
|
|
21
|
+
const found = source === void 0 ? void 0 : scene.get(source);
|
|
22
|
+
if (found === void 0) return null;
|
|
23
|
+
return role === "text" && !isTextData(found.data) ? null : source;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The sources a removal of `ids` takes with it: those that a path effect with
|
|
27
|
+
* `hideSources` in the removal hid, and that nothing staying still derives
|
|
28
|
+
* from. A source another effect still reads stays, and so does one shown
|
|
29
|
+
* again since or named in `keep`. Transitive, so an effect's hidden effect
|
|
30
|
+
* takes its own too.
|
|
31
|
+
*/
|
|
32
|
+
function hiddenSourcesTakenWith(scene, ids, keep = /* @__PURE__ */ new Set()) {
|
|
33
|
+
const going = new Set(scene.removalClosure(ids));
|
|
34
|
+
const taken = [];
|
|
35
|
+
const within = (node, root) => {
|
|
36
|
+
for (let at = node; at != null; at = scene.get(at)?.parent) if (at === root) return true;
|
|
37
|
+
return false;
|
|
38
|
+
};
|
|
39
|
+
let grew = true;
|
|
40
|
+
while (grew) {
|
|
41
|
+
grew = false;
|
|
42
|
+
for (const id of [...going]) {
|
|
43
|
+
const node = scene.get(id);
|
|
44
|
+
if (pathEffectOf(node)?.hideSources !== true || !Array.isArray(node?.dependsOn)) continue;
|
|
45
|
+
for (const source of node.dependsOn) {
|
|
46
|
+
if (going.has(source) || keep.has(source) || scene.get(source)?.hidden !== true) continue;
|
|
47
|
+
const closure = scene.removalClosure([source]);
|
|
48
|
+
if (!closure.every((n) => going.has(n) || within(n, source))) continue;
|
|
49
|
+
for (const n of closure) going.add(n);
|
|
50
|
+
taken.push(source);
|
|
51
|
+
grew = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return taken;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
//#endregion
|
|
59
|
+
export { effectSourceFor, hiddenSourcesTakenWith };
|
|
60
|
+
//# sourceMappingURL=effectSources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effectSources.js","names":[],"sources":["../../../src/features/pathEffects/effectSources.ts"],"sourcesContent":["/**\n * A path effect that hides its sources is edited through itself: its text and\n * its path are reached from the effect node.\n */\nimport { isTextData } from '@weasel-js/text';\nimport { pathEffectOf } from 'core/scene/pathEffects/derive';\nimport type { NodeId, Scene } from 'core/scene/types';\n\n/**\n * For a path-effect node with `hideSources`, the source an edit of `role`\n * goes to: `'text'`, its first source when that is a text node; `'path'`, the\n * path it lays along — the second source of an along kind, a blend's spine.\n * `null` otherwise. The one answer every edit route asks.\n */\nexport function effectSourceFor<TData, TLayer extends string, TPose>(\n scene: Pick<Scene<TData, TLayer, TPose>, 'get'>,\n id: NodeId,\n role: 'text' | 'path',\n): NodeId | null {\n const node = scene.get(id);\n const effect = pathEffectOf(node);\n const sources = node?.dependsOn;\n if (effect === null || effect.hideSources !== true || !Array.isArray(sources)) return null;\n const at = role === 'text' ? 0 : effect.kind === 'blend' ? 2 : 1;\n const source = (sources as readonly NodeId[])[at];\n const found = source === undefined ? undefined : scene.get(source);\n if (found === undefined) return null;\n return role === 'text' && !isTextData(found.data) ? null : source!;\n}\n\n/**\n * The sources a removal of `ids` takes with it: those that a path effect with\n * `hideSources` in the removal hid, and that nothing staying still derives\n * from. A source another effect still reads stays, and so does one shown\n * again since or named in `keep`. Transitive, so an effect's hidden effect\n * takes its own too.\n */\nexport function hiddenSourcesTakenWith<TData, TLayer extends string, TPose>(\n scene: Pick<Scene<TData, TLayer, TPose>, 'get' | 'removalClosure'>,\n ids: readonly NodeId[],\n keep: ReadonlySet<NodeId> = new Set(),\n): NodeId[] {\n const going = new Set<NodeId>(scene.removalClosure(ids));\n const taken: NodeId[] = [];\n const within = (node: NodeId, root: NodeId): boolean => {\n for (let at: NodeId | null | undefined = node; at != null; at = scene.get(at)?.parent) if (at === root) return true;\n return false;\n };\n let grew = true;\n while (grew) {\n grew = false;\n for (const id of [...going]) {\n const node = scene.get(id);\n if (pathEffectOf(node)?.hideSources !== true || !Array.isArray(node?.dependsOn)) continue;\n for (const source of node.dependsOn as readonly NodeId[]) {\n if (going.has(source) || keep.has(source) || scene.get(source)?.hidden !== true) continue;\n const closure = scene.removalClosure([source]);\n if (!closure.every((n) => going.has(n) || within(n, source))) continue;\n for (const n of closure) going.add(n);\n taken.push(source);\n grew = true;\n }\n }\n }\n return taken;\n}\n"],"mappings":";;;;;;;;;;;;;;AAcA,SAAgB,gBACd,OACA,IACA,MACe;CACf,MAAM,OAAO,MAAM,IAAI,EAAE;CACzB,MAAM,SAAS,aAAa,IAAI;CAChC,MAAM,UAAU,MAAM;CACtB,IAAI,WAAW,QAAQ,OAAO,gBAAgB,QAAQ,CAAC,MAAM,QAAQ,OAAO,GAAG,OAAO;CAEtF,MAAM,SAAU,QADL,SAAS,SAAS,IAAI,OAAO,SAAS,UAAU,IAAI;CAE/D,MAAM,QAAQ,WAAW,SAAY,SAAY,MAAM,IAAI,MAAM;CACjE,IAAI,UAAU,QAAW,OAAO;CAChC,OAAO,SAAS,UAAU,CAAC,WAAW,MAAM,IAAI,IAAI,OAAO;AAC7D;;;;;;;;AASA,SAAgB,uBACd,OACA,KACA,uBAA4B,IAAI,IAAI,GAC1B;CACV,MAAM,QAAQ,IAAI,IAAY,MAAM,eAAe,GAAG,CAAC;CACvD,MAAM,QAAkB,CAAC;CACzB,MAAM,UAAU,MAAc,SAA0B;EACtD,KAAK,IAAI,KAAgC,MAAM,MAAM,MAAM,KAAK,MAAM,IAAI,EAAE,CAAC,EAAE,QAAQ,IAAI,OAAO,MAAM,OAAO;EAC/G,OAAO;CACT;CACA,IAAI,OAAO;CACX,OAAO,MAAM;EACX,OAAO;EACP,KAAK,MAAM,MAAM,CAAC,GAAG,KAAK,GAAG;GAC3B,MAAM,OAAO,MAAM,IAAI,EAAE;GACzB,IAAI,aAAa,IAAI,CAAC,EAAE,gBAAgB,QAAQ,CAAC,MAAM,QAAQ,MAAM,SAAS,GAAG;GACjF,KAAK,MAAM,UAAU,KAAK,WAAgC;IACxD,IAAI,MAAM,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,CAAC,EAAE,WAAW,MAAM;IACjF,MAAM,UAAU,MAAM,eAAe,CAAC,MAAM,CAAC;IAC7C,IAAI,CAAC,QAAQ,OAAO,MAAM,MAAM,IAAI,CAAC,KAAK,OAAO,GAAG,MAAM,CAAC,GAAG;IAC9D,KAAK,MAAM,KAAK,SAAS,MAAM,IAAI,CAAC;IACpC,MAAM,KAAK,MAAM;IACjB,OAAO;GACT;EACF;CACF;CACA,OAAO;AACT"}
|
|
@@ -1,15 +1,31 @@
|
|
|
1
|
+
import { noteFontRead } from "../../core/scene/fontReads.js";
|
|
1
2
|
import { isRectPose } from "../../core/geometry/autoPoseDescriptor.js";
|
|
2
3
|
import { pathPaintOf } from "../../canvas/nodeShapes/nodePaint.js";
|
|
3
4
|
import { findNodeShape } from "../../canvas/NodeShape.js";
|
|
5
|
+
import { textEffectItems } from "./textItems.js";
|
|
4
6
|
import { pathThroughPoseFrame, poseLinearOf } from "@weasel-js/geom";
|
|
7
|
+
import { isTextData } from "@weasel-js/text";
|
|
5
8
|
|
|
6
9
|
//#region src/features/pathEffects/readSource.ts
|
|
7
10
|
/**
|
|
8
11
|
* How the kit's path effects read a source node: its outline from its
|
|
9
12
|
* painter's silhouette, its paint as `kit:path` resolves it.
|
|
10
13
|
*/
|
|
11
|
-
/** `dep` as a path effect's source, or `null` when it has no outline.
|
|
14
|
+
/** `dep` as a path effect's source, or `null` when it has no outline. A text
|
|
15
|
+
* node's source also carries its grapheme clusters as items. */
|
|
12
16
|
function readEffectSource(dep) {
|
|
17
|
+
const data = dep.node.data;
|
|
18
|
+
const text = isTextData(data);
|
|
19
|
+
if (text) noteFontRead();
|
|
20
|
+
const source = readShape(dep);
|
|
21
|
+
const pose = dep.pose;
|
|
22
|
+
if (source === null || !text || !isRectPose(pose)) return source;
|
|
23
|
+
return {
|
|
24
|
+
...source,
|
|
25
|
+
items: () => textEffectItems(data, pose)
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function readShape(dep) {
|
|
13
29
|
const data = dep.node.data ?? {};
|
|
14
30
|
const { fill, stroke } = pathPaintOf(data);
|
|
15
31
|
const paint = {
|