@react-x11/components 0.3.0 → 0.5.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 (314) hide show
  1. package/README.md +131 -4
  2. package/dist/charts/components.d.ts +3 -1
  3. package/dist/charts/components.d.ts.map +1 -1
  4. package/dist/charts/components.js +8 -4
  5. package/dist/charts/components.js.map +1 -1
  6. package/dist/charts/node.d.ts +13 -6
  7. package/dist/charts/node.d.ts.map +1 -1
  8. package/dist/charts/node.js +75 -24
  9. package/dist/charts/node.js.map +1 -1
  10. package/dist/charts/render.d.ts +4 -0
  11. package/dist/charts/render.d.ts.map +1 -1
  12. package/dist/charts/render.js +11 -6
  13. package/dist/charts/render.js.map +1 -1
  14. package/dist/code-editor/node.d.ts +47 -16
  15. package/dist/code-editor/node.d.ts.map +1 -1
  16. package/dist/code-editor/node.js +121 -51
  17. package/dist/code-editor/node.js.map +1 -1
  18. package/dist/flow/draw.d.ts +19 -1
  19. package/dist/flow/draw.d.ts.map +1 -1
  20. package/dist/flow/draw.js +64 -35
  21. package/dist/flow/draw.js.map +1 -1
  22. package/dist/flow/index.d.ts.map +1 -1
  23. package/dist/flow/index.js +24 -8
  24. package/dist/flow/index.js.map +1 -1
  25. package/dist/flow/model.d.ts +4 -2
  26. package/dist/flow/model.d.ts.map +1 -1
  27. package/dist/flow/model.js +4 -2
  28. package/dist/flow/model.js.map +1 -1
  29. package/dist/flow/node.d.ts +40 -9
  30. package/dist/flow/node.d.ts.map +1 -1
  31. package/dist/flow/node.js +142 -53
  32. package/dist/flow/node.js.map +1 -1
  33. package/dist/flow/types.d.ts +46 -21
  34. package/dist/flow/types.d.ts.map +1 -1
  35. package/dist/formula/index.d.ts +4 -2
  36. package/dist/formula/index.d.ts.map +1 -1
  37. package/dist/formula/index.js.map +1 -1
  38. package/dist/formula/node.d.ts +5 -1
  39. package/dist/formula/node.d.ts.map +1 -1
  40. package/dist/formula/node.js +19 -3
  41. package/dist/formula/node.js.map +1 -1
  42. package/dist/html/controls.d.ts +4 -1
  43. package/dist/html/controls.d.ts.map +1 -1
  44. package/dist/html/controls.js.map +1 -1
  45. package/dist/html/css/cascade.d.ts +11 -5
  46. package/dist/html/css/cascade.d.ts.map +1 -1
  47. package/dist/html/css/cascade.js +18 -8
  48. package/dist/html/css/cascade.js.map +1 -1
  49. package/dist/html/css/parse.d.ts +15 -10
  50. package/dist/html/css/parse.d.ts.map +1 -1
  51. package/dist/html/css/parse.js +41 -16
  52. package/dist/html/css/parse.js.map +1 -1
  53. package/dist/html/css/style.d.ts +3 -0
  54. package/dist/html/css/style.d.ts.map +1 -1
  55. package/dist/html/css/style.js +3 -1
  56. package/dist/html/css/style.js.map +1 -1
  57. package/dist/html/css/values.d.ts +14 -1
  58. package/dist/html/css/values.d.ts.map +1 -1
  59. package/dist/html/css/values.js +11 -8
  60. package/dist/html/css/values.js.map +1 -1
  61. package/dist/html/index.d.ts.map +1 -1
  62. package/dist/html/index.js +1 -0
  63. package/dist/html/index.js.map +1 -1
  64. package/dist/html/layout/boxes.d.ts +12 -5
  65. package/dist/html/layout/boxes.d.ts.map +1 -1
  66. package/dist/html/layout/boxes.js +7 -4
  67. package/dist/html/layout/boxes.js.map +1 -1
  68. package/dist/html/layout/inline.d.ts.map +1 -1
  69. package/dist/html/layout/inline.js +22 -1
  70. package/dist/html/layout/inline.js.map +1 -1
  71. package/dist/html/node.d.ts +22 -8
  72. package/dist/html/node.d.ts.map +1 -1
  73. package/dist/html/node.js +97 -15
  74. package/dist/html/node.js.map +1 -1
  75. package/dist/html/paint.d.ts +3 -0
  76. package/dist/html/paint.d.ts.map +1 -1
  77. package/dist/html/paint.js +2 -2
  78. package/dist/html/paint.js.map +1 -1
  79. package/dist/index.d.ts +11 -1
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +26 -0
  82. package/dist/index.js.map +1 -1
  83. package/dist/maps/clip.d.ts +43 -0
  84. package/dist/maps/clip.d.ts.map +1 -0
  85. package/dist/maps/clip.js +171 -0
  86. package/dist/maps/clip.js.map +1 -0
  87. package/dist/maps/gzip.d.ts +15 -0
  88. package/dist/maps/gzip.d.ts.map +1 -0
  89. package/dist/maps/gzip.js +56 -0
  90. package/dist/maps/gzip.js.map +1 -0
  91. package/dist/maps/index.d.ts +55 -0
  92. package/dist/maps/index.d.ts.map +1 -0
  93. package/dist/maps/index.js +141 -0
  94. package/dist/maps/index.js.map +1 -0
  95. package/dist/maps/labels.d.ts +123 -0
  96. package/dist/maps/labels.d.ts.map +1 -0
  97. package/dist/maps/labels.js +323 -0
  98. package/dist/maps/labels.js.map +1 -0
  99. package/dist/maps/mvt.d.ts +244 -0
  100. package/dist/maps/mvt.d.ts.map +1 -0
  101. package/dist/maps/mvt.js +820 -0
  102. package/dist/maps/mvt.js.map +1 -0
  103. package/dist/maps/node.d.ts +278 -0
  104. package/dist/maps/node.d.ts.map +1 -0
  105. package/dist/maps/node.js +1459 -0
  106. package/dist/maps/node.js.map +1 -0
  107. package/dist/maps/overlay.d.ts +157 -0
  108. package/dist/maps/overlay.d.ts.map +1 -0
  109. package/dist/maps/overlay.js +472 -0
  110. package/dist/maps/overlay.js.map +1 -0
  111. package/dist/maps/paint.d.ts +226 -0
  112. package/dist/maps/paint.d.ts.map +1 -0
  113. package/dist/maps/paint.js +705 -0
  114. package/dist/maps/paint.js.map +1 -0
  115. package/dist/maps/proj.d.ts +281 -0
  116. package/dist/maps/proj.d.ts.map +1 -0
  117. package/dist/maps/proj.js +451 -0
  118. package/dist/maps/proj.js.map +1 -0
  119. package/dist/maps/sources.d.ts +256 -0
  120. package/dist/maps/sources.d.ts.map +1 -0
  121. package/dist/maps/sources.js +210 -0
  122. package/dist/maps/sources.js.map +1 -0
  123. package/dist/maps/style.d.ts +141 -0
  124. package/dist/maps/style.d.ts.map +1 -0
  125. package/dist/maps/style.js +147 -0
  126. package/dist/maps/style.js.map +1 -0
  127. package/dist/maps/styles.d.ts +82 -0
  128. package/dist/maps/styles.d.ts.map +1 -0
  129. package/dist/maps/styles.js +898 -0
  130. package/dist/maps/styles.js.map +1 -0
  131. package/dist/maps/tiles.d.ts +283 -0
  132. package/dist/maps/tiles.d.ts.map +1 -0
  133. package/dist/maps/tiles.js +469 -0
  134. package/dist/maps/tiles.js.map +1 -0
  135. package/dist/maps/types.d.ts +244 -0
  136. package/dist/maps/types.d.ts.map +1 -0
  137. package/dist/maps/types.js +2 -0
  138. package/dist/maps/types.js.map +1 -0
  139. package/dist/qml/containers.d.ts +3 -0
  140. package/dist/qml/containers.d.ts.map +1 -0
  141. package/dist/qml/containers.js +331 -0
  142. package/dist/qml/containers.js.map +1 -0
  143. package/dist/qml/controls.d.ts +12 -0
  144. package/dist/qml/controls.d.ts.map +1 -0
  145. package/dist/qml/controls.js +21 -0
  146. package/dist/qml/controls.js.map +1 -0
  147. package/dist/qml/globals.d.ts +6 -0
  148. package/dist/qml/globals.d.ts.map +1 -0
  149. package/dist/qml/globals.js +25 -0
  150. package/dist/qml/globals.js.map +1 -0
  151. package/dist/qml/index.d.ts +15 -0
  152. package/dist/qml/index.d.ts.map +1 -0
  153. package/dist/qml/index.js +25 -0
  154. package/dist/qml/index.js.map +1 -0
  155. package/dist/qml/interaction.d.ts +12 -0
  156. package/dist/qml/interaction.d.ts.map +1 -0
  157. package/dist/qml/interaction.js +246 -0
  158. package/dist/qml/interaction.js.map +1 -0
  159. package/dist/qml/ir.d.ts +106 -0
  160. package/dist/qml/ir.d.ts.map +1 -0
  161. package/dist/qml/ir.js +7 -0
  162. package/dist/qml/ir.js.map +1 -0
  163. package/dist/qml/layouts.d.ts +3 -0
  164. package/dist/qml/layouts.d.ts.map +1 -0
  165. package/dist/qml/layouts.js +122 -0
  166. package/dist/qml/layouts.js.map +1 -0
  167. package/dist/qml/models.d.ts +14 -0
  168. package/dist/qml/models.d.ts.map +1 -0
  169. package/dist/qml/models.js +80 -0
  170. package/dist/qml/models.js.map +1 -0
  171. package/dist/qml/objects.d.ts +228 -0
  172. package/dist/qml/objects.d.ts.map +1 -0
  173. package/dist/qml/objects.js +1103 -0
  174. package/dist/qml/objects.js.map +1 -0
  175. package/dist/qml/parse.d.ts +10 -0
  176. package/dist/qml/parse.d.ts.map +1 -0
  177. package/dist/qml/parse.js +607 -0
  178. package/dist/qml/parse.js.map +1 -0
  179. package/dist/qml/qtquick.d.ts +7 -0
  180. package/dist/qml/qtquick.d.ts.map +1 -0
  181. package/dist/qml/qtquick.js +347 -0
  182. package/dist/qml/qtquick.js.map +1 -0
  183. package/dist/qml/react.d.ts +83 -0
  184. package/dist/qml/react.d.ts.map +1 -0
  185. package/dist/qml/react.js +323 -0
  186. package/dist/qml/react.js.map +1 -0
  187. package/dist/qml/resolver.d.ts +38 -0
  188. package/dist/qml/resolver.d.ts.map +1 -0
  189. package/dist/qml/resolver.js +64 -0
  190. package/dist/qml/resolver.js.map +1 -0
  191. package/dist/qml/slots.d.ts +65 -0
  192. package/dist/qml/slots.d.ts.map +1 -0
  193. package/dist/qml/slots.js +230 -0
  194. package/dist/qml/slots.js.map +1 -0
  195. package/dist/qml/states.d.ts +5 -0
  196. package/dist/qml/states.d.ts.map +1 -0
  197. package/dist/qml/states.js +276 -0
  198. package/dist/qml/states.js.map +1 -0
  199. package/dist/qml/view-utils.d.ts +42 -0
  200. package/dist/qml/view-utils.d.ts.map +1 -0
  201. package/dist/qml/view-utils.js +121 -0
  202. package/dist/qml/view-utils.js.map +1 -0
  203. package/dist/reorder/hx.d.ts +18 -0
  204. package/dist/reorder/hx.d.ts.map +1 -0
  205. package/dist/reorder/hx.js +10 -0
  206. package/dist/reorder/hx.js.map +1 -0
  207. package/dist/reorder/index.d.ts +314 -0
  208. package/dist/reorder/index.d.ts.map +1 -0
  209. package/dist/reorder/index.js +1539 -0
  210. package/dist/reorder/index.js.map +1 -0
  211. package/dist/reorder/model.d.ts +107 -0
  212. package/dist/reorder/model.d.ts.map +1 -0
  213. package/dist/reorder/model.js +162 -0
  214. package/dist/reorder/model.js.map +1 -0
  215. package/dist/richtext/node.d.ts +15 -5
  216. package/dist/richtext/node.d.ts.map +1 -1
  217. package/dist/richtext/node.js +40 -11
  218. package/dist/richtext/node.js.map +1 -1
  219. package/dist/richtext/runs.d.ts +35 -13
  220. package/dist/richtext/runs.d.ts.map +1 -1
  221. package/dist/richtext/runs.js +83 -27
  222. package/dist/richtext/runs.js.map +1 -1
  223. package/dist/tabs/hx.d.ts +18 -0
  224. package/dist/tabs/hx.d.ts.map +1 -0
  225. package/dist/tabs/hx.js +10 -0
  226. package/dist/tabs/hx.js.map +1 -0
  227. package/dist/tabs/index.d.ts +160 -0
  228. package/dist/tabs/index.d.ts.map +1 -0
  229. package/dist/tabs/index.js +1265 -0
  230. package/dist/tabs/index.js.map +1 -0
  231. package/dist/terminal/vt/fonts.d.ts +45 -15
  232. package/dist/terminal/vt/fonts.d.ts.map +1 -1
  233. package/dist/terminal/vt/fonts.js +89 -19
  234. package/dist/terminal/vt/fonts.js.map +1 -1
  235. package/dist/terminal/vt/index.d.ts +1 -1
  236. package/dist/terminal/vt/index.d.ts.map +1 -1
  237. package/dist/terminal/vt/index.js +3 -3
  238. package/dist/terminal/vt/index.js.map +1 -1
  239. package/dist/terminal/vt/node.d.ts +42 -0
  240. package/dist/terminal/vt/node.d.ts.map +1 -1
  241. package/dist/terminal/vt/node.js +95 -11
  242. package/dist/terminal/vt/node.js.map +1 -1
  243. package/dist/terminal/vt/pty.d.ts +16 -0
  244. package/dist/terminal/vt/pty.d.ts.map +1 -1
  245. package/dist/terminal/vt/pty.js +186 -0
  246. package/dist/terminal/vt/pty.js.map +1 -1
  247. package/dist/terminal/vt/renderer.d.ts.map +1 -1
  248. package/dist/terminal/vt/renderer.js +13 -3
  249. package/dist/terminal/vt/renderer.js.map +1 -1
  250. package/package.json +23 -3
  251. package/src/charts/components.ts +14 -7
  252. package/src/charts/node.ts +81 -26
  253. package/src/charts/render.ts +16 -6
  254. package/src/code-editor/node.ts +147 -53
  255. package/src/flow/draw.ts +104 -35
  256. package/src/flow/index.ts +33 -13
  257. package/src/flow/model.ts +4 -2
  258. package/src/flow/node.ts +158 -59
  259. package/src/flow/types.ts +46 -21
  260. package/src/formula/index.ts +4 -2
  261. package/src/formula/node.ts +24 -5
  262. package/src/html/controls.ts +4 -1
  263. package/src/html/css/cascade.ts +18 -7
  264. package/src/html/css/parse.ts +46 -21
  265. package/src/html/css/style.ts +6 -1
  266. package/src/html/css/values.ts +25 -9
  267. package/src/html/index.ts +1 -0
  268. package/src/html/layout/boxes.ts +19 -9
  269. package/src/html/layout/inline.ts +23 -1
  270. package/src/html/node.ts +110 -17
  271. package/src/html/paint.ts +17 -2
  272. package/src/index.ts +142 -0
  273. package/src/maps/clip.ts +184 -0
  274. package/src/maps/gzip.ts +66 -0
  275. package/src/maps/index.ts +316 -0
  276. package/src/maps/labels.ts +442 -0
  277. package/src/maps/mvt.ts +857 -0
  278. package/src/maps/node.ts +1819 -0
  279. package/src/maps/overlay.ts +639 -0
  280. package/src/maps/paint.ts +978 -0
  281. package/src/maps/proj.ts +641 -0
  282. package/src/maps/sources.ts +425 -0
  283. package/src/maps/style.ts +309 -0
  284. package/src/maps/styles.ts +1040 -0
  285. package/src/maps/tiles.ts +676 -0
  286. package/src/maps/types.ts +260 -0
  287. package/src/qml/containers.tsx +380 -0
  288. package/src/qml/controls.tsx +47 -0
  289. package/src/qml/globals.ts +33 -0
  290. package/src/qml/index.ts +67 -0
  291. package/src/qml/interaction.tsx +317 -0
  292. package/src/qml/ir.ts +80 -0
  293. package/src/qml/layouts.tsx +141 -0
  294. package/src/qml/models.ts +101 -0
  295. package/src/qml/objects.ts +1431 -0
  296. package/src/qml/parse.ts +624 -0
  297. package/src/qml/qtquick.tsx +413 -0
  298. package/src/qml/react.tsx +454 -0
  299. package/src/qml/resolver.ts +99 -0
  300. package/src/qml/slots.ts +249 -0
  301. package/src/qml/states.ts +318 -0
  302. package/src/qml/view-utils.ts +140 -0
  303. package/src/reorder/hx.ts +31 -0
  304. package/src/reorder/index.ts +2223 -0
  305. package/src/reorder/model.ts +232 -0
  306. package/src/richtext/node.ts +49 -14
  307. package/src/richtext/runs.ts +115 -30
  308. package/src/tabs/hx.ts +31 -0
  309. package/src/tabs/index.ts +1686 -0
  310. package/src/terminal/vt/fonts.ts +128 -36
  311. package/src/terminal/vt/index.ts +4 -2
  312. package/src/terminal/vt/node.ts +105 -12
  313. package/src/terminal/vt/pty.ts +243 -0
  314. package/src/terminal/vt/renderer.ts +13 -3
