react-x11 2.26.0 → 2.26.2
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 +1 -1
- package/src/cocoa/app.js +6 -0
- package/src/cocoa/fonts.js +63 -0
- package/src/events.js +84 -1
- package/src/frame/index.js +5 -0
package/package.json
CHANGED
package/src/cocoa/app.js
CHANGED
|
@@ -259,6 +259,12 @@ export class CocoaApp {
|
|
|
259
259
|
? new CocoaFilePanels(this)
|
|
260
260
|
: null;
|
|
261
261
|
|
|
262
|
+
// A window that is not the active one does not track the pointer here:
|
|
263
|
+
// no `:hover`, no enter or leave, no motion, until it is the active one
|
|
264
|
+
// again (`EventManager.hoverLive`). The platform's convention and not
|
|
265
|
+
// X11's, where every toolkit hovers in a window without the keyboard.
|
|
266
|
+
this.hoverNeedsActiveWindow = true;
|
|
267
|
+
|
|
262
268
|
// AppKit-rendered control bezels. Its *presence* is the capability:
|
|
263
269
|
// `useSupports('nativeControls')` and the widget set's `controls:
|
|
264
270
|
// 'auto'` policy both test for this property, so a backend without it
|
package/src/cocoa/fonts.js
CHANGED
|
@@ -150,6 +150,31 @@ function unpackLines(lineData = NO_GEOMETRY, runData = NO_GEOMETRY) {
|
|
|
150
150
|
return lines;
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
/**
|
|
154
|
+
* The one line of an empty paragraph, placed the way the native places a
|
|
155
|
+
* line with text in it: a `lineHeight` multiplies the face's natural line,
|
|
156
|
+
* and the leading is split either side of the glyphs (CSS half-leading,
|
|
157
|
+
* which ntk and `@windowkit/appkit` >= 0.15 both follow).
|
|
158
|
+
*/
|
|
159
|
+
function blankLine(metrics, lineHeight, x) {
|
|
160
|
+
const { ascent, descent } = metrics;
|
|
161
|
+
const natural = metrics.lineHeight ?? ascent + descent;
|
|
162
|
+
const multiple = lineHeight >= 0 && lineHeight < Infinity ? lineHeight : 1;
|
|
163
|
+
const height = natural * multiple;
|
|
164
|
+
return {
|
|
165
|
+
x,
|
|
166
|
+
y: 0,
|
|
167
|
+
width: 0,
|
|
168
|
+
height,
|
|
169
|
+
baseline: (height - (ascent + descent)) / 2 + ascent,
|
|
170
|
+
ascent,
|
|
171
|
+
descent,
|
|
172
|
+
start: 0,
|
|
173
|
+
end: 0,
|
|
174
|
+
runs: [],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
153
178
|
/** Whether two spans read the same, field for field. */
|
|
154
179
|
function sameSpan(a, b) {
|
|
155
180
|
if (a === b) return true;
|
|
@@ -302,6 +327,19 @@ class CocoaTextLayout {
|
|
|
302
327
|
ctx._drawLayout(this, x, y);
|
|
303
328
|
}
|
|
304
329
|
|
|
330
|
+
/**
|
|
331
|
+
* An empty paragraph as ntk lays it out: one line with no runs, as tall as
|
|
332
|
+
* a line of the face it would have been set in. CoreText sets no line for
|
|
333
|
+
* no text, and a layout with no lines has no height and a caret of none —
|
|
334
|
+
* so an empty field's caret was its three-pixel mark padding either side
|
|
335
|
+
* of nothing, and grew to the line the moment the first letter arrived.
|
|
336
|
+
*/
|
|
337
|
+
_setBlank(line) {
|
|
338
|
+
this.lines = [line];
|
|
339
|
+
this.height = line.height;
|
|
340
|
+
this._blank = line;
|
|
341
|
+
}
|
|
342
|
+
|
|
305
343
|
/** Code point boundary nearest the point, in layout coordinates. */
|
|
306
344
|
indexAt(x, y) {
|
|
307
345
|
return this._cpOf(this._native.layoutIndexAt(this._handle, x, y));
|
|
@@ -309,6 +347,13 @@ class CocoaTextLayout {
|
|
|
309
347
|
|
|
310
348
|
/** Caret rect for a code-point index: { x, y, height }. */
|
|
311
349
|
caretPosition(cp) {
|
|
350
|
+
// the rect the native gives a caret on a line with text in it — the
|
|
351
|
+
// line's box — so typing the first letter moves the caret and nothing
|
|
352
|
+
// else
|
|
353
|
+
const blank = this._blank;
|
|
354
|
+
if (blank) {
|
|
355
|
+
return { x: blank.x, y: blank.y, height: blank.height, line: 0 };
|
|
356
|
+
}
|
|
312
357
|
return this._native.layoutCaret(this._handle, this._cuOf(cp));
|
|
313
358
|
}
|
|
314
359
|
|
|
@@ -1315,6 +1360,24 @@ export class CocoaFontManager {
|
|
|
1315
1360
|
minContent ? laid.map((span) => span.text).join('') : text,
|
|
1316
1361
|
);
|
|
1317
1362
|
layout._contextInk = contextInk;
|
|
1363
|
+
if (!text) {
|
|
1364
|
+
const first = spans[0] ?? {};
|
|
1365
|
+
const size = first.size ?? base.size ?? 14;
|
|
1366
|
+
const face = first.font ?? base.font;
|
|
1367
|
+
const metrics = (
|
|
1368
|
+
typeof face?.metrics === 'function'
|
|
1369
|
+
? face
|
|
1370
|
+
: this.match(first.family ?? base.family, {
|
|
1371
|
+
weight: first.weight ?? base.weight,
|
|
1372
|
+
style: first.style ?? base.style,
|
|
1373
|
+
})
|
|
1374
|
+
).metrics(size);
|
|
1375
|
+
const container =
|
|
1376
|
+
Number.isFinite(maxWidth) && maxWidth > 0 ? maxWidth : 0;
|
|
1377
|
+
layout._setBlank(
|
|
1378
|
+
blankLine(metrics, lineHeight, flushFor(align, direction) * container),
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1318
1381
|
if (!minContent) {
|
|
1319
1382
|
const runs = new Map();
|
|
1320
1383
|
layout._hangRuns(sources, (i, rtl) => {
|
package/src/events.js
CHANGED
|
@@ -647,6 +647,12 @@ export class EventManager {
|
|
|
647
647
|
// …and the focused node hears the focus group's answer, not this
|
|
648
648
|
// window's, whichever of them the X focus just moved between
|
|
649
649
|
manager._syncDefaultFocus();
|
|
650
|
+
// …and so does hover, where the platform keeps it to the active window:
|
|
651
|
+
// this window's, and the top-level's whose answer it is — a dialog
|
|
652
|
+
// taking the keyboard is what puts its owner's hover back
|
|
653
|
+
const top = this.topLevelManager;
|
|
654
|
+
this._syncHover(native);
|
|
655
|
+
if (top !== this) top._syncHover(native);
|
|
650
656
|
if (!changed) return;
|
|
651
657
|
// …and the things that keep a window of their own open on the strength
|
|
652
658
|
// of this one having focus — a menu, a dropdown — which the focused node
|
|
@@ -1014,6 +1020,12 @@ export class EventManager {
|
|
|
1014
1020
|
inspectHandler('move', this._hit(native), native);
|
|
1015
1021
|
return;
|
|
1016
1022
|
}
|
|
1023
|
+
// a window the pointer does not hover in hears no motion (`hoverLive`);
|
|
1024
|
+
// a gesture that began in it is the pointer's own business and runs on
|
|
1025
|
+
if (!this.hoverLive && !this._gesture()) {
|
|
1026
|
+
this._dropHover(native);
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1017
1029
|
runWithPriority(ContinuousEventPriority, () => {
|
|
1018
1030
|
// an active drag owns the pointer: drag-path diffing replaces hover,
|
|
1019
1031
|
// onDrag replaces mousemove. Below the threshold this falls through.
|
|
@@ -1088,6 +1100,77 @@ export class EventManager {
|
|
|
1088
1100
|
return isFocusable(node);
|
|
1089
1101
|
}
|
|
1090
1102
|
|
|
1103
|
+
/**
|
|
1104
|
+
* Whether the pointer hovers in this window at all.
|
|
1105
|
+
*
|
|
1106
|
+
* Nothing in CSS or in the DOM's events ties hover to focus: `:hover` is
|
|
1107
|
+
* "while the user designates an element with a pointing device", and a
|
|
1108
|
+
* `mousemove` is owed whenever the pointer moves over an element. What
|
|
1109
|
+
* ties them is the platform. On macOS a window that is not the active one
|
|
1110
|
+
* does not track the pointer — AppKit's own controls roll over in the key
|
|
1111
|
+
* window alone, and a browser there drops the moves over a window that is
|
|
1112
|
+
* neither key nor main and tells the page the pointer left — while on
|
|
1113
|
+
* X11, Wayland and Windows every toolkit hovers in a window that does not
|
|
1114
|
+
* have the keyboard, and with focus-follows-mouse the question hardly
|
|
1115
|
+
* arises. So the backend says which it is (`app.hoverNeedsActiveWindow`),
|
|
1116
|
+
* and only the Cocoa one does.
|
|
1117
|
+
*
|
|
1118
|
+
* It is also half enforced there already, which is how this was found:
|
|
1119
|
+
* a cursor set by an application that is not the active one is ignored by
|
|
1120
|
+
* the window server, so a link in a background window lit up and the
|
|
1121
|
+
* pointer over it stayed an arrow.
|
|
1122
|
+
*
|
|
1123
|
+
* "Active" is the top-level's answer for everything sharing its focus
|
|
1124
|
+
* (`keyboardFocused`), so a window whose dialog has the keyboard still
|
|
1125
|
+
* hovers, as a main window does under a key panel. A popup nothing
|
|
1126
|
+
* manages — a menu, a dropdown, a popover under a tray item — is on the
|
|
1127
|
+
* screen because the user just asked for it, in an application that may
|
|
1128
|
+
* have no active window at all, and always hovers.
|
|
1129
|
+
*/
|
|
1130
|
+
get hoverLive() {
|
|
1131
|
+
if (!this.node.app?.hoverNeedsActiveWindow) return true;
|
|
1132
|
+
if (this.node.isPopup && this.node.attributes?.overrideRedirect === true) {
|
|
1133
|
+
return true;
|
|
1134
|
+
}
|
|
1135
|
+
return this.topLevelManager.keyboardFocused;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/** A press, a capture or a drag holds the pointer. */
|
|
1139
|
+
_gesture() {
|
|
1140
|
+
return Boolean(this._captured() || this.downNode || this._dragArmed);
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* The pointer is over the window and hovers nothing in it: what its
|
|
1145
|
+
* leaving does to hover, less the leaving. `_pointer` stays, so the hover
|
|
1146
|
+
* comes back at that point when the window does (`_syncHover`), and
|
|
1147
|
+
* `<window onMouseOut>` is not called, since the pointer is still in it.
|
|
1148
|
+
*/
|
|
1149
|
+
_dropHover(native) {
|
|
1150
|
+
if (!this.hoverPath.length && !this.attentionNode) return;
|
|
1151
|
+
runWithPriority(ContinuousEventPriority, () => {
|
|
1152
|
+
this._updateHover([], native);
|
|
1153
|
+
if (this._attentionNodes.size !== 0) {
|
|
1154
|
+
this._attentionSamples.length = 0;
|
|
1155
|
+
this._setAttention(null, native);
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
/**
|
|
1161
|
+
* The keyboard moved between windows: hover goes with it where the
|
|
1162
|
+
* platform has it follow (`hoverLive`). A window that stopped being the
|
|
1163
|
+
* active one drops its hover as if the pointer had left; one that became
|
|
1164
|
+
* it is asked what is under the pointer, which did not move to say so.
|
|
1165
|
+
* Not during a gesture, which keeps what it has until it ends.
|
|
1166
|
+
*/
|
|
1167
|
+
_syncHover(native) {
|
|
1168
|
+
if (!this.node.app?.hoverNeedsActiveWindow) return;
|
|
1169
|
+
if (this.node.destroyed || this._gesture()) return;
|
|
1170
|
+
if (this.hoverLive) this.refreshHover();
|
|
1171
|
+
else this._dropHover({ ...native, ...this._pointer });
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1091
1174
|
_onMouseOut(native) {
|
|
1092
1175
|
// The pointer went into one of this window's surfaces, not out of the
|
|
1093
1176
|
// window: X reports crossing into a child window as leaving this one,
|
|
@@ -1211,7 +1294,7 @@ export class EventManager {
|
|
|
1211
1294
|
refreshHover() {
|
|
1212
1295
|
const at = this._pointer;
|
|
1213
1296
|
if (!at || inspectHandler || this.node.destroyed) return;
|
|
1214
|
-
if (this.
|
|
1297
|
+
if (this._gesture() || !this.hoverLive) return;
|
|
1215
1298
|
runWithPriority(ContinuousEventPriority, () => {
|
|
1216
1299
|
const target = this._hit(at);
|
|
1217
1300
|
const path = this._path(target);
|
package/src/frame/index.js
CHANGED
|
@@ -618,6 +618,11 @@ function PaneHostView({
|
|
|
618
618
|
onMouseDown: forward('mousedown', (ev) => ({ keycode: ev.button ?? 1 })),
|
|
619
619
|
onMouseUp: forward('mouseup', (ev) => ({ keycode: ev.button ?? 1 })),
|
|
620
620
|
onMouseMove: forward('mousemove', () => ({})),
|
|
621
|
+
// …and the pointer leaving, which the pane has no window to hear for
|
|
622
|
+
// itself: without it what the pointer last crossed in the pane stayed
|
|
623
|
+
// hovered once it was over the host, or the host was no longer the
|
|
624
|
+
// active window (`EventManager.hoverLive`)
|
|
625
|
+
onMouseLeave: forward('mouseout', () => ({})),
|
|
621
626
|
// The host's event is in pixels — events.js turned each notch into
|
|
622
627
|
// WHEEL_NOTCH_PX of them — and the pane's window turns what it is sent
|
|
623
628
|
// into pixels again, so it is sent notches: sent the pixels, one notch
|