chat-layout 0.1.4 → 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 +195 -110
- package/index.mjs +1304 -590
- 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);
|
|
21
|
+
}
|
|
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);
|
|
77
29
|
}
|
|
78
|
-
function
|
|
79
|
-
|
|
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);
|
|
80
48
|
}
|
|
81
|
-
function
|
|
82
|
-
|
|
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;
|
|
164
|
-
}
|
|
165
|
-
measure(ctx) {
|
|
166
|
-
let width = 0;
|
|
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
|
-
};
|
|
58
|
+
this.#children = [...children];
|
|
59
|
+
replaceNodesParent([], this.#children, this);
|
|
192
60
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const reverse = this.options.reverse ?? ctx.reverse;
|
|
196
|
-
if (this.options.reverse) ctx.reverse = this.options.reverse;
|
|
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;
|
|
61
|
+
get children() {
|
|
62
|
+
return this.#children;
|
|
224
63
|
}
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
956
|
+
getLayoutResult(node, constraints) {
|
|
957
|
+
return self.getLayoutResult(node, constraints);
|
|
958
|
+
},
|
|
959
|
+
setLayoutResult(node, result, constraints) {
|
|
960
|
+
self.setLayoutResult(node, result, constraints);
|
|
504
961
|
},
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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;
|
|
538
1062
|
}
|
|
539
|
-
|
|
540
|
-
|
|
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);
|
|
1104
|
+
}
|
|
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,28 +1229,39 @@ 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.
|
|
639
|
-
const
|
|
1248
|
+
const currentState = this._normalizeListState(this._readListState());
|
|
1249
|
+
const targetBlock = options.block ?? this._getDefaultJumpBlock();
|
|
1250
|
+
const targetAnchor = this._getTargetAnchor(targetIndex, targetBlock);
|
|
640
1251
|
if (!(options.animated ?? true)) {
|
|
641
1252
|
this.#cancelJumpAnimation();
|
|
642
1253
|
this._applyAnchor(targetAnchor);
|
|
643
1254
|
options.onComplete?.();
|
|
644
1255
|
return;
|
|
645
1256
|
}
|
|
646
|
-
const startAnchor = this._readAnchor();
|
|
1257
|
+
const startAnchor = this._readAnchor(currentState);
|
|
647
1258
|
if (!Number.isFinite(startAnchor)) {
|
|
648
1259
|
this.#cancelJumpAnimation();
|
|
649
1260
|
this._applyAnchor(targetAnchor);
|
|
650
1261
|
options.onComplete?.();
|
|
651
1262
|
return;
|
|
652
1263
|
}
|
|
653
|
-
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);
|
|
654
1265
|
if (duration <= 0 || Math.abs(targetAnchor - startAnchor) <= Number.EPSILON) {
|
|
655
1266
|
this.#cancelJumpAnimation();
|
|
656
1267
|
this._applyAnchor(targetAnchor);
|
|
@@ -665,10 +1276,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
665
1276
|
needsMoreFrames: true,
|
|
666
1277
|
onComplete: options.onComplete
|
|
667
1278
|
};
|
|
668
|
-
this.#controlledState =
|
|
669
|
-
position: this.position,
|
|
670
|
-
offset: this.offset
|
|
671
|
-
};
|
|
1279
|
+
this.#controlledState = this._readListState();
|
|
672
1280
|
}
|
|
673
1281
|
_resetRenderFeedback(feedback) {
|
|
674
1282
|
if (feedback == null) return;
|
|
@@ -680,8 +1288,8 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
680
1288
|
_accumulateRenderFeedback(feedback, idx, top, height) {
|
|
681
1289
|
if (!Number.isFinite(top) || !Number.isFinite(height) || height <= 0) return;
|
|
682
1290
|
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
683
|
-
const visibleTop = clamp(-top, 0, height);
|
|
684
|
-
const visibleBottom = clamp(viewportHeight - top, 0, height);
|
|
1291
|
+
const visibleTop = clamp$3(-top, 0, height);
|
|
1292
|
+
const visibleBottom = clamp$3(viewportHeight - top, 0, height);
|
|
685
1293
|
if (visibleBottom <= visibleTop) return;
|
|
686
1294
|
const itemMin = idx + visibleTop / height;
|
|
687
1295
|
const itemMax = idx + visibleBottom / height;
|
|
@@ -693,14 +1301,26 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
693
1301
|
_renderDrawList(list, shift, feedback) {
|
|
694
1302
|
let result = false;
|
|
695
1303
|
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
696
|
-
for (const { idx, node, offset, height } of list) {
|
|
1304
|
+
for (const { idx, value: node, offset, height } of list) {
|
|
697
1305
|
const y = offset + shift;
|
|
698
1306
|
if (feedback != null) this._accumulateRenderFeedback(feedback, idx, y, height);
|
|
699
1307
|
if (y + height < 0 || y > viewportHeight) continue;
|
|
700
|
-
if (
|
|
1308
|
+
if (this.drawRootNode(node, 0, y)) result = true;
|
|
701
1309
|
}
|
|
702
1310
|
return result;
|
|
703
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
|
+
}
|
|
704
1324
|
_prepareRender() {
|
|
705
1325
|
const animation = this.#jumpAnimation;
|
|
706
1326
|
if (animation == null) return false;
|
|
@@ -712,7 +1332,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
712
1332
|
this.#cancelJumpAnimation();
|
|
713
1333
|
return false;
|
|
714
1334
|
}
|
|
715
|
-
const progress = clamp((getNow() - animation.startTime) / animation.duration, 0, 1);
|
|
1335
|
+
const progress = clamp$3((getNow() - animation.startTime) / animation.duration, 0, 1);
|
|
716
1336
|
const eased = progress >= 1 ? 1 : smoothstep(progress);
|
|
717
1337
|
const anchor = animation.startAnchor + (animation.targetAnchor - animation.startAnchor) * eased;
|
|
718
1338
|
this._applyAnchor(anchor);
|
|
@@ -723,10 +1343,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
723
1343
|
const animation = this.#jumpAnimation;
|
|
724
1344
|
if (animation == null) return requestRedraw;
|
|
725
1345
|
if (animation.needsMoreFrames) {
|
|
726
|
-
this.#controlledState =
|
|
727
|
-
position: this.position,
|
|
728
|
-
offset: this.offset
|
|
729
|
-
};
|
|
1346
|
+
this.#controlledState = this._readListState();
|
|
730
1347
|
return true;
|
|
731
1348
|
}
|
|
732
1349
|
const onComplete = animation.onComplete;
|
|
@@ -735,245 +1352,342 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
735
1352
|
return requestRedraw || this.#jumpAnimation != null;
|
|
736
1353
|
}
|
|
737
1354
|
_clampItemIndex(index) {
|
|
738
|
-
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);
|
|
739
1356
|
}
|
|
740
1357
|
_getItemHeight(index) {
|
|
741
1358
|
const item = this.items[index];
|
|
742
1359
|
const node = this.options.renderItem(item);
|
|
743
|
-
return this.
|
|
1360
|
+
return this.measureRootNode(node).height;
|
|
1361
|
+
}
|
|
1362
|
+
_getAnchorAtOffset(index, offset) {
|
|
1363
|
+
if (this.items.length === 0) return 0;
|
|
1364
|
+
let currentIndex = this._clampItemIndex(index);
|
|
1365
|
+
let remaining = Number.isFinite(offset) ? offset : 0;
|
|
1366
|
+
while (true) {
|
|
1367
|
+
if (remaining < 0) {
|
|
1368
|
+
if (currentIndex === 0) return 0;
|
|
1369
|
+
currentIndex -= 1;
|
|
1370
|
+
const height = this._getItemHeight(currentIndex);
|
|
1371
|
+
if (height > 0) remaining += height;
|
|
1372
|
+
continue;
|
|
1373
|
+
}
|
|
1374
|
+
const height = this._getItemHeight(currentIndex);
|
|
1375
|
+
if (height > 0) {
|
|
1376
|
+
if (remaining <= height) return currentIndex + remaining / height;
|
|
1377
|
+
remaining -= height;
|
|
1378
|
+
} else if (remaining === 0) return currentIndex;
|
|
1379
|
+
if (currentIndex === this.items.length - 1) return this.items.length;
|
|
1380
|
+
currentIndex += 1;
|
|
1381
|
+
}
|
|
744
1382
|
}
|
|
745
1383
|
#cancelJumpAnimation() {
|
|
746
1384
|
this.#jumpAnimation = void 0;
|
|
747
1385
|
this.#controlledState = void 0;
|
|
748
1386
|
}
|
|
749
1387
|
};
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
+
}
|
|
1492
|
+
}
|
|
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
|
|
757
1569
|
}
|
|
758
|
-
|
|
759
|
-
|
|
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";
|
|
760
1589
|
}
|
|
761
|
-
|
|
762
|
-
this.
|
|
1590
|
+
_normalizeListState(state) {
|
|
1591
|
+
return normalizeChatState(this.items.length, state);
|
|
1592
|
+
}
|
|
1593
|
+
_readAnchor(state) {
|
|
763
1594
|
if (this.items.length === 0) return 0;
|
|
764
|
-
const height = this._getItemHeight(
|
|
765
|
-
return height > 0 ?
|
|
1595
|
+
const height = this._getItemHeight(state.position);
|
|
1596
|
+
return height > 0 ? state.position + 1 - state.offset / height : state.position + 1;
|
|
766
1597
|
}
|
|
767
1598
|
_applyAnchor(anchor) {
|
|
768
1599
|
if (this.items.length === 0) return;
|
|
769
|
-
const clampedAnchor = clamp(anchor, 0, this.items.length);
|
|
770
|
-
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);
|
|
771
1602
|
const height = this._getItemHeight(position);
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1603
|
+
const offset = height > 0 ? (position + 1 - clampedAnchor) * height : 0;
|
|
1604
|
+
this._commitListState({
|
|
1605
|
+
position,
|
|
1606
|
+
offset: Object.is(offset, -0) ? 0 : offset
|
|
1607
|
+
});
|
|
775
1608
|
}
|
|
776
|
-
_getTargetAnchor(index) {
|
|
777
|
-
|
|
1609
|
+
_getTargetAnchor(index, block) {
|
|
1610
|
+
const height = this._getItemHeight(index);
|
|
1611
|
+
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
1612
|
+
switch (block) {
|
|
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);
|
|
1616
|
+
}
|
|
778
1617
|
}
|
|
779
1618
|
render(feedback) {
|
|
780
1619
|
const keepAnimating = this._prepareRender();
|
|
781
1620
|
const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
|
|
782
1621
|
this.graphics.clearRect(0, 0, viewportWidth, viewportHeight);
|
|
783
|
-
this
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
if (this.offset > 0) if (this.position === 0) this.offset = 0;
|
|
787
|
-
else {
|
|
788
|
-
for (let i = this.position - 1; i >= 0; i -= 1) {
|
|
789
|
-
const item = this.items[i];
|
|
790
|
-
const node = this.options.renderItem(item);
|
|
791
|
-
const { height } = this.measureNode(node);
|
|
792
|
-
this.position = i;
|
|
793
|
-
this.offset -= height;
|
|
794
|
-
if (this.offset <= 0) break;
|
|
795
|
-
}
|
|
796
|
-
if (this.position === 0 && this.offset > 0) this.offset = 0;
|
|
797
|
-
}
|
|
798
|
-
let y = this.offset;
|
|
799
|
-
const drawList = [];
|
|
800
|
-
for (let i = this.position; i < this.items.length; i += 1) {
|
|
801
|
-
const item = this.items[i];
|
|
802
|
-
const node = this.options.renderItem(item);
|
|
803
|
-
const { height } = this.measureNode(node);
|
|
804
|
-
if (y + height > 0) {
|
|
805
|
-
drawList.push({
|
|
806
|
-
idx: i,
|
|
807
|
-
node,
|
|
808
|
-
offset: y,
|
|
809
|
-
height
|
|
810
|
-
});
|
|
811
|
-
drawLength += height;
|
|
812
|
-
} else {
|
|
813
|
-
this.offset += height;
|
|
814
|
-
this.position = i + 1;
|
|
815
|
-
}
|
|
816
|
-
y += height;
|
|
817
|
-
if (y >= viewportHeight) break;
|
|
818
|
-
}
|
|
819
|
-
let shift = 0;
|
|
820
|
-
if (y < viewportHeight) if (this.position === 0 && drawLength < viewportHeight) {
|
|
821
|
-
shift = -this.offset;
|
|
822
|
-
this.offset = 0;
|
|
823
|
-
} else {
|
|
824
|
-
shift = viewportHeight - y;
|
|
825
|
-
y = this.offset += shift;
|
|
826
|
-
let lastIdx = -1;
|
|
827
|
-
for (let i = this.position - 1; i >= 0; i -= 1) {
|
|
828
|
-
const item = this.items[lastIdx = i];
|
|
829
|
-
const node = this.options.renderItem(item);
|
|
830
|
-
const { height } = this.measureNode(node);
|
|
831
|
-
drawLength += height;
|
|
832
|
-
y -= height;
|
|
833
|
-
drawList.push({
|
|
834
|
-
idx: i,
|
|
835
|
-
node,
|
|
836
|
-
offset: y - shift,
|
|
837
|
-
height
|
|
838
|
-
});
|
|
839
|
-
if (y < 0) break;
|
|
840
|
-
}
|
|
841
|
-
if (lastIdx === 0 && drawLength < viewportHeight) {
|
|
842
|
-
shift = -drawList[drawList.length - 1].offset;
|
|
843
|
-
this.position = 0;
|
|
844
|
-
this.offset = 0;
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
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);
|
|
848
1625
|
return this._finishRender(keepAnimating || requestRedraw);
|
|
849
1626
|
}
|
|
850
1627
|
hittest(test) {
|
|
851
|
-
|
|
852
|
-
let y = this.offset;
|
|
853
|
-
for (let i = this.position; i < this.items.length; i += 1) {
|
|
854
|
-
const item = this.items[i];
|
|
855
|
-
const node = this.options.renderItem(item);
|
|
856
|
-
const { height } = this.measureNode(node);
|
|
857
|
-
if (test.y < y + height) return node.hittest(this.context, shallowMerge(test, { y: test.y - y }));
|
|
858
|
-
y += height;
|
|
859
|
-
if (y >= viewportHeight) break;
|
|
860
|
-
}
|
|
861
|
-
return false;
|
|
1628
|
+
return this._hittestVisibleWindow(this.#resolveVisibleWindow().window, test);
|
|
862
1629
|
}
|
|
863
1630
|
};
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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
|
+
});
|
|
874
1645
|
}
|
|
875
|
-
|
|
876
|
-
|
|
1646
|
+
_getDefaultJumpBlock() {
|
|
1647
|
+
return "start";
|
|
1648
|
+
}
|
|
1649
|
+
_normalizeListState(state) {
|
|
1650
|
+
return normalizeTimelineState(this.items.length, state);
|
|
1651
|
+
}
|
|
1652
|
+
_readAnchor(state) {
|
|
877
1653
|
if (this.items.length === 0) return 0;
|
|
878
|
-
const height = this._getItemHeight(
|
|
879
|
-
return height > 0 ?
|
|
1654
|
+
const height = this._getItemHeight(state.position);
|
|
1655
|
+
return height > 0 ? state.position - state.offset / height : state.position;
|
|
880
1656
|
}
|
|
881
1657
|
_applyAnchor(anchor) {
|
|
882
1658
|
if (this.items.length === 0) return;
|
|
883
1659
|
const clampedAnchor = clamp(anchor, 0, this.items.length);
|
|
884
|
-
const position = clamp(Math.
|
|
1660
|
+
const position = clamp(Math.floor(clampedAnchor), 0, this.items.length - 1);
|
|
885
1661
|
const height = this._getItemHeight(position);
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1662
|
+
const offset = height > 0 ? -(clampedAnchor - position) * height : 0;
|
|
1663
|
+
this._commitListState({
|
|
1664
|
+
position,
|
|
1665
|
+
offset: Object.is(offset, -0) ? 0 : offset
|
|
1666
|
+
});
|
|
889
1667
|
}
|
|
890
|
-
_getTargetAnchor(index) {
|
|
891
|
-
|
|
1668
|
+
_getTargetAnchor(index, block) {
|
|
1669
|
+
const height = this._getItemHeight(index);
|
|
1670
|
+
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
1671
|
+
switch (block) {
|
|
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);
|
|
1675
|
+
}
|
|
892
1676
|
}
|
|
893
1677
|
render(feedback) {
|
|
894
1678
|
const keepAnimating = this._prepareRender();
|
|
895
1679
|
const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
|
|
896
1680
|
this.graphics.clearRect(0, 0, viewportWidth, viewportHeight);
|
|
897
|
-
this
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
if (this.offset < 0) if (this.position === this.items.length - 1) this.offset = 0;
|
|
901
|
-
else for (let i = this.position + 1; i < this.items.length; i += 1) {
|
|
902
|
-
const item = this.items[i];
|
|
903
|
-
const node = this.options.renderItem(item);
|
|
904
|
-
const { height } = this.measureNode(node);
|
|
905
|
-
this.position = i;
|
|
906
|
-
this.offset += height;
|
|
907
|
-
if (this.offset > 0) break;
|
|
908
|
-
}
|
|
909
|
-
let y = viewportHeight + this.offset;
|
|
910
|
-
const drawList = [];
|
|
911
|
-
for (let i = this.position; i >= 0; i -= 1) {
|
|
912
|
-
const item = this.items[i];
|
|
913
|
-
const node = this.options.renderItem(item);
|
|
914
|
-
const { height } = this.measureNode(node);
|
|
915
|
-
y -= height;
|
|
916
|
-
if (y <= viewportHeight) {
|
|
917
|
-
drawList.push({
|
|
918
|
-
idx: i,
|
|
919
|
-
node,
|
|
920
|
-
offset: y,
|
|
921
|
-
height
|
|
922
|
-
});
|
|
923
|
-
drawLength += height;
|
|
924
|
-
} else {
|
|
925
|
-
this.offset -= height;
|
|
926
|
-
this.position = i - 1;
|
|
927
|
-
}
|
|
928
|
-
if (y < 0) break;
|
|
929
|
-
}
|
|
930
|
-
let shift = 0;
|
|
931
|
-
if (y > 0) {
|
|
932
|
-
shift = -y;
|
|
933
|
-
if (drawLength < viewportHeight) {
|
|
934
|
-
y = drawLength;
|
|
935
|
-
for (let i = this.position + 1; i < this.items.length; i += 1) {
|
|
936
|
-
const item = this.items[i];
|
|
937
|
-
const node = this.options.renderItem(item);
|
|
938
|
-
const { height } = this.measureNode(node);
|
|
939
|
-
drawList.push({
|
|
940
|
-
idx: i,
|
|
941
|
-
node,
|
|
942
|
-
offset: y - shift,
|
|
943
|
-
height
|
|
944
|
-
});
|
|
945
|
-
y = drawLength += height;
|
|
946
|
-
this.position = i;
|
|
947
|
-
if (y >= viewportHeight) break;
|
|
948
|
-
}
|
|
949
|
-
if (drawLength < viewportHeight) this.offset = 0;
|
|
950
|
-
else this.offset = drawLength - viewportHeight;
|
|
951
|
-
} else this.offset = drawLength - viewportHeight;
|
|
952
|
-
}
|
|
953
|
-
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);
|
|
954
1684
|
return this._finishRender(keepAnimating || requestRedraw);
|
|
955
1685
|
}
|
|
956
1686
|
hittest(test) {
|
|
957
|
-
|
|
958
|
-
let drawLength = 0;
|
|
959
|
-
const heights = [];
|
|
960
|
-
for (let i = this.position; i >= 0; i -= 1) {
|
|
961
|
-
const item = this.items[i];
|
|
962
|
-
const node = this.options.renderItem(item);
|
|
963
|
-
const { height } = this.measureNode(node);
|
|
964
|
-
drawLength += height;
|
|
965
|
-
heights.push([node, height]);
|
|
966
|
-
}
|
|
967
|
-
let y = drawLength < viewportHeight ? drawLength : viewportHeight + this.offset;
|
|
968
|
-
if (test.y > y) return false;
|
|
969
|
-
for (const [node, height] of heights) {
|
|
970
|
-
y -= height;
|
|
971
|
-
if (test.y > y) return node.hittest(this.context, shallowMerge(test, { y: test.y - y }));
|
|
972
|
-
}
|
|
973
|
-
return false;
|
|
1687
|
+
return this._hittestVisibleWindow(this.#resolveVisibleWindow().window, test);
|
|
974
1688
|
}
|
|
975
1689
|
};
|
|
976
1690
|
//#endregion
|
|
977
|
-
export {
|
|
1691
|
+
export { BaseRenderer, ChatRenderer, DebugRenderer, Fixed, Flex, FlexItem, Group, ListState, MultilineText, PaddingBox, Place, Text, TimelineRenderer, VirtualizedRenderer, Wrapper, memoRenderItem, memoRenderItemBy };
|
|
978
1692
|
|
|
979
1693
|
//# sourceMappingURL=index.mjs.map
|