package/src/flow/draw.ts CHANGED
@@ -9,6 +9,17 @@
9
9
  // returns `null` there, the pane skips its drawing, and a custom node type
10
10
  // written against `FlowPainter` never has to know (see `src/sparkline/`,
11
11
  // which makes the same check for one stroke).
12
+ //
13
+ // It is also where the display scale is applied. The pane, and every node
14
+ // type drawing through this, think in logical pixels — the unit a style
15
+ // length is in, the unit an event's `x`/`y` arrive in — while ntk's context
16
+ // draws on the device grid, which on a retina panel is two pixels to the
17
+ // logical one (react-x11's docs/scale.md). The multiply happens here, once,
18
+ // on the way into the context: geometry by `scale`, and text by shaping at
19
+ // `size * scale` so a label on a 2x panel is sharper rather than twice as
20
+ // big or half as small. Deliberately not a `ctx.scale()`: ntk positions
21
+ // glyphs through the transform but sizes them from the font, and a
22
+ // non-identity transform forfeits every server-side fast path a fill has.
12
23
  import type {
13
24
  FlowPainter,
14
25
  ShapeOptions,
@@ -47,7 +58,8 @@ interface LayoutLike {
47
58
  draw(ctx: unknown, x: number, y: number): void;
48
59
  }
49
60
 
50
- /** One shaped string, kept between frames. */
61
+ /** One shaped string, kept between frames. `width` and `height` are
62
+ * logical; the layout itself was shaped at device size. */
51
63
  export interface CachedText {
52
64
  width: number;
53
65
  height: number;
@@ -69,6 +81,13 @@ export interface PainterOptions {
69
81
  family: string;
70
82
  /** Ink for text that names no colour. */
71
83
  color: string;
84
+ /**
85
+ * Device pixels per logical pixel — the display scale the pane's window
86
+ * resolved to. Everything handed to the painter is logical; this is the
87
+ * one multiply on the way to the context, and the one divide on the way
88
+ * back from a measured layout.
89
+ */
90
+ scale: number;
72
91
  /**
73
92
  * Laid-out text, keyed by face, size, weight, colour and the string.
74
93
  *
@@ -92,13 +111,30 @@ function isCanvas(ctx: unknown): ctx is CanvasLike {
92
111
  /** Bound, so a pathological graph cannot turn the width cache into a leak. */
93
112
  const CACHE_LIMIT = 4000;
94
113
 
114
+ /**
115
+ * A logical value on the device grid.
116
+ *
117
+ * A value the pane already put on whole device pixels has to *arrive* as the
118
+ * integer it is: `x * 1.5` is not always one in floating point, and ntk's
119
+ * fast paths for a rounded box, a stroke and a blit are all gated on
120
+ * integral geometry. Anything else is left alone — the sub-pixel position
121
+ * of a bezier's control point is not something to snap.
122
+ */
123
+ export function toDevice(value: number, scale: number): number {
124
+ const out = value * scale;
125
+ const whole = Math.round(out);
126
+ return Math.abs(out - whole) < 1e-6 ? whole : out;
127
+ }
128
+
95
129
  function fontStyle(
96
130
  opts: PainterOptions,
97
131
  options: TextOptions | undefined,
98
132
  ): Record<string, unknown> {
99
133
  return {
100
134
  family: options?.family ?? opts.family,
101
- size: options?.size ?? 13,
135
+ // `app.fonts` takes device sizes — core hands it `fontSize * scale`
136
+ // for the same reason — so the face is shaped for the panel's grid.
137
+ size: (options?.size ?? 13) * opts.scale,
102
138
  weight: options?.weight ?? 400,
103
139
  style: 'normal',
104
140
  color: options?.color ?? opts.color,
@@ -121,15 +157,17 @@ function shape(
121
157
  const size = options?.size ?? 13;
122
158
  const family = options?.family ?? opts.family;
123
159
  // The colour is part of the key: it is baked into the layout, so two
124
- // labels that differ only in ink are two shaped runs.
160
+ // labels that differ only in ink are two shaped runs. The scale is not:
161
+ // it is constant for the life of the pane that owns the cache.
125
162
  const key = `${family}|${size}|${options?.weight ?? 400}|${options?.color ?? opts.color}|${text}`;
126
163
  const hit = cache.get(key);
127
164
  if (hit) return hit;
128
165
  const layout = fonts.layout(text, fontStyle(opts, options));
129
166
  if (cache.size >= CACHE_LIMIT) cache.clear();
167
+ const s = opts.scale;
130
168
  const entry = {
131
- width: layout.width,
132
- height: layout.height || size * 1.3,
169
+ width: layout.width / s,
170
+ height: layout.height ? layout.height / s : size * 1.3,
133
171
  layout,
134
172
  };
135
173
  cache.set(key, entry);
@@ -153,6 +191,7 @@ export function measureText(
153
191
 
154
192
  class Painter implements FlowPainter {
155
193
  readonly raw: unknown;
194
+ readonly scale: number;
156
195
  private readonly ctx: CanvasLike;
157
196
  private readonly opts: PainterOptions;
158
197
 
@@ -160,6 +199,12 @@ class Painter implements FlowPainter {
160
199
  this.ctx = ctx;
161
200
  this.raw = ctx;
162
201
  this.opts = opts;
202
+ this.scale = opts.scale;
203
+ }
204
+
205
+ /** A logical coordinate or length as the context wants it. */
206
+ private d(value: number): number {
207
+ return toDevice(value, this.scale);
163
208
  }
164
209
 
165
210
  save(): void {
@@ -174,9 +219,9 @@ class Painter implements FlowPainter {
174
219
  const { ctx } = this;
175
220
  ctx.beginPath();
176
221
  if (radius > 0 && typeof ctx.roundRect === 'function') {
177
- ctx.roundRect(x, y, w, h, radius);
222
+ ctx.roundRect(this.d(x), this.d(y), this.d(w), this.d(h), this.d(radius));
178
223
  } else {
179
- ctx.rect(x, y, w, h);
224
+ ctx.rect(this.d(x), this.d(y), this.d(w), this.d(h));
180
225
  }
181
226
  ctx.clip();
182
227
  }
@@ -184,10 +229,12 @@ class Painter implements FlowPainter {
184
229
  private applyStroke(options: StrokeOptions): void {
185
230
  const { ctx } = this;
186
231
  ctx.strokeStyle = options.stroke ?? this.opts.color;
187
- ctx.lineWidth = options.lineWidth ?? 1;
232
+ ctx.lineWidth = this.d(options.lineWidth ?? 1);
188
233
  if (typeof ctx.setLineDash === 'function') {
189
- ctx.setLineDash(options.dash ? [...options.dash] : []);
190
- if ('lineDashOffset' in ctx) ctx.lineDashOffset = options.dashOffset ?? 0;
234
+ ctx.setLineDash(options.dash ? options.dash.map((v) => this.d(v)) : []);
235
+ if ('lineDashOffset' in ctx) {
236
+ ctx.lineDashOffset = this.d(options.dashOffset ?? 0);
237
+ }
191
238
  }
192
239
  }
193
240
 
@@ -215,12 +262,21 @@ class Painter implements FlowPainter {
215
262
  // single server-side rectangle rather than a path to rasterize.
216
263
  if (!rounded && !options.stroke && options.fill) {
217
264
  ctx.fillStyle = options.fill;
218
- ctx.fillRect(x, y, w, h);
265
+ ctx.fillRect(this.d(x), this.d(y), this.d(w), this.d(h));
219
266
  return;
220
267
  }
221
268
  ctx.beginPath();
222
- if (rounded) ctx.roundRect!(x, y, w, h, radius);
223
- else ctx.rect(x, y, w, h);
269
+ if (rounded) {
270
+ ctx.roundRect!(
271
+ this.d(x),
272
+ this.d(y),
273
+ this.d(w),
274
+ this.d(h),
275
+ this.d(radius),
276
+ );
277
+ } else {
278
+ ctx.rect(this.d(x), this.d(y), this.d(w), this.d(h));
279
+ }
224
280
  if (options.fill) {
225
281
  ctx.fillStyle = options.fill;
226
282
  ctx.fill();
@@ -239,14 +295,19 @@ class Painter implements FlowPainter {
239
295
  ctx.beginPath();
240
296
  if (rounded) {
241
297
  ctx.roundRect!(
242
- x + inset,
243
- y + inset,
244
- w - pen,
245
- h - pen,
246
- Math.max(0, radius - inset),
298
+ this.d(x + inset),
299
+ this.d(y + inset),
300
+ this.d(w - pen),
301
+ this.d(h - pen),
302
+ this.d(Math.max(0, radius - inset)),
247
303
  );
248
304
  } else {
249
- ctx.rect(x + inset, y + inset, w - pen, h - pen);
305
+ ctx.rect(
306
+ this.d(x + inset),
307
+ this.d(y + inset),
308
+ this.d(w - pen),
309
+ this.d(h - pen),
310
+ );
250
311
  }
251
312
  }
252
313
  this.applyStroke(options);
@@ -259,7 +320,7 @@ class Painter implements FlowPainter {
259
320
  if (r <= 0) return;
260
321
  const { ctx } = this;
261
322
  ctx.beginPath();
262
- ctx.arc(x, y, r, 0, Math.PI * 2);
323
+ ctx.arc(this.d(x), this.d(y), this.d(r), 0, Math.PI * 2);
263
324
  if (options.fill) {
264
325
  ctx.fillStyle = options.fill;
265
326
  ctx.fill();
@@ -271,13 +332,20 @@ class Painter implements FlowPainter {
271
332
  }
272
333
  }
273
334
 
335
+ private trace(points: readonly XYPosition[], close: boolean): void {
336
+ const { ctx } = this;
337
+ ctx.moveTo(this.d(points[0].x), this.d(points[0].y));
338
+ for (let i = 1; i < points.length; i++) {
339
+ ctx.lineTo(this.d(points[i].x), this.d(points[i].y));
340
+ }
341
+ if (close) ctx.closePath();
342
+ }
343
+
274
344
  polyline(points: readonly XYPosition[], options: StrokeOptions): void {
275
345
  if (points.length < 2) return;
276
346
  const { ctx } = this;
277
347
  ctx.beginPath();
278
- ctx.moveTo(points[0].x, points[0].y);
279
- for (let i = 1; i < points.length; i++)
280
- ctx.lineTo(points[i].x, points[i].y);
348
+ this.trace(points, false);
281
349
  this.applyStroke(options);
282
350
  ctx.lineJoin = 'round';
283
351
  ctx.lineCap = 'round';
@@ -295,8 +363,7 @@ class Painter implements FlowPainter {
295
363
  for (const run of runs) {
296
364
  if (run.length < 2) continue;
297
365
  any = true;
298
- ctx.moveTo(run[0].x, run[0].y);
299
- for (let i = 1; i < run.length; i++) ctx.lineTo(run[i].x, run[i].y);
366
+ this.trace(run, false);
300
367
  }
301
368
  if (!any) return;
302
369
  this.applyStroke(options);
@@ -308,8 +375,11 @@ class Painter implements FlowPainter {
308
375
  if (centres.length === 0 || size <= 0) return;
309
376
  const { ctx } = this;
310
377
  const half = size / 2;
378
+ const side = this.d(size);
311
379
  ctx.beginPath();
312
- for (const c of centres) ctx.rect(c.x - half, c.y - half, size, size);
380
+ for (const c of centres) {
381
+ ctx.rect(this.d(c.x - half), this.d(c.y - half), side, side);
382
+ }
313
383
  ctx.fillStyle = color;
314
384
  ctx.fill();
315
385
  }
@@ -324,10 +394,7 @@ class Painter implements FlowPainter {
324
394
  for (const points of shapes) {
325
395
  if (points.length < 3) continue;
326
396
  any = true;
327
- ctx.moveTo(points[0].x, points[0].y);
328
- for (let i = 1; i < points.length; i++)
329
- ctx.lineTo(points[i].x, points[i].y);
330
- ctx.closePath();
397
+ this.trace(points, true);
331
398
  }
332
399
  if (!any) return;
333
400
  if (options.fill) {
@@ -345,10 +412,7 @@ class Painter implements FlowPainter {
345
412
  if (points.length < 3) return;
346
413
  const { ctx } = this;
347
414
  ctx.beginPath();
348
- ctx.moveTo(points[0].x, points[0].y);
349
- for (let i = 1; i < points.length; i++)
350
- ctx.lineTo(points[i].x, points[i].y);
351
- ctx.closePath();
415
+ this.trace(points, true);
352
416
  if (options.fill) {
353
417
  ctx.fillStyle = options.fill;
354
418
  ctx.fill();
@@ -401,7 +465,12 @@ class Painter implements FlowPainter {
401
465
  ? -layout.width
402
466
  : 0;
403
467
  const dy = options?.baseline === 'middle' ? -layout.height / 2 : 0;
404
- entry.layout.draw(this.raw, Math.round(x + dx), Math.round(y + dy));
468
+ // Rounded on the device grid, where the glyphs land.
469
+ entry.layout.draw(
470
+ this.raw,
471
+ Math.round(this.d(x + dx)),
472
+ Math.round(this.d(y + dy)),
473
+ );
405
474
  }
406
475
  }
407
476
 
package/src/flow/index.ts CHANGED
@@ -309,6 +309,16 @@ export function Flow<N = FlowNodeData, E = unknown>(
309
309
  const node = byId.get(body.id);
310
310
  const type = node && nodeTypes?.[node.type ?? 'default'];
311
311
  if (!node || !type?.render) return null;
312
+ // The two boxes are two units. The outer one is the pane's: it is
313
+ // positioned and clipped in the same logical window pixels the pane
314
+ // painted the card in, so the body lands on the card exactly. The
315
+ // inner one is the body's, and `scale` is what makes the difference
316
+ // between them the zoom (react-x11#449, core 2.6): every length
317
+ // under it is multiplied by that factor, so a body sized in graph
318
+ // units comes out at the size the card is drawn at, with its text
319
+ // shaped at that size rather than stretched.
320
+ const width = body.width / body.zoom;
321
+ const height = body.height / body.zoom;
312
322
  return React.createElement(
313
323
  'box',
314
324
  {
@@ -319,22 +329,32 @@ export function Flow<N = FlowNodeData, E = unknown>(
319
329
  top: body.y,
320
330
  width: body.width,
321
331
  height: body.height,
322
- // The box follows the zoom; what is inside it does not, so
323
- // this is what stops a zoomed-out node spilling its form over
324
- // the graph.
332
+ // A body is free to overflow what it was given a popup's
333
+ // fallback, a long line — and this is what stops it spilling
334
+ // over the graph.
325
335
  overflow: 'hidden',
326
336
  },
327
337
  },
328
- React.createElement(FlowNodeBody, {
329
- type: type as FlowNodeType<unknown>,
330
- node: node as FlowNode<unknown>,
331
- selected: body.selected,
332
- zoom: body.zoom,
333
- x: body.x,
334
- y: body.y,
335
- width: body.width,
336
- height: body.height,
337
- }),
338
+ React.createElement(
339
+ 'box',
340
+ {
341
+ scale: body.zoom,
342
+ // Sized in the unit it establishes, which is what makes it
343
+ // fill the outer box at every zoom.
344
+ style: { width, height },
345
+ },
346
+ React.createElement(FlowNodeBody, {
347
+ type: type as FlowNodeType<unknown>,
348
+ node: node as FlowNode<unknown>,
349
+ selected: body.selected,
350
+ zoom: body.zoom,
351
+ // Graph units, like everything else the body sees.
352
+ x: body.x / body.zoom,
353
+ y: body.y / body.zoom,
354
+ width,
355
+ height,
356
+ }),
357
+ ),
338
358
  );
339
359
  })
340
360
  : null;
package/src/flow/model.ts CHANGED
@@ -53,8 +53,10 @@ export const NODE_HEADER = 26;
53
53
  /** How far a `render` node's body is inset from its own border, so the
54
54
  * resize grips and the border stay the pane's to hit. */
55
55
  export const NODE_BODY_INSET = 5;
56
- /** Below this the mounted body is not worth showing: it is laid out at its
57
- * natural size, so a small box would show a corner of a form. */
56
+ /** Below this the mounted body is not worth showing. It scales with the
57
+ * zoom now, so this is no longer about a form clipped to a corner of its
58
+ * card — it is that nobody can read a form a third of its size, and that a
59
+ * real subtree per card is the one cost a zoomed-out overview cannot pay. */
58
60
  export const RENDER_ZOOM = 0.6;
59
61
  /** How far outside a handle a press still counts as grabbing it. */
60
62
  export const HANDLE_SLOP = 5;