react-x11 2.18.2 → 2.18.4
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.
- package/package.json +1 -1
- package/src/Reconciler.js +8 -1
- package/src/appcontext.js +10 -5
- package/src/cocoa/overlay.js +40 -3
- package/src/glnodes.js +133 -7
- package/src/globalmenu.js +104 -8
- package/src/gloverlay.js +340 -22
- package/src/index.d.ts +4 -1
- package/src/nodes/animation.js +2 -0
- package/src/nodes/damage.js +8 -0
- package/src/nodes/invalidate.js +126 -10
- package/src/nodes/node.js +24 -4
- package/src/nodes/position.js +11 -2
- package/src/nodes/scrollable.js +5 -2
- package/src/nodes/scrollblit.js +90 -24
- package/src/nodes/window/flush.js +40 -18
- package/src/nodes/window/window.js +4 -0
- package/src/statusnotifier.js +49 -7
- package/src/styles.js +22 -0
- package/src/svgnodes.js +76 -15
- package/src/types/elements.d.ts +6 -5
|
@@ -101,8 +101,8 @@ export class WindowFlush {
|
|
|
101
101
|
return changed;
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
/** …and their paint, with the
|
|
105
|
-
*
|
|
104
|
+
/** …and their paint, with the damage the panes are owed — their own
|
|
105
|
+
* list, which the claims that can reach them go to (`_paneReach`). */
|
|
106
106
|
_paintOverlays(damage) {
|
|
107
107
|
for (const area of this._overlaid) area._paintOverlay(damage);
|
|
108
108
|
}
|
|
@@ -195,13 +195,21 @@ export class WindowFlush {
|
|
|
195
195
|
// A bounded frame watches the walk: whatever this pass actually moved
|
|
196
196
|
// claims its old and new rects through the sink, and the frame stays
|
|
197
197
|
// a few rects instead of the whole window. An unbounded frame skips
|
|
198
|
-
// the bookkeeping — it repaints everything anyway
|
|
199
|
-
|
|
198
|
+
// the bookkeeping — it repaints everything anyway — unless the panes
|
|
199
|
+
// over a `<glarea>` are still bounded: they paint from a list of
|
|
200
|
+
// their own, and a move of the children they hold is theirs to claim.
|
|
201
|
+
const panes = this._overlaid.size !== 0;
|
|
202
|
+
if (
|
|
203
|
+
this._damage !== FULL_DAMAGE ||
|
|
204
|
+
(panes && this._paneDamage !== FULL_DAMAGE)
|
|
205
|
+
) {
|
|
200
206
|
const cap = this._damageRectCap();
|
|
201
207
|
layoutDiff.sink = (rect) => {
|
|
202
|
-
if (this._damage
|
|
203
|
-
|
|
204
|
-
|
|
208
|
+
if (this._damage !== FULL_DAMAGE) {
|
|
209
|
+
layoutMoved = true;
|
|
210
|
+
this._damage = addDamageRect(this._damage, rect, cap);
|
|
211
|
+
}
|
|
212
|
+
if (panes) this._addPaneDamage(rect);
|
|
205
213
|
};
|
|
206
214
|
}
|
|
207
215
|
try {
|
|
@@ -233,7 +241,13 @@ export class WindowFlush {
|
|
|
233
241
|
for (const node of this._reflowed) {
|
|
234
242
|
// …and the pre-mutation walk this frame reused goes with it
|
|
235
243
|
node._reflowBefore = null;
|
|
236
|
-
if (
|
|
244
|
+
if (
|
|
245
|
+
node.destroyed ||
|
|
246
|
+
(this._damage === FULL_DAMAGE &&
|
|
247
|
+
(!panes || this._paneDamage === FULL_DAMAGE))
|
|
248
|
+
) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
237
251
|
// clipped to a blitting viewport above it, like every other claim
|
|
238
252
|
// this frame, and written to that viewport's ledger too (issue
|
|
239
253
|
// #398): the claim would otherwise coalesce into the scroll's own
|
|
@@ -245,11 +259,14 @@ export class WindowFlush {
|
|
|
245
259
|
if (sv && !sv._recordBlitClaim(after)) {
|
|
246
260
|
sv._pendingBlitFrom = BLIT_POISONED;
|
|
247
261
|
}
|
|
248
|
-
this._damage
|
|
249
|
-
this._damage
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
262
|
+
if (this._damage !== FULL_DAMAGE) {
|
|
263
|
+
this._damage = addDamageRect(
|
|
264
|
+
this._damage,
|
|
265
|
+
after,
|
|
266
|
+
this._damageRectCap(),
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
if (panes) this._addPaneDamage(after);
|
|
253
270
|
}
|
|
254
271
|
this._reflowed.clear();
|
|
255
272
|
} else if (this._reflowed.size) {
|
|
@@ -286,6 +303,10 @@ export class WindowFlush {
|
|
|
286
303
|
if (!this.needsPaint) return false;
|
|
287
304
|
this.needsPaint = false;
|
|
288
305
|
const damage = this._takeDamage(width, height);
|
|
306
|
+
// …and the panes' own, which they paint instead of the window's
|
|
307
|
+
const paneDamage =
|
|
308
|
+
this._overlaid.size !== 0 ? this._takePaneDamage(width, height) : null;
|
|
309
|
+
this._paneDamage = null;
|
|
289
310
|
if (debugPaint === 'full' && !damage && width > 0 && height > 0) {
|
|
290
311
|
// Silent full-window repaints are the perf bug class this renderer
|
|
291
312
|
// actually has (see AGENTS.md); this is what surfaces them. The stack
|
|
@@ -305,8 +326,8 @@ export class WindowFlush {
|
|
|
305
326
|
// list was still taken (its bookkeeping is what keeps the two paths one
|
|
306
327
|
// code) and is simply not consumed; the presenter diffs at the layer.
|
|
307
328
|
if (typeof this.window.presentFrame === 'function') {
|
|
308
|
-
// the panes are no presenter's: they paint from
|
|
309
|
-
if (this._overlaid.size !== 0) this._paintOverlays(
|
|
329
|
+
// the panes are no presenter's: they paint from their damage either way
|
|
330
|
+
if (this._overlaid.size !== 0) this._paintOverlays(paneDamage);
|
|
310
331
|
this.window.presentFrame(this, damage);
|
|
311
332
|
this.app._reactX11Startup?.painted();
|
|
312
333
|
return true;
|
|
@@ -328,10 +349,11 @@ export class WindowFlush {
|
|
|
328
349
|
for (const rect of damage ?? [null]) {
|
|
329
350
|
this._paintRegion(ctx, rect, width, height);
|
|
330
351
|
}
|
|
331
|
-
// …and the panes over the surfaces, with
|
|
352
|
+
// …and the panes over the surfaces, with their own damage: a claim from
|
|
332
353
|
// a `<glarea>`'s children is theirs to repaint — the window's pass under
|
|
333
|
-
// the surface is one nobody sees
|
|
334
|
-
|
|
354
|
+
// the surface is one nobody sees — and one from a node that cannot reach
|
|
355
|
+
// them is not. Inside the cache's frame, like a pass.
|
|
356
|
+
if (this._overlaid.size !== 0) this._paintOverlays(paneDamage);
|
|
335
357
|
// after every region: an entry drawn in one damage rect must not be
|
|
336
358
|
// evicted before the next rect of the same frame asks for it
|
|
337
359
|
this._paintCache?.endFrame();
|
|
@@ -205,6 +205,10 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
205
205
|
// and paints with its damage (nodes/window/flush.js, src/gloverlay.js).
|
|
206
206
|
// Empty is one `size` read a frame.
|
|
207
207
|
this._overlaid = new Set();
|
|
208
|
+
// …and the damage their panes repaint, a list beside the window's:
|
|
209
|
+
// each claim goes to one or both by what it can reach (`_paneReach`).
|
|
210
|
+
// Null with nothing owed.
|
|
211
|
+
this._paneDamage = null;
|
|
208
212
|
this.events = new EventManager(this);
|
|
209
213
|
// ids of the child windows in the order the *server* stacks them,
|
|
210
214
|
// bottom to top — see _restackWindowChildren
|
package/src/statusnotifier.js
CHANGED
|
@@ -101,6 +101,13 @@ const PIXMAP_SIGNATURE = 'a(iiay)';
|
|
|
101
101
|
* the *same* tray icon and must come back on the same path — see `stop()`. */
|
|
102
102
|
let nextItemIndex = 1;
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Per slot, the moment it is free: settles once every item that has started
|
|
106
|
+
* on it so far has stopped. The next item to start on the slot waits for it —
|
|
107
|
+
* see `start()`.
|
|
108
|
+
*/
|
|
109
|
+
const vacancies = new Map();
|
|
110
|
+
|
|
104
111
|
/** Claim a tray slot. `useTray()` takes one per hook instance, for its life. */
|
|
105
112
|
export function allocateItemSlot() {
|
|
106
113
|
return nextItemIndex++;
|
|
@@ -232,6 +239,11 @@ export class StatusNotifierItem {
|
|
|
232
239
|
/** Set while withdrawing, so `Status` reads `Passive` on the way out —
|
|
233
240
|
* see `announcePassive()`. */
|
|
234
241
|
this.withdrawing = false;
|
|
242
|
+
/** Settles once `stop()` has finished, which is what the next item on
|
|
243
|
+
* this slot waits for — see `start()`. */
|
|
244
|
+
this.gone = new Promise((resolve) => {
|
|
245
|
+
this.markGone = resolve;
|
|
246
|
+
});
|
|
235
247
|
|
|
236
248
|
this.ref = null;
|
|
237
249
|
this.dbus = null;
|
|
@@ -252,6 +264,24 @@ export class StatusNotifierItem {
|
|
|
252
264
|
// ------------------------------------------------------------------ setup
|
|
253
265
|
|
|
254
266
|
async start() {
|
|
267
|
+
// **One item per slot at a time.** A hook switched off and on again puts
|
|
268
|
+
// its new item on the old one's path while the old one is still leaving:
|
|
269
|
+
// `announcePassive()` holds its object open for a moment, and its
|
|
270
|
+
// teardown comes after that. dbus-native's `registration.remove()`
|
|
271
|
+
// unexports whatever is at the path when it runs, whoever exported it,
|
|
272
|
+
// so a successor already there would lose its object — and its menu —
|
|
273
|
+
// while the watcher kept its registration: a dead icon. So an item starts
|
|
274
|
+
// once every item before it on the slot has stopped. Every one, not only
|
|
275
|
+
// the last: an item stopped while it was still waiting is gone at once,
|
|
276
|
+
// and the one it was waiting for may not be.
|
|
277
|
+
const before = vacancies.get(this.index);
|
|
278
|
+
const vacated = Promise.all([before, this.gone]);
|
|
279
|
+
vacancies.set(this.index, vacated);
|
|
280
|
+
void vacated.then(() => {
|
|
281
|
+
if (vacancies.get(this.index) === vacated) vacancies.delete(this.index);
|
|
282
|
+
});
|
|
283
|
+
await before;
|
|
284
|
+
|
|
255
285
|
const ref = await sessionBus();
|
|
256
286
|
// No bus is a first-class configuration, not a degraded one: ssh, a bare
|
|
257
287
|
// startx, CI, Node 20 without the transport. There is simply no tray.
|
|
@@ -450,6 +480,8 @@ export class StatusNotifierItem {
|
|
|
450
480
|
*
|
|
451
481
|
* The pair to this is the **stable path** (`slot`): coming back re-registers
|
|
452
482
|
* the same id, which a host dedupes to a reset rather than a second icon.
|
|
483
|
+
* The same path is why the item coming back waits for this hold, and the
|
|
484
|
+
* teardown after it, to finish — see `start()`.
|
|
453
485
|
*/
|
|
454
486
|
async announcePassive() {
|
|
455
487
|
if (!this.exported || !this.iface) return;
|
|
@@ -479,12 +511,18 @@ export class StatusNotifierItem {
|
|
|
479
511
|
}
|
|
480
512
|
|
|
481
513
|
async stop() {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
514
|
+
try {
|
|
515
|
+
// Announced **before** `stopped`, which gates `_sync()` and every other
|
|
516
|
+
// path that could tear the export out from under the signal.
|
|
517
|
+
await this.announcePassive();
|
|
518
|
+
this.stopped = true;
|
|
519
|
+
await this.syncing;
|
|
520
|
+
await this.teardown();
|
|
521
|
+
} finally {
|
|
522
|
+
// Whatever happened, the slot is free: the next item on it would
|
|
523
|
+
// otherwise wait for good, and the icon never come back.
|
|
524
|
+
this.markGone();
|
|
525
|
+
}
|
|
488
526
|
}
|
|
489
527
|
|
|
490
528
|
async teardown() {
|
|
@@ -746,7 +784,11 @@ export class StatusNotifierItem {
|
|
|
746
784
|
}
|
|
747
785
|
}
|
|
748
786
|
|
|
749
|
-
/** Test seam, not public: make paths predictable across test files
|
|
787
|
+
/** Test seam, not public: make paths predictable across test files, and
|
|
788
|
+
* forget who held them. A previous test's items are on a bus it has closed,
|
|
789
|
+
* so there is nothing to wait for, and one it never stopped would otherwise
|
|
790
|
+
* hold its slot for the rest of the run. */
|
|
750
791
|
export function _resetItemIndex() {
|
|
751
792
|
nextItemIndex = 1;
|
|
793
|
+
vacancies.clear();
|
|
752
794
|
}
|
package/src/styles.js
CHANGED
|
@@ -2008,6 +2008,28 @@ function samePosition(a, b) {
|
|
|
2008
2008
|
return true;
|
|
2009
2009
|
}
|
|
2010
2010
|
|
|
2011
|
+
/**
|
|
2012
|
+
* Whether the only properties that differ between two resolved styles are
|
|
2013
|
+
* the insets — `left`, `top` and the rest: a box that moved, and drew
|
|
2014
|
+
* nothing differently (issue #644). Every other key counts, whether layout,
|
|
2015
|
+
* paint or text reads it, so a style that also changed anything else
|
|
2016
|
+
* answers false.
|
|
2017
|
+
*/
|
|
2018
|
+
export function onlyInsetsChanged(props, oldProps = {}) {
|
|
2019
|
+
let moved = false;
|
|
2020
|
+
for (const key of Object.keys(props)) {
|
|
2021
|
+
if (props[key] === oldProps[key]) continue;
|
|
2022
|
+
if (!INSETS.has(key)) return false;
|
|
2023
|
+
moved = true;
|
|
2024
|
+
}
|
|
2025
|
+
for (const key of Object.keys(oldProps)) {
|
|
2026
|
+
if (key in props) continue;
|
|
2027
|
+
if (!INSETS.has(key)) return false;
|
|
2028
|
+
moved = true;
|
|
2029
|
+
}
|
|
2030
|
+
return moved;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2011
2033
|
/** @returns true if any paint-only prop changed */
|
|
2012
2034
|
export function paintPropsChanged(props, oldProps = {}) {
|
|
2013
2035
|
for (const key of PAINT_PROPS) {
|
package/src/svgnodes.js
CHANGED
|
@@ -51,23 +51,57 @@ function svgAttribs(props, skip) {
|
|
|
51
51
|
const value = props[key];
|
|
52
52
|
if (key === 'children' || value == null) continue;
|
|
53
53
|
if (typeof value === 'function') continue;
|
|
54
|
+
// React's, not the drawing's. Since React 19 a ref arrives as an
|
|
55
|
+
// ordinary prop, and an object one — `useRef()` — was written into the
|
|
56
|
+
// document as `ref="[object Object]"`. `key` would be the same if React
|
|
57
|
+
// ever passed it on.
|
|
58
|
+
if (key === 'ref' || key === 'key') continue;
|
|
54
59
|
if (skip && skip(key)) continue;
|
|
55
60
|
attribs[toSvgAttrib(key)] = String(value);
|
|
56
61
|
}
|
|
57
62
|
return attribs;
|
|
58
63
|
}
|
|
59
64
|
|
|
65
|
+
/** Whether two `svgAttribs` results say the same thing. Order aside: the
|
|
66
|
+
* document reads an attribute by its name. */
|
|
67
|
+
function sameAttribs(a, b) {
|
|
68
|
+
const names = Object.keys(a);
|
|
69
|
+
if (names.length !== Object.keys(b).length) return false;
|
|
70
|
+
for (const name of names) {
|
|
71
|
+
if (a[name] !== b[name]) return false;
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Props of the `<svg>` element that are react-x11's rather than the
|
|
77
|
+
// drawing's: `source`, the other form of the content, and what lays the
|
|
78
|
+
// element out, styles it and takes its input. `style` is one of them, an
|
|
79
|
+
// object that reached the document as "[object Object]".
|
|
80
|
+
const notAnAttribute = (key) =>
|
|
81
|
+
key === 'source' ||
|
|
82
|
+
key === 'style' ||
|
|
83
|
+
isLayoutProp(key) ||
|
|
84
|
+
isPaintProp(key) ||
|
|
85
|
+
key === 'cursor' ||
|
|
86
|
+
key === 'focusable' ||
|
|
87
|
+
key === 'pointerEvents';
|
|
88
|
+
|
|
60
89
|
/**
|
|
61
90
|
* An element inside <svg>: no yoga node, no painting of its own — it only
|
|
62
91
|
* carries a tag name and props, and is serialized into the htmlparser2-style
|
|
63
|
-
* DOM that SvgView consumes.
|
|
64
|
-
*
|
|
65
|
-
*
|
|
92
|
+
* DOM that SvgView consumes. A change that reaches that DOM — an attribute
|
|
93
|
+
* that serializes differently, a child added or removed — bubbles to the
|
|
94
|
+
* owning SvgNode via the _textContentChanged channel (also used by text
|
|
95
|
+
* chunks, so string children of an SVG <text> invalidate the same way).
|
|
66
96
|
*/
|
|
67
97
|
export class SvgChildNode extends Node {
|
|
68
98
|
constructor(tag, props, app) {
|
|
69
99
|
super(tag, props, app, { yoga: false });
|
|
70
100
|
this.isSvgChild = true;
|
|
101
|
+
/** What this element writes into the document: serialized once per
|
|
102
|
+
* change rather than once per rebuild, and what a commit is compared
|
|
103
|
+
* against. */
|
|
104
|
+
this._attribs = svgAttribs(props);
|
|
71
105
|
}
|
|
72
106
|
|
|
73
107
|
// <circle cx r fill>… is SVG's vocabulary, not the style channel's
|
|
@@ -81,14 +115,34 @@ export class SvgChildNode extends Node {
|
|
|
81
115
|
|
|
82
116
|
applyProps(newProps, oldProps) {
|
|
83
117
|
super.applyProps(newProps, oldProps);
|
|
118
|
+
// React commits every child of an `<svg>` it re-renders, each with a new
|
|
119
|
+
// props object and almost always with every attribute as it was — and a
|
|
120
|
+
// rebuild re-parses the whole drawing and re-measures the `<svg>`. So
|
|
121
|
+
// only a commit that serializes differently is a change.
|
|
122
|
+
const attribs = svgAttribs(newProps);
|
|
123
|
+
if (sameAttribs(attribs, this._attribs)) return;
|
|
124
|
+
this._attribs = attribs;
|
|
84
125
|
this._textContentChanged();
|
|
85
126
|
}
|
|
86
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Nothing this node draws, whatever changed: the `<svg>` paints the whole
|
|
130
|
+
* document, and a change to it claims that element's box on the way
|
|
131
|
+
* (`SvgNode._textContentChanged`). This node has no box — its rect is an
|
|
132
|
+
* empty one at the window's origin — so core's answer, a claim of that
|
|
133
|
+
* rect whenever a prop is a new value, repainted a corner nothing here
|
|
134
|
+
* drew, on every commit that handed over an attribute rebuilt equal: a
|
|
135
|
+
* `points` or `strokeDasharray` array, say.
|
|
136
|
+
*/
|
|
137
|
+
paintChanged() {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
87
141
|
toDom() {
|
|
88
142
|
return {
|
|
89
143
|
type: 'tag',
|
|
90
144
|
name: this.kind,
|
|
91
|
-
attribs:
|
|
145
|
+
attribs: this._attribs,
|
|
92
146
|
children: this.children
|
|
93
147
|
.map((c) =>
|
|
94
148
|
c.kind === 'textchunk'
|
|
@@ -114,6 +168,9 @@ export class SvgNode extends Node {
|
|
|
114
168
|
super('svg', props, app);
|
|
115
169
|
this.view = null;
|
|
116
170
|
this._stale = true;
|
|
171
|
+
/** This element's own attributes in children form, kept like a child's
|
|
172
|
+
* for the same comparison. */
|
|
173
|
+
this._attribs = svgAttribs(props, notAnAttribute);
|
|
117
174
|
/** Bumped every time the document is rebuilt, and sampled at paint time,
|
|
118
175
|
* so `paintCachePlan` can tell an animated document from a settled one.
|
|
119
176
|
* See the note there. */
|
|
@@ -138,7 +195,9 @@ export class SvgNode extends Node {
|
|
|
138
195
|
);
|
|
139
196
|
}
|
|
140
197
|
|
|
141
|
-
/** Any change to the
|
|
198
|
+
/** Any change to the document — `source`, an attribute, a child, a text
|
|
199
|
+
* chunk — lands here, and only such a change: a commit that serializes
|
|
200
|
+
* the same never calls it, so it rebuilds nothing and claims nothing. */
|
|
142
201
|
_textContentChanged() {
|
|
143
202
|
this._stale = true;
|
|
144
203
|
this.invalidateMeasure('props');
|
|
@@ -155,17 +214,10 @@ export class SvgNode extends Node {
|
|
|
155
214
|
(c) => c.isSvgChild || c.kind === 'textchunk',
|
|
156
215
|
);
|
|
157
216
|
if (children.length > 0) {
|
|
158
|
-
const skip = (key) =>
|
|
159
|
-
key === 'source' ||
|
|
160
|
-
isLayoutProp(key) ||
|
|
161
|
-
isPaintProp(key) ||
|
|
162
|
-
key === 'cursor' ||
|
|
163
|
-
key === 'focusable' ||
|
|
164
|
-
key === 'pointerEvents';
|
|
165
217
|
const dom = {
|
|
166
218
|
type: 'tag',
|
|
167
219
|
name: 'svg',
|
|
168
|
-
attribs:
|
|
220
|
+
attribs: this._attribs,
|
|
169
221
|
children: children.map((c) =>
|
|
170
222
|
c.kind === 'textchunk' ? { type: 'text', data: c.text } : c.toDom(),
|
|
171
223
|
),
|
|
@@ -190,9 +242,18 @@ export class SvgNode extends Node {
|
|
|
190
242
|
}
|
|
191
243
|
|
|
192
244
|
applyProps(newProps, oldProps) {
|
|
245
|
+
const source = this.props.source;
|
|
193
246
|
super.applyProps(newProps, oldProps);
|
|
194
|
-
//
|
|
195
|
-
//
|
|
247
|
+
// The document is `source`, or this element's attributes over its
|
|
248
|
+
// children's, whose changes arrive on their own. A commit that leaves
|
|
249
|
+
// both as they were — most of them: a parent re-rendering, a new style
|
|
250
|
+
// object, a new ref — has nothing to re-parse or re-measure. The style
|
|
251
|
+
// is core's to diff, like any element's.
|
|
252
|
+
const attribs = svgAttribs(newProps, notAnAttribute);
|
|
253
|
+
if (newProps.source === source && sameAttribs(attribs, this._attribs)) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
this._attribs = attribs;
|
|
196
257
|
this._textContentChanged();
|
|
197
258
|
}
|
|
198
259
|
|
package/src/types/elements.d.ts
CHANGED
|
@@ -992,11 +992,12 @@ export type FrameLoop = 'demand' | 'always';
|
|
|
992
992
|
* `<glarea>`: a GL surface in the layout. Its **children** are 2D content
|
|
993
993
|
* drawn above the surface — laid out in its box like a `<box>`'s and cut to
|
|
994
994
|
* it, painted over the GL frame, hit before it — composited on the Cocoa
|
|
995
|
-
* backend
|
|
996
|
-
* asks whether they will be. The
|
|
997
|
-
* node: the handlers it inherits
|
|
998
|
-
* fire at the child under the
|
|
999
|
-
*
|
|
995
|
+
* backend, opaque on X11 (docs/elements.md), and not drawn at all on
|
|
996
|
+
* XQuartz; `useSupports('glOverlay')` asks whether they will be. The
|
|
997
|
+
* pointer over it is the tree's, as over any node: the handlers it inherits
|
|
998
|
+
* (`onMouseDown`, `onClick`, `onWheel`, …) fire at the child under the
|
|
999
|
+
* pointer or the `<glarea>` and bubble, on both backends, and
|
|
1000
|
+
* `GlAreaNode.forwardsPointer` says so at run time.
|
|
1000
1001
|
*/
|
|
1001
1002
|
export interface GlAreaProps extends DrawnProps<DrawnNode> {
|
|
1002
1003
|
/** CSS colour, or `[r, g, b, a]` floats. Default black. */
|