react-x11 2.22.3 → 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/animation.js +1 -0
- package/src/nodes/damage.js +7 -0
- package/src/nodes/invalidate.js +9 -2
- package/src/nodes/layout.js +98 -11
- package/src/nodes/node.js +43 -7
- package/src/nodes/queries.js +1 -0
- package/src/nodes/scrollable.js +53 -15
- package/src/nodes/scrollblit.js +31 -5
- package/src/nodes/window/exactcopy.js +65 -0
- package/src/nodes/window/floors.js +76 -2
- package/src/nodes/window/flush.js +5 -0
- package/src/nodes/window/size.js +185 -35
- package/src/nodes/window/spine.js +294 -0
- package/src/nodes/window/window.js +7 -0
- package/src/styles.js +28 -0
package/package.json
CHANGED
package/src/nodes/animation.js
CHANGED
|
@@ -491,6 +491,7 @@ export class NodeAnimation {
|
|
|
491
491
|
this.root.needsLayout = true;
|
|
492
492
|
this.root._floorsDirty = true;
|
|
493
493
|
this.root._floorsSources.add(this);
|
|
494
|
+
this.root._floorsOtherSources.add(this);
|
|
494
495
|
}
|
|
495
496
|
}
|
|
496
497
|
// A tick writes `this.style` without going through `_retarget`, so it
|
package/src/nodes/damage.js
CHANGED
|
@@ -59,6 +59,13 @@ export const layoutDiff = {
|
|
|
59
59
|
// would have claimed its children — claims its own box instead, since the
|
|
60
60
|
// move claimed nothing for it.
|
|
61
61
|
ride: false,
|
|
62
|
+
|
|
63
|
+
// Whether this walk may leave alone a subtree yoga did not reach under a
|
|
64
|
+
// parent that did not move (`Node._followParent`) — everything in it is
|
|
65
|
+
// where the last walk put it. False on a frame something scrolled on, or
|
|
66
|
+
// asked to: a scroll moves boxes without a pass, and the walk is what
|
|
67
|
+
// carries the offset down to them (`WindowNode._walkWhole`).
|
|
68
|
+
skipUnreached: false,
|
|
62
69
|
};
|
|
63
70
|
|
|
64
71
|
// What an invalidate() may name as its reason — a small closed set, so the
|
package/src/nodes/invalidate.js
CHANGED
|
@@ -309,6 +309,11 @@ export class WindowInvalidate {
|
|
|
309
309
|
const windowToo = reach !== PANES_ONLY;
|
|
310
310
|
if (layoutChanged) {
|
|
311
311
|
this.needsLayout = true;
|
|
312
|
+
// What the next walk may skip (`layoutDiff.skipUnreached`): nothing,
|
|
313
|
+
// after a scroll or a change that names no node.
|
|
314
|
+
if (reason === 'scroll' || !source || source === this) {
|
|
315
|
+
this._walkWhole = true;
|
|
316
|
+
}
|
|
312
317
|
// The content floors are measured from the tree, so anything that
|
|
313
318
|
// changed it has to give them up — and **scrolling does not**, which is
|
|
314
319
|
// the whole reason this is not just `needsLayout`: a scroll moves an
|
|
@@ -325,8 +330,10 @@ export class WindowInvalidate {
|
|
|
325
330
|
// …and which node changed, so that a change inside a box that sizes
|
|
326
331
|
// itself measures that box and nothing around it (`_floorsScope`).
|
|
327
332
|
// A claim that names no node, or names the window, is the tree's.
|
|
328
|
-
if (source && source !== this)
|
|
329
|
-
|
|
333
|
+
if (source && source !== this) {
|
|
334
|
+
this._floorsSources.add(source);
|
|
335
|
+
if (reason !== 'child-list') this._floorsOtherSources.add(source);
|
|
336
|
+
} else this._floorsUnscoped = true;
|
|
330
337
|
}
|
|
331
338
|
if (reason !== 'scroll' && reason !== 'resize' && reason !== 'position') {
|
|
332
339
|
this._floorsContentDirty = true;
|
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
|
|
|
@@ -228,22 +229,29 @@ export class NodeLayout {
|
|
|
228
229
|
}
|
|
229
230
|
|
|
230
231
|
/**
|
|
231
|
-
* A child
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
232
|
+
* A child yoga did not lay out again: its box and everything under it
|
|
233
|
+
* sit exactly where the last pass put them relative to their parents, so
|
|
234
|
+
* the whole subtree moves by however far its parent did (`dx`, `dy`) —
|
|
235
|
+
* `_shiftAbs`, the scroll fast path's translation (issue #405), instead
|
|
236
|
+
* of the walk — and where its parent did not move, it has not moved at
|
|
237
|
+
* all, and there is nothing to do, unless something scrolled since the
|
|
238
|
+
* last walk (`layoutDiff.skipUnreached`): a scroll moves boxes with no
|
|
239
|
+
* pass, and it is the walk that carries the new offset down to them.
|
|
240
|
+
* Answers whether it was handled; false means walk it.
|
|
237
241
|
*
|
|
238
242
|
* The witness is yoga's has-new-layout flag, the one that fast path
|
|
239
243
|
* reads: a pass sets it on every node it lays out and on every child of
|
|
240
244
|
* one, cached or not, so a child it is clear on was not reached, nor was
|
|
241
245
|
* anything under it. Cleared here when it is found set, just before the
|
|
242
246
|
* walk that reads what the pass left — no pass runs in between — so the
|
|
243
|
-
* next
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
247
|
+
* next look at the same subtree finds it clear. A scroll pane is always
|
|
248
|
+
* walked, since its flag is its own (and a scroll moves its children
|
|
249
|
+
* with no pass at all).
|
|
250
|
+
*
|
|
251
|
+
* Walking a subtree nothing moved was the whole tree for a change in one
|
|
252
|
+
* place: a block that arrived at the end of a document of 13,000 boxes
|
|
253
|
+
* was laid out alone, and the walk still read four numbers out of yoga
|
|
254
|
+
* for every box in the document — 30 ms of a 60 ms frame.
|
|
247
255
|
*
|
|
248
256
|
* Walking such a subtree read four numbers out of yoga per node and wrote
|
|
249
257
|
* them back unchanged: the bodies of a graph's cards, fifty widgets deep,
|
|
@@ -255,13 +263,13 @@ export class NodeLayout {
|
|
|
255
263
|
* walk's claims came to.
|
|
256
264
|
*/
|
|
257
265
|
_followParent(dx, dy) {
|
|
258
|
-
if (dx === 0 && dy === 0) return false;
|
|
259
266
|
const yoga = this.yoga;
|
|
260
267
|
if (!yoga || !this._placed || this.isScroller?.()) return false;
|
|
261
268
|
if (yoga.hasNewLayout()) {
|
|
262
269
|
yoga.markLayoutSeen();
|
|
263
270
|
return false;
|
|
264
271
|
}
|
|
272
|
+
if (dx === 0 && dy === 0) return layoutDiff.skipUnreached;
|
|
265
273
|
const sink = layoutDiff.sink;
|
|
266
274
|
const shift = layoutDiff.shift;
|
|
267
275
|
if (
|
|
@@ -513,12 +521,33 @@ export class NodeLayout {
|
|
|
513
521
|
) {
|
|
514
522
|
return;
|
|
515
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
|
+
}
|
|
516
535
|
if (had) layoutDiff.sink(insetRect(was, -grow), this);
|
|
517
536
|
if (width > 0 && height > 0) {
|
|
518
537
|
layoutDiff.sink(insetRect(this.abs, -grow), this);
|
|
519
538
|
}
|
|
520
539
|
return;
|
|
521
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
|
+
}
|
|
522
551
|
if (had) {
|
|
523
552
|
layoutDiff.sink(insetRect(old, -grow), this);
|
|
524
553
|
}
|
|
@@ -528,6 +557,64 @@ export class NodeLayout {
|
|
|
528
557
|
}
|
|
529
558
|
}
|
|
530
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
|
+
|
|
531
618
|
/**
|
|
532
619
|
* Move an already-laid-out subtree by a constant, without asking yoga
|
|
533
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/queries.js
CHANGED
package/src/nodes/scrollable.js
CHANGED
|
@@ -30,6 +30,56 @@ import { BLIT_POISONED } from './scrollblit.js';
|
|
|
30
30
|
|
|
31
31
|
const clampScroll = (v, max) => Math.min(Math.max(0, v), max);
|
|
32
32
|
|
|
33
|
+
/**
|
|
34
|
+
* How far everything under `node` reaches from its origin — the leftmost
|
|
35
|
+
* edge, the rightmost, the lowest — through every box that does not clip
|
|
36
|
+
* what it holds: the walk `measureScrollContent` sums a pane's extent from.
|
|
37
|
+
*
|
|
38
|
+
* Kept on each box (`_contentReach`) and reused while yoga has laid nothing
|
|
39
|
+
* under it out again. Its has-new-layout flag is set on every box a pass
|
|
40
|
+
* lays out and on each child of one, and a change anywhere below dirties
|
|
41
|
+
* every box above it, so a clear flag is a subtree whose boxes sit where
|
|
42
|
+
* they did relative to it — the same witness `_followParent` reads, read
|
|
43
|
+
* here before the walk that clears it. Hiding a box, or clipping one, goes
|
|
44
|
+
* through yoga too (`display`, `overflow`). A layout host places its
|
|
45
|
+
* children where yoga cannot see, so neither it nor they keep one.
|
|
46
|
+
*
|
|
47
|
+
* Walked whole, a pane's content cost every box in it on every pass that
|
|
48
|
+
* laid out any of it: a block arriving at the end of a document of 13,000
|
|
49
|
+
* boxes read three numbers out of yoga for each of them.
|
|
50
|
+
*/
|
|
51
|
+
function contentReach(node, top) {
|
|
52
|
+
const cached = node._contentReach;
|
|
53
|
+
if (
|
|
54
|
+
!top &&
|
|
55
|
+
cached !== undefined &&
|
|
56
|
+
node._host === null &&
|
|
57
|
+
node.parent?._host === null &&
|
|
58
|
+
!node.yoga.hasNewLayout()
|
|
59
|
+
) {
|
|
60
|
+
return cached;
|
|
61
|
+
}
|
|
62
|
+
let left = Infinity;
|
|
63
|
+
let right = -Infinity;
|
|
64
|
+
let bottom = -Infinity;
|
|
65
|
+
for (const child of node.children) {
|
|
66
|
+
if (child.isWindow || !child.yoga || child.hidden) continue;
|
|
67
|
+
const offset = offsetInParent(child);
|
|
68
|
+
left = Math.min(left, offset.x);
|
|
69
|
+
right = Math.max(right, offset.x + child.yoga.getComputedWidth());
|
|
70
|
+
bottom = Math.max(bottom, offset.y + child.yoga.getComputedHeight());
|
|
71
|
+
if (!child.clipsChildren()) {
|
|
72
|
+
const inner = contentReach(child, false);
|
|
73
|
+
left = Math.min(left, offset.x + inner.left);
|
|
74
|
+
right = Math.max(right, offset.x + inner.right);
|
|
75
|
+
bottom = Math.max(bottom, offset.y + inner.bottom);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const reach = { left, right, bottom };
|
|
79
|
+
if (!top) node._contentReach = reach;
|
|
80
|
+
return reach;
|
|
81
|
+
}
|
|
82
|
+
|
|
33
83
|
/**
|
|
34
84
|
* One axis of a scroll request held for a pass
|
|
35
85
|
* (`Scrollable._holdScrollTo`): `{ base, steps }`, the request the extent in
|
|
@@ -435,21 +485,9 @@ export const Scrollable = (Base) =>
|
|
|
435
485
|
measureScrollContent() {
|
|
436
486
|
const rtl = this.direction === 'rtl';
|
|
437
487
|
const width = this.yoga.getComputedWidth();
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
const
|
|
441
|
-
for (const child of node.children) {
|
|
442
|
-
if (child.isWindow || !child.yoga || child.hidden) continue;
|
|
443
|
-
const offset = offsetInParent(child);
|
|
444
|
-
const x = dx + offset.x;
|
|
445
|
-
const y = dy + offset.y;
|
|
446
|
-
const w = child.yoga.getComputedWidth();
|
|
447
|
-
start = Math.max(start, rtl ? width - x : x + w);
|
|
448
|
-
bottom = Math.max(bottom, y + child.yoga.getComputedHeight());
|
|
449
|
-
if (!child.clipsChildren()) walk(child, x, y);
|
|
450
|
-
}
|
|
451
|
-
};
|
|
452
|
-
walk(this, 0, 0);
|
|
488
|
+
const reach = contentReach(this, true);
|
|
489
|
+
const start = Math.max(0, rtl ? width - reach.left : reach.right);
|
|
490
|
+
const bottom = Math.max(0, reach.bottom);
|
|
453
491
|
// the end padding is part of the content box a browser scrolls to, and
|
|
454
492
|
// it is the one part of it yoga has already resolved for us — on the
|
|
455
493
|
// left in RTL, since that is the end there
|
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
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Measuring a box alone without unsettling the tree around it.
|
|
2
|
+
//
|
|
3
|
+
// The renderer lays everything out in one yoga config, rounded to the pixel
|
|
4
|
+
// grid, and a measurement switches the rounding off (`measuringExactly`,
|
|
5
|
+
// styles.js), which yoga answers by treating every layout it has cached as
|
|
6
|
+
// stale: the pass after any measurement lays the whole tree out again. On a
|
|
7
|
+
// document of 13,000 boxes that pass is 58 ms, where one yoga answers from
|
|
8
|
+
// its cache is 0.05 ms. A measurement over the whole tree pays it anyway,
|
|
9
|
+
// having just laid everything out three times; a box measured alone — a
|
|
10
|
+
// card (`_measureBoundary`), a block on a column spine
|
|
11
|
+
// (`_measureSpineRoot`) — is a few dozen boxes, and made the pass after it
|
|
12
|
+
// cost the whole tree.
|
|
13
|
+
//
|
|
14
|
+
// So a box measured alone is measured on copies: its boxes and everything
|
|
15
|
+
// under them, copied into a config that is never rounded
|
|
16
|
+
// (`exactLayoutConfig`), laid out there, read, and freed. The config the tree
|
|
17
|
+
// is laid out in never changes, and yoga's record of the real boxes — their
|
|
18
|
+
// caches, what is dirty — is not touched.
|
|
19
|
+
|
|
20
|
+
import { Yoga } from '../../yoga.js';
|
|
21
|
+
import { exactLayoutConfig, measuringInExactConfig } from '../../styles.js';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Run `measure` with `root` and everything under it standing on copies of
|
|
25
|
+
* their boxes: every node's `yoga` is its copy for the duration, so the
|
|
26
|
+
* floors code measures, reads and writes extents exactly as it would on the
|
|
27
|
+
* real boxes.
|
|
28
|
+
*
|
|
29
|
+
* What a measurement writes on a node stays — its extents. What it writes on
|
|
30
|
+
* a box — a floor taken off to measure what is under it, a shrink it
|
|
31
|
+
* borrows, a width it pins — goes with the copy, so the floors' record of
|
|
32
|
+
* what each real box holds (`_floorMinW`/`_floorMinH`) is put back, and the
|
|
33
|
+
* floors written after the measurement are judged against the real box.
|
|
34
|
+
*/
|
|
35
|
+
export function onExactCopy(root, measure) {
|
|
36
|
+
const swapped = [];
|
|
37
|
+
const copy = (node) => {
|
|
38
|
+
const real = node.yoga;
|
|
39
|
+
const box = Yoga.Node.create(exactLayoutConfig());
|
|
40
|
+
box.copyStyle(real);
|
|
41
|
+
if (node._measureFn) box.setMeasureFunc(node._measureFn);
|
|
42
|
+
swapped.push({ node, real, minW: node._floorMinW, minH: node._floorMinH });
|
|
43
|
+
let at = 0;
|
|
44
|
+
// the same children yoga has (`Node._joinsYoga`)
|
|
45
|
+
if (node._host === null) {
|
|
46
|
+
for (const child of node.children) {
|
|
47
|
+
if (!child.yoga || child.isWindow) continue;
|
|
48
|
+
box.insertChild(copy(child), at++);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
node.yoga = box;
|
|
52
|
+
return box;
|
|
53
|
+
};
|
|
54
|
+
const top = copy(root);
|
|
55
|
+
try {
|
|
56
|
+
return measuringInExactConfig(measure);
|
|
57
|
+
} finally {
|
|
58
|
+
for (const { node, real, minW, minH } of swapped) {
|
|
59
|
+
node.yoga = real;
|
|
60
|
+
node._floorMinW = minW;
|
|
61
|
+
node._floorMinH = minH;
|
|
62
|
+
}
|
|
63
|
+
top.freeRecursive();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -94,7 +94,7 @@ export function inFlow(node) {
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/** Which axis this node lays its children out along. */
|
|
97
|
-
const mainAxisOf = (node) => {
|
|
97
|
+
export const mainAxisOf = (node) => {
|
|
98
98
|
const direction = node.style.flexDirection ?? 'column';
|
|
99
99
|
return direction === 'row' || direction === 'row-reverse'
|
|
100
100
|
? 'width'
|
|
@@ -107,7 +107,7 @@ const mainAxisOf = (node) => {
|
|
|
107
107
|
* content. The same test `writeFloors` applies, asked ahead of time — it is
|
|
108
108
|
* what decides whether a stale extent is one anybody will read.
|
|
109
109
|
*/
|
|
110
|
-
function receivesFloor(child, axis) {
|
|
110
|
+
export function receivesFloor(child, axis) {
|
|
111
111
|
const parent = child.parent;
|
|
112
112
|
if (
|
|
113
113
|
!parent ||
|
|
@@ -279,6 +279,80 @@ export function dirtyOutside(node, boundaries, paths) {
|
|
|
279
279
|
return false;
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
/**
|
|
283
|
+
* `contentSpan`'s height for a column, from the extents its children
|
|
284
|
+
* already carry rather than from a measuring pass — the sum a column spine
|
|
285
|
+
* (`spine.js`) is recomputed with, bottom up, where a pass would lay the
|
|
286
|
+
* whole tree out to read it.
|
|
287
|
+
*
|
|
288
|
+
* It is the same number. In a measuring pass `contentSpan` places each
|
|
289
|
+
* child at the sum of the extents before it (its `shift`), so what it reads
|
|
290
|
+
* back is every in-flow child's extent, the margins *between* them — the
|
|
291
|
+
* first child's leading margin and the last one's trailing margin fall
|
|
292
|
+
* outside the span, which runs from the first box to the last — the gap
|
|
293
|
+
* between each pair, and the column's own padding and border. That holds
|
|
294
|
+
* for a column whose height is its content's, which is the only kind a
|
|
295
|
+
* spine sums (`isSummableColumn`): with no height of its own the pass has
|
|
296
|
+
* no free space to justify into, and nothing in it to squash.
|
|
297
|
+
*
|
|
298
|
+
* Undefined where it cannot be summed: a child with no extent on hand, a
|
|
299
|
+
* negative margin (the boxes would overlap, and the last would not have to
|
|
300
|
+
* be the lowest), or nothing in flow at all — a leaf's answer, which is
|
|
301
|
+
* `contentSpan`'s to give.
|
|
302
|
+
*/
|
|
303
|
+
export function columnHeightSpan(node) {
|
|
304
|
+
const yoga = node.yoga;
|
|
305
|
+
let span = 0;
|
|
306
|
+
let count = 0;
|
|
307
|
+
let trailing = 0;
|
|
308
|
+
for (const child of node.children) {
|
|
309
|
+
if (!inFlow(child)) continue;
|
|
310
|
+
const extent = child._floorH;
|
|
311
|
+
if (extent === undefined) return undefined;
|
|
312
|
+
const top = marginDown(child.yoga, Yoga.EDGE_TOP);
|
|
313
|
+
const bottom = marginDown(child.yoga, Yoga.EDGE_BOTTOM);
|
|
314
|
+
if (!(top >= 0 && bottom >= 0)) return undefined;
|
|
315
|
+
span += count === 0 ? extent : trailing + top + extent;
|
|
316
|
+
trailing = bottom;
|
|
317
|
+
count += 1;
|
|
318
|
+
}
|
|
319
|
+
if (count === 0) return undefined;
|
|
320
|
+
const gap = columnGap(node);
|
|
321
|
+
if (gap === undefined) return undefined;
|
|
322
|
+
return (
|
|
323
|
+
span +
|
|
324
|
+
gap * (count - 1) +
|
|
325
|
+
yoga.getComputedPadding(Yoga.EDGE_TOP) +
|
|
326
|
+
yoga.getComputedPadding(Yoga.EDGE_BOTTOM) +
|
|
327
|
+
yoga.getComputedBorder(Yoga.EDGE_TOP) +
|
|
328
|
+
yoga.getComputedBorder(Yoga.EDGE_BOTTOM)
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* A box's margin on one edge down the page, from its style as yoga resolves
|
|
334
|
+
* it — the edge, else the vertical pair, else all four — rather than from a
|
|
335
|
+
* pass: a box that has just arrived has never been laid out in the tree, and
|
|
336
|
+
* its computed margins read 0 until it is. Undefined where it is not a
|
|
337
|
+
* length (a percentage, `auto`).
|
|
338
|
+
*/
|
|
339
|
+
function marginDown(yoga, edge) {
|
|
340
|
+
for (const at of [edge, Yoga.EDGE_VERTICAL, Yoga.EDGE_ALL]) {
|
|
341
|
+
const value = yoga.getMargin(at);
|
|
342
|
+
if (value.unit === Yoga.UNIT_POINT) return value.value;
|
|
343
|
+
if (value.unit !== Yoga.UNIT_UNDEFINED) return undefined;
|
|
344
|
+
}
|
|
345
|
+
return 0;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** The gap a column leaves between its rows, as yoga resolves it — the row
|
|
349
|
+
* gutter, else the both-ways one — or undefined where it is not a length. */
|
|
350
|
+
function columnGap(node) {
|
|
351
|
+
const style = node.style;
|
|
352
|
+
const gap = style.rowGap ?? style.gap ?? 0;
|
|
353
|
+
return typeof gap === 'number' ? gap : undefined;
|
|
354
|
+
}
|
|
355
|
+
|
|
282
356
|
/**
|
|
283
357
|
* After a pass that settled the widths: which of the height floors, each a
|
|
284
358
|
* height *for a width*, were measured for a width their node no longer has.
|
|
@@ -212,6 +212,9 @@ export class WindowFlush {
|
|
|
212
212
|
}
|
|
213
213
|
// the moves this walk hands over to be copied are this frame's alone
|
|
214
214
|
this._rigidMoves = null;
|
|
215
|
+
// a subtree no pass reached and nothing scrolled in is where the last
|
|
216
|
+
// walk left it (`_followParent`)
|
|
217
|
+
layoutDiff.skipUnreached = !this._walkWhole;
|
|
215
218
|
try {
|
|
216
219
|
// `_absolutizeChildren`, not the loop it wraps: a `<window
|
|
217
220
|
// style={{overflow: 'scroll'}}>` is a scroll container like any box,
|
|
@@ -219,7 +222,9 @@ export class WindowFlush {
|
|
|
219
222
|
this._absolutizeChildren(0, 0);
|
|
220
223
|
} finally {
|
|
221
224
|
layoutDiff.sink = null;
|
|
225
|
+
layoutDiff.skipUnreached = false;
|
|
222
226
|
}
|
|
227
|
+
this._walkWhole = false;
|
|
223
228
|
// …and placed nodes against the arrangement that walk produced: where
|
|
224
229
|
// one goes depends on where its pane and its parent landed
|
|
225
230
|
if (this._placedNodes.size !== 0) this._placeNodes();
|
package/src/nodes/window/size.js
CHANGED
|
@@ -8,9 +8,12 @@ import { Yoga } from '../../yoga.js';
|
|
|
8
8
|
import {
|
|
9
9
|
captureLeafHeights,
|
|
10
10
|
collectFloorStale,
|
|
11
|
+
columnHeightSpan,
|
|
12
|
+
declaresOwnMinimum,
|
|
11
13
|
dirtyOutside,
|
|
12
14
|
floorBoundaryOf,
|
|
13
15
|
probeHeightFloors,
|
|
16
|
+
receivesFloor,
|
|
14
17
|
setMeasuringShrink,
|
|
15
18
|
restoreShrink,
|
|
16
19
|
freezeWidths,
|
|
@@ -18,6 +21,8 @@ import {
|
|
|
18
21
|
contentSpan,
|
|
19
22
|
writeFloors,
|
|
20
23
|
} from './floors.js';
|
|
24
|
+
import { spineScope } from './spine.js';
|
|
25
|
+
import { onExactCopy } from './exactcopy.js';
|
|
21
26
|
import {
|
|
22
27
|
MAX_WINDOW_EXTENT,
|
|
23
28
|
clampExtent,
|
|
@@ -36,18 +41,14 @@ import {
|
|
|
36
41
|
// REACT_X11_NO_SCROLL_BLIT gives.
|
|
37
42
|
const NO_SCOPED_FLOORS = process.env.REACT_X11_NO_SCOPED_FLOORS === '1';
|
|
38
43
|
|
|
39
|
-
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
function relayOut(node) {
|
|
48
|
-
const shrink = node.style.flexShrink ?? 1;
|
|
49
|
-
node.yoga.setFlexShrink(shrink + 1);
|
|
50
|
-
node.yoga.setFlexShrink(shrink);
|
|
44
|
+
/** Take a node's extents off, as `collectFloorStale` does a dirty child's:
|
|
45
|
+
* both are measured again, and the minimum yoga holds may no longer be
|
|
46
|
+
* the one the floors wrote. */
|
|
47
|
+
function forgetExtents(node) {
|
|
48
|
+
node._floorW = undefined;
|
|
49
|
+
node._floorH = undefined;
|
|
50
|
+
node._floorMinW = null;
|
|
51
|
+
node._floorMinH = null;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
/** Content-sized windows, installed onto `WindowNode.prototype` by window.js. */
|
|
@@ -132,12 +133,43 @@ export class WindowSize {
|
|
|
132
133
|
const scope = this._floorsScope();
|
|
133
134
|
this._floorsScopeNow = scope;
|
|
134
135
|
if (scope !== null) {
|
|
136
|
+
const spine = this._floorsSpineNow;
|
|
135
137
|
for (const boundary of scope) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
+
if (spine?.roots.has(boundary)) {
|
|
139
|
+
// A spine's root is measured with what is inside it, unlike a box
|
|
140
|
+
// that sizes itself, and its column writes the floor it gets.
|
|
141
|
+
forgetExtents(boundary);
|
|
142
|
+
if (receivesFloor(boundary, 'width')) found.width = true;
|
|
143
|
+
if (receivesFloor(boundary, 'height')) found.height = true;
|
|
144
|
+
this._floorsStale.add(boundary.parent);
|
|
145
|
+
}
|
|
146
|
+
// its children's floors are written from here; a sized box's own
|
|
147
|
+
// extent is its style's, and stands
|
|
138
148
|
this._floorsStale.add(boundary);
|
|
139
149
|
collectFloorStale(boundary, this._floorsStale, found, false);
|
|
140
150
|
}
|
|
151
|
+
if (spine !== null) {
|
|
152
|
+
// the columns the roots sit in are summed again (`_sumSpine`), and
|
|
153
|
+
// every one of them writes its children's floors, as the box each
|
|
154
|
+
// spine stops at does
|
|
155
|
+
for (const column of spine.spine) {
|
|
156
|
+
column._floorW = undefined;
|
|
157
|
+
column._floorH = undefined;
|
|
158
|
+
this._floorsStale.add(column);
|
|
159
|
+
if (receivesFloor(column, 'height')) found.height = true;
|
|
160
|
+
}
|
|
161
|
+
for (const stop of spine.stops) this._floorsStale.add(stop);
|
|
162
|
+
// How wide a root is in the width pass is worked out on the way
|
|
163
|
+
// down from the window (spine.js), and past a row only a pass over
|
|
164
|
+
// the whole tree knows. A width owed there is the whole tree's.
|
|
165
|
+
if (found.width) {
|
|
166
|
+
for (const widths of spine.roots.values()) {
|
|
167
|
+
if (widths.widthPass !== null) continue;
|
|
168
|
+
this._floorsUnscoped = true;
|
|
169
|
+
return this._collectFloorStale();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
141
173
|
return found;
|
|
142
174
|
}
|
|
143
175
|
// the root's own children are written from here too, and its direction
|
|
@@ -189,16 +221,37 @@ export class WindowSize {
|
|
|
189
221
|
) {
|
|
190
222
|
return null;
|
|
191
223
|
}
|
|
224
|
+
this._floorsSpineNow = null;
|
|
192
225
|
const boundaries = new Set();
|
|
226
|
+
// the changes no sized box holds, which a column spine may (spine.js)
|
|
227
|
+
const loose = [];
|
|
193
228
|
for (const source of sources) {
|
|
194
229
|
// gone, or moved to another window: its old parent's child list is
|
|
195
230
|
// the change that counts, and that parent said so itself
|
|
196
231
|
if (source.destroyed || source.root !== this) continue;
|
|
197
232
|
const boundary = floorBoundaryOf(source, this);
|
|
198
|
-
if (boundary === null)
|
|
199
|
-
boundaries.add(boundary);
|
|
233
|
+
if (boundary === null) loose.push(source);
|
|
234
|
+
else boundaries.add(boundary);
|
|
235
|
+
}
|
|
236
|
+
let spine = null;
|
|
237
|
+
if (loose.length !== 0) {
|
|
238
|
+
spine = spineScope(
|
|
239
|
+
this,
|
|
240
|
+
loose,
|
|
241
|
+
(node) => !this._floorsOtherSources.has(node),
|
|
242
|
+
);
|
|
243
|
+
if (spine === null) return null;
|
|
244
|
+
// a sized box inside a root is measured with the root
|
|
245
|
+
for (const boundary of [...boundaries]) {
|
|
246
|
+
for (let n = boundary.parent; n && n !== this; n = n.parent) {
|
|
247
|
+
if (spine.roots.has(n)) {
|
|
248
|
+
boundaries.delete(boundary);
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
200
253
|
}
|
|
201
|
-
if (boundaries.size === 0) return null;
|
|
254
|
+
if (boundaries.size === 0 && spine === null) return null;
|
|
202
255
|
// the outermost of nested ones: measuring it measures what it holds
|
|
203
256
|
const paths = new Set();
|
|
204
257
|
for (const boundary of [...boundaries]) {
|
|
@@ -214,10 +267,94 @@ export class WindowSize {
|
|
|
214
267
|
paths.add(n);
|
|
215
268
|
}
|
|
216
269
|
}
|
|
270
|
+
if (spine !== null) {
|
|
271
|
+
for (const root of spine.roots.keys()) {
|
|
272
|
+
boundaries.add(root);
|
|
273
|
+
for (let n = root.parent; n && n !== this; n = n.parent) paths.add(n);
|
|
274
|
+
}
|
|
275
|
+
// the columns summed, and the stops with the way down to them: a
|
|
276
|
+
// child that left one is a change to it, and nothing below it
|
|
277
|
+
for (const column of spine.spine) paths.add(column);
|
|
278
|
+
for (const stop of spine.stops) {
|
|
279
|
+
for (let n = stop; n && n !== this; n = n.parent) paths.add(n);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
217
282
|
if (dirtyOutside(this, boundaries, paths)) return null;
|
|
283
|
+
this._floorsSpineNow = spine;
|
|
218
284
|
return boundaries;
|
|
219
285
|
}
|
|
220
286
|
|
|
287
|
+
/**
|
|
288
|
+
* One spine root's floors, measured with it laid out alone — at the width
|
|
289
|
+
* it has in each of the passes over the whole tree (`spine.js`), and with
|
|
290
|
+
* no height on offer in either height pass, because down a column whose
|
|
291
|
+
* height is its content's the pass over the whole tree has none to offer
|
|
292
|
+
* it either. Its own extents are written here: unlike a box that sizes
|
|
293
|
+
* itself, a root's are its content's, and the column it sits in is summed
|
|
294
|
+
* from them (`_sumSpine`).
|
|
295
|
+
*/
|
|
296
|
+
_measureSpineRoot(root, widths, axis, probe = false) {
|
|
297
|
+
onExactCopy(root, () =>
|
|
298
|
+
this._measureSpineRootOnCopy(root, widths, axis, probe),
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
_measureSpineRootOnCopy(root, widths, axis, probe) {
|
|
303
|
+
const yoga = root.yoga;
|
|
304
|
+
const dir =
|
|
305
|
+
root.direction === 'rtl' ? Yoga.DIRECTION_RTL : Yoga.DIRECTION_LTR;
|
|
306
|
+
if (axis === 'width') {
|
|
307
|
+
const shrunk = [];
|
|
308
|
+
setMeasuringShrink(root, axis, shrunk);
|
|
309
|
+
this._scopedFloorPasses += 1;
|
|
310
|
+
yoga.calculateLayout(widths.widthPass, undefined, dir);
|
|
311
|
+
const span = contentSpan(root, axis, null, this);
|
|
312
|
+
root._floorW = declaresOwnMinimum(root, axis)
|
|
313
|
+
? yoga.getComputedWidth()
|
|
314
|
+
: span;
|
|
315
|
+
restoreShrink(shrunk);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
this._scopedFloorPasses += 1;
|
|
319
|
+
yoga.calculateLayout(widths.width, undefined, dir);
|
|
320
|
+
if (probe) {
|
|
321
|
+
const hit = { marked: false, owed: false };
|
|
322
|
+
probeHeightFloors(root, this, hit, root);
|
|
323
|
+
if (hit.marked) {
|
|
324
|
+
this._writeFloors('height');
|
|
325
|
+
this._scopedFloorPasses += 1;
|
|
326
|
+
yoga.calculateLayout(widths.width, undefined, dir);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const intrinsic = new Map();
|
|
330
|
+
captureLeafHeights(root, intrinsic);
|
|
331
|
+
const frozen = [];
|
|
332
|
+
freezeWidths(root, frozen);
|
|
333
|
+
const shrunk = [];
|
|
334
|
+
setMeasuringShrink(root, axis, shrunk);
|
|
335
|
+
this._scopedFloorPasses += 1;
|
|
336
|
+
yoga.calculateLayout(widths.width, undefined, dir);
|
|
337
|
+
const span = contentSpan(root, axis, intrinsic, this);
|
|
338
|
+
root._floorH = declaresOwnMinimum(root, axis)
|
|
339
|
+
? yoga.getComputedHeight()
|
|
340
|
+
: span;
|
|
341
|
+
root._floorAtW = yoga.getComputedWidth();
|
|
342
|
+
this._floorsMeasured += 1;
|
|
343
|
+
restoreWidths(frozen);
|
|
344
|
+
restoreShrink(shrunk);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** The columns of this frame's spines, deepest first, summed from their
|
|
348
|
+
* children's extents (`columnHeightSpan`) — their widths are the last
|
|
349
|
+
* pass's, which is what a height is for. */
|
|
350
|
+
_sumSpine(columns) {
|
|
351
|
+
for (const column of columns) {
|
|
352
|
+
column._floorH = columnHeightSpan(column);
|
|
353
|
+
column._floorAtW = column.yoga.getComputedWidth();
|
|
354
|
+
this._floorsMeasured += 1;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
221
358
|
/**
|
|
222
359
|
* One boundary's floors, measured with it laid out alone — the passes
|
|
223
360
|
* `_measureContentSpans` runs over the whole tree, run over the box: its
|
|
@@ -226,6 +363,15 @@ export class WindowSize {
|
|
|
226
363
|
* leaves whose width moved, stopped at the box, whose own extent stands.
|
|
227
364
|
*/
|
|
228
365
|
_measureBoundary(boundary, axis, probe = false) {
|
|
366
|
+
// on copies of its boxes (exactcopy.js): the tree's own are left as the
|
|
367
|
+
// last pass cached them, which is what keeps the pass after this one
|
|
368
|
+
// from laying the whole tree out again
|
|
369
|
+
onExactCopy(boundary, () =>
|
|
370
|
+
this._measureBoundaryOnCopy(boundary, axis, probe),
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
_measureBoundaryOnCopy(boundary, axis, probe) {
|
|
229
375
|
const yoga = boundary.yoga;
|
|
230
376
|
const dir =
|
|
231
377
|
boundary.direction === 'rtl' ? Yoga.DIRECTION_RTL : Yoga.DIRECTION_LTR;
|
|
@@ -236,7 +382,6 @@ export class WindowSize {
|
|
|
236
382
|
yoga.calculateLayout(undefined, undefined, dir);
|
|
237
383
|
contentSpan(boundary, axis, null, this);
|
|
238
384
|
restoreShrink(shrunk);
|
|
239
|
-
relayOut(boundary);
|
|
240
385
|
return;
|
|
241
386
|
}
|
|
242
387
|
this._scopedFloorPasses += 1;
|
|
@@ -261,7 +406,6 @@ export class WindowSize {
|
|
|
261
406
|
contentSpan(boundary, axis, intrinsic, this);
|
|
262
407
|
restoreWidths(frozen);
|
|
263
408
|
restoreShrink(shrunk);
|
|
264
|
-
relayOut(boundary);
|
|
265
409
|
}
|
|
266
410
|
|
|
267
411
|
/** The floors on the children of every node found stale, from the extents
|
|
@@ -386,8 +530,10 @@ export class WindowSize {
|
|
|
386
530
|
this._floorsSwept = true;
|
|
387
531
|
this._floorsWidth = width;
|
|
388
532
|
this._floorsSources.clear();
|
|
533
|
+
this._floorsOtherSources.clear();
|
|
389
534
|
this._floorsUnscoped = false;
|
|
390
535
|
this._floorsScopeNow = null;
|
|
536
|
+
this._floorsSpineNow = null;
|
|
391
537
|
}
|
|
392
538
|
|
|
393
539
|
/**
|
|
@@ -398,16 +544,21 @@ export class WindowSize {
|
|
|
398
544
|
* everywhere but the boxes and the way down to them.
|
|
399
545
|
*/
|
|
400
546
|
_applyScopedFloors(boundaries, found, width, height) {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
547
|
+
const spine = this._floorsSpineNow;
|
|
548
|
+
const measure = (boundary, axis, probe) => {
|
|
549
|
+
const widths = spine?.roots.get(boundary);
|
|
550
|
+
if (widths) this._measureSpineRoot(boundary, widths, axis, probe);
|
|
551
|
+
else this._measureBoundary(boundary, axis, probe);
|
|
552
|
+
};
|
|
553
|
+
// Measured on copies in the exact config (exactcopy.js), so the
|
|
554
|
+
// renderer's config never leaves the grid, and the pass below answers
|
|
555
|
+
// from its cache everywhere these boxes and the way down to them are not.
|
|
556
|
+
if (found.width) {
|
|
408
557
|
this._writeFloors('width');
|
|
409
|
-
|
|
410
|
-
}
|
|
558
|
+
for (const boundary of boundaries) measure(boundary, 'width');
|
|
559
|
+
}
|
|
560
|
+
this._writeFloors('width');
|
|
561
|
+
this._writeFloors('height');
|
|
411
562
|
let heights = found.height;
|
|
412
563
|
let probed = false;
|
|
413
564
|
if (!heights) {
|
|
@@ -420,13 +571,12 @@ export class WindowSize {
|
|
|
420
571
|
probed = true;
|
|
421
572
|
}
|
|
422
573
|
if (heights) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
});
|
|
574
|
+
this._writeFloors('height');
|
|
575
|
+
for (const boundary of boundaries) {
|
|
576
|
+
measure(boundary, 'height', !probed);
|
|
577
|
+
}
|
|
578
|
+
if (spine !== null) this._sumSpine(spine.spine);
|
|
579
|
+
this._writeFloors('height');
|
|
430
580
|
this._layoutRoot(width, height);
|
|
431
581
|
}
|
|
432
582
|
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
// Column spines: the scope a change outside every sized box can still be
|
|
2
|
+
// measured in (`WindowNode._floorsScope`), when what stands between it and a
|
|
3
|
+
// box that sizes itself is a run of columns whose height is their content's.
|
|
4
|
+
//
|
|
5
|
+
// A document, a log, a chat transcript, a feed: one scroll pane, one column
|
|
6
|
+
// in it, thousands of blocks in the column. A block that arrives or changes
|
|
7
|
+
// there is confined to nothing `isFloorBoundary` recognises, so it used to
|
|
8
|
+
// be measured with the whole tree — three layout passes over every block,
|
|
9
|
+
// each evicting the one yoga cached for the pass before — to learn the
|
|
10
|
+
// extents of one block and the column it sits in. A spine measures the
|
|
11
|
+
// block alone and sums the column: `columnHeightSpan` over the extents its
|
|
12
|
+
// other children already carry.
|
|
13
|
+
//
|
|
14
|
+
// Three facts make that the same answer the whole tree gives:
|
|
15
|
+
//
|
|
16
|
+
// - Down a column whose height is its content's, the measuring passes have
|
|
17
|
+
// no free space to squash into, so a block in it is laid out at its
|
|
18
|
+
// content height in every pass. Laid out alone with no height on offer,
|
|
19
|
+
// it comes out the same.
|
|
20
|
+
// - Across, a block stretched over its column is as wide as the column's
|
|
21
|
+
// content box, in every pass: now, at the width the heights are measured
|
|
22
|
+
// for, and in the width pass, whose width the way down from the root
|
|
23
|
+
// decides (`widthPassWidth`).
|
|
24
|
+
// - Only heights travel up a spine. A column writes floors down its main
|
|
25
|
+
// axis, and the box a spine stops at names its own size on both axes
|
|
26
|
+
// (`isSpineStop`), so no width extent above the block is ever read. They
|
|
27
|
+
// are left unmeasured, which is what the floors do with every extent
|
|
28
|
+
// nobody reads (`collectFloorStale`).
|
|
29
|
+
//
|
|
30
|
+
// Anything else — a row on the way, a margin across the block, a width
|
|
31
|
+
// that is not a length — is not a spine, and the tree is measured whole.
|
|
32
|
+
|
|
33
|
+
import { declaresOwnMinimum, inFlow, mainAxisOf } from './floors.js';
|
|
34
|
+
import { Yoga } from '../../yoga.js';
|
|
35
|
+
|
|
36
|
+
const LENGTHS_ACROSS = [
|
|
37
|
+
'margin',
|
|
38
|
+
'marginHorizontal',
|
|
39
|
+
'marginLeft',
|
|
40
|
+
'marginRight',
|
|
41
|
+
'marginStart',
|
|
42
|
+
'marginEnd',
|
|
43
|
+
];
|
|
44
|
+
const MARGINS = [
|
|
45
|
+
...LENGTHS_ACROSS,
|
|
46
|
+
'marginVertical',
|
|
47
|
+
'marginTop',
|
|
48
|
+
'marginBottom',
|
|
49
|
+
];
|
|
50
|
+
const PADDINGS = [
|
|
51
|
+
'padding',
|
|
52
|
+
'paddingTop',
|
|
53
|
+
'paddingRight',
|
|
54
|
+
'paddingBottom',
|
|
55
|
+
'paddingLeft',
|
|
56
|
+
'paddingHorizontal',
|
|
57
|
+
'paddingVertical',
|
|
58
|
+
'paddingStart',
|
|
59
|
+
'paddingEnd',
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A column a spine can sum (`columnHeightSpan`): laid out by flex down its
|
|
64
|
+
* main axis without wrapping, and with a height that is its content's —
|
|
65
|
+
* nothing named on the height axis (`declaresOwnMinimum`), so neither a
|
|
66
|
+
* size, a floor, a ceiling nor a clip stands between its children and its
|
|
67
|
+
* extent.
|
|
68
|
+
*/
|
|
69
|
+
export function isSummableColumn(node) {
|
|
70
|
+
const style = node.style;
|
|
71
|
+
return (
|
|
72
|
+
node.yoga != null &&
|
|
73
|
+
!node.isWindow &&
|
|
74
|
+
node._host === null &&
|
|
75
|
+
style.display !== 'none' &&
|
|
76
|
+
mainAxisOf(node) === 'height' &&
|
|
77
|
+
style.flexDirection !== 'column-reverse' &&
|
|
78
|
+
(style.flexWrap == null || style.flexWrap === 'nowrap') &&
|
|
79
|
+
!declaresOwnMinimum(node, 'height')
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Where a change stops mattering on its way up: the window, or a column
|
|
85
|
+
* whose extent on both axes is its style's — a scroll pane is the common
|
|
86
|
+
* one — so that what changed inside it moves nothing above it. A column,
|
|
87
|
+
* because it writes its children's floors down its main axis, and a spine
|
|
88
|
+
* only works out heights.
|
|
89
|
+
*/
|
|
90
|
+
export function isSpineStop(node) {
|
|
91
|
+
if (node.isWindow) return true;
|
|
92
|
+
return (
|
|
93
|
+
node.yoga != null &&
|
|
94
|
+
node._host === null &&
|
|
95
|
+
mainAxisOf(node) === 'height' &&
|
|
96
|
+
declaresOwnMinimum(node, 'height') &&
|
|
97
|
+
declaresOwnMinimum(node, 'width')
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The spine scope of this frame's changes, or null when one of them has
|
|
103
|
+
* none. `sources` are the nodes that changed and are inside no sized box;
|
|
104
|
+
* `listOnly(node)` says whether all a node did was gain or lose children.
|
|
105
|
+
*
|
|
106
|
+
* A change is measured from the highest box on its way up that no column
|
|
107
|
+
* above it can sum — a text inside a row inside a list is measured with the
|
|
108
|
+
* row — and everything above that is summed. A column that only gained or
|
|
109
|
+
* lost children is not measured itself: the children that are new or
|
|
110
|
+
* changed are, and it is summed with the rest.
|
|
111
|
+
*
|
|
112
|
+
* Answers `{ roots, spine, stops }`: the boxes measured alone, with the
|
|
113
|
+
* widths to measure each at (`rootWidths`); the columns summed, deepest
|
|
114
|
+
* first; and the boxes each spine ends at, whose children's floors are
|
|
115
|
+
* written.
|
|
116
|
+
*/
|
|
117
|
+
export function spineScope(root, sources, listOnly) {
|
|
118
|
+
const roots = new Map();
|
|
119
|
+
const spine = new Set();
|
|
120
|
+
const stops = new Set();
|
|
121
|
+
for (const source of sources) {
|
|
122
|
+
const list = listOnly(source);
|
|
123
|
+
// the way up to where the change stops mattering
|
|
124
|
+
const path = [];
|
|
125
|
+
let node = source;
|
|
126
|
+
while (!isSpineStop(node)) {
|
|
127
|
+
path.push(node);
|
|
128
|
+
node = node.parent;
|
|
129
|
+
if (!node) return null;
|
|
130
|
+
}
|
|
131
|
+
if (node !== root && node.root !== root) return null;
|
|
132
|
+
// a stop changed in itself: its padding, its direction — everything it
|
|
133
|
+
// holds is laid out anew, which is the whole tree's measurement
|
|
134
|
+
if (path.length === 0 && !list) return null;
|
|
135
|
+
stops.add(node);
|
|
136
|
+
// the highest box on the way that a column above it cannot sum
|
|
137
|
+
let top = -1;
|
|
138
|
+
for (let i = path.length - 1; i >= 0; i--) {
|
|
139
|
+
const summed = i > 0 || list;
|
|
140
|
+
if (!summed || !isSummableColumn(path[i])) {
|
|
141
|
+
top = i;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const column of path.slice(top + 1)) spine.add(column);
|
|
146
|
+
const changed = top === -1 ? staleChildren(source) : [path[top]];
|
|
147
|
+
for (const child of changed) {
|
|
148
|
+
if (!child.yoga || child.isWindow || child.style.display === 'none') {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const widths = rootWidths(child, root);
|
|
152
|
+
if (widths === null) return null;
|
|
153
|
+
roots.set(child, widths);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// A root inside another root is measured with it, and so is a column it
|
|
157
|
+
// would have been summed through.
|
|
158
|
+
const within = (node) => {
|
|
159
|
+
for (let n = node.parent; n; n = n.parent) if (roots.has(n)) return true;
|
|
160
|
+
return false;
|
|
161
|
+
};
|
|
162
|
+
for (const node of [...roots.keys()]) if (within(node)) roots.delete(node);
|
|
163
|
+
for (const node of [...spine]) {
|
|
164
|
+
if (roots.has(node) || within(node)) spine.delete(node);
|
|
165
|
+
}
|
|
166
|
+
// Every other child of a summed column answers from the extent it carries.
|
|
167
|
+
for (const column of spine) {
|
|
168
|
+
for (const child of column.children) {
|
|
169
|
+
if (!inFlow(child) || roots.has(child) || spine.has(child)) continue;
|
|
170
|
+
if (child._floorH === undefined) return null;
|
|
171
|
+
if (MARGINS.some((key) => typeof child.style[key] === 'string')) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const depth = (node) => {
|
|
177
|
+
let d = 0;
|
|
178
|
+
for (let n = node; n; n = n.parent) d += 1;
|
|
179
|
+
return d;
|
|
180
|
+
};
|
|
181
|
+
return {
|
|
182
|
+
roots,
|
|
183
|
+
spine: [...spine].sort((a, b) => depth(b) - depth(a)),
|
|
184
|
+
stops,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** The children of a node that gained or lost some: the ones yoga has a
|
|
189
|
+
* change on record for, and the ones never measured. */
|
|
190
|
+
function staleChildren(node) {
|
|
191
|
+
const out = [];
|
|
192
|
+
for (const child of node.children) {
|
|
193
|
+
if (!child.yoga || child.isWindow) continue;
|
|
194
|
+
if (child.yoga.isDirty() || child._floorH === undefined) out.push(child);
|
|
195
|
+
}
|
|
196
|
+
return out;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* The widths a box is measured alone at, or null when it cannot be: in flow
|
|
201
|
+
* and stretched across its column with no margin, width or ceiling of its
|
|
202
|
+
* own across it, so that it is exactly as wide as the column's content box.
|
|
203
|
+
* `width` is that box now — the width its heights are for — and
|
|
204
|
+
* `widthPass` the one the width pass over the whole tree would give it
|
|
205
|
+
* (`widthPassWidth`), null where only a pass knows.
|
|
206
|
+
*/
|
|
207
|
+
function rootWidths(node, root) {
|
|
208
|
+
const style = node.style;
|
|
209
|
+
const parent = node.parent;
|
|
210
|
+
if (!parent || !inFlow(node) || node._host !== null) return null;
|
|
211
|
+
if (style.width !== undefined || style.maxWidth !== undefined) return null;
|
|
212
|
+
if (typeof style.minWidth === 'string') return null;
|
|
213
|
+
if (!stretched(node)) return null;
|
|
214
|
+
if (
|
|
215
|
+
LENGTHS_ACROSS.some((key) => style[key] !== undefined && style[key] !== 0)
|
|
216
|
+
) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
if (MARGINS.some((key) => typeof style[key] === 'string')) return null;
|
|
220
|
+
if (PADDINGS.some((key) => typeof style[key] === 'string')) return null;
|
|
221
|
+
return {
|
|
222
|
+
width: contentWidth(parent),
|
|
223
|
+
widthPass: widthPassContent(parent, root),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Stretched across its parent's column — `alignSelf`, or the column's
|
|
228
|
+
* `alignItems` when the box leaves it to them. */
|
|
229
|
+
function stretched(node) {
|
|
230
|
+
const parent = node.parent;
|
|
231
|
+
if (!parent || mainAxisOf(parent) !== 'height') return false;
|
|
232
|
+
const self = node.style.alignSelf ?? 'auto';
|
|
233
|
+
if (self === 'stretch') return true;
|
|
234
|
+
return (
|
|
235
|
+
self === 'auto' && (parent.style.alignItems ?? 'stretch') === 'stretch'
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Padding and border across, as the last pass resolved them. */
|
|
240
|
+
function insetAcross(node) {
|
|
241
|
+
const yoga = node.yoga;
|
|
242
|
+
return (
|
|
243
|
+
yoga.getComputedPadding(Yoga.EDGE_LEFT) +
|
|
244
|
+
yoga.getComputedPadding(Yoga.EDGE_RIGHT) +
|
|
245
|
+
yoga.getComputedBorder(Yoga.EDGE_LEFT) +
|
|
246
|
+
yoga.getComputedBorder(Yoga.EDGE_RIGHT)
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** A box's content width in the last pass. */
|
|
251
|
+
function contentWidth(node) {
|
|
252
|
+
return Math.max(0, node.yoga.getComputedWidth() - insetAcross(node));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* How wide `node` is in the width pass over the whole tree
|
|
257
|
+
* (`_measureContentSpans('width')`), worked out on the way down rather than
|
|
258
|
+
* laid out: the root at nothing, and each box below it either the width its
|
|
259
|
+
* style names or stretched over its column's content box — clamped as yoga
|
|
260
|
+
* clamps it, and never narrower than its own padding and border. Null where
|
|
261
|
+
* the way down is anything else: a row, whose children are as wide as the
|
|
262
|
+
* pass decides.
|
|
263
|
+
*/
|
|
264
|
+
function widthPassWidth(node, root) {
|
|
265
|
+
if (node === root) return insetAcross(node);
|
|
266
|
+
const style = node.style;
|
|
267
|
+
let width;
|
|
268
|
+
if (typeof style.width === 'number') width = style.width;
|
|
269
|
+
else if (style.width !== undefined) return null;
|
|
270
|
+
else {
|
|
271
|
+
if (!inFlow(node) || !stretched(node)) return null;
|
|
272
|
+
if (MARGINS.some((key) => typeof style[key] === 'string')) return null;
|
|
273
|
+
const inner = widthPassContent(node.parent, root);
|
|
274
|
+
if (inner === null) return null;
|
|
275
|
+
const yoga = node.yoga;
|
|
276
|
+
width =
|
|
277
|
+
inner -
|
|
278
|
+
yoga.getComputedMargin(Yoga.EDGE_LEFT) -
|
|
279
|
+
yoga.getComputedMargin(Yoga.EDGE_RIGHT);
|
|
280
|
+
}
|
|
281
|
+
if (typeof style.maxWidth === 'number')
|
|
282
|
+
width = Math.min(width, style.maxWidth);
|
|
283
|
+
else if (style.maxWidth !== undefined) return null;
|
|
284
|
+
if (typeof style.minWidth === 'number')
|
|
285
|
+
width = Math.max(width, style.minWidth);
|
|
286
|
+
else if (style.minWidth !== undefined) return null;
|
|
287
|
+
return Math.max(width, insetAcross(node));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** …and the content box inside it. */
|
|
291
|
+
function widthPassContent(node, root) {
|
|
292
|
+
const width = widthPassWidth(node, root);
|
|
293
|
+
return width === null ? null : Math.max(0, width - insetAcross(node));
|
|
294
|
+
}
|
|
@@ -277,8 +277,15 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
277
277
|
// the nodes whose changes the next measurement owes, and whether one
|
|
278
278
|
// came with no node to name (`_floorsScope`)
|
|
279
279
|
this._floorsSources = new Set();
|
|
280
|
+
// …the ones that changed for any reason but their child list, which
|
|
281
|
+
// are measured themselves rather than through the children they gained
|
|
282
|
+
// (spine.js), and the spines this frame's measurement runs on
|
|
283
|
+
this._floorsOtherSources = new Set();
|
|
280
284
|
this._floorsUnscoped = false;
|
|
281
285
|
this._floorsScopeNow = null;
|
|
286
|
+
this._floorsSpineNow = null;
|
|
287
|
+
// whether the next layout walk must visit every box (`invalidate`)
|
|
288
|
+
this._walkWhole = true;
|
|
282
289
|
this._scopedFloorPasses = 0;
|
|
283
290
|
// whether the first floors pass has run (`collectFloorStale`'s sweep)
|
|
284
291
|
this._floorsSwept = false;
|
package/src/styles.js
CHANGED
|
@@ -1950,6 +1950,34 @@ let exactDepth = 0;
|
|
|
1950
1950
|
* here is not the size the same layout comes to on it. */
|
|
1951
1951
|
export const isMeasuringExactly = () => exactDepth > 0;
|
|
1952
1952
|
|
|
1953
|
+
/**
|
|
1954
|
+
* A second config, never rounded, for measuring a box on copies of its
|
|
1955
|
+
* boxes (nodes/window/exactcopy.js). Switching the renderer's config off the
|
|
1956
|
+
* grid and back is a change yoga answers by treating every layout it has
|
|
1957
|
+
* cached as stale, so the pass after any `measuringExactly` lays the whole
|
|
1958
|
+
* tree out again; a measurement confined to one box is made in this config
|
|
1959
|
+
* instead, and leaves the renderer's, and its caches, alone.
|
|
1960
|
+
*/
|
|
1961
|
+
let exactConfig = null;
|
|
1962
|
+
export const exactLayoutConfig = () => {
|
|
1963
|
+
if (exactConfig === null) {
|
|
1964
|
+
exactConfig = Yoga.Config.create();
|
|
1965
|
+
exactConfig.setPointScaleFactor(0);
|
|
1966
|
+
}
|
|
1967
|
+
return exactConfig;
|
|
1968
|
+
};
|
|
1969
|
+
|
|
1970
|
+
/** `measure` for layouts made in the exact config: off the grid as far as
|
|
1971
|
+
* `isMeasuringExactly` is concerned, with nothing to switch. */
|
|
1972
|
+
export function measuringInExactConfig(measure) {
|
|
1973
|
+
exactDepth++;
|
|
1974
|
+
try {
|
|
1975
|
+
return measure();
|
|
1976
|
+
} finally {
|
|
1977
|
+
exactDepth--;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1953
1981
|
/**
|
|
1954
1982
|
* The yoga defaults that are not CSS's, written once per node.
|
|
1955
1983
|
*
|