@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.
Files changed (45) hide show
  1. package/dist/framerate.d.ts +3 -0
  2. package/dist/framerate.js +2 -0
  3. package/dist/index.d.ts +6 -0
  4. package/dist/index.js +8 -0
  5. package/dist/logger.d.ts +3 -0
  6. package/dist/logger.js +2 -0
  7. package/dist/matrix.d.ts +3 -0
  8. package/dist/matrix.js +4 -0
  9. package/dist/registry.d.ts +6 -0
  10. package/dist/registry.js +7 -0
  11. package/dist/snake.d.ts +3 -0
  12. package/dist/snake.js +4 -0
  13. package/dist/utils/color.d.ts +10 -0
  14. package/dist/utils/color.js +35 -0
  15. package/dist/videoplayer.d.ts +3 -0
  16. package/dist/videoplayer.js +4 -0
  17. package/dist/widgets/framerate/FrameRateWatcher.d.ts +20 -0
  18. package/dist/widgets/framerate/FrameRateWatcher.js +87 -0
  19. package/dist/widgets/logger/LoggerWidget.d.ts +33 -0
  20. package/dist/widgets/logger/LoggerWidget.js +170 -0
  21. package/dist/widgets/matrix/MatrixWidget.d.ts +13 -0
  22. package/dist/widgets/matrix/MatrixWidget.js +139 -0
  23. package/dist/widgets/matrix/charset.d.ts +1 -0
  24. package/dist/widgets/matrix/charset.js +11 -0
  25. package/dist/widgets/matrix/defaults.d.ts +3 -0
  26. package/dist/widgets/matrix/defaults.js +17 -0
  27. package/dist/widgets/matrix/matrix-column.d.ts +24 -0
  28. package/dist/widgets/matrix/matrix-column.js +58 -0
  29. package/dist/widgets/matrix/types.d.ts +38 -0
  30. package/dist/widgets/matrix/types.js +0 -0
  31. package/dist/widgets/snake/SnakeWidget.d.ts +14 -0
  32. package/dist/widgets/snake/SnakeWidget.js +384 -0
  33. package/dist/widgets/snake/defaults.d.ts +3 -0
  34. package/dist/widgets/snake/defaults.js +19 -0
  35. package/dist/widgets/snake/types.d.ts +25 -0
  36. package/dist/widgets/snake/types.js +0 -0
  37. package/dist/widgets/videoplayer/VideoPlayerWidget.d.ts +16 -0
  38. package/dist/widgets/videoplayer/VideoPlayerWidget.js +465 -0
  39. package/dist/widgets/videoplayer/braille.d.ts +18 -0
  40. package/dist/widgets/videoplayer/braille.js +69 -0
  41. package/dist/widgets/videoplayer/defaults.d.ts +3 -0
  42. package/dist/widgets/videoplayer/defaults.js +17 -0
  43. package/dist/widgets/videoplayer/types.d.ts +21 -0
  44. package/dist/widgets/videoplayer/types.js +0 -0
  45. package/package.json +50 -0
@@ -0,0 +1,3 @@
1
+ export { FrameRateWatcher, createFrameRateWatcher } from './widgets/framerate/FrameRateWatcher';
2
+ export type { FrameRateWatcherOptions } from './widgets/framerate/FrameRateWatcher';
3
+ export { createFrameRateWatcher as default } from './widgets/framerate/FrameRateWatcher';
@@ -0,0 +1,2 @@
1
+ export { FrameRateWatcher, createFrameRateWatcher } from './widgets/framerate/FrameRateWatcher';
2
+ export { createFrameRateWatcher as default } from './widgets/framerate/FrameRateWatcher';
@@ -0,0 +1,6 @@
1
+ export * from './matrix';
2
+ export * from './framerate';
3
+ export * from './snake';
4
+ export * from './videoplayer';
5
+ export * from './logger';
6
+ export { EXTENSION_REGISTRY } from './registry';
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';
@@ -0,0 +1,3 @@
1
+ export { LoggerWidget, createLoggerWidget } from './widgets/logger/LoggerWidget';
2
+ export type { LoggerWidgetOptions } from './widgets/logger/LoggerWidget';
3
+ export { createLoggerWidget as default } from './widgets/logger/LoggerWidget';
package/dist/logger.js ADDED
@@ -0,0 +1,2 @@
1
+ export { LoggerWidget, createLoggerWidget } from './widgets/logger/LoggerWidget';
2
+ export { createLoggerWidget as default } from './widgets/logger/LoggerWidget';
@@ -0,0 +1,3 @@
1
+ export type { MatrixWidgetOptions, MatrixColorScheme, MatrixSpeedRange, } from './widgets/matrix/types';
2
+ export { MatrixWidget, createMatrixWidget } from './widgets/matrix/MatrixWidget';
3
+ export { createMatrixWidget as default } from './widgets/matrix/MatrixWidget';
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';
@@ -0,0 +1,6 @@
1
+ type TuiComponentRegistry = Record<string, {
2
+ creator: string;
3
+ module: string;
4
+ }>;
5
+ export declare const EXTENSION_REGISTRY: TuiComponentRegistry;
6
+ export {};
@@ -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
+ };
@@ -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,11 @@
1
+ function range(start, end) {
2
+ const result = [];
3
+ for (let i = start; i <= end; i++) {
4
+ result.push(i);
5
+ }
6
+ return result;
7
+ }
8
+ export const MATRIX_CHARSET = [
9
+ ...range(0x41, 0x5A), // A-Z
10
+ ...range(0x61, 0x7A), // A-Za-z
11
+ ];
@@ -0,0 +1,3 @@
1
+ import type { MatrixColorScheme, MatrixWidgetOptions } from './types';
2
+ export declare const DEFAULT_MATRIX_COLOR_SCHEME: MatrixColorScheme;
3
+ export declare const DEFAULT_MATRIX_OPTIONS: MatrixWidgetOptions;
@@ -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;