@react-x11/components 0.7.1 → 0.9.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 (354) hide show
  1. package/README.md +295 -51
  2. package/dist/charts/node.d.ts +1 -1
  3. package/dist/charts/node.d.ts.map +1 -1
  4. package/dist/charts/node.js +6 -2
  5. package/dist/charts/node.js.map +1 -1
  6. package/dist/color-picker/ColorPicker.d.ts.map +1 -1
  7. package/dist/color-picker/ColorPicker.js +43 -33
  8. package/dist/color-picker/ColorPicker.js.map +1 -1
  9. package/dist/embed/client.d.ts +48 -3
  10. package/dist/embed/client.d.ts.map +1 -1
  11. package/dist/embed/client.js +79 -5
  12. package/dist/embed/client.js.map +1 -1
  13. package/dist/embed/index.d.ts +1 -1
  14. package/dist/embed/index.d.ts.map +1 -1
  15. package/dist/embed/index.js +1 -1
  16. package/dist/embed/index.js.map +1 -1
  17. package/dist/html/node.d.ts +3 -1
  18. package/dist/html/node.d.ts.map +1 -1
  19. package/dist/html/node.js +7 -5
  20. package/dist/html/node.js.map +1 -1
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/{markdown → internal/markdown}/ast.d.ts +12 -0
  26. package/dist/internal/markdown/ast.d.ts.map +1 -0
  27. package/dist/internal/markdown/ast.js +22 -0
  28. package/dist/internal/markdown/ast.js.map +1 -0
  29. package/dist/internal/markdown/parse.d.ts.map +1 -0
  30. package/dist/{markdown → internal/markdown}/parse.js +112 -22
  31. package/dist/internal/markdown/parse.js.map +1 -0
  32. package/dist/internal/markdown/stringify.d.ts +4 -0
  33. package/dist/internal/markdown/stringify.d.ts.map +1 -0
  34. package/dist/internal/markdown/stringify.js +684 -0
  35. package/dist/internal/markdown/stringify.js.map +1 -0
  36. package/dist/internal/markdown/tags.d.ts.map +1 -0
  37. package/dist/internal/markdown/tags.js.map +1 -0
  38. package/dist/internal/scroll.d.ts +3 -2
  39. package/dist/internal/scroll.d.ts.map +1 -1
  40. package/dist/internal/scroll.js +33 -15
  41. package/dist/internal/scroll.js.map +1 -1
  42. package/dist/internal/units.d.ts +2 -0
  43. package/dist/internal/units.d.ts.map +1 -0
  44. package/dist/internal/units.js +35 -0
  45. package/dist/internal/units.js.map +1 -0
  46. package/dist/internal/window.d.ts.map +1 -1
  47. package/dist/internal/window.js +4 -2
  48. package/dist/internal/window.js.map +1 -1
  49. package/dist/maps/anchors.d.ts +98 -0
  50. package/dist/maps/anchors.d.ts.map +1 -0
  51. package/dist/maps/anchors.js +531 -0
  52. package/dist/maps/anchors.js.map +1 -0
  53. package/dist/maps/controller.d.ts +283 -0
  54. package/dist/maps/controller.d.ts.map +1 -0
  55. package/dist/maps/controller.js +743 -0
  56. package/dist/maps/controller.js.map +1 -0
  57. package/dist/maps/gl/buckets.d.ts +95 -0
  58. package/dist/maps/gl/buckets.d.ts.map +1 -0
  59. package/dist/maps/gl/buckets.js +362 -0
  60. package/dist/maps/gl/buckets.js.map +1 -0
  61. package/dist/maps/gl/build-worker.d.ts +13 -0
  62. package/dist/maps/gl/build-worker.d.ts.map +1 -0
  63. package/dist/maps/gl/build-worker.js +44 -0
  64. package/dist/maps/gl/build-worker.js.map +1 -0
  65. package/dist/maps/gl/color.d.ts +8 -0
  66. package/dist/maps/gl/color.d.ts.map +1 -0
  67. package/dist/maps/gl/color.js +57 -0
  68. package/dist/maps/gl/color.js.map +1 -0
  69. package/dist/maps/gl/cover.d.ts +51 -0
  70. package/dist/maps/gl/cover.d.ts.map +1 -0
  71. package/dist/maps/gl/cover.js +124 -0
  72. package/dist/maps/gl/cover.js.map +1 -0
  73. package/dist/maps/gl/index.d.ts +3 -0
  74. package/dist/maps/gl/index.d.ts.map +1 -0
  75. package/dist/maps/gl/index.js +20 -0
  76. package/dist/maps/gl/index.js.map +1 -0
  77. package/dist/maps/gl/markers.d.ts +28 -0
  78. package/dist/maps/gl/markers.d.ts.map +1 -0
  79. package/dist/maps/gl/markers.js +60 -0
  80. package/dist/maps/gl/markers.js.map +1 -0
  81. package/dist/maps/gl/overlays.d.ts +68 -0
  82. package/dist/maps/gl/overlays.d.ts.map +1 -0
  83. package/dist/maps/gl/overlays.js +300 -0
  84. package/dist/maps/gl/overlays.js.map +1 -0
  85. package/dist/maps/gl/pane.d.ts +12 -0
  86. package/dist/maps/gl/pane.d.ts.map +1 -0
  87. package/dist/maps/gl/pane.js +37 -0
  88. package/dist/maps/gl/pane.js.map +1 -0
  89. package/dist/maps/gl/placement.d.ts +111 -0
  90. package/dist/maps/gl/placement.d.ts.map +1 -0
  91. package/dist/maps/gl/placement.js +567 -0
  92. package/dist/maps/gl/placement.js.map +1 -0
  93. package/dist/maps/gl/renderer.d.ts +352 -0
  94. package/dist/maps/gl/renderer.d.ts.map +1 -0
  95. package/dist/maps/gl/renderer.js +1127 -0
  96. package/dist/maps/gl/renderer.js.map +1 -0
  97. package/dist/maps/gl/shaders.d.ts +120 -0
  98. package/dist/maps/gl/shaders.d.ts.map +1 -0
  99. package/dist/maps/gl/shaders.js +451 -0
  100. package/dist/maps/gl/shaders.js.map +1 -0
  101. package/dist/maps/gl/store.d.ts +115 -0
  102. package/dist/maps/gl/store.d.ts.map +1 -0
  103. package/dist/maps/gl/store.js +469 -0
  104. package/dist/maps/gl/store.js.map +1 -0
  105. package/dist/maps/gl/text.d.ts +154 -0
  106. package/dist/maps/gl/text.d.ts.map +1 -0
  107. package/dist/maps/gl/text.js +382 -0
  108. package/dist/maps/gl/text.js.map +1 -0
  109. package/dist/maps/gl/view.d.ts +54 -0
  110. package/dist/maps/gl/view.d.ts.map +1 -0
  111. package/dist/maps/gl/view.js +974 -0
  112. package/dist/maps/gl/view.js.map +1 -0
  113. package/dist/maps/index.d.ts +1 -1
  114. package/dist/maps/index.d.ts.map +1 -1
  115. package/dist/maps/index.js +216 -52
  116. package/dist/maps/index.js.map +1 -1
  117. package/dist/maps/labels.d.ts +37 -16
  118. package/dist/maps/labels.d.ts.map +1 -1
  119. package/dist/maps/labels.js +188 -119
  120. package/dist/maps/labels.js.map +1 -1
  121. package/dist/maps/node.d.ts +162 -56
  122. package/dist/maps/node.d.ts.map +1 -1
  123. package/dist/maps/node.js +607 -515
  124. package/dist/maps/node.js.map +1 -1
  125. package/dist/maps/overlay.d.ts +72 -9
  126. package/dist/maps/overlay.d.ts.map +1 -1
  127. package/dist/maps/overlay.js +108 -28
  128. package/dist/maps/overlay.js.map +1 -1
  129. package/dist/maps/paint.d.ts +3 -0
  130. package/dist/maps/paint.d.ts.map +1 -1
  131. package/dist/maps/paint.js.map +1 -1
  132. package/dist/maps/proj.d.ts +14 -0
  133. package/dist/maps/proj.d.ts.map +1 -1
  134. package/dist/maps/proj.js +28 -0
  135. package/dist/maps/proj.js.map +1 -1
  136. package/dist/maps/renderer.d.ts +69 -0
  137. package/dist/maps/renderer.d.ts.map +1 -0
  138. package/dist/maps/renderer.js +66 -0
  139. package/dist/maps/renderer.js.map +1 -0
  140. package/dist/maps/sources.d.ts +25 -3
  141. package/dist/maps/sources.d.ts.map +1 -1
  142. package/dist/maps/sources.js +17 -0
  143. package/dist/maps/sources.js.map +1 -1
  144. package/dist/maps/theme.d.ts +19 -0
  145. package/dist/maps/theme.d.ts.map +1 -0
  146. package/dist/maps/theme.js +44 -0
  147. package/dist/maps/theme.js.map +1 -0
  148. package/dist/maps/tiles.d.ts +117 -17
  149. package/dist/maps/tiles.d.ts.map +1 -1
  150. package/dist/maps/tiles.js +163 -30
  151. package/dist/maps/tiles.js.map +1 -1
  152. package/dist/maps/types.d.ts +188 -20
  153. package/dist/maps/types.d.ts.map +1 -1
  154. package/dist/markdown/expressions.d.ts +1 -1
  155. package/dist/markdown/expressions.d.ts.map +1 -1
  156. package/dist/markdown/expressions.js.map +1 -1
  157. package/dist/markdown/index.d.ts +7 -6
  158. package/dist/markdown/index.d.ts.map +1 -1
  159. package/dist/markdown/index.js +9 -7
  160. package/dist/markdown/index.js.map +1 -1
  161. package/dist/markdown/spans.d.ts +1 -1
  162. package/dist/markdown/spans.d.ts.map +1 -1
  163. package/dist/media-player/index.d.ts +11 -3
  164. package/dist/media-player/index.d.ts.map +1 -1
  165. package/dist/media-player/index.js +13 -2
  166. package/dist/media-player/index.js.map +1 -1
  167. package/dist/rich-text-editor/clipboard.d.ts +23 -0
  168. package/dist/rich-text-editor/clipboard.d.ts.map +1 -0
  169. package/dist/rich-text-editor/clipboard.js +307 -0
  170. package/dist/rich-text-editor/clipboard.js.map +1 -0
  171. package/dist/rich-text-editor/collab.d.ts +32 -0
  172. package/dist/rich-text-editor/collab.d.ts.map +1 -0
  173. package/dist/rich-text-editor/collab.js +55 -0
  174. package/dist/rich-text-editor/collab.js.map +1 -0
  175. package/dist/rich-text-editor/commands.d.ts +50 -0
  176. package/dist/rich-text-editor/commands.d.ts.map +1 -0
  177. package/dist/rich-text-editor/commands.js +376 -0
  178. package/dist/rich-text-editor/commands.js.map +1 -0
  179. package/dist/rich-text-editor/drag.d.ts +68 -0
  180. package/dist/rich-text-editor/drag.d.ts.map +1 -0
  181. package/dist/rich-text-editor/drag.js +148 -0
  182. package/dist/rich-text-editor/drag.js.map +1 -0
  183. package/dist/rich-text-editor/html.d.ts +145 -0
  184. package/dist/rich-text-editor/html.d.ts.map +1 -0
  185. package/dist/rich-text-editor/html.js +516 -0
  186. package/dist/rich-text-editor/html.js.map +1 -0
  187. package/dist/rich-text-editor/index.d.ts +263 -0
  188. package/dist/rich-text-editor/index.d.ts.map +1 -0
  189. package/dist/rich-text-editor/index.js +822 -0
  190. package/dist/rich-text-editor/index.js.map +1 -0
  191. package/dist/rich-text-editor/inline.d.ts +94 -0
  192. package/dist/rich-text-editor/inline.d.ts.map +1 -0
  193. package/dist/rich-text-editor/inline.js +252 -0
  194. package/dist/rich-text-editor/inline.js.map +1 -0
  195. package/dist/rich-text-editor/keymap.d.ts +71 -0
  196. package/dist/rich-text-editor/keymap.d.ts.map +1 -0
  197. package/dist/rich-text-editor/keymap.js +393 -0
  198. package/dist/rich-text-editor/keymap.js.map +1 -0
  199. package/dist/rich-text-editor/keys.d.ts +29 -0
  200. package/dist/rich-text-editor/keys.d.ts.map +1 -0
  201. package/dist/rich-text-editor/keys.js +93 -0
  202. package/dist/rich-text-editor/keys.js.map +1 -0
  203. package/dist/rich-text-editor/look.d.ts +71 -0
  204. package/dist/rich-text-editor/look.d.ts.map +1 -0
  205. package/dist/rich-text-editor/look.js +342 -0
  206. package/dist/rich-text-editor/look.js.map +1 -0
  207. package/dist/rich-text-editor/markdown.d.ts +18 -0
  208. package/dist/rich-text-editor/markdown.d.ts.map +1 -0
  209. package/dist/rich-text-editor/markdown.js +496 -0
  210. package/dist/rich-text-editor/markdown.js.map +1 -0
  211. package/dist/rich-text-editor/nodes.d.ts +135 -0
  212. package/dist/rich-text-editor/nodes.d.ts.map +1 -0
  213. package/dist/rich-text-editor/nodes.js +397 -0
  214. package/dist/rich-text-editor/nodes.js.map +1 -0
  215. package/dist/rich-text-editor/render.d.ts +68 -0
  216. package/dist/rich-text-editor/render.d.ts.map +1 -0
  217. package/dist/rich-text-editor/render.js +500 -0
  218. package/dist/rich-text-editor/render.js.map +1 -0
  219. package/dist/rich-text-editor/schema.d.ts +16 -0
  220. package/dist/rich-text-editor/schema.d.ts.map +1 -0
  221. package/dist/rich-text-editor/schema.js +386 -0
  222. package/dist/rich-text-editor/schema.js.map +1 -0
  223. package/dist/rich-text-editor/store.d.ts +11 -0
  224. package/dist/rich-text-editor/store.d.ts.map +1 -0
  225. package/dist/rich-text-editor/store.js +43 -0
  226. package/dist/rich-text-editor/store.js.map +1 -0
  227. package/dist/rich-text-editor/suggest.d.ts +127 -0
  228. package/dist/rich-text-editor/suggest.d.ts.map +1 -0
  229. package/dist/rich-text-editor/suggest.js +0 -0
  230. package/dist/rich-text-editor/suggest.js.map +1 -0
  231. package/dist/rich-text-editor/tables.d.ts +41 -0
  232. package/dist/rich-text-editor/tables.d.ts.map +1 -0
  233. package/dist/rich-text-editor/tables.js +204 -0
  234. package/dist/rich-text-editor/tables.js.map +1 -0
  235. package/dist/rich-text-editor/toolbar.d.ts +58 -0
  236. package/dist/rich-text-editor/toolbar.d.ts.map +1 -0
  237. package/dist/rich-text-editor/toolbar.js +548 -0
  238. package/dist/rich-text-editor/toolbar.js.map +1 -0
  239. package/dist/rich-text-editor/view.d.ts +368 -0
  240. package/dist/rich-text-editor/view.d.ts.map +1 -0
  241. package/dist/rich-text-editor/view.js +1968 -0
  242. package/dist/rich-text-editor/view.js.map +1 -0
  243. package/dist/rich-text-editor/virtual.d.ts +54 -0
  244. package/dist/rich-text-editor/virtual.d.ts.map +1 -0
  245. package/dist/rich-text-editor/virtual.js +262 -0
  246. package/dist/rich-text-editor/virtual.js.map +1 -0
  247. package/dist/richtext/node.d.ts +25 -2
  248. package/dist/richtext/node.d.ts.map +1 -1
  249. package/dist/richtext/node.js +46 -24
  250. package/dist/richtext/node.js.map +1 -1
  251. package/dist/table/index.d.ts.map +1 -1
  252. package/dist/table/index.js +14 -5
  253. package/dist/table/index.js.map +1 -1
  254. package/dist/terminal/backends.d.ts +4 -1
  255. package/dist/terminal/backends.d.ts.map +1 -1
  256. package/dist/terminal/backends.js.map +1 -1
  257. package/dist/terminal/index.d.ts +18 -7
  258. package/dist/terminal/index.d.ts.map +1 -1
  259. package/dist/terminal/index.js +28 -11
  260. package/dist/terminal/index.js.map +1 -1
  261. package/dist/terminal/vt/node.d.ts +56 -3
  262. package/dist/terminal/vt/node.d.ts.map +1 -1
  263. package/dist/terminal/vt/node.js +106 -26
  264. package/dist/terminal/vt/node.js.map +1 -1
  265. package/dist/terminal/vt/renderer.d.ts +4 -0
  266. package/dist/terminal/vt/renderer.d.ts.map +1 -1
  267. package/dist/terminal/vt/renderer.js +14 -5
  268. package/dist/terminal/vt/renderer.js.map +1 -1
  269. package/dist/tree/index.d.ts.map +1 -1
  270. package/dist/tree/index.js +6 -4
  271. package/dist/tree/index.js.map +1 -1
  272. package/package.json +23 -4
  273. package/src/charts/node.ts +6 -2
  274. package/src/color-picker/ColorPicker.ts +107 -81
  275. package/src/embed/client.ts +102 -8
  276. package/src/embed/index.ts +2 -0
  277. package/src/html/node.ts +7 -5
  278. package/src/index.ts +2 -0
  279. package/src/{markdown → internal/markdown}/ast.ts +24 -7
  280. package/src/{markdown → internal/markdown}/parse.ts +134 -24
  281. package/src/internal/markdown/stringify.ts +772 -0
  282. package/src/internal/scroll.ts +39 -18
  283. package/src/internal/units.ts +34 -0
  284. package/src/internal/window.ts +4 -2
  285. package/src/maps/anchors.ts +607 -0
  286. package/src/maps/controller.ts +999 -0
  287. package/src/maps/gl/buckets.ts +457 -0
  288. package/src/maps/gl/build-worker.ts +55 -0
  289. package/src/maps/gl/color.ts +62 -0
  290. package/src/maps/gl/cover.ts +170 -0
  291. package/src/maps/gl/index.ts +23 -0
  292. package/src/maps/gl/markers.ts +83 -0
  293. package/src/maps/gl/overlays.ts +379 -0
  294. package/src/maps/gl/pane.ts +42 -0
  295. package/src/maps/gl/placement.ts +816 -0
  296. package/src/maps/gl/renderer.ts +1576 -0
  297. package/src/maps/gl/shaders.ts +486 -0
  298. package/src/maps/gl/store.ts +566 -0
  299. package/src/maps/gl/text.ts +495 -0
  300. package/src/maps/gl/view.ts +1224 -0
  301. package/src/maps/index.ts +270 -81
  302. package/src/maps/labels.ts +214 -121
  303. package/src/maps/node.ts +763 -629
  304. package/src/maps/overlay.ts +148 -43
  305. package/src/maps/paint.ts +3 -0
  306. package/src/maps/proj.ts +31 -0
  307. package/src/maps/renderer.ts +155 -0
  308. package/src/maps/sources.ts +38 -3
  309. package/src/maps/theme.ts +51 -0
  310. package/src/maps/tiles.ts +191 -33
  311. package/src/maps/types.ts +188 -20
  312. package/src/markdown/expressions.ts +5 -1
  313. package/src/markdown/index.ts +14 -11
  314. package/src/markdown/spans.ts +1 -1
  315. package/src/media-player/index.ts +18 -4
  316. package/src/rich-text-editor/clipboard.ts +439 -0
  317. package/src/rich-text-editor/collab.ts +91 -0
  318. package/src/rich-text-editor/commands.ts +448 -0
  319. package/src/rich-text-editor/dom-globals.d.ts +23 -0
  320. package/src/rich-text-editor/drag.ts +211 -0
  321. package/src/rich-text-editor/html.ts +619 -0
  322. package/src/rich-text-editor/index.ts +1331 -0
  323. package/src/rich-text-editor/inline.ts +345 -0
  324. package/src/rich-text-editor/keymap.ts +510 -0
  325. package/src/rich-text-editor/keys.ts +119 -0
  326. package/src/rich-text-editor/look.ts +427 -0
  327. package/src/rich-text-editor/markdown.ts +653 -0
  328. package/src/rich-text-editor/nodes.ts +497 -0
  329. package/src/rich-text-editor/render.ts +800 -0
  330. package/src/rich-text-editor/schema.ts +431 -0
  331. package/src/rich-text-editor/store.ts +39 -0
  332. package/src/rich-text-editor/suggest.ts +597 -0
  333. package/src/rich-text-editor/tables.ts +272 -0
  334. package/src/rich-text-editor/toolbar.ts +724 -0
  335. package/src/rich-text-editor/view.ts +2287 -0
  336. package/src/rich-text-editor/virtual.ts +347 -0
  337. package/src/richtext/node.ts +59 -30
  338. package/src/table/index.ts +14 -5
  339. package/src/terminal/backends.ts +4 -1
  340. package/src/terminal/index.ts +43 -15
  341. package/src/terminal/vt/node.ts +128 -29
  342. package/src/terminal/vt/renderer.ts +27 -5
  343. package/src/tree/index.ts +6 -4
  344. package/dist/markdown/ast.d.ts.map +0 -1
  345. package/dist/markdown/ast.js +0 -17
  346. package/dist/markdown/ast.js.map +0 -1
  347. package/dist/markdown/parse.d.ts.map +0 -1
  348. package/dist/markdown/parse.js.map +0 -1
  349. package/dist/markdown/tags.d.ts.map +0 -1
  350. package/dist/markdown/tags.js.map +0 -1
  351. /package/dist/{markdown → internal/markdown}/parse.d.ts +0 -0
  352. /package/dist/{markdown → internal/markdown}/tags.d.ts +0 -0
  353. /package/dist/{markdown → internal/markdown}/tags.js +0 -0
  354. /package/src/{markdown → internal/markdown}/tags.ts +0 -0
