@weasel-js/core 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (586) hide show
  1. package/CHANGELOG.md +611 -0
  2. package/README.md +1 -1
  3. package/dist/WeaselProvider.js +1 -1
  4. package/dist/affordances/pathAnchors.js +10 -6
  5. package/dist/affordances/pathAnchors.js.map +1 -1
  6. package/dist/animation/index.js +3 -3
  7. package/dist/animation/rig/bindRig.js +1 -1
  8. package/dist/animation/rig/rigNode.js.map +1 -1
  9. package/dist/barrel/canvas.js +9 -3
  10. package/dist/barrel/interactions.js +5 -4
  11. package/dist/barrel/paint.js +4 -4
  12. package/dist/barrel/paths.js +10 -6
  13. package/dist/barrel/renderer.js +5 -3
  14. package/dist/barrel/routing.js +7 -12
  15. package/dist/barrel/scene.js +9 -5
  16. package/dist/barrel/text.js +2 -2
  17. package/dist/barrel/viewport.js +9 -4
  18. package/dist/canvas/Canvas.js +5 -2
  19. package/dist/canvas/Canvas.js.map +1 -1
  20. package/dist/canvas/CanvasView.js +23 -34
  21. package/dist/canvas/CanvasView.js.map +1 -1
  22. package/dist/canvas/ModalityHud.js +1 -1
  23. package/dist/canvas/NodeShape.js +4 -1
  24. package/dist/canvas/NodeShape.js.map +1 -1
  25. package/dist/canvas/SceneCanvas/ActionsProviderIfRoot.js +1 -1
  26. package/dist/canvas/SceneCanvas/DepRegistryProviderIfRoot.js +1 -1
  27. package/dist/canvas/SceneCanvas/PointerProviderIfRoot.js +1 -1
  28. package/dist/canvas/SceneCanvas/SceneCanvasInput.js +17 -3
  29. package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
  30. package/dist/canvas/SceneCanvas/SurfaceDeclarations.js +23 -0
  31. package/dist/canvas/SceneCanvas/SurfaceDeclarations.js.map +1 -0
  32. package/dist/canvas/SceneCanvas/builtinToolSets.js +30 -0
  33. package/dist/canvas/SceneCanvas/builtinToolSets.js.map +1 -0
  34. package/dist/canvas/SceneCanvas/canvasPassThrough.js +2 -0
  35. package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
  36. package/dist/canvas/SceneCanvas/coordTrace.js +5 -16
  37. package/dist/canvas/SceneCanvas/coordTrace.js.map +1 -1
  38. package/dist/canvas/SceneCanvas/defaultNodeProperties.js +5 -0
  39. package/dist/canvas/SceneCanvas/defaultNodeProperties.js.map +1 -1
  40. package/dist/canvas/SceneCanvas/features.js +45 -5
  41. package/dist/canvas/SceneCanvas/features.js.map +1 -1
  42. package/dist/canvas/SceneCanvas/props.js +1 -11
  43. package/dist/canvas/SceneCanvas/props.js.map +1 -1
  44. package/dist/canvas/SceneCanvas/scenePointQuery.js +7 -3
  45. package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -1
  46. package/dist/canvas/SceneCanvas/toolRegistry.js +8 -4
  47. package/dist/canvas/SceneCanvas/toolRegistry.js.map +1 -1
  48. package/dist/canvas/SceneCanvas/useActionTunings.js +6 -1
  49. package/dist/canvas/SceneCanvas/useActionTunings.js.map +1 -1
  50. package/dist/canvas/SceneCanvas/useActionsPropResolver.js +1 -11
  51. package/dist/canvas/SceneCanvas/useActionsPropResolver.js.map +1 -1
  52. package/dist/canvas/SceneCanvas/useBuiltinShapeTools.js.map +1 -1
  53. package/dist/canvas/SceneCanvas/useBuiltinTools.js +2 -9
  54. package/dist/canvas/SceneCanvas/useBuiltinTools.js.map +1 -1
  55. package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js +94 -0
  56. package/dist/canvas/SceneCanvas/useCanvasDevtoolsRoot.js.map +1 -0
  57. package/dist/canvas/SceneCanvas/useCanvasSelection.js +7 -2
  58. package/dist/canvas/SceneCanvas/useCanvasSelection.js.map +1 -1
  59. package/dist/canvas/SceneCanvas/useCanvasTools.js +15 -10
  60. package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
  61. package/dist/canvas/SceneCanvas/useNodeKinds.js +1 -1
  62. package/dist/canvas/SceneCanvas/useNodeKinds.js.map +1 -1
  63. package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js +9 -3
  64. package/dist/canvas/SceneCanvas/useSceneCanvasHandle.js.map +1 -1
  65. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +23 -4
  66. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
  67. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +2 -3
  68. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
  69. package/dist/canvas/SceneCanvas/useSceneChrome.js +6 -7
  70. package/dist/canvas/SceneCanvas/useSceneChrome.js.map +1 -1
  71. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js +24 -7
  72. package/dist/canvas/SceneCanvas/useSceneGestureDispatcher.js.map +1 -1
  73. package/dist/canvas/SceneCanvas/useScenePicking.js +2 -3
  74. package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
  75. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +2 -1
  76. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  77. package/dist/canvas/SceneCanvas/useSceneStandardActions.js +5 -5
  78. package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
  79. package/dist/canvas/SceneCanvas.js +27 -24
  80. package/dist/canvas/SceneCanvas.js.map +1 -1
  81. package/dist/canvas/SurfaceCanvas.js +5 -22
  82. package/dist/canvas/SurfaceCanvas.js.map +1 -1
  83. package/dist/canvas/ViewMarker.js +35 -0
  84. package/dist/canvas/ViewMarker.js.map +1 -0
  85. package/dist/canvas/canvasRenderer.js +9 -5
  86. package/dist/canvas/canvasRenderer.js.map +1 -1
  87. package/dist/canvas/contextAttributes.js +14 -0
  88. package/dist/canvas/contextAttributes.js.map +1 -0
  89. package/dist/canvas/defaultDrawOne.js +2 -0
  90. package/dist/canvas/defaultDrawOne.js.map +1 -1
  91. package/dist/canvas/deps/booleans.js +2 -1
  92. package/dist/canvas/deps/booleans.js.map +1 -1
  93. package/dist/canvas/deps/editAnchors.js +9 -113
  94. package/dist/canvas/deps/editAnchors.js.map +1 -1
  95. package/dist/canvas/deps/hitTestArea.js +2 -2
  96. package/dist/canvas/deps/hitTestArea.js.map +1 -1
  97. package/dist/canvas/deps/insert.js +10 -4
  98. package/dist/canvas/deps/insert.js.map +1 -1
  99. package/dist/canvas/deps/pathEditCommits.js +189 -0
  100. package/dist/canvas/deps/pathEditCommits.js.map +1 -0
  101. package/dist/canvas/deps/snap.js +12 -9
  102. package/dist/canvas/deps/snap.js.map +1 -1
  103. package/dist/canvas/deps/transformFollowers.js +20 -0
  104. package/dist/canvas/deps/transformFollowers.js.map +1 -0
  105. package/dist/canvas/derivedPath.js +20 -1
  106. package/dist/canvas/derivedPath.js.map +1 -1
  107. package/dist/canvas/drawnBounds.js +38 -4
  108. package/dist/canvas/drawnBounds.js.map +1 -1
  109. package/dist/canvas/inheritedPaints.js +44 -0
  110. package/dist/canvas/inheritedPaints.js.map +1 -0
  111. package/dist/canvas/nodeShapes/derivedPainter.js +23 -16
  112. package/dist/canvas/nodeShapes/derivedPainter.js.map +1 -1
  113. package/dist/canvas/nodeShapes/nodePaint.js +13 -34
  114. package/dist/canvas/nodeShapes/nodePaint.js.map +1 -1
  115. package/dist/canvas/nodeShapes/pathPainter.js +12 -10
  116. package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
  117. package/dist/canvas/nodeShapes/rectFallbackPainter.js +4 -5
  118. package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -1
  119. package/dist/canvas/nodeShapes/shapePainter.js +12 -11
  120. package/dist/canvas/nodeShapes/shapePainter.js.map +1 -1
  121. package/dist/canvas/nodeShapes/textPainter.js +13 -12
  122. package/dist/canvas/nodeShapes/textPainter.js.map +1 -1
  123. package/dist/canvas/overlayRoleStrokes.js +12 -0
  124. package/dist/canvas/overlayRoleStrokes.js.map +1 -1
  125. package/dist/canvas/pathOverlay.js +27 -7
  126. package/dist/canvas/pathOverlay.js.map +1 -1
  127. package/dist/canvas/pickReach.js +3 -3
  128. package/dist/canvas/pickReach.js.map +1 -1
  129. package/dist/canvas/pickWalk.js +3 -1
  130. package/dist/canvas/pickWalk.js.map +1 -1
  131. package/dist/canvas/renderDebugSnapshot.js +1 -0
  132. package/dist/canvas/renderDebugSnapshot.js.map +1 -1
  133. package/dist/canvas/renderSceneToPixels.js +7 -6
  134. package/dist/canvas/renderSceneToPixels.js.map +1 -1
  135. package/dist/canvas/sceneAdapter.js.map +1 -1
  136. package/dist/canvas/sceneSlotWiring.js +2 -1
  137. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  138. package/dist/canvas/sceneViewRender.js +2 -1
  139. package/dist/canvas/sceneViewRender.js.map +1 -1
  140. package/dist/canvas/surfaceContribution.js +12 -8
  141. package/dist/canvas/surfaceContribution.js.map +1 -1
  142. package/dist/canvas/useCanvasPaint.js +2 -1
  143. package/dist/canvas/useCanvasPaint.js.map +1 -1
  144. package/dist/canvas/useCanvasView.js +4 -2
  145. package/dist/canvas/useCanvasView.js.map +1 -1
  146. package/dist/canvas/useInFlightBounds.js +52 -0
  147. package/dist/canvas/useInFlightBounds.js.map +1 -0
  148. package/dist/canvas/useLayerStack.js +6 -2
  149. package/dist/canvas/useLayerStack.js.map +1 -1
  150. package/dist/canvas/useViewAim.js +121 -0
  151. package/dist/canvas/useViewAim.js.map +1 -0
  152. package/dist/canvas/useViewHelpers.js +7 -9
  153. package/dist/canvas/useViewHelpers.js.map +1 -1
  154. package/dist/canvas/useViewNesting.js +3 -2
  155. package/dist/canvas/useViewNesting.js.map +1 -1
  156. package/dist/canvas/useViewRegistrySurface.js +4 -2
  157. package/dist/canvas/useViewRegistrySurface.js.map +1 -1
  158. package/dist/canvas/viewMarkers.js +63 -0
  159. package/dist/canvas/viewMarkers.js.map +1 -0
  160. package/dist/canvas/viewRegistry.js +27 -1
  161. package/dist/canvas/viewRegistry.js.map +1 -1
  162. package/dist/clipboard.d.ts +3 -2
  163. package/dist/{clone-DZDrfIlU.d.ts → clone-C3e0OTuF.d.ts} +8 -21
  164. package/dist/clone.d.ts +4 -3
  165. package/dist/core/geometry/autoPoseDescriptor.js +1 -1
  166. package/dist/core/ops/setDependsOn.js +37 -0
  167. package/dist/core/ops/setDependsOn.js.map +1 -0
  168. package/dist/core/ops/writtenBy.js +53 -0
  169. package/dist/core/ops/writtenBy.js.map +1 -0
  170. package/dist/core/pages/fitViewToPage.js +12 -1
  171. package/dist/core/pages/fitViewToPage.js.map +1 -1
  172. package/dist/core/pages/pageEdits.js +163 -0
  173. package/dist/core/pages/pageEdits.js.map +1 -0
  174. package/dist/core/pages/pages.js +30 -1
  175. package/dist/core/pages/pages.js.map +1 -1
  176. package/dist/core/paint/meshKindLazy.js +59 -2
  177. package/dist/core/paint/meshKindLazy.js.map +1 -1
  178. package/dist/core/scene/NodeRouting.js +8 -3
  179. package/dist/core/scene/NodeRouting.js.map +1 -1
  180. package/dist/core/scene/arrivalWindow.js.map +1 -1
  181. package/dist/core/scene/carryDescendants.js +51 -0
  182. package/dist/core/scene/carryDescendants.js.map +1 -0
  183. package/dist/core/scene/clipNothing.js +13 -0
  184. package/dist/core/scene/clipNothing.js.map +1 -0
  185. package/dist/core/scene/depMemo.js +11 -3
  186. package/dist/core/scene/depMemo.js.map +1 -1
  187. package/dist/core/scene/dependents.js +7 -1
  188. package/dist/core/scene/dependents.js.map +1 -1
  189. package/dist/core/scene/derivedParts.js +10 -11
  190. package/dist/core/scene/derivedParts.js.map +1 -1
  191. package/dist/core/scene/docValues.js +57 -0
  192. package/dist/core/scene/docValues.js.map +1 -0
  193. package/dist/core/scene/editPass.js +111 -0
  194. package/dist/core/scene/editPass.js.map +1 -0
  195. package/dist/core/scene/effectivePose.js +13 -2
  196. package/dist/core/scene/effectivePose.js.map +1 -1
  197. package/dist/core/scene/fieldEdits.js +4 -1
  198. package/dist/core/scene/fieldEdits.js.map +1 -1
  199. package/dist/core/scene/fontReads.js +37 -0
  200. package/dist/core/scene/fontReads.js.map +1 -0
  201. package/dist/core/scene/historyFacade.js +1 -0
  202. package/dist/core/scene/historyFacade.js.map +1 -1
  203. package/dist/core/scene/inheritedPaint.js +75 -0
  204. package/dist/core/scene/inheritedPaint.js.map +1 -0
  205. package/dist/core/scene/kitRegistry.js +5 -0
  206. package/dist/core/scene/kitRegistry.js.map +1 -1
  207. package/dist/core/scene/layoutPass.js +28 -3
  208. package/dist/core/scene/layoutPass.js.map +1 -1
  209. package/dist/core/scene/nodeMemo.js +15 -7
  210. package/dist/core/scene/nodeMemo.js.map +1 -1
  211. package/dist/core/scene/pathEffects/blend.js.map +1 -1
  212. package/dist/core/scene/pathEffects/derive.js +7 -2
  213. package/dist/core/scene/pathEffects/derive.js.map +1 -1
  214. package/dist/core/scene/pathEffects/effects.js +3 -1
  215. package/dist/core/scene/pathEffects/effects.js.map +1 -1
  216. package/dist/core/scene/pathEffects/textAlong.js +95 -0
  217. package/dist/core/scene/pathEffects/textAlong.js.map +1 -0
  218. package/dist/core/scene/recorder.js +28 -19
  219. package/dist/core/scene/recorder.js.map +1 -1
  220. package/dist/core/scene/scene.js +15 -9
  221. package/dist/core/scene/scene.js.map +1 -1
  222. package/dist/core/scene/sceneChanges.js +13 -0
  223. package/dist/core/scene/sceneChanges.js.map +1 -0
  224. package/dist/core/scene/sceneEdits.js +8 -1
  225. package/dist/core/scene/sceneEdits.js.map +1 -1
  226. package/dist/core/scene/sceneMutators.js +1 -0
  227. package/dist/core/scene/sceneMutators.js.map +1 -1
  228. package/dist/core/scene/sceneRecording.js +20 -3
  229. package/dist/core/scene/sceneRecording.js.map +1 -1
  230. package/dist/core/scene/sceneSelection.js +4 -1
  231. package/dist/core/scene/sceneSelection.js.map +1 -1
  232. package/dist/core/scene/sceneState.js +11 -5
  233. package/dist/core/scene/sceneState.js.map +1 -1
  234. package/dist/core/scene/serialization.js +5 -3
  235. package/dist/core/scene/serialization.js.map +1 -1
  236. package/dist/core/scene/types.js.map +1 -1
  237. package/dist/core/scene/unitScopes.js +19 -1
  238. package/dist/core/scene/unitScopes.js.map +1 -1
  239. package/dist/core/selection/usePublishSceneSelection.js +6 -12
  240. package/dist/core/selection/usePublishSceneSelection.js.map +1 -1
  241. package/dist/core/shapeKinds.js +25 -11
  242. package/dist/core/shapeKinds.js.map +1 -1
  243. package/dist/{cornerHandles-Camw_yeu.d.ts → cornerHandles-p8fDIAKt.d.ts} +3 -2
  244. package/dist/debug/createDebugOverlayLayer.js +16 -111
  245. package/dist/debug/createDebugOverlayLayer.js.map +1 -1
  246. package/dist/debug/createDebugSink.js +2 -1
  247. package/dist/debug/createDebugSink.js.map +1 -1
  248. package/dist/debug/defaultTheme.js +4 -2
  249. package/dist/debug/defaultTheme.js.map +1 -1
  250. package/dist/debug/features.js +17 -0
  251. package/dist/debug/features.js.map +1 -0
  252. package/dist/debug/overlayPanels.js +117 -0
  253. package/dist/debug/overlayPanels.js.map +1 -0
  254. package/dist/debug/parseDebugFlags.js +4 -13
  255. package/dist/debug/parseDebugFlags.js.map +1 -1
  256. package/dist/{depSchema-Cgfu_G9s.d.ts → depSchema-mG19aEdD.d.ts} +174 -904
  257. package/dist/devtools/handlers.js +112 -0
  258. package/dist/devtools/handlers.js.map +1 -0
  259. package/dist/devtools/history.js +71 -0
  260. package/dist/devtools/history.js.map +1 -0
  261. package/dist/devtools/hook.js +74 -0
  262. package/dist/devtools/hook.js.map +1 -0
  263. package/dist/devtools/label.js +25 -0
  264. package/dist/devtools/label.js.map +1 -0
  265. package/dist/devtools/notices.js +58 -0
  266. package/dist/devtools/notices.js.map +1 -0
  267. package/dist/devtools/protocol.js +7 -0
  268. package/dist/devtools/protocol.js.map +1 -0
  269. package/dist/devtools/registry.js +90 -0
  270. package/dist/devtools/registry.js.map +1 -0
  271. package/dist/devtools/root.js +80 -0
  272. package/dist/devtools/root.js.map +1 -0
  273. package/dist/devtools/serialize.js +86 -0
  274. package/dist/devtools/serialize.js.map +1 -0
  275. package/dist/devtools/session.js +111 -0
  276. package/dist/devtools/session.js.map +1 -0
  277. package/dist/devtools/transport.js +67 -0
  278. package/dist/devtools/transport.js.map +1 -0
  279. package/dist/devtools/useDevtoolsRoot.js +81 -0
  280. package/dist/devtools/useDevtoolsRoot.js.map +1 -0
  281. package/dist/devtools-protocol.d.ts +435 -0
  282. package/dist/devtools-protocol.js +7 -0
  283. package/dist/devtools.d.ts +8 -0
  284. package/dist/devtools.js +12 -0
  285. package/dist/devtools.js.map +1 -0
  286. package/dist/features/chrome-caps/useHoverTracking.js +16 -10
  287. package/dist/features/chrome-caps/useHoverTracking.js.map +1 -1
  288. package/dist/features/ingestion/contentHandlers.js +25 -2
  289. package/dist/features/ingestion/contentHandlers.js.map +1 -1
  290. package/dist/features/measure/measureCommands.js +7 -5
  291. package/dist/features/measure/measureCommands.js.map +1 -1
  292. package/dist/features/measure/measureContribution.js +4 -2
  293. package/dist/features/measure/measureContribution.js.map +1 -1
  294. package/dist/features/modes/modeTriggers.js +7 -3
  295. package/dist/features/modes/modeTriggers.js.map +1 -1
  296. package/dist/features/pathEffects/effectSources.js +60 -0
  297. package/dist/features/pathEffects/effectSources.js.map +1 -0
  298. package/dist/features/pathEffects/readSource.js +17 -1
  299. package/dist/features/pathEffects/readSource.js.map +1 -1
  300. package/dist/features/pathEffects/textItems.js +53 -0
  301. package/dist/features/pathEffects/textItems.js.map +1 -0
  302. package/dist/features/pathEffects/useEffectSourcesLayer.js +57 -0
  303. package/dist/features/pathEffects/useEffectSourcesLayer.js.map +1 -0
  304. package/dist/features/paths/index.js +2 -2
  305. package/dist/features/paths/pathEditExtension.js.map +1 -1
  306. package/dist/features/paths/pathEditState.js +5 -2
  307. package/dist/features/paths/pathEditState.js.map +1 -1
  308. package/dist/features/paths/pathSnap/pathSnapSource.js +5 -5
  309. package/dist/features/paths/pathSnap/pathSnapSource.js.map +1 -1
  310. package/dist/features/paths/pathSnap/sceneIndex.js +10 -3
  311. package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
  312. package/dist/features/selection/selectionPose.js +4 -1
  313. package/dist/features/selection/selectionPose.js.map +1 -1
  314. package/dist/features/snap/gather.js +9 -1
  315. package/dist/features/snap/gather.js.map +1 -1
  316. package/dist/features/snap/lazySession.js +17 -0
  317. package/dist/features/snap/lazySession.js.map +1 -0
  318. package/dist/features/snap/marks.js.map +1 -1
  319. package/dist/features/snap/probes2d.js +27 -19
  320. package/dist/features/snap/probes2d.js.map +1 -1
  321. package/dist/features/snap/readoutLayer.js.map +1 -1
  322. package/dist/features/snap/roles.js +2 -1
  323. package/dist/features/snap/roles.js.map +1 -1
  324. package/dist/features/snap/rotationSource.js +25 -8
  325. package/dist/features/snap/rotationSource.js.map +1 -1
  326. package/dist/features/snap/sceneSession2d.js +6 -4
  327. package/dist/features/snap/sceneSession2d.js.map +1 -1
  328. package/dist/features/snap/session2d.js +37 -23
  329. package/dist/features/snap/session2d.js.map +1 -1
  330. package/dist/features/snap/spanSnap.js +5 -2
  331. package/dist/features/snap/spanSnap.js.map +1 -1
  332. package/dist/features/text/sceneTextDrafts.js +65 -0
  333. package/dist/features/text/sceneTextDrafts.js.map +1 -0
  334. package/dist/features/text/textHitAt.js +31 -2
  335. package/dist/features/text/textHitAt.js.map +1 -1
  336. package/dist/features/text/useSceneTextEdit.js +22 -18
  337. package/dist/features/text/useSceneTextEdit.js.map +1 -1
  338. package/dist/features/viewports/aim.js +105 -0
  339. package/dist/features/viewports/aim.js.map +1 -0
  340. package/dist/features/viewports/viewMarker.js +82 -0
  341. package/dist/features/viewports/viewMarker.js.map +1 -0
  342. package/dist/features/viewports/viewMarkerDrag.js +55 -0
  343. package/dist/features/viewports/viewMarkerDrag.js.map +1 -0
  344. package/dist/icons.table.js +1 -1
  345. package/dist/icons.table.js.map +1 -1
  346. package/dist/icons.table.json +0 -10
  347. package/dist/index.d.ts +1198 -894
  348. package/dist/index.js +55 -38
  349. package/dist/interactions/actions/align/action.js +6 -4
  350. package/dist/interactions/actions/align/action.js.map +1 -1
  351. package/dist/interactions/actions/align/align.js.map +1 -1
  352. package/dist/interactions/actions/booleans/action.js +7 -1
  353. package/dist/interactions/actions/booleans/action.js.map +1 -1
  354. package/dist/interactions/actions/booleans/booleans.js +2 -1
  355. package/dist/interactions/actions/booleans/booleans.js.map +1 -1
  356. package/dist/interactions/actions/booleans/operands.js +6 -5
  357. package/dist/interactions/actions/booleans/operands.js.map +1 -1
  358. package/dist/interactions/actions/clipboard/action.js +27 -17
  359. package/dist/interactions/actions/clipboard/action.js.map +1 -1
  360. package/dist/interactions/actions/clone/action.js +1 -1
  361. package/dist/interactions/actions/clone/behaviors.js +4 -4
  362. package/dist/interactions/actions/clone/behaviors.js.map +1 -1
  363. package/dist/interactions/actions/defaults/anchorEditing.js +44 -23
  364. package/dist/interactions/actions/defaults/anchorEditing.js.map +1 -1
  365. package/dist/interactions/actions/defaults/anchorJoin.js +12 -6
  366. package/dist/interactions/actions/defaults/anchorJoin.js.map +1 -1
  367. package/dist/interactions/actions/defaults/bendSegment.js +8 -5
  368. package/dist/interactions/actions/defaults/bendSegment.js.map +1 -1
  369. package/dist/interactions/actions/defaults/cancelGesture.js +11 -8
  370. package/dist/interactions/actions/defaults/cancelGesture.js.map +1 -1
  371. package/dist/interactions/actions/defaults/contextMenu.js +17 -8
  372. package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
  373. package/dist/interactions/actions/defaults/createPaintAction.js +2 -2
  374. package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
  375. package/dist/interactions/actions/defaults/delete.js +64 -41
  376. package/dist/interactions/actions/defaults/delete.js.map +1 -1
  377. package/dist/interactions/actions/defaults/duplicate.js +12 -9
  378. package/dist/interactions/actions/defaults/duplicate.js.map +1 -1
  379. package/dist/interactions/actions/defaults/enterPathEdit.js +19 -9
  380. package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
  381. package/dist/interactions/actions/defaults/escape.js +11 -8
  382. package/dist/interactions/actions/defaults/escape.js.map +1 -1
  383. package/dist/interactions/actions/defaults/exitPathEdit.js +7 -4
  384. package/dist/interactions/actions/defaults/exitPathEdit.js.map +1 -1
  385. package/dist/interactions/actions/defaults/group.js +21 -15
  386. package/dist/interactions/actions/defaults/group.js.map +1 -1
  387. package/dist/interactions/actions/defaults/ingest.js +17 -9
  388. package/dist/interactions/actions/defaults/ingest.js.map +1 -1
  389. package/dist/interactions/actions/defaults/insertPathAnchor.js +7 -4
  390. package/dist/interactions/actions/defaults/insertPathAnchor.js.map +1 -1
  391. package/dist/interactions/actions/defaults/nudge.js +21 -40
  392. package/dist/interactions/actions/defaults/nudge.js.map +1 -1
  393. package/dist/interactions/actions/defaults/pathEditTools.js +11 -8
  394. package/dist/interactions/actions/defaults/pathEditTools.js.map +1 -1
  395. package/dist/interactions/actions/defaults/pinchZoom.js +7 -4
  396. package/dist/interactions/actions/defaults/pinchZoom.js.map +1 -1
  397. package/dist/interactions/actions/defaults/reorder.js +24 -6
  398. package/dist/interactions/actions/defaults/reorder.js.map +1 -1
  399. package/dist/interactions/actions/defaults/selectAll.js +8 -5
  400. package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
  401. package/dist/interactions/actions/defaults/setFill.js +7 -3
  402. package/dist/interactions/actions/defaults/setFill.js.map +1 -1
  403. package/dist/interactions/actions/defaults/setFillOpacity.js +1 -1
  404. package/dist/interactions/actions/defaults/setStroke.js +12 -11
  405. package/dist/interactions/actions/defaults/setStroke.js.map +1 -1
  406. package/dist/interactions/actions/defaults/setStrokeOpacity.js +1 -1
  407. package/dist/interactions/actions/defaults/toolActivate.js +7 -1
  408. package/dist/interactions/actions/defaults/toolActivate.js.map +1 -1
  409. package/dist/interactions/actions/defaults/toolResetToDefault.js +7 -4
  410. package/dist/interactions/actions/defaults/toolResetToDefault.js.map +1 -1
  411. package/dist/interactions/actions/defaults/undoRedo.js +19 -13
  412. package/dist/interactions/actions/defaults/undoRedo.js.map +1 -1
  413. package/dist/interactions/actions/defaults/viewportWheelPan.js +16 -7
  414. package/dist/interactions/actions/defaults/viewportWheelPan.js.map +1 -1
  415. package/dist/interactions/actions/defaults/viewportZoom.js +24 -6
  416. package/dist/interactions/actions/defaults/viewportZoom.js.map +1 -1
  417. package/dist/interactions/actions/distribute/action.js +6 -4
  418. package/dist/interactions/actions/distribute/action.js.map +1 -1
  419. package/dist/interactions/actions/drawnBoxes.js +2 -1
  420. package/dist/interactions/actions/drawnBoxes.js.map +1 -1
  421. package/dist/interactions/actions/edit-anchors/action.js +10 -10
  422. package/dist/interactions/actions/edit-anchors/action.js.map +1 -1
  423. package/dist/interactions/actions/edit-anchors/preview.js +41 -8
  424. package/dist/interactions/actions/edit-anchors/preview.js.map +1 -1
  425. package/dist/interactions/actions/flip/action.js +14 -6
  426. package/dist/interactions/actions/flip/action.js.map +1 -1
  427. package/dist/interactions/actions/gestureLifecycle.js +5 -2
  428. package/dist/interactions/actions/gestureLifecycle.js.map +1 -1
  429. package/dist/interactions/actions/insert/action.js +1 -2
  430. package/dist/interactions/actions/insert/action.js.map +1 -1
  431. package/dist/interactions/actions/insert/commit.js +1 -1
  432. package/dist/interactions/actions/insert/parent.js +11 -0
  433. package/dist/interactions/actions/insert/parent.js.map +1 -0
  434. package/dist/interactions/actions/insert/placeAction.js +7 -2
  435. package/dist/interactions/actions/insert/placeAction.js.map +1 -1
  436. package/dist/interactions/actions/insert/repositionAction.js +2 -1
  437. package/dist/interactions/actions/insert/repositionAction.js.map +1 -1
  438. package/dist/interactions/actions/insert/session.js +5 -1
  439. package/dist/interactions/actions/insert/session.js.map +1 -1
  440. package/dist/interactions/actions/move/action.js +15 -8
  441. package/dist/interactions/actions/move/action.js.map +1 -1
  442. package/dist/interactions/actions/move/behaviors/layoutDrop.js +0 -1
  443. package/dist/interactions/actions/move/behaviors/layoutDrop.js.map +1 -1
  444. package/dist/interactions/actions/move/behaviors/snapToContainer.js +1 -1
  445. package/dist/interactions/actions/move/capture.js +1 -1
  446. package/dist/interactions/actions/move/commit/index.js +0 -2
  447. package/dist/interactions/actions/move/commit/index.js.map +1 -1
  448. package/dist/interactions/actions/move/commit/layoutDrop.js +2 -2
  449. package/dist/interactions/actions/move/commit/layoutRelease.js +2 -2
  450. package/dist/interactions/actions/move/commit/reparent.js +11 -5
  451. package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
  452. package/dist/interactions/actions/move/frame.js +4 -10
  453. package/dist/interactions/actions/move/frame.js.map +1 -1
  454. package/dist/interactions/actions/move/landedDescendants.js +20 -17
  455. package/dist/interactions/actions/move/landedDescendants.js.map +1 -1
  456. package/dist/interactions/actions/move/layout/container.js +2 -1
  457. package/dist/interactions/actions/move/layout/container.js.map +1 -1
  458. package/dist/interactions/actions/move/layout/dragged.js +1 -1
  459. package/dist/interactions/actions/move/layout/dropPass.js +18 -1
  460. package/dist/interactions/actions/move/layout/dropPass.js.map +1 -1
  461. package/dist/interactions/actions/move/layout/exits.js +5 -5
  462. package/dist/interactions/actions/move/layout/exits.js.map +1 -1
  463. package/dist/interactions/actions/move/session.js +14 -3
  464. package/dist/interactions/actions/move/session.js.map +1 -1
  465. package/dist/interactions/actions/move/translate.js +8 -37
  466. package/dist/interactions/actions/move/translate.js.map +1 -1
  467. package/dist/interactions/actions/moveNodes.js +87 -0
  468. package/dist/interactions/actions/moveNodes.js.map +1 -0
  469. package/dist/interactions/actions/outlines/createOutlinesAction.js +8 -5
  470. package/dist/interactions/actions/outlines/createOutlinesAction.js.map +1 -1
  471. package/dist/interactions/actions/outlines/outlineStroke.js +5 -3
  472. package/dist/interactions/actions/outlines/outlineStroke.js.map +1 -1
  473. package/dist/interactions/actions/pathCommands/cutJoin.js +8 -5
  474. package/dist/interactions/actions/pathCommands/cutJoin.js.map +1 -1
  475. package/dist/interactions/actions/planeInput.js.map +1 -1
  476. package/dist/interactions/actions/resize/action.js +83 -72
  477. package/dist/interactions/actions/resize/action.js.map +1 -1
  478. package/dist/interactions/actions/resize/commit.js +2 -1
  479. package/dist/interactions/actions/resize/commit.js.map +1 -1
  480. package/dist/interactions/actions/resize/cornerHandles.js +2 -1
  481. package/dist/interactions/actions/resize/cornerHandles.js.map +1 -1
  482. package/dist/interactions/actions/resize/session.js +2 -0
  483. package/dist/interactions/actions/resize/session.js.map +1 -1
  484. package/dist/interactions/actions/resolvePreviews.js +3 -3
  485. package/dist/interactions/actions/resolvePreviews.js.map +1 -1
  486. package/dist/interactions/actions/rotate/action.js +7 -4
  487. package/dist/interactions/actions/rotate/action.js.map +1 -1
  488. package/dist/interactions/actions/rotate/commit.js +2 -1
  489. package/dist/interactions/actions/rotate/commit.js.map +1 -1
  490. package/dist/interactions/actions/rotate/session.js +2 -0
  491. package/dist/interactions/actions/rotate/session.js.map +1 -1
  492. package/dist/interactions/actions/selectionSubtree.js +25 -9
  493. package/dist/interactions/actions/selectionSubtree.js.map +1 -1
  494. package/dist/interactions/actions/testUtils.js.map +1 -1
  495. package/dist/interactions/actions/transform-again/action.js +13 -11
  496. package/dist/interactions/actions/transform-again/action.js.map +1 -1
  497. package/dist/interactions/actions/transformFinish.js +106 -0
  498. package/dist/interactions/actions/transformFinish.js.map +1 -0
  499. package/dist/json.d.ts +4 -0
  500. package/dist/json.js +3 -0
  501. package/dist/kitSettingsSchema.js +8 -5
  502. package/dist/kitSettingsSchema.js.map +1 -1
  503. package/dist/{math-CrJULaZI.d.ts → math-B56zxVs5.d.ts} +10 -1
  504. package/dist/math.d.ts +1 -1
  505. package/dist/math.js +2 -1
  506. package/dist/move.d.ts +5 -4
  507. package/dist/{options-p9GZMXbY.d.ts → options-DBxEryYq.d.ts} +1 -1
  508. package/dist/resize.d.ts +4 -3
  509. package/dist/routing.d.ts +1 -1
  510. package/dist/routing.js +2 -2
  511. package/dist/test-hook/createTestHook.js +1 -1
  512. package/dist/test-hook/createTestHook.js.map +1 -1
  513. package/dist/test-hook/useTestHook.js +2 -1
  514. package/dist/test-hook/useTestHook.js.map +1 -1
  515. package/dist/test-seams.d.ts +9 -5
  516. package/dist/tools/builtin/ellipse/useEllipseTool.js +1 -0
  517. package/dist/tools/builtin/ellipse/useEllipseTool.js.map +1 -1
  518. package/dist/tools/builtin/eyedropper/useEyedropperTool.js +2 -1
  519. package/dist/tools/builtin/eyedropper/useEyedropperTool.js.map +1 -1
  520. package/dist/tools/builtin/hand/dragPanContribution.js +4 -1
  521. package/dist/tools/builtin/hand/dragPanContribution.js.map +1 -1
  522. package/dist/tools/builtin/hand/useHandTool.js +7 -4
  523. package/dist/tools/builtin/hand/useHandTool.js.map +1 -1
  524. package/dist/tools/builtin/image/useImageTool.js +1 -0
  525. package/dist/tools/builtin/image/useImageTool.js.map +1 -1
  526. package/dist/tools/builtin/lasso/useLassoTool.js +1 -0
  527. package/dist/tools/builtin/lasso/useLassoTool.js.map +1 -1
  528. package/dist/tools/builtin/line/useLineTool.js +1 -0
  529. package/dist/tools/builtin/line/useLineTool.js.map +1 -1
  530. package/dist/tools/builtin/measure/useMeasureTool.js +12 -5
  531. package/dist/tools/builtin/measure/useMeasureTool.js.map +1 -1
  532. package/dist/tools/builtin/pen/penHandle.js +3 -5
  533. package/dist/tools/builtin/pen/penHandle.js.map +1 -1
  534. package/dist/tools/builtin/pen/usePenTool.js +14 -6
  535. package/dist/tools/builtin/pen/usePenTool.js.map +1 -1
  536. package/dist/tools/builtin/pencil/usePencilTool.js +1 -0
  537. package/dist/tools/builtin/pencil/usePencilTool.js.map +1 -1
  538. package/dist/tools/builtin/polygon/usePolygonTool.js +13 -4
  539. package/dist/tools/builtin/polygon/usePolygonTool.js.map +1 -1
  540. package/dist/tools/builtin/rect/useRectTool.js +1 -0
  541. package/dist/tools/builtin/rect/useRectTool.js.map +1 -1
  542. package/dist/tools/builtin/select/adapterPointQuery.js +3 -2
  543. package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
  544. package/dist/tools/builtin/select/pressDecision.js +32 -0
  545. package/dist/tools/builtin/select/pressDecision.js.map +1 -0
  546. package/dist/tools/builtin/select/selectionContributions.js +22 -13
  547. package/dist/tools/builtin/select/selectionContributions.js.map +1 -1
  548. package/dist/tools/builtin/select/useSelectTool.js +13 -10
  549. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  550. package/dist/tools/builtin/slice/useSliceTool.js +22 -8
  551. package/dist/tools/builtin/slice/useSliceTool.js.map +1 -1
  552. package/dist/tools/builtin/star/useStarTool.js +13 -4
  553. package/dist/tools/builtin/star/useStarTool.js.map +1 -1
  554. package/dist/tools/builtin/sticky/stickyNode.js +4 -1
  555. package/dist/tools/builtin/sticky/stickyNode.js.map +1 -1
  556. package/dist/tools/builtin/sticky/useStickyTool.js +10 -5
  557. package/dist/tools/builtin/sticky/useStickyTool.js.map +1 -1
  558. package/dist/tools/builtin/text/useTextTool.js +3 -1
  559. package/dist/tools/builtin/text/useTextTool.js.map +1 -1
  560. package/dist/tools/index.js +26 -0
  561. package/dist/{types-CHVXGfYm.d.ts → types-7puAuVD2.d.ts} +3 -262
  562. package/dist/types-BN9hbdMI.d.ts +184 -0
  563. package/dist/types-CniVctmk.d.ts +949 -0
  564. package/dist/types-oFeQ5VjV.d.ts +264 -0
  565. package/dist/util/paint.js +1 -3
  566. package/dist/util/paint.js.map +1 -1
  567. package/dist/version.js +1 -1
  568. package/package.json +39 -25
  569. package/dist/canvas/SceneCanvas/useBuiltinRigTools.js +0 -65
  570. package/dist/canvas/SceneCanvas/useBuiltinRigTools.js.map +0 -1
  571. package/dist/tools/builtin/bone/boneActions.js +0 -156
  572. package/dist/tools/builtin/bone/boneActions.js.map +0 -1
  573. package/dist/tools/builtin/bone/boneOverlay.js +0 -71
  574. package/dist/tools/builtin/bone/boneOverlay.js.map +0 -1
  575. package/dist/tools/builtin/bone/placedRig.js +0 -28
  576. package/dist/tools/builtin/bone/placedRig.js.map +0 -1
  577. package/dist/tools/builtin/bone/rigEditState.js +0 -30
  578. package/dist/tools/builtin/bone/rigEditState.js.map +0 -1
  579. package/dist/tools/builtin/bone/useBoneTool.js +0 -125
  580. package/dist/tools/builtin/bone/useBoneTool.js.map +0 -1
  581. package/dist/tools/builtin/weight/useWeightTool.js +0 -101
  582. package/dist/tools/builtin/weight/useWeightTool.js.map +0 -1
  583. package/dist/tools/builtin/weight/weightActions.js +0 -140
  584. package/dist/tools/builtin/weight/weightActions.js.map +0 -1
  585. package/dist/tools/builtin/weight/weightOverlay.js +0 -78
  586. package/dist/tools/builtin/weight/weightOverlay.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"shapePainter.js","names":[],"sources":["../../../src/canvas/nodeShapes/shapePainter.ts"],"sourcesContent":["import {\n ellipsePath,\n regularPolygonPath,\n starPath,\n type Path\n} from '@weasel-js/geom';\nimport type { Stroke, Fill } from '@weasel-js/paint';\nimport type { NodeShapeEntry } from 'canvas/NodeShape';\nimport { nodeMemo } from 'core/scene/nodeMemo';\nimport type { RectPose } from 'core/scene/types';\nimport { commandFill, commandStroke } from '@weasel-js/renderer';\nimport { DEFAULT_SHAPE_FILL } from '../../util/paint';\nimport { PAINT_SLOT } from './memo';\nimport { inkReach, withAnimatedVertexColors, resolveNodeFill, resolveNodeStroke } from './nodePaint';\nimport { paintsPose, poseBoxBounds } from './poseBox';\nimport { outsetPoseBox, strokedPoseBox } from './strokedBounds';\n\n// ─── Built-in painters ─────────────────────────────────────────────────\n/** Built-in shape dispatcher — matches when `data.shape` names a kit-known\n * shape kind. Computes both paint and silhouette from the pose so consumer\n * scenes can declare clipping/rendering directly in JSON without\n * registering a custom painter. */\nexport const SHAPE_PAINTER: NodeShapeEntry<unknown, RectPose> = {\n id: 'kit:shape',\n matches: (node) => {\n if (!paintsPose(node.pose)) return false;\n const s = (node.data as { shape?: string } | null)?.shape;\n return s != null && SHAPE_KINDS.has(s);\n },\n // Memoized: a pure function of `(data, pose)`, and `pathForShape` builds a\n // fresh `Uint8Array` + `Float32Array` for every ellipse, polygon and star.\n // See PAINT_SLOT.\n paint: (node, pose, ctx) => withAnimatedVertexColors(nodeMemo(node, PAINT_SLOT, pose, () => {\n const d = node.data as { shape: string; fill?: Fill | null; stroke?: Stroke | null; sides?: number; points?: number };\n const path = pathForShape(d, pose);\n const declaredFill = resolveNodeFill(d.fill, DEFAULT_SHAPE_FILL);\n const shapeFill = declaredFill && commandFill(declaredFill, pose);\n const strokeSpec = resolveNodeStroke(d.stroke);\n const stroke = strokeSpec && commandStroke(strokeSpec, pose);\n return [{\n kind: 'path',\n path,\n ...(shapeFill ? { fill: shapeFill } : {}),\n ...(stroke ? { stroke } : {}),\n }];\n }), ctx),\n silhouette: (node, pose) => {\n const d = node.data as { shape: string; sides?: number; points?: number };\n return pathForShape(d, pose);\n },\n // `paint` above emits the default fill when none is declared, so a shape\n // is filled unless it declares `fill: null` — unlike `kit:path`, it has no\n // implicit stroke-only form.\n ink: (node, _pose, ctx) => {\n const d = node.data as { fill?: Fill | null; stroke?: Stroke | null };\n return {\n filled: resolveNodeFill(d.fill, DEFAULT_SHAPE_FILL) !== null,\n ...inkReach(resolveNodeStroke(d.stroke), ctx?.scale),\n };\n },\n // Every shape kind is inscribed in the pose box.\n bounds: (node, pose, ctx) =>\n poseBoxBounds(pose, resolveNodeStroke((node.data as { stroke?: Stroke | null }).stroke), ctx),\n // A rect's or an ellipse's stroke reaches its box evenly; a polygon's or a\n // star's corners are sharper than a right angle, so their miters are measured.\n drawnBounds: (node, pose, ctx) => {\n const d = node.data as { shape: string; stroke?: Stroke | null; sides?: number; points?: number };\n const stroke = resolveNodeStroke(d.stroke);\n return d.shape === 'polygon' || d.shape === 'star'\n ? strokedPoseBox(node, pose, stroke, ctx, (at) => pathForShape(d, at))\n : outsetPoseBox(pose, stroke, ctx);\n },\n};\n\nexport const SHAPE_KINDS = new Set(['rect', 'ellipse', 'polygon', 'star']);\n\nexport function pathForShape(\n d: { shape: string; sides?: number; points?: number },\n p: RectPose,\n): Path {\n switch (d.shape) {\n case 'ellipse':\n return ellipsePath(p);\n case 'polygon': {\n const cx = p.x + p.width / 2;\n const cy = p.y + p.height / 2;\n const r = Math.min(p.width, p.height) / 2;\n return regularPolygonPath({ x: cx, y: cy }, r, d.sides ?? 6);\n }\n case 'star': {\n const cx = p.x + p.width / 2;\n const cy = p.y + p.height / 2;\n const r = Math.min(p.width, p.height) / 2;\n return starPath({ x: cx, y: cy }, r, d.points ?? 5);\n }\n case 'rect':\n default:\n return { kind: 'rect', x: p.x, y: p.y, width: p.width, height: p.height };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAsBA,IAAa,gBAAmD;CAC9D,IAAI;CACJ,UAAU,SAAS;EACjB,IAAI,CAAC,WAAW,KAAK,IAAI,GAAG,OAAO;EACnC,MAAM,IAAK,KAAK,MAAoC;EACpD,OAAO,KAAK,QAAQ,YAAY,IAAI,CAAC;CACvC;CAIA,QAAQ,MAAM,MAAM,QAAQ,yBAAyB,SAAS,MAAM,YAAY,YAAY;EAC1F,MAAM,IAAI,KAAK;EACf,MAAM,OAAO,aAAa,GAAG,IAAI;EACjC,MAAM,eAAe,gBAAgB,EAAE,MAAM,kBAAkB;EAC/D,MAAM,YAAY,gBAAgB,YAAY,cAAc,IAAI;EAChE,MAAM,aAAa,kBAAkB,EAAE,MAAM;EAC7C,MAAM,SAAS,cAAc,cAAc,YAAY,IAAI;EAC3D,OAAO,CAAC;GACN,MAAM;GACN;GACA,GAAI,YAAY,EAAE,MAAM,UAAU,IAAI,CAAC;GACvC,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAC7B,CAAC;CACH,CAAC,GAAG,GAAG;CACP,aAAa,MAAM,SAAS;EAC1B,MAAM,IAAI,KAAK;EACf,OAAO,aAAa,GAAG,IAAI;CAC7B;CAIA,MAAM,MAAM,OAAO,QAAQ;EACzB,MAAM,IAAI,KAAK;EACf,OAAO;GACL,QAAQ,gBAAgB,EAAE,MAAM,kBAAkB,MAAM;GACxD,GAAG,SAAS,kBAAkB,EAAE,MAAM,GAAG,KAAK,KAAK;EACrD;CACF;CAEA,SAAS,MAAM,MAAM,QACnB,cAAc,MAAM,kBAAmB,KAAK,KAAoC,MAAM,GAAG,GAAG;CAG9F,cAAc,MAAM,MAAM,QAAQ;EAChC,MAAM,IAAI,KAAK;EACf,MAAM,SAAS,kBAAkB,EAAE,MAAM;EACzC,OAAO,EAAE,UAAU,aAAa,EAAE,UAAU,SACxC,eAAe,MAAM,MAAM,QAAQ,MAAM,OAAO,aAAa,GAAG,EAAE,CAAC,IACnE,cAAc,MAAM,QAAQ,GAAG;CACrC;AACF;AAEA,IAAa,8BAAc,IAAI,IAAI;CAAC;CAAQ;CAAW;CAAW;AAAM,CAAC;AAEzE,SAAgB,aACd,GACA,GACM;CACN,QAAQ,EAAE,OAAV;EACE,KAAK,WACH,OAAO,YAAY,CAAC;EACtB,KAAK,WAAW;GACd,MAAM,KAAK,EAAE,IAAI,EAAE,QAAQ;GAC3B,MAAM,KAAK,EAAE,IAAI,EAAE,SAAS;GAC5B,MAAM,IAAI,KAAK,IAAI,EAAE,OAAO,EAAE,MAAM,IAAI;GACxC,OAAO,mBAAmB;IAAE,GAAG;IAAI,GAAG;GAAG,GAAG,GAAG,EAAE,SAAS,CAAC;EAC7D;EACA,KAAK,QAAQ;GACX,MAAM,KAAK,EAAE,IAAI,EAAE,QAAQ;GAC3B,MAAM,KAAK,EAAE,IAAI,EAAE,SAAS;GAC5B,MAAM,IAAI,KAAK,IAAI,EAAE,OAAO,EAAE,MAAM,IAAI;GACxC,OAAO,SAAS;IAAE,GAAG;IAAI,GAAG;GAAG,GAAG,GAAG,EAAE,UAAU,CAAC;EACpD;EAEA,SACE,OAAO;GAAE,MAAM;GAAQ,GAAG,EAAE;GAAG,GAAG,EAAE;GAAG,OAAO,EAAE;GAAO,QAAQ,EAAE;EAAO;CAC5E;AACF"}
