react-x11 2.18.2 → 2.18.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/Reconciler.js +8 -1
- package/src/appcontext.js +10 -5
- package/src/cocoa/overlay.js +40 -3
- package/src/glnodes.js +133 -7
- package/src/globalmenu.js +104 -8
- package/src/gloverlay.js +340 -22
- package/src/index.d.ts +4 -1
- package/src/nodes/animation.js +2 -0
- package/src/nodes/damage.js +8 -0
- package/src/nodes/invalidate.js +126 -10
- package/src/nodes/node.js +24 -4
- package/src/nodes/position.js +11 -2
- package/src/nodes/scrollable.js +5 -2
- package/src/nodes/scrollblit.js +90 -24
- package/src/nodes/window/flush.js +40 -18
- package/src/nodes/window/window.js +4 -0
- package/src/statusnotifier.js +49 -7
- package/src/styles.js +22 -0
- package/src/svgnodes.js +76 -15
- package/src/types/elements.d.ts +6 -5
package/src/nodes/invalidate.js
CHANGED
|
@@ -18,6 +18,20 @@ import { BLIT_POISONED } from './scrollblit.js';
|
|
|
18
18
|
import { DEV } from './util.js';
|
|
19
19
|
import { debugPaint } from './window/debugpaint.js';
|
|
20
20
|
|
|
21
|
+
// Which damage lists a claim belongs in (`WindowInvalidate._paneReach`).
|
|
22
|
+
const WINDOW_ONLY = 0;
|
|
23
|
+
const BOTH = 1;
|
|
24
|
+
const PANES_ONLY = 2;
|
|
25
|
+
|
|
26
|
+
/** Is `node` inside a `<glarea>` below `root` — drawn on its panes, and
|
|
27
|
+
* never by the window's own paint walk? */
|
|
28
|
+
function insideSurface(node, root) {
|
|
29
|
+
for (let n = node.parent; n && n !== root; n = n.parent) {
|
|
30
|
+
if (n.isGlArea) return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
/** Node's half of invalidation, installed onto `Node.prototype` by node.js. */
|
|
22
36
|
export class NodeInvalidate {
|
|
23
37
|
/**
|
|
@@ -40,7 +54,10 @@ export class NodeInvalidate {
|
|
|
40
54
|
// reaches — a shadow, an outline, a scene element's ink — so its cached
|
|
41
55
|
// paint reach goes with the claim
|
|
42
56
|
if (damage === this || damage === null) this._clearPaintBounds();
|
|
43
|
-
|
|
57
|
+
// …and names itself, so a rect it claims goes to the damage lists it can
|
|
58
|
+
// reach — the window's, a `<glarea>`'s panes', or both
|
|
59
|
+
// (`WindowNode._paneReach`)
|
|
60
|
+
this.root?.invalidate(layoutChanged, damage, reason, this);
|
|
44
61
|
}
|
|
45
62
|
|
|
46
63
|
/**
|
|
@@ -112,7 +129,7 @@ export class NodeInvalidate {
|
|
|
112
129
|
// Same walk, same frame, same answer — see `_childListBefore`, whose
|
|
113
130
|
// record this shares so that a reflow and a child-list change on one
|
|
114
131
|
// node in one frame walk the subtree once between them.
|
|
115
|
-
root.invalidate(true, this._childListBefore(), reason);
|
|
132
|
+
root.invalidate(true, this._childListBefore(), reason, this);
|
|
116
133
|
root._reflowed.add(this);
|
|
117
134
|
}
|
|
118
135
|
|
|
@@ -235,8 +252,14 @@ export class WindowInvalidate {
|
|
|
235
252
|
* diagnostic, collected per frame into `_lastReasons` so the frame log,
|
|
236
253
|
* REACT_X11_DEBUG_PAINT=full and the tracer can attribute a repaint.
|
|
237
254
|
* Omitting it costs nothing but attribution.
|
|
255
|
+
*
|
|
256
|
+
* `source` is the node making a claim that names a rect rather than a
|
|
257
|
+
* node — `Node.invalidate` passes itself — and is what decides which of
|
|
258
|
+
* the frame's two lists the claim goes to: the window's, the one the
|
|
259
|
+
* panes over its `<glarea>`s paint from, or both (`_paneReach`). Left
|
|
260
|
+
* out, both.
|
|
238
261
|
*/
|
|
239
|
-
invalidate(layoutChanged, damage = null, reason = null) {
|
|
262
|
+
invalidate(layoutChanged, damage = null, reason = null, source = null) {
|
|
240
263
|
if (this.destroyed || !this.window) return;
|
|
241
264
|
if (!layoutChanged && damage === NO_DAMAGE) {
|
|
242
265
|
// Nothing this node draws changed, so it contributes no region — and
|
|
@@ -274,6 +297,16 @@ export class WindowInvalidate {
|
|
|
274
297
|
this._scheduleFrame();
|
|
275
298
|
return;
|
|
276
299
|
}
|
|
300
|
+
// The panes over this window's `<glarea>`s paint from a list of their
|
|
301
|
+
// own (`_paneDamage`): a claim goes to the window's list, the panes',
|
|
302
|
+
// or both, by what its source can reach (`_paneReach`). One `size`
|
|
303
|
+
// read for a window with no surface children.
|
|
304
|
+
const reach =
|
|
305
|
+
this._overlaid.size !== 0
|
|
306
|
+
? this._paneReach(damage?.kind ? damage : source)
|
|
307
|
+
: WINDOW_ONLY;
|
|
308
|
+
const panes = reach !== WINDOW_ONLY;
|
|
309
|
+
const windowToo = reach !== PANES_ONLY;
|
|
277
310
|
if (layoutChanged) {
|
|
278
311
|
this.needsLayout = true;
|
|
279
312
|
// The content floors are measured from the tree, so anything that
|
|
@@ -314,8 +347,11 @@ export class WindowInvalidate {
|
|
|
314
347
|
// where nothing can be seen, so it owes no pixels), and null on a frame
|
|
315
348
|
// that is already unbounded, which owes neither a rect nor the subtree
|
|
316
349
|
// walk that measures one — a blit cannot fire there either.
|
|
350
|
+
const bounded =
|
|
351
|
+
(windowToo && this._damage !== FULL_DAMAGE) ||
|
|
352
|
+
(panes && this._paneDamage !== FULL_DAMAGE);
|
|
317
353
|
const bounds =
|
|
318
|
-
damage && damage !== NO_DAMAGE &&
|
|
354
|
+
damage && damage !== NO_DAMAGE && bounded
|
|
319
355
|
? damage._claimBounds
|
|
320
356
|
? damage._claimBounds()
|
|
321
357
|
: damage
|
|
@@ -324,6 +360,9 @@ export class WindowInvalidate {
|
|
|
324
360
|
if (pendingScrolls?.size && bounds && this._scrollClaim !== damage) {
|
|
325
361
|
const rect = bounds;
|
|
326
362
|
for (const sv of pendingScrolls) {
|
|
363
|
+
// A claim the window's list never sees is no change to the pixels
|
|
364
|
+
// a blit of the window's moves: a surface's children are on panes
|
|
365
|
+
if (!windowToo && !insideSurface(sv, this)) continue;
|
|
327
366
|
// An element blitting a region of its own drawing (issue #303) is
|
|
328
367
|
// waiting on that region, not on the whole node it lives in — and
|
|
329
368
|
// it is waiting on it *exactly* (issue #309). Its claim is the rect
|
|
@@ -363,25 +402,102 @@ export class WindowInvalidate {
|
|
|
363
402
|
}
|
|
364
403
|
}
|
|
365
404
|
}
|
|
366
|
-
if (
|
|
367
|
-
|
|
368
|
-
|
|
405
|
+
if (!damage) {
|
|
406
|
+
// "something, somewhere" — in the panes alone, for a node whose
|
|
407
|
+
// pixels are only ever on one; everywhere, for anything else
|
|
408
|
+
if (windowToo) this._damage = FULL_DAMAGE;
|
|
409
|
+
else this._damage ??= [];
|
|
410
|
+
if (panes) this._paneDamage = FULL_DAMAGE;
|
|
411
|
+
} else if (!bounds) {
|
|
369
412
|
// A layout change that names no region: either NO_DAMAGE, from a
|
|
370
413
|
// caller with a finer claim already in flight, or a node whose reach
|
|
371
414
|
// a clipping ancestor left nothing of (issue #398). Unlike `!damage`
|
|
372
|
-
// neither is "something, somewhere", so neither costs a full repaint
|
|
373
|
-
|
|
415
|
+
// neither is "something, somewhere", so neither costs a full repaint
|
|
416
|
+
// — which a frame with no list at all would be, however little its
|
|
417
|
+
// layout pass goes on to claim (`_takeDamage`). A child of a
|
|
418
|
+
// `<glarea>` that only moved is this claim (issue #644), and a frame
|
|
419
|
+
// whose move is settled on a pane claims only the strips it uncovered.
|
|
420
|
+
this._damage ??= [];
|
|
421
|
+
} else {
|
|
374
422
|
// a node, or a bare rect for a caller that has a region rather than a
|
|
375
423
|
// node — a subtree that is about to be removed, say. Claims accumulate
|
|
376
424
|
// as a list of rects rather than one box around them all, so two changes
|
|
377
425
|
// at opposite corners of the window no longer repaint everything
|
|
378
426
|
// between them.
|
|
379
|
-
|
|
427
|
+
if (!windowToo) {
|
|
428
|
+
// the window owes nothing, and a frame with no list is unbounded
|
|
429
|
+
this._damage ??= [];
|
|
430
|
+
} else if (this._damage !== FULL_DAMAGE) {
|
|
431
|
+
this._damage = addDamageRect(
|
|
432
|
+
this._damage,
|
|
433
|
+
bounds,
|
|
434
|
+
this._damageRectCap(),
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
if (panes) this._addPaneDamage(bounds);
|
|
380
438
|
}
|
|
381
439
|
this.needsPaint = true;
|
|
382
440
|
this._scheduleFrame();
|
|
383
441
|
}
|
|
384
442
|
|
|
443
|
+
/**
|
|
444
|
+
* Which damage lists a claim from `node` belongs in: the window's, the
|
|
445
|
+
* panes' over its `<glarea>`s, or both. The panes hold the surfaces'
|
|
446
|
+
* children and nothing else (src/gloverlay.js), and the window paints
|
|
447
|
+
* none of those, so:
|
|
448
|
+
*
|
|
449
|
+
* - a node inside a surface is the panes' alone — the window's pass under
|
|
450
|
+
* the surface would repaint pixels no child of it ever had;
|
|
451
|
+
* - a node above a surface is both: its colour or font is what the
|
|
452
|
+
* children below it paint with. So is a surface itself, whose
|
|
453
|
+
* `clearColor` an X11 pane's ground is, and so is a claim with no
|
|
454
|
+
* source to go by;
|
|
455
|
+
* - anything else is the window's alone. It draws only into the window,
|
|
456
|
+
* where the surface covers it: a graph pane under the surface that its
|
|
457
|
+
* own pan claims whole every frame (issue #644) is the claim this keeps
|
|
458
|
+
* out of the panes' repaint.
|
|
459
|
+
*/
|
|
460
|
+
_paneReach(node) {
|
|
461
|
+
if (!node || node === this) return BOTH;
|
|
462
|
+
if (insideSurface(node, this)) return PANES_ONLY;
|
|
463
|
+
if (node.isGlArea) return BOTH;
|
|
464
|
+
for (const area of this._overlaid) {
|
|
465
|
+
for (let n = area.parent; n && n !== this; n = n.parent) {
|
|
466
|
+
if (n === node) return BOTH;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return WINDOW_ONLY;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** One rect more for the panes, unless they repaint whole already. */
|
|
473
|
+
_addPaneDamage(rect) {
|
|
474
|
+
if (this._paneDamage === FULL_DAMAGE) return;
|
|
475
|
+
this._paneDamage = addDamageRect(
|
|
476
|
+
this._paneDamage,
|
|
477
|
+
rect,
|
|
478
|
+
this._damageRectCap(),
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* What the panes repaint this frame: null for all of them, and — unlike
|
|
484
|
+
* the window's list — an empty list when nothing reached them, which is
|
|
485
|
+
* most frames of a window whose surface children hold still.
|
|
486
|
+
*/
|
|
487
|
+
_takePaneDamage(width, height) {
|
|
488
|
+
const damage = this._paneDamage;
|
|
489
|
+
this._paneDamage = null;
|
|
490
|
+
if (damage === FULL_DAMAGE) return null;
|
|
491
|
+
if (!damage || damage.length === 0) return [];
|
|
492
|
+
const rects = [];
|
|
493
|
+
for (const claimed of damage) {
|
|
494
|
+
const clamped = this._clampDamage(claimed, width, height);
|
|
495
|
+
if (clamped === FULL_DAMAGE) return null;
|
|
496
|
+
if (clamped) rects.push(clamped);
|
|
497
|
+
}
|
|
498
|
+
return damageToPaint(rects);
|
|
499
|
+
}
|
|
500
|
+
|
|
385
501
|
/**
|
|
386
502
|
* The rects this frame will repaint, or null for the whole window.
|
|
387
503
|
*
|
package/src/nodes/node.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
applyLayoutStyle,
|
|
14
14
|
applyLayoutDefaults,
|
|
15
15
|
createLayoutNode,
|
|
16
|
+
onlyInsetsChanged,
|
|
16
17
|
paintPropsChanged,
|
|
17
18
|
isEventProp,
|
|
18
19
|
} from '../styles.js';
|
|
@@ -473,10 +474,10 @@ export class Node {
|
|
|
473
474
|
// `_reflowed` would undo both — its post-layout claim is this node's
|
|
474
475
|
// box, the whole band the blit is about to move.
|
|
475
476
|
if (this._blitLedgerOpen()) {
|
|
476
|
-
root.invalidate(true, before ?? NO_DAMAGE, 'child-list');
|
|
477
|
+
root.invalidate(true, before ?? NO_DAMAGE, 'child-list', this);
|
|
477
478
|
return;
|
|
478
479
|
}
|
|
479
|
-
root.invalidate(true, before, 'child-list');
|
|
480
|
+
root.invalidate(true, before, 'child-list', this);
|
|
480
481
|
root._reflowed.add(this);
|
|
481
482
|
}
|
|
482
483
|
|
|
@@ -604,7 +605,21 @@ export class Node {
|
|
|
604
605
|
// which is what keeps a commit from widening the region to every node
|
|
605
606
|
// it touched.
|
|
606
607
|
if (layoutChanged) {
|
|
607
|
-
|
|
608
|
+
// A child of a `<glarea>` that only moved (issue #644): its pixels are
|
|
609
|
+
// on a pane, one shift away, and the layout pass reports the move to
|
|
610
|
+
// the overlay rather than claiming it (src/glnodes.js). A claim of the
|
|
611
|
+
// subtree here, before and after, would be the repaint the overlay
|
|
612
|
+
// exists to skip — and the pass claims both ends of the move itself
|
|
613
|
+
// whenever the overlay cannot move the pixels.
|
|
614
|
+
if (
|
|
615
|
+
this.parent?.isGlArea &&
|
|
616
|
+
onlyInsetsChanged(style, prevStyle) &&
|
|
617
|
+
!this.paintChanged(newProps, prev)
|
|
618
|
+
) {
|
|
619
|
+
this.invalidate(true, NO_DAMAGE, 'props');
|
|
620
|
+
} else {
|
|
621
|
+
this._invalidateLayout('props');
|
|
622
|
+
}
|
|
608
623
|
} else {
|
|
609
624
|
// The style half is asked here rather than inside `paintChanged`, and
|
|
610
625
|
// stays core's answer: what a style change moves is the background,
|
|
@@ -632,12 +647,16 @@ export class Node {
|
|
|
632
647
|
* safe without knowing about subclasses: `<image src>`, `<canvas onDraw>`,
|
|
633
648
|
* a `value`, a `placeholder`, a `caretColor` — any prop a subclass paints
|
|
634
649
|
* from is a prop, so a change to it lands here as an inequality and damages
|
|
635
|
-
* the node.
|
|
650
|
+
* the node. Four kinds are skipped because they cannot affect this node's
|
|
636
651
|
* own drawing:
|
|
637
652
|
*
|
|
638
653
|
* - `children`, which the reconciler mutates through appendChild /
|
|
639
654
|
* removeChild / commitTextUpdate, each of which invalidates on its own;
|
|
640
655
|
* - event handlers, rebuilt every render and never painted;
|
|
656
|
+
* - `ref`, which is React's rather than the element's. Since React 19 it
|
|
657
|
+
* arrives as an ordinary prop, so an inline callback is a new function
|
|
658
|
+
* every render, like a handler. `key` would be the same if React ever
|
|
659
|
+
* passed it on; today React strips it;
|
|
641
660
|
* - `style`, compared by value by the caller — so a style object React
|
|
642
661
|
* rebuilt with the same contents costs nothing, which is the whole
|
|
643
662
|
* point, since React rebuilds sibling styles on every render and a
|
|
@@ -669,6 +688,7 @@ export class Node {
|
|
|
669
688
|
const keys = new Set([...Object.keys(newProps), ...Object.keys(prev)]);
|
|
670
689
|
for (const key of keys) {
|
|
671
690
|
if (key === 'children' || key === 'style' || isEventProp(key)) continue;
|
|
691
|
+
if (key === 'ref' || key === 'key') continue;
|
|
672
692
|
if (claimed.has(key)) continue;
|
|
673
693
|
if (newProps[key] !== prev[key]) return true;
|
|
674
694
|
}
|
package/src/nodes/position.js
CHANGED
|
@@ -317,6 +317,7 @@ export class WindowPosition {
|
|
|
317
317
|
nodes.sort((a, b) => depth.get(a) - depth.get(b));
|
|
318
318
|
}
|
|
319
319
|
const cap = this._damageRectCap();
|
|
320
|
+
const panes = this._overlaid.size !== 0;
|
|
320
321
|
const t = now();
|
|
321
322
|
let claimed = false;
|
|
322
323
|
for (const node of nodes) {
|
|
@@ -330,7 +331,12 @@ export class WindowPosition {
|
|
|
330
331
|
? node.paintBounds()
|
|
331
332
|
: null;
|
|
332
333
|
node._placedShown = after;
|
|
333
|
-
if (
|
|
334
|
+
if (
|
|
335
|
+
this._damage === FULL_DAMAGE &&
|
|
336
|
+
(!panes || this._paneDamage === FULL_DAMAGE)
|
|
337
|
+
) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
334
340
|
const sv = node._blitViewport();
|
|
335
341
|
let was = before;
|
|
336
342
|
if (was && sv) {
|
|
@@ -354,7 +360,10 @@ export class WindowPosition {
|
|
|
354
360
|
if (sv && !sv._recordBlitClaim(claim)) {
|
|
355
361
|
sv._pendingBlitFrom = BLIT_POISONED;
|
|
356
362
|
}
|
|
357
|
-
|
|
363
|
+
if (this._damage !== FULL_DAMAGE) {
|
|
364
|
+
this._damage = addDamageRect(this._damage, claim, cap);
|
|
365
|
+
}
|
|
366
|
+
if (panes) this._addPaneDamage(claim);
|
|
358
367
|
claimed = true;
|
|
359
368
|
}
|
|
360
369
|
}
|
package/src/nodes/scrollable.js
CHANGED
|
@@ -297,8 +297,11 @@ export const Scrollable = (Base) =>
|
|
|
297
297
|
// …which holds for a scroll's shift and not for the clamp's: the
|
|
298
298
|
// content shrank, or the viewport grew, under the offset — a row
|
|
299
299
|
// collapsing at the end of a list scrolled to its end — and
|
|
300
|
-
// nothing claimed the viewport every child just moved in.
|
|
301
|
-
|
|
300
|
+
// nothing claimed the viewport every child just moved in. Nor
|
|
301
|
+
// for a `<glarea>` child's move that this pane rides (issue
|
|
302
|
+
// #644): its box claimed nothing, and a pane laid out again may
|
|
303
|
+
// have moved rows the move's copy carries where they were.
|
|
304
|
+
if (clamped || layoutDiff.ride) outer(vp);
|
|
302
305
|
} else {
|
|
303
306
|
layoutDiff.sink = (rect) => {
|
|
304
307
|
const clipped = intersectRects(rect, vp);
|
package/src/nodes/scrollblit.js
CHANGED
|
@@ -409,7 +409,7 @@ export class NodeScrollBlit {
|
|
|
409
409
|
// reason to un-blit it. `state.rect` by identity, so a second call this
|
|
410
410
|
// frame is recognised as the same claim rather than as foreign damage.
|
|
411
411
|
root._scrollClaim = state.rect;
|
|
412
|
-
root.invalidate(false, state.rect, 'scroll');
|
|
412
|
+
root.invalidate(false, state.rect, 'scroll', this);
|
|
413
413
|
root._scrollClaim = null;
|
|
414
414
|
return this._pendingBlitFrom !== BLIT_POISONED;
|
|
415
415
|
}
|
|
@@ -485,8 +485,10 @@ export class WindowScrollBlit {
|
|
|
485
485
|
// claim the scroll recorded, like every other declined gate here
|
|
486
486
|
if (from === BLIT_POISONED) return;
|
|
487
487
|
if (NO_SCROLL_BLIT || !from || node.destroyed || node.root !== this) return;
|
|
488
|
-
|
|
489
|
-
|
|
488
|
+
// the window's backing store, or the pane over a `<glarea>` that holds
|
|
489
|
+
// the pixels of a node inside it — none where neither can move them
|
|
490
|
+
const target = this._blitTarget(node);
|
|
491
|
+
if (!target) return;
|
|
490
492
|
// the debug overlays and the DevTools highlight draw over the whole
|
|
491
493
|
// window; a blit would drag shifted copies of them along
|
|
492
494
|
if (
|
|
@@ -497,7 +499,8 @@ export class WindowScrollBlit {
|
|
|
497
499
|
) {
|
|
498
500
|
return;
|
|
499
501
|
}
|
|
500
|
-
|
|
502
|
+
// unbounded already, on the surface the blit would move
|
|
503
|
+
if (!Array.isArray(target.damage)) return;
|
|
501
504
|
// the layout diff claimed real movement this pass — the frame is not a
|
|
502
505
|
// pure scroll, and a blit under rearranged content would shift stale
|
|
503
506
|
// pixels into place the repaint no longer covers
|
|
@@ -506,7 +509,7 @@ export class WindowScrollBlit {
|
|
|
506
509
|
// region and the shift itself (issue #303), where a scroll container is
|
|
507
510
|
// its whole viewport shifted by the change in its offsets.
|
|
508
511
|
if (contents) {
|
|
509
|
-
this._applyContentsBlit(node, contents, width, height);
|
|
512
|
+
this._applyContentsBlit(node, contents, width, height, target);
|
|
510
513
|
return;
|
|
511
514
|
}
|
|
512
515
|
// Back where the frame started: a burst of scrolls that went one way
|
|
@@ -518,14 +521,14 @@ export class WindowScrollBlit {
|
|
|
518
521
|
// (Every pixel-shift gate below is moot for a shift of nothing.)
|
|
519
522
|
if (from.x === node.scrollX && from.y === node.scrollY) {
|
|
520
523
|
const box = node.abs;
|
|
521
|
-
const kept = this._blitKeptDamage(box);
|
|
524
|
+
const kept = this._blitKeptDamage(box, false, target.damage);
|
|
522
525
|
if (!kept) return;
|
|
523
526
|
let rects = kept;
|
|
524
527
|
for (const claim of ledger ?? []) {
|
|
525
528
|
const inside = intersectRects(claim, box);
|
|
526
529
|
if (inside) rects = addDamageRect(rects, inside);
|
|
527
530
|
}
|
|
528
|
-
|
|
531
|
+
target.damage = rects;
|
|
529
532
|
return;
|
|
530
533
|
}
|
|
531
534
|
// children clip to the border box, so a border ring or rounded corner
|
|
@@ -575,7 +578,7 @@ export class WindowScrollBlit {
|
|
|
575
578
|
) {
|
|
576
579
|
return;
|
|
577
580
|
}
|
|
578
|
-
const keep = this._blitKeptDamage(vp);
|
|
581
|
+
const keep = this._blitKeptDamage(vp, false, target.damage);
|
|
579
582
|
if (!keep) return;
|
|
580
583
|
// What changed inside the viewport while the blit was armed, in the
|
|
581
584
|
// coordinates the frame is about to paint in (issue #398). A claim made
|
|
@@ -635,7 +638,7 @@ export class WindowScrollBlit {
|
|
|
635
638
|
? unionArea([strip, ...repairs]) - rectArea(strip)
|
|
636
639
|
: 0;
|
|
637
640
|
if (repairArea > area * BLIT_MAX_CLAIM_AREA) return;
|
|
638
|
-
if (!this._scrollBlitSafe(node, vp)) return;
|
|
641
|
+
if (!this._scrollBlitSafe(node, vp, target.top)) return;
|
|
639
642
|
// The band the scrolled bar's thumb travels in is repaired on its own.
|
|
640
643
|
// The thumb's rects are thin and run along the viewport's edge, and a
|
|
641
644
|
// rect reaching into the band from inside — a column held down the
|
|
@@ -730,8 +733,8 @@ export class WindowScrollBlit {
|
|
|
730
733
|
if (inside) painted += inside.width * inside.height;
|
|
731
734
|
}
|
|
732
735
|
if (painted > area * SCROLL_BLIT_MAX_REPAINT) return;
|
|
733
|
-
if (!
|
|
734
|
-
|
|
736
|
+
if (!target.blit({ ...vp }, dx, dy)) return;
|
|
737
|
+
target.damage = rects;
|
|
735
738
|
}
|
|
736
739
|
|
|
737
740
|
/**
|
|
@@ -751,12 +754,15 @@ export class WindowScrollBlit {
|
|
|
751
754
|
* damage cap merges neighbours on its own, without a claim-time gate to
|
|
752
755
|
* poison first. Requiring the rect back unchanged is what lets the
|
|
753
756
|
* claim-time gate narrow to `vp` itself and let furniture beside it live.
|
|
757
|
+
*
|
|
758
|
+
* `damage` is the list the blit's surface paints from: the window's, or
|
|
759
|
+
* its panes' for a node inside a `<glarea>` (`_blitTarget`).
|
|
754
760
|
*/
|
|
755
|
-
_blitKeptDamage(vp, exact = false) {
|
|
761
|
+
_blitKeptDamage(vp, exact = false, damage = this._damage) {
|
|
756
762
|
const keep = [];
|
|
757
763
|
let sawClaim = false;
|
|
758
764
|
const slopped = insetRect(vp, -(DAMAGE_SLOP + 1));
|
|
759
|
-
for (const rect of
|
|
765
|
+
for (const rect of damage) {
|
|
760
766
|
if (!rectsOverlap(rect, vp)) {
|
|
761
767
|
keep.push(rect);
|
|
762
768
|
continue;
|
|
@@ -795,7 +801,7 @@ export class WindowScrollBlit {
|
|
|
795
801
|
* balloon back towards the whole viewport. With no bars the L is two
|
|
796
802
|
* disjoint rects and stays two.
|
|
797
803
|
*/
|
|
798
|
-
_applyContentsBlit(node, { rect: vp, dx, dy }, width, height) {
|
|
804
|
+
_applyContentsBlit(node, { rect: vp, dx, dy }, width, height, target) {
|
|
799
805
|
// only a whole-pixel shift of a whole-pixel region is a copy
|
|
800
806
|
if (!isIntegerRect(vp)) return;
|
|
801
807
|
if (!Number.isInteger(dx) || !Number.isInteger(dy)) return;
|
|
@@ -840,14 +846,14 @@ export class WindowScrollBlit {
|
|
|
840
846
|
if (child.style?.display === 'none') continue;
|
|
841
847
|
if (rectsOverlap(child._subtreeBounds(), vp)) return;
|
|
842
848
|
}
|
|
843
|
-
const keep = this._blitKeptDamage(vp, true);
|
|
849
|
+
const keep = this._blitKeptDamage(vp, true, target.damage);
|
|
844
850
|
if (!keep) return;
|
|
845
851
|
// An ancestor's rounded corners reach into the top and bottom rows of
|
|
846
852
|
// the region (a graph pane inside a rounded card is the common shape):
|
|
847
853
|
// those rows do not translate, so they leave the blit and get repainted
|
|
848
854
|
// as bands — the same carve the element does for its own furniture —
|
|
849
855
|
// and the band that shifts is what is left between them.
|
|
850
|
-
const bands = this._cornerBands(node, vp);
|
|
856
|
+
const bands = this._cornerBands(node, vp, target.top);
|
|
851
857
|
const shifted =
|
|
852
858
|
bands.top || bands.bottom
|
|
853
859
|
? {
|
|
@@ -864,11 +870,11 @@ export class WindowScrollBlit {
|
|
|
864
870
|
) {
|
|
865
871
|
return;
|
|
866
872
|
}
|
|
867
|
-
if (!this._scrollBlitSafe(node, shifted)) return;
|
|
873
|
+
if (!this._scrollBlitSafe(node, shifted, target.top)) return;
|
|
868
874
|
// the element's deltas are already how far the pixels moved, the sense
|
|
869
875
|
// scrollRegion takes (0 + x rather than x: a caller's -0 would survive
|
|
870
876
|
// into request buffers and test comparisons)
|
|
871
|
-
if (!
|
|
877
|
+
if (!target.blit({ ...shifted }, 0 + dx, 0 + dy)) return;
|
|
872
878
|
let rects = keep;
|
|
873
879
|
if (bands.top) {
|
|
874
880
|
rects = addDamageRect(rects, {
|
|
@@ -908,7 +914,7 @@ export class WindowScrollBlit {
|
|
|
908
914
|
height: vp.height - Math.abs(dy),
|
|
909
915
|
});
|
|
910
916
|
}
|
|
911
|
-
|
|
917
|
+
target.damage = rects;
|
|
912
918
|
}
|
|
913
919
|
|
|
914
920
|
/**
|
|
@@ -916,11 +922,13 @@ export class WindowScrollBlit {
|
|
|
916
922
|
* corners reach into — the rows an element blit has to leave behind and
|
|
917
923
|
* repaint, so that what shifts stays clear of every corner square
|
|
918
924
|
* (`_scrollBlitSafe`). Whole pixels, and zero when no corner reaches in.
|
|
925
|
+
* Ancestors below `upTo`, the node the blit's surface belongs to
|
|
926
|
+
* (`_blitTarget`): a pane is a rectangle no corner above it clips.
|
|
919
927
|
*/
|
|
920
|
-
_cornerBands(node, vp) {
|
|
928
|
+
_cornerBands(node, vp, upTo = this) {
|
|
921
929
|
let top = 0;
|
|
922
930
|
let bottom = 0;
|
|
923
|
-
for (let n = node.parent; n && n !==
|
|
931
|
+
for (let n = node.parent; n && n !== upTo; n = n.parent) {
|
|
924
932
|
const radius = n.style?.borderRadius ?? 0;
|
|
925
933
|
if (!(radius > 0) || !n.abs) continue;
|
|
926
934
|
if (!cornerSquaresOverlap(n.abs, radius, vp)) continue;
|
|
@@ -940,13 +948,18 @@ export class WindowScrollBlit {
|
|
|
940
948
|
* the viewport — an overlapping sibling, an ancestor's border ring or
|
|
941
949
|
* rounded corner, an enclosing viewport's scrollbar — would have its
|
|
942
950
|
* pixels dragged along by the blit, so any of them is a no.
|
|
951
|
+
*
|
|
952
|
+
* "Outside" is below `top`, the node whose subtree the blit's surface
|
|
953
|
+
* holds (`_blitTarget`): the window, or a `<glarea>` whose pane holds
|
|
954
|
+
* only its own children — what the window draws under the surface was
|
|
955
|
+
* never in the pane's pixels.
|
|
943
956
|
*/
|
|
944
|
-
_scrollBlitSafe(scroller, vp) {
|
|
957
|
+
_scrollBlitSafe(scroller, vp, top = this) {
|
|
945
958
|
// the window itself scrolls: everything inside it *is* the scrolled
|
|
946
959
|
// content, so there is nothing that could be dragged along
|
|
947
960
|
if (scroller === this) return true;
|
|
948
961
|
const ancestors = new Set();
|
|
949
|
-
for (let n = scroller.parent; n && n !==
|
|
962
|
+
for (let n = scroller.parent; n && n !== top; n = n.parent) {
|
|
950
963
|
ancestors.add(n);
|
|
951
964
|
}
|
|
952
965
|
const check = (parent) => {
|
|
@@ -993,7 +1006,60 @@ export class WindowScrollBlit {
|
|
|
993
1006
|
}
|
|
994
1007
|
return true;
|
|
995
1008
|
};
|
|
996
|
-
return check(
|
|
1009
|
+
return check(top);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
/**
|
|
1013
|
+
* Where a blit for `node` moves pixels, as `{ top, blit(rect, dx, dy),
|
|
1014
|
+
* damage }` — `blit` with `Window.scrollRegion`'s contract, `top` the node
|
|
1015
|
+
* whose subtree that surface holds, `damage` the list it paints from and
|
|
1016
|
+
* the blit narrows — or null when nothing can.
|
|
1017
|
+
*
|
|
1018
|
+
* The window's backing store, for a node the window paints. A node inside
|
|
1019
|
+
* a `<glarea>` is painted on the pane over the surface instead
|
|
1020
|
+
* (src/gloverlay.js), and the window's pixels under the surface are none
|
|
1021
|
+
* of its: shifting them moved nothing on screen while the strip-only
|
|
1022
|
+
* repaint left the pane holding the scroll's old frame (issue #644). So
|
|
1023
|
+
* the pane that holds the rect moves it, or the frame repaints — a pane
|
|
1024
|
+
* owed a paint whole, or none holding the whole rect, is a refusal like
|
|
1025
|
+
* any other here.
|
|
1026
|
+
*/
|
|
1027
|
+
_blitTarget(node) {
|
|
1028
|
+
for (let n = node.parent; n && n !== this; n = n.parent) {
|
|
1029
|
+
if (!n.isGlArea) continue;
|
|
1030
|
+
const overlay = n._overlay;
|
|
1031
|
+
if (!overlay) return null;
|
|
1032
|
+
const root = this;
|
|
1033
|
+
return {
|
|
1034
|
+
top: n,
|
|
1035
|
+
blit: (rect, dx, dy) => {
|
|
1036
|
+
const pane = overlay.panes.find((p) => p.canBlit(rect));
|
|
1037
|
+
return pane ? pane.blit(rect, dx, dy) : false;
|
|
1038
|
+
},
|
|
1039
|
+
// the panes' list: a scroll inside a surface claims nothing in the
|
|
1040
|
+
// window's, which paints none of its children (`_paneReach`)
|
|
1041
|
+
get damage() {
|
|
1042
|
+
return root._paneDamage;
|
|
1043
|
+
},
|
|
1044
|
+
set damage(rects) {
|
|
1045
|
+
root._paneDamage = rects;
|
|
1046
|
+
},
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
const wnd = this.window;
|
|
1050
|
+
// an ntk without #139, or a presenter with no bitmap to move
|
|
1051
|
+
if (typeof wnd?.scrollRegion !== 'function') return null;
|
|
1052
|
+
const root = this;
|
|
1053
|
+
return {
|
|
1054
|
+
top: this,
|
|
1055
|
+
blit: (rect, dx, dy) => wnd.scrollRegion(rect, dx, dy),
|
|
1056
|
+
get damage() {
|
|
1057
|
+
return root._damage;
|
|
1058
|
+
},
|
|
1059
|
+
set damage(rects) {
|
|
1060
|
+
root._damage = rects;
|
|
1061
|
+
},
|
|
1062
|
+
};
|
|
997
1063
|
}
|
|
998
1064
|
|
|
999
1065
|
/**
|