react-x11 2.22.4 → 2.22.5
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/nodes/layout.js +80 -0
- package/src/nodes/node.js +43 -7
- package/src/nodes/scrollblit.js +31 -5
package/package.json
CHANGED
package/src/nodes/layout.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { Yoga } from '../yoga.js';
|
|
6
6
|
import { callHandler } from '../errors.js';
|
|
7
7
|
import { DAMAGE_SLOP, layoutDiff } from './damage.js';
|
|
8
|
+
import { resolveBorderWidths } from '../styles.js';
|
|
8
9
|
import { insetRect, intersectRects, outerPixels, outside } from './rects.js';
|
|
9
10
|
import { DEV } from './util.js';
|
|
10
11
|
|
|
@@ -520,12 +521,33 @@ export class NodeLayout {
|
|
|
520
521
|
) {
|
|
521
522
|
return;
|
|
522
523
|
}
|
|
524
|
+
// …and one that rode it and only grew or shrank changed the pixels
|
|
525
|
+
// along the edges that moved — the column a virtualized document's
|
|
526
|
+
// blocks sit in, whose spacers re-slice in the scroll's own frame
|
|
527
|
+
const bands =
|
|
528
|
+
had && was.x === x && was.y === y && width > 0 && height > 0
|
|
529
|
+
? this._edgeBands(was, grow)
|
|
530
|
+
: null;
|
|
531
|
+
if (bands) {
|
|
532
|
+
for (const band of bands) layoutDiff.sink(band, this);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
523
535
|
if (had) layoutDiff.sink(insetRect(was, -grow), this);
|
|
524
536
|
if (width > 0 && height > 0) {
|
|
525
537
|
layoutDiff.sink(insetRect(this.abs, -grow), this);
|
|
526
538
|
}
|
|
527
539
|
return;
|
|
528
540
|
}
|
|
541
|
+
// A box that stayed where it was and only grew or shrank changed the
|
|
542
|
+
// pixels along the edges that moved, and no others (`_edgeBands`)
|
|
543
|
+
const bands =
|
|
544
|
+
had && old.x === x && old.y === y && width > 0 && height > 0
|
|
545
|
+
? this._edgeBands(old, grow)
|
|
546
|
+
: null;
|
|
547
|
+
if (bands) {
|
|
548
|
+
for (const band of bands) layoutDiff.sink(band, this);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
529
551
|
if (had) {
|
|
530
552
|
layoutDiff.sink(insetRect(old, -grow), this);
|
|
531
553
|
}
|
|
@@ -535,6 +557,64 @@ export class NodeLayout {
|
|
|
535
557
|
}
|
|
536
558
|
}
|
|
537
559
|
|
|
560
|
+
/**
|
|
561
|
+
* The pixels a resize changed, for a box whose origin held: a band along
|
|
562
|
+
* each edge that moved, from where it was to where it is, as deep as the
|
|
563
|
+
* box's paint reaches in from that edge — a rounded corner, a border, a
|
|
564
|
+
* shadow's or an outline's reach. Its own paint anywhere else is the same
|
|
565
|
+
* solid fill it was, and its children claim for themselves when they move.
|
|
566
|
+
*
|
|
567
|
+
* A tall box that grows at its far end is the case: a document or a log
|
|
568
|
+
* or a virtualized list whose column gained a block far below the fold
|
|
569
|
+
* claimed the column, clipped to its scroll box — the whole viewport — on
|
|
570
|
+
* every frame it grew, for pixels that were all out of sight.
|
|
571
|
+
*
|
|
572
|
+
* Null where the whole box has to be claimed: anything that is not a
|
|
573
|
+
* plain box, one that scrolls (its bars are sized by it), one whose
|
|
574
|
+
* gradient is stretched over it, and one drawn through a group surface.
|
|
575
|
+
*/
|
|
576
|
+
_edgeBands(old, grow) {
|
|
577
|
+
if (this.kind !== 'box' || this.isScroller?.()) return null;
|
|
578
|
+
const style = this.style;
|
|
579
|
+
if (style.backgroundImage) return null;
|
|
580
|
+
if (style.opacity !== undefined && style.opacity < 1) return null;
|
|
581
|
+
const bw = resolveBorderWidths(style, this.direction);
|
|
582
|
+
const reach =
|
|
583
|
+
Math.max(
|
|
584
|
+
style.borderRadius ?? 0,
|
|
585
|
+
bw.top,
|
|
586
|
+
bw.right,
|
|
587
|
+
bw.bottom,
|
|
588
|
+
bw.left,
|
|
589
|
+
grow,
|
|
590
|
+
) + DAMAGE_SLOP;
|
|
591
|
+
const now = this.abs;
|
|
592
|
+
const bands = [];
|
|
593
|
+
const oldBottom = old.y + old.height;
|
|
594
|
+
const newBottom = now.y + now.height;
|
|
595
|
+
if (oldBottom !== newBottom) {
|
|
596
|
+
const top = Math.min(oldBottom, newBottom) - reach;
|
|
597
|
+
bands.push({
|
|
598
|
+
x: now.x - reach,
|
|
599
|
+
y: top,
|
|
600
|
+
width: Math.max(old.width, now.width) + 2 * reach,
|
|
601
|
+
height: Math.max(oldBottom, newBottom) + reach - top,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
const oldRight = old.x + old.width;
|
|
605
|
+
const newRight = now.x + now.width;
|
|
606
|
+
if (oldRight !== newRight) {
|
|
607
|
+
const left = Math.min(oldRight, newRight) - reach;
|
|
608
|
+
bands.push({
|
|
609
|
+
x: left,
|
|
610
|
+
y: now.y - reach,
|
|
611
|
+
width: Math.max(oldRight, newRight) + reach - left,
|
|
612
|
+
height: Math.max(old.height, now.height) + 2 * reach,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
return bands;
|
|
616
|
+
}
|
|
617
|
+
|
|
538
618
|
/**
|
|
539
619
|
* Move an already-laid-out subtree by a constant, without asking yoga
|
|
540
620
|
* anything — the scroll fast path's walk (issue #405).
|
package/src/nodes/node.js
CHANGED
|
@@ -384,8 +384,10 @@ export class Node {
|
|
|
384
384
|
// captured before the child joins, so it covers the arrangement that is
|
|
385
385
|
// about to be replaced (see _childListChanged). A viewport mid-blit has
|
|
386
386
|
// nothing vacating — the child being added had no pixels — and the
|
|
387
|
-
// layout diff claims where it lands, so it names no region at all.
|
|
388
|
-
|
|
387
|
+
// layout diff claims where it lands, so it names no region at all. A
|
|
388
|
+
// child already here is a move, and says so (`_childListFine`).
|
|
389
|
+
const fine = this._childListFine(child.parent !== this);
|
|
390
|
+
const before = fine ? null : this._childListBefore();
|
|
389
391
|
// a move has to leave the yoga tree too — yoga aborts on insertChild of
|
|
390
392
|
// a node that still has a parent
|
|
391
393
|
if (child.parent === this && this._joinsYoga(child)) {
|
|
@@ -414,7 +416,7 @@ export class Node {
|
|
|
414
416
|
// against the app's, and only now can see the `scale` props above it.
|
|
415
417
|
child._rescaleSubtree(mounting);
|
|
416
418
|
this._textContentChanged();
|
|
417
|
-
this._childListChanged(before);
|
|
419
|
+
this._childListChanged(before, fine);
|
|
418
420
|
a11yHooks.attached?.(this, child);
|
|
419
421
|
}
|
|
420
422
|
|
|
@@ -464,7 +466,7 @@ export class Node {
|
|
|
464
466
|
* through the layout diff in flush(), which is what lets this claim stay
|
|
465
467
|
* bounded without requiring the node's own size to be pinned.
|
|
466
468
|
*/
|
|
467
|
-
_childListChanged(before) {
|
|
469
|
+
_childListChanged(before, fine = this._childListFine(true)) {
|
|
468
470
|
// belt for a subtree attached imperatively with its rect already laid
|
|
469
471
|
// out — nothing then re-runs _assignAbs to notice the reach grew
|
|
470
472
|
this._clearHitBounds();
|
|
@@ -477,7 +479,7 @@ export class Node {
|
|
|
477
479
|
// and says nothing about the ones that only rode the scroll. Joining
|
|
478
480
|
// `_reflowed` would undo both — its post-layout claim is this node's
|
|
479
481
|
// box, the whole band the blit is about to move.
|
|
480
|
-
if (
|
|
482
|
+
if (fine) {
|
|
481
483
|
root.invalidate(true, before ?? NO_DAMAGE, 'child-list', this);
|
|
482
484
|
return;
|
|
483
485
|
}
|
|
@@ -485,6 +487,39 @@ export class Node {
|
|
|
485
487
|
root._reflowed.add(this);
|
|
486
488
|
}
|
|
487
489
|
|
|
490
|
+
/**
|
|
491
|
+
* Whether a child-list change here is claimed the fine way — the child
|
|
492
|
+
* that leaves claims its own rect, and the layout diff names where an
|
|
493
|
+
* entering child lands and whatever its arrival displaced — rather than
|
|
494
|
+
* as this node's box before and after the pass.
|
|
495
|
+
*
|
|
496
|
+
* Always for a viewport keeping a ledger this frame (issue #398). And,
|
|
497
|
+
* for a child that enters or leaves (`entersOrLeaves`), for any node
|
|
498
|
+
* inside a scroll box, scrolling or not: a document's blocks, the rows
|
|
499
|
+
* of a virtualized list one box down from its scroll box, a log that
|
|
500
|
+
* grows. Such a node is usually taller than the view, and its box
|
|
501
|
+
* clipped to the viewport is the whole viewport — so every re-slice of
|
|
502
|
+
* a list inside a column, all of it below the fold, repainted the view,
|
|
503
|
+
* and one landing in a scroll's frame poisoned its blit. The diff runs
|
|
504
|
+
* over every node that pass laid out, however deep, and a node that
|
|
505
|
+
* only grew claims the edge that moved (`_edgeBands`).
|
|
506
|
+
*
|
|
507
|
+
* Not for a move: a child moved within the list keeps its rect when
|
|
508
|
+
* what changed is only which sibling it is painted over, and no rect
|
|
509
|
+
* says that. Nor for a scroll box's own children outside a ledger frame:
|
|
510
|
+
* its box is also the claim that repaints its bars, which its content
|
|
511
|
+
* sizes.
|
|
512
|
+
*/
|
|
513
|
+
_childListFine(entersOrLeaves) {
|
|
514
|
+
if (this._blitLedgerOpen()) return true;
|
|
515
|
+
if (!entersOrLeaves) return false;
|
|
516
|
+
for (let n = this.parent; n; n = n.parent) {
|
|
517
|
+
if (n.isWindow) return false;
|
|
518
|
+
if (n.isScroller?.()) return true;
|
|
519
|
+
}
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
|
|
488
523
|
removeChild(child) {
|
|
489
524
|
const index = this._indexOfChild(child);
|
|
490
525
|
if (index === -1) return;
|
|
@@ -494,7 +529,8 @@ export class Node {
|
|
|
494
529
|
// captured while the child is still attached, so it covers the rect the
|
|
495
530
|
// child is about to stop occupying — the child's own, for a viewport
|
|
496
531
|
// mid-blit, where this node's box is the whole scrolled band
|
|
497
|
-
const
|
|
532
|
+
const fine = this._childListFine(true);
|
|
533
|
+
const before = fine
|
|
498
534
|
? (child._claimBounds() ?? NO_DAMAGE)
|
|
499
535
|
: this._childListBefore();
|
|
500
536
|
this.children.splice(index, 1);
|
|
@@ -515,7 +551,7 @@ export class Node {
|
|
|
515
551
|
child.yoga = null;
|
|
516
552
|
}
|
|
517
553
|
this._textContentChanged();
|
|
518
|
-
this._childListChanged(before);
|
|
554
|
+
this._childListChanged(before, fine);
|
|
519
555
|
}
|
|
520
556
|
|
|
521
557
|
/** Destroy real resources (X windows) in this subtree. Yoga nodes are
|
package/src/nodes/scrollblit.js
CHANGED
|
@@ -855,7 +855,15 @@ export class WindowScrollBlit {
|
|
|
855
855
|
? unionArea([strip, ...repairs]) - rectArea(strip)
|
|
856
856
|
: 0;
|
|
857
857
|
if (repairArea > area * BLIT_MAX_CLAIM_AREA) return;
|
|
858
|
-
|
|
858
|
+
// An ancestor's rounded corners reach into the viewport: a scroll box
|
|
859
|
+
// filling a rounded card, the commonest place a scroll box is. Their
|
|
860
|
+
// arcs do not translate, but they live in four small squares and
|
|
861
|
+
// nowhere else, so they are pinned the way an element blit pins them
|
|
862
|
+
// (issue #691) and repaired below, where they are and where the copy
|
|
863
|
+
// dragged their image. Refused instead, a list or a document inside a
|
|
864
|
+
// rounded frame repainted its whole viewport on every step of a scroll.
|
|
865
|
+
const corners = this._cornerPins(node, vp, target.top);
|
|
866
|
+
if (!this._scrollBlitSafe(node, vp, target.top, null, corners)) return;
|
|
859
867
|
// The band the scrolled bar's thumb travels in is repaired on its own.
|
|
860
868
|
// The thumb's rects are thin and run along the viewport's edge, and a
|
|
861
869
|
// rect reaching into the band from inside — a column held down the
|
|
@@ -932,10 +940,28 @@ export class WindowScrollBlit {
|
|
|
932
940
|
if (dragged) add(dragged);
|
|
933
941
|
}
|
|
934
942
|
for (const repair of repairs) add(repair);
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
)
|
|
943
|
+
// Each pinned corner where it is and where the copy put its image — one
|
|
944
|
+
// box, since a step moves it a few pixels, and less the strip, which
|
|
945
|
+
// repaints anyway.
|
|
946
|
+
for (const pin of corners ?? []) {
|
|
947
|
+
const image = { ...pin, x: pin.x + dx, y: pin.y + dy };
|
|
948
|
+
const repair = intersectRects(unionRect(pin, image), vp);
|
|
949
|
+
if (!repair) continue;
|
|
950
|
+
for (const piece of outside(repair, strip)) add(piece);
|
|
951
|
+
}
|
|
952
|
+
// Capped in one call where corners were pinned: two at each end of the
|
|
953
|
+
// viewport and the strip at one of them are more pieces than the cap
|
|
954
|
+
// holds, and squeezed under it one at a time a corner joined whatever
|
|
955
|
+
// box an earlier merge had grown — scrolling back up merged the bottom
|
|
956
|
+
// corners with the strip at the top, which is the viewport. Offered
|
|
957
|
+
// together, neighbours merge first (`addDamageRects`), with the room an
|
|
958
|
+
// element blit's pins get.
|
|
959
|
+
rects = corners
|
|
960
|
+
? addDamageRects([], joinAlongEdges(rects), CONTENTS_BLIT_MAX_RECTS)
|
|
961
|
+
: joinAlongEdges(rects).reduce(
|
|
962
|
+
(capped, rect) => addDamageRect(capped, rect),
|
|
963
|
+
[],
|
|
964
|
+
);
|
|
939
965
|
// The last gate, and the only one that has to wait until the rects are
|
|
940
966
|
// assembled: damage rects must not overlap, and the frame carries at
|
|
941
967
|
// most MAX_DAMAGE_RECTS of them, so repairs that meet — a column and a
|