1
+ {"version":3,"file":"shapePainter.js","names":[],"sources":["../../../src/canvas/nodeShapes/shapePainter.ts"],"sourcesContent":["import {\n ellipsePath,\n regularPolygonPath,\n starPath,\n type Path\n} from '@weasel-js/geom';\nimport type { Stroke, Fill, Inherit } from '@weasel-js/paint';\nimport type { NodeShapeEntry } from 'canvas/NodeShape';\nimport { nodeMemo } from 'core/scene/nodeMemo';\nimport type { RectPose } from 'core/scene/types';\nimport { commandFill, commandStroke } from '@weasel-js/renderer';\nimport { PAINT_SLOT } from './memo';\nimport { CONTAINER_INK, inkReach, withAnimatedVertexColors } from './nodePaint';\nimport { resolveFill, resolveStroke } from '@weasel-js/paint';\nimport { paintsPose, poseBoxBounds } from './poseBox';\nimport { outsetPoseBox, strokedPoseBox } from './strokedBounds';\n\ntype ShapeData = {\n shape: string;\n fill?: Fill | Inherit | null;\n stroke?: Stroke | Inherit | null;\n sides?: number;\n points?: number;\n};\n\n// ─── Built-in painters ─────────────────────────────────────────────────\n/** Built-in shape dispatcher — matches when `data.shape` names a kit-known\n * shape kind. Computes both paint and silhouette from the pose so consumer\n * scenes can declare clipping/rendering directly in JSON without\n * registering a custom painter. */\nexport const SHAPE_PAINTER: NodeShapeEntry<unknown, RectPose> = {\n id: 'kit:shape',\n matches: (node) => {\n if (!paintsPose(node.pose)) return false;\n const s = (node.data as { shape?: string } | null)?.shape;\n return s != null && SHAPE_KINDS.has(s);\n },\n // Memoized: a pure function of `(data, pose)`, and `pathForShape` builds a\n // fresh `Uint8Array` + `Float32Array` for every ellipse, polygon and star.\n // See PAINT_SLOT.\n // A container's shape is its clip, and its paint is only what its\n // children's 'inherit' takes.\n paint: (node, pose, ctx) => node.kind === 'container' ? [] : withAnimatedVertexColors(nodeMemo(node, PAINT_SLOT, pose, () => {\n const d = node.data as ShapeData;\n const path = pathForShape(d, pose);\n const declaredFill = resolveFill(d.fill, ctx?.inherited?.fill);\n const shapeFill = declaredFill && commandFill(declaredFill, pose);\n const strokeSpec = resolveStroke(d.stroke, ctx?.inherited?.stroke);\n const stroke = strokeSpec && commandStroke(strokeSpec, pose);\n return [{\n kind: 'path',\n path,\n ...(shapeFill ? { fill: shapeFill } : {}),\n ...(stroke ? { stroke } : {}),\n }];\n }, ctx?.inherited), ctx),\n silhouette: (node, pose) => {\n const d = node.data as { shape: string; sides?: number; points?: number };\n return pathForShape(d, pose);\n },\n ink: (node, _pose, ctx) => {\n if (node.kind === 'container') return CONTAINER_INK;\n const d = node.data as ShapeData;\n return {\n filled: resolveFill(d.fill, ctx?.inherited?.fill) !== null,\n ...inkReach(resolveStroke(d.stroke, ctx?.inherited?.stroke), ctx?.scale),\n };\n },\n // Every shape kind is inscribed in the pose box.\n bounds: (node, pose, ctx) =>\n poseBoxBounds(pose, resolveStroke((node.data as ShapeData).stroke, ctx?.inherited?.stroke), ctx),\n // A rect's or an ellipse's stroke reaches its box evenly; a polygon's or a\n // star's corners are sharper than a right angle, so their miters are measured.\n drawnBounds: (node, pose, ctx) => {\n const d = node.data as ShapeData;\n const stroke = resolveStroke(d.stroke, ctx?.inherited?.stroke);\n return d.shape === 'polygon' || d.shape === 'star'\n ? strokedPoseBox(node, pose, stroke, ctx, (at) => pathForShape(d, at))\n : outsetPoseBox(pose, stroke, ctx);\n },\n};\n\nexport const SHAPE_KINDS = new Set(['rect', 'ellipse', 'polygon', 'star']);\n\nexport function pathForShape(\n d: { shape: string; sides?: number; points?: number },\n p: RectPose,\n): Path {\n switch (d.shape) {\n case 'ellipse':\n return ellipsePath(p);\n case 'polygon': {\n const cx = p.x + p.width / 2;\n const cy = p.y + p.height / 2;\n const r = Math.min(p.width, p.height) / 2;\n return regularPolygonPath({ x: cx, y: cy }, r, d.sides ?? 6);\n }\n case 'star': {\n const cx = p.x + p.width / 2;\n const cy = p.y + p.height / 2;\n const r = Math.min(p.width, p.height) / 2;\n return starPath({ x: cx, y: cy }, r, d.points ?? 5);\n }\n case 'rect':\n default:\n return { kind: 'rect', x: p.x, y: p.y, width: p.width, height: p.height };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AA8BA,IAAa,gBAAmD;CAC9D,IAAI;CACJ,UAAU,SAAS;EACjB,IAAI,CAAC,WAAW,KAAK,IAAI,GAAG,OAAO;EACnC,MAAM,IAAK,KAAK,MAAoC;EACpD,OAAO,KAAK,QAAQ,YAAY,IAAI,CAAC;CACvC;CAMA,QAAQ,MAAM,MAAM,QAAQ,KAAK,SAAS,cAAc,CAAC,IAAI,yBAAyB,SAAS,MAAM,YAAY,YAAY;EAC3H,MAAM,IAAI,KAAK;EACf,MAAM,OAAO,aAAa,GAAG,IAAI;EACjC,MAAM,eAAe,YAAY,EAAE,MAAM,KAAK,WAAW,IAAI;EAC7D,MAAM,YAAY,gBAAgB,YAAY,cAAc,IAAI;EAChE,MAAM,aAAa,cAAc,EAAE,QAAQ,KAAK,WAAW,MAAM;EACjE,MAAM,SAAS,cAAc,cAAc,YAAY,IAAI;EAC3D,OAAO,CAAC;GACN,MAAM;GACN;GACA,GAAI,YAAY,EAAE,MAAM,UAAU,IAAI,CAAC;GACvC,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAC7B,CAAC;CACH,GAAG,KAAK,SAAS,GAAG,GAAG;CACvB,aAAa,MAAM,SAAS;EAC1B,MAAM,IAAI,KAAK;EACf,OAAO,aAAa,GAAG,IAAI;CAC7B;CACA,MAAM,MAAM,OAAO,QAAQ;EACzB,IAAI,KAAK,SAAS,aAAa,OAAO;EACtC,MAAM,IAAI,KAAK;EACf,OAAO;GACL,QAAQ,YAAY,EAAE,MAAM,KAAK,WAAW,IAAI,MAAM;GACtD,GAAG,SAAS,cAAc,EAAE,QAAQ,KAAK,WAAW,MAAM,GAAG,KAAK,KAAK;EACzE;CACF;CAEA,SAAS,MAAM,MAAM,QACnB,cAAc,MAAM,cAAe,KAAK,KAAmB,QAAQ,KAAK,WAAW,MAAM,GAAG,GAAG;CAGjG,cAAc,MAAM,MAAM,QAAQ;EAChC,MAAM,IAAI,KAAK;EACf,MAAM,SAAS,cAAc,EAAE,QAAQ,KAAK,WAAW,MAAM;EAC7D,OAAO,EAAE,UAAU,aAAa,EAAE,UAAU,SACxC,eAAe,MAAM,MAAM,QAAQ,MAAM,OAAO,aAAa,GAAG,EAAE,CAAC,IACnE,cAAc,MAAM,QAAQ,GAAG;CACrC;AACF;AAEA,IAAa,8BAAc,IAAI,IAAI;CAAC;CAAQ;CAAW;CAAW;AAAM,CAAC;AAEzE,SAAgB,aACd,GACA,GACM;CACN,QAAQ,EAAE,OAAV;EACE,KAAK,WACH,OAAO,YAAY,CAAC;EACtB,KAAK,WAAW;GACd,MAAM,KAAK,EAAE,IAAI,EAAE,QAAQ;GAC3B,MAAM,KAAK,EAAE,IAAI,EAAE,SAAS;GAC5B,MAAM,IAAI,KAAK,IAAI,EAAE,OAAO,EAAE,MAAM,IAAI;GACxC,OAAO,mBAAmB;IAAE,GAAG;IAAI,GAAG;GAAG,GAAG,GAAG,EAAE,SAAS,CAAC;EAC7D;EACA,KAAK,QAAQ;GACX,MAAM,KAAK,EAAE,IAAI,EAAE,QAAQ;GAC3B,MAAM,KAAK,EAAE,IAAI,EAAE,SAAS;GAC5B,MAAM,IAAI,KAAK,IAAI,EAAE,OAAO,EAAE,MAAM,IAAI;GACxC,OAAO,SAAS;IAAE,GAAG;IAAI,GAAG;GAAG,GAAG,GAAG,EAAE,UAAU,CAAC;EACpD;EAEA,SACE,OAAO;GAAE,MAAM;GAAQ,GAAG,EAAE;GAAG,GAAG,EAAE;GAAG,OAAO,EAAE;GAAO,QAAQ,EAAE;EAAO;CAC5E;AACF"}
@@ -1,9 +1,10 @@
1
1
  import { nodeMemo } from "../../core/scene/nodeMemo.js";
2
2
  import { PAINT_SLOT } from "./memo.js";
3
- import { inkReach, resolveNodeFill, resolveNodeStroke, strokeOutsetPerAxis } from "./nodePaint.js";
3
+ import { inkReach, strokeOutsetPerAxis } from "./nodePaint.js";
4
4
  import { paintsPose } from "./poseBox.js";
5
5
  import { commandFill, textCommandFromPose } from "@weasel-js/renderer";
6
6
  import { PATH_L, PATH_M, PATH_Z } from "@weasel-js/geom";
7
+ import { INHERIT, resolveFill, resolveStroke } from "@weasel-js/paint";
7
8
  import { textLineBoxes } from "@weasel-js/text";
8
9
 
9
10
  //#region src/canvas/nodeShapes/textPainter.ts
@@ -39,14 +40,14 @@ function textBoxOf(d, p) {
39
40
  * {@link textBoxOf} insets. The stroke goes through the same resolver every
40
41
  * other node kind's does, so one carrying no paint is dropped here rather
41
42
  * than throwing in the renderer. */
42
- function textPoseOf(d, p) {
43
+ function textPoseOf(d, p, inherited) {
43
44
  return {
44
45
  ...textBoxOf(d, p),
45
46
  text: d.text,
46
47
  runs: d.runs,
47
48
  style: d.style,
48
- fill: d.fill,
49
- stroke: resolveNodeStroke(d.stroke) ?? void 0,
49
+ fill: d.fill === INHERIT ? resolveFill(d.fill, inherited?.fill) : d.fill,
50
+ stroke: resolveStroke(d.stroke, inherited?.stroke) ?? void 0,
50
51
  verticalAlign: d.verticalAlign
51
52
  };
52
53
  }
@@ -57,11 +58,11 @@ var TEXT_PAINTER = {
57
58
  if (!paintsPose(node.pose)) return false;
58
59
  return node.data?.text != null;
59
60
  },
60
- paint: (node, pose) => nodeMemo(node, PAINT_SLOT, pose, () => {
61
+ paint: (node, pose, ctx) => nodeMemo(node, PAINT_SLOT, pose, () => {
61
62
  const d = node.data;
62
- const laid = textCommandFromPose(textPoseOf(d, pose));
63
+ const laid = textCommandFromPose(textPoseOf(d, pose, ctx?.inherited));
63
64
  const text = d.overflow === "clip" ? clippedToBox(laid, pose) : laid;
64
- const bg = resolveNodeFill(d.background, null);
65
+ const bg = resolveFill(d.background);
65
66
  if (!bg) return [text];
66
67
  const fill = commandFill(bg, pose);
67
68
  return [{
@@ -75,10 +76,10 @@ var TEXT_PAINTER = {
75
76
  },
76
77
  ...fill ? { fill } : {}
77
78
  }, text];
78
- }),
79
+ }, ctx?.inherited),
79
80
  silhouette: (node, pose) => {
80
81
  const d = node.data;
81
- if (resolveNodeFill(d.background, null)) return {
82
+ if (resolveFill(d.background)) return {
82
83
  kind: "rect",
83
84
  x: pose.x,
84
85
  y: pose.y,
@@ -94,13 +95,13 @@ var TEXT_PAINTER = {
94
95
  const d = node.data;
95
96
  return {
96
97
  filled: true,
97
- ...inkReach(resolveNodeStroke(d.stroke), ctx?.scale)
98
+ ...inkReach(resolveStroke(d.stroke, ctx?.inherited?.stroke), ctx?.scale)
98
99
  };
99
100
  },
100
101
  drawnBounds: (node, pose, ctx) => {
101
102
  const d = node.data;
102
- const stroke = resolveNodeStroke(d.stroke);
103
- return stroke ? strokedLinesBox(pose, textLineBoxes(textPoseOf(d, pose)), strokeOutsetPerAxis(stroke, ctx)) : null;
103
+ const stroke = resolveStroke(d.stroke, ctx?.inherited?.stroke);
104
+ return stroke ? strokedLinesBox(pose, textLineBoxes(textPoseOf(d, pose, ctx?.inherited)), strokeOutsetPerAxis(stroke, ctx)) : null;
104
105
  }
105
106
  };
106
107
  /** Each of `rects` cut to `box`, leaving out any it does not overlap. */
@@ -1 +1 @@
1
- {"version":3,"file":"textPainter.js","names":[],"sources":["../../../src/canvas/nodeShapes/textPainter.ts"],"sourcesContent":["import {\n PATH_L,\n PATH_M,\n PATH_Z,\n type PolygonPath,\n type Rect,\n} from '@weasel-js/geom';\nimport type { Stroke, Fill } from '@weasel-js/paint';\nimport type { StyledRun, TextPose, TextStyle, TextVerticalAlign } from '@weasel-js/text';\nimport { textLineBoxes } from '@weasel-js/text';\nimport type { NodeShapeEntry } from 'canvas/NodeShape';\nimport { nodeMemo } from 'core/scene/nodeMemo';\nimport type { RectPose } from 'core/scene/types';\nimport { commandFill, textCommandFromPose, type DrawCommand } from '@weasel-js/renderer';\nimport { PAINT_SLOT } from './memo';\nimport { inkReach, resolveNodeFill, resolveNodeStroke, strokeOutsetPerAxis } from './nodePaint';\nimport { paintsPose } from './poseBox';\n\n/** The `data` a `kit:text` node carries. */\nexport interface TextNodeData {\n text: string;\n style?: TextStyle;\n runs?: readonly StyledRun[];\n fill?: Fill | null;\n stroke?: Stroke | null;\n verticalAlign?: TextVerticalAlign;\n /** Paint for the whole pose box, drawn under the glyphs. `fill` is the\n * glyphs' own paint, so a box behind them needs a key of its own. */\n background?: Fill | null;\n /** World units between the pose box and the box the text lays out in, on\n * every side. */\n padding?: number;\n /** What happens to text that runs past the pose box: `'visible'` (the\n * default) draws it there, `'clip'` cuts it at the box's edge. */\n overflow?: TextOverflow;\n}\n\n/** How a text node treats lines that run past its box. */\nexport type TextOverflow = 'visible' | 'clip';\n\ntype Box = { x: number; y: number; width: number; height: number };\n\n/** `cmd` cut to `box`: a group whose clip is the box as a rect, which the\n * renderer cuts on the CPU when the frame keeps it axis-aligned. */\nexport function clippedToBox(cmd: DrawCommand, box: Box): DrawCommand {\n return {\n kind: 'group',\n clip: { kind: 'rect', x: box.x, y: box.y, width: box.width, height: box.height },\n children: [cmd],\n };\n}\n\n/** The box a text node's lines lay out in: its pose box, inset by\n * `data.padding` and never past its center. */\nexport function textBoxOf(\n d: Pick<TextNodeData, 'padding'>,\n p: RectPose,\n): { x: number; y: number; width: number; height: number } {\n const pad = Math.max(0, d.padding ?? 0);\n const px = Math.min(pad, p.width / 2);\n const py = Math.min(pad, p.height / 2);\n return { x: p.x + px, y: p.y + py, width: p.width - 2 * px, height: p.height - 2 * py };\n}\n\n/** A `kit:text` node as the `TextPose` every text consumer reads, in the box\n * {@link textBoxOf} insets. The stroke goes through the same resolver every\n * other node kind's does, so one carrying no paint is dropped here rather\n * than throwing in the renderer. */\nexport function textPoseOf(d: TextNodeData, p: RectPose): TextPose {\n return {\n ...textBoxOf(d, p),\n text: d.text,\n runs: d.runs as StyledRun[] | undefined,\n style: d.style,\n fill: d.fill,\n stroke: resolveNodeStroke(d.stroke) ?? undefined,\n verticalAlign: d.verticalAlign,\n };\n}\n\nexport const TEXT_PAINTER: NodeShapeEntry<unknown, RectPose> = {\n id: 'kit:text',\n snapsToBox: true,\n matches: (node) => {\n if (!paintsPose(node.pose)) return false;\n const d = node.data as { text?: string } | null;\n return d?.text != null;\n },\n // Memoized because the renderer's layout cache keys on the `ResolvedRun[]` array\n // identity: a fresh runs array per frame would leave that cache missing on every\n // draw. Both resolvers are pure style merging — no font reads — so `(data, pose)` is\n // the whole key. See PAINT_SLOT.\n //\n // `y` is the top of the first line box, not a baseline — see `TextDrawCommand.y`.\n paint: (node, pose) => nodeMemo(node, PAINT_SLOT, pose, (): DrawCommand[] => {\n const d = node.data as TextNodeData;\n const laid = textCommandFromPose(textPoseOf(d, pose));\n const text = d.overflow === 'clip' ? clippedToBox(laid, pose) : laid;\n const bg = resolveNodeFill(d.background, null);\n if (!bg) return [text];\n const fill = commandFill(bg, pose);\n return [\n { kind: 'path', path: { kind: 'rect', x: pose.x, y: pose.y, width: pose.width, height: pose.height }, ...(fill ? { fill } : {}) },\n text,\n ];\n }),\n // The pose is a layout box, not a bounding box: the union of the line boxes is what\n // the node actually covers, so picking, lasso, and clipping stop grabbing blank space.\n //\n // `null` rather than an empty path when there are no non-blank lines: a caller\n // reading \"no silhouette\" falls back to the pose rect, which keeps an empty text\n // node pickable.\n //\n // A background is ink across the whole box, so a node with one is its box.\n // A clipped node's lines are cut to the box, as they are drawn.\n silhouette: (node, pose) => {\n const d = node.data as TextNodeData;\n if (resolveNodeFill(d.background, null)) {\n return { kind: 'rect', x: pose.x, y: pose.y, width: pose.width, height: pose.height };\n }\n const lines = textLineBoxes(textPoseOf(d, pose));\n const boxes = d.overflow === 'clip' ? cutRects(lines, pose) : lines;\n if (boxes.length === 0) return null;\n return rectsToPath(boxes);\n },\n // `filled` is unconditional, `data.fill: null` included: the silhouette above is line\n // boxes, not glyph ink, so reporting outline-only text as unfilled would leave it\n // grabbable within a stroke width of a box edge and nowhere near the letters.\n ink: (node, _pose, ctx) => {\n const d = node.data as { stroke?: Stroke | null };\n return { filled: true, ...inkReach(resolveNodeStroke(d.stroke), ctx?.scale) };\n },\n drawnBounds: (node, pose, ctx) => {\n const d = node.data as TextNodeData;\n const stroke = resolveNodeStroke(d.stroke);\n return stroke ? strokedLinesBox(pose, textLineBoxes(textPoseOf(d, pose)), strokeOutsetPerAxis(stroke, ctx)) : null;\n },\n};\n\n/** Each of `rects` cut to `box`, leaving out any it does not overlap. */\nfunction cutRects(rects: readonly Box[], box: Box): Box[] {\n const out: Box[] = [];\n for (const r of rects) {\n const x0 = Math.max(r.x, box.x);\n const y0 = Math.max(r.y, box.y);\n const x1 = Math.min(r.x + r.width, box.x + box.width);\n const y1 = Math.min(r.y + r.height, box.y + box.height);\n if (x1 > x0 && y1 > y0) out.push({ x: x0, y: y0, width: x1 - x0, height: y1 - y0 });\n }\n return out;\n}\n\n/**\n * The layout box grown where a stroked line reaches it: each line box, cut to\n * the layout box, then outset by the stroke's reach. A line that overflows the\n * layout box is left to it — the layout box is what a text node claims.\n */\nfunction strokedLinesBox(pose: RectPose, lines: readonly Rect[], reach: { x: number; y: number }): Rect | null {\n if (!(reach.x > 0 || reach.y > 0)) return null;\n let x0 = pose.x, y0 = pose.y, x1 = pose.x + pose.width, y1 = pose.y + pose.height;\n for (const l of lines) {\n const lx0 = Math.max(l.x, pose.x), ly0 = Math.max(l.y, pose.y);\n const lx1 = Math.min(l.x + l.width, pose.x + pose.width), ly1 = Math.min(l.y + l.height, pose.y + pose.height);\n if (lx1 <= lx0 || ly1 <= ly0) continue;\n x0 = Math.min(x0, lx0 - reach.x); y0 = Math.min(y0, ly0 - reach.y);\n x1 = Math.max(x1, lx1 + reach.x); y1 = Math.max(y1, ly1 + reach.y);\n }\n return { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };\n}\n\n/**\n * Union of axis-aligned rects as one multi-contour `PolygonPath`.\n *\n * Every contour winds the same way, so `'nonzero'` reads overlapping rects as\n * covered rather than punching a hole where two lines happen to overlap —\n * which they do the moment `lineHeight` drops below 1.\n */\nexport function rectsToPath(rects: readonly { x: number; y: number; width: number; height: number }[]): PolygonPath {\n const commands = new Uint8Array(rects.length * 6);\n const coords = new Float32Array(rects.length * 10);\n let ci = 0;\n let cmd = 0;\n for (const r of rects) {\n const { x, y, width: w, height: h } = r;\n commands[cmd++] = PATH_M; coords[ci++] = x; coords[ci++] = y;\n commands[cmd++] = PATH_L; coords[ci++] = x + w; coords[ci++] = y;\n commands[cmd++] = PATH_L; coords[ci++] = x + w; coords[ci++] = y + h;\n commands[cmd++] = PATH_L; coords[ci++] = x; coords[ci++] = y + h;\n commands[cmd++] = PATH_L; coords[ci++] = x; coords[ci++] = y;\n commands[cmd++] = PATH_Z;\n }\n return { kind: 'polygon', commands, coords, fillRule: 'nonzero' };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"textPainter.js","names":[],"sources":["../../../src/canvas/nodeShapes/textPainter.ts"],"sourcesContent":["import {\n PATH_L,\n PATH_M,\n PATH_Z,\n type PolygonPath,\n type Rect,\n} from '@weasel-js/geom';\nimport type { Stroke, Fill, Inherit } from '@weasel-js/paint';\nimport type { StyledRun, TextPose, TextStyle, TextVerticalAlign } from '@weasel-js/text';\nimport { textLineBoxes } from '@weasel-js/text';\nimport type { NodeShapeEntry } from 'canvas/NodeShape';\nimport { nodeMemo } from 'core/scene/nodeMemo';\nimport type { RectPose } from 'core/scene/types';\nimport { commandFill, textCommandFromPose, type DrawCommand } from '@weasel-js/renderer';\nimport { PAINT_SLOT } from './memo';\nimport { inkReach, strokeOutsetPerAxis } from './nodePaint';\nimport { INHERIT, resolveFill, resolveStroke } from '@weasel-js/paint';\nimport type { InheritedPaint } from 'core/scene/inheritedPaint';\nimport { paintsPose } from './poseBox';\n\n/** The `data` a `kit:text` node carries. */\nexport interface TextNodeData {\n text: string;\n style?: TextStyle;\n runs?: readonly StyledRun[];\n fill?: Fill | Inherit | null;\n stroke?: Stroke | Inherit | null;\n verticalAlign?: TextVerticalAlign;\n /** Paint for the whole pose box, drawn under the glyphs. `fill` is the\n * glyphs' own paint, so a box behind them needs a key of its own. */\n background?: Fill | null;\n /** World units between the pose box and the box the text lays out in, on\n * every side. */\n padding?: number;\n /** What happens to text that runs past the pose box: `'visible'` (the\n * default) draws it there, `'clip'` cuts it at the box's edge. */\n overflow?: TextOverflow;\n}\n\n/** How a text node treats lines that run past its box. */\nexport type TextOverflow = 'visible' | 'clip';\n\ntype Box = { x: number; y: number; width: number; height: number };\n\n/** `cmd` cut to `box`: a group whose clip is the box as a rect, which the\n * renderer cuts on the CPU when the frame keeps it axis-aligned. */\nexport function clippedToBox(cmd: DrawCommand, box: Box): DrawCommand {\n return {\n kind: 'group',\n clip: { kind: 'rect', x: box.x, y: box.y, width: box.width, height: box.height },\n children: [cmd],\n };\n}\n\n/** The box a text node's lines lay out in: its pose box, inset by\n * `data.padding` and never past its center. */\nexport function textBoxOf(\n d: Pick<TextNodeData, 'padding'>,\n p: RectPose,\n): { x: number; y: number; width: number; height: number } {\n const pad = Math.max(0, d.padding ?? 0);\n const px = Math.min(pad, p.width / 2);\n const py = Math.min(pad, p.height / 2);\n return { x: p.x + px, y: p.y + py, width: p.width - 2 * px, height: p.height - 2 * py };\n}\n\n/** A `kit:text` node as the `TextPose` every text consumer reads, in the box\n * {@link textBoxOf} insets. The stroke goes through the same resolver every\n * other node kind's does, so one carrying no paint is dropped here rather\n * than throwing in the renderer. */\nexport function textPoseOf(d: TextNodeData, p: RectPose, inherited?: InheritedPaint): TextPose {\n return {\n ...textBoxOf(d, p),\n text: d.text,\n runs: d.runs as StyledRun[] | undefined,\n style: d.style,\n fill: d.fill === INHERIT ? resolveFill(d.fill, inherited?.fill) : d.fill,\n stroke: resolveStroke(d.stroke, inherited?.stroke) ?? undefined,\n verticalAlign: d.verticalAlign,\n };\n}\n\nexport const TEXT_PAINTER: NodeShapeEntry<unknown, RectPose> = {\n id: 'kit:text',\n snapsToBox: true,\n matches: (node) => {\n if (!paintsPose(node.pose)) return false;\n const d = node.data as { text?: string } | null;\n return d?.text != null;\n },\n // Memoized because the renderer's layout cache keys on the `ResolvedRun[]` array\n // identity: a fresh runs array per frame would leave that cache missing on every\n // draw. Both resolvers are pure style merging — no font reads — so `(data, pose)` is\n // the whole key. See PAINT_SLOT.\n //\n // `y` is the top of the first line box, not a baseline — see `TextDrawCommand.y`.\n paint: (node, pose, ctx) => nodeMemo(node, PAINT_SLOT, pose, (): DrawCommand[] => {\n const d = node.data as TextNodeData;\n const laid = textCommandFromPose(textPoseOf(d, pose, ctx?.inherited));\n const text = d.overflow === 'clip' ? clippedToBox(laid, pose) : laid;\n const bg = resolveFill(d.background);\n if (!bg) return [text];\n const fill = commandFill(bg, pose);\n return [\n { kind: 'path', path: { kind: 'rect', x: pose.x, y: pose.y, width: pose.width, height: pose.height }, ...(fill ? { fill } : {}) },\n text,\n ];\n }, ctx?.inherited),\n // The pose is a layout box, not a bounding box: the union of the line boxes is what\n // the node actually covers, so picking, lasso, and clipping stop grabbing blank space.\n //\n // `null` rather than an empty path when there are no non-blank lines: a caller\n // reading \"no silhouette\" falls back to the pose rect, which keeps an empty text\n // node pickable.\n //\n // A background is ink across the whole box, so a node with one is its box.\n // A clipped node's lines are cut to the box, as they are drawn.\n silhouette: (node, pose) => {\n const d = node.data as TextNodeData;\n if (resolveFill(d.background)) {\n return { kind: 'rect', x: pose.x, y: pose.y, width: pose.width, height: pose.height };\n }\n const lines = textLineBoxes(textPoseOf(d, pose));\n const boxes = d.overflow === 'clip' ? cutRects(lines, pose) : lines;\n if (boxes.length === 0) return null;\n return rectsToPath(boxes);\n },\n // `filled` is unconditional, `data.fill: null` included: the silhouette above is line\n // boxes, not glyph ink, so reporting outline-only text as unfilled would leave it\n // grabbable within a stroke width of a box edge and nowhere near the letters.\n ink: (node, _pose, ctx) => {\n const d = node.data as TextNodeData;\n return { filled: true, ...inkReach(resolveStroke(d.stroke, ctx?.inherited?.stroke), ctx?.scale) };\n },\n drawnBounds: (node, pose, ctx) => {\n const d = node.data as TextNodeData;\n const stroke = resolveStroke(d.stroke, ctx?.inherited?.stroke);\n return stroke ? strokedLinesBox(pose, textLineBoxes(textPoseOf(d, pose, ctx?.inherited)), strokeOutsetPerAxis(stroke, ctx)) : null;\n },\n};\n\n/** Each of `rects` cut to `box`, leaving out any it does not overlap. */\nfunction cutRects(rects: readonly Box[], box: Box): Box[] {\n const out: Box[] = [];\n for (const r of rects) {\n const x0 = Math.max(r.x, box.x);\n const y0 = Math.max(r.y, box.y);\n const x1 = Math.min(r.x + r.width, box.x + box.width);\n const y1 = Math.min(r.y + r.height, box.y + box.height);\n if (x1 > x0 && y1 > y0) out.push({ x: x0, y: y0, width: x1 - x0, height: y1 - y0 });\n }\n return out;\n}\n\n/**\n * The layout box grown where a stroked line reaches it: each line box, cut to\n * the layout box, then outset by the stroke's reach. A line that overflows the\n * layout box is left to it — the layout box is what a text node claims.\n */\nfunction strokedLinesBox(pose: RectPose, lines: readonly Rect[], reach: { x: number; y: number }): Rect | null {\n if (!(reach.x > 0 || reach.y > 0)) return null;\n let x0 = pose.x, y0 = pose.y, x1 = pose.x + pose.width, y1 = pose.y + pose.height;\n for (const l of lines) {\n const lx0 = Math.max(l.x, pose.x), ly0 = Math.max(l.y, pose.y);\n const lx1 = Math.min(l.x + l.width, pose.x + pose.width), ly1 = Math.min(l.y + l.height, pose.y + pose.height);\n if (lx1 <= lx0 || ly1 <= ly0) continue;\n x0 = Math.min(x0, lx0 - reach.x); y0 = Math.min(y0, ly0 - reach.y);\n x1 = Math.max(x1, lx1 + reach.x); y1 = Math.max(y1, ly1 + reach.y);\n }\n return { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };\n}\n\n/**\n * Union of axis-aligned rects as one multi-contour `PolygonPath`.\n *\n * Every contour winds the same way, so `'nonzero'` reads overlapping rects as\n * covered rather than punching a hole where two lines happen to overlap —\n * which they do the moment `lineHeight` drops below 1.\n */\nexport function rectsToPath(rects: readonly { x: number; y: number; width: number; height: number }[]): PolygonPath {\n const commands = new Uint8Array(rects.length * 6);\n const coords = new Float32Array(rects.length * 10);\n let ci = 0;\n let cmd = 0;\n for (const r of rects) {\n const { x, y, width: w, height: h } = r;\n commands[cmd++] = PATH_M; coords[ci++] = x; coords[ci++] = y;\n commands[cmd++] = PATH_L; coords[ci++] = x + w; coords[ci++] = y;\n commands[cmd++] = PATH_L; coords[ci++] = x + w; coords[ci++] = y + h;\n commands[cmd++] = PATH_L; coords[ci++] = x; coords[ci++] = y + h;\n commands[cmd++] = PATH_L; coords[ci++] = x; coords[ci++] = y;\n commands[cmd++] = PATH_Z;\n }\n return { kind: 'polygon', commands, coords, fillRule: 'nonzero' };\n}\n"],"mappings":"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"}
@@ -26,6 +26,18 @@ var OVERLAY_ROLE_STROKES = {
26
26
  lock: {
27
27
  paint: { color: "#d9480f" },
28
28
  width: 1.5
29
+ },
30
+ /** Where a dragged item would land. */
31
+ insert: {
32
+ paint: { color: "#1f8fff" },
33
+ width: 3,
34
+ cap: "round"
35
+ },
36
+ /** Where a dragged item is over but cannot land. */
37
+ refused: {
38
+ paint: { color: "#ff5d5d" },
39
+ width: 3,
40
+ cap: "round"
29
41
  }
30
42
  };
31
43
 
@@ -1 +1 @@
1
- {"version":3,"file":"overlayRoleStrokes.js","names":[],"sources":["../../src/canvas/overlayRoleStrokes.ts"],"sourcesContent":["import type { CommandPaint, CommandStroke } from '@weasel-js/paint';\n\n/** How each `polyline` overlay role reads. An action publishes the run and\n * the word; this table is the whole of the paint, for the dispatcher overlay\n * and for any tool previewing the same kind of run between gestures. */\nexport const OVERLAY_ROLE_STROKES: Readonly<Record<string, CommandStroke & { paint: CommandPaint }>> = {\n cut: { paint: { color: '#e23b3b' }, width: 1, dash: [6, 4] },\n connector: { paint: { color: '#7ba7c7' }, width: 2, dash: [4, 4] },\n falloff: { paint: { color: 'rgba(52, 120, 246, 0.28)' }, width: 10, cap: 'round', join: 'round' },\n measure: { paint: { color: '#1f8fff' }, width: 1 },\n lock: { paint: { color: '#d9480f' }, width: 1.5 },\n};\n"],"mappings":";;;;AAKA,IAAa,uBAA0F;CACrG,KAAK;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;EAAG,MAAM,CAAC,GAAG,CAAC;CAAE;CAC3D,WAAW;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;EAAG,MAAM,CAAC,GAAG,CAAC;CAAE;CACjE,SAAS;EAAE,OAAO,EAAE,OAAO,2BAA2B;EAAG,OAAO;EAAI,KAAK;EAAS,MAAM;CAAQ;CAChG,SAAS;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;CAAE;CACjD,MAAM;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;CAAI;AAClD"}
1
+ {"version":3,"file":"overlayRoleStrokes.js","names":[],"sources":["../../src/canvas/overlayRoleStrokes.ts"],"sourcesContent":["import type { CommandPaint, CommandStroke } from '@weasel-js/paint';\n\n/** How each `polyline` overlay role reads. An action publishes the run and\n * the word; this table is the whole of the paint, for the dispatcher overlay\n * and for any tool previewing the same kind of run between gestures. */\nexport const OVERLAY_ROLE_STROKES: Readonly<Record<string, CommandStroke & { paint: CommandPaint }>> = {\n cut: { paint: { color: '#e23b3b' }, width: 1, dash: [6, 4] },\n connector: { paint: { color: '#7ba7c7' }, width: 2, dash: [4, 4] },\n falloff: { paint: { color: 'rgba(52, 120, 246, 0.28)' }, width: 10, cap: 'round', join: 'round' },\n measure: { paint: { color: '#1f8fff' }, width: 1 },\n lock: { paint: { color: '#d9480f' }, width: 1.5 },\n /** Where a dragged item would land. */\n insert: { paint: { color: '#1f8fff' }, width: 3, cap: 'round' },\n /** Where a dragged item is over but cannot land. */\n refused: { paint: { color: '#ff5d5d' }, width: 3, cap: 'round' },\n};\n"],"mappings":";;;;AAKA,IAAa,uBAA0F;CACrG,KAAK;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;EAAG,MAAM,CAAC,GAAG,CAAC;CAAE;CAC3D,WAAW;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;EAAG,MAAM,CAAC,GAAG,CAAC;CAAE;CACjE,SAAS;EAAE,OAAO,EAAE,OAAO,2BAA2B;EAAG,OAAO;EAAI,KAAK;EAAS,MAAM;CAAQ;CAChG,SAAS;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;CAAE;CACjD,MAAM;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;CAAI;;CAEhD,QAAQ;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;EAAG,KAAK;CAAQ;;CAE9D,SAAS;EAAE,OAAO,EAAE,OAAO,UAAU;EAAG,OAAO;EAAG,KAAK;CAAQ;AACjE"}
@@ -1,7 +1,13 @@
1
+ import { commandFill, commandStroke } from "@weasel-js/renderer";
2
+ import { boundsOfPath } from "@weasel-js/geom";
1
3
  import { createEmitter } from "@weasel-js/registry";
2
4
 
3
5
  //#region src/canvas/pathOverlay.ts
4
6
  /**
7
+ * Paths that are no node, drawn over the scene: a region a tool would take,
8
+ * the result an operation would make before it is applied.
9
+ */
10
+ /**
5
11
  * The kit's roles: `'chrome'` for a path shown for reference, `'picked'` for
6
12
  * one a gesture has taken, and `'remove'` for one it would delete.
7
13
  */
@@ -42,17 +48,31 @@ function createPathOverlay({ id, label = "Transient paths", roles }) {
42
48
  label,
43
49
  space: "world",
44
50
  subscribe: changed.subscribe,
45
- draw: () => current.map(({ path, role = "chrome" }) => {
51
+ draw: () => current.flatMap(({ path, role = "chrome", paint }) => {
46
52
  const style = styles[role] ?? styles["chrome"];
47
- return {
53
+ const outline = style.stroke ? { stroke: {
54
+ paint: style.stroke,
55
+ width: { px: 1 }
56
+ } } : {};
57
+ if (!paint) return [{
48
58
  kind: "path",
49
59
  path,
50
60
  ...style.fill ? { fill: { paint: style.fill } } : {},
51
- ...style.stroke ? { stroke: {
52
- paint: style.stroke,
53
- width: { px: 1 }
54
- } } : {}
55
- };
61
+ ...outline
62
+ }];
63
+ const box = boundsOfPath(path);
64
+ const fill = paint.fill && commandFill(paint.fill, box);
65
+ const stroke = paint.stroke && commandStroke(paint.stroke, box);
66
+ return [{
67
+ kind: "path",
68
+ path,
69
+ ...fill ? { fill } : {},
70
+ ...stroke ? { stroke } : {}
71
+ }, ...style.stroke ? [{
72
+ kind: "path",
73
+ path,
74
+ ...outline
75
+ }] : []];
56
76
  })
57
77
  }
58
78
  };
@@ -1 +1 @@
1
- {"version":3,"file":"pathOverlay.js","names":[],"sources":["../../src/canvas/pathOverlay.ts"],"sourcesContent":["/**\n * Paths that are no node, drawn over the scene: a region a tool would take,\n * the result an operation would make before it is applied.\n */\nimport type { Path } from '@weasel-js/geom';\nimport type { CommandPaint } from '@weasel-js/paint';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport type { OverlayRole } from '@weasel-js/routing';\nimport { createEmitter } from '@weasel-js/registry';\nimport type { RenderLayer } from 'core/layers/render';\n\n/** One path to draw, in world coordinates, and what it is. */\nexport interface TransientPath {\n path: Path;\n /** Omitted reads as `'chrome'`. */\n role?: OverlayRole;\n}\n\n/** How one role reads. The stroke is one screen pixel wide at any zoom. */\nexport interface PathOverlayStyle {\n fill?: CommandPaint;\n stroke?: CommandPaint;\n}\n\n/**\n * The kit's roles: `'chrome'` for a path shown for reference, `'picked'` for\n * one a gesture has taken, and `'remove'` for one it would delete.\n */\nexport const PATH_OVERLAY_ROLES: Readonly<Record<string, PathOverlayStyle>> = {\n chrome: { fill: { color: 'rgba(164, 139, 212, 0.18)' }, stroke: { color: '#a48bd4' } },\n picked: { fill: { color: 'rgba(164, 139, 212, 0.45)' }, stroke: { color: '#7d5fc0' } },\n remove: { fill: { color: 'rgba(226, 59, 59, 0.35)' }, stroke: { color: '#e23b3b' } },\n};\n\n/** The paths an overlay holds, and the layer that draws them. */\nexport interface PathOverlay {\n get(): readonly TransientPath[];\n /** Replace every path. */\n set(paths: readonly TransientPath[]): void;\n clear(): void;\n /** A world-space layer drawing the paths in order, repainting on each change. */\n layer: RenderLayer<unknown>;\n}\n\n/** Options for {@link createPathOverlay}. */\nexport interface PathOverlayOptions {\n /** The layer's id; see `RenderLayer.id`. */\n id: string;\n label?: string;\n /** Styles per role, merged over {@link PATH_OVERLAY_ROLES}. A role with\n * none paints as `'chrome'`. */\n roles?: Readonly<Record<string, PathOverlayStyle>>;\n}\n\n/** A store of transient paths and the layer that draws it; hand the layer to\n * a tool's `overlay` or a canvas's `layers`. */\nexport function createPathOverlay({ id, label = 'Transient paths', roles }: PathOverlayOptions): PathOverlay {\n const styles = { ...PATH_OVERLAY_ROLES, ...roles };\n const changed = createEmitter();\n let current: readonly TransientPath[] = [];\n const set = (paths: readonly TransientPath[]): void => {\n if (paths.length === 0 && current.length === 0) return;\n current = paths;\n changed.emit();\n };\n return {\n get: () => current,\n set,\n clear: () => set([]),\n layer: {\n id,\n label,\n space: 'world',\n subscribe: changed.subscribe,\n draw: () =>\n current.map(({ path, role = 'chrome' }): DrawCommand => {\n const style = styles[role] ?? styles['chrome']!;\n return {\n kind: 'path',\n path,\n ...(style.fill ? { fill: { paint: style.fill } } : {}),\n ...(style.stroke ? { stroke: { paint: style.stroke, width: { px: 1 } } } : {}),\n };\n }),\n },\n };\n}\n"],"mappings":";;;;;;;AA4BA,IAAa,qBAAiE;CAC5E,QAAQ;EAAE,MAAM,EAAE,OAAO,4BAA4B;EAAG,QAAQ,EAAE,OAAO,UAAU;CAAE;CACrF,QAAQ;EAAE,MAAM,EAAE,OAAO,4BAA4B;EAAG,QAAQ,EAAE,OAAO,UAAU;CAAE;CACrF,QAAQ;EAAE,MAAM,EAAE,OAAO,0BAA0B;EAAG,QAAQ,EAAE,OAAO,UAAU;CAAE;AACrF;;;AAwBA,SAAgB,kBAAkB,EAAE,IAAI,QAAQ,mBAAmB,SAA0C;CAC3G,MAAM,SAAS;EAAE,GAAG;EAAoB,GAAG;CAAM;CACjD,MAAM,UAAU,cAAc;CAC9B,IAAI,UAAoC,CAAC;CACzC,MAAM,OAAO,UAA0C;EACrD,IAAI,MAAM,WAAW,KAAK,QAAQ,WAAW,GAAG;EAChD,UAAU;EACV,QAAQ,KAAK;CACf;CACA,OAAO;EACL,WAAW;EACX;EACA,aAAa,IAAI,CAAC,CAAC;EACnB,OAAO;GACL;GACA;GACA,OAAO;GACP,WAAW,QAAQ;GACnB,YACE,QAAQ,KAAK,EAAE,MAAM,OAAO,eAA4B;IACtD,MAAM,QAAQ,OAAO,SAAS,OAAO;IACrC,OAAO;KACL,MAAM;KACN;KACA,GAAI,MAAM,OAAO,EAAE,MAAM,EAAE,OAAO,MAAM,KAAK,EAAE,IAAI,CAAC;KACpD,GAAI,MAAM,SAAS,EAAE,QAAQ;MAAE,OAAO,MAAM;MAAQ,OAAO,EAAE,IAAI,EAAE;KAAE,EAAE,IAAI,CAAC;IAC9E;GACF,CAAC;EACL;CACF;AACF"}
1
+ {"version":3,"file":"pathOverlay.js","names":[],"sources":["../../src/canvas/pathOverlay.ts"],"sourcesContent":["/**\n * Paths that are no node, drawn over the scene: a region a tool would take,\n * the result an operation would make before it is applied.\n */\nimport { boundsOfPath, type Path } from '@weasel-js/geom';\nimport type { CommandPaint } from '@weasel-js/paint';\nimport { commandFill, commandStroke, type DrawCommand } from '@weasel-js/renderer';\nimport type { OverlayRole } from '@weasel-js/routing';\nimport { createEmitter } from '@weasel-js/registry';\nimport type { RenderLayer } from 'core/layers/render';\nimport type { PathPaint } from './nodeShapes/nodePaint';\n\n/** One path to draw, in world coordinates, and what it is. */\nexport interface TransientPath {\n path: Path;\n /** Omitted reads as `'chrome'`. */\n role?: OverlayRole;\n /** Paint the path wears, drawn in place of the role's fill and under the\n * role's outline: a result shown as it will look once made. */\n paint?: PathPaint;\n}\n\n/** How one role reads. The stroke is one screen pixel wide at any zoom. */\nexport interface PathOverlayStyle {\n fill?: CommandPaint;\n stroke?: CommandPaint;\n}\n\n/**\n * The kit's roles: `'chrome'` for a path shown for reference, `'picked'` for\n * one a gesture has taken, and `'remove'` for one it would delete.\n */\nexport const PATH_OVERLAY_ROLES: Readonly<Record<string, PathOverlayStyle>> = {\n chrome: { fill: { color: 'rgba(164, 139, 212, 0.18)' }, stroke: { color: '#a48bd4' } },\n picked: { fill: { color: 'rgba(164, 139, 212, 0.45)' }, stroke: { color: '#7d5fc0' } },\n remove: { fill: { color: 'rgba(226, 59, 59, 0.35)' }, stroke: { color: '#e23b3b' } },\n};\n\n/** The paths an overlay holds, and the layer that draws them. */\nexport interface PathOverlay {\n get(): readonly TransientPath[];\n /** Replace every path. */\n set(paths: readonly TransientPath[]): void;\n clear(): void;\n /** A world-space layer drawing the paths in order, repainting on each change. */\n layer: RenderLayer<unknown>;\n}\n\n/** Options for {@link createPathOverlay}. */\nexport interface PathOverlayOptions {\n /** The layer's id; see `RenderLayer.id`. */\n id: string;\n label?: string;\n /** Styles per role, merged over {@link PATH_OVERLAY_ROLES}. A role with\n * none paints as `'chrome'`. */\n roles?: Readonly<Record<string, PathOverlayStyle>>;\n}\n\n/** A store of transient paths and the layer that draws it; hand the layer to\n * a tool's `overlay` or a canvas's `layers`. */\nexport function createPathOverlay({ id, label = 'Transient paths', roles }: PathOverlayOptions): PathOverlay {\n const styles = { ...PATH_OVERLAY_ROLES, ...roles };\n const changed = createEmitter();\n let current: readonly TransientPath[] = [];\n const set = (paths: readonly TransientPath[]): void => {\n if (paths.length === 0 && current.length === 0) return;\n current = paths;\n changed.emit();\n };\n return {\n get: () => current,\n set,\n clear: () => set([]),\n layer: {\n id,\n label,\n space: 'world',\n subscribe: changed.subscribe,\n draw: () =>\n current.flatMap(({ path, role = 'chrome', paint }): DrawCommand[] => {\n const style = styles[role] ?? styles['chrome']!;\n const outline = style.stroke ? { stroke: { paint: style.stroke, width: { px: 1 } } } : {};\n if (!paint) return [{ kind: 'path', path, ...(style.fill ? { fill: { paint: style.fill } } : {}), ...outline }];\n const box = boundsOfPath(path);\n const fill = paint.fill && commandFill(paint.fill, box);\n const stroke = paint.stroke && commandStroke(paint.stroke, box);\n return [\n { kind: 'path', path, ...(fill ? { fill } : {}), ...(stroke ? { stroke } : {}) },\n ...(style.stroke ? [{ kind: 'path', path, ...outline } as DrawCommand] : []),\n ];\n }),\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAgCA,IAAa,qBAAiE;CAC5E,QAAQ;EAAE,MAAM,EAAE,OAAO,4BAA4B;EAAG,QAAQ,EAAE,OAAO,UAAU;CAAE;CACrF,QAAQ;EAAE,MAAM,EAAE,OAAO,4BAA4B;EAAG,QAAQ,EAAE,OAAO,UAAU;CAAE;CACrF,QAAQ;EAAE,MAAM,EAAE,OAAO,0BAA0B;EAAG,QAAQ,EAAE,OAAO,UAAU;CAAE;AACrF;;;AAwBA,SAAgB,kBAAkB,EAAE,IAAI,QAAQ,mBAAmB,SAA0C;CAC3G,MAAM,SAAS;EAAE,GAAG;EAAoB,GAAG;CAAM;CACjD,MAAM,UAAU,cAAc;CAC9B,IAAI,UAAoC,CAAC;CACzC,MAAM,OAAO,UAA0C;EACrD,IAAI,MAAM,WAAW,KAAK,QAAQ,WAAW,GAAG;EAChD,UAAU;EACV,QAAQ,KAAK;CACf;CACA,OAAO;EACL,WAAW;EACX;EACA,aAAa,IAAI,CAAC,CAAC;EACnB,OAAO;GACL;GACA;GACA,OAAO;GACP,WAAW,QAAQ;GACnB,YACE,QAAQ,SAAS,EAAE,MAAM,OAAO,UAAU,YAA2B;IACnE,MAAM,QAAQ,OAAO,SAAS,OAAO;IACrC,MAAM,UAAU,MAAM,SAAS,EAAE,QAAQ;KAAE,OAAO,MAAM;KAAQ,OAAO,EAAE,IAAI,EAAE;IAAE,EAAE,IAAI,CAAC;IACxF,IAAI,CAAC,OAAO,OAAO,CAAC;KAAE,MAAM;KAAQ;KAAM,GAAI,MAAM,OAAO,EAAE,MAAM,EAAE,OAAO,MAAM,KAAK,EAAE,IAAI,CAAC;KAAI,GAAG;IAAQ,CAAC;IAC9G,MAAM,MAAM,aAAa,IAAI;IAC7B,MAAM,OAAO,MAAM,QAAQ,YAAY,MAAM,MAAM,GAAG;IACtD,MAAM,SAAS,MAAM,UAAU,cAAc,MAAM,QAAQ,GAAG;IAC9D,OAAO,CACL;KAAE,MAAM;KAAQ;KAAM,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;KAAI,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;IAAG,GAC/E,GAAI,MAAM,SAAS,CAAC;KAAE,MAAM;KAAQ;KAAM,GAAG;IAAQ,CAAgB,IAAI,CAAC,CAC5E;GACF,CAAC;EACL;CACF;AACF"}
@@ -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,5 +1,6 @@
1
1
  import { planeMap } from "../core/viewport/parallax.js";
2
2
  import { resolveDerivedPath } from "../core/scene/derivedPath.js";
3
+ import { inheritedPaintOf, inheritsPaint } from "../core/scene/inheritedPaint.js";
3
4
  import { findShapeSilhouette } from "./NodeShape.js";
4
5
  import { clipCarrier } from "./planeClips.js";
5
6
  import { asNodeId } from "../core/scene/types.js";
@@ -87,7 +88,8 @@ function pickWalk(src, q) {
87
88
  if (query === null) continue;
88
89
  const pose = src.poseOf(node);
89
90
  const derived = src.derivedPathOf?.(node);
90
- if (!reaches(node, pose, derived, query, () => src.parentsOf(node), copying, {
91
+ const inherited = inheritsPaint(node) ? inheritedPaintOf(node, src.parentsOf(node)) : void 0;
92
+ if (!reaches(node, pose, derived, inherited, query, () => src.parentsOf(node), copying, {
91
93
  clipOf,
92
94
  copiesOf,
93
95
  carry
@@ -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. 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 { inheritedPaintOf, inheritsPaint, type InheritedPaint } from 'core/scene/inheritedPaint';\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 /** Read for an `'inherit'` fill or stroke, which takes its containers'. */\n data?: unknown;\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. `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 `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 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 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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"}
@@ -25,6 +25,7 @@ function debugSnapshotArgs(args) {
25
25
  const width = size.width / Math.abs(view.scale.x);
26
26
  const height = size.height / Math.abs(view.scale.y);
27
27
  return {
28
+ annotations: true,
28
29
  ...rest,
29
30
  sourceRect: {
30
31
  x: view.scale.x < 0 ? view.x - width : view.x,
@@ -1 +1 @@
1
- {"version":3,"file":"renderDebugSnapshot.js","names":[],"sources":["../../src/canvas/renderDebugSnapshot.ts"],"sourcesContent":["/**\n * `renderDebugSnapshot` — the canvas as its debug overlay showed it, scene and\n * overlay in one raster, for attaching to a bug report.\n */\nimport { renderSceneToPixels, type RasterImage, type RenderSceneToPixelsArgs } from './renderSceneToPixels';\nimport { buildDebugOverlayCommands } from '../debug/createDebugOverlayLayer';\nimport type { DebugConfig, DebugSnapshot } from '../debug/types';\nimport type { View } from '../core/viewport/view';\nimport { mat3 } from '@weasel-js/geom';\n\n/** The canvas to reproduce — its camera, its size, and what its debug overlay\n * had recorded — plus how to rasterize the scene under it. */\nexport interface RenderDebugSnapshotArgs<TData, TLayer extends string, TPose>\n extends Omit<RenderSceneToPixelsArgs<TData, TLayer, TPose>, 'sourceRect' | 'scale' | 'overlay'> {\n /** The camera the canvas was looking through. */\n view: View;\n /** The canvas's size in CSS pixels. */\n size: { width: number; height: number };\n /** Output pixels per CSS pixel. Default 1; pass `devicePixelRatio` for what\n * the screen showed. */\n pixelRatio?: number;\n /** What the overlay draws — a canvas's `getDebug()?.snapshot()`. */\n debug: DebugSnapshot;\n /** Which overlay features to draw, as on `<Canvas debug>`. The frame panel\n * shows the recorded paint cost but no rate: a still image has no frames. */\n config: DebugConfig;\n}\n\n/**\n * Rasterize the scene through `view` at `size` and paint the debug overlay\n * over it, the way the canvas composed them. Built on `renderSceneToPixels`,\n * so the scene pixels are the screen's pixels at that resolution, and\n * synchronous like it: `await warmRender({ render: debugSnapshotArgs(args) })`\n * first. Encode the result with `rasterToPng` to attach it somewhere.\n */\nexport function renderDebugSnapshot<TData, TLayer extends string, TPose>(\n args: RenderDebugSnapshotArgs<TData, TLayer, TPose>,\n): RasterImage {\n return renderSceneToPixels(debugSnapshotArgs(args));\n}\n\n/** What {@link renderDebugSnapshot} hands `renderSceneToPixels`: the source\n * rect and scale that reproduce the canvas, with the overlay composed on top. */\nexport function debugSnapshotArgs<TData, TLayer extends string, TPose>(\n args: RenderDebugSnapshotArgs<TData, TLayer, TPose>,\n): RenderSceneToPixelsArgs<TData, TLayer, TPose> {\n const { view, size, debug, config, pixelRatio = 1, ...rest } = args;\n const overlay = buildDebugOverlayCommands(debug, config, view, size);\n const width = size.width / Math.abs(view.scale.x);\n const height = size.height / Math.abs(view.scale.y);\n return {\n ...rest,\n sourceRect: {\n x: view.scale.x < 0 ? view.x - width : view.x,\n y: view.scale.y < 0 ? view.y - height : view.y,\n width,\n height,\n },\n scale: { x: view.scale.x * pixelRatio, y: view.scale.y * pixelRatio },\n overlay: pixelRatio === 1\n ? overlay\n : [{ kind: 'group', transform: mat3.scaled(mat3.identity(), pixelRatio, pixelRatio), children: overlay }],\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAmCA,SAAgB,oBACd,MACa;CACb,OAAO,oBAAoB,kBAAkB,IAAI,CAAC;AACpD;;;AAIA,SAAgB,kBACd,MAC+C;CAC/C,MAAM,EAAE,MAAM,MAAM,OAAO,QAAQ,aAAa,GAAG,GAAG,SAAS;CAC/D,MAAM,UAAU,0BAA0B,OAAO,QAAQ,MAAM,IAAI;CACnE,MAAM,QAAQ,KAAK,QAAQ,KAAK,IAAI,KAAK,MAAM,CAAC;CAChD,MAAM,SAAS,KAAK,SAAS,KAAK,IAAI,KAAK,MAAM,CAAC;CAClD,OAAO;EACL,GAAG;EACH,YAAY;GACV,GAAG,KAAK,MAAM,IAAI,IAAI,KAAK,IAAI,QAAQ,KAAK;GAC5C,GAAG,KAAK,MAAM,IAAI,IAAI,KAAK,IAAI,SAAS,KAAK;GAC7C;GACA;EACF;EACA,OAAO;GAAE,GAAG,KAAK,MAAM,IAAI;GAAY,GAAG,KAAK,MAAM,IAAI;EAAW;EACpE,SAAS,eAAe,IACpB,UACA,CAAC;GAAE,MAAM;GAAS,WAAW,KAAK,OAAO,KAAK,SAAS,GAAG,YAAY,UAAU;GAAG,UAAU;EAAQ,CAAC;CAC5G;AACF"}
1
+ {"version":3,"file":"renderDebugSnapshot.js","names":[],"sources":["../../src/canvas/renderDebugSnapshot.ts"],"sourcesContent":["/**\n * `renderDebugSnapshot` — the canvas as its debug overlay showed it, scene and\n * overlay in one raster, for attaching to a bug report.\n */\nimport { renderSceneToPixels, type RasterImage, type RenderSceneToPixelsArgs } from './renderSceneToPixels';\nimport { buildDebugOverlayCommands } from '../debug/createDebugOverlayLayer';\nimport type { DebugConfig, DebugSnapshot } from '../debug/types';\nimport type { View } from '../core/viewport/view';\nimport { mat3 } from '@weasel-js/geom';\n\n/** The canvas to reproduce — its camera, its size, and what its debug overlay\n * had recorded — plus how to rasterize the scene under it. */\nexport interface RenderDebugSnapshotArgs<TData, TLayer extends string, TPose>\n extends Omit<RenderSceneToPixelsArgs<TData, TLayer, TPose>, 'sourceRect' | 'scale' | 'overlay'> {\n /** The camera the canvas was looking through. */\n view: View;\n /** The canvas's size in CSS pixels. */\n size: { width: number; height: number };\n /** Output pixels per CSS pixel. Default 1; pass `devicePixelRatio` for what\n * the screen showed. */\n pixelRatio?: number;\n /** What the overlay draws — a canvas's `getDebug()?.snapshot()`. */\n debug: DebugSnapshot;\n /** Which overlay features to draw, as on `<Canvas debug>`. The frame panel\n * shows the recorded paint cost but no rate: a still image has no frames. */\n config: DebugConfig;\n /** Draw the scene's annotation layers. Default `true`, as the canvas did. */\n annotations?: boolean;\n}\n\n/**\n * Rasterize the scene through `view` at `size` and paint the debug overlay\n * over it, the way the canvas composed them. Built on `renderSceneToPixels`,\n * so the scene pixels are the screen's pixels at that resolution, and\n * synchronous like it: `await warmRender({ render: debugSnapshotArgs(args) })`\n * first. Encode the result with `rasterToPng` to attach it somewhere.\n */\nexport function renderDebugSnapshot<TData, TLayer extends string, TPose>(\n args: RenderDebugSnapshotArgs<TData, TLayer, TPose>,\n): RasterImage {\n return renderSceneToPixels(debugSnapshotArgs(args));\n}\n\n/** What {@link renderDebugSnapshot} hands `renderSceneToPixels`: the source\n * rect and scale that reproduce the canvas, with the overlay composed on top. */\nexport function debugSnapshotArgs<TData, TLayer extends string, TPose>(\n args: RenderDebugSnapshotArgs<TData, TLayer, TPose>,\n): RenderSceneToPixelsArgs<TData, TLayer, TPose> {\n const { view, size, debug, config, pixelRatio = 1, ...rest } = args;\n const overlay = buildDebugOverlayCommands(debug, config, view, size);\n const width = size.width / Math.abs(view.scale.x);\n const height = size.height / Math.abs(view.scale.y);\n return {\n annotations: true,\n ...rest,\n sourceRect: {\n x: view.scale.x < 0 ? view.x - width : view.x,\n y: view.scale.y < 0 ? view.y - height : view.y,\n width,\n height,\n },\n scale: { x: view.scale.x * pixelRatio, y: view.scale.y * pixelRatio },\n overlay: pixelRatio === 1\n ? overlay\n : [{ kind: 'group', transform: mat3.scaled(mat3.identity(), pixelRatio, pixelRatio), children: overlay }],\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAqCA,SAAgB,oBACd,MACa;CACb,OAAO,oBAAoB,kBAAkB,IAAI,CAAC;AACpD;;;AAIA,SAAgB,kBACd,MAC+C;CAC/C,MAAM,EAAE,MAAM,MAAM,OAAO,QAAQ,aAAa,GAAG,GAAG,SAAS;CAC/D,MAAM,UAAU,0BAA0B,OAAO,QAAQ,MAAM,IAAI;CACnE,MAAM,QAAQ,KAAK,QAAQ,KAAK,IAAI,KAAK,MAAM,CAAC;CAChD,MAAM,SAAS,KAAK,SAAS,KAAK,IAAI,KAAK,MAAM,CAAC;CAClD,OAAO;EACL,aAAa;EACb,GAAG;EACH,YAAY;GACV,GAAG,KAAK,MAAM,IAAI,IAAI,KAAK,IAAI,QAAQ,KAAK;GAC5C,GAAG,KAAK,MAAM,IAAI,IAAI,KAAK,IAAI,SAAS,KAAK;GAC7C;GACA;EACF;EACA,OAAO;GAAE,GAAG,KAAK,MAAM,IAAI;GAAY,GAAG,KAAK,MAAM,IAAI;EAAW;EACpE,SAAS,eAAe,IACpB,UACA,CAAC;GAAE,MAAM;GAAS,WAAW,KAAK,OAAO,KAAK,SAAS,GAAG,YAAY,UAAU;GAAG,UAAU;EAAQ,CAAC;CAC5G;AACF"}
@@ -1,3 +1,6 @@
1
+ import { sceneLayerKey } from "./sceneLayerPaint.js";
2
+ import { CANVAS_CONTEXT_ATTRIBUTES } from "./contextAttributes.js";
3
+ import { annotationLayerIds } from "../core/scene/annotation.js";
1
4
  import { defaultDrawOne } from "./defaultDrawOne.js";
2
5
  import { buildSceneViewCommands } from "./sceneViewRender.js";
3
6
  import { renderNeeds } from "./renderNeeds.js";
@@ -70,7 +73,8 @@ function planPixelRender(args) {
70
73
  color: args.background
71
74
  } }
72
75
  });
73
- commands.push(...buildSceneViewCommands(args.scene, view, drawOne, void 0, args.alphaFor, void 0, args.colorOverrides));
76
+ const layers = args.annotations ? void 0 : { layerVisibility: Object.fromEntries([...annotationLayerIds(args.scene.layers)].map((id) => [sceneLayerKey(id), false])) };
77
+ commands.push(...buildSceneViewCommands(args.scene, view, drawOne, void 0, args.alphaFor, layers, args.colorOverrides));
74
78
  if (args.overlay) commands.push(...args.overlay);
75
79
  return {
76
80
  width,
@@ -111,10 +115,7 @@ function createRasterSession(options = {}) {
111
115
  canvas = (options.createCanvas ?? defaultCreateCanvas)(width, height);
112
116
  canvas.width = width;
113
117
  canvas.height = height;
114
- gl = canvas.getContext("webgl2", {
115
- preserveDrawingBuffer: true,
116
- stencil: true
117
- }) ?? void 0;
118
+ gl = canvas.getContext("webgl2", CANVAS_CONTEXT_ATTRIBUTES) ?? void 0;
118
119
  } else {
119
120
  if (canvas.width < width) canvas.width = width;
120
121
  if (canvas.height < height) canvas.height = height;
@@ -150,7 +151,7 @@ function createRasterSession(options = {}) {
150
151
  });
151
152
  renderer.setFlattenTolerance(flattenTolerance);
152
153
  }
153
- renderer.render(plan.commands, viewToMat3$1(plan.view));
154
+ renderer.render(plan.commands, viewToMat3$1(plan.view), { screen: args.screen });
154
155
  if (isLost(ctx)) throw new Error("raster session: the WebGL2 context was lost during render");
155
156
  const raw = new Uint8Array(width * height * 4);
156
157
  ctx.readPixels(0, 0, width, height, ctx.RGBA, ctx.UNSIGNED_BYTE, raw);