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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (292) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/dist/animation/poseTarget.js +46 -15
  3. package/dist/animation/poseTarget.js.map +1 -1
  4. package/dist/animation/poseTransition.js +3 -3
  5. package/dist/animation/poseTransition.js.map +1 -1
  6. package/dist/animation/reflow.js +3 -6
  7. package/dist/animation/reflow.js.map +1 -1
  8. package/dist/animation/rig/bindRig.js +2 -2
  9. package/dist/animation/rig/bindRig.js.map +1 -1
  10. package/dist/animation/rig/skinBinding.js +2 -2
  11. package/dist/animation/rig/skinBinding.js.map +1 -1
  12. package/dist/barrel/canvas.js +5 -3
  13. package/dist/barrel/chrome.js +3 -1
  14. package/dist/barrel/paths.js +3 -2
  15. package/dist/barrel/routing.js +2 -2
  16. package/dist/barrel/scene.js +6 -1
  17. package/dist/canvas/NodeShape.js +1 -0
  18. package/dist/canvas/NodeShape.js.map +1 -1
  19. package/dist/canvas/SceneCanvas/SceneCanvasInput.js +2 -0
  20. package/dist/canvas/SceneCanvas/SceneCanvasInput.js.map +1 -1
  21. package/dist/canvas/SceneCanvas/canvasPassThrough.js +1 -1
  22. package/dist/canvas/SceneCanvas/canvasPassThrough.js.map +1 -1
  23. package/dist/canvas/SceneCanvas/defaultNodeRouting.js +2 -2
  24. package/dist/canvas/SceneCanvas/defaultNodeRouting.js.map +1 -1
  25. package/dist/canvas/SceneCanvas/scenePointQuery.js +20 -2
  26. package/dist/canvas/SceneCanvas/scenePointQuery.js.map +1 -1
  27. package/dist/canvas/SceneCanvas/useCanvasTools.js +3 -0
  28. package/dist/canvas/SceneCanvas/useCanvasTools.js.map +1 -1
  29. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js +25 -19
  30. package/dist/canvas/SceneCanvas/usePreviewGhostLayer.js.map +1 -1
  31. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js +7 -0
  32. package/dist/canvas/SceneCanvas/useSceneCanvasLayers.js.map +1 -1
  33. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js +1 -0
  34. package/dist/canvas/SceneCanvas/useSceneCanvasTools.js.map +1 -1
  35. package/dist/canvas/SceneCanvas/useScenePicking.js +31 -6
  36. package/dist/canvas/SceneCanvas/useScenePicking.js.map +1 -1
  37. package/dist/canvas/SceneCanvas/useSceneSelectTool.js +4 -5
  38. package/dist/canvas/SceneCanvas/useSceneSelectTool.js.map +1 -1
  39. package/dist/canvas/SceneCanvas/useSceneStandardActions.js +5 -3
  40. package/dist/canvas/SceneCanvas/useSceneStandardActions.js.map +1 -1
  41. package/dist/canvas/SceneCanvas.js +19 -2
  42. package/dist/canvas/SceneCanvas.js.map +1 -1
  43. package/dist/canvas/accentColor.js +26 -3
  44. package/dist/canvas/accentColor.js.map +1 -1
  45. package/dist/canvas/buildSceneTree.js +14 -20
  46. package/dist/canvas/buildSceneTree.js.map +1 -1
  47. package/dist/canvas/copiesAbove.js +1 -1
  48. package/dist/canvas/copiesAbove.js.map +1 -1
  49. package/dist/canvas/defaultDrawOne.js +10 -7
  50. package/dist/canvas/defaultDrawOne.js.map +1 -1
  51. package/dist/canvas/deps/areaSelect.js +5 -2
  52. package/dist/canvas/deps/areaSelect.js.map +1 -1
  53. package/dist/canvas/deps/booleans.js +1 -1
  54. package/dist/canvas/deps/booleans.js.map +1 -1
  55. package/dist/canvas/deps/enteredGroup.js +81 -0
  56. package/dist/canvas/deps/enteredGroup.js.map +1 -0
  57. package/dist/canvas/deps/hitTestArea.js +27 -15
  58. package/dist/canvas/deps/hitTestArea.js.map +1 -1
  59. package/dist/canvas/deps/insert.js +4 -36
  60. package/dist/canvas/deps/insert.js.map +1 -1
  61. package/dist/canvas/deps/insertSite.js +39 -0
  62. package/dist/canvas/deps/insertSite.js.map +1 -0
  63. package/dist/canvas/deps/lassoSelect.js +6 -3
  64. package/dist/canvas/deps/lassoSelect.js.map +1 -1
  65. package/dist/canvas/deps/pathLeaf.js +1 -1
  66. package/dist/canvas/deps/pathLeaf.js.map +1 -1
  67. package/dist/canvas/deps/slice.js +2 -1
  68. package/dist/canvas/deps/slice.js.map +1 -1
  69. package/dist/canvas/derivedPath.js +14 -6
  70. package/dist/canvas/derivedPath.js.map +1 -1
  71. package/dist/canvas/doubleClickTarget.js +9 -11
  72. package/dist/canvas/doubleClickTarget.js.map +1 -1
  73. package/dist/canvas/drawnBounds.js +16 -7
  74. package/dist/canvas/drawnBounds.js.map +1 -1
  75. package/dist/canvas/inheritedPaints.js +22 -11
  76. package/dist/canvas/inheritedPaints.js.map +1 -1
  77. package/dist/canvas/nodeShapes/nodePaint.js +1 -7
  78. package/dist/canvas/nodeShapes/nodePaint.js.map +1 -1
  79. package/dist/canvas/nodeShapes/pathPainter.js +2 -3
  80. package/dist/canvas/nodeShapes/pathPainter.js.map +1 -1
  81. package/dist/canvas/nodeShapes/rectFallbackPainter.js +1 -1
  82. package/dist/canvas/nodeShapes/rectFallbackPainter.js.map +1 -1
  83. package/dist/canvas/nodeShapes/shapePainter.js +2 -3
  84. package/dist/canvas/nodeShapes/shapePainter.js.map +1 -1
  85. package/dist/canvas/ownClip.js +24 -0
  86. package/dist/canvas/ownClip.js.map +1 -0
  87. package/dist/canvas/paintCull.js.map +1 -1
  88. package/dist/canvas/pickIndex/nodeBroadBox.js +86 -0
  89. package/dist/canvas/pickIndex/nodeBroadBox.js.map +1 -0
  90. package/dist/canvas/pickIndex/scenePickIndex.js +208 -0
  91. package/dist/canvas/pickIndex/scenePickIndex.js.map +1 -0
  92. package/dist/canvas/pickWalk.js +14 -90
  93. package/dist/canvas/pickWalk.js.map +1 -1
  94. package/dist/canvas/poseCascade.js +1 -1
  95. package/dist/canvas/poseCascade.js.map +1 -1
  96. package/dist/canvas/publishPreviews.js +97 -0
  97. package/dist/canvas/publishPreviews.js.map +1 -0
  98. package/dist/canvas/sceneAdapter.js +7 -5
  99. package/dist/canvas/sceneAdapter.js.map +1 -1
  100. package/dist/canvas/sceneClipboard.js +17 -10
  101. package/dist/canvas/sceneClipboard.js.map +1 -1
  102. package/dist/canvas/scenePickReads.js +37 -0
  103. package/dist/canvas/scenePickReads.js.map +1 -0
  104. package/dist/canvas/scenePickSource.js +74 -0
  105. package/dist/canvas/scenePickSource.js.map +1 -0
  106. package/dist/canvas/sceneSlotWiring.js +9 -4
  107. package/dist/canvas/sceneSlotWiring.js.map +1 -1
  108. package/dist/canvas/sceneViewRender.js +9 -5
  109. package/dist/canvas/sceneViewRender.js.map +1 -1
  110. package/dist/canvas/shownNodes.js +15 -0
  111. package/dist/canvas/shownNodes.js.map +1 -0
  112. package/dist/canvas/useSceneAdapter.js +5 -3
  113. package/dist/canvas/useSceneAdapter.js.map +1 -1
  114. package/dist/clipboard.d.ts +2 -2
  115. package/dist/{clone-C3e0OTuF.d.ts → clone-qb2e1F5p.d.ts} +2 -2
  116. package/dist/clone.d.ts +3 -3
  117. package/dist/core/isPlainObject.js +2 -2
  118. package/dist/core/layers/layerGroup.js.map +1 -1
  119. package/dist/core/scene/NodeRouting.js.map +1 -1
  120. package/dist/core/scene/arrivalWindow.js +1 -1
  121. package/dist/core/scene/arrivalWindow.js.map +1 -1
  122. package/dist/core/scene/clipNothing.js +2 -5
  123. package/dist/core/scene/clipNothing.js.map +1 -1
  124. package/dist/core/scene/declaredFieldSetters.js +44 -0
  125. package/dist/core/scene/declaredFieldSetters.js.map +1 -0
  126. package/dist/core/scene/depMemo.js +3 -0
  127. package/dist/core/scene/depMemo.js.map +1 -1
  128. package/dist/core/scene/dependents.js +3 -1
  129. package/dist/core/scene/dependents.js.map +1 -1
  130. package/dist/core/scene/documentOf.js +43 -0
  131. package/dist/core/scene/documentOf.js.map +1 -0
  132. package/dist/core/scene/effectivePose.js +37 -13
  133. package/dist/core/scene/effectivePose.js.map +1 -1
  134. package/dist/core/scene/fieldEdits.js +17 -14
  135. package/dist/core/scene/fieldEdits.js.map +1 -1
  136. package/dist/core/scene/historyForm.js +4 -4
  137. package/dist/core/scene/historyForm.js.map +1 -1
  138. package/dist/core/scene/inheritedPaint.js +27 -5
  139. package/dist/core/scene/inheritedPaint.js.map +1 -1
  140. package/dist/core/scene/kitOps.js +3 -23
  141. package/dist/core/scene/kitOps.js.map +1 -1
  142. package/dist/core/scene/layoutPass.js +1 -2
  143. package/dist/core/scene/layoutPass.js.map +1 -1
  144. package/dist/core/scene/nodeAsShown.js +72 -0
  145. package/dist/core/scene/nodeAsShown.js.map +1 -0
  146. package/dist/core/scene/nodeFnFields.js +2 -10
  147. package/dist/core/scene/nodeFnFields.js.map +1 -1
  148. package/dist/core/scene/nodeMemo.js +6 -1
  149. package/dist/core/scene/nodeMemo.js.map +1 -1
  150. package/dist/core/scene/nodeStore.js +2 -4
  151. package/dist/core/scene/nodeStore.js.map +1 -1
  152. package/dist/core/scene/pathEffects/derive.js +3 -2
  153. package/dist/core/scene/pathEffects/derive.js.map +1 -1
  154. package/dist/core/scene/pathEffects/foundHidden.js +35 -0
  155. package/dist/core/scene/pathEffects/foundHidden.js.map +1 -0
  156. package/dist/core/scene/plyStacks.js +89 -0
  157. package/dist/core/scene/plyStacks.js.map +1 -0
  158. package/dist/core/scene/poseHold.js +42 -24
  159. package/dist/core/scene/poseHold.js.map +1 -1
  160. package/dist/core/scene/poseOverrides.js +35 -38
  161. package/dist/core/scene/poseOverrides.js.map +1 -1
  162. package/dist/core/scene/posePlies.js +59 -0
  163. package/dist/core/scene/posePlies.js.map +1 -0
  164. package/dist/core/scene/reconcileSpecs.js +6 -15
  165. package/dist/core/scene/reconcileSpecs.js.map +1 -1
  166. package/dist/core/scene/renderOrder.js +10 -4
  167. package/dist/core/scene/renderOrder.js.map +1 -1
  168. package/dist/core/scene/sameValue.js +17 -0
  169. package/dist/core/scene/sameValue.js.map +1 -0
  170. package/dist/core/scene/scene.js +3 -3
  171. package/dist/core/scene/scene.js.map +1 -1
  172. package/dist/core/scene/sceneEdits.js +8 -0
  173. package/dist/core/scene/sceneEdits.js.map +1 -1
  174. package/dist/core/scene/sceneState.js +1 -2
  175. package/dist/core/scene/sceneState.js.map +1 -1
  176. package/dist/core/scene/serialization.js +45 -9
  177. package/dist/core/scene/serialization.js.map +1 -1
  178. package/dist/core/scene/staleReplay.js +5 -2
  179. package/dist/core/scene/staleReplay.js.map +1 -1
  180. package/dist/core/scene/statedFields.js +4 -2
  181. package/dist/core/scene/statedFields.js.map +1 -1
  182. package/dist/core/scene/treeEdits.js +2 -4
  183. package/dist/core/scene/treeEdits.js.map +1 -1
  184. package/dist/core/scene/treeOrder.js +3 -1
  185. package/dist/core/scene/treeOrder.js.map +1 -1
  186. package/dist/core/scene/treeReads.js +1 -2
  187. package/dist/core/scene/treeReads.js.map +1 -1
  188. package/dist/core/scene/treeShape.js +13 -0
  189. package/dist/core/scene/treeShape.js.map +1 -0
  190. package/dist/core/scene/types.js.map +1 -1
  191. package/dist/core/scene/useScene.js +1 -1
  192. package/dist/core/scene/useScene.js.map +1 -1
  193. package/dist/{cornerHandles-p8fDIAKt.d.ts → cornerHandles-CNCb7aTY.d.ts} +1 -1
  194. package/dist/{depSchema-mG19aEdD.d.ts → depSchema-B1RS-IJx.d.ts} +27 -54
  195. package/dist/devtools/handlers.js +1 -1
  196. package/dist/devtools/handlers.js.map +1 -1
  197. package/dist/devtools/protocol.js.map +1 -1
  198. package/dist/devtools-protocol.d.ts +4 -3
  199. package/dist/devtools.d.ts +3 -2
  200. package/dist/features/measure/measureHit.js +5 -3
  201. package/dist/features/measure/measureHit.js.map +1 -1
  202. package/dist/features/measure/measurement.js +1 -1
  203. package/dist/features/measure/measurement.js.map +1 -1
  204. package/dist/features/modes/modeLayers.js +14 -7
  205. package/dist/features/modes/modeLayers.js.map +1 -1
  206. package/dist/features/modes/modeTriggers.js +3 -3
  207. package/dist/features/modes/modeTriggers.js.map +1 -1
  208. package/dist/features/pathEffects/effectSources.js +25 -16
  209. package/dist/features/pathEffects/effectSources.js.map +1 -1
  210. package/dist/features/pathEffects/readSource.js +1 -1
  211. package/dist/features/pathEffects/readSource.js.map +1 -1
  212. package/dist/features/pathEffects/useEffectSourcesLayer.js +15 -19
  213. package/dist/features/pathEffects/useEffectSourcesLayer.js.map +1 -1
  214. package/dist/features/paths/editablePath.js +1 -1
  215. package/dist/features/paths/editablePath.js.map +1 -1
  216. package/dist/features/paths/pathSnap/sceneIndex.js +1 -1
  217. package/dist/features/paths/pathSnap/sceneIndex.js.map +1 -1
  218. package/dist/features/selection/enteredGroupFrame.js +65 -0
  219. package/dist/features/selection/enteredGroupFrame.js.map +1 -0
  220. package/dist/features/selection/enteredGroupScope.js +44 -0
  221. package/dist/features/selection/enteredGroupScope.js.map +1 -0
  222. package/dist/features/text/sceneTextDrafts.js +26 -27
  223. package/dist/features/text/sceneTextDrafts.js.map +1 -1
  224. package/dist/features/text/textHitAt.js +4 -2
  225. package/dist/features/text/textHitAt.js.map +1 -1
  226. package/dist/features/text/useSceneTextEdit.js +3 -3
  227. package/dist/features/text/useSceneTextEdit.js.map +1 -1
  228. package/dist/index.d.ts +225 -73
  229. package/dist/index.js +15 -5
  230. package/dist/interactions/actions/admitsChild.js +11 -0
  231. package/dist/interactions/actions/admitsChild.js.map +1 -0
  232. package/dist/interactions/actions/defaults/contextMenu.js +16 -3
  233. package/dist/interactions/actions/defaults/contextMenu.js.map +1 -1
  234. package/dist/interactions/actions/defaults/createPaintAction.js +4 -1
  235. package/dist/interactions/actions/defaults/createPaintAction.js.map +1 -1
  236. package/dist/interactions/actions/defaults/enterGroup.js +88 -0
  237. package/dist/interactions/actions/defaults/enterGroup.js.map +1 -0
  238. package/dist/interactions/actions/defaults/enterPathEdit.js +1 -1
  239. package/dist/interactions/actions/defaults/enterPathEdit.js.map +1 -1
  240. package/dist/interactions/actions/defaults/enterTextEdit.js +1 -1
  241. package/dist/interactions/actions/defaults/enterTextEdit.js.map +1 -1
  242. package/dist/interactions/actions/defaults/group.js +7 -7
  243. package/dist/interactions/actions/defaults/group.js.map +1 -1
  244. package/dist/interactions/actions/defaults/selectAll.js +7 -3
  245. package/dist/interactions/actions/defaults/selectAll.js.map +1 -1
  246. package/dist/interactions/actions/defaults/selectDeep.js +35 -0
  247. package/dist/interactions/actions/defaults/selectDeep.js.map +1 -0
  248. package/dist/interactions/actions/drawnBoxes.js +2 -1
  249. package/dist/interactions/actions/drawnBoxes.js.map +1 -1
  250. package/dist/interactions/actions/insert/action.js +1 -0
  251. package/dist/interactions/actions/insert/action.js.map +1 -1
  252. package/dist/interactions/actions/insert/parent.js +7 -5
  253. package/dist/interactions/actions/insert/parent.js.map +1 -1
  254. package/dist/interactions/actions/insert/placeAction.js +3 -2
  255. package/dist/interactions/actions/insert/placeAction.js.map +1 -1
  256. package/dist/interactions/actions/insert/session.js +2 -2
  257. package/dist/interactions/actions/insert/session.js.map +1 -1
  258. package/dist/interactions/actions/move/commit/reparent.js +1 -1
  259. package/dist/interactions/actions/move/commit/reparent.js.map +1 -1
  260. package/dist/interactions/actions/move/gestureAdapter.js +6 -7
  261. package/dist/interactions/actions/move/gestureAdapter.js.map +1 -1
  262. package/dist/interactions/actions/move/layout/exits.js +1 -1
  263. package/dist/interactions/actions/move/layout/exits.js.map +1 -1
  264. package/dist/interactions/actions/move/session.js +4 -4
  265. package/dist/interactions/actions/move/session.js.map +1 -1
  266. package/dist/interactions/actions/pathCommands/pathNodes.js +14 -5
  267. package/dist/interactions/actions/pathCommands/pathNodes.js.map +1 -1
  268. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js +14 -2
  269. package/dist/interactions/gestures/thresholdDrag/thresholdDrag.js.map +1 -1
  270. package/dist/{math-B56zxVs5.d.ts → math-C_elShP_.d.ts} +2 -5
  271. package/dist/math.d.ts +1 -1
  272. package/dist/move.d.ts +4 -4
  273. package/dist/{options-DBxEryYq.d.ts → options-ClEm3SEl.d.ts} +1 -1
  274. package/dist/resize.d.ts +4 -4
  275. package/dist/test-seams.d.ts +6 -6
  276. package/dist/tools/builtin/pressTarget.js +22 -0
  277. package/dist/tools/builtin/pressTarget.js.map +1 -0
  278. package/dist/tools/builtin/select/adapterPointQuery.js +3 -3
  279. package/dist/tools/builtin/select/adapterPointQuery.js.map +1 -1
  280. package/dist/tools/builtin/select/collapseDeferred.js +40 -0
  281. package/dist/tools/builtin/select/collapseDeferred.js.map +1 -0
  282. package/dist/tools/builtin/select/groupEntry.js +82 -0
  283. package/dist/tools/builtin/select/groupEntry.js.map +1 -0
  284. package/dist/tools/builtin/select/pressDecision.js +1 -1
  285. package/dist/tools/builtin/select/pressDecision.js.map +1 -1
  286. package/dist/tools/builtin/select/useSelectTool.js +61 -45
  287. package/dist/tools/builtin/select/useSelectTool.js.map +1 -1
  288. package/dist/{types-7puAuVD2.d.ts → types-49uYJN3j.d.ts} +1 -1
  289. package/dist/{types-CniVctmk.d.ts → types-BEdRIhNc.d.ts} +272 -109
  290. package/dist/{types-oFeQ5VjV.d.ts → types-p2alu-R1.d.ts} +1 -1
  291. package/dist/version.js +1 -1
  292. package/package.json +25 -25