@@ -0,0 +1,974 @@
1
+ // The GL renderer as `<Map>` mounts it: a pane (`./pane.ts`) with a
2
+ // `<glarea>` in it, and every frame drawn into the surface from vector tiles
3
+ // — the style, and over it the labels.
4
+ //
5
+ // **The camera is not this renderer's.** It is the map's controller's
6
+ // (`../controller.ts`), which the retained renderer answers to as well, so a
7
+ // fallback in the middle of a pan keeps the camera and the handle. What this
8
+ // file adds is what a camera move costs here: a frame. A pan step is two
9
+ // numbers on the controller and `requestFrame()`, with no render and no
10
+ // commit in the loop — which matters more here than for the retained
11
+ // renderer, because every frame here is a full redraw and the budget for one
12
+ // is the whole frame, not a strip.
13
+ //
14
+ // **Input reaches the map through the pane's handlers**, which a press, a
15
+ // drag and a wheel over the surface bubble up to on both backends — the
16
+ // pointer over a `<glarea>` is the tree's (react-x11#545) — and which end at
17
+ // the controller methods the retained renderer's element calls.
18
+ //
19
+ // **A failure is `<Map>`'s to handle.** A surface that will not come up, a
20
+ // connection with no direct GL, a context or a shader that will not build, a
21
+ // throw from a frame: each is reported once through `onFailure`, and `<Map>`
22
+ // moves an `'auto'` map to the retained renderer or tells a `'gl'` one
23
+ // through `onError`. Nothing here falls back on its own.
24
+ import React, { useEffect, useMemo } from 'react';
25
+ import { useTheme } from 'react-x11';
26
+ import { scaleOf } from '../../internal/units.js';
27
+ import { ATTRIBUTION_OPACITY, ATTRIBUTION_SIZE, attributionLayout, } from '../overlay.js';
28
+ import { prepareStyle } from '../paint.js';
29
+ import { DEFAULT_TILE_SIZE, project, transformFor, worldSize, } from '../proj.js';
30
+ import { attributionOf, pyramidOf } from '../sources.js';
31
+ import { defaultStyleFor, isDarkTheme, overlayPalette } from '../theme.js';
32
+ import { parseColor, premultiplied } from './color.js';
33
+ import { renderCover } from './cover.js';
34
+ import { MarkerBatcher } from './markers.js';
35
+ import { buildOverlayBucket, overlayRegion, regionHolds, regionPlacement, } from './overlays.js';
36
+ import { GL_PANE } from './pane.js';
37
+ import { LABEL_INSTANCE, LabelPlacer } from './placement.js';
38
+ import { GlMapRenderer } from './renderer.js';
39
+ import { GlTileStore } from './store.js';
40
+ import { LabelAtlas, SurfaceTextEngine } from './text.js';
41
+ const h = React.createElement;
42
+ /**
43
+ * Adaptive quality's rungs, cheapest last. Each leaves out more than the
44
+ * one before it, in the order that costs the picture least:
45
+ *
46
+ * 1. **The fill edge pass** — the half-pixel line that antialiases every
47
+ * polygon. A fifth of a dense frame, measured, and a staircase edge is
48
+ * invisible on a map in motion.
49
+ * 2. **The style's newest detail layers** — those whose `minZoom` is within
50
+ * `detail` levels of the zoom: buildings and service roads first, then
51
+ * sites, minor roads. The style already says which layers are details,
52
+ * by bringing them in last.
53
+ * 3. **A coarser tile level** — the parent level's tiles, drawn at this
54
+ * zoom: generalized geometry and fewer features, which is what a low-zoom
55
+ * frame (all land and water polygons) is made cheaper by.
56
+ */
57
+ export const QUALITY_LADDER = [
58
+ { edges: true, detail: 0, coarser: 0 },
59
+ { edges: false, detail: 0, coarser: 0 },
60
+ { edges: false, detail: 1, coarser: 0 },
61
+ { edges: false, detail: 2, coarser: 0 },
62
+ { edges: false, detail: 2, coarser: 1 },
63
+ { edges: false, detail: 3, coarser: 2 },
64
+ ];
65
+ /**
66
+ * The rung a moving frame is drawn at: the lowest predicted to fit the
67
+ * budget, and a rung up only once that one is predicted well inside it —
68
+ * so a frame that sits at the budget does not flicker between two rungs.
69
+ */
70
+ export function chooseQuality(current, predict, budgetMs) {
71
+ const top = QUALITY_LADDER.length - 1;
72
+ let rung = Math.max(0, Math.min(current, top));
73
+ while (rung < top && predict(rung) > budgetMs)
74
+ rung++;
75
+ while (rung > 0 && predict(rung - 1) <= budgetMs * 0.75)
76
+ rung--;
77
+ return rung;
78
+ }
79
+ /**
80
+ * Milliseconds per segment instance, learned from frames it measured. It
81
+ * starts at what this renderer measured on an M1 Pro (3.7 M instances in
82
+ * 8.5 ms) and moves toward each measured frame, so a slower GPU is priced
83
+ * right within a few frames of the first gesture.
84
+ */
85
+ class CostModel {
86
+ msPerInstance = 2.4e-6;
87
+ overheadMs = 0.4;
88
+ predict(instances) {
89
+ return this.overheadMs + instances * this.msPerInstance;
90
+ }
91
+ learn(instances, ms) {
92
+ // A near-empty frame is all overhead and says nothing about the slope.
93
+ if (instances < 50_000)
94
+ return;
95
+ const slope = Math.max(0, ms - this.overheadMs) / instances;
96
+ this.msPerInstance += 0.25 * (slope - this.msPerInstance);
97
+ }
98
+ }
99
+ /** How far outside the pane tiles are kept warm, in logical pixels — the
100
+ * retained renderer's ring, so an ordinary flick has its tiles. */
101
+ const COVER_PADDING = 256;
102
+ /** How long a fade waits for the new level's tiles before starting anyway. */
103
+ const FADE_WAIT_MS = 400;
104
+ const DEFAULT_BUDGET_MS = 12;
105
+ /** Milliseconds a frame may spend building tiles' buckets on this thread;
106
+ * a frame always builds at least one waiting tile. */
107
+ const BUILD_BUDGET_MS = 6;
108
+ /** Milliseconds a frame may spend measuring strings it has not set before
109
+ * — the one label cost that can be large (the first strings on X11 load
110
+ * fonts and rasterize glyphs in JS). Placement's remainder waits a frame. */
111
+ const TEXT_BUDGET_MOVING_MS = 1;
112
+ const TEXT_BUDGET_SETTLED_MS = 4;
113
+ /**
114
+ * How often a moving view is placed again, in milliseconds — every other
115
+ * frame or so. The labels shown follow the map every frame regardless; what
116
+ * waits a frame is a name coming into view, and what that buys is the frame
117
+ * that would have placed *and* measured *and* uploaded at once, which is
118
+ * what the late frames of a labelled pan were made of.
119
+ */
120
+ const PLACE_INTERVAL_MS = 25;
121
+ /** How long the zoom must hold still before labels not already shown are
122
+ * placed (see `PlacementFrame.admit`). Under the controller's settle
123
+ * window, so the frame that marks the camera settled is one that admits
124
+ * them. */
125
+ const ZOOM_QUIET_MS = 120;
126
+ /** Sources taken off the map whose tiles are kept for a switch back. */
127
+ const INACTIVE_STORES = 2;
128
+ const NO_RASTER = {
129
+ features: 0,
130
+ vertices: 0,
131
+ decimated: 0,
132
+ culled: 0,
133
+ batches: 0,
134
+ };
135
+ /** A theme colour that does not parse falls back to these. */
136
+ const BLACK = [0, 0, 0, 1];
137
+ const WHITE = [1, 1, 1, 1];
138
+ const INDIRECT = '@react-x11/components maps: this <glarea> draws through indirect GLX, ' +
139
+ 'which has no shaders — the GL renderer needs the direct backend, which ' +
140
+ "on X11 is createRoot({ glPolicy: 'auto' }) and a DRI3 or Apple-DRI server";
141
+ const globals = globalThis;
142
+ const now = () => globals.performance?.now() ?? Date.now();
143
+ const EMPTY_PANE = { x: 0, y: 0, width: 0, height: 0 };
144
+ const EMPTY_COVER = {
145
+ tiles: [],
146
+ missing: [],
147
+ level: 0,
148
+ own: 0,
149
+ ancestors: 0,
150
+ descendants: 0,
151
+ inView: [],
152
+ };
153
+ class GlMapDriver {
154
+ props;
155
+ theme = null;
156
+ _controller;
157
+ _pane = null;
158
+ _area = null;
159
+ _renderer = null;
160
+ _gl = null;
161
+ _failed = false;
162
+ /** A store per source object — the retained cache's rule: a source is its
163
+ * own cache, whatever its id. */
164
+ _stores = new Map();
165
+ /** Sources taken off the map, oldest first, whose tiles are kept. */
166
+ _inactive = [];
167
+ _style = null;
168
+ _prepared = null;
169
+ _committed = null;
170
+ _lastEnd = null;
171
+ _drawnZoom = null;
172
+ /** When the zoom last changed — which holds new labels back. */
173
+ _zoomedAt = -Infinity;
174
+ _rung = 0;
175
+ _cost = new CostModel();
176
+ _shown = null;
177
+ _fade = null;
178
+ _placer = new LabelPlacer();
179
+ _markers = new MarkerBatcher();
180
+ /** The overlays' bucket, and what it was built from. */
181
+ _overlay = null;
182
+ _atlas = null;
183
+ _atlasKey = '';
184
+ /** No labels — the app has no fonts, or placing them failed — and no
185
+ * asking again every frame. */
186
+ _noText = false;
187
+ /** The attribution's one instance. */
188
+ _attributionText = new Float32Array(LABEL_INSTANCE);
189
+ /** An attribution a frame could not draw yet — its string not measured,
190
+ * or its raster not in the texture — and the size it is set at. */
191
+ _attributionWanted = null;
192
+ /** When labels were last placed. */
193
+ _placedAt = -Infinity;
194
+ _pumpTimer = null;
195
+ _stats = null;
196
+ _announced = false;
197
+ _paneStyle = null;
198
+ constructor(controller, props) {
199
+ this._controller = controller;
200
+ this.props = props;
201
+ }
202
+ // --- the controller's view ----------------------------------------------------
203
+ /** The pane in the window's logical pixels: its box's, which is current
204
+ * before the first frame and on a surface that never draws at all. */
205
+ pane() {
206
+ const box = this._pane?.contentBox?.();
207
+ if (!box)
208
+ return EMPTY_PANE;
209
+ const s = scaleOf(this._pane);
210
+ return {
211
+ x: box.x / s,
212
+ y: box.y / s,
213
+ width: box.width / s,
214
+ height: box.height / s,
215
+ };
216
+ }
217
+ scale() {
218
+ return scaleOf(this._pane);
219
+ }
220
+ /** Any camera move is one frame — there is no blit to prefer. */
221
+ moved() {
222
+ this.request();
223
+ }
224
+ /** Still: the next frame draws everything adaptive quality left out. */
225
+ settled() {
226
+ this.request();
227
+ }
228
+ /** `refresh()`: the style read again, and every tile's buckets and label
229
+ * anchors built again from its bytes. */
230
+ refresh() {
231
+ this._style = null;
232
+ this._prepared = null;
233
+ for (const store of this._stores.values())
234
+ store.rebuild();
235
+ this._placer.reset();
236
+ this.request();
237
+ }
238
+ stats() {
239
+ return this._stats;
240
+ }
241
+ focus() {
242
+ this._pane?.focus?.();
243
+ }
244
+ request = () => {
245
+ this._area?.requestFrame?.();
246
+ };
247
+ attach() {
248
+ this._controller.attach(this);
249
+ }
250
+ paneRef = (node) => {
251
+ this._pane = node;
252
+ if (node)
253
+ this._controller.attach(this);
254
+ };
255
+ areaRef = (node) => {
256
+ this._area = node;
257
+ };
258
+ /** A commit landed: what it changed that a frame shows asks for one. */
259
+ committed() {
260
+ const map = this.props.map;
261
+ const before = this._committed;
262
+ this._committed = map;
263
+ if (!before)
264
+ return;
265
+ if (map.markers !== before.markers) {
266
+ this._pane?.notifyA11ySceneChanged?.();
267
+ }
268
+ if (map.camera !== before.camera ||
269
+ map.markers !== before.markers ||
270
+ map.overlays !== before.overlays ||
271
+ map.sources !== before.sources ||
272
+ map.mapStyle !== before.mapStyle ||
273
+ map.attribution !== before.attribution ||
274
+ map.antialias !== before.antialias ||
275
+ map.fillRule !== before.fillRule ||
276
+ map.levelFade !== before.levelFade ||
277
+ map.adaptive !== before.adaptive) {
278
+ this.request();
279
+ }
280
+ }
281
+ /** The style's background: what the pane shows under the surface, and
282
+ * what the surface is cleared to. */
283
+ background() {
284
+ const style = this.props.map.mapStyle ?? defaultStyleFor(isDarkTheme(this.theme));
285
+ return style.background ?? '#ffffff';
286
+ }
287
+ /** The pane's style, the same object for as long as nothing in it changes
288
+ * — a new one every render would be a damage claim every render. */
289
+ paneStyle(base, background) {
290
+ const cached = this._paneStyle;
291
+ if (cached && cached.base === base && cached.background === background) {
292
+ return cached.value;
293
+ }
294
+ const value = [base, { backgroundColor: background }];
295
+ this._paneStyle = { base, background, value };
296
+ return value;
297
+ }
298
+ // --- input --------------------------------------------------------------------
299
+ _local(ev) {
300
+ const pane = this.pane();
301
+ return { x: ev.x - pane.x, y: ev.y - pane.y };
302
+ }
303
+ _input(ev) {
304
+ const p = this._local(ev);
305
+ return {
306
+ x: p.x,
307
+ y: p.y,
308
+ button: ev.button,
309
+ detail: ev.detail,
310
+ shiftKey: ev.shiftKey,
311
+ ctrlKey: ev.ctrlKey,
312
+ altKey: ev.altKey,
313
+ metaKey: ev.metaKey,
314
+ };
315
+ }
316
+ /** The pane's handlers — the whole of the map's input, on both backends. */
317
+ handlers = {
318
+ onMouseDown: (ev) => {
319
+ if (this._controller.pointerDown(this._input(ev)) === 'pan') {
320
+ this._pane?.focus?.();
321
+ }
322
+ ev.capturePointer?.();
323
+ },
324
+ onMouseMove: (ev) => {
325
+ const input = this._input(ev);
326
+ if (this._controller.dragging)
327
+ this._controller.pointerDrag(input);
328
+ else
329
+ this._controller.pointerMove(input);
330
+ },
331
+ onMouseUp: (ev) => {
332
+ this._controller.pointerUp(this._input(ev));
333
+ },
334
+ onMouseLeave: () => this._controller.pointerLeave(),
335
+ onWheel: (ev) => {
336
+ if (this._controller.wheel(this._local(ev), ev.deltaY ?? 0, ev.smooth === true)) {
337
+ ev.preventDefault?.();
338
+ }
339
+ },
340
+ onKeyDown: (ev) => {
341
+ if (this._controller.keyDown(ev.keysym ?? 0, ev.shiftKey ?? false)) {
342
+ ev.preventDefault?.();
343
+ }
344
+ },
345
+ };
346
+ // --- failure ------------------------------------------------------------------
347
+ surfaceError = (error) => {
348
+ this._fail(error);
349
+ };
350
+ _fail(error) {
351
+ if (this._failed)
352
+ return;
353
+ this._failed = true;
354
+ try {
355
+ this._renderer?.dispose();
356
+ }
357
+ catch {
358
+ // A context that failed may not delete what it made either.
359
+ }
360
+ this._renderer = null;
361
+ this.props.onFailure(error instanceof Error ? error : new Error(String(error)));
362
+ }
363
+ // --- the frame ------------------------------------------------------------------
364
+ draw = (gl, info) => {
365
+ if (this._failed)
366
+ return;
367
+ try {
368
+ if (gl.backend !== 'direct') {
369
+ throw new Error(INDIRECT);
370
+ }
371
+ const controller = this._controller;
372
+ // A fit asked for before layout had a size lands in the first frame
373
+ // that has one, and this frame draws it.
374
+ controller.beginFrame();
375
+ controller.painting = true;
376
+ try {
377
+ this._frame(gl, info);
378
+ }
379
+ finally {
380
+ controller.painting = false;
381
+ }
382
+ }
383
+ catch (error) {
384
+ // Core hands an `onDraw` throw to `<glarea onError>` and nothing else;
385
+ // caught here, it is the map's failure, reported the one way.
386
+ this._fail(error);
387
+ }
388
+ };
389
+ _styleNow() {
390
+ const style = this.props.map.mapStyle ?? defaultStyleFor(isDarkTheme(this.theme));
391
+ if (style !== this._style || !this._prepared) {
392
+ this._style = style;
393
+ this._prepared = prepareStyle(style);
394
+ for (const store of this._stores.values()) {
395
+ store.setStyle(this._prepared);
396
+ }
397
+ }
398
+ return { style, prepared: this._prepared };
399
+ }
400
+ /** A store for every source on the map, in order; the stores of sources
401
+ * taken off it stop loading and keep their tiles, a couple of them. */
402
+ _sourcesNow(prepared) {
403
+ const map = this.props.map;
404
+ const sources = map.sources ?? [];
405
+ const out = [];
406
+ sources.forEach((source, index) => {
407
+ let store = this._stores.get(source);
408
+ if (!store) {
409
+ store = new GlTileStore({
410
+ source,
411
+ sourceId: source.id ?? `source-${index}`,
412
+ prepared,
413
+ onChange: this.request,
414
+ onError: (error, tile) => this.props.map.onTileError?.(error, tile),
415
+ workers: map.buildWorkers ?? 0,
416
+ });
417
+ this._stores.set(source, store);
418
+ }
419
+ out.push({ source, store });
420
+ });
421
+ this._inactive = this._inactive.filter((s) => !sources.includes(s));
422
+ for (const [source, store] of this._stores) {
423
+ if (sources.includes(source) || this._inactive.includes(source)) {
424
+ continue;
425
+ }
426
+ store.abortLoads();
427
+ this._inactive.push(source);
428
+ }
429
+ while (this._inactive.length > INACTIVE_STORES) {
430
+ const old = this._inactive.shift();
431
+ this._stores.get(old)?.dispose((data) => this._renderer?.release(data));
432
+ this._stores.delete(old);
433
+ }
434
+ return out;
435
+ }
436
+ _budget() {
437
+ const adaptive = this.props.map.adaptive;
438
+ if (!adaptive)
439
+ return null;
440
+ return typeof adaptive === 'object'
441
+ ? (adaptive.budgetMs ?? DEFAULT_BUDGET_MS)
442
+ : DEFAULT_BUDGET_MS;
443
+ }
444
+ /**
445
+ * The level fade: which level the frame is drawn from, and the one fading
446
+ * in over it.
447
+ *
448
+ * A new level is waited for — briefly, until every tile of it in view has
449
+ * answered — so the fade shows it arriving rather than a stand-in for it;
450
+ * then it fades in over the level it replaces, with an ease at both ends.
451
+ * A level more than one step away is not faded (the view it would fade
452
+ * from is sixteen times the tiles), and a change of target halfway
453
+ * through a fade takes the half-shown level as the one it fades from.
454
+ */
455
+ _planLevels(target, cover, at) {
456
+ const fadeMs = this.props.map.levelFade ?? 0;
457
+ const to = target.level;
458
+ const plain = { base: target, next: null, alpha: 0 };
459
+ if (fadeMs <= 0 || this._shown === null || Math.abs(to - this._shown) > 1) {
460
+ this._shown = to;
461
+ this._fade = null;
462
+ return plain;
463
+ }
464
+ if (to === this._shown) {
465
+ this._fade = null;
466
+ return plain;
467
+ }
468
+ let fade = this._fade;
469
+ if (!fade || fade.to !== to) {
470
+ if (fade && fade.start !== null && (at - fade.start) / fadeMs >= 0.5) {
471
+ this._shown = fade.to;
472
+ }
473
+ if (this._shown === to) {
474
+ this._fade = null;
475
+ return plain;
476
+ }
477
+ fade = this._fade = { to, start: null, waitSince: at };
478
+ }
479
+ const base = cover(this._shown);
480
+ if (fade.start === null) {
481
+ if (target.missing.length > 0 && at - fade.waitSince < FADE_WAIT_MS) {
482
+ return { base, next: null, alpha: 0 };
483
+ }
484
+ fade.start = at;
485
+ }
486
+ const p = (at - fade.start) / fadeMs;
487
+ if (p >= 1) {
488
+ this._shown = to;
489
+ this._fade = null;
490
+ return plain;
491
+ }
492
+ return { base, next: target, alpha: p * p * (3 - 2 * p) };
493
+ }
494
+ _frame(gl, info) {
495
+ const map = this.props.map;
496
+ const controller = this._controller;
497
+ const scale = scaleOf(info.node);
498
+ if (gl !== this._gl) {
499
+ // A new context — the surface was recreated — owns none of the old
500
+ // one's buffers, so everything is uploaded again from the buckets the
501
+ // stores still hold.
502
+ this._renderer = null;
503
+ this._gl = gl;
504
+ }
505
+ this._renderer ??= new GlMapRenderer(gl, {
506
+ antialias: map.antialias,
507
+ fillRule: map.fillRule,
508
+ });
509
+ const renderer = this._renderer;
510
+ renderer.antialias = map.antialias ?? true;
511
+ renderer.fillRule = map.fillRule ?? 'nonzero';
512
+ const { style, prepared } = this._styleNow();
513
+ const camera = controller.camera();
514
+ if (this._drawnZoom !== null && this._drawnZoom !== camera.zoom) {
515
+ this._zoomedAt = now();
516
+ }
517
+ this._drawnZoom = camera.zoom;
518
+ const pane = { width: info.width / scale, height: info.height / scale };
519
+ const at = now();
520
+ const moving = controller.moving;
521
+ const budget = this._budget();
522
+ // Every source's tiles: built as far as the budget goes, then asked
523
+ // for — the view, and the ring around it that keeps a pan's tiles warm.
524
+ const sources = this._sourcesNow(prepared);
525
+ const buildStart = now();
526
+ const deadline = buildStart + BUILD_BUDGET_MS;
527
+ let more = false;
528
+ for (const { store } of sources) {
529
+ store.tick();
530
+ if (store.pump(deadline))
531
+ more = true;
532
+ }
533
+ const buildMs = now() - buildStart;
534
+ const covers = sources.map(({ source, store }) => {
535
+ const pyramid = pyramidOf(source);
536
+ const target = renderCover(camera, pane, scale, pyramid, store, {
537
+ padding: COVER_PADDING,
538
+ });
539
+ store.want(target.missing);
540
+ return { source, store, pyramid, target };
541
+ });
542
+ // The level fade and adaptive quality are the first source's — the
543
+ // basemap's; a pyramid over it is drawn at its own level throughout.
544
+ const primary = covers[0] ?? null;
545
+ const coverAt = (level) => primary
546
+ ? renderCover(camera, pane, scale, primary.pyramid, primary.store, {
547
+ level,
548
+ })
549
+ : EMPTY_COVER;
550
+ const plan = primary
551
+ ? this._planLevels(primary.target, coverAt, at)
552
+ : { base: EMPTY_COVER, next: null, alpha: 0 };
553
+ if (primary && plan.base !== primary.target) {
554
+ primary.store.want(plan.base.missing);
555
+ }
556
+ const frameOf = (tiles, rung) => ({
557
+ width: info.width,
558
+ height: info.height,
559
+ zoom: camera.zoom,
560
+ scale,
561
+ style: prepared,
562
+ background: style.background,
563
+ sources: tiles,
564
+ detail: rung.detail,
565
+ edges: rung.edges ? undefined : false,
566
+ });
567
+ // The frame at a rung: the plan's levels with that rung's detail left
568
+ // out — and, when no fade is running, a coarser level for the base.
569
+ const build = (r) => {
570
+ const q = QUALITY_LADDER[r];
571
+ const base = [];
572
+ const arriving = [];
573
+ covers.forEach((c, i) => {
574
+ let own = i === 0 ? plan.base : c.target;
575
+ if (i === 0 && q.coarser > 0 && !plan.next) {
576
+ const coarser = Math.max(c.pyramid.minZoom, own.level - q.coarser);
577
+ if (coarser < own.level)
578
+ own = coverAt(coarser);
579
+ }
580
+ base.push(own.tiles);
581
+ arriving.push(i === 0 && plan.next ? plan.next.tiles : c.target.tiles);
582
+ });
583
+ return {
584
+ frame: frameOf(base, q),
585
+ fade: plan.next
586
+ ? { frame: frameOf(arriving, q), alpha: plan.alpha }
587
+ : null,
588
+ };
589
+ };
590
+ let rung = 0;
591
+ let predictedMs = 0;
592
+ if (budget !== null && moving && primary) {
593
+ // The coarser level is what the ladder's last rungs draw, so ask for
594
+ // it too — after the view's own tiles, which come first.
595
+ primary.store.want(coverAt(Math.max(primary.pyramid.minZoom, primary.target.level - 1))
596
+ .missing);
597
+ const predict = (r) => {
598
+ const built = build(r);
599
+ const work = renderer.estimate(built.frame) +
600
+ (built.fade ? renderer.estimate(built.fade.frame) : 0);
601
+ return this._cost.predict(work);
602
+ };
603
+ rung = chooseQuality(this._rung, predict, budget);
604
+ predictedMs = predict(rung);
605
+ }
606
+ this._rung = rung;
607
+ const { frame, fade } = build(rung);
608
+ // The text: the labels, and the attribution, set in the same atlas.
609
+ let labels = null;
610
+ let attribution = null;
611
+ let labelMs = 0;
612
+ if (!this._noText) {
613
+ const started = now();
614
+ try {
615
+ const atlas = this._atlasFor(info.node, style, scale);
616
+ if (atlas) {
617
+ atlas.beginFrame(now() + (moving ? TEXT_BUDGET_MOVING_MS : TEXT_BUDGET_SETTLED_MS));
618
+ // The attribution before the names: it is one string, measured
619
+ // once, and a frame whose budget went on names would leave it
620
+ // for a later one — for as long as names keep arriving.
621
+ attribution = this._attributionFor(atlas, frame);
622
+ labels = this._labelsFor(atlas, frame, fade, at, moving);
623
+ this._pumpSoon();
624
+ }
625
+ }
626
+ catch (error) {
627
+ // Labels are the map's to lose, not the frame's: a failure placing
628
+ // them turns them off, once and said, and the map draws on.
629
+ this._noText = true;
630
+ this._atlas?.dispose();
631
+ this._atlas = null;
632
+ if (globals.process?.env?.NODE_ENV !== 'production') {
633
+ globals.console?.warn(`@react-x11/components: <Map> stopped drawing labels: ${String(error)}`);
634
+ }
635
+ }
636
+ labelMs = now() - started;
637
+ }
638
+ // The overlays and the markers, laid out on the same world as the
639
+ // tiles under them.
640
+ const palette = overlayPalette(this.theme);
641
+ const overlays = this._overlaysFor(renderer, camera, pane, scale, palette, info);
642
+ const markers = map.markers?.length
643
+ ? this._markers.batch(map.markers, transformFor(camera, pane, DEFAULT_TILE_SIZE), pane, scale, palette)
644
+ : null;
645
+ // Adaptive quality prices moving frames with a rate it learns from
646
+ // frames it times — and it times only settled ones. Timing a frame means
647
+ // `glFinish`, which holds this thread until the GPU is done and, on the
648
+ // Cocoa backend, until the surface is: a moving frame cannot afford
649
+ // that, and what it reads is not the frame's cost either. Timed on every
650
+ // moving frame, the cheapest rungs read 16–52 ms of what is a few
651
+ // milliseconds of drawing, and the model learned from that put nearly
652
+ // every frame on the coarsest rung. A settled frame is drawn once and
653
+ // animates nothing, so it can afford two finishes: one to drain what
654
+ // came before it, and one to time it alone. It still errs slow — a lone
655
+ // frame after a pause meets a GPU that has clocked down — which errs on
656
+ // the side of frame rate.
657
+ const calibrate = budget !== null && !moving;
658
+ const finish = () => gl.finish?.();
659
+ if (calibrate)
660
+ finish();
661
+ const drawStart = now();
662
+ const stats = renderer.render(frame, {
663
+ fade,
664
+ labels,
665
+ overlays,
666
+ markers,
667
+ attribution,
668
+ });
669
+ let measuredMs = 0;
670
+ if (calibrate) {
671
+ finish();
672
+ measuredMs = now() - drawStart;
673
+ // An upload is not drawing, and would teach the model it was.
674
+ if (stats.uploads === 0)
675
+ this._cost.learn(stats.instances, measuredMs);
676
+ }
677
+ map.onAfterDraw?.(gl, { width: info.width, height: info.height });
678
+ for (const { store } of sources) {
679
+ store.evict((data) => renderer.release(data));
680
+ }
681
+ const end = now();
682
+ const interval = this._lastEnd === null ? 0 : end - this._lastEnd;
683
+ this._lastEnd = end;
684
+ let tiles = 0;
685
+ let ready = 0;
686
+ let fromAncestor = 0;
687
+ let fromDescendant = 0;
688
+ let errors = 0;
689
+ let building = 0;
690
+ let loading = 0;
691
+ for (const c of covers) {
692
+ tiles += c.target.inView.length;
693
+ ready += c.target.own;
694
+ fromAncestor += c.target.ancestors;
695
+ fromDescendant += c.target.descendants;
696
+ errors += c.store.failedAmong(c.target.inView);
697
+ building += c.store.building;
698
+ loading += c.store.loading;
699
+ }
700
+ const level = primary?.target.level ?? 0;
701
+ this._stats = {
702
+ renderer: 'gl',
703
+ // Nothing rasterized, no surfaces held, and every frame whole.
704
+ rasterMs: 0,
705
+ surfaceBytes: 0,
706
+ damage: null,
707
+ draw: NO_RASTER,
708
+ drawMs: stats.cpuMs + labelMs,
709
+ tiles,
710
+ ready,
711
+ fromAncestor,
712
+ fromDescendant,
713
+ pending: building,
714
+ restyling: false,
715
+ labels: stats.labels,
716
+ errors,
717
+ gl: {
718
+ interval,
719
+ cpuMs: stats.cpuMs,
720
+ buildMs,
721
+ building,
722
+ loading,
723
+ gpuBytes: renderer.gpuBytes,
724
+ instances: stats.instances,
725
+ drawCalls: stats.drawCalls,
726
+ uploads: stats.uploads,
727
+ uploadBytes: stats.uploadBytes,
728
+ offscreen: stats.offscreen,
729
+ level,
730
+ shownLevel: this._shown ?? level,
731
+ fade: stats.fade,
732
+ quality: rung,
733
+ predictedMs,
734
+ measuredMs,
735
+ moving,
736
+ labelCandidates: labels ? this._placer.stats.candidates : 0,
737
+ labelsPlaced: labels ? this._placer.stats.placed : 0,
738
+ labelMs,
739
+ textPending: this._atlas?.pending ?? false,
740
+ },
741
+ };
742
+ // A label still fading, strings this frame had no budget to measure, or
743
+ // rasters still to go into the texture want the next frame; a raster
744
+ // in flight asks for one itself when it lands.
745
+ const labelling = labels !== null &&
746
+ (this._placer.animating ||
747
+ this._atlas?.starved === true ||
748
+ this._atlas?.uploading === true);
749
+ // An attribution this frame could not draw may have gone into the
750
+ // texture during it — the upload happens as the labels draw, after the
751
+ // frame chose what to draw — and then it is the next frame's.
752
+ const wanted = this._attributionWanted;
753
+ const attributing = wanted !== null && this._atlas?.entry(wanted.text, wanted.size) != null;
754
+ if (more || this._fade || labelling || attributing)
755
+ this.request();
756
+ if (!this._announced) {
757
+ this._announced = true;
758
+ this._pane?.notifyA11ySceneChanged?.();
759
+ }
760
+ map.onFrame?.(this._stats);
761
+ }
762
+ /**
763
+ * The overlays' bucket, placed for this frame: the one there is while the
764
+ * overlays are the same array, the theme's accent the same colour and
765
+ * the view inside the bucket's region, and a new one around the view
766
+ * otherwise.
767
+ */
768
+ _overlaysFor(renderer, camera, pane, scale, palette, info) {
769
+ const overlays = this.props.map.overlays;
770
+ let current = this._overlay;
771
+ if (!overlays || overlays.length === 0) {
772
+ if (current)
773
+ renderer.release(current.bucket.data);
774
+ this._overlay = null;
775
+ return null;
776
+ }
777
+ if (!current ||
778
+ current.overlays !== overlays ||
779
+ current.accent !== palette.accent ||
780
+ !regionHolds(current.bucket.region, camera, pane, scale)) {
781
+ if (current)
782
+ renderer.release(current.bucket.data);
783
+ current = this._overlay = {
784
+ bucket: buildOverlayBucket(overlays, overlayRegion(camera, pane, scale), palette),
785
+ overlays,
786
+ accent: palette.accent,
787
+ };
788
+ }
789
+ const { bucket } = current;
790
+ return {
791
+ data: bucket.data,
792
+ passes: bucket.passes,
793
+ ...regionPlacement(bucket.region, camera, info.width, info.height, scale),
794
+ };
795
+ }
796
+ /**
797
+ * The atlas every string on the map is set in — the labels, and the
798
+ * attribution. Made on the first frame, from the app the surface belongs
799
+ * to, and again when the face or the scale changes; null, and not asked
800
+ * for again, when the app has no fonts.
801
+ */
802
+ _atlasFor(node, style, scale) {
803
+ const family = style.fontFamily ??
804
+ this._pane?.resolvedTextStyle?.().family ??
805
+ 'sans-serif';
806
+ const key = `${family}|${scale}`;
807
+ if (this._atlas && this._atlasKey === key)
808
+ return this._atlas;
809
+ this._atlas?.dispose();
810
+ this._atlas = null;
811
+ const engine = SurfaceTextEngine.forApp(node.app, family);
812
+ if (!engine) {
813
+ this._noText = true;
814
+ return null;
815
+ }
816
+ // Margin for a halo of two logical pixels, and the texel either side
817
+ // that keeps its samples inside it (see `quadInset`).
818
+ this._atlas = new LabelAtlas(engine, { pad: 2 * Math.ceil(2 * scale) + 2 });
819
+ this._atlas.onChange = this.request;
820
+ this._atlasKey = key;
821
+ this._placer.reset();
822
+ return this._atlas;
823
+ }
824
+ /**
825
+ * The frame's labels: placed over the tiles of the level being shown —
826
+ * the arriving one, once a fade is past halfway — and batched for the
827
+ * renderer.
828
+ */
829
+ _labelsFor(atlas, frame, fade, at, moving) {
830
+ const from = fade && fade.alpha >= 0.5 ? fade.frame : frame;
831
+ const centre = project(this._controller.camera().center);
832
+ const placement = {
833
+ width: frame.width,
834
+ height: frame.height,
835
+ scale: frame.scale,
836
+ zoom: frame.zoom,
837
+ style: frame.style,
838
+ tiles: from.sources.flat(),
839
+ centerX: centre.x,
840
+ centerY: centre.y,
841
+ // The projection's world, which the cover lays every source out on.
842
+ world: worldSize(frame.zoom, DEFAULT_TILE_SIZE) * frame.scale,
843
+ now: at,
844
+ admit: at - this._zoomedAt >= ZOOM_QUIET_MS,
845
+ moving,
846
+ };
847
+ if (!moving || at - this._placedAt >= PLACE_INTERVAL_MS) {
848
+ this._placer.place(placement, atlas);
849
+ this._placedAt = at;
850
+ }
851
+ return this._placer.batch(placement, atlas);
852
+ }
853
+ /**
854
+ * The attribution, as the retained renderer draws it: the same box on the
855
+ * same pixels (`attributionLayout`), the text in the labels' face, from
856
+ * the labels' atlas. Null while there is none to draw — `attribution=""`,
857
+ * or no source that names one — and until its raster is in the texture,
858
+ * so the box and its text arrive together.
859
+ */
860
+ _attributionFor(atlas, frame) {
861
+ this._attributionWanted = null;
862
+ const map = this.props.map;
863
+ const text = attributionOf(map.attribution, map.sources ?? []);
864
+ if (!text)
865
+ return null;
866
+ const s = frame.scale;
867
+ const size = ATTRIBUTION_SIZE * s;
868
+ const box = atlas.measure(text, size);
869
+ const entry = box ? atlas.entry(text, size) : null;
870
+ if (!box || !entry) {
871
+ this._attributionWanted = { text, size };
872
+ return null;
873
+ }
874
+ const at = attributionLayout({ width: box.width / s, height: box.height / s }, { x: 0, y: 0, width: frame.width / s, height: frame.height / s }, s);
875
+ const palette = overlayPalette(this.theme);
876
+ // The string sits `pad` inside its raster, and a level label's quad is
877
+ // centred on its anchor and set on whole pixels: this anchor puts the
878
+ // string's corner on the layout's.
879
+ const d = this._attributionText;
880
+ d[0] = at.x - atlas.pad + entry.width / 2;
881
+ d[1] = at.y - atlas.pad + entry.height / 2;
882
+ d[2] = 1;
883
+ d[3] = 0;
884
+ d[4] = entry.x;
885
+ d[5] = entry.y;
886
+ d[6] = entry.width;
887
+ d[7] = entry.height;
888
+ d.set(premultiplied(parseColor(palette.text) ?? BLACK), 8);
889
+ d.fill(0, 12, 17); // no halo: the box is what it reads against
890
+ d[17] = 1;
891
+ return {
892
+ box: at.box,
893
+ boxColor: premultiplied(parseColor(palette.background) ?? WHITE, ATTRIBUTION_OPACITY),
894
+ text: { atlas, instances: d, count: 1 },
895
+ };
896
+ }
897
+ /**
898
+ * Rasterize what placement asked for — after this frame rather than in
899
+ * it. Setting a batch of strings is milliseconds of this thread, and the
900
+ * gap between two frames is where that costs no frame anything.
901
+ */
902
+ _pumpSoon() {
903
+ if (this._pumpTimer !== null || !this._atlas?.pending)
904
+ return;
905
+ this._pumpTimer = globals.setTimeout(() => {
906
+ this._pumpTimer = null;
907
+ this._atlas?.pump();
908
+ }, 0);
909
+ }
910
+ /**
911
+ * Let go of everything GL: the stores, the atlas, the renderer — all of
912
+ * it made again, lazily, by the next frame if there is one. React's
913
+ * strict-mode double effect is the case where there is.
914
+ */
915
+ dispose() {
916
+ this._controller.detach(this);
917
+ if (this._pumpTimer !== null)
918
+ globals.clearTimeout(this._pumpTimer);
919
+ this._pumpTimer = null;
920
+ this._atlas?.dispose();
921
+ this._atlas = null;
922
+ this._atlasKey = '';
923
+ for (const store of this._stores.values()) {
924
+ store.dispose((data) => this._renderer?.release(data));
925
+ }
926
+ this._stores.clear();
927
+ this._inactive = [];
928
+ // Its buffers go with the renderer's.
929
+ this._overlay = null;
930
+ try {
931
+ this._renderer?.dispose();
932
+ }
933
+ catch {
934
+ // The surface may be gone already, and its context with it.
935
+ }
936
+ this._renderer = null;
937
+ this._gl = null;
938
+ }
939
+ }
940
+ /**
941
+ * The GL half of `<Map>`: a pane with a `<glarea>` in it, drawn every frame
942
+ * from vector tiles. Mounted by `<Map>` when it chooses GL; not exported
943
+ * from the package on its own.
944
+ */
945
+ export function GlMapPane(props) {
946
+ const theme = useTheme();
947
+ const driver = useMemo(() => new GlMapDriver(props.controller, props), [props.controller]);
948
+ driver.props = props;
949
+ driver.theme = theme;
950
+ useEffect(() => {
951
+ driver.attach();
952
+ return () => driver.dispose();
953
+ }, [driver]);
954
+ useEffect(() => driver.committed());
955
+ const background = driver.background();
956
+ return h(GL_PANE, {
957
+ ref: driver.paneRef,
958
+ mapController: props.controller,
959
+ style: driver.paneStyle(props.style, background),
960
+ role: props.role,
961
+ 'aria-label': props['aria-label'],
962
+ 'data-testname': props['data-testname'],
963
+ ...driver.handlers,
964
+ }, h('glarea', {
965
+ ref: driver.areaRef,
966
+ style: FILL,
967
+ clearColor: background,
968
+ frameLoop: 'demand',
969
+ onDraw: driver.draw,
970
+ onError: driver.surfaceError,
971
+ }, props.children));
972
+ }
973
+ const FILL = { flexGrow: 1 };
974
+ //# sourceMappingURL=view.js.map