chat-layout 0.1.5 → 1.0.0-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +200 -6
- package/index.d.mts +187 -107
- package/index.mjs +1268 -594
- package/index.mjs.map +1 -1
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -1,283 +1,85 @@
|
|
|
1
|
-
import { layoutWithLines, prepareWithSegments } from "@chenglou/pretext";
|
|
2
|
-
//#region src/
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { layoutNextLine, layoutWithLines, prepareWithSegments, walkLineRanges } from "@chenglou/pretext";
|
|
2
|
+
//#region src/internal/node-registry.ts
|
|
3
|
+
const registry = /* @__PURE__ */ new WeakMap();
|
|
4
|
+
const revisions = /* @__PURE__ */ new WeakMap();
|
|
5
|
+
function getOwnershipError() {
|
|
6
|
+
return /* @__PURE__ */ new Error("A node can only be attached to one parent. Shared nodes are not supported.");
|
|
5
7
|
}
|
|
6
|
-
function
|
|
7
|
-
|
|
8
|
-
const segment = preprocessSegments(text)[0];
|
|
9
|
-
if (!segment) return {
|
|
10
|
-
width: 0,
|
|
11
|
-
text: "",
|
|
12
|
-
shift: 0
|
|
13
|
-
};
|
|
14
|
-
const { fontBoundingBoxAscent: ascent = 0, fontBoundingBoxDescent: descent = 0 } = ctx.graphics.measureText(segment);
|
|
15
|
-
const shift = ascent - descent;
|
|
16
|
-
if (maxWidth === 0) return {
|
|
17
|
-
width: 0,
|
|
18
|
-
text: "",
|
|
19
|
-
shift
|
|
20
|
-
};
|
|
21
|
-
const { lines } = layoutWithLines(prepareWithSegments(segment, ctx.graphics.font), maxWidth, 0);
|
|
22
|
-
if (lines.length === 0) return {
|
|
23
|
-
width: 0,
|
|
24
|
-
text: "",
|
|
25
|
-
shift
|
|
26
|
-
};
|
|
27
|
-
return {
|
|
28
|
-
width: lines[0].width,
|
|
29
|
-
text: lines[0].text,
|
|
30
|
-
shift
|
|
31
|
-
};
|
|
8
|
+
function getDetachOwnershipError() {
|
|
9
|
+
return /* @__PURE__ */ new Error("Cannot detach or replace a node from a parent that does not own it.");
|
|
32
10
|
}
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
const segments = preprocessSegments(text);
|
|
36
|
-
if (segments.length === 0 || maxWidth === 0) return {
|
|
37
|
-
width: 0,
|
|
38
|
-
lines: []
|
|
39
|
-
};
|
|
40
|
-
const font = ctx.graphics.font;
|
|
41
|
-
let width = 0;
|
|
42
|
-
const lines = [];
|
|
43
|
-
for (const segment of segments) {
|
|
44
|
-
const { fontBoundingBoxAscent: ascent = 0, fontBoundingBoxDescent: descent = 0 } = ctx.graphics.measureText(segment);
|
|
45
|
-
const shift = ascent - descent;
|
|
46
|
-
const { lines: segLines } = layoutWithLines(prepareWithSegments(segment, font), maxWidth, 0);
|
|
47
|
-
for (const segLine of segLines) {
|
|
48
|
-
width = Math.max(width, segLine.width);
|
|
49
|
-
lines.push({
|
|
50
|
-
width: segLine.width,
|
|
51
|
-
text: segLine.text,
|
|
52
|
-
shift
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return {
|
|
57
|
-
width,
|
|
58
|
-
lines
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
//#endregion
|
|
62
|
-
//#region src/utils.ts
|
|
63
|
-
function shallow(object) {
|
|
64
|
-
return Object.create(object);
|
|
11
|
+
function bumpRevision(node) {
|
|
12
|
+
revisions.set(node, (revisions.get(node) ?? 0) + 1);
|
|
65
13
|
}
|
|
66
|
-
function
|
|
67
|
-
return
|
|
68
|
-
__proto__: object,
|
|
69
|
-
...other
|
|
70
|
-
};
|
|
14
|
+
function getNodeRevision(node) {
|
|
15
|
+
return revisions.get(node) ?? 0;
|
|
71
16
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const registry = /* @__PURE__ */ new WeakMap();
|
|
75
|
-
function registerNodeParent(node, parent) {
|
|
17
|
+
function attachNodeToParent(node, parent) {
|
|
18
|
+
if (registry.has(node)) throw getOwnershipError();
|
|
76
19
|
registry.set(node, parent);
|
|
20
|
+
bumpRevision(parent);
|
|
77
21
|
}
|
|
78
|
-
function
|
|
79
|
-
|
|
22
|
+
function replaceNodeParent(previousNode, nextNode, parent) {
|
|
23
|
+
if (previousNode === nextNode) return;
|
|
24
|
+
if (registry.get(previousNode) !== parent) throw getDetachOwnershipError();
|
|
25
|
+
if (registry.has(nextNode)) throw getOwnershipError();
|
|
26
|
+
registry.delete(previousNode);
|
|
27
|
+
registry.set(nextNode, parent);
|
|
28
|
+
bumpRevision(parent);
|
|
80
29
|
}
|
|
81
|
-
function
|
|
82
|
-
|
|
30
|
+
function replaceNodesParent(previousNodes, nextNodes, parent) {
|
|
31
|
+
const previousSnapshot = Array.from(previousNodes);
|
|
32
|
+
const nextSnapshot = Array.from(nextNodes);
|
|
33
|
+
if (previousSnapshot.length === nextSnapshot.length && previousSnapshot.every((node, index) => node === nextSnapshot[index])) return;
|
|
34
|
+
const previousSet = new Set(previousSnapshot);
|
|
35
|
+
const nextSet = /* @__PURE__ */ new Set();
|
|
36
|
+
for (const node of nextSnapshot) {
|
|
37
|
+
if (nextSet.has(node)) throw getOwnershipError();
|
|
38
|
+
nextSet.add(node);
|
|
39
|
+
const currentParent = registry.get(node);
|
|
40
|
+
if (currentParent != null && currentParent !== parent) throw getOwnershipError();
|
|
41
|
+
}
|
|
42
|
+
for (const node of previousSnapshot) if (!nextSet.has(node)) {
|
|
43
|
+
if (registry.get(node) !== parent) throw getDetachOwnershipError();
|
|
44
|
+
registry.delete(node);
|
|
45
|
+
}
|
|
46
|
+
for (const node of nextSnapshot) if (!previousSet.has(node)) registry.set(node, parent);
|
|
47
|
+
bumpRevision(parent);
|
|
48
|
+
}
|
|
49
|
+
function forEachNodeAncestor(node, visitor) {
|
|
50
|
+
let current = node;
|
|
51
|
+
while (current = registry.get(current)) visitor(current);
|
|
83
52
|
}
|
|
84
53
|
//#endregion
|
|
85
|
-
//#region src/nodes.ts
|
|
54
|
+
//#region src/nodes/base.ts
|
|
86
55
|
var Group = class {
|
|
56
|
+
#children;
|
|
87
57
|
constructor(children) {
|
|
88
|
-
this
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
get flex() {
|
|
92
|
-
return this.children.some((item) => item.flex);
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
var VStack = class extends Group {
|
|
96
|
-
constructor(children, options = {}) {
|
|
97
|
-
super(children);
|
|
98
|
-
this.options = options;
|
|
99
|
-
}
|
|
100
|
-
measure(ctx) {
|
|
101
|
-
let width = 0;
|
|
102
|
-
let height = 0;
|
|
103
|
-
if (this.options.alignment != null) ctx.alignment = this.options.alignment;
|
|
104
|
-
for (const [index, child] of this.children.entries()) {
|
|
105
|
-
if (this.options.gap != null && index !== 0) height += this.options.gap;
|
|
106
|
-
const result = shallow(ctx).measureNode(child);
|
|
107
|
-
height += result.height;
|
|
108
|
-
width = Math.max(width, result.width);
|
|
109
|
-
}
|
|
110
|
-
ctx.remainingWidth -= width;
|
|
111
|
-
return {
|
|
112
|
-
width,
|
|
113
|
-
height
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
draw(ctx, x, y) {
|
|
117
|
-
let result = false;
|
|
118
|
-
const fullWidth = ctx.measureNode(this).width;
|
|
119
|
-
const alignment = this.options.alignment ?? ctx.alignment;
|
|
120
|
-
if (this.options.alignment != null) ctx.alignment = this.options.alignment;
|
|
121
|
-
for (const [index, child] of this.children.entries()) {
|
|
122
|
-
if (this.options.gap != null && index !== 0) y += this.options.gap;
|
|
123
|
-
const { width, height } = shallow(ctx).measureNode(child);
|
|
124
|
-
const curCtx = shallow(ctx);
|
|
125
|
-
let requestRedraw;
|
|
126
|
-
if (alignment === "right") requestRedraw = child.draw(curCtx, x + fullWidth - width, y);
|
|
127
|
-
else if (alignment === "center") requestRedraw = child.draw(curCtx, x + (fullWidth - width) / 2, y);
|
|
128
|
-
else requestRedraw = child.draw(curCtx, x, y);
|
|
129
|
-
result ||= requestRedraw;
|
|
130
|
-
y += height;
|
|
131
|
-
}
|
|
132
|
-
return result;
|
|
133
|
-
}
|
|
134
|
-
hittest(ctx, test) {
|
|
135
|
-
let y = 0;
|
|
136
|
-
const fullWidth = ctx.measureNode(this).width;
|
|
137
|
-
const alignment = this.options.alignment ?? ctx.alignment;
|
|
138
|
-
if (this.options.alignment != null) ctx.alignment = this.options.alignment;
|
|
139
|
-
for (const [index, child] of this.children.entries()) {
|
|
140
|
-
if (this.options.gap != null && index !== 0) y += this.options.gap;
|
|
141
|
-
const { width, height } = shallow(ctx).measureNode(child);
|
|
142
|
-
const curCtx = shallow(ctx);
|
|
143
|
-
if (test.y >= y && test.y < y + height) {
|
|
144
|
-
let x;
|
|
145
|
-
if (alignment === "right") x = test.x - fullWidth + width;
|
|
146
|
-
else if (alignment === "center") x = test.x - (fullWidth - width) / 2;
|
|
147
|
-
else x = test.x;
|
|
148
|
-
if (x < 0 || x >= width) return false;
|
|
149
|
-
return child.hittest(curCtx, shallowMerge(test, {
|
|
150
|
-
x,
|
|
151
|
-
y: test.y - y
|
|
152
|
-
}));
|
|
153
|
-
}
|
|
154
|
-
y += height;
|
|
155
|
-
}
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
var HStack = class extends Group {
|
|
160
|
-
constructor(children, options = {}) {
|
|
161
|
-
super(children);
|
|
162
|
-
this.children = children;
|
|
163
|
-
this.options = options;
|
|
58
|
+
this.#children = [...children];
|
|
59
|
+
replaceNodesParent([], this.#children, this);
|
|
164
60
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
let height = 0;
|
|
168
|
-
let firstFlex;
|
|
169
|
-
for (const [index, child] of this.children.entries()) {
|
|
170
|
-
if (this.options.gap != null && index !== 0) width += this.options.gap;
|
|
171
|
-
if (firstFlex == null && child.flex) {
|
|
172
|
-
firstFlex = child;
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
const curCtx = shallow(ctx);
|
|
176
|
-
curCtx.remainingWidth = ctx.remainingWidth - width;
|
|
177
|
-
const result = curCtx.measureNode(child);
|
|
178
|
-
width += result.width;
|
|
179
|
-
height = Math.max(height, result.height);
|
|
180
|
-
}
|
|
181
|
-
if (firstFlex != null) {
|
|
182
|
-
const curCtx = shallow(ctx);
|
|
183
|
-
curCtx.remainingWidth = ctx.remainingWidth - width;
|
|
184
|
-
const result = curCtx.measureNode(firstFlex);
|
|
185
|
-
width += result.width;
|
|
186
|
-
height = Math.max(height, result.height);
|
|
187
|
-
}
|
|
188
|
-
return {
|
|
189
|
-
width,
|
|
190
|
-
height
|
|
191
|
-
};
|
|
61
|
+
get children() {
|
|
62
|
+
return this.#children;
|
|
192
63
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (reverse) {
|
|
198
|
-
x += ctx.measureNode(this).width;
|
|
199
|
-
for (const [index, child] of this.children.entries()) {
|
|
200
|
-
const gap = this.options.gap != null && index !== 0 ? this.options.gap : void 0;
|
|
201
|
-
if (gap) {
|
|
202
|
-
x -= gap;
|
|
203
|
-
ctx.remainingWidth -= gap;
|
|
204
|
-
}
|
|
205
|
-
const { width } = shallow(ctx).measureNode(child);
|
|
206
|
-
x -= width;
|
|
207
|
-
const requestRedraw = child.draw(shallow(ctx), x, y);
|
|
208
|
-
result ||= requestRedraw;
|
|
209
|
-
ctx.remainingWidth -= width;
|
|
210
|
-
}
|
|
211
|
-
} else for (const [index, child] of this.children.entries()) {
|
|
212
|
-
const gap = this.options.gap != null && index !== 0 ? this.options.gap : void 0;
|
|
213
|
-
if (gap) {
|
|
214
|
-
x += gap;
|
|
215
|
-
ctx.remainingWidth -= gap;
|
|
216
|
-
}
|
|
217
|
-
const requestRedraw = child.draw(shallow(ctx), x, y);
|
|
218
|
-
result ||= requestRedraw;
|
|
219
|
-
const { width } = shallow(ctx).measureNode(child);
|
|
220
|
-
ctx.remainingWidth -= width;
|
|
221
|
-
x += width;
|
|
222
|
-
}
|
|
223
|
-
return result;
|
|
224
|
-
}
|
|
225
|
-
hittest(ctx, test) {
|
|
226
|
-
const reverse = this.options.reverse ?? ctx.reverse;
|
|
227
|
-
if (this.options.reverse) ctx.reverse = this.options.reverse;
|
|
228
|
-
if (reverse) {
|
|
229
|
-
let x = ctx.measureNode(this).width;
|
|
230
|
-
for (const [index, child] of this.children.entries()) {
|
|
231
|
-
const gap = this.options.gap != null && index !== 0 ? this.options.gap : void 0;
|
|
232
|
-
if (gap) {
|
|
233
|
-
x -= gap;
|
|
234
|
-
ctx.remainingWidth -= gap;
|
|
235
|
-
}
|
|
236
|
-
const { width, height } = shallow(ctx).measureNode(child);
|
|
237
|
-
x -= width;
|
|
238
|
-
if (x <= test.x && test.x < x + width) {
|
|
239
|
-
if (test.y >= height) return false;
|
|
240
|
-
return child.hittest(shallow(ctx), shallowMerge(test, { x: test.x - x }));
|
|
241
|
-
}
|
|
242
|
-
ctx.remainingWidth -= width;
|
|
243
|
-
}
|
|
244
|
-
} else {
|
|
245
|
-
let x = 0;
|
|
246
|
-
for (const [index, child] of this.children.entries()) {
|
|
247
|
-
const gap = this.options.gap != null && index !== 0 ? this.options.gap : void 0;
|
|
248
|
-
if (gap) {
|
|
249
|
-
x += gap;
|
|
250
|
-
ctx.remainingWidth -= gap;
|
|
251
|
-
}
|
|
252
|
-
const { width, height } = shallow(ctx).measureNode(child);
|
|
253
|
-
if (x <= test.x && test.x < x + width) {
|
|
254
|
-
if (test.y >= height) return false;
|
|
255
|
-
return child.hittest(shallow(ctx), shallowMerge(test, { x: test.x - x }));
|
|
256
|
-
}
|
|
257
|
-
x += width;
|
|
258
|
-
ctx.remainingWidth -= width;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
return false;
|
|
64
|
+
replaceChildren(nextChildren) {
|
|
65
|
+
const nextSnapshot = [...nextChildren];
|
|
66
|
+
replaceNodesParent(this.#children, nextSnapshot, this);
|
|
67
|
+
this.#children = nextSnapshot;
|
|
262
68
|
}
|
|
263
69
|
};
|
|
264
70
|
var Wrapper = class {
|
|
265
71
|
#inner;
|
|
266
72
|
constructor(inner) {
|
|
267
73
|
this.#inner = inner;
|
|
268
|
-
|
|
74
|
+
attachNodeToParent(this.#inner, this);
|
|
269
75
|
}
|
|
270
76
|
get inner() {
|
|
271
77
|
return this.#inner;
|
|
272
78
|
}
|
|
273
79
|
set inner(newNode) {
|
|
274
80
|
if (newNode === this.#inner) return;
|
|
275
|
-
|
|
81
|
+
replaceNodeParent(this.#inner, newNode, this);
|
|
276
82
|
this.#inner = newNode;
|
|
277
|
-
registerNodeParent(newNode, this);
|
|
278
|
-
}
|
|
279
|
-
get flex() {
|
|
280
|
-
return this.inner.flex;
|
|
281
83
|
}
|
|
282
84
|
measure(ctx) {
|
|
283
85
|
return this.inner.measure(ctx);
|
|
@@ -289,6 +91,128 @@ var Wrapper = class {
|
|
|
289
91
|
return this.inner.hittest(ctx, test);
|
|
290
92
|
}
|
|
291
93
|
};
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region src/layout.ts
|
|
96
|
+
/**
|
|
97
|
+
* 创建 LayoutRect 的辅助函数
|
|
98
|
+
*/
|
|
99
|
+
function createRect(x, y, width, height) {
|
|
100
|
+
return {
|
|
101
|
+
x,
|
|
102
|
+
y,
|
|
103
|
+
width,
|
|
104
|
+
height
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 合并多个 rect 得到包含所有 rect 的最小外接矩形
|
|
109
|
+
*/
|
|
110
|
+
function mergeRects(rects) {
|
|
111
|
+
if (rects.length === 0) return createRect(0, 0, 0, 0);
|
|
112
|
+
let minX = Infinity;
|
|
113
|
+
let minY = Infinity;
|
|
114
|
+
let maxX = -Infinity;
|
|
115
|
+
let maxY = -Infinity;
|
|
116
|
+
for (const rect of rects) {
|
|
117
|
+
minX = Math.min(minX, rect.x);
|
|
118
|
+
minY = Math.min(minY, rect.y);
|
|
119
|
+
maxX = Math.max(maxX, rect.x + rect.width);
|
|
120
|
+
maxY = Math.max(maxY, rect.y + rect.height);
|
|
121
|
+
}
|
|
122
|
+
return createRect(minX, minY, maxX - minX, maxY - minY);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 从子节点布局结果计算容器的 contentBox
|
|
126
|
+
*/
|
|
127
|
+
function computeContentBox(children) {
|
|
128
|
+
return mergeRects(children.map((child) => child.contentBox));
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 检查点是否在 rect 内
|
|
132
|
+
*/
|
|
133
|
+
function pointInRect(x, y, rect) {
|
|
134
|
+
return x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 读取单子节点布局结果中的唯一 child。
|
|
138
|
+
*/
|
|
139
|
+
function getSingleChildLayout(layout) {
|
|
140
|
+
return layout.children[0];
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 在布局结果中按指定盒模型查找命中的 child,并返回局部坐标。
|
|
144
|
+
*/
|
|
145
|
+
function findChildAtPoint(children, x, y, box = "contentBox") {
|
|
146
|
+
for (let i = children.length - 1; i >= 0; i -= 1) {
|
|
147
|
+
const child = children[i];
|
|
148
|
+
const target = box === "rect" ? child.rect : child.contentBox;
|
|
149
|
+
if (!pointInRect(x, y, target)) continue;
|
|
150
|
+
return {
|
|
151
|
+
child,
|
|
152
|
+
localX: x - target.x,
|
|
153
|
+
localY: y - target.y
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/utils.ts
|
|
159
|
+
function shallow(object) {
|
|
160
|
+
return Object.create(object);
|
|
161
|
+
}
|
|
162
|
+
function shallowMerge(object, other) {
|
|
163
|
+
return {
|
|
164
|
+
__proto__: object,
|
|
165
|
+
...other
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/nodes/shared.ts
|
|
170
|
+
function withConstraints(ctx, constraints) {
|
|
171
|
+
const next = shallow(ctx);
|
|
172
|
+
next.constraints = constraints;
|
|
173
|
+
return next;
|
|
174
|
+
}
|
|
175
|
+
function getLayoutContext(ctx) {
|
|
176
|
+
return ctx;
|
|
177
|
+
}
|
|
178
|
+
function readLayoutResult(node, ctx) {
|
|
179
|
+
return getLayoutContext(ctx).getLayoutResult(node, ctx.constraints);
|
|
180
|
+
}
|
|
181
|
+
function writeLayoutResult(node, ctx, result) {
|
|
182
|
+
getLayoutContext(ctx).setLayoutResult(node, result, ctx.constraints);
|
|
183
|
+
}
|
|
184
|
+
function ensureLayoutResult(node, ctx) {
|
|
185
|
+
return readLayoutResult(node, ctx);
|
|
186
|
+
}
|
|
187
|
+
function drawLayoutChildren(node, ctx, x, y) {
|
|
188
|
+
const layoutResult = ensureLayoutResult(node, ctx);
|
|
189
|
+
if (!layoutResult) return false;
|
|
190
|
+
let result = false;
|
|
191
|
+
for (const childResult of layoutResult.children) result ||= childResult.node.draw(withConstraints(ctx, childResult.constraints), x + childResult.contentBox.x, y + childResult.contentBox.y);
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
function hittestLayoutChildren(node, ctx, test, box = "contentBox") {
|
|
195
|
+
const layoutResult = ensureLayoutResult(node, ctx);
|
|
196
|
+
if (!layoutResult) return false;
|
|
197
|
+
const hit = findChildAtPoint(layoutResult.children, test.x, test.y, box);
|
|
198
|
+
if (!hit) return false;
|
|
199
|
+
return hit.child.node.hittest(withConstraints(ctx, hit.child.constraints), shallowMerge(test, {
|
|
200
|
+
x: hit.localX,
|
|
201
|
+
y: hit.localY
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
//#endregion
|
|
205
|
+
//#region src/nodes/box.ts
|
|
206
|
+
function clampToConstraints$1(value, min, max) {
|
|
207
|
+
let result = value;
|
|
208
|
+
if (min != null) result = Math.max(result, min);
|
|
209
|
+
if (max != null) result = Math.min(result, max);
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
function shrinkConstraint(value, padding) {
|
|
213
|
+
if (value == null) return;
|
|
214
|
+
return Math.max(0, value - padding);
|
|
215
|
+
}
|
|
292
216
|
var PaddingBox = class extends Wrapper {
|
|
293
217
|
constructor(inner, padding = {}) {
|
|
294
218
|
super(inner);
|
|
@@ -307,80 +231,634 @@ var PaddingBox = class extends Wrapper {
|
|
|
307
231
|
return this.padding.right ?? 0;
|
|
308
232
|
}
|
|
309
233
|
measure(ctx) {
|
|
310
|
-
|
|
311
|
-
const
|
|
234
|
+
const paddingLeft = this.#left;
|
|
235
|
+
const paddingRight = this.#right;
|
|
236
|
+
const paddingTop = this.#top;
|
|
237
|
+
const paddingBottom = this.#bottom;
|
|
238
|
+
const horizontalPadding = paddingLeft + paddingRight;
|
|
239
|
+
const verticalPadding = paddingTop + paddingBottom;
|
|
240
|
+
const childConstraints = ctx.constraints ? {
|
|
241
|
+
...ctx.constraints,
|
|
242
|
+
minWidth: shrinkConstraint(ctx.constraints.minWidth, horizontalPadding),
|
|
243
|
+
maxWidth: shrinkConstraint(ctx.constraints.maxWidth, horizontalPadding),
|
|
244
|
+
minHeight: shrinkConstraint(ctx.constraints.minHeight, verticalPadding),
|
|
245
|
+
maxHeight: shrinkConstraint(ctx.constraints.maxHeight, verticalPadding)
|
|
246
|
+
} : void 0;
|
|
247
|
+
const { width, height } = ctx.measureNode(this.inner, childConstraints);
|
|
248
|
+
const containerBox = createRect(0, 0, clampToConstraints$1(width + horizontalPadding, ctx.constraints?.minWidth, ctx.constraints?.maxWidth), clampToConstraints$1(height + verticalPadding, ctx.constraints?.minHeight, ctx.constraints?.maxHeight));
|
|
249
|
+
const childRect = createRect(paddingLeft, paddingTop, width, height);
|
|
250
|
+
writeLayoutResult(this, ctx, {
|
|
251
|
+
containerBox,
|
|
252
|
+
contentBox: childRect,
|
|
253
|
+
children: [{
|
|
254
|
+
node: this.inner,
|
|
255
|
+
rect: childRect,
|
|
256
|
+
contentBox: childRect,
|
|
257
|
+
constraints: childConstraints
|
|
258
|
+
}],
|
|
259
|
+
constraints: ctx.constraints
|
|
260
|
+
});
|
|
312
261
|
return {
|
|
313
|
-
width: width
|
|
314
|
-
height: height
|
|
262
|
+
width: containerBox.width,
|
|
263
|
+
height: containerBox.height
|
|
315
264
|
};
|
|
316
265
|
}
|
|
317
266
|
draw(ctx, x, y) {
|
|
318
|
-
|
|
319
|
-
return this.inner.draw(ctx, x + this.#left, y + this.#top);
|
|
267
|
+
const layoutResult = readLayoutResult(this, ctx);
|
|
268
|
+
if (!layoutResult) return this.inner.draw(ctx, x + this.#left, y + this.#top);
|
|
269
|
+
const childResult = getSingleChildLayout(layoutResult);
|
|
270
|
+
if (!childResult) return false;
|
|
271
|
+
return childResult.node.draw(withConstraints(ctx, childResult.constraints), x + childResult.rect.x, y + childResult.rect.y);
|
|
320
272
|
}
|
|
321
273
|
hittest(ctx, test) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
274
|
+
const layoutResult = readLayoutResult(this, ctx);
|
|
275
|
+
if (!layoutResult) return false;
|
|
276
|
+
const hit = findChildAtPoint(layoutResult.children, test.x, test.y, "rect");
|
|
277
|
+
if (!hit) return false;
|
|
278
|
+
return hit.child.node.hittest(withConstraints(ctx, hit.child.constraints), shallowMerge(test, {
|
|
279
|
+
x: hit.localX,
|
|
280
|
+
y: hit.localY
|
|
327
281
|
}));
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
var Fixed = class {
|
|
285
|
+
constructor(width, height) {
|
|
286
|
+
this.width = width;
|
|
287
|
+
this.height = height;
|
|
288
|
+
}
|
|
289
|
+
measure(_ctx) {
|
|
290
|
+
return {
|
|
291
|
+
width: this.width,
|
|
292
|
+
height: this.height
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
draw(_ctx, _x, _y) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
hittest(_ctx, _test) {
|
|
328
299
|
return false;
|
|
329
300
|
}
|
|
330
301
|
};
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region src/nodes/flex.ts
|
|
304
|
+
function getMainSize(axis, box) {
|
|
305
|
+
return axis === "row" ? box.width : box.height;
|
|
306
|
+
}
|
|
307
|
+
function getCrossSize(axis, box) {
|
|
308
|
+
return axis === "row" ? box.height : box.width;
|
|
309
|
+
}
|
|
310
|
+
function getMinMain(axis, constraints) {
|
|
311
|
+
return axis === "row" ? constraints?.minWidth : constraints?.minHeight;
|
|
312
|
+
}
|
|
313
|
+
function getMaxMain(axis, constraints) {
|
|
314
|
+
return axis === "row" ? constraints?.maxWidth : constraints?.maxHeight;
|
|
315
|
+
}
|
|
316
|
+
function getMinCross(axis, constraints) {
|
|
317
|
+
return axis === "row" ? constraints?.minHeight : constraints?.minWidth;
|
|
318
|
+
}
|
|
319
|
+
function getMaxCross(axis, constraints) {
|
|
320
|
+
return axis === "row" ? constraints?.maxHeight : constraints?.maxWidth;
|
|
321
|
+
}
|
|
322
|
+
function createAxisConstraints(axis, constraints, main, cross = {}) {
|
|
323
|
+
if (constraints == null && main.min == null && main.max == null && cross.min == null && cross.max == null) return;
|
|
324
|
+
const next = { ...constraints };
|
|
325
|
+
if (axis === "row") {
|
|
326
|
+
next.minWidth = main.min;
|
|
327
|
+
next.maxWidth = main.max;
|
|
328
|
+
next.minHeight = cross.min;
|
|
329
|
+
next.maxHeight = cross.max;
|
|
330
|
+
} else {
|
|
331
|
+
next.minHeight = main.min;
|
|
332
|
+
next.maxHeight = main.max;
|
|
333
|
+
next.minWidth = cross.min;
|
|
334
|
+
next.maxWidth = cross.max;
|
|
335
|
+
}
|
|
336
|
+
return next;
|
|
337
|
+
}
|
|
338
|
+
function clampToConstraints(value, min, max) {
|
|
339
|
+
let result = value;
|
|
340
|
+
if (min != null) result = Math.max(result, min);
|
|
341
|
+
if (max != null) result = Math.min(result, max);
|
|
342
|
+
return result;
|
|
343
|
+
}
|
|
344
|
+
function getCrossAlignment(alignSelf, alignItems) {
|
|
345
|
+
if (alignSelf == null || alignSelf === "auto") return alignItems;
|
|
346
|
+
return alignSelf;
|
|
347
|
+
}
|
|
348
|
+
function getJustifySpacing(justifyContent, freeSpace, itemCount, gap) {
|
|
349
|
+
switch (justifyContent) {
|
|
350
|
+
case "center": return {
|
|
351
|
+
leading: freeSpace / 2,
|
|
352
|
+
between: gap
|
|
353
|
+
};
|
|
354
|
+
case "end": return {
|
|
355
|
+
leading: freeSpace,
|
|
356
|
+
between: gap
|
|
357
|
+
};
|
|
358
|
+
case "space-between": return {
|
|
359
|
+
leading: 0,
|
|
360
|
+
between: itemCount > 1 ? gap + freeSpace / (itemCount - 1) : gap
|
|
361
|
+
};
|
|
362
|
+
case "space-around": return {
|
|
363
|
+
leading: itemCount > 0 ? freeSpace / itemCount / 2 : 0,
|
|
364
|
+
between: itemCount > 0 ? gap + freeSpace / itemCount : gap
|
|
365
|
+
};
|
|
366
|
+
case "space-evenly": return {
|
|
367
|
+
leading: itemCount > 0 ? freeSpace / (itemCount + 1) : 0,
|
|
368
|
+
between: itemCount > 0 ? gap + freeSpace / (itemCount + 1) : gap
|
|
369
|
+
};
|
|
370
|
+
default: return {
|
|
371
|
+
leading: 0,
|
|
372
|
+
between: gap
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function getCrossOffset(align, frameCross, contentCross) {
|
|
377
|
+
switch (align) {
|
|
378
|
+
case "center": return (frameCross - contentCross) / 2;
|
|
379
|
+
case "end": return frameCross - contentCross;
|
|
380
|
+
default: return 0;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function createRectFromAxis(axis, main, cross, mainSize, crossSize) {
|
|
384
|
+
return axis === "row" ? createRect(main, cross, mainSize, crossSize) : createRect(cross, main, crossSize, mainSize);
|
|
385
|
+
}
|
|
386
|
+
function readFlexItemOptions(child) {
|
|
387
|
+
if (child instanceof FlexItem) return child.item;
|
|
388
|
+
return {};
|
|
389
|
+
}
|
|
390
|
+
function computeFlexLayout(children, options, constraints, measureChild) {
|
|
391
|
+
const axis = options.direction ?? "row";
|
|
392
|
+
const gap = options.gap ?? 0;
|
|
393
|
+
const justifyContent = options.justifyContent ?? "start";
|
|
394
|
+
const alignItems = options.alignItems ?? "start";
|
|
395
|
+
const reverse = options.reverse ?? false;
|
|
396
|
+
const mainAxisSize = options.mainAxisSize ?? "fill";
|
|
397
|
+
const orderedChildren = reverse ? [...children].reverse() : children;
|
|
398
|
+
const maxMain = getMaxMain(axis, constraints);
|
|
399
|
+
const minMain = getMinMain(axis, constraints);
|
|
400
|
+
const maxCross = getMaxCross(axis, constraints);
|
|
401
|
+
const minCross = getMinCross(axis, constraints);
|
|
402
|
+
const gapTotal = orderedChildren.length > 1 ? gap * (orderedChildren.length - 1) : 0;
|
|
403
|
+
const finiteMain = maxMain != null;
|
|
404
|
+
const finiteCross = maxCross != null;
|
|
405
|
+
const availableMain = finiteMain ? Math.max(0, maxMain - gapTotal) : void 0;
|
|
406
|
+
let consumedMain = 0;
|
|
407
|
+
let totalGrow = 0;
|
|
408
|
+
const measurements = /* @__PURE__ */ new Map();
|
|
409
|
+
for (const child of orderedChildren) {
|
|
410
|
+
const item = readFlexItemOptions(child);
|
|
411
|
+
const grow = item.grow ?? 0;
|
|
412
|
+
totalGrow += grow;
|
|
413
|
+
if (grow > 0 && finiteMain) continue;
|
|
414
|
+
const effectiveAlign = getCrossAlignment(item.alignSelf, alignItems);
|
|
415
|
+
const stretch = effectiveAlign === "stretch";
|
|
416
|
+
const childConstraints = createAxisConstraints(axis, constraints, { max: finiteMain && availableMain != null ? Math.max(0, availableMain - consumedMain) : maxMain }, {
|
|
417
|
+
min: void 0,
|
|
418
|
+
max: maxCross
|
|
419
|
+
});
|
|
420
|
+
const measured = measureChild(child, childConstraints);
|
|
421
|
+
const frameMain = getMainSize(axis, measured);
|
|
422
|
+
const frameCross = getCrossSize(axis, measured);
|
|
423
|
+
measurements.set(child, {
|
|
424
|
+
child,
|
|
425
|
+
item,
|
|
426
|
+
measured,
|
|
427
|
+
initialConstraints: childConstraints,
|
|
428
|
+
finalConstraints: childConstraints,
|
|
429
|
+
allocatedMain: void 0,
|
|
430
|
+
grow,
|
|
431
|
+
effectiveAlign,
|
|
432
|
+
stretch,
|
|
433
|
+
frameMain,
|
|
434
|
+
frameCross
|
|
435
|
+
});
|
|
436
|
+
consumedMain += frameMain;
|
|
437
|
+
}
|
|
438
|
+
const remainingMain = finiteMain && availableMain != null ? Math.max(0, availableMain - consumedMain) : void 0;
|
|
439
|
+
for (const child of orderedChildren) {
|
|
440
|
+
if (measurements.has(child)) continue;
|
|
441
|
+
const item = readFlexItemOptions(child);
|
|
442
|
+
const grow = item.grow ?? 0;
|
|
443
|
+
const effectiveAlign = getCrossAlignment(item.alignSelf, alignItems);
|
|
444
|
+
const stretch = effectiveAlign === "stretch";
|
|
445
|
+
const allocatedMain = finiteMain && remainingMain != null && totalGrow > 0 ? remainingMain * grow / totalGrow : void 0;
|
|
446
|
+
const childConstraints = createAxisConstraints(axis, constraints, { max: allocatedMain }, {
|
|
447
|
+
min: void 0,
|
|
448
|
+
max: maxCross
|
|
449
|
+
});
|
|
450
|
+
const measured = measureChild(child, childConstraints);
|
|
451
|
+
const measuredMain = getMainSize(axis, measured);
|
|
452
|
+
const frameMain = allocatedMain ?? measuredMain;
|
|
453
|
+
const frameCross = getCrossSize(axis, measured);
|
|
454
|
+
measurements.set(child, {
|
|
455
|
+
child,
|
|
456
|
+
item,
|
|
457
|
+
measured,
|
|
458
|
+
initialConstraints: childConstraints,
|
|
459
|
+
finalConstraints: childConstraints,
|
|
460
|
+
allocatedMain,
|
|
461
|
+
grow,
|
|
462
|
+
effectiveAlign,
|
|
463
|
+
stretch,
|
|
464
|
+
frameMain,
|
|
465
|
+
frameCross
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
let contentMain = gapTotal;
|
|
469
|
+
let contentCross = 0;
|
|
470
|
+
for (const child of orderedChildren) {
|
|
471
|
+
const measurement = measurements.get(child);
|
|
472
|
+
contentMain += measurement.frameMain;
|
|
473
|
+
contentCross = Math.max(contentCross, measurement.frameCross);
|
|
474
|
+
}
|
|
475
|
+
finiteMain && mainAxisSize === "fill" || clampToConstraints(contentMain, minMain, maxMain);
|
|
476
|
+
const containerCross = clampToConstraints(contentCross, minCross, maxCross);
|
|
477
|
+
if (finiteCross) {
|
|
478
|
+
for (const child of orderedChildren) {
|
|
479
|
+
const measurement = measurements.get(child);
|
|
480
|
+
if (!measurement.stretch) continue;
|
|
481
|
+
const finalConstraints = createAxisConstraints(axis, measurement.initialConstraints, {
|
|
482
|
+
min: getMinMain(axis, measurement.initialConstraints),
|
|
483
|
+
max: getMaxMain(axis, measurement.initialConstraints)
|
|
484
|
+
}, {
|
|
485
|
+
min: containerCross,
|
|
486
|
+
max: containerCross
|
|
487
|
+
});
|
|
488
|
+
const remeasured = measureChild(child, finalConstraints);
|
|
489
|
+
measurement.measured = remeasured;
|
|
490
|
+
measurement.finalConstraints = finalConstraints;
|
|
491
|
+
measurement.frameCross = containerCross;
|
|
492
|
+
measurement.frameMain = measurement.allocatedMain ?? getMainSize(axis, remeasured);
|
|
493
|
+
}
|
|
494
|
+
contentMain = gapTotal;
|
|
495
|
+
contentCross = 0;
|
|
496
|
+
for (const child of orderedChildren) {
|
|
497
|
+
const measurement = measurements.get(child);
|
|
498
|
+
contentMain += measurement.frameMain;
|
|
499
|
+
contentCross = Math.max(contentCross, getCrossSize(axis, measurement.measured));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const finalContainerMain = finiteMain && mainAxisSize === "fill" ? Math.max(maxMain, contentMain) : clampToConstraints(contentMain, minMain, maxMain);
|
|
503
|
+
const spacing = getJustifySpacing(justifyContent, Math.max(0, finalContainerMain - contentMain), orderedChildren.length, gap);
|
|
504
|
+
const childResults = [];
|
|
505
|
+
let cursor = spacing.leading;
|
|
506
|
+
for (const child of orderedChildren) {
|
|
507
|
+
const measurement = measurements.get(child);
|
|
508
|
+
const frameCross = measurement.stretch && finiteCross ? containerCross : measurement.frameCross;
|
|
509
|
+
const contentMainSize = getMainSize(axis, measurement.measured);
|
|
510
|
+
const contentCrossSize = getCrossSize(axis, measurement.measured);
|
|
511
|
+
const rectCross = measurement.stretch ? 0 : getCrossOffset(measurement.effectiveAlign, containerCross, frameCross);
|
|
512
|
+
const contentCrossOffset = rectCross + getCrossOffset(measurement.effectiveAlign, frameCross, contentCrossSize);
|
|
513
|
+
const rect = createRectFromAxis(axis, cursor, rectCross, measurement.frameMain, frameCross);
|
|
514
|
+
const contentBox = createRectFromAxis(axis, cursor, contentCrossOffset, contentMainSize, contentCrossSize);
|
|
515
|
+
childResults.push({
|
|
516
|
+
node: child,
|
|
517
|
+
rect,
|
|
518
|
+
contentBox,
|
|
519
|
+
constraints: measurement.finalConstraints
|
|
520
|
+
});
|
|
521
|
+
cursor += measurement.frameMain + spacing.between;
|
|
522
|
+
}
|
|
523
|
+
const containerBox = axis === "row" ? createRect(0, 0, finalContainerMain, containerCross) : createRect(0, 0, containerCross, finalContainerMain);
|
|
524
|
+
const finalContentBox = childResults.length > 0 ? computeContentBox(childResults) : createRect(0, 0, 0, 0);
|
|
525
|
+
return {
|
|
526
|
+
box: {
|
|
527
|
+
width: containerBox.width,
|
|
528
|
+
height: containerBox.height
|
|
529
|
+
},
|
|
530
|
+
layout: {
|
|
531
|
+
containerBox,
|
|
532
|
+
contentBox: finalContentBox,
|
|
533
|
+
children: childResults,
|
|
534
|
+
constraints
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
var FlexItem = class extends Wrapper {
|
|
539
|
+
constructor(inner, item = {}) {
|
|
334
540
|
super(inner);
|
|
541
|
+
this.item = item;
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
var Flex = class extends Group {
|
|
545
|
+
constructor(children, options = {}) {
|
|
546
|
+
super(children);
|
|
335
547
|
this.options = options;
|
|
336
548
|
}
|
|
337
549
|
measure(ctx) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
550
|
+
const result = computeFlexLayout(this.children, this.options, ctx.constraints, (node, constraints) => ctx.measureNode(node, constraints));
|
|
551
|
+
writeLayoutResult(this, ctx, result.layout);
|
|
552
|
+
return result.box;
|
|
553
|
+
}
|
|
554
|
+
draw(ctx, x, y) {
|
|
555
|
+
return drawLayoutChildren(this, ctx, x, y);
|
|
556
|
+
}
|
|
557
|
+
hittest(ctx, test) {
|
|
558
|
+
return hittestLayoutChildren(this, ctx, test, "contentBox");
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
//#endregion
|
|
562
|
+
//#region src/nodes/place.ts
|
|
563
|
+
function resolveHorizontalOffset(align, availableWidth, childWidth) {
|
|
564
|
+
switch (align) {
|
|
565
|
+
case "center": return (availableWidth - childWidth) / 2;
|
|
566
|
+
case "end": return availableWidth - childWidth;
|
|
567
|
+
case "start": return 0;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
var Place = class extends Wrapper {
|
|
571
|
+
constructor(inner, options = {}) {
|
|
572
|
+
super(inner);
|
|
573
|
+
this.options = options;
|
|
574
|
+
}
|
|
575
|
+
measure(ctx) {
|
|
576
|
+
const availableWidth = ctx.constraints?.maxWidth;
|
|
577
|
+
const expand = this.options.expand ?? true;
|
|
578
|
+
const childConstraints = ctx.constraints ? { ...ctx.constraints } : void 0;
|
|
579
|
+
const childBox = ctx.measureNode(this.inner, childConstraints);
|
|
580
|
+
let width = expand && availableWidth != null ? availableWidth : childBox.width;
|
|
581
|
+
if (ctx.constraints?.minWidth != null) width = Math.max(width, ctx.constraints.minWidth);
|
|
582
|
+
if (ctx.constraints?.maxWidth != null) width = Math.min(width, ctx.constraints.maxWidth);
|
|
583
|
+
const childRect = createRect(resolveHorizontalOffset(this.options.align ?? "start", width, childBox.width), 0, childBox.width, childBox.height);
|
|
584
|
+
writeLayoutResult(this, ctx, {
|
|
585
|
+
containerBox: createRect(0, 0, width, childBox.height),
|
|
586
|
+
contentBox: childRect,
|
|
587
|
+
children: [{
|
|
588
|
+
node: this.inner,
|
|
589
|
+
rect: childRect,
|
|
590
|
+
contentBox: createRect(0, 0, childBox.width, childBox.height),
|
|
591
|
+
constraints: childConstraints
|
|
592
|
+
}],
|
|
593
|
+
constraints: ctx.constraints
|
|
594
|
+
});
|
|
349
595
|
return {
|
|
350
|
-
width
|
|
351
|
-
height
|
|
596
|
+
width,
|
|
597
|
+
height: childBox.height
|
|
352
598
|
};
|
|
353
599
|
}
|
|
354
600
|
draw(ctx, x, y) {
|
|
355
|
-
|
|
356
|
-
return this.inner.draw(ctx, x
|
|
601
|
+
const layoutResult = readLayoutResult(this, ctx);
|
|
602
|
+
if (!layoutResult) return this.inner.draw(ctx, x, y);
|
|
603
|
+
const childResult = getSingleChildLayout(layoutResult);
|
|
604
|
+
if (!childResult) return false;
|
|
605
|
+
const childCtx = withConstraints(ctx, childResult.constraints);
|
|
606
|
+
return childResult.node.draw(childCtx, x + childResult.rect.x, y + childResult.rect.y);
|
|
357
607
|
}
|
|
358
608
|
hittest(ctx, test) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
return false;
|
|
609
|
+
const layoutResult = readLayoutResult(this, ctx);
|
|
610
|
+
if (!layoutResult) return false;
|
|
611
|
+
const hit = findChildAtPoint(layoutResult.children, test.x, test.y, "rect");
|
|
612
|
+
if (!hit) return false;
|
|
613
|
+
return hit.child.node.hittest(withConstraints(ctx, hit.child.constraints), shallowMerge(test, {
|
|
614
|
+
x: hit.localX,
|
|
615
|
+
y: hit.localY
|
|
616
|
+
}));
|
|
363
617
|
}
|
|
364
618
|
};
|
|
619
|
+
//#endregion
|
|
620
|
+
//#region src/text.ts
|
|
621
|
+
const FONT_SHIFT_PROBE = "M";
|
|
622
|
+
const PREPARED_SEGMENT_CACHE_CAPACITY = 512;
|
|
623
|
+
const FONT_SHIFT_CACHE_CAPACITY = 64;
|
|
624
|
+
const LINE_START_CURSOR = {
|
|
625
|
+
segmentIndex: 0,
|
|
626
|
+
graphemeIndex: 0
|
|
627
|
+
};
|
|
628
|
+
const preparedSegmentCache = /* @__PURE__ */ new Map();
|
|
629
|
+
const fontShiftCache = /* @__PURE__ */ new Map();
|
|
630
|
+
function preprocessSegments(text, whitespace = "preserve") {
|
|
631
|
+
const segments = text.split("\n");
|
|
632
|
+
if (whitespace === "trim-and-collapse") return segments.map((line) => line.trim()).filter((line) => line.length > 0);
|
|
633
|
+
return segments;
|
|
634
|
+
}
|
|
635
|
+
function readLruValue(cache, key) {
|
|
636
|
+
const cached = cache.get(key);
|
|
637
|
+
if (cached == null) return;
|
|
638
|
+
cache.delete(key);
|
|
639
|
+
cache.set(key, cached);
|
|
640
|
+
return cached;
|
|
641
|
+
}
|
|
642
|
+
function writeLruValue(cache, key, value, capacity) {
|
|
643
|
+
if (cache.has(key)) cache.delete(key);
|
|
644
|
+
else if (cache.size >= capacity) {
|
|
645
|
+
const firstKey = cache.keys().next().value;
|
|
646
|
+
if (firstKey != null) cache.delete(firstKey);
|
|
647
|
+
}
|
|
648
|
+
cache.set(key, value);
|
|
649
|
+
return value;
|
|
650
|
+
}
|
|
651
|
+
function getPreparedSegmentCacheKey(segment, font) {
|
|
652
|
+
return `${font}\u0000${segment}`;
|
|
653
|
+
}
|
|
654
|
+
function readPreparedSegment(segment, font) {
|
|
655
|
+
const key = getPreparedSegmentCacheKey(segment, font);
|
|
656
|
+
const cached = readLruValue(preparedSegmentCache, key);
|
|
657
|
+
if (cached != null) return cached;
|
|
658
|
+
return writeLruValue(preparedSegmentCache, key, prepareWithSegments(segment, font), PREPARED_SEGMENT_CACHE_CAPACITY);
|
|
659
|
+
}
|
|
660
|
+
function measureFontShift(ctx) {
|
|
661
|
+
const font = ctx.graphics.font;
|
|
662
|
+
const cached = readLruValue(fontShiftCache, font);
|
|
663
|
+
if (cached != null) return cached;
|
|
664
|
+
const { fontBoundingBoxAscent: ascent = 0, fontBoundingBoxDescent: descent = 0 } = ctx.graphics.measureText(FONT_SHIFT_PROBE);
|
|
665
|
+
return writeLruValue(fontShiftCache, font, ascent - descent, FONT_SHIFT_CACHE_CAPACITY);
|
|
666
|
+
}
|
|
667
|
+
function layoutFirstLineIntrinsic(ctx, text, whitespace = "preserve") {
|
|
668
|
+
const segment = preprocessSegments(text, whitespace)[0];
|
|
669
|
+
if (!segment) return {
|
|
670
|
+
width: 0,
|
|
671
|
+
text: "",
|
|
672
|
+
shift: 0
|
|
673
|
+
};
|
|
674
|
+
const shift = measureFontShift(ctx);
|
|
675
|
+
return {
|
|
676
|
+
width: ctx.graphics.measureText(segment).width,
|
|
677
|
+
text: segment,
|
|
678
|
+
shift
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
function measureTextIntrinsic(ctx, text, whitespace = "preserve") {
|
|
682
|
+
const segments = preprocessSegments(text, whitespace);
|
|
683
|
+
if (segments.length === 0) return {
|
|
684
|
+
width: 0,
|
|
685
|
+
lineCount: 0
|
|
686
|
+
};
|
|
687
|
+
let width = 0;
|
|
688
|
+
for (const segment of segments) width = Math.max(width, ctx.graphics.measureText(segment).width);
|
|
689
|
+
return {
|
|
690
|
+
width,
|
|
691
|
+
lineCount: segments.length
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function layoutTextIntrinsic(ctx, text, whitespace = "preserve") {
|
|
695
|
+
const segments = preprocessSegments(text, whitespace);
|
|
696
|
+
if (segments.length === 0) return {
|
|
697
|
+
width: 0,
|
|
698
|
+
lines: []
|
|
699
|
+
};
|
|
700
|
+
const shift = measureFontShift(ctx);
|
|
701
|
+
let width = 0;
|
|
702
|
+
const lines = [];
|
|
703
|
+
for (const segment of segments) {
|
|
704
|
+
const measuredWidth = ctx.graphics.measureText(segment).width;
|
|
705
|
+
width = Math.max(width, measuredWidth);
|
|
706
|
+
lines.push({
|
|
707
|
+
width: measuredWidth,
|
|
708
|
+
text: segment,
|
|
709
|
+
shift
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
return {
|
|
713
|
+
width,
|
|
714
|
+
lines
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
function layoutFirstLine(ctx, text, maxWidth, whitespace = "preserve") {
|
|
718
|
+
if (maxWidth < 0) maxWidth = 0;
|
|
719
|
+
const segment = preprocessSegments(text, whitespace)[0];
|
|
720
|
+
if (!segment) return {
|
|
721
|
+
width: 0,
|
|
722
|
+
text: "",
|
|
723
|
+
shift: 0
|
|
724
|
+
};
|
|
725
|
+
const shift = measureFontShift(ctx);
|
|
726
|
+
if (maxWidth === 0) return {
|
|
727
|
+
width: 0,
|
|
728
|
+
text: "",
|
|
729
|
+
shift
|
|
730
|
+
};
|
|
731
|
+
const line = layoutNextLine(readPreparedSegment(segment, ctx.graphics.font), LINE_START_CURSOR, maxWidth);
|
|
732
|
+
if (line == null) return {
|
|
733
|
+
width: 0,
|
|
734
|
+
text: "",
|
|
735
|
+
shift
|
|
736
|
+
};
|
|
737
|
+
return {
|
|
738
|
+
width: line.width,
|
|
739
|
+
text: line.text,
|
|
740
|
+
shift
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
function measureText(ctx, text, maxWidth, whitespace = "preserve") {
|
|
744
|
+
if (maxWidth < 0) maxWidth = 0;
|
|
745
|
+
const segments = preprocessSegments(text, whitespace);
|
|
746
|
+
if (segments.length === 0 || maxWidth === 0) return {
|
|
747
|
+
width: 0,
|
|
748
|
+
lineCount: 0
|
|
749
|
+
};
|
|
750
|
+
const font = ctx.graphics.font;
|
|
751
|
+
let width = 0;
|
|
752
|
+
let lineCount = 0;
|
|
753
|
+
for (const segment of segments) {
|
|
754
|
+
if (segment.length === 0) {
|
|
755
|
+
lineCount += 1;
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
const prepared = readPreparedSegment(segment, font);
|
|
759
|
+
lineCount += walkLineRanges(prepared, maxWidth, (line) => {
|
|
760
|
+
width = Math.max(width, line.width);
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
return {
|
|
764
|
+
width,
|
|
765
|
+
lineCount
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
function layoutText(ctx, text, maxWidth, whitespace = "preserve") {
|
|
769
|
+
if (maxWidth < 0) maxWidth = 0;
|
|
770
|
+
const segments = preprocessSegments(text, whitespace);
|
|
771
|
+
if (segments.length === 0 || maxWidth === 0) return {
|
|
772
|
+
width: 0,
|
|
773
|
+
lines: []
|
|
774
|
+
};
|
|
775
|
+
const font = ctx.graphics.font;
|
|
776
|
+
const shift = measureFontShift(ctx);
|
|
777
|
+
let width = 0;
|
|
778
|
+
const lines = [];
|
|
779
|
+
for (const segment of segments) {
|
|
780
|
+
if (segment.length === 0) {
|
|
781
|
+
lines.push({
|
|
782
|
+
width: 0,
|
|
783
|
+
text: "",
|
|
784
|
+
shift
|
|
785
|
+
});
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
const { lines: segLines } = layoutWithLines(readPreparedSegment(segment, font), maxWidth, 0);
|
|
789
|
+
for (const segLine of segLines) {
|
|
790
|
+
width = Math.max(width, segLine.width);
|
|
791
|
+
lines.push({
|
|
792
|
+
width: segLine.width,
|
|
793
|
+
text: segLine.text,
|
|
794
|
+
shift
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return {
|
|
799
|
+
width,
|
|
800
|
+
lines
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
//#endregion
|
|
804
|
+
//#region src/nodes/text.ts
|
|
805
|
+
function resolvePhysicalTextAlign(options) {
|
|
806
|
+
if (options.physicalAlign != null) return options.physicalAlign;
|
|
807
|
+
if (options.align != null) switch (options.align) {
|
|
808
|
+
case "start": return "left";
|
|
809
|
+
case "center": return "center";
|
|
810
|
+
case "end": return "right";
|
|
811
|
+
}
|
|
812
|
+
return "left";
|
|
813
|
+
}
|
|
814
|
+
function normalizeTextMaxWidth(maxWidth) {
|
|
815
|
+
if (maxWidth == null) return;
|
|
816
|
+
return Math.max(0, maxWidth);
|
|
817
|
+
}
|
|
818
|
+
function getTextLayoutContext(ctx) {
|
|
819
|
+
return ctx;
|
|
820
|
+
}
|
|
821
|
+
function readCachedTextLayout(node, ctx, key, compute) {
|
|
822
|
+
const textCtx = getTextLayoutContext(ctx);
|
|
823
|
+
const cached = textCtx.getTextLayout(node, key);
|
|
824
|
+
if (cached != null) return cached;
|
|
825
|
+
const layout = compute();
|
|
826
|
+
textCtx.setTextLayout(node, key, layout);
|
|
827
|
+
return layout;
|
|
828
|
+
}
|
|
829
|
+
function getSingleLineLayoutKey(maxWidth) {
|
|
830
|
+
return maxWidth == null ? "single:intrinsic" : `single:${maxWidth}`;
|
|
831
|
+
}
|
|
832
|
+
function getMultiLineMeasureLayoutKey(maxWidth) {
|
|
833
|
+
return maxWidth == null ? "multi:measure:intrinsic" : `multi:measure:${maxWidth}`;
|
|
834
|
+
}
|
|
835
|
+
function getMultiLineDrawLayoutKey(maxWidth) {
|
|
836
|
+
return maxWidth == null ? "multi:draw:intrinsic" : `multi:draw:${maxWidth}`;
|
|
837
|
+
}
|
|
838
|
+
function getSingleLineLayout(node, ctx, text, whitespace) {
|
|
839
|
+
const maxWidth = normalizeTextMaxWidth(ctx.constraints?.maxWidth);
|
|
840
|
+
return readCachedTextLayout(node, ctx, getSingleLineLayoutKey(maxWidth), () => maxWidth == null ? layoutFirstLineIntrinsic(ctx, text, whitespace) : layoutFirstLine(ctx, text, maxWidth, whitespace));
|
|
841
|
+
}
|
|
842
|
+
function getMultiLineMeasureLayout(node, ctx, text, whitespace) {
|
|
843
|
+
const maxWidth = normalizeTextMaxWidth(ctx.constraints?.maxWidth);
|
|
844
|
+
return readCachedTextLayout(node, ctx, getMultiLineMeasureLayoutKey(maxWidth), () => maxWidth == null ? measureTextIntrinsic(ctx, text, whitespace) : measureText(ctx, text, maxWidth, whitespace));
|
|
845
|
+
}
|
|
846
|
+
function getMultiLineDrawLayout(node, ctx, text, whitespace) {
|
|
847
|
+
const maxWidth = normalizeTextMaxWidth(ctx.constraints?.maxWidth);
|
|
848
|
+
return readCachedTextLayout(node, ctx, getMultiLineDrawLayoutKey(maxWidth), () => maxWidth == null ? layoutTextIntrinsic(ctx, text, whitespace) : layoutText(ctx, text, maxWidth, whitespace));
|
|
849
|
+
}
|
|
365
850
|
var MultilineText = class {
|
|
366
|
-
#width = 0;
|
|
367
|
-
#lines = [];
|
|
368
851
|
constructor(text, options) {
|
|
369
852
|
this.text = text;
|
|
370
853
|
this.options = options;
|
|
371
854
|
}
|
|
372
|
-
get flex() {
|
|
373
|
-
return true;
|
|
374
|
-
}
|
|
375
855
|
measure(ctx) {
|
|
376
856
|
return ctx.with((g) => {
|
|
377
857
|
g.font = this.options.font;
|
|
378
|
-
const { width,
|
|
379
|
-
this.#width = width;
|
|
380
|
-
this.#lines = lines;
|
|
858
|
+
const { width, lineCount } = getMultiLineMeasureLayout(this, ctx, this.text, this.options.whitespace);
|
|
381
859
|
return {
|
|
382
|
-
width
|
|
383
|
-
height:
|
|
860
|
+
width,
|
|
861
|
+
height: lineCount * this.options.lineHeight
|
|
384
862
|
};
|
|
385
863
|
});
|
|
386
864
|
}
|
|
@@ -388,25 +866,26 @@ var MultilineText = class {
|
|
|
388
866
|
return ctx.with((g) => {
|
|
389
867
|
g.font = this.options.font;
|
|
390
868
|
g.fillStyle = ctx.resolveDynValue(this.options.style);
|
|
391
|
-
|
|
869
|
+
const { width, lines } = getMultiLineDrawLayout(this, ctx, this.text, this.options.whitespace);
|
|
870
|
+
switch (resolvePhysicalTextAlign(this.options)) {
|
|
392
871
|
case "left":
|
|
393
|
-
for (const { text, shift } of
|
|
872
|
+
for (const { text, shift } of lines) {
|
|
394
873
|
g.fillText(text, x, y + (this.options.lineHeight + shift) / 2);
|
|
395
874
|
y += this.options.lineHeight;
|
|
396
875
|
}
|
|
397
876
|
break;
|
|
398
877
|
case "right":
|
|
399
|
-
x +=
|
|
878
|
+
x += width;
|
|
400
879
|
g.textAlign = "right";
|
|
401
|
-
for (const { text, shift } of
|
|
880
|
+
for (const { text, shift } of lines) {
|
|
402
881
|
g.fillText(text, x, y + (this.options.lineHeight + shift) / 2);
|
|
403
882
|
y += this.options.lineHeight;
|
|
404
883
|
}
|
|
405
884
|
break;
|
|
406
885
|
case "center":
|
|
407
|
-
x +=
|
|
886
|
+
x += width / 2;
|
|
408
887
|
g.textAlign = "center";
|
|
409
|
-
for (const { text, shift } of
|
|
888
|
+
for (const { text, shift } of lines) {
|
|
410
889
|
g.fillText(text, x, y + (this.options.lineHeight + shift) / 2);
|
|
411
890
|
y += this.options.lineHeight;
|
|
412
891
|
}
|
|
@@ -420,25 +899,16 @@ var MultilineText = class {
|
|
|
420
899
|
}
|
|
421
900
|
};
|
|
422
901
|
var Text = class {
|
|
423
|
-
#width = 0;
|
|
424
|
-
#text = "";
|
|
425
|
-
#shift = 0;
|
|
426
902
|
constructor(text, options) {
|
|
427
903
|
this.text = text;
|
|
428
904
|
this.options = options;
|
|
429
905
|
}
|
|
430
|
-
get flex() {
|
|
431
|
-
return false;
|
|
432
|
-
}
|
|
433
906
|
measure(ctx) {
|
|
434
907
|
return ctx.with((g) => {
|
|
435
908
|
g.font = this.options.font;
|
|
436
|
-
const { width
|
|
437
|
-
this.#width = width;
|
|
438
|
-
this.#text = text;
|
|
439
|
-
this.#shift = shift;
|
|
909
|
+
const { width } = getSingleLineLayout(this, ctx, this.text, this.options.whitespace);
|
|
440
910
|
return {
|
|
441
|
-
width
|
|
911
|
+
width,
|
|
442
912
|
height: this.options.lineHeight
|
|
443
913
|
};
|
|
444
914
|
});
|
|
@@ -447,7 +917,8 @@ var Text = class {
|
|
|
447
917
|
return ctx.with((g) => {
|
|
448
918
|
g.font = this.options.font;
|
|
449
919
|
g.fillStyle = ctx.resolveDynValue(this.options.style);
|
|
450
|
-
|
|
920
|
+
const { text, shift } = getSingleLineLayout(this, ctx, this.text, this.options.whitespace);
|
|
921
|
+
g.fillText(text, x, y + (this.options.lineHeight + shift) / 2);
|
|
451
922
|
return false;
|
|
452
923
|
});
|
|
453
924
|
}
|
|
@@ -455,34 +926,20 @@ var Text = class {
|
|
|
455
926
|
return false;
|
|
456
927
|
}
|
|
457
928
|
};
|
|
458
|
-
var Fixed = class {
|
|
459
|
-
constructor(width, height) {
|
|
460
|
-
this.width = width;
|
|
461
|
-
this.height = height;
|
|
462
|
-
}
|
|
463
|
-
get flex() {
|
|
464
|
-
return false;
|
|
465
|
-
}
|
|
466
|
-
measure(_ctx) {
|
|
467
|
-
return {
|
|
468
|
-
width: this.width,
|
|
469
|
-
height: this.height
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
draw(_ctx, _x, _y) {
|
|
473
|
-
return false;
|
|
474
|
-
}
|
|
475
|
-
hittest(_ctx, _test) {
|
|
476
|
-
return false;
|
|
477
|
-
}
|
|
478
|
-
};
|
|
479
929
|
//#endregion
|
|
480
|
-
//#region src/renderer.ts
|
|
930
|
+
//#region src/renderer/base.ts
|
|
931
|
+
const MAX_CONSTRAINT_VARIANTS = 8;
|
|
932
|
+
function constraintKey(constraints) {
|
|
933
|
+
if (constraints == null) return "";
|
|
934
|
+
return `${constraints.minWidth ?? ""},${constraints.maxWidth ?? ""},${constraints.minHeight ?? ""},${constraints.maxHeight ?? ""}`;
|
|
935
|
+
}
|
|
481
936
|
var BaseRenderer = class {
|
|
482
937
|
graphics;
|
|
483
938
|
#ctx;
|
|
484
939
|
#lastWidth;
|
|
485
940
|
#cache = /* @__PURE__ */ new WeakMap();
|
|
941
|
+
#layoutCache = /* @__PURE__ */ new WeakMap();
|
|
942
|
+
#textLayoutCache = /* @__PURE__ */ new WeakMap();
|
|
486
943
|
get context() {
|
|
487
944
|
return shallow(this.#ctx);
|
|
488
945
|
}
|
|
@@ -493,19 +950,20 @@ var BaseRenderer = class {
|
|
|
493
950
|
const self = this;
|
|
494
951
|
this.#ctx = {
|
|
495
952
|
graphics: this.graphics,
|
|
496
|
-
|
|
497
|
-
return
|
|
953
|
+
measureNode(node, constraints) {
|
|
954
|
+
return self.measureNode(node, constraints);
|
|
498
955
|
},
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
value,
|
|
502
|
-
writable: true
|
|
503
|
-
});
|
|
956
|
+
getLayoutResult(node, constraints) {
|
|
957
|
+
return self.getLayoutResult(node, constraints);
|
|
504
958
|
},
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
959
|
+
setLayoutResult(node, result, constraints) {
|
|
960
|
+
self.setLayoutResult(node, result, constraints);
|
|
961
|
+
},
|
|
962
|
+
getTextLayout(node, key) {
|
|
963
|
+
return self.getTextLayout(node, key);
|
|
964
|
+
},
|
|
965
|
+
setTextLayout(node, key, layout) {
|
|
966
|
+
self.setTextLayout(node, key, layout);
|
|
509
967
|
},
|
|
510
968
|
invalidateNode: this.invalidateNode.bind(this),
|
|
511
969
|
resolveDynValue(value) {
|
|
@@ -523,21 +981,131 @@ var BaseRenderer = class {
|
|
|
523
981
|
};
|
|
524
982
|
this.#lastWidth = this.graphics.canvas.clientWidth;
|
|
525
983
|
}
|
|
984
|
+
#clearAllCaches() {
|
|
985
|
+
this.#cache = /* @__PURE__ */ new WeakMap();
|
|
986
|
+
this.#layoutCache = /* @__PURE__ */ new WeakMap();
|
|
987
|
+
this.#textLayoutCache = /* @__PURE__ */ new WeakMap();
|
|
988
|
+
}
|
|
989
|
+
#syncCachesToViewportWidth() {
|
|
990
|
+
const width = this.graphics.canvas.clientWidth;
|
|
991
|
+
if (this.#lastWidth === width) return;
|
|
992
|
+
this.#clearAllCaches();
|
|
993
|
+
this.#lastWidth = width;
|
|
994
|
+
}
|
|
995
|
+
getRootConstraints() {
|
|
996
|
+
return { maxWidth: this.graphics.canvas.clientWidth };
|
|
997
|
+
}
|
|
998
|
+
getRootContext() {
|
|
999
|
+
const ctx = this.context;
|
|
1000
|
+
ctx.constraints = this.getRootConstraints();
|
|
1001
|
+
return ctx;
|
|
1002
|
+
}
|
|
1003
|
+
measureRootNode(node) {
|
|
1004
|
+
return this.measureNode(node, this.getRootConstraints());
|
|
1005
|
+
}
|
|
1006
|
+
drawRootNode(node, x = 0, y = 0) {
|
|
1007
|
+
this.measureRootNode(node);
|
|
1008
|
+
return node.draw(this.getRootContext(), x, y);
|
|
1009
|
+
}
|
|
1010
|
+
hittestRootNode(node, test) {
|
|
1011
|
+
this.measureRootNode(node);
|
|
1012
|
+
return node.hittest(this.getRootContext(), test);
|
|
1013
|
+
}
|
|
526
1014
|
invalidateNode(node) {
|
|
1015
|
+
this.#syncCachesToViewportWidth();
|
|
527
1016
|
this.#cache.delete(node);
|
|
528
|
-
|
|
529
|
-
|
|
1017
|
+
this.#layoutCache.delete(node);
|
|
1018
|
+
this.#textLayoutCache.delete(node);
|
|
1019
|
+
forEachNodeAncestor(node, (ancestor) => {
|
|
1020
|
+
this.#cache.delete(ancestor);
|
|
1021
|
+
this.#layoutCache.delete(ancestor);
|
|
1022
|
+
this.#textLayoutCache.delete(ancestor);
|
|
1023
|
+
});
|
|
530
1024
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
1025
|
+
getLayoutResult(node, constraints) {
|
|
1026
|
+
this.#syncCachesToViewportWidth();
|
|
1027
|
+
const nodeCache = this.#layoutCache.get(node);
|
|
1028
|
+
if (nodeCache == null) return;
|
|
1029
|
+
const key = constraintKey(constraints);
|
|
1030
|
+
const cached = nodeCache.get(key);
|
|
1031
|
+
if (cached == null) return;
|
|
1032
|
+
if (cached.revision !== getNodeRevision(node)) {
|
|
1033
|
+
nodeCache.delete(key);
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
return cached.layout;
|
|
1037
|
+
}
|
|
1038
|
+
setLayoutResult(node, result, constraints) {
|
|
1039
|
+
this.#syncCachesToViewportWidth();
|
|
1040
|
+
let nodeCache = this.#layoutCache.get(node);
|
|
1041
|
+
if (nodeCache == null) {
|
|
1042
|
+
nodeCache = /* @__PURE__ */ new Map();
|
|
1043
|
+
this.#layoutCache.set(node, nodeCache);
|
|
1044
|
+
} else if (nodeCache.size >= MAX_CONSTRAINT_VARIANTS) {
|
|
1045
|
+
const firstKey = nodeCache.keys().next().value;
|
|
1046
|
+
nodeCache.delete(firstKey);
|
|
1047
|
+
}
|
|
1048
|
+
nodeCache.set(constraintKey(constraints), {
|
|
1049
|
+
revision: getNodeRevision(node),
|
|
1050
|
+
layout: result
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
getTextLayout(node, key) {
|
|
1054
|
+
this.#syncCachesToViewportWidth();
|
|
1055
|
+
const nodeCache = this.#textLayoutCache.get(node);
|
|
1056
|
+
if (nodeCache == null) return;
|
|
1057
|
+
const cached = nodeCache.get(key);
|
|
1058
|
+
if (cached == null) return;
|
|
1059
|
+
if (cached.revision !== getNodeRevision(node)) {
|
|
1060
|
+
nodeCache.delete(key);
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
return cached.layout;
|
|
1064
|
+
}
|
|
1065
|
+
setTextLayout(node, key, layout) {
|
|
1066
|
+
this.#syncCachesToViewportWidth();
|
|
1067
|
+
let nodeCache = this.#textLayoutCache.get(node);
|
|
1068
|
+
if (nodeCache == null) {
|
|
1069
|
+
nodeCache = /* @__PURE__ */ new Map();
|
|
1070
|
+
this.#textLayoutCache.set(node, nodeCache);
|
|
1071
|
+
} else if (nodeCache.size >= MAX_CONSTRAINT_VARIANTS) {
|
|
1072
|
+
const firstKey = nodeCache.keys().next().value;
|
|
1073
|
+
nodeCache.delete(firstKey);
|
|
1074
|
+
}
|
|
1075
|
+
nodeCache.set(key, {
|
|
1076
|
+
revision: getNodeRevision(node),
|
|
1077
|
+
layout
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1080
|
+
measureNode(node, constraints) {
|
|
1081
|
+
this.#syncCachesToViewportWidth();
|
|
1082
|
+
{
|
|
1083
|
+
const nodeCache = this.#cache.get(node);
|
|
1084
|
+
if (nodeCache != null) {
|
|
1085
|
+
const key = constraintKey(constraints);
|
|
1086
|
+
const cached = nodeCache.get(key);
|
|
1087
|
+
if (cached != null) {
|
|
1088
|
+
if (cached.revision === getNodeRevision(node)) return cached.box;
|
|
1089
|
+
nodeCache.delete(key);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
const ctx = this.context;
|
|
1094
|
+
if (constraints != null) ctx.constraints = constraints;
|
|
1095
|
+
const result = node.measure(ctx);
|
|
1096
|
+
const key = constraintKey(constraints);
|
|
1097
|
+
let nodeCache = this.#cache.get(node);
|
|
1098
|
+
if (nodeCache == null) {
|
|
1099
|
+
nodeCache = /* @__PURE__ */ new Map();
|
|
1100
|
+
this.#cache.set(node, nodeCache);
|
|
1101
|
+
} else if (nodeCache.size >= MAX_CONSTRAINT_VARIANTS) {
|
|
1102
|
+
const firstKey = nodeCache.keys().next().value;
|
|
1103
|
+
nodeCache.delete(firstKey);
|
|
538
1104
|
}
|
|
539
|
-
|
|
540
|
-
|
|
1105
|
+
nodeCache.set(key, {
|
|
1106
|
+
revision: getNodeRevision(node),
|
|
1107
|
+
box: result
|
|
1108
|
+
});
|
|
541
1109
|
return result;
|
|
542
1110
|
}
|
|
543
1111
|
};
|
|
@@ -545,33 +1113,26 @@ var DebugRenderer = class extends BaseRenderer {
|
|
|
545
1113
|
draw(node) {
|
|
546
1114
|
const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
|
|
547
1115
|
this.graphics.clearRect(0, 0, viewportWidth, viewportHeight);
|
|
548
|
-
return
|
|
1116
|
+
return this.drawRootNode(node);
|
|
549
1117
|
}
|
|
550
1118
|
hittest(node, test) {
|
|
551
|
-
return
|
|
1119
|
+
return this.hittestRootNode(node, test);
|
|
552
1120
|
}
|
|
553
1121
|
};
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
function fn(item) {
|
|
557
|
-
const key = item;
|
|
558
|
-
const cached = cache.get(key);
|
|
559
|
-
if (cached != null) return cached;
|
|
560
|
-
const result = renderItem(item);
|
|
561
|
-
cache.set(key, result);
|
|
562
|
-
return result;
|
|
563
|
-
}
|
|
564
|
-
return Object.assign(fn, { reset: (key) => cache.delete(key) });
|
|
565
|
-
}
|
|
1122
|
+
//#endregion
|
|
1123
|
+
//#region src/renderer/list-state.ts
|
|
566
1124
|
var ListState = class {
|
|
567
1125
|
offset = 0;
|
|
568
|
-
position
|
|
1126
|
+
position;
|
|
569
1127
|
items = [];
|
|
1128
|
+
constructor(items = []) {
|
|
1129
|
+
this.items = [...items];
|
|
1130
|
+
}
|
|
570
1131
|
unshift(...items) {
|
|
571
1132
|
this.unshiftAll(items);
|
|
572
1133
|
}
|
|
573
1134
|
unshiftAll(items) {
|
|
574
|
-
this.position += items.length;
|
|
1135
|
+
if (this.position != null) this.position += items.length;
|
|
575
1136
|
this.items = items.concat(this.items);
|
|
576
1137
|
}
|
|
577
1138
|
push(...items) {
|
|
@@ -580,20 +1141,59 @@ var ListState = class {
|
|
|
580
1141
|
pushAll(items) {
|
|
581
1142
|
this.items.push(...items);
|
|
582
1143
|
}
|
|
583
|
-
|
|
584
|
-
this.
|
|
1144
|
+
setAnchor(position, offset = 0) {
|
|
1145
|
+
this.position = Number.isFinite(position) ? Math.trunc(position) : void 0;
|
|
1146
|
+
this.offset = Number.isFinite(offset) ? offset : 0;
|
|
1147
|
+
}
|
|
1148
|
+
reset(items = []) {
|
|
1149
|
+
this.items = [...items];
|
|
585
1150
|
this.offset = 0;
|
|
586
|
-
this.position =
|
|
1151
|
+
this.position = void 0;
|
|
587
1152
|
}
|
|
588
1153
|
resetScroll() {
|
|
589
1154
|
this.offset = 0;
|
|
590
|
-
this.position =
|
|
1155
|
+
this.position = void 0;
|
|
591
1156
|
}
|
|
592
1157
|
applyScroll(delta) {
|
|
593
1158
|
this.offset += delta;
|
|
594
1159
|
}
|
|
595
1160
|
};
|
|
596
|
-
|
|
1161
|
+
//#endregion
|
|
1162
|
+
//#region src/renderer/memo.ts
|
|
1163
|
+
function isWeakMapKey(value) {
|
|
1164
|
+
return typeof value === "object" && value !== null || typeof value === "function";
|
|
1165
|
+
}
|
|
1166
|
+
function memoRenderItem(renderItem) {
|
|
1167
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
1168
|
+
function fn(item) {
|
|
1169
|
+
if (!isWeakMapKey(item)) throw new TypeError("memoRenderItem() only supports object items. Use memoRenderItemBy() for primitive keys.");
|
|
1170
|
+
const key = item;
|
|
1171
|
+
const cached = cache.get(key);
|
|
1172
|
+
if (cached != null) return cached;
|
|
1173
|
+
const result = renderItem(item);
|
|
1174
|
+
cache.set(key, result);
|
|
1175
|
+
return result;
|
|
1176
|
+
}
|
|
1177
|
+
return Object.assign(fn, { reset: (key) => cache.delete(key) });
|
|
1178
|
+
}
|
|
1179
|
+
function memoRenderItemBy(keyOf, renderItem) {
|
|
1180
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1181
|
+
function fn(item) {
|
|
1182
|
+
const key = keyOf(item);
|
|
1183
|
+
const cached = cache.get(key);
|
|
1184
|
+
if (cached != null) return cached;
|
|
1185
|
+
const result = renderItem(item);
|
|
1186
|
+
cache.set(key, result);
|
|
1187
|
+
return result;
|
|
1188
|
+
}
|
|
1189
|
+
return Object.assign(fn, {
|
|
1190
|
+
reset: (item) => cache.delete(keyOf(item)),
|
|
1191
|
+
resetKey: (key) => cache.delete(key)
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
//#endregion
|
|
1195
|
+
//#region src/renderer/virtualized/base.ts
|
|
1196
|
+
function clamp$3(value, min, max) {
|
|
597
1197
|
return Math.min(Math.max(value, min), max);
|
|
598
1198
|
}
|
|
599
1199
|
function sameState(state, position, offset) {
|
|
@@ -629,13 +1229,23 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
629
1229
|
set items(value) {
|
|
630
1230
|
this.options.list.items = value;
|
|
631
1231
|
}
|
|
1232
|
+
_readListState() {
|
|
1233
|
+
return {
|
|
1234
|
+
position: this.position,
|
|
1235
|
+
offset: this.offset
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
_commitListState(state) {
|
|
1239
|
+
this.position = state.position;
|
|
1240
|
+
this.offset = state.offset;
|
|
1241
|
+
}
|
|
632
1242
|
jumpTo(index, options = {}) {
|
|
633
1243
|
if (this.items.length === 0) {
|
|
634
1244
|
this.#cancelJumpAnimation();
|
|
635
1245
|
return;
|
|
636
1246
|
}
|
|
637
1247
|
const targetIndex = this._clampItemIndex(index);
|
|
638
|
-
this.
|
|
1248
|
+
const currentState = this._normalizeListState(this._readListState());
|
|
639
1249
|
const targetBlock = options.block ?? this._getDefaultJumpBlock();
|
|
640
1250
|
const targetAnchor = this._getTargetAnchor(targetIndex, targetBlock);
|
|
641
1251
|
if (!(options.animated ?? true)) {
|
|
@@ -644,14 +1254,14 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
644
1254
|
options.onComplete?.();
|
|
645
1255
|
return;
|
|
646
1256
|
}
|
|
647
|
-
const startAnchor = this._readAnchor();
|
|
1257
|
+
const startAnchor = this._readAnchor(currentState);
|
|
648
1258
|
if (!Number.isFinite(startAnchor)) {
|
|
649
1259
|
this.#cancelJumpAnimation();
|
|
650
1260
|
this._applyAnchor(targetAnchor);
|
|
651
1261
|
options.onComplete?.();
|
|
652
1262
|
return;
|
|
653
1263
|
}
|
|
654
|
-
const duration = clamp(options.duration ?? VirtualizedRenderer.MIN_JUMP_DURATION + Math.abs(targetAnchor - startAnchor) * VirtualizedRenderer.JUMP_DURATION_PER_ITEM, 0, VirtualizedRenderer.MAX_JUMP_DURATION);
|
|
1264
|
+
const duration = clamp$3(options.duration ?? VirtualizedRenderer.MIN_JUMP_DURATION + Math.abs(targetAnchor - startAnchor) * VirtualizedRenderer.JUMP_DURATION_PER_ITEM, 0, VirtualizedRenderer.MAX_JUMP_DURATION);
|
|
655
1265
|
if (duration <= 0 || Math.abs(targetAnchor - startAnchor) <= Number.EPSILON) {
|
|
656
1266
|
this.#cancelJumpAnimation();
|
|
657
1267
|
this._applyAnchor(targetAnchor);
|
|
@@ -666,10 +1276,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
666
1276
|
needsMoreFrames: true,
|
|
667
1277
|
onComplete: options.onComplete
|
|
668
1278
|
};
|
|
669
|
-
this.#controlledState =
|
|
670
|
-
position: this.position,
|
|
671
|
-
offset: this.offset
|
|
672
|
-
};
|
|
1279
|
+
this.#controlledState = this._readListState();
|
|
673
1280
|
}
|
|
674
1281
|
_resetRenderFeedback(feedback) {
|
|
675
1282
|
if (feedback == null) return;
|
|
@@ -681,8 +1288,8 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
681
1288
|
_accumulateRenderFeedback(feedback, idx, top, height) {
|
|
682
1289
|
if (!Number.isFinite(top) || !Number.isFinite(height) || height <= 0) return;
|
|
683
1290
|
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
684
|
-
const visibleTop = clamp(-top, 0, height);
|
|
685
|
-
const visibleBottom = clamp(viewportHeight - top, 0, height);
|
|
1291
|
+
const visibleTop = clamp$3(-top, 0, height);
|
|
1292
|
+
const visibleBottom = clamp$3(viewportHeight - top, 0, height);
|
|
686
1293
|
if (visibleBottom <= visibleTop) return;
|
|
687
1294
|
const itemMin = idx + visibleTop / height;
|
|
688
1295
|
const itemMax = idx + visibleBottom / height;
|
|
@@ -694,14 +1301,26 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
694
1301
|
_renderDrawList(list, shift, feedback) {
|
|
695
1302
|
let result = false;
|
|
696
1303
|
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
697
|
-
for (const { idx, node, offset, height } of list) {
|
|
1304
|
+
for (const { idx, value: node, offset, height } of list) {
|
|
698
1305
|
const y = offset + shift;
|
|
699
1306
|
if (feedback != null) this._accumulateRenderFeedback(feedback, idx, y, height);
|
|
700
1307
|
if (y + height < 0 || y > viewportHeight) continue;
|
|
701
|
-
if (
|
|
1308
|
+
if (this.drawRootNode(node, 0, y)) result = true;
|
|
702
1309
|
}
|
|
703
1310
|
return result;
|
|
704
1311
|
}
|
|
1312
|
+
_renderVisibleWindow(window, feedback) {
|
|
1313
|
+
this._resetRenderFeedback(feedback);
|
|
1314
|
+
return this._renderDrawList(window.drawList, window.shift, feedback);
|
|
1315
|
+
}
|
|
1316
|
+
_hittestVisibleWindow(window, test) {
|
|
1317
|
+
for (const { value: node, offset, height } of window.drawList) {
|
|
1318
|
+
const y = offset + window.shift;
|
|
1319
|
+
if (test.y < y || test.y >= y + height) continue;
|
|
1320
|
+
return node.hittest(this.getRootContext(), shallowMerge(test, { y: test.y - y }));
|
|
1321
|
+
}
|
|
1322
|
+
return false;
|
|
1323
|
+
}
|
|
705
1324
|
_prepareRender() {
|
|
706
1325
|
const animation = this.#jumpAnimation;
|
|
707
1326
|
if (animation == null) return false;
|
|
@@ -713,7 +1332,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
713
1332
|
this.#cancelJumpAnimation();
|
|
714
1333
|
return false;
|
|
715
1334
|
}
|
|
716
|
-
const progress = clamp((getNow() - animation.startTime) / animation.duration, 0, 1);
|
|
1335
|
+
const progress = clamp$3((getNow() - animation.startTime) / animation.duration, 0, 1);
|
|
717
1336
|
const eased = progress >= 1 ? 1 : smoothstep(progress);
|
|
718
1337
|
const anchor = animation.startAnchor + (animation.targetAnchor - animation.startAnchor) * eased;
|
|
719
1338
|
this._applyAnchor(anchor);
|
|
@@ -724,10 +1343,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
724
1343
|
const animation = this.#jumpAnimation;
|
|
725
1344
|
if (animation == null) return requestRedraw;
|
|
726
1345
|
if (animation.needsMoreFrames) {
|
|
727
|
-
this.#controlledState =
|
|
728
|
-
position: this.position,
|
|
729
|
-
offset: this.offset
|
|
730
|
-
};
|
|
1346
|
+
this.#controlledState = this._readListState();
|
|
731
1347
|
return true;
|
|
732
1348
|
}
|
|
733
1349
|
const onComplete = animation.onComplete;
|
|
@@ -736,12 +1352,12 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
736
1352
|
return requestRedraw || this.#jumpAnimation != null;
|
|
737
1353
|
}
|
|
738
1354
|
_clampItemIndex(index) {
|
|
739
|
-
return clamp(Number.isFinite(index) ? Math.trunc(index) : 0, 0, this.items.length - 1);
|
|
1355
|
+
return clamp$3(Number.isFinite(index) ? Math.trunc(index) : 0, 0, this.items.length - 1);
|
|
740
1356
|
}
|
|
741
1357
|
_getItemHeight(index) {
|
|
742
1358
|
const item = this.items[index];
|
|
743
1359
|
const node = this.options.renderItem(item);
|
|
744
|
-
return this.
|
|
1360
|
+
return this.measureRootNode(node).height;
|
|
745
1361
|
}
|
|
746
1362
|
_getAnchorAtOffset(index, offset) {
|
|
747
1363
|
if (this.items.length === 0) return 0;
|
|
@@ -769,251 +1385,309 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
769
1385
|
this.#controlledState = void 0;
|
|
770
1386
|
}
|
|
771
1387
|
};
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1388
|
+
//#endregion
|
|
1389
|
+
//#region src/renderer/virtualized/solver.ts
|
|
1390
|
+
function clamp$2(value, min, max) {
|
|
1391
|
+
return Math.min(Math.max(value, min), max);
|
|
1392
|
+
}
|
|
1393
|
+
function normalizeOffset(offset) {
|
|
1394
|
+
return Number.isFinite(offset) ? offset : 0;
|
|
1395
|
+
}
|
|
1396
|
+
function normalizeTimelineState(itemCount, state) {
|
|
1397
|
+
if (itemCount <= 0) return {
|
|
1398
|
+
position: 0,
|
|
1399
|
+
offset: 0
|
|
1400
|
+
};
|
|
1401
|
+
const position = state.position;
|
|
1402
|
+
if (typeof position !== "number" || !Number.isFinite(position)) return {
|
|
1403
|
+
position: 0,
|
|
1404
|
+
offset: normalizeOffset(state.offset)
|
|
1405
|
+
};
|
|
1406
|
+
return {
|
|
1407
|
+
position: clamp$2(Math.trunc(position), 0, itemCount - 1),
|
|
1408
|
+
offset: normalizeOffset(state.offset)
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
function normalizeChatState(itemCount, state) {
|
|
1412
|
+
if (itemCount <= 0) return {
|
|
1413
|
+
position: 0,
|
|
1414
|
+
offset: 0
|
|
1415
|
+
};
|
|
1416
|
+
const position = state.position;
|
|
1417
|
+
if (typeof position !== "number" || !Number.isFinite(position)) return {
|
|
1418
|
+
position: itemCount - 1,
|
|
1419
|
+
offset: normalizeOffset(state.offset)
|
|
1420
|
+
};
|
|
1421
|
+
return {
|
|
1422
|
+
position: clamp$2(Math.trunc(position), 0, itemCount - 1),
|
|
1423
|
+
offset: normalizeOffset(state.offset)
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
function resolveTimelineVisibleWindow(items, state, viewportHeight, resolveItem) {
|
|
1427
|
+
const normalizedState = normalizeTimelineState(items.length, state);
|
|
1428
|
+
if (items.length === 0) return {
|
|
1429
|
+
normalizedState,
|
|
1430
|
+
window: {
|
|
1431
|
+
drawList: [],
|
|
1432
|
+
shift: 0
|
|
1433
|
+
}
|
|
1434
|
+
};
|
|
1435
|
+
let { position, offset } = normalizedState;
|
|
1436
|
+
let drawLength = 0;
|
|
1437
|
+
if (offset > 0) if (position === 0) offset = 0;
|
|
1438
|
+
else {
|
|
1439
|
+
for (let i = position - 1; i >= 0; i -= 1) {
|
|
1440
|
+
const { height } = resolveItem(items[i], i);
|
|
1441
|
+
position = i;
|
|
1442
|
+
offset -= height;
|
|
1443
|
+
if (offset <= 0) break;
|
|
1444
|
+
}
|
|
1445
|
+
if (position === 0 && offset > 0) offset = 0;
|
|
1446
|
+
}
|
|
1447
|
+
let y = offset;
|
|
1448
|
+
const drawList = [];
|
|
1449
|
+
for (let i = position; i < items.length; i += 1) {
|
|
1450
|
+
const { value, height } = resolveItem(items[i], i);
|
|
1451
|
+
if (y + height > 0) {
|
|
1452
|
+
drawList.push({
|
|
1453
|
+
idx: i,
|
|
1454
|
+
value,
|
|
1455
|
+
offset: y,
|
|
1456
|
+
height
|
|
1457
|
+
});
|
|
1458
|
+
drawLength += height;
|
|
1459
|
+
} else {
|
|
1460
|
+
offset += height;
|
|
1461
|
+
position = i + 1;
|
|
1462
|
+
}
|
|
1463
|
+
y += height;
|
|
1464
|
+
if (y >= viewportHeight) break;
|
|
1465
|
+
}
|
|
1466
|
+
let shift = 0;
|
|
1467
|
+
if (y < viewportHeight) if (position === 0 && drawLength < viewportHeight) {
|
|
1468
|
+
shift = -offset;
|
|
1469
|
+
offset = 0;
|
|
1470
|
+
} else {
|
|
1471
|
+
shift = viewportHeight - y;
|
|
1472
|
+
y = offset += shift;
|
|
1473
|
+
let lastIdx = -1;
|
|
1474
|
+
for (let i = position - 1; i >= 0; i -= 1) {
|
|
1475
|
+
const { value, height } = resolveItem(items[i], i);
|
|
1476
|
+
drawLength += height;
|
|
1477
|
+
y -= height;
|
|
1478
|
+
drawList.push({
|
|
1479
|
+
idx: i,
|
|
1480
|
+
value,
|
|
1481
|
+
offset: y - shift,
|
|
1482
|
+
height
|
|
1483
|
+
});
|
|
1484
|
+
lastIdx = i;
|
|
1485
|
+
if (y < 0) break;
|
|
1486
|
+
}
|
|
1487
|
+
if (lastIdx === 0 && drawLength < viewportHeight) {
|
|
1488
|
+
shift = drawList.at(-1)?.offset == null ? 0 : -drawList.at(-1).offset;
|
|
1489
|
+
position = 0;
|
|
1490
|
+
offset = 0;
|
|
1491
|
+
}
|
|
775
1492
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1493
|
+
return {
|
|
1494
|
+
normalizedState: {
|
|
1495
|
+
position,
|
|
1496
|
+
offset
|
|
1497
|
+
},
|
|
1498
|
+
window: {
|
|
1499
|
+
drawList,
|
|
1500
|
+
shift
|
|
1501
|
+
}
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
function resolveChatVisibleWindow(items, state, viewportHeight, resolveItem) {
|
|
1505
|
+
const normalizedState = normalizeChatState(items.length, state);
|
|
1506
|
+
if (items.length === 0) return {
|
|
1507
|
+
normalizedState,
|
|
1508
|
+
window: {
|
|
1509
|
+
drawList: [],
|
|
1510
|
+
shift: 0
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
let { position, offset } = normalizedState;
|
|
1514
|
+
let drawLength = 0;
|
|
1515
|
+
if (offset < 0) if (position === items.length - 1) offset = 0;
|
|
1516
|
+
else for (let i = position + 1; i < items.length; i += 1) {
|
|
1517
|
+
const { height } = resolveItem(items[i], i);
|
|
1518
|
+
position = i;
|
|
1519
|
+
offset += height;
|
|
1520
|
+
if (offset > 0) break;
|
|
1521
|
+
}
|
|
1522
|
+
let y = viewportHeight + offset;
|
|
1523
|
+
const drawList = [];
|
|
1524
|
+
for (let i = position; i >= 0; i -= 1) {
|
|
1525
|
+
const { value, height } = resolveItem(items[i], i);
|
|
1526
|
+
y -= height;
|
|
1527
|
+
if (y <= viewportHeight) {
|
|
1528
|
+
drawList.push({
|
|
1529
|
+
idx: i,
|
|
1530
|
+
value,
|
|
1531
|
+
offset: y,
|
|
1532
|
+
height
|
|
1533
|
+
});
|
|
1534
|
+
drawLength += height;
|
|
1535
|
+
} else {
|
|
1536
|
+
offset -= height;
|
|
1537
|
+
position = i - 1;
|
|
1538
|
+
}
|
|
1539
|
+
if (y < 0) break;
|
|
1540
|
+
}
|
|
1541
|
+
let shift = 0;
|
|
1542
|
+
if (y > 0) {
|
|
1543
|
+
shift = -y;
|
|
1544
|
+
if (drawLength < viewportHeight) {
|
|
1545
|
+
y = drawLength;
|
|
1546
|
+
for (let i = position + 1; i < items.length; i += 1) {
|
|
1547
|
+
const { value, height } = resolveItem(items[i], i);
|
|
1548
|
+
drawList.push({
|
|
1549
|
+
idx: i,
|
|
1550
|
+
value,
|
|
1551
|
+
offset: y - shift,
|
|
1552
|
+
height
|
|
1553
|
+
});
|
|
1554
|
+
y = drawLength += height;
|
|
1555
|
+
position = i;
|
|
1556
|
+
if (y >= viewportHeight) break;
|
|
1557
|
+
}
|
|
1558
|
+
offset = drawLength < viewportHeight ? 0 : drawLength - viewportHeight;
|
|
1559
|
+
} else offset = drawLength - viewportHeight;
|
|
1560
|
+
}
|
|
1561
|
+
return {
|
|
1562
|
+
normalizedState: {
|
|
1563
|
+
position,
|
|
1564
|
+
offset
|
|
1565
|
+
},
|
|
1566
|
+
window: {
|
|
1567
|
+
drawList,
|
|
1568
|
+
shift
|
|
782
1569
|
}
|
|
783
|
-
|
|
784
|
-
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
//#endregion
|
|
1573
|
+
//#region src/renderer/virtualized/chat.ts
|
|
1574
|
+
function clamp$1(value, min, max) {
|
|
1575
|
+
return Math.min(Math.max(value, min), max);
|
|
1576
|
+
}
|
|
1577
|
+
var ChatRenderer = class extends VirtualizedRenderer {
|
|
1578
|
+
#resolveVisibleWindow() {
|
|
1579
|
+
return resolveChatVisibleWindow(this.items, this._readListState(), this.graphics.canvas.clientHeight, (item) => {
|
|
1580
|
+
const node = this.options.renderItem(item);
|
|
1581
|
+
return {
|
|
1582
|
+
value: node,
|
|
1583
|
+
height: this.measureRootNode(node).height
|
|
1584
|
+
};
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
_getDefaultJumpBlock() {
|
|
1588
|
+
return "end";
|
|
1589
|
+
}
|
|
1590
|
+
_normalizeListState(state) {
|
|
1591
|
+
return normalizeChatState(this.items.length, state);
|
|
785
1592
|
}
|
|
786
|
-
_readAnchor() {
|
|
787
|
-
this._prepareAnchorState();
|
|
1593
|
+
_readAnchor(state) {
|
|
788
1594
|
if (this.items.length === 0) return 0;
|
|
789
|
-
const height = this._getItemHeight(
|
|
790
|
-
return height > 0 ?
|
|
1595
|
+
const height = this._getItemHeight(state.position);
|
|
1596
|
+
return height > 0 ? state.position + 1 - state.offset / height : state.position + 1;
|
|
791
1597
|
}
|
|
792
1598
|
_applyAnchor(anchor) {
|
|
793
1599
|
if (this.items.length === 0) return;
|
|
794
|
-
const clampedAnchor = clamp(anchor, 0, this.items.length);
|
|
795
|
-
const position = clamp(Math.
|
|
1600
|
+
const clampedAnchor = clamp$1(anchor, 0, this.items.length);
|
|
1601
|
+
const position = clamp$1(Math.ceil(clampedAnchor) - 1, 0, this.items.length - 1);
|
|
796
1602
|
const height = this._getItemHeight(position);
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
1603
|
+
const offset = height > 0 ? (position + 1 - clampedAnchor) * height : 0;
|
|
1604
|
+
this._commitListState({
|
|
1605
|
+
position,
|
|
1606
|
+
offset: Object.is(offset, -0) ? 0 : offset
|
|
1607
|
+
});
|
|
800
1608
|
}
|
|
801
1609
|
_getTargetAnchor(index, block) {
|
|
802
1610
|
const height = this._getItemHeight(index);
|
|
803
1611
|
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
804
1612
|
switch (block) {
|
|
805
|
-
case "start": return this._getAnchorAtOffset(index,
|
|
806
|
-
case "center": return this._getAnchorAtOffset(index, height / 2
|
|
807
|
-
case "end": return this._getAnchorAtOffset(index, height
|
|
1613
|
+
case "start": return this._getAnchorAtOffset(index, viewportHeight);
|
|
1614
|
+
case "center": return this._getAnchorAtOffset(index, height / 2 + viewportHeight / 2);
|
|
1615
|
+
case "end": return this._getAnchorAtOffset(index, height);
|
|
808
1616
|
}
|
|
809
1617
|
}
|
|
810
1618
|
render(feedback) {
|
|
811
1619
|
const keepAnimating = this._prepareRender();
|
|
812
1620
|
const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
|
|
813
1621
|
this.graphics.clearRect(0, 0, viewportWidth, viewportHeight);
|
|
814
|
-
this
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
if (this.offset > 0) if (this.position === 0) this.offset = 0;
|
|
818
|
-
else {
|
|
819
|
-
for (let i = this.position - 1; i >= 0; i -= 1) {
|
|
820
|
-
const item = this.items[i];
|
|
821
|
-
const node = this.options.renderItem(item);
|
|
822
|
-
const { height } = this.measureNode(node);
|
|
823
|
-
this.position = i;
|
|
824
|
-
this.offset -= height;
|
|
825
|
-
if (this.offset <= 0) break;
|
|
826
|
-
}
|
|
827
|
-
if (this.position === 0 && this.offset > 0) this.offset = 0;
|
|
828
|
-
}
|
|
829
|
-
let y = this.offset;
|
|
830
|
-
const drawList = [];
|
|
831
|
-
for (let i = this.position; i < this.items.length; i += 1) {
|
|
832
|
-
const item = this.items[i];
|
|
833
|
-
const node = this.options.renderItem(item);
|
|
834
|
-
const { height } = this.measureNode(node);
|
|
835
|
-
if (y + height > 0) {
|
|
836
|
-
drawList.push({
|
|
837
|
-
idx: i,
|
|
838
|
-
node,
|
|
839
|
-
offset: y,
|
|
840
|
-
height
|
|
841
|
-
});
|
|
842
|
-
drawLength += height;
|
|
843
|
-
} else {
|
|
844
|
-
this.offset += height;
|
|
845
|
-
this.position = i + 1;
|
|
846
|
-
}
|
|
847
|
-
y += height;
|
|
848
|
-
if (y >= viewportHeight) break;
|
|
849
|
-
}
|
|
850
|
-
let shift = 0;
|
|
851
|
-
if (y < viewportHeight) if (this.position === 0 && drawLength < viewportHeight) {
|
|
852
|
-
shift = -this.offset;
|
|
853
|
-
this.offset = 0;
|
|
854
|
-
} else {
|
|
855
|
-
shift = viewportHeight - y;
|
|
856
|
-
y = this.offset += shift;
|
|
857
|
-
let lastIdx = -1;
|
|
858
|
-
for (let i = this.position - 1; i >= 0; i -= 1) {
|
|
859
|
-
const item = this.items[lastIdx = i];
|
|
860
|
-
const node = this.options.renderItem(item);
|
|
861
|
-
const { height } = this.measureNode(node);
|
|
862
|
-
drawLength += height;
|
|
863
|
-
y -= height;
|
|
864
|
-
drawList.push({
|
|
865
|
-
idx: i,
|
|
866
|
-
node,
|
|
867
|
-
offset: y - shift,
|
|
868
|
-
height
|
|
869
|
-
});
|
|
870
|
-
if (y < 0) break;
|
|
871
|
-
}
|
|
872
|
-
if (lastIdx === 0 && drawLength < viewportHeight) {
|
|
873
|
-
shift = -drawList[drawList.length - 1].offset;
|
|
874
|
-
this.position = 0;
|
|
875
|
-
this.offset = 0;
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
const requestRedraw = this._renderDrawList(drawList, shift, feedback);
|
|
1622
|
+
const solution = this.#resolveVisibleWindow();
|
|
1623
|
+
const requestRedraw = this._renderVisibleWindow(solution.window, feedback);
|
|
1624
|
+
this._commitListState(solution.normalizedState);
|
|
879
1625
|
return this._finishRender(keepAnimating || requestRedraw);
|
|
880
1626
|
}
|
|
881
1627
|
hittest(test) {
|
|
882
|
-
|
|
883
|
-
let y = this.offset;
|
|
884
|
-
for (let i = this.position; i < this.items.length; i += 1) {
|
|
885
|
-
const item = this.items[i];
|
|
886
|
-
const node = this.options.renderItem(item);
|
|
887
|
-
const { height } = this.measureNode(node);
|
|
888
|
-
if (test.y < y + height) return node.hittest(this.context, shallowMerge(test, { y: test.y - y }));
|
|
889
|
-
y += height;
|
|
890
|
-
if (y >= viewportHeight) break;
|
|
891
|
-
}
|
|
892
|
-
return false;
|
|
1628
|
+
return this._hittestVisibleWindow(this.#resolveVisibleWindow().window, test);
|
|
893
1629
|
}
|
|
894
1630
|
};
|
|
895
|
-
|
|
1631
|
+
//#endregion
|
|
1632
|
+
//#region src/renderer/virtualized/timeline.ts
|
|
1633
|
+
function clamp(value, min, max) {
|
|
1634
|
+
return Math.min(Math.max(value, min), max);
|
|
1635
|
+
}
|
|
1636
|
+
var TimelineRenderer = class extends VirtualizedRenderer {
|
|
1637
|
+
#resolveVisibleWindow() {
|
|
1638
|
+
return resolveTimelineVisibleWindow(this.items, this._readListState(), this.graphics.canvas.clientHeight, (item) => {
|
|
1639
|
+
const node = this.options.renderItem(item);
|
|
1640
|
+
return {
|
|
1641
|
+
value: node,
|
|
1642
|
+
height: this.measureRootNode(node).height
|
|
1643
|
+
};
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
896
1646
|
_getDefaultJumpBlock() {
|
|
897
|
-
return "
|
|
1647
|
+
return "start";
|
|
898
1648
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
if (!Number.isFinite(this.position)) {
|
|
902
|
-
this.position = this.items.length - 1;
|
|
903
|
-
this.offset = 0;
|
|
904
|
-
return;
|
|
905
|
-
}
|
|
906
|
-
this.position = this._clampItemIndex(this.position);
|
|
907
|
-
if (!Number.isFinite(this.offset)) this.offset = 0;
|
|
1649
|
+
_normalizeListState(state) {
|
|
1650
|
+
return normalizeTimelineState(this.items.length, state);
|
|
908
1651
|
}
|
|
909
|
-
_readAnchor() {
|
|
910
|
-
this._prepareAnchorState();
|
|
1652
|
+
_readAnchor(state) {
|
|
911
1653
|
if (this.items.length === 0) return 0;
|
|
912
|
-
const height = this._getItemHeight(
|
|
913
|
-
return height > 0 ?
|
|
1654
|
+
const height = this._getItemHeight(state.position);
|
|
1655
|
+
return height > 0 ? state.position - state.offset / height : state.position;
|
|
914
1656
|
}
|
|
915
1657
|
_applyAnchor(anchor) {
|
|
916
1658
|
if (this.items.length === 0) return;
|
|
917
1659
|
const clampedAnchor = clamp(anchor, 0, this.items.length);
|
|
918
|
-
const position = clamp(Math.
|
|
1660
|
+
const position = clamp(Math.floor(clampedAnchor), 0, this.items.length - 1);
|
|
919
1661
|
const height = this._getItemHeight(position);
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1662
|
+
const offset = height > 0 ? -(clampedAnchor - position) * height : 0;
|
|
1663
|
+
this._commitListState({
|
|
1664
|
+
position,
|
|
1665
|
+
offset: Object.is(offset, -0) ? 0 : offset
|
|
1666
|
+
});
|
|
923
1667
|
}
|
|
924
1668
|
_getTargetAnchor(index, block) {
|
|
925
1669
|
const height = this._getItemHeight(index);
|
|
926
1670
|
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
927
1671
|
switch (block) {
|
|
928
|
-
case "start": return this._getAnchorAtOffset(index,
|
|
929
|
-
case "center": return this._getAnchorAtOffset(index, height / 2
|
|
930
|
-
case "end": return this._getAnchorAtOffset(index, height);
|
|
1672
|
+
case "start": return this._getAnchorAtOffset(index, 0);
|
|
1673
|
+
case "center": return this._getAnchorAtOffset(index, height / 2 - viewportHeight / 2);
|
|
1674
|
+
case "end": return this._getAnchorAtOffset(index, height - viewportHeight);
|
|
931
1675
|
}
|
|
932
1676
|
}
|
|
933
1677
|
render(feedback) {
|
|
934
1678
|
const keepAnimating = this._prepareRender();
|
|
935
1679
|
const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
|
|
936
1680
|
this.graphics.clearRect(0, 0, viewportWidth, viewportHeight);
|
|
937
|
-
this
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
if (this.offset < 0) if (this.position === this.items.length - 1) this.offset = 0;
|
|
941
|
-
else for (let i = this.position + 1; i < this.items.length; i += 1) {
|
|
942
|
-
const item = this.items[i];
|
|
943
|
-
const node = this.options.renderItem(item);
|
|
944
|
-
const { height } = this.measureNode(node);
|
|
945
|
-
this.position = i;
|
|
946
|
-
this.offset += height;
|
|
947
|
-
if (this.offset > 0) break;
|
|
948
|
-
}
|
|
949
|
-
let y = viewportHeight + this.offset;
|
|
950
|
-
const drawList = [];
|
|
951
|
-
for (let i = this.position; i >= 0; i -= 1) {
|
|
952
|
-
const item = this.items[i];
|
|
953
|
-
const node = this.options.renderItem(item);
|
|
954
|
-
const { height } = this.measureNode(node);
|
|
955
|
-
y -= height;
|
|
956
|
-
if (y <= viewportHeight) {
|
|
957
|
-
drawList.push({
|
|
958
|
-
idx: i,
|
|
959
|
-
node,
|
|
960
|
-
offset: y,
|
|
961
|
-
height
|
|
962
|
-
});
|
|
963
|
-
drawLength += height;
|
|
964
|
-
} else {
|
|
965
|
-
this.offset -= height;
|
|
966
|
-
this.position = i - 1;
|
|
967
|
-
}
|
|
968
|
-
if (y < 0) break;
|
|
969
|
-
}
|
|
970
|
-
let shift = 0;
|
|
971
|
-
if (y > 0) {
|
|
972
|
-
shift = -y;
|
|
973
|
-
if (drawLength < viewportHeight) {
|
|
974
|
-
y = drawLength;
|
|
975
|
-
for (let i = this.position + 1; i < this.items.length; i += 1) {
|
|
976
|
-
const item = this.items[i];
|
|
977
|
-
const node = this.options.renderItem(item);
|
|
978
|
-
const { height } = this.measureNode(node);
|
|
979
|
-
drawList.push({
|
|
980
|
-
idx: i,
|
|
981
|
-
node,
|
|
982
|
-
offset: y - shift,
|
|
983
|
-
height
|
|
984
|
-
});
|
|
985
|
-
y = drawLength += height;
|
|
986
|
-
this.position = i;
|
|
987
|
-
if (y >= viewportHeight) break;
|
|
988
|
-
}
|
|
989
|
-
if (drawLength < viewportHeight) this.offset = 0;
|
|
990
|
-
else this.offset = drawLength - viewportHeight;
|
|
991
|
-
} else this.offset = drawLength - viewportHeight;
|
|
992
|
-
}
|
|
993
|
-
const requestRedraw = this._renderDrawList(drawList, shift, feedback);
|
|
1681
|
+
const solution = this.#resolveVisibleWindow();
|
|
1682
|
+
const requestRedraw = this._renderVisibleWindow(solution.window, feedback);
|
|
1683
|
+
this._commitListState(solution.normalizedState);
|
|
994
1684
|
return this._finishRender(keepAnimating || requestRedraw);
|
|
995
1685
|
}
|
|
996
1686
|
hittest(test) {
|
|
997
|
-
|
|
998
|
-
let drawLength = 0;
|
|
999
|
-
const heights = [];
|
|
1000
|
-
for (let i = this.position; i >= 0; i -= 1) {
|
|
1001
|
-
const item = this.items[i];
|
|
1002
|
-
const node = this.options.renderItem(item);
|
|
1003
|
-
const { height } = this.measureNode(node);
|
|
1004
|
-
drawLength += height;
|
|
1005
|
-
heights.push([node, height]);
|
|
1006
|
-
}
|
|
1007
|
-
let y = drawLength < viewportHeight ? drawLength : viewportHeight + this.offset;
|
|
1008
|
-
if (test.y > y) return false;
|
|
1009
|
-
for (const [node, height] of heights) {
|
|
1010
|
-
y -= height;
|
|
1011
|
-
if (test.y > y) return node.hittest(this.context, shallowMerge(test, { y: test.y - y }));
|
|
1012
|
-
}
|
|
1013
|
-
return false;
|
|
1687
|
+
return this._hittestVisibleWindow(this.#resolveVisibleWindow().window, test);
|
|
1014
1688
|
}
|
|
1015
1689
|
};
|
|
1016
1690
|
//#endregion
|
|
1017
|
-
export {
|
|
1691
|
+
export { BaseRenderer, ChatRenderer, DebugRenderer, Fixed, Flex, FlexItem, Group, ListState, MultilineText, PaddingBox, Place, Text, TimelineRenderer, VirtualizedRenderer, Wrapper, memoRenderItem, memoRenderItemBy };
|
|
1018
1692
|
|
|
1019
1693
|
//# sourceMappingURL=index.mjs.map
|