gesso-core 0.6.19 → 0.6.21
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/CHANGELOG.md +28 -0
- package/dist/{CanvasSurface-tAao5BLh.js → CanvasSurface-BAhcLj2K.js} +985 -223
- package/dist/CanvasSurface-BAhcLj2K.js.map +1 -0
- package/dist/{index-8yhrkCnb.d.ts → index-DR8HAfh3.d.ts} +730 -383
- package/dist/index.d.ts +1 -1
- package/dist/index.js +278 -14
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/package.json +1 -1
- package/dist/CanvasSurface-tAao5BLh.js.map +0 -1
|
@@ -804,6 +804,10 @@ var DirtyNodeSet = class {
|
|
|
804
804
|
isEmpty() {
|
|
805
805
|
return this.nodes.size === 0;
|
|
806
806
|
}
|
|
807
|
+
/** The nodes in the set, without draining it. Not to be changed while walked. */
|
|
808
|
+
values() {
|
|
809
|
+
return this.nodes.values();
|
|
810
|
+
}
|
|
807
811
|
/**
|
|
808
812
|
* Whether any node in the set carries one of `flags`, without
|
|
809
813
|
* draining it.
|
|
@@ -877,9 +881,24 @@ var UiNode = class {
|
|
|
877
881
|
* opacity → 0.5
|
|
878
882
|
* visible → true
|
|
879
883
|
* width → 200
|
|
884
|
+
*
|
|
885
|
+
* Read it directly; write it through `setProperty` and
|
|
886
|
+
* `removeProperty`, which count the change in `propertyVersion`.
|
|
880
887
|
*/
|
|
881
888
|
properties = /* @__PURE__ */ new Map();
|
|
882
889
|
/**
|
|
890
|
+
* Counts writes to `properties`.
|
|
891
|
+
*
|
|
892
|
+
* Lets something derived from a node's own properties tell that it
|
|
893
|
+
* is still right without reading them again. The layout engine
|
|
894
|
+
* resolves forty of them into each record, and on a full pass over a
|
|
895
|
+
* tree where nothing changed that reading was most of a sixth of the
|
|
896
|
+
* pass. A counter is exact where a dirty flag is not: flags are
|
|
897
|
+
* cleared by whichever pass runs next, and a record is resolved by
|
|
898
|
+
* whichever pass reaches it.
|
|
899
|
+
*/
|
|
900
|
+
propertyVersion = 0;
|
|
901
|
+
/**
|
|
883
902
|
* Values a modifier has written over the element's own.
|
|
884
903
|
*
|
|
885
904
|
* Null until the first modifier writes, so a tree without modifiers
|
|
@@ -925,6 +944,13 @@ var UiNode = class {
|
|
|
925
944
|
}
|
|
926
945
|
setProperty(property, value) {
|
|
927
946
|
this.properties.set(property, value);
|
|
947
|
+
this.propertyVersion++;
|
|
948
|
+
}
|
|
949
|
+
/** Removes a property; whether it was there. */
|
|
950
|
+
removeProperty(property) {
|
|
951
|
+
if (!this.properties.delete(property)) return false;
|
|
952
|
+
this.propertyVersion++;
|
|
953
|
+
return true;
|
|
928
954
|
}
|
|
929
955
|
};
|
|
930
956
|
//#endregion
|
|
@@ -1609,6 +1635,7 @@ function resolvedSpansOf(node) {
|
|
|
1609
1635
|
* as soon as the application catches up.
|
|
1610
1636
|
*/
|
|
1611
1637
|
function editableSpansOf(node, text, composition) {
|
|
1638
|
+
if (node.properties.get("secret") === true) return EMPTY_SPANS;
|
|
1612
1639
|
const spanned = spannedTextOf(node);
|
|
1613
1640
|
if (spanned === void 0) return EMPTY_SPANS;
|
|
1614
1641
|
if (spanned.text === text) return spanned.spans;
|
|
@@ -3919,6 +3946,19 @@ const UiProperties = {
|
|
|
3919
3946
|
inherited: false,
|
|
3920
3947
|
affects: 6
|
|
3921
3948
|
}),
|
|
3949
|
+
/**
|
|
3950
|
+
* Whether a scroll container shows its overlay scrollbars: \`'auto'\`
|
|
3951
|
+
* (the default) while scrolling, \`'none'\` never, and then they cannot
|
|
3952
|
+
* be grabbed either. For a container scrolled by something else, as a
|
|
3953
|
+
* picture is moved inside a window onto it, where a bar would claim
|
|
3954
|
+
* the person could scroll it.
|
|
3955
|
+
*/
|
|
3956
|
+
scrollbars: defineProperty({
|
|
3957
|
+
name: "scrollbars",
|
|
3958
|
+
defaultValue: void 0,
|
|
3959
|
+
inherited: false,
|
|
3960
|
+
affects: 6
|
|
3961
|
+
}),
|
|
3922
3962
|
inset: defineProperty({
|
|
3923
3963
|
name: "inset",
|
|
3924
3964
|
defaultValue: void 0,
|
|
@@ -4202,6 +4242,17 @@ const UiProperties = {
|
|
|
4202
4242
|
inherited: false,
|
|
4203
4243
|
affects: 64
|
|
4204
4244
|
}),
|
|
4245
|
+
/**
|
|
4246
|
+
* A password field: every character is drawn as a bullet, copy and
|
|
4247
|
+
* cut take nothing, and the shell edits it through a password input.
|
|
4248
|
+
* The value itself is unchanged. Single-line only.
|
|
4249
|
+
*/
|
|
4250
|
+
secret: defineProperty({
|
|
4251
|
+
name: "secret",
|
|
4252
|
+
defaultValue: void 0,
|
|
4253
|
+
inherited: false,
|
|
4254
|
+
affects: 262
|
|
4255
|
+
}),
|
|
4205
4256
|
/** Colour of the caret; defaults to the text colour. */
|
|
4206
4257
|
caretColor: defineProperty({
|
|
4207
4258
|
name: "caretColor",
|
|
@@ -4949,39 +5000,6 @@ function createFallbackKey(definition) {
|
|
|
4949
5000
|
* no line height is set. Shared by ParagraphLayout and PaintState.
|
|
4950
5001
|
*/
|
|
4951
5002
|
const DEFAULT_LINE_HEIGHT_FACTOR = 1.2;
|
|
4952
|
-
/**
|
|
4953
|
-
* Resolves a node's font the one way layout and paint both use.
|
|
4954
|
-
*
|
|
4955
|
-
* Font size, family, weight and letter spacing inherit through the
|
|
4956
|
-
* environment's text style. Line height is the subtle one: a text
|
|
4957
|
-
* style carries a line height that belongs with its font size, so a
|
|
4958
|
-
* node that inherits the style takes it whole. A node that sets its
|
|
4959
|
-
* own `fontSize` without a `lineHeight` gets a normal line height for
|
|
4960
|
-
* that size, `fontSize × 1.2`, rather than the inherited pixel value
|
|
4961
|
-
* sized for a different font — the inherited 16.8px of the default
|
|
4962
|
-
* style is right for 14px text and wrong for 22px. An explicit
|
|
4963
|
-
* `lineHeight` always wins.
|
|
4964
|
-
*
|
|
4965
|
-
* Before this, layout read the raw props (unset line height → ×1.2)
|
|
4966
|
-
* and paint read the inherited ones (→ 16.8px), so any text whose size
|
|
4967
|
-
* differed from the style's was measured in one line box and drawn in
|
|
4968
|
-
* another, and sat high or low inside it.
|
|
4969
|
-
*/
|
|
4970
|
-
function resolveFont(node) {
|
|
4971
|
-
const out = {
|
|
4972
|
-
fontSize: 0,
|
|
4973
|
-
fontFamily: "",
|
|
4974
|
-
fontWeight: "normal",
|
|
4975
|
-
lineHeight: 0,
|
|
4976
|
-
letterSpacing: 0,
|
|
4977
|
-
fontStyle: "normal",
|
|
4978
|
-
fontStretch: "normal",
|
|
4979
|
-
fontVariant: "normal",
|
|
4980
|
-
fontKerning: "auto"
|
|
4981
|
-
};
|
|
4982
|
-
resolveFontInto(node, out);
|
|
4983
|
-
return out;
|
|
4984
|
-
}
|
|
4985
5003
|
/** `resolveFont`, writing into a target the caller owns. */
|
|
4986
5004
|
function resolveFontInto(node, out) {
|
|
4987
5005
|
const fontSize = positive$1(resolveProperty(node, UiProperties.fontSize)) ?? UiProperties.fontSize.defaultValue;
|
|
@@ -5161,6 +5179,36 @@ var UiGraph = class {
|
|
|
5161
5179
|
this.nodeRemovedListener?.(node);
|
|
5162
5180
|
for (let i = removed.length - 1; i > 0; i--) this.detachNode(removed[i]);
|
|
5163
5181
|
}
|
|
5182
|
+
/**
|
|
5183
|
+
* Moves a child before \`reference\` among its own siblings, or to the
|
|
5184
|
+
* end when that is null. Its parent is unchanged, and so is everything
|
|
5185
|
+
* it inherits, so its subtree is not visited: a lazy list recycling its
|
|
5186
|
+
* rows moves one from end to end on every frame of a scroll, and
|
|
5187
|
+
* re-inheriting walked the whole row each time.
|
|
5188
|
+
*/
|
|
5189
|
+
moveWithin(child, reference) {
|
|
5190
|
+
const parent = child.parent;
|
|
5191
|
+
if (parent === null) throw new Error(`Node '${child.id}' has no parent to move within.`);
|
|
5192
|
+
if (reference !== null && reference.parent !== parent) throw new Error(`Reference node '${reference.id}' is not a sibling of '${child.id}'.`);
|
|
5193
|
+
if (child === reference || child.nextSibling === reference) return;
|
|
5194
|
+
this.detachNode(child);
|
|
5195
|
+
parent.childOrderVersion++;
|
|
5196
|
+
child.parent = parent;
|
|
5197
|
+
if (reference === null) {
|
|
5198
|
+
const last = parent.lastChild;
|
|
5199
|
+
child.previousSibling = last;
|
|
5200
|
+
if (last !== null) last.nextSibling = child;
|
|
5201
|
+
else parent.firstChild = child;
|
|
5202
|
+
parent.lastChild = child;
|
|
5203
|
+
return;
|
|
5204
|
+
}
|
|
5205
|
+
const previous = reference.previousSibling;
|
|
5206
|
+
if (previous !== null) previous.nextSibling = child;
|
|
5207
|
+
else parent.firstChild = child;
|
|
5208
|
+
child.previousSibling = previous;
|
|
5209
|
+
child.nextSibling = reference;
|
|
5210
|
+
reference.previousSibling = child;
|
|
5211
|
+
}
|
|
5164
5212
|
detachNode(node) {
|
|
5165
5213
|
const parent = node.parent;
|
|
5166
5214
|
if (!parent) return;
|
|
@@ -5426,7 +5474,7 @@ var UiGraph = class {
|
|
|
5426
5474
|
applyResolvedProperty(node, property, present, value, dirtyFlags) {
|
|
5427
5475
|
if (!present) {
|
|
5428
5476
|
if (!node.properties.has(property)) return false;
|
|
5429
|
-
node.
|
|
5477
|
+
node.removeProperty(property);
|
|
5430
5478
|
this.markDirty(node, this.withProvidedFlags(property, dirtyFlags));
|
|
5431
5479
|
return true;
|
|
5432
5480
|
}
|
|
@@ -6801,7 +6849,10 @@ const FIT_EPSILON = 1 / 64;
|
|
|
6801
6849
|
* narrower than the widest segment. When a segment is wider than the
|
|
6802
6850
|
* available width the paragraph grows to it, and every line wraps
|
|
6803
6851
|
* against that grown width rather than the one asked for, because that
|
|
6804
|
-
* is the box the lines will be painted in.
|
|
6852
|
+
* is the box the lines will be painted in. When the width is the box's
|
|
6853
|
+
* own (`exactWidth`: a width set from outside, or the box being
|
|
6854
|
+
* painted), it does not grow: the lines wrap against it and the wide
|
|
6855
|
+
* segment overflows alone, as CSS wraps text in a box of a fixed width.
|
|
6805
6856
|
*
|
|
6806
6857
|
* The first baseline sits half the leading below the line top, plus the
|
|
6807
6858
|
* ascent, with the half-leading floored to a whole pixel. That floor is
|
|
@@ -6867,7 +6918,7 @@ function layoutParagraph(request, runs) {
|
|
|
6867
6918
|
if (paragraphEnd >= text.length) break;
|
|
6868
6919
|
paragraphStart = paragraphEnd + 1;
|
|
6869
6920
|
}
|
|
6870
|
-
const available = Math.max(maxWidth, minContentWidth);
|
|
6921
|
+
const available = request.exactWidth === true ? maxWidth : Math.max(maxWidth, minContentWidth);
|
|
6871
6922
|
const unwrapped = wrap === "none" || !isFinite(available);
|
|
6872
6923
|
const lines = [];
|
|
6873
6924
|
for (const paragraph of paragraphs) {
|
|
@@ -6876,7 +6927,7 @@ function layoutParagraph(request, runs) {
|
|
|
6876
6927
|
const key = unwrapped ? -1 : available;
|
|
6877
6928
|
let relative = paragraph.cached.lines.get(key);
|
|
6878
6929
|
if (relative === void 0) {
|
|
6879
|
-
relative = breakHardLine(paragraph.text, paragraph.segments, width, unwrapped, measure);
|
|
6930
|
+
relative = breakHardLine(paragraph.cached.text, paragraph.segments, width, unwrapped, measure);
|
|
6880
6931
|
if (paragraph.cached.lines.size >= WIDTHS_PER_LINE) paragraph.cached.lines.clear();
|
|
6881
6932
|
paragraph.cached.lines.set(key, relative);
|
|
6882
6933
|
}
|
|
@@ -6950,12 +7001,14 @@ function hardLine(memo, style, text, wrap, measure) {
|
|
|
6950
7001
|
memo.set(key, cached);
|
|
6951
7002
|
return cached;
|
|
6952
7003
|
}
|
|
6953
|
-
const
|
|
7004
|
+
const own = ownCopy(text);
|
|
7005
|
+
const segments = segmentParagraph(own, wrap);
|
|
6954
7006
|
let minContentWidth = 0;
|
|
6955
|
-
for (const segment of segments) minContentWidth = Math.max(minContentWidth, measure(
|
|
7007
|
+
for (const segment of segments) minContentWidth = Math.max(minContentWidth, measure(own.slice(segment.start, segment.end), segment.start, segment.end));
|
|
6956
7008
|
const entry = {
|
|
7009
|
+
text: own,
|
|
6957
7010
|
segments,
|
|
6958
|
-
maxContentWidth: measure(
|
|
7011
|
+
maxContentWidth: measure(own, 0, own.length),
|
|
6959
7012
|
minContentWidth,
|
|
6960
7013
|
lines: /* @__PURE__ */ new Map()
|
|
6961
7014
|
};
|
|
@@ -6966,6 +7019,23 @@ function hardLine(memo, style, text, wrap, measure) {
|
|
|
6966
7019
|
memo.set(key, entry);
|
|
6967
7020
|
return entry;
|
|
6968
7021
|
}
|
|
7022
|
+
/**
|
|
7023
|
+
* `text` in a string of its own.
|
|
7024
|
+
*
|
|
7025
|
+
* A hard line arrives as a slice of the whole paragraph, and a slice
|
|
7026
|
+
* in V8 is a view that keeps the string it was cut from alive. The
|
|
7027
|
+
* memo outlives the paragraph — it is what a later edit finds the
|
|
7028
|
+
* unchanged lines in — so each line it remembered, and every line
|
|
7029
|
+
* broken from it, held on to the whole text it came from. Typing into
|
|
7030
|
+
* a 115 KB field kept every version of it: 100 MB over 4,096 edits.
|
|
7031
|
+
* Prefixing a character makes a new string that has to be flattened
|
|
7032
|
+
* before it can be sliced, and the slice is then of that copy. A short
|
|
7033
|
+
* line is copied by `slice` anyway, and an engine that copies every
|
|
7034
|
+
* slice loses nothing.
|
|
7035
|
+
*/
|
|
7036
|
+
function ownCopy(text) {
|
|
7037
|
+
return text.length < 16 ? text : (" " + text).slice(1);
|
|
7038
|
+
}
|
|
6969
7039
|
/** Breaks one hard line on its own, as if it were the whole text. */
|
|
6970
7040
|
function breakHardLine(text, segments, available, unwrapped, measure) {
|
|
6971
7041
|
const lines = [];
|
|
@@ -7266,8 +7336,58 @@ function spannedRunsFor(request, runs) {
|
|
|
7266
7336
|
}
|
|
7267
7337
|
};
|
|
7268
7338
|
}
|
|
7269
|
-
/**
|
|
7270
|
-
|
|
7339
|
+
/**
|
|
7340
|
+
* How much the paragraph cache holds before the least recently asked
|
|
7341
|
+
* for is forgotten: a number of paragraphs, and their text counted in
|
|
7342
|
+
* UTF-16 code units, whichever runs out first.
|
|
7343
|
+
*
|
|
7344
|
+
* A count alone treated a two-word label and a whole document as the
|
|
7345
|
+
* same size. Every keystroke in a long field is a new request, its key
|
|
7346
|
+
* the whole text, so 4,096 edits to a 115 KB field held 4,096 copies of
|
|
7347
|
+
* it, keys and lines: 1.1 GB of heap, for paragraphs nobody would ask
|
|
7348
|
+
* for again. The text budget keeps a handful of those; the count is
|
|
7349
|
+
* what bounds a screen of labels, and is larger than it was, since a
|
|
7350
|
+
* label costs a few hundred bytes and a list that remounts its rows
|
|
7351
|
+
* should find them still here.
|
|
7352
|
+
*/
|
|
7353
|
+
const PARAGRAPH_CACHE_ENTRIES = 16384;
|
|
7354
|
+
const PARAGRAPH_CACHE_TEXT = 1 << 20;
|
|
7355
|
+
/** A least-recently-used map of paragraphs, bounded by count and by text; see `PARAGRAPH_CACHE_ENTRIES`. */
|
|
7356
|
+
var ParagraphCache = class {
|
|
7357
|
+
entries = /* @__PURE__ */ new Map();
|
|
7358
|
+
text = 0;
|
|
7359
|
+
get size() {
|
|
7360
|
+
return this.entries.size;
|
|
7361
|
+
}
|
|
7362
|
+
get(key) {
|
|
7363
|
+
const entry = this.entries.get(key);
|
|
7364
|
+
if (entry === void 0) return;
|
|
7365
|
+
this.entries.delete(key);
|
|
7366
|
+
this.entries.set(key, entry);
|
|
7367
|
+
return entry.paragraph;
|
|
7368
|
+
}
|
|
7369
|
+
set(key, paragraph, text) {
|
|
7370
|
+
const previous = this.entries.get(key);
|
|
7371
|
+
if (previous !== void 0) {
|
|
7372
|
+
this.entries.delete(key);
|
|
7373
|
+
this.text -= previous.text;
|
|
7374
|
+
}
|
|
7375
|
+
while (this.entries.size > 0 && (this.entries.size >= PARAGRAPH_CACHE_ENTRIES || this.text + text > PARAGRAPH_CACHE_TEXT)) {
|
|
7376
|
+
const [oldest, entry] = this.entries.entries().next().value;
|
|
7377
|
+
this.entries.delete(oldest);
|
|
7378
|
+
this.text -= entry.text;
|
|
7379
|
+
}
|
|
7380
|
+
this.entries.set(key, {
|
|
7381
|
+
paragraph,
|
|
7382
|
+
text
|
|
7383
|
+
});
|
|
7384
|
+
this.text += text;
|
|
7385
|
+
}
|
|
7386
|
+
clear() {
|
|
7387
|
+
this.entries.clear();
|
|
7388
|
+
this.text = 0;
|
|
7389
|
+
}
|
|
7390
|
+
};
|
|
7271
7391
|
/**
|
|
7272
7392
|
* Base class for measurers: implement the two primitives and the
|
|
7273
7393
|
* paragraph algorithm does the rest.
|
|
@@ -7289,21 +7409,31 @@ const PARAGRAPH_CACHE_ENTRIES = 4096;
|
|
|
7289
7409
|
* The result is shared between callers and must not be mutated.
|
|
7290
7410
|
*/
|
|
7291
7411
|
var ParagraphTextMeasurer = class {
|
|
7292
|
-
paragraphs =
|
|
7412
|
+
paragraphs = new ParagraphCache();
|
|
7413
|
+
/**
|
|
7414
|
+
* Paragraphs that came out the same at every width they fit in, by
|
|
7415
|
+
* their key without the width; see `layoutFitsAnyWidth`.
|
|
7416
|
+
*
|
|
7417
|
+
* A resize asks every paragraph again at a width a few pixels from
|
|
7418
|
+
* the last, and a title, a caption or a label that fitted before
|
|
7419
|
+
* fits still: its lines, its box and everything else are what they
|
|
7420
|
+
* were. Keyed by its width as well, each of those missed on every
|
|
7421
|
+
* step of a dragged window edge and was laid out again, and the
|
|
7422
|
+
* box it was then painted in (its own width, exact) missed once
|
|
7423
|
+
* more.
|
|
7424
|
+
*/
|
|
7425
|
+
fitting = new ParagraphCache();
|
|
7426
|
+
generations = 0;
|
|
7293
7427
|
layout(request) {
|
|
7294
|
-
const
|
|
7428
|
+
const style = paragraphStyleKey(request);
|
|
7429
|
+
const key = `${request.maxWidth ?? ""}\0${request.exactWidth === true ? 1 : ""}\0${style}`;
|
|
7295
7430
|
const cached = this.paragraphs.get(key);
|
|
7296
|
-
if (cached !== void 0)
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
return cached;
|
|
7300
|
-
}
|
|
7431
|
+
if (cached !== void 0) return cached;
|
|
7432
|
+
const fitted = this.fitting.get(style);
|
|
7433
|
+
if (fitted !== void 0 && fitsWidth(fitted, request.maxWidth)) return fitted;
|
|
7301
7434
|
const paragraph = layoutParagraph(request, this);
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
if (oldest !== void 0) this.paragraphs.delete(oldest);
|
|
7305
|
-
}
|
|
7306
|
-
this.paragraphs.set(key, paragraph);
|
|
7435
|
+
this.paragraphs.set(key, paragraph, request.text.length);
|
|
7436
|
+
if (layoutFitsAnyWidth(request, paragraph)) this.fitting.set(style, paragraph, request.text.length);
|
|
7307
7437
|
return paragraph;
|
|
7308
7438
|
}
|
|
7309
7439
|
measure(request) {
|
|
@@ -7317,6 +7447,9 @@ var ParagraphTextMeasurer = class {
|
|
|
7317
7447
|
get cachedParagraphs() {
|
|
7318
7448
|
return this.paragraphs.size;
|
|
7319
7449
|
}
|
|
7450
|
+
get generation() {
|
|
7451
|
+
return this.generations;
|
|
7452
|
+
}
|
|
7320
7453
|
/**
|
|
7321
7454
|
* Forgets every laid-out paragraph. A subclass that caches its own
|
|
7322
7455
|
* primitives overrides this and calls it, so a font that finished
|
|
@@ -7324,13 +7457,47 @@ var ParagraphTextMeasurer = class {
|
|
|
7324
7457
|
* built from.
|
|
7325
7458
|
*/
|
|
7326
7459
|
invalidate() {
|
|
7460
|
+
this.generations++;
|
|
7327
7461
|
this.paragraphs.clear();
|
|
7462
|
+
this.fitting.clear();
|
|
7328
7463
|
forgetHardLines(this);
|
|
7329
7464
|
}
|
|
7330
7465
|
};
|
|
7331
|
-
/**
|
|
7332
|
-
|
|
7333
|
-
|
|
7466
|
+
/**
|
|
7467
|
+
* Whether `paragraph`, laid out for `request`, is what a request
|
|
7468
|
+
* differing only in its width would get at any width it fits in.
|
|
7469
|
+
*
|
|
7470
|
+
* A paragraph's width enters its layout in four places, and none of
|
|
7471
|
+
* them can tell two widths apart once every hard line fits whole:
|
|
7472
|
+
* nothing breaks (a line that fits is not broken, `exactWidth` or
|
|
7473
|
+
* not); the box is fit-content, which is the natural width; no line is
|
|
7474
|
+
* wider than the box, so none is ellipsised in place; and the one
|
|
7475
|
+
* ellipsis that is fitted to the width — the last line kept by
|
|
7476
|
+
* `maxLines` — is excluded here, because it is only drawn when there
|
|
7477
|
+
* were more hard lines than that. An unbounded request is left out:
|
|
7478
|
+
* it is laid out unwrapped, which is a different path, and it has a
|
|
7479
|
+
* key of its own that never changes anyway.
|
|
7480
|
+
*/
|
|
7481
|
+
function layoutFitsAnyWidth(request, paragraph) {
|
|
7482
|
+
if (!fitsWidth(paragraph, request.maxWidth)) return false;
|
|
7483
|
+
const maxLines = request.maxLines;
|
|
7484
|
+
return request.overflow !== "ellipsis" || maxLines === void 0 || maxLines < 1 || !hasMoreHardLines(request.text, maxLines);
|
|
7485
|
+
}
|
|
7486
|
+
/** Whether a paragraph with nothing in it that depends on the width fits at `maxWidth`. */
|
|
7487
|
+
function fitsWidth(paragraph, maxWidth) {
|
|
7488
|
+
return maxWidth !== void 0 && maxWidth >= 0 && paragraph.maxContentWidth <= maxWidth;
|
|
7489
|
+
}
|
|
7490
|
+
function hasMoreHardLines(text, count) {
|
|
7491
|
+
let at = -1;
|
|
7492
|
+
for (let i = 0; i < count; i++) {
|
|
7493
|
+
at = text.indexOf("\n", at + 1);
|
|
7494
|
+
if (at < 0) return false;
|
|
7495
|
+
}
|
|
7496
|
+
return true;
|
|
7497
|
+
}
|
|
7498
|
+
/** Every field of a request but its width, in a fixed order; two requests that lay out alike at one width share a key. */
|
|
7499
|
+
function paragraphStyleKey(request) {
|
|
7500
|
+
return `${request.fontSize}\0${request.fontFamily ?? ""}\0${request.fontWeight ?? ""}\0${request.lineHeight ?? ""}\0${request.letterSpacing ?? ""}\0${request.wrap ?? ""}\0${request.maxLines ?? ""}\0${request.overflow ?? ""}\0${request.fontStyle ?? ""}\0${request.fontStretch ?? ""}\0${request.fontVariant ?? ""}\0${request.fontKerning ?? ""}\0${spansKey(request.spans)}\0${request.text}`;
|
|
7334
7501
|
}
|
|
7335
7502
|
/**
|
|
7336
7503
|
* The runs' part of a paragraph key, remembered by the identity of the
|
|
@@ -7582,7 +7749,7 @@ function editorFor(node) {
|
|
|
7582
7749
|
let model = node.properties.get(EDITOR_PROP);
|
|
7583
7750
|
if (!(model instanceof EditableTextModel)) {
|
|
7584
7751
|
model = new EditableTextModel();
|
|
7585
|
-
node.
|
|
7752
|
+
node.setProperty(EDITOR_PROP, model);
|
|
7586
7753
|
}
|
|
7587
7754
|
syncEditorValue(node, model);
|
|
7588
7755
|
syncEditorHidden(node, model);
|
|
@@ -7612,12 +7779,33 @@ function syncEditorHidden(node, model) {
|
|
|
7612
7779
|
function isEditableNode(node) {
|
|
7613
7780
|
return node.type === "editable-text";
|
|
7614
7781
|
}
|
|
7782
|
+
/** Whether Enter inserts a newline. Never in a secret field, which is one line, as a password input is. */
|
|
7615
7783
|
function isMultiline(node) {
|
|
7616
|
-
return node.properties.get("multiline") === true;
|
|
7784
|
+
return node.properties.get("multiline") === true && !isSecret(node);
|
|
7617
7785
|
}
|
|
7618
7786
|
function isReadOnly(node) {
|
|
7619
7787
|
return node.properties.get("readOnly") === true;
|
|
7620
7788
|
}
|
|
7789
|
+
function isSecret(node) {
|
|
7790
|
+
return node.properties.get("secret") === true;
|
|
7791
|
+
}
|
|
7792
|
+
/**
|
|
7793
|
+
* A secret field's text as it is drawn: one bullet per UTF-16 code
|
|
7794
|
+
* unit, not per character. Every offset the editor works in (caret,
|
|
7795
|
+
* selection, hit-testing, the shell's mirror) is a code unit offset
|
|
7796
|
+
* into the model's text, and a mask of the same length keeps each of
|
|
7797
|
+
* them meaning the same position in both strings. A character outside
|
|
7798
|
+
* the BMP draws as two bullets, which says a little about it; counting
|
|
7799
|
+
* graphemes instead would need a map between the two strings' offsets
|
|
7800
|
+
* in every one of those places.
|
|
7801
|
+
*/
|
|
7802
|
+
function maskText(text) {
|
|
7803
|
+
return "•".repeat(text.length);
|
|
7804
|
+
}
|
|
7805
|
+
/** The text an editable draws and measures: its model's, masked when it is secret. */
|
|
7806
|
+
function shownTextOf(node, model) {
|
|
7807
|
+
return isSecret(node) ? maskText(model.text) : model.text;
|
|
7808
|
+
}
|
|
7621
7809
|
/**
|
|
7622
7810
|
* Whether the caret is in the visible half of its blink at `now`. It
|
|
7623
7811
|
* is always visible while a composition is open — the IME caret must
|
|
@@ -7655,8 +7843,8 @@ function linkHoverOf(node) {
|
|
|
7655
7843
|
/** Records the hovered run. Returns whether it changed. */
|
|
7656
7844
|
function setLinkHover(node, index) {
|
|
7657
7845
|
if (linkHoverOf(node) === index) return false;
|
|
7658
|
-
if (index < 0) node.
|
|
7659
|
-
else node.
|
|
7846
|
+
if (index < 0) node.removeProperty(TEXT_LINK_HOVER_PROP);
|
|
7847
|
+
else node.setProperty(TEXT_LINK_HOVER_PROP, index);
|
|
7660
7848
|
return true;
|
|
7661
7849
|
}
|
|
7662
7850
|
/** Forgets the hovered run. Returns whether there was one. */
|
|
@@ -7857,7 +8045,7 @@ function selectionRangeOf(node) {
|
|
|
7857
8045
|
function setSelectionRange(node, start, end) {
|
|
7858
8046
|
const current = selectionRangeOf(node);
|
|
7859
8047
|
if (current !== void 0 && current.start === start && current.end === end) return false;
|
|
7860
|
-
node.
|
|
8048
|
+
node.setProperty(TEXT_SELECTION_PROP, {
|
|
7861
8049
|
start,
|
|
7862
8050
|
end
|
|
7863
8051
|
});
|
|
@@ -7865,7 +8053,7 @@ function setSelectionRange(node, start, end) {
|
|
|
7865
8053
|
}
|
|
7866
8054
|
/** Clears a node's selected range. Returns whether it had one. */
|
|
7867
8055
|
function clearSelectionRange(node) {
|
|
7868
|
-
return node.
|
|
8056
|
+
return node.removeProperty(TEXT_SELECTION_PROP);
|
|
7869
8057
|
}
|
|
7870
8058
|
/**
|
|
7871
8059
|
* The text a `Text` node draws, when the user may select it.
|
|
@@ -7935,12 +8123,12 @@ function setMatchRanges(node, ranges) {
|
|
|
7935
8123
|
const current = matchRangesOf(node);
|
|
7936
8124
|
if (current !== void 0 && rangesEqual(current, ranges)) return false;
|
|
7937
8125
|
if (ranges.length === 0) return clearMatchRanges(node);
|
|
7938
|
-
node.
|
|
8126
|
+
node.setProperty(TEXT_MATCHES_PROP, ranges);
|
|
7939
8127
|
return true;
|
|
7940
8128
|
}
|
|
7941
8129
|
/** Clears a node's matched ranges. Returns whether it had any. */
|
|
7942
8130
|
function clearMatchRanges(node) {
|
|
7943
|
-
return node.
|
|
8131
|
+
return node.removeProperty(TEXT_MATCHES_PROP);
|
|
7944
8132
|
}
|
|
7945
8133
|
function rangesEqual(a, b) {
|
|
7946
8134
|
if (a.length !== b.length) return false;
|
|
@@ -8045,7 +8233,7 @@ function resolvePaintState(node, out) {
|
|
|
8045
8233
|
if (isEditableNode(node)) {
|
|
8046
8234
|
const model = editorFor(node);
|
|
8047
8235
|
out.editor = model;
|
|
8048
|
-
out.text = model
|
|
8236
|
+
out.text = shownTextOf(node, model);
|
|
8049
8237
|
out.placeholder = resolveString(node, "placeholder");
|
|
8050
8238
|
out.placeholderColor = resolveColor(node, UiProperties.placeholderColor) ?? themeColor(node, "textMuted") ?? DEFAULT_PLACEHOLDER_COLOR;
|
|
8051
8239
|
out.selectionColor = resolveColor(node, UiProperties.selectionColor) ?? defaultSelectionColor(node);
|
|
@@ -8507,6 +8695,7 @@ function textMeasureRequest(text, state, box) {
|
|
|
8507
8695
|
fontVariant: state.fontVariant,
|
|
8508
8696
|
fontKerning: state.fontKerning,
|
|
8509
8697
|
maxWidth: box.width > 0 ? box.width : void 0,
|
|
8698
|
+
exactWidth: box.width > 0,
|
|
8510
8699
|
wrap: state.textWrap,
|
|
8511
8700
|
maxLines: state.maxLines,
|
|
8512
8701
|
overflow: state.textOverflow,
|
|
@@ -8834,23 +9023,32 @@ var EditableLayout = class {
|
|
|
8834
9023
|
placeholderLines;
|
|
8835
9024
|
measure;
|
|
8836
9025
|
rtl;
|
|
9026
|
+
/**
|
|
9027
|
+
* The text as drawn: the model's, or a secret field's mask of it
|
|
9028
|
+
* (see `maskText`). The geometry is the drawing's, so it is measured
|
|
9029
|
+
* from this; the two are the same length, so its offsets are the
|
|
9030
|
+
* model's.
|
|
9031
|
+
*/
|
|
9032
|
+
text;
|
|
8837
9033
|
constructor(model, box, state, measurer) {
|
|
8838
9034
|
this.model = model;
|
|
8839
9035
|
this.box = box;
|
|
8840
|
-
const
|
|
9036
|
+
const text = state.text ?? model.text;
|
|
9037
|
+
this.text = text;
|
|
9038
|
+
const request = textMeasureRequest(text, state, box);
|
|
8841
9039
|
request.maxLines = void 0;
|
|
8842
9040
|
request.overflow = "clip";
|
|
8843
9041
|
this.lines = placeLines(box, state, measurer.layout(request));
|
|
8844
9042
|
const spanned = spannedRunsFor(request, measurer);
|
|
8845
|
-
this.measure = (text, start) => text.length === 0 ? 0 : spanned === void 0 || start === void 0 ? measurer.measureRunWidth(text, request) : spanned.width(
|
|
9043
|
+
this.measure = (text, start) => text.length === 0 ? 0 : spanned === void 0 || start === void 0 ? measurer.measureRunWidth(text, request) : spanned.width(this.text, start, start + text.length);
|
|
8846
9044
|
this.rtl = state.rtl;
|
|
8847
|
-
this.placeholderLines =
|
|
9045
|
+
this.placeholderLines = text.length === 0 && state.placeholder !== void 0 && state.placeholder.length > 0 ? placeLines(box, state, measurer.layout({
|
|
8848
9046
|
...request,
|
|
8849
9047
|
text: state.placeholder
|
|
8850
9048
|
})) : [];
|
|
8851
9049
|
}
|
|
8852
9050
|
caretRect(offset = this.model.focus) {
|
|
8853
|
-
return caretRectFor(this.lines, this.
|
|
9051
|
+
return caretRectFor(this.lines, this.text, offset, this.measure, this.rtl);
|
|
8854
9052
|
}
|
|
8855
9053
|
/**
|
|
8856
9054
|
* The caret position nearest a point. Where hidden text sits at the
|
|
@@ -8861,10 +9059,10 @@ var EditableLayout = class {
|
|
|
8861
9059
|
* apart. Never inside hidden text.
|
|
8862
9060
|
*/
|
|
8863
9061
|
offsetAt(x, y) {
|
|
8864
|
-
return this.model.caretOffsetNear(offsetAtPoint(this.lines, this.
|
|
9062
|
+
return this.model.caretOffsetNear(offsetAtPoint(this.lines, this.text, x, y, this.measure, this.rtl));
|
|
8865
9063
|
}
|
|
8866
9064
|
verticalMove(offset, direction, goalX) {
|
|
8867
|
-
const move = offsetForVerticalMove(this.lines, this.
|
|
9065
|
+
const move = offsetForVerticalMove(this.lines, this.text, offset, direction, this.measure, this.rtl, goalX);
|
|
8868
9066
|
return move === null ? null : {
|
|
8869
9067
|
offset: this.model.caretOffsetNear(move.offset),
|
|
8870
9068
|
x: move.x
|
|
@@ -8877,13 +9075,13 @@ var EditableLayout = class {
|
|
|
8877
9075
|
selectionBoxes(range) {
|
|
8878
9076
|
const start = range?.start ?? this.model.start;
|
|
8879
9077
|
const end = range?.end ?? this.model.end;
|
|
8880
|
-
return selectionRects(this.lines, this.
|
|
9078
|
+
return selectionRects(this.lines, this.text, start, end, this.measure, this.rtl);
|
|
8881
9079
|
}
|
|
8882
9080
|
/** Boxes under the text the IME is composing, for its underline. */
|
|
8883
9081
|
compositionBoxes() {
|
|
8884
9082
|
const range = this.model.composition;
|
|
8885
9083
|
if (range === null) return [];
|
|
8886
|
-
return selectionRects(this.lines, this.
|
|
9084
|
+
return selectionRects(this.lines, this.text, range.start, range.end, this.measure, this.rtl);
|
|
8887
9085
|
}
|
|
8888
9086
|
};
|
|
8889
9087
|
//#endregion
|
|
@@ -9379,6 +9577,7 @@ function scrollbarThumb(rec, axis) {
|
|
|
9379
9577
|
return thumbFor(rec, axis);
|
|
9380
9578
|
}
|
|
9381
9579
|
function thumbFor(rec, axis) {
|
|
9580
|
+
if (rec.hidesScrollbars) return null;
|
|
9382
9581
|
const viewport = axis === "y" ? rec.height : rec.width;
|
|
9383
9582
|
const content = axis === "y" ? rec.contentHeight : rec.contentWidth;
|
|
9384
9583
|
if (!(content > viewport) || viewport <= 0) return null;
|
|
@@ -10109,8 +10308,21 @@ var UiFocusManager = class {
|
|
|
10109
10308
|
* when that scope was pushed, when that node is still in the tree
|
|
10110
10309
|
* and still focusable. Otherwise focus is dropped rather than left
|
|
10111
10310
|
* on a node the scope was hiding.
|
|
10112
|
-
|
|
10113
|
-
|
|
10311
|
+
*
|
|
10312
|
+
* Given its root, only that scope ends: none, when it has gone already
|
|
10313
|
+
* (its subtree removed, which pops it), so a trap released after its
|
|
10314
|
+
* content was taken away cannot end the one under it, a submenu's
|
|
10315
|
+
* menu's; and one under another is taken out where it is.
|
|
10316
|
+
*/
|
|
10317
|
+
popScope(root) {
|
|
10318
|
+
if (root !== void 0 && this.scopes.at(-1)?.root !== root) {
|
|
10319
|
+
const at = this.scopes.findIndex((each) => each.root === root);
|
|
10320
|
+
if (at >= 0) {
|
|
10321
|
+
this.scopes.splice(at, 1);
|
|
10322
|
+
this.notifyScope();
|
|
10323
|
+
}
|
|
10324
|
+
return;
|
|
10325
|
+
}
|
|
10114
10326
|
const scope = this.scopes.pop();
|
|
10115
10327
|
if (scope === void 0) return;
|
|
10116
10328
|
const restore = scope.restore;
|
|
@@ -10930,6 +11142,11 @@ var UiWheelController = class {
|
|
|
10930
11142
|
* wheel of a burst land correctly even though the first could not.
|
|
10931
11143
|
*/
|
|
10932
11144
|
lastWheelTarget = null;
|
|
11145
|
+
/**
|
|
11146
|
+
* The container the wheel last scrolled, for a frame's report of where
|
|
11147
|
+
* scrolling put it. Null until the wheel has scrolled something.
|
|
11148
|
+
*/
|
|
11149
|
+
lastScrolled = null;
|
|
10933
11150
|
wheel(x, y, deltaX, deltaY, modifiers = noKeyModifiers(), deltaMode = 0, wheelDeltaY, at) {
|
|
10934
11151
|
const target = this.hitTester.hitTest(x, y)?.node ?? null;
|
|
10935
11152
|
this.lastWheelTarget = target;
|
|
@@ -11082,7 +11299,8 @@ var UiWheelController = class {
|
|
|
11082
11299
|
dirX: 0,
|
|
11083
11300
|
dirY: 0,
|
|
11084
11301
|
quiet: 0,
|
|
11085
|
-
fresh: false
|
|
11302
|
+
fresh: false,
|
|
11303
|
+
fitted: false
|
|
11086
11304
|
};
|
|
11087
11305
|
this.pacing.set(container, paced);
|
|
11088
11306
|
}
|
|
@@ -11098,9 +11316,11 @@ var UiWheelController = class {
|
|
|
11098
11316
|
});
|
|
11099
11317
|
while (paced.samples.length > 2 && paced.samples[0].t < this.eventTime - VELOCITY_WINDOW_MS) paced.samples.shift();
|
|
11100
11318
|
this.scrollSink.scrollBy(container, 0, 0, "instant");
|
|
11319
|
+
this.lastScrolled = container;
|
|
11101
11320
|
return;
|
|
11102
11321
|
}
|
|
11103
11322
|
this.scrollSink.scrollBy(container, dx, dy, behavior);
|
|
11323
|
+
this.lastScrolled = container;
|
|
11104
11324
|
}
|
|
11105
11325
|
/**
|
|
11106
11326
|
* A container's state as a step should judge it: during a paced gesture,
|
|
@@ -11128,16 +11348,15 @@ var UiWheelController = class {
|
|
|
11128
11348
|
* comes a varying time after the last step: applied as they arrive, a
|
|
11129
11349
|
* steady flick moves the page by uneven amounts, which reads as
|
|
11130
11350
|
* judder. Evening that out by waiting for steps puts the page behind
|
|
11131
|
-
* the hand. So this does what a browser does for its own scrolling:
|
|
11132
|
-
*
|
|
11133
|
-
*
|
|
11134
|
-
*
|
|
11135
|
-
* even, and nothing trails.
|
|
11351
|
+
* the hand. So this does what a browser does for its own scrolling: it
|
|
11352
|
+
* resamples the steps at the frame's time, reading a line fitted through
|
|
11353
|
+
* the last `VELOCITY_WINDOW_MS` of them. Frames come out even, and
|
|
11354
|
+
* nothing trails.
|
|
11136
11355
|
*
|
|
11137
|
-
* A gesture's first step, with no
|
|
11138
|
-
* steps stop, the
|
|
11139
|
-
*
|
|
11140
|
-
*
|
|
11356
|
+
* A gesture's first step, with no line yet, goes as it is. Once the
|
|
11357
|
+
* steps stop, the page eases over a few frames to exactly where they add
|
|
11358
|
+
* up to; a trackpad's momentum slows to nothing first, so there is next
|
|
11359
|
+
* to nothing to take up.
|
|
11141
11360
|
*
|
|
11142
11361
|
* True while a gesture is still being paced, so the host keeps frames
|
|
11143
11362
|
* coming until it has settled.
|
|
@@ -11157,14 +11376,38 @@ var UiWheelController = class {
|
|
|
11157
11376
|
let y = paced.totalY;
|
|
11158
11377
|
const samples = paced.samples;
|
|
11159
11378
|
const last = samples[samples.length - 1];
|
|
11160
|
-
|
|
11161
|
-
for (let k = samples.length - 2; k >= 0 && last !== void 0 && samples[k].t >= last.t - RECENT_MS; k--) first = samples[k];
|
|
11379
|
+
const first = samples[0];
|
|
11162
11380
|
const since = time === void 0 || last === void 0 ? -1 : time - last.t;
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
11167
|
-
|
|
11381
|
+
const at = time === void 0 ? void 0 : time - RESAMPLE_MS;
|
|
11382
|
+
if (at !== void 0 && last !== void 0 && first !== void 0 && since >= -1 && at - last.t < STALE_MS && samples.length >= MIN_FIT_STEPS && last.t - first.t >= 2) {
|
|
11383
|
+
let meanT = 0;
|
|
11384
|
+
let meanX = 0;
|
|
11385
|
+
let meanY = 0;
|
|
11386
|
+
for (const sample of samples) {
|
|
11387
|
+
meanT += sample.t;
|
|
11388
|
+
meanX += sample.x;
|
|
11389
|
+
meanY += sample.y;
|
|
11390
|
+
}
|
|
11391
|
+
meanT /= samples.length;
|
|
11392
|
+
meanX /= samples.length;
|
|
11393
|
+
meanY /= samples.length;
|
|
11394
|
+
let varT = 0;
|
|
11395
|
+
let covX = 0;
|
|
11396
|
+
let covY = 0;
|
|
11397
|
+
for (const sample of samples) {
|
|
11398
|
+
const dt = sample.t - meanT;
|
|
11399
|
+
varT += dt * dt;
|
|
11400
|
+
covX += dt * (sample.x - meanX);
|
|
11401
|
+
covY += dt * (sample.y - meanY);
|
|
11402
|
+
}
|
|
11403
|
+
x = meanX + covX / varT * (at - meanT);
|
|
11404
|
+
y = meanY + covY / varT * (at - meanT);
|
|
11405
|
+
paced.fitted = true;
|
|
11406
|
+
} else if (paced.fitted) {
|
|
11407
|
+
const drawnX = paced.lastX - paced.baseX;
|
|
11408
|
+
const drawnY = paced.lastY - paced.baseY;
|
|
11409
|
+
x = Math.abs(x - drawnX) < SETTLED_PX ? x : drawnX + (x - drawnX) * SETTLE_RATE;
|
|
11410
|
+
y = Math.abs(y - drawnY) < SETTLED_PX ? y : drawnY + (y - drawnY) * SETTLE_RATE;
|
|
11168
11411
|
}
|
|
11169
11412
|
const state = this.scrollSink.containerState(container);
|
|
11170
11413
|
if (state === void 0) {
|
|
@@ -11190,7 +11433,7 @@ var UiWheelController = class {
|
|
|
11190
11433
|
if (dx !== 0 || dy !== 0) this.scrollSink.scrollBy(container, dx, dy, "instant");
|
|
11191
11434
|
paced.lastX = toX;
|
|
11192
11435
|
paced.lastY = toY;
|
|
11193
|
-
if (toX === Math.min(Math.max(paced.baseX + paced.totalX, 0), state.maxScrollX) && toY === Math.min(Math.max(paced.baseY + paced.totalY, 0), state.maxScrollY) && paced.quiet >= QUIET_FRAMES) this.pacing.delete(container);
|
|
11436
|
+
if (toX === Math.min(Math.max(paced.baseX + paced.totalX, 0), state.maxScrollX) && toY === Math.min(Math.max(paced.baseY + paced.totalY, 0), state.maxScrollY) && paced.quiet >= QUIET_FRAMES && (since < 0 || since >= STALE_MS)) this.pacing.delete(container);
|
|
11194
11437
|
}
|
|
11195
11438
|
return this.pacing.size > 0;
|
|
11196
11439
|
}
|
|
@@ -11244,18 +11487,29 @@ function isScrollContainer(node) {
|
|
|
11244
11487
|
* hand from a trackpad to a mouse.
|
|
11245
11488
|
*/
|
|
11246
11489
|
const PRECISE_MEMORY_MS = 400;
|
|
11247
|
-
/** How far back the velocity is read: a few of a trackpad's steps. */
|
|
11248
|
-
const VELOCITY_WINDOW_MS = 40;
|
|
11249
|
-
/** How long after the last step its velocity is still trusted. */
|
|
11250
|
-
const STALE_MS = 40;
|
|
11251
11490
|
/**
|
|
11252
|
-
*
|
|
11253
|
-
*
|
|
11254
|
-
*
|
|
11491
|
+
* How far back the steps are fitted: five or six of a trackpad's. Less, and
|
|
11492
|
+
* the line follows each step's rounding; more, and it lags a flick that is
|
|
11493
|
+
* speeding up or slowing down. Measured on a real flick on a Mac, 48 ms left
|
|
11494
|
+
* a sixth of the frame-to-frame variation of the last-few-steps velocity,
|
|
11495
|
+
* with no frame standing still and the page no further behind the hand.
|
|
11255
11496
|
*/
|
|
11256
|
-
const
|
|
11257
|
-
/** How
|
|
11258
|
-
const
|
|
11497
|
+
const VELOCITY_WINDOW_MS = 48;
|
|
11498
|
+
/** How long after the last step its line is still trusted. */
|
|
11499
|
+
const STALE_MS = 40;
|
|
11500
|
+
/**
|
|
11501
|
+
* How long before the frame the line is read. Each millisecond is one more
|
|
11502
|
+
* of lag and less of a guess: measured on recorded flicks, 12 left the
|
|
11503
|
+
* worst frames a third as uneven as reading at the frame, and the page
|
|
11504
|
+
* about 11 ms behind the hand, against 15 for steps applied as they come.
|
|
11505
|
+
*/
|
|
11506
|
+
const RESAMPLE_MS = 12;
|
|
11507
|
+
/** Steps a line is fitted through: two are a line through their rounding. */
|
|
11508
|
+
const MIN_FIT_STEPS = 3;
|
|
11509
|
+
/** How much of the way to where the steps add up to a frame goes once they stop. */
|
|
11510
|
+
const SETTLE_RATE = .35;
|
|
11511
|
+
/** Close enough to where the steps add up to, to go the rest at once. */
|
|
11512
|
+
const SETTLED_PX = .25;
|
|
11259
11513
|
/** The display's interval before one has been measured. */
|
|
11260
11514
|
const DEFAULT_FRAME_MS = 1e3 / 60;
|
|
11261
11515
|
/** Quiet frames, once settled, after which a gesture is over. */
|
|
@@ -12009,9 +12263,13 @@ var UiEditingController = class {
|
|
|
12009
12263
|
if (command === null) return false;
|
|
12010
12264
|
return this.execute(node, command);
|
|
12011
12265
|
}
|
|
12012
|
-
execute(node,
|
|
12266
|
+
execute(node, keyed) {
|
|
12013
12267
|
const model = editorFor(node);
|
|
12014
12268
|
const readOnly = isReadOnly(node);
|
|
12269
|
+
const command = (keyed.kind === "move" || keyed.kind === "delete") && keyed.unit === "word" ? {
|
|
12270
|
+
...keyed,
|
|
12271
|
+
unit: secretWordUnit(node)
|
|
12272
|
+
} : keyed;
|
|
12015
12273
|
switch (command.kind) {
|
|
12016
12274
|
case "move":
|
|
12017
12275
|
if (this.span !== null && !command.extend) {
|
|
@@ -12182,7 +12440,8 @@ var UiEditingController = class {
|
|
|
12182
12440
|
case "deleteByCut":
|
|
12183
12441
|
case "deleteByDrag": {
|
|
12184
12442
|
if (readOnly) return true;
|
|
12185
|
-
|
|
12443
|
+
if (inputType === "deleteByCut" && isSecret(node)) return true;
|
|
12444
|
+
const unit = /Word/.test(inputType) ? secretWordUnit(node) : /Line/.test(inputType) ? "line" : "grapheme";
|
|
12186
12445
|
const forward = /Forward/.test(inputType);
|
|
12187
12446
|
return this.applyEdit(node, model, inputType, null, () => {
|
|
12188
12447
|
if (inputType === "deleteContent" || inputType === "deleteByCut" || inputType === "deleteByDrag") model.replaceRange(model.start, model.end, "");
|
|
@@ -12387,6 +12646,7 @@ var UiEditingController = class {
|
|
|
12387
12646
|
};
|
|
12388
12647
|
this.dragging = node;
|
|
12389
12648
|
if (modifiers.shift) model.moveTo(offset, true);
|
|
12649
|
+
else if (count === 2 && isSecret(node)) model.selectAll();
|
|
12390
12650
|
else if (count === 2) {
|
|
12391
12651
|
const word = visibleWordRange(model.text, model.hidden, offset);
|
|
12392
12652
|
model.select(word.start, word.end);
|
|
@@ -12452,6 +12712,16 @@ var UiEditingController = class {
|
|
|
12452
12712
|
...html === void 0 ? {} : { html }
|
|
12453
12713
|
};
|
|
12454
12714
|
}
|
|
12715
|
+
if (isSecret(node)) return {
|
|
12716
|
+
text: maskText(model.text),
|
|
12717
|
+
selectionStart: model.start,
|
|
12718
|
+
selectionEnd: model.end,
|
|
12719
|
+
caret,
|
|
12720
|
+
multiline: false,
|
|
12721
|
+
composing: model.composing,
|
|
12722
|
+
clipboard: "",
|
|
12723
|
+
secret: true
|
|
12724
|
+
};
|
|
12455
12725
|
const html = model.collapsed || model.composing ? void 0 : this.fieldHtml(node, model.start, model.end);
|
|
12456
12726
|
return {
|
|
12457
12727
|
text: model.text,
|
|
@@ -12722,10 +12992,11 @@ var UiEditingController = class {
|
|
|
12722
12992
|
const end = forward ? span.focus : span.anchor;
|
|
12723
12993
|
const key = [span];
|
|
12724
12994
|
for (const node of span.lit) key.push(editorFor(node).text);
|
|
12995
|
+
const textOf = (node) => isSecret(node) ? maskText(editorFor(node).text) : editorFor(node).text;
|
|
12725
12996
|
return this.cachedCopy(key, () => {
|
|
12726
12997
|
return {
|
|
12727
12998
|
text: span.group.copyText !== void 0 ? span.group.copyText(start, end) : span.lit.map((node) => {
|
|
12728
|
-
const text =
|
|
12999
|
+
const text = textOf(node);
|
|
12729
13000
|
return text.slice(node === start.node ? start.offset : 0, node === end.node ? end.offset : text.length);
|
|
12730
13001
|
}).join("\n"),
|
|
12731
13002
|
html: span.group.copyHtml?.(start, end) ?? void 0
|
|
@@ -12882,6 +13153,13 @@ var UiEditingController = class {
|
|
|
12882
13153
|
return new EditableLayout(state.editor, box, state, this.host.measurer);
|
|
12883
13154
|
}
|
|
12884
13155
|
};
|
|
13156
|
+
/**
|
|
13157
|
+
* The unit a word move or delete takes in a field: the whole text in a
|
|
13158
|
+
* secret one, whose word boundaries the caret stopping at would show.
|
|
13159
|
+
*/
|
|
13160
|
+
function secretWordUnit(node) {
|
|
13161
|
+
return isSecret(node) ? "document" : "word";
|
|
13162
|
+
}
|
|
12885
13163
|
function deleteInputType(unit, direction) {
|
|
12886
13164
|
const side = direction < 0 ? "Backward" : "Forward";
|
|
12887
13165
|
switch (unit) {
|
|
@@ -13985,6 +14263,8 @@ var LayoutRecord = class {
|
|
|
13985
14263
|
* multiline, the overlay scrollbars.
|
|
13986
14264
|
*/
|
|
13987
14265
|
scrollsText = false;
|
|
14266
|
+
/** A scroll container set to show no scrollbars (\`scrollbars: 'none'\`). */
|
|
14267
|
+
hidesScrollbars = false;
|
|
13988
14268
|
/**
|
|
13989
14269
|
* A multiline editable, which shows the overlay scrollbars a scroll
|
|
13990
14270
|
* container does over its own text. A single-line field scrolls
|
|
@@ -14050,6 +14330,23 @@ var LayoutRecord = class {
|
|
|
14050
14330
|
/** Constraints this record was last measured under. */
|
|
14051
14331
|
lastConstraints = NEVER_MEASURED;
|
|
14052
14332
|
/**
|
|
14333
|
+
* The paragraphs a text leaf was last measured as, one asked with a
|
|
14334
|
+
* width and one without; see `LayoutEngine.measureLeaf`. Flex asks
|
|
14335
|
+
* an item both questions every pass (its max-content basis, then its
|
|
14336
|
+
* final width), so one slot would be overwritten by the other every
|
|
14337
|
+
* time. Null until the node is measured as text that way.
|
|
14338
|
+
*
|
|
14339
|
+
* **Not reset.** These are the one thing a record carries from one
|
|
14340
|
+
* full pass into the next, on purpose: a full pass re-asks every
|
|
14341
|
+
* text leaf, and a memo that every pass cleared would answer
|
|
14342
|
+
* nothing a resize asks. They are safe to keep because they check
|
|
14343
|
+
* themselves — a memo is used only when every field of the request
|
|
14344
|
+
* it was laid out for matches and the measurer has not been
|
|
14345
|
+
* invalidated since — rather than trusting that nothing changed.
|
|
14346
|
+
*/
|
|
14347
|
+
textBounded = null;
|
|
14348
|
+
textUnbounded = null;
|
|
14349
|
+
/**
|
|
14053
14350
|
* The percentage base the current measurement resolved against, and
|
|
14054
14351
|
* whether the node has a length that resolves against one.
|
|
14055
14352
|
*
|
|
@@ -14165,6 +14462,20 @@ var LayoutRecord = class {
|
|
|
14165
14462
|
propsBaseWidth = void 0;
|
|
14166
14463
|
propsBaseHeight = void 0;
|
|
14167
14464
|
/**
|
|
14465
|
+
* The node's `propertyVersion` when its layout properties were last
|
|
14466
|
+
* resolved, and whether that resolution read the node and nothing
|
|
14467
|
+
* else (no inherited direction, no scroll leader). Together with the
|
|
14468
|
+
* percentage base they let a later pass, a full one included, keep
|
|
14469
|
+
* what the last one resolved instead of reading forty properties
|
|
14470
|
+
* again; see `LayoutEngine.resolveLayoutProps`.
|
|
14471
|
+
*
|
|
14472
|
+
* **Not reset,** with the fields they vouch for: `reset()` leaves
|
|
14473
|
+
* every field `resolveLayoutProps` writes as it was, because these
|
|
14474
|
+
* say whether it is still right.
|
|
14475
|
+
*/
|
|
14476
|
+
propsVersion = -1;
|
|
14477
|
+
propsFromNodeAlone = false;
|
|
14478
|
+
/**
|
|
14168
14479
|
* Returns the record to the state a freshly constructed one is in,
|
|
14169
14480
|
* so a full layout can reuse the object instead of replacing it.
|
|
14170
14481
|
*
|
|
@@ -14178,7 +14489,9 @@ var LayoutRecord = class {
|
|
|
14178
14489
|
* never reused across.
|
|
14179
14490
|
*
|
|
14180
14491
|
* **Every field above has to appear here, with the value it is
|
|
14181
|
-
* declared with
|
|
14492
|
+
* declared with,** but the two text memos and the resolved layout
|
|
14493
|
+
* properties, which check themselves and are kept across passes on
|
|
14494
|
+
* purpose. A field that is added and not reset would carry
|
|
14182
14495
|
* one pass's answer into the next, which is the kind of fault that
|
|
14183
14496
|
* shows as a stale box on a screen and as nothing at all in a test
|
|
14184
14497
|
* that lays out once. `LayoutRecord.spec.ts` compares a reset record
|
|
@@ -14193,28 +14506,6 @@ var LayoutRecord = class {
|
|
|
14193
14506
|
this.measuredHeight = 0;
|
|
14194
14507
|
this.outerWidth = 0;
|
|
14195
14508
|
this.outerHeight = 0;
|
|
14196
|
-
this.paddingLeft = 0;
|
|
14197
|
-
this.paddingRight = 0;
|
|
14198
|
-
this.paddingTop = 0;
|
|
14199
|
-
this.paddingBottom = 0;
|
|
14200
|
-
this.marginLeft = 0;
|
|
14201
|
-
this.marginRight = 0;
|
|
14202
|
-
this.marginTop = 0;
|
|
14203
|
-
this.marginBottom = 0;
|
|
14204
|
-
this.minWidth = 0;
|
|
14205
|
-
this.maxWidth = Infinity;
|
|
14206
|
-
this.minHeight = 0;
|
|
14207
|
-
this.maxHeight = Infinity;
|
|
14208
|
-
this.flexGrow = 0;
|
|
14209
|
-
this.flexShrink = 1;
|
|
14210
|
-
this.flexBasisZero = false;
|
|
14211
|
-
this.minWidthAuto = true;
|
|
14212
|
-
this.minHeightAuto = true;
|
|
14213
|
-
this.marginLeftAuto = false;
|
|
14214
|
-
this.marginRightAuto = false;
|
|
14215
|
-
this.marginTopAuto = false;
|
|
14216
|
-
this.marginBottomAuto = false;
|
|
14217
|
-
this.aspectRatio = void 0;
|
|
14218
14509
|
this.hasBaseline = false;
|
|
14219
14510
|
this.baseline = 0;
|
|
14220
14511
|
this.minContentWidth = 0;
|
|
@@ -14227,16 +14518,6 @@ var LayoutRecord = class {
|
|
|
14227
14518
|
this.flexMin = 0;
|
|
14228
14519
|
this.flexMax = Infinity;
|
|
14229
14520
|
this.flexMinAuto = false;
|
|
14230
|
-
this.positioned = false;
|
|
14231
|
-
this.absolute = false;
|
|
14232
|
-
this.top = void 0;
|
|
14233
|
-
this.right = void 0;
|
|
14234
|
-
this.bottom = void 0;
|
|
14235
|
-
this.left = void 0;
|
|
14236
|
-
this.zIndex = 0;
|
|
14237
|
-
this.lifted = false;
|
|
14238
|
-
this.liftBoundary = false;
|
|
14239
|
-
this.clips = false;
|
|
14240
14521
|
this.extentMinX = 0;
|
|
14241
14522
|
this.extentMinY = 0;
|
|
14242
14523
|
this.extentMaxX = 0;
|
|
@@ -14245,11 +14526,6 @@ var LayoutRecord = class {
|
|
|
14245
14526
|
this.paintReachTop = 0;
|
|
14246
14527
|
this.paintReachRight = 0;
|
|
14247
14528
|
this.paintReachBottom = 0;
|
|
14248
|
-
this.scrollable = false;
|
|
14249
|
-
this.scrollsText = false;
|
|
14250
|
-
this.textScrollbars = false;
|
|
14251
|
-
this.mirrored = false;
|
|
14252
|
-
this.sticky = false;
|
|
14253
14529
|
this.stickyOffsetX = 0;
|
|
14254
14530
|
this.stickyOffsetY = 0;
|
|
14255
14531
|
this.scrollbarVisibleUntil = 0;
|
|
@@ -14268,8 +14544,6 @@ var LayoutRecord = class {
|
|
|
14268
14544
|
this.lastConstraints = NEVER_MEASURED;
|
|
14269
14545
|
this.lastBaseWidth = void 0;
|
|
14270
14546
|
this.lastBaseHeight = void 0;
|
|
14271
|
-
this.percentWidth = false;
|
|
14272
|
-
this.percentHeight = false;
|
|
14273
14547
|
this.alternates = null;
|
|
14274
14548
|
this.contentMatters = true;
|
|
14275
14549
|
this.relayoutBoundary = false;
|
|
@@ -14278,8 +14552,6 @@ var LayoutRecord = class {
|
|
|
14278
14552
|
this.placeDirty = true;
|
|
14279
14553
|
this.transformDirty = false;
|
|
14280
14554
|
this.propsPass = 0;
|
|
14281
|
-
this.propsBaseWidth = void 0;
|
|
14282
|
-
this.propsBaseHeight = void 0;
|
|
14283
14555
|
}
|
|
14284
14556
|
};
|
|
14285
14557
|
/** How many earlier measurements a record remembers besides the current one. */
|
|
@@ -15174,6 +15446,18 @@ var LayoutEngine = class {
|
|
|
15174
15446
|
*/
|
|
15175
15447
|
stickyShifted = /* @__PURE__ */ new Set();
|
|
15176
15448
|
textMeasurer;
|
|
15449
|
+
/** Where `measureLeaf` resolves a text node's font; read before anything else can resolve one. */
|
|
15450
|
+
fontScratch = {
|
|
15451
|
+
fontSize: 0,
|
|
15452
|
+
fontFamily: "",
|
|
15453
|
+
fontWeight: "normal",
|
|
15454
|
+
lineHeight: 0,
|
|
15455
|
+
letterSpacing: 0,
|
|
15456
|
+
fontStyle: "normal",
|
|
15457
|
+
fontStretch: "normal",
|
|
15458
|
+
fontVariant: "normal",
|
|
15459
|
+
fontKerning: "auto"
|
|
15460
|
+
};
|
|
15177
15461
|
/**
|
|
15178
15462
|
* The definite size percentages resolve against while a container's
|
|
15179
15463
|
* children are measured: its content box. Undefined on an axis whose
|
|
@@ -17012,37 +17296,79 @@ var LayoutEngine = class {
|
|
|
17012
17296
|
const paddingV = rec.paddingTop + rec.paddingBottom;
|
|
17013
17297
|
const editable = node.type === "editable-text";
|
|
17014
17298
|
if (node.type === "text" || node.type === "button" || editable) {
|
|
17015
|
-
const typed = editable ? editorFor(node)
|
|
17299
|
+
const typed = editable ? shownTextOf(node, editorFor(node)) : "";
|
|
17016
17300
|
const spans = editable ? editableSpansOf(node, typed, editorFor(node).composition) : resolvedSpansOf(node);
|
|
17017
17301
|
const text = editable ? typed : spans.length > 0 ? textContentOf(node) : String(node.properties.get("text") ?? "");
|
|
17018
17302
|
const placeholderProp = node.properties.get("placeholder");
|
|
17019
17303
|
const placeholder = editable && text.length === 0 && typeof placeholderProp === "string" ? placeholderProp : void 0;
|
|
17020
|
-
const font =
|
|
17304
|
+
const font = this.fontScratch;
|
|
17305
|
+
resolveFontInto(node, font);
|
|
17021
17306
|
const maxLines = editable ? void 0 : this.numberProp(node, "maxLines");
|
|
17022
|
-
const
|
|
17023
|
-
|
|
17024
|
-
|
|
17025
|
-
|
|
17026
|
-
|
|
17027
|
-
|
|
17028
|
-
|
|
17029
|
-
|
|
17030
|
-
|
|
17031
|
-
|
|
17032
|
-
|
|
17033
|
-
|
|
17034
|
-
|
|
17035
|
-
|
|
17036
|
-
|
|
17037
|
-
|
|
17038
|
-
|
|
17039
|
-
|
|
17307
|
+
const maxWidth = isFinite(effective.maxWidth) ? Math.max(0, effective.maxWidth - paddingH) : void 0;
|
|
17308
|
+
const exactWidth = isFinite(effective.maxWidth) && effective.minWidth === effective.maxWidth;
|
|
17309
|
+
const wrap = this.textWrapProp(node);
|
|
17310
|
+
const lines = maxLines !== void 0 && maxLines >= 1 ? Math.floor(maxLines) : void 0;
|
|
17311
|
+
const overflow = editable ? "clip" : this.textOverflowProp(node);
|
|
17312
|
+
const runs = spans.length > 0 ? spans : void 0;
|
|
17313
|
+
/**
|
|
17314
|
+
* The node's own last paragraph, when nothing it was laid out
|
|
17315
|
+
* from has changed.
|
|
17316
|
+
*
|
|
17317
|
+
* Flex asks a text leaf three or four questions a pass (its
|
|
17318
|
+
* basis, its min- and max-content sizes, its final size), and
|
|
17319
|
+
* each one went to the measurer as a new request, whose cache
|
|
17320
|
+
* key spells out every field and the whole text before it can
|
|
17321
|
+
* look anything up. That was a fifth of a full layout pass over
|
|
17322
|
+
* the benchmark list with every answer already cached. Compared
|
|
17323
|
+
* field by field here it is a handful of identity checks, and a
|
|
17324
|
+
* paragraph that fits at the width it was laid out at also
|
|
17325
|
+
* answers any wider question, which is most of the questions a
|
|
17326
|
+
* row of labels is asked.
|
|
17327
|
+
*/
|
|
17328
|
+
const generation = this.textMeasurer.generation;
|
|
17329
|
+
const memo = maxWidth === void 0 ? rec.textUnbounded : rec.textBounded;
|
|
17330
|
+
const last = memo?.request;
|
|
17331
|
+
let paragraph;
|
|
17332
|
+
let asked;
|
|
17333
|
+
if (memo !== null && last !== void 0 && generation !== void 0 && memo.generation === generation && last.text === text && last.fontSize === font.fontSize && last.fontFamily === font.fontFamily && last.fontWeight === font.fontWeight && last.lineHeight === font.lineHeight && last.letterSpacing === font.letterSpacing && last.fontStyle === font.fontStyle && last.fontStretch === font.fontStretch && last.fontVariant === font.fontVariant && last.fontKerning === font.fontKerning && last.wrap === wrap && last.maxLines === lines && last.overflow === overflow && last.spans === runs && (last.maxWidth === maxWidth && last.exactWidth === exactWidth || memo.fitsAnyWidth && fitsWidth(memo.paragraph, maxWidth))) {
|
|
17334
|
+
paragraph = memo.paragraph;
|
|
17335
|
+
asked = last;
|
|
17336
|
+
} else {
|
|
17337
|
+
const request = {
|
|
17338
|
+
text,
|
|
17339
|
+
fontSize: font.fontSize,
|
|
17340
|
+
fontFamily: font.fontFamily,
|
|
17341
|
+
fontWeight: font.fontWeight,
|
|
17342
|
+
lineHeight: font.lineHeight,
|
|
17343
|
+
letterSpacing: font.letterSpacing,
|
|
17344
|
+
fontStyle: font.fontStyle,
|
|
17345
|
+
fontStretch: font.fontStretch,
|
|
17346
|
+
fontVariant: font.fontVariant,
|
|
17347
|
+
fontKerning: font.fontKerning,
|
|
17348
|
+
maxWidth,
|
|
17349
|
+
exactWidth,
|
|
17350
|
+
wrap,
|
|
17351
|
+
maxLines: lines,
|
|
17352
|
+
overflow,
|
|
17353
|
+
spans: runs
|
|
17354
|
+
};
|
|
17355
|
+
paragraph = this.textMeasurer.layout(request);
|
|
17356
|
+
asked = request;
|
|
17357
|
+
const fresh = {
|
|
17358
|
+
request,
|
|
17359
|
+
paragraph,
|
|
17360
|
+
generation: generation ?? -1,
|
|
17361
|
+
fitsAnyWidth: layoutFitsAnyWidth(request, paragraph)
|
|
17362
|
+
};
|
|
17363
|
+
if (maxWidth === void 0) rec.textUnbounded = fresh;
|
|
17364
|
+
else rec.textBounded = fresh;
|
|
17365
|
+
}
|
|
17040
17366
|
let width = paragraph.width;
|
|
17041
17367
|
let minContentWidth = paragraph.minContentWidth;
|
|
17042
17368
|
let maxContentWidth = paragraph.maxContentWidth;
|
|
17043
17369
|
if (placeholder !== void 0 && placeholder.length > 0) {
|
|
17044
17370
|
const hint = this.textMeasurer.layout({
|
|
17045
|
-
...
|
|
17371
|
+
...asked,
|
|
17046
17372
|
text: placeholder
|
|
17047
17373
|
});
|
|
17048
17374
|
width = Math.max(width, hint.width);
|
|
@@ -18225,7 +18551,28 @@ var LayoutEngine = class {
|
|
|
18225
18551
|
const props = node.properties;
|
|
18226
18552
|
const base = this.percentBase;
|
|
18227
18553
|
if (rec.propsPass === this.layoutPass && rec.propsBaseWidth === base.width && rec.propsBaseHeight === base.height) return;
|
|
18554
|
+
/**
|
|
18555
|
+
* What an earlier pass resolved, when none of what it read has
|
|
18556
|
+
* changed: the node's own properties (`propertyVersion`), and the
|
|
18557
|
+
* percentage base only on an axis that has a percentage.
|
|
18558
|
+
*
|
|
18559
|
+
* Each pass used to read every property below again for every node
|
|
18560
|
+
* it reached, and a full pass reaches all of them: a sixth of a full
|
|
18561
|
+
* pass over the benchmark list, where nothing had changed since the
|
|
18562
|
+
* last. A node whose resolution read anything inherited (a logical
|
|
18563
|
+
* edge or a scroll container's direction) or another node (a scroll
|
|
18564
|
+
* leader) is resolved again every pass, as it always was; they are
|
|
18565
|
+
* few. The record keeps these fields across `reset()` for this.
|
|
18566
|
+
*/
|
|
18567
|
+
if (rec.propsFromNodeAlone && rec.propsVersion === node.propertyVersion && (!rec.percentWidth || rec.propsBaseWidth === base.width) && (!rec.percentHeight || rec.propsBaseHeight === base.height)) {
|
|
18568
|
+
rec.propsPass = this.layoutPass;
|
|
18569
|
+
rec.propsBaseWidth = base.width;
|
|
18570
|
+
rec.propsBaseHeight = base.height;
|
|
18571
|
+
if (rec.lifted) this.liftedNodes.add(node);
|
|
18572
|
+
return;
|
|
18573
|
+
}
|
|
18228
18574
|
rec.propsPass = this.layoutPass;
|
|
18575
|
+
rec.propsVersion = node.propertyVersion;
|
|
18229
18576
|
rec.propsBaseWidth = base.width;
|
|
18230
18577
|
rec.propsBaseHeight = base.height;
|
|
18231
18578
|
rec.percentWidth = hasPercentLength(props, WIDTH_RELATIVE);
|
|
@@ -18266,6 +18613,7 @@ var LayoutEngine = class {
|
|
|
18266
18613
|
const overflow = props.get("overflow");
|
|
18267
18614
|
rec.scrollable = node.type === "scroll-view" || overflow === "scroll" || overflow === "auto" || overflow === "hidden" && scrollLeaderOf(node) !== null;
|
|
18268
18615
|
rec.scrollsText = node.type === "editable-text";
|
|
18616
|
+
rec.hidesScrollbars = props.get("scrollbars") === "none";
|
|
18269
18617
|
rec.textScrollbars = rec.scrollsText && props.get("multiline") === true;
|
|
18270
18618
|
rec.mirrored = (rec.scrollable || rec.textScrollbars) && this.startEdge(node) === "right";
|
|
18271
18619
|
rec.clips = rec.scrollable || overflow === "hidden" || node.type === "editable-text";
|
|
@@ -18277,9 +18625,11 @@ var LayoutEngine = class {
|
|
|
18277
18625
|
rec.left = this.lengthProp(node, "left", base.width) ?? insetH;
|
|
18278
18626
|
rec.zIndex = this.numberProp(node, "zIndex") ?? 0;
|
|
18279
18627
|
this.setLifted(node, rec, node.properties.get("lift") === true);
|
|
18628
|
+
if (rec.lifted) this.liftedNodes.add(node);
|
|
18280
18629
|
rec.liftBoundary = node.properties.get("liftBoundary") === true;
|
|
18281
18630
|
const ratio = this.numberProp(node, "aspectRatio");
|
|
18282
18631
|
rec.aspectRatio = ratio !== void 0 && ratio > 0 ? ratio : void 0;
|
|
18632
|
+
rec.propsFromNodeAlone = !hasLogicalSpacing(props) && !rec.scrollable && !rec.textScrollbars && overflow !== "hidden";
|
|
18283
18633
|
}
|
|
18284
18634
|
/**
|
|
18285
18635
|
* Sizes with a fixed width:height ratio (CSS `aspect-ratio`). The
|
|
@@ -20441,15 +20791,10 @@ var ScrollLayerCache = class {
|
|
|
20441
20791
|
* is how every one of those ends.
|
|
20442
20792
|
*/
|
|
20443
20793
|
refused = /* @__PURE__ */ new Set();
|
|
20444
|
-
geometryVersion = NaN;
|
|
20445
20794
|
dpr = 0;
|
|
20446
20795
|
surfaceWidth = 0;
|
|
20447
20796
|
surfaceHeight = 0;
|
|
20448
20797
|
frame = null;
|
|
20449
|
-
/** Whether this frame changed nothing a layer could not absorb: no layout, children, content or environment. */
|
|
20450
|
-
calm = false;
|
|
20451
|
-
/** Nodes this frame dirtied, and every ancestor of each, less a container that only scrolled. */
|
|
20452
|
-
disturbed = /* @__PURE__ */ new Set();
|
|
20453
20798
|
/** Consecutive frames each container without a layer has scrolled quietly. See `SCROLL_LAYER_SETTLE_FRAMES`. */
|
|
20454
20799
|
streaks = /* @__PURE__ */ new WeakMap();
|
|
20455
20800
|
/**
|
|
@@ -20481,46 +20826,26 @@ var ScrollLayerCache = class {
|
|
|
20481
20826
|
/**
|
|
20482
20827
|
* Settles which layers this frame's changes leave standing, before
|
|
20483
20828
|
* anything is drawn. Every rule here drops; none keeps. A frame the
|
|
20484
|
-
* caller could not vouch for drops them all.
|
|
20829
|
+
* caller could not vouch for drops them all. What changed inside a
|
|
20830
|
+
* container is not settled here but drawn over: see \`damage\`.
|
|
20485
20831
|
*/
|
|
20486
20832
|
beginFrame(changes, dpr, surfaceWidth, surfaceHeight) {
|
|
20487
20833
|
const interrupted = this.open;
|
|
20488
20834
|
this.open = true;
|
|
20489
|
-
this.
|
|
20490
|
-
this.calm = changes !== void 0;
|
|
20491
|
-
for (const [node, flags] of changes?.frame.entries() ?? []) {
|
|
20492
|
-
if ((flags & -355) !== 0) {
|
|
20493
|
-
this.calm = false;
|
|
20494
|
-
break;
|
|
20495
|
-
}
|
|
20496
|
-
for (let current = (flags & 32) !== 0 ? node.parent : node; current !== null; current = current.parent) this.disturbed.add(current);
|
|
20497
|
-
}
|
|
20498
|
-
if (interrupted || changes === void 0 || dpr !== this.dpr || surfaceWidth !== this.surfaceWidth || surfaceHeight !== this.surfaceHeight || changes.geometryVersion !== this.geometryVersion) {
|
|
20835
|
+
if (interrupted || changes === void 0 || dpr !== this.dpr || surfaceWidth !== this.surfaceWidth || surfaceHeight !== this.surfaceHeight) {
|
|
20499
20836
|
this.dropAll();
|
|
20500
20837
|
this.dpr = dpr;
|
|
20501
20838
|
this.surfaceWidth = surfaceWidth;
|
|
20502
20839
|
this.surfaceHeight = surfaceHeight;
|
|
20503
|
-
this.geometryVersion = changes?.geometryVersion ?? NaN;
|
|
20504
20840
|
this.frame = changes?.frame ?? null;
|
|
20505
20841
|
return;
|
|
20506
20842
|
}
|
|
20507
20843
|
const frame = changes.frame;
|
|
20508
20844
|
this.frame = frame;
|
|
20509
|
-
|
|
20845
|
+
for (const node of this.layers.keys()) if (dirtyAbove(frame, node)) this.forget(node);
|
|
20510
20846
|
for (const [node, flags] of frame.entries()) {
|
|
20511
|
-
if (
|
|
20512
|
-
|
|
20513
|
-
return;
|
|
20514
|
-
}
|
|
20515
|
-
const scrolledOnly = flags === 32;
|
|
20516
|
-
for (let current = scrolledOnly ? node.parent : node; current !== null; current = current.parent) this.forget(current);
|
|
20517
|
-
}
|
|
20518
|
-
for (const node of [...this.layers.keys(), ...this.refused]) for (let ancestor = node.parent; ancestor !== null; ancestor = ancestor.parent) {
|
|
20519
|
-
const flags = frame.dirtyFlagsFor(ancestor);
|
|
20520
|
-
if (flags !== 0 && flags !== 32) {
|
|
20521
|
-
this.forget(node);
|
|
20522
|
-
break;
|
|
20523
|
-
}
|
|
20847
|
+
if (flags === 32) continue;
|
|
20848
|
+
for (const refused of this.refused) if (node === refused || contains(node, refused) || contains(refused, node)) this.refused.delete(refused);
|
|
20524
20849
|
}
|
|
20525
20850
|
}
|
|
20526
20851
|
/** Drops the layers the frame did not composite: their container was culled, removed, or drawn directly. */
|
|
@@ -20534,14 +20859,16 @@ var ScrollLayerCache = class {
|
|
|
20534
20859
|
}
|
|
20535
20860
|
/**
|
|
20536
20861
|
* Whether a container with no layer should get one now: it has
|
|
20537
|
-
* scrolled on the last few frames running with nothing
|
|
20538
|
-
*
|
|
20539
|
-
*
|
|
20862
|
+
* scrolled on the last few frames running with nothing above it
|
|
20863
|
+
* changing, which is the evidence a layer would be kept, and it has
|
|
20864
|
+
* not just shown it holds something a layer cannot. What changes
|
|
20865
|
+
* inside it is absorbed item by item (see \`damage\`), so a lazy list
|
|
20866
|
+
* mounting rows as it scrolls keeps its layer.
|
|
20540
20867
|
*/
|
|
20541
20868
|
wants(node) {
|
|
20542
20869
|
const frame = this.frame;
|
|
20543
20870
|
if (frame === null || this.layers.size >= MAX_LAYERS || this.refused.has(node)) return false;
|
|
20544
|
-
if (
|
|
20871
|
+
if (dirtyAbove(frame, node)) {
|
|
20545
20872
|
this.streaks.delete(node);
|
|
20546
20873
|
return false;
|
|
20547
20874
|
}
|
|
@@ -20571,9 +20898,10 @@ var ScrollLayerCache = class {
|
|
|
20571
20898
|
offsetX: placement.offsetX,
|
|
20572
20899
|
offsetY: placement.offsetY,
|
|
20573
20900
|
dpr: this.dpr,
|
|
20574
|
-
scrollX: rec.scrollX,
|
|
20575
|
-
scrollY: rec.scrollY,
|
|
20576
|
-
used: true
|
|
20901
|
+
scrollX: snapScroll(rec.scrollX, this.dpr),
|
|
20902
|
+
scrollY: snapScroll(rec.scrollY, this.dpr),
|
|
20903
|
+
used: true,
|
|
20904
|
+
items: /* @__PURE__ */ new Map()
|
|
20577
20905
|
};
|
|
20578
20906
|
this.layers.set(node, layer);
|
|
20579
20907
|
this.stats.built++;
|
|
@@ -20594,8 +20922,8 @@ var ScrollLayerCache = class {
|
|
|
20594
20922
|
* device pixels, which a copy cannot follow without resampling.
|
|
20595
20923
|
*/
|
|
20596
20924
|
shift(layer, rec) {
|
|
20597
|
-
const exactX = (layer.scrollX - rec.scrollX) * layer.dpr;
|
|
20598
|
-
const exactY = (layer.scrollY - rec.scrollY) * layer.dpr;
|
|
20925
|
+
const exactX = (layer.scrollX - snapScroll(rec.scrollX, layer.dpr)) * layer.dpr;
|
|
20926
|
+
const exactY = (layer.scrollY - snapScroll(rec.scrollY, layer.dpr)) * layer.dpr;
|
|
20599
20927
|
const dx = Math.round(exactX);
|
|
20600
20928
|
const dy = Math.round(exactY);
|
|
20601
20929
|
if (Math.abs(exactX - dx) > EPSILON || Math.abs(exactY - dy) > EPSILON) return null;
|
|
@@ -20633,6 +20961,79 @@ var ScrollLayerCache = class {
|
|
|
20633
20961
|
});
|
|
20634
20962
|
return strips;
|
|
20635
20963
|
}
|
|
20964
|
+
/**
|
|
20965
|
+
* The parts of a layer this frame's changes inside the container left
|
|
20966
|
+
* wrong, in the layer's pixels, or \`'all'\` when that is most of it.
|
|
20967
|
+
* Called after \`shift\`, so the layer's offset is the frame's.
|
|
20968
|
+
*
|
|
20969
|
+
* Judged item by item, an item being one of the container's children
|
|
20970
|
+
* (through any fragments): a lazy list's row, say. An item added,
|
|
20971
|
+
* removed or moved is wrong where it was and where it is; an item with
|
|
20972
|
+
* anything changed inside it is wrong where it is. Whatever none of
|
|
20973
|
+
* that touches is right already, which in a lazy list scrolling is
|
|
20974
|
+
* everything on screen: the rows it mounts and unmounts are in its
|
|
20975
|
+
* overscan, and the spacers that stand for the rest only grow and
|
|
20976
|
+
* shrink off the top and bottom.
|
|
20977
|
+
*/
|
|
20978
|
+
damage(layer, container, layout) {
|
|
20979
|
+
const frame = this.frame;
|
|
20980
|
+
if (frame === null) return "all";
|
|
20981
|
+
const boxes = [];
|
|
20982
|
+
for (const [node, flags] of frame.entries()) {
|
|
20983
|
+
if (node === container) {
|
|
20984
|
+
if ((flags & 128) !== 0) return "all";
|
|
20985
|
+
continue;
|
|
20986
|
+
}
|
|
20987
|
+
const item = itemHolding(container, node);
|
|
20988
|
+
if (item === null) continue;
|
|
20989
|
+
const was = layer.items.get(item);
|
|
20990
|
+
const now = boxOf(layout, item);
|
|
20991
|
+
if (was !== void 0) boxes.push(was);
|
|
20992
|
+
if (now !== void 0 && (was === void 0 || !sameBox(was, now))) boxes.push(now);
|
|
20993
|
+
}
|
|
20994
|
+
const seen = /* @__PURE__ */ new Set();
|
|
20995
|
+
for (const item of itemsOf(container)) {
|
|
20996
|
+
seen.add(item);
|
|
20997
|
+
const now = boxOf(layout, item);
|
|
20998
|
+
const was = layer.items.get(item);
|
|
20999
|
+
if (now === void 0) continue;
|
|
21000
|
+
if (was === void 0) boxes.push(now);
|
|
21001
|
+
else if (!sameBox(was, now)) boxes.push(was, now);
|
|
21002
|
+
}
|
|
21003
|
+
for (const [item, was] of layer.items) if (!seen.has(item)) boxes.push(was);
|
|
21004
|
+
const rects = [];
|
|
21005
|
+
let area = 0;
|
|
21006
|
+
for (const box of boxes) {
|
|
21007
|
+
const rect = this.toLayer(layer, box);
|
|
21008
|
+
if (rect !== null && !rects.some((other) => sameBox(other, rect))) {
|
|
21009
|
+
rects.push(rect);
|
|
21010
|
+
area += rect.width * rect.height;
|
|
21011
|
+
}
|
|
21012
|
+
}
|
|
21013
|
+
return area > layer.width * layer.height * MOSTLY ? "all" : rects;
|
|
21014
|
+
}
|
|
21015
|
+
/** Notes where the container's items are, for the next frame's \`damage\`. */
|
|
21016
|
+
remember(layer, container, layout) {
|
|
21017
|
+
layer.items.clear();
|
|
21018
|
+
for (const item of itemsOf(container)) {
|
|
21019
|
+
const box = boxOf(layout, item);
|
|
21020
|
+
if (box !== void 0) layer.items.set(item, box);
|
|
21021
|
+
}
|
|
21022
|
+
}
|
|
21023
|
+
/** An item's box in the layer's pixels, widened for what paints past it, or null off the layer. */
|
|
21024
|
+
toLayer(layer, box) {
|
|
21025
|
+
const dpr = layer.dpr;
|
|
21026
|
+
const left = Math.max(0, Math.floor(layer.offsetX + dpr * (box.x - layer.scrollX - 8)));
|
|
21027
|
+
const top = Math.max(0, Math.floor(layer.offsetY + dpr * (box.y - layer.scrollY - 8)));
|
|
21028
|
+
const right = Math.min(layer.width, Math.ceil(layer.offsetX + dpr * (box.x + box.width - layer.scrollX + 8)));
|
|
21029
|
+
const bottom = Math.min(layer.height, Math.ceil(layer.offsetY + dpr * (box.y + box.height - layer.scrollY + 8)));
|
|
21030
|
+
return right > left && bottom > top ? {
|
|
21031
|
+
x: left,
|
|
21032
|
+
y: top,
|
|
21033
|
+
width: right - left,
|
|
21034
|
+
height: bottom - top
|
|
21035
|
+
} : null;
|
|
21036
|
+
}
|
|
20636
21037
|
/** Stops building layers for a container until its subtree is next dirtied. */
|
|
20637
21038
|
refuse(node) {
|
|
20638
21039
|
this.drop(node);
|
|
@@ -20655,14 +21056,63 @@ var ScrollLayerCache = class {
|
|
|
20655
21056
|
this.refused.delete(node);
|
|
20656
21057
|
}
|
|
20657
21058
|
};
|
|
20658
|
-
/**
|
|
21059
|
+
/**
|
|
21060
|
+
* Whether an ancestor's properties or environment changed. An inherited
|
|
21061
|
+
* value set on an ancestor marks every descendant that reads it as well
|
|
21062
|
+
* (the graph re-provides it), so what it changes inside the container
|
|
21063
|
+
* is damage like any other; this keeps the old caution for the rest.
|
|
21064
|
+
* An ancestor marked for paint alone is the runtime asking for a frame
|
|
21065
|
+
* (a scrollbar fading, an inspector outline), which nothing inside the
|
|
21066
|
+
* container hears; one laid out again either leaves the container where
|
|
21067
|
+
* it was or moves it, which \`fits\` hears about; one whose children
|
|
21068
|
+
* changed has changed nothing inside this one. The last two happen on
|
|
21069
|
+
* every frame a lazy list mounts a row, since a layout need is marked
|
|
21070
|
+
* all the way up.
|
|
21071
|
+
*/
|
|
20659
21072
|
function dirtyAbove(frame, node) {
|
|
20660
|
-
for (let ancestor = node.parent; ancestor !== null; ancestor = ancestor.parent)
|
|
20661
|
-
|
|
20662
|
-
|
|
20663
|
-
|
|
21073
|
+
for (let ancestor = node.parent; ancestor !== null; ancestor = ancestor.parent) if ((frame.dirtyFlagsFor(ancestor) & INHERITED) !== 0) return true;
|
|
21074
|
+
return false;
|
|
21075
|
+
}
|
|
21076
|
+
/** What an ancestor's dirt can say about how its descendants look. */
|
|
21077
|
+
const INHERITED = 192;
|
|
21078
|
+
/** Whether \`ancestor\` is above \`node\` in the tree. */
|
|
21079
|
+
function contains(ancestor, node) {
|
|
21080
|
+
for (let current = node.parent; current !== null; current = current.parent) if (current === ancestor) return true;
|
|
20664
21081
|
return false;
|
|
20665
21082
|
}
|
|
21083
|
+
/** A container's items: its children, and the children of any fragment among them, in order. */
|
|
21084
|
+
function itemsOf(container) {
|
|
21085
|
+
const items = [];
|
|
21086
|
+
const visit = (parent) => {
|
|
21087
|
+
for (let child = parent.firstChild; child !== null; child = child.nextSibling) if (child.type === "fragment") visit(child);
|
|
21088
|
+
else items.push(child);
|
|
21089
|
+
};
|
|
21090
|
+
visit(container);
|
|
21091
|
+
return items;
|
|
21092
|
+
}
|
|
21093
|
+
/** The item of a container that holds a node, the node itself if it is one, or null outside the container or above its items. */
|
|
21094
|
+
function itemHolding(container, node) {
|
|
21095
|
+
let item = null;
|
|
21096
|
+
for (let current = node; current !== null; current = current.parent) {
|
|
21097
|
+
if (current === container) return item;
|
|
21098
|
+
if (current.type !== "fragment") item = current;
|
|
21099
|
+
}
|
|
21100
|
+
return null;
|
|
21101
|
+
}
|
|
21102
|
+
function sameBox(a, b) {
|
|
21103
|
+
return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
|
21104
|
+
}
|
|
21105
|
+
function boxOf(layout, node) {
|
|
21106
|
+
const rec = layout.recordFor(node);
|
|
21107
|
+
return rec === void 0 ? void 0 : {
|
|
21108
|
+
x: rec.x,
|
|
21109
|
+
y: rec.y,
|
|
21110
|
+
width: rec.width,
|
|
21111
|
+
height: rec.height
|
|
21112
|
+
};
|
|
21113
|
+
}
|
|
21114
|
+
/** Damage over this share of a layer repaints all of it in one walk. */
|
|
21115
|
+
const MOSTLY = .5;
|
|
20666
21116
|
/**
|
|
20667
21117
|
* Where a scroll container's viewport lands in device pixels, or null
|
|
20668
21118
|
* when a layer could not reproduce the direct draw exactly: no
|
|
@@ -20708,6 +21158,273 @@ function clearLayer(ctx, width, height) {
|
|
|
20708
21158
|
ctx.clearRect(0, 0, width, height);
|
|
20709
21159
|
}
|
|
20710
21160
|
//#endregion
|
|
21161
|
+
//#region src/rendering/canvas2d/TextRasterCache.ts
|
|
21162
|
+
/**
|
|
21163
|
+
* Text kept as pixels, so a frame that only moved it copies it rather
|
|
21164
|
+
* than drawing its glyphs again.
|
|
21165
|
+
*
|
|
21166
|
+
* **Why.** Drawing glyphs is most of what a frame of text costs:
|
|
21167
|
+
* profiled while flicking through a file in Easel, `fillText` was 1,064
|
|
21168
|
+
* of the renderer's 1,985 ms. A scroll moves every line on screen and
|
|
21169
|
+
* changes none of them, so every one of those glyphs was drawn again
|
|
21170
|
+
* only to land a few pixels from where it was. In WKWebView a frame of
|
|
21171
|
+
* that cost 4 to 7 ms, which is more than a 165 Hz refresh holds.
|
|
21172
|
+
*
|
|
21173
|
+
* **What is kept.** What `drawText` draws for one node, drawn once into
|
|
21174
|
+
* a canvas of its own at the device scale and fraction of a pixel it
|
|
21175
|
+
* lands on, and copied onto the frame from then on. The copy lands on
|
|
21176
|
+
* whole device pixels, so the pixels are the ones a direct draw at that
|
|
21177
|
+
* fraction would have made, and a scroll, which moves by whole device
|
|
21178
|
+
* pixels, keeps the fraction.
|
|
21179
|
+
*
|
|
21180
|
+
* **When it is drawn again.** When anything `drawText` reads changes:
|
|
21181
|
+
* the text, its runs, its font, its colour, its box, the scale, or the
|
|
21182
|
+
* fraction of a pixel it sits at. Each is compared every frame, which is
|
|
21183
|
+
* a few comparisons; a run list or a colour that has not changed is the
|
|
21184
|
+
* same object it was.
|
|
21185
|
+
*
|
|
21186
|
+
* **What it is not for.** Text that changes on most frames (a clock, a
|
|
21187
|
+
* counter, an animated colour) would be drawn twice a frame, into the
|
|
21188
|
+
* cache and out of it. A node redrawn on most of its recent frames is
|
|
21189
|
+
* drawn directly instead, until it has been still for a while.
|
|
21190
|
+
*/
|
|
21191
|
+
var TextRasterCache = class {
|
|
21192
|
+
createCanvas;
|
|
21193
|
+
entries = /* @__PURE__ */ new Map();
|
|
21194
|
+
frame = 0;
|
|
21195
|
+
pixels = 0;
|
|
21196
|
+
/** Draws served from the cache and drawn into it, since the last reset. For budget specs. */
|
|
21197
|
+
stats = {
|
|
21198
|
+
copied: 0,
|
|
21199
|
+
drawn: 0,
|
|
21200
|
+
direct: 0
|
|
21201
|
+
};
|
|
21202
|
+
constructor(createCanvas) {
|
|
21203
|
+
this.createCanvas = createCanvas;
|
|
21204
|
+
}
|
|
21205
|
+
beginFrame() {
|
|
21206
|
+
this.frame++;
|
|
21207
|
+
}
|
|
21208
|
+
/** Lets go of text not drawn lately: removed, scrolled far away, or hidden. */
|
|
21209
|
+
endFrame() {
|
|
21210
|
+
for (const [node, entry] of this.entries) if (this.frame - entry.used > IDLE_FRAMES) this.drop(node, entry);
|
|
21211
|
+
}
|
|
21212
|
+
dropAll() {
|
|
21213
|
+
for (const [node, entry] of this.entries) this.drop(node, entry);
|
|
21214
|
+
}
|
|
21215
|
+
resetStats() {
|
|
21216
|
+
this.stats.copied = 0;
|
|
21217
|
+
this.stats.drawn = 0;
|
|
21218
|
+
this.stats.direct = 0;
|
|
21219
|
+
}
|
|
21220
|
+
/**
|
|
21221
|
+
* Draws a node's text from the cache, drawing it into the cache first
|
|
21222
|
+
* when what it holds is out of date. False when the text should be
|
|
21223
|
+
* drawn directly: a context that cannot say where it draws, one that
|
|
21224
|
+
* rotates or skews, text too large to keep, or text that changes too
|
|
21225
|
+
* often to be worth keeping.
|
|
21226
|
+
*/
|
|
21227
|
+
draw(ctx, node, box, paint, measurer) {
|
|
21228
|
+
const m = ctx.getTransform?.();
|
|
21229
|
+
if (m === void 0 || m.b !== 0 || m.c !== 0 || !(m.a > 0) || !(m.d > 0)) return false;
|
|
21230
|
+
let entry = this.entries.get(node);
|
|
21231
|
+
if (entry !== void 0 && entry.directUntil > this.frame) {
|
|
21232
|
+
entry.used = this.frame;
|
|
21233
|
+
this.stats.direct++;
|
|
21234
|
+
return false;
|
|
21235
|
+
}
|
|
21236
|
+
let extent = entry !== void 0 && entry.drawnFrom(paint, box, m.a, m.d) ? entry.extent : null;
|
|
21237
|
+
if (extent === null) {
|
|
21238
|
+
extent = inkExtent(box, paint, measurer);
|
|
21239
|
+
if (extent === null) return false;
|
|
21240
|
+
}
|
|
21241
|
+
const left = m.a * (box.x + extent.left) + m.e;
|
|
21242
|
+
const top = m.d * (box.y + extent.top) + m.f;
|
|
21243
|
+
const x = Math.floor(left);
|
|
21244
|
+
const y = Math.floor(top);
|
|
21245
|
+
const width = Math.ceil(m.a * (extent.right - extent.left) + (left - x)) + 1;
|
|
21246
|
+
const height = Math.ceil(m.d * (extent.bottom - extent.top) + (top - y)) + 1;
|
|
21247
|
+
if (width * height > MAX_ENTRY_PIXELS) return false;
|
|
21248
|
+
const offsetX = m.e - x;
|
|
21249
|
+
const offsetY = m.f - y;
|
|
21250
|
+
let raster = entry ?? null;
|
|
21251
|
+
if (raster === null || !raster.holds(paint, box, m.a, m.d, offsetX, offsetY, width, height)) {
|
|
21252
|
+
raster = this.redraw(node, entry, paint, box, measurer, m.a, m.d, offsetX, offsetY, width, height, extent);
|
|
21253
|
+
if (raster === null) return false;
|
|
21254
|
+
}
|
|
21255
|
+
raster.used = this.frame;
|
|
21256
|
+
ctx.save();
|
|
21257
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
21258
|
+
ctx.drawImage(raster.canvas, x, y, raster.canvasWidth, raster.canvasHeight);
|
|
21259
|
+
ctx.restore();
|
|
21260
|
+
this.stats.copied++;
|
|
21261
|
+
return true;
|
|
21262
|
+
}
|
|
21263
|
+
redraw(node, previous, paint, box, measurer, scaleX, scaleY, offsetX, offsetY, width, height, extent) {
|
|
21264
|
+
let entry = previous;
|
|
21265
|
+
if (entry !== void 0) {
|
|
21266
|
+
entry.churn = this.frame - entry.drawnAt <= CHURN_FRAMES ? entry.churn + 1 : 0;
|
|
21267
|
+
if (entry.churn >= CHURN_LIMIT) {
|
|
21268
|
+
entry.churn = 0;
|
|
21269
|
+
entry.directUntil = this.frame + DIRECT_FRAMES;
|
|
21270
|
+
entry.used = this.frame;
|
|
21271
|
+
this.stats.direct++;
|
|
21272
|
+
return null;
|
|
21273
|
+
}
|
|
21274
|
+
}
|
|
21275
|
+
if (entry === void 0 || entry.canvasWidth < width || entry.canvasHeight < height) {
|
|
21276
|
+
const canvasWidth = Math.max(width, entry?.canvasWidth ?? 0);
|
|
21277
|
+
const canvasHeight = Math.max(height, entry?.canvasHeight ?? 0);
|
|
21278
|
+
const canvas = this.createCanvas(canvasWidth, canvasHeight);
|
|
21279
|
+
const context = canvas?.getContext("2d");
|
|
21280
|
+
if (canvas === null || !context) return null;
|
|
21281
|
+
this.make(canvasWidth * canvasHeight);
|
|
21282
|
+
if (entry !== void 0) {
|
|
21283
|
+
this.pixels -= entry.canvasWidth * entry.canvasHeight;
|
|
21284
|
+
entry.canvas = canvas;
|
|
21285
|
+
entry.context = context;
|
|
21286
|
+
entry.canvasWidth = canvasWidth;
|
|
21287
|
+
entry.canvasHeight = canvasHeight;
|
|
21288
|
+
} else {
|
|
21289
|
+
entry = new TextRaster(canvas, context, canvasWidth, canvasHeight);
|
|
21290
|
+
this.entries.set(node, entry);
|
|
21291
|
+
}
|
|
21292
|
+
this.pixels += canvasWidth * canvasHeight;
|
|
21293
|
+
}
|
|
21294
|
+
const context = entry.context;
|
|
21295
|
+
context.setTransform(1, 0, 0, 1, 0, 0);
|
|
21296
|
+
context.clearRect(0, 0, entry.canvasWidth, entry.canvasHeight);
|
|
21297
|
+
context.setTransform(scaleX, 0, 0, scaleY, offsetX, offsetY);
|
|
21298
|
+
context.globalAlpha = 1;
|
|
21299
|
+
drawText(context, box, paint, measurer);
|
|
21300
|
+
entry.remember(paint, box, scaleX, scaleY, offsetX, offsetY, width, height);
|
|
21301
|
+
entry.extent = extent;
|
|
21302
|
+
entry.drawnAt = this.frame;
|
|
21303
|
+
this.stats.drawn++;
|
|
21304
|
+
return entry;
|
|
21305
|
+
}
|
|
21306
|
+
/** Makes room for `needed` more pixels, letting go of what was drawn least recently. */
|
|
21307
|
+
make(needed) {
|
|
21308
|
+
if (this.pixels + needed <= MAX_PIXELS) return;
|
|
21309
|
+
const byAge = [...this.entries].sort((a, b) => a[1].used - b[1].used);
|
|
21310
|
+
for (const [node, entry] of byAge) {
|
|
21311
|
+
if (this.pixels + needed <= MAX_PIXELS || entry.used === this.frame) break;
|
|
21312
|
+
this.drop(node, entry);
|
|
21313
|
+
}
|
|
21314
|
+
}
|
|
21315
|
+
drop(node, entry) {
|
|
21316
|
+
this.entries.delete(node);
|
|
21317
|
+
this.pixels -= entry.canvasWidth * entry.canvasHeight;
|
|
21318
|
+
entry.canvas.width = 0;
|
|
21319
|
+
entry.canvas.height = 0;
|
|
21320
|
+
}
|
|
21321
|
+
};
|
|
21322
|
+
/** One node's text as pixels, and what it was drawn from. */
|
|
21323
|
+
var TextRaster = class {
|
|
21324
|
+
canvas;
|
|
21325
|
+
context;
|
|
21326
|
+
canvasWidth;
|
|
21327
|
+
canvasHeight;
|
|
21328
|
+
used = 0;
|
|
21329
|
+
drawnAt = 0;
|
|
21330
|
+
churn = 0;
|
|
21331
|
+
directUntil = 0;
|
|
21332
|
+
/** Where the ink reaches, from the box's corner, in layout pixels. */
|
|
21333
|
+
extent = null;
|
|
21334
|
+
text;
|
|
21335
|
+
spans;
|
|
21336
|
+
font = "";
|
|
21337
|
+
color = "";
|
|
21338
|
+
style = "";
|
|
21339
|
+
linkHover = 0;
|
|
21340
|
+
boxWidth = 0;
|
|
21341
|
+
boxHeight = 0;
|
|
21342
|
+
scaleX = 0;
|
|
21343
|
+
scaleY = 0;
|
|
21344
|
+
offsetX = 0;
|
|
21345
|
+
offsetY = 0;
|
|
21346
|
+
width = 0;
|
|
21347
|
+
height = 0;
|
|
21348
|
+
constructor(canvas, context, canvasWidth, canvasHeight) {
|
|
21349
|
+
this.canvas = canvas;
|
|
21350
|
+
this.context = context;
|
|
21351
|
+
this.canvasWidth = canvasWidth;
|
|
21352
|
+
this.canvasHeight = canvasHeight;
|
|
21353
|
+
}
|
|
21354
|
+
holds(paint, box, scaleX, scaleY, offsetX, offsetY, width, height) {
|
|
21355
|
+
return this.drawnFrom(paint, box, scaleX, scaleY) && Math.abs(this.offsetX - offsetX) < SAME_PLACE && Math.abs(this.offsetY - offsetY) < SAME_PLACE && this.width === width && this.height === height;
|
|
21356
|
+
}
|
|
21357
|
+
/** Whether the pixels are of this text, in this style, at this size and scale, wherever they are. */
|
|
21358
|
+
drawnFrom(paint, box, scaleX, scaleY) {
|
|
21359
|
+
return this.text === paint.text && this.spans === paint.spans && this.linkHover === paint.linkHover && this.boxWidth === box.width && this.boxHeight === box.height && this.scaleX === scaleX && this.scaleY === scaleY && this.font === fontOf(paint) && this.color === colorToCss(paint.textColor) && this.style === styleOf(paint);
|
|
21360
|
+
}
|
|
21361
|
+
remember(paint, box, scaleX, scaleY, offsetX, offsetY, width, height) {
|
|
21362
|
+
this.text = paint.text;
|
|
21363
|
+
this.spans = paint.spans;
|
|
21364
|
+
this.linkHover = paint.linkHover;
|
|
21365
|
+
this.boxWidth = box.width;
|
|
21366
|
+
this.boxHeight = box.height;
|
|
21367
|
+
this.scaleX = scaleX;
|
|
21368
|
+
this.scaleY = scaleY;
|
|
21369
|
+
this.offsetX = offsetX;
|
|
21370
|
+
this.offsetY = offsetY;
|
|
21371
|
+
this.width = width;
|
|
21372
|
+
this.height = height;
|
|
21373
|
+
this.font = fontOf(paint);
|
|
21374
|
+
this.color = colorToCss(paint.textColor);
|
|
21375
|
+
this.style = styleOf(paint);
|
|
21376
|
+
}
|
|
21377
|
+
};
|
|
21378
|
+
/**
|
|
21379
|
+
* The lines' extent, and a margin for what glyphs reach past their line
|
|
21380
|
+
* box (an italic's lean, a descender below a tight line height, an
|
|
21381
|
+
* underline). Null for text that draws nothing.
|
|
21382
|
+
*/
|
|
21383
|
+
function inkExtent(box, paint, measurer) {
|
|
21384
|
+
const placements = layoutTextLines(box, paint, measurer);
|
|
21385
|
+
if (placements.length === 0) return null;
|
|
21386
|
+
let left = Infinity;
|
|
21387
|
+
let top = Infinity;
|
|
21388
|
+
let right = -Infinity;
|
|
21389
|
+
let bottom = -Infinity;
|
|
21390
|
+
for (const line of placements) {
|
|
21391
|
+
left = Math.min(left, line.x);
|
|
21392
|
+
top = Math.min(top, line.y);
|
|
21393
|
+
right = Math.max(right, line.x + line.width);
|
|
21394
|
+
bottom = Math.max(bottom, line.y + line.height);
|
|
21395
|
+
}
|
|
21396
|
+
const margin = Math.ceil(paint.fontSize * INK_MARGIN) + 2;
|
|
21397
|
+
return {
|
|
21398
|
+
left: left - box.x - margin,
|
|
21399
|
+
top: top - box.y - margin,
|
|
21400
|
+
right: right - box.x + margin,
|
|
21401
|
+
bottom: bottom - box.y + margin
|
|
21402
|
+
};
|
|
21403
|
+
}
|
|
21404
|
+
function fontOf(paint) {
|
|
21405
|
+
return `${paint.fontStyle} ${paint.fontVariant} ${paint.fontWeight} ${paint.fontStretch} ${paint.fontSize}/${paint.lineHeight} ${paint.fontFamily}`;
|
|
21406
|
+
}
|
|
21407
|
+
/** Everything else `drawText` reads that places or decorates the glyphs. */
|
|
21408
|
+
function styleOf(paint) {
|
|
21409
|
+
return `${paint.letterSpacing}|${paint.fontKerning}|${paint.textDecoration}|${paint.textAlign}|${paint.verticalAlign}|${paint.textWrap}|${paint.maxLines}|${paint.textOverflow}|${paint.rtl}`;
|
|
21410
|
+
}
|
|
21411
|
+
/** How far past its line box, as a share of the font size, a glyph's ink may reach. */
|
|
21412
|
+
const INK_MARGIN = .5;
|
|
21413
|
+
/** Within this much of a device pixel, text is at the same place. */
|
|
21414
|
+
const SAME_PLACE = 1 / 256;
|
|
21415
|
+
/** The largest single piece of text kept: a few screens of a long paragraph. */
|
|
21416
|
+
const MAX_ENTRY_PIXELS = 4e6;
|
|
21417
|
+
/** Everything kept, at most: about 128 MB. */
|
|
21418
|
+
const MAX_PIXELS = 32e6;
|
|
21419
|
+
/** Frames text is kept for without being drawn: about two seconds at 60 Hz. */
|
|
21420
|
+
const IDLE_FRAMES = 120;
|
|
21421
|
+
/** Drawn again within this many frames of the last time counts toward churn. */
|
|
21422
|
+
const CHURN_FRAMES = 2;
|
|
21423
|
+
/** Times in a row text may churn before it is drawn directly. */
|
|
21424
|
+
const CHURN_LIMIT = 3;
|
|
21425
|
+
/** Frames churning text is drawn directly for before the cache tries it again. */
|
|
21426
|
+
const DIRECT_FRAMES = 60;
|
|
21427
|
+
//#endregion
|
|
20711
21428
|
//#region src/rendering/canvas2d/Canvas2DRenderer.ts
|
|
20712
21429
|
/**
|
|
20713
21430
|
* Canvas2D rendering backend.
|
|
@@ -20819,6 +21536,13 @@ var Canvas2DRenderer = class {
|
|
|
20819
21536
|
constructor(options) {
|
|
20820
21537
|
this.options = options;
|
|
20821
21538
|
this.layers = new ScrollLayerCache(options.createLayerCanvas ?? offscreenLayerCanvas, options.scrollLayerSettleFrames);
|
|
21539
|
+
this.textCache = options.textCache === false ? null : new TextRasterCache(options.createLayerCanvas ?? offscreenLayerCanvas);
|
|
21540
|
+
}
|
|
21541
|
+
/** Text kept as pixels; see `TextRasterCache.ts`. */
|
|
21542
|
+
textCache;
|
|
21543
|
+
/** What the text cache did since the last reset, or null with it off. For budget specs. */
|
|
21544
|
+
get textCacheStats() {
|
|
21545
|
+
return this.textCache?.stats ?? null;
|
|
20822
21546
|
}
|
|
20823
21547
|
/** What the scroll layers did since the last reset. For budget specs. */
|
|
20824
21548
|
get scrollLayerStats() {
|
|
@@ -20844,11 +21568,13 @@ var Canvas2DRenderer = class {
|
|
|
20844
21568
|
/** A restored scroll layer reads as healthy and holds nothing. */
|
|
20845
21569
|
surfaceRestored() {
|
|
20846
21570
|
this.layers.dropAll();
|
|
21571
|
+
this.textCache?.dropAll();
|
|
20847
21572
|
}
|
|
20848
21573
|
dispose() {
|
|
20849
21574
|
this.disposed = true;
|
|
20850
21575
|
this.scaledImages.dispose();
|
|
20851
21576
|
this.layers.dropAll();
|
|
21577
|
+
this.textCache?.dropAll();
|
|
20852
21578
|
}
|
|
20853
21579
|
render(root, context) {
|
|
20854
21580
|
const raw = this.surface.getContext2D();
|
|
@@ -20862,11 +21588,13 @@ var Canvas2DRenderer = class {
|
|
|
20862
21588
|
this.liftedPass.length = 0;
|
|
20863
21589
|
this.paintOwner = null;
|
|
20864
21590
|
this.layerDepth = 0;
|
|
21591
|
+
this.textCache?.beginFrame();
|
|
20865
21592
|
this.layers.beginFrame(this.options.scrollLayers === false ? void 0 : context.changes, this.surface.dpr, this.surface.physicalWidth, this.surface.physicalHeight);
|
|
20866
21593
|
this.renderNode(root, context, ctx, true, false);
|
|
20867
21594
|
this.renderLifted(context, ctx);
|
|
20868
21595
|
if (context.overlay !== void 0 && context.overlay.length > 0) drawOverlayShapes(ctx, context.overlay);
|
|
20869
21596
|
this.layers.endFrame();
|
|
21597
|
+
this.textCache?.endFrame();
|
|
20870
21598
|
}
|
|
20871
21599
|
/**
|
|
20872
21600
|
* Folds a node's style into the shared scratch and records that it
|
|
@@ -20931,7 +21659,7 @@ var Canvas2DRenderer = class {
|
|
|
20931
21659
|
if (rec.clips) this.popCull();
|
|
20932
21660
|
if (hasText) {
|
|
20933
21661
|
if (this.paintOwner !== node) this.resolvePaint(node);
|
|
20934
|
-
this.paintContent(ctx, rec, this.paint, context);
|
|
21662
|
+
this.paintContent(ctx, node, rec, this.paint, context);
|
|
20935
21663
|
}
|
|
20936
21664
|
if (rec.liftBoundary && this.liftedPass.length > liftedBefore) this.renderLifted(context, ctx, liftedBefore, node);
|
|
20937
21665
|
if (rec.clips) ctx.restore();
|
|
@@ -21205,7 +21933,7 @@ var Canvas2DRenderer = class {
|
|
|
21205
21933
|
} else ctx.strokeRect(x, y, width, height);
|
|
21206
21934
|
}
|
|
21207
21935
|
}
|
|
21208
|
-
paintContent(ctx, rec, paint, context) {
|
|
21936
|
+
paintContent(ctx, node, rec, paint, context) {
|
|
21209
21937
|
if (paint.text !== void 0) {
|
|
21210
21938
|
this.contentBox.x = rec.x + rec.paddingLeft - rec.scrollX;
|
|
21211
21939
|
this.contentBox.y = rec.y + rec.paddingTop - rec.scrollY;
|
|
@@ -21219,6 +21947,7 @@ var Canvas2DRenderer = class {
|
|
|
21219
21947
|
this.paintHighlightedText(ctx, paint, context);
|
|
21220
21948
|
return;
|
|
21221
21949
|
}
|
|
21950
|
+
if (this.textCache?.draw(ctx, node, this.contentBox, paint, context.text) === true) return;
|
|
21222
21951
|
drawText(ctx, this.contentBox, paint, context.text);
|
|
21223
21952
|
}
|
|
21224
21953
|
}
|
|
@@ -21320,8 +22049,21 @@ var Canvas2DRenderer = class {
|
|
|
21320
22049
|
layers.drop(node);
|
|
21321
22050
|
return false;
|
|
21322
22051
|
}
|
|
21323
|
-
|
|
22052
|
+
const damage = layers.damage(layer, node, context.layout);
|
|
22053
|
+
const wrong = damage === "all" ? [{
|
|
22054
|
+
x: 0,
|
|
22055
|
+
y: 0,
|
|
22056
|
+
width: layer.width,
|
|
22057
|
+
height: layer.height
|
|
22058
|
+
}] : damage;
|
|
22059
|
+
if (wrong.length > 0) {
|
|
22060
|
+
const front = layer.frontContext;
|
|
22061
|
+
front.setTransform(1, 0, 0, 1, 0, 0);
|
|
22062
|
+
for (const rect of wrong) front.clearRect(rect.x, rect.y, rect.width, rect.height);
|
|
22063
|
+
}
|
|
22064
|
+
if (strips.length > 0 || wrong.length > 0) this.paintLayer(layer, [...strips, ...wrong], node, rec, context);
|
|
21324
22065
|
}
|
|
22066
|
+
layers.remember(layer, node, context.layout);
|
|
21325
22067
|
layer.used = true;
|
|
21326
22068
|
layers.stats.composited++;
|
|
21327
22069
|
ctx.save();
|
|
@@ -21343,13 +22085,13 @@ var Canvas2DRenderer = class {
|
|
|
21343
22085
|
* strip saves is raster, which is the cost it exists for; the walk
|
|
21344
22086
|
* costs what a direct draw's walk would.
|
|
21345
22087
|
*/
|
|
21346
|
-
paintLayer(layer,
|
|
22088
|
+
paintLayer(layer, strips, node, rec, context) {
|
|
21347
22089
|
const lctx = new PreciseContext(layer.frontContext);
|
|
21348
22090
|
lctx.save();
|
|
21349
|
-
if (
|
|
22091
|
+
if (strips !== null) {
|
|
21350
22092
|
lctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
21351
22093
|
lctx.beginPath();
|
|
21352
|
-
lctx.rect(strip.x, strip.y, strip.width, strip.height);
|
|
22094
|
+
for (const strip of strips) lctx.rect(strip.x, strip.y, strip.width, strip.height);
|
|
21353
22095
|
lctx.clip();
|
|
21354
22096
|
}
|
|
21355
22097
|
const dpr = layer.dpr;
|
|
@@ -21363,6 +22105,26 @@ var Canvas2DRenderer = class {
|
|
|
21363
22105
|
this.cullY = rec.y + layer.scrollY;
|
|
21364
22106
|
this.cullWidth = rec.width;
|
|
21365
22107
|
this.cullHeight = rec.height;
|
|
22108
|
+
if (strips !== null && strips.length > 0) {
|
|
22109
|
+
let left = Infinity;
|
|
22110
|
+
let top = Infinity;
|
|
22111
|
+
let right = -Infinity;
|
|
22112
|
+
let bottom = -Infinity;
|
|
22113
|
+
for (const strip of strips) {
|
|
22114
|
+
left = Math.min(left, strip.x);
|
|
22115
|
+
top = Math.min(top, strip.y);
|
|
22116
|
+
right = Math.max(right, strip.x + strip.width);
|
|
22117
|
+
bottom = Math.max(bottom, strip.y + strip.height);
|
|
22118
|
+
}
|
|
22119
|
+
const x0 = Math.max(this.cullX, (left - layer.offsetX) / dpr + layer.scrollX - 8);
|
|
22120
|
+
const y0 = Math.max(this.cullY, (top - layer.offsetY) / dpr + layer.scrollY - 8);
|
|
22121
|
+
const x1 = Math.min(this.cullX + this.cullWidth, (right - layer.offsetX) / dpr + layer.scrollX + 8);
|
|
22122
|
+
const y1 = Math.min(this.cullY + this.cullHeight, (bottom - layer.offsetY) / dpr + layer.scrollY + 8);
|
|
22123
|
+
this.cullX = x0;
|
|
22124
|
+
this.cullY = y0;
|
|
22125
|
+
this.cullWidth = Math.max(0, x1 - x0);
|
|
22126
|
+
this.cullHeight = Math.max(0, y1 - y0);
|
|
22127
|
+
}
|
|
21366
22128
|
this.layerDepth++;
|
|
21367
22129
|
try {
|
|
21368
22130
|
this.renderChildren(node, context, lctx, true);
|
|
@@ -21571,6 +22333,6 @@ function createCanvasSurface(host) {
|
|
|
21571
22333
|
return new CanvasSurface(host);
|
|
21572
22334
|
}
|
|
21573
22335
|
//#endregion
|
|
21574
|
-
export { isImeKey as $,
|
|
22336
|
+
export { isImeKey as $, visualStatesEqual as $a, scaleSpacing as $i, spannedRunsFor as $n, transformIsIdentity as $r, registerFontStack as $t, withOverrideSource as A, UiBasicColors as Aa, noInsets as Ai, clearLinkHover as An, inheritedPropertyFlags as Ar, adjacentField as At, FlexDirection as B, findEnvironmentKey as Ba, withDensity as Bi, editorFor as Bn, isUiSemanticState as Br, createCanvasTextStyle as Bt, SCROLLBAR_LINGER_MS as C, spansDifferOnlyInPaint as Ca, normalizeBorderRadius as Ci, isNodeInert as Cn, resolveNumber as Cr, hasDrawnText as Ct, formatExplanation as D, colorsEqualPalette as Da, UiEnvironmentKeys as Di, isSubtreeInert as Dn, closestPropertyName as Dr, selectionRectsIn as Dt, formatConstraints as E, textStylesEqual as Ea, propertyValuesEqual as Ei, isPressable as En, resolveString as Er, paragraphGeometryFrom as Et, clampSize as F, normalizeColor as Fa, unknownContainerSize as Fi, setLinkHover as Fn, propertyValuesEqualByName as Fr, detectEditingPlatform as Ft, CrossAxisAlignment as G, overrideSources as Ga, themeExtensionsEqual as Gi, maskText as Gn, UI_SUBGRID_AXES as Gr, runFontString as Gt, placeGridItems as H, UiNode as Ha, hasThemeExtension as Hi, isEditableNode as Hn, statesEqual as Hr, fillTextRects as Ht, constraintsEqual as I, parseColor as Ia, darkTheme as Ii, CARET_BLINK_MS as In, UiProperties as Ir, isPrintable as It, parseCrossAxisAlignment as J, writeOverrideProperty as Ja, highContrastColors as Ji, resolveColorValue as Jn, paintValuesEqual as Jr, textRunBackgrounds as Jt, MainAxisAlignment as K, resetOverrideWarnings as Ka, withThemeExtension as Ki, nextCaretToggle as Kn, validateSubgrid as Kr, spanOf as Kt, tightenConstraints as L, rgb8 as La, lightTheme as Li, CARET_WIDTH as Ln, UI_ROLES as Lr, EditableLayout as Lt, contentOffset as M, colorToRgba as Ma, UiContainerSizeSource as Mi, linkHoverOf as Mn, propertyEffects as Mr, edgeField as Mt, LayoutRecord as N, colorValuesEqual as Na, bandOf as Ni, linkOf as Nn, propertyIsInherited as Nr, editingGroupOf as Nt, formatNumber as O, darkColors as Oa, UiInsetRegistry as Oi, pointerEventsOf as On, findPropertyDefinition as Or, wordRangeIn as Ot, Constraints as P, colorsEqual as Pa, containerBands as Pi, linkRunAtPointIn as Pn, propertyNarrowers as Pr, commandForKey as Pt, capturePointer as Q, visualState as Qa, defaultSpacing as Qi, ParagraphTextMeasurer as Qn, transform as Qr, fontStackFor as Qt, MAX_MEASURES_PER_CHILD as R, rgba as Ra, themesEqual as Ri, EDITOR_PROP as Rn, UI_SEMANTIC_STATES as Rr, applyCanvasTextStyle as Rt, SCROLLBAR_FADE_MS as S, spannedTextOf as Sa, borderRadiusValuesEqual as Si, isNodeHitTestable as Sn, resolveBoolean as Sr, UiInputDispatcher as St, describeOverrides as T, textSpansEqual as Ta, defineProperty as Ti, isNodeTabStop as Tn, resolvePropertyByName as Tr, paragraphGeometry as Tt, sizeGridTracks as U, DirtyNodeSet as Ua, noThemeExtensions as Ui, isMultiline as Un, validateRole as Ur, layoutTextLines as Ut, parseFlexDirection as V, UiEnvironment as Va, defineThemeExtension as Vi, editorOf as Vn, normalizeStates as Vr, drawText as Vt, AlignContent as W, clearOverrideProperty as Wa, themeExtension as Wi, isReadOnly as Wn, validateStates as Wr, runCanvasStyleInto as Wt, CanvasPlatformSurface as X, defaultVisualState as Xa, relativeLuminance as Xi, videoFrameSize as Xn, UiTransforms as Xr, bumpFontStack as Xt, parseMainAxisAlignment as Y, UiVisualState as Ya, raiseContrast as Yi, isVideoSurface as Yn, pathValuesEqual as Yr, textRunDecorations as Yt, UiPlatformAdapter as Z, hasVisualState as Za, densityFactors as Zi, CharacterCountTextMeasurer as Zn, parseTransform as Zr, clearFontStacks as Zt, LABEL_PADDING_X as _, editableSpansOf as _a, UiBorderRadiuses as _i, selectableTextNodes as _n, UiNodeType as _o, previousGraphemeStart as _r, scrollLeaderOf as _t, PaintPictureCache as a, boxShadow as aa, radialGradient as ai, computeObjectFitRect as an, UiGestureEvent as ao, lineIndexForOffset as ar, UiGestureRecognizer as at, snapScroll as b, resolvedSpansOf as ba, borderRadiusEqual as bi, setSelectionRange as bn, UiGraph as br, scrollbarThumbs as bt, EMPTY_RECORDING as c, boxShadowReach as ca, fr as ci, normalizeTextAlign as cn, UiPasteEvent as co, offsetAtX as cr, isScrollContainer as ct, tracePathData as d, isShapeName as da, isMinMaxTrack as di, TEXT_MATCHES_PROP as dn, UiSelectionChangeEvent as do, EditableTextModel as dr, UiFocusManager as dt, spacingEqual as ea, transformsEqual as ei, DEFAULT_FONT_FAMILY as en, MOUSE_POINTER as eo, ELLIPSIS as er, pointerDeviceOf as et, ScaledImageCache as f, shapesEqual as fa, isPercentLength as fi, clearMatchRanges as fn, UiTextChangeEvent as fo, graphemeBoundaries as fr, UiHitTester as ft, paintsAfterChildren as g, defaultTextStyle as ga, resolveLength as gi, clearSelectionRange as gn, wheelDeltaYOf as go, nextWordEnd as gr, SCROLLBAR_THICKNESS as gt, hasDecorationPhase as h, typographyEqual as ha, repeat as hi, TEXT_SELECTION_PROP as hn, noKeyModifiers as ho, nextGraphemeEnd as hr, SCROLLBAR_MIN_THUMB as ht, traceRoundedRect as i, BOX_SHADOW_BLUR_REACH as ia, linearGradient as ii, colorToCss as in, UiFocusEvent as io, lineIndexAtY as ir, UiTouchScroller as it, accumulatedOffsetTo as j, colorToHex as ja, observeViewportInsets as ji, hasTextLinks as jn, inheritedPropertyNames as jr, comparePositions as jt, labelNode as k, lightColors as ka, insetsEqual as ki, resolveCursor as kn, getPropertyNames as kr, textInputOf as kt, PaintRecorder as l, boxShadowsEqual as la, isAutoLength as li, normalizeVerticalAlign as ln, UiPinchEvent as lo, offsetForVerticalMove as lr, UiKeyboardController as lt, decorationRect as m, isTypographyRole as ma, percent as mi, setMatchRanges as mn, UiWheelEvent as mo, lineStartAt as mr, SCROLLBAR_INSET as mt, createCanvasSurface as n, defaultShadows as na, gradientPaint as ni, DEFAULT_FONT_WEIGHT as nn, UiCompositionEvent as no, proportionalFontMetrics as nr, touchActionFor as nt, paintPictures as o, boxShadowArraysEqual as oa, validateGradient as oi, createPaintState as on, UiInputEvent as oo, lineLimit as or, UiPinchRecognizer as ot, decorationColor as p, defaultTypography as pa, minmax as pi, matchRangesOf as pn, UiWheelDeltaMode as po, lineEndAt as pr, SCROLLBAR_HOVER_ZONE as pt, parseAlignContent as q, writeDeclaredProperty as qa, contrastRatio as qi, syncEditorValue as qn, inputsEqual as qr, textMeasureRequest as qt, Canvas2DRenderer as r, shadowsEqual as ra, gradientsEqual as ri, DEFAULT_TEXT_COLOR as rn, UiEventType as ro, caretRectFor as rr, UiEditingController as rt, PaintTarget as s, boxShadowRadius as sa, auto as si, isPaintVisible as sn, UiKeyboardEvent as so, offsetAtPoint as sr, UiWheelController as st, CanvasSurface as t, spacingSteps as ta, MAX_GRADIENT_STOPS as ti, DEFAULT_FONT_SIZE as tn, UiBeforeInputEvent as to, layoutParagraph as tr, prepareInputSurface as tt, replayPaint as u, defaultShapes as ua, isFrLength as ui, resolvePaintState as un, UiPointerEvent as uo, selectionRects as ur, UiPointerController as ut, drawOverlayShapes as v, flattenTextSpans as va, borderRadius as vi, selectableTextOf as vn, DirtyFlags as vo, previousWordStart as vr, scrollRange as vt, describeLength as w, textContentOf as wa, uniformBorderRadius as wi, isNodeSelectable as wn, resolveProperty as wr, offsetAtPointIn as wt, LayoutEngine as x, spanAtOffset as xa, borderRadiusIsZero as xi, isNodeFocusable as xn, DEFAULT_LINE_HEIGHT_FACTOR as xr, scrollbarZoneAt as xt, labelOrigin as y, hiddenRangesOf as ya, borderRadiusCorners as yi, selectionRangeOf as yn, UiBinding as yo, wordRangeAt as yr, scrollbarThumb as yt, isLayoutProtocol as z, createEnvironmentKey as za, withContrast as zi, caretVisibleAt as zn, isUiRole as zr, buildFontString as zt };
|
|
21575
22337
|
|
|
21576
|
-
//# sourceMappingURL=CanvasSurface-
|
|
22338
|
+
//# sourceMappingURL=CanvasSurface-BAhcLj2K.js.map
|