chat-layout 0.1.5 → 1.0.0-2
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 +212 -6
- package/index.d.mts +199 -107
- package/index.mjs +1479 -584
- package/index.mjs.map +1 -1
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -1,62 +1,54 @@
|
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
11
|
+
function bumpRevision(node) {
|
|
12
|
+
revisions.set(node, (revisions.get(node) ?? 0) + 1);
|
|
13
|
+
}
|
|
14
|
+
function getNodeRevision(node) {
|
|
15
|
+
return revisions.get(node) ?? 0;
|
|
16
|
+
}
|
|
17
|
+
function attachNodeToParent(node, parent) {
|
|
18
|
+
if (registry.has(node)) throw getOwnershipError();
|
|
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);
|
|
29
|
+
}
|
|
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);
|
|
60
52
|
}
|
|
61
53
|
//#endregion
|
|
62
54
|
//#region src/utils.ts
|
|
@@ -70,218 +62,53 @@ function shallowMerge(object, other) {
|
|
|
70
62
|
};
|
|
71
63
|
}
|
|
72
64
|
//#endregion
|
|
73
|
-
//#region src/
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
65
|
+
//#region src/nodes/base.ts
|
|
66
|
+
function withNodeConstraints(ctx, constraints) {
|
|
67
|
+
if (constraints === ctx.constraints) return ctx;
|
|
68
|
+
const next = shallow(ctx);
|
|
69
|
+
next.constraints = constraints;
|
|
70
|
+
return next;
|
|
77
71
|
}
|
|
78
|
-
function
|
|
79
|
-
|
|
72
|
+
function measureNodeMinContent(ctx, node, constraints = ctx.constraints) {
|
|
73
|
+
const nextCtx = withNodeConstraints(ctx, constraints);
|
|
74
|
+
if (node.measureMinContent != null) return node.measureMinContent(nextCtx);
|
|
75
|
+
return node.measure(nextCtx);
|
|
80
76
|
}
|
|
81
|
-
function getNodeParent(node) {
|
|
82
|
-
return registry.get(node);
|
|
83
|
-
}
|
|
84
|
-
//#endregion
|
|
85
|
-
//#region src/nodes.ts
|
|
86
77
|
var Group = class {
|
|
78
|
+
#children;
|
|
87
79
|
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;
|
|
80
|
+
this.#children = [...children];
|
|
81
|
+
replaceNodesParent([], this.#children, this);
|
|
164
82
|
}
|
|
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
|
-
};
|
|
192
|
-
}
|
|
193
|
-
draw(ctx, x, y) {
|
|
194
|
-
let result = false;
|
|
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;
|
|
83
|
+
get children() {
|
|
84
|
+
return this.#children;
|
|
224
85
|
}
|
|
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;
|
|
86
|
+
replaceChildren(nextChildren) {
|
|
87
|
+
const nextSnapshot = [...nextChildren];
|
|
88
|
+
replaceNodesParent(this.#children, nextSnapshot, this);
|
|
89
|
+
this.#children = nextSnapshot;
|
|
262
90
|
}
|
|
263
91
|
};
|
|
264
92
|
var Wrapper = class {
|
|
265
93
|
#inner;
|
|
266
94
|
constructor(inner) {
|
|
267
95
|
this.#inner = inner;
|
|
268
|
-
|
|
96
|
+
attachNodeToParent(this.#inner, this);
|
|
269
97
|
}
|
|
270
98
|
get inner() {
|
|
271
99
|
return this.#inner;
|
|
272
100
|
}
|
|
273
101
|
set inner(newNode) {
|
|
274
102
|
if (newNode === this.#inner) return;
|
|
275
|
-
|
|
103
|
+
replaceNodeParent(this.#inner, newNode, this);
|
|
276
104
|
this.#inner = newNode;
|
|
277
|
-
registerNodeParent(newNode, this);
|
|
278
|
-
}
|
|
279
|
-
get flex() {
|
|
280
|
-
return this.inner.flex;
|
|
281
105
|
}
|
|
282
106
|
measure(ctx) {
|
|
283
107
|
return this.inner.measure(ctx);
|
|
284
108
|
}
|
|
109
|
+
measureMinContent(ctx) {
|
|
110
|
+
return measureNodeMinContent(ctx, this.inner);
|
|
111
|
+
}
|
|
285
112
|
draw(ctx, x, y) {
|
|
286
113
|
return this.inner.draw(ctx, x, y);
|
|
287
114
|
}
|
|
@@ -289,6 +116,117 @@ var Wrapper = class {
|
|
|
289
116
|
return this.inner.hittest(ctx, test);
|
|
290
117
|
}
|
|
291
118
|
};
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region src/layout.ts
|
|
121
|
+
/**
|
|
122
|
+
* 创建 LayoutRect 的辅助函数
|
|
123
|
+
*/
|
|
124
|
+
function createRect(x, y, width, height) {
|
|
125
|
+
return {
|
|
126
|
+
x,
|
|
127
|
+
y,
|
|
128
|
+
width,
|
|
129
|
+
height
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 合并多个 rect 得到包含所有 rect 的最小外接矩形
|
|
134
|
+
*/
|
|
135
|
+
function mergeRects(rects) {
|
|
136
|
+
if (rects.length === 0) return createRect(0, 0, 0, 0);
|
|
137
|
+
let minX = Infinity;
|
|
138
|
+
let minY = Infinity;
|
|
139
|
+
let maxX = -Infinity;
|
|
140
|
+
let maxY = -Infinity;
|
|
141
|
+
for (const rect of rects) {
|
|
142
|
+
minX = Math.min(minX, rect.x);
|
|
143
|
+
minY = Math.min(minY, rect.y);
|
|
144
|
+
maxX = Math.max(maxX, rect.x + rect.width);
|
|
145
|
+
maxY = Math.max(maxY, rect.y + rect.height);
|
|
146
|
+
}
|
|
147
|
+
return createRect(minX, minY, maxX - minX, maxY - minY);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 从子节点布局结果计算容器的 contentBox
|
|
151
|
+
*/
|
|
152
|
+
function computeContentBox(children) {
|
|
153
|
+
return mergeRects(children.map((child) => child.contentBox));
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* 检查点是否在 rect 内
|
|
157
|
+
*/
|
|
158
|
+
function pointInRect(x, y, rect) {
|
|
159
|
+
return x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 读取单子节点布局结果中的唯一 child。
|
|
163
|
+
*/
|
|
164
|
+
function getSingleChildLayout(layout) {
|
|
165
|
+
return layout.children[0];
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* 在布局结果中按指定盒模型查找命中的 child,并返回局部坐标。
|
|
169
|
+
*/
|
|
170
|
+
function findChildAtPoint(children, x, y, box = "contentBox") {
|
|
171
|
+
for (let i = children.length - 1; i >= 0; i -= 1) {
|
|
172
|
+
const child = children[i];
|
|
173
|
+
const target = box === "rect" ? child.rect : child.contentBox;
|
|
174
|
+
if (!pointInRect(x, y, target)) continue;
|
|
175
|
+
return {
|
|
176
|
+
child,
|
|
177
|
+
localX: x - target.x,
|
|
178
|
+
localY: y - target.y
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/nodes/shared.ts
|
|
184
|
+
function withConstraints(ctx, constraints) {
|
|
185
|
+
const next = shallow(ctx);
|
|
186
|
+
next.constraints = constraints;
|
|
187
|
+
return next;
|
|
188
|
+
}
|
|
189
|
+
function getLayoutContext(ctx) {
|
|
190
|
+
return ctx;
|
|
191
|
+
}
|
|
192
|
+
function readLayoutResult(node, ctx) {
|
|
193
|
+
return getLayoutContext(ctx).getLayoutResult(node, ctx.constraints);
|
|
194
|
+
}
|
|
195
|
+
function writeLayoutResult(node, ctx, result) {
|
|
196
|
+
getLayoutContext(ctx).setLayoutResult(node, result, ctx.constraints);
|
|
197
|
+
}
|
|
198
|
+
function ensureLayoutResult(node, ctx) {
|
|
199
|
+
return readLayoutResult(node, ctx);
|
|
200
|
+
}
|
|
201
|
+
function drawLayoutChildren(node, ctx, x, y) {
|
|
202
|
+
const layoutResult = ensureLayoutResult(node, ctx);
|
|
203
|
+
if (!layoutResult) return false;
|
|
204
|
+
let result = false;
|
|
205
|
+
for (const childResult of layoutResult.children) result ||= childResult.node.draw(withConstraints(ctx, childResult.constraints), x + childResult.contentBox.x, y + childResult.contentBox.y);
|
|
206
|
+
return result;
|
|
207
|
+
}
|
|
208
|
+
function hittestLayoutChildren(node, ctx, test, box = "contentBox") {
|
|
209
|
+
const layoutResult = ensureLayoutResult(node, ctx);
|
|
210
|
+
if (!layoutResult) return false;
|
|
211
|
+
const hit = findChildAtPoint(layoutResult.children, test.x, test.y, box);
|
|
212
|
+
if (!hit) return false;
|
|
213
|
+
return hit.child.node.hittest(withConstraints(ctx, hit.child.constraints), shallowMerge(test, {
|
|
214
|
+
x: hit.localX,
|
|
215
|
+
y: hit.localY
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/nodes/box.ts
|
|
220
|
+
function clampToConstraints$1(value, min, max) {
|
|
221
|
+
let result = value;
|
|
222
|
+
if (min != null) result = Math.max(result, min);
|
|
223
|
+
if (max != null) result = Math.min(result, max);
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
function shrinkConstraint(value, padding) {
|
|
227
|
+
if (value == null) return;
|
|
228
|
+
return Math.max(0, value - padding);
|
|
229
|
+
}
|
|
292
230
|
var PaddingBox = class extends Wrapper {
|
|
293
231
|
constructor(inner, padding = {}) {
|
|
294
232
|
super(inner);
|
|
@@ -307,80 +245,831 @@ var PaddingBox = class extends Wrapper {
|
|
|
307
245
|
return this.padding.right ?? 0;
|
|
308
246
|
}
|
|
309
247
|
measure(ctx) {
|
|
310
|
-
|
|
311
|
-
const
|
|
248
|
+
const paddingLeft = this.#left;
|
|
249
|
+
const paddingRight = this.#right;
|
|
250
|
+
const paddingTop = this.#top;
|
|
251
|
+
const paddingBottom = this.#bottom;
|
|
252
|
+
const horizontalPadding = paddingLeft + paddingRight;
|
|
253
|
+
const verticalPadding = paddingTop + paddingBottom;
|
|
254
|
+
const childConstraints = ctx.constraints ? {
|
|
255
|
+
...ctx.constraints,
|
|
256
|
+
minWidth: shrinkConstraint(ctx.constraints.minWidth, horizontalPadding),
|
|
257
|
+
maxWidth: shrinkConstraint(ctx.constraints.maxWidth, horizontalPadding),
|
|
258
|
+
minHeight: shrinkConstraint(ctx.constraints.minHeight, verticalPadding),
|
|
259
|
+
maxHeight: shrinkConstraint(ctx.constraints.maxHeight, verticalPadding)
|
|
260
|
+
} : void 0;
|
|
261
|
+
const { width, height } = ctx.measureNode(this.inner, childConstraints);
|
|
262
|
+
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));
|
|
263
|
+
const childRect = createRect(paddingLeft, paddingTop, width, height);
|
|
264
|
+
writeLayoutResult(this, ctx, {
|
|
265
|
+
containerBox,
|
|
266
|
+
contentBox: childRect,
|
|
267
|
+
children: [{
|
|
268
|
+
node: this.inner,
|
|
269
|
+
rect: childRect,
|
|
270
|
+
contentBox: childRect,
|
|
271
|
+
constraints: childConstraints
|
|
272
|
+
}],
|
|
273
|
+
constraints: ctx.constraints
|
|
274
|
+
});
|
|
275
|
+
return {
|
|
276
|
+
width: containerBox.width,
|
|
277
|
+
height: containerBox.height
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
measureMinContent(ctx) {
|
|
281
|
+
const paddingLeft = this.#left;
|
|
282
|
+
const paddingRight = this.#right;
|
|
283
|
+
const paddingTop = this.#top;
|
|
284
|
+
const paddingBottom = this.#bottom;
|
|
285
|
+
const horizontalPadding = paddingLeft + paddingRight;
|
|
286
|
+
const verticalPadding = paddingTop + paddingBottom;
|
|
287
|
+
const childConstraints = ctx.constraints ? {
|
|
288
|
+
...ctx.constraints,
|
|
289
|
+
minWidth: shrinkConstraint(ctx.constraints.minWidth, horizontalPadding),
|
|
290
|
+
maxWidth: shrinkConstraint(ctx.constraints.maxWidth, horizontalPadding),
|
|
291
|
+
minHeight: shrinkConstraint(ctx.constraints.minHeight, verticalPadding),
|
|
292
|
+
maxHeight: shrinkConstraint(ctx.constraints.maxHeight, verticalPadding)
|
|
293
|
+
} : void 0;
|
|
294
|
+
const { width, height } = measureNodeMinContent(ctx, this.inner, childConstraints);
|
|
312
295
|
return {
|
|
313
|
-
width: width +
|
|
314
|
-
height: height +
|
|
296
|
+
width: width + horizontalPadding,
|
|
297
|
+
height: height + verticalPadding
|
|
315
298
|
};
|
|
316
299
|
}
|
|
317
300
|
draw(ctx, x, y) {
|
|
318
|
-
|
|
319
|
-
return this.inner.draw(ctx, x + this.#left, y + this.#top);
|
|
301
|
+
const layoutResult = readLayoutResult(this, ctx);
|
|
302
|
+
if (!layoutResult) return this.inner.draw(ctx, x + this.#left, y + this.#top);
|
|
303
|
+
const childResult = getSingleChildLayout(layoutResult);
|
|
304
|
+
if (!childResult) return false;
|
|
305
|
+
return childResult.node.draw(withConstraints(ctx, childResult.constraints), x + childResult.rect.x, y + childResult.rect.y);
|
|
320
306
|
}
|
|
321
307
|
hittest(ctx, test) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
308
|
+
const layoutResult = readLayoutResult(this, ctx);
|
|
309
|
+
if (!layoutResult) return false;
|
|
310
|
+
const hit = findChildAtPoint(layoutResult.children, test.x, test.y, "rect");
|
|
311
|
+
if (!hit) return false;
|
|
312
|
+
return hit.child.node.hittest(withConstraints(ctx, hit.child.constraints), shallowMerge(test, {
|
|
313
|
+
x: hit.localX,
|
|
314
|
+
y: hit.localY
|
|
327
315
|
}));
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
var Fixed = class {
|
|
319
|
+
constructor(width, height) {
|
|
320
|
+
this.width = width;
|
|
321
|
+
this.height = height;
|
|
322
|
+
}
|
|
323
|
+
measure(_ctx) {
|
|
324
|
+
return {
|
|
325
|
+
width: this.width,
|
|
326
|
+
height: this.height
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
measureMinContent(_ctx) {
|
|
330
|
+
return {
|
|
331
|
+
width: this.width,
|
|
332
|
+
height: this.height
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
draw(_ctx, _x, _y) {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
hittest(_ctx, _test) {
|
|
328
339
|
return false;
|
|
329
340
|
}
|
|
330
341
|
};
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
342
|
+
//#endregion
|
|
343
|
+
//#region src/nodes/flex.ts
|
|
344
|
+
function getMainSize(axis, box) {
|
|
345
|
+
return axis === "row" ? box.width : box.height;
|
|
346
|
+
}
|
|
347
|
+
function getCrossSize(axis, box) {
|
|
348
|
+
return axis === "row" ? box.height : box.width;
|
|
349
|
+
}
|
|
350
|
+
function getMinMain(axis, constraints) {
|
|
351
|
+
return axis === "row" ? constraints?.minWidth : constraints?.minHeight;
|
|
352
|
+
}
|
|
353
|
+
function getMaxMain(axis, constraints) {
|
|
354
|
+
return axis === "row" ? constraints?.maxWidth : constraints?.maxHeight;
|
|
355
|
+
}
|
|
356
|
+
function getMinCross(axis, constraints) {
|
|
357
|
+
return axis === "row" ? constraints?.minHeight : constraints?.minWidth;
|
|
358
|
+
}
|
|
359
|
+
function getMaxCross(axis, constraints) {
|
|
360
|
+
return axis === "row" ? constraints?.maxHeight : constraints?.maxWidth;
|
|
361
|
+
}
|
|
362
|
+
function createAxisConstraints(axis, constraints, main, cross = {}) {
|
|
363
|
+
if (constraints == null && main.min == null && main.max == null && cross.min == null && cross.max == null) return;
|
|
364
|
+
const next = { ...constraints };
|
|
365
|
+
if (axis === "row") {
|
|
366
|
+
next.minWidth = main.min;
|
|
367
|
+
next.maxWidth = main.max;
|
|
368
|
+
next.minHeight = cross.min;
|
|
369
|
+
next.maxHeight = cross.max;
|
|
370
|
+
} else {
|
|
371
|
+
next.minHeight = main.min;
|
|
372
|
+
next.maxHeight = main.max;
|
|
373
|
+
next.minWidth = cross.min;
|
|
374
|
+
next.maxWidth = cross.max;
|
|
375
|
+
}
|
|
376
|
+
return next;
|
|
377
|
+
}
|
|
378
|
+
function clampToConstraints(value, min, max) {
|
|
379
|
+
let result = value;
|
|
380
|
+
if (min != null) result = Math.max(result, min);
|
|
381
|
+
if (max != null) result = Math.min(result, max);
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
function constraintsEqual(left, right) {
|
|
385
|
+
if (left === right) return true;
|
|
386
|
+
if (left == null || right == null) return left == null && right == null;
|
|
387
|
+
return left.minWidth === right.minWidth && left.maxWidth === right.maxWidth && left.minHeight === right.minHeight && left.maxHeight === right.maxHeight;
|
|
388
|
+
}
|
|
389
|
+
function getCrossAlignment(alignSelf, alignItems) {
|
|
390
|
+
if (alignSelf == null || alignSelf === "auto") return alignItems;
|
|
391
|
+
return alignSelf;
|
|
392
|
+
}
|
|
393
|
+
function getJustifySpacing(justifyContent, freeSpace, itemCount, gap) {
|
|
394
|
+
switch (justifyContent) {
|
|
395
|
+
case "center": return {
|
|
396
|
+
leading: freeSpace / 2,
|
|
397
|
+
between: gap
|
|
398
|
+
};
|
|
399
|
+
case "end": return {
|
|
400
|
+
leading: freeSpace,
|
|
401
|
+
between: gap
|
|
402
|
+
};
|
|
403
|
+
case "space-between": return {
|
|
404
|
+
leading: 0,
|
|
405
|
+
between: itemCount > 1 ? gap + freeSpace / (itemCount - 1) : gap
|
|
406
|
+
};
|
|
407
|
+
case "space-around": return {
|
|
408
|
+
leading: itemCount > 0 ? freeSpace / itemCount / 2 : 0,
|
|
409
|
+
between: itemCount > 0 ? gap + freeSpace / itemCount : gap
|
|
410
|
+
};
|
|
411
|
+
case "space-evenly": return {
|
|
412
|
+
leading: itemCount > 0 ? freeSpace / (itemCount + 1) : 0,
|
|
413
|
+
between: itemCount > 0 ? gap + freeSpace / (itemCount + 1) : gap
|
|
414
|
+
};
|
|
415
|
+
default: return {
|
|
416
|
+
leading: 0,
|
|
417
|
+
between: gap
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function getCrossOffset(align, frameCross, contentCross) {
|
|
422
|
+
switch (align) {
|
|
423
|
+
case "center": return (frameCross - contentCross) / 2;
|
|
424
|
+
case "end": return frameCross - contentCross;
|
|
425
|
+
default: return 0;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function createRectFromAxis(axis, main, cross, mainSize, crossSize) {
|
|
429
|
+
return axis === "row" ? createRect(main, cross, mainSize, crossSize) : createRect(cross, main, crossSize, mainSize);
|
|
430
|
+
}
|
|
431
|
+
const SHRINK_EPSILON = 1e-6;
|
|
432
|
+
function readFlexItemOptions(child) {
|
|
433
|
+
if (child instanceof FlexItem) return child.item;
|
|
434
|
+
return {};
|
|
435
|
+
}
|
|
436
|
+
function computeFlexLayout(children, options, constraints, measureChild, measureChildMinContent) {
|
|
437
|
+
const axis = options.direction ?? "row";
|
|
438
|
+
const gap = options.gap ?? 0;
|
|
439
|
+
const justifyContent = options.justifyContent ?? "start";
|
|
440
|
+
const alignItems = options.alignItems ?? "start";
|
|
441
|
+
const reverse = options.reverse ?? false;
|
|
442
|
+
const mainAxisSize = options.mainAxisSize ?? "fill";
|
|
443
|
+
const orderedChildren = reverse ? [...children].reverse() : children;
|
|
444
|
+
const maxMain = getMaxMain(axis, constraints);
|
|
445
|
+
const minMain = getMinMain(axis, constraints);
|
|
446
|
+
const maxCross = getMaxCross(axis, constraints);
|
|
447
|
+
const minCross = getMinCross(axis, constraints);
|
|
448
|
+
const gapTotal = orderedChildren.length > 1 ? gap * (orderedChildren.length - 1) : 0;
|
|
449
|
+
const finiteMain = maxMain != null;
|
|
450
|
+
const finiteCross = maxCross != null;
|
|
451
|
+
const availableMain = finiteMain ? Math.max(0, maxMain - gapTotal) : void 0;
|
|
452
|
+
let totalGrow = 0;
|
|
453
|
+
let totalBasis = 0;
|
|
454
|
+
let nonGrowBasis = 0;
|
|
455
|
+
const measurements = /* @__PURE__ */ new Map();
|
|
456
|
+
const basisConstraints = createAxisConstraints(axis, constraints, {
|
|
457
|
+
min: void 0,
|
|
458
|
+
max: void 0
|
|
459
|
+
}, {
|
|
460
|
+
min: void 0,
|
|
461
|
+
max: maxCross
|
|
462
|
+
});
|
|
463
|
+
for (const child of orderedChildren) {
|
|
464
|
+
const item = readFlexItemOptions(child);
|
|
465
|
+
const grow = item.grow ?? 0;
|
|
466
|
+
const shrink = item.shrink ?? 0;
|
|
467
|
+
totalGrow += grow;
|
|
468
|
+
const effectiveAlign = getCrossAlignment(item.alignSelf, alignItems);
|
|
469
|
+
const stretch = effectiveAlign === "stretch";
|
|
470
|
+
const basisMeasured = measureChild(child, basisConstraints);
|
|
471
|
+
const basis = getMainSize(axis, basisMeasured);
|
|
472
|
+
totalBasis += basis;
|
|
473
|
+
if (grow <= 0) nonGrowBasis += basis;
|
|
474
|
+
measurements.set(child, {
|
|
475
|
+
child,
|
|
476
|
+
item,
|
|
477
|
+
basisMeasured,
|
|
478
|
+
measured: basisMeasured,
|
|
479
|
+
basisConstraints,
|
|
480
|
+
initialConstraints: basisConstraints,
|
|
481
|
+
finalConstraints: basisConstraints,
|
|
482
|
+
allocatedMain: void 0,
|
|
483
|
+
grow,
|
|
484
|
+
shrink,
|
|
485
|
+
effectiveAlign,
|
|
486
|
+
stretch,
|
|
487
|
+
basis,
|
|
488
|
+
minContentMain: basis,
|
|
489
|
+
finalMain: basis,
|
|
490
|
+
frozen: false,
|
|
491
|
+
frameMain: basis,
|
|
492
|
+
frameCross: getCrossSize(axis, basisMeasured)
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
if (finiteMain && availableMain != null && totalBasis - availableMain > SHRINK_EPSILON) {
|
|
496
|
+
const totalDeficit = totalBasis - availableMain;
|
|
497
|
+
let remainingDeficit = totalDeficit;
|
|
498
|
+
for (const child of orderedChildren) {
|
|
499
|
+
const measurement = measurements.get(child);
|
|
500
|
+
const minContentMeasured = measureChildMinContent(child, measurement.basisConstraints);
|
|
501
|
+
measurement.minContentMain = Math.min(measurement.basis, getMainSize(axis, minContentMeasured));
|
|
502
|
+
measurement.finalMain = measurement.basis;
|
|
503
|
+
measurement.frozen = measurement.shrink <= 0 || measurement.basis - measurement.minContentMain <= SHRINK_EPSILON;
|
|
504
|
+
}
|
|
505
|
+
while (remainingDeficit > SHRINK_EPSILON) {
|
|
506
|
+
const active = orderedChildren.map((child) => measurements.get(child)).filter((measurement) => !measurement.frozen && measurement.shrink > 0);
|
|
507
|
+
const totalScaled = active.reduce((sum, measurement) => sum + measurement.shrink * measurement.basis, 0);
|
|
508
|
+
if (active.length === 0 || totalScaled <= SHRINK_EPSILON) break;
|
|
509
|
+
let frozeAny = false;
|
|
510
|
+
for (const measurement of active) {
|
|
511
|
+
const tentative = measurement.basis - remainingDeficit * (measurement.shrink * measurement.basis / totalScaled);
|
|
512
|
+
if (tentative <= measurement.minContentMain + SHRINK_EPSILON) {
|
|
513
|
+
measurement.finalMain = measurement.minContentMain;
|
|
514
|
+
measurement.frozen = true;
|
|
515
|
+
frozeAny = true;
|
|
516
|
+
} else measurement.finalMain = tentative;
|
|
517
|
+
}
|
|
518
|
+
if (!frozeAny) {
|
|
519
|
+
remainingDeficit = 0;
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
let absorbedDeficit = 0;
|
|
523
|
+
for (const child of orderedChildren) {
|
|
524
|
+
const measurement = measurements.get(child);
|
|
525
|
+
if (measurement.frozen) absorbedDeficit += Math.max(0, measurement.basis - measurement.finalMain);
|
|
526
|
+
}
|
|
527
|
+
remainingDeficit = Math.max(0, totalDeficit - absorbedDeficit);
|
|
528
|
+
}
|
|
529
|
+
for (const child of orderedChildren) {
|
|
530
|
+
const measurement = measurements.get(child);
|
|
531
|
+
measurement.measured = measurement.basisMeasured;
|
|
532
|
+
measurement.initialConstraints = measurement.basisConstraints;
|
|
533
|
+
measurement.finalConstraints = createAxisConstraints(axis, constraints, {
|
|
534
|
+
min: void 0,
|
|
535
|
+
max: measurement.finalMain
|
|
536
|
+
}, {
|
|
537
|
+
min: void 0,
|
|
538
|
+
max: maxCross
|
|
539
|
+
});
|
|
540
|
+
measurement.allocatedMain = void 0;
|
|
541
|
+
measurement.frameMain = measurement.finalMain;
|
|
542
|
+
measurement.frameCross = getCrossSize(axis, measurement.measured);
|
|
543
|
+
}
|
|
544
|
+
} else {
|
|
545
|
+
const remainingMain = finiteMain && availableMain != null ? Math.max(0, availableMain - nonGrowBasis) : void 0;
|
|
546
|
+
for (const child of orderedChildren) {
|
|
547
|
+
const measurement = measurements.get(child);
|
|
548
|
+
if (!(measurement.grow > 0 && finiteMain && remainingMain != null && totalGrow > 0)) {
|
|
549
|
+
measurement.measured = measurement.basisMeasured;
|
|
550
|
+
measurement.initialConstraints = measurement.basisConstraints;
|
|
551
|
+
measurement.finalConstraints = finiteMain ? createAxisConstraints(axis, constraints, {
|
|
552
|
+
min: void 0,
|
|
553
|
+
max: measurement.finalMain
|
|
554
|
+
}, {
|
|
555
|
+
min: void 0,
|
|
556
|
+
max: maxCross
|
|
557
|
+
}) : measurement.basisConstraints;
|
|
558
|
+
measurement.allocatedMain = void 0;
|
|
559
|
+
measurement.finalMain = measurement.basis;
|
|
560
|
+
measurement.frameMain = measurement.basis;
|
|
561
|
+
measurement.frameCross = getCrossSize(axis, measurement.measured);
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
const allocatedMain = remainingMain * measurement.grow / totalGrow;
|
|
565
|
+
const childConstraints = createAxisConstraints(axis, constraints, { max: allocatedMain }, {
|
|
566
|
+
min: void 0,
|
|
567
|
+
max: maxCross
|
|
568
|
+
});
|
|
569
|
+
const measured = measureChild(child, childConstraints);
|
|
570
|
+
measurement.measured = measured;
|
|
571
|
+
measurement.initialConstraints = childConstraints;
|
|
572
|
+
measurement.finalConstraints = childConstraints;
|
|
573
|
+
measurement.allocatedMain = allocatedMain;
|
|
574
|
+
measurement.finalMain = allocatedMain;
|
|
575
|
+
measurement.frameMain = allocatedMain;
|
|
576
|
+
measurement.frameCross = getCrossSize(axis, measured);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
for (const child of orderedChildren) {
|
|
580
|
+
const measurement = measurements.get(child);
|
|
581
|
+
if (!constraintsEqual(measurement.initialConstraints, measurement.finalConstraints)) measurement.measured = measureChild(child, measurement.finalConstraints);
|
|
582
|
+
measurement.frameMain = measurement.finalMain;
|
|
583
|
+
measurement.frameCross = getCrossSize(axis, measurement.measured);
|
|
584
|
+
}
|
|
585
|
+
let contentMain = gapTotal;
|
|
586
|
+
let contentCross = 0;
|
|
587
|
+
for (const child of orderedChildren) {
|
|
588
|
+
const measurement = measurements.get(child);
|
|
589
|
+
contentMain += measurement.frameMain;
|
|
590
|
+
contentCross = Math.max(contentCross, measurement.frameCross);
|
|
591
|
+
}
|
|
592
|
+
finiteMain && mainAxisSize === "fill" || clampToConstraints(contentMain, minMain, maxMain);
|
|
593
|
+
const containerCross = clampToConstraints(contentCross, minCross, maxCross);
|
|
594
|
+
if (finiteCross) {
|
|
595
|
+
for (const child of orderedChildren) {
|
|
596
|
+
const measurement = measurements.get(child);
|
|
597
|
+
if (!measurement.stretch) continue;
|
|
598
|
+
const finalConstraints = createAxisConstraints(axis, measurement.finalConstraints, {
|
|
599
|
+
min: getMinMain(axis, measurement.finalConstraints),
|
|
600
|
+
max: getMaxMain(axis, measurement.finalConstraints)
|
|
601
|
+
}, {
|
|
602
|
+
min: containerCross,
|
|
603
|
+
max: containerCross
|
|
604
|
+
});
|
|
605
|
+
const remeasured = measureChild(child, finalConstraints);
|
|
606
|
+
measurement.measured = remeasured;
|
|
607
|
+
measurement.finalConstraints = finalConstraints;
|
|
608
|
+
measurement.frameCross = containerCross;
|
|
609
|
+
measurement.frameMain = measurement.allocatedMain ?? getMainSize(axis, remeasured);
|
|
610
|
+
}
|
|
611
|
+
contentMain = gapTotal;
|
|
612
|
+
contentCross = 0;
|
|
613
|
+
for (const child of orderedChildren) {
|
|
614
|
+
const measurement = measurements.get(child);
|
|
615
|
+
contentMain += measurement.frameMain;
|
|
616
|
+
contentCross = Math.max(contentCross, getCrossSize(axis, measurement.measured));
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
const finalContainerMain = finiteMain && mainAxisSize === "fill" ? Math.max(maxMain, contentMain) : clampToConstraints(contentMain, minMain, maxMain);
|
|
620
|
+
const spacing = getJustifySpacing(justifyContent, Math.max(0, finalContainerMain - contentMain), orderedChildren.length, gap);
|
|
621
|
+
const childResults = [];
|
|
622
|
+
let cursor = spacing.leading;
|
|
623
|
+
for (const child of orderedChildren) {
|
|
624
|
+
const measurement = measurements.get(child);
|
|
625
|
+
const frameCross = measurement.stretch && finiteCross ? containerCross : measurement.frameCross;
|
|
626
|
+
const contentMainSize = getMainSize(axis, measurement.measured);
|
|
627
|
+
const contentCrossSize = getCrossSize(axis, measurement.measured);
|
|
628
|
+
const rectCross = measurement.stretch ? 0 : getCrossOffset(measurement.effectiveAlign, containerCross, frameCross);
|
|
629
|
+
const contentCrossOffset = rectCross + getCrossOffset(measurement.effectiveAlign, frameCross, contentCrossSize);
|
|
630
|
+
const rect = createRectFromAxis(axis, cursor, rectCross, measurement.frameMain, frameCross);
|
|
631
|
+
const contentBox = createRectFromAxis(axis, cursor, contentCrossOffset, contentMainSize, contentCrossSize);
|
|
632
|
+
childResults.push({
|
|
633
|
+
node: child,
|
|
634
|
+
rect,
|
|
635
|
+
contentBox,
|
|
636
|
+
constraints: measurement.finalConstraints
|
|
637
|
+
});
|
|
638
|
+
cursor += measurement.frameMain + spacing.between;
|
|
639
|
+
}
|
|
640
|
+
const containerBox = axis === "row" ? createRect(0, 0, finalContainerMain, containerCross) : createRect(0, 0, containerCross, finalContainerMain);
|
|
641
|
+
const finalContentBox = childResults.length > 0 ? computeContentBox(childResults) : createRect(0, 0, 0, 0);
|
|
642
|
+
return {
|
|
643
|
+
box: {
|
|
644
|
+
width: containerBox.width,
|
|
645
|
+
height: containerBox.height
|
|
646
|
+
},
|
|
647
|
+
layout: {
|
|
648
|
+
containerBox,
|
|
649
|
+
contentBox: finalContentBox,
|
|
650
|
+
children: childResults,
|
|
651
|
+
constraints
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
var FlexItem = class extends Wrapper {
|
|
656
|
+
constructor(inner, item = {}) {
|
|
334
657
|
super(inner);
|
|
658
|
+
this.item = item;
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
var Flex = class extends Group {
|
|
662
|
+
constructor(children, options = {}) {
|
|
663
|
+
super(children);
|
|
335
664
|
this.options = options;
|
|
336
665
|
}
|
|
337
666
|
measure(ctx) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
667
|
+
const result = computeFlexLayout(this.children, this.options, ctx.constraints, (node, constraints) => ctx.measureNode(node, constraints), (node, constraints) => measureNodeMinContent(ctx, node, constraints));
|
|
668
|
+
writeLayoutResult(this, ctx, result.layout);
|
|
669
|
+
return result.box;
|
|
670
|
+
}
|
|
671
|
+
measureMinContent(ctx) {
|
|
672
|
+
const axis = this.options.direction ?? "row";
|
|
673
|
+
const gap = this.options.gap ?? 0;
|
|
674
|
+
const orderedChildren = this.options.reverse ? [...this.children].reverse() : this.children;
|
|
675
|
+
const gapTotal = orderedChildren.length > 1 ? gap * (orderedChildren.length - 1) : 0;
|
|
676
|
+
const childConstraints = createAxisConstraints(axis, ctx.constraints, {
|
|
677
|
+
min: void 0,
|
|
678
|
+
max: void 0
|
|
679
|
+
}, {
|
|
680
|
+
min: void 0,
|
|
681
|
+
max: getMaxCross(axis, ctx.constraints)
|
|
682
|
+
});
|
|
683
|
+
let width = axis === "row" ? gapTotal : 0;
|
|
684
|
+
let height = axis === "column" ? gapTotal : 0;
|
|
685
|
+
for (const child of orderedChildren) {
|
|
686
|
+
const measured = measureNodeMinContent(ctx, child, childConstraints);
|
|
687
|
+
if (axis === "row") {
|
|
688
|
+
width += measured.width;
|
|
689
|
+
height = Math.max(height, measured.height);
|
|
690
|
+
} else {
|
|
691
|
+
width = Math.max(width, measured.width);
|
|
692
|
+
height += measured.height;
|
|
693
|
+
}
|
|
348
694
|
}
|
|
349
695
|
return {
|
|
350
|
-
width
|
|
696
|
+
width,
|
|
351
697
|
height
|
|
352
698
|
};
|
|
353
699
|
}
|
|
354
700
|
draw(ctx, x, y) {
|
|
355
|
-
ctx
|
|
356
|
-
return this.inner.draw(ctx, x + this.#shift, y);
|
|
701
|
+
return drawLayoutChildren(this, ctx, x, y);
|
|
357
702
|
}
|
|
358
703
|
hittest(ctx, test) {
|
|
359
|
-
ctx
|
|
360
|
-
const { width } = shallow(ctx).measureNode(this.inner);
|
|
361
|
-
if (0 <= test.x - this.#shift && test.x - this.#shift < width) return this.inner.hittest(shallow(ctx), shallowMerge(test, { x: test.x - this.#shift }));
|
|
362
|
-
return false;
|
|
704
|
+
return hittestLayoutChildren(this, ctx, test, "contentBox");
|
|
363
705
|
}
|
|
364
706
|
};
|
|
707
|
+
//#endregion
|
|
708
|
+
//#region src/nodes/place.ts
|
|
709
|
+
function resolveHorizontalOffset(align, availableWidth, childWidth) {
|
|
710
|
+
switch (align) {
|
|
711
|
+
case "center": return (availableWidth - childWidth) / 2;
|
|
712
|
+
case "end": return availableWidth - childWidth;
|
|
713
|
+
case "start": return 0;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
var Place = class extends Wrapper {
|
|
717
|
+
constructor(inner, options = {}) {
|
|
718
|
+
super(inner);
|
|
719
|
+
this.options = options;
|
|
720
|
+
}
|
|
721
|
+
measure(ctx) {
|
|
722
|
+
const availableWidth = ctx.constraints?.maxWidth;
|
|
723
|
+
const expand = this.options.expand ?? true;
|
|
724
|
+
const childConstraints = ctx.constraints ? { ...ctx.constraints } : void 0;
|
|
725
|
+
const childBox = ctx.measureNode(this.inner, childConstraints);
|
|
726
|
+
let width = expand && availableWidth != null ? availableWidth : childBox.width;
|
|
727
|
+
if (ctx.constraints?.minWidth != null) width = Math.max(width, ctx.constraints.minWidth);
|
|
728
|
+
if (ctx.constraints?.maxWidth != null) width = Math.min(width, ctx.constraints.maxWidth);
|
|
729
|
+
const childRect = createRect(resolveHorizontalOffset(this.options.align ?? "start", width, childBox.width), 0, childBox.width, childBox.height);
|
|
730
|
+
writeLayoutResult(this, ctx, {
|
|
731
|
+
containerBox: createRect(0, 0, width, childBox.height),
|
|
732
|
+
contentBox: childRect,
|
|
733
|
+
children: [{
|
|
734
|
+
node: this.inner,
|
|
735
|
+
rect: childRect,
|
|
736
|
+
contentBox: createRect(0, 0, childBox.width, childBox.height),
|
|
737
|
+
constraints: childConstraints
|
|
738
|
+
}],
|
|
739
|
+
constraints: ctx.constraints
|
|
740
|
+
});
|
|
741
|
+
return {
|
|
742
|
+
width,
|
|
743
|
+
height: childBox.height
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
measureMinContent(ctx) {
|
|
747
|
+
return measureNodeMinContent(ctx, this.inner);
|
|
748
|
+
}
|
|
749
|
+
draw(ctx, x, y) {
|
|
750
|
+
const layoutResult = readLayoutResult(this, ctx);
|
|
751
|
+
if (!layoutResult) return this.inner.draw(ctx, x, y);
|
|
752
|
+
const childResult = getSingleChildLayout(layoutResult);
|
|
753
|
+
if (!childResult) return false;
|
|
754
|
+
const childCtx = withConstraints(ctx, childResult.constraints);
|
|
755
|
+
return childResult.node.draw(childCtx, x + childResult.rect.x, y + childResult.rect.y);
|
|
756
|
+
}
|
|
757
|
+
hittest(ctx, test) {
|
|
758
|
+
const layoutResult = readLayoutResult(this, ctx);
|
|
759
|
+
if (!layoutResult) return false;
|
|
760
|
+
const hit = findChildAtPoint(layoutResult.children, test.x, test.y, "rect");
|
|
761
|
+
if (!hit) return false;
|
|
762
|
+
return hit.child.node.hittest(withConstraints(ctx, hit.child.constraints), shallowMerge(test, {
|
|
763
|
+
x: hit.localX,
|
|
764
|
+
y: hit.localY
|
|
765
|
+
}));
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
//#endregion
|
|
769
|
+
//#region src/text.ts
|
|
770
|
+
const FONT_SHIFT_PROBE = "M";
|
|
771
|
+
const PREPARED_SEGMENT_CACHE_CAPACITY = 512;
|
|
772
|
+
const FONT_SHIFT_CACHE_CAPACITY = 64;
|
|
773
|
+
const LINE_START_CURSOR = {
|
|
774
|
+
segmentIndex: 0,
|
|
775
|
+
graphemeIndex: 0
|
|
776
|
+
};
|
|
777
|
+
const MIN_CONTENT_WIDTH_EPSILON = .001;
|
|
778
|
+
const preparedSegmentCache = /* @__PURE__ */ new Map();
|
|
779
|
+
const fontShiftCache = /* @__PURE__ */ new Map();
|
|
780
|
+
function preprocessSegments(text, whitespace = "preserve") {
|
|
781
|
+
const segments = text.split("\n");
|
|
782
|
+
if (whitespace === "trim-and-collapse") return segments.map((line) => line.trim()).filter((line) => line.length > 0);
|
|
783
|
+
return segments;
|
|
784
|
+
}
|
|
785
|
+
function readLruValue(cache, key) {
|
|
786
|
+
const cached = cache.get(key);
|
|
787
|
+
if (cached == null) return;
|
|
788
|
+
cache.delete(key);
|
|
789
|
+
cache.set(key, cached);
|
|
790
|
+
return cached;
|
|
791
|
+
}
|
|
792
|
+
function writeLruValue(cache, key, value, capacity) {
|
|
793
|
+
if (cache.has(key)) cache.delete(key);
|
|
794
|
+
else if (cache.size >= capacity) {
|
|
795
|
+
const firstKey = cache.keys().next().value;
|
|
796
|
+
if (firstKey != null) cache.delete(firstKey);
|
|
797
|
+
}
|
|
798
|
+
cache.set(key, value);
|
|
799
|
+
return value;
|
|
800
|
+
}
|
|
801
|
+
function getPreparedSegmentCacheKey(segment, font) {
|
|
802
|
+
return `${font}\u0000${segment}`;
|
|
803
|
+
}
|
|
804
|
+
function readPreparedSegment(segment, font) {
|
|
805
|
+
const key = getPreparedSegmentCacheKey(segment, font);
|
|
806
|
+
const cached = readLruValue(preparedSegmentCache, key);
|
|
807
|
+
if (cached != null) return cached;
|
|
808
|
+
return writeLruValue(preparedSegmentCache, key, prepareWithSegments(segment, font), PREPARED_SEGMENT_CACHE_CAPACITY);
|
|
809
|
+
}
|
|
810
|
+
function measureFontShift(ctx) {
|
|
811
|
+
const font = ctx.graphics.font;
|
|
812
|
+
const cached = readLruValue(fontShiftCache, font);
|
|
813
|
+
if (cached != null) return cached;
|
|
814
|
+
const { fontBoundingBoxAscent: ascent = 0, fontBoundingBoxDescent: descent = 0 } = ctx.graphics.measureText(FONT_SHIFT_PROBE);
|
|
815
|
+
return writeLruValue(fontShiftCache, font, ascent - descent, FONT_SHIFT_CACHE_CAPACITY);
|
|
816
|
+
}
|
|
817
|
+
function measurePreparedMinContentWidth(prepared) {
|
|
818
|
+
let maxWidth = 0;
|
|
819
|
+
let maxAnyWidth = 0;
|
|
820
|
+
for (let i = 0; i < prepared.widths.length; i += 1) {
|
|
821
|
+
const segmentWidth = prepared.widths[i] ?? 0;
|
|
822
|
+
maxAnyWidth = Math.max(maxAnyWidth, segmentWidth);
|
|
823
|
+
const segment = prepared.segments[i];
|
|
824
|
+
if (segment != null && segment.trim().length > 0) maxWidth = Math.max(maxWidth, segmentWidth);
|
|
825
|
+
}
|
|
826
|
+
return maxWidth > 0 ? maxWidth : maxAnyWidth;
|
|
827
|
+
}
|
|
828
|
+
function layoutFirstLineIntrinsic(ctx, text, whitespace = "preserve") {
|
|
829
|
+
const segment = preprocessSegments(text, whitespace)[0];
|
|
830
|
+
if (!segment) return {
|
|
831
|
+
width: 0,
|
|
832
|
+
text: "",
|
|
833
|
+
shift: 0
|
|
834
|
+
};
|
|
835
|
+
const shift = measureFontShift(ctx);
|
|
836
|
+
return {
|
|
837
|
+
width: ctx.graphics.measureText(segment).width,
|
|
838
|
+
text: segment,
|
|
839
|
+
shift
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
function measureTextIntrinsic(ctx, text, whitespace = "preserve") {
|
|
843
|
+
const segments = preprocessSegments(text, whitespace);
|
|
844
|
+
if (segments.length === 0) return {
|
|
845
|
+
width: 0,
|
|
846
|
+
lineCount: 0
|
|
847
|
+
};
|
|
848
|
+
let width = 0;
|
|
849
|
+
for (const segment of segments) width = Math.max(width, ctx.graphics.measureText(segment).width);
|
|
850
|
+
return {
|
|
851
|
+
width,
|
|
852
|
+
lineCount: segments.length
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
function layoutTextIntrinsic(ctx, text, whitespace = "preserve") {
|
|
856
|
+
const segments = preprocessSegments(text, whitespace);
|
|
857
|
+
if (segments.length === 0) return {
|
|
858
|
+
width: 0,
|
|
859
|
+
lines: []
|
|
860
|
+
};
|
|
861
|
+
const shift = measureFontShift(ctx);
|
|
862
|
+
let width = 0;
|
|
863
|
+
const lines = [];
|
|
864
|
+
for (const segment of segments) {
|
|
865
|
+
const measuredWidth = ctx.graphics.measureText(segment).width;
|
|
866
|
+
width = Math.max(width, measuredWidth);
|
|
867
|
+
lines.push({
|
|
868
|
+
width: measuredWidth,
|
|
869
|
+
text: segment,
|
|
870
|
+
shift
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
return {
|
|
874
|
+
width,
|
|
875
|
+
lines
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
function layoutFirstLine(ctx, text, maxWidth, whitespace = "preserve") {
|
|
879
|
+
if (maxWidth < 0) maxWidth = 0;
|
|
880
|
+
const segment = preprocessSegments(text, whitespace)[0];
|
|
881
|
+
if (!segment) return {
|
|
882
|
+
width: 0,
|
|
883
|
+
text: "",
|
|
884
|
+
shift: 0
|
|
885
|
+
};
|
|
886
|
+
const shift = measureFontShift(ctx);
|
|
887
|
+
if (maxWidth === 0) return {
|
|
888
|
+
width: 0,
|
|
889
|
+
text: "",
|
|
890
|
+
shift
|
|
891
|
+
};
|
|
892
|
+
const line = layoutNextLine(readPreparedSegment(segment, ctx.graphics.font), LINE_START_CURSOR, maxWidth);
|
|
893
|
+
if (line == null) return {
|
|
894
|
+
width: 0,
|
|
895
|
+
text: "",
|
|
896
|
+
shift
|
|
897
|
+
};
|
|
898
|
+
return {
|
|
899
|
+
width: line.width,
|
|
900
|
+
text: line.text,
|
|
901
|
+
shift
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
function measureText(ctx, text, maxWidth, whitespace = "preserve") {
|
|
905
|
+
if (maxWidth < 0) maxWidth = 0;
|
|
906
|
+
const segments = preprocessSegments(text, whitespace);
|
|
907
|
+
if (segments.length === 0 || maxWidth === 0) return {
|
|
908
|
+
width: 0,
|
|
909
|
+
lineCount: 0
|
|
910
|
+
};
|
|
911
|
+
const font = ctx.graphics.font;
|
|
912
|
+
let width = 0;
|
|
913
|
+
let lineCount = 0;
|
|
914
|
+
for (const segment of segments) {
|
|
915
|
+
if (segment.length === 0) {
|
|
916
|
+
lineCount += 1;
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
const prepared = readPreparedSegment(segment, font);
|
|
920
|
+
lineCount += walkLineRanges(prepared, maxWidth, (line) => {
|
|
921
|
+
width = Math.max(width, line.width);
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
return {
|
|
925
|
+
width,
|
|
926
|
+
lineCount
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
function measureTextMinContent(ctx, text, whitespace = "preserve") {
|
|
930
|
+
const segments = preprocessSegments(text, whitespace);
|
|
931
|
+
if (segments.length === 0) return {
|
|
932
|
+
width: 0,
|
|
933
|
+
lineCount: 0
|
|
934
|
+
};
|
|
935
|
+
const font = ctx.graphics.font;
|
|
936
|
+
let width = 0;
|
|
937
|
+
for (const segment of segments) {
|
|
938
|
+
if (segment.length === 0) continue;
|
|
939
|
+
const prepared = readPreparedSegment(segment, font);
|
|
940
|
+
width = Math.max(width, measurePreparedMinContentWidth(prepared));
|
|
941
|
+
}
|
|
942
|
+
let lineCount = 0;
|
|
943
|
+
const lineMaxWidth = Math.max(width, MIN_CONTENT_WIDTH_EPSILON);
|
|
944
|
+
for (const segment of segments) {
|
|
945
|
+
if (segment.length === 0) {
|
|
946
|
+
lineCount += 1;
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
const prepared = readPreparedSegment(segment, font);
|
|
950
|
+
lineCount += walkLineRanges(prepared, lineMaxWidth, () => {});
|
|
951
|
+
}
|
|
952
|
+
return {
|
|
953
|
+
width,
|
|
954
|
+
lineCount
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
function layoutText(ctx, text, maxWidth, whitespace = "preserve") {
|
|
958
|
+
if (maxWidth < 0) maxWidth = 0;
|
|
959
|
+
const segments = preprocessSegments(text, whitespace);
|
|
960
|
+
if (segments.length === 0 || maxWidth === 0) return {
|
|
961
|
+
width: 0,
|
|
962
|
+
lines: []
|
|
963
|
+
};
|
|
964
|
+
const font = ctx.graphics.font;
|
|
965
|
+
const shift = measureFontShift(ctx);
|
|
966
|
+
let width = 0;
|
|
967
|
+
const lines = [];
|
|
968
|
+
for (const segment of segments) {
|
|
969
|
+
if (segment.length === 0) {
|
|
970
|
+
lines.push({
|
|
971
|
+
width: 0,
|
|
972
|
+
text: "",
|
|
973
|
+
shift
|
|
974
|
+
});
|
|
975
|
+
continue;
|
|
976
|
+
}
|
|
977
|
+
const { lines: segLines } = layoutWithLines(readPreparedSegment(segment, font), maxWidth, 0);
|
|
978
|
+
for (const segLine of segLines) {
|
|
979
|
+
width = Math.max(width, segLine.width);
|
|
980
|
+
lines.push({
|
|
981
|
+
width: segLine.width,
|
|
982
|
+
text: segLine.text,
|
|
983
|
+
shift
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return {
|
|
988
|
+
width,
|
|
989
|
+
lines
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
//#endregion
|
|
993
|
+
//#region src/nodes/text.ts
|
|
994
|
+
function resolvePhysicalTextAlign(options) {
|
|
995
|
+
if (options.physicalAlign != null) return options.physicalAlign;
|
|
996
|
+
if (options.align != null) switch (options.align) {
|
|
997
|
+
case "start": return "left";
|
|
998
|
+
case "center": return "center";
|
|
999
|
+
case "end": return "right";
|
|
1000
|
+
}
|
|
1001
|
+
return "left";
|
|
1002
|
+
}
|
|
1003
|
+
function normalizeTextMaxWidth(maxWidth) {
|
|
1004
|
+
if (maxWidth == null) return;
|
|
1005
|
+
return Math.max(0, maxWidth);
|
|
1006
|
+
}
|
|
1007
|
+
function getTextLayoutContext(ctx) {
|
|
1008
|
+
return ctx;
|
|
1009
|
+
}
|
|
1010
|
+
function readCachedTextLayout(node, ctx, key, compute) {
|
|
1011
|
+
const textCtx = getTextLayoutContext(ctx);
|
|
1012
|
+
const cached = textCtx.getTextLayout(node, key);
|
|
1013
|
+
if (cached != null) return cached;
|
|
1014
|
+
const layout = compute();
|
|
1015
|
+
textCtx.setTextLayout(node, key, layout);
|
|
1016
|
+
return layout;
|
|
1017
|
+
}
|
|
1018
|
+
function getSingleLineLayoutKey(maxWidth) {
|
|
1019
|
+
return maxWidth == null ? "single:intrinsic" : `single:${maxWidth}`;
|
|
1020
|
+
}
|
|
1021
|
+
function getMultiLineMeasureLayoutKey(maxWidth) {
|
|
1022
|
+
return maxWidth == null ? "multi:measure:intrinsic" : `multi:measure:${maxWidth}`;
|
|
1023
|
+
}
|
|
1024
|
+
function getMultiLineDrawLayoutKey(maxWidth) {
|
|
1025
|
+
return maxWidth == null ? "multi:draw:intrinsic" : `multi:draw:${maxWidth}`;
|
|
1026
|
+
}
|
|
1027
|
+
function getSingleLineMinContentLayoutKey() {
|
|
1028
|
+
return "single:min-content";
|
|
1029
|
+
}
|
|
1030
|
+
function getMultiLineMinContentLayoutKey() {
|
|
1031
|
+
return "multi:min-content";
|
|
1032
|
+
}
|
|
1033
|
+
function getSingleLineLayout(node, ctx, text, whitespace) {
|
|
1034
|
+
const maxWidth = normalizeTextMaxWidth(ctx.constraints?.maxWidth);
|
|
1035
|
+
return readCachedTextLayout(node, ctx, getSingleLineLayoutKey(maxWidth), () => maxWidth == null ? layoutFirstLineIntrinsic(ctx, text, whitespace) : layoutFirstLine(ctx, text, maxWidth, whitespace));
|
|
1036
|
+
}
|
|
1037
|
+
function getMultiLineMeasureLayout(node, ctx, text, whitespace) {
|
|
1038
|
+
const maxWidth = normalizeTextMaxWidth(ctx.constraints?.maxWidth);
|
|
1039
|
+
return readCachedTextLayout(node, ctx, getMultiLineMeasureLayoutKey(maxWidth), () => maxWidth == null ? measureTextIntrinsic(ctx, text, whitespace) : measureText(ctx, text, maxWidth, whitespace));
|
|
1040
|
+
}
|
|
1041
|
+
function getMultiLineDrawLayout(node, ctx, text, whitespace) {
|
|
1042
|
+
const maxWidth = normalizeTextMaxWidth(ctx.constraints?.maxWidth);
|
|
1043
|
+
return readCachedTextLayout(node, ctx, getMultiLineDrawLayoutKey(maxWidth), () => maxWidth == null ? layoutTextIntrinsic(ctx, text, whitespace) : layoutText(ctx, text, maxWidth, whitespace));
|
|
1044
|
+
}
|
|
1045
|
+
function getSingleLineMinContentLayout(node, ctx, text, whitespace) {
|
|
1046
|
+
return readCachedTextLayout(node, ctx, getSingleLineMinContentLayoutKey(), () => layoutFirstLineIntrinsic(ctx, text, whitespace));
|
|
1047
|
+
}
|
|
1048
|
+
function getMultiLineMinContentLayout(node, ctx, text, whitespace) {
|
|
1049
|
+
return readCachedTextLayout(node, ctx, getMultiLineMinContentLayoutKey(), () => measureTextMinContent(ctx, text, whitespace));
|
|
1050
|
+
}
|
|
365
1051
|
var MultilineText = class {
|
|
366
|
-
#width = 0;
|
|
367
|
-
#lines = [];
|
|
368
1052
|
constructor(text, options) {
|
|
369
1053
|
this.text = text;
|
|
370
1054
|
this.options = options;
|
|
371
1055
|
}
|
|
372
|
-
get flex() {
|
|
373
|
-
return true;
|
|
374
|
-
}
|
|
375
1056
|
measure(ctx) {
|
|
376
1057
|
return ctx.with((g) => {
|
|
377
1058
|
g.font = this.options.font;
|
|
378
|
-
const { width,
|
|
379
|
-
this.#width = width;
|
|
380
|
-
this.#lines = lines;
|
|
1059
|
+
const { width, lineCount } = getMultiLineMeasureLayout(this, ctx, this.text, this.options.whitespace);
|
|
381
1060
|
return {
|
|
382
|
-
width
|
|
383
|
-
height:
|
|
1061
|
+
width,
|
|
1062
|
+
height: lineCount * this.options.lineHeight
|
|
1063
|
+
};
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
measureMinContent(ctx) {
|
|
1067
|
+
return ctx.with((g) => {
|
|
1068
|
+
g.font = this.options.font;
|
|
1069
|
+
const { width, lineCount } = getMultiLineMinContentLayout(this, ctx, this.text, this.options.whitespace);
|
|
1070
|
+
return {
|
|
1071
|
+
width,
|
|
1072
|
+
height: lineCount * this.options.lineHeight
|
|
384
1073
|
};
|
|
385
1074
|
});
|
|
386
1075
|
}
|
|
@@ -388,25 +1077,26 @@ var MultilineText = class {
|
|
|
388
1077
|
return ctx.with((g) => {
|
|
389
1078
|
g.font = this.options.font;
|
|
390
1079
|
g.fillStyle = ctx.resolveDynValue(this.options.style);
|
|
391
|
-
|
|
1080
|
+
const { width, lines } = getMultiLineDrawLayout(this, ctx, this.text, this.options.whitespace);
|
|
1081
|
+
switch (resolvePhysicalTextAlign(this.options)) {
|
|
392
1082
|
case "left":
|
|
393
|
-
for (const { text, shift } of
|
|
1083
|
+
for (const { text, shift } of lines) {
|
|
394
1084
|
g.fillText(text, x, y + (this.options.lineHeight + shift) / 2);
|
|
395
1085
|
y += this.options.lineHeight;
|
|
396
1086
|
}
|
|
397
1087
|
break;
|
|
398
1088
|
case "right":
|
|
399
|
-
x +=
|
|
1089
|
+
x += width;
|
|
400
1090
|
g.textAlign = "right";
|
|
401
|
-
for (const { text, shift } of
|
|
1091
|
+
for (const { text, shift } of lines) {
|
|
402
1092
|
g.fillText(text, x, y + (this.options.lineHeight + shift) / 2);
|
|
403
1093
|
y += this.options.lineHeight;
|
|
404
1094
|
}
|
|
405
1095
|
break;
|
|
406
1096
|
case "center":
|
|
407
|
-
x +=
|
|
1097
|
+
x += width / 2;
|
|
408
1098
|
g.textAlign = "center";
|
|
409
|
-
for (const { text, shift } of
|
|
1099
|
+
for (const { text, shift } of lines) {
|
|
410
1100
|
g.fillText(text, x, y + (this.options.lineHeight + shift) / 2);
|
|
411
1101
|
y += this.options.lineHeight;
|
|
412
1102
|
}
|
|
@@ -420,25 +1110,26 @@ var MultilineText = class {
|
|
|
420
1110
|
}
|
|
421
1111
|
};
|
|
422
1112
|
var Text = class {
|
|
423
|
-
#width = 0;
|
|
424
|
-
#text = "";
|
|
425
|
-
#shift = 0;
|
|
426
1113
|
constructor(text, options) {
|
|
427
1114
|
this.text = text;
|
|
428
1115
|
this.options = options;
|
|
429
1116
|
}
|
|
430
|
-
get flex() {
|
|
431
|
-
return false;
|
|
432
|
-
}
|
|
433
1117
|
measure(ctx) {
|
|
434
1118
|
return ctx.with((g) => {
|
|
435
1119
|
g.font = this.options.font;
|
|
436
|
-
const { width
|
|
437
|
-
this.#width = width;
|
|
438
|
-
this.#text = text;
|
|
439
|
-
this.#shift = shift;
|
|
1120
|
+
const { width } = getSingleLineLayout(this, ctx, this.text, this.options.whitespace);
|
|
440
1121
|
return {
|
|
441
|
-
width
|
|
1122
|
+
width,
|
|
1123
|
+
height: this.options.lineHeight
|
|
1124
|
+
};
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
measureMinContent(ctx) {
|
|
1128
|
+
return ctx.with((g) => {
|
|
1129
|
+
g.font = this.options.font;
|
|
1130
|
+
const { width } = getSingleLineMinContentLayout(this, ctx, this.text, this.options.whitespace);
|
|
1131
|
+
return {
|
|
1132
|
+
width,
|
|
442
1133
|
height: this.options.lineHeight
|
|
443
1134
|
};
|
|
444
1135
|
});
|
|
@@ -447,7 +1138,8 @@ var Text = class {
|
|
|
447
1138
|
return ctx.with((g) => {
|
|
448
1139
|
g.font = this.options.font;
|
|
449
1140
|
g.fillStyle = ctx.resolveDynValue(this.options.style);
|
|
450
|
-
|
|
1141
|
+
const { text, shift } = getSingleLineLayout(this, ctx, this.text, this.options.whitespace);
|
|
1142
|
+
g.fillText(text, x, y + (this.options.lineHeight + shift) / 2);
|
|
451
1143
|
return false;
|
|
452
1144
|
});
|
|
453
1145
|
}
|
|
@@ -455,34 +1147,20 @@ var Text = class {
|
|
|
455
1147
|
return false;
|
|
456
1148
|
}
|
|
457
1149
|
};
|
|
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
1150
|
//#endregion
|
|
480
|
-
//#region src/renderer.ts
|
|
1151
|
+
//#region src/renderer/base.ts
|
|
1152
|
+
const MAX_CONSTRAINT_VARIANTS = 8;
|
|
1153
|
+
function constraintKey(constraints) {
|
|
1154
|
+
if (constraints == null) return "";
|
|
1155
|
+
return `${constraints.minWidth ?? ""},${constraints.maxWidth ?? ""},${constraints.minHeight ?? ""},${constraints.maxHeight ?? ""}`;
|
|
1156
|
+
}
|
|
481
1157
|
var BaseRenderer = class {
|
|
482
1158
|
graphics;
|
|
483
1159
|
#ctx;
|
|
484
1160
|
#lastWidth;
|
|
485
1161
|
#cache = /* @__PURE__ */ new WeakMap();
|
|
1162
|
+
#layoutCache = /* @__PURE__ */ new WeakMap();
|
|
1163
|
+
#textLayoutCache = /* @__PURE__ */ new WeakMap();
|
|
486
1164
|
get context() {
|
|
487
1165
|
return shallow(this.#ctx);
|
|
488
1166
|
}
|
|
@@ -493,19 +1171,20 @@ var BaseRenderer = class {
|
|
|
493
1171
|
const self = this;
|
|
494
1172
|
this.#ctx = {
|
|
495
1173
|
graphics: this.graphics,
|
|
496
|
-
|
|
497
|
-
return
|
|
1174
|
+
measureNode(node, constraints) {
|
|
1175
|
+
return self.measureNode(node, constraints);
|
|
498
1176
|
},
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
1177
|
+
getLayoutResult(node, constraints) {
|
|
1178
|
+
return self.getLayoutResult(node, constraints);
|
|
1179
|
+
},
|
|
1180
|
+
setLayoutResult(node, result, constraints) {
|
|
1181
|
+
self.setLayoutResult(node, result, constraints);
|
|
1182
|
+
},
|
|
1183
|
+
getTextLayout(node, key) {
|
|
1184
|
+
return self.getTextLayout(node, key);
|
|
504
1185
|
},
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
measureNode(node) {
|
|
508
|
-
return self.measureNode(node, this);
|
|
1186
|
+
setTextLayout(node, key, layout) {
|
|
1187
|
+
self.setTextLayout(node, key, layout);
|
|
509
1188
|
},
|
|
510
1189
|
invalidateNode: this.invalidateNode.bind(this),
|
|
511
1190
|
resolveDynValue(value) {
|
|
@@ -523,21 +1202,131 @@ var BaseRenderer = class {
|
|
|
523
1202
|
};
|
|
524
1203
|
this.#lastWidth = this.graphics.canvas.clientWidth;
|
|
525
1204
|
}
|
|
1205
|
+
#clearAllCaches() {
|
|
1206
|
+
this.#cache = /* @__PURE__ */ new WeakMap();
|
|
1207
|
+
this.#layoutCache = /* @__PURE__ */ new WeakMap();
|
|
1208
|
+
this.#textLayoutCache = /* @__PURE__ */ new WeakMap();
|
|
1209
|
+
}
|
|
1210
|
+
#syncCachesToViewportWidth() {
|
|
1211
|
+
const width = this.graphics.canvas.clientWidth;
|
|
1212
|
+
if (this.#lastWidth === width) return;
|
|
1213
|
+
this.#clearAllCaches();
|
|
1214
|
+
this.#lastWidth = width;
|
|
1215
|
+
}
|
|
1216
|
+
getRootConstraints() {
|
|
1217
|
+
return { maxWidth: this.graphics.canvas.clientWidth };
|
|
1218
|
+
}
|
|
1219
|
+
getRootContext() {
|
|
1220
|
+
const ctx = this.context;
|
|
1221
|
+
ctx.constraints = this.getRootConstraints();
|
|
1222
|
+
return ctx;
|
|
1223
|
+
}
|
|
1224
|
+
measureRootNode(node) {
|
|
1225
|
+
return this.measureNode(node, this.getRootConstraints());
|
|
1226
|
+
}
|
|
1227
|
+
drawRootNode(node, x = 0, y = 0) {
|
|
1228
|
+
this.measureRootNode(node);
|
|
1229
|
+
return node.draw(this.getRootContext(), x, y);
|
|
1230
|
+
}
|
|
1231
|
+
hittestRootNode(node, test) {
|
|
1232
|
+
this.measureRootNode(node);
|
|
1233
|
+
return node.hittest(this.getRootContext(), test);
|
|
1234
|
+
}
|
|
526
1235
|
invalidateNode(node) {
|
|
1236
|
+
this.#syncCachesToViewportWidth();
|
|
527
1237
|
this.#cache.delete(node);
|
|
528
|
-
|
|
529
|
-
|
|
1238
|
+
this.#layoutCache.delete(node);
|
|
1239
|
+
this.#textLayoutCache.delete(node);
|
|
1240
|
+
forEachNodeAncestor(node, (ancestor) => {
|
|
1241
|
+
this.#cache.delete(ancestor);
|
|
1242
|
+
this.#layoutCache.delete(ancestor);
|
|
1243
|
+
this.#textLayoutCache.delete(ancestor);
|
|
1244
|
+
});
|
|
530
1245
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
1246
|
+
getLayoutResult(node, constraints) {
|
|
1247
|
+
this.#syncCachesToViewportWidth();
|
|
1248
|
+
const nodeCache = this.#layoutCache.get(node);
|
|
1249
|
+
if (nodeCache == null) return;
|
|
1250
|
+
const key = constraintKey(constraints);
|
|
1251
|
+
const cached = nodeCache.get(key);
|
|
1252
|
+
if (cached == null) return;
|
|
1253
|
+
if (cached.revision !== getNodeRevision(node)) {
|
|
1254
|
+
nodeCache.delete(key);
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
return cached.layout;
|
|
1258
|
+
}
|
|
1259
|
+
setLayoutResult(node, result, constraints) {
|
|
1260
|
+
this.#syncCachesToViewportWidth();
|
|
1261
|
+
let nodeCache = this.#layoutCache.get(node);
|
|
1262
|
+
if (nodeCache == null) {
|
|
1263
|
+
nodeCache = /* @__PURE__ */ new Map();
|
|
1264
|
+
this.#layoutCache.set(node, nodeCache);
|
|
1265
|
+
} else if (nodeCache.size >= MAX_CONSTRAINT_VARIANTS) {
|
|
1266
|
+
const firstKey = nodeCache.keys().next().value;
|
|
1267
|
+
nodeCache.delete(firstKey);
|
|
1268
|
+
}
|
|
1269
|
+
nodeCache.set(constraintKey(constraints), {
|
|
1270
|
+
revision: getNodeRevision(node),
|
|
1271
|
+
layout: result
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
getTextLayout(node, key) {
|
|
1275
|
+
this.#syncCachesToViewportWidth();
|
|
1276
|
+
const nodeCache = this.#textLayoutCache.get(node);
|
|
1277
|
+
if (nodeCache == null) return;
|
|
1278
|
+
const cached = nodeCache.get(key);
|
|
1279
|
+
if (cached == null) return;
|
|
1280
|
+
if (cached.revision !== getNodeRevision(node)) {
|
|
1281
|
+
nodeCache.delete(key);
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
return cached.layout;
|
|
1285
|
+
}
|
|
1286
|
+
setTextLayout(node, key, layout) {
|
|
1287
|
+
this.#syncCachesToViewportWidth();
|
|
1288
|
+
let nodeCache = this.#textLayoutCache.get(node);
|
|
1289
|
+
if (nodeCache == null) {
|
|
1290
|
+
nodeCache = /* @__PURE__ */ new Map();
|
|
1291
|
+
this.#textLayoutCache.set(node, nodeCache);
|
|
1292
|
+
} else if (nodeCache.size >= MAX_CONSTRAINT_VARIANTS) {
|
|
1293
|
+
const firstKey = nodeCache.keys().next().value;
|
|
1294
|
+
nodeCache.delete(firstKey);
|
|
538
1295
|
}
|
|
539
|
-
|
|
540
|
-
|
|
1296
|
+
nodeCache.set(key, {
|
|
1297
|
+
revision: getNodeRevision(node),
|
|
1298
|
+
layout
|
|
1299
|
+
});
|
|
1300
|
+
}
|
|
1301
|
+
measureNode(node, constraints) {
|
|
1302
|
+
this.#syncCachesToViewportWidth();
|
|
1303
|
+
{
|
|
1304
|
+
const nodeCache = this.#cache.get(node);
|
|
1305
|
+
if (nodeCache != null) {
|
|
1306
|
+
const key = constraintKey(constraints);
|
|
1307
|
+
const cached = nodeCache.get(key);
|
|
1308
|
+
if (cached != null) {
|
|
1309
|
+
if (cached.revision === getNodeRevision(node)) return cached.box;
|
|
1310
|
+
nodeCache.delete(key);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
const ctx = this.context;
|
|
1315
|
+
if (constraints != null) ctx.constraints = constraints;
|
|
1316
|
+
const result = node.measure(ctx);
|
|
1317
|
+
const key = constraintKey(constraints);
|
|
1318
|
+
let nodeCache = this.#cache.get(node);
|
|
1319
|
+
if (nodeCache == null) {
|
|
1320
|
+
nodeCache = /* @__PURE__ */ new Map();
|
|
1321
|
+
this.#cache.set(node, nodeCache);
|
|
1322
|
+
} else if (nodeCache.size >= MAX_CONSTRAINT_VARIANTS) {
|
|
1323
|
+
const firstKey = nodeCache.keys().next().value;
|
|
1324
|
+
nodeCache.delete(firstKey);
|
|
1325
|
+
}
|
|
1326
|
+
nodeCache.set(key, {
|
|
1327
|
+
revision: getNodeRevision(node),
|
|
1328
|
+
box: result
|
|
1329
|
+
});
|
|
541
1330
|
return result;
|
|
542
1331
|
}
|
|
543
1332
|
};
|
|
@@ -545,33 +1334,26 @@ var DebugRenderer = class extends BaseRenderer {
|
|
|
545
1334
|
draw(node) {
|
|
546
1335
|
const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
|
|
547
1336
|
this.graphics.clearRect(0, 0, viewportWidth, viewportHeight);
|
|
548
|
-
return
|
|
1337
|
+
return this.drawRootNode(node);
|
|
549
1338
|
}
|
|
550
1339
|
hittest(node, test) {
|
|
551
|
-
return
|
|
1340
|
+
return this.hittestRootNode(node, test);
|
|
552
1341
|
}
|
|
553
1342
|
};
|
|
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
|
-
}
|
|
1343
|
+
//#endregion
|
|
1344
|
+
//#region src/renderer/list-state.ts
|
|
566
1345
|
var ListState = class {
|
|
567
1346
|
offset = 0;
|
|
568
|
-
position
|
|
1347
|
+
position;
|
|
569
1348
|
items = [];
|
|
1349
|
+
constructor(items = []) {
|
|
1350
|
+
this.items = [...items];
|
|
1351
|
+
}
|
|
570
1352
|
unshift(...items) {
|
|
571
1353
|
this.unshiftAll(items);
|
|
572
1354
|
}
|
|
573
1355
|
unshiftAll(items) {
|
|
574
|
-
this.position += items.length;
|
|
1356
|
+
if (this.position != null) this.position += items.length;
|
|
575
1357
|
this.items = items.concat(this.items);
|
|
576
1358
|
}
|
|
577
1359
|
push(...items) {
|
|
@@ -580,20 +1362,59 @@ var ListState = class {
|
|
|
580
1362
|
pushAll(items) {
|
|
581
1363
|
this.items.push(...items);
|
|
582
1364
|
}
|
|
583
|
-
|
|
584
|
-
this.
|
|
1365
|
+
setAnchor(position, offset = 0) {
|
|
1366
|
+
this.position = Number.isFinite(position) ? Math.trunc(position) : void 0;
|
|
1367
|
+
this.offset = Number.isFinite(offset) ? offset : 0;
|
|
1368
|
+
}
|
|
1369
|
+
reset(items = []) {
|
|
1370
|
+
this.items = [...items];
|
|
585
1371
|
this.offset = 0;
|
|
586
|
-
this.position =
|
|
1372
|
+
this.position = void 0;
|
|
587
1373
|
}
|
|
588
1374
|
resetScroll() {
|
|
589
1375
|
this.offset = 0;
|
|
590
|
-
this.position =
|
|
1376
|
+
this.position = void 0;
|
|
591
1377
|
}
|
|
592
1378
|
applyScroll(delta) {
|
|
593
1379
|
this.offset += delta;
|
|
594
1380
|
}
|
|
595
1381
|
};
|
|
596
|
-
|
|
1382
|
+
//#endregion
|
|
1383
|
+
//#region src/renderer/memo.ts
|
|
1384
|
+
function isWeakMapKey(value) {
|
|
1385
|
+
return typeof value === "object" && value !== null || typeof value === "function";
|
|
1386
|
+
}
|
|
1387
|
+
function memoRenderItem(renderItem) {
|
|
1388
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
1389
|
+
function fn(item) {
|
|
1390
|
+
if (!isWeakMapKey(item)) throw new TypeError("memoRenderItem() only supports object items. Use memoRenderItemBy() for primitive keys.");
|
|
1391
|
+
const key = item;
|
|
1392
|
+
const cached = cache.get(key);
|
|
1393
|
+
if (cached != null) return cached;
|
|
1394
|
+
const result = renderItem(item);
|
|
1395
|
+
cache.set(key, result);
|
|
1396
|
+
return result;
|
|
1397
|
+
}
|
|
1398
|
+
return Object.assign(fn, { reset: (key) => cache.delete(key) });
|
|
1399
|
+
}
|
|
1400
|
+
function memoRenderItemBy(keyOf, renderItem) {
|
|
1401
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1402
|
+
function fn(item) {
|
|
1403
|
+
const key = keyOf(item);
|
|
1404
|
+
const cached = cache.get(key);
|
|
1405
|
+
if (cached != null) return cached;
|
|
1406
|
+
const result = renderItem(item);
|
|
1407
|
+
cache.set(key, result);
|
|
1408
|
+
return result;
|
|
1409
|
+
}
|
|
1410
|
+
return Object.assign(fn, {
|
|
1411
|
+
reset: (item) => cache.delete(keyOf(item)),
|
|
1412
|
+
resetKey: (key) => cache.delete(key)
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
//#endregion
|
|
1416
|
+
//#region src/renderer/virtualized/base.ts
|
|
1417
|
+
function clamp$3(value, min, max) {
|
|
597
1418
|
return Math.min(Math.max(value, min), max);
|
|
598
1419
|
}
|
|
599
1420
|
function sameState(state, position, offset) {
|
|
@@ -629,13 +1450,23 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
629
1450
|
set items(value) {
|
|
630
1451
|
this.options.list.items = value;
|
|
631
1452
|
}
|
|
1453
|
+
_readListState() {
|
|
1454
|
+
return {
|
|
1455
|
+
position: this.position,
|
|
1456
|
+
offset: this.offset
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
_commitListState(state) {
|
|
1460
|
+
this.position = state.position;
|
|
1461
|
+
this.offset = state.offset;
|
|
1462
|
+
}
|
|
632
1463
|
jumpTo(index, options = {}) {
|
|
633
1464
|
if (this.items.length === 0) {
|
|
634
1465
|
this.#cancelJumpAnimation();
|
|
635
1466
|
return;
|
|
636
1467
|
}
|
|
637
1468
|
const targetIndex = this._clampItemIndex(index);
|
|
638
|
-
this.
|
|
1469
|
+
const currentState = this._normalizeListState(this._readListState());
|
|
639
1470
|
const targetBlock = options.block ?? this._getDefaultJumpBlock();
|
|
640
1471
|
const targetAnchor = this._getTargetAnchor(targetIndex, targetBlock);
|
|
641
1472
|
if (!(options.animated ?? true)) {
|
|
@@ -644,14 +1475,14 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
644
1475
|
options.onComplete?.();
|
|
645
1476
|
return;
|
|
646
1477
|
}
|
|
647
|
-
const startAnchor = this._readAnchor();
|
|
1478
|
+
const startAnchor = this._readAnchor(currentState);
|
|
648
1479
|
if (!Number.isFinite(startAnchor)) {
|
|
649
1480
|
this.#cancelJumpAnimation();
|
|
650
1481
|
this._applyAnchor(targetAnchor);
|
|
651
1482
|
options.onComplete?.();
|
|
652
1483
|
return;
|
|
653
1484
|
}
|
|
654
|
-
const duration = clamp(options.duration ?? VirtualizedRenderer.MIN_JUMP_DURATION + Math.abs(targetAnchor - startAnchor) * VirtualizedRenderer.JUMP_DURATION_PER_ITEM, 0, VirtualizedRenderer.MAX_JUMP_DURATION);
|
|
1485
|
+
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
1486
|
if (duration <= 0 || Math.abs(targetAnchor - startAnchor) <= Number.EPSILON) {
|
|
656
1487
|
this.#cancelJumpAnimation();
|
|
657
1488
|
this._applyAnchor(targetAnchor);
|
|
@@ -666,10 +1497,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
666
1497
|
needsMoreFrames: true,
|
|
667
1498
|
onComplete: options.onComplete
|
|
668
1499
|
};
|
|
669
|
-
this.#controlledState =
|
|
670
|
-
position: this.position,
|
|
671
|
-
offset: this.offset
|
|
672
|
-
};
|
|
1500
|
+
this.#controlledState = this._readListState();
|
|
673
1501
|
}
|
|
674
1502
|
_resetRenderFeedback(feedback) {
|
|
675
1503
|
if (feedback == null) return;
|
|
@@ -681,8 +1509,8 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
681
1509
|
_accumulateRenderFeedback(feedback, idx, top, height) {
|
|
682
1510
|
if (!Number.isFinite(top) || !Number.isFinite(height) || height <= 0) return;
|
|
683
1511
|
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
684
|
-
const visibleTop = clamp(-top, 0, height);
|
|
685
|
-
const visibleBottom = clamp(viewportHeight - top, 0, height);
|
|
1512
|
+
const visibleTop = clamp$3(-top, 0, height);
|
|
1513
|
+
const visibleBottom = clamp$3(viewportHeight - top, 0, height);
|
|
686
1514
|
if (visibleBottom <= visibleTop) return;
|
|
687
1515
|
const itemMin = idx + visibleTop / height;
|
|
688
1516
|
const itemMax = idx + visibleBottom / height;
|
|
@@ -694,14 +1522,26 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
694
1522
|
_renderDrawList(list, shift, feedback) {
|
|
695
1523
|
let result = false;
|
|
696
1524
|
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
697
|
-
for (const { idx, node, offset, height } of list) {
|
|
1525
|
+
for (const { idx, value: node, offset, height } of list) {
|
|
698
1526
|
const y = offset + shift;
|
|
699
1527
|
if (feedback != null) this._accumulateRenderFeedback(feedback, idx, y, height);
|
|
700
1528
|
if (y + height < 0 || y > viewportHeight) continue;
|
|
701
|
-
if (
|
|
1529
|
+
if (this.drawRootNode(node, 0, y)) result = true;
|
|
702
1530
|
}
|
|
703
1531
|
return result;
|
|
704
1532
|
}
|
|
1533
|
+
_renderVisibleWindow(window, feedback) {
|
|
1534
|
+
this._resetRenderFeedback(feedback);
|
|
1535
|
+
return this._renderDrawList(window.drawList, window.shift, feedback);
|
|
1536
|
+
}
|
|
1537
|
+
_hittestVisibleWindow(window, test) {
|
|
1538
|
+
for (const { value: node, offset, height } of window.drawList) {
|
|
1539
|
+
const y = offset + window.shift;
|
|
1540
|
+
if (test.y < y || test.y >= y + height) continue;
|
|
1541
|
+
return node.hittest(this.getRootContext(), shallowMerge(test, { y: test.y - y }));
|
|
1542
|
+
}
|
|
1543
|
+
return false;
|
|
1544
|
+
}
|
|
705
1545
|
_prepareRender() {
|
|
706
1546
|
const animation = this.#jumpAnimation;
|
|
707
1547
|
if (animation == null) return false;
|
|
@@ -713,7 +1553,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
713
1553
|
this.#cancelJumpAnimation();
|
|
714
1554
|
return false;
|
|
715
1555
|
}
|
|
716
|
-
const progress = clamp((getNow() - animation.startTime) / animation.duration, 0, 1);
|
|
1556
|
+
const progress = clamp$3((getNow() - animation.startTime) / animation.duration, 0, 1);
|
|
717
1557
|
const eased = progress >= 1 ? 1 : smoothstep(progress);
|
|
718
1558
|
const anchor = animation.startAnchor + (animation.targetAnchor - animation.startAnchor) * eased;
|
|
719
1559
|
this._applyAnchor(anchor);
|
|
@@ -724,10 +1564,7 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
724
1564
|
const animation = this.#jumpAnimation;
|
|
725
1565
|
if (animation == null) return requestRedraw;
|
|
726
1566
|
if (animation.needsMoreFrames) {
|
|
727
|
-
this.#controlledState =
|
|
728
|
-
position: this.position,
|
|
729
|
-
offset: this.offset
|
|
730
|
-
};
|
|
1567
|
+
this.#controlledState = this._readListState();
|
|
731
1568
|
return true;
|
|
732
1569
|
}
|
|
733
1570
|
const onComplete = animation.onComplete;
|
|
@@ -736,12 +1573,12 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
736
1573
|
return requestRedraw || this.#jumpAnimation != null;
|
|
737
1574
|
}
|
|
738
1575
|
_clampItemIndex(index) {
|
|
739
|
-
return clamp(Number.isFinite(index) ? Math.trunc(index) : 0, 0, this.items.length - 1);
|
|
1576
|
+
return clamp$3(Number.isFinite(index) ? Math.trunc(index) : 0, 0, this.items.length - 1);
|
|
740
1577
|
}
|
|
741
1578
|
_getItemHeight(index) {
|
|
742
1579
|
const item = this.items[index];
|
|
743
1580
|
const node = this.options.renderItem(item);
|
|
744
|
-
return this.
|
|
1581
|
+
return this.measureRootNode(node).height;
|
|
745
1582
|
}
|
|
746
1583
|
_getAnchorAtOffset(index, offset) {
|
|
747
1584
|
if (this.items.length === 0) return 0;
|
|
@@ -769,251 +1606,309 @@ var VirtualizedRenderer = class VirtualizedRenderer extends BaseRenderer {
|
|
|
769
1606
|
this.#controlledState = void 0;
|
|
770
1607
|
}
|
|
771
1608
|
};
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1609
|
+
//#endregion
|
|
1610
|
+
//#region src/renderer/virtualized/solver.ts
|
|
1611
|
+
function clamp$2(value, min, max) {
|
|
1612
|
+
return Math.min(Math.max(value, min), max);
|
|
1613
|
+
}
|
|
1614
|
+
function normalizeOffset(offset) {
|
|
1615
|
+
return Number.isFinite(offset) ? offset : 0;
|
|
1616
|
+
}
|
|
1617
|
+
function normalizeTimelineState(itemCount, state) {
|
|
1618
|
+
if (itemCount <= 0) return {
|
|
1619
|
+
position: 0,
|
|
1620
|
+
offset: 0
|
|
1621
|
+
};
|
|
1622
|
+
const position = state.position;
|
|
1623
|
+
if (typeof position !== "number" || !Number.isFinite(position)) return {
|
|
1624
|
+
position: 0,
|
|
1625
|
+
offset: normalizeOffset(state.offset)
|
|
1626
|
+
};
|
|
1627
|
+
return {
|
|
1628
|
+
position: clamp$2(Math.trunc(position), 0, itemCount - 1),
|
|
1629
|
+
offset: normalizeOffset(state.offset)
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
function normalizeChatState(itemCount, state) {
|
|
1633
|
+
if (itemCount <= 0) return {
|
|
1634
|
+
position: 0,
|
|
1635
|
+
offset: 0
|
|
1636
|
+
};
|
|
1637
|
+
const position = state.position;
|
|
1638
|
+
if (typeof position !== "number" || !Number.isFinite(position)) return {
|
|
1639
|
+
position: itemCount - 1,
|
|
1640
|
+
offset: normalizeOffset(state.offset)
|
|
1641
|
+
};
|
|
1642
|
+
return {
|
|
1643
|
+
position: clamp$2(Math.trunc(position), 0, itemCount - 1),
|
|
1644
|
+
offset: normalizeOffset(state.offset)
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
function resolveTimelineVisibleWindow(items, state, viewportHeight, resolveItem) {
|
|
1648
|
+
const normalizedState = normalizeTimelineState(items.length, state);
|
|
1649
|
+
if (items.length === 0) return {
|
|
1650
|
+
normalizedState,
|
|
1651
|
+
window: {
|
|
1652
|
+
drawList: [],
|
|
1653
|
+
shift: 0
|
|
1654
|
+
}
|
|
1655
|
+
};
|
|
1656
|
+
let { position, offset } = normalizedState;
|
|
1657
|
+
let drawLength = 0;
|
|
1658
|
+
if (offset > 0) if (position === 0) offset = 0;
|
|
1659
|
+
else {
|
|
1660
|
+
for (let i = position - 1; i >= 0; i -= 1) {
|
|
1661
|
+
const { height } = resolveItem(items[i], i);
|
|
1662
|
+
position = i;
|
|
1663
|
+
offset -= height;
|
|
1664
|
+
if (offset <= 0) break;
|
|
1665
|
+
}
|
|
1666
|
+
if (position === 0 && offset > 0) offset = 0;
|
|
1667
|
+
}
|
|
1668
|
+
let y = offset;
|
|
1669
|
+
const drawList = [];
|
|
1670
|
+
for (let i = position; i < items.length; i += 1) {
|
|
1671
|
+
const { value, height } = resolveItem(items[i], i);
|
|
1672
|
+
if (y + height > 0) {
|
|
1673
|
+
drawList.push({
|
|
1674
|
+
idx: i,
|
|
1675
|
+
value,
|
|
1676
|
+
offset: y,
|
|
1677
|
+
height
|
|
1678
|
+
});
|
|
1679
|
+
drawLength += height;
|
|
1680
|
+
} else {
|
|
1681
|
+
offset += height;
|
|
1682
|
+
position = i + 1;
|
|
1683
|
+
}
|
|
1684
|
+
y += height;
|
|
1685
|
+
if (y >= viewportHeight) break;
|
|
1686
|
+
}
|
|
1687
|
+
let shift = 0;
|
|
1688
|
+
if (y < viewportHeight) if (position === 0 && drawLength < viewportHeight) {
|
|
1689
|
+
shift = -offset;
|
|
1690
|
+
offset = 0;
|
|
1691
|
+
} else {
|
|
1692
|
+
shift = viewportHeight - y;
|
|
1693
|
+
y = offset += shift;
|
|
1694
|
+
let lastIdx = -1;
|
|
1695
|
+
for (let i = position - 1; i >= 0; i -= 1) {
|
|
1696
|
+
const { value, height } = resolveItem(items[i], i);
|
|
1697
|
+
drawLength += height;
|
|
1698
|
+
y -= height;
|
|
1699
|
+
drawList.push({
|
|
1700
|
+
idx: i,
|
|
1701
|
+
value,
|
|
1702
|
+
offset: y - shift,
|
|
1703
|
+
height
|
|
1704
|
+
});
|
|
1705
|
+
lastIdx = i;
|
|
1706
|
+
if (y < 0) break;
|
|
1707
|
+
}
|
|
1708
|
+
if (lastIdx === 0 && drawLength < viewportHeight) {
|
|
1709
|
+
shift = drawList.at(-1)?.offset == null ? 0 : -drawList.at(-1).offset;
|
|
1710
|
+
position = 0;
|
|
1711
|
+
offset = 0;
|
|
1712
|
+
}
|
|
775
1713
|
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1714
|
+
return {
|
|
1715
|
+
normalizedState: {
|
|
1716
|
+
position,
|
|
1717
|
+
offset
|
|
1718
|
+
},
|
|
1719
|
+
window: {
|
|
1720
|
+
drawList,
|
|
1721
|
+
shift
|
|
1722
|
+
}
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
function resolveChatVisibleWindow(items, state, viewportHeight, resolveItem) {
|
|
1726
|
+
const normalizedState = normalizeChatState(items.length, state);
|
|
1727
|
+
if (items.length === 0) return {
|
|
1728
|
+
normalizedState,
|
|
1729
|
+
window: {
|
|
1730
|
+
drawList: [],
|
|
1731
|
+
shift: 0
|
|
1732
|
+
}
|
|
1733
|
+
};
|
|
1734
|
+
let { position, offset } = normalizedState;
|
|
1735
|
+
let drawLength = 0;
|
|
1736
|
+
if (offset < 0) if (position === items.length - 1) offset = 0;
|
|
1737
|
+
else for (let i = position + 1; i < items.length; i += 1) {
|
|
1738
|
+
const { height } = resolveItem(items[i], i);
|
|
1739
|
+
position = i;
|
|
1740
|
+
offset += height;
|
|
1741
|
+
if (offset > 0) break;
|
|
1742
|
+
}
|
|
1743
|
+
let y = viewportHeight + offset;
|
|
1744
|
+
const drawList = [];
|
|
1745
|
+
for (let i = position; i >= 0; i -= 1) {
|
|
1746
|
+
const { value, height } = resolveItem(items[i], i);
|
|
1747
|
+
y -= height;
|
|
1748
|
+
if (y <= viewportHeight) {
|
|
1749
|
+
drawList.push({
|
|
1750
|
+
idx: i,
|
|
1751
|
+
value,
|
|
1752
|
+
offset: y,
|
|
1753
|
+
height
|
|
1754
|
+
});
|
|
1755
|
+
drawLength += height;
|
|
1756
|
+
} else {
|
|
1757
|
+
offset -= height;
|
|
1758
|
+
position = i - 1;
|
|
1759
|
+
}
|
|
1760
|
+
if (y < 0) break;
|
|
1761
|
+
}
|
|
1762
|
+
let shift = 0;
|
|
1763
|
+
if (y > 0) {
|
|
1764
|
+
shift = -y;
|
|
1765
|
+
if (drawLength < viewportHeight) {
|
|
1766
|
+
y = drawLength;
|
|
1767
|
+
for (let i = position + 1; i < items.length; i += 1) {
|
|
1768
|
+
const { value, height } = resolveItem(items[i], i);
|
|
1769
|
+
drawList.push({
|
|
1770
|
+
idx: i,
|
|
1771
|
+
value,
|
|
1772
|
+
offset: y - shift,
|
|
1773
|
+
height
|
|
1774
|
+
});
|
|
1775
|
+
y = drawLength += height;
|
|
1776
|
+
position = i;
|
|
1777
|
+
if (y >= viewportHeight) break;
|
|
1778
|
+
}
|
|
1779
|
+
offset = drawLength < viewportHeight ? 0 : drawLength - viewportHeight;
|
|
1780
|
+
} else offset = drawLength - viewportHeight;
|
|
1781
|
+
}
|
|
1782
|
+
return {
|
|
1783
|
+
normalizedState: {
|
|
1784
|
+
position,
|
|
1785
|
+
offset
|
|
1786
|
+
},
|
|
1787
|
+
window: {
|
|
1788
|
+
drawList,
|
|
1789
|
+
shift
|
|
782
1790
|
}
|
|
783
|
-
|
|
784
|
-
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
//#endregion
|
|
1794
|
+
//#region src/renderer/virtualized/chat.ts
|
|
1795
|
+
function clamp$1(value, min, max) {
|
|
1796
|
+
return Math.min(Math.max(value, min), max);
|
|
1797
|
+
}
|
|
1798
|
+
var ChatRenderer = class extends VirtualizedRenderer {
|
|
1799
|
+
#resolveVisibleWindow() {
|
|
1800
|
+
return resolveChatVisibleWindow(this.items, this._readListState(), this.graphics.canvas.clientHeight, (item) => {
|
|
1801
|
+
const node = this.options.renderItem(item);
|
|
1802
|
+
return {
|
|
1803
|
+
value: node,
|
|
1804
|
+
height: this.measureRootNode(node).height
|
|
1805
|
+
};
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
_getDefaultJumpBlock() {
|
|
1809
|
+
return "end";
|
|
1810
|
+
}
|
|
1811
|
+
_normalizeListState(state) {
|
|
1812
|
+
return normalizeChatState(this.items.length, state);
|
|
785
1813
|
}
|
|
786
|
-
_readAnchor() {
|
|
787
|
-
this._prepareAnchorState();
|
|
1814
|
+
_readAnchor(state) {
|
|
788
1815
|
if (this.items.length === 0) return 0;
|
|
789
|
-
const height = this._getItemHeight(
|
|
790
|
-
return height > 0 ?
|
|
1816
|
+
const height = this._getItemHeight(state.position);
|
|
1817
|
+
return height > 0 ? state.position + 1 - state.offset / height : state.position + 1;
|
|
791
1818
|
}
|
|
792
1819
|
_applyAnchor(anchor) {
|
|
793
1820
|
if (this.items.length === 0) return;
|
|
794
|
-
const clampedAnchor = clamp(anchor, 0, this.items.length);
|
|
795
|
-
const position = clamp(Math.
|
|
1821
|
+
const clampedAnchor = clamp$1(anchor, 0, this.items.length);
|
|
1822
|
+
const position = clamp$1(Math.ceil(clampedAnchor) - 1, 0, this.items.length - 1);
|
|
796
1823
|
const height = this._getItemHeight(position);
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
1824
|
+
const offset = height > 0 ? (position + 1 - clampedAnchor) * height : 0;
|
|
1825
|
+
this._commitListState({
|
|
1826
|
+
position,
|
|
1827
|
+
offset: Object.is(offset, -0) ? 0 : offset
|
|
1828
|
+
});
|
|
800
1829
|
}
|
|
801
1830
|
_getTargetAnchor(index, block) {
|
|
802
1831
|
const height = this._getItemHeight(index);
|
|
803
1832
|
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
804
1833
|
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
|
|
1834
|
+
case "start": return this._getAnchorAtOffset(index, viewportHeight);
|
|
1835
|
+
case "center": return this._getAnchorAtOffset(index, height / 2 + viewportHeight / 2);
|
|
1836
|
+
case "end": return this._getAnchorAtOffset(index, height);
|
|
808
1837
|
}
|
|
809
1838
|
}
|
|
810
1839
|
render(feedback) {
|
|
811
1840
|
const keepAnimating = this._prepareRender();
|
|
812
1841
|
const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
|
|
813
1842
|
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);
|
|
1843
|
+
const solution = this.#resolveVisibleWindow();
|
|
1844
|
+
const requestRedraw = this._renderVisibleWindow(solution.window, feedback);
|
|
1845
|
+
this._commitListState(solution.normalizedState);
|
|
879
1846
|
return this._finishRender(keepAnimating || requestRedraw);
|
|
880
1847
|
}
|
|
881
1848
|
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;
|
|
1849
|
+
return this._hittestVisibleWindow(this.#resolveVisibleWindow().window, test);
|
|
893
1850
|
}
|
|
894
1851
|
};
|
|
895
|
-
|
|
1852
|
+
//#endregion
|
|
1853
|
+
//#region src/renderer/virtualized/timeline.ts
|
|
1854
|
+
function clamp(value, min, max) {
|
|
1855
|
+
return Math.min(Math.max(value, min), max);
|
|
1856
|
+
}
|
|
1857
|
+
var TimelineRenderer = class extends VirtualizedRenderer {
|
|
1858
|
+
#resolveVisibleWindow() {
|
|
1859
|
+
return resolveTimelineVisibleWindow(this.items, this._readListState(), this.graphics.canvas.clientHeight, (item) => {
|
|
1860
|
+
const node = this.options.renderItem(item);
|
|
1861
|
+
return {
|
|
1862
|
+
value: node,
|
|
1863
|
+
height: this.measureRootNode(node).height
|
|
1864
|
+
};
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
896
1867
|
_getDefaultJumpBlock() {
|
|
897
|
-
return "
|
|
1868
|
+
return "start";
|
|
898
1869
|
}
|
|
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;
|
|
1870
|
+
_normalizeListState(state) {
|
|
1871
|
+
return normalizeTimelineState(this.items.length, state);
|
|
908
1872
|
}
|
|
909
|
-
_readAnchor() {
|
|
910
|
-
this._prepareAnchorState();
|
|
1873
|
+
_readAnchor(state) {
|
|
911
1874
|
if (this.items.length === 0) return 0;
|
|
912
|
-
const height = this._getItemHeight(
|
|
913
|
-
return height > 0 ?
|
|
1875
|
+
const height = this._getItemHeight(state.position);
|
|
1876
|
+
return height > 0 ? state.position - state.offset / height : state.position;
|
|
914
1877
|
}
|
|
915
1878
|
_applyAnchor(anchor) {
|
|
916
1879
|
if (this.items.length === 0) return;
|
|
917
1880
|
const clampedAnchor = clamp(anchor, 0, this.items.length);
|
|
918
|
-
const position = clamp(Math.
|
|
1881
|
+
const position = clamp(Math.floor(clampedAnchor), 0, this.items.length - 1);
|
|
919
1882
|
const height = this._getItemHeight(position);
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1883
|
+
const offset = height > 0 ? -(clampedAnchor - position) * height : 0;
|
|
1884
|
+
this._commitListState({
|
|
1885
|
+
position,
|
|
1886
|
+
offset: Object.is(offset, -0) ? 0 : offset
|
|
1887
|
+
});
|
|
923
1888
|
}
|
|
924
1889
|
_getTargetAnchor(index, block) {
|
|
925
1890
|
const height = this._getItemHeight(index);
|
|
926
1891
|
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
927
1892
|
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);
|
|
1893
|
+
case "start": return this._getAnchorAtOffset(index, 0);
|
|
1894
|
+
case "center": return this._getAnchorAtOffset(index, height / 2 - viewportHeight / 2);
|
|
1895
|
+
case "end": return this._getAnchorAtOffset(index, height - viewportHeight);
|
|
931
1896
|
}
|
|
932
1897
|
}
|
|
933
1898
|
render(feedback) {
|
|
934
1899
|
const keepAnimating = this._prepareRender();
|
|
935
1900
|
const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
|
|
936
1901
|
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);
|
|
1902
|
+
const solution = this.#resolveVisibleWindow();
|
|
1903
|
+
const requestRedraw = this._renderVisibleWindow(solution.window, feedback);
|
|
1904
|
+
this._commitListState(solution.normalizedState);
|
|
994
1905
|
return this._finishRender(keepAnimating || requestRedraw);
|
|
995
1906
|
}
|
|
996
1907
|
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;
|
|
1908
|
+
return this._hittestVisibleWindow(this.#resolveVisibleWindow().window, test);
|
|
1014
1909
|
}
|
|
1015
1910
|
};
|
|
1016
1911
|
//#endregion
|
|
1017
|
-
export {
|
|
1912
|
+
export { BaseRenderer, ChatRenderer, DebugRenderer, Fixed, Flex, FlexItem, Group, ListState, MultilineText, PaddingBox, Place, Text, TimelineRenderer, VirtualizedRenderer, Wrapper, memoRenderItem, memoRenderItemBy };
|
|
1018
1913
|
|
|
1019
1914
|
//# sourceMappingURL=index.mjs.map
|