react-x11 2.22.8 → 2.22.10

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.
@@ -19,7 +19,7 @@
19
19
 
20
20
  import React, { useCallback, useRef } from 'react';
21
21
  import { useAppOrNull } from '../appcontext.js';
22
- import { useTheme } from './theme.js';
22
+ import { capBand, useTheme } from './theme.js';
23
23
 
24
24
  const h = React.createElement;
25
25
 
@@ -101,6 +101,37 @@ export function nativeTitleStyle(controlSize = 'regular') {
101
101
  return { alignSelf: 'flex-end', fontSize: CONTROL_FONT_SIZE[controlSize] };
102
102
  }
103
103
 
104
+ /**
105
+ * Where a push button's title goes in a control whose padding is only the
106
+ * bezel's shadow — so the box inside it is the body, and an icon beside the
107
+ * title is centred on the body as AppKit centres an image. The title is a
108
+ * margin box inside that: `TITLE_BASELINE` under its last baseline, and over
109
+ * its capitals the room a one-line title leaves there in a body of the
110
+ * bezel's own height. So a title that fits sits exactly where the cell puts
111
+ * it, filling the body to the pixel, and one that wrapped keeps the same
112
+ * margins round the lines it grew by: the control is taller by those lines,
113
+ * and so is the bezel drawn to it (src/cocoa/bezels.js, the flexible push).
114
+ * AppKit's own flexible push starts a wrapped title's capitals within half a
115
+ * point of here (4.5pt under the body's top edge at the regular size, 4 at
116
+ * the small) and ends it 2.5pt nearer the bottom edge at the regular size:
117
+ * the one-line baseline is kept instead, so a title does not move when it
118
+ * wraps, and a wrapped button is that much taller than AppKit's.
119
+ *
120
+ * The capitals are measured the way the title will be, `capBand` of the
121
+ * control font size, so the two cancel: a face whose trimmed caps round to
122
+ * the band — every UI face measured — fills the body exactly and never
123
+ * grows the control by a line it does not have.
124
+ */
125
+ export function nativeTitleMargins(nat, shadow, controlSize = 'regular') {
126
+ const body = nat.height - shadow.top - shadow.bottom;
127
+ const baseline = TITLE_BASELINE[controlSize];
128
+ const caps = capBand(CONTROL_FONT_SIZE[controlSize]);
129
+ return {
130
+ marginTop: Math.max(0, body - baseline - caps),
131
+ marginBottom: baseline,
132
+ };
133
+ }
134
+
104
135
  /**
105
136
  * The **footprint** a native control sits in: the box the caller's style
106
137
  * sizes, with the control — bezel, title and press wash, one unit at
@@ -108,9 +139,9 @@ export function nativeTitleStyle(controlSize = 'regular') {
108
139
  *
109
140
  * Two boxes rather than one, because the title is *placed* and not centred:
110
141
  * it rides `TITLE_BASELINE` above the bezel body's bottom edge, and that is
111
- * only where the cell puts it while the box is exactly `bezelNatural` tall.
112
- * The caller's style is applied last and flex stretches a box regardless, so
113
- * it was not always: `style={{ height: 44 }}`, a `height: '100%'` in a taller
142
+ * only where the cell puts it while the box is the control's own height. The
143
+ * caller's style is applied last and flex stretches a box regardless, so it
144
+ * was not always: `style={{ height: 44 }}`, a `height: '100%'` in a taller
114
145
  * parent and a bare `flexGrow: 1` each made the box taller, and the two
115
146
  * halves — the bezel absolutely filling it, the title glued to its bottom —
116
147
  * came apart, the label landing below the control it names (issue #510). The
@@ -118,20 +149,20 @@ export function nativeTitleStyle(controlSize = 'regular') {
118
149
  * control is merely roomy, where a stretched native one was broken.
119
150
  *
120
151
  * So the height a caller can move belongs to a box the control sits in, and
121
- * the control keeps AppKit's. The default here is exactly today's box — an
122
- * untouched control lays out as it always did, and the explicit height still
123
- * resists a row's align-stretch — and the caller's style, applied after it,
124
- * moves the footprint alone. The control stretches across it (align-stretch,
125
- * the default), so a footprint given a width is a bezel that wide.
152
+ * the control keeps its own. Untouched, the footprint is exactly as tall as
153
+ * the control — AppKit's height, or a button's taller one when its label
154
+ * wrapped — and the caller's style, applied after it, moves the footprint
155
+ * alone. A row's align-stretch moves it too, and the control stays centred
156
+ * in the slot, which is where a control that cannot stretch belongs beside a
157
+ * taller neighbour. The control stretches across it (align-stretch, the
158
+ * default), so a footprint given a width is a bezel that wide.
126
159
  *
127
160
  * The control, not the footprint, stays the *control*: the role, the
128
161
  * handlers, the focus ring and the ref go on it, so a press in the slack
129
162
  * above a 22pt popup in a 46pt hole does nothing at all — as it does in
130
163
  * AppKit — rather than firing a button with no visible answer to the press.
131
164
  */