@@ -1 +1 @@
1
- {"version":3,"file":"doubleClickTarget.js","names":[],"sources":["../../src/canvas/doubleClickTarget.ts"],"sourcesContent":["/** How {@link doubleClickTarget} reads the tree. */\nexport interface DoubleClickTree {\n parentOf: (id: string) => string | null;\n /** A node's routing kind; a node routed `'group'` is one a double-click can name. */\n kindOf: (id: string) => string | undefined;\n /** The group isolation is working inside, or null at the scene root. */\n scope: string | null;\n}\n\n/**\n * The node a double-click on `hitId` names: the outermost group on the way up from it, stopping\n * below `scope`, or `hitId` itself where no group stands between them. So a double-click on a\n * grouped member names the group, and each one inside an isolated group names the next group in.\n */\nexport function doubleClickTarget(hitId: string, tree: DoubleClickTree): string {\n const { parentOf, kindOf, scope } = tree;\n let named = hitId;\n let id: string | null = hitId;\n // Capped so a cyclic parent chain cannot hang the pick.\n for (let depth = 0; id !== null && id !== scope && depth < 10_000; depth++) {\n if (kindOf(id) === 'group') named = id;\n id = parentOf(id);\n }\n return named;\n}\n"],"mappings":";;;;;;AAcA,SAAgB,kBAAkB,OAAe,MAA+B;CAC9E,MAAM,EAAE,UAAU,QAAQ,UAAU;CACpC,IAAI,QAAQ;CACZ,IAAI,KAAoB;CAExB,KAAK,IAAI,QAAQ,GAAG,OAAO,QAAQ,OAAO,SAAS,QAAQ,KAAQ,SAAS;EAC1E,IAAI,OAAO,EAAE,MAAM,SAAS,QAAQ;EACpC,KAAK,SAAS,EAAE;CAClB;CACA,OAAO;AACT"}
1
+ {"version":3,"file":"doubleClickTarget.js","names":[],"sources":["../../src/canvas/doubleClickTarget.ts"],"sourcesContent":["import { pressTarget } from '../tools/builtin/pressTarget';\n\n/** How {@link doubleClickTarget} reads the tree. */\nexport interface DoubleClickTree {\n parentOf: (id: string) => string | null;\n /** A node's routing kind; a node routed `'group'` is one a double-click can name. */\n kindOf: (id: string) => string | undefined;\n /** The entered group, or null at the scene root. */\n scope: string | null;\n}\n\n/**\n * The node a double-click on `hitId` names: what a press there would select, taken inside\n * `scope` (`pressTarget`). So a double-click on a grouped member names the outermost group\n * above it, and each one inside the entered group names the next group in.\n */\nexport function doubleClickTarget(hitId: string, tree: DoubleClickTree): string {\n return pressTarget(\n { getParent: tree.parentOf, getNode: (id) => ({ kind: tree.kindOf(id) }) },\n hitId,\n tree.scope,\n );\n}\n"],"mappings":";;;;;;;;AAgBA,SAAgB,kBAAkB,OAAe,MAA+B;CAC9E,OAAO,YACL;EAAE,WAAW,KAAK;EAAU,UAAU,QAAQ,EAAE,MAAM,KAAK,OAAO,EAAE,EAAE;CAAG,GACzE,OACA,KAAK,KACP;AACF"}
@@ -1,7 +1,7 @@
1
+ import { inheritedPaintIn } from "../core/scene/inheritedPaint.js";
1
2
  import { axisAlignedBounds, unionBounds } from "../core/geometry/unionBounds.js";
2
3
  import { framedBounds, poseDescriptorForNode } from "../core/geometry/poseDescriptor.js";
3
4
  import { AUTO_POSE_DESCRIPTOR } from "../core/geometry/autoPoseDescriptor.js";
4
- import { inheritedPaintIn } from "./inheritedPaints.js";
5
5
  import { targetCorners, targetTransformOf, worldToLocal } from "../core/geometry/targetFrame.js";
6
6
  import { NO_INK_EXTENTS, inkExtentsBetween, outsetBox } from "../core/geometry/inkExtents.js";
7
7
  import { findNodeShape } from "./NodeShape.js";
@@ -18,8 +18,9 @@ import { boundsOfPath, meanScale } from "@weasel-js/geom";
18
18
  * The box of `id` drawn at `pose`, stroke included, in the frame the pose is
19
19
  * drawn in: a turned node's box comes back turned with it. A node with no
20
20
  * stroke, a painter that declares no {@link NodeShapeEntry.drawnBounds}, or
21
- * `includeStroke: false` answers `framedBounds` unchanged. A container's box
22
- * reaches as far as its members' boxes do. A node showing a derived path is
21
+ * `includeStroke: false` answers `framedBounds` unchanged. A group's box
22
+ * reaches as far as its members' boxes do; a mark's, as far as its own ink and its
23
+ * children's do. A node showing a derived path is
23
24
  * boxed around that path, and its placeholder pose plays no part.
24
25
  */
25
26
  function drawnBoundsOf(scene, id, pose, opts = {}) {
@@ -40,10 +41,18 @@ function inkExtentsOf(scene, id, pose, opts = {}) {
40
41
  width: own.width,
41
42
  height: own.height
42
43
  };
43
- const members = node.kind === "container" ? scene.childrenOf?.(id) ?? [] : [];
44
- if (members.length > 0) return membersExtents(scene, members, own, opts);
44
+ const members = scene.childrenOf?.(id) ?? [];
45
+ const reached = members.length > 0 ? membersExtents(scene, members, own, opts) : NO_INK_EXTENTS;
46
+ if (node.kind === "group" && members.length > 0) return reached;
45
47
  const drawn = findShapeDrawnBounds(node, pose, withInherited(scene, node, inkCtx(opts.scale, own)));
46
- return drawn === null ? NO_INK_EXTENTS : inkExtentsBetween(normalized(unframed), drawn);
48
+ if (drawn === null) return reached;
49
+ const ink = inkExtentsBetween(normalized(unframed), drawn);
50
+ return {
51
+ left: Math.max(ink.left, reached.left),
52
+ top: Math.max(ink.top, reached.top),
53
+ right: Math.max(ink.right, reached.right),
54
+ bottom: Math.max(ink.bottom, reached.bottom)
55
+ };
47
56
  }
48
57
  /** The box of a node drawn along `path`: the path's own, grown by the stroke
49
58
  * its painter reports. */
@@ -68,7 +77,7 @@ function derivedBoundsOf(scene, id, pose, path, opts) {
68
77
  });
69
78
  return drawn === null ? own : unionBounds([own, drawn]) ?? own;
70
79
  }
71
- /** A container's extents: its members' drawn boxes, taken into its own frame,
80
+ /** A parent's member extents: its members' drawn boxes, taken into its own frame,
72
81
  * past its own box. */
