react-x11 2.11.0 → 2.13.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 (100) hide show
  1. package/README.md +278 -129
  2. package/package.json +12 -4
  3. package/src/Reconciler.js +19 -31
  4. package/src/a11y.js +2 -2
  5. package/src/anchor.js +7 -5
  6. package/src/appcontext.js +59 -30
  7. package/src/bootstrap.js +14 -0
  8. package/src/clientmessage.js +1 -1
  9. package/src/cocoa/app.js +303 -49
  10. package/src/cocoa/bezels.js +175 -30
  11. package/src/cocoa/dnd.js +27 -13
  12. package/src/cocoa/fonts.js +3 -3
  13. package/src/cocoa/glarea.js +24 -5
  14. package/src/cocoa/main.d.ts +8 -0
  15. package/src/cocoa/main.js +43 -0
  16. package/src/cocoa/overlay.js +159 -0
  17. package/src/cocoa/panehost.js +15 -5
  18. package/src/cocoa/presenter.js +13 -9
  19. package/src/cocoa/promotion.js +17 -7
  20. package/src/cocoa/relaunch.js +207 -0
  21. package/src/cocoa/threaded.js +246 -0
  22. package/src/cocoa/window.js +256 -42
  23. package/src/components/Select.js +2 -2
  24. package/src/components/anchor.js +3 -3
  25. package/src/components/native.js +12 -7
  26. package/src/components/theme.js +2 -2
  27. package/src/debug.js +1 -1
  28. package/src/decorations.js +1 -1
  29. package/src/editmenu.js +2 -2
  30. package/src/embedding.js +31 -0
  31. package/src/errors.js +46 -0
  32. package/src/events.js +78 -18
  33. package/src/foreignnodes.js +59 -5
  34. package/src/frames.js +2 -2
  35. package/src/glnodes.js +172 -41
  36. package/src/gloverlay.js +383 -0
  37. package/src/grid.js +1653 -0
  38. package/src/host.d.ts +230 -1
  39. package/src/host.js +11 -3
  40. package/src/imagesource.js +1 -1
  41. package/src/index.d.ts +34 -4
  42. package/src/index.js +9 -1
  43. package/src/layouts.js +721 -0
  44. package/src/node.d.ts +16 -3
  45. package/src/node.js +19 -21
  46. package/src/nodes/animation.js +644 -0
  47. package/src/nodes/box.js +21 -0
  48. package/src/nodes/boxpaint.js +473 -0
  49. package/src/nodes/canvas.js +269 -0
  50. package/src/nodes/cascade.js +600 -0
  51. package/src/nodes/damage.js +183 -0
  52. package/src/nodes/edithistory.js +124 -0
  53. package/src/nodes/editmenupopup.js +260 -0
  54. package/src/nodes/hittest.js +185 -0
  55. package/src/nodes/image.js +266 -0
  56. package/src/nodes/install.js +75 -0
  57. package/src/nodes/invalidate.js +465 -0
  58. package/src/nodes/kinds.js +31 -0
  59. package/src/nodes/layout.js +439 -0
  60. package/src/nodes/layouthost.js +949 -0
  61. package/src/nodes/node.js +868 -0
  62. package/src/nodes/paint.js +466 -0
  63. package/src/nodes/position.js +366 -0
  64. package/src/nodes/preedit.js +127 -0
  65. package/src/nodes/queries.js +330 -0
  66. package/src/nodes/rects.js +102 -0
  67. package/src/nodes/scrollable.js +891 -0
  68. package/src/nodes/scrollbars.js +138 -0
  69. package/src/nodes/scrollblit.js +1034 -0
  70. package/src/nodes/selectable.js +142 -0
  71. package/src/nodes/styling.js +225 -0
  72. package/src/nodes/text.js +649 -0
  73. package/src/nodes/textarea.js +391 -0
  74. package/src/nodes/textinput.js +1146 -0
  75. package/src/nodes/util.js +17 -0
  76. package/src/nodes/window/anchoring.js +161 -0
  77. package/src/nodes/window/capabilities.js +190 -0
  78. package/src/nodes/window/debugpaint.js +83 -0
  79. package/src/nodes/window/droptarget.js +145 -0
  80. package/src/nodes/window/floors.js +577 -0
  81. package/src/nodes/window/flush.js +369 -0
  82. package/src/nodes/window/hints.js +482 -0
  83. package/src/nodes/window/listeners.js +222 -0
  84. package/src/nodes/window/popup.js +71 -0
  85. package/src/nodes/window/size.js +591 -0
  86. package/src/nodes/window/window.js +954 -0
  87. package/src/palette.js +1 -1
  88. package/src/registry.js +7 -3
  89. package/src/styles.js +137 -15
  90. package/src/svgnodes.js +2 -1
  91. package/src/testing/harness.js +2 -2
  92. package/src/textselection.js +5 -3
  93. package/src/trace-registry.js +1 -1
  94. package/src/types/components.d.ts +38 -6
  95. package/src/types/elements.d.ts +26 -14
  96. package/src/types/nodes.d.ts +17 -2
  97. package/src/types/style.d.ts +94 -3
  98. package/src/windowstate.js +1 -1
  99. package/src/yoga.js +1 -1
  100. package/src/nodes.js +0 -13120
