@weasel-js/core 1.11.1 → 1.13.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (488) hide show
  1. package/CHANGELOG.md +275 -0
  2. package/dist/WeaselProvider.js +1 -1
  3. package/dist/affordances/pathAnchors.js +10 -6
  4. package/dist/affordances/pathAnchors.js.map +1 -1
  5. package/dist/animation/poseTarget.js +46 -15
  6. package/dist/animation/poseTarget.js.map +1 -1
  7. package/dist/animation/poseTransition.js +3 -3
  8. package/dist/animation/poseTransition.js.map +1 -1
  9. package/dist/animation/reflow.js +3 -6
  10. package/dist/animation/reflow.js.map +1 -1
  11. package/dist/animation/rig/bindRig.js +2 -2
  12. package/dist/animation/rig/bindRig.js.map +1 -1
  13. package/dist/animation/rig/skinBinding.js +2 -2
  14. package/dist/animation/rig/skinBinding.js.map +1 -1
  15. package/dist/barrel/canvas.js +5 -1
  16. package/dist/barrel/chrome.js +3 -1
  17. package/dist/barrel/paint.js +4 -4
  18. package/dist/barrel/paths.js +9 -4
  19. package/dist/barrel/renderer.js +3 -2
  20. package/dist/barrel/routing.js +4 -4
  21. package/dist/barrel/scene.js +7 -1
  22. package/dist/barrel/text.js +2 -2
  23. package/dist/barrel/viewport.js +6 -2
  24. package/dist/canvas/Canvas.js +2 -1
  25. package/dist/canvas/Canvas.js.map +1 -1
  26. package/dist/canvas/CanvasView.js +23 -34
  27. package/dist/canvas/CanvasView.js.map +1 -1
  28. package/dist/canvas/ModalityHud.js +1 -1
  29. package/dist/canvas/NodeShape.js +5 -1
  30. package/dist/canvas/NodeShape.js.map +1 -1
  31. package/dist/canvas/SceneCanvas/ActionsProviderIfRoot.js +1 -1
  32. package/dist/canvas/SceneCanvas/DepRegistryProviderIfRoot.js +1 -1
  33. package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +1 -1
  34. package/dist/canvas/SceneCanvas/SceneCanvasInput.js +2 -0
  35. package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
  36. package/dist/canvas/SceneCanvas/SurfaceDeclarations.js +23 -0
  37. package/dist/canvas/SceneCanvas/SurfaceDeclarations.js.map +1 -0
  38. package/dist/canvas/SceneCanvas/canvasPassThrough.js +2 -1
  39. package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
  40. package/dist/canvas/SceneCanvas/defaultNodeRouting.js +2 -2
  41. package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
  42. package/dist/canvas/SceneCanvas/props.js.map +1 -1
  43. package/dist/canvas/SceneCanvas/scenePointQuery.js +27 -5
  44. package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -1
  45. package/dist/canvas/SceneCanvas/useActionTunings.js +6 -1
  46. package/dist/canvas/SceneCanvas/useActionTunings.js.map +1 -1
  47. package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js +1 -0
  48. package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js.map +1 -1
  49. package/dist/canvas/SceneCanvas/useCanvasTools.js +10 -2
  50. package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
  51. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +25 -19
  52. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
  53. package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js +9 -3
  54. package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js.map +1 -1
  55. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +17 -2
  56. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
  57. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +2 -0
  58. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
  59. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js +19 -4
  60. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js.map +1 -1
  61. package/dist/canvas/SceneCanvas/useScenePicking.js +31 -6
  62. package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
  63. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +4 -5
  64. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  65. package/dist/canvas/SceneCanvas/useSceneStandardActions.js +7 -4
  66. package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
  67. package/dist/canvas/SceneCanvas.js +30 -12
  68. package/dist/canvas/SceneCanvas.js.map +1 -1
  69. package/dist/canvas/ViewMarker.js +35 -0
  70. package/dist/canvas/ViewMarker.js.map +1 -0
  71. package/dist/canvas/accentColor.js +26 -3
  72. package/dist/canvas/accentColor.js.map +1 -1
  73. package/dist/canvas/buildSceneTree.js +14 -20
  74. package/dist/canvas/buildSceneTree.js.map +1 -1
  75. package/dist/canvas/copiesAbove.js +1 -1
  76. package/dist/canvas/copiesAbove.js.map +1 -1
  77. package/dist/canvas/defaultDrawOne.js +11 -6
  78. package/dist/canvas/defaultDrawOne.js.map +1 -1
  79. package/dist/canvas/deps/areaSelect.js +5 -2
  80. package/dist/canvas/deps/areaSelect.js.map +1 -1
  81. package/dist/canvas/deps/booleans.js +3 -2
  82. package/dist/canvas/deps/booleans.js.map +1 -1
  83. package/dist/canvas/deps/enteredGroup.js +81 -0
  84. package/dist/canvas/deps/enteredGroup.js.map +1 -0
  85. package/dist/canvas/deps/hitTestArea.js +28 -16
  86. package/dist/canvas/deps/hitTestArea.js.map +1 -1
  87. package/dist/canvas/deps/insert.js +10 -38
  88. package/dist/canvas/deps/insert.js.map +1 -1
  89. package/dist/canvas/deps/insertSite.js +39 -0
  90. package/dist/canvas/deps/insertSite.js.map +1 -0
  91. package/dist/canvas/deps/lassoSelect.js +6 -3
  92. package/dist/canvas/deps/lassoSelect.js.map +1 -1
  93. package/dist/canvas/deps/pathEditCommits.js +3 -7
  94. package/dist/canvas/deps/pathEditCommits.js.map +1 -1
  95. package/dist/canvas/deps/pathLeaf.js +1 -1
  96. package/dist/canvas/deps/pathLeaf.js.map +1 -1
  97. package/dist/canvas/deps/slice.js +2 -1
  98. package/dist/canvas/deps/slice.js.map +1 -1
  99. package/dist/canvas/derivedPath.js +14 -6
  100. package/dist/canvas/derivedPath.js.map +1 -1
  101. package/dist/canvas/doubleClickTarget.js +9 -11
  102. package/dist/canvas/doubleClickTarget.js.map +1 -1
  103. package/dist/canvas/drawnBounds.js +26 -9
  104. package/dist/canvas/drawnBounds.js.map +1 -1
  105. package/dist/canvas/inheritedPaints.js +55 -0
  106. package/dist/canvas/inheritedPaints.js.map +1 -0
  107. package/dist/canvas/nodeShapes/derivedPainter.js +15 -16
  108. package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -1
  109. package/dist/canvas/nodeShapes/nodePaint.js +7 -34
  110. package/dist/canvas/nodeShapes/nodePaint.js.map +1 -1
  111. package/dist/canvas/nodeShapes/pathPainter.js +7 -7
  112. package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
  113. package/dist/canvas/nodeShapes/rectFallbackPainter.js +4 -5
  114. package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -1
  115. package/dist/canvas/nodeShapes/shapePainter.js +9 -9
  116. package/dist/canvas/nodeShapes/shapePainter.js.map +1 -1
  117. package/dist/canvas/nodeShapes/textPainter.js +13 -12
  118. package/dist/canvas/nodeShapes/textPainter.js.map +1 -1
  119. package/dist/canvas/ownClip.js +24 -0
  120. package/dist/canvas/ownClip.js.map +1 -0
  121. package/dist/canvas/paintCull.js.map +1 -1
  122. package/dist/canvas/pickIndex/nodeBroadBox.js +86 -0
  123. package/dist/canvas/pickIndex/nodeBroadBox.js.map +1 -0
  124. package/dist/canvas/pickIndex/scenePickIndex.js +208 -0
  125. package/dist/canvas/pickIndex/scenePickIndex.js.map +1 -0
  126. package/dist/canvas/pickReach.js +3 -3
  127. package/dist/canvas/pickReach.js.map +1 -1
  128. package/dist/canvas/pickWalk.js +17 -91
  129. package/dist/canvas/pickWalk.js.map +1 -1
  130. package/dist/canvas/poseCascade.js +1 -1
  131. package/dist/canvas/poseCascade.js.map +1 -1
  132. package/dist/canvas/publishPreviews.js +97 -0
  133. package/dist/canvas/publishPreviews.js.map +1 -0
  134. package/dist/canvas/renderSceneToPixels.js +1 -1
  135. package/dist/canvas/renderSceneToPixels.js.map +1 -1
  136. package/dist/canvas/sceneAdapter.js +7 -5
  137. package/dist/canvas/sceneAdapter.js.map +1 -1
  138. package/dist/canvas/sceneClipboard.js +17 -10
  139. package/dist/canvas/sceneClipboard.js.map +1 -1
  140. package/dist/canvas/scenePickReads.js +37 -0
  141. package/dist/canvas/scenePickReads.js.map +1 -0
  142. package/dist/canvas/scenePickSource.js +74 -0
  143. package/dist/canvas/scenePickSource.js.map +1 -0
  144. package/dist/canvas/sceneSlotWiring.js +9 -3
  145. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  146. package/dist/canvas/sceneViewRender.js +9 -4
  147. package/dist/canvas/sceneViewRender.js.map +1 -1
  148. package/dist/canvas/shownNodes.js +15 -0
  149. package/dist/canvas/shownNodes.js.map +1 -0
  150. package/dist/canvas/surfaceContribution.js +12 -8
  151. package/dist/canvas/surfaceContribution.js.map +1 -1
  152. package/dist/canvas/useCanvasView.js +4 -2
  153. package/dist/canvas/useCanvasView.js.map +1 -1
  154. package/dist/canvas/useLayerStack.js +6 -2
  155. package/dist/canvas/useLayerStack.js.map +1 -1
  156. package/dist/canvas/useSceneAdapter.js +5 -3
  157. package/dist/canvas/useSceneAdapter.js.map +1 -1
  158. package/dist/canvas/useViewAim.js +121 -0
  159. package/dist/canvas/useViewAim.js.map +1 -0
  160. package/dist/canvas/useViewNesting.js +3 -2
  161. package/dist/canvas/useViewNesting.js.map +1 -1
  162. package/dist/canvas/useViewRegistrySurface.js +4 -2
  163. package/dist/canvas/useViewRegistrySurface.js.map +1 -1
  164. package/dist/canvas/viewMarkers.js +63 -0
  165. package/dist/canvas/viewMarkers.js.map +1 -0
  166. package/dist/canvas/viewRegistry.js +27 -1
  167. package/dist/canvas/viewRegistry.js.map +1 -1
  168. package/dist/clipboard.d.ts +2 -2
  169. package/dist/{clone-C3e0OTuF.d.ts → clone-qb2e1F5p.d.ts} +2 -2
  170. package/dist/clone.d.ts +3 -3
  171. package/dist/core/isPlainObject.js +2 -2
  172. package/dist/core/layers/layerGroup.js.map +1 -1
  173. package/dist/core/pages/pageEdits.js +2 -3
  174. package/dist/core/pages/pageEdits.js.map +1 -1
  175. package/dist/core/paint/meshKindLazy.js +59 -2
  176. package/dist/core/paint/meshKindLazy.js.map +1 -1
  177. package/dist/core/scene/NodeRouting.js.map +1 -1
  178. package/dist/core/scene/arrivalWindow.js +1 -1
  179. package/dist/core/scene/arrivalWindow.js.map +1 -1
  180. package/dist/core/scene/clipNothing.js +10 -0
  181. package/dist/core/scene/clipNothing.js.map +1 -0
  182. package/dist/core/scene/declaredFieldSetters.js +44 -0
  183. package/dist/core/scene/declaredFieldSetters.js.map +1 -0
  184. package/dist/core/scene/depMemo.js +14 -3
  185. package/dist/core/scene/depMemo.js.map +1 -1
  186. package/dist/core/scene/dependents.js +9 -1
  187. package/dist/core/scene/dependents.js.map +1 -1
  188. package/dist/core/scene/derivedParts.js +10 -11
  189. package/dist/core/scene/derivedParts.js.map +1 -1
  190. package/dist/core/scene/documentOf.js +43 -0
  191. package/dist/core/scene/documentOf.js.map +1 -0
  192. package/dist/core/scene/effectivePose.js +44 -15
  193. package/dist/core/scene/effectivePose.js.map +1 -1
  194. package/dist/core/scene/fieldEdits.js +17 -14
  195. package/dist/core/scene/fieldEdits.js.map +1 -1
  196. package/dist/core/scene/fontReads.js +37 -0
  197. package/dist/core/scene/fontReads.js.map +1 -0
  198. package/dist/core/scene/historyForm.js +4 -4
  199. package/dist/core/scene/historyForm.js.map +1 -1
  200. package/dist/core/scene/inheritedPaint.js +97 -0
  201. package/dist/core/scene/inheritedPaint.js.map +1 -0
  202. package/dist/core/scene/kitOps.js +3 -23
  203. package/dist/core/scene/kitOps.js.map +1 -1
  204. package/dist/core/scene/kitRegistry.js +5 -0
  205. package/dist/core/scene/kitRegistry.js.map +1 -1
  206. package/dist/core/scene/layoutPass.js +1 -2
  207. package/dist/core/scene/layoutPass.js.map +1 -1
  208. package/dist/core/scene/nodeAsShown.js +72 -0
  209. package/dist/core/scene/nodeAsShown.js.map +1 -0
  210. package/dist/core/scene/nodeFnFields.js +2 -10
  211. package/dist/core/scene/nodeFnFields.js.map +1 -1
  212. package/dist/core/scene/nodeMemo.js +21 -8
  213. package/dist/core/scene/nodeMemo.js.map +1 -1
  214. package/dist/core/scene/nodeStore.js +2 -4
  215. package/dist/core/scene/nodeStore.js.map +1 -1
  216. package/dist/core/scene/pathEffects/blend.js.map +1 -1
  217. package/dist/core/scene/pathEffects/derive.js +10 -4
  218. package/dist/core/scene/pathEffects/derive.js.map +1 -1
  219. package/dist/core/scene/pathEffects/effects.js +3 -1
  220. package/dist/core/scene/pathEffects/effects.js.map +1 -1
  221. package/dist/core/scene/pathEffects/foundHidden.js +35 -0
  222. package/dist/core/scene/pathEffects/foundHidden.js.map +1 -0
  223. package/dist/core/scene/pathEffects/textAlong.js +95 -0
  224. package/dist/core/scene/pathEffects/textAlong.js.map +1 -0
  225. package/dist/core/scene/plyStacks.js +89 -0
  226. package/dist/core/scene/plyStacks.js.map +1 -0
  227. package/dist/core/scene/poseHold.js +42 -24
  228. package/dist/core/scene/poseHold.js.map +1 -1
  229. package/dist/core/scene/poseOverrides.js +35 -38
  230. package/dist/core/scene/poseOverrides.js.map +1 -1
  231. package/dist/core/scene/posePlies.js +59 -0
  232. package/dist/core/scene/posePlies.js.map +1 -0
  233. package/dist/core/scene/reconcileSpecs.js +6 -15
  234. package/dist/core/scene/reconcileSpecs.js.map +1 -1
  235. package/dist/core/scene/renderOrder.js +10 -4
  236. package/dist/core/scene/renderOrder.js.map +1 -1
  237. package/dist/core/scene/sameValue.js +17 -0
  238. package/dist/core/scene/sameValue.js.map +1 -0
  239. package/dist/core/scene/scene.js +3 -3
  240. package/dist/core/scene/scene.js.map +1 -1
  241. package/dist/core/scene/sceneEdits.js +8 -0
  242. package/dist/core/scene/sceneEdits.js.map +1 -1
  243. package/dist/core/scene/sceneState.js +1 -2
  244. package/dist/core/scene/sceneState.js.map +1 -1
  245. package/dist/core/scene/serialization.js +45 -9
  246. package/dist/core/scene/serialization.js.map +1 -1
  247. package/dist/core/scene/staleReplay.js +5 -2
  248. package/dist/core/scene/staleReplay.js.map +1 -1
  249. package/dist/core/scene/statedFields.js +4 -2
  250. package/dist/core/scene/statedFields.js.map +1 -1
  251. package/dist/core/scene/treeEdits.js +2 -4
  252. package/dist/core/scene/treeEdits.js.map +1 -1
  253. package/dist/core/scene/treeOrder.js +3 -1
  254. package/dist/core/scene/treeOrder.js.map +1 -1
  255. package/dist/core/scene/treeReads.js +1 -2
  256. package/dist/core/scene/treeReads.js.map +1 -1
  257. package/dist/core/scene/treeShape.js +13 -0
  258. package/dist/core/scene/treeShape.js.map +1 -0
  259. package/dist/core/scene/types.js.map +1 -1
  260. package/dist/core/scene/useScene.js +1 -1
  261. package/dist/core/scene/useScene.js.map +1 -1
  262. package/dist/{cornerHandles-BHuoMs1o.d.ts → cornerHandles-CNCb7aTY.d.ts} +3 -2
  263. package/dist/{depSchema-BpZNOv7q.d.ts → depSchema-B1RS-IJx.d.ts} +39 -18
  264. package/dist/devtools/handlers.js +1 -1
  265. package/dist/devtools/handlers.js.map +1 -1
  266. package/dist/devtools/protocol.js.map +1 -1
  267. package/dist/devtools/registry.js +4 -4
  268. package/dist/devtools/registry.js.map +1 -1
  269. package/dist/devtools-protocol.d.ts +4 -3
  270. package/dist/devtools.d.ts +3 -2
  271. package/dist/features/measure/measureContribution.js +4 -2
  272. package/dist/features/measure/measureContribution.js.map +1 -1
  273. package/dist/features/measure/measureHit.js +5 -3
  274. package/dist/features/measure/measureHit.js.map +1 -1
  275. package/dist/features/measure/measurement.js +1 -1
  276. package/dist/features/measure/measurement.js.map +1 -1
  277. package/dist/features/modes/modeLayers.js +14 -7
  278. package/dist/features/modes/modeLayers.js.map +1 -1
  279. package/dist/features/modes/modeTriggers.js +10 -6
  280. package/dist/features/modes/modeTriggers.js.map +1 -1
  281. package/dist/features/pathEffects/effectSources.js +69 -0
  282. package/dist/features/pathEffects/effectSources.js.map +1 -0
  283. package/dist/features/pathEffects/readSource.js +18 -2
  284. package/dist/features/pathEffects/readSource.js.map +1 -1
  285. package/dist/features/pathEffects/textItems.js +53 -0
  286. package/dist/features/pathEffects/textItems.js.map +1 -0
  287. package/dist/features/pathEffects/useEffectSourcesLayer.js +53 -0
  288. package/dist/features/pathEffects/useEffectSourcesLayer.js.map +1 -0
  289. package/dist/features/paths/editablePath.js +1 -1
  290. package/dist/features/paths/editablePath.js.map +1 -1
  291. package/dist/features/paths/pathEditState.js +5 -2
  292. package/dist/features/paths/pathEditState.js.map +1 -1
  293. package/dist/features/paths/pathSnap/sceneIndex.js +1 -1
  294. package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
  295. package/dist/features/selection/enteredGroupFrame.js +65 -0
  296. package/dist/features/selection/enteredGroupFrame.js.map +1 -0
  297. package/dist/features/selection/enteredGroupScope.js +44 -0
  298. package/dist/features/selection/enteredGroupScope.js.map +1 -0
  299. package/dist/features/text/sceneTextDrafts.js +64 -0
  300. package/dist/features/text/sceneTextDrafts.js.map +1 -0
  301. package/dist/features/text/textHitAt.js +34 -3
  302. package/dist/features/text/textHitAt.js.map +1 -1
  303. package/dist/features/text/useSceneTextEdit.js +23 -19
  304. package/dist/features/text/useSceneTextEdit.js.map +1 -1
  305. package/dist/features/viewports/aim.js +105 -0
  306. package/dist/features/viewports/aim.js.map +1 -0
  307. package/dist/features/viewports/viewMarker.js +82 -0
  308. package/dist/features/viewports/viewMarker.js.map +1 -0
  309. package/dist/features/viewports/viewMarkerDrag.js +55 -0
  310. package/dist/features/viewports/viewMarkerDrag.js.map +1 -0
  311. package/dist/index.d.ts +709 -258
  312. package/dist/index.js +33 -12
  313. package/dist/interactions/actions/admitsChild.js +11 -0
  314. package/dist/interactions/actions/admitsChild.js.map +1 -0
  315. package/dist/interactions/actions/booleans/booleans.js +2 -1
  316. package/dist/interactions/actions/booleans/booleans.js.map +1 -1
  317. package/dist/interactions/actions/booleans/operands.js +6 -5
  318. package/dist/interactions/actions/booleans/operands.js.map +1 -1
  319. package/dist/interactions/actions/clipboard/action.js +27 -17
  320. package/dist/interactions/actions/clipboard/action.js.map +1 -1
  321. package/dist/interactions/actions/defaults/anchorEditing.js +44 -23
  322. package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
  323. package/dist/interactions/actions/defaults/anchorJoin.js +12 -6
  324. package/dist/interactions/actions/defaults/anchorJoin.js.map +1 -1
  325. package/dist/interactions/actions/defaults/bendSegment.js +7 -4
  326. package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
  327. package/dist/interactions/actions/defaults/cancelGesture.js +11 -8
  328. package/dist/interactions/actions/defaults/cancelGesture.js.map +1 -1
  329. package/dist/interactions/actions/defaults/contextMenu.js +33 -11
  330. package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
  331. package/dist/interactions/actions/defaults/createPaintAction.js +5 -2
  332. package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
  333. package/dist/interactions/actions/defaults/delete.js +28 -14
  334. package/dist/interactions/actions/defaults/delete.js.map +1 -1
  335. package/dist/interactions/actions/defaults/duplicate.js +11 -8
  336. package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
  337. package/dist/interactions/actions/defaults/enterGroup.js +88 -0
  338. package/dist/interactions/actions/defaults/enterGroup.js.map +1 -0
  339. package/dist/interactions/actions/defaults/enterPathEdit.js +20 -10
  340. package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
  341. package/dist/interactions/actions/defaults/enterTextEdit.js +1 -1
  342. package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
  343. package/dist/interactions/actions/defaults/escape.js +11 -8
  344. package/dist/interactions/actions/defaults/escape.js.map +1 -1
  345. package/dist/interactions/actions/defaults/exitPathEdit.js +7 -4
  346. package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
  347. package/dist/interactions/actions/defaults/group.js +26 -20
  348. package/dist/interactions/actions/defaults/group.js.map +1 -1
  349. package/dist/interactions/actions/defaults/ingest.js +10 -4
  350. package/dist/interactions/actions/defaults/ingest.js.map +1 -1
  351. package/dist/interactions/actions/defaults/insertPathAnchor.js +7 -4
  352. package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
  353. package/dist/interactions/actions/defaults/nudge.js +8 -2
  354. package/dist/interactions/actions/defaults/nudge.js.map +1 -1
  355. package/dist/interactions/actions/defaults/pathEditTools.js +11 -8
  356. package/dist/interactions/actions/defaults/pathEditTools.js.map +1 -1
  357. package/dist/interactions/actions/defaults/pinchZoom.js +7 -4
  358. package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
  359. package/dist/interactions/actions/defaults/reorder.js +24 -6
  360. package/dist/interactions/actions/defaults/reorder.js.map +1 -1
  361. package/dist/interactions/actions/defaults/selectAll.js +15 -8
  362. package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
  363. package/dist/interactions/actions/defaults/selectDeep.js +35 -0
  364. package/dist/interactions/actions/defaults/selectDeep.js.map +1 -0
  365. package/dist/interactions/actions/defaults/setFill.js +7 -3
  366. package/dist/interactions/actions/defaults/setFill.js.map +1 -1
  367. package/dist/interactions/actions/defaults/setFillOpacity.js +1 -1
  368. package/dist/interactions/actions/defaults/setStroke.js +12 -11
  369. package/dist/interactions/actions/defaults/setStroke.js.map +1 -1
  370. package/dist/interactions/actions/defaults/setStrokeOpacity.js +1 -1
  371. package/dist/interactions/actions/defaults/toolActivate.js +7 -1
  372. package/dist/interactions/actions/defaults/toolActivate.js.map +1 -1
  373. package/dist/interactions/actions/defaults/toolResetToDefault.js +7 -4
  374. package/dist/interactions/actions/defaults/toolResetToDefault.js.map +1 -1
  375. package/dist/interactions/actions/defaults/undoRedo.js +19 -13
  376. package/dist/interactions/actions/defaults/undoRedo.js.map +1 -1
  377. package/dist/interactions/actions/defaults/viewportWheelPan.js +16 -7
  378. package/dist/interactions/actions/defaults/viewportWheelPan.js.map +1 -1
  379. package/dist/interactions/actions/defaults/viewportZoom.js +24 -6
  380. package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
  381. package/dist/interactions/actions/drawnBoxes.js +2 -1
  382. package/dist/interactions/actions/drawnBoxes.js.map +1 -1
  383. package/dist/interactions/actions/edit-anchors/action.js +7 -4
  384. package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
  385. package/dist/interactions/actions/flip/action.js +8 -2
  386. package/dist/interactions/actions/flip/action.js.map +1 -1
  387. package/dist/interactions/actions/insert/action.js +1 -0
  388. package/dist/interactions/actions/insert/action.js.map +1 -1
  389. package/dist/interactions/actions/insert/parent.js +7 -5
  390. package/dist/interactions/actions/insert/parent.js.map +1 -1
  391. package/dist/interactions/actions/insert/placeAction.js +3 -2
  392. package/dist/interactions/actions/insert/placeAction.js.map +1 -1
  393. package/dist/interactions/actions/insert/repositionAction.js +2 -1
  394. package/dist/interactions/actions/insert/repositionAction.js.map +1 -1
  395. package/dist/interactions/actions/insert/session.js +2 -2
  396. package/dist/interactions/actions/insert/session.js.map +1 -1
  397. package/dist/interactions/actions/move/action.js +7 -4
  398. package/dist/interactions/actions/move/action.js.map +1 -1
  399. package/dist/interactions/actions/move/commit/reparent.js +1 -1
  400. package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
  401. package/dist/interactions/actions/move/gestureAdapter.js +6 -7
  402. package/dist/interactions/actions/move/gestureAdapter.js.map +1 -1
  403. package/dist/interactions/actions/move/layout/exits.js +1 -1
  404. package/dist/interactions/actions/move/layout/exits.js.map +1 -1
  405. package/dist/interactions/actions/move/session.js +4 -4
  406. package/dist/interactions/actions/move/session.js.map +1 -1
  407. package/dist/interactions/actions/outlines/createOutlinesAction.js +8 -5
  408. package/dist/interactions/actions/outlines/createOutlinesAction.js.map +1 -1
  409. package/dist/interactions/actions/outlines/outlineStroke.js +5 -3
  410. package/dist/interactions/actions/outlines/outlineStroke.js.map +1 -1
  411. package/dist/interactions/actions/pathCommands/cutJoin.js +8 -5
  412. package/dist/interactions/actions/pathCommands/cutJoin.js.map +1 -1
  413. package/dist/interactions/actions/pathCommands/pathNodes.js +14 -5
  414. package/dist/interactions/actions/pathCommands/pathNodes.js.map +1 -1
  415. package/dist/interactions/actions/resize/cornerHandles.js +2 -1
  416. package/dist/interactions/actions/resize/cornerHandles.js.map +1 -1
  417. package/dist/interactions/actions/selectionSubtree.js +12 -7
  418. package/dist/interactions/actions/selectionSubtree.js.map +1 -1
  419. package/dist/interactions/actions/transform-again/action.js +8 -5
  420. package/dist/interactions/actions/transform-again/action.js.map +1 -1
  421. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js +14 -2
  422. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js.map +1 -1
  423. package/dist/json.d.ts +4 -0
  424. package/dist/json.js +3 -0
  425. package/dist/kitSettingsSchema.js +8 -1
  426. package/dist/kitSettingsSchema.js.map +1 -1
  427. package/dist/{math-CrJULaZI.d.ts → math-C_elShP_.d.ts} +7 -1
  428. package/dist/math.d.ts +1 -1
  429. package/dist/math.js +2 -1
  430. package/dist/move.d.ts +4 -4
  431. package/dist/{options-DBxEryYq.d.ts → options-ClEm3SEl.d.ts} +1 -1
  432. package/dist/resize.d.ts +4 -4
  433. package/dist/test-seams.d.ts +6 -6
  434. package/dist/tools/builtin/ellipse/useEllipseTool.js +1 -0
  435. package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
  436. package/dist/tools/builtin/eyedropper/useEyedropperTool.js +2 -1
  437. package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
  438. package/dist/tools/builtin/hand/dragPanContribution.js +4 -1
  439. package/dist/tools/builtin/hand/dragPanContribution.js.map +1 -1
  440. package/dist/tools/builtin/hand/useHandTool.js +7 -4
  441. package/dist/tools/builtin/hand/useHandTool.js.map +1 -1
  442. package/dist/tools/builtin/image/useImageTool.js +1 -0
  443. package/dist/tools/builtin/image/useImageTool.js.map +1 -1
  444. package/dist/tools/builtin/lasso/useLassoTool.js +1 -0
  445. package/dist/tools/builtin/lasso/useLassoTool.js.map +1 -1
  446. package/dist/tools/builtin/line/useLineTool.js +1 -0
  447. package/dist/tools/builtin/line/useLineTool.js.map +1 -1
  448. package/dist/tools/builtin/measure/useMeasureTool.js +12 -5
  449. package/dist/tools/builtin/measure/useMeasureTool.js.map +1 -1
  450. package/dist/tools/builtin/pen/usePenTool.js +14 -6
  451. package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
  452. package/dist/tools/builtin/pencil/usePencilTool.js +1 -0
  453. package/dist/tools/builtin/pencil/usePencilTool.js.map +1 -1
  454. package/dist/tools/builtin/polygon/usePolygonTool.js +13 -4
  455. package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
  456. package/dist/tools/builtin/pressTarget.js +22 -0
  457. package/dist/tools/builtin/pressTarget.js.map +1 -0
  458. package/dist/tools/builtin/rect/useRectTool.js +1 -0
  459. package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
  460. package/dist/tools/builtin/select/adapterPointQuery.js +6 -5
  461. package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
  462. package/dist/tools/builtin/select/collapseDeferred.js +40 -0
  463. package/dist/tools/builtin/select/collapseDeferred.js.map +1 -0
  464. package/dist/tools/builtin/select/groupEntry.js +82 -0
  465. package/dist/tools/builtin/select/groupEntry.js.map +1 -0
  466. package/dist/tools/builtin/select/pressDecision.js +32 -0
  467. package/dist/tools/builtin/select/pressDecision.js.map +1 -0
  468. package/dist/tools/builtin/select/selectionContributions.js +22 -13
  469. package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
  470. package/dist/tools/builtin/select/useSelectTool.js +74 -55
  471. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  472. package/dist/tools/builtin/slice/useSliceTool.js +22 -8
  473. package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
  474. package/dist/tools/builtin/star/useStarTool.js +13 -4
  475. package/dist/tools/builtin/star/useStarTool.js.map +1 -1
  476. package/dist/tools/builtin/sticky/stickyNode.js +4 -1
  477. package/dist/tools/builtin/sticky/stickyNode.js.map +1 -1
  478. package/dist/tools/builtin/sticky/useStickyTool.js +10 -5
  479. package/dist/tools/builtin/sticky/useStickyTool.js.map +1 -1
  480. package/dist/tools/builtin/text/useTextTool.js +3 -1
  481. package/dist/tools/builtin/text/useTextTool.js.map +1 -1
  482. package/dist/{types-7puAuVD2.d.ts → types-49uYJN3j.d.ts} +1 -1
  483. package/dist/{types-CniVctmk.d.ts → types-BEdRIhNc.d.ts} +272 -109
  484. package/dist/{types-oFeQ5VjV.d.ts → types-p2alu-R1.d.ts} +1 -1
  485. package/dist/util/paint.js +1 -3
  486. package/dist/util/paint.js.map +1 -1
  487. package/dist/version.js +1 -1
  488. package/package.json +29 -25
