ntk 6.5.0 → 6.6.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/lib/app.js CHANGED
@@ -146,6 +146,26 @@ export default class App {
146
146
  return chooseGLXConfig(this, spec);
147
147
  }
148
148
 
149
+ /**
150
+ * Find a 32-bit TrueColor visual with an alpha channel, for per-pixel
151
+ * transparent windows (ARGB). Returns `{ visual, depth: 32 }` — pass
152
+ * these to `createWindow()` together with `backgroundPixel: 0`. Returns
153
+ * `null` when the server has no such visual (XQuartz, for example).
154
+ *
155
+ * The compositor blends the window's alpha channel automatically; no
156
+ * Composite extension calls or EWMH properties are needed from the client.
157
+ * Without a running compositor, transparent regions render black.
158
+ */
159
+ findArgbVisual(screen = 0) {
160
+ const depths = this.display.screen[screen].depths;
161
+ const visuals = depths?.[32];
162
+ if (!visuals) return null;
163
+ for (const [id, visual] of Object.entries(visuals)) {
164
+ if (visual.class === 4) return { visual: Number(id), depth: 32 };
165
+ }
166
+ return null;
167
+ }
168
+
149
169
  rootWindow(screen = 0) {
150
170
  return new Window(this, { id: this.display.screen[screen].root });
151
171
  }
@@ -284,6 +284,9 @@ class RenderingContext2d {
284
284
  : depth === 8
285
285
  ? this.Render.a8
286
286
  : this.Render.rgb24;
287
+ // Does the target have a real alpha channel? Only then is "transparent"
288
+ // a colour it can hold, which is what clearRect turns on.
289
+ this._hasAlpha = depth === 32;
287
290
  this.picture = new Picture(this.window.app, {
288
291
  drawable: target,
289
292
  format,
@@ -1363,12 +1366,19 @@ class RenderingContext2d {
1363
1366
  // ------------------------------------------------------------------
1364
1367
  // drawing
1365
1368
 
1369
+ // Clearing means "back to nothing", and what nothing looks like depends on
1370
+ // whether the target can hold transparency. A depth-32 ARGB window gets
1371
+ // transparent black, the canvas spec's answer, and the compositor shows
1372
+ // whatever is behind it. Anything else has no alpha channel to write, so
1373
+ // clearing stays opaque white — the paper an opaque window starts from,
1374
+ // and what every caller predating ARGB windows expects.
1366
1375
  clearRect(x, y, w, h) {
1376
+ const alpha = this._hasAlpha;
1367
1377
  if (matIsIdentity(this._m) && !this._clips.length) {
1368
1378
  this.Render.FillRectangles(
1369
1379
  this.Render.PictOp.Src,
1370
1380
  this.picture.id,
1371
- [1, 1, 1, 1],
1381
+ alpha ? [0, 0, 0, 0] : [1, 1, 1, 1],
1372
1382
  [x, y, w, h],
1373
1383
  );
1374
1384
  this._markDirty();
@@ -1376,11 +1386,18 @@ class RenderingContext2d {
1376
1386
  }
1377
1387
  const tmp = new Path2D();
1378
1388
  tmp.rect(x, y, w, h);
1379
- // clear = opaque white, ignoring alpha and the composite op (but
1380
- // honoring the clip), matching the identity fast path above
1389
+ // Transformed or clipped, so the rect is a polygon and the erase has to
1390
+ // run through the coverage mask. OutReverse is `dst OUT src`: against an
1391
+ // opaque source it scales the destination by 1 - coverage, erasing to
1392
+ // transparent with an antialiased edge. PictOpSrc would take the whole
1393
+ // bounding box with it, coverage or not. Opaque targets keep the old
1394
+ // behaviour: paint white over the shape, ignoring alpha and the
1395
+ // composite op but honoring the clip, matching the fast path above.
1381
1396
  this._fillPolys(flattenPath(tmp._cmds, this._m), "nonzero", {
1382
- src: this.createSolidPicture(1, 1, 1, 1),
1383
- op: this.Render.PictOp.Over,
1397
+ src: alpha
1398
+ ? this.createSolidPicture(0, 0, 0, 1)
1399
+ : this.createSolidPicture(1, 1, 1, 1),
1400
+ op: alpha ? this.Render.PictOp.OutReverse : this.Render.PictOp.Over,
1384
1401
  alpha: 1,
1385
1402
  });
1386
1403
  }
package/lib/window.js CHANGED
@@ -697,27 +697,25 @@ export default class Window extends Drawable {
697
697
  }
698
698
 
699
699
  // grow-only backing pixmap (re)allocation, with headroom (see
700
- // BACKING_GRANULARITY); new area is cleared to white
700
+ // BACKING_GRANULARITY); new area is cleared to the window's background
701
+ // (white for opaque windows, fully transparent for depth-32 ARGB ones)
701
702
  _allocBacking(w, h) {
702
703
  const cur = this._backing;
703
704
  if (cur && cur.width >= w && cur.height >= h) return;
704
705
  const roundUp = (v) => Math.ceil(v / BACKING_GRANULARITY) * BACKING_GRANULARITY;
705
706
  const newW = roundUp(Math.max(w, cur ? cur.width : 0));
706
707
  const newH = roundUp(Math.max(h, cur ? cur.height : 0));
708
+ const depth = this.depth || this.display.screen[0].root_depth;
707
709
  const pixmap = new Pixmap(this.app, {
708
710
  parent: this,
709
711
  width: newW,
710
712
  height: newH,
711
- // must match the window's depth: CopyArea between drawables of
712
- // different depths is a BadMatch
713
- depth: this.depth || this.display.screen[0].root_depth
713
+ depth
714
714
  });
715
715
  if (!this._clearGc) {
716
- // reusable across reallocs: a GC is valid for any drawable of the
717
- // same screen and depth, so one outlives every backing pixmap
718
716
  this._clearGc = this.X.AllocID();
719
717
  this.X.CreateGC(this._clearGc, pixmap.id, {
720
- foreground: this.display.screen[0].white_pixel,
718
+ foreground: depth === 32 ? 0 : this.display.screen[0].white_pixel,
721
719
  graphicsExposures: 0
722
720
  });
723
721
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ntk",
3
- "version": "6.5.0",
3
+ "version": "6.6.0",
4
4
  "description": "Desktop UI toolkit for X11 with canvas-like 2d and OpenGL rendering",
5
5
  "author": "Andrey Sidorov <sidorares@yandex.ru>",
6
6
  "license": "MIT",