react-x11 2.29.0 → 2.31.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/README.md +1 -1
- package/package.json +2 -1
- 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 +21 -1
- package/src/cocoa/sprites.js +54 -11
- package/src/components/Select.js +98 -7
- package/src/frame/index.js +60 -7
- package/src/imagedecode.js +255 -0
- package/src/imagesource.js +20 -15
- package/src/node.d.ts +5 -0
- package/src/nodes/image.js +44 -45
- package/src/nodes/window/floors.js +76 -15
- package/src/nodes/window/window.js +11 -1
- package/src/types/components.d.ts +10 -0
- package/src/types/elements.d.ts +10 -6
package/README.md
CHANGED
|
@@ -256,7 +256,7 @@ drawn tree. See [docs/elements.md](docs/elements.md) for the full reference.
|
|
|
256
256
|
| `<text>` | shaped, wrapped text (bidi, ligatures, font fallback); nested `<text>` elements are style spans |
|
|
257
257
|
| `<textinput>` | single-line editor: caret/selection, clipboard, word select, undo/redo, right-click menu |
|
|
258
258
|
| `<textarea>` | the same editor, wrapped and multi-line, scrolling with its content |
|
|
259
|
-
| `<image>` | PNG/JPEG from `src
|
|
259
|
+
| `<image>` | PNG/JPEG/WebP from `src` (more formats under Bun), natural-size aware; also pixels the server already has |
|
|
260
260
|
| `<canvas>` | escape hatch: `onDraw={(ctx, {width, height}) => …}` with a canvas-like 2d context (XRender-backed on X11, CoreGraphics on Cocoa) |
|
|
261
261
|
| `<svg>` | static SVG (paths, shapes, gradients, transforms), sized like `<image>` |
|
|
262
262
|
| `<glarea>` | an OpenGL surface in the layout — see [3D](#3d) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-x11",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.31.0",
|
|
4
4
|
"description": "react renderer with X11 as a target",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -102,6 +102,7 @@
|
|
|
102
102
|
},
|
|
103
103
|
"dependencies": {
|
|
104
104
|
"ical.js": "^2.2.1",
|
|
105
|
+
"image-in-browser": "^3.6.0",
|
|
105
106
|
"linebreak": "^1.1.0",
|
|
106
107
|
"ntk": "^8.17.8",
|
|
107
108
|
"react-reconciler": "^0.34.0",
|
package/src/cocoa/app.js
CHANGED
|
@@ -34,7 +34,7 @@ import { CocoaCalendars } from './calendar.js';
|
|
|
34
34
|
import { CocoaNotifications } from './notifications.js';
|
|
35
35
|
import { CocoaPaneHost } from './panehost.js';
|
|
36
36
|
import { CocoaPermissions } from './permissions.js';
|
|
37
|
-
import { CocoaPaneWindow } from './panewindow.js';
|
|
37
|
+
import { CocoaPaneSubwindow, CocoaPaneWindow } from './panewindow.js';
|
|
38
38
|
import { CocoaColorSampler } from './screencolor.js';
|
|
39
39
|
import { CocoaFilePanels } from './filepanels.js';
|
|
40
40
|
import { CocoaFontManager } from './fonts.js';
|
|
@@ -331,6 +331,11 @@ export class CocoaApp {
|
|
|
331
331
|
// composites (REACT_X11_FRAME is what the Frame host sets on the fork).
|
|
332
332
|
this._paneMode = options.pane ?? process.env.REACT_X11_FRAME === '1';
|
|
333
333
|
this._paneSend = null;
|
|
334
|
+
// the pane's own window, the one the host lays out and forwards to
|
|
335
|
+
this._paneWindow = null;
|
|
336
|
+
// What the pane's other windows said before the host was listening
|
|
337
|
+
// (`_postWindow`), until its first pane-rect.
|
|
338
|
+
this._paneOutbox = [];
|
|
334
339
|
|
|
335
340
|
// The X stub: just enough for the modules that carry an X escape hatch
|
|
336
341
|
// to no-op the way they do against the headless mock.
|
|
@@ -447,7 +452,18 @@ export class CocoaApp {
|
|
|
447
452
|
// shared IOSurfaces and the HOST composites them (src/cocoa/
|
|
448
453
|
// panewindow.js). Chosen by the same prop the X11 pane uses.
|
|
449
454
|
if (this._paneMode && attributes.embeddable) {
|
|
450
|
-
|
|
455
|
+
const wnd = new CocoaPaneWindow(this, attributes);
|
|
456
|
+
if (!this._paneWindow || this._paneWindow.destroyed) {
|
|
457
|
+
this._paneWindow = wnd;
|
|
458
|
+
}
|
|
459
|
+
return wnd;
|
|
460
|
+
}
|
|
461
|
+
// Any other window a pane makes — a menu, a tooltip, a dialog — is
|
|
462
|
+
// drawn here and shown by the host, which has the NSApplication this
|
|
463
|
+
// process does not (#824). An NSWindow made here exists and is never on
|
|
464
|
+
// glass.
|
|
465
|
+
if (this._paneMode && !attributes.parent) {
|
|
466
|
+
return new CocoaPaneSubwindow(this, attributes);
|
|
451
467
|
}
|
|
452
468
|
if (attributes.parent) {
|
|
453
469
|
// A parented "window" here is a GL child surface — GlAreaNode's
|
|
@@ -487,10 +503,88 @@ export class CocoaApp {
|
|
|
487
503
|
/**
|
|
488
504
|
* The Frame host seam (src/frame/index.js): a pane's composited region
|
|
489
505
|
* in this window. Its presence is what routes <Frame> to the shared-
|
|
490
|
-
* memory pane path instead of the X foreign-window embed.
|
|
506
|
+
* memory pane path instead of the X foreign-window embed. `send` is the
|
|
507
|
+
* pane's channel, for the windows the pane makes besides its own: a pane
|
|
508
|
+
* process runs no AppKit, so they are made here (src/cocoa/panehost.js).
|
|
491
509
|
*/
|
|
492
|
-
createPaneHost(wnd) {
|
|
493
|
-
return new CocoaPaneHost(this, wnd);
|
|
510
|
+
createPaneHost(wnd, { send } = {}) {
|
|
511
|
+
return new CocoaPaneHost(this, wnd, { send });
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Whether this backend drops a control's menu as the platform's own — a
|
|
516
|
+
* bridge with `popUpMenu` (@windowkit/appkit 0.20) — rather than a drawn
|
|
517
|
+
* `<popup>` sized as one. What `<Select>` asks before it opens.
|
|
518
|
+
*/
|
|
519
|
+
get nativePopUpMenus() {
|
|
520
|
+
return typeof this._native?.popUpMenu === 'function';
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* The platform's own menu, dropped from a control in one of this app's
|
|
525
|
+
* windows as an NSPopUpButton's drops: the item `spec.selected` placed
|
|
526
|
+
* over `spec.frame` and checked, the menu at least the frame's width.
|
|
527
|
+
* `spec` is the bridge's (`items`, `selected`, `fontSize`, `fontFamily`,
|
|
528
|
+
* `appearance`, `rtl`) with `frame` in the window's device pixels, which
|
|
529
|
+
* every rect on this side is. `onAnswer(id | null)` hears the item chosen,
|
|
530
|
+
* or null for a menu dismissed, once; the frame that answer causes goes
|
|
531
|
+
* out with it. Returns a function that cancels the menu.
|
|
532
|
+
*
|
|
533
|
+
* Opened on a microtask, after the input that asked for it has been
|
|
534
|
+
* answered and painted: in pump mode the menu's tracking holds this thread
|
|
535
|
+
* from the call to the choice, and the press that opened it should be on
|
|
536
|
+
* glass before it does. From a worker the call returns at once.
|
|
537
|
+
*
|
|
538
|
+
* A pane has no NSWindow to drop it from, so in a pane the host does,
|
|
539
|
+
* over the pane's place in its window (`CocoaPaneHost.popUpMenu`).
|
|
540
|
+
*/
|
|
541
|
+
popUpMenu(wnd, spec, onAnswer) {
|
|
542
|
+
let answered = false;
|
|
543
|
+
const answer = (id) => {
|
|
544
|
+
if (answered) return;
|
|
545
|
+
answered = true;
|
|
546
|
+
onAnswer(id ?? null);
|
|
547
|
+
this._afterInput();
|
|
548
|
+
};
|
|
549
|
+
if (this._paneMode) return this._paneMenu(wnd, spec, answer);
|
|
550
|
+
let cancelled = false;
|
|
551
|
+
let handle = null;
|
|
552
|
+
queueMicrotask(() => {
|
|
553
|
+
if (cancelled || this._closed || !wnd || wnd.destroyed) {
|
|
554
|
+
answer(null);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
const s = wnd.scale ?? this.scale;
|
|
558
|
+
handle = this._native.popUpMenu(
|
|
559
|
+
wnd._h,
|
|
560
|
+
{ ...spec, frame: spec.frame.map((v) => v / s) },
|
|
561
|
+
(id) => answer(id),
|
|
562
|
+
);
|
|
563
|
+
});
|
|
564
|
+
return () => {
|
|
565
|
+
if (answered) return;
|
|
566
|
+
cancelled = true;
|
|
567
|
+
if (handle) this._native.cancelPopUpMenu?.(handle);
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/** A pane's menu, asked of the host: `pane-menu` with the frame in the
|
|
572
|
+
* window it is in, `pane-menu-answer` back. */
|
|
573
|
+
_paneMenu(wnd, spec, answer) {
|
|
574
|
+
const menu = (this._paneMenuSeq = (this._paneMenuSeq ?? 0) + 1);
|
|
575
|
+
(this._paneMenus ??= new Map()).set(menu, answer);
|
|
576
|
+
this._paneSend?.({
|
|
577
|
+
type: 'pane-menu',
|
|
578
|
+
menu,
|
|
579
|
+
// the pane's own window is the one the host knows as the pane
|
|
580
|
+
...(wnd && wnd !== this._paneWindow && { window: wnd.windowId }),
|
|
581
|
+
spec,
|
|
582
|
+
});
|
|
583
|
+
return () => {
|
|
584
|
+
if (this._paneMenus?.has(menu)) {
|
|
585
|
+
this._paneSend?.({ type: 'pane-menu-cancel', menu });
|
|
586
|
+
}
|
|
587
|
+
};
|
|
494
588
|
}
|
|
495
589
|
|
|
496
590
|
/**
|
|
@@ -780,18 +874,72 @@ export class CocoaApp {
|
|
|
780
874
|
}
|
|
781
875
|
};
|
|
782
876
|
channel.onMessage((msg) => {
|
|
783
|
-
const
|
|
784
|
-
if (!
|
|
877
|
+
const pane = this._paneWindow ?? [...this._windows.values()][0];
|
|
878
|
+
if (!pane) return;
|
|
785
879
|
if (msg?.type === 'pane-rect') {
|
|
786
|
-
|
|
880
|
+
pane.setPaneSize(msg.width, msg.height, msg.scale, msg.screen);
|
|
881
|
+
this._flushPaneOutbox();
|
|
787
882
|
this._afterInput();
|
|
883
|
+
} else if (msg?.type === 'pane-menu-answer') {
|
|
884
|
+
const answer = this._paneMenus?.get(msg.menu);
|
|
885
|
+
this._paneMenus?.delete(msg.menu);
|
|
886
|
+
answer?.(msg.id ?? null);
|
|
788
887
|
} else if (msg?.type === 'pane-event') {
|
|
789
|
-
|
|
888
|
+
// input on one of the windows the host shows for this pane names
|
|
889
|
+
// it; the pane's own is the one that names none
|
|
890
|
+
const wnd = msg.window != null ? this._windows.get(msg.window) : pane;
|
|
891
|
+
if (!wnd || wnd.destroyed) return;
|
|
892
|
+
if (msg.name === 'resize') wnd._hostGeometry?.(msg.ev);
|
|
893
|
+
// a close request answers with `preventDefault`, which did not
|
|
894
|
+
// survive the channel
|
|
895
|
+
else if (msg.name === 'close')
|
|
896
|
+
wnd.emit('close', { ...msg.ev, preventDefault() {} });
|
|
897
|
+
else wnd.emit(msg.name, msg.ev);
|
|
790
898
|
this._afterInput();
|
|
791
899
|
}
|
|
792
900
|
});
|
|
793
901
|
}
|
|
794
902
|
|
|
903
|
+
/**
|
|
904
|
+
* A message about one of the pane's other windows (`CocoaPaneSubwindow`),
|
|
905
|
+
* held until the host's first pane-rect. The host hears a pane's messages
|
|
906
|
+
* from the moment it has a `<Frame>`, and knows what to do with these
|
|
907
|
+
* only once the pane is laid out in it — a menu or a dialog open in the
|
|
908
|
+
* pane's first commit was made, and announced to nobody. That pane-rect
|
|
909
|
+
* is also where the pane learns where it is on the screen, so a popup
|
|
910
|
+
* anchored before it is placed again before anyone shows it.
|
|
911
|
+
*/
|
|
912
|
+
_postWindow(msg) {
|
|
913
|
+
if (this._paneOutbox) this._paneOutbox.push(msg);
|
|
914
|
+
else this._paneSend?.(msg);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* The host is listening, and the pane knows where it is — `setPaneSize`
|
|
919
|
+
* has just moved what is anchored to it. Everything held goes out in
|
|
920
|
+
* order, but a window is announced where it is now: a held move or resize
|
|
921
|
+
* is where it was before, and sent in order it came after a present the
|
|
922
|
+
* host showed at the old place.
|
|
923
|
+
*/
|
|
924
|
+
_flushPaneOutbox() {
|
|
925
|
+
const held = this._paneOutbox;
|
|
926
|
+
if (!held) return;
|
|
927
|
+
this._paneOutbox = null;
|
|
928
|
+
for (const msg of held) {
|
|
929
|
+
if (msg.type === 'pane-window') {
|
|
930
|
+
if (msg.op === 'move' || msg.op === 'resize') continue;
|
|
931
|
+
const wnd = msg.op === 'create' && this._windows.get(msg.window);
|
|
932
|
+
if (wnd) {
|
|
933
|
+
msg.x = wnd.x;
|
|
934
|
+
msg.y = wnd.y;
|
|
935
|
+
msg.width = wnd.width;
|
|
936
|
+
msg.height = wnd.height;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
this._paneSend(msg);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
|
|
795
943
|
_unregisterWindow(wnd) {
|
|
796
944
|
this._windows.delete(wnd._key);
|
|
797
945
|
if (this._grabWindow === wnd) this._grabWindow = null;
|
package/src/cocoa/panehost.js
CHANGED
|
@@ -3,9 +3,16 @@
|
|
|
3
3
|
// contents are whatever IOSurface the pane process last presented. The pane
|
|
4
4
|
// owns its buffers and its drawing; this side owns the layer, the layout
|
|
5
5
|
// and the input — CPU offloading, not isolation (docs/frame.md).
|
|
6
|
+
//
|
|
7
|
+
// And the windows the pane makes besides its own — a menu, a dropdown's
|
|
8
|
+
// sheet, a tooltip, a dialog. A pane process runs no AppKit, so each is an
|
|
9
|
+
// NSWindow made here, showing the ring the pane draws it into the same way
|
|
10
|
+
// (`CocoaPaneSubwindow`, src/cocoa/panewindow.js), with its input sent back.
|
|
6
11
|
import { withoutActions } from './quiet.js';
|
|
7
12
|
|
|
8
|
-
|
|
13
|
+
/** One shared surface on glass: a sublayer of a window's root layer, its
|
|
14
|
+
* contents the IOSurface the pane last presented into it. */
|
|
15
|
+
class PaneLayer {
|
|
9
16
|
constructor(app, wnd) {
|
|
10
17
|
this.app = app;
|
|
11
18
|
this.wnd = wnd;
|
|
@@ -67,12 +74,14 @@ export class CocoaPaneHost {
|
|
|
67
74
|
* error is the bug it says it is.
|
|
68
75
|
*/
|
|
69
76
|
present(iosurfaceId) {
|
|
70
|
-
if (this.destroyed) return;
|
|
77
|
+
if (this.destroyed) return false;
|
|
71
78
|
try {
|
|
72
79
|
this._native.setLayerContentsIOSurface(this.layer, iosurfaceId);
|
|
73
80
|
} catch (err) {
|
|
74
81
|
if (!/IOSurfaceLookup/.test(err?.message ?? '')) throw err;
|
|
82
|
+
return false;
|
|
75
83
|
}
|
|
84
|
+
return true;
|
|
76
85
|
}
|
|
77
86
|
|
|
78
87
|
destroy() {
|
|
@@ -83,3 +92,218 @@ export class CocoaPaneHost {
|
|
|
83
92
|
);
|
|
84
93
|
}
|
|
85
94
|
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The input a window shows for the pane hands back, by the names its
|
|
98
|
+
* events go by (`CocoaApp._route`). A managed window's moves, resizes and
|
|
99
|
+
* close button go back as well (`_hostGeometry`).
|
|
100
|
+
*/
|
|
101
|
+
const FORWARDED = [
|
|
102
|
+
'mousedown',
|
|
103
|
+
'mouseup',
|
|
104
|
+
'mousemove',
|
|
105
|
+
'mouseout',
|
|
106
|
+
'wheel',
|
|
107
|
+
'keydown',
|
|
108
|
+
'keyup',
|
|
109
|
+
'focus',
|
|
110
|
+
'blur',
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
/** An event as data: the channel carries structured clones, and a
|
|
114
|
+
* `preventDefault` does not clone. */
|
|
115
|
+
function plain(ev) {
|
|
116
|
+
const out = {};
|
|
117
|
+
for (const key in ev) {
|
|
118
|
+
const value = ev[key];
|
|
119
|
+
if (value === null || typeof value !== 'object') {
|
|
120
|
+
if (typeof value !== 'function') out[key] = value;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export class CocoaPaneHost extends PaneLayer {
|
|
127
|
+
/**
|
|
128
|
+
* `send` is the pane's channel (`<Frame>`'s session): what the windows
|
|
129
|
+
* shown here for the pane hear goes back down it.
|
|
130
|
+
*/
|
|
131
|
+
constructor(app, wnd, { send } = {}) {
|
|
132
|
+
super(app, wnd);
|
|
133
|
+
this._send = send ?? null;
|
|
134
|
+
/** @type {Map<number, object>} the pane's windows, by its id for them */
|
|
135
|
+
this._windows = new Map();
|
|
136
|
+
/** @type {Map<number, () => void>} the pane's open menus, by its id */
|
|
137
|
+
this._menus = new Map();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* A `pane-menu` message: the pane's `<Select>` asked for the platform's
|
|
142
|
+
* menu, which a process with no AppKit cannot drop (`CocoaApp.popUpMenu`).
|
|
143
|
+
* Its frame is in the window it is in — the pane's own, laid out at this
|
|
144
|
+
* layer's rect in the host's window, or one the pane made, shown here —
|
|
145
|
+
* and the answer goes back as `pane-menu-answer`.
|
|
146
|
+
*/
|
|
147
|
+
popUpMenu(msg) {
|
|
148
|
+
if (this.destroyed || !msg.spec) return;
|
|
149
|
+
const entry = msg.window != null ? this._windows.get(msg.window) : null;
|
|
150
|
+
const [x, y, width, height] = msg.spec.frame;
|
|
151
|
+
const at = entry ? { x: 0, y: 0 } : (this._rect ?? { x: 0, y: 0 });
|
|
152
|
+
const cancel = this.app.popUpMenu(
|
|
153
|
+
entry ? entry.wnd : this.wnd,
|
|
154
|
+
{ ...msg.spec, frame: [x + at.x, y + at.y, width, height] },
|
|
155
|
+
(id) => {
|
|
156
|
+
this._menus.delete(msg.menu);
|
|
157
|
+
this._send?.({ type: 'pane-menu-answer', menu: msg.menu, id });
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
this._menus.set(msg.menu, cancel);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** `pane-menu-cancel`: the pane's `<Select>` went, or closed it. */
|
|
164
|
+
cancelMenu(menu) {
|
|
165
|
+
this._menus.get(menu)?.();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* A `pane-window` message: one of the pane's other windows was made,
|
|
170
|
+
* mapped, moved, grabbed with or destroyed. `x`/`y` are the screen
|
|
171
|
+
* position the pane worked out — its anchoring ran against the origin
|
|
172
|
+
* this side sent it — so the window goes where it says.
|
|
173
|
+
*/
|
|
174
|
+
paneWindow(msg) {
|
|
175
|
+
if (this.destroyed) return;
|
|
176
|
+
if (msg.op === 'create') {
|
|
177
|
+
this._create(msg);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const entry = this._windows.get(msg.window);
|
|
181
|
+
if (!entry) return;
|
|
182
|
+
const { wnd } = entry;
|
|
183
|
+
switch (msg.op) {
|
|
184
|
+
case 'map':
|
|
185
|
+
// on the first frame rather than now: a window mapped before the
|
|
186
|
+
// pane has drawn it is a window of nothing, for a frame or two
|
|
187
|
+
if (entry.presented) wnd.map();
|
|
188
|
+
else entry.mapOnPresent = true;
|
|
189
|
+
break;
|
|
190
|
+
case 'unmap':
|
|
191
|
+
entry.mapOnPresent = false;
|
|
192
|
+
wnd.unmap();
|
|
193
|
+
break;
|
|
194
|
+
case 'move':
|
|
195
|
+
wnd.move(msg.x, msg.y);
|
|
196
|
+
break;
|
|
197
|
+
case 'resize':
|
|
198
|
+
// the layer follows on the present drawn at the new size
|
|
199
|
+
wnd.resize(msg.width, msg.height);
|
|
200
|
+
break;
|
|
201
|
+
case 'grab':
|
|
202
|
+
wnd.grabPointer();
|
|
203
|
+
break;
|
|
204
|
+
case 'ungrab':
|
|
205
|
+
wnd.ungrabPointer();
|
|
206
|
+
break;
|
|
207
|
+
case 'title':
|
|
208
|
+
wnd.setTitle(msg.title);
|
|
209
|
+
break;
|
|
210
|
+
case 'size-hints':
|
|
211
|
+
wnd.setSizeHints(msg.hints);
|
|
212
|
+
break;
|
|
213
|
+
case 'destroy':
|
|
214
|
+
this._drop(entry);
|
|
215
|
+
break;
|
|
216
|
+
default:
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** A pane-present naming one of the pane's other windows. */
|
|
222
|
+
presentWindow(msg) {
|
|
223
|
+
const entry = this._windows.get(msg.window);
|
|
224
|
+
if (!entry || this.destroyed) return;
|
|
225
|
+
entry.layer.setRect({ x: 0, y: 0, width: msg.width, height: msg.height });
|
|
226
|
+
if (!entry.layer.present(msg.id)) return;
|
|
227
|
+
entry.presented = true;
|
|
228
|
+
// a transparent window's shadow is the shape of what it shows
|
|
229
|
+
entry.wnd._shadowAfterFlip?.();
|
|
230
|
+
if (entry.mapOnPresent) {
|
|
231
|
+
entry.mapOnPresent = false;
|
|
232
|
+
entry.wnd.map();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** The cursor a tree names for one of the pane's other windows: the
|
|
237
|
+
* pointer is over the window shown here, so this side sets it. */
|
|
238
|
+
windowCursor(msg) {
|
|
239
|
+
this._windows.get(msg.window)?.wnd.setCursor(msg.cursor ?? 'default');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
_create(msg) {
|
|
243
|
+
if (this._windows.has(msg.window)) return;
|
|
244
|
+
const wnd = this.app.createWindow({
|
|
245
|
+
overrideRedirect: msg.popup,
|
|
246
|
+
x: msg.x,
|
|
247
|
+
y: msg.y,
|
|
248
|
+
width: msg.width,
|
|
249
|
+
height: msg.height,
|
|
250
|
+
transparent: msg.transparent,
|
|
251
|
+
grabKeyboard: msg.grabKeyboard,
|
|
252
|
+
dragPreview: msg.dragPreview,
|
|
253
|
+
title: msg.title,
|
|
254
|
+
decorations: msg.decorations,
|
|
255
|
+
resizable: msg.resizable,
|
|
256
|
+
sizeHints: msg.sizeHints,
|
|
257
|
+
});
|
|
258
|
+
const entry = {
|
|
259
|
+
id: msg.window,
|
|
260
|
+
wnd,
|
|
261
|
+
layer: new PaneLayer(this.app, wnd),
|
|
262
|
+
presented: false,
|
|
263
|
+
mapOnPresent: false,
|
|
264
|
+
};
|
|
265
|
+
this._windows.set(msg.window, entry);
|
|
266
|
+
const forward = (name, ev) => {
|
|
267
|
+
if (this.destroyed || entry.dropped) return;
|
|
268
|
+
this._send?.({ type: 'pane-event', window: entry.id, name, ev });
|
|
269
|
+
};
|
|
270
|
+
for (const name of FORWARDED) {
|
|
271
|
+
wnd.on(name, (ev) => forward(name, plain(ev)));
|
|
272
|
+
}
|
|
273
|
+
// A popup goes where the pane puts it and no one else moves it. A
|
|
274
|
+
// managed window is the user's to move, resize and close as well, and
|
|
275
|
+
// the pane hears of each — of a resize only the user's, since the
|
|
276
|
+
// pane's own come back as AppKit's echo of the frame it was asked for.
|
|
277
|
+
if (!msg.popup) {
|
|
278
|
+
wnd.on('resize', (ev) =>
|
|
279
|
+
forward('resize', {
|
|
280
|
+
x: ev.x,
|
|
281
|
+
y: ev.y,
|
|
282
|
+
width: ev.width,
|
|
283
|
+
height: ev.height,
|
|
284
|
+
resized: Boolean(ev.resized && wnd.liveResizing),
|
|
285
|
+
}),
|
|
286
|
+
);
|
|
287
|
+
wnd.on('close', () => forward('close', {}));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
_drop(entry) {
|
|
292
|
+
if (entry.dropped) return;
|
|
293
|
+
entry.dropped = true;
|
|
294
|
+
this._windows.delete(entry.id);
|
|
295
|
+
entry.wnd.ungrabPointer();
|
|
296
|
+
entry.layer.destroy();
|
|
297
|
+
entry.wnd.destroy();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** The pane is going, and every window it had here goes with it — a pane
|
|
301
|
+
* that crashed with a menu open leaves no menu behind. */
|
|
302
|
+
destroy() {
|
|
303
|
+
if (this.destroyed) return;
|
|
304
|
+
for (const cancel of [...this._menus.values()]) cancel();
|
|
305
|
+
this._menus.clear();
|
|
306
|
+
for (const entry of [...this._windows.values()]) this._drop(entry);
|
|
307
|
+
super.destroy();
|
|
308
|
+
}
|
|
309
|
+
}
|