react-x11 2.18.4 → 2.18.6

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.18.4",
3
+ "version": "2.18.6",
4
4
  "description": "react renderer with X11 as a target",
5
5
  "main": "./src/index.js",
6
6
  "files": [
@@ -103,14 +103,14 @@
103
103
  "dependencies": {
104
104
  "ical.js": "^2.2.1",
105
105
  "linebreak": "^1.1.0",
106
- "ntk": "^8.9.0",
106
+ "ntk": "^8.10.0",
107
107
  "react-reconciler": "^0.34.0",
108
108
  "yoga-layout": "^3.2.1"
109
109
  },
110
110
  "optionalDependencies": {
111
111
  "@windowkit/appkit": "^0.12.0",
112
112
  "@windowkit/wayland": "^3.1.1",
113
- "@windowkit/win32": "^0.0.1",
113
+ "@windowkit/win32": "^0.0.2",
114
114
  "dbus-native": "^0.15.1",
115
115
  "x11-dri": "^0.9.0"
116
116
  },
package/src/Reconciler.js CHANGED
@@ -7,7 +7,6 @@
7
7
  import { createRequire } from 'node:module';
8
8
  import React from 'react';
9
9
  import ReactReconciler from 'react-reconciler';
10
- import { createClient } from 'ntk';
11
10
 
12
11
  import {
13
12
  ConcurrentRoot,
@@ -22,6 +21,7 @@ import {
22
21
  flushWindowMaps,
23
22
  flushWindowRestacks,
24
23
  } from './nodes/window/window.js';
24
+ import { loadNtk } from './ntkroot.js';
25
25
  import { PopupNode } from './nodes/window/popup.js';
26
26
  import { BoxNode } from './nodes/box.js';
27
27
  import { THEME_SCOPE } from './nodes/kinds.js';
@@ -629,6 +629,8 @@ function loadIntegrations() {
629
629
  * anyone hits and the least self-explanatory failure in the library.
630
630
  */
631
631
  async function connect(options) {
632
+ // ntk's root is the X11 backend's, and loads with it (src/ntkroot.js)
633
+ const { createClient } = await loadNtk();
632
634
  try {
633
635
  return await createClient(options);
634
636
  } catch (err) {
@@ -822,7 +824,12 @@ export async function createRoot(options = {}) {
822
824
  const win32Asked =
823
825
  rest.backend === 'win32' || process.env.REACT_X11_BACKEND === 'win32';
824
826
  const connecting = !owned
825
- ? Promise.resolve(borrowed)
827
+ ? // an ntk app could only have come from ntk's own createClient: the
828
+ // root is imported already, and this hands it to the code that reads
829
+ // it synchronously (src/ntkroot.js)
830
+ isNtkApp(borrowed)
831
+ ? loadNtk().then(() => borrowed)
832
+ : Promise.resolve(borrowed)
826
833
  : backend === 'wayland'
827
834
  ? // Opt-in only, never through 'auto': X11 stays the default on Linux
828
835
  // because the remote case (docs/remote.md) is the flagship reason
@@ -23,7 +23,7 @@
23
23
  // class keeps the JS-visible state (fillStyle strings, gradient objects,
24
24
  // dash arrays) and re-syncs it when the backing surface is replaced after a
25
25
  // resize — `_gen` is that generation.
26
- import { cssColorStraight } from 'ntk';
26
+ import { cssColorStraight } from 'ntk/color';
27
27
 
28
28
  const BLACK = [0, 0, 0, 1];
29
29
 
@@ -324,6 +324,17 @@ export class BackendContext2D {
324
324
  // chunked stroke leaves only its last chunk behind)
325
325
  this._cmds = [];
326
326
  this._pathStale = false;
327
+ // A bridge that takes a whole path in one call (`ctxPath`, the Windows
328
+ // bridge's) is handed it that way when something paints it: the verbs
329
+ // below only record. A graph's edges are tens of thousands of `lineTo`s
330
+ // a frame, and a call across the boundary for each was milliseconds of
331
+ // every frame spent on the crossing. A bridge without it is handed the
332
+ // path a point at a time, as it is built, exactly as before.
333
+ // Asked with `in` as well: a test's bridge that answers every name with
334
+ // a no-op must not look like one that takes paths whole.
335
+ this._bulkPaths =
336
+ 'ctxPath' in native && typeof native.ctxPath === 'function';
337
+ this._pathBuf = null;
327
338
  this._strokeChunking = !NO_STROKE_CHUNKING;
328
339
  }
329
340
 
@@ -547,13 +558,36 @@ export class BackendContext2D {
547
558
 
548
559
  setLineDash(segments) {
549
560
  this._state.dash = Array.isArray(segments) ? segments : [];
550
- this._native.ctxSetLineDash(this._s(), this._state.dash, 0);
561
+ // the offset is state of its own, and a new pattern keeps it
562
+ this._native.ctxSetLineDash(
563
+ this._s(),
564
+ this._state.dash,
565
+ this._state.dashOffset,
566
+ );
551
567
  }
552
568
 
553
569
  getLineDash() {
554
570
  return [...this._state.dash];
555
571
  }
556
572
 
573
+ /**
574
+ * Where along the pattern a stroke starts — the phase a marching dash
575
+ * moves. Missing here, a caller that asks `'lineDashOffset' in ctx` found
576
+ * no such thing and every stroke started its pattern at nought: the
577
+ * dashes of an animated edge were repainted sixteen times a second and
578
+ * never moved.
579
+ */
580
+ get lineDashOffset() {
581
+ return this._state.dashOffset;
582
+ }
583
+
584
+ set lineDashOffset(value) {
585
+ // canvas ignores anything that is not a finite number
586
+ if (typeof value !== 'number' || !Number.isFinite(value)) return;
587
+ this._state.dashOffset = value;
588
+ this._native.ctxSetLineDash(this._s(), this._state.dash, value);
589
+ }
590
+
557
591
  save() {
558
592
  // Sync before pushing: a fresh surface empties the stack on its way in,
559
593
  // and a state pushed ahead of that sync was lost to it — the first
@@ -670,6 +704,21 @@ export class BackendContext2D {
670
704
  _emit(surface, from, to) {
671
705
  const c = this._cmds;
672
706
  const n = this._native;
707
+ if (this._bulkPaths) {
708
+ // one call, through a buffer kept between paths — the record is the
709
+ // same stream the bridge parses, arguments and all
710
+ const count = to - from;
711
+ if (count <= 0) return;
712
+ let buf = this._pathBuf;
713
+ if (!buf || buf.length < count) {
714
+ buf = this._pathBuf = new Float64Array(
715
+ Math.max(count, (buf?.length ?? 512) * 2),
716
+ );
717
+ }
718
+ for (let i = 0; i < count; i++) buf[i] = c[from + i];
719
+ n.ctxPath(surface, buf.subarray(0, count));
720
+ return;
721
+ }
673
722
  for (let i = from; i < to;) {
674
723
  const op = c[i];
675
724
  const a = i + 1;
@@ -720,26 +769,44 @@ export class BackendContext2D {
720
769
 
721
770
  beginPath() {
722
771
  this._cmds.length = 0;
772
+ if (this._bulkPaths) {
773
+ this._s();
774
+ // uploaded, empty or not, when something paints it
775
+ this._pathStale = true;
776
+ return;
777
+ }
723
778
  this._pathStale = false;
724
779
  this._native.ctxBeginPath(this._s());
725
780
  }
726
781
 
782
+ /**
783
+ * The surface to add a path command to now, or null where the path is
784
+ * only recorded and handed over whole when painted (`_bulkPaths`). Either
785
+ * way a fresh surface drops the path first, as `_s` has always done.
786
+ */
787
+ _pathFor() {
788
+ if (!this._bulkPaths) return this._path();
789
+ this._s();
790
+ this._pathStale = true;
791
+ return null;
792
+ }
793
+
727
794
  moveTo(x, y) {
728
- const surface = this._path();
795
+ const surface = this._pathFor();
729
796
  this._cmds.push(P_MOVE, x, y);
730
- this._native.ctxMoveTo(surface, x, y);
797
+ if (surface !== null) this._native.ctxMoveTo(surface, x, y);
731
798
  }
732
799
 
733
800
  lineTo(x, y) {
734
- const surface = this._path();
801
+ const surface = this._pathFor();
735
802
  this._cmds.push(P_LINE, x, y);
736
- this._native.ctxLineTo(surface, x, y);
803
+ if (surface !== null) this._native.ctxLineTo(surface, x, y);
737
804
  }
738
805
 
739
806
  rect(x, y, w, h) {
740
- const surface = this._path();
807
+ const surface = this._pathFor();
741
808
  this._cmds.push(P_RECT, x, y, w, h);
742
- this._native.ctxRect(surface, x, y, w, h);
809
+ if (surface !== null) this._native.ctxRect(surface, x, y, w, h);
743
810
  }
744
811
 
745
812
  roundRect(x, y, w, h, radii) {
@@ -750,7 +817,7 @@ export class BackendContext2D {
750
817
  else if (r.length === 3) r = [r[0], r[1], r[2], r[1]];
751
818
  const cap = Math.min(Math.abs(w) / 2, Math.abs(h) / 2);
752
819
  const clamp = (v) => Math.max(0, Math.min(Number(v) || 0, cap));
753
- const surface = this._path();
820
+ const surface = this._pathFor();
754
821
  const [r0, r1, r2, r3] = [
755
822
  clamp(r[0]),
756
823
  clamp(r[1]),
@@ -758,37 +825,40 @@ export class BackendContext2D {
758
825
  clamp(r[3]),
759
826
  ];
760
827
  this._cmds.push(P_ROUND, x, y, w, h, r0, r1, r2, r3);
761
- this._native.ctxRoundRect(surface, x, y, w, h, r0, r1, r2, r3);
828
+ if (surface !== null)
829
+ this._native.ctxRoundRect(surface, x, y, w, h, r0, r1, r2, r3);
762
830
  }
763
831
 
764
832
  arc(x, y, radius, start, end, anticlockwise = false) {
765
- const surface = this._path();
833
+ const surface = this._pathFor();
766
834
  this._cmds.push(P_ARC, x, y, radius, start, end, anticlockwise ? 1 : 0);
767
- this._native.ctxArc(surface, x, y, radius, start, end, anticlockwise);
835
+ if (surface !== null)
836
+ this._native.ctxArc(surface, x, y, radius, start, end, anticlockwise);
768
837
  }
769
838
 
770
839
  ellipse(x, y, rx, ry) {
771
- const surface = this._path();
840
+ const surface = this._pathFor();
772
841
  this._cmds.push(P_ELLIPSE, x, y, rx, ry);
773
- this._native.ctxEllipse(surface, x, y, rx, ry);
842
+ if (surface !== null) this._native.ctxEllipse(surface, x, y, rx, ry);
774
843
  }
775
844
 
776
845
  bezierCurveTo(c1x, c1y, c2x, c2y, x, y) {
777
- const surface = this._path();
846
+ const surface = this._pathFor();
778
847
  this._cmds.push(P_CURVE, c1x, c1y, c2x, c2y, x, y);
779
- this._native.ctxCurveTo(surface, c1x, c1y, c2x, c2y, x, y);
848
+ if (surface !== null)
849
+ this._native.ctxCurveTo(surface, c1x, c1y, c2x, c2y, x, y);
780
850
  }
781
851
 
782
852
  quadraticCurveTo(cx, cy, x, y) {
783
- const surface = this._path();
853
+ const surface = this._pathFor();
784
854
  this._cmds.push(P_QUAD, cx, cy, x, y);
785
- this._native.ctxQuadTo(surface, cx, cy, x, y);
855
+ if (surface !== null) this._native.ctxQuadTo(surface, cx, cy, x, y);
786
856
  }
787
857
 
788
858
  closePath() {
789
- const surface = this._path();
859
+ const surface = this._pathFor();
790
860
  this._cmds.push(P_CLOSE);
791
- this._native.ctxClosePath(surface);
861
+ if (surface !== null) this._native.ctxClosePath(surface);
792
862
  }
793
863
 
794
864
  // --- painting ------------------------------------------------------------
package/src/cocoa/app.js CHANGED
@@ -16,7 +16,7 @@
16
16
  // (`_routeBatch`) with no pump at all (docs/macos.md §"JS on a worker").
17
17
  import { isMainThread } from 'node:worker_threads';
18
18
 
19
- import { cssColorStraight } from 'ntk';
19
+ import { cssColorStraight } from 'ntk/color';
20
20
 
21
21
  import { deliverActivate, deliverOpen } from '../application.js';
22
22
  import { flushPendingFrames } from '../frames.js';
@@ -26,7 +26,8 @@ import { readFileSync } from 'node:fs';
26
26
  import { inflateSync } from 'node:zlib';
27
27
 
28
28
  import LineBreaker from 'linebreak';
29
- import { cssColorStraight, Font } from 'ntk';
29
+ import { cssColorStraight } from 'ntk/color';
30
+ import Font from 'ntk/font';
30
31
 
31
32
  import { loadNative } from './native.js';
32
33
 
@@ -24,7 +24,7 @@
24
24
  // named repaints everything" rule the X11 damage model has), and geometry
25
25
  // is re-diffed every frame because comparing four numbers is cheaper than
26
26
  // knowing.
27
- import { cssColorStraight } from 'ntk';
27
+ import { cssColorStraight } from 'ntk/color';
28
28
 
29
29
  import { Node } from '../nodes/node.js';
30
30
  import { addDamageRect, damageToPaint } from '../nodes/damage.js';
@@ -34,7 +34,7 @@
34
34
  // derives is the padding a coverage surface has to carry. Both are pure
35
35
  // functions — no connection, nothing drawn — so this module stays testable
36
36
  // without a server.
37
- import { shadowReach, shadowSigma } from 'ntk';
37
+ import { shadowReach, shadowSigma } from 'ntk/shadow-math';
38
38
 
39
39
  /** A number, with or without the `px` CSS wants and this vocabulary does not. */
40
40
  const LENGTH = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:px)?$/i;
package/src/fonts.js CHANGED
@@ -37,7 +37,7 @@
37
37
 
38
38
  import { basename, extname } from 'node:path';
39
39
 
40
- import { Font } from 'ntk';
40
+ import Font from 'ntk/font';
41
41
 
42
42
  /**
43
43
  * Per-app bookkeeping. Keyed on the app rather than stored on it, like
@@ -30,7 +30,7 @@
30
30
  // save set, **never destroyed**. Destroying somebody else's window because a
31
31
  // React tree changed is the failure mode this element is most able to cause,
32
32
  // and `test/foreign.test.js` asserts against it first.
33
- import { XEMBED, XEmbedSocket } from 'ntk';
33
+ import { x11Ntk } from './ntkroot.js';
34
34
 
35
35
  import { isFocusable } from './a11y.js';
36
36
  import { canEmbed } from './embedding.js';
@@ -161,6 +161,8 @@ export class ForeignNode extends Node {
161
161
  if (!canEmbed(this.app)) return this._refuse();
162
162
  const rect = this._geometry();
163
163
  this.rect = rect;
164
+ // `<foreign>` embeds on an X connection only, where ntk's root is loaded
165
+ const { XEmbedSocket } = x11Ntk('<foreign>');
164
166
  const socket = new XEmbedSocket(parent, {
165
167
  ...rect,
166
168
  // react-x11 has a focus manager; a socket that answered
@@ -318,6 +320,8 @@ export class ForeignNode extends Node {
318
320
  defaultFocus(info) {
319
321
  const socket = this.socket;
320
322
  if (!socket || !this.client) return;
323
+ // a socket is an X connection's, so ntk's root is loaded (src/ntkroot.js)
324
+ const { XEMBED } = x11Ntk('<foreign>');
321
325
  const time = lastInputTime(this.app);
322
326
  socket.activate(true, { time });
323
327
  const detail =
@@ -338,6 +342,7 @@ export class ForeignNode extends Node {
338
342
  if (!socket) return;
339
343
  const time = lastInputTime(this.app);
340
344
  if (this._focusSent) {
345
+ const { XEMBED } = x11Ntk('<foreign>');
341
346
  socket.send(XEMBED.FOCUS_OUT, 0, 0, 0, { time });
342
347
  this._focusSent = false;
343
348
  }
package/src/glbackend.js CHANGED
@@ -19,7 +19,7 @@
19
19
  // `appcontext.js` (for `useSupports('shaders')`) and the node layer can use
20
20
  // it without the two importing each other.
21
21
 
22
- import { GLError } from 'ntk';
22
+ import { GLError } from 'ntk/gl';
23
23
 
24
24
  /** Does this connection's policy ever choose the direct backend? */
25
25
  const wantsDirect = (app) => {
package/src/glnodes.js CHANGED
@@ -5,7 +5,7 @@
5
5
  // (NEXT_STEPS §4), sized and positioned by the parent's yoga layout like any
6
6
  // other drawn node. Everything about the surface is here; the scene graph
7
7
  // that draws into it comes later (docs/glx-plan.md).
8
- import { cssColorStraight } from 'ntk';
8
+ import { cssColorStraight } from 'ntk/color';
9
9
 
10
10
  // re-exported so the GL element layer stays one import for consumers
11
11
  export { directGLFailure, hasDirectGL } from './glbackend.js';
package/src/gloverlay.js CHANGED
@@ -60,7 +60,7 @@
60
60
  // A leaf module, like src/embedding.js: `appcontext.js` asks `canOverlay`
61
61
  // for `useSupports('glOverlay')`, and it imports nothing of ours but the
62
62
  // damage model's arithmetic, which imports nothing at all.
63
- import { cssColorStraight } from 'ntk';
63
+ import { cssColorStraight } from 'ntk/color';
64
64
 
65
65
  import { FULL_DAMAGE, addDamageRect } from './nodes/damage.js';
66
66
  import {
@@ -114,6 +114,18 @@ export function overlayRefusal(app) {
114
114
  return (app && refusals.get(app)) ?? null;
115
115
  }
116
116
 
117
+ /**
118
+ * A backend saying nothing can be drawn over a GL surface on this app, and
119
+ * why: one whose child windows are not X windows, and that has no
120
+ * composited pane either — on Windows a window with a parent is a GL
121
+ * surface of its own (src/win32/glarea.js), and a bridge without layers has
122
+ * nothing else to offer. Before the first render, like `beginGlOverlay`, so
123
+ * `useSupports('glOverlay')` never changes its answer.
124
+ */
125
+ export function refuseOverlay(app, reason) {
126
+ if (app) refusals.set(app, reason);
127
+ }
128
+
117
129
  /**
118
130
  * Is this an X server where nothing can be drawn over a GL surface? Asked
119
131
  * in `createRoot`, with the other startup probes and before the first
@@ -654,35 +666,46 @@ export class GlOverlay {
654
666
  _paintPane(pane, passes) {
655
667
  const area = this.area;
656
668
  const root = area.root;
657
- const ctx = pane.context();
658
- if (!ctx || !root) return;
669
+ if (!root) return;
659
670
  // an opaque pane is filled with what the surface starts its frames from;
660
671
  // a composited one is cleared, and shows the frame itself
661
672
  const ground = pane.transparent ? null : groundOf(area.props);
662
- ctx.save();
663
- try {
664
- ctx.translate(-pane.rect.x, -pane.rect.y);
665
- for (const pass of passes) {
666
- ctx.save();
667
- try {
668
- ctx.beginPath();
669
- ctx.rect(pass.x, pass.y, pass.width, pass.height);
670
- ctx.clip();
671
- if (ground) {
672
- ctx.fillStyle = ground;
673
- ctx.fillRect(pass.x, pass.y, pass.width, pass.height);
674
- } else {
675
- ctx.clearRect(pass.x, pass.y, pass.width, pass.height);
676
- }
677
- root._paintDamage = pass;
678
- area._paintChildren(ctx);
679
- } finally {
680
- root._paintDamage = null;
681
- ctx.restore();
673
+ // one pass, drawn by a context in window coordinates
674
+ const paintPass = (ctx, pass) => {
675
+ ctx.save();
676
+ try {
677
+ ctx.beginPath();
678
+ ctx.rect(pass.x, pass.y, pass.width, pass.height);
679
+ ctx.clip();
680
+ if (ground) {
681
+ ctx.fillStyle = ground;
682
+ ctx.fillRect(pass.x, pass.y, pass.width, pass.height);
683
+ } else {
684
+ ctx.clearRect(pass.x, pass.y, pass.width, pass.height);
682
685
  }
686
+ root._paintDamage = pass;
687
+ area._paintChildren(ctx);
688
+ } finally {
689
+ root._paintDamage = null;
690
+ ctx.restore();
691
+ }
692
+ };
693
+ if (typeof pane.wnd.paintPasses === 'function') {
694
+ // A pane drawn a rect at a time — a DirectComposition surface hands
695
+ // out a context per `BeginDraw`, valid until `EndDraw` — opens each
696
+ // pass itself (src/win32/overlay.js), the way such a window takes its
697
+ // frame through `presentFrame`.
698
+ pane.wnd.paintPasses(passes, paintPass);
699
+ } else {
700
+ const ctx = pane.context();
701
+ if (!ctx) return;
702
+ ctx.save();
703
+ try {
704
+ ctx.translate(-pane.rect.x, -pane.rect.y);
705
+ for (const pass of passes) paintPass(ctx, pass);
706
+ } finally {
707
+ ctx.restore();
683
708
  }
684
- } finally {
685
- ctx.restore();
686
709
  }
687
710
  // A layer's contents are a copy the bitmap is pushed to; an X window's
688
711
  // backing store is blitted by ntk on its own, from the paint above
@@ -6,7 +6,9 @@
6
6
  // and the `cacheKey` cache live here, where a test needs no server; the node
7
7
  // half in nodes/image.js is only lifecycle — when to resolve, when to claim
8
8
  // damage, when to let go.
9
- import { Image, Picture, decodeImage } from 'ntk';
9
+ import { Image, decodeImage } from 'ntk/image';
10
+
11
+ import { x11Ntk } from './ntkroot.js';
10
12
 
11
13
  /**
12
14
  * A client-side object the 2d context composites as-is: an ntk `Image` or
@@ -429,6 +431,7 @@ export class DrawableSource {
429
431
  picture() {
430
432
  if (!this._picture) {
431
433
  const Render = this.app.display.Render;
434
+ const { Picture } = x11Ntk('<image> on a server-side picture');
432
435
  this._picture = new Picture(this.app, {
433
436
  drawable: { id: this.id },
434
437
  format: Render[DEPTH_FORMATS[this.depth]],
package/src/node.d.ts CHANGED
@@ -100,6 +100,9 @@ export interface Context2D {
100
100
  };
101
101
  setLineDash(segments: number[]): void;
102
102
  getLineDash(): number[];
103
+ /** Where along the dash pattern a stroke starts, in the same units as the
104
+ * pattern — the phase a marching dash moves. */
105
+ lineDashOffset: number;
103
106
  beginPath(): void;
104
107
  closePath(): void;
105
108
  moveTo(x: number, y: number): void;
@@ -18,7 +18,7 @@ import {
18
18
  // `paintcache.js`: a named import of something an older ntk does not export
19
19
  // is a *load-time* SyntaxError, which would take the renderer down rather
20
20
  // than the one feature that needs it.
21
- import * as ntk from 'ntk';
21
+ import { ntkRoot, x11Ntk } from '../ntkroot.js';
22
22
  import { isFocusable as a11yFocusable } from '../a11y.js';
23
23
  import { DEV } from './util.js';
24
24
 
@@ -320,7 +320,7 @@ export class NodeBoxPaint {
320
320
  );
321
321
  sctx.fill();
322
322
  },
323
- after: (surface) => ntk.blurCoverage(surface, sigma),
323
+ after: (surface) => x11Ntk('a baked shadow').blurCoverage(surface, sigma),
324
324
  live: () => this._paintShadowLive(ctx, plan),
325
325
  };
326
326
  const cache = this.root?._paintCache;
@@ -338,7 +338,9 @@ export class NodeBoxPaint {
338
338
  * a k x k kernel, by the ratio of 2k to k squared.
339
339
  */
340
340
  _paintShadowLive(ctx, plan) {
341
- if (typeof ntk.Surface !== 'function' || !this.app?.display?.Render) return;
341
+ const ntk = ntkRoot();
342
+ if (typeof ntk?.Surface !== 'function' || !this.app?.display?.Render)
343
+ return;
342
344
  let surface = null;
343
345
  try {
344
346
  surface = new ntk.Surface(this.app, {
@@ -20,7 +20,7 @@ import {
20
20
  // `paintcache.js`: a named import of something an older ntk does not export
21
21
  // is a *load-time* SyntaxError, which would take the renderer down rather
22
22
  // than the one feature that needs it.
23
- import * as ntk from 'ntk';
23
+ import { loadImage } from 'ntk/image';
24
24
  import { symbolWeight, symbolsFor, warnOnce } from '../symbols.js';
25
25
  import { intrinsicSize } from './layout.js';
26
26
  import { Node } from './node.js';
@@ -164,7 +164,7 @@ export class ImageNode extends Node {
164
164
  _loadEntry(src) {
165
165
  if (isPathImageSource(src)) {
166
166
  return {
167
- promise: ntk.loadImage(toLoadablePath(src)).then(
167
+ promise: loadImage(toLoadablePath(src)).then(
168
168
  (image) => image,
169
169
  (err) => {
170
170
  console.error(
@@ -196,7 +196,7 @@ export class ImageNode extends Node {
196
196
  async _loadFile(src) {
197
197
  const token = ++this._loadToken;
198
198
  try {
199
- const image = await ntk.loadImage(toLoadablePath(src));
199
+ const image = await loadImage(toLoadablePath(src));
200
200
  if (token !== this._loadToken || this.destroyed) return;
201
201
  this._ownedImage = image;
202
202
  this._setImage(image);
@@ -314,8 +314,16 @@ export class WindowInvalidate {
314
314
  // the whole reason this is not just `needsLayout`: a scroll moves an
315
315
  // offset applied during `absolutize` and leaves every yoga node exactly
316
316
  // as it was, at input rate, on the biggest trees in any app.
317
- if (reason !== 'scroll') this._floorsDirty = true;
318
- if (reason !== 'scroll' && reason !== 'resize') {
317
+ //
318
+ // Nor does a box that only moved (`'position'`, from `applyProps`):
319
+ // absolutely positioned and sized by its own width and height, it is
320
+ // out of every ancestor's flow and sizes nothing inside it by its
321
+ // insets. A card dragged across a graph — or a thousand of them
322
+ // panned — was a full measurement of the content floors a step.
323
+ if (reason !== 'scroll' && reason !== 'position') {
324
+ this._floorsDirty = true;
325
+ }
326
+ if (reason !== 'scroll' && reason !== 'resize' && reason !== 'position') {
319
327
  this._floorsContentDirty = true;
320
328
  }
321
329
  }
@@ -469,6 +477,27 @@ export class WindowInvalidate {
469
477
  return WINDOW_ONLY;
470
478
  }
471
479
 
480
+ /**
481
+ * One rect the layout diff says a node moved through (`layoutDiff.sink`,
482
+ * nodes/window/flush.js), to the lists that paint that node — the same
483
+ * reach a claim of it has (`_paneReach`). A node inside a surface is the
484
+ * panes' alone: the window's pass under the surface would repaint pixels
485
+ * no child of it ever had, and it went to both, so a card dragged across
486
+ * a graph drawn through GL was a window pass under the surface on every
487
+ * step. Answers whether the window's list took it.
488
+ */
489
+ _claimLayoutMove(rect, node, cap) {
490
+ const reach =
491
+ this._overlaid.size !== 0 ? this._paneReach(node ?? null) : WINDOW_ONLY;
492
+ let windowTook = false;
493
+ if (reach !== PANES_ONLY && this._damage !== FULL_DAMAGE) {
494
+ this._damage = addDamageRect(this._damage, rect, cap);
495
+ windowTook = true;
496
+ }
497
+ if (reach !== WINDOW_ONLY) this._addPaneDamage(rect);
498
+ return windowTook;
499
+ }
500
+
472
501
  /** One rect more for the panes, unless they repaint whole already. */
473
502
  _addPaneDamage(rect) {
474
503
  if (this._paneDamage === FULL_DAMAGE) return;
@@ -313,17 +313,17 @@ export class NodeLayout {
313
313
  ) {
314
314
  return;
315
315
  }
316
- if (had) layoutDiff.sink(insetRect(was, -grow));
316
+ if (had) layoutDiff.sink(insetRect(was, -grow), this);
317
317
  if (width > 0 && height > 0) {
318
- layoutDiff.sink(insetRect(this.abs, -grow));
318
+ layoutDiff.sink(insetRect(this.abs, -grow), this);
319
319
  }
320
320
  return;
321
321
  }
322
322
  if (had) {
323
- layoutDiff.sink(insetRect(old, -grow));
323
+ layoutDiff.sink(insetRect(old, -grow), this);
324
324
  }
325
325
  if (width > 0 && height > 0) {
326
- layoutDiff.sink(insetRect(this.abs, -grow));
326
+ layoutDiff.sink(insetRect(this.abs, -grow), this);
327
327
  }
328
328
  }
329
329
  }
package/src/nodes/node.js CHANGED
@@ -605,6 +605,18 @@ export class Node {
605
605
  // which is what keeps a commit from widening the region to every node
606
606
  // it touched.
607
607
  if (layoutChanged) {
608
+ const moved = onlyInsetsChanged(style, prevStyle);
609
+ // An absolutely positioned box of a set size that moved and changed
610
+ // nothing else is out of its ancestors' flow and sized by its width
611
+ // and height rather than its insets, so no content floor anywhere
612
+ // moved with it (src/nodes/invalidate.js, `'position'`).
613
+ const reason =
614
+ moved &&
615
+ style.position === 'absolute' &&
616
+ typeof style.width === 'number' &&
617
+ typeof style.height === 'number'
618
+ ? 'position'
619
+ : 'props';
608
620
  // A child of a `<glarea>` that only moved (issue #644): its pixels are
609
621
  // on a pane, one shift away, and the layout pass reports the move to
610
622
  // the overlay rather than claiming it (src/glnodes.js). A claim of the
@@ -613,12 +625,12 @@ export class Node {
613
625
  // whenever the overlay cannot move the pixels.
614
626
  if (
615
627
  this.parent?.isGlArea &&
616
- onlyInsetsChanged(style, prevStyle) &&
628
+ moved &&
617
629
  !this.paintChanged(newProps, prev)
618
630
  ) {
619
- this.invalidate(true, NO_DAMAGE, 'props');
631
+ this.invalidate(true, NO_DAMAGE, reason);
620
632
  } else {
621
- this._invalidateLayout('props');
633
+ this._invalidateLayout(reason);
622
634
  }
623
635
  } else {
624
636
  // The style half is asked here rather than inside `paintChanged`, and