@opentui/core 0.0.0-20250908-4906ddad
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/3d/SpriteResourceManager.d.ts +74 -0
- package/3d/SpriteUtils.d.ts +13 -0
- package/3d/TextureUtils.d.ts +24 -0
- package/3d/WGPURenderer.d.ts +59 -0
- package/3d/animation/ExplodingSpriteEffect.d.ts +71 -0
- package/3d/animation/PhysicsExplodingSpriteEffect.d.ts +76 -0
- package/3d/animation/SpriteAnimator.d.ts +124 -0
- package/3d/animation/SpriteParticleGenerator.d.ts +62 -0
- package/3d/canvas.d.ts +42 -0
- package/3d/index.d.ts +11 -0
- package/3d/physics/PlanckPhysicsAdapter.d.ts +19 -0
- package/3d/physics/RapierPhysicsAdapter.d.ts +19 -0
- package/3d/physics/physics-interface.d.ts +27 -0
- package/3d.d.ts +2 -0
- package/3d.js +33847 -0
- package/3d.js.map +154 -0
- package/LICENSE +21 -0
- package/README.md +43 -0
- package/Renderable.d.ts +266 -0
- package/animation/Timeline.d.ts +125 -0
- package/ansi.d.ts +28 -0
- package/buffer.d.ts +74 -0
- package/console.d.ts +86 -0
- package/index-d6kwx5pm.js +8837 -0
- package/index-d6kwx5pm.js.map +36 -0
- package/index.d.ts +12 -0
- package/index.js +3721 -0
- package/index.js.map +23 -0
- package/lib/KeyHandler.d.ts +11 -0
- package/lib/RGBA.d.ts +24 -0
- package/lib/TrackedNode.d.ts +36 -0
- package/lib/ascii.font.d.ts +301 -0
- package/lib/border.d.ts +47 -0
- package/lib/hast-styled-text.d.ts +38 -0
- package/lib/index.d.ts +11 -0
- package/lib/output.capture.d.ts +24 -0
- package/lib/parse.keypress.d.ts +14 -0
- package/lib/parse.mouse.d.ts +23 -0
- package/lib/selection.d.ts +63 -0
- package/lib/styled-text.d.ts +73 -0
- package/lib/word-jumps.d.ts +2 -0
- package/lib/yoga.options.d.ts +31 -0
- package/package.json +48 -0
- package/post/filters.d.ts +105 -0
- package/renderables/ASCIIFont.d.ts +43 -0
- package/renderables/Box.d.ts +70 -0
- package/renderables/FrameBuffer.d.ts +16 -0
- package/renderables/Input.d.ts +70 -0
- package/renderables/ScrollBar.d.ts +77 -0
- package/renderables/ScrollBox.d.ts +82 -0
- package/renderables/Select.d.ts +102 -0
- package/renderables/Slider.d.ts +31 -0
- package/renderables/TabSelect.d.ts +86 -0
- package/renderables/Text.d.ts +72 -0
- package/renderables/composition/VRenderable.d.ts +16 -0
- package/renderables/composition/constructs.d.ts +12 -0
- package/renderables/composition/vnode.d.ts +46 -0
- package/renderables/index.d.ts +12 -0
- package/renderer.d.ts +232 -0
- package/singleton.d.ts +5 -0
- package/text-buffer.d.ts +52 -0
- package/types.d.ts +56 -0
- package/utils.d.ts +10 -0
- package/zig.d.ts +110 -0
package/index.js
ADDED
|
@@ -0,0 +1,3721 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
ASCIIFontSelectionHelper,
|
|
4
|
+
BorderCharArrays,
|
|
5
|
+
BorderChars,
|
|
6
|
+
CliRenderEvents,
|
|
7
|
+
CliRenderer,
|
|
8
|
+
ConsolePosition,
|
|
9
|
+
DebugOverlayCorner,
|
|
10
|
+
Edge,
|
|
11
|
+
Gutter,
|
|
12
|
+
KeyHandler,
|
|
13
|
+
LayoutEvents,
|
|
14
|
+
LogLevel,
|
|
15
|
+
MeasureMode,
|
|
16
|
+
MouseButton,
|
|
17
|
+
MouseEvent,
|
|
18
|
+
MouseParser,
|
|
19
|
+
OptimizedBuffer,
|
|
20
|
+
RGBA,
|
|
21
|
+
Renderable,
|
|
22
|
+
RenderableEvents,
|
|
23
|
+
RootRenderable,
|
|
24
|
+
Selection,
|
|
25
|
+
StyledText,
|
|
26
|
+
SyntaxStyle,
|
|
27
|
+
TerminalConsole,
|
|
28
|
+
TextAttributes,
|
|
29
|
+
TextBuffer,
|
|
30
|
+
TrackedNode,
|
|
31
|
+
bg,
|
|
32
|
+
bgBlack,
|
|
33
|
+
bgBlue,
|
|
34
|
+
bgCyan,
|
|
35
|
+
bgGreen,
|
|
36
|
+
bgMagenta,
|
|
37
|
+
bgRed,
|
|
38
|
+
bgWhite,
|
|
39
|
+
bgYellow,
|
|
40
|
+
black,
|
|
41
|
+
blink,
|
|
42
|
+
blue,
|
|
43
|
+
bold,
|
|
44
|
+
borderCharsToArray,
|
|
45
|
+
brightBlack,
|
|
46
|
+
brightBlue,
|
|
47
|
+
brightCyan,
|
|
48
|
+
brightGreen,
|
|
49
|
+
brightMagenta,
|
|
50
|
+
brightRed,
|
|
51
|
+
brightWhite,
|
|
52
|
+
brightYellow,
|
|
53
|
+
capture,
|
|
54
|
+
convertGlobalToLocalSelection,
|
|
55
|
+
coordinateToCharacterIndex,
|
|
56
|
+
createCliRenderer,
|
|
57
|
+
createTextAttributes,
|
|
58
|
+
createTrackedNode,
|
|
59
|
+
cyan,
|
|
60
|
+
delegate,
|
|
61
|
+
dim,
|
|
62
|
+
fg,
|
|
63
|
+
fonts,
|
|
64
|
+
getBorderFromSides,
|
|
65
|
+
getBorderSides,
|
|
66
|
+
getCharacterPositions,
|
|
67
|
+
getKeyHandler,
|
|
68
|
+
green,
|
|
69
|
+
h,
|
|
70
|
+
hastToStyledText,
|
|
71
|
+
hexToRgb,
|
|
72
|
+
hsvToRgb,
|
|
73
|
+
instantiate,
|
|
74
|
+
isDimensionType,
|
|
75
|
+
isFlexBasisType,
|
|
76
|
+
isMarginType,
|
|
77
|
+
isOverflowType,
|
|
78
|
+
isPaddingType,
|
|
79
|
+
isPositionType,
|
|
80
|
+
isPositionTypeType,
|
|
81
|
+
isRenderable,
|
|
82
|
+
isSizeType,
|
|
83
|
+
isVNode,
|
|
84
|
+
isValidPercentage,
|
|
85
|
+
italic,
|
|
86
|
+
magenta,
|
|
87
|
+
maybeMakeRenderable,
|
|
88
|
+
measureText,
|
|
89
|
+
nonAlphanumericKeys,
|
|
90
|
+
parseAlign,
|
|
91
|
+
parseBoxSizing,
|
|
92
|
+
parseColor,
|
|
93
|
+
parseDimension,
|
|
94
|
+
parseDirection,
|
|
95
|
+
parseDisplay,
|
|
96
|
+
parseEdge,
|
|
97
|
+
parseFlexDirection,
|
|
98
|
+
parseGutter,
|
|
99
|
+
parseJustify,
|
|
100
|
+
parseKeypress,
|
|
101
|
+
parseLogLevel,
|
|
102
|
+
parseMeasureMode,
|
|
103
|
+
parseOverflow,
|
|
104
|
+
parsePositionType,
|
|
105
|
+
parseUnit,
|
|
106
|
+
parseWrap,
|
|
107
|
+
red,
|
|
108
|
+
renderFontToFrameBuffer,
|
|
109
|
+
resolveRenderLib,
|
|
110
|
+
reverse,
|
|
111
|
+
rgbToHex,
|
|
112
|
+
setRenderLibPath,
|
|
113
|
+
strikethrough,
|
|
114
|
+
stringToStyledText,
|
|
115
|
+
t,
|
|
116
|
+
underline,
|
|
117
|
+
white,
|
|
118
|
+
wrapWithDelegates,
|
|
119
|
+
yellow
|
|
120
|
+
} from "./index-d6kwx5pm.js";
|
|
121
|
+
// src/post/filters.ts
|
|
122
|
+
function applyScanlines(buffer, strength = 0.8, step = 2) {
|
|
123
|
+
const width = buffer.width;
|
|
124
|
+
const height = buffer.height;
|
|
125
|
+
const bg2 = buffer.buffers.bg;
|
|
126
|
+
for (let y = 0;y < height; y += step) {
|
|
127
|
+
for (let x = 0;x < width; x++) {
|
|
128
|
+
const colorIndex = (y * width + x) * 4;
|
|
129
|
+
bg2[colorIndex] *= strength;
|
|
130
|
+
bg2[colorIndex + 1] *= strength;
|
|
131
|
+
bg2[colorIndex + 2] *= strength;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function applyGrayscale(buffer) {
|
|
136
|
+
const size = buffer.width * buffer.height;
|
|
137
|
+
const fg2 = buffer.buffers.fg;
|
|
138
|
+
const bg2 = buffer.buffers.bg;
|
|
139
|
+
for (let i = 0;i < size; i++) {
|
|
140
|
+
const colorIndex = i * 4;
|
|
141
|
+
const fgR = fg2[colorIndex];
|
|
142
|
+
const fgG = fg2[colorIndex + 1];
|
|
143
|
+
const fgB = fg2[colorIndex + 2];
|
|
144
|
+
const fgLum = 0.299 * fgR + 0.587 * fgG + 0.114 * fgB;
|
|
145
|
+
fg2[colorIndex] = fgLum;
|
|
146
|
+
fg2[colorIndex + 1] = fgLum;
|
|
147
|
+
fg2[colorIndex + 2] = fgLum;
|
|
148
|
+
const bgR = bg2[colorIndex];
|
|
149
|
+
const bgG = bg2[colorIndex + 1];
|
|
150
|
+
const bgB = bg2[colorIndex + 2];
|
|
151
|
+
const bgLum = 0.299 * bgR + 0.587 * bgG + 0.114 * bgB;
|
|
152
|
+
bg2[colorIndex] = bgLum;
|
|
153
|
+
bg2[colorIndex + 1] = bgLum;
|
|
154
|
+
bg2[colorIndex + 2] = bgLum;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function applySepia(buffer) {
|
|
158
|
+
const size = buffer.width * buffer.height;
|
|
159
|
+
const fg2 = buffer.buffers.fg;
|
|
160
|
+
const bg2 = buffer.buffers.bg;
|
|
161
|
+
for (let i = 0;i < size; i++) {
|
|
162
|
+
const colorIndex = i * 4;
|
|
163
|
+
let fgR = fg2[colorIndex];
|
|
164
|
+
let fgG = fg2[colorIndex + 1];
|
|
165
|
+
let fgB = fg2[colorIndex + 2];
|
|
166
|
+
let newFgR = Math.min(1, fgR * 0.393 + fgG * 0.769 + fgB * 0.189);
|
|
167
|
+
let newFgG = Math.min(1, fgR * 0.349 + fgG * 0.686 + fgB * 0.168);
|
|
168
|
+
let newFgB = Math.min(1, fgR * 0.272 + fgG * 0.534 + fgB * 0.131);
|
|
169
|
+
fg2[colorIndex] = newFgR;
|
|
170
|
+
fg2[colorIndex + 1] = newFgG;
|
|
171
|
+
fg2[colorIndex + 2] = newFgB;
|
|
172
|
+
let bgR = bg2[colorIndex];
|
|
173
|
+
let bgG = bg2[colorIndex + 1];
|
|
174
|
+
let bgB = bg2[colorIndex + 2];
|
|
175
|
+
let newBgR = Math.min(1, bgR * 0.393 + bgG * 0.769 + bgB * 0.189);
|
|
176
|
+
let newBgG = Math.min(1, bgR * 0.349 + bgG * 0.686 + bgB * 0.168);
|
|
177
|
+
let newBgB = Math.min(1, bgR * 0.272 + bgG * 0.534 + bgB * 0.131);
|
|
178
|
+
bg2[colorIndex] = newBgR;
|
|
179
|
+
bg2[colorIndex + 1] = newBgG;
|
|
180
|
+
bg2[colorIndex + 2] = newBgB;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function applyInvert(buffer) {
|
|
184
|
+
const size = buffer.width * buffer.height;
|
|
185
|
+
const fg2 = buffer.buffers.fg;
|
|
186
|
+
const bg2 = buffer.buffers.bg;
|
|
187
|
+
for (let i = 0;i < size; i++) {
|
|
188
|
+
const colorIndex = i * 4;
|
|
189
|
+
fg2[colorIndex] = 1 - fg2[colorIndex];
|
|
190
|
+
fg2[colorIndex + 1] = 1 - fg2[colorIndex + 1];
|
|
191
|
+
fg2[colorIndex + 2] = 1 - fg2[colorIndex + 2];
|
|
192
|
+
bg2[colorIndex] = 1 - bg2[colorIndex];
|
|
193
|
+
bg2[colorIndex + 1] = 1 - bg2[colorIndex + 1];
|
|
194
|
+
bg2[colorIndex + 2] = 1 - bg2[colorIndex + 2];
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function applyNoise(buffer, strength = 0.1) {
|
|
198
|
+
const size = buffer.width * buffer.height;
|
|
199
|
+
const fg2 = buffer.buffers.fg;
|
|
200
|
+
const bg2 = buffer.buffers.bg;
|
|
201
|
+
for (let i = 0;i < size; i++) {
|
|
202
|
+
const colorIndex = i * 4;
|
|
203
|
+
const noise = (Math.random() - 0.5) * strength;
|
|
204
|
+
fg2[colorIndex] = Math.max(0, Math.min(1, fg2[colorIndex] + noise));
|
|
205
|
+
fg2[colorIndex + 1] = Math.max(0, Math.min(1, fg2[colorIndex + 1] + noise));
|
|
206
|
+
fg2[colorIndex + 2] = Math.max(0, Math.min(1, fg2[colorIndex + 2] + noise));
|
|
207
|
+
bg2[colorIndex] = Math.max(0, Math.min(1, bg2[colorIndex] + noise));
|
|
208
|
+
bg2[colorIndex + 1] = Math.max(0, Math.min(1, bg2[colorIndex + 1] + noise));
|
|
209
|
+
bg2[colorIndex + 2] = Math.max(0, Math.min(1, bg2[colorIndex + 2] + noise));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function applyChromaticAberration(buffer, strength = 1) {
|
|
213
|
+
const width = buffer.width;
|
|
214
|
+
const height = buffer.height;
|
|
215
|
+
const srcFg = Float32Array.from(buffer.buffers.fg);
|
|
216
|
+
const destFg = buffer.buffers.fg;
|
|
217
|
+
const centerX = width / 2;
|
|
218
|
+
const centerY = height / 2;
|
|
219
|
+
for (let y = 0;y < height; y++) {
|
|
220
|
+
for (let x = 0;x < width; x++) {
|
|
221
|
+
const dx = x - centerX;
|
|
222
|
+
const dy = y - centerY;
|
|
223
|
+
const offset = Math.round(Math.sqrt(dx * dx + dy * dy) / Math.max(centerX, centerY) * strength);
|
|
224
|
+
const rX = Math.max(0, Math.min(width - 1, x - offset));
|
|
225
|
+
const bX = Math.max(0, Math.min(width - 1, x + offset));
|
|
226
|
+
const rIndex = (y * width + rX) * 4;
|
|
227
|
+
const gIndex = (y * width + x) * 4;
|
|
228
|
+
const bIndex = (y * width + bX) * 4;
|
|
229
|
+
const destIndex = (y * width + x) * 4;
|
|
230
|
+
destFg[destIndex] = srcFg[rIndex];
|
|
231
|
+
destFg[destIndex + 1] = srcFg[gIndex + 1];
|
|
232
|
+
destFg[destIndex + 2] = srcFg[bIndex + 2];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function applyAsciiArt(buffer, ramp = " .:-=+*#%@") {
|
|
237
|
+
const width = buffer.width;
|
|
238
|
+
const height = buffer.height;
|
|
239
|
+
const chars = buffer.buffers.char;
|
|
240
|
+
const bg2 = buffer.buffers.bg;
|
|
241
|
+
const rampLength = ramp.length;
|
|
242
|
+
for (let y = 0;y < height; y++) {
|
|
243
|
+
for (let x = 0;x < width; x++) {
|
|
244
|
+
const index = y * width + x;
|
|
245
|
+
const colorIndex = index * 4;
|
|
246
|
+
const bgR = bg2[colorIndex];
|
|
247
|
+
const bgG = bg2[colorIndex + 1];
|
|
248
|
+
const bgB = bg2[colorIndex + 2];
|
|
249
|
+
const lum = 0.299 * bgR + 0.587 * bgG + 0.114 * bgB;
|
|
250
|
+
const rampIndex = Math.min(rampLength - 1, Math.floor(lum * rampLength));
|
|
251
|
+
chars[index] = ramp[rampIndex].charCodeAt(0);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
class DistortionEffect {
|
|
257
|
+
glitchChancePerSecond = 0.5;
|
|
258
|
+
maxGlitchLines = 3;
|
|
259
|
+
minGlitchDuration = 0.05;
|
|
260
|
+
maxGlitchDuration = 0.2;
|
|
261
|
+
maxShiftAmount = 10;
|
|
262
|
+
shiftFlipRatio = 0.6;
|
|
263
|
+
colorGlitchChance = 0.2;
|
|
264
|
+
lastGlitchTime = 0;
|
|
265
|
+
glitchDuration = 0;
|
|
266
|
+
activeGlitches = [];
|
|
267
|
+
constructor(options) {
|
|
268
|
+
if (options) {
|
|
269
|
+
Object.assign(this, options);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
apply(buffer, deltaTime) {
|
|
273
|
+
const width = buffer.width;
|
|
274
|
+
const height = buffer.height;
|
|
275
|
+
const buf = buffer.buffers;
|
|
276
|
+
this.lastGlitchTime += deltaTime;
|
|
277
|
+
if (this.activeGlitches.length > 0 && this.lastGlitchTime >= this.glitchDuration) {
|
|
278
|
+
this.activeGlitches = [];
|
|
279
|
+
this.glitchDuration = 0;
|
|
280
|
+
}
|
|
281
|
+
if (this.activeGlitches.length === 0 && Math.random() < this.glitchChancePerSecond * deltaTime) {
|
|
282
|
+
this.lastGlitchTime = 0;
|
|
283
|
+
this.glitchDuration = this.minGlitchDuration + Math.random() * (this.maxGlitchDuration - this.minGlitchDuration);
|
|
284
|
+
const numGlitches = 1 + Math.floor(Math.random() * this.maxGlitchLines);
|
|
285
|
+
for (let i = 0;i < numGlitches; i++) {
|
|
286
|
+
const y = Math.floor(Math.random() * height);
|
|
287
|
+
let type;
|
|
288
|
+
let amount = 0;
|
|
289
|
+
const typeRoll = Math.random();
|
|
290
|
+
if (typeRoll < this.colorGlitchChance) {
|
|
291
|
+
type = "color";
|
|
292
|
+
} else {
|
|
293
|
+
const shiftRoll = (typeRoll - this.colorGlitchChance) / (1 - this.colorGlitchChance);
|
|
294
|
+
if (shiftRoll < this.shiftFlipRatio) {
|
|
295
|
+
type = "shift";
|
|
296
|
+
amount = Math.floor((Math.random() - 0.5) * 2 * this.maxShiftAmount);
|
|
297
|
+
} else {
|
|
298
|
+
type = "flip";
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (!this.activeGlitches.some((g) => g.y === y)) {
|
|
302
|
+
this.activeGlitches.push({ y, type, amount });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (this.activeGlitches.length > 0) {
|
|
307
|
+
let tempChar = null;
|
|
308
|
+
let tempFg = null;
|
|
309
|
+
let tempBg = null;
|
|
310
|
+
let tempAttr = null;
|
|
311
|
+
for (const glitch of this.activeGlitches) {
|
|
312
|
+
const y = glitch.y;
|
|
313
|
+
if (y < 0 || y >= height)
|
|
314
|
+
continue;
|
|
315
|
+
const baseIndex = y * width;
|
|
316
|
+
if (glitch.type === "shift" || glitch.type === "flip") {
|
|
317
|
+
if (!tempChar) {
|
|
318
|
+
tempChar = new Uint32Array(width);
|
|
319
|
+
tempFg = new Float32Array(width * 4);
|
|
320
|
+
tempBg = new Float32Array(width * 4);
|
|
321
|
+
tempAttr = new Uint8Array(width);
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
tempChar.set(buf.char.subarray(baseIndex, baseIndex + width));
|
|
325
|
+
tempFg.set(buf.fg.subarray(baseIndex * 4, (baseIndex + width) * 4));
|
|
326
|
+
tempBg.set(buf.bg.subarray(baseIndex * 4, (baseIndex + width) * 4));
|
|
327
|
+
tempAttr.set(buf.attributes.subarray(baseIndex, baseIndex + width));
|
|
328
|
+
} catch (e) {
|
|
329
|
+
console.error(`Error copying row ${y} for distortion:`, e);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (glitch.type === "shift") {
|
|
333
|
+
const shift = glitch.amount;
|
|
334
|
+
for (let x = 0;x < width; x++) {
|
|
335
|
+
const srcX = (x - shift + width) % width;
|
|
336
|
+
const destIndex = baseIndex + x;
|
|
337
|
+
const srcTempIndex = srcX;
|
|
338
|
+
buf.char[destIndex] = tempChar[srcTempIndex];
|
|
339
|
+
buf.attributes[destIndex] = tempAttr[srcTempIndex];
|
|
340
|
+
const destColorIndex = destIndex * 4;
|
|
341
|
+
const srcTempColorIndex = srcTempIndex * 4;
|
|
342
|
+
buf.fg.set(tempFg.subarray(srcTempColorIndex, srcTempColorIndex + 4), destColorIndex);
|
|
343
|
+
buf.bg.set(tempBg.subarray(srcTempColorIndex, srcTempColorIndex + 4), destColorIndex);
|
|
344
|
+
}
|
|
345
|
+
} else {
|
|
346
|
+
for (let x = 0;x < width; x++) {
|
|
347
|
+
const srcX = width - 1 - x;
|
|
348
|
+
const destIndex = baseIndex + x;
|
|
349
|
+
const srcTempIndex = srcX;
|
|
350
|
+
buf.char[destIndex] = tempChar[srcTempIndex];
|
|
351
|
+
buf.attributes[destIndex] = tempAttr[srcTempIndex];
|
|
352
|
+
const destColorIndex = destIndex * 4;
|
|
353
|
+
const srcTempColorIndex = srcTempIndex * 4;
|
|
354
|
+
buf.fg.set(tempFg.subarray(srcTempColorIndex, srcTempColorIndex + 4), destColorIndex);
|
|
355
|
+
buf.bg.set(tempBg.subarray(srcTempColorIndex, srcTempColorIndex + 4), destColorIndex);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
} else if (glitch.type === "color") {
|
|
359
|
+
const glitchStart = Math.floor(Math.random() * width);
|
|
360
|
+
const maxPossibleLength = width - glitchStart;
|
|
361
|
+
let glitchLength = Math.floor(Math.random() * maxPossibleLength) + 1;
|
|
362
|
+
if (Math.random() < 0.2) {
|
|
363
|
+
glitchLength = Math.floor(Math.random() * (width / 4)) + 1;
|
|
364
|
+
}
|
|
365
|
+
glitchLength = Math.min(glitchLength, maxPossibleLength);
|
|
366
|
+
for (let x = glitchStart;x < glitchStart + glitchLength; x++) {
|
|
367
|
+
if (x >= width)
|
|
368
|
+
break;
|
|
369
|
+
const destIndex = baseIndex + x;
|
|
370
|
+
const destColorIndex = destIndex * 4;
|
|
371
|
+
let rFg, gFg, bFg, rBg, gBg, bBg;
|
|
372
|
+
const colorMode = Math.random();
|
|
373
|
+
if (colorMode < 0.33) {
|
|
374
|
+
rFg = Math.random();
|
|
375
|
+
gFg = Math.random();
|
|
376
|
+
bFg = Math.random();
|
|
377
|
+
rBg = Math.random();
|
|
378
|
+
gBg = Math.random();
|
|
379
|
+
bBg = Math.random();
|
|
380
|
+
} else if (colorMode < 0.66) {
|
|
381
|
+
const emphasis = Math.random();
|
|
382
|
+
if (emphasis < 0.25) {
|
|
383
|
+
rFg = Math.random();
|
|
384
|
+
gFg = 0;
|
|
385
|
+
bFg = 0;
|
|
386
|
+
} else if (emphasis < 0.5) {
|
|
387
|
+
rFg = 0;
|
|
388
|
+
gFg = Math.random();
|
|
389
|
+
bFg = 0;
|
|
390
|
+
} else if (emphasis < 0.75) {
|
|
391
|
+
rFg = 0;
|
|
392
|
+
gFg = 0;
|
|
393
|
+
bFg = Math.random();
|
|
394
|
+
} else {
|
|
395
|
+
const glitchColorRoll = Math.random();
|
|
396
|
+
if (glitchColorRoll < 0.33) {
|
|
397
|
+
rFg = 1;
|
|
398
|
+
gFg = 0;
|
|
399
|
+
bFg = 1;
|
|
400
|
+
} else if (glitchColorRoll < 0.66) {
|
|
401
|
+
rFg = 0;
|
|
402
|
+
gFg = 1;
|
|
403
|
+
bFg = 1;
|
|
404
|
+
} else {
|
|
405
|
+
rFg = 1;
|
|
406
|
+
gFg = 1;
|
|
407
|
+
bFg = 0;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (Math.random() < 0.5) {
|
|
411
|
+
rBg = 1 - rFg;
|
|
412
|
+
gBg = 1 - gFg;
|
|
413
|
+
bBg = 1 - bFg;
|
|
414
|
+
} else {
|
|
415
|
+
rBg = rFg * (Math.random() * 0.5 + 0.2);
|
|
416
|
+
gBg = gFg * (Math.random() * 0.5 + 0.2);
|
|
417
|
+
bBg = bFg * (Math.random() * 0.5 + 0.2);
|
|
418
|
+
}
|
|
419
|
+
} else {
|
|
420
|
+
rFg = Math.random() > 0.5 ? 1 : 0;
|
|
421
|
+
gFg = Math.random() > 0.5 ? 1 : 0;
|
|
422
|
+
bFg = Math.random() > 0.5 ? 1 : 0;
|
|
423
|
+
rBg = 1 - rFg;
|
|
424
|
+
gBg = 1 - gFg;
|
|
425
|
+
bBg = 1 - bFg;
|
|
426
|
+
}
|
|
427
|
+
buf.fg[destColorIndex] = rFg;
|
|
428
|
+
buf.fg[destColorIndex + 1] = gFg;
|
|
429
|
+
buf.fg[destColorIndex + 2] = bFg;
|
|
430
|
+
buf.bg[destColorIndex] = rBg;
|
|
431
|
+
buf.bg[destColorIndex + 1] = gBg;
|
|
432
|
+
buf.bg[destColorIndex + 2] = bBg;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
class VignetteEffect {
|
|
441
|
+
_strength;
|
|
442
|
+
precomputedBaseAttenuation = null;
|
|
443
|
+
cachedWidth = -1;
|
|
444
|
+
cachedHeight = -1;
|
|
445
|
+
constructor(strength = 0.5) {
|
|
446
|
+
this._strength = strength;
|
|
447
|
+
}
|
|
448
|
+
set strength(newStrength) {
|
|
449
|
+
this._strength = Math.max(0, newStrength);
|
|
450
|
+
}
|
|
451
|
+
get strength() {
|
|
452
|
+
return this._strength;
|
|
453
|
+
}
|
|
454
|
+
_computeFactors(width, height) {
|
|
455
|
+
this.precomputedBaseAttenuation = new Float32Array(width * height);
|
|
456
|
+
const centerX = width / 2;
|
|
457
|
+
const centerY = height / 2;
|
|
458
|
+
const maxDistSq = centerX * centerX + centerY * centerY;
|
|
459
|
+
const safeMaxDistSq = maxDistSq === 0 ? 1 : maxDistSq;
|
|
460
|
+
for (let y = 0;y < height; y++) {
|
|
461
|
+
const dy = y - centerY;
|
|
462
|
+
const dySq = dy * dy;
|
|
463
|
+
for (let x = 0;x < width; x++) {
|
|
464
|
+
const dx = x - centerX;
|
|
465
|
+
const distSq = dx * dx + dySq;
|
|
466
|
+
const baseAttenuation = Math.min(1, distSq / safeMaxDistSq);
|
|
467
|
+
const index = y * width + x;
|
|
468
|
+
this.precomputedBaseAttenuation[index] = baseAttenuation;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
this.cachedWidth = width;
|
|
472
|
+
this.cachedHeight = height;
|
|
473
|
+
}
|
|
474
|
+
apply(buffer) {
|
|
475
|
+
const width = buffer.width;
|
|
476
|
+
const height = buffer.height;
|
|
477
|
+
const buf = buffer.buffers;
|
|
478
|
+
const size = width * height;
|
|
479
|
+
if (width !== this.cachedWidth || height !== this.cachedHeight || !this.precomputedBaseAttenuation) {
|
|
480
|
+
this._computeFactors(width, height);
|
|
481
|
+
}
|
|
482
|
+
for (let i = 0;i < size; i++) {
|
|
483
|
+
const factor = Math.max(0, 1 - this.precomputedBaseAttenuation[i] * this._strength);
|
|
484
|
+
const colorIndex = i * 4;
|
|
485
|
+
buf.fg[colorIndex] *= factor;
|
|
486
|
+
buf.fg[colorIndex + 1] *= factor;
|
|
487
|
+
buf.fg[colorIndex + 2] *= factor;
|
|
488
|
+
buf.bg[colorIndex] *= factor;
|
|
489
|
+
buf.bg[colorIndex + 1] *= factor;
|
|
490
|
+
buf.bg[colorIndex + 2] *= factor;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
class BrightnessEffect {
|
|
496
|
+
_brightness;
|
|
497
|
+
constructor(brightness = 1) {
|
|
498
|
+
this._brightness = Math.max(0, brightness);
|
|
499
|
+
}
|
|
500
|
+
set brightness(newBrightness) {
|
|
501
|
+
this._brightness = Math.max(0, newBrightness);
|
|
502
|
+
}
|
|
503
|
+
get brightness() {
|
|
504
|
+
return this._brightness;
|
|
505
|
+
}
|
|
506
|
+
apply(buffer) {
|
|
507
|
+
const size = buffer.width * buffer.height;
|
|
508
|
+
const fg2 = buffer.buffers.fg;
|
|
509
|
+
const bg2 = buffer.buffers.bg;
|
|
510
|
+
const factor = this._brightness;
|
|
511
|
+
if (factor === 1) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
for (let i = 0;i < size; i++) {
|
|
515
|
+
const colorIndex = i * 4;
|
|
516
|
+
fg2[colorIndex] = Math.min(1, fg2[colorIndex] * factor);
|
|
517
|
+
fg2[colorIndex + 1] = Math.min(1, fg2[colorIndex + 1] * factor);
|
|
518
|
+
fg2[colorIndex + 2] = Math.min(1, fg2[colorIndex + 2] * factor);
|
|
519
|
+
bg2[colorIndex] = Math.min(1, bg2[colorIndex] * factor);
|
|
520
|
+
bg2[colorIndex + 1] = Math.min(1, bg2[colorIndex + 1] * factor);
|
|
521
|
+
bg2[colorIndex + 2] = Math.min(1, bg2[colorIndex + 2] * factor);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
class BlurEffect {
|
|
527
|
+
_radius;
|
|
528
|
+
constructor(radius = 1) {
|
|
529
|
+
this._radius = Math.max(0, Math.round(radius));
|
|
530
|
+
}
|
|
531
|
+
set radius(newRadius) {
|
|
532
|
+
this._radius = Math.max(0, Math.round(newRadius));
|
|
533
|
+
}
|
|
534
|
+
get radius() {
|
|
535
|
+
return this._radius;
|
|
536
|
+
}
|
|
537
|
+
apply(buffer) {
|
|
538
|
+
const radius = this._radius;
|
|
539
|
+
if (radius <= 0)
|
|
540
|
+
return;
|
|
541
|
+
const width = buffer.width;
|
|
542
|
+
const height = buffer.height;
|
|
543
|
+
const buf = buffer.buffers;
|
|
544
|
+
const srcFg = buf.fg;
|
|
545
|
+
const srcBg = buf.bg;
|
|
546
|
+
const destFg = buf.fg;
|
|
547
|
+
const destBg = buf.bg;
|
|
548
|
+
const chars = buf.char;
|
|
549
|
+
const size = width * height;
|
|
550
|
+
const numChannels = 4;
|
|
551
|
+
const tempBufferFg = new Float32Array(size * numChannels);
|
|
552
|
+
const tempBufferBg = new Float32Array(size * numChannels);
|
|
553
|
+
const windowSize = radius * 2 + 1;
|
|
554
|
+
for (let y = 0;y < height; y++) {
|
|
555
|
+
let sumR = 0, sumG = 0, sumB = 0, sumA = 0;
|
|
556
|
+
const baseRowIndex = y * width;
|
|
557
|
+
for (let x = -radius;x <= radius; x++) {
|
|
558
|
+
const sampleX = Math.max(0, Math.min(width - 1, x));
|
|
559
|
+
const srcIndex = (baseRowIndex + sampleX) * numChannels;
|
|
560
|
+
sumR += srcFg[srcIndex];
|
|
561
|
+
sumG += srcFg[srcIndex + 1];
|
|
562
|
+
sumB += srcFg[srcIndex + 2];
|
|
563
|
+
sumA += srcFg[srcIndex + 3];
|
|
564
|
+
}
|
|
565
|
+
for (let x = 0;x < width; x++) {
|
|
566
|
+
const destIndex = (baseRowIndex + x) * numChannels;
|
|
567
|
+
tempBufferFg[destIndex] = sumR / windowSize;
|
|
568
|
+
tempBufferFg[destIndex + 1] = sumG / windowSize;
|
|
569
|
+
tempBufferFg[destIndex + 2] = sumB / windowSize;
|
|
570
|
+
tempBufferFg[destIndex + 3] = sumA / windowSize;
|
|
571
|
+
const leavingX = Math.max(0, Math.min(width - 1, x - radius));
|
|
572
|
+
const leavingIndex = (baseRowIndex + leavingX) * numChannels;
|
|
573
|
+
sumR -= srcFg[leavingIndex];
|
|
574
|
+
sumG -= srcFg[leavingIndex + 1];
|
|
575
|
+
sumB -= srcFg[leavingIndex + 2];
|
|
576
|
+
sumA -= srcFg[leavingIndex + 3];
|
|
577
|
+
const enteringX = Math.max(0, Math.min(width - 1, x + radius + 1));
|
|
578
|
+
const enteringIndex = (baseRowIndex + enteringX) * numChannels;
|
|
579
|
+
sumR += srcFg[enteringIndex];
|
|
580
|
+
sumG += srcFg[enteringIndex + 1];
|
|
581
|
+
sumB += srcFg[enteringIndex + 2];
|
|
582
|
+
sumA += srcFg[enteringIndex + 3];
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
for (let y = 0;y < height; y++) {
|
|
586
|
+
let sumR = 0, sumG = 0, sumB = 0, sumA = 0;
|
|
587
|
+
const baseRowIndex = y * width;
|
|
588
|
+
for (let x = -radius;x <= radius; x++) {
|
|
589
|
+
const sampleX = Math.max(0, Math.min(width - 1, x));
|
|
590
|
+
const srcIndex = (baseRowIndex + sampleX) * numChannels;
|
|
591
|
+
sumR += srcBg[srcIndex];
|
|
592
|
+
sumG += srcBg[srcIndex + 1];
|
|
593
|
+
sumB += srcBg[srcIndex + 2];
|
|
594
|
+
sumA += srcBg[srcIndex + 3];
|
|
595
|
+
}
|
|
596
|
+
for (let x = 0;x < width; x++) {
|
|
597
|
+
const destIndex = (baseRowIndex + x) * numChannels;
|
|
598
|
+
tempBufferBg[destIndex] = sumR / windowSize;
|
|
599
|
+
tempBufferBg[destIndex + 1] = sumG / windowSize;
|
|
600
|
+
tempBufferBg[destIndex + 2] = sumB / windowSize;
|
|
601
|
+
tempBufferBg[destIndex + 3] = sumA / windowSize;
|
|
602
|
+
const leavingX = Math.max(0, Math.min(width - 1, x - radius));
|
|
603
|
+
const leavingIndex = (baseRowIndex + leavingX) * numChannels;
|
|
604
|
+
sumR -= srcBg[leavingIndex];
|
|
605
|
+
sumG -= srcBg[leavingIndex + 1];
|
|
606
|
+
sumB -= srcBg[leavingIndex + 2];
|
|
607
|
+
sumA -= srcBg[leavingIndex + 3];
|
|
608
|
+
const enteringX = Math.max(0, Math.min(width - 1, x + radius + 1));
|
|
609
|
+
const enteringIndex = (baseRowIndex + enteringX) * numChannels;
|
|
610
|
+
sumR += srcBg[enteringIndex];
|
|
611
|
+
sumG += srcBg[enteringIndex + 1];
|
|
612
|
+
sumB += srcBg[enteringIndex + 2];
|
|
613
|
+
sumA += srcBg[enteringIndex + 3];
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
for (let x = 0;x < width; x++) {
|
|
617
|
+
let sumR = 0, sumG = 0, sumB = 0, sumA = 0;
|
|
618
|
+
for (let y = -radius;y <= radius; y++) {
|
|
619
|
+
const sampleY = Math.max(0, Math.min(height - 1, y));
|
|
620
|
+
const srcIndex = (sampleY * width + x) * numChannels;
|
|
621
|
+
sumR += tempBufferFg[srcIndex];
|
|
622
|
+
sumG += tempBufferFg[srcIndex + 1];
|
|
623
|
+
sumB += tempBufferFg[srcIndex + 2];
|
|
624
|
+
sumA += tempBufferFg[srcIndex + 3];
|
|
625
|
+
}
|
|
626
|
+
for (let y = 0;y < height; y++) {
|
|
627
|
+
const destIndex = (y * width + x) * numChannels;
|
|
628
|
+
destFg[destIndex] = sumR / windowSize;
|
|
629
|
+
destFg[destIndex + 1] = sumG / windowSize;
|
|
630
|
+
destFg[destIndex + 2] = sumB / windowSize;
|
|
631
|
+
destFg[destIndex + 3] = sumA / windowSize;
|
|
632
|
+
const leavingY = Math.max(0, Math.min(height - 1, y - radius));
|
|
633
|
+
const leavingIndex = (leavingY * width + x) * numChannels;
|
|
634
|
+
sumR -= tempBufferFg[leavingIndex];
|
|
635
|
+
sumG -= tempBufferFg[leavingIndex + 1];
|
|
636
|
+
sumB -= tempBufferFg[leavingIndex + 2];
|
|
637
|
+
sumA -= tempBufferFg[leavingIndex + 3];
|
|
638
|
+
const enteringY = Math.max(0, Math.min(height - 1, y + radius + 1));
|
|
639
|
+
const enteringIndex = (enteringY * width + x) * numChannels;
|
|
640
|
+
sumR += tempBufferFg[enteringIndex];
|
|
641
|
+
sumG += tempBufferFg[enteringIndex + 1];
|
|
642
|
+
sumB += tempBufferFg[enteringIndex + 2];
|
|
643
|
+
sumA += tempBufferFg[enteringIndex + 3];
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
for (let x = 0;x < width; x++) {
|
|
647
|
+
let sumR = 0, sumG = 0, sumB = 0, sumA = 0;
|
|
648
|
+
for (let y = -radius;y <= radius; y++) {
|
|
649
|
+
const sampleY = Math.max(0, Math.min(height - 1, y));
|
|
650
|
+
const srcIndex = (sampleY * width + x) * numChannels;
|
|
651
|
+
sumR += tempBufferBg[srcIndex];
|
|
652
|
+
sumG += tempBufferBg[srcIndex + 1];
|
|
653
|
+
sumB += tempBufferBg[srcIndex + 2];
|
|
654
|
+
sumA += tempBufferBg[srcIndex + 3];
|
|
655
|
+
}
|
|
656
|
+
for (let y = 0;y < height; y++) {
|
|
657
|
+
const destIndex = (y * width + x) * numChannels;
|
|
658
|
+
destBg[destIndex] = sumR / windowSize;
|
|
659
|
+
destBg[destIndex + 1] = sumG / windowSize;
|
|
660
|
+
destBg[destIndex + 2] = sumB / windowSize;
|
|
661
|
+
destBg[destIndex + 3] = sumA / windowSize;
|
|
662
|
+
const leavingY = Math.max(0, Math.min(height - 1, y - radius));
|
|
663
|
+
const leavingIndex = (leavingY * width + x) * numChannels;
|
|
664
|
+
sumR -= tempBufferBg[leavingIndex];
|
|
665
|
+
sumG -= tempBufferBg[leavingIndex + 1];
|
|
666
|
+
sumB -= tempBufferBg[leavingIndex + 2];
|
|
667
|
+
sumA -= tempBufferBg[leavingIndex + 3];
|
|
668
|
+
const enteringY = Math.max(0, Math.min(height - 1, y + radius + 1));
|
|
669
|
+
const enteringIndex = (enteringY * width + x) * numChannels;
|
|
670
|
+
sumR += tempBufferBg[enteringIndex];
|
|
671
|
+
sumG += tempBufferBg[enteringIndex + 1];
|
|
672
|
+
sumB += tempBufferBg[enteringIndex + 2];
|
|
673
|
+
sumA += tempBufferBg[enteringIndex + 3];
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
const charRamp = [" ", "\u2591", "\u2592", "\u2593", " "];
|
|
677
|
+
const rampLength = charRamp.length;
|
|
678
|
+
for (let i = 0;i < size; i++) {
|
|
679
|
+
const alphaIndex = i * numChannels + 3;
|
|
680
|
+
const fgAlpha = destFg[alphaIndex];
|
|
681
|
+
const clampedAlpha = Math.max(0, Math.min(1, fgAlpha));
|
|
682
|
+
const rampIndex = Math.min(rampLength - 1, Math.floor(clampedAlpha * rampLength));
|
|
683
|
+
chars[i] = charRamp[rampIndex].charCodeAt(0);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
class BloomEffect {
|
|
689
|
+
_threshold;
|
|
690
|
+
_strength;
|
|
691
|
+
_radius;
|
|
692
|
+
constructor(threshold = 0.8, strength = 0.2, radius = 2) {
|
|
693
|
+
this._threshold = Math.max(0, Math.min(1, threshold));
|
|
694
|
+
this._strength = Math.max(0, strength);
|
|
695
|
+
this._radius = Math.max(0, Math.round(radius));
|
|
696
|
+
}
|
|
697
|
+
set threshold(newThreshold) {
|
|
698
|
+
this._threshold = Math.max(0, Math.min(1, newThreshold));
|
|
699
|
+
}
|
|
700
|
+
get threshold() {
|
|
701
|
+
return this._threshold;
|
|
702
|
+
}
|
|
703
|
+
set strength(newStrength) {
|
|
704
|
+
this._strength = Math.max(0, newStrength);
|
|
705
|
+
}
|
|
706
|
+
get strength() {
|
|
707
|
+
return this._strength;
|
|
708
|
+
}
|
|
709
|
+
set radius(newRadius) {
|
|
710
|
+
this._radius = Math.max(0, Math.round(newRadius));
|
|
711
|
+
}
|
|
712
|
+
get radius() {
|
|
713
|
+
return this._radius;
|
|
714
|
+
}
|
|
715
|
+
apply(buffer) {
|
|
716
|
+
const threshold = this._threshold;
|
|
717
|
+
const strength = this._strength;
|
|
718
|
+
const radius = this._radius;
|
|
719
|
+
if (strength <= 0 || radius <= 0)
|
|
720
|
+
return;
|
|
721
|
+
const width = buffer.width;
|
|
722
|
+
const height = buffer.height;
|
|
723
|
+
const srcFg = Float32Array.from(buffer.buffers.fg);
|
|
724
|
+
const srcBg = Float32Array.from(buffer.buffers.bg);
|
|
725
|
+
const destFg = buffer.buffers.fg;
|
|
726
|
+
const destBg = buffer.buffers.bg;
|
|
727
|
+
const brightPixels = [];
|
|
728
|
+
for (let y = 0;y < height; y++) {
|
|
729
|
+
for (let x = 0;x < width; x++) {
|
|
730
|
+
const index = (y * width + x) * 4;
|
|
731
|
+
const fgLum = 0.299 * srcFg[index] + 0.587 * srcFg[index + 1] + 0.114 * srcFg[index + 2];
|
|
732
|
+
const bgLum = 0.299 * srcBg[index] + 0.587 * srcBg[index + 1] + 0.114 * srcBg[index + 2];
|
|
733
|
+
const lum = Math.max(fgLum, bgLum);
|
|
734
|
+
if (lum > threshold) {
|
|
735
|
+
const intensity = (lum - threshold) / (1 - threshold + 0.000001);
|
|
736
|
+
brightPixels.push({ x, y, intensity: Math.max(0, intensity) });
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (brightPixels.length === 0)
|
|
741
|
+
return;
|
|
742
|
+
destFg.set(srcFg);
|
|
743
|
+
destBg.set(srcBg);
|
|
744
|
+
for (const bright of brightPixels) {
|
|
745
|
+
for (let ky = -radius;ky <= radius; ky++) {
|
|
746
|
+
for (let kx = -radius;kx <= radius; kx++) {
|
|
747
|
+
if (kx === 0 && ky === 0)
|
|
748
|
+
continue;
|
|
749
|
+
const sampleX = bright.x + kx;
|
|
750
|
+
const sampleY = bright.y + ky;
|
|
751
|
+
if (sampleX >= 0 && sampleX < width && sampleY >= 0 && sampleY < height) {
|
|
752
|
+
const distSq = kx * kx + ky * ky;
|
|
753
|
+
const radiusSq = radius * radius;
|
|
754
|
+
if (distSq <= radiusSq) {
|
|
755
|
+
const falloff = 1 - distSq / radiusSq;
|
|
756
|
+
const bloomAmount = bright.intensity * strength * falloff;
|
|
757
|
+
const destIndex = (sampleY * width + sampleX) * 4;
|
|
758
|
+
destFg[destIndex] = Math.min(1, destFg[destIndex] + bloomAmount);
|
|
759
|
+
destFg[destIndex + 1] = Math.min(1, destFg[destIndex + 1] + bloomAmount);
|
|
760
|
+
destFg[destIndex + 2] = Math.min(1, destFg[destIndex + 2] + bloomAmount);
|
|
761
|
+
destBg[destIndex] = Math.min(1, destBg[destIndex] + bloomAmount);
|
|
762
|
+
destBg[destIndex + 1] = Math.min(1, destBg[destIndex + 1] + bloomAmount);
|
|
763
|
+
destBg[destIndex + 2] = Math.min(1, destBg[destIndex + 2] + bloomAmount);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
// src/animation/Timeline.ts
|
|
772
|
+
var easingFunctions = {
|
|
773
|
+
linear: (t2) => t2,
|
|
774
|
+
inQuad: (t2) => t2 * t2,
|
|
775
|
+
outQuad: (t2) => t2 * (2 - t2),
|
|
776
|
+
inOutQuad: (t2) => t2 < 0.5 ? 2 * t2 * t2 : -1 + (4 - 2 * t2) * t2,
|
|
777
|
+
inExpo: (t2) => t2 === 0 ? 0 : Math.pow(2, 10 * (t2 - 1)),
|
|
778
|
+
outExpo: (t2) => t2 === 1 ? 1 : 1 - Math.pow(2, -10 * t2),
|
|
779
|
+
inOutSine: (t2) => -(Math.cos(Math.PI * t2) - 1) / 2,
|
|
780
|
+
outBounce: (t2) => {
|
|
781
|
+
const n1 = 7.5625;
|
|
782
|
+
const d1 = 2.75;
|
|
783
|
+
if (t2 < 1 / d1) {
|
|
784
|
+
return n1 * t2 * t2;
|
|
785
|
+
} else if (t2 < 2 / d1) {
|
|
786
|
+
return n1 * (t2 -= 1.5 / d1) * t2 + 0.75;
|
|
787
|
+
} else if (t2 < 2.5 / d1) {
|
|
788
|
+
return n1 * (t2 -= 2.25 / d1) * t2 + 0.9375;
|
|
789
|
+
} else {
|
|
790
|
+
return n1 * (t2 -= 2.625 / d1) * t2 + 0.984375;
|
|
791
|
+
}
|
|
792
|
+
},
|
|
793
|
+
outElastic: (t2) => {
|
|
794
|
+
const c4 = 2 * Math.PI / 3;
|
|
795
|
+
return t2 === 0 ? 0 : t2 === 1 ? 1 : Math.pow(2, -10 * t2) * Math.sin((t2 * 10 - 0.75) * c4) + 1;
|
|
796
|
+
},
|
|
797
|
+
inBounce: (t2) => 1 - easingFunctions.outBounce(1 - t2),
|
|
798
|
+
inCirc: (t2) => 1 - Math.sqrt(1 - t2 * t2),
|
|
799
|
+
outCirc: (t2) => Math.sqrt(1 - Math.pow(t2 - 1, 2)),
|
|
800
|
+
inOutCirc: (t2) => {
|
|
801
|
+
if ((t2 *= 2) < 1)
|
|
802
|
+
return -0.5 * (Math.sqrt(1 - t2 * t2) - 1);
|
|
803
|
+
return 0.5 * (Math.sqrt(1 - (t2 -= 2) * t2) + 1);
|
|
804
|
+
},
|
|
805
|
+
inBack: (t2, s = 1.70158) => t2 * t2 * ((s + 1) * t2 - s),
|
|
806
|
+
outBack: (t2, s = 1.70158) => --t2 * t2 * ((s + 1) * t2 + s) + 1,
|
|
807
|
+
inOutBack: (t2, s = 1.70158) => {
|
|
808
|
+
s *= 1.525;
|
|
809
|
+
if ((t2 *= 2) < 1)
|
|
810
|
+
return 0.5 * (t2 * t2 * ((s + 1) * t2 - s));
|
|
811
|
+
return 0.5 * ((t2 -= 2) * t2 * ((s + 1) * t2 + s) + 2);
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
function captureInitialValues(item) {
|
|
815
|
+
if (!item.properties)
|
|
816
|
+
return;
|
|
817
|
+
if (!item.initialValues || item.initialValues.length === 0) {
|
|
818
|
+
const initialValues = [];
|
|
819
|
+
for (let i = 0;i < item.target.length; i++) {
|
|
820
|
+
const target = item.target[i];
|
|
821
|
+
const targetInitialValues = {};
|
|
822
|
+
for (const key of Object.keys(item.properties)) {
|
|
823
|
+
if (typeof target[key] === "number") {
|
|
824
|
+
targetInitialValues[key] = target[key];
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
initialValues.push(targetInitialValues);
|
|
828
|
+
}
|
|
829
|
+
item.initialValues = initialValues;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
function applyAnimationAtProgress(item, progress, reversed, timelineTime, deltaTime = 0) {
|
|
833
|
+
if (!item.properties || !item.initialValues)
|
|
834
|
+
return;
|
|
835
|
+
const easingFn = easingFunctions[item.ease || "linear"] || easingFunctions.linear;
|
|
836
|
+
const easedProgress = easingFn(Math.max(0, Math.min(1, progress)));
|
|
837
|
+
const finalProgress = reversed ? 1 - easedProgress : easedProgress;
|
|
838
|
+
for (let i = 0;i < item.target.length; i++) {
|
|
839
|
+
const target = item.target[i];
|
|
840
|
+
const targetInitialValues = item.initialValues[i];
|
|
841
|
+
if (!targetInitialValues)
|
|
842
|
+
continue;
|
|
843
|
+
for (const [key, endValue] of Object.entries(item.properties)) {
|
|
844
|
+
const startValue = targetInitialValues[key];
|
|
845
|
+
const newValue = startValue + (endValue - startValue) * finalProgress;
|
|
846
|
+
target[key] = newValue;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (item.onUpdate) {
|
|
850
|
+
const animation = {
|
|
851
|
+
targets: item.target,
|
|
852
|
+
progress: easedProgress,
|
|
853
|
+
currentTime: timelineTime,
|
|
854
|
+
deltaTime
|
|
855
|
+
};
|
|
856
|
+
item.onUpdate(animation);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
function evaluateAnimation(item, timelineTime, deltaTime = 0) {
|
|
860
|
+
if (timelineTime < item.startTime) {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
const animationTime = timelineTime - item.startTime;
|
|
864
|
+
const duration = item.duration || 0;
|
|
865
|
+
if (timelineTime >= item.startTime && !item.started) {
|
|
866
|
+
captureInitialValues(item);
|
|
867
|
+
if (item.onStart) {
|
|
868
|
+
item.onStart();
|
|
869
|
+
}
|
|
870
|
+
item.started = true;
|
|
871
|
+
}
|
|
872
|
+
if (duration === 0) {
|
|
873
|
+
if (!item.completed) {
|
|
874
|
+
applyAnimationAtProgress(item, 1, false, timelineTime, deltaTime);
|
|
875
|
+
if (item.onComplete) {
|
|
876
|
+
item.onComplete();
|
|
877
|
+
}
|
|
878
|
+
item.completed = true;
|
|
879
|
+
}
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
const maxLoops = !item.loop || item.loop === 1 ? 1 : typeof item.loop === "number" ? item.loop : Infinity;
|
|
883
|
+
const loopDelay = item.loopDelay || 0;
|
|
884
|
+
const cycleTime = duration + loopDelay;
|
|
885
|
+
let currentCycle = Math.floor(animationTime / cycleTime);
|
|
886
|
+
let timeInCycle = animationTime % cycleTime;
|
|
887
|
+
if (item.onLoop && item.currentLoop !== undefined && currentCycle > item.currentLoop && currentCycle < maxLoops) {
|
|
888
|
+
item.onLoop();
|
|
889
|
+
}
|
|
890
|
+
item.currentLoop = currentCycle;
|
|
891
|
+
if (item.onComplete && !item.completed && currentCycle === maxLoops - 1 && timeInCycle >= duration) {
|
|
892
|
+
const finalLoopReversed = (item.alternate || false) && currentCycle % 2 === 1;
|
|
893
|
+
applyAnimationAtProgress(item, 1, finalLoopReversed, timelineTime, deltaTime);
|
|
894
|
+
item.onComplete();
|
|
895
|
+
item.completed = true;
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
if (currentCycle >= maxLoops) {
|
|
899
|
+
if (!item.completed) {
|
|
900
|
+
const finalReversed = (item.alternate || false) && (maxLoops - 1) % 2 === 1;
|
|
901
|
+
applyAnimationAtProgress(item, 1, finalReversed, timelineTime, deltaTime);
|
|
902
|
+
if (item.onComplete) {
|
|
903
|
+
item.onComplete();
|
|
904
|
+
}
|
|
905
|
+
item.completed = true;
|
|
906
|
+
}
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
if (timeInCycle === 0 && animationTime > 0 && currentCycle < maxLoops) {
|
|
910
|
+
currentCycle = currentCycle - 1;
|
|
911
|
+
timeInCycle = cycleTime;
|
|
912
|
+
}
|
|
913
|
+
if (timeInCycle >= duration) {
|
|
914
|
+
const isReversed2 = (item.alternate || false) && currentCycle % 2 === 1;
|
|
915
|
+
applyAnimationAtProgress(item, 1, isReversed2, timelineTime, deltaTime);
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
const progress = timeInCycle / duration;
|
|
919
|
+
const isReversed = (item.alternate || false) && currentCycle % 2 === 1;
|
|
920
|
+
applyAnimationAtProgress(item, progress, isReversed, timelineTime, deltaTime);
|
|
921
|
+
}
|
|
922
|
+
function evaluateCallback(item, timelineTime) {
|
|
923
|
+
if (!item.executed && timelineTime >= item.startTime && item.callback) {
|
|
924
|
+
item.callback();
|
|
925
|
+
item.executed = true;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
function evaluateTimelineSync(item, timelineTime, deltaTime = 0) {
|
|
929
|
+
if (!item.timeline)
|
|
930
|
+
return;
|
|
931
|
+
if (timelineTime < item.startTime) {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
if (!item.timelineStarted) {
|
|
935
|
+
item.timelineStarted = true;
|
|
936
|
+
item.timeline.play();
|
|
937
|
+
const overshoot = timelineTime - item.startTime;
|
|
938
|
+
item.timeline.update(overshoot);
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
item.timeline.update(deltaTime);
|
|
942
|
+
}
|
|
943
|
+
function evaluateItem(item, timelineTime, deltaTime = 0) {
|
|
944
|
+
if (item.type === "animation") {
|
|
945
|
+
evaluateAnimation(item, timelineTime, deltaTime);
|
|
946
|
+
} else if (item.type === "callback") {
|
|
947
|
+
evaluateCallback(item, timelineTime);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
class Timeline {
|
|
952
|
+
items = [];
|
|
953
|
+
subTimelines = [];
|
|
954
|
+
currentTime = 0;
|
|
955
|
+
isPlaying = false;
|
|
956
|
+
isComplete = false;
|
|
957
|
+
duration;
|
|
958
|
+
loop;
|
|
959
|
+
synced = false;
|
|
960
|
+
autoplay;
|
|
961
|
+
onComplete;
|
|
962
|
+
onPause;
|
|
963
|
+
stateChangeListeners = [];
|
|
964
|
+
constructor(options = {}) {
|
|
965
|
+
this.duration = options.duration || 1000;
|
|
966
|
+
this.loop = options.loop === true;
|
|
967
|
+
this.autoplay = options.autoplay !== false;
|
|
968
|
+
this.onComplete = options.onComplete;
|
|
969
|
+
this.onPause = options.onPause;
|
|
970
|
+
}
|
|
971
|
+
addStateChangeListener(listener) {
|
|
972
|
+
this.stateChangeListeners.push(listener);
|
|
973
|
+
}
|
|
974
|
+
removeStateChangeListener(listener) {
|
|
975
|
+
this.stateChangeListeners = this.stateChangeListeners.filter((l) => l !== listener);
|
|
976
|
+
}
|
|
977
|
+
notifyStateChange() {
|
|
978
|
+
for (const listener of this.stateChangeListeners) {
|
|
979
|
+
listener(this);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
add(target, properties, startTime = 0) {
|
|
983
|
+
const resolvedStartTime = typeof startTime === "string" ? 0 : startTime;
|
|
984
|
+
const animationProperties = {};
|
|
985
|
+
for (const key in properties) {
|
|
986
|
+
if (!["duration", "ease", "onUpdate", "onComplete", "onStart", "onLoop", "loop", "loopDelay", "alternate"].includes(key)) {
|
|
987
|
+
if (typeof properties[key] === "number") {
|
|
988
|
+
animationProperties[key] = properties[key];
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
this.items.push({
|
|
993
|
+
type: "animation",
|
|
994
|
+
startTime: resolvedStartTime,
|
|
995
|
+
target: Array.isArray(target) ? target : [target],
|
|
996
|
+
properties: animationProperties,
|
|
997
|
+
initialValues: [],
|
|
998
|
+
duration: properties.duration !== undefined ? properties.duration : 1000,
|
|
999
|
+
ease: properties.ease || "linear",
|
|
1000
|
+
loop: properties.loop,
|
|
1001
|
+
loopDelay: properties.loopDelay || 0,
|
|
1002
|
+
alternate: properties.alternate || false,
|
|
1003
|
+
onUpdate: properties.onUpdate,
|
|
1004
|
+
onComplete: properties.onComplete,
|
|
1005
|
+
onStart: properties.onStart,
|
|
1006
|
+
onLoop: properties.onLoop,
|
|
1007
|
+
completed: false,
|
|
1008
|
+
started: false,
|
|
1009
|
+
currentLoop: 0,
|
|
1010
|
+
once: properties.once ?? false
|
|
1011
|
+
});
|
|
1012
|
+
return this;
|
|
1013
|
+
}
|
|
1014
|
+
once(target, properties) {
|
|
1015
|
+
this.add(target, {
|
|
1016
|
+
...properties,
|
|
1017
|
+
once: true
|
|
1018
|
+
}, this.currentTime);
|
|
1019
|
+
return this;
|
|
1020
|
+
}
|
|
1021
|
+
call(callback, startTime = 0) {
|
|
1022
|
+
const resolvedStartTime = typeof startTime === "string" ? 0 : startTime;
|
|
1023
|
+
this.items.push({
|
|
1024
|
+
type: "callback",
|
|
1025
|
+
startTime: resolvedStartTime,
|
|
1026
|
+
callback,
|
|
1027
|
+
executed: false
|
|
1028
|
+
});
|
|
1029
|
+
return this;
|
|
1030
|
+
}
|
|
1031
|
+
sync(timeline, startTime = 0) {
|
|
1032
|
+
if (timeline.synced) {
|
|
1033
|
+
throw new Error("Timeline already synced");
|
|
1034
|
+
}
|
|
1035
|
+
this.subTimelines.push({
|
|
1036
|
+
type: "timeline",
|
|
1037
|
+
startTime,
|
|
1038
|
+
timeline
|
|
1039
|
+
});
|
|
1040
|
+
timeline.synced = true;
|
|
1041
|
+
return this;
|
|
1042
|
+
}
|
|
1043
|
+
play() {
|
|
1044
|
+
if (this.isComplete) {
|
|
1045
|
+
return this.restart();
|
|
1046
|
+
}
|
|
1047
|
+
this.subTimelines.forEach((subTimeline) => {
|
|
1048
|
+
if (subTimeline.timelineStarted) {
|
|
1049
|
+
subTimeline.timeline.play();
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
this.isPlaying = true;
|
|
1053
|
+
this.notifyStateChange();
|
|
1054
|
+
return this;
|
|
1055
|
+
}
|
|
1056
|
+
pause() {
|
|
1057
|
+
this.subTimelines.forEach((subTimeline) => {
|
|
1058
|
+
subTimeline.timeline.pause();
|
|
1059
|
+
});
|
|
1060
|
+
this.isPlaying = false;
|
|
1061
|
+
if (this.onPause) {
|
|
1062
|
+
this.onPause();
|
|
1063
|
+
}
|
|
1064
|
+
this.notifyStateChange();
|
|
1065
|
+
return this;
|
|
1066
|
+
}
|
|
1067
|
+
resetItems() {
|
|
1068
|
+
this.items.forEach((item) => {
|
|
1069
|
+
if (item.type === "callback") {
|
|
1070
|
+
item.executed = false;
|
|
1071
|
+
} else if (item.type === "animation") {
|
|
1072
|
+
item.completed = false;
|
|
1073
|
+
item.started = false;
|
|
1074
|
+
item.currentLoop = 0;
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
this.subTimelines.forEach((subTimeline) => {
|
|
1078
|
+
subTimeline.timelineStarted = false;
|
|
1079
|
+
if (subTimeline.timeline) {
|
|
1080
|
+
subTimeline.timeline.restart();
|
|
1081
|
+
subTimeline.timeline.pause();
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
restart() {
|
|
1086
|
+
this.isComplete = false;
|
|
1087
|
+
this.currentTime = 0;
|
|
1088
|
+
this.isPlaying = true;
|
|
1089
|
+
this.resetItems();
|
|
1090
|
+
this.notifyStateChange();
|
|
1091
|
+
return this;
|
|
1092
|
+
}
|
|
1093
|
+
update(deltaTime) {
|
|
1094
|
+
for (const subTimeline of this.subTimelines) {
|
|
1095
|
+
evaluateTimelineSync(subTimeline, this.currentTime + deltaTime, deltaTime);
|
|
1096
|
+
}
|
|
1097
|
+
if (!this.isPlaying)
|
|
1098
|
+
return;
|
|
1099
|
+
this.currentTime += deltaTime;
|
|
1100
|
+
for (const item of this.items) {
|
|
1101
|
+
evaluateItem(item, this.currentTime, deltaTime);
|
|
1102
|
+
}
|
|
1103
|
+
for (let i = this.items.length - 1;i >= 0; i--) {
|
|
1104
|
+
const item = this.items[i];
|
|
1105
|
+
if (item.type === "animation" && item.once && item.completed) {
|
|
1106
|
+
this.items.splice(i, 1);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
if (this.loop && this.currentTime >= this.duration) {
|
|
1110
|
+
const overshoot = this.currentTime % this.duration;
|
|
1111
|
+
this.resetItems();
|
|
1112
|
+
this.currentTime = 0;
|
|
1113
|
+
if (overshoot > 0) {
|
|
1114
|
+
this.update(overshoot);
|
|
1115
|
+
}
|
|
1116
|
+
} else if (!this.loop && this.currentTime >= this.duration) {
|
|
1117
|
+
this.currentTime = this.duration;
|
|
1118
|
+
this.isPlaying = false;
|
|
1119
|
+
this.isComplete = true;
|
|
1120
|
+
if (this.onComplete) {
|
|
1121
|
+
this.onComplete();
|
|
1122
|
+
}
|
|
1123
|
+
this.notifyStateChange();
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
class TimelineEngine {
|
|
1129
|
+
timelines = new Set;
|
|
1130
|
+
renderer = null;
|
|
1131
|
+
frameCallback = null;
|
|
1132
|
+
isLive = false;
|
|
1133
|
+
defaults = {
|
|
1134
|
+
frameRate: 60
|
|
1135
|
+
};
|
|
1136
|
+
attach(renderer) {
|
|
1137
|
+
if (this.renderer) {
|
|
1138
|
+
this.detach();
|
|
1139
|
+
}
|
|
1140
|
+
this.renderer = renderer;
|
|
1141
|
+
this.frameCallback = async (deltaTime) => {
|
|
1142
|
+
this.update(deltaTime);
|
|
1143
|
+
};
|
|
1144
|
+
renderer.setFrameCallback(this.frameCallback);
|
|
1145
|
+
}
|
|
1146
|
+
detach() {
|
|
1147
|
+
if (this.renderer && this.frameCallback) {
|
|
1148
|
+
this.renderer.removeFrameCallback(this.frameCallback);
|
|
1149
|
+
if (this.isLive) {
|
|
1150
|
+
this.renderer.dropLive();
|
|
1151
|
+
this.isLive = false;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
this.renderer = null;
|
|
1155
|
+
this.frameCallback = null;
|
|
1156
|
+
}
|
|
1157
|
+
updateLiveState() {
|
|
1158
|
+
if (!this.renderer)
|
|
1159
|
+
return;
|
|
1160
|
+
const hasRunningTimelines = Array.from(this.timelines).some((timeline) => !timeline.synced && timeline.isPlaying && !timeline.isComplete);
|
|
1161
|
+
if (hasRunningTimelines && !this.isLive) {
|
|
1162
|
+
this.renderer.requestLive();
|
|
1163
|
+
this.isLive = true;
|
|
1164
|
+
} else if (!hasRunningTimelines && this.isLive) {
|
|
1165
|
+
this.renderer.dropLive();
|
|
1166
|
+
this.isLive = false;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
onTimelineStateChange = (timeline) => {
|
|
1170
|
+
this.updateLiveState();
|
|
1171
|
+
};
|
|
1172
|
+
register(timeline) {
|
|
1173
|
+
if (!this.timelines.has(timeline)) {
|
|
1174
|
+
this.timelines.add(timeline);
|
|
1175
|
+
timeline.addStateChangeListener(this.onTimelineStateChange);
|
|
1176
|
+
this.updateLiveState();
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
unregister(timeline) {
|
|
1180
|
+
if (this.timelines.has(timeline)) {
|
|
1181
|
+
this.timelines.delete(timeline);
|
|
1182
|
+
timeline.removeStateChangeListener(this.onTimelineStateChange);
|
|
1183
|
+
this.updateLiveState();
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
clear() {
|
|
1187
|
+
for (const timeline of this.timelines) {
|
|
1188
|
+
timeline.removeStateChangeListener(this.onTimelineStateChange);
|
|
1189
|
+
}
|
|
1190
|
+
this.timelines.clear();
|
|
1191
|
+
this.updateLiveState();
|
|
1192
|
+
}
|
|
1193
|
+
update(deltaTime) {
|
|
1194
|
+
for (const timeline of this.timelines) {
|
|
1195
|
+
if (!timeline.synced) {
|
|
1196
|
+
timeline.update(deltaTime);
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
var engine = new TimelineEngine;
|
|
1202
|
+
function createTimeline(options = {}) {
|
|
1203
|
+
const timeline = new Timeline(options);
|
|
1204
|
+
if (options.autoplay !== false) {
|
|
1205
|
+
timeline.play();
|
|
1206
|
+
}
|
|
1207
|
+
engine.register(timeline);
|
|
1208
|
+
return timeline;
|
|
1209
|
+
}
|
|
1210
|
+
// src/renderables/Box.ts
|
|
1211
|
+
function isGapType(value) {
|
|
1212
|
+
if (value === undefined) {
|
|
1213
|
+
return true;
|
|
1214
|
+
}
|
|
1215
|
+
if (typeof value === "number" && !Number.isNaN(value)) {
|
|
1216
|
+
return true;
|
|
1217
|
+
}
|
|
1218
|
+
return isValidPercentage(value);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
class BoxRenderable extends Renderable {
|
|
1222
|
+
_backgroundColor;
|
|
1223
|
+
_border;
|
|
1224
|
+
_borderStyle;
|
|
1225
|
+
_borderColor;
|
|
1226
|
+
_focusedBorderColor;
|
|
1227
|
+
_customBorderCharsObj;
|
|
1228
|
+
_customBorderChars;
|
|
1229
|
+
borderSides;
|
|
1230
|
+
shouldFill;
|
|
1231
|
+
_title;
|
|
1232
|
+
_titleAlignment;
|
|
1233
|
+
_defaultOptions = {
|
|
1234
|
+
backgroundColor: "transparent",
|
|
1235
|
+
borderStyle: "single",
|
|
1236
|
+
border: false,
|
|
1237
|
+
borderColor: "#FFFFFF",
|
|
1238
|
+
shouldFill: true,
|
|
1239
|
+
titleAlignment: "left",
|
|
1240
|
+
focusedBorderColor: "#00AAFF"
|
|
1241
|
+
};
|
|
1242
|
+
constructor(ctx, options) {
|
|
1243
|
+
super(ctx, options);
|
|
1244
|
+
this._backgroundColor = parseColor(options.backgroundColor || this._defaultOptions.backgroundColor);
|
|
1245
|
+
this._border = options.border ?? this._defaultOptions.border;
|
|
1246
|
+
if (!options.border && (options.borderStyle || options.borderColor || options.focusedBorderColor || options.customBorderChars)) {
|
|
1247
|
+
this._border = true;
|
|
1248
|
+
}
|
|
1249
|
+
this._borderStyle = options.borderStyle || this._defaultOptions.borderStyle;
|
|
1250
|
+
this._borderColor = parseColor(options.borderColor || this._defaultOptions.borderColor);
|
|
1251
|
+
this._focusedBorderColor = parseColor(options.focusedBorderColor || this._defaultOptions.focusedBorderColor);
|
|
1252
|
+
this._customBorderCharsObj = options.customBorderChars;
|
|
1253
|
+
this._customBorderChars = this._customBorderCharsObj ? borderCharsToArray(this._customBorderCharsObj) : undefined;
|
|
1254
|
+
this.borderSides = getBorderSides(this._border);
|
|
1255
|
+
this.shouldFill = options.shouldFill ?? this._defaultOptions.shouldFill;
|
|
1256
|
+
this._title = options.title;
|
|
1257
|
+
this._titleAlignment = options.titleAlignment || this._defaultOptions.titleAlignment;
|
|
1258
|
+
this.applyYogaBorders();
|
|
1259
|
+
const hasInitialGapProps = options.gap !== undefined || options.rowGap !== undefined || options.columnGap !== undefined;
|
|
1260
|
+
if (hasInitialGapProps) {
|
|
1261
|
+
this.applyYogaGap(options);
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
get customBorderChars() {
|
|
1265
|
+
return this._customBorderCharsObj;
|
|
1266
|
+
}
|
|
1267
|
+
set customBorderChars(value) {
|
|
1268
|
+
this._customBorderCharsObj = value;
|
|
1269
|
+
this._customBorderChars = value ? borderCharsToArray(value) : undefined;
|
|
1270
|
+
this.requestRender();
|
|
1271
|
+
}
|
|
1272
|
+
get backgroundColor() {
|
|
1273
|
+
return this._backgroundColor;
|
|
1274
|
+
}
|
|
1275
|
+
set backgroundColor(value) {
|
|
1276
|
+
const newColor = parseColor(value ?? this._defaultOptions.backgroundColor);
|
|
1277
|
+
if (this._backgroundColor !== newColor) {
|
|
1278
|
+
this._backgroundColor = newColor;
|
|
1279
|
+
this.requestRender();
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
get border() {
|
|
1283
|
+
return this._border;
|
|
1284
|
+
}
|
|
1285
|
+
set border(value) {
|
|
1286
|
+
if (this._border !== value) {
|
|
1287
|
+
this._border = value;
|
|
1288
|
+
this.borderSides = getBorderSides(value);
|
|
1289
|
+
this.applyYogaBorders();
|
|
1290
|
+
this.requestRender();
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
get borderStyle() {
|
|
1294
|
+
return this._borderStyle;
|
|
1295
|
+
}
|
|
1296
|
+
set borderStyle(value) {
|
|
1297
|
+
let _value = value ?? this._defaultOptions.borderStyle;
|
|
1298
|
+
if (this._borderStyle !== _value) {
|
|
1299
|
+
this._borderStyle = _value;
|
|
1300
|
+
this._customBorderChars = undefined;
|
|
1301
|
+
this.requestRender();
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
get borderColor() {
|
|
1305
|
+
return this._borderColor;
|
|
1306
|
+
}
|
|
1307
|
+
set borderColor(value) {
|
|
1308
|
+
const newColor = parseColor(value ?? this._defaultOptions.borderColor);
|
|
1309
|
+
if (this._borderColor !== newColor) {
|
|
1310
|
+
this._borderColor = newColor;
|
|
1311
|
+
this.requestRender();
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
get focusedBorderColor() {
|
|
1315
|
+
return this._focusedBorderColor;
|
|
1316
|
+
}
|
|
1317
|
+
set focusedBorderColor(value) {
|
|
1318
|
+
const newColor = parseColor(value ?? this._defaultOptions.focusedBorderColor);
|
|
1319
|
+
if (this._focusedBorderColor !== newColor) {
|
|
1320
|
+
this._focusedBorderColor = newColor;
|
|
1321
|
+
if (this._focused) {
|
|
1322
|
+
this.requestRender();
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
get title() {
|
|
1327
|
+
return this._title;
|
|
1328
|
+
}
|
|
1329
|
+
set title(value) {
|
|
1330
|
+
if (this._title !== value) {
|
|
1331
|
+
this._title = value;
|
|
1332
|
+
this.requestRender();
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
get titleAlignment() {
|
|
1336
|
+
return this._titleAlignment;
|
|
1337
|
+
}
|
|
1338
|
+
set titleAlignment(value) {
|
|
1339
|
+
if (this._titleAlignment !== value) {
|
|
1340
|
+
this._titleAlignment = value;
|
|
1341
|
+
this.requestRender();
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
renderSelf(buffer) {
|
|
1345
|
+
const currentBorderColor = this._focused ? this._focusedBorderColor : this._borderColor;
|
|
1346
|
+
buffer.drawBox({
|
|
1347
|
+
x: this.x,
|
|
1348
|
+
y: this.y,
|
|
1349
|
+
width: this.width,
|
|
1350
|
+
height: this.height,
|
|
1351
|
+
borderStyle: this._borderStyle,
|
|
1352
|
+
customBorderChars: this._customBorderChars,
|
|
1353
|
+
border: this._border,
|
|
1354
|
+
borderColor: currentBorderColor,
|
|
1355
|
+
backgroundColor: this._backgroundColor,
|
|
1356
|
+
shouldFill: this.shouldFill,
|
|
1357
|
+
title: this._title,
|
|
1358
|
+
titleAlignment: this._titleAlignment
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
getScissorRect() {
|
|
1362
|
+
const baseRect = super.getScissorRect();
|
|
1363
|
+
if (!this.borderSides.top && !this.borderSides.right && !this.borderSides.bottom && !this.borderSides.left) {
|
|
1364
|
+
return baseRect;
|
|
1365
|
+
}
|
|
1366
|
+
const leftInset = this.borderSides.left ? 1 : 0;
|
|
1367
|
+
const rightInset = this.borderSides.right ? 1 : 0;
|
|
1368
|
+
const topInset = this.borderSides.top ? 1 : 0;
|
|
1369
|
+
const bottomInset = this.borderSides.bottom ? 1 : 0;
|
|
1370
|
+
return {
|
|
1371
|
+
x: baseRect.x + leftInset,
|
|
1372
|
+
y: baseRect.y + topInset,
|
|
1373
|
+
width: Math.max(0, baseRect.width - leftInset - rightInset),
|
|
1374
|
+
height: Math.max(0, baseRect.height - topInset - bottomInset)
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
applyYogaBorders() {
|
|
1378
|
+
const node = this.layoutNode.yogaNode;
|
|
1379
|
+
node.setBorder(Edge.Left, this.borderSides.left ? 1 : 0);
|
|
1380
|
+
node.setBorder(Edge.Right, this.borderSides.right ? 1 : 0);
|
|
1381
|
+
node.setBorder(Edge.Top, this.borderSides.top ? 1 : 0);
|
|
1382
|
+
node.setBorder(Edge.Bottom, this.borderSides.bottom ? 1 : 0);
|
|
1383
|
+
this.requestRender();
|
|
1384
|
+
}
|
|
1385
|
+
applyYogaGap(options) {
|
|
1386
|
+
const node = this.layoutNode.yogaNode;
|
|
1387
|
+
if (isGapType(options.gap)) {
|
|
1388
|
+
node.setGap(Gutter.All, options.gap);
|
|
1389
|
+
}
|
|
1390
|
+
if (isGapType(options.rowGap)) {
|
|
1391
|
+
node.setGap(Gutter.Row, options.rowGap);
|
|
1392
|
+
}
|
|
1393
|
+
if (isGapType(options.columnGap)) {
|
|
1394
|
+
node.setGap(Gutter.Column, options.columnGap);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
set gap(gap) {
|
|
1398
|
+
if (isGapType(gap)) {
|
|
1399
|
+
this.layoutNode.yogaNode.setGap(Gutter.All, gap);
|
|
1400
|
+
this.requestRender();
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
set rowGap(rowGap) {
|
|
1404
|
+
if (isGapType(rowGap)) {
|
|
1405
|
+
this.layoutNode.yogaNode.setGap(Gutter.Row, rowGap);
|
|
1406
|
+
this.requestRender();
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
set columnGap(columnGap) {
|
|
1410
|
+
if (isGapType(columnGap)) {
|
|
1411
|
+
this.layoutNode.yogaNode.setGap(Gutter.Column, columnGap);
|
|
1412
|
+
this.requestRender();
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
// src/renderables/FrameBuffer.ts
|
|
1417
|
+
class FrameBufferRenderable extends Renderable {
|
|
1418
|
+
frameBuffer;
|
|
1419
|
+
respectAlpha;
|
|
1420
|
+
constructor(ctx, options) {
|
|
1421
|
+
super(ctx, options);
|
|
1422
|
+
this.respectAlpha = options.respectAlpha || false;
|
|
1423
|
+
this.frameBuffer = OptimizedBuffer.create(options.width, options.height, this._ctx.widthMethod, {
|
|
1424
|
+
respectAlpha: this.respectAlpha,
|
|
1425
|
+
id: options.id || `framebufferrenderable-${this.id}`
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
onResize(width, height) {
|
|
1429
|
+
if (width <= 0 || height <= 0) {
|
|
1430
|
+
throw new Error(`Invalid resize dimensions for FrameBufferRenderable ${this.id}: ${width}x${height}`);
|
|
1431
|
+
}
|
|
1432
|
+
this.frameBuffer.resize(width, height);
|
|
1433
|
+
super.onResize(width, height);
|
|
1434
|
+
this.requestRender();
|
|
1435
|
+
}
|
|
1436
|
+
renderSelf(buffer) {
|
|
1437
|
+
if (!this.visible)
|
|
1438
|
+
return;
|
|
1439
|
+
buffer.drawFrameBuffer(this.x, this.y, this.frameBuffer);
|
|
1440
|
+
}
|
|
1441
|
+
destroySelf() {
|
|
1442
|
+
this.frameBuffer?.destroy();
|
|
1443
|
+
super.destroySelf();
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
// src/renderables/Text.ts
|
|
1447
|
+
class TextRenderable extends Renderable {
|
|
1448
|
+
selectable = true;
|
|
1449
|
+
_text;
|
|
1450
|
+
_defaultFg;
|
|
1451
|
+
_defaultBg;
|
|
1452
|
+
_defaultAttributes;
|
|
1453
|
+
_selectionBg;
|
|
1454
|
+
_selectionFg;
|
|
1455
|
+
lastLocalSelection = null;
|
|
1456
|
+
textBuffer;
|
|
1457
|
+
_lineInfo = { lineStarts: [], lineWidths: [] };
|
|
1458
|
+
_defaultOptions = {
|
|
1459
|
+
content: "",
|
|
1460
|
+
fg: RGBA.fromValues(1, 1, 1, 1),
|
|
1461
|
+
bg: RGBA.fromValues(0, 0, 0, 0),
|
|
1462
|
+
selectionBg: undefined,
|
|
1463
|
+
selectionFg: undefined,
|
|
1464
|
+
selectable: true,
|
|
1465
|
+
attributes: 0
|
|
1466
|
+
};
|
|
1467
|
+
constructor(ctx, options) {
|
|
1468
|
+
super(ctx, options);
|
|
1469
|
+
const content = options.content ?? this._defaultOptions.content;
|
|
1470
|
+
const styledText = typeof content === "string" ? stringToStyledText(content) : content;
|
|
1471
|
+
this._text = styledText;
|
|
1472
|
+
this._defaultFg = parseColor(options.fg ?? this._defaultOptions.fg);
|
|
1473
|
+
this._defaultBg = parseColor(options.bg ?? this._defaultOptions.bg);
|
|
1474
|
+
this._defaultAttributes = options.attributes ?? this._defaultOptions.attributes;
|
|
1475
|
+
this._selectionBg = options.selectionBg ? parseColor(options.selectionBg) : this._defaultOptions.selectionBg;
|
|
1476
|
+
this._selectionFg = options.selectionFg ? parseColor(options.selectionFg) : this._defaultOptions.selectionFg;
|
|
1477
|
+
this.selectable = options.selectable ?? this._defaultOptions.selectable;
|
|
1478
|
+
this.textBuffer = TextBuffer.create(64, this._ctx.widthMethod);
|
|
1479
|
+
this.textBuffer.setDefaultFg(this._defaultFg);
|
|
1480
|
+
this.textBuffer.setDefaultBg(this._defaultBg);
|
|
1481
|
+
this.textBuffer.setDefaultAttributes(this._defaultAttributes);
|
|
1482
|
+
this.setupMeasureFunc();
|
|
1483
|
+
this.updateTextBuffer(styledText);
|
|
1484
|
+
this._text.mount(this);
|
|
1485
|
+
this.updateTextInfo();
|
|
1486
|
+
}
|
|
1487
|
+
updateTextBuffer(styledText) {
|
|
1488
|
+
this.textBuffer.setStyledText(styledText);
|
|
1489
|
+
this.clearChunks(styledText);
|
|
1490
|
+
}
|
|
1491
|
+
clearChunks(styledText) {
|
|
1492
|
+
styledText.chunks.forEach((chunk) => {
|
|
1493
|
+
chunk.text = undefined;
|
|
1494
|
+
chunk.plainText = undefined;
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
get content() {
|
|
1498
|
+
return this._text;
|
|
1499
|
+
}
|
|
1500
|
+
get plainText() {
|
|
1501
|
+
return this.textBuffer.getPlainText();
|
|
1502
|
+
}
|
|
1503
|
+
get textLength() {
|
|
1504
|
+
return this.textBuffer.length;
|
|
1505
|
+
}
|
|
1506
|
+
get chunks() {
|
|
1507
|
+
return this._text.chunks;
|
|
1508
|
+
}
|
|
1509
|
+
set content(value) {
|
|
1510
|
+
const styledText = typeof value === "string" ? stringToStyledText(value) : value;
|
|
1511
|
+
if (this._text !== styledText) {
|
|
1512
|
+
this._text = styledText;
|
|
1513
|
+
styledText.mount(this);
|
|
1514
|
+
this.updateTextBuffer(styledText);
|
|
1515
|
+
this.updateTextInfo();
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
get fg() {
|
|
1519
|
+
return this._defaultFg;
|
|
1520
|
+
}
|
|
1521
|
+
set fg(value) {
|
|
1522
|
+
const newColor = parseColor(value ?? this._defaultOptions.fg);
|
|
1523
|
+
if (this._defaultFg !== newColor) {
|
|
1524
|
+
this._defaultFg = newColor;
|
|
1525
|
+
this.textBuffer.setDefaultFg(this._defaultFg);
|
|
1526
|
+
this.requestRender();
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
get selectionBg() {
|
|
1530
|
+
return this._selectionBg;
|
|
1531
|
+
}
|
|
1532
|
+
set selectionBg(value) {
|
|
1533
|
+
const newColor = value ? parseColor(value) : this._defaultOptions.selectionBg;
|
|
1534
|
+
if (this._selectionBg !== newColor) {
|
|
1535
|
+
this._selectionBg = newColor;
|
|
1536
|
+
if (this.lastLocalSelection) {
|
|
1537
|
+
this.updateLocalSelection(this.lastLocalSelection);
|
|
1538
|
+
}
|
|
1539
|
+
this.requestRender();
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
get selectionFg() {
|
|
1543
|
+
return this._selectionFg;
|
|
1544
|
+
}
|
|
1545
|
+
set selectionFg(value) {
|
|
1546
|
+
const newColor = value ? parseColor(value) : this._defaultOptions.selectionFg;
|
|
1547
|
+
if (this._selectionFg !== newColor) {
|
|
1548
|
+
this._selectionFg = newColor;
|
|
1549
|
+
if (this.lastLocalSelection) {
|
|
1550
|
+
this.updateLocalSelection(this.lastLocalSelection);
|
|
1551
|
+
}
|
|
1552
|
+
this.requestRender();
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
get bg() {
|
|
1556
|
+
return this._defaultBg;
|
|
1557
|
+
}
|
|
1558
|
+
set bg(value) {
|
|
1559
|
+
const newColor = parseColor(value ?? this._defaultOptions.bg);
|
|
1560
|
+
if (this._defaultBg !== newColor) {
|
|
1561
|
+
this._defaultBg = newColor;
|
|
1562
|
+
this.textBuffer.setDefaultBg(this._defaultBg);
|
|
1563
|
+
this.requestRender();
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
get attributes() {
|
|
1567
|
+
return this._defaultAttributes;
|
|
1568
|
+
}
|
|
1569
|
+
set attributes(value) {
|
|
1570
|
+
if (this._defaultAttributes !== value) {
|
|
1571
|
+
this._defaultAttributes = value;
|
|
1572
|
+
this.textBuffer.setDefaultAttributes(this._defaultAttributes);
|
|
1573
|
+
this.requestRender();
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
onResize(width, height) {
|
|
1577
|
+
if (this.lastLocalSelection) {
|
|
1578
|
+
const changed = this.updateLocalSelection(this.lastLocalSelection);
|
|
1579
|
+
if (changed) {
|
|
1580
|
+
this.requestRender();
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
updateLocalSelection(localSelection) {
|
|
1585
|
+
if (!localSelection?.isActive) {
|
|
1586
|
+
this.textBuffer.resetLocalSelection();
|
|
1587
|
+
return true;
|
|
1588
|
+
}
|
|
1589
|
+
return this.textBuffer.setLocalSelection(localSelection.anchorX, localSelection.anchorY, localSelection.focusX, localSelection.focusY, this._selectionBg, this._selectionFg);
|
|
1590
|
+
}
|
|
1591
|
+
updateTextInfo() {
|
|
1592
|
+
const lineInfo = this.textBuffer.lineInfo;
|
|
1593
|
+
this._lineInfo.lineStarts = lineInfo.lineStarts;
|
|
1594
|
+
this._lineInfo.lineWidths = lineInfo.lineWidths;
|
|
1595
|
+
if (this.lastLocalSelection) {
|
|
1596
|
+
const changed = this.updateLocalSelection(this.lastLocalSelection);
|
|
1597
|
+
if (changed) {
|
|
1598
|
+
this.requestRender();
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
this.layoutNode.yogaNode.markDirty();
|
|
1602
|
+
this.requestRender();
|
|
1603
|
+
}
|
|
1604
|
+
setupMeasureFunc() {
|
|
1605
|
+
const measureFunc = (width, widthMode, height, heightMode) => {
|
|
1606
|
+
const maxLineWidth = Math.max(...this._lineInfo.lineWidths, 0);
|
|
1607
|
+
const numLines = this._lineInfo.lineStarts.length || 1;
|
|
1608
|
+
let measuredWidth = maxLineWidth;
|
|
1609
|
+
let measuredHeight = numLines;
|
|
1610
|
+
if (widthMode === MeasureMode.Exactly) {
|
|
1611
|
+
measuredWidth = width;
|
|
1612
|
+
} else if (widthMode === MeasureMode.AtMost) {
|
|
1613
|
+
measuredWidth = Math.min(maxLineWidth, width);
|
|
1614
|
+
}
|
|
1615
|
+
if (heightMode === MeasureMode.Exactly) {
|
|
1616
|
+
measuredHeight = height;
|
|
1617
|
+
} else if (heightMode === MeasureMode.AtMost) {
|
|
1618
|
+
measuredHeight = Math.min(numLines, height);
|
|
1619
|
+
}
|
|
1620
|
+
return {
|
|
1621
|
+
width: Math.max(1, measuredWidth),
|
|
1622
|
+
height: Math.max(1, measuredHeight)
|
|
1623
|
+
};
|
|
1624
|
+
};
|
|
1625
|
+
this.layoutNode.yogaNode.setMeasureFunc(measureFunc);
|
|
1626
|
+
}
|
|
1627
|
+
insertChunk(chunk, index) {
|
|
1628
|
+
this.textBuffer.insertEncodedChunkGroup(index ?? this.textBuffer.chunkGroupCount, chunk.text, chunk.fg, chunk.bg, chunk.attributes);
|
|
1629
|
+
this.updateTextInfo();
|
|
1630
|
+
this.clearChunks(this._text);
|
|
1631
|
+
}
|
|
1632
|
+
removeChunk(chunk) {
|
|
1633
|
+
const index = this._text.chunks.indexOf(chunk);
|
|
1634
|
+
if (index === -1)
|
|
1635
|
+
return;
|
|
1636
|
+
this.textBuffer.removeChunkGroup(index);
|
|
1637
|
+
this.updateTextInfo();
|
|
1638
|
+
this.clearChunks(this._text);
|
|
1639
|
+
}
|
|
1640
|
+
replaceChunk(chunk, oldChunk) {
|
|
1641
|
+
const index = this._text.chunks.indexOf(oldChunk);
|
|
1642
|
+
if (index === -1)
|
|
1643
|
+
return;
|
|
1644
|
+
this.textBuffer.replaceEncodedChunkGroup(index, chunk.text, chunk.fg, chunk.bg, chunk.attributes);
|
|
1645
|
+
this.updateTextInfo();
|
|
1646
|
+
this.clearChunks(this._text);
|
|
1647
|
+
}
|
|
1648
|
+
shouldStartSelection(x, y) {
|
|
1649
|
+
if (!this.selectable)
|
|
1650
|
+
return false;
|
|
1651
|
+
const localX = x - this.x;
|
|
1652
|
+
const localY = y - this.y;
|
|
1653
|
+
return localX >= 0 && localX < this.width && localY >= 0 && localY < this.height;
|
|
1654
|
+
}
|
|
1655
|
+
onSelectionChanged(selection) {
|
|
1656
|
+
const localSelection = convertGlobalToLocalSelection(selection, this.x, this.y);
|
|
1657
|
+
this.lastLocalSelection = localSelection;
|
|
1658
|
+
const changed = this.updateLocalSelection(localSelection);
|
|
1659
|
+
if (changed) {
|
|
1660
|
+
this.requestRender();
|
|
1661
|
+
}
|
|
1662
|
+
return this.hasSelection();
|
|
1663
|
+
}
|
|
1664
|
+
getSelectedText() {
|
|
1665
|
+
return this.textBuffer.getSelectedText();
|
|
1666
|
+
}
|
|
1667
|
+
hasSelection() {
|
|
1668
|
+
return this.textBuffer.hasSelection();
|
|
1669
|
+
}
|
|
1670
|
+
getSelection() {
|
|
1671
|
+
return this.textBuffer.getSelection();
|
|
1672
|
+
}
|
|
1673
|
+
renderSelf(buffer) {
|
|
1674
|
+
if (this.textBuffer.ptr) {
|
|
1675
|
+
const clipRect = {
|
|
1676
|
+
x: this.x,
|
|
1677
|
+
y: this.y,
|
|
1678
|
+
width: this.width,
|
|
1679
|
+
height: this.height
|
|
1680
|
+
};
|
|
1681
|
+
buffer.drawTextBuffer(this.textBuffer, this.x, this.y, clipRect);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
destroy() {
|
|
1685
|
+
this.textBuffer.destroy();
|
|
1686
|
+
super.destroy();
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
// src/renderables/ASCIIFont.ts
|
|
1690
|
+
class ASCIIFontRenderable extends FrameBufferRenderable {
|
|
1691
|
+
selectable = true;
|
|
1692
|
+
_text;
|
|
1693
|
+
_font;
|
|
1694
|
+
_fg;
|
|
1695
|
+
_bg;
|
|
1696
|
+
_selectionBg;
|
|
1697
|
+
_selectionFg;
|
|
1698
|
+
lastLocalSelection = null;
|
|
1699
|
+
selectionHelper;
|
|
1700
|
+
constructor(ctx, options) {
|
|
1701
|
+
const font = options.font || "tiny";
|
|
1702
|
+
const text = options.text || "";
|
|
1703
|
+
const measurements = measureText({ text, font });
|
|
1704
|
+
super(ctx, {
|
|
1705
|
+
...options,
|
|
1706
|
+
width: measurements.width || 1,
|
|
1707
|
+
height: measurements.height || 1,
|
|
1708
|
+
respectAlpha: true
|
|
1709
|
+
});
|
|
1710
|
+
this._text = text;
|
|
1711
|
+
this._font = font;
|
|
1712
|
+
this._fg = Array.isArray(options.fg) ? options.fg : [options.fg || RGBA.fromInts(255, 255, 255, 255)];
|
|
1713
|
+
this._bg = options.bg || RGBA.fromValues(0, 0, 0, 0);
|
|
1714
|
+
this._selectionBg = options.selectionBg ? parseColor(options.selectionBg) : undefined;
|
|
1715
|
+
this._selectionFg = options.selectionFg ? parseColor(options.selectionFg) : undefined;
|
|
1716
|
+
this.selectable = options.selectable ?? true;
|
|
1717
|
+
this.selectionHelper = new ASCIIFontSelectionHelper(() => this._text, () => this._font);
|
|
1718
|
+
this.renderFontToBuffer();
|
|
1719
|
+
}
|
|
1720
|
+
get text() {
|
|
1721
|
+
return this._text;
|
|
1722
|
+
}
|
|
1723
|
+
set text(value) {
|
|
1724
|
+
this._text = value;
|
|
1725
|
+
this.updateDimensions();
|
|
1726
|
+
if (this.lastLocalSelection) {
|
|
1727
|
+
this.selectionHelper.onLocalSelectionChanged(this.lastLocalSelection, this.width, this.height);
|
|
1728
|
+
}
|
|
1729
|
+
this.renderFontToBuffer();
|
|
1730
|
+
this.requestRender();
|
|
1731
|
+
}
|
|
1732
|
+
get font() {
|
|
1733
|
+
return this._font;
|
|
1734
|
+
}
|
|
1735
|
+
set font(value) {
|
|
1736
|
+
this._font = value;
|
|
1737
|
+
this.updateDimensions();
|
|
1738
|
+
if (this.lastLocalSelection) {
|
|
1739
|
+
this.selectionHelper.onLocalSelectionChanged(this.lastLocalSelection, this.width, this.height);
|
|
1740
|
+
}
|
|
1741
|
+
this.renderFontToBuffer();
|
|
1742
|
+
this.requestRender();
|
|
1743
|
+
}
|
|
1744
|
+
get fg() {
|
|
1745
|
+
return this._fg;
|
|
1746
|
+
}
|
|
1747
|
+
set fg(value) {
|
|
1748
|
+
if (Array.isArray(value)) {
|
|
1749
|
+
this._fg = value.map((color) => typeof color === "string" ? parseColor(color) : color);
|
|
1750
|
+
} else {
|
|
1751
|
+
this._fg = [typeof value === "string" ? parseColor(value) : value];
|
|
1752
|
+
}
|
|
1753
|
+
this.renderFontToBuffer();
|
|
1754
|
+
this.requestRender();
|
|
1755
|
+
}
|
|
1756
|
+
get bg() {
|
|
1757
|
+
return this._bg;
|
|
1758
|
+
}
|
|
1759
|
+
set bg(value) {
|
|
1760
|
+
this._bg = typeof value === "string" ? parseColor(value) : value;
|
|
1761
|
+
this.renderFontToBuffer();
|
|
1762
|
+
this.requestRender();
|
|
1763
|
+
}
|
|
1764
|
+
updateDimensions() {
|
|
1765
|
+
const measurements = measureText({ text: this._text, font: this._font });
|
|
1766
|
+
this.width = measurements.width;
|
|
1767
|
+
this.height = measurements.height;
|
|
1768
|
+
}
|
|
1769
|
+
shouldStartSelection(x, y) {
|
|
1770
|
+
const localX = x - this.x;
|
|
1771
|
+
const localY = y - this.y;
|
|
1772
|
+
return this.selectionHelper.shouldStartSelection(localX, localY, this.width, this.height);
|
|
1773
|
+
}
|
|
1774
|
+
onSelectionChanged(selection) {
|
|
1775
|
+
const localSelection = convertGlobalToLocalSelection(selection, this.x, this.y);
|
|
1776
|
+
this.lastLocalSelection = localSelection;
|
|
1777
|
+
const changed = this.selectionHelper.onLocalSelectionChanged(localSelection, this.width, this.height);
|
|
1778
|
+
if (changed) {
|
|
1779
|
+
this.renderFontToBuffer();
|
|
1780
|
+
this.requestRender();
|
|
1781
|
+
}
|
|
1782
|
+
return changed;
|
|
1783
|
+
}
|
|
1784
|
+
getSelectedText() {
|
|
1785
|
+
const selection = this.selectionHelper.getSelection();
|
|
1786
|
+
if (!selection)
|
|
1787
|
+
return "";
|
|
1788
|
+
return this._text.slice(selection.start, selection.end);
|
|
1789
|
+
}
|
|
1790
|
+
hasSelection() {
|
|
1791
|
+
return this.selectionHelper.hasSelection();
|
|
1792
|
+
}
|
|
1793
|
+
onResize(width, height) {
|
|
1794
|
+
super.onResize(width, height);
|
|
1795
|
+
this.renderFontToBuffer();
|
|
1796
|
+
}
|
|
1797
|
+
renderFontToBuffer() {
|
|
1798
|
+
this.frameBuffer.clear(this._bg);
|
|
1799
|
+
renderFontToFrameBuffer(this.frameBuffer, {
|
|
1800
|
+
text: this._text,
|
|
1801
|
+
x: 0,
|
|
1802
|
+
y: 0,
|
|
1803
|
+
fg: this._fg,
|
|
1804
|
+
bg: this._bg,
|
|
1805
|
+
font: this._font
|
|
1806
|
+
});
|
|
1807
|
+
const selection = this.selectionHelper.getSelection();
|
|
1808
|
+
if (selection && (this._selectionBg || this._selectionFg)) {
|
|
1809
|
+
this.renderSelectionHighlight(selection);
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
renderSelectionHighlight(selection) {
|
|
1813
|
+
if (!this._selectionBg && !this._selectionFg)
|
|
1814
|
+
return;
|
|
1815
|
+
const selectedText = this._text.slice(selection.start, selection.end);
|
|
1816
|
+
if (!selectedText)
|
|
1817
|
+
return;
|
|
1818
|
+
const positions = getCharacterPositions(this._text, this._font);
|
|
1819
|
+
const startX = positions[selection.start] || 0;
|
|
1820
|
+
const endX = selection.end < positions.length ? positions[selection.end] : measureText({ text: this._text, font: this._font }).width;
|
|
1821
|
+
if (this._selectionBg) {
|
|
1822
|
+
this.frameBuffer.fillRect(startX, 0, endX - startX, this.height, this._selectionBg);
|
|
1823
|
+
}
|
|
1824
|
+
if (this._selectionFg || this._selectionBg) {
|
|
1825
|
+
renderFontToFrameBuffer(this.frameBuffer, {
|
|
1826
|
+
text: selectedText,
|
|
1827
|
+
x: startX,
|
|
1828
|
+
y: 0,
|
|
1829
|
+
fg: this._selectionFg ? [this._selectionFg] : this._fg,
|
|
1830
|
+
bg: this._selectionBg || this._bg,
|
|
1831
|
+
font: this._font
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
// src/renderables/Input.ts
|
|
1837
|
+
var InputRenderableEvents;
|
|
1838
|
+
((InputRenderableEvents2) => {
|
|
1839
|
+
InputRenderableEvents2["INPUT"] = "input";
|
|
1840
|
+
InputRenderableEvents2["CHANGE"] = "change";
|
|
1841
|
+
InputRenderableEvents2["ENTER"] = "enter";
|
|
1842
|
+
})(InputRenderableEvents ||= {});
|
|
1843
|
+
|
|
1844
|
+
class InputRenderable extends Renderable {
|
|
1845
|
+
focusable = true;
|
|
1846
|
+
_value = "";
|
|
1847
|
+
_cursorPosition = 0;
|
|
1848
|
+
_placeholder;
|
|
1849
|
+
_backgroundColor;
|
|
1850
|
+
_textColor;
|
|
1851
|
+
_focusedBackgroundColor;
|
|
1852
|
+
_focusedTextColor;
|
|
1853
|
+
_placeholderColor;
|
|
1854
|
+
_cursorColor;
|
|
1855
|
+
_maxLength;
|
|
1856
|
+
_lastCommittedValue = "";
|
|
1857
|
+
_defaultOptions = {
|
|
1858
|
+
backgroundColor: "transparent",
|
|
1859
|
+
textColor: "#FFFFFF",
|
|
1860
|
+
focusedBackgroundColor: "#1a1a1a",
|
|
1861
|
+
focusedTextColor: "#FFFFFF",
|
|
1862
|
+
placeholder: "",
|
|
1863
|
+
placeholderColor: "#666666",
|
|
1864
|
+
cursorColor: "#FFFFFF",
|
|
1865
|
+
maxLength: 1000,
|
|
1866
|
+
value: ""
|
|
1867
|
+
};
|
|
1868
|
+
constructor(ctx, options) {
|
|
1869
|
+
super(ctx, { ...options, buffered: true });
|
|
1870
|
+
this._backgroundColor = parseColor(options.backgroundColor || this._defaultOptions.backgroundColor);
|
|
1871
|
+
this._textColor = parseColor(options.textColor || this._defaultOptions.textColor);
|
|
1872
|
+
this._focusedBackgroundColor = parseColor(options.focusedBackgroundColor || options.backgroundColor || this._defaultOptions.focusedBackgroundColor);
|
|
1873
|
+
this._focusedTextColor = parseColor(options.focusedTextColor || options.textColor || this._defaultOptions.focusedTextColor);
|
|
1874
|
+
this._placeholder = options.placeholder || this._defaultOptions.placeholder;
|
|
1875
|
+
this._value = options.value || this._defaultOptions.value;
|
|
1876
|
+
this._lastCommittedValue = this._value;
|
|
1877
|
+
this._cursorPosition = this._value.length;
|
|
1878
|
+
this._maxLength = options.maxLength || this._defaultOptions.maxLength;
|
|
1879
|
+
this._placeholderColor = parseColor(options.placeholderColor || this._defaultOptions.placeholderColor);
|
|
1880
|
+
this._cursorColor = parseColor(options.cursorColor || this._defaultOptions.cursorColor);
|
|
1881
|
+
}
|
|
1882
|
+
updateCursorPosition() {
|
|
1883
|
+
if (!this._focused)
|
|
1884
|
+
return;
|
|
1885
|
+
const contentX = 0;
|
|
1886
|
+
const contentY = 0;
|
|
1887
|
+
const contentWidth = this.width;
|
|
1888
|
+
const maxVisibleChars = contentWidth - 1;
|
|
1889
|
+
let displayStartIndex = 0;
|
|
1890
|
+
if (this._cursorPosition >= maxVisibleChars) {
|
|
1891
|
+
displayStartIndex = this._cursorPosition - maxVisibleChars + 1;
|
|
1892
|
+
}
|
|
1893
|
+
const cursorDisplayX = this._cursorPosition - displayStartIndex;
|
|
1894
|
+
if (cursorDisplayX >= 0 && cursorDisplayX < contentWidth) {
|
|
1895
|
+
const absoluteCursorX = this.x + contentX + cursorDisplayX + 1;
|
|
1896
|
+
const absoluteCursorY = this.y + contentY + 1;
|
|
1897
|
+
this._ctx.setCursorPosition(absoluteCursorX, absoluteCursorY, true);
|
|
1898
|
+
this._ctx.setCursorColor(this._cursorColor);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
focus() {
|
|
1902
|
+
super.focus();
|
|
1903
|
+
this._ctx.setCursorStyle("block", true);
|
|
1904
|
+
this._ctx.setCursorColor(this._cursorColor);
|
|
1905
|
+
this.updateCursorPosition();
|
|
1906
|
+
}
|
|
1907
|
+
blur() {
|
|
1908
|
+
super.blur();
|
|
1909
|
+
this._ctx.setCursorPosition(0, 0, false);
|
|
1910
|
+
if (this._value !== this._lastCommittedValue) {
|
|
1911
|
+
this._lastCommittedValue = this._value;
|
|
1912
|
+
this.emit("change" /* CHANGE */, this._value);
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
renderSelf(buffer, deltaTime) {
|
|
1916
|
+
if (!this.visible || !this.frameBuffer)
|
|
1917
|
+
return;
|
|
1918
|
+
if (this.isDirty) {
|
|
1919
|
+
this.refreshFrameBuffer();
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
refreshFrameBuffer() {
|
|
1923
|
+
if (!this.frameBuffer)
|
|
1924
|
+
return;
|
|
1925
|
+
const bgColor = this._focused ? this._focusedBackgroundColor : this._backgroundColor;
|
|
1926
|
+
this.frameBuffer.clear(bgColor);
|
|
1927
|
+
const contentX = 0;
|
|
1928
|
+
const contentY = 0;
|
|
1929
|
+
const contentWidth = this.width;
|
|
1930
|
+
const contentHeight = this.height;
|
|
1931
|
+
const displayText = this._value || this._placeholder;
|
|
1932
|
+
const isPlaceholder = !this._value && this._placeholder;
|
|
1933
|
+
const baseTextColor = this._focused ? this._focusedTextColor : this._textColor;
|
|
1934
|
+
const textColor = isPlaceholder ? this._placeholderColor : baseTextColor;
|
|
1935
|
+
const maxVisibleChars = contentWidth - 1;
|
|
1936
|
+
let displayStartIndex = 0;
|
|
1937
|
+
if (this._cursorPosition >= maxVisibleChars) {
|
|
1938
|
+
displayStartIndex = this._cursorPosition - maxVisibleChars + 1;
|
|
1939
|
+
}
|
|
1940
|
+
const visibleText = displayText.substring(displayStartIndex, displayStartIndex + maxVisibleChars);
|
|
1941
|
+
if (visibleText) {
|
|
1942
|
+
this.frameBuffer.drawText(visibleText, contentX, contentY, textColor);
|
|
1943
|
+
}
|
|
1944
|
+
if (this._focused) {
|
|
1945
|
+
this.updateCursorPosition();
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
get value() {
|
|
1949
|
+
return this._value;
|
|
1950
|
+
}
|
|
1951
|
+
set value(value) {
|
|
1952
|
+
const newValue = value.substring(0, this._maxLength);
|
|
1953
|
+
if (this._value !== newValue) {
|
|
1954
|
+
this._value = newValue;
|
|
1955
|
+
this._cursorPosition = Math.min(this._cursorPosition, this._value.length);
|
|
1956
|
+
this.requestRender();
|
|
1957
|
+
this.updateCursorPosition();
|
|
1958
|
+
this.emit("input" /* INPUT */, this._value);
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
set placeholder(placeholder) {
|
|
1962
|
+
if (this._placeholder !== placeholder) {
|
|
1963
|
+
this._placeholder = placeholder;
|
|
1964
|
+
this.requestRender();
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
get cursorPosition() {
|
|
1968
|
+
return this._cursorPosition;
|
|
1969
|
+
}
|
|
1970
|
+
set cursorPosition(position) {
|
|
1971
|
+
const newPosition = Math.max(0, Math.min(position, this._value.length));
|
|
1972
|
+
if (this._cursorPosition !== newPosition) {
|
|
1973
|
+
this._cursorPosition = newPosition;
|
|
1974
|
+
this.requestRender();
|
|
1975
|
+
this.updateCursorPosition();
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
insertText(text) {
|
|
1979
|
+
if (this._value.length + text.length > this._maxLength) {
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
const beforeCursor = this._value.substring(0, this._cursorPosition);
|
|
1983
|
+
const afterCursor = this._value.substring(this._cursorPosition);
|
|
1984
|
+
this._value = beforeCursor + text + afterCursor;
|
|
1985
|
+
this._cursorPosition += text.length;
|
|
1986
|
+
this.requestRender();
|
|
1987
|
+
this.updateCursorPosition();
|
|
1988
|
+
this.emit("input" /* INPUT */, this._value);
|
|
1989
|
+
}
|
|
1990
|
+
deleteCharacter(direction) {
|
|
1991
|
+
if (direction === "backward" && this._cursorPosition > 0) {
|
|
1992
|
+
const beforeCursor = this._value.substring(0, this._cursorPosition - 1);
|
|
1993
|
+
const afterCursor = this._value.substring(this._cursorPosition);
|
|
1994
|
+
this._value = beforeCursor + afterCursor;
|
|
1995
|
+
this._cursorPosition--;
|
|
1996
|
+
this.requestRender();
|
|
1997
|
+
this.updateCursorPosition();
|
|
1998
|
+
this.emit("input" /* INPUT */, this._value);
|
|
1999
|
+
} else if (direction === "forward" && this._cursorPosition < this._value.length) {
|
|
2000
|
+
const beforeCursor = this._value.substring(0, this._cursorPosition);
|
|
2001
|
+
const afterCursor = this._value.substring(this._cursorPosition + 1);
|
|
2002
|
+
this._value = beforeCursor + afterCursor;
|
|
2003
|
+
this.requestRender();
|
|
2004
|
+
this.updateCursorPosition();
|
|
2005
|
+
this.emit("input" /* INPUT */, this._value);
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
handleKeyPress(key) {
|
|
2009
|
+
const keyName = typeof key === "string" ? key : key.name;
|
|
2010
|
+
const keySequence = typeof key === "string" ? key : key.sequence;
|
|
2011
|
+
switch (keyName) {
|
|
2012
|
+
case "left":
|
|
2013
|
+
this.cursorPosition = this._cursorPosition - 1;
|
|
2014
|
+
return true;
|
|
2015
|
+
case "right":
|
|
2016
|
+
this.cursorPosition = this._cursorPosition + 1;
|
|
2017
|
+
return true;
|
|
2018
|
+
case "home":
|
|
2019
|
+
this.cursorPosition = 0;
|
|
2020
|
+
return true;
|
|
2021
|
+
case "end":
|
|
2022
|
+
this.cursorPosition = this._value.length;
|
|
2023
|
+
return true;
|
|
2024
|
+
case "backspace":
|
|
2025
|
+
this.deleteCharacter("backward");
|
|
2026
|
+
return true;
|
|
2027
|
+
case "delete":
|
|
2028
|
+
this.deleteCharacter("forward");
|
|
2029
|
+
return true;
|
|
2030
|
+
case "return":
|
|
2031
|
+
case "enter":
|
|
2032
|
+
if (this._value !== this._lastCommittedValue) {
|
|
2033
|
+
this._lastCommittedValue = this._value;
|
|
2034
|
+
this.emit("change" /* CHANGE */, this._value);
|
|
2035
|
+
}
|
|
2036
|
+
this.emit("enter" /* ENTER */, this._value);
|
|
2037
|
+
return true;
|
|
2038
|
+
default:
|
|
2039
|
+
if (keySequence && keySequence.length === 1 && keySequence.charCodeAt(0) >= 32 && keySequence.charCodeAt(0) <= 126) {
|
|
2040
|
+
this.insertText(keySequence);
|
|
2041
|
+
return true;
|
|
2042
|
+
}
|
|
2043
|
+
break;
|
|
2044
|
+
}
|
|
2045
|
+
return false;
|
|
2046
|
+
}
|
|
2047
|
+
set maxLength(maxLength) {
|
|
2048
|
+
this._maxLength = maxLength;
|
|
2049
|
+
if (this._value.length > maxLength) {
|
|
2050
|
+
this._value = this._value.substring(0, maxLength);
|
|
2051
|
+
this.requestRender();
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
set backgroundColor(value) {
|
|
2055
|
+
const newColor = parseColor(value ?? this._defaultOptions.backgroundColor);
|
|
2056
|
+
if (this._backgroundColor !== newColor) {
|
|
2057
|
+
this._backgroundColor = newColor;
|
|
2058
|
+
this.requestRender();
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
set textColor(value) {
|
|
2062
|
+
const newColor = parseColor(value ?? this._defaultOptions.textColor);
|
|
2063
|
+
if (this._textColor !== newColor) {
|
|
2064
|
+
this._textColor = newColor;
|
|
2065
|
+
this.requestRender();
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
set focusedBackgroundColor(value) {
|
|
2069
|
+
const newColor = parseColor(value ?? this._defaultOptions.focusedBackgroundColor);
|
|
2070
|
+
if (this._focusedBackgroundColor !== newColor) {
|
|
2071
|
+
this._focusedBackgroundColor = newColor;
|
|
2072
|
+
this.requestRender();
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
set focusedTextColor(value) {
|
|
2076
|
+
const newColor = parseColor(value ?? this._defaultOptions.focusedTextColor);
|
|
2077
|
+
if (this._focusedTextColor !== newColor) {
|
|
2078
|
+
this._focusedTextColor = newColor;
|
|
2079
|
+
this.requestRender();
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
set placeholderColor(value) {
|
|
2083
|
+
const newColor = parseColor(value ?? this._defaultOptions.placeholderColor);
|
|
2084
|
+
if (this._placeholderColor !== newColor) {
|
|
2085
|
+
this._placeholderColor = newColor;
|
|
2086
|
+
this.requestRender();
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
set cursorColor(value) {
|
|
2090
|
+
const newColor = parseColor(value ?? this._defaultOptions.cursorColor);
|
|
2091
|
+
if (this._cursorColor !== newColor) {
|
|
2092
|
+
this._cursorColor = newColor;
|
|
2093
|
+
this.requestRender();
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
updateFromLayout() {
|
|
2097
|
+
super.updateFromLayout();
|
|
2098
|
+
this.updateCursorPosition();
|
|
2099
|
+
}
|
|
2100
|
+
onResize(width, height) {
|
|
2101
|
+
super.onResize(width, height);
|
|
2102
|
+
this.updateCursorPosition();
|
|
2103
|
+
}
|
|
2104
|
+
onRemove() {
|
|
2105
|
+
if (this._focused) {
|
|
2106
|
+
this._ctx.setCursorPosition(0, 0, false);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
// src/renderables/Select.ts
|
|
2111
|
+
var SelectRenderableEvents;
|
|
2112
|
+
((SelectRenderableEvents2) => {
|
|
2113
|
+
SelectRenderableEvents2["SELECTION_CHANGED"] = "selectionChanged";
|
|
2114
|
+
SelectRenderableEvents2["ITEM_SELECTED"] = "itemSelected";
|
|
2115
|
+
})(SelectRenderableEvents ||= {});
|
|
2116
|
+
|
|
2117
|
+
class SelectRenderable extends Renderable {
|
|
2118
|
+
focusable = true;
|
|
2119
|
+
_options = [];
|
|
2120
|
+
selectedIndex = 0;
|
|
2121
|
+
scrollOffset = 0;
|
|
2122
|
+
maxVisibleItems;
|
|
2123
|
+
_backgroundColor;
|
|
2124
|
+
_textColor;
|
|
2125
|
+
_focusedBackgroundColor;
|
|
2126
|
+
_focusedTextColor;
|
|
2127
|
+
_selectedBackgroundColor;
|
|
2128
|
+
_selectedTextColor;
|
|
2129
|
+
_descriptionColor;
|
|
2130
|
+
_selectedDescriptionColor;
|
|
2131
|
+
_showScrollIndicator;
|
|
2132
|
+
_wrapSelection;
|
|
2133
|
+
_showDescription;
|
|
2134
|
+
_font;
|
|
2135
|
+
_itemSpacing;
|
|
2136
|
+
linesPerItem;
|
|
2137
|
+
fontHeight;
|
|
2138
|
+
_fastScrollStep;
|
|
2139
|
+
_defaultOptions = {
|
|
2140
|
+
backgroundColor: "transparent",
|
|
2141
|
+
textColor: "#FFFFFF",
|
|
2142
|
+
focusedBackgroundColor: "#1a1a1a",
|
|
2143
|
+
focusedTextColor: "#FFFFFF",
|
|
2144
|
+
selectedBackgroundColor: "#334455",
|
|
2145
|
+
selectedTextColor: "#FFFF00",
|
|
2146
|
+
descriptionColor: "#888888",
|
|
2147
|
+
selectedDescriptionColor: "#CCCCCC",
|
|
2148
|
+
showScrollIndicator: false,
|
|
2149
|
+
wrapSelection: false,
|
|
2150
|
+
showDescription: true,
|
|
2151
|
+
itemSpacing: 0,
|
|
2152
|
+
fastScrollStep: 5
|
|
2153
|
+
};
|
|
2154
|
+
constructor(ctx, options) {
|
|
2155
|
+
super(ctx, { ...options, buffered: true });
|
|
2156
|
+
this._backgroundColor = parseColor(options.backgroundColor || this._defaultOptions.backgroundColor);
|
|
2157
|
+
this._textColor = parseColor(options.textColor || this._defaultOptions.textColor);
|
|
2158
|
+
this._focusedBackgroundColor = parseColor(options.focusedBackgroundColor || this._defaultOptions.focusedBackgroundColor);
|
|
2159
|
+
this._focusedTextColor = parseColor(options.focusedTextColor || this._defaultOptions.focusedTextColor);
|
|
2160
|
+
this._options = options.options || [];
|
|
2161
|
+
this._showScrollIndicator = options.showScrollIndicator ?? this._defaultOptions.showScrollIndicator;
|
|
2162
|
+
this._wrapSelection = options.wrapSelection ?? this._defaultOptions.wrapSelection;
|
|
2163
|
+
this._showDescription = options.showDescription ?? this._defaultOptions.showDescription;
|
|
2164
|
+
this._font = options.font;
|
|
2165
|
+
this._itemSpacing = options.itemSpacing || this._defaultOptions.itemSpacing;
|
|
2166
|
+
this.fontHeight = this._font ? measureText({ text: "A", font: this._font }).height : 1;
|
|
2167
|
+
this.linesPerItem = this._showDescription ? this._font ? this.fontHeight + 1 : 2 : this._font ? this.fontHeight : 1;
|
|
2168
|
+
this.linesPerItem += this._itemSpacing;
|
|
2169
|
+
this.maxVisibleItems = Math.max(1, Math.floor(this.height / this.linesPerItem));
|
|
2170
|
+
this._selectedBackgroundColor = parseColor(options.selectedBackgroundColor || this._defaultOptions.selectedBackgroundColor);
|
|
2171
|
+
this._selectedTextColor = parseColor(options.selectedTextColor || this._defaultOptions.selectedTextColor);
|
|
2172
|
+
this._descriptionColor = parseColor(options.descriptionColor || this._defaultOptions.descriptionColor);
|
|
2173
|
+
this._selectedDescriptionColor = parseColor(options.selectedDescriptionColor || this._defaultOptions.selectedDescriptionColor);
|
|
2174
|
+
this._fastScrollStep = options.fastScrollStep || this._defaultOptions.fastScrollStep;
|
|
2175
|
+
this.requestRender();
|
|
2176
|
+
}
|
|
2177
|
+
renderSelf(buffer, deltaTime) {
|
|
2178
|
+
if (!this.visible || !this.frameBuffer)
|
|
2179
|
+
return;
|
|
2180
|
+
if (this.isDirty) {
|
|
2181
|
+
this.refreshFrameBuffer();
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
refreshFrameBuffer() {
|
|
2185
|
+
if (!this.frameBuffer || this._options.length === 0)
|
|
2186
|
+
return;
|
|
2187
|
+
const bgColor = this._focused ? this._focusedBackgroundColor : this._backgroundColor;
|
|
2188
|
+
this.frameBuffer.clear(bgColor);
|
|
2189
|
+
const contentX = 0;
|
|
2190
|
+
const contentY = 0;
|
|
2191
|
+
const contentWidth = this.width;
|
|
2192
|
+
const contentHeight = this.height;
|
|
2193
|
+
const visibleOptions = this._options.slice(this.scrollOffset, this.scrollOffset + this.maxVisibleItems);
|
|
2194
|
+
for (let i = 0;i < visibleOptions.length; i++) {
|
|
2195
|
+
const actualIndex = this.scrollOffset + i;
|
|
2196
|
+
const option = visibleOptions[i];
|
|
2197
|
+
const isSelected = actualIndex === this.selectedIndex;
|
|
2198
|
+
const itemY = contentY + i * this.linesPerItem;
|
|
2199
|
+
if (itemY + this.linesPerItem - 1 >= contentY + contentHeight)
|
|
2200
|
+
break;
|
|
2201
|
+
if (isSelected) {
|
|
2202
|
+
const contentHeight2 = this.linesPerItem - this._itemSpacing;
|
|
2203
|
+
this.frameBuffer.fillRect(contentX, itemY, contentWidth, contentHeight2, this._selectedBackgroundColor);
|
|
2204
|
+
}
|
|
2205
|
+
const nameContent = `${isSelected ? "\u25B6 " : " "}${option.name}`;
|
|
2206
|
+
const baseTextColor = this._focused ? this._focusedTextColor : this._textColor;
|
|
2207
|
+
const nameColor = isSelected ? this._selectedTextColor : baseTextColor;
|
|
2208
|
+
let descX = contentX + 3;
|
|
2209
|
+
if (this._font) {
|
|
2210
|
+
const indicator = isSelected ? "\u25B6 " : " ";
|
|
2211
|
+
this.frameBuffer.drawText(indicator, contentX + 1, itemY, nameColor);
|
|
2212
|
+
const indicatorWidth = 2;
|
|
2213
|
+
renderFontToFrameBuffer(this.frameBuffer, {
|
|
2214
|
+
text: option.name,
|
|
2215
|
+
x: contentX + 1 + indicatorWidth,
|
|
2216
|
+
y: itemY,
|
|
2217
|
+
fg: nameColor,
|
|
2218
|
+
bg: isSelected ? this._selectedBackgroundColor : bgColor,
|
|
2219
|
+
font: this._font
|
|
2220
|
+
});
|
|
2221
|
+
descX = contentX + 1 + indicatorWidth;
|
|
2222
|
+
} else {
|
|
2223
|
+
this.frameBuffer.drawText(nameContent, contentX + 1, itemY, nameColor);
|
|
2224
|
+
}
|
|
2225
|
+
if (this._showDescription && itemY + this.fontHeight < contentY + contentHeight) {
|
|
2226
|
+
const descColor = isSelected ? this._selectedDescriptionColor : this._descriptionColor;
|
|
2227
|
+
const descBg = this._focused ? this._focusedBackgroundColor : this._backgroundColor;
|
|
2228
|
+
this.frameBuffer.drawText(option.description, descX, itemY + this.fontHeight, descColor);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
if (this._showScrollIndicator && this._options.length > this.maxVisibleItems) {
|
|
2232
|
+
this.renderScrollIndicatorToFrameBuffer(contentX, contentY, contentWidth, contentHeight);
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
renderScrollIndicatorToFrameBuffer(contentX, contentY, contentWidth, contentHeight) {
|
|
2236
|
+
if (!this.frameBuffer)
|
|
2237
|
+
return;
|
|
2238
|
+
const scrollPercent = this.selectedIndex / Math.max(1, this._options.length - 1);
|
|
2239
|
+
const indicatorHeight = Math.max(1, contentHeight - 2);
|
|
2240
|
+
const indicatorY = contentY + 1 + Math.floor(scrollPercent * indicatorHeight);
|
|
2241
|
+
const indicatorX = contentX + contentWidth - 1;
|
|
2242
|
+
this.frameBuffer.drawText("\u2588", indicatorX, indicatorY, parseColor("#666666"));
|
|
2243
|
+
}
|
|
2244
|
+
get options() {
|
|
2245
|
+
return this._options;
|
|
2246
|
+
}
|
|
2247
|
+
set options(options) {
|
|
2248
|
+
this._options = options;
|
|
2249
|
+
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, options.length - 1));
|
|
2250
|
+
this.updateScrollOffset();
|
|
2251
|
+
this.requestRender();
|
|
2252
|
+
}
|
|
2253
|
+
getSelectedOption() {
|
|
2254
|
+
return this._options[this.selectedIndex] || null;
|
|
2255
|
+
}
|
|
2256
|
+
getSelectedIndex() {
|
|
2257
|
+
return this.selectedIndex;
|
|
2258
|
+
}
|
|
2259
|
+
moveUp(steps = 1) {
|
|
2260
|
+
const newIndex = this.selectedIndex - steps;
|
|
2261
|
+
if (newIndex >= 0) {
|
|
2262
|
+
this.selectedIndex = newIndex;
|
|
2263
|
+
} else if (this._wrapSelection && this._options.length > 0) {
|
|
2264
|
+
this.selectedIndex = this._options.length - 1;
|
|
2265
|
+
} else {
|
|
2266
|
+
this.selectedIndex = 0;
|
|
2267
|
+
}
|
|
2268
|
+
this.updateScrollOffset();
|
|
2269
|
+
this.requestRender();
|
|
2270
|
+
this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
|
|
2271
|
+
}
|
|
2272
|
+
moveDown(steps = 1) {
|
|
2273
|
+
const newIndex = this.selectedIndex + steps;
|
|
2274
|
+
if (newIndex < this._options.length) {
|
|
2275
|
+
this.selectedIndex = newIndex;
|
|
2276
|
+
} else if (this._wrapSelection && this._options.length > 0) {
|
|
2277
|
+
this.selectedIndex = 0;
|
|
2278
|
+
} else {
|
|
2279
|
+
this.selectedIndex = this._options.length - 1;
|
|
2280
|
+
}
|
|
2281
|
+
this.updateScrollOffset();
|
|
2282
|
+
this.requestRender();
|
|
2283
|
+
this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
|
|
2284
|
+
}
|
|
2285
|
+
selectCurrent() {
|
|
2286
|
+
const selected = this.getSelectedOption();
|
|
2287
|
+
if (selected) {
|
|
2288
|
+
this.emit("itemSelected" /* ITEM_SELECTED */, this.selectedIndex, selected);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
setSelectedIndex(index) {
|
|
2292
|
+
if (index >= 0 && index < this._options.length) {
|
|
2293
|
+
this.selectedIndex = index;
|
|
2294
|
+
this.updateScrollOffset();
|
|
2295
|
+
this.requestRender();
|
|
2296
|
+
this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
updateScrollOffset() {
|
|
2300
|
+
if (!this._options)
|
|
2301
|
+
return;
|
|
2302
|
+
const halfVisible = Math.floor(this.maxVisibleItems / 2);
|
|
2303
|
+
const newScrollOffset = Math.max(0, Math.min(this.selectedIndex - halfVisible, this._options.length - this.maxVisibleItems));
|
|
2304
|
+
if (newScrollOffset !== this.scrollOffset) {
|
|
2305
|
+
this.scrollOffset = newScrollOffset;
|
|
2306
|
+
this.requestRender();
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
onResize(width, height) {
|
|
2310
|
+
this.maxVisibleItems = Math.max(1, Math.floor(height / this.linesPerItem));
|
|
2311
|
+
this.updateScrollOffset();
|
|
2312
|
+
this.requestRender();
|
|
2313
|
+
}
|
|
2314
|
+
handleKeyPress(key) {
|
|
2315
|
+
const keyName = typeof key === "string" ? key : key.name;
|
|
2316
|
+
const isShift = typeof key !== "string" && key.shift;
|
|
2317
|
+
switch (keyName) {
|
|
2318
|
+
case "up":
|
|
2319
|
+
case "k":
|
|
2320
|
+
this.moveUp(isShift ? this._fastScrollStep : 1);
|
|
2321
|
+
return true;
|
|
2322
|
+
case "down":
|
|
2323
|
+
case "j":
|
|
2324
|
+
this.moveDown(isShift ? this._fastScrollStep : 1);
|
|
2325
|
+
return true;
|
|
2326
|
+
case "return":
|
|
2327
|
+
case "enter":
|
|
2328
|
+
this.selectCurrent();
|
|
2329
|
+
return true;
|
|
2330
|
+
}
|
|
2331
|
+
return false;
|
|
2332
|
+
}
|
|
2333
|
+
get showScrollIndicator() {
|
|
2334
|
+
return this._showScrollIndicator;
|
|
2335
|
+
}
|
|
2336
|
+
set showScrollIndicator(show) {
|
|
2337
|
+
this._showScrollIndicator = show;
|
|
2338
|
+
this.requestRender();
|
|
2339
|
+
}
|
|
2340
|
+
get showDescription() {
|
|
2341
|
+
return this._showDescription;
|
|
2342
|
+
}
|
|
2343
|
+
set showDescription(show) {
|
|
2344
|
+
if (this._showDescription !== show) {
|
|
2345
|
+
this._showDescription = show;
|
|
2346
|
+
this.linesPerItem = this._showDescription ? this._font ? this.fontHeight + 1 : 2 : this._font ? this.fontHeight : 1;
|
|
2347
|
+
this.linesPerItem += this._itemSpacing;
|
|
2348
|
+
this.maxVisibleItems = Math.max(1, Math.floor(this.height / this.linesPerItem));
|
|
2349
|
+
this.updateScrollOffset();
|
|
2350
|
+
this.requestRender();
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
get wrapSelection() {
|
|
2354
|
+
return this._wrapSelection;
|
|
2355
|
+
}
|
|
2356
|
+
set wrapSelection(wrap) {
|
|
2357
|
+
this._wrapSelection = wrap;
|
|
2358
|
+
}
|
|
2359
|
+
set backgroundColor(value) {
|
|
2360
|
+
const newColor = parseColor(value ?? this._defaultOptions.backgroundColor);
|
|
2361
|
+
if (this._backgroundColor !== newColor) {
|
|
2362
|
+
this._backgroundColor = newColor;
|
|
2363
|
+
this.requestRender();
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
set textColor(value) {
|
|
2367
|
+
const newColor = parseColor(value ?? this._defaultOptions.textColor);
|
|
2368
|
+
if (this._textColor !== newColor) {
|
|
2369
|
+
this._textColor = newColor;
|
|
2370
|
+
this.requestRender();
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
set focusedBackgroundColor(value) {
|
|
2374
|
+
const newColor = parseColor(value ?? this._defaultOptions.focusedBackgroundColor);
|
|
2375
|
+
if (this._focusedBackgroundColor !== newColor) {
|
|
2376
|
+
this._focusedBackgroundColor = newColor;
|
|
2377
|
+
this.requestRender();
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
set focusedTextColor(value) {
|
|
2381
|
+
const newColor = parseColor(value ?? this._defaultOptions.focusedTextColor);
|
|
2382
|
+
if (this._focusedTextColor !== newColor) {
|
|
2383
|
+
this._focusedTextColor = newColor;
|
|
2384
|
+
this.requestRender();
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
set selectedBackgroundColor(value) {
|
|
2388
|
+
const newColor = parseColor(value ?? this._defaultOptions.selectedBackgroundColor);
|
|
2389
|
+
if (this._selectedBackgroundColor !== newColor) {
|
|
2390
|
+
this._selectedBackgroundColor = newColor;
|
|
2391
|
+
this.requestRender();
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
set selectedTextColor(value) {
|
|
2395
|
+
const newColor = parseColor(value ?? this._defaultOptions.selectedTextColor);
|
|
2396
|
+
if (this._selectedTextColor !== newColor) {
|
|
2397
|
+
this._selectedTextColor = newColor;
|
|
2398
|
+
this.requestRender();
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
set descriptionColor(value) {
|
|
2402
|
+
const newColor = parseColor(value ?? this._defaultOptions.descriptionColor);
|
|
2403
|
+
if (this._descriptionColor !== newColor) {
|
|
2404
|
+
this._descriptionColor = newColor;
|
|
2405
|
+
this.requestRender();
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
set selectedDescriptionColor(value) {
|
|
2409
|
+
const newColor = parseColor(value ?? this._defaultOptions.selectedDescriptionColor);
|
|
2410
|
+
if (this._selectedDescriptionColor !== newColor) {
|
|
2411
|
+
this._selectedDescriptionColor = newColor;
|
|
2412
|
+
this.requestRender();
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
set font(font) {
|
|
2416
|
+
this._font = font;
|
|
2417
|
+
this.fontHeight = measureText({ text: "A", font: this._font }).height;
|
|
2418
|
+
this.linesPerItem = this._showDescription ? this._font ? this.fontHeight + 1 : 2 : this._font ? this.fontHeight : 1;
|
|
2419
|
+
this.linesPerItem += this._itemSpacing;
|
|
2420
|
+
this.maxVisibleItems = Math.max(1, Math.floor(this.height / this.linesPerItem));
|
|
2421
|
+
this.updateScrollOffset();
|
|
2422
|
+
this.requestRender();
|
|
2423
|
+
}
|
|
2424
|
+
set itemSpacing(spacing) {
|
|
2425
|
+
this._itemSpacing = spacing;
|
|
2426
|
+
this.linesPerItem = this._showDescription ? this._font ? this.fontHeight + 1 : 2 : this._font ? this.fontHeight : 1;
|
|
2427
|
+
this.linesPerItem += this._itemSpacing;
|
|
2428
|
+
this.maxVisibleItems = Math.max(1, Math.floor(this.height / this.linesPerItem));
|
|
2429
|
+
this.updateScrollOffset();
|
|
2430
|
+
this.requestRender();
|
|
2431
|
+
}
|
|
2432
|
+
set fastScrollStep(step) {
|
|
2433
|
+
this._fastScrollStep = step;
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
// src/renderables/TabSelect.ts
|
|
2437
|
+
var TabSelectRenderableEvents;
|
|
2438
|
+
((TabSelectRenderableEvents2) => {
|
|
2439
|
+
TabSelectRenderableEvents2["SELECTION_CHANGED"] = "selectionChanged";
|
|
2440
|
+
TabSelectRenderableEvents2["ITEM_SELECTED"] = "itemSelected";
|
|
2441
|
+
})(TabSelectRenderableEvents ||= {});
|
|
2442
|
+
function calculateDynamicHeight(showUnderline, showDescription) {
|
|
2443
|
+
let height = 1;
|
|
2444
|
+
if (showUnderline) {
|
|
2445
|
+
height += 1;
|
|
2446
|
+
}
|
|
2447
|
+
if (showDescription) {
|
|
2448
|
+
height += 1;
|
|
2449
|
+
}
|
|
2450
|
+
return height;
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
class TabSelectRenderable extends Renderable {
|
|
2454
|
+
focusable = true;
|
|
2455
|
+
_options = [];
|
|
2456
|
+
selectedIndex = 0;
|
|
2457
|
+
scrollOffset = 0;
|
|
2458
|
+
_tabWidth;
|
|
2459
|
+
maxVisibleTabs;
|
|
2460
|
+
_backgroundColor;
|
|
2461
|
+
_textColor;
|
|
2462
|
+
_focusedBackgroundColor;
|
|
2463
|
+
_focusedTextColor;
|
|
2464
|
+
_selectedBackgroundColor;
|
|
2465
|
+
_selectedTextColor;
|
|
2466
|
+
_selectedDescriptionColor;
|
|
2467
|
+
_showScrollArrows;
|
|
2468
|
+
_showDescription;
|
|
2469
|
+
_showUnderline;
|
|
2470
|
+
_wrapSelection;
|
|
2471
|
+
constructor(ctx, options) {
|
|
2472
|
+
const calculatedHeight = calculateDynamicHeight(options.showUnderline ?? true, options.showDescription ?? true);
|
|
2473
|
+
super(ctx, { ...options, height: calculatedHeight, buffered: true });
|
|
2474
|
+
this._backgroundColor = parseColor(options.backgroundColor || "transparent");
|
|
2475
|
+
this._textColor = parseColor(options.textColor || "#FFFFFF");
|
|
2476
|
+
this._focusedBackgroundColor = parseColor(options.focusedBackgroundColor || options.backgroundColor || "#1a1a1a");
|
|
2477
|
+
this._focusedTextColor = parseColor(options.focusedTextColor || options.textColor || "#FFFFFF");
|
|
2478
|
+
this._options = options.options || [];
|
|
2479
|
+
this._tabWidth = options.tabWidth || 20;
|
|
2480
|
+
this._showDescription = options.showDescription ?? true;
|
|
2481
|
+
this._showUnderline = options.showUnderline ?? true;
|
|
2482
|
+
this._showScrollArrows = options.showScrollArrows ?? true;
|
|
2483
|
+
this._wrapSelection = options.wrapSelection ?? false;
|
|
2484
|
+
this.maxVisibleTabs = Math.max(1, Math.floor(this.width / this._tabWidth));
|
|
2485
|
+
this._selectedBackgroundColor = parseColor(options.selectedBackgroundColor || "#334455");
|
|
2486
|
+
this._selectedTextColor = parseColor(options.selectedTextColor || "#FFFF00");
|
|
2487
|
+
this._selectedDescriptionColor = parseColor(options.selectedDescriptionColor || "#CCCCCC");
|
|
2488
|
+
}
|
|
2489
|
+
calculateDynamicHeight() {
|
|
2490
|
+
return calculateDynamicHeight(this._showUnderline, this._showDescription);
|
|
2491
|
+
}
|
|
2492
|
+
renderSelf(buffer, deltaTime) {
|
|
2493
|
+
if (!this.visible || !this.frameBuffer)
|
|
2494
|
+
return;
|
|
2495
|
+
if (this.isDirty) {
|
|
2496
|
+
this.refreshFrameBuffer();
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
refreshFrameBuffer() {
|
|
2500
|
+
if (!this.frameBuffer || this._options.length === 0)
|
|
2501
|
+
return;
|
|
2502
|
+
const bgColor = this._focused ? this._focusedBackgroundColor : this._backgroundColor;
|
|
2503
|
+
this.frameBuffer.clear(bgColor);
|
|
2504
|
+
const contentX = 0;
|
|
2505
|
+
const contentY = 0;
|
|
2506
|
+
const contentWidth = this.width;
|
|
2507
|
+
const contentHeight = this.height;
|
|
2508
|
+
const visibleOptions = this._options.slice(this.scrollOffset, this.scrollOffset + this.maxVisibleTabs);
|
|
2509
|
+
for (let i = 0;i < visibleOptions.length; i++) {
|
|
2510
|
+
const actualIndex = this.scrollOffset + i;
|
|
2511
|
+
const option = visibleOptions[i];
|
|
2512
|
+
const isSelected = actualIndex === this.selectedIndex;
|
|
2513
|
+
const tabX = contentX + i * this._tabWidth;
|
|
2514
|
+
if (tabX >= contentX + contentWidth)
|
|
2515
|
+
break;
|
|
2516
|
+
const actualTabWidth = Math.min(this._tabWidth, contentWidth - i * this._tabWidth);
|
|
2517
|
+
if (isSelected) {
|
|
2518
|
+
this.frameBuffer.fillRect(tabX, contentY, actualTabWidth, 1, this._selectedBackgroundColor);
|
|
2519
|
+
}
|
|
2520
|
+
const baseTextColor = this._focused ? this._focusedTextColor : this._textColor;
|
|
2521
|
+
const nameColor = isSelected ? this._selectedTextColor : baseTextColor;
|
|
2522
|
+
const nameContent = this.truncateText(option.name, actualTabWidth - 2);
|
|
2523
|
+
this.frameBuffer.drawText(nameContent, tabX + 1, contentY, nameColor);
|
|
2524
|
+
if (isSelected && this._showUnderline && contentHeight >= 2) {
|
|
2525
|
+
const underlineY = contentY + 1;
|
|
2526
|
+
const underlineBg = isSelected ? this._selectedBackgroundColor : bgColor;
|
|
2527
|
+
this.frameBuffer.drawText("\u25AC".repeat(actualTabWidth), tabX, underlineY, nameColor, underlineBg);
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
if (this._showDescription && contentHeight >= (this._showUnderline ? 3 : 2)) {
|
|
2531
|
+
const selectedOption = this.getSelectedOption();
|
|
2532
|
+
if (selectedOption) {
|
|
2533
|
+
const descriptionY = contentY + (this._showUnderline ? 2 : 1);
|
|
2534
|
+
const descColor = this._selectedDescriptionColor;
|
|
2535
|
+
const descContent = this.truncateText(selectedOption.description, contentWidth - 2);
|
|
2536
|
+
this.frameBuffer.drawText(descContent, contentX + 1, descriptionY, descColor);
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
if (this._showScrollArrows && this._options.length > this.maxVisibleTabs) {
|
|
2540
|
+
this.renderScrollArrowsToFrameBuffer(contentX, contentY, contentWidth, contentHeight);
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
truncateText(text, maxWidth) {
|
|
2544
|
+
if (text.length <= maxWidth)
|
|
2545
|
+
return text;
|
|
2546
|
+
return text.substring(0, Math.max(0, maxWidth - 1)) + "\u2026";
|
|
2547
|
+
}
|
|
2548
|
+
renderScrollArrowsToFrameBuffer(contentX, contentY, contentWidth, contentHeight) {
|
|
2549
|
+
if (!this.frameBuffer)
|
|
2550
|
+
return;
|
|
2551
|
+
const hasMoreLeft = this.scrollOffset > 0;
|
|
2552
|
+
const hasMoreRight = this.scrollOffset + this.maxVisibleTabs < this._options.length;
|
|
2553
|
+
if (hasMoreLeft) {
|
|
2554
|
+
this.frameBuffer.drawText("\u2039", contentX, contentY, parseColor("#AAAAAA"));
|
|
2555
|
+
}
|
|
2556
|
+
if (hasMoreRight) {
|
|
2557
|
+
this.frameBuffer.drawText("\u203A", contentX + contentWidth - 1, contentY, parseColor("#AAAAAA"));
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
setOptions(options) {
|
|
2561
|
+
this._options = options;
|
|
2562
|
+
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, options.length - 1));
|
|
2563
|
+
this.updateScrollOffset();
|
|
2564
|
+
this.requestRender();
|
|
2565
|
+
}
|
|
2566
|
+
getSelectedOption() {
|
|
2567
|
+
return this._options[this.selectedIndex] || null;
|
|
2568
|
+
}
|
|
2569
|
+
getSelectedIndex() {
|
|
2570
|
+
return this.selectedIndex;
|
|
2571
|
+
}
|
|
2572
|
+
moveLeft() {
|
|
2573
|
+
if (this.selectedIndex > 0) {
|
|
2574
|
+
this.selectedIndex--;
|
|
2575
|
+
} else if (this._wrapSelection && this._options.length > 0) {
|
|
2576
|
+
this.selectedIndex = this._options.length - 1;
|
|
2577
|
+
} else {
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
this.updateScrollOffset();
|
|
2581
|
+
this.requestRender();
|
|
2582
|
+
this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
|
|
2583
|
+
}
|
|
2584
|
+
moveRight() {
|
|
2585
|
+
if (this.selectedIndex < this._options.length - 1) {
|
|
2586
|
+
this.selectedIndex++;
|
|
2587
|
+
} else if (this._wrapSelection && this._options.length > 0) {
|
|
2588
|
+
this.selectedIndex = 0;
|
|
2589
|
+
} else {
|
|
2590
|
+
return;
|
|
2591
|
+
}
|
|
2592
|
+
this.updateScrollOffset();
|
|
2593
|
+
this.requestRender();
|
|
2594
|
+
this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
|
|
2595
|
+
}
|
|
2596
|
+
selectCurrent() {
|
|
2597
|
+
const selected = this.getSelectedOption();
|
|
2598
|
+
if (selected) {
|
|
2599
|
+
this.emit("itemSelected" /* ITEM_SELECTED */, this.selectedIndex, selected);
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
setSelectedIndex(index) {
|
|
2603
|
+
if (index >= 0 && index < this._options.length) {
|
|
2604
|
+
this.selectedIndex = index;
|
|
2605
|
+
this.updateScrollOffset();
|
|
2606
|
+
this.requestRender();
|
|
2607
|
+
this.emit("selectionChanged" /* SELECTION_CHANGED */, this.selectedIndex, this.getSelectedOption());
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
updateScrollOffset() {
|
|
2611
|
+
const halfVisible = Math.floor(this.maxVisibleTabs / 2);
|
|
2612
|
+
const newScrollOffset = Math.max(0, Math.min(this.selectedIndex - halfVisible, this._options.length - this.maxVisibleTabs));
|
|
2613
|
+
if (newScrollOffset !== this.scrollOffset) {
|
|
2614
|
+
this.scrollOffset = newScrollOffset;
|
|
2615
|
+
this.requestRender();
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
onResize(width, height) {
|
|
2619
|
+
this.maxVisibleTabs = Math.max(1, Math.floor(width / this._tabWidth));
|
|
2620
|
+
this.updateScrollOffset();
|
|
2621
|
+
this.requestRender();
|
|
2622
|
+
}
|
|
2623
|
+
setTabWidth(tabWidth) {
|
|
2624
|
+
if (this._tabWidth === tabWidth)
|
|
2625
|
+
return;
|
|
2626
|
+
this._tabWidth = tabWidth;
|
|
2627
|
+
this.maxVisibleTabs = Math.max(1, Math.floor(this.width / this._tabWidth));
|
|
2628
|
+
this.updateScrollOffset();
|
|
2629
|
+
this.requestRender();
|
|
2630
|
+
}
|
|
2631
|
+
getTabWidth() {
|
|
2632
|
+
return this._tabWidth;
|
|
2633
|
+
}
|
|
2634
|
+
handleKeyPress(key) {
|
|
2635
|
+
const keyName = typeof key === "string" ? key : key.name;
|
|
2636
|
+
switch (keyName) {
|
|
2637
|
+
case "left":
|
|
2638
|
+
case "[":
|
|
2639
|
+
this.moveLeft();
|
|
2640
|
+
return true;
|
|
2641
|
+
case "right":
|
|
2642
|
+
case "]":
|
|
2643
|
+
this.moveRight();
|
|
2644
|
+
return true;
|
|
2645
|
+
case "return":
|
|
2646
|
+
case "enter":
|
|
2647
|
+
this.selectCurrent();
|
|
2648
|
+
return true;
|
|
2649
|
+
}
|
|
2650
|
+
return false;
|
|
2651
|
+
}
|
|
2652
|
+
get options() {
|
|
2653
|
+
return this._options;
|
|
2654
|
+
}
|
|
2655
|
+
set options(options) {
|
|
2656
|
+
this._options = options;
|
|
2657
|
+
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, options.length - 1));
|
|
2658
|
+
this.updateScrollOffset();
|
|
2659
|
+
this.requestRender();
|
|
2660
|
+
}
|
|
2661
|
+
set backgroundColor(color) {
|
|
2662
|
+
this._backgroundColor = parseColor(color);
|
|
2663
|
+
this.requestRender();
|
|
2664
|
+
}
|
|
2665
|
+
set textColor(color) {
|
|
2666
|
+
this._textColor = parseColor(color);
|
|
2667
|
+
this.requestRender();
|
|
2668
|
+
}
|
|
2669
|
+
set focusedBackgroundColor(color) {
|
|
2670
|
+
this._focusedBackgroundColor = parseColor(color);
|
|
2671
|
+
this.requestRender();
|
|
2672
|
+
}
|
|
2673
|
+
set focusedTextColor(color) {
|
|
2674
|
+
this._focusedTextColor = parseColor(color);
|
|
2675
|
+
this.requestRender();
|
|
2676
|
+
}
|
|
2677
|
+
set selectedBackgroundColor(color) {
|
|
2678
|
+
this._selectedBackgroundColor = parseColor(color);
|
|
2679
|
+
this.requestRender();
|
|
2680
|
+
}
|
|
2681
|
+
set selectedTextColor(color) {
|
|
2682
|
+
this._selectedTextColor = parseColor(color);
|
|
2683
|
+
this.requestRender();
|
|
2684
|
+
}
|
|
2685
|
+
set selectedDescriptionColor(color) {
|
|
2686
|
+
this._selectedDescriptionColor = parseColor(color);
|
|
2687
|
+
this.requestRender();
|
|
2688
|
+
}
|
|
2689
|
+
get showDescription() {
|
|
2690
|
+
return this._showDescription;
|
|
2691
|
+
}
|
|
2692
|
+
set showDescription(show) {
|
|
2693
|
+
if (this._showDescription !== show) {
|
|
2694
|
+
this._showDescription = show;
|
|
2695
|
+
const newHeight = this.calculateDynamicHeight();
|
|
2696
|
+
this.height = newHeight;
|
|
2697
|
+
this.requestRender();
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
get showUnderline() {
|
|
2701
|
+
return this._showUnderline;
|
|
2702
|
+
}
|
|
2703
|
+
set showUnderline(show) {
|
|
2704
|
+
if (this._showUnderline !== show) {
|
|
2705
|
+
this._showUnderline = show;
|
|
2706
|
+
const newHeight = this.calculateDynamicHeight();
|
|
2707
|
+
this.height = newHeight;
|
|
2708
|
+
this.requestRender();
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
get showScrollArrows() {
|
|
2712
|
+
return this._showScrollArrows;
|
|
2713
|
+
}
|
|
2714
|
+
set showScrollArrows(show) {
|
|
2715
|
+
if (this._showScrollArrows !== show) {
|
|
2716
|
+
this._showScrollArrows = show;
|
|
2717
|
+
this.requestRender();
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
get wrapSelection() {
|
|
2721
|
+
return this._wrapSelection;
|
|
2722
|
+
}
|
|
2723
|
+
set wrapSelection(wrap) {
|
|
2724
|
+
this._wrapSelection = wrap;
|
|
2725
|
+
}
|
|
2726
|
+
get tabWidth() {
|
|
2727
|
+
return this._tabWidth;
|
|
2728
|
+
}
|
|
2729
|
+
set tabWidth(tabWidth) {
|
|
2730
|
+
if (this._tabWidth === tabWidth)
|
|
2731
|
+
return;
|
|
2732
|
+
this._tabWidth = tabWidth;
|
|
2733
|
+
this.maxVisibleTabs = Math.max(1, Math.floor(this.width / this._tabWidth));
|
|
2734
|
+
this.updateScrollOffset();
|
|
2735
|
+
this.requestRender();
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
// src/renderables/Slider.ts
|
|
2739
|
+
var defaultThumbBackgroundColor = RGBA.fromHex("#9a9ea3");
|
|
2740
|
+
var defaultTrackBackgroundColor = RGBA.fromHex("#252527");
|
|
2741
|
+
|
|
2742
|
+
class SliderRenderable extends Renderable {
|
|
2743
|
+
orientation;
|
|
2744
|
+
_thumbSize;
|
|
2745
|
+
_thumbPosition;
|
|
2746
|
+
_backgroundColor;
|
|
2747
|
+
_foregroundColor;
|
|
2748
|
+
_onChange;
|
|
2749
|
+
constructor(ctx, options) {
|
|
2750
|
+
super(ctx, options);
|
|
2751
|
+
this.orientation = options.orientation;
|
|
2752
|
+
this._thumbSize = options.thumbSize ?? 1;
|
|
2753
|
+
this._thumbPosition = options.thumbPosition ?? 0;
|
|
2754
|
+
this._onChange = options.onChange;
|
|
2755
|
+
this._backgroundColor = options.backgroundColor ? parseColor(options.backgroundColor) : defaultTrackBackgroundColor;
|
|
2756
|
+
this._foregroundColor = options.foregroundColor ? parseColor(options.foregroundColor) : defaultThumbBackgroundColor;
|
|
2757
|
+
this.setupMouseHandling();
|
|
2758
|
+
}
|
|
2759
|
+
get thumbSize() {
|
|
2760
|
+
return this._thumbSize;
|
|
2761
|
+
}
|
|
2762
|
+
set thumbSize(value) {
|
|
2763
|
+
const clamped = Math.max(1, Math.min(value, this.orientation === "vertical" ? this.height : this.width));
|
|
2764
|
+
if (clamped !== this._thumbSize) {
|
|
2765
|
+
this._thumbSize = clamped;
|
|
2766
|
+
this.requestRender();
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
get thumbPosition() {
|
|
2770
|
+
return this._thumbPosition;
|
|
2771
|
+
}
|
|
2772
|
+
set thumbPosition(value) {
|
|
2773
|
+
const clamped = Math.max(0, Math.min(1, value));
|
|
2774
|
+
if (clamped !== this._thumbPosition) {
|
|
2775
|
+
this._thumbPosition = clamped;
|
|
2776
|
+
this._onChange?.(clamped);
|
|
2777
|
+
this.emit("change", { position: clamped });
|
|
2778
|
+
this.requestRender();
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
get backgroundColor() {
|
|
2782
|
+
return this._backgroundColor;
|
|
2783
|
+
}
|
|
2784
|
+
set backgroundColor(value) {
|
|
2785
|
+
this._backgroundColor = parseColor(value);
|
|
2786
|
+
this.requestRender();
|
|
2787
|
+
}
|
|
2788
|
+
get foregroundColor() {
|
|
2789
|
+
return this._foregroundColor;
|
|
2790
|
+
}
|
|
2791
|
+
set foregroundColor(value) {
|
|
2792
|
+
this._foregroundColor = parseColor(value);
|
|
2793
|
+
this.requestRender();
|
|
2794
|
+
}
|
|
2795
|
+
setupMouseHandling() {
|
|
2796
|
+
let isDragging = false;
|
|
2797
|
+
let relativeStartPos = 0;
|
|
2798
|
+
this.onMouseDown = (event) => {
|
|
2799
|
+
event.stopPropagation();
|
|
2800
|
+
event.preventDefault();
|
|
2801
|
+
isDragging = true;
|
|
2802
|
+
const thumbRect = this.getThumbRect();
|
|
2803
|
+
const isOnThumb = event.x >= thumbRect.x && event.x < thumbRect.x + thumbRect.width && event.y >= thumbRect.y && event.y < thumbRect.y + thumbRect.height;
|
|
2804
|
+
if (isOnThumb) {
|
|
2805
|
+
relativeStartPos = this.orientation === "vertical" ? event.y - thumbRect.y : event.x - thumbRect.x;
|
|
2806
|
+
} else {
|
|
2807
|
+
relativeStartPos = this.orientation === "vertical" ? thumbRect.height / 2 : thumbRect.width / 2;
|
|
2808
|
+
}
|
|
2809
|
+
this.updatePositionFromMouse(event, relativeStartPos);
|
|
2810
|
+
};
|
|
2811
|
+
this.onMouseDrag = (event) => {
|
|
2812
|
+
if (!isDragging)
|
|
2813
|
+
return;
|
|
2814
|
+
event.stopPropagation();
|
|
2815
|
+
this.updatePositionFromMouse(event, relativeStartPos);
|
|
2816
|
+
};
|
|
2817
|
+
this.onMouseUp = () => {
|
|
2818
|
+
isDragging = false;
|
|
2819
|
+
};
|
|
2820
|
+
}
|
|
2821
|
+
updatePositionFromMouse(event, relativeStartPos) {
|
|
2822
|
+
const trackStart = this.orientation === "vertical" ? this.y : this.x;
|
|
2823
|
+
const trackSize = this.orientation === "vertical" ? this.height : this.width;
|
|
2824
|
+
const mousePos = this.orientation === "vertical" ? event.y : event.x;
|
|
2825
|
+
const thumbStartPos = mousePos - trackStart - relativeStartPos;
|
|
2826
|
+
const maxThumbStartPos = trackSize - this._thumbSize;
|
|
2827
|
+
const clampedThumbStartPos = Math.max(0, Math.min(maxThumbStartPos, thumbStartPos));
|
|
2828
|
+
const newPosition = maxThumbStartPos > 0 ? clampedThumbStartPos / maxThumbStartPos : 0;
|
|
2829
|
+
this.thumbPosition = newPosition;
|
|
2830
|
+
}
|
|
2831
|
+
getThumbPosition() {
|
|
2832
|
+
const trackSize = this.orientation === "vertical" ? this.height : this.width;
|
|
2833
|
+
const maxPos = trackSize - this._thumbSize;
|
|
2834
|
+
return Math.round(this._thumbPosition * maxPos);
|
|
2835
|
+
}
|
|
2836
|
+
getThumbRect() {
|
|
2837
|
+
const thumbPos = this.getThumbPosition();
|
|
2838
|
+
if (this.orientation === "vertical") {
|
|
2839
|
+
return {
|
|
2840
|
+
x: this.x,
|
|
2841
|
+
y: this.y + thumbPos,
|
|
2842
|
+
width: this.width,
|
|
2843
|
+
height: this._thumbSize
|
|
2844
|
+
};
|
|
2845
|
+
} else {
|
|
2846
|
+
return {
|
|
2847
|
+
x: this.x + thumbPos,
|
|
2848
|
+
y: this.y,
|
|
2849
|
+
width: this._thumbSize,
|
|
2850
|
+
height: this.height
|
|
2851
|
+
};
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
renderSelf(buffer) {
|
|
2855
|
+
buffer.fillRect(this.x, this.y, this.width, this.height, this._backgroundColor);
|
|
2856
|
+
const thumbRect = this.getThumbRect();
|
|
2857
|
+
buffer.fillRect(thumbRect.x, thumbRect.y, thumbRect.width, thumbRect.height, this._foregroundColor);
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
// src/renderables/ScrollBar.ts
|
|
2862
|
+
class ScrollBarRenderable extends Renderable {
|
|
2863
|
+
slider;
|
|
2864
|
+
startArrow;
|
|
2865
|
+
endArrow;
|
|
2866
|
+
orientation;
|
|
2867
|
+
focusable = true;
|
|
2868
|
+
_scrollSize = 0;
|
|
2869
|
+
_scrollPosition = 0;
|
|
2870
|
+
_viewportSize = 0;
|
|
2871
|
+
_showArrows = false;
|
|
2872
|
+
_manualVisibility = false;
|
|
2873
|
+
_onChange;
|
|
2874
|
+
scrollStep = null;
|
|
2875
|
+
get visible() {
|
|
2876
|
+
return super.visible;
|
|
2877
|
+
}
|
|
2878
|
+
set visible(value) {
|
|
2879
|
+
this._manualVisibility = true;
|
|
2880
|
+
super.visible = value;
|
|
2881
|
+
}
|
|
2882
|
+
resetVisibilityControl() {
|
|
2883
|
+
this._manualVisibility = false;
|
|
2884
|
+
this.recalculateVisibility();
|
|
2885
|
+
}
|
|
2886
|
+
get scrollSize() {
|
|
2887
|
+
return this._scrollSize;
|
|
2888
|
+
}
|
|
2889
|
+
get scrollPosition() {
|
|
2890
|
+
return this._scrollPosition;
|
|
2891
|
+
}
|
|
2892
|
+
get viewportSize() {
|
|
2893
|
+
return this._viewportSize;
|
|
2894
|
+
}
|
|
2895
|
+
set scrollSize(value) {
|
|
2896
|
+
if (value === this.scrollSize)
|
|
2897
|
+
return;
|
|
2898
|
+
this._scrollSize = value;
|
|
2899
|
+
this.recalculateVisibility();
|
|
2900
|
+
this.scrollPosition = this.scrollPosition;
|
|
2901
|
+
}
|
|
2902
|
+
set scrollPosition(value) {
|
|
2903
|
+
const newPosition = Math.round(Math.min(Math.max(0, value), this.scrollSize - this.viewportSize));
|
|
2904
|
+
if (newPosition !== this._scrollPosition) {
|
|
2905
|
+
this._scrollPosition = newPosition;
|
|
2906
|
+
this.updateSliderFromScrollState();
|
|
2907
|
+
this._onChange?.(newPosition);
|
|
2908
|
+
this.emit("change", { position: newPosition });
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
set viewportSize(value) {
|
|
2912
|
+
if (value === this.viewportSize)
|
|
2913
|
+
return;
|
|
2914
|
+
this._viewportSize = value;
|
|
2915
|
+
this.recalculateVisibility();
|
|
2916
|
+
this.scrollPosition = this.scrollPosition;
|
|
2917
|
+
}
|
|
2918
|
+
get showArrows() {
|
|
2919
|
+
return this._showArrows;
|
|
2920
|
+
}
|
|
2921
|
+
set showArrows(value) {
|
|
2922
|
+
if (value === this._showArrows)
|
|
2923
|
+
return;
|
|
2924
|
+
this._showArrows = value;
|
|
2925
|
+
this.startArrow.visible = value;
|
|
2926
|
+
this.endArrow.visible = value;
|
|
2927
|
+
}
|
|
2928
|
+
constructor(ctx, { trackOptions, arrowOptions, orientation, showArrows = false, ...options }) {
|
|
2929
|
+
super(ctx, {
|
|
2930
|
+
flexDirection: orientation === "vertical" ? "column" : "row",
|
|
2931
|
+
alignSelf: "stretch",
|
|
2932
|
+
alignItems: "stretch",
|
|
2933
|
+
...options
|
|
2934
|
+
});
|
|
2935
|
+
this._onChange = options.onChange;
|
|
2936
|
+
this.orientation = orientation;
|
|
2937
|
+
this._showArrows = showArrows;
|
|
2938
|
+
this.slider = new SliderRenderable(ctx, {
|
|
2939
|
+
orientation,
|
|
2940
|
+
onChange: (position) => {
|
|
2941
|
+
const scrollRange = Math.max(0, this._scrollSize - this._viewportSize);
|
|
2942
|
+
this._scrollPosition = Math.round(position * scrollRange);
|
|
2943
|
+
this._onChange?.(this._scrollPosition);
|
|
2944
|
+
this.emit("change", { position: this._scrollPosition });
|
|
2945
|
+
},
|
|
2946
|
+
...orientation === "vertical" ? {
|
|
2947
|
+
width: 2,
|
|
2948
|
+
height: "100%",
|
|
2949
|
+
marginLeft: "auto"
|
|
2950
|
+
} : {
|
|
2951
|
+
width: "100%",
|
|
2952
|
+
height: 1,
|
|
2953
|
+
marginTop: "auto"
|
|
2954
|
+
},
|
|
2955
|
+
flexGrow: 1,
|
|
2956
|
+
flexShrink: 1,
|
|
2957
|
+
...trackOptions
|
|
2958
|
+
});
|
|
2959
|
+
this.updateSliderFromScrollState();
|
|
2960
|
+
const arrowOpts = arrowOptions ? {
|
|
2961
|
+
foregroundColor: arrowOptions.backgroundColor,
|
|
2962
|
+
backgroundColor: arrowOptions.backgroundColor,
|
|
2963
|
+
attributes: arrowOptions.attributes,
|
|
2964
|
+
...arrowOptions
|
|
2965
|
+
} : {};
|
|
2966
|
+
this.startArrow = new ArrowRenderable(ctx, {
|
|
2967
|
+
alignSelf: "center",
|
|
2968
|
+
visible: this.showArrows,
|
|
2969
|
+
direction: this.orientation === "vertical" ? "up" : "left",
|
|
2970
|
+
height: this.orientation === "vertical" ? 1 : 1,
|
|
2971
|
+
...arrowOpts
|
|
2972
|
+
});
|
|
2973
|
+
this.endArrow = new ArrowRenderable(ctx, {
|
|
2974
|
+
alignSelf: "center",
|
|
2975
|
+
visible: this.showArrows,
|
|
2976
|
+
direction: this.orientation === "vertical" ? "down" : "right",
|
|
2977
|
+
height: this.orientation === "vertical" ? 1 : 1,
|
|
2978
|
+
...arrowOpts
|
|
2979
|
+
});
|
|
2980
|
+
this.add(this.startArrow);
|
|
2981
|
+
this.add(this.slider);
|
|
2982
|
+
this.add(this.endArrow);
|
|
2983
|
+
let startArrowMouseTimeout = undefined;
|
|
2984
|
+
let endArrowMouseTimeout = undefined;
|
|
2985
|
+
this.startArrow.onMouseDown = (event) => {
|
|
2986
|
+
event.stopPropagation();
|
|
2987
|
+
event.preventDefault();
|
|
2988
|
+
this.scrollBy(-0.5, "viewport");
|
|
2989
|
+
startArrowMouseTimeout = setTimeout(() => {
|
|
2990
|
+
this.scrollBy(-0.5, "viewport");
|
|
2991
|
+
startArrowMouseTimeout = setInterval(() => {
|
|
2992
|
+
this.scrollBy(-0.2, "viewport");
|
|
2993
|
+
}, 200);
|
|
2994
|
+
}, 500);
|
|
2995
|
+
};
|
|
2996
|
+
this.startArrow.onMouseUp = (event) => {
|
|
2997
|
+
event.stopPropagation();
|
|
2998
|
+
clearInterval(startArrowMouseTimeout);
|
|
2999
|
+
};
|
|
3000
|
+
this.endArrow.onMouseDown = (event) => {
|
|
3001
|
+
event.stopPropagation();
|
|
3002
|
+
event.preventDefault();
|
|
3003
|
+
this.scrollBy(0.5, "viewport");
|
|
3004
|
+
endArrowMouseTimeout = setTimeout(() => {
|
|
3005
|
+
this.scrollBy(0.5, "viewport");
|
|
3006
|
+
endArrowMouseTimeout = setInterval(() => {
|
|
3007
|
+
this.scrollBy(0.2, "viewport");
|
|
3008
|
+
}, 200);
|
|
3009
|
+
}, 500);
|
|
3010
|
+
};
|
|
3011
|
+
this.endArrow.onMouseUp = (event) => {
|
|
3012
|
+
event.stopPropagation();
|
|
3013
|
+
clearInterval(endArrowMouseTimeout);
|
|
3014
|
+
};
|
|
3015
|
+
}
|
|
3016
|
+
set arrowOptions(options) {
|
|
3017
|
+
Object.assign(this.startArrow, options);
|
|
3018
|
+
Object.assign(this.endArrow, options);
|
|
3019
|
+
this.requestRender();
|
|
3020
|
+
}
|
|
3021
|
+
set trackOptions(options) {
|
|
3022
|
+
Object.assign(this.slider, options);
|
|
3023
|
+
this.requestRender();
|
|
3024
|
+
}
|
|
3025
|
+
updateSliderFromScrollState() {
|
|
3026
|
+
const trackSize = this.orientation === "vertical" ? this.slider.height : this.slider.width;
|
|
3027
|
+
const scrollRange = Math.max(0, this._scrollSize - this._viewportSize);
|
|
3028
|
+
if (scrollRange === 0) {
|
|
3029
|
+
this.slider.thumbSize = trackSize;
|
|
3030
|
+
this.slider.thumbPosition = 0;
|
|
3031
|
+
} else {
|
|
3032
|
+
const sizeRatio = this._viewportSize / this._scrollSize;
|
|
3033
|
+
this.slider.thumbSize = Math.max(1, Math.round(sizeRatio * trackSize));
|
|
3034
|
+
const positionRatio = this._scrollPosition / scrollRange;
|
|
3035
|
+
this.slider.thumbPosition = Math.max(0, Math.min(1, positionRatio));
|
|
3036
|
+
}
|
|
3037
|
+
}
|
|
3038
|
+
scrollBy(delta, unit = "absolute") {
|
|
3039
|
+
const multiplier = unit === "viewport" ? this.viewportSize : unit === "content" ? this.scrollSize : unit === "step" ? this.scrollStep ?? 1 : 1;
|
|
3040
|
+
const resolvedDelta = multiplier * delta;
|
|
3041
|
+
this.scrollPosition += resolvedDelta;
|
|
3042
|
+
}
|
|
3043
|
+
recalculateVisibility() {
|
|
3044
|
+
if (!this._manualVisibility) {
|
|
3045
|
+
const sizeRatio = this.scrollSize <= this.viewportSize ? 1 : this.viewportSize / this.scrollSize;
|
|
3046
|
+
super.visible = sizeRatio < 1;
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
handleKeyPress(key) {
|
|
3050
|
+
const keyName = typeof key === "string" ? key : key.name;
|
|
3051
|
+
switch (keyName) {
|
|
3052
|
+
case "left":
|
|
3053
|
+
case "h":
|
|
3054
|
+
if (this.orientation !== "horizontal")
|
|
3055
|
+
return false;
|
|
3056
|
+
this.scrollBy(-1 / 5, "viewport");
|
|
3057
|
+
return true;
|
|
3058
|
+
case "right":
|
|
3059
|
+
case "l":
|
|
3060
|
+
if (this.orientation !== "horizontal")
|
|
3061
|
+
return false;
|
|
3062
|
+
this.scrollBy(1 / 5, "viewport");
|
|
3063
|
+
return true;
|
|
3064
|
+
case "up":
|
|
3065
|
+
case "k":
|
|
3066
|
+
if (this.orientation !== "vertical")
|
|
3067
|
+
return false;
|
|
3068
|
+
this.scrollBy(-1 / 5, "viewport");
|
|
3069
|
+
return true;
|
|
3070
|
+
case "down":
|
|
3071
|
+
case "j":
|
|
3072
|
+
if (this.orientation !== "vertical")
|
|
3073
|
+
return false;
|
|
3074
|
+
this.scrollBy(1 / 5, "viewport");
|
|
3075
|
+
return true;
|
|
3076
|
+
case "pageup":
|
|
3077
|
+
this.scrollBy(-1 / 2, "viewport");
|
|
3078
|
+
return true;
|
|
3079
|
+
case "pagedown":
|
|
3080
|
+
this.scrollBy(1 / 2, "viewport");
|
|
3081
|
+
return true;
|
|
3082
|
+
case "home":
|
|
3083
|
+
this.scrollBy(-1, "content");
|
|
3084
|
+
return true;
|
|
3085
|
+
case "end":
|
|
3086
|
+
this.scrollBy(1, "content");
|
|
3087
|
+
return true;
|
|
3088
|
+
}
|
|
3089
|
+
return false;
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
class ArrowRenderable extends Renderable {
|
|
3094
|
+
_direction;
|
|
3095
|
+
_foregroundColor;
|
|
3096
|
+
_backgroundColor;
|
|
3097
|
+
_attributes;
|
|
3098
|
+
_arrowChars;
|
|
3099
|
+
constructor(ctx, options) {
|
|
3100
|
+
super(ctx, options);
|
|
3101
|
+
this._direction = options.direction;
|
|
3102
|
+
this._foregroundColor = options.foregroundColor ? parseColor(options.foregroundColor) : RGBA.fromValues(1, 1, 1, 1);
|
|
3103
|
+
this._backgroundColor = options.backgroundColor ? parseColor(options.backgroundColor) : RGBA.fromValues(0, 0, 0, 0);
|
|
3104
|
+
this._attributes = options.attributes ?? 0;
|
|
3105
|
+
this._arrowChars = {
|
|
3106
|
+
up: "\u25E2\u25E3",
|
|
3107
|
+
down: "\u25E5\u25E4",
|
|
3108
|
+
left: " \u25C0 ",
|
|
3109
|
+
right: " \u25B6 ",
|
|
3110
|
+
...options.arrowChars
|
|
3111
|
+
};
|
|
3112
|
+
if (!options.width) {
|
|
3113
|
+
this.width = Bun.stringWidth(this.getArrowChar());
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
get direction() {
|
|
3117
|
+
return this._direction;
|
|
3118
|
+
}
|
|
3119
|
+
set direction(value) {
|
|
3120
|
+
if (this._direction !== value) {
|
|
3121
|
+
this._direction = value;
|
|
3122
|
+
this.requestRender();
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
get foregroundColor() {
|
|
3126
|
+
return this._foregroundColor;
|
|
3127
|
+
}
|
|
3128
|
+
set foregroundColor(value) {
|
|
3129
|
+
if (this._foregroundColor !== value) {
|
|
3130
|
+
this._foregroundColor = parseColor(value);
|
|
3131
|
+
this.requestRender();
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
get backgroundColor() {
|
|
3135
|
+
return this._backgroundColor;
|
|
3136
|
+
}
|
|
3137
|
+
set backgroundColor(value) {
|
|
3138
|
+
if (this._backgroundColor !== value) {
|
|
3139
|
+
this._backgroundColor = parseColor(value);
|
|
3140
|
+
this.requestRender();
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
get attributes() {
|
|
3144
|
+
return this._attributes;
|
|
3145
|
+
}
|
|
3146
|
+
set attributes(value) {
|
|
3147
|
+
if (this._attributes !== value) {
|
|
3148
|
+
this._attributes = value;
|
|
3149
|
+
this.requestRender();
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
set arrowChars(value) {
|
|
3153
|
+
this._arrowChars = {
|
|
3154
|
+
...this._arrowChars,
|
|
3155
|
+
...value
|
|
3156
|
+
};
|
|
3157
|
+
this.requestRender();
|
|
3158
|
+
}
|
|
3159
|
+
renderSelf(buffer) {
|
|
3160
|
+
const char = this.getArrowChar();
|
|
3161
|
+
buffer.drawText(char, this.x, this.y, this._foregroundColor, this._backgroundColor, this._attributes);
|
|
3162
|
+
}
|
|
3163
|
+
getArrowChar() {
|
|
3164
|
+
switch (this._direction) {
|
|
3165
|
+
case "up":
|
|
3166
|
+
return this._arrowChars.up;
|
|
3167
|
+
case "down":
|
|
3168
|
+
return this._arrowChars.down;
|
|
3169
|
+
case "left":
|
|
3170
|
+
return this._arrowChars.left;
|
|
3171
|
+
case "right":
|
|
3172
|
+
return this._arrowChars.right;
|
|
3173
|
+
default:
|
|
3174
|
+
return "?";
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
// src/renderables/ScrollBox.ts
|
|
3180
|
+
class ContentRenderable extends BoxRenderable {
|
|
3181
|
+
viewport;
|
|
3182
|
+
constructor(ctx, viewport, options) {
|
|
3183
|
+
super(ctx, options);
|
|
3184
|
+
this.viewport = viewport;
|
|
3185
|
+
}
|
|
3186
|
+
_getChildren() {
|
|
3187
|
+
return this.getChildrenInViewport(this.viewport);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
class ScrollBoxRenderable extends BoxRenderable {
|
|
3192
|
+
static idCounter = 0;
|
|
3193
|
+
internalId = 0;
|
|
3194
|
+
wrapper;
|
|
3195
|
+
viewport;
|
|
3196
|
+
content;
|
|
3197
|
+
horizontalScrollBar;
|
|
3198
|
+
verticalScrollBar;
|
|
3199
|
+
focusable = true;
|
|
3200
|
+
selectionListener;
|
|
3201
|
+
autoScrollMouseX = 0;
|
|
3202
|
+
autoScrollMouseY = 0;
|
|
3203
|
+
autoScrollThresholdVertical = 3;
|
|
3204
|
+
autoScrollThresholdHorizontal = 3;
|
|
3205
|
+
autoScrollSpeedSlow = 6;
|
|
3206
|
+
autoScrollSpeedMedium = 36;
|
|
3207
|
+
autoScrollSpeedFast = 72;
|
|
3208
|
+
isAutoScrolling = false;
|
|
3209
|
+
cachedAutoScrollSpeed = 3;
|
|
3210
|
+
autoScrollAccumulatorX = 0;
|
|
3211
|
+
autoScrollAccumulatorY = 0;
|
|
3212
|
+
get scrollTop() {
|
|
3213
|
+
return this.verticalScrollBar.scrollPosition;
|
|
3214
|
+
}
|
|
3215
|
+
set scrollTop(value) {
|
|
3216
|
+
this.verticalScrollBar.scrollPosition = value;
|
|
3217
|
+
}
|
|
3218
|
+
get scrollLeft() {
|
|
3219
|
+
return this.horizontalScrollBar.scrollPosition;
|
|
3220
|
+
}
|
|
3221
|
+
set scrollLeft(value) {
|
|
3222
|
+
this.horizontalScrollBar.scrollPosition = value;
|
|
3223
|
+
}
|
|
3224
|
+
get scrollWidth() {
|
|
3225
|
+
return this.horizontalScrollBar.scrollSize;
|
|
3226
|
+
}
|
|
3227
|
+
get scrollHeight() {
|
|
3228
|
+
return this.verticalScrollBar.scrollSize;
|
|
3229
|
+
}
|
|
3230
|
+
constructor(ctx, {
|
|
3231
|
+
wrapperOptions,
|
|
3232
|
+
viewportOptions,
|
|
3233
|
+
contentOptions,
|
|
3234
|
+
rootOptions,
|
|
3235
|
+
scrollbarOptions,
|
|
3236
|
+
verticalScrollbarOptions,
|
|
3237
|
+
horizontalScrollbarOptions,
|
|
3238
|
+
...options
|
|
3239
|
+
}) {
|
|
3240
|
+
super(ctx, {
|
|
3241
|
+
flexShrink: 1,
|
|
3242
|
+
flexGrow: 1,
|
|
3243
|
+
flexDirection: "row",
|
|
3244
|
+
flexWrap: "wrap",
|
|
3245
|
+
alignItems: "stretch",
|
|
3246
|
+
...options,
|
|
3247
|
+
...rootOptions
|
|
3248
|
+
});
|
|
3249
|
+
this.internalId = ScrollBoxRenderable.idCounter++;
|
|
3250
|
+
this.wrapper = new BoxRenderable(ctx, {
|
|
3251
|
+
flexDirection: "column",
|
|
3252
|
+
flexGrow: 1,
|
|
3253
|
+
flexShrink: 1,
|
|
3254
|
+
flexBasis: "auto",
|
|
3255
|
+
maxHeight: "100%",
|
|
3256
|
+
maxWidth: "100%",
|
|
3257
|
+
...wrapperOptions,
|
|
3258
|
+
id: `scroll-box-wrapper-${this.internalId}`
|
|
3259
|
+
});
|
|
3260
|
+
super.add(this.wrapper);
|
|
3261
|
+
this.viewport = new BoxRenderable(ctx, {
|
|
3262
|
+
flexDirection: "column",
|
|
3263
|
+
flexGrow: 1,
|
|
3264
|
+
flexShrink: 1,
|
|
3265
|
+
flexBasis: "auto",
|
|
3266
|
+
maxHeight: "100%",
|
|
3267
|
+
maxWidth: "100%",
|
|
3268
|
+
overflow: "scroll",
|
|
3269
|
+
onSizeChange: () => {
|
|
3270
|
+
this.recalculateBarProps();
|
|
3271
|
+
},
|
|
3272
|
+
...viewportOptions,
|
|
3273
|
+
id: `scroll-box-viewport-${this.internalId}`
|
|
3274
|
+
});
|
|
3275
|
+
this.wrapper.add(this.viewport);
|
|
3276
|
+
this.content = new ContentRenderable(ctx, this.viewport, {
|
|
3277
|
+
alignSelf: "flex-start",
|
|
3278
|
+
minWidth: "100%",
|
|
3279
|
+
minHeight: "100%",
|
|
3280
|
+
onSizeChange: () => {
|
|
3281
|
+
this.recalculateBarProps();
|
|
3282
|
+
},
|
|
3283
|
+
...contentOptions,
|
|
3284
|
+
id: `scroll-box-content-${this.internalId}`
|
|
3285
|
+
});
|
|
3286
|
+
this.viewport.add(this.content);
|
|
3287
|
+
this.verticalScrollBar = new ScrollBarRenderable(ctx, {
|
|
3288
|
+
...scrollbarOptions,
|
|
3289
|
+
...verticalScrollbarOptions,
|
|
3290
|
+
arrowOptions: {
|
|
3291
|
+
...scrollbarOptions?.arrowOptions,
|
|
3292
|
+
...verticalScrollbarOptions?.arrowOptions
|
|
3293
|
+
},
|
|
3294
|
+
id: `scroll-box-vertical-scrollbar-${this.internalId}`,
|
|
3295
|
+
orientation: "vertical",
|
|
3296
|
+
onChange: (position) => {
|
|
3297
|
+
this.content.translateY = -position;
|
|
3298
|
+
}
|
|
3299
|
+
});
|
|
3300
|
+
super.add(this.verticalScrollBar);
|
|
3301
|
+
this.horizontalScrollBar = new ScrollBarRenderable(ctx, {
|
|
3302
|
+
...scrollbarOptions,
|
|
3303
|
+
...horizontalScrollbarOptions,
|
|
3304
|
+
arrowOptions: {
|
|
3305
|
+
...scrollbarOptions?.arrowOptions,
|
|
3306
|
+
...horizontalScrollbarOptions?.arrowOptions
|
|
3307
|
+
},
|
|
3308
|
+
id: `scroll-box-horizontal-scrollbar-${this.internalId}`,
|
|
3309
|
+
orientation: "horizontal",
|
|
3310
|
+
onChange: (position) => {
|
|
3311
|
+
this.content.translateX = -position;
|
|
3312
|
+
}
|
|
3313
|
+
});
|
|
3314
|
+
this.wrapper.add(this.horizontalScrollBar);
|
|
3315
|
+
this.recalculateBarProps();
|
|
3316
|
+
this.selectionListener = () => {
|
|
3317
|
+
const selection = this._ctx.getSelection();
|
|
3318
|
+
if (!selection || !selection.isSelecting) {
|
|
3319
|
+
this.stopAutoScroll();
|
|
3320
|
+
}
|
|
3321
|
+
};
|
|
3322
|
+
this._ctx.on("selection", this.selectionListener);
|
|
3323
|
+
}
|
|
3324
|
+
onUpdate(deltaTime) {
|
|
3325
|
+
this.handleAutoScroll(deltaTime);
|
|
3326
|
+
}
|
|
3327
|
+
scrollBy(delta, unit = "absolute") {
|
|
3328
|
+
if (typeof delta === "number") {
|
|
3329
|
+
this.verticalScrollBar.scrollBy(delta, unit);
|
|
3330
|
+
} else {
|
|
3331
|
+
this.verticalScrollBar.scrollBy(delta.y, unit);
|
|
3332
|
+
this.horizontalScrollBar.scrollBy(delta.x, unit);
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
scrollTo(position) {
|
|
3336
|
+
if (typeof position === "number") {
|
|
3337
|
+
this.scrollTop = position;
|
|
3338
|
+
} else {
|
|
3339
|
+
this.scrollTop = position.y;
|
|
3340
|
+
this.scrollLeft = position.x;
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
add(obj, index) {
|
|
3344
|
+
return this.content.add(obj, index);
|
|
3345
|
+
}
|
|
3346
|
+
remove(id) {
|
|
3347
|
+
this.content.remove(id);
|
|
3348
|
+
}
|
|
3349
|
+
getChildren() {
|
|
3350
|
+
return this.content.getChildren();
|
|
3351
|
+
}
|
|
3352
|
+
onMouseEvent(event) {
|
|
3353
|
+
if (event.type === "scroll") {
|
|
3354
|
+
let dir = event.scroll?.direction;
|
|
3355
|
+
if (event.modifiers.shift)
|
|
3356
|
+
dir = dir === "up" ? "left" : dir === "down" ? "right" : dir === "right" ? "down" : "up";
|
|
3357
|
+
if (dir === "up")
|
|
3358
|
+
this.scrollTop -= event.scroll?.delta ?? 0;
|
|
3359
|
+
else if (dir === "down")
|
|
3360
|
+
this.scrollTop += event.scroll?.delta ?? 0;
|
|
3361
|
+
else if (dir === "left")
|
|
3362
|
+
this.scrollLeft -= event.scroll?.delta ?? 0;
|
|
3363
|
+
else if (dir === "right")
|
|
3364
|
+
this.scrollLeft += event.scroll?.delta ?? 0;
|
|
3365
|
+
}
|
|
3366
|
+
if (event.type === "drag" && event.isSelecting) {
|
|
3367
|
+
this.updateAutoScroll(event.x, event.y);
|
|
3368
|
+
} else if (event.type === "up") {
|
|
3369
|
+
this.stopAutoScroll();
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
handleKeyPress(key) {
|
|
3373
|
+
if (this.verticalScrollBar.handleKeyPress(key))
|
|
3374
|
+
return true;
|
|
3375
|
+
if (this.horizontalScrollBar.handleKeyPress(key))
|
|
3376
|
+
return true;
|
|
3377
|
+
return false;
|
|
3378
|
+
}
|
|
3379
|
+
startAutoScroll(mouseX, mouseY) {
|
|
3380
|
+
this.stopAutoScroll();
|
|
3381
|
+
this.autoScrollMouseX = mouseX;
|
|
3382
|
+
this.autoScrollMouseY = mouseY;
|
|
3383
|
+
this.cachedAutoScrollSpeed = this.getAutoScrollSpeed(mouseX, mouseY);
|
|
3384
|
+
this.isAutoScrolling = true;
|
|
3385
|
+
if (!this.live) {
|
|
3386
|
+
this.live = true;
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
updateAutoScroll(mouseX, mouseY) {
|
|
3390
|
+
this.autoScrollMouseX = mouseX;
|
|
3391
|
+
this.autoScrollMouseY = mouseY;
|
|
3392
|
+
this.cachedAutoScrollSpeed = this.getAutoScrollSpeed(mouseX, mouseY);
|
|
3393
|
+
const scrollX = this.getAutoScrollDirectionX(mouseX);
|
|
3394
|
+
const scrollY = this.getAutoScrollDirectionY(mouseY);
|
|
3395
|
+
if (scrollX === 0 && scrollY === 0) {
|
|
3396
|
+
this.stopAutoScroll();
|
|
3397
|
+
} else if (!this.isAutoScrolling) {
|
|
3398
|
+
this.startAutoScroll(mouseX, mouseY);
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
stopAutoScroll() {
|
|
3402
|
+
const wasAutoScrolling = this.isAutoScrolling;
|
|
3403
|
+
this.isAutoScrolling = false;
|
|
3404
|
+
this.autoScrollAccumulatorX = 0;
|
|
3405
|
+
this.autoScrollAccumulatorY = 0;
|
|
3406
|
+
if (wasAutoScrolling && !this.hasOtherLiveReasons()) {
|
|
3407
|
+
this.live = false;
|
|
3408
|
+
}
|
|
3409
|
+
}
|
|
3410
|
+
hasOtherLiveReasons() {
|
|
3411
|
+
return false;
|
|
3412
|
+
}
|
|
3413
|
+
handleAutoScroll(deltaTime) {
|
|
3414
|
+
if (!this.isAutoScrolling)
|
|
3415
|
+
return;
|
|
3416
|
+
const scrollX = this.getAutoScrollDirectionX(this.autoScrollMouseX);
|
|
3417
|
+
const scrollY = this.getAutoScrollDirectionY(this.autoScrollMouseY);
|
|
3418
|
+
const scrollAmount = this.cachedAutoScrollSpeed * (deltaTime / 1000);
|
|
3419
|
+
let scrolled = false;
|
|
3420
|
+
if (scrollX !== 0) {
|
|
3421
|
+
this.autoScrollAccumulatorX += scrollX * scrollAmount;
|
|
3422
|
+
const integerScrollX = Math.trunc(this.autoScrollAccumulatorX);
|
|
3423
|
+
if (integerScrollX !== 0) {
|
|
3424
|
+
this.scrollLeft += integerScrollX;
|
|
3425
|
+
this.autoScrollAccumulatorX -= integerScrollX;
|
|
3426
|
+
scrolled = true;
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
if (scrollY !== 0) {
|
|
3430
|
+
this.autoScrollAccumulatorY += scrollY * scrollAmount;
|
|
3431
|
+
const integerScrollY = Math.trunc(this.autoScrollAccumulatorY);
|
|
3432
|
+
if (integerScrollY !== 0) {
|
|
3433
|
+
this.scrollTop += integerScrollY;
|
|
3434
|
+
this.autoScrollAccumulatorY -= integerScrollY;
|
|
3435
|
+
scrolled = true;
|
|
3436
|
+
}
|
|
3437
|
+
}
|
|
3438
|
+
if (scrolled) {
|
|
3439
|
+
this._ctx.requestSelectionUpdate();
|
|
3440
|
+
}
|
|
3441
|
+
if (scrollX === 0 && scrollY === 0) {
|
|
3442
|
+
this.stopAutoScroll();
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
getAutoScrollDirectionX(mouseX) {
|
|
3446
|
+
const relativeX = mouseX - this.x;
|
|
3447
|
+
const distToLeft = relativeX;
|
|
3448
|
+
const distToRight = this.width - relativeX;
|
|
3449
|
+
if (distToLeft <= this.autoScrollThresholdHorizontal) {
|
|
3450
|
+
return this.scrollLeft > 0 ? -1 : 0;
|
|
3451
|
+
} else if (distToRight <= this.autoScrollThresholdHorizontal) {
|
|
3452
|
+
const maxScrollLeft = this.scrollWidth - this.viewport.width;
|
|
3453
|
+
return this.scrollLeft < maxScrollLeft ? 1 : 0;
|
|
3454
|
+
}
|
|
3455
|
+
return 0;
|
|
3456
|
+
}
|
|
3457
|
+
getAutoScrollDirectionY(mouseY) {
|
|
3458
|
+
const relativeY = mouseY - this.y;
|
|
3459
|
+
const distToTop = relativeY;
|
|
3460
|
+
const distToBottom = this.height - relativeY;
|
|
3461
|
+
if (distToTop <= this.autoScrollThresholdVertical) {
|
|
3462
|
+
return this.scrollTop > 0 ? -1 : 0;
|
|
3463
|
+
} else if (distToBottom <= this.autoScrollThresholdVertical) {
|
|
3464
|
+
const maxScrollTop = this.scrollHeight - this.viewport.height;
|
|
3465
|
+
return this.scrollTop < maxScrollTop ? 1 : 0;
|
|
3466
|
+
}
|
|
3467
|
+
return 0;
|
|
3468
|
+
}
|
|
3469
|
+
getAutoScrollSpeed(mouseX, mouseY) {
|
|
3470
|
+
const relativeX = mouseX - this.x;
|
|
3471
|
+
const relativeY = mouseY - this.y;
|
|
3472
|
+
const distToLeft = relativeX;
|
|
3473
|
+
const distToRight = this.width - relativeX;
|
|
3474
|
+
const distToTop = relativeY;
|
|
3475
|
+
const distToBottom = this.height - relativeY;
|
|
3476
|
+
const minDistance = Math.min(distToLeft, distToRight, distToTop, distToBottom);
|
|
3477
|
+
if (minDistance <= 1) {
|
|
3478
|
+
return this.autoScrollSpeedFast;
|
|
3479
|
+
} else if (minDistance <= 2) {
|
|
3480
|
+
return this.autoScrollSpeedMedium;
|
|
3481
|
+
} else {
|
|
3482
|
+
return this.autoScrollSpeedSlow;
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
recalculateBarProps() {
|
|
3486
|
+
this.verticalScrollBar.scrollSize = this.content.height;
|
|
3487
|
+
this.verticalScrollBar.viewportSize = this.viewport.height;
|
|
3488
|
+
this.horizontalScrollBar.scrollSize = this.content.width;
|
|
3489
|
+
this.horizontalScrollBar.viewportSize = this.viewport.width;
|
|
3490
|
+
}
|
|
3491
|
+
set rootOptions(options) {
|
|
3492
|
+
Object.assign(this, options);
|
|
3493
|
+
this.requestRender();
|
|
3494
|
+
}
|
|
3495
|
+
set wrapperOptions(options) {
|
|
3496
|
+
Object.assign(this.wrapper, options);
|
|
3497
|
+
this.requestRender();
|
|
3498
|
+
}
|
|
3499
|
+
set viewportOptions(options) {
|
|
3500
|
+
Object.assign(this.viewport, options);
|
|
3501
|
+
this.requestRender();
|
|
3502
|
+
}
|
|
3503
|
+
set contentOptions(options) {
|
|
3504
|
+
Object.assign(this.content, options);
|
|
3505
|
+
this.requestRender();
|
|
3506
|
+
}
|
|
3507
|
+
set scrollbarOptions(options) {
|
|
3508
|
+
Object.assign(this.verticalScrollBar, options);
|
|
3509
|
+
Object.assign(this.horizontalScrollBar, options);
|
|
3510
|
+
this.requestRender();
|
|
3511
|
+
}
|
|
3512
|
+
set verticalScrollbarOptions(options) {
|
|
3513
|
+
Object.assign(this.verticalScrollBar, options);
|
|
3514
|
+
this.requestRender();
|
|
3515
|
+
}
|
|
3516
|
+
set horizontalScrollbarOptions(options) {
|
|
3517
|
+
Object.assign(this.horizontalScrollBar, options);
|
|
3518
|
+
this.requestRender();
|
|
3519
|
+
}
|
|
3520
|
+
destroySelf() {
|
|
3521
|
+
if (this.selectionListener) {
|
|
3522
|
+
this._ctx.off("selection", this.selectionListener);
|
|
3523
|
+
this.selectionListener = undefined;
|
|
3524
|
+
}
|
|
3525
|
+
super.destroySelf();
|
|
3526
|
+
}
|
|
3527
|
+
}
|
|
3528
|
+
// src/renderables/composition/constructs.ts
|
|
3529
|
+
function Generic(props, ...children) {
|
|
3530
|
+
return h(VRenderable, props || {}, ...children);
|
|
3531
|
+
}
|
|
3532
|
+
function Box(props, ...children) {
|
|
3533
|
+
return h(BoxRenderable, props || {}, ...children);
|
|
3534
|
+
}
|
|
3535
|
+
function Text(props, ...children) {
|
|
3536
|
+
return h(TextRenderable, props || {}, ...children);
|
|
3537
|
+
}
|
|
3538
|
+
function ASCIIFont(props, ...children) {
|
|
3539
|
+
return h(ASCIIFontRenderable, props || {}, ...children);
|
|
3540
|
+
}
|
|
3541
|
+
function Input(props, ...children) {
|
|
3542
|
+
return h(InputRenderable, props || {}, ...children);
|
|
3543
|
+
}
|
|
3544
|
+
function Select(props, ...children) {
|
|
3545
|
+
return h(SelectRenderable, props || {}, ...children);
|
|
3546
|
+
}
|
|
3547
|
+
function TabSelect(props, ...children) {
|
|
3548
|
+
return h(TabSelectRenderable, props || {}, ...children);
|
|
3549
|
+
}
|
|
3550
|
+
function FrameBuffer(props, ...children) {
|
|
3551
|
+
return h(FrameBufferRenderable, props, ...children);
|
|
3552
|
+
}
|
|
3553
|
+
// src/renderables/composition/VRenderable.ts
|
|
3554
|
+
class VRenderable extends Renderable {
|
|
3555
|
+
options;
|
|
3556
|
+
constructor(ctx, options) {
|
|
3557
|
+
super(ctx, options);
|
|
3558
|
+
this.options = options;
|
|
3559
|
+
}
|
|
3560
|
+
renderSelf(buffer, deltaTime) {
|
|
3561
|
+
if (this.options.render) {
|
|
3562
|
+
this.options.render.call(this.options, buffer, deltaTime, this);
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
export {
|
|
3567
|
+
yellow,
|
|
3568
|
+
wrapWithDelegates,
|
|
3569
|
+
white,
|
|
3570
|
+
underline,
|
|
3571
|
+
t,
|
|
3572
|
+
stringToStyledText,
|
|
3573
|
+
strikethrough,
|
|
3574
|
+
setRenderLibPath,
|
|
3575
|
+
rgbToHex,
|
|
3576
|
+
reverse,
|
|
3577
|
+
resolveRenderLib,
|
|
3578
|
+
renderFontToFrameBuffer,
|
|
3579
|
+
red,
|
|
3580
|
+
parseWrap,
|
|
3581
|
+
parseUnit,
|
|
3582
|
+
parsePositionType,
|
|
3583
|
+
parseOverflow,
|
|
3584
|
+
parseMeasureMode,
|
|
3585
|
+
parseLogLevel,
|
|
3586
|
+
parseKeypress,
|
|
3587
|
+
parseJustify,
|
|
3588
|
+
parseGutter,
|
|
3589
|
+
parseFlexDirection,
|
|
3590
|
+
parseEdge,
|
|
3591
|
+
parseDisplay,
|
|
3592
|
+
parseDirection,
|
|
3593
|
+
parseDimension,
|
|
3594
|
+
parseColor,
|
|
3595
|
+
parseBoxSizing,
|
|
3596
|
+
parseAlign,
|
|
3597
|
+
nonAlphanumericKeys,
|
|
3598
|
+
measureText,
|
|
3599
|
+
maybeMakeRenderable,
|
|
3600
|
+
magenta,
|
|
3601
|
+
italic,
|
|
3602
|
+
isValidPercentage,
|
|
3603
|
+
isVNode,
|
|
3604
|
+
isSizeType,
|
|
3605
|
+
isRenderable,
|
|
3606
|
+
isPositionTypeType,
|
|
3607
|
+
isPositionType,
|
|
3608
|
+
isPaddingType,
|
|
3609
|
+
isOverflowType,
|
|
3610
|
+
isMarginType,
|
|
3611
|
+
isFlexBasisType,
|
|
3612
|
+
isDimensionType,
|
|
3613
|
+
instantiate,
|
|
3614
|
+
hsvToRgb,
|
|
3615
|
+
hexToRgb,
|
|
3616
|
+
hastToStyledText,
|
|
3617
|
+
h,
|
|
3618
|
+
green,
|
|
3619
|
+
getKeyHandler,
|
|
3620
|
+
getCharacterPositions,
|
|
3621
|
+
getBorderSides,
|
|
3622
|
+
getBorderFromSides,
|
|
3623
|
+
fonts,
|
|
3624
|
+
fg,
|
|
3625
|
+
engine,
|
|
3626
|
+
dim,
|
|
3627
|
+
delegate,
|
|
3628
|
+
cyan,
|
|
3629
|
+
createTrackedNode,
|
|
3630
|
+
createTimeline,
|
|
3631
|
+
createTextAttributes,
|
|
3632
|
+
createCliRenderer,
|
|
3633
|
+
coordinateToCharacterIndex,
|
|
3634
|
+
convertGlobalToLocalSelection,
|
|
3635
|
+
capture,
|
|
3636
|
+
brightYellow,
|
|
3637
|
+
brightWhite,
|
|
3638
|
+
brightRed,
|
|
3639
|
+
brightMagenta,
|
|
3640
|
+
brightGreen,
|
|
3641
|
+
brightCyan,
|
|
3642
|
+
brightBlue,
|
|
3643
|
+
brightBlack,
|
|
3644
|
+
borderCharsToArray,
|
|
3645
|
+
bold,
|
|
3646
|
+
blue,
|
|
3647
|
+
blink,
|
|
3648
|
+
black,
|
|
3649
|
+
bgYellow,
|
|
3650
|
+
bgWhite,
|
|
3651
|
+
bgRed,
|
|
3652
|
+
bgMagenta,
|
|
3653
|
+
bgGreen,
|
|
3654
|
+
bgCyan,
|
|
3655
|
+
bgBlue,
|
|
3656
|
+
bgBlack,
|
|
3657
|
+
bg,
|
|
3658
|
+
applySepia,
|
|
3659
|
+
applyScanlines,
|
|
3660
|
+
applyNoise,
|
|
3661
|
+
applyInvert,
|
|
3662
|
+
applyGrayscale,
|
|
3663
|
+
applyChromaticAberration,
|
|
3664
|
+
applyAsciiArt,
|
|
3665
|
+
VignetteEffect,
|
|
3666
|
+
VRenderable,
|
|
3667
|
+
TrackedNode,
|
|
3668
|
+
Timeline,
|
|
3669
|
+
TextRenderable,
|
|
3670
|
+
TextBuffer,
|
|
3671
|
+
TextAttributes,
|
|
3672
|
+
Text,
|
|
3673
|
+
TerminalConsole,
|
|
3674
|
+
TabSelectRenderableEvents,
|
|
3675
|
+
TabSelectRenderable,
|
|
3676
|
+
TabSelect,
|
|
3677
|
+
SyntaxStyle,
|
|
3678
|
+
StyledText,
|
|
3679
|
+
Selection,
|
|
3680
|
+
SelectRenderableEvents,
|
|
3681
|
+
SelectRenderable,
|
|
3682
|
+
Select,
|
|
3683
|
+
ScrollBoxRenderable,
|
|
3684
|
+
ScrollBarRenderable,
|
|
3685
|
+
RootRenderable,
|
|
3686
|
+
RenderableEvents,
|
|
3687
|
+
Renderable,
|
|
3688
|
+
RGBA,
|
|
3689
|
+
OptimizedBuffer,
|
|
3690
|
+
MouseParser,
|
|
3691
|
+
MouseEvent,
|
|
3692
|
+
MouseButton,
|
|
3693
|
+
LogLevel,
|
|
3694
|
+
LayoutEvents,
|
|
3695
|
+
KeyHandler,
|
|
3696
|
+
InputRenderableEvents,
|
|
3697
|
+
InputRenderable,
|
|
3698
|
+
Input,
|
|
3699
|
+
Generic,
|
|
3700
|
+
FrameBufferRenderable,
|
|
3701
|
+
FrameBuffer,
|
|
3702
|
+
DistortionEffect,
|
|
3703
|
+
DebugOverlayCorner,
|
|
3704
|
+
ConsolePosition,
|
|
3705
|
+
CliRenderer,
|
|
3706
|
+
CliRenderEvents,
|
|
3707
|
+
BrightnessEffect,
|
|
3708
|
+
BoxRenderable,
|
|
3709
|
+
Box,
|
|
3710
|
+
BorderChars,
|
|
3711
|
+
BorderCharArrays,
|
|
3712
|
+
BlurEffect,
|
|
3713
|
+
BloomEffect,
|
|
3714
|
+
ArrowRenderable,
|
|
3715
|
+
ASCIIFontSelectionHelper,
|
|
3716
|
+
ASCIIFontRenderable,
|
|
3717
|
+
ASCIIFont
|
|
3718
|
+
};
|
|
3719
|
+
|
|
3720
|
+
//# debugId=D81404410A6C6E2564756E2164756E21
|
|
3721
|
+
//# sourceMappingURL=index.js.map
|