@@ -0,0 +1,383 @@
1
+ // The children of a `<glarea>`: 2D content, drawn above the GL surface.
2
+ //
3
+ // A surface is stacked over everything 2D in its window — a child X window
4
+ // above the parent's drawing on X11, a layer at zPosition 1e7 over both
5
+ // presenters on the Cocoa backend — so the window's own paint walk can never
6
+ // put a pixel on it, and does not try: a `<glarea>` is in no paint order, and
7
+ // so is nothing under it. Its children are otherwise ordinary. They are laid
8
+ // out in its box like a `<box>`'s; they live in the owning window's tree, so
9
+ // their claims land in its damage list and their input comes through its
10
+ // event manager, the hit test asking them before the surface itself
11
+ // (`GlAreaNode.hitSurface`). What is theirs alone is where they are painted:
12
+ // into *panes* above the surface, from inside the owning window's frame and
13
+ // with that frame's damage (nodes/window/flush.js, `_syncOverlays` and
14
+ // `_paintOverlays`).
15
+ //
16
+ // A pane is the backend's answer to one question — can a window composite a
17
+ // translucent child over a GL surface?
18
+ //
19
+ // - **The Cocoa backend: yes.** Core Animation composites every layer, so
20
+ // one pane holds everything — a transparent bitmap layer over the whole
21
+ // surface, above the GL layer — and a translucent background, an
22
+ // antialiased edge or a shadow blends with the GL frame under it.
23
+ // `app.createOverlayPane` is how a backend says it composites.
24
+ // - **X11: no.** A child window is opaque without a compositor, and a
25
+ // compositor would not change that: it redirects top-level windows, not
26
+ // the children inside one. So a pane is a plain child window just big
27
+ // enough for what it holds, one per region the children reach, and the
28
+ // surface shows between them. Inside a pane the ground is the surface's
29
+ // `clearColor` — the colour its frames start from — so a pixel a child
30
+ // leaves unpainted there (a rounded corner, a translucent background, text
31
+ // with no box behind it) shows that colour, never the GL frame.
32
+ //
33
+ // A window per region rather than one window shaped by the SHAPE
34
+ // extension, for two reasons: SHAPE is near-universal but not universal —
35
+ // node-x11's in-process server, where this is tested, has none — and a
36
+ // window as big as the surface would keep a backing pixmap the size of a
37
+ // map to show a legend in its corner.
38
+ //
39
+ // A pane selects no input, so the pointer over one reaches the owning window
40
+ // by the same propagation that brings it the pointer over the surface
41
+ // (src/glnodes.js, `_create`), and lands on the child it is over.
42
+ //
43
+ // A leaf module, like src/embedding.js: `appcontext.js` asks `canOverlay`
44
+ // for `useSupports('glOverlay')`, and it imports nothing of ours.
45
+ import { cssColorStraight } from 'ntk';
46
+
47
+ // ConfigureWindow's stack mode: directly above the sibling it names
48
+ const STACK_ABOVE = 0;
49
+
50
+ /**
51
+ * Whether the children of a `<glarea>` can be drawn over its surface on this
52
+ * connection: a backend that composites a pane itself, or one that can make
53
+ * the plain child window a pane is on X11. One function for the element and
54
+ * for `useSupports('glOverlay')`, which have to agree — the rule `canEmbed`
55
+ * follows for `<foreign>`.
56
+ */
57
+ export function canOverlay(app) {
58
+ return (
59
+ typeof app?.createOverlayPane === 'function' ||
60
+ typeof app?.createWindow === 'function'
61
+ );
62
+ }
63
+
64
+ /** A rect grown out to whole device pixels. */
65
+ function whole(r) {
66
+ const x = Math.floor(r.x);
67
+ const y = Math.floor(r.y);
68
+ return {
69
+ x,
70
+ y,
71
+ width: Math.ceil(r.x + r.width) - x,
72
+ height: Math.ceil(r.y + r.height) - y,
73
+ };
74
+ }
75
+
76
+ function intersect(a, b) {
77
+ const x = Math.max(a.x, b.x);
78
+ const y = Math.max(a.y, b.y);
79
+ const right = Math.min(a.x + a.width, b.x + b.width);
80
+ const bottom = Math.min(a.y + a.height, b.y + b.height);
81
+ return right > x && bottom > y
82
+ ? { x, y, width: right - x, height: bottom - y }
83
+ : null;
84
+ }
85
+
86
+ const overlaps = (a, b) =>
87
+ a.x < b.x + b.width &&
88
+ b.x < a.x + a.width &&
89
+ a.y < b.y + b.height &&
90
+ b.y < a.y + a.height;
91
+
92
+ function around(a, b) {
93
+ const x = Math.min(a.x, b.x);
94
+ const y = Math.min(a.y, b.y);
95
+ return {
96
+ x,
97
+ y,
98
+ width: Math.max(a.x + a.width, b.x + b.width) - x,
99
+ height: Math.max(a.y + a.height, b.y + b.height) - y,
100
+ };
101
+ }
102
+
103
+ const sameRect = (a, b) =>
104
+ a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
105
+
106
+ /** On screen: nothing from here to the window hidden or `display: 'none'`. */
107
+ function shown(node) {
108
+ for (let n = node; n; n = n.parent) {
109
+ if (n.destroyed || n.hidden || n.style?.display === 'none') return false;
110
+ if (n.isWindow) break;
111
+ }
112
+ return true;
113
+ }
114
+
115
+ /**
116
+ * Where the children put ink, as rects no two of which overlap: each drawn
117
+ * child's reach in whole pixels, cut to the surface, and any two that
118
+ * overlap merged into the box around both. A pane is a rectangle, and two
119
+ * panes over one pixel would each have to hold what the other draws there.
120
+ *
121
+ * The reach is `_subtreeBounds()`, the rect the damage model culls against —
122
+ * so a child's shadow, its outline and a descendant that sticks out of it
123
+ * are all inside its pane.
124
+ */
125
+ export function overlayRegions(area, surface) {
126
+ const rects = [];
127
+ for (const child of area.paintOrder()) {
128
+ const reach = intersect(whole(child._subtreeBounds()), surface);
129
+ if (reach) rects.push(reach);
130
+ }
131
+ for (let merged = true; merged;) {
132
+ merged = false;
133
+ search: for (let i = 0; i < rects.length; i++) {
134
+ for (let j = i + 1; j < rects.length; j++) {
135
+ if (overlaps(rects[i], rects[j])) {
136
+ rects[i] = around(rects[i], rects[j]);
137
+ rects.splice(j, 1);
138
+ merged = true;
139
+ break search;
140
+ }
141
+ }
142
+ }
143
+ }
144
+ return rects;
145
+ }
146
+
147
+ /**
148
+ * The colour an opaque pane is filled with before its children paint: the
149
+ * surface's `clearColor`, what the GL frame under the pane starts from, at
150
+ * full alpha. The pane is opaque, and a translucent fill would pile up on
151
+ * the pane's own last frame rather than show anything under it.
152
+ */
153
+ function groundOf(props) {
154
+ const value = props.clearColor ?? 'black';
155
+ const [r, g, b] = Array.isArray(value)
156
+ ? value
157
+ : (cssColorStraight(value) ?? [0, 0, 0, 1]);
158
+ const byte = (c) => Math.round(Math.max(0, Math.min(1, c)) * 255);
159
+ return `rgb(${byte(r)}, ${byte(g)}, ${byte(b)})`;
160
+ }
161
+
162
+ /**
163
+ * One pane: the window — or on the Cocoa backend the layer, which speaks the
164
+ * same few verbs — and what the overlay knows about it: where it is, whether
165
+ * all of it is owed a paint, and its 2d context, made once, since ntk builds
166
+ * a fresh one with subscriptions of its own on every `getContext`.
167
+ */
168
+ class Pane {
169
+ constructor(wnd, rect, transparent) {
170
+ this.wnd = wnd;
171
+ this.rect = rect;
172
+ this.transparent = transparent;
173
+ this.full = true;
174
+ this.ctx = null;
175
+ }
176
+
177
+ context() {
178
+ if (!this.ctx && typeof this.wnd.getContext === 'function') {
179
+ this.ctx = this.wnd.getContext('2d');
180
+ }
181
+ return this.ctx;
182
+ }
183
+
184
+ /** Somewhere else. A new size is a new backing — a pixmap on X11, a
185
+ * bitmap on Cocoa — and has to be painted whole; a move keeps what the
186
+ * pane holds, which moved with the children it shows. */
187
+ place(rect) {
188
+ if (rect.width !== this.rect.width || rect.height !== this.rect.height) {
189
+ this.full = true;
190
+ }
191
+ this.rect = rect;
192
+ if (typeof this.wnd.setState === 'function') this.wnd.setState(rect);
193
+ else {
194
+ this.wnd.move?.(rect.x, rect.y);
195
+ this.wnd.resize?.(rect.width, rect.height);
196
+ }
197
+ }
198
+
199
+ show(on) {
200
+ if (on) this.wnd.map?.();
201
+ else this.wnd.unmap?.();
202
+ }
203
+
204
+ destroy() {
205
+ this.wnd.destroy?.();
206
+ this.ctx = null;
207
+ }
208
+ }
209
+
210
+ /** A `<glarea>`'s panes, from the owning window's frame. */
211
+ export class GlOverlay {
212
+ constructor(area) {
213
+ this.area = area;
214
+ this.app = area.app;
215
+ // one pane over the whole surface, where the backend composites it
216
+ this.composited = typeof this.app?.createOverlayPane === 'function';
217
+ this.panes = [];
218
+ }
219
+
220
+ /**
221
+ * After layout: panes for where the children are now. True when a pane was
222
+ * made, resized or dropped — a paint the frame then owes.
223
+ *
224
+ * Nothing is made for a surface that is hidden, or has no area, or has no
225
+ * child on screen: a pane with nothing on it would only hide the surface.
226
+ * The panes of one that goes are dropped rather than kept unmapped, and
227
+ * the frame that brings it back makes new ones and paints them whole.
228
+ */
229
+ sync() {
230
+ const area = this.area;
231
+ const abs = area.abs;
232
+ let rects = [];
233
+ if (shown(area) && abs.width > 0 && abs.height > 0) {
234
+ // the surface's own rect, rounded the way its window's is
235
+ const surface = area._geometry();
236
+ if (!this.composited) rects = overlayRegions(area, surface);
237
+ else if (area.paintOrder().length) rects = [surface];
238
+ }
239
+ let changed = false;
240
+ while (this.panes.length > rects.length) {
241
+ this.panes.pop().destroy();
242
+ changed = true;
243
+ }
244
+ let made = false;
245
+ for (let i = 0; i < rects.length; i++) {
246
+ const pane = this.panes[i];
247
+ if (!pane) {
248
+ this.panes.push(this._makePane(rects[i]));
249
+ made = changed = true;
250
+ } else if (!sameRect(pane.rect, rects[i])) {
251
+ pane.place(rects[i]);
252
+ changed = true;
253
+ }
254
+ }
255
+ if (made) this.restack();
256
+ return changed;
257
+ }
258
+
259
+ _makePane(rect) {
260
+ const owner = this.area.root.window;
261
+ const attributes = { parent: owner, ...rect };
262
+ const pane = this.composited
263
+ ? new Pane(this.app.createOverlayPane(attributes), rect, true)
264
+ : // A plain child window on the owning window's visual, selecting
265
+ // nothing but what ntk selects for itself — the structure events and
266
+ // the exposures its backing store answers. The pointer is the tree's.
267
+ new Pane(this.app.createWindow(attributes), rect, false);
268
+ // ntk asks for a redraw when the backing no longer holds the picture — a
269
+ // resize it could not carry over — and a pane has nothing to redraw from
270
+ // but the children, on the next frame of the window they live in
271
+ pane.wnd.on?.('draw', () => this._lost(pane));
272
+ pane.show(true);
273
+ return pane;
274
+ }
275
+
276
+ /** A pane whose pixels are gone: all of it is owed, on the owning window's
277
+ * next frame, claimed as its rect so the rest of the window stays put. */
278
+ _lost(pane) {
279
+ pane.full = true;
280
+ const root = this.area.root;
281
+ if (root && !root.destroyed)
282
+ root.invalidate(false, { ...pane.rect }, 'expose');
283
+ }
284
+
285
+ /**
286
+ * Put the panes directly over the surface, bottom to top. Only X11 needs
287
+ * it: a child window is made on top of its siblings, which is right for a
288
+ * pane made after the surface and wrong for one made before it — the
289
+ * surface's window is made once its visual is known, which can be after
290
+ * the first frame has laid out and painted its children. So the surface
291
+ * restacks its panes when its own window is made, too (`_create`). On the
292
+ * Cocoa backend the layer's zPosition is the whole of it.
293
+ */
294
+ restack() {
295
+ if (this.composited) return;
296
+ const X = this.app?.X;
297
+ if (typeof X?.ConfigureWindow !== 'function') return;
298
+ let below = this.area.window?.id ?? null;
299
+ for (const pane of this.panes) {
300
+ const id = pane.wnd.id;
301
+ if (id == null) continue;
302
+ if (below == null) X.ConfigureWindow(id, { stackMode: STACK_ABOVE });
303
+ else X.ConfigureWindow(id, { sibling: below, stackMode: STACK_ABOVE });
304
+ below = id;
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Paint what the frame owes: every pane whole after it was made, resized
310
+ * or lost its pixels, and otherwise the frame's damage cut to each pane —
311
+ * `null` damage meaning the whole window, as it does for the paint walk.
312
+ *
313
+ * Each pass is the window's own paint walk over the children, translated
314
+ * so the pane's corner is the bitmap's, clipped to the pass, with
315
+ * `paintDamage()` naming it — the same culling, the same paint cache, the
316
+ * same everything as a pass over the window, and in its coordinates.
317
+ */
318
+ paint(damage) {
319
+ for (const pane of this.panes) {
320
+ let passes;
321
+ if (pane.full || !damage) {
322
+ passes = [pane.rect];
323
+ } else {
324
+ passes = [];
325
+ for (const rect of damage) {
326
+ const hit = intersect(rect, pane.rect);
327
+ if (hit) passes.push(hit);
328
+ }
329
+ if (passes.length === 0) continue;
330
+ }
331
+ pane.full = false;
332
+ this._paintPane(pane, passes);
333
+ }
334
+ }
335
+
336
+ _paintPane(pane, passes) {
337
+ const area = this.area;
338
+ const root = area.root;
339
+ const ctx = pane.context();
340
+ if (!ctx || !root) return;
341
+ // an opaque pane is filled with what the surface starts its frames from;
342
+ // a composited one is cleared, and shows the frame itself
343
+ const ground = pane.transparent ? null : groundOf(area.props);
344
+ ctx.save();
345
+ try {
346
+ ctx.translate(-pane.rect.x, -pane.rect.y);
347
+ for (const pass of passes) {
348
+ ctx.save();
349
+ try {
350
+ ctx.beginPath();
351
+ ctx.rect(pass.x, pass.y, pass.width, pass.height);
352
+ ctx.clip();
353
+ if (ground) {
354
+ ctx.fillStyle = ground;
355
+ ctx.fillRect(pass.x, pass.y, pass.width, pass.height);
356
+ } else {
357
+ ctx.clearRect(pass.x, pass.y, pass.width, pass.height);
358
+ }
359
+ root._paintDamage = pass;
360
+ area._paintChildren(ctx);
361
+ } finally {
362
+ root._paintDamage = null;
363
+ ctx.restore();
364
+ }
365
+ }
366
+ } finally {
367
+ ctx.restore();
368
+ }
369
+ // A layer's contents are a copy the bitmap is pushed to; an X window's
370
+ // backing store is blitted by ntk on its own, from the paint above
371
+ pane.wnd.present?.();
372
+ }
373
+
374
+ /** Unmapped now; `sync` drops them on the frame the hide lays out. */
375
+ setHidden(hidden) {
376
+ for (const pane of this.panes) pane.show(!hidden);
377
+ }
378
+
379
+ destroy() {
380
+ for (const pane of this.panes) pane.destroy();
381
+ this.panes = [];
382
+ }
383
+ }