react-x11 2.12.0 → 2.14.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 +12 -8
- package/src/Reconciler.js +38 -35
- package/src/appcontext.js +59 -30
- package/src/cocoa/app.js +11 -0
- package/src/cocoa/glarea.js +4 -2
- package/src/cocoa/overlay.js +159 -0
- package/src/cocoa/promotion.js +13 -0
- package/src/components/Select.js +8 -2
- package/src/embedding.js +31 -0
- package/src/events.js +80 -14
- package/src/foreignnodes.js +56 -3
- package/src/glnodes.js +171 -40
- package/src/gloverlay.js +383 -0
- package/src/host.d.ts +0 -1
- package/src/index.d.ts +19 -1
- package/src/node.d.ts +12 -1
- package/src/nodes/boxpaint.js +9 -0
- package/src/nodes/preedit.js +64 -14
- package/src/nodes/window/flush.js +35 -0
- package/src/nodes/window/window.js +9 -0
- package/src/scale.js +52 -22
- package/src/screencolor.js +104 -17
- package/src/types/elements.d.ts +15 -13
- package/src/wayland/app.js +560 -0
- package/src/wayland/backendwindow.js +1123 -0
- package/src/wayland/clipboard.js +326 -0
- package/src/wayland/connection.js +482 -0
- package/src/wayland/context2d.js +2133 -0
- package/src/wayland/decorations.js +476 -0
- package/src/wayland/dmabuf.js +89 -0
- package/src/wayland/dnd.js +581 -0
- package/src/wayland/fdutil.js +108 -0
- package/src/wayland/framestyle.js +257 -0
- package/src/wayland/glarea.js +371 -0
- package/src/wayland/glcontext.js +415 -0
- package/src/wayland/glyphatlas.js +237 -0
- package/src/wayland/input.js +417 -0
- package/src/wayland/keysymnames.js +35 -0
- package/src/wayland/layershell.js +363 -0
- package/src/wayland/outputs.js +601 -0
- package/src/wayland/protocols/cursor-shape-v1.json +1 -0
- package/src/wayland/protocols/ext-idle-notify-v1.json +1 -0
- package/src/wayland/protocols/ext-image-capture-source-v1.json +1 -0
- package/src/wayland/protocols/ext-image-copy-capture-v1.json +1 -0
- package/src/wayland/protocols/fractional-scale-v1.json +1 -0
- package/src/wayland/protocols/index.json +127 -0
- package/src/wayland/protocols/keyboard-shortcuts-inhibit-unstable-v1.json +1 -0
- package/src/wayland/protocols/linux-dmabuf-v1.json +1 -0
- package/src/wayland/protocols/pointer-constraints-unstable-v1.json +1 -0
- package/src/wayland/protocols/presentation-time.json +1 -0
- package/src/wayland/protocols/primary-selection-unstable-v1.json +1 -0
- package/src/wayland/protocols/relative-pointer-unstable-v1.json +1 -0
- package/src/wayland/protocols/tablet-v2.json +1 -0
- package/src/wayland/protocols/text-input-unstable-v3.json +1 -0
- package/src/wayland/protocols/viewporter.json +1 -0
- package/src/wayland/protocols/wayland.json +1 -0
- package/src/wayland/protocols/wlr-layer-shell-unstable-v1.json +1 -0
- package/src/wayland/protocols/wlr-screencopy-unstable-v1.json +1 -0
- package/src/wayland/protocols/xdg-activation-v1.json +1 -0
- package/src/wayland/protocols/xdg-decoration-unstable-v1.json +1 -0
- package/src/wayland/protocols/xdg-output-unstable-v1.json +1 -0
- package/src/wayland/protocols/xdg-shell.json +1 -0
- package/src/wayland/protocols/xdg-toplevel-icon-v1.json +1 -0
- package/src/wayland/readback.js +99 -0
- package/src/wayland/screencopy.js +584 -0
- package/src/wayland/seat.js +584 -0
- package/src/wayland/shm.js +226 -0
- package/src/wayland/ssd.js +106 -0
- package/src/wayland/surface.js +123 -0
- package/src/wayland/swapchain.js +411 -0
- package/src/wayland/tablet.js +522 -0
- package/src/wayland/target.js +263 -0
- package/src/wayland/text.js +113 -0
- package/src/wayland/textinput.js +671 -0
- package/src/wayland/touch.js +284 -0
- package/src/wayland/window.js +827 -0
- package/src/wayland/xkb.js +425 -0
package/src/gloverlay.js
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
// The children of a `<glarea>`: 2D content, drawn above the GL surface.
|
|
2
|
+
//
|
|
3
|
+
// A surface is stacked over everything 2D in its window — a child X window
|
|
4
|
+
// above the parent's drawing on X11, a layer at zPosition 1e7 over both
|
|
5
|
+
// presenters on the Cocoa backend — so the window's own paint walk can never
|
|
6
|
+
// put a pixel on it, and does not try: a `<glarea>` is in no paint order, and
|
|
7
|
+
// so is nothing under it. Its children are otherwise ordinary. They are laid
|
|
8
|
+
// out in its box like a `<box>`'s; they live in the owning window's tree, so
|
|
9
|
+
// their claims land in its damage list and their input comes through its
|
|
10
|
+
// event manager, the hit test asking them before the surface itself
|
|
11
|
+
// (`GlAreaNode.hitSurface`). What is theirs alone is where they are painted:
|
|
12
|
+
// into *panes* above the surface, from inside the owning window's frame and
|
|
13
|
+
// with that frame's damage (nodes/window/flush.js, `_syncOverlays` and
|
|
14
|
+
// `_paintOverlays`).
|
|
15
|
+
//
|
|
16
|
+
// A pane is the backend's answer to one question — can a window composite a
|
|
17
|
+
// translucent child over a GL surface?
|
|
18
|
+
//
|
|
19
|
+
// - **The Cocoa backend: yes.** Core Animation composites every layer, so
|
|
20
|
+
// one pane holds everything — a transparent bitmap layer over the whole
|
|
21
|
+
// surface, above the GL layer — and a translucent background, an
|
|
22
|
+
// antialiased edge or a shadow blends with the GL frame under it.
|
|
23
|
+
// `app.createOverlayPane` is how a backend says it composites.
|
|
24
|
+
// - **X11: no.** A child window is opaque without a compositor, and a
|
|
25
|
+
// compositor would not change that: it redirects top-level windows, not
|
|
26
|
+
// the children inside one. So a pane is a plain child window just big
|
|
27
|
+
// enough for what it holds, one per region the children reach, and the
|
|
28
|
+
// surface shows between them. Inside a pane the ground is the surface's
|
|
29
|
+
// `clearColor` — the colour its frames start from — so a pixel a child
|
|
30
|
+
// leaves unpainted there (a rounded corner, a translucent background, text
|
|
31
|
+
// with no box behind it) shows that colour, never the GL frame.
|
|
32
|
+
//
|
|
33
|
+
// A window per region rather than one window shaped by the SHAPE
|
|
34
|
+
// extension, for two reasons: SHAPE is near-universal but not universal —
|
|
35
|
+
// node-x11's in-process server, where this is tested, has none — and a
|
|
36
|
+
// window as big as the surface would keep a backing pixmap the size of a
|
|
37
|
+
// map to show a legend in its corner.
|
|
38
|
+
//
|
|
39
|
+
// A pane selects no input, so the pointer over one reaches the owning window
|
|
40
|
+
// by the same propagation that brings it the pointer over the surface
|
|
41
|
+
// (src/glnodes.js, `_create`), and lands on the child it is over.
|
|
42
|
+
//
|
|
43
|
+
// A leaf module, like src/embedding.js: `appcontext.js` asks `canOverlay`
|
|
44
|
+
// for `useSupports('glOverlay')`, and it imports nothing of ours.
|
|
45
|
+
import { cssColorStraight } from 'ntk';
|
|
46
|
+
|
|
47
|
+
// ConfigureWindow's stack mode: directly above the sibling it names
|
|
48
|
+
const STACK_ABOVE = 0;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Whether the children of a `<glarea>` can be drawn over its surface on this
|
|
52
|
+
* connection: a backend that composites a pane itself, or one that can make
|
|
53
|
+
* the plain child window a pane is on X11. One function for the element and
|
|
54
|
+
* for `useSupports('glOverlay')`, which have to agree — the rule `canEmbed`
|
|
55
|
+
* follows for `<foreign>`.
|
|
56
|
+
*/
|
|
57
|
+
export function canOverlay(app) {
|
|
58
|
+
return (
|
|
59
|
+
typeof app?.createOverlayPane === 'function' ||
|
|
60
|
+
typeof app?.createWindow === 'function'
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A rect grown out to whole device pixels. */
|
|
65
|
+
function whole(r) {
|
|
66
|
+
const x = Math.floor(r.x);
|
|
67
|
+
const y = Math.floor(r.y);
|
|
68
|
+
return {
|
|
69
|
+
x,
|
|
70
|
+
y,
|
|
71
|
+
width: Math.ceil(r.x + r.width) - x,
|
|
72
|
+
height: Math.ceil(r.y + r.height) - y,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function intersect(a, b) {
|
|
77
|
+
const x = Math.max(a.x, b.x);
|
|
78
|
+
const y = Math.max(a.y, b.y);
|
|
79
|
+
const right = Math.min(a.x + a.width, b.x + b.width);
|
|
80
|
+
const bottom = Math.min(a.y + a.height, b.y + b.height);
|
|
81
|
+
return right > x && bottom > y
|
|
82
|
+
? { x, y, width: right - x, height: bottom - y }
|
|
83
|
+
: null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const overlaps = (a, b) =>
|
|
87
|
+
a.x < b.x + b.width &&
|
|
88
|
+
b.x < a.x + a.width &&
|
|
89
|
+
a.y < b.y + b.height &&
|
|
90
|
+
b.y < a.y + a.height;
|
|
91
|
+
|
|
92
|
+
function around(a, b) {
|
|
93
|
+
const x = Math.min(a.x, b.x);
|
|
94
|
+
const y = Math.min(a.y, b.y);
|
|
95
|
+
return {
|
|
96
|
+
x,
|
|
97
|
+
y,
|
|
98
|
+
width: Math.max(a.x + a.width, b.x + b.width) - x,
|
|
99
|
+
height: Math.max(a.y + a.height, b.y + b.height) - y,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const sameRect = (a, b) =>
|
|
104
|
+
a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
|
105
|
+
|
|
106
|
+
/** On screen: nothing from here to the window hidden or `display: 'none'`. */
|
|
107
|
+
function shown(node) {
|
|
108
|
+
for (let n = node; n; n = n.parent) {
|
|
109
|
+
if (n.destroyed || n.hidden || n.style?.display === 'none') return false;
|
|
110
|
+
if (n.isWindow) break;
|
|
111
|
+
}
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Where the children put ink, as rects no two of which overlap: each drawn
|
|
117
|
+
* child's reach in whole pixels, cut to the surface, and any two that
|
|
118
|
+
* overlap merged into the box around both. A pane is a rectangle, and two
|
|
119
|
+
* panes over one pixel would each have to hold what the other draws there.
|
|
120
|
+
*
|
|
121
|
+
* The reach is `_subtreeBounds()`, the rect the damage model culls against —
|
|
122
|
+
* so a child's shadow, its outline and a descendant that sticks out of it
|
|
123
|
+
* are all inside its pane.
|
|
124
|
+
*/
|
|
125
|
+
export function overlayRegions(area, surface) {
|
|
126
|
+
const rects = [];
|
|
127
|
+
for (const child of area.paintOrder()) {
|
|
128
|
+
const reach = intersect(whole(child._subtreeBounds()), surface);
|
|
129
|
+
if (reach) rects.push(reach);
|
|
130
|
+
}
|
|
131
|
+
for (let merged = true; merged;) {
|
|
132
|
+
merged = false;
|
|
133
|
+
search: for (let i = 0; i < rects.length; i++) {
|
|
134
|
+
for (let j = i + 1; j < rects.length; j++) {
|
|
135
|
+
if (overlaps(rects[i], rects[j])) {
|
|
136
|
+
rects[i] = around(rects[i], rects[j]);
|
|
137
|
+
rects.splice(j, 1);
|
|
138
|
+
merged = true;
|
|
139
|
+
break search;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return rects;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The colour an opaque pane is filled with before its children paint: the
|
|
149
|
+
* surface's `clearColor`, what the GL frame under the pane starts from, at
|
|
150
|
+
* full alpha. The pane is opaque, and a translucent fill would pile up on
|
|
151
|
+
* the pane's own last frame rather than show anything under it.
|
|
152
|
+
*/
|
|
153
|
+
function groundOf(props) {
|
|
154
|
+
const value = props.clearColor ?? 'black';
|
|
155
|
+
const [r, g, b] = Array.isArray(value)
|
|
156
|
+
? value
|
|
157
|
+
: (cssColorStraight(value) ?? [0, 0, 0, 1]);
|
|
158
|
+
const byte = (c) => Math.round(Math.max(0, Math.min(1, c)) * 255);
|
|
159
|
+
return `rgb(${byte(r)}, ${byte(g)}, ${byte(b)})`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* One pane: the window — or on the Cocoa backend the layer, which speaks the
|
|
164
|
+
* same few verbs — and what the overlay knows about it: where it is, whether
|
|
165
|
+
* all of it is owed a paint, and its 2d context, made once, since ntk builds
|
|
166
|
+
* a fresh one with subscriptions of its own on every `getContext`.
|
|
167
|
+
*/
|
|
168
|
+
class Pane {
|
|
169
|
+
constructor(wnd, rect, transparent) {
|
|
170
|
+
this.wnd = wnd;
|
|
171
|
+
this.rect = rect;
|
|
172
|
+
this.transparent = transparent;
|
|
173
|
+
this.full = true;
|
|
174
|
+
this.ctx = null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
context() {
|
|
178
|
+
if (!this.ctx && typeof this.wnd.getContext === 'function') {
|
|
179
|
+
this.ctx = this.wnd.getContext('2d');
|
|
180
|
+
}
|
|
181
|
+
return this.ctx;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Somewhere else. A new size is a new backing — a pixmap on X11, a
|
|
185
|
+
* bitmap on Cocoa — and has to be painted whole; a move keeps what the
|
|
186
|
+
* pane holds, which moved with the children it shows. */
|
|
187
|
+
place(rect) {
|
|
188
|
+
if (rect.width !== this.rect.width || rect.height !== this.rect.height) {
|
|
189
|
+
this.full = true;
|
|
190
|
+
}
|
|
191
|
+
this.rect = rect;
|
|
192
|
+
if (typeof this.wnd.setState === 'function') this.wnd.setState(rect);
|
|
193
|
+
else {
|
|
194
|
+
this.wnd.move?.(rect.x, rect.y);
|
|
195
|
+
this.wnd.resize?.(rect.width, rect.height);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
show(on) {
|
|
200
|
+
if (on) this.wnd.map?.();
|
|
201
|
+
else this.wnd.unmap?.();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
destroy() {
|
|
205
|
+
this.wnd.destroy?.();
|
|
206
|
+
this.ctx = null;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** A `<glarea>`'s panes, from the owning window's frame. */
|
|
211
|
+
export class GlOverlay {
|
|
212
|
+
constructor(area) {
|
|
213
|
+
this.area = area;
|
|
214
|
+
this.app = area.app;
|
|
215
|
+
// one pane over the whole surface, where the backend composites it
|
|
216
|
+
this.composited = typeof this.app?.createOverlayPane === 'function';
|
|
217
|
+
this.panes = [];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* After layout: panes for where the children are now. True when a pane was
|
|
222
|
+
* made, resized or dropped — a paint the frame then owes.
|
|
223
|
+
*
|
|
224
|
+
* Nothing is made for a surface that is hidden, or has no area, or has no
|
|
225
|
+
* child on screen: a pane with nothing on it would only hide the surface.
|
|
226
|
+
* The panes of one that goes are dropped rather than kept unmapped, and
|
|
227
|
+
* the frame that brings it back makes new ones and paints them whole.
|
|
228
|
+
*/
|
|
229
|
+
sync() {
|
|
230
|
+
const area = this.area;
|
|
231
|
+
const abs = area.abs;
|
|
232
|
+
let rects = [];
|
|
233
|
+
if (shown(area) && abs.width > 0 && abs.height > 0) {
|
|
234
|
+
// the surface's own rect, rounded the way its window's is
|
|
235
|
+
const surface = area._geometry();
|
|
236
|
+
if (!this.composited) rects = overlayRegions(area, surface);
|
|
237
|
+
else if (area.paintOrder().length) rects = [surface];
|
|
238
|
+
}
|
|
239
|
+
let changed = false;
|
|
240
|
+
while (this.panes.length > rects.length) {
|
|
241
|
+
this.panes.pop().destroy();
|
|
242
|
+
changed = true;
|
|
243
|
+
}
|
|
244
|
+
let made = false;
|
|
245
|
+
for (let i = 0; i < rects.length; i++) {
|
|
246
|
+
const pane = this.panes[i];
|
|
247
|
+
if (!pane) {
|
|
248
|
+
this.panes.push(this._makePane(rects[i]));
|
|
249
|
+
made = changed = true;
|
|
250
|
+
} else if (!sameRect(pane.rect, rects[i])) {
|
|
251
|
+
pane.place(rects[i]);
|
|
252
|
+
changed = true;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (made) this.restack();
|
|
256
|
+
return changed;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
_makePane(rect) {
|
|
260
|
+
const owner = this.area.root.window;
|
|
261
|
+
const attributes = { parent: owner, ...rect };
|
|
262
|
+
const pane = this.composited
|
|
263
|
+
? new Pane(this.app.createOverlayPane(attributes), rect, true)
|
|
264
|
+
: // A plain child window on the owning window's visual, selecting
|
|
265
|
+
// nothing but what ntk selects for itself — the structure events and
|
|
266
|
+
// the exposures its backing store answers. The pointer is the tree's.
|
|
267
|
+
new Pane(this.app.createWindow(attributes), rect, false);
|
|
268
|
+
// ntk asks for a redraw when the backing no longer holds the picture — a
|
|
269
|
+
// resize it could not carry over — and a pane has nothing to redraw from
|
|
270
|
+
// but the children, on the next frame of the window they live in
|
|
271
|
+
pane.wnd.on?.('draw', () => this._lost(pane));
|
|
272
|
+
pane.show(true);
|
|
273
|
+
return pane;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** A pane whose pixels are gone: all of it is owed, on the owning window's
|
|
277
|
+
* next frame, claimed as its rect so the rest of the window stays put. */
|
|
278
|
+
_lost(pane) {
|
|
279
|
+
pane.full = true;
|
|
280
|
+
const root = this.area.root;
|
|
281
|
+
if (root && !root.destroyed)
|
|
282
|
+
root.invalidate(false, { ...pane.rect }, 'expose');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Put the panes directly over the surface, bottom to top. Only X11 needs
|
|
287
|
+
* it: a child window is made on top of its siblings, which is right for a
|
|
288
|
+
* pane made after the surface and wrong for one made before it — the
|
|
289
|
+
* surface's window is made once its visual is known, which can be after
|
|
290
|
+
* the first frame has laid out and painted its children. So the surface
|
|
291
|
+
* restacks its panes when its own window is made, too (`_create`). On the
|
|
292
|
+
* Cocoa backend the layer's zPosition is the whole of it.
|
|
293
|
+
*/
|
|
294
|
+
restack() {
|
|
295
|
+
if (this.composited) return;
|
|
296
|
+
const X = this.app?.X;
|
|
297
|
+
if (typeof X?.ConfigureWindow !== 'function') return;
|
|
298
|
+
let below = this.area.window?.id ?? null;
|
|
299
|
+
for (const pane of this.panes) {
|
|
300
|
+
const id = pane.wnd.id;
|
|
301
|
+
if (id == null) continue;
|
|
302
|
+
if (below == null) X.ConfigureWindow(id, { stackMode: STACK_ABOVE });
|
|
303
|
+
else X.ConfigureWindow(id, { sibling: below, stackMode: STACK_ABOVE });
|
|
304
|
+
below = id;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Paint what the frame owes: every pane whole after it was made, resized
|
|
310
|
+
* or lost its pixels, and otherwise the frame's damage cut to each pane —
|
|
311
|
+
* `null` damage meaning the whole window, as it does for the paint walk.
|
|
312
|
+
*
|
|
313
|
+
* Each pass is the window's own paint walk over the children, translated
|
|
314
|
+
* so the pane's corner is the bitmap's, clipped to the pass, with
|
|
315
|
+
* `paintDamage()` naming it — the same culling, the same paint cache, the
|
|
316
|
+
* same everything as a pass over the window, and in its coordinates.
|
|
317
|
+
*/
|
|
318
|
+
paint(damage) {
|
|
319
|
+
for (const pane of this.panes) {
|
|
320
|
+
let passes;
|
|
321
|
+
if (pane.full || !damage) {
|
|
322
|
+
passes = [pane.rect];
|
|
323
|
+
} else {
|
|
324
|
+
passes = [];
|
|
325
|
+
for (const rect of damage) {
|
|
326
|
+
const hit = intersect(rect, pane.rect);
|
|
327
|
+
if (hit) passes.push(hit);
|
|
328
|
+
}
|
|
329
|
+
if (passes.length === 0) continue;
|
|
330
|
+
}
|
|
331
|
+
pane.full = false;
|
|
332
|
+
this._paintPane(pane, passes);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
_paintPane(pane, passes) {
|
|
337
|
+
const area = this.area;
|
|
338
|
+
const root = area.root;
|
|
339
|
+
const ctx = pane.context();
|
|
340
|
+
if (!ctx || !root) return;
|
|
341
|
+
// an opaque pane is filled with what the surface starts its frames from;
|
|
342
|
+
// a composited one is cleared, and shows the frame itself
|
|
343
|
+
const ground = pane.transparent ? null : groundOf(area.props);
|
|
344
|
+
ctx.save();
|
|
345
|
+
try {
|
|
346
|
+
ctx.translate(-pane.rect.x, -pane.rect.y);
|
|
347
|
+
for (const pass of passes) {
|
|
348
|
+
ctx.save();
|
|
349
|
+
try {
|
|
350
|
+
ctx.beginPath();
|
|
351
|
+
ctx.rect(pass.x, pass.y, pass.width, pass.height);
|
|
352
|
+
ctx.clip();
|
|
353
|
+
if (ground) {
|
|
354
|
+
ctx.fillStyle = ground;
|
|
355
|
+
ctx.fillRect(pass.x, pass.y, pass.width, pass.height);
|
|
356
|
+
} else {
|
|
357
|
+
ctx.clearRect(pass.x, pass.y, pass.width, pass.height);
|
|
358
|
+
}
|
|
359
|
+
root._paintDamage = pass;
|
|
360
|
+
area._paintChildren(ctx);
|
|
361
|
+
} finally {
|
|
362
|
+
root._paintDamage = null;
|
|
363
|
+
ctx.restore();
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
} finally {
|
|
367
|
+
ctx.restore();
|
|
368
|
+
}
|
|
369
|
+
// A layer's contents are a copy the bitmap is pushed to; an X window's
|
|
370
|
+
// backing store is blitted by ntk on its own, from the paint above
|
|
371
|
+
pane.wnd.present?.();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** Unmapped now; `sync` drops them on the frame the hide lays out. */
|
|
375
|
+
setHidden(hidden) {
|
|
376
|
+
for (const pane of this.panes) pane.show(!hidden);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
destroy() {
|
|
380
|
+
for (const pane of this.panes) pane.destroy();
|
|
381
|
+
this.panes = [];
|
|
382
|
+
}
|
|
383
|
+
}
|
package/src/host.d.ts
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -163,8 +163,21 @@ export function useClipboard(): Clipboard;
|
|
|
163
163
|
* branches on it through the theme's `controls: 'auto'` policy; this is for
|
|
164
164
|
* application code composing its own controls to sit beside native ones. It
|
|
165
165
|
* is a property of the backend and never changes over the app's life.
|
|
166
|
+
*
|
|
167
|
+
* `'embedding'` is whether this connection can take another process's window
|
|
168
|
+
* into its own — X11, never Cocoa or the headless mock. Ask it before
|
|
169
|
+
* rendering a `<foreign>`, which refuses with one `onError` where it is
|
|
170
|
+
* false. Also a property of the backend, and it never changes either.
|
|
171
|
+
*
|
|
172
|
+
* `'glOverlay'` is whether the children of a `<glarea>` are drawn above its
|
|
173
|
+
* GL surface — laid out in its box, painted on panes stacked over it, hit
|
|
174
|
+
* before it. Both backends draw them; translucency is the difference,
|
|
175
|
+
* composited by Core Animation on Cocoa and opaque on X11, where what a
|
|
176
|
+
* child leaves unpainted shows the surface's `clearColor` (docs/elements.md).
|
|
177
|
+
* Ask it before handing a surface its HUD. A property of the backend.
|
|
166
178
|
*/
|
|
167
|
-
export type SupportsFeature =
|
|
179
|
+
export type SupportsFeature =
|
|
180
|
+
'transparency' | 'shaders' | 'nativeControls' | 'embedding' | 'glOverlay';
|
|
168
181
|
|
|
169
182
|
/**
|
|
170
183
|
* Can this **display** do something? `'transparency'` is true when the
|
|
@@ -287,6 +300,11 @@ export interface RootOptions {
|
|
|
287
300
|
| 'off'
|
|
288
301
|
| {
|
|
289
302
|
mode?: 'auto' | 'direct' | 'indirect' | 'off';
|
|
303
|
+
/**
|
|
304
|
+
* Wayland: the DRM node to render on. Defaults to
|
|
305
|
+
* `REACT_X11_GL_DEVICE`, then the first render node, then, on a
|
|
306
|
+
* machine with no render node, each card node in turn.
|
|
307
|
+
*/
|
|
290
308
|
devicePath?: string | null;
|
|
291
309
|
maxInFlight?: number;
|
|
292
310
|
linearFallback?: boolean;
|
package/src/node.d.ts
CHANGED
|
@@ -820,5 +820,16 @@ export declare class WindowNode extends Node {}
|
|
|
820
820
|
export declare class PopupNode extends WindowNode {}
|
|
821
821
|
/** The precedents for an element owning a real child X window — a surface of
|
|
822
822
|
* its own, and one holding somebody else's window. */
|
|
823
|
-
export declare class GlAreaNode extends Node {
|
|
823
|
+
export declare class GlAreaNode extends Node {
|
|
824
|
+
/**
|
|
825
|
+
* `true`: the pointer over the surface is dispatched through the owning
|
|
826
|
+
* window's event manager, at this node, on this backend. So an element
|
|
827
|
+
* built on `<glarea>` must not listen for the pointer on `node.window` —
|
|
828
|
+
* on X11 that selects the event on the surface's own window, and X then
|
|
829
|
+
* delivers it there instead of to the tree. Readable without rendering,
|
|
830
|
+
* as `GlAreaNode.prototype.forwardsPointer`; absent on a core that does
|
|
831
|
+
* not deliver it.
|
|
832
|
+
*/
|
|
833
|
+
readonly forwardsPointer: true;
|
|
834
|
+
}
|
|
824
835
|
export declare class ForeignNode extends Node {}
|
package/src/nodes/boxpaint.js
CHANGED
|
@@ -254,6 +254,15 @@ export class NodeBoxPaint {
|
|
|
254
254
|
ctx.fill();
|
|
255
255
|
continue;
|
|
256
256
|
}
|
|
257
|
+
// A context that can feather a rounded rectangle itself — the GPU
|
|
258
|
+
// backend's distance-field shadow — skips the coverage surface, the
|
|
259
|
+
// CPU blur and the paint cache entirely. Feature-detected, like
|
|
260
|
+
// `presentFrame`: an ntk context has no such method and takes the
|
|
261
|
+
// path below.
|
|
262
|
+
if (typeof ctx.fillShadow === 'function') {
|
|
263
|
+
ctx.fillShadow(rect, r, shadow.blur, color);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
257
266
|
this._paintBlurredShadow(ctx, rect, r, shadow.blur, color);
|
|
258
267
|
}
|
|
259
268
|
}
|
package/src/nodes/preedit.js
CHANGED
|
@@ -43,12 +43,18 @@ export class TextInputPreedit {
|
|
|
43
43
|
|
|
44
44
|
/** A value index in the displayed string. The caret is at the composition
|
|
45
45
|
* while it is open, so it maps to the *end* of the preedit — which is
|
|
46
|
-
* where the next keystroke of the sequence appears
|
|
46
|
+
* where the next keystroke of the sequence appears — unless the input
|
|
47
|
+
* method said where inside its preedit the cursor is (`_preeditCursor`,
|
|
48
|
+
* a compositor IME converting one segment of a longer phrase). */
|
|
47
49
|
_displayIndex(index) {
|
|
48
50
|
if (!this._preedit) return index;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
const start = this._preeditStart();
|
|
52
|
+
if (index < start) return index;
|
|
53
|
+
const length = Array.from(this._preedit).length;
|
|
54
|
+
if (index === start && this._preeditCursor) {
|
|
55
|
+
return start + Math.min(length, this._preeditCursor.begin);
|
|
56
|
+
}
|
|
57
|
+
return index + length;
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
/** The inverse, for indices that come back out of a layout. A hit inside
|
|
@@ -61,10 +67,20 @@ export class TextInputPreedit {
|
|
|
61
67
|
return Math.max(start, index - Array.from(this._preedit).length);
|
|
62
68
|
}
|
|
63
69
|
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
/**
|
|
71
|
+
* `cursor` is `{ begin, end }` in code points into `text` — where an input
|
|
72
|
+
* method wants the caret inside its preedit, or a segment of it
|
|
73
|
+
* highlighted when the two differ — and null for the end, which is where
|
|
74
|
+
* the client-side composer's caret always is.
|
|
75
|
+
*/
|
|
76
|
+
_setPreedit(text, cursor = null) {
|
|
77
|
+
const range = text && cursor ? cursor : null;
|
|
78
|
+
if (text === this._preedit && sameRange(range, this._preeditCursor)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
66
81
|
if (!this._preedit) this._preeditAt = this._selection()[0];
|
|
67
82
|
this._preedit = text;
|
|
83
|
+
this._preeditCursor = range;
|
|
68
84
|
this._repaint();
|
|
69
85
|
}
|
|
70
86
|
|
|
@@ -79,7 +95,18 @@ export class TextInputPreedit {
|
|
|
79
95
|
*/
|
|
80
96
|
defaultComposition(ev) {
|
|
81
97
|
if (ev.type !== 'compositionEnd') {
|
|
82
|
-
|
|
98
|
+
// `cursorBegin`/`cursorEnd` ride the event only when an input method
|
|
99
|
+
// put them there (src/wayland/textinput.js); a dead key carries none
|
|
100
|
+
const begin = ev.cursorBegin;
|
|
101
|
+
const cursor = Number.isInteger(begin)
|
|
102
|
+
? {
|
|
103
|
+
begin,
|
|
104
|
+
end: Number.isInteger(ev.cursorEnd)
|
|
105
|
+
? Math.max(begin, ev.cursorEnd)
|
|
106
|
+
: begin,
|
|
107
|
+
}
|
|
108
|
+
: null;
|
|
109
|
+
this._setPreedit(ev.data, cursor);
|
|
83
110
|
return;
|
|
84
111
|
}
|
|
85
112
|
this._setPreedit('');
|
|
@@ -114,14 +141,37 @@ export class TextInputPreedit {
|
|
|
114
141
|
// this and a composition at a wrap point is two spans — drawn as one
|
|
115
142
|
// batch, so the underline costs one request however it wraps
|
|
116
143
|
const rects = [];
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
144
|
+
const underline = (from, to, thickness) => {
|
|
145
|
+
for (let li = from.line; li <= to.line; li++) {
|
|
146
|
+
const line = layout.lines[li];
|
|
147
|
+
if (!line) break;
|
|
148
|
+
const x0 = li === from.line ? from.x : line.x;
|
|
149
|
+
const x1 = li === to.line ? to.x : line.x + line.width;
|
|
150
|
+
if (x1 <= x0) continue;
|
|
151
|
+
rects.push(
|
|
152
|
+
originX + x0,
|
|
153
|
+
originY + line.y + line.ascent + 1,
|
|
154
|
+
x1 - x0,
|
|
155
|
+
thickness,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
underline(from, to, 1);
|
|
160
|
+
// The segment an input method is converting, when it named one: the
|
|
161
|
+
// convention (GTK, Qt, the browsers) is a heavier line under the part of
|
|
162
|
+
// the phrase the candidates are for, and a plain one under the rest.
|
|
163
|
+
const cursor = this._preeditCursor;
|
|
164
|
+
if (cursor && cursor.end > cursor.begin) {
|
|
165
|
+
underline(
|
|
166
|
+
layout.caretPosition(start + cursor.begin),
|
|
167
|
+
layout.caretPosition(start + cursor.end),
|
|
168
|
+
2,
|
|
169
|
+
);
|
|
124
170
|
}
|
|
125
171
|
if (rects.length) ctx.fillRects(rects);
|
|
126
172
|
}
|
|
127
173
|
}
|
|
174
|
+
|
|
175
|
+
function sameRange(a, b) {
|
|
176
|
+
return a === b || (!!a && !!b && a.begin === b.begin && a.end === b.end);
|
|
177
|
+
}
|
|
@@ -84,6 +84,29 @@ export class WindowFlush {
|
|
|
84
84
|
this._pacer.charge(ms);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Panes for the children of every `<glarea>` here, where this frame's
|
|
89
|
+
* layout put them (src/gloverlay.js). True when one was made, resized or
|
|
90
|
+
* dropped, which the frame then owes a paint for.
|
|
91
|
+
*/
|
|
92
|
+
_syncOverlays() {
|
|
93
|
+
let changed = false;
|
|
94
|
+
for (const area of this._overlaid) {
|
|
95
|
+
if (area.destroyed || area.root !== this) {
|
|
96
|
+
this._overlaid.delete(area);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (area._syncOverlay()) changed = true;
|
|
100
|
+
}
|
|
101
|
+
return changed;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** …and their paint, with the frame's own damage: a claim inside a
|
|
105
|
+
* `<glarea>`'s children is in that list like any other. */
|
|
106
|
+
_paintOverlays(damage) {
|
|
107
|
+
for (const area of this._overlaid) area._paintOverlay(damage);
|
|
108
|
+
}
|
|
109
|
+
|
|
87
110
|
/**
|
|
88
111
|
* A frame: layout if owed, then the paint passes — or the presenter's
|
|
89
112
|
* frame — then the backend's word. Runs on the window's clock through
|
|
@@ -254,6 +277,12 @@ export class WindowFlush {
|
|
|
254
277
|
// …and the next commit's claims name the arrangement this frame leaves
|
|
255
278
|
// behind again, from before whatever scroll comes with them
|
|
256
279
|
this._laidOut = false;
|
|
280
|
+
// The children of a `<glarea>` get panes where they are now: after
|
|
281
|
+
// layout and the scroll's shift, before the damage is taken, so a pane
|
|
282
|
+
// made or resized in this frame is painted in it, whole
|
|
283
|
+
if (this._overlaid.size !== 0 && this._syncOverlays()) {
|
|
284
|
+
this.needsPaint = true;
|
|
285
|
+
}
|
|
257
286
|
if (!this.needsPaint) return false;
|
|
258
287
|
this.needsPaint = false;
|
|
259
288
|
const damage = this._takeDamage(width, height);
|
|
@@ -276,6 +305,8 @@ export class WindowFlush {
|
|
|
276
305
|
// list was still taken (its bookkeeping is what keeps the two paths one
|
|
277
306
|
// code) and is simply not consumed; the presenter diffs at the layer.
|
|
278
307
|
if (typeof this.window.presentFrame === 'function') {
|
|
308
|
+
// the panes are no presenter's: they paint from the damage either way
|
|
309
|
+
if (this._overlaid.size !== 0) this._paintOverlays(damage);
|
|
279
310
|
this.window.presentFrame(this, damage);
|
|
280
311
|
this.app._reactX11Startup?.painted();
|
|
281
312
|
return true;
|
|
@@ -297,6 +328,10 @@ export class WindowFlush {
|
|
|
297
328
|
for (const rect of damage ?? [null]) {
|
|
298
329
|
this._paintRegion(ctx, rect, width, height);
|
|
299
330
|
}
|
|
331
|
+
// …and the panes over the surfaces, with the same damage: a claim from
|
|
332
|
+
// a `<glarea>`'s children is theirs to repaint — the window's pass under
|
|
333
|
+
// the surface is one nobody sees. Inside the cache's frame, like a pass.
|
|
334
|
+
if (this._overlaid.size !== 0) this._paintOverlays(damage);
|
|
300
335
|
// after every region: an entry drawn in one damage rect must not be
|
|
301
336
|
// evicted before the next rect of the same frame asks for it
|
|
302
337
|
this._paintCache?.endFrame();
|
|
@@ -191,6 +191,15 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
191
191
|
// behind one `size` read on the motion path, and a tree that never asked
|
|
192
192
|
// for attention must not pay a property walk to find that out.
|
|
193
193
|
this._attentionNodes = new Set();
|
|
194
|
+
// The GL surfaces in this window, bottom to top: `<glarea>`s, stacked
|
|
195
|
+
// above everything 2D here, which the hit test therefore asks before the
|
|
196
|
+
// tree (`EventManager._surfaceAt`). A surface joins when its window is
|
|
197
|
+
// made and leaves when it goes (src/glnodes.js).
|
|
198
|
+
this._surfaces = [];
|
|
199
|
+
// …and the ones with children, whose panes each frame syncs after layout
|
|
200
|
+
// and paints with its damage (nodes/window/flush.js, src/gloverlay.js).
|
|
201
|
+
// Empty is one `size` read a frame.
|
|
202
|
+
this._overlaid = new Set();
|
|
194
203
|
this.events = new EventManager(this);
|
|
195
204
|
// ids of the child windows in the order the *server* stacks them,
|
|
196
205
|
// bottom to top — see _restackWindowChildren
|