react-x11 2.38.0 → 2.39.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.38.0",
3
+ "version": "2.39.0",
4
4
  "description": "react renderer with X11 as a target",
5
5
  "main": "./src/index.js",
6
6
  "files": [
@@ -27,7 +27,10 @@
27
27
  // `@windowkit/appkit` 0.19 (`transformForms()`) along with the negative
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
- // element.
30
+ // element. Or it is CSS's `matrix3d()`, a part turned out of the plane and
31
+ // seen in a perspective, which Core Animation draws through the whole
32
+ // matrix on the GPU: a part none of whose corners is behind the viewer, on a
33
+ // bridge that takes one (`matrix3d`).
31
34
  //
32
35
  // A part none of which shows — scrolled out of its pane, or outside its own
33
36
  // clip — keeps its layer, cut to show nothing, up to a budget of raster for
@@ -68,6 +71,14 @@ const AFTER_CHILDREN = Number.MAX_SAFE_INTEGER;
68
71
 
69
72
  const IDENTITY = Object.freeze([1, 0, 0, 1, 0, 0]);
70
73
 
74
+ // what a part with no bounds may reach (`_underDrawn`)
75
+ const EVERYWHERE = Object.freeze({
76
+ x: -1e9,
77
+ y: -1e9,
78
+ width: 2e9,
79
+ height: 2e9,
80
+ });
81
+
71
82
  const ORIGIN = Object.freeze({ x: 0, y: 0 });
72
83
 
73
84
  // what a part with no part lifted inside it is handed
@@ -98,7 +109,20 @@ const isRect = (r) =>
98
109
  r.width > 0 &&
99
110
  r.height > 0;
100
111
 
101
- const isMatrix = (m) => Array.isArray(m) && m.length === 6 && m.every(finite);
112
+ // CSS's `matrix(a, b, c, d, e, f)`, or its `matrix3d()`'s sixteen numbers
113
+ const isMatrix = (m) =>
114
+ Array.isArray(m) && (m.length === 6 || m.length === 16) && m.every(finite);
115
+
116
+ const isSolid = (m) => m.length === 16;
117
+
118
+ /** Does `sprite` turn out of the plane, at rest or in an animation? */
119
+ function solidSprite(sprite) {
120
+ if (sprite.transform && isSolid(sprite.transform)) return true;
121
+ for (const a of sprite.animations ?? []) {
122
+ if (a.property === 'transform' && a.values.some(isSolid)) return true;
123
+ }
124
+ return false;
125
+ }
102
126
 
103
127
  const isCurve = (t) =>
104
128
  typeof t === 'string' ||
@@ -203,12 +227,15 @@ const originOf = (sprite) =>
203
227
  y: sprite.rect.y + sprite.rect.height / 2,
204
228
  };
205
229
 
