chat-layout 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +15 -0
- package/index.d.ts +201 -0
- package/index.d.ts.map +43 -0
- package/index.js +884 -0
- package/package.json +8 -0
- package/tsconfig.json +28 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 CodeHz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# chat-layout
|
|
2
|
+
|
|
3
|
+
To install dependencies:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bun install
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
To run:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun run index.ts
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This project was created using `bun init` in bun v1.1.37. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
declare module '.' {
|
|
2
|
+
export abstract class Group implements Node {
|
|
3
|
+
readonly children: Node[];
|
|
4
|
+
constructor(children: Node[]);
|
|
5
|
+
abstract measure(ctx: Context): Box;
|
|
6
|
+
abstract draw(ctx: Context, x: number, y: number): boolean;
|
|
7
|
+
abstract hittest(ctx: Context, test: HitTest): boolean;
|
|
8
|
+
get flex(): boolean;
|
|
9
|
+
invalidate(ctx: Context): void;
|
|
10
|
+
}
|
|
11
|
+
export class VStack extends Group {
|
|
12
|
+
readonly options: {
|
|
13
|
+
gap?: number;
|
|
14
|
+
};
|
|
15
|
+
constructor(children: Node[], options?: {
|
|
16
|
+
gap?: number;
|
|
17
|
+
});
|
|
18
|
+
measure(ctx: Context): Box;
|
|
19
|
+
draw(ctx: Context, x: number, y: number): boolean;
|
|
20
|
+
hittest(ctx: Context, test: HitTest): boolean;
|
|
21
|
+
}
|
|
22
|
+
export class HStack extends Group {
|
|
23
|
+
readonly children: Node[];
|
|
24
|
+
readonly options: {
|
|
25
|
+
reverse?: boolean;
|
|
26
|
+
gap?: number;
|
|
27
|
+
};
|
|
28
|
+
constructor(children: Node[], options?: {
|
|
29
|
+
reverse?: boolean;
|
|
30
|
+
gap?: number;
|
|
31
|
+
});
|
|
32
|
+
measure(ctx: Context): Box;
|
|
33
|
+
draw(ctx: Context, x: number, y: number): boolean;
|
|
34
|
+
hittest(ctx: Context, test: HitTest): boolean;
|
|
35
|
+
}
|
|
36
|
+
export class Wrapper implements Node {
|
|
37
|
+
readonly inner: Node;
|
|
38
|
+
constructor(inner: Node);
|
|
39
|
+
get flex(): boolean;
|
|
40
|
+
measure(ctx: Context): Box;
|
|
41
|
+
draw(ctx: Context, x: number, y: number): boolean;
|
|
42
|
+
hittest(ctx: Context, test: HitTest): boolean;
|
|
43
|
+
invalidate(ctx: Context): void;
|
|
44
|
+
}
|
|
45
|
+
export class PaddingBox extends Wrapper {
|
|
46
|
+
#private;
|
|
47
|
+
readonly padding: {
|
|
48
|
+
top?: number;
|
|
49
|
+
bottom?: number;
|
|
50
|
+
left?: number;
|
|
51
|
+
right?: number;
|
|
52
|
+
};
|
|
53
|
+
constructor(inner: Node, padding?: {
|
|
54
|
+
top?: number;
|
|
55
|
+
bottom?: number;
|
|
56
|
+
left?: number;
|
|
57
|
+
right?: number;
|
|
58
|
+
});
|
|
59
|
+
measure(ctx: Context): Box;
|
|
60
|
+
draw(ctx: Context, x: number, y: number): boolean;
|
|
61
|
+
hittest(ctx: Context, test: HitTest): boolean;
|
|
62
|
+
}
|
|
63
|
+
export class AlignBox extends Wrapper {
|
|
64
|
+
#private;
|
|
65
|
+
readonly options: {
|
|
66
|
+
alignment: "left" | "center" | "right";
|
|
67
|
+
};
|
|
68
|
+
constructor(inner: Node, options: {
|
|
69
|
+
alignment: "left" | "center" | "right";
|
|
70
|
+
});
|
|
71
|
+
measure(ctx: Context): Box;
|
|
72
|
+
draw(ctx: Context, x: number, y: number): boolean;
|
|
73
|
+
hittest(ctx: Context, test: HitTest): boolean;
|
|
74
|
+
}
|
|
75
|
+
export class MultilineText implements Node {
|
|
76
|
+
#private;
|
|
77
|
+
readonly text: string;
|
|
78
|
+
readonly options: {
|
|
79
|
+
lineHeight: number;
|
|
80
|
+
font: string;
|
|
81
|
+
alignment: "left" | "center" | "right";
|
|
82
|
+
style: DynValue<string>;
|
|
83
|
+
};
|
|
84
|
+
constructor(text: string, options: {
|
|
85
|
+
lineHeight: number;
|
|
86
|
+
font: string;
|
|
87
|
+
alignment: "left" | "center" | "right";
|
|
88
|
+
style: DynValue<string>;
|
|
89
|
+
});
|
|
90
|
+
get flex(): boolean;
|
|
91
|
+
measure(ctx: Context): Box;
|
|
92
|
+
draw(ctx: Context, x: number, y: number): boolean;
|
|
93
|
+
hittest(ctx: Context, test: HitTest): boolean;
|
|
94
|
+
}
|
|
95
|
+
export class Text implements Node {
|
|
96
|
+
#private;
|
|
97
|
+
readonly text: string;
|
|
98
|
+
readonly options: {
|
|
99
|
+
lineHeight: number;
|
|
100
|
+
font: string;
|
|
101
|
+
style: DynValue<string>;
|
|
102
|
+
};
|
|
103
|
+
constructor(text: string, options: {
|
|
104
|
+
lineHeight: number;
|
|
105
|
+
font: string;
|
|
106
|
+
style: DynValue<string>;
|
|
107
|
+
});
|
|
108
|
+
get flex(): boolean;
|
|
109
|
+
measure(ctx: Context): Box;
|
|
110
|
+
draw(ctx: Context, x: number, y: number): boolean;
|
|
111
|
+
hittest(ctx: Context, test: HitTest): boolean;
|
|
112
|
+
}
|
|
113
|
+
export class Fixed implements Node {
|
|
114
|
+
readonly width: number;
|
|
115
|
+
readonly height: number;
|
|
116
|
+
constructor(width: number, height: number);
|
|
117
|
+
get flex(): boolean;
|
|
118
|
+
measure(ctx: Context): Box;
|
|
119
|
+
draw(ctx: Context, x: number, y: number): boolean;
|
|
120
|
+
hittest(ctx: Context, test: HitTest): boolean;
|
|
121
|
+
}
|
|
122
|
+
export class BaseRenderer<O = {}> {
|
|
123
|
+
#private;
|
|
124
|
+
readonly options: RendererOptions & O;
|
|
125
|
+
graphics: CanvasRenderingContext2D;
|
|
126
|
+
protected get context(): Context;
|
|
127
|
+
constructor(graphics1: CanvasRenderingContext2D, options: RendererOptions & O);
|
|
128
|
+
invalidateNode(node: Node): void;
|
|
129
|
+
measureNode(node: Node, ctx?: Context): Box;
|
|
130
|
+
}
|
|
131
|
+
export class DebugRenderer extends BaseRenderer {
|
|
132
|
+
draw(node: Node): boolean;
|
|
133
|
+
hittest(node: Node, test: HitTest): boolean;
|
|
134
|
+
}
|
|
135
|
+
export function memoRenderItem<T extends {}>(renderItem: (item: T) => Node): ((item: T) => Node) & {
|
|
136
|
+
reset: (key: T) => boolean;
|
|
137
|
+
};
|
|
138
|
+
export abstract class VirtualizedRenderer<T extends {}> extends BaseRenderer<{
|
|
139
|
+
renderItem: (item: T) => Node;
|
|
140
|
+
}> {
|
|
141
|
+
protected offset: number;
|
|
142
|
+
protected position: number;
|
|
143
|
+
protected items: T[];
|
|
144
|
+
unshift(...items: T[]): T[];
|
|
145
|
+
unshiftAll(items: T[]): T[];
|
|
146
|
+
push(...items: T[]): T[];
|
|
147
|
+
pushAll(items: T[]): T[];
|
|
148
|
+
reset(): void;
|
|
149
|
+
applyScroll(delta: number): number;
|
|
150
|
+
get pos(): number;
|
|
151
|
+
get length(): number;
|
|
152
|
+
abstract render(): boolean;
|
|
153
|
+
abstract hittest(test: HitTest): boolean;
|
|
154
|
+
}
|
|
155
|
+
export class TimelineRenderer<T extends {}> extends VirtualizedRenderer<T> {
|
|
156
|
+
#private;
|
|
157
|
+
render(): boolean;
|
|
158
|
+
hittest(test: HitTest): boolean;
|
|
159
|
+
scrollToTop(): void;
|
|
160
|
+
}
|
|
161
|
+
export class ChatRenderer<T extends {}> extends VirtualizedRenderer<T> {
|
|
162
|
+
#private;
|
|
163
|
+
render(): boolean;
|
|
164
|
+
hittest(test: HitTest): boolean;
|
|
165
|
+
scrollToBottom(): void;
|
|
166
|
+
}
|
|
167
|
+
export type RendererOptions = {
|
|
168
|
+
splitText(text: string): string[];
|
|
169
|
+
};
|
|
170
|
+
export interface ContextHelper {
|
|
171
|
+
}
|
|
172
|
+
export interface Context extends RendererOptions, ContextHelper {
|
|
173
|
+
graphics: CanvasRenderingContext2D;
|
|
174
|
+
remainingWidth: number;
|
|
175
|
+
measureNode(node: Node): Box;
|
|
176
|
+
invalidateNode(node: Node): void;
|
|
177
|
+
with<T>(this: Context, cb: (g: CanvasRenderingContext2D) => T): T;
|
|
178
|
+
}
|
|
179
|
+
export type Box = {
|
|
180
|
+
width: number;
|
|
181
|
+
height: number;
|
|
182
|
+
};
|
|
183
|
+
export type HitTest = {
|
|
184
|
+
x: number;
|
|
185
|
+
y: number;
|
|
186
|
+
type: "click" | "auxclick" | "hover";
|
|
187
|
+
};
|
|
188
|
+
export interface Node {
|
|
189
|
+
measure(ctx: Context): Box;
|
|
190
|
+
draw(ctx: Context, x: number, y: number): boolean;
|
|
191
|
+
hittest(ctx: Context, test: HitTest): boolean;
|
|
192
|
+
invalidate?(ctx: Context): void;
|
|
193
|
+
readonly flex: boolean;
|
|
194
|
+
}
|
|
195
|
+
export type DynValue<T> = (T extends Function ? never : T | (() => T));
|
|
196
|
+
export function resolveDynValue<T>(value: DynValue<T>): T;
|
|
197
|
+
|
|
198
|
+
export {};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
//# sourceMappingURL=index.d.ts.map
|
package/index.d.ts.map
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"file": "index.d.ts",
|
|
4
|
+
"names": [
|
|
5
|
+
"Group",
|
|
6
|
+
"VStack",
|
|
7
|
+
"HStack",
|
|
8
|
+
"Wrapper",
|
|
9
|
+
"PaddingBox",
|
|
10
|
+
"AlignBox",
|
|
11
|
+
"MultilineText",
|
|
12
|
+
"Text",
|
|
13
|
+
"Fixed",
|
|
14
|
+
"BaseRenderer",
|
|
15
|
+
"DebugRenderer",
|
|
16
|
+
"memoRenderItem",
|
|
17
|
+
"VirtualizedRenderer",
|
|
18
|
+
"TimelineRenderer",
|
|
19
|
+
"ChatRenderer",
|
|
20
|
+
"RendererOptions",
|
|
21
|
+
"ContextHelper",
|
|
22
|
+
"Context",
|
|
23
|
+
"Box",
|
|
24
|
+
"HitTest",
|
|
25
|
+
"Node",
|
|
26
|
+
"DynValue",
|
|
27
|
+
"resolveDynValue"
|
|
28
|
+
],
|
|
29
|
+
"sources": [
|
|
30
|
+
"../nodes.civet.d.ts",
|
|
31
|
+
"../renderer.civet.d.ts",
|
|
32
|
+
"../types.civet.d.ts",
|
|
33
|
+
"../utils.civet.d.ts"
|
|
34
|
+
],
|
|
35
|
+
"sourcesContent": [
|
|
36
|
+
null,
|
|
37
|
+
null,
|
|
38
|
+
null,
|
|
39
|
+
null
|
|
40
|
+
],
|
|
41
|
+
"mappings": ";uBAE8BA,KAAKA;;;;;;;;;cASdC,MAAMA;;;;;;;;;;;cAWNC,MAAMA;;;;;;;;;;;;;;cAcNC,OAAOA;;;;;;;;;cASPC,UAAUA;;;;;;;;;;;;;;;;;;cAkBVC,QAAQA;;;;;;;;;;;;cAYRC,aAAaA;;;;;;;;;;;;;;;;;;;;cAoBbC,IAAIA;;;;;;;;;;;;;;;;;;cAkBJC,KAAKA;;;;;;;;;cChHLC,YAAYA;;;;;;;;;cASZC,aAAaA;;;;iBAIVC,cAAcA;;;uBAGRC,mBAAmBA;;;;;;;;;;;;;;;;;cAiB5BC,gBAAgBA;;;;;;cAMhBC,YAAYA;;;;;;aCxCrBC,eAAeA;;;kBAGVC,aAAaA;;kBAEbC,OAAOA;;;;;;;aAOZC,GAAGA;;;;aAIHC,OAAOA;;;;;kBAKFC,IAAIA;;;;;;;aCnBTC,QAAQA;iBACIC,eAAeA",
|
|
42
|
+
"ignoreList": []
|
|
43
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,884 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// text.civet
|
|
3
|
+
function layoutFirstLine(ctx, text, maxWidth) {
|
|
4
|
+
const line = text.replaceAll(/[^\S\n]+/g, " ").split(`
|
|
5
|
+
`, 1)[0].trim();
|
|
6
|
+
const { width: textWidth, actualBoundingBoxAscent: shift, actualBoundingBoxDescent: lineHeight } = ctx.graphics.measureText(line);
|
|
7
|
+
if (textWidth <= maxWidth) {
|
|
8
|
+
return { width: textWidth, text: line, shift, lineHeight };
|
|
9
|
+
} else {
|
|
10
|
+
const { width, text: text2 } = splitToFitText(ctx, line, maxWidth, textWidth);
|
|
11
|
+
return { width, text: text2, shift, lineHeight };
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function layoutText(ctx, text, maxWidth) {
|
|
15
|
+
const results = [];
|
|
16
|
+
for (let ref = text.replaceAll(/[^\S\n]+/g, " ").split(`
|
|
17
|
+
`), i = 0, len = ref.length;i < len; i++) {
|
|
18
|
+
const line = ref[i];
|
|
19
|
+
const trimed = line.trim();
|
|
20
|
+
if (!trimed) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
results.push(trimed);
|
|
24
|
+
}
|
|
25
|
+
const inputLines = results;
|
|
26
|
+
if (inputLines.length === 0) {
|
|
27
|
+
return { width: 0, lines: [] };
|
|
28
|
+
}
|
|
29
|
+
let width = 0;
|
|
30
|
+
const lines = [];
|
|
31
|
+
for (let i1 = 0, len1 = inputLines.length;i1 < len1; i1++) {
|
|
32
|
+
let line = inputLines[i1];
|
|
33
|
+
let { width: textWidth, actualBoundingBoxAscent: shift, actualBoundingBoxDescent: lineHeight } = ctx.graphics.measureText(line);
|
|
34
|
+
if (textWidth <= maxWidth) {
|
|
35
|
+
width = Math.max(width, textWidth);
|
|
36
|
+
lines.push({ width: textWidth, text: line, shift, lineHeight });
|
|
37
|
+
} else {
|
|
38
|
+
while (textWidth > maxWidth) {
|
|
39
|
+
const splited = splitToFitText(ctx, line, maxWidth, textWidth);
|
|
40
|
+
width = Math.max(width, splited.width);
|
|
41
|
+
lines.push({ text: splited.text, width: splited.width, shift, lineHeight });
|
|
42
|
+
line = splited.rest;
|
|
43
|
+
({ width: textWidth } = ctx.graphics.measureText(line));
|
|
44
|
+
}
|
|
45
|
+
if (textWidth > 0) {
|
|
46
|
+
width = Math.max(width, textWidth);
|
|
47
|
+
lines.push({ width: textWidth, text: line, shift, lineHeight });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { width, lines };
|
|
52
|
+
}
|
|
53
|
+
function splitToFitText(ctx, text, width, totalWidth) {
|
|
54
|
+
const arr = ctx.splitText(text);
|
|
55
|
+
let guess = Math.floor(width / totalWidth * arr.length);
|
|
56
|
+
let guessText;
|
|
57
|
+
let { width: guessWidth } = ctx.graphics.measureText(guessText = arr.slice(0, guess).join(""));
|
|
58
|
+
while (!(guessWidth >= width)) {
|
|
59
|
+
guess++;
|
|
60
|
+
({ width: guessWidth } = ctx.graphics.measureText(guessText = arr.slice(0, guess).join("")));
|
|
61
|
+
}
|
|
62
|
+
while (guessWidth > width) {
|
|
63
|
+
const lastSpace = arr.lastIndexOf(" ", guess - 1);
|
|
64
|
+
if (lastSpace > 0) {
|
|
65
|
+
guess = lastSpace;
|
|
66
|
+
} else {
|
|
67
|
+
guess--;
|
|
68
|
+
}
|
|
69
|
+
({ width: guessWidth } = ctx.graphics.measureText(guessText = arr.slice(0, guess).join("")));
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
text: guessText,
|
|
73
|
+
width: guessWidth,
|
|
74
|
+
rest: arr.slice(guess).join("").trimStart()
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// utils.civet
|
|
79
|
+
function shallow(object) {
|
|
80
|
+
return Object.create(object);
|
|
81
|
+
}
|
|
82
|
+
function shallowMerge(object, other) {
|
|
83
|
+
return { __proto__: object, ...other };
|
|
84
|
+
}
|
|
85
|
+
function resolveDynValue(value) {
|
|
86
|
+
if (typeof value === "function") {
|
|
87
|
+
return value();
|
|
88
|
+
}
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// nodes.civet
|
|
93
|
+
class Group {
|
|
94
|
+
children;
|
|
95
|
+
constructor(children) {
|
|
96
|
+
this.children = children;
|
|
97
|
+
}
|
|
98
|
+
get flex() {
|
|
99
|
+
let results = false;
|
|
100
|
+
for (let ref = this.children, i = 0, len = ref.length;i < len; i++) {
|
|
101
|
+
const item = ref[i];
|
|
102
|
+
if (item.flex) {
|
|
103
|
+
results = true;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return results;
|
|
108
|
+
}
|
|
109
|
+
invalidate(ctx) {
|
|
110
|
+
for (let ref1 = this.children, i1 = 0, len1 = ref1.length;i1 < len1; i1++) {
|
|
111
|
+
const child = ref1[i1];
|
|
112
|
+
ctx.invalidateNode(child);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class VStack extends Group {
|
|
118
|
+
options;
|
|
119
|
+
constructor(children, options = {}) {
|
|
120
|
+
super(children);
|
|
121
|
+
this.options = options;
|
|
122
|
+
}
|
|
123
|
+
measure(ctx) {
|
|
124
|
+
let width = 0;
|
|
125
|
+
let height = 0;
|
|
126
|
+
for (let ref2 = this.children, i2 = 0, len2 = ref2.length;i2 < len2; i2++) {
|
|
127
|
+
const index = i2;
|
|
128
|
+
const child = ref2[i2];
|
|
129
|
+
if (this.options.gap != null && index !== 0) {
|
|
130
|
+
height += this.options.gap;
|
|
131
|
+
}
|
|
132
|
+
let result = shallow(ctx).measureNode(child);
|
|
133
|
+
height += result.height;
|
|
134
|
+
width = Math.max(width, result.width);
|
|
135
|
+
}
|
|
136
|
+
ctx.remainingWidth -= width;
|
|
137
|
+
return { width, height };
|
|
138
|
+
}
|
|
139
|
+
draw(ctx, x, y) {
|
|
140
|
+
let result = false;
|
|
141
|
+
for (let ref3 = this.children, i3 = 0, len3 = ref3.length;i3 < len3; i3++) {
|
|
142
|
+
const index = i3;
|
|
143
|
+
const child = ref3[i3];
|
|
144
|
+
if (this.options.gap != null && index !== 0) {
|
|
145
|
+
y += this.options.gap;
|
|
146
|
+
}
|
|
147
|
+
const request_redraw = child.draw(ctx, x, y);
|
|
148
|
+
result ||= request_redraw;
|
|
149
|
+
let { height } = shallow(ctx).measureNode(child);
|
|
150
|
+
y += height;
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
hittest(ctx, test) {
|
|
155
|
+
let y = 0;
|
|
156
|
+
for (let ref4 = this.children, i4 = 0, len4 = ref4.length;i4 < len4; i4++) {
|
|
157
|
+
const index = i4;
|
|
158
|
+
const child = ref4[i4];
|
|
159
|
+
if (this.options.gap != null && index !== 0) {
|
|
160
|
+
y += this.options.gap;
|
|
161
|
+
}
|
|
162
|
+
const curctx = shallow(ctx);
|
|
163
|
+
let { height } = curctx.measureNode(child);
|
|
164
|
+
if (test.y >= y && test.y < y + height) {
|
|
165
|
+
return child.hittest(curctx, shallowMerge(test, {
|
|
166
|
+
y: test.y - y
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
y += height;
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
class HStack extends Group {
|
|
176
|
+
children;
|
|
177
|
+
options;
|
|
178
|
+
constructor(children, options = {}) {
|
|
179
|
+
super(children);
|
|
180
|
+
this.children = children;
|
|
181
|
+
this.options = options;
|
|
182
|
+
}
|
|
183
|
+
measure(ctx) {
|
|
184
|
+
let width = 0;
|
|
185
|
+
let height = 0;
|
|
186
|
+
let firstflex;
|
|
187
|
+
for (let ref5 = this.children, i5 = 0, len5 = ref5.length;i5 < len5; i5++) {
|
|
188
|
+
const index = i5;
|
|
189
|
+
const child = ref5[i5];
|
|
190
|
+
if (this.options.gap != null && index !== 0) {
|
|
191
|
+
width += this.options.gap;
|
|
192
|
+
}
|
|
193
|
+
if (firstflex == null && child.flex) {
|
|
194
|
+
firstflex = child;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const curctx = shallow(ctx);
|
|
198
|
+
curctx.remainingWidth = ctx.remainingWidth - width;
|
|
199
|
+
const result = curctx.measureNode(child);
|
|
200
|
+
width += result.width;
|
|
201
|
+
height = Math.max(height, result.height);
|
|
202
|
+
}
|
|
203
|
+
if (firstflex != null) {
|
|
204
|
+
const curctx = shallow(ctx);
|
|
205
|
+
curctx.remainingWidth = ctx.remainingWidth - width;
|
|
206
|
+
const result = curctx.measureNode(firstflex);
|
|
207
|
+
width += result.width;
|
|
208
|
+
height = Math.max(height, result.height);
|
|
209
|
+
}
|
|
210
|
+
return { width, height };
|
|
211
|
+
}
|
|
212
|
+
draw(ctx, x, y) {
|
|
213
|
+
let result = false;
|
|
214
|
+
if (this.options.reverse) {
|
|
215
|
+
x += ctx.measureNode(this).width;
|
|
216
|
+
for (let ref6 = this.children, i6 = 0, len6 = ref6.length;i6 < len6; i6++) {
|
|
217
|
+
const index = i6;
|
|
218
|
+
const child = ref6[i6];
|
|
219
|
+
if (this.options.gap != null && index !== 0) {
|
|
220
|
+
x -= this.options.gap;
|
|
221
|
+
}
|
|
222
|
+
const { width } = shallow(ctx).measureNode(child);
|
|
223
|
+
x -= width;
|
|
224
|
+
const request_redraw = child.draw(ctx, x, y);
|
|
225
|
+
result ||= request_redraw;
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
for (let ref7 = this.children, i7 = 0, len7 = ref7.length;i7 < len7; i7++) {
|
|
229
|
+
const index = i7;
|
|
230
|
+
const child = ref7[i7];
|
|
231
|
+
if (this.options.gap != null && index !== 0) {
|
|
232
|
+
x += this.options.gap;
|
|
233
|
+
}
|
|
234
|
+
const request_redraw = child.draw(ctx, x, y);
|
|
235
|
+
result ||= request_redraw;
|
|
236
|
+
const { width } = shallow(ctx).measureNode(child);
|
|
237
|
+
x += width;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
hittest(ctx, test) {
|
|
243
|
+
if (this.options.reverse) {
|
|
244
|
+
let x = ctx.measureNode(this).width;
|
|
245
|
+
for (let ref8 = this.children, i8 = 0, len8 = ref8.length;i8 < len8; i8++) {
|
|
246
|
+
const index = i8;
|
|
247
|
+
const child = ref8[i8];
|
|
248
|
+
if (this.options.gap != null && index !== 0) {
|
|
249
|
+
x -= this.options.gap;
|
|
250
|
+
}
|
|
251
|
+
const curctx = shallow(ctx);
|
|
252
|
+
const { width } = curctx.measureNode(child);
|
|
253
|
+
x -= width;
|
|
254
|
+
let ref9;
|
|
255
|
+
if (x <= (ref9 = test.x) && ref9 <= x + width) {
|
|
256
|
+
return child.hittest(curctx, shallowMerge(test, {
|
|
257
|
+
x: test.x - x
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
let x = 0;
|
|
263
|
+
for (let ref10 = this.children, i9 = 0, len9 = ref10.length;i9 < len9; i9++) {
|
|
264
|
+
const index = i9;
|
|
265
|
+
const child = ref10[i9];
|
|
266
|
+
if (this.options.gap != null && index !== 0) {
|
|
267
|
+
x += this.options.gap;
|
|
268
|
+
}
|
|
269
|
+
const curctx = shallow(ctx);
|
|
270
|
+
const { width } = curctx.measureNode(child);
|
|
271
|
+
let ref11;
|
|
272
|
+
if (x <= (ref11 = test.x) && ref11 <= x + width) {
|
|
273
|
+
return child.hittest(curctx, shallowMerge(test, {
|
|
274
|
+
x: test.x - x
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
x += width;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
class Wrapper {
|
|
285
|
+
inner;
|
|
286
|
+
constructor(inner) {
|
|
287
|
+
this.inner = inner;
|
|
288
|
+
}
|
|
289
|
+
get flex() {
|
|
290
|
+
return this.inner.flex;
|
|
291
|
+
}
|
|
292
|
+
measure(ctx) {
|
|
293
|
+
return this.inner.measure(ctx);
|
|
294
|
+
}
|
|
295
|
+
draw(ctx, x, y) {
|
|
296
|
+
return this.inner.draw(ctx, x, y);
|
|
297
|
+
}
|
|
298
|
+
hittest(ctx, test) {
|
|
299
|
+
return this.inner.hittest(ctx, test);
|
|
300
|
+
}
|
|
301
|
+
invalidate(ctx) {
|
|
302
|
+
ctx.invalidateNode(this.inner);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
class PaddingBox extends Wrapper {
|
|
307
|
+
padding;
|
|
308
|
+
constructor(inner, padding = {}) {
|
|
309
|
+
super(inner);
|
|
310
|
+
this.padding = padding;
|
|
311
|
+
}
|
|
312
|
+
get #top() {
|
|
313
|
+
return this.padding.top ?? 0;
|
|
314
|
+
}
|
|
315
|
+
get #bottom() {
|
|
316
|
+
return this.padding.bottom ?? 0;
|
|
317
|
+
}
|
|
318
|
+
get #left() {
|
|
319
|
+
return this.padding.left ?? 0;
|
|
320
|
+
}
|
|
321
|
+
get #right() {
|
|
322
|
+
return this.padding.right ?? 0;
|
|
323
|
+
}
|
|
324
|
+
measure(ctx) {
|
|
325
|
+
ctx = shallow(ctx);
|
|
326
|
+
ctx.remainingWidth -= this.#left + this.#right;
|
|
327
|
+
const { width, height } = ctx.measureNode(this.inner);
|
|
328
|
+
return {
|
|
329
|
+
width: width + this.#left + this.#right,
|
|
330
|
+
height: height + this.#top + this.#bottom
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
draw(ctx, x, y) {
|
|
334
|
+
return this.inner.draw(ctx, x + this.#left, y + this.#top);
|
|
335
|
+
}
|
|
336
|
+
hittest(ctx, test) {
|
|
337
|
+
const { width, height } = ctx.measureNode(this.inner);
|
|
338
|
+
let ref12;
|
|
339
|
+
let ref13;
|
|
340
|
+
if (0 <= (ref12 = test.x - this.#left) && ref12 < width && (0 <= (ref13 = test.y - this.#top) && ref13 < height)) {
|
|
341
|
+
return this.inner.hittest(ctx, shallowMerge(test, {
|
|
342
|
+
x: test.x - this.#left,
|
|
343
|
+
y: test.y - this.#top
|
|
344
|
+
}));
|
|
345
|
+
}
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
class AlignBox extends Wrapper {
|
|
351
|
+
options;
|
|
352
|
+
#shift = 0;
|
|
353
|
+
constructor(inner, options) {
|
|
354
|
+
super(inner);
|
|
355
|
+
this.options = options;
|
|
356
|
+
}
|
|
357
|
+
measure(ctx) {
|
|
358
|
+
const { width, height } = ctx.measureNode(this.inner);
|
|
359
|
+
let ref14;
|
|
360
|
+
switch (this.options.alignment) {
|
|
361
|
+
case "center": {
|
|
362
|
+
ref14 = (ctx.remainingWidth - width) / 2;
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
case "right": {
|
|
366
|
+
ref14 = ctx.remainingWidth - width;
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
default: {
|
|
370
|
+
ref14 = 0;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
this.#shift = ref14;
|
|
374
|
+
return {
|
|
375
|
+
width: ctx.remainingWidth,
|
|
376
|
+
height
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
draw(ctx, x, y) {
|
|
380
|
+
return this.inner.draw(ctx, x + this.#shift, y);
|
|
381
|
+
}
|
|
382
|
+
hittest(ctx, test) {
|
|
383
|
+
const { width } = ctx.measureNode(this.inner);
|
|
384
|
+
let ref15;
|
|
385
|
+
if (0 <= (ref15 = test.x - this.#shift) && ref15 < width) {
|
|
386
|
+
return this.inner.hittest(ctx, shallowMerge(test, {
|
|
387
|
+
x: test.x - this.#shift
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
class MultilineText {
|
|
395
|
+
text;
|
|
396
|
+
options;
|
|
397
|
+
#width = 0;
|
|
398
|
+
#lines = [];
|
|
399
|
+
constructor(text, options) {
|
|
400
|
+
this.text = text;
|
|
401
|
+
this.options = options;
|
|
402
|
+
}
|
|
403
|
+
get flex() {
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
406
|
+
measure(ctx) {
|
|
407
|
+
return ctx.with((g) => {
|
|
408
|
+
g.font = this.options.font;
|
|
409
|
+
const { width: width1, lines } = layoutText(ctx, this.text, ctx.remainingWidth);
|
|
410
|
+
this.#width = width1;
|
|
411
|
+
this.#lines = lines;
|
|
412
|
+
return { width: this.#width, height: this.#lines.length * this.options.lineHeight };
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
draw(ctx, x, y) {
|
|
416
|
+
return ctx.with((g) => {
|
|
417
|
+
g.font = this.options.font;
|
|
418
|
+
g.fillStyle = resolveDynValue(this.options.style);
|
|
419
|
+
switch (this.options.alignment) {
|
|
420
|
+
case "left": {
|
|
421
|
+
for (let ref16 = this.#lines, i10 = 0, len10 = ref16.length;i10 < len10; i10++) {
|
|
422
|
+
const { text, shift, lineHeight } = ref16[i10];
|
|
423
|
+
g.fillText(text, x, y + shift + (this.options.lineHeight - lineHeight) / 2);
|
|
424
|
+
y += this.options.lineHeight;
|
|
425
|
+
}
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
case "right": {
|
|
429
|
+
x += this.#width;
|
|
430
|
+
g.textAlign = "right";
|
|
431
|
+
for (let ref17 = this.#lines, i11 = 0, len11 = ref17.length;i11 < len11; i11++) {
|
|
432
|
+
const { text, shift, lineHeight } = ref17[i11];
|
|
433
|
+
g.fillText(text, x, y + shift + (this.options.lineHeight - lineHeight) / 2);
|
|
434
|
+
y += this.options.lineHeight;
|
|
435
|
+
}
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
case "center": {
|
|
439
|
+
x += this.#width / 2;
|
|
440
|
+
g.textAlign = "center";
|
|
441
|
+
for (let ref18 = this.#lines, i12 = 0, len12 = ref18.length;i12 < len12; i12++) {
|
|
442
|
+
const { text, shift, lineHeight } = ref18[i12];
|
|
443
|
+
g.fillText(text, x, y + shift + (this.options.lineHeight - lineHeight) / 2);
|
|
444
|
+
y += this.options.lineHeight;
|
|
445
|
+
}
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return false;
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
hittest(ctx, test) {
|
|
453
|
+
return false;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
class Text {
|
|
458
|
+
text;
|
|
459
|
+
options;
|
|
460
|
+
constructor(text, options) {
|
|
461
|
+
this.text = text;
|
|
462
|
+
this.options = options;
|
|
463
|
+
}
|
|
464
|
+
#width = 0;
|
|
465
|
+
#text = "";
|
|
466
|
+
#shift = 0;
|
|
467
|
+
#lineHeight = 0;
|
|
468
|
+
get flex() {
|
|
469
|
+
return false;
|
|
470
|
+
}
|
|
471
|
+
measure(ctx) {
|
|
472
|
+
return ctx.with((g) => {
|
|
473
|
+
g.font = this.options.font;
|
|
474
|
+
const { width: width2, text: text1, shift: shift1, lineHeight: lineHeight1 } = layoutFirstLine(ctx, this.text, ctx.remainingWidth);
|
|
475
|
+
this.#width = width2;
|
|
476
|
+
this.#text = text1;
|
|
477
|
+
this.#shift = shift1;
|
|
478
|
+
this.#lineHeight = lineHeight1;
|
|
479
|
+
return { width: this.#width, height: this.options.lineHeight };
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
draw(ctx, x, y) {
|
|
483
|
+
return ctx.with((g) => {
|
|
484
|
+
g.font = this.options.font;
|
|
485
|
+
g.fillStyle = resolveDynValue(this.options.style);
|
|
486
|
+
g.fillText(this.#text, x, y + this.#shift + (this.options.lineHeight - this.#lineHeight) / 2);
|
|
487
|
+
return false;
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
hittest(ctx, test) {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
class Fixed {
|
|
496
|
+
width;
|
|
497
|
+
height;
|
|
498
|
+
constructor(width, height) {
|
|
499
|
+
this.width = width;
|
|
500
|
+
this.height = height;
|
|
501
|
+
}
|
|
502
|
+
get flex() {
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
measure(ctx) {
|
|
506
|
+
return { width: this.width, height: this.height };
|
|
507
|
+
}
|
|
508
|
+
draw(ctx, x, y) {
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
hittest(ctx, test) {
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// renderer.civet
|
|
516
|
+
var concatAssign = (lhs, rhs) => (rhs?.[Symbol.isConcatSpreadable] ?? Array.isArray(rhs) ? lhs.push.apply(lhs, rhs) : lhs.push(rhs), lhs);
|
|
517
|
+
|
|
518
|
+
class BaseRenderer {
|
|
519
|
+
options;
|
|
520
|
+
graphics;
|
|
521
|
+
#ctx;
|
|
522
|
+
#lastWidth;
|
|
523
|
+
#cache = new WeakMap;
|
|
524
|
+
get context() {
|
|
525
|
+
return shallow(this.#ctx);
|
|
526
|
+
}
|
|
527
|
+
constructor(graphics1, options) {
|
|
528
|
+
this.options = options;
|
|
529
|
+
this.graphics = graphics1;
|
|
530
|
+
this.graphics.textBaseline = "top";
|
|
531
|
+
this.graphics.textRendering = "optimizeLegibility";
|
|
532
|
+
const self = this;
|
|
533
|
+
this.#ctx = {
|
|
534
|
+
graphics: this.graphics,
|
|
535
|
+
get remainingWidth() {
|
|
536
|
+
return this.graphics.canvas.clientWidth;
|
|
537
|
+
},
|
|
538
|
+
set remainingWidth(value) {
|
|
539
|
+
Object.defineProperty(this, "remainingWidth", { value, writable: true });
|
|
540
|
+
},
|
|
541
|
+
measureNode(node) {
|
|
542
|
+
return self.measureNode(node, this);
|
|
543
|
+
},
|
|
544
|
+
invalidateNode: this.invalidateNode.bind(this),
|
|
545
|
+
with(cb) {
|
|
546
|
+
this.graphics.save();
|
|
547
|
+
try {
|
|
548
|
+
return cb(this.graphics);
|
|
549
|
+
} finally {
|
|
550
|
+
this.graphics.restore();
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
splitText(text) {
|
|
554
|
+
return options.splitText(text);
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
this.#lastWidth = this.graphics.canvas.clientWidth;
|
|
558
|
+
}
|
|
559
|
+
invalidateNode(node) {
|
|
560
|
+
this.#cache.delete(node);
|
|
561
|
+
node.invalidate?.(this.context);
|
|
562
|
+
}
|
|
563
|
+
measureNode(node, ctx) {
|
|
564
|
+
let ref;
|
|
565
|
+
if (this.#lastWidth != this.graphics.canvas.clientWidth) {
|
|
566
|
+
this.#cache = new WeakMap;
|
|
567
|
+
} else if ((ref = this.#cache.get(node)) != null) {
|
|
568
|
+
const result2 = ref;
|
|
569
|
+
return result2;
|
|
570
|
+
}
|
|
571
|
+
const result = node.measure(ctx ?? this.context);
|
|
572
|
+
this.#cache.set(node, result);
|
|
573
|
+
return result;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
class DebugRenderer extends BaseRenderer {
|
|
578
|
+
draw(node) {
|
|
579
|
+
const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
|
|
580
|
+
this.graphics.clearRect(0, 0, viewportWidth, viewportHeight);
|
|
581
|
+
return node.draw(this.context, 0, 0);
|
|
582
|
+
}
|
|
583
|
+
hittest(node, test) {
|
|
584
|
+
return node.hittest(this.context, test);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
function memoRenderItem(renderItem) {
|
|
588
|
+
const cache = new WeakMap;
|
|
589
|
+
function fn(item) {
|
|
590
|
+
let ref1;
|
|
591
|
+
if ((ref1 = cache.get(item)) != null) {
|
|
592
|
+
const result2 = ref1;
|
|
593
|
+
return result2;
|
|
594
|
+
}
|
|
595
|
+
const result = renderItem(item);
|
|
596
|
+
cache.set(item, result);
|
|
597
|
+
return result;
|
|
598
|
+
}
|
|
599
|
+
return Object.assign(fn, { reset: cache.delete.bind(cache) });
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
class VirtualizedRenderer extends BaseRenderer {
|
|
603
|
+
offset = 0;
|
|
604
|
+
position = NaN;
|
|
605
|
+
items = [];
|
|
606
|
+
unshift(...items) {
|
|
607
|
+
return this.unshiftAll(items);
|
|
608
|
+
}
|
|
609
|
+
unshiftAll(items) {
|
|
610
|
+
this.position += items.length;
|
|
611
|
+
return this.items = items.concat(this.items);
|
|
612
|
+
}
|
|
613
|
+
push(...items) {
|
|
614
|
+
return this.pushAll(items);
|
|
615
|
+
}
|
|
616
|
+
pushAll(items) {
|
|
617
|
+
return concatAssign(this.items, items);
|
|
618
|
+
}
|
|
619
|
+
reset() {
|
|
620
|
+
this.items = [];
|
|
621
|
+
this.offset = 0;
|
|
622
|
+
this.position = -1;
|
|
623
|
+
}
|
|
624
|
+
applyScroll(delta) {
|
|
625
|
+
return this.offset += delta;
|
|
626
|
+
}
|
|
627
|
+
get pos() {
|
|
628
|
+
return this.position;
|
|
629
|
+
}
|
|
630
|
+
get length() {
|
|
631
|
+
return this.items.length;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
class TimelineRenderer extends VirtualizedRenderer {
|
|
636
|
+
#action = "idle";
|
|
637
|
+
render() {
|
|
638
|
+
const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
|
|
639
|
+
this.graphics.clearRect(0, 0, viewportWidth, viewportHeight);
|
|
640
|
+
let drawlength = 0;
|
|
641
|
+
if (isNaN(this.position)) {
|
|
642
|
+
this.position = 0;
|
|
643
|
+
}
|
|
644
|
+
if (this.offset > 0) {
|
|
645
|
+
if (this.position == 0) {
|
|
646
|
+
this.offset = 0;
|
|
647
|
+
} else {
|
|
648
|
+
for (let i1 = this.position - 1;i1 >= 0; --i1) {
|
|
649
|
+
const i = i1;
|
|
650
|
+
const item = this.items[i];
|
|
651
|
+
const node = this.options.renderItem(item);
|
|
652
|
+
const { height } = this.measureNode(node);
|
|
653
|
+
this.position = i;
|
|
654
|
+
this.offset -= height;
|
|
655
|
+
if (this.offset <= 0) {
|
|
656
|
+
break;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (this.position == 0 && this.offset > 0) {
|
|
660
|
+
this.offset = 0;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
let y = this.offset;
|
|
665
|
+
const drawlist = [];
|
|
666
|
+
for (let end = this.items.length, i2 = this.position;i2 < end; ++i2) {
|
|
667
|
+
const i = i2;
|
|
668
|
+
const item = this.items[i];
|
|
669
|
+
const node = this.options.renderItem(item);
|
|
670
|
+
const { height } = this.measureNode(node);
|
|
671
|
+
if (y + height > 0) {
|
|
672
|
+
drawlist.push([node, y]);
|
|
673
|
+
drawlength += height;
|
|
674
|
+
} else {
|
|
675
|
+
this.offset += height;
|
|
676
|
+
this.position = i + 1;
|
|
677
|
+
}
|
|
678
|
+
y += height;
|
|
679
|
+
if (y >= viewportHeight) {
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
let shift = 0;
|
|
684
|
+
if (y < viewportHeight) {
|
|
685
|
+
if (this.position == 0 && drawlength < viewportHeight) {
|
|
686
|
+
shift = -this.offset;
|
|
687
|
+
this.offset = 0;
|
|
688
|
+
} else {
|
|
689
|
+
shift = viewportHeight - y;
|
|
690
|
+
y = this.offset += shift;
|
|
691
|
+
let lastidx = -1;
|
|
692
|
+
for (let i3 = this.position - 1;i3 >= 0; --i3) {
|
|
693
|
+
const i = i3;
|
|
694
|
+
const item = this.items[lastidx = i];
|
|
695
|
+
const node = this.options.renderItem(item);
|
|
696
|
+
const { height } = this.measureNode(node);
|
|
697
|
+
drawlength += height;
|
|
698
|
+
y -= height;
|
|
699
|
+
drawlist.push([node, y - shift]);
|
|
700
|
+
if (y < 0) {
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
if (lastidx == 0 && drawlength < viewportHeight) {
|
|
705
|
+
shift = -drawlist[drawlist.length - 1][1];
|
|
706
|
+
this.position = 0;
|
|
707
|
+
this.offset = 0;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
let result = false;
|
|
712
|
+
for (let i4 = 0, len = drawlist.length;i4 < len; i4++) {
|
|
713
|
+
const [node, y2] = drawlist[i4];
|
|
714
|
+
const request_redraw = node.draw(this.context, 0, y2 + shift);
|
|
715
|
+
result ||= request_redraw;
|
|
716
|
+
}
|
|
717
|
+
if (this.#action === "scrollToTop") {
|
|
718
|
+
if (this.position === 0 && this.offset === 0) {
|
|
719
|
+
this.#action = "idle";
|
|
720
|
+
} else {
|
|
721
|
+
this.applyScroll(5 * (this.position + 1));
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return result;
|
|
726
|
+
}
|
|
727
|
+
hittest(test) {
|
|
728
|
+
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
729
|
+
let y = this.offset;
|
|
730
|
+
for (let end1 = this.items.length, i5 = this.position;i5 < end1; ++i5) {
|
|
731
|
+
const i = i5;
|
|
732
|
+
const item = this.items[i];
|
|
733
|
+
const node = this.options.renderItem(item);
|
|
734
|
+
const { height } = this.measureNode(node);
|
|
735
|
+
if (test.y < y + height) {
|
|
736
|
+
return node.hittest(this.context, shallowMerge(test, { y: test.y - y }));
|
|
737
|
+
}
|
|
738
|
+
y += height;
|
|
739
|
+
if (y >= viewportHeight) {
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
scrollToTop() {
|
|
746
|
+
this.#action = "scrollToTop";
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
class ChatRenderer extends VirtualizedRenderer {
|
|
751
|
+
#action = "idle";
|
|
752
|
+
render() {
|
|
753
|
+
const { clientWidth: viewportWidth, clientHeight: viewportHeight } = this.graphics.canvas;
|
|
754
|
+
this.graphics.clearRect(0, 0, viewportWidth, viewportHeight);
|
|
755
|
+
let drawlength = 0;
|
|
756
|
+
if (isNaN(this.position)) {
|
|
757
|
+
this.position = this.items.length - 1;
|
|
758
|
+
}
|
|
759
|
+
if (this.offset < 0) {
|
|
760
|
+
if (this.position == this.items.length - 1) {
|
|
761
|
+
this.offset = 0;
|
|
762
|
+
} else {
|
|
763
|
+
for (let end2 = this.items.length, i6 = this.position + 1;i6 < end2; ++i6) {
|
|
764
|
+
const i = i6;
|
|
765
|
+
const item = this.items[i];
|
|
766
|
+
const node = this.options.renderItem(item);
|
|
767
|
+
const { height } = this.measureNode(node);
|
|
768
|
+
this.position = i;
|
|
769
|
+
this.offset += height;
|
|
770
|
+
if (this.offset > 0) {
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
let y = viewportHeight + this.offset;
|
|
777
|
+
const drawlist = [];
|
|
778
|
+
for (let i7 = this.position;i7 >= 0; --i7) {
|
|
779
|
+
const i = i7;
|
|
780
|
+
const item = this.items[i];
|
|
781
|
+
const node = this.options.renderItem(item);
|
|
782
|
+
const { height } = this.measureNode(node);
|
|
783
|
+
y -= height;
|
|
784
|
+
if (y <= viewportHeight) {
|
|
785
|
+
drawlist.push([node, y]);
|
|
786
|
+
drawlength += height;
|
|
787
|
+
} else {
|
|
788
|
+
this.offset -= height;
|
|
789
|
+
this.position = i - 1;
|
|
790
|
+
}
|
|
791
|
+
if (y < 0) {
|
|
792
|
+
break;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
let shift = 0;
|
|
796
|
+
if (y > 0) {
|
|
797
|
+
shift = -y;
|
|
798
|
+
if (drawlength < viewportHeight) {
|
|
799
|
+
y = drawlength;
|
|
800
|
+
for (let end3 = this.items.length, i8 = this.position + 1;i8 < end3; ++i8) {
|
|
801
|
+
const i = i8;
|
|
802
|
+
const item = this.items[i];
|
|
803
|
+
const node = this.options.renderItem(item);
|
|
804
|
+
const { height } = this.measureNode(node);
|
|
805
|
+
drawlist.push([node, y - shift]);
|
|
806
|
+
y = drawlength += height;
|
|
807
|
+
this.position = i;
|
|
808
|
+
if (y >= viewportHeight) {
|
|
809
|
+
break;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (drawlength < viewportHeight) {
|
|
813
|
+
this.offset = 0;
|
|
814
|
+
} else {
|
|
815
|
+
this.offset = drawlength - viewportHeight;
|
|
816
|
+
}
|
|
817
|
+
} else {
|
|
818
|
+
this.offset = drawlength - viewportHeight;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
let result = false;
|
|
822
|
+
for (let i9 = 0, len1 = drawlist.length;i9 < len1; i9++) {
|
|
823
|
+
const [node, y2] = drawlist[i9];
|
|
824
|
+
const request_redraw = node.draw(this.context, 0, y2 + shift);
|
|
825
|
+
result ||= request_redraw;
|
|
826
|
+
}
|
|
827
|
+
if (this.#action === "scrollToBottom") {
|
|
828
|
+
if (this.position === this.items.length - 1 && this.offset === 0) {
|
|
829
|
+
this.#action = "idle";
|
|
830
|
+
} else {
|
|
831
|
+
this.applyScroll(-5 * (this.items.length - this.position));
|
|
832
|
+
return true;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return result;
|
|
836
|
+
}
|
|
837
|
+
hittest(test) {
|
|
838
|
+
const viewportHeight = this.graphics.canvas.clientHeight;
|
|
839
|
+
let drawlength = 0;
|
|
840
|
+
const results = [];
|
|
841
|
+
for (let i10 = this.position;i10 >= 0; --i10) {
|
|
842
|
+
const i = i10;
|
|
843
|
+
const item = this.items[i];
|
|
844
|
+
const node = this.options.renderItem(item);
|
|
845
|
+
const { height } = this.measureNode(node);
|
|
846
|
+
drawlength += height;
|
|
847
|
+
results.push([node, height]);
|
|
848
|
+
}
|
|
849
|
+
const heights = results;
|
|
850
|
+
let y = drawlength < viewportHeight ? drawlength : viewportHeight + this.offset;
|
|
851
|
+
if (test.y > y) {
|
|
852
|
+
return false;
|
|
853
|
+
}
|
|
854
|
+
for (let i11 = 0, len2 = heights.length;i11 < len2; i11++) {
|
|
855
|
+
const [node, height] = heights[i11];
|
|
856
|
+
y -= height;
|
|
857
|
+
if (test.y > y) {
|
|
858
|
+
return node.hittest(this.context, shallowMerge(test, { y: test.y - y }));
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
return false;
|
|
862
|
+
}
|
|
863
|
+
scrollToBottom() {
|
|
864
|
+
this.#action = "scrollToBottom";
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
export {
|
|
868
|
+
resolveDynValue,
|
|
869
|
+
memoRenderItem,
|
|
870
|
+
Wrapper,
|
|
871
|
+
VirtualizedRenderer,
|
|
872
|
+
VStack,
|
|
873
|
+
TimelineRenderer,
|
|
874
|
+
Text,
|
|
875
|
+
PaddingBox,
|
|
876
|
+
MultilineText,
|
|
877
|
+
HStack,
|
|
878
|
+
Group,
|
|
879
|
+
Fixed,
|
|
880
|
+
DebugRenderer,
|
|
881
|
+
ChatRenderer,
|
|
882
|
+
BaseRenderer,
|
|
883
|
+
AlignBox
|
|
884
|
+
};
|
package/package.json
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Enable latest features
|
|
4
|
+
"lib": ["ESNext", "DOM"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
|
|
11
|
+
// Bundler mode
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
// Best practices
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
|
|
22
|
+
// Some stricter flags (disabled by default)
|
|
23
|
+
"noUnusedLocals": false,
|
|
24
|
+
"noUnusedParameters": false,
|
|
25
|
+
"noPropertyAccessFromIndexSignature": false
|
|
26
|
+
},
|
|
27
|
+
"include": ["*.d.ts", "*.civet"]
|
|
28
|
+
}
|