react-x11 2.2.1 → 2.3.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 +5 -3
- package/package.json +12 -5
- package/src/Reconciler.js +76 -11
- package/src/anchor.js +15 -5
- package/src/appcontext.js +27 -7
- package/src/cocoa/app.js +557 -0
- package/src/cocoa/bezels.js +161 -0
- package/src/cocoa/context2d.js +679 -0
- package/src/cocoa/fonts.js +541 -0
- package/src/cocoa/glarea.js +321 -0
- package/src/cocoa/globalmenu.js +149 -0
- package/src/cocoa/keymap.js +86 -0
- package/src/cocoa/native.js +51 -0
- package/src/cocoa/panehost.js +50 -0
- package/src/cocoa/panewindow.js +236 -0
- package/src/cocoa/presenter.js +837 -0
- package/src/cocoa/window.js +383 -0
- package/src/components/Button.js +68 -0
- package/src/components/Checkbox.js +43 -0
- package/src/components/Radio.js +37 -1
- package/src/components/Select.js +73 -28
- package/src/components/Slider.js +59 -0
- package/src/components/Switch.js +52 -0
- package/src/components/native.js +142 -0
- package/src/decorations.js +21 -24
- package/src/editmenu.js +23 -14
- package/src/events.js +11 -2
- package/src/frame/childmain.js +9 -0
- package/src/frame/index.js +162 -2
- package/src/glnodes.js +43 -0
- package/src/globalmenu.js +11 -3
- package/src/index.d.ts +10 -0
- package/src/nodes.js +107 -33
- package/src/paintcache.js +15 -4
- package/src/palette.js +14 -0
- package/src/styles.js +5 -0
- package/src/types/elements.d.ts +10 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// The pane process's "window" on the Cocoa backend — docs/frame.md's child
|
|
2
|
+
// half, with the X server replaced by shared memory.
|
|
3
|
+
//
|
|
4
|
+
// On X11 a pane renders into a real window of its own and the host embeds
|
|
5
|
+
// it; here there is no server to share, so the pane paints into IOSurfaces
|
|
6
|
+
// created shared (kIOSurfaceIsGlobal) and presents by message: the host
|
|
7
|
+
// looks the id up and points the pane's sublayer at it. Same swapchain
|
|
8
|
+
// discipline as CocoaWindow — draw into back, flip, catch the new back up
|
|
9
|
+
// by the damage — because the host's WindowServer reads the shown buffer
|
|
10
|
+
// asynchronously either way.
|
|
11
|
+
//
|
|
12
|
+
// What the node tree sees is the ordinary window contract: getContext,
|
|
13
|
+
// present, scrollRegion, noteFrameDamage, requestAnimationFrame, events
|
|
14
|
+
// via emit. Geometry and input arrive as channel messages (the host owns
|
|
15
|
+
// layout and hit-testing — this is CPU offloading, not isolation), and the
|
|
16
|
+
// only outbound traffic is pane-present.
|
|
17
|
+
import { CocoaContext2D } from './context2d.js';
|
|
18
|
+
|
|
19
|
+
let nextPaneId = 1;
|
|
20
|
+
|
|
21
|
+
export class CocoaPaneWindow {
|
|
22
|
+
constructor(app, attributes = {}) {
|
|
23
|
+
this.app = app;
|
|
24
|
+
this._native = app._native;
|
|
25
|
+
this.destroyed = false;
|
|
26
|
+
// a real-looking id so the shared ready/handshake path (which polls
|
|
27
|
+
// windowIdOf) fires; the host on this backend never dereferences it
|
|
28
|
+
this.windowId = 0xc0c0a000 + nextPaneId++;
|
|
29
|
+
// windowIdOf reads `.id` (the ntk Window field) — the ready handshake
|
|
30
|
+
// polls it through the pane's ref, so both spellings answer
|
|
31
|
+
this.id = this.windowId;
|
|
32
|
+
this.windowNumber = this.windowId;
|
|
33
|
+
this.scale = app.scale ?? 2;
|
|
34
|
+
this.width = Math.max(
|
|
35
|
+
1,
|
|
36
|
+
Math.round((attributes.width ?? 400) * this.scale),
|
|
37
|
+
);
|
|
38
|
+
this.height = Math.max(
|
|
39
|
+
1,
|
|
40
|
+
Math.round((attributes.height ?? 300) * this.scale),
|
|
41
|
+
);
|
|
42
|
+
this._listeners = new Map();
|
|
43
|
+
this._surface = null;
|
|
44
|
+
this._surfaceGen = 0;
|
|
45
|
+
this._surfaceSize = null;
|
|
46
|
+
this._ring = null;
|
|
47
|
+
this._drawIndex = 0;
|
|
48
|
+
this._shownIndex = -1;
|
|
49
|
+
this._ctx = null;
|
|
50
|
+
this._dirty = false;
|
|
51
|
+
this._flushDamage = 'full';
|
|
52
|
+
this._seq = 0;
|
|
53
|
+
this._reactX11Node = null;
|
|
54
|
+
app._registerWindow(this);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// --- the channel-facing half --------------------------------------------
|
|
58
|
+
|
|
59
|
+
/** The host's layout answer: logical size plus the display scale. */
|
|
60
|
+
setPaneSize(width, height, scale) {
|
|
61
|
+
if (this.destroyed) return;
|
|
62
|
+
if (scale) this.scale = scale;
|
|
63
|
+
const w = Math.max(1, Math.round(width * this.scale));
|
|
64
|
+
const h = Math.max(1, Math.round(height * this.scale));
|
|
65
|
+
if (w === this.width && h === this.height) return;
|
|
66
|
+
this.width = w;
|
|
67
|
+
this.height = h;
|
|
68
|
+
this.emit('resize', {
|
|
69
|
+
width: w,
|
|
70
|
+
height: h,
|
|
71
|
+
x: 0,
|
|
72
|
+
y: 0,
|
|
73
|
+
moved: false,
|
|
74
|
+
resized: true,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// --- the window contract -------------------------------------------------
|
|
79
|
+
|
|
80
|
+
on(name, fn) {
|
|
81
|
+
let set = this._listeners.get(name);
|
|
82
|
+
if (!set) this._listeners.set(name, (set = new Set()));
|
|
83
|
+
set.add(fn);
|
|
84
|
+
return () => set.delete(fn);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
emit(name, ev) {
|
|
88
|
+
for (const fn of [...(this._listeners.get(name) ?? [])]) fn(ev);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
map() {}
|
|
92
|
+
|
|
93
|
+
focus() {}
|
|
94
|
+
|
|
95
|
+
setTitle() {}
|
|
96
|
+
|
|
97
|
+
requestAnimationFrame(cb) {
|
|
98
|
+
return this.app._requestFrame(cb);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
frameInFlight() {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Three buffers, not two. A pane is cross-process: the host keeps
|
|
106
|
+
// scanning the last buffer it was handed until it processes the next
|
|
107
|
+
// present message, and nothing here waits for that. With two buffers the
|
|
108
|
+
// next paint (and the catch-up copy) lands in the very buffer the host is
|
|
109
|
+
// still displaying — every present tears it, the flash. A third buffer is
|
|
110
|
+
// always at least two presents behind what the host shows, so the pane
|
|
111
|
+
// never writes a buffer the host might still be reading. Same-process
|
|
112
|
+
// windows need only two because Core Animation latches the front buffer.
|
|
113
|
+
static RING = 3;
|
|
114
|
+
|
|
115
|
+
_ensureSurface() {
|
|
116
|
+
const w = this.width;
|
|
117
|
+
const h = this.height;
|
|
118
|
+
if (
|
|
119
|
+
!this._ring ||
|
|
120
|
+
this._surfaceSize?.width !== w ||
|
|
121
|
+
this._surfaceSize?.height !== h
|
|
122
|
+
) {
|
|
123
|
+
const hadSurface = Boolean(this._ring);
|
|
124
|
+
this._ring = [];
|
|
125
|
+
for (let i = 0; i < CocoaPaneWindow.RING; i += 1) {
|
|
126
|
+
const s = this._native.createSurfaceIOSurface(w, h, this.scale, true);
|
|
127
|
+
this._native.ctxClearRect(s.handle, 0, 0, w, h);
|
|
128
|
+
this._ring.push(s);
|
|
129
|
+
}
|
|
130
|
+
this._drawIndex = 0;
|
|
131
|
+
this._shownIndex = -1;
|
|
132
|
+
this._native.surfaceLock(this._ring[0].handle);
|
|
133
|
+
this._surface = this._ring[0].handle;
|
|
134
|
+
this._surfaceSize = { width: w, height: h };
|
|
135
|
+
this._surfaceGen++;
|
|
136
|
+
this._flushDamage = 'full';
|
|
137
|
+
if (hadSurface) {
|
|
138
|
+
queueMicrotask(() => {
|
|
139
|
+
const node = this._reactX11Node;
|
|
140
|
+
if (node && !node.destroyed) node.invalidate(true, null, 'resize');
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return this._surface;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getContext() {
|
|
148
|
+
if (!this._ctx) {
|
|
149
|
+
this._ctx = new CocoaContext2D(
|
|
150
|
+
this._native,
|
|
151
|
+
() => this._ensureSurface(),
|
|
152
|
+
() => {
|
|
153
|
+
this._ensureSurface();
|
|
154
|
+
return this._surfaceGen;
|
|
155
|
+
},
|
|
156
|
+
);
|
|
157
|
+
this._ctx._fonts = this.app.fonts;
|
|
158
|
+
this._ctx._onDirty = () => {
|
|
159
|
+
this._dirty = true;
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return this._ctx;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
noteFrameDamage(rects) {
|
|
166
|
+
if (this._flushDamage === 'full') return;
|
|
167
|
+
if (!rects) {
|
|
168
|
+
this._flushDamage = 'full';
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
(this._flushDamage ??= []).push(...rects);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
scrollRegion(rect, dx, dy) {
|
|
175
|
+
if (!this._surface) return false;
|
|
176
|
+
if (!Number.isInteger(dx) || !Number.isInteger(dy)) return false;
|
|
177
|
+
const moved = this._native.scrollSurface(
|
|
178
|
+
this._surface,
|
|
179
|
+
Math.round(rect.x),
|
|
180
|
+
Math.round(rect.y),
|
|
181
|
+
Math.round(rect.width),
|
|
182
|
+
Math.round(rect.height),
|
|
183
|
+
dx,
|
|
184
|
+
dy,
|
|
185
|
+
);
|
|
186
|
+
if (moved) this._dirty = true;
|
|
187
|
+
return Boolean(moved);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Flip and tell the host, instead of touching any layer of our own. */
|
|
191
|
+
present() {
|
|
192
|
+
if (!this._dirty || !this._ring || this.destroyed) return;
|
|
193
|
+
this._dirty = false;
|
|
194
|
+
const shown = this._ring[this._drawIndex];
|
|
195
|
+
this._native.surfaceUnlock(shown.handle);
|
|
196
|
+
const damage = this._flushDamage;
|
|
197
|
+
this._flushDamage = null;
|
|
198
|
+
this.app._paneSend?.({
|
|
199
|
+
type: 'pane-present',
|
|
200
|
+
seq: ++this._seq,
|
|
201
|
+
id: shown.iosurfaceId,
|
|
202
|
+
width: this.width,
|
|
203
|
+
height: this.height,
|
|
204
|
+
});
|
|
205
|
+
this._shownIndex = this._drawIndex;
|
|
206
|
+
// the next buffer round the ring — two behind what the host will be
|
|
207
|
+
// showing, so it is safe to write even before the host has switched
|
|
208
|
+
this._drawIndex = (this._drawIndex + 1) % CocoaPaneWindow.RING;
|
|
209
|
+
const next = this._ring[this._drawIndex];
|
|
210
|
+
this._surface = next.handle;
|
|
211
|
+
this._surfaceGen++;
|
|
212
|
+
this._native.surfaceLock(next.handle);
|
|
213
|
+
// A full-repaint frame overwrites everything next, so no catch-up is
|
|
214
|
+
// needed. A partial frame paints only its damage, so `next` must first
|
|
215
|
+
// hold the last complete frame underneath — and the WHOLE of it, not
|
|
216
|
+
// just this frame's rects, because in a three-buffer ring `next` was
|
|
217
|
+
// last drawn two presents ago and is stale everywhere. A full copy of
|
|
218
|
+
// the just-shown buffer is the complete background; the safe target is
|
|
219
|
+
// what triple buffering buys.
|
|
220
|
+
if (damage !== 'full' && damage) {
|
|
221
|
+
this._native.copySurfaceRegion(shown.handle, next.handle, null);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
snapshot() {
|
|
226
|
+
return false; // no window of our own to capture; the host's shows it
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
destroy() {
|
|
230
|
+
if (this.destroyed) return;
|
|
231
|
+
this.destroyed = true;
|
|
232
|
+
this.app._unregisterWindow(this);
|
|
233
|
+
this._ring = null;
|
|
234
|
+
this._surface = null;
|
|
235
|
+
}
|
|
236
|
+
}
|