73
82
  function membersExtents(scene, members, own, opts) {
74
83
  const xf = targetTransformOf(own);
@@ -1 +1 @@
1
- {"version":3,"file":"drawnBounds.js","names":[],"sources":["../../src/canvas/drawnBounds.ts"],"sourcesContent":["/**\n * The box of a node as drawn — its pose box with the stroke included — which\n * is what selection chrome, resize, rotate, and alignment sit on. Places that\n * store geometry (a group's derived pose, booleans, export) keep reading the\n * pose box through `framedBounds`.\n */\nimport { inheritedPaintIn } from './inheritedPaints';\nimport { framedBounds, poseDescriptorForNode, type PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport {\n inkExtentsBetween,\n NO_INK_EXTENTS,\n outsetBox,\n type InkExtents,\n} from 'core/geometry/inkExtents';\nimport { targetCorners, targetTransformOf, worldToLocal } from 'core/geometry/targetFrame';\nimport { axisAlignedBounds, unionBounds } from 'core/geometry/unionBounds';\nimport type { Node, NodeId } from 'core/scene/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { boundsOfPath, meanScale, type Path, type Scale2 } from '@weasel-js/geom';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport { findNodeShape, type NodeInkCtx } from './NodeShape';\n\n/** The scene reads {@link drawnBoundsOf} makes. `childrenOf` absent: every\n * node is a leaf. `derivedPathOf` absent: every node is measured by its pose,\n * a path-derived one included — `drawnBoundsScene` supplies it for a `Scene`. */\nexport interface DrawnBoundsScene {\n get(id: NodeId): Node<unknown, string, unknown> | undefined;\n childrenOf?(id: NodeId): readonly NodeId[];\n /** The path a node shows in place of its pose's, or `null` for one that shows its own. */\n derivedPathOf?(id: NodeId): Path | null;\n}\n\n/** How {@link drawnBoundsOf} reads a node. */\nexport interface DrawnBoundsOptions {\n /** How poses are read. Default `AUTO_POSE_DESCRIPTOR`. */\n descriptor?: PoseDescriptor<unknown>;\n /** A member's world pose, for boxing a container around its members.\n * Default: its pose as stored, which is world unless containers compose. */\n worldPoseOf?: (id: NodeId) => unknown;\n /** `false` answers the pose box alone. Default `true`. */\n includeStroke?: boolean;\n /** View scale, which a screen-pixel stroke width is resolved at. Default 1. */\n scale?: number | Scale2;\n}\n\n/**\n * The box of `id` drawn at `pose`, stroke included, in the frame the pose is\n * drawn in: a turned node's box comes back turned with it. A node with no\n * stroke, a painter that declares no {@link NodeShapeEntry.drawnBounds}, or\n * `includeStroke: false` answers `framedBounds` unchanged. A container's box\n * reaches as far as its members' boxes do. A node showing a derived path is\n * boxed around that path, and its placeholder pose plays no part.\n */\nexport function drawnBoundsOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): Bounds {\n const derived = scene.derivedPathOf?.(id) ?? null;\n if (derived !== null) return derivedBoundsOf(scene, id, pose, derived, opts);\n return outsetBox(framedBounds(pose, descriptorFor(scene, id, opts)), inkExtentsOf(scene, id, pose, opts));\n}\n\n/** How far `id`'s ink at `pose` reaches past its pose box on each side, in\n * the box's own frame: what {@link drawnBoundsOf} grows the pose box by. */\nexport function inkExtentsOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): InkExtents {\n if (opts.includeStroke === false) return NO_INK_EXTENTS;\n const node = scene.get(id);\n if (!node) return NO_INK_EXTENTS;\n const own = framedBounds(pose, descriptorFor(scene, id, opts));\n const unframed = { x: own.x, y: own.y, width: own.width, height: own.height };\n const members = node.kind === 'container' ? scene.childrenOf?.(id) ?? [] : [];\n if (members.length > 0) return membersExtents(scene, members, own, opts);\n const drawn = findShapeDrawnBounds(node, pose, withInherited(scene, node, inkCtx(opts.scale, own)));\n return drawn === null ? NO_INK_EXTENTS : inkExtentsBetween(normalized(unframed), drawn);\n}\n\n/** The box of a node drawn along `path`: the path's own, grown by the stroke\n * its painter reports. */\nfunction derivedBoundsOf(scene: DrawnBoundsScene, id: NodeId, pose: unknown, path: Path, opts: DrawnBoundsOptions): Bounds {\n const { x, y, width, height } = boundsOfPath(path);\n const own = { x, y, width, height };\n const node = scene.get(id);\n if (opts.includeStroke === false || !node) return own;\n const ctx = { ...withInherited(scene, node, inkCtx(opts.scale, { x: 0, y: 0, width: 0, height: 0 })), derivedPath: path };\n const drawn = findShapeDrawnBounds(node, pose, ctx);\n return drawn === null ? own : unionBounds([own, drawn]) ?? own;\n}\n\n/** A container's extents: its members' drawn boxes, taken into its own frame,\n * past its own box. */\nfunction membersExtents(\n scene: DrawnBoundsScene,\n members: readonly NodeId[],\n own: Bounds,\n opts: DrawnBoundsOptions,\n): InkExtents {\n const xf = targetTransformOf(own);\n const worldPoseOf = opts.worldPoseOf ?? ((m: NodeId) => scene.get(m)?.pose);\n let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;\n for (const m of members) {\n const box = drawnBoundsOf(scene, m, worldPoseOf(m), opts);\n for (const c of targetCorners(box, targetTransformOf(box))) {\n const p = worldToLocal(xf, c.x, c.y);\n if (!p) continue;\n x0 = Math.min(x0, p.x); y0 = Math.min(y0, p.y);\n x1 = Math.max(x1, p.x); y1 = Math.max(y1, p.y);\n }\n }\n if (x0 === Infinity) return NO_INK_EXTENTS;\n return inkExtentsBetween(normalized(own), { x: x0, y: y0, width: x1 - x0, height: y1 - y0 });\n}\n\n/** {@link drawnBoundsOf} as an axis-aligned box: what a turned node covers. */\nexport function drawnAABBOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): Bounds {\n return axisAlignedBounds(drawnBoundsOf(scene, id, pose, opts));\n}\n\n/** The painter's {@link NodeShapeEntry.drawnBounds} for `node`, or `null`\n * when no painter matches or the one that does declares none. */\nexport function findShapeDrawnBounds<TData, TPose>(\n node: Node<TData, string, TPose>,\n pose: TPose,\n ctx?: NodeInkCtx,\n): Bounds | null {\n return findNodeShape(node)?.drawnBounds?.(node, pose, ctx) ?? null;\n}\n\nfunction descriptorFor(scene: DrawnBoundsScene, id: NodeId, opts: DrawnBoundsOptions): PoseDescriptor<unknown> {\n const d = opts.descriptor ?? AUTO_POSE_DESCRIPTOR;\n const node = scene.get(id);\n return node ? poseDescriptorForNode(d, node) : d;\n}\n\n/** A per-axis view scale also carries the map from `own`'s frame to screen,\n * so a `{ px }` stroke is measured there rather than at the mean. */\nfunction inkCtx(scale: number | Scale2 | undefined, own: Bounds): NodeInkCtx | undefined {\n if (scale === undefined) return undefined;\n if (typeof scale === 'number') return { scale };\n const xf = targetTransformOf(own);\n const [a, b, c, d] = xf.identity ? [1, 0, 0, 1] : xf.toWorld;\n return { scale: meanScale(scale), screen: [scale.x * a, scale.y * b, scale.x * c, scale.y * d] };\n}\n\nfunction withInherited(\n scene: DrawnBoundsScene,\n node: Node<unknown, string, unknown>,\n ctx: NodeInkCtx | undefined,\n): NodeInkCtx | undefined {\n const inherited = inheritedPaintIn(scene, node);\n return inherited === undefined ? ctx : { ...ctx, inherited };\n}\n\nfunction normalized(b: Bounds): Bounds {\n return {\n x: Math.min(b.x, b.x + b.width),\n y: Math.min(b.y, b.y + b.height),\n width: Math.abs(b.width),\n height: Math.abs(b.height),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"drawnBounds.js","names":[],"sources":["../../src/canvas/drawnBounds.ts"],"sourcesContent":["/**\n * The box of a node as drawn — its pose box with the stroke included — which\n * is what selection chrome, resize, rotate, and alignment sit on. Places that\n * store geometry (a group's derived pose, booleans, export) keep reading the\n * pose box through `framedBounds`.\n */\nimport { inheritedPaintIn } from './inheritedPaints';\nimport { framedBounds, poseDescriptorForNode, type PoseDescriptor } from 'core/geometry/poseDescriptor';\nimport {\n inkExtentsBetween,\n NO_INK_EXTENTS,\n outsetBox,\n type InkExtents,\n} from 'core/geometry/inkExtents';\nimport { targetCorners, targetTransformOf, worldToLocal } from 'core/geometry/targetFrame';\nimport { axisAlignedBounds, unionBounds } from 'core/geometry/unionBounds';\nimport type { Node, NodeId } from 'core/scene/types';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { boundsOfPath, meanScale, type Path, type Scale2 } from '@weasel-js/geom';\nimport { AUTO_POSE_DESCRIPTOR } from 'core/geometry/autoPoseDescriptor';\nimport { findNodeShape, type NodeInkCtx } from './NodeShape';\n\n/** The scene reads {@link drawnBoundsOf} makes. `childrenOf` absent: every\n * node is a leaf. `derivedPathOf` absent: every node is measured by its pose,\n * a path-derived one included — `drawnBoundsScene` supplies it for a `Scene`. */\nexport interface DrawnBoundsScene {\n get(id: NodeId): Node<unknown, string, unknown> | undefined;\n childrenOf?(id: NodeId): readonly NodeId[];\n /** The path a node shows in place of its pose's, or `null` for one that shows its own. */\n derivedPathOf?(id: NodeId): Path | null;\n}\n\n/** How {@link drawnBoundsOf} reads a node. */\nexport interface DrawnBoundsOptions {\n /** How poses are read. Default `AUTO_POSE_DESCRIPTOR`. */\n descriptor?: PoseDescriptor<unknown>;\n /** A member's world pose, for boxing a container around its members.\n * Default: its pose as stored, which is world unless containers compose. */\n worldPoseOf?: (id: NodeId) => unknown;\n /** `false` answers the pose box alone. Default `true`. */\n includeStroke?: boolean;\n /** View scale, which a screen-pixel stroke width is resolved at. Default 1. */\n scale?: number | Scale2;\n}\n\n/**\n * The box of `id` drawn at `pose`, stroke included, in the frame the pose is\n * drawn in: a turned node's box comes back turned with it. A node with no\n * stroke, a painter that declares no {@link NodeShapeEntry.drawnBounds}, or\n * `includeStroke: false` answers `framedBounds` unchanged. A group's box\n * reaches as far as its members' boxes do; a mark's, as far as its own ink and its\n * children's do. A node showing a derived path is\n * boxed around that path, and its placeholder pose plays no part.\n */\nexport function drawnBoundsOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): Bounds {\n const derived = scene.derivedPathOf?.(id) ?? null;\n if (derived !== null) return derivedBoundsOf(scene, id, pose, derived, opts);\n return outsetBox(framedBounds(pose, descriptorFor(scene, id, opts)), inkExtentsOf(scene, id, pose, opts));\n}\n\n/** How far `id`'s ink at `pose` reaches past its pose box on each side, in\n * the box's own frame: what {@link drawnBoundsOf} grows the pose box by. */\nexport function inkExtentsOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): InkExtents {\n if (opts.includeStroke === false) return NO_INK_EXTENTS;\n const node = scene.get(id);\n if (!node) return NO_INK_EXTENTS;\n const own = framedBounds(pose, descriptorFor(scene, id, opts));\n const unframed = { x: own.x, y: own.y, width: own.width, height: own.height };\n const members = scene.childrenOf?.(id) ?? [];\n const reached = members.length > 0 ? membersExtents(scene, members, own, opts) : NO_INK_EXTENTS;\n if (node.kind === 'group' && members.length > 0) return reached;\n const drawn = findShapeDrawnBounds(node, pose, withInherited(scene, node, inkCtx(opts.scale, own)));\n if (drawn === null) return reached;\n const ink = inkExtentsBetween(normalized(unframed), drawn);\n return {\n left: Math.max(ink.left, reached.left),\n top: Math.max(ink.top, reached.top),\n right: Math.max(ink.right, reached.right),\n bottom: Math.max(ink.bottom, reached.bottom),\n };\n}\n\n/** The box of a node drawn along `path`: the path's own, grown by the stroke\n * its painter reports. */\nfunction derivedBoundsOf(scene: DrawnBoundsScene, id: NodeId, pose: unknown, path: Path, opts: DrawnBoundsOptions): Bounds {\n const { x, y, width, height } = boundsOfPath(path);\n const own = { x, y, width, height };\n const node = scene.get(id);\n if (opts.includeStroke === false || !node) return own;\n const ctx = { ...withInherited(scene, node, inkCtx(opts.scale, { x: 0, y: 0, width: 0, height: 0 })), derivedPath: path };\n const drawn = findShapeDrawnBounds(node, pose, ctx);\n return drawn === null ? own : unionBounds([own, drawn]) ?? own;\n}\n\n/** A parent's member extents: its members' drawn boxes, taken into its own frame,\n * past its own box. */\nfunction membersExtents(\n scene: DrawnBoundsScene,\n members: readonly NodeId[],\n own: Bounds,\n opts: DrawnBoundsOptions,\n): InkExtents {\n const xf = targetTransformOf(own);\n const worldPoseOf = opts.worldPoseOf ?? ((m: NodeId) => scene.get(m)?.pose);\n let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;\n for (const m of members) {\n const box = drawnBoundsOf(scene, m, worldPoseOf(m), opts);\n for (const c of targetCorners(box, targetTransformOf(box))) {\n const p = worldToLocal(xf, c.x, c.y);\n if (!p) continue;\n x0 = Math.min(x0, p.x); y0 = Math.min(y0, p.y);\n x1 = Math.max(x1, p.x); y1 = Math.max(y1, p.y);\n }\n }\n if (x0 === Infinity) return NO_INK_EXTENTS;\n return inkExtentsBetween(normalized(own), { x: x0, y: y0, width: x1 - x0, height: y1 - y0 });\n}\n\n/** {@link drawnBoundsOf} as an axis-aligned box: what a turned node covers. */\nexport function drawnAABBOf(\n scene: DrawnBoundsScene,\n id: NodeId,\n pose: unknown,\n opts: DrawnBoundsOptions = {},\n): Bounds {\n return axisAlignedBounds(drawnBoundsOf(scene, id, pose, opts));\n}\n\n/** The painter's {@link NodeShapeEntry.drawnBounds} for `node`, or `null`\n * when no painter matches or the one that does declares none. */\nexport function findShapeDrawnBounds<TData, TPose>(\n node: Node<TData, string, TPose>,\n pose: TPose,\n ctx?: NodeInkCtx,\n): Bounds | null {\n return findNodeShape(node)?.drawnBounds?.(node, pose, ctx) ?? null;\n}\n\nfunction descriptorFor(scene: DrawnBoundsScene, id: NodeId, opts: DrawnBoundsOptions): PoseDescriptor<unknown> {\n const d = opts.descriptor ?? AUTO_POSE_DESCRIPTOR;\n const node = scene.get(id);\n return node ? poseDescriptorForNode(d, node) : d;\n}\n\n/** A per-axis view scale also carries the map from `own`'s frame to screen,\n * so a `{ px }` stroke is measured there rather than at the mean. */\nfunction inkCtx(scale: number | Scale2 | undefined, own: Bounds): NodeInkCtx | undefined {\n if (scale === undefined) return undefined;\n if (typeof scale === 'number') return { scale };\n const xf = targetTransformOf(own);\n const [a, b, c, d] = xf.identity ? [1, 0, 0, 1] : xf.toWorld;\n return { scale: meanScale(scale), screen: [scale.x * a, scale.y * b, scale.x * c, scale.y * d] };\n}\n\nfunction withInherited(\n scene: DrawnBoundsScene,\n node: Node<unknown, string, unknown>,\n ctx: NodeInkCtx | undefined,\n): NodeInkCtx | undefined {\n const inherited = inheritedPaintIn(scene, node);\n return inherited === undefined ? ctx : { ...ctx, inherited };\n}\n\nfunction normalized(b: Bounds): Bounds {\n return {\n x: Math.min(b.x, b.x + b.width),\n y: Math.min(b.y, b.y + b.height),\n width: Math.abs(b.width),\n height: Math.abs(b.height),\n };\n}\n"],"mappings":"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"}
@@ -1,13 +1,7 @@
1
- import { inheritedPaintOf, inheritsPaint, sceneAncestors } from "../core/scene/inheritedPaint.js";
1
+ import { inheritedPaintIn, inheritedPaintOf, sceneAncestors } from "../core/scene/inheritedPaint.js";
2
2
  import { INHERIT } from "@weasel-js/paint";
3
3
 
4
4
  //#region src/canvas/inheritedPaints.ts
5
- /** What `node`'s `'inherit'` fields take from its containers in `scene`, or
6
- * `undefined` for a node that inherits nothing. */
7
- function inheritedPaintIn(scene, node) {
8
- if (!inheritsPaint(node)) return void 0;
9
- return inheritedPaintOf(node, sceneAncestors((id) => scene.get(id), node));
10
- }
11
5
  var INHERITS_BOTH = { data: {
12
6
  fill: INHERIT,
13
7
  stroke: INHERIT
@@ -25,20 +19,37 @@ function sceneInheritedPaintOf(scene) {
25
19
  /**
26
20
  * Wrap a `drawOne` so a node whose fill or stroke is `'inherit'` arrives at
27
21
  * the painter with its containers' paint on `ctx.inherited`, as
28
- * `withDerivedPaths` does for a derived path. Every other node passes the
29
- * caller's `ctx` through untouched.
22
+ * `withDerivedPaths` does for a derived path. Every other node, and one whose
23
+ * caller already says what it inherits — a ghost whose containers are mid-edit —
24
+ * passes the caller's `ctx` through untouched.
30
25
  */
31
26
  function withInheritedPaints(scene, drawOne) {
32
27
  const inheritedOf = sceneInheritedPaintOf(scene);
33
28
  return (node, pose, view, ctx) => {
34
- const inherited = inheritedOf(node);
29
+ const inherited = ctx?.inherited ?? inheritedOf(node);
35
30
  return inherited === void 0 ? drawOne(node, pose, view, ctx) : drawOne(node, pose, view, {
36
31
  ...ctx,
37
32
  inherited
38
33
  });
39
34
  };
40
35
  }
36
+ /**
37
+ * {@link withInheritedPaints} for a cull's `paintBounds`: a node whose stroke
38
+ * is `'inherit'` is measured with its containers' stroke on `ctx.inherited`,
39
+ * unless its caller already says what it inherits. For a consumer culling its
40
+ * own scene walk with `defaultPaintBounds`, which cannot bound such a node alone.
41
+ */
42
+ function withInheritedPaintBounds(scene, paintBounds) {
43
+ const inheritedOf = sceneInheritedPaintOf(scene);
44
+ return (node, pose, view, ctx) => {
45
+ const inherited = ctx?.inherited ?? inheritedOf(node);
46
+ return paintBounds(node, pose, view, inherited === void 0 ? ctx : {
47
+ ...ctx,
48
+ inherited
49
+ });
50
+ };
51
+ }
41
52
 
42
53
  //#endregion
43
- export { inheritedPaintIn, paintAbove, sceneInheritedPaintOf, withInheritedPaints };
54
+ export { inheritedPaintIn, paintAbove, sceneInheritedPaintOf, withInheritedPaintBounds, withInheritedPaints };
44
55
  //# sourceMappingURL=inheritedPaints.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"inheritedPaints.js","names":[],"sources":["../../src/canvas/inheritedPaints.ts"],"sourcesContent":["import { INHERIT } from '@weasel-js/paint';\nimport type { Node, NodeId } from 'core/scene/types';\nimport { inheritedPaintOf, inheritsPaint, sceneAncestors, type InheritedPaint } from 'core/scene/inheritedPaint';\nimport type { SceneViewDrawOne } from './nodeShapeTypes';\n\n/** The part of a scene the lookup reads. */\ntype ParentLookup<TNode> = { get(id: NodeId): TNode | undefined };\n\ntype PaintedNode = { data?: unknown; parent?: NodeId | null };\n\n/** What `node`'s `'inherit'` fields take from its containers in `scene`, or\n * `undefined` for a node that inherits nothing. */\nexport function inheritedPaintIn<TNode extends PaintedNode>(\n scene: ParentLookup<TNode>,\n node: TNode,\n): InheritedPaint | undefined {\n if (!inheritsPaint(node)) return undefined;\n return inheritedPaintOf(node, sceneAncestors((id: never) => scene.get(id), node));\n}\n\nconst INHERITS_BOTH = { data: { fill: INHERIT, stroke: INHERIT } };\n\n/** What an `'inherit'` fill and stroke on `node` would take from its\n * containers in `scene`, whatever `node` holds now — what an editor offering\n * the choice shows it would paint. */\nexport function paintAbove<TNode extends PaintedNode>(scene: ParentLookup<TNode>, node: TNode): InheritedPaint {\n return inheritedPaintOf(INHERITS_BOTH, sceneAncestors((id: never) => scene.get(id), node));\n}\n\n/** {@link inheritedPaintIn} bound to `scene`. */\nexport function sceneInheritedPaintOf<TNode extends PaintedNode>(\n scene: ParentLookup<TNode>,\n): (node: TNode) => InheritedPaint | undefined {\n return (node) => inheritedPaintIn(scene, node);\n}\n\n/**\n * Wrap a `drawOne` so a node whose fill or stroke is `'inherit'` arrives at\n * the painter with its containers' paint on `ctx.inherited`, as\n * `withDerivedPaths` does for a derived path. Every other node passes the\n * caller's `ctx` through untouched.\n */\nexport function withInheritedPaints<TData, TLayer extends string, TPose>(\n scene: ParentLookup<Node<TData, TLayer, TPose>>,\n drawOne: SceneViewDrawOne<TData, TLayer, TPose>,\n): SceneViewDrawOne<TData, TLayer, TPose> {\n const inheritedOf = sceneInheritedPaintOf(scene);\n return (node, pose, view, ctx) => {\n const inherited = inheritedOf(node);\n return inherited === undefined ? drawOne(node, pose, view, ctx) : drawOne(node, pose, view, { ...ctx, inherited });\n };\n}\n"],"mappings":";;;;;;AAYA,SAAgB,iBACd,OACA,MAC4B;CAC5B,IAAI,CAAC,cAAc,IAAI,GAAG,OAAO;CACjC,OAAO,iBAAiB,MAAM,gBAAgB,OAAc,MAAM,IAAI,EAAE,GAAG,IAAI,CAAC;AAClF;AAEA,IAAM,gBAAgB,EAAE,MAAM;CAAE,MAAM;CAAS,QAAQ;AAAQ,EAAE;;;;AAKjE,SAAgB,WAAsC,OAA4B,MAA6B;CAC7G,OAAO,iBAAiB,eAAe,gBAAgB,OAAc,MAAM,IAAI,EAAE,GAAG,IAAI,CAAC;AAC3F;;AAGA,SAAgB,sBACd,OAC6C;CAC7C,QAAQ,SAAS,iBAAiB,OAAO,IAAI;AAC/C;;;;;;;AAQA,SAAgB,oBACd,OACA,SACwC;CACxC,MAAM,cAAc,sBAAsB,KAAK;CAC/C,QAAQ,MAAM,MAAM,MAAM,QAAQ;EAChC,MAAM,YAAY,YAAY,IAAI;EAClC,OAAO,cAAc,SAAY,QAAQ,MAAM,MAAM,MAAM,GAAG,IAAI,QAAQ,MAAM,MAAM,MAAM;GAAE,GAAG;GAAK;EAAU,CAAC;CACnH;AACF"}
1
+ {"version":3,"file":"inheritedPaints.js","names":[],"sources":["../../src/canvas/inheritedPaints.ts"],"sourcesContent":["import { INHERIT } from '@weasel-js/paint';\nimport type { Node, NodeId } from 'core/scene/types';\nimport { inheritedPaintIn, inheritedPaintOf, sceneAncestors, type InheritedPaint } from 'core/scene/inheritedPaint';\nimport type { SceneViewDrawOne } from './nodeShapeTypes';\nimport type { PaintBoundsFn } from './paintCull';\n\n/** The part of a scene the lookup reads. */\ntype ParentLookup<TNode> = { get(id: NodeId): TNode | undefined };\n\ntype PaintedNode = { data?: unknown; parent?: NodeId | null };\n\nexport { inheritedPaintIn };\n\nconst INHERITS_BOTH = { data: { fill: INHERIT, stroke: INHERIT } };\n\n/** What an `'inherit'` fill and stroke on `node` would take from its\n * containers in `scene`, whatever `node` holds now — what an editor offering\n * the choice shows it would paint. */\nexport function paintAbove<TNode extends PaintedNode>(scene: ParentLookup<TNode>, node: TNode): InheritedPaint {\n return inheritedPaintOf(INHERITS_BOTH, sceneAncestors((id: never) => scene.get(id), node));\n}\n\n/** {@link inheritedPaintIn} bound to `scene`. */\nexport function sceneInheritedPaintOf<TNode extends PaintedNode>(\n scene: ParentLookup<TNode>,\n): (node: TNode) => InheritedPaint | undefined {\n return (node) => inheritedPaintIn(scene, node);\n}\n\n/**\n * Wrap a `drawOne` so a node whose fill or stroke is `'inherit'` arrives at\n * the painter with its containers' paint on `ctx.inherited`, as\n * `withDerivedPaths` does for a derived path. Every other node, and one whose\n * caller already says what it inherits — a ghost whose containers are mid-edit —\n * passes the caller's `ctx` through untouched.\n */\nexport function withInheritedPaints<TData, TLayer extends string, TPose>(\n scene: ParentLookup<Node<TData, TLayer, TPose>>,\n drawOne: SceneViewDrawOne<TData, TLayer, TPose>,\n): SceneViewDrawOne<TData, TLayer, TPose> {\n const inheritedOf = sceneInheritedPaintOf(scene);\n return (node, pose, view, ctx) => {\n const inherited = ctx?.inherited ?? inheritedOf(node);\n return inherited === undefined ? drawOne(node, pose, view, ctx) : drawOne(node, pose, view, { ...ctx, inherited });\n };\n}\n\n/**\n * {@link withInheritedPaints} for a cull's `paintBounds`: a node whose stroke\n * is `'inherit'` is measured with its containers' stroke on `ctx.inherited`,\n * unless its caller already says what it inherits. For a consumer culling its\n * own scene walk with `defaultPaintBounds`, which cannot bound such a node alone.\n */\nexport function withInheritedPaintBounds<TData, TLayer extends string, TPose>(\n scene: ParentLookup<Node<TData, TLayer, TPose>>,\n paintBounds: PaintBoundsFn<Node<TData, TLayer, TPose>, TPose>,\n): PaintBoundsFn<Node<TData, TLayer, TPose>, TPose> {\n const inheritedOf = sceneInheritedPaintOf(scene);\n return (node, pose, view, ctx) => {\n const inherited = ctx?.inherited ?? inheritedOf(node);\n return paintBounds(node, pose, view, inherited === undefined ? ctx : { ...ctx, inherited });\n };\n}\n"],"mappings":";;;;AAaA,IAAM,gBAAgB,EAAE,MAAM;CAAE,MAAM;CAAS,QAAQ;AAAQ,EAAE;;;;AAKjE,SAAgB,WAAsC,OAA4B,MAA6B;CAC7G,OAAO,iBAAiB,eAAe,gBAAgB,OAAc,MAAM,IAAI,EAAE,GAAG,IAAI,CAAC;AAC3F;;AAGA,SAAgB,sBACd,OAC6C;CAC7C,QAAQ,SAAS,iBAAiB,OAAO,IAAI;AAC/C;;;;;;;;AASA,SAAgB,oBACd,OACA,SACwC;CACxC,MAAM,cAAc,sBAAsB,KAAK;CAC/C,QAAQ,MAAM,MAAM,MAAM,QAAQ;EAChC,MAAM,YAAY,KAAK,aAAa,YAAY,IAAI;EACpD,OAAO,cAAc,SAAY,QAAQ,MAAM,MAAM,MAAM,GAAG,IAAI,QAAQ,MAAM,MAAM,MAAM;GAAE,GAAG;GAAK;EAAU,CAAC;CACnH;AACF;;;;;;;AAQA,SAAgB,yBACd,OACA,aACkD;CAClD,MAAM,cAAc,sBAAsB,KAAK;CAC/C,QAAQ,MAAM,MAAM,MAAM,QAAQ;EAChC,MAAM,YAAY,KAAK,aAAa,YAAY,IAAI;EACpD,OAAO,YAAY,MAAM,MAAM,MAAM,cAAc,SAAY,MAAM;GAAE,GAAG;GAAK;EAAU,CAAC;CAC5F;AACF"}
@@ -6,12 +6,6 @@ import { markerReach, resolveStrokeWidth, widestStrokeWidth } from "@weasel-js/s
6
6
 
7
7
  //#region src/canvas/nodeShapes/nodePaint.ts
8
8
  paintDrawKindRegistry.subscribe(bumpNodeMemoGeneration);
9
- /** How a container's silhouette is inked: not at all, since it paints nothing. */
10
- var CONTAINER_INK = Object.freeze({
11
- filled: false,
12
- outset: 0,
13
- inset: 0
14
- });
15
9
  /** Per-side grab reach for a resolved stroke: world units for what the
16
10
  * renderer builds in world, screen pixels for what it builds on screen — a
17
11
  * `{ px }` width, and each head by its own size's units. */
@@ -122,5 +116,5 @@ function pathPaintOf(data, inherited) {
122
116
  }
123
117
 
124
118
  //#endregion
125
- export { CONTAINER_INK, inkReach, pathPaintOf, strokeOutset, strokeOutsetPerAxis, withAnimatedVertexColors };
119
+ export { inkReach, pathPaintOf, strokeOutset, strokeOutsetPerAxis, withAnimatedVertexColors };
126
120
  //# sourceMappingURL=nodePaint.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"nodePaint.js","names":[],"sources":["../../../src/canvas/nodeShapes/nodePaint.ts"],"sourcesContent":["import type { Stroke, Fill, Inherit } from '@weasel-js/paint';\nimport { resolveFill, resolveStroke } from '@weasel-js/paint';\nimport type { NodeInk, NodeInkCtx, NodePaintCtx } from 'canvas/NodeShape';\nimport { countPathAnchors } from '@weasel-js/geom';\nimport { markerReach } from '@weasel-js/stroke';\nimport { resolveStrokeWidth, widestStrokeWidth } from '@weasel-js/stroke';\nimport type { DrawCommand, PathDrawCommand } from '@weasel-js/renderer';\nimport { paintDrawKindRegistry } from '@weasel-js/renderer';\nimport { bumpNodeMemoGeneration } from 'core/scene/nodeMemo';\nimport type { InheritedPaint } from 'core/scene/inheritedPaint';\n\n// The painters memoize a node's paint and resolve a fill's frame inside the\n// memo, so the kind set is ambient state that memo cannot see change.\npaintDrawKindRegistry.subscribe(bumpNodeMemoGeneration);\n\n/** How a container's silhouette is inked: not at all, since it paints nothing. */\nexport const CONTAINER_INK: Readonly<NodeInk> = Object.freeze({ filled: false, outset: 0, inset: 0 });\n\n/** Per-side grab reach for a resolved stroke: world units for what the\n * renderer builds in world, screen pixels for what it builds on screen — a\n * `{ px }` width, and each head by its own size's units. */\nexport function inkReach(\n stroke: Stroke | null,\n scale: number | undefined,\n): Pick<NodeInk, 'outset' | 'inset' | 'outsetPx' | 'insetPx'> {\n if (stroke === null) return { outset: 0, inset: 0 };\n // A marker paints past the path's own end, and the kit's rule is that\n // visible chrome is hittable — so the reach has to cover it.\n const heads = markerReach(stroke);\n const sides = (width: number, headReach: number) => {\n switch (stroke.align ?? 'center') {\n case 'inner': return { outset: headReach, inset: width };\n case 'outer': return { outset: width + headReach, inset: 0 };\n default: return { outset: width / 2 + headReach, inset: width / 2 };\n }\n };\n const width = stroke.width ?? 1;\n // Per-vertex widths are world units even on a `{ px }` stroke.\n const worldWidth = widestStrokeWidth(typeof width === 'object' ? 0 : resolveStrokeWidth(width, scale ?? 1), stroke.vertexWidths);\n const world = sides(worldWidth, heads.world);\n if (typeof width !== 'object' && !(heads.px > 0)) return world;\n const px = sides(typeof width === 'object' ? width.px : 0, heads.px);\n return { outset: world.outset, inset: world.inset, outsetPx: px.outset, insetPx: px.inset };\n}\n\n/** How far `stroke` reaches outside its outline on every side, world units at\n * `scale`: {@link inkReach}'s outset with its screen part resolved. */\nexport function strokeOutset(stroke: Stroke | null, scale: number): number {\n const r = inkReach(stroke, scale);\n return r.outset + (r.outsetPx ?? 0) / (scale > 0 ? scale : 1);\n}\n\n/** {@link strokeOutset} per axis of the frame `ctx.screen` maps from: `x` past\n * the left and right sides, `y` past the top and bottom. A `{ px }` reach\n * is a disk on screen, and its extent along each axis of that frame is the\n * support of the disk pulled back through the map. */\nexport function strokeOutsetPerAxis(stroke: Stroke | null, ctx: NodeInkCtx | undefined): { x: number; y: number } {\n const scale = ctx?.scale ?? 1;\n const screen = ctx?.screen;\n if (!screen) {\n const r = strokeOutset(stroke, scale);\n return { x: r, y: r };\n }\n const r = inkReach(stroke, scale);\n const px = r.outsetPx ?? 0;\n if (px === 0) return { x: r.outset, y: r.outset };\n const [a, b, c, d] = screen;\n const det = Math.abs(a * d - b * c);\n if (!(det > 0)) return { x: r.outset, y: r.outset };\n return { x: r.outset + (px * Math.hypot(c, d)) / det, y: r.outset + (px * Math.hypot(a, b)) / det };\n}\n\n/** Apply {@link NodePaintCtx.vertexColors} to a path painter's body — its first\n * command. Applied outside the paint memo, whose key\n * cannot see an override: `cmds` comes back as-is when nothing changes, and\n * otherwise as a copy, since a memoized list is not ours to edit. Colors whose\n * length does not match the path's anchors are dropped. */\nexport function withAnimatedVertexColors(cmds: DrawCommand[], ctx: NodePaintCtx | undefined): DrawCommand[] {\n const resolve = ctx?.vertexColors;\n const body = cmds[0];\n if (resolve === undefined || body?.kind !== 'path') return cmds;\n const expected = 4 * countPathAnchors(body.path);\n const fill = body.fill ? resolve('fill', body.vertexColors) : undefined;\n const stroke = body.stroke ? resolve('stroke', body.stroke.vertexColors) : undefined;\n const fillChanged = fill?.length === expected && fill !== body.vertexColors;\n const strokeChanged = stroke?.length === expected && stroke !== body.stroke?.vertexColors;\n if (!fillChanged && !strokeChanged) return cmds;\n const next: PathDrawCommand = {\n ...body,\n ...(fillChanged ? { vertexColors: fill as number[] } : {}),\n ...(strokeChanged ? { stroke: { ...body.stroke!, vertexColors: stroke as number[] } } : {}),\n };\n return [next, ...cmds.slice(1)];\n}\n\n/** The paint a path node wears, resolved: what an action that turns ink into\n * geometry reads from a node and tells a consumer to put on a result. */\nexport interface PathPaint {\n /** `null` is no fill. */\n fill: Fill | null;\n /** Absent for no stroke. */\n stroke?: Stroke;\n}\n\n/** {@link PathPaint} of a node that keeps its paint in `data.fill` and\n * `data.stroke`, as the built-in path painter draws it: a stroke or a fill\n * with no `paint` is none, and an `'inherit'` one is `inherited`'s. */\nexport function pathPaintOf(\n data: { fill?: Fill | Inherit | null; stroke?: Stroke | Inherit | null },\n inherited?: InheritedPaint,\n): PathPaint {\n const stroke = resolveStroke(data.stroke, inherited?.stroke);\n const fill = resolveFill(data.fill, inherited?.fill);\n return stroke !== null ? { fill, stroke } : { fill };\n}\n"],"mappings":";;;;;;;AAaA,sBAAsB,UAAU,sBAAsB;;AAGtD,IAAa,gBAAmC,OAAO,OAAO;CAAE,QAAQ;CAAO,QAAQ;CAAG,OAAO;AAAE,CAAC;;;;AAKpG,SAAgB,SACd,QACA,OAC4D;CAC5D,IAAI,WAAW,MAAM,OAAO;EAAE,QAAQ;EAAG,OAAO;CAAE;CAGlD,MAAM,QAAQ,YAAY,MAAM;CAChC,MAAM,SAAS,OAAe,cAAsB;EAClD,QAAQ,OAAO,SAAS,UAAxB;GACE,KAAK,SAAS,OAAO;IAAE,QAAQ;IAAW,OAAO;GAAM;GACvD,KAAK,SAAS,OAAO;IAAE,QAAQ,QAAQ;IAAW,OAAO;GAAE;GAC3D,SAAS,OAAO;IAAE,QAAQ,QAAQ,IAAI;IAAW,OAAO,QAAQ;GAAE;EACpE;CACF;CACA,MAAM,QAAQ,OAAO,SAAS;CAG9B,MAAM,QAAQ,MADK,kBAAkB,OAAO,UAAU,WAAW,IAAI,mBAAmB,OAAO,SAAS,CAAC,GAAG,OAAO,YAC/F,GAAY,MAAM,KAAK;CAC3C,IAAI,OAAO,UAAU,YAAY,EAAE,MAAM,KAAK,IAAI,OAAO;CACzD,MAAM,KAAK,MAAM,OAAO,UAAU,WAAW,MAAM,KAAK,GAAG,MAAM,EAAE;CACnE,OAAO;EAAE,QAAQ,MAAM;EAAQ,OAAO,MAAM;EAAO,UAAU,GAAG;EAAQ,SAAS,GAAG;CAAM;AAC5F;;;AAIA,SAAgB,aAAa,QAAuB,OAAuB;CACzE,MAAM,IAAI,SAAS,QAAQ,KAAK;CAChC,OAAO,EAAE,UAAU,EAAE,YAAY,MAAM,QAAQ,IAAI,QAAQ;AAC7D;;;;;AAMA,SAAgB,oBAAoB,QAAuB,KAAuD;CAChH,MAAM,QAAQ,KAAK,SAAS;CAC5B,MAAM,SAAS,KAAK;CACpB,IAAI,CAAC,QAAQ;EACX,MAAM,IAAI,aAAa,QAAQ,KAAK;EACpC,OAAO;GAAE,GAAG;GAAG,GAAG;EAAE;CACtB;CACA,MAAM,IAAI,SAAS,QAAQ,KAAK;CAChC,MAAM,KAAK,EAAE,YAAY;CACzB,IAAI,OAAO,GAAG,OAAO;EAAE,GAAG,EAAE;EAAQ,GAAG,EAAE;CAAO;CAChD,MAAM,CAAC,GAAG,GAAG,GAAG,KAAK;CACrB,MAAM,MAAM,KAAK,IAAI,IAAI,IAAI,IAAI,CAAC;CAClC,IAAI,EAAE,MAAM,IAAI,OAAO;EAAE,GAAG,EAAE;EAAQ,GAAG,EAAE;CAAO;CAClD,OAAO;EAAE,GAAG,EAAE,SAAU,KAAK,KAAK,MAAM,GAAG,CAAC,IAAK;EAAK,GAAG,EAAE,SAAU,KAAK,KAAK,MAAM,GAAG,CAAC,IAAK;CAAI;AACpG;;;;;;AAOA,SAAgB,yBAAyB,MAAqB,KAA8C;CAC1G,MAAM,UAAU,KAAK;CACrB,MAAM,OAAO,KAAK;CAClB,IAAI,YAAY,UAAa,MAAM,SAAS,QAAQ,OAAO;CAC3D,MAAM,WAAW,IAAI,iBAAiB,KAAK,IAAI;CAC/C,MAAM,OAAO,KAAK,OAAO,QAAQ,QAAQ,KAAK,YAAY,IAAI;CAC9D,MAAM,SAAS,KAAK,SAAS,QAAQ,UAAU,KAAK,OAAO,YAAY,IAAI;CAC3E,MAAM,cAAc,MAAM,WAAW,YAAY,SAAS,KAAK;CAC/D,MAAM,gBAAgB,QAAQ,WAAW,YAAY,WAAW,KAAK,QAAQ;CAC7E,IAAI,CAAC,eAAe,CAAC,eAAe,OAAO;CAM3C,OAAO,CAAC;EAJN,GAAG;EACH,GAAI,cAAc,EAAE,cAAc,KAAiB,IAAI,CAAC;EACxD,GAAI,gBAAgB,EAAE,QAAQ;GAAE,GAAG,KAAK;GAAS,cAAc;EAAmB,EAAE,IAAI,CAAC;CAEnF,GAAM,GAAG,KAAK,MAAM,CAAC,CAAC;AAChC;;;;AAcA,SAAgB,YACd,MACA,WACW;CACX,MAAM,SAAS,cAAc,KAAK,QAAQ,WAAW,MAAM;CAC3D,MAAM,OAAO,YAAY,KAAK,MAAM,WAAW,IAAI;CACnD,OAAO,WAAW,OAAO;EAAE;EAAM;CAAO,IAAI,EAAE,KAAK;AACrD"}
1
+ {"version":3,"file":"nodePaint.js","names":[],"sources":["../../../src/canvas/nodeShapes/nodePaint.ts"],"sourcesContent":["import type { Stroke, Fill, Inherit } from '@weasel-js/paint';\nimport { resolveFill, resolveStroke } from '@weasel-js/paint';\nimport type { NodeInk, NodeInkCtx, NodePaintCtx } from 'canvas/NodeShape';\nimport { countPathAnchors } from '@weasel-js/geom';\nimport { markerReach } from '@weasel-js/stroke';\nimport { resolveStrokeWidth, widestStrokeWidth } from '@weasel-js/stroke';\nimport type { DrawCommand, PathDrawCommand } from '@weasel-js/renderer';\nimport { paintDrawKindRegistry } from '@weasel-js/renderer';\nimport { bumpNodeMemoGeneration } from 'core/scene/nodeMemo';\nimport type { InheritedPaint } from 'core/scene/inheritedPaint';\n\n// The painters memoize a node's paint and resolve a fill's frame inside the\n// memo, so the kind set is ambient state that memo cannot see change.\npaintDrawKindRegistry.subscribe(bumpNodeMemoGeneration);\n\n/** Per-side grab reach for a resolved stroke: world units for what the\n * renderer builds in world, screen pixels for what it builds on screen — a\n * `{ px }` width, and each head by its own size's units. */\nexport function inkReach(\n stroke: Stroke | null,\n scale: number | undefined,\n): Pick<NodeInk, 'outset' | 'inset' | 'outsetPx' | 'insetPx'> {\n if (stroke === null) return { outset: 0, inset: 0 };\n // A marker paints past the path's own end, and the kit's rule is that\n // visible chrome is hittable — so the reach has to cover it.\n const heads = markerReach(stroke);\n const sides = (width: number, headReach: number) => {\n switch (stroke.align ?? 'center') {\n case 'inner': return { outset: headReach, inset: width };\n case 'outer': return { outset: width + headReach, inset: 0 };\n default: return { outset: width / 2 + headReach, inset: width / 2 };\n }\n };\n const width = stroke.width ?? 1;\n // Per-vertex widths are world units even on a `{ px }` stroke.\n const worldWidth = widestStrokeWidth(typeof width === 'object' ? 0 : resolveStrokeWidth(width, scale ?? 1), stroke.vertexWidths);\n const world = sides(worldWidth, heads.world);\n if (typeof width !== 'object' && !(heads.px > 0)) return world;\n const px = sides(typeof width === 'object' ? width.px : 0, heads.px);\n return { outset: world.outset, inset: world.inset, outsetPx: px.outset, insetPx: px.inset };\n}\n\n/** How far `stroke` reaches outside its outline on every side, world units at\n * `scale`: {@link inkReach}'s outset with its screen part resolved. */\nexport function strokeOutset(stroke: Stroke | null, scale: number): number {\n const r = inkReach(stroke, scale);\n return r.outset + (r.outsetPx ?? 0) / (scale > 0 ? scale : 1);\n}\n\n/** {@link strokeOutset} per axis of the frame `ctx.screen` maps from: `x` past\n * the left and right sides, `y` past the top and bottom. A `{ px }` reach\n * is a disk on screen, and its extent along each axis of that frame is the\n * support of the disk pulled back through the map. */\nexport function strokeOutsetPerAxis(stroke: Stroke | null, ctx: NodeInkCtx | undefined): { x: number; y: number } {\n const scale = ctx?.scale ?? 1;\n const screen = ctx?.screen;\n if (!screen) {\n const r = strokeOutset(stroke, scale);\n return { x: r, y: r };\n }\n const r = inkReach(stroke, scale);\n const px = r.outsetPx ?? 0;\n if (px === 0) return { x: r.outset, y: r.outset };\n const [a, b, c, d] = screen;\n const det = Math.abs(a * d - b * c);\n if (!(det > 0)) return { x: r.outset, y: r.outset };\n return { x: r.outset + (px * Math.hypot(c, d)) / det, y: r.outset + (px * Math.hypot(a, b)) / det };\n}\n\n/** Apply {@link NodePaintCtx.vertexColors} to a path painter's body — its first\n * command. Applied outside the paint memo, whose key\n * cannot see an override: `cmds` comes back as-is when nothing changes, and\n * otherwise as a copy, since a memoized list is not ours to edit. Colors whose\n * length does not match the path's anchors are dropped. */\nexport function withAnimatedVertexColors(cmds: DrawCommand[], ctx: NodePaintCtx | undefined): DrawCommand[] {\n const resolve = ctx?.vertexColors;\n const body = cmds[0];\n if (resolve === undefined || body?.kind !== 'path') return cmds;\n const expected = 4 * countPathAnchors(body.path);\n const fill = body.fill ? resolve('fill', body.vertexColors) : undefined;\n const stroke = body.stroke ? resolve('stroke', body.stroke.vertexColors) : undefined;\n const fillChanged = fill?.length === expected && fill !== body.vertexColors;\n const strokeChanged = stroke?.length === expected && stroke !== body.stroke?.vertexColors;\n if (!fillChanged && !strokeChanged) return cmds;\n const next: PathDrawCommand = {\n ...body,\n ...(fillChanged ? { vertexColors: fill as number[] } : {}),\n ...(strokeChanged ? { stroke: { ...body.stroke!, vertexColors: stroke as number[] } } : {}),\n };\n return [next, ...cmds.slice(1)];\n}\n\n/** The paint a path node wears, resolved: what an action that turns ink into\n * geometry reads from a node and tells a consumer to put on a result. */\nexport interface PathPaint {\n /** `null` is no fill. */\n fill: Fill | null;\n /** Absent for no stroke. */\n stroke?: Stroke;\n}\n\n/** {@link PathPaint} of a node that keeps its paint in `data.fill` and\n * `data.stroke`, as the built-in path painter draws it: a stroke or a fill\n * with no `paint` is none, and an `'inherit'` one is `inherited`'s. */\nexport function pathPaintOf(\n data: { fill?: Fill | Inherit | null; stroke?: Stroke | Inherit | null },\n inherited?: InheritedPaint,\n): PathPaint {\n const stroke = resolveStroke(data.stroke, inherited?.stroke);\n const fill = resolveFill(data.fill, inherited?.fill);\n return stroke !== null ? { fill, stroke } : { fill };\n}\n"],"mappings":";;;;;;;AAaA,sBAAsB,UAAU,sBAAsB;;;;AAKtD,SAAgB,SACd,QACA,OAC4D;CAC5D,IAAI,WAAW,MAAM,OAAO;EAAE,QAAQ;EAAG,OAAO;CAAE;CAGlD,MAAM,QAAQ,YAAY,MAAM;CAChC,MAAM,SAAS,OAAe,cAAsB;EAClD,QAAQ,OAAO,SAAS,UAAxB;GACE,KAAK,SAAS,OAAO;IAAE,QAAQ;IAAW,OAAO;GAAM;GACvD,KAAK,SAAS,OAAO;IAAE,QAAQ,QAAQ;IAAW,OAAO;GAAE;GAC3D,SAAS,OAAO;IAAE,QAAQ,QAAQ,IAAI;IAAW,OAAO,QAAQ;GAAE;EACpE;CACF;CACA,MAAM,QAAQ,OAAO,SAAS;CAG9B,MAAM,QAAQ,MADK,kBAAkB,OAAO,UAAU,WAAW,IAAI,mBAAmB,OAAO,SAAS,CAAC,GAAG,OAAO,YAC/F,GAAY,MAAM,KAAK;CAC3C,IAAI,OAAO,UAAU,YAAY,EAAE,MAAM,KAAK,IAAI,OAAO;CACzD,MAAM,KAAK,MAAM,OAAO,UAAU,WAAW,MAAM,KAAK,GAAG,MAAM,EAAE;CACnE,OAAO;EAAE,QAAQ,MAAM;EAAQ,OAAO,MAAM;EAAO,UAAU,GAAG;EAAQ,SAAS,GAAG;CAAM;AAC5F;;;AAIA,SAAgB,aAAa,QAAuB,OAAuB;CACzE,MAAM,IAAI,SAAS,QAAQ,KAAK;CAChC,OAAO,EAAE,UAAU,EAAE,YAAY,MAAM,QAAQ,IAAI,QAAQ;AAC7D;;;;;AAMA,SAAgB,oBAAoB,QAAuB,KAAuD;CAChH,MAAM,QAAQ,KAAK,SAAS;CAC5B,MAAM,SAAS,KAAK;CACpB,IAAI,CAAC,QAAQ;EACX,MAAM,IAAI,aAAa,QAAQ,KAAK;EACpC,OAAO;GAAE,GAAG;GAAG,GAAG;EAAE;CACtB;CACA,MAAM,IAAI,SAAS,QAAQ,KAAK;CAChC,MAAM,KAAK,EAAE,YAAY;CACzB,IAAI,OAAO,GAAG,OAAO;EAAE,GAAG,EAAE;EAAQ,GAAG,EAAE;CAAO;CAChD,MAAM,CAAC,GAAG,GAAG,GAAG,KAAK;CACrB,MAAM,MAAM,KAAK,IAAI,IAAI,IAAI,IAAI,CAAC;CAClC,IAAI,EAAE,MAAM,IAAI,OAAO;EAAE,GAAG,EAAE;EAAQ,GAAG,EAAE;CAAO;CAClD,OAAO;EAAE,GAAG,EAAE,SAAU,KAAK,KAAK,MAAM,GAAG,CAAC,IAAK;EAAK,GAAG,EAAE,SAAU,KAAK,KAAK,MAAM,GAAG,CAAC,IAAK;CAAI;AACpG;;;;;;AAOA,SAAgB,yBAAyB,MAAqB,KAA8C;CAC1G,MAAM,UAAU,KAAK;CACrB,MAAM,OAAO,KAAK;CAClB,IAAI,YAAY,UAAa,MAAM,SAAS,QAAQ,OAAO;CAC3D,MAAM,WAAW,IAAI,iBAAiB,KAAK,IAAI;CAC/C,MAAM,OAAO,KAAK,OAAO,QAAQ,QAAQ,KAAK,YAAY,IAAI;CAC9D,MAAM,SAAS,KAAK,SAAS,QAAQ,UAAU,KAAK,OAAO,YAAY,IAAI;CAC3E,MAAM,cAAc,MAAM,WAAW,YAAY,SAAS,KAAK;CAC/D,MAAM,gBAAgB,QAAQ,WAAW,YAAY,WAAW,KAAK,QAAQ;CAC7E,IAAI,CAAC,eAAe,CAAC,eAAe,OAAO;CAM3C,OAAO,CAAC;EAJN,GAAG;EACH,GAAI,cAAc,EAAE,cAAc,KAAiB,IAAI,CAAC;EACxD,GAAI,gBAAgB,EAAE,QAAQ;GAAE,GAAG,KAAK;GAAS,cAAc;EAAmB,EAAE,IAAI,CAAC;CAEnF,GAAM,GAAG,KAAK,MAAM,CAAC,CAAC;AAChC;;;;AAcA,SAAgB,YACd,MACA,WACW;CACX,MAAM,SAAS,cAAc,KAAK,QAAQ,WAAW,MAAM;CAC3D,MAAM,OAAO,YAAY,KAAK,MAAM,WAAW,IAAI;CACnD,OAAO,WAAW,OAAO;EAAE;EAAM;CAAO,IAAI,EAAE,KAAK;AACrD"}
@@ -1,6 +1,6 @@
1
1
  import { nodeMemo } from "../../core/scene/nodeMemo.js";
2
2
  import { PAINT_SLOT } from "./memo.js";
3
- import { CONTAINER_INK, inkReach, pathPaintOf, withAnimatedVertexColors } from "./nodePaint.js";
3
+ import { inkReach, pathPaintOf, withAnimatedVertexColors } from "./nodePaint.js";
4
4
  import { strokedPoseBox } from "./strokedBounds.js";
5
5
  import { DERIVED_PAINTER, derivedDrawnBounds } from "./derivedPainter.js";
6
6
  import { paintsPose, poseBoxBounds } from "./poseBox.js";
@@ -15,7 +15,7 @@ var PATH_PAINTER = {
15
15
  if (!paintsPose(node.pose)) return false;
16
16
  return node.data?.path != null;
17
17
  },
18
- paint: (node, pose, ctx) => node.kind === "container" ? [] : ctx?.derivedPath != null ? DERIVED_PAINTER.paint(node, pose, ctx) : withAnimatedVertexColors(nodeMemo(node, PAINT_SLOT, pose, () => {
18
+ paint: (node, pose, ctx) => ctx?.derivedPath != null ? DERIVED_PAINTER.paint(node, pose, ctx) : withAnimatedVertexColors(nodeMemo(node, PAINT_SLOT, pose, () => {
19
19
  const d = node.data;
20
20
  const projected = pathInPoseFrame(d.path, pose);
21
21
  const { fill: declared, stroke: strokeSpec } = pathPaintOf(d, ctx?.inherited);
@@ -35,7 +35,6 @@ var PATH_PAINTER = {
35
35
  return pathInPoseFrame(d.path, pose);
36
36
  },
37
37
  ink: (node, _pose, ctx) => {
38
- if (node.kind === "container") return CONTAINER_INK;
39
38
  const { fill, stroke } = pathPaintOf(node.data, ctx?.inherited);
40
39
  return {
41
40
  filled: fill !== null,
@@ -1 +1 @@
1
- {"version":3,"file":"pathPainter.js","names":[],"sources":["../../../src/canvas/nodeShapes/pathPainter.ts"],"sourcesContent":["import type {\n 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 { pathInPoseFrame } from '@weasel-js/geom';\nimport { commandFill, commandStroke } from '@weasel-js/renderer';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport { DERIVED_PAINTER, derivedDrawnBounds } from './derivedPainter';\nimport { PAINT_SLOT } from './memo';\nimport { CONTAINER_INK, inkReach, pathPaintOf, withAnimatedVertexColors } from './nodePaint';\nimport { resolveStroke } from '@weasel-js/paint';\nimport { paintsPose, poseBoxBounds } from './poseBox';\nimport { strokedPoseBox } from './strokedBounds';\n\ntype PathData = { fill?: Fill | Inherit | null; stroke?: Stroke | Inherit | null; vertexColors?: number[] };\n\n// ─── Built-in painters ─────────────────────────────────────────────────\nexport const PATH_PAINTER: NodeShapeEntry<unknown, RectPose> = {\n id: 'kit:path',\n matches: (node) => {\n if (!paintsPose(node.pose)) return false;\n const d = node.data as { path?: Path } | null;\n return d?.path != null;\n },\n // Memoized: a pure function of `(data, pose)`, and `pathInPoseFrame`\n // allocates on every call but the identity (`kind: 'rect'`) branch. See\n // PAINT_SLOT.\n // As `kit:shape`, a container paints nothing of its own.\n paint: (node, pose, ctx) => node.kind === 'container' ? [] : ctx?.derivedPath != null\n // A pose override's path stands in for `data.path`, already where it is drawn.\n ? DERIVED_PAINTER.paint(node, pose, ctx)\n : withAnimatedVertexColors(nodeMemo(node, PAINT_SLOT, pose, () => {\n const d = node.data as PathData & { path: Path };\n const projected = pathInPoseFrame(d.path, pose);\n const { fill: declared, stroke: strokeSpec } = pathPaintOf(d, ctx?.inherited);\n // The pose is baked into `projected` rather than emitted as a transform,\n // so a box-relative gradient has to be baked onto the same box here or\n // it would arrive in the renderer referring to a frame that never exists.\n const fill = declared && commandFill(declared, pose);\n const stroke = strokeSpec && commandStroke(strokeSpec, pose);\n const cmd: DrawCommand = {\n kind: 'path',\n path: projected,\n ...(fill ? { fill } : {}),\n ...(fill && d.vertexColors ? { vertexColors: d.vertexColors } : {}),\n ...(stroke ? { stroke } : {}),\n };\n return [cmd];\n }, ctx?.inherited), ctx),\n silhouette: (node, pose, ctx) => {\n if (ctx?.derivedPath != null) return ctx.derivedPath;\n const d = node.data as { path: Path };\n return pathInPoseFrame(d.path, pose);\n },\n ink: (node, _pose, ctx) => {\n if (node.kind === 'container') return CONTAINER_INK;\n const { fill, stroke } = pathPaintOf(node.data as PathData, ctx?.inherited);\n return { filled: fill !== null, ...inkReach(stroke ?? null, ctx?.scale) };\n },\n // `pathInPoseFrame` fits the path's own bounds to the pose box.\n bounds: (node, pose, ctx) =>\n poseBoxBounds(pose, resolveStroke((node.data as PathData).stroke, ctx?.inherited?.stroke), ctx),\n drawnBounds: (node, pose, ctx) => {\n if (ctx?.derivedPath != null) return derivedDrawnBounds(node, ctx);\n const d = node.data as PathData & { path: Path };\n return strokedPoseBox(node, pose, resolveStroke(d.stroke, ctx?.inherited?.stroke), ctx, (at) => pathInPoseFrame(d.path, at));\n },\n};\n"],"mappings":";;;;;;;;;;;AAoBA,IAAa,eAAkD;CAC7D,IAAI;CACJ,UAAU,SAAS;EACjB,IAAI,CAAC,WAAW,KAAK,IAAI,GAAG,OAAO;EAEnC,OADU,KAAK,MACL,QAAQ;CACpB;CAKA,QAAQ,MAAM,MAAM,QAAQ,KAAK,SAAS,cAAc,CAAC,IAAI,KAAK,eAAe,OAE7E,gBAAgB,MAAM,MAAM,MAAM,GAAG,IACrC,yBAAyB,SAAS,MAAM,YAAY,YAAY;EAClE,MAAM,IAAI,KAAK;EACf,MAAM,YAAY,gBAAgB,EAAE,MAAM,IAAI;EAC9C,MAAM,EAAE,MAAM,UAAU,QAAQ,eAAe,YAAY,GAAG,KAAK,SAAS;EAI5E,MAAM,OAAO,YAAY,YAAY,UAAU,IAAI;EACnD,MAAM,SAAS,cAAc,cAAc,YAAY,IAAI;EAQ3D,OAAO,CAAC;GANN,MAAM;GACN,MAAM;GACN,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;GACvB,GAAI,QAAQ,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,IAAI,CAAC;GACjE,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAErB,CAAG;CACb,GAAG,KAAK,SAAS,GAAG,GAAG;CACvB,aAAa,MAAM,MAAM,QAAQ;EAC/B,IAAI,KAAK,eAAe,MAAM,OAAO,IAAI;EACzC,MAAM,IAAI,KAAK;EACf,OAAO,gBAAgB,EAAE,MAAM,IAAI;CACrC;CACA,MAAM,MAAM,OAAO,QAAQ;EACzB,IAAI,KAAK,SAAS,aAAa,OAAO;EACtC,MAAM,EAAE,MAAM,WAAW,YAAY,KAAK,MAAkB,KAAK,SAAS;EAC1E,OAAO;GAAE,QAAQ,SAAS;GAAM,GAAG,SAAS,UAAU,MAAM,KAAK,KAAK;EAAE;CAC1E;CAEA,SAAS,MAAM,MAAM,QACnB,cAAc,MAAM,cAAe,KAAK,KAAkB,QAAQ,KAAK,WAAW,MAAM,GAAG,GAAG;CAChG,cAAc,MAAM,MAAM,QAAQ;EAChC,IAAI,KAAK,eAAe,MAAM,OAAO,mBAAmB,MAAM,GAAG;EACjE,MAAM,IAAI,KAAK;EACf,OAAO,eAAe,MAAM,MAAM,cAAc,EAAE,QAAQ,KAAK,WAAW,MAAM,GAAG,MAAM,OAAO,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC7H;AACF"}
1
+ {"version":3,"file":"pathPainter.js","names":[],"sources":["../../../src/canvas/nodeShapes/pathPainter.ts"],"sourcesContent":["import type {\n 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 { pathInPoseFrame } from '@weasel-js/geom';\nimport { commandFill, commandStroke } from '@weasel-js/renderer';\nimport type { DrawCommand } from '@weasel-js/renderer';\nimport { DERIVED_PAINTER, derivedDrawnBounds } from './derivedPainter';\nimport { PAINT_SLOT } from './memo';\nimport { inkReach, pathPaintOf, withAnimatedVertexColors } from './nodePaint';\nimport { resolveStroke } from '@weasel-js/paint';\nimport { paintsPose, poseBoxBounds } from './poseBox';\nimport { strokedPoseBox } from './strokedBounds';\n\ntype PathData = { fill?: Fill | Inherit | null; stroke?: Stroke | Inherit | null; vertexColors?: number[] };\n\n// ─── Built-in painters ─────────────────────────────────────────────────\nexport const PATH_PAINTER: NodeShapeEntry<unknown, RectPose> = {\n id: 'kit:path',\n matches: (node) => {\n if (!paintsPose(node.pose)) return false;\n const d = node.data as { path?: Path } | null;\n return d?.path != null;\n },\n // Memoized: a pure function of `(data, pose)`, and `pathInPoseFrame`\n // allocates on every call but the identity (`kind: 'rect'`) branch. See\n // PAINT_SLOT.\n paint: (node, pose, ctx) => ctx?.derivedPath != null\n // A pose override's path stands in for `data.path`, already where it is drawn.\n ? DERIVED_PAINTER.paint(node, pose, ctx)\n : withAnimatedVertexColors(nodeMemo(node, PAINT_SLOT, pose, () => {\n const d = node.data as PathData & { path: Path };\n const projected = pathInPoseFrame(d.path, pose);\n const { fill: declared, stroke: strokeSpec } = pathPaintOf(d, ctx?.inherited);\n // The pose is baked into `projected` rather than emitted as a transform,\n // so a box-relative gradient has to be baked onto the same box here or\n // it would arrive in the renderer referring to a frame that never exists.\n const fill = declared && commandFill(declared, pose);\n const stroke = strokeSpec && commandStroke(strokeSpec, pose);\n const cmd: DrawCommand = {\n kind: 'path',\n path: projected,\n ...(fill ? { fill } : {}),\n ...(fill && d.vertexColors ? { vertexColors: d.vertexColors } : {}),\n ...(stroke ? { stroke } : {}),\n };\n return [cmd];\n }, ctx?.inherited), ctx),\n silhouette: (node, pose, ctx) => {\n if (ctx?.derivedPath != null) return ctx.derivedPath;\n const d = node.data as { path: Path };\n return pathInPoseFrame(d.path, pose);\n },\n ink: (node, _pose, ctx) => {\n const { fill, stroke } = pathPaintOf(node.data as PathData, ctx?.inherited);\n return { filled: fill !== null, ...inkReach(stroke ?? null, ctx?.scale) };\n },\n // `pathInPoseFrame` fits the path's own bounds to the pose box.\n bounds: (node, pose, ctx) =>\n poseBoxBounds(pose, resolveStroke((node.data as PathData).stroke, ctx?.inherited?.stroke), ctx),\n drawnBounds: (node, pose, ctx) => {\n if (ctx?.derivedPath != null) return derivedDrawnBounds(node, ctx);\n const d = node.data as PathData & { path: Path };\n return strokedPoseBox(node, pose, resolveStroke(d.stroke, ctx?.inherited?.stroke), ctx, (at) => pathInPoseFrame(d.path, at));\n },\n};\n"],"mappings":";;;;;;;;;;;AAoBA,IAAa,eAAkD;CAC7D,IAAI;CACJ,UAAU,SAAS;EACjB,IAAI,CAAC,WAAW,KAAK,IAAI,GAAG,OAAO;EAEnC,OADU,KAAK,MACL,QAAQ;CACpB;CAIA,QAAQ,MAAM,MAAM,QAAQ,KAAK,eAAe,OAE5C,gBAAgB,MAAM,MAAM,MAAM,GAAG,IACrC,yBAAyB,SAAS,MAAM,YAAY,YAAY;EAClE,MAAM,IAAI,KAAK;EACf,MAAM,YAAY,gBAAgB,EAAE,MAAM,IAAI;EAC9C,MAAM,EAAE,MAAM,UAAU,QAAQ,eAAe,YAAY,GAAG,KAAK,SAAS;EAI5E,MAAM,OAAO,YAAY,YAAY,UAAU,IAAI;EACnD,MAAM,SAAS,cAAc,cAAc,YAAY,IAAI;EAQ3D,OAAO,CAAC;GANN,MAAM;GACN,MAAM;GACN,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;GACvB,GAAI,QAAQ,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,IAAI,CAAC;GACjE,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAErB,CAAG;CACb,GAAG,KAAK,SAAS,GAAG,GAAG;CACvB,aAAa,MAAM,MAAM,QAAQ;EAC/B,IAAI,KAAK,eAAe,MAAM,OAAO,IAAI;EACzC,MAAM,IAAI,KAAK;EACf,OAAO,gBAAgB,EAAE,MAAM,IAAI;CACrC;CACA,MAAM,MAAM,OAAO,QAAQ;EACzB,MAAM,EAAE,MAAM,WAAW,YAAY,KAAK,MAAkB,KAAK,SAAS;EAC1E,OAAO;GAAE,QAAQ,SAAS;GAAM,GAAG,SAAS,UAAU,MAAM,KAAK,KAAK;EAAE;CAC1E;CAEA,SAAS,MAAM,MAAM,QACnB,cAAc,MAAM,cAAe,KAAK,KAAkB,QAAQ,KAAK,WAAW,MAAM,GAAG,GAAG;CAChG,cAAc,MAAM,MAAM,QAAQ;EAChC,IAAI,KAAK,eAAe,MAAM,OAAO,mBAAmB,MAAM,GAAG;EACjE,MAAM,IAAI,KAAK;EACf,OAAO,eAAe,MAAM,MAAM,cAAc,EAAE,QAAQ,KAAK,WAAW,MAAM,GAAG,MAAM,OAAO,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC7H;AACF"}
@@ -5,7 +5,7 @@ import { resolveFill } from "@weasel-js/paint";
5
5
  //#region src/canvas/nodeShapes/rectFallbackPainter.ts
6
6
  var RECT_FALLBACK_PAINTER = {
7
7
  id: "kit:rect-fallback",
8
- matches: (node) => node.kind !== "container" && paintsPose(node.pose),
8
+ matches: (node) => node.kind !== "group" && paintsPose(node.pose),
9
9
  paint: (node, pose, ctx) => {
10
10
  const d = node.data;
11
11
  const p = pose;
@@ -1 +1 @@
1
- {"version":3,"file":"rectFallbackPainter.js","names":[],"sources":["../../../src/canvas/nodeShapes/rectFallbackPainter.ts"],"sourcesContent":["import type { Fill, Inherit } from '@weasel-js/paint';\nimport type { NodeShapeEntry } from 'canvas/NodeShape';\nimport type { RectPose } from 'core/scene/types';\nimport { commandFill } from '@weasel-js/renderer';\nimport { resolveFill } from '@weasel-js/paint';\nimport { paintsPose, poseBoxBounds } from './poseBox';\n\n// ─── Built-in painters ─────────────────────────────────────────────────\nexport const RECT_FALLBACK_PAINTER: NodeShapeEntry<unknown, RectPose> = {\n // Last-resort painter — matches any rect-posed leaf, so it must be\n // registered last within `'normal'`. A container is a group, not a box: matching\n // one would also hand it a silhouette, which clips its members. Consumers who want a different\n // fallback should register their own painter at `'high'` priority and let\n // this one never fire (or unregister it explicitly).\n id: 'kit:rect-fallback',\n matches: (node) => node.kind !== 'container' && paintsPose(node.pose),\n paint: (node, pose, ctx) => {\n const d = node.data as { fill?: Fill | Inherit | null } | null;\n const p = pose;\n const declared = resolveFill(d?.fill, ctx?.inherited?.fill);\n const fill = declared && commandFill(declared, p);\n return [{\n kind: 'path',\n path: { kind: 'rect', x: p.x, y: p.y, width: p.width, height: p.height },\n ...(fill ? { fill } : {}),\n }];\n },\n silhouette: (_node, pose) => {\n const p = pose;\n return { kind: 'rect', x: p.x, y: p.y, width: p.width, height: p.height };\n },\n bounds: (_node, pose) => poseBoxBounds(pose, null, undefined),\n};\n"],"mappings":";;;;;AAQA,IAAa,wBAA2D;CAMtE,IAAI;CACJ,UAAU,SAAS,KAAK,SAAS,eAAe,WAAW,KAAK,IAAI;CACpE,QAAQ,MAAM,MAAM,QAAQ;EAC1B,MAAM,IAAI,KAAK;EACf,MAAM,IAAI;EACV,MAAM,WAAW,YAAY,GAAG,MAAM,KAAK,WAAW,IAAI;EAC1D,MAAM,OAAO,YAAY,YAAY,UAAU,CAAC;EAChD,OAAO,CAAC;GACN,MAAM;GACN,MAAM;IAAE,MAAM;IAAQ,GAAG,EAAE;IAAG,GAAG,EAAE;IAAG,OAAO,EAAE;IAAO,QAAQ,EAAE;GAAO;GACvE,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;EACzB,CAAC;CACH;CACA,aAAa,OAAO,SAAS;EAC3B,MAAM,IAAI;EACV,OAAO;GAAE,MAAM;GAAQ,GAAG,EAAE;GAAG,GAAG,EAAE;GAAG,OAAO,EAAE;GAAO,QAAQ,EAAE;EAAO;CAC1E;CACA,SAAS,OAAO,SAAS,cAAc,MAAM,MAAM,MAAS;AAC9D"}
1
+ {"version":3,"file":"rectFallbackPainter.js","names":[],"sources":["../../../src/canvas/nodeShapes/rectFallbackPainter.ts"],"sourcesContent":["import type { Fill, Inherit } from '@weasel-js/paint';\nimport type { NodeShapeEntry } from 'canvas/NodeShape';\nimport type { RectPose } from 'core/scene/types';\nimport { commandFill } from '@weasel-js/renderer';\nimport { resolveFill } from '@weasel-js/paint';\nimport { paintsPose, poseBoxBounds } from './poseBox';\n\n// ─── Built-in painters ─────────────────────────────────────────────────\nexport const RECT_FALLBACK_PAINTER: NodeShapeEntry<unknown, RectPose> = {\n // Last-resort painter — matches any rect-posed mark, so it must be\n // registered last within `'normal'`.\n // Consumers who want a different\n // fallback should register their own painter at `'high'` priority and let\n // this one never fire (or unregister it explicitly).\n id: 'kit:rect-fallback',\n matches: (node) => node.kind !== 'group' && paintsPose(node.pose),\n paint: (node, pose, ctx) => {\n const d = node.data as { fill?: Fill | Inherit | null } | null;\n const p = pose;\n const declared = resolveFill(d?.fill, ctx?.inherited?.fill);\n const fill = declared && commandFill(declared, p);\n return [{\n kind: 'path',\n path: { kind: 'rect', x: p.x, y: p.y, width: p.width, height: p.height },\n ...(fill ? { fill } : {}),\n }];\n },\n silhouette: (_node, pose) => {\n const p = pose;\n return { kind: 'rect', x: p.x, y: p.y, width: p.width, height: p.height };\n },\n bounds: (_node, pose) => poseBoxBounds(pose, null, undefined),\n};\n"],"mappings":";;;;;AAQA,IAAa,wBAA2D;CAMtE,IAAI;CACJ,UAAU,SAAS,KAAK,SAAS,WAAW,WAAW,KAAK,IAAI;CAChE,QAAQ,MAAM,MAAM,QAAQ;EAC1B,MAAM,IAAI,KAAK;EACf,MAAM,IAAI;EACV,MAAM,WAAW,YAAY,GAAG,MAAM,KAAK,WAAW,IAAI;EAC1D,MAAM,OAAO,YAAY,YAAY,UAAU,CAAC;EAChD,OAAO,CAAC;GACN,MAAM;GACN,MAAM;IAAE,MAAM;IAAQ,GAAG,EAAE;IAAG,GAAG,EAAE;IAAG,OAAO,EAAE;IAAO,QAAQ,EAAE;GAAO;GACvE,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;EACzB,CAAC;CACH;CACA,aAAa,OAAO,SAAS;EAC3B,MAAM,IAAI;EACV,OAAO;GAAE,MAAM;GAAQ,GAAG,EAAE;GAAG,GAAG,EAAE;GAAG,OAAO,EAAE;GAAO,QAAQ,EAAE;EAAO;CAC1E;CACA,SAAS,OAAO,SAAS,cAAc,MAAM,MAAM,MAAS;AAC9D"}
@@ -1,6 +1,6 @@
1
1
  import { nodeMemo } from "../../core/scene/nodeMemo.js";
2
2
  import { PAINT_SLOT } from "./memo.js";
3
- import { CONTAINER_INK, inkReach, withAnimatedVertexColors } from "./nodePaint.js";
3
+ import { inkReach, withAnimatedVertexColors } from "./nodePaint.js";
4
4
  import { outsetPoseBox, strokedPoseBox } from "./strokedBounds.js";
5
5
  import { paintsPose, poseBoxBounds } from "./poseBox.js";
6
6
  import { commandFill, commandStroke } from "@weasel-js/renderer";
@@ -19,7 +19,7 @@ var SHAPE_PAINTER = {
19
19
  const s = node.data?.shape;
20
20
  return s != null && SHAPE_KINDS.has(s);
21
21
  },
22
- paint: (node, pose, ctx) => node.kind === "container" ? [] : withAnimatedVertexColors(nodeMemo(node, PAINT_SLOT, pose, () => {
22
+ paint: (node, pose, ctx) => withAnimatedVertexColors(nodeMemo(node, PAINT_SLOT, pose, () => {
23
23
  const d = node.data;
24
24
  const path = pathForShape(d, pose);
25
25
  const declaredFill = resolveFill(d.fill, ctx?.inherited?.fill);
@@ -38,7 +38,6 @@ var SHAPE_PAINTER = {
38
38
  return pathForShape(d, pose);
39
39
  },
40
40
  ink: (node, _pose, ctx) => {
41
- if (node.kind === "container") return CONTAINER_INK;
42
41
  const d = node.data;
43
42
  return {
44
43
  filled: resolveFill(d.fill, ctx?.inherited?.fill) !== null,
@@ -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, 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
+ {"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 { 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 paint: (node, pose, ctx) => 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 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;CAIA,QAAQ,MAAM,MAAM,QAAQ,yBAAyB,SAAS,MAAM,YAAY,YAAY;EAC1F,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,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"}
@@ -0,0 +1,24 @@
1
+ import { findShapeSilhouette } from "./NodeShape.js";
2
+
3
+ //#region src/canvas/ownClip.ts
4
+ /**
5
+ * What a node declaring `clip: 'outline'` clips its children to, in world
6
+ * coordinates: the silhouette of whichever painter claims it, the rect
7
+ * fallback's box included, or `null` when none does. `derived` supplies a
8
+ * derived path when there is one.
9
+ */
10
+ function clipOutline(node, pose, derived) {
11
+ return findShapeSilhouette(node, pose, { derivedPath: derived?.() ?? null });
12
+ }
13
+ /** The clip a node imposes on its children, or null when it imposes none:
14
+ * its `clipFromPose` when it has one, else its {@link clipOutline} when it
15
+ * declares `clip: 'outline'`. */
16
+ function ownClipOf(node, pose, derived) {
17
+ if (typeof node.clipFromPose === "function") return node.clipFromPose(pose);
18
+ if (node.clip !== "outline") return null;
19
+ return clipOutline(node, pose, derived);
20
+ }
21
+
22
+ //#endregion
23
+ export { clipOutline, ownClipOf };
24
+ //# sourceMappingURL=ownClip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ownClip.js","names":[],"sources":["../../src/canvas/ownClip.ts"],"sourcesContent":["import type { Path } from '@weasel-js/geom';\nimport { findShapeSilhouette } from 'canvas/NodeShape';\nimport type { ClipFromPoseFn, Node, NodeClip } from 'core/scene/types';\n\n/** What {@link ownClipOf} reads of a node. */\nexport interface ClipBearing<TPose> {\n clipFromPose?: ClipFromPoseFn<TPose>;\n clip?: NodeClip;\n}\n\n/**\n * What a node declaring `clip: 'outline'` clips its children to, in world\n * coordinates: the silhouette of whichever painter claims it, the rect\n * fallback's box included, or `null` when none does. `derived` supplies a\n * derived path when there is one.\n */\nexport function clipOutline<TPose>(\n node: Node<unknown, string, TPose>,\n pose: TPose,\n derived?: () => Path | null | undefined,\n): Path | null {\n return findShapeSilhouette(node, pose, { derivedPath: derived?.() ?? null });\n}\n\n/** The clip a node imposes on its children, or null when it imposes none:\n * its `clipFromPose` when it has one, else its {@link clipOutline} when it\n * declares `clip: 'outline'`. */\nexport function ownClipOf<TPose>(\n node: ClipBearing<TPose>,\n pose: TPose,\n derived?: () => Path | null | undefined,\n): Path | null {\n if (typeof node.clipFromPose === 'function') return node.clipFromPose(pose);\n if (node.clip !== 'outline') return null;\n return clipOutline(node as unknown as Node<unknown, string, TPose>, pose, derived);\n}\n"],"mappings":";;;;;;;;;AAgBA,SAAgB,YACd,MACA,MACA,SACa;CACb,OAAO,oBAAoB,MAAM,MAAM,EAAE,aAAa,UAAU,KAAK,KAAK,CAAC;AAC7E;;;;AAKA,SAAgB,UACd,MACA,MACA,SACa;CACb,IAAI,OAAO,KAAK,iBAAiB,YAAY,OAAO,KAAK,aAAa,IAAI;CAC1E,IAAI,KAAK,SAAS,WAAW,OAAO;CACpC,OAAO,YAAY,MAAiD,MAAM,OAAO;AACnF"}
@@ -1 +1 @@
1
- {"version":3,"file":"paintCull.js","names":["viewToMat3","mat3"],"sources":["../../src/canvas/paintCull.ts"],"sourcesContent":["import type { View } from 'core/viewport/view';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { poseFrameOf } from '@weasel-js/geom';\nimport { boundsMissRect, type CullRect } from '@weasel-js/renderer';\nimport { mat3, viewToMat3 } from '@weasel-js/geom';\n\n/** Where a node's paint can reach, before its pose frame — the scene\n * slot's `paintBounds`. `null` means unknown, and the node is painted. */\nexport type PaintBoundsFn<TNode, TPose> = (node: TNode, pose: TPose, view: View) => Bounds | null;\n\n/**\n * A predicate true for the nodes whose paint cannot reach `rect` (screen\n * pixels) under `view`, by `paintBounds` taken through the pose frame\n * (`poseFrameOf`) `wrapNodeOutput` applies and then the view. A node `paintBounds` cannot\n * bound is never culled.\n */\nexport function paintMissesView<TNode, TPose>(\n paintBounds: PaintBoundsFn<TNode, TPose>,\n view: View,\n rect: CullRect,\n): (node: TNode, pose: TPose, layerView?: View) => boolean {\n // `layerView` is a parallax plane's own view; planes are few, so one slot\n // of memo covers a walk that meets them layer by layer.\n let last = view;\n let toScreen = viewToMat3(view);\n return (node, pose, layerView = view) => {\n if (layerView !== last) { last = layerView; toScreen = viewToMat3(layerView); }\n const b = paintBounds(node, pose, layerView);\n if (!b) return false;\n const frame = poseFrameOf(pose);\n const m = frame ? mat3.multiply(toScreen, mat3.fromAffine(frame)) : toScreen;\n return boundsMissRect(b, m, rect);\n };\n}\n"],"mappings":";;;;;;;;;;AAgBA,SAAgB,gBACd,aACA,MACA,MACyD;CAGzD,IAAI,OAAO;CACX,IAAI,WAAWA,aAAW,IAAI;CAC9B,QAAQ,MAAM,MAAM,YAAY,SAAS;EACvC,IAAI,cAAc,MAAM;GAAE,OAAO;GAAW,WAAWA,aAAW,SAAS;EAAG;EAC9E,MAAM,IAAI,YAAY,MAAM,MAAM,SAAS;EAC3C,IAAI,CAAC,GAAG,OAAO;EACf,MAAM,QAAQ,YAAY,IAAI;EAC9B,MAAM,IAAI,QAAQC,OAAK,SAAS,UAAUA,OAAK,WAAW,KAAK,CAAC,IAAI;EACpE,OAAO,eAAe,GAAG,GAAG,IAAI;CAClC;AACF"}
1
+ {"version":3,"file":"paintCull.js","names":["viewToMat3","mat3"],"sources":["../../src/canvas/paintCull.ts"],"sourcesContent":["import type { View } from 'core/viewport/view';\nimport type { Bounds } from 'core/viewport/fitViewToBounds';\nimport { poseFrameOf } from '@weasel-js/geom';\nimport { boundsMissRect, type CullRect } from '@weasel-js/renderer';\nimport { mat3, viewToMat3 } from '@weasel-js/geom';\nimport type { NodePaintCtx } from './nodeShapeTypes';\n\n/** Where a node's paint can reach, before its pose frame — the scene\n * slot's `paintBounds`. `null` means unknown, and the node is painted.\n * `ctx.inherited` is what its `'inherit'` fill and stroke take, as `drawOne` gets it. */\nexport type PaintBoundsFn<TNode, TPose> = (\n node: TNode, pose: TPose, view: View, ctx?: Pick<NodePaintCtx, 'inherited'>,\n) => Bounds | null;\n\n/**\n * A predicate true for the nodes whose paint cannot reach `rect` (screen\n * pixels) under `view`, by `paintBounds` taken through the pose frame\n * (`poseFrameOf`) `wrapNodeOutput` applies and then the view. A node `paintBounds` cannot\n * bound is never culled.\n */\nexport function paintMissesView<TNode, TPose>(\n paintBounds: PaintBoundsFn<TNode, TPose>,\n view: View,\n rect: CullRect,\n): (node: TNode, pose: TPose, layerView?: View) => boolean {\n // `layerView` is a parallax plane's own view; planes are few, so one slot\n // of memo covers a walk that meets them layer by layer.\n let last = view;\n let toScreen = viewToMat3(view);\n return (node, pose, layerView = view) => {\n if (layerView !== last) { last = layerView; toScreen = viewToMat3(layerView); }\n const b = paintBounds(node, pose, layerView);\n if (!b) return false;\n const frame = poseFrameOf(pose);\n const m = frame ? mat3.multiply(toScreen, mat3.fromAffine(frame)) : toScreen;\n return boundsMissRect(b, m, rect);\n };\n}\n"],"mappings":";;;;;;;;;;AAoBA,SAAgB,gBACd,aACA,MACA,MACyD;CAGzD,IAAI,OAAO;CACX,IAAI,WAAWA,aAAW,IAAI;CAC9B,QAAQ,MAAM,MAAM,YAAY,SAAS;EACvC,IAAI,cAAc,MAAM;GAAE,OAAO;GAAW,WAAWA,aAAW,SAAS;EAAG;EAC9E,MAAM,IAAI,YAAY,MAAM,MAAM,SAAS;EAC3C,IAAI,CAAC,GAAG,OAAO;EACf,MAAM,QAAQ,YAAY,IAAI;EAC9B,MAAM,IAAI,QAAQC,OAAK,SAAS,UAAUA,OAAK,WAAW,KAAK,CAAC,IAAI;EACpE,OAAO,eAAe,GAAG,GAAG,IAAI;CAClC;AACF"}
@@ -0,0 +1,86 @@
1
+ import { framedBounds, poseDescriptorForNode } from "../../core/geometry/poseDescriptor.js";
2
+ import { AUTO_POSE_DESCRIPTOR, isPathLike, isRectPose } from "../../core/geometry/autoPoseDescriptor.js";
3
+ import { findShapeInk } from "../NodeShape.js";
4
+ import { regionBoundsOf } from "../../core/geometry/regionHit.js";
5
+ import { applyToPoint, boundsOfPath, poseFrameOf } from "@weasel-js/geom";
6
+
7
+ //#region src/canvas/pickIndex/nodeBroadBox.ts
8
+ /**
9
+ * One node's box in the pick index: a world box that every query's `hits`
10
+ * accepts only inside of, before that query's own screen-pixel slop.
11
+ *
12
+ * The point query and the region query read a node differently, so the box is
13
+ * the union of both: the region's `regionBoundsOf` through the node's own
14
+ * descriptor, and the frame box the point query's `poseContainsRotated` tests,
15
+ * grown in the pose's own frame by the ink's `outset`. A derived node is read
16
+ * by its path alone, as both queries read it.
17
+ */
18
+ function grow(out, b, by) {
19
+ const x1 = b.x + b.width, y1 = b.y + b.height;
20
+ out[0] = Math.min(out[0], Math.min(b.x, x1) - by);
21
+ out[1] = Math.min(out[1], Math.min(b.y, y1) - by);
22
+ out[2] = Math.max(out[2], Math.max(b.x, x1) + by);
23
+ out[3] = Math.max(out[3], Math.max(b.y, y1) + by);
24
+ }
25
+ /** The local box `poseContainsRotated` tests, grown by `by` and carried through
26
+ * the pose's frame, as that function reads it. */
27
+ function framedBox(out, pose, d, by) {
28
+ const local = isPathLike(pose) ? boundsOfPath(pose) : d.getBounds(pose);
29
+ const framed = d === AUTO_POSE_DESCRIPTOR ? pose : framedBounds(pose, d);
30
+ const frame = poseFrameOf(framed);
31
+ if (!frame) {
32
+ grow(out, local, by);
33
+ return;
34
+ }
35
+ const x0 = local.x - by, y0 = local.y - by;
36
+ const x1 = local.x + local.width + by, y1 = local.y + local.height + by;
37
+ for (const [x, y] of [
38
+ [x0, y0],
39
+ [x1, y0],
40
+ [x1, y1],
41
+ [x0, y1]
42
+ ]) {
43
+ const [wx, wy] = applyToPoint(frame, x, y);
44
+ grow(out, {
45
+ x: wx,
46
+ y: wy,
47
+ width: 0,
48
+ height: 0
49
+ }, 0);
50
+ }
51
+ }
52
+ /** Float error in a frame's round trip must not put an accepted point just outside. */
53
+ function pad(b) {
54
+ const e = 1e-9 * (1 + Math.max(Math.abs(b[0]), Math.abs(b[1]), Math.abs(b[2]), Math.abs(b[3])));
55
+ b[0] -= e;
56
+ b[1] -= e;
57
+ b[2] += e;
58
+ b[3] += e;
59
+ }
60
+ var UNINHERITED = Object.freeze({ scale: 1 });
61
+ /** Writes the box into `out` and returns the ink's reach measured on screen,
62
+ * which no world box can hold. */
63
+ function nodeBroadBox(node, pose, derived, inherited, descriptor, out) {
64
+ const ink = findShapeInk(node, pose, inherited === void 0 ? UNINHERITED : {
65
+ scale: 1,
66
+ inherited
67
+ });
68
+ const outset = Math.max(0, ink?.outset ?? 0);
69
+ out[0] = out[1] = Infinity;
70
+ out[2] = out[3] = -Infinity;
71
+ if (derived) grow(out, boundsOfPath(derived), outset);
72
+ else {
73
+ const own = poseDescriptorForNode(descriptor, node);
74
+ if (descriptor === AUTO_POSE_DESCRIPTOR && own === descriptor && isRectPose(pose) && poseFrameOf(pose) === null) grow(out, pose, outset);
75
+ else {
76
+ grow(out, regionBoundsOf(node, pose, own), outset);
77
+ framedBox(out, pose, descriptor, outset);
78
+ }
79
+ }
80
+ pad(out);
81
+ return Math.max(0, ink?.outsetPx ?? 0);
82
+ }
83
+
84
+ //#endregion
85
+ export { nodeBroadBox };
86
+ //# sourceMappingURL=nodeBroadBox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodeBroadBox.js","names":[],"sources":["../../../src/canvas/pickIndex/nodeBroadBox.ts"],"sourcesContent":["/**\n * One node's box in the pick index: a world box that every query's `hits`\n * accepts only inside of, before that query's own screen-pixel slop.\n *\n * The point query and the region query read a node differently, so the box is\n * the union of both: the region's `regionBoundsOf` through the node's own\n * descriptor, and the frame box the point query's `poseContainsRotated` tests,\n * grown in the pose's own frame by the ink's `outset`. A derived node is read\n * by its path alone, as both queries read it.\n */\nimport { applyToPoint, boundsOfPath, poseFrameOf, type Path } from '@weasel-js/geom';\nimport { findShapeInk } from 'canvas/NodeShape';\nimport { AUTO_POSE_DESCRIPTOR, isPathLike, isRectPose } from 'core/geometry/autoPoseDescriptor';\nimport { framedBounds } from 'core/geometry/poseDescriptor';\nimport { regionBoundsOf } from 'core/geometry/regionHit';\nimport type { InheritedPaint } from 'core/scene/inheritedPaint';\nimport { poseDescriptorForNode, type PoseDescriptor } from 'interactions/actions/resize/geometry';\n\n/** `[minX, minY, maxX, maxY]`, the layout `createBvh(2)` takes. */\nexport type Box2 = [number, number, number, number];\n\ntype Rect = { x: number; y: number; width: number; height: number };\n\nfunction grow(out: Box2, b: Rect, by: number): void {\n const x1 = b.x + b.width, y1 = b.y + b.height;\n out[0] = Math.min(out[0], Math.min(b.x, x1) - by);\n out[1] = Math.min(out[1], Math.min(b.y, y1) - by);\n out[2] = Math.max(out[2], Math.max(b.x, x1) + by);\n out[3] = Math.max(out[3], Math.max(b.y, y1) + by);\n}\n\n/** The local box `poseContainsRotated` tests, grown by `by` and carried through\n * the pose's frame, as that function reads it. */\nfunction framedBox(out: Box2, pose: unknown, d: PoseDescriptor<unknown>, by: number): void {\n const local: Rect = isPathLike(pose) ? boundsOfPath(pose) : d.getBounds(pose);\n const framed = d === AUTO_POSE_DESCRIPTOR ? pose : framedBounds(pose, d);\n const frame = poseFrameOf(framed);\n if (!frame) {\n grow(out, local, by);\n return;\n }\n const x0 = local.x - by, y0 = local.y - by;\n const x1 = local.x + local.width + by, y1 = local.y + local.height + by;\n for (const [x, y] of [[x0, y0], [x1, y0], [x1, y1], [x0, y1]] as const) {\n const [wx, wy] = applyToPoint(frame, x, y);\n grow(out, { x: wx, y: wy, width: 0, height: 0 }, 0);\n }\n}\n\n/** Float error in a frame's round trip must not put an accepted point just outside. */\nfunction pad(b: Box2): void {\n const e = 1e-9 * (1 + Math.max(Math.abs(b[0]), Math.abs(b[1]), Math.abs(b[2]), Math.abs(b[3])));\n b[0] -= e; b[1] -= e; b[2] += e; b[3] += e;\n}\n\nconst UNINHERITED = Object.freeze({ scale: 1 });\n\n/** Writes the box into `out` and returns the ink's reach measured on screen,\n * which no world box can hold. */\nexport function nodeBroadBox(\n node: object,\n pose: unknown,\n derived: Path | null | undefined,\n inherited: InheritedPaint | undefined,\n descriptor: PoseDescriptor<unknown>,\n out: Box2,\n): number {\n const ink = findShapeInk(node as never, pose, inherited === undefined ? UNINHERITED : { scale: 1, inherited });\n const outset = Math.max(0, ink?.outset ?? 0);\n out[0] = out[1] = Infinity;\n out[2] = out[3] = -Infinity;\n if (derived) {\n grow(out, boundsOfPath(derived), outset);\n } else {\n const own = poseDescriptorForNode(descriptor, node);\n if (descriptor === AUTO_POSE_DESCRIPTOR && own === descriptor && isRectPose(pose) && poseFrameOf(pose) === null) {\n // Both reads come to the pose rect itself.\n grow(out, pose, outset);\n } else {\n grow(out, regionBoundsOf(node, pose, own), outset);\n framedBox(out, pose, descriptor, outset);\n }\n }\n pad(out);\n return Math.max(0, ink?.outsetPx ?? 0);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuBA,SAAS,KAAK,KAAW,GAAS,IAAkB;CAClD,MAAM,KAAK,EAAE,IAAI,EAAE,OAAO,KAAK,EAAE,IAAI,EAAE;CACvC,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE;CAChD,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE;CAChD,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE;CAChD,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE;AAClD;;;AAIA,SAAS,UAAU,KAAW,MAAe,GAA4B,IAAkB;CACzF,MAAM,QAAc,WAAW,IAAI,IAAI,aAAa,IAAI,IAAI,EAAE,UAAU,IAAI;CAC5E,MAAM,SAAS,MAAM,uBAAuB,OAAO,aAAa,MAAM,CAAC;CACvE,MAAM,QAAQ,YAAY,MAAM;CAChC,IAAI,CAAC,OAAO;EACV,KAAK,KAAK,OAAO,EAAE;EACnB;CACF;CACA,MAAM,KAAK,MAAM,IAAI,IAAI,KAAK,MAAM,IAAI;CACxC,MAAM,KAAK,MAAM,IAAI,MAAM,QAAQ,IAAI,KAAK,MAAM,IAAI,MAAM,SAAS;CACrE,KAAK,MAAM,CAAC,GAAG,MAAM;EAAC,CAAC,IAAI,EAAE;EAAG,CAAC,IAAI,EAAE;EAAG,CAAC,IAAI,EAAE;EAAG,CAAC,IAAI,EAAE;CAAC,GAAY;EACtE,MAAM,CAAC,IAAI,MAAM,aAAa,OAAO,GAAG,CAAC;EACzC,KAAK,KAAK;GAAE,GAAG;GAAI,GAAG;GAAI,OAAO;GAAG,QAAQ;EAAE,GAAG,CAAC;CACpD;AACF;;AAGA,SAAS,IAAI,GAAe;CAC1B,MAAM,IAAI,QAAQ,IAAI,KAAK,IAAI,KAAK,IAAI,EAAE,EAAE,GAAG,KAAK,IAAI,EAAE,EAAE,GAAG,KAAK,IAAI,EAAE,EAAE,GAAG,KAAK,IAAI,EAAE,EAAE,CAAC;CAC7F,EAAE,MAAM;CAAG,EAAE,MAAM;CAAG,EAAE,MAAM;CAAG,EAAE,MAAM;AAC3C;AAEA,IAAM,cAAc,OAAO,OAAO,EAAE,OAAO,EAAE,CAAC;;;AAI9C,SAAgB,aACd,MACA,MACA,SACA,WACA,YACA,KACQ;CACR,MAAM,MAAM,aAAa,MAAe,MAAM,cAAc,SAAY,cAAc;EAAE,OAAO;EAAG;CAAU,CAAC;CAC7G,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK,UAAU,CAAC;CAC3C,IAAI,KAAK,IAAI,KAAK;CAClB,IAAI,KAAK,IAAI,KAAK;CAClB,IAAI,SACF,KAAK,KAAK,aAAa,OAAO,GAAG,MAAM;MAClC;EACL,MAAM,MAAM,sBAAsB,YAAY,IAAI;EAClD,IAAI,eAAe,wBAAwB,QAAQ,cAAc,WAAW,IAAI,KAAK,YAAY,IAAI,MAAM,MAEzG,KAAK,KAAK,MAAM,MAAM;OACjB;GACL,KAAK,KAAK,eAAe,MAAM,MAAM,GAAG,GAAG,MAAM;GACjD,UAAU,KAAK,MAAM,YAAY,MAAM;EACzC;CACF;CACA,IAAI,GAAG;CACP,OAAO,KAAK,IAAI,GAAG,KAAK,YAAY,CAAC;AACvC"}