@@ -0,0 +1,86 @@
1
+ import { framedBounds, poseDescriptorForNode } from "../../core/geometry/poseDescriptor.js";
2
+ import { AUTO_POSE_DESCRIPTOR, isPathLike, isRectPose } from "../../core/geometry/autoPoseDescriptor.js";
3
+ import { findShapeInk } from "../NodeShape.js";
4
+ import { regionBoundsOf } from "../../core/geometry/regionHit.js";
5
+ import { applyToPoint, boundsOfPath, poseFrameOf } from "@weasel-js/geom";
6
+
7
+ //#region src/canvas/pickIndex/nodeBroadBox.ts
8
+ /**
9
+ * One node's box in the pick index: a world box that every query's `hits`
10
+ * accepts only inside of, before that query's own screen-pixel slop.
11
+ *
12
+ * The point query and the region query read a node differently, so the box is
13
+ * the union of both: the region's `regionBoundsOf` through the node's own
14
+ * descriptor, and the frame box the point query's `poseContainsRotated` tests,
15
+ * grown in the pose's own frame by the ink's `outset`. A derived node is read
16
+ * by its path alone, as both queries read it.
17
+ */
18
+ function grow(out, b, by) {
19
+ const x1 = b.x + b.width, y1 = b.y + b.height;
20
+ out[0] = Math.min(out[0], Math.min(b.x, x1) - by);
21
+ out[1] = Math.min(out[1], Math.min(b.y, y1) - by);
22
+ out[2] = Math.max(out[2], Math.max(b.x, x1) + by);
23
+ out[3] = Math.max(out[3], Math.max(b.y, y1) + by);
24
+ }
25
+ /** The local box `poseContainsRotated` tests, grown by `by` and carried through
26
+ * the pose's frame, as that function reads it. */
27
+ function framedBox(out, pose, d, by) {
28
+ const local = isPathLike(pose) ? boundsOfPath(pose) : d.getBounds(pose);
29
+ const framed = d === AUTO_POSE_DESCRIPTOR ? pose : framedBounds(pose, d);
30
+ const frame = poseFrameOf(framed);
31
+ if (!frame) {
32
+ grow(out, local, by);
33
+ return;
34
+ }
35
+ const x0 = local.x - by, y0 = local.y - by;
36
+ const x1 = local.x + local.width + by, y1 = local.y + local.height + by;
37
+ for (const [x, y] of [
38
+ [x0, y0],
39
+ [x1, y0],
40
+ [x1, y1],
41
+ [x0, y1]
42
+ ]) {
43
+ const [wx, wy] = applyToPoint(frame, x, y);
44
+ grow(out, {
45
+ x: wx,
46
+ y: wy,
47
+ width: 0,
48
+ height: 0
49
+ }, 0);
50
+ }
51
+ }
52
+ /** Float error in a frame's round trip must not put an accepted point just outside. */
53
+ function pad(b) {
54
+ const e = 1e-9 * (1 + Math.max(Math.abs(b[0]), Math.abs(b[1]), Math.abs(b[2]), Math.abs(b[3])));
55
+ b[0] -= e;
56
+ b[1] -= e;
57
+ b[2] += e;
58
+ b[3] += e;
59
+ }
60
+ var UNINHERITED = Object.freeze({ scale: 1 });
61
+ /** Writes the box into `out` and returns the ink's reach measured on screen,
62
+ * which no world box can hold. */
63
+ function nodeBroadBox(node, pose, derived, inherited, descriptor, out) {
64
+ const ink = findShapeInk(node, pose, inherited === void 0 ? UNINHERITED : {
65
+ scale: 1,
66
+ inherited
67
+ });
68
+ const outset = Math.max(0, ink?.outset ?? 0);
69
+ out[0] = out[1] = Infinity;
70
+ out[2] = out[3] = -Infinity;
71
+ if (derived) grow(out, boundsOfPath(derived), outset);
72
+ else {
73
+ const own = poseDescriptorForNode(descriptor, node);
74
+ if (descriptor === AUTO_POSE_DESCRIPTOR && own === descriptor && isRectPose(pose) && poseFrameOf(pose) === null) grow(out, pose, outset);
75
+ else {
76
+ grow(out, regionBoundsOf(node, pose, own), outset);
77
+ framedBox(out, pose, descriptor, outset);
78
+ }
79
+ }
80
+ pad(out);
81
+ return Math.max(0, ink?.outsetPx ?? 0);
82
+ }
83
+
84
+ //#endregion
85
+ export { nodeBroadBox };
86
+ //# sourceMappingURL=nodeBroadBox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodeBroadBox.js","names":[],"sources":["../../../src/canvas/pickIndex/nodeBroadBox.ts"],"sourcesContent":["/**\n * One node's box in the pick index: a world box that every query's `hits`\n * accepts only inside of, before that query's own screen-pixel slop.\n *\n * The point query and the region query read a node differently, so the box is\n * the union of both: the region's `regionBoundsOf` through the node's own\n * descriptor, and the frame box the point query's `poseContainsRotated` tests,\n * grown in the pose's own frame by the ink's `outset`. A derived node is read\n * by its path alone, as both queries read it.\n */\nimport { applyToPoint, boundsOfPath, poseFrameOf, type Path } from '@weasel-js/geom';\nimport { findShapeInk } from 'canvas/NodeShape';\nimport { AUTO_POSE_DESCRIPTOR, isPathLike, isRectPose } from 'core/geometry/autoPoseDescriptor';\nimport { framedBounds } from 'core/geometry/poseDescriptor';\nimport { regionBoundsOf } from 'core/geometry/regionHit';\nimport type { InheritedPaint } from 'core/scene/inheritedPaint';\nimport { poseDescriptorForNode, type PoseDescriptor } from 'interactions/actions/resize/geometry';\n\n/** `[minX, minY, maxX, maxY]`, the layout `createBvh(2)` takes. */\nexport type Box2 = [number, number, number, number];\n\ntype Rect = { x: number; y: number; width: number; height: number };\n\nfunction grow(out: Box2, b: Rect, by: number): void {\n const x1 = b.x + b.width, y1 = b.y + b.height;\n out[0] = Math.min(out[0], Math.min(b.x, x1) - by);\n out[1] = Math.min(out[1], Math.min(b.y, y1) - by);\n out[2] = Math.max(out[2], Math.max(b.x, x1) + by);\n out[3] = Math.max(out[3], Math.max(b.y, y1) + by);\n}\n\n/** The local box `poseContainsRotated` tests, grown by `by` and carried through\n * the pose's frame, as that function reads it. */\nfunction framedBox(out: Box2, pose: unknown, d: PoseDescriptor<unknown>, by: number): void {\n const local: Rect = isPathLike(pose) ? boundsOfPath(pose) : d.getBounds(pose);\n const framed = d === AUTO_POSE_DESCRIPTOR ? pose : framedBounds(pose, d);\n const frame = poseFrameOf(framed);\n if (!frame) {\n grow(out, local, by);\n return;\n }\n const x0 = local.x - by, y0 = local.y - by;\n const x1 = local.x + local.width + by, y1 = local.y + local.height + by;\n for (const [x, y] of [[x0, y0], [x1, y0], [x1, y1], [x0, y1]] as const) {\n const [wx, wy] = applyToPoint(frame, x, y);\n grow(out, { x: wx, y: wy, width: 0, height: 0 }, 0);\n }\n}\n\n/** Float error in a frame's round trip must not put an accepted point just outside. */\nfunction pad(b: Box2): void {\n const e = 1e-9 * (1 + Math.max(Math.abs(b[0]), Math.abs(b[1]), Math.abs(b[2]), Math.abs(b[3])));\n b[0] -= e; b[1] -= e; b[2] += e; b[3] += e;\n}\n\nconst UNINHERITED = Object.freeze({ scale: 1 });\n\n/** Writes the box into `out` and returns the ink's reach measured on screen,\n * which no world box can hold. */\nexport function nodeBroadBox(\n node: object,\n pose: unknown,\n derived: Path | null | undefined,\n inherited: InheritedPaint | undefined,\n descriptor: PoseDescriptor<unknown>,\n out: Box2,\n): number {\n const ink = findShapeInk(node as never, pose, inherited === undefined ? UNINHERITED : { scale: 1, inherited });\n const outset = Math.max(0, ink?.outset ?? 0);\n out[0] = out[1] = Infinity;\n out[2] = out[3] = -Infinity;\n if (derived) {\n grow(out, boundsOfPath(derived), outset);\n } else {\n const own = poseDescriptorForNode(descriptor, node);\n if (descriptor === AUTO_POSE_DESCRIPTOR && own === descriptor && isRectPose(pose) && poseFrameOf(pose) === null) {\n // Both reads come to the pose rect itself.\n grow(out, pose, outset);\n } else {\n grow(out, regionBoundsOf(node, pose, own), outset);\n framedBox(out, pose, descriptor, outset);\n }\n }\n pad(out);\n return Math.max(0, ink?.outsetPx ?? 0);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuBA,SAAS,KAAK,KAAW,GAAS,IAAkB;CAClD,MAAM,KAAK,EAAE,IAAI,EAAE,OAAO,KAAK,EAAE,IAAI,EAAE;CACvC,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE;CAChD,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE;CAChD,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE;CAChD,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE;AAClD;;;AAIA,SAAS,UAAU,KAAW,MAAe,GAA4B,IAAkB;CACzF,MAAM,QAAc,WAAW,IAAI,IAAI,aAAa,IAAI,IAAI,EAAE,UAAU,IAAI;CAC5E,MAAM,SAAS,MAAM,uBAAuB,OAAO,aAAa,MAAM,CAAC;CACvE,MAAM,QAAQ,YAAY,MAAM;CAChC,IAAI,CAAC,OAAO;EACV,KAAK,KAAK,OAAO,EAAE;EACnB;CACF;CACA,MAAM,KAAK,MAAM,IAAI,IAAI,KAAK,MAAM,IAAI;CACxC,MAAM,KAAK,MAAM,IAAI,MAAM,QAAQ,IAAI,KAAK,MAAM,IAAI,MAAM,SAAS;CACrE,KAAK,MAAM,CAAC,GAAG,MAAM;EAAC,CAAC,IAAI,EAAE;EAAG,CAAC,IAAI,EAAE;EAAG,CAAC,IAAI,EAAE;EAAG,CAAC,IAAI,EAAE;CAAC,GAAY;EACtE,MAAM,CAAC,IAAI,MAAM,aAAa,OAAO,GAAG,CAAC;EACzC,KAAK,KAAK;GAAE,GAAG;GAAI,GAAG;GAAI,OAAO;GAAG,QAAQ;EAAE,GAAG,CAAC;CACpD;AACF;;AAGA,SAAS,IAAI,GAAe;CAC1B,MAAM,IAAI,QAAQ,IAAI,KAAK,IAAI,KAAK,IAAI,EAAE,EAAE,GAAG,KAAK,IAAI,EAAE,EAAE,GAAG,KAAK,IAAI,EAAE,EAAE,GAAG,KAAK,IAAI,EAAE,EAAE,CAAC;CAC7F,EAAE,MAAM;CAAG,EAAE,MAAM;CAAG,EAAE,MAAM;CAAG,EAAE,MAAM;AAC3C;AAEA,IAAM,cAAc,OAAO,OAAO,EAAE,OAAO,EAAE,CAAC;;;AAI9C,SAAgB,aACd,MACA,MACA,SACA,WACA,YACA,KACQ;CACR,MAAM,MAAM,aAAa,MAAe,MAAM,cAAc,SAAY,cAAc;EAAE,OAAO;EAAG;CAAU,CAAC;CAC7G,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,UAAU,CAAC;CAC3C,IAAI,KAAK,IAAI,KAAK;CAClB,IAAI,KAAK,IAAI,KAAK;CAClB,IAAI,SACF,KAAK,KAAK,aAAa,OAAO,GAAG,MAAM;MAClC;EACL,MAAM,MAAM,sBAAsB,YAAY,IAAI;EAClD,IAAI,eAAe,wBAAwB,QAAQ,cAAc,WAAW,IAAI,KAAK,YAAY,IAAI,MAAM,MAEzG,KAAK,KAAK,MAAM,MAAM;OACjB;GACL,KAAK,KAAK,eAAe,MAAM,MAAM,GAAG,GAAG,MAAM;GACjD,UAAU,KAAK,MAAM,YAAY,MAAM;EACzC;CACF;CACA,IAAI,GAAG;CACP,OAAO,KAAK,IAAI,GAAG,KAAK,YAAY,CAAC;AACvC"}
@@ -0,0 +1,208 @@
1
+ import { nodeMemoGeneration } from "../../core/scene/nodeMemo.js";
2
+ import { inheritedPaintOf, inheritsPaint } from "../../core/scene/inheritedPaint.js";
3
+ import { nodesAsShown } from "../../core/scene/nodeAsShown.js";
4
+ import { getNodeShapes } from "../NodeShape.js";
5
+ import { scenePaintedPoseOf } from "../derivedPath.js";
6
+ import { sceneParentsOf, sceneShownPathOf } from "../scenePickReads.js";
7
+ import { nodeBroadBox } from "./nodeBroadBox.js";
8
+ import { markerGeneration } from "@weasel-js/stroke";
9
+ import { createBvh } from "@weasel-js/geom/nd";
10
+
11
+ //#region src/canvas/pickIndex/scenePickIndex.ts
12
+ /**
13
+ * The per-scene pick index: one BVH per scene layer over every node's broad
14
+ * box (`nodeBroadBox`), keyed by the node's place in the render order it was
15
+ * built from. A pick after anything it read can have changed walks the scene
16
+ * and only records that state; the next pick to find the scene unchanged
17
+ * builds, so a scene edited before every pick never pays for a build. Nodes it
18
+ * cannot box conservatively are on `always` and are tested by every walk.
19
+ */
20
+ var builds = 0;
21
+ var CACHE = /* @__PURE__ */ new WeakMap();
22
+ /** Past this share of the scene the index costs more than the walk it narrows. */
23
+ var MAX_SHARE = .5;
24
+ var BuiltIndex = class {
25
+ built;
26
+ trees;
27
+ always;
28
+ maxOutsetPx;
29
+ layers;
30
+ /** The render order the candidates are read out of. */
31
+ order;
32
+ /** Build position to position in `order`, or null while they are the same array. */
33
+ remap = null;
34
+ marks;
35
+ found;
36
+ constructor(built, trees, always, maxOutsetPx) {
37
+ this.built = built;
38
+ this.trees = trees;
39
+ this.always = always;
40
+ this.maxOutsetPx = maxOutsetPx;
41
+ this.layers = [...trees.keys()];
42
+ this.order = built;
43
+ this.marks = new Uint8Array(built.length);
44
+ this.found = new Int32Array(Math.max(16, always.length));
45
+ }
46
+ /** Follow a new render order array the scene hands back for the same state. */
47
+ rankBy(order) {
48
+ if (order === this.order) return;
49
+ this.order = order;
50
+ if (order === this.built) {
51
+ this.remap = null;
52
+ return;
53
+ }
54
+ const rank = /* @__PURE__ */ new Map();
55
+ for (let i = 0; i < order.length; i++) rank.set(order[i].id, i);
56
+ const remap = new Int32Array(this.built.length);
57
+ for (let i = 0; i < remap.length; i++) remap[i] = rank.get(this.built[i].id) ?? -1;
58
+ this.remap = remap;
59
+ }
60
+ /** The nodes the boxes reach, in render order, or null when that is most of the scene. */
61
+ candidates(broads) {
62
+ const limit = Math.floor(this.order.length * MAX_SHARE);
63
+ let count = 0;
64
+ const add = (built) => {
65
+ const at = this.remap === null ? built : this.remap[built];
66
+ if (at < 0) return true;
67
+ if (count === this.found.length) {
68
+ const next = new Int32Array(count * 2);
69
+ next.set(this.found);
70
+ this.found = next;
71
+ }
72
+ this.found[count++] = at;
73
+ return count <= limit;
74
+ };
75
+ for (const i of this.always) if (!add(i)) return null;
76
+ for (let l = 0; l < this.layers.length; l++) {
77
+ const broad = broads[l];
78
+ const tree = this.trees.get(this.layers[l]);
79
+ if (broad === null || tree === void 0) continue;
80
+ const { x, y, width, height } = broad.box;
81
+ const k = broad.pxPerWorld;
82
+ const by = this.maxOutsetPx > 0 && k !== void 0 ? k > 0 ? this.maxOutsetPx / k : Infinity : 0;
83
+ let over = false;
84
+ tree.queryBox([
85
+ x - by,
86
+ y - by,
87
+ x + width + by,
88
+ y + height + by
89
+ ], (i) => {
90
+ if (add(i)) return true;
91
+ over = true;
92
+ return false;
93
+ });
94
+ if (over) return null;
95
+ }
96
+ return this.readOut(count);
97
+ }
98
+ readOut(count) {
99
+ const { order, found } = this;
100
+ const out = new Array(count);
101
+ if (count * 32 < order.length) {
102
+ const ranks = found.subarray(0, count).sort();
103
+ for (let i = 0; i < count; i++) out[i] = order[ranks[i]];
104
+ return out;
105
+ }
106
+ const marks = this.marks.length >= order.length ? this.marks : new Uint8Array(order.length);
107
+ for (let i = 0; i < count; i++) marks[found[i]] = 1;
108
+ let n = 0;
109
+ for (let i = 0; n < count && i < order.length; i++) {
110
+ if (marks[i] === 0) continue;
111
+ marks[i] = 0;
112
+ out[n++] = order[i];
113
+ }
114
+ return out;
115
+ }
116
+ };
117
+ function hasCopyingAncestor(parents) {
118
+ return parents.some((p) => typeof p.copiesFromPose === "function");
119
+ }
120
+ function build(scene, descriptor, order) {
121
+ builds++;
122
+ const poseOf = scenePaintedPoseOf(scene);
123
+ const pathOf = sceneShownPathOf(scene);
124
+ const parentsOf = sceneParentsOf(scene);
125
+ const entries = /* @__PURE__ */ new Map();
126
+ const always = [];
127
+ let maxOutsetPx = 0;
128
+ for (let i = 0; i < order.length; i++) {
129
+ const node = order[i];
130
+ const parents = parentsOf(node);
131
+ if (hasCopyingAncestor(parents)) {
132
+ always.push(i);
133
+ continue;
134
+ }
135
+ const box = [
136
+ 0,
137
+ 0,
138
+ 0,
139
+ 0
140
+ ];
141
+ let outsetPx;
142
+ try {
143
+ const inherited = inheritsPaint(node) ? inheritedPaintOf(node, parents) : void 0;
144
+ outsetPx = nodeBroadBox(node, poseOf(node), pathOf(node), inherited, descriptor, box);
145
+ } catch {
146
+ always.push(i);
147
+ continue;
148
+ }
149
+ if (!Number.isFinite(outsetPx) || !box.every(Number.isFinite)) {
150
+ always.push(i);
151
+ continue;
152
+ }
153
+ if (outsetPx > maxOutsetPx) maxOutsetPx = outsetPx;
154
+ let list = entries.get(node.layer);
155
+ if (list === void 0) entries.set(node.layer, list = []);
156
+ list.push([i, box]);
157
+ }
158
+ const trees = /* @__PURE__ */ new Map();
159
+ for (const [layer, list] of entries) {
160
+ const tree = createBvh(2, list);
161
+ if (tree.size !== list.length) {
162
+ for (const [i, box] of list) if (!(box[0] <= box[2] && box[1] <= box[3])) always.push(i);
163
+ }
164
+ trees.set(layer, tree);
165
+ }
166
+ return new BuiltIndex(order, trees, Int32Array.from(always), maxOutsetPx);
167
+ }
168
+ function current(scene, descriptor) {
169
+ getNodeShapes();
170
+ const version = scene.getVersion();
171
+ const overrides = scene.overrides.getGeneration();
172
+ const memo = nodeMemoGeneration();
173
+ const markers = markerGeneration();
174
+ let perScene = CACHE.get(scene);
175
+ if (perScene === void 0) CACHE.set(scene, perScene = /* @__PURE__ */ new WeakMap());
176
+ const hit = perScene.get(descriptor);
177
+ const order = nodesAsShown(scene.overrides, scene.renderOrderNodes());
178
+ if (hit && hit.version === version && hit.overrides === overrides && hit.memo === memo && hit.markers === markers) {
179
+ if (hit.index === null) hit.index = build(scene, descriptor, order);
180
+ else hit.index.rankBy(order);
181
+ return hit.index;
182
+ }
183
+ perScene.set(descriptor, {
184
+ version,
185
+ overrides,
186
+ memo,
187
+ markers,
188
+ index: null
189
+ });
190
+ return null;
191
+ }
192
+ /** What `PickSource.candidates` answers for a scene source with an index. */
193
+ function indexedCandidates(scene, descriptor, broadFor) {
194
+ const index = current(scene, descriptor);
195
+ if (index === null) return null;
196
+ const broads = [];
197
+ for (const layer of index.layers) {
198
+ const broad = broadFor(layer);
199
+ if (broad === void 0) return null;
200
+ if (broad !== null && Number.isNaN(broad.box.x + broad.box.y + broad.box.width + broad.box.height)) return null;
201
+ broads.push(broad);
202
+ }
203
+ return index.candidates(broads);
204
+ }
205
+
206
+ //#endregion
207
+ export { indexedCandidates };
208
+ //# sourceMappingURL=scenePickIndex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scenePickIndex.js","names":[],"sources":["../../../src/canvas/pickIndex/scenePickIndex.ts"],"sourcesContent":["/**\n * The per-scene pick index: one BVH per scene layer over every node's broad\n * box (`nodeBroadBox`), keyed by the node's place in the render order it was\n * built from. A pick after anything it read can have changed walks the scene\n * and only records that state; the next pick to find the scene unchanged\n * builds, so a scene edited before every pick never pays for a build. Nodes it\n * cannot box conservatively are on `always` and are tested by every walk.\n */\nimport { createBvh, type Bvh } from '@weasel-js/geom/nd';\nimport { markerGeneration } from '@weasel-js/stroke';\nimport { getNodeShapes } from 'canvas/NodeShape';\nimport { inheritedPaintOf, inheritsPaint } from 'core/scene/inheritedPaint';\nimport { nodesAsShown } from 'core/scene/nodeAsShown';\nimport { nodeMemoGeneration } from 'core/scene/nodeMemo';\nimport type { Scene } from 'core/scene/types';\nimport type { PoseDescriptor } from 'interactions/actions/resize/geometry';\nimport { scenePaintedPoseOf } from '../derivedPath';\nimport type { PickBroad, PickCandidate } from '../pickWalk';\nimport { sceneParentsOf, sceneShownPathOf } from '../scenePickReads';\nimport { nodeBroadBox, type Box2 } from './nodeBroadBox';\n\ntype AnyScene = Scene<unknown, string, unknown>;\ntype Order = readonly PickCandidate<unknown>[];\n\nexport interface ScenePickIndex {\n /** The layers holding indexed nodes; `undefined` for nodes with none. */\n readonly layers: readonly (string | undefined)[];\n readonly maxOutsetPx: number;\n}\n\nlet builds = 0;\n\n/** How many indexes have been built, for tests that pin when one rebuilds. */\nexport function pickIndexBuildCount(): number {\n return builds;\n}\n\ninterface Cached {\n version: number;\n overrides: number;\n memo: number;\n markers: number;\n /** Null until a second pick finds the same state. */\n index: BuiltIndex | null;\n}\n\nconst CACHE = new WeakMap<object, WeakMap<PoseDescriptor<unknown>, Cached>>();\n\n/** Past this share of the scene the index costs more than the walk it narrows. */\nconst MAX_SHARE = 0.5;\n\nclass BuiltIndex implements ScenePickIndex {\n readonly layers: readonly (string | undefined)[];\n /** The render order the candidates are read out of. */\n private order: Order;\n /** Build position to position in `order`, or null while they are the same array. */\n private remap: Int32Array | null = null;\n private readonly marks: Uint8Array;\n private found: Int32Array;\n\n constructor(\n private readonly built: Order,\n private readonly trees: Map<string | undefined, Bvh<number>>,\n private readonly always: Int32Array,\n readonly maxOutsetPx: number,\n ) {\n this.layers = [...trees.keys()];\n this.order = built;\n this.marks = new Uint8Array(built.length);\n this.found = new Int32Array(Math.max(16, always.length));\n }\n\n /** Follow a new render order array the scene hands back for the same state. */\n rankBy(order: Order): void {\n if (order === this.order) return;\n this.order = order;\n if (order === this.built) {\n this.remap = null;\n return;\n }\n const rank = new Map<string, number>();\n for (let i = 0; i < order.length; i++) rank.set(order[i].id, i);\n const remap = new Int32Array(this.built.length);\n for (let i = 0; i < remap.length; i++) remap[i] = rank.get(this.built[i].id) ?? -1;\n this.remap = remap;\n }\n\n /** The nodes the boxes reach, in render order, or null when that is most of the scene. */\n candidates(broads: readonly (PickBroad | null)[]): Order | null {\n const limit = Math.floor(this.order.length * MAX_SHARE);\n let count = 0;\n const add = (built: number): boolean => {\n const at = this.remap === null ? built : this.remap[built];\n if (at < 0) return true;\n if (count === this.found.length) {\n const next = new Int32Array(count * 2);\n next.set(this.found);\n this.found = next;\n }\n this.found[count++] = at;\n return count <= limit;\n };\n for (const i of this.always) if (!add(i)) return null;\n for (let l = 0; l < this.layers.length; l++) {\n const broad = broads[l];\n const tree = this.trees.get(this.layers[l]);\n if (broad === null || tree === undefined) continue;\n const { x, y, width, height } = broad.box;\n const k = broad.pxPerWorld;\n const by = this.maxOutsetPx > 0 && k !== undefined ? (k > 0 ? this.maxOutsetPx / k : Infinity) : 0;\n let over = false;\n tree.queryBox([x - by, y - by, x + width + by, y + height + by], (i) => {\n if (add(i)) return true;\n over = true;\n return false;\n });\n if (over) return null;\n }\n return this.readOut(count);\n }\n\n private readOut(count: number): Order {\n const { order, found } = this;\n const out: PickCandidate<unknown>[] = new Array(count);\n // A handful sorts faster than a pass over every mark.\n if (count * 32 < order.length) {\n const ranks = found.subarray(0, count).sort();\n for (let i = 0; i < count; i++) out[i] = order[ranks[i]];\n return out;\n }\n const marks = this.marks.length >= order.length ? this.marks : new Uint8Array(order.length);\n for (let i = 0; i < count; i++) marks[found[i]] = 1;\n let n = 0;\n for (let i = 0; n < count && i < order.length; i++) {\n if (marks[i] === 0) continue;\n marks[i] = 0;\n out[n++] = order[i];\n }\n return out;\n }\n}\n\nfunction hasCopyingAncestor(parents: Order): boolean {\n return parents.some((p) => typeof p.copiesFromPose === 'function');\n}\n\nfunction build(scene: AnyScene, descriptor: PoseDescriptor<unknown>, order: Order): BuiltIndex {\n builds++;\n const poseOf = scenePaintedPoseOf(scene);\n const pathOf = sceneShownPathOf(scene);\n const parentsOf = sceneParentsOf(scene);\n const entries = new Map<string | undefined, [number, Box2][]>();\n const always: number[] = [];\n let maxOutsetPx = 0;\n for (let i = 0; i < order.length; i++) {\n const node = order[i];\n const parents = parentsOf(node);\n // Also hit wherever an ancestor paints a copy of it, which its own box does not cover.\n if (hasCopyingAncestor(parents)) {\n always.push(i);\n continue;\n }\n const box: Box2 = [0, 0, 0, 0];\n let outsetPx: number;\n try {\n const inherited = inheritsPaint(node) ? inheritedPaintOf(node, parents) : undefined;\n outsetPx = nodeBroadBox(node, poseOf(node as never), pathOf(node), inherited, descriptor, box);\n } catch {\n always.push(i);\n continue;\n }\n if (!Number.isFinite(outsetPx) || !box.every(Number.isFinite)) {\n always.push(i);\n continue;\n }\n if (outsetPx > maxOutsetPx) maxOutsetPx = outsetPx;\n let list = entries.get(node.layer);\n if (list === undefined) entries.set(node.layer, (list = []));\n list.push([i, box]);\n }\n const trees = new Map<string | undefined, Bvh<number>>();\n for (const [layer, list] of entries) {\n const tree = createBvh<number>(2, list);\n // The tree drops an inverted box: test that node the old way.\n if (tree.size !== list.length) {\n for (const [i, box] of list) if (!(box[0] <= box[2] && box[1] <= box[3])) always.push(i);\n }\n trees.set(layer, tree);\n }\n return new BuiltIndex(order, trees, Int32Array.from(always), maxOutsetPx);\n}\n\n/** The index for `scene` read through `descriptor`, or null when this pick walks instead. */\nexport function scenePickIndex(scene: AnyScene, descriptor: PoseDescriptor<unknown>): ScenePickIndex | null {\n return current(scene, descriptor);\n}\n\nfunction current(scene: AnyScene, descriptor: PoseDescriptor<unknown>): BuiltIndex | null {\n // Registers the built-in painters first, which bumps the memo generation.\n getNodeShapes();\n const version = scene.getVersion();\n const overrides = scene.overrides.getGeneration();\n const memo = nodeMemoGeneration();\n const markers = markerGeneration();\n let perScene = CACHE.get(scene);\n if (perScene === undefined) CACHE.set(scene, (perScene = new WeakMap()));\n const hit = perScene.get(descriptor);\n // Boxed as shown, so a data override that grows a node's ink is met where it is painted.\n const order = nodesAsShown(scene.overrides, scene.renderOrderNodes()) as unknown as Order;\n if (hit && hit.version === version && hit.overrides === overrides && hit.memo === memo && hit.markers === markers) {\n if (hit.index === null) hit.index = build(scene, descriptor, order);\n else hit.index.rankBy(order);\n return hit.index;\n }\n perScene.set(descriptor, { version, overrides, memo, markers, index: null });\n return null;\n}\n\n/** What `PickSource.candidates` answers for a scene source with an index. */\nexport function indexedCandidates(\n scene: AnyScene,\n descriptor: PoseDescriptor<unknown>,\n broadFor: (layer: string | undefined) => PickBroad | null | undefined,\n): Order | null {\n const index = current(scene, descriptor);\n if (index === null) return null;\n const broads: (PickBroad | null)[] = [];\n for (const layer of index.layers) {\n const broad = broadFor(layer);\n if (broad === undefined) return null;\n if (broad !== null && Number.isNaN(broad.box.x + broad.box.y + broad.box.width + broad.box.height)) return null;\n broads.push(broad);\n }\n return index.candidates(broads);\n}\n"],"mappings":"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"}
@@ -11,19 +11,19 @@
11
11
  * `copying` says some container in the walk draws copies, otherwise only
