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
package/src/gloverlay.js
CHANGED
|
@@ -35,30 +35,122 @@
|
|
|
35
35
|
// node-x11's in-process server, where this is tested, has none — and a
|
|
36
36
|
// window as big as the surface would keep a backing pixmap the size of a
|
|
37
37
|
// map to show a legend in its corner.
|
|
38
|
+
// - **XQuartz: no, and not opaquely either.** Every GL surface there, the
|
|
39
|
+
// direct ones Apple-DRI exports and indirect GLX's alike, is a surface of
|
|
40
|
+
// the macOS window server, composited above everything the X server draws
|
|
41
|
+
// in the window (hw/xquartz/xpr/dri.c makes it `XP_MAPPED_ABOVE`). X's
|
|
42
|
+
// stacking never reaches it: a pane stacked over the surface's window is
|
|
43
|
+
// drawn under the frame, cutting that window down with SHAPE leaves the
|
|
44
|
+
// frame covering all of it, and only a pane over the whole surface shows —
|
|
45
|
+
// by hiding the frame (issue #653). So no pane is made there, and
|
|
46
|
+
// `canOverlay` says so from the first render on (`beginGlOverlay`).
|
|
38
47
|
//
|
|
39
48
|
// A pane selects no input, so the pointer over one reaches the owning window
|
|
40
49
|
// by the same propagation that brings it the pointer over the surface
|
|
41
50
|
// (src/glnodes.js, `_create`), and lands on the child it is over.
|
|
42
51
|
//
|
|
52
|
+
// A pane keeps what it holds between frames, so a child that only moved is
|
|
53
|
+
// already painted, one shift away (issue #644). The layout pass reports such
|
|
54
|
+
// a child instead of claiming it (`GlAreaNode._absolutizeChild`), and the
|
|
55
|
+
// frame moves its pixels inside the pane — `scrollRegion`, the verb the
|
|
56
|
+
// scroll blit uses on a window — and repaints only what the move uncovered
|
|
57
|
+
// and what it now overlaps differently (`settleMoves`). A pan over a
|
|
58
|
+
// graph's mounted node bodies is that frame, sixty times a second.
|
|
59
|
+
//
|
|
43
60
|
// A leaf module, like src/embedding.js: `appcontext.js` asks `canOverlay`
|
|
44
|
-
// for `useSupports('glOverlay')`, and it imports nothing of ours
|
|
61
|
+
// for `useSupports('glOverlay')`, and it imports nothing of ours but the
|
|
62
|
+
// damage model's arithmetic, which imports nothing at all.
|
|
45
63
|
import { cssColorStraight } from 'ntk';
|
|
46
64
|
|
|
65
|
+
import { FULL_DAMAGE, addDamageRect } from './nodes/damage.js';
|
|
66
|
+
import {
|
|
67
|
+
innerPixels,
|
|
68
|
+
intersectRects,
|
|
69
|
+
rectArea,
|
|
70
|
+
rectContains,
|
|
71
|
+
unionRect,
|
|
72
|
+
} from './nodes/rects.js';
|
|
73
|
+
|
|
47
74
|
// ConfigureWindow's stack mode: directly above the sibling it names
|
|
48
75
|
const STACK_ABOVE = 0;
|
|
49
76
|
|
|
77
|
+
// Past this share of the pixels a move carries, the frame would repaint
|
|
78
|
+
// most of them anyway — around what it claims itself, or around the other
|
|
79
|
+
// children the copy reached — and the copy only adds to the bill.
|
|
80
|
+
const MOVE_BLIT_MAX_REPAINT = 0.75;
|
|
81
|
+
|
|
82
|
+
// The scroll blit's escape hatch (src/nodes/scrollblit.js), read the same
|
|
83
|
+
// way: every path that moves retained pixels instead of repainting them is
|
|
84
|
+
// one variable away from the plain repaint (docs/debugging.md).
|
|
85
|
+
const NO_BLIT = process.env.REACT_X11_NO_SCROLL_BLIT === '1';
|
|
86
|
+
|
|
87
|
+
// Connections on which nothing can be drawn over a GL surface, each with the
|
|
88
|
+
// reason (`beginGlOverlay`), and the probe that finds out, once per app.
|
|
89
|
+
const refusals = new WeakMap();
|
|
90
|
+
const probes = new WeakMap();
|
|
91
|
+
|
|
92
|
+
const XQUARTZ =
|
|
93
|
+
'this display is XQuartz, where the macOS window server composites every ' +
|
|
94
|
+
'GL surface above everything the X server draws in its window. Draw the ' +
|
|
95
|
+
'overlay beside the surface or in GL instead, or run on the Cocoa backend ' +
|
|
96
|
+
'(the default on macOS), which composites it';
|
|
97
|
+
|
|
50
98
|
/**
|
|
51
99
|
* Whether the children of a `<glarea>` can be drawn over its surface on this
|
|
52
100
|
* connection: a backend that composites a pane itself, or one that can make
|
|
53
|
-
* the plain child window a pane is on X11
|
|
54
|
-
*
|
|
55
|
-
*
|
|
101
|
+
* the plain child window a pane is on X11 — on any server but XQuartz, where
|
|
102
|
+
* nothing can be drawn over a GL surface (`beginGlOverlay`). One function
|
|
103
|
+
* for the element and for `useSupports('glOverlay')`, which have to agree —
|
|
104
|
+
* the rule `canEmbed` follows for `<foreign>`.
|
|
56
105
|
*/
|
|
57
106
|
export function canOverlay(app) {
|
|
58
|
-
return
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
107
|
+
if (typeof app?.createOverlayPane === 'function') return true;
|
|
108
|
+
return typeof app?.createWindow === 'function' && !refusals.has(app);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Why nothing is drawn over a GL surface on this connection, for the
|
|
112
|
+
* warning a `<glarea>` with children gives there — or null. */
|
|
113
|
+
export function overlayRefusal(app) {
|
|
114
|
+
return (app && refusals.get(app)) ?? null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Is this an X server where nothing can be drawn over a GL surface? Asked
|
|
119
|
+
* in `createRoot`, with the other startup probes and before the first
|
|
120
|
+
* render, so that `canOverlay` answers the same from the first frame on —
|
|
121
|
+
* a component choosing how to draw by it would otherwise build one way and
|
|
122
|
+
* tear that down a frame later.
|
|
123
|
+
*
|
|
124
|
+
* The server is XQuartz, and the question is its Apple-DRI extension, which
|
|
125
|
+
* no other server has: every GL surface XQuartz makes comes from the
|
|
126
|
+
* machinery that extension exports, direct and indirect alike. Asked of the
|
|
127
|
+
* server rather than of `app.glCapabilities()`, because the default policy
|
|
128
|
+
* never probes direct rendering and XQuartz's indirect GLX hides the panes
|
|
129
|
+
* all the same. One QueryExtension; where ntk's direct-rendering probe has
|
|
130
|
+
* asked already, node-x11 answers it from the reply it kept.
|
|
131
|
+
*/
|
|
132
|
+
export function beginGlOverlay(app) {
|
|
133
|
+
if (!app) return Promise.resolve();
|
|
134
|
+
let pending = probes.get(app);
|
|
135
|
+
if (pending) return pending;
|
|
136
|
+
const X = app.X;
|
|
137
|
+
pending =
|
|
138
|
+
typeof app.createOverlayPane === 'function' ||
|
|
139
|
+
typeof X?.QueryExtension !== 'function'
|
|
140
|
+
? Promise.resolve()
|
|
141
|
+
: new Promise((resolve) => {
|
|
142
|
+
try {
|
|
143
|
+
X.QueryExtension('Apple-DRI', (err, reply) => {
|
|
144
|
+
if (!err && reply?.present) refusals.set(app, XQUARTZ);
|
|
145
|
+
resolve();
|
|
146
|
+
});
|
|
147
|
+
} catch {
|
|
148
|
+
// a connection that cannot ask is one this cannot find out about
|
|
149
|
+
resolve();
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
probes.set(app, pending);
|
|
153
|
+
return pending;
|
|
62
154
|
}
|
|
63
155
|
|
|
64
156
|
/** A rect grown out to whole device pixels. */
|
|
@@ -73,16 +165,6 @@ function whole(r) {
|
|
|
73
165
|
};
|
|
74
166
|
}
|
|
75
167
|
|
|
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
168
|
const overlaps = (a, b) =>
|
|
87
169
|
a.x < b.x + b.width &&
|
|
88
170
|
b.x < a.x + a.width &&
|
|
@@ -103,6 +185,48 @@ function around(a, b) {
|
|
|
103
185
|
const sameRect = (a, b) =>
|
|
104
186
|
a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
|
105
187
|
|
|
188
|
+
const shifted = (r, dx, dy) => ({
|
|
189
|
+
x: r.x + dx,
|
|
190
|
+
y: r.y + dy,
|
|
191
|
+
width: r.width,
|
|
192
|
+
height: r.height,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
/** `a` with `b` taken out of it: up to four rects — the full-width bands
|
|
196
|
+
* above and below `b`, and the pieces either side of it between them. */
|
|
197
|
+
function subtractRect(a, b) {
|
|
198
|
+
const cut = intersectRects(a, b);
|
|
199
|
+
if (!cut) return [a];
|
|
200
|
+
const out = [];
|
|
201
|
+
const right = a.x + a.width;
|
|
202
|
+
const bottom = a.y + a.height;
|
|
203
|
+
const cutRight = cut.x + cut.width;
|
|
204
|
+
const cutBottom = cut.y + cut.height;
|
|
205
|
+
if (cut.y > a.y) {
|
|
206
|
+
out.push({ x: a.x, y: a.y, width: a.width, height: cut.y - a.y });
|
|
207
|
+
}
|
|
208
|
+
if (cutBottom < bottom) {
|
|
209
|
+
out.push({
|
|
210
|
+
x: a.x,
|
|
211
|
+
y: cutBottom,
|
|
212
|
+
width: a.width,
|
|
213
|
+
height: bottom - cutBottom,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
if (cut.x > a.x) {
|
|
217
|
+
out.push({ x: a.x, y: cut.y, width: cut.x - a.x, height: cut.height });
|
|
218
|
+
}
|
|
219
|
+
if (cutRight < right) {
|
|
220
|
+
out.push({
|
|
221
|
+
x: cutRight,
|
|
222
|
+
y: cut.y,
|
|
223
|
+
width: right - cutRight,
|
|
224
|
+
height: cut.height,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
|
|
106
230
|
/** On screen: nothing from here to the window hidden or `display: 'none'`. */
|
|
107
231
|
function shown(node) {
|
|
108
232
|
for (let n = node; n; n = n.parent) {
|
|
@@ -125,7 +249,7 @@ function shown(node) {
|
|
|
125
249
|
export function overlayRegions(area, surface) {
|
|
126
250
|
const rects = [];
|
|
127
251
|
for (const child of area.paintOrder()) {
|
|
128
|
-
const reach =
|
|
252
|
+
const reach = intersectRects(whole(child._subtreeBounds()), surface);
|
|
129
253
|
if (reach) rects.push(reach);
|
|
130
254
|
}
|
|
131
255
|
for (let merged = true; merged;) {
|
|
@@ -172,6 +296,13 @@ class Pane {
|
|
|
172
296
|
this.transparent = transparent;
|
|
173
297
|
this.full = true;
|
|
174
298
|
this.ctx = null;
|
|
299
|
+
// placed this frame: what the pane holds is where the children were in
|
|
300
|
+
// the pane's own corner, not in the window, so nothing in it is a known
|
|
301
|
+
// shift away from where it goes
|
|
302
|
+
this.placed = false;
|
|
303
|
+
// moved pixels this frame, which has to reach the screen even when no
|
|
304
|
+
// pass follows it (`GlOverlay.paint`)
|
|
305
|
+
this.blitted = false;
|
|
175
306
|
}
|
|
176
307
|
|
|
177
308
|
context() {
|
|
@@ -188,7 +319,12 @@ class Pane {
|
|
|
188
319
|
if (rect.width !== this.rect.width || rect.height !== this.rect.height) {
|
|
189
320
|
this.full = true;
|
|
190
321
|
}
|
|
322
|
+
// pixels a scroll blit moved this frame (nodes/scrollblit.js, which runs
|
|
323
|
+
// before `sync`) were moved in the pane's own corner, and the pane has
|
|
324
|
+
// since gone somewhere else
|
|
325
|
+
if (this.blitted) this.full = true;
|
|
191
326
|
this.rect = rect;
|
|
327
|
+
this.placed = true;
|
|
192
328
|
if (typeof this.wnd.setState === 'function') this.wnd.setState(rect);
|
|
193
329
|
else {
|
|
194
330
|
this.wnd.move?.(rect.x, rect.y);
|
|
@@ -196,6 +332,37 @@ class Pane {
|
|
|
196
332
|
}
|
|
197
333
|
}
|
|
198
334
|
|
|
335
|
+
/** Can the pixels of `rect`, in window coordinates, move inside this
|
|
336
|
+
* pane? Not when it is owed a paint whole or was placed this frame, and
|
|
337
|
+
* not where the backend has no verb for it. */
|
|
338
|
+
canBlit(rect) {
|
|
339
|
+
return (
|
|
340
|
+
!this.full &&
|
|
341
|
+
!this.placed &&
|
|
342
|
+
typeof this.wnd.scrollRegion === 'function' &&
|
|
343
|
+
rectContains(this.rect, rect)
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Move the pixels of `rect` — window coordinates — by (dx, dy) inside the
|
|
349
|
+
* pane, with the window verb's contract (ntk `Window.scrollRegion`, and
|
|
350
|
+
* the Cocoa pane's own): the band that survives inside `rect` moves, the
|
|
351
|
+
* rest of it is left as it was. False when the backend could not, which
|
|
352
|
+
* leaves the pane exactly as it was.
|
|
353
|
+
*/
|
|
354
|
+
blit(rect, dx, dy) {
|
|
355
|
+
const local = {
|
|
356
|
+
x: rect.x - this.rect.x,
|
|
357
|
+
y: rect.y - this.rect.y,
|
|
358
|
+
width: rect.width,
|
|
359
|
+
height: rect.height,
|
|
360
|
+
};
|
|
361
|
+
if (!this.wnd.scrollRegion(local, dx, dy)) return false;
|
|
362
|
+
this.blitted = true;
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
|
|
199
366
|
show(on) {
|
|
200
367
|
if (on) this.wnd.map?.();
|
|
201
368
|
else this.wnd.unmap?.();
|
|
@@ -215,6 +382,10 @@ export class GlOverlay {
|
|
|
215
382
|
// one pane over the whole surface, where the backend composites it
|
|
216
383
|
this.composited = typeof this.app?.createOverlayPane === 'function';
|
|
217
384
|
this.panes = [];
|
|
385
|
+
// the children this frame's layout moved and nothing else, reported
|
|
386
|
+
// rather than claimed (`GlAreaNode._absolutizeChild`) and settled after
|
|
387
|
+
// the panes are (`settleMoves`)
|
|
388
|
+
this.moves = [];
|
|
218
389
|
}
|
|
219
390
|
|
|
220
391
|
/**
|
|
@@ -229,6 +400,7 @@ export class GlOverlay {
|
|
|
229
400
|
sync() {
|
|
230
401
|
const area = this.area;
|
|
231
402
|
const abs = area.abs;
|
|
403
|
+
for (const pane of this.panes) pane.placed = false;
|
|
232
404
|
let rects = [];
|
|
233
405
|
if (shown(area) && abs.width > 0 && abs.height > 0) {
|
|
234
406
|
// the surface's own rect, rounded the way its window's is
|
|
@@ -279,7 +451,7 @@ export class GlOverlay {
|
|
|
279
451
|
pane.full = true;
|
|
280
452
|
const root = this.area.root;
|
|
281
453
|
if (root && !root.destroyed)
|
|
282
|
-
root.invalidate(false, { ...pane.rect }, 'expose');
|
|
454
|
+
root.invalidate(false, { ...pane.rect }, 'expose', this.area);
|
|
283
455
|
}
|
|
284
456
|
|
|
285
457
|
/**
|
|
@@ -305,6 +477,145 @@ export class GlOverlay {
|
|
|
305
477
|
}
|
|
306
478
|
}
|
|
307
479
|
|
|
480
|
+
/**
|
|
481
|
+
* A child the layout pass moved and nothing else — every descendant
|
|
482
|
+
* landed where it was plus (dx, dy) or claimed where it did not — and
|
|
483
|
+
* claimed nothing for (`GlAreaNode._absolutizeChild`). `reach` is its
|
|
484
|
+
* `paintBounds()` from before the move: every pixel of it the panes hold.
|
|
485
|
+
*/
|
|
486
|
+
noteMove(node, reach, dx, dy) {
|
|
487
|
+
this.moves.push({ node, reach, dx, dy });
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* The frame's moves, settled after `sync` has put the panes where the
|
|
492
|
+
* children are and before the frame takes its damage
|
|
493
|
+
* (`WindowNode._syncOverlays`). The one covering the most of the surface
|
|
494
|
+
* moves its pixels on its pane where that is safe and saves the repaint
|
|
495
|
+
* (`_blitMove`); every other one, and that one when not, is claimed where
|
|
496
|
+
* it was and where it is — the pixels the layout diff would have claimed
|
|
497
|
+
* for it and every descendant. True when there was anything to settle.
|
|
498
|
+
*
|
|
499
|
+
* The claims go to the panes' damage alone (`WindowNode._paneDamage`):
|
|
500
|
+
* the window paints none of a surface's children, and its pass under the
|
|
501
|
+
* surface is one nobody sees.
|
|
502
|
+
*/
|
|
503
|
+
settleMoves(root) {
|
|
504
|
+
const moves = this.moves;
|
|
505
|
+
if (moves.length === 0) return false;
|
|
506
|
+
this.moves = [];
|
|
507
|
+
// the panes repaint whole this frame: nothing is owed
|
|
508
|
+
if (!root || root.destroyed || root._paneDamage === FULL_DAMAGE) {
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
const surface = this.area._geometry();
|
|
512
|
+
let best = null;
|
|
513
|
+
let most = 0;
|
|
514
|
+
for (const move of moves) {
|
|
515
|
+
const shown = intersectRects(whole(move.reach), surface);
|
|
516
|
+
const size = shown ? rectArea(shown) : 0;
|
|
517
|
+
if (size > most) {
|
|
518
|
+
best = move;
|
|
519
|
+
most = size;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
// the others first: the pixels they claim are ones the copy may carry,
|
|
523
|
+
// and `_blitMove` repairs whatever the frame claims by then
|
|
524
|
+
for (const move of moves) if (move !== best) this._claimMove(root, move);
|
|
525
|
+
if (best && !this._blitMove(root, best)) this._claimMove(root, best);
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/** Where a move's child was and where it is, claimed the ordinary way —
|
|
530
|
+
* cut to the surface's box, which clips the children (`clipsChildren`). */
|
|
531
|
+
_claimMove(root, { node, reach }) {
|
|
532
|
+
const box = this.area.abs;
|
|
533
|
+
for (const rect of [reach, node.paintBounds()]) {
|
|
534
|
+
const claim = intersectRects(rect, box);
|
|
535
|
+
if (claim) root._addPaneDamage(claim);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Move a child's pixels on its pane, and narrow what the frame owes the
|
|
541
|
+
* move to what the copy cannot supply. True when it did; false leaves
|
|
542
|
+
* the pane and the frame's damage as they were, for `_claimMove`.
|
|
543
|
+
*
|
|
544
|
+
* The copy is of what the child showed, moved as far as the child moved
|
|
545
|
+
* and kept where it is still on show (`dest`). That is right wherever
|
|
546
|
+
* the child's own drawing is the only thing that changed — and the frame
|
|
547
|
+
* repaints the rest:
|
|
548
|
+
*
|
|
549
|
+
* - what the child uncovered, and whatever of its new reach the copy did
|
|
550
|
+
* not land on: its reach before and after, less `dest`;
|
|
551
|
+
* - the other children: the pixels of theirs the copy carried off with
|
|
552
|
+
* the child's, and theirs that the copy covered — over the child or
|
|
553
|
+
* under it, their own pixels are not a shift away;
|
|
554
|
+
* - what the panes are owed already, where the copy would carry it: a
|
|
555
|
+
* claim is often a rect named before layout ran, and when it was
|
|
556
|
+
* inside the child its content went with the copy. Repainted shifted
|
|
557
|
+
* as well as where it stands, which is right whichever it was. A
|
|
558
|
+
* claim that cannot reach the panes is not in their list
|
|
559
|
+
* (`WindowNode._paneReach`) — a graph pane under the surface that
|
|
560
|
+
* claims itself whole on every step of the pan this copy is.
|
|
561
|
+
*
|
|
562
|
+
* Past `MOVE_BLIT_MAX_REPAINT` of `dest` those repaint most of what the
|
|
563
|
+
* copy saves, and the plain claims have the frame.
|
|
564
|
+
*/
|
|
565
|
+
_blitMove(root, { node, reach, dx, dy }) {
|
|
566
|
+
if (NO_BLIT || !Number.isInteger(dx) || !Number.isInteger(dy)) {
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
const area = this.area;
|
|
570
|
+
// a rounded surface clips its children to an arc, and the arc stays put
|
|
571
|
+
if ((area.style?.borderRadius ?? 0) > 0) return false;
|
|
572
|
+
// the whole pixels of the surface's box: a fractional edge is an
|
|
573
|
+
// antialiased clip, and that does not move either
|
|
574
|
+
const box = innerPixels(area.abs);
|
|
575
|
+
if (!box) return false;
|
|
576
|
+
const was = intersectRects(whole(reach), box);
|
|
577
|
+
const now = intersectRects(whole(node.paintBounds()), box);
|
|
578
|
+
// nothing of it was on show, so there is nothing to carry
|
|
579
|
+
if (!was) return false;
|
|
580
|
+
const pane = this.panes.find(
|
|
581
|
+
(p) => p.canBlit(was) && (!now || p.canBlit(now)),
|
|
582
|
+
);
|
|
583
|
+
if (!pane) return false;
|
|
584
|
+
const view = intersectRects(pane.rect, box);
|
|
585
|
+
const dest = intersectRects(shifted(was, dx, dy), view);
|
|
586
|
+
if (!dest) return false;
|
|
587
|
+
const cap = root._damageRectCap();
|
|
588
|
+
const claimed = root._paneDamage ?? [];
|
|
589
|
+
let rects = claimed;
|
|
590
|
+
const add = (rect) => {
|
|
591
|
+
if (rect) rects = addDamageRect(rects, rect, cap);
|
|
592
|
+
};
|
|
593
|
+
for (const claim of claimed)
|
|
594
|
+
add(intersectRects(shifted(claim, dx, dy), dest));
|
|
595
|
+
for (const piece of subtractRect(was, dest)) add(piece);
|
|
596
|
+
if (now) for (const piece of subtractRect(now, dest)) add(piece);
|
|
597
|
+
for (const other of area.paintOrder()) {
|
|
598
|
+
if (other === node) continue;
|
|
599
|
+
const theirs = whole(other.paintBounds());
|
|
600
|
+
add(intersectRects(theirs, dest));
|
|
601
|
+
add(intersectRects(shifted(theirs, dx, dy), dest));
|
|
602
|
+
}
|
|
603
|
+
// the list is disjoint, so the sum is the area it covers
|
|
604
|
+
let repainted = 0;
|
|
605
|
+
for (const rect of rects) {
|
|
606
|
+
const inside = intersectRects(rect, dest);
|
|
607
|
+
if (inside) repainted += rectArea(inside);
|
|
608
|
+
}
|
|
609
|
+
if (repainted > rectArea(dest) * MOVE_BLIT_MAX_REPAINT) return false;
|
|
610
|
+
// the verb moves the band that survives inside the rect it is handed,
|
|
611
|
+
// so handed where the pixels are and where they go, that band is `dest`
|
|
612
|
+
if (!pane.blit(unionRect(shifted(dest, -dx, -dy), dest), dx, dy)) {
|
|
613
|
+
return false;
|
|
614
|
+
}
|
|
615
|
+
root._paneDamage = rects;
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
|
|
308
619
|
/**
|
|
309
620
|
* Paint what the frame owes: every pane whole after it was made, resized
|
|
310
621
|
* or lost its pixels, and otherwise the frame's damage cut to each pane —
|
|
@@ -323,10 +634,17 @@ export class GlOverlay {
|
|
|
323
634
|
} else {
|
|
324
635
|
passes = [];
|
|
325
636
|
for (const rect of damage) {
|
|
326
|
-
const hit =
|
|
637
|
+
const hit = intersectRects(rect, pane.rect);
|
|
327
638
|
if (hit) passes.push(hit);
|
|
328
639
|
}
|
|
329
|
-
|
|
640
|
+
}
|
|
641
|
+
const blitted = pane.blitted;
|
|
642
|
+
pane.blitted = false;
|
|
643
|
+
pane.placed = false;
|
|
644
|
+
if (passes.length === 0) {
|
|
645
|
+
// pixels moved and nothing else owed: they still go out
|
|
646
|
+
if (blitted) pane.wnd.present?.();
|
|
647
|
+
continue;
|
|
330
648
|
}
|
|
331
649
|
pane.full = false;
|
|
332
650
|
this._paintPane(pane, passes);
|
package/src/index.d.ts
CHANGED
|
@@ -216,7 +216,10 @@ export function useClipboard(): Clipboard;
|
|
|
216
216
|
* before it. Both backends draw them; translucency is the difference,
|
|
217
217
|
* composited by Core Animation on Cocoa and opaque on X11, where what a
|
|
218
218
|
* child leaves unpainted shows the surface's `clearColor` (docs/elements.md).
|
|
219
|
-
*
|
|
219
|
+
* False on XQuartz, whose GL surfaces the macOS window server composites
|
|
220
|
+
* above everything the X server draws: nothing can be drawn over one there.
|
|
221
|
+
* Ask it before handing a surface its HUD. A property of the backend and the
|
|
222
|
+
* display, settled before the first render.
|
|
220
223
|
*/
|
|
221
224
|
export type SupportsFeature =
|
|
222
225
|
'transparency' | 'shaders' | 'nativeControls' | 'embedding' | 'glOverlay';
|
package/src/nodes/animation.js
CHANGED
|
@@ -215,6 +215,7 @@ export class NodeAnimation {
|
|
|
215
215
|
false,
|
|
216
216
|
insetRect(this.paintBounds(), -shrank),
|
|
217
217
|
'shadow',
|
|
218
|
+
this,
|
|
218
219
|
);
|
|
219
220
|
}
|
|
220
221
|
}
|
|
@@ -235,6 +236,7 @@ export class NodeAnimation {
|
|
|
235
236
|
false,
|
|
236
237
|
insetRect(this.paintBounds(), -shrank),
|
|
237
238
|
'outline',
|
|
239
|
+
this,
|
|
238
240
|
);
|
|
239
241
|
}
|
|
240
242
|
}
|
package/src/nodes/damage.js
CHANGED
|
@@ -49,6 +49,14 @@ export const layoutDiff = {
|
|
|
49
49
|
// diff reports only the children that landed somewhere else. Null everywhere
|
|
50
50
|
// else, and restored through `finally` like the sink beside it.
|
|
51
51
|
shift: null,
|
|
52
|
+
|
|
53
|
+
// True when `shift` is a `<glarea>` child's own move rather than a scroll
|
|
54
|
+
// (src/glnodes.js, `_absolutizeChild`, issue #644): the overlay moves the
|
|
55
|
+
// child's pixels on its pane, and a node that lands where it was plus the
|
|
56
|
+
// shift is carried by that. A node that turns the diff off under a shift —
|
|
57
|
+
// a scroll pane laid out again, whose box would have claimed its children
|
|
58
|
+
// — claims its own box instead, since the move claimed nothing for it.
|
|
59
|
+
ride: false,
|
|
52
60
|
};
|
|
53
61
|
|
|
54
62
|
// What an invalidate() may name as its reason — a small closed set, so the
|