react-x11 2.28.0 → 2.30.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 +2 -2
- package/src/cocoa/app.js +157 -9
- package/src/cocoa/panehost.js +226 -2
- package/src/cocoa/panewindow.js +204 -14
- package/src/cocoa/promotion.js +78 -13
- package/src/cocoa/sprites.js +605 -0
- package/src/cocoa/window.js +4 -0
- package/src/components/Select.js +98 -7
- package/src/frame/index.js +60 -7
- package/src/node.d.ts +96 -0
- package/src/nodes/node.js +8 -1
- package/src/nodes/sprites.js +73 -0
- package/src/nodes/window/window.js +14 -1
- package/src/types/components.d.ts +10 -0
package/src/cocoa/panewindow.js
CHANGED
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
// via emit. Geometry and input arrive as channel messages (the host owns
|
|
15
15
|
// layout and hit-testing — this is CPU offloading, not isolation), and what
|
|
16
16
|
// goes out is pane-present, and pane-cursor for the cursor the host shows.
|
|
17
|
+
//
|
|
18
|
+
// Every other window the pane's tree makes — a menu, a dropdown's sheet, a
|
|
19
|
+
// tooltip, a dialog — is a `CocoaPaneSubwindow`: the same ring and the same
|
|
20
|
+
// presents, naming the window, and an NSWindow the host makes for it
|
|
21
|
+
// (src/cocoa/panehost.js), because nothing in a pane process runs AppKit.
|
|
17
22
|
import { BackendContext2D } from '../backend/context2d.js';
|
|
18
23
|
|
|
19
24
|
let nextPaneId = 1;
|
|
@@ -31,13 +36,15 @@ export class CocoaPaneWindow {
|
|
|
31
36
|
this.id = this.windowId;
|
|
32
37
|
this.windowNumber = this.windowId;
|
|
33
38
|
this.scale = app.scale ?? 2;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
// Device pixels, as every window's attributes are (`windowAttributes`
|
|
40
|
+
// multiplies the props through once): the size the pane is born at,
|
|
41
|
+
// until the host's first pane-rect says what it is. Multiplied again
|
|
42
|
+
// here, a pane was born twice its size, and a dialog it opened in its
|
|
43
|
+
// first commit was centred over a window that was never there.
|
|
44
|
+
this.width = Math.max(1, Math.round(attributes.width ?? 400 * this.scale));
|
|
38
45
|
this.height = Math.max(
|
|
39
46
|
1,
|
|
40
|
-
Math.round(
|
|
47
|
+
Math.round(attributes.height ?? 300 * this.scale),
|
|
41
48
|
);
|
|
42
49
|
this._listeners = new Map();
|
|
43
50
|
this._surface = null;
|
|
@@ -52,27 +59,51 @@ export class CocoaPaneWindow {
|
|
|
52
59
|
this._seq = 0;
|
|
53
60
|
this._presentedAt = -Infinity;
|
|
54
61
|
this._reactX11Node = null;
|
|
62
|
+
// Where the pane's corner is on the screen, in device pixels, as the
|
|
63
|
+
// host last said (`setPaneSize`): what a popup the pane anchors to one
|
|
64
|
+
// of its nodes is placed against (`windowOrigin`, src/anchor.js). The
|
|
65
|
+
// pane has no window of its own to ask, so until the host says, a menu
|
|
66
|
+
// dropped from the screen's corner.
|
|
67
|
+
this.x = 0;
|
|
68
|
+
this.y = 0;
|
|
69
|
+
this._screenOrigin = { x: 0, y: 0 };
|
|
70
|
+
// what the app's window map keys this window by, and the id the host
|
|
71
|
+
// names it by in a pane-event (`CocoaApp.attachPaneChannel`)
|
|
72
|
+
this._key = this.windowId;
|
|
55
73
|
app._registerWindow(this);
|
|
56
74
|
}
|
|
57
75
|
|
|
58
76
|
// --- the channel-facing half --------------------------------------------
|
|
59
77
|
|
|
60
|
-
/**
|
|
61
|
-
|
|
78
|
+
/**
|
|
79
|
+
* The host's layout answer: logical size plus the display scale, and
|
|
80
|
+
* where the pane is on the screen — `screen`, device pixels, absent from
|
|
81
|
+
* a host that does not say. A move alone is a `resize` that `moved`, the
|
|
82
|
+
* event a window's own move is, so what is anchored here follows it.
|
|
83
|
+
*/
|
|
84
|
+
setPaneSize(width, height, scale, screen) {
|
|
62
85
|
if (this.destroyed) return;
|
|
63
86
|
if (scale) this.scale = scale;
|
|
64
87
|
const w = Math.max(1, Math.round(width * this.scale));
|
|
65
88
|
const h = Math.max(1, Math.round(height * this.scale));
|
|
66
|
-
|
|
89
|
+
const moved =
|
|
90
|
+
screen != null && (screen.x !== this.x || screen.y !== this.y);
|
|
91
|
+
const resized = w !== this.width || h !== this.height;
|
|
92
|
+
if (!moved && !resized) return;
|
|
93
|
+
if (moved) {
|
|
94
|
+
this.x = screen.x;
|
|
95
|
+
this.y = screen.y;
|
|
96
|
+
this._screenOrigin = { x: screen.x, y: screen.y };
|
|
97
|
+
}
|
|
67
98
|
this.width = w;
|
|
68
99
|
this.height = h;
|
|
69
100
|
this.emit('resize', {
|
|
70
101
|
width: w,
|
|
71
102
|
height: h,
|
|
72
|
-
x:
|
|
73
|
-
y:
|
|
74
|
-
moved
|
|
75
|
-
resized
|
|
103
|
+
x: this.x,
|
|
104
|
+
y: this.y,
|
|
105
|
+
moved,
|
|
106
|
+
resized,
|
|
76
107
|
});
|
|
77
108
|
}
|
|
78
109
|
|
|
@@ -103,7 +134,13 @@ export class CocoaPaneWindow {
|
|
|
103
134
|
*/
|
|
104
135
|
setCursor(name) {
|
|
105
136
|
if (this.destroyed) return;
|
|
106
|
-
this.
|
|
137
|
+
this._post({ type: 'pane-cursor', cursor: name ?? null });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** A message about this window, to the host. The pane's own names none:
|
|
141
|
+
* the host knows it as the pane (`CocoaPaneSubwindow` names itself). */
|
|
142
|
+
_post(msg) {
|
|
143
|
+
this.app._paneSend?.(msg);
|
|
107
144
|
}
|
|
108
145
|
|
|
109
146
|
requestAnimationFrame(cb) {
|
|
@@ -264,7 +301,7 @@ export class CocoaPaneWindow {
|
|
|
264
301
|
const shown = this._ring[this._drawIndex];
|
|
265
302
|
this._native.surfaceUnlock(shown.handle);
|
|
266
303
|
this._flushDamage = null;
|
|
267
|
-
this.
|
|
304
|
+
this._post({
|
|
268
305
|
type: 'pane-present',
|
|
269
306
|
seq: ++this._seq,
|
|
270
307
|
id: shown.iosurfaceId,
|
|
@@ -304,3 +341,156 @@ export class CocoaPaneWindow {
|
|
|
304
341
|
this._releaseRing();
|
|
305
342
|
}
|
|
306
343
|
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Any other window a pane's tree makes: a `<popup>` — a menu, a dropdown's
|
|
347
|
+
* sheet, a tooltip, the edit menu — or a managed one, a dialog (#824).
|
|
348
|
+
*
|
|
349
|
+
* A pane process runs no AppKit (`CocoaApp.start`): no NSApplication, no
|
|
350
|
+
* event pump. An NSWindow made here exists and is never shown, so a
|
|
351
|
+
* `<Select>` in a pane took the press, opened, and showed nothing. This
|
|
352
|
+
* window paints into a shared ring exactly as the pane does, and the host
|
|
353
|
+
* makes the NSWindow and shows the ring in it (`CocoaPaneHost`,
|
|
354
|
+
* src/cocoa/panehost.js) — the split the pane already has: the pane draws,
|
|
355
|
+
* the host owns glass and input.
|
|
356
|
+
*
|
|
357
|
+
* Its place is the pane's to decide and the host's to take as given: `x`
|
|
358
|
+
* and `y` are the screen position the pane's own anchoring worked out,
|
|
359
|
+
* against the origin the host sends with every pane-rect. What goes out is
|
|
360
|
+
* the window's life as `pane-window` messages and its frames as
|
|
361
|
+
* `pane-present`s naming it; what comes back is the input on the host's
|
|
362
|
+
* window, as `pane-event`s naming it, and for a managed window the moves
|
|
363
|
+
* and the resizes the user made and the close button.
|
|
364
|
+
*/
|
|
365
|
+
export class CocoaPaneSubwindow extends CocoaPaneWindow {
|
|
366
|
+
constructor(app, attributes = {}) {
|
|
367
|
+
super(app, attributes);
|
|
368
|
+
this.attributes = attributes;
|
|
369
|
+
this._popup = attributes.overrideRedirect === true;
|
|
370
|
+
this.mapped = false;
|
|
371
|
+
// device pixels, as every window's attributes are — unlike the pane's
|
|
372
|
+
// own, whose size is the host's to say
|
|
373
|
+
const size = this.snapSize(attributes.width ?? 1, attributes.height ?? 1);
|
|
374
|
+
this.width = size.width;
|
|
375
|
+
this.height = size.height;
|
|
376
|
+
this.x = Math.round(attributes.x ?? 0);
|
|
377
|
+
this.y = Math.round(attributes.y ?? 0);
|
|
378
|
+
this._screenOrigin = { x: this.x, y: this.y };
|
|
379
|
+
this._send({
|
|
380
|
+
op: 'create',
|
|
381
|
+
x: this.x,
|
|
382
|
+
y: this.y,
|
|
383
|
+
width: this.width,
|
|
384
|
+
height: this.height,
|
|
385
|
+
popup: this._popup,
|
|
386
|
+
// an ARGB visual is a transparent window here, as in `CocoaWindow`
|
|
387
|
+
transparent:
|
|
388
|
+
attributes.visual !== undefined || Boolean(attributes.transparent),
|
|
389
|
+
grabKeyboard: attributes.grabKeyboard === true,
|
|
390
|
+
dragPreview: Boolean(attributes.dragPreview),
|
|
391
|
+
title: attributes.title ?? '',
|
|
392
|
+
decorations: attributes.decorations,
|
|
393
|
+
resizable: attributes.resizable,
|
|
394
|
+
sizeHints: attributes.sizeHints,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** Named, and held until the host is listening (`CocoaApp._postWindow`). */
|
|
399
|
+
_post(msg) {
|
|
400
|
+
this.app._postWindow({ ...msg, window: this.windowId });
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
_send(msg) {
|
|
404
|
+
if (this.destroyed) return;
|
|
405
|
+
this._post({ type: 'pane-window', ...msg });
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** `CocoaWindow`'s rule, so the size this window records is the size the
|
|
409
|
+
* host's NSWindow will take: whole points, rounded up. */
|
|
410
|
+
snapSize(width, height) {
|
|
411
|
+
const s = this.scale;
|
|
412
|
+
const up = (v) =>
|
|
413
|
+
Math.max(1, Math.ceil(Math.max(1, Math.round(v)) / s) * s);
|
|
414
|
+
return { width: up(width), height: up(height) };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
map() {
|
|
418
|
+
this.mapped = true;
|
|
419
|
+
this._send({ op: 'map' });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
unmap() {
|
|
423
|
+
this.mapped = false;
|
|
424
|
+
this._send({ op: 'unmap' });
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
move(x, y) {
|
|
428
|
+
this.x = Math.round(x);
|
|
429
|
+
this.y = Math.round(y);
|
|
430
|
+
this._screenOrigin = { x: this.x, y: this.y };
|
|
431
|
+
this._send({ op: 'move', x: this.x, y: this.y });
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
resize(width, height) {
|
|
435
|
+
const size = this.snapSize(width, height);
|
|
436
|
+
this.width = size.width;
|
|
437
|
+
this.height = size.height;
|
|
438
|
+
this._send({ op: 'resize', width: this.width, height: this.height });
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
setTitle(title) {
|
|
442
|
+
this._send({ op: 'title', title: String(title ?? '') });
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
setSizeHints(hints = {}) {
|
|
446
|
+
this._send({ op: 'size-hints', hints: { ...hints } });
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** The grab is the host's: a press anywhere in its windows comes here,
|
|
450
|
+
* outside this window, which is the dismissal (`CocoaApp._grabTarget`). */
|
|
451
|
+
grabPointer(options, cb) {
|
|
452
|
+
this._send({ op: 'grab' });
|
|
453
|
+
cb?.(null, 0);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
ungrabPointer() {
|
|
457
|
+
this._send({ op: 'ungrab' });
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Where the host's window went for a reason of its own — the user moved a
|
|
462
|
+
* dialog, or resized it — which a `resize` here says as a window's own
|
|
463
|
+
* move or resize does. A size comes only from the user (`resized`): the
|
|
464
|
+
* pane's own resizes come back as the host's echo, and one taken as news
|
|
465
|
+
* would read as the user taking an `'auto'` window over.
|
|
466
|
+
*/
|
|
467
|
+
_hostGeometry(ev) {
|
|
468
|
+
if (this.destroyed) return;
|
|
469
|
+
const moved = ev.x !== this.x || ev.y !== this.y;
|
|
470
|
+
const resized =
|
|
471
|
+
Boolean(ev.resized) &&
|
|
472
|
+
(ev.width !== this.width || ev.height !== this.height);
|
|
473
|
+
if (!moved && !resized) return;
|
|
474
|
+
this.x = ev.x;
|
|
475
|
+
this.y = ev.y;
|
|
476
|
+
this._screenOrigin = { x: ev.x, y: ev.y };
|
|
477
|
+
if (resized) {
|
|
478
|
+
this.width = ev.width;
|
|
479
|
+
this.height = ev.height;
|
|
480
|
+
}
|
|
481
|
+
this.emit('resize', {
|
|
482
|
+
width: this.width,
|
|
483
|
+
height: this.height,
|
|
484
|
+
x: this.x,
|
|
485
|
+
y: this.y,
|
|
486
|
+
moved,
|
|
487
|
+
resized,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
destroy() {
|
|
492
|
+
if (this.destroyed) return;
|
|
493
|
+
this._send({ op: 'destroy' });
|
|
494
|
+
super.destroy();
|
|
495
|
+
}
|
|
496
|
+
}
|
package/src/cocoa/promotion.js
CHANGED
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
propBoxProps,
|
|
46
46
|
stylePaintsPlain,
|
|
47
47
|
} from './presenter.js';
|
|
48
|
+
import { SpriteLayers } from './sprites.js';
|
|
48
49
|
|
|
49
50
|
const ORIGIN = Object.freeze({ x: 0, y: 0 });
|
|
50
51
|
|
|
@@ -61,6 +62,17 @@ const IDLE_GRACE_MS = 1000;
|
|
|
61
62
|
// the repaint they save (the layer presenter's rule, per raster).
|
|
62
63
|
const MAX_DIRTY_RECTS = 16;
|
|
63
64
|
|
|
65
|
+
/** Paint order, for entries carrying a `_paintKey`: a key the walk would
|
|
66
|
+
* not reach sorts last. */
|
|
67
|
+
function byPaintKey(a, b) {
|
|
68
|
+
if (!a.key || !b.key) return (a.key ? 1 : 0) - (b.key ? 1 : 0);
|
|
69
|
+
const n = Math.min(a.key.length, b.key.length);
|
|
70
|
+
for (let i = 0; i < n; i++) {
|
|
71
|
+
if (a.key[i] !== b.key[i]) return a.key[i] - b.key[i];
|
|
72
|
+
}
|
|
73
|
+
return a.key.length - b.key.length;
|
|
74
|
+
}
|
|
75
|
+
|
|
64
76
|
const rectsOverlap = (a, b) =>
|
|
65
77
|
a.x < b.x + b.width &&
|
|
66
78
|
b.x < a.x + a.width &&
|
|
@@ -243,6 +255,9 @@ export class CocoaPromotion {
|
|
|
243
255
|
layerOf: (node) => this.promoted.get(node)?.visual.layer ?? null,
|
|
244
256
|
onIdle: (node) => this._idle(node),
|
|
245
257
|
});
|
|
258
|
+
// the parts of elements' drawing they offer (`Node.sprites()`), each on
|
|
259
|
+
// a layer of its own under the same rules (src/cocoa/sprites.js)
|
|
260
|
+
this.sprites = new SpriteLayers(this);
|
|
246
261
|
this._claiming = false;
|
|
247
262
|
}
|
|
248
263
|
|
|
@@ -390,8 +405,19 @@ export class CocoaPromotion {
|
|
|
390
405
|
*/
|
|
391
406
|
frame(root, layoutRan) {
|
|
392
407
|
if (layoutRan) this.layoutGen++;
|
|
393
|
-
|
|
408
|
+
// what elements offer, asked before anything is decided: an answer is
|
|
409
|
+
// about the element's own drawing, not about the layers
|
|
410
|
+
const offers = this.sprites.ask(root);
|
|
411
|
+
if (
|
|
412
|
+
this.promoted.size === 0 &&
|
|
413
|
+
this.candidates.size === 0 &&
|
|
414
|
+
offers.size === 0 &&
|
|
415
|
+
this.sprites.hosts.size === 0
|
|
416
|
+
) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
394
419
|
const native = this.native;
|
|
420
|
+
let lifted = null;
|
|
395
421
|
native.txBegin({ disableActions: true });
|
|
396
422
|
try {
|
|
397
423
|
// Later-painted first: a node painted after this one that is coming
|
|
@@ -416,13 +442,26 @@ export class CocoaPromotion {
|
|
|
416
442
|
}
|
|
417
443
|
}
|
|
418
444
|
}
|
|
445
|
+
// Then the parts, against the layers this frame keeps: a node
|
|
446
|
+
// promoted over a part is a layer above it, not ink on it.
|
|
447
|
+
lifted = this.sprites.decide(root, offers);
|
|
448
|
+
// One order for every layer above the bitmap, nodes and parts alike.
|
|
419
449
|
const order = this._inPaintOrder([...this.promoted.keys()]);
|
|
450
|
+
for (const state of this.sprites.entries()) {
|
|
451
|
+
order.push({ state, key: state.order });
|
|
452
|
+
}
|
|
453
|
+
order.sort(byPaintKey);
|
|
420
454
|
for (let i = 0; i < order.length; i++) {
|
|
421
|
-
|
|
455
|
+
const { node, state } = order[i];
|
|
456
|
+
if (node) this._sync(node, i, root, layoutRan);
|
|
457
|
+
else this.sprites.sync(state, i);
|
|
422
458
|
}
|
|
423
459
|
} finally {
|
|
424
460
|
native.txCommit();
|
|
425
461
|
}
|
|
462
|
+
// An element told which of its parts are on layers now may claim,
|
|
463
|
+
// restyle or ask for frames; the frame has not taken its damage yet.
|
|
464
|
+
if (lifted?.size) this.sprites.notify(lifted);
|
|
426
465
|
}
|
|
427
466
|
|
|
428
467
|
_mayStay(node, key) {
|
|
@@ -528,17 +567,22 @@ export class CocoaPromotion {
|
|
|
528
567
|
|
|
529
568
|
_inPaintOrder(nodes) {
|
|
530
569
|
const keyed = nodes.map((node) => ({ node, key: this._paintKey(node) }));
|
|
531
|
-
keyed.sort(
|
|
532
|
-
if (!a.key || !b.key) return (a.key ? 1 : 0) - (b.key ? 1 : 0);
|
|
533
|
-
const n = Math.min(a.key.length, b.key.length);
|
|
534
|
-
for (let i = 0; i < n; i++) {
|
|
535
|
-
if (a.key[i] !== b.key[i]) return a.key[i] - b.key[i];
|
|
536
|
-
}
|
|
537
|
-
return a.key.length - b.key.length;
|
|
538
|
-
});
|
|
570
|
+
keyed.sort(byPaintKey);
|
|
539
571
|
return keyed;
|
|
540
572
|
}
|
|
541
573
|
|
|
574
|
+
/**
|
|
575
|
+
* Where an element offering parts stands in the paint order, or null when
|
|
576
|
+
* none of them may go on a layer: hidden, inside a `<glarea>`, or itself
|
|
577
|
+
* faded — its group on the bitmap would leave a part's layer out of it. A
|
|
578
|
+
* faded ancestor is `_clear`'s to find, as it is for a node.
|
|
579
|
+
*/
|
|
580
|
+
_spritePlace(host) {
|
|
581
|
+
if (host.destroyed || host.hidden) return null;
|
|
582
|
+
if (insideGlArea(host) || fadesAsGroup(host)) return null;
|
|
583
|
+
return this._paintKey(host);
|
|
584
|
+
}
|
|
585
|
+
|
|
542
586
|
/**
|
|
543
587
|
* Is nothing painted over this node, is all of it visible, and is it in
|
|
544
588
|
* no faded group? Walked up the chain: at every level, what the parent
|
|
@@ -547,15 +591,34 @@ export class CocoaPromotion {
|
|
|
547
591
|
* node's reach, the parent's clip, if it has one, must hold the whole of
|
|
548
592
|
* it, and the parent must not fade: its group is drawn on the bitmap or on
|
|
549
593
|
* its own layer, and either way a layer of the node's would be outside it.
|
|
594
|
+
*
|
|
595
|
+
* With `cut`, the reach is an element's part (src/cocoa/sprites.js),
|
|
596
|
+
* whose layer can be cut to a rectangle: a clipping ancestor with square
|
|
597
|
+
* corners narrows `cut.clip` to its padding box — its border is drawn
|
|
598
|
+
* over what it holds anyway — instead of having to hold the part whole,
|
|
599
|
+
* and every test from there up is asked of what shows through the clip.
|
|
600
|
+
* `cut.bounds` comes back as that. A clip that leaves nothing showing is
|
|
601
|
+
* a refusal.
|
|
550
602
|
*/
|
|
551
|
-
_clear(node) {
|
|
603
|
+
_clear(node, bounds = node._subtreeBounds(), cut = null) {
|
|
552
604
|
// the exact reach, not `paintBounds()`: that one carries the damage
|
|
553
605
|
// model's pixel of slop, and a section laid out flush under a card
|
|
554
|
-
// would read as reaching into it
|
|
555
|
-
|
|
606
|
+
// would read as reaching into it. An element's part is asked about at
|
|
607
|
+
// everywhere it can draw (src/cocoa/sprites.js).
|
|
556
608
|
for (let n = node; !n.isWindow; n = n.parent) {
|
|
557
609
|
const parent = n.parent;
|
|
558
610
|
if (!parent) return false;
|
|
611
|
+
if (
|
|
612
|
+
cut &&
|
|
613
|
+
!parent.isWindow &&
|
|
614
|
+
parent.clipsChildren?.() &&
|
|
615
|
+
!(parent.style?.borderRadius > 0)
|
|
616
|
+
) {
|
|
617
|
+
const inner = insetRect(parent.abs, borderReach(parent));
|
|
618
|
+
cut.clip = cut.clip ? intersectRects(cut.clip, inner) : inner;
|
|
619
|
+
bounds = cut.clip && intersectRects(bounds, cut.clip);
|
|
620
|
+
if (!bounds) return false;
|
|
621
|
+
}
|
|
559
622
|
const order = parent.paintOrder();
|
|
560
623
|
for (let j = order.indexOf(n) + 1; j < order.length; j++) {
|
|
561
624
|
if (this._reaches(order[j], bounds)) return false;
|
|
@@ -592,6 +655,7 @@ export class CocoaPromotion {
|
|
|
592
655
|
}
|
|
593
656
|
}
|
|
594
657
|
}
|
|
658
|
+
if (cut) cut.bounds = bounds;
|
|
595
659
|
return true;
|
|
596
660
|
}
|
|
597
661
|
|
|
@@ -733,6 +797,7 @@ export class CocoaPromotion {
|
|
|
733
797
|
|
|
734
798
|
/** Everything off the root layer and freed: the window is going. */
|
|
735
799
|
destroy() {
|
|
800
|
+
this.sprites.destroy();
|
|
736
801
|
for (const timer of this.idle.values()) clearTimeout(timer);
|
|
737
802
|
this.idle.clear();
|
|
738
803
|
this.releasing.clear();
|