react-x11 2.10.2 → 2.12.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 +278 -129
- package/package.json +10 -3
- package/src/Reconciler.js +15 -17
- package/src/a11y.js +2 -2
- package/src/anchor.js +7 -5
- package/src/bootstrap.js +14 -0
- package/src/clientmessage.js +1 -1
- package/src/cocoa/app.js +304 -49
- package/src/cocoa/bezels.js +175 -30
- package/src/cocoa/dnd.js +27 -13
- package/src/cocoa/fonts.js +3 -3
- package/src/cocoa/glarea.js +20 -3
- package/src/cocoa/main.d.ts +8 -0
- package/src/cocoa/main.js +43 -0
- package/src/cocoa/panehost.js +15 -5
- package/src/cocoa/presenter.js +13 -9
- package/src/cocoa/promotion.js +4 -7
- package/src/cocoa/relaunch.js +207 -0
- package/src/cocoa/screencolor.js +62 -0
- package/src/cocoa/threaded.js +246 -0
- package/src/cocoa/window.js +256 -42
- package/src/components/Select.js +2 -2
- package/src/components/anchor.js +3 -3
- package/src/components/native.js +12 -7
- package/src/components/theme.js +2 -2
- package/src/debug.js +1 -1
- package/src/decorations.js +1 -1
- package/src/editmenu.js +2 -2
- package/src/errors.js +46 -0
- package/src/events.js +6 -6
- package/src/foreignnodes.js +3 -2
- package/src/frames.js +2 -2
- package/src/glnodes.js +1 -1
- package/src/grid.js +1653 -0
- package/src/host.d.ts +230 -0
- package/src/host.js +11 -3
- package/src/imagesource.js +1 -1
- package/src/index.d.ts +21 -4
- package/src/index.js +9 -1
- package/src/layouts.js +721 -0
- package/src/node.d.ts +4 -2
- package/src/node.js +19 -21
- package/src/nodes/animation.js +644 -0
- package/src/nodes/box.js +21 -0
- package/src/nodes/boxpaint.js +473 -0
- package/src/nodes/canvas.js +269 -0
- package/src/nodes/cascade.js +600 -0
- package/src/nodes/damage.js +183 -0
- package/src/nodes/edithistory.js +124 -0
- package/src/nodes/editmenupopup.js +260 -0
- package/src/nodes/hittest.js +185 -0
- package/src/nodes/image.js +266 -0
- package/src/nodes/install.js +75 -0
- package/src/nodes/invalidate.js +465 -0
- package/src/nodes/kinds.js +31 -0
- package/src/nodes/layout.js +439 -0
- package/src/nodes/layouthost.js +949 -0
- package/src/nodes/node.js +868 -0
- package/src/nodes/paint.js +466 -0
- package/src/nodes/position.js +366 -0
- package/src/nodes/preedit.js +127 -0
- package/src/nodes/queries.js +330 -0
- package/src/nodes/rects.js +102 -0
- package/src/nodes/scrollable.js +891 -0
- package/src/nodes/scrollbars.js +138 -0
- package/src/nodes/scrollblit.js +1034 -0
- package/src/nodes/selectable.js +142 -0
- package/src/nodes/styling.js +225 -0
- package/src/nodes/text.js +649 -0
- package/src/nodes/textarea.js +391 -0
- package/src/nodes/textinput.js +1146 -0
- package/src/nodes/util.js +17 -0
- package/src/nodes/window/anchoring.js +161 -0
- package/src/nodes/window/capabilities.js +190 -0
- package/src/nodes/window/debugpaint.js +83 -0
- package/src/nodes/window/droptarget.js +145 -0
- package/src/nodes/window/floors.js +577 -0
- package/src/nodes/window/flush.js +334 -0
- package/src/nodes/window/hints.js +482 -0
- package/src/nodes/window/listeners.js +222 -0
- package/src/nodes/window/popup.js +71 -0
- package/src/nodes/window/size.js +591 -0
- package/src/nodes/window/window.js +945 -0
- package/src/palette.js +1 -1
- package/src/registry.js +7 -3
- package/src/screencolor.js +212 -38
- package/src/screencolorhooks.js +6 -2
- package/src/styles.js +137 -15
- package/src/svgnodes.js +2 -1
- package/src/testing/harness.js +2 -2
- package/src/textselection.js +5 -3
- package/src/trace-registry.js +1 -1
- package/src/types/components.d.ts +38 -6
- package/src/types/elements.d.ts +11 -1
- package/src/types/nodes.d.ts +33 -5
- package/src/types/screencolor.d.ts +20 -14
- package/src/types/style.d.ts +94 -3
- package/src/windowstate.js +1 -1
- package/src/yoga.js +1 -1
- package/src/nodes.js +0 -13120
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
// Layout: yoga's measure seam for a node's content, and the walk that turns
|
|
2
|
+
// yoga's offsets into absolute rects each pass (`absolutize`), reporting
|
|
3
|
+
// what moved to onLayout and to the layout diff.
|
|
4
|
+
|
|
5
|
+
import { Yoga } from '../yoga.js';
|
|
6
|
+
import { callHandler } from '../errors.js';
|
|
7
|
+
import { DAMAGE_SLOP, layoutDiff } from './damage.js';
|
|
8
|
+
import { insetRect } from './rects.js';
|
|
9
|
+
import { DEV } from './util.js';
|
|
10
|
+
|
|
11
|
+
export const MEASURE_MODES = [];
|
|
12
|
+
MEASURE_MODES[Yoga.MEASURE_MODE_UNDEFINED] = 'unconstrained';
|
|
13
|
+
MEASURE_MODES[Yoga.MEASURE_MODE_EXACTLY] = 'exactly';
|
|
14
|
+
MEASURE_MODES[Yoga.MEASURE_MODE_AT_MOST] = 'at-most';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The pixels on offer on one axis, as a number an element can do arithmetic
|
|
18
|
+
* with. Yoga says "no bound" with a null, so `Math.min(preferred, width)`
|
|
19
|
+
* would answer 0 to the one question where the honest answer is `preferred`;
|
|
20
|
+
* `Infinity` is what "no bound" means in that expression, and it makes the
|
|
21
|
+
* mode something an element consults only when it has a reason to.
|
|
22
|
+
*/
|
|
23
|
+
export function measureOffer(value, mode) {
|
|
24
|
+
return mode === Yoga.MEASURE_MODE_UNDEFINED || !Number.isFinite(value)
|
|
25
|
+
? Infinity
|
|
26
|
+
: value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** What a measure function answered, for the error that rejects it. */
|
|
30
|
+
export function describeSize(size) {
|
|
31
|
+
if (size === null || typeof size !== 'object') return String(size);
|
|
32
|
+
return `{ width: ${size.width}, height: ${size.height} }`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Fit a natural size into what layout offered — the shape `<image>`, `<svg>`
|
|
37
|
+
* and any other element whose content has a size of its own and an aspect
|
|
38
|
+
* ratio to keep.
|
|
39
|
+
*
|
|
40
|
+
* **Which axes the style fixed is something layout already knows**, and says
|
|
41
|
+
* in the measure modes: `'exactly'` on an axis means the style made it
|
|
42
|
+
* definite. Working it out a second time by reading style back would be
|
|
43
|
+
* duplication; working it out by reading *props* was issue #118 — `width` is
|
|
44
|
+
* a style name, so `<image width={40}>` throws in development and only ever
|
|
45
|
+
* reached that branch in production.
|
|
46
|
+
*
|
|
47
|
+
* - Both fixed: layout skips the measure entirely, so there is no
|
|
48
|
+
* fixed-size case to write here.
|
|
49
|
+
* - Height fixed alone: scale the width with it, the way an `<img>` with
|
|
50
|
+
* only a height set does, rather than stretching to the container.
|
|
51
|
+
* - Otherwise: natural size, shrunk to the width on offer, height following
|
|
52
|
+
* the aspect ratio.
|
|
53
|
+
*
|
|
54
|
+
* @param {{width: number, height: number}} natural the content's own size
|
|
55
|
+
* @param {MeasureConstraints} constraints the argument of `measureContent`
|
|
56
|
+
*/
|
|
57
|
+
export function intrinsicSize(
|
|
58
|
+
natural,
|
|
59
|
+
{ width, height, widthMode, heightMode },
|
|
60
|
+
) {
|
|
61
|
+
const { width: natW, height: natH } = natural;
|
|
62
|
+
if (heightMode === 'exactly' && widthMode !== 'exactly' && natH > 0) {
|
|
63
|
+
return { width: (height * natW) / natH, height };
|
|
64
|
+
}
|
|
65
|
+
const w = width < natW ? width : natW;
|
|
66
|
+
return { width: w, height: natW > 0 ? (w * natH) / natW : natH };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Where layout put `node` inside its parent's border box — yoga's offset,
|
|
71
|
+
* plus, for a child a layout algorithm placed, the slot it was placed in
|
|
72
|
+
* (its own yoga tree is a root, and a root's offset is only its margin).
|
|
73
|
+
* The one sum `absolutize`, `onLayout` and `scrollIntoView` all make.
|
|
74
|
+
*/
|
|
75
|
+
export function offsetInParent(node) {
|
|
76
|
+
const slot =
|
|
77
|
+
node.parent !== null && node.parent._host !== null ? node._hostSlot : null;
|
|
78
|
+
const yoga = node.yoga;
|
|
79
|
+
return {
|
|
80
|
+
x: yoga.getComputedLeft() + (slot === null ? 0 : slot.x),
|
|
81
|
+
y: yoga.getComputedTop() + (slot === null ? 0 : slot.y),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Layout, installed onto `Node.prototype` by node.js. */
|
|
86
|
+
export class NodeLayout {
|
|
87
|
+
/**
|
|
88
|
+
* Give this node's box a measure function, keeping a reference that can be
|
|
89
|
+
* asked again later.
|
|
90
|
+
*
|
|
91
|
+
* A leaf's content is recorded nowhere but in its measure function, and
|
|
92
|
+
* the size yoga keeps for it is not always what that function said:
|
|
93
|
+
* `align-items` defaults to `stretch`, so in a pass run with no room on
|
|
94
|
+
* offer — which is how a content floor is measured, see `contentSpan` —
|
|
95
|
+
* the cross size a leaf ends up at is the container's, not its own. A
|
|
96
|
+
* container in that position is recovered by looking inside it. A leaf
|
|
97
|
+
* has nothing inside, so it is asked again instead.
|
|
98
|
+
*/
|
|
99
|
+
_setMeasureFunc(measure) {
|
|
100
|
+
this._measureFn = measure;
|
|
101
|
+
this.yoga.setMeasureFunc(measure);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The height this leaf takes at `width` with nothing bounding its height
|
|
106
|
+
* — the answer yoga gets from the measuring pass that settles the height
|
|
107
|
+
* floors, asked directly. `probeHeightFloors` asks it twice, for the width
|
|
108
|
+
* a leaf was measured at and the one it has now, to find out whether a
|
|
109
|
+
* relayout that moved the leaf changed what it needs; a paragraph answers
|
|
110
|
+
* from its layout cache, and the elements whose height is not a function
|
|
111
|
+
* of their width at all answer at once.
|
|
112
|
+
*/
|
|
113
|
+
_heightForWidth(width) {
|
|
114
|
+
if (this._host !== null) {
|
|
115
|
+
return this._measureHost(
|
|
116
|
+
width,
|
|
117
|
+
this._floorMeasureMode === 'exactly'
|
|
118
|
+
? Yoga.MEASURE_MODE_EXACTLY
|
|
119
|
+
: Yoga.MEASURE_MODE_AT_MOST,
|
|
120
|
+
Number.NaN,
|
|
121
|
+
Yoga.MEASURE_MODE_UNDEFINED,
|
|
122
|
+
).height;
|
|
123
|
+
}
|
|
124
|
+
return this.measureContent({
|
|
125
|
+
width,
|
|
126
|
+
height: Infinity,
|
|
127
|
+
widthMode: this._floorMeasureMode ?? 'at-most',
|
|
128
|
+
heightMode: 'unconstrained',
|
|
129
|
+
})?.height;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Hand `measureContent` to layout, translated: the modes arrive as words,
|
|
134
|
+
* an axis with no bound arrives as `Infinity` rather than as yoga's null,
|
|
135
|
+
* and what comes back is checked before it can turn a whole tree into
|
|
136
|
+
* NaNs. Called by the constructor, so an element only writes the method.
|
|
137
|
+
*/
|
|
138
|
+
_useMeasureContent() {
|
|
139
|
+
this._setMeasureFunc((width, widthMode, height, heightMode) => {
|
|
140
|
+
if (heightMode === Yoga.MEASURE_MODE_UNDEFINED) {
|
|
141
|
+
this._floorMeasureMode = MEASURE_MODES[widthMode];
|
|
142
|
+
}
|
|
143
|
+
const size = this.measureContent({
|
|
144
|
+
width: measureOffer(width, widthMode),
|
|
145
|
+
height: measureOffer(height, heightMode),
|
|
146
|
+
widthMode: MEASURE_MODES[widthMode],
|
|
147
|
+
heightMode: MEASURE_MODES[heightMode],
|
|
148
|
+
});
|
|
149
|
+
if (!Number.isFinite(size?.width) || !Number.isFinite(size?.height)) {
|
|
150
|
+
// Left to itself this is a destructuring TypeError from inside
|
|
151
|
+
// yoga's wrapper, or — worse, because it does not throw at all — a
|
|
152
|
+
// NaN that spreads through every ancestor's rect.
|
|
153
|
+
throw new Error(
|
|
154
|
+
`react-x11: <${this.kind}>.measureContent() must return ` +
|
|
155
|
+
'{ width, height } as finite numbers; it returned ' +
|
|
156
|
+
`${describeSize(size)}. Return { width: 0, height: 0 } for ` +
|
|
157
|
+
'content that has not arrived yet.',
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return size;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* The inputs to `measureContent` changed — a prop it reads, data that
|
|
166
|
+
* loaded — so the next layout has to ask again instead of reusing the
|
|
167
|
+
* answer it cached.
|
|
168
|
+
*
|
|
169
|
+
* `reason` joins the closed set the diagnostics print (docs/debugging.md);
|
|
170
|
+
* the default says the measurement itself moved.
|
|
171
|
+
*/
|
|
172
|
+
invalidateMeasure(reason = 'measure') {
|
|
173
|
+
// Nothing to re-ask, and both halves matter: layout aborts the process
|
|
174
|
+
// on a node that never had a measure function, and a destroyed node's
|
|
175
|
+
// box has already been freed under it.
|
|
176
|
+
if (this.destroyed || !this._measureFn) {
|
|
177
|
+
// Said once, in development: an element that asks for a re-measure and
|
|
178
|
+
// silently gets none looks broken rather than degraded, and there is
|
|
179
|
+
// nothing in the frame to pull on.
|
|
180
|
+
if (DEV && !this.destroyed && !this._measureNagged) {
|
|
181
|
+
this._measureNagged = true;
|
|
182
|
+
console.warn(
|
|
183
|
+
`react-x11: <${this.kind}>.invalidateMeasure() has nothing to ` +
|
|
184
|
+
're-measure — this element implements no measureContent(). ' +
|
|
185
|
+
'Note it has to be a method on the class: assigning it in the ' +
|
|
186
|
+
'constructor is too late, since the base Node constructor is ' +
|
|
187
|
+
'what wires it to layout.',
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
this.yoga.markDirty();
|
|
193
|
+
this._invalidateLayout(reason);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
absolutize(originX, originY) {
|
|
197
|
+
// before the yoga check, so a span — placed by its paragraph, no box of
|
|
198
|
+
// its own — counts as on screen too
|
|
199
|
+
this._placed = true;
|
|
200
|
+
if (!this.yoga) return;
|
|
201
|
+
this._assignAbs(
|
|
202
|
+
originX + this.yoga.getComputedLeft(),
|
|
203
|
+
originY + this.yoga.getComputedTop(),
|
|
204
|
+
this.yoga.getComputedWidth(),
|
|
205
|
+
this.yoga.getComputedHeight(),
|
|
206
|
+
);
|
|
207
|
+
if (this.props.onLayout) this._reportLayout();
|
|
208
|
+
if (this._host !== null) {
|
|
209
|
+
this._absolutizeHostChildren();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
for (const child of this.children) {
|
|
213
|
+
if (!child.isWindow) child.absolutize(this.abs.x, this.abs.y);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* `onLayout`: the rect a layout pass gave this node, reported when it
|
|
219
|
+
* changed — React Native's contract, and the seam for a decision that is
|
|
220
|
+
* not a style (docs/react-features.md): how many columns to build, which
|
|
221
|
+
* component to render. Where the decision *is* a style, a container
|
|
222
|
+
* query answers it in the same frame instead (docs/styling.md).
|
|
223
|
+
*
|
|
224
|
+
* `x`/`y` are the position **within the parent as laid out** — yoga's
|
|
225
|
+
* answer, which a scroll does not move — rather than the window
|
|
226
|
+
* coordinates `abs` holds: a list scrolling under the pointer must not
|
|
227
|
+
* re-render every row on every notch. Logical pixels, this node's own,
|
|
228
|
+
* the same division `measure()` makes.
|
|
229
|
+
*
|
|
230
|
+
* Deferred, like `onViewport`: this runs inside the layout pass, and a
|
|
231
|
+
* `setState` from the handler would re-enter it. One report per frame,
|
|
232
|
+
* because `absolutize` runs once, after the container blocks have
|
|
233
|
+
* settled — a card whose block changed its height reports the height it
|
|
234
|
+
* ended the frame at, not the one it had between passes.
|
|
235
|
+
*/
|
|
236
|
+
_reportLayout() {
|
|
237
|
+
const s = this.scale;
|
|
238
|
+
const offset = offsetInParent(this);
|
|
239
|
+
const next = {
|
|
240
|
+
x: offset.x / s,
|
|
241
|
+
y: offset.y / s,
|
|
242
|
+
width: this.abs.width / s,
|
|
243
|
+
height: this.abs.height / s,
|
|
244
|
+
};
|
|
245
|
+
const last = this._lastLayout;
|
|
246
|
+
if (
|
|
247
|
+
last &&
|
|
248
|
+
last.x === next.x &&
|
|
249
|
+
last.y === next.y &&
|
|
250
|
+
last.width === next.width &&
|
|
251
|
+
last.height === next.height
|
|
252
|
+
) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
this._lastLayout = next;
|
|
256
|
+
setImmediate(() => {
|
|
257
|
+
// the handler as it is *now*: React may have re-rendered in between
|
|
258
|
+
const notify = this.props.onLayout;
|
|
259
|
+
if (this.destroyed || !notify) return;
|
|
260
|
+
callHandler(this, 'onLayout', notify, next);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* absolutize's write to `abs`, funneled through one place so a bounded
|
|
266
|
+
* frame's layout diff sees every node the pass actually moved or resized.
|
|
267
|
+
* The old and new rects are claimed separately (not their union box —
|
|
268
|
+
* a node crossing the window would drag everything between them along),
|
|
269
|
+
* each grown by this node's own paint reach. A rect that was or became
|
|
270
|
+
* zero-area claims nothing: there were, or will be, no pixels there.
|
|
271
|
+
*/
|
|
272
|
+
_assignAbs(x, y, width, height) {
|
|
273
|
+
const old = this.abs;
|
|
274
|
+
if (
|
|
275
|
+
old.x === x &&
|
|
276
|
+
old.y === y &&
|
|
277
|
+
old.width === width &&
|
|
278
|
+
old.height === height
|
|
279
|
+
) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
this.abs = { x, y, width, height };
|
|
283
|
+
// moving or resizing changes where this subtree can be hit, and the
|
|
284
|
+
// cached unions all the way up with it
|
|
285
|
+
this._clearHitBounds();
|
|
286
|
+
if (layoutDiff.sink) {
|
|
287
|
+
const grow = this._outlineExtent() + DAMAGE_SLOP;
|
|
288
|
+
const shift = layoutDiff.shift;
|
|
289
|
+
const had = old.width > 0 && old.height > 0;
|
|
290
|
+
if (shift) {
|
|
291
|
+
// Riding a blit (issue #398): the rect this node *would* have had if
|
|
292
|
+
// nothing but the scroll had happened. Landing there is the blit's
|
|
293
|
+
// own translation and claims nothing — claiming it would repaint the
|
|
294
|
+
// band the blit exists to keep. Landing anywhere else is a real move,
|
|
295
|
+
// and both ends of it are claimed in post-blit coordinates, which is
|
|
296
|
+
// where the frame will paint them.
|
|
297
|
+
const was = {
|
|
298
|
+
x: old.x + shift.x,
|
|
299
|
+
y: old.y + shift.y,
|
|
300
|
+
width: old.width,
|
|
301
|
+
height: old.height,
|
|
302
|
+
};
|
|
303
|
+
if (
|
|
304
|
+
had &&
|
|
305
|
+
was.x === x &&
|
|
306
|
+
was.y === y &&
|
|
307
|
+
old.width === width &&
|
|
308
|
+
old.height === height
|
|
309
|
+
) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (had) layoutDiff.sink(insetRect(was, -grow));
|
|
313
|
+
if (width > 0 && height > 0) {
|
|
314
|
+
layoutDiff.sink(insetRect(this.abs, -grow));
|
|
315
|
+
}
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (had) {
|
|
319
|
+
layoutDiff.sink(insetRect(old, -grow));
|
|
320
|
+
}
|
|
321
|
+
if (width > 0 && height > 0) {
|
|
322
|
+
layoutDiff.sink(insetRect(this.abs, -grow));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Move an already-laid-out subtree by a constant, without asking yoga
|
|
329
|
+
* anything — the scroll fast path's walk (issue #405).
|
|
330
|
+
*
|
|
331
|
+
* A pure-scroll frame changes nothing about the arrangement inside a
|
|
332
|
+
* viewport: every descendant sits exactly where the last pass put it,
|
|
333
|
+
* shifted by the scroll delta. `absolutize` would re-derive each rect
|
|
334
|
+
* through four wasm-boundary getters to learn what one addition already
|
|
335
|
+
* says, so the scroller calls this instead — only after proving nothing
|
|
336
|
+
* inside was laid out this pass (see `_absolutizeChildren`).
|
|
337
|
+
*
|
|
338
|
+
* `abs` is adjusted in place rather than replaced: its identity is
|
|
339
|
+
* already long-lived (`_assignAbs` keeps the object whenever a rect is
|
|
340
|
+
* unchanged), and everything that records a rect for later copies it.
|
|
341
|
+
* The cached hit bounds ride along instead of being dropped — a uniform
|
|
342
|
+
* translation is the one change a cached union survives — which keeps a
|
|
343
|
+
* wheel flick from rebuilding the pane's whole hit-bounds tree per notch.
|
|
344
|
+
*
|
|
345
|
+
* No layout diff runs here, and none is owed: under a blit ledger the
|
|
346
|
+
* shifted diff's claims are the *deviations* from exactly this
|
|
347
|
+
* translation, and a subtree nothing laid out again has none.
|
|
348
|
+
*/
|
|
349
|
+
_shiftAbs(dx, dy) {
|
|
350
|
+
if (!this.yoga) return;
|
|
351
|
+
const abs = this.abs;
|
|
352
|
+
abs.x += dx;
|
|
353
|
+
abs.y += dy;
|
|
354
|
+
// the paint reach rides along the same way — unless it *is* `abs`,
|
|
355
|
+
// which just moved
|
|
356
|
+
const p = this._paintBoundsCache;
|
|
357
|
+
if (p && p !== abs) {
|
|
358
|
+
p.x += dx;
|
|
359
|
+
p.y += dy;
|
|
360
|
+
}
|
|
361
|
+
const b = this._hitBoundsCache;
|
|
362
|
+
if (b) {
|
|
363
|
+
b.left += dx;
|
|
364
|
+
b.right += dx;
|
|
365
|
+
b.top += dy;
|
|
366
|
+
b.bottom += dy;
|
|
367
|
+
}
|
|
368
|
+
this._shiftChildren(dx, dy);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/** Split from `_shiftAbs` so a scroller can reroute its children through
|
|
372
|
+
* its own offset bookkeeping — the box moves rigidly, but the children's
|
|
373
|
+
* origin also carries scroll offsets that may have changed again this
|
|
374
|
+
* same frame (`Scrollable._shiftChildren`). */
|
|
375
|
+
_shiftChildren(dx, dy) {
|
|
376
|
+
for (const child of this.children) {
|
|
377
|
+
if (!child.isWindow) child._shiftAbs(dx, dy);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* A layout-affecting change at this node may change how far the content
|
|
383
|
+
* of an enclosing scroll pane reaches through a route yoga never
|
|
384
|
+
* witnesses — an element that paints its own content growing its extent
|
|
385
|
+
* announces it with `invalidate(true, this, 'scroll')`
|
|
386
|
+
* (docs/extending.md), and no yoga node is dirtied by that. Mark every
|
|
387
|
+
* scroller whose measurement can see this node, so the next pass asks
|
|
388
|
+
* `measureScrollContent` again instead of reusing the cached reach
|
|
389
|
+
* (issue #405). The walk stops where the measurement does: at the first
|
|
390
|
+
* ancestor that clips its children, whose overflow is its own business.
|
|
391
|
+
*/
|
|
392
|
+
_markScrollMeasureDirty() {
|
|
393
|
+
for (let n = this; n; n = n.parent) {
|
|
394
|
+
// only a Scrollable carries the flag; a stale `true` on a box that is
|
|
395
|
+
// not currently a scroller costs nothing and re-measures correctly if
|
|
396
|
+
// its style later makes it one
|
|
397
|
+
if (n._scrollMeasureDirty === false) n._scrollMeasureDirty = true;
|
|
398
|
+
if (n !== this && n.clipsChildren()) return;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* The rectangle this node's **content** goes in — `abs` inset by the
|
|
404
|
+
* border and the padding, in the owning window's coordinates. Every text
|
|
405
|
+
* element in core paints inside it, and so should anything a registered
|
|
406
|
+
* element draws that the padding is meant to hold off.
|
|
407
|
+
*
|
|
408
|
+
* Public (docs/extending.md) because the arithmetic is not reproducible
|
|
409
|
+
* from `this.style`: the insets come off the yoga node, which is where
|
|
410
|
+
* percentages, the per-side overrides and the border widths have already
|
|
411
|
+
* been resolved against this frame's size. An element deriving them from
|
|
412
|
+
* the style bag instead re-implements a resolution order it cannot see,
|
|
413
|
+
* and silently disagrees with `<text>` the day the vocabulary grows
|
|
414
|
+
* another edge — the per-side border widths (#262) were the last one.
|
|
415
|
+
*/
|
|
416
|
+
contentBox() {
|
|
417
|
+
// A node with no yoga node (`{ yoga: false }`) has no resolved insets,
|
|
418
|
+
// so its box is its content box.
|
|
419
|
+
if (!this.yoga) return { ...this.abs };
|
|
420
|
+
const padL =
|
|
421
|
+
this.yoga.getComputedPadding(Yoga.EDGE_LEFT) +
|
|
422
|
+
this.yoga.getComputedBorder(Yoga.EDGE_LEFT);
|
|
423
|
+
const padT =
|
|
424
|
+
this.yoga.getComputedPadding(Yoga.EDGE_TOP) +
|
|
425
|
+
this.yoga.getComputedBorder(Yoga.EDGE_TOP);
|
|
426
|
+
const padR =
|
|
427
|
+
this.yoga.getComputedPadding(Yoga.EDGE_RIGHT) +
|
|
428
|
+
this.yoga.getComputedBorder(Yoga.EDGE_RIGHT);
|
|
429
|
+
const padB =
|
|
430
|
+
this.yoga.getComputedPadding(Yoga.EDGE_BOTTOM) +
|
|
431
|
+
this.yoga.getComputedBorder(Yoga.EDGE_BOTTOM);
|
|
432
|
+
return {
|
|
433
|
+
x: this.abs.x + padL,
|
|
434
|
+
y: this.abs.y + padT,
|
|
435
|
+
width: Math.max(0, this.abs.width - padL - padR),
|
|
436
|
+
height: Math.max(0, this.abs.height - padT - padB),
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
}
|