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.
@@ -0,0 +1,161 @@
1
+ // Native control bezels — AppKit's own pixels for the core controls, cached
2
+ // as surfaces the ordinary 2d paint path can blit (docs/macos.md §Native
3
+ // controls). The mechanism is offscreen NSCell/NSControl rendering, the
4
+ // WebKit/Gecko form-control technique: interaction, focus and keyboard stay
5
+ // the shared component implementation, and only the *bezel* — the pixels of
6
+ // the well, the track, the button face — is asked of the system.
7
+ //
8
+ // Everything here is measured in points at the native boundary and delivered
9
+ // in device pixels, the same split the rest of the backend keeps.
10
+ //
11
+ // ## The ink box
12
+ //
13
+ // AppKit cells draw inside margins of their own: a push button cell insets
14
+ // its bezel ~6pt each side, a checkbox floats its 16pt well in an 18pt
15
+ // frame. Component layout wants the *visible* control — a well that is 16px
16
+ // is 16px in the row it sits in — so the store scans each (kind, size)'s
17
+ // alpha bounding box once, and every bezel afterwards is rendered into a
18
+ // frame padded back out by those insets and blitted from the ink region.
19
+ // Scanned rather than hard-coded so a macOS release that redraws its
20
+ // controls moves the answer instead of breaking it.
21
+ export class BezelStore {
22
+ constructor(native) {
23
+ this._native = native;
24
+ this._canonical = new Map(); // kind|size|scale → { insets, natural }
25
+ this._cache = new Map(); // full param key → { surface, sx, sy, sw, sh }
26
+ this._MAX = 160;
27
+ }
28
+
29
+ /**
30
+ * The control's natural size in logical px — the size the bezel is
31
+ * designed at, which layout adopts for the kinds that must not stretch
32
+ * (checkbox, radio, switch). For the stretchable kinds only `height` is
33
+ * meaningful: a push button is as wide as its label needs.
34
+ */
35
+ natural(kind, controlSize = 'regular') {
36
+ const c = this._scan(kind, controlSize, 2);
37
+ return {
38
+ width: Math.round(c.natural.width),
39
+ height: Math.round(c.natural.height),
40
+ };
41
+ }
42
+
43
+ /**
44
+ * The bezel for one laid-out box: `w`/`h` in device px, blit-ready.
45
+ * Returns `{ surface, sx, sy, sw, sh }` — draw with the 9-arg
46
+ * `ctx.drawImage` so the ink region lands exactly on the box.
47
+ */
48
+ get(params, w, h, scale) {
49
+ const controlSize = params.controlSize ?? 'regular';
50
+ const c = this._scan(params.kind, controlSize, scale);
51
+ const key = JSON.stringify([
52
+ params.kind,
53
+ controlSize,
54
+ params.state ?? 0,
55
+ params.pressed ?? false,
56
+ params.enabled ?? true,
57
+ params.isDefault ?? false,
58
+ params.value,
59
+ params.appearance,
60
+ w,
61
+ h,
62
+ scale,
63
+ ]);
64
+ let entry = this._cache.get(key);
65
+ if (entry) {
66
+ // Map order is the recency order: re-inserting keeps the hot bezels
67
+ // at the young end when the cache is over budget.
68
+ this._cache.delete(key);
69
+ this._cache.set(key, entry);
70
+ return entry;
71
+ }
72
+ const fw = w / scale + c.insets.left + c.insets.right;
73
+ const fh = h / scale + c.insets.top + c.insets.bottom;
74
+ const surface = this._native.createSurface(
75
+ Math.max(1, Math.round(fw * scale)),
76
+ Math.max(1, Math.round(fh * scale)),
77
+ scale,
78
+ );
79
+ this._native.drawControlIntoSurface(surface, {
80
+ ...params,
81
+ controlSize,
82
+ });
83
+ entry = {
84
+ surface,
85
+ sx: Math.round(c.insets.left * scale),
86
+ sy: Math.round(c.insets.top * scale),
87
+ sw: w,
88
+ sh: h,
89
+ };
90
+ this._cache.set(key, entry);
91
+ if (this._cache.size > this._MAX) {
92
+ // eldest first; the surface itself is freed by its External finalizer
93
+ const oldest = this._cache.keys().next().value;
94
+ this._cache.delete(oldest);
95
+ }
96
+ return entry;
97
+ }
98
+
99
+ /**
100
+ * One render + alpha scan per (kind, size, scale): where does this cell
101
+ * actually put ink inside the frame it is given? The frame is the natural
102
+ * cellSize widened by 24pt so a stretchable bezel's side margins are
103
+ * visible as margins rather than crowding the ends.
104
+ */
105
+ _scan(kind, controlSize, scale) {
106
+ const key = `${kind}|${controlSize}|${scale}`;
107
+ let c = this._canonical.get(key);
108
+ if (c) return c;
109
+ const m = this._native.measureControl({ kind, controlSize });
110
+ const fw = m.width + 24;
111
+ const fh = m.height;
112
+ const pw = Math.max(1, Math.round(fw * scale));
113
+ const ph = Math.max(1, Math.round(fh * scale));
114
+ const surface = this._native.createSurface(pw, ph, scale);
115
+ this._native.drawControlIntoSurface(surface, {
116
+ kind,
117
+ controlSize,
118
+ // the fullest state, so the scan sees the whole footprint
119
+ state: 1,
120
+ value: 0.5,
121
+ enabled: true,
122
+ appearance: 'light',
123
+ });
124
+ const buf = this._native.ctxGetImageData(surface, 0, 0, pw, ph);
125
+ let x0 = pw;
126
+ let y0 = ph;
127
+ let x1 = -1;
128
+ let y1 = -1;
129
+ for (let y = 0; y < ph; y++) {
130
+ for (let x = 0; x < pw; x++) {
131
+ if (buf[(y * pw + x) * 4 + 3] > 8) {
132
+ if (x < x0) x0 = x;
133
+ if (x > x1) x1 = x;
134
+ if (y < y0) y0 = y;
135
+ if (y > y1) y1 = y;
136
+ }
137
+ }
138
+ }
139
+ if (x1 < 0) {
140
+ // nothing painted (should not happen) — treat the frame as the ink
141
+ x0 = 0;
142
+ y0 = 0;
143
+ x1 = pw - 1;
144
+ y1 = ph - 1;
145
+ }
146
+ c = {
147
+ insets: {
148
+ left: x0 / scale,
149
+ top: y0 / scale,
150
+ right: fw - (x1 + 1) / scale,
151
+ bottom: fh - (y1 + 1) / scale,
152
+ },
153
+ natural: {
154
+ width: (x1 - x0 + 1) / scale,
155
+ height: (y1 - y0 + 1) / scale,
156
+ },
157
+ };
158
+ this._canonical.set(key, c);
159
+ return c;
160
+ }
161
+ }