132
- export function nativeFootprintStyle(nat) {
133
- return { height: nat.height, justifyContent: 'center' };
134
- }
165
+ export const NATIVE_FOOTPRINT = Object.freeze({ justifyContent: 'center' });
135
166
 
136
167
  /**
137
168
  * The rest of what a native control's own box says about its size: nothing
package/src/frames.js CHANGED
@@ -53,6 +53,12 @@ export function clearPendingFrame(node) {
53
53
  * - **Every window answers for itself.** The set is app-wide, so a click
54
54
  * that dirtied both a menu and the window under it paints both, each
55
55
  * gated on its own window's frame clock.
56
+ * - **Nor while a `<glarea>` of it waits on the clock.** A window's frame
57
+ * paints the overlay a surface's children are drawn on, and the clock
58
+ * draws the surface's own frame after it, in the same tick (#641). A
59
+ * window that says it owes one (`surfaceFrameOwed`) keeps its paced
60
+ * frame: painted now, the overlay would go out a refresh or more ahead
61
+ * of the GL frame it was painted for.
56
62
  *
57
63
  * A window whose scheduled frame this pays off keeps that scheduled
58
64
  * callback; it runs and finds `needsPaint` false, which is already a cheap
@@ -68,6 +74,7 @@ export function flushPendingFrames() {
68
74
  // Same for a headless mock window that models no frame clock at all.
69
75
  if (typeof wnd?.frameInFlight !== 'function') continue;
70
76
  if (wnd.frameInFlight()) continue;
77
+ if (wnd.surfaceFrameOwed?.()) continue;
71
78
  node.flush();
72
79
  }
73
80
  }
package/src/glnodes.js CHANGED
@@ -38,6 +38,10 @@ function recordGlxFailure(app, err) {
38
38
  if (app && err && !glxFailures.has(app)) glxFailures.set(app, err);
39
39
  }
40
40
 
41
+ // ChangeWindowAttributes' backing-store value: the server keeps what the
42
+ // window shows while it is mapped (`_create`)
43
+ const BACKING_STORE_WHEN_MAPPED = 1;
44
+
41
45
  // Apps already told that a <glarea>'s children are not drawn on their
42
46
  // display. Once per app, as for a <foreign> that cannot embed: the answer
43
47
  // belongs to the server, so the next surface to find out carries no news.
@@ -301,6 +305,19 @@ export class GlAreaNode extends Node {
301
305
  this.rect = rect;
302
306
  this.config = config;
303
307
  wnd._reactX11Node = this;
308
+ // The server keeps what the surface shows while it is mapped (X backing
309
+ // store), so a pane over it that moves away — a card dragged across a
310
+ // graph drawn in GL — uncovers the last frame at once. Without it the
311
+ // uncovered pixels were the pane's until the surface drew again: an
312
+ // Expose, a frame requested, and the next vblank, which is a trail of
313
+ // the card behind it on every step. ntk forwards no backing-store
314
+ // attribute (its `backingStore` is its own double buffer), and a
315
+ // backend with no X connection has none to set.
316
+ if (typeof this.app.X?.ChangeWindowAttributes === 'function') {
317
+ this.app.X.ChangeWindowAttributes(wnd.id, {
318
+ backingStore: BACKING_STORE_WHEN_MAPPED,
319
+ });
320
+ }
304
321
  // Above everything 2D in the owning window on both backends, and above
305
322
  // every surface made before this one: X stacks a new child window over
306
323
  // its siblings, and Core Animation a layer added later over one at the
@@ -328,6 +345,15 @@ export class GlAreaNode extends Node {
328
345
  this.gl?.ready?.catch((err) => {
329
346
  if (!this.destroyed) this._failed(err, { reported: true });
330
347
  });
348
+ // …and a context that fails after it was ready — a swap the driver
349
+ // refused mid-life — says so here (ntk >= 8.12.11). Before ntk said so,
350
+ // the surface kept its last frame, or nothing, with no word to anyone:
351
+ // on NVIDIA every resize did that (sidorares/ntk#399).
352
+ if (this.gl && 'onError' in this.gl) {
353
+ this.gl.onError = (err) => {
354
+ if (!this.destroyed) this._failed(err);
355
+ };
356
+ }
331
357
  wnd.on?.('expose', () => this.requestFrame());
332
358
  wnd.map?.();
333
359
  // made on top of its siblings — over the panes of children that were