react-x11 2.21.2 → 2.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-x11",
3
- "version": "2.21.2",
3
+ "version": "2.22.0",
4
4
  "description": "react renderer with X11 as a target",
5
5
  "main": "./src/index.js",
6
6
  "files": [
@@ -110,7 +110,7 @@
110
110
  "optionalDependencies": {
111
111
  "@windowkit/appkit": "^0.13.0",
112
112
  "@windowkit/wayland": "^3.1.1",
113
- "@windowkit/win32": "^0.0.4",
113
+ "@windowkit/win32": "^0.0.5",
114
114
  "dbus-native": "^0.15.1",
115
115
  "x11-dri": "^0.9.0"
116
116
  },
package/src/gloverlay.js CHANGED
@@ -66,8 +66,10 @@ import { FULL_DAMAGE, addDamageRect } from './nodes/damage.js';
66
66
  import {
67
67
  innerPixels,
68
68
  intersectRects,
69
+ outside,
69
70
  rectArea,
70
71
  rectContains,
72
+ shiftRect,
71
73
  unionRect,
72
74
  } from './nodes/rects.js';
73
75
 
@@ -197,48 +199,6 @@ function around(a, b) {
197
199
  const sameRect = (a, b) =>
198
200
  a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
199
201
 
200
- const shifted = (r, dx, dy) => ({
201
- x: r.x + dx,
202
- y: r.y + dy,
203
- width: r.width,
204
- height: r.height,
205
- });
206
-
207
- /** `a` with `b` taken out of it: up to four rects — the full-width bands
208
- * above and below `b`, and the pieces either side of it between them. */
209
- function subtractRect(a, b) {
210
- const cut = intersectRects(a, b);
211
- if (!cut) return [a];
212
- const out = [];
213
- const right = a.x + a.width;
214
- const bottom = a.y + a.height;
215
- const cutRight = cut.x + cut.width;
216
- const cutBottom = cut.y + cut.height;
217
- if (cut.y > a.y) {
218
- out.push({ x: a.x, y: a.y, width: a.width, height: cut.y - a.y });
219
- }
220
- if (cutBottom < bottom) {
221
- out.push({
222
- x: a.x,
223
- y: cutBottom,
224
- width: a.width,
225
- height: bottom - cutBottom,
226
- });
227
- }
228
- if (cut.x > a.x) {
229
- out.push({ x: a.x, y: cut.y, width: cut.x - a.x, height: cut.height });
230
- }
231
- if (cutRight < right) {
232
- out.push({
233
- x: cutRight,
234
- y: cut.y,
235
- width: right - cutRight,
236
- height: cut.height,
237
- });
238
- }
239
- return out;
240
- }
241
-
242
202
  /** On screen: nothing from here to the window hidden or `display: 'none'`. */
243
203
  function shown(node) {
244
204
  for (let n = node; n; n = n.parent) {
@@ -594,7 +554,7 @@ export class GlOverlay {
594
554
  );
595
555
  if (!pane) return false;
596
556
  const view = intersectRects(pane.rect, box);
597
- const dest = intersectRects(shifted(was, dx, dy), view);
557
+ const dest = intersectRects(shiftRect(was, dx, dy), view);
598
558
  if (!dest) return false;
599
559
  const cap = root._damageRectCap();
600
560
  const claimed = root._paneDamage ?? [];
@@ -603,14 +563,14 @@ export class GlOverlay {
603
563
  if (rect) rects = addDamageRect(rects, rect, cap);
604
564
  };
605
565
  for (const claim of claimed)
606
- add(intersectRects(shifted(claim, dx, dy), dest));
607
- for (const piece of subtractRect(was, dest)) add(piece);
608
- if (now) for (const piece of subtractRect(now, dest)) add(piece);
566
+ add(intersectRects(shiftRect(claim, dx, dy), dest));
567
+ for (const piece of outside(was, dest)) add(piece);
568
+ if (now) for (const piece of outside(now, dest)) add(piece);
609
569
  for (const other of area.paintOrder()) {
610
570
  if (other === node) continue;
611
571
  const theirs = whole(other.paintBounds());
612
572
  add(intersectRects(theirs, dest));
613
- add(intersectRects(shifted(theirs, dx, dy), dest));
573
+ add(intersectRects(shiftRect(theirs, dx, dy), dest));
614
574
  }
615
575
  // the list is disjoint, so the sum is the area it covers
616
576
  let repainted = 0;
@@ -621,7 +581,7 @@ export class GlOverlay {
621
581
  if (repainted > rectArea(dest) * MOVE_BLIT_MAX_REPAINT) return false;
622
582
  // the verb moves the band that survives inside the rect it is handed,
623
583
  // so handed where the pixels are and where they go, that band is `dest`
624
- if (!pane.blit(unionRect(shifted(dest, -dx, -dy), dest), dx, dy)) {
584
+ if (!pane.blit(unionRect(shiftRect(dest, -dx, -dy), dest), dx, dy)) {
625
585
  return false;
626
586
  }
627
587
  root._paneDamage = rects;
package/src/node.d.ts CHANGED
@@ -553,12 +553,22 @@ export declare class Node {
553
553
  * what it leaves or reaches outside `rect` is repainted, so keep what it
554
554
  * can paint inside the element (a box that clips it). Anything else it
555
555
  * does is claimed as usual, which declines the blit.
556
+ *
557
+ * `pinned` is the other way round: furniture inside `rect` that stays put
558
+ * while everything around it moves — a minimap, zoom controls — as rects
559
+ * in the same coordinates. The copy drags a stale image of each along, so
560
+ * the frame repaints the pinned rect and that image on every pan frame,
561
+ * changed or not; a claim inside a pinned rect, or a node that lies inside
562
+ * one, leaves the blit standing. Pin furniture in a corner rather than
563
+ * carving it out of `rect`: what shifts is one rectangle, so a carved
564
+ * corner takes the rows beside it too.
556
565
  */
557
566
  scrollContents(
558
567
  rect: Rect,
559
568
  dx: number,
560
569
  dy: number,
561
- riders?: Iterable<Node>,
570
+ riders?: Iterable<Node> | null,
571
+ pinned?: Iterable<Rect> | null,
562
572
  ): boolean;
563
573
  /**
564
574
  * Did anything this node draws change? True damages the whole node, false
@@ -50,12 +50,14 @@ export const layoutDiff = {
50
50
  // else, and restored through `finally` like the sink beside it.
51
51
  shift: null,
52
52
 
53
- // True when `shift` is a `<glarea>` child's own move rather than a scroll
54
- // (src/glnodes.js, `_absolutizeChild`, issue #644): the overlay moves the
55
- // child's pixels on its pane, and a node that lands where it was plus the
56
- // shift is carried by that. A node that turns the diff off under a shift —
57
- // a scroll pane laid out again, whose box would have claimed its children
58
- // — claims its own box instead, since the move claimed nothing for it.
53
+ // True when `shift` is a subtree's own move whose pixels a copy may carry,
54
+ // rather than a scroll: a `<glarea>` child's, moved on its pane
55
+ // (src/glnodes.js, `_absolutizeChild`, issue #644), or one the window may
56
+ // copy in its backing (nodes/moveblit.js, issue #681). A node that lands
57
+ // where it was plus the shift is carried by that copy. A node that turns
58
+ // the diff off under a shift — a scroll pane laid out again, whose box
59
+ // would have claimed its children — claims its own box instead, since the
60
+ // move claimed nothing for it.
59
61
  ride: false,
60
62
  };
61
63
 
@@ -65,6 +67,7 @@ export const layoutDiff = {
65
67
  // DEV validates against this list.
66
68
  export const INVALIDATE_REASONS = new Set([
67
69
  'props', // a React commit changed what a node draws
70
+ 'position', // an absolute box of a set size moved and changed nothing else
68
71
  'style-state', // :hover/:focus/:active/:disabled restyle
69
72
  'shadow', // a boxShadow got smaller: where it *was* still owes a repaint
70
73
  'outline', // …and the same for an outline a style swap took away
@@ -387,7 +387,7 @@ export class WindowInvalidate {
387
387
  // viewport — and the wider zone is what keeps it out.
388
388
  //
389
389
  // The difference is what lets an element carve furniture out of the
390
- // rect it blits — a minimap pinned to a corner, a strip it repaints
390
+ // rect it blits — a HUD strip along the pane's edge that it repaints
391
391
  // itself — and keep the pan at blit cost while that furniture
392
392
  // claims beside it.
393
393
  const contents = sv._pendingBlitContents;
@@ -404,6 +404,12 @@ export class WindowInvalidate {
404
404
  if (sv._blitLedgerOpen() && sv._recordBlitClaim(rect)) {
405
405
  continue;
406
406
  }
407
+ // …or it lies inside furniture an element pinned over what it
408
+ // shifts (issue #682): the frame repaints the pinned rect, and
409
+ // the image of it the copy drags along, whether or not anything
410
+ // claims there — so a claim inside one changes nothing about
411
+ // the pixels the blit moves.
412
+ if (sv._pinnedHolds(rect)) continue;
407
413
  // Poison rather than disarm (react-x11#295): a null here would
408
414
  // let a second scrollTo in the same frame re-arm from a
409
415
  // mid-frame origin, and the blit would then move pixels that
@@ -446,6 +452,9 @@ export class WindowInvalidate {
446
452
  bounds,
447
453
  this._damageRectCap(),
448
454
  );
455
+ // …and whose claim it was, for a copy of a subtree that moved
456
+ // (`_settleRigidMoves`): what is under the subtree it covers
457
+ this._logClaim(bounds, damage?.kind ? damage : source);
449
458
  }
450
459
  if (panes) this._addPaneDamage(bounds);
451
460
  }
@@ -497,6 +506,7 @@ export class WindowInvalidate {
497
506
  let windowTook = false;
498
507
  if (reach !== PANES_ONLY && this._damage !== FULL_DAMAGE) {
499
508
  this._damage = addDamageRect(this._damage, rect, cap);
509
+ this._logClaim(rect, node ?? null);
500
510
  windowTook = true;
501
511
  }
502
512
  if (reach !== WINDOW_ONLY) this._addPaneDamage(rect);
@@ -557,6 +567,8 @@ export class WindowInvalidate {
557
567
  _takeDamage(width, height) {
558
568
  const damage = this._damage;
559
569
  this._damage = null;
570
+ // the next frame's claims start a log of their own
571
+ this._claimLog = [];
560
572
  // what the frame about to run settled on, for the tests and for
561
573
  // REACT_X11_DEBUG_LAYOUT to report; null means it repainted everything.
562
574
  // `_lastDamage` is the box around the rects, which is what a caller
@@ -5,54 +5,9 @@
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 { insetRect, intersectRects, outerPixels } from './rects.js';
8
+ import { insetRect, intersectRects, outerPixels, outside } from './rects.js';
9
9
  import { DEV } from './util.js';
10
10
 
11
- /** The parts of `rect` outside `hole`: up to four bands, full width above
12
- * and below, the rest beside. */
13
- function outside(rect, hole) {
14
- const cut = intersectRects(rect, hole);
15
- if (!cut) return [rect];
16
- const out = [];
17
- const right = rect.x + rect.width;
18
- const bottom = rect.y + rect.height;
19
- if (cut.y > rect.y) {
20
- out.push({
21
- x: rect.x,
22
- y: rect.y,
23
- width: rect.width,
24
- height: cut.y - rect.y,
25
- });
26
- }
27
- const cutBottom = cut.y + cut.height;
28
- if (cutBottom < bottom) {
29
- out.push({
30
- x: rect.x,
31
- y: cutBottom,
32
- width: rect.width,
33
- height: bottom - cutBottom,
34
- });
35
- }
36
- if (cut.x > rect.x) {
37
- out.push({
38
- x: rect.x,
39
- y: cut.y,
40
- width: cut.x - rect.x,
41
- height: cut.height,
42
- });
43
- }
44
- const cutRight = cut.x + cut.width;
45
- if (cutRight < right) {
46
- out.push({
47
- x: cutRight,
48
- y: cut.y,
49
- width: right - cutRight,
50
- height: cut.height,
51
- });
52
- }
53
- return out;
54
- }
55
-
56
11
  export const MEASURE_MODES = [];
57
12
  MEASURE_MODES[Yoga.MEASURE_MODE_UNDEFINED] = 'unconstrained';
58
13
  MEASURE_MODES[Yoga.MEASURE_MODE_EXACTLY] = 'exactly';
@@ -264,7 +219,10 @@ export class NodeLayout {
264
219
  }
265
220
  }
266
221
  } finally {
267
- if (move) layoutDiff.shift = null;
222
+ if (move) {
223
+ layoutDiff.shift = null;
224
+ layoutDiff.ride = false;
225
+ }
268
226
  }
269
227
  if (move) this._claimRigidMove(move);
270
228
  }
@@ -357,6 +315,12 @@ export class NodeLayout {
357
315
  *
358
316
  * Null when the move is not of that kind, or no diff is listening; the
359
317
  * walk is then the plain one.
318
+ *
319
+ * A move the window may copy rather than repaint (`_mayBlitMove`, issue
320
+ * #681) is walked as a ride as well, the way a `<glarea>` child's is
321
+ * (`layoutDiff.ride`): a scroll pane inside it that is laid out again
322
+ * claims its viewport, since a copy of the pane would carry rows that
323
+ * moved inside it and nothing else claims them.
360
324
  */
361
325
  _beginRigidMove(x, y, width, height) {
362
326
  if (!layoutDiff.sink || layoutDiff.shift) return null;
@@ -379,7 +343,9 @@ export class NodeLayout {
379
343
  contents && contents.dx === shift.x && contents.dy === shift.y
380
344
  ? contents.rect
381
345
  : null;
382
- return { sink: layoutDiff.sink, was, shift, carried };
346
+ const defer = !carried && this.root._mayBlitMove(this, shift);
347
+ if (defer) layoutDiff.ride = true;
348
+ return { sink: layoutDiff.sink, was, shift, carried, defer };
383
349
  }
384
350
 
385
351
  /**
@@ -391,9 +357,18 @@ export class NodeLayout {
391
357
  * Named for the node that moved, which is what sends each claim to the
392
358
  * lists that paint it (`_paneReach`): the panes alone inside a
393
359
  * `<glarea>`, both for a subtree holding one.
360
+ *
361
+ * A move the window may copy is handed to it instead, and claimed once
362
+ * the frame's other claims are in (`WindowNode._settleRigidMoves`): the
363
+ * copy, and so the question of what is left to paint, needs to know what
364
+ * else changed around the subtree.
394
365
  */
395
- _claimRigidMove({ sink, was, shift, carried }) {
366
+ _claimRigidMove({ sink, was, shift, carried, defer }) {
396
367
  const root = this.root;
368
+ if (defer) {
369
+ root._deferRigidMove(this, was, shift);
370
+ return;
371
+ }
397
372
  const to = {
398
373
  x: was.x + shift.x,
399
374
  y: was.y + shift.y,