react-x11 2.33.0 → 2.34.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.33.0",
3
+ "version": "2.34.0",
4
4
  "description": "react renderer with X11 as a target",
5
5
  "main": "./src/index.js",
6
6
  "files": [
@@ -28,11 +28,20 @@
28
28
  // delay a part joined half way through needs, so that is the bridge sprites
29
29
  // ask for: on an older one nothing is asked and every part is drawn by its
30
30
  // element.
31
+ //
32
+ // A part may be inside another of its element's (`parent`): a spinner in a
33
+ // card that fades. Its layer is lifted inside the parent's, so it fades,
34
+ // turns and is cut with it, and the parent's raster leaves it out — the
35
+ // presenter hands the parent's `paint` the keys of its children lifted with
36
+ // it, and paints it again whenever they change, in the frame they do. The
37
+ // parent was asked about everywhere it can be, so a child is asked about
38
+ // nothing more where it stays inside the parent's raster, and is its
39
+ // element's to draw where it does not, or where its parent is not lifted.
31
40
 
32
41
  import { RASTER_PAD, RasterState } from './presenter.js';
33
42
  import { CocoaPlayer, PlayerLift } from './player.js';
34
43
  import { VideoLift, canLiftVideo } from './video.js';
35
- import { intersectRects } from '../nodes/rects.js';
44
+ import { intersectRects, rectContains } from '../nodes/rects.js';
36
45
  import { DEV } from '../nodes/util.js';
37
46
  import { isVideoFrames } from '../videoframes.js';
38
47
 
@@ -47,6 +56,11 @@ const AFTER_CHILDREN = Number.MAX_SAFE_INTEGER;
47
56
 
48
57
  const IDENTITY = Object.freeze([1, 0, 0, 1, 0, 0]);
49
58
 
59
+ const ORIGIN = Object.freeze({ x: 0, y: 0 });
60
+
61
+ // what a part with no part lifted inside it is handed
62
+ const NO_KIDS = Object.freeze(new Set());
63
+
50
64
  // what a part's raster holds before its first paint
51
65
  const UNPAINTED = Symbol('unpainted');
52
66
 
@@ -127,6 +141,9 @@ function isSprite(sprite) {
127
141
  }
128
142
  if (sprite.reach !== undefined && !isRect(sprite.reach)) return false;
129
143
  if (sprite.clip !== undefined && !isRect(sprite.clip)) return false;
144
+ if (sprite.parent !== undefined && typeof sprite.parent !== 'string') {
145
+ return false;
146
+ }
130
147
  if (
131
148
  sprite.clipRadius !== undefined &&
132
149
  !(finite(sprite.clipRadius) && sprite.clipRadius >= 0)
@@ -331,6 +348,15 @@ export class SpriteLayers {
331
348
  continue;
332
349
  }
333
350
  if (now.has(sprite.key)) continue;
351
+ // inside another of the element's parts: lifted with it or not at
352
+ // all, its parent earlier in the list, and neither of them a source
353
+ let parent = null;
354
+ if (sprite.parent !== undefined) {
355
+ parent = now.get(sprite.parent) ?? null;
356
+ if (!parent || parent.contents !== null || sprite.contents != null) {
357
+ continue;
358
+ }
359
+ }
334
360
  const contents = sprite.contents ?? null;
335
361
  // a source this bridge cannot show: the element draws it
336
362
  if (contents !== null && !this._canShow(contents)) continue;
@@ -352,32 +378,52 @@ export class SpriteLayers {
352
378
  };
353
379
  const extent = extentOf(sprite);
354
380
  const bounds = cut.clip ? intersectRects(extent, cut.clip) : extent;
355
- if (!bounds || !this.promotion._clear(host, bounds, cut)) continue;
381
+ if (!bounds) continue;
382
+ if (parent) {
383
+ // the parent was asked about everywhere it can be, and its layer
384
+ // carries this one: nothing more to ask where it stays inside the
385
+ // parent's raster, and its element draws it where it does not
386
+ if (!rectContains(rasterRectOf(parent.sprite), bounds)) continue;
387
+ cut.bounds = bounds;
388
+ } else if (!this.promotion._clear(host, bounds, cut)) {
389
+ continue;
390
+ }
356
391
  const shown = cut.bounds;
357
392
  let state = was?.get(sprite.key);
358
393
  // a clip that comes or goes is a layer in a box or out of one: lifted
359
394
  // again, rather than a running layer moved between parents — and so
360
- // is a part that shows another source, or stops painting to show one
395
+ // is a part that shows another source, or stops painting to show
396
+ // one, and one in another layer than it was
361
397
  if (
362
398
  state &&
363
399
  (Boolean(state.clip) !== Boolean(cut.clip) ||
364
- state.contents !== contents)
400
+ state.contents !== contents ||
401
+ state.parent !== parent)
365
402
  ) {
366
403
  this._drop(state, root);
367
404
  state = null;
368
405
  }
369
406
  const lifting = !state;
370
407
  if (lifting) {
371
- state = this._lift(host, sprite.key, cut.clip !== null, contents);
408
+ state = this._lift(
409
+ host,
410
+ sprite.key,
411
+ cut.clip !== null,
412
+ contents,
413
+ parent,
414
+ );
372
415
  }
373
416
  state.sprite = sprite;
374
417
  state.clip = cut.clip;
375
418
  state.radius = cut.radius;
376
- state.order = [...place, AFTER_CHILDREN, index];
419
+ state.order = parent ? null : [...place, AFTER_CHILDREN, index];
420
+ state.kids = [];
421
+ parent?.kids.push(state);
377
422
  // the bitmap under where it shows repaints without it, from this
378
423
  // frame on; where it went since, it is the element's own claims
379
- // that move the hole
380
- if (lifting) this.promotion._claim(root, shown);
424
+ // that move the hole. A child is under its parent's layer, whose
425
+ // raster leaves it out instead (`sync`).
426
+ if (lifting && !parent) this.promotion._claim(root, shown);
381
427
  state.extent = shown;
382
428
  now.set(sprite.key, state);
383
429
  }
