react-x11 2.22.7 → 2.22.9
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 +2 -2
- package/src/cocoa/fonts.js +159 -1
- package/src/glnodes.js +26 -0
- package/src/gloverlay.js +772 -79
- package/src/nodes/paint.js +11 -2
- package/src/nodes/text.js +7 -22
- package/src/nodes/textinput.js +8 -9
- package/src/win32/fonts.js +3 -3
package/src/gloverlay.js
CHANGED
|
@@ -24,17 +24,38 @@
|
|
|
24
24
|
// - **X11: no.** A child window is opaque without a compositor, and a
|
|
25
25
|
// compositor would not change that: it redirects top-level windows, not
|
|
26
26
|
// the children inside one. So a pane is a plain child window just big
|
|
27
|
-
// enough for what it holds, one per
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
27
|
+
// enough for what it holds, one per child, and the surface shows between
|
|
28
|
+
// them. Inside a pane the ground is the surface's `clearColor` — the
|
|
29
|
+
// colour its frames start from — so a pixel a child leaves unpainted there
|
|
30
|
+
// (a rounded corner, a translucent background, text with no box behind
|
|
31
|
+
// it) shows that colour, never the GL frame.
|
|
32
32
|
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
33
|
+
// One pane per child, kept by the child, and holding that child and what
|
|
34
|
+
// is under it where the two meet — never what is over it (`_paintPane`).
|
|
35
|
+
// Two children that overlap keep a pane each, stacked in their order, the
|
|
36
|
+
// upper one holding the lower one's pixels where they meet. So a card
|
|
37
|
+
// dragged across another moves its own pane and leaves the other one's
|
|
38
|
+
// alone: nothing the other holds changed. Panes used to be one per region
|
|
39
|
+
// the children reached, overlapping ones merged, handed out by position in
|
|
40
|
+
// a list — and a card dragged into a neighbour's reach re-cut the regions,
|
|
41
|
+
// so every pane after it in the list went to someone else's place with
|
|
42
|
+
// that someone's pixels in it, for a frame, on every step.
|
|
43
|
+
//
|
|
44
|
+
// A pane's window takes a new place and the pixels that go there together
|
|
45
|
+
// (`X11PaneWindow`), and keeps its contents in the server while it is
|
|
46
|
+
// mapped, as the surface's window does (src/glnodes.js): a pane or a
|
|
47
|
+
// frame another pane moves off is put back by the server at once, not
|
|
48
|
+
// after an Expose and a repaint.
|
|
49
|
+
//
|
|
50
|
+
// Each pane is cut with the SHAPE extension to what its child painted
|
|
51
|
+
// (`X11PaneWindow.cutToInk`), so the frame shows around a child and not
|
|
52
|
+
// only between the children's reaches — a card's handles and rounded
|
|
53
|
+
// corners, not the rectangle its canvas reaches. A window per child
|
|
54
|
+
// rather than one window over the surface cut to all of them keeps each
|
|
55
|
+
// pixmap as small as what it holds: one the size of the surface would
|
|
56
|
+
// hold a map's worth of pixels to show a legend in its corner. Where the
|
|
57
|
+
// server has no SHAPE — node-x11's in-process one — a pane is the
|
|
58
|
+
// rectangle of its child's reach, as it always was.
|
|
38
59
|
// - **XQuartz: no, and not opaquely either.** Every GL surface there, the
|
|
39
60
|
// direct ones Apple-DRI exports and indirect GLX's alike, is a surface of
|
|
40
61
|
// the macOS window server, composited above everything the X server draws
|
|
@@ -51,11 +72,13 @@
|
|
|
51
72
|
//
|
|
52
73
|
// A pane keeps what it holds between frames, so a child that only moved is
|
|
53
74
|
// already painted, one shift away (issue #644). The layout pass reports such
|
|
54
|
-
// a child instead of claiming it (`GlAreaNode._absolutizeChild`)
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
75
|
+
// a child instead of claiming it (`GlAreaNode._absolutizeChild`). On X11 its
|
|
76
|
+
// pane goes with it, and the window's move carries its pixels; on a
|
|
77
|
+
// composited backend the frame moves them inside the one pane —
|
|
78
|
+
// `scrollRegion`, the verb the scroll blit uses on a window. Either way the
|
|
79
|
+
// frame repaints only what the move uncovered and what it now overlaps
|
|
80
|
+
// differently (`settleMoves`). A pan over a graph's mounted node bodies is
|
|
81
|
+
// that frame, sixty times a second.
|
|
59
82
|
//
|
|
60
83
|
// A leaf module, like src/embedding.js: `appcontext.js` asks `canOverlay`
|
|
61
84
|
// for `useSupports('glOverlay')`, and it imports nothing of ours but the
|
|
@@ -76,6 +99,10 @@ import {
|
|
|
76
99
|
// ConfigureWindow's stack mode: directly above the sibling it names
|
|
77
100
|
const STACK_ABOVE = 0;
|
|
78
101
|
|
|
102
|
+
// How many rects one pane's own list keeps before merging the closest pair
|
|
103
|
+
// (`Pane.owe`): each is a pass over that pane alone, which is small.
|
|
104
|
+
const OWED_CAP = 4;
|
|
105
|
+
|
|
79
106
|
// Past this share of the pixels a move carries, the frame would repaint
|
|
80
107
|
// most of them anyway — around what it claims itself, or around the other
|
|
81
108
|
// children the copy reached — and the copy only adds to the bill.
|
|
@@ -86,6 +113,10 @@ const MOVE_BLIT_MAX_REPAINT = 0.75;
|
|
|
86
113
|
// one variable away from the plain repaint (docs/debugging.md).
|
|
87
114
|
const NO_BLIT = process.env.REACT_X11_NO_SCROLL_BLIT === '1';
|
|
88
115
|
|
|
116
|
+
// …and the one for cutting an X11 pane to its ink (`X11PaneWindow.cutToInk`):
|
|
117
|
+
// with it set, every pane is the rectangle of its child's reach
|
|
118
|
+
const NO_PANE_SHAPE = process.env.REACT_X11_NO_PANE_SHAPE === '1';
|
|
119
|
+
|
|
89
120
|
// Connections on which nothing can be drawn over a GL surface, each with the
|
|
90
121
|
// reason (`beginGlOverlay`), and the probe that finds out, once per app.
|
|
91
122
|
const refusals = new WeakMap();
|
|
@@ -185,17 +216,6 @@ const overlaps = (a, b) =>
|
|
|
185
216
|
a.y < b.y + b.height &&
|
|
186
217
|
b.y < a.y + a.height;
|
|
187
218
|
|
|
188
|
-
function around(a, b) {
|
|
189
|
-
const x = Math.min(a.x, b.x);
|
|
190
|
-
const y = Math.min(a.y, b.y);
|
|
191
|
-
return {
|
|
192
|
-
x,
|
|
193
|
-
y,
|
|
194
|
-
width: Math.max(a.x + a.width, b.x + b.width) - x,
|
|
195
|
-
height: Math.max(a.y + a.height, b.y + b.height) - y,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
219
|
const sameRect = (a, b) =>
|
|
200
220
|
a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
|
201
221
|
|
|
@@ -209,35 +229,464 @@ function shown(node) {
|
|
|
209
229
|
}
|
|
210
230
|
|
|
211
231
|
/**
|
|
212
|
-
* Where
|
|
213
|
-
*
|
|
214
|
-
* overlap
|
|
215
|
-
* panes over one pixel would each have to hold what the other draws there.
|
|
232
|
+
* Where each child puts ink, in paint order: its reach in whole pixels, cut
|
|
233
|
+
* to the surface — the rect of the X11 pane that is that child's. Two that
|
|
234
|
+
* overlap keep a rect each (see the header).
|
|
216
235
|
*
|
|
217
236
|
* The reach is `_subtreeBounds()`, the rect the damage model culls against —
|
|
218
237
|
* so a child's shadow, its outline and a descendant that sticks out of it
|
|
219
238
|
* are all inside its pane.
|
|
220
239
|
*/
|
|
221
|
-
export function
|
|
222
|
-
const
|
|
240
|
+
export function childRegions(area, surface) {
|
|
241
|
+
const regions = [];
|
|
223
242
|
for (const child of area.paintOrder()) {
|
|
224
|
-
const
|
|
225
|
-
if (
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
243
|
+
const rect = intersectRects(whole(child._subtreeBounds()), surface);
|
|
244
|
+
if (rect) regions.push({ node: child, rect });
|
|
245
|
+
}
|
|
246
|
+
return regions;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ChangeWindowAttributes' backing-store value: the server keeps what the
|
|
250
|
+
// window shows while it is mapped
|
|
251
|
+
const BACKING_STORE_WHEN_MAPPED = 1;
|
|
252
|
+
// a resize keeps what the window shows at its top-left corner rather than
|
|
253
|
+
// discarding it
|
|
254
|
+
const NORTH_WEST_GRAVITY = 1;
|
|
255
|
+
// a background of None: an exposure shows what was there, not a fill colour
|
|
256
|
+
const NO_BACKGROUND = 0;
|
|
257
|
+
// The step an X11 pane's pixmap grows in. A pane whose child is zooming is
|
|
258
|
+
// a new size every frame, and a pixmap and its picture made per frame per
|
|
259
|
+
// pane is a round of requests for nothing; within the step the one it has
|
|
260
|
+
// does.
|
|
261
|
+
const PIXMAP_STEP = 64;
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* An X11 pane's drawable: a plain child window of the owning window, and a
|
|
265
|
+
* pixmap the pane is painted in — its double buffer — with the verbs a pane
|
|
266
|
+
* asks of its window (`Pane`).
|
|
267
|
+
*
|
|
268
|
+
* Not an ntk window's own backing store, because of *when* that shows. ntk
|
|
269
|
+
* presents a backing store on its own schedule — the next vblank, through
|
|
270
|
+
* Present — while a window moves and resizes the moment its ConfigureWindow
|
|
271
|
+
* reaches the server. So a pane that went somewhere else showed its old
|
|
272
|
+
* pixels in the new place for a frame, and one that grew was shown cut to
|
|
273
|
+
* the size ntk had not heard back about yet. Here the geometry waits for
|
|
274
|
+
* the paint: `present` sends the ConfigureWindow and the CopyArea of what
|
|
275
|
+
* was painted back to back, and the server applies the move and the pixels
|
|
276
|
+
* that go with it together.
|
|
277
|
+
*
|
|
278
|
+
* The window keeps its contents in the server while it is mapped (X backing
|
|
279
|
+
* store), so what a pane moving off another uncovers is put back at once;
|
|
280
|
+
* its background is None, so an exposure it does get is filled from the
|
|
281
|
+
* pixmap with nothing flashed first; and a resize keeps what it shows at
|
|
282
|
+
* the corner until the copy lands.
|
|
283
|
+
*
|
|
284
|
+
* **Cut to its ink** where the server has SHAPE (`cutToInk`). A pane is a
|
|
285
|
+
* rectangle as big as its child's reach, and a child's reach is more than
|
|
286
|
+
* it paints: a card's canvas reaches past the card for its handles, a
|
|
287
|
+
* rounded box leaves its corners, a shadow its falloff. As a rectangle, all
|
|
288
|
+
* of that covered the GL frame with the pane's ground — a graph's edges
|
|
289
|
+
* stopped short of every card and lost their arrowheads, which the 2D
|
|
290
|
+
* renderer of the same graph drew. Cut, the children are painted on a
|
|
291
|
+
* transparent ground, and the window's bounding shape is where they left
|
|
292
|
+
* half their alpha or more: the frame shows everywhere they did not paint.
|
|
293
|
+
* What stays is the X11 limit on translucency — a pixel inside the shape
|
|
294
|
+
* blends with the ground, not with the frame.
|
|
295
|
+
*/
|
|
296
|
+
export class X11PaneWindow {
|
|
297
|
+
/** Whether the app is one this can be made on: an ntk connection. */
|
|
298
|
+
static usable(app) {
|
|
299
|
+
return (
|
|
300
|
+
typeof app?.X?.CopyArea === 'function' &&
|
|
301
|
+
typeof app.createPixmap === 'function' &&
|
|
302
|
+
typeof app.createWindow === 'function'
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* @param shape the SHAPE extension to cut the pane to its ink with, or
|
|
308
|
+
* null for a rectangle (`cutToInk` can still cut it later)
|
|
309
|
+
*/
|
|
310
|
+
constructor(app, owner, rect, shape = null) {
|
|
311
|
+
this.app = app;
|
|
312
|
+
this.X = app.X;
|
|
313
|
+
// where the window is on the server, and where the next present puts it
|
|
314
|
+
this.rect = { ...rect };
|
|
315
|
+
this._next = null;
|
|
316
|
+
// mapped once there is something to show, and while the pane is shown
|
|
317
|
+
this._mapped = false;
|
|
318
|
+
this._shown = false;
|
|
319
|
+
this._painted = false;
|
|
320
|
+
// pane-local rects painted since the last present
|
|
321
|
+
this._owed = [];
|
|
322
|
+
// cut to its ink: the SHAPE extension, and the ground its alpha is laid
|
|
323
|
+
// over, straight floats (`_paintPane` sets it)
|
|
324
|
+
this._shape = null;
|
|
325
|
+
this.ground = [0, 0, 0];
|
|
326
|
+
this.window = app.createWindow({
|
|
327
|
+
parent: owner,
|
|
328
|
+
x: rect.x,
|
|
329
|
+
y: rect.y,
|
|
330
|
+
width: rect.width,
|
|
331
|
+
height: rect.height,
|
|
332
|
+
// ntk's double buffer, which this replaces — not X's backing store
|
|
333
|
+
backingStore: false,
|
|
334
|
+
backgroundPixmap: NO_BACKGROUND,
|
|
335
|
+
bitGravity: NORTH_WEST_GRAVITY,
|
|
336
|
+
});
|
|
337
|
+
this.id = this.window.id;
|
|
338
|
+
this.depth =
|
|
339
|
+
this.window.depth || owner.depth || app.display.screen[0].root_depth;
|
|
340
|
+
this.X.ChangeWindowAttributes(this.id, {
|
|
341
|
+
backingStore: BACKING_STORE_WHEN_MAPPED,
|
|
342
|
+
});
|
|
343
|
+
this._gc = this.X.AllocID();
|
|
344
|
+
this.X.CreateGC(this._gc, this.id, { graphicsExposures: 0 });
|
|
345
|
+
// the pixmaps: `buffer`, what the window is copied from; cut to its
|
|
346
|
+
// ink, `argb`, what the children paint in, and `mask`, a bit a pixel
|
|
347
|
+
// of where they did
|
|
348
|
+
this._store = null;
|
|
349
|
+
this._ctx = null;
|
|
350
|
+
if (!this.cutToInk(shape)) this._grow(rect.width, rect.height);
|
|
351
|
+
// An exposure the server could not fill from its own copy — one with no
|
|
352
|
+
// backing store, or one that let it go — is filled from the pixmap.
|
|
353
|
+
// Adding the listener is what selects Exposure (ntk).
|
|
354
|
+
this.window.on('expose', (ev) => {
|
|
355
|
+
if (this._painted) this._copy([ev]);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Paint on a transparent ground from now on, and show only where the
|
|
361
|
+
* children painted: the window's bounding shape follows their alpha. True
|
|
362
|
+
* when it changed — the pane's pixels are gone, and it is painted whole
|
|
363
|
+
* next — and false where it cannot: no SHAPE, no 1-bit picture format,
|
|
364
|
+
* or already cut.
|
|
365
|
+
*/
|
|
366
|
+
cutToInk(shape) {
|
|
367
|
+
if (NO_PANE_SHAPE || this._shape || !shape) return false;
|
|
368
|
+
const R = this.app.display.Render;
|
|
369
|
+
if (!R?.mono1 || !R.rgba32) return false;
|
|
370
|
+
this._shape = shape;
|
|
371
|
+
this._free(this._store);
|
|
372
|
+
this._store = null;
|
|
373
|
+
this._grow(this.rect.width, this.rect.height);
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** What a pane asks of its window's event selection: none of the pointer. */
|
|
378
|
+
get eventMask() {
|
|
379
|
+
return this.window.eventMask;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** A pane asks for the context every pass: it is the pixmap's, and a pane
|
|
383
|
+
* that grew past its pixmap has a new one. */
|
|
384
|
+
get ownsContext() {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** Painted on a transparent ground, which the pane lays over `ground`. */
|
|
389
|
+
get paintsAlpha() {
|
|
390
|
+
return this._shape !== null;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** Whether a present is owed without a paint: a place to take, or pixels
|
|
394
|
+
* a blit moved. */
|
|
395
|
+
get presentOwed() {
|
|
396
|
+
return this._next !== null || this._owed.length !== 0 || !this._mapped;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
getContext(name) {
|
|
400
|
+
if (name !== '2d') return null;
|
|
401
|
+
const store = this._store;
|
|
402
|
+
return (this._ctx ??= (store.argb ?? store.buffer).getContext('2d'));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** The pixmaps, at least `width`×`height`, grown in steps and never
|
|
406
|
+
* shrunk. */
|
|
407
|
+
_grow(width, height) {
|
|
408
|
+
const cur = this._store;
|
|
409
|
+
if (cur && cur.width >= width && cur.height >= height) return;
|
|
410
|
+
const up = (v) =>
|
|
411
|
+
Math.max(PIXMAP_STEP, Math.ceil(v / PIXMAP_STEP) * PIXMAP_STEP);
|
|
412
|
+
const w = up(Math.max(width, cur?.width ?? 0));
|
|
413
|
+
const h = up(Math.max(height, cur?.height ?? 0));
|
|
414
|
+
const app = this.app;
|
|
415
|
+
const X = this.X;
|
|
416
|
+
const pixmap = (depth, visual) =>
|
|
417
|
+
app.createPixmap({
|
|
418
|
+
parent: this.window,
|
|
419
|
+
width: w,
|
|
420
|
+
height: h,
|
|
421
|
+
depth,
|
|
422
|
+
visual,
|
|
423
|
+
});
|
|
424
|
+
// the window's pixels, read through the window's visual
|
|
425
|
+
const store = {
|
|
426
|
+
width: w,
|
|
427
|
+
height: h,
|
|
428
|
+
buffer: pixmap(this.depth, this.window.visualId),
|
|
429
|
+
};
|
|
430
|
+
if (this._shape) {
|
|
431
|
+
const R = app.display.Render;
|
|
432
|
+
const picture = (drawable, format) => {
|
|
433
|
+
const id = X.AllocID();
|
|
434
|
+
R.CreatePicture(id, drawable.id, format);
|
|
435
|
+
return id;
|
|
436
|
+
};
|
|
437
|
+
store.bufferPicture = picture(
|
|
438
|
+
store.buffer,
|
|
439
|
+
this.depth === 32 ? R.rgba32 : R.rgb24,
|
|
440
|
+
);
|
|
441
|
+
store.argb = pixmap(32);
|
|
442
|
+
store.argbPicture = picture(store.argb, R.rgba32);
|
|
443
|
+
store.argbGc = X.AllocID();
|
|
444
|
+
X.CreateGC(store.argbGc, store.argb.id, { graphicsExposures: 0 });
|
|
445
|
+
store.mask = pixmap(1);
|
|
446
|
+
store.maskPicture = picture(store.mask, R.mono1);
|
|
447
|
+
// nothing painted yet is nothing shown
|
|
448
|
+
R.FillRectangles(
|
|
449
|
+
R.PictOp.Src,
|
|
450
|
+
store.maskPicture,
|
|
451
|
+
[0, 0, 0, 0],
|
|
452
|
+
[0, 0, w, h],
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
this._free(cur);
|
|
456
|
+
this._store = store;
|
|
457
|
+
this._ctx = null;
|
|
458
|
+
// what the old pixmaps held is gone: the pane is painted whole next
|
|
459
|
+
this._painted = false;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
_free(store) {
|
|
463
|
+
if (!store) return;
|
|
464
|
+
const R = this.app.display.Render;
|
|
465
|
+
try {
|
|
466
|
+
for (const id of [
|
|
467
|
+
store.bufferPicture,
|
|
468
|
+
store.argbPicture,
|
|
469
|
+
store.maskPicture,
|
|
470
|
+
]) {
|
|
471
|
+
if (id) R.FreePicture(id);
|
|
237
472
|
}
|
|
473
|
+
if (store.argbGc) this.X.FreeGC(store.argbGc);
|
|
474
|
+
} catch {
|
|
475
|
+
// the connection is closing, and takes them with it
|
|
476
|
+
}
|
|
477
|
+
store.buffer.destroy();
|
|
478
|
+
store.argb?.destroy();
|
|
479
|
+
store.mask?.destroy();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/** Where the next present puts the window. A new size may be new
|
|
483
|
+
* pixmaps, which the pane paints whole anyway (`Pane.place`). */
|
|
484
|
+
setState(rect) {
|
|
485
|
+
this._next = { ...rect };
|
|
486
|
+
this._grow(rect.width, rect.height);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/** Pixels painted at `rect`, pane-local, for the next present to show. */
|
|
490
|
+
owe(rect) {
|
|
491
|
+
this._owed.push(rect);
|
|
492
|
+
this._painted = true;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** `rect` in whole pixels, cut to the pane's size — or null. */
|
|
496
|
+
_clamp(rect) {
|
|
497
|
+
const { width, height } = this._next ?? this.rect;
|
|
498
|
+
const x0 = Math.max(0, Math.floor(rect.x));
|
|
499
|
+
const y0 = Math.max(0, Math.floor(rect.y));
|
|
500
|
+
const x1 = Math.min(width, Math.ceil(rect.x + rect.width));
|
|
501
|
+
const y1 = Math.min(height, Math.ceil(rect.y + rect.height));
|
|
502
|
+
return x1 > x0 && y1 > y0 ? { x0, y0, x1, y1 } : null;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* `Window.scrollRegion`'s contract, on the pixmap the children are
|
|
507
|
+
* painted in: the band of `rect` (pane-local) that survives the shift
|
|
508
|
+
* moves, and all of `rect` is owed to the window at the next present.
|
|
509
|
+
*/
|
|
510
|
+
scrollRegion(rect, dx, dy) {
|
|
511
|
+
if (!this._painted) return false;
|
|
512
|
+
if (
|
|
513
|
+
!Number.isInteger(dx) ||
|
|
514
|
+
!Number.isInteger(dy) ||
|
|
515
|
+
(dx === 0 && dy === 0)
|
|
516
|
+
) {
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
const r = this._clamp(rect);
|
|
520
|
+
if (!r) return false;
|
|
521
|
+
const { x0, y0, x1, y1 } = r;
|
|
522
|
+
const dstX0 = Math.max(x0, x0 + dx);
|
|
523
|
+
const dstY0 = Math.max(y0, y0 + dy);
|
|
524
|
+
const dstX1 = Math.min(x1, x1 + dx);
|
|
525
|
+
const dstY1 = Math.min(y1, y1 + dy);
|
|
526
|
+
if (dstX1 <= dstX0 || dstY1 <= dstY0) return false;
|
|
527
|
+
const store = this._store;
|
|
528
|
+
const target = store.argb ?? store.buffer;
|
|
529
|
+
this.X.CopyArea(
|
|
530
|
+
target.id,
|
|
531
|
+
target.id,
|
|
532
|
+
store.argbGc ?? this._gc,
|
|
533
|
+
dstX0 - dx,
|
|
534
|
+
dstY0 - dy,
|
|
535
|
+
dstX0,
|
|
536
|
+
dstY0,
|
|
537
|
+
dstX1 - dstX0,
|
|
538
|
+
dstY1 - dstY0,
|
|
539
|
+
);
|
|
540
|
+
this.owe({ x: x0, y: y0, width: x1 - x0, height: y1 - y0 });
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Show what was painted, where it goes: the ConfigureWindow of a new
|
|
546
|
+
* place; where the pane is cut to its ink, the painted rects laid over the
|
|
547
|
+
* ground and their alpha into the mask, and the shape set from it; the
|
|
548
|
+
* map of a pane shown for the first time; and the copies of what was
|
|
549
|
+
* painted since — back to back, one batch the server applies together.
|
|
550
|
+
* The shape goes before the copies: a pixel it takes in shows what was
|
|
551
|
+
* under it, never the pane's old one, until its copy lands.
|
|
552
|
+
*/
|
|
553
|
+
present() {
|
|
554
|
+
const X = this.X;
|
|
555
|
+
const next = this._next;
|
|
556
|
+
if (next) {
|
|
557
|
+
this._next = null;
|
|
558
|
+
const change = {};
|
|
559
|
+
if (next.x !== this.rect.x) change.x = next.x;
|
|
560
|
+
if (next.y !== this.rect.y) change.y = next.y;
|
|
561
|
+
if (next.width !== this.rect.width) change.width = next.width;
|
|
562
|
+
if (next.height !== this.rect.height) change.height = next.height;
|
|
563
|
+
this.rect = next;
|
|
564
|
+
if (Object.keys(change).length !== 0) X.ConfigureWindow(this.id, change);
|
|
565
|
+
}
|
|
566
|
+
if (!this._painted) return;
|
|
567
|
+
const owed = [];
|
|
568
|
+
for (const rect of this._owed) {
|
|
569
|
+
const r = this._clamp(rect);
|
|
570
|
+
if (r) owed.push(r);
|
|
571
|
+
}
|
|
572
|
+
this._owed = [];
|
|
573
|
+
const store = this._store;
|
|
574
|
+
if (this._shape && owed.length !== 0) {
|
|
575
|
+
const R = this.app.display.Render;
|
|
576
|
+
const [r, g, b] = this.ground;
|
|
577
|
+
for (const { x0, y0, x1, y1 } of owed) {
|
|
578
|
+
const w = x1 - x0;
|
|
579
|
+
const h = y1 - y0;
|
|
580
|
+
R.FillRectangles(
|
|
581
|
+
R.PictOp.Src,
|
|
582
|
+
store.bufferPicture,
|
|
583
|
+
[r, g, b, 1],
|
|
584
|
+
[x0, y0, w, h],
|
|
585
|
+
);
|
|
586
|
+
R.Composite(
|
|
587
|
+
R.PictOp.Over,
|
|
588
|
+
store.argbPicture,
|
|
589
|
+
0,
|
|
590
|
+
store.bufferPicture,
|
|
591
|
+
x0,
|
|
592
|
+
y0,
|
|
593
|
+
0,
|
|
594
|
+
0,
|
|
595
|
+
x0,
|
|
596
|
+
y0,
|
|
597
|
+
w,
|
|
598
|
+
h,
|
|
599
|
+
);
|
|
600
|
+
R.Composite(
|
|
601
|
+
R.PictOp.Src,
|
|
602
|
+
store.argbPicture,
|
|
603
|
+
0,
|
|
604
|
+
store.maskPicture,
|
|
605
|
+
x0,
|
|
606
|
+
y0,
|
|
607
|
+
0,
|
|
608
|
+
0,
|
|
609
|
+
x0,
|
|
610
|
+
y0,
|
|
611
|
+
w,
|
|
612
|
+
h,
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
const S = this._shape;
|
|
616
|
+
S.Mask(S.Op.Set, S.Kind.Bounding, this.id, 0, 0, store.mask.id);
|
|
617
|
+
}
|
|
618
|
+
if (this._shown && !this._mapped) {
|
|
619
|
+
X.MapWindow(this.id);
|
|
620
|
+
this._mapped = true;
|
|
621
|
+
}
|
|
622
|
+
for (const { x0, y0, x1, y1 } of owed) {
|
|
623
|
+
X.CopyArea(
|
|
624
|
+
store.buffer.id,
|
|
625
|
+
this.id,
|
|
626
|
+
this._gc,
|
|
627
|
+
x0,
|
|
628
|
+
y0,
|
|
629
|
+
x0,
|
|
630
|
+
y0,
|
|
631
|
+
x1 - x0,
|
|
632
|
+
y1 - y0,
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/** The buffer's pixels at `rects` (pane-local), onto the window. */
|
|
638
|
+
_copy(rects) {
|
|
639
|
+
const { width, height } = this.rect;
|
|
640
|
+
for (const r of rects) {
|
|
641
|
+
const x0 = Math.max(0, Math.floor(r.x));
|
|
642
|
+
const y0 = Math.max(0, Math.floor(r.y));
|
|
643
|
+
const x1 = Math.min(width, Math.ceil(r.x + r.width));
|
|
644
|
+
const y1 = Math.min(height, Math.ceil(r.y + r.height));
|
|
645
|
+
if (x1 <= x0 || y1 <= y0) continue;
|
|
646
|
+
this.X.CopyArea(
|
|
647
|
+
this._store.buffer.id,
|
|
648
|
+
this.id,
|
|
649
|
+
this._gc,
|
|
650
|
+
x0,
|
|
651
|
+
y0,
|
|
652
|
+
x0,
|
|
653
|
+
y0,
|
|
654
|
+
x1 - x0,
|
|
655
|
+
y1 - y0,
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
map() {
|
|
661
|
+
this._shown = true;
|
|
662
|
+
// a pane with nothing painted yet is mapped by the present that shows it
|
|
663
|
+
if (this._painted && !this._mapped) {
|
|
664
|
+
this.X.MapWindow(this.id);
|
|
665
|
+
this._mapped = true;
|
|
666
|
+
this._copy([
|
|
667
|
+
{ x: 0, y: 0, width: this.rect.width, height: this.rect.height },
|
|
668
|
+
]);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
unmap() {
|
|
673
|
+
this._shown = false;
|
|
674
|
+
if (!this._mapped) return;
|
|
675
|
+
this.X.UnmapWindow(this.id);
|
|
676
|
+
this._mapped = false;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
destroy() {
|
|
680
|
+
this.window.destroy?.();
|
|
681
|
+
this._free(this._store);
|
|
682
|
+
this._store = null;
|
|
683
|
+
this._ctx = null;
|
|
684
|
+
try {
|
|
685
|
+
this.X.FreeGC(this._gc);
|
|
686
|
+
} catch {
|
|
687
|
+
// the connection is closing, and takes the GC with it
|
|
238
688
|
}
|
|
239
689
|
}
|
|
240
|
-
return rects;
|
|
241
690
|
}
|
|
242
691
|
|
|
243
692
|
/**
|
|
@@ -247,12 +696,45 @@ export function overlayRegions(area, surface) {
|
|
|
247
696
|
* the pane's own last frame rather than show anything under it.
|
|
248
697
|
*/
|
|
249
698
|
function groundOf(props) {
|
|
699
|
+
const [r, g, b] = groundRgb(props);
|
|
700
|
+
const byte = (c) => Math.round(c * 255);
|
|
701
|
+
return `rgb(${byte(r)}, ${byte(g)}, ${byte(b)})`;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/** `groundOf` as straight [r, g, b] floats, for a pane that lays its
|
|
705
|
+
* children over it itself (`X11PaneWindow.present`). */
|
|
706
|
+
function groundRgb(props) {
|
|
250
707
|
const value = props.clearColor ?? 'black';
|
|
251
708
|
const [r, g, b] = Array.isArray(value)
|
|
252
709
|
? value
|
|
253
710
|
: (cssColorStraight(value) ?? [0, 0, 0, 1]);
|
|
254
|
-
const
|
|
255
|
-
return
|
|
711
|
+
const clamp = (c) => Math.max(0, Math.min(1, c));
|
|
712
|
+
return [clamp(r), clamp(g), clamp(b)];
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* The SHAPE extension of an app's connection, asked once: resolves to it,
|
|
717
|
+
* or to null where the server has none — node-x11's in-process server, for
|
|
718
|
+
* one. Asked by the first overlay that makes X11 panes rather than at
|
|
719
|
+
* startup, so an app with no `<glarea>` children never asks.
|
|
720
|
+
*/
|
|
721
|
+
const shapeQueries = new WeakMap();
|
|
722
|
+
function askShape(app) {
|
|
723
|
+
let pending = shapeQueries.get(app);
|
|
724
|
+
if (!pending) {
|
|
725
|
+
pending = new Promise((resolve) => {
|
|
726
|
+
try {
|
|
727
|
+
app.X.require('shape', (err, ext) =>
|
|
728
|
+
resolve(err ? null : (ext ?? null)),
|
|
729
|
+
);
|
|
730
|
+
} catch {
|
|
731
|
+
// a connection that cannot ask has none to give
|
|
732
|
+
resolve(null);
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
shapeQueries.set(app, pending);
|
|
736
|
+
}
|
|
737
|
+
return pending;
|
|
256
738
|
}
|
|
257
739
|
|
|
258
740
|
/**
|
|
@@ -262,10 +744,17 @@ function groundOf(props) {
|
|
|
262
744
|
* a fresh one with subscriptions of its own on every `getContext`.
|
|
263
745
|
*/
|
|
264
746
|
class Pane {
|
|
265
|
-
|
|
747
|
+
/**
|
|
748
|
+
* @param node the child an X11 pane is kept by and holds, up to — null for
|
|
749
|
+
* a composited pane, which holds every child
|
|
750
|
+
* @param overlay the panes it is one of, which `canBlit` asks about
|
|
751
|
+
*/
|
|
752
|
+
constructor(wnd, rect, transparent, node = null, overlay = null) {
|
|
266
753
|
this.wnd = wnd;
|
|
267
754
|
this.rect = rect;
|
|
268
755
|
this.transparent = transparent;
|
|
756
|
+
this.node = node;
|
|
757
|
+
this.overlay = overlay;
|
|
269
758
|
this.full = true;
|
|
270
759
|
this.ctx = null;
|
|
271
760
|
// placed this frame: what the pane holds is where the children were in
|
|
@@ -275,15 +764,30 @@ class Pane {
|
|
|
275
764
|
// moved pixels this frame, which has to reach the screen even when no
|
|
276
765
|
// pass follows it (`GlOverlay.paint`)
|
|
277
766
|
this.blitted = false;
|
|
767
|
+
// what this pane alone is owed this frame, beside the damage every pane
|
|
768
|
+
// is cut from: the pixels a moved child's pane carried wrongly, or the
|
|
769
|
+
// ones of it a pane over it holds (`_settleMove`)
|
|
770
|
+
this.owed = [];
|
|
771
|
+
// X11: placed this frame by exactly its child's move, which the window's
|
|
772
|
+
// move carried (`_syncChildren`, `_settleCarried`)
|
|
773
|
+
this.carried = false;
|
|
278
774
|
}
|
|
279
775
|
|
|
280
776
|
context() {
|
|
777
|
+
// an X11 pane's is its pixmap's, which a pane that grew has a new one of
|
|
778
|
+
if (this.wnd.ownsContext) return this.wnd.getContext('2d');
|
|
281
779
|
if (!this.ctx && typeof this.wnd.getContext === 'function') {
|
|
282
780
|
this.ctx = this.wnd.getContext('2d');
|
|
283
781
|
}
|
|
284
782
|
return this.ctx;
|
|
285
783
|
}
|
|
286
784
|
|
|
785
|
+
/** One rect more for this pane alone, window coordinates. */
|
|
786
|
+
owe(rect) {
|
|
787
|
+
const hit = rect && intersectRects(rect, this.rect);
|
|
788
|
+
if (hit) this.owed = addDamageRect(this.owed, hit, OWED_CAP);
|
|
789
|
+
}
|
|
790
|
+
|
|
287
791
|
/** Somewhere else. A new size is a new backing — a pixmap on X11, a
|
|
288
792
|
* bitmap on Cocoa — and has to be painted whole; a move keeps what the
|
|
289
793
|
* pane holds, which moved with the children it shows. */
|
|
@@ -322,6 +826,11 @@ class Pane {
|
|
|
322
826
|
* the Cocoa pane's own): the band that survives inside `rect` moves, the
|
|
323
827
|
* rest of it is left as it was. False when the backend could not, which
|
|
324
828
|
* leaves the pane exactly as it was.
|
|
829
|
+
*
|
|
830
|
+
* A child's pane on X11 is not the only one holding those pixels: every
|
|
831
|
+
* pane over it that reaches into `rect` holds a copy, which the move
|
|
832
|
+
* leaves where it was. Those are owed `rect` (`owe`), so they repaint
|
|
833
|
+
* what they hold of it.
|
|
325
834
|
*/
|
|
326
835
|
blit(rect, dx, dy) {
|
|
327
836
|
const local = {
|
|
@@ -332,6 +841,12 @@ class Pane {
|
|
|
332
841
|
};
|
|
333
842
|
if (!this.wnd.scrollRegion(local, dx, dy)) return false;
|
|
334
843
|
this.blitted = true;
|
|
844
|
+
const panes = this.node ? this.overlay?.panes : null;
|
|
845
|
+
if (panes) {
|
|
846
|
+
for (let i = panes.indexOf(this) + 1; i < panes.length; i++) {
|
|
847
|
+
panes[i].owe(rect);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
335
850
|
return true;
|
|
336
851
|
}
|
|
337
852
|
|
|
@@ -353,7 +868,23 @@ export class GlOverlay {
|
|
|
353
868
|
this.app = area.app;
|
|
354
869
|
// one pane over the whole surface, where the backend composites it
|
|
355
870
|
this.composited = typeof this.app?.createOverlayPane === 'function';
|
|
871
|
+
// bottom to top: the children's order on X11, one pane each
|
|
356
872
|
this.panes = [];
|
|
873
|
+
// X11: each child's pane, kept by the child for as long as it has one
|
|
874
|
+
this.byNode = new Map();
|
|
875
|
+
// X11: the SHAPE extension the panes are cut to their ink with — null
|
|
876
|
+
// until the server has answered, and for good where it has none
|
|
877
|
+
this.shape = null;
|
|
878
|
+
if (!this.composited && X11PaneWindow.usable(this.app)) {
|
|
879
|
+
askShape(this.app).then((shape) => {
|
|
880
|
+
if (!shape || this.destroyed) return;
|
|
881
|
+
this.shape = shape;
|
|
882
|
+
// panes made before the answer: cut now, painted whole next frame
|
|
883
|
+
for (const pane of this.panes) {
|
|
884
|
+
if (pane.wnd.cutToInk?.(shape)) this._lost(pane);
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
}
|
|
357
888
|
// the children this frame's layout moved and nothing else, reported
|
|
358
889
|
// rather than claimed (`GlAreaNode._absolutizeChild`) and settled after
|
|
359
890
|
// the panes are (`settleMoves`)
|
|
@@ -373,42 +904,115 @@ export class GlOverlay {
|
|
|
373
904
|
const area = this.area;
|
|
374
905
|
const abs = area.abs;
|
|
375
906
|
for (const pane of this.panes) pane.placed = false;
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
else if (area.paintOrder().length) rects = [surface];
|
|
382
|
-
}
|
|
907
|
+
const visible = shown(area) && abs.width > 0 && abs.height > 0;
|
|
908
|
+
// the surface's own rect, rounded the way its window's is
|
|
909
|
+
const surface = visible ? area._geometry() : null;
|
|
910
|
+
if (!this.composited) return this._syncChildren(surface);
|
|
911
|
+
const rects = surface && area.paintOrder().length ? [surface] : [];
|
|
383
912
|
let changed = false;
|
|
384
913
|
while (this.panes.length > rects.length) {
|
|
385
914
|
this.panes.pop().destroy();
|
|
386
915
|
changed = true;
|
|
387
916
|
}
|
|
388
|
-
let made = false;
|
|
389
917
|
for (let i = 0; i < rects.length; i++) {
|
|
390
918
|
const pane = this.panes[i];
|
|
391
919
|
if (!pane) {
|
|
392
|
-
this.panes.push(this._makePane(rects[i]));
|
|
393
|
-
|
|
920
|
+
this.panes.push(this._makePane(rects[i], null));
|
|
921
|
+
changed = true;
|
|
394
922
|
} else if (!sameRect(pane.rect, rects[i])) {
|
|
395
923
|
pane.place(rects[i]);
|
|
396
924
|
changed = true;
|
|
397
925
|
}
|
|
398
926
|
}
|
|
399
|
-
if (made) this.restack();
|
|
400
927
|
return changed;
|
|
401
928
|
}
|
|
402
929
|
|
|
403
|
-
|
|
930
|
+
/**
|
|
931
|
+
* `sync` on X11: a pane per child, kept by the child. A child that is new
|
|
932
|
+
* gets one, a child that went takes its with it, and one whose reach moved
|
|
933
|
+
* has its pane placed there — and painted whole, unless the move was the
|
|
934
|
+
* child's own and nothing else, which the window's move carries
|
|
935
|
+
* (`_settleMove` repaints what it does not). A change of order restacks
|
|
936
|
+
* them, and repaints whichever overlap another: what a pane holds is what
|
|
937
|
+
* is under its child.
|
|
938
|
+
*/
|
|
939
|
+
_syncChildren(surface) {
|
|
940
|
+
const regions = surface ? childRegions(this.area, surface) : [];
|
|
941
|
+
const moved = new Map();
|
|
942
|
+
for (const move of this.moves) moved.set(move.node, move);
|
|
943
|
+
const next = [];
|
|
944
|
+
let changed = false;
|
|
945
|
+
let made = false;
|
|
946
|
+
const kept = new Set();
|
|
947
|
+
for (const pane of this.panes) pane.carried = false;
|
|
948
|
+
for (const { node, rect } of regions) {
|
|
949
|
+
let pane = this.byNode.get(node);
|
|
950
|
+
if (!pane) {
|
|
951
|
+
pane = this._makePane(rect, node);
|
|
952
|
+
this.byNode.set(node, pane);
|
|
953
|
+
made = changed = true;
|
|
954
|
+
} else if (!sameRect(pane.rect, rect)) {
|
|
955
|
+
const move = moved.get(node);
|
|
956
|
+
const carried =
|
|
957
|
+
move !== undefined &&
|
|
958
|
+
!pane.full &&
|
|
959
|
+
rect.width === pane.rect.width &&
|
|
960
|
+
rect.height === pane.rect.height &&
|
|
961
|
+
rect.x === pane.rect.x + move.dx &&
|
|
962
|
+
rect.y === pane.rect.y + move.dy;
|
|
963
|
+
pane.place(rect);
|
|
964
|
+
if (carried) pane.carried = true;
|
|
965
|
+
else pane.full = true;
|
|
966
|
+
changed = true;
|
|
967
|
+
}
|
|
968
|
+
kept.add(node);
|
|
969
|
+
next.push(pane);
|
|
970
|
+
}
|
|
971
|
+
for (const [node, pane] of this.byNode) {
|
|
972
|
+
if (kept.has(node)) continue;
|
|
973
|
+
pane.destroy();
|
|
974
|
+
this.byNode.delete(node);
|
|
975
|
+
changed = true;
|
|
976
|
+
}
|
|
977
|
+
const reordered =
|
|
978
|
+
next.length !== this.panes.length ||
|
|
979
|
+
next.some((pane, i) => pane !== this.panes[i]);
|
|
980
|
+
this.panes = next;
|
|
981
|
+
if (reordered) {
|
|
982
|
+
this.restack();
|
|
983
|
+
if (!made) {
|
|
984
|
+
// what each pane holds is its child and what is under it: a new
|
|
985
|
+
// order is new contents wherever two of them meet
|
|
986
|
+
for (const pane of next) {
|
|
987
|
+
if (
|
|
988
|
+
next.some(
|
|
989
|
+
(other) => other !== pane && overlaps(other.rect, pane.rect),
|
|
990
|
+
)
|
|
991
|
+
) {
|
|
992
|
+
pane.full = true;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
changed = true;
|
|
997
|
+
}
|
|
998
|
+
return changed;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
_makePane(rect, node) {
|
|
404
1002
|
const owner = this.area.root.window;
|
|
405
1003
|
const attributes = { parent: owner, ...rect };
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
1004
|
+
let wnd;
|
|
1005
|
+
if (this.composited) wnd = this.app.createOverlayPane(attributes);
|
|
1006
|
+
else if (X11PaneWindow.usable(this.app)) {
|
|
1007
|
+
// a child window and its own double buffer, selecting nothing but the
|
|
1008
|
+
// exposures a pixmap answers — the pointer is the tree's
|
|
1009
|
+
wnd = new X11PaneWindow(this.app, owner, rect, this.shape);
|
|
1010
|
+
} else {
|
|
1011
|
+
// an app with no X connection to speak for itself — the headless mock
|
|
1012
|
+
// — makes its panes as plain windows
|
|
1013
|
+
wnd = this.app.createWindow(attributes);
|
|
1014
|
+
}
|
|
1015
|
+
const pane = new Pane(wnd, rect, this.composited, node, this);
|
|
412
1016
|
// ntk asks for a redraw when the backing no longer holds the picture — a
|
|
413
1017
|
// resize it could not carry over — and a pane has nothing to redraw from
|
|
414
1018
|
// but the children, on the next frame of the window they live in
|
|
@@ -480,6 +1084,22 @@ export class GlOverlay {
|
|
|
480
1084
|
if (!root || root.destroyed || root._paneDamage === FULL_DAMAGE) {
|
|
481
1085
|
return true;
|
|
482
1086
|
}
|
|
1087
|
+
if (!this.composited) {
|
|
1088
|
+
// Each child's move is its own pane's to settle: carried by the
|
|
1089
|
+
// window's move where its pane went with it, moved on the pane where
|
|
1090
|
+
// the pane stayed — cut to the surface, say, which a box bigger than
|
|
1091
|
+
// it is — or claimed.
|
|
1092
|
+
const reaches = new Map();
|
|
1093
|
+
for (const move of moves) reaches.set(move.node, move.reach);
|
|
1094
|
+
for (const move of moves) {
|
|
1095
|
+
const own = this.byNode.get(move.node);
|
|
1096
|
+
if (own?.carried) this._settleCarried(move, reaches);
|
|
1097
|
+
else if (!own || !this._blitMove(root, move, own)) {
|
|
1098
|
+
this._claimMove(root, move);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
return true;
|
|
1102
|
+
}
|
|
483
1103
|
const surface = this.area._geometry();
|
|
484
1104
|
let best = null;
|
|
485
1105
|
let most = 0;
|
|
@@ -498,6 +1118,46 @@ export class GlOverlay {
|
|
|
498
1118
|
return true;
|
|
499
1119
|
}
|
|
500
1120
|
|
|
1121
|
+
/**
|
|
1122
|
+
* A child that only moved, on X11, whose pane went with it (`sync`): what
|
|
1123
|
+
* each pane is owed for it, and nothing in the frame's damage, which every
|
|
1124
|
+
* pane is cut from.
|
|
1125
|
+
*
|
|
1126
|
+
* - Its own pane: the window's move brought the child's pixels and the
|
|
1127
|
+
* ground around them, which is one colour, and took along whatever of
|
|
1128
|
+
* the children under it the pane held. Those are owed where they are
|
|
1129
|
+
* now and where the move put their old pixels — and when nothing is
|
|
1130
|
+
* under it, nothing is, which is a card dragged through open graph: one
|
|
1131
|
+
* ConfigureWindow a step.
|
|
1132
|
+
* - The panes over it, which hold its pixels where they meet it: where it
|
|
1133
|
+
* was, and where it is.
|
|
1134
|
+
* - The panes under it hold nothing of it, and are owed nothing.
|
|
1135
|
+
*/
|
|
1136
|
+
_settleCarried({ node, reach, dx, dy }, reaches) {
|
|
1137
|
+
const panes = this.panes;
|
|
1138
|
+
const at = panes.indexOf(this.byNode.get(node));
|
|
1139
|
+
const own = panes[at];
|
|
1140
|
+
const order = this.area.paintOrder();
|
|
1141
|
+
const upTo = order.indexOf(node);
|
|
1142
|
+
for (let i = 0; i < upTo; i++) {
|
|
1143
|
+
const under = order[i];
|
|
1144
|
+
const bounds = [whole(under.paintBounds())];
|
|
1145
|
+
// one that moved this frame as well was somewhere else in the pane
|
|
1146
|
+
const before = reaches.get(under);
|
|
1147
|
+
if (before) bounds.push(whole(before));
|
|
1148
|
+
for (const b of bounds) {
|
|
1149
|
+
own.owe(b);
|
|
1150
|
+
own.owe(shiftRect(b, dx, dy));
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
const was = whole(reach);
|
|
1154
|
+
const now = whole(node.paintBounds());
|
|
1155
|
+
for (let i = at + 1; i < panes.length; i++) {
|
|
1156
|
+
panes[i].owe(was);
|
|
1157
|
+
panes[i].owe(now);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
501
1161
|
/** Where a move's child was and where it is, claimed the ordinary way —
|
|
502
1162
|
* cut to the surface's box, which clips the children (`clipsChildren`). */
|
|
503
1163
|
_claimMove(root, { node, reach }) {
|
|
@@ -534,7 +1194,7 @@ export class GlOverlay {
|
|
|
534
1194
|
* Past `MOVE_BLIT_MAX_REPAINT` of `dest` those repaint most of what the
|
|
535
1195
|
* copy saves, and the plain claims have the frame.
|
|
536
1196
|
*/
|
|
537
|
-
_blitMove(root, { node, reach, dx, dy }) {
|
|
1197
|
+
_blitMove(root, { node, reach, dx, dy }, own = null) {
|
|
538
1198
|
if (NO_BLIT || !Number.isInteger(dx) || !Number.isInteger(dy)) {
|
|
539
1199
|
return false;
|
|
540
1200
|
}
|
|
@@ -549,9 +1209,12 @@ export class GlOverlay {
|
|
|
549
1209
|
const now = intersectRects(whole(node.paintBounds()), box);
|
|
550
1210
|
// nothing of it was on show, so there is nothing to carry
|
|
551
1211
|
if (!was) return false;
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
1212
|
+
// the child's own pane on X11, the one over the whole surface elsewhere
|
|
1213
|
+
const pane = own
|
|
1214
|
+
? own.canBlit(was) && (!now || own.canBlit(now))
|
|
1215
|
+
? own
|
|
1216
|
+
: null
|
|
1217
|
+
: this.panes.find((p) => p.canBlit(was) && (!now || p.canBlit(now)));
|
|
555
1218
|
if (!pane) return false;
|
|
556
1219
|
const view = intersectRects(pane.rect, box);
|
|
557
1220
|
const dest = intersectRects(shiftRect(was, dx, dy), view);
|
|
@@ -566,7 +1229,13 @@ export class GlOverlay {
|
|
|
566
1229
|
add(intersectRects(shiftRect(claim, dx, dy), dest));
|
|
567
1230
|
for (const piece of outside(was, dest)) add(piece);
|
|
568
1231
|
if (now) for (const piece of outside(now, dest)) add(piece);
|
|
569
|
-
|
|
1232
|
+
// the children whose pixels the pane holds: every other one on a pane
|
|
1233
|
+
// over the whole surface, and on X11 the ones under this child — the
|
|
1234
|
+
// panes over it are owed the copy by `Pane.blit`
|
|
1235
|
+
const order = area.paintOrder();
|
|
1236
|
+
const upTo = own ? order.indexOf(node) : order.length;
|
|
1237
|
+
for (let i = 0; i < upTo; i++) {
|
|
1238
|
+
const other = order[i];
|
|
570
1239
|
if (other === node) continue;
|
|
571
1240
|
const theirs = whole(other.paintBounds());
|
|
572
1241
|
add(intersectRects(theirs, dest));
|
|
@@ -607,15 +1276,20 @@ export class GlOverlay {
|
|
|
607
1276
|
passes = [];
|
|
608
1277
|
for (const rect of damage) {
|
|
609
1278
|
const hit = intersectRects(rect, pane.rect);
|
|
610
|
-
if (hit) passes
|
|
1279
|
+
if (hit) passes = addDamageRect(passes, hit, OWED_CAP);
|
|
1280
|
+
}
|
|
1281
|
+
for (const rect of pane.owed) {
|
|
1282
|
+
passes = addDamageRect(passes, rect, OWED_CAP);
|
|
611
1283
|
}
|
|
612
1284
|
}
|
|
1285
|
+
pane.owed = [];
|
|
613
1286
|
const blitted = pane.blitted;
|
|
614
1287
|
pane.blitted = false;
|
|
615
1288
|
pane.placed = false;
|
|
616
1289
|
if (passes.length === 0) {
|
|
617
|
-
// pixels moved and nothing else owed: they
|
|
618
|
-
|
|
1290
|
+
// pixels moved, or a place to take, and nothing else owed: they
|
|
1291
|
+
// still go out
|
|
1292
|
+
if (blitted || pane.wnd.presentOwed) pane.wnd.present?.();
|
|
619
1293
|
continue;
|
|
620
1294
|
}
|
|
621
1295
|
pane.full = false;
|
|
@@ -628,8 +1302,12 @@ export class GlOverlay {
|
|
|
628
1302
|
const root = area.root;
|
|
629
1303
|
if (!root) return;
|
|
630
1304
|
// an opaque pane is filled with what the surface starts its frames from;
|
|
631
|
-
// a composited one is cleared, and shows the frame itself
|
|
632
|
-
|
|
1305
|
+
// a composited one is cleared, and shows the frame itself; and one cut
|
|
1306
|
+
// to its ink is cleared too, and lays the ground under what the children
|
|
1307
|
+
// painted itself, where the frame is not shown (`X11PaneWindow`)
|
|
1308
|
+
const alpha = pane.transparent || pane.wnd.paintsAlpha === true;
|
|
1309
|
+
const ground = alpha ? null : groundOf(area.props);
|
|
1310
|
+
if (pane.wnd.paintsAlpha) pane.wnd.ground = groundRgb(area.props);
|
|
633
1311
|
// one pass, drawn by a context in window coordinates
|
|
634
1312
|
const paintPass = (ctx, pass) => {
|
|
635
1313
|
ctx.save();
|
|
@@ -644,7 +1322,9 @@ export class GlOverlay {
|
|
|
644
1322
|
ctx.clearRect(pass.x, pass.y, pass.width, pass.height);
|
|
645
1323
|
}
|
|
646
1324
|
root._paintDamage = pass;
|
|
647
|
-
|
|
1325
|
+
// an X11 pane holds its child and what is under it, and never
|
|
1326
|
+
// what is over it — the pane over it holds that (see the header)
|
|
1327
|
+
area._paintChildren(ctx, pane.node);
|
|
648
1328
|
} finally {
|
|
649
1329
|
root._paintDamage = null;
|
|
650
1330
|
ctx.restore();
|
|
@@ -667,8 +1347,19 @@ export class GlOverlay {
|
|
|
667
1347
|
ctx.restore();
|
|
668
1348
|
}
|
|
669
1349
|
}
|
|
670
|
-
//
|
|
1350
|
+
// What was painted goes out with the place it goes to (`X11PaneWindow`);
|
|
1351
|
+
// a layer's contents are a copy the bitmap is pushed to; an ntk window's
|
|
671
1352
|
// backing store is blitted by ntk on its own, from the paint above
|
|
1353
|
+
if (typeof pane.wnd.owe === 'function') {
|
|
1354
|
+
for (const pass of passes) {
|
|
1355
|
+
pane.wnd.owe({
|
|
1356
|
+
x: pass.x - pane.rect.x,
|
|
1357
|
+
y: pass.y - pane.rect.y,
|
|
1358
|
+
width: pass.width,
|
|
1359
|
+
height: pass.height,
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
672
1363
|
pane.wnd.present?.();
|
|
673
1364
|
}
|
|
674
1365
|
|
|
@@ -678,7 +1369,9 @@ export class GlOverlay {
|
|
|
678
1369
|
}
|
|
679
1370
|
|
|
680
1371
|
destroy() {
|
|
1372
|
+
this.destroyed = true;
|
|
681
1373
|
for (const pane of this.panes) pane.destroy();
|
|
682
1374
|
this.panes = [];
|
|
1375
|
+
this.byNode.clear();
|
|
683
1376
|
}
|
|
684
1377
|
}
|