ntk 6.5.0 → 6.6.1
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 +20 -0
- package/lib/renderingcontext_2d.js +22 -5
- package/lib/window.js +70 -8
- package/package.json +1 -1
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
|
-
//
|
|
1380
|
-
//
|
|
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:
|
|
1383
|
-
|
|
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
|
@@ -348,6 +348,9 @@ export default class Window extends Drawable {
|
|
|
348
348
|
this._foreign = !!args.id;
|
|
349
349
|
this._backingOptOut = args.backingStore === false;
|
|
350
350
|
this._backing = null;
|
|
351
|
+
// What the backing store clears to, and the window attribute it mirrors.
|
|
352
|
+
// Undefined means "nothing was asked for" — see _clearColour.
|
|
353
|
+
this._clearPixel = args.backgroundPixel;
|
|
351
354
|
this._dirty = false;
|
|
352
355
|
this._presentPending = false;
|
|
353
356
|
|
|
@@ -696,32 +699,91 @@ export default class Window extends Drawable {
|
|
|
696
699
|
});
|
|
697
700
|
}
|
|
698
701
|
|
|
702
|
+
/**
|
|
703
|
+
* The pixel the backing store's unpainted area is cleared to.
|
|
704
|
+
*
|
|
705
|
+
* A pixmap's contents start *undefined* — whatever was in that memory — so
|
|
706
|
+
* something has to be written before it can be presented, and the same goes
|
|
707
|
+
* for the region a grow adds. What that something is follows the window's
|
|
708
|
+
* own `backgroundPixel`, which is exactly what the server would paint into
|
|
709
|
+
* exposed area on a window with no backing store. Without one: transparent
|
|
710
|
+
* for a depth-32 ARGB window, and the screen's white otherwise.
|
|
711
|
+
*/
|
|
712
|
+
_clearColour() {
|
|
713
|
+
if (this._clearPixel !== undefined) return this._clearPixel;
|
|
714
|
+
const depth = this.depth || this.display.screen[0].root_depth;
|
|
715
|
+
return depth === 32 ? 0 : this.display.screen[0].white_pixel;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* The window's background colour, as an X pixel value.
|
|
720
|
+
*
|
|
721
|
+
* A double-buffered window has two of them and they have to agree: the X
|
|
722
|
+
* window attribute the server paints into exposed area, and the colour the
|
|
723
|
+
* backing store clears to. Setting one and not the other is why a resize
|
|
724
|
+
* could show white on a window that draws itself dark.
|
|
725
|
+
*
|
|
726
|
+
* The backing store's *headroom* — the area a future grow will expand
|
|
727
|
+
* into, which by definition holds nothing yet — is recoloured too, so a
|
|
728
|
+
* later resize does not expose the previous colour.
|
|
729
|
+
*/
|
|
730
|
+
setBackgroundPixel(pixel) {
|
|
731
|
+
this._clearPixel = pixel;
|
|
732
|
+
if (this._destroyed) return this;
|
|
733
|
+
if (this._clearGc) this.X.ChangeGC(this._clearGc, { foreground: pixel });
|
|
734
|
+
if (!this._foreign) {
|
|
735
|
+
this.X.ChangeWindowAttributes(this.id, { backgroundPixel: pixel }, () => {});
|
|
736
|
+
}
|
|
737
|
+
const backing = this._backing;
|
|
738
|
+
if (backing && this._clearGc) {
|
|
739
|
+
const rects = [];
|
|
740
|
+
if (backing.width > this.width) {
|
|
741
|
+
rects.push(this.width, 0, backing.width - this.width, backing.height);
|
|
742
|
+
}
|
|
743
|
+
if (backing.height > this.height) {
|
|
744
|
+
rects.push(0, this.height, this.width, backing.height - this.height);
|
|
745
|
+
}
|
|
746
|
+
if (rects.length) this.X.PolyFillRectangle(backing.id, this._clearGc, rects);
|
|
747
|
+
}
|
|
748
|
+
return this;
|
|
749
|
+
}
|
|
750
|
+
|
|
699
751
|
// grow-only backing pixmap (re)allocation, with headroom (see
|
|
700
|
-
// BACKING_GRANULARITY)
|
|
752
|
+
// BACKING_GRANULARITY). What is already drawn is carried across and only
|
|
753
|
+
// the region the grow adds is cleared (see _clearColour) — clearing the
|
|
754
|
+
// whole pixmap made the *entire* window flash the clear colour between a
|
|
755
|
+
// resize and the redraw, not just the new strip.
|
|
701
756
|
_allocBacking(w, h) {
|
|
702
757
|
const cur = this._backing;
|
|
703
758
|
if (cur && cur.width >= w && cur.height >= h) return;
|
|
704
759
|
const roundUp = (v) => Math.ceil(v / BACKING_GRANULARITY) * BACKING_GRANULARITY;
|
|
705
760
|
const newW = roundUp(Math.max(w, cur ? cur.width : 0));
|
|
706
761
|
const newH = roundUp(Math.max(h, cur ? cur.height : 0));
|
|
762
|
+
const depth = this.depth || this.display.screen[0].root_depth;
|
|
707
763
|
const pixmap = new Pixmap(this.app, {
|
|
708
764
|
parent: this,
|
|
709
765
|
width: newW,
|
|
710
766
|
height: newH,
|
|
711
|
-
|
|
712
|
-
// different depths is a BadMatch
|
|
713
|
-
depth: this.depth || this.display.screen[0].root_depth
|
|
767
|
+
depth
|
|
714
768
|
});
|
|
715
769
|
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
770
|
this._clearGc = this.X.AllocID();
|
|
719
771
|
this.X.CreateGC(this._clearGc, pixmap.id, {
|
|
720
|
-
foreground: this.
|
|
772
|
+
foreground: this._clearColour(),
|
|
721
773
|
graphicsExposures: 0
|
|
722
774
|
});
|
|
723
775
|
}
|
|
724
|
-
|
|
776
|
+
if (cur) {
|
|
777
|
+
// The new pixmap is strictly larger, so the old content lands at the
|
|
778
|
+
// same coordinates and only the L-shaped remainder is new.
|
|
779
|
+
this.X.CopyArea(cur.id, pixmap.id, this._clearGc, 0, 0, 0, 0, cur.width, cur.height);
|
|
780
|
+
const rects = [];
|
|
781
|
+
if (newW > cur.width) rects.push(cur.width, 0, newW - cur.width, newH);
|
|
782
|
+
if (newH > cur.height) rects.push(0, cur.height, cur.width, newH - cur.height);
|
|
783
|
+
if (rects.length) this.X.PolyFillRectangle(pixmap.id, this._clearGc, rects);
|
|
784
|
+
} else {
|
|
785
|
+
this.X.PolyFillRectangle(pixmap.id, this._clearGc, [0, 0, newW, newH]);
|
|
786
|
+
}
|
|
725
787
|
if (cur) cur.destroy();
|
|
726
788
|
this._backing = pixmap;
|
|
727
789
|
this._backingValid = false;
|