@@ -396,11 +442,12 @@ export class SpriteLayers {
396
442
  return changed;
397
443
  }
398
444
 
399
- /** Every lifted part, with its place in the paint order. */
445
+ /** Every lifted part above the bitmap, with its place in the paint
446
+ * order: not one inside another's layer, which goes with it (`sync`). */
400
447
  entries() {
401
448
  const out = [];
402
449
  for (const lifted of this.hosts.values()) {
403
- for (const state of lifted.values()) out.push(state);
450
+ for (const state of lifted.values()) if (!state.parent) out.push(state);
404
451
  }
405
452
  return out;
406
453
  }
@@ -426,23 +473,29 @@ export class SpriteLayers {
426
473
  return isVideoFrames(contents) && this.videos;
427
474
  }
428
475
 
429
- _lift(host, key, clipped, contents = null) {
476
+ _lift(host, key, clipped, contents = null, parent = null) {
430
477
  const layer = this.native.createLayer();
431
478
  // a part cut to a clip is in a box of the clip's size that masks to it,
432
- // and the box is what stands among the layers above the bitmap
479
+ // and the box is what stands among the layers above the bitmap — or
480
+ // among its parent's children, for a part inside another's layer
481
+ const above = parent ? parent.layer : this.promotion.rootVisual.layer;
433
482
  let box = null;
434
483
  if (clipped) {
435
484
  box = this.native.createLayer();
436
- this.native.addSublayer(this.promotion.rootVisual.layer, box);
485
+ this.native.addSublayer(above, box);
437
486
  this.native.addSublayer(box, layer);
438
487
  } else {
439
- this.native.addSublayer(this.promotion.rootVisual.layer, layer);
488
+ this.native.addSublayer(above, layer);
440
489
  }
441
490
  return {
442
491
  host,
443
492
  key,
444
493
  layer,
445
494
  box,
495
+ // the part whose layer this one is in, and the parts in this one's
496
+ parent,
497
+ kids: [],
498
+ paintedKids: '',
446
499
  boxProps: {},
447
500
  clip: null, // device pixels, window coordinates
448
501
  radius: 0, // the clip's corners, device pixels
@@ -479,7 +532,9 @@ export class SpriteLayers {
479
532
  state.raster.release(this.native);
480
533
  for (const run of state.runs.values()) this._ends().delete(run.caId);
481
534
  state.runs.clear();
482
- if (!state.host.destroyed && state.extent) {
535
+ // a child's is under its parent's layer, which paints it again or goes
536
+ // with it
537
+ if (!state.host.destroyed && state.extent && !state.parent) {
483
538
  this.promotion._claim(root, state.extent);
484
539
  }
485
540
  }
@@ -491,28 +546,41 @@ export class SpriteLayers {
491
546
  const rect = rasterRectOf(sprite);
492
547
  const reach = sprite.reach ?? sprite.rect;
493
548
  const phase = `${reach.x - Math.floor(reach.x)},${reach.y - Math.floor(reach.y)}`;
549
+ // the parts lifted inside it, which its raster leaves out
550
+ const kids = state.kids.map((kid) => kid.key).join('\n');
494
551
  // painted again for a new version, and for a raster of another size or
495
- // with the part at another fraction of a pixel; a move by whole pixels
496
- // is the layer's alone. A source paints nothing: it shows itself.
552
+ // with the part at another fraction of a pixel, or with other parts
553
+ // lifted inside it; a move by whole pixels is the layer's alone. A
554
+ // source paints nothing: it shows itself.
497
555
  if (
498
556
  !state.lift &&
499
557
  (state.painted === UNPAINTED ||
500
558
  sprite.version !== state.painted ||
501
559
  state.rect?.width !== rect.width ||
502
560
  state.rect?.height !== rect.height ||
503
- state.phase !== phase)
561
+ state.phase !== phase ||
562
+ state.paintedKids !== kids)
504
563
  ) {
505
564
  this._paint(state, sprite, rect);
506
565
  state.phase = phase;
566
+ state.paintedKids = kids;
507
567
  }
508
568
  state.rect = rect;
509
569
  const origin = originOf(sprite);
510
570
  const m = sprite.transform ?? IDENTITY;
571
+ // where its layer's coordinates start: the window's corner, or the
572
+ // corner of its parent's raster, for a part inside another's layer
573
+ const base = state.parent?.rect ?? ORIGIN;
511
574
  // in its box, where it is cut to a clip: placed from the clip's corner,
512
575
  // and the box at the part's place among the layers
513
576
  const clip = state.clip;
514
577
  if (state.box) {
515
- const frame = [clip.x / s, clip.y / s, clip.width / s, clip.height / s];
578
+ const frame = [
579
+ (clip.x - base.x) / s,
580
+ (clip.y - base.y) / s,
581
+ clip.width / s,
582
+ clip.height / s,
583
+ ];
516
584
  // its corners rounded where the clip's are: a circle's arc, as wide
517
585
  // as the box allows
518
586
  const cornerRadius =
@@ -532,8 +600,8 @@ export class SpriteLayers {
532
600
  state.boxProps = { frame, zPosition: z, cornerRadius };
533
601
  }
534
602
  }
535
- const ox = state.box ? clip.x : 0;
536
- const oy = state.box ? clip.y : 0;
603
+ const ox = state.box ? clip.x : base.x;
604
+ const oy = state.box ? clip.y : base.y;
537
605
  const next = {
538
606
  bounds: [0, 0, rect.width / s, rect.height / s],
539
607
  anchorPoint: [
@@ -570,6 +638,10 @@ export class SpriteLayers {
570
638
  state.lift?.layout?.(next.bounds);
571
639
  // after the model went out, in the same transaction
572
640
  this._syncAnimations(state, sprite);
641
+ // the parts inside it, in the element's order, over its raster
642
+ for (let i = 0; i < state.kids.length; i++) {
643
+ this.sync(state.kids[i], i + 1);
644
+ }
573
645
  }
574
646
 
575
647
  _paint(state, sprite, rect) {
@@ -586,7 +658,10 @@ export class SpriteLayers {
586
658
  ctx.translate(-rect.x, -rect.y);
587
659
  // a part paints whole: there is no pass to cull it by
588
660
  if (root) root._paintDamage = null;
589
- sprite.paint(ctx);
661
+ sprite.paint(
662
+ ctx,
663
+ state.kids.length ? new Set(state.kids.map((kid) => kid.key)) : NO_KIDS,
664
+ );
590
665
  } catch (err) {
591
666
  this._warn(
592
667
  state.host,
package/src/node.d.ts CHANGED
@@ -297,8 +297,16 @@ export interface Sprite {
297
297
  * cannot take both. */
298
298
  clipRadius?: number;
299
299
  /** Draws the part where it is in the window, as `paint` would, but at
300
- * opacity 1 and untransformed. A part with `contents` has none. */
301
- paint?(ctx: Context2D): void;
300
+ * opacity 1 and untransformed — and without the parts lifted inside it
301
+ * (`parent`), whose keys the presenter hands it as `children`. A part
302
+ * with `contents` has none. */
303
+ paint?(ctx: Context2D, children?: ReadonlySet<string>): void;
304
+ /** The key of another of the element's parts this one is inside, earlier
305
+ * in the list: its layer goes in that part's layer, fading, turning and
306
+ * cut with it, and is lifted only with it, and only where everywhere it
307
+ * can be is inside that part's raster. Its `rect`, `origin` and `clip`
308
+ * are the window's, with its parent untransformed. */
309
+ parent?: string;
302
310
  /**
303
311
  * A source that shows itself on the part's layer instead of a paint — a
304
312
  * `VideoFrames` sink, whose every push reaches the layer with no frame of