206
- /** `rect` through `m` about `origin`: the bounds of its four corners. */
230
+ /** `rect` through `m` about `origin`: the bounds of its four corners, or
231
+ * null where `m` is a matrix3d that puts a corner behind the viewer, past
232
+ * which the plane's picture has no bounds. */
207
233
  function mapRect(rect, m, origin) {
208
234
  let x0 = Infinity;
209
235
  let y0 = Infinity;
210
236
  let x1 = -Infinity;
211
237
  let y1 = -Infinity;
238
+ const solid = isSolid(m);
212
239
  for (const [px, py] of [
213
240
  [rect.x, rect.y],
214
241
  [rect.x + rect.width, rect.y],
@@ -217,8 +244,18 @@ function mapRect(rect, m, origin) {
217
244
  ]) {
218
245
  const dx = px - origin.x;
219
246
  const dy = py - origin.y;
220
- const x = m[0] * dx + m[2] * dy + m[4] + origin.x;
221
- const y = m[1] * dx + m[3] * dy + m[5] + origin.y;
247
+ let x;
248
+ let y;
249
+ if (solid) {
250
+ // column-major, as `matrix3d()` writes it: the point (dx, dy, 0, 1)
251
+ const w = m[3] * dx + m[7] * dy + m[15];
252
+ if (!(w > W_NEAR)) return null;
253
+ x = (m[0] * dx + m[4] * dy + m[12]) / w + origin.x;
254
+ y = (m[1] * dx + m[5] * dy + m[13]) / w + origin.y;
255
+ } else {
256
+ x = m[0] * dx + m[2] * dy + m[4] + origin.x;
257
+ y = m[1] * dx + m[3] * dy + m[5] + origin.y;
258
+ }
222
259
  if (x < x0) x0 = x;
223
260
  if (y < y0) y0 = y;
224
261
  if (x > x1) x1 = x;
@@ -227,6 +264,10 @@ function mapRect(rect, m, origin) {
227
264
  return { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
228
265
  }
229
266
 
267
+ // A corner whose w is this close to the viewer's plane is as good as behind
268
+ // it: projected that near, it lands off any window.
269
+ const W_NEAR = 1e-6;
270
+
230
271
  function unionRect(a, b) {
231
272
  if (!a) return b;
232
273
  const x = Math.min(a.x, b.x);
@@ -242,7 +283,8 @@ function unionRect(a, b) {
242
283
  /**
243
284
  * Everywhere `sprite` can draw while its animations run: its reach through
244
285
  * its own transform and every transform it is animated through, out to
245
- * whole pixels and the antialiasing a raster is padded for. Between two
286
+ * whole pixels and the antialiasing a raster is padded for — or null, where
287
+ * one of those puts a corner of it behind the viewer. Between two
246
288
  * keyframes Core Animation interpolates the matrices by their parts, which
247
289
  * stays within a pixel of the two for the densely sampled keyframes a CSS
248
290
  * transform arrives as; an element whose keyframes are far apart, or whose
@@ -271,12 +313,18 @@ function extentOf(sprite) {
271
313
  if (about?.key !== key) {
272
314
  let box = mapRect(rel, m0, ORIGIN);
273
315
  for (const a of sprite.animations ?? []) {
274
- if (a.property !== 'transform') continue;
275
- for (const m of a.values) box = unionRect(box, mapRect(rel, m, ORIGIN));
316
+ if (a.property !== 'transform' || !box) continue;
317
+ for (const m of a.values) {
318
+ const at = mapRect(rel, m, ORIGIN);
319
+ box = at && unionRect(box, at);
320
+ if (!box) break;
321
+ }
276
322
  }
277
323
  about = { key, box };
278
324
  if (turns) EXTENTS.set(turns, about);
279
325
  }
326
+ // a corner behind the viewer, at rest or on the way: nowhere bounded
327
+ if (!about.box) return null;
280
328
  const box = {
281
329
  x: about.box.x + origin.x,
282
330
  y: about.box.y + origin.y,
@@ -311,7 +359,31 @@ function rasterRectOf(sprite) {
311
359
  }
312
360
 
313
361
  const sameMatrix = (a, b) =>
314
- a === b || (a != null && b != null && a.every((v, i) => v === b[i]));
362
+ a === b ||
363
+ (a != null &&
364
+ b != null &&
365
+ a.length === b.length &&
366
+ a.every((v, i) => v === b[i]));
367
+
368
+ /**
369
+ * A part's matrix in device pixels as the bridge takes it, in points: its
370
+ * translation over the scale and, for a matrix3d, its perspective row times
371
+ * it — S⁻¹·M·S, with S scaling x, y and z, so that a turn and a perspective
372
+ * of so many CSS pixels look the same at any scale.
373
+ */
374
+ function inPoints(m, s) {
375
+ if (!isSolid(m)) {
376
+ return { matrix: [m[0], m[1], m[2], m[3], m[4] / s, m[5] / s] };
377
+ }
378
+ const p = m.slice();
379
+ p[12] /= s;
380
+ p[13] /= s;
381
+ p[14] /= s;
382
+ p[3] *= s;
383
+ p[7] *= s;
384
+ p[11] *= s;
385
+ return { matrix3d: p };
386
+ }
315
387
 
316
388
  /**
317
389
  * The lifted parts of every element in one window: a layer each, its raster,
@@ -326,9 +398,10 @@ export class SpriteLayers {
326
398
  this.app = promotion.app;
327
399
  // a matrix and a negative delay are what a CSS animation needs from the
328
400
  // bridge, and @windowkit/appkit 0.19 is where both are
329
- this.supported = Boolean(
330
- this.native.transformForms?.()?.includes?.('matrix'),
331
- );
401
+ const forms = this.native.transformForms?.();
402
+ this.supported = Boolean(forms?.includes?.('matrix'));
403
+ // and a part turned out of the plane, its matrix3d, the same release
404
+ this.solid = Boolean(forms?.includes?.('matrix3d'));
332
405
  // and a video's frames on a layer need its video surfaces
333
406
  this.videos = canLiftVideo(this.native);
334
407
  this.hosts = new Map(); // element -> Map(key -> state)
@@ -429,7 +502,12 @@ export class SpriteLayers {
429
502
  radius: sprite.clip ? (sprite.clipRadius ?? 0) : 0,
430
503
  bounds: null,
431
504
  };
505
+ // turned out of the plane, on a bridge that takes no matrix3d
506
+ if (!this.solid && solidSprite(sprite)) continue;
432
507
  const extent = extentOf(sprite);
508
+ // a corner behind the viewer: the element draws it, which it can cut
509
+ // where the plane meets the viewer's
510
+ if (!extent) continue;
433
511
  const bounds = cut.clip ? intersectRects(extent, cut.clip) : extent;
434
512
  if (parent) {
435
513
  // the parent was asked about everywhere it can be, and its layer
@@ -544,8 +622,9 @@ export class SpriteLayers {
544
622
  drawn.push(state.extent);
545
623
  continue;
546
624
  }
547
- // the element draws it, where its own clip lets it show
548
- const extent = extentOf(sprite);
625
+ // the element draws it, where its own clip lets it show — anywhere,
626
+ // for one with a corner behind the viewer and no clip
627
+ const extent = extentOf(sprite) ?? EVERYWHERE;
549
628
  const shows = sprite.clip ? intersectRects(extent, sprite.clip) : extent;
550
629
  if (shows) drawn.push(shows);
551
630
  }
@@ -776,7 +855,7 @@ export class SpriteLayers {
776
855
  }
777
856
  }
778
857
  if (!sameMatrix(prev.matrix, m)) {
779
- out.transform = { matrix: [m[0], m[1], m[2], m[3], m[4] / s, m[5] / s] };
858
+ out.transform = inPoints(m, s);
780
859
  any = true;
781
860
  }
782
861
  if (any) this.native.setLayerProps(state.layer, out);
@@ -843,9 +922,7 @@ export class SpriteLayers {
843
922
  const opts = {
844
923
  values:
845
924
  a.property === 'transform'
846
- ? a.values.map((m) => ({
847
- matrix: [m[0], m[1], m[2], m[3], m[4] / s, m[5] / s],
848
- }))
925
+ ? a.values.map((m) => inPoints(m, s))
849
926
  : a.values,
850
927
  duration: a.duration / 1000,
851
928
  id: caId,
package/src/node.d.ts CHANGED
@@ -237,6 +237,23 @@ export interface PaintCachePlan {
237
237
  */
238
238
  export type SpriteMatrix = [number, number, number, number, number, number];
239
239
 
240
+ /**
241
+ * CSS's `matrix3d()`: sixteen numbers, column by column, a point
242
+ * (x, y, z, 1) going to the first three rows over the fourth — a turn out
243
+ * of the plane and the perspective it is seen in, about the sprite's
244
+ * `origin`. Its translation (the 13th to 15th) is in device pixels, and so
245
+ * is the distance its perspective row divides by. A presenter lifts a
246
+ * sprite with one only where none of its corners is behind the viewer, at
247
+ * rest or in any keyframe, and where its bridge takes one.
248
+ */
249
+ // prettier-ignore
250
+ export type SpriteMatrix3D = [
251
+ number, number, number, number,
252
+ number, number, number, number,
253
+ number, number, number, number,
254
+ number, number, number, number,
255
+ ];
256
+
240
257
  /** A cubic-bezier's control points `[x1, y1, x2, y2]`, `x1` and `x2` in
241
258
  * 0..1, or one of Core Animation's curve names. */
242
259
  export type SpriteTiming = [number, number, number, number] | string;
@@ -252,8 +269,9 @@ export interface SpriteAnimation {
252
269
  id: string;
253
270
  property: 'opacity' | 'transform';
254
271
  /** At least two: opacities, or matrices for a transform — sampled densely
255
- * where the element interpolates in a way the render server would not. */
256
- values: number[] | SpriteMatrix[];
272
+ * where the element interpolates in a way the render server would not.
273
+ * A transform's may be `matrix3d`s, in any mix with `matrix`es. */
274
+ values: number[] | (SpriteMatrix | SpriteMatrix3D)[];
257
275
  /** One per value, 0 to 1, never decreasing; evenly spaced when left out. */
258
276
  keyTimes?: number[];
259
277
  /** One curve per segment, so one fewer than `values`; linear when left
@@ -321,7 +339,7 @@ export interface Sprite {
321
339
  /** What the layer shows when no animation runs on it; 1 when left out. */
322
340
  opacity?: number;
323
341
  /** …and the transform, about `origin`; the identity when left out. */
324
- transform?: SpriteMatrix;
342
+ transform?: SpriteMatrix | SpriteMatrix3D;
325
343
  /** Device pixels, window coordinates; `rect`'s centre when left out. */
326
344
  origin?: { x: number; y: number };
327
345
  animations?: SpriteAnimation[];
@@ -25,9 +25,12 @@ export class NodeSprites {
25
25
  * - `version`: compared with `===` from frame to frame; a change paints
26
26
  * the part again. Left out, it is painted once.
27
27
  * - `opacity`, and `transform` — CSS's `matrix(a, b, c, d, e, f)` as an
28
- * array, `e` and `f` in device pixels — about `origin`, a point in
29
- * device pixels that defaults to `rect`'s centre: the values the layer
30
- * shows when no animation is running on it.
28
+ * array, `e` and `f` in device pixels, or `matrix3d()`'s sixteen
29
+ * numbers for a part turned out of the plane and seen in a perspective
30
+ * — about `origin`, a point in device pixels that defaults to `rect`'s
31
+ * centre: the values the layer shows when no animation is running on
32
+ * it. A matrix3d part is lifted only where none of its corners is behind
33
+ * the viewer, at rest or in any keyframe.
31
34
  * - `animations`: what the render server runs on it, each `{ id,
32
35
  * property: 'opacity' | 'transform', values, keyTimes, timings,
33
36
  * duration, delay, repeat, autoreverse, hold }`, with times in