12
12
  * after the node hits as authored.
13
13
  */
14
- function reaches(node, pose, derived, q, parentsOf, copying, look) {
14
+ function reaches(node, pose, derived, inherited, q, parentsOf, copying, look) {
15
15
  const admits = (query, ancestor) => {
16
16
  const clip = look.clipOf(ancestor);
17
17
  return clip === null || query.clipAdmits(look.carry(clip, ancestor.layer, node.layer), node, pose, derived);
18
18
  };
19
19
  const parents = copying && q.inCopy !== void 0 ? parentsOf() : null;
20
20
  if (parents === null || !parents.some((a) => look.copiesOf(a).length > 0)) {
21
- if (!q.hits(node, pose, derived)) return false;
21
+ if (!q.hits(node, pose, derived, inherited)) return false;
22
22
  return (parents ?? parentsOf()).every((a) => admits(q, a));
23
23
  }
24
24
  const outermostFirst = [...parents].reverse();
25
25
  const from = (i, query) => {
26
- if (i === outermostFirst.length) return query.hits(node, pose, derived);
26
+ if (i === outermostFirst.length) return query.hits(node, pose, derived, inherited);
27
27
  const ancestor = outermostFirst[i];
28
28
  if (admits(query, ancestor) && from(i + 1, query)) return true;
29
29
  for (const m of look.copiesOf(ancestor)) {
@@ -1 +1 @@
1
- {"version":3,"file":"pickReach.js","names":[],"sources":["../../src/canvas/pickReach.ts"],"sourcesContent":["import type { Affine, Path } from '@weasel-js/geom';\nimport type { PickCandidate, PickQuery } from './pickWalk';\n\n/** What {@link reaches} needs from the walk, each memoized per walk there. */\nexport interface ReachLookups<TPose> {\n /** An ancestor's own clip, or null. */\n clipOf(ancestor: PickCandidate<TPose>): Path | null;\n /** The maps from world into the source frame of each copy `ancestor` draws:\n * the inverses of its `copiesFromPose`. Empty when it draws none. */\n copiesOf(ancestor: PickCandidate<TPose>): readonly Affine[];\n /** A clip carried from its container's plane into the node's. */\n carry(clip: Path, fromLayer: string | undefined, toLayer: string | undefined): Path;\n}\n\n/**\n * Whether `q` lands on `node` anywhere it is painted: as authored, or in any\n * copy an ancestor draws of it. `parents` is innermost first.\n *\n * A copy is the clipped content transformed, so the query is carried into the\n * copy's source frame before the copying container's own clip is asked, and\n * every clip below it is asked in that frame too.\n *\n * `parents` is called only when the answer needs it — always when\n * `copying` says some container in the walk draws copies, otherwise only\n * after the node hits as authored.\n */\nexport function reaches<TPose>(\n node: PickCandidate<TPose>,\n pose: TPose,\n derived: Path | null | undefined,\n q: PickQuery<TPose>,\n parentsOf: () => readonly PickCandidate<TPose>[],\n copying: boolean,\n look: ReachLookups<TPose>,\n): boolean {\n const admits = (query: PickQuery<TPose>, ancestor: PickCandidate<TPose>): boolean => {\n const clip = look.clipOf(ancestor);\n return clip === null\n || query.clipAdmits(look.carry(clip, ancestor.layer, node.layer), node, pose, derived);\n };\n\n const parents = copying && q.inCopy !== undefined ? parentsOf() : null;\n if (parents === null || !parents.some((a) => look.copiesOf(a).length > 0)) {\n if (!q.hits(node, pose, derived)) return false;\n return (parents ?? parentsOf()).every((a) => admits(q, a));\n }\n\n const outermostFirst = [...parents].reverse();\n const from = (i: number, query: PickQuery<TPose>): boolean => {\n if (i === outermostFirst.length) return query.hits(node, pose, derived);\n const ancestor = outermostFirst[i];\n if (admits(query, ancestor) && from(i + 1, query)) return true;\n for (const m of look.copiesOf(ancestor)) {\n const inCopy = query.inCopy?.(m);\n if (inCopy !== undefined && admits(inCopy, ancestor) && from(i + 1, inCopy)) return true;\n }\n return false;\n };\n return from(0, q);\n}\n"],"mappings":";;;;;;;;;;;;;AA0BA,SAAgB,QACd,MACA,MACA,SACA,GACA,WACA,SACA,MACS;CACT,MAAM,UAAU,OAAyB,aAA4C;EACnF,MAAM,OAAO,KAAK,OAAO,QAAQ;EACjC,OAAO,SAAS,QACX,MAAM,WAAW,KAAK,MAAM,MAAM,SAAS,OAAO,KAAK,KAAK,GAAG,MAAM,MAAM,OAAO;CACzF;CAEA,MAAM,UAAU,WAAW,EAAE,WAAW,SAAY,UAAU,IAAI;CAClE,IAAI,YAAY,QAAQ,CAAC,QAAQ,MAAM,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG;EACzE,IAAI,CAAC,EAAE,KAAK,MAAM,MAAM,OAAO,GAAG,OAAO;EACzC,QAAQ,WAAW,UAAU,EAAA,CAAG,OAAO,MAAM,OAAO,GAAG,CAAC,CAAC;CAC3D;CAEA,MAAM,iBAAiB,CAAC,GAAG,OAAO,CAAC,CAAC,QAAQ;CAC5C,MAAM,QAAQ,GAAW,UAAqC;EAC5D,IAAI,MAAM,eAAe,QAAQ,OAAO,MAAM,KAAK,MAAM,MAAM,OAAO;EACtE,MAAM,WAAW,eAAe;EAChC,IAAI,OAAO,OAAO,QAAQ,KAAK,KAAK,IAAI,GAAG,KAAK,GAAG,OAAO;EAC1D,KAAK,MAAM,KAAK,KAAK,SAAS,QAAQ,GAAG;GACvC,MAAM,SAAS,MAAM,SAAS,CAAC;GAC/B,IAAI,WAAW,UAAa,OAAO,QAAQ,QAAQ,KAAK,KAAK,IAAI,GAAG,MAAM,GAAG,OAAO;EACtF;EACA,OAAO;CACT;CACA,OAAO,KAAK,GAAG,CAAC;AAClB"}
1
+ {"version":3,"file":"pickReach.js","names":[],"sources":["../../src/canvas/pickReach.ts"],"sourcesContent":["import type { Affine, Path } from '@weasel-js/geom';\nimport type { PickCandidate, PickQuery } from './pickWalk';\nimport type { InheritedPaint } from 'core/scene/inheritedPaint';\n\n/** What {@link reaches} needs from the walk, each memoized per walk there. */\nexport interface ReachLookups<TPose> {\n /** An ancestor's own clip, or null. */\n clipOf(ancestor: PickCandidate<TPose>): Path | null;\n /** The maps from world into the source frame of each copy `ancestor` draws:\n * the inverses of its `copiesFromPose`. Empty when it draws none. */\n copiesOf(ancestor: PickCandidate<TPose>): readonly Affine[];\n /** A clip carried from its container's plane into the node's. */\n carry(clip: Path, fromLayer: string | undefined, toLayer: string | undefined): Path;\n}\n\n/**\n * Whether `q` lands on `node` anywhere it is painted: as authored, or in any\n * copy an ancestor draws of it. `parents` is innermost first.\n *\n * A copy is the clipped content transformed, so the query is carried into the\n * copy's source frame before the copying container's own clip is asked, and\n * every clip below it is asked in that frame too.\n *\n * `parents` is called only when the answer needs it — always when\n * `copying` says some container in the walk draws copies, otherwise only\n * after the node hits as authored.\n */\nexport function reaches<TPose>(\n node: PickCandidate<TPose>,\n pose: TPose,\n derived: Path | null | undefined,\n inherited: InheritedPaint | undefined,\n q: PickQuery<TPose>,\n parentsOf: () => readonly PickCandidate<TPose>[],\n copying: boolean,\n look: ReachLookups<TPose>,\n): boolean {\n const admits = (query: PickQuery<TPose>, ancestor: PickCandidate<TPose>): boolean => {\n const clip = look.clipOf(ancestor);\n return clip === null\n || query.clipAdmits(look.carry(clip, ancestor.layer, node.layer), node, pose, derived);\n };\n\n const parents = copying && q.inCopy !== undefined ? parentsOf() : null;\n if (parents === null || !parents.some((a) => look.copiesOf(a).length > 0)) {\n if (!q.hits(node, pose, derived, inherited)) return false;\n return (parents ?? parentsOf()).every((a) => admits(q, a));\n }\n\n const outermostFirst = [...parents].reverse();\n const from = (i: number, query: PickQuery<TPose>): boolean => {\n if (i === outermostFirst.length) return query.hits(node, pose, derived, inherited);\n const ancestor = outermostFirst[i];\n if (admits(query, ancestor) && from(i + 1, query)) return true;\n for (const m of look.copiesOf(ancestor)) {\n const inCopy = query.inCopy?.(m);\n if (inCopy !== undefined && admits(inCopy, ancestor) && from(i + 1, inCopy)) return true;\n }\n return false;\n };\n return from(0, q);\n}\n"],"mappings":";;;;;;;;;;;;;AA2BA,SAAgB,QACd,MACA,MACA,SACA,WACA,GACA,WACA,SACA,MACS;CACT,MAAM,UAAU,OAAyB,aAA4C;EACnF,MAAM,OAAO,KAAK,OAAO,QAAQ;EACjC,OAAO,SAAS,QACX,MAAM,WAAW,KAAK,MAAM,MAAM,SAAS,OAAO,KAAK,KAAK,GAAG,MAAM,MAAM,OAAO;CACzF;CAEA,MAAM,UAAU,WAAW,EAAE,WAAW,SAAY,UAAU,IAAI;CAClE,IAAI,YAAY,QAAQ,CAAC,QAAQ,MAAM,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG;EACzE,IAAI,CAAC,EAAE,KAAK,MAAM,MAAM,SAAS,SAAS,GAAG,OAAO;EACpD,QAAQ,WAAW,UAAU,EAAA,CAAG,OAAO,MAAM,OAAO,GAAG,CAAC,CAAC;CAC3D;CAEA,MAAM,iBAAiB,CAAC,GAAG,OAAO,CAAC,CAAC,QAAQ;CAC5C,MAAM,QAAQ,GAAW,UAAqC;EAC5D,IAAI,MAAM,eAAe,QAAQ,OAAO,MAAM,KAAK,MAAM,MAAM,SAAS,SAAS;EACjF,MAAM,WAAW,eAAe;EAChC,IAAI,OAAO,OAAO,QAAQ,KAAK,KAAK,IAAI,GAAG,KAAK,GAAG,OAAO;EAC1D,KAAK,MAAM,KAAK,KAAK,SAAS,QAAQ,GAAG;GACvC,MAAM,SAAS,MAAM,SAAS,CAAC;GAC/B,IAAI,WAAW,UAAa,OAAO,QAAQ,QAAQ,KAAK,KAAK,IAAI,GAAG,MAAM,GAAG,OAAO;EACtF;EACA,OAAO;CACT;CACA,OAAO,KAAK,GAAG,CAAC;AAClB"}
@@ -1,12 +1,8 @@
1
- import { planeMap } from "../core/viewport/parallax.js";
2
- import { resolveDerivedPath } from "../core/scene/derivedPath.js";
3
- import { findShapeSilhouette } from "./NodeShape.js";
1
+ import { inheritedPaintOf, inheritsPaint } from "../core/scene/inheritedPaint.js";
4
2
  import { clipCarrier } from "./planeClips.js";
5
- import { asNodeId } from "../core/scene/types.js";
6
- import { annotationLayerIds } from "../core/scene/annotation.js";
7
- import { nodeGates } from "../core/scene/nodeGates.js";
8
- import { sceneDerivedPathOf, scenePaintedPoseOf, showsComputedPath } from "./derivedPath.js";
3
+ import { ownClipOf } from "./ownClip.js";
9
4
  import { reaches } from "./pickReach.js";
5
+ import { scenePickSource, scenePlaneOf } from "./scenePickSource.js";
10
6
  import { adapterPickSource } from "./adapterPickSource.js";
11
7
  import { invert } from "@weasel-js/geom";
12
8
 
@@ -16,7 +12,7 @@ import { invert } from "@weasel-js/geom";
16
12
  * query supplies only the shape test and the clip predicate for its own region.
17
13
  *
18
14
  * The gates, in the order the walk applies them:
19
- * 1. containers, when the query does not want them
15
+ * 1. groups, when the query does not want them
20
16
  * 2. the node is painted at all — not hidden in the scene (`nodeGates`),
21
17
  * and on a layer the asking view's `layerVisibility` / `layerOrder` paints
22
18
  * 3. painted alpha is above zero
@@ -28,20 +24,12 @@ import { invert } from "@weasel-js/geom";
28
24
  * that skips one answers for a node the user cannot see. 4 is the other axis:
29
25
  * a locked node is on screen and still out of reach.
30
26
  */
31
- /** Shared, never mutated: most scenes are flat and every node returns it. */
32
- var EMPTY_PARENTS = [];
33
- /** The clip a container imposes on its subtree, or null when it imposes none. */
34
- function ownClipOf(node, pose, derived) {
35
- if (node.kind !== "container") return null;
36
- if (typeof node.clipFromPose === "function") return node.clipFromPose(pose);
37
- return findShapeSilhouette(node, pose, { derivedPath: derived });
38
- }
39
27
  /**
40
28
  * Collect every candidate the query hits, back-to-front — the last element is
41
29
  * the topmost, which is the order `pickTopMostHit` expects.
42
30
  */
43
31
  function pickWalk(src, q) {
44
- const includeContainers = q.includeContainers ?? true;
32
+ const includeGroups = q.includeGroups ?? true;
45
33
  const isLocked = q.includeLocked === true ? void 0 : src.isLocked;
46
34
  const out = [];
47
35
  const clipPath = /* @__PURE__ */ new Map();
@@ -49,7 +37,7 @@ function pickWalk(src, q) {
49
37
  const clipOf = (ancestor) => {
50
38
  const cached = clipPath.get(ancestor.id);
51
39
  if (cached !== void 0) return cached;
52
- const resolved = ownClipOf(ancestor, src.poseOf(ancestor), src.derivedPathOf?.(ancestor));
40
+ const resolved = ownClipOf(ancestor, src.poseOf(ancestor), () => src.derivedPathOf?.(ancestor));
53
41
  clipPath.set(ancestor.id, resolved);
54
42
  return resolved;
55
43
  };
@@ -57,7 +45,7 @@ function pickWalk(src, q) {
57
45
  const copiesOf = (ancestor) => {
58
46
  let found = copyMaps.get(ancestor.id);
59
47
  if (found === void 0) {
60
- const fn = ancestor.kind === "container" ? ancestor.copiesFromPose : void 0;
48
+ const fn = ancestor.copiesFromPose;
61
49
  found = (fn ? fn(src.poseOf(ancestor), ancestor) ?? [] : []).map(invert).filter((m) => m !== null);
62
50
  copyMaps.set(ancestor.id, found);
63
51
  }
@@ -75,9 +63,13 @@ function pickWalk(src, q) {
75
63
  return found;
76
64
  };
77
65
  const order = src.order();
78
- const copying = order.some((n) => n.kind === "container" && typeof n.copiesFromPose === "function");
79
- for (const node of order) {
80
- if (!includeContainers && node.kind === "container") continue;
66
+ const copying = order.some((n) => typeof n.copiesFromPose === "function");
67
+ const visit = (q.broad !== void 0 ? src.candidates?.((layer) => {
68
+ const query = queryFor(layer);
69
+ return query === null ? null : query.broad;
70
+ }) : null) ?? order;
71
+ for (const node of visit) {
72
+ if (!includeGroups && node.kind === "group") continue;
81
73
  if (node.pickable === false) continue;
82
74
  if (src.isHidden?.(node) === true) continue;
83
75
  if (node.layer !== void 0 && src.layerIsPainted?.(node.layer) === false) continue;
@@ -87,7 +79,8 @@ function pickWalk(src, q) {
87
79
  if (query === null) continue;
88
80
  const pose = src.poseOf(node);
89
81
  const derived = src.derivedPathOf?.(node);
90
- if (!reaches(node, pose, derived, query, () => src.parentsOf(node), copying, {
82
+ const inherited = inheritsPaint(node) ? inheritedPaintOf(node, src.parentsOf(node)) : void 0;
83
+ if (!reaches(node, pose, derived, inherited, query, () => src.parentsOf(node), copying, {
91
84
  clipOf,
92
85
  copiesOf,
93
86
  carry
@@ -96,74 +89,7 @@ function pickWalk(src, q) {
96
89
  }
97
90
  return out;
98
91
  }
99
- function scenePickSource(scene, opts = {}) {
100
- const gates = nodeGates(scene);
101
- const annotations = annotationLayerIds(scene.layers);
102
- const { getPose, alphaOf, layerIsPainted } = opts;
103
- let faded = null;
104
- const enumerable = typeof scene.overrides?.ids === "function";
105
- if (!alphaOf && enumerable) for (const id of scene.overrides.ids()) {
106
- const a = scene.overrides.read(id)?.alpha;
107
- if (a !== void 0 && a <= 0) (faded ??= /* @__PURE__ */ new Set()).add(id);
108
- }
109
- const readAlpha = alphaOf ?? (enumerable ? faded ? (id) => faded.has(id) ? 0 : 1 : void 0 : (id) => scene.overrides.read(asNodeId(id))?.alpha ?? 1);
110
- const paintedPoseOf = scenePaintedPoseOf(scene);
111
- const poseOf = getPose ? (node) => getPose(node.id) : (node) => paintedPoseOf(node);
112
- const childrenOf = (id) => scene.childrenOf(id);
113
- const depOf = (id) => {
114
- const dep = scene.get(id);
115
- if (dep === void 0) return void 0;
116
- return {
117
- node: dep,
118
- pose: poseOf(dep),
119
- get path() {
120
- return resolveDerivedPath(dep, depOf, childrenOf);
121
- }
122
- };
123
- };
124
- const sceneDerivedPath = getPose ? null : sceneDerivedPathOf(scene);
125
- const derivedPathOf = (node) => {
126
- const n = node;
127
- if (!sceneDerivedPath) return n.dependsOn === void 0 ? null : resolveDerivedPath(n, depOf, childrenOf);
128
- return showsComputedPath(scene, n) ? sceneDerivedPath(n) : null;
129
- };
130
- const planeOf = opts.camera ? scenePlaneOf(scene.layers, opts.camera) : null;
131
- return {
132
- ...planeOf ? { planeOf } : {},
133
- order: () => scene.renderOrderNodes(),
134
- derivedPathOf,
135
- poseOf,
136
- parentsOf(node) {
137
- let parentId = node.parent ?? null;
138
- if (parentId === null) return EMPTY_PARENTS;
139
- const chain = [];
140
- const seen = /* @__PURE__ */ new Set();
141
- while (parentId !== null) {
142
- if (seen.has(parentId)) break;
143
- seen.add(parentId);
144
- const parent = scene.get(asNodeId(parentId));
145
- if (!parent) break;
146
- chain.push(parent);
147
- parentId = parent.parent ?? null;
148
- }
149
- return chain;
150
- },
151
- ...readAlpha ? { alphaOf: readAlpha } : {},
152
- isLocked: (node) => gates.locked(node.id),
153
- isHidden: (node) => gates.hidden(node.id),
154
- ...annotations.size > 0 || layerIsPainted ? { layerIsPainted: (layer) => !annotations.has(layer) && (layerIsPainted === void 0 || layerIsPainted(layer)) } : {}
155
- };
156
- }
157
- /** Each parallax layer's plane map under `camera`, or null when no layer is a
158
- * plane — which keeps the per-layer lookup off the walk entirely. */
159
- function scenePlaneOf(layers, camera) {
160
- let maps = null;
161
- for (const l of layers ?? []) if (l.parallax) (maps ??= /* @__PURE__ */ new Map()).set(l.id, planeMap(camera, l.parallax));
162
- if (maps === null) return null;
163
- const found = maps;
164
- return (layer) => found.get(layer) ?? null;
165
- }
166
92
 
167
93
  //#endregion
168
- export { adapterPickSource, ownClipOf, pickWalk, scenePickSource, scenePlaneOf };
94
+ export { adapterPickSource, pickWalk, scenePickSource, scenePlaneOf };
169
95
  //# sourceMappingURL=pickWalk.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pickWalk.js","names":[],"sources":["../../src/canvas/pickWalk.ts"],"sourcesContent":["/**\n * The one hit-test walk. Everything that decides whether a node *can* be hit lives here; a\n * query supplies only the shape test and the clip predicate for its own region.\n *\n * The gates, in the order the walk applies them:\n * 1. containers, when the query does not want them\n * 2. the node is painted at all — not hidden in the scene (`nodeGates`),\n * and on a layer the asking view's `layerVisibility` / `layerOrder` paints\n * 3. painted alpha is above zero\n * 4. the node is not locked, unless the query asks for locked nodes\n * 5. the query's own shape test\n * 6. every ancestor clip admits the query region\n *\n * 2, 3 and 6 are all \"the renderer did not put this on screen\". A pick path\n * that skips one answers for a node the user cannot see. 4 is the other axis:\n * a locked node is on screen and still out of reach.\n */\n\nimport { invert, type Affine, type Path } from '@weasel-js/geom';\nimport { findShapeSilhouette } from 'canvas/NodeShape';\nimport type { ClipFromPoseFn, CopiesFromPoseFn, DerivedDep, Node, NodeId, Scene } from 'core/scene/types';\nimport { asNodeId } from 'core/scene/types';\nimport { annotationLayerIds } from 'core/scene/annotation';\nimport { nodeGates } from 'core/scene/nodeGates';\nimport { resolveDerivedPath, sceneDerivedPathOf, scenePaintedPoseOf, showsComputedPath } from './derivedPath';\nimport { planeMap, type ParallaxOpts, type PlaneMap } from 'core/viewport/parallax';\nimport type { View } from 'core/viewport/view';\nimport { clipCarrier } from './planeClips';\nimport { reaches } from './pickReach';\n\n/** Shared, never mutated: most scenes are flat and every node returns it. */\nconst EMPTY_PARENTS: readonly never[] = [];\n\n/** The subset of a node the walk itself reads. Deliberately structural: a\n * bare adapter's node satisfies it as readily as a `SceneNode`. */\nexport interface PickCandidate<TPose> {\n id: string;\n kind?: string;\n layer?: string;\n pose: TPose;\n /** `false` makes the node transparent to the walk — see `SceneNode.pickable`. */\n pickable?: boolean;\n clipFromPose?: ClipFromPoseFn<TPose>;\n copiesFromPose?: CopiesFromPoseFn<TPose>;\n}\n\n/**\n * Everything the walk reads that a **view** owns.\n *\n * A layer must not hold state a view owns, so none of these are closed over\n * at construction — each is asked per walk, of the view being dispatched to.\n */\nexport interface PickSource<TPose> {\n /** Candidates in paint order, back-to-front. Containers included; the\n * query decides whether they can be hit. */\n order(): readonly PickCandidate<TPose>[];\n /** The pose the renderer draws — override-aware. Not `node.pose`, which is\n * how the render and pick paths came to disagree about where a node is. */\n poseOf(node: PickCandidate<TPose>): TPose;\n /** Ancestors, innermost first. Only walked for candidates that already\n * passed their own shape test. */\n parentsOf(node: PickCandidate<TPose>): readonly PickCandidate<TPose>[];\n /** Painted alpha for the asking view: the view's `alphaFor` times any\n * per-node override alpha. Omit when the source has no alpha concept. */\n alphaOf?(id: string): number;\n /** Whether this layer reaches the screen in the asking view. Omit for a\n * source with no layers. */\n layerIsPainted?(layer: string): boolean;\n /** Whether the node is locked — painted, but not something a pointer can\n * take. Omit for a source with no lock concept. */\n isLocked?(node: PickCandidate<TPose>): boolean;\n /** Whether the source hides the node, so nothing of it is on screen. Omit\n * for a source that hides nothing. */\n isHidden?(node: PickCandidate<TPose>): boolean;\n /** The path a node computes from its dependencies, or null for none. */\n derivedPathOf?(node: PickCandidate<TPose>): Path | null;\n /** How the asking view's world maps into a parallax layer's, or null for a\n * layer that moves with the camera. Omit for a source with no planes. */\n planeOf?(layer: string): PlaneMap | null;\n}\n\n/** One hit-test question: what region, and what counts as covering it. */\nexport interface PickQuery<TPose> {\n /** Default `true`. The area walks exclude containers so a marquee does not\n * return a container and its children both. */\n includeContainers?: boolean;\n /** Default `false`. Every selecting or editing query passes over locked\n * nodes; one that only samples what is painted can take them back. */\n includeLocked?: boolean;\n /** Does this node, drawn at this pose, cover the query region? `derived`\n * is the node's resolved path when it has one — a derived node's pose is a\n * placeholder, so a region test that reads only the pose answers for the\n * wrong box. */\n hits(node: PickCandidate<TPose>, pose: TPose, derived?: Path | null): boolean;\n /**\n * Does an ancestor's clip leave anything of `node` for this query to hit?\n *\n * A point query asks point-in-clip and ignores the node. An area query has\n * to ask about the node as well: the clip and the area can overlap where\n * the node is not, and the node and the clip can overlap where the area is\n * not, and only the two terms together reject both.\n */\n clipAdmits(\n clip: Path,\n node: PickCandidate<TPose>,\n pose: TPose,\n derived?: Path | null,\n ): boolean;\n /** The same question asked in a parallax plane's world: the region carried\n * through `map`, and any screen-pixel slop re-derived at the plane's scale.\n * A query without it cannot reach nodes on a plane — it would test them\n * where their poses sit rather than where they are painted. */\n inPlane?(map: PlaneMap): PickQuery<TPose>;\n /** The same question carried by `m`, an affine from world into the source\n * frame of a copy a container draws. A query without it cannot reach\n * copies — see `ContainerNode.copiesFromPose`. */\n inCopy?(m: Affine): PickQuery<TPose>;\n}\n\n/** The clip a container imposes on its subtree, or null when it imposes none. */\nexport function ownClipOf<TPose>(\n node: PickCandidate<TPose>,\n pose: TPose,\n derived?: Path | null,\n): Path | null {\n if (node.kind !== 'container') return null;\n if (typeof node.clipFromPose === 'function') return node.clipFromPose(pose);\n return findShapeSilhouette(\n node as unknown as Node<unknown, string, TPose>,\n pose,\n { derivedPath: derived },\n );\n}\n\n/**\n * Collect every candidate the query hits, back-to-front — the last element is\n * the topmost, which is the order `pickTopMostHit` expects.\n */\nexport function pickWalk<TPose>(\n src: PickSource<TPose>,\n q: PickQuery<TPose>,\n): string[] {\n const includeContainers = q.includeContainers ?? true;\n const isLocked = q.includeLocked === true ? undefined : src.isLocked;\n const out: string[] = [];\n\n // Resolving a container's clip is the expensive half, so the *path* is memoized per\n // container for the walk. The predicate re-runs per candidate: caching its answer per\n // container would answer the wrong question for an area query.\n const clipPath = new Map<string, Path | null>();\n // A clip is in its container's plane; the query is in the candidate's.\n const carry = clipCarrier(src.planeOf ?? null);\n\n const clipOf = (ancestor: PickCandidate<TPose>): Path | null => {\n const cached = clipPath.get(ancestor.id);\n if (cached !== undefined) return cached;\n const resolved = ownClipOf(\n ancestor, src.poseOf(ancestor), src.derivedPathOf?.(ancestor),\n );\n clipPath.set(ancestor.id, resolved);\n return resolved;\n };\n\n const copyMaps = new Map<string, readonly Affine[]>();\n const copiesOf = (ancestor: PickCandidate<TPose>): readonly Affine[] => {\n let found = copyMaps.get(ancestor.id);\n if (found === undefined) {\n const fn = ancestor.kind === 'container' ? ancestor.copiesFromPose : undefined;\n const copies = fn ? fn(src.poseOf(ancestor), ancestor as never) ?? [] : [];\n found = copies.map(invert).filter((m): m is Affine => m !== null);\n copyMaps.set(ancestor.id, found);\n }\n return found;\n };\n\n // One reprojected query per plane per walk; `null` marks a plane this\n // query cannot reach.\n const planeQueries = new Map<string, PickQuery<TPose> | null>();\n const queryFor = (layer: string | undefined): PickQuery<TPose> | null => {\n if (layer === undefined || src.planeOf === undefined) return q;\n let found = planeQueries.get(layer);\n if (found === undefined) {\n const map = src.planeOf(layer);\n found = map === null ? q : (q.inPlane?.(map) ?? null);\n planeQueries.set(layer, found);\n }\n return found;\n };\n\n const order = src.order();\n const copying = order.some((n) => n.kind === 'container' && typeof n.copiesFromPose === 'function');\n for (const node of order) {\n if (!includeContainers && node.kind === 'container') continue;\n // Transparent to picking: the click belongs to whatever is behind it,\n // which for a label inside a box is the box.\n if (node.pickable === false) continue;\n if (src.isHidden?.(node) === true) continue;\n if (node.layer !== undefined && src.layerIsPainted?.(node.layer) === false) continue;\n if (src.alphaOf !== undefined && src.alphaOf(node.id) <= 0) continue;\n if (isLocked !== undefined && isLocked(node)) continue;\n\n const query = queryFor(node.layer);\n if (query === null) continue;\n const pose = src.poseOf(node);\n const derived = src.derivedPathOf?.(node);\n if (!reaches(node, pose, derived, query, () => src.parentsOf(node), copying, { clipOf, copiesOf, carry })) continue;\n out.push(node.id);\n }\n\n return out;\n}\n\n/**\n * The two answers a pick needs from the view it is asked for rather than from\n * the data behind it: how that view paints a node, and whether it paints a\n * layer at all. A scene and a bare adapter take the same pair.\n */\nexport interface ViewPickGates {\n /** Painted alpha: the view's `alphaFor` times any per-node override alpha.\n * A node painted at alpha 0 cannot be hit. */\n alphaOf?: (id: string) => number;\n /** Whether a node's `layer` reaches the screen in the asking view. */\n layerIsPainted?: (layer: string) => boolean;\n}\n\n/** The gates a bare adapter takes from its view, plus how that view's camera\n * maps into each parallax layer — see `scenePlaneOf`. */\nexport interface AdapterPickGates extends ViewPickGates {\n planeOf?: (layer: string) => PlaneMap | null;\n}\n\nexport interface ScenePickSourceOptions<TPose> extends ViewPickGates {\n /** Pose lookup, when the caller holds an adapter that already resolves\n * overrides. Defaults to `effectivePose` against the scene's own. */\n getPose?: (id: string) => TPose;\n /** The asking view's composed alpha — `alphaFor(id)` times override alpha.\n * Defaults to the override alpha alone, which is all a bare scene knows. */\n alphaOf?: (id: string) => number;\n /** The asking view's answer to \"does this scene layer reach the screen\",\n * which is `layerVisibility` and `layerOrder` on top of the scene's own\n * `visible` flag. Consulted after the scene's flag, never instead of it. */\n layerIsPainted?: (layer: string) => boolean;\n /** The asking view's camera. With it, a layer carrying `parallax` is picked\n * through its plane; without it, every layer is taken to move with the\n * camera. */\n camera?: View;\n}\n\nexport function scenePickSource<TData, TLayer extends string, TPose>(\n scene: Scene<TData, TLayer, TPose>,\n opts: ScenePickSourceOptions<TPose> = {},\n): PickSource<TPose> {\n // Partial scene stand-ins carry only `layers` and `get`, which is all the gates read.\n const gates = nodeGates(scene as never);\n const annotations = annotationLayerIds(scene.layers);\n const { getPose, alphaOf, layerIsPainted } = opts;\n\n // Resolved once per walk rather than once per candidate. Needs enumeration — a\n // partial scene stand-in carrying only `read` falls back to reading per node.\n let faded: Set<string> | null = null;\n const enumerable = typeof scene.overrides?.ids === 'function';\n if (!alphaOf && enumerable) {\n for (const id of scene.overrides.ids()) {\n const a = scene.overrides.read(id)?.alpha;\n if (a !== undefined && a <= 0) (faded ??= new Set()).add(id);\n }\n }\n const readAlpha = alphaOf\n ?? (enumerable\n ? (faded ? (id: string) => (faded.has(id) ? 0 : 1) : undefined)\n : (id: string) => scene.overrides.read(asNodeId(id))?.alpha ?? 1);\n\n // A composing strategy makes a node's own pose local, so picking folds the\n // parent chain the same way the render walk does: one definition of both.\n const paintedPoseOf = scenePaintedPoseOf(scene);\n const poseOf = getPose\n ? (node: PickCandidate<TPose>) => getPose(node.id)\n : (node: PickCandidate<TPose>) => paintedPoseOf(node as never);\n\n // Through `poseOf`, not the document pose: a derived node has to be tested\n // where the renderer draws its dependencies, which is what an override says.\n const childrenOf = (id: NodeId): readonly NodeId[] => scene.childrenOf(id);\n const depOf = (id: NodeId): DerivedDep<TPose> | undefined => {\n const dep = scene.get(id);\n if (dep === undefined) return undefined;\n return {\n node: dep as Node<unknown, string, TPose>,\n pose: poseOf(dep as never),\n get path(): Path | null {\n return resolveDerivedPath(dep as never, depOf, childrenOf);\n },\n };\n };\n\n const sceneDerivedPath = getPose ? null : sceneDerivedPathOf(scene);\n const derivedPathOf = (node: PickCandidate<TPose>): Path | null => {\n const n = node as unknown as Node<TData, TLayer, TPose>;\n if (!sceneDerivedPath) return n.dependsOn === undefined ? null : resolveDerivedPath(n, depOf, childrenOf);\n return showsComputedPath(scene, n) ? sceneDerivedPath(n) : null;\n };\n\n const planeOf = opts.camera ? scenePlaneOf(scene.layers, opts.camera) : null;\n\n return {\n ...(planeOf ? { planeOf } : {}),\n order: () => scene.renderOrderNodes() as unknown as readonly PickCandidate<TPose>[],\n derivedPathOf,\n poseOf,\n parentsOf(node) {\n let parentId = (node as { parent?: string | null }).parent ?? null;\n if (parentId === null) return EMPTY_PARENTS;\n const chain: PickCandidate<TPose>[] = [];\n const seen = new Set<string>();\n while (parentId !== null) {\n if (seen.has(parentId)) break;\n seen.add(parentId);\n const parent = scene.get(asNodeId(parentId));\n if (!parent) break;\n chain.push(parent as unknown as PickCandidate<TPose>);\n parentId = parent.parent ?? null;\n }\n return chain;\n },\n // Absent entirely when nothing is faded, so the walk skips the gate.\n ...(readAlpha ? { alphaOf: readAlpha } : {}),\n isLocked: (node: PickCandidate<TPose>) => gates.locked(node.id),\n isHidden: (node: PickCandidate<TPose>) => gates.hidden(node.id),\n ...(annotations.size > 0 || layerIsPainted\n ? {\n layerIsPainted: (layer: string) =>\n !annotations.has(layer) && (layerIsPainted === undefined || layerIsPainted(layer)),\n }\n : {}),\n };\n}\n\n/** Each parallax layer's plane map under `camera`, or null when no layer is a\n * plane — which keeps the per-layer lookup off the walk entirely. */\nexport function scenePlaneOf(\n layers: readonly { id: string; parallax?: ParallaxOpts }[] | undefined,\n camera: View,\n): ((layer: string) => PlaneMap | null) | null {\n let maps: Map<string, PlaneMap> | null = null;\n for (const l of layers ?? []) {\n if (l.parallax) (maps ??= new Map()).set(l.id, planeMap(camera, l.parallax));\n }\n if (maps === null) return null;\n const found = maps;\n return (layer) => found.get(layer) ?? null;\n}\n\nexport { adapterPickSource } from './adapterPickSource';\n"],"mappings":"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"}
1
+ {"version":3,"file":"pickWalk.js","names":[],"sources":["../../src/canvas/pickWalk.ts"],"sourcesContent":["/**\n * The one hit-test walk. Everything that decides whether a node *can* be hit lives here; a\n * query supplies only the shape test and the clip predicate for its own region.\n *\n * The gates, in the order the walk applies them:\n * 1. groups, when the query does not want them\n * 2. the node is painted at all — not hidden in the scene (`nodeGates`),\n * and on a layer the asking view's `layerVisibility` / `layerOrder` paints\n * 3. painted alpha is above zero\n * 4. the node is not locked, unless the query asks for locked nodes\n * 5. the query's own shape test\n * 6. every ancestor clip admits the query region\n *\n * 2, 3 and 6 are all \"the renderer did not put this on screen\". A pick path\n * that skips one answers for a node the user cannot see. 4 is the other axis:\n * a locked node is on screen and still out of reach.\n */\n\nimport { invert, type Affine, type Path } from '@weasel-js/geom';\nimport type { CopiesFromPoseFn } from 'core/scene/types';\nimport { ownClipOf, type ClipBearing } from './ownClip';\nimport { inheritedPaintOf, inheritsPaint, type InheritedPaint } from 'core/scene/inheritedPaint';\nimport { rectToPlane, type PlaneMap } from 'core/viewport/parallax';\nimport { clipCarrier } from './planeClips';\nimport { reaches } from './pickReach';\n\n/** The subset of a node the walk itself reads. Deliberately structural: a\n * bare adapter's node satisfies it as readily as a `SceneNode`. */\nexport interface PickCandidate<TPose> extends ClipBearing<TPose> {\n id: string;\n kind?: string;\n layer?: string;\n pose: TPose;\n /** `false` makes the node transparent to the walk — see `SceneNode.pickable`. */\n pickable?: boolean;\n /** Read for an `'inherit'` fill or stroke, which takes its containers'. */\n data?: unknown;\n copiesFromPose?: CopiesFromPoseFn<TPose>;\n}\n\n/**\n * Everything the walk reads that a **view** owns.\n *\n * A layer must not hold state a view owns, so none of these are closed over\n * at construction — each is asked per walk, of the view being dispatched to.\n */\nexport interface PickSource<TPose> {\n /** Candidates in paint order, back-to-front. Containers included; the\n * query decides whether they can be hit. */\n order(): readonly PickCandidate<TPose>[];\n /** The pose the renderer draws — override-aware. Not `node.pose`, which is\n * how the render and pick paths came to disagree about where a node is. */\n poseOf(node: PickCandidate<TPose>): TPose;\n /** Ancestors, innermost first. Only walked for candidates that already\n * passed their own shape test. */\n parentsOf(node: PickCandidate<TPose>): readonly PickCandidate<TPose>[];\n /** Painted alpha for the asking view: the view's `alphaFor` times any\n * per-node override alpha. Omit when the source has no alpha concept. */\n alphaOf?(id: string): number;\n /** Whether this layer reaches the screen in the asking view. Omit for a\n * source with no layers. */\n layerIsPainted?(layer: string): boolean;\n /** Whether the node is locked — painted, but not something a pointer can\n * take. Omit for a source with no lock concept. */\n isLocked?(node: PickCandidate<TPose>): boolean;\n /** Whether the source hides the node, so nothing of it is on screen. Omit\n * for a source that hides nothing. */\n isHidden?(node: PickCandidate<TPose>): boolean;\n /** The path a node computes from its dependencies, or null for none. */\n derivedPathOf?(node: PickCandidate<TPose>): Path | null;\n /** How the asking view's world maps into a parallax layer's, or null for a\n * layer that moves with the camera. Omit for a source with no planes. */\n planeOf?(layer: string): PlaneMap | null;\n /**\n * The candidates a query with these broad boxes can hit, back-to-front: a\n * subset of `order()` that drops only nodes no query inside the box could\n * hit. `broadFor(layer)` is the box for that layer's query: `null` for a\n * layer it cannot reach, `undefined` for one with no box, which makes this\n * answer `null` and the walk visit `order()`. Omit for a source with no index.\n */\n candidates?(\n broadFor: (layer: string | undefined) => PickBroad | null | undefined,\n ): readonly PickCandidate<TPose>[] | null;\n}\n\n/** The box a query cannot hit outside of, for a source's index to look up. */\nexport interface PickBroad {\n /** World box, already grown by the query's own slop. */\n box: { x: number; y: number; width: number; height: number };\n /** Screen px per world unit at the asking view (least axis); grows the box\n * by the largest `outsetPx` the index holds. Omit for a query whose test\n * reads no `outsetPx`. */\n pxPerWorld?: number;\n}\n\n/** `broad` carried into a plane's world, as `inPlane` carries the query. */\nexport function broadInPlane(broad: PickBroad, m: PlaneMap): PickBroad {\n const box = rectToPlane(m, broad.box);\n if (broad.pxPerWorld === undefined) return { box };\n // A plane's pixel is the same on screen, so its world unit holds fewer of them.\n const k = Math.max(Math.abs(m.scale.x), Math.abs(m.scale.y));\n return { box, pxPerWorld: broad.pxPerWorld / k };\n}\n\n/** One hit-test question: what region, and what counts as covering it. */\nexport interface PickQuery<TPose> {\n /** Default `true`. The area walks exclude groups so a marquee does not\n * return a group and its members both. */\n includeGroups?: boolean;\n /** Default `false`. Every selecting or editing query passes over locked\n * nodes; one that only samples what is painted can take them back. */\n includeLocked?: boolean;\n /** Where the query can hit at all, for a source with an index. `inPlane`\n * carries it with the query; without it the walk visits every node. */\n broad?: PickBroad;\n /** Does this node, drawn at this pose, cover the query region? `derived`\n * is the node's resolved path when it has one — a derived node's pose is a\n * placeholder, so a region test that reads only the pose answers for the\n * wrong box. `inherited` is what its `'inherit'` fields take from its\n * containers, for the ink it is tested against; absent when it inherits\n * nothing. */\n hits(node: PickCandidate<TPose>, pose: TPose, derived?: Path | null, inherited?: InheritedPaint): boolean;\n /**\n * Does an ancestor's clip leave anything of `node` for this query to hit?\n *\n * A point query asks point-in-clip and ignores the node. An area query has\n * to ask about the node as well: the clip and the area can overlap where\n * the node is not, and the node and the clip can overlap where the area is\n * not, and only the two terms together reject both.\n */\n clipAdmits(\n clip: Path,\n node: PickCandidate<TPose>,\n pose: TPose,\n derived?: Path | null,\n ): boolean;\n /** The same question asked in a parallax plane's world: the region carried\n * through `map`, and any screen-pixel slop re-derived at the plane's scale.\n * A query without it cannot reach nodes on a plane — it would test them\n * where their poses sit rather than where they are painted. */\n inPlane?(map: PlaneMap): PickQuery<TPose>;\n /** The same question carried by `m`, an affine from world into the source\n * frame of a copy a container draws. A query without it cannot reach\n * copies — see `GroupNode.copiesFromPose`. */\n inCopy?(m: Affine): PickQuery<TPose>;\n}\n\n/**\n * Collect every candidate the query hits, back-to-front — the last element is\n * the topmost, which is the order `pickTopMostHit` expects.\n */\nexport function pickWalk<TPose>(\n src: PickSource<TPose>,\n q: PickQuery<TPose>,\n): string[] {\n const includeGroups = q.includeGroups ?? true;\n const isLocked = q.includeLocked === true ? undefined : src.isLocked;\n const out: string[] = [];\n\n // Resolving a container's clip is the expensive half, so the *path* is memoized per\n // container for the walk. The predicate re-runs per candidate: caching its answer per\n // container would answer the wrong question for an area query.\n const clipPath = new Map<string, Path | null>();\n // A clip is in its container's plane; the query is in the candidate's.\n const carry = clipCarrier(src.planeOf ?? null);\n\n const clipOf = (ancestor: PickCandidate<TPose>): Path | null => {\n const cached = clipPath.get(ancestor.id);\n if (cached !== undefined) return cached;\n const resolved = ownClipOf(\n ancestor, src.poseOf(ancestor), () => src.derivedPathOf?.(ancestor),\n );\n clipPath.set(ancestor.id, resolved);\n return resolved;\n };\n\n const copyMaps = new Map<string, readonly Affine[]>();\n const copiesOf = (ancestor: PickCandidate<TPose>): readonly Affine[] => {\n let found = copyMaps.get(ancestor.id);\n if (found === undefined) {\n const fn = ancestor.copiesFromPose;\n const copies = fn ? fn(src.poseOf(ancestor), ancestor as never) ?? [] : [];\n found = copies.map(invert).filter((m): m is Affine => m !== null);\n copyMaps.set(ancestor.id, found);\n }\n return found;\n };\n\n // One reprojected query per plane per walk; `null` marks a plane this\n // query cannot reach.\n const planeQueries = new Map<string, PickQuery<TPose> | null>();\n const queryFor = (layer: string | undefined): PickQuery<TPose> | null => {\n if (layer === undefined || src.planeOf === undefined) return q;\n let found = planeQueries.get(layer);\n if (found === undefined) {\n const map = src.planeOf(layer);\n found = map === null ? q : (q.inPlane?.(map) ?? null);\n planeQueries.set(layer, found);\n }\n return found;\n };\n\n const order = src.order();\n const copying = order.some((n) => typeof n.copiesFromPose === 'function');\n const visit = (q.broad !== undefined ? src.candidates?.((layer) => {\n const query = queryFor(layer);\n return query === null ? null : query.broad;\n }) : null) ?? order;\n for (const node of visit) {\n if (!includeGroups && node.kind === 'group') continue;\n // Transparent to picking: the click belongs to whatever is behind it,\n // which for a label inside a box is the box.\n if (node.pickable === false) continue;\n if (src.isHidden?.(node) === true) continue;\n if (node.layer !== undefined && src.layerIsPainted?.(node.layer) === false) continue;\n if (src.alphaOf !== undefined && src.alphaOf(node.id) <= 0) continue;\n if (isLocked !== undefined && isLocked(node)) continue;\n\n const query = queryFor(node.layer);\n if (query === null) continue;\n const pose = src.poseOf(node);\n const derived = src.derivedPathOf?.(node);\n const inherited = inheritsPaint(node) ? inheritedPaintOf(node, src.parentsOf(node)) : undefined;\n if (!reaches(node, pose, derived, inherited, query, () => src.parentsOf(node), copying, { clipOf, copiesOf, carry })) continue;\n out.push(node.id);\n }\n\n return out;\n}\n\n/**\n * The two answers a pick needs from the view it is asked for rather than from\n * the data behind it: how that view paints a node, and whether it paints a\n * layer at all. A scene and a bare adapter take the same pair.\n */\nexport interface ViewPickGates {\n /** Painted alpha: the view's `alphaFor` times any per-node override alpha.\n * A node painted at alpha 0 cannot be hit. */\n alphaOf?: (id: string) => number;\n /** Whether a node's `layer` reaches the screen in the asking view. */\n layerIsPainted?: (layer: string) => boolean;\n}\n\n/** The gates a bare adapter takes from its view, plus how that view's camera\n * maps into each parallax layer — see `scenePlaneOf`. */\nexport interface AdapterPickGates extends ViewPickGates {\n planeOf?: (layer: string) => PlaneMap | null;\n}\n\nexport { scenePickSource, scenePlaneOf, type ScenePickSourceOptions } from './scenePickSource';\nexport { adapterPickSource } from './adapterPickSource';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuJA,SAAgB,SACd,KACA,GACU;CACV,MAAM,gBAAgB,EAAE,iBAAiB;CACzC,MAAM,WAAW,EAAE,kBAAkB,OAAO,SAAY,IAAI;CAC5D,MAAM,MAAgB,CAAC;CAKvB,MAAM,2BAAW,IAAI,IAAyB;CAE9C,MAAM,QAAQ,YAAY,IAAI,WAAW,IAAI;CAE7C,MAAM,UAAU,aAAgD;EAC9D,MAAM,SAAS,SAAS,IAAI,SAAS,EAAE;EACvC,IAAI,WAAW,QAAW,OAAO;EACjC,MAAM,WAAW,UACf,UAAU,IAAI,OAAO,QAAQ,SAAS,IAAI,gBAAgB,QAAQ,CACpE;EACA,SAAS,IAAI,SAAS,IAAI,QAAQ;EAClC,OAAO;CACT;CAEA,MAAM,2BAAW,IAAI,IAA+B;CACpD,MAAM,YAAY,aAAsD;EACtE,IAAI,QAAQ,SAAS,IAAI,SAAS,EAAE;EACpC,IAAI,UAAU,QAAW;GACvB,MAAM,KAAK,SAAS;GAEpB,SADe,KAAK,GAAG,IAAI,OAAO,QAAQ,GAAG,QAAiB,KAAK,CAAC,IAAI,CAAC,EACjE,CAAO,IAAI,MAAM,CAAC,CAAC,QAAQ,MAAmB,MAAM,IAAI;GAChE,SAAS,IAAI,SAAS,IAAI,KAAK;EACjC;EACA,OAAO;CACT;CAIA,MAAM,+BAAe,IAAI,IAAqC;CAC9D,MAAM,YAAY,UAAuD;EACvE,IAAI,UAAU,UAAa,IAAI,YAAY,QAAW,OAAO;EAC7D,IAAI,QAAQ,aAAa,IAAI,KAAK;EAClC,IAAI,UAAU,QAAW;GACvB,MAAM,MAAM,IAAI,QAAQ,KAAK;GAC7B,QAAQ,QAAQ,OAAO,IAAK,EAAE,UAAU,GAAG,KAAK;GAChD,aAAa,IAAI,OAAO,KAAK;EAC/B;EACA,OAAO;CACT;CAEA,MAAM,QAAQ,IAAI,MAAM;CACxB,MAAM,UAAU,MAAM,MAAM,MAAM,OAAO,EAAE,mBAAmB,UAAU;CACxE,MAAM,SAAS,EAAE,UAAU,SAAY,IAAI,cAAc,UAAU;EACjE,MAAM,QAAQ,SAAS,KAAK;EAC5B,OAAO,UAAU,OAAO,OAAO,MAAM;CACvC,CAAC,IAAI,SAAS;CACd,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,CAAC,iBAAiB,KAAK,SAAS,SAAS;EAG7C,IAAI,KAAK,aAAa,OAAO;EAC7B,IAAI,IAAI,WAAW,IAAI,MAAM,MAAM;EACnC,IAAI,KAAK,UAAU,UAAa,IAAI,iBAAiB,KAAK,KAAK,MAAM,OAAO;EAC5E,IAAI,IAAI,YAAY,UAAa,IAAI,QAAQ,KAAK,EAAE,KAAK,GAAG;EAC5D,IAAI,aAAa,UAAa,SAAS,IAAI,GAAG;EAE9C,MAAM,QAAQ,SAAS,KAAK,KAAK;EACjC,IAAI,UAAU,MAAM;EACpB,MAAM,OAAO,IAAI,OAAO,IAAI;EAC5B,MAAM,UAAU,IAAI,gBAAgB,IAAI;EACxC,MAAM,YAAY,cAAc,IAAI,IAAI,iBAAiB,MAAM,IAAI,UAAU,IAAI,CAAC,IAAI;EACtF,IAAI,CAAC,QAAQ,MAAM,MAAM,SAAS,WAAW,aAAa,IAAI,UAAU,IAAI,GAAG,SAAS;GAAE;GAAQ;GAAU;EAAM,CAAC,GAAG;EACtH,IAAI,KAAK,KAAK,EAAE;CAClB;CAEA,OAAO;AACT"}
@@ -42,7 +42,7 @@ function containerCascade(scene, id, d, write) {
42
42
  function cascadingSetPose(scene, d) {
43
43
  return (id, pose) => {
44
44
  const node = scene.get(id);
45
- if (!node || node.kind !== "container") return scene.setPose(id, pose);
45
+ if (!node || node.kind !== "group") return scene.setPose(id, pose);
46
46
  const g = poseDescriptorForNode(d, node);
47
47
  const from = g.getBounds(node.pose);
48
48
  const to = g.getBounds(pose);