react-x11 2.21.2 → 2.22.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.
- package/package.json +2 -2
- package/src/gloverlay.js +8 -48
- package/src/node.d.ts +11 -1
- package/src/nodes/damage.js +9 -6
- package/src/nodes/invalidate.js +13 -1
- package/src/nodes/layout.js +24 -49
- package/src/nodes/moveblit.js +610 -0
- package/src/nodes/node.js +14 -15
- package/src/nodes/paint.js +103 -8
- package/src/nodes/position.js +1 -0
- package/src/nodes/rects.js +88 -0
- package/src/nodes/roundclip.js +214 -0
- package/src/nodes/scrollable.js +9 -4
- package/src/nodes/scrollbars.js +9 -3
- package/src/nodes/scrollblit.js +232 -40
- package/src/nodes/window/flush.js +12 -0
- package/src/nodes/window/window.js +8 -0
package/src/nodes/scrollblit.js
CHANGED
|
@@ -14,14 +14,17 @@ import {
|
|
|
14
14
|
isIntegerRect,
|
|
15
15
|
insetRect,
|
|
16
16
|
innerPixels,
|
|
17
|
+
outerPixels,
|
|
17
18
|
intersectRects,
|
|
18
19
|
unionRect,
|
|
19
20
|
rectArea,
|
|
20
21
|
cornerSquaresOverlap,
|
|
21
22
|
rectsOverlap,
|
|
23
|
+
outside,
|
|
22
24
|
} from './rects.js';
|
|
23
25
|
import { isPaintedColor } from './boxpaint.js';
|
|
24
26
|
import { scrollbarTrackRect } from './scrollbars.js';
|
|
27
|
+
import { DEV } from './util.js';
|
|
25
28
|
import { debugPaint } from './window/debugpaint.js';
|
|
26
29
|
|
|
27
30
|
// --- scroll blitting (issue #138) ---------------------------------------
|
|
@@ -201,6 +204,86 @@ function unionIsBox(a, b) {
|
|
|
201
204
|
);
|
|
202
205
|
}
|
|
203
206
|
|
|
207
|
+
/** Is `rect` inside one of `pins`, the furniture an element blit pinned —
|
|
208
|
+
* repaired whole after the copy, whatever changed in it (issue #682)? */
|
|
209
|
+
function insidePinned(rect, pins) {
|
|
210
|
+
if (!pins) return false;
|
|
211
|
+
for (const pin of pins) {
|
|
212
|
+
if (rectContains(pin, rect)) return true;
|
|
213
|
+
}
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** One of `scrollContents`' lists as an array, read once — an iterable may
|
|
218
|
+
* be a generator — null when it was left out, and undefined when it is not
|
|
219
|
+
* a list at all. */
|
|
220
|
+
function asList(value) {
|
|
221
|
+
if (value == null) return null;
|
|
222
|
+
return typeof value[Symbol.iterator] === 'function' ? [...value] : undefined;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** The first entry of `list` that `ok` refuses, the value itself when it is
|
|
226
|
+
* not a list, or undefined when there is nothing wrong with it. */
|
|
227
|
+
function strayIn(value, list, ok) {
|
|
228
|
+
if (list === undefined) return value;
|
|
229
|
+
return list?.find((entry) => entry != null && !ok(entry));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const isNode = (value) => typeof value.paintBounds === 'function';
|
|
233
|
+
|
|
234
|
+
const isRect = (value) =>
|
|
235
|
+
Number.isFinite(value.x) &&
|
|
236
|
+
Number.isFinite(value.y) &&
|
|
237
|
+
Number.isFinite(value.width) &&
|
|
238
|
+
Number.isFinite(value.height) &&
|
|
239
|
+
value.width >= 0 &&
|
|
240
|
+
value.height >= 0;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The furniture one `scrollContents` call pins: the whole pixels each rect
|
|
244
|
+
* touches inside `rect`, rounded outward because repairing a pixel more
|
|
245
|
+
* costs nothing and a pixel less is a sliver of stale furniture the pan
|
|
246
|
+
* drags along. A rect beside `rect` has no pixels the copy moves, and is
|
|
247
|
+
* left out, as is an empty one. Null when nothing is left.
|
|
248
|
+
*/
|
|
249
|
+
function pinnedRects(rect, list) {
|
|
250
|
+
let out = null;
|
|
251
|
+
for (const pin of list ?? []) {
|
|
252
|
+
if (pin == null || pin.width === 0 || pin.height === 0) continue;
|
|
253
|
+
const inside = intersectRects(outerPixels(pin), rect);
|
|
254
|
+
if (inside) (out ??= []).push(inside);
|
|
255
|
+
}
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** A value from a caller, short enough for a warning. An object that will
|
|
260
|
+
* not serialize — a node's tree is a cycle — is named by its keys. */
|
|
261
|
+
function describe(value) {
|
|
262
|
+
if (typeof value?.paintBounds === 'function') {
|
|
263
|
+
return `the node <${value.kind}>`;
|
|
264
|
+
}
|
|
265
|
+
// the likeliest mistake of all, from a React component
|
|
266
|
+
if (typeof value?.current?.paintBounds === 'function') {
|
|
267
|
+
return `a ref to <${value.current.kind}> rather than the node, its .current`;
|
|
268
|
+
}
|
|
269
|
+
if (value === null || typeof value !== 'object') return String(value);
|
|
270
|
+
try {
|
|
271
|
+
const json = JSON.stringify(value);
|
|
272
|
+
return json.length > 120 ? `${json.slice(0, 117)}...` : json;
|
|
273
|
+
} catch {
|
|
274
|
+
return `an object with ${Object.keys(value).join(', ') || 'no keys'}`;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const warned = new Set();
|
|
279
|
+
/** A development warning, once per kind of mistake rather than per value:
|
|
280
|
+
* the value is new on every frame of a pan. */
|
|
281
|
+
function warnOnce(key, message) {
|
|
282
|
+
if (!DEV || warned.has(key)) return;
|
|
283
|
+
warned.add(key);
|
|
284
|
+
console.warn(message);
|
|
285
|
+
}
|
|
286
|
+
|
|
204
287
|
/** Node's half of the scroll blit, installed onto `Node.prototype` by node.js. */
|
|
205
288
|
export class NodeScrollBlit {
|
|
206
289
|
/**
|
|
@@ -215,8 +298,10 @@ export class NodeScrollBlit {
|
|
|
215
298
|
* away, so the paths that make them take a finer route while this is true.
|
|
216
299
|
*
|
|
217
300
|
* `scrollContents` is out: an element blit already tests foreign claims
|
|
218
|
-
* against the rect it handed over (issue #309),
|
|
219
|
-
* viewport whose children *are* the scrolled content
|
|
301
|
+
* against the rect it handed over (issue #309), its region is not a
|
|
302
|
+
* viewport whose children *are* the scrolled content, and what changes
|
|
303
|
+
* inside it on a pan frame is furniture the element names up front as
|
|
304
|
+
* pinned (issue #682, `_pinnedHolds`).
|
|
220
305
|
*/
|
|
221
306
|
_blitLedgerOpen() {
|
|
222
307
|
const from = this._pendingBlitFrom;
|
|
@@ -277,6 +362,18 @@ export class NodeScrollBlit {
|
|
|
277
362
|
return true;
|
|
278
363
|
}
|
|
279
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Is `rect` inside furniture this node's pending element blit pinned
|
|
367
|
+
* (issue #682)? A claim there is the element's own and costs the blit
|
|
368
|
+
* nothing: the frame repaints the pinned rect after the copy, and the
|
|
369
|
+
* rect the copy dragged its old pixels to, so the change is covered
|
|
370
|
+
* whichever side of the shift the claim was named on — where the
|
|
371
|
+
* furniture stays, or where the content under it was before it moved.
|
|
372
|
+
*/
|
|
373
|
+
_pinnedHolds(rect) {
|
|
374
|
+
return insidePinned(rect, this._pendingBlitContents?.pinned);
|
|
375
|
+
}
|
|
376
|
+
|
|
280
377
|
/**
|
|
281
378
|
* How far the blit this viewport has pending moves the pixels it keeps:
|
|
282
379
|
* the delta `_applyScrollBlits` hands `scrollRegion`, from the offsets
|
|
@@ -330,12 +427,24 @@ export class NodeScrollBlit {
|
|
|
330
427
|
* moved something — and each of them falls back to repainting the rect,
|
|
331
428
|
* which is the behaviour without this call at all.
|
|
332
429
|
*
|
|
333
|
-
* All of those are about `rect`, not about this node (issue #309)
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
430
|
+
* All of those are about `rect`, not about this node (issue #309): a
|
|
431
|
+
* claim beside the rect leaves the frame a blit, so an element can carve
|
|
432
|
+
* a HUD strip along the pane's edge out of the region it shifts and claim
|
|
433
|
+
* the strip as ordinary damage.
|
|
434
|
+
*
|
|
435
|
+
* Furniture in a corner does better as `pinned` (issue #682): rects inside
|
|
436
|
+
* `rect`, in the same coordinates, that stay put while everything around
|
|
437
|
+
* them moves — a minimap, zoom controls, drawn by the element over its
|
|
438
|
+
* own drawing or laid out over it as nodes. Carved out instead, a corner
|
|
439
|
+
* takes the rows beside it too, since what shifts is one rectangle, and
|
|
440
|
+
* two bottom corners carve a band the pane's full width, repainted every
|
|
441
|
+
* frame. Pinned, the copy drags a stale image of each piece of furniture
|
|
442
|
+
* by (dx, dy), and the frame repaints the pinned rect and that image —
|
|
443
|
+
* two small rects, whether the furniture changed or not, so a minimap's
|
|
444
|
+
* viewport box can follow the pan without claiming a thing. A claim
|
|
445
|
+
* inside a pinned rect is the element's own rather than a reason to
|
|
446
|
+
* decline, and so is a node that lies inside one: its pixels are
|
|
447
|
+
* repaired with the rest of the rect.
|
|
339
448
|
*
|
|
340
449
|
* `riders` are the other way round: nodes that are not this one whose
|
|
341
450
|
* pixels in `rect` are part of what moves — a layer of widgets an element
|
|
@@ -352,7 +461,7 @@ export class NodeScrollBlit {
|
|
|
352
461
|
* arrives as `paintDamage()`, because most of the gates cannot be decided
|
|
353
462
|
* until the frame closes; a `false` here is only the ones that can.
|
|
354
463
|
*/
|
|
355
|
-
scrollContents(rect, dx, dy, riders) {
|
|
464
|
+
scrollContents(rect, dx, dy, riders, pinned) {
|
|
356
465
|
const root = this.root;
|
|
357
466
|
if (
|
|
358
467
|
!root ||
|
|
@@ -367,6 +476,33 @@ export class NodeScrollBlit {
|
|
|
367
476
|
// rounding a gesture to whole pixels lands here on most events.
|
|
368
477
|
if (!dx && !dy) return true;
|
|
369
478
|
const pending = this._pendingBlitContents;
|
|
479
|
+
// Two lists, one of nodes and one of rects, and the cost of mixing them
|
|
480
|
+
// up is not a slow frame but a wrong one: furniture handed over as a
|
|
481
|
+
// rider is dragged along by the pan and repaired by nobody. So each has
|
|
482
|
+
// to be what it says, or the frame repaints.
|
|
483
|
+
const riderList = asList(riders);
|
|
484
|
+
const pinList = asList(pinned);
|
|
485
|
+
const strayRider = strayIn(riders, riderList, isNode);
|
|
486
|
+
const strayPin = strayIn(pinned, pinList, isRect);
|
|
487
|
+
if (strayRider !== undefined) {
|
|
488
|
+
warnOnce(
|
|
489
|
+
'riders',
|
|
490
|
+
'react-x11: scrollContents() takes the nodes a pan carries as a ' +
|
|
491
|
+
`list of riders, and got ${describe(strayRider)}. Furniture that ` +
|
|
492
|
+
'stays put while the pan moves around it is pinned — a list of ' +
|
|
493
|
+
'rects, the argument after riders. This frame repaints the region ' +
|
|
494
|
+
'instead of blitting it.',
|
|
495
|
+
);
|
|
496
|
+
} else if (strayPin !== undefined) {
|
|
497
|
+
warnOnce(
|
|
498
|
+
'pinned',
|
|
499
|
+
'react-x11: scrollContents() takes the furniture it pins as a list ' +
|
|
500
|
+
'of rects — { x, y, width, height }, in window coordinates like ' +
|
|
501
|
+
`the region — and got ${describe(strayPin)}. This frame repaints ` +
|
|
502
|
+
'the region instead of blitting it, since a blit would drag the ' +
|
|
503
|
+
'furniture along with the pan.',
|
|
504
|
+
);
|
|
505
|
+
}
|
|
370
506
|
// The rect has to be the same one all frame: two different regions of
|
|
371
507
|
// one node shifting by different deltas is not one CopyArea, and the
|
|
372
508
|
// second claim would coalesce into the first past the point either can
|
|
@@ -374,12 +510,14 @@ export class NodeScrollBlit {
|
|
|
374
510
|
// offsets this frame — two shifts of the same pixels, and a frame can
|
|
375
511
|
// only have one.
|
|
376
512
|
if (
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
pending
|
|
381
|
-
|
|
382
|
-
|
|
513
|
+
strayRider !== undefined ||
|
|
514
|
+
strayPin !== undefined ||
|
|
515
|
+
(this._pendingBlitFrom != null &&
|
|
516
|
+
(!pending ||
|
|
517
|
+
pending.rect.x !== rect.x ||
|
|
518
|
+
pending.rect.y !== rect.y ||
|
|
519
|
+
pending.rect.width !== rect.width ||
|
|
520
|
+
pending.rect.height !== rect.height))
|
|
383
521
|
) {
|
|
384
522
|
this._pendingBlitFrom = BLIT_POISONED;
|
|
385
523
|
} else if (this._pendingBlitFrom == null && Array.isArray(root._damage)) {
|
|
@@ -394,11 +532,13 @@ export class NodeScrollBlit {
|
|
|
394
532
|
// claim it could swallow has to overlap it, and every claim carries
|
|
395
533
|
// its own slop already — `paintBounds` inflates a node's region by
|
|
396
534
|
// `DAMAGE_SLOP` on every side, so ink that bleeds into `rect` is
|
|
397
|
-
// claimed overlapping it. Furniture *beside* the rect — a
|
|
398
|
-
//
|
|
399
|
-
//
|
|
535
|
+
// claimed overlapping it. Furniture *beside* the rect — a strip the
|
|
536
|
+
// element carved out and repaints itself — is not a change to the
|
|
537
|
+
// pixels about to move, and nor is furniture this call pins inside
|
|
538
|
+
// it (issue #682): the frame repaints that whole, blit or no blit.
|
|
539
|
+
const pins = pinnedRects(rect, pinList);
|
|
400
540
|
for (const claimed of root._damage) {
|
|
401
|
-
if (rectsOverlap(claimed, rect)) {
|
|
541
|
+
if (rectsOverlap(claimed, rect) && !insidePinned(claimed, pins)) {
|
|
402
542
|
this._pendingBlitFrom = BLIT_POISONED;
|
|
403
543
|
break;
|
|
404
544
|
}
|
|
@@ -415,13 +555,22 @@ export class NodeScrollBlit {
|
|
|
415
555
|
// coalescing scrollTo gets from recording an origin
|
|
416
556
|
state.dx += dx;
|
|
417
557
|
state.dy += dy;
|
|
418
|
-
if (
|
|
419
|
-
for (const rider of
|
|
558
|
+
if (strayRider === undefined) {
|
|
559
|
+
for (const rider of riderList ?? []) {
|
|
420
560
|
if (!rider || rider === this || rider.destroyed) continue;
|
|
421
561
|
(state.riders ??= new Set()).add(rider);
|
|
422
562
|
rider._ridesBlitOf = this;
|
|
423
563
|
}
|
|
424
564
|
}
|
|
565
|
+
// …and the furniture of every call this frame: a pan that moved its
|
|
566
|
+
// minimap names both places, and the frame repairs both. Kept in whole
|
|
567
|
+
// pixels inside the rect, once each.
|
|
568
|
+
if (strayPin === undefined) {
|
|
569
|
+
for (const pin of pinnedRects(state.rect, pinList) ?? []) {
|
|
570
|
+
const pins = (state.pinned ??= []);
|
|
571
|
+
if (!pins.some((held) => rectContains(held, pin))) pins.push(pin);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
425
574
|
this._pendingBlitFrom ??= BLIT_CONTENTS;
|
|
426
575
|
(root._pendingScrolls ??= new Set()).add(this);
|
|
427
576
|
// ...and the claim about to be recorded is the shift itself, not a
|
|
@@ -842,10 +991,15 @@ export class WindowScrollBlit {
|
|
|
842
991
|
* the L of exposed strips overlaps the scrollbar rects and the merges
|
|
843
992
|
* balloon back towards the whole viewport. With no bars the L is two
|
|
844
993
|
* disjoint rects and stays two.
|
|
994
|
+
*
|
|
995
|
+
* Furniture the element pinned (issue #682) is the one thing inside the
|
|
996
|
+
* region that does not move: the copy drags a stale image of it along,
|
|
997
|
+
* so each pinned rect is repaired where it is and where the image landed,
|
|
998
|
+
* and a node inside one is furniture too rather than a reason to refuse.
|
|
845
999
|
*/
|
|
846
1000
|
_applyContentsBlit(
|
|
847
1001
|
node,
|
|
848
|
-
{ rect: vp, dx, dy, riders },
|
|
1002
|
+
{ rect: vp, dx, dy, riders, pinned },
|
|
849
1003
|
width,
|
|
850
1004
|
height,
|
|
851
1005
|
target,
|
|
@@ -888,11 +1042,14 @@ export class WindowScrollBlit {
|
|
|
888
1042
|
// A scroll container's children *are* the scrolled content, which is
|
|
889
1043
|
// why _scrollBlitSafe skips that subtree. An element's are not: it
|
|
890
1044
|
// draws the region in paintContent and its children are laid out on
|
|
891
|
-
// top, so one reaching in would have its pixels dragged along
|
|
1045
|
+
// top, so one reaching in would have its pixels dragged along — unless
|
|
1046
|
+
// it lies inside furniture the element pinned, which is repaired after
|
|
1047
|
+
// the copy whoever drew it.
|
|
892
1048
|
for (const child of node.children) {
|
|
893
1049
|
if (child.isWindow || !child.yoga || child.hidden) continue;
|
|
894
1050
|
if (child.style?.display === 'none') continue;
|
|
895
|
-
|
|
1051
|
+
const reach = child._subtreeBounds();
|
|
1052
|
+
if (rectsOverlap(reach, vp) && !insidePinned(reach, pinned)) return;
|
|
896
1053
|
}
|
|
897
1054
|
const keep = this._blitKeptDamage(vp, true, target.damage);
|
|
898
1055
|
if (!keep) return;
|
|
@@ -918,11 +1075,9 @@ export class WindowScrollBlit {
|
|
|
918
1075
|
) {
|
|
919
1076
|
return;
|
|
920
1077
|
}
|
|
921
|
-
if (!this._scrollBlitSafe(node, shifted, target.top, riders))
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
// into request buffers and test comparisons)
|
|
925
|
-
if (!target.blit({ ...shifted }, 0 + dx, 0 + dy)) return;
|
|
1078
|
+
if (!this._scrollBlitSafe(node, shifted, target.top, riders, pinned)) {
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
926
1081
|
let rects = keep;
|
|
927
1082
|
if (bands.top) {
|
|
928
1083
|
rects = addDamageRect(rects, {
|
|
@@ -940,28 +1095,61 @@ export class WindowScrollBlit {
|
|
|
940
1095
|
height: bands.bottom,
|
|
941
1096
|
});
|
|
942
1097
|
}
|
|
943
|
-
vp = shifted;
|
|
944
1098
|
// The strips the shift exposed, on the sides the pixels came from. The
|
|
945
1099
|
// horizontal one takes the full width and the vertical one takes what
|
|
946
1100
|
// is left, so a diagonal shift claims two rects that do not overlap —
|
|
947
1101
|
// overlapping claims merge into their box, and the box of an L is the
|
|
948
1102
|
// whole region again.
|
|
1103
|
+
const strips = [];
|
|
949
1104
|
if (dy !== 0) {
|
|
950
|
-
|
|
951
|
-
x:
|
|
952
|
-
y: dy > 0 ?
|
|
953
|
-
width:
|
|
1105
|
+
strips.push({
|
|
1106
|
+
x: shifted.x,
|
|
1107
|
+
y: dy > 0 ? shifted.y : shifted.y + shifted.height + dy,
|
|
1108
|
+
width: shifted.width,
|
|
954
1109
|
height: Math.abs(dy),
|
|
955
1110
|
});
|
|
956
1111
|
}
|
|
957
1112
|
if (dx !== 0) {
|
|
958
|
-
|
|
959
|
-
x: dx > 0 ?
|
|
960
|
-
y: dy > 0 ?
|
|
1113
|
+
strips.push({
|
|
1114
|
+
x: dx > 0 ? shifted.x : shifted.x + shifted.width + dx,
|
|
1115
|
+
y: dy > 0 ? shifted.y + dy : shifted.y,
|
|
961
1116
|
width: Math.abs(dx),
|
|
962
|
-
height:
|
|
1117
|
+
height: shifted.height - Math.abs(dy),
|
|
963
1118
|
});
|
|
964
1119
|
}
|
|
1120
|
+
for (const strip of strips) rects = addDamageRect(rects, strip);
|
|
1121
|
+
if (pinned) {
|
|
1122
|
+
// Each piece of furniture where it stays, and where the copy put the
|
|
1123
|
+
// image of it it dragged along — one box, since a pan step moves it a
|
|
1124
|
+
// few pixels, and inside what shifted, because the corner bands
|
|
1125
|
+
// repaint whole and the copy wrote nothing outside. Less the strips,
|
|
1126
|
+
// which repaint anyway: furniture flush against the side the pixels
|
|
1127
|
+
// came from would otherwise merge with the strip down that side into
|
|
1128
|
+
// the box of the two, most of the region's height.
|
|
1129
|
+
for (const pin of pinned) {
|
|
1130
|
+
const image = { ...pin, x: pin.x + dx, y: pin.y + dy };
|
|
1131
|
+
const repair = intersectRects(unionRect(pin, image), shifted);
|
|
1132
|
+
if (!repair) continue;
|
|
1133
|
+
let pieces = [repair];
|
|
1134
|
+
for (const strip of strips) {
|
|
1135
|
+
pieces = pieces.flatMap((piece) => outside(piece, strip));
|
|
1136
|
+
}
|
|
1137
|
+
for (const piece of pieces) rects = addDamageRect(rects, piece);
|
|
1138
|
+
}
|
|
1139
|
+
// Furniture can be most of a pane, and the damage cap can merge a
|
|
1140
|
+
// repair with a strip into the box of the two: past this, the blit is
|
|
1141
|
+
// buying a shift and paying for the region anyway.
|
|
1142
|
+
let painted = 0;
|
|
1143
|
+
for (const rect of rects) {
|
|
1144
|
+
const inside = intersectRects(rect, vp);
|
|
1145
|
+
if (inside) painted += rectArea(inside);
|
|
1146
|
+
}
|
|
1147
|
+
if (painted > area * SCROLL_BLIT_MAX_REPAINT) return;
|
|
1148
|
+
}
|
|
1149
|
+
// the element's deltas are already how far the pixels moved, the sense
|
|
1150
|
+
// scrollRegion takes (0 + x rather than x: a caller's -0 would survive
|
|
1151
|
+
// into request buffers and test comparisons)
|
|
1152
|
+
if (!target.blit({ ...shifted }, 0 + dx, 0 + dy)) return;
|
|
965
1153
|
target.damage = rects;
|
|
966
1154
|
}
|
|
967
1155
|
|
|
@@ -1002,7 +1190,7 @@ export class WindowScrollBlit {
|
|
|
1002
1190
|
* only its own children — what the window draws under the surface was
|
|
1003
1191
|
* never in the pane's pixels.
|
|
1004
1192
|
*/
|
|
1005
|
-
_scrollBlitSafe(scroller, vp, top = this, riders = null) {
|
|
1193
|
+
_scrollBlitSafe(scroller, vp, top = this, riders = null, pinned = null) {
|
|
1006
1194
|
// the window itself scrolls: everything inside it *is* the scrolled
|
|
1007
1195
|
// content, so there is nothing that could be dragged along
|
|
1008
1196
|
if (scroller === this) return true;
|
|
@@ -1063,7 +1251,11 @@ export class WindowScrollBlit {
|
|
|
1063
1251
|
// …and a node the blit carries has pixels that move with it
|
|
1064
1252
|
// (`scrollContents`'s riders)
|
|
1065
1253
|
if (riders?.has(child)) continue;
|
|
1066
|
-
|
|
1254
|
+
const reach = child._subtreeBounds();
|
|
1255
|
+
if (!rectsOverlap(reach, vp)) continue;
|
|
1256
|
+
// …and one inside furniture an element blit pinned is repaired
|
|
1257
|
+
// where it is and where the copy dragged it, like the element's own
|
|
1258
|
+
if (insidePinned(reach, pinned)) continue;
|
|
1067
1259
|
// A box that paints nothing of its own is its children, and as
|
|
1068
1260
|
// safe as they are: the box that clips a rider to its element.
|
|
1069
1261
|
if (child._paintsNothingOwn() && check(child)) continue;
|
|
@@ -210,6 +210,8 @@ export class WindowFlush {
|
|
|
210
210
|
if (this._claimLayoutMove(rect, node, cap)) layoutMoved = true;
|
|
211
211
|
};
|
|
212
212
|
}
|
|
213
|
+
// the moves this walk hands over to be copied are this frame's alone
|
|
214
|
+
this._rigidMoves = null;
|
|
213
215
|
try {
|
|
214
216
|
// `_absolutizeChildren`, not the loop it wraps: a `<window
|
|
215
217
|
// style={{overflow: 'scroll'}}>` is a scroll container like any box,
|
|
@@ -279,6 +281,16 @@ export class WindowFlush {
|
|
|
279
281
|
this.window.prepareFrame?.(this, layoutRan);
|
|
280
282
|
// any node this pass laid out may be what an open popup is anchored to
|
|
281
283
|
if (layoutRan) this._notifyAnchorChange();
|
|
284
|
+
// A subtree that only moved and covers its box opaquely is copied where
|
|
285
|
+
// it went, and the frame paints around the copy — or its move is
|
|
286
|
+
// claimed, where it was and where it is (nodes/moveblit.js). Once every
|
|
287
|
+
// other claim of the frame is in: what the copy leaves to paint depends
|
|
288
|
+
// on what else changed around it.
|
|
289
|
+
const moves = layoutRan ? this._rigidMoves : null;
|
|
290
|
+
this._rigidMoves = null;
|
|
291
|
+
if (moves !== null && this._settleRigidMoves(moves, width, height)) {
|
|
292
|
+
layoutMoved = true;
|
|
293
|
+
}
|
|
282
294
|
// after layout (the claims above included), before the damage is taken:
|
|
283
295
|
// a frame that turns out to be a pure scroll blits the surviving band
|
|
284
296
|
// and narrows its claim to the exposed strip
|
|
@@ -18,6 +18,7 @@ import { NO_DAMAGE } from '../damage.js';
|
|
|
18
18
|
import { installMethods } from '../install.js';
|
|
19
19
|
import { WindowInvalidate } from '../invalidate.js';
|
|
20
20
|
import { WindowLayoutHost } from '../layouthost.js';
|
|
21
|
+
import { WindowMoveBlit } from '../moveblit.js';
|
|
21
22
|
import { DEVTOOLS_FAKE_DOCUMENT, Node } from '../node.js';
|
|
22
23
|
import { WindowPaint } from '../paint.js';
|
|
23
24
|
import { WindowPosition } from '../position.js';
|
|
@@ -209,6 +210,12 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
209
210
|
// each claim goes to one or both by what it can reach (`_paneReach`).
|
|
210
211
|
// Null with nothing owed.
|
|
211
212
|
this._paneDamage = null;
|
|
213
|
+
// The claims the window's list took this frame, each with the node that
|
|
214
|
+
// made it — null once there are too many to be worth keeping — and the
|
|
215
|
+
// subtrees this frame's layout moved that the window may copy rather
|
|
216
|
+
// than repaint, settled once the pass is over (nodes/moveblit.js).
|
|
217
|
+
this._claimLog = [];
|
|
218
|
+
this._rigidMoves = null;
|
|
212
219
|
this.events = new EventManager(this);
|
|
213
220
|
// ids of the child windows in the order the *server* stacks them,
|
|
214
221
|
// bottom to top — see _restackWindowChildren
|
|
@@ -979,6 +986,7 @@ installMethods(
|
|
|
979
986
|
WindowPosition,
|
|
980
987
|
WindowInvalidate,
|
|
981
988
|
WindowScrollBlit,
|
|
989
|
+
WindowMoveBlit,
|
|
982
990
|
WindowPaint,
|
|
983
991
|
WindowListeners,
|
|
984
992
|
WindowHints,
|