@buntui/extensions 0.0.0
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/dist/framerate.d.ts +3 -0
- package/dist/framerate.js +2 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +8 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +2 -0
- package/dist/matrix.d.ts +3 -0
- package/dist/matrix.js +4 -0
- package/dist/registry.d.ts +6 -0
- package/dist/registry.js +7 -0
- package/dist/snake.d.ts +3 -0
- package/dist/snake.js +4 -0
- package/dist/utils/color.d.ts +10 -0
- package/dist/utils/color.js +35 -0
- package/dist/videoplayer.d.ts +3 -0
- package/dist/videoplayer.js +4 -0
- package/dist/widgets/framerate/FrameRateWatcher.d.ts +20 -0
- package/dist/widgets/framerate/FrameRateWatcher.js +87 -0
- package/dist/widgets/logger/LoggerWidget.d.ts +33 -0
- package/dist/widgets/logger/LoggerWidget.js +170 -0
- package/dist/widgets/matrix/MatrixWidget.d.ts +13 -0
- package/dist/widgets/matrix/MatrixWidget.js +139 -0
- package/dist/widgets/matrix/charset.d.ts +1 -0
- package/dist/widgets/matrix/charset.js +11 -0
- package/dist/widgets/matrix/defaults.d.ts +3 -0
- package/dist/widgets/matrix/defaults.js +17 -0
- package/dist/widgets/matrix/matrix-column.d.ts +24 -0
- package/dist/widgets/matrix/matrix-column.js +58 -0
- package/dist/widgets/matrix/types.d.ts +38 -0
- package/dist/widgets/matrix/types.js +0 -0
- package/dist/widgets/snake/SnakeWidget.d.ts +14 -0
- package/dist/widgets/snake/SnakeWidget.js +384 -0
- package/dist/widgets/snake/defaults.d.ts +3 -0
- package/dist/widgets/snake/defaults.js +19 -0
- package/dist/widgets/snake/types.d.ts +25 -0
- package/dist/widgets/snake/types.js +0 -0
- package/dist/widgets/videoplayer/VideoPlayerWidget.d.ts +16 -0
- package/dist/widgets/videoplayer/VideoPlayerWidget.js +465 -0
- package/dist/widgets/videoplayer/braille.d.ts +18 -0
- package/dist/widgets/videoplayer/braille.js +69 -0
- package/dist/widgets/videoplayer/defaults.d.ts +3 -0
- package/dist/widgets/videoplayer/defaults.js +17 -0
- package/dist/widgets/videoplayer/types.d.ts +21 -0
- package/dist/widgets/videoplayer/types.js +0 -0
- package/package.json +50 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Barrel re-exports — for deep-import tree-shaking, import from sub-paths:
|
|
2
|
+
// import {Matrix} from '@buntui/extensions/matrix'
|
|
3
|
+
export * from './matrix';
|
|
4
|
+
export * from './framerate';
|
|
5
|
+
export * from './snake';
|
|
6
|
+
export * from './videoplayer';
|
|
7
|
+
export * from './logger';
|
|
8
|
+
export { EXTENSION_REGISTRY } from './registry';
|
package/dist/logger.d.ts
ADDED
package/dist/logger.js
ADDED
package/dist/matrix.d.ts
ADDED
package/dist/matrix.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { MatrixWidget, createMatrixWidget } from './widgets/matrix/MatrixWidget';
|
|
2
|
+
// Default export = creator function, for SFC default import usage:
|
|
3
|
+
// import Matrix from '@buntui/extensions/matrix'
|
|
4
|
+
export { createMatrixWidget as default } from './widgets/matrix/MatrixWidget';
|
package/dist/registry.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const EXTENSION_REGISTRY = {
|
|
2
|
+
FrameRateWatcher: { creator: 'createFrameRateWatcher', module: '@buntui/extensions' },
|
|
3
|
+
Matrix: { creator: 'createMatrixWidget', module: '@buntui/extensions' },
|
|
4
|
+
Snake: { creator: 'createSnakeWidget', module: '@buntui/extensions' },
|
|
5
|
+
VideoPlayer: { creator: 'createVideoPlayerWidget', module: '@buntui/extensions' },
|
|
6
|
+
Logger: { creator: 'createLoggerWidget', module: '@buntui/extensions' },
|
|
7
|
+
};
|
package/dist/snake.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type { SnakeWidgetOptions, SnakeDirection, SnakeGameState, SnakePoint, SnakeColorScheme, } from './widgets/snake/types';
|
|
2
|
+
export { SnakeWidget, createSnakeWidget } from './widgets/snake/SnakeWidget';
|
|
3
|
+
export { createSnakeWidget as default } from './widgets/snake/SnakeWidget';
|
package/dist/snake.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { SnakeWidget, createSnakeWidget } from './widgets/snake/SnakeWidget';
|
|
2
|
+
// Default export = creator function, for SFC default import usage:
|
|
3
|
+
// import Snake from '@buntui/extensions/snake'
|
|
4
|
+
export { createSnakeWidget as default } from './widgets/snake/SnakeWidget';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interpolate between two RGBA colors by factor t (0.0 = from, 1.0 = to).
|
|
3
|
+
* Both inputs must be 0xAARRGGBB.
|
|
4
|
+
*/
|
|
5
|
+
export declare function lerpRgba(from: number, to: number, t: number): number;
|
|
6
|
+
/**
|
|
7
|
+
* Build a lookup table of `steps` RGBA colors fading from lead to trail.
|
|
8
|
+
* Index 0 is the brightest (lead), index steps-1 is the dimmest (near-bg).
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildTrailGradient(leadRgba: number, trailRgba: number, steps: number): number[];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { withAlpha } from '@buntui/core';
|
|
2
|
+
/**
|
|
3
|
+
* Interpolate between two RGBA colors by factor t (0.0 = from, 1.0 = to).
|
|
4
|
+
* Both inputs must be 0xAARRGGBB.
|
|
5
|
+
*/
|
|
6
|
+
export function lerpRgba(from, to, t) {
|
|
7
|
+
const clampT = Math.max(0, Math.min(1, t));
|
|
8
|
+
const fA = (from >>> 24) & 0xFF;
|
|
9
|
+
const fR = (from >>> 16) & 0xFF;
|
|
10
|
+
const fG = (from >>> 8) & 0xFF;
|
|
11
|
+
const fB = from & 0xFF;
|
|
12
|
+
const tA = (to >>> 24) & 0xFF;
|
|
13
|
+
const tR = (to >>> 16) & 0xFF;
|
|
14
|
+
const tG = (to >>> 8) & 0xFF;
|
|
15
|
+
const tB = to & 0xFF;
|
|
16
|
+
const r = Math.round(fR + ((tR - fR) * clampT));
|
|
17
|
+
const g = Math.round(fG + ((tG - fG) * clampT));
|
|
18
|
+
const b = Math.round(fB + ((tB - fB) * clampT));
|
|
19
|
+
const a = Math.round(fA + ((tA - fA) * clampT));
|
|
20
|
+
return ((a << 24) | (r << 16) | (g << 8) | b) >>> 0;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build a lookup table of `steps` RGBA colors fading from lead to trail.
|
|
24
|
+
* Index 0 is the brightest (lead), index steps-1 is the dimmest (near-bg).
|
|
25
|
+
*/
|
|
26
|
+
export function buildTrailGradient(leadRgba, trailRgba, steps) {
|
|
27
|
+
const table = [];
|
|
28
|
+
for (let i = 0; i < steps; i++) {
|
|
29
|
+
const t = i / Math.max(1, steps - 1);
|
|
30
|
+
const color = lerpRgba(leadRgba, trailRgba, t);
|
|
31
|
+
const alpha = Math.round(255 * (1 - (t * 0.7)));
|
|
32
|
+
table.push(withAlpha(color, alpha));
|
|
33
|
+
}
|
|
34
|
+
return table;
|
|
35
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type { VideoPlayerWidgetOptions, VideoPlayerState, VideoPlayerColorScheme, } from './widgets/videoplayer/types';
|
|
2
|
+
export { VideoPlayerWidget, createVideoPlayerWidget } from './widgets/videoplayer/VideoPlayerWidget';
|
|
3
|
+
export { createVideoPlayerWidget as default } from './widgets/videoplayer/VideoPlayerWidget';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { VideoPlayerWidget, createVideoPlayerWidget } from './widgets/videoplayer/VideoPlayerWidget';
|
|
2
|
+
// Default export = creator function, for SFC default import usage:
|
|
3
|
+
// import VideoPlayer from '@buntui/extensions/videoplayer'
|
|
4
|
+
export { createVideoPlayerWidget as default } from './widgets/videoplayer/VideoPlayerWidget';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type DrawListBuffer, type TuiWidgetRect, type TuiSizeValue, TuiWidgetEntity, type TuiColor } from '@buntui/core';
|
|
2
|
+
export type FrameRateWatcherOptions = {
|
|
3
|
+
x?: TuiSizeValue;
|
|
4
|
+
y?: TuiSizeValue;
|
|
5
|
+
colorFg?: TuiColor;
|
|
6
|
+
colorBg?: TuiColor;
|
|
7
|
+
};
|
|
8
|
+
export declare class FrameRateWatcher extends TuiWidgetEntity {
|
|
9
|
+
#private;
|
|
10
|
+
constructor(options?: FrameRateWatcherOptions);
|
|
11
|
+
get zIndex(): number;
|
|
12
|
+
get rect(): TuiWidgetRect;
|
|
13
|
+
updateRect(rect: Partial<TuiWidgetRect>): void;
|
|
14
|
+
containsPoint(x: number, y: number): boolean;
|
|
15
|
+
emitDrawCommands(buf: DrawListBuffer): void;
|
|
16
|
+
mounted(): void;
|
|
17
|
+
unmounted(): void;
|
|
18
|
+
}
|
|
19
|
+
export declare function createFrameRateWatcher(options?: Partial<FrameRateWatcherOptions>): FrameRateWatcher;
|
|
20
|
+
export default FrameRateWatcher;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { TUI_CONTEXT_INSTANCE, TuiWidgetEntity, getTheme, parseColor, } from '@buntui/core';
|
|
2
|
+
const FPS_BG = 0x1E_1E_2E_DD;
|
|
3
|
+
export class FrameRateWatcher extends TuiWidgetEntity {
|
|
4
|
+
#x;
|
|
5
|
+
#y;
|
|
6
|
+
#latestTick = 0n;
|
|
7
|
+
#fps = 0;
|
|
8
|
+
#timer = null;
|
|
9
|
+
#colorFg;
|
|
10
|
+
#colorBg;
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
super();
|
|
13
|
+
const rect = this.initRect(options.x, options.y);
|
|
14
|
+
this.#x = rect.x;
|
|
15
|
+
this.#y = rect.y;
|
|
16
|
+
const theme = getTheme();
|
|
17
|
+
this.#colorFg = parseColor(options.colorFg ?? theme.colors.text);
|
|
18
|
+
this.#colorBg = parseColor(options.colorBg ?? FPS_BG);
|
|
19
|
+
this.setDraggable(true);
|
|
20
|
+
}
|
|
21
|
+
get zIndex() {
|
|
22
|
+
return 999;
|
|
23
|
+
}
|
|
24
|
+
get rect() {
|
|
25
|
+
const text = `${this.#fps}fps`;
|
|
26
|
+
return {
|
|
27
|
+
x: this.#x,
|
|
28
|
+
y: this.#y,
|
|
29
|
+
width: text.length,
|
|
30
|
+
height: 1,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
updateRect(rect) {
|
|
34
|
+
if (rect.x !== undefined) {
|
|
35
|
+
this.#x = rect.x;
|
|
36
|
+
}
|
|
37
|
+
if (rect.y !== undefined) {
|
|
38
|
+
this.#y = rect.y;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
containsPoint(x, y) {
|
|
42
|
+
const text = `${this.#fps}fps`;
|
|
43
|
+
return x >= this.#x
|
|
44
|
+
&& x < this.#x + text.length
|
|
45
|
+
&& y >= this.#y
|
|
46
|
+
&& y < this.#y + 1;
|
|
47
|
+
}
|
|
48
|
+
emitDrawCommands(buf) {
|
|
49
|
+
const text = `${this.#fps}fps`;
|
|
50
|
+
const w = text.length;
|
|
51
|
+
buf.pushClip(this.#x, this.#y, w, 1);
|
|
52
|
+
buf.drawRect({
|
|
53
|
+
x: this.#x,
|
|
54
|
+
y: this.#y,
|
|
55
|
+
width: w,
|
|
56
|
+
height: 1,
|
|
57
|
+
bgRgba: this.#colorBg,
|
|
58
|
+
});
|
|
59
|
+
buf.drawText({
|
|
60
|
+
x: this.#x,
|
|
61
|
+
y: this.#y,
|
|
62
|
+
text,
|
|
63
|
+
fgRgba: this.#colorFg,
|
|
64
|
+
bgRgba: this.#colorBg,
|
|
65
|
+
});
|
|
66
|
+
buf.popClip();
|
|
67
|
+
}
|
|
68
|
+
mounted() {
|
|
69
|
+
super.mounted();
|
|
70
|
+
this.#timer = setInterval(() => {
|
|
71
|
+
const currentTick = TUI_CONTEXT_INSTANCE.tick;
|
|
72
|
+
this.#fps = Number(currentTick - this.#latestTick);
|
|
73
|
+
this.#latestTick = currentTick;
|
|
74
|
+
}, 1000);
|
|
75
|
+
}
|
|
76
|
+
unmounted() {
|
|
77
|
+
super.unmounted();
|
|
78
|
+
if (this.#timer) {
|
|
79
|
+
clearInterval(this.#timer);
|
|
80
|
+
this.#timer = null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export function createFrameRateWatcher(options) {
|
|
85
|
+
return new FrameRateWatcher(options);
|
|
86
|
+
}
|
|
87
|
+
export default FrameRateWatcher;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type TuiColor, TuiWidgetEntity } from '@buntui/core';
|
|
2
|
+
export type LoggerWidgetOptions = {
|
|
3
|
+
x?: number;
|
|
4
|
+
y?: number;
|
|
5
|
+
panelWidth?: number;
|
|
6
|
+
panelHeight?: number;
|
|
7
|
+
maxLines?: number;
|
|
8
|
+
timestamp?: boolean;
|
|
9
|
+
colorFg?: TuiColor;
|
|
10
|
+
colorBg?: TuiColor;
|
|
11
|
+
label?: string;
|
|
12
|
+
hijack?: boolean;
|
|
13
|
+
};
|
|
14
|
+
export declare class LoggerWidget extends TuiWidgetEntity {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(options?: LoggerWidgetOptions);
|
|
17
|
+
log(message: string): void;
|
|
18
|
+
clear(): void;
|
|
19
|
+
toggle(): void;
|
|
20
|
+
get messages(): readonly string[];
|
|
21
|
+
get isOpen(): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Replace console.log with a wrapper that sends output to this logger.
|
|
24
|
+
* Call restoreConsole() to undo.
|
|
25
|
+
*/
|
|
26
|
+
hijackConsole(): void;
|
|
27
|
+
restoreConsole(): void;
|
|
28
|
+
get zIndex(): number;
|
|
29
|
+
containsPoint(x: number, y: number): boolean;
|
|
30
|
+
emitDrawCommands(buf: Parameters<TuiWidgetEntity['emitDrawCommands']>[0]): void;
|
|
31
|
+
}
|
|
32
|
+
export declare function createLoggerWidget(options?: Partial<LoggerWidgetOptions>): LoggerWidget;
|
|
33
|
+
export default LoggerWidget;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { BoxWidget, TextWidget, ScrollBoxWidget, TuiWidgetEntity, parseColor, getTheme, } from '@buntui/core';
|
|
2
|
+
export class LoggerWidget extends TuiWidgetEntity {
|
|
3
|
+
#toggleBtn;
|
|
4
|
+
#btnLabel;
|
|
5
|
+
#panel;
|
|
6
|
+
#messages = [];
|
|
7
|
+
#maxLines;
|
|
8
|
+
#showTimestamp;
|
|
9
|
+
#colorFg;
|
|
10
|
+
#panelWidth;
|
|
11
|
+
#panelHeight;
|
|
12
|
+
#panelVisible = false;
|
|
13
|
+
#scrollPending = false;
|
|
14
|
+
#restoreConsole = null;
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
super();
|
|
17
|
+
const theme = getTheme();
|
|
18
|
+
this.#maxLines = options.maxLines ?? 200;
|
|
19
|
+
this.#showTimestamp = options.timestamp ?? true;
|
|
20
|
+
this.#colorFg = parseColor(options.colorFg ?? theme.colors.text);
|
|
21
|
+
this.#panelWidth = options.panelWidth ?? 40;
|
|
22
|
+
this.#panelHeight = options.panelHeight ?? 15;
|
|
23
|
+
const colorBg = parseColor(options.colorBg ?? 0x1E_1E_2E_CC);
|
|
24
|
+
const label = options.label ?? '◉';
|
|
25
|
+
// Floating toggle button
|
|
26
|
+
this.#toggleBtn = new BoxWidget({
|
|
27
|
+
x: options.x ?? 0,
|
|
28
|
+
y: options.y ?? 0,
|
|
29
|
+
width: label.length + 2,
|
|
30
|
+
height: 1,
|
|
31
|
+
colorBg,
|
|
32
|
+
border: false,
|
|
33
|
+
draggable: true,
|
|
34
|
+
styleZIndex: 999,
|
|
35
|
+
});
|
|
36
|
+
this.#btnLabel = new TextWidget({
|
|
37
|
+
x: options.x ?? 0,
|
|
38
|
+
y: options.y ?? 0,
|
|
39
|
+
width: label.length + 2,
|
|
40
|
+
height: 1,
|
|
41
|
+
value: ` ${label}`,
|
|
42
|
+
colorFg: this.#colorFg,
|
|
43
|
+
colorBg: 0x00_00_00_00,
|
|
44
|
+
});
|
|
45
|
+
this.#toggleBtn.addChild(this.#btnLabel);
|
|
46
|
+
// Log panel
|
|
47
|
+
this.#panel = new ScrollBoxWidget({
|
|
48
|
+
x: 0,
|
|
49
|
+
y: 0,
|
|
50
|
+
width: this.#panelWidth,
|
|
51
|
+
height: this.#panelHeight,
|
|
52
|
+
colorBg: (colorBg & 0xFF_FF_FF_00) | 0xDD,
|
|
53
|
+
borderTop: true,
|
|
54
|
+
borderRight: true,
|
|
55
|
+
borderBottom: true,
|
|
56
|
+
borderLeft: true,
|
|
57
|
+
borderStyle: 'solid',
|
|
58
|
+
borderColor: this.#colorFg,
|
|
59
|
+
});
|
|
60
|
+
this.#panel.setVisible(false);
|
|
61
|
+
this.addChild(this.#toggleBtn);
|
|
62
|
+
this.addChild(this.#panel);
|
|
63
|
+
this.#toggleBtn.on('click', () => {
|
|
64
|
+
this.toggle();
|
|
65
|
+
});
|
|
66
|
+
if (options.hijack) {
|
|
67
|
+
this.hijackConsole();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// -- Public API --
|
|
71
|
+
log(message) {
|
|
72
|
+
const line = this.#showTimestamp ? `[${this.#timestamp()}] ${message}` : message;
|
|
73
|
+
this.#messages.push(line);
|
|
74
|
+
if (this.#messages.length > this.#maxLines) {
|
|
75
|
+
this.#messages.shift();
|
|
76
|
+
}
|
|
77
|
+
const textWidget = new TextWidget({
|
|
78
|
+
x: 0,
|
|
79
|
+
y: 0,
|
|
80
|
+
width: this.#panelWidth - 2,
|
|
81
|
+
height: 1,
|
|
82
|
+
value: line,
|
|
83
|
+
colorFg: this.#colorFg,
|
|
84
|
+
colorBg: 0x00_00_00_00,
|
|
85
|
+
});
|
|
86
|
+
this.#panel.addChild(textWidget);
|
|
87
|
+
this.#scrollPending = true;
|
|
88
|
+
}
|
|
89
|
+
clear() {
|
|
90
|
+
this.#messages.length = 0;
|
|
91
|
+
const { children } = this.#panel;
|
|
92
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
93
|
+
// eslint-disable-next-line unicorn/prefer-dom-node-remove
|
|
94
|
+
this.#panel.removeChild(children[i]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
toggle() {
|
|
98
|
+
this.#panelVisible = !this.#panelVisible;
|
|
99
|
+
this.#panel.setVisible(this.#panelVisible);
|
|
100
|
+
if (this.#panelVisible) {
|
|
101
|
+
this.#updatePanelPosition();
|
|
102
|
+
this.#scrollPending = true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
get messages() {
|
|
106
|
+
return this.#messages;
|
|
107
|
+
}
|
|
108
|
+
get isOpen() {
|
|
109
|
+
return this.#panelVisible;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Replace console.log with a wrapper that sends output to this logger.
|
|
113
|
+
* Call restoreConsole() to undo.
|
|
114
|
+
*/
|
|
115
|
+
hijackConsole() {
|
|
116
|
+
const original = console.log;
|
|
117
|
+
this.#restoreConsole = () => {
|
|
118
|
+
console.log = original;
|
|
119
|
+
};
|
|
120
|
+
console.log = (...args) => {
|
|
121
|
+
this.log(args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' '));
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
restoreConsole() {
|
|
125
|
+
const restore = this.#restoreConsole;
|
|
126
|
+
this.#restoreConsole = null;
|
|
127
|
+
restore?.();
|
|
128
|
+
}
|
|
129
|
+
// -- Overrides --
|
|
130
|
+
// -- Overrides --
|
|
131
|
+
get zIndex() {
|
|
132
|
+
return 999;
|
|
133
|
+
}
|
|
134
|
+
containsPoint(x, y) {
|
|
135
|
+
if (this.#toggleBtn.containsPoint(x, y)) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
return this.#panelVisible && this.#panel.containsPoint(x, y);
|
|
139
|
+
}
|
|
140
|
+
emitDrawCommands(buf) {
|
|
141
|
+
this.#toggleBtn.emitDrawCommands(buf);
|
|
142
|
+
if (this.#panelVisible) {
|
|
143
|
+
this.#updatePanelPosition();
|
|
144
|
+
if (this.#scrollPending) {
|
|
145
|
+
this.#panel.scrollToBottom();
|
|
146
|
+
this.#scrollPending = false;
|
|
147
|
+
}
|
|
148
|
+
this.#panel.emitDrawCommands(buf);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// -- Private --
|
|
152
|
+
#updatePanelPosition() {
|
|
153
|
+
const { x: bx, y: by, height: bh } = this.#toggleBtn.rect;
|
|
154
|
+
this.#panel.updateRect({
|
|
155
|
+
x: bx,
|
|
156
|
+
y: by + bh,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
#timestamp() {
|
|
160
|
+
const d = new Date();
|
|
161
|
+
const h = String(d.getHours()).padStart(2, '0');
|
|
162
|
+
const m = String(d.getMinutes()).padStart(2, '0');
|
|
163
|
+
const s = String(d.getSeconds()).padStart(2, '0');
|
|
164
|
+
return `${h}:${m}:${s}`;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
export function createLoggerWidget(options) {
|
|
168
|
+
return new LoggerWidget(options);
|
|
169
|
+
}
|
|
170
|
+
export default LoggerWidget;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type DrawListBuffer, type TuiWidgetRect, TuiWidgetEntity } from '@buntui/core';
|
|
2
|
+
import type { MatrixWidgetOptions } from './types';
|
|
3
|
+
export declare class MatrixWidget extends TuiWidgetEntity {
|
|
4
|
+
#private;
|
|
5
|
+
constructor(options?: MatrixWidgetOptions);
|
|
6
|
+
updateRect(rect: Partial<TuiWidgetRect>): void;
|
|
7
|
+
containsPoint(x: number, y: number): boolean;
|
|
8
|
+
update(dt: number): void;
|
|
9
|
+
emitDrawCommands(buffer: DrawListBuffer): void;
|
|
10
|
+
get rect(): TuiWidgetRect;
|
|
11
|
+
}
|
|
12
|
+
export declare function createMatrixWidget(options?: MatrixWidgetOptions): MatrixWidget;
|
|
13
|
+
export default MatrixWidget;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { TuiWidgetEntity, } from '@buntui/core';
|
|
2
|
+
import { buildTrailGradient } from '../../utils/color';
|
|
3
|
+
import { MATRIX_CHARSET } from './charset';
|
|
4
|
+
import { DEFAULT_MATRIX_COLOR_SCHEME, DEFAULT_MATRIX_OPTIONS } from './defaults';
|
|
5
|
+
import { createColumn, tickColumn, } from './matrix-column';
|
|
6
|
+
export class MatrixWidget extends TuiWidgetEntity {
|
|
7
|
+
#x;
|
|
8
|
+
#y;
|
|
9
|
+
#width;
|
|
10
|
+
#height;
|
|
11
|
+
#colorScheme;
|
|
12
|
+
#speedRange;
|
|
13
|
+
#minTrailLength;
|
|
14
|
+
#maxTrailLength;
|
|
15
|
+
#density;
|
|
16
|
+
#charset;
|
|
17
|
+
#tickIntervalRange;
|
|
18
|
+
#columns = [];
|
|
19
|
+
#gradientLut = [];
|
|
20
|
+
#initialized = false;
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
super();
|
|
23
|
+
const resolved = { ...DEFAULT_MATRIX_OPTIONS, ...options };
|
|
24
|
+
const rect = this.initRect(resolved.x, resolved.y, resolved.width, resolved.height);
|
|
25
|
+
this.#x = rect.x;
|
|
26
|
+
this.#y = rect.y;
|
|
27
|
+
this.#width = rect.width;
|
|
28
|
+
this.#height = rect.height;
|
|
29
|
+
const schemeOverride = resolved.colorScheme ?? {};
|
|
30
|
+
this.#colorScheme = {
|
|
31
|
+
leadRgba: schemeOverride.leadRgba ?? DEFAULT_MATRIX_COLOR_SCHEME.leadRgba,
|
|
32
|
+
trailRgba: schemeOverride.trailRgba ?? DEFAULT_MATRIX_COLOR_SCHEME.trailRgba,
|
|
33
|
+
bgRgba: schemeOverride.bgRgba ?? DEFAULT_MATRIX_COLOR_SCHEME.bgRgba,
|
|
34
|
+
};
|
|
35
|
+
this.#speedRange = resolved.speedRange ?? { min: 1, max: 3 };
|
|
36
|
+
this.#minTrailLength = resolved.minTrailLength ?? 5;
|
|
37
|
+
this.#maxTrailLength = resolved.maxTrailLength ?? 20;
|
|
38
|
+
this.#density = resolved.density ?? 0.8;
|
|
39
|
+
this.#charset = resolved.charset ?? MATRIX_CHARSET;
|
|
40
|
+
this.#tickIntervalRange = resolved.tickIntervalRange ?? { min: 60, max: 150 };
|
|
41
|
+
this.#rebuildGradient();
|
|
42
|
+
}
|
|
43
|
+
updateRect(rect) {
|
|
44
|
+
if (rect.width !== undefined) {
|
|
45
|
+
this.#width = rect.width;
|
|
46
|
+
}
|
|
47
|
+
if (rect.height !== undefined) {
|
|
48
|
+
this.#height = rect.height;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
containsPoint(x, y) {
|
|
52
|
+
return x >= this.#x
|
|
53
|
+
&& x < this.#x + this.#width
|
|
54
|
+
&& y >= this.#y
|
|
55
|
+
&& y < this.#y + this.#height;
|
|
56
|
+
}
|
|
57
|
+
update(dt) {
|
|
58
|
+
this.#ensureColumns(this.#width, this.#height);
|
|
59
|
+
const config = this.#columnConfig(this.#height);
|
|
60
|
+
for (const col of this.#columns) {
|
|
61
|
+
tickColumn(col, dt, this.#density, config);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
emitDrawCommands(buffer) {
|
|
65
|
+
const w = this.#width;
|
|
66
|
+
const h = this.#height;
|
|
67
|
+
if (w <= 0 || h <= 0) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
this.#ensureColumns(w, h);
|
|
71
|
+
const absX = this.#x;
|
|
72
|
+
const absY = this.#y;
|
|
73
|
+
const maxLength = this.#maxTrailLength;
|
|
74
|
+
buffer.pushClip(absX, absY, w, h);
|
|
75
|
+
for (let col = 0; col < this.#columns.length; col++) {
|
|
76
|
+
const column = this.#columns[col];
|
|
77
|
+
if (!column.active) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const cx = absX + col;
|
|
81
|
+
const { headY } = column;
|
|
82
|
+
const { trailLength } = column;
|
|
83
|
+
const { chars } = column;
|
|
84
|
+
for (let t = 0; t < trailLength; t++) {
|
|
85
|
+
const cy = absY + headY - t;
|
|
86
|
+
if (cy < absY || cy >= absY + h) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const isLead = t === 0;
|
|
90
|
+
const fgRgba = isLead
|
|
91
|
+
? this.#colorScheme.leadRgba
|
|
92
|
+
: (this.#gradientLut[Math.min(t, maxLength - 1)] ?? this.#colorScheme.trailRgba);
|
|
93
|
+
buffer.drawChar({
|
|
94
|
+
x: cx,
|
|
95
|
+
y: cy,
|
|
96
|
+
char: chars[t] ?? this.#charset[0],
|
|
97
|
+
fgRgba,
|
|
98
|
+
bgRgba: this.#colorScheme.bgRgba,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
buffer.popClip();
|
|
103
|
+
}
|
|
104
|
+
get rect() {
|
|
105
|
+
return {
|
|
106
|
+
x: this.#x,
|
|
107
|
+
y: this.#y,
|
|
108
|
+
width: this.#width,
|
|
109
|
+
height: this.#height,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
#rebuildGradient() {
|
|
113
|
+
this.#gradientLut = buildTrailGradient(this.#colorScheme.leadRgba, this.#colorScheme.trailRgba, this.#maxTrailLength);
|
|
114
|
+
}
|
|
115
|
+
#columnConfig(maxY) {
|
|
116
|
+
return {
|
|
117
|
+
maxY,
|
|
118
|
+
speedRange: this.#speedRange,
|
|
119
|
+
minTrail: this.#minTrailLength,
|
|
120
|
+
maxTrail: this.#maxTrailLength,
|
|
121
|
+
charset: this.#charset,
|
|
122
|
+
tickIntervalRange: this.#tickIntervalRange,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
#ensureColumns(width, height) {
|
|
126
|
+
if (this.#columns.length === width && this.#initialized) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this.#columns = [];
|
|
130
|
+
for (let x = 0; x < width; x++) {
|
|
131
|
+
this.#columns.push(createColumn(this.#columnConfig(height)));
|
|
132
|
+
}
|
|
133
|
+
this.#initialized = true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export function createMatrixWidget(options) {
|
|
137
|
+
return new MatrixWidget(options);
|
|
138
|
+
}
|
|
139
|
+
export default MatrixWidget;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const MATRIX_CHARSET: number[];
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { rgbToRgba } from '@buntui/core';
|
|
2
|
+
export const DEFAULT_MATRIX_COLOR_SCHEME = {
|
|
3
|
+
leadRgba: rgbToRgba(0x57, 0xFF, 0x57), // Bright green
|
|
4
|
+
trailRgba: rgbToRgba(0x00, 0x8F, 0x11), // Medium green
|
|
5
|
+
bgRgba: rgbToRgba(0x00, 0x00, 0x00), // Black background
|
|
6
|
+
};
|
|
7
|
+
export const DEFAULT_MATRIX_OPTIONS = {
|
|
8
|
+
x: 0,
|
|
9
|
+
y: 0,
|
|
10
|
+
width: 0,
|
|
11
|
+
height: 0,
|
|
12
|
+
colorScheme: DEFAULT_MATRIX_COLOR_SCHEME,
|
|
13
|
+
speedRange: { min: 1, max: 3 },
|
|
14
|
+
minTrailLength: 5,
|
|
15
|
+
maxTrailLength: 20,
|
|
16
|
+
density: 0.8,
|
|
17
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { MatrixSpeedRange } from './types';
|
|
2
|
+
export type MatrixColumnState = {
|
|
3
|
+
headY: number;
|
|
4
|
+
trailLength: number;
|
|
5
|
+
speed: number;
|
|
6
|
+
active: boolean;
|
|
7
|
+
cooldown: number;
|
|
8
|
+
chars: number[];
|
|
9
|
+
accumulator: number;
|
|
10
|
+
tickInterval: number;
|
|
11
|
+
};
|
|
12
|
+
export type MatrixColumnConfig = {
|
|
13
|
+
maxY: number;
|
|
14
|
+
speedRange: MatrixSpeedRange;
|
|
15
|
+
minTrail: number;
|
|
16
|
+
maxTrail: number;
|
|
17
|
+
charset: number[];
|
|
18
|
+
tickIntervalRange: {
|
|
19
|
+
min: number;
|
|
20
|
+
max: number;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export declare function createColumn(config: MatrixColumnConfig): MatrixColumnState;
|
|
24
|
+
export declare function tickColumn(col: MatrixColumnState, dt: number, density: number, config: MatrixColumnConfig): void;
|