From c559111619529fd1b755e78b52474a33df2b7ce0 Mon Sep 17 00:00:00 2001 From: entrepeneur4lyf Date: Fri, 22 Aug 2025 02:39:39 -0400 Subject: [PATCH 1/6] Consolidate and improve API documentation - Consolidates duplicate renderable.md and renderer.md files - Creates organized structure under /components/ and /core/ - Adds comprehensive API references for all components - Includes complete examples and type definitions - Documents future FontDefinition API for custom fonts --- .../core/docs/api/components/renderables.md | 592 ++++++++++++++++++ packages/core/docs/api/core/rendering.md | 585 +++++++++++++++++ 2 files changed, 1177 insertions(+) create mode 100644 packages/core/docs/api/components/renderables.md create mode 100644 packages/core/docs/api/core/rendering.md diff --git a/packages/core/docs/api/components/renderables.md b/packages/core/docs/api/components/renderables.md new file mode 100644 index 000000000..c5e75d416 --- /dev/null +++ b/packages/core/docs/api/components/renderables.md @@ -0,0 +1,592 @@ +# Renderables API + +Renderables are the building blocks of OpenTUI interfaces. They represent visual elements that can be rendered to the terminal screen. + +## Renderable Base Class + +All visual components in OpenTUI extend the `Renderable` base class, which provides core functionality for layout, rendering, and event handling. + +### Creating a Renderable + +```typescript +import { Renderable, OptimizedBuffer, RenderContext } from '@opentui/core'; + +class MyComponent extends Renderable { + constructor(id: string, options: RenderableOptions = {}) { + super(id, options); + } + + // Override to provide custom rendering + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + // Custom rendering logic + buffer.drawText('My Component', this.x, this.y, RGBA.fromHex('#ffffff')); + } +} +``` + +### RenderableOptions + +```typescript +interface RenderableOptions { + // Size + width?: number | 'auto' | `${number}%` + height?: number | 'auto' | `${number}%` + + // Visibility + visible?: boolean + zIndex?: number + buffered?: boolean + + // Layout (Flexbox) + flexGrow?: number + flexShrink?: number + flexDirection?: 'row' | 'column' | 'row-reverse' | 'column-reverse' + flexBasis?: number | 'auto' + alignItems?: 'flex-start' | 'flex-end' | 'center' | 'baseline' | 'stretch' + justifyContent?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly' + + // Position + position?: 'relative' | 'absolute' + top?: number | 'auto' | `${number}%` + right?: number | 'auto' | `${number}%` + bottom?: number | 'auto' | `${number}%` + left?: number | 'auto' | `${number}%` + + // Size constraints + minWidth?: number | `${number}%` + minHeight?: number | `${number}%` + maxWidth?: number | `${number}%` + maxHeight?: number | `${number}%` + + // Spacing + margin?: number | 'auto' | `${number}%` + marginTop?: number | 'auto' | `${number}%` + marginRight?: number | 'auto' | `${number}%` + marginBottom?: number | 'auto' | `${number}%` + marginLeft?: number | 'auto' | `${number}%` + + padding?: number | `${number}%` + paddingTop?: number | `${number}%` + paddingRight?: number | `${number}%` + paddingBottom?: number | `${number}%` + paddingLeft?: number | `${number}%` + + // Layout control + enableLayout?: boolean +} +``` + +## Properties + +### Layout Properties + +| Property | Type | Description | +|----------|------|-------------| +| `x` | `number` | Computed X position (read-only) | +| `y` | `number` | Computed Y position (read-only) | +| `width` | `number` | Computed width in characters | +| `height` | `number` | Computed height in lines | +| `visible` | `boolean` | Visibility state | +| `zIndex` | `number` | Stacking order | + +### Position Properties + +| Property | Type | Description | +|----------|------|-------------| +| `position` | `'relative' \| 'absolute'` | Position type | +| `top` | `number \| 'auto' \| \`${number}%\`` | Top offset | +| `right` | `number \| 'auto' \| \`${number}%\`` | Right offset | +| `bottom` | `number \| 'auto' \| \`${number}%\`` | Bottom offset | +| `left` | `number \| 'auto' \| \`${number}%\`` | Left offset | + +### Flexbox Properties + +| Property | Type | Description | +|----------|------|-------------| +| `flexGrow` | `number` | Flex grow factor | +| `flexShrink` | `number` | Flex shrink factor | +| `flexDirection` | `string` | Flex container direction | +| `flexBasis` | `number \| 'auto'` | Initial main size | +| `alignItems` | `string` | Cross-axis alignment | +| `justifyContent` | `string` | Main-axis alignment | + +### Size Constraint Properties + +| Property | Type | Description | +|----------|------|-------------| +| `minWidth` | `number \| \`${number}%\`` | Minimum width | +| `minHeight` | `number \| \`${number}%\`` | Minimum height | +| `maxWidth` | `number \| \`${number}%\`` | Maximum width | +| `maxHeight` | `number \| \`${number}%\`` | Maximum height | + +### Spacing Properties + +| Property | Type | Description | +|----------|------|-------------| +| `margin` | `number \| 'auto' \| \`${number}%\`` | All margins | +| `marginTop` | `number \| 'auto' \| \`${number}%\`` | Top margin | +| `marginRight` | `number \| 'auto' \| \`${number}%\`` | Right margin | +| `marginBottom` | `number \| 'auto' \| \`${number}%\`` | Bottom margin | +| `marginLeft` | `number \| 'auto' \| \`${number}%\`` | Left margin | +| `padding` | `number \| \`${number}%\`` | All padding | +| `paddingTop` | `number \| \`${number}%\`` | Top padding | +| `paddingRight` | `number \| \`${number}%\`` | Right padding | +| `paddingBottom` | `number \| \`${number}%\`` | Bottom padding | +| `paddingLeft` | `number \| \`${number}%\`` | Left padding | + +### State Properties + +| Property | Type | Description | +|----------|------|-------------| +| `focused` | `boolean` | Focus state (read-only) | +| `selectable` | `boolean` | Whether text can be selected | +| `focusable` | `boolean` | Whether component can receive focus | + +## Methods + +### Hierarchy Management + +#### `add(child: Renderable, index?: number): number` +Add a child component at optional index. + +```typescript +container.add(childComponent); +container.add(childComponent, 0); // Insert at beginning +``` + +#### `insertBefore(child: Renderable, anchor?: Renderable): number` +Insert a child before another child. + +```typescript +container.insertBefore(newChild, existingChild); +``` + +#### `remove(id: string): void` +Remove a child component by ID. + +```typescript +container.remove('child-id'); +``` + +#### `getRenderable(id: string): Renderable | undefined` +Find a child by ID. + +```typescript +const child = container.getRenderable('my-child'); +``` + +#### `getChildren(): Renderable[]` +Get all child components. + +```typescript +const children = container.getChildren(); +``` + +### Focus Management + +#### `focus(): void` +Request focus for this component. + +```typescript +input.focus(); +``` + +#### `blur(): void` +Remove focus from this component. + +```typescript +input.blur(); +``` + +### Layout Control + +#### `needsUpdate(): void` +Mark the component as needing a render update. + +```typescript +component.needsUpdate(); +``` + +#### `requestMeasure(): void` +Request a layout measurement pass. + +```typescript +component.requestMeasure(); +``` + +#### `getLayoutNode(): TrackedNode` +Get the Yoga layout node. + +```typescript +const node = component.getLayoutNode(); +``` + +### Selection + +#### `getSelectedText(): string` +Get the currently selected text. + +```typescript +const selected = component.getSelectedText(); +``` + +### Rendering (Protected Methods) + +#### `renderSelf(buffer: OptimizedBuffer, deltaTime: number): void` +Main rendering method (override in subclasses). + +```typescript +protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + // Custom rendering logic + buffer.drawText('Hello', this.x, this.y, RGBA.fromHex('#ffffff')); +} +``` + +#### `render(buffer: OptimizedBuffer, deltaTime: number): void` +Full render method (usually not overridden). + +### Event Handling (Protected Methods) + +#### `handleMouse(event: MouseEvent): boolean` +Handle mouse events (override for custom behavior). + +```typescript +protected handleMouse(event: MouseEvent): boolean { + if (event.type === 'click') { + // Handle click + return true; // Event handled + } + return false; +} +``` + +#### `handleKeyPress(key: ParsedKey): boolean` +Handle keyboard events (override for custom behavior). + +```typescript +protected handleKeyPress(key: ParsedKey): boolean { + if (key.name === 'enter') { + // Handle enter key + return true; + } + return false; +} +``` + +#### `handleSelection(state: SelectionState): void` +Handle text selection (override for custom behavior). + +```typescript +protected handleSelection(state: SelectionState): void { + // Custom selection logic +} +``` + +### Lifecycle Methods + +#### `onResize(width: number, height: number): void` +Called when the component is resized. + +```typescript +protected onResize(width: number, height: number): void { + super.onResize(width, height); + // Custom resize logic +} +``` + +#### `destroy(): void` +Clean up resources. + +```typescript +component.destroy(); +``` + +## Events + +Renderable extends `EventEmitter` and emits the following events: + +| Event | Data | Description | +|-------|------|-------------| +| `layout-changed` | - | Layout was recalculated | +| `added` | `parent: Renderable` | Added to parent | +| `removed` | `parent: Renderable` | Removed from parent | +| `resized` | `{width, height}` | Size changed | +| `focused` | - | Component gained focus | +| `blurred` | - | Component lost focus | + +## Built-in Components + +### BoxRenderable + +Container with optional borders and background. + +```typescript +import { BoxRenderable } from '@opentui/core'; + +const box = new BoxRenderable('myBox', { + width: 30, + height: 15, + backgroundColor: '#222222', + borderStyle: 'double', + borderColor: '#3498db', + title: 'My Box', + titleAlignment: 'center' +}); +``` + +**BoxRenderable Options:** +- `backgroundColor`: Background color +- `borderStyle`: Border style ('single', 'double', 'rounded', 'bold', 'ascii') +- `border`: Show borders (boolean or array of sides) +- `borderColor`: Border color +- `title`: Title text +- `titleAlignment`: Title alignment ('left', 'center', 'right') +- `focusedBorderColor`: Border color when focused + +### TextRenderable + +Displays text with styling. + +```typescript +import { TextRenderable } from '@opentui/core'; + +const text = new TextRenderable('myText', { + content: 'Hello, world!', + fg: '#ffffff', + bg: '#000000', + selectable: true +}); +``` + +**TextRenderable Options:** +- `content`: Text content (string or StyledText) +- `fg`: Foreground color +- `bg`: Background color +- `selectable`: Enable text selection +- `attributes`: Text attributes (bold, underline, etc.) + +### FrameBufferRenderable + +Provides an offscreen buffer for custom drawing. + +```typescript +import { FrameBufferRenderable, RGBA } from '@opentui/core'; + +const canvas = new FrameBufferRenderable('myCanvas', { + width: 40, + height: 20, + respectAlpha: true +}); + +// Draw on the internal buffer +canvas.frameBuffer.fillRect(0, 0, 40, 20, RGBA.fromHex('#000000')); +canvas.frameBuffer.drawText('Hello', 2, 2, RGBA.fromHex('#ffffff')); +``` + +**FrameBufferRenderable Properties:** +- `frameBuffer`: Internal OptimizedBuffer for drawing +- `respectAlpha`: Enable alpha blending + +### ASCIIFontRenderable + +Renders text using ASCII art fonts. Supports both built-in fonts and custom fonts loaded from JSON files. + +```typescript +import { ASCIIFontRenderable, fonts, type FontDefinition } from '@opentui/core'; + +// Using built-in font +const asciiText = new ASCIIFontRenderable('myAsciiText', { + text: 'HELLO', + font: fonts.block, + fg: '#ffffff' +}); + +// Using custom font from JSON file +import customFont from './my-custom-font.json'; + +const customText = new ASCIIFontRenderable('customText', { + text: 'CUSTOM', + font: customFont as FontDefinition, + fg: '#00ff00' +}); +``` + +**ASCIIFontRenderable Options:** +- `text`: Text to render +- `font`: FontDefinition object (use `fonts.tiny`, `fonts.block`, `fonts.slick`, `fonts.shade` for built-in, or import custom JSON) +- `fg`: Foreground color (string or RGBA or array for multi-color support) +- `bg`: Background color (string or RGBA) +- `selectable`: Enable text selection (boolean) +- `selectionBg`: Selection background color +- `selectionFg`: Selection foreground color + +**Built-in Fonts:** +- `fonts.tiny`: Small 2-line font +- `fonts.block`: Bold block letters +- `fonts.shade`: Shaded 3D effect +- `fonts.slick`: Slick stylized font + +**Custom Fonts:** +Custom fonts must be cfont-compatible JSON files with the following structure: +```json +{ + "name": "myfont", + "lines": 3, + "letterspace_size": 1, + "letterspace": [" ", " ", " "], + "chars": { + "A": ["▄▀█", "█▀█", "█ █"], + "B": ["█▄▄", "█▄█", "█▄█"], + // ... more characters + } +} +``` + +### Input + +Text input field component. + +```typescript +import { Input } from '@opentui/core'; + +const input = new Input('myInput', { + width: 30, + placeholder: 'Enter text...', + value: '', + password: false +}); + +input.on('submit', (value: string) => { + console.log('Submitted:', value); +}); +``` + +**Input Options:** +- `value`: Initial value +- `placeholder`: Placeholder text +- `password`: Hide input (password mode) +- `multiline`: Enable multiline input + +### Select + +Dropdown selection component. + +```typescript +import { Select } from '@opentui/core'; + +const select = new Select('mySelect', { + options: ['Option 1', 'Option 2', 'Option 3'], + selected: 0, + width: 20 +}); + +select.on('change', (index: number, value: string) => { + console.log('Selected:', value); +}); +``` + +**Select Options:** +- `options`: Array of options +- `selected`: Initially selected index +- `maxHeight`: Maximum dropdown height + +### TabSelect + +Tab selection component. + +```typescript +import { TabSelect } from '@opentui/core'; + +const tabs = new TabSelect('myTabs', { + tabs: ['Tab 1', 'Tab 2', 'Tab 3'], + selected: 0 +}); + +tabs.on('change', (index: number) => { + console.log('Selected tab:', index); +}); +``` + +**TabSelect Options:** +- `tabs`: Array of tab labels +- `selected`: Initially selected tab index + +### Group + +Container for grouping components without visual representation. + +```typescript +import { Group } from '@opentui/core'; + +const group = new Group('myGroup', { + flexDirection: 'row', + gap: 2 +}); +``` + +## Creating Custom Components + +Extend the `Renderable` class to create custom components: + +```typescript +import { Renderable, OptimizedBuffer, RGBA, RenderableOptions } from '@opentui/core'; + +interface ProgressBarOptions extends RenderableOptions { + progress?: number; + barColor?: string; + backgroundColor?: string; +} + +class ProgressBar extends Renderable { + private _progress: number = 0; + private _barColor: RGBA; + private _backgroundColor: RGBA; + + constructor(id: string, options: ProgressBarOptions = {}) { + super(id, { + height: 1, + ...options + }); + + this._progress = options.progress || 0; + this._barColor = RGBA.fromHex(options.barColor || '#3498db'); + this._backgroundColor = RGBA.fromHex(options.backgroundColor || '#222222'); + } + + get progress(): number { + return this._progress; + } + + set progress(value: number) { + this._progress = Math.max(0, Math.min(100, value)); + this.needsUpdate(); + } + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + const width = this.width; + const filledWidth = Math.floor((width * this._progress) / 100); + + // Draw background + buffer.fillRect(this.x, this.y, width, 1, this._backgroundColor); + + // Draw progress + if (filledWidth > 0) { + buffer.fillRect(this.x, this.y, filledWidth, 1, this._barColor); + } + + // Draw percentage text + const text = `${this._progress}%`; + const textX = this.x + Math.floor((width - text.length) / 2); + buffer.drawText(text, textX, this.y, RGBA.fromHex('#ffffff')); + } +} + +// Usage +const progressBar = new ProgressBar('progress', { + width: 30, + progress: 75 +}); + +// Update progress +progressBar.progress = 80; +``` \ No newline at end of file diff --git a/packages/core/docs/api/core/rendering.md b/packages/core/docs/api/core/rendering.md new file mode 100644 index 000000000..8802dded6 --- /dev/null +++ b/packages/core/docs/api/core/rendering.md @@ -0,0 +1,585 @@ +# Rendering API + +The OpenTUI rendering system provides a powerful and flexible way to create terminal user interfaces. It handles the low-level details of terminal rendering, layout management, and input handling. + +## CliRenderer + +The `CliRenderer` is the core rendering engine that manages the terminal output, input handling, and rendering loop. + +### Creating a Renderer + +```typescript +import { createCliRenderer } from '@opentui/core'; + +// Create a renderer with default options +const renderer = await createCliRenderer(); + +// Create a renderer with custom options +const renderer = await createCliRenderer({ + exitOnCtrlC: true, + targetFps: 60, + useMouse: true, + useAlternateScreen: true, + useConsole: false, + gatherStats: false +}); +``` + +### Renderer Configuration Options + +```typescript +interface CliRendererConfig { + stdin?: NodeJS.ReadStream; // Input stream (default: process.stdin) + stdout?: NodeJS.WriteStream; // Output stream (default: process.stdout) + exitOnCtrlC?: boolean; // Exit on Ctrl+C (default: true) + debounceDelay?: number; // Debounce delay for rendering in ms (default: 0) + targetFps?: number; // Target frames per second (default: 60) + memorySnapshotInterval?: number; // Memory snapshot interval in ms + useThread?: boolean; // Use worker thread (default: false) + gatherStats?: boolean; // Collect performance stats (default: false) + maxStatSamples?: number; // Max stat samples (default: 100) + consoleOptions?: ConsoleOptions; // Console capture options + postProcessFns?: Function[]; // Post-processing functions + enableMouseMovement?: boolean; // Track mouse movement (default: false) + useMouse?: boolean; // Enable mouse support (default: false) + useAlternateScreen?: boolean; // Use alternate screen buffer (default: true) + useConsole?: boolean; // Capture console output (default: false) + experimental_splitHeight?: number; // Split screen height (experimental) +} +``` + +## Renderer Properties + +### `root: RootRenderable` +The root component container. All UI components should be added as children of root. + +```typescript +const { root } = renderer; +root.add(myComponent); +``` + +### `width: number` +Current terminal width in columns. + +### `height: number` +Current terminal height in rows. + +### `console: TerminalConsole | null` +Console instance for captured output (when `useConsole` is true). + +### `selection: Selection` +Global text selection manager. + +## Renderer Methods + +### Lifecycle Methods + +#### `start(): void` +Start the rendering loop. + +```typescript +renderer.start(); +``` + +#### `pause(): void` +Pause the rendering loop. + +```typescript +renderer.pause(); +``` + +#### `stop(): void` +Stop the rendering loop. + +```typescript +renderer.stop(); +``` + +#### `destroy(): void` +Clean up resources and restore terminal state. + +```typescript +renderer.destroy(); +``` + +### Display Control + +#### `setBackgroundColor(color: ColorInput): void` +Set the terminal background color. + +```typescript +renderer.setBackgroundColor('#1a1a1a'); +renderer.setBackgroundColor(RGBA.fromHex('#000000')); +``` + +#### `setCursorPosition(x: number, y: number, visible?: boolean): void` +Set the cursor position and visibility. + +```typescript +renderer.setCursorPosition(10, 5, true); +``` + +#### `setCursorStyle(style: CursorStyle, blinking?: boolean, color?: RGBA): void` +Set the cursor appearance. + +```typescript +renderer.setCursorStyle('block', true); +renderer.setCursorStyle('underline', false, RGBA.fromHex('#00ff00')); +// Styles: 'block', 'underline', 'bar', 'block-blinking', 'underline-blinking', 'bar-blinking' +``` + +#### `setCursorColor(color: RGBA): void` +Set the cursor color. + +```typescript +renderer.setCursorColor(RGBA.fromHex('#ffffff')); +``` + +### Debug and Performance + +#### `toggleDebugOverlay(): void` +Toggle the debug overlay display. + +```typescript +renderer.toggleDebugOverlay(); +``` + +#### `configureDebugOverlay(config: DebugOverlayConfig): void` +Configure debug overlay settings. + +```typescript +renderer.configureDebugOverlay({ + enabled: true, + corner: DebugOverlayCorner.bottomRight +}); +``` + +#### `getStats(): RendererStats` +Get performance statistics. + +```typescript +const stats = renderer.getStats(); +console.log(`FPS: ${stats.fps}`); +console.log(`Frame time: ${stats.averageFrameTime}ms`); +console.log(`Max frame time: ${stats.maxFrameTime}ms`); +``` + +#### `resetStats(): void` +Reset performance statistics. + +```typescript +renderer.resetStats(); +``` + +#### `setGatherStats(enabled: boolean): void` +Enable or disable statistics gathering. + +```typescript +renderer.setGatherStats(true); +``` + +### Post-Processing + +#### `addPostProcessFn(fn: PostProcessFunction): void` +Add a post-processing function applied to each frame. + +```typescript +renderer.addPostProcessFn((buffer: OptimizedBuffer, deltaTime: number) => { + // Apply effects to the buffer + for (let y = 0; y < buffer.height; y += 2) { + for (let x = 0; x < buffer.width; x++) { + const cell = buffer.getCell(x, y); + // Modify cell attributes for scanline effect + buffer.setCell(x, y, cell.char, cell.fg, cell.bg, cell.attributes | 0x08); + } + } +}); +``` + +#### `removePostProcessFn(fn: PostProcessFunction): void` +Remove a post-processing function. + +```typescript +renderer.removePostProcessFn(myPostProcessFn); +``` + +#### `clearPostProcessFns(): void` +Clear all post-processing functions. + +```typescript +renderer.clearPostProcessFns(); +``` + +### Frame Callbacks + +#### `setFrameCallback(callback: FrameCallback): void` +Set a callback executed each frame. + +```typescript +renderer.setFrameCallback(async (deltaTime: number) => { + // Update animations or perform frame-based logic + await updateAnimations(deltaTime); +}); +``` + +### Animation Frame API + +OpenTUI provides browser-compatible animation frame methods: + +```typescript +// Request an animation frame +const frameId = renderer.requestAnimationFrame((deltaTime: number) => { + // Animation logic +}); + +// Cancel an animation frame +renderer.cancelAnimationFrame(frameId); +``` + +### Selection Management + +#### `hasSelection(): boolean` +Check if there's an active text selection. + +```typescript +if (renderer.hasSelection()) { + const text = renderer.getSelection(); +} +``` + +#### `getSelection(): string` +Get the currently selected text. + +```typescript +const selectedText = renderer.getSelection(); +``` + +#### `clearSelection(): void` +Clear the current selection. + +```typescript +renderer.clearSelection(); +``` + +#### `getSelectionContainer(): Renderable | null` +Get the container of the current selection. + +```typescript +const container = renderer.getSelectionContainer(); +``` + +## Events + +The renderer extends `EventEmitter` and emits the following events: + +### `'key'` +Fired when a key is pressed. + +```typescript +renderer.on('key', (key: ParsedKey) => { + console.log('Key pressed:', key.name); + if (key.ctrl && key.name === 'c') { + // Handle Ctrl+C + } +}); +``` + +**ParsedKey Structure:** +```typescript +interface ParsedKey { + name: string; // Key name (e.g., 'a', 'enter', 'up') + ctrl: boolean; // Ctrl modifier + meta: boolean; // Meta/Alt modifier + shift: boolean; // Shift modifier + sequence: string; // Raw key sequence +} +``` + +### `'mouse'` +Fired for mouse events. + +```typescript +renderer.on('mouse', (event: MouseEvent) => { + console.log('Mouse event:', event.type, 'at', event.x, event.y); +}); +``` + +### `'resize'` +Fired when the terminal is resized. + +```typescript +renderer.on('resize', (width: number, height: number) => { + console.log('Terminal resized to:', width, 'x', height); +}); +``` + +## RenderContext + +The `RenderContext` provides information and utilities for rendering components. + +```typescript +interface RenderContext { + // Add component to hit testing grid + addToHitGrid(x: number, y: number, width: number, height: number, id: number): void; + + // Get current viewport dimensions + width(): number; + height(): number; + + // Request a re-render + needsUpdate(): void; +} +``` + +## Mouse Events + +OpenTUI provides comprehensive mouse event handling. + +### MouseEvent Class + +```typescript +class MouseEvent { + readonly type: MouseEventType; // Event type + readonly button: number; // Mouse button (MouseButton enum) + readonly x: number; // X coordinate + readonly y: number; // Y coordinate + readonly source?: Renderable; // Source component (for drag) + readonly modifiers: { + shift: boolean; + alt: boolean; + ctrl: boolean; + }; + readonly scroll?: ScrollInfo; // Scroll information + readonly target: Renderable | null; // Target component + + preventDefault(): void; // Prevent default handling +} +``` + +### MouseEventType + +```typescript +type MouseEventType = + | 'click' // Mouse click + | 'dblclick' // Double click + | 'down' // Mouse button down + | 'up' // Mouse button up + | 'move' // Mouse movement + | 'over' // Mouse over component + | 'out' // Mouse out of component + | 'drag' // Dragging + | 'dragstart' // Drag started + | 'dragend' // Drag ended + | 'scroll'; // Mouse wheel scroll +``` + +### MouseButton Enum + +```typescript +enum MouseButton { + LEFT = 0, + MIDDLE = 1, + RIGHT = 2, + WHEEL_UP = 4, + WHEEL_DOWN = 5, +} +``` + +### Example: Handling Mouse Events + +```typescript +class InteractiveBox extends BoxRenderable { + protected handleMouse(event: MouseEvent): boolean { + switch (event.type) { + case 'over': + this.borderColor = '#00ff00'; + break; + case 'out': + this.borderColor = '#ffffff'; + break; + case 'click': + if (event.button === MouseButton.LEFT) { + console.log('Left clicked at', event.x, event.y); + event.preventDefault(); + return true; + } + break; + case 'scroll': + if (event.scroll) { + console.log('Scrolled:', event.scroll.direction); + } + break; + } + return false; + } +} +``` + +## Complete Example Application + +```typescript +import { + createCliRenderer, + BoxRenderable, + TextRenderable, + Input, + RGBA +} from '@opentui/core'; + +async function main() { + // Create renderer with options + const renderer = await createCliRenderer({ + useMouse: true, + useAlternateScreen: true, + targetFps: 60, + exitOnCtrlC: true + }); + + // Get root container + const { root } = renderer; + + // Create main container + const app = new BoxRenderable('app', { + width: '100%', + height: '100%', + borderStyle: 'double', + borderColor: '#3498db', + backgroundColor: '#1a1a1a', + padding: 1 + }); + + // Add title + const title = new TextRenderable('title', { + content: 'OpenTUI Application', + fg: '#ffffff', + alignItems: 'center', + marginBottom: 2 + }); + + // Add input field + const input = new Input('input', { + width: '80%', + placeholder: 'Type a command...', + marginLeft: '10%' + }); + + // Build component tree + app.add(title); + app.add(input); + root.add(app); + + // Handle input submission + input.on('submit', (value: string) => { + console.log('Command:', value); + input.value = ''; + }); + + // Handle global keys + renderer.on('key', (key) => { + if (key.name === 'escape') { + renderer.destroy(); + process.exit(0); + } + }); + + // Handle resize + renderer.on('resize', (width, height) => { + console.log(`Resized to ${width}x${height}`); + }); + + // Add post-processing effect + renderer.addPostProcessFn((buffer, deltaTime) => { + // Add a subtle vignette effect + const centerX = Math.floor(buffer.width / 2); + const centerY = Math.floor(buffer.height / 2); + const maxDist = Math.sqrt(centerX * centerX + centerY * centerY); + + for (let y = 0; y < buffer.height; y++) { + for (let x = 0; x < buffer.width; x++) { + const dist = Math.sqrt( + Math.pow(x - centerX, 2) + + Math.pow(y - centerY, 2) + ); + const factor = 1 - (dist / maxDist) * 0.3; + + const cell = buffer.getCell(x, y); + if (cell) { + const dimmedFg = cell.fg.multiply(factor); + buffer.setCell(x, y, cell.char, dimmedFg, cell.bg); + } + } + } + }); + + // Start rendering + renderer.start(); + + // Focus the input + input.focus(); + + // Cleanup on exit + process.on('SIGINT', () => { + renderer.destroy(); + process.exit(0); + }); +} + +main().catch(console.error); +``` + +## Advanced Features + +### Split Screen Mode (Experimental) + +Split the terminal between the renderer and regular console output: + +```typescript +const renderer = await createCliRenderer({ + experimental_splitHeight: 10 // Reserve 10 lines for renderer +}); + +// The renderer will only use the top 10 lines +// Console output will appear below +``` + +### Console Capture + +Capture and display console output within the TUI: + +```typescript +const renderer = await createCliRenderer({ + useConsole: true, + consoleOptions: { + maxLines: 100, + autoscroll: true + } +}); + +// Console output is now captured +console.log('This appears in the TUI console'); + +// Access the console component +const consoleComponent = renderer.console; +``` + +### Performance Optimization + +For high-performance applications: + +```typescript +const renderer = await createCliRenderer({ + targetFps: 120, // Higher frame rate + debounceDelay: 0, // No debouncing + useThread: true, // Use worker thread + gatherStats: true, // Monitor performance + maxStatSamples: 1000 // More samples for analysis +}); + +// Monitor performance +setInterval(() => { + const stats = renderer.getStats(); + if (stats.averageFrameTime > 16.67) { + console.warn('Frame rate below 60 FPS'); + } +}, 1000); +``` \ No newline at end of file From 1143926c81f5cc54756a1f6f8918293142645eb1 Mon Sep 17 00:00:00 2001 From: entrepeneur4lyf Date: Fri, 22 Aug 2025 02:41:27 -0400 Subject: [PATCH 2/6] Add comprehensive API documentation and CLAUDE.md - Adds complete API documentation for all OpenTUI components - Creates organized documentation structure with proper categorization - Includes CLAUDE.md guide for future Claude instances - Documents all renderables, utilities, 3D features, and animations - Provides code examples and type definitions for each component - Consolidates duplicate files into proper locations --- CLAUDE.md | 78 ++ packages/core/docs/api/3d/canvas.md | 126 +++ .../docs/api/3d/exploding-sprite-effect.md | 120 +++ .../api/3d/physics-exploding-sprite-effect.md | 122 +++ packages/core/docs/api/3d/physics.md | 454 +++++++++ packages/core/docs/api/3d/shaders.md | 874 ++++++++++++++++++ packages/core/docs/api/3d/sprite-animation.md | 518 +++++++++++ packages/core/docs/api/3d/sprite-animator.md | 115 +++ packages/core/docs/api/3d/sprites.md | 192 ++++ packages/core/docs/api/3d/webgpu.md | 452 +++++++++ packages/core/docs/api/3d/wgpu-renderer.md | 161 ++++ packages/core/docs/api/README.md | 121 +++ packages/core/docs/api/advanced/3d.md | 854 +++++++++++++++++ packages/core/docs/api/animation/animation.md | 761 +++++++++++++++ packages/core/docs/api/animation/timeline.md | 356 +++++++ packages/core/docs/api/buffer.md | 159 ++++ .../core/docs/api/components/ascii-font.md | 399 ++++++++ packages/core/docs/api/components/box.md | 270 ++++++ .../core/docs/api/components/framebuffer.md | 394 ++++++++ packages/core/docs/api/components/group.md | 358 +++++++ packages/core/docs/api/components/input.md | 289 ++++++ packages/core/docs/api/components/select.md | 425 +++++++++ .../core/docs/api/components/tab-select.md | 454 +++++++++ packages/core/docs/api/components/text.md | 295 ++++++ packages/core/docs/api/index.md | 227 +++++ packages/core/docs/api/input/input.md | 742 +++++++++++++++ packages/core/docs/api/lib/border.md | 489 ++++++++++ .../core/docs/api/lib/hast-styled-text.md | 481 ++++++++++ packages/core/docs/api/lib/keyhandler.md | 432 +++++++++ packages/core/docs/api/lib/selection.md | 284 ++++++ packages/core/docs/api/lib/styled-text.md | 393 ++++++++ packages/core/docs/api/lib/tracked-node.md | 246 +++++ packages/core/docs/api/native-integration.md | 144 +++ packages/core/docs/api/post/filters.md | 420 +++++++++ packages/core/docs/api/react/reconciler.md | 601 ++++++++++++ .../core/docs/api/renderables/ascii-font.md | 515 +++++++++++ .../core/docs/api/styling/text-styling.md | 451 +++++++++ packages/core/docs/api/utils/console.md | 464 ++++++++++ .../core/docs/api/utils/output-capture.md | 428 +++++++++ packages/core/docs/api/utils/utilities.md | 733 +++++++++++++++ 40 files changed, 15397 insertions(+) create mode 100644 CLAUDE.md create mode 100644 packages/core/docs/api/3d/canvas.md create mode 100644 packages/core/docs/api/3d/exploding-sprite-effect.md create mode 100644 packages/core/docs/api/3d/physics-exploding-sprite-effect.md create mode 100644 packages/core/docs/api/3d/physics.md create mode 100644 packages/core/docs/api/3d/shaders.md create mode 100644 packages/core/docs/api/3d/sprite-animation.md create mode 100644 packages/core/docs/api/3d/sprite-animator.md create mode 100644 packages/core/docs/api/3d/sprites.md create mode 100644 packages/core/docs/api/3d/webgpu.md create mode 100644 packages/core/docs/api/3d/wgpu-renderer.md create mode 100644 packages/core/docs/api/README.md create mode 100644 packages/core/docs/api/advanced/3d.md create mode 100644 packages/core/docs/api/animation/animation.md create mode 100644 packages/core/docs/api/animation/timeline.md create mode 100644 packages/core/docs/api/buffer.md create mode 100644 packages/core/docs/api/components/ascii-font.md create mode 100644 packages/core/docs/api/components/box.md create mode 100644 packages/core/docs/api/components/framebuffer.md create mode 100644 packages/core/docs/api/components/group.md create mode 100644 packages/core/docs/api/components/input.md create mode 100644 packages/core/docs/api/components/select.md create mode 100644 packages/core/docs/api/components/tab-select.md create mode 100644 packages/core/docs/api/components/text.md create mode 100644 packages/core/docs/api/index.md create mode 100644 packages/core/docs/api/input/input.md create mode 100644 packages/core/docs/api/lib/border.md create mode 100644 packages/core/docs/api/lib/hast-styled-text.md create mode 100644 packages/core/docs/api/lib/keyhandler.md create mode 100644 packages/core/docs/api/lib/selection.md create mode 100644 packages/core/docs/api/lib/styled-text.md create mode 100644 packages/core/docs/api/lib/tracked-node.md create mode 100644 packages/core/docs/api/native-integration.md create mode 100644 packages/core/docs/api/post/filters.md create mode 100644 packages/core/docs/api/react/reconciler.md create mode 100644 packages/core/docs/api/renderables/ascii-font.md create mode 100644 packages/core/docs/api/styling/text-styling.md create mode 100644 packages/core/docs/api/utils/console.md create mode 100644 packages/core/docs/api/utils/output-capture.md create mode 100644 packages/core/docs/api/utils/utilities.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..e0cf73d70 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,78 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +OpenTUI is a TypeScript library for building terminal user interfaces (TUIs), serving as the foundational framework for opencode and terminaldotshop projects. The project is structured as a monorepo with multiple packages. + +## Essential Commands + +```bash +# Development +bun install # Install dependencies +bun run build # Build all packages +cd packages/core && bun run src/examples/index.ts # Run core examples + +# Package-specific builds (from packages/core) +bun run build:dev # Debug build with Zig +bun run build:prod # Production build with Zig +bun run build # Full build (native + TypeScript) + +# Testing +bun test # Run all tests +bun test # Run specific test file + +# Release workflow +bun run prepare-release # Prepare for release +bun run pre-publish # Pre-publish checks +bun run publish # Publish packages +``` + +## Architecture + +### Package Structure +- **@opentui/core**: Core library with imperative API, renderables, animations, text styling, and input handling. Contains native Zig components for performance-critical operations. +- **@opentui/react**: React reconciler implementation with custom hooks (useKeyboard, useRenderer, useResize) +- **@opentui/solid**: SolidJS reconciler with JSX runtime and Babel transformation + +### Key Technologies +- **Runtime**: Bun (>=1.2.0 required) +- **Language**: TypeScript with strict mode +- **Native**: Zig (0.14.0-0.14.1) for performance-critical components +- **Cross-platform**: Builds for darwin/linux/windows on x64/arm64 + +### Native Integration +The core package includes Zig components that compile to native libraries (.so, .dll, .dylib) for each platform. These handle performance-critical rendering operations and are automatically built during `bun run build`. + +### 3D Capabilities +Core package exports 3D features through separate entry points: +- WebGPU integration +- Three.js support +- Physics engines (Rapier, Planck) + +## Development Patterns + +### Testing +Tests use Bun's built-in test framework. Import pattern: +```typescript +import { expect, describe, it, beforeEach, afterEach } from "bun:test" +``` + +### Code Style +- No semicolons (Prettier configured) +- 120 character line width +- Explicit TypeScript types for public APIs +- camelCase for variables/functions, PascalCase for classes/interfaces + +### File Organization +- Group related functionality in directories +- Use index files for clean exports +- Examples in `/examples` directories within each package + +## Important Notes + +- Always use Bun (not npm/yarn) for package management +- Native builds require Zig 0.14.0-0.14.1 installed +- When modifying native code, rebuild with appropriate optimization level +- Cross-platform binaries are pre-built but can be regenerated with build commands \ No newline at end of file diff --git a/packages/core/docs/api/3d/canvas.md b/packages/core/docs/api/3d/canvas.md new file mode 100644 index 000000000..7ec51317b --- /dev/null +++ b/packages/core/docs/api/3d/canvas.md @@ -0,0 +1,126 @@ +# CLICanvas (3D Canvas API) + +This document describes the terminal/CLI canvas used by the 3D/WebGPU rendering subsystem. Implementation reference: `packages/core/src/3d/canvas.ts`. + +CLICanvas is a lightweight, testable canvas abstraction used by the WGPURenderer to provide a GPU-backed drawing surface and to read pixels back into OpenTUI buffers for terminal rendering. + +Important types +- GPUDevice: WebGPU device (via bun-webgpu / platform WebGPU) +- OptimizedBuffer: OpenTUI optimized framebuffer for terminal cells (see buffer.md) +- SuperSampleType: enum exported by `WGPURenderer` — controls supersampling mode +- SuperSampleAlgorithm: enum defined in this module (STANDARD | PRE_SQUEEZED) + +## SuperSampleAlgorithm + +```ts +export enum SuperSampleAlgorithm { + STANDARD = 0, + PRE_SQUEEZED = 1, +} +``` + +Use this enum to choose the compute shader algorithm for GPU supersampling. + +## Class: CLICanvas + +Constructor +```ts +new CLICanvas( + device: GPUDevice, + width: number, + height: number, + superSample: SuperSampleType, + sampleAlgo: SuperSampleAlgorithm = SuperSampleAlgorithm.STANDARD +) +``` +- device: the WebGPU device used for creating buffers, pipelines and submitting commands. +- width/height: render dimensions (in pixels). +- superSample: initial SuperSampleType used by WGPURenderer (NONE | CPU | GPU or similar defined in WGPURenderer). +- sampleAlgo: choose a supersampling algorithm. + +Primary properties +- width: number — current render width (pixels) +- height: number — current render height (pixels) +- superSample: SuperSampleType — current supersample mode +- superSampleAlgorithm: SuperSampleAlgorithm — selected compute algorithm +- superSampleDrawTimeMs: number — measured time spent drawing supersampled output +- mapAsyncTimeMs: number — measured time spent mapping GPU readback buffers +- (internal) computePipeline, computeBindGroupLayout, computeOutputBuffer, computeReadbackBuffer, etc. + +Public methods + +- setSuperSampleAlgorithm(superSampleAlgorithm: SuperSampleAlgorithm): void + - Switch the compute shader algorithm; updates internal state and schedules buffer updates. + +- getSuperSampleAlgorithm(): SuperSampleAlgorithm + +- getContext(type: string, attrs?: WebGLContextAttributes) + - Supported type: `"webgpu"`. + - When `"webgpu"` is requested, CLICanvas prepares GPU readback / compute buffers and returns a GPUCanvasContext (here a GPUCanvasContextMock). + - Throws for other `type` values. + +- setSize(width: number, height: number): void + - Resize the internal canvas/context and readback buffers. Also schedules compute buffer updates. + +- setSuperSample(superSample: SuperSampleType): void + - Change supersampling mode (NONE / CPU / GPU / ...). + +- async saveToFile(filePath: string): Promise + - Capture the current texture, copy it to a GPU buffer, map it, and write an image file. + - Handles row padding and BGRA vs RGBA formats. + - Uses `jimp` to produce an image file (path must include extension). + - Useful for debugging or saving screenshots of the GPU render output. + +- async readPixelsIntoBuffer(buffer: OptimizedBuffer): Promise + - Read pixels from the current texture into the provided OptimizedBuffer. + - Behavior depends on `superSample`: + - `SuperSampleType.GPU`: runs compute shader supersampling and then unpacks compute output into the OptimizedBuffer via `buffer.drawPackedBuffer(...)`. + - `SuperSampleType.CPU`: uses the readback buffer and calls `buffer.drawSuperSampleBuffer(...)`. + - Otherwise: maps the readback buffer and converts pixel bytes into RGBA floats and writes them into `buffer` by calling `buffer.setCell(...)` per cell. + - Handles BGRA vs RGBA formats and aligned bytes-per-row when mapping GPU readback buffers. + +Notes on compute pipeline and buffers +- CLICanvas builds a compute pipeline for supersampling: + - `initComputePipeline()` creates shader module, bind group layout and compute pipeline. + - `updateComputeParams()` writes a uniform buffer containing width/height/algorithm. + - `updateComputeBuffers(width,height)` allocates storage and readback buffers sized to match the compute shader's output layout (must match WGSL shader). + - `runComputeShaderSuperSampling(texture, buffer)` dispatches the compute shader, copies compute output to readback buffer, maps it, and then calls `buffer.drawPackedBuffer(...)` with the mapped pointer. + +Performance and alignment +- The code carefully computes `alignedBytesPerRow` (ceil to 256) when copying textures to GPU buffers — this is required by many GPU APIs. +- The compute output packing uses a specific cell byte layout (48 bytes per cell in the implementation). This must exactly match the WGSL shader layout in `shaders/supersampling.wgsl`. + +Example: Capture and store the GPU render into an OptimizedBuffer +```ts +// device and renderer provided by WGPURenderer +const canvas = new CLICanvas(device, width, height, superSampleType, SuperSampleAlgorithm.STANDARD); +const optimized = OptimizedBuffer.create(width, height, { respectAlpha: false }); + +// After a frame is presented by your WebGPU code: +await canvas.readPixelsIntoBuffer(optimized); + +// Now `optimized` contains character-like pixel representations (canvas uses '█' for pixels) +``` + +Example: Save screenshot to disk +```ts +await canvas.saveToFile('/tmp/opentui-screenshot.png') +``` + +Implementation notes and debugging +- The WGSL shader used for compute supersampling is embedded at build time: `shaders/supersampling.wgsl`. +- If you change the WGSL shader, ensure the compute output packing / `cellBytesSize` calculation in `updateComputeBuffers` is updated accordingly. +- The code uses a `GPUCanvasContextMock` (from bun-webgpu) to emulate canvas behavior in non-browser contexts. + +Related docs +- See `packages/core/docs/api/3d/shaders.md` for details on the WGSL shader and how it maps to compute outputs. +- See `packages/core/src/3d/WGPURenderer.ts` for the renderer that uses `CLICanvas` (documented next). + +--- + +Next steps I will take (unless you prefer otherwise): +- Read and document `WGPURenderer.ts` (expose public API, options, and how it integrates with CliRenderer). +- Document sprite-related modules: `SpriteResourceManager.ts`, `SpriteUtils.ts`, `TextureUtils.ts`, and animation classes in `3d/animation/`. +- Document physics adapters: `PlanckPhysicsAdapter.ts` and `RapierPhysicsAdapter.ts`. + +If you'd like me to proceed, I will read the next file `packages/core/src/3d/WGPURenderer.ts` and generate a corresponding doc page. diff --git a/packages/core/docs/api/3d/exploding-sprite-effect.md b/packages/core/docs/api/3d/exploding-sprite-effect.md new file mode 100644 index 000000000..e31465d33 --- /dev/null +++ b/packages/core/docs/api/3d/exploding-sprite-effect.md @@ -0,0 +1,120 @@ +# ExplodingSpriteEffect & ExplosionManager + +This page documents the GPU-driven exploding sprite particle effect used by the 3D animation subsystem. Implementation reference: `packages/core/src/3d/animation/ExplodingSpriteEffect.ts`. + +The effect slices a sprite into a grid of smaller particles and launches them outward using instanced GPU particles with per-instance velocity, angular velocity, UV offsets, and lifetime. A manager class (`ExplosionManager`) provides pooling and convenience helpers to create explosions from existing sprite instances. + +## Key types + +- ExplosionEffectParameters — configuration for number of particles, lifetime, strength, gravity, etc. +- ExplosionCreationData — data required to create an explosion (resource, UV offsets, transform). +- ExplosionHandle — returned handle allowing restoration of original sprite after explosion. + +## ExplosionEffectParameters (fields) + +```ts +interface ExplosionEffectParameters { + numRows: number + numCols: number + durationMs: number + strength: number + strengthVariation: number + gravity: number + gravityScale: number + fadeOut: boolean + angularVelocityMin: THREE.Vector3 + angularVelocityMax: THREE.Vector3 + initialVelocityYBoost: number + zVariationStrength: number + materialFactory: () => NodeMaterial +} +``` + +Default parameters (`DEFAULT_EXPLOSION_PARAMETERS`) are provided; override fields via `userParams` in the constructor. + +Important notes: +- `numRows` x `numCols` determines the number of particles. +- `durationMs` is lifetime in milliseconds. +- `strength` controls particle initial velocity magnitude; `strengthVariation` adds random spread. +- `gravity` and `gravityScale` control vertical acceleration applied to particles. +- `fadeOut`: if true particles fade out near the end of lifetime. +- `materialFactory`: factory returning a NodeMaterial for GPU shading (defaults provided). + +## Class: ExplodingSpriteEffect + +Constructor +```ts +new ExplodingSpriteEffect( + scene: THREE.Scene, + resource: SpriteResource, + frameUvOffset: THREE.Vector2, + frameUvSize: THREE.Vector2, + spriteWorldTransform: THREE.Matrix4, + userParams?: Partial, +) +``` +- `resource`: a SpriteResource (from SpriteResourceManager) containing texture and meshPool. +- `frameUvOffset` and `frameUvSize`: UV offsets and size for the particular sprite frame being exploded. +- `spriteWorldTransform`: world transform of the original sprite (particles start at sprite location). +- `userParams`: partial overrides of the default parameters. + +Public properties and methods: +- `isActive: boolean` — whether effect is active +- `update(deltaTimeMs: number): void` — advance particle time; disposes effect when lifetime exceeded +- `dispose(): void` — removes instanced mesh from scene and returns mesh to pool + +Behavior details: +- Creates an InstancedMesh with `numParticles` instances and per-instance attributes: + - `a_particleData` (vec4) — local particle position, seed, life variation + - `a_velocity` (vec4) — initial velocity vector + - `a_angularVel` (vec4) — angular velocity for orientation over time + - `a_uvOffset` (vec4) — uv offset and uv size for the particle's subtexture +- The material is constructed via `NodeMaterial` and stores uniform refs for time/duration/gravity. The particle vertex transforms are computed in the shader using these attributes and uniforms. +- On each frame, the effect updates `time` uniform (via `onBeforeRender`) and the GPU material computes positions/colors/opacities. + +## ExplosionManager + +Purpose: keep track of active explosions and provide pooling/factory helpers. + +API +```ts +class ExplosionManager { + constructor(scene: THREE.Scene) + + fillPool(resource: SpriteResource, count: number, params?: Partial): void + + createExplosionForSprite(spriteToExplode: TiledSprite, userParams?: Partial): ExplosionHandle | null + + update(deltaTimeMs: number): void + + disposeAll(): void +} +``` + +- `fillPool`: pre-fill mesh pools for a resource for performance. +- `createExplosionForSprite`: destroys the given sprite and replaces it with an ExplodingSpriteEffect; returns `ExplosionHandle` which can be used to restore the original sprite via `restoreSprite(spriteAnimator)`. +- `update`: advances all active explosions and removes finished effects. + +## Examples + +Create and trigger an explosion for a sprite (pseudocode): +```ts +const manager = new ExplosionManager(scene) +const explosionHandle = manager.createExplosionForSprite(myTiledSprite, { strength: 6, durationMs: 1500 }) + +// Optionally restore later: +if (explosionHandle) { + await explosionHandle.restoreSprite(spriteAnimator) +} +``` + +## Implementation details and GPU nodes + +- The implementation uses `three/tsl` nodes and `three/webgpu` NodeMaterial to implement particle computations in the shader. +- The material template is cached per resource + particle grid configuration. +- Ensure the NodeMaterial produced by `materialFactory` is compatible with the attributes and node graph built by `_buildTemplateMaterial`. + +--- + +Next steps: +- Document PhysicsExplodingSpriteEffect (physics-driven explosion), SpriteAnimator, and SpriteParticleGenerator to complete the animation docs. diff --git a/packages/core/docs/api/3d/physics-exploding-sprite-effect.md b/packages/core/docs/api/3d/physics-exploding-sprite-effect.md new file mode 100644 index 000000000..2e2fa8be5 --- /dev/null +++ b/packages/core/docs/api/3d/physics-exploding-sprite-effect.md @@ -0,0 +1,122 @@ +# PhysicsExplodingSpriteEffect & PhysicsExplosionManager + +This document describes the physics-driven exploding-sprite effect used by OpenTUI's 3D animation subsystem. Reference implementation: `packages/core/src/3d/animation/PhysicsExplodingSpriteEffect.ts`. + +The physics-driven effect uses a physics engine (abstracted by the project's physics adapter interface) to simulate particle rigid bodies and synchronizes their transforms into an instanced mesh for rendering. + +## Key types + +- PhysicsExplosionEffectParameters — configuration for particle physics (force, damping, restitution, density, etc.) +- PhysicsExplosionCreationData — resource + UV + transform needed to create an effect +- PhysicsExplosionHandle — a handle returned on creation that can restore the original sprite + +The effect depends on the physics abstraction implemented by the project's adapters (see `packages/core/src/3d/physics/physics-interface.ts` and corresponding Planck/Rapier adapters). + +## PhysicsExplosionEffectParameters (fields) + +```ts +interface PhysicsExplosionEffectParameters { + numRows: number + numCols: number + durationMs: number + explosionForce: number + forceVariation: number + torqueStrength: number + gravityScale: number + fadeOut: boolean + linearDamping: number + angularDamping: number + restitution: number + friction: number + density: number + materialFactory: () => NodeMaterial +} +``` + +Defaults available in `DEFAULT_PHYSICS_EXPLOSION_PARAMETERS`. Use `userParams` to override. + +## Class: PhysicsExplodingSpriteEffect + +Constructor +```ts +new PhysicsExplodingSpriteEffect( + scene: THREE.Scene, + physicsWorld: PhysicsWorld, + resource: SpriteResource, + frameUvOffset: THREE.Vector2, + frameUvSize: THREE.Vector2, + spriteWorldTransform: THREE.Matrix4, + userParams?: Partial +) +``` + +- `physicsWorld`: instance implementing the physics adapter interface (PhysicsWorld). +- The effect creates per-particle rigid bodies and colliders through the physicsWorld, applies impulses/torque, and tracks each particle's rigid body. +- It maintains an `InstancedMesh` used for rendering; per-instance UV offsets are stored in an InstancedBufferAttribute. + +Public methods +- `update(deltaTimeMs: number): void` — queries each particle rigid body for translation/rotation and writes instance matrices to the InstancedMesh. Disposes when time >= duration. +- `dispose(): void` — removes mesh from scene, releases it to MeshPool, and removes rigid bodies from physics world. + +Behavior notes +- For each particle: + - A rigid body is created with `translation`, `linearDamping`, `angularDamping` (via physicsWorld.createRigidBody). + - A collider matching particle size is created and attached. + - An impulse and torque impulse are applied to simulate explosion. +- The effect synchronizes physics transforms to the instanced mesh each `update` call. +- The effect uses a shared NodeMaterial (cached per texture) to sample the sprite UVs. + +## PhysicsExplosionManager + +Purpose: manage many physics-driven explosions, pool geometry/materials, and provide helpers to create explosions for sprites. + +API +```ts +class PhysicsExplosionManager { + constructor(scene: THREE.Scene, physicsWorld: PhysicsWorld) + + fillPool(resource: SpriteResource, count: number, params?: Partial): void + + createExplosionForSprite(spriteToExplode: TiledSprite, userParams?: Partial): Promise + + update(deltaTimeMs: number): void + + disposeAll(): void +} +``` + +- `createExplosionForSprite` removes the supplied sprite, creates the physics particles and returns a handle with `restoreSprite(spriteAnimator)` to recreate the sprite when needed. +- `fillPool` preallocates pooled meshes for a given resource to reduce allocation overhead. + +## Physics adapter requirements (overview) + +The physics-driven effects rely on the project's physics adapter interface. The adapter must provide: +- PhysicsWorld: ability to `createRigidBody(desc)`, `createCollider(desc, body)`, `removeRigidBody(body)`, and `create`/`destroy` lifecycle. +- PhysicsRigidBody: must expose `applyImpulse`, `applyTorqueImpulse`, `getTranslation()` and `getRotation()` (rotation scalar for 2D), and similar. +- Physics types referenced in the code: PhysicsRigidBody, PhysicsWorld, PhysicsRigidBodyDesc, PhysicsColliderDesc, PhysicsVector2. + +See `packages/core/src/3d/physics/physics-interface.ts` for exact method names and required shapes. Available adapters: Planck and Rapier (PlanckPhysicsAdapter.ts and RapierPhysicsAdapter.ts). + +## Example usage (pseudocode) + +```ts +// Assuming scene and physicsWorld are initialized, and spriteAnimator exists +const manager = new PhysicsExplosionManager(scene, physicsWorld) +const handle = await manager.createExplosionForSprite(myTiledSprite, { explosionForce: 30, durationMs: 2500 }) +// Optionally restore later: +if (handle) { + await handle.restoreSprite(spriteAnimator) +} +``` + +## Recommendations and notes + +- Ensure physicsWorld is stepped/updated by your application loop (outside this class) so rigid bodies advance and `update(...)` reads correct transforms. +- Tune `explosionForce`, `forceVariation`, `torqueStrength`, and `density` per your scene scale and physics adapter units. +- Use `fillPool` to pre-warm mesh pools for frequently used configurations. + +--- + +Next steps: +- Document `SpriteAnimator.ts` and `SpriteParticleGenerator.ts` (animation & particle API). +- Document physics adapter implementations (Planck / Rapier) with configuration examples. diff --git a/packages/core/docs/api/3d/physics.md b/packages/core/docs/api/3d/physics.md new file mode 100644 index 000000000..f713bf29c --- /dev/null +++ b/packages/core/docs/api/3d/physics.md @@ -0,0 +1,454 @@ +# Physics Integration + +OpenTUI provides physics integration through adapters for popular physics engines, allowing you to create realistic physics simulations in your terminal applications. + +## Physics Interface + +The physics integration is built around a common interface that allows different physics engines to be used interchangeably. + +```typescript +import { PhysicsInterface } from '@opentui/core/3d'; + +// The physics interface defines common methods for working with physics engines +interface PhysicsInterface { + createWorld(options?: PhysicsWorldOptions): PhysicsWorld; + createStaticBody(options: BodyOptions): PhysicsBody; + createDynamicBody(options: BodyOptions): PhysicsBody; + createKinematicBody(options: BodyOptions): PhysicsBody; + update(deltaTime: number): void; + // ... other methods +} +``` + +## Physics Adapters + +OpenTUI provides adapters for two popular physics engines: + +### Planck.js Adapter + +[Planck.js](https://github.com/shakiba/planck.js/) is a JavaScript rewrite of the Box2D physics engine. + +```typescript +import { PlanckPhysicsAdapter } from '@opentui/core/3d'; + +// Create a Planck.js physics adapter +const physics = new PlanckPhysicsAdapter({ + gravity: { x: 0, y: 10 }, + scale: 30 // Pixels per meter +}); + +// Create a world +const world = physics.createWorld(); + +// Create a static ground body +const ground = physics.createStaticBody({ + position: { x: 50, y: 80 }, + shape: { + type: 'box', + width: 100, + height: 5 + } +}); + +// Create a dynamic box body +const box = physics.createDynamicBody({ + position: { x: 50, y: 10 }, + shape: { + type: 'box', + width: 5, + height: 5 + }, + restitution: 0.5, // Bounciness + friction: 0.2 +}); + +// Update the physics simulation +function update(deltaTime: number) { + physics.update(deltaTime); + + // Get the new position of the box + const position = box.getPosition(); + + // Update your renderable with the new position + boxRenderable.x = position.x - boxRenderable.width / 2; + boxRenderable.y = position.y - boxRenderable.height / 2; +} +``` + +### Rapier Adapter + +[Rapier](https://rapier.rs/) is a high-performance physics engine written in Rust with WebAssembly bindings. + +```typescript +import { RapierPhysicsAdapter } from '@opentui/core/3d'; + +// Create a Rapier physics adapter +const physics = new RapierPhysicsAdapter({ + gravity: { x: 0, y: 9.81 }, + scale: 30 // Pixels per meter +}); + +// Create a world +const world = physics.createWorld(); + +// Create a static ground body +const ground = physics.createStaticBody({ + position: { x: 50, y: 80 }, + shape: { + type: 'box', + width: 100, + height: 5 + } +}); + +// Create a dynamic circle body +const circle = physics.createDynamicBody({ + position: { x: 50, y: 10 }, + shape: { + type: 'circle', + radius: 3 + }, + restitution: 0.7, // Bounciness + friction: 0.1 +}); + +// Update the physics simulation +function update(deltaTime: number) { + physics.update(deltaTime); + + // Get the new position of the circle + const position = circle.getPosition(); + + // Update your renderable with the new position + circleRenderable.x = position.x - circleRenderable.width / 2; + circleRenderable.y = position.y - circleRenderable.height / 2; +} +``` + +## Physics Bodies + +Physics bodies represent physical objects in the simulation. There are three types of bodies: + +- **Static Bodies**: Don't move and are not affected by forces +- **Dynamic Bodies**: Move and are affected by forces +- **Kinematic Bodies**: Move but are not affected by forces (controlled programmatically) + +```typescript +// Create a static body (e.g., ground, walls) +const ground = physics.createStaticBody({ + position: { x: 50, y: 80 }, + shape: { + type: 'box', + width: 100, + height: 5 + } +}); + +// Create a dynamic body (e.g., player, objects) +const box = physics.createDynamicBody({ + position: { x: 50, y: 10 }, + shape: { + type: 'box', + width: 5, + height: 5 + }, + restitution: 0.5, + friction: 0.2 +}); + +// Create a kinematic body (e.g., moving platforms) +const platform = physics.createKinematicBody({ + position: { x: 30, y: 40 }, + shape: { + type: 'box', + width: 20, + height: 2 + } +}); + +// Move a kinematic body programmatically +platform.setLinearVelocity({ x: 1, y: 0 }); +``` + +## Shapes + +Physics bodies can have different shapes: + +- **Box**: Rectangular shape +- **Circle**: Circular shape +- **Polygon**: Custom polygon shape +- **Compound**: Multiple shapes combined + +```typescript +// Box shape +const box = physics.createDynamicBody({ + position: { x: 50, y: 10 }, + shape: { + type: 'box', + width: 5, + height: 5 + } +}); + +// Circle shape +const circle = physics.createDynamicBody({ + position: { x: 60, y: 10 }, + shape: { + type: 'circle', + radius: 3 + } +}); + +// Polygon shape +const polygon = physics.createDynamicBody({ + position: { x: 70, y: 10 }, + shape: { + type: 'polygon', + vertices: [ + { x: 0, y: 0 }, + { x: 5, y: 0 }, + { x: 2.5, y: 5 } + ] + } +}); +``` + +## Joints + +Joints connect bodies together and constrain their movement: + +- **Distance Joint**: Keeps bodies at a fixed distance +- **Revolute Joint**: Allows rotation around a point +- **Prismatic Joint**: Allows movement along an axis +- **Pulley Joint**: Connects bodies with a pulley system +- **Gear Joint**: Connects bodies with a gear ratio + +```typescript +// Create a revolute joint (hinge) +const joint = physics.createRevoluteJoint({ + bodyA: box1, + bodyB: box2, + anchorPoint: { x: 55, y: 10 }, + collideConnected: false +}); + +// Create a distance joint (spring) +const spring = physics.createDistanceJoint({ + bodyA: box1, + bodyB: box3, + length: 10, + frequency: 5, // Oscillation frequency + damping: 0.5, // Damping ratio + collideConnected: true +}); +``` + +## Collision Detection + +You can detect and respond to collisions between bodies: + +```typescript +// Set up collision callbacks +physics.onBeginContact((bodyA, bodyB) => { + console.log(`Collision started between ${bodyA.id} and ${bodyB.id}`); +}); + +physics.onEndContact((bodyA, bodyB) => { + console.log(`Collision ended between ${bodyA.id} and ${bodyB.id}`); +}); + +// Check if two bodies are in contact +const inContact = physics.areInContact(bodyA, bodyB); +``` + +## Example: Simple Physics Simulation + +Here's a complete example of a simple physics simulation: + +```typescript +import { createCliRenderer, BoxRenderable } from '@opentui/core'; +import { PlanckPhysicsAdapter } from '@opentui/core/3d'; + +async function createPhysicsDemo() { + // Create the renderer + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222' + }); + + root.add(container); + + // Create the physics adapter + const physics = new PlanckPhysicsAdapter({ + gravity: { x: 0, y: 10 }, + scale: 30 + }); + + // Create a world + const world = physics.createWorld(); + + // Create a ground body + const ground = physics.createStaticBody({ + position: { x: renderer.width / 2, y: renderer.height - 5 }, + shape: { + type: 'box', + width: renderer.width - 10, + height: 2 + } + }); + + // Create walls + const leftWall = physics.createStaticBody({ + position: { x: 2, y: renderer.height / 2 }, + shape: { + type: 'box', + width: 2, + height: renderer.height - 10 + } + }); + + const rightWall = physics.createStaticBody({ + position: { x: renderer.width - 2, y: renderer.height / 2 }, + shape: { + type: 'box', + width: 2, + height: renderer.height - 10 + } + }); + + // Create some dynamic bodies + const bodies = []; + const renderables = []; + + for (let i = 0; i < 10; i++) { + // Create a dynamic body + const body = physics.createDynamicBody({ + position: { + x: 10 + Math.random() * (renderer.width - 20), + y: 5 + Math.random() * 10 + }, + shape: { + type: Math.random() > 0.5 ? 'box' : 'circle', + width: 3 + Math.random() * 3, + height: 3 + Math.random() * 3, + radius: 2 + Math.random() * 2 + }, + restitution: 0.3 + Math.random() * 0.5, + friction: 0.1 + Math.random() * 0.3 + }); + + bodies.push(body); + + // Create a renderable for the body + const isBox = body.getShapeType() === 'box'; + const size = isBox ? body.getSize() : { width: body.getRadius() * 2, height: body.getRadius() * 2 }; + + const renderable = new BoxRenderable(`body${i}`, { + width: size.width, + height: size.height, + position: 'absolute', + x: body.getPosition().x - size.width / 2, + y: body.getPosition().y - size.height / 2, + borderStyle: isBox ? 'single' : 'rounded', + borderColor: '#e74c3c', + backgroundColor: 'transparent' + }); + + renderables.push(renderable); + container.add(renderable); + } + + // Create a renderable for the ground + const groundRenderable = new BoxRenderable('ground', { + width: renderer.width - 10, + height: 2, + position: 'absolute', + x: 5, + y: renderer.height - 5, + borderStyle: 'single', + borderColor: '#2ecc71', + backgroundColor: 'transparent' + }); + + container.add(groundRenderable); + + // Set up the update loop + renderer.setFrameCallback((deltaTime) => { + // Update physics + physics.update(deltaTime / 1000); // Convert to seconds + + // Update renderables + for (let i = 0; i < bodies.length; i++) { + const body = bodies[i]; + const renderable = renderables[i]; + const position = body.getPosition(); + const angle = body.getAngle(); + + renderable.x = position.x - renderable.width / 2; + renderable.y = position.y - renderable.height / 2; + + // TODO: Handle rotation when supported + } + }); + + // Start the renderer + renderer.start(); + + // Handle keyboard input + renderer.on('key', (key) => { + const keyStr = key.toString(); + + if (keyStr === ' ') { + // Add a new body on spacebar + const body = physics.createDynamicBody({ + position: { + x: 10 + Math.random() * (renderer.width - 20), + y: 5 + }, + shape: { + type: Math.random() > 0.5 ? 'box' : 'circle', + width: 3 + Math.random() * 3, + height: 3 + Math.random() * 3, + radius: 2 + Math.random() * 2 + }, + restitution: 0.3 + Math.random() * 0.5, + friction: 0.1 + Math.random() * 0.3 + }); + + bodies.push(body); + + const isBox = body.getShapeType() === 'box'; + const size = isBox ? body.getSize() : { width: body.getRadius() * 2, height: body.getRadius() * 2 }; + + const renderable = new BoxRenderable(`body${bodies.length}`, { + width: size.width, + height: size.height, + position: 'absolute', + x: body.getPosition().x - size.width / 2, + y: body.getPosition().y - size.height / 2, + borderStyle: isBox ? 'single' : 'rounded', + borderColor: '#e74c3c', + backgroundColor: 'transparent' + }); + + renderables.push(renderable); + container.add(renderable); + } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C + renderer.destroy(); + process.exit(0); + } + }); + + return renderer; +} + +// Create and run the physics demo +createPhysicsDemo().catch(console.error); +``` diff --git a/packages/core/docs/api/3d/shaders.md b/packages/core/docs/api/3d/shaders.md new file mode 100644 index 000000000..2e008ae68 --- /dev/null +++ b/packages/core/docs/api/3d/shaders.md @@ -0,0 +1,874 @@ +# WebGPU Shaders + +OpenTUI provides WebGPU integration for high-performance graphics rendering, including support for custom shaders. + +## WebGPU Renderer + +The `WGPURenderer` class provides a WebGPU rendering context for OpenTUI. + +```typescript +import { WGPURenderer } from '@opentui/core/3d'; + +// Create a WebGPU renderer +const gpuRenderer = new WGPURenderer({ + width: 800, + height: 600 +}); + +// Initialize the renderer +await gpuRenderer.initialize(); + +// Create a render pipeline +const pipeline = await gpuRenderer.createRenderPipeline({ + vertex: { + module: gpuRenderer.device.createShaderModule({ + code: vertexShaderCode + }), + entryPoint: 'main' + }, + fragment: { + module: gpuRenderer.device.createShaderModule({ + code: fragmentShaderCode + }), + entryPoint: 'main', + targets: [{ format: gpuRenderer.format }] + }, + primitive: { + topology: 'triangle-list' + } +}); + +// Render a frame +gpuRenderer.beginFrame(); +// ... rendering commands ... +gpuRenderer.endFrame(); + +// Destroy the renderer +gpuRenderer.destroy(); +``` + +## WGSL Shaders + +WebGPU uses the WebGPU Shading Language (WGSL) for writing shaders. Here's an example of a simple vertex and fragment shader: + +### Vertex Shader + +```wgsl +@vertex +fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4 { + var pos = array, 3>( + vec2(0.0, 0.5), + vec2(-0.5, -0.5), + vec2(0.5, -0.5) + ); + return vec4(pos[VertexIndex], 0.0, 1.0); +} +``` + +### Fragment Shader + +```wgsl +@fragment +fn main() -> @location(0) vec4 { + return vec4(1.0, 0.0, 0.0, 1.0); +} +``` + +## Supersampling Shader + +OpenTUI includes a supersampling shader for improving the quality of rendered graphics: + +```wgsl +// supersampling.wgsl + +@group(0) @binding(0) var inputTexture: texture_2d; +@group(0) @binding(1) var outputTexture: texture_storage_2d; +@group(0) @binding(2) var params: Params; + +struct Params { + width: u32, + height: u32, + sampleCount: u32, +} + +@compute @workgroup_size(16, 16) +fn main(@builtin(global_invocation_id) global_id: vec3) { + let x = global_id.x; + let y = global_id.y; + + if (x >= params.width || y >= params.height) { + return; + } + + var color = vec4(0.0, 0.0, 0.0, 0.0); + let sampleSize = 1.0 / f32(params.sampleCount); + + for (var i = 0u; i < params.sampleCount; i = i + 1u) { + for (var j = 0u; j < params.sampleCount; j = j + 1u) { + let offsetX = (f32(i) + 0.5) * sampleSize; + let offsetY = (f32(j) + 0.5) * sampleSize; + let sampleX = f32(x) + offsetX; + let sampleY = f32(y) + offsetY; + + color = color + textureLoad(inputTexture, vec2(sampleX, sampleY), 0); + } + } + + color = color / f32(params.sampleCount * params.sampleCount); + textureStore(outputTexture, vec2(x, y), color); +} +``` + +## Example: Fractal Shader + +Here's an example of creating a fractal shader with WebGPU: + +```typescript +import { createCliRenderer, BoxRenderable, OptimizedBuffer, RGBA } from '@opentui/core'; +import { WGPURenderer } from '@opentui/core/3d'; + +// Vertex shader +const vertexShaderCode = ` +@vertex +fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4 { + var pos = array, 6>( + vec2(-1.0, -1.0), + vec2(1.0, -1.0), + vec2(1.0, 1.0), + vec2(-1.0, -1.0), + vec2(1.0, 1.0), + vec2(-1.0, 1.0) + ); + return vec4(pos[VertexIndex], 0.0, 1.0); +} +`; + +// Fragment shader (Mandelbrot set) +const fragmentShaderCode = ` +@group(0) @binding(0) var params: Params; + +struct Params { + width: f32, + height: f32, + time: f32, + zoom: f32, + offsetX: f32, + offsetY: f32, +} + +@fragment +fn main(@builtin(position) fragCoord: vec4) -> @location(0) vec4 { + let aspect = params.width / params.height; + let uv = vec2(fragCoord.xy / vec2(params.width, params.height)); + + // Map to complex plane + let c = vec2( + (uv.x * 2.0 - 1.0) * aspect * params.zoom + params.offsetX, + (uv.y * 2.0 - 1.0) * params.zoom + params.offsetY + ); + + // Mandelbrot iteration + let maxIter = 100.0; + var z = vec2(0.0, 0.0); + var iter = 0.0; + + for (var i = 0.0; i < maxIter; i += 1.0) { + // z = z^2 + c + let real = z.x * z.x - z.y * z.y + c.x; + let imag = 2.0 * z.x * z.y + c.y; + z = vec2(real, imag); + + if (dot(z, z) > 4.0) { + iter = i; + break; + } + } + + // Coloring + if (iter >= maxIter) { + return vec4(0.0, 0.0, 0.0, 1.0); + } + + let t = iter / maxIter; + let hue = 360.0 * (0.5 + sin(params.time * 0.1) * 0.5) * t; + + // HSV to RGB conversion + let h = hue / 60.0; + let i = floor(h); + let f = h - i; + let p = 1.0 - t; + let q = 1.0 - (t * f); + let r = 1.0 - (t * (1.0 - f)); + + var rgb: vec3; + + if (i == 0.0) { + rgb = vec3(t, r, p); + } else if (i == 1.0) { + rgb = vec3(q, t, p); + } else if (i == 2.0) { + rgb = vec3(p, t, r); + } else if (i == 3.0) { + rgb = vec3(p, q, t); + } else if (i == 4.0) { + rgb = vec3(r, p, t); + } else { + rgb = vec3(t, p, q); + } + + return vec4(rgb, 1.0); +} +`; + +class FractalRenderable extends BoxRenderable { + private gpuRenderer: WGPURenderer; + private pipeline: GPURenderPipeline; + private bindGroup: GPUBindGroup; + private uniformBuffer: GPUBuffer; + private params: { + width: number; + height: number; + time: number; + zoom: number; + offsetX: number; + offsetY: number; + }; + + constructor(id: string, options = {}) { + super(id, { + width: '100%', + height: '100%', + border: false, + ...options + }); + + this.params = { + width: 0, + height: 0, + time: 0, + zoom: 1.5, + offsetX: -0.5, + offsetY: 0.0 + }; + + this.initWebGPU(); + } + + private async initWebGPU() { + // Create a WebGPU renderer + this.gpuRenderer = new WGPURenderer({ + width: this.width * 2, // Double resolution for better quality + height: this.height * 2 + }); + + // Initialize the renderer + await this.gpuRenderer.initialize(); + + // Update params + this.params.width = this.gpuRenderer.width; + this.params.height = this.gpuRenderer.height; + + // Create a uniform buffer + this.uniformBuffer = this.gpuRenderer.device.createBuffer({ + size: 6 * 4, // 6 floats (width, height, time, zoom, offsetX, offsetY) + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST + }); + + // Create a bind group layout + const bindGroupLayout = this.gpuRenderer.device.createBindGroupLayout({ + entries: [ + { + binding: 0, + visibility: GPUShaderStage.FRAGMENT, + buffer: { type: 'uniform' } + } + ] + }); + + // Create a pipeline layout + const pipelineLayout = this.gpuRenderer.device.createPipelineLayout({ + bindGroupLayouts: [bindGroupLayout] + }); + + // Create a render pipeline + this.pipeline = this.gpuRenderer.device.createRenderPipeline({ + layout: pipelineLayout, + vertex: { + module: this.gpuRenderer.device.createShaderModule({ + code: vertexShaderCode + }), + entryPoint: 'main' + }, + fragment: { + module: this.gpuRenderer.device.createShaderModule({ + code: fragmentShaderCode + }), + entryPoint: 'main', + targets: [{ format: this.gpuRenderer.format }] + }, + primitive: { + topology: 'triangle-list' + } + }); + + // Create a bind group + this.bindGroup = this.gpuRenderer.device.createBindGroup({ + layout: bindGroupLayout, + entries: [ + { + binding: 0, + resource: { buffer: this.uniformBuffer } + } + ] + }); + } + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + if (!this.gpuRenderer || !this.pipeline || !this.bindGroup) return; + + // Update time + this.params.time += deltaTime; + + // Update uniform buffer + this.gpuRenderer.device.queue.writeBuffer( + this.uniformBuffer, + 0, + new Float32Array([ + this.params.width, + this.params.height, + this.params.time, + this.params.zoom, + this.params.offsetX, + this.params.offsetY + ]) + ); + + // Begin frame + this.gpuRenderer.beginFrame(); + + // Get command encoder + const commandEncoder = this.gpuRenderer.device.createCommandEncoder(); + + // Begin render pass + const renderPass = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + view: this.gpuRenderer.context.getCurrentTexture().createView(), + loadOp: 'clear', + storeOp: 'store', + clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 } + } + ] + }); + + // Set pipeline and bind group + renderPass.setPipeline(this.pipeline); + renderPass.setBindGroup(0, this.bindGroup); + + // Draw + renderPass.draw(6); + + // End render pass + renderPass.end(); + + // Submit commands + this.gpuRenderer.device.queue.submit([commandEncoder.finish()]); + + // End frame + this.gpuRenderer.endFrame(); + + // Get the rendered image + const imageData = this.gpuRenderer.getImageData(); + + // Render to terminal + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + // Sample the WebGL output with proper scaling + const glX = Math.floor(x * (this.gpuRenderer.width / this.width)); + const glY = Math.floor(y * (this.gpuRenderer.height / this.height)); + + const idx = (glY * this.gpuRenderer.width + glX) * 4; + const r = imageData.data[idx] / 255; + const g = imageData.data[idx + 1] / 255; + const b = imageData.data[idx + 2] / 255; + const a = imageData.data[idx + 3] / 255; + + // Apply brightness-based character selection for better visibility + const brightness = 0.299 * r + 0.587 * g + 0.114 * b; + const character = brightness > 0.8 ? '█' : + brightness > 0.6 ? '▓' : + brightness > 0.4 ? '▒' : + brightness > 0.2 ? '░' : ' '; + + // Draw the pixel with appropriate character and color + buffer.setCell( + this.x + x, + this.y + y, + character, + RGBA.fromValues(r, g, b, a), + RGBA.fromValues(0, 0, 0, 1) + ); + } + } + } + + // Control methods + public setZoom(zoom: number): void { + this.params.zoom = zoom; + } + + public setOffset(x: number, y: number): void { + this.params.offsetX = x; + this.params.offsetY = y; + } + + public destroy(): void { + if (this.gpuRenderer) { + this.gpuRenderer.destroy(); + } + } +} + +async function createFractalDemo() { + // Create the renderer + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222' + }); + + root.add(container); + + // Create a fractal renderable + const fractal = new FractalRenderable('fractal'); + container.add(fractal); + + // Add instructions + const instructions = new TextRenderable('instructions', { + content: 'Arrow keys: Move | +/-: Zoom | Q: Quit', + fg: '#ffffff', + position: 'absolute', + x: 2, + y: 1 + }); + + container.add(instructions); + + // Handle keyboard input + renderer.on('key', (key) => { + const keyStr = key.toString(); + + if (keyStr === 'ArrowRight') { + fractal.setOffset(fractal.params.offsetX + 0.1 * fractal.params.zoom, fractal.params.offsetY); + } else if (keyStr === 'ArrowLeft') { + fractal.setOffset(fractal.params.offsetX - 0.1 * fractal.params.zoom, fractal.params.offsetY); + } else if (keyStr === 'ArrowUp') { + fractal.setOffset(fractal.params.offsetX, fractal.params.offsetY - 0.1 * fractal.params.zoom); + } else if (keyStr === 'ArrowDown') { + fractal.setOffset(fractal.params.offsetX, fractal.params.offsetY + 0.1 * fractal.params.zoom); + } else if (keyStr === '+') { + fractal.setZoom(fractal.params.zoom * 0.8); + } else if (keyStr === '-') { + fractal.setZoom(fractal.params.zoom * 1.25); + } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C + fractal.destroy(); + renderer.destroy(); + process.exit(0); + } + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the fractal demo +createFractalDemo().catch(console.error); +``` + +## Example: Cube Shader + +Here's an example of rendering a 3D cube with WebGPU: + +```typescript +import { createCliRenderer, BoxRenderable, OptimizedBuffer, RGBA } from '@opentui/core'; +import { WGPURenderer } from '@opentui/core/3d'; + +// Vertex shader +const vertexShaderCode = ` +struct Uniforms { + modelViewProjection: mat4x4, + time: f32, +}; + +@group(0) @binding(0) var uniforms: Uniforms; + +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) color: vec4, +}; + +@vertex +fn main( + @location(0) position: vec3, + @location(1) color: vec4 +) -> VertexOutput { + var output: VertexOutput; + output.position = uniforms.modelViewProjection * vec4(position, 1.0); + output.color = color; + return output; +} +`; + +// Fragment shader +const fragmentShaderCode = ` +@fragment +fn main(@location(0) color: vec4) -> @location(0) vec4 { + return color; +} +`; + +class CubeRenderable extends BoxRenderable { + private gpuRenderer: WGPURenderer; + private pipeline: GPURenderPipeline; + private bindGroup: GPUBindGroup; + private uniformBuffer: GPUBuffer; + private vertexBuffer: GPUBuffer; + private indexBuffer: GPUBuffer; + private time: number = 0; + + constructor(id: string, options = {}) { + super(id, { + width: '100%', + height: '100%', + border: false, + ...options + }); + + this.initWebGPU(); + } + + private async initWebGPU() { + // Create a WebGPU renderer + this.gpuRenderer = new WGPURenderer({ + width: this.width * 2, // Double resolution for better quality + height: this.height * 2 + }); + + // Initialize the renderer + await this.gpuRenderer.initialize(); + + // Create vertex data for a cube + const vertices = new Float32Array([ + // Position (xyz), Color (rgba) + // Front face + -1.0, -1.0, 1.0, 1.0, 0.0, 0.0, 1.0, + 1.0, -1.0, 1.0, 0.0, 1.0, 0.0, 1.0, + 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, + -1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, + + // Back face + -1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 1.0, + 1.0, -1.0, -1.0, 1.0, 0.0, 1.0, 1.0, + 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, + -1.0, 1.0, -1.0, 0.0, 0.0, 0.0, 1.0, + ]); + + // Create index data for a cube + const indices = new Uint16Array([ + // Front face + 0, 1, 2, 0, 2, 3, + // Back face + 4, 5, 6, 4, 6, 7, + // Top face + 3, 2, 6, 3, 6, 7, + // Bottom face + 0, 1, 5, 0, 5, 4, + // Right face + 1, 2, 6, 1, 6, 5, + // Left face + 0, 3, 7, 0, 7, 4 + ]); + + // Create a vertex buffer + this.vertexBuffer = this.gpuRenderer.device.createBuffer({ + size: vertices.byteLength, + usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST + }); + + // Create an index buffer + this.indexBuffer = this.gpuRenderer.device.createBuffer({ + size: indices.byteLength, + usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST + }); + + // Create a uniform buffer + this.uniformBuffer = this.gpuRenderer.device.createBuffer({ + size: 4 * 16 + 4, // mat4x4 + float + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST + }); + + // Write data to buffers + this.gpuRenderer.device.queue.writeBuffer(this.vertexBuffer, 0, vertices); + this.gpuRenderer.device.queue.writeBuffer(this.indexBuffer, 0, indices); + + // Create a bind group layout + const bindGroupLayout = this.gpuRenderer.device.createBindGroupLayout({ + entries: [ + { + binding: 0, + visibility: GPUShaderStage.VERTEX, + buffer: { type: 'uniform' } + } + ] + }); + + // Create a pipeline layout + const pipelineLayout = this.gpuRenderer.device.createPipelineLayout({ + bindGroupLayouts: [bindGroupLayout] + }); + + // Create a render pipeline + this.pipeline = this.gpuRenderer.device.createRenderPipeline({ + layout: pipelineLayout, + vertex: { + module: this.gpuRenderer.device.createShaderModule({ + code: vertexShaderCode + }), + entryPoint: 'main', + buffers: [ + { + arrayStride: 7 * 4, // 7 floats per vertex + attributes: [ + { + // Position + shaderLocation: 0, + offset: 0, + format: 'float32x3' + }, + { + // Color + shaderLocation: 1, + offset: 3 * 4, + format: 'float32x4' + } + ] + } + ] + }, + fragment: { + module: this.gpuRenderer.device.createShaderModule({ + code: fragmentShaderCode + }), + entryPoint: 'main', + targets: [{ format: this.gpuRenderer.format }] + }, + primitive: { + topology: 'triangle-list', + cullMode: 'back' + }, + depthStencil: { + depthWriteEnabled: true, + depthCompare: 'less', + format: 'depth24plus' + } + }); + + // Create a bind group + this.bindGroup = this.gpuRenderer.device.createBindGroup({ + layout: bindGroupLayout, + entries: [ + { + binding: 0, + resource: { buffer: this.uniformBuffer } + } + ] + }); + } + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + if (!this.gpuRenderer || !this.pipeline || !this.bindGroup) return; + + // Update time + this.time += deltaTime; + + // Create model-view-projection matrix + const aspect = this.gpuRenderer.width / this.gpuRenderer.height; + const projectionMatrix = mat4.perspective( + mat4.create(), + Math.PI / 4, + aspect, + 0.1, + 100.0 + ); + + const viewMatrix = mat4.lookAt( + mat4.create(), + [0, 0, 5], // Camera position + [0, 0, 0], // Look at + [0, 1, 0] // Up vector + ); + + const modelMatrix = mat4.create(); + mat4.rotateY(modelMatrix, modelMatrix, this.time * 0.001); + mat4.rotateX(modelMatrix, modelMatrix, this.time * 0.0007); + + const modelViewProjection = mat4.create(); + mat4.multiply(modelViewProjection, viewMatrix, modelMatrix); + mat4.multiply(modelViewProjection, projectionMatrix, modelViewProjection); + + // Update uniform buffer + const uniformData = new Float32Array(16 + 1); + uniformData.set(modelViewProjection, 0); + uniformData[16] = this.time; + + this.gpuRenderer.device.queue.writeBuffer( + this.uniformBuffer, + 0, + uniformData + ); + + // Begin frame + this.gpuRenderer.beginFrame(); + + // Get command encoder + const commandEncoder = this.gpuRenderer.device.createCommandEncoder(); + + // Begin render pass + const renderPass = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + view: this.gpuRenderer.context.getCurrentTexture().createView(), + loadOp: 'clear', + storeOp: 'store', + clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 } + } + ], + depthStencilAttachment: { + view: this.gpuRenderer.depthTextureView, + depthLoadOp: 'clear', + depthStoreOp: 'store', + depthClearValue: 1.0 + } + }); + + // Set pipeline and bind group + renderPass.setPipeline(this.pipeline); + renderPass.setBindGroup(0, this.bindGroup); + + // Set vertex and index buffers + renderPass.setVertexBuffer(0, this.vertexBuffer); + renderPass.setIndexBuffer(this.indexBuffer, 'uint16'); + + // Draw + renderPass.drawIndexed(36); + + // End render pass + renderPass.end(); + + // Submit commands + this.gpuRenderer.device.queue.submit([commandEncoder.finish()]); + + // End frame + this.gpuRenderer.endFrame(); + + // Get the rendered image + const imageData = this.gpuRenderer.getImageData(); + + // Render to terminal + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + // Sample the WebGL output with proper scaling + const glX = Math.floor(x * (this.gpuRenderer.width / this.width)); + const glY = Math.floor(y * (this.gpuRenderer.height / this.height)); + + const idx = (glY * this.gpuRenderer.width + glX) * 4; + const r = imageData.data[idx] / 255; + const g = imageData.data[idx + 1] / 255; + const b = imageData.data[idx + 2] / 255; + const a = imageData.data[idx + 3] / 255; + + // Apply brightness-based character selection for better visibility + const brightness = 0.299 * r + 0.587 * g + 0.114 * b; + const character = brightness > 0.8 ? '█' : + brightness > 0.6 ? '▓' : + brightness > 0.4 ? '▒' : + brightness > 0.2 ? '░' : ' '; + + // Draw the pixel with appropriate character and color + buffer.setCell( + this.x + x, + this.y + y, + character, + RGBA.fromValues(r, g, b, a), + RGBA.fromValues(0, 0, 0, 1) + ); + } + } + } + + public destroy(): void { + if (this.gpuRenderer) { + this.gpuRenderer.destroy(); + } + } +} + +async function createCubeDemo() { + // Create the renderer + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222' + }); + + root.add(container); + + // Create a cube renderable + const cube = new CubeRenderable('cube'); + container.add(cube); + + // Add instructions + const instructions = new TextRenderable('instructions', { + content: 'Q: Quit', + fg: '#ffffff', + position: 'absolute', + x: 2, + y: 1 + }); + + container.add(instructions); + + // Handle keyboard input + renderer.on('key', (key) => { + const keyStr = key.toString(); + + if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C + cube.destroy(); + renderer.destroy(); + process.exit(0); + } + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the cube demo +createCubeDemo().catch(console.error); +``` diff --git a/packages/core/docs/api/3d/sprite-animation.md b/packages/core/docs/api/3d/sprite-animation.md new file mode 100644 index 000000000..0c39460fc --- /dev/null +++ b/packages/core/docs/api/3d/sprite-animation.md @@ -0,0 +1,518 @@ +# Sprite Animation + +OpenTUI provides powerful sprite animation capabilities for creating dynamic and interactive terminal user interfaces. + +## Sprite Resource Manager + +The `SpriteResourceManager` class is responsible for loading, managing, and releasing sprite resources. + +```typescript +import { SpriteResourceManager } from '@opentui/core/3d'; +import { Scene } from 'three'; + +// Create a scene +const scene = new Scene(); + +// Create a sprite resource manager +const spriteManager = new SpriteResourceManager(scene); + +// Create a sprite resource +const spriteResource = await spriteManager.createResource({ + imagePath: 'path/to/sprite.png', + sheetNumFrames: 8 +}); + +// Clear the cache +spriteManager.clearCache(); +``` + +## Sprite Animator + +The `SpriteAnimator` class provides functionality for animating sprites with frame-based animations. + +```typescript +import { SpriteAnimator, SpriteDefinition } from '@opentui/core/3d'; +import { Scene } from 'three'; + +// Create a scene +const scene = new Scene(); + +// Create a sprite animator +const animator = new SpriteAnimator(scene); + +// Define a sprite with animations +const spriteDefinition: SpriteDefinition = { + initialAnimation: 'idle', + scale: 1.0, + maxInstances: 100, + animations: { + idle: { + resource: spriteResource, + animNumFrames: 4, + animFrameOffset: 0, + frameDuration: 100, + loop: true + }, + run: { + resource: spriteResource, + animNumFrames: 6, + animFrameOffset: 4, + frameDuration: 80, + loop: true + } + } +}; + +// Create a sprite instance +const sprite = await animator.createSprite('player', spriteDefinition); + +// Play the animation +sprite.play(); + +// Stop the animation +sprite.stop(); + +// Go to a specific frame +sprite.goToFrame(2); + +// Change animation +await sprite.setAnimation('run'); + +// Check if animation is playing +const isPlaying = sprite.isPlaying(); + +// Set animation speed by changing frame duration +sprite.setFrameDuration(50); // faster animation + +// Update animations (call in animation loop) +animator.update(deltaTime); +``` + +## Sprite Animation Component + +Here's an example of creating a custom component for sprite animation: + +```typescript +import { Renderable, OptimizedBuffer, RGBA } from '@opentui/core'; +import { SpriteAnimator, SpriteResourceManager } from '@opentui/core/3d'; + +interface SpriteRenderableOptions { + width?: number; + height?: number; + spriteSheet: string; + frameWidth: number; + frameHeight: number; + frameCount: number; + frameDuration?: number; + loop?: boolean; +} + +class SpriteRenderable extends Renderable { + private animator: SpriteAnimator; + + constructor(id: string, options: SpriteRenderableOptions) { + super(id, { + width: options.width ?? options.frameWidth, + height: options.height ?? options.frameHeight, + position: 'absolute', + ...options + }); + + // Load the sprite sheet + const spriteManager = new SpriteResourceManager(); + spriteManager.loadSprite(options.spriteSheet).then(sprite => { + // Create the animator + this.animator = new SpriteAnimator({ + spriteSheet: sprite, + frameWidth: options.frameWidth, + frameHeight: options.frameHeight, + frameCount: options.frameCount, + frameDuration: options.frameDuration ?? 100, + loop: options.loop ?? true + }); + + // Start the animation + this.animator.play(); + }); + } + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + if (!this.animator) return; + + // Update the animation + this.animator.update(deltaTime); + + // Get the current frame + const frame = this.animator.getCurrentFrame(); + + // Render the sprite with proper scaling + if (frame) { + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + // Sample the sprite pixel with bilinear interpolation for smoother scaling + const pixelX = Math.floor(x * (frame.width / this.width)); + const pixelY = Math.floor(y * (frame.height / this.height)); + + // Get pixel color from the sprite frame + const idx = (pixelY * frame.width + pixelX) * 4; + const r = frame.data[idx] / 255; + const g = frame.data[idx + 1] / 255; + const b = frame.data[idx + 2] / 255; + const a = frame.data[idx + 3] / 255; + + if (a > 0.5) { + // Draw the pixel + buffer.setCell( + this.x + x, + this.y + y, + ' ', + RGBA.fromValues(0, 0, 0, 0), + RGBA.fromValues(r, g, b, a) + ); + } + } + } + } + } + + // Control methods + public play(): void { + if (this.animator) this.animator.play(); + } + + public pause(): void { + if (this.animator) this.animator.pause(); + } + + public stop(): void { + if (this.animator) this.animator.stop(); + } + + public setFrame(frame: number): void { + if (this.animator) this.animator.setFrame(frame); + } + + public setSpeed(speed: number): void { + if (this.animator) this.animator.setSpeed(speed); + } +} +``` + +## Sprite Particle Effects + +OpenTUI provides classes for creating particle effects using sprites. + +### Exploding Sprite Effect + +The `ExplodingSpriteEffect` class creates an explosion effect from a sprite. + +```typescript +import { ExplodingSpriteEffect } from '@opentui/core/3d'; + +// Create an exploding sprite effect +const explosion = new ExplodingSpriteEffect({ + sprite: sprite, + position: { x: 50, y: 50 }, + particleCount: 50, + minSpeed: 10, + maxSpeed: 30, + minLifetime: 500, + maxLifetime: 1500, + gravity: { x: 0, y: 9.8 }, + fadeOut: true +}); + +// Start the effect +explosion.start(); + +// Update the effect +explosion.update(deltaTime); + +// Render the effect +explosion.render(buffer); + +// Check if the effect is complete +const isComplete = explosion.isComplete(); +``` + +### Physics-Based Exploding Sprite Effect + +The `PhysicsExplodingSpriteEffect` class creates a physics-based explosion effect. + +```typescript +import { PhysicsExplodingSpriteEffect } from '@opentui/core/3d'; +import { PlanckPhysicsAdapter } from '@opentui/core/3d'; + +// Create a physics adapter +const physics = new PlanckPhysicsAdapter({ + gravity: { x: 0, y: 9.8 }, + scale: 30 +}); + +// Create a physics-based exploding sprite effect +const explosion = new PhysicsExplodingSpriteEffect({ + sprite: sprite, + position: { x: 50, y: 50 }, + particleCount: 50, + minImpulse: 1, + maxImpulse: 5, + minAngularVelocity: -5, + maxAngularVelocity: 5, + minLifetime: 500, + maxLifetime: 1500, + physics: physics, + fadeOut: true +}); + +// Start the effect +explosion.start(); + +// Update the effect +explosion.update(deltaTime); + +// Render the effect +explosion.render(buffer); + +// Check if the effect is complete +const isComplete = explosion.isComplete(); +``` + +### Sprite Particle Generator + +The `SpriteParticleGenerator` class provides a more general-purpose particle system. + +```typescript +import { SpriteParticleGenerator } from '@opentui/core/3d'; + +// Create a sprite particle generator +const particleGenerator = new SpriteParticleGenerator({ + sprite: sprite, + position: { x: 50, y: 50 }, + emissionRate: 10, // particles per second + minSpeed: 10, + maxSpeed: 30, + minDirection: 0, + maxDirection: Math.PI * 2, + minLifetime: 500, + maxLifetime: 1500, + minScale: 0.5, + maxScale: 1.5, + gravity: { x: 0, y: 9.8 }, + fadeOut: true +}); + +// Start the generator +particleGenerator.start(); + +// Stop the generator +particleGenerator.stop(); + +// Update the generator +particleGenerator.update(deltaTime); + +// Render the particles +particleGenerator.render(buffer); + +// Set the position +particleGenerator.setPosition({ x: 60, y: 40 }); + +// Set the emission rate +particleGenerator.setEmissionRate(20); +``` + +## Example: Character Animation + +Here's a complete example of a character animation: + +```typescript +import { createCliRenderer, BoxRenderable } from '@opentui/core'; +import { SpriteAnimator, SpriteResourceManager } from '@opentui/core/3d'; + +class CharacterRenderable extends BoxRenderable { + private spriteManager: SpriteResourceManager; + private idleAnimator: SpriteAnimator; + private runAnimator: SpriteAnimator; + private jumpAnimator: SpriteAnimator; + private currentAnimator: SpriteAnimator; + private state: 'idle' | 'run' | 'jump' = 'idle'; + + constructor(id: string, options = {}) { + super(id, { + width: 16, + height: 24, + position: 'absolute', + x: 50, + y: 50, + border: false, + ...options + }); + + this.spriteManager = new SpriteResourceManager(); + this.loadAnimations(); + } + + private async loadAnimations() { + // Load sprite sheets + const idleSprite = await this.spriteManager.loadSprite('src/examples/assets/main_char_idle.png'); + const runSprite = await this.spriteManager.loadSprite('src/examples/assets/main_char_run_loop.png'); + const jumpSprite = await this.spriteManager.loadSprite('src/examples/assets/main_char_jump_start.png'); + + // Create animators + this.idleAnimator = new SpriteAnimator({ + spriteSheet: idleSprite, + frameWidth: 16, + frameHeight: 24, + frameCount: 4, + frameDuration: 200, + loop: true + }); + + this.runAnimator = new SpriteAnimator({ + spriteSheet: runSprite, + frameWidth: 16, + frameHeight: 24, + frameCount: 6, + frameDuration: 100, + loop: true + }); + + this.jumpAnimator = new SpriteAnimator({ + spriteSheet: jumpSprite, + frameWidth: 16, + frameHeight: 24, + frameCount: 3, + frameDuration: 150, + loop: false + }); + + // Set the current animator to idle + this.currentAnimator = this.idleAnimator; + this.currentAnimator.play(); + } + + public setState(state: 'idle' | 'run' | 'jump') { + if (this.state === state) return; + + this.state = state; + + // Stop the current animator + if (this.currentAnimator) { + this.currentAnimator.stop(); + } + + // Set the new animator + switch (state) { + case 'idle': + this.currentAnimator = this.idleAnimator; + break; + case 'run': + this.currentAnimator = this.runAnimator; + break; + case 'jump': + this.currentAnimator = this.jumpAnimator; + break; + } + + // Start the new animator + if (this.currentAnimator) { + this.currentAnimator.play(); + } + } + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + if (!this.currentAnimator) return; + + // Update the animation + this.currentAnimator.update(deltaTime); + + // Get the current frame + const frame = this.currentAnimator.getCurrentFrame(); + + // Render the sprite + if (frame) { + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + // Sample the sprite pixel + const pixelX = Math.floor(x * (frame.width / this.width)); + const pixelY = Math.floor(y * (frame.height / this.height)); + + // Get pixel color from the sprite frame + const idx = (pixelY * frame.width + pixelX) * 4; + const r = frame.data[idx] / 255; + const g = frame.data[idx + 1] / 255; + const b = frame.data[idx + 2] / 255; + const a = frame.data[idx + 3] / 255; + + if (a > 0.5) { + // Draw the pixel + buffer.setCell( + this.x + x, + this.y + y, + ' ', + RGBA.fromValues(0, 0, 0, 0), + RGBA.fromValues(r, g, b, a) + ); + } + } + } + } + + // If jump animation is complete, go back to idle + if (this.state === 'jump' && !this.currentAnimator.isPlaying()) { + this.setState('idle'); + } + } +} + +async function createCharacterAnimationDemo() { + // Create the renderer + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222' + }); + + root.add(container); + + // Create a character + const character = new CharacterRenderable('character'); + container.add(character); + + // Handle keyboard input + renderer.on('key', (key) => { + const keyStr = key.toString(); + + if (keyStr === 'ArrowRight' || keyStr === 'ArrowLeft') { + character.setState('run'); + + // Move the character + if (keyStr === 'ArrowRight') { + character.x += 1; + } else { + character.x -= 1; + } + } else if (keyStr === 'ArrowUp' || keyStr === ' ') { + character.setState('jump'); + } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C + renderer.destroy(); + process.exit(0); + } else { + character.setState('idle'); + } + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the character animation demo +createCharacterAnimationDemo().catch(console.error); +``` diff --git a/packages/core/docs/api/3d/sprite-animator.md b/packages/core/docs/api/3d/sprite-animator.md new file mode 100644 index 000000000..6c0e820e1 --- /dev/null +++ b/packages/core/docs/api/3d/sprite-animator.md @@ -0,0 +1,115 @@ +I think sp# SpriteAnimator, TiledSprite, and Animation (3D Sprite Animation API) + +This page documents the sprite animation system implemented in `packages/core/src/3d/animation/SpriteAnimator.ts`. It covers the Animation helper, TiledSprite instance, and the SpriteAnimator manager that creates and updates animated, instanced sprites. + +## Key concepts + +- Sprite sheets are represented by `SpriteResource` objects (see sprites.md). +- Animations are defined per-sprite with a mapping of animation names to `AnimationDefinition` objects (number of frames, offsets, frame duration, looping, flip flags). +- The animator uses instanced rendering (InstancedMesh) with per-instance attributes for frame index and flip flags, allowing many sprites to be drawn efficiently. + +## Types / Interfaces + +- `AnimationStateConfig` — config provided for a specific animation (imagePath, sheetNumFrames, animNumFrames, animFrameOffset, frameDuration, loop, initialFrame, flipX, flipY) +- `ResolvedAnimationState` — resolved state with texture and tileset sizes +- `SpriteDefinition` — top-level sprite definition: `initialAnimation` and `animations` map +- `SpriteDefinition` example: + ```ts + const spriteDef: SpriteDefinition = { + initialAnimation: "idle", + animations: { + idle: { resource, animNumFrames: 4, frameDuration: 100, loop: true }, + run: { resource, animNumFrames: 6, frameDuration: 75, loop: true }, + }, + scale: 1.0 + } + ``` + +## Class: Animation (internal per-sprite animation instance) + +Used internally by `TiledSprite`. Main responsibilities: +- Track `currentLocalFrame`, `timeAccumulator`, `isPlaying`, `_isActive` +- Manage per-instance attributes: + - `a_frameIndexInstanced` (frame index per instance) + - `a_flipInstanced` (flipX / flipY per instance) +- Methods: + - `activate(worldTransform: Matrix4)` — enable and place the instance + - `deactivate()` — hide and stop updates + - `updateVisuals(worldTransform: Matrix4)` — update instance matrix transform + - `updateTime(deltaTimeMs: number): boolean` — advance frames based on `frameDuration`; returns true if frame attribute updated + - `play()`, `stop()`, `goToFrame()`, `setFrameDuration()`, `getResource()`, `releaseInstanceSlot()` + +Notes: +- Frame attributes are updated by setting `frameAttribute.setX(instanceIndex, absoluteFrame)` and marking `needsUpdate = true`. + +## Class: TiledSprite + +Represents a single logical sprite (which may contain multiple instanced animations internally, e.g., frames from different sprite sheets). + +Constructor: +```ts +new TiledSprite( + id: string, + userSpriteDefinition: SpriteDefinition, + animator: SpriteAnimator, + animationInstanceParams: Array<{ name, state, resource, index, instanceManager, frameAttribute, flipAttribute }> +) +``` + +Public API: +- `play()`, `stop()`, `goToFrame(frame)`, `setFrameDuration(ms)` +- `setPosition(Vector3)`, `setRotation(Quaternion)`, `setScale(Vector3)` and `setTransform(position, quaternion, scale)` +- `setAnimation(animationName: string): Promise` — switch animation (activates/deactivates instance slots accordingly) +- `update(deltaTime: number)` — called by animator to advance animation timing +- `destroy()` — release instance slots and cleanup +- `visible` getter/setter — toggles per-instance activation (hiding / showing) +- Accessors: `getCurrentAnimationName()`, `getWorldTransform()`, `getWorldPlaneSize()`, `definition`, `currentTransform` + +Notes: +- `TiledSprite` computes instance matrix scale based on sprite definition scale and sheet aspect ratio to preserve pixel aspect. +- The class stores a transform object used to compute world matrix for the instanced mesh. + +## Class: SpriteAnimator + +Manager that creates TiledSprite instances and manages instance pools. + +Constructor: +```ts +new SpriteAnimator(scene: THREE.Scene) +``` + +Primary methods: +- `async createSprite(userSpriteDefinition: SpriteDefinition, materialFactory?: () => NodeMaterial): Promise` + - Resolves resources and instance managers for each animation resource, acquires instance slots, creates per-instance attributes, constructs `TiledSprite`. +- `update(deltaTime: number): void` — calls `update` on each TiledSprite (advance time) +- `removeSprite(id: string)`: void — destroy and free instance slot(s) +- `removeAllSprites()`: void + +Instance manager caching: +- `getOrCreateInstanceManager(resource, maxInstances, renderOrder, depthWrite, materialFactory)` + - Builds `geometry` with instanced attributes: + - `a_frameIndexInstanced` (Float32Array, 1 element per instance) + - `a_flipInstanced` (Float32Array, 2 elements per instance) + - Creates material via `createSpriteAnimationMaterial(...)` which builds a NodeMaterial using the resource texture and per-instance attributes. + +Material and shader notes: +- The material uses three/tsl nodes to compute final UV based on per-instance frame index and flip flags. +- `createSpriteAnimationMaterial` packs the per-instance attributes and sets `material.colorNode` to the sampled texture color. + +Example usage: +```ts +const animator = new SpriteAnimator(scene) +const mySprite = await animator.createSprite(spriteDefinition) +animator.update(deltaTimeMs) // in your loop +mySprite.setPosition(new THREE.Vector3(1,2,0)) +mySprite.setAnimation('run') +``` + +Performance tips: +- Choose `maxInstances` sufficiently large when creating sprite definitions to avoid `acquireInstanceSlot` failures. +- Materials are created per resource + options combination and reused via instance manager caching. +- Use `removeSprite` to free instance slots when sprites are no longer needed. + +Next steps: +- Document `SpriteParticleGenerator.ts` (particle spawning helper) and then finalize 3D animation docs. + diff --git a/packages/core/docs/api/3d/sprites.md b/packages/core/docs/api/3d/sprites.md new file mode 100644 index 000000000..595425390 --- /dev/null +++ b/packages/core/docs/api/3d/sprites.md @@ -0,0 +1,192 @@ +# 3D Sprite Subsystem + +This page documents the sprite-related utilities used by the 3D renderer: mesh pooling, instanced sprite management, sprite resources, and the SpriteResourceManager. Implementation references: `packages/core/src/3d/SpriteResourceManager.ts` and `packages/core/src/3d/TextureUtils.ts`. + +Primary exported classes: +- MeshPool +- InstanceManager +- SpriteResource +- SpriteResourceManager + +--- + +## MeshPool + +Purpose: reuse InstancedMesh objects to avoid repeated allocation/dispose during dynamic scenes. + +API +```ts +class MeshPool { + acquireMesh(poolId: string, options: { + geometry: () => THREE.BufferGeometry + material: THREE.Material + maxInstances: number + name?: string + }): THREE.InstancedMesh + + releaseMesh(poolId: string, mesh: THREE.InstancedMesh): void + + fill(poolId: string, options: MeshPoolOptions, count: number): void + + clearPool(poolId: string): void + + clearAllPools(): void +} +``` + +Notes: +- `acquireMesh` returns an existing pooled InstancedMesh if available or creates a new one. +- `releaseMesh` returns a mesh into the pool for later reuse. +- `fill` preallocates `count` meshes for a pool. +- `clearPool` disposes geometry and materials of meshes in the pool. +- `clearAllPools` clears every pool. + +Example: +```ts +const pool = new MeshPool() + +const mesh = pool.acquireMesh('sprites', { + geometry: () => new THREE.PlaneGeometry(1, 1), + material: spriteMaterial, + maxInstances: 100, + name: 'spriteMesh' +}) + +// use mesh in scene... +pool.releaseMesh('sprites', mesh) +``` + +--- + +## InstanceManager + +Purpose: manage a single `THREE.InstancedMesh` and provide slot allocation for instances (acquire/release per-instance transforms). + +API +```ts +class InstanceManager { + constructor(scene: Scene, geometry: THREE.BufferGeometry, material: THREE.Material, options: { + maxInstances: number + renderOrder?: number + depthWrite?: boolean + name?: string + frustumCulled?: boolean + matrix?: THREE.Matrix4 + }) + + acquireInstanceSlot(): number + releaseInstanceSlot(instanceIndex: number): void + getInstanceCount(): number + getMaxInstances(): number + get hasFreeIndices(): boolean + get mesh(): THREE.InstancedMesh + dispose(): void +} +``` + +Behavior: +- Constructor creates an `InstancedMesh` with `maxInstances` capacity and registers it on `scene`. +- `acquireInstanceSlot()` returns an available instance index; throws if none available. +- `releaseInstanceSlot()` marks the index free and resets the instance transform to a hidden matrix. +- `mesh` returns the underlying InstancedMesh for adding instance-specific attributes (colors/UVs) or custom settings. +- `dispose()` removes the mesh from the scene and disposes geometry/material. + +Example: +```ts +const manager = new InstanceManager(scene, new THREE.PlaneGeometry(1,1), spriteMaterial, { maxInstances: 100, name: 'sprites' }) +const idx = manager.acquireInstanceSlot() + +// set transform +const mat = new THREE.Matrix4().makeTranslation(x, y, z) +manager.mesh.setMatrixAt(idx, mat) +manager.mesh.instanceMatrix.needsUpdate = true + +// later +manager.releaseInstanceSlot(idx) +``` + +--- + +## SpriteResource + +Purpose: represent a loaded sprite sheet texture and provide a `MeshPool` and helpers for instance managers. + +API +```ts +class SpriteResource { + constructor(texture: THREE.DataTexture, sheetProperties: { + imagePath: string + sheetTilesetWidth: number + sheetTilesetHeight: number + sheetNumFrames: number + }, scene: Scene) + + get texture(): THREE.DataTexture + get sheetProperties(): SheetProperties + get meshPool(): MeshPool + + createInstanceManager(geometry: THREE.BufferGeometry, material: THREE.Material, options: InstanceManagerOptions): InstanceManager + + get uvTileSize(): THREE.Vector2 + + dispose(): void +} +``` + +Notes: +- `uvTileSize` returns the normalized tile size for UV mapping based on `sheetNumFrames`. +- `createInstanceManager` is a convenience to create an `InstanceManager` bound to this resource and scene. +- `dispose` clears the internal mesh pools. + +Example: +```ts +const tex = await TextureUtils.fromFile('spritesheet.png') +const sheet = { imagePath: 'spritesheet.png', sheetTilesetWidth: tex.image.width, sheetTilesetHeight: tex.image.height, sheetNumFrames: 8 } +const resource = new SpriteResource(tex, sheet, scene) +const manager = resource.createInstanceManager(new THREE.PlaneGeometry(1,1), spriteMaterial, { maxInstances: 200 }) +``` + +--- + +## SpriteResourceManager + +Purpose: central manager to create/load sprite sheet textures (via TextureUtils), cache them, and provide `SpriteResource` objects. + +API +```ts +class SpriteResourceManager { + constructor(scene: Scene) + + getOrCreateResource(texture: THREE.DataTexture, sheetProps: SheetProperties): Promise + + createResource(config: { imagePath: string, sheetNumFrames: number }): Promise + + clearCache(): void +} +``` + +Behavior: +- `createResource` loads texture via `TextureUtils.fromFile(imagePath)` and builds `SheetProperties` from the loaded texture dimensions and `sheetNumFrames`. +- Resources and raw texture objects are cached by `imagePath` key. +- `clearCache` clears both resource and texture caches. + +Example: +```ts +const manager = new SpriteResourceManager(scene) +const resource = await manager.createResource({ imagePath: 'spritesheet.png', sheetNumFrames: 8 }) +const instanceManager = resource.createInstanceManager(geometry, material, { maxInstances: 100 }) +``` + +--- + +## TextureUtils (note) + +The manager uses `TextureUtils.fromFile(path)` to load a `THREE.DataTexture`. Refer to `packages/core/src/3d/TextureUtils.ts` for exact signature and supported file formats. + +--- + +## Recommendations + +- Use `SpriteResourceManager` to centralize loading of sprite atlases and reuse textures across scenes. +- Use `MeshPool` and `InstanceManager` for high-performance instanced rendering — they avoid frequent allocation and GPU buffer churn. +- When using sprite sheets, compute UV offsets using `SpriteResource.uvTileSize` and set instance UV attributes accordingly. diff --git a/packages/core/docs/api/3d/webgpu.md b/packages/core/docs/api/3d/webgpu.md new file mode 100644 index 000000000..03f6601cd --- /dev/null +++ b/packages/core/docs/api/3d/webgpu.md @@ -0,0 +1,452 @@ +# WebGPU Integration + +OpenTUI provides powerful 3D rendering capabilities through WebGPU integration, allowing you to create rich visual experiences in the terminal. + +## Overview + +The WebGPU integration consists of: + +1. **ThreeCliRenderer**: A renderer that integrates Three.js with WebGPU +2. **CLICanvas**: A canvas implementation for rendering to the terminal +3. **Supersampling**: Techniques for improving rendering quality +4. **Shaders**: Custom WebGPU shaders for visual effects + +## ThreeCliRenderer API + +The `ThreeCliRenderer` class provides a bridge between Three.js and the terminal: + +```typescript +import { + ThreeCliRenderer, + ThreeCliRendererOptions, + SuperSampleType, + createCliRenderer, + Scene, + PerspectiveCamera, + OrthographicCamera, + RGBA +} from '@opentui/core'; + +// Create a CLI renderer +const renderer = await createCliRenderer(); + +// Create a Three.js scene +const scene = new Scene(); + +// Create a ThreeCliRenderer +const threeRenderer = new ThreeCliRenderer(renderer, { + width: 80, + height: 40, + focalLength: 50, + backgroundColor: RGBA.fromHex('#000000'), + superSample: SuperSampleType.GPU, + alpha: false, + autoResize: true +}); + +// Initialize the renderer +await threeRenderer.init(); + +// Set the active camera +const camera = new PerspectiveCamera(75, threeRenderer.aspectRatio, 0.1, 1000); +camera.position.set(0, 0, 5); +camera.lookAt(0, 0, 0); +threeRenderer.setActiveCamera(camera); + +// Draw the scene +renderer.on('update', async (context) => { + await threeRenderer.drawScene(scene, renderer.buffer, context.deltaTime); +}); + +// Start the renderer +renderer.start(); +``` + +### Renderer Options + +The `ThreeCliRenderer` constructor accepts the following options: + +```typescript +interface ThreeCliRendererOptions { + width: number; // Output width in characters + height: number; // Output height in characters + focalLength?: number; // Camera focal length + backgroundColor?: RGBA; // Background color + superSample?: SuperSampleType; // Supersampling type + alpha?: boolean; // Enable alpha blending + autoResize?: boolean; // Automatically resize on terminal resize + libPath?: string; // Path to WebGPU library +} +``` + +### Supersampling + +The renderer supports three supersampling modes to improve rendering quality: + +```typescript +enum SuperSampleType { + NONE = "none", // No supersampling + GPU = "gpu", // GPU-based supersampling + CPU = "cpu" // CPU-based supersampling +} +``` + +You can toggle between supersampling modes: + +```typescript +// Toggle between supersampling modes +threeRenderer.toggleSuperSampling(); + +// Set a specific supersampling algorithm +threeRenderer.setSuperSampleAlgorithm(SuperSampleAlgorithm.PRE_SQUEEZED); +``` + +### Camera Control + +You can set and get the active camera: + +```typescript +// Set the active camera +threeRenderer.setActiveCamera(camera); + +// Get the active camera +const activeCamera = threeRenderer.getActiveCamera(); +``` + +### Resizing + +You can resize the renderer: + +```typescript +// Resize the renderer +threeRenderer.setSize(100, 50); +``` + +### Saving to File + +You can save the rendered scene to a file: + +```typescript +// Save the current frame to a file +await threeRenderer.saveToFile('screenshot.png'); +``` + +### Cleanup + +When you're done with the renderer, you should destroy it to free resources: + +```typescript +// Destroy the renderer +threeRenderer.destroy(); +``` + +## CLICanvas API + +The `CLICanvas` class provides a canvas implementation for rendering to the terminal: + +```typescript +import { CLICanvas, SuperSampleAlgorithm, SuperSampleType } from '@opentui/core'; + +// Create a canvas (typically done by ThreeCliRenderer) +const canvas = new CLICanvas( + device, // WebGPU device + width, // Canvas width + height, // Canvas height + SuperSampleType.GPU, + SuperSampleAlgorithm.STANDARD +); + +// Set the canvas size +canvas.setSize(width, height); + +// Set the supersampling mode +canvas.setSuperSample(SuperSampleType.GPU); + +// Set the supersampling algorithm +canvas.setSuperSampleAlgorithm(SuperSampleAlgorithm.PRE_SQUEEZED); + +// Read pixels into a buffer +await canvas.readPixelsIntoBuffer(buffer); + +// Save the canvas to a file +await canvas.saveToFile('screenshot.png'); +``` + +## Supersampling Algorithms + +OpenTUI supports two supersampling algorithms: + +```typescript +enum SuperSampleAlgorithm { + STANDARD = 0, // Standard supersampling + PRE_SQUEEZED = 1 // Pre-squeezed supersampling (better for text) +} +``` + +## Integration with Three.js + +The WebGPU integration works with Three.js to provide a familiar API for 3D rendering: + +```typescript +import { + Scene, + Mesh, + BoxGeometry, + MeshPhongNodeMaterial, + DirectionalLight, + Color +} from '@opentui/core'; + +// Create a scene +const scene = new Scene(); + +// Add a light +const light = new DirectionalLight(0xffffff, 1); +light.position.set(1, 1, 1); +scene.add(light); + +// Create a mesh +const geometry = new BoxGeometry(1, 1, 1); +const material = new MeshPhongNodeMaterial({ + color: new Color(0x3498db), + emissive: new Color(0x000000), + specular: new Color(0x111111), + shininess: 30 +}); +const cube = new Mesh(geometry, material); +scene.add(cube); + +// Animate the cube +renderer.on('update', (context) => { + cube.rotation.x += 0.01; + cube.rotation.y += 0.01; +}); +``` + +## WebGPU Shaders + +OpenTUI supports custom WebGPU shaders for advanced visual effects: + +```typescript +import { + Scene, + Mesh, + BoxGeometry, + ShaderMaterial, + WebGPURenderer +} from '@opentui/core'; + +// Create a shader material +const material = new ShaderMaterial({ + vertexShader: ` + @vertex + fn main(@location(0) position: vec3) -> @builtin(position) vec4 { + return vec4(position, 1.0); + } + `, + fragmentShader: ` + @fragment + fn main() -> @location(0) vec4 { + return vec4(1.0, 0.0, 0.0, 1.0); + } + ` +}); + +// Create a mesh with the shader material +const geometry = new BoxGeometry(1, 1, 1); +const cube = new Mesh(geometry, material); +scene.add(cube); +``` + +## Performance Considerations + +The WebGPU integration is designed for performance, but there are some considerations: + +- **Supersampling**: Supersampling improves quality but reduces performance +- **Resolution**: Higher resolutions require more GPU memory and processing power +- **Complexity**: Complex scenes with many objects will be slower +- **Shaders**: Custom shaders can be expensive, especially with complex calculations + +For best performance: + +- Use appropriate resolution for your terminal +- Use supersampling only when needed +- Optimize your Three.js scene (reduce polygon count, use efficient materials) +- Use GPU-based supersampling when possible + +## Example: Creating a 3D Cube + +```typescript +import { + createCliRenderer, + ThreeCliRenderer, + SuperSampleType, + Scene, + PerspectiveCamera, + BoxGeometry, + Mesh, + MeshPhongNodeMaterial, + DirectionalLight, + Color, + RGBA +} from '@opentui/core'; + +async function createCubeDemo() { + // Create a CLI renderer + const renderer = await createCliRenderer(); + + // Create a Three.js scene + const scene = new Scene(); + + // Create a ThreeCliRenderer + const threeRenderer = new ThreeCliRenderer(renderer, { + width: 80, + height: 40, + backgroundColor: RGBA.fromHex('#000000'), + superSample: SuperSampleType.GPU + }); + + // Initialize the renderer + await threeRenderer.init(); + + // Create a camera + const camera = new PerspectiveCamera(75, threeRenderer.aspectRatio, 0.1, 1000); + camera.position.set(0, 0, 5); + camera.lookAt(0, 0, 0); + threeRenderer.setActiveCamera(camera); + + // Add a light + const light = new DirectionalLight(0xffffff, 1); + light.position.set(1, 1, 1); + scene.add(light); + + // Create a cube + const geometry = new BoxGeometry(2, 2, 2); + const material = new MeshPhongNodeMaterial({ + color: new Color(0x3498db), + emissive: new Color(0x000000), + specular: new Color(0x111111), + shininess: 30 + }); + const cube = new Mesh(geometry, material); + scene.add(cube); + + // Animate the cube + renderer.on('update', async (context) => { + cube.rotation.x += 0.01; + cube.rotation.y += 0.01; + + await threeRenderer.drawScene(scene, renderer.buffer, context.deltaTime); + }); + + // Handle keyboard input + renderer.on('key', (data) => { + const key = data.toString(); + + if (key === 's') { + threeRenderer.toggleSuperSampling(); + } else if (key === 'q') { + renderer.destroy(); + process.exit(0); + } + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the cube demo +createCubeDemo().catch(console.error); +``` + +## Example: Creating a Shader Effect + +```typescript +import { + createCliRenderer, + ThreeCliRenderer, + SuperSampleType, + Scene, + PerspectiveCamera, + PlaneGeometry, + Mesh, + ShaderMaterial, + RGBA +} from '@opentui/core'; + +async function createShaderDemo() { + // Create a CLI renderer + const renderer = await createCliRenderer(); + + // Create a Three.js scene + const scene = new Scene(); + + // Create a ThreeCliRenderer + const threeRenderer = new ThreeCliRenderer(renderer, { + width: 80, + height: 40, + backgroundColor: RGBA.fromHex('#000000'), + superSample: SuperSampleType.GPU + }); + + // Initialize the renderer + await threeRenderer.init(); + + // Create a camera + const camera = new PerspectiveCamera(75, threeRenderer.aspectRatio, 0.1, 1000); + camera.position.set(0, 0, 5); + camera.lookAt(0, 0, 0); + threeRenderer.setActiveCamera(camera); + + // Create a shader material + const material = new ShaderMaterial({ + vertexShader: ` + @vertex + fn main(@location(0) position: vec3, + @location(1) uv: vec2) -> @builtin(position) vec4 { + return vec4(position, 1.0); + } + `, + fragmentShader: ` + @group(0) @binding(0) var time: f32; + + @fragment + fn main(@location(0) uv: vec2) -> @location(0) vec4 { + let color = vec3( + sin(uv.x * 10.0 + time) * 0.5 + 0.5, + sin(uv.y * 10.0 + time * 0.5) * 0.5 + 0.5, + sin((uv.x + uv.y) * 5.0 + time * 0.2) * 0.5 + 0.5 + ); + return vec4(color, 1.0); + } + `, + uniforms: { + time: { value: 0 } + } + }); + + // Create a plane with the shader material + const geometry = new PlaneGeometry(4, 4); + const plane = new Mesh(geometry, material); + scene.add(plane); + + // Animate the shader + let time = 0; + renderer.on('update', async (context) => { + time += context.deltaTime; + material.uniforms.time.value = time; + + await threeRenderer.drawScene(scene, renderer.buffer, context.deltaTime); + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the shader demo +createShaderDemo().catch(console.error); +``` diff --git a/packages/core/docs/api/3d/wgpu-renderer.md b/packages/core/docs/api/3d/wgpu-renderer.md new file mode 100644 index 000000000..fd7ca0ea1 --- /dev/null +++ b/packages/core/docs/api/3d/wgpu-renderer.md @@ -0,0 +1,161 @@ +# WGPURenderer / ThreeCliRenderer (3D Renderer API) + +This page documents the ThreeCliRenderer class which integrates Three.js WebGPU rendering with OpenTUI's CLI renderer. Implementation reference: `packages/core/src/3d/WGPURenderer.ts`. + +ThreeCliRenderer renders a Three.js `Scene` to a GPU-backed canvas (CLICanvas) and copies the resulting pixels into an OpenTUI `OptimizedBuffer`. It supports optional supersampling (CPU or GPU) and exposes render statistics. + +## Key types + +- Scene — three.js scene to render +- PerspectiveCamera / OrthographicCamera — three.js cameras +- OptimizedBuffer — OpenTUI framebuffer (see buffer.md) +- SuperSampleType — enum: NONE | GPU | CPU +- SuperSampleAlgorithm — defined in `canvas` module + +## SuperSampleType + +```ts +export enum SuperSampleType { + NONE = "none", + GPU = "gpu", + CPU = "cpu", +} +``` + +Controls supersampling mode used by the renderer. + +## Interface: ThreeCliRendererOptions + +```ts +export interface ThreeCliRendererOptions { + width: number + height: number + focalLength?: number + backgroundColor?: RGBA + superSample?: SuperSampleType + alpha?: boolean + autoResize?: boolean + libPath?: string +} +``` + +- width/height: output terminal cell dimensions +- focalLength: optional camera focal length (used to compute FOV) +- backgroundColor: RGBA background color for clear +- superSample: initial supersampling mode (NONE/CPU/GPU) +- alpha: whether to use alpha in clear color +- autoResize: if true (default), ThreeCliRenderer listens to CliRenderer resize events +- libPath: optional native lib path passed to setupGlobals + +## Class: ThreeCliRenderer + +### Constructor + +```ts +new ThreeCliRenderer(cliRenderer: CliRenderer, options: ThreeCliRendererOptions) +``` + +- `cliRenderer` — OpenTUI CliRenderer instance used to receive resize/debug events and to integrate lifecycle. +- `options` — see ThreeCliRendererOptions. + +### Lifecycle + +- async init(): Promise + - Creates a WebGPU device, constructs a `CLICanvas` and a `WebGPURenderer` (three.js). + - Initializes three renderer and sets render method to internal draw function. + - Should be called before use. + +- destroy(): void + - Removes event listeners, disposes the three renderer, releases GPU references; resets internal state. + +### Rendering + +- async drawScene(root: Scene, buffer: OptimizedBuffer, deltaTime: number): Promise + - Public entry: draws the provided scene into the provided OptimizedBuffer. Internally calls `renderMethod` which is set to either `doDrawScene` or a no-op depending on initialization. + +- private async doDrawScene(root, camera, buffer, deltaTime): Promise + - Internal implementation that: + 1. Calls `threeRenderer.render(root, camera)` + 2. Calls `canvas.readPixelsIntoBuffer(buffer)` to transfer GPU pixels into the OptimizedBuffer + 3. Measures render/readback timings (renderTimeMs, readbackTimeMs, totalDrawTimeMs) + - It guards against concurrent calls (logs and returns if called concurrently). + +### Cameras and viewport + +- setActiveCamera(camera: PerspectiveCamera | OrthographicCamera): void +- getActiveCamera(): PerspectiveCamera | OrthographicCamera +- get aspectRatio(): number + - Computes the aspect ratio based on configured aspect override, renderer resolution, or terminal dimensions. + +- setSize(width: number, height: number, forceUpdate: boolean = false): void + - Updates output size, recomputes renderWidth/renderHeight (accounts for supersampling), resizes the CLICanvas and three renderer, and updates camera.aspect and projection matrix. + +### Supersampling control & stats + +- toggleSuperSampling(): void + - Cycles between NONE -> CPU -> GPU -> NONE and updates canvas state & sizes. + +- setSuperSampleAlgorithm(superSampleAlgorithm: SuperSampleAlgorithm): void +- getSuperSampleAlgorithm(): SuperSampleAlgorithm + +- saveToFile(filePath: string): Promise + - Proxy to canvas.saveToFile(filePath) to write a screenshot. + +- toggleDebugStats(): void + - Toggle internal flag to show render stats overlay. + +- renderStats(buffer: OptimizedBuffer): void + - Writes a small debug overlay of timing stats into the provided OptimizedBuffer using `buffer.drawText(...)`. + +### Performance notes + +- `ThreeCliRenderer` measures: + - `renderTimeMs` — time to run threeRenderer.render + - `readbackTimeMs` — time to transfer pixels to the OptimizedBuffer + - `canvas.mapAsyncTimeMs` — time to map GPU readback buffer + - `canvas.superSampleDrawTimeMs` — time spent converting supersampled output to framebuffer +- When `doRenderStats` is enabled (debug overlay), `renderStats` writes formatted timing lines into the buffer. + +### Event integration + +- By default (`autoResize !== false`) the renderer registers a handler on `cliRenderer` resize events to call `setSize(...)`. +- It listens for `CliRenderEvents.DEBUG_OVERLAY_TOGGLE` to update debug stat visibility. + +## Example usage + +```ts +import { createCliRenderer } from '@opentui/core' +import { ThreeCliRenderer, SuperSampleType } from '@opentui/core/3d/WGPURenderer' // pseudo import +import { Scene, PerspectiveCamera } from 'three' + +async function main() { + const cli = await createCliRenderer() + const threeRenderer = new ThreeCliRenderer(cli, { + width: 80, + height: 24, + superSample: SuperSampleType.GPU, + alpha: false + }) + + await threeRenderer.init() + + const scene = new Scene() + const camera = threeRenderer.getActiveCamera() as PerspectiveCamera + + // On each frame, create or reuse an OptimizedBuffer and render: + const buffer = cli.root.getBuffer() // pseudocode — use actual renderer buffer access + await threeRenderer.drawScene(scene, buffer, 16) +} +``` + +## Integration notes + +- `ThreeCliRenderer` relies on `CLICanvas` to handle readback and supersampling. See `packages/core/docs/api/3d/canvas.md`. +- Use GPU supersampling (GPU) for best quality and performance when a GPU is available; CPU supersampling is a fallback. +- The `WebGPURenderer` (three.js) expects an HTMLCanvas-like object — `CLICanvas` provides a `GPUCanvasContextMock` for use in non-browser environments. + +--- + +Next steps I can take: +- Document the sprite subsystem (SpriteResourceManager, SpriteUtils, animations) and the physics adapters (Planck and Rapier). +- Create an examples page showing a minimal three.js scene wired to ThreeCliRenderer. diff --git a/packages/core/docs/api/README.md b/packages/core/docs/api/README.md new file mode 100644 index 000000000..71d297723 --- /dev/null +++ b/packages/core/docs/api/README.md @@ -0,0 +1,121 @@ +# OpenTUI API Reference + +OpenTUI is a TypeScript library for building rich terminal user interfaces with support for layouts, animations, 3D graphics, and interactive components. + +## Core Modules + +### Renderer +- [`CliRenderer`](./renderer.md) - Main terminal renderer class +- [`createCliRenderer`](./renderer.md#createclirenderer) - Factory function to create renderer instances + +### Components +- [`Renderable`](./renderable.md) - Base class for all UI components +- [`BoxRenderable`](./components/box.md) - Container with borders and background +- [`TextRenderable`](./components/text.md) - Text display component +- [`GroupRenderable`](./components/group.md) - Layout container for child components +- [`Input`](./components/input.md) - Text input field +- [`Select`](./components/select.md) - Selection list component +- [`TabSelect`](./components/tab-select.md) - Tab-based selection +- [`FrameBuffer`](./components/framebuffer.md) - Offscreen rendering buffer +- [`ASCIIFont`](./components/ascii-font.md) - ASCII art text rendering + +### Layout System +- [Yoga Layout Integration](./layout.md) - Flexbox-based layout system +- [Position Types](./layout.md#position-types) - Absolute, relative positioning +- [Flex Properties](./layout.md#flex-properties) - Flexbox configuration + +### Styling +- [`StyledText`](./styled-text.md) - Rich text formatting +- [`RGBA`](./colors.md) - Color management +- [Border Styles](./borders.md) - Border configuration + +### Input Handling +- [`KeyHandler`](./input/keys.md) - Keyboard input management +- [`MouseEvent`](./input/mouse.md) - Mouse interaction handling +- [`Selection`](./input/selection.md) - Text selection utilities + +### Animation +- [`Timeline`](./animation/timeline.md) - Animation sequencing +- [Easing Functions](./animation/easing.md) - Animation curves + +### Buffers +- [`OptimizedBuffer`](./buffers.md#optimizedbuffer) - High-performance terminal buffer +- [`TextBuffer`](./buffers.md#textbuffer) - Text rendering buffer + +### 3D Graphics (Optional) +- [`WGPURenderer`](./3d/webgpu.md) - WebGPU-based 3D rendering +- [Sprite System](./3d/sprites.md) - 2D sprites in 3D space +- [Physics Integration](./3d/physics.md) - 2D physics engines + +### Utilities +- [`parseColor`](./utils.md#parsecolor) - Color parsing utility +- [`ANSI`](./utils.md#ansi) - ANSI escape code helpers +- [Console Capture](./utils.md#console) - Console output management + +## Quick Start + +```typescript +import { createCliRenderer, BoxRenderable, TextRenderable } from '@opentui/core' + +// Create renderer +const renderer = await createCliRenderer({ + stdout: process.stdout, + stdin: process.stdin, + useMouse: true, + useAlternateScreen: true +}) + +// Create UI components +const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + borderStyle: 'rounded', + backgroundColor: '#1a1a1a' +}) + +const title = new TextRenderable('title', { + content: 'Welcome to OpenTUI', + fg: '#00ff00', + marginTop: 1, + marginLeft: 2 +}) + +// Build UI hierarchy +container.appendChild(title) +renderer.root.appendChild(container) + +// Handle input +renderer.on('keypress', (key) => { + if (key.name === 'q') { + renderer.cleanup() + process.exit(0) + } +}) + +// Start rendering +renderer.start() +``` + +## Installation + +```bash +bun add @opentui/core +# or +npm install @opentui/core +``` + +## Requirements + +- Bun >= 1.2.0 or Node.js >= 18 +- TypeScript >= 5.0 +- Terminal with ANSI color support +- Optional: GPU support for 3D features + +## Next Steps + +- [Getting Started Guide](../guides/getting-started.md) +- [Component Examples](../examples/README.md) +- [Layout Tutorial](../guides/layouts.md) +- [Animation Guide](../guides/animations.md) + +api version 0.1.7 2025-08-19 wip diff --git a/packages/core/docs/api/advanced/3d.md b/packages/core/docs/api/advanced/3d.md new file mode 100644 index 000000000..0b5ddf995 --- /dev/null +++ b/packages/core/docs/api/advanced/3d.md @@ -0,0 +1,854 @@ +# 3D Rendering API + +OpenTUI provides advanced 3D rendering capabilities through integration with Three.js and WebGPU, allowing you to create rich visual experiences in the terminal. + +## Three.js Integration + +OpenTUI integrates with Three.js to provide 3D rendering capabilities in the terminal. + +### Setting Up Three.js + +```typescript +import { createCliRenderer, BoxRenderable } from '@opentui/core'; +import * as THREE from 'three'; + +async function setup3DScene() { + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container for the 3D scene + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + border: false + }); + + root.add(container); + + // Create a Three.js scene + const scene = new THREE.Scene(); + + // Create a camera + const camera = new THREE.PerspectiveCamera( + 75, // Field of view + renderer.width / renderer.height, // Aspect ratio + 0.1, // Near clipping plane + 1000 // Far clipping plane + ); + camera.position.z = 5; + + // Create a renderer + const threeRenderer = new THREE.WebGLRenderer({ alpha: true }); + threeRenderer.setSize(renderer.width, renderer.height); + + // Create a cube + const geometry = new THREE.BoxGeometry(); + const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true }); + const cube = new THREE.Mesh(geometry, material); + scene.add(cube); + + // Animation loop + renderer.setFrameCallback(async (deltaTime) => { + // Rotate the cube + cube.rotation.x += 0.01; + cube.rotation.y += 0.01; + + // Render the scene + threeRenderer.render(scene, camera); + + // Get the rendered image data + const imageData = threeRenderer.domElement.getContext('2d').getImageData( + 0, 0, renderer.width, renderer.height + ); + + // Convert the 3D rendering to terminal representation + for (let y = 0; y < container.height; y++) { + for (let x = 0; x < container.width; x++) { + // Sample the WebGL output (with proper scaling) + const glX = Math.floor(x * (threeRenderer.domElement.width / container.width)); + const glY = Math.floor(y * (threeRenderer.domElement.height / container.height)); + + const idx = (glY * threeRenderer.domElement.width + glX) * 4; + const r = imageData.data[idx] / 255; + const g = imageData.data[idx + 1] / 255; + const b = imageData.data[idx + 2] / 255; + const a = imageData.data[idx + 3] / 255; + + if (a > 0.1) { + // Draw the pixel to the terminal buffer + container.buffer.setCell( + container.x + x, + container.y + y, + ' ', // Use space character with background color + RGBA.fromValues(0, 0, 0, 0), // Transparent foreground + RGBA.fromValues(r, g, b, a) // Background color from the 3D scene + ); + } + } + } + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the 3D scene +setup3DScene().catch(console.error); +``` + +## WebGPU Integration + +OpenTUI provides WebGPU integration for high-performance graphics rendering. + +### WGPURenderer + +The `WGPURenderer` class provides a WebGPU-based renderer for OpenTUI. + +```typescript +import { WGPURenderer } from '@opentui/core/3d'; + +// Create a WebGPU renderer +const gpuRenderer = new WGPURenderer({ + width: 80, + height: 40, + device: null, // Will be initialized automatically + format: null // Will be initialized automatically +}); + +// Initialize the renderer +await gpuRenderer.initialize(); + +// Create a render pipeline +const pipeline = await gpuRenderer.createRenderPipeline({ + vertex: { + module: device.createShaderModule({ + code: vertexShaderCode + }), + entryPoint: 'main' + }, + fragment: { + module: device.createShaderModule({ + code: fragmentShaderCode + }), + entryPoint: 'main', + targets: [{ format: gpuRenderer.format }] + }, + primitive: { + topology: 'triangle-list' + } +}); + +// Render a frame +gpuRenderer.beginFrame(); +// ... rendering commands ... +gpuRenderer.endFrame(); + +// Get the rendered image +const imageData = gpuRenderer.getImageData(); +``` + +### Example: WebGPU Shader + +```typescript +import { createCliRenderer, BoxRenderable, OptimizedBuffer, RGBA } from '@opentui/core'; +import { WGPURenderer } from '@opentui/core/3d'; + +// Vertex shader +const vertexShaderCode = ` +@vertex +fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4 { + var pos = array, 3>( + vec2(0.0, 0.5), + vec2(-0.5, -0.5), + vec2(0.5, -0.5) + ); + return vec4(pos[VertexIndex], 0.0, 1.0); +} +`; + +// Fragment shader +const fragmentShaderCode = ` +@fragment +fn main() -> @location(0) vec4 { + return vec4(1.0, 0.0, 0.0, 1.0); +} +`; + +async function createShaderDemo() { + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + border: false + }); + + root.add(container); + + // Create a WebGPU renderer + const gpuRenderer = new WGPURenderer({ + width: renderer.width * 2, // Double resolution for better quality + height: renderer.height * 2 + }); + + // Initialize the renderer + await gpuRenderer.initialize(); + + // Create a render pipeline + const pipeline = await gpuRenderer.createRenderPipeline({ + vertex: { + module: gpuRenderer.device.createShaderModule({ + code: vertexShaderCode + }), + entryPoint: 'main' + }, + fragment: { + module: gpuRenderer.device.createShaderModule({ + code: fragmentShaderCode + }), + entryPoint: 'main', + targets: [{ format: gpuRenderer.format }] + }, + primitive: { + topology: 'triangle-list' + } + }); + + // Create a custom renderable for the shader + class ShaderRenderable extends BoxRenderable { + private gpuRenderer: WGPURenderer; + private pipeline: GPURenderPipeline; + + constructor(id: string, gpuRenderer: WGPURenderer, pipeline: GPURenderPipeline, options = {}) { + super(id, { + width: '100%', + height: '100%', + border: false, + ...options + }); + + this.gpuRenderer = gpuRenderer; + this.pipeline = pipeline; + } + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + // Render with WebGPU + this.gpuRenderer.beginFrame(); + + const passEncoder = this.gpuRenderer.commandEncoder.beginRenderPass({ + colorAttachments: [{ + view: this.gpuRenderer.textureView, + clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, + loadOp: 'clear', + storeOp: 'store' + }] + }); + + passEncoder.setPipeline(this.pipeline); + passEncoder.draw(3); // Draw a triangle + passEncoder.end(); + + this.gpuRenderer.endFrame(); + + // Get the rendered image + const imageData = this.gpuRenderer.getImageData(); + + // Convert to terminal representation + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + // Sample the WebGPU output (with proper scaling) + const gpuX = Math.floor(x * (this.gpuRenderer.width / this.width)); + const gpuY = Math.floor(y * (this.gpuRenderer.height / this.height)); + + const idx = (gpuY * this.gpuRenderer.width + gpuX) * 4; + const r = imageData.data[idx] / 255; + const g = imageData.data[idx + 1] / 255; + const b = imageData.data[idx + 2] / 255; + const a = imageData.data[idx + 3] / 255; + + if (a > 0.1) { + // Draw the pixel + buffer.setCell( + this.x + x, + this.y + y, + ' ', // Use space character with background color + RGBA.fromValues(0, 0, 0, 0), + RGBA.fromValues(r, g, b, a) + ); + } + } + } + } + } + + // Create the shader renderable + const shaderView = new ShaderRenderable('shader', gpuRenderer, pipeline); + container.add(shaderView); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the shader demo +createShaderDemo().catch(console.error); +``` + +## Sprite Rendering + +OpenTUI provides sprite rendering capabilities for displaying images in the terminal. + +### SpriteResourceManager + +The `SpriteResourceManager` class manages sprite resources for efficient rendering. + +```typescript +import { SpriteResourceManager } from '@opentui/core/3d'; +import Jimp from 'jimp'; + +// Create a sprite resource manager +const spriteManager = new SpriteResourceManager(); + +// Load a sprite +const sprite = await spriteManager.loadSprite('path/to/sprite.png'); + +// Load a sprite from a Jimp image +const jimpImage = await Jimp.read('path/to/another_sprite.png'); +const spriteFromJimp = await spriteManager.loadSpriteFromJimp(jimpImage); + +// Get a sprite by ID +const cachedSprite = spriteManager.getSprite('sprite_id'); + +// Release a sprite +spriteManager.releaseSprite('sprite_id'); + +// Clear all sprites +spriteManager.clear(); +``` + +### Example: Rendering Sprites + +```typescript +import { createCliRenderer, BoxRenderable, OptimizedBuffer, RGBA } from '@opentui/core'; +import { SpriteResourceManager } from '@opentui/core/3d'; + +async function createSpriteDemo() { + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + border: false, + backgroundColor: '#222222' + }); + + root.add(container); + + // Create a sprite resource manager + const spriteManager = new SpriteResourceManager(); + + // Load sprites + const characterSprite = await spriteManager.loadSprite('path/to/character.png'); + const backgroundSprite = await spriteManager.loadSprite('path/to/background.png'); + + // Create a custom renderable for sprites + class SpriteRenderable extends BoxRenderable { + private sprite: any; + private scale: number; + + constructor(id: string, sprite: any, options = {}) { + super(id, { + width: sprite.width, + height: sprite.height / 2, // Terminal characters are roughly 2:1 ratio + border: false, + position: 'absolute', + ...options + }); + + this.sprite = sprite; + this.scale = options.scale || 1; + } + + protected renderSelf(buffer: OptimizedBuffer): void { + // Render the sprite + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + // Sample the sprite pixel + const spriteX = Math.floor(x / this.scale); + const spriteY = Math.floor(y / this.scale * 2); // Adjust for terminal aspect ratio + + if (spriteX < this.sprite.width && spriteY < this.sprite.height) { + // Get pixel color from the sprite + const idx = (spriteY * this.sprite.width + spriteX) * 4; + const r = this.sprite.data[idx] / 255; + const g = this.sprite.data[idx + 1] / 255; + const b = this.sprite.data[idx + 2] / 255; + const a = this.sprite.data[idx + 3] / 255; + + if (a > 0.5) { + // Draw the pixel + buffer.setCell( + this.x + x, + this.y + y, + ' ', + RGBA.fromValues(0, 0, 0, 0), + RGBA.fromValues(r, g, b, a) + ); + } + } + } + } + } + } + + // Create background sprite + const background = new SpriteRenderable('background', backgroundSprite, { + x: 0, + y: 0, + width: renderer.width, + height: renderer.height, + scale: backgroundSprite.width / renderer.width + }); + + // Create character sprite + const character = new SpriteRenderable('character', characterSprite, { + x: 20, + y: 15, + scale: 0.5 + }); + + // Add sprites to the container + container.add(background); + container.add(character); + + // Add keyboard controls for the character + renderer.on('key', (data) => { + const key = data.toString(); + + switch (key) { + case 'w': + character.y = Math.max(0, character.y - 1); + break; + case 's': + character.y = Math.min(renderer.height - character.height, character.y + 1); + break; + case 'a': + character.x = Math.max(0, character.x - 1); + break; + case 'd': + character.x = Math.min(renderer.width - character.width, character.x + 1); + break; + } + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the sprite demo +createSpriteDemo().catch(console.error); +``` + +## Texture Loading + +OpenTUI provides utilities for loading and managing textures. + +### TextureUtils + +The `TextureUtils` class provides utilities for working with textures. + +```typescript +import { TextureUtils } from '@opentui/core/3d'; +import Jimp from 'jimp'; + +// Load a texture +const texture = await TextureUtils.loadTexture('path/to/texture.png'); + +// Load a texture from a Jimp image +const jimpImage = await Jimp.read('path/to/another_texture.png'); +const textureFromJimp = TextureUtils.textureFromJimp(jimpImage); + +// Create a texture from raw data +const rawData = new Uint8Array([/* RGBA pixel data */]); +const rawTexture = TextureUtils.createTexture(rawData, 32, 32); + +// Resize a texture +const resizedTexture = TextureUtils.resizeTexture(texture, 64, 64); + +// Crop a texture +const croppedTexture = TextureUtils.cropTexture(texture, 10, 10, 20, 20); + +// Get a pixel from a texture +const pixel = TextureUtils.getPixel(texture, 5, 5); +console.log(`RGBA: ${pixel.r}, ${pixel.g}, ${pixel.b}, ${pixel.a}`); +``` + +## Lighting and Materials + +When using Three.js integration, you can create advanced lighting and materials. + +### Example: Phong Lighting + +```typescript +import { createCliRenderer, BoxRenderable } from '@opentui/core'; +import * as THREE from 'three'; + +async function createLightingDemo() { + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + border: false + }); + + root.add(container); + + // Create a Three.js scene + const scene = new THREE.Scene(); + + // Create a camera + const camera = new THREE.PerspectiveCamera( + 75, + renderer.width / renderer.height, + 0.1, + 1000 + ); + camera.position.z = 5; + + // Create a renderer + const threeRenderer = new THREE.WebGLRenderer({ alpha: true }); + threeRenderer.setSize(renderer.width * 2, renderer.height * 2); + + // Create a cube with Phong material + const geometry = new THREE.BoxGeometry(); + const material = new THREE.MeshPhongMaterial({ + color: 0x3498db, + specular: 0xffffff, + shininess: 30 + }); + const cube = new THREE.Mesh(geometry, material); + scene.add(cube); + + // Add lighting + const ambientLight = new THREE.AmbientLight(0x404040); + scene.add(ambientLight); + + const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); + directionalLight.position.set(1, 1, 1); + scene.add(directionalLight); + + const pointLight = new THREE.PointLight(0xff0000, 1, 100); + pointLight.position.set(2, 2, 2); + scene.add(pointLight); + + // Animation loop + renderer.setFrameCallback(async (deltaTime) => { + // Rotate the cube + cube.rotation.x += 0.01; + cube.rotation.y += 0.01; + + // Move the point light in a circle + const time = Date.now() * 0.001; + pointLight.position.x = Math.sin(time) * 3; + pointLight.position.z = Math.cos(time) * 3; + + // Render the scene + threeRenderer.render(scene, camera); + + // Get the rendered image data + const imageData = threeRenderer.domElement.getContext('2d').getImageData( + 0, 0, threeRenderer.domElement.width, threeRenderer.domElement.height + ); + + // Convert the 3D rendering to terminal representation + for (let y = 0; y < container.height; y++) { + for (let x = 0; x < container.width; x++) { + // Sample the WebGL output with proper scaling + const glX = Math.floor(x * (threeRenderer.domElement.width / container.width)); + const glY = Math.floor(y * (threeRenderer.domElement.height / container.height)); + + const idx = (glY * threeRenderer.domElement.width + glX) * 4; + const r = imageData.data[idx] / 255; + const g = imageData.data[idx + 1] / 255; + const b = imageData.data[idx + 2] / 255; + const a = imageData.data[idx + 3] / 255; + + // Apply lighting effects to enhance visibility in terminal + const brightness = Math.max(r, g, b); + const character = brightness > 0.8 ? '█' : + brightness > 0.6 ? '▓' : + brightness > 0.4 ? '▒' : + brightness > 0.2 ? '░' : ' '; + + // Draw the pixel with appropriate character and color + buffer.setCell( + container.x + x, + container.y + y, + character, + RGBA.fromValues(r, g, b, a), + RGBA.fromValues(0, 0, 0, 1) + ); + } + } + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the lighting demo +createLightingDemo().catch(console.error); +``` + +## Shaders + +OpenTUI supports custom shaders for advanced visual effects. + +### Example: Fractal Shader + +```typescript +import { createCliRenderer, BoxRenderable, OptimizedBuffer, RGBA } from '@opentui/core'; +import { WGPURenderer } from '@opentui/core/3d'; + +// Vertex shader +const vertexShaderCode = ` +@vertex +fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4 { + var pos = array, 6>( + vec2(-1.0, -1.0), + vec2(1.0, -1.0), + vec2(1.0, 1.0), + vec2(-1.0, -1.0), + vec2(1.0, 1.0), + vec2(-1.0, 1.0) + ); + return vec4(pos[VertexIndex], 0.0, 1.0); +} +`; + +// Fragment shader (Mandelbrot fractal) +const fragmentShaderCode = ` +@group(0) @binding(0) var time: f32; + +@fragment +fn main(@builtin(position) fragCoord: vec4) -> @location(0) vec4 { + let resolution = vec2(80.0, 40.0); + let uv = (fragCoord.xy / resolution) * 2.0 - 1.0; + uv.x *= resolution.x / resolution.y; + + // Mandelbrot parameters + let zoom = 0.8 + 0.2 * sin(time * 0.1); + let centerX = -0.5 + 0.1 * sin(time * 0.05); + let centerY = 0.0 + 0.1 * cos(time * 0.05); + + // Map to Mandelbrot space + let c = vec2(uv.x / zoom + centerX, uv.y / zoom + centerY); + let z = vec2(0.0, 0.0); + + // Mandelbrot iteration + let maxIter = 100; + var iter = 0; + for (var i = 0; i < maxIter; i++) { + // z = z^2 + c + let x = z.x * z.x - z.y * z.y + c.x; + let y = 2.0 * z.x * z.y + c.y; + z = vec2(x, y); + + if (dot(z, z) > 4.0) { + iter = i; + break; + } + } + + // Coloring + if (iter == maxIter) { + return vec4(0.0, 0.0, 0.0, 1.0); + } else { + let t = f32(iter) / f32(maxIter); + let r = 0.5 + 0.5 * sin(t * 6.28 + time); + let g = 0.5 + 0.5 * sin(t * 6.28 + time + 2.09); + let b = 0.5 + 0.5 * sin(t * 6.28 + time + 4.18); + return vec4(r, g, b, 1.0); + } +} +`; + +async function createFractalShaderDemo() { + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + border: false + }); + + root.add(container); + + // Create a WebGPU renderer + const gpuRenderer = new WGPURenderer({ + width: renderer.width, + height: renderer.height + }); + + // Initialize the renderer + await gpuRenderer.initialize(); + + // Create a uniform buffer for time + const uniformBuffer = gpuRenderer.device.createBuffer({ + size: 4, // 4 bytes for a float + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST + }); + + // Create a bind group layout + const bindGroupLayout = gpuRenderer.device.createBindGroupLayout({ + entries: [{ + binding: 0, + visibility: GPUShaderStage.FRAGMENT, + buffer: { type: 'uniform' } + }] + }); + + // Create a bind group + const bindGroup = gpuRenderer.device.createBindGroup({ + layout: bindGroupLayout, + entries: [{ + binding: 0, + resource: { buffer: uniformBuffer } + }] + }); + + // Create a pipeline layout + const pipelineLayout = gpuRenderer.device.createPipelineLayout({ + bindGroupLayouts: [bindGroupLayout] + }); + + // Create a render pipeline + const pipeline = await gpuRenderer.device.createRenderPipeline({ + layout: pipelineLayout, + vertex: { + module: gpuRenderer.device.createShaderModule({ + code: vertexShaderCode + }), + entryPoint: 'main' + }, + fragment: { + module: gpuRenderer.device.createShaderModule({ + code: fragmentShaderCode + }), + entryPoint: 'main', + targets: [{ format: gpuRenderer.format }] + }, + primitive: { + topology: 'triangle-list' + } + }); + + // Create a custom renderable for the shader + class FractalShaderRenderable extends BoxRenderable { + private gpuRenderer: WGPURenderer; + private pipeline: GPURenderPipeline; + private bindGroup: GPUBindGroup; + private uniformBuffer: GPUBuffer; + private startTime: number; + + constructor(id: string, gpuRenderer: WGPURenderer, pipeline: GPURenderPipeline, + bindGroup: GPUBindGroup, uniformBuffer: GPUBuffer, options = {}) { + super(id, { + width: '100%', + height: '100%', + border: false, + ...options + }); + + this.gpuRenderer = gpuRenderer; + this.pipeline = pipeline; + this.bindGroup = bindGroup; + this.uniformBuffer = uniformBuffer; + this.startTime = Date.now(); + } + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + // Update time uniform + const time = (Date.now() - this.startTime) / 1000; + this.gpuRenderer.device.queue.writeBuffer(this.uniformBuffer, 0, new Float32Array([time])); + + // Render with WebGPU + this.gpuRenderer.beginFrame(); + + const passEncoder = this.gpuRenderer.commandEncoder.beginRenderPass({ + colorAttachments: [{ + view: this.gpuRenderer.textureView, + clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, + loadOp: 'clear', + storeOp: 'store' + }] + }); + + passEncoder.setPipeline(this.pipeline); + passEncoder.setBindGroup(0, this.bindGroup); + passEncoder.draw(6); // Draw two triangles (a quad) + passEncoder.end(); + + this.gpuRenderer.endFrame(); + + // Get the rendered image + const imageData = this.gpuRenderer.getImageData(); + + // Convert to terminal representation + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + // Sample the WebGPU output + const idx = (y * this.gpuRenderer.width + x) * 4; + const r = imageData.data[idx] / 255; + const g = imageData.data[idx + 1] / 255; + const b = imageData.data[idx + 2] / 255; + const a = imageData.data[idx + 3] / 255; + + // Draw the pixel + buffer.setCell( + this.x + x, + this.y + y, + ' ', // Use space character with background color + RGBA.fromValues(0, 0, 0, 0), + RGBA.fromValues(r, g, b, a) + ); + } + } + } + } + + // Create the fractal shader renderable + const fractalView = new FractalShaderRenderable( + 'fractal', + gpuRenderer, + pipeline, + bindGroup, + uniformBuffer + ); + container.add(fractalView); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the fractal shader demo +createFractalShaderDemo().catch(console.error); +``` diff --git a/packages/core/docs/api/animation/animation.md b/packages/core/docs/api/animation/animation.md new file mode 100644 index 000000000..78997f8ce --- /dev/null +++ b/packages/core/docs/api/animation/animation.md @@ -0,0 +1,761 @@ +# Animation API + +OpenTUI provides powerful animation capabilities for creating dynamic and interactive terminal user interfaces. + +## Timeline + +The `Timeline` class is the core of OpenTUI's animation system, allowing you to create and manage animations with precise timing control. + +### Creating a Timeline + +```typescript +import { Timeline } from '@opentui/core'; + +// Create a timeline with default options +const timeline = new Timeline(); + +// Create a timeline with custom options +const customTimeline = new Timeline({ + duration: 1000, // Duration in milliseconds + easing: 'easeInOut', // Easing function + repeat: 2, // Number of repetitions (0 = no repeat, -1 = infinite) + yoyo: true // Whether to reverse on alternate repetitions +}); +``` + +### Timeline Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `duration` | `number` | `1000` | Duration in milliseconds | +| `easing` | `string \| Function` | `'linear'` | Easing function | +| `repeat` | `number` | `0` | Number of repetitions (0 = no repeat, -1 = infinite) | +| `yoyo` | `boolean` | `false` | Whether to reverse on alternate repetitions | +| `autoPlay` | `boolean` | `false` | Whether to start playing automatically | +| `onComplete` | `Function` | `undefined` | Callback when animation completes | +| `onRepeat` | `Function` | `undefined` | Callback on each repetition | +| `onUpdate` | `Function` | `undefined` | Callback on each update | + +### Controlling Animations + +```typescript +// Start the animation +timeline.play(); + +// Pause the animation +timeline.pause(); + +// Resume the animation +timeline.resume(); + +// Stop the animation and reset to beginning +timeline.stop(); + +// Restart the animation from the beginning +timeline.restart(); + +// Reverse the animation direction +timeline.reverse(); + +// Check if the animation is playing +const isPlaying = timeline.isPlaying(); + +// Get the current progress (0-1) +const progress = timeline.getProgress(); + +// Set the progress manually (0-1) +timeline.setProgress(0.5); + +// Get the current time in milliseconds +const time = timeline.getCurrentTime(); + +// Set the current time in milliseconds +timeline.setCurrentTime(500); +``` + +### Adding Animations + +```typescript +// Animate a property +timeline.to(target, { + property: 'x', // Property to animate + from: 0, // Starting value + to: 100, // Ending value + duration: 1000, // Duration in milliseconds + easing: 'easeInOut', // Easing function + onUpdate: (value) => { + // Custom update logic + console.log('Current value:', value); + } +}); + +// Animate multiple properties +timeline.to(target, { + properties: { + x: { from: 0, to: 100 }, + y: { from: 0, to: 50 }, + opacity: { from: 0, to: 1 } + }, + duration: 1000, + easing: 'easeInOut' +}); + +// Add a delay +timeline.delay(500); + +// Add a callback +timeline.call(() => { + console.log('Animation reached this point'); +}); +``` + +### Easing Functions + +OpenTUI provides various easing functions for animations: + +| Easing | Description | +|--------|-------------| +| `'linear'` | Linear easing (no acceleration) | +| `'easeIn'` | Accelerating from zero velocity | +| `'easeOut'` | Decelerating to zero velocity | +| `'easeInOut'` | Acceleration until halfway, then deceleration | +| `'easeInQuad'` | Quadratic easing in | +| `'easeOutQuad'` | Quadratic easing out | +| `'easeInOutQuad'` | Quadratic easing in and out | +| `'easeInCubic'` | Cubic easing in | +| `'easeOutCubic'` | Cubic easing out | +| `'easeInOutCubic'` | Cubic easing in and out | +| `'easeInElastic'` | Elastic easing in | +| `'easeOutElastic'` | Elastic easing out | +| `'easeInOutElastic'` | Elastic easing in and out | +| `'easeInBounce'` | Bouncing easing in | +| `'easeOutBounce'` | Bouncing easing out | +| `'easeInOutBounce'` | Bouncing easing in and out | + +You can also provide a custom easing function: + +```typescript +// Custom easing function (t: 0-1) +const customEasing = (t: number): number => { + return t * t * (3 - 2 * t); // Custom smoothstep +}; + +timeline.to(target, { + property: 'x', + from: 0, + to: 100, + duration: 1000, + easing: customEasing +}); +``` + +### Example: Animating a Box + +```typescript +import { createCliRenderer, BoxRenderable, Timeline } from '@opentui/core'; + +async function animateBox() { + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a box + const box = new BoxRenderable('box', { + width: 10, + height: 5, + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222', + position: 'absolute', + x: 0, + y: 10 + }); + + root.add(box); + + // Create a timeline + const timeline = new Timeline({ + repeat: -1, // Infinite repeat + yoyo: true, // Reverse on alternate repetitions + autoPlay: true + }); + + // Animate the box horizontally + timeline.to(box, { + property: 'x', + from: 0, + to: renderer.width - box.width, + duration: 3000, + easing: 'easeInOutQuad' + }); + + // Add a color animation + timeline.to(box, { + property: 'borderColor', + from: '#3498db', + to: '#e74c3c', + duration: 1500, + easing: 'linear' + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the animation +animateBox().catch(console.error); +``` + +### Chaining Animations + +You can chain animations to create complex sequences: + +```typescript +// Create a sequence of animations +timeline + .to(box, { + property: 'x', + from: 0, + to: 100, + duration: 1000 + }) + .delay(500) + .to(box, { + property: 'y', + from: 0, + to: 50, + duration: 1000 + }) + .call(() => { + console.log('Horizontal and vertical movement complete'); + }) + .to(box, { + property: 'borderColor', + from: '#3498db', + to: '#e74c3c', + duration: 500 + }); +``` + +### Parallel Animations + +You can run multiple animations in parallel: + +```typescript +// Create parallel animations +const timeline = new Timeline(); + +// Add multiple animations that will run simultaneously +timeline.to(box1, { + property: 'x', + from: 0, + to: 100, + duration: 1000 +}); + +timeline.to(box2, { + property: 'y', + from: 0, + to: 50, + duration: 1000 +}); + +// Start all animations +timeline.play(); +``` + +## Sprite Animation + +OpenTUI provides sprite animation capabilities for creating animated characters and effects. + +### SpriteAnimator + +The `SpriteAnimator` class allows you to create frame-based animations from sprite sheets. + +```typescript +import { SpriteAnimator } from '@opentui/core/3d'; +import Jimp from 'jimp'; + +// Load a sprite sheet +const spriteSheet = await Jimp.read('path/to/sprite_sheet.png'); + +// Create a sprite animator +const animator = new SpriteAnimator({ + image: spriteSheet, + frameWidth: 32, // Width of each frame + frameHeight: 32, // Height of each frame + frameCount: 8, // Total number of frames + frameDuration: 100 // Duration of each frame in milliseconds +}); + +// Start the animation +animator.play(); + +// Pause the animation +animator.pause(); + +// Set the current frame +animator.setFrame(3); + +// Get the current frame +const currentFrame = animator.getCurrentFrame(); + +// Update the animation (call in render loop) +animator.update(deltaTime); +``` + +### Example: Creating an Animated Character + +```typescript +import { createCliRenderer, BoxRenderable, RGBA } from '@opentui/core'; +import { SpriteAnimator } from '@opentui/core/3d'; +import Jimp from 'jimp'; + +async function createAnimatedCharacter() { + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + border: false, + backgroundColor: '#222222' + }); + + root.add(container); + + // Load character sprite sheet + const spriteSheet = await Jimp.read('path/to/character_run.png'); + + // Create a sprite animator + const characterAnimator = new SpriteAnimator({ + image: spriteSheet, + frameWidth: 32, + frameHeight: 32, + frameCount: 8, + frameDuration: 100 + }); + + // Create a custom renderable for the character + class CharacterRenderable extends BoxRenderable { + private animator: SpriteAnimator; + + constructor(id: string, animator: SpriteAnimator, options = {}) { + super(id, { + width: 16, + height: 8, + border: false, + position: 'absolute', + x: 10, + y: 10, + ...options + }); + + this.animator = animator; + this.animator.play(); + } + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + // Update the animation + this.animator.update(deltaTime); + + // Get the current frame + const frame = this.animator.getCurrentFrame(); + + // Render the sprite with proper scaling + if (frame) { + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + // Sample the sprite pixel with bilinear interpolation for smoother scaling + const pixelX = Math.floor(x * (frame.width / this.width)); + const pixelY = Math.floor(y * (frame.height / this.height)); + + // Get pixel color from the sprite frame + const idx = (pixelY * frame.width + pixelX) * 4; + const r = frame.data[idx] / 255; + const g = frame.data[idx + 1] / 255; + const b = frame.data[idx + 2] / 255; + const a = frame.data[idx + 3] / 255; + + if (a > 0.5) { + // Draw the pixel + buffer.setCell( + this.x + x, + this.y + y, + ' ', + RGBA.fromValues(0, 0, 0, 0), + RGBA.fromValues(r, g, b, a) + ); + } + } + } + } + } + } + + // Create the character + const character = new CharacterRenderable('character', characterAnimator); + container.add(character); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the animation +createAnimatedCharacter().catch(console.error); +``` + +## Particle Effects + +OpenTUI provides particle system capabilities for creating visual effects. + +### SpriteParticleGenerator + +The `SpriteParticleGenerator` class allows you to create particle effects. + +```typescript +import { SpriteParticleGenerator } from '@opentui/core/3d'; +import Jimp from 'jimp'; + +// Load a particle texture +const particleTexture = await Jimp.read('path/to/particle.png'); + +// Create a particle generator +const particles = new SpriteParticleGenerator({ + texture: particleTexture, + maxParticles: 100, + emissionRate: 10, // Particles per second + particleLifetime: { + min: 1000, // Minimum lifetime in milliseconds + max: 3000 // Maximum lifetime in milliseconds + }, + position: { x: 40, y: 20 }, + positionVariance: { x: 5, y: 0 }, + velocity: { x: 0, y: -0.05 }, + velocityVariance: { x: 0.02, y: 0.01 }, + acceleration: { x: 0, y: 0.0001 }, + startScale: { min: 0.5, max: 1.0 }, + endScale: { min: 0, max: 0.2 }, + startColor: RGBA.fromHex('#ffff00'), + endColor: RGBA.fromHex('#ff0000'), + startAlpha: 1.0, + endAlpha: 0.0, + rotationSpeed: { min: -0.1, max: 0.1 } +}); + +// Start emitting particles +particles.start(); + +// Stop emitting particles +particles.stop(); + +// Update the particle system (call in render loop) +particles.update(deltaTime); + +// Render the particles (call in render method) +particles.render(buffer, x, y); +``` + +### Example: Creating a Fire Effect + +```typescript +import { createCliRenderer, BoxRenderable, RGBA } from '@opentui/core'; +import { SpriteParticleGenerator } from '@opentui/core/3d'; +import Jimp from 'jimp'; + +async function createFireEffect() { + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + border: false, + backgroundColor: '#222222' + }); + + root.add(container); + + // Load particle texture + const particleTexture = await Jimp.read('path/to/particle.png'); + + // Create a fire particle effect + const fireEffect = new SpriteParticleGenerator({ + texture: particleTexture, + maxParticles: 200, + emissionRate: 50, + particleLifetime: { + min: 500, + max: 1500 + }, + position: { x: renderer.width / 2, y: renderer.height - 5 }, + positionVariance: { x: 3, y: 0 }, + velocity: { x: 0, y: -0.08 }, + velocityVariance: { x: 0.03, y: 0.02 }, + acceleration: { x: 0, y: -0.0001 }, + startScale: { min: 0.8, max: 1.2 }, + endScale: { min: 0.1, max: 0.3 }, + startColor: RGBA.fromHex('#ffff00'), + endColor: RGBA.fromHex('#ff0000'), + startAlpha: 1.0, + endAlpha: 0.0, + rotationSpeed: { min: -0.05, max: 0.05 } + }); + + // Create a custom renderable for the fire + class FireRenderable extends BoxRenderable { + private particles: SpriteParticleGenerator; + + constructor(id: string, particles: SpriteParticleGenerator, options = {}) { + super(id, { + width: '100%', + height: '100%', + border: false, + ...options + }); + + this.particles = particles; + this.particles.start(); + } + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + // Update the particles + this.particles.update(deltaTime); + + // Render the particles + this.particles.render(buffer, 0, 0); + } + } + + // Create the fire effect + const fire = new FireRenderable('fire', fireEffect); + container.add(fire); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the fire effect +createFireEffect().catch(console.error); +``` + +## Physics-Based Animation + +OpenTUI supports physics-based animations through integration with physics engines. + +### RapierPhysicsAdapter + +The `RapierPhysicsAdapter` class provides integration with the Rapier 2D physics engine. + +```typescript +import { RapierPhysicsAdapter } from '@opentui/core/3d/physics'; + +// Create a physics world +const physics = new RapierPhysicsAdapter({ + gravity: { x: 0, y: 9.81 } +}); + +// Create a static ground body +const ground = physics.createStaticBody({ + position: { x: 40, y: 40 }, + shape: { + type: 'box', + width: 80, + height: 2 + } +}); + +// Create a dynamic box body +const box = physics.createDynamicBody({ + position: { x: 40, y: 10 }, + shape: { + type: 'box', + width: 4, + height: 4 + }, + restitution: 0.5, // Bounciness + friction: 0.2 // Friction +}); + +// Update the physics world (call in render loop) +physics.update(deltaTime); + +// Get the position of the box +const position = box.getPosition(); +``` + +### PlanckPhysicsAdapter + +The `PlanckPhysicsAdapter` class provides integration with the Planck.js physics engine. + +```typescript +import { PlanckPhysicsAdapter } from '@opentui/core/3d/physics'; + +// Create a physics world +const physics = new PlanckPhysicsAdapter({ + gravity: { x: 0, y: 10 } +}); + +// Create a static ground body +const ground = physics.createStaticBody({ + position: { x: 40, y: 40 }, + shape: { + type: 'box', + width: 80, + height: 2 + } +}); + +// Create a dynamic circle body +const ball = physics.createDynamicBody({ + position: { x: 40, y: 10 }, + shape: { + type: 'circle', + radius: 2 + }, + restitution: 0.8, // Bounciness + friction: 0.1 // Friction +}); + +// Apply an impulse to the ball +ball.applyLinearImpulse({ x: 5, y: -5 }); + +// Update the physics world (call in render loop) +physics.update(deltaTime); +``` + +### Example: Creating a Physics Simulation + +```typescript +import { createCliRenderer, BoxRenderable, RGBA } from '@opentui/core'; +import { RapierPhysicsAdapter } from '@opentui/core/3d/physics'; + +async function createPhysicsSimulation() { + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + border: false, + backgroundColor: '#222222' + }); + + root.add(container); + + // Create a physics world + const physics = new RapierPhysicsAdapter({ + gravity: { x: 0, y: 20 } + }); + + // Create a static ground body + const ground = physics.createStaticBody({ + position: { x: renderer.width / 2, y: renderer.height - 5 }, + shape: { + type: 'box', + width: renderer.width, + height: 2 + } + }); + + // Create walls + const leftWall = physics.createStaticBody({ + position: { x: 2, y: renderer.height / 2 }, + shape: { + type: 'box', + width: 2, + height: renderer.height + } + }); + + const rightWall = physics.createStaticBody({ + position: { x: renderer.width - 2, y: renderer.height / 2 }, + shape: { + type: 'box', + width: 2, + height: renderer.height + } + }); + + // Create some dynamic bodies + const bodies = []; + const renderables = []; + + for (let i = 0; i < 10; i++) { + // Create a dynamic body + const body = physics.createDynamicBody({ + position: { + x: 10 + Math.random() * (renderer.width - 20), + y: 5 + Math.random() * 10 + }, + shape: { + type: Math.random() > 0.5 ? 'box' : 'circle', + width: 3 + Math.random() * 3, + height: 3 + Math.random() * 3, + radius: 2 + Math.random() * 2 + }, + restitution: 0.3 + Math.random() * 0.5, + friction: 0.1 + Math.random() * 0.3 + }); + + bodies.push(body); + + // Create a renderable for the body + const isBox = body.getShapeType() === 'box'; + const size = isBox ? body.getSize() : { width: body.getRadius() * 2, height: body.getRadius() * 2 }; + + const renderable = new BoxRenderable(`body${i}`, { + width: size.width, + height: size.height, + position: 'absolute', + x: body.getPosition().x - size.width / 2, + y: body.getPosition().y - size.height / 2, + borderStyle: isBox ? 'single' : 'rounded', + borderColor: RGBA.fromHex( + ['#3498db', '#2ecc71', '#e74c3c', '#f39c12', '#9b59b6'][Math.floor(Math.random() * 5)] + ), + backgroundColor: 'transparent' + }); + + renderables.push(renderable); + container.add(renderable); + } + + // Create a frame callback to update physics + renderer.setFrameCallback(async (deltaTime) => { + // Update physics (with fixed timestep) + const fixedDelta = Math.min(deltaTime, 33) / 1000; // Cap at 30 FPS, convert to seconds + physics.update(fixedDelta); + + // Update renderables + for (let i = 0; i < bodies.length; i++) { + const body = bodies[i]; + const renderable = renderables[i]; + const position = body.getPosition(); + const angle = body.getAngle(); + + // Update position + renderable.x = position.x - renderable.width / 2; + renderable.y = position.y - renderable.height / 2; + + // We could update rotation too if OpenTUI supported it + } + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the physics simulation +createPhysicsSimulation().catch(console.error); +``` diff --git a/packages/core/docs/api/animation/timeline.md b/packages/core/docs/api/animation/timeline.md new file mode 100644 index 000000000..23b99d62a --- /dev/null +++ b/packages/core/docs/api/animation/timeline.md @@ -0,0 +1,356 @@ +# Animation Timeline + +OpenTUI provides a powerful animation system through the `Timeline` class, which allows you to create complex animations with precise timing control. + +## Overview + +The Timeline system consists of: + +1. **Timeline**: The main class for managing animations +2. **Animations**: Individual animations that can be added to the timeline +3. **Easing Functions**: Various easing functions for smooth animations +4. **Callbacks**: Functions that can be called at specific times in the timeline + +## Timeline API + +```typescript +import { Timeline, TimelineOptions } from '@opentui/core'; + +// Create a timeline +const timeline = new Timeline({ + duration: 1000, // Duration in milliseconds + loop: false, // Whether to loop the timeline + autoplay: true, // Whether to start playing immediately + onComplete: () => { // Called when the timeline completes + console.log('Timeline completed'); + }, + onPause: () => { // Called when the timeline is paused + console.log('Timeline paused'); + } +}); + +// Control the timeline +timeline.play(); // Start or resume playback +timeline.pause(); // Pause playback +timeline.stop(); // Stop playback and reset to beginning +timeline.seek(500); // Seek to a specific time (in milliseconds) +timeline.reverse(); // Reverse the playback direction + +// Get timeline state +const isPlaying = timeline.isPlaying(); +const currentTime = timeline.getCurrentTime(); +const duration = timeline.getDuration(); +const progress = timeline.getProgress(); // 0 to 1 +``` + +## Adding Animations + +You can add animations to a timeline with precise timing: + +```typescript +import { Timeline, EasingFunctions } from '@opentui/core'; + +const timeline = new Timeline({ duration: 2000 }); + +// Add an animation that starts at the beginning +timeline.animate( + myElement, // Target element + { + x: 100, // Target property and value + y: 50, + opacity: 1 + }, + { + duration: 500, // Duration in milliseconds + ease: 'outQuad', // Easing function + onUpdate: (anim) => { + console.log(`Progress: ${anim.progress}`); + }, + onComplete: () => { + console.log('Animation completed'); + } + } +); + +// Add an animation that starts at 500ms +timeline.animate( + anotherElement, + { + scale: 2, + rotation: 45 + }, + { + duration: 800, + ease: 'inOutSine', + startTime: 500 // Start time in milliseconds + } +); +``` + +## Adding Callbacks + +You can add callbacks to a timeline at specific times: + +```typescript +// Add a callback at 1000ms +timeline.addCallback(1000, () => { + console.log('Halfway point reached'); +}); + +// Add a callback at the end +timeline.addCallback(timeline.getDuration(), () => { + console.log('Timeline ended'); +}); +``` + +## Nesting Timelines + +You can nest timelines for complex animation sequences: + +```typescript +const mainTimeline = new Timeline({ duration: 5000 }); +const subTimeline = new Timeline({ duration: 2000 }); + +// Add animations to the sub-timeline +subTimeline.animate(element1, { x: 100 }, { duration: 500 }); +subTimeline.animate(element2, { y: 50 }, { duration: 800, startTime: 500 }); + +// Add the sub-timeline to the main timeline +mainTimeline.addTimeline(subTimeline, 1000); // Start at 1000ms + +// Play the main timeline +mainTimeline.play(); +``` + +## Easing Functions + +OpenTUI provides various easing functions for smooth animations: + +```typescript +import { EasingFunctions } from '@opentui/core'; + +// Available easing functions: +const easings: EasingFunctions[] = [ + 'linear', + 'inQuad', + 'outQuad', + 'inOutQuad', + 'inExpo', + 'outExpo', + 'inOutSine', + 'outBounce', + 'outElastic', + 'inBounce', + 'inCirc', + 'outCirc', + 'inOutCirc' +]; + +// Use an easing function +timeline.animate(element, { x: 100 }, { + duration: 500, + ease: 'outBounce' +}); +``` + +## Animation Options + +The `animate` method accepts various options: + +```typescript +timeline.animate(element, { x: 100 }, { + duration: 500, // Duration in milliseconds + ease: 'outQuad', // Easing function + startTime: 0, // Start time in milliseconds (default: 0) + loop: false, // Whether to loop this animation + loopDelay: 0, // Delay between loops in milliseconds + alternate: false, // Whether to alternate direction on loop + once: false, // Whether to run only once + onUpdate: (anim) => { + // Called on each update + console.log(`Progress: ${anim.progress}`); + }, + onComplete: () => { + // Called when the animation completes + console.log('Animation completed'); + }, + onStart: () => { + // Called when the animation starts + console.log('Animation started'); + }, + onLoop: () => { + // Called when the animation loops + console.log('Animation looped'); + } +}); +``` + +## Updating the Timeline + +The timeline needs to be updated on each frame: + +```typescript +// In your render loop +function update(deltaTime: number) { + timeline.update(deltaTime); + + // Request the next frame + requestAnimationFrame((time) => { + const delta = time - lastTime; + lastTime = time; + update(delta); + }); +} + +let lastTime = performance.now(); +update(0); +``` + +## Example: Creating a Complex Animation + +```typescript +import { Timeline, BoxRenderable } from '@opentui/core'; + +// Create elements +const box1 = new BoxRenderable('box1', { + width: 10, + height: 5, + x: 0, + y: 0, + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222' +}); + +const box2 = new BoxRenderable('box2', { + width: 10, + height: 5, + x: 0, + y: 10, + borderStyle: 'single', + borderColor: '#e74c3c', + backgroundColor: '#222222' +}); + +// Add to the renderer +renderer.root.add(box1); +renderer.root.add(box2); + +// Create a timeline +const timeline = new Timeline({ + duration: 5000, + loop: true, + autoplay: true +}); + +// Animate box1 +timeline.animate(box1, { x: 50 }, { + duration: 1000, + ease: 'outQuad' +}); + +timeline.animate(box1, { y: 20 }, { + duration: 1000, + startTime: 1000, + ease: 'inOutSine' +}); + +timeline.animate(box1, { x: 0 }, { + duration: 1000, + startTime: 2000, + ease: 'inQuad' +}); + +timeline.animate(box1, { y: 0 }, { + duration: 1000, + startTime: 3000, + ease: 'inOutSine' +}); + +// Animate box2 with a delay +timeline.animate(box2, { x: 50 }, { + duration: 1000, + startTime: 500, + ease: 'outBounce' +}); + +timeline.animate(box2, { y: 30 }, { + duration: 1000, + startTime: 1500, + ease: 'outElastic' +}); + +timeline.animate(box2, { x: 0 }, { + duration: 1000, + startTime: 2500, + ease: 'inBounce' +}); + +timeline.animate(box2, { y: 10 }, { + duration: 1000, + startTime: 3500, + ease: 'inOutCirc' +}); + +// Add a callback +timeline.addCallback(2000, () => { + console.log('Halfway point reached'); +}); + +// Update the timeline in the render loop +renderer.on('update', (context) => { + timeline.update(context.deltaTime); +}); +``` + +## Example: Creating a Typing Animation + +```typescript +import { Timeline, TextRenderable } from '@opentui/core'; + +// Create a text element +const text = new TextRenderable('text', { + content: '', + x: 5, + y: 5, + fg: '#ffffff' +}); + +// Add to the renderer +renderer.root.add(text); + +// Create a timeline +const timeline = new Timeline({ + duration: 3000, + autoplay: true +}); + +// The full text to type +const fullText = 'Hello, world! This is a typing animation.'; + +// Create a typing animation +for (let i = 1; i <= fullText.length; i++) { + timeline.addCallback(i * 100, () => { + text.content = fullText.substring(0, i); + }); +} + +// Add a blinking cursor +let cursorVisible = true; +timeline.addCallback(fullText.length * 100 + 500, () => { + const interval = setInterval(() => { + cursorVisible = !cursorVisible; + text.content = fullText + (cursorVisible ? '|' : ''); + }, 500); + + // Clean up the interval when the timeline is stopped + timeline.on('stop', () => { + clearInterval(interval); + }); +}); + +// Update the timeline in the render loop +renderer.on('update', (context) => { + timeline.update(context.deltaTime); +}); +``` diff --git a/packages/core/docs/api/buffer.md b/packages/core/docs/api/buffer.md new file mode 100644 index 000000000..4e554c946 --- /dev/null +++ b/packages/core/docs/api/buffer.md @@ -0,0 +1,159 @@ +# Buffer System (detailed API) + +This page documents the OptimizedBuffer class in detail — method signatures, parameters, return values, and important behavior notes so code samples are precise and reliable. + +Source reference: packages/core/src/buffer.ts + +Important types used below: +- RGBA — color container (use `RGBA.fromValues`, `RGBA.fromHex`, etc.) +- Pointer — native pointer type (FFI-backed operations) +- OptimizedBuffer — the class documented here + +--- + +## Class: OptimizedBuffer + +Overview: a high-performance framebuffer abstraction that exposes typed-array access to characters, foreground colors, background colors, and per-cell attributes. Many heavy operations call into the native render library (FFI); the class exposes both FFI-backed and JS-local implementations. + +Creation +```ts +// Static factory (preferred) +const buf = OptimizedBuffer.create(width: number, height: number, options?: { respectAlpha?: boolean }): OptimizedBuffer +``` +- width, height: pixel/cell dimensions +- options.respectAlpha: if true, compositing from source buffers respects alpha channels; default false. + +Properties +- ptr: Pointer — pointer to the native buffer object (used internally; exposed for advanced use) +- buffers: { char: Uint32Array; fg: Float32Array; bg: Float32Array; attributes: Uint8Array } + - char: uint32 per cell (Unicode codepoints) + - fg/bg: Float32Array with 4 floats per cell (r,g,b,a) in 0..1 range + - attributes: Uint8Array per cell (bitflags for attributes) +- id: string — internal id string +- respectAlpha: boolean — whether this buffer respects alpha on draws + +Basic size methods +```ts +getWidth(): number +getHeight(): number +resize(width: number, height: number): void +``` +- `resize` will replace the internal typed arrays (via FFI in native mode) and update internal width/height. + +Lifecycle +```ts +clear(bg?: RGBA, clearChar?: string): void +clearFFI(bg?: RGBA): void +clearLocal(bg?: RGBA, clearChar?: string): void +destroy(): void +``` +- `clear()` delegates to the FFI implementation when available; `clearLocal` is the JS fallback. +- `destroy()` frees the native buffer via the RenderLib wrapper. + +Per-cell operations +```ts +setCell(x: number, y: number, char: string, fg: RGBA, bg: RGBA, attributes?: number): void +get(x: number, y: number): { char: number; fg: RGBA; bg: RGBA; attributes: number } | null +``` +- Coordinates outside the buffer are ignored (setCell is a no-op; get returns null). +- char: first codepoint of provided string will be used; stored as numeric code. +- attributes: integer bitflags (project uses small integers for bold/underline/dim/etc). + +Alpha-aware per-cell writes +```ts +setCellWithAlphaBlending(x: number, y: number, char: string, fg: RGBA, bg: RGBA, attributes?: number): void +setCellWithAlphaBlendingFFI(x: number, y: number, char: string, fg: RGBA, bg: RGBA, attributes?: number): void +setCellWithAlphaBlendingLocal(x: number, y: number, char: string, fg: RGBA, bg: RGBA, attributes?: number): void +``` +- `setCellWithAlphaBlending` routes to FFI when available; otherwise the local JS implementation performs perceptual alpha blending (`blendColors` logic inside buffer.ts). +- Behavior notes: + - If bg/fg have alpha < 1, blending occurs against the destination cell. + - When drawing a space character ' ' over a non-empty cell, the implementation preserves the destination character by default and blends colors accordingly. + +Text drawing +```ts +// High-level +drawText(text: string, x: number, y: number, fg: RGBA, bg?: RGBA, attributes?: number, selection?: { start: number; end: number; bgColor?: RGBA; fgColor?: RGBA } | null): void + +// FFI-level +drawTextFFI(text: string, x: number, y: number, fg?: RGBA, bg?: RGBA, attributes?: number): void +``` +- drawText supports selection highlighting by splitting the text and drawing the selected portion with alternate fg/bg. +- Parameter order is (text, x, y, fg, bg?, attributes?, selection?). +- For performance, drawText calls into `drawTextFFI` when available. + +TextBuffer rendering +```ts +drawTextBuffer(textBuffer: TextBuffer, x: number, y: number, clipRect?: { x: number; y: number; width: number; height: number }): void +``` +- Use this to render a TextBuffer (rich/styled content) efficiently via the native helper. + +Rectangles, boxes and compositing +```ts +fillRect(x: number, y: number, width: number, height: number, bg: RGBA): void +fillRectFFI(x: number, y: number, width: number, height: number, bg: RGBA): void +fillRectLocal(x: number, y: number, width: number, height: number, bg: RGBA): void + +drawBox(options: { + x: number + y: number + width: number + height: number + borderStyle?: BorderStyle + customBorderChars?: Uint32Array + border: boolean | BorderSides[] + borderColor: RGBA + backgroundColor: RGBA + shouldFill?: boolean + title?: string + titleAlignment?: 'left' | 'center' | 'right' +}): void +``` +- `drawBox` packs options and forwards to the native `bufferDrawBox` for speed. +- `fillRect` is alpha-aware; if `bg` has alpha < 1 the implementation blends per cell. + +Framebuffer compositing (copying one buffer into another) +```ts +drawFrameBuffer(destX: number, destY: number, frameBuffer: OptimizedBuffer, sourceX?: number, sourceY?: number, sourceWidth?: number, sourceHeight?: number): void +drawFrameBufferLocal(destX: number, destY: number, frameBuffer: OptimizedBuffer, sourceX?: number, sourceY?: number, sourceWidth?: number, sourceHeight?: number): void +drawFrameBufferFFI(destX: number, destY: number, frameBuffer: OptimizedBuffer, sourceX?: number, sourceY?: number, sourceWidth?: number, sourceHeight?: number): void +``` +- Preferred: `drawFrameBuffer` which delegates to FFI; `drawFrameBufferLocal` exists as a JS fallback. +- Behavior: + - When `frameBuffer.respectAlpha` is false the copy is a straight copy of char/fg/bg/attributes. + - When `respectAlpha` is true, transparent pixels (alpha 0) are skipped, and blended compositing occurs per cell. + +Packed / supersampled drawing (advanced) +```ts +drawPackedBuffer(dataPtr: Pointer, dataLen: number, posX: number, posY: number, terminalWidthCells: number, terminalHeightCells: number): void +drawSuperSampleBuffer(x: number, y: number, pixelDataPtr: Pointer, pixelDataLength: number, format: 'bgra8unorm' | 'rgba8unorm', alignedBytesPerRow: number): void +drawSuperSampleBufferFFI(x: number, y: number, pixelDataPtr: Pointer, pixelDataLength: number, format: 'bgra8unorm' | 'rgba8unorm', alignedBytesPerRow: number): void +``` +- Use these for WebGPU/WebGL-like pixel data uploads or packed buffer formats. These call into FFI for performance. + +FFI helpers (when using native lib) +- clearFFI(bg) +- drawTextFFI(...) +- setCellWithAlphaBlendingFFI(...) +- fillRectFFI(...) +- drawFrameBufferFFI(...) +- drawPackedBuffer(...) / drawSuperSampleBufferFFI(...) + +Notes and edge-cases +- Color format: RGBA floats in 0..1. Use `RGBA.fromHex('#rrggbb')` or `RGBA.fromValues(r,g,b,a)` to build colors. +- Performance: + - Prefer FFI-backed methods (default when native library is loaded via `resolveRenderLib()`). + - Avoid frequent calls to `resize`. + - Use `drawFrameBuffer` for compositing pre-computed buffers rather than redrawing many primitives per frame. +- Selection support in drawText: + - Provide selection `{ start, end, bgColor?, fgColor? }` to highlight segments. start/end indices are character indices in the string. + +Example: alpha-aware text and compositing +```ts +const buf = OptimizedBuffer.create(80, 24, { respectAlpha: true }); +const fg = RGBA.fromHex('#ffffff'); +const transparentBg = RGBA.fromValues(0, 0, 0, 0.5); +buf.setCellWithAlphaBlending(10, 10, 'A', fg, transparentBg, 0); +``` + +--- diff --git a/packages/core/docs/api/components/ascii-font.md b/packages/core/docs/api/components/ascii-font.md new file mode 100644 index 000000000..bbd5d2918 --- /dev/null +++ b/packages/core/docs/api/components/ascii-font.md @@ -0,0 +1,399 @@ +# ASCIIFontRenderable + +Renders text using ASCII art fonts for large, decorative text displays. + +## Class: `ASCIIFontRenderable` + +```typescript +import { ASCIIFontRenderable } from '@opentui/core' + +const title = new ASCIIFontRenderable('title', { + text: 'HELLO', + font: 'block', + fg: '#00ff00' +}) +``` + +## Constructor + +### `new ASCIIFontRenderable(id: string, options: ASCIIFontOptions)` + +## Options + +### `ASCIIFontOptions` + +Extends [`RenderableOptions`](../renderable.md#renderableoptions) with: + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `text` | `string` | `''` | Text to render | +| `font` | `'tiny' \| 'block' \| 'shade' \| 'slick'` | `'tiny'` | ASCII font style | +| `fg` | `RGBA \| RGBA[] \| string \| string[]` | `'#ffffff'` | Foreground color(s) | +| `bg` | `RGBA \| string` | `transparent` | Background color | +| `selectionBg` | `string \| RGBA` | - | Selection background color | +| `selectionFg` | `string \| RGBA` | - | Selection foreground color | +| `selectable` | `boolean` | `true` | Enable text selection | + +## Properties + +| Property | Type | Description | +|----------|------|-------------| +| `text` | `string` | Get/set the text content | +| `font` | `'tiny' \| 'block' \| 'shade' \| 'slick'` | Get/set the font style | +| `fg` | `RGBA[]` | Get/set foreground colors | +| `bg` | `RGBA` | Get/set background color | +| `selectable` | `boolean` | Enable/disable selection | + +## Font Styles + +### `tiny` +Compact font, good for headers in limited space: +``` +H H EEEEE L L OOO +H H E L L O O +HHHHH EEEE L L O O +H H E L L O O +H H EEEEE LLLLL LLLLL OOO +``` + +### `block` +Bold, blocky letters for maximum impact: +``` +██ ██ ███████ ██ ██ ██████ +██ ██ ██ ██ ██ ██ ██ +███████ █████ ██ ██ ██ ██ +██ ██ ██ ██ ██ ██ ██ +██ ██ ███████ ███████ ███████ ██████ +``` + +### `shade` +Shaded/gradient effect using different density characters: +``` +░█ ░█ ▒█▒█▒█ ░█ ░█ ▒█▒█▒█ +▒█ ▒█ ▒█ ▒█ ▒█ ▒█ ▒█ +▓█▓█▓█ ▓█▓█ ▓█ ▓█ ▓█ ▓█ +▒█ ▒█ ▒█ ▒█ ▒█ ▒█ ▒█ +░█ ░█ ▒█▒█▒█ ▒█▒█▒█ ▒█▒█▒█ ▒█▒█▒█ +``` + +### `slick` +Stylized font with decorative elements: +``` +╦ ╦╔═╗╦ ╦ ╔═╗ +╠═╣║╣ ║ ║ ║ ║ +╩ ╩╚═╝╩═╝╩═╝╚═╝ +``` + +## Examples + +### Basic Title + +```typescript +const title = new ASCIIFontRenderable('title', { + text: 'GAME OVER', + font: 'block', + fg: '#ff0000' +}) +``` + +### Rainbow Colors + +```typescript +const rainbow = new ASCIIFontRenderable('rainbow', { + text: 'RAINBOW', + font: 'block', + fg: ['#ff0000', '#ff7f00', '#ffff00', '#00ff00', '#0000ff', '#4b0082', '#9400d3'] +}) +``` + +### Animated Title + +```typescript +class AnimatedTitle extends ASCIIFontRenderable { + private colors = [ + '#ff0000', '#ff3333', '#ff6666', '#ff9999', '#ffcccc', + '#ff9999', '#ff6666', '#ff3333' + ] + private colorIndex = 0 + + constructor(id: string, text: string) { + super(id, { + text, + font: 'block', + fg: '#ff0000' + }) + + this.startAnimation() + } + + private startAnimation() { + setInterval(() => { + this.fg = this.colors[this.colorIndex] + this.colorIndex = (this.colorIndex + 1) % this.colors.length + }, 100) + } +} + +const animatedTitle = new AnimatedTitle('animated', 'ALERT!') +``` + +### Gradient Effect + +```typescript +function createGradient(text: string, startColor: string, endColor: string): string[] { + const colors: string[] = [] + const steps = text.length + + // Parse RGB values + const start = parseRGB(startColor) + const end = parseRGB(endColor) + + for (let i = 0; i < steps; i++) { + const ratio = i / (steps - 1) + const r = Math.round(start.r + (end.r - start.r) * ratio) + const g = Math.round(start.g + (end.g - start.g) * ratio) + const b = Math.round(start.b + (end.b - start.b) * ratio) + colors.push(`rgb(${r}, ${g}, ${b})`) + } + + return colors +} + +const gradient = new ASCIIFontRenderable('gradient', { + text: 'GRADIENT', + font: 'shade', + fg: createGradient('GRADIENT', '#0000ff', '#ff00ff') +}) +``` + +### Menu Title + +```typescript +const menuTitle = new ASCIIFontRenderable('menu-title', { + text: 'MAIN MENU', + font: 'slick', + fg: '#00ff00', + bg: '#001100' +}) + +// Center it +menuTitle.alignSelf = 'center' +menuTitle.marginTop = 2 +menuTitle.marginBottom = 2 +``` + +### Score Display + +```typescript +class ScoreDisplay extends GroupRenderable { + private label: ASCIIFontRenderable + private score: ASCIIFontRenderable + private _value = 0 + + constructor(id: string) { + super(id, { + flexDirection: 'column', + alignItems: 'center' + }) + + this.label = new ASCIIFontRenderable('label', { + text: 'SCORE', + font: 'tiny', + fg: '#ffff00' + }) + + this.score = new ASCIIFontRenderable('score', { + text: '000000', + font: 'block', + fg: '#ffffff' + }) + + this.appendChild(this.label) + this.appendChild(this.score) + } + + set value(score: number) { + this._value = score + this.score.text = score.toString().padStart(6, '0') + + // Flash effect on score change + this.score.fg = '#ffff00' + setTimeout(() => { + this.score.fg = '#ffffff' + }, 200) + } + + get value(): number { + return this._value + } +} +``` + +### ASCII Art Logo + +```typescript +const logo = new GroupRenderable('logo', { + flexDirection: 'column', + alignItems: 'center', + padding: 2 +}) + +const line1 = new ASCIIFontRenderable('line1', { + text: 'OPEN', + font: 'block', + fg: '#00aaff' +}) + +const line2 = new ASCIIFontRenderable('line2', { + text: 'TUI', + font: 'shade', + fg: '#00ff00' +}) + +logo.appendChild(line1) +logo.appendChild(line2) +``` + +### Loading Screen + +```typescript +class LoadingScreen extends GroupRenderable { + private title: ASCIIFontRenderable + private dots = 0 + + constructor(id: string) { + super(id, { + width: '100%', + height: '100%', + justifyContent: 'center', + alignItems: 'center' + }) + + this.title = new ASCIIFontRenderable('loading', { + text: 'LOADING', + font: 'block', + fg: '#00ff00' + }) + + this.appendChild(this.title) + this.startAnimation() + } + + private startAnimation() { + setInterval(() => { + this.dots = (this.dots + 1) % 4 + const dotStr = '.'.repeat(this.dots) + this.title.text = 'LOADING' + dotStr + }, 500) + } +} +``` + +### Game Title with Subtitle + +```typescript +const gameTitle = new GroupRenderable('game-title', { + flexDirection: 'column', + alignItems: 'center', + gap: 1 +}) + +const mainTitle = new ASCIIFontRenderable('main', { + text: 'SPACE', + font: 'block', + fg: ['#0000ff', '#0066ff', '#00aaff', '#00ddff', '#00ffff'] +}) + +const subTitle = new ASCIIFontRenderable('sub', { + text: 'INVADERS', + font: 'shade', + fg: '#ff0000' +}) + +const tagline = new TextRenderable('tagline', { + content: 'Press ENTER to start', + fg: '#999999' +}) + +gameTitle.appendChild(mainTitle) +gameTitle.appendChild(subTitle) +gameTitle.appendChild(tagline) +``` + +## Text Selection + +ASCIIFontRenderable supports text selection when `selectable` is true: + +```typescript +const selectableTitle = new ASCIIFontRenderable('title', { + text: 'SELECT ME', + font: 'tiny', + selectable: true, + selectionBg: '#0066cc', + selectionFg: '#ffffff' +}) + +// Check if has selection +if (selectableTitle.hasSelection()) { + const selected = selectableTitle.getSelectedText() + console.log('Selected:', selected) +} +``` + +## Performance Considerations + +1. **Font Rendering**: ASCII fonts are pre-rendered to a frame buffer +2. **Size**: Larger fonts consume more screen space and memory +3. **Color Arrays**: Using color arrays has minimal performance impact +4. **Updates**: Changing text or font triggers a full re-render + +## Dimensions + +The component automatically calculates its dimensions based on: +- Font size +- Text length +- Font style + +Dimensions update automatically when text or font changes. + +## Integration + +```typescript +// Complete example: Game menu +const menu = new BoxRenderable('menu', { + width: 60, + height: 30, + borderStyle: 'double', + backgroundColor: '#1a1a1a' +}) + +const content = new GroupRenderable('content', { + flexDirection: 'column', + alignItems: 'center', + padding: 2, + gap: 2 +}) + +const title = new ASCIIFontRenderable('title', { + text: 'MAIN MENU', + font: 'block', + fg: '#00ff00' +}) + +const options = new Select('options', { + options: ['New Game', 'Load Game', 'Settings', 'Exit'], + width: 30, + selectedBg: '#003366' +}) + +content.appendChild(title) +content.appendChild(options) +menu.appendChild(content) +``` + +## Limitations + +1. **Character Set**: Limited to ASCII characters +2. **Font Selection**: Only 4 built-in fonts available +3. **Scaling**: Cannot dynamically scale fonts +4. **Line Breaks**: Single line only (no multi-line support) \ No newline at end of file diff --git a/packages/core/docs/api/components/box.md b/packages/core/docs/api/components/box.md new file mode 100644 index 000000000..d8713eeaa --- /dev/null +++ b/packages/core/docs/api/components/box.md @@ -0,0 +1,270 @@ +# BoxRenderable + +A container component with borders, background color, and optional title. + +## Class: `BoxRenderable` + +```typescript +import { BoxRenderable } from '@opentui/core' + +const box = new BoxRenderable('my-box', { + width: 40, + height: 10, + borderStyle: 'rounded', + backgroundColor: '#1a1a1a', + borderColor: '#00ff00' +}) +``` + +## Constructor + +### `new BoxRenderable(id: string, options: BoxOptions)` + +## Options + +### `BoxOptions` + +Extends [`RenderableOptions`](../renderable.md#renderableoptions) with: + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `backgroundColor` | `string \| RGBA` | `'transparent'` | Background color | +| `borderStyle` | `BorderStyle` | `'single'` | Border style preset | +| `border` | `boolean \| BorderSides[]` | `true` | Which borders to show | +| `borderColor` | `string \| RGBA` | `'#FFFFFF'` | Border color | +| `focusedBorderColor` | `string \| RGBA` | `'#00AAFF'` | Border color when focused | +| `customBorderChars` | `BorderCharacters` | - | Custom border characters | +| `shouldFill` | `boolean` | `true` | Fill background | +| `title` | `string` | - | Optional title text | +| `titleAlignment` | `'left' \| 'center' \| 'right'` | `'left'` | Title alignment | + +### Border Styles + +Available border style presets: + +- `'single'` - Single line borders `┌─┐│└┘` +- `'double'` - Double line borders `╔═╗║╚╝` +- `'rounded'` - Rounded corners `╭─╮│╰╯` +- `'bold'` - Bold lines `┏━┓┃┗┛` +- `'dotted'` - Dotted lines (custom chars) +- `'dashed'` - Dashed lines (custom chars) + +### Border Sides + +Control which borders to display: + +```typescript +type BorderSides = 'top' | 'right' | 'bottom' | 'left' + +// Examples: +border: true // All borders +border: false // No borders +border: ['top', 'bottom'] // Only top and bottom +border: ['left'] // Only left border +``` + +### Custom Border Characters + +Define custom border characters: + +```typescript +interface BorderCharacters { + topLeft: string + top: string + topRight: string + right: string + bottomRight: string + bottom: string + bottomLeft: string + left: string +} + +// Example: +customBorderChars: { + topLeft: '╭', + top: '─', + topRight: '╮', + right: '│', + bottomRight: '╯', + bottom: '─', + bottomLeft: '╰', + left: '│' +} +``` + +## Properties + +### Styling Properties + +| Property | Type | Description | +|----------|------|-------------| +| `backgroundColor` | `RGBA` | Get/set background color | +| `border` | `boolean \| BorderSides[]` | Get/set border configuration | +| `borderStyle` | `BorderStyle` | Get/set border style | +| `borderColor` | `RGBA` | Get/set border color | +| `focusedBorderColor` | `RGBA` | Get/set focused border color | +| `title` | `string \| undefined` | Get/set title text | +| `titleAlignment` | `'left' \| 'center' \| 'right'` | Get/set title alignment | +| `shouldFill` | `boolean` | Whether to fill background | + +## Methods + +BoxRenderable inherits all methods from [`Renderable`](../renderable.md). It doesn't add any additional public methods. + +Properties like `borderStyle`, `title`, and `titleAlignment` can be set directly: + +```typescript +box.borderStyle = 'double' +box.title = 'Settings' +box.titleAlignment = 'center' +``` + +## Examples + +### Basic Box + +```typescript +const box = new BoxRenderable('box', { + width: 30, + height: 10, + borderStyle: 'single', + backgroundColor: '#222222' +}) +``` + +### Box with Title + +```typescript +const dialog = new BoxRenderable('dialog', { + width: 50, + height: 15, + borderStyle: 'double', + title: 'Confirm Action', + titleAlignment: 'center', + backgroundColor: '#1a1a1a', + borderColor: '#ffff00' +}) +``` + +### Focused Box + +```typescript +const input = new BoxRenderable('input-box', { + width: 40, + height: 3, + borderStyle: 'rounded', + borderColor: '#666666', + focusedBorderColor: '#00ff00' +}) + +// Border color changes when focused +input.on('focused', () => { + console.log('Box focused') +}) +``` + +### Custom Borders + +```typescript +const custom = new BoxRenderable('custom', { + width: 25, + height: 8, + customBorderChars: { + topLeft: '╔', + top: '═', + topRight: '╗', + right: '║', + bottomRight: '╝', + bottom: '═', + bottomLeft: '╚', + left: '║' + }, + borderColor: '#00ffff' +}) +``` + +### Partial Borders + +```typescript +const partial = new BoxRenderable('partial', { + width: 30, + height: 10, + border: ['top', 'bottom'], + borderStyle: 'bold', + backgroundColor: '#333333' +}) +``` + +### Nested Boxes + +```typescript +const outer = new BoxRenderable('outer', { + width: 60, + height: 20, + borderStyle: 'double', + backgroundColor: '#111111', + padding: 1 +}) + +const inner = new BoxRenderable('inner', { + width: '100%', + height: '100%', + borderStyle: 'single', + backgroundColor: '#222222', + margin: 2 +}) + +outer.appendChild(inner) +``` + +### Dynamic Styling + +```typescript +const status = new BoxRenderable('status', { + width: 40, + height: 5, + borderStyle: 'rounded' +}) + +// Change appearance based on state +function setStatus(type: 'success' | 'warning' | 'error') { + switch (type) { + case 'success': + status.backgroundColor = '#004400' + status.borderColor = '#00ff00' + break + case 'warning': + status.backgroundColor = '#444400' + status.borderColor = '#ffff00' + break + case 'error': + status.backgroundColor = '#440000' + status.borderColor = '#ff0000' + break + } +} +``` + +## Layout Considerations + +BoxRenderable automatically applies padding for borders: + +- Single-line borders: 1 character padding on each side +- The padding is internal and doesn't affect the specified width/height +- Child components are positioned inside the border area + +```typescript +const box = new BoxRenderable('box', { + width: 20, + height: 10, + border: true +}) + +// Actual content area is 18x8 (20-2 for borders, 10-2 for borders) +const text = new TextRenderable('text', { + content: 'Content inside' +}) + +box.appendChild(text) +// Text will be positioned inside the borders +``` \ No newline at end of file diff --git a/packages/core/docs/api/components/framebuffer.md b/packages/core/docs/api/components/framebuffer.md new file mode 100644 index 000000000..b51906c26 --- /dev/null +++ b/packages/core/docs/api/components/framebuffer.md @@ -0,0 +1,394 @@ +# FrameBufferRenderable + +An offscreen rendering buffer component that allows for advanced rendering techniques like double buffering, caching, and compositing. + +## Class: `FrameBufferRenderable` + +```typescript +import { FrameBufferRenderable } from '@opentui/core' + +const frameBuffer = new FrameBufferRenderable('buffer', { + width: 80, + height: 24, + respectAlpha: true +}) +``` + +## Constructor + +### `new FrameBufferRenderable(id: string, options: FrameBufferOptions)` + +## Options + +### `FrameBufferOptions` + +Extends [`RenderableOptions`](../renderable.md#renderableoptions) with: + +| Property | Type | Required | Default | Description | +|----------|------|----------|---------|-------------| +| `width` | `number` | Yes | - | Buffer width in columns | +| `height` | `number` | Yes | - | Buffer height in rows | +| `respectAlpha` | `boolean` | No | `false` | Enable alpha blending | + +## Properties + +### Buffer Properties + +| Property | Type | Description | +|----------|------|-------------| +| `frameBuffer` | `OptimizedBuffer` | The internal buffer instance | +| `respectAlpha` | `boolean` | Whether alpha blending is enabled | + +## Methods + +All methods from [`Renderable`](../renderable.md) plus: + +### Direct Buffer Access + +The `frameBuffer` property provides direct access to the `OptimizedBuffer` instance, allowing you to: + +```typescript +// Clear the buffer +frameBuffer.frameBuffer.clear() + +// Draw text +frameBuffer.frameBuffer.drawText('Hello', 0, 0) + +// Fill rectangle +frameBuffer.frameBuffer.fillRect(0, 0, 10, 5, '#ff0000') + +// Draw borders +frameBuffer.frameBuffer.drawBorder(0, 0, 20, 10, 'single') +``` + +## Use Cases + +### 1. Caching Complex Renders + +```typescript +class CachedComponent extends FrameBufferRenderable { + private isDirty = true + + constructor(id: string, width: number, height: number) { + super(id, { width, height, respectAlpha: false }) + } + + update() { + if (this.isDirty) { + // Clear and redraw only when needed + this.frameBuffer.clear() + this.drawComplexContent() + this.isDirty = false + } + } + + private drawComplexContent() { + // Expensive rendering operations + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + const char = this.calculateComplexChar(x, y) + this.frameBuffer.drawText(char, x, y) + } + } + } + + markDirty() { + this.isDirty = true + } +} +``` + +### 2. Animation Buffers + +```typescript +class AnimatedSprite extends FrameBufferRenderable { + private frames: string[][] = [] + private currentFrame = 0 + + constructor(id: string, frames: string[][]) { + const width = Math.max(...frames.map(f => f[0]?.length || 0)) + const height = Math.max(...frames.map(f => f.length)) + + super(id, { width, height, respectAlpha: true }) + this.frames = frames + this.drawFrame(0) + } + + nextFrame() { + this.currentFrame = (this.currentFrame + 1) % this.frames.length + this.drawFrame(this.currentFrame) + } + + private drawFrame(index: number) { + this.frameBuffer.clear() + const frame = this.frames[index] + + frame.forEach((line, y) => { + this.frameBuffer.drawText(line, 0, y) + }) + } +} + +// Usage +const sprite = new AnimatedSprite('sprite', [ + [' O ', ' /|\\ ', ' / \\ '], // Frame 1 + [' O ', ' \\|/ ', ' / \\ '], // Frame 2 + [' O ', ' /|\\ ', ' \\ / '], // Frame 3 +]) + +setInterval(() => sprite.nextFrame(), 100) +``` + +### 3. Layered Rendering + +```typescript +class LayeredView extends GroupRenderable { + private background: FrameBufferRenderable + private midground: FrameBufferRenderable + private foreground: FrameBufferRenderable + + constructor(id: string, width: number, height: number) { + super(id, { width, height }) + + // Create layers with alpha support + this.background = new FrameBufferRenderable('bg', { + width, height, + respectAlpha: false + }) + + this.midground = new FrameBufferRenderable('mid', { + width, height, + respectAlpha: true + }) + + this.foreground = new FrameBufferRenderable('fg', { + width, height, + respectAlpha: true + }) + + // Stack layers + this.appendChild(this.background) + this.appendChild(this.midground) + this.appendChild(this.foreground) + } + + drawBackground(pattern: string) { + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + this.background.frameBuffer.drawText(pattern, x, y) + } + } + } + + drawMidground(content: string, x: number, y: number) { + this.midground.frameBuffer.clear() + this.midground.frameBuffer.drawText(content, x, y) + } + + drawForeground(overlay: string, x: number, y: number) { + this.foreground.frameBuffer.clear() + this.foreground.frameBuffer.drawText(overlay, x, y) + } +} +``` + +### 4. Viewport/Camera + +```typescript +class Viewport extends FrameBufferRenderable { + private worldBuffer: OptimizedBuffer + private cameraX = 0 + private cameraY = 0 + + constructor(id: string, viewWidth: number, viewHeight: number, worldWidth: number, worldHeight: number) { + super(id, { width: viewWidth, height: viewHeight }) + + // Create larger world buffer + this.worldBuffer = OptimizedBuffer.create(worldWidth, worldHeight) + this.renderWorld() + } + + private renderWorld() { + // Draw a large world + for (let y = 0; y < this.worldBuffer.height; y++) { + for (let x = 0; x < this.worldBuffer.width; x++) { + const char = ((x + y) % 2 === 0) ? '.' : ' ' + this.worldBuffer.drawText(char, x, y) + } + } + + // Add some landmarks + this.worldBuffer.drawText('START', 0, 0) + this.worldBuffer.drawText('END', this.worldBuffer.width - 5, this.worldBuffer.height - 1) + } + + moveCamera(dx: number, dy: number) { + this.cameraX = Math.max(0, Math.min(this.cameraX + dx, this.worldBuffer.width - this.width)) + this.cameraY = Math.max(0, Math.min(this.cameraY + dy, this.worldBuffer.height - this.height)) + this.updateView() + } + + private updateView() { + this.frameBuffer.clear() + + // Copy visible portion of world to viewport + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + const worldX = this.cameraX + x + const worldY = this.cameraY + y + + if (worldX < this.worldBuffer.width && worldY < this.worldBuffer.height) { + const cell = this.worldBuffer.getCell(worldX, worldY) + this.frameBuffer.setCell(x, y, cell) + } + } + } + } +} +``` + +### 5. Effects Buffer + +```typescript +class EffectsBuffer extends FrameBufferRenderable { + constructor(id: string, width: number, height: number) { + super(id, { width, height, respectAlpha: true }) + } + + applyGlowEffect(text: string, x: number, y: number, color: string) { + // Draw glow layers + const glowColors = ['#330000', '#660000', '#990000', color] + + glowColors.forEach((glowColor, layer) => { + const offset = glowColors.length - layer - 1 + + // Draw in all directions for glow + for (let dy = -offset; dy <= offset; dy++) { + for (let dx = -offset; dx <= offset; dx++) { + if (dx !== 0 || dy !== 0) { + this.frameBuffer.drawText(text, x + dx, y + dy, { + fg: glowColor, + alpha: 0.3 + }) + } + } + } + }) + + // Draw main text on top + this.frameBuffer.drawText(text, x, y, { fg: color }) + } + + applyNoiseEffect(intensity: number = 0.1) { + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + if (Math.random() < intensity) { + const noise = String.fromCharCode(0x2591 + Math.floor(Math.random() * 3)) + this.frameBuffer.drawText(noise, x, y, { + fg: '#333333', + alpha: 0.5 + }) + } + } + } + } +} +``` + +### 6. Double Buffering + +```typescript +class DoubleBuffered extends Renderable { + private frontBuffer: FrameBufferRenderable + private backBuffer: FrameBufferRenderable + + constructor(id: string, width: number, height: number) { + super(id, { width, height }) + + this.frontBuffer = new FrameBufferRenderable('front', { + width, height, + respectAlpha: false + }) + + this.backBuffer = new FrameBufferRenderable('back', { + width, height, + respectAlpha: false + }) + + this.appendChild(this.frontBuffer) + } + + draw(drawFn: (buffer: OptimizedBuffer) => void) { + // Clear back buffer + this.backBuffer.frameBuffer.clear() + + // Draw to back buffer + drawFn(this.backBuffer.frameBuffer) + + // Swap buffers + this.swapBuffers() + } + + private swapBuffers() { + // Swap the buffers + [this.frontBuffer, this.backBuffer] = [this.backBuffer, this.frontBuffer] + + // Update which one is visible + this.removeAllChildren() + this.appendChild(this.frontBuffer) + } +} +``` + +## Performance Considerations + +1. **Buffer Size**: Large buffers consume more memory. Size appropriately. + +2. **Alpha Blending**: `respectAlpha: true` has a performance cost. Only use when needed. + +3. **Clearing**: Clear buffers only when necessary, not every frame. + +4. **Reuse Buffers**: Reuse FrameBufferRenderables instead of creating new ones. + +5. **Batch Operations**: Group multiple draw operations together. + +## Best Practices + +1. **Use for Caching**: Cache complex, static content that doesn't change often. + +2. **Animation Frames**: Pre-render animation frames into buffers. + +3. **Layering**: Use multiple buffers with alpha for layered effects. + +4. **Viewport Pattern**: Use for scrollable areas larger than the screen. + +5. **Memory Management**: Destroy buffers when no longer needed: +```typescript +frameBuffer.destroy() +``` + +## Integration with Buffered Renderables + +Note: The base `Renderable` class also supports buffering via the `buffered: true` option. Consider using that for simpler cases: + +```typescript +// Simple buffering +const buffered = new BoxRenderable('box', { + buffered: true, // Uses internal frame buffer + width: 20, + height: 10 +}) + +// vs explicit FrameBuffer for advanced control +const frameBuffer = new FrameBufferRenderable('buffer', { + width: 20, + height: 10, + respectAlpha: true +}) +``` + +Use `FrameBufferRenderable` when you need: +- Direct buffer manipulation +- Alpha blending control +- Custom rendering pipelines +- Multi-buffer techniques \ No newline at end of file diff --git a/packages/core/docs/api/components/group.md b/packages/core/docs/api/components/group.md new file mode 100644 index 000000000..7da7b811b --- /dev/null +++ b/packages/core/docs/api/components/group.md @@ -0,0 +1,358 @@ +# GroupRenderable + +A container component for organizing child components with flexbox layout support. + +## Class: `GroupRenderable` + +```typescript +import { GroupRenderable } from '@opentui/core' + +const group = new GroupRenderable('my-group', { + flexDirection: 'row', + gap: 2, + padding: 1 +}) +``` + +## Constructor + +### `new GroupRenderable(id: string, options: RenderableOptions)` + +## Options + +### `GroupRenderable Options` + +Uses standard [`RenderableOptions`](../renderable.md#renderableoptions). GroupRenderable is specifically designed for layout management. + +## Key Features + +GroupRenderable is a pure layout container that: +- Renders no visual content itself +- Manages child component positioning using Yoga flexbox +- Provides layout control through flexbox properties +- Supports nested layouts for complex UIs + +## Layout Properties + +All flexbox properties from [`Renderable`](../renderable.md) are particularly useful: + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `flexDirection` | `'row' \| 'column' \| 'row-reverse' \| 'column-reverse'` | `'column'` | Main axis direction | +| `justifyContent` | `'flex-start' \| 'flex-end' \| 'center' \| 'space-between' \| 'space-around' \| 'space-evenly'` | `'flex-start'` | Main axis alignment | +| `alignItems` | `'flex-start' \| 'flex-end' \| 'center' \| 'baseline' \| 'stretch'` | `'stretch'` | Cross axis alignment | +| `flexWrap` | `'nowrap' \| 'wrap' \| 'wrap-reverse'` | `'nowrap'` | Wrap behavior | +| `gap` | `number` | `0` | Space between items | +| `padding` | `number \| string` | `0` | Inner spacing | +| `margin` | `number \| string` | `0` | Outer spacing | + +## Examples + +### Horizontal Layout + +```typescript +const row = new GroupRenderable('row', { + flexDirection: 'row', + justifyContent: 'space-between', + width: '100%', + height: 3 +}) + +const left = new TextRenderable('left', { content: 'Left' }) +const center = new TextRenderable('center', { content: 'Center' }) +const right = new TextRenderable('right', { content: 'Right' }) + +row.appendChild(left) +row.appendChild(center) +row.appendChild(right) +``` + +### Vertical Layout + +```typescript +const column = new GroupRenderable('column', { + flexDirection: 'column', + alignItems: 'center', + width: '100%', + height: '100%', + padding: 2 +}) + +const header = new TextRenderable('header', { + content: 'Header', + marginBottom: 1 +}) + +const content = new BoxRenderable('content', { + width: '80%', + flexGrow: 1, + borderStyle: 'single' +}) + +const footer = new TextRenderable('footer', { + content: 'Footer', + marginTop: 1 +}) + +column.appendChild(header) +column.appendChild(content) +column.appendChild(footer) +``` + +### Grid Layout + +```typescript +const grid = new GroupRenderable('grid', { + flexDirection: 'row', + flexWrap: 'wrap', + width: '100%', + gap: 1 +}) + +for (let i = 0; i < 9; i++) { + const cell = new BoxRenderable(`cell-${i}`, { + width: '33%', + height: 5, + borderStyle: 'single', + backgroundColor: '#333333' + }) + grid.appendChild(cell) +} +``` + +### Nested Groups + +```typescript +const app = new GroupRenderable('app', { + flexDirection: 'column', + width: '100%', + height: '100%' +}) + +const header = new GroupRenderable('header', { + flexDirection: 'row', + justifyContent: 'space-between', + padding: 1, + height: 3 +}) + +const body = new GroupRenderable('body', { + flexDirection: 'row', + flexGrow: 1 +}) + +const sidebar = new GroupRenderable('sidebar', { + flexDirection: 'column', + width: 20, + padding: 1 +}) + +const main = new GroupRenderable('main', { + flexDirection: 'column', + flexGrow: 1, + padding: 2 +}) + +body.appendChild(sidebar) +body.appendChild(main) +app.appendChild(header) +app.appendChild(body) +``` + +### Centered Content + +```typescript +const centerContainer = new GroupRenderable('center', { + width: '100%', + height: '100%', + justifyContent: 'center', + alignItems: 'center' +}) + +const dialog = new BoxRenderable('dialog', { + width: 40, + height: 10, + borderStyle: 'double', + padding: 2 +}) + +centerContainer.appendChild(dialog) +``` + +### List Layout + +```typescript +const list = new GroupRenderable('list', { + flexDirection: 'column', + width: '100%', + gap: 1, + padding: 1 +}) + +const items = ['Item 1', 'Item 2', 'Item 3', 'Item 4'] + +items.forEach((text, index) => { + const item = new GroupRenderable(`item-${index}`, { + flexDirection: 'row', + alignItems: 'center', + padding: 1 + }) + + const bullet = new TextRenderable(`bullet-${index}`, { + content: '• ', + marginRight: 1 + }) + + const label = new TextRenderable(`label-${index}`, { + content: text + }) + + item.appendChild(bullet) + item.appendChild(label) + list.appendChild(item) +}) +``` + +### Responsive Layout + +```typescript +const responsive = new GroupRenderable('responsive', { + flexDirection: 'row', + width: '100%', + height: '100%' +}) + +const leftPanel = new BoxRenderable('left', { + minWidth: 20, + maxWidth: 40, + width: '25%', + height: '100%', + borderStyle: 'single' +}) + +const mainPanel = new BoxRenderable('main', { + flexGrow: 1, + height: '100%', + borderStyle: 'single' +}) + +const rightPanel = new BoxRenderable('right', { + width: '20%', + minWidth: 15, + height: '100%', + borderStyle: 'single' +}) + +responsive.appendChild(leftPanel) +responsive.appendChild(mainPanel) +responsive.appendChild(rightPanel) +``` + +### Form Layout + +```typescript +const form = new GroupRenderable('form', { + flexDirection: 'column', + width: 50, + padding: 2, + gap: 2 +}) + +// Form fields +const fields = [ + { label: 'Name:', id: 'name' }, + { label: 'Email:', id: 'email' }, + { label: 'Message:', id: 'message' } +] + +fields.forEach(field => { + const row = new GroupRenderable(`${field.id}-row`, { + flexDirection: 'row', + alignItems: 'center' + }) + + const label = new TextRenderable(`${field.id}-label`, { + content: field.label, + width: 10 + }) + + const input = new InputRenderable(`${field.id}-input`, { + flexGrow: 1, + placeholder: `Enter ${field.id}...` + }) + + row.appendChild(label) + row.appendChild(input) + form.appendChild(row) +}) + +// Submit button +const buttonRow = new GroupRenderable('button-row', { + flexDirection: 'row', + justifyContent: 'flex-end', + marginTop: 1 +}) + +const submitBtn = new BoxRenderable('submit', { + width: 15, + height: 3, + borderStyle: 'rounded', + backgroundColor: '#0066cc' +}) + +buttonRow.appendChild(submitBtn) +form.appendChild(buttonRow) +``` + +## Best Practices + +1. **Use for Layout Only**: GroupRenderable should be used purely for layout organization, not for visual styling. + +2. **Combine with BoxRenderable**: For containers that need borders or backgrounds, use BoxRenderable instead. + +3. **Leverage Flexbox**: Take advantage of flexbox properties for responsive layouts. + +4. **Nest Groups**: Create complex layouts by nesting multiple GroupRenderables. + +5. **Performance**: Groups have minimal overhead as they don't render any visual content themselves. + +## Common Patterns + +### Toolbar +```typescript +const toolbar = new GroupRenderable('toolbar', { + flexDirection: 'row', + justifyContent: 'space-between', + padding: 1, + height: 3 +}) +``` + +### Sidebar Layout +```typescript +const layout = new GroupRenderable('layout', { + flexDirection: 'row', + width: '100%', + height: '100%' +}) + +const sidebar = new GroupRenderable('sidebar', { + width: 25, + flexDirection: 'column' +}) + +const content = new GroupRenderable('content', { + flexGrow: 1, + flexDirection: 'column' +}) +``` + +### Card Grid +```typescript +const cardGrid = new GroupRenderable('cards', { + flexDirection: 'row', + flexWrap: 'wrap', + gap: 2, + padding: 2 +}) +``` \ No newline at end of file diff --git a/packages/core/docs/api/components/input.md b/packages/core/docs/api/components/input.md new file mode 100644 index 000000000..fb4a5f716 --- /dev/null +++ b/packages/core/docs/api/components/input.md @@ -0,0 +1,289 @@ +# InputRenderable + +Text input component with support for placeholders, validation, and keyboard navigation. + +## Class: `InputRenderable` + +```typescript +import { InputRenderable } from '@opentui/core' + +const input = new InputRenderable('my-input', { + placeholder: 'Enter your name...', + width: 30, + value: '' +}) +``` + +## Constructor + +### `new InputRenderable(id: string, options: InputRenderableOptions)` + +## Options + +### `InputRenderableOptions` + +Extends [`RenderableOptions`](../renderable.md#renderableoptions) with: + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `value` | `string` | `''` | Initial input value | +| `placeholder` | `string` | `''` | Placeholder text when empty | +| `textColor` | `string \| RGBA` | `'#FFFFFF'` | Text color | +| `backgroundColor` | `string \| RGBA` | `'transparent'` | Background color | +| `placeholderColor` | `string \| RGBA` | `'#666666'` | Placeholder text color | +| `cursorColor` | `string \| RGBA` | `'#FFFFFF'` | Cursor color | +| `focusedBackgroundColor` | `string \| RGBA` | `'#1a1a1a'` | Background when focused | +| `focusedTextColor` | `string \| RGBA` | `'#FFFFFF'` | Text color when focused | +| `maxLength` | `number` | `1000` | Maximum character length | + +## Properties + +### Value Properties + +| Property | Type | Description | +|----------|------|-------------| +| `value` | `string` | Get/set input value | +| `placeholder` | `string` | Set placeholder text | +| `cursorPosition` | `number` | Set cursor position | +| `maxLength` | `number` | Set maximum character limit | + +### Style Properties + +| Property | Type | Description | +|----------|------|-------------| +| `textColor` | `ColorInput` | Set text color | +| `backgroundColor` | `ColorInput` | Set background color | +| `placeholderColor` | `ColorInput` | Set placeholder color | +| `cursorColor` | `ColorInput` | Set cursor color | +| `focusedBackgroundColor` | `ColorInput` | Set focused background color | +| `focusedTextColor` | `ColorInput` | Set focused text color | + +## Methods + +All methods from [`Renderable`](../renderable.md) plus: + +### `handleKeyPress(key: ParsedKey | string): boolean` +Handle keyboard input (called internally by the renderer). + +### `focus(): void` +Focus the input and show cursor. + +```typescript +input.focus() +``` + +### `blur(): void` +Remove focus and hide cursor. Emits `change` event if value changed. + +```typescript +input.blur() +``` + +## Events + +InputRenderable emits the following events: + +| Event | Data | Description | +|-------|------|-------------| +| `input` | `value: string` | Value changed during typing | +| `change` | `value: string` | Value committed (on blur or enter) | +| `enter` | `value: string` | Enter key pressed | + +## Keyboard Shortcuts + +| Key | Action | +|-----|--------| +| `Left Arrow` | Move cursor left | +| `Right Arrow` | Move cursor right | +| `Home` | Move to start | +| `End` | Move to end | +| `Backspace` | Delete before cursor | +| `Delete` | Delete at cursor | +| `Enter` | Submit value and emit events | +| Any printable character | Insert at cursor position | + +## Examples + +### Basic Input + +```typescript +const nameInput = new InputRenderable('name', { + placeholder: 'Enter name...', + width: 30 +}) + +nameInput.on('submit', (value) => { + console.log('Name entered:', value) +}) +``` + +### Password Input + +```typescript +const passwordInput = new Input('password', { + placeholder: 'Enter password...', + password: true, + width: 30 +}) + +passwordInput.on('submit', (value) => { + console.log('Password length:', value.length) +}) +``` + +### Input with Validation + +```typescript +const emailInput = new Input('email', { + placeholder: 'user@example.com', + width: 40 +}) + +emailInput.on('change', (value) => { + const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) + emailInput.fg = isValid ? '#00ff00' : '#ff0000' +}) +``` + +### Multi-line Input + +```typescript +const textArea = new Input('textarea', { + multiline: true, + width: 50, + height: 10, + placeholder: 'Enter your message...' +}) + +textArea.on('change', (value) => { + const lines = value.split('\n').length + console.log(`Lines: ${lines}`) +}) +``` + +### Input with Max Length + +```typescript +const limitedInput = new Input('limited', { + placeholder: 'Max 10 characters', + maxLength: 10, + width: 30 +}) + +limitedInput.on('change', (value) => { + const remaining = 10 - value.length + console.log(`${remaining} characters remaining`) +}) +``` + +### Styled Input + +```typescript +const styledInput = new Input('styled', { + placeholder: 'Styled input', + width: 30, + fg: '#00ff00', + bg: '#1a1a1a', + placeholderFg: '#666666', + cursorBg: '#00ff00', + focusedBg: '#2a2a2a' +}) +``` + +### Form with Multiple Inputs + +```typescript +const form = new GroupRenderable('form', { + flexDirection: 'column', + padding: 2 +}) + +const usernameInput = new Input('username', { + placeholder: 'Username', + width: '100%', + marginBottom: 1 +}) + +const passwordInput = new Input('password', { + placeholder: 'Password', + password: true, + width: '100%', + marginBottom: 1 +}) + +const submitButton = new BoxRenderable('submit', { + width: '100%', + height: 3, + borderStyle: 'rounded', + backgroundColor: '#0066cc' +}) + +form.appendChild(usernameInput) +form.appendChild(passwordInput) +form.appendChild(submitButton) + +// Handle form submission +passwordInput.on('submit', () => { + const username = usernameInput.value + const password = passwordInput.value + console.log('Login:', { username, password }) +}) +``` + +### Read-only Input + +```typescript +const readOnlyInput = new Input('readonly', { + value: 'This cannot be edited', + editable: false, + width: 30, + fg: '#999999' +}) +``` + +### Dynamic Placeholder + +```typescript +const searchInput = new Input('search', { + placeholder: 'Search...', + width: 40 +}) + +// Update placeholder based on context +function setSearchContext(context: string) { + searchInput.placeholder = `Search ${context}...` +} + +setSearchContext('users') // "Search users..." +setSearchContext('files') // "Search files..." +``` + +## Focus Management + +Input components can receive keyboard focus: + +```typescript +const input = new Input('input', { + width: 30 +}) + +// Request focus +input.focus() + +// Check if focused +if (input.focused) { + console.log('Input has focus') +} + +// Remove focus +input.blur() + +// Focus events +input.on('focus', () => { + console.log('Input focused') +}) + +input.on('blur', () => { + console.log('Input blurred') +}) +``` \ No newline at end of file diff --git a/packages/core/docs/api/components/select.md b/packages/core/docs/api/components/select.md new file mode 100644 index 000000000..6ef3c17d3 --- /dev/null +++ b/packages/core/docs/api/components/select.md @@ -0,0 +1,425 @@ +# Select + +A selection list component for choosing from multiple options with keyboard navigation. + +## Class: `Select` + +```typescript +import { Select } from '@opentui/core' + +const select = new Select('my-select', { + options: ['Option 1', 'Option 2', 'Option 3'], + width: 30, + height: 10 +}) +``` + +## Constructor + +### `new Select(id: string, options: SelectOptions)` + +## Options + +### `SelectOptions` + +Extends [`RenderableOptions`](../renderable.md#renderableoptions) with: + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `options` | `string[]` | `[]` | List of selectable options | +| `selectedIndex` | `number` | `0` | Initially selected index | +| `fg` | `ColorInput` | `'#ffffff'` | Text color | +| `bg` | `ColorInput` | `'transparent'` | Background color | +| `selectedFg` | `ColorInput` | `'#000000'` | Selected item text color | +| `selectedBg` | `ColorInput` | `'#00aaff'` | Selected item background | +| `focusedSelectedFg` | `ColorInput` | - | Focused selected text color | +| `focusedSelectedBg` | `ColorInput` | - | Focused selected background | +| `scrollbar` | `boolean` | `true` | Show scrollbar indicator | +| `wrap` | `boolean` | `false` | Wrap selection at boundaries | + +## Properties + +### Selection Properties + +| Property | Type | Description | +|----------|------|-------------| +| `options` | `string[]` | Get/set option list | +| `selectedIndex` | `number` | Get/set selected index | +| `selectedOption` | `string \| undefined` | Get selected option text | +| `length` | `number` | Number of options | + +### Display Properties + +| Property | Type | Description | +|----------|------|-------------| +| `scrollOffset` | `number` | Current scroll position | +| `visibleItems` | `number` | Number of visible items | + +## Methods + +All methods from [`Renderable`](../renderable.md) plus: + +### `setOptions(options: string[]): void` +Update the option list. + +```typescript +select.setOptions(['New 1', 'New 2', 'New 3']) +``` + +### `selectNext(): void` +Select the next option. + +```typescript +select.selectNext() +``` + +### `selectPrevious(): void` +Select the previous option. + +```typescript +select.selectPrevious() +``` + +### `selectFirst(): void` +Select the first option. + +```typescript +select.selectFirst() +``` + +### `selectLast(): void` +Select the last option. + +```typescript +select.selectLast() +``` + +### `selectIndex(index: number): void` +Select option by index. + +```typescript +select.selectIndex(2) +``` + +### `getSelectedOption(): string | undefined` +Get the currently selected option text. + +```typescript +const selected = select.getSelectedOption() +``` + +### `scrollToSelected(): void` +Scroll to make selected item visible. + +```typescript +select.scrollToSelected() +``` + +## Events + +Select emits the following events: + +| Event | Data | Description | +|-------|------|-------------| +| `change` | `{index: number, value: string}` | Selection changed | +| `select` | `{index: number, value: string}` | Item selected (Enter key) | +| `scroll` | `offset: number` | List scrolled | + +## Keyboard Shortcuts + +| Key | Action | +|-----|--------| +| `Up Arrow` / `k` | Select previous item | +| `Down Arrow` / `j` | Select next item | +| `Home` / `g` | Select first item | +| `End` / `G` | Select last item | +| `Page Up` | Scroll up one page | +| `Page Down` | Scroll down one page | +| `Enter` / `Space` | Confirm selection | + +## Examples + +### Basic Select + +```typescript +const select = new Select('select', { + options: ['Apple', 'Banana', 'Cherry', 'Date'], + width: 20, + height: 5 +}) + +select.on('select', ({ value }) => { + console.log('Selected:', value) +}) +``` + +### Styled Select + +```typescript +const styledSelect = new Select('styled', { + options: ['Red', 'Green', 'Blue', 'Yellow'], + width: 25, + height: 6, + fg: '#cccccc', + bg: '#1a1a1a', + selectedFg: '#ffffff', + selectedBg: '#0066cc', + focusedSelectedBg: '#0088ff' +}) +``` + +### Dynamic Options + +```typescript +const dynamicSelect = new Select('dynamic', { + options: [], + width: 30, + height: 10 +}) + +// Load options asynchronously +async function loadOptions() { + const response = await fetch('/api/options') + const options = await response.json() + dynamicSelect.setOptions(options) +} + +loadOptions() +``` + +### Menu System + +```typescript +const menu = new Select('menu', { + options: [ + 'New File', + 'Open File', + 'Save', + 'Save As...', + '---', + 'Settings', + 'Exit' + ], + width: 20, + height: 8 +}) + +menu.on('select', ({ value }) => { + switch (value) { + case 'New File': + createNewFile() + break + case 'Open File': + openFileDialog() + break + case 'Exit': + process.exit(0) + break + } +}) +``` + +### File Browser + +```typescript +const fileBrowser = new Select('files', { + options: [], + width: 40, + height: 20, + selectedBg: '#003366' +}) + +// Load directory contents +import { readdirSync } from 'fs' + +function loadDirectory(path: string) { + const entries = readdirSync(path, { withFileTypes: true }) + const options = entries.map(entry => { + const prefix = entry.isDirectory() ? '📁 ' : '📄 ' + return prefix + entry.name + }) + fileBrowser.setOptions(options) +} + +loadDirectory('./') + +fileBrowser.on('select', ({ value }) => { + const name = value.substring(2) // Remove icon + if (value.startsWith('📁')) { + loadDirectory(`./${name}`) + } else { + openFile(name) + } +}) +``` + +### Multi-Column Select + +```typescript +const table = new Select('table', { + options: [ + 'Name Age City', + '─────────────────────────', + 'Alice 28 NYC', + 'Bob 32 LA', + 'Charlie 25 SF', + 'Diana 30 CHI' + ], + width: 30, + height: 8, + selectedBg: '#334455' +}) + +// Skip header rows when selecting +table.on('change', ({ index }) => { + if (index <= 1) { + table.selectIndex(2) // Skip to first data row + } +}) +``` + +### Filtered Select + +```typescript +class FilteredSelect extends GroupRenderable { + private input: InputRenderable + private select: Select + private allOptions: string[] + + constructor(id: string, options: string[]) { + super(id, { + flexDirection: 'column', + width: 30, + height: 15 + }) + + this.allOptions = options + + this.input = new InputRenderable('filter', { + placeholder: 'Filter...', + width: '100%', + marginBottom: 1 + }) + + this.select = new Select('list', { + options: options, + width: '100%', + flexGrow: 1 + }) + + this.appendChild(this.input) + this.appendChild(this.select) + + this.input.on('input', (value) => { + this.filterOptions(value) + }) + } + + private filterOptions(filter: string) { + const filtered = this.allOptions.filter(opt => + opt.toLowerCase().includes(filter.toLowerCase()) + ) + this.select.setOptions(filtered) + } +} +``` + +### Command Palette + +```typescript +const commands = new Select('commands', { + options: [ + '> Open File', + '> Save File', + '> Find in Files', + '> Replace in Files', + '> Toggle Terminal', + '> Settings', + '> Keyboard Shortcuts' + ], + width: 50, + height: 10, + selectedFg: '#000000', + selectedBg: '#ffff00' +}) + +commands.on('select', ({ value }) => { + const command = value.substring(2) // Remove "> " + executeCommand(command) +}) +``` + +### Paginated Select + +```typescript +class PaginatedSelect extends Select { + private pageSize = 10 + private currentPage = 0 + private allItems: string[] = [] + + setItems(items: string[]) { + this.allItems = items + this.showPage(0) + } + + showPage(page: number) { + const start = page * this.pageSize + const end = start + this.pageSize + const pageItems = this.allItems.slice(start, end) + + if (pageItems.length > 0) { + this.currentPage = page + this.setOptions([ + `Page ${page + 1} of ${Math.ceil(this.allItems.length / this.pageSize)}`, + '─────────────', + ...pageItems + ]) + } + } + + nextPage() { + const maxPage = Math.ceil(this.allItems.length / this.pageSize) - 1 + if (this.currentPage < maxPage) { + this.showPage(this.currentPage + 1) + } + } + + previousPage() { + if (this.currentPage > 0) { + this.showPage(this.currentPage - 1) + } + } +} +``` + +## Styling + +The Select component supports various styling options: + +```typescript +const customSelect = new Select('custom', { + options: ['Option 1', 'Option 2', 'Option 3'], + width: 30, + height: 10, + + // Normal state + fg: '#aaaaaa', + bg: '#111111', + + // Selected state + selectedFg: '#ffffff', + selectedBg: '#003366', + + // Focused + selected state + focusedSelectedFg: '#ffff00', + focusedSelectedBg: '#0066cc' +}) +``` + +## Performance + +For large lists: +- The component automatically handles scrolling and viewport rendering +- Only visible items are rendered for performance +- Consider implementing virtual scrolling for lists with thousands of items +- Use pagination or filtering for better UX with large datasets \ No newline at end of file diff --git a/packages/core/docs/api/components/tab-select.md b/packages/core/docs/api/components/tab-select.md new file mode 100644 index 000000000..76c309e80 --- /dev/null +++ b/packages/core/docs/api/components/tab-select.md @@ -0,0 +1,454 @@ +# TabSelectRenderable + +A horizontal tab-style selection component with optional descriptions, similar to a menu bar or tab navigation. + +## Class: `TabSelectRenderable` + +```typescript +import { TabSelectRenderable } from '@opentui/core' + +const tabSelect = new TabSelectRenderable('tabs', { + options: [ + { name: 'General', description: 'General settings' }, + { name: 'Advanced', description: 'Advanced configuration' }, + { name: 'Security', description: 'Security options' } + ], + tabWidth: 20, + showDescription: true +}) +``` + +## Constructor + +### `new TabSelectRenderable(id: string, options: TabSelectRenderableOptions)` + +## Options + +### `TabSelectRenderableOptions` + +Extends [`RenderableOptions`](../renderable.md#renderableoptions) (excluding `height` which is auto-calculated) with: + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `options` | `TabSelectOption[]` | `[]` | Tab options array | +| `tabWidth` | `number` | `20` | Width of each tab | +| `backgroundColor` | `ColorInput` | `'transparent'` | Background color | +| `textColor` | `ColorInput` | `'#FFFFFF'` | Text color | +| `focusedBackgroundColor` | `ColorInput` | `'#1a1a1a'` | Focused background | +| `focusedTextColor` | `ColorInput` | `'#FFFFFF'` | Focused text color | +| `selectedBackgroundColor` | `ColorInput` | `'#334455'` | Selected tab background | +| `selectedTextColor` | `ColorInput` | `'#FFFF00'` | Selected tab text color | +| `selectedDescriptionColor` | `ColorInput` | `'#CCCCCC'` | Selected description color | +| `showScrollArrows` | `boolean` | `true` | Show scroll indicators | +| `showDescription` | `boolean` | `true` | Show description line | +| `showUnderline` | `boolean` | `true` | Show underline separator | +| `wrapSelection` | `boolean` | `false` | Wrap at boundaries | + +### `TabSelectOption` + +```typescript +interface TabSelectOption { + name: string // Tab title + description: string // Tab description + value?: any // Optional associated value +} +``` + +## Properties + +### Selection Properties + +| Property | Type | Description | +|----------|------|-------------| +| `options` | `TabSelectOption[]` | Tab options | +| `selectedIndex` | `number` | Currently selected index | +| `selectedOption` | `TabSelectOption \| undefined` | Currently selected option | +| `maxVisibleTabs` | `number` | Maximum visible tabs based on width | + +## Methods + +All methods from [`Renderable`](../renderable.md) plus: + +### `setOptions(options: TabSelectOption[]): void` +Update the tab options. + +```typescript +tabSelect.setOptions([ + { name: 'File', description: 'File operations' }, + { name: 'Edit', description: 'Edit operations' } +]) +``` + +### `setSelectedIndex(index: number): void` +Select a tab by index. + +```typescript +tabSelect.setSelectedIndex(1) +``` + +### `getSelectedOption(): TabSelectOption | undefined` +Get the currently selected option. + +```typescript +const selected = tabSelect.getSelectedOption() +console.log(selected?.name, selected?.description) +``` + +### `handleKeyPress(key: ParsedKey): boolean` +Handle keyboard input (called internally). + +## Events + +TabSelectRenderable emits the following events: + +| Event | Data | Description | +|-------|------|-------------| +| `selectionChanged` | `TabSelectOption` | Selection changed | +| `itemSelected` | `TabSelectOption` | Item selected (Enter key) | + +## Keyboard Shortcuts + +| Key | Action | +|-----|--------| +| `Left Arrow` | Select previous tab | +| `Right Arrow` | Select next tab | +| `Home` | Select first tab | +| `End` | Select last tab | +| `Enter` | Confirm selection | + +## Display Behavior + +The component height is automatically calculated based on options: +- Base height: 1 line for tab names +- +1 line if `showUnderline` is true +- +1 line if `showDescription` is true + +The component displays tabs horizontally with: +- Fixed width tabs (controlled by `tabWidth`) +- Automatic scrolling when tabs exceed visible width +- Optional scroll arrows to indicate more tabs +- Selected tab highlighting + +## Examples + +### Basic Tab Selection + +```typescript +const tabSelect = new TabSelectRenderable('tabs', { + options: [ + { name: 'Home', description: 'Go to home screen' }, + { name: 'Settings', description: 'Configure application' }, + { name: 'About', description: 'About this app' } + ], + width: 60, + tabWidth: 20 +}) + +tabSelect.on('selectionChanged', (option) => { + console.log(`Selected: ${option.name} - ${option.description}`) +}) + +tabSelect.on('itemSelected', (option) => { + console.log(`Confirmed selection: ${option.name}`) + // Navigate to the selected section +}) +``` + +### Styled Tab Selection + +```typescript +const styledTabs = new TabSelectRenderable('styled', { + options: [ + { name: 'Tab 1', description: 'First tab' }, + { name: 'Tab 2', description: 'Second tab' }, + { name: 'Tab 3', description: 'Third tab' } + ], + width: 70, + tabWidth: 23, + backgroundColor: '#1a1a1a', + textColor: '#999999', + selectedBackgroundColor: '#0066cc', + selectedTextColor: '#ffffff', + selectedDescriptionColor: '#aaaaff', + showDescription: true, + showUnderline: true +}) +``` + +### Menu Bar Example + +```typescript +const menuBar = new TabSelectRenderable('menu', { + options: [ + { name: 'File', description: 'File operations', value: 'file' }, + { name: 'Edit', description: 'Edit operations', value: 'edit' }, + { name: 'View', description: 'View options', value: 'view' }, + { name: 'Help', description: 'Get help', value: 'help' } + ], + width: '100%', + tabWidth: 15, + showDescription: false, // Hide descriptions for menu bar + showUnderline: true, + backgroundColor: '#2a2a2a', + selectedBackgroundColor: '#0066cc' +}) + +menuBar.on('itemSelected', (option) => { + switch (option.value) { + case 'file': + openFileMenu() + break + case 'edit': + openEditMenu() + break + case 'view': + toggleViewOptions() + break + case 'help': + showHelp() + break + } +}) +``` + +### Navigation Tabs + +```typescript +const navigationTabs = new TabSelectRenderable('nav', { + options: [ + { name: 'General', description: 'General settings' }, + { name: 'Appearance', description: 'Theme and colors' }, + { name: 'Keyboard', description: 'Shortcuts' }, + { name: 'Advanced', description: 'Advanced options' } + ], + width: 80, + tabWidth: 20, + selectedBackgroundColor: '#003366', + selectedTextColor: '#ffff00' +}) + +// Use with a content area that changes based on selection +const contentArea = new GroupRenderable('content', { + flexDirection: 'column', + padding: 2 +}) + +navigationTabs.on('selectionChanged', (option) => { + // Clear and update content area + contentArea.removeAllChildren() + + const title = new TextRenderable('title', { + content: option.name, + fg: '#00ff00', + marginBottom: 1 + }) + + const description = new TextRenderable('desc', { + content: option.description, + fg: '#cccccc' + }) + + contentArea.appendChild(title) + contentArea.appendChild(description) +}) +``` + +### Tool Selection + +```typescript +const toolSelect = new TabSelectRenderable('tools', { + options: [ + { name: '🔨 Build', description: 'Build project', value: 'build' }, + { name: '🐛 Debug', description: 'Start debugger', value: 'debug' }, + { name: '✅ Test', description: 'Run tests', value: 'test' }, + { name: '📦 Package', description: 'Create package', value: 'package' } + ], + width: 80, + tabWidth: 20, + showDescription: true, + focusedBackgroundColor: '#1a1a1a', + selectedBackgroundColor: '#00aa00', + selectedTextColor: '#000000' +}) + +toolSelect.on('itemSelected', async (option) => { + const statusText = new TextRenderable('status', { + content: `Running ${option.name}...`, + fg: '#ffff00' + }) + + switch (option.value) { + case 'build': + await runBuild() + break + case 'debug': + await startDebugger() + break + case 'test': + await runTests() + break + case 'package': + await createPackage() + break + } +}) { + private openFiles: Map = new Map() + + openFile(filename: string, content: string) { + // Check if already open + const existingIndex = this.tabs.indexOf(filename) + if (existingIndex >= 0) { + this.selectTab(existingIndex) + return + } + + // Create editor for file + const editor = new InputRenderable(`editor-${filename}`, { + value: content, + multiline: true, + width: '100%', + height: '100%' + }) + + editor.on('change', (value) => { + this.openFiles.set(filename, value) + this.markAsModified(filename) + }) + + this.addTab(filename, editor) + this.openFiles.set(filename, content) + } + + markAsModified(filename: string) { + const index = this.tabs.indexOf(filename) + if (index >= 0 && !this.tabs[index].endsWith('*')) { + this.tabs[index] = filename + ' *' + } + } + + saveFile(index: number) { + const filename = this.tabs[index].replace(' *', '') + const content = this.openFiles.get(filename) + // Save logic here + this.tabs[index] = filename // Remove asterisk + } +} +``` + +### Compact Navigation + +```typescript +const compactNav = new TabSelectRenderable('compact', { + tabs: [], + width: '100%', + height: '100%', + tabWidth: 20, // Fixed width tabs + tabBarHeight: 4 +}) + +function createWebView(url: string): Renderable { + const view = new GroupRenderable('view', { + flexDirection: 'column', + padding: 1 + }) + + const urlBar = new TextRenderable('url', { + content: `🌐 ${url}`, + fg: '#666666', + marginBottom: 1 + }) + + const content = new TextRenderable('content', { + content: `Loading ${url}...`, + fg: '#ffffff' + }) + + view.appendChild(urlBar) + view.appendChild(content) + + return view +} + +// Add new tab function +function newTab(url: string = 'about:blank') { + const title = url.length > 15 ? url.substring(0, 12) + '...' : url + const content = createWebView(url) + browserTabs.addTab(title, content) +} + +// Initial tab +newTab('https://example.com') +``` + +### Wizard Interface + +```typescript +const wizard = new TabSelect('wizard', { + tabs: ['Step 1: Setup', 'Step 2: Configure', 'Step 3: Confirm'], + width: 60, + height: 25, + selectedIndex: 0 +}) + +// Disable manual tab switching +wizard.handleKeyPress = (key) => { + // Only allow programmatic navigation + return false +} + +// Navigation buttons +const nextButton = new BoxRenderable('next', { + content: 'Next →', + width: 10, + height: 3 +}) + +const prevButton = new BoxRenderable('prev', { + content: '← Previous', + width: 10, + height: 3 +}) + +nextButton.on('click', () => { + if (wizard.selectedIndex < wizard.tabs.length - 1) { + wizard.selectTab(wizard.selectedIndex + 1) + } +}) + +prevButton.on('click', () => { + if (wizard.selectedIndex > 0) { + wizard.selectTab(wizard.selectedIndex - 1) + } +}) +``` + +## Styling + +### Tab Bar Customization + +```typescript +const customTabs = new TabSelect('custom', { + tabs: ['Tab 1', 'Tab 2'], + tabBarHeight: 4, + tabWidth: 15, + tabBarBg: '#0a0a0a', + borderStyle: 'double' +}) +``` + +### Content Area Styling + +```typescript +// Access content area directly +tabs.contentArea.backgroundColor = '#1a1a1a' +tabs.contentArea.padding = 2 +``` + +## Best Practices + +1. **Limit tab count**: Too many tabs can be hard to navigate +2. **Use clear titles**: Keep tab titles short and descriptive +3. **Lazy loading**: Load tab content only when selected for performance +4. **Keyboard shortcuts**: Implement number keys for quick tab access +5. **Visual feedback**: Use distinct colors for active/inactive tabs +6. **Content caching**: Cache expensive content instead of recreating \ No newline at end of file diff --git a/packages/core/docs/api/components/text.md b/packages/core/docs/api/components/text.md new file mode 100644 index 000000000..3022f91a0 --- /dev/null +++ b/packages/core/docs/api/components/text.md @@ -0,0 +1,295 @@ +# TextRenderable + +Component for displaying styled text with support for colors, attributes, and text selection. + +## Class: `TextRenderable` + +```typescript +import { TextRenderable } from '@opentui/core' + +const text = new TextRenderable('my-text', { + content: 'Hello, World!', + fg: '#00ff00', + bg: '#1a1a1a' +}) +``` + +## Constructor + +### `new TextRenderable(id: string, options: TextOptions)` + +## Options + +### `TextOptions` + +Extends [`RenderableOptions`](../renderable.md#renderableoptions) with: + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `content` | `StyledText \| string` | `''` | Text content to display | +| `fg` | `string \| RGBA` | `'#ffffff'` | Default foreground color | +| `bg` | `string \| RGBA` | `'transparent'` | Default background color | +| `selectionBg` | `string \| RGBA` | - | Selection background color | +| `selectionFg` | `string \| RGBA` | - | Selection foreground color | +| `selectable` | `boolean` | `true` | Enable text selection | +| `attributes` | `number` | `0` | Text attributes (bold, italic, etc.) | + +## Properties + +### Content Properties + +| Property | Type | Description | +|----------|------|-------------| +| `content` | `StyledText \| string` | Get/set text content | +| `fg` | `RGBA` | Get/set foreground color | +| `bg` | `RGBA` | Get/set background color | +| `selectable` | `boolean` | Enable/disable selection | +| `plainText` | `string` | Plain text without styling (read-only) | + +## StyledText Format + +StyledText allows rich text formatting with colors and attributes: + +```typescript +import { stringToStyledText, StyledText } from '@opentui/core' + +// Simple string +const simple = stringToStyledText('Plain text') + +// With inline styles +const styled: StyledText = { + type: 'styled', + children: [ + { text: 'Normal ' }, + { text: 'Bold', bold: true }, + { text: ' and ', italic: true }, + { text: 'Colored', fg: '#00ff00', bg: '#0000ff' } + ] +} +``` + +### Text Attributes + +Available text attributes (can be combined): + +```typescript +interface TextStyle { + text: string + fg?: string | RGBA // Foreground color + bg?: string | RGBA // Background color + bold?: boolean // Bold text + italic?: boolean // Italic text + underline?: boolean // Underlined text + strikethrough?: boolean // Strikethrough text + dim?: boolean // Dimmed text + inverse?: boolean // Inverted colors + blink?: boolean // Blinking text (terminal support varies) +} +``` + +## Methods + +All methods from [`Renderable`](../renderable.md) plus: + +### `getSelectedText(): string` +Get currently selected text. + +```typescript +const selected = text.getSelectedText() +``` + +### `hasSelection(): boolean` +Check if any text is currently selected. + +```typescript +if (text.hasSelection()) { + const selected = text.getSelectedText() +} +``` + +### `shouldStartSelection(x: number, y: number): boolean` +Check if selection should start at given coordinates (used internally). + +### `onSelectionChanged(selection: SelectionState | null): boolean` +Handle selection state changes (used internally by the selection system). + +## Text Selection + +TextRenderable supports text selection when `selectable` is true: + +```typescript +const text = new TextRenderable('text', { + content: 'Selectable text content', + selectable: true, + selectionBg: '#0066cc', + selectionFg: '#ffffff' +}) + +// Selection is handled automatically with mouse +// The renderer manages selection state globally +renderer.on('mouse', (event) => { + // Selection is handled internally by the renderer +}) + +// Check if text has selection +if (text.hasSelection()) { + const selected = text.getSelectedText() + console.log('Selected text:', selected) +} +``` + +## Examples + +### Basic Text + +```typescript +const label = new TextRenderable('label', { + content: 'Username:', + fg: '#ffffff' +}) +``` + +### Styled Text + +```typescript +const styled = new TextRenderable('styled', { + content: { + type: 'styled', + children: [ + { text: 'Status: ' }, + { text: 'Online', fg: '#00ff00', bold: true } + ] + } +}) +``` + +### Multi-line Text + +```typescript +const paragraph = new TextRenderable('paragraph', { + content: `This is a paragraph of text +that spans multiple lines +and wraps automatically`, + width: 40, + fg: '#cccccc' +}) +``` + +### Dynamic Updates + +```typescript +const counter = new TextRenderable('counter', { + content: 'Count: 0', + fg: '#ffff00' +}) + +let count = 0 +setInterval(() => { + count++ + counter.content = `Count: ${count}` +}, 1000) +``` + +### Error Messages + +```typescript +const error = new TextRenderable('error', { + content: { + type: 'styled', + children: [ + { text: '✗ ', fg: '#ff0000', bold: true }, + { text: 'Error: ', fg: '#ff6666', bold: true }, + { text: 'File not found', fg: '#ffcccc' } + ] + }, + bg: '#330000' +}) +``` + +### Code Display + +```typescript +const code = new TextRenderable('code', { + content: { + type: 'styled', + children: [ + { text: 'function ', fg: '#ff79c6' }, + { text: 'hello', fg: '#50fa7b' }, + { text: '() {\n' }, + { text: ' return ', fg: '#ff79c6' }, + { text: '"Hello, World!"', fg: '#f1fa8c' }, + { text: '\n}' } + ] + }, + bg: '#282a36', + padding: 1 +}) +``` + +### Status Indicators + +```typescript +function createStatusText(status: 'idle' | 'loading' | 'success' | 'error') { + const indicators = { + idle: { symbol: '○', color: '#666666' }, + loading: { symbol: '◔', color: '#ffff00' }, + success: { symbol: '●', color: '#00ff00' }, + error: { symbol: '✗', color: '#ff0000' } + } + + const { symbol, color } = indicators[status] + + return new TextRenderable('status', { + content: { + type: 'styled', + children: [ + { text: symbol + ' ', fg: color }, + { text: status.toUpperCase(), fg: color, bold: true } + ] + } + }) +} +``` + +### Selectable List + +```typescript +const items = ['Option 1', 'Option 2', 'Option 3'] +const list = new GroupRenderable('list', { + flexDirection: 'column' +}) + +items.forEach((item, index) => { + const text = new TextRenderable(`item-${index}`, { + content: item, + selectable: true, + selectionBg: '#0066cc', + padding: 1 + }) + + text.on('click', () => { + console.log(`Selected: ${item}`) + }) + + list.appendChild(text) +}) +``` + +## Text Wrapping + +Text automatically wraps based on the component width: + +```typescript +const wrapped = new TextRenderable('wrapped', { + content: 'This is a long line of text that will automatically wrap when it exceeds the width of the component', + width: 30, + fg: '#ffffff' +}) +``` + +## Performance Considerations + +- Text rendering is optimized with internal text buffers +- Styled text segments are cached for efficient rendering +- Large text content is handled efficiently with viewport clipping +- Updates only trigger re-renders when content actually changes \ No newline at end of file diff --git a/packages/core/docs/api/index.md b/packages/core/docs/api/index.md new file mode 100644 index 000000000..4346941df --- /dev/null +++ b/packages/core/docs/api/index.md @@ -0,0 +1,227 @@ +# OpenTUI API Reference + +Welcome to the OpenTUI API reference documentation. This comprehensive guide provides detailed information about all the components, functions, and classes available in the OpenTUI Core package. + +## Core Rendering System + +The core rendering system is the foundation of OpenTUI, providing the main renderer, layout management, and lifecycle handling. + +### [Rendering Engine](./core/rendering.md) +- **CliRenderer**: The main rendering engine that manages terminal output and input +- **RenderContext**: Context information for rendering components +- **Lifecycle Methods**: Methods for controlling the rendering lifecycle +- **Performance Monitoring**: Tools for monitoring and optimizing performance + +### [Buffer System](./buffer.md) +- **OptimizedBuffer**: High-performance buffer for terminal rendering +- **Drawing Operations**: Methods for drawing text, boxes, and other elements +- **Color Blending**: Functions for blending colors with alpha transparency +- **Buffer Composition**: Techniques for composing multiple buffers + +### [Native Integration](./native-integration.md) +- **Zig Code**: Performance-critical code written in Zig +- **FFI Bindings**: JavaScript bindings to the native code +- **Platform Support**: Pre-built binaries for different platforms + +## Components + +OpenTUI provides a variety of components for building terminal user interfaces. + +### [Renderables](./components/renderables.md) +- **BoxRenderable**: Container component with optional borders and background +- **TextRenderable**: Component for displaying text with styling +- **InputRenderable**: Text input field component +- **SelectRenderable**: Dropdown selection component +- **TabSelectRenderable**: Tabbed interface component +- **FrameBufferRenderable**: Canvas for custom drawing + +### [ASCII Font](./renderables/ascii-font.md) +- **ASCIIFontRenderable**: Component for rendering ASCII art fonts +- **Built-in Fonts**: Several built-in ASCII art fonts +- **Custom Fonts**: Create and use custom ASCII art fonts + +## Styling + +OpenTUI provides rich styling capabilities for text, borders, and other visual elements. + +### [Text Styling](./styling/text-styling.md) +- **Text Formatting**: Format text with colors and attributes +- **Color Management**: Foreground and background color handling + +### [Border Styles](./lib/border.md) +- **Built-in Borders**: Various border styles for boxes and containers +- **Custom Borders**: Create your own border styles +- **Border Characters**: Control individual border characters + +### [Styled Text](./lib/styled-text.md) +- **StyledText Class**: Create rich text with different styles +- **Text Attributes**: Bold, italic, underline, and other text attributes +- **Syntax Highlighting**: Create syntax highlighters for code + +### [HAST Styled Text](./lib/hast-styled-text.md) +- **HAST Structure**: Hypertext Abstract Syntax Tree for complex text styling +- **SyntaxStyle**: Define and merge text styles +- **Syntax Highlighting**: Create syntax highlighters with HAST + +### [Text Selection](./lib/selection.md) +- **Selection System**: Select and copy text from the terminal +- **TextSelectionHelper**: Handle text selection for standard text components +- **ASCIIFontSelectionHelper**: Handle text selection for ASCII font components + +### [TrackedNode](./lib/tracked-node.md) +- **Layout Tree**: Build and manage layout trees with Yoga +- **Node Hierarchy**: Parent-child relationships between nodes +- **Percentage Dimensions**: Support for percentage-based dimensions + +## Input Handling + +OpenTUI provides comprehensive input handling for keyboard and mouse events. + +### [Input System](./input/input.md) +- **Keyboard Input**: Handling key presses and keyboard shortcuts +- **Mouse Input**: Handling mouse clicks, movement, and scroll events +- **Focus Management**: Managing focus between components +- **Drag and Drop**: Support for drag and drop operations + +### [Key Handler](./lib/keyhandler.md) +- **Key Events**: Processing and handling keyboard events +- **Key Combinations**: Support for key combinations like Ctrl+C +- **Navigation**: Keyboard-based navigation between components + +## Animation + +OpenTUI provides powerful animation capabilities for creating dynamic interfaces. + +### [Animation System](./animation/animation.md) +- **Animation Basics**: Core animation concepts and techniques +- **Easing Functions**: Various easing functions for smooth animations +- **Sprite Animation**: Frame-based animations from sprite sheets +- **Particle Effects**: Visual effects with particle systems +- **Physics-Based Animation**: Integration with physics engines + +### [Timeline](./animation/timeline.md) +- **Animation Sequencing**: Create complex animation sequences +- **Precise Timing**: Control animation timing with millisecond precision +- **Easing Functions**: Apply easing functions to animations +- **Nested Timelines**: Create hierarchical animation structures + +## Advanced Features + +OpenTUI includes advanced rendering capabilities for creating rich visual experiences. + +### [3D Rendering](./advanced/3d.md) +- **Three.js Integration**: Create 3D scenes in the terminal +- **WebGPU Integration**: High-performance graphics rendering +- **Sprite Rendering**: Display images in the terminal +- **Texture Loading**: Load and manage textures +- **Lighting and Materials**: Advanced lighting and materials + +### [WebGPU Shaders](./3d/shaders.md) +- **WGSL Shaders**: Write custom shaders in WebGPU Shading Language +- **Shader Effects**: Create visual effects with shaders +- **Supersampling**: Improve rendering quality with supersampling + +### [Post-Processing Filters](./post/filters.md) +- **Basic Filters**: Scanlines, grayscale, sepia, invert, noise, and more +- **Advanced Effects**: Distortion, vignette, brightness, blur, and bloom +- **Combining Effects**: Create complex visual styles + +### [Sprite Animation](./3d/sprite-animation.md) +- **Sprite Animator**: Animate sprites with frame-based animations +- **Sprite Resource Manager**: Load and manage sprite resources +- **Particle Effects**: Create particle effects with sprites + +### [Physics Integration](./3d/physics.md) +- **Physics Adapters**: Integrate with Planck.js and Rapier physics engines +- **Rigid Bodies**: Create static, dynamic, and kinematic bodies +- **Joints and Constraints**: Connect bodies with joints and constraints +- **Collision Detection**: Detect and respond to collisions + +## Utilities + +OpenTUI provides various utility functions and classes for common tasks. + +### [Utility Functions](./utils/utilities.md) +- **Color Utilities**: Tools for working with colors +- **ANSI Terminal Utilities**: Working with ANSI escape sequences +- **Buffer Utilities**: High-performance buffers for terminal rendering +- **Layout Utilities**: Tools for working with the layout system +- **Border Utilities**: Utilities for working with borders + +### [Console](./utils/console.md) +- **Debug Console**: Terminal console for debugging and logging +- **Logging**: Log messages with different levels and colors +- **Console Window**: Create a console window within your application + +### [Output Capture](./utils/output-capture.md) +- **Stdout Capture**: Capture and redirect standard output +- **Stderr Capture**: Capture and redirect standard error +- **Child Process Capture**: Capture output from child processes + +## Quick Start Example + +Here's a simple example to get you started with OpenTUI: + +```typescript +import { createCliRenderer, BoxRenderable, TextRenderable } from '@opentui/core'; + +async function main() { + // Create the renderer + const renderer = await createCliRenderer({ + targetFps: 30, + useAlternateScreen: true + }); + + // Access the root element + const { root } = renderer; + + // Create a container box + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + borderStyle: 'double', + borderColor: '#3498db' + }); + + // Add a text element + const text = new TextRenderable('title', { + content: 'Hello, OpenTUI!', + fg: '#ffffff', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 + }); + + // Build the component tree + container.add(text); + root.add(container); + + // Start the rendering loop + renderer.start(); + + // Handle cleanup on exit + process.on('SIGINT', () => { + renderer.destroy(); + process.exit(0); + }); +} + +main().catch(console.error); +``` + +## Framework Integration + +OpenTUI provides integration with popular JavaScript frameworks. + +### [React Integration](./react/reconciler.md) +- **React Reconciler**: Build terminal UIs using React components and hooks +- **React Components**: Pre-built React components for common UI elements +- **React Hooks**: Custom React hooks for terminal-specific functionality +- **Event Handling**: React-style event handling for terminal events + +## Additional Resources + +- [Getting Started Guide](../getting-started.md): A beginner's guide to OpenTUI +- [Guides](../guides/index.md): In-depth guides on specific topics +- [Examples](../examples/index.md): Example applications built with OpenTUI +- [Source Code Examples](https://github.com/yourusername/opentui/tree/main/packages/core/src/examples): Examples in the source code repository diff --git a/packages/core/docs/api/input/input.md b/packages/core/docs/api/input/input.md new file mode 100644 index 000000000..74cd10b4c --- /dev/null +++ b/packages/core/docs/api/input/input.md @@ -0,0 +1,742 @@ +# Input Handling API + +OpenTUI provides a comprehensive input handling system for keyboard and mouse events, allowing you to create interactive terminal applications. + +## Keyboard Input + +### Key Events + +The renderer emits key events that you can listen for: + +```typescript +import { createCliRenderer } from '@opentui/core'; + +const renderer = await createCliRenderer(); + +// Listen for raw key events +renderer.on('key', (data) => { + console.log('Key data:', data.toString()); +}); +``` + +### ParsedKey + +The `ParsedKey` interface provides a structured representation of keyboard input: + +```typescript +interface ParsedKey { + sequence: string; // Raw key sequence + name: string; // Key name (e.g., 'a', 'return', 'escape') + ctrl: boolean; // Whether Ctrl was pressed + meta: boolean; // Whether Meta/Alt was pressed + shift: boolean; // Whether Shift was pressed + code?: string; // Key code for special keys +} +``` + +### KeyHandler + +The `KeyHandler` class provides a higher-level API for handling keyboard input: + +```typescript +import { getKeyHandler, parseKeypress } from '@opentui/core'; + +// Get the global key handler +const keyHandler = getKeyHandler(); + +// Listen for keypress events +keyHandler.on('keypress', (key) => { + console.log('Key pressed:', key.name); + + if (key.ctrl && key.name === 'c') { + console.log('Ctrl+C pressed'); + } +}); + +// Parse a key sequence manually +const key = parseKeypress('\x1b[A'); // Up arrow key +console.log(key); // { name: 'up', sequence: '\x1b[A', ... } +``` + +### Key Names + +Common key names you can check for: + +| Category | Key Names | +|----------|-----------| +| Letters | `'a'` through `'z'` | +| Numbers | `'0'` through `'9'` | +| Special | `'space'`, `'backspace'`, `'tab'`, `'return'`, `'escape'` | +| Function | `'f1'` through `'f12'` | +| Navigation | `'up'`, `'down'`, `'left'`, `'right'`, `'home'`, `'end'`, `'pageup'`, `'pagedown'` | +| Editing | `'delete'`, `'insert'` | + +### Example: Handling Keyboard Shortcuts + +```typescript +import { getKeyHandler, Renderable } from '@opentui/core'; + +class KeyboardShortcutsComponent extends Renderable { + constructor(id: string, options = {}) { + super(id, options); + this.focusable = true; // Enable focus to receive key events + } + + handleKeyPress(key: ParsedKey): boolean { + // Check for specific keys + if (key.name === 'return') { + console.log('Enter key pressed'); + return true; + } + + // Check for key combinations + if (key.ctrl && key.name === 's') { + console.log('Ctrl+S pressed - Save action'); + return true; + } + + if (key.ctrl && key.shift && key.name === 'p') { + console.log('Ctrl+Shift+P pressed - Print action'); + return true; + } + + // Check for arrow keys + if (key.name === 'up' || key.name === 'down' || + key.name === 'left' || key.name === 'right') { + console.log(`Arrow key pressed: ${key.name}`); + return true; + } + + return false; // Key not handled + } +} +``` + +### Creating a Global Keyboard Shortcut Handler + +```typescript +import { getKeyHandler } from '@opentui/core'; + +// Define a keyboard shortcut handler +function setupGlobalShortcuts() { + const keyHandler = getKeyHandler(); + + const shortcuts = { + 'ctrl+q': () => { + console.log('Quit application'); + process.exit(0); + }, + 'ctrl+s': () => { + console.log('Save action'); + }, + 'ctrl+o': () => { + console.log('Open action'); + }, + 'f1': () => { + console.log('Show help'); + } + }; + + keyHandler.on('keypress', (key) => { + // Build a key identifier string + let keyId = ''; + if (key.ctrl) keyId += 'ctrl+'; + if (key.meta) keyId += 'alt+'; + if (key.shift) keyId += 'shift+'; + keyId += key.name; + + // Check if we have a handler for this shortcut + if (shortcuts[keyId]) { + shortcuts[keyId](); + } + }); +} + +// Call this function to set up global shortcuts +setupGlobalShortcuts(); +``` + +## Mouse Input + +OpenTUI provides comprehensive mouse event handling for creating interactive interfaces. + +### Mouse Event Types + +| Event Type | Description | +|------------|-------------| +| `'down'` | Mouse button pressed | +| `'up'` | Mouse button released | +| `'click'` | Mouse click (down followed by up) | +| `'drag'` | Mouse moved while button pressed | +| `'drag-end'` | Mouse button released after dragging | +| `'move'` | Mouse moved without button pressed | +| `'over'` | Mouse entered a component | +| `'out'` | Mouse left a component | +| `'drop'` | Item dropped on a component | +| `'scroll'` | Mouse wheel scrolled | + +### MouseEvent Class + +The `MouseEvent` class provides information about mouse events: + +```typescript +class MouseEvent { + public readonly type: MouseEventType; // Event type + public readonly button: number; // Button number + public readonly x: number; // X coordinate + public readonly y: number; // Y coordinate + public readonly source?: Renderable; // Source component (for drag operations) + public readonly modifiers: { // Modifier keys + shift: boolean; + alt: boolean; + ctrl: boolean; + }; + public readonly scroll?: ScrollInfo; // Scroll information + public readonly target: Renderable | null; // Target component + + // Prevent event bubbling + public preventDefault(): void; +} +``` + +### MouseButton Enum + +```typescript +enum MouseButton { + LEFT = 0, + MIDDLE = 1, + RIGHT = 2, + WHEEL_UP = 4, + WHEEL_DOWN = 5, +} +``` + +### Handling Mouse Events + +Components can handle mouse events by overriding the `onMouseEvent` method: + +```typescript +import { BoxRenderable, MouseEvent, MouseButton } from '@opentui/core'; + +class ClickableBox extends BoxRenderable { + protected onMouseEvent(event: MouseEvent): void { + switch (event.type) { + case 'over': + this.borderColor = '#00ff00'; + break; + case 'out': + this.borderColor = '#ffffff'; + break; + case 'down': + if (event.button === MouseButton.LEFT) { + this.backgroundColor = '#555555'; + } + break; + case 'up': + this.backgroundColor = 'transparent'; + break; + case 'click': + console.log('Box clicked at', event.x, event.y); + // Emit a custom event + this.emit('activated'); + break; + } + } +} + +// Usage +const clickable = new ClickableBox('clickable', { + width: 20, + height: 5, + borderStyle: 'single' +}); + +// Listen for custom events +clickable.on('activated', () => { + console.log('Box was activated!'); +}); +``` + +### Drag and Drop + +OpenTUI supports drag and drop operations: + +```typescript +import { BoxRenderable, TextRenderable, MouseEvent, MouseButton } from '@opentui/core'; + +// Draggable item +class DraggableItem extends BoxRenderable { + private isDragging = false; + private startX = 0; + private startY = 0; + + constructor(id: string, options = {}) { + super(id, { + width: 10, + height: 3, + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222', + position: 'absolute', + ...options + }); + + // Add a label + const label = new TextRenderable(`${id}-label`, { + content: 'Drag me', + fg: '#ffffff', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 + }); + + this.add(label); + } + + protected onMouseEvent(event: MouseEvent): void { + switch (event.type) { + case 'down': + if (event.button === MouseButton.LEFT) { + this.isDragging = true; + this.startX = event.x - this.x; + this.startY = event.y - this.y; + this.borderColor = '#e74c3c'; + event.preventDefault(); // Capture the mouse + } + break; + + case 'drag': + if (this.isDragging) { + this.x = event.x - this.startX; + this.y = event.y - this.startY; + event.preventDefault(); + } + break; + + case 'drag-end': + this.isDragging = false; + this.borderColor = '#3498db'; + break; + + case 'over': + if (!this.isDragging) { + this.borderColor = '#2ecc71'; + } + break; + + case 'out': + if (!this.isDragging) { + this.borderColor = '#3498db'; + } + break; + } + } +} + +// Drop target +class DropTarget extends BoxRenderable { + constructor(id: string, options = {}) { + super(id, { + width: 20, + height: 10, + borderStyle: 'dashed', + borderColor: '#3498db', + backgroundColor: 'transparent', + ...options + }); + + // Add a label + const label = new TextRenderable(`${id}-label`, { + content: 'Drop here', + fg: '#ffffff', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 + }); + + this.add(label); + } + + protected onMouseEvent(event: MouseEvent): void { + switch (event.type) { + case 'over': + if (event.source) { + this.borderColor = '#2ecc71'; + this.borderStyle = 'double'; + } + break; + + case 'out': + this.borderColor = '#3498db'; + this.borderStyle = 'dashed'; + break; + + case 'drop': + if (event.source) { + this.borderColor = '#e74c3c'; + this.borderStyle = 'single'; + + console.log(`Item ${event.source.id} dropped on ${this.id}`); + this.emit('item-dropped', event.source); + + // Reset after a delay + setTimeout(() => { + this.borderColor = '#3498db'; + this.borderStyle = 'dashed'; + }, 1000); + } + break; + } + } +} + +// Usage +const draggable = new DraggableItem('draggable', { + x: 5, + y: 5 +}); + +const dropTarget = new DropTarget('dropTarget', { + x: 30, + y: 10 +}); + +dropTarget.on('item-dropped', (item) => { + console.log(`Handling drop of ${item.id}`); +}); + +// Add to the renderer +renderer.root.add(draggable); +renderer.root.add(dropTarget); +``` + +### Scroll Events + +Handle scroll events for scrollable components: + +```typescript +import { BoxRenderable, TextRenderable, MouseEvent } from '@opentui/core'; + +class ScrollableContent extends BoxRenderable { + private scrollOffset = 0; + private content: TextRenderable; + private maxScroll = 0; + + constructor(id: string, options = {}) { + super(id, { + width: 40, + height: 10, + borderStyle: 'single', + borderColor: '#3498db', + ...options + }); + + // Create long content + const longText = Array(30).fill(0).map((_, i) => `Line ${i + 1}`).join('\n'); + + this.content = new TextRenderable(`${id}-content`, { + content: longText, + fg: '#ffffff' + }); + + this.add(this.content); + this.maxScroll = 30 - this.height + 2; // Account for borders + } + + protected onMouseEvent(event: MouseEvent): void { + if (event.type === 'scroll' && event.scroll) { + // Handle scroll up/down + if (event.scroll.direction === 'up') { + this.scrollOffset = Math.max(0, this.scrollOffset - 1); + } else if (event.scroll.direction === 'down') { + this.scrollOffset = Math.min(this.maxScroll, this.scrollOffset + 1); + } + + // Update content position + this.content.top = -this.scrollOffset; + + event.preventDefault(); + } + } +} + +// Usage +const scrollable = new ScrollableContent('scrollable', { + x: 5, + y: 5 +}); + +renderer.root.add(scrollable); +``` + +## Focus Management + +OpenTUI provides a focus system for keyboard navigation between components. + +### Making Components Focusable + +```typescript +import { BoxRenderable } from '@opentui/core'; + +class FocusableBox extends BoxRenderable { + constructor(id: string, options = {}) { + super(id, options); + this.focusable = true; // Enable focus + } + + // Optional: Handle focus events + public focus(): void { + super.focus(); + this.borderColor = '#2ecc71'; + console.log(`${this.id} gained focus`); + } + + public blur(): void { + super.blur(); + this.borderColor = '#3498db'; + console.log(`${this.id} lost focus`); + } +} +``` + +### Focus Navigation + +Create a focus manager for keyboard navigation: + +```typescript +import { getKeyHandler, Renderable } from '@opentui/core'; + +class FocusManager { + private focusableElements: Renderable[] = []; + private currentFocusIndex: number = -1; + + constructor() { + const keyHandler = getKeyHandler(); + + keyHandler.on('keypress', (key) => { + if (key.name === 'tab') { + if (key.shift) { + this.focusPrevious(); + } else { + this.focusNext(); + } + } + }); + } + + public register(element: Renderable): void { + if (element.focusable) { + this.focusableElements.push(element); + } + } + + public unregister(element: Renderable): void { + const index = this.focusableElements.indexOf(element); + if (index !== -1) { + this.focusableElements.splice(index, 1); + if (this.currentFocusIndex >= this.focusableElements.length) { + this.currentFocusIndex = this.focusableElements.length - 1; + } + } + } + + public focusNext(): void { + if (this.focusableElements.length === 0) return; + + // Blur current element + if (this.currentFocusIndex !== -1) { + this.focusableElements[this.currentFocusIndex].blur(); + } + + // Move to next element + this.currentFocusIndex = (this.currentFocusIndex + 1) % this.focusableElements.length; + + // Focus new element + this.focusableElements[this.currentFocusIndex].focus(); + } + + public focusPrevious(): void { + if (this.focusableElements.length === 0) return; + + // Blur current element + if (this.currentFocusIndex !== -1) { + this.focusableElements[this.currentFocusIndex].blur(); + } + + // Move to previous element + this.currentFocusIndex = (this.currentFocusIndex - 1 + this.focusableElements.length) % this.focusableElements.length; + + // Focus new element + this.focusableElements[this.currentFocusIndex].focus(); + } + + public focusFirst(): void { + if (this.focusableElements.length === 0) return; + + // Blur current element + if (this.currentFocusIndex !== -1) { + this.focusableElements[this.currentFocusIndex].blur(); + } + + // Focus first element + this.currentFocusIndex = 0; + this.focusableElements[this.currentFocusIndex].focus(); + } +} + +// Usage +const focusManager = new FocusManager(); + +// Register focusable elements +focusManager.register(input1); +focusManager.register(input2); +focusManager.register(button); + +// Focus the first element +focusManager.focusFirst(); +``` + +### Example: Creating a Form with Focus Navigation + +```typescript +import { BoxRenderable, TextRenderable, InputRenderable, createCliRenderer, getKeyHandler } from '@opentui/core'; + +async function createForm() { + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a form container + const form = new BoxRenderable('form', { + width: 40, + height: 15, + borderStyle: 'rounded', + borderColor: '#3498db', + backgroundColor: '#222222', + title: 'Login Form', + titleAlignment: 'center', + padding: 1, + flexDirection: 'column' + }); + + // Username field + const usernameLabel = new TextRenderable('usernameLabel', { + content: 'Username:', + fg: '#ffffff', + marginBottom: 1 + }); + + const usernameInput = new InputRenderable('usernameInput', { + width: '100%', + placeholder: 'Enter username', + borderStyle: 'single', + borderColor: '#3498db', + focusedBorderColor: '#2ecc71', + marginBottom: 2 + }); + + // Password field + const passwordLabel = new TextRenderable('passwordLabel', { + content: 'Password:', + fg: '#ffffff', + marginBottom: 1 + }); + + const passwordInput = new InputRenderable('passwordInput', { + width: '100%', + placeholder: 'Enter password', + borderStyle: 'single', + borderColor: '#3498db', + focusedBorderColor: '#2ecc71', + marginBottom: 2 + }); + + // Login button + const loginButton = new BoxRenderable('loginButton', { + width: 10, + height: 3, + borderStyle: 'single', + borderColor: '#3498db', + focusedBorderColor: '#2ecc71', + alignSelf: 'center', + marginTop: 1 + }); + + loginButton.focusable = true; + + const buttonLabel = new TextRenderable('buttonLabel', { + content: 'Login', + fg: '#ffffff', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 + }); + + loginButton.add(buttonLabel); + + // Handle button click + loginButton.on('mouseEvent', (event) => { + if (event.type === 'click') { + console.log('Login clicked'); + console.log(`Username: ${usernameInput.value}`); + console.log(`Password: ${passwordInput.value}`); + } + }); + + // Handle button keyboard activation + loginButton.handleKeyPress = (key) => { + if (key.name === 'return' || key.name === 'space') { + console.log('Login activated via keyboard'); + console.log(`Username: ${usernameInput.value}`); + console.log(`Password: ${passwordInput.value}`); + return true; + } + return false; + }; + + // Assemble the form + form.add(usernameLabel); + form.add(usernameInput); + form.add(passwordLabel); + form.add(passwordInput); + form.add(loginButton); + + // Add the form to the root + root.add(form); + + // Set up focus navigation + const keyHandler = getKeyHandler(); + const focusableElements = [usernameInput, passwordInput, loginButton]; + let currentFocusIndex = -1; + + keyHandler.on('keypress', (key) => { + if (key.name === 'tab') { + // Blur current element + if (currentFocusIndex !== -1) { + focusableElements[currentFocusIndex].blur(); + } + + // Move to next/previous element + if (key.shift) { + currentFocusIndex = (currentFocusIndex - 1 + focusableElements.length) % focusableElements.length; + } else { + currentFocusIndex = (currentFocusIndex + 1) % focusableElements.length; + } + + // Focus new element + focusableElements[currentFocusIndex].focus(); + } + }); + + // Focus the first input + currentFocusIndex = 0; + focusableElements[currentFocusIndex].focus(); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and show the form +createForm().catch(console.error); +``` diff --git a/packages/core/docs/api/lib/border.md b/packages/core/docs/api/lib/border.md new file mode 100644 index 000000000..5b1a23b9c --- /dev/null +++ b/packages/core/docs/api/lib/border.md @@ -0,0 +1,489 @@ +# Border Styles + +OpenTUI provides a variety of border styles for creating visually appealing terminal user interfaces. + +## Overview + +Borders are an important visual element in terminal user interfaces, helping to define and separate different areas of the screen. OpenTUI provides several built-in border styles and allows you to create custom border styles. + +## Border Style API + +### Border Style Types + +OpenTUI supports the following border style types: + +| Style | Description | +|-------|-------------| +| `'none'` | No border | +| `'single'` | Single-line border | +| `'double'` | Double-line border | +| `'rounded'` | Rounded corners with single lines | +| `'dashed'` | Dashed border | +| `'thick'` | Thick border | +| `'block'` | Block border | +| `'custom'` | Custom border defined by the user | + +### Using Border Styles + +```typescript +import { BoxRenderable } from '@opentui/core'; + +// Create a box with a single-line border +const singleBox = new BoxRenderable('single-box', { + width: 20, + height: 5, + borderStyle: 'single', + borderColor: '#ffffff' +}); + +// Create a box with a double-line border +const doubleBox = new BoxRenderable('double-box', { + width: 20, + height: 5, + borderStyle: 'double', + borderColor: '#3498db' +}); + +// Create a box with a rounded border +const roundedBox = new BoxRenderable('rounded-box', { + width: 20, + height: 5, + borderStyle: 'rounded', + borderColor: '#2ecc71' +}); + +// Create a box with a dashed border +const dashedBox = new BoxRenderable('dashed-box', { + width: 20, + height: 5, + borderStyle: 'dashed', + borderColor: '#e74c3c' +}); + +// Create a box with a thick border +const thickBox = new BoxRenderable('thick-box', { + width: 20, + height: 5, + borderStyle: 'thick', + borderColor: '#f39c12' +}); + +// Create a box with a block border +const blockBox = new BoxRenderable('block-box', { + width: 20, + height: 5, + borderStyle: 'block', + borderColor: '#9b59b6' +}); +``` + +### Border Characters + +Each border style defines a set of characters for different parts of the border: + +```typescript +interface BorderChars { + topLeft: string; // Top-left corner + topRight: string; // Top-right corner + bottomLeft: string; // Bottom-left corner + bottomRight: string; // Bottom-right corner + horizontal: string; // Horizontal line + vertical: string; // Vertical line + left: string; // Left T-junction + right: string; // Right T-junction + top: string; // Top T-junction + bottom: string; // Bottom T-junction + cross: string; // Cross junction +} +``` + +### Creating Custom Border Styles + +You can create custom border styles by defining your own border characters: + +```typescript +import { BoxRenderable, registerBorderStyle } from '@opentui/core'; + +// Register a custom border style +registerBorderStyle('stars', { + topLeft: '*', + topRight: '*', + bottomLeft: '*', + bottomRight: '*', + horizontal: '*', + vertical: '*', + left: '*', + right: '*', + top: '*', + bottom: '*', + cross: '*' +}); + +// Use the custom border style +const starsBox = new BoxRenderable('stars-box', { + width: 20, + height: 5, + borderStyle: 'stars', + borderColor: '#f1c40f' +}); +``` + +### Getting Border Characters + +You can get the border characters for a specific style: + +```typescript +import { getBorderChars } from '@opentui/core'; + +// Get the border characters for the 'single' style +const singleBorderChars = getBorderChars('single'); +console.log(singleBorderChars); +``` + +## Example: Creating a Panel with a Title + +```typescript +import { BoxRenderable, TextRenderable } from '@opentui/core'; + +// Create a panel with a title +function createPanel(id: string, title: string, options = {}) { + const panel = new BoxRenderable(id, { + width: 40, + height: 10, + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222', + ...options + }); + + // Create a title bar + const titleBar = new BoxRenderable(`${id}-title-bar`, { + width: '100%', + height: 3, + borderStyle: 'none', + backgroundColor: '#3498db' + }); + + // Create a title text + const titleText = new TextRenderable(`${id}-title-text`, { + content: title, + fg: '#ffffff', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 + }); + + // Create a content area + const contentArea = new BoxRenderable(`${id}-content-area`, { + width: '100%', + height: 'calc(100% - 3)', + y: 3, + borderStyle: 'none', + backgroundColor: 'transparent', + padding: 1 + }); + + // Build the component tree + titleBar.add(titleText); + panel.add(titleBar); + panel.add(contentArea); + + return { + panel, + contentArea + }; +} + +// Usage +const { panel, contentArea } = createPanel('my-panel', 'My Panel'); + +// Add content to the panel +const content = new TextRenderable('content', { + content: 'This is the panel content.', + fg: '#ffffff', + flexGrow: 1 +}); + +contentArea.add(content); +``` + +## Example: Creating a Dialog Box + +```typescript +import { BoxRenderable, TextRenderable } from '@opentui/core'; + +// Create a dialog box +function createDialog(id: string, title: string, message: string, options = {}) { + const dialog = new BoxRenderable(id, { + width: 50, + height: 15, + borderStyle: 'double', + borderColor: '#3498db', + backgroundColor: '#222222', + position: 'absolute', + x: 'center', + y: 'center', + ...options + }); + + // Create a title bar + const titleBar = new BoxRenderable(`${id}-title-bar`, { + width: '100%', + height: 3, + borderStyle: 'none', + backgroundColor: '#3498db' + }); + + // Create a title text + const titleText = new TextRenderable(`${id}-title-text`, { + content: title, + fg: '#ffffff', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 + }); + + // Create a message area + const messageArea = new BoxRenderable(`${id}-message-area`, { + width: '100%', + height: 'calc(100% - 6)', + y: 3, + borderStyle: 'none', + backgroundColor: 'transparent', + padding: 1 + }); + + // Create a message text + const messageText = new TextRenderable(`${id}-message-text`, { + content: message, + fg: '#ffffff', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 + }); + + // Create a button area + const buttonArea = new BoxRenderable(`${id}-button-area`, { + width: '100%', + height: 3, + y: 'calc(100% - 3)', + borderStyle: 'none', + backgroundColor: 'transparent', + padding: 1, + flexDirection: 'row', + justifyContent: 'flex-end', + alignItems: 'center' + }); + + // Create an OK button + const okButton = new BoxRenderable(`${id}-ok-button`, { + width: 10, + height: 1, + borderStyle: 'single', + borderColor: '#2ecc71', + backgroundColor: 'transparent', + marginRight: 1 + }); + + // Create an OK button text + const okButtonText = new TextRenderable(`${id}-ok-button-text`, { + content: 'OK', + fg: '#2ecc71', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 + }); + + // Create a Cancel button + const cancelButton = new BoxRenderable(`${id}-cancel-button`, { + width: 10, + height: 1, + borderStyle: 'single', + borderColor: '#e74c3c', + backgroundColor: 'transparent' + }); + + // Create a Cancel button text + const cancelButtonText = new TextRenderable(`${id}-cancel-button-text`, { + content: 'Cancel', + fg: '#e74c3c', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 + }); + + // Build the component tree + titleBar.add(titleText); + messageArea.add(messageText); + okButton.add(okButtonText); + cancelButton.add(cancelButtonText); + buttonArea.add(okButton); + buttonArea.add(cancelButton); + dialog.add(titleBar); + dialog.add(messageArea); + dialog.add(buttonArea); + + // Make the buttons focusable + okButton.focusable = true; + cancelButton.focusable = true; + + // Focus the OK button by default + okButton.focus(); + + // Handle button clicks + okButton.on('click', () => { + dialog.emit('ok'); + }); + + cancelButton.on('click', () => { + dialog.emit('cancel'); + }); + + return dialog; +} + +// Usage +const dialog = createDialog('my-dialog', 'Confirmation', 'Are you sure you want to proceed?'); + +// Handle dialog events +dialog.on('ok', () => { + console.log('OK button clicked'); + dialog.remove(); +}); + +dialog.on('cancel', () => { + console.log('Cancel button clicked'); + dialog.remove(); +}); +``` + +## Example: Creating a Tabbed Interface + +```typescript +import { BoxRenderable, TextRenderable } from '@opentui/core'; + +// Create a tabbed interface +function createTabbedInterface(id: string, tabs: string[], options = {}) { + const container = new BoxRenderable(id, { + width: 60, + height: 20, + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222', + ...options + }); + + // Create a tab bar + const tabBar = new BoxRenderable(`${id}-tab-bar`, { + width: '100%', + height: 3, + borderStyle: 'none', + backgroundColor: 'transparent', + flexDirection: 'row' + }); + + // Create a content area + const contentArea = new BoxRenderable(`${id}-content-area`, { + width: '100%', + height: 'calc(100% - 3)', + y: 3, + borderStyle: 'none', + backgroundColor: 'transparent', + padding: 1 + }); + + // Create tab buttons and content panels + const tabButtons: BoxRenderable[] = []; + const contentPanels: BoxRenderable[] = []; + + tabs.forEach((tab, index) => { + // Create a tab button + const tabButton = new BoxRenderable(`${id}-tab-${index}`, { + width: Math.floor(100 / tabs.length) + '%', + height: '100%', + borderStyle: index === 0 ? 'bottom-none' : 'single', + borderColor: index === 0 ? '#3498db' : '#bbbbbb', + backgroundColor: index === 0 ? '#222222' : 'transparent' + }); + + // Create a tab button text + const tabButtonText = new TextRenderable(`${id}-tab-${index}-text`, { + content: tab, + fg: index === 0 ? '#ffffff' : '#bbbbbb', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 + }); + + // Create a content panel + const contentPanel = new BoxRenderable(`${id}-content-${index}`, { + width: '100%', + height: '100%', + borderStyle: 'none', + backgroundColor: 'transparent', + visible: index === 0 + }); + + // Create a content panel text + const contentPanelText = new TextRenderable(`${id}-content-${index}-text`, { + content: `Content for ${tab}`, + fg: '#ffffff', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 + }); + + // Build the component tree + tabButton.add(tabButtonText); + contentPanel.add(contentPanelText); + + tabBar.add(tabButton); + contentArea.add(contentPanel); + + tabButtons.push(tabButton); + contentPanels.push(contentPanel); + + // Handle tab button clicks + tabButton.on('click', () => { + // Update tab buttons + tabButtons.forEach((button, i) => { + button.borderStyle = i === index ? 'bottom-none' : 'single'; + button.borderColor = i === index ? '#3498db' : '#bbbbbb'; + button.backgroundColor = i === index ? '#222222' : 'transparent'; + button.children[0].fg = i === index ? '#ffffff' : '#bbbbbb'; + }); + + // Update content panels + contentPanels.forEach((panel, i) => { + panel.visible = i === index; + }); + }); + }); + + // Build the main component tree + container.add(tabBar); + container.add(contentArea); + + return { + container, + tabButtons, + contentPanels + }; +} + +// Usage +const { container, tabButtons, contentPanels } = createTabbedInterface('my-tabs', ['Tab 1', 'Tab 2', 'Tab 3']); + +// Add custom content to a tab +const customContent = new TextRenderable('custom-content', { + content: 'This is custom content for Tab 2', + fg: '#2ecc71', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 +}); + +// Replace the default content +contentPanels[1].children = []; +contentPanels[1].add(customContent); +``` diff --git a/packages/core/docs/api/lib/hast-styled-text.md b/packages/core/docs/api/lib/hast-styled-text.md new file mode 100644 index 000000000..ccd74d699 --- /dev/null +++ b/packages/core/docs/api/lib/hast-styled-text.md @@ -0,0 +1,481 @@ +# HAST Styled Text + +OpenTUI provides support for Hypertext Abstract Syntax Tree (HAST) for complex text styling, allowing you to create rich text with different styles using a tree-based structure. + +## Overview + +The HAST Styled Text system consists of: + +1. **HASTNode**: A tree structure representing styled text +2. **SyntaxStyle**: A class for defining and merging text styles +3. **hastToStyledText**: A function for converting HAST to StyledText + +## HAST Structure + +HAST (Hypertext Abstract Syntax Tree) is a tree structure that represents HTML-like markup. In OpenTUI, it's used to represent styled text with nested elements and classes. + +```typescript +import { HASTNode, HASTElement, HASTText } from '@opentui/core'; + +// A text node +const textNode: HASTText = { + type: 'text', + value: 'Hello, world!' +}; + +// An element node with a class +const elementNode: HASTElement = { + type: 'element', + tagName: 'span', + properties: { + className: 'keyword' + }, + children: [ + { + type: 'text', + value: 'function' + } + ] +}; + +// A complex HAST tree +const hastTree: HASTNode = { + type: 'element', + tagName: 'div', + children: [ + { + type: 'element', + tagName: 'span', + properties: { + className: 'keyword' + }, + children: [ + { + type: 'text', + value: 'function' + } + ] + }, + { + type: 'text', + value: ' ' + }, + { + type: 'element', + tagName: 'span', + properties: { + className: 'function-name' + }, + children: [ + { + type: 'text', + value: 'example' + } + ] + }, + { + type: 'text', + value: '() {' + } + ] +}; +``` + +## SyntaxStyle API + +The `SyntaxStyle` class defines styles for different class names and provides methods for merging styles: + +```typescript +import { SyntaxStyle, StyleDefinition, RGBA } from '@opentui/core'; + +// Define styles for different classes +const styles: Record = { + default: { + fg: RGBA.fromHex('#ffffff') + }, + keyword: { + fg: RGBA.fromHex('#569cd6'), + bold: true + }, + 'function-name': { + fg: RGBA.fromHex('#dcdcaa') + }, + string: { + fg: RGBA.fromHex('#ce9178') + }, + comment: { + fg: RGBA.fromHex('#6a9955'), + italic: true + } +}; + +// Create a syntax style +const syntaxStyle = new SyntaxStyle(styles); + +// Merge styles +const mergedStyle = syntaxStyle.mergeStyles('keyword', 'bold'); + +// Clear the style cache +syntaxStyle.clearCache(); + +// Get the cache size +const cacheSize = syntaxStyle.getCacheSize(); +``` + +## Converting HAST to StyledText + +The `hastToStyledText` function converts a HAST tree to a `StyledText` instance: + +```typescript +import { hastToStyledText, SyntaxStyle, HASTNode } from '@opentui/core'; + +// Define a syntax style +const syntaxStyle = new SyntaxStyle({ + default: { + fg: RGBA.fromHex('#ffffff') + }, + keyword: { + fg: RGBA.fromHex('#569cd6'), + bold: true + }, + 'function-name': { + fg: RGBA.fromHex('#dcdcaa') + } +}); + +// Define a HAST tree +const hast: HASTNode = { + type: 'element', + tagName: 'div', + children: [ + { + type: 'element', + tagName: 'span', + properties: { + className: 'keyword' + }, + children: [ + { + type: 'text', + value: 'function' + } + ] + }, + { + type: 'text', + value: ' ' + }, + { + type: 'element', + tagName: 'span', + properties: { + className: 'function-name' + }, + children: [ + { + type: 'text', + value: 'example' + } + ] + } + ] +}; + +// Convert HAST to StyledText +const styledText = hastToStyledText(hast, syntaxStyle); + +// Use the styled text +console.log(styledText.toString()); +``` + +## Example: Syntax Highlighting + +Here's an example of using HAST Styled Text for syntax highlighting: + +```typescript +import { + SyntaxStyle, + HASTNode, + hastToStyledText, + RGBA, + TextRenderable +} from '@opentui/core'; + +// Define a syntax style for JavaScript +const jsStyle = new SyntaxStyle({ + default: { + fg: RGBA.fromHex('#d4d4d4') + }, + keyword: { + fg: RGBA.fromHex('#569cd6'), + bold: true + }, + 'function-name': { + fg: RGBA.fromHex('#dcdcaa') + }, + string: { + fg: RGBA.fromHex('#ce9178') + }, + number: { + fg: RGBA.fromHex('#b5cea8') + }, + comment: { + fg: RGBA.fromHex('#6a9955'), + italic: true + }, + punctuation: { + fg: RGBA.fromHex('#d4d4d4') + } +}); + +// Create a HAST tree for JavaScript code +const jsCode: HASTNode = { + type: 'element', + tagName: 'div', + children: [ + { + type: 'element', + tagName: 'span', + properties: { + className: 'comment' + }, + children: [ + { + type: 'text', + value: '// Example function\n' + } + ] + }, + { + type: 'element', + tagName: 'span', + properties: { + className: 'keyword' + }, + children: [ + { + type: 'text', + value: 'function' + } + ] + }, + { + type: 'text', + value: ' ' + }, + { + type: 'element', + tagName: 'span', + properties: { + className: 'function-name' + }, + children: [ + { + type: 'text', + value: 'calculateSum' + } + ] + }, + { + type: 'element', + tagName: 'span', + properties: { + className: 'punctuation' + }, + children: [ + { + type: 'text', + value: '(' + } + ] + }, + { + type: 'element', + tagName: 'span', + properties: { + className: 'parameter' + }, + children: [ + { + type: 'text', + value: 'a, b' + } + ] + }, + { + type: 'element', + tagName: 'span', + properties: { + className: 'punctuation' + }, + children: [ + { + type: 'text', + value: ') {\n ' + } + ] + }, + { + type: 'element', + tagName: 'span', + properties: { + className: 'keyword' + }, + children: [ + { + type: 'text', + value: 'return' + } + ] + }, + { + type: 'text', + value: ' a + b;\n' + }, + { + type: 'element', + tagName: 'span', + properties: { + className: 'punctuation' + }, + children: [ + { + type: 'text', + value: '}' + } + ] + } + ] +}; + +// Convert HAST to StyledText +const styledCode = hastToStyledText(jsCode, jsStyle); + +// Create a text renderable with the styled text +const codeBlock = new TextRenderable('code-block', { + styledContent: styledCode, + borderStyle: 'single', + borderColor: '#3498db', + padding: 1 +}); + +// Add to the renderer +renderer.root.add(codeBlock); +``` + +## Example: Creating a Syntax Highlighter + +Here's an example of creating a simple syntax highlighter that generates HAST from code: + +```typescript +import { + SyntaxStyle, + HASTNode, + HASTElement, + HASTText, + hastToStyledText, + RGBA +} from '@opentui/core'; + +// Define a simple JavaScript syntax highlighter +function highlightJS(code: string): HASTNode { + const root: HASTElement = { + type: 'element', + tagName: 'div', + children: [] + }; + + // Simple regex-based tokenization + const tokens = code.match(/\/\/.*|\/\*[\s\S]*?\*\/|\b(function|return|const|let|var|if|else|for|while)\b|"[^"]*"|'[^']*'|\d+|\w+|[^\s\w]+/g) || []; + + for (const token of tokens) { + let element: HASTNode; + + if (/^(function|return|const|let|var|if|else|for|while)$/.test(token)) { + // Keywords + element = { + type: 'element', + tagName: 'span', + properties: { className: 'keyword' }, + children: [{ type: 'text', value: token }] + }; + } else if (/^\/\/.*/.test(token) || /^\/\*[\s\S]*?\*\/$/.test(token)) { + // Comments + element = { + type: 'element', + tagName: 'span', + properties: { className: 'comment' }, + children: [{ type: 'text', value: token }] + }; + } else if (/^"[^"]*"$/.test(token) || /^'[^']*'$/.test(token)) { + // Strings + element = { + type: 'element', + tagName: 'span', + properties: { className: 'string' }, + children: [{ type: 'text', value: token }] + }; + } else if (/^\d+$/.test(token)) { + // Numbers + element = { + type: 'element', + tagName: 'span', + properties: { className: 'number' }, + children: [{ type: 'text', value: token }] + }; + } else if (/^[^\s\w]+$/.test(token)) { + // Punctuation + element = { + type: 'element', + tagName: 'span', + properties: { className: 'punctuation' }, + children: [{ type: 'text', value: token }] + }; + } else if (/^\w+$/.test(token)) { + // Identifiers + element = { + type: 'element', + tagName: 'span', + properties: { className: 'identifier' }, + children: [{ type: 'text', value: token }] + }; + } else { + // Plain text + element = { type: 'text', value: token }; + } + + root.children.push(element); + } + + return root; +} + +// Usage +const code = ` +// Example function +function calculateSum(a, b) { + return a + b; +} +`; + +const jsStyle = new SyntaxStyle({ + default: { fg: RGBA.fromHex('#d4d4d4') }, + keyword: { fg: RGBA.fromHex('#569cd6'), bold: true }, + comment: { fg: RGBA.fromHex('#6a9955'), italic: true }, + string: { fg: RGBA.fromHex('#ce9178') }, + number: { fg: RGBA.fromHex('#b5cea8') }, + punctuation: { fg: RGBA.fromHex('#d4d4d4') }, + identifier: { fg: RGBA.fromHex('#9cdcfe') } +}); + +const hastTree = highlightJS(code); +const styledText = hastToStyledText(hastTree, jsStyle); + +// Create a text renderable with the styled text +const codeBlock = new TextRenderable('code-block', { + styledContent: styledText, + borderStyle: 'single', + borderColor: '#3498db', + padding: 1 +}); +``` diff --git a/packages/core/docs/api/lib/keyhandler.md b/packages/core/docs/api/lib/keyhandler.md new file mode 100644 index 000000000..497a189bd --- /dev/null +++ b/packages/core/docs/api/lib/keyhandler.md @@ -0,0 +1,432 @@ +# Key Handler + +The `KeyHandler` class provides a high-level API for handling keyboard input in OpenTUI applications. + +## Overview + +The key handler processes raw key events from the terminal and provides a structured representation of keyboard input, including support for key combinations and special keys. + +## Key Handler API + +### Getting the Key Handler + +```typescript +import { getKeyHandler } from '@opentui/core'; + +// Get the global key handler +const keyHandler = getKeyHandler(); +``` + +### Listening for Key Events + +```typescript +// Listen for keypress events +keyHandler.on('keypress', (key) => { + console.log('Key pressed:', key.name); + + if (key.ctrl && key.name === 'c') { + console.log('Ctrl+C pressed'); + } +}); +``` + +### ParsedKey Interface + +The `ParsedKey` interface provides a structured representation of keyboard input: + +```typescript +interface ParsedKey { + sequence: string; // Raw key sequence + name: string; // Key name (e.g., 'a', 'return', 'escape') + ctrl: boolean; // Whether Ctrl was pressed + meta: boolean; // Whether Meta/Alt was pressed + shift: boolean; // Whether Shift was pressed + option: boolean; // Whether Option/Alt was pressed + number: boolean; // Whether this is a number key + raw: string; // Raw key data + code?: string; // Key code for special keys +} +``` + +### Parsing Key Sequences + +You can manually parse key sequences using the `parseKeypress` function: + +```typescript +import { parseKeypress } from '@opentui/core'; + +// Parse a key sequence +const key = parseKeypress('\x1b[A'); // Up arrow key +console.log(key); // { name: 'up', sequence: '\x1b[A', ... } +``` + +## Common Key Names + +Here are some common key names you can check for: + +| Category | Key Names | +|----------|-----------| +| Letters | `'a'` through `'z'` | +| Numbers | `'0'` through `'9'` | +| Special | `'space'`, `'backspace'`, `'tab'`, `'return'`, `'escape'` | +| Function | `'f1'` through `'f12'` | +| Navigation | `'up'`, `'down'`, `'left'`, `'right'`, `'home'`, `'end'`, `'pageup'`, `'pagedown'` | +| Editing | `'delete'`, `'insert'` | + +## Example: Handling Keyboard Shortcuts + +```typescript +import { getKeyHandler } from '@opentui/core'; + +// Define a keyboard shortcut handler +function setupGlobalShortcuts() { + const keyHandler = getKeyHandler(); + + const shortcuts = { + 'ctrl+q': () => { + console.log('Quit application'); + process.exit(0); + }, + 'ctrl+s': () => { + console.log('Save action'); + }, + 'ctrl+o': () => { + console.log('Open action'); + }, + 'f1': () => { + console.log('Show help'); + } + }; + + keyHandler.on('keypress', (key) => { + // Build a key identifier string + let keyId = ''; + if (key.ctrl) keyId += 'ctrl+'; + if (key.meta) keyId += 'alt+'; + if (key.shift) keyId += 'shift+'; + keyId += key.name; + + // Check if we have a handler for this shortcut + if (shortcuts[keyId]) { + shortcuts[keyId](); + } + }); +} + +// Call this function to set up global shortcuts +setupGlobalShortcuts(); +``` + +## Example: Creating a Key-Based Navigation System + +```typescript +import { getKeyHandler, BoxRenderable } from '@opentui/core'; + +class KeyboardNavigationManager { + private focusableElements: BoxRenderable[] = []; + private currentFocusIndex: number = -1; + + constructor() { + const keyHandler = getKeyHandler(); + + keyHandler.on('keypress', (key) => { + if (key.name === 'tab') { + if (key.shift) { + this.focusPrevious(); + } else { + this.focusNext(); + } + } else if (key.name === 'up') { + this.focusUp(); + } else if (key.name === 'down') { + this.focusDown(); + } else if (key.name === 'left') { + this.focusLeft(); + } else if (key.name === 'right') { + this.focusRight(); + } + }); + } + + public addFocusableElement(element: BoxRenderable): void { + this.focusableElements.push(element); + element.focusable = true; + + if (this.currentFocusIndex === -1) { + this.currentFocusIndex = 0; + element.focus(); + } + } + + public removeFocusableElement(element: BoxRenderable): void { + const index = this.focusableElements.indexOf(element); + if (index !== -1) { + this.focusableElements.splice(index, 1); + + if (this.currentFocusIndex >= this.focusableElements.length) { + this.currentFocusIndex = this.focusableElements.length - 1; + } + + if (this.currentFocusIndex !== -1) { + this.focusableElements[this.currentFocusIndex].focus(); + } + } + } + + public focusNext(): void { + if (this.focusableElements.length === 0) return; + + if (this.currentFocusIndex !== -1) { + this.focusableElements[this.currentFocusIndex].blur(); + } + + this.currentFocusIndex = (this.currentFocusIndex + 1) % this.focusableElements.length; + this.focusableElements[this.currentFocusIndex].focus(); + } + + public focusPrevious(): void { + if (this.focusableElements.length === 0) return; + + if (this.currentFocusIndex !== -1) { + this.focusableElements[this.currentFocusIndex].blur(); + } + + this.currentFocusIndex = (this.currentFocusIndex - 1 + this.focusableElements.length) % this.focusableElements.length; + this.focusableElements[this.currentFocusIndex].focus(); + } + + public focusUp(): void { + if (this.focusableElements.length === 0 || this.currentFocusIndex === -1) return; + + const currentElement = this.focusableElements[this.currentFocusIndex]; + let closestElement: BoxRenderable | null = null; + let closestDistance = Infinity; + + for (const element of this.focusableElements) { + if (element === currentElement) continue; + + // Check if the element is above the current element + if (element.y + element.height <= currentElement.y) { + const horizontalDistance = Math.abs((element.x + element.width / 2) - (currentElement.x + currentElement.width / 2)); + const verticalDistance = currentElement.y - (element.y + element.height); + const distance = Math.sqrt(horizontalDistance * horizontalDistance + verticalDistance * verticalDistance); + + if (distance < closestDistance) { + closestDistance = distance; + closestElement = element; + } + } + } + + if (closestElement) { + currentElement.blur(); + this.currentFocusIndex = this.focusableElements.indexOf(closestElement); + closestElement.focus(); + } + } + + public focusDown(): void { + // Similar to focusUp but for elements below + // ... + } + + public focusLeft(): void { + // Similar to focusUp but for elements to the left + // ... + } + + public focusRight(): void { + // Similar to focusUp but for elements to the right + // ... + } +} + +// Usage +const navigationManager = new KeyboardNavigationManager(); + +// Add focusable elements +navigationManager.addFocusableElement(button1); +navigationManager.addFocusableElement(button2); +navigationManager.addFocusableElement(inputField); +``` + +## Example: Creating a Text Editor with Keyboard Shortcuts + +```typescript +import { createCliRenderer, BoxRenderable, TextRenderable, getKeyHandler } from '@opentui/core'; + +class TextEditor extends BoxRenderable { + private content: string = ''; + private cursor: { row: number, col: number } = { row: 0, col: 0 }; + private lines: string[] = ['']; + private textDisplay: TextRenderable; + + constructor(id: string, options = {}) { + super(id, { + width: 60, + height: 20, + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222', + padding: 1, + ...options + }); + + this.focusable = true; + + this.textDisplay = new TextRenderable(`${id}-text`, { + content: '', + fg: '#ffffff', + flexGrow: 1 + }); + + this.add(this.textDisplay); + + // Set up key handler + const keyHandler = getKeyHandler(); + + keyHandler.on('keypress', (key) => { + if (!this.isFocused) return; + + if (key.name === 'return') { + this.insertNewline(); + } else if (key.name === 'backspace') { + this.deleteCharacter(); + } else if (key.name === 'delete') { + this.deleteCharacterForward(); + } else if (key.name === 'left') { + this.moveCursorLeft(); + } else if (key.name === 'right') { + this.moveCursorRight(); + } else if (key.name === 'up') { + this.moveCursorUp(); + } else if (key.name === 'down') { + this.moveCursorDown(); + } else if (key.name === 'home') { + this.moveCursorToLineStart(); + } else if (key.name === 'end') { + this.moveCursorToLineEnd(); + } else if (key.ctrl && key.name === 'a') { + this.moveCursorToLineStart(); + } else if (key.ctrl && key.name === 'e') { + this.moveCursorToLineEnd(); + } else if (key.ctrl && key.name === 'k') { + this.deleteToEndOfLine(); + } else if (key.ctrl && key.name === 'u') { + this.deleteToStartOfLine(); + } else if (key.name.length === 1) { + this.insertCharacter(key.name); + } + + this.updateDisplay(); + }); + } + + private updateDisplay(): void { + // Create a copy of the lines with the cursor + const displayLines = [...this.lines]; + const cursorLine = displayLines[this.cursor.row]; + + // Insert cursor character + displayLines[this.cursor.row] = + cursorLine.substring(0, this.cursor.col) + + '█' + + cursorLine.substring(this.cursor.col); + + // Update the text display + this.textDisplay.content = displayLines.join('\n'); + } + + private insertCharacter(char: string): void { + const line = this.lines[this.cursor.row]; + this.lines[this.cursor.row] = + line.substring(0, this.cursor.col) + + char + + line.substring(this.cursor.col); + + this.cursor.col++; + } + + private insertNewline(): void { + const line = this.lines[this.cursor.row]; + const newLine = line.substring(this.cursor.col); + this.lines[this.cursor.row] = line.substring(0, this.cursor.col); + this.lines.splice(this.cursor.row + 1, 0, newLine); + + this.cursor.row++; + this.cursor.col = 0; + } + + private deleteCharacter(): void { + if (this.cursor.col > 0) { + // Delete character before cursor + const line = this.lines[this.cursor.row]; + this.lines[this.cursor.row] = + line.substring(0, this.cursor.col - 1) + + line.substring(this.cursor.col); + + this.cursor.col--; + } else if (this.cursor.row > 0) { + // Join with previous line + const previousLine = this.lines[this.cursor.row - 1]; + const currentLine = this.lines[this.cursor.row]; + + this.cursor.col = previousLine.length; + this.lines[this.cursor.row - 1] = previousLine + currentLine; + this.lines.splice(this.cursor.row, 1); + + this.cursor.row--; + } + } + + // ... other editing methods ... + + public focus(): void { + super.focus(); + this.borderColor = '#2ecc71'; + this.updateDisplay(); + } + + public blur(): void { + super.blur(); + this.borderColor = '#3498db'; + this.updateDisplay(); + } + + public getText(): string { + return this.lines.join('\n'); + } + + public setText(text: string): void { + this.lines = text.split('\n'); + if (this.lines.length === 0) { + this.lines = ['']; + } + + this.cursor = { row: 0, col: 0 }; + this.updateDisplay(); + } +} + +// Usage +async function createTextEditorDemo() { + const renderer = await createCliRenderer(); + const { root } = renderer; + + const editor = new TextEditor('editor', { + x: 10, + y: 5, + width: 60, + height: 20 + }); + + root.add(editor); + editor.focus(); + + renderer.start(); + + return renderer; +} + +createTextEditorDemo().catch(console.error); +``` diff --git a/packages/core/docs/api/lib/selection.md b/packages/core/docs/api/lib/selection.md new file mode 100644 index 000000000..7b8ccea40 --- /dev/null +++ b/packages/core/docs/api/lib/selection.md @@ -0,0 +1,284 @@ +# Text Selection System + +OpenTUI provides a comprehensive text selection system that allows users to select and copy text from the terminal interface. + +## Overview + +The text selection system consists of three main classes: + +1. **Selection**: Manages the global selection state with anchor and focus points +2. **TextSelectionHelper**: Handles text selection for standard text components +3. **ASCIIFontSelectionHelper**: Handles text selection for ASCII font components + +## Selection API + +The `Selection` class represents a selection area in the terminal with anchor and focus points. + +```typescript +import { Selection } from '@opentui/core'; + +// Create a selection with anchor and focus points +const selection = new Selection( + { x: 10, y: 5 }, // anchor point + { x: 20, y: 5 } // focus point +); + +// Get the anchor point +const anchor = selection.anchor; + +// Get the focus point +const focus = selection.focus; + +// Get the selection bounds +const bounds = selection.bounds; +// bounds = { startX: 10, startY: 5, endX: 20, endY: 5 } + +// Update the selected renderables +selection.updateSelectedRenderables(selectedComponents); + +// Get the selected text +const text = selection.getSelectedText(); +``` + +## TextSelectionHelper API + +The `TextSelectionHelper` class helps text components handle selection. + +```typescript +import { TextSelectionHelper, SelectionState } from '@opentui/core'; + +class MyTextComponent extends Renderable { + private selectionHelper: TextSelectionHelper; + + constructor(id: string, options = {}) { + super(id, options); + + // Create a selection helper + this.selectionHelper = new TextSelectionHelper( + () => this.x, // Get component X position + () => this.y, // Get component Y position + () => this.content.length, // Get text length + () => ({ // Get line information for multi-line text + lineStarts: this.lineStarts, + lineWidths: this.lineWidths + }) + ); + } + + // Check if the component should start a selection + shouldStartSelection(x: number, y: number): boolean { + return this.selectionHelper.shouldStartSelection(x, y, this.width, this.height); + } + + // Handle selection changes + onSelectionChanged(selection: SelectionState | null): void { + if (this.selectionHelper.onSelectionChanged(selection, this.width, this.height)) { + this.needsRedraw = true; + } + } + + // Check if the component has a selection + hasSelection(): boolean { + return this.selectionHelper.hasSelection(); + } + + // Get the current selection + getSelection(): { start: number; end: number } | null { + return this.selectionHelper.getSelection(); + } + + // Get the selected text + getSelectedText(): string { + const selection = this.selectionHelper.getSelection(); + if (!selection) return ''; + return this.content.substring(selection.start, selection.end); + } + + // Reevaluate selection after component changes + reevaluateSelection(): void { + if (this.selectionHelper.reevaluateSelection(this.width, this.height)) { + this.needsRedraw = true; + } + } +} +``` + +## ASCIIFontSelectionHelper API + +The `ASCIIFontSelectionHelper` class helps ASCII font components handle selection. + +```typescript +import { ASCIIFontSelectionHelper, SelectionState } from '@opentui/core'; + +class MyASCIIFontComponent extends Renderable { + private selectionHelper: ASCIIFontSelectionHelper; + + constructor(id: string, options = {}) { + super(id, options); + + // Create a selection helper + this.selectionHelper = new ASCIIFontSelectionHelper( + () => this.x, // Get component X position + () => this.y, // Get component Y position + () => this.content, // Get text content + () => this.font // Get font name + ); + } + + // Check if the component should start a selection + shouldStartSelection(x: number, y: number): boolean { + return this.selectionHelper.shouldStartSelection(x, y, this.width, this.height); + } + + // Handle selection changes + onSelectionChanged(selection: SelectionState | null): void { + if (this.selectionHelper.onSelectionChanged(selection, this.width, this.height)) { + this.needsRedraw = true; + } + } + + // Check if the component has a selection + hasSelection(): boolean { + return this.selectionHelper.hasSelection(); + } + + // Get the current selection + getSelection(): { start: number; end: number } | null { + return this.selectionHelper.getSelection(); + } + + // Get the selected text + getSelectedText(): string { + const selection = this.selectionHelper.getSelection(); + if (!selection) return ''; + return this.content.substring(selection.start, selection.end); + } + + // Reevaluate selection after component changes + reevaluateSelection(): void { + if (this.selectionHelper.reevaluateSelection(this.width, this.height)) { + this.needsRedraw = true; + } + } +} +``` + +## Example: Implementing Text Selection + +Here's a complete example of implementing text selection in a custom component: + +```typescript +import { Renderable, TextSelectionHelper, SelectionState } from '@opentui/core'; + +class SelectableText extends Renderable { + private content: string; + private selectionHelper: TextSelectionHelper; + private lineStarts: number[] = []; + private lineWidths: number[] = []; + + constructor(id: string, options: { content: string, width: number, height: number }) { + super(id, options); + this.content = options.content; + + // Calculate line information + this.calculateLineInfo(); + + // Create selection helper + this.selectionHelper = new TextSelectionHelper( + () => this.x, + () => this.y, + () => this.content.length, + () => ({ lineStarts: this.lineStarts, lineWidths: this.lineWidths }) + ); + } + + private calculateLineInfo(): void { + this.lineStarts = [0]; + this.lineWidths = []; + + let currentLine = 0; + let currentLineWidth = 0; + + for (let i = 0; i < this.content.length; i++) { + if (this.content[i] === '\n') { + this.lineWidths.push(currentLineWidth); + this.lineStarts.push(i + 1); + currentLine++; + currentLineWidth = 0; + } else { + currentLineWidth++; + } + } + + // Add the last line + this.lineWidths.push(currentLineWidth); + } + + public render(context: RenderContext): void { + // Render the text + const selection = this.selectionHelper.getSelection(); + + for (let i = 0; i < this.lineStarts.length; i++) { + const lineStart = this.lineStarts[i]; + const lineEnd = i < this.lineStarts.length - 1 ? this.lineStarts[i + 1] - 1 : this.content.length; + const line = this.content.substring(lineStart, lineEnd); + + for (let j = 0; j < line.length; j++) { + const charIndex = lineStart + j; + const isSelected = selection && charIndex >= selection.start && charIndex < selection.end; + + // Render character with selection highlighting if needed + context.setChar(this.x + j, this.y + i, line[j], { + fg: isSelected ? '#000000' : '#ffffff', + bg: isSelected ? '#3498db' : 'transparent' + }); + } + } + } + + public onMouseEvent(event: MouseEvent): void { + if (event.type === 'down') { + if (this.selectionHelper.shouldStartSelection(event.x, event.y, this.width, this.height)) { + // Start selection + this.renderer.startSelection(event.x, event.y); + event.preventDefault(); + } + } + } + + public onSelectionChanged(selection: SelectionState | null): void { + if (this.selectionHelper.onSelectionChanged(selection, this.width, this.height)) { + this.needsRedraw = true; + } + } + + public getSelectedText(): string { + const selection = this.selectionHelper.getSelection(); + if (!selection) return ''; + return this.content.substring(selection.start, selection.end); + } +} +``` + +## Example: Copying Selected Text + +```typescript +import { getKeyHandler } from '@opentui/core'; +import * as clipboard from 'clipboard-polyfill'; + +// Set up keyboard shortcut for copying +const keyHandler = getKeyHandler(); + +keyHandler.on('keypress', (key) => { + if (key.ctrl && key.name === 'c') { + const selection = renderer.getSelection(); + if (selection) { + const text = selection.getSelectedText(); + if (text) { + clipboard.writeText(text); + console.log('Copied to clipboard:', text); + } + } + } +}); +``` diff --git a/packages/core/docs/api/lib/styled-text.md b/packages/core/docs/api/lib/styled-text.md new file mode 100644 index 000000000..837092894 --- /dev/null +++ b/packages/core/docs/api/lib/styled-text.md @@ -0,0 +1,393 @@ +# Styled Text + +OpenTUI provides a powerful styled text system for creating rich text with different styles, colors, and attributes. + +## StyledText Class + +The `StyledText` class is the core of OpenTUI's text styling system. It allows you to create rich text with different styles, colors, and attributes. + +```typescript +import { StyledText, RGBA } from '@opentui/core'; + +// Create a styled text +const text = new StyledText(); + +// Add text with different styles +text.pushFg('#ff0000'); // Set foreground color to red +text.pushText('This text is red. '); +text.popFg(); // Restore previous foreground color + +text.pushBg('#0000ff'); // Set background color to blue +text.pushText('This text has a blue background. '); +text.popBg(); // Restore previous background color + +text.pushAttributes(0x01); // Set text to bold (0x01 = bold) +text.pushText('This text is bold. '); +text.popAttributes(); // Restore previous attributes + +// Combine styles +text.pushFg('#00ff00'); // Green +text.pushBg('#000000'); // Black background +text.pushAttributes(0x01 | 0x02); // Bold and italic +text.pushText('This text is bold, italic, green on black. '); +text.popAttributes(); +text.popBg(); +text.popFg(); + +// Get the styled text as a string +const plainText = text.toString(); + +// Get the styled text with ANSI escape sequences +const ansiText = text.toANSI(); +``` + +## Text Attributes + +OpenTUI supports the following text attributes: + +| Attribute | Value | Description | +|-----------|-------|-------------| +| Bold | `0x01` | Bold text | +| Italic | `0x02` | Italic text | +| Underline | `0x04` | Underlined text | +| Strikethrough | `0x08` | Strikethrough text | +| Blink | `0x10` | Blinking text | +| Inverse | `0x20` | Inverted colors | +| Hidden | `0x40` | Hidden text | + +You can combine attributes using the bitwise OR operator (`|`): + +```typescript +// Bold and underlined text +text.pushAttributes(0x01 | 0x04); +text.pushText('Bold and underlined text'); +text.popAttributes(); +``` + +## Colors + +OpenTUI supports various color formats: + +```typescript +// Hex colors +text.pushFg('#ff0000'); // Red +text.pushBg('#00ff00'); // Green + +// RGB colors +text.pushFg(RGBA.fromValues(1.0, 0.0, 0.0, 1.0)); // Red +text.pushBg(RGBA.fromValues(0.0, 1.0, 0.0, 1.0)); // Green + +// Named colors +text.pushFg('red'); +text.pushBg('green'); + +// ANSI colors (0-15) +text.pushFg(RGBA.fromANSI(1)); // ANSI red +text.pushBg(RGBA.fromANSI(2)); // ANSI green +``` + +## HAST Support + +OpenTUI supports the Hypertext Abstract Syntax Tree (HAST) format for complex text styling: + +```typescript +import { StyledText } from '@opentui/core'; + +// Create a styled text from HAST +const hast = { + type: 'root', + children: [ + { + type: 'element', + tagName: 'span', + properties: { + style: 'color: red; font-weight: bold;' + }, + children: [ + { + type: 'text', + value: 'This text is red and bold. ' + } + ] + }, + { + type: 'element', + tagName: 'span', + properties: { + style: 'color: blue; text-decoration: underline;' + }, + children: [ + { + type: 'text', + value: 'This text is blue and underlined.' + } + ] + } + ] +}; + +const text = StyledText.fromHAST(hast); +``` + +## Example: Creating a Syntax Highlighter + +```typescript +import { StyledText } from '@opentui/core'; + +function highlightJavaScript(code: string): StyledText { + const result = new StyledText(); + + // Simple tokenizer for demonstration + const tokens = tokenizeJavaScript(code); + + for (const token of tokens) { + switch (token.type) { + case 'keyword': + result.pushFg('#569cd6'); // Blue + result.pushText(token.value); + result.popFg(); + break; + case 'string': + result.pushFg('#ce9178'); // Orange + result.pushText(token.value); + result.popFg(); + break; + case 'number': + result.pushFg('#b5cea8'); // Light green + result.pushText(token.value); + result.popFg(); + break; + case 'comment': + result.pushFg('#6a9955'); // Green + result.pushText(token.value); + result.popFg(); + break; + case 'function': + result.pushFg('#dcdcaa'); // Yellow + result.pushText(token.value); + result.popFg(); + break; + default: + result.pushText(token.value); + break; + } + } + + return result; +} + +// Simple tokenizer for demonstration +function tokenizeJavaScript(code: string): Array<{ type: string, value: string }> { + // This is a simplified tokenizer for demonstration purposes + // In a real implementation, you would use a proper parser + + const keywords = ['const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while']; + const tokens = []; + + // Split the code into lines + const lines = code.split('\n'); + + for (const line of lines) { + let i = 0; + + while (i < line.length) { + // Skip whitespace + if (/\s/.test(line[i])) { + const start = i; + while (i < line.length && /\s/.test(line[i])) { + i++; + } + tokens.push({ type: 'whitespace', value: line.substring(start, i) }); + continue; + } + + // Comments + if (line[i] === '/' && line[i + 1] === '/') { + tokens.push({ type: 'comment', value: line.substring(i) }); + break; + } + + // Strings + if (line[i] === '"' || line[i] === "'") { + const quote = line[i]; + const start = i; + i++; + while (i < line.length && line[i] !== quote) { + if (line[i] === '\\') { + i += 2; + } else { + i++; + } + } + if (i < line.length) { + i++; + } + tokens.push({ type: 'string', value: line.substring(start, i) }); + continue; + } + + // Numbers + if (/\d/.test(line[i])) { + const start = i; + while (i < line.length && /[\d.]/.test(line[i])) { + i++; + } + tokens.push({ type: 'number', value: line.substring(start, i) }); + continue; + } + + // Identifiers and keywords + if (/[a-zA-Z_$]/.test(line[i])) { + const start = i; + while (i < line.length && /[a-zA-Z0-9_$]/.test(line[i])) { + i++; + } + const word = line.substring(start, i); + + if (keywords.includes(word)) { + tokens.push({ type: 'keyword', value: word }); + } else if (i < line.length && line[i] === '(') { + tokens.push({ type: 'function', value: word }); + } else { + tokens.push({ type: 'identifier', value: word }); + } + continue; + } + + // Punctuation + tokens.push({ type: 'punctuation', value: line[i] }); + i++; + } + + tokens.push({ type: 'whitespace', value: '\n' }); + } + + return tokens; +} + +// Usage +const code = ` +function factorial(n) { + // Calculate factorial + if (n <= 1) { + return 1; + } + return n * factorial(n - 1); +} + +const result = factorial(5); +console.log("The factorial of 5 is: " + result); +`; + +const highlightedCode = highlightJavaScript(code); +``` + +## Example: Creating a Logger with Styled Output + +```typescript +import { StyledText } from '@opentui/core'; + +class Logger { + private static instance: Logger; + + private constructor() {} + + public static getInstance(): Logger { + if (!Logger.instance) { + Logger.instance = new Logger(); + } + return Logger.instance; + } + + public info(message: string): StyledText { + const text = new StyledText(); + + // Add timestamp + text.pushFg('#888888'); + text.pushText(`[${new Date().toISOString()}] `); + text.popFg(); + + // Add level + text.pushFg('#3498db'); + text.pushAttributes(0x01); // Bold + text.pushText('INFO'); + text.popAttributes(); + text.popFg(); + + // Add separator + text.pushText(': '); + + // Add message + text.pushText(message); + + return text; + } + + public warn(message: string): StyledText { + const text = new StyledText(); + + // Add timestamp + text.pushFg('#888888'); + text.pushText(`[${new Date().toISOString()}] `); + text.popFg(); + + // Add level + text.pushFg('#f39c12'); + text.pushAttributes(0x01); // Bold + text.pushText('WARN'); + text.popAttributes(); + text.popFg(); + + // Add separator + text.pushText(': '); + + // Add message + text.pushFg('#f39c12'); + text.pushText(message); + text.popFg(); + + return text; + } + + public error(message: string, error?: Error): StyledText { + const text = new StyledText(); + + // Add timestamp + text.pushFg('#888888'); + text.pushText(`[${new Date().toISOString()}] `); + text.popFg(); + + // Add level + text.pushFg('#e74c3c'); + text.pushAttributes(0x01); // Bold + text.pushText('ERROR'); + text.popAttributes(); + text.popFg(); + + // Add separator + text.pushText(': '); + + // Add message + text.pushFg('#e74c3c'); + text.pushText(message); + text.popFg(); + + // Add error details if provided + if (error) { + text.pushText('\n'); + text.pushFg('#888888'); + text.pushText(error.stack || error.message); + text.popFg(); + } + + return text; + } +} + +// Usage +const logger = Logger.getInstance(); + +console.log(logger.info('Application started').toANSI()); +console.log(logger.warn('Disk space is low').toANSI()); +console.log(logger.error('Failed to connect to database', new Error('Connection timeout')).toANSI()); +``` diff --git a/packages/core/docs/api/lib/tracked-node.md b/packages/core/docs/api/lib/tracked-node.md new file mode 100644 index 000000000..3dfeb1c54 --- /dev/null +++ b/packages/core/docs/api/lib/tracked-node.md @@ -0,0 +1,246 @@ +# TrackedNode System + +The TrackedNode system is a core part of OpenTUI's layout engine, providing a TypeScript wrapper around Yoga layout nodes with additional tracking and relationship management. + +## Overview + +The `TrackedNode` class wraps Yoga layout nodes and maintains parent-child relationships, handles percentage-based dimensions, and provides a metadata system for storing additional information. + +## TrackedNode API + +```typescript +import { TrackedNode, createTrackedNode } from '@opentui/core'; + +// Create a new tracked node +const node = createTrackedNode({ + id: 'my-node', + type: 'box' +}); + +// Set dimensions +node.setWidth(100); +node.setHeight(50); + +// Set percentage-based dimensions +node.setWidth('50%'); +node.setHeight('25%'); + +// Set dimensions to auto +node.setWidth('auto'); +node.setHeight('auto'); +``` + +### Creating Nodes + +The `createTrackedNode` function creates a new `TrackedNode` instance with an underlying Yoga node: + +```typescript +import { createTrackedNode } from '@opentui/core'; +import Yoga from 'yoga-layout'; + +// Create a node with metadata +const node = createTrackedNode({ + id: 'my-node', + type: 'box' +}); + +// Create a node with metadata and custom Yoga config +const config = Yoga.Config.create(); +const nodeWithConfig = createTrackedNode({ + id: 'custom-node', + type: 'box' +}, config); +``` + +### Node Dimensions + +The `TrackedNode` class provides methods for setting and parsing dimensions, including support for percentage-based dimensions: + +```typescript +// Set fixed dimensions +node.setWidth(100); +node.setHeight(50); + +// Set percentage-based dimensions (relative to parent) +node.setWidth('50%'); +node.setHeight('25%'); + +// Set dimensions to auto (let Yoga determine the size) +node.setWidth('auto'); +node.setHeight('auto'); + +// Parse dimensions (converts percentages to absolute values) +const parsedWidth = node.parseWidth('50%'); +const parsedHeight = node.parseHeight('25%'); +``` + +### Node Hierarchy + +The `TrackedNode` class provides methods for managing the node hierarchy: + +```typescript +// Add a child node +const childIndex = parentNode.addChild(childNode); + +// Insert a child node at a specific index +const insertedIndex = parentNode.insertChild(childNode, 2); + +// Remove a child node +const removed = parentNode.removeChild(childNode); + +// Remove a child node at a specific index +const removedNode = parentNode.removeChildAtIndex(2); + +// Move a child node to a new index +const newIndex = parentNode.moveChild(childNode, 3); + +// Get the index of a child node +const index = parentNode.getChildIndex(childNode); + +// Check if a node is a child of this node +const isChild = parentNode.hasChild(childNode); + +// Get the number of children +const childCount = parentNode.getChildCount(); + +// Get a child node at a specific index +const child = parentNode.getChildAtIndex(2); +``` + +### Metadata + +The `TrackedNode` class provides methods for managing metadata: + +```typescript +// Set metadata +node.setMetadata('visible', true); + +// Get metadata +const isVisible = node.getMetadata('visible'); + +// Remove metadata +node.removeMetadata('visible'); +``` + +### Cleanup + +The `TrackedNode` class provides a method for cleaning up resources: + +```typescript +// Destroy the node and free resources +node.destroy(); +``` + +## Example: Building a Layout Tree + +```typescript +import { createTrackedNode } from '@opentui/core'; + +// Create a root node +const root = createTrackedNode({ + id: 'root', + type: 'container' +}); + +// Set root dimensions +root.setWidth(800); +root.setHeight(600); + +// Create a header node +const header = createTrackedNode({ + id: 'header', + type: 'box' +}); + +// Set header dimensions +header.setWidth('100%'); +header.setHeight(50); + +// Create a content node +const content = createTrackedNode({ + id: 'content', + type: 'box' +}); + +// Set content dimensions +content.setWidth('100%'); +content.setHeight('auto'); + +// Create a footer node +const footer = createTrackedNode({ + id: 'footer', + type: 'box' +}); + +// Set footer dimensions +footer.setWidth('100%'); +footer.setHeight(50); + +// Build the layout tree +root.addChild(header); +root.addChild(content); +root.addChild(footer); + +// Add some content items +for (let i = 0; i < 3; i++) { + const item = createTrackedNode({ + id: `item-${i}`, + type: 'box' + }); + + item.setWidth('33%'); + item.setHeight(100); + + content.addChild(item); +} + +// Later, clean up resources +root.destroy(); // This will also destroy all child nodes +``` + +## Integration with Yoga Layout + +The `TrackedNode` system is built on top of the Yoga layout engine, which provides a flexible and powerful layout system based on Flexbox. The `TrackedNode` class wraps Yoga nodes and provides additional functionality for managing the node hierarchy and metadata. + +When you set dimensions or add/remove children, the `TrackedNode` class updates the underlying Yoga node accordingly. When the layout is calculated, the Yoga engine determines the final positions and dimensions of all nodes based on the layout constraints. + +```typescript +import { createTrackedNode } from '@opentui/core'; + +// Create a container with flexbox layout +const container = createTrackedNode({ + id: 'container', + type: 'box' +}); + +container.setWidth(500); +container.setHeight(300); + +// Configure flexbox properties on the Yoga node +container.yogaNode.setFlexDirection(Yoga.FLEX_DIRECTION_ROW); +container.yogaNode.setJustifyContent(Yoga.JUSTIFY_SPACE_BETWEEN); +container.yogaNode.setAlignItems(Yoga.ALIGN_CENTER); + +// Add some flex items +for (let i = 0; i < 3; i++) { + const item = createTrackedNode({ + id: `item-${i}`, + type: 'box' + }); + + item.setWidth(100); + item.setHeight(100); + item.yogaNode.setMargin(Yoga.EDGE_ALL, 10); + + container.addChild(item); +} + +// Calculate the layout +container.yogaNode.calculateLayout(); + +// Get the computed layout values +const width = container.yogaNode.getComputedWidth(); +const height = container.yogaNode.getComputedHeight(); +const x = container.yogaNode.getComputedLeft(); +const y = container.yogaNode.getComputedTop(); +``` diff --git a/packages/core/docs/api/native-integration.md b/packages/core/docs/api/native-integration.md new file mode 100644 index 000000000..229bd6b6e --- /dev/null +++ b/packages/core/docs/api/native-integration.md @@ -0,0 +1,144 @@ +# Native Integration (detailed RenderLib API) + +OpenTUI uses native code written in Zig for performance-critical operations. The JavaScript side exposes a RenderLib wrapper (packages/core/src/zig.ts) that converts JS types to the FFI layer and provides convenient helpers. + +This page documents the RenderLib wrapper signatures and the TextBuffer/native buffer helpers in detail so examples can call them correctly. + +Source reference: packages/core/src/zig.ts (FFIRenderLib / RenderLib interface) + +--- + +## Getting the RenderLib + +```ts +import { resolveRenderLib, type RenderLib } from '@opentui/core'; + +const lib: RenderLib = resolveRenderLib(); // throws if native lib cannot load +``` + +The `RenderLib` object provides both renderer-level and buffer-level helpers. Most application code should prefer the high-level JS API (OptimizedBuffer, TextBuffer) but the RenderLib helpers are useful for advanced/native usage. + +--- + +## Renderer-level methods (selected) + +- createRenderer(width: number, height: number): Pointer | null +- destroyRenderer(renderer: Pointer): void +- setUseThread(renderer: Pointer, useThread: boolean): void +- setBackgroundColor(renderer: Pointer, color: RGBA): void +- setRenderOffset(renderer: Pointer, offset: number): void +- updateStats(renderer: Pointer, time: number, fps: number, frameCallbackTime: number): void +- updateMemoryStats(renderer: Pointer, heapUsed: number, heapTotal: number, arrayBuffers: number): void +- render(renderer: Pointer, force: boolean): void +- getNextBuffer(renderer: Pointer): OptimizedBuffer +- getCurrentBuffer(renderer: Pointer): OptimizedBuffer +- resizeRenderer(renderer: Pointer, width: number, height: number): void +- setDebugOverlay(renderer: Pointer, enabled: boolean, corner: DebugOverlayCorner): void +- clearTerminal(renderer: Pointer): void +- addToHitGrid(renderer: Pointer, x: number, y: number, width: number, height: number, id: number): void +- checkHit(renderer: Pointer, x: number, y: number): number +- dumpHitGrid(renderer: Pointer): void +- dumpBuffers(renderer: Pointer, timestamp?: number): void +- dumpStdoutBuffer(renderer: Pointer, timestamp?: number): void + +Notes: +- getNextBuffer/getCurrentBuffer return OptimizedBuffer instances that wrap native buffer pointers and typed arrays. + +--- + +## Optimized buffer / buffer primitives (native-facing helpers) + +These are the RenderLib wrapper methods that operate on native buffer pointers or wrapped OptimizedBuffer objects. Prefer the high-level OptimizedBuffer methods, but this lists the wrapper signatures for precise behavior. + +- createOptimizedBuffer(width: number, height: number, respectAlpha?: boolean): OptimizedBuffer + - Returns an OptimizedBuffer instance wrapping the native buffer pointer and typed arrays. +- destroyOptimizedBuffer(bufferPtr: Pointer): void + +Buffer property helpers: +- getBufferWidth(buffer: Pointer): number +- getBufferHeight(buffer: Pointer): number +- bufferGetCharPtr(buffer: Pointer): Pointer +- bufferGetFgPtr(buffer: Pointer): Pointer +- bufferGetBgPtr(buffer: Pointer): Pointer +- bufferGetAttributesPtr(buffer: Pointer): Pointer +- bufferGetRespectAlpha(buffer: Pointer): boolean +- bufferSetRespectAlpha(buffer: Pointer, respectAlpha: boolean): void +- bufferClear(buffer: Pointer, color: RGBA): void + +Drawing helpers (native/FFI-backed): +- bufferDrawText(buffer: Pointer, text: string, x: number, y: number, color: RGBA, bgColor?: RGBA, attributes?: number): void + - In the wrapper, JS strings are encoded and forwarded to the native symbol with length. + - Use RGBA instances for color arguments. +- bufferSetCellWithAlphaBlending(buffer: Pointer, x: number, y: number, char: string, color: RGBA, bgColor: RGBA, attributes?: number): void + - Accepts a single-character string (the wrapper converts to codepoint). +- bufferFillRect(buffer: Pointer, x: number, y: number, width: number, height: number, color: RGBA): void +- bufferDrawSuperSampleBuffer(buffer: Pointer, x: number, y: number, pixelDataPtr: Pointer, pixelDataLength: number, format: 'bgra8unorm' | 'rgba8unorm', alignedBytesPerRow: number): void + - Format argument in wrapper is converted to an internal format id. +- bufferDrawPackedBuffer(buffer: Pointer, dataPtr: Pointer, dataLen: number, posX: number, posY: number, terminalWidthCells: number, terminalHeightCells: number): void +- bufferDrawBox(buffer: Pointer, x: number, y: number, width: number, height: number, borderChars: Uint32Array, packedOptions: number, borderColor: RGBA, backgroundColor: RGBA, title: string | null): void + - The wrapper accepts a JS string title and encodes it with a length. +- bufferResize(buffer: Pointer, width: number, height: number): { char: Uint32Array; fg: Float32Array; bg: Float32Array; attributes: Uint8Array } + - Returns the new typed arrays mapped to the buffer. + +Notes: +- The wrapper converts JS RGBA objects into underlying Float32Array pointers and encodes strings into Uint8Array payloads for the FFI call. +- There are no native helpers named drawHorizontalLine or drawVerticalLine; use bufferFillRect, bufferDrawText / bufferSetCellWithAlphaBlending, or bufferDrawBox to implement lines. + +--- + +## TextBuffer native helpers (RenderLib wrapper) + +`TextBuffer` is a native-backed rich text buffer; the RenderLib exposes wrapper methods: + +- createTextBuffer(capacity: number): TextBuffer + - Returns a TextBuffer instance wrapping a native pointer and typed arrays. +- destroyTextBuffer(buffer: Pointer): void +- textBufferGetCharPtr(buffer: Pointer): Pointer +- textBufferGetFgPtr(buffer: Pointer): Pointer +- textBufferGetBgPtr(buffer: Pointer): Pointer +- textBufferGetAttributesPtr(buffer: Pointer): Pointer +- textBufferGetLength(buffer: Pointer): number +- textBufferSetCell(buffer: Pointer, index: number, char: number, fg: Float32Array, bg: Float32Array, attr: number): void +- textBufferConcat(buffer1: Pointer, buffer2: Pointer): TextBuffer +- textBufferResize(buffer: Pointer, newLength: number): { char: Uint32Array; fg: Float32Array; bg: Float32Array; attributes: Uint16Array } +- textBufferReset(buffer: Pointer): void +- textBufferSetSelection(buffer: Pointer, start: number, end: number, bgColor: RGBA | null, fgColor: RGBA | null): void +- textBufferResetSelection(buffer: Pointer): void +- textBufferSetDefaultFg(buffer: Pointer, fg: RGBA | null): void +- textBufferSetDefaultBg(buffer: Pointer, bg: RGBA | null): void +- textBufferSetDefaultAttributes(buffer: Pointer, attributes: number | null): void +- textBufferResetDefaults(buffer: Pointer): void +- textBufferWriteChunk(buffer: Pointer, textBytes: Uint8Array, fg: RGBA | null, bg: RGBA | null, attributes: number | null): number + - Returns number of bytes written / used by the chunk write operation. +- textBufferGetCapacity(buffer: Pointer): number +- textBufferFinalizeLineInfo(buffer: Pointer): void +- textBufferGetLineInfo(buffer: Pointer): { lineStarts: number[]; lineWidths: number[] } +- getTextBufferArrays(buffer: Pointer, size: number): { char: Uint32Array; fg: Float32Array; bg: Float32Array; attributes: Uint16Array } +- bufferDrawTextBuffer(buffer: Pointer, textBuffer: Pointer, x: number, y: number, clipRect?: { x: number; y: number; width: number; height: number }): void + +Usage notes: +- The RenderLib wrapper encodes text and forwards typed arrays to native implementations. +- Use `createTextBuffer` and `bufferDrawTextBuffer` to render styled content efficiently. +- `textBufferWriteChunk` accepts a Uint8Array of UTF-8 bytes and optional RGBA buffers for default fg/bg; the wrapper handles encoding. + +--- + +## Examples + +Create an optimized buffer via RenderLib and draw text: +```ts +import { resolveRenderLib, RGBA } from '@opentui/core'; + +const lib = resolveRenderLib(); +const fb = lib.createOptimizedBuffer(80, 24, false); // OptimizedBuffer instance +fb.drawText('Hello via FFI', 0, 0, RGBA.fromValues(1,1,1,1)); +``` + +Create a TextBuffer and render it: +```ts +const tb = lib.createTextBuffer(128); +lib.textBufferWriteChunk(tb.ptr, new TextEncoder().encode('Hello TB'), null, null, 0); +lib.bufferDrawTextBuffer(fb.ptr, tb.ptr, 2, 2); +``` + +--- diff --git a/packages/core/docs/api/post/filters.md b/packages/core/docs/api/post/filters.md new file mode 100644 index 000000000..653b36435 --- /dev/null +++ b/packages/core/docs/api/post/filters.md @@ -0,0 +1,420 @@ +# Post-Processing Filters + +OpenTUI provides a set of post-processing filters that can be applied to the terminal output to create various visual effects. + +## Overview + +Post-processing filters allow you to modify the appearance of the terminal output after it has been rendered. This can be used to create visual effects like scanlines, grayscale, sepia tone, and more. + +## Basic Filters + +### Scanlines + +Applies a scanline effect by darkening every nth row. + +```typescript +import { applyScanlines, createCliRenderer, BoxRenderable } from '@opentui/core'; + +// Create a renderer +const renderer = await createCliRenderer(); + +// Create a component +const box = new BoxRenderable('box', { + width: 40, + height: 20, + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222' +}); + +renderer.root.add(box); + +// Apply scanlines to the renderer's buffer +renderer.on('afterRender', () => { + applyScanlines(renderer.buffer, 0.8, 2); +}); +``` + +### Grayscale + +Converts the buffer colors to grayscale. + +```typescript +import { applyGrayscale, createCliRenderer } from '@opentui/core'; + +// Create a renderer +const renderer = await createCliRenderer(); + +// Apply grayscale to the renderer's buffer +renderer.on('afterRender', () => { + applyGrayscale(renderer.buffer); +}); +``` + +### Sepia Tone + +Applies a sepia tone to the buffer. + +```typescript +import { applySepia, createCliRenderer } from '@opentui/core'; + +// Create a renderer +const renderer = await createCliRenderer(); + +// Apply sepia tone to the renderer's buffer +renderer.on('afterRender', () => { + applySepia(renderer.buffer); +}); +``` + +### Invert Colors + +Inverts the colors in the buffer. + +```typescript +import { applyInvert, createCliRenderer } from '@opentui/core'; + +// Create a renderer +const renderer = await createCliRenderer(); + +// Apply color inversion to the renderer's buffer +renderer.on('afterRender', () => { + applyInvert(renderer.buffer); +}); +``` + +### Noise + +Adds random noise to the buffer colors. + +```typescript +import { applyNoise, createCliRenderer } from '@opentui/core'; + +// Create a renderer +const renderer = await createCliRenderer(); + +// Apply noise to the renderer's buffer +renderer.on('afterRender', () => { + applyNoise(renderer.buffer, 0.1); +}); +``` + +### Chromatic Aberration + +Applies a simplified chromatic aberration effect. + +```typescript +import { applyChromaticAberration, createCliRenderer } from '@opentui/core'; + +// Create a renderer +const renderer = await createCliRenderer(); + +// Apply chromatic aberration to the renderer's buffer +renderer.on('afterRender', () => { + applyChromaticAberration(renderer.buffer, 1); +}); +``` + +### ASCII Art + +Converts the buffer to ASCII art based on background brightness. + +```typescript +import { applyAsciiArt, createCliRenderer } from '@opentui/core'; + +// Create a renderer +const renderer = await createCliRenderer(); + +// Apply ASCII art effect to the renderer's buffer +renderer.on('afterRender', () => { + applyAsciiArt(renderer.buffer, " .:-=+*#%@"); +}); +``` + +## Advanced Effects + +### Distortion Effect + +The `DistortionEffect` class provides an animated glitch/distortion effect. + +```typescript +import { DistortionEffect, createCliRenderer } from '@opentui/core'; + +// Create a renderer +const renderer = await createCliRenderer(); + +// Create a distortion effect +const distortion = new DistortionEffect({ + glitchChancePerSecond: 0.5, + maxGlitchLines: 3, + minGlitchDuration: 0.05, + maxGlitchDuration: 0.2, + maxShiftAmount: 10, + shiftFlipRatio: 0.6, + colorGlitchChance: 0.2 +}); + +// Apply the distortion effect in the render loop +renderer.on('afterRender', (context) => { + distortion.apply(renderer.buffer, context.deltaTime); +}); +``` + +### Vignette Effect + +The `VignetteEffect` class adds a vignette (darkened corners) to the buffer. + +```typescript +import { VignetteEffect, createCliRenderer } from '@opentui/core'; + +// Create a renderer +const renderer = await createCliRenderer(); + +// Create a vignette effect +const vignette = new VignetteEffect({ + strength: 0.7, + size: 0.8, + smoothness: 0.5 +}); + +// Apply the vignette effect in the render loop +renderer.on('afterRender', () => { + vignette.apply(renderer.buffer); +}); +``` + +### Brightness Effect + +The `BrightnessEffect` class adjusts the brightness of the buffer. + +```typescript +import { BrightnessEffect, createCliRenderer } from '@opentui/core'; + +// Create a renderer +const renderer = await createCliRenderer(); + +// Create a brightness effect +const brightness = new BrightnessEffect({ + value: 1.2 // Values > 1 increase brightness, < 1 decrease brightness +}); + +// Apply the brightness effect in the render loop +renderer.on('afterRender', () => { + brightness.apply(renderer.buffer); +}); +``` + +### Blur Effect + +The `BlurEffect` class applies a blur effect to the buffer. + +```typescript +import { BlurEffect, createCliRenderer } from '@opentui/core'; + +// Create a renderer +const renderer = await createCliRenderer(); + +// Create a blur effect +const blur = new BlurEffect({ + radius: 1, + passes: 2 +}); + +// Apply the blur effect in the render loop +renderer.on('afterRender', () => { + blur.apply(renderer.buffer); +}); +``` + +### Bloom Effect + +The `BloomEffect` class applies a bloom effect to bright areas of the buffer. + +```typescript +import { BloomEffect, createCliRenderer } from '@opentui/core'; + +// Create a renderer +const renderer = await createCliRenderer(); + +// Create a bloom effect +const bloom = new BloomEffect({ + threshold: 0.7, + intensity: 0.5, + radius: 1 +}); + +// Apply the bloom effect in the render loop +renderer.on('afterRender', () => { + bloom.apply(renderer.buffer); +}); +``` + +## Combining Effects + +You can combine multiple effects to create more complex visual styles. + +```typescript +import { + applyGrayscale, + applyScanlines, + VignetteEffect, + createCliRenderer +} from '@opentui/core'; + +// Create a renderer +const renderer = await createCliRenderer(); + +// Create a vignette effect +const vignette = new VignetteEffect({ + strength: 0.7, + size: 0.8, + smoothness: 0.5 +}); + +// Apply multiple effects in the render loop +renderer.on('afterRender', () => { + // Apply effects in sequence + applyGrayscale(renderer.buffer); + applyScanlines(renderer.buffer, 0.8, 2); + vignette.apply(renderer.buffer); +}); +``` + +## Example: Creating a Retro Terminal Effect + +```typescript +import { + createCliRenderer, + BoxRenderable, + TextRenderable, + applyGrayscale, + applyScanlines, + applyNoise, + VignetteEffect +} from '@opentui/core'; + +async function createRetroTerminal() { + // Create the renderer + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + borderStyle: 'single', + borderColor: '#00ff00', + backgroundColor: '#001100' + }); + + // Create a text element + const text = new TextRenderable('text', { + content: 'TERMINAL READY\n\n> _', + fg: '#00ff00', + padding: 1, + flexGrow: 1 + }); + + // Build the component tree + container.add(text); + root.add(container); + + // Create a vignette effect + const vignette = new VignetteEffect({ + strength: 0.6, + size: 0.75, + smoothness: 0.3 + }); + + // Apply retro effects + renderer.on('afterRender', (context) => { + // Apply green monochrome effect + applyGrayscale(renderer.buffer); + + // Apply scanlines + applyScanlines(renderer.buffer, 0.7, 2); + + // Add some noise + applyNoise(renderer.buffer, 0.05); + + // Add vignette + vignette.apply(renderer.buffer); + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the retro terminal +createRetroTerminal().catch(console.error); +``` + +## Example: Creating a Glitch Effect + +```typescript +import { + createCliRenderer, + BoxRenderable, + TextRenderable, + DistortionEffect, + applyChromaticAberration +} from '@opentui/core'; + +async function createGlitchEffect() { + // Create the renderer + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + borderStyle: 'double', + borderColor: '#ff00ff', + backgroundColor: '#110022' + }); + + // Create a text element + const text = new TextRenderable('text', { + content: 'SYSTEM ERROR\nCORRUPTION DETECTED\nATTEMPTING RECOVERY...', + fg: '#ffffff', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 + }); + + // Build the component tree + container.add(text); + root.add(container); + + // Create a distortion effect + const distortion = new DistortionEffect({ + glitchChancePerSecond: 0.8, + maxGlitchLines: 5, + minGlitchDuration: 0.1, + maxGlitchDuration: 0.3, + maxShiftAmount: 15, + shiftFlipRatio: 0.7, + colorGlitchChance: 0.3 + }); + + // Apply glitch effects + renderer.on('afterRender', (context) => { + // Apply chromatic aberration + applyChromaticAberration(renderer.buffer, 2); + + // Apply distortion + distortion.apply(renderer.buffer, context.deltaTime); + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the glitch effect +createGlitchEffect().catch(console.error); +``` diff --git a/packages/core/docs/api/react/reconciler.md b/packages/core/docs/api/react/reconciler.md new file mode 100644 index 000000000..d6fa14b3b --- /dev/null +++ b/packages/core/docs/api/react/reconciler.md @@ -0,0 +1,601 @@ +# React Reconciler + +OpenTUI provides a React reconciler that allows you to build terminal user interfaces using React components and hooks. + +## Overview + +The React reconciler consists of: + +1. **Reconciler**: The core React reconciler that bridges React and OpenTUI +2. **Host Config**: Configuration for the reconciler that defines how React components map to OpenTUI renderables +3. **Components**: Pre-built React components for common UI elements +4. **Hooks**: Custom React hooks for terminal-specific functionality + +## Getting Started + +To use React with OpenTUI, you need to install the React package: + +```bash +npm install @opentui/react +# or +yarn add @opentui/react +# or +bun add @opentui/react +``` + +Then you can create a React application: + +```tsx +import React from 'react'; +import { render, Box, Text } from '@opentui/react'; + +function App() { + return ( + + + + ); +} + +// Render the React application +render(); +``` + +## Components + +The React package provides components that map to OpenTUI renderables: + +### Box Component + +```tsx +import { Box } from '@opentui/react'; + +function MyComponent() { + return ( + + {/* Children go here */} + + ); +} +``` + +### Text Component + +```tsx +import { Text } from '@opentui/react'; + +function MyComponent() { + return ( + + ); +} +``` + +### Input Component + +```tsx +import { Input } from '@opentui/react'; +import { useState } from 'react'; + +function MyComponent() { + const [value, setValue] = useState(''); + + return ( + + ); +} +``` + +### Select Component + +```tsx +import { Select } from '@opentui/react'; +import { useState } from 'react'; + +function MyComponent() { + const [value, setValue] = useState('option1'); + + return ( + + + + + + + + + {todos.length === 0 ? ( + + ) : ( + todos.map((todo, index) => ( + toggleTodo(todo.id)} + > + + + + )) + )} + + + ); +} + +// Render the Todo app +render(); +``` diff --git a/packages/core/docs/api/renderables/ascii-font.md b/packages/core/docs/api/renderables/ascii-font.md new file mode 100644 index 000000000..c2cc797a5 --- /dev/null +++ b/packages/core/docs/api/renderables/ascii-font.md @@ -0,0 +1,515 @@ +# ASCII Font Renderer + +OpenTUI provides an ASCII font renderer for creating stylized text using ASCII art fonts. + +## Overview + +The `ASCIIFontRenderable` component allows you to render text using ASCII art fonts, which are defined as JSON files. This is useful for creating headers, logos, and other stylized text elements in your terminal applications. + +## ASCII Font API + +### Creating an ASCII Font Renderable + +```typescript +import { ASCIIFontRenderable } from '@opentui/core'; + +// Create an ASCII font renderable with the default font +const asciiText = new ASCIIFontRenderable('ascii-text', { + content: 'Hello', + fg: '#3498db', + alignItems: 'center', + justifyContent: 'center' +}); + +// Create an ASCII font renderable with a specific font +const customFont = new ASCIIFontRenderable('custom-font', { + content: 'OpenTUI', + font: 'block', // Use the 'block' font + fg: '#e74c3c', + alignItems: 'center', + justifyContent: 'center' +}); +``` + +### ASCII Font Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `content` | `string` | `''` | The text to render | +| `font` | `string` | `'slick'` | The font to use (e.g., 'slick', 'block', 'tiny', 'shade') | +| `fg` | `string` | `'#ffffff'` | The foreground color | +| `bg` | `string` | `'transparent'` | The background color | +| `alignItems` | `string` | `'flex-start'` | Vertical alignment | +| `justifyContent` | `string` | `'flex-start'` | Horizontal alignment | +| `width` | `number \| string` | `'auto'` | Width of the component | +| `height` | `number \| string` | `'auto'` | Height of the component | + +### Available Fonts + +OpenTUI includes several built-in ASCII art fonts: + +1. **Slick**: A sleek, minimalist font +2. **Block**: A bold, blocky font +3. **Tiny**: A small, compact font +4. **Shade**: A font with shading effects + +```typescript +// Examples of different fonts +const slickFont = new ASCIIFontRenderable('slick-font', { + content: 'Slick', + font: 'slick', + fg: '#3498db' +}); + +const blockFont = new ASCIIFontRenderable('block-font', { + content: 'Block', + font: 'block', + fg: '#e74c3c' +}); + +const tinyFont = new ASCIIFontRenderable('tiny-font', { + content: 'Tiny', + font: 'tiny', + fg: '#2ecc71' +}); + +const shadeFont = new ASCIIFontRenderable('shade-font', { + content: 'Shade', + font: 'shade', + fg: '#f39c12' +}); +``` + +### Changing the Content + +```typescript +// Create an ASCII font renderable +const asciiText = new ASCIIFontRenderable('ascii-text', { + content: 'Hello', + font: 'block', + fg: '#3498db' +}); + +// Change the content +asciiText.content = 'World'; + +// Change the font +asciiText.font = 'slick'; + +// Change the color +asciiText.fg = '#e74c3c'; +``` + +### Creating Custom Fonts + +You can create custom ASCII art fonts by defining them in JSON files: + +```json +{ + "name": "MyCustomFont", + "height": 5, + "chars": { + "A": [ + " # ", + " # # ", + "#####", + "# #", + "# #" + ], + "B": [ + "#### ", + "# #", + "#### ", + "# #", + "#### " + ], + // Define other characters... + } +} +``` + +Then load and use the custom font: + +```typescript +import { ASCIIFontRenderable, loadASCIIFont } from '@opentui/core'; +import * as fs from 'fs'; + +// Load a custom font +const customFontData = JSON.parse(fs.readFileSync('path/to/custom-font.json', 'utf8')); +loadASCIIFont('custom-font', customFontData); + +// Use the custom font +const customFont = new ASCIIFontRenderable('custom-font', { + content: 'Custom', + font: 'custom-font', + fg: '#9b59b6' +}); +``` + +## Example: Creating a Title Screen + +```typescript +import { createCliRenderer, BoxRenderable, TextRenderable, ASCIIFontRenderable } from '@opentui/core'; + +async function createTitleScreenDemo() { + // Create the renderer + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + borderStyle: 'double', + borderColor: '#3498db', + backgroundColor: '#222222', + padding: 2 + }); + + root.add(container); + + // Create a title using ASCII font + const title = new ASCIIFontRenderable('title', { + content: 'OpenTUI', + font: 'block', + fg: '#e74c3c', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + height: '30%' + }); + + container.add(title); + + // Create a subtitle + const subtitle = new TextRenderable('subtitle', { + content: 'Terminal User Interface Framework', + fg: '#3498db', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + height: '10%' + }); + + container.add(subtitle); + + // Create menu options + const menuContainer = new BoxRenderable('menu-container', { + width: '50%', + height: '40%', + x: '25%', + y: '50%', + borderStyle: 'single', + borderColor: '#2ecc71', + backgroundColor: 'transparent', + padding: 1 + }); + + container.add(menuContainer); + + // Add menu items + const menuItems = [ + 'New Game', + 'Load Game', + 'Options', + 'Credits', + 'Exit' + ]; + + let selectedIndex = 0; + + const menuItemRenderables = menuItems.map((item, index) => { + const menuItem = new TextRenderable(`menu-item-${index}`, { + content: `${index === selectedIndex ? '> ' : ' '}${item}`, + fg: index === selectedIndex ? '#ffffff' : '#bbbbbb', + y: index * 2, + width: '100%', + height: 1 + }); + + menuContainer.add(menuItem); + + return menuItem; + }); + + // Handle keyboard input + renderer.on('key', (key) => { + const keyStr = key.toString(); + + if (keyStr === 'up' || keyStr === 'k') { + // Update the previously selected item + menuItemRenderables[selectedIndex].content = ` ${menuItems[selectedIndex]}`; + menuItemRenderables[selectedIndex].fg = '#bbbbbb'; + + // Move selection up + selectedIndex = (selectedIndex - 1 + menuItems.length) % menuItems.length; + + // Update the newly selected item + menuItemRenderables[selectedIndex].content = `> ${menuItems[selectedIndex]}`; + menuItemRenderables[selectedIndex].fg = '#ffffff'; + } else if (keyStr === 'down' || keyStr === 'j') { + // Update the previously selected item + menuItemRenderables[selectedIndex].content = ` ${menuItems[selectedIndex]}`; + menuItemRenderables[selectedIndex].fg = '#bbbbbb'; + + // Move selection down + selectedIndex = (selectedIndex + 1) % menuItems.length; + + // Update the newly selected item + menuItemRenderables[selectedIndex].content = `> ${menuItems[selectedIndex]}`; + menuItemRenderables[selectedIndex].fg = '#ffffff'; + } else if (keyStr === 'return') { + // Handle menu selection + if (selectedIndex === menuItems.length - 1) { + // Exit option + renderer.destroy(); + process.exit(0); + } else { + // Show a message for other options + title.content = menuItems[selectedIndex]; + } + } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C + renderer.destroy(); + process.exit(0); + } + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the title screen demo +createTitleScreenDemo().catch(console.error); +``` + +## Example: Creating a Banner + +```typescript +import { createCliRenderer, BoxRenderable, ASCIIFontRenderable } from '@opentui/core'; + +async function createBannerDemo() { + // Create the renderer + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + borderStyle: 'none', + backgroundColor: '#222222' + }); + + root.add(container); + + // Create a banner using ASCII font + const banner = new ASCIIFontRenderable('banner', { + content: 'WELCOME', + font: 'block', + fg: '#e74c3c', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + height: '30%', + y: '10%' + }); + + container.add(banner); + + // Create a second line + const secondLine = new ASCIIFontRenderable('second-line', { + content: 'TO', + font: 'slick', + fg: '#f39c12', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + height: '20%', + y: '40%' + }); + + container.add(secondLine); + + // Create a third line + const thirdLine = new ASCIIFontRenderable('third-line', { + content: 'OPENTUI', + font: 'shade', + fg: '#2ecc71', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + height: '30%', + y: '60%' + }); + + container.add(thirdLine); + + // Start the renderer + renderer.start(); + + // Animate the banner + let frame = 0; + const colors = [ + '#e74c3c', // Red + '#e67e22', // Orange + '#f1c40f', // Yellow + '#2ecc71', // Green + '#3498db', // Blue + '#9b59b6' // Purple + ]; + + const interval = setInterval(() => { + frame = (frame + 1) % colors.length; + + banner.fg = colors[frame]; + secondLine.fg = colors[(frame + 2) % colors.length]; + thirdLine.fg = colors[(frame + 4) % colors.length]; + }, 500); + + // Handle keyboard input + renderer.on('key', (key) => { + const keyStr = key.toString(); + + if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C + clearInterval(interval); + renderer.destroy(); + process.exit(0); + } + }); + + return renderer; +} + +// Create and run the banner demo +createBannerDemo().catch(console.error); +``` + +## Example: Creating a Loading Screen + +```typescript +import { createCliRenderer, BoxRenderable, TextRenderable, ASCIIFontRenderable } from '@opentui/core'; + +async function createLoadingScreenDemo() { + // Create the renderer + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + borderStyle: 'none', + backgroundColor: '#222222' + }); + + root.add(container); + + // Create a title using ASCII font + const title = new ASCIIFontRenderable('title', { + content: 'LOADING', + font: 'block', + fg: '#3498db', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + height: '30%', + y: '20%' + }); + + container.add(title); + + // Create a loading bar container + const loadingBarContainer = new BoxRenderable('loading-bar-container', { + width: '80%', + height: 3, + x: '10%', + y: '60%', + borderStyle: 'single', + borderColor: '#ffffff', + backgroundColor: 'transparent' + }); + + container.add(loadingBarContainer); + + // Create a loading bar + const loadingBar = new BoxRenderable('loading-bar', { + width: '0%', + height: 1, + x: 0, + y: 1, + borderStyle: 'none', + backgroundColor: '#2ecc71' + }); + + loadingBarContainer.add(loadingBar); + + // Create a loading text + const loadingText = new TextRenderable('loading-text', { + content: 'Loading... 0%', + fg: '#ffffff', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + height: 1, + y: '70%' + }); + + container.add(loadingText); + + // Start the renderer + renderer.start(); + + // Simulate loading + let progress = 0; + + const interval = setInterval(() => { + progress += 1; + + if (progress > 100) { + clearInterval(interval); + + // Change the title + title.content = 'COMPLETE'; + title.fg = '#2ecc71'; + + // Update the loading text + loadingText.content = 'Press any key to continue'; + + return; + } + + // Update the loading bar + loadingBar.width = `${progress}%`; + + // Update the loading text + loadingText.content = `Loading... ${progress}%`; + + // Change the title color based on progress + if (progress < 30) { + title.fg = '#3498db'; + } else if (progress < 60) { + title.fg = '#f39c12'; + } else { + title.fg = '#2ecc71'; + } + }, 50); + + // Handle keyboard input + renderer.on('key', (key) => { + const keyStr = key.toString(); + + if (progress >= 100 || keyStr === 'q' || keyStr === '\u0003') { // Any key after loading or q or Ctrl+C + clearInterval(interval); + renderer.destroy(); + process.exit(0); + } + }); + + return renderer; +} + +// Create and run the loading screen demo +createLoadingScreenDemo().catch(console.error); +``` diff --git a/packages/core/docs/api/styling/text-styling.md b/packages/core/docs/api/styling/text-styling.md new file mode 100644 index 000000000..241196278 --- /dev/null +++ b/packages/core/docs/api/styling/text-styling.md @@ -0,0 +1,451 @@ +# Text Styling API + +OpenTUI provides a rich text styling system that allows you to create formatted text with colors, attributes, and complex layouts. + +## StyledText + +The `StyledText` class is the core of the text styling system, allowing you to create rich text with different styles. + +### Creating Styled Text + +```typescript +import { StyledText, stringToStyledText } from '@opentui/core'; + +// Create an empty styled text +const text = new StyledText(); + +// Create from a string +const fromString = stringToStyledText('Hello, world!'); + +// Create from a string with ANSI escape codes +const withAnsi = stringToStyledText('\x1b[31mRed text\x1b[0m and \x1b[1mBold text\x1b[0m'); +``` + +### Adding Content + +```typescript +// Add plain text +text.pushText('Hello, '); +text.pushText('world!'); + +// Add styled text +text.pushFg('#ff0000'); // Start red text +text.pushText('This is red'); +text.popFg(); // End red text + +text.pushBg('#0000ff'); // Start blue background +text.pushText('This has a blue background'); +text.popBg(); // End blue background + +text.pushAttributes(0x01); // Start bold text (0x01 is bold) +text.pushText('This is bold'); +text.popAttributes(); // End bold text + +// Combine styles +text.pushFg('#ff0000'); +text.pushBg('#ffffff'); +text.pushAttributes(0x01 | 0x04); // Bold and underline +text.pushText('Bold red text on white background with underline'); +text.popAttributes(); +text.popBg(); +text.popFg(); +``` + +### Text Attributes + +Text attributes can be combined using bitwise OR (`|`). + +| Attribute | Value | Description | +|-----------|-------|-------------| +| `BOLD` | `0x01` | Bold text | +| `DIM` | `0x02` | Dimmed text | +| `ITALIC` | `0x04` | Italic text | +| `UNDERLINE` | `0x08` | Underlined text | +| `BLINK` | `0x10` | Blinking text | +| `INVERSE` | `0x20` | Inverted colors | +| `HIDDEN` | `0x40` | Hidden text | +| `STRIKETHROUGH` | `0x80` | Strikethrough text | + +```typescript +// Example of combining attributes +text.pushAttributes(0x01 | 0x08); // Bold and underlined +text.pushText('Bold and underlined text'); +text.popAttributes(); +``` + +### Converting to String + +```typescript +// Convert to plain text (strips all formatting) +const plainText = text.toString(); + +// Convert to ANSI-encoded string (preserves formatting) +const ansiText = text.toAnsiString(); +``` + +### Example: Creating a Rich Text Message + +```typescript +import { StyledText, TextRenderable } from '@opentui/core'; + +// Create a styled text message +const message = new StyledText(); + +// Add a timestamp +message.pushFg('#888888'); +message.pushText('[12:34:56] '); +message.popFg(); + +// Add a status indicator +message.pushFg('#ff0000'); +message.pushAttributes(0x01); // Bold +message.pushText('ERROR'); +message.popAttributes(); +message.popFg(); + +// Add a separator +message.pushText(': '); + +// Add the message +message.pushText('Failed to connect to server '); + +// Add details +message.pushFg('#3498db'); +message.pushText('example.com'); +message.popFg(); + +// Create a text component with the styled message +const textComponent = new TextRenderable('errorMessage', { + content: message +}); +``` + +## HAST Styled Text + +OpenTUI supports HAST (Hypertext Abstract Syntax Tree) for more complex text styling. + +### Creating HAST Styled Text + +```typescript +import { hastToStyledText } from '@opentui/core'; + +// Create styled text from HAST +const hast = { + type: 'root', + children: [ + { + type: 'element', + tagName: 'span', + properties: { + style: 'color: red; font-weight: bold;' + }, + children: [ + { + type: 'text', + value: 'Important' + } + ] + }, + { + type: 'text', + value: ' message' + } + ] +}; + +const styledText = hastToStyledText(hast); +``` + +### Supported HAST Properties + +| Property | Description | +|----------|-------------| +| `style` | CSS-like style string | +| `color`, `backgroundColor` | Text colors | +| `bold`, `italic`, `underline` | Text formatting | + +### Example: Syntax Highlighting with HAST + +```typescript +import { hastToStyledText, TextRenderable } from '@opentui/core'; + +// HAST representation of syntax-highlighted code +const codeHast = { + type: 'root', + children: [ + { + type: 'element', + tagName: 'span', + properties: { style: 'color: #569cd6;' }, + children: [{ type: 'text', value: 'function' }] + }, + { type: 'text', value: ' ' }, + { + type: 'element', + tagName: 'span', + properties: { style: 'color: #dcdcaa;' }, + children: [{ type: 'text', value: 'greet' }] + }, + { type: 'text', value: '(' }, + { + type: 'element', + tagName: 'span', + properties: { style: 'color: #9cdcfe;' }, + children: [{ type: 'text', value: 'name' }] + }, + { type: 'text', value: ') {\n ' }, + { + type: 'element', + tagName: 'span', + properties: { style: 'color: #c586c0;' }, + children: [{ type: 'text', value: 'return' }] + }, + { type: 'text', value: ' ' }, + { + type: 'element', + tagName: 'span', + properties: { style: 'color: #ce9178;' }, + children: [{ type: 'text', value: '`Hello, ${name}!`' }] + }, + { type: 'text', value: ';\n}' } + ] +}; + +// Convert to styled text +const codeStyledText = hastToStyledText(codeHast); + +// Create a text component with the syntax-highlighted code +const codeBlock = new TextRenderable('codeBlock', { + content: codeStyledText, + bg: '#1e1e1e' +}); +``` + +## Text Buffer + +The `TextBuffer` class provides low-level text rendering capabilities. + +### Creating a Text Buffer + +```typescript +import { TextBuffer, RGBA } from '@opentui/core'; + +// Create a text buffer with initial capacity +const buffer = TextBuffer.create(100); + +// Set default styles +buffer.setDefaultFg(RGBA.fromHex('#ffffff')); +buffer.setDefaultBg(RGBA.fromHex('#000000')); +buffer.setDefaultAttributes(0); // No attributes +``` + +### Adding Text + +```typescript +// Add text at a specific position +buffer.addText(0, 0, 'Hello, world!'); + +// Add text with specific styles +buffer.addText(0, 1, 'Colored text', RGBA.fromHex('#ff0000'), RGBA.fromHex('#000000'), 0x01); + +// Add styled text +buffer.setStyledText(styledTextObject); +``` + +### Selection + +```typescript +// Set selection range +buffer.setSelection(5, 10, RGBA.fromHex('#3498db'), RGBA.fromHex('#ffffff')); + +// Reset selection +buffer.resetSelection(); +``` + +### Example: Creating a Custom Text Component + +```typescript +import { Renderable, TextBuffer, OptimizedBuffer, RGBA } from '@opentui/core'; + +class HighlightedTextRenderable extends Renderable { + private textBuffer: TextBuffer; + private _text: string = ''; + private _highlightRanges: Array<{ start: number; end: number; color: RGBA }> = []; + + constructor(id: string, options: any = {}) { + super(id, options); + + this._text = options.text || ''; + this._highlightRanges = options.highlightRanges || []; + + this.textBuffer = TextBuffer.create(this._text.length + 100); + this.textBuffer.setDefaultFg(RGBA.fromHex('#ffffff')); + this.textBuffer.setDefaultBg(RGBA.fromHex('#000000')); + + this.updateTextBuffer(); + } + + get text(): string { + return this._text; + } + + set text(value: string) { + this._text = value; + this.updateTextBuffer(); + } + + get highlightRanges(): Array<{ start: number; end: number; color: RGBA }> { + return [...this._highlightRanges]; + } + + set highlightRanges(value: Array<{ start: number; end: number; color: RGBA | string }>) { + this._highlightRanges = value.map(range => ({ + start: range.start, + end: range.end, + color: typeof range.color === 'string' ? RGBA.fromHex(range.color) : range.color + })); + this.updateTextBuffer(); + } + + private updateTextBuffer(): void { + this.textBuffer.clear(); + this.textBuffer.addText(0, 0, this._text); + + // Apply highlights + for (const range of this._highlightRanges) { + this.textBuffer.setSelection(range.start, range.end, range.color); + } + + this.needsUpdate(); + } + + protected renderSelf(buffer: OptimizedBuffer): void { + const clipRect = { + x: this.x, + y: this.y, + width: this.width, + height: this.height + }; + + buffer.drawTextBuffer(this.textBuffer, this.x, this.y, clipRect); + } + + destroy(): void { + this.textBuffer.destroy(); + super.destroy(); + } +} + +// Usage +const highlightedText = new HighlightedTextRenderable('highlightedText', { + width: 40, + height: 1, + text: 'This is a text with highlighted parts', + highlightRanges: [ + { start: 10, end: 14, color: '#ff0000' }, // "text" in red + { start: 20, end: 31, color: '#00ff00' } // "highlighted" in green + ] +}); + +// Update text +highlightedText.text = 'New text with different highlights'; + +// Update highlights +highlightedText.highlightRanges = [ + { start: 0, end: 3, color: '#3498db' }, // "New" in blue + { start: 9, end: 18, color: '#e74c3c' } // "different" in red +]; +``` + +## Border Styling + +OpenTUI provides various border styles for boxes and other components. + +### Border Styles + +| Style | Description | +|-------|-------------| +| `'single'` | Single line border | +| `'double'` | Double line border | +| `'rounded'` | Rounded corners with single lines | +| `'bold'` | Bold single line border | +| `'dashed'` | Dashed border | +| `'dotted'` | Dotted border | +| `'ascii'` | ASCII characters (`+`, `-`, `|`) | + +### Border Sides + +You can specify which sides of a component should have borders: + +```typescript +import { BoxRenderable } from '@opentui/core'; + +// All sides (default) +const box1 = new BoxRenderable('box1', { + border: true +}); + +// Specific sides +const box2 = new BoxRenderable('box2', { + border: ['top', 'bottom'] +}); + +const box3 = new BoxRenderable('box3', { + border: ['left', 'right'] +}); + +// No borders +const box4 = new BoxRenderable('box4', { + border: false +}); +``` + +### Custom Border Characters + +You can define custom border characters for unique styling: + +```typescript +import { BoxRenderable } from '@opentui/core'; + +const customBox = new BoxRenderable('customBox', { + customBorderChars: { + topLeft: '╭', + topRight: '╮', + bottomLeft: '╰', + bottomRight: '╯', + horizontal: '─', + vertical: '│', + left: '├', + right: '┤', + top: '┬', + bottom: '┴', + middle: '┼' + } +}); +``` + +### Example: Creating a Custom Panel + +```typescript +import { BoxRenderable, TextRenderable } from '@opentui/core'; + +// Create a panel with custom styling +const panel = new BoxRenderable('panel', { + width: 40, + height: 10, + borderStyle: 'rounded', + borderColor: '#3498db', + backgroundColor: '#222222', + title: 'Custom Panel', + titleAlignment: 'center' +}); + +// Add content +const content = new TextRenderable('content', { + content: 'This panel has rounded corners and a centered title.', + fg: '#ffffff', + padding: 1 +}); + +panel.add(content); +``` diff --git a/packages/core/docs/api/utils/console.md b/packages/core/docs/api/utils/console.md new file mode 100644 index 000000000..d3e8457d2 --- /dev/null +++ b/packages/core/docs/api/utils/console.md @@ -0,0 +1,464 @@ +# Console Utility + +OpenTUI provides a console utility for debugging and logging in terminal applications. + +## Overview + +The console utility allows you to create a console window within your terminal application for displaying logs, errors, and debug information without interfering with your main UI. + +## Console API + +### Creating a Console + +```typescript +import { TerminalConsole, ConsolePosition } from '@opentui/core'; +import { createCliRenderer } from '@opentui/core'; + +// Create a renderer +const renderer = await createCliRenderer(); + +// Create a console with default options +const console = new TerminalConsole(renderer); + +// Create a console with custom options +const customConsole = new TerminalConsole(renderer, { + position: ConsolePosition.BOTTOM, + sizePercent: 30, + zIndex: 100, + colorInfo: '#00FFFF', + colorWarn: '#FFFF00', + colorError: '#FF0000', + colorDebug: '#808080', + colorDefault: '#FFFFFF', + backgroundColor: 'rgba(0.1, 0.1, 0.1, 0.7)', + startInDebugMode: false, + title: 'Debug Console', + titleBarColor: 'rgba(0.05, 0.05, 0.05, 0.7)', + titleBarTextColor: '#FFFFFF', + cursorColor: '#00A0FF', + maxStoredLogs: 2000, + maxDisplayLines: 3000 +}); +``` + +### Console Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `position` | `ConsolePosition` | `ConsolePosition.BOTTOM` | Position of the console window | +| `sizePercent` | `number` | `30` | Size percentage of the console relative to terminal | +| `zIndex` | `number` | `Infinity` | Z-index of the console | +| `colorInfo` | `ColorInput` | `'#00FFFF'` | Color for info messages | +| `colorWarn` | `ColorInput` | `'#FFFF00'` | Color for warning messages | +| `colorError` | `ColorInput` | `'#FF0000'` | Color for error messages | +| `colorDebug` | `ColorInput` | `'#808080'` | Color for debug messages | +| `colorDefault` | `ColorInput` | `'#FFFFFF'` | Default text color | +| `backgroundColor` | `ColorInput` | `RGBA.fromValues(0.1, 0.1, 0.1, 0.7)` | Background color | +| `startInDebugMode` | `boolean` | `false` | Whether to start in debug mode | +| `title` | `string` | `'Console'` | Title of the console window | +| `titleBarColor` | `ColorInput` | `RGBA.fromValues(0.05, 0.05, 0.05, 0.7)` | Title bar color | +| `titleBarTextColor` | `ColorInput` | `'#FFFFFF'` | Title bar text color | +| `cursorColor` | `ColorInput` | `'#00A0FF'` | Cursor color | +| `maxStoredLogs` | `number` | `2000` | Maximum number of logs to store | +| `maxDisplayLines` | `number` | `3000` | Maximum number of lines to display | + +### Logging Messages + +```typescript +// The TerminalConsole captures standard console methods +// These will be displayed in the terminal console +console.log('Hello, world!'); +console.error('Something went wrong'); +console.warn('This is a warning'); +console.info('This is an informational message'); +console.debug('This is a debug message'); + +// Log an object +console.log({ name: 'John', age: 30 }); + +// Log with formatting +console.log('User %s logged in at %s', 'John', new Date().toISOString()); + +// Activate console capture +terminalConsole.activate(); + +// Deactivate console capture +terminalConsole.deactivate(); +``` + +### Clearing the Console + +```typescript +// Clear the console +terminalConsoleCache.clearConsole(); +``` + +### Showing and Hiding the Console + +```typescript +// Show the console +terminalConsole.show(); + +// Hide the console +terminalConsole.hide(); + +// Toggle the console visibility +// Not directly available, but can be implemented: +if (terminalConsole.isVisible) { + terminalConsole.hide(); +} else { + terminalConsole.show(); +} + +// Check if the console is visible +const isVisible = terminalConsole.isVisible; +``` + +### Resizing the Console + +```typescript +// Resize the console +console.resize(100, 30); + +// Get the console dimensions +const { width, height } = console.getDimensions(); +``` + +### Scrolling the Console + +```typescript +// Scroll to the top +console.scrollToTop(); + +// Scroll to the bottom +console.scrollToBottom(); + +// Scroll up by a number of lines +console.scrollUp(5); + +// Scroll down by a number of lines +console.scrollDown(5); + +// Scroll to a specific line +console.scrollToLine(42); +``` + +### Filtering Console Output + +```typescript +// Set a filter function +console.setFilter((message) => { + // Only show messages containing 'error' + return message.text.includes('error'); +}); + +// Clear the filter +console.clearFilter(); +``` + +### Capturing Standard Output + +```typescript +// Capture stdout and stderr +console.captureStdout(); +console.captureStderr(); + +// Stop capturing +console.releaseStdout(); +console.releaseStderr(); +``` + +## Example: Creating a Debug Console + +```typescript +import { createCliRenderer, BoxRenderable, TextRenderable, Console } from '@opentui/core'; + +async function createDebugConsoleDemo() { + // Create the renderer + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container for the main UI + const container = new BoxRenderable('container', { + width: '100%', + height: '70%', + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222' + }); + + root.add(container); + + // Add some content to the main UI + const text = new TextRenderable('text', { + content: 'Press F12 to toggle the debug console\nPress L to log a message\nPress E to log an error\nPress W to log a warning\nPress C to clear the console', + fg: '#ffffff', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 + }); + + container.add(text); + + // Create a debug console + const debugConsole = new Console({ + width: renderer.width, + height: Math.floor(renderer.height * 0.3), + title: 'Debug Console', + position: 'bottom', + borderStyle: 'double', + borderColor: '#e74c3c', + backgroundColor: '#222222', + fg: '#ffffff' + }); + + // Add the console to the renderer + root.add(debugConsole); + + // Hide the console initially + debugConsole.hide(); + + // Log some initial messages + debugConsole.log('Debug console initialized'); + debugConsole.info('Press F12 to toggle the console'); + + // Handle keyboard input + renderer.on('key', (key) => { + const keyStr = key.toString(); + + if (keyStr === 'f12') { + debugConsole.toggle(); + } else if (keyStr === 'l') { + debugConsole.log(`Log message at ${new Date().toISOString()}`); + } else if (keyStr === 'e') { + debugConsole.error(`Error message at ${new Date().toISOString()}`); + } else if (keyStr === 'w') { + debugConsole.warn(`Warning message at ${new Date().toISOString()}`); + } else if (keyStr === 'c') { + debugConsole.clear(); + } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C + renderer.destroy(); + process.exit(0); + } + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the debug console demo +createDebugConsoleDemo().catch(console.error); +``` + +## Example: Capturing Standard Output + +```typescript +import { createCliRenderer, BoxRenderable, TextRenderable, Console } from '@opentui/core'; + +async function createStdoutCaptureDemo() { + // Create the renderer + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container for the main UI + const container = new BoxRenderable('container', { + width: '100%', + height: '70%', + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222' + }); + + root.add(container); + + // Add some content to the main UI + const text = new TextRenderable('text', { + content: 'Press F12 to toggle the console\nPress L to log to stdout\nPress E to log to stderr\nPress C to clear the console', + fg: '#ffffff', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 + }); + + container.add(text); + + // Create a debug console + const debugConsole = new Console({ + width: renderer.width, + height: Math.floor(renderer.height * 0.3), + title: 'Stdout/Stderr Capture', + position: 'bottom', + borderStyle: 'double', + borderColor: '#e74c3c', + backgroundColor: '#222222', + fg: '#ffffff' + }); + + // Add the console to the renderer + root.add(debugConsole); + + // Hide the console initially + debugConsole.hide(); + + // Capture stdout and stderr + debugConsole.captureStdout(); + debugConsole.captureStderr(); + + // Log some initial messages + debugConsole.log('Stdout/stderr capture initialized'); + debugConsole.info('Press F12 to toggle the console'); + + // Handle keyboard input + renderer.on('key', (key) => { + const keyStr = key.toString(); + + if (keyStr === 'f12') { + debugConsole.toggle(); + } else if (keyStr === 'l') { + // This will be captured by the console + console.log(`Stdout message at ${new Date().toISOString()}`); + } else if (keyStr === 'e') { + // This will be captured by the console + console.error(`Stderr message at ${new Date().toISOString()}`); + } else if (keyStr === 'c') { + debugConsole.clear(); + } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C + // Release stdout and stderr before exiting + debugConsole.releaseStdout(); + debugConsole.releaseStderr(); + + renderer.destroy(); + process.exit(0); + } + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the stdout capture demo +createStdoutCaptureDemo().catch(console.error); +``` + +## Example: Creating a Network Monitor + +```typescript +import { createCliRenderer, BoxRenderable, TextRenderable, Console } from '@opentui/core'; +import * as http from 'http'; + +async function createNetworkMonitorDemo() { + // Create the renderer + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container for the main UI + const container = new BoxRenderable('container', { + width: '100%', + height: '70%', + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222' + }); + + root.add(container); + + // Add some content to the main UI + const text = new TextRenderable('text', { + content: 'Network Monitor\n\nPress F12 to toggle the console\nPress R to make a request\nPress C to clear the console', + fg: '#ffffff', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 + }); + + container.add(text); + + // Create a network monitor console + const networkConsole = new Console({ + width: renderer.width, + height: Math.floor(renderer.height * 0.3), + title: 'Network Monitor', + position: 'bottom', + borderStyle: 'double', + borderColor: '#9b59b6', + backgroundColor: '#222222', + fg: '#ffffff' + }); + + // Add the console to the renderer + root.add(networkConsole); + + // Hide the console initially + networkConsole.hide(); + + // Log some initial messages + networkConsole.log('Network monitor initialized'); + networkConsole.info('Press F12 to toggle the console'); + + // Function to make a request + function makeRequest() { + const startTime = Date.now(); + networkConsole.log(`Making request to example.com...`, '#3498db'); + + http.get('http://example.com', (res) => { + const endTime = Date.now(); + const duration = endTime - startTime; + + networkConsole.log(`Response received in ${duration}ms`, '#2ecc71'); + networkConsole.log(`Status: ${res.statusCode} ${res.statusMessage}`, '#2ecc71'); + + // Log headers + networkConsole.log('Headers:', '#f39c12'); + for (const [key, value] of Object.entries(res.headers)) { + networkConsole.log(` ${key}: ${value}`, '#f39c12'); + } + + // Collect response body + let body = ''; + res.on('data', (chunk) => { + body += chunk; + }); + + res.on('end', () => { + networkConsole.log(`Body length: ${body.length} bytes`, '#2ecc71'); + + // Show a preview of the body + if (body.length > 0) { + networkConsole.log('Body preview:', '#f39c12'); + networkConsole.log(body.substring(0, 200) + (body.length > 200 ? '...' : ''), '#f39c12'); + } + }); + }).on('error', (err) => { + networkConsole.error(`Request failed: ${err.message}`); + }); + } + + // Handle keyboard input + renderer.on('key', (key) => { + const keyStr = key.toString(); + + if (keyStr === 'f12') { + networkConsole.toggle(); + } else if (keyStr === 'r') { + makeRequest(); + } else if (keyStr === 'c') { + networkConsole.clear(); + } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C + renderer.destroy(); + process.exit(0); + } + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the network monitor demo +createNetworkMonitorDemo().catch(console.error); +``` diff --git a/packages/core/docs/api/utils/output-capture.md b/packages/core/docs/api/utils/output-capture.md new file mode 100644 index 000000000..5b000e913 --- /dev/null +++ b/packages/core/docs/api/utils/output-capture.md @@ -0,0 +1,428 @@ +# Output Capture + +OpenTUI provides utilities for capturing stdout and stderr output, allowing you to redirect console output to your terminal UI. + +## Overview + +The output capture utilities allow you to intercept standard output and error streams and redirect them to your OpenTUI application. This is useful for creating debug consoles, log viewers, and other tools that need to display output from other parts of your application. + +## Output Capture API + +### Capturing Stdout and Stderr + +```typescript +import { capture, CapturedWritableStream } from '@opentui/core'; + +// Create captured writable streams +const mockStdout = new CapturedWritableStream("stdout", capture); +const mockStderr = new CapturedWritableStream("stderr", capture); + +// Listen for captured output +capture.on('write', (stream, data) => { + console.log(`Captured ${stream}:`, data); +}); + +// Write to the streams +mockStdout.write('Hello from stdout'); +mockStderr.write('Error from stderr'); + +// Get all captured output +const allOutput = capture.claimOutput(); +``` + +### Capture API + +The `Capture` class provides methods for capturing and managing output: + +```typescript +import { Capture } from '@opentui/core'; + +// Create a capture instance +const capture = new Capture(); + +// Listen for write events +capture.on('write', (stream, data) => { + console.log(`Captured ${stream}:`, data); +}); + +// Write data to the capture +capture.write('stdout', 'Hello world'); + +// Get the size of captured data +const size = capture.size; + +// Get all captured output and clear the buffer +const output = capture.claimOutput(); +``` + +### CapturedWritableStream API + +The `CapturedWritableStream` class implements Node's Writable stream interface: + +```typescript +import { Capture, CapturedWritableStream } from '@opentui/core'; + +// Create a capture instance +const capture = new Capture(); + +// Create writable streams +const stdout = new CapturedWritableStream('stdout', capture); +const stderr = new CapturedWritableStream('stderr', capture); + +// Write to the streams +stdout.write('Standard output'); +stderr.write('Standard error'); +``` + +## Example: Creating a Log Viewer + +```typescript +import { createCliRenderer, BoxRenderable, TextRenderable, captureStdout, captureStderr } from '@opentui/core'; + +async function createLogViewerDemo() { + // Create the renderer + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container for the main UI + const container = new BoxRenderable('container', { + width: '100%', + height: '40%', + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222' + }); + + root.add(container); + + // Add some content to the main UI + const text = new TextRenderable('text', { + content: 'Log Viewer\n\nPress L to log to stdout\nPress E to log to stderr\nPress Q to quit', + fg: '#ffffff', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 + }); + + container.add(text); + + // Create a log viewer for stdout + const stdoutViewer = new BoxRenderable('stdout-viewer', { + width: '100%', + height: '30%', + borderStyle: 'single', + borderColor: '#2ecc71', + backgroundColor: '#222222', + title: 'Stdout', + padding: 1, + y: '40%' + }); + + const stdoutText = new TextRenderable('stdout-text', { + content: '', + fg: '#ffffff', + flexGrow: 1 + }); + + stdoutViewer.add(stdoutText); + root.add(stdoutViewer); + + // Create a log viewer for stderr + const stderrViewer = new BoxRenderable('stderr-viewer', { + width: '100%', + height: '30%', + borderStyle: 'single', + borderColor: '#e74c3c', + backgroundColor: '#222222', + title: 'Stderr', + padding: 1, + y: '70%' + }); + + const stderrText = new TextRenderable('stderr-text', { + content: '', + fg: '#ffffff', + flexGrow: 1 + }); + + stderrViewer.add(stderrText); + root.add(stderrViewer); + + // Capture stdout + const stdoutRelease = captureStdout((data) => { + // Append the data to the stdout text + stdoutText.content += data; + + // Trim the content if it gets too long + if (stdoutText.content.length > 1000) { + stdoutText.content = stdoutText.content.substring(stdoutText.content.length - 1000); + } + }, { + passthrough: true + }); + + // Capture stderr + const stderrRelease = captureStderr((data) => { + // Append the data to the stderr text + stderrText.content += data; + + // Trim the content if it gets too long + if (stderrText.content.length > 1000) { + stderrText.content = stderrText.content.substring(stderrText.content.length - 1000); + } + }, { + passthrough: true + }); + + // Handle keyboard input + renderer.on('key', (key) => { + const keyStr = key.toString(); + + if (keyStr === 'l') { + // Log to stdout + console.log(`Stdout message at ${new Date().toISOString()}`); + } else if (keyStr === 'e') { + // Log to stderr + console.error(`Stderr message at ${new Date().toISOString()}`); + } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C + // Release stdout and stderr before exiting + stdoutRelease(); + stderrRelease(); + + renderer.destroy(); + process.exit(0); + } + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the log viewer demo +createLogViewerDemo().catch(console.error); +``` + +## Example: Capturing Child Process Output + +```typescript +import { createCliRenderer, BoxRenderable, TextRenderable, captureStdout, captureStderr } from '@opentui/core'; +import { spawn } from 'child_process'; + +async function createProcessMonitorDemo() { + // Create the renderer + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container for the main UI + const container = new BoxRenderable('container', { + width: '100%', + height: '40%', + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222' + }); + + root.add(container); + + // Add some content to the main UI + const text = new TextRenderable('text', { + content: 'Process Monitor\n\nPress R to run a process\nPress C to clear the output\nPress Q to quit', + fg: '#ffffff', + alignItems: 'center', + justifyContent: 'center', + flexGrow: 1 + }); + + container.add(text); + + // Create a process output viewer + const outputViewer = new BoxRenderable('output-viewer', { + width: '100%', + height: '60%', + borderStyle: 'single', + borderColor: '#9b59b6', + backgroundColor: '#222222', + title: 'Process Output', + padding: 1, + y: '40%' + }); + + const outputText = new TextRenderable('output-text', { + content: '', + fg: '#ffffff', + flexGrow: 1 + }); + + outputViewer.add(outputText); + root.add(outputViewer); + + // Function to run a process + function runProcess() { + // Clear the output + outputText.content = ''; + + // Add a header + outputText.content = `Running 'ls -la' at ${new Date().toISOString()}\n\n`; + + // Spawn a process + const process = spawn('ls', ['-la']); + + // Capture stdout + process.stdout.on('data', (data) => { + outputText.content += `[stdout] ${data}`; + }); + + // Capture stderr + process.stderr.on('data', (data) => { + outputText.content += `[stderr] ${data}`; + }); + + // Handle process exit + process.on('close', (code) => { + outputText.content += `\nProcess exited with code ${code}\n`; + }); + } + + // Handle keyboard input + renderer.on('key', (key) => { + const keyStr = key.toString(); + + if (keyStr === 'r') { + runProcess(); + } else if (keyStr === 'c') { + outputText.content = ''; + } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C + renderer.destroy(); + process.exit(0); + } + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the process monitor demo +createProcessMonitorDemo().catch(console.error); +``` + +## Example: Creating a REPL + +```typescript +import { createCliRenderer, BoxRenderable, TextRenderable, InputRenderable, captureStdout, captureStderr } from '@opentui/core'; +import { runInNewContext } from 'vm'; + +async function createReplDemo() { + // Create the renderer + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a container for the output + const outputContainer = new BoxRenderable('output-container', { + width: '100%', + height: '90%', + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222', + title: 'Output', + padding: 1 + }); + + const outputText = new TextRenderable('output-text', { + content: 'JavaScript REPL\nType JavaScript code and press Enter to execute\n\n', + fg: '#ffffff', + flexGrow: 1 + }); + + outputContainer.add(outputText); + root.add(outputContainer); + + // Create an input field + const inputContainer = new BoxRenderable('input-container', { + width: '100%', + height: '10%', + borderStyle: 'single', + borderColor: '#2ecc71', + backgroundColor: '#222222', + title: 'Input', + padding: 1, + y: '90%' + }); + + const inputField = new InputRenderable('input-field', { + placeholder: 'Enter JavaScript code...', + fg: '#ffffff', + flexGrow: 1 + }); + + inputContainer.add(inputField); + root.add(inputContainer); + + // Focus the input field + inputField.focus(); + + // Create a context for the REPL + const context = { + console: { + log: (...args) => { + outputText.content += args.map(arg => String(arg)).join(' ') + '\n'; + }, + error: (...args) => { + outputText.content += '\x1b[31m' + args.map(arg => String(arg)).join(' ') + '\x1b[0m\n'; + }, + warn: (...args) => { + outputText.content += '\x1b[33m' + args.map(arg => String(arg)).join(' ') + '\x1b[0m\n'; + }, + info: (...args) => { + outputText.content += '\x1b[36m' + args.map(arg => String(arg)).join(' ') + '\x1b[0m\n'; + } + } + }; + + // Handle input submission + inputField.on('submit', (value) => { + // Add the input to the output + outputText.content += `> ${value}\n`; + + // Clear the input field + inputField.setValue(''); + + // Execute the code + try { + const result = runInNewContext(value, context); + + // Display the result + if (result !== undefined) { + outputText.content += `${result}\n`; + } + } catch (error) { + // Display the error + outputText.content += `\x1b[31mError: ${error.message}\x1b[0m\n`; + } + + // Add a blank line + outputText.content += '\n'; + }); + + // Handle keyboard input + renderer.on('key', (key) => { + const keyStr = key.toString(); + + if (keyStr === '\u0003') { // Ctrl+C + renderer.destroy(); + process.exit(0); + } + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the REPL demo +createReplDemo().catch(console.error); +``` diff --git a/packages/core/docs/api/utils/utilities.md b/packages/core/docs/api/utils/utilities.md new file mode 100644 index 000000000..70f399748 --- /dev/null +++ b/packages/core/docs/api/utils/utilities.md @@ -0,0 +1,733 @@ +# Utilities API + +OpenTUI provides a variety of utility functions and classes to help with common tasks when building terminal user interfaces. + +## Color Utilities + +### RGBA Class + +The `RGBA` class represents a color with red, green, blue, and alpha components. + +```typescript +import { RGBA } from '@opentui/core'; + +// Create a color from RGBA values (0-1 range) +const red = RGBA.fromValues(1, 0, 0, 1); + +// Create a color from hex string +const blue = RGBA.fromHex('#0000ff'); + +// Create a color from RGB values (0-255 range) +const green = RGBA.fromRGB(0, 255, 0); + +// Create a color from RGBA values (0-255 range) +const purple = RGBA.fromRGBA(128, 0, 128, 255); + +// Get color components +const r = red.r; // 1 +const g = red.g; // 0 +const b = red.b; // 0 +const a = red.a; // 1 + +// Convert to integer components (0-255) +const ints = red.toInts(); // [255, 0, 0, 255] + +// Convert to hex string +const hex = red.toHex(); // '#ff0000' + +// Check if two colors are equal +const isEqual = red.equals(RGBA.fromHex('#ff0000')); // true + +// Create a new color with modified alpha +const transparentRed = red.withAlpha(0.5); +``` + +### parseColor Function + +The `parseColor` function converts various color formats to an `RGBA` object. + +```typescript +import { parseColor, RGBA } from '@opentui/core'; + +// Parse a hex string +const red = parseColor('#ff0000'); + +// Parse an RGB string +const green = parseColor('rgb(0, 255, 0)'); + +// Parse an RGBA string +const blue = parseColor('rgba(0, 0, 255, 0.5)'); + +// Pass through an existing RGBA object +const existing = parseColor(RGBA.fromHex('#ff00ff')); + +// Parse a named color +const black = parseColor('black'); + +// Parse 'transparent' +const transparent = parseColor('transparent'); // RGBA with 0 alpha +``` + +### Example: Creating a Color Palette + +```typescript +import { BoxRenderable, TextRenderable, RGBA } from '@opentui/core'; + +// Create a color palette component +class ColorPalette extends BoxRenderable { + constructor(id: string, options = {}) { + super(id, { + width: 40, + height: 20, + borderStyle: 'single', + borderColor: '#ffffff', + padding: 1, + ...options + }); + + // Define colors + const colors = [ + { name: 'Red', hex: '#e74c3c' }, + { name: 'Orange', hex: '#e67e22' }, + { name: 'Yellow', hex: '#f1c40f' }, + { name: 'Green', hex: '#2ecc71' }, + { name: 'Blue', hex: '#3498db' }, + { name: 'Purple', hex: '#9b59b6' }, + { name: 'Gray', hex: '#95a5a6' }, + { name: 'Black', hex: '#000000' }, + { name: 'White', hex: '#ffffff' } + ]; + + // Create color swatches + for (let i = 0; i < colors.length; i++) { + const color = colors[i]; + + // Create a swatch container + const swatch = new BoxRenderable(`swatch-${i}`, { + width: '100%', + height: 2, + marginBottom: 1, + flexDirection: 'row', + border: false + }); + + // Create a color box + const colorBox = new BoxRenderable(`color-${i}`, { + width: 4, + height: 2, + marginRight: 1, + borderStyle: 'single', + borderColor: '#ffffff', + backgroundColor: color.hex + }); + + // Create a label + const label = new TextRenderable(`label-${i}`, { + content: `${color.name} (${color.hex})`, + fg: '#ffffff' + }); + + // Add to swatch + swatch.add(colorBox); + swatch.add(label); + + // Add to palette + this.add(swatch); + } + } +} + +// Usage +const palette = new ColorPalette('palette'); +root.add(palette); +``` + +## ANSI Terminal Utilities + +The `ANSI` class provides utilities for working with ANSI escape sequences. + +```typescript +import { ANSI } from '@opentui/core'; + +// Cursor movement +const moveCursor = ANSI.moveCursor(10, 5); // Move to row 10, column 5 +const moveUp = ANSI.moveUp(2); // Move up 2 lines +const moveDown = ANSI.moveDown(3); // Move down 3 lines +const moveLeft = ANSI.moveLeft(4); // Move left 4 columns +const moveRight = ANSI.moveRight(5); // Move right 5 columns + +// Cursor visibility +const showCursor = ANSI.showCursor; +const hideCursor = ANSI.hideCursor; + +// Screen control +const clearScreen = ANSI.clearScreen; +const clearLine = ANSI.clearLine; +const clearToEndOfLine = ANSI.clearToEndOfLine; +const clearToStartOfLine = ANSI.clearToStartOfLine; + +// Terminal modes +const alternateScreen = ANSI.switchToAlternateScreen; +const mainScreen = ANSI.switchToMainScreen; + +// Colors +const setForeground = ANSI.setRgbForeground(255, 0, 0); // Red text +const setBackground = ANSI.setRgbBackground(0, 0, 255); // Blue background +const resetColors = ANSI.resetColors; + +// Text styles +const bold = ANSI.bold; +const dim = ANSI.dim; +const italic = ANSI.italic; +const underline = ANSI.underline; +const blink = ANSI.blink; +const inverse = ANSI.inverse; +const hidden = ANSI.hidden; +const strikethrough = ANSI.strikethrough; +const resetStyles = ANSI.resetStyles; + +// Mouse support +const enableMouse = ANSI.enableMouseTracking; +const disableMouse = ANSI.disableMouseTracking; +``` + +## Buffer Utilities + +### OptimizedBuffer + +The `OptimizedBuffer` class provides a high-performance buffer for terminal rendering. The public API uses these primary operations (many are FFI-backed for performance). + +```typescript +import { OptimizedBuffer, RGBA } from '@opentui/core'; + +// Create a buffer +const buffer = OptimizedBuffer.create(80, 24); + +// Set a cell (x, y, char, fg, bg, attributes) +buffer.setCell(10, 5, 'A', RGBA.fromHex('#ffffff'), RGBA.fromHex('#000000'), 0); + +// Read a cell (returns { char, fg, bg, attributes } or null) +const cell = buffer.get(10, 5); +if (cell) { + const charCode = cell.char; + const fg = cell.fg; + const bg = cell.bg; + const attrs = cell.attributes; +} + +// Alpha-aware single-cell write +buffer.setCellWithAlphaBlending(5, 5, 'A', RGBA.fromHex('#ffffff'), RGBA.fromValues(0,0,0,0.5), 0); + +// Draw text +// Signature: drawText(text, x, y, fg, bg?, attributes?, selection?) +buffer.drawText('Hello, world!', 0, 0, RGBA.fromHex('#ffffff')); + +// If you have a TextBuffer (styled/rich text), render it with drawTextBuffer +buffer.drawTextBuffer(textBuffer, 2, 2, /*clipRect?*/ undefined); + +// Fill a rectangle area (alpha-aware) +buffer.fillRect(0, 0, 10, 3, RGBA.fromHex('#222222')); + +// Draw a bordered box (uses border chars + title) +buffer.drawBox({ + x: 5, + y: 5, + width: 20, + height: 10, + border: true, + borderStyle: 'single', + borderColor: RGBA.fromHex('#ffffff'), + backgroundColor: RGBA.fromHex('#000000'), + title: 'My Box', + titleAlignment: 'center' +}); + +// Compose / copy another buffer (FFI-preferred) +buffer.drawFrameBuffer(10, 5, otherBuffer); + +// Packed / supersample helpers (for advanced use) +buffer.drawPackedBuffer(dataPtr, dataLen, posX, posY, terminalWidthCells, terminalHeightCells); +buffer.drawSuperSampleBuffer(x, y, pixelDataPtr, pixelDataLength, 'rgba8unorm', alignedBytesPerRow); + +// Clear, resize, destroy +buffer.clear(RGBA.fromHex('#000000')); +buffer.resize(100, 30); +buffer.destroy(); +``` + +- Many drawing operations are implemented in native code and invoked via FFI (the public methods call into FFI where available). Use the high-level public methods above rather than relying on non-existent convenience shims. +- If you need line/rectangle helpers, implement them using `drawText`/`setCell`/`fillRect` or add a helper in your application code. + +### TextBuffer + +The `TextBuffer` class provides specialized buffer for text rendering. + +```typescript +import { TextBuffer, RGBA } from '@opentui/core'; + +// Create a text buffer +const buffer = TextBuffer.create(100); // Initial capacity + +// Set default styles +buffer.setDefaultFg(RGBA.fromHex('#ffffff')); +buffer.setDefaultBg(RGBA.fromHex('#000000')); +buffer.setDefaultAttributes(0x01); // Bold + +// Add text +buffer.addText(0, 0, 'Hello, world!'); + +// Add styled text +buffer.addText(0, 1, 'Colored text', RGBA.fromHex('#ff0000')); + +// Set styled text +buffer.setStyledText(styledTextObject); + +// Get line information +const lineInfo = buffer.lineInfo; +console.log(`Line starts: ${lineInfo.lineStarts}`); +console.log(`Line widths: ${lineInfo.lineWidths}`); + +// Handle selection +buffer.setSelection(5, 10, RGBA.fromHex('#3498db'), RGBA.fromHex('#ffffff')); +buffer.resetSelection(); + +// Clear the buffer +buffer.clear(); + +// Clean up +buffer.destroy(); +``` + +## Layout Utilities + +### TrackedNode + +The `TrackedNode` class provides a wrapper around Yoga layout nodes with additional tracking. + +```typescript +import { TrackedNode, createTrackedNode } from '@opentui/core'; +import Yoga from 'yoga-layout'; + +// Create a tracked node +const node = createTrackedNode(); + +// Create a tracked node with custom data +const nodeWithData = createTrackedNode({ myData: 'value' }); + +// Create a tracked node with custom Yoga config +const config = Yoga.Config.create(); +const nodeWithConfig = createTrackedNode({}, config); + +// Access the Yoga node +const yogaNode = node.yogaNode; + +// Set width and height +node.setWidth(100); +node.setHeight(50); + +// Set width and height with percentage +node.setWidth('50%'); +node.setHeight('25%'); + +// Add a child node +const child = createTrackedNode(); +const childIndex = node.addChild(child); + +// Insert a child at a specific index +const anotherChild = createTrackedNode(); +const insertedIndex = node.insertChild(anotherChild, 0); + +// Remove a child +node.removeChild(child); + +// Clean up +node.destroy(); +``` + +### Yoga Options Utilities + +OpenTUI provides utilities for working with Yoga layout options. + +```typescript +import { + parseFlexDirection, + parseAlign, + parseJustify, + parsePositionType +} from '@opentui/core'; +import { FlexDirection, Align, Justify, PositionType } from 'yoga-layout'; + +// Parse flex direction +const flexDirection = parseFlexDirection('row'); // FlexDirection.Row +const flexDirectionReverse = parseFlexDirection('row-reverse'); // FlexDirection.RowReverse + +// Parse align +const align = parseAlign('center'); // Align.Center +const alignStart = parseAlign('flex-start'); // Align.FlexStart + +// Parse justify +const justify = parseJustify('space-between'); // Justify.SpaceBetween +const justifyCenter = parseJustify('center'); // Justify.Center + +// Parse position type +const position = parsePositionType('absolute'); // PositionType.Absolute +const positionRelative = parsePositionType('relative'); // PositionType.Relative +``` + +## Border Utilities + +OpenTUI provides utilities for working with borders. + +```typescript +import { getBorderSides, borderCharsToArray } from '@opentui/core'; + +// Get border sides configuration +const allSides = getBorderSides(true); // All sides +const topBottom = getBorderSides(['top', 'bottom']); // Only top and bottom +const none = getBorderSides(false); // No sides + +// Convert border characters to array +const borderChars = { + topLeft: '╭', + topRight: '╮', + bottomLeft: '╰', + bottomRight: '╯', + horizontal: '─', + vertical: '│', + left: '├', + right: '┤', + top: '┬', + bottom: '┴', + middle: '┼' +}; + +const borderArray = borderCharsToArray(borderChars); +``` + +## Console Utilities + +OpenTUI provides a terminal console for debugging and logging. + +```typescript +import { createCliRenderer } from '@opentui/core'; + +// Create a renderer +const renderer = await createCliRenderer({ + useConsole: true, // Enable console + consoleOptions: { + maxLines: 100, + showTimestamps: true + } +}); + +// Access the console +const console = renderer.console; + +// Log messages +console.log('Info message'); +console.warn('Warning message'); +console.error('Error message'); +console.debug('Debug message'); + +// Clear the console +console.clear(); + +// Get cached logs +const logs = console.getCachedLogs(); + +// Enable/disable the console +renderer.useConsole = false; // Disable +renderer.useConsole = true; // Enable + +// Activate/deactivate the console +console.activate(); +console.deactivate(); +``` + +## Output Capture + +OpenTUI provides utilities for capturing stdout and stderr output. + +```typescript +import { capture } from '@opentui/core'; + +// Start capturing output +capture.start(); + +// Write to the capture buffer +capture.write('stdout', 'This is captured stdout'); +capture.write('stderr', 'This is captured stderr'); + +// Get the captured output +const output = capture.getOutput(); +console.log(output); // All captured output + +// Claim the output (get and clear) +const claimed = capture.claimOutput(); +console.log(claimed); // All captured output, buffer now empty + +// Check if there's any captured output +const size = capture.size; +console.log(`Captured ${size} bytes`); + +// Listen for write events +capture.on('write', () => { + console.log('New output captured'); +}); + +// Stop capturing +capture.stop(); +``` + +## Miscellaneous Utilities + +### Parsing Keypresses + +```typescript +import { parseKeypress } from '@opentui/core'; + +// Parse a keypress +const key = parseKeypress('\x1b[A'); // Up arrow key +console.log(key); +// { +// name: 'up', +// sequence: '\x1b[A', +// ctrl: false, +// meta: false, +// shift: false +// } + +// Parse a control key +const ctrlC = parseKeypress('\u0003'); // Ctrl+C +console.log(ctrlC); +// { +// name: 'c', +// sequence: '\u0003', +// ctrl: true, +// meta: false, +// shift: false +// } +``` + +### Parsing Mouse Events + +```typescript +import { MouseParser } from '@opentui/core'; + +// Create a mouse parser +const mouseParser = new MouseParser(); + +// Parse a mouse event +const event = mouseParser.parseMouseEvent(Buffer.from('\x1b[M !"')); +console.log(event); +// { +// type: 'down', +// button: 0, // Left button +// x: 33, +// y: 32, +// modifiers: { shift: false, alt: false, ctrl: false } +// } + +// Reset the parser +mouseParser.reset(); +``` + +### Example: Creating a Debug Overlay + +```typescript +import { createCliRenderer, BoxRenderable, TextRenderable, RGBA } from '@opentui/core'; + +async function createDebugOverlay() { + const renderer = await createCliRenderer(); + const { root } = renderer; + + // Create a main content area + const content = new BoxRenderable('content', { + width: '100%', + height: '100%', + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222' + }); + + root.add(content); + + // Create a debug overlay + class DebugOverlay extends BoxRenderable { + private fpsText: TextRenderable; + private memoryText: TextRenderable; + private positionText: TextRenderable; + private mouseX: number = 0; + private mouseY: number = 0; + + constructor(id: string, options = {}) { + super(id, { + width: 30, + height: 10, + position: 'absolute', + x: 2, + y: 2, + borderStyle: 'single', + borderColor: '#e74c3c', + backgroundColor: RGBA.fromValues(0, 0, 0, 0.8), + padding: 1, + flexDirection: 'column', + ...options + }); + + // Create text elements + this.fpsText = new TextRenderable(`${id}-fps`, { + content: 'FPS: 0', + fg: '#ffffff', + marginBottom: 1 + }); + + this.memoryText = new TextRenderable(`${id}-memory`, { + content: 'Memory: 0 MB', + fg: '#ffffff', + marginBottom: 1 + }); + + this.positionText = new TextRenderable(`${id}-position`, { + content: 'Mouse: 0,0', + fg: '#ffffff' + }); + + // Add text elements + this.add(this.fpsText); + this.add(this.memoryText); + this.add(this.positionText); + + // Listen for mouse events + renderer.on('key', (data) => { + // Toggle overlay with F12 + if (data.toString() === '\x1b[24~') { + this.visible = !this.visible; + } + }); + } + + public updateStats(fps: number, memory: number): void { + this.fpsText.content = `FPS: ${fps}`; + this.memoryText.content = `Memory: ${(memory / (1024 * 1024)).toFixed(2)} MB`; + } + + public updateMousePosition(x: number, y: number): void { + this.mouseX = x; + this.mouseY = y; + this.positionText.content = `Mouse: ${x},${y}`; + } + } + + // Create the debug overlay + const debug = new DebugOverlay('debug'); + root.add(debug); + + // Update stats periodically + setInterval(() => { + const stats = renderer.getStats(); + const memory = process.memoryUsage().heapUsed; + debug.updateStats(stats.fps, memory); + }, 1000); + + // Track mouse position + renderer.on('mouseEvent', (event) => { + debug.updateMousePosition(event.x, event.y); + }); + + // Start the renderer + renderer.start(); + + return renderer; +} + +// Create and run the debug overlay +createDebugOverlay().catch(console.error); +``` + +## Performance Utilities + +### Benchmarking + +```typescript +import { createCliRenderer, BoxRenderable, TextRenderable } from '@opentui/core'; + +async function runBenchmark() { + const renderer = await createCliRenderer({ + gatherStats: true, // Enable stats gathering + maxStatSamples: 1000 + }); + + const { root } = renderer; + + // Create a container + const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + borderStyle: 'single', + borderColor: '#3498db', + backgroundColor: '#222222', + padding: 1, + flexDirection: 'column' + }); + + // Create a stats display + const statsText = new TextRenderable('stats', { + content: 'Running benchmark...', + fg: '#ffffff' + }); + + container.add(statsText); + root.add(container); + + // Start the renderer + renderer.start(); + + // Create test components + const testComponents = []; + for (let i = 0; i < 100; i++) { + const box = new BoxRenderable(`box-${i}`, { + width: 10, + height: 3, + position: 'absolute', + x: Math.floor(Math.random() * (renderer.width - 10)), + y: Math.floor(Math.random() * (renderer.height - 3)), + borderStyle: 'single', + borderColor: '#ffffff', + backgroundColor: '#333333' + }); + + testComponents.push(box); + container.add(box); + } + + // Run the benchmark for 5 seconds + setTimeout(() => { + // Get the stats + const stats = renderer.getStats(); + + // Update the display + statsText.content = ` +Benchmark Results: +FPS: ${stats.fps} +Average Frame Time: ${stats.averageFrameTime.toFixed(2)}ms +Min Frame Time: ${stats.minFrameTime.toFixed(2)}ms +Max Frame Time: ${stats.maxFrameTime.toFixed(2)}ms +Total Frames: ${stats.frameCount} + `; + + // Remove test components + for (const component of testComponents) { + container.remove(component.id); + } + + // Reset stats + renderer.resetStats(); + }, 5000); + + return renderer; +} + +// Run the benchmark +runBenchmark().catch(console.error); +``` From c5b555ee9ad59e7a2373bfba8efd188eab2db55a Mon Sep 17 00:00:00 2001 From: entrepeneur4lyf Date: Fri, 22 Aug 2025 03:00:01 -0400 Subject: [PATCH 3/6] Add JSON schema generation for API documentation - Created generate-api-schemas.sh script using ts-json-schema-generator - Generated 17 JSON schemas for core OpenTUI types: - Renderable options (ASCIIFont, Box, Text, Input, Select, etc.) - Core types (RenderableOptions, LayoutOptions, CliRendererConfig) - 3D/Animation types (ExplosionEffectParameters, ThreeCliRendererOptions) - Library types (BorderConfig, BoxDrawOptions) - Schemas provide accurate, type-safe API documentation - Can be used for validation, IDE support, and documentation generation --- .../docs/api/schemas/ASCIIFontOptions.json | 745 ++++++ .../docs/api/schemas/AnimationOptions.json | 108 + .../core/docs/api/schemas/BorderConfig.json | 166 ++ .../core/docs/api/schemas/BoxDrawOptions.json | 201 ++ .../core/docs/api/schemas/BoxOptions.json | 830 ++++++ .../docs/api/schemas/CliRendererConfig.json | 2332 +++++++++++++++++ .../core/docs/api/schemas/ConsoleOptions.json | 127 + .../schemas/ExplosionEffectParameters.json | 95 + .../docs/api/schemas/FrameBufferOptions.json | 631 +++++ .../api/schemas/InputRenderableOptions.json | 731 ++++++ .../core/docs/api/schemas/LayoutOptions.json | 254 ++ .../docs/api/schemas/RenderableOptions.json | 646 +++++ .../api/schemas/SelectRenderableOptions.json | 775 ++++++ .../schemas/TabSelectRenderableOptions.json | 752 ++++++ .../core/docs/api/schemas/TextOptions.json | 831 ++++++ .../api/schemas/ThreeCliRendererOptions.json | 96 + .../docs/api/schemas/TimelineOptions.json | 27 + scripts/generate-api-schemas.sh | 39 + 18 files changed, 9386 insertions(+) create mode 100644 packages/core/docs/api/schemas/ASCIIFontOptions.json create mode 100644 packages/core/docs/api/schemas/AnimationOptions.json create mode 100644 packages/core/docs/api/schemas/BorderConfig.json create mode 100644 packages/core/docs/api/schemas/BoxDrawOptions.json create mode 100644 packages/core/docs/api/schemas/BoxOptions.json create mode 100644 packages/core/docs/api/schemas/CliRendererConfig.json create mode 100644 packages/core/docs/api/schemas/ConsoleOptions.json create mode 100644 packages/core/docs/api/schemas/ExplosionEffectParameters.json create mode 100644 packages/core/docs/api/schemas/FrameBufferOptions.json create mode 100644 packages/core/docs/api/schemas/InputRenderableOptions.json create mode 100644 packages/core/docs/api/schemas/LayoutOptions.json create mode 100644 packages/core/docs/api/schemas/RenderableOptions.json create mode 100644 packages/core/docs/api/schemas/SelectRenderableOptions.json create mode 100644 packages/core/docs/api/schemas/TabSelectRenderableOptions.json create mode 100644 packages/core/docs/api/schemas/TextOptions.json create mode 100644 packages/core/docs/api/schemas/ThreeCliRendererOptions.json create mode 100644 packages/core/docs/api/schemas/TimelineOptions.json create mode 100755 scripts/generate-api-schemas.sh diff --git a/packages/core/docs/api/schemas/ASCIIFontOptions.json b/packages/core/docs/api/schemas/ASCIIFontOptions.json new file mode 100644 index 000000000..941761218 --- /dev/null +++ b/packages/core/docs/api/schemas/ASCIIFontOptions.json @@ -0,0 +1,745 @@ +{ + "$ref": "#/definitions/ASCIIFontOptions", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ASCIIFontOptions": { + "additionalProperties": false, + "properties": { + "alignItems": { + "$ref": "#/definitions/AlignString" + }, + "bg": { + "$ref": "#/definitions/RGBA" + }, + "bottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "buffered": { + "type": "boolean" + }, + "enableLayout": { + "type": "boolean" + }, + "fg": { + "anyOf": [ + { + "$ref": "#/definitions/RGBA" + }, + { + "items": { + "$ref": "#/definitions/RGBA" + }, + "type": "array" + } + ] + }, + "flexBasis": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + } + ] + }, + "flexDirection": { + "$ref": "#/definitions/FlexDirectionString" + }, + "flexGrow": { + "type": "number" + }, + "flexShrink": { + "type": "number" + }, + "font": { + "enum": [ + "tiny", + "block", + "shade", + "slick" + ], + "type": "string" + }, + "height": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "justifyContent": { + "$ref": "#/definitions/JustifyString" + }, + "left": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "live": { + "type": "boolean" + }, + "margin": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginBottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginLeft": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginRight": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginTop": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "maxHeight": { + "type": "number" + }, + "maxWidth": { + "type": "number" + }, + "minHeight": { + "type": "number" + }, + "minWidth": { + "type": "number" + }, + "onKeyDown": { + "$comment": "(key: ParsedKey) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "key": { + "$ref": "#/definitions/ParsedKey" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDown": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDrag": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDragEnd": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDrop": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseMove": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseOut": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseOver": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseScroll": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseUp": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "padding": { + "type": [ + "number", + "string" + ] + }, + "paddingBottom": { + "type": [ + "number", + "string" + ] + }, + "paddingLeft": { + "type": [ + "number", + "string" + ] + }, + "paddingRight": { + "type": [ + "number", + "string" + ] + }, + "paddingTop": { + "type": [ + "number", + "string" + ] + }, + "position": { + "$ref": "#/definitions/PositionTypeString" + }, + "right": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "selectable": { + "type": "boolean" + }, + "selectionBg": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RGBA" + } + ] + }, + "selectionFg": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RGBA" + } + ] + }, + "text": { + "type": "string" + }, + "top": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "visible": { + "type": "boolean" + }, + "width": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "zIndex": { + "type": "number" + } + }, + "type": "object" + }, + "AlignString": { + "enum": [ + "auto", + "flex-start", + "center", + "flex-end", + "stretch", + "baseline", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "FlexDirectionString": { + "enum": [ + "column", + "column-reverse", + "row", + "row-reverse" + ], + "type": "string" + }, + "JustifyString": { + "enum": [ + "flex-start", + "center", + "flex-end", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "MouseEvent": { + "additionalProperties": false, + "properties": { + "button": { + "type": "number" + }, + "modifiers": { + "additionalProperties": false, + "properties": { + "alt": { + "type": "boolean" + }, + "ctrl": { + "type": "boolean" + }, + "shift": { + "type": "boolean" + } + }, + "required": [ + "shift", + "alt", + "ctrl" + ], + "type": "object" + }, + "scroll": { + "$ref": "#/definitions/ScrollInfo" + }, + "source": { + "$ref": "#/definitions/Renderable" + }, + "target": { + "anyOf": [ + { + "$ref": "#/definitions/Renderable" + }, + { + "type": "null" + } + ] + }, + "type": { + "$ref": "#/definitions/MouseEventType" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "type", + "button", + "x", + "y", + "modifiers", + "target" + ], + "type": "object" + }, + "MouseEventType": { + "enum": [ + "down", + "up", + "move", + "drag", + "drag-end", + "drop", + "over", + "out", + "scroll" + ], + "type": "string" + }, + "ParsedKey": { + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "ctrl": { + "type": "boolean" + }, + "meta": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "number": { + "type": "boolean" + }, + "option": { + "type": "boolean" + }, + "raw": { + "type": "string" + }, + "sequence": { + "type": "string" + }, + "shift": { + "type": "boolean" + } + }, + "required": [ + "name", + "ctrl", + "meta", + "shift", + "option", + "sequence", + "number", + "raw" + ], + "type": "object" + }, + "PositionTypeString": { + "enum": [ + "static", + "relative", + "absolute" + ], + "type": "string" + }, + "RGBA": { + "additionalProperties": false, + "properties": { + "buffer": { + "additionalProperties": { + "type": "number" + }, + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "additionalProperties": false, + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "type": "object" + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "type": "object" + } + }, + "required": [ + "buffer" + ], + "type": "object" + }, + "Renderable": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "num": { + "type": "number" + }, + "parent": { + "anyOf": [ + { + "$ref": "#/definitions/Renderable" + }, + { + "type": "null" + } + ] + }, + "selectable": { + "type": "boolean" + } + }, + "required": [ + "id", + "num", + "selectable", + "parent" + ], + "type": "object" + }, + "ScrollInfo": { + "additionalProperties": false, + "properties": { + "delta": { + "type": "number" + }, + "direction": { + "enum": [ + "up", + "down", + "left", + "right" + ], + "type": "string" + } + }, + "required": [ + "direction", + "delta" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/core/docs/api/schemas/AnimationOptions.json b/packages/core/docs/api/schemas/AnimationOptions.json new file mode 100644 index 000000000..36fa7d868 --- /dev/null +++ b/packages/core/docs/api/schemas/AnimationOptions.json @@ -0,0 +1,108 @@ +{ + "$ref": "#/definitions/AnimationOptions", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AnimationOptions": { + "properties": { + "alternate": { + "type": "boolean" + }, + "duration": { + "type": "number" + }, + "ease": { + "$ref": "#/definitions/EasingFunctions" + }, + "loop": { + "type": [ + "boolean", + "number" + ] + }, + "loopDelay": { + "type": "number" + }, + "onComplete": { + "$comment": "() => void" + }, + "onLoop": { + "$comment": "() => void" + }, + "onStart": { + "$comment": "() => void" + }, + "onUpdate": { + "$comment": "(animation: JSAnimation) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "animation": { + "$ref": "#/definitions/JSAnimation" + } + }, + "required": [ + "animation" + ], + "type": "object" + } + }, + "type": "object" + }, + "once": { + "type": "boolean" + } + }, + "required": [ + "duration" + ], + "type": "object" + }, + "EasingFunctions": { + "enum": [ + "linear", + "inQuad", + "outQuad", + "inOutQuad", + "inExpo", + "outExpo", + "inOutSine", + "outBounce", + "outElastic", + "inBounce", + "inCirc", + "outCirc", + "inOutCirc", + "inBack", + "outBack", + "inOutBack" + ], + "type": "string" + }, + "JSAnimation": { + "additionalProperties": false, + "properties": { + "currentTime": { + "type": "number" + }, + "deltaTime": { + "type": "number" + }, + "progress": { + "type": "number" + }, + "targets": { + "items": {}, + "type": "array" + } + }, + "required": [ + "targets", + "deltaTime", + "progress", + "currentTime" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/core/docs/api/schemas/BorderConfig.json b/packages/core/docs/api/schemas/BorderConfig.json new file mode 100644 index 000000000..4c197fb02 --- /dev/null +++ b/packages/core/docs/api/schemas/BorderConfig.json @@ -0,0 +1,166 @@ +{ + "$ref": "#/definitions/BorderConfig", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "BorderCharacters": { + "additionalProperties": false, + "properties": { + "bottomLeft": { + "type": "string" + }, + "bottomRight": { + "type": "string" + }, + "bottomT": { + "type": "string" + }, + "cross": { + "type": "string" + }, + "horizontal": { + "type": "string" + }, + "leftT": { + "type": "string" + }, + "rightT": { + "type": "string" + }, + "topLeft": { + "type": "string" + }, + "topRight": { + "type": "string" + }, + "topT": { + "type": "string" + }, + "vertical": { + "type": "string" + } + }, + "required": [ + "topLeft", + "topRight", + "bottomLeft", + "bottomRight", + "horizontal", + "vertical", + "topT", + "bottomT", + "leftT", + "rightT", + "cross" + ], + "type": "object" + }, + "BorderConfig": { + "additionalProperties": false, + "properties": { + "border": { + "anyOf": [ + { + "type": "boolean" + }, + { + "items": { + "$ref": "#/definitions/BorderSides" + }, + "type": "array" + } + ] + }, + "borderColor": { + "$ref": "#/definitions/ColorInput" + }, + "borderStyle": { + "$ref": "#/definitions/BorderStyle" + }, + "customBorderChars": { + "$ref": "#/definitions/BorderCharacters" + } + }, + "required": [ + "borderStyle", + "border" + ], + "type": "object" + }, + "BorderSides": { + "enum": [ + "top", + "right", + "bottom", + "left" + ], + "type": "string" + }, + "BorderStyle": { + "enum": [ + "single", + "double", + "rounded", + "heavy" + ], + "type": "string" + }, + "ColorInput": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RGBA" + } + ] + }, + "RGBA": { + "additionalProperties": false, + "properties": { + "buffer": { + "additionalProperties": { + "type": "number" + }, + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "additionalProperties": false, + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "type": "object" + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "type": "object" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/core/docs/api/schemas/BoxDrawOptions.json b/packages/core/docs/api/schemas/BoxDrawOptions.json new file mode 100644 index 000000000..24fdf2182 --- /dev/null +++ b/packages/core/docs/api/schemas/BoxDrawOptions.json @@ -0,0 +1,201 @@ +{ + "$ref": "#/definitions/BoxDrawOptions", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "BorderCharacters": { + "additionalProperties": false, + "properties": { + "bottomLeft": { + "type": "string" + }, + "bottomRight": { + "type": "string" + }, + "bottomT": { + "type": "string" + }, + "cross": { + "type": "string" + }, + "horizontal": { + "type": "string" + }, + "leftT": { + "type": "string" + }, + "rightT": { + "type": "string" + }, + "topLeft": { + "type": "string" + }, + "topRight": { + "type": "string" + }, + "topT": { + "type": "string" + }, + "vertical": { + "type": "string" + } + }, + "required": [ + "topLeft", + "topRight", + "bottomLeft", + "bottomRight", + "horizontal", + "vertical", + "topT", + "bottomT", + "leftT", + "rightT", + "cross" + ], + "type": "object" + }, + "BorderSides": { + "enum": [ + "top", + "right", + "bottom", + "left" + ], + "type": "string" + }, + "BorderStyle": { + "enum": [ + "single", + "double", + "rounded", + "heavy" + ], + "type": "string" + }, + "BoxDrawOptions": { + "additionalProperties": false, + "properties": { + "backgroundColor": { + "$ref": "#/definitions/ColorInput" + }, + "border": { + "anyOf": [ + { + "type": "boolean" + }, + { + "items": { + "$ref": "#/definitions/BorderSides" + }, + "type": "array" + } + ] + }, + "borderColor": { + "$ref": "#/definitions/ColorInput" + }, + "borderStyle": { + "$ref": "#/definitions/BorderStyle" + }, + "customBorderChars": { + "$ref": "#/definitions/BorderCharacters" + }, + "height": { + "type": "number" + }, + "shouldFill": { + "type": "boolean" + }, + "title": { + "type": "string" + }, + "titleAlignment": { + "enum": [ + "left", + "center", + "right" + ], + "type": "string" + }, + "width": { + "type": "number" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "x", + "y", + "width", + "height", + "borderStyle", + "border", + "borderColor", + "backgroundColor" + ], + "type": "object" + }, + "ColorInput": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RGBA" + } + ] + }, + "RGBA": { + "additionalProperties": false, + "properties": { + "buffer": { + "additionalProperties": { + "type": "number" + }, + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "additionalProperties": false, + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "type": "object" + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "type": "object" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/core/docs/api/schemas/BoxOptions.json b/packages/core/docs/api/schemas/BoxOptions.json new file mode 100644 index 000000000..097214281 --- /dev/null +++ b/packages/core/docs/api/schemas/BoxOptions.json @@ -0,0 +1,830 @@ +{ + "$ref": "#/definitions/BoxOptions", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AlignString": { + "enum": [ + "auto", + "flex-start", + "center", + "flex-end", + "stretch", + "baseline", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "BorderCharacters": { + "additionalProperties": false, + "properties": { + "bottomLeft": { + "type": "string" + }, + "bottomRight": { + "type": "string" + }, + "bottomT": { + "type": "string" + }, + "cross": { + "type": "string" + }, + "horizontal": { + "type": "string" + }, + "leftT": { + "type": "string" + }, + "rightT": { + "type": "string" + }, + "topLeft": { + "type": "string" + }, + "topRight": { + "type": "string" + }, + "topT": { + "type": "string" + }, + "vertical": { + "type": "string" + } + }, + "required": [ + "topLeft", + "topRight", + "bottomLeft", + "bottomRight", + "horizontal", + "vertical", + "topT", + "bottomT", + "leftT", + "rightT", + "cross" + ], + "type": "object" + }, + "BorderSides": { + "enum": [ + "top", + "right", + "bottom", + "left" + ], + "type": "string" + }, + "BorderStyle": { + "enum": [ + "single", + "double", + "rounded", + "heavy" + ], + "type": "string" + }, + "BoxOptions": { + "additionalProperties": false, + "properties": { + "alignItems": { + "$ref": "#/definitions/AlignString" + }, + "backgroundColor": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RGBA" + } + ] + }, + "border": { + "anyOf": [ + { + "type": "boolean" + }, + { + "items": { + "$ref": "#/definitions/BorderSides" + }, + "type": "array" + } + ] + }, + "borderColor": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RGBA" + } + ] + }, + "borderStyle": { + "$ref": "#/definitions/BorderStyle" + }, + "bottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "buffered": { + "type": "boolean" + }, + "customBorderChars": { + "$ref": "#/definitions/BorderCharacters" + }, + "enableLayout": { + "type": "boolean" + }, + "flexBasis": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + } + ] + }, + "flexDirection": { + "$ref": "#/definitions/FlexDirectionString" + }, + "flexGrow": { + "type": "number" + }, + "flexShrink": { + "type": "number" + }, + "focusedBorderColor": { + "$ref": "#/definitions/ColorInput" + }, + "height": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "justifyContent": { + "$ref": "#/definitions/JustifyString" + }, + "left": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "live": { + "type": "boolean" + }, + "margin": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginBottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginLeft": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginRight": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginTop": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "maxHeight": { + "type": "number" + }, + "maxWidth": { + "type": "number" + }, + "minHeight": { + "type": "number" + }, + "minWidth": { + "type": "number" + }, + "onKeyDown": { + "$comment": "(key: ParsedKey) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "key": { + "$ref": "#/definitions/ParsedKey" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDown": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDrag": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDragEnd": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDrop": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseMove": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseOut": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseOver": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseScroll": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseUp": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "padding": { + "type": [ + "number", + "string" + ] + }, + "paddingBottom": { + "type": [ + "number", + "string" + ] + }, + "paddingLeft": { + "type": [ + "number", + "string" + ] + }, + "paddingRight": { + "type": [ + "number", + "string" + ] + }, + "paddingTop": { + "type": [ + "number", + "string" + ] + }, + "position": { + "$ref": "#/definitions/PositionTypeString" + }, + "right": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "shouldFill": { + "type": "boolean" + }, + "title": { + "type": "string" + }, + "titleAlignment": { + "enum": [ + "left", + "center", + "right" + ], + "type": "string" + }, + "top": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "visible": { + "type": "boolean" + }, + "width": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "zIndex": { + "type": "number" + } + }, + "type": "object" + }, + "ColorInput": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RGBA" + } + ] + }, + "FlexDirectionString": { + "enum": [ + "column", + "column-reverse", + "row", + "row-reverse" + ], + "type": "string" + }, + "JustifyString": { + "enum": [ + "flex-start", + "center", + "flex-end", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "MouseEvent": { + "additionalProperties": false, + "properties": { + "button": { + "type": "number" + }, + "modifiers": { + "additionalProperties": false, + "properties": { + "alt": { + "type": "boolean" + }, + "ctrl": { + "type": "boolean" + }, + "shift": { + "type": "boolean" + } + }, + "required": [ + "shift", + "alt", + "ctrl" + ], + "type": "object" + }, + "scroll": { + "$ref": "#/definitions/ScrollInfo" + }, + "source": { + "$ref": "#/definitions/Renderable" + }, + "target": { + "anyOf": [ + { + "$ref": "#/definitions/Renderable" + }, + { + "type": "null" + } + ] + }, + "type": { + "$ref": "#/definitions/MouseEventType" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "type", + "button", + "x", + "y", + "modifiers", + "target" + ], + "type": "object" + }, + "MouseEventType": { + "enum": [ + "down", + "up", + "move", + "drag", + "drag-end", + "drop", + "over", + "out", + "scroll" + ], + "type": "string" + }, + "ParsedKey": { + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "ctrl": { + "type": "boolean" + }, + "meta": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "number": { + "type": "boolean" + }, + "option": { + "type": "boolean" + }, + "raw": { + "type": "string" + }, + "sequence": { + "type": "string" + }, + "shift": { + "type": "boolean" + } + }, + "required": [ + "name", + "ctrl", + "meta", + "shift", + "option", + "sequence", + "number", + "raw" + ], + "type": "object" + }, + "PositionTypeString": { + "enum": [ + "static", + "relative", + "absolute" + ], + "type": "string" + }, + "RGBA": { + "additionalProperties": false, + "properties": { + "buffer": { + "additionalProperties": { + "type": "number" + }, + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "additionalProperties": false, + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "type": "object" + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "type": "object" + } + }, + "required": [ + "buffer" + ], + "type": "object" + }, + "Renderable": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "num": { + "type": "number" + }, + "parent": { + "anyOf": [ + { + "$ref": "#/definitions/Renderable" + }, + { + "type": "null" + } + ] + }, + "selectable": { + "type": "boolean" + } + }, + "required": [ + "id", + "num", + "selectable", + "parent" + ], + "type": "object" + }, + "ScrollInfo": { + "additionalProperties": false, + "properties": { + "delta": { + "type": "number" + }, + "direction": { + "enum": [ + "up", + "down", + "left", + "right" + ], + "type": "string" + } + }, + "required": [ + "direction", + "delta" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/core/docs/api/schemas/CliRendererConfig.json b/packages/core/docs/api/schemas/CliRendererConfig.json new file mode 100644 index 000000000..56aebbe43 --- /dev/null +++ b/packages/core/docs/api/schemas/CliRendererConfig.json @@ -0,0 +1,2332 @@ +{ + "$ref": "#/definitions/CliRendererConfig", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "CliRendererConfig": { + "additionalProperties": false, + "properties": { + "consoleOptions": { + "$ref": "#/definitions/ConsoleOptions" + }, + "debounceDelay": { + "type": "number" + }, + "enableMouseMovement": { + "type": "boolean" + }, + "exitOnCtrlC": { + "type": "boolean" + }, + "experimental_splitHeight": { + "type": "number" + }, + "gatherStats": { + "type": "boolean" + }, + "maxStatSamples": { + "type": "number" + }, + "memorySnapshotInterval": { + "type": "number" + }, + "postProcessFns": { + "items": { + "$comment": "(buffer: OptimizedBuffer, deltaTime: number) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/OptimizedBuffer" + }, + "deltaTime": { + "type": "number" + } + }, + "required": [ + "buffer", + "deltaTime" + ], + "type": "object" + } + }, + "type": "object" + }, + "type": "array" + }, + "stdin": { + "$ref": "#/definitions/global.NodeJS.ReadStream" + }, + "stdout": { + "$ref": "#/definitions/global.NodeJS.WriteStream" + }, + "targetFps": { + "type": "number" + }, + "useAlternateScreen": { + "type": "boolean" + }, + "useConsole": { + "type": "boolean" + }, + "useMouse": { + "type": "boolean" + }, + "useThread": { + "type": "boolean" + } + }, + "type": "object" + }, + "ColorInput": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RGBA" + } + ] + }, + "ConsoleOptions": { + "additionalProperties": false, + "properties": { + "backgroundColor": { + "$ref": "#/definitions/ColorInput" + }, + "colorDebug": { + "$ref": "#/definitions/ColorInput" + }, + "colorDefault": { + "$ref": "#/definitions/ColorInput" + }, + "colorError": { + "$ref": "#/definitions/ColorInput" + }, + "colorInfo": { + "$ref": "#/definitions/ColorInput" + }, + "colorWarn": { + "$ref": "#/definitions/ColorInput" + }, + "cursorColor": { + "$ref": "#/definitions/ColorInput" + }, + "maxDisplayLines": { + "type": "number" + }, + "maxStoredLogs": { + "type": "number" + }, + "position": { + "$ref": "#/definitions/ConsolePosition" + }, + "sizePercent": { + "type": "number" + }, + "startInDebugMode": { + "type": "boolean" + }, + "title": { + "type": "string" + }, + "titleBarColor": { + "$ref": "#/definitions/ColorInput" + }, + "titleBarTextColor": { + "$ref": "#/definitions/ColorInput" + }, + "zIndex": { + "type": "number" + } + }, + "type": "object" + }, + "ConsolePosition": { + "enum": [ + "top", + "bottom", + "left", + "right" + ], + "type": "string" + }, + "CursorStyle": { + "enum": [ + "block", + "line", + "underline" + ], + "type": "string" + }, + "DebugOverlayCorner": { + "enum": [ + 0, + 1, + 2, + 3 + ], + "type": "number" + }, + "OptimizedBuffer": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "lib": { + "$ref": "#/definitions/RenderLib" + }, + "respectAlpha": { + "type": "boolean" + } + }, + "required": [ + "id", + "lib", + "respectAlpha" + ], + "type": "object" + }, + "Pointer": { + "type": "number" + }, + "RGBA": { + "additionalProperties": false, + "properties": { + "buffer": { + "additionalProperties": { + "type": "number" + }, + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "additionalProperties": false, + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "type": "object" + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "type": "object" + } + }, + "required": [ + "buffer" + ], + "type": "object" + }, + "RenderLib": { + "additionalProperties": false, + "properties": { + "addToHitGrid": { + "$comment": "(renderer: Pointer, x: number, y: number, width: number, height: number, id: number) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "height": { + "type": "number" + }, + "id": { + "type": "number" + }, + "renderer": { + "$ref": "#/definitions/Pointer" + }, + "width": { + "type": "number" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "renderer", + "x", + "y", + "width", + "height", + "id" + ], + "type": "object" + } + }, + "type": "object" + }, + "bufferClear": { + "$comment": "(buffer: Pointer, color: RGBA) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + }, + "color": { + "$ref": "#/definitions/RGBA" + } + }, + "required": [ + "buffer", + "color" + ], + "type": "object" + } + }, + "type": "object" + }, + "bufferDrawBox": { + "$comment": "(\n buffer: Pointer,\n x: number,\n y: number,\n width: number,\n height: number,\n borderChars: Uint32Array,\n packedOptions: number,\n borderColor: RGBA,\n backgroundColor: RGBA,\n title: string | null) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "backgroundColor": { + "$ref": "#/definitions/RGBA" + }, + "borderChars": { + "additionalProperties": { + "type": "number" + }, + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "additionalProperties": false, + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "type": "object" + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "type": "object" + }, + "borderColor": { + "$ref": "#/definitions/RGBA" + }, + "buffer": { + "$ref": "#/definitions/Pointer" + }, + "height": { + "type": "number" + }, + "packedOptions": { + "type": "number" + }, + "title": { + "type": [ + "string", + "null" + ] + }, + "width": { + "type": "number" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "buffer", + "x", + "y", + "width", + "height", + "borderChars", + "packedOptions", + "borderColor", + "backgroundColor", + "title" + ], + "type": "object" + } + }, + "type": "object" + }, + "bufferDrawPackedBuffer": { + "$comment": "(\n buffer: Pointer,\n dataPtr: Pointer,\n dataLen: number,\n posX: number,\n posY: number,\n terminalWidthCells: number,\n terminalHeightCells: number) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + }, + "dataLen": { + "type": "number" + }, + "dataPtr": { + "$ref": "#/definitions/Pointer" + }, + "posX": { + "type": "number" + }, + "posY": { + "type": "number" + }, + "terminalHeightCells": { + "type": "number" + }, + "terminalWidthCells": { + "type": "number" + } + }, + "required": [ + "buffer", + "dataPtr", + "dataLen", + "posX", + "posY", + "terminalWidthCells", + "terminalHeightCells" + ], + "type": "object" + } + }, + "type": "object" + }, + "bufferDrawSuperSampleBuffer": { + "$comment": "(\n buffer: Pointer,\n x: number,\n y: number,\n pixelDataPtr: Pointer,\n pixelDataLength: number,\n format: \"bgra8unorm\" | \"rgba8unorm\",\n alignedBytesPerRow: number) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "alignedBytesPerRow": { + "type": "number" + }, + "buffer": { + "$ref": "#/definitions/Pointer" + }, + "format": { + "enum": [ + "bgra8unorm", + "rgba8unorm" + ], + "type": "string" + }, + "pixelDataLength": { + "type": "number" + }, + "pixelDataPtr": { + "$ref": "#/definitions/Pointer" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "buffer", + "x", + "y", + "pixelDataPtr", + "pixelDataLength", + "format", + "alignedBytesPerRow" + ], + "type": "object" + } + }, + "type": "object" + }, + "bufferDrawText": { + "$comment": "(\n buffer: Pointer,\n text: string,\n x: number,\n y: number,\n color: RGBA,\n bgColor?: RGBA,\n attributes?: number) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "attributes": { + "type": "number" + }, + "bgColor": { + "$ref": "#/definitions/RGBA" + }, + "buffer": { + "$ref": "#/definitions/Pointer" + }, + "color": { + "$ref": "#/definitions/RGBA" + }, + "text": { + "type": "string" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "buffer", + "text", + "x", + "y", + "color" + ], + "type": "object" + } + }, + "type": "object" + }, + "bufferDrawTextBuffer": { + "$comment": "(\n buffer: Pointer,\n textBuffer: Pointer,\n x: number,\n y: number,\n clipRect?: { x: number; y: number; width: number; height: number }) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + }, + "clipRect": { + "additionalProperties": false, + "properties": { + "height": { + "type": "number" + }, + "width": { + "type": "number" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "x", + "y", + "width", + "height" + ], + "type": "object" + }, + "textBuffer": { + "$ref": "#/definitions/Pointer" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "buffer", + "textBuffer", + "x", + "y" + ], + "type": "object" + } + }, + "type": "object" + }, + "bufferFillRect": { + "$comment": "(buffer: Pointer, x: number, y: number, width: number, height: number, color: RGBA) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + }, + "color": { + "$ref": "#/definitions/RGBA" + }, + "height": { + "type": "number" + }, + "width": { + "type": "number" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "buffer", + "x", + "y", + "width", + "height", + "color" + ], + "type": "object" + } + }, + "type": "object" + }, + "bufferGetAttributesPtr": { + "$comment": "(buffer: Pointer) => Pointer", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "bufferGetBgPtr": { + "$comment": "(buffer: Pointer) => Pointer", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "bufferGetCharPtr": { + "$comment": "(buffer: Pointer) => Pointer", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "bufferGetFgPtr": { + "$comment": "(buffer: Pointer) => Pointer", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "bufferGetRespectAlpha": { + "$comment": "(buffer: Pointer) => boolean", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "bufferResize": { + "$comment": "(\n buffer: Pointer,\n width: number,\n height: number) => {\n char: Uint32Array\n fg: Float32Array\n bg: Float32Array\n attributes: Uint8Array\n }", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + }, + "height": { + "type": "number" + }, + "width": { + "type": "number" + } + }, + "required": [ + "buffer", + "width", + "height" + ], + "type": "object" + } + }, + "type": "object" + }, + "bufferSetCellWithAlphaBlending": { + "$comment": "(\n buffer: Pointer,\n x: number,\n y: number,\n char: string,\n color: RGBA,\n bgColor: RGBA,\n attributes?: number) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "attributes": { + "type": "number" + }, + "bgColor": { + "$ref": "#/definitions/RGBA" + }, + "buffer": { + "$ref": "#/definitions/Pointer" + }, + "char": { + "type": "string" + }, + "color": { + "$ref": "#/definitions/RGBA" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "buffer", + "x", + "y", + "char", + "color", + "bgColor" + ], + "type": "object" + } + }, + "type": "object" + }, + "bufferSetRespectAlpha": { + "$comment": "(buffer: Pointer, respectAlpha: boolean) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + }, + "respectAlpha": { + "type": "boolean" + } + }, + "required": [ + "buffer", + "respectAlpha" + ], + "type": "object" + } + }, + "type": "object" + }, + "checkHit": { + "$comment": "(renderer: Pointer, x: number, y: number) => number", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "renderer": { + "$ref": "#/definitions/Pointer" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "renderer", + "x", + "y" + ], + "type": "object" + } + }, + "type": "object" + }, + "clearTerminal": { + "$comment": "(renderer: Pointer) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "renderer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "renderer" + ], + "type": "object" + } + }, + "type": "object" + }, + "createOptimizedBuffer": { + "$comment": "(width: number, height: number, respectAlpha?: boolean) => OptimizedBuffer", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "height": { + "type": "number" + }, + "respectAlpha": { + "type": "boolean" + }, + "width": { + "type": "number" + } + }, + "required": [ + "width", + "height" + ], + "type": "object" + } + }, + "type": "object" + }, + "createRenderer": { + "$comment": "(width: number, height: number) => Pointer | null", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "height": { + "type": "number" + }, + "width": { + "type": "number" + } + }, + "required": [ + "width", + "height" + ], + "type": "object" + } + }, + "type": "object" + }, + "createTextBuffer": { + "$comment": "(capacity: number) => TextBuffer", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "capacity": { + "type": "number" + } + }, + "required": [ + "capacity" + ], + "type": "object" + } + }, + "type": "object" + }, + "destroyOptimizedBuffer": { + "$comment": "(bufferPtr: Pointer) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "bufferPtr": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "bufferPtr" + ], + "type": "object" + } + }, + "type": "object" + }, + "destroyRenderer": { + "$comment": "(renderer: Pointer, useAlternateScreen: boolean, splitHeight: number) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "renderer": { + "$ref": "#/definitions/Pointer" + }, + "splitHeight": { + "type": "number" + }, + "useAlternateScreen": { + "type": "boolean" + } + }, + "required": [ + "renderer", + "useAlternateScreen", + "splitHeight" + ], + "type": "object" + } + }, + "type": "object" + }, + "destroyTextBuffer": { + "$comment": "(buffer: Pointer) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "disableMouse": { + "$comment": "(renderer: Pointer) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "renderer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "renderer" + ], + "type": "object" + } + }, + "type": "object" + }, + "drawFrameBuffer": { + "$comment": "(\n targetBufferPtr: Pointer,\n destX: number,\n destY: number,\n bufferPtr: Pointer,\n sourceX?: number,\n sourceY?: number,\n sourceWidth?: number,\n sourceHeight?: number) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "bufferPtr": { + "$ref": "#/definitions/Pointer" + }, + "destX": { + "type": "number" + }, + "destY": { + "type": "number" + }, + "sourceHeight": { + "type": "number" + }, + "sourceWidth": { + "type": "number" + }, + "sourceX": { + "type": "number" + }, + "sourceY": { + "type": "number" + }, + "targetBufferPtr": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "targetBufferPtr", + "destX", + "destY", + "bufferPtr" + ], + "type": "object" + } + }, + "type": "object" + }, + "dumpBuffers": { + "$comment": "(renderer: Pointer, timestamp?: number) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "renderer": { + "$ref": "#/definitions/Pointer" + }, + "timestamp": { + "type": "number" + } + }, + "required": [ + "renderer" + ], + "type": "object" + } + }, + "type": "object" + }, + "dumpHitGrid": { + "$comment": "(renderer: Pointer) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "renderer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "renderer" + ], + "type": "object" + } + }, + "type": "object" + }, + "dumpStdoutBuffer": { + "$comment": "(renderer: Pointer, timestamp?: number) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "renderer": { + "$ref": "#/definitions/Pointer" + }, + "timestamp": { + "type": "number" + } + }, + "required": [ + "renderer" + ], + "type": "object" + } + }, + "type": "object" + }, + "enableMouse": { + "$comment": "(renderer: Pointer, enableMovement: boolean) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "enableMovement": { + "type": "boolean" + }, + "renderer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "renderer", + "enableMovement" + ], + "type": "object" + } + }, + "type": "object" + }, + "getBufferHeight": { + "$comment": "(buffer: Pointer) => number", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "getBufferWidth": { + "$comment": "(buffer: Pointer) => number", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "getCurrentBuffer": { + "$comment": "(renderer: Pointer) => OptimizedBuffer", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "renderer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "renderer" + ], + "type": "object" + } + }, + "type": "object" + }, + "getNextBuffer": { + "$comment": "(renderer: Pointer) => OptimizedBuffer", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "renderer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "renderer" + ], + "type": "object" + } + }, + "type": "object" + }, + "getTextBufferArrays": { + "$comment": "(\n buffer: Pointer,\n size: number) => {\n char: Uint32Array\n fg: Float32Array\n bg: Float32Array\n attributes: Uint16Array\n }", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + }, + "size": { + "type": "number" + } + }, + "required": [ + "buffer", + "size" + ], + "type": "object" + } + }, + "type": "object" + }, + "render": { + "$comment": "(renderer: Pointer, force: boolean) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "force": { + "type": "boolean" + }, + "renderer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "renderer", + "force" + ], + "type": "object" + } + }, + "type": "object" + }, + "resizeRenderer": { + "$comment": "(renderer: Pointer, width: number, height: number) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "height": { + "type": "number" + }, + "renderer": { + "$ref": "#/definitions/Pointer" + }, + "width": { + "type": "number" + } + }, + "required": [ + "renderer", + "width", + "height" + ], + "type": "object" + } + }, + "type": "object" + }, + "setBackgroundColor": { + "$comment": "(renderer: Pointer, color: RGBA) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "color": { + "$ref": "#/definitions/RGBA" + }, + "renderer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "renderer", + "color" + ], + "type": "object" + } + }, + "type": "object" + }, + "setCursorColor": { + "$comment": "(color: RGBA) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "color": { + "$ref": "#/definitions/RGBA" + } + }, + "required": [ + "color" + ], + "type": "object" + } + }, + "type": "object" + }, + "setCursorPosition": { + "$comment": "(x: number, y: number, visible: boolean) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "visible": { + "type": "boolean" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "x", + "y", + "visible" + ], + "type": "object" + } + }, + "type": "object" + }, + "setCursorStyle": { + "$comment": "(style: CursorStyle, blinking: boolean) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "blinking": { + "type": "boolean" + }, + "style": { + "$ref": "#/definitions/CursorStyle" + } + }, + "required": [ + "style", + "blinking" + ], + "type": "object" + } + }, + "type": "object" + }, + "setDebugOverlay": { + "$comment": "(renderer: Pointer, enabled: boolean, corner: DebugOverlayCorner) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "corner": { + "$ref": "#/definitions/DebugOverlayCorner" + }, + "enabled": { + "type": "boolean" + }, + "renderer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "renderer", + "enabled", + "corner" + ], + "type": "object" + } + }, + "type": "object" + }, + "setRenderOffset": { + "$comment": "(renderer: Pointer, offset: number) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "offset": { + "type": "number" + }, + "renderer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "renderer", + "offset" + ], + "type": "object" + } + }, + "type": "object" + }, + "setUseThread": { + "$comment": "(renderer: Pointer, useThread: boolean) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "renderer": { + "$ref": "#/definitions/Pointer" + }, + "useThread": { + "type": "boolean" + } + }, + "required": [ + "renderer", + "useThread" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferConcat": { + "$comment": "(buffer1: Pointer, buffer2: Pointer) => TextBuffer", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer1": { + "$ref": "#/definitions/Pointer" + }, + "buffer2": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer1", + "buffer2" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferFinalizeLineInfo": { + "$comment": "(buffer: Pointer) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferGetAttributesPtr": { + "$comment": "(buffer: Pointer) => Pointer", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferGetBgPtr": { + "$comment": "(buffer: Pointer) => Pointer", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferGetCapacity": { + "$comment": "(buffer: Pointer) => number", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferGetCharPtr": { + "$comment": "(buffer: Pointer) => Pointer", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferGetFgPtr": { + "$comment": "(buffer: Pointer) => Pointer", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferGetLength": { + "$comment": "(buffer: Pointer) => number", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferGetLineInfo": { + "$comment": "(buffer: Pointer) => { lineStarts: number[]; lineWidths: number[] }", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferReset": { + "$comment": "(buffer: Pointer) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferResetDefaults": { + "$comment": "(buffer: Pointer) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferResetSelection": { + "$comment": "(buffer: Pointer) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferResize": { + "$comment": "(\n buffer: Pointer,\n newLength: number) => {\n char: Uint32Array\n fg: Float32Array\n bg: Float32Array\n attributes: Uint16Array\n }", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + }, + "newLength": { + "type": "number" + } + }, + "required": [ + "buffer", + "newLength" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferSetCell": { + "$comment": "(\n buffer: Pointer,\n index: number,\n char: number,\n fg: Float32Array,\n bg: Float32Array,\n attr: number) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "attr": { + "type": "number" + }, + "bg": { + "additionalProperties": { + "type": "number" + }, + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "additionalProperties": false, + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "type": "object" + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "type": "object" + }, + "buffer": { + "$ref": "#/definitions/Pointer" + }, + "char": { + "type": "number" + }, + "fg": { + "additionalProperties": { + "type": "number" + }, + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "additionalProperties": false, + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "type": "object" + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "type": "object" + }, + "index": { + "type": "number" + } + }, + "required": [ + "buffer", + "index", + "char", + "fg", + "bg", + "attr" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferSetDefaultAttributes": { + "$comment": "(buffer: Pointer, attributes: number | null) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "attributes": { + "type": [ + "number", + "null" + ] + }, + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer", + "attributes" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferSetDefaultBg": { + "$comment": "(buffer: Pointer, bg: RGBA | null) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "bg": { + "anyOf": [ + { + "$ref": "#/definitions/RGBA" + }, + { + "type": "null" + } + ] + }, + "buffer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "buffer", + "bg" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferSetDefaultFg": { + "$comment": "(buffer: Pointer, fg: RGBA | null) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "buffer": { + "$ref": "#/definitions/Pointer" + }, + "fg": { + "anyOf": [ + { + "$ref": "#/definitions/RGBA" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "buffer", + "fg" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferSetSelection": { + "$comment": "(\n buffer: Pointer,\n start: number,\n end: number,\n bgColor: RGBA | null,\n fgColor: RGBA | null) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "bgColor": { + "anyOf": [ + { + "$ref": "#/definitions/RGBA" + }, + { + "type": "null" + } + ] + }, + "buffer": { + "$ref": "#/definitions/Pointer" + }, + "end": { + "type": "number" + }, + "fgColor": { + "anyOf": [ + { + "$ref": "#/definitions/RGBA" + }, + { + "type": "null" + } + ] + }, + "start": { + "type": "number" + } + }, + "required": [ + "buffer", + "start", + "end", + "bgColor", + "fgColor" + ], + "type": "object" + } + }, + "type": "object" + }, + "textBufferWriteChunk": { + "$comment": "(\n buffer: Pointer,\n textBytes: Uint8Array,\n fg: RGBA | null,\n bg: RGBA | null,\n attributes: number | null) => number", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "attributes": { + "type": [ + "number", + "null" + ] + }, + "bg": { + "anyOf": [ + { + "$ref": "#/definitions/RGBA" + }, + { + "type": "null" + } + ] + }, + "buffer": { + "$ref": "#/definitions/Pointer" + }, + "fg": { + "anyOf": [ + { + "$ref": "#/definitions/RGBA" + }, + { + "type": "null" + } + ] + }, + "textBytes": { + "additionalProperties": { + "type": "number" + }, + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "additionalProperties": false, + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "type": "object" + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "type": "object" + } + }, + "required": [ + "buffer", + "textBytes", + "fg", + "bg", + "attributes" + ], + "type": "object" + } + }, + "type": "object" + }, + "updateMemoryStats": { + "$comment": "(renderer: Pointer, heapUsed: number, heapTotal: number, arrayBuffers: number) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "arrayBuffers": { + "type": "number" + }, + "heapTotal": { + "type": "number" + }, + "heapUsed": { + "type": "number" + }, + "renderer": { + "$ref": "#/definitions/Pointer" + } + }, + "required": [ + "renderer", + "heapUsed", + "heapTotal", + "arrayBuffers" + ], + "type": "object" + } + }, + "type": "object" + }, + "updateStats": { + "$comment": "(renderer: Pointer, time: number, fps: number, frameCallbackTime: number) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "fps": { + "type": "number" + }, + "frameCallbackTime": { + "type": "number" + }, + "renderer": { + "$ref": "#/definitions/Pointer" + }, + "time": { + "type": "number" + } + }, + "required": [ + "renderer", + "time", + "fps", + "frameCallbackTime" + ], + "type": "object" + } + }, + "type": "object" + } + }, + "required": [ + "createRenderer", + "destroyRenderer", + "setUseThread", + "setBackgroundColor", + "setRenderOffset", + "updateStats", + "updateMemoryStats", + "render", + "getNextBuffer", + "getCurrentBuffer", + "createOptimizedBuffer", + "destroyOptimizedBuffer", + "drawFrameBuffer", + "getBufferWidth", + "getBufferHeight", + "bufferClear", + "bufferGetCharPtr", + "bufferGetFgPtr", + "bufferGetBgPtr", + "bufferGetAttributesPtr", + "bufferGetRespectAlpha", + "bufferSetRespectAlpha", + "bufferDrawText", + "bufferSetCellWithAlphaBlending", + "bufferFillRect", + "bufferDrawSuperSampleBuffer", + "bufferDrawPackedBuffer", + "bufferDrawBox", + "bufferResize", + "resizeRenderer", + "setCursorPosition", + "setCursorStyle", + "setCursorColor", + "setDebugOverlay", + "clearTerminal", + "addToHitGrid", + "checkHit", + "dumpHitGrid", + "dumpBuffers", + "dumpStdoutBuffer", + "enableMouse", + "disableMouse", + "createTextBuffer", + "destroyTextBuffer", + "textBufferGetCharPtr", + "textBufferGetFgPtr", + "textBufferGetBgPtr", + "textBufferGetAttributesPtr", + "textBufferGetLength", + "textBufferSetCell", + "textBufferConcat", + "textBufferResize", + "textBufferReset", + "textBufferSetSelection", + "textBufferResetSelection", + "textBufferSetDefaultFg", + "textBufferSetDefaultBg", + "textBufferSetDefaultAttributes", + "textBufferResetDefaults", + "textBufferWriteChunk", + "textBufferGetCapacity", + "textBufferFinalizeLineInfo", + "textBufferGetLineInfo", + "getTextBufferArrays", + "bufferDrawTextBuffer" + ], + "type": "object" + }, + "SocketReadyState": { + "enum": [ + "opening", + "open", + "readOnly", + "writeOnly", + "closed" + ], + "type": "string" + }, + "global.NodeJS.ReadStream": { + "additionalProperties": false, + "properties": { + "allowHalfOpen": { + "description": "If `false` then the stream will automatically end the writable side when the readable side ends. Set initially by the `allowHalfOpen` constructor option, which defaults to `true`.\n\nThis can be changed manually to change the half-open behavior of an existing `Duplex` stream instance, but must be changed before the `'end'` event is emitted.", + "type": "boolean" + }, + "autoSelectFamilyAttemptedAddresses": { + "description": "This property is only present if the family autoselection algorithm is enabled in `socket.connect(options)` and it is an array of the addresses that have been attempted.\n\nEach address is a string in the form of `$IP:$PORT`. If the connection was successful, then the last address is the one that the socket is currently connected to.", + "items": { + "type": "string" + }, + "type": "array" + }, + "bufferSize": { + "deprecated": "Since v14.6.0 - Use `writableLength` instead.", + "description": "This property shows the number of characters buffered for writing. The buffer may contain strings whose length after encoding is not yet known. So this number is only an approximation of the number of bytes in the buffer.\n\n`net.Socket` has the property that `socket.write()` always works. This is to help users get up and running quickly. The computer cannot always keep up with the amount of data that is written to a socket. The network connection simply might be too slow. Node.js will internally queue up the data written to a socket and send it out over the wire when it is possible.\n\nThe consequence of this internal buffering is that memory may grow. Users who experience large or growing `bufferSize` should attempt to \"throttle\" the data flows in their program with `socket.pause()` and `socket.resume()`.", + "type": "number" + }, + "bytesRead": { + "description": "The amount of received bytes.", + "type": "number" + }, + "bytesWritten": { + "description": "The amount of bytes sent.", + "type": "number" + }, + "connecting": { + "description": "If `true`, `socket.connect(options[, connectListener])` was called and has not yet finished. It will stay `true` until the socket becomes connected, then it is set to `false` and the `'connect'` event is emitted. Note that the `socket.connect(options[, connectListener])` callback is a listener for the `'connect'` event.", + "type": "boolean" + }, + "destroyed": { + "description": "See `writable.destroyed` for further details.", + "type": "boolean" + }, + "isRaw": { + "description": "A `boolean` that is `true` if the TTY is currently configured to operate as a raw device.\n\nThis flag is always `false` when a process starts, even if the terminal is operating in raw mode. Its value will change with subsequent calls to `setRawMode`.", + "type": "boolean" + }, + "isTTY": { + "description": "A `boolean` that is always `true` for `tty.ReadStream` instances.", + "type": "boolean" + }, + "localAddress": { + "description": "The string representation of the local IP address the remote client is connecting on. For example, in a server listening on `'0.0.0.0'`, if a client connects on `'192.168.1.1'`, the value of `socket.localAddress` would be`'192.168.1.1'`.", + "type": "string" + }, + "localFamily": { + "description": "The string representation of the local IP family. `'IPv4'` or `'IPv6'`.", + "type": "string" + }, + "localPort": { + "description": "The numeric representation of the local port. For example, `80` or `21`.", + "type": "number" + }, + "pending": { + "description": "This is `true` if the socket is not connected yet, either because `.connect()`has not yet been called or because it is still in the process of connecting (see `socket.connecting`).", + "type": "boolean" + }, + "readable": { + "type": "boolean" + }, + "readyState": { + "$ref": "#/definitions/SocketReadyState", + "description": "This property represents the state of the connection as a string.\n\n* If the stream is connecting `socket.readyState` is `opening`.\n* If the stream is readable and writable, it is `open`.\n* If the stream is readable and not writable, it is `readOnly`.\n* If the stream is not readable and writable, it is `writeOnly`." + }, + "remoteAddress": { + "description": "The string representation of the remote IP address. For example,`'74.125.127.100'` or `'2001:4860:a005::68'`. Value may be `undefined` if the socket is destroyed (for example, if the client disconnected).", + "type": "string" + }, + "remoteFamily": { + "description": "The string representation of the remote IP family. `'IPv4'` or `'IPv6'`. Value may be `undefined` if the socket is destroyed (for example, if the client disconnected).", + "type": "string" + }, + "remotePort": { + "description": "The numeric representation of the remote port. For example, `80` or `21`. Value may be `undefined` if the socket is destroyed (for example, if the client disconnected).", + "type": "number" + }, + "timeout": { + "description": "The socket timeout in milliseconds as set by `socket.setTimeout()`. It is `undefined` if a timeout has not been set.", + "type": "number" + }, + "writable": { + "type": "boolean" + } + }, + "required": [ + "allowHalfOpen", + "autoSelectFamilyAttemptedAddresses", + "bufferSize", + "bytesRead", + "bytesWritten", + "connecting", + "destroyed", + "isRaw", + "isTTY", + "pending", + "readable", + "readyState", + "writable" + ], + "type": "object" + }, + "global.NodeJS.WriteStream": { + "additionalProperties": false, + "properties": { + "allowHalfOpen": { + "description": "If `false` then the stream will automatically end the writable side when the readable side ends. Set initially by the `allowHalfOpen` constructor option, which defaults to `true`.\n\nThis can be changed manually to change the half-open behavior of an existing `Duplex` stream instance, but must be changed before the `'end'` event is emitted.", + "type": "boolean" + }, + "autoSelectFamilyAttemptedAddresses": { + "description": "This property is only present if the family autoselection algorithm is enabled in `socket.connect(options)` and it is an array of the addresses that have been attempted.\n\nEach address is a string in the form of `$IP:$PORT`. If the connection was successful, then the last address is the one that the socket is currently connected to.", + "items": { + "type": "string" + }, + "type": "array" + }, + "bufferSize": { + "deprecated": "Since v14.6.0 - Use `writableLength` instead.", + "description": "This property shows the number of characters buffered for writing. The buffer may contain strings whose length after encoding is not yet known. So this number is only an approximation of the number of bytes in the buffer.\n\n`net.Socket` has the property that `socket.write()` always works. This is to help users get up and running quickly. The computer cannot always keep up with the amount of data that is written to a socket. The network connection simply might be too slow. Node.js will internally queue up the data written to a socket and send it out over the wire when it is possible.\n\nThe consequence of this internal buffering is that memory may grow. Users who experience large or growing `bufferSize` should attempt to \"throttle\" the data flows in their program with `socket.pause()` and `socket.resume()`.", + "type": "number" + }, + "bytesRead": { + "description": "The amount of received bytes.", + "type": "number" + }, + "bytesWritten": { + "description": "The amount of bytes sent.", + "type": "number" + }, + "columns": { + "description": "A `number` specifying the number of columns the TTY currently has. This property is updated whenever the `'resize'` event is emitted.", + "type": "number" + }, + "connecting": { + "description": "If `true`, `socket.connect(options[, connectListener])` was called and has not yet finished. It will stay `true` until the socket becomes connected, then it is set to `false` and the `'connect'` event is emitted. Note that the `socket.connect(options[, connectListener])` callback is a listener for the `'connect'` event.", + "type": "boolean" + }, + "destroyed": { + "description": "See `writable.destroyed` for further details.", + "type": "boolean" + }, + "isTTY": { + "description": "A `boolean` that is always `true`.", + "type": "boolean" + }, + "localAddress": { + "description": "The string representation of the local IP address the remote client is connecting on. For example, in a server listening on `'0.0.0.0'`, if a client connects on `'192.168.1.1'`, the value of `socket.localAddress` would be`'192.168.1.1'`.", + "type": "string" + }, + "localFamily": { + "description": "The string representation of the local IP family. `'IPv4'` or `'IPv6'`.", + "type": "string" + }, + "localPort": { + "description": "The numeric representation of the local port. For example, `80` or `21`.", + "type": "number" + }, + "pending": { + "description": "This is `true` if the socket is not connected yet, either because `.connect()`has not yet been called or because it is still in the process of connecting (see `socket.connecting`).", + "type": "boolean" + }, + "readable": { + "type": "boolean" + }, + "readyState": { + "$ref": "#/definitions/SocketReadyState", + "description": "This property represents the state of the connection as a string.\n\n* If the stream is connecting `socket.readyState` is `opening`.\n* If the stream is readable and writable, it is `open`.\n* If the stream is readable and not writable, it is `readOnly`.\n* If the stream is not readable and writable, it is `writeOnly`." + }, + "remoteAddress": { + "description": "The string representation of the remote IP address. For example,`'74.125.127.100'` or `'2001:4860:a005::68'`. Value may be `undefined` if the socket is destroyed (for example, if the client disconnected).", + "type": "string" + }, + "remoteFamily": { + "description": "The string representation of the remote IP family. `'IPv4'` or `'IPv6'`. Value may be `undefined` if the socket is destroyed (for example, if the client disconnected).", + "type": "string" + }, + "remotePort": { + "description": "The numeric representation of the remote port. For example, `80` or `21`. Value may be `undefined` if the socket is destroyed (for example, if the client disconnected).", + "type": "number" + }, + "rows": { + "description": "A `number` specifying the number of rows the TTY currently has. This property is updated whenever the `'resize'` event is emitted.", + "type": "number" + }, + "timeout": { + "description": "The socket timeout in milliseconds as set by `socket.setTimeout()`. It is `undefined` if a timeout has not been set.", + "type": "number" + }, + "writable": { + "type": "boolean" + } + }, + "required": [ + "allowHalfOpen", + "autoSelectFamilyAttemptedAddresses", + "bufferSize", + "bytesRead", + "bytesWritten", + "columns", + "connecting", + "destroyed", + "isTTY", + "pending", + "readable", + "readyState", + "rows", + "writable" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/core/docs/api/schemas/ConsoleOptions.json b/packages/core/docs/api/schemas/ConsoleOptions.json new file mode 100644 index 000000000..b2e9febbf --- /dev/null +++ b/packages/core/docs/api/schemas/ConsoleOptions.json @@ -0,0 +1,127 @@ +{ + "$ref": "#/definitions/ConsoleOptions", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ColorInput": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RGBA" + } + ] + }, + "ConsoleOptions": { + "additionalProperties": false, + "properties": { + "backgroundColor": { + "$ref": "#/definitions/ColorInput" + }, + "colorDebug": { + "$ref": "#/definitions/ColorInput" + }, + "colorDefault": { + "$ref": "#/definitions/ColorInput" + }, + "colorError": { + "$ref": "#/definitions/ColorInput" + }, + "colorInfo": { + "$ref": "#/definitions/ColorInput" + }, + "colorWarn": { + "$ref": "#/definitions/ColorInput" + }, + "cursorColor": { + "$ref": "#/definitions/ColorInput" + }, + "maxDisplayLines": { + "type": "number" + }, + "maxStoredLogs": { + "type": "number" + }, + "position": { + "$ref": "#/definitions/ConsolePosition" + }, + "sizePercent": { + "type": "number" + }, + "startInDebugMode": { + "type": "boolean" + }, + "title": { + "type": "string" + }, + "titleBarColor": { + "$ref": "#/definitions/ColorInput" + }, + "titleBarTextColor": { + "$ref": "#/definitions/ColorInput" + }, + "zIndex": { + "type": "number" + } + }, + "type": "object" + }, + "ConsolePosition": { + "enum": [ + "top", + "bottom", + "left", + "right" + ], + "type": "string" + }, + "RGBA": { + "additionalProperties": false, + "properties": { + "buffer": { + "additionalProperties": { + "type": "number" + }, + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "additionalProperties": false, + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "type": "object" + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "type": "object" + } + }, + "required": [ + "buffer" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/core/docs/api/schemas/ExplosionEffectParameters.json b/packages/core/docs/api/schemas/ExplosionEffectParameters.json new file mode 100644 index 000000000..b372a6ba5 --- /dev/null +++ b/packages/core/docs/api/schemas/ExplosionEffectParameters.json @@ -0,0 +1,95 @@ +{ + "$ref": "#/definitions/ExplosionEffectParameters", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ExplosionEffectParameters": { + "additionalProperties": false, + "properties": { + "angularVelocityMax": { + "$ref": "#/definitions/Vector3" + }, + "angularVelocityMin": { + "$ref": "#/definitions/Vector3" + }, + "durationMs": { + "type": "number" + }, + "fadeOut": { + "type": "boolean" + }, + "gravity": { + "type": "number" + }, + "gravityScale": { + "type": "number" + }, + "initialVelocityYBoost": { + "type": "number" + }, + "materialFactory": { + "$comment": "() => NodeMaterial" + }, + "numCols": { + "type": "number" + }, + "numRows": { + "type": "number" + }, + "strength": { + "type": "number" + }, + "strengthVariation": { + "type": "number" + }, + "zVariationStrength": { + "type": "number" + } + }, + "required": [ + "numRows", + "numCols", + "durationMs", + "strength", + "strengthVariation", + "gravity", + "gravityScale", + "fadeOut", + "angularVelocityMin", + "angularVelocityMax", + "initialVelocityYBoost", + "zVariationStrength", + "materialFactory" + ], + "type": "object" + }, + "Vector3": { + "additionalProperties": false, + "description": "3D vector.\n\nsee {@link https://github.com/mrdoob/three.js/blob/master/src/math/Vector3.js }", + "properties": { + "isVector3": { + "const": true, + "type": "boolean" + }, + "x": { + "default": 0, + "type": "number" + }, + "y": { + "default": 0, + "type": "number" + }, + "z": { + "default": 0, + "type": "number" + } + }, + "required": [ + "x", + "y", + "z", + "isVector3" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/core/docs/api/schemas/FrameBufferOptions.json b/packages/core/docs/api/schemas/FrameBufferOptions.json new file mode 100644 index 000000000..0a9708648 --- /dev/null +++ b/packages/core/docs/api/schemas/FrameBufferOptions.json @@ -0,0 +1,631 @@ +{ + "$ref": "#/definitions/FrameBufferOptions", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AlignString": { + "enum": [ + "auto", + "flex-start", + "center", + "flex-end", + "stretch", + "baseline", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "FlexDirectionString": { + "enum": [ + "column", + "column-reverse", + "row", + "row-reverse" + ], + "type": "string" + }, + "FrameBufferOptions": { + "additionalProperties": false, + "properties": { + "alignItems": { + "$ref": "#/definitions/AlignString" + }, + "bottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "buffered": { + "type": "boolean" + }, + "enableLayout": { + "type": "boolean" + }, + "flexBasis": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + } + ] + }, + "flexDirection": { + "$ref": "#/definitions/FlexDirectionString" + }, + "flexGrow": { + "type": "number" + }, + "flexShrink": { + "type": "number" + }, + "height": { + "type": "number" + }, + "justifyContent": { + "$ref": "#/definitions/JustifyString" + }, + "left": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "live": { + "type": "boolean" + }, + "margin": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginBottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginLeft": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginRight": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginTop": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "maxHeight": { + "type": "number" + }, + "maxWidth": { + "type": "number" + }, + "minHeight": { + "type": "number" + }, + "minWidth": { + "type": "number" + }, + "onKeyDown": { + "$comment": "(key: ParsedKey) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "key": { + "$ref": "#/definitions/ParsedKey" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDown": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDrag": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDragEnd": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDrop": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseMove": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseOut": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseOver": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseScroll": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseUp": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "padding": { + "type": [ + "number", + "string" + ] + }, + "paddingBottom": { + "type": [ + "number", + "string" + ] + }, + "paddingLeft": { + "type": [ + "number", + "string" + ] + }, + "paddingRight": { + "type": [ + "number", + "string" + ] + }, + "paddingTop": { + "type": [ + "number", + "string" + ] + }, + "position": { + "$ref": "#/definitions/PositionTypeString" + }, + "respectAlpha": { + "type": "boolean" + }, + "right": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "top": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "visible": { + "type": "boolean" + }, + "width": { + "type": "number" + }, + "zIndex": { + "type": "number" + } + }, + "required": [ + "width", + "height" + ], + "type": "object" + }, + "JustifyString": { + "enum": [ + "flex-start", + "center", + "flex-end", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "MouseEvent": { + "additionalProperties": false, + "properties": { + "button": { + "type": "number" + }, + "modifiers": { + "additionalProperties": false, + "properties": { + "alt": { + "type": "boolean" + }, + "ctrl": { + "type": "boolean" + }, + "shift": { + "type": "boolean" + } + }, + "required": [ + "shift", + "alt", + "ctrl" + ], + "type": "object" + }, + "scroll": { + "$ref": "#/definitions/ScrollInfo" + }, + "source": { + "$ref": "#/definitions/Renderable" + }, + "target": { + "anyOf": [ + { + "$ref": "#/definitions/Renderable" + }, + { + "type": "null" + } + ] + }, + "type": { + "$ref": "#/definitions/MouseEventType" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "type", + "button", + "x", + "y", + "modifiers", + "target" + ], + "type": "object" + }, + "MouseEventType": { + "enum": [ + "down", + "up", + "move", + "drag", + "drag-end", + "drop", + "over", + "out", + "scroll" + ], + "type": "string" + }, + "ParsedKey": { + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "ctrl": { + "type": "boolean" + }, + "meta": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "number": { + "type": "boolean" + }, + "option": { + "type": "boolean" + }, + "raw": { + "type": "string" + }, + "sequence": { + "type": "string" + }, + "shift": { + "type": "boolean" + } + }, + "required": [ + "name", + "ctrl", + "meta", + "shift", + "option", + "sequence", + "number", + "raw" + ], + "type": "object" + }, + "PositionTypeString": { + "enum": [ + "static", + "relative", + "absolute" + ], + "type": "string" + }, + "Renderable": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "num": { + "type": "number" + }, + "parent": { + "anyOf": [ + { + "$ref": "#/definitions/Renderable" + }, + { + "type": "null" + } + ] + }, + "selectable": { + "type": "boolean" + } + }, + "required": [ + "id", + "num", + "selectable", + "parent" + ], + "type": "object" + }, + "ScrollInfo": { + "additionalProperties": false, + "properties": { + "delta": { + "type": "number" + }, + "direction": { + "enum": [ + "up", + "down", + "left", + "right" + ], + "type": "string" + } + }, + "required": [ + "direction", + "delta" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/core/docs/api/schemas/InputRenderableOptions.json b/packages/core/docs/api/schemas/InputRenderableOptions.json new file mode 100644 index 000000000..476ea3920 --- /dev/null +++ b/packages/core/docs/api/schemas/InputRenderableOptions.json @@ -0,0 +1,731 @@ +{ + "$ref": "#/definitions/InputRenderableOptions", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AlignString": { + "enum": [ + "auto", + "flex-start", + "center", + "flex-end", + "stretch", + "baseline", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "ColorInput": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RGBA" + } + ] + }, + "FlexDirectionString": { + "enum": [ + "column", + "column-reverse", + "row", + "row-reverse" + ], + "type": "string" + }, + "InputRenderableOptions": { + "additionalProperties": false, + "properties": { + "alignItems": { + "$ref": "#/definitions/AlignString" + }, + "backgroundColor": { + "$ref": "#/definitions/ColorInput" + }, + "bottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "buffered": { + "type": "boolean" + }, + "cursorColor": { + "$ref": "#/definitions/ColorInput" + }, + "enableLayout": { + "type": "boolean" + }, + "flexBasis": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + } + ] + }, + "flexDirection": { + "$ref": "#/definitions/FlexDirectionString" + }, + "flexGrow": { + "type": "number" + }, + "flexShrink": { + "type": "number" + }, + "focusedBackgroundColor": { + "$ref": "#/definitions/ColorInput" + }, + "focusedTextColor": { + "$ref": "#/definitions/ColorInput" + }, + "height": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "justifyContent": { + "$ref": "#/definitions/JustifyString" + }, + "left": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "live": { + "type": "boolean" + }, + "margin": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginBottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginLeft": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginRight": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginTop": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "maxHeight": { + "type": "number" + }, + "maxLength": { + "type": "number" + }, + "maxWidth": { + "type": "number" + }, + "minHeight": { + "type": "number" + }, + "minWidth": { + "type": "number" + }, + "onKeyDown": { + "$comment": "(key: ParsedKey) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "key": { + "$ref": "#/definitions/ParsedKey" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDown": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDrag": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDragEnd": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDrop": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseMove": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseOut": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseOver": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseScroll": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseUp": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "padding": { + "type": [ + "number", + "string" + ] + }, + "paddingBottom": { + "type": [ + "number", + "string" + ] + }, + "paddingLeft": { + "type": [ + "number", + "string" + ] + }, + "paddingRight": { + "type": [ + "number", + "string" + ] + }, + "paddingTop": { + "type": [ + "number", + "string" + ] + }, + "placeholder": { + "type": "string" + }, + "placeholderColor": { + "$ref": "#/definitions/ColorInput" + }, + "position": { + "$ref": "#/definitions/PositionTypeString" + }, + "right": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "textColor": { + "$ref": "#/definitions/ColorInput" + }, + "top": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "value": { + "type": "string" + }, + "visible": { + "type": "boolean" + }, + "width": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "zIndex": { + "type": "number" + } + }, + "type": "object" + }, + "JustifyString": { + "enum": [ + "flex-start", + "center", + "flex-end", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "MouseEvent": { + "additionalProperties": false, + "properties": { + "button": { + "type": "number" + }, + "modifiers": { + "additionalProperties": false, + "properties": { + "alt": { + "type": "boolean" + }, + "ctrl": { + "type": "boolean" + }, + "shift": { + "type": "boolean" + } + }, + "required": [ + "shift", + "alt", + "ctrl" + ], + "type": "object" + }, + "scroll": { + "$ref": "#/definitions/ScrollInfo" + }, + "source": { + "$ref": "#/definitions/Renderable" + }, + "target": { + "anyOf": [ + { + "$ref": "#/definitions/Renderable" + }, + { + "type": "null" + } + ] + }, + "type": { + "$ref": "#/definitions/MouseEventType" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "type", + "button", + "x", + "y", + "modifiers", + "target" + ], + "type": "object" + }, + "MouseEventType": { + "enum": [ + "down", + "up", + "move", + "drag", + "drag-end", + "drop", + "over", + "out", + "scroll" + ], + "type": "string" + }, + "ParsedKey": { + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "ctrl": { + "type": "boolean" + }, + "meta": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "number": { + "type": "boolean" + }, + "option": { + "type": "boolean" + }, + "raw": { + "type": "string" + }, + "sequence": { + "type": "string" + }, + "shift": { + "type": "boolean" + } + }, + "required": [ + "name", + "ctrl", + "meta", + "shift", + "option", + "sequence", + "number", + "raw" + ], + "type": "object" + }, + "PositionTypeString": { + "enum": [ + "static", + "relative", + "absolute" + ], + "type": "string" + }, + "RGBA": { + "additionalProperties": false, + "properties": { + "buffer": { + "additionalProperties": { + "type": "number" + }, + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "additionalProperties": false, + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "type": "object" + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "type": "object" + } + }, + "required": [ + "buffer" + ], + "type": "object" + }, + "Renderable": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "num": { + "type": "number" + }, + "parent": { + "anyOf": [ + { + "$ref": "#/definitions/Renderable" + }, + { + "type": "null" + } + ] + }, + "selectable": { + "type": "boolean" + } + }, + "required": [ + "id", + "num", + "selectable", + "parent" + ], + "type": "object" + }, + "ScrollInfo": { + "additionalProperties": false, + "properties": { + "delta": { + "type": "number" + }, + "direction": { + "enum": [ + "up", + "down", + "left", + "right" + ], + "type": "string" + } + }, + "required": [ + "direction", + "delta" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/core/docs/api/schemas/LayoutOptions.json b/packages/core/docs/api/schemas/LayoutOptions.json new file mode 100644 index 000000000..4aa08feba --- /dev/null +++ b/packages/core/docs/api/schemas/LayoutOptions.json @@ -0,0 +1,254 @@ +{ + "$ref": "#/definitions/LayoutOptions", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AlignString": { + "enum": [ + "auto", + "flex-start", + "center", + "flex-end", + "stretch", + "baseline", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "FlexDirectionString": { + "enum": [ + "column", + "column-reverse", + "row", + "row-reverse" + ], + "type": "string" + }, + "JustifyString": { + "enum": [ + "flex-start", + "center", + "flex-end", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "LayoutOptions": { + "additionalProperties": false, + "properties": { + "alignItems": { + "$ref": "#/definitions/AlignString" + }, + "bottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "enableLayout": { + "type": "boolean" + }, + "flexBasis": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + } + ] + }, + "flexDirection": { + "$ref": "#/definitions/FlexDirectionString" + }, + "flexGrow": { + "type": "number" + }, + "flexShrink": { + "type": "number" + }, + "justifyContent": { + "$ref": "#/definitions/JustifyString" + }, + "left": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "margin": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginBottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginLeft": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginRight": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginTop": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "maxHeight": { + "type": "number" + }, + "maxWidth": { + "type": "number" + }, + "minHeight": { + "type": "number" + }, + "minWidth": { + "type": "number" + }, + "padding": { + "type": [ + "number", + "string" + ] + }, + "paddingBottom": { + "type": [ + "number", + "string" + ] + }, + "paddingLeft": { + "type": [ + "number", + "string" + ] + }, + "paddingRight": { + "type": [ + "number", + "string" + ] + }, + "paddingTop": { + "type": [ + "number", + "string" + ] + }, + "position": { + "$ref": "#/definitions/PositionTypeString" + }, + "right": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "top": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + } + }, + "type": "object" + }, + "PositionTypeString": { + "enum": [ + "static", + "relative", + "absolute" + ], + "type": "string" + } + } +} \ No newline at end of file diff --git a/packages/core/docs/api/schemas/RenderableOptions.json b/packages/core/docs/api/schemas/RenderableOptions.json new file mode 100644 index 000000000..358670ced --- /dev/null +++ b/packages/core/docs/api/schemas/RenderableOptions.json @@ -0,0 +1,646 @@ +{ + "$ref": "#/definitions/RenderableOptions", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AlignString": { + "enum": [ + "auto", + "flex-start", + "center", + "flex-end", + "stretch", + "baseline", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "FlexDirectionString": { + "enum": [ + "column", + "column-reverse", + "row", + "row-reverse" + ], + "type": "string" + }, + "JustifyString": { + "enum": [ + "flex-start", + "center", + "flex-end", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "MouseEvent": { + "additionalProperties": false, + "properties": { + "button": { + "type": "number" + }, + "modifiers": { + "additionalProperties": false, + "properties": { + "alt": { + "type": "boolean" + }, + "ctrl": { + "type": "boolean" + }, + "shift": { + "type": "boolean" + } + }, + "required": [ + "shift", + "alt", + "ctrl" + ], + "type": "object" + }, + "scroll": { + "$ref": "#/definitions/ScrollInfo" + }, + "source": { + "$ref": "#/definitions/Renderable" + }, + "target": { + "anyOf": [ + { + "$ref": "#/definitions/Renderable" + }, + { + "type": "null" + } + ] + }, + "type": { + "$ref": "#/definitions/MouseEventType" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "type", + "button", + "x", + "y", + "modifiers", + "target" + ], + "type": "object" + }, + "MouseEventType": { + "enum": [ + "down", + "up", + "move", + "drag", + "drag-end", + "drop", + "over", + "out", + "scroll" + ], + "type": "string" + }, + "ParsedKey": { + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "ctrl": { + "type": "boolean" + }, + "meta": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "number": { + "type": "boolean" + }, + "option": { + "type": "boolean" + }, + "raw": { + "type": "string" + }, + "sequence": { + "type": "string" + }, + "shift": { + "type": "boolean" + } + }, + "required": [ + "name", + "ctrl", + "meta", + "shift", + "option", + "sequence", + "number", + "raw" + ], + "type": "object" + }, + "PositionTypeString": { + "enum": [ + "static", + "relative", + "absolute" + ], + "type": "string" + }, + "Renderable": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "num": { + "type": "number" + }, + "parent": { + "anyOf": [ + { + "$ref": "#/definitions/Renderable" + }, + { + "type": "null" + } + ] + }, + "selectable": { + "type": "boolean" + } + }, + "required": [ + "id", + "num", + "selectable", + "parent" + ], + "type": "object" + }, + "RenderableOptions": { + "additionalProperties": false, + "properties": { + "alignItems": { + "$ref": "#/definitions/AlignString" + }, + "bottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "buffered": { + "type": "boolean" + }, + "enableLayout": { + "type": "boolean" + }, + "flexBasis": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + } + ] + }, + "flexDirection": { + "$ref": "#/definitions/FlexDirectionString" + }, + "flexGrow": { + "type": "number" + }, + "flexShrink": { + "type": "number" + }, + "height": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "justifyContent": { + "$ref": "#/definitions/JustifyString" + }, + "left": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "live": { + "type": "boolean" + }, + "margin": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginBottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginLeft": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginRight": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginTop": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "maxHeight": { + "type": "number" + }, + "maxWidth": { + "type": "number" + }, + "minHeight": { + "type": "number" + }, + "minWidth": { + "type": "number" + }, + "onKeyDown": { + "$comment": "(key: ParsedKey) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "key": { + "$ref": "#/definitions/ParsedKey" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDown": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDrag": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDragEnd": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDrop": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseMove": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseOut": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseOver": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseScroll": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseUp": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "padding": { + "type": [ + "number", + "string" + ] + }, + "paddingBottom": { + "type": [ + "number", + "string" + ] + }, + "paddingLeft": { + "type": [ + "number", + "string" + ] + }, + "paddingRight": { + "type": [ + "number", + "string" + ] + }, + "paddingTop": { + "type": [ + "number", + "string" + ] + }, + "position": { + "$ref": "#/definitions/PositionTypeString" + }, + "right": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "top": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "visible": { + "type": "boolean" + }, + "width": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "zIndex": { + "type": "number" + } + }, + "type": "object" + }, + "ScrollInfo": { + "additionalProperties": false, + "properties": { + "delta": { + "type": "number" + }, + "direction": { + "enum": [ + "up", + "down", + "left", + "right" + ], + "type": "string" + } + }, + "required": [ + "direction", + "delta" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/core/docs/api/schemas/SelectRenderableOptions.json b/packages/core/docs/api/schemas/SelectRenderableOptions.json new file mode 100644 index 000000000..81f3a2ab8 --- /dev/null +++ b/packages/core/docs/api/schemas/SelectRenderableOptions.json @@ -0,0 +1,775 @@ +{ + "$ref": "#/definitions/SelectRenderableOptions", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AlignString": { + "enum": [ + "auto", + "flex-start", + "center", + "flex-end", + "stretch", + "baseline", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "ColorInput": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RGBA" + } + ] + }, + "FlexDirectionString": { + "enum": [ + "column", + "column-reverse", + "row", + "row-reverse" + ], + "type": "string" + }, + "JustifyString": { + "enum": [ + "flex-start", + "center", + "flex-end", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "MouseEvent": { + "additionalProperties": false, + "properties": { + "button": { + "type": "number" + }, + "modifiers": { + "additionalProperties": false, + "properties": { + "alt": { + "type": "boolean" + }, + "ctrl": { + "type": "boolean" + }, + "shift": { + "type": "boolean" + } + }, + "required": [ + "shift", + "alt", + "ctrl" + ], + "type": "object" + }, + "scroll": { + "$ref": "#/definitions/ScrollInfo" + }, + "source": { + "$ref": "#/definitions/Renderable" + }, + "target": { + "anyOf": [ + { + "$ref": "#/definitions/Renderable" + }, + { + "type": "null" + } + ] + }, + "type": { + "$ref": "#/definitions/MouseEventType" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "type", + "button", + "x", + "y", + "modifiers", + "target" + ], + "type": "object" + }, + "MouseEventType": { + "enum": [ + "down", + "up", + "move", + "drag", + "drag-end", + "drop", + "over", + "out", + "scroll" + ], + "type": "string" + }, + "ParsedKey": { + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "ctrl": { + "type": "boolean" + }, + "meta": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "number": { + "type": "boolean" + }, + "option": { + "type": "boolean" + }, + "raw": { + "type": "string" + }, + "sequence": { + "type": "string" + }, + "shift": { + "type": "boolean" + } + }, + "required": [ + "name", + "ctrl", + "meta", + "shift", + "option", + "sequence", + "number", + "raw" + ], + "type": "object" + }, + "PositionTypeString": { + "enum": [ + "static", + "relative", + "absolute" + ], + "type": "string" + }, + "RGBA": { + "additionalProperties": false, + "properties": { + "buffer": { + "additionalProperties": { + "type": "number" + }, + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "additionalProperties": false, + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "type": "object" + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "type": "object" + } + }, + "required": [ + "buffer" + ], + "type": "object" + }, + "Renderable": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "num": { + "type": "number" + }, + "parent": { + "anyOf": [ + { + "$ref": "#/definitions/Renderable" + }, + { + "type": "null" + } + ] + }, + "selectable": { + "type": "boolean" + } + }, + "required": [ + "id", + "num", + "selectable", + "parent" + ], + "type": "object" + }, + "ScrollInfo": { + "additionalProperties": false, + "properties": { + "delta": { + "type": "number" + }, + "direction": { + "enum": [ + "up", + "down", + "left", + "right" + ], + "type": "string" + } + }, + "required": [ + "direction", + "delta" + ], + "type": "object" + }, + "SelectOption": { + "additionalProperties": false, + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "value": {} + }, + "required": [ + "name", + "description" + ], + "type": "object" + }, + "SelectRenderableOptions": { + "additionalProperties": false, + "properties": { + "alignItems": { + "$ref": "#/definitions/AlignString" + }, + "backgroundColor": { + "$ref": "#/definitions/ColorInput" + }, + "bottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "buffered": { + "type": "boolean" + }, + "descriptionColor": { + "$ref": "#/definitions/ColorInput" + }, + "enableLayout": { + "type": "boolean" + }, + "fastScrollStep": { + "type": "number" + }, + "flexBasis": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + } + ] + }, + "flexDirection": { + "$ref": "#/definitions/FlexDirectionString" + }, + "flexGrow": { + "type": "number" + }, + "flexShrink": { + "type": "number" + }, + "focusedBackgroundColor": { + "$ref": "#/definitions/ColorInput" + }, + "focusedTextColor": { + "$ref": "#/definitions/ColorInput" + }, + "font": { + "enum": [ + "tiny", + "block", + "shade", + "slick" + ], + "type": "string" + }, + "height": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "itemSpacing": { + "type": "number" + }, + "justifyContent": { + "$ref": "#/definitions/JustifyString" + }, + "left": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "live": { + "type": "boolean" + }, + "margin": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginBottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginLeft": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginRight": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginTop": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "maxHeight": { + "type": "number" + }, + "maxWidth": { + "type": "number" + }, + "minHeight": { + "type": "number" + }, + "minWidth": { + "type": "number" + }, + "onKeyDown": { + "$comment": "(key: ParsedKey) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "key": { + "$ref": "#/definitions/ParsedKey" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDown": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDrag": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDragEnd": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDrop": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseMove": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseOut": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseOver": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseScroll": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseUp": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "options": { + "items": { + "$ref": "#/definitions/SelectOption" + }, + "type": "array" + }, + "padding": { + "type": [ + "number", + "string" + ] + }, + "paddingBottom": { + "type": [ + "number", + "string" + ] + }, + "paddingLeft": { + "type": [ + "number", + "string" + ] + }, + "paddingRight": { + "type": [ + "number", + "string" + ] + }, + "paddingTop": { + "type": [ + "number", + "string" + ] + }, + "position": { + "$ref": "#/definitions/PositionTypeString" + }, + "right": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "selectedBackgroundColor": { + "$ref": "#/definitions/ColorInput" + }, + "selectedDescriptionColor": { + "$ref": "#/definitions/ColorInput" + }, + "selectedTextColor": { + "$ref": "#/definitions/ColorInput" + }, + "showDescription": { + "type": "boolean" + }, + "showScrollIndicator": { + "type": "boolean" + }, + "textColor": { + "$ref": "#/definitions/ColorInput" + }, + "top": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "visible": { + "type": "boolean" + }, + "width": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "wrapSelection": { + "type": "boolean" + }, + "zIndex": { + "type": "number" + } + }, + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/core/docs/api/schemas/TabSelectRenderableOptions.json b/packages/core/docs/api/schemas/TabSelectRenderableOptions.json new file mode 100644 index 000000000..5406d464e --- /dev/null +++ b/packages/core/docs/api/schemas/TabSelectRenderableOptions.json @@ -0,0 +1,752 @@ +{ + "$ref": "#/definitions/TabSelectRenderableOptions", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AlignString": { + "enum": [ + "auto", + "flex-start", + "center", + "flex-end", + "stretch", + "baseline", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "ColorInput": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RGBA" + } + ] + }, + "FlexDirectionString": { + "enum": [ + "column", + "column-reverse", + "row", + "row-reverse" + ], + "type": "string" + }, + "JustifyString": { + "enum": [ + "flex-start", + "center", + "flex-end", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "MouseEvent": { + "additionalProperties": false, + "properties": { + "button": { + "type": "number" + }, + "modifiers": { + "additionalProperties": false, + "properties": { + "alt": { + "type": "boolean" + }, + "ctrl": { + "type": "boolean" + }, + "shift": { + "type": "boolean" + } + }, + "required": [ + "shift", + "alt", + "ctrl" + ], + "type": "object" + }, + "scroll": { + "$ref": "#/definitions/ScrollInfo" + }, + "source": { + "$ref": "#/definitions/Renderable" + }, + "target": { + "anyOf": [ + { + "$ref": "#/definitions/Renderable" + }, + { + "type": "null" + } + ] + }, + "type": { + "$ref": "#/definitions/MouseEventType" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "type", + "button", + "x", + "y", + "modifiers", + "target" + ], + "type": "object" + }, + "MouseEventType": { + "enum": [ + "down", + "up", + "move", + "drag", + "drag-end", + "drop", + "over", + "out", + "scroll" + ], + "type": "string" + }, + "ParsedKey": { + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "ctrl": { + "type": "boolean" + }, + "meta": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "number": { + "type": "boolean" + }, + "option": { + "type": "boolean" + }, + "raw": { + "type": "string" + }, + "sequence": { + "type": "string" + }, + "shift": { + "type": "boolean" + } + }, + "required": [ + "name", + "ctrl", + "meta", + "shift", + "option", + "sequence", + "number", + "raw" + ], + "type": "object" + }, + "PositionTypeString": { + "enum": [ + "static", + "relative", + "absolute" + ], + "type": "string" + }, + "RGBA": { + "additionalProperties": false, + "properties": { + "buffer": { + "additionalProperties": { + "type": "number" + }, + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "additionalProperties": false, + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "type": "object" + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "type": "object" + } + }, + "required": [ + "buffer" + ], + "type": "object" + }, + "Renderable": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "num": { + "type": "number" + }, + "parent": { + "anyOf": [ + { + "$ref": "#/definitions/Renderable" + }, + { + "type": "null" + } + ] + }, + "selectable": { + "type": "boolean" + } + }, + "required": [ + "id", + "num", + "selectable", + "parent" + ], + "type": "object" + }, + "ScrollInfo": { + "additionalProperties": false, + "properties": { + "delta": { + "type": "number" + }, + "direction": { + "enum": [ + "up", + "down", + "left", + "right" + ], + "type": "string" + } + }, + "required": [ + "direction", + "delta" + ], + "type": "object" + }, + "TabSelectOption": { + "additionalProperties": false, + "properties": { + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "value": {} + }, + "required": [ + "name", + "description" + ], + "type": "object" + }, + "TabSelectRenderableOptions": { + "additionalProperties": false, + "properties": { + "alignItems": { + "$ref": "#/definitions/AlignString" + }, + "backgroundColor": { + "$ref": "#/definitions/ColorInput" + }, + "bottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "buffered": { + "type": "boolean" + }, + "enableLayout": { + "type": "boolean" + }, + "flexBasis": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + } + ] + }, + "flexDirection": { + "$ref": "#/definitions/FlexDirectionString" + }, + "flexGrow": { + "type": "number" + }, + "flexShrink": { + "type": "number" + }, + "focusedBackgroundColor": { + "$ref": "#/definitions/ColorInput" + }, + "focusedTextColor": { + "$ref": "#/definitions/ColorInput" + }, + "height": { + "type": "number" + }, + "justifyContent": { + "$ref": "#/definitions/JustifyString" + }, + "left": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "live": { + "type": "boolean" + }, + "margin": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginBottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginLeft": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginRight": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginTop": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "maxHeight": { + "type": "number" + }, + "maxWidth": { + "type": "number" + }, + "minHeight": { + "type": "number" + }, + "minWidth": { + "type": "number" + }, + "onKeyDown": { + "$comment": "(key: ParsedKey) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "key": { + "$ref": "#/definitions/ParsedKey" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDown": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDrag": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDragEnd": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDrop": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseMove": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseOut": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseOver": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseScroll": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseUp": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "options": { + "items": { + "$ref": "#/definitions/TabSelectOption" + }, + "type": "array" + }, + "padding": { + "type": [ + "number", + "string" + ] + }, + "paddingBottom": { + "type": [ + "number", + "string" + ] + }, + "paddingLeft": { + "type": [ + "number", + "string" + ] + }, + "paddingRight": { + "type": [ + "number", + "string" + ] + }, + "paddingTop": { + "type": [ + "number", + "string" + ] + }, + "position": { + "$ref": "#/definitions/PositionTypeString" + }, + "right": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "selectedBackgroundColor": { + "$ref": "#/definitions/ColorInput" + }, + "selectedDescriptionColor": { + "$ref": "#/definitions/ColorInput" + }, + "selectedTextColor": { + "$ref": "#/definitions/ColorInput" + }, + "showDescription": { + "type": "boolean" + }, + "showScrollArrows": { + "type": "boolean" + }, + "showUnderline": { + "type": "boolean" + }, + "tabWidth": { + "type": "number" + }, + "textColor": { + "$ref": "#/definitions/ColorInput" + }, + "top": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "visible": { + "type": "boolean" + }, + "width": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "wrapSelection": { + "type": "boolean" + }, + "zIndex": { + "type": "number" + } + }, + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/core/docs/api/schemas/TextOptions.json b/packages/core/docs/api/schemas/TextOptions.json new file mode 100644 index 000000000..4843b7164 --- /dev/null +++ b/packages/core/docs/api/schemas/TextOptions.json @@ -0,0 +1,831 @@ +{ + "$ref": "#/definitions/TextOptions", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "AlignString": { + "enum": [ + "auto", + "flex-start", + "center", + "flex-end", + "stretch", + "baseline", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "FlexDirectionString": { + "enum": [ + "column", + "column-reverse", + "row", + "row-reverse" + ], + "type": "string" + }, + "JustifyString": { + "enum": [ + "flex-start", + "center", + "flex-end", + "space-between", + "space-around", + "space-evenly" + ], + "type": "string" + }, + "MouseEvent": { + "additionalProperties": false, + "properties": { + "button": { + "type": "number" + }, + "modifiers": { + "additionalProperties": false, + "properties": { + "alt": { + "type": "boolean" + }, + "ctrl": { + "type": "boolean" + }, + "shift": { + "type": "boolean" + } + }, + "required": [ + "shift", + "alt", + "ctrl" + ], + "type": "object" + }, + "scroll": { + "$ref": "#/definitions/ScrollInfo" + }, + "source": { + "$ref": "#/definitions/Renderable" + }, + "target": { + "anyOf": [ + { + "$ref": "#/definitions/Renderable" + }, + { + "type": "null" + } + ] + }, + "type": { + "$ref": "#/definitions/MouseEventType" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + } + }, + "required": [ + "type", + "button", + "x", + "y", + "modifiers", + "target" + ], + "type": "object" + }, + "MouseEventType": { + "enum": [ + "down", + "up", + "move", + "drag", + "drag-end", + "drop", + "over", + "out", + "scroll" + ], + "type": "string" + }, + "ParsedKey": { + "additionalProperties": false, + "properties": { + "code": { + "type": "string" + }, + "ctrl": { + "type": "boolean" + }, + "meta": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "number": { + "type": "boolean" + }, + "option": { + "type": "boolean" + }, + "raw": { + "type": "string" + }, + "sequence": { + "type": "string" + }, + "shift": { + "type": "boolean" + } + }, + "required": [ + "name", + "ctrl", + "meta", + "shift", + "option", + "sequence", + "number", + "raw" + ], + "type": "object" + }, + "PositionTypeString": { + "enum": [ + "static", + "relative", + "absolute" + ], + "type": "string" + }, + "RGBA": { + "additionalProperties": false, + "properties": { + "buffer": { + "additionalProperties": { + "type": "number" + }, + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "additionalProperties": false, + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "type": "object" + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "type": "object" + } + }, + "required": [ + "buffer" + ], + "type": "object" + }, + "Renderable": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "num": { + "type": "number" + }, + "parent": { + "anyOf": [ + { + "$ref": "#/definitions/Renderable" + }, + { + "type": "null" + } + ] + }, + "selectable": { + "type": "boolean" + } + }, + "required": [ + "id", + "num", + "selectable", + "parent" + ], + "type": "object" + }, + "ScrollInfo": { + "additionalProperties": false, + "properties": { + "delta": { + "type": "number" + }, + "direction": { + "enum": [ + "up", + "down", + "left", + "right" + ], + "type": "string" + } + }, + "required": [ + "direction", + "delta" + ], + "type": "object" + }, + "StyledText": { + "additionalProperties": false, + "properties": { + "chunks": { + "items": { + "$ref": "#/definitions/TextChunk" + }, + "type": "array" + } + }, + "required": [ + "chunks" + ], + "type": "object" + }, + "TextChunk": { + "additionalProperties": false, + "properties": { + "__isChunk": { + "const": true, + "type": "boolean" + }, + "attributes": { + "type": "number" + }, + "bg": { + "$ref": "#/definitions/RGBA" + }, + "fg": { + "$ref": "#/definitions/RGBA" + }, + "plainText": { + "type": "string" + }, + "text": { + "additionalProperties": { + "type": "number" + }, + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "additionalProperties": false, + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "type": "object" + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "type": "object" + } + }, + "required": [ + "__isChunk", + "text", + "plainText" + ], + "type": "object" + }, + "TextOptions": { + "additionalProperties": false, + "properties": { + "alignItems": { + "$ref": "#/definitions/AlignString" + }, + "attributes": { + "type": "number" + }, + "bg": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RGBA" + } + ] + }, + "bottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "buffered": { + "type": "boolean" + }, + "content": { + "anyOf": [ + { + "$ref": "#/definitions/StyledText" + }, + { + "type": "string" + } + ] + }, + "enableLayout": { + "type": "boolean" + }, + "fg": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RGBA" + } + ] + }, + "flexBasis": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + } + ] + }, + "flexDirection": { + "$ref": "#/definitions/FlexDirectionString" + }, + "flexGrow": { + "type": "number" + }, + "flexShrink": { + "type": "number" + }, + "height": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "justifyContent": { + "$ref": "#/definitions/JustifyString" + }, + "left": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "live": { + "type": "boolean" + }, + "margin": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginBottom": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginLeft": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginRight": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "marginTop": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "maxHeight": { + "type": "number" + }, + "maxWidth": { + "type": "number" + }, + "minHeight": { + "type": "number" + }, + "minWidth": { + "type": "number" + }, + "onKeyDown": { + "$comment": "(key: ParsedKey) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "key": { + "$ref": "#/definitions/ParsedKey" + } + }, + "required": [ + "key" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDown": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDrag": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDragEnd": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseDrop": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseMove": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseOut": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseOver": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseScroll": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "onMouseUp": { + "$comment": "(event: MouseEvent) => void", + "properties": { + "namedArgs": { + "additionalProperties": false, + "properties": { + "event": { + "$ref": "#/definitions/MouseEvent" + } + }, + "required": [ + "event" + ], + "type": "object" + } + }, + "type": "object" + }, + "padding": { + "type": [ + "number", + "string" + ] + }, + "paddingBottom": { + "type": [ + "number", + "string" + ] + }, + "paddingLeft": { + "type": [ + "number", + "string" + ] + }, + "paddingRight": { + "type": [ + "number", + "string" + ] + }, + "paddingTop": { + "type": [ + "number", + "string" + ] + }, + "position": { + "$ref": "#/definitions/PositionTypeString" + }, + "right": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "selectable": { + "type": "boolean" + }, + "selectionBg": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RGBA" + } + ] + }, + "selectionFg": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/RGBA" + } + ] + }, + "top": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "visible": { + "type": "boolean" + }, + "width": { + "anyOf": [ + { + "type": "number" + }, + { + "const": "auto", + "type": "string" + }, + { + "type": "string" + } + ] + }, + "zIndex": { + "type": "number" + } + }, + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/core/docs/api/schemas/ThreeCliRendererOptions.json b/packages/core/docs/api/schemas/ThreeCliRendererOptions.json new file mode 100644 index 000000000..2459a4103 --- /dev/null +++ b/packages/core/docs/api/schemas/ThreeCliRendererOptions.json @@ -0,0 +1,96 @@ +{ + "$ref": "#/definitions/ThreeCliRendererOptions", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "RGBA": { + "additionalProperties": false, + "properties": { + "buffer": { + "additionalProperties": { + "type": "number" + }, + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "additionalProperties": false, + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "type": "object" + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "type": "object" + } + }, + "required": [ + "buffer" + ], + "type": "object" + }, + "SuperSampleType": { + "enum": [ + "none", + "gpu", + "cpu" + ], + "type": "string" + }, + "ThreeCliRendererOptions": { + "additionalProperties": false, + "properties": { + "alpha": { + "type": "boolean" + }, + "autoResize": { + "type": "boolean" + }, + "backgroundColor": { + "$ref": "#/definitions/RGBA" + }, + "focalLength": { + "type": "number" + }, + "height": { + "type": "number" + }, + "libPath": { + "type": "string" + }, + "superSample": { + "$ref": "#/definitions/SuperSampleType" + }, + "width": { + "type": "number" + } + }, + "required": [ + "width", + "height" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/core/docs/api/schemas/TimelineOptions.json b/packages/core/docs/api/schemas/TimelineOptions.json new file mode 100644 index 000000000..0e77be9d5 --- /dev/null +++ b/packages/core/docs/api/schemas/TimelineOptions.json @@ -0,0 +1,27 @@ +{ + "$ref": "#/definitions/TimelineOptions", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "TimelineOptions": { + "additionalProperties": false, + "properties": { + "autoplay": { + "type": "boolean" + }, + "duration": { + "type": "number" + }, + "loop": { + "type": "boolean" + }, + "onComplete": { + "$comment": "() => void" + }, + "onPause": { + "$comment": "() => void" + } + }, + "type": "object" + } + } +} \ No newline at end of file diff --git a/scripts/generate-api-schemas.sh b/scripts/generate-api-schemas.sh new file mode 100755 index 000000000..953c182ee --- /dev/null +++ b/scripts/generate-api-schemas.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +# Generate JSON schemas for OpenTUI API documentation + +SCHEMA_DIR="packages/core/docs/api/schemas" +mkdir -p "$SCHEMA_DIR" + +echo "Generating JSON schemas for OpenTUI API..." + +# Renderables +echo "Generating schemas for renderables..." +npx ts-json-schema-generator --path "packages/core/src/renderables/ASCIIFont.ts" --type "ASCIIFontOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/ASCIIFontOptions.json" 2>/dev/null +npx ts-json-schema-generator --path "packages/core/src/renderables/Box.ts" --type "BoxOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/BoxOptions.json" 2>/dev/null +npx ts-json-schema-generator --path "packages/core/src/renderables/Text.ts" --type "TextOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/TextOptions.json" 2>/dev/null +npx ts-json-schema-generator --path "packages/core/src/renderables/Input.ts" --type "InputRenderableOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/InputRenderableOptions.json" 2>/dev/null +npx ts-json-schema-generator --path "packages/core/src/renderables/Select.ts" --type "SelectRenderableOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/SelectRenderableOptions.json" 2>/dev/null +npx ts-json-schema-generator --path "packages/core/src/renderables/TabSelect.ts" --type "TabSelectRenderableOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/TabSelectRenderableOptions.json" 2>/dev/null +npx ts-json-schema-generator --path "packages/core/src/renderables/FrameBuffer.ts" --type "FrameBufferOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/FrameBufferOptions.json" 2>/dev/null + +# Core types +echo "Generating schemas for core types..." +npx ts-json-schema-generator --path "packages/core/src/Renderable.ts" --type "RenderableOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/RenderableOptions.json" 2>/dev/null +npx ts-json-schema-generator --path "packages/core/src/Renderable.ts" --type "LayoutOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/LayoutOptions.json" 2>/dev/null +npx ts-json-schema-generator --path "packages/core/src/renderer.ts" --type "CliRendererConfig" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/CliRendererConfig.json" 2>/dev/null +npx ts-json-schema-generator --path "packages/core/src/console.ts" --type "ConsoleOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/ConsoleOptions.json" 2>/dev/null + +# 3D/Animation types +echo "Generating schemas for 3D/Animation types..." +npx ts-json-schema-generator --path "packages/core/src/3d/animation/ExplodingSpriteEffect.ts" --type "ExplosionEffectParameters" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/ExplosionEffectParameters.json" 2>/dev/null +npx ts-json-schema-generator --path "packages/core/src/3d/WGPURenderer.ts" --type "ThreeCliRendererOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/ThreeCliRendererOptions.json" 2>/dev/null +npx ts-json-schema-generator --path "packages/core/src/animation/Timeline.ts" --type "TimelineOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/TimelineOptions.json" 2>/dev/null +npx ts-json-schema-generator --path "packages/core/src/animation/Timeline.ts" --type "AnimationOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/AnimationOptions.json" 2>/dev/null + +# Library types +echo "Generating schemas for library types..." +npx ts-json-schema-generator --path "packages/core/src/lib/border.ts" --type "BorderConfig" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/BorderConfig.json" 2>/dev/null +npx ts-json-schema-generator --path "packages/core/src/lib/border.ts" --type "BoxDrawOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/BoxDrawOptions.json" 2>/dev/null + +echo "Schema generation complete! Check $SCHEMA_DIR for JSON schema files." \ No newline at end of file From a31b4131d32dfbbecb8b7cfeee1902b65d3c6b9a Mon Sep 17 00:00:00 2001 From: entrepeneur4lyf Date: Fri, 22 Aug 2025 03:06:45 -0400 Subject: [PATCH 4/6] Add JSDoc generation from JSON schemas - Created multiple scripts to convert JSON schemas to JSDoc - Used json-schema-to-jsdoc library for automatic conversion - Generated JSDoc typedefs for all 17 OpenTUI types - Includes both individual files and combined all-types.js - JSDoc includes auto-descriptions and proper type formatting - Can be used for IDE support and inline documentation --- .../core/docs/api/jsdoc/ASCIIFontOptions.js | 81 ++ .../core/docs/api/jsdoc/AnimationOptions.js | 21 + packages/core/docs/api/jsdoc/BorderConfig.js | 53 + .../core/docs/api/jsdoc/BoxDrawOptions.js | 53 + packages/core/docs/api/jsdoc/BoxOptions.js | 116 ++ .../core/docs/api/jsdoc/CliRendererConfig.js | 54 + .../core/docs/api/jsdoc/ConsoleOptions.js | 30 + .../api/jsdoc/ExplosionEffectParameters.js | 17 + .../core/docs/api/jsdoc/FrameBufferOptions.js | 68 ++ .../docs/api/jsdoc/InputRenderableOptions.js | 87 ++ packages/core/docs/api/jsdoc/LayoutOptions.js | 29 + .../core/docs/api/jsdoc/RenderableOptions.js | 68 ++ .../docs/api/jsdoc/SelectRenderableOptions.js | 96 ++ .../api/jsdoc/TabSelectRenderableOptions.js | 96 ++ packages/core/docs/api/jsdoc/TextOptions.js | 88 ++ .../docs/api/jsdoc/ThreeCliRendererOptions.js | 24 + .../core/docs/api/jsdoc/TimelineOptions.js | 16 + packages/core/docs/api/jsdoc/all-types.js | 1035 +++++++++++++++++ scripts/convert-schema-to-jsdoc.js | 88 ++ scripts/generate-api-jsdoc.js | 159 +++ scripts/generate-jsdoc-from-schemas.js | 89 ++ scripts/jsdoc-from-schemas.js | 127 ++ scripts/schemas-to-jsdoc.js | 40 + 23 files changed, 2535 insertions(+) create mode 100644 packages/core/docs/api/jsdoc/ASCIIFontOptions.js create mode 100644 packages/core/docs/api/jsdoc/AnimationOptions.js create mode 100644 packages/core/docs/api/jsdoc/BorderConfig.js create mode 100644 packages/core/docs/api/jsdoc/BoxDrawOptions.js create mode 100644 packages/core/docs/api/jsdoc/BoxOptions.js create mode 100644 packages/core/docs/api/jsdoc/CliRendererConfig.js create mode 100644 packages/core/docs/api/jsdoc/ConsoleOptions.js create mode 100644 packages/core/docs/api/jsdoc/ExplosionEffectParameters.js create mode 100644 packages/core/docs/api/jsdoc/FrameBufferOptions.js create mode 100644 packages/core/docs/api/jsdoc/InputRenderableOptions.js create mode 100644 packages/core/docs/api/jsdoc/LayoutOptions.js create mode 100644 packages/core/docs/api/jsdoc/RenderableOptions.js create mode 100644 packages/core/docs/api/jsdoc/SelectRenderableOptions.js create mode 100644 packages/core/docs/api/jsdoc/TabSelectRenderableOptions.js create mode 100644 packages/core/docs/api/jsdoc/TextOptions.js create mode 100644 packages/core/docs/api/jsdoc/ThreeCliRendererOptions.js create mode 100644 packages/core/docs/api/jsdoc/TimelineOptions.js create mode 100644 packages/core/docs/api/jsdoc/all-types.js create mode 100644 scripts/convert-schema-to-jsdoc.js create mode 100755 scripts/generate-api-jsdoc.js create mode 100755 scripts/generate-jsdoc-from-schemas.js create mode 100755 scripts/jsdoc-from-schemas.js create mode 100755 scripts/schemas-to-jsdoc.js diff --git a/packages/core/docs/api/jsdoc/ASCIIFontOptions.js b/packages/core/docs/api/jsdoc/ASCIIFontOptions.js new file mode 100644 index 000000000..eb4ddbdd0 --- /dev/null +++ b/packages/core/docs/api/jsdoc/ASCIIFontOptions.js @@ -0,0 +1,81 @@ +/** + * Generated from: ASCIIFontOptions.json + * Date: 2025-08-22T07:06:25.491Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a MouseEventType string + * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType + */ + + + /** + * Represents a ParsedKey object + * @typedef {object} ParsedKey + * @property {string} [code] + * @property {boolean} ctrl + * @property {boolean} meta + * @property {string} name + * @property {boolean} number + * @property {boolean} option + * @property {string} raw + * @property {string} sequence + * @property {boolean} shift + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + /** + * Represents a Renderable object + * @typedef {object} Renderable + * @property {string} id + * @property {number} num + * @property {*} parent + * @property {boolean} selectable + */ + + + /** + * Represents a ScrollInfo object + * @typedef {object} ScrollInfo + * @property {number} delta + * @property {"up"|"down"|"left"|"right"} direction + */ + + diff --git a/packages/core/docs/api/jsdoc/AnimationOptions.js b/packages/core/docs/api/jsdoc/AnimationOptions.js new file mode 100644 index 000000000..9a8f745c7 --- /dev/null +++ b/packages/core/docs/api/jsdoc/AnimationOptions.js @@ -0,0 +1,21 @@ +/** + * Generated from: AnimationOptions.json + * Date: 2025-08-22T07:06:25.492Z + */ + + /** + * Represents a EasingFunctions string + * @typedef {"linear"|"inQuad"|"outQuad"|"inOutQuad"|"inExpo"|"outExpo"|"inOutSine"|"outBounce"|"outElastic"|"inBounce"|"inCirc"|"outCirc"|"inOutCirc"|"inBack"|"outBack"|"inOutBack"} EasingFunctions + */ + + + /** + * Represents a JSAnimation object + * @typedef {object} JSAnimation + * @property {number} currentTime + * @property {number} deltaTime + * @property {number} progress + * @property {array} targets + */ + + diff --git a/packages/core/docs/api/jsdoc/BorderConfig.js b/packages/core/docs/api/jsdoc/BorderConfig.js new file mode 100644 index 000000000..7c6002240 --- /dev/null +++ b/packages/core/docs/api/jsdoc/BorderConfig.js @@ -0,0 +1,53 @@ +/** + * Generated from: BorderConfig.json + * Date: 2025-08-22T07:06:25.492Z + */ + + /** + * Represents a BorderCharacters object + * @typedef {object} BorderCharacters + * @property {string} bottomLeft + * @property {string} bottomRight + * @property {string} bottomT + * @property {string} cross + * @property {string} horizontal + * @property {string} leftT + * @property {string} rightT + * @property {string} topLeft + * @property {string} topRight + * @property {string} topT + * @property {string} vertical + */ + + + /** + * Represents a BorderSides string + * @typedef {"top"|"right"|"bottom"|"left"} BorderSides + */ + + + /** + * Represents a BorderStyle string + * @typedef {"single"|"double"|"rounded"|"heavy"} BorderStyle + */ + + + /** + * Represents a ColorInput undefined + * @typedef {undefined} ColorInput + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + diff --git a/packages/core/docs/api/jsdoc/BoxDrawOptions.js b/packages/core/docs/api/jsdoc/BoxDrawOptions.js new file mode 100644 index 000000000..8fbb699f3 --- /dev/null +++ b/packages/core/docs/api/jsdoc/BoxDrawOptions.js @@ -0,0 +1,53 @@ +/** + * Generated from: BoxDrawOptions.json + * Date: 2025-08-22T07:06:25.496Z + */ + + /** + * Represents a BorderCharacters object + * @typedef {object} BorderCharacters + * @property {string} bottomLeft + * @property {string} bottomRight + * @property {string} bottomT + * @property {string} cross + * @property {string} horizontal + * @property {string} leftT + * @property {string} rightT + * @property {string} topLeft + * @property {string} topRight + * @property {string} topT + * @property {string} vertical + */ + + + /** + * Represents a BorderSides string + * @typedef {"top"|"right"|"bottom"|"left"} BorderSides + */ + + + /** + * Represents a BorderStyle string + * @typedef {"single"|"double"|"rounded"|"heavy"} BorderStyle + */ + + + /** + * Represents a ColorInput undefined + * @typedef {undefined} ColorInput + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + diff --git a/packages/core/docs/api/jsdoc/BoxOptions.js b/packages/core/docs/api/jsdoc/BoxOptions.js new file mode 100644 index 000000000..dbf6e3cd4 --- /dev/null +++ b/packages/core/docs/api/jsdoc/BoxOptions.js @@ -0,0 +1,116 @@ +/** + * Generated from: BoxOptions.json + * Date: 2025-08-22T07:06:25.496Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a BorderCharacters object + * @typedef {object} BorderCharacters + * @property {string} bottomLeft + * @property {string} bottomRight + * @property {string} bottomT + * @property {string} cross + * @property {string} horizontal + * @property {string} leftT + * @property {string} rightT + * @property {string} topLeft + * @property {string} topRight + * @property {string} topT + * @property {string} vertical + */ + + + /** + * Represents a BorderSides string + * @typedef {"top"|"right"|"bottom"|"left"} BorderSides + */ + + + /** + * Represents a BorderStyle string + * @typedef {"single"|"double"|"rounded"|"heavy"} BorderStyle + */ + + + /** + * Represents a ColorInput undefined + * @typedef {undefined} ColorInput + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a MouseEventType string + * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType + */ + + + /** + * Represents a ParsedKey object + * @typedef {object} ParsedKey + * @property {string} [code] + * @property {boolean} ctrl + * @property {boolean} meta + * @property {string} name + * @property {boolean} number + * @property {boolean} option + * @property {string} raw + * @property {string} sequence + * @property {boolean} shift + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + /** + * Represents a Renderable object + * @typedef {object} Renderable + * @property {string} id + * @property {number} num + * @property {*} parent + * @property {boolean} selectable + */ + + + /** + * Represents a ScrollInfo object + * @typedef {object} ScrollInfo + * @property {number} delta + * @property {"up"|"down"|"left"|"right"} direction + */ + + diff --git a/packages/core/docs/api/jsdoc/CliRendererConfig.js b/packages/core/docs/api/jsdoc/CliRendererConfig.js new file mode 100644 index 000000000..34745eed9 --- /dev/null +++ b/packages/core/docs/api/jsdoc/CliRendererConfig.js @@ -0,0 +1,54 @@ +/** + * Generated from: CliRendererConfig.json + * Date: 2025-08-22T07:06:25.497Z + */ + + /** + * Represents a ColorInput undefined + * @typedef {undefined} ColorInput + */ + + + /** + * Represents a ConsolePosition string + * @typedef {"top"|"bottom"|"left"|"right"} ConsolePosition + */ + + + /** + * Represents a CursorStyle string + * @typedef {"block"|"line"|"underline"} CursorStyle + */ + + + /** + * Represents a DebugOverlayCorner number + * @typedef {0|1|2|3} DebugOverlayCorner + */ + + + /** + * Represents a Pointer number + * @typedef {number} Pointer + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + /** + * Represents a SocketReadyState string + * @typedef {"opening"|"open"|"readOnly"|"writeOnly"|"closed"} SocketReadyState + */ + + diff --git a/packages/core/docs/api/jsdoc/ConsoleOptions.js b/packages/core/docs/api/jsdoc/ConsoleOptions.js new file mode 100644 index 000000000..66a41beb1 --- /dev/null +++ b/packages/core/docs/api/jsdoc/ConsoleOptions.js @@ -0,0 +1,30 @@ +/** + * Generated from: ConsoleOptions.json + * Date: 2025-08-22T07:06:25.497Z + */ + + /** + * Represents a ColorInput undefined + * @typedef {undefined} ColorInput + */ + + + /** + * Represents a ConsolePosition string + * @typedef {"top"|"bottom"|"left"|"right"} ConsolePosition + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + diff --git a/packages/core/docs/api/jsdoc/ExplosionEffectParameters.js b/packages/core/docs/api/jsdoc/ExplosionEffectParameters.js new file mode 100644 index 000000000..8a6d06ac1 --- /dev/null +++ b/packages/core/docs/api/jsdoc/ExplosionEffectParameters.js @@ -0,0 +1,17 @@ +/** + * Generated from: ExplosionEffectParameters.json + * Date: 2025-08-22T07:06:25.497Z + */ + + /** + * 3D vector. + +see {@link https://github.com/mrdoob/three.js/blob/master/src/math/Vector3.js } + * @typedef {object} Vector3 + * @property {true} isVector3 + * @property {number} x + * @property {number} y + * @property {number} z + */ + + diff --git a/packages/core/docs/api/jsdoc/FrameBufferOptions.js b/packages/core/docs/api/jsdoc/FrameBufferOptions.js new file mode 100644 index 000000000..367cfabc9 --- /dev/null +++ b/packages/core/docs/api/jsdoc/FrameBufferOptions.js @@ -0,0 +1,68 @@ +/** + * Generated from: FrameBufferOptions.json + * Date: 2025-08-22T07:06:25.497Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a MouseEventType string + * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType + */ + + + /** + * Represents a ParsedKey object + * @typedef {object} ParsedKey + * @property {string} [code] + * @property {boolean} ctrl + * @property {boolean} meta + * @property {string} name + * @property {boolean} number + * @property {boolean} option + * @property {string} raw + * @property {string} sequence + * @property {boolean} shift + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + + /** + * Represents a Renderable object + * @typedef {object} Renderable + * @property {string} id + * @property {number} num + * @property {*} parent + * @property {boolean} selectable + */ + + + /** + * Represents a ScrollInfo object + * @typedef {object} ScrollInfo + * @property {number} delta + * @property {"up"|"down"|"left"|"right"} direction + */ + + diff --git a/packages/core/docs/api/jsdoc/InputRenderableOptions.js b/packages/core/docs/api/jsdoc/InputRenderableOptions.js new file mode 100644 index 000000000..c30646614 --- /dev/null +++ b/packages/core/docs/api/jsdoc/InputRenderableOptions.js @@ -0,0 +1,87 @@ +/** + * Generated from: InputRenderableOptions.json + * Date: 2025-08-22T07:06:25.497Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a ColorInput undefined + * @typedef {undefined} ColorInput + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a MouseEventType string + * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType + */ + + + /** + * Represents a ParsedKey object + * @typedef {object} ParsedKey + * @property {string} [code] + * @property {boolean} ctrl + * @property {boolean} meta + * @property {string} name + * @property {boolean} number + * @property {boolean} option + * @property {string} raw + * @property {string} sequence + * @property {boolean} shift + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + /** + * Represents a Renderable object + * @typedef {object} Renderable + * @property {string} id + * @property {number} num + * @property {*} parent + * @property {boolean} selectable + */ + + + /** + * Represents a ScrollInfo object + * @typedef {object} ScrollInfo + * @property {number} delta + * @property {"up"|"down"|"left"|"right"} direction + */ + + diff --git a/packages/core/docs/api/jsdoc/LayoutOptions.js b/packages/core/docs/api/jsdoc/LayoutOptions.js new file mode 100644 index 000000000..96a8f04bf --- /dev/null +++ b/packages/core/docs/api/jsdoc/LayoutOptions.js @@ -0,0 +1,29 @@ +/** + * Generated from: LayoutOptions.json + * Date: 2025-08-22T07:06:25.498Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + diff --git a/packages/core/docs/api/jsdoc/RenderableOptions.js b/packages/core/docs/api/jsdoc/RenderableOptions.js new file mode 100644 index 000000000..1c42e9887 --- /dev/null +++ b/packages/core/docs/api/jsdoc/RenderableOptions.js @@ -0,0 +1,68 @@ +/** + * Generated from: RenderableOptions.json + * Date: 2025-08-22T07:06:25.498Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a MouseEventType string + * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType + */ + + + /** + * Represents a ParsedKey object + * @typedef {object} ParsedKey + * @property {string} [code] + * @property {boolean} ctrl + * @property {boolean} meta + * @property {string} name + * @property {boolean} number + * @property {boolean} option + * @property {string} raw + * @property {string} sequence + * @property {boolean} shift + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + + /** + * Represents a Renderable object + * @typedef {object} Renderable + * @property {string} id + * @property {number} num + * @property {*} parent + * @property {boolean} selectable + */ + + + /** + * Represents a ScrollInfo object + * @typedef {object} ScrollInfo + * @property {number} delta + * @property {"up"|"down"|"left"|"right"} direction + */ + + diff --git a/packages/core/docs/api/jsdoc/SelectRenderableOptions.js b/packages/core/docs/api/jsdoc/SelectRenderableOptions.js new file mode 100644 index 000000000..d1874e73c --- /dev/null +++ b/packages/core/docs/api/jsdoc/SelectRenderableOptions.js @@ -0,0 +1,96 @@ +/** + * Generated from: SelectRenderableOptions.json + * Date: 2025-08-22T07:06:25.498Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a ColorInput undefined + * @typedef {undefined} ColorInput + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a MouseEventType string + * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType + */ + + + /** + * Represents a ParsedKey object + * @typedef {object} ParsedKey + * @property {string} [code] + * @property {boolean} ctrl + * @property {boolean} meta + * @property {string} name + * @property {boolean} number + * @property {boolean} option + * @property {string} raw + * @property {string} sequence + * @property {boolean} shift + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + /** + * Represents a Renderable object + * @typedef {object} Renderable + * @property {string} id + * @property {number} num + * @property {*} parent + * @property {boolean} selectable + */ + + + /** + * Represents a ScrollInfo object + * @typedef {object} ScrollInfo + * @property {number} delta + * @property {"up"|"down"|"left"|"right"} direction + */ + + + /** + * Represents a SelectOption object + * @typedef {object} SelectOption + * @property {string} description + * @property {string} name + * @property {*} [value] + */ + + diff --git a/packages/core/docs/api/jsdoc/TabSelectRenderableOptions.js b/packages/core/docs/api/jsdoc/TabSelectRenderableOptions.js new file mode 100644 index 000000000..2f3ea7c88 --- /dev/null +++ b/packages/core/docs/api/jsdoc/TabSelectRenderableOptions.js @@ -0,0 +1,96 @@ +/** + * Generated from: TabSelectRenderableOptions.json + * Date: 2025-08-22T07:06:25.498Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a ColorInput undefined + * @typedef {undefined} ColorInput + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a MouseEventType string + * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType + */ + + + /** + * Represents a ParsedKey object + * @typedef {object} ParsedKey + * @property {string} [code] + * @property {boolean} ctrl + * @property {boolean} meta + * @property {string} name + * @property {boolean} number + * @property {boolean} option + * @property {string} raw + * @property {string} sequence + * @property {boolean} shift + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + /** + * Represents a Renderable object + * @typedef {object} Renderable + * @property {string} id + * @property {number} num + * @property {*} parent + * @property {boolean} selectable + */ + + + /** + * Represents a ScrollInfo object + * @typedef {object} ScrollInfo + * @property {number} delta + * @property {"up"|"down"|"left"|"right"} direction + */ + + + /** + * Represents a TabSelectOption object + * @typedef {object} TabSelectOption + * @property {string} description + * @property {string} name + * @property {*} [value] + */ + + diff --git a/packages/core/docs/api/jsdoc/TextOptions.js b/packages/core/docs/api/jsdoc/TextOptions.js new file mode 100644 index 000000000..3eeea6893 --- /dev/null +++ b/packages/core/docs/api/jsdoc/TextOptions.js @@ -0,0 +1,88 @@ +/** + * Generated from: TextOptions.json + * Date: 2025-08-22T07:06:25.499Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a MouseEventType string + * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType + */ + + + /** + * Represents a ParsedKey object + * @typedef {object} ParsedKey + * @property {string} [code] + * @property {boolean} ctrl + * @property {boolean} meta + * @property {string} name + * @property {boolean} number + * @property {boolean} option + * @property {string} raw + * @property {string} sequence + * @property {boolean} shift + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + /** + * Represents a Renderable object + * @typedef {object} Renderable + * @property {string} id + * @property {number} num + * @property {*} parent + * @property {boolean} selectable + */ + + + /** + * Represents a ScrollInfo object + * @typedef {object} ScrollInfo + * @property {number} delta + * @property {"up"|"down"|"left"|"right"} direction + */ + + + /** + * Represents a StyledText object + * @typedef {object} StyledText + * @property {array} chunks + */ + + diff --git a/packages/core/docs/api/jsdoc/ThreeCliRendererOptions.js b/packages/core/docs/api/jsdoc/ThreeCliRendererOptions.js new file mode 100644 index 000000000..a392e97f0 --- /dev/null +++ b/packages/core/docs/api/jsdoc/ThreeCliRendererOptions.js @@ -0,0 +1,24 @@ +/** + * Generated from: ThreeCliRendererOptions.json + * Date: 2025-08-22T07:06:25.499Z + */ + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + /** + * Represents a SuperSampleType string + * @typedef {"none"|"gpu"|"cpu"} SuperSampleType + */ + + diff --git a/packages/core/docs/api/jsdoc/TimelineOptions.js b/packages/core/docs/api/jsdoc/TimelineOptions.js new file mode 100644 index 000000000..cdf0120c9 --- /dev/null +++ b/packages/core/docs/api/jsdoc/TimelineOptions.js @@ -0,0 +1,16 @@ +/** + * Generated from: TimelineOptions.json + * Date: 2025-08-22T07:06:25.499Z + */ + + /** + * Represents a TimelineOptions object + * @typedef {object} TimelineOptions + * @property {boolean} [autoplay] + * @property {number} [duration] + * @property {boolean} [loop] + * @property {*} [onComplete] + * @property {*} [onPause] + */ + + diff --git a/packages/core/docs/api/jsdoc/all-types.js b/packages/core/docs/api/jsdoc/all-types.js new file mode 100644 index 000000000..8e6eb49e8 --- /dev/null +++ b/packages/core/docs/api/jsdoc/all-types.js @@ -0,0 +1,1035 @@ +/** + * OpenTUI Complete Type Definitions + * Generated from JSON Schemas + * Date: 2025-08-22T07:06:25.499Z + */ + +/** + * Generated from: ASCIIFontOptions.json + * Date: 2025-08-22T07:06:25.491Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a MouseEventType string + * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType + */ + + + /** + * Represents a ParsedKey object + * @typedef {object} ParsedKey + * @property {string} [code] + * @property {boolean} ctrl + * @property {boolean} meta + * @property {string} name + * @property {boolean} number + * @property {boolean} option + * @property {string} raw + * @property {string} sequence + * @property {boolean} shift + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + /** + * Represents a Renderable object + * @typedef {object} Renderable + * @property {string} id + * @property {number} num + * @property {*} parent + * @property {boolean} selectable + */ + + + /** + * Represents a ScrollInfo object + * @typedef {object} ScrollInfo + * @property {number} delta + * @property {"up"|"down"|"left"|"right"} direction + */ + + + + +/** + * Generated from: AnimationOptions.json + * Date: 2025-08-22T07:06:25.492Z + */ + + /** + * Represents a EasingFunctions string + * @typedef {"linear"|"inQuad"|"outQuad"|"inOutQuad"|"inExpo"|"outExpo"|"inOutSine"|"outBounce"|"outElastic"|"inBounce"|"inCirc"|"outCirc"|"inOutCirc"|"inBack"|"outBack"|"inOutBack"} EasingFunctions + */ + + + /** + * Represents a JSAnimation object + * @typedef {object} JSAnimation + * @property {number} currentTime + * @property {number} deltaTime + * @property {number} progress + * @property {array} targets + */ + + + + +/** + * Generated from: BorderConfig.json + * Date: 2025-08-22T07:06:25.492Z + */ + + /** + * Represents a BorderCharacters object + * @typedef {object} BorderCharacters + * @property {string} bottomLeft + * @property {string} bottomRight + * @property {string} bottomT + * @property {string} cross + * @property {string} horizontal + * @property {string} leftT + * @property {string} rightT + * @property {string} topLeft + * @property {string} topRight + * @property {string} topT + * @property {string} vertical + */ + + + /** + * Represents a BorderSides string + * @typedef {"top"|"right"|"bottom"|"left"} BorderSides + */ + + + /** + * Represents a BorderStyle string + * @typedef {"single"|"double"|"rounded"|"heavy"} BorderStyle + */ + + + /** + * Represents a ColorInput undefined + * @typedef {undefined} ColorInput + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + + +/** + * Generated from: BoxDrawOptions.json + * Date: 2025-08-22T07:06:25.496Z + */ + + /** + * Represents a BorderCharacters object + * @typedef {object} BorderCharacters + * @property {string} bottomLeft + * @property {string} bottomRight + * @property {string} bottomT + * @property {string} cross + * @property {string} horizontal + * @property {string} leftT + * @property {string} rightT + * @property {string} topLeft + * @property {string} topRight + * @property {string} topT + * @property {string} vertical + */ + + + /** + * Represents a BorderSides string + * @typedef {"top"|"right"|"bottom"|"left"} BorderSides + */ + + + /** + * Represents a BorderStyle string + * @typedef {"single"|"double"|"rounded"|"heavy"} BorderStyle + */ + + + /** + * Represents a ColorInput undefined + * @typedef {undefined} ColorInput + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + + +/** + * Generated from: BoxOptions.json + * Date: 2025-08-22T07:06:25.496Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a BorderCharacters object + * @typedef {object} BorderCharacters + * @property {string} bottomLeft + * @property {string} bottomRight + * @property {string} bottomT + * @property {string} cross + * @property {string} horizontal + * @property {string} leftT + * @property {string} rightT + * @property {string} topLeft + * @property {string} topRight + * @property {string} topT + * @property {string} vertical + */ + + + /** + * Represents a BorderSides string + * @typedef {"top"|"right"|"bottom"|"left"} BorderSides + */ + + + /** + * Represents a BorderStyle string + * @typedef {"single"|"double"|"rounded"|"heavy"} BorderStyle + */ + + + /** + * Represents a ColorInput undefined + * @typedef {undefined} ColorInput + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a MouseEventType string + * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType + */ + + + /** + * Represents a ParsedKey object + * @typedef {object} ParsedKey + * @property {string} [code] + * @property {boolean} ctrl + * @property {boolean} meta + * @property {string} name + * @property {boolean} number + * @property {boolean} option + * @property {string} raw + * @property {string} sequence + * @property {boolean} shift + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + /** + * Represents a Renderable object + * @typedef {object} Renderable + * @property {string} id + * @property {number} num + * @property {*} parent + * @property {boolean} selectable + */ + + + /** + * Represents a ScrollInfo object + * @typedef {object} ScrollInfo + * @property {number} delta + * @property {"up"|"down"|"left"|"right"} direction + */ + + + + +/** + * Generated from: CliRendererConfig.json + * Date: 2025-08-22T07:06:25.497Z + */ + + /** + * Represents a ColorInput undefined + * @typedef {undefined} ColorInput + */ + + + /** + * Represents a ConsolePosition string + * @typedef {"top"|"bottom"|"left"|"right"} ConsolePosition + */ + + + /** + * Represents a CursorStyle string + * @typedef {"block"|"line"|"underline"} CursorStyle + */ + + + /** + * Represents a DebugOverlayCorner number + * @typedef {0|1|2|3} DebugOverlayCorner + */ + + + /** + * Represents a Pointer number + * @typedef {number} Pointer + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + /** + * Represents a SocketReadyState string + * @typedef {"opening"|"open"|"readOnly"|"writeOnly"|"closed"} SocketReadyState + */ + + + + +/** + * Generated from: ConsoleOptions.json + * Date: 2025-08-22T07:06:25.497Z + */ + + /** + * Represents a ColorInput undefined + * @typedef {undefined} ColorInput + */ + + + /** + * Represents a ConsolePosition string + * @typedef {"top"|"bottom"|"left"|"right"} ConsolePosition + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + + +/** + * Generated from: ExplosionEffectParameters.json + * Date: 2025-08-22T07:06:25.497Z + */ + + /** + * 3D vector. + +see {@link https://github.com/mrdoob/three.js/blob/master/src/math/Vector3.js } + * @typedef {object} Vector3 + * @property {true} isVector3 + * @property {number} x + * @property {number} y + * @property {number} z + */ + + + + +/** + * Generated from: FrameBufferOptions.json + * Date: 2025-08-22T07:06:25.497Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a MouseEventType string + * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType + */ + + + /** + * Represents a ParsedKey object + * @typedef {object} ParsedKey + * @property {string} [code] + * @property {boolean} ctrl + * @property {boolean} meta + * @property {string} name + * @property {boolean} number + * @property {boolean} option + * @property {string} raw + * @property {string} sequence + * @property {boolean} shift + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + + /** + * Represents a Renderable object + * @typedef {object} Renderable + * @property {string} id + * @property {number} num + * @property {*} parent + * @property {boolean} selectable + */ + + + /** + * Represents a ScrollInfo object + * @typedef {object} ScrollInfo + * @property {number} delta + * @property {"up"|"down"|"left"|"right"} direction + */ + + + + +/** + * Generated from: InputRenderableOptions.json + * Date: 2025-08-22T07:06:25.497Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a ColorInput undefined + * @typedef {undefined} ColorInput + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a MouseEventType string + * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType + */ + + + /** + * Represents a ParsedKey object + * @typedef {object} ParsedKey + * @property {string} [code] + * @property {boolean} ctrl + * @property {boolean} meta + * @property {string} name + * @property {boolean} number + * @property {boolean} option + * @property {string} raw + * @property {string} sequence + * @property {boolean} shift + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + /** + * Represents a Renderable object + * @typedef {object} Renderable + * @property {string} id + * @property {number} num + * @property {*} parent + * @property {boolean} selectable + */ + + + /** + * Represents a ScrollInfo object + * @typedef {object} ScrollInfo + * @property {number} delta + * @property {"up"|"down"|"left"|"right"} direction + */ + + + + +/** + * Generated from: LayoutOptions.json + * Date: 2025-08-22T07:06:25.498Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + + + +/** + * Generated from: RenderableOptions.json + * Date: 2025-08-22T07:06:25.498Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a MouseEventType string + * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType + */ + + + /** + * Represents a ParsedKey object + * @typedef {object} ParsedKey + * @property {string} [code] + * @property {boolean} ctrl + * @property {boolean} meta + * @property {string} name + * @property {boolean} number + * @property {boolean} option + * @property {string} raw + * @property {string} sequence + * @property {boolean} shift + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + + /** + * Represents a Renderable object + * @typedef {object} Renderable + * @property {string} id + * @property {number} num + * @property {*} parent + * @property {boolean} selectable + */ + + + /** + * Represents a ScrollInfo object + * @typedef {object} ScrollInfo + * @property {number} delta + * @property {"up"|"down"|"left"|"right"} direction + */ + + + + +/** + * Generated from: SelectRenderableOptions.json + * Date: 2025-08-22T07:06:25.498Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a ColorInput undefined + * @typedef {undefined} ColorInput + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a MouseEventType string + * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType + */ + + + /** + * Represents a ParsedKey object + * @typedef {object} ParsedKey + * @property {string} [code] + * @property {boolean} ctrl + * @property {boolean} meta + * @property {string} name + * @property {boolean} number + * @property {boolean} option + * @property {string} raw + * @property {string} sequence + * @property {boolean} shift + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + /** + * Represents a Renderable object + * @typedef {object} Renderable + * @property {string} id + * @property {number} num + * @property {*} parent + * @property {boolean} selectable + */ + + + /** + * Represents a ScrollInfo object + * @typedef {object} ScrollInfo + * @property {number} delta + * @property {"up"|"down"|"left"|"right"} direction + */ + + + /** + * Represents a SelectOption object + * @typedef {object} SelectOption + * @property {string} description + * @property {string} name + * @property {*} [value] + */ + + + + +/** + * Generated from: TabSelectRenderableOptions.json + * Date: 2025-08-22T07:06:25.498Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a ColorInput undefined + * @typedef {undefined} ColorInput + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a MouseEventType string + * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType + */ + + + /** + * Represents a ParsedKey object + * @typedef {object} ParsedKey + * @property {string} [code] + * @property {boolean} ctrl + * @property {boolean} meta + * @property {string} name + * @property {boolean} number + * @property {boolean} option + * @property {string} raw + * @property {string} sequence + * @property {boolean} shift + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + /** + * Represents a Renderable object + * @typedef {object} Renderable + * @property {string} id + * @property {number} num + * @property {*} parent + * @property {boolean} selectable + */ + + + /** + * Represents a ScrollInfo object + * @typedef {object} ScrollInfo + * @property {number} delta + * @property {"up"|"down"|"left"|"right"} direction + */ + + + /** + * Represents a TabSelectOption object + * @typedef {object} TabSelectOption + * @property {string} description + * @property {string} name + * @property {*} [value] + */ + + + + +/** + * Generated from: TextOptions.json + * Date: 2025-08-22T07:06:25.499Z + */ + + /** + * Represents a AlignString string + * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString + */ + + + /** + * Represents a FlexDirectionString string + * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString + */ + + + /** + * Represents a JustifyString string + * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString + */ + + + /** + * Represents a MouseEventType string + * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType + */ + + + /** + * Represents a ParsedKey object + * @typedef {object} ParsedKey + * @property {string} [code] + * @property {boolean} ctrl + * @property {boolean} meta + * @property {string} name + * @property {boolean} number + * @property {boolean} option + * @property {string} raw + * @property {string} sequence + * @property {boolean} shift + */ + + + /** + * Represents a PositionTypeString string + * @typedef {"static"|"relative"|"absolute"} PositionTypeString + */ + + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + /** + * Represents a Renderable object + * @typedef {object} Renderable + * @property {string} id + * @property {number} num + * @property {*} parent + * @property {boolean} selectable + */ + + + /** + * Represents a ScrollInfo object + * @typedef {object} ScrollInfo + * @property {number} delta + * @property {"up"|"down"|"left"|"right"} direction + */ + + + /** + * Represents a StyledText object + * @typedef {object} StyledText + * @property {array} chunks + */ + + + + +/** + * Generated from: ThreeCliRendererOptions.json + * Date: 2025-08-22T07:06:25.499Z + */ + + /** + * Represents a RGBA object + * @typedef {object} RGBA + * @property {object} buffer + * @property {number} buffer.BYTES_PER_ELEMENT + * @property {object} buffer.buffer + * @property {number} buffer.buffer.byteLength + * @property {number} buffer.byteLength + * @property {number} buffer.byteOffset + * @property {number} buffer.length + */ + + + /** + * Represents a SuperSampleType string + * @typedef {"none"|"gpu"|"cpu"} SuperSampleType + */ + + + + +/** + * Generated from: TimelineOptions.json + * Date: 2025-08-22T07:06:25.499Z + */ + + /** + * Represents a TimelineOptions object + * @typedef {object} TimelineOptions + * @property {boolean} [autoplay] + * @property {number} [duration] + * @property {boolean} [loop] + * @property {*} [onComplete] + * @property {*} [onPause] + */ + + diff --git a/scripts/convert-schema-to-jsdoc.js b/scripts/convert-schema-to-jsdoc.js new file mode 100644 index 000000000..0db2791fe --- /dev/null +++ b/scripts/convert-schema-to-jsdoc.js @@ -0,0 +1,88 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +// First, let's test with a single schema file +const schemaPath = path.join(__dirname, '../packages/core/docs/api/schemas/ASCIIFontOptions.json'); +const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8')); + +// Since json-schema-to-jsdoc is a library, let's create a simple converter +function schemaToJSDoc(schema, depth = 0) { + const indent = ' '.repeat(depth * 2); + let jsdoc = []; + + if (depth === 0) { + jsdoc.push('/**'); + if (schema.description) { + jsdoc.push(` * ${schema.description}`); + } + if (schema.$ref) { + const typeName = schema.$ref.split('/').pop(); + jsdoc.push(` * @typedef {Object} ${typeName}`); + } + } + + if (schema.definitions) { + Object.entries(schema.definitions).forEach(([name, def]) => { + jsdoc.push('/**'); + if (def.description) { + jsdoc.push(` * ${def.description}`); + } + jsdoc.push(` * @typedef {Object} ${name}`); + + if (def.properties) { + Object.entries(def.properties).forEach(([propName, prop]) => { + let type = 'any'; + if (prop.type) { + type = prop.type === 'integer' ? 'number' : prop.type; + } else if (prop.$ref) { + type = prop.$ref.split('/').pop(); + } else if (prop.anyOf) { + type = prop.anyOf.map(t => { + if (t.type) return t.type; + if (t.$ref) return t.$ref.split('/').pop(); + if (t.const) return `"${t.const}"`; + return 'any'; + }).join('|'); + } else if (prop.enum) { + type = prop.enum.map(v => `"${v}"`).join('|'); + } + + const required = def.required && def.required.includes(propName); + const optionalMark = required ? '' : '?'; + + let description = ''; + if (prop.description) { + description = ` - ${prop.description}`; + } else if (prop.$comment) { + description = ` - ${prop.$comment}`; + } + + jsdoc.push(` * @property {${type}} ${optionalMark}${propName}${description}`); + }); + } + + jsdoc.push(' */'); + jsdoc.push(''); + }); + } + + return jsdoc.join('\n'); +} + +const jsdocOutput = schemaToJSDoc(schema); + +// Write to file +const outputPath = path.join(__dirname, '../packages/core/docs/api/jsdoc/ASCIIFontOptions.js'); +const outputDir = path.dirname(outputPath); + +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); +} + +fs.writeFileSync(outputPath, jsdocOutput, 'utf8'); +console.log('Generated JSDoc for ASCIIFontOptions'); +console.log('\nSample output:'); +console.log(jsdocOutput.split('\n').slice(0, 50).join('\n')); \ No newline at end of file diff --git a/scripts/generate-api-jsdoc.js b/scripts/generate-api-jsdoc.js new file mode 100755 index 000000000..87d4a25a6 --- /dev/null +++ b/scripts/generate-api-jsdoc.js @@ -0,0 +1,159 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +const schemasDir = path.join(__dirname, '../packages/core/docs/api/schemas'); +const outputDir = path.join(__dirname, '../packages/core/docs/api/jsdoc'); + +// Create output directory if it doesn't exist +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); +} + +// Helper to convert JSON schema types to JSDoc types +function getJSDocType(prop) { + if (prop.type) { + if (Array.isArray(prop.type)) { + return prop.type.map(t => t === 'integer' ? 'number' : t).join('|'); + } + return prop.type === 'integer' ? 'number' : prop.type; + } + + if (prop.$ref) { + return prop.$ref.split('/').pop(); + } + + if (prop.anyOf) { + return prop.anyOf.map(t => { + if (t.type) return t.type === 'integer' ? 'number' : t.type; + if (t.$ref) return t.$ref.split('/').pop(); + if (t.const) return JSON.stringify(t.const); + if (t.enum) return t.enum.map(v => JSON.stringify(v)).join('|'); + return 'any'; + }).join('|'); + } + + if (prop.enum) { + return prop.enum.map(v => JSON.stringify(v)).join('|'); + } + + if (prop.items) { + const itemType = getJSDocType(prop.items); + return `Array<${itemType}>`; + } + + return 'any'; +} + +// Convert schema to JSDoc +function schemaToJSDoc(schema, fileName) { + const typeName = fileName.replace('.json', ''); + let jsdoc = []; + + // Add file header + jsdoc.push('/**'); + jsdoc.push(` * JSDoc type definitions generated from JSON Schema`); + jsdoc.push(` * Source: ${fileName}`); + jsdoc.push(` * Generated: ${new Date().toISOString()}`); + jsdoc.push(' */'); + jsdoc.push(''); + + // Process main type if referenced + if (schema.$ref) { + const mainType = schema.$ref.split('/').pop(); + const mainDef = schema.definitions[mainType]; + + if (mainDef) { + jsdoc.push('/**'); + jsdoc.push(` * ${mainDef.description || mainType}`); + jsdoc.push(` * @typedef {Object} ${mainType}`); + + if (mainDef.properties) { + Object.entries(mainDef.properties).forEach(([propName, prop]) => { + const type = getJSDocType(prop); + const required = mainDef.required && mainDef.required.includes(propName); + const optionalMark = required ? '' : '['; + const optionalEnd = required ? '' : ']'; + + let description = prop.description || ''; + if (!description && prop.$comment) { + description = prop.$comment; + } + + jsdoc.push(` * @property {${type}} ${optionalMark}${propName}${optionalEnd} ${description ? '- ' + description : ''}`); + }); + } + + jsdoc.push(' */'); + jsdoc.push(''); + } + } + + // Process other definitions + if (schema.definitions) { + Object.entries(schema.definitions).forEach(([name, def]) => { + // Skip if already processed as main type + if (schema.$ref && schema.$ref.endsWith(name)) { + return; + } + + jsdoc.push('/**'); + jsdoc.push(` * ${def.description || name}`); + jsdoc.push(` * @typedef {Object} ${name}`); + + if (def.properties) { + Object.entries(def.properties).forEach(([propName, prop]) => { + const type = getJSDocType(prop); + const required = def.required && def.required.includes(propName); + const optionalMark = required ? '' : '['; + const optionalEnd = required ? '' : ']'; + + let description = prop.description || ''; + if (!description && prop.$comment) { + description = prop.$comment; + } + + jsdoc.push(` * @property {${type}} ${optionalMark}${propName}${optionalEnd} ${description ? '- ' + description : ''}`); + }); + } + + // Handle enums as separate typedef + if (def.enum) { + const enumType = def.enum.map(v => JSON.stringify(v)).join('|'); + jsdoc.push(` * @typedef {${enumType}} ${name}`); + } + + jsdoc.push(' */'); + jsdoc.push(''); + }); + } + + return jsdoc.join('\n'); +} + +// Process all schema files +const schemaFiles = fs.readdirSync(schemasDir).filter(file => file.endsWith('.json')); + +console.log(`Converting ${schemaFiles.length} JSON schemas to JSDoc...`); +console.log(); + +schemaFiles.forEach(file => { + try { + const schemaPath = path.join(schemasDir, file); + const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8')); + + const jsdocContent = schemaToJSDoc(schema, file); + + const outputFile = file.replace('.json', '.js'); + const outputPath = path.join(outputDir, outputFile); + + fs.writeFileSync(outputPath, jsdocContent, 'utf8'); + console.log(`✓ ${file} -> ${outputFile}`); + } catch (error) { + console.error(`✗ Error converting ${file}:`, error.message); + } +}); + +console.log(); +console.log(`JSDoc files generated in ${outputDir}`); \ No newline at end of file diff --git a/scripts/generate-jsdoc-from-schemas.js b/scripts/generate-jsdoc-from-schemas.js new file mode 100755 index 000000000..80459c5b1 --- /dev/null +++ b/scripts/generate-jsdoc-from-schemas.js @@ -0,0 +1,89 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const generate = require('/home/linuxbrew/.linuxbrew/lib/node_modules/json-schema-to-jsdoc'); + +const schemasDir = path.join(__dirname, '../packages/core/docs/api/schemas'); +const outputDir = path.join(__dirname, '../packages/core/docs/api/jsdoc'); + +// Create output directory if it doesn't exist +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); +} + +// JSDoc generation options +const options = { + autoDescribe: true, // Adds auto-generated descriptions + hyphenatedDescriptions: true, // Adds hyphen before property descriptions + capitalizeTitle: true, // Capitalizes titles + indent: 2, // Indentation level + maxLength: 100, // Max line length for descriptions + types: { + object: 'Object', + array: 'Array', + string: 'string', + number: 'number', + boolean: 'boolean', + integer: 'number' + }, + formats: { + 'date-time': 'Date', + 'uri': 'string', + 'email': 'string' + } +}; + +// Process all schema files +const schemaFiles = fs.readdirSync(schemasDir).filter(file => file.endsWith('.json')); + +console.log(`Converting ${schemaFiles.length} JSON schemas to JSDoc with json-schema-to-jsdoc...`); +console.log(); + +let allJSDocs = []; + +schemaFiles.forEach(file => { + try { + const schemaPath = path.join(schemasDir, file); + const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8')); + + // Generate JSDoc for each schema + const jsdocContent = generate(schema, options); + + // Add source file comment + const enhancedJSDoc = `/** + * Generated from JSON Schema: ${file} + * Date: ${new Date().toISOString()} + */ + +${jsdocContent}`; + + // Write individual file + const outputFile = file.replace('.json', '.js'); + const outputPath = path.join(outputDir, outputFile); + + fs.writeFileSync(outputPath, enhancedJSDoc, 'utf8'); + console.log(`✓ ${file} -> ${outputFile}`); + + // Also collect for combined file + allJSDocs.push(`// === ${file} ===\n${enhancedJSDoc}`); + } catch (error) { + console.error(`✗ Error converting ${file}:`, error.message); + } +}); + +// Create a combined file with all typedefs +const combinedPath = path.join(outputDir, 'all-types.js'); +const combinedContent = `/** + * OpenTUI Complete Type Definitions + * Generated from JSON Schemas + * Date: ${new Date().toISOString()} + */ + +${allJSDocs.join('\n\n')}`; + +fs.writeFileSync(combinedPath, combinedContent, 'utf8'); + +console.log(); +console.log(`JSDoc files generated in ${outputDir}`); +console.log(`Combined typedef file: all-types.js`); \ No newline at end of file diff --git a/scripts/jsdoc-from-schemas.js b/scripts/jsdoc-from-schemas.js new file mode 100755 index 000000000..b258e2bd4 --- /dev/null +++ b/scripts/jsdoc-from-schemas.js @@ -0,0 +1,127 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const jsdoc = require('/home/linuxbrew/.linuxbrew/lib/node_modules/json-schema-to-jsdoc'); + +const schemasDir = path.join(__dirname, '../packages/core/docs/api/schemas'); +const outputDir = path.join(__dirname, '../packages/core/docs/api/jsdoc'); + +// Create output directory if it doesn't exist +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); +} + +// Process all schema files +const schemaFiles = fs.readdirSync(schemasDir).filter(file => file.endsWith('.json')); + +console.log(`Converting ${schemaFiles.length} JSON schemas to JSDoc...`); +console.log(); + +let allJSDocs = []; + +schemaFiles.forEach(file => { + try { + const schemaPath = path.join(schemasDir, file); + const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8')); + + let jsdocContent = ''; + + // If schema has $ref, extract the main definition + if (schema.$ref && schema.definitions) { + const typeName = schema.$ref.split('/').pop(); + const mainSchema = schema.definitions[typeName]; + + if (mainSchema) { + // Add title if not present + if (!mainSchema.title) { + mainSchema.title = typeName; + } + + // Generate JSDoc for main type + try { + const mainJSDoc = jsdoc(mainSchema, { + autoDescribe: true, + hyphenatedDescriptions: true, + capitalizeTitle: true, + indent: 2 + }); + jsdocContent += mainJSDoc + '\n\n'; + } catch (e) { + console.log(` Note: Could not generate JSDoc for ${typeName}: ${e.message}`); + } + } + + // Generate JSDoc for other definitions + Object.entries(schema.definitions).forEach(([name, def]) => { + if (name !== typeName) { + if (!def.title) { + def.title = name; + } + try { + const defJSDoc = jsdoc(def, { + autoDescribe: true, + hyphenatedDescriptions: true, + capitalizeTitle: true, + indent: 2 + }); + jsdocContent += defJSDoc + '\n\n'; + } catch (e) { + // Skip definitions that can't be converted + } + } + }); + } else { + // Direct schema without $ref + jsdocContent = jsdoc(schema, { + autoDescribe: true, + hyphenatedDescriptions: true, + capitalizeTitle: true, + indent: 2 + }); + } + + if (jsdocContent.trim()) { + // Add header + const fullContent = `/** + * Generated from: ${file} + * Date: ${new Date().toISOString()} + */ + +${jsdocContent}`; + + // Write individual file + const outputFile = file.replace('.json', '.js'); + const outputPath = path.join(outputDir, outputFile); + + fs.writeFileSync(outputPath, fullContent, 'utf8'); + console.log(`✓ ${file} -> ${outputFile}`); + + // Collect for combined file + allJSDocs.push(fullContent); + } else { + console.log(`⚠ ${file} - no JSDoc generated`); + } + } catch (error) { + console.error(`✗ Error processing ${file}:`, error.message); + } +}); + +// Create combined file +if (allJSDocs.length > 0) { + const combinedPath = path.join(outputDir, 'all-types.js'); + const combinedContent = `/** + * OpenTUI Complete Type Definitions + * Generated from JSON Schemas + * Date: ${new Date().toISOString()} + */ + +${allJSDocs.join('\n\n')}`; + + fs.writeFileSync(combinedPath, combinedContent, 'utf8'); + console.log(); + console.log(`Combined typedef file created: all-types.js`); +} + +console.log(); +console.log(`JSDoc files generated in ${outputDir}`); \ No newline at end of file diff --git a/scripts/schemas-to-jsdoc.js b/scripts/schemas-to-jsdoc.js new file mode 100755 index 000000000..839f8f926 --- /dev/null +++ b/scripts/schemas-to-jsdoc.js @@ -0,0 +1,40 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const jsdoc = require('json-schema-to-jsdoc'); + +const schemasDir = path.join(__dirname, '../packages/core/docs/api/schemas'); +const outputDir = path.join(__dirname, '../packages/core/docs/api/jsdoc'); + +// Create output directory if it doesn't exist +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); +} + +// Get all JSON schema files +const schemaFiles = fs.readdirSync(schemasDir).filter(file => file.endsWith('.json')); + +console.log(`Converting ${schemaFiles.length} JSON schemas to JSDoc...`); + +schemaFiles.forEach(file => { + const schemaPath = path.join(schemasDir, file); + const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8')); + + try { + // Convert schema to JSDoc + const jsdocComment = jsdoc(schema); + + // Create output filename + const outputFile = file.replace('.json', '.js'); + const outputPath = path.join(outputDir, outputFile); + + // Write JSDoc to file + fs.writeFileSync(outputPath, jsdocComment, 'utf8'); + console.log(`✓ Converted ${file} -> ${outputFile}`); + } catch (error) { + console.error(`✗ Error converting ${file}:`, error.message); + } +}); + +console.log(`\nJSDoc files generated in ${outputDir}`); \ No newline at end of file From 58692dd912cd80c71eee88bac8a48b59f101d55a Mon Sep 17 00:00:00 2001 From: entrepeneur4lyf Date: Fri, 22 Aug 2025 04:48:22 -0400 Subject: [PATCH 5/6] Regenerate API docs: replace old pages with reference Regenerate API documentation output: remove legacy topic pages under packages/core/docs/api and add a consolidated reference tree (packages/core/docs/api/reference/*), a new api-summary.md and machine-readable extracted-api.json. Update index.md to reflect the new layout. Add .vscode snippets/settings and update .gitignore. Switch to the new API reference generation format and clean up obsolete docs. --- .vscode/opentui.code-snippets | 219 ++ .vscode/settings.json | 73 + packages/core/docs/api/3d/canvas.md | 126 - .../docs/api/3d/exploding-sprite-effect.md | 120 - .../api/3d/physics-exploding-sprite-effect.md | 122 - packages/core/docs/api/3d/physics.md | 454 ---- packages/core/docs/api/3d/shaders.md | 874 ------- packages/core/docs/api/3d/sprite-animation.md | 518 ---- packages/core/docs/api/3d/sprite-animator.md | 115 - packages/core/docs/api/3d/sprites.md | 192 -- packages/core/docs/api/3d/webgpu.md | 452 ---- packages/core/docs/api/3d/wgpu-renderer.md | 161 -- packages/core/docs/api/README.md | 121 - packages/core/docs/api/advanced/3d.md | 854 ------- packages/core/docs/api/animation/animation.md | 761 ------ packages/core/docs/api/animation/timeline.md | 356 --- packages/core/docs/api/api-summary.md | 427 ++++ packages/core/docs/api/buffer.md | 159 -- .../core/docs/api/components/ascii-font.md | 399 --- packages/core/docs/api/components/box.md | 270 -- .../core/docs/api/components/framebuffer.md | 394 --- packages/core/docs/api/components/group.md | 358 --- packages/core/docs/api/components/input.md | 289 --- .../core/docs/api/components/renderables.md | 592 ----- packages/core/docs/api/components/select.md | 425 ---- .../core/docs/api/components/tab-select.md | 454 ---- packages/core/docs/api/components/text.md | 295 --- packages/core/docs/api/core/rendering.md | 585 ----- packages/core/docs/api/extracted-api.json | 2262 +++++++++++++++++ packages/core/docs/api/index.md | 322 +-- packages/core/docs/api/input/input.md | 742 ------ .../core/docs/api/jsdoc/ASCIIFontOptions.js | 81 - .../core/docs/api/jsdoc/AnimationOptions.js | 21 - packages/core/docs/api/jsdoc/BorderConfig.js | 53 - .../core/docs/api/jsdoc/BoxDrawOptions.js | 53 - packages/core/docs/api/jsdoc/BoxOptions.js | 116 - .../core/docs/api/jsdoc/CliRendererConfig.js | 54 - .../core/docs/api/jsdoc/ConsoleOptions.js | 30 - .../api/jsdoc/ExplosionEffectParameters.js | 17 - .../core/docs/api/jsdoc/FrameBufferOptions.js | 68 - .../docs/api/jsdoc/InputRenderableOptions.js | 87 - packages/core/docs/api/jsdoc/LayoutOptions.js | 29 - .../core/docs/api/jsdoc/RenderableOptions.js | 68 - .../docs/api/jsdoc/SelectRenderableOptions.js | 96 - .../api/jsdoc/TabSelectRenderableOptions.js | 96 - packages/core/docs/api/jsdoc/TextOptions.js | 88 - .../docs/api/jsdoc/ThreeCliRendererOptions.js | 24 - .../core/docs/api/jsdoc/TimelineOptions.js | 16 - packages/core/docs/api/jsdoc/all-types.js | 1035 -------- packages/core/docs/api/lib/border.md | 489 ---- .../core/docs/api/lib/hast-styled-text.md | 481 ---- packages/core/docs/api/lib/keyhandler.md | 432 ---- packages/core/docs/api/lib/selection.md | 284 --- packages/core/docs/api/lib/styled-text.md | 393 --- packages/core/docs/api/lib/tracked-node.md | 246 -- packages/core/docs/api/native-integration.md | 144 -- packages/core/docs/api/post/filters.md | 420 --- packages/core/docs/api/react/reconciler.md | 601 ----- packages/core/docs/api/reference/README.md | 10 + .../reference/classes/ASCIIFontRenderable.md | 265 ++ .../api/reference/classes/BoxRenderable.md | 344 +++ .../docs/api/reference/classes/CliRenderer.md | 486 ++++ .../api/reference/classes/InputRenderable.md | 311 +++ .../docs/api/reference/classes/MouseEvent.md | 109 + .../api/reference/classes/OptimizedBuffer.md | 511 ++++ .../docs/api/reference/classes/Renderable.md | 392 +++ .../api/reference/classes/RootRenderable.md | 59 + .../api/reference/classes/TextRenderable.md | 228 ++ .../docs/api/reference/classes/Timeline.md | 225 ++ packages/core/docs/api/reference/index.md | 43 + .../reference/interfaces/ASCIIFontOptions.md | 222 ++ .../reference/interfaces/AnimationOptions.md | 54 + .../api/reference/interfaces/BoxOptions.md | 305 +++ .../reference/interfaces/CliRendererConfig.md | 70 + .../interfaces/InputRenderableOptions.md | 230 ++ .../api/reference/interfaces/JSAnimation.md | 22 + .../api/reference/interfaces/LayoutOptions.md | 110 + .../docs/api/reference/interfaces/Position.md | 22 + .../reference/interfaces/RenderableOptions.md | 194 ++ .../api/reference/interfaces/RootContext.md | 14 + .../api/reference/interfaces/TextOptions.md | 222 ++ .../reference/interfaces/TimelineOptions.md | 30 + .../docs/api/reference/types/BorderConfig.md | 27 + .../docs/api/reference/types/BorderStyle.md | 71 + .../api/reference/types/BoxDrawOptions.md | 37 + .../api/reference/types/ConsoleOptions.md | 36 + .../types/ExplosionEffectParameters.md | 35 + .../api/reference/types/FrameBufferOptions.md | 69 + .../api/reference/types/MouseEventType.md | 118 + .../api/reference/types/Renderable.class.md | 22 + .../types/SelectRenderableOptions.md | 86 + .../types/TabSelectRenderableOptions.md | 84 + .../types/ThreeCliRendererOptions.md | 27 + .../core/docs/api/renderables/ascii-font.md | 515 ---- .../docs/api/schemas/Renderable.class.json | 37 + .../core/docs/api/styling/text-styling.md | 451 ---- packages/core/docs/api/utils/console.md | 464 ---- .../core/docs/api/utils/output-capture.md | 428 ---- packages/core/docs/api/utils/utilities.md | 733 ------ packages/core/docs/modules/3d.md | 499 ++++ packages/core/docs/modules/animation.md | 664 +++++ packages/core/docs/modules/buffer.md | 335 +++ packages/core/docs/modules/components.md | 252 ++ packages/core/docs/modules/console.md | 374 +++ packages/core/docs/modules/events.md | 332 +++ packages/core/docs/modules/filters.md | 293 +++ packages/core/docs/modules/guide.md | 91 + packages/core/docs/modules/index.md | 134 + packages/core/docs/modules/layout.md | 243 ++ packages/core/docs/modules/lib.md | 258 ++ packages/core/docs/modules/rendering.md | 713 ++++++ packages/core/docs/modules/text-buffer.md | 158 ++ packages/core/docs/modules/types.md | 408 +++ packages/core/docs/modules/utils.md | 331 +++ packages/core/docs/modules/zig.md | 428 ++++ packages/core/src/intellisense.d.ts | 346 +++ packages/core/src/types/ASCIIFontOptions.d.ts | 136 + packages/core/src/types/AnimationOptions.d.ts | 40 + packages/core/src/types/BorderConfig.d.ts | 16 + packages/core/src/types/BoxDrawOptions.d.ts | 32 + packages/core/src/types/BoxOptions.d.ts | 140 + .../core/src/types/CliRendererConfig.d.ts | 40 + packages/core/src/types/ConsoleOptions.d.ts | 40 + .../src/types/ExplosionEffectParameters.d.ts | 37 + .../core/src/types/FrameBufferOptions.d.ts | 124 + .../src/types/InputRenderableOptions.d.ts | 140 + packages/core/src/types/LayoutOptions.d.ts | 60 + .../core/src/types/RenderableOptions.d.ts | 122 + .../src/types/SelectRenderableOptions.d.ts | 152 ++ .../src/types/TabSelectRenderableOptions.d.ts | 148 ++ packages/core/src/types/TextOptions.d.ts | 136 + .../src/types/ThreeCliRendererOptions.d.ts | 24 + packages/core/src/types/TimelineOptions.d.ts | 24 + packages/core/src/types/index.d.ts | 78 + packages/core/src/types/package.json | 5 + scripts/convert-schema-to-jsdoc.js | 88 - scripts/generate-api-jsdoc.js | 159 -- scripts/generate-api-schemas.sh | 39 - scripts/generate-jsdoc-from-schemas.js | 89 - scripts/jsdoc-from-schemas.js | 127 - scripts/jsdoc-to-tsdoc.js | 158 ++ scripts/schemas-to-jsdoc.js | 40 - scripts/schemas-to-tsdoc.cjs | 226 ++ tsconfig.ide.json | 67 + 144 files changed, 16032 insertions(+), 19045 deletions(-) create mode 100644 .vscode/opentui.code-snippets create mode 100644 .vscode/settings.json delete mode 100644 packages/core/docs/api/3d/canvas.md delete mode 100644 packages/core/docs/api/3d/exploding-sprite-effect.md delete mode 100644 packages/core/docs/api/3d/physics-exploding-sprite-effect.md delete mode 100644 packages/core/docs/api/3d/physics.md delete mode 100644 packages/core/docs/api/3d/shaders.md delete mode 100644 packages/core/docs/api/3d/sprite-animation.md delete mode 100644 packages/core/docs/api/3d/sprite-animator.md delete mode 100644 packages/core/docs/api/3d/sprites.md delete mode 100644 packages/core/docs/api/3d/webgpu.md delete mode 100644 packages/core/docs/api/3d/wgpu-renderer.md delete mode 100644 packages/core/docs/api/README.md delete mode 100644 packages/core/docs/api/advanced/3d.md delete mode 100644 packages/core/docs/api/animation/animation.md delete mode 100644 packages/core/docs/api/animation/timeline.md create mode 100644 packages/core/docs/api/api-summary.md delete mode 100644 packages/core/docs/api/buffer.md delete mode 100644 packages/core/docs/api/components/ascii-font.md delete mode 100644 packages/core/docs/api/components/box.md delete mode 100644 packages/core/docs/api/components/framebuffer.md delete mode 100644 packages/core/docs/api/components/group.md delete mode 100644 packages/core/docs/api/components/input.md delete mode 100644 packages/core/docs/api/components/renderables.md delete mode 100644 packages/core/docs/api/components/select.md delete mode 100644 packages/core/docs/api/components/tab-select.md delete mode 100644 packages/core/docs/api/components/text.md delete mode 100644 packages/core/docs/api/core/rendering.md create mode 100644 packages/core/docs/api/extracted-api.json delete mode 100644 packages/core/docs/api/input/input.md delete mode 100644 packages/core/docs/api/jsdoc/ASCIIFontOptions.js delete mode 100644 packages/core/docs/api/jsdoc/AnimationOptions.js delete mode 100644 packages/core/docs/api/jsdoc/BorderConfig.js delete mode 100644 packages/core/docs/api/jsdoc/BoxDrawOptions.js delete mode 100644 packages/core/docs/api/jsdoc/BoxOptions.js delete mode 100644 packages/core/docs/api/jsdoc/CliRendererConfig.js delete mode 100644 packages/core/docs/api/jsdoc/ConsoleOptions.js delete mode 100644 packages/core/docs/api/jsdoc/ExplosionEffectParameters.js delete mode 100644 packages/core/docs/api/jsdoc/FrameBufferOptions.js delete mode 100644 packages/core/docs/api/jsdoc/InputRenderableOptions.js delete mode 100644 packages/core/docs/api/jsdoc/LayoutOptions.js delete mode 100644 packages/core/docs/api/jsdoc/RenderableOptions.js delete mode 100644 packages/core/docs/api/jsdoc/SelectRenderableOptions.js delete mode 100644 packages/core/docs/api/jsdoc/TabSelectRenderableOptions.js delete mode 100644 packages/core/docs/api/jsdoc/TextOptions.js delete mode 100644 packages/core/docs/api/jsdoc/ThreeCliRendererOptions.js delete mode 100644 packages/core/docs/api/jsdoc/TimelineOptions.js delete mode 100644 packages/core/docs/api/jsdoc/all-types.js delete mode 100644 packages/core/docs/api/lib/border.md delete mode 100644 packages/core/docs/api/lib/hast-styled-text.md delete mode 100644 packages/core/docs/api/lib/keyhandler.md delete mode 100644 packages/core/docs/api/lib/selection.md delete mode 100644 packages/core/docs/api/lib/styled-text.md delete mode 100644 packages/core/docs/api/lib/tracked-node.md delete mode 100644 packages/core/docs/api/native-integration.md delete mode 100644 packages/core/docs/api/post/filters.md delete mode 100644 packages/core/docs/api/react/reconciler.md create mode 100644 packages/core/docs/api/reference/README.md create mode 100644 packages/core/docs/api/reference/classes/ASCIIFontRenderable.md create mode 100644 packages/core/docs/api/reference/classes/BoxRenderable.md create mode 100644 packages/core/docs/api/reference/classes/CliRenderer.md create mode 100644 packages/core/docs/api/reference/classes/InputRenderable.md create mode 100644 packages/core/docs/api/reference/classes/MouseEvent.md create mode 100644 packages/core/docs/api/reference/classes/OptimizedBuffer.md create mode 100644 packages/core/docs/api/reference/classes/Renderable.md create mode 100644 packages/core/docs/api/reference/classes/RootRenderable.md create mode 100644 packages/core/docs/api/reference/classes/TextRenderable.md create mode 100644 packages/core/docs/api/reference/classes/Timeline.md create mode 100644 packages/core/docs/api/reference/index.md create mode 100644 packages/core/docs/api/reference/interfaces/ASCIIFontOptions.md create mode 100644 packages/core/docs/api/reference/interfaces/AnimationOptions.md create mode 100644 packages/core/docs/api/reference/interfaces/BoxOptions.md create mode 100644 packages/core/docs/api/reference/interfaces/CliRendererConfig.md create mode 100644 packages/core/docs/api/reference/interfaces/InputRenderableOptions.md create mode 100644 packages/core/docs/api/reference/interfaces/JSAnimation.md create mode 100644 packages/core/docs/api/reference/interfaces/LayoutOptions.md create mode 100644 packages/core/docs/api/reference/interfaces/Position.md create mode 100644 packages/core/docs/api/reference/interfaces/RenderableOptions.md create mode 100644 packages/core/docs/api/reference/interfaces/RootContext.md create mode 100644 packages/core/docs/api/reference/interfaces/TextOptions.md create mode 100644 packages/core/docs/api/reference/interfaces/TimelineOptions.md create mode 100644 packages/core/docs/api/reference/types/BorderConfig.md create mode 100644 packages/core/docs/api/reference/types/BorderStyle.md create mode 100644 packages/core/docs/api/reference/types/BoxDrawOptions.md create mode 100644 packages/core/docs/api/reference/types/ConsoleOptions.md create mode 100644 packages/core/docs/api/reference/types/ExplosionEffectParameters.md create mode 100644 packages/core/docs/api/reference/types/FrameBufferOptions.md create mode 100644 packages/core/docs/api/reference/types/MouseEventType.md create mode 100644 packages/core/docs/api/reference/types/Renderable.class.md create mode 100644 packages/core/docs/api/reference/types/SelectRenderableOptions.md create mode 100644 packages/core/docs/api/reference/types/TabSelectRenderableOptions.md create mode 100644 packages/core/docs/api/reference/types/ThreeCliRendererOptions.md delete mode 100644 packages/core/docs/api/renderables/ascii-font.md create mode 100644 packages/core/docs/api/schemas/Renderable.class.json delete mode 100644 packages/core/docs/api/styling/text-styling.md delete mode 100644 packages/core/docs/api/utils/console.md delete mode 100644 packages/core/docs/api/utils/output-capture.md delete mode 100644 packages/core/docs/api/utils/utilities.md create mode 100644 packages/core/docs/modules/3d.md create mode 100644 packages/core/docs/modules/animation.md create mode 100644 packages/core/docs/modules/buffer.md create mode 100644 packages/core/docs/modules/components.md create mode 100644 packages/core/docs/modules/console.md create mode 100644 packages/core/docs/modules/events.md create mode 100644 packages/core/docs/modules/filters.md create mode 100644 packages/core/docs/modules/guide.md create mode 100644 packages/core/docs/modules/index.md create mode 100644 packages/core/docs/modules/layout.md create mode 100644 packages/core/docs/modules/lib.md create mode 100644 packages/core/docs/modules/rendering.md create mode 100644 packages/core/docs/modules/text-buffer.md create mode 100644 packages/core/docs/modules/types.md create mode 100644 packages/core/docs/modules/utils.md create mode 100644 packages/core/docs/modules/zig.md create mode 100644 packages/core/src/intellisense.d.ts create mode 100644 packages/core/src/types/ASCIIFontOptions.d.ts create mode 100644 packages/core/src/types/AnimationOptions.d.ts create mode 100644 packages/core/src/types/BorderConfig.d.ts create mode 100644 packages/core/src/types/BoxDrawOptions.d.ts create mode 100644 packages/core/src/types/BoxOptions.d.ts create mode 100644 packages/core/src/types/CliRendererConfig.d.ts create mode 100644 packages/core/src/types/ConsoleOptions.d.ts create mode 100644 packages/core/src/types/ExplosionEffectParameters.d.ts create mode 100644 packages/core/src/types/FrameBufferOptions.d.ts create mode 100644 packages/core/src/types/InputRenderableOptions.d.ts create mode 100644 packages/core/src/types/LayoutOptions.d.ts create mode 100644 packages/core/src/types/RenderableOptions.d.ts create mode 100644 packages/core/src/types/SelectRenderableOptions.d.ts create mode 100644 packages/core/src/types/TabSelectRenderableOptions.d.ts create mode 100644 packages/core/src/types/TextOptions.d.ts create mode 100644 packages/core/src/types/ThreeCliRendererOptions.d.ts create mode 100644 packages/core/src/types/TimelineOptions.d.ts create mode 100644 packages/core/src/types/index.d.ts create mode 100644 packages/core/src/types/package.json delete mode 100644 scripts/convert-schema-to-jsdoc.js delete mode 100755 scripts/generate-api-jsdoc.js delete mode 100755 scripts/generate-api-schemas.sh delete mode 100755 scripts/generate-jsdoc-from-schemas.js delete mode 100755 scripts/jsdoc-from-schemas.js create mode 100755 scripts/jsdoc-to-tsdoc.js delete mode 100755 scripts/schemas-to-jsdoc.js create mode 100755 scripts/schemas-to-tsdoc.cjs create mode 100644 tsconfig.ide.json diff --git a/.vscode/opentui.code-snippets b/.vscode/opentui.code-snippets new file mode 100644 index 000000000..65d30248b --- /dev/null +++ b/.vscode/opentui.code-snippets @@ -0,0 +1,219 @@ +{ + // OpenTUI Component Snippets + + "OpenTUI Box Component": { + "prefix": "tui-box", + "body": [ + "const ${1:box} = new BoxRenderable('${2:id}', {", + " width: ${3:'100%'},", + " height: ${4:10},", + " border: ${5:true},", + " borderStyle: '${6|single,double,rounded,heavy|}',", + " padding: ${7:1}", + "});", + "$0" + ], + "description": "Create a BoxRenderable component" + }, + + "OpenTUI Text Component": { + "prefix": "tui-text", + "body": [ + "const ${1:text} = new TextRenderable('${2:id}', {", + " text: '${3:Hello World}',", + " color: ${4:'#ffffff'},", + " align: '${5|left,center,right|}',", + " wrap: ${6:true}", + "});", + "$0" + ], + "description": "Create a TextRenderable component" + }, + + "OpenTUI Input Component": { + "prefix": "tui-input", + "body": [ + "const ${1:input} = new InputRenderable('${2:id}', {", + " placeholder: '${3:Enter text...}',", + " value: '${4:}',", + " maxLength: ${5:50},", + " onChange: (value) => {", + " ${6:console.log(value);}", + " },", + " onSubmit: (value) => {", + " ${7:// Handle submit}", + " }", + "});", + "$0" + ], + "description": "Create an InputRenderable component" + }, + + "OpenTUI ASCII Font": { + "prefix": "tui-ascii", + "body": [ + "const ${1:title} = new ASCIIFontRenderable('${2:id}', {", + " text: '${3:TITLE}',", + " font: '${4|default,bulky,chrome,huge|}',", + " color: '${5:#00ff00}',", + " align: '${6|left,center,right|}'", + "});", + "$0" + ], + "description": "Create ASCII art text" + }, + + "OpenTUI Application": { + "prefix": "tui-app", + "body": [ + "import { CliRenderer, BoxRenderable, TextRenderable } from '@opentui/core';", + "", + "// Create renderer", + "const renderer = new CliRenderer(", + " lib,", + " rendererPtr,", + " process.stdin,", + " process.stdout,", + " ${1:80}, // width", + " ${2:24}, // height", + " {", + " backgroundColor: '${3:#1e1e1e}'", + " }", + ");", + "", + "// Create main container", + "const mainBox = new BoxRenderable('main', {", + " width: '100%',", + " height: '100%',", + " border: true,", + " borderStyle: 'rounded',", + " padding: 2", + "});", + "", + "// Add components", + "${4:// Your components here}", + "", + "// Build tree", + "renderer.root.add(mainBox, 0);", + "", + "// Start rendering", + "renderer.start();", + "$0" + ], + "description": "Create a complete OpenTUI application" + }, + + "OpenTUI Flexbox Layout": { + "prefix": "tui-flex", + "body": [ + "const ${1:container} = new BoxRenderable('${2:flex-container}', {", + " width: '100%',", + " height: '100%',", + " flexDirection: '${3|row,column,row-reverse,column-reverse|}',", + " justifyContent: '${4|flex-start,flex-end,center,space-between,space-around,space-evenly|}',", + " alignItems: '${5|flex-start,flex-end,center,stretch,baseline|}',", + " gap: ${6:1}", + "});", + "$0" + ], + "description": "Create a flexbox container" + }, + + "OpenTUI Animation Timeline": { + "prefix": "tui-timeline", + "body": [ + "const timeline = new Timeline({", + " duration: ${1:1000},", + " loop: ${2:false},", + " autoplay: ${3:true}", + "});", + "", + "timeline.add({", + " target: ${4:component},", + " properties: {", + " ${5:x}: { from: ${6:0}, to: ${7:100} },", + " ${8:opacity}: { from: ${9:0}, to: ${10:1} }", + " },", + " duration: ${11:500},", + " easing: '${12|linear,easeInQuad,easeOutQuad,easeInOutQuad,easeInCubic,easeOutCubic,easeInOutCubic|}'", + "});", + "$0" + ], + "description": "Create an animation timeline" + }, + + "OpenTUI Event Handler": { + "prefix": "tui-event", + "body": [ + "on${1|MouseDown,MouseUp,MouseMove,MouseDrag,MouseScroll,KeyDown,KeyUp|}: (${2:event}) => {", + " ${3:// Handle event}", + " ${4:return true; // Prevent bubbling}", + "}" + ], + "description": "Add an event handler" + }, + + "OpenTUI Custom Component": { + "prefix": "tui-component", + "body": [ + "class ${1:MyComponent} extends ${2|Renderable,BoxRenderable,TextRenderable|} {", + " constructor(id: string, options: ${3:RenderableOptions}) {", + " super(id, options);", + " ${4:// Initialize}", + " }", + "", + " protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void {", + " super.renderSelf(buffer, deltaTime);", + " ${5:// Custom rendering}", + " }", + "", + " handleKeyPress(key: ParsedKey | string): boolean {", + " ${6:// Handle keyboard input}", + " return super.handleKeyPress(key);", + " }", + "}", + "$0" + ], + "description": "Create a custom OpenTUI component" + }, + + "OpenTUI Form": { + "prefix": "tui-form", + "body": [ + "class ${1:LoginForm} extends BoxRenderable {", + " private ${2:username}Input: InputRenderable;", + " private ${3:password}Input: InputRenderable;", + "", + " constructor() {", + " super('${4:form}', {", + " flexDirection: 'column',", + " gap: 1,", + " padding: 2,", + " border: true,", + " title: '${5:Form Title}'", + " });", + "", + " this.${2}Input = new InputRenderable('${2}', {", + " placeholder: '${6:Username}',", + " onSubmit: () => this.${3}Input.focus()", + " });", + "", + " this.${3}Input = new InputRenderable('${3}', {", + " placeholder: '${7:Password}',", + " password: true,", + " onSubmit: () => this.submit()", + " });", + "", + " this.add(this.${2}Input, 0);", + " this.add(this.${3}Input, 1);", + " }", + "", + " private submit() {", + " ${8:// Handle form submission}", + " }", + "}", + "$0" + ], + "description": "Create a form with inputs" + } +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..aec8880f8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,73 @@ +{ + // TypeScript & JavaScript + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true, + "typescript.preferences.includePackageJsonAutoImports": "on", + "typescript.preferences.quoteStyle": "single", + "typescript.suggest.paths": true, + "typescript.suggest.completeFunctionCalls": true, + "typescript.updateImportsOnFileMove.enabled": "always", + "typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces": true, + // IntelliSense Enhancement + "editor.quickSuggestions": { + "strings": true, + "comments": false, + "other": true + }, + "editor.suggest.snippetsPreventQuickSuggestions": false, + "editor.suggest.showMethods": true, + "editor.suggest.showFunctions": true, + "editor.suggest.showConstructors": true, + "editor.suggest.showFields": true, + "editor.suggest.showVariables": true, + "editor.suggest.showClasses": true, + "editor.suggest.showStructs": true, + "editor.suggest.showInterfaces": true, + "editor.suggest.showModules": true, + "editor.suggest.showProperties": true, + "editor.suggest.showEvents": true, + "editor.suggest.showOperators": true, + "editor.suggest.showUnits": true, + "editor.suggest.showValues": true, + "editor.suggest.showConstants": true, + "editor.suggest.showEnums": true, + "editor.suggest.showEnumMembers": true, + "editor.suggest.showKeywords": true, + "editor.suggest.showWords": true, + "editor.suggest.showColors": true, + "editor.suggest.showFiles": true, + "editor.suggest.showReferences": true, + "editor.suggest.showCustomcolors": true, + "editor.suggest.showFolders": true, + "editor.suggest.showTypeParameters": true, + "editor.suggest.showSnippets": true, + "editor.suggest.showIssues": true, + "editor.suggest.showUsers": true, + // Auto Import + "javascript.suggest.autoImports": true, + "typescript.suggest.autoImports": true, + "javascript.updateImportsOnFileMove.enabled": "always", + // Code Actions + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit", + "source.fixAll.eslint": "explicit" + }, + // File Associations + "files.associations": { + "*.tsx": "typescriptreact", + "*.ts": "typescript", + "*.jsx": "javascriptreact", + "*.js": "javascript" + }, + // OpenTUI Specific + "typescript.preferences.importModuleSpecifier": "non-relative", + // Prettier + "editor.formatOnSave": true, + // Search Exclusions + "search.exclude": { + "**/node_modules": true, + "**/dist": true, + "**/build": true, + "**/.git": true + } +} diff --git a/packages/core/docs/api/3d/canvas.md b/packages/core/docs/api/3d/canvas.md deleted file mode 100644 index 7ec51317b..000000000 --- a/packages/core/docs/api/3d/canvas.md +++ /dev/null @@ -1,126 +0,0 @@ -# CLICanvas (3D Canvas API) - -This document describes the terminal/CLI canvas used by the 3D/WebGPU rendering subsystem. Implementation reference: `packages/core/src/3d/canvas.ts`. - -CLICanvas is a lightweight, testable canvas abstraction used by the WGPURenderer to provide a GPU-backed drawing surface and to read pixels back into OpenTUI buffers for terminal rendering. - -Important types -- GPUDevice: WebGPU device (via bun-webgpu / platform WebGPU) -- OptimizedBuffer: OpenTUI optimized framebuffer for terminal cells (see buffer.md) -- SuperSampleType: enum exported by `WGPURenderer` — controls supersampling mode -- SuperSampleAlgorithm: enum defined in this module (STANDARD | PRE_SQUEEZED) - -## SuperSampleAlgorithm - -```ts -export enum SuperSampleAlgorithm { - STANDARD = 0, - PRE_SQUEEZED = 1, -} -``` - -Use this enum to choose the compute shader algorithm for GPU supersampling. - -## Class: CLICanvas - -Constructor -```ts -new CLICanvas( - device: GPUDevice, - width: number, - height: number, - superSample: SuperSampleType, - sampleAlgo: SuperSampleAlgorithm = SuperSampleAlgorithm.STANDARD -) -``` -- device: the WebGPU device used for creating buffers, pipelines and submitting commands. -- width/height: render dimensions (in pixels). -- superSample: initial SuperSampleType used by WGPURenderer (NONE | CPU | GPU or similar defined in WGPURenderer). -- sampleAlgo: choose a supersampling algorithm. - -Primary properties -- width: number — current render width (pixels) -- height: number — current render height (pixels) -- superSample: SuperSampleType — current supersample mode -- superSampleAlgorithm: SuperSampleAlgorithm — selected compute algorithm -- superSampleDrawTimeMs: number — measured time spent drawing supersampled output -- mapAsyncTimeMs: number — measured time spent mapping GPU readback buffers -- (internal) computePipeline, computeBindGroupLayout, computeOutputBuffer, computeReadbackBuffer, etc. - -Public methods - -- setSuperSampleAlgorithm(superSampleAlgorithm: SuperSampleAlgorithm): void - - Switch the compute shader algorithm; updates internal state and schedules buffer updates. - -- getSuperSampleAlgorithm(): SuperSampleAlgorithm - -- getContext(type: string, attrs?: WebGLContextAttributes) - - Supported type: `"webgpu"`. - - When `"webgpu"` is requested, CLICanvas prepares GPU readback / compute buffers and returns a GPUCanvasContext (here a GPUCanvasContextMock). - - Throws for other `type` values. - -- setSize(width: number, height: number): void - - Resize the internal canvas/context and readback buffers. Also schedules compute buffer updates. - -- setSuperSample(superSample: SuperSampleType): void - - Change supersampling mode (NONE / CPU / GPU / ...). - -- async saveToFile(filePath: string): Promise - - Capture the current texture, copy it to a GPU buffer, map it, and write an image file. - - Handles row padding and BGRA vs RGBA formats. - - Uses `jimp` to produce an image file (path must include extension). - - Useful for debugging or saving screenshots of the GPU render output. - -- async readPixelsIntoBuffer(buffer: OptimizedBuffer): Promise - - Read pixels from the current texture into the provided OptimizedBuffer. - - Behavior depends on `superSample`: - - `SuperSampleType.GPU`: runs compute shader supersampling and then unpacks compute output into the OptimizedBuffer via `buffer.drawPackedBuffer(...)`. - - `SuperSampleType.CPU`: uses the readback buffer and calls `buffer.drawSuperSampleBuffer(...)`. - - Otherwise: maps the readback buffer and converts pixel bytes into RGBA floats and writes them into `buffer` by calling `buffer.setCell(...)` per cell. - - Handles BGRA vs RGBA formats and aligned bytes-per-row when mapping GPU readback buffers. - -Notes on compute pipeline and buffers -- CLICanvas builds a compute pipeline for supersampling: - - `initComputePipeline()` creates shader module, bind group layout and compute pipeline. - - `updateComputeParams()` writes a uniform buffer containing width/height/algorithm. - - `updateComputeBuffers(width,height)` allocates storage and readback buffers sized to match the compute shader's output layout (must match WGSL shader). - - `runComputeShaderSuperSampling(texture, buffer)` dispatches the compute shader, copies compute output to readback buffer, maps it, and then calls `buffer.drawPackedBuffer(...)` with the mapped pointer. - -Performance and alignment -- The code carefully computes `alignedBytesPerRow` (ceil to 256) when copying textures to GPU buffers — this is required by many GPU APIs. -- The compute output packing uses a specific cell byte layout (48 bytes per cell in the implementation). This must exactly match the WGSL shader layout in `shaders/supersampling.wgsl`. - -Example: Capture and store the GPU render into an OptimizedBuffer -```ts -// device and renderer provided by WGPURenderer -const canvas = new CLICanvas(device, width, height, superSampleType, SuperSampleAlgorithm.STANDARD); -const optimized = OptimizedBuffer.create(width, height, { respectAlpha: false }); - -// After a frame is presented by your WebGPU code: -await canvas.readPixelsIntoBuffer(optimized); - -// Now `optimized` contains character-like pixel representations (canvas uses '█' for pixels) -``` - -Example: Save screenshot to disk -```ts -await canvas.saveToFile('/tmp/opentui-screenshot.png') -``` - -Implementation notes and debugging -- The WGSL shader used for compute supersampling is embedded at build time: `shaders/supersampling.wgsl`. -- If you change the WGSL shader, ensure the compute output packing / `cellBytesSize` calculation in `updateComputeBuffers` is updated accordingly. -- The code uses a `GPUCanvasContextMock` (from bun-webgpu) to emulate canvas behavior in non-browser contexts. - -Related docs -- See `packages/core/docs/api/3d/shaders.md` for details on the WGSL shader and how it maps to compute outputs. -- See `packages/core/src/3d/WGPURenderer.ts` for the renderer that uses `CLICanvas` (documented next). - ---- - -Next steps I will take (unless you prefer otherwise): -- Read and document `WGPURenderer.ts` (expose public API, options, and how it integrates with CliRenderer). -- Document sprite-related modules: `SpriteResourceManager.ts`, `SpriteUtils.ts`, `TextureUtils.ts`, and animation classes in `3d/animation/`. -- Document physics adapters: `PlanckPhysicsAdapter.ts` and `RapierPhysicsAdapter.ts`. - -If you'd like me to proceed, I will read the next file `packages/core/src/3d/WGPURenderer.ts` and generate a corresponding doc page. diff --git a/packages/core/docs/api/3d/exploding-sprite-effect.md b/packages/core/docs/api/3d/exploding-sprite-effect.md deleted file mode 100644 index e31465d33..000000000 --- a/packages/core/docs/api/3d/exploding-sprite-effect.md +++ /dev/null @@ -1,120 +0,0 @@ -# ExplodingSpriteEffect & ExplosionManager - -This page documents the GPU-driven exploding sprite particle effect used by the 3D animation subsystem. Implementation reference: `packages/core/src/3d/animation/ExplodingSpriteEffect.ts`. - -The effect slices a sprite into a grid of smaller particles and launches them outward using instanced GPU particles with per-instance velocity, angular velocity, UV offsets, and lifetime. A manager class (`ExplosionManager`) provides pooling and convenience helpers to create explosions from existing sprite instances. - -## Key types - -- ExplosionEffectParameters — configuration for number of particles, lifetime, strength, gravity, etc. -- ExplosionCreationData — data required to create an explosion (resource, UV offsets, transform). -- ExplosionHandle — returned handle allowing restoration of original sprite after explosion. - -## ExplosionEffectParameters (fields) - -```ts -interface ExplosionEffectParameters { - numRows: number - numCols: number - durationMs: number - strength: number - strengthVariation: number - gravity: number - gravityScale: number - fadeOut: boolean - angularVelocityMin: THREE.Vector3 - angularVelocityMax: THREE.Vector3 - initialVelocityYBoost: number - zVariationStrength: number - materialFactory: () => NodeMaterial -} -``` - -Default parameters (`DEFAULT_EXPLOSION_PARAMETERS`) are provided; override fields via `userParams` in the constructor. - -Important notes: -- `numRows` x `numCols` determines the number of particles. -- `durationMs` is lifetime in milliseconds. -- `strength` controls particle initial velocity magnitude; `strengthVariation` adds random spread. -- `gravity` and `gravityScale` control vertical acceleration applied to particles. -- `fadeOut`: if true particles fade out near the end of lifetime. -- `materialFactory`: factory returning a NodeMaterial for GPU shading (defaults provided). - -## Class: ExplodingSpriteEffect - -Constructor -```ts -new ExplodingSpriteEffect( - scene: THREE.Scene, - resource: SpriteResource, - frameUvOffset: THREE.Vector2, - frameUvSize: THREE.Vector2, - spriteWorldTransform: THREE.Matrix4, - userParams?: Partial, -) -``` -- `resource`: a SpriteResource (from SpriteResourceManager) containing texture and meshPool. -- `frameUvOffset` and `frameUvSize`: UV offsets and size for the particular sprite frame being exploded. -- `spriteWorldTransform`: world transform of the original sprite (particles start at sprite location). -- `userParams`: partial overrides of the default parameters. - -Public properties and methods: -- `isActive: boolean` — whether effect is active -- `update(deltaTimeMs: number): void` — advance particle time; disposes effect when lifetime exceeded -- `dispose(): void` — removes instanced mesh from scene and returns mesh to pool - -Behavior details: -- Creates an InstancedMesh with `numParticles` instances and per-instance attributes: - - `a_particleData` (vec4) — local particle position, seed, life variation - - `a_velocity` (vec4) — initial velocity vector - - `a_angularVel` (vec4) — angular velocity for orientation over time - - `a_uvOffset` (vec4) — uv offset and uv size for the particle's subtexture -- The material is constructed via `NodeMaterial` and stores uniform refs for time/duration/gravity. The particle vertex transforms are computed in the shader using these attributes and uniforms. -- On each frame, the effect updates `time` uniform (via `onBeforeRender`) and the GPU material computes positions/colors/opacities. - -## ExplosionManager - -Purpose: keep track of active explosions and provide pooling/factory helpers. - -API -```ts -class ExplosionManager { - constructor(scene: THREE.Scene) - - fillPool(resource: SpriteResource, count: number, params?: Partial): void - - createExplosionForSprite(spriteToExplode: TiledSprite, userParams?: Partial): ExplosionHandle | null - - update(deltaTimeMs: number): void - - disposeAll(): void -} -``` - -- `fillPool`: pre-fill mesh pools for a resource for performance. -- `createExplosionForSprite`: destroys the given sprite and replaces it with an ExplodingSpriteEffect; returns `ExplosionHandle` which can be used to restore the original sprite via `restoreSprite(spriteAnimator)`. -- `update`: advances all active explosions and removes finished effects. - -## Examples - -Create and trigger an explosion for a sprite (pseudocode): -```ts -const manager = new ExplosionManager(scene) -const explosionHandle = manager.createExplosionForSprite(myTiledSprite, { strength: 6, durationMs: 1500 }) - -// Optionally restore later: -if (explosionHandle) { - await explosionHandle.restoreSprite(spriteAnimator) -} -``` - -## Implementation details and GPU nodes - -- The implementation uses `three/tsl` nodes and `three/webgpu` NodeMaterial to implement particle computations in the shader. -- The material template is cached per resource + particle grid configuration. -- Ensure the NodeMaterial produced by `materialFactory` is compatible with the attributes and node graph built by `_buildTemplateMaterial`. - ---- - -Next steps: -- Document PhysicsExplodingSpriteEffect (physics-driven explosion), SpriteAnimator, and SpriteParticleGenerator to complete the animation docs. diff --git a/packages/core/docs/api/3d/physics-exploding-sprite-effect.md b/packages/core/docs/api/3d/physics-exploding-sprite-effect.md deleted file mode 100644 index 2e2fa8be5..000000000 --- a/packages/core/docs/api/3d/physics-exploding-sprite-effect.md +++ /dev/null @@ -1,122 +0,0 @@ -# PhysicsExplodingSpriteEffect & PhysicsExplosionManager - -This document describes the physics-driven exploding-sprite effect used by OpenTUI's 3D animation subsystem. Reference implementation: `packages/core/src/3d/animation/PhysicsExplodingSpriteEffect.ts`. - -The physics-driven effect uses a physics engine (abstracted by the project's physics adapter interface) to simulate particle rigid bodies and synchronizes their transforms into an instanced mesh for rendering. - -## Key types - -- PhysicsExplosionEffectParameters — configuration for particle physics (force, damping, restitution, density, etc.) -- PhysicsExplosionCreationData — resource + UV + transform needed to create an effect -- PhysicsExplosionHandle — a handle returned on creation that can restore the original sprite - -The effect depends on the physics abstraction implemented by the project's adapters (see `packages/core/src/3d/physics/physics-interface.ts` and corresponding Planck/Rapier adapters). - -## PhysicsExplosionEffectParameters (fields) - -```ts -interface PhysicsExplosionEffectParameters { - numRows: number - numCols: number - durationMs: number - explosionForce: number - forceVariation: number - torqueStrength: number - gravityScale: number - fadeOut: boolean - linearDamping: number - angularDamping: number - restitution: number - friction: number - density: number - materialFactory: () => NodeMaterial -} -``` - -Defaults available in `DEFAULT_PHYSICS_EXPLOSION_PARAMETERS`. Use `userParams` to override. - -## Class: PhysicsExplodingSpriteEffect - -Constructor -```ts -new PhysicsExplodingSpriteEffect( - scene: THREE.Scene, - physicsWorld: PhysicsWorld, - resource: SpriteResource, - frameUvOffset: THREE.Vector2, - frameUvSize: THREE.Vector2, - spriteWorldTransform: THREE.Matrix4, - userParams?: Partial -) -``` - -- `physicsWorld`: instance implementing the physics adapter interface (PhysicsWorld). -- The effect creates per-particle rigid bodies and colliders through the physicsWorld, applies impulses/torque, and tracks each particle's rigid body. -- It maintains an `InstancedMesh` used for rendering; per-instance UV offsets are stored in an InstancedBufferAttribute. - -Public methods -- `update(deltaTimeMs: number): void` — queries each particle rigid body for translation/rotation and writes instance matrices to the InstancedMesh. Disposes when time >= duration. -- `dispose(): void` — removes mesh from scene, releases it to MeshPool, and removes rigid bodies from physics world. - -Behavior notes -- For each particle: - - A rigid body is created with `translation`, `linearDamping`, `angularDamping` (via physicsWorld.createRigidBody). - - A collider matching particle size is created and attached. - - An impulse and torque impulse are applied to simulate explosion. -- The effect synchronizes physics transforms to the instanced mesh each `update` call. -- The effect uses a shared NodeMaterial (cached per texture) to sample the sprite UVs. - -## PhysicsExplosionManager - -Purpose: manage many physics-driven explosions, pool geometry/materials, and provide helpers to create explosions for sprites. - -API -```ts -class PhysicsExplosionManager { - constructor(scene: THREE.Scene, physicsWorld: PhysicsWorld) - - fillPool(resource: SpriteResource, count: number, params?: Partial): void - - createExplosionForSprite(spriteToExplode: TiledSprite, userParams?: Partial): Promise - - update(deltaTimeMs: number): void - - disposeAll(): void -} -``` - -- `createExplosionForSprite` removes the supplied sprite, creates the physics particles and returns a handle with `restoreSprite(spriteAnimator)` to recreate the sprite when needed. -- `fillPool` preallocates pooled meshes for a given resource to reduce allocation overhead. - -## Physics adapter requirements (overview) - -The physics-driven effects rely on the project's physics adapter interface. The adapter must provide: -- PhysicsWorld: ability to `createRigidBody(desc)`, `createCollider(desc, body)`, `removeRigidBody(body)`, and `create`/`destroy` lifecycle. -- PhysicsRigidBody: must expose `applyImpulse`, `applyTorqueImpulse`, `getTranslation()` and `getRotation()` (rotation scalar for 2D), and similar. -- Physics types referenced in the code: PhysicsRigidBody, PhysicsWorld, PhysicsRigidBodyDesc, PhysicsColliderDesc, PhysicsVector2. - -See `packages/core/src/3d/physics/physics-interface.ts` for exact method names and required shapes. Available adapters: Planck and Rapier (PlanckPhysicsAdapter.ts and RapierPhysicsAdapter.ts). - -## Example usage (pseudocode) - -```ts -// Assuming scene and physicsWorld are initialized, and spriteAnimator exists -const manager = new PhysicsExplosionManager(scene, physicsWorld) -const handle = await manager.createExplosionForSprite(myTiledSprite, { explosionForce: 30, durationMs: 2500 }) -// Optionally restore later: -if (handle) { - await handle.restoreSprite(spriteAnimator) -} -``` - -## Recommendations and notes - -- Ensure physicsWorld is stepped/updated by your application loop (outside this class) so rigid bodies advance and `update(...)` reads correct transforms. -- Tune `explosionForce`, `forceVariation`, `torqueStrength`, and `density` per your scene scale and physics adapter units. -- Use `fillPool` to pre-warm mesh pools for frequently used configurations. - ---- - -Next steps: -- Document `SpriteAnimator.ts` and `SpriteParticleGenerator.ts` (animation & particle API). -- Document physics adapter implementations (Planck / Rapier) with configuration examples. diff --git a/packages/core/docs/api/3d/physics.md b/packages/core/docs/api/3d/physics.md deleted file mode 100644 index f713bf29c..000000000 --- a/packages/core/docs/api/3d/physics.md +++ /dev/null @@ -1,454 +0,0 @@ -# Physics Integration - -OpenTUI provides physics integration through adapters for popular physics engines, allowing you to create realistic physics simulations in your terminal applications. - -## Physics Interface - -The physics integration is built around a common interface that allows different physics engines to be used interchangeably. - -```typescript -import { PhysicsInterface } from '@opentui/core/3d'; - -// The physics interface defines common methods for working with physics engines -interface PhysicsInterface { - createWorld(options?: PhysicsWorldOptions): PhysicsWorld; - createStaticBody(options: BodyOptions): PhysicsBody; - createDynamicBody(options: BodyOptions): PhysicsBody; - createKinematicBody(options: BodyOptions): PhysicsBody; - update(deltaTime: number): void; - // ... other methods -} -``` - -## Physics Adapters - -OpenTUI provides adapters for two popular physics engines: - -### Planck.js Adapter - -[Planck.js](https://github.com/shakiba/planck.js/) is a JavaScript rewrite of the Box2D physics engine. - -```typescript -import { PlanckPhysicsAdapter } from '@opentui/core/3d'; - -// Create a Planck.js physics adapter -const physics = new PlanckPhysicsAdapter({ - gravity: { x: 0, y: 10 }, - scale: 30 // Pixels per meter -}); - -// Create a world -const world = physics.createWorld(); - -// Create a static ground body -const ground = physics.createStaticBody({ - position: { x: 50, y: 80 }, - shape: { - type: 'box', - width: 100, - height: 5 - } -}); - -// Create a dynamic box body -const box = physics.createDynamicBody({ - position: { x: 50, y: 10 }, - shape: { - type: 'box', - width: 5, - height: 5 - }, - restitution: 0.5, // Bounciness - friction: 0.2 -}); - -// Update the physics simulation -function update(deltaTime: number) { - physics.update(deltaTime); - - // Get the new position of the box - const position = box.getPosition(); - - // Update your renderable with the new position - boxRenderable.x = position.x - boxRenderable.width / 2; - boxRenderable.y = position.y - boxRenderable.height / 2; -} -``` - -### Rapier Adapter - -[Rapier](https://rapier.rs/) is a high-performance physics engine written in Rust with WebAssembly bindings. - -```typescript -import { RapierPhysicsAdapter } from '@opentui/core/3d'; - -// Create a Rapier physics adapter -const physics = new RapierPhysicsAdapter({ - gravity: { x: 0, y: 9.81 }, - scale: 30 // Pixels per meter -}); - -// Create a world -const world = physics.createWorld(); - -// Create a static ground body -const ground = physics.createStaticBody({ - position: { x: 50, y: 80 }, - shape: { - type: 'box', - width: 100, - height: 5 - } -}); - -// Create a dynamic circle body -const circle = physics.createDynamicBody({ - position: { x: 50, y: 10 }, - shape: { - type: 'circle', - radius: 3 - }, - restitution: 0.7, // Bounciness - friction: 0.1 -}); - -// Update the physics simulation -function update(deltaTime: number) { - physics.update(deltaTime); - - // Get the new position of the circle - const position = circle.getPosition(); - - // Update your renderable with the new position - circleRenderable.x = position.x - circleRenderable.width / 2; - circleRenderable.y = position.y - circleRenderable.height / 2; -} -``` - -## Physics Bodies - -Physics bodies represent physical objects in the simulation. There are three types of bodies: - -- **Static Bodies**: Don't move and are not affected by forces -- **Dynamic Bodies**: Move and are affected by forces -- **Kinematic Bodies**: Move but are not affected by forces (controlled programmatically) - -```typescript -// Create a static body (e.g., ground, walls) -const ground = physics.createStaticBody({ - position: { x: 50, y: 80 }, - shape: { - type: 'box', - width: 100, - height: 5 - } -}); - -// Create a dynamic body (e.g., player, objects) -const box = physics.createDynamicBody({ - position: { x: 50, y: 10 }, - shape: { - type: 'box', - width: 5, - height: 5 - }, - restitution: 0.5, - friction: 0.2 -}); - -// Create a kinematic body (e.g., moving platforms) -const platform = physics.createKinematicBody({ - position: { x: 30, y: 40 }, - shape: { - type: 'box', - width: 20, - height: 2 - } -}); - -// Move a kinematic body programmatically -platform.setLinearVelocity({ x: 1, y: 0 }); -``` - -## Shapes - -Physics bodies can have different shapes: - -- **Box**: Rectangular shape -- **Circle**: Circular shape -- **Polygon**: Custom polygon shape -- **Compound**: Multiple shapes combined - -```typescript -// Box shape -const box = physics.createDynamicBody({ - position: { x: 50, y: 10 }, - shape: { - type: 'box', - width: 5, - height: 5 - } -}); - -// Circle shape -const circle = physics.createDynamicBody({ - position: { x: 60, y: 10 }, - shape: { - type: 'circle', - radius: 3 - } -}); - -// Polygon shape -const polygon = physics.createDynamicBody({ - position: { x: 70, y: 10 }, - shape: { - type: 'polygon', - vertices: [ - { x: 0, y: 0 }, - { x: 5, y: 0 }, - { x: 2.5, y: 5 } - ] - } -}); -``` - -## Joints - -Joints connect bodies together and constrain their movement: - -- **Distance Joint**: Keeps bodies at a fixed distance -- **Revolute Joint**: Allows rotation around a point -- **Prismatic Joint**: Allows movement along an axis -- **Pulley Joint**: Connects bodies with a pulley system -- **Gear Joint**: Connects bodies with a gear ratio - -```typescript -// Create a revolute joint (hinge) -const joint = physics.createRevoluteJoint({ - bodyA: box1, - bodyB: box2, - anchorPoint: { x: 55, y: 10 }, - collideConnected: false -}); - -// Create a distance joint (spring) -const spring = physics.createDistanceJoint({ - bodyA: box1, - bodyB: box3, - length: 10, - frequency: 5, // Oscillation frequency - damping: 0.5, // Damping ratio - collideConnected: true -}); -``` - -## Collision Detection - -You can detect and respond to collisions between bodies: - -```typescript -// Set up collision callbacks -physics.onBeginContact((bodyA, bodyB) => { - console.log(`Collision started between ${bodyA.id} and ${bodyB.id}`); -}); - -physics.onEndContact((bodyA, bodyB) => { - console.log(`Collision ended between ${bodyA.id} and ${bodyB.id}`); -}); - -// Check if two bodies are in contact -const inContact = physics.areInContact(bodyA, bodyB); -``` - -## Example: Simple Physics Simulation - -Here's a complete example of a simple physics simulation: - -```typescript -import { createCliRenderer, BoxRenderable } from '@opentui/core'; -import { PlanckPhysicsAdapter } from '@opentui/core/3d'; - -async function createPhysicsDemo() { - // Create the renderer - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222' - }); - - root.add(container); - - // Create the physics adapter - const physics = new PlanckPhysicsAdapter({ - gravity: { x: 0, y: 10 }, - scale: 30 - }); - - // Create a world - const world = physics.createWorld(); - - // Create a ground body - const ground = physics.createStaticBody({ - position: { x: renderer.width / 2, y: renderer.height - 5 }, - shape: { - type: 'box', - width: renderer.width - 10, - height: 2 - } - }); - - // Create walls - const leftWall = physics.createStaticBody({ - position: { x: 2, y: renderer.height / 2 }, - shape: { - type: 'box', - width: 2, - height: renderer.height - 10 - } - }); - - const rightWall = physics.createStaticBody({ - position: { x: renderer.width - 2, y: renderer.height / 2 }, - shape: { - type: 'box', - width: 2, - height: renderer.height - 10 - } - }); - - // Create some dynamic bodies - const bodies = []; - const renderables = []; - - for (let i = 0; i < 10; i++) { - // Create a dynamic body - const body = physics.createDynamicBody({ - position: { - x: 10 + Math.random() * (renderer.width - 20), - y: 5 + Math.random() * 10 - }, - shape: { - type: Math.random() > 0.5 ? 'box' : 'circle', - width: 3 + Math.random() * 3, - height: 3 + Math.random() * 3, - radius: 2 + Math.random() * 2 - }, - restitution: 0.3 + Math.random() * 0.5, - friction: 0.1 + Math.random() * 0.3 - }); - - bodies.push(body); - - // Create a renderable for the body - const isBox = body.getShapeType() === 'box'; - const size = isBox ? body.getSize() : { width: body.getRadius() * 2, height: body.getRadius() * 2 }; - - const renderable = new BoxRenderable(`body${i}`, { - width: size.width, - height: size.height, - position: 'absolute', - x: body.getPosition().x - size.width / 2, - y: body.getPosition().y - size.height / 2, - borderStyle: isBox ? 'single' : 'rounded', - borderColor: '#e74c3c', - backgroundColor: 'transparent' - }); - - renderables.push(renderable); - container.add(renderable); - } - - // Create a renderable for the ground - const groundRenderable = new BoxRenderable('ground', { - width: renderer.width - 10, - height: 2, - position: 'absolute', - x: 5, - y: renderer.height - 5, - borderStyle: 'single', - borderColor: '#2ecc71', - backgroundColor: 'transparent' - }); - - container.add(groundRenderable); - - // Set up the update loop - renderer.setFrameCallback((deltaTime) => { - // Update physics - physics.update(deltaTime / 1000); // Convert to seconds - - // Update renderables - for (let i = 0; i < bodies.length; i++) { - const body = bodies[i]; - const renderable = renderables[i]; - const position = body.getPosition(); - const angle = body.getAngle(); - - renderable.x = position.x - renderable.width / 2; - renderable.y = position.y - renderable.height / 2; - - // TODO: Handle rotation when supported - } - }); - - // Start the renderer - renderer.start(); - - // Handle keyboard input - renderer.on('key', (key) => { - const keyStr = key.toString(); - - if (keyStr === ' ') { - // Add a new body on spacebar - const body = physics.createDynamicBody({ - position: { - x: 10 + Math.random() * (renderer.width - 20), - y: 5 - }, - shape: { - type: Math.random() > 0.5 ? 'box' : 'circle', - width: 3 + Math.random() * 3, - height: 3 + Math.random() * 3, - radius: 2 + Math.random() * 2 - }, - restitution: 0.3 + Math.random() * 0.5, - friction: 0.1 + Math.random() * 0.3 - }); - - bodies.push(body); - - const isBox = body.getShapeType() === 'box'; - const size = isBox ? body.getSize() : { width: body.getRadius() * 2, height: body.getRadius() * 2 }; - - const renderable = new BoxRenderable(`body${bodies.length}`, { - width: size.width, - height: size.height, - position: 'absolute', - x: body.getPosition().x - size.width / 2, - y: body.getPosition().y - size.height / 2, - borderStyle: isBox ? 'single' : 'rounded', - borderColor: '#e74c3c', - backgroundColor: 'transparent' - }); - - renderables.push(renderable); - container.add(renderable); - } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C - renderer.destroy(); - process.exit(0); - } - }); - - return renderer; -} - -// Create and run the physics demo -createPhysicsDemo().catch(console.error); -``` diff --git a/packages/core/docs/api/3d/shaders.md b/packages/core/docs/api/3d/shaders.md deleted file mode 100644 index 2e008ae68..000000000 --- a/packages/core/docs/api/3d/shaders.md +++ /dev/null @@ -1,874 +0,0 @@ -# WebGPU Shaders - -OpenTUI provides WebGPU integration for high-performance graphics rendering, including support for custom shaders. - -## WebGPU Renderer - -The `WGPURenderer` class provides a WebGPU rendering context for OpenTUI. - -```typescript -import { WGPURenderer } from '@opentui/core/3d'; - -// Create a WebGPU renderer -const gpuRenderer = new WGPURenderer({ - width: 800, - height: 600 -}); - -// Initialize the renderer -await gpuRenderer.initialize(); - -// Create a render pipeline -const pipeline = await gpuRenderer.createRenderPipeline({ - vertex: { - module: gpuRenderer.device.createShaderModule({ - code: vertexShaderCode - }), - entryPoint: 'main' - }, - fragment: { - module: gpuRenderer.device.createShaderModule({ - code: fragmentShaderCode - }), - entryPoint: 'main', - targets: [{ format: gpuRenderer.format }] - }, - primitive: { - topology: 'triangle-list' - } -}); - -// Render a frame -gpuRenderer.beginFrame(); -// ... rendering commands ... -gpuRenderer.endFrame(); - -// Destroy the renderer -gpuRenderer.destroy(); -``` - -## WGSL Shaders - -WebGPU uses the WebGPU Shading Language (WGSL) for writing shaders. Here's an example of a simple vertex and fragment shader: - -### Vertex Shader - -```wgsl -@vertex -fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4 { - var pos = array, 3>( - vec2(0.0, 0.5), - vec2(-0.5, -0.5), - vec2(0.5, -0.5) - ); - return vec4(pos[VertexIndex], 0.0, 1.0); -} -``` - -### Fragment Shader - -```wgsl -@fragment -fn main() -> @location(0) vec4 { - return vec4(1.0, 0.0, 0.0, 1.0); -} -``` - -## Supersampling Shader - -OpenTUI includes a supersampling shader for improving the quality of rendered graphics: - -```wgsl -// supersampling.wgsl - -@group(0) @binding(0) var inputTexture: texture_2d; -@group(0) @binding(1) var outputTexture: texture_storage_2d; -@group(0) @binding(2) var params: Params; - -struct Params { - width: u32, - height: u32, - sampleCount: u32, -} - -@compute @workgroup_size(16, 16) -fn main(@builtin(global_invocation_id) global_id: vec3) { - let x = global_id.x; - let y = global_id.y; - - if (x >= params.width || y >= params.height) { - return; - } - - var color = vec4(0.0, 0.0, 0.0, 0.0); - let sampleSize = 1.0 / f32(params.sampleCount); - - for (var i = 0u; i < params.sampleCount; i = i + 1u) { - for (var j = 0u; j < params.sampleCount; j = j + 1u) { - let offsetX = (f32(i) + 0.5) * sampleSize; - let offsetY = (f32(j) + 0.5) * sampleSize; - let sampleX = f32(x) + offsetX; - let sampleY = f32(y) + offsetY; - - color = color + textureLoad(inputTexture, vec2(sampleX, sampleY), 0); - } - } - - color = color / f32(params.sampleCount * params.sampleCount); - textureStore(outputTexture, vec2(x, y), color); -} -``` - -## Example: Fractal Shader - -Here's an example of creating a fractal shader with WebGPU: - -```typescript -import { createCliRenderer, BoxRenderable, OptimizedBuffer, RGBA } from '@opentui/core'; -import { WGPURenderer } from '@opentui/core/3d'; - -// Vertex shader -const vertexShaderCode = ` -@vertex -fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4 { - var pos = array, 6>( - vec2(-1.0, -1.0), - vec2(1.0, -1.0), - vec2(1.0, 1.0), - vec2(-1.0, -1.0), - vec2(1.0, 1.0), - vec2(-1.0, 1.0) - ); - return vec4(pos[VertexIndex], 0.0, 1.0); -} -`; - -// Fragment shader (Mandelbrot set) -const fragmentShaderCode = ` -@group(0) @binding(0) var params: Params; - -struct Params { - width: f32, - height: f32, - time: f32, - zoom: f32, - offsetX: f32, - offsetY: f32, -} - -@fragment -fn main(@builtin(position) fragCoord: vec4) -> @location(0) vec4 { - let aspect = params.width / params.height; - let uv = vec2(fragCoord.xy / vec2(params.width, params.height)); - - // Map to complex plane - let c = vec2( - (uv.x * 2.0 - 1.0) * aspect * params.zoom + params.offsetX, - (uv.y * 2.0 - 1.0) * params.zoom + params.offsetY - ); - - // Mandelbrot iteration - let maxIter = 100.0; - var z = vec2(0.0, 0.0); - var iter = 0.0; - - for (var i = 0.0; i < maxIter; i += 1.0) { - // z = z^2 + c - let real = z.x * z.x - z.y * z.y + c.x; - let imag = 2.0 * z.x * z.y + c.y; - z = vec2(real, imag); - - if (dot(z, z) > 4.0) { - iter = i; - break; - } - } - - // Coloring - if (iter >= maxIter) { - return vec4(0.0, 0.0, 0.0, 1.0); - } - - let t = iter / maxIter; - let hue = 360.0 * (0.5 + sin(params.time * 0.1) * 0.5) * t; - - // HSV to RGB conversion - let h = hue / 60.0; - let i = floor(h); - let f = h - i; - let p = 1.0 - t; - let q = 1.0 - (t * f); - let r = 1.0 - (t * (1.0 - f)); - - var rgb: vec3; - - if (i == 0.0) { - rgb = vec3(t, r, p); - } else if (i == 1.0) { - rgb = vec3(q, t, p); - } else if (i == 2.0) { - rgb = vec3(p, t, r); - } else if (i == 3.0) { - rgb = vec3(p, q, t); - } else if (i == 4.0) { - rgb = vec3(r, p, t); - } else { - rgb = vec3(t, p, q); - } - - return vec4(rgb, 1.0); -} -`; - -class FractalRenderable extends BoxRenderable { - private gpuRenderer: WGPURenderer; - private pipeline: GPURenderPipeline; - private bindGroup: GPUBindGroup; - private uniformBuffer: GPUBuffer; - private params: { - width: number; - height: number; - time: number; - zoom: number; - offsetX: number; - offsetY: number; - }; - - constructor(id: string, options = {}) { - super(id, { - width: '100%', - height: '100%', - border: false, - ...options - }); - - this.params = { - width: 0, - height: 0, - time: 0, - zoom: 1.5, - offsetX: -0.5, - offsetY: 0.0 - }; - - this.initWebGPU(); - } - - private async initWebGPU() { - // Create a WebGPU renderer - this.gpuRenderer = new WGPURenderer({ - width: this.width * 2, // Double resolution for better quality - height: this.height * 2 - }); - - // Initialize the renderer - await this.gpuRenderer.initialize(); - - // Update params - this.params.width = this.gpuRenderer.width; - this.params.height = this.gpuRenderer.height; - - // Create a uniform buffer - this.uniformBuffer = this.gpuRenderer.device.createBuffer({ - size: 6 * 4, // 6 floats (width, height, time, zoom, offsetX, offsetY) - usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST - }); - - // Create a bind group layout - const bindGroupLayout = this.gpuRenderer.device.createBindGroupLayout({ - entries: [ - { - binding: 0, - visibility: GPUShaderStage.FRAGMENT, - buffer: { type: 'uniform' } - } - ] - }); - - // Create a pipeline layout - const pipelineLayout = this.gpuRenderer.device.createPipelineLayout({ - bindGroupLayouts: [bindGroupLayout] - }); - - // Create a render pipeline - this.pipeline = this.gpuRenderer.device.createRenderPipeline({ - layout: pipelineLayout, - vertex: { - module: this.gpuRenderer.device.createShaderModule({ - code: vertexShaderCode - }), - entryPoint: 'main' - }, - fragment: { - module: this.gpuRenderer.device.createShaderModule({ - code: fragmentShaderCode - }), - entryPoint: 'main', - targets: [{ format: this.gpuRenderer.format }] - }, - primitive: { - topology: 'triangle-list' - } - }); - - // Create a bind group - this.bindGroup = this.gpuRenderer.device.createBindGroup({ - layout: bindGroupLayout, - entries: [ - { - binding: 0, - resource: { buffer: this.uniformBuffer } - } - ] - }); - } - - protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { - if (!this.gpuRenderer || !this.pipeline || !this.bindGroup) return; - - // Update time - this.params.time += deltaTime; - - // Update uniform buffer - this.gpuRenderer.device.queue.writeBuffer( - this.uniformBuffer, - 0, - new Float32Array([ - this.params.width, - this.params.height, - this.params.time, - this.params.zoom, - this.params.offsetX, - this.params.offsetY - ]) - ); - - // Begin frame - this.gpuRenderer.beginFrame(); - - // Get command encoder - const commandEncoder = this.gpuRenderer.device.createCommandEncoder(); - - // Begin render pass - const renderPass = commandEncoder.beginRenderPass({ - colorAttachments: [ - { - view: this.gpuRenderer.context.getCurrentTexture().createView(), - loadOp: 'clear', - storeOp: 'store', - clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 } - } - ] - }); - - // Set pipeline and bind group - renderPass.setPipeline(this.pipeline); - renderPass.setBindGroup(0, this.bindGroup); - - // Draw - renderPass.draw(6); - - // End render pass - renderPass.end(); - - // Submit commands - this.gpuRenderer.device.queue.submit([commandEncoder.finish()]); - - // End frame - this.gpuRenderer.endFrame(); - - // Get the rendered image - const imageData = this.gpuRenderer.getImageData(); - - // Render to terminal - for (let y = 0; y < this.height; y++) { - for (let x = 0; x < this.width; x++) { - // Sample the WebGL output with proper scaling - const glX = Math.floor(x * (this.gpuRenderer.width / this.width)); - const glY = Math.floor(y * (this.gpuRenderer.height / this.height)); - - const idx = (glY * this.gpuRenderer.width + glX) * 4; - const r = imageData.data[idx] / 255; - const g = imageData.data[idx + 1] / 255; - const b = imageData.data[idx + 2] / 255; - const a = imageData.data[idx + 3] / 255; - - // Apply brightness-based character selection for better visibility - const brightness = 0.299 * r + 0.587 * g + 0.114 * b; - const character = brightness > 0.8 ? '█' : - brightness > 0.6 ? '▓' : - brightness > 0.4 ? '▒' : - brightness > 0.2 ? '░' : ' '; - - // Draw the pixel with appropriate character and color - buffer.setCell( - this.x + x, - this.y + y, - character, - RGBA.fromValues(r, g, b, a), - RGBA.fromValues(0, 0, 0, 1) - ); - } - } - } - - // Control methods - public setZoom(zoom: number): void { - this.params.zoom = zoom; - } - - public setOffset(x: number, y: number): void { - this.params.offsetX = x; - this.params.offsetY = y; - } - - public destroy(): void { - if (this.gpuRenderer) { - this.gpuRenderer.destroy(); - } - } -} - -async function createFractalDemo() { - // Create the renderer - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222' - }); - - root.add(container); - - // Create a fractal renderable - const fractal = new FractalRenderable('fractal'); - container.add(fractal); - - // Add instructions - const instructions = new TextRenderable('instructions', { - content: 'Arrow keys: Move | +/-: Zoom | Q: Quit', - fg: '#ffffff', - position: 'absolute', - x: 2, - y: 1 - }); - - container.add(instructions); - - // Handle keyboard input - renderer.on('key', (key) => { - const keyStr = key.toString(); - - if (keyStr === 'ArrowRight') { - fractal.setOffset(fractal.params.offsetX + 0.1 * fractal.params.zoom, fractal.params.offsetY); - } else if (keyStr === 'ArrowLeft') { - fractal.setOffset(fractal.params.offsetX - 0.1 * fractal.params.zoom, fractal.params.offsetY); - } else if (keyStr === 'ArrowUp') { - fractal.setOffset(fractal.params.offsetX, fractal.params.offsetY - 0.1 * fractal.params.zoom); - } else if (keyStr === 'ArrowDown') { - fractal.setOffset(fractal.params.offsetX, fractal.params.offsetY + 0.1 * fractal.params.zoom); - } else if (keyStr === '+') { - fractal.setZoom(fractal.params.zoom * 0.8); - } else if (keyStr === '-') { - fractal.setZoom(fractal.params.zoom * 1.25); - } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C - fractal.destroy(); - renderer.destroy(); - process.exit(0); - } - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the fractal demo -createFractalDemo().catch(console.error); -``` - -## Example: Cube Shader - -Here's an example of rendering a 3D cube with WebGPU: - -```typescript -import { createCliRenderer, BoxRenderable, OptimizedBuffer, RGBA } from '@opentui/core'; -import { WGPURenderer } from '@opentui/core/3d'; - -// Vertex shader -const vertexShaderCode = ` -struct Uniforms { - modelViewProjection: mat4x4, - time: f32, -}; - -@group(0) @binding(0) var uniforms: Uniforms; - -struct VertexOutput { - @builtin(position) position: vec4, - @location(0) color: vec4, -}; - -@vertex -fn main( - @location(0) position: vec3, - @location(1) color: vec4 -) -> VertexOutput { - var output: VertexOutput; - output.position = uniforms.modelViewProjection * vec4(position, 1.0); - output.color = color; - return output; -} -`; - -// Fragment shader -const fragmentShaderCode = ` -@fragment -fn main(@location(0) color: vec4) -> @location(0) vec4 { - return color; -} -`; - -class CubeRenderable extends BoxRenderable { - private gpuRenderer: WGPURenderer; - private pipeline: GPURenderPipeline; - private bindGroup: GPUBindGroup; - private uniformBuffer: GPUBuffer; - private vertexBuffer: GPUBuffer; - private indexBuffer: GPUBuffer; - private time: number = 0; - - constructor(id: string, options = {}) { - super(id, { - width: '100%', - height: '100%', - border: false, - ...options - }); - - this.initWebGPU(); - } - - private async initWebGPU() { - // Create a WebGPU renderer - this.gpuRenderer = new WGPURenderer({ - width: this.width * 2, // Double resolution for better quality - height: this.height * 2 - }); - - // Initialize the renderer - await this.gpuRenderer.initialize(); - - // Create vertex data for a cube - const vertices = new Float32Array([ - // Position (xyz), Color (rgba) - // Front face - -1.0, -1.0, 1.0, 1.0, 0.0, 0.0, 1.0, - 1.0, -1.0, 1.0, 0.0, 1.0, 0.0, 1.0, - 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, - -1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, - - // Back face - -1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 1.0, - 1.0, -1.0, -1.0, 1.0, 0.0, 1.0, 1.0, - 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, - -1.0, 1.0, -1.0, 0.0, 0.0, 0.0, 1.0, - ]); - - // Create index data for a cube - const indices = new Uint16Array([ - // Front face - 0, 1, 2, 0, 2, 3, - // Back face - 4, 5, 6, 4, 6, 7, - // Top face - 3, 2, 6, 3, 6, 7, - // Bottom face - 0, 1, 5, 0, 5, 4, - // Right face - 1, 2, 6, 1, 6, 5, - // Left face - 0, 3, 7, 0, 7, 4 - ]); - - // Create a vertex buffer - this.vertexBuffer = this.gpuRenderer.device.createBuffer({ - size: vertices.byteLength, - usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST - }); - - // Create an index buffer - this.indexBuffer = this.gpuRenderer.device.createBuffer({ - size: indices.byteLength, - usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST - }); - - // Create a uniform buffer - this.uniformBuffer = this.gpuRenderer.device.createBuffer({ - size: 4 * 16 + 4, // mat4x4 + float - usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST - }); - - // Write data to buffers - this.gpuRenderer.device.queue.writeBuffer(this.vertexBuffer, 0, vertices); - this.gpuRenderer.device.queue.writeBuffer(this.indexBuffer, 0, indices); - - // Create a bind group layout - const bindGroupLayout = this.gpuRenderer.device.createBindGroupLayout({ - entries: [ - { - binding: 0, - visibility: GPUShaderStage.VERTEX, - buffer: { type: 'uniform' } - } - ] - }); - - // Create a pipeline layout - const pipelineLayout = this.gpuRenderer.device.createPipelineLayout({ - bindGroupLayouts: [bindGroupLayout] - }); - - // Create a render pipeline - this.pipeline = this.gpuRenderer.device.createRenderPipeline({ - layout: pipelineLayout, - vertex: { - module: this.gpuRenderer.device.createShaderModule({ - code: vertexShaderCode - }), - entryPoint: 'main', - buffers: [ - { - arrayStride: 7 * 4, // 7 floats per vertex - attributes: [ - { - // Position - shaderLocation: 0, - offset: 0, - format: 'float32x3' - }, - { - // Color - shaderLocation: 1, - offset: 3 * 4, - format: 'float32x4' - } - ] - } - ] - }, - fragment: { - module: this.gpuRenderer.device.createShaderModule({ - code: fragmentShaderCode - }), - entryPoint: 'main', - targets: [{ format: this.gpuRenderer.format }] - }, - primitive: { - topology: 'triangle-list', - cullMode: 'back' - }, - depthStencil: { - depthWriteEnabled: true, - depthCompare: 'less', - format: 'depth24plus' - } - }); - - // Create a bind group - this.bindGroup = this.gpuRenderer.device.createBindGroup({ - layout: bindGroupLayout, - entries: [ - { - binding: 0, - resource: { buffer: this.uniformBuffer } - } - ] - }); - } - - protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { - if (!this.gpuRenderer || !this.pipeline || !this.bindGroup) return; - - // Update time - this.time += deltaTime; - - // Create model-view-projection matrix - const aspect = this.gpuRenderer.width / this.gpuRenderer.height; - const projectionMatrix = mat4.perspective( - mat4.create(), - Math.PI / 4, - aspect, - 0.1, - 100.0 - ); - - const viewMatrix = mat4.lookAt( - mat4.create(), - [0, 0, 5], // Camera position - [0, 0, 0], // Look at - [0, 1, 0] // Up vector - ); - - const modelMatrix = mat4.create(); - mat4.rotateY(modelMatrix, modelMatrix, this.time * 0.001); - mat4.rotateX(modelMatrix, modelMatrix, this.time * 0.0007); - - const modelViewProjection = mat4.create(); - mat4.multiply(modelViewProjection, viewMatrix, modelMatrix); - mat4.multiply(modelViewProjection, projectionMatrix, modelViewProjection); - - // Update uniform buffer - const uniformData = new Float32Array(16 + 1); - uniformData.set(modelViewProjection, 0); - uniformData[16] = this.time; - - this.gpuRenderer.device.queue.writeBuffer( - this.uniformBuffer, - 0, - uniformData - ); - - // Begin frame - this.gpuRenderer.beginFrame(); - - // Get command encoder - const commandEncoder = this.gpuRenderer.device.createCommandEncoder(); - - // Begin render pass - const renderPass = commandEncoder.beginRenderPass({ - colorAttachments: [ - { - view: this.gpuRenderer.context.getCurrentTexture().createView(), - loadOp: 'clear', - storeOp: 'store', - clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 } - } - ], - depthStencilAttachment: { - view: this.gpuRenderer.depthTextureView, - depthLoadOp: 'clear', - depthStoreOp: 'store', - depthClearValue: 1.0 - } - }); - - // Set pipeline and bind group - renderPass.setPipeline(this.pipeline); - renderPass.setBindGroup(0, this.bindGroup); - - // Set vertex and index buffers - renderPass.setVertexBuffer(0, this.vertexBuffer); - renderPass.setIndexBuffer(this.indexBuffer, 'uint16'); - - // Draw - renderPass.drawIndexed(36); - - // End render pass - renderPass.end(); - - // Submit commands - this.gpuRenderer.device.queue.submit([commandEncoder.finish()]); - - // End frame - this.gpuRenderer.endFrame(); - - // Get the rendered image - const imageData = this.gpuRenderer.getImageData(); - - // Render to terminal - for (let y = 0; y < this.height; y++) { - for (let x = 0; x < this.width; x++) { - // Sample the WebGL output with proper scaling - const glX = Math.floor(x * (this.gpuRenderer.width / this.width)); - const glY = Math.floor(y * (this.gpuRenderer.height / this.height)); - - const idx = (glY * this.gpuRenderer.width + glX) * 4; - const r = imageData.data[idx] / 255; - const g = imageData.data[idx + 1] / 255; - const b = imageData.data[idx + 2] / 255; - const a = imageData.data[idx + 3] / 255; - - // Apply brightness-based character selection for better visibility - const brightness = 0.299 * r + 0.587 * g + 0.114 * b; - const character = brightness > 0.8 ? '█' : - brightness > 0.6 ? '▓' : - brightness > 0.4 ? '▒' : - brightness > 0.2 ? '░' : ' '; - - // Draw the pixel with appropriate character and color - buffer.setCell( - this.x + x, - this.y + y, - character, - RGBA.fromValues(r, g, b, a), - RGBA.fromValues(0, 0, 0, 1) - ); - } - } - } - - public destroy(): void { - if (this.gpuRenderer) { - this.gpuRenderer.destroy(); - } - } -} - -async function createCubeDemo() { - // Create the renderer - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222' - }); - - root.add(container); - - // Create a cube renderable - const cube = new CubeRenderable('cube'); - container.add(cube); - - // Add instructions - const instructions = new TextRenderable('instructions', { - content: 'Q: Quit', - fg: '#ffffff', - position: 'absolute', - x: 2, - y: 1 - }); - - container.add(instructions); - - // Handle keyboard input - renderer.on('key', (key) => { - const keyStr = key.toString(); - - if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C - cube.destroy(); - renderer.destroy(); - process.exit(0); - } - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the cube demo -createCubeDemo().catch(console.error); -``` diff --git a/packages/core/docs/api/3d/sprite-animation.md b/packages/core/docs/api/3d/sprite-animation.md deleted file mode 100644 index 0c39460fc..000000000 --- a/packages/core/docs/api/3d/sprite-animation.md +++ /dev/null @@ -1,518 +0,0 @@ -# Sprite Animation - -OpenTUI provides powerful sprite animation capabilities for creating dynamic and interactive terminal user interfaces. - -## Sprite Resource Manager - -The `SpriteResourceManager` class is responsible for loading, managing, and releasing sprite resources. - -```typescript -import { SpriteResourceManager } from '@opentui/core/3d'; -import { Scene } from 'three'; - -// Create a scene -const scene = new Scene(); - -// Create a sprite resource manager -const spriteManager = new SpriteResourceManager(scene); - -// Create a sprite resource -const spriteResource = await spriteManager.createResource({ - imagePath: 'path/to/sprite.png', - sheetNumFrames: 8 -}); - -// Clear the cache -spriteManager.clearCache(); -``` - -## Sprite Animator - -The `SpriteAnimator` class provides functionality for animating sprites with frame-based animations. - -```typescript -import { SpriteAnimator, SpriteDefinition } from '@opentui/core/3d'; -import { Scene } from 'three'; - -// Create a scene -const scene = new Scene(); - -// Create a sprite animator -const animator = new SpriteAnimator(scene); - -// Define a sprite with animations -const spriteDefinition: SpriteDefinition = { - initialAnimation: 'idle', - scale: 1.0, - maxInstances: 100, - animations: { - idle: { - resource: spriteResource, - animNumFrames: 4, - animFrameOffset: 0, - frameDuration: 100, - loop: true - }, - run: { - resource: spriteResource, - animNumFrames: 6, - animFrameOffset: 4, - frameDuration: 80, - loop: true - } - } -}; - -// Create a sprite instance -const sprite = await animator.createSprite('player', spriteDefinition); - -// Play the animation -sprite.play(); - -// Stop the animation -sprite.stop(); - -// Go to a specific frame -sprite.goToFrame(2); - -// Change animation -await sprite.setAnimation('run'); - -// Check if animation is playing -const isPlaying = sprite.isPlaying(); - -// Set animation speed by changing frame duration -sprite.setFrameDuration(50); // faster animation - -// Update animations (call in animation loop) -animator.update(deltaTime); -``` - -## Sprite Animation Component - -Here's an example of creating a custom component for sprite animation: - -```typescript -import { Renderable, OptimizedBuffer, RGBA } from '@opentui/core'; -import { SpriteAnimator, SpriteResourceManager } from '@opentui/core/3d'; - -interface SpriteRenderableOptions { - width?: number; - height?: number; - spriteSheet: string; - frameWidth: number; - frameHeight: number; - frameCount: number; - frameDuration?: number; - loop?: boolean; -} - -class SpriteRenderable extends Renderable { - private animator: SpriteAnimator; - - constructor(id: string, options: SpriteRenderableOptions) { - super(id, { - width: options.width ?? options.frameWidth, - height: options.height ?? options.frameHeight, - position: 'absolute', - ...options - }); - - // Load the sprite sheet - const spriteManager = new SpriteResourceManager(); - spriteManager.loadSprite(options.spriteSheet).then(sprite => { - // Create the animator - this.animator = new SpriteAnimator({ - spriteSheet: sprite, - frameWidth: options.frameWidth, - frameHeight: options.frameHeight, - frameCount: options.frameCount, - frameDuration: options.frameDuration ?? 100, - loop: options.loop ?? true - }); - - // Start the animation - this.animator.play(); - }); - } - - protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { - if (!this.animator) return; - - // Update the animation - this.animator.update(deltaTime); - - // Get the current frame - const frame = this.animator.getCurrentFrame(); - - // Render the sprite with proper scaling - if (frame) { - for (let y = 0; y < this.height; y++) { - for (let x = 0; x < this.width; x++) { - // Sample the sprite pixel with bilinear interpolation for smoother scaling - const pixelX = Math.floor(x * (frame.width / this.width)); - const pixelY = Math.floor(y * (frame.height / this.height)); - - // Get pixel color from the sprite frame - const idx = (pixelY * frame.width + pixelX) * 4; - const r = frame.data[idx] / 255; - const g = frame.data[idx + 1] / 255; - const b = frame.data[idx + 2] / 255; - const a = frame.data[idx + 3] / 255; - - if (a > 0.5) { - // Draw the pixel - buffer.setCell( - this.x + x, - this.y + y, - ' ', - RGBA.fromValues(0, 0, 0, 0), - RGBA.fromValues(r, g, b, a) - ); - } - } - } - } - } - - // Control methods - public play(): void { - if (this.animator) this.animator.play(); - } - - public pause(): void { - if (this.animator) this.animator.pause(); - } - - public stop(): void { - if (this.animator) this.animator.stop(); - } - - public setFrame(frame: number): void { - if (this.animator) this.animator.setFrame(frame); - } - - public setSpeed(speed: number): void { - if (this.animator) this.animator.setSpeed(speed); - } -} -``` - -## Sprite Particle Effects - -OpenTUI provides classes for creating particle effects using sprites. - -### Exploding Sprite Effect - -The `ExplodingSpriteEffect` class creates an explosion effect from a sprite. - -```typescript -import { ExplodingSpriteEffect } from '@opentui/core/3d'; - -// Create an exploding sprite effect -const explosion = new ExplodingSpriteEffect({ - sprite: sprite, - position: { x: 50, y: 50 }, - particleCount: 50, - minSpeed: 10, - maxSpeed: 30, - minLifetime: 500, - maxLifetime: 1500, - gravity: { x: 0, y: 9.8 }, - fadeOut: true -}); - -// Start the effect -explosion.start(); - -// Update the effect -explosion.update(deltaTime); - -// Render the effect -explosion.render(buffer); - -// Check if the effect is complete -const isComplete = explosion.isComplete(); -``` - -### Physics-Based Exploding Sprite Effect - -The `PhysicsExplodingSpriteEffect` class creates a physics-based explosion effect. - -```typescript -import { PhysicsExplodingSpriteEffect } from '@opentui/core/3d'; -import { PlanckPhysicsAdapter } from '@opentui/core/3d'; - -// Create a physics adapter -const physics = new PlanckPhysicsAdapter({ - gravity: { x: 0, y: 9.8 }, - scale: 30 -}); - -// Create a physics-based exploding sprite effect -const explosion = new PhysicsExplodingSpriteEffect({ - sprite: sprite, - position: { x: 50, y: 50 }, - particleCount: 50, - minImpulse: 1, - maxImpulse: 5, - minAngularVelocity: -5, - maxAngularVelocity: 5, - minLifetime: 500, - maxLifetime: 1500, - physics: physics, - fadeOut: true -}); - -// Start the effect -explosion.start(); - -// Update the effect -explosion.update(deltaTime); - -// Render the effect -explosion.render(buffer); - -// Check if the effect is complete -const isComplete = explosion.isComplete(); -``` - -### Sprite Particle Generator - -The `SpriteParticleGenerator` class provides a more general-purpose particle system. - -```typescript -import { SpriteParticleGenerator } from '@opentui/core/3d'; - -// Create a sprite particle generator -const particleGenerator = new SpriteParticleGenerator({ - sprite: sprite, - position: { x: 50, y: 50 }, - emissionRate: 10, // particles per second - minSpeed: 10, - maxSpeed: 30, - minDirection: 0, - maxDirection: Math.PI * 2, - minLifetime: 500, - maxLifetime: 1500, - minScale: 0.5, - maxScale: 1.5, - gravity: { x: 0, y: 9.8 }, - fadeOut: true -}); - -// Start the generator -particleGenerator.start(); - -// Stop the generator -particleGenerator.stop(); - -// Update the generator -particleGenerator.update(deltaTime); - -// Render the particles -particleGenerator.render(buffer); - -// Set the position -particleGenerator.setPosition({ x: 60, y: 40 }); - -// Set the emission rate -particleGenerator.setEmissionRate(20); -``` - -## Example: Character Animation - -Here's a complete example of a character animation: - -```typescript -import { createCliRenderer, BoxRenderable } from '@opentui/core'; -import { SpriteAnimator, SpriteResourceManager } from '@opentui/core/3d'; - -class CharacterRenderable extends BoxRenderable { - private spriteManager: SpriteResourceManager; - private idleAnimator: SpriteAnimator; - private runAnimator: SpriteAnimator; - private jumpAnimator: SpriteAnimator; - private currentAnimator: SpriteAnimator; - private state: 'idle' | 'run' | 'jump' = 'idle'; - - constructor(id: string, options = {}) { - super(id, { - width: 16, - height: 24, - position: 'absolute', - x: 50, - y: 50, - border: false, - ...options - }); - - this.spriteManager = new SpriteResourceManager(); - this.loadAnimations(); - } - - private async loadAnimations() { - // Load sprite sheets - const idleSprite = await this.spriteManager.loadSprite('src/examples/assets/main_char_idle.png'); - const runSprite = await this.spriteManager.loadSprite('src/examples/assets/main_char_run_loop.png'); - const jumpSprite = await this.spriteManager.loadSprite('src/examples/assets/main_char_jump_start.png'); - - // Create animators - this.idleAnimator = new SpriteAnimator({ - spriteSheet: idleSprite, - frameWidth: 16, - frameHeight: 24, - frameCount: 4, - frameDuration: 200, - loop: true - }); - - this.runAnimator = new SpriteAnimator({ - spriteSheet: runSprite, - frameWidth: 16, - frameHeight: 24, - frameCount: 6, - frameDuration: 100, - loop: true - }); - - this.jumpAnimator = new SpriteAnimator({ - spriteSheet: jumpSprite, - frameWidth: 16, - frameHeight: 24, - frameCount: 3, - frameDuration: 150, - loop: false - }); - - // Set the current animator to idle - this.currentAnimator = this.idleAnimator; - this.currentAnimator.play(); - } - - public setState(state: 'idle' | 'run' | 'jump') { - if (this.state === state) return; - - this.state = state; - - // Stop the current animator - if (this.currentAnimator) { - this.currentAnimator.stop(); - } - - // Set the new animator - switch (state) { - case 'idle': - this.currentAnimator = this.idleAnimator; - break; - case 'run': - this.currentAnimator = this.runAnimator; - break; - case 'jump': - this.currentAnimator = this.jumpAnimator; - break; - } - - // Start the new animator - if (this.currentAnimator) { - this.currentAnimator.play(); - } - } - - protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { - if (!this.currentAnimator) return; - - // Update the animation - this.currentAnimator.update(deltaTime); - - // Get the current frame - const frame = this.currentAnimator.getCurrentFrame(); - - // Render the sprite - if (frame) { - for (let y = 0; y < this.height; y++) { - for (let x = 0; x < this.width; x++) { - // Sample the sprite pixel - const pixelX = Math.floor(x * (frame.width / this.width)); - const pixelY = Math.floor(y * (frame.height / this.height)); - - // Get pixel color from the sprite frame - const idx = (pixelY * frame.width + pixelX) * 4; - const r = frame.data[idx] / 255; - const g = frame.data[idx + 1] / 255; - const b = frame.data[idx + 2] / 255; - const a = frame.data[idx + 3] / 255; - - if (a > 0.5) { - // Draw the pixel - buffer.setCell( - this.x + x, - this.y + y, - ' ', - RGBA.fromValues(0, 0, 0, 0), - RGBA.fromValues(r, g, b, a) - ); - } - } - } - } - - // If jump animation is complete, go back to idle - if (this.state === 'jump' && !this.currentAnimator.isPlaying()) { - this.setState('idle'); - } - } -} - -async function createCharacterAnimationDemo() { - // Create the renderer - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222' - }); - - root.add(container); - - // Create a character - const character = new CharacterRenderable('character'); - container.add(character); - - // Handle keyboard input - renderer.on('key', (key) => { - const keyStr = key.toString(); - - if (keyStr === 'ArrowRight' || keyStr === 'ArrowLeft') { - character.setState('run'); - - // Move the character - if (keyStr === 'ArrowRight') { - character.x += 1; - } else { - character.x -= 1; - } - } else if (keyStr === 'ArrowUp' || keyStr === ' ') { - character.setState('jump'); - } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C - renderer.destroy(); - process.exit(0); - } else { - character.setState('idle'); - } - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the character animation demo -createCharacterAnimationDemo().catch(console.error); -``` diff --git a/packages/core/docs/api/3d/sprite-animator.md b/packages/core/docs/api/3d/sprite-animator.md deleted file mode 100644 index 6c0e820e1..000000000 --- a/packages/core/docs/api/3d/sprite-animator.md +++ /dev/null @@ -1,115 +0,0 @@ -I think sp# SpriteAnimator, TiledSprite, and Animation (3D Sprite Animation API) - -This page documents the sprite animation system implemented in `packages/core/src/3d/animation/SpriteAnimator.ts`. It covers the Animation helper, TiledSprite instance, and the SpriteAnimator manager that creates and updates animated, instanced sprites. - -## Key concepts - -- Sprite sheets are represented by `SpriteResource` objects (see sprites.md). -- Animations are defined per-sprite with a mapping of animation names to `AnimationDefinition` objects (number of frames, offsets, frame duration, looping, flip flags). -- The animator uses instanced rendering (InstancedMesh) with per-instance attributes for frame index and flip flags, allowing many sprites to be drawn efficiently. - -## Types / Interfaces - -- `AnimationStateConfig` — config provided for a specific animation (imagePath, sheetNumFrames, animNumFrames, animFrameOffset, frameDuration, loop, initialFrame, flipX, flipY) -- `ResolvedAnimationState` — resolved state with texture and tileset sizes -- `SpriteDefinition` — top-level sprite definition: `initialAnimation` and `animations` map -- `SpriteDefinition` example: - ```ts - const spriteDef: SpriteDefinition = { - initialAnimation: "idle", - animations: { - idle: { resource, animNumFrames: 4, frameDuration: 100, loop: true }, - run: { resource, animNumFrames: 6, frameDuration: 75, loop: true }, - }, - scale: 1.0 - } - ``` - -## Class: Animation (internal per-sprite animation instance) - -Used internally by `TiledSprite`. Main responsibilities: -- Track `currentLocalFrame`, `timeAccumulator`, `isPlaying`, `_isActive` -- Manage per-instance attributes: - - `a_frameIndexInstanced` (frame index per instance) - - `a_flipInstanced` (flipX / flipY per instance) -- Methods: - - `activate(worldTransform: Matrix4)` — enable and place the instance - - `deactivate()` — hide and stop updates - - `updateVisuals(worldTransform: Matrix4)` — update instance matrix transform - - `updateTime(deltaTimeMs: number): boolean` — advance frames based on `frameDuration`; returns true if frame attribute updated - - `play()`, `stop()`, `goToFrame()`, `setFrameDuration()`, `getResource()`, `releaseInstanceSlot()` - -Notes: -- Frame attributes are updated by setting `frameAttribute.setX(instanceIndex, absoluteFrame)` and marking `needsUpdate = true`. - -## Class: TiledSprite - -Represents a single logical sprite (which may contain multiple instanced animations internally, e.g., frames from different sprite sheets). - -Constructor: -```ts -new TiledSprite( - id: string, - userSpriteDefinition: SpriteDefinition, - animator: SpriteAnimator, - animationInstanceParams: Array<{ name, state, resource, index, instanceManager, frameAttribute, flipAttribute }> -) -``` - -Public API: -- `play()`, `stop()`, `goToFrame(frame)`, `setFrameDuration(ms)` -- `setPosition(Vector3)`, `setRotation(Quaternion)`, `setScale(Vector3)` and `setTransform(position, quaternion, scale)` -- `setAnimation(animationName: string): Promise` — switch animation (activates/deactivates instance slots accordingly) -- `update(deltaTime: number)` — called by animator to advance animation timing -- `destroy()` — release instance slots and cleanup -- `visible` getter/setter — toggles per-instance activation (hiding / showing) -- Accessors: `getCurrentAnimationName()`, `getWorldTransform()`, `getWorldPlaneSize()`, `definition`, `currentTransform` - -Notes: -- `TiledSprite` computes instance matrix scale based on sprite definition scale and sheet aspect ratio to preserve pixel aspect. -- The class stores a transform object used to compute world matrix for the instanced mesh. - -## Class: SpriteAnimator - -Manager that creates TiledSprite instances and manages instance pools. - -Constructor: -```ts -new SpriteAnimator(scene: THREE.Scene) -``` - -Primary methods: -- `async createSprite(userSpriteDefinition: SpriteDefinition, materialFactory?: () => NodeMaterial): Promise` - - Resolves resources and instance managers for each animation resource, acquires instance slots, creates per-instance attributes, constructs `TiledSprite`. -- `update(deltaTime: number): void` — calls `update` on each TiledSprite (advance time) -- `removeSprite(id: string)`: void — destroy and free instance slot(s) -- `removeAllSprites()`: void - -Instance manager caching: -- `getOrCreateInstanceManager(resource, maxInstances, renderOrder, depthWrite, materialFactory)` - - Builds `geometry` with instanced attributes: - - `a_frameIndexInstanced` (Float32Array, 1 element per instance) - - `a_flipInstanced` (Float32Array, 2 elements per instance) - - Creates material via `createSpriteAnimationMaterial(...)` which builds a NodeMaterial using the resource texture and per-instance attributes. - -Material and shader notes: -- The material uses three/tsl nodes to compute final UV based on per-instance frame index and flip flags. -- `createSpriteAnimationMaterial` packs the per-instance attributes and sets `material.colorNode` to the sampled texture color. - -Example usage: -```ts -const animator = new SpriteAnimator(scene) -const mySprite = await animator.createSprite(spriteDefinition) -animator.update(deltaTimeMs) // in your loop -mySprite.setPosition(new THREE.Vector3(1,2,0)) -mySprite.setAnimation('run') -``` - -Performance tips: -- Choose `maxInstances` sufficiently large when creating sprite definitions to avoid `acquireInstanceSlot` failures. -- Materials are created per resource + options combination and reused via instance manager caching. -- Use `removeSprite` to free instance slots when sprites are no longer needed. - -Next steps: -- Document `SpriteParticleGenerator.ts` (particle spawning helper) and then finalize 3D animation docs. - diff --git a/packages/core/docs/api/3d/sprites.md b/packages/core/docs/api/3d/sprites.md deleted file mode 100644 index 595425390..000000000 --- a/packages/core/docs/api/3d/sprites.md +++ /dev/null @@ -1,192 +0,0 @@ -# 3D Sprite Subsystem - -This page documents the sprite-related utilities used by the 3D renderer: mesh pooling, instanced sprite management, sprite resources, and the SpriteResourceManager. Implementation references: `packages/core/src/3d/SpriteResourceManager.ts` and `packages/core/src/3d/TextureUtils.ts`. - -Primary exported classes: -- MeshPool -- InstanceManager -- SpriteResource -- SpriteResourceManager - ---- - -## MeshPool - -Purpose: reuse InstancedMesh objects to avoid repeated allocation/dispose during dynamic scenes. - -API -```ts -class MeshPool { - acquireMesh(poolId: string, options: { - geometry: () => THREE.BufferGeometry - material: THREE.Material - maxInstances: number - name?: string - }): THREE.InstancedMesh - - releaseMesh(poolId: string, mesh: THREE.InstancedMesh): void - - fill(poolId: string, options: MeshPoolOptions, count: number): void - - clearPool(poolId: string): void - - clearAllPools(): void -} -``` - -Notes: -- `acquireMesh` returns an existing pooled InstancedMesh if available or creates a new one. -- `releaseMesh` returns a mesh into the pool for later reuse. -- `fill` preallocates `count` meshes for a pool. -- `clearPool` disposes geometry and materials of meshes in the pool. -- `clearAllPools` clears every pool. - -Example: -```ts -const pool = new MeshPool() - -const mesh = pool.acquireMesh('sprites', { - geometry: () => new THREE.PlaneGeometry(1, 1), - material: spriteMaterial, - maxInstances: 100, - name: 'spriteMesh' -}) - -// use mesh in scene... -pool.releaseMesh('sprites', mesh) -``` - ---- - -## InstanceManager - -Purpose: manage a single `THREE.InstancedMesh` and provide slot allocation for instances (acquire/release per-instance transforms). - -API -```ts -class InstanceManager { - constructor(scene: Scene, geometry: THREE.BufferGeometry, material: THREE.Material, options: { - maxInstances: number - renderOrder?: number - depthWrite?: boolean - name?: string - frustumCulled?: boolean - matrix?: THREE.Matrix4 - }) - - acquireInstanceSlot(): number - releaseInstanceSlot(instanceIndex: number): void - getInstanceCount(): number - getMaxInstances(): number - get hasFreeIndices(): boolean - get mesh(): THREE.InstancedMesh - dispose(): void -} -``` - -Behavior: -- Constructor creates an `InstancedMesh` with `maxInstances` capacity and registers it on `scene`. -- `acquireInstanceSlot()` returns an available instance index; throws if none available. -- `releaseInstanceSlot()` marks the index free and resets the instance transform to a hidden matrix. -- `mesh` returns the underlying InstancedMesh for adding instance-specific attributes (colors/UVs) or custom settings. -- `dispose()` removes the mesh from the scene and disposes geometry/material. - -Example: -```ts -const manager = new InstanceManager(scene, new THREE.PlaneGeometry(1,1), spriteMaterial, { maxInstances: 100, name: 'sprites' }) -const idx = manager.acquireInstanceSlot() - -// set transform -const mat = new THREE.Matrix4().makeTranslation(x, y, z) -manager.mesh.setMatrixAt(idx, mat) -manager.mesh.instanceMatrix.needsUpdate = true - -// later -manager.releaseInstanceSlot(idx) -``` - ---- - -## SpriteResource - -Purpose: represent a loaded sprite sheet texture and provide a `MeshPool` and helpers for instance managers. - -API -```ts -class SpriteResource { - constructor(texture: THREE.DataTexture, sheetProperties: { - imagePath: string - sheetTilesetWidth: number - sheetTilesetHeight: number - sheetNumFrames: number - }, scene: Scene) - - get texture(): THREE.DataTexture - get sheetProperties(): SheetProperties - get meshPool(): MeshPool - - createInstanceManager(geometry: THREE.BufferGeometry, material: THREE.Material, options: InstanceManagerOptions): InstanceManager - - get uvTileSize(): THREE.Vector2 - - dispose(): void -} -``` - -Notes: -- `uvTileSize` returns the normalized tile size for UV mapping based on `sheetNumFrames`. -- `createInstanceManager` is a convenience to create an `InstanceManager` bound to this resource and scene. -- `dispose` clears the internal mesh pools. - -Example: -```ts -const tex = await TextureUtils.fromFile('spritesheet.png') -const sheet = { imagePath: 'spritesheet.png', sheetTilesetWidth: tex.image.width, sheetTilesetHeight: tex.image.height, sheetNumFrames: 8 } -const resource = new SpriteResource(tex, sheet, scene) -const manager = resource.createInstanceManager(new THREE.PlaneGeometry(1,1), spriteMaterial, { maxInstances: 200 }) -``` - ---- - -## SpriteResourceManager - -Purpose: central manager to create/load sprite sheet textures (via TextureUtils), cache them, and provide `SpriteResource` objects. - -API -```ts -class SpriteResourceManager { - constructor(scene: Scene) - - getOrCreateResource(texture: THREE.DataTexture, sheetProps: SheetProperties): Promise - - createResource(config: { imagePath: string, sheetNumFrames: number }): Promise - - clearCache(): void -} -``` - -Behavior: -- `createResource` loads texture via `TextureUtils.fromFile(imagePath)` and builds `SheetProperties` from the loaded texture dimensions and `sheetNumFrames`. -- Resources and raw texture objects are cached by `imagePath` key. -- `clearCache` clears both resource and texture caches. - -Example: -```ts -const manager = new SpriteResourceManager(scene) -const resource = await manager.createResource({ imagePath: 'spritesheet.png', sheetNumFrames: 8 }) -const instanceManager = resource.createInstanceManager(geometry, material, { maxInstances: 100 }) -``` - ---- - -## TextureUtils (note) - -The manager uses `TextureUtils.fromFile(path)` to load a `THREE.DataTexture`. Refer to `packages/core/src/3d/TextureUtils.ts` for exact signature and supported file formats. - ---- - -## Recommendations - -- Use `SpriteResourceManager` to centralize loading of sprite atlases and reuse textures across scenes. -- Use `MeshPool` and `InstanceManager` for high-performance instanced rendering — they avoid frequent allocation and GPU buffer churn. -- When using sprite sheets, compute UV offsets using `SpriteResource.uvTileSize` and set instance UV attributes accordingly. diff --git a/packages/core/docs/api/3d/webgpu.md b/packages/core/docs/api/3d/webgpu.md deleted file mode 100644 index 03f6601cd..000000000 --- a/packages/core/docs/api/3d/webgpu.md +++ /dev/null @@ -1,452 +0,0 @@ -# WebGPU Integration - -OpenTUI provides powerful 3D rendering capabilities through WebGPU integration, allowing you to create rich visual experiences in the terminal. - -## Overview - -The WebGPU integration consists of: - -1. **ThreeCliRenderer**: A renderer that integrates Three.js with WebGPU -2. **CLICanvas**: A canvas implementation for rendering to the terminal -3. **Supersampling**: Techniques for improving rendering quality -4. **Shaders**: Custom WebGPU shaders for visual effects - -## ThreeCliRenderer API - -The `ThreeCliRenderer` class provides a bridge between Three.js and the terminal: - -```typescript -import { - ThreeCliRenderer, - ThreeCliRendererOptions, - SuperSampleType, - createCliRenderer, - Scene, - PerspectiveCamera, - OrthographicCamera, - RGBA -} from '@opentui/core'; - -// Create a CLI renderer -const renderer = await createCliRenderer(); - -// Create a Three.js scene -const scene = new Scene(); - -// Create a ThreeCliRenderer -const threeRenderer = new ThreeCliRenderer(renderer, { - width: 80, - height: 40, - focalLength: 50, - backgroundColor: RGBA.fromHex('#000000'), - superSample: SuperSampleType.GPU, - alpha: false, - autoResize: true -}); - -// Initialize the renderer -await threeRenderer.init(); - -// Set the active camera -const camera = new PerspectiveCamera(75, threeRenderer.aspectRatio, 0.1, 1000); -camera.position.set(0, 0, 5); -camera.lookAt(0, 0, 0); -threeRenderer.setActiveCamera(camera); - -// Draw the scene -renderer.on('update', async (context) => { - await threeRenderer.drawScene(scene, renderer.buffer, context.deltaTime); -}); - -// Start the renderer -renderer.start(); -``` - -### Renderer Options - -The `ThreeCliRenderer` constructor accepts the following options: - -```typescript -interface ThreeCliRendererOptions { - width: number; // Output width in characters - height: number; // Output height in characters - focalLength?: number; // Camera focal length - backgroundColor?: RGBA; // Background color - superSample?: SuperSampleType; // Supersampling type - alpha?: boolean; // Enable alpha blending - autoResize?: boolean; // Automatically resize on terminal resize - libPath?: string; // Path to WebGPU library -} -``` - -### Supersampling - -The renderer supports three supersampling modes to improve rendering quality: - -```typescript -enum SuperSampleType { - NONE = "none", // No supersampling - GPU = "gpu", // GPU-based supersampling - CPU = "cpu" // CPU-based supersampling -} -``` - -You can toggle between supersampling modes: - -```typescript -// Toggle between supersampling modes -threeRenderer.toggleSuperSampling(); - -// Set a specific supersampling algorithm -threeRenderer.setSuperSampleAlgorithm(SuperSampleAlgorithm.PRE_SQUEEZED); -``` - -### Camera Control - -You can set and get the active camera: - -```typescript -// Set the active camera -threeRenderer.setActiveCamera(camera); - -// Get the active camera -const activeCamera = threeRenderer.getActiveCamera(); -``` - -### Resizing - -You can resize the renderer: - -```typescript -// Resize the renderer -threeRenderer.setSize(100, 50); -``` - -### Saving to File - -You can save the rendered scene to a file: - -```typescript -// Save the current frame to a file -await threeRenderer.saveToFile('screenshot.png'); -``` - -### Cleanup - -When you're done with the renderer, you should destroy it to free resources: - -```typescript -// Destroy the renderer -threeRenderer.destroy(); -``` - -## CLICanvas API - -The `CLICanvas` class provides a canvas implementation for rendering to the terminal: - -```typescript -import { CLICanvas, SuperSampleAlgorithm, SuperSampleType } from '@opentui/core'; - -// Create a canvas (typically done by ThreeCliRenderer) -const canvas = new CLICanvas( - device, // WebGPU device - width, // Canvas width - height, // Canvas height - SuperSampleType.GPU, - SuperSampleAlgorithm.STANDARD -); - -// Set the canvas size -canvas.setSize(width, height); - -// Set the supersampling mode -canvas.setSuperSample(SuperSampleType.GPU); - -// Set the supersampling algorithm -canvas.setSuperSampleAlgorithm(SuperSampleAlgorithm.PRE_SQUEEZED); - -// Read pixels into a buffer -await canvas.readPixelsIntoBuffer(buffer); - -// Save the canvas to a file -await canvas.saveToFile('screenshot.png'); -``` - -## Supersampling Algorithms - -OpenTUI supports two supersampling algorithms: - -```typescript -enum SuperSampleAlgorithm { - STANDARD = 0, // Standard supersampling - PRE_SQUEEZED = 1 // Pre-squeezed supersampling (better for text) -} -``` - -## Integration with Three.js - -The WebGPU integration works with Three.js to provide a familiar API for 3D rendering: - -```typescript -import { - Scene, - Mesh, - BoxGeometry, - MeshPhongNodeMaterial, - DirectionalLight, - Color -} from '@opentui/core'; - -// Create a scene -const scene = new Scene(); - -// Add a light -const light = new DirectionalLight(0xffffff, 1); -light.position.set(1, 1, 1); -scene.add(light); - -// Create a mesh -const geometry = new BoxGeometry(1, 1, 1); -const material = new MeshPhongNodeMaterial({ - color: new Color(0x3498db), - emissive: new Color(0x000000), - specular: new Color(0x111111), - shininess: 30 -}); -const cube = new Mesh(geometry, material); -scene.add(cube); - -// Animate the cube -renderer.on('update', (context) => { - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; -}); -``` - -## WebGPU Shaders - -OpenTUI supports custom WebGPU shaders for advanced visual effects: - -```typescript -import { - Scene, - Mesh, - BoxGeometry, - ShaderMaterial, - WebGPURenderer -} from '@opentui/core'; - -// Create a shader material -const material = new ShaderMaterial({ - vertexShader: ` - @vertex - fn main(@location(0) position: vec3) -> @builtin(position) vec4 { - return vec4(position, 1.0); - } - `, - fragmentShader: ` - @fragment - fn main() -> @location(0) vec4 { - return vec4(1.0, 0.0, 0.0, 1.0); - } - ` -}); - -// Create a mesh with the shader material -const geometry = new BoxGeometry(1, 1, 1); -const cube = new Mesh(geometry, material); -scene.add(cube); -``` - -## Performance Considerations - -The WebGPU integration is designed for performance, but there are some considerations: - -- **Supersampling**: Supersampling improves quality but reduces performance -- **Resolution**: Higher resolutions require more GPU memory and processing power -- **Complexity**: Complex scenes with many objects will be slower -- **Shaders**: Custom shaders can be expensive, especially with complex calculations - -For best performance: - -- Use appropriate resolution for your terminal -- Use supersampling only when needed -- Optimize your Three.js scene (reduce polygon count, use efficient materials) -- Use GPU-based supersampling when possible - -## Example: Creating a 3D Cube - -```typescript -import { - createCliRenderer, - ThreeCliRenderer, - SuperSampleType, - Scene, - PerspectiveCamera, - BoxGeometry, - Mesh, - MeshPhongNodeMaterial, - DirectionalLight, - Color, - RGBA -} from '@opentui/core'; - -async function createCubeDemo() { - // Create a CLI renderer - const renderer = await createCliRenderer(); - - // Create a Three.js scene - const scene = new Scene(); - - // Create a ThreeCliRenderer - const threeRenderer = new ThreeCliRenderer(renderer, { - width: 80, - height: 40, - backgroundColor: RGBA.fromHex('#000000'), - superSample: SuperSampleType.GPU - }); - - // Initialize the renderer - await threeRenderer.init(); - - // Create a camera - const camera = new PerspectiveCamera(75, threeRenderer.aspectRatio, 0.1, 1000); - camera.position.set(0, 0, 5); - camera.lookAt(0, 0, 0); - threeRenderer.setActiveCamera(camera); - - // Add a light - const light = new DirectionalLight(0xffffff, 1); - light.position.set(1, 1, 1); - scene.add(light); - - // Create a cube - const geometry = new BoxGeometry(2, 2, 2); - const material = new MeshPhongNodeMaterial({ - color: new Color(0x3498db), - emissive: new Color(0x000000), - specular: new Color(0x111111), - shininess: 30 - }); - const cube = new Mesh(geometry, material); - scene.add(cube); - - // Animate the cube - renderer.on('update', async (context) => { - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - await threeRenderer.drawScene(scene, renderer.buffer, context.deltaTime); - }); - - // Handle keyboard input - renderer.on('key', (data) => { - const key = data.toString(); - - if (key === 's') { - threeRenderer.toggleSuperSampling(); - } else if (key === 'q') { - renderer.destroy(); - process.exit(0); - } - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the cube demo -createCubeDemo().catch(console.error); -``` - -## Example: Creating a Shader Effect - -```typescript -import { - createCliRenderer, - ThreeCliRenderer, - SuperSampleType, - Scene, - PerspectiveCamera, - PlaneGeometry, - Mesh, - ShaderMaterial, - RGBA -} from '@opentui/core'; - -async function createShaderDemo() { - // Create a CLI renderer - const renderer = await createCliRenderer(); - - // Create a Three.js scene - const scene = new Scene(); - - // Create a ThreeCliRenderer - const threeRenderer = new ThreeCliRenderer(renderer, { - width: 80, - height: 40, - backgroundColor: RGBA.fromHex('#000000'), - superSample: SuperSampleType.GPU - }); - - // Initialize the renderer - await threeRenderer.init(); - - // Create a camera - const camera = new PerspectiveCamera(75, threeRenderer.aspectRatio, 0.1, 1000); - camera.position.set(0, 0, 5); - camera.lookAt(0, 0, 0); - threeRenderer.setActiveCamera(camera); - - // Create a shader material - const material = new ShaderMaterial({ - vertexShader: ` - @vertex - fn main(@location(0) position: vec3, - @location(1) uv: vec2) -> @builtin(position) vec4 { - return vec4(position, 1.0); - } - `, - fragmentShader: ` - @group(0) @binding(0) var time: f32; - - @fragment - fn main(@location(0) uv: vec2) -> @location(0) vec4 { - let color = vec3( - sin(uv.x * 10.0 + time) * 0.5 + 0.5, - sin(uv.y * 10.0 + time * 0.5) * 0.5 + 0.5, - sin((uv.x + uv.y) * 5.0 + time * 0.2) * 0.5 + 0.5 - ); - return vec4(color, 1.0); - } - `, - uniforms: { - time: { value: 0 } - } - }); - - // Create a plane with the shader material - const geometry = new PlaneGeometry(4, 4); - const plane = new Mesh(geometry, material); - scene.add(plane); - - // Animate the shader - let time = 0; - renderer.on('update', async (context) => { - time += context.deltaTime; - material.uniforms.time.value = time; - - await threeRenderer.drawScene(scene, renderer.buffer, context.deltaTime); - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the shader demo -createShaderDemo().catch(console.error); -``` diff --git a/packages/core/docs/api/3d/wgpu-renderer.md b/packages/core/docs/api/3d/wgpu-renderer.md deleted file mode 100644 index fd7ca0ea1..000000000 --- a/packages/core/docs/api/3d/wgpu-renderer.md +++ /dev/null @@ -1,161 +0,0 @@ -# WGPURenderer / ThreeCliRenderer (3D Renderer API) - -This page documents the ThreeCliRenderer class which integrates Three.js WebGPU rendering with OpenTUI's CLI renderer. Implementation reference: `packages/core/src/3d/WGPURenderer.ts`. - -ThreeCliRenderer renders a Three.js `Scene` to a GPU-backed canvas (CLICanvas) and copies the resulting pixels into an OpenTUI `OptimizedBuffer`. It supports optional supersampling (CPU or GPU) and exposes render statistics. - -## Key types - -- Scene — three.js scene to render -- PerspectiveCamera / OrthographicCamera — three.js cameras -- OptimizedBuffer — OpenTUI framebuffer (see buffer.md) -- SuperSampleType — enum: NONE | GPU | CPU -- SuperSampleAlgorithm — defined in `canvas` module - -## SuperSampleType - -```ts -export enum SuperSampleType { - NONE = "none", - GPU = "gpu", - CPU = "cpu", -} -``` - -Controls supersampling mode used by the renderer. - -## Interface: ThreeCliRendererOptions - -```ts -export interface ThreeCliRendererOptions { - width: number - height: number - focalLength?: number - backgroundColor?: RGBA - superSample?: SuperSampleType - alpha?: boolean - autoResize?: boolean - libPath?: string -} -``` - -- width/height: output terminal cell dimensions -- focalLength: optional camera focal length (used to compute FOV) -- backgroundColor: RGBA background color for clear -- superSample: initial supersampling mode (NONE/CPU/GPU) -- alpha: whether to use alpha in clear color -- autoResize: if true (default), ThreeCliRenderer listens to CliRenderer resize events -- libPath: optional native lib path passed to setupGlobals - -## Class: ThreeCliRenderer - -### Constructor - -```ts -new ThreeCliRenderer(cliRenderer: CliRenderer, options: ThreeCliRendererOptions) -``` - -- `cliRenderer` — OpenTUI CliRenderer instance used to receive resize/debug events and to integrate lifecycle. -- `options` — see ThreeCliRendererOptions. - -### Lifecycle - -- async init(): Promise - - Creates a WebGPU device, constructs a `CLICanvas` and a `WebGPURenderer` (three.js). - - Initializes three renderer and sets render method to internal draw function. - - Should be called before use. - -- destroy(): void - - Removes event listeners, disposes the three renderer, releases GPU references; resets internal state. - -### Rendering - -- async drawScene(root: Scene, buffer: OptimizedBuffer, deltaTime: number): Promise - - Public entry: draws the provided scene into the provided OptimizedBuffer. Internally calls `renderMethod` which is set to either `doDrawScene` or a no-op depending on initialization. - -- private async doDrawScene(root, camera, buffer, deltaTime): Promise - - Internal implementation that: - 1. Calls `threeRenderer.render(root, camera)` - 2. Calls `canvas.readPixelsIntoBuffer(buffer)` to transfer GPU pixels into the OptimizedBuffer - 3. Measures render/readback timings (renderTimeMs, readbackTimeMs, totalDrawTimeMs) - - It guards against concurrent calls (logs and returns if called concurrently). - -### Cameras and viewport - -- setActiveCamera(camera: PerspectiveCamera | OrthographicCamera): void -- getActiveCamera(): PerspectiveCamera | OrthographicCamera -- get aspectRatio(): number - - Computes the aspect ratio based on configured aspect override, renderer resolution, or terminal dimensions. - -- setSize(width: number, height: number, forceUpdate: boolean = false): void - - Updates output size, recomputes renderWidth/renderHeight (accounts for supersampling), resizes the CLICanvas and three renderer, and updates camera.aspect and projection matrix. - -### Supersampling control & stats - -- toggleSuperSampling(): void - - Cycles between NONE -> CPU -> GPU -> NONE and updates canvas state & sizes. - -- setSuperSampleAlgorithm(superSampleAlgorithm: SuperSampleAlgorithm): void -- getSuperSampleAlgorithm(): SuperSampleAlgorithm - -- saveToFile(filePath: string): Promise - - Proxy to canvas.saveToFile(filePath) to write a screenshot. - -- toggleDebugStats(): void - - Toggle internal flag to show render stats overlay. - -- renderStats(buffer: OptimizedBuffer): void - - Writes a small debug overlay of timing stats into the provided OptimizedBuffer using `buffer.drawText(...)`. - -### Performance notes - -- `ThreeCliRenderer` measures: - - `renderTimeMs` — time to run threeRenderer.render - - `readbackTimeMs` — time to transfer pixels to the OptimizedBuffer - - `canvas.mapAsyncTimeMs` — time to map GPU readback buffer - - `canvas.superSampleDrawTimeMs` — time spent converting supersampled output to framebuffer -- When `doRenderStats` is enabled (debug overlay), `renderStats` writes formatted timing lines into the buffer. - -### Event integration - -- By default (`autoResize !== false`) the renderer registers a handler on `cliRenderer` resize events to call `setSize(...)`. -- It listens for `CliRenderEvents.DEBUG_OVERLAY_TOGGLE` to update debug stat visibility. - -## Example usage - -```ts -import { createCliRenderer } from '@opentui/core' -import { ThreeCliRenderer, SuperSampleType } from '@opentui/core/3d/WGPURenderer' // pseudo import -import { Scene, PerspectiveCamera } from 'three' - -async function main() { - const cli = await createCliRenderer() - const threeRenderer = new ThreeCliRenderer(cli, { - width: 80, - height: 24, - superSample: SuperSampleType.GPU, - alpha: false - }) - - await threeRenderer.init() - - const scene = new Scene() - const camera = threeRenderer.getActiveCamera() as PerspectiveCamera - - // On each frame, create or reuse an OptimizedBuffer and render: - const buffer = cli.root.getBuffer() // pseudocode — use actual renderer buffer access - await threeRenderer.drawScene(scene, buffer, 16) -} -``` - -## Integration notes - -- `ThreeCliRenderer` relies on `CLICanvas` to handle readback and supersampling. See `packages/core/docs/api/3d/canvas.md`. -- Use GPU supersampling (GPU) for best quality and performance when a GPU is available; CPU supersampling is a fallback. -- The `WebGPURenderer` (three.js) expects an HTMLCanvas-like object — `CLICanvas` provides a `GPUCanvasContextMock` for use in non-browser environments. - ---- - -Next steps I can take: -- Document the sprite subsystem (SpriteResourceManager, SpriteUtils, animations) and the physics adapters (Planck and Rapier). -- Create an examples page showing a minimal three.js scene wired to ThreeCliRenderer. diff --git a/packages/core/docs/api/README.md b/packages/core/docs/api/README.md deleted file mode 100644 index 71d297723..000000000 --- a/packages/core/docs/api/README.md +++ /dev/null @@ -1,121 +0,0 @@ -# OpenTUI API Reference - -OpenTUI is a TypeScript library for building rich terminal user interfaces with support for layouts, animations, 3D graphics, and interactive components. - -## Core Modules - -### Renderer -- [`CliRenderer`](./renderer.md) - Main terminal renderer class -- [`createCliRenderer`](./renderer.md#createclirenderer) - Factory function to create renderer instances - -### Components -- [`Renderable`](./renderable.md) - Base class for all UI components -- [`BoxRenderable`](./components/box.md) - Container with borders and background -- [`TextRenderable`](./components/text.md) - Text display component -- [`GroupRenderable`](./components/group.md) - Layout container for child components -- [`Input`](./components/input.md) - Text input field -- [`Select`](./components/select.md) - Selection list component -- [`TabSelect`](./components/tab-select.md) - Tab-based selection -- [`FrameBuffer`](./components/framebuffer.md) - Offscreen rendering buffer -- [`ASCIIFont`](./components/ascii-font.md) - ASCII art text rendering - -### Layout System -- [Yoga Layout Integration](./layout.md) - Flexbox-based layout system -- [Position Types](./layout.md#position-types) - Absolute, relative positioning -- [Flex Properties](./layout.md#flex-properties) - Flexbox configuration - -### Styling -- [`StyledText`](./styled-text.md) - Rich text formatting -- [`RGBA`](./colors.md) - Color management -- [Border Styles](./borders.md) - Border configuration - -### Input Handling -- [`KeyHandler`](./input/keys.md) - Keyboard input management -- [`MouseEvent`](./input/mouse.md) - Mouse interaction handling -- [`Selection`](./input/selection.md) - Text selection utilities - -### Animation -- [`Timeline`](./animation/timeline.md) - Animation sequencing -- [Easing Functions](./animation/easing.md) - Animation curves - -### Buffers -- [`OptimizedBuffer`](./buffers.md#optimizedbuffer) - High-performance terminal buffer -- [`TextBuffer`](./buffers.md#textbuffer) - Text rendering buffer - -### 3D Graphics (Optional) -- [`WGPURenderer`](./3d/webgpu.md) - WebGPU-based 3D rendering -- [Sprite System](./3d/sprites.md) - 2D sprites in 3D space -- [Physics Integration](./3d/physics.md) - 2D physics engines - -### Utilities -- [`parseColor`](./utils.md#parsecolor) - Color parsing utility -- [`ANSI`](./utils.md#ansi) - ANSI escape code helpers -- [Console Capture](./utils.md#console) - Console output management - -## Quick Start - -```typescript -import { createCliRenderer, BoxRenderable, TextRenderable } from '@opentui/core' - -// Create renderer -const renderer = await createCliRenderer({ - stdout: process.stdout, - stdin: process.stdin, - useMouse: true, - useAlternateScreen: true -}) - -// Create UI components -const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - borderStyle: 'rounded', - backgroundColor: '#1a1a1a' -}) - -const title = new TextRenderable('title', { - content: 'Welcome to OpenTUI', - fg: '#00ff00', - marginTop: 1, - marginLeft: 2 -}) - -// Build UI hierarchy -container.appendChild(title) -renderer.root.appendChild(container) - -// Handle input -renderer.on('keypress', (key) => { - if (key.name === 'q') { - renderer.cleanup() - process.exit(0) - } -}) - -// Start rendering -renderer.start() -``` - -## Installation - -```bash -bun add @opentui/core -# or -npm install @opentui/core -``` - -## Requirements - -- Bun >= 1.2.0 or Node.js >= 18 -- TypeScript >= 5.0 -- Terminal with ANSI color support -- Optional: GPU support for 3D features - -## Next Steps - -- [Getting Started Guide](../guides/getting-started.md) -- [Component Examples](../examples/README.md) -- [Layout Tutorial](../guides/layouts.md) -- [Animation Guide](../guides/animations.md) - -api version 0.1.7 2025-08-19 wip diff --git a/packages/core/docs/api/advanced/3d.md b/packages/core/docs/api/advanced/3d.md deleted file mode 100644 index 0b5ddf995..000000000 --- a/packages/core/docs/api/advanced/3d.md +++ /dev/null @@ -1,854 +0,0 @@ -# 3D Rendering API - -OpenTUI provides advanced 3D rendering capabilities through integration with Three.js and WebGPU, allowing you to create rich visual experiences in the terminal. - -## Three.js Integration - -OpenTUI integrates with Three.js to provide 3D rendering capabilities in the terminal. - -### Setting Up Three.js - -```typescript -import { createCliRenderer, BoxRenderable } from '@opentui/core'; -import * as THREE from 'three'; - -async function setup3DScene() { - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container for the 3D scene - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - border: false - }); - - root.add(container); - - // Create a Three.js scene - const scene = new THREE.Scene(); - - // Create a camera - const camera = new THREE.PerspectiveCamera( - 75, // Field of view - renderer.width / renderer.height, // Aspect ratio - 0.1, // Near clipping plane - 1000 // Far clipping plane - ); - camera.position.z = 5; - - // Create a renderer - const threeRenderer = new THREE.WebGLRenderer({ alpha: true }); - threeRenderer.setSize(renderer.width, renderer.height); - - // Create a cube - const geometry = new THREE.BoxGeometry(); - const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true }); - const cube = new THREE.Mesh(geometry, material); - scene.add(cube); - - // Animation loop - renderer.setFrameCallback(async (deltaTime) => { - // Rotate the cube - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - // Render the scene - threeRenderer.render(scene, camera); - - // Get the rendered image data - const imageData = threeRenderer.domElement.getContext('2d').getImageData( - 0, 0, renderer.width, renderer.height - ); - - // Convert the 3D rendering to terminal representation - for (let y = 0; y < container.height; y++) { - for (let x = 0; x < container.width; x++) { - // Sample the WebGL output (with proper scaling) - const glX = Math.floor(x * (threeRenderer.domElement.width / container.width)); - const glY = Math.floor(y * (threeRenderer.domElement.height / container.height)); - - const idx = (glY * threeRenderer.domElement.width + glX) * 4; - const r = imageData.data[idx] / 255; - const g = imageData.data[idx + 1] / 255; - const b = imageData.data[idx + 2] / 255; - const a = imageData.data[idx + 3] / 255; - - if (a > 0.1) { - // Draw the pixel to the terminal buffer - container.buffer.setCell( - container.x + x, - container.y + y, - ' ', // Use space character with background color - RGBA.fromValues(0, 0, 0, 0), // Transparent foreground - RGBA.fromValues(r, g, b, a) // Background color from the 3D scene - ); - } - } - } - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the 3D scene -setup3DScene().catch(console.error); -``` - -## WebGPU Integration - -OpenTUI provides WebGPU integration for high-performance graphics rendering. - -### WGPURenderer - -The `WGPURenderer` class provides a WebGPU-based renderer for OpenTUI. - -```typescript -import { WGPURenderer } from '@opentui/core/3d'; - -// Create a WebGPU renderer -const gpuRenderer = new WGPURenderer({ - width: 80, - height: 40, - device: null, // Will be initialized automatically - format: null // Will be initialized automatically -}); - -// Initialize the renderer -await gpuRenderer.initialize(); - -// Create a render pipeline -const pipeline = await gpuRenderer.createRenderPipeline({ - vertex: { - module: device.createShaderModule({ - code: vertexShaderCode - }), - entryPoint: 'main' - }, - fragment: { - module: device.createShaderModule({ - code: fragmentShaderCode - }), - entryPoint: 'main', - targets: [{ format: gpuRenderer.format }] - }, - primitive: { - topology: 'triangle-list' - } -}); - -// Render a frame -gpuRenderer.beginFrame(); -// ... rendering commands ... -gpuRenderer.endFrame(); - -// Get the rendered image -const imageData = gpuRenderer.getImageData(); -``` - -### Example: WebGPU Shader - -```typescript -import { createCliRenderer, BoxRenderable, OptimizedBuffer, RGBA } from '@opentui/core'; -import { WGPURenderer } from '@opentui/core/3d'; - -// Vertex shader -const vertexShaderCode = ` -@vertex -fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4 { - var pos = array, 3>( - vec2(0.0, 0.5), - vec2(-0.5, -0.5), - vec2(0.5, -0.5) - ); - return vec4(pos[VertexIndex], 0.0, 1.0); -} -`; - -// Fragment shader -const fragmentShaderCode = ` -@fragment -fn main() -> @location(0) vec4 { - return vec4(1.0, 0.0, 0.0, 1.0); -} -`; - -async function createShaderDemo() { - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - border: false - }); - - root.add(container); - - // Create a WebGPU renderer - const gpuRenderer = new WGPURenderer({ - width: renderer.width * 2, // Double resolution for better quality - height: renderer.height * 2 - }); - - // Initialize the renderer - await gpuRenderer.initialize(); - - // Create a render pipeline - const pipeline = await gpuRenderer.createRenderPipeline({ - vertex: { - module: gpuRenderer.device.createShaderModule({ - code: vertexShaderCode - }), - entryPoint: 'main' - }, - fragment: { - module: gpuRenderer.device.createShaderModule({ - code: fragmentShaderCode - }), - entryPoint: 'main', - targets: [{ format: gpuRenderer.format }] - }, - primitive: { - topology: 'triangle-list' - } - }); - - // Create a custom renderable for the shader - class ShaderRenderable extends BoxRenderable { - private gpuRenderer: WGPURenderer; - private pipeline: GPURenderPipeline; - - constructor(id: string, gpuRenderer: WGPURenderer, pipeline: GPURenderPipeline, options = {}) { - super(id, { - width: '100%', - height: '100%', - border: false, - ...options - }); - - this.gpuRenderer = gpuRenderer; - this.pipeline = pipeline; - } - - protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { - // Render with WebGPU - this.gpuRenderer.beginFrame(); - - const passEncoder = this.gpuRenderer.commandEncoder.beginRenderPass({ - colorAttachments: [{ - view: this.gpuRenderer.textureView, - clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, - loadOp: 'clear', - storeOp: 'store' - }] - }); - - passEncoder.setPipeline(this.pipeline); - passEncoder.draw(3); // Draw a triangle - passEncoder.end(); - - this.gpuRenderer.endFrame(); - - // Get the rendered image - const imageData = this.gpuRenderer.getImageData(); - - // Convert to terminal representation - for (let y = 0; y < this.height; y++) { - for (let x = 0; x < this.width; x++) { - // Sample the WebGPU output (with proper scaling) - const gpuX = Math.floor(x * (this.gpuRenderer.width / this.width)); - const gpuY = Math.floor(y * (this.gpuRenderer.height / this.height)); - - const idx = (gpuY * this.gpuRenderer.width + gpuX) * 4; - const r = imageData.data[idx] / 255; - const g = imageData.data[idx + 1] / 255; - const b = imageData.data[idx + 2] / 255; - const a = imageData.data[idx + 3] / 255; - - if (a > 0.1) { - // Draw the pixel - buffer.setCell( - this.x + x, - this.y + y, - ' ', // Use space character with background color - RGBA.fromValues(0, 0, 0, 0), - RGBA.fromValues(r, g, b, a) - ); - } - } - } - } - } - - // Create the shader renderable - const shaderView = new ShaderRenderable('shader', gpuRenderer, pipeline); - container.add(shaderView); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the shader demo -createShaderDemo().catch(console.error); -``` - -## Sprite Rendering - -OpenTUI provides sprite rendering capabilities for displaying images in the terminal. - -### SpriteResourceManager - -The `SpriteResourceManager` class manages sprite resources for efficient rendering. - -```typescript -import { SpriteResourceManager } from '@opentui/core/3d'; -import Jimp from 'jimp'; - -// Create a sprite resource manager -const spriteManager = new SpriteResourceManager(); - -// Load a sprite -const sprite = await spriteManager.loadSprite('path/to/sprite.png'); - -// Load a sprite from a Jimp image -const jimpImage = await Jimp.read('path/to/another_sprite.png'); -const spriteFromJimp = await spriteManager.loadSpriteFromJimp(jimpImage); - -// Get a sprite by ID -const cachedSprite = spriteManager.getSprite('sprite_id'); - -// Release a sprite -spriteManager.releaseSprite('sprite_id'); - -// Clear all sprites -spriteManager.clear(); -``` - -### Example: Rendering Sprites - -```typescript -import { createCliRenderer, BoxRenderable, OptimizedBuffer, RGBA } from '@opentui/core'; -import { SpriteResourceManager } from '@opentui/core/3d'; - -async function createSpriteDemo() { - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - border: false, - backgroundColor: '#222222' - }); - - root.add(container); - - // Create a sprite resource manager - const spriteManager = new SpriteResourceManager(); - - // Load sprites - const characterSprite = await spriteManager.loadSprite('path/to/character.png'); - const backgroundSprite = await spriteManager.loadSprite('path/to/background.png'); - - // Create a custom renderable for sprites - class SpriteRenderable extends BoxRenderable { - private sprite: any; - private scale: number; - - constructor(id: string, sprite: any, options = {}) { - super(id, { - width: sprite.width, - height: sprite.height / 2, // Terminal characters are roughly 2:1 ratio - border: false, - position: 'absolute', - ...options - }); - - this.sprite = sprite; - this.scale = options.scale || 1; - } - - protected renderSelf(buffer: OptimizedBuffer): void { - // Render the sprite - for (let y = 0; y < this.height; y++) { - for (let x = 0; x < this.width; x++) { - // Sample the sprite pixel - const spriteX = Math.floor(x / this.scale); - const spriteY = Math.floor(y / this.scale * 2); // Adjust for terminal aspect ratio - - if (spriteX < this.sprite.width && spriteY < this.sprite.height) { - // Get pixel color from the sprite - const idx = (spriteY * this.sprite.width + spriteX) * 4; - const r = this.sprite.data[idx] / 255; - const g = this.sprite.data[idx + 1] / 255; - const b = this.sprite.data[idx + 2] / 255; - const a = this.sprite.data[idx + 3] / 255; - - if (a > 0.5) { - // Draw the pixel - buffer.setCell( - this.x + x, - this.y + y, - ' ', - RGBA.fromValues(0, 0, 0, 0), - RGBA.fromValues(r, g, b, a) - ); - } - } - } - } - } - } - - // Create background sprite - const background = new SpriteRenderable('background', backgroundSprite, { - x: 0, - y: 0, - width: renderer.width, - height: renderer.height, - scale: backgroundSprite.width / renderer.width - }); - - // Create character sprite - const character = new SpriteRenderable('character', characterSprite, { - x: 20, - y: 15, - scale: 0.5 - }); - - // Add sprites to the container - container.add(background); - container.add(character); - - // Add keyboard controls for the character - renderer.on('key', (data) => { - const key = data.toString(); - - switch (key) { - case 'w': - character.y = Math.max(0, character.y - 1); - break; - case 's': - character.y = Math.min(renderer.height - character.height, character.y + 1); - break; - case 'a': - character.x = Math.max(0, character.x - 1); - break; - case 'd': - character.x = Math.min(renderer.width - character.width, character.x + 1); - break; - } - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the sprite demo -createSpriteDemo().catch(console.error); -``` - -## Texture Loading - -OpenTUI provides utilities for loading and managing textures. - -### TextureUtils - -The `TextureUtils` class provides utilities for working with textures. - -```typescript -import { TextureUtils } from '@opentui/core/3d'; -import Jimp from 'jimp'; - -// Load a texture -const texture = await TextureUtils.loadTexture('path/to/texture.png'); - -// Load a texture from a Jimp image -const jimpImage = await Jimp.read('path/to/another_texture.png'); -const textureFromJimp = TextureUtils.textureFromJimp(jimpImage); - -// Create a texture from raw data -const rawData = new Uint8Array([/* RGBA pixel data */]); -const rawTexture = TextureUtils.createTexture(rawData, 32, 32); - -// Resize a texture -const resizedTexture = TextureUtils.resizeTexture(texture, 64, 64); - -// Crop a texture -const croppedTexture = TextureUtils.cropTexture(texture, 10, 10, 20, 20); - -// Get a pixel from a texture -const pixel = TextureUtils.getPixel(texture, 5, 5); -console.log(`RGBA: ${pixel.r}, ${pixel.g}, ${pixel.b}, ${pixel.a}`); -``` - -## Lighting and Materials - -When using Three.js integration, you can create advanced lighting and materials. - -### Example: Phong Lighting - -```typescript -import { createCliRenderer, BoxRenderable } from '@opentui/core'; -import * as THREE from 'three'; - -async function createLightingDemo() { - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - border: false - }); - - root.add(container); - - // Create a Three.js scene - const scene = new THREE.Scene(); - - // Create a camera - const camera = new THREE.PerspectiveCamera( - 75, - renderer.width / renderer.height, - 0.1, - 1000 - ); - camera.position.z = 5; - - // Create a renderer - const threeRenderer = new THREE.WebGLRenderer({ alpha: true }); - threeRenderer.setSize(renderer.width * 2, renderer.height * 2); - - // Create a cube with Phong material - const geometry = new THREE.BoxGeometry(); - const material = new THREE.MeshPhongMaterial({ - color: 0x3498db, - specular: 0xffffff, - shininess: 30 - }); - const cube = new THREE.Mesh(geometry, material); - scene.add(cube); - - // Add lighting - const ambientLight = new THREE.AmbientLight(0x404040); - scene.add(ambientLight); - - const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5); - directionalLight.position.set(1, 1, 1); - scene.add(directionalLight); - - const pointLight = new THREE.PointLight(0xff0000, 1, 100); - pointLight.position.set(2, 2, 2); - scene.add(pointLight); - - // Animation loop - renderer.setFrameCallback(async (deltaTime) => { - // Rotate the cube - cube.rotation.x += 0.01; - cube.rotation.y += 0.01; - - // Move the point light in a circle - const time = Date.now() * 0.001; - pointLight.position.x = Math.sin(time) * 3; - pointLight.position.z = Math.cos(time) * 3; - - // Render the scene - threeRenderer.render(scene, camera); - - // Get the rendered image data - const imageData = threeRenderer.domElement.getContext('2d').getImageData( - 0, 0, threeRenderer.domElement.width, threeRenderer.domElement.height - ); - - // Convert the 3D rendering to terminal representation - for (let y = 0; y < container.height; y++) { - for (let x = 0; x < container.width; x++) { - // Sample the WebGL output with proper scaling - const glX = Math.floor(x * (threeRenderer.domElement.width / container.width)); - const glY = Math.floor(y * (threeRenderer.domElement.height / container.height)); - - const idx = (glY * threeRenderer.domElement.width + glX) * 4; - const r = imageData.data[idx] / 255; - const g = imageData.data[idx + 1] / 255; - const b = imageData.data[idx + 2] / 255; - const a = imageData.data[idx + 3] / 255; - - // Apply lighting effects to enhance visibility in terminal - const brightness = Math.max(r, g, b); - const character = brightness > 0.8 ? '█' : - brightness > 0.6 ? '▓' : - brightness > 0.4 ? '▒' : - brightness > 0.2 ? '░' : ' '; - - // Draw the pixel with appropriate character and color - buffer.setCell( - container.x + x, - container.y + y, - character, - RGBA.fromValues(r, g, b, a), - RGBA.fromValues(0, 0, 0, 1) - ); - } - } - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the lighting demo -createLightingDemo().catch(console.error); -``` - -## Shaders - -OpenTUI supports custom shaders for advanced visual effects. - -### Example: Fractal Shader - -```typescript -import { createCliRenderer, BoxRenderable, OptimizedBuffer, RGBA } from '@opentui/core'; -import { WGPURenderer } from '@opentui/core/3d'; - -// Vertex shader -const vertexShaderCode = ` -@vertex -fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4 { - var pos = array, 6>( - vec2(-1.0, -1.0), - vec2(1.0, -1.0), - vec2(1.0, 1.0), - vec2(-1.0, -1.0), - vec2(1.0, 1.0), - vec2(-1.0, 1.0) - ); - return vec4(pos[VertexIndex], 0.0, 1.0); -} -`; - -// Fragment shader (Mandelbrot fractal) -const fragmentShaderCode = ` -@group(0) @binding(0) var time: f32; - -@fragment -fn main(@builtin(position) fragCoord: vec4) -> @location(0) vec4 { - let resolution = vec2(80.0, 40.0); - let uv = (fragCoord.xy / resolution) * 2.0 - 1.0; - uv.x *= resolution.x / resolution.y; - - // Mandelbrot parameters - let zoom = 0.8 + 0.2 * sin(time * 0.1); - let centerX = -0.5 + 0.1 * sin(time * 0.05); - let centerY = 0.0 + 0.1 * cos(time * 0.05); - - // Map to Mandelbrot space - let c = vec2(uv.x / zoom + centerX, uv.y / zoom + centerY); - let z = vec2(0.0, 0.0); - - // Mandelbrot iteration - let maxIter = 100; - var iter = 0; - for (var i = 0; i < maxIter; i++) { - // z = z^2 + c - let x = z.x * z.x - z.y * z.y + c.x; - let y = 2.0 * z.x * z.y + c.y; - z = vec2(x, y); - - if (dot(z, z) > 4.0) { - iter = i; - break; - } - } - - // Coloring - if (iter == maxIter) { - return vec4(0.0, 0.0, 0.0, 1.0); - } else { - let t = f32(iter) / f32(maxIter); - let r = 0.5 + 0.5 * sin(t * 6.28 + time); - let g = 0.5 + 0.5 * sin(t * 6.28 + time + 2.09); - let b = 0.5 + 0.5 * sin(t * 6.28 + time + 4.18); - return vec4(r, g, b, 1.0); - } -} -`; - -async function createFractalShaderDemo() { - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - border: false - }); - - root.add(container); - - // Create a WebGPU renderer - const gpuRenderer = new WGPURenderer({ - width: renderer.width, - height: renderer.height - }); - - // Initialize the renderer - await gpuRenderer.initialize(); - - // Create a uniform buffer for time - const uniformBuffer = gpuRenderer.device.createBuffer({ - size: 4, // 4 bytes for a float - usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST - }); - - // Create a bind group layout - const bindGroupLayout = gpuRenderer.device.createBindGroupLayout({ - entries: [{ - binding: 0, - visibility: GPUShaderStage.FRAGMENT, - buffer: { type: 'uniform' } - }] - }); - - // Create a bind group - const bindGroup = gpuRenderer.device.createBindGroup({ - layout: bindGroupLayout, - entries: [{ - binding: 0, - resource: { buffer: uniformBuffer } - }] - }); - - // Create a pipeline layout - const pipelineLayout = gpuRenderer.device.createPipelineLayout({ - bindGroupLayouts: [bindGroupLayout] - }); - - // Create a render pipeline - const pipeline = await gpuRenderer.device.createRenderPipeline({ - layout: pipelineLayout, - vertex: { - module: gpuRenderer.device.createShaderModule({ - code: vertexShaderCode - }), - entryPoint: 'main' - }, - fragment: { - module: gpuRenderer.device.createShaderModule({ - code: fragmentShaderCode - }), - entryPoint: 'main', - targets: [{ format: gpuRenderer.format }] - }, - primitive: { - topology: 'triangle-list' - } - }); - - // Create a custom renderable for the shader - class FractalShaderRenderable extends BoxRenderable { - private gpuRenderer: WGPURenderer; - private pipeline: GPURenderPipeline; - private bindGroup: GPUBindGroup; - private uniformBuffer: GPUBuffer; - private startTime: number; - - constructor(id: string, gpuRenderer: WGPURenderer, pipeline: GPURenderPipeline, - bindGroup: GPUBindGroup, uniformBuffer: GPUBuffer, options = {}) { - super(id, { - width: '100%', - height: '100%', - border: false, - ...options - }); - - this.gpuRenderer = gpuRenderer; - this.pipeline = pipeline; - this.bindGroup = bindGroup; - this.uniformBuffer = uniformBuffer; - this.startTime = Date.now(); - } - - protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { - // Update time uniform - const time = (Date.now() - this.startTime) / 1000; - this.gpuRenderer.device.queue.writeBuffer(this.uniformBuffer, 0, new Float32Array([time])); - - // Render with WebGPU - this.gpuRenderer.beginFrame(); - - const passEncoder = this.gpuRenderer.commandEncoder.beginRenderPass({ - colorAttachments: [{ - view: this.gpuRenderer.textureView, - clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }, - loadOp: 'clear', - storeOp: 'store' - }] - }); - - passEncoder.setPipeline(this.pipeline); - passEncoder.setBindGroup(0, this.bindGroup); - passEncoder.draw(6); // Draw two triangles (a quad) - passEncoder.end(); - - this.gpuRenderer.endFrame(); - - // Get the rendered image - const imageData = this.gpuRenderer.getImageData(); - - // Convert to terminal representation - for (let y = 0; y < this.height; y++) { - for (let x = 0; x < this.width; x++) { - // Sample the WebGPU output - const idx = (y * this.gpuRenderer.width + x) * 4; - const r = imageData.data[idx] / 255; - const g = imageData.data[idx + 1] / 255; - const b = imageData.data[idx + 2] / 255; - const a = imageData.data[idx + 3] / 255; - - // Draw the pixel - buffer.setCell( - this.x + x, - this.y + y, - ' ', // Use space character with background color - RGBA.fromValues(0, 0, 0, 0), - RGBA.fromValues(r, g, b, a) - ); - } - } - } - } - - // Create the fractal shader renderable - const fractalView = new FractalShaderRenderable( - 'fractal', - gpuRenderer, - pipeline, - bindGroup, - uniformBuffer - ); - container.add(fractalView); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the fractal shader demo -createFractalShaderDemo().catch(console.error); -``` diff --git a/packages/core/docs/api/animation/animation.md b/packages/core/docs/api/animation/animation.md deleted file mode 100644 index 78997f8ce..000000000 --- a/packages/core/docs/api/animation/animation.md +++ /dev/null @@ -1,761 +0,0 @@ -# Animation API - -OpenTUI provides powerful animation capabilities for creating dynamic and interactive terminal user interfaces. - -## Timeline - -The `Timeline` class is the core of OpenTUI's animation system, allowing you to create and manage animations with precise timing control. - -### Creating a Timeline - -```typescript -import { Timeline } from '@opentui/core'; - -// Create a timeline with default options -const timeline = new Timeline(); - -// Create a timeline with custom options -const customTimeline = new Timeline({ - duration: 1000, // Duration in milliseconds - easing: 'easeInOut', // Easing function - repeat: 2, // Number of repetitions (0 = no repeat, -1 = infinite) - yoyo: true // Whether to reverse on alternate repetitions -}); -``` - -### Timeline Options - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `duration` | `number` | `1000` | Duration in milliseconds | -| `easing` | `string \| Function` | `'linear'` | Easing function | -| `repeat` | `number` | `0` | Number of repetitions (0 = no repeat, -1 = infinite) | -| `yoyo` | `boolean` | `false` | Whether to reverse on alternate repetitions | -| `autoPlay` | `boolean` | `false` | Whether to start playing automatically | -| `onComplete` | `Function` | `undefined` | Callback when animation completes | -| `onRepeat` | `Function` | `undefined` | Callback on each repetition | -| `onUpdate` | `Function` | `undefined` | Callback on each update | - -### Controlling Animations - -```typescript -// Start the animation -timeline.play(); - -// Pause the animation -timeline.pause(); - -// Resume the animation -timeline.resume(); - -// Stop the animation and reset to beginning -timeline.stop(); - -// Restart the animation from the beginning -timeline.restart(); - -// Reverse the animation direction -timeline.reverse(); - -// Check if the animation is playing -const isPlaying = timeline.isPlaying(); - -// Get the current progress (0-1) -const progress = timeline.getProgress(); - -// Set the progress manually (0-1) -timeline.setProgress(0.5); - -// Get the current time in milliseconds -const time = timeline.getCurrentTime(); - -// Set the current time in milliseconds -timeline.setCurrentTime(500); -``` - -### Adding Animations - -```typescript -// Animate a property -timeline.to(target, { - property: 'x', // Property to animate - from: 0, // Starting value - to: 100, // Ending value - duration: 1000, // Duration in milliseconds - easing: 'easeInOut', // Easing function - onUpdate: (value) => { - // Custom update logic - console.log('Current value:', value); - } -}); - -// Animate multiple properties -timeline.to(target, { - properties: { - x: { from: 0, to: 100 }, - y: { from: 0, to: 50 }, - opacity: { from: 0, to: 1 } - }, - duration: 1000, - easing: 'easeInOut' -}); - -// Add a delay -timeline.delay(500); - -// Add a callback -timeline.call(() => { - console.log('Animation reached this point'); -}); -``` - -### Easing Functions - -OpenTUI provides various easing functions for animations: - -| Easing | Description | -|--------|-------------| -| `'linear'` | Linear easing (no acceleration) | -| `'easeIn'` | Accelerating from zero velocity | -| `'easeOut'` | Decelerating to zero velocity | -| `'easeInOut'` | Acceleration until halfway, then deceleration | -| `'easeInQuad'` | Quadratic easing in | -| `'easeOutQuad'` | Quadratic easing out | -| `'easeInOutQuad'` | Quadratic easing in and out | -| `'easeInCubic'` | Cubic easing in | -| `'easeOutCubic'` | Cubic easing out | -| `'easeInOutCubic'` | Cubic easing in and out | -| `'easeInElastic'` | Elastic easing in | -| `'easeOutElastic'` | Elastic easing out | -| `'easeInOutElastic'` | Elastic easing in and out | -| `'easeInBounce'` | Bouncing easing in | -| `'easeOutBounce'` | Bouncing easing out | -| `'easeInOutBounce'` | Bouncing easing in and out | - -You can also provide a custom easing function: - -```typescript -// Custom easing function (t: 0-1) -const customEasing = (t: number): number => { - return t * t * (3 - 2 * t); // Custom smoothstep -}; - -timeline.to(target, { - property: 'x', - from: 0, - to: 100, - duration: 1000, - easing: customEasing -}); -``` - -### Example: Animating a Box - -```typescript -import { createCliRenderer, BoxRenderable, Timeline } from '@opentui/core'; - -async function animateBox() { - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a box - const box = new BoxRenderable('box', { - width: 10, - height: 5, - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222', - position: 'absolute', - x: 0, - y: 10 - }); - - root.add(box); - - // Create a timeline - const timeline = new Timeline({ - repeat: -1, // Infinite repeat - yoyo: true, // Reverse on alternate repetitions - autoPlay: true - }); - - // Animate the box horizontally - timeline.to(box, { - property: 'x', - from: 0, - to: renderer.width - box.width, - duration: 3000, - easing: 'easeInOutQuad' - }); - - // Add a color animation - timeline.to(box, { - property: 'borderColor', - from: '#3498db', - to: '#e74c3c', - duration: 1500, - easing: 'linear' - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the animation -animateBox().catch(console.error); -``` - -### Chaining Animations - -You can chain animations to create complex sequences: - -```typescript -// Create a sequence of animations -timeline - .to(box, { - property: 'x', - from: 0, - to: 100, - duration: 1000 - }) - .delay(500) - .to(box, { - property: 'y', - from: 0, - to: 50, - duration: 1000 - }) - .call(() => { - console.log('Horizontal and vertical movement complete'); - }) - .to(box, { - property: 'borderColor', - from: '#3498db', - to: '#e74c3c', - duration: 500 - }); -``` - -### Parallel Animations - -You can run multiple animations in parallel: - -```typescript -// Create parallel animations -const timeline = new Timeline(); - -// Add multiple animations that will run simultaneously -timeline.to(box1, { - property: 'x', - from: 0, - to: 100, - duration: 1000 -}); - -timeline.to(box2, { - property: 'y', - from: 0, - to: 50, - duration: 1000 -}); - -// Start all animations -timeline.play(); -``` - -## Sprite Animation - -OpenTUI provides sprite animation capabilities for creating animated characters and effects. - -### SpriteAnimator - -The `SpriteAnimator` class allows you to create frame-based animations from sprite sheets. - -```typescript -import { SpriteAnimator } from '@opentui/core/3d'; -import Jimp from 'jimp'; - -// Load a sprite sheet -const spriteSheet = await Jimp.read('path/to/sprite_sheet.png'); - -// Create a sprite animator -const animator = new SpriteAnimator({ - image: spriteSheet, - frameWidth: 32, // Width of each frame - frameHeight: 32, // Height of each frame - frameCount: 8, // Total number of frames - frameDuration: 100 // Duration of each frame in milliseconds -}); - -// Start the animation -animator.play(); - -// Pause the animation -animator.pause(); - -// Set the current frame -animator.setFrame(3); - -// Get the current frame -const currentFrame = animator.getCurrentFrame(); - -// Update the animation (call in render loop) -animator.update(deltaTime); -``` - -### Example: Creating an Animated Character - -```typescript -import { createCliRenderer, BoxRenderable, RGBA } from '@opentui/core'; -import { SpriteAnimator } from '@opentui/core/3d'; -import Jimp from 'jimp'; - -async function createAnimatedCharacter() { - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - border: false, - backgroundColor: '#222222' - }); - - root.add(container); - - // Load character sprite sheet - const spriteSheet = await Jimp.read('path/to/character_run.png'); - - // Create a sprite animator - const characterAnimator = new SpriteAnimator({ - image: spriteSheet, - frameWidth: 32, - frameHeight: 32, - frameCount: 8, - frameDuration: 100 - }); - - // Create a custom renderable for the character - class CharacterRenderable extends BoxRenderable { - private animator: SpriteAnimator; - - constructor(id: string, animator: SpriteAnimator, options = {}) { - super(id, { - width: 16, - height: 8, - border: false, - position: 'absolute', - x: 10, - y: 10, - ...options - }); - - this.animator = animator; - this.animator.play(); - } - - protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { - // Update the animation - this.animator.update(deltaTime); - - // Get the current frame - const frame = this.animator.getCurrentFrame(); - - // Render the sprite with proper scaling - if (frame) { - for (let y = 0; y < this.height; y++) { - for (let x = 0; x < this.width; x++) { - // Sample the sprite pixel with bilinear interpolation for smoother scaling - const pixelX = Math.floor(x * (frame.width / this.width)); - const pixelY = Math.floor(y * (frame.height / this.height)); - - // Get pixel color from the sprite frame - const idx = (pixelY * frame.width + pixelX) * 4; - const r = frame.data[idx] / 255; - const g = frame.data[idx + 1] / 255; - const b = frame.data[idx + 2] / 255; - const a = frame.data[idx + 3] / 255; - - if (a > 0.5) { - // Draw the pixel - buffer.setCell( - this.x + x, - this.y + y, - ' ', - RGBA.fromValues(0, 0, 0, 0), - RGBA.fromValues(r, g, b, a) - ); - } - } - } - } - } - } - - // Create the character - const character = new CharacterRenderable('character', characterAnimator); - container.add(character); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the animation -createAnimatedCharacter().catch(console.error); -``` - -## Particle Effects - -OpenTUI provides particle system capabilities for creating visual effects. - -### SpriteParticleGenerator - -The `SpriteParticleGenerator` class allows you to create particle effects. - -```typescript -import { SpriteParticleGenerator } from '@opentui/core/3d'; -import Jimp from 'jimp'; - -// Load a particle texture -const particleTexture = await Jimp.read('path/to/particle.png'); - -// Create a particle generator -const particles = new SpriteParticleGenerator({ - texture: particleTexture, - maxParticles: 100, - emissionRate: 10, // Particles per second - particleLifetime: { - min: 1000, // Minimum lifetime in milliseconds - max: 3000 // Maximum lifetime in milliseconds - }, - position: { x: 40, y: 20 }, - positionVariance: { x: 5, y: 0 }, - velocity: { x: 0, y: -0.05 }, - velocityVariance: { x: 0.02, y: 0.01 }, - acceleration: { x: 0, y: 0.0001 }, - startScale: { min: 0.5, max: 1.0 }, - endScale: { min: 0, max: 0.2 }, - startColor: RGBA.fromHex('#ffff00'), - endColor: RGBA.fromHex('#ff0000'), - startAlpha: 1.0, - endAlpha: 0.0, - rotationSpeed: { min: -0.1, max: 0.1 } -}); - -// Start emitting particles -particles.start(); - -// Stop emitting particles -particles.stop(); - -// Update the particle system (call in render loop) -particles.update(deltaTime); - -// Render the particles (call in render method) -particles.render(buffer, x, y); -``` - -### Example: Creating a Fire Effect - -```typescript -import { createCliRenderer, BoxRenderable, RGBA } from '@opentui/core'; -import { SpriteParticleGenerator } from '@opentui/core/3d'; -import Jimp from 'jimp'; - -async function createFireEffect() { - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - border: false, - backgroundColor: '#222222' - }); - - root.add(container); - - // Load particle texture - const particleTexture = await Jimp.read('path/to/particle.png'); - - // Create a fire particle effect - const fireEffect = new SpriteParticleGenerator({ - texture: particleTexture, - maxParticles: 200, - emissionRate: 50, - particleLifetime: { - min: 500, - max: 1500 - }, - position: { x: renderer.width / 2, y: renderer.height - 5 }, - positionVariance: { x: 3, y: 0 }, - velocity: { x: 0, y: -0.08 }, - velocityVariance: { x: 0.03, y: 0.02 }, - acceleration: { x: 0, y: -0.0001 }, - startScale: { min: 0.8, max: 1.2 }, - endScale: { min: 0.1, max: 0.3 }, - startColor: RGBA.fromHex('#ffff00'), - endColor: RGBA.fromHex('#ff0000'), - startAlpha: 1.0, - endAlpha: 0.0, - rotationSpeed: { min: -0.05, max: 0.05 } - }); - - // Create a custom renderable for the fire - class FireRenderable extends BoxRenderable { - private particles: SpriteParticleGenerator; - - constructor(id: string, particles: SpriteParticleGenerator, options = {}) { - super(id, { - width: '100%', - height: '100%', - border: false, - ...options - }); - - this.particles = particles; - this.particles.start(); - } - - protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { - // Update the particles - this.particles.update(deltaTime); - - // Render the particles - this.particles.render(buffer, 0, 0); - } - } - - // Create the fire effect - const fire = new FireRenderable('fire', fireEffect); - container.add(fire); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the fire effect -createFireEffect().catch(console.error); -``` - -## Physics-Based Animation - -OpenTUI supports physics-based animations through integration with physics engines. - -### RapierPhysicsAdapter - -The `RapierPhysicsAdapter` class provides integration with the Rapier 2D physics engine. - -```typescript -import { RapierPhysicsAdapter } from '@opentui/core/3d/physics'; - -// Create a physics world -const physics = new RapierPhysicsAdapter({ - gravity: { x: 0, y: 9.81 } -}); - -// Create a static ground body -const ground = physics.createStaticBody({ - position: { x: 40, y: 40 }, - shape: { - type: 'box', - width: 80, - height: 2 - } -}); - -// Create a dynamic box body -const box = physics.createDynamicBody({ - position: { x: 40, y: 10 }, - shape: { - type: 'box', - width: 4, - height: 4 - }, - restitution: 0.5, // Bounciness - friction: 0.2 // Friction -}); - -// Update the physics world (call in render loop) -physics.update(deltaTime); - -// Get the position of the box -const position = box.getPosition(); -``` - -### PlanckPhysicsAdapter - -The `PlanckPhysicsAdapter` class provides integration with the Planck.js physics engine. - -```typescript -import { PlanckPhysicsAdapter } from '@opentui/core/3d/physics'; - -// Create a physics world -const physics = new PlanckPhysicsAdapter({ - gravity: { x: 0, y: 10 } -}); - -// Create a static ground body -const ground = physics.createStaticBody({ - position: { x: 40, y: 40 }, - shape: { - type: 'box', - width: 80, - height: 2 - } -}); - -// Create a dynamic circle body -const ball = physics.createDynamicBody({ - position: { x: 40, y: 10 }, - shape: { - type: 'circle', - radius: 2 - }, - restitution: 0.8, // Bounciness - friction: 0.1 // Friction -}); - -// Apply an impulse to the ball -ball.applyLinearImpulse({ x: 5, y: -5 }); - -// Update the physics world (call in render loop) -physics.update(deltaTime); -``` - -### Example: Creating a Physics Simulation - -```typescript -import { createCliRenderer, BoxRenderable, RGBA } from '@opentui/core'; -import { RapierPhysicsAdapter } from '@opentui/core/3d/physics'; - -async function createPhysicsSimulation() { - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - border: false, - backgroundColor: '#222222' - }); - - root.add(container); - - // Create a physics world - const physics = new RapierPhysicsAdapter({ - gravity: { x: 0, y: 20 } - }); - - // Create a static ground body - const ground = physics.createStaticBody({ - position: { x: renderer.width / 2, y: renderer.height - 5 }, - shape: { - type: 'box', - width: renderer.width, - height: 2 - } - }); - - // Create walls - const leftWall = physics.createStaticBody({ - position: { x: 2, y: renderer.height / 2 }, - shape: { - type: 'box', - width: 2, - height: renderer.height - } - }); - - const rightWall = physics.createStaticBody({ - position: { x: renderer.width - 2, y: renderer.height / 2 }, - shape: { - type: 'box', - width: 2, - height: renderer.height - } - }); - - // Create some dynamic bodies - const bodies = []; - const renderables = []; - - for (let i = 0; i < 10; i++) { - // Create a dynamic body - const body = physics.createDynamicBody({ - position: { - x: 10 + Math.random() * (renderer.width - 20), - y: 5 + Math.random() * 10 - }, - shape: { - type: Math.random() > 0.5 ? 'box' : 'circle', - width: 3 + Math.random() * 3, - height: 3 + Math.random() * 3, - radius: 2 + Math.random() * 2 - }, - restitution: 0.3 + Math.random() * 0.5, - friction: 0.1 + Math.random() * 0.3 - }); - - bodies.push(body); - - // Create a renderable for the body - const isBox = body.getShapeType() === 'box'; - const size = isBox ? body.getSize() : { width: body.getRadius() * 2, height: body.getRadius() * 2 }; - - const renderable = new BoxRenderable(`body${i}`, { - width: size.width, - height: size.height, - position: 'absolute', - x: body.getPosition().x - size.width / 2, - y: body.getPosition().y - size.height / 2, - borderStyle: isBox ? 'single' : 'rounded', - borderColor: RGBA.fromHex( - ['#3498db', '#2ecc71', '#e74c3c', '#f39c12', '#9b59b6'][Math.floor(Math.random() * 5)] - ), - backgroundColor: 'transparent' - }); - - renderables.push(renderable); - container.add(renderable); - } - - // Create a frame callback to update physics - renderer.setFrameCallback(async (deltaTime) => { - // Update physics (with fixed timestep) - const fixedDelta = Math.min(deltaTime, 33) / 1000; // Cap at 30 FPS, convert to seconds - physics.update(fixedDelta); - - // Update renderables - for (let i = 0; i < bodies.length; i++) { - const body = bodies[i]; - const renderable = renderables[i]; - const position = body.getPosition(); - const angle = body.getAngle(); - - // Update position - renderable.x = position.x - renderable.width / 2; - renderable.y = position.y - renderable.height / 2; - - // We could update rotation too if OpenTUI supported it - } - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the physics simulation -createPhysicsSimulation().catch(console.error); -``` diff --git a/packages/core/docs/api/animation/timeline.md b/packages/core/docs/api/animation/timeline.md deleted file mode 100644 index 23b99d62a..000000000 --- a/packages/core/docs/api/animation/timeline.md +++ /dev/null @@ -1,356 +0,0 @@ -# Animation Timeline - -OpenTUI provides a powerful animation system through the `Timeline` class, which allows you to create complex animations with precise timing control. - -## Overview - -The Timeline system consists of: - -1. **Timeline**: The main class for managing animations -2. **Animations**: Individual animations that can be added to the timeline -3. **Easing Functions**: Various easing functions for smooth animations -4. **Callbacks**: Functions that can be called at specific times in the timeline - -## Timeline API - -```typescript -import { Timeline, TimelineOptions } from '@opentui/core'; - -// Create a timeline -const timeline = new Timeline({ - duration: 1000, // Duration in milliseconds - loop: false, // Whether to loop the timeline - autoplay: true, // Whether to start playing immediately - onComplete: () => { // Called when the timeline completes - console.log('Timeline completed'); - }, - onPause: () => { // Called when the timeline is paused - console.log('Timeline paused'); - } -}); - -// Control the timeline -timeline.play(); // Start or resume playback -timeline.pause(); // Pause playback -timeline.stop(); // Stop playback and reset to beginning -timeline.seek(500); // Seek to a specific time (in milliseconds) -timeline.reverse(); // Reverse the playback direction - -// Get timeline state -const isPlaying = timeline.isPlaying(); -const currentTime = timeline.getCurrentTime(); -const duration = timeline.getDuration(); -const progress = timeline.getProgress(); // 0 to 1 -``` - -## Adding Animations - -You can add animations to a timeline with precise timing: - -```typescript -import { Timeline, EasingFunctions } from '@opentui/core'; - -const timeline = new Timeline({ duration: 2000 }); - -// Add an animation that starts at the beginning -timeline.animate( - myElement, // Target element - { - x: 100, // Target property and value - y: 50, - opacity: 1 - }, - { - duration: 500, // Duration in milliseconds - ease: 'outQuad', // Easing function - onUpdate: (anim) => { - console.log(`Progress: ${anim.progress}`); - }, - onComplete: () => { - console.log('Animation completed'); - } - } -); - -// Add an animation that starts at 500ms -timeline.animate( - anotherElement, - { - scale: 2, - rotation: 45 - }, - { - duration: 800, - ease: 'inOutSine', - startTime: 500 // Start time in milliseconds - } -); -``` - -## Adding Callbacks - -You can add callbacks to a timeline at specific times: - -```typescript -// Add a callback at 1000ms -timeline.addCallback(1000, () => { - console.log('Halfway point reached'); -}); - -// Add a callback at the end -timeline.addCallback(timeline.getDuration(), () => { - console.log('Timeline ended'); -}); -``` - -## Nesting Timelines - -You can nest timelines for complex animation sequences: - -```typescript -const mainTimeline = new Timeline({ duration: 5000 }); -const subTimeline = new Timeline({ duration: 2000 }); - -// Add animations to the sub-timeline -subTimeline.animate(element1, { x: 100 }, { duration: 500 }); -subTimeline.animate(element2, { y: 50 }, { duration: 800, startTime: 500 }); - -// Add the sub-timeline to the main timeline -mainTimeline.addTimeline(subTimeline, 1000); // Start at 1000ms - -// Play the main timeline -mainTimeline.play(); -``` - -## Easing Functions - -OpenTUI provides various easing functions for smooth animations: - -```typescript -import { EasingFunctions } from '@opentui/core'; - -// Available easing functions: -const easings: EasingFunctions[] = [ - 'linear', - 'inQuad', - 'outQuad', - 'inOutQuad', - 'inExpo', - 'outExpo', - 'inOutSine', - 'outBounce', - 'outElastic', - 'inBounce', - 'inCirc', - 'outCirc', - 'inOutCirc' -]; - -// Use an easing function -timeline.animate(element, { x: 100 }, { - duration: 500, - ease: 'outBounce' -}); -``` - -## Animation Options - -The `animate` method accepts various options: - -```typescript -timeline.animate(element, { x: 100 }, { - duration: 500, // Duration in milliseconds - ease: 'outQuad', // Easing function - startTime: 0, // Start time in milliseconds (default: 0) - loop: false, // Whether to loop this animation - loopDelay: 0, // Delay between loops in milliseconds - alternate: false, // Whether to alternate direction on loop - once: false, // Whether to run only once - onUpdate: (anim) => { - // Called on each update - console.log(`Progress: ${anim.progress}`); - }, - onComplete: () => { - // Called when the animation completes - console.log('Animation completed'); - }, - onStart: () => { - // Called when the animation starts - console.log('Animation started'); - }, - onLoop: () => { - // Called when the animation loops - console.log('Animation looped'); - } -}); -``` - -## Updating the Timeline - -The timeline needs to be updated on each frame: - -```typescript -// In your render loop -function update(deltaTime: number) { - timeline.update(deltaTime); - - // Request the next frame - requestAnimationFrame((time) => { - const delta = time - lastTime; - lastTime = time; - update(delta); - }); -} - -let lastTime = performance.now(); -update(0); -``` - -## Example: Creating a Complex Animation - -```typescript -import { Timeline, BoxRenderable } from '@opentui/core'; - -// Create elements -const box1 = new BoxRenderable('box1', { - width: 10, - height: 5, - x: 0, - y: 0, - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222' -}); - -const box2 = new BoxRenderable('box2', { - width: 10, - height: 5, - x: 0, - y: 10, - borderStyle: 'single', - borderColor: '#e74c3c', - backgroundColor: '#222222' -}); - -// Add to the renderer -renderer.root.add(box1); -renderer.root.add(box2); - -// Create a timeline -const timeline = new Timeline({ - duration: 5000, - loop: true, - autoplay: true -}); - -// Animate box1 -timeline.animate(box1, { x: 50 }, { - duration: 1000, - ease: 'outQuad' -}); - -timeline.animate(box1, { y: 20 }, { - duration: 1000, - startTime: 1000, - ease: 'inOutSine' -}); - -timeline.animate(box1, { x: 0 }, { - duration: 1000, - startTime: 2000, - ease: 'inQuad' -}); - -timeline.animate(box1, { y: 0 }, { - duration: 1000, - startTime: 3000, - ease: 'inOutSine' -}); - -// Animate box2 with a delay -timeline.animate(box2, { x: 50 }, { - duration: 1000, - startTime: 500, - ease: 'outBounce' -}); - -timeline.animate(box2, { y: 30 }, { - duration: 1000, - startTime: 1500, - ease: 'outElastic' -}); - -timeline.animate(box2, { x: 0 }, { - duration: 1000, - startTime: 2500, - ease: 'inBounce' -}); - -timeline.animate(box2, { y: 10 }, { - duration: 1000, - startTime: 3500, - ease: 'inOutCirc' -}); - -// Add a callback -timeline.addCallback(2000, () => { - console.log('Halfway point reached'); -}); - -// Update the timeline in the render loop -renderer.on('update', (context) => { - timeline.update(context.deltaTime); -}); -``` - -## Example: Creating a Typing Animation - -```typescript -import { Timeline, TextRenderable } from '@opentui/core'; - -// Create a text element -const text = new TextRenderable('text', { - content: '', - x: 5, - y: 5, - fg: '#ffffff' -}); - -// Add to the renderer -renderer.root.add(text); - -// Create a timeline -const timeline = new Timeline({ - duration: 3000, - autoplay: true -}); - -// The full text to type -const fullText = 'Hello, world! This is a typing animation.'; - -// Create a typing animation -for (let i = 1; i <= fullText.length; i++) { - timeline.addCallback(i * 100, () => { - text.content = fullText.substring(0, i); - }); -} - -// Add a blinking cursor -let cursorVisible = true; -timeline.addCallback(fullText.length * 100 + 500, () => { - const interval = setInterval(() => { - cursorVisible = !cursorVisible; - text.content = fullText + (cursorVisible ? '|' : ''); - }, 500); - - // Clean up the interval when the timeline is stopped - timeline.on('stop', () => { - clearInterval(interval); - }); -}); - -// Update the timeline in the render loop -renderer.on('update', (context) => { - timeline.update(context.deltaTime); -}); -``` diff --git a/packages/core/docs/api/api-summary.md b/packages/core/docs/api/api-summary.md new file mode 100644 index 000000000..f8d2d199a --- /dev/null +++ b/packages/core/docs/api/api-summary.md @@ -0,0 +1,427 @@ +# OpenTUI API Summary + +## Classes + +### Renderable + +**Constructor:** +```typescript +constructor(id: string, options: RenderableOptions) +``` + +**Properties:** +- `renderablesByNumber: Map` +- `id: string` +- `num: number` +- `selectable: boolean` +- `parent: Renderable | null` + +**Methods:** +- `hasSelection(): boolean` +- `onSelectionChanged(selection: SelectionState | null): boolean` +- `getSelectedText(): string` +- `shouldStartSelection(x: number, y: number): boolean` +- `focus(): void` +- `blur(): void` +- `handleKeyPress(key: ParsedKey | string): boolean` +- `needsUpdate(): void` +- `requestZIndexSort(): void` +- `setPosition(position: Position): void` +- `getLayoutNode(): TrackedNode` +- `updateFromLayout(): void` +- `add(obj: Renderable, index: number): number` +- `insertBefore(obj: Renderable, anchor: Renderable): number` +- `propagateContext(ctx: RenderContext | null): void` +- `getRenderable(id: string): Renderable` +- `remove(id: string): void` +- `getChildren(): Renderable[]` +- `render(buffer: OptimizedBuffer, deltaTime: number): void` +- `destroy(): void` +- `destroyRecursively(): void` +- `processMouseEvent(event: MouseEvent): void` + +### RootRenderable + +**Constructor:** +```typescript +constructor(width: number, height: number, ctx: RenderContext, rootContext: RootContext) +``` + +**Methods:** +- `requestLayout(): void` +- `calculateLayout(): void` +- `resize(width: number, height: number): void` + +### MouseEvent + +**Constructor:** +```typescript +constructor(target: Renderable | null, attributes: RawMouseEvent & { source?: Renderable }) +``` + +**Properties:** +- `type: MouseEventType` +- `button: number` +- `x: number` +- `y: number` +- `source: Renderable` +- `modifiers: { + shift: boolean + alt: boolean + ctrl: boolean + }` +- `scroll: ScrollInfo` +- `target: Renderable | null` + +**Methods:** +- `preventDefault(): void` + +### CliRenderer + +**Constructor:** +```typescript +constructor(lib: RenderLib, rendererPtr: Pointer, stdin: NodeJS.ReadStream, stdout: NodeJS.WriteStream, width: number, height: number, config: CliRendererConfig) +``` + +**Properties:** +- `rendererPtr: Pointer` +- `nextRenderBuffer: OptimizedBuffer` +- `currentRenderBuffer: OptimizedBuffer` +- `root: RootRenderable` +- `width: number` +- `height: number` +- `debugOverlay: any` + +**Methods:** +- `needsUpdate(): void` +- `setMemorySnapshotInterval(interval: number): void` +- `setBackgroundColor(color: ColorInput): void` +- `toggleDebugOverlay(): void` +- `configureDebugOverlay(options: { enabled?: boolean; corner?: DebugOverlayCorner }): void` +- `clearTerminal(): void` +- `dumpHitGrid(): void` +- `dumpBuffers(timestamp: number): void` +- `dumpStdoutBuffer(timestamp: number): void` +- `setCursorPosition(x: number, y: number, visible: boolean): void` +- `setCursorStyle(style: CursorStyle, blinking: boolean, color: RGBA): void` +- `setCursorColor(color: RGBA): void` +- `setCursorPosition(x: number, y: number, visible: boolean): void` +- `setCursorStyle(style: CursorStyle, blinking: boolean, color: RGBA): void` +- `setCursorColor(color: RGBA): void` +- `addPostProcessFn(processFn: (buffer: OptimizedBuffer, deltaTime: number) => void): void` +- `removePostProcessFn(processFn: (buffer: OptimizedBuffer, deltaTime: number) => void): void` +- `clearPostProcessFns(): void` +- `setFrameCallback(callback: (deltaTime: number) => Promise): void` +- `removeFrameCallback(callback: (deltaTime: number) => Promise): void` +- `clearFrameCallbacks(): void` +- `requestLive(): void` +- `dropLive(): void` +- `start(): void` +- `pause(): void` +- `stop(): void` +- `destroy(): void` +- `intermediateRender(): void` +- `getStats(): { fps: number; frameCount: number; frameTimes: number[]; averageFrameTime: number; minFrameTime: number; maxFrameTime: number; }` +- `resetStats(): void` +- `setGatherStats(enabled: boolean): void` +- `getSelection(): Selection` +- `getSelectionContainer(): Renderable` +- `hasSelection(): boolean` +- `clearSelection(): void` + +### OptimizedBuffer + +**Constructor:** +```typescript +constructor(lib: RenderLib, ptr: Pointer, buffer: { + char: Uint32Array + fg: Float32Array + bg: Float32Array + attributes: Uint8Array + }, width: number, height: number, options: { respectAlpha?: boolean }) +``` + +**Properties:** +- `id: string` +- `lib: RenderLib` +- `respectAlpha: boolean` + +**Methods:** +- `create(width: number, height: number, options: { respectAlpha?: boolean }): OptimizedBuffer` +- `getWidth(): number` +- `getHeight(): number` +- `setRespectAlpha(respectAlpha: boolean): void` +- `clear(bg: RGBA, clearChar: string): void` +- `clearLocal(bg: RGBA, clearChar: string): void` +- `setCell(x: number, y: number, char: string, fg: RGBA, bg: RGBA, attributes: number): void` +- `get(x: number, y: number): { char: number; fg: RGBA; bg: RGBA; attributes: number; }` +- `setCellWithAlphaBlending(x: number, y: number, char: string, fg: RGBA, bg: RGBA, attributes: number): void` +- `setCellWithAlphaBlendingLocal(x: number, y: number, char: string, fg: RGBA, bg: RGBA, attributes: number): void` +- `drawText(text: string, x: number, y: number, fg: RGBA, bg: RGBA, attributes: number, selection: { start: number; end: number; bgColor?: RGBA; fgColor?: RGBA } | null): void` +- `fillRect(x: number, y: number, width: number, height: number, bg: RGBA): void` +- `fillRectLocal(x: number, y: number, width: number, height: number, bg: RGBA): void` +- `drawFrameBuffer(destX: number, destY: number, frameBuffer: OptimizedBuffer, sourceX: number, sourceY: number, sourceWidth: number, sourceHeight: number): void` +- `drawFrameBufferLocal(destX: number, destY: number, frameBuffer: OptimizedBuffer, sourceX: number, sourceY: number, sourceWidth: number, sourceHeight: number): void` +- `destroy(): void` +- `drawTextBuffer(textBuffer: TextBuffer, x: number, y: number, clipRect: { x: number; y: number; width: number; height: number }): void` +- `drawSuperSampleBuffer(x: number, y: number, pixelDataPtr: Pointer, pixelDataLength: number, format: "bgra8unorm" | "rgba8unorm", alignedBytesPerRow: number): void` +- `drawSuperSampleBufferFFI(x: number, y: number, pixelDataPtr: Pointer, pixelDataLength: number, format: "bgra8unorm" | "rgba8unorm", alignedBytesPerRow: number): void` +- `drawPackedBuffer(dataPtr: Pointer, dataLen: number, posX: number, posY: number, terminalWidthCells: number, terminalHeightCells: number): void` +- `setCellWithAlphaBlendingFFI(x: number, y: number, char: string, fg: RGBA, bg: RGBA, attributes: number): void` +- `fillRectFFI(x: number, y: number, width: number, height: number, bg: RGBA): void` +- `resize(width: number, height: number): void` +- `clearFFI(bg: RGBA): void` +- `drawTextFFI(text: string, x: number, y: number, fg: RGBA, bg: RGBA, attributes: number): void` +- `drawFrameBufferFFI(destX: number, destY: number, frameBuffer: OptimizedBuffer, sourceX: number, sourceY: number, sourceWidth: number, sourceHeight: number): void` +- `drawBox(options: { + x: number + y: number + width: number + height: number + borderStyle?: BorderStyle + customBorderChars?: Uint32Array + border: boolean | BorderSides[] + borderColor: RGBA + backgroundColor: RGBA + shouldFill?: boolean + title?: string + titleAlignment?: "left" | "center" | "right" + }): void` + +### BoxRenderable + +**Constructor:** +```typescript +constructor(id: string, options: BoxOptions) +``` + +**Properties:** +- `shouldFill: boolean` + +### TextRenderable + +**Constructor:** +```typescript +constructor(id: string, options: TextOptions) +``` + +**Properties:** +- `selectable: boolean` + +**Methods:** +- `shouldStartSelection(x: number, y: number): boolean` +- `onSelectionChanged(selection: SelectionState | null): boolean` +- `getSelectedText(): string` +- `hasSelection(): boolean` +- `destroy(): void` + +### ASCIIFontRenderable + +**Constructor:** +```typescript +constructor(id: string, options: ASCIIFontOptions) +``` + +**Properties:** +- `selectable: boolean` + +**Methods:** +- `shouldStartSelection(x: number, y: number): boolean` +- `onSelectionChanged(selection: SelectionState | null): boolean` +- `getSelectedText(): string` +- `hasSelection(): boolean` + +### InputRenderable + +**Constructor:** +```typescript +constructor(id: string, options: InputRenderableOptions) +``` + +**Methods:** +- `focus(): void` +- `blur(): void` +- `handleKeyPress(key: ParsedKey | string): boolean` + +### Timeline + +**Constructor:** +```typescript +constructor(options: TimelineOptions) +``` + +**Properties:** +- `items: (TimelineAnimationItem | TimelineCallbackItem)[]` +- `subTimelines: TimelineTimelineItem[]` +- `currentTime: number` +- `isPlaying: boolean` +- `isComplete: boolean` +- `duration: number` +- `loop: boolean` +- `synced: boolean` + +**Methods:** +- `add(target: any, properties: AnimationOptions, startTime: number | string): this` +- `once(target: any, properties: AnimationOptions): this` +- `call(callback: () => void, startTime: number | string): this` +- `sync(timeline: Timeline, startTime: number): this` +- `play(): this` +- `pause(): this` +- `resetItems(): void` +- `restart(): this` +- `update(deltaTime: number): void` + +## Interfaces + +### RootContext + +- `requestLive: void` +- `dropLive: void` + +### Position + +- `top: number | "auto" | `${number}%`` +- `right: number | "auto" | `${number}%`` +- `bottom: number | "auto" | `${number}%`` +- `left: number | "auto" | `${number}%`` + +### LayoutOptions + +- `flexGrow: number` +- `flexShrink: number` +- `flexDirection: FlexDirectionString` +- `alignItems: AlignString` +- `justifyContent: JustifyString` +- `flexBasis: number | "auto" | undefined` +- `position: PositionTypeString` +- `top: number | "auto" | `${number}%`` +- `right: number | "auto" | `${number}%`` +- `bottom: number | "auto" | `${number}%`` +- `left: number | "auto" | `${number}%`` +- `minWidth: number` +- `minHeight: number` +- `maxWidth: number` +- `maxHeight: number` +- `margin: number | "auto" | `${number}%`` +- `marginTop: number | "auto" | `${number}%`` +- `marginRight: number | "auto" | `${number}%`` +- `marginBottom: number | "auto" | `${number}%`` +- `marginLeft: number | "auto" | `${number}%`` +- `padding: number | `${number}%`` +- `paddingTop: number | `${number}%`` +- `paddingRight: number | `${number}%`` +- `paddingBottom: number | `${number}%`` +- `paddingLeft: number | `${number}%`` +- `enableLayout: boolean` + +### RenderableOptions + +- `width: number | "auto" | `${number}%`` +- `height: number | "auto" | `${number}%`` +- `zIndex: number` +- `visible: boolean` +- `buffered: boolean` +- `live: boolean` +- `onMouseDown: (event: MouseEvent) => void` +- `onMouseUp: (event: MouseEvent) => void` +- `onMouseMove: (event: MouseEvent) => void` +- `onMouseDrag: (event: MouseEvent) => void` +- `onMouseDragEnd: (event: MouseEvent) => void` +- `onMouseDrop: (event: MouseEvent) => void` +- `onMouseOver: (event: MouseEvent) => void` +- `onMouseOut: (event: MouseEvent) => void` +- `onMouseScroll: (event: MouseEvent) => void` +- `onKeyDown: (key: ParsedKey) => void` + +### CliRendererConfig + +- `stdin: NodeJS.ReadStream` +- `stdout: NodeJS.WriteStream` +- `exitOnCtrlC: boolean` +- `debounceDelay: number` +- `targetFps: number` +- `memorySnapshotInterval: number` +- `useThread: boolean` +- `gatherStats: boolean` +- `maxStatSamples: number` +- `consoleOptions: ConsoleOptions` +- `postProcessFns: ((buffer: OptimizedBuffer, deltaTime: number) => void)[]` +- `enableMouseMovement: boolean` +- `useMouse: boolean` +- `useAlternateScreen: boolean` +- `useConsole: boolean` +- `experimental_splitHeight: number` + +### BoxOptions + +- `backgroundColor: string | RGBA` +- `borderStyle: BorderStyle` +- `border: boolean | BorderSides[]` +- `borderColor: string | RGBA` +- `customBorderChars: BorderCharacters` +- `shouldFill: boolean` +- `title: string` +- `titleAlignment: "left" | "center" | "right"` +- `focusedBorderColor: ColorInput` + +### TextOptions + +- `content: StyledText | string` +- `fg: string | RGBA` +- `bg: string | RGBA` +- `selectionBg: string | RGBA` +- `selectionFg: string | RGBA` +- `selectable: boolean` +- `attributes: number` + +### ASCIIFontOptions + +- `text: string` +- `font: "tiny" | "block" | "shade" | "slick"` +- `fg: RGBA | RGBA[]` +- `bg: RGBA` +- `selectionBg: string | RGBA` +- `selectionFg: string | RGBA` +- `selectable: boolean` + +### InputRenderableOptions + +- `backgroundColor: ColorInput` +- `textColor: ColorInput` +- `focusedBackgroundColor: ColorInput` +- `focusedTextColor: ColorInput` +- `placeholder: string` +- `placeholderColor: ColorInput` +- `cursorColor: ColorInput` +- `maxLength: number` +- `value: string` + +### TimelineOptions + +- `duration: number` +- `loop: boolean` +- `autoplay: boolean` +- `onComplete: () => void` +- `onPause: () => void` + +### AnimationOptions + +- `duration: number` +- `ease: EasingFunctions` +- `onUpdate: (animation: JSAnimation) => void` +- `onComplete: () => void` +- `onStart: () => void` +- `onLoop: () => void` +- `loop: boolean | number` +- `loopDelay: number` +- `alternate: boolean` +- `once: boolean` +- `: any` + +### JSAnimation + +- `targets: any[]` +- `deltaTime: number` +- `progress: number` +- `currentTime: number` + diff --git a/packages/core/docs/api/buffer.md b/packages/core/docs/api/buffer.md deleted file mode 100644 index 4e554c946..000000000 --- a/packages/core/docs/api/buffer.md +++ /dev/null @@ -1,159 +0,0 @@ -# Buffer System (detailed API) - -This page documents the OptimizedBuffer class in detail — method signatures, parameters, return values, and important behavior notes so code samples are precise and reliable. - -Source reference: packages/core/src/buffer.ts - -Important types used below: -- RGBA — color container (use `RGBA.fromValues`, `RGBA.fromHex`, etc.) -- Pointer — native pointer type (FFI-backed operations) -- OptimizedBuffer — the class documented here - ---- - -## Class: OptimizedBuffer - -Overview: a high-performance framebuffer abstraction that exposes typed-array access to characters, foreground colors, background colors, and per-cell attributes. Many heavy operations call into the native render library (FFI); the class exposes both FFI-backed and JS-local implementations. - -Creation -```ts -// Static factory (preferred) -const buf = OptimizedBuffer.create(width: number, height: number, options?: { respectAlpha?: boolean }): OptimizedBuffer -``` -- width, height: pixel/cell dimensions -- options.respectAlpha: if true, compositing from source buffers respects alpha channels; default false. - -Properties -- ptr: Pointer — pointer to the native buffer object (used internally; exposed for advanced use) -- buffers: { char: Uint32Array; fg: Float32Array; bg: Float32Array; attributes: Uint8Array } - - char: uint32 per cell (Unicode codepoints) - - fg/bg: Float32Array with 4 floats per cell (r,g,b,a) in 0..1 range - - attributes: Uint8Array per cell (bitflags for attributes) -- id: string — internal id string -- respectAlpha: boolean — whether this buffer respects alpha on draws - -Basic size methods -```ts -getWidth(): number -getHeight(): number -resize(width: number, height: number): void -``` -- `resize` will replace the internal typed arrays (via FFI in native mode) and update internal width/height. - -Lifecycle -```ts -clear(bg?: RGBA, clearChar?: string): void -clearFFI(bg?: RGBA): void -clearLocal(bg?: RGBA, clearChar?: string): void -destroy(): void -``` -- `clear()` delegates to the FFI implementation when available; `clearLocal` is the JS fallback. -- `destroy()` frees the native buffer via the RenderLib wrapper. - -Per-cell operations -```ts -setCell(x: number, y: number, char: string, fg: RGBA, bg: RGBA, attributes?: number): void -get(x: number, y: number): { char: number; fg: RGBA; bg: RGBA; attributes: number } | null -``` -- Coordinates outside the buffer are ignored (setCell is a no-op; get returns null). -- char: first codepoint of provided string will be used; stored as numeric code. -- attributes: integer bitflags (project uses small integers for bold/underline/dim/etc). - -Alpha-aware per-cell writes -```ts -setCellWithAlphaBlending(x: number, y: number, char: string, fg: RGBA, bg: RGBA, attributes?: number): void -setCellWithAlphaBlendingFFI(x: number, y: number, char: string, fg: RGBA, bg: RGBA, attributes?: number): void -setCellWithAlphaBlendingLocal(x: number, y: number, char: string, fg: RGBA, bg: RGBA, attributes?: number): void -``` -- `setCellWithAlphaBlending` routes to FFI when available; otherwise the local JS implementation performs perceptual alpha blending (`blendColors` logic inside buffer.ts). -- Behavior notes: - - If bg/fg have alpha < 1, blending occurs against the destination cell. - - When drawing a space character ' ' over a non-empty cell, the implementation preserves the destination character by default and blends colors accordingly. - -Text drawing -```ts -// High-level -drawText(text: string, x: number, y: number, fg: RGBA, bg?: RGBA, attributes?: number, selection?: { start: number; end: number; bgColor?: RGBA; fgColor?: RGBA } | null): void - -// FFI-level -drawTextFFI(text: string, x: number, y: number, fg?: RGBA, bg?: RGBA, attributes?: number): void -``` -- drawText supports selection highlighting by splitting the text and drawing the selected portion with alternate fg/bg. -- Parameter order is (text, x, y, fg, bg?, attributes?, selection?). -- For performance, drawText calls into `drawTextFFI` when available. - -TextBuffer rendering -```ts -drawTextBuffer(textBuffer: TextBuffer, x: number, y: number, clipRect?: { x: number; y: number; width: number; height: number }): void -``` -- Use this to render a TextBuffer (rich/styled content) efficiently via the native helper. - -Rectangles, boxes and compositing -```ts -fillRect(x: number, y: number, width: number, height: number, bg: RGBA): void -fillRectFFI(x: number, y: number, width: number, height: number, bg: RGBA): void -fillRectLocal(x: number, y: number, width: number, height: number, bg: RGBA): void - -drawBox(options: { - x: number - y: number - width: number - height: number - borderStyle?: BorderStyle - customBorderChars?: Uint32Array - border: boolean | BorderSides[] - borderColor: RGBA - backgroundColor: RGBA - shouldFill?: boolean - title?: string - titleAlignment?: 'left' | 'center' | 'right' -}): void -``` -- `drawBox` packs options and forwards to the native `bufferDrawBox` for speed. -- `fillRect` is alpha-aware; if `bg` has alpha < 1 the implementation blends per cell. - -Framebuffer compositing (copying one buffer into another) -```ts -drawFrameBuffer(destX: number, destY: number, frameBuffer: OptimizedBuffer, sourceX?: number, sourceY?: number, sourceWidth?: number, sourceHeight?: number): void -drawFrameBufferLocal(destX: number, destY: number, frameBuffer: OptimizedBuffer, sourceX?: number, sourceY?: number, sourceWidth?: number, sourceHeight?: number): void -drawFrameBufferFFI(destX: number, destY: number, frameBuffer: OptimizedBuffer, sourceX?: number, sourceY?: number, sourceWidth?: number, sourceHeight?: number): void -``` -- Preferred: `drawFrameBuffer` which delegates to FFI; `drawFrameBufferLocal` exists as a JS fallback. -- Behavior: - - When `frameBuffer.respectAlpha` is false the copy is a straight copy of char/fg/bg/attributes. - - When `respectAlpha` is true, transparent pixels (alpha 0) are skipped, and blended compositing occurs per cell. - -Packed / supersampled drawing (advanced) -```ts -drawPackedBuffer(dataPtr: Pointer, dataLen: number, posX: number, posY: number, terminalWidthCells: number, terminalHeightCells: number): void -drawSuperSampleBuffer(x: number, y: number, pixelDataPtr: Pointer, pixelDataLength: number, format: 'bgra8unorm' | 'rgba8unorm', alignedBytesPerRow: number): void -drawSuperSampleBufferFFI(x: number, y: number, pixelDataPtr: Pointer, pixelDataLength: number, format: 'bgra8unorm' | 'rgba8unorm', alignedBytesPerRow: number): void -``` -- Use these for WebGPU/WebGL-like pixel data uploads or packed buffer formats. These call into FFI for performance. - -FFI helpers (when using native lib) -- clearFFI(bg) -- drawTextFFI(...) -- setCellWithAlphaBlendingFFI(...) -- fillRectFFI(...) -- drawFrameBufferFFI(...) -- drawPackedBuffer(...) / drawSuperSampleBufferFFI(...) - -Notes and edge-cases -- Color format: RGBA floats in 0..1. Use `RGBA.fromHex('#rrggbb')` or `RGBA.fromValues(r,g,b,a)` to build colors. -- Performance: - - Prefer FFI-backed methods (default when native library is loaded via `resolveRenderLib()`). - - Avoid frequent calls to `resize`. - - Use `drawFrameBuffer` for compositing pre-computed buffers rather than redrawing many primitives per frame. -- Selection support in drawText: - - Provide selection `{ start, end, bgColor?, fgColor? }` to highlight segments. start/end indices are character indices in the string. - -Example: alpha-aware text and compositing -```ts -const buf = OptimizedBuffer.create(80, 24, { respectAlpha: true }); -const fg = RGBA.fromHex('#ffffff'); -const transparentBg = RGBA.fromValues(0, 0, 0, 0.5); -buf.setCellWithAlphaBlending(10, 10, 'A', fg, transparentBg, 0); -``` - ---- diff --git a/packages/core/docs/api/components/ascii-font.md b/packages/core/docs/api/components/ascii-font.md deleted file mode 100644 index bbd5d2918..000000000 --- a/packages/core/docs/api/components/ascii-font.md +++ /dev/null @@ -1,399 +0,0 @@ -# ASCIIFontRenderable - -Renders text using ASCII art fonts for large, decorative text displays. - -## Class: `ASCIIFontRenderable` - -```typescript -import { ASCIIFontRenderable } from '@opentui/core' - -const title = new ASCIIFontRenderable('title', { - text: 'HELLO', - font: 'block', - fg: '#00ff00' -}) -``` - -## Constructor - -### `new ASCIIFontRenderable(id: string, options: ASCIIFontOptions)` - -## Options - -### `ASCIIFontOptions` - -Extends [`RenderableOptions`](../renderable.md#renderableoptions) with: - -| Property | Type | Default | Description | -|----------|------|---------|-------------| -| `text` | `string` | `''` | Text to render | -| `font` | `'tiny' \| 'block' \| 'shade' \| 'slick'` | `'tiny'` | ASCII font style | -| `fg` | `RGBA \| RGBA[] \| string \| string[]` | `'#ffffff'` | Foreground color(s) | -| `bg` | `RGBA \| string` | `transparent` | Background color | -| `selectionBg` | `string \| RGBA` | - | Selection background color | -| `selectionFg` | `string \| RGBA` | - | Selection foreground color | -| `selectable` | `boolean` | `true` | Enable text selection | - -## Properties - -| Property | Type | Description | -|----------|------|-------------| -| `text` | `string` | Get/set the text content | -| `font` | `'tiny' \| 'block' \| 'shade' \| 'slick'` | Get/set the font style | -| `fg` | `RGBA[]` | Get/set foreground colors | -| `bg` | `RGBA` | Get/set background color | -| `selectable` | `boolean` | Enable/disable selection | - -## Font Styles - -### `tiny` -Compact font, good for headers in limited space: -``` -H H EEEEE L L OOO -H H E L L O O -HHHHH EEEE L L O O -H H E L L O O -H H EEEEE LLLLL LLLLL OOO -``` - -### `block` -Bold, blocky letters for maximum impact: -``` -██ ██ ███████ ██ ██ ██████ -██ ██ ██ ██ ██ ██ ██ -███████ █████ ██ ██ ██ ██ -██ ██ ██ ██ ██ ██ ██ -██ ██ ███████ ███████ ███████ ██████ -``` - -### `shade` -Shaded/gradient effect using different density characters: -``` -░█ ░█ ▒█▒█▒█ ░█ ░█ ▒█▒█▒█ -▒█ ▒█ ▒█ ▒█ ▒█ ▒█ ▒█ -▓█▓█▓█ ▓█▓█ ▓█ ▓█ ▓█ ▓█ -▒█ ▒█ ▒█ ▒█ ▒█ ▒█ ▒█ -░█ ░█ ▒█▒█▒█ ▒█▒█▒█ ▒█▒█▒█ ▒█▒█▒█ -``` - -### `slick` -Stylized font with decorative elements: -``` -╦ ╦╔═╗╦ ╦ ╔═╗ -╠═╣║╣ ║ ║ ║ ║ -╩ ╩╚═╝╩═╝╩═╝╚═╝ -``` - -## Examples - -### Basic Title - -```typescript -const title = new ASCIIFontRenderable('title', { - text: 'GAME OVER', - font: 'block', - fg: '#ff0000' -}) -``` - -### Rainbow Colors - -```typescript -const rainbow = new ASCIIFontRenderable('rainbow', { - text: 'RAINBOW', - font: 'block', - fg: ['#ff0000', '#ff7f00', '#ffff00', '#00ff00', '#0000ff', '#4b0082', '#9400d3'] -}) -``` - -### Animated Title - -```typescript -class AnimatedTitle extends ASCIIFontRenderable { - private colors = [ - '#ff0000', '#ff3333', '#ff6666', '#ff9999', '#ffcccc', - '#ff9999', '#ff6666', '#ff3333' - ] - private colorIndex = 0 - - constructor(id: string, text: string) { - super(id, { - text, - font: 'block', - fg: '#ff0000' - }) - - this.startAnimation() - } - - private startAnimation() { - setInterval(() => { - this.fg = this.colors[this.colorIndex] - this.colorIndex = (this.colorIndex + 1) % this.colors.length - }, 100) - } -} - -const animatedTitle = new AnimatedTitle('animated', 'ALERT!') -``` - -### Gradient Effect - -```typescript -function createGradient(text: string, startColor: string, endColor: string): string[] { - const colors: string[] = [] - const steps = text.length - - // Parse RGB values - const start = parseRGB(startColor) - const end = parseRGB(endColor) - - for (let i = 0; i < steps; i++) { - const ratio = i / (steps - 1) - const r = Math.round(start.r + (end.r - start.r) * ratio) - const g = Math.round(start.g + (end.g - start.g) * ratio) - const b = Math.round(start.b + (end.b - start.b) * ratio) - colors.push(`rgb(${r}, ${g}, ${b})`) - } - - return colors -} - -const gradient = new ASCIIFontRenderable('gradient', { - text: 'GRADIENT', - font: 'shade', - fg: createGradient('GRADIENT', '#0000ff', '#ff00ff') -}) -``` - -### Menu Title - -```typescript -const menuTitle = new ASCIIFontRenderable('menu-title', { - text: 'MAIN MENU', - font: 'slick', - fg: '#00ff00', - bg: '#001100' -}) - -// Center it -menuTitle.alignSelf = 'center' -menuTitle.marginTop = 2 -menuTitle.marginBottom = 2 -``` - -### Score Display - -```typescript -class ScoreDisplay extends GroupRenderable { - private label: ASCIIFontRenderable - private score: ASCIIFontRenderable - private _value = 0 - - constructor(id: string) { - super(id, { - flexDirection: 'column', - alignItems: 'center' - }) - - this.label = new ASCIIFontRenderable('label', { - text: 'SCORE', - font: 'tiny', - fg: '#ffff00' - }) - - this.score = new ASCIIFontRenderable('score', { - text: '000000', - font: 'block', - fg: '#ffffff' - }) - - this.appendChild(this.label) - this.appendChild(this.score) - } - - set value(score: number) { - this._value = score - this.score.text = score.toString().padStart(6, '0') - - // Flash effect on score change - this.score.fg = '#ffff00' - setTimeout(() => { - this.score.fg = '#ffffff' - }, 200) - } - - get value(): number { - return this._value - } -} -``` - -### ASCII Art Logo - -```typescript -const logo = new GroupRenderable('logo', { - flexDirection: 'column', - alignItems: 'center', - padding: 2 -}) - -const line1 = new ASCIIFontRenderable('line1', { - text: 'OPEN', - font: 'block', - fg: '#00aaff' -}) - -const line2 = new ASCIIFontRenderable('line2', { - text: 'TUI', - font: 'shade', - fg: '#00ff00' -}) - -logo.appendChild(line1) -logo.appendChild(line2) -``` - -### Loading Screen - -```typescript -class LoadingScreen extends GroupRenderable { - private title: ASCIIFontRenderable - private dots = 0 - - constructor(id: string) { - super(id, { - width: '100%', - height: '100%', - justifyContent: 'center', - alignItems: 'center' - }) - - this.title = new ASCIIFontRenderable('loading', { - text: 'LOADING', - font: 'block', - fg: '#00ff00' - }) - - this.appendChild(this.title) - this.startAnimation() - } - - private startAnimation() { - setInterval(() => { - this.dots = (this.dots + 1) % 4 - const dotStr = '.'.repeat(this.dots) - this.title.text = 'LOADING' + dotStr - }, 500) - } -} -``` - -### Game Title with Subtitle - -```typescript -const gameTitle = new GroupRenderable('game-title', { - flexDirection: 'column', - alignItems: 'center', - gap: 1 -}) - -const mainTitle = new ASCIIFontRenderable('main', { - text: 'SPACE', - font: 'block', - fg: ['#0000ff', '#0066ff', '#00aaff', '#00ddff', '#00ffff'] -}) - -const subTitle = new ASCIIFontRenderable('sub', { - text: 'INVADERS', - font: 'shade', - fg: '#ff0000' -}) - -const tagline = new TextRenderable('tagline', { - content: 'Press ENTER to start', - fg: '#999999' -}) - -gameTitle.appendChild(mainTitle) -gameTitle.appendChild(subTitle) -gameTitle.appendChild(tagline) -``` - -## Text Selection - -ASCIIFontRenderable supports text selection when `selectable` is true: - -```typescript -const selectableTitle = new ASCIIFontRenderable('title', { - text: 'SELECT ME', - font: 'tiny', - selectable: true, - selectionBg: '#0066cc', - selectionFg: '#ffffff' -}) - -// Check if has selection -if (selectableTitle.hasSelection()) { - const selected = selectableTitle.getSelectedText() - console.log('Selected:', selected) -} -``` - -## Performance Considerations - -1. **Font Rendering**: ASCII fonts are pre-rendered to a frame buffer -2. **Size**: Larger fonts consume more screen space and memory -3. **Color Arrays**: Using color arrays has minimal performance impact -4. **Updates**: Changing text or font triggers a full re-render - -## Dimensions - -The component automatically calculates its dimensions based on: -- Font size -- Text length -- Font style - -Dimensions update automatically when text or font changes. - -## Integration - -```typescript -// Complete example: Game menu -const menu = new BoxRenderable('menu', { - width: 60, - height: 30, - borderStyle: 'double', - backgroundColor: '#1a1a1a' -}) - -const content = new GroupRenderable('content', { - flexDirection: 'column', - alignItems: 'center', - padding: 2, - gap: 2 -}) - -const title = new ASCIIFontRenderable('title', { - text: 'MAIN MENU', - font: 'block', - fg: '#00ff00' -}) - -const options = new Select('options', { - options: ['New Game', 'Load Game', 'Settings', 'Exit'], - width: 30, - selectedBg: '#003366' -}) - -content.appendChild(title) -content.appendChild(options) -menu.appendChild(content) -``` - -## Limitations - -1. **Character Set**: Limited to ASCII characters -2. **Font Selection**: Only 4 built-in fonts available -3. **Scaling**: Cannot dynamically scale fonts -4. **Line Breaks**: Single line only (no multi-line support) \ No newline at end of file diff --git a/packages/core/docs/api/components/box.md b/packages/core/docs/api/components/box.md deleted file mode 100644 index d8713eeaa..000000000 --- a/packages/core/docs/api/components/box.md +++ /dev/null @@ -1,270 +0,0 @@ -# BoxRenderable - -A container component with borders, background color, and optional title. - -## Class: `BoxRenderable` - -```typescript -import { BoxRenderable } from '@opentui/core' - -const box = new BoxRenderable('my-box', { - width: 40, - height: 10, - borderStyle: 'rounded', - backgroundColor: '#1a1a1a', - borderColor: '#00ff00' -}) -``` - -## Constructor - -### `new BoxRenderable(id: string, options: BoxOptions)` - -## Options - -### `BoxOptions` - -Extends [`RenderableOptions`](../renderable.md#renderableoptions) with: - -| Property | Type | Default | Description | -|----------|------|---------|-------------| -| `backgroundColor` | `string \| RGBA` | `'transparent'` | Background color | -| `borderStyle` | `BorderStyle` | `'single'` | Border style preset | -| `border` | `boolean \| BorderSides[]` | `true` | Which borders to show | -| `borderColor` | `string \| RGBA` | `'#FFFFFF'` | Border color | -| `focusedBorderColor` | `string \| RGBA` | `'#00AAFF'` | Border color when focused | -| `customBorderChars` | `BorderCharacters` | - | Custom border characters | -| `shouldFill` | `boolean` | `true` | Fill background | -| `title` | `string` | - | Optional title text | -| `titleAlignment` | `'left' \| 'center' \| 'right'` | `'left'` | Title alignment | - -### Border Styles - -Available border style presets: - -- `'single'` - Single line borders `┌─┐│└┘` -- `'double'` - Double line borders `╔═╗║╚╝` -- `'rounded'` - Rounded corners `╭─╮│╰╯` -- `'bold'` - Bold lines `┏━┓┃┗┛` -- `'dotted'` - Dotted lines (custom chars) -- `'dashed'` - Dashed lines (custom chars) - -### Border Sides - -Control which borders to display: - -```typescript -type BorderSides = 'top' | 'right' | 'bottom' | 'left' - -// Examples: -border: true // All borders -border: false // No borders -border: ['top', 'bottom'] // Only top and bottom -border: ['left'] // Only left border -``` - -### Custom Border Characters - -Define custom border characters: - -```typescript -interface BorderCharacters { - topLeft: string - top: string - topRight: string - right: string - bottomRight: string - bottom: string - bottomLeft: string - left: string -} - -// Example: -customBorderChars: { - topLeft: '╭', - top: '─', - topRight: '╮', - right: '│', - bottomRight: '╯', - bottom: '─', - bottomLeft: '╰', - left: '│' -} -``` - -## Properties - -### Styling Properties - -| Property | Type | Description | -|----------|------|-------------| -| `backgroundColor` | `RGBA` | Get/set background color | -| `border` | `boolean \| BorderSides[]` | Get/set border configuration | -| `borderStyle` | `BorderStyle` | Get/set border style | -| `borderColor` | `RGBA` | Get/set border color | -| `focusedBorderColor` | `RGBA` | Get/set focused border color | -| `title` | `string \| undefined` | Get/set title text | -| `titleAlignment` | `'left' \| 'center' \| 'right'` | Get/set title alignment | -| `shouldFill` | `boolean` | Whether to fill background | - -## Methods - -BoxRenderable inherits all methods from [`Renderable`](../renderable.md). It doesn't add any additional public methods. - -Properties like `borderStyle`, `title`, and `titleAlignment` can be set directly: - -```typescript -box.borderStyle = 'double' -box.title = 'Settings' -box.titleAlignment = 'center' -``` - -## Examples - -### Basic Box - -```typescript -const box = new BoxRenderable('box', { - width: 30, - height: 10, - borderStyle: 'single', - backgroundColor: '#222222' -}) -``` - -### Box with Title - -```typescript -const dialog = new BoxRenderable('dialog', { - width: 50, - height: 15, - borderStyle: 'double', - title: 'Confirm Action', - titleAlignment: 'center', - backgroundColor: '#1a1a1a', - borderColor: '#ffff00' -}) -``` - -### Focused Box - -```typescript -const input = new BoxRenderable('input-box', { - width: 40, - height: 3, - borderStyle: 'rounded', - borderColor: '#666666', - focusedBorderColor: '#00ff00' -}) - -// Border color changes when focused -input.on('focused', () => { - console.log('Box focused') -}) -``` - -### Custom Borders - -```typescript -const custom = new BoxRenderable('custom', { - width: 25, - height: 8, - customBorderChars: { - topLeft: '╔', - top: '═', - topRight: '╗', - right: '║', - bottomRight: '╝', - bottom: '═', - bottomLeft: '╚', - left: '║' - }, - borderColor: '#00ffff' -}) -``` - -### Partial Borders - -```typescript -const partial = new BoxRenderable('partial', { - width: 30, - height: 10, - border: ['top', 'bottom'], - borderStyle: 'bold', - backgroundColor: '#333333' -}) -``` - -### Nested Boxes - -```typescript -const outer = new BoxRenderable('outer', { - width: 60, - height: 20, - borderStyle: 'double', - backgroundColor: '#111111', - padding: 1 -}) - -const inner = new BoxRenderable('inner', { - width: '100%', - height: '100%', - borderStyle: 'single', - backgroundColor: '#222222', - margin: 2 -}) - -outer.appendChild(inner) -``` - -### Dynamic Styling - -```typescript -const status = new BoxRenderable('status', { - width: 40, - height: 5, - borderStyle: 'rounded' -}) - -// Change appearance based on state -function setStatus(type: 'success' | 'warning' | 'error') { - switch (type) { - case 'success': - status.backgroundColor = '#004400' - status.borderColor = '#00ff00' - break - case 'warning': - status.backgroundColor = '#444400' - status.borderColor = '#ffff00' - break - case 'error': - status.backgroundColor = '#440000' - status.borderColor = '#ff0000' - break - } -} -``` - -## Layout Considerations - -BoxRenderable automatically applies padding for borders: - -- Single-line borders: 1 character padding on each side -- The padding is internal and doesn't affect the specified width/height -- Child components are positioned inside the border area - -```typescript -const box = new BoxRenderable('box', { - width: 20, - height: 10, - border: true -}) - -// Actual content area is 18x8 (20-2 for borders, 10-2 for borders) -const text = new TextRenderable('text', { - content: 'Content inside' -}) - -box.appendChild(text) -// Text will be positioned inside the borders -``` \ No newline at end of file diff --git a/packages/core/docs/api/components/framebuffer.md b/packages/core/docs/api/components/framebuffer.md deleted file mode 100644 index b51906c26..000000000 --- a/packages/core/docs/api/components/framebuffer.md +++ /dev/null @@ -1,394 +0,0 @@ -# FrameBufferRenderable - -An offscreen rendering buffer component that allows for advanced rendering techniques like double buffering, caching, and compositing. - -## Class: `FrameBufferRenderable` - -```typescript -import { FrameBufferRenderable } from '@opentui/core' - -const frameBuffer = new FrameBufferRenderable('buffer', { - width: 80, - height: 24, - respectAlpha: true -}) -``` - -## Constructor - -### `new FrameBufferRenderable(id: string, options: FrameBufferOptions)` - -## Options - -### `FrameBufferOptions` - -Extends [`RenderableOptions`](../renderable.md#renderableoptions) with: - -| Property | Type | Required | Default | Description | -|----------|------|----------|---------|-------------| -| `width` | `number` | Yes | - | Buffer width in columns | -| `height` | `number` | Yes | - | Buffer height in rows | -| `respectAlpha` | `boolean` | No | `false` | Enable alpha blending | - -## Properties - -### Buffer Properties - -| Property | Type | Description | -|----------|------|-------------| -| `frameBuffer` | `OptimizedBuffer` | The internal buffer instance | -| `respectAlpha` | `boolean` | Whether alpha blending is enabled | - -## Methods - -All methods from [`Renderable`](../renderable.md) plus: - -### Direct Buffer Access - -The `frameBuffer` property provides direct access to the `OptimizedBuffer` instance, allowing you to: - -```typescript -// Clear the buffer -frameBuffer.frameBuffer.clear() - -// Draw text -frameBuffer.frameBuffer.drawText('Hello', 0, 0) - -// Fill rectangle -frameBuffer.frameBuffer.fillRect(0, 0, 10, 5, '#ff0000') - -// Draw borders -frameBuffer.frameBuffer.drawBorder(0, 0, 20, 10, 'single') -``` - -## Use Cases - -### 1. Caching Complex Renders - -```typescript -class CachedComponent extends FrameBufferRenderable { - private isDirty = true - - constructor(id: string, width: number, height: number) { - super(id, { width, height, respectAlpha: false }) - } - - update() { - if (this.isDirty) { - // Clear and redraw only when needed - this.frameBuffer.clear() - this.drawComplexContent() - this.isDirty = false - } - } - - private drawComplexContent() { - // Expensive rendering operations - for (let y = 0; y < this.height; y++) { - for (let x = 0; x < this.width; x++) { - const char = this.calculateComplexChar(x, y) - this.frameBuffer.drawText(char, x, y) - } - } - } - - markDirty() { - this.isDirty = true - } -} -``` - -### 2. Animation Buffers - -```typescript -class AnimatedSprite extends FrameBufferRenderable { - private frames: string[][] = [] - private currentFrame = 0 - - constructor(id: string, frames: string[][]) { - const width = Math.max(...frames.map(f => f[0]?.length || 0)) - const height = Math.max(...frames.map(f => f.length)) - - super(id, { width, height, respectAlpha: true }) - this.frames = frames - this.drawFrame(0) - } - - nextFrame() { - this.currentFrame = (this.currentFrame + 1) % this.frames.length - this.drawFrame(this.currentFrame) - } - - private drawFrame(index: number) { - this.frameBuffer.clear() - const frame = this.frames[index] - - frame.forEach((line, y) => { - this.frameBuffer.drawText(line, 0, y) - }) - } -} - -// Usage -const sprite = new AnimatedSprite('sprite', [ - [' O ', ' /|\\ ', ' / \\ '], // Frame 1 - [' O ', ' \\|/ ', ' / \\ '], // Frame 2 - [' O ', ' /|\\ ', ' \\ / '], // Frame 3 -]) - -setInterval(() => sprite.nextFrame(), 100) -``` - -### 3. Layered Rendering - -```typescript -class LayeredView extends GroupRenderable { - private background: FrameBufferRenderable - private midground: FrameBufferRenderable - private foreground: FrameBufferRenderable - - constructor(id: string, width: number, height: number) { - super(id, { width, height }) - - // Create layers with alpha support - this.background = new FrameBufferRenderable('bg', { - width, height, - respectAlpha: false - }) - - this.midground = new FrameBufferRenderable('mid', { - width, height, - respectAlpha: true - }) - - this.foreground = new FrameBufferRenderable('fg', { - width, height, - respectAlpha: true - }) - - // Stack layers - this.appendChild(this.background) - this.appendChild(this.midground) - this.appendChild(this.foreground) - } - - drawBackground(pattern: string) { - for (let y = 0; y < this.height; y++) { - for (let x = 0; x < this.width; x++) { - this.background.frameBuffer.drawText(pattern, x, y) - } - } - } - - drawMidground(content: string, x: number, y: number) { - this.midground.frameBuffer.clear() - this.midground.frameBuffer.drawText(content, x, y) - } - - drawForeground(overlay: string, x: number, y: number) { - this.foreground.frameBuffer.clear() - this.foreground.frameBuffer.drawText(overlay, x, y) - } -} -``` - -### 4. Viewport/Camera - -```typescript -class Viewport extends FrameBufferRenderable { - private worldBuffer: OptimizedBuffer - private cameraX = 0 - private cameraY = 0 - - constructor(id: string, viewWidth: number, viewHeight: number, worldWidth: number, worldHeight: number) { - super(id, { width: viewWidth, height: viewHeight }) - - // Create larger world buffer - this.worldBuffer = OptimizedBuffer.create(worldWidth, worldHeight) - this.renderWorld() - } - - private renderWorld() { - // Draw a large world - for (let y = 0; y < this.worldBuffer.height; y++) { - for (let x = 0; x < this.worldBuffer.width; x++) { - const char = ((x + y) % 2 === 0) ? '.' : ' ' - this.worldBuffer.drawText(char, x, y) - } - } - - // Add some landmarks - this.worldBuffer.drawText('START', 0, 0) - this.worldBuffer.drawText('END', this.worldBuffer.width - 5, this.worldBuffer.height - 1) - } - - moveCamera(dx: number, dy: number) { - this.cameraX = Math.max(0, Math.min(this.cameraX + dx, this.worldBuffer.width - this.width)) - this.cameraY = Math.max(0, Math.min(this.cameraY + dy, this.worldBuffer.height - this.height)) - this.updateView() - } - - private updateView() { - this.frameBuffer.clear() - - // Copy visible portion of world to viewport - for (let y = 0; y < this.height; y++) { - for (let x = 0; x < this.width; x++) { - const worldX = this.cameraX + x - const worldY = this.cameraY + y - - if (worldX < this.worldBuffer.width && worldY < this.worldBuffer.height) { - const cell = this.worldBuffer.getCell(worldX, worldY) - this.frameBuffer.setCell(x, y, cell) - } - } - } - } -} -``` - -### 5. Effects Buffer - -```typescript -class EffectsBuffer extends FrameBufferRenderable { - constructor(id: string, width: number, height: number) { - super(id, { width, height, respectAlpha: true }) - } - - applyGlowEffect(text: string, x: number, y: number, color: string) { - // Draw glow layers - const glowColors = ['#330000', '#660000', '#990000', color] - - glowColors.forEach((glowColor, layer) => { - const offset = glowColors.length - layer - 1 - - // Draw in all directions for glow - for (let dy = -offset; dy <= offset; dy++) { - for (let dx = -offset; dx <= offset; dx++) { - if (dx !== 0 || dy !== 0) { - this.frameBuffer.drawText(text, x + dx, y + dy, { - fg: glowColor, - alpha: 0.3 - }) - } - } - } - }) - - // Draw main text on top - this.frameBuffer.drawText(text, x, y, { fg: color }) - } - - applyNoiseEffect(intensity: number = 0.1) { - for (let y = 0; y < this.height; y++) { - for (let x = 0; x < this.width; x++) { - if (Math.random() < intensity) { - const noise = String.fromCharCode(0x2591 + Math.floor(Math.random() * 3)) - this.frameBuffer.drawText(noise, x, y, { - fg: '#333333', - alpha: 0.5 - }) - } - } - } - } -} -``` - -### 6. Double Buffering - -```typescript -class DoubleBuffered extends Renderable { - private frontBuffer: FrameBufferRenderable - private backBuffer: FrameBufferRenderable - - constructor(id: string, width: number, height: number) { - super(id, { width, height }) - - this.frontBuffer = new FrameBufferRenderable('front', { - width, height, - respectAlpha: false - }) - - this.backBuffer = new FrameBufferRenderable('back', { - width, height, - respectAlpha: false - }) - - this.appendChild(this.frontBuffer) - } - - draw(drawFn: (buffer: OptimizedBuffer) => void) { - // Clear back buffer - this.backBuffer.frameBuffer.clear() - - // Draw to back buffer - drawFn(this.backBuffer.frameBuffer) - - // Swap buffers - this.swapBuffers() - } - - private swapBuffers() { - // Swap the buffers - [this.frontBuffer, this.backBuffer] = [this.backBuffer, this.frontBuffer] - - // Update which one is visible - this.removeAllChildren() - this.appendChild(this.frontBuffer) - } -} -``` - -## Performance Considerations - -1. **Buffer Size**: Large buffers consume more memory. Size appropriately. - -2. **Alpha Blending**: `respectAlpha: true` has a performance cost. Only use when needed. - -3. **Clearing**: Clear buffers only when necessary, not every frame. - -4. **Reuse Buffers**: Reuse FrameBufferRenderables instead of creating new ones. - -5. **Batch Operations**: Group multiple draw operations together. - -## Best Practices - -1. **Use for Caching**: Cache complex, static content that doesn't change often. - -2. **Animation Frames**: Pre-render animation frames into buffers. - -3. **Layering**: Use multiple buffers with alpha for layered effects. - -4. **Viewport Pattern**: Use for scrollable areas larger than the screen. - -5. **Memory Management**: Destroy buffers when no longer needed: -```typescript -frameBuffer.destroy() -``` - -## Integration with Buffered Renderables - -Note: The base `Renderable` class also supports buffering via the `buffered: true` option. Consider using that for simpler cases: - -```typescript -// Simple buffering -const buffered = new BoxRenderable('box', { - buffered: true, // Uses internal frame buffer - width: 20, - height: 10 -}) - -// vs explicit FrameBuffer for advanced control -const frameBuffer = new FrameBufferRenderable('buffer', { - width: 20, - height: 10, - respectAlpha: true -}) -``` - -Use `FrameBufferRenderable` when you need: -- Direct buffer manipulation -- Alpha blending control -- Custom rendering pipelines -- Multi-buffer techniques \ No newline at end of file diff --git a/packages/core/docs/api/components/group.md b/packages/core/docs/api/components/group.md deleted file mode 100644 index 7da7b811b..000000000 --- a/packages/core/docs/api/components/group.md +++ /dev/null @@ -1,358 +0,0 @@ -# GroupRenderable - -A container component for organizing child components with flexbox layout support. - -## Class: `GroupRenderable` - -```typescript -import { GroupRenderable } from '@opentui/core' - -const group = new GroupRenderable('my-group', { - flexDirection: 'row', - gap: 2, - padding: 1 -}) -``` - -## Constructor - -### `new GroupRenderable(id: string, options: RenderableOptions)` - -## Options - -### `GroupRenderable Options` - -Uses standard [`RenderableOptions`](../renderable.md#renderableoptions). GroupRenderable is specifically designed for layout management. - -## Key Features - -GroupRenderable is a pure layout container that: -- Renders no visual content itself -- Manages child component positioning using Yoga flexbox -- Provides layout control through flexbox properties -- Supports nested layouts for complex UIs - -## Layout Properties - -All flexbox properties from [`Renderable`](../renderable.md) are particularly useful: - -| Property | Type | Default | Description | -|----------|------|---------|-------------| -| `flexDirection` | `'row' \| 'column' \| 'row-reverse' \| 'column-reverse'` | `'column'` | Main axis direction | -| `justifyContent` | `'flex-start' \| 'flex-end' \| 'center' \| 'space-between' \| 'space-around' \| 'space-evenly'` | `'flex-start'` | Main axis alignment | -| `alignItems` | `'flex-start' \| 'flex-end' \| 'center' \| 'baseline' \| 'stretch'` | `'stretch'` | Cross axis alignment | -| `flexWrap` | `'nowrap' \| 'wrap' \| 'wrap-reverse'` | `'nowrap'` | Wrap behavior | -| `gap` | `number` | `0` | Space between items | -| `padding` | `number \| string` | `0` | Inner spacing | -| `margin` | `number \| string` | `0` | Outer spacing | - -## Examples - -### Horizontal Layout - -```typescript -const row = new GroupRenderable('row', { - flexDirection: 'row', - justifyContent: 'space-between', - width: '100%', - height: 3 -}) - -const left = new TextRenderable('left', { content: 'Left' }) -const center = new TextRenderable('center', { content: 'Center' }) -const right = new TextRenderable('right', { content: 'Right' }) - -row.appendChild(left) -row.appendChild(center) -row.appendChild(right) -``` - -### Vertical Layout - -```typescript -const column = new GroupRenderable('column', { - flexDirection: 'column', - alignItems: 'center', - width: '100%', - height: '100%', - padding: 2 -}) - -const header = new TextRenderable('header', { - content: 'Header', - marginBottom: 1 -}) - -const content = new BoxRenderable('content', { - width: '80%', - flexGrow: 1, - borderStyle: 'single' -}) - -const footer = new TextRenderable('footer', { - content: 'Footer', - marginTop: 1 -}) - -column.appendChild(header) -column.appendChild(content) -column.appendChild(footer) -``` - -### Grid Layout - -```typescript -const grid = new GroupRenderable('grid', { - flexDirection: 'row', - flexWrap: 'wrap', - width: '100%', - gap: 1 -}) - -for (let i = 0; i < 9; i++) { - const cell = new BoxRenderable(`cell-${i}`, { - width: '33%', - height: 5, - borderStyle: 'single', - backgroundColor: '#333333' - }) - grid.appendChild(cell) -} -``` - -### Nested Groups - -```typescript -const app = new GroupRenderable('app', { - flexDirection: 'column', - width: '100%', - height: '100%' -}) - -const header = new GroupRenderable('header', { - flexDirection: 'row', - justifyContent: 'space-between', - padding: 1, - height: 3 -}) - -const body = new GroupRenderable('body', { - flexDirection: 'row', - flexGrow: 1 -}) - -const sidebar = new GroupRenderable('sidebar', { - flexDirection: 'column', - width: 20, - padding: 1 -}) - -const main = new GroupRenderable('main', { - flexDirection: 'column', - flexGrow: 1, - padding: 2 -}) - -body.appendChild(sidebar) -body.appendChild(main) -app.appendChild(header) -app.appendChild(body) -``` - -### Centered Content - -```typescript -const centerContainer = new GroupRenderable('center', { - width: '100%', - height: '100%', - justifyContent: 'center', - alignItems: 'center' -}) - -const dialog = new BoxRenderable('dialog', { - width: 40, - height: 10, - borderStyle: 'double', - padding: 2 -}) - -centerContainer.appendChild(dialog) -``` - -### List Layout - -```typescript -const list = new GroupRenderable('list', { - flexDirection: 'column', - width: '100%', - gap: 1, - padding: 1 -}) - -const items = ['Item 1', 'Item 2', 'Item 3', 'Item 4'] - -items.forEach((text, index) => { - const item = new GroupRenderable(`item-${index}`, { - flexDirection: 'row', - alignItems: 'center', - padding: 1 - }) - - const bullet = new TextRenderable(`bullet-${index}`, { - content: '• ', - marginRight: 1 - }) - - const label = new TextRenderable(`label-${index}`, { - content: text - }) - - item.appendChild(bullet) - item.appendChild(label) - list.appendChild(item) -}) -``` - -### Responsive Layout - -```typescript -const responsive = new GroupRenderable('responsive', { - flexDirection: 'row', - width: '100%', - height: '100%' -}) - -const leftPanel = new BoxRenderable('left', { - minWidth: 20, - maxWidth: 40, - width: '25%', - height: '100%', - borderStyle: 'single' -}) - -const mainPanel = new BoxRenderable('main', { - flexGrow: 1, - height: '100%', - borderStyle: 'single' -}) - -const rightPanel = new BoxRenderable('right', { - width: '20%', - minWidth: 15, - height: '100%', - borderStyle: 'single' -}) - -responsive.appendChild(leftPanel) -responsive.appendChild(mainPanel) -responsive.appendChild(rightPanel) -``` - -### Form Layout - -```typescript -const form = new GroupRenderable('form', { - flexDirection: 'column', - width: 50, - padding: 2, - gap: 2 -}) - -// Form fields -const fields = [ - { label: 'Name:', id: 'name' }, - { label: 'Email:', id: 'email' }, - { label: 'Message:', id: 'message' } -] - -fields.forEach(field => { - const row = new GroupRenderable(`${field.id}-row`, { - flexDirection: 'row', - alignItems: 'center' - }) - - const label = new TextRenderable(`${field.id}-label`, { - content: field.label, - width: 10 - }) - - const input = new InputRenderable(`${field.id}-input`, { - flexGrow: 1, - placeholder: `Enter ${field.id}...` - }) - - row.appendChild(label) - row.appendChild(input) - form.appendChild(row) -}) - -// Submit button -const buttonRow = new GroupRenderable('button-row', { - flexDirection: 'row', - justifyContent: 'flex-end', - marginTop: 1 -}) - -const submitBtn = new BoxRenderable('submit', { - width: 15, - height: 3, - borderStyle: 'rounded', - backgroundColor: '#0066cc' -}) - -buttonRow.appendChild(submitBtn) -form.appendChild(buttonRow) -``` - -## Best Practices - -1. **Use for Layout Only**: GroupRenderable should be used purely for layout organization, not for visual styling. - -2. **Combine with BoxRenderable**: For containers that need borders or backgrounds, use BoxRenderable instead. - -3. **Leverage Flexbox**: Take advantage of flexbox properties for responsive layouts. - -4. **Nest Groups**: Create complex layouts by nesting multiple GroupRenderables. - -5. **Performance**: Groups have minimal overhead as they don't render any visual content themselves. - -## Common Patterns - -### Toolbar -```typescript -const toolbar = new GroupRenderable('toolbar', { - flexDirection: 'row', - justifyContent: 'space-between', - padding: 1, - height: 3 -}) -``` - -### Sidebar Layout -```typescript -const layout = new GroupRenderable('layout', { - flexDirection: 'row', - width: '100%', - height: '100%' -}) - -const sidebar = new GroupRenderable('sidebar', { - width: 25, - flexDirection: 'column' -}) - -const content = new GroupRenderable('content', { - flexGrow: 1, - flexDirection: 'column' -}) -``` - -### Card Grid -```typescript -const cardGrid = new GroupRenderable('cards', { - flexDirection: 'row', - flexWrap: 'wrap', - gap: 2, - padding: 2 -}) -``` \ No newline at end of file diff --git a/packages/core/docs/api/components/input.md b/packages/core/docs/api/components/input.md deleted file mode 100644 index fb4a5f716..000000000 --- a/packages/core/docs/api/components/input.md +++ /dev/null @@ -1,289 +0,0 @@ -# InputRenderable - -Text input component with support for placeholders, validation, and keyboard navigation. - -## Class: `InputRenderable` - -```typescript -import { InputRenderable } from '@opentui/core' - -const input = new InputRenderable('my-input', { - placeholder: 'Enter your name...', - width: 30, - value: '' -}) -``` - -## Constructor - -### `new InputRenderable(id: string, options: InputRenderableOptions)` - -## Options - -### `InputRenderableOptions` - -Extends [`RenderableOptions`](../renderable.md#renderableoptions) with: - -| Property | Type | Default | Description | -|----------|------|---------|-------------| -| `value` | `string` | `''` | Initial input value | -| `placeholder` | `string` | `''` | Placeholder text when empty | -| `textColor` | `string \| RGBA` | `'#FFFFFF'` | Text color | -| `backgroundColor` | `string \| RGBA` | `'transparent'` | Background color | -| `placeholderColor` | `string \| RGBA` | `'#666666'` | Placeholder text color | -| `cursorColor` | `string \| RGBA` | `'#FFFFFF'` | Cursor color | -| `focusedBackgroundColor` | `string \| RGBA` | `'#1a1a1a'` | Background when focused | -| `focusedTextColor` | `string \| RGBA` | `'#FFFFFF'` | Text color when focused | -| `maxLength` | `number` | `1000` | Maximum character length | - -## Properties - -### Value Properties - -| Property | Type | Description | -|----------|------|-------------| -| `value` | `string` | Get/set input value | -| `placeholder` | `string` | Set placeholder text | -| `cursorPosition` | `number` | Set cursor position | -| `maxLength` | `number` | Set maximum character limit | - -### Style Properties - -| Property | Type | Description | -|----------|------|-------------| -| `textColor` | `ColorInput` | Set text color | -| `backgroundColor` | `ColorInput` | Set background color | -| `placeholderColor` | `ColorInput` | Set placeholder color | -| `cursorColor` | `ColorInput` | Set cursor color | -| `focusedBackgroundColor` | `ColorInput` | Set focused background color | -| `focusedTextColor` | `ColorInput` | Set focused text color | - -## Methods - -All methods from [`Renderable`](../renderable.md) plus: - -### `handleKeyPress(key: ParsedKey | string): boolean` -Handle keyboard input (called internally by the renderer). - -### `focus(): void` -Focus the input and show cursor. - -```typescript -input.focus() -``` - -### `blur(): void` -Remove focus and hide cursor. Emits `change` event if value changed. - -```typescript -input.blur() -``` - -## Events - -InputRenderable emits the following events: - -| Event | Data | Description | -|-------|------|-------------| -| `input` | `value: string` | Value changed during typing | -| `change` | `value: string` | Value committed (on blur or enter) | -| `enter` | `value: string` | Enter key pressed | - -## Keyboard Shortcuts - -| Key | Action | -|-----|--------| -| `Left Arrow` | Move cursor left | -| `Right Arrow` | Move cursor right | -| `Home` | Move to start | -| `End` | Move to end | -| `Backspace` | Delete before cursor | -| `Delete` | Delete at cursor | -| `Enter` | Submit value and emit events | -| Any printable character | Insert at cursor position | - -## Examples - -### Basic Input - -```typescript -const nameInput = new InputRenderable('name', { - placeholder: 'Enter name...', - width: 30 -}) - -nameInput.on('submit', (value) => { - console.log('Name entered:', value) -}) -``` - -### Password Input - -```typescript -const passwordInput = new Input('password', { - placeholder: 'Enter password...', - password: true, - width: 30 -}) - -passwordInput.on('submit', (value) => { - console.log('Password length:', value.length) -}) -``` - -### Input with Validation - -```typescript -const emailInput = new Input('email', { - placeholder: 'user@example.com', - width: 40 -}) - -emailInput.on('change', (value) => { - const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) - emailInput.fg = isValid ? '#00ff00' : '#ff0000' -}) -``` - -### Multi-line Input - -```typescript -const textArea = new Input('textarea', { - multiline: true, - width: 50, - height: 10, - placeholder: 'Enter your message...' -}) - -textArea.on('change', (value) => { - const lines = value.split('\n').length - console.log(`Lines: ${lines}`) -}) -``` - -### Input with Max Length - -```typescript -const limitedInput = new Input('limited', { - placeholder: 'Max 10 characters', - maxLength: 10, - width: 30 -}) - -limitedInput.on('change', (value) => { - const remaining = 10 - value.length - console.log(`${remaining} characters remaining`) -}) -``` - -### Styled Input - -```typescript -const styledInput = new Input('styled', { - placeholder: 'Styled input', - width: 30, - fg: '#00ff00', - bg: '#1a1a1a', - placeholderFg: '#666666', - cursorBg: '#00ff00', - focusedBg: '#2a2a2a' -}) -``` - -### Form with Multiple Inputs - -```typescript -const form = new GroupRenderable('form', { - flexDirection: 'column', - padding: 2 -}) - -const usernameInput = new Input('username', { - placeholder: 'Username', - width: '100%', - marginBottom: 1 -}) - -const passwordInput = new Input('password', { - placeholder: 'Password', - password: true, - width: '100%', - marginBottom: 1 -}) - -const submitButton = new BoxRenderable('submit', { - width: '100%', - height: 3, - borderStyle: 'rounded', - backgroundColor: '#0066cc' -}) - -form.appendChild(usernameInput) -form.appendChild(passwordInput) -form.appendChild(submitButton) - -// Handle form submission -passwordInput.on('submit', () => { - const username = usernameInput.value - const password = passwordInput.value - console.log('Login:', { username, password }) -}) -``` - -### Read-only Input - -```typescript -const readOnlyInput = new Input('readonly', { - value: 'This cannot be edited', - editable: false, - width: 30, - fg: '#999999' -}) -``` - -### Dynamic Placeholder - -```typescript -const searchInput = new Input('search', { - placeholder: 'Search...', - width: 40 -}) - -// Update placeholder based on context -function setSearchContext(context: string) { - searchInput.placeholder = `Search ${context}...` -} - -setSearchContext('users') // "Search users..." -setSearchContext('files') // "Search files..." -``` - -## Focus Management - -Input components can receive keyboard focus: - -```typescript -const input = new Input('input', { - width: 30 -}) - -// Request focus -input.focus() - -// Check if focused -if (input.focused) { - console.log('Input has focus') -} - -// Remove focus -input.blur() - -// Focus events -input.on('focus', () => { - console.log('Input focused') -}) - -input.on('blur', () => { - console.log('Input blurred') -}) -``` \ No newline at end of file diff --git a/packages/core/docs/api/components/renderables.md b/packages/core/docs/api/components/renderables.md deleted file mode 100644 index c5e75d416..000000000 --- a/packages/core/docs/api/components/renderables.md +++ /dev/null @@ -1,592 +0,0 @@ -# Renderables API - -Renderables are the building blocks of OpenTUI interfaces. They represent visual elements that can be rendered to the terminal screen. - -## Renderable Base Class - -All visual components in OpenTUI extend the `Renderable` base class, which provides core functionality for layout, rendering, and event handling. - -### Creating a Renderable - -```typescript -import { Renderable, OptimizedBuffer, RenderContext } from '@opentui/core'; - -class MyComponent extends Renderable { - constructor(id: string, options: RenderableOptions = {}) { - super(id, options); - } - - // Override to provide custom rendering - protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { - // Custom rendering logic - buffer.drawText('My Component', this.x, this.y, RGBA.fromHex('#ffffff')); - } -} -``` - -### RenderableOptions - -```typescript -interface RenderableOptions { - // Size - width?: number | 'auto' | `${number}%` - height?: number | 'auto' | `${number}%` - - // Visibility - visible?: boolean - zIndex?: number - buffered?: boolean - - // Layout (Flexbox) - flexGrow?: number - flexShrink?: number - flexDirection?: 'row' | 'column' | 'row-reverse' | 'column-reverse' - flexBasis?: number | 'auto' - alignItems?: 'flex-start' | 'flex-end' | 'center' | 'baseline' | 'stretch' - justifyContent?: 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly' - - // Position - position?: 'relative' | 'absolute' - top?: number | 'auto' | `${number}%` - right?: number | 'auto' | `${number}%` - bottom?: number | 'auto' | `${number}%` - left?: number | 'auto' | `${number}%` - - // Size constraints - minWidth?: number | `${number}%` - minHeight?: number | `${number}%` - maxWidth?: number | `${number}%` - maxHeight?: number | `${number}%` - - // Spacing - margin?: number | 'auto' | `${number}%` - marginTop?: number | 'auto' | `${number}%` - marginRight?: number | 'auto' | `${number}%` - marginBottom?: number | 'auto' | `${number}%` - marginLeft?: number | 'auto' | `${number}%` - - padding?: number | `${number}%` - paddingTop?: number | `${number}%` - paddingRight?: number | `${number}%` - paddingBottom?: number | `${number}%` - paddingLeft?: number | `${number}%` - - // Layout control - enableLayout?: boolean -} -``` - -## Properties - -### Layout Properties - -| Property | Type | Description | -|----------|------|-------------| -| `x` | `number` | Computed X position (read-only) | -| `y` | `number` | Computed Y position (read-only) | -| `width` | `number` | Computed width in characters | -| `height` | `number` | Computed height in lines | -| `visible` | `boolean` | Visibility state | -| `zIndex` | `number` | Stacking order | - -### Position Properties - -| Property | Type | Description | -|----------|------|-------------| -| `position` | `'relative' \| 'absolute'` | Position type | -| `top` | `number \| 'auto' \| \`${number}%\`` | Top offset | -| `right` | `number \| 'auto' \| \`${number}%\`` | Right offset | -| `bottom` | `number \| 'auto' \| \`${number}%\`` | Bottom offset | -| `left` | `number \| 'auto' \| \`${number}%\`` | Left offset | - -### Flexbox Properties - -| Property | Type | Description | -|----------|------|-------------| -| `flexGrow` | `number` | Flex grow factor | -| `flexShrink` | `number` | Flex shrink factor | -| `flexDirection` | `string` | Flex container direction | -| `flexBasis` | `number \| 'auto'` | Initial main size | -| `alignItems` | `string` | Cross-axis alignment | -| `justifyContent` | `string` | Main-axis alignment | - -### Size Constraint Properties - -| Property | Type | Description | -|----------|------|-------------| -| `minWidth` | `number \| \`${number}%\`` | Minimum width | -| `minHeight` | `number \| \`${number}%\`` | Minimum height | -| `maxWidth` | `number \| \`${number}%\`` | Maximum width | -| `maxHeight` | `number \| \`${number}%\`` | Maximum height | - -### Spacing Properties - -| Property | Type | Description | -|----------|------|-------------| -| `margin` | `number \| 'auto' \| \`${number}%\`` | All margins | -| `marginTop` | `number \| 'auto' \| \`${number}%\`` | Top margin | -| `marginRight` | `number \| 'auto' \| \`${number}%\`` | Right margin | -| `marginBottom` | `number \| 'auto' \| \`${number}%\`` | Bottom margin | -| `marginLeft` | `number \| 'auto' \| \`${number}%\`` | Left margin | -| `padding` | `number \| \`${number}%\`` | All padding | -| `paddingTop` | `number \| \`${number}%\`` | Top padding | -| `paddingRight` | `number \| \`${number}%\`` | Right padding | -| `paddingBottom` | `number \| \`${number}%\`` | Bottom padding | -| `paddingLeft` | `number \| \`${number}%\`` | Left padding | - -### State Properties - -| Property | Type | Description | -|----------|------|-------------| -| `focused` | `boolean` | Focus state (read-only) | -| `selectable` | `boolean` | Whether text can be selected | -| `focusable` | `boolean` | Whether component can receive focus | - -## Methods - -### Hierarchy Management - -#### `add(child: Renderable, index?: number): number` -Add a child component at optional index. - -```typescript -container.add(childComponent); -container.add(childComponent, 0); // Insert at beginning -``` - -#### `insertBefore(child: Renderable, anchor?: Renderable): number` -Insert a child before another child. - -```typescript -container.insertBefore(newChild, existingChild); -``` - -#### `remove(id: string): void` -Remove a child component by ID. - -```typescript -container.remove('child-id'); -``` - -#### `getRenderable(id: string): Renderable | undefined` -Find a child by ID. - -```typescript -const child = container.getRenderable('my-child'); -``` - -#### `getChildren(): Renderable[]` -Get all child components. - -```typescript -const children = container.getChildren(); -``` - -### Focus Management - -#### `focus(): void` -Request focus for this component. - -```typescript -input.focus(); -``` - -#### `blur(): void` -Remove focus from this component. - -```typescript -input.blur(); -``` - -### Layout Control - -#### `needsUpdate(): void` -Mark the component as needing a render update. - -```typescript -component.needsUpdate(); -``` - -#### `requestMeasure(): void` -Request a layout measurement pass. - -```typescript -component.requestMeasure(); -``` - -#### `getLayoutNode(): TrackedNode` -Get the Yoga layout node. - -```typescript -const node = component.getLayoutNode(); -``` - -### Selection - -#### `getSelectedText(): string` -Get the currently selected text. - -```typescript -const selected = component.getSelectedText(); -``` - -### Rendering (Protected Methods) - -#### `renderSelf(buffer: OptimizedBuffer, deltaTime: number): void` -Main rendering method (override in subclasses). - -```typescript -protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { - // Custom rendering logic - buffer.drawText('Hello', this.x, this.y, RGBA.fromHex('#ffffff')); -} -``` - -#### `render(buffer: OptimizedBuffer, deltaTime: number): void` -Full render method (usually not overridden). - -### Event Handling (Protected Methods) - -#### `handleMouse(event: MouseEvent): boolean` -Handle mouse events (override for custom behavior). - -```typescript -protected handleMouse(event: MouseEvent): boolean { - if (event.type === 'click') { - // Handle click - return true; // Event handled - } - return false; -} -``` - -#### `handleKeyPress(key: ParsedKey): boolean` -Handle keyboard events (override for custom behavior). - -```typescript -protected handleKeyPress(key: ParsedKey): boolean { - if (key.name === 'enter') { - // Handle enter key - return true; - } - return false; -} -``` - -#### `handleSelection(state: SelectionState): void` -Handle text selection (override for custom behavior). - -```typescript -protected handleSelection(state: SelectionState): void { - // Custom selection logic -} -``` - -### Lifecycle Methods - -#### `onResize(width: number, height: number): void` -Called when the component is resized. - -```typescript -protected onResize(width: number, height: number): void { - super.onResize(width, height); - // Custom resize logic -} -``` - -#### `destroy(): void` -Clean up resources. - -```typescript -component.destroy(); -``` - -## Events - -Renderable extends `EventEmitter` and emits the following events: - -| Event | Data | Description | -|-------|------|-------------| -| `layout-changed` | - | Layout was recalculated | -| `added` | `parent: Renderable` | Added to parent | -| `removed` | `parent: Renderable` | Removed from parent | -| `resized` | `{width, height}` | Size changed | -| `focused` | - | Component gained focus | -| `blurred` | - | Component lost focus | - -## Built-in Components - -### BoxRenderable - -Container with optional borders and background. - -```typescript -import { BoxRenderable } from '@opentui/core'; - -const box = new BoxRenderable('myBox', { - width: 30, - height: 15, - backgroundColor: '#222222', - borderStyle: 'double', - borderColor: '#3498db', - title: 'My Box', - titleAlignment: 'center' -}); -``` - -**BoxRenderable Options:** -- `backgroundColor`: Background color -- `borderStyle`: Border style ('single', 'double', 'rounded', 'bold', 'ascii') -- `border`: Show borders (boolean or array of sides) -- `borderColor`: Border color -- `title`: Title text -- `titleAlignment`: Title alignment ('left', 'center', 'right') -- `focusedBorderColor`: Border color when focused - -### TextRenderable - -Displays text with styling. - -```typescript -import { TextRenderable } from '@opentui/core'; - -const text = new TextRenderable('myText', { - content: 'Hello, world!', - fg: '#ffffff', - bg: '#000000', - selectable: true -}); -``` - -**TextRenderable Options:** -- `content`: Text content (string or StyledText) -- `fg`: Foreground color -- `bg`: Background color -- `selectable`: Enable text selection -- `attributes`: Text attributes (bold, underline, etc.) - -### FrameBufferRenderable - -Provides an offscreen buffer for custom drawing. - -```typescript -import { FrameBufferRenderable, RGBA } from '@opentui/core'; - -const canvas = new FrameBufferRenderable('myCanvas', { - width: 40, - height: 20, - respectAlpha: true -}); - -// Draw on the internal buffer -canvas.frameBuffer.fillRect(0, 0, 40, 20, RGBA.fromHex('#000000')); -canvas.frameBuffer.drawText('Hello', 2, 2, RGBA.fromHex('#ffffff')); -``` - -**FrameBufferRenderable Properties:** -- `frameBuffer`: Internal OptimizedBuffer for drawing -- `respectAlpha`: Enable alpha blending - -### ASCIIFontRenderable - -Renders text using ASCII art fonts. Supports both built-in fonts and custom fonts loaded from JSON files. - -```typescript -import { ASCIIFontRenderable, fonts, type FontDefinition } from '@opentui/core'; - -// Using built-in font -const asciiText = new ASCIIFontRenderable('myAsciiText', { - text: 'HELLO', - font: fonts.block, - fg: '#ffffff' -}); - -// Using custom font from JSON file -import customFont from './my-custom-font.json'; - -const customText = new ASCIIFontRenderable('customText', { - text: 'CUSTOM', - font: customFont as FontDefinition, - fg: '#00ff00' -}); -``` - -**ASCIIFontRenderable Options:** -- `text`: Text to render -- `font`: FontDefinition object (use `fonts.tiny`, `fonts.block`, `fonts.slick`, `fonts.shade` for built-in, or import custom JSON) -- `fg`: Foreground color (string or RGBA or array for multi-color support) -- `bg`: Background color (string or RGBA) -- `selectable`: Enable text selection (boolean) -- `selectionBg`: Selection background color -- `selectionFg`: Selection foreground color - -**Built-in Fonts:** -- `fonts.tiny`: Small 2-line font -- `fonts.block`: Bold block letters -- `fonts.shade`: Shaded 3D effect -- `fonts.slick`: Slick stylized font - -**Custom Fonts:** -Custom fonts must be cfont-compatible JSON files with the following structure: -```json -{ - "name": "myfont", - "lines": 3, - "letterspace_size": 1, - "letterspace": [" ", " ", " "], - "chars": { - "A": ["▄▀█", "█▀█", "█ █"], - "B": ["█▄▄", "█▄█", "█▄█"], - // ... more characters - } -} -``` - -### Input - -Text input field component. - -```typescript -import { Input } from '@opentui/core'; - -const input = new Input('myInput', { - width: 30, - placeholder: 'Enter text...', - value: '', - password: false -}); - -input.on('submit', (value: string) => { - console.log('Submitted:', value); -}); -``` - -**Input Options:** -- `value`: Initial value -- `placeholder`: Placeholder text -- `password`: Hide input (password mode) -- `multiline`: Enable multiline input - -### Select - -Dropdown selection component. - -```typescript -import { Select } from '@opentui/core'; - -const select = new Select('mySelect', { - options: ['Option 1', 'Option 2', 'Option 3'], - selected: 0, - width: 20 -}); - -select.on('change', (index: number, value: string) => { - console.log('Selected:', value); -}); -``` - -**Select Options:** -- `options`: Array of options -- `selected`: Initially selected index -- `maxHeight`: Maximum dropdown height - -### TabSelect - -Tab selection component. - -```typescript -import { TabSelect } from '@opentui/core'; - -const tabs = new TabSelect('myTabs', { - tabs: ['Tab 1', 'Tab 2', 'Tab 3'], - selected: 0 -}); - -tabs.on('change', (index: number) => { - console.log('Selected tab:', index); -}); -``` - -**TabSelect Options:** -- `tabs`: Array of tab labels -- `selected`: Initially selected tab index - -### Group - -Container for grouping components without visual representation. - -```typescript -import { Group } from '@opentui/core'; - -const group = new Group('myGroup', { - flexDirection: 'row', - gap: 2 -}); -``` - -## Creating Custom Components - -Extend the `Renderable` class to create custom components: - -```typescript -import { Renderable, OptimizedBuffer, RGBA, RenderableOptions } from '@opentui/core'; - -interface ProgressBarOptions extends RenderableOptions { - progress?: number; - barColor?: string; - backgroundColor?: string; -} - -class ProgressBar extends Renderable { - private _progress: number = 0; - private _barColor: RGBA; - private _backgroundColor: RGBA; - - constructor(id: string, options: ProgressBarOptions = {}) { - super(id, { - height: 1, - ...options - }); - - this._progress = options.progress || 0; - this._barColor = RGBA.fromHex(options.barColor || '#3498db'); - this._backgroundColor = RGBA.fromHex(options.backgroundColor || '#222222'); - } - - get progress(): number { - return this._progress; - } - - set progress(value: number) { - this._progress = Math.max(0, Math.min(100, value)); - this.needsUpdate(); - } - - protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { - const width = this.width; - const filledWidth = Math.floor((width * this._progress) / 100); - - // Draw background - buffer.fillRect(this.x, this.y, width, 1, this._backgroundColor); - - // Draw progress - if (filledWidth > 0) { - buffer.fillRect(this.x, this.y, filledWidth, 1, this._barColor); - } - - // Draw percentage text - const text = `${this._progress}%`; - const textX = this.x + Math.floor((width - text.length) / 2); - buffer.drawText(text, textX, this.y, RGBA.fromHex('#ffffff')); - } -} - -// Usage -const progressBar = new ProgressBar('progress', { - width: 30, - progress: 75 -}); - -// Update progress -progressBar.progress = 80; -``` \ No newline at end of file diff --git a/packages/core/docs/api/components/select.md b/packages/core/docs/api/components/select.md deleted file mode 100644 index 6ef3c17d3..000000000 --- a/packages/core/docs/api/components/select.md +++ /dev/null @@ -1,425 +0,0 @@ -# Select - -A selection list component for choosing from multiple options with keyboard navigation. - -## Class: `Select` - -```typescript -import { Select } from '@opentui/core' - -const select = new Select('my-select', { - options: ['Option 1', 'Option 2', 'Option 3'], - width: 30, - height: 10 -}) -``` - -## Constructor - -### `new Select(id: string, options: SelectOptions)` - -## Options - -### `SelectOptions` - -Extends [`RenderableOptions`](../renderable.md#renderableoptions) with: - -| Property | Type | Default | Description | -|----------|------|---------|-------------| -| `options` | `string[]` | `[]` | List of selectable options | -| `selectedIndex` | `number` | `0` | Initially selected index | -| `fg` | `ColorInput` | `'#ffffff'` | Text color | -| `bg` | `ColorInput` | `'transparent'` | Background color | -| `selectedFg` | `ColorInput` | `'#000000'` | Selected item text color | -| `selectedBg` | `ColorInput` | `'#00aaff'` | Selected item background | -| `focusedSelectedFg` | `ColorInput` | - | Focused selected text color | -| `focusedSelectedBg` | `ColorInput` | - | Focused selected background | -| `scrollbar` | `boolean` | `true` | Show scrollbar indicator | -| `wrap` | `boolean` | `false` | Wrap selection at boundaries | - -## Properties - -### Selection Properties - -| Property | Type | Description | -|----------|------|-------------| -| `options` | `string[]` | Get/set option list | -| `selectedIndex` | `number` | Get/set selected index | -| `selectedOption` | `string \| undefined` | Get selected option text | -| `length` | `number` | Number of options | - -### Display Properties - -| Property | Type | Description | -|----------|------|-------------| -| `scrollOffset` | `number` | Current scroll position | -| `visibleItems` | `number` | Number of visible items | - -## Methods - -All methods from [`Renderable`](../renderable.md) plus: - -### `setOptions(options: string[]): void` -Update the option list. - -```typescript -select.setOptions(['New 1', 'New 2', 'New 3']) -``` - -### `selectNext(): void` -Select the next option. - -```typescript -select.selectNext() -``` - -### `selectPrevious(): void` -Select the previous option. - -```typescript -select.selectPrevious() -``` - -### `selectFirst(): void` -Select the first option. - -```typescript -select.selectFirst() -``` - -### `selectLast(): void` -Select the last option. - -```typescript -select.selectLast() -``` - -### `selectIndex(index: number): void` -Select option by index. - -```typescript -select.selectIndex(2) -``` - -### `getSelectedOption(): string | undefined` -Get the currently selected option text. - -```typescript -const selected = select.getSelectedOption() -``` - -### `scrollToSelected(): void` -Scroll to make selected item visible. - -```typescript -select.scrollToSelected() -``` - -## Events - -Select emits the following events: - -| Event | Data | Description | -|-------|------|-------------| -| `change` | `{index: number, value: string}` | Selection changed | -| `select` | `{index: number, value: string}` | Item selected (Enter key) | -| `scroll` | `offset: number` | List scrolled | - -## Keyboard Shortcuts - -| Key | Action | -|-----|--------| -| `Up Arrow` / `k` | Select previous item | -| `Down Arrow` / `j` | Select next item | -| `Home` / `g` | Select first item | -| `End` / `G` | Select last item | -| `Page Up` | Scroll up one page | -| `Page Down` | Scroll down one page | -| `Enter` / `Space` | Confirm selection | - -## Examples - -### Basic Select - -```typescript -const select = new Select('select', { - options: ['Apple', 'Banana', 'Cherry', 'Date'], - width: 20, - height: 5 -}) - -select.on('select', ({ value }) => { - console.log('Selected:', value) -}) -``` - -### Styled Select - -```typescript -const styledSelect = new Select('styled', { - options: ['Red', 'Green', 'Blue', 'Yellow'], - width: 25, - height: 6, - fg: '#cccccc', - bg: '#1a1a1a', - selectedFg: '#ffffff', - selectedBg: '#0066cc', - focusedSelectedBg: '#0088ff' -}) -``` - -### Dynamic Options - -```typescript -const dynamicSelect = new Select('dynamic', { - options: [], - width: 30, - height: 10 -}) - -// Load options asynchronously -async function loadOptions() { - const response = await fetch('/api/options') - const options = await response.json() - dynamicSelect.setOptions(options) -} - -loadOptions() -``` - -### Menu System - -```typescript -const menu = new Select('menu', { - options: [ - 'New File', - 'Open File', - 'Save', - 'Save As...', - '---', - 'Settings', - 'Exit' - ], - width: 20, - height: 8 -}) - -menu.on('select', ({ value }) => { - switch (value) { - case 'New File': - createNewFile() - break - case 'Open File': - openFileDialog() - break - case 'Exit': - process.exit(0) - break - } -}) -``` - -### File Browser - -```typescript -const fileBrowser = new Select('files', { - options: [], - width: 40, - height: 20, - selectedBg: '#003366' -}) - -// Load directory contents -import { readdirSync } from 'fs' - -function loadDirectory(path: string) { - const entries = readdirSync(path, { withFileTypes: true }) - const options = entries.map(entry => { - const prefix = entry.isDirectory() ? '📁 ' : '📄 ' - return prefix + entry.name - }) - fileBrowser.setOptions(options) -} - -loadDirectory('./') - -fileBrowser.on('select', ({ value }) => { - const name = value.substring(2) // Remove icon - if (value.startsWith('📁')) { - loadDirectory(`./${name}`) - } else { - openFile(name) - } -}) -``` - -### Multi-Column Select - -```typescript -const table = new Select('table', { - options: [ - 'Name Age City', - '─────────────────────────', - 'Alice 28 NYC', - 'Bob 32 LA', - 'Charlie 25 SF', - 'Diana 30 CHI' - ], - width: 30, - height: 8, - selectedBg: '#334455' -}) - -// Skip header rows when selecting -table.on('change', ({ index }) => { - if (index <= 1) { - table.selectIndex(2) // Skip to first data row - } -}) -``` - -### Filtered Select - -```typescript -class FilteredSelect extends GroupRenderable { - private input: InputRenderable - private select: Select - private allOptions: string[] - - constructor(id: string, options: string[]) { - super(id, { - flexDirection: 'column', - width: 30, - height: 15 - }) - - this.allOptions = options - - this.input = new InputRenderable('filter', { - placeholder: 'Filter...', - width: '100%', - marginBottom: 1 - }) - - this.select = new Select('list', { - options: options, - width: '100%', - flexGrow: 1 - }) - - this.appendChild(this.input) - this.appendChild(this.select) - - this.input.on('input', (value) => { - this.filterOptions(value) - }) - } - - private filterOptions(filter: string) { - const filtered = this.allOptions.filter(opt => - opt.toLowerCase().includes(filter.toLowerCase()) - ) - this.select.setOptions(filtered) - } -} -``` - -### Command Palette - -```typescript -const commands = new Select('commands', { - options: [ - '> Open File', - '> Save File', - '> Find in Files', - '> Replace in Files', - '> Toggle Terminal', - '> Settings', - '> Keyboard Shortcuts' - ], - width: 50, - height: 10, - selectedFg: '#000000', - selectedBg: '#ffff00' -}) - -commands.on('select', ({ value }) => { - const command = value.substring(2) // Remove "> " - executeCommand(command) -}) -``` - -### Paginated Select - -```typescript -class PaginatedSelect extends Select { - private pageSize = 10 - private currentPage = 0 - private allItems: string[] = [] - - setItems(items: string[]) { - this.allItems = items - this.showPage(0) - } - - showPage(page: number) { - const start = page * this.pageSize - const end = start + this.pageSize - const pageItems = this.allItems.slice(start, end) - - if (pageItems.length > 0) { - this.currentPage = page - this.setOptions([ - `Page ${page + 1} of ${Math.ceil(this.allItems.length / this.pageSize)}`, - '─────────────', - ...pageItems - ]) - } - } - - nextPage() { - const maxPage = Math.ceil(this.allItems.length / this.pageSize) - 1 - if (this.currentPage < maxPage) { - this.showPage(this.currentPage + 1) - } - } - - previousPage() { - if (this.currentPage > 0) { - this.showPage(this.currentPage - 1) - } - } -} -``` - -## Styling - -The Select component supports various styling options: - -```typescript -const customSelect = new Select('custom', { - options: ['Option 1', 'Option 2', 'Option 3'], - width: 30, - height: 10, - - // Normal state - fg: '#aaaaaa', - bg: '#111111', - - // Selected state - selectedFg: '#ffffff', - selectedBg: '#003366', - - // Focused + selected state - focusedSelectedFg: '#ffff00', - focusedSelectedBg: '#0066cc' -}) -``` - -## Performance - -For large lists: -- The component automatically handles scrolling and viewport rendering -- Only visible items are rendered for performance -- Consider implementing virtual scrolling for lists with thousands of items -- Use pagination or filtering for better UX with large datasets \ No newline at end of file diff --git a/packages/core/docs/api/components/tab-select.md b/packages/core/docs/api/components/tab-select.md deleted file mode 100644 index 76c309e80..000000000 --- a/packages/core/docs/api/components/tab-select.md +++ /dev/null @@ -1,454 +0,0 @@ -# TabSelectRenderable - -A horizontal tab-style selection component with optional descriptions, similar to a menu bar or tab navigation. - -## Class: `TabSelectRenderable` - -```typescript -import { TabSelectRenderable } from '@opentui/core' - -const tabSelect = new TabSelectRenderable('tabs', { - options: [ - { name: 'General', description: 'General settings' }, - { name: 'Advanced', description: 'Advanced configuration' }, - { name: 'Security', description: 'Security options' } - ], - tabWidth: 20, - showDescription: true -}) -``` - -## Constructor - -### `new TabSelectRenderable(id: string, options: TabSelectRenderableOptions)` - -## Options - -### `TabSelectRenderableOptions` - -Extends [`RenderableOptions`](../renderable.md#renderableoptions) (excluding `height` which is auto-calculated) with: - -| Property | Type | Default | Description | -|----------|------|---------|-------------| -| `options` | `TabSelectOption[]` | `[]` | Tab options array | -| `tabWidth` | `number` | `20` | Width of each tab | -| `backgroundColor` | `ColorInput` | `'transparent'` | Background color | -| `textColor` | `ColorInput` | `'#FFFFFF'` | Text color | -| `focusedBackgroundColor` | `ColorInput` | `'#1a1a1a'` | Focused background | -| `focusedTextColor` | `ColorInput` | `'#FFFFFF'` | Focused text color | -| `selectedBackgroundColor` | `ColorInput` | `'#334455'` | Selected tab background | -| `selectedTextColor` | `ColorInput` | `'#FFFF00'` | Selected tab text color | -| `selectedDescriptionColor` | `ColorInput` | `'#CCCCCC'` | Selected description color | -| `showScrollArrows` | `boolean` | `true` | Show scroll indicators | -| `showDescription` | `boolean` | `true` | Show description line | -| `showUnderline` | `boolean` | `true` | Show underline separator | -| `wrapSelection` | `boolean` | `false` | Wrap at boundaries | - -### `TabSelectOption` - -```typescript -interface TabSelectOption { - name: string // Tab title - description: string // Tab description - value?: any // Optional associated value -} -``` - -## Properties - -### Selection Properties - -| Property | Type | Description | -|----------|------|-------------| -| `options` | `TabSelectOption[]` | Tab options | -| `selectedIndex` | `number` | Currently selected index | -| `selectedOption` | `TabSelectOption \| undefined` | Currently selected option | -| `maxVisibleTabs` | `number` | Maximum visible tabs based on width | - -## Methods - -All methods from [`Renderable`](../renderable.md) plus: - -### `setOptions(options: TabSelectOption[]): void` -Update the tab options. - -```typescript -tabSelect.setOptions([ - { name: 'File', description: 'File operations' }, - { name: 'Edit', description: 'Edit operations' } -]) -``` - -### `setSelectedIndex(index: number): void` -Select a tab by index. - -```typescript -tabSelect.setSelectedIndex(1) -``` - -### `getSelectedOption(): TabSelectOption | undefined` -Get the currently selected option. - -```typescript -const selected = tabSelect.getSelectedOption() -console.log(selected?.name, selected?.description) -``` - -### `handleKeyPress(key: ParsedKey): boolean` -Handle keyboard input (called internally). - -## Events - -TabSelectRenderable emits the following events: - -| Event | Data | Description | -|-------|------|-------------| -| `selectionChanged` | `TabSelectOption` | Selection changed | -| `itemSelected` | `TabSelectOption` | Item selected (Enter key) | - -## Keyboard Shortcuts - -| Key | Action | -|-----|--------| -| `Left Arrow` | Select previous tab | -| `Right Arrow` | Select next tab | -| `Home` | Select first tab | -| `End` | Select last tab | -| `Enter` | Confirm selection | - -## Display Behavior - -The component height is automatically calculated based on options: -- Base height: 1 line for tab names -- +1 line if `showUnderline` is true -- +1 line if `showDescription` is true - -The component displays tabs horizontally with: -- Fixed width tabs (controlled by `tabWidth`) -- Automatic scrolling when tabs exceed visible width -- Optional scroll arrows to indicate more tabs -- Selected tab highlighting - -## Examples - -### Basic Tab Selection - -```typescript -const tabSelect = new TabSelectRenderable('tabs', { - options: [ - { name: 'Home', description: 'Go to home screen' }, - { name: 'Settings', description: 'Configure application' }, - { name: 'About', description: 'About this app' } - ], - width: 60, - tabWidth: 20 -}) - -tabSelect.on('selectionChanged', (option) => { - console.log(`Selected: ${option.name} - ${option.description}`) -}) - -tabSelect.on('itemSelected', (option) => { - console.log(`Confirmed selection: ${option.name}`) - // Navigate to the selected section -}) -``` - -### Styled Tab Selection - -```typescript -const styledTabs = new TabSelectRenderable('styled', { - options: [ - { name: 'Tab 1', description: 'First tab' }, - { name: 'Tab 2', description: 'Second tab' }, - { name: 'Tab 3', description: 'Third tab' } - ], - width: 70, - tabWidth: 23, - backgroundColor: '#1a1a1a', - textColor: '#999999', - selectedBackgroundColor: '#0066cc', - selectedTextColor: '#ffffff', - selectedDescriptionColor: '#aaaaff', - showDescription: true, - showUnderline: true -}) -``` - -### Menu Bar Example - -```typescript -const menuBar = new TabSelectRenderable('menu', { - options: [ - { name: 'File', description: 'File operations', value: 'file' }, - { name: 'Edit', description: 'Edit operations', value: 'edit' }, - { name: 'View', description: 'View options', value: 'view' }, - { name: 'Help', description: 'Get help', value: 'help' } - ], - width: '100%', - tabWidth: 15, - showDescription: false, // Hide descriptions for menu bar - showUnderline: true, - backgroundColor: '#2a2a2a', - selectedBackgroundColor: '#0066cc' -}) - -menuBar.on('itemSelected', (option) => { - switch (option.value) { - case 'file': - openFileMenu() - break - case 'edit': - openEditMenu() - break - case 'view': - toggleViewOptions() - break - case 'help': - showHelp() - break - } -}) -``` - -### Navigation Tabs - -```typescript -const navigationTabs = new TabSelectRenderable('nav', { - options: [ - { name: 'General', description: 'General settings' }, - { name: 'Appearance', description: 'Theme and colors' }, - { name: 'Keyboard', description: 'Shortcuts' }, - { name: 'Advanced', description: 'Advanced options' } - ], - width: 80, - tabWidth: 20, - selectedBackgroundColor: '#003366', - selectedTextColor: '#ffff00' -}) - -// Use with a content area that changes based on selection -const contentArea = new GroupRenderable('content', { - flexDirection: 'column', - padding: 2 -}) - -navigationTabs.on('selectionChanged', (option) => { - // Clear and update content area - contentArea.removeAllChildren() - - const title = new TextRenderable('title', { - content: option.name, - fg: '#00ff00', - marginBottom: 1 - }) - - const description = new TextRenderable('desc', { - content: option.description, - fg: '#cccccc' - }) - - contentArea.appendChild(title) - contentArea.appendChild(description) -}) -``` - -### Tool Selection - -```typescript -const toolSelect = new TabSelectRenderable('tools', { - options: [ - { name: '🔨 Build', description: 'Build project', value: 'build' }, - { name: '🐛 Debug', description: 'Start debugger', value: 'debug' }, - { name: '✅ Test', description: 'Run tests', value: 'test' }, - { name: '📦 Package', description: 'Create package', value: 'package' } - ], - width: 80, - tabWidth: 20, - showDescription: true, - focusedBackgroundColor: '#1a1a1a', - selectedBackgroundColor: '#00aa00', - selectedTextColor: '#000000' -}) - -toolSelect.on('itemSelected', async (option) => { - const statusText = new TextRenderable('status', { - content: `Running ${option.name}...`, - fg: '#ffff00' - }) - - switch (option.value) { - case 'build': - await runBuild() - break - case 'debug': - await startDebugger() - break - case 'test': - await runTests() - break - case 'package': - await createPackage() - break - } -}) { - private openFiles: Map = new Map() - - openFile(filename: string, content: string) { - // Check if already open - const existingIndex = this.tabs.indexOf(filename) - if (existingIndex >= 0) { - this.selectTab(existingIndex) - return - } - - // Create editor for file - const editor = new InputRenderable(`editor-${filename}`, { - value: content, - multiline: true, - width: '100%', - height: '100%' - }) - - editor.on('change', (value) => { - this.openFiles.set(filename, value) - this.markAsModified(filename) - }) - - this.addTab(filename, editor) - this.openFiles.set(filename, content) - } - - markAsModified(filename: string) { - const index = this.tabs.indexOf(filename) - if (index >= 0 && !this.tabs[index].endsWith('*')) { - this.tabs[index] = filename + ' *' - } - } - - saveFile(index: number) { - const filename = this.tabs[index].replace(' *', '') - const content = this.openFiles.get(filename) - // Save logic here - this.tabs[index] = filename // Remove asterisk - } -} -``` - -### Compact Navigation - -```typescript -const compactNav = new TabSelectRenderable('compact', { - tabs: [], - width: '100%', - height: '100%', - tabWidth: 20, // Fixed width tabs - tabBarHeight: 4 -}) - -function createWebView(url: string): Renderable { - const view = new GroupRenderable('view', { - flexDirection: 'column', - padding: 1 - }) - - const urlBar = new TextRenderable('url', { - content: `🌐 ${url}`, - fg: '#666666', - marginBottom: 1 - }) - - const content = new TextRenderable('content', { - content: `Loading ${url}...`, - fg: '#ffffff' - }) - - view.appendChild(urlBar) - view.appendChild(content) - - return view -} - -// Add new tab function -function newTab(url: string = 'about:blank') { - const title = url.length > 15 ? url.substring(0, 12) + '...' : url - const content = createWebView(url) - browserTabs.addTab(title, content) -} - -// Initial tab -newTab('https://example.com') -``` - -### Wizard Interface - -```typescript -const wizard = new TabSelect('wizard', { - tabs: ['Step 1: Setup', 'Step 2: Configure', 'Step 3: Confirm'], - width: 60, - height: 25, - selectedIndex: 0 -}) - -// Disable manual tab switching -wizard.handleKeyPress = (key) => { - // Only allow programmatic navigation - return false -} - -// Navigation buttons -const nextButton = new BoxRenderable('next', { - content: 'Next →', - width: 10, - height: 3 -}) - -const prevButton = new BoxRenderable('prev', { - content: '← Previous', - width: 10, - height: 3 -}) - -nextButton.on('click', () => { - if (wizard.selectedIndex < wizard.tabs.length - 1) { - wizard.selectTab(wizard.selectedIndex + 1) - } -}) - -prevButton.on('click', () => { - if (wizard.selectedIndex > 0) { - wizard.selectTab(wizard.selectedIndex - 1) - } -}) -``` - -## Styling - -### Tab Bar Customization - -```typescript -const customTabs = new TabSelect('custom', { - tabs: ['Tab 1', 'Tab 2'], - tabBarHeight: 4, - tabWidth: 15, - tabBarBg: '#0a0a0a', - borderStyle: 'double' -}) -``` - -### Content Area Styling - -```typescript -// Access content area directly -tabs.contentArea.backgroundColor = '#1a1a1a' -tabs.contentArea.padding = 2 -``` - -## Best Practices - -1. **Limit tab count**: Too many tabs can be hard to navigate -2. **Use clear titles**: Keep tab titles short and descriptive -3. **Lazy loading**: Load tab content only when selected for performance -4. **Keyboard shortcuts**: Implement number keys for quick tab access -5. **Visual feedback**: Use distinct colors for active/inactive tabs -6. **Content caching**: Cache expensive content instead of recreating \ No newline at end of file diff --git a/packages/core/docs/api/components/text.md b/packages/core/docs/api/components/text.md deleted file mode 100644 index 3022f91a0..000000000 --- a/packages/core/docs/api/components/text.md +++ /dev/null @@ -1,295 +0,0 @@ -# TextRenderable - -Component for displaying styled text with support for colors, attributes, and text selection. - -## Class: `TextRenderable` - -```typescript -import { TextRenderable } from '@opentui/core' - -const text = new TextRenderable('my-text', { - content: 'Hello, World!', - fg: '#00ff00', - bg: '#1a1a1a' -}) -``` - -## Constructor - -### `new TextRenderable(id: string, options: TextOptions)` - -## Options - -### `TextOptions` - -Extends [`RenderableOptions`](../renderable.md#renderableoptions) with: - -| Property | Type | Default | Description | -|----------|------|---------|-------------| -| `content` | `StyledText \| string` | `''` | Text content to display | -| `fg` | `string \| RGBA` | `'#ffffff'` | Default foreground color | -| `bg` | `string \| RGBA` | `'transparent'` | Default background color | -| `selectionBg` | `string \| RGBA` | - | Selection background color | -| `selectionFg` | `string \| RGBA` | - | Selection foreground color | -| `selectable` | `boolean` | `true` | Enable text selection | -| `attributes` | `number` | `0` | Text attributes (bold, italic, etc.) | - -## Properties - -### Content Properties - -| Property | Type | Description | -|----------|------|-------------| -| `content` | `StyledText \| string` | Get/set text content | -| `fg` | `RGBA` | Get/set foreground color | -| `bg` | `RGBA` | Get/set background color | -| `selectable` | `boolean` | Enable/disable selection | -| `plainText` | `string` | Plain text without styling (read-only) | - -## StyledText Format - -StyledText allows rich text formatting with colors and attributes: - -```typescript -import { stringToStyledText, StyledText } from '@opentui/core' - -// Simple string -const simple = stringToStyledText('Plain text') - -// With inline styles -const styled: StyledText = { - type: 'styled', - children: [ - { text: 'Normal ' }, - { text: 'Bold', bold: true }, - { text: ' and ', italic: true }, - { text: 'Colored', fg: '#00ff00', bg: '#0000ff' } - ] -} -``` - -### Text Attributes - -Available text attributes (can be combined): - -```typescript -interface TextStyle { - text: string - fg?: string | RGBA // Foreground color - bg?: string | RGBA // Background color - bold?: boolean // Bold text - italic?: boolean // Italic text - underline?: boolean // Underlined text - strikethrough?: boolean // Strikethrough text - dim?: boolean // Dimmed text - inverse?: boolean // Inverted colors - blink?: boolean // Blinking text (terminal support varies) -} -``` - -## Methods - -All methods from [`Renderable`](../renderable.md) plus: - -### `getSelectedText(): string` -Get currently selected text. - -```typescript -const selected = text.getSelectedText() -``` - -### `hasSelection(): boolean` -Check if any text is currently selected. - -```typescript -if (text.hasSelection()) { - const selected = text.getSelectedText() -} -``` - -### `shouldStartSelection(x: number, y: number): boolean` -Check if selection should start at given coordinates (used internally). - -### `onSelectionChanged(selection: SelectionState | null): boolean` -Handle selection state changes (used internally by the selection system). - -## Text Selection - -TextRenderable supports text selection when `selectable` is true: - -```typescript -const text = new TextRenderable('text', { - content: 'Selectable text content', - selectable: true, - selectionBg: '#0066cc', - selectionFg: '#ffffff' -}) - -// Selection is handled automatically with mouse -// The renderer manages selection state globally -renderer.on('mouse', (event) => { - // Selection is handled internally by the renderer -}) - -// Check if text has selection -if (text.hasSelection()) { - const selected = text.getSelectedText() - console.log('Selected text:', selected) -} -``` - -## Examples - -### Basic Text - -```typescript -const label = new TextRenderable('label', { - content: 'Username:', - fg: '#ffffff' -}) -``` - -### Styled Text - -```typescript -const styled = new TextRenderable('styled', { - content: { - type: 'styled', - children: [ - { text: 'Status: ' }, - { text: 'Online', fg: '#00ff00', bold: true } - ] - } -}) -``` - -### Multi-line Text - -```typescript -const paragraph = new TextRenderable('paragraph', { - content: `This is a paragraph of text -that spans multiple lines -and wraps automatically`, - width: 40, - fg: '#cccccc' -}) -``` - -### Dynamic Updates - -```typescript -const counter = new TextRenderable('counter', { - content: 'Count: 0', - fg: '#ffff00' -}) - -let count = 0 -setInterval(() => { - count++ - counter.content = `Count: ${count}` -}, 1000) -``` - -### Error Messages - -```typescript -const error = new TextRenderable('error', { - content: { - type: 'styled', - children: [ - { text: '✗ ', fg: '#ff0000', bold: true }, - { text: 'Error: ', fg: '#ff6666', bold: true }, - { text: 'File not found', fg: '#ffcccc' } - ] - }, - bg: '#330000' -}) -``` - -### Code Display - -```typescript -const code = new TextRenderable('code', { - content: { - type: 'styled', - children: [ - { text: 'function ', fg: '#ff79c6' }, - { text: 'hello', fg: '#50fa7b' }, - { text: '() {\n' }, - { text: ' return ', fg: '#ff79c6' }, - { text: '"Hello, World!"', fg: '#f1fa8c' }, - { text: '\n}' } - ] - }, - bg: '#282a36', - padding: 1 -}) -``` - -### Status Indicators - -```typescript -function createStatusText(status: 'idle' | 'loading' | 'success' | 'error') { - const indicators = { - idle: { symbol: '○', color: '#666666' }, - loading: { symbol: '◔', color: '#ffff00' }, - success: { symbol: '●', color: '#00ff00' }, - error: { symbol: '✗', color: '#ff0000' } - } - - const { symbol, color } = indicators[status] - - return new TextRenderable('status', { - content: { - type: 'styled', - children: [ - { text: symbol + ' ', fg: color }, - { text: status.toUpperCase(), fg: color, bold: true } - ] - } - }) -} -``` - -### Selectable List - -```typescript -const items = ['Option 1', 'Option 2', 'Option 3'] -const list = new GroupRenderable('list', { - flexDirection: 'column' -}) - -items.forEach((item, index) => { - const text = new TextRenderable(`item-${index}`, { - content: item, - selectable: true, - selectionBg: '#0066cc', - padding: 1 - }) - - text.on('click', () => { - console.log(`Selected: ${item}`) - }) - - list.appendChild(text) -}) -``` - -## Text Wrapping - -Text automatically wraps based on the component width: - -```typescript -const wrapped = new TextRenderable('wrapped', { - content: 'This is a long line of text that will automatically wrap when it exceeds the width of the component', - width: 30, - fg: '#ffffff' -}) -``` - -## Performance Considerations - -- Text rendering is optimized with internal text buffers -- Styled text segments are cached for efficient rendering -- Large text content is handled efficiently with viewport clipping -- Updates only trigger re-renders when content actually changes \ No newline at end of file diff --git a/packages/core/docs/api/core/rendering.md b/packages/core/docs/api/core/rendering.md deleted file mode 100644 index 8802dded6..000000000 --- a/packages/core/docs/api/core/rendering.md +++ /dev/null @@ -1,585 +0,0 @@ -# Rendering API - -The OpenTUI rendering system provides a powerful and flexible way to create terminal user interfaces. It handles the low-level details of terminal rendering, layout management, and input handling. - -## CliRenderer - -The `CliRenderer` is the core rendering engine that manages the terminal output, input handling, and rendering loop. - -### Creating a Renderer - -```typescript -import { createCliRenderer } from '@opentui/core'; - -// Create a renderer with default options -const renderer = await createCliRenderer(); - -// Create a renderer with custom options -const renderer = await createCliRenderer({ - exitOnCtrlC: true, - targetFps: 60, - useMouse: true, - useAlternateScreen: true, - useConsole: false, - gatherStats: false -}); -``` - -### Renderer Configuration Options - -```typescript -interface CliRendererConfig { - stdin?: NodeJS.ReadStream; // Input stream (default: process.stdin) - stdout?: NodeJS.WriteStream; // Output stream (default: process.stdout) - exitOnCtrlC?: boolean; // Exit on Ctrl+C (default: true) - debounceDelay?: number; // Debounce delay for rendering in ms (default: 0) - targetFps?: number; // Target frames per second (default: 60) - memorySnapshotInterval?: number; // Memory snapshot interval in ms - useThread?: boolean; // Use worker thread (default: false) - gatherStats?: boolean; // Collect performance stats (default: false) - maxStatSamples?: number; // Max stat samples (default: 100) - consoleOptions?: ConsoleOptions; // Console capture options - postProcessFns?: Function[]; // Post-processing functions - enableMouseMovement?: boolean; // Track mouse movement (default: false) - useMouse?: boolean; // Enable mouse support (default: false) - useAlternateScreen?: boolean; // Use alternate screen buffer (default: true) - useConsole?: boolean; // Capture console output (default: false) - experimental_splitHeight?: number; // Split screen height (experimental) -} -``` - -## Renderer Properties - -### `root: RootRenderable` -The root component container. All UI components should be added as children of root. - -```typescript -const { root } = renderer; -root.add(myComponent); -``` - -### `width: number` -Current terminal width in columns. - -### `height: number` -Current terminal height in rows. - -### `console: TerminalConsole | null` -Console instance for captured output (when `useConsole` is true). - -### `selection: Selection` -Global text selection manager. - -## Renderer Methods - -### Lifecycle Methods - -#### `start(): void` -Start the rendering loop. - -```typescript -renderer.start(); -``` - -#### `pause(): void` -Pause the rendering loop. - -```typescript -renderer.pause(); -``` - -#### `stop(): void` -Stop the rendering loop. - -```typescript -renderer.stop(); -``` - -#### `destroy(): void` -Clean up resources and restore terminal state. - -```typescript -renderer.destroy(); -``` - -### Display Control - -#### `setBackgroundColor(color: ColorInput): void` -Set the terminal background color. - -```typescript -renderer.setBackgroundColor('#1a1a1a'); -renderer.setBackgroundColor(RGBA.fromHex('#000000')); -``` - -#### `setCursorPosition(x: number, y: number, visible?: boolean): void` -Set the cursor position and visibility. - -```typescript -renderer.setCursorPosition(10, 5, true); -``` - -#### `setCursorStyle(style: CursorStyle, blinking?: boolean, color?: RGBA): void` -Set the cursor appearance. - -```typescript -renderer.setCursorStyle('block', true); -renderer.setCursorStyle('underline', false, RGBA.fromHex('#00ff00')); -// Styles: 'block', 'underline', 'bar', 'block-blinking', 'underline-blinking', 'bar-blinking' -``` - -#### `setCursorColor(color: RGBA): void` -Set the cursor color. - -```typescript -renderer.setCursorColor(RGBA.fromHex('#ffffff')); -``` - -### Debug and Performance - -#### `toggleDebugOverlay(): void` -Toggle the debug overlay display. - -```typescript -renderer.toggleDebugOverlay(); -``` - -#### `configureDebugOverlay(config: DebugOverlayConfig): void` -Configure debug overlay settings. - -```typescript -renderer.configureDebugOverlay({ - enabled: true, - corner: DebugOverlayCorner.bottomRight -}); -``` - -#### `getStats(): RendererStats` -Get performance statistics. - -```typescript -const stats = renderer.getStats(); -console.log(`FPS: ${stats.fps}`); -console.log(`Frame time: ${stats.averageFrameTime}ms`); -console.log(`Max frame time: ${stats.maxFrameTime}ms`); -``` - -#### `resetStats(): void` -Reset performance statistics. - -```typescript -renderer.resetStats(); -``` - -#### `setGatherStats(enabled: boolean): void` -Enable or disable statistics gathering. - -```typescript -renderer.setGatherStats(true); -``` - -### Post-Processing - -#### `addPostProcessFn(fn: PostProcessFunction): void` -Add a post-processing function applied to each frame. - -```typescript -renderer.addPostProcessFn((buffer: OptimizedBuffer, deltaTime: number) => { - // Apply effects to the buffer - for (let y = 0; y < buffer.height; y += 2) { - for (let x = 0; x < buffer.width; x++) { - const cell = buffer.getCell(x, y); - // Modify cell attributes for scanline effect - buffer.setCell(x, y, cell.char, cell.fg, cell.bg, cell.attributes | 0x08); - } - } -}); -``` - -#### `removePostProcessFn(fn: PostProcessFunction): void` -Remove a post-processing function. - -```typescript -renderer.removePostProcessFn(myPostProcessFn); -``` - -#### `clearPostProcessFns(): void` -Clear all post-processing functions. - -```typescript -renderer.clearPostProcessFns(); -``` - -### Frame Callbacks - -#### `setFrameCallback(callback: FrameCallback): void` -Set a callback executed each frame. - -```typescript -renderer.setFrameCallback(async (deltaTime: number) => { - // Update animations or perform frame-based logic - await updateAnimations(deltaTime); -}); -``` - -### Animation Frame API - -OpenTUI provides browser-compatible animation frame methods: - -```typescript -// Request an animation frame -const frameId = renderer.requestAnimationFrame((deltaTime: number) => { - // Animation logic -}); - -// Cancel an animation frame -renderer.cancelAnimationFrame(frameId); -``` - -### Selection Management - -#### `hasSelection(): boolean` -Check if there's an active text selection. - -```typescript -if (renderer.hasSelection()) { - const text = renderer.getSelection(); -} -``` - -#### `getSelection(): string` -Get the currently selected text. - -```typescript -const selectedText = renderer.getSelection(); -``` - -#### `clearSelection(): void` -Clear the current selection. - -```typescript -renderer.clearSelection(); -``` - -#### `getSelectionContainer(): Renderable | null` -Get the container of the current selection. - -```typescript -const container = renderer.getSelectionContainer(); -``` - -## Events - -The renderer extends `EventEmitter` and emits the following events: - -### `'key'` -Fired when a key is pressed. - -```typescript -renderer.on('key', (key: ParsedKey) => { - console.log('Key pressed:', key.name); - if (key.ctrl && key.name === 'c') { - // Handle Ctrl+C - } -}); -``` - -**ParsedKey Structure:** -```typescript -interface ParsedKey { - name: string; // Key name (e.g., 'a', 'enter', 'up') - ctrl: boolean; // Ctrl modifier - meta: boolean; // Meta/Alt modifier - shift: boolean; // Shift modifier - sequence: string; // Raw key sequence -} -``` - -### `'mouse'` -Fired for mouse events. - -```typescript -renderer.on('mouse', (event: MouseEvent) => { - console.log('Mouse event:', event.type, 'at', event.x, event.y); -}); -``` - -### `'resize'` -Fired when the terminal is resized. - -```typescript -renderer.on('resize', (width: number, height: number) => { - console.log('Terminal resized to:', width, 'x', height); -}); -``` - -## RenderContext - -The `RenderContext` provides information and utilities for rendering components. - -```typescript -interface RenderContext { - // Add component to hit testing grid - addToHitGrid(x: number, y: number, width: number, height: number, id: number): void; - - // Get current viewport dimensions - width(): number; - height(): number; - - // Request a re-render - needsUpdate(): void; -} -``` - -## Mouse Events - -OpenTUI provides comprehensive mouse event handling. - -### MouseEvent Class - -```typescript -class MouseEvent { - readonly type: MouseEventType; // Event type - readonly button: number; // Mouse button (MouseButton enum) - readonly x: number; // X coordinate - readonly y: number; // Y coordinate - readonly source?: Renderable; // Source component (for drag) - readonly modifiers: { - shift: boolean; - alt: boolean; - ctrl: boolean; - }; - readonly scroll?: ScrollInfo; // Scroll information - readonly target: Renderable | null; // Target component - - preventDefault(): void; // Prevent default handling -} -``` - -### MouseEventType - -```typescript -type MouseEventType = - | 'click' // Mouse click - | 'dblclick' // Double click - | 'down' // Mouse button down - | 'up' // Mouse button up - | 'move' // Mouse movement - | 'over' // Mouse over component - | 'out' // Mouse out of component - | 'drag' // Dragging - | 'dragstart' // Drag started - | 'dragend' // Drag ended - | 'scroll'; // Mouse wheel scroll -``` - -### MouseButton Enum - -```typescript -enum MouseButton { - LEFT = 0, - MIDDLE = 1, - RIGHT = 2, - WHEEL_UP = 4, - WHEEL_DOWN = 5, -} -``` - -### Example: Handling Mouse Events - -```typescript -class InteractiveBox extends BoxRenderable { - protected handleMouse(event: MouseEvent): boolean { - switch (event.type) { - case 'over': - this.borderColor = '#00ff00'; - break; - case 'out': - this.borderColor = '#ffffff'; - break; - case 'click': - if (event.button === MouseButton.LEFT) { - console.log('Left clicked at', event.x, event.y); - event.preventDefault(); - return true; - } - break; - case 'scroll': - if (event.scroll) { - console.log('Scrolled:', event.scroll.direction); - } - break; - } - return false; - } -} -``` - -## Complete Example Application - -```typescript -import { - createCliRenderer, - BoxRenderable, - TextRenderable, - Input, - RGBA -} from '@opentui/core'; - -async function main() { - // Create renderer with options - const renderer = await createCliRenderer({ - useMouse: true, - useAlternateScreen: true, - targetFps: 60, - exitOnCtrlC: true - }); - - // Get root container - const { root } = renderer; - - // Create main container - const app = new BoxRenderable('app', { - width: '100%', - height: '100%', - borderStyle: 'double', - borderColor: '#3498db', - backgroundColor: '#1a1a1a', - padding: 1 - }); - - // Add title - const title = new TextRenderable('title', { - content: 'OpenTUI Application', - fg: '#ffffff', - alignItems: 'center', - marginBottom: 2 - }); - - // Add input field - const input = new Input('input', { - width: '80%', - placeholder: 'Type a command...', - marginLeft: '10%' - }); - - // Build component tree - app.add(title); - app.add(input); - root.add(app); - - // Handle input submission - input.on('submit', (value: string) => { - console.log('Command:', value); - input.value = ''; - }); - - // Handle global keys - renderer.on('key', (key) => { - if (key.name === 'escape') { - renderer.destroy(); - process.exit(0); - } - }); - - // Handle resize - renderer.on('resize', (width, height) => { - console.log(`Resized to ${width}x${height}`); - }); - - // Add post-processing effect - renderer.addPostProcessFn((buffer, deltaTime) => { - // Add a subtle vignette effect - const centerX = Math.floor(buffer.width / 2); - const centerY = Math.floor(buffer.height / 2); - const maxDist = Math.sqrt(centerX * centerX + centerY * centerY); - - for (let y = 0; y < buffer.height; y++) { - for (let x = 0; x < buffer.width; x++) { - const dist = Math.sqrt( - Math.pow(x - centerX, 2) + - Math.pow(y - centerY, 2) - ); - const factor = 1 - (dist / maxDist) * 0.3; - - const cell = buffer.getCell(x, y); - if (cell) { - const dimmedFg = cell.fg.multiply(factor); - buffer.setCell(x, y, cell.char, dimmedFg, cell.bg); - } - } - } - }); - - // Start rendering - renderer.start(); - - // Focus the input - input.focus(); - - // Cleanup on exit - process.on('SIGINT', () => { - renderer.destroy(); - process.exit(0); - }); -} - -main().catch(console.error); -``` - -## Advanced Features - -### Split Screen Mode (Experimental) - -Split the terminal between the renderer and regular console output: - -```typescript -const renderer = await createCliRenderer({ - experimental_splitHeight: 10 // Reserve 10 lines for renderer -}); - -// The renderer will only use the top 10 lines -// Console output will appear below -``` - -### Console Capture - -Capture and display console output within the TUI: - -```typescript -const renderer = await createCliRenderer({ - useConsole: true, - consoleOptions: { - maxLines: 100, - autoscroll: true - } -}); - -// Console output is now captured -console.log('This appears in the TUI console'); - -// Access the console component -const consoleComponent = renderer.console; -``` - -### Performance Optimization - -For high-performance applications: - -```typescript -const renderer = await createCliRenderer({ - targetFps: 120, // Higher frame rate - debounceDelay: 0, // No debouncing - useThread: true, // Use worker thread - gatherStats: true, // Monitor performance - maxStatSamples: 1000 // More samples for analysis -}); - -// Monitor performance -setInterval(() => { - const stats = renderer.getStats(); - if (stats.averageFrameTime > 16.67) { - console.warn('Frame rate below 60 FPS'); - } -}, 1000); -``` \ No newline at end of file diff --git a/packages/core/docs/api/extracted-api.json b/packages/core/docs/api/extracted-api.json new file mode 100644 index 000000000..363a77d46 --- /dev/null +++ b/packages/core/docs/api/extracted-api.json @@ -0,0 +1,2262 @@ +{ + "classes": [ + { + "name": "Renderable", + "methods": [ + { + "name": "hasSelection", + "returnType": "boolean", + "parameters": [] + }, + { + "name": "onSelectionChanged", + "returnType": "boolean", + "parameters": [ + { + "name": "selection", + "type": "SelectionState | null" + } + ] + }, + { + "name": "getSelectedText", + "returnType": "string", + "parameters": [] + }, + { + "name": "shouldStartSelection", + "returnType": "boolean", + "parameters": [ + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + } + ] + }, + { + "name": "focus", + "returnType": "void", + "parameters": [] + }, + { + "name": "blur", + "returnType": "void", + "parameters": [] + }, + { + "name": "handleKeyPress", + "returnType": "boolean", + "parameters": [ + { + "name": "key", + "type": "ParsedKey | string" + } + ] + }, + { + "name": "needsUpdate", + "returnType": "void", + "parameters": [] + }, + { + "name": "requestZIndexSort", + "returnType": "void", + "parameters": [] + }, + { + "name": "setPosition", + "returnType": "void", + "parameters": [ + { + "name": "position", + "type": "Position" + } + ] + }, + { + "name": "getLayoutNode", + "returnType": "TrackedNode", + "parameters": [] + }, + { + "name": "updateFromLayout", + "returnType": "void", + "parameters": [] + }, + { + "name": "add", + "returnType": "number", + "parameters": [ + { + "name": "obj", + "type": "Renderable" + }, + { + "name": "index", + "type": "number" + } + ] + }, + { + "name": "insertBefore", + "returnType": "number", + "parameters": [ + { + "name": "obj", + "type": "Renderable" + }, + { + "name": "anchor", + "type": "Renderable" + } + ] + }, + { + "name": "propagateContext", + "returnType": "void", + "parameters": [ + { + "name": "ctx", + "type": "RenderContext | null" + } + ] + }, + { + "name": "getRenderable", + "returnType": "Renderable", + "parameters": [ + { + "name": "id", + "type": "string" + } + ] + }, + { + "name": "remove", + "returnType": "void", + "parameters": [ + { + "name": "id", + "type": "string" + } + ] + }, + { + "name": "getChildren", + "returnType": "Renderable[]", + "parameters": [] + }, + { + "name": "render", + "returnType": "void", + "parameters": [ + { + "name": "buffer", + "type": "OptimizedBuffer" + }, + { + "name": "deltaTime", + "type": "number" + } + ] + }, + { + "name": "destroy", + "returnType": "void", + "parameters": [] + }, + { + "name": "destroyRecursively", + "returnType": "void", + "parameters": [] + }, + { + "name": "processMouseEvent", + "returnType": "void", + "parameters": [ + { + "name": "event", + "type": "MouseEvent" + } + ] + } + ], + "properties": [ + { + "name": "renderablesByNumber", + "type": "Map" + }, + { + "name": "id", + "type": "string" + }, + { + "name": "num", + "type": "number" + }, + { + "name": "selectable", + "type": "boolean" + }, + { + "name": "parent", + "type": "Renderable | null" + } + ], + "constructor": { + "parameters": [ + { + "name": "id", + "type": "string" + }, + { + "name": "options", + "type": "RenderableOptions" + } + ] + } + }, + { + "name": "RootRenderable", + "methods": [ + { + "name": "requestLayout", + "returnType": "void", + "parameters": [] + }, + { + "name": "calculateLayout", + "returnType": "void", + "parameters": [] + }, + { + "name": "resize", + "returnType": "void", + "parameters": [ + { + "name": "width", + "type": "number" + }, + { + "name": "height", + "type": "number" + } + ] + } + ], + "properties": [], + "constructor": { + "parameters": [ + { + "name": "width", + "type": "number" + }, + { + "name": "height", + "type": "number" + }, + { + "name": "ctx", + "type": "RenderContext" + }, + { + "name": "rootContext", + "type": "RootContext" + } + ] + } + }, + { + "name": "MouseEvent", + "methods": [ + { + "name": "preventDefault", + "returnType": "void", + "parameters": [] + } + ], + "properties": [ + { + "name": "type", + "type": "MouseEventType" + }, + { + "name": "button", + "type": "number" + }, + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + }, + { + "name": "source", + "type": "Renderable" + }, + { + "name": "modifiers", + "type": "{\n shift: boolean\n alt: boolean\n ctrl: boolean\n }" + }, + { + "name": "scroll", + "type": "ScrollInfo" + }, + { + "name": "target", + "type": "Renderable | null" + } + ], + "constructor": { + "parameters": [ + { + "name": "target", + "type": "Renderable | null" + }, + { + "name": "attributes", + "type": "RawMouseEvent & { source?: Renderable }" + } + ] + } + }, + { + "name": "CliRenderer", + "methods": [ + { + "name": "needsUpdate", + "returnType": "void", + "parameters": [] + }, + { + "name": "setMemorySnapshotInterval", + "returnType": "void", + "parameters": [ + { + "name": "interval", + "type": "number" + } + ] + }, + { + "name": "setBackgroundColor", + "returnType": "void", + "parameters": [ + { + "name": "color", + "type": "ColorInput" + } + ] + }, + { + "name": "toggleDebugOverlay", + "returnType": "void", + "parameters": [] + }, + { + "name": "configureDebugOverlay", + "returnType": "void", + "parameters": [ + { + "name": "options", + "type": "{ enabled?: boolean; corner?: DebugOverlayCorner }" + } + ] + }, + { + "name": "clearTerminal", + "returnType": "void", + "parameters": [] + }, + { + "name": "dumpHitGrid", + "returnType": "void", + "parameters": [] + }, + { + "name": "dumpBuffers", + "returnType": "void", + "parameters": [ + { + "name": "timestamp", + "type": "number" + } + ] + }, + { + "name": "dumpStdoutBuffer", + "returnType": "void", + "parameters": [ + { + "name": "timestamp", + "type": "number" + } + ] + }, + { + "name": "setCursorPosition", + "returnType": "void", + "parameters": [ + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + }, + { + "name": "visible", + "type": "boolean" + } + ] + }, + { + "name": "setCursorStyle", + "returnType": "void", + "parameters": [ + { + "name": "style", + "type": "CursorStyle" + }, + { + "name": "blinking", + "type": "boolean" + }, + { + "name": "color", + "type": "RGBA" + } + ] + }, + { + "name": "setCursorColor", + "returnType": "void", + "parameters": [ + { + "name": "color", + "type": "RGBA" + } + ] + }, + { + "name": "setCursorPosition", + "returnType": "void", + "parameters": [ + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + }, + { + "name": "visible", + "type": "boolean" + } + ] + }, + { + "name": "setCursorStyle", + "returnType": "void", + "parameters": [ + { + "name": "style", + "type": "CursorStyle" + }, + { + "name": "blinking", + "type": "boolean" + }, + { + "name": "color", + "type": "RGBA" + } + ] + }, + { + "name": "setCursorColor", + "returnType": "void", + "parameters": [ + { + "name": "color", + "type": "RGBA" + } + ] + }, + { + "name": "addPostProcessFn", + "returnType": "void", + "parameters": [ + { + "name": "processFn", + "type": "(buffer: OptimizedBuffer, deltaTime: number) => void" + } + ] + }, + { + "name": "removePostProcessFn", + "returnType": "void", + "parameters": [ + { + "name": "processFn", + "type": "(buffer: OptimizedBuffer, deltaTime: number) => void" + } + ] + }, + { + "name": "clearPostProcessFns", + "returnType": "void", + "parameters": [] + }, + { + "name": "setFrameCallback", + "returnType": "void", + "parameters": [ + { + "name": "callback", + "type": "(deltaTime: number) => Promise" + } + ] + }, + { + "name": "removeFrameCallback", + "returnType": "void", + "parameters": [ + { + "name": "callback", + "type": "(deltaTime: number) => Promise" + } + ] + }, + { + "name": "clearFrameCallbacks", + "returnType": "void", + "parameters": [] + }, + { + "name": "requestLive", + "returnType": "void", + "parameters": [] + }, + { + "name": "dropLive", + "returnType": "void", + "parameters": [] + }, + { + "name": "start", + "returnType": "void", + "parameters": [] + }, + { + "name": "pause", + "returnType": "void", + "parameters": [] + }, + { + "name": "stop", + "returnType": "void", + "parameters": [] + }, + { + "name": "destroy", + "returnType": "void", + "parameters": [] + }, + { + "name": "intermediateRender", + "returnType": "void", + "parameters": [] + }, + { + "name": "getStats", + "returnType": "{ fps: number; frameCount: number; frameTimes: number[]; averageFrameTime: number; minFrameTime: number; maxFrameTime: number; }", + "parameters": [] + }, + { + "name": "resetStats", + "returnType": "void", + "parameters": [] + }, + { + "name": "setGatherStats", + "returnType": "void", + "parameters": [ + { + "name": "enabled", + "type": "boolean" + } + ] + }, + { + "name": "getSelection", + "returnType": "Selection", + "parameters": [] + }, + { + "name": "getSelectionContainer", + "returnType": "Renderable", + "parameters": [] + }, + { + "name": "hasSelection", + "returnType": "boolean", + "parameters": [] + }, + { + "name": "clearSelection", + "returnType": "void", + "parameters": [] + } + ], + "properties": [ + { + "name": "rendererPtr", + "type": "Pointer" + }, + { + "name": "nextRenderBuffer", + "type": "OptimizedBuffer" + }, + { + "name": "currentRenderBuffer", + "type": "OptimizedBuffer" + }, + { + "name": "root", + "type": "RootRenderable" + }, + { + "name": "width", + "type": "number" + }, + { + "name": "height", + "type": "number" + }, + { + "name": "debugOverlay", + "type": "any" + } + ], + "constructor": { + "parameters": [ + { + "name": "lib", + "type": "RenderLib" + }, + { + "name": "rendererPtr", + "type": "Pointer" + }, + { + "name": "stdin", + "type": "NodeJS.ReadStream" + }, + { + "name": "stdout", + "type": "NodeJS.WriteStream" + }, + { + "name": "width", + "type": "number" + }, + { + "name": "height", + "type": "number" + }, + { + "name": "config", + "type": "CliRendererConfig" + } + ] + } + }, + { + "name": "OptimizedBuffer", + "methods": [ + { + "name": "create", + "returnType": "OptimizedBuffer", + "parameters": [ + { + "name": "width", + "type": "number" + }, + { + "name": "height", + "type": "number" + }, + { + "name": "options", + "type": "{ respectAlpha?: boolean }" + } + ] + }, + { + "name": "getWidth", + "returnType": "number", + "parameters": [] + }, + { + "name": "getHeight", + "returnType": "number", + "parameters": [] + }, + { + "name": "setRespectAlpha", + "returnType": "void", + "parameters": [ + { + "name": "respectAlpha", + "type": "boolean" + } + ] + }, + { + "name": "clear", + "returnType": "void", + "parameters": [ + { + "name": "bg", + "type": "RGBA" + }, + { + "name": "clearChar", + "type": "string" + } + ] + }, + { + "name": "clearLocal", + "returnType": "void", + "parameters": [ + { + "name": "bg", + "type": "RGBA" + }, + { + "name": "clearChar", + "type": "string" + } + ] + }, + { + "name": "setCell", + "returnType": "void", + "parameters": [ + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + }, + { + "name": "char", + "type": "string" + }, + { + "name": "fg", + "type": "RGBA" + }, + { + "name": "bg", + "type": "RGBA" + }, + { + "name": "attributes", + "type": "number" + } + ] + }, + { + "name": "get", + "returnType": "{ char: number; fg: RGBA; bg: RGBA; attributes: number; }", + "parameters": [ + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + } + ] + }, + { + "name": "setCellWithAlphaBlending", + "returnType": "void", + "parameters": [ + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + }, + { + "name": "char", + "type": "string" + }, + { + "name": "fg", + "type": "RGBA" + }, + { + "name": "bg", + "type": "RGBA" + }, + { + "name": "attributes", + "type": "number" + } + ] + }, + { + "name": "setCellWithAlphaBlendingLocal", + "returnType": "void", + "parameters": [ + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + }, + { + "name": "char", + "type": "string" + }, + { + "name": "fg", + "type": "RGBA" + }, + { + "name": "bg", + "type": "RGBA" + }, + { + "name": "attributes", + "type": "number" + } + ] + }, + { + "name": "drawText", + "returnType": "void", + "parameters": [ + { + "name": "text", + "type": "string" + }, + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + }, + { + "name": "fg", + "type": "RGBA" + }, + { + "name": "bg", + "type": "RGBA" + }, + { + "name": "attributes", + "type": "number" + }, + { + "name": "selection", + "type": "{ start: number; end: number; bgColor?: RGBA; fgColor?: RGBA } | null" + } + ] + }, + { + "name": "fillRect", + "returnType": "void", + "parameters": [ + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + }, + { + "name": "width", + "type": "number" + }, + { + "name": "height", + "type": "number" + }, + { + "name": "bg", + "type": "RGBA" + } + ] + }, + { + "name": "fillRectLocal", + "returnType": "void", + "parameters": [ + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + }, + { + "name": "width", + "type": "number" + }, + { + "name": "height", + "type": "number" + }, + { + "name": "bg", + "type": "RGBA" + } + ] + }, + { + "name": "drawFrameBuffer", + "returnType": "void", + "parameters": [ + { + "name": "destX", + "type": "number" + }, + { + "name": "destY", + "type": "number" + }, + { + "name": "frameBuffer", + "type": "OptimizedBuffer" + }, + { + "name": "sourceX", + "type": "number" + }, + { + "name": "sourceY", + "type": "number" + }, + { + "name": "sourceWidth", + "type": "number" + }, + { + "name": "sourceHeight", + "type": "number" + } + ] + }, + { + "name": "drawFrameBufferLocal", + "returnType": "void", + "parameters": [ + { + "name": "destX", + "type": "number" + }, + { + "name": "destY", + "type": "number" + }, + { + "name": "frameBuffer", + "type": "OptimizedBuffer" + }, + { + "name": "sourceX", + "type": "number" + }, + { + "name": "sourceY", + "type": "number" + }, + { + "name": "sourceWidth", + "type": "number" + }, + { + "name": "sourceHeight", + "type": "number" + } + ] + }, + { + "name": "destroy", + "returnType": "void", + "parameters": [] + }, + { + "name": "drawTextBuffer", + "returnType": "void", + "parameters": [ + { + "name": "textBuffer", + "type": "TextBuffer" + }, + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + }, + { + "name": "clipRect", + "type": "{ x: number; y: number; width: number; height: number }" + } + ] + }, + { + "name": "drawSuperSampleBuffer", + "returnType": "void", + "parameters": [ + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + }, + { + "name": "pixelDataPtr", + "type": "Pointer" + }, + { + "name": "pixelDataLength", + "type": "number" + }, + { + "name": "format", + "type": "\"bgra8unorm\" | \"rgba8unorm\"" + }, + { + "name": "alignedBytesPerRow", + "type": "number" + } + ] + }, + { + "name": "drawSuperSampleBufferFFI", + "returnType": "void", + "parameters": [ + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + }, + { + "name": "pixelDataPtr", + "type": "Pointer" + }, + { + "name": "pixelDataLength", + "type": "number" + }, + { + "name": "format", + "type": "\"bgra8unorm\" | \"rgba8unorm\"" + }, + { + "name": "alignedBytesPerRow", + "type": "number" + } + ] + }, + { + "name": "drawPackedBuffer", + "returnType": "void", + "parameters": [ + { + "name": "dataPtr", + "type": "Pointer" + }, + { + "name": "dataLen", + "type": "number" + }, + { + "name": "posX", + "type": "number" + }, + { + "name": "posY", + "type": "number" + }, + { + "name": "terminalWidthCells", + "type": "number" + }, + { + "name": "terminalHeightCells", + "type": "number" + } + ] + }, + { + "name": "setCellWithAlphaBlendingFFI", + "returnType": "void", + "parameters": [ + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + }, + { + "name": "char", + "type": "string" + }, + { + "name": "fg", + "type": "RGBA" + }, + { + "name": "bg", + "type": "RGBA" + }, + { + "name": "attributes", + "type": "number" + } + ] + }, + { + "name": "fillRectFFI", + "returnType": "void", + "parameters": [ + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + }, + { + "name": "width", + "type": "number" + }, + { + "name": "height", + "type": "number" + }, + { + "name": "bg", + "type": "RGBA" + } + ] + }, + { + "name": "resize", + "returnType": "void", + "parameters": [ + { + "name": "width", + "type": "number" + }, + { + "name": "height", + "type": "number" + } + ] + }, + { + "name": "clearFFI", + "returnType": "void", + "parameters": [ + { + "name": "bg", + "type": "RGBA" + } + ] + }, + { + "name": "drawTextFFI", + "returnType": "void", + "parameters": [ + { + "name": "text", + "type": "string" + }, + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + }, + { + "name": "fg", + "type": "RGBA" + }, + { + "name": "bg", + "type": "RGBA" + }, + { + "name": "attributes", + "type": "number" + } + ] + }, + { + "name": "drawFrameBufferFFI", + "returnType": "void", + "parameters": [ + { + "name": "destX", + "type": "number" + }, + { + "name": "destY", + "type": "number" + }, + { + "name": "frameBuffer", + "type": "OptimizedBuffer" + }, + { + "name": "sourceX", + "type": "number" + }, + { + "name": "sourceY", + "type": "number" + }, + { + "name": "sourceWidth", + "type": "number" + }, + { + "name": "sourceHeight", + "type": "number" + } + ] + }, + { + "name": "drawBox", + "returnType": "void", + "parameters": [ + { + "name": "options", + "type": "{\n x: number\n y: number\n width: number\n height: number\n borderStyle?: BorderStyle\n customBorderChars?: Uint32Array\n border: boolean | BorderSides[]\n borderColor: RGBA\n backgroundColor: RGBA\n shouldFill?: boolean\n title?: string\n titleAlignment?: \"left\" | \"center\" | \"right\"\n }" + } + ] + } + ], + "properties": [ + { + "name": "id", + "type": "string" + }, + { + "name": "lib", + "type": "RenderLib" + }, + { + "name": "respectAlpha", + "type": "boolean" + } + ], + "constructor": { + "parameters": [ + { + "name": "lib", + "type": "RenderLib" + }, + { + "name": "ptr", + "type": "Pointer" + }, + { + "name": "buffer", + "type": "{\n char: Uint32Array\n fg: Float32Array\n bg: Float32Array\n attributes: Uint8Array\n }" + }, + { + "name": "width", + "type": "number" + }, + { + "name": "height", + "type": "number" + }, + { + "name": "options", + "type": "{ respectAlpha?: boolean }" + } + ] + } + }, + { + "name": "BoxRenderable", + "methods": [], + "properties": [ + { + "name": "shouldFill", + "type": "boolean" + } + ], + "constructor": { + "parameters": [ + { + "name": "id", + "type": "string" + }, + { + "name": "options", + "type": "BoxOptions" + } + ] + } + }, + { + "name": "TextRenderable", + "methods": [ + { + "name": "shouldStartSelection", + "returnType": "boolean", + "parameters": [ + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + } + ] + }, + { + "name": "onSelectionChanged", + "returnType": "boolean", + "parameters": [ + { + "name": "selection", + "type": "SelectionState | null" + } + ] + }, + { + "name": "getSelectedText", + "returnType": "string", + "parameters": [] + }, + { + "name": "hasSelection", + "returnType": "boolean", + "parameters": [] + }, + { + "name": "destroy", + "returnType": "void", + "parameters": [] + } + ], + "properties": [ + { + "name": "selectable", + "type": "boolean" + } + ], + "constructor": { + "parameters": [ + { + "name": "id", + "type": "string" + }, + { + "name": "options", + "type": "TextOptions" + } + ] + } + }, + { + "name": "ASCIIFontRenderable", + "methods": [ + { + "name": "shouldStartSelection", + "returnType": "boolean", + "parameters": [ + { + "name": "x", + "type": "number" + }, + { + "name": "y", + "type": "number" + } + ] + }, + { + "name": "onSelectionChanged", + "returnType": "boolean", + "parameters": [ + { + "name": "selection", + "type": "SelectionState | null" + } + ] + }, + { + "name": "getSelectedText", + "returnType": "string", + "parameters": [] + }, + { + "name": "hasSelection", + "returnType": "boolean", + "parameters": [] + } + ], + "properties": [ + { + "name": "selectable", + "type": "boolean" + } + ], + "constructor": { + "parameters": [ + { + "name": "id", + "type": "string" + }, + { + "name": "options", + "type": "ASCIIFontOptions" + } + ] + } + }, + { + "name": "InputRenderable", + "methods": [ + { + "name": "focus", + "returnType": "void", + "parameters": [] + }, + { + "name": "blur", + "returnType": "void", + "parameters": [] + }, + { + "name": "handleKeyPress", + "returnType": "boolean", + "parameters": [ + { + "name": "key", + "type": "ParsedKey | string" + } + ] + } + ], + "properties": [], + "constructor": { + "parameters": [ + { + "name": "id", + "type": "string" + }, + { + "name": "options", + "type": "InputRenderableOptions" + } + ] + } + }, + { + "name": "Timeline", + "methods": [ + { + "name": "add", + "returnType": "this", + "parameters": [ + { + "name": "target", + "type": "any" + }, + { + "name": "properties", + "type": "AnimationOptions" + }, + { + "name": "startTime", + "type": "number | string" + } + ] + }, + { + "name": "once", + "returnType": "this", + "parameters": [ + { + "name": "target", + "type": "any" + }, + { + "name": "properties", + "type": "AnimationOptions" + } + ] + }, + { + "name": "call", + "returnType": "this", + "parameters": [ + { + "name": "callback", + "type": "() => void" + }, + { + "name": "startTime", + "type": "number | string" + } + ] + }, + { + "name": "sync", + "returnType": "this", + "parameters": [ + { + "name": "timeline", + "type": "Timeline" + }, + { + "name": "startTime", + "type": "number" + } + ] + }, + { + "name": "play", + "returnType": "this", + "parameters": [] + }, + { + "name": "pause", + "returnType": "this", + "parameters": [] + }, + { + "name": "resetItems", + "returnType": "void", + "parameters": [] + }, + { + "name": "restart", + "returnType": "this", + "parameters": [] + }, + { + "name": "update", + "returnType": "void", + "parameters": [ + { + "name": "deltaTime", + "type": "number" + } + ] + } + ], + "properties": [ + { + "name": "items", + "type": "(TimelineAnimationItem | TimelineCallbackItem)[]" + }, + { + "name": "subTimelines", + "type": "TimelineTimelineItem[]" + }, + { + "name": "currentTime", + "type": "number" + }, + { + "name": "isPlaying", + "type": "boolean" + }, + { + "name": "isComplete", + "type": "boolean" + }, + { + "name": "duration", + "type": "number" + }, + { + "name": "loop", + "type": "boolean" + }, + { + "name": "synced", + "type": "boolean" + } + ], + "constructor": { + "parameters": [ + { + "name": "options", + "type": "TimelineOptions" + } + ] + } + } + ], + "interfaces": [ + { + "name": "RootContext", + "members": [ + { + "name": "requestLive", + "type": "void" + }, + { + "name": "dropLive", + "type": "void" + } + ] + }, + { + "name": "Position", + "members": [ + { + "name": "top", + "type": "number | \"auto\" | `${number}%`" + }, + { + "name": "right", + "type": "number | \"auto\" | `${number}%`" + }, + { + "name": "bottom", + "type": "number | \"auto\" | `${number}%`" + }, + { + "name": "left", + "type": "number | \"auto\" | `${number}%`" + } + ] + }, + { + "name": "LayoutOptions", + "members": [ + { + "name": "flexGrow", + "type": "number" + }, + { + "name": "flexShrink", + "type": "number" + }, + { + "name": "flexDirection", + "type": "FlexDirectionString" + }, + { + "name": "alignItems", + "type": "AlignString" + }, + { + "name": "justifyContent", + "type": "JustifyString" + }, + { + "name": "flexBasis", + "type": "number | \"auto\" | undefined" + }, + { + "name": "position", + "type": "PositionTypeString" + }, + { + "name": "top", + "type": "number | \"auto\" | `${number}%`" + }, + { + "name": "right", + "type": "number | \"auto\" | `${number}%`" + }, + { + "name": "bottom", + "type": "number | \"auto\" | `${number}%`" + }, + { + "name": "left", + "type": "number | \"auto\" | `${number}%`" + }, + { + "name": "minWidth", + "type": "number" + }, + { + "name": "minHeight", + "type": "number" + }, + { + "name": "maxWidth", + "type": "number" + }, + { + "name": "maxHeight", + "type": "number" + }, + { + "name": "margin", + "type": "number | \"auto\" | `${number}%`" + }, + { + "name": "marginTop", + "type": "number | \"auto\" | `${number}%`" + }, + { + "name": "marginRight", + "type": "number | \"auto\" | `${number}%`" + }, + { + "name": "marginBottom", + "type": "number | \"auto\" | `${number}%`" + }, + { + "name": "marginLeft", + "type": "number | \"auto\" | `${number}%`" + }, + { + "name": "padding", + "type": "number | `${number}%`" + }, + { + "name": "paddingTop", + "type": "number | `${number}%`" + }, + { + "name": "paddingRight", + "type": "number | `${number}%`" + }, + { + "name": "paddingBottom", + "type": "number | `${number}%`" + }, + { + "name": "paddingLeft", + "type": "number | `${number}%`" + }, + { + "name": "enableLayout", + "type": "boolean" + } + ] + }, + { + "name": "RenderableOptions", + "members": [ + { + "name": "width", + "type": "number | \"auto\" | `${number}%`" + }, + { + "name": "height", + "type": "number | \"auto\" | `${number}%`" + }, + { + "name": "zIndex", + "type": "number" + }, + { + "name": "visible", + "type": "boolean" + }, + { + "name": "buffered", + "type": "boolean" + }, + { + "name": "live", + "type": "boolean" + }, + { + "name": "onMouseDown", + "type": "(event: MouseEvent) => void" + }, + { + "name": "onMouseUp", + "type": "(event: MouseEvent) => void" + }, + { + "name": "onMouseMove", + "type": "(event: MouseEvent) => void" + }, + { + "name": "onMouseDrag", + "type": "(event: MouseEvent) => void" + }, + { + "name": "onMouseDragEnd", + "type": "(event: MouseEvent) => void" + }, + { + "name": "onMouseDrop", + "type": "(event: MouseEvent) => void" + }, + { + "name": "onMouseOver", + "type": "(event: MouseEvent) => void" + }, + { + "name": "onMouseOut", + "type": "(event: MouseEvent) => void" + }, + { + "name": "onMouseScroll", + "type": "(event: MouseEvent) => void" + }, + { + "name": "onKeyDown", + "type": "(key: ParsedKey) => void" + } + ] + }, + { + "name": "CliRendererConfig", + "members": [ + { + "name": "stdin", + "type": "NodeJS.ReadStream" + }, + { + "name": "stdout", + "type": "NodeJS.WriteStream" + }, + { + "name": "exitOnCtrlC", + "type": "boolean" + }, + { + "name": "debounceDelay", + "type": "number" + }, + { + "name": "targetFps", + "type": "number" + }, + { + "name": "memorySnapshotInterval", + "type": "number" + }, + { + "name": "useThread", + "type": "boolean" + }, + { + "name": "gatherStats", + "type": "boolean" + }, + { + "name": "maxStatSamples", + "type": "number" + }, + { + "name": "consoleOptions", + "type": "ConsoleOptions" + }, + { + "name": "postProcessFns", + "type": "((buffer: OptimizedBuffer, deltaTime: number) => void)[]" + }, + { + "name": "enableMouseMovement", + "type": "boolean" + }, + { + "name": "useMouse", + "type": "boolean" + }, + { + "name": "useAlternateScreen", + "type": "boolean" + }, + { + "name": "useConsole", + "type": "boolean" + }, + { + "name": "experimental_splitHeight", + "type": "number" + } + ] + }, + { + "name": "BoxOptions", + "members": [ + { + "name": "backgroundColor", + "type": "string | RGBA" + }, + { + "name": "borderStyle", + "type": "BorderStyle" + }, + { + "name": "border", + "type": "boolean | BorderSides[]" + }, + { + "name": "borderColor", + "type": "string | RGBA" + }, + { + "name": "customBorderChars", + "type": "BorderCharacters" + }, + { + "name": "shouldFill", + "type": "boolean" + }, + { + "name": "title", + "type": "string" + }, + { + "name": "titleAlignment", + "type": "\"left\" | \"center\" | \"right\"" + }, + { + "name": "focusedBorderColor", + "type": "ColorInput" + } + ] + }, + { + "name": "TextOptions", + "members": [ + { + "name": "content", + "type": "StyledText | string" + }, + { + "name": "fg", + "type": "string | RGBA" + }, + { + "name": "bg", + "type": "string | RGBA" + }, + { + "name": "selectionBg", + "type": "string | RGBA" + }, + { + "name": "selectionFg", + "type": "string | RGBA" + }, + { + "name": "selectable", + "type": "boolean" + }, + { + "name": "attributes", + "type": "number" + } + ] + }, + { + "name": "ASCIIFontOptions", + "members": [ + { + "name": "text", + "type": "string" + }, + { + "name": "font", + "type": "\"tiny\" | \"block\" | \"shade\" | \"slick\"" + }, + { + "name": "fg", + "type": "RGBA | RGBA[]" + }, + { + "name": "bg", + "type": "RGBA" + }, + { + "name": "selectionBg", + "type": "string | RGBA" + }, + { + "name": "selectionFg", + "type": "string | RGBA" + }, + { + "name": "selectable", + "type": "boolean" + } + ] + }, + { + "name": "InputRenderableOptions", + "members": [ + { + "name": "backgroundColor", + "type": "ColorInput" + }, + { + "name": "textColor", + "type": "ColorInput" + }, + { + "name": "focusedBackgroundColor", + "type": "ColorInput" + }, + { + "name": "focusedTextColor", + "type": "ColorInput" + }, + { + "name": "placeholder", + "type": "string" + }, + { + "name": "placeholderColor", + "type": "ColorInput" + }, + { + "name": "cursorColor", + "type": "ColorInput" + }, + { + "name": "maxLength", + "type": "number" + }, + { + "name": "value", + "type": "string" + } + ] + }, + { + "name": "TimelineOptions", + "members": [ + { + "name": "duration", + "type": "number" + }, + { + "name": "loop", + "type": "boolean" + }, + { + "name": "autoplay", + "type": "boolean" + }, + { + "name": "onComplete", + "type": "() => void" + }, + { + "name": "onPause", + "type": "() => void" + } + ] + }, + { + "name": "AnimationOptions", + "members": [ + { + "name": "duration", + "type": "number" + }, + { + "name": "ease", + "type": "EasingFunctions" + }, + { + "name": "onUpdate", + "type": "(animation: JSAnimation) => void" + }, + { + "name": "onComplete", + "type": "() => void" + }, + { + "name": "onStart", + "type": "() => void" + }, + { + "name": "onLoop", + "type": "() => void" + }, + { + "name": "loop", + "type": "boolean | number" + }, + { + "name": "loopDelay", + "type": "number" + }, + { + "name": "alternate", + "type": "boolean" + }, + { + "name": "once", + "type": "boolean" + }, + { + "name": "", + "type": "any" + } + ] + }, + { + "name": "JSAnimation", + "members": [ + { + "name": "targets", + "type": "any[]" + }, + { + "name": "deltaTime", + "type": "number" + }, + { + "name": "progress", + "type": "number" + }, + { + "name": "currentTime", + "type": "number" + } + ] + } + ], + "functions": [ + { + "name": "isMarginType", + "parameters": [ + { + "name": "value", + "type": "any" + } + ], + "returnType": "value is number | \"auto\" | `${number}%`" + }, + { + "name": "isPaddingType", + "parameters": [ + { + "name": "value", + "type": "any" + } + ], + "returnType": "value is number | `${number}%`" + }, + { + "name": "isPositionType", + "parameters": [ + { + "name": "value", + "type": "any" + } + ], + "returnType": "value is number | \"auto\" | `${number}%`" + }, + { + "name": "isPostionTypeType", + "parameters": [ + { + "name": "value", + "type": "any" + } + ], + "returnType": "value is PositionTypeString" + }, + { + "name": "isDimensionType", + "parameters": [ + { + "name": "value", + "type": "any" + } + ], + "returnType": "value is number | \"auto\" | `${number}%`" + }, + { + "name": "isFlexBasisType", + "parameters": [ + { + "name": "value", + "type": "any" + } + ], + "returnType": "value is number | \"auto\" | undefined" + }, + { + "name": "isSizeType", + "parameters": [ + { + "name": "value", + "type": "any" + } + ], + "returnType": "value is number | `${number}%` | undefined" + }, + { + "name": "createCliRenderer", + "parameters": [ + { + "name": "config", + "type": "CliRendererConfig" + } + ], + "returnType": "Promise" + }, + { + "name": "createTimeline", + "parameters": [ + { + "name": "options", + "type": "TimelineOptions" + } + ], + "returnType": "Timeline" + } + ], + "enums": [], + "types": [] +} \ No newline at end of file diff --git a/packages/core/docs/api/index.md b/packages/core/docs/api/index.md index 4346941df..027818b1b 100644 --- a/packages/core/docs/api/index.md +++ b/packages/core/docs/api/index.md @@ -1,227 +1,145 @@ -# OpenTUI API Reference +# OpenTUI API Documentation -Welcome to the OpenTUI API reference documentation. This comprehensive guide provides detailed information about all the components, functions, and classes available in the OpenTUI Core package. +OpenTUI is a modern terminal UI framework that brings React-like component architecture to the terminal. -## Core Rendering System +## Quick Links -The core rendering system is the foundation of OpenTUI, providing the main renderer, layout management, and lifecycle handling. +### Module Documentation +Learn how to use OpenTUI with practical guides and examples: -### [Rendering Engine](./core/rendering.md) -- **CliRenderer**: The main rendering engine that manages terminal output and input -- **RenderContext**: Context information for rendering components -- **Lifecycle Methods**: Methods for controlling the rendering lifecycle -- **Performance Monitoring**: Tools for monitoring and optimizing performance +- [Getting Started](../modules/guide.md) - Installation and basic usage +- [Components](../modules/components.md) - Built-in UI components +- [Layout System](../modules/layout.md) - Flexbox layout in the terminal +- [Event Handling](../modules/events.md) - Mouse and keyboard input +- [Animation](../modules/animation.md) - Smooth animations and transitions +- [Rendering](../modules/rendering.md) - Rendering pipeline and optimization -### [Buffer System](./buffer.md) -- **OptimizedBuffer**: High-performance buffer for terminal rendering -- **Drawing Operations**: Methods for drawing text, boxes, and other elements -- **Color Blending**: Functions for blending colors with alpha transparency -- **Buffer Composition**: Techniques for composing multiple buffers +### API Reference -### [Native Integration](./native-integration.md) -- **Zig Code**: Performance-critical code written in Zig -- **FFI Bindings**: JavaScript bindings to the native code -- **Platform Support**: Pre-built binaries for different platforms +#### Core Classes +- [CliRenderer](./reference/classes/CliRenderer.md) - Main renderer managing the terminal +- [Renderable](./reference/classes/Renderable.md) - Base class for all components +- [OptimizedBuffer](./reference/classes/OptimizedBuffer.md) - High-performance rendering buffer -## Components +#### Components +- [BoxRenderable](./reference/classes/BoxRenderable.md) - Container component with borders +- [TextRenderable](./reference/classes/TextRenderable.md) - Text display with styling +- [InputRenderable](./reference/classes/InputRenderable.md) - Text input field +- [ASCIIFontRenderable](./reference/classes/ASCIIFontRenderable.md) - ASCII art text -OpenTUI provides a variety of components for building terminal user interfaces. +#### Events +- [MouseEvent](./reference/classes/MouseEvent.md) - Mouse interaction handling +- [MouseEventType](./reference/types/MouseEventType.md) - Types of mouse events -### [Renderables](./components/renderables.md) -- **BoxRenderable**: Container component with optional borders and background -- **TextRenderable**: Component for displaying text with styling -- **InputRenderable**: Text input field component -- **SelectRenderable**: Dropdown selection component -- **TabSelectRenderable**: Tabbed interface component -- **FrameBufferRenderable**: Canvas for custom drawing - -### [ASCII Font](./renderables/ascii-font.md) -- **ASCIIFontRenderable**: Component for rendering ASCII art fonts -- **Built-in Fonts**: Several built-in ASCII art fonts -- **Custom Fonts**: Create and use custom ASCII art fonts +#### Animation +- [Timeline](./reference/classes/Timeline.md) - Animation timeline system -## Styling +#### Configuration Interfaces +- [BoxOptions](./reference/interfaces/BoxOptions.md) - Box component configuration +- [TextOptions](./reference/interfaces/TextOptions.md) - Text component configuration +- [InputRenderableOptions](./reference/interfaces/InputRenderableOptions.md) - Input field configuration +- [ASCIIFontOptions](./reference/interfaces/ASCIIFontOptions.md) - ASCII font configuration +- [CliRendererConfig](./reference/interfaces/CliRendererConfig.md) - Renderer configuration -OpenTUI provides rich styling capabilities for text, borders, and other visual elements. +#### Type Definitions +- [BorderStyle](./reference/types/BorderStyle.md) - Available border styles +- [MouseEventType](./reference/types/MouseEventType.md) - Mouse event types -### [Text Styling](./styling/text-styling.md) -- **Text Formatting**: Format text with colors and attributes -- **Color Management**: Foreground and background color handling +## Quick Start Example -### [Border Styles](./lib/border.md) -- **Built-in Borders**: Various border styles for boxes and containers -- **Custom Borders**: Create your own border styles -- **Border Characters**: Control individual border characters +```typescript +import { CliRenderer, BoxRenderable, TextRenderable } from '@opentui/core'; +import { RGBA } from '@opentui/core'; + +// Create the renderer +const renderer = new CliRenderer(lib, ptr, stdin, stdout, 80, 24, { + backgroundColor: '#1e1e1e' +}); + +// Create a main container +const mainBox = new BoxRenderable('main', { + width: '100%', + height: '100%', + border: true, + borderStyle: 'rounded', + title: 'My App', + padding: 1 +}); + +// Add some text +const text = new TextRenderable('greeting', { + text: 'Welcome to OpenTUI!', + color: RGBA.fromHex('#00ff00'), + align: 'center' +}); + +// Build the UI tree +mainBox.add(text, 0); +renderer.root.add(mainBox, 0); + +// Start rendering +renderer.start(); +``` -### [Styled Text](./lib/styled-text.md) -- **StyledText Class**: Create rich text with different styles -- **Text Attributes**: Bold, italic, underline, and other text attributes -- **Syntax Highlighting**: Create syntax highlighters for code - -### [HAST Styled Text](./lib/hast-styled-text.md) -- **HAST Structure**: Hypertext Abstract Syntax Tree for complex text styling -- **SyntaxStyle**: Define and merge text styles -- **Syntax Highlighting**: Create syntax highlighters with HAST - -### [Text Selection](./lib/selection.md) -- **Selection System**: Select and copy text from the terminal -- **TextSelectionHelper**: Handle text selection for standard text components -- **ASCIIFontSelectionHelper**: Handle text selection for ASCII font components - -### [TrackedNode](./lib/tracked-node.md) -- **Layout Tree**: Build and manage layout trees with Yoga -- **Node Hierarchy**: Parent-child relationships between nodes -- **Percentage Dimensions**: Support for percentage-based dimensions - -## Input Handling - -OpenTUI provides comprehensive input handling for keyboard and mouse events. - -### [Input System](./input/input.md) -- **Keyboard Input**: Handling key presses and keyboard shortcuts -- **Mouse Input**: Handling mouse clicks, movement, and scroll events -- **Focus Management**: Managing focus between components -- **Drag and Drop**: Support for drag and drop operations - -### [Key Handler](./lib/keyhandler.md) -- **Key Events**: Processing and handling keyboard events -- **Key Combinations**: Support for key combinations like Ctrl+C -- **Navigation**: Keyboard-based navigation between components - -## Animation - -OpenTUI provides powerful animation capabilities for creating dynamic interfaces. - -### [Animation System](./animation/animation.md) -- **Animation Basics**: Core animation concepts and techniques -- **Easing Functions**: Various easing functions for smooth animations -- **Sprite Animation**: Frame-based animations from sprite sheets -- **Particle Effects**: Visual effects with particle systems -- **Physics-Based Animation**: Integration with physics engines - -### [Timeline](./animation/timeline.md) -- **Animation Sequencing**: Create complex animation sequences -- **Precise Timing**: Control animation timing with millisecond precision -- **Easing Functions**: Apply easing functions to animations -- **Nested Timelines**: Create hierarchical animation structures - -## Advanced Features - -OpenTUI includes advanced rendering capabilities for creating rich visual experiences. - -### [3D Rendering](./advanced/3d.md) -- **Three.js Integration**: Create 3D scenes in the terminal -- **WebGPU Integration**: High-performance graphics rendering -- **Sprite Rendering**: Display images in the terminal -- **Texture Loading**: Load and manage textures -- **Lighting and Materials**: Advanced lighting and materials - -### [WebGPU Shaders](./3d/shaders.md) -- **WGSL Shaders**: Write custom shaders in WebGPU Shading Language -- **Shader Effects**: Create visual effects with shaders -- **Supersampling**: Improve rendering quality with supersampling - -### [Post-Processing Filters](./post/filters.md) -- **Basic Filters**: Scanlines, grayscale, sepia, invert, noise, and more -- **Advanced Effects**: Distortion, vignette, brightness, blur, and bloom -- **Combining Effects**: Create complex visual styles - -### [Sprite Animation](./3d/sprite-animation.md) -- **Sprite Animator**: Animate sprites with frame-based animations -- **Sprite Resource Manager**: Load and manage sprite resources -- **Particle Effects**: Create particle effects with sprites - -### [Physics Integration](./3d/physics.md) -- **Physics Adapters**: Integrate with Planck.js and Rapier physics engines -- **Rigid Bodies**: Create static, dynamic, and kinematic bodies -- **Joints and Constraints**: Connect bodies with joints and constraints -- **Collision Detection**: Detect and respond to collisions - -## Utilities - -OpenTUI provides various utility functions and classes for common tasks. - -### [Utility Functions](./utils/utilities.md) -- **Color Utilities**: Tools for working with colors -- **ANSI Terminal Utilities**: Working with ANSI escape sequences -- **Buffer Utilities**: High-performance buffers for terminal rendering -- **Layout Utilities**: Tools for working with the layout system -- **Border Utilities**: Utilities for working with borders - -### [Console](./utils/console.md) -- **Debug Console**: Terminal console for debugging and logging -- **Logging**: Log messages with different levels and colors -- **Console Window**: Create a console window within your application - -### [Output Capture](./utils/output-capture.md) -- **Stdout Capture**: Capture and redirect standard output -- **Stderr Capture**: Capture and redirect standard error -- **Child Process Capture**: Capture output from child processes +## Framework Integrations -## Quick Start Example +OpenTUI provides React and Solid.js bindings for declarative UI development: -Here's a simple example to get you started with OpenTUI: +### React +```tsx +import { render, Box, Text } from '@opentui/react'; -```typescript -import { createCliRenderer, BoxRenderable, TextRenderable } from '@opentui/core'; - -async function main() { - // Create the renderer - const renderer = await createCliRenderer({ - targetFps: 30, - useAlternateScreen: true - }); - - // Access the root element - const { root } = renderer; - - // Create a container box - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - borderStyle: 'double', - borderColor: '#3498db' - }); - - // Add a text element - const text = new TextRenderable('title', { - content: 'Hello, OpenTUI!', - fg: '#ffffff', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 - }); - - // Build the component tree - container.add(text); - root.add(container); - - // Start the rendering loop - renderer.start(); - - // Handle cleanup on exit - process.on('SIGINT', () => { - renderer.destroy(); - process.exit(0); - }); +function App() { + return ( + + Hello from React! + + ); +} + +render(); +``` + +### Solid.js +```tsx +import { render, Box, Text } from '@opentui/solid'; + +function App() { + return ( + + Hello from Solid! + + ); } -main().catch(console.error); +render(() => ); ``` -## Framework Integration +## Features + +- 🎨 **Rich Styling** - Colors, borders, backgrounds, and ASCII fonts +- 📐 **Flexbox Layout** - Powered by Yoga layout engine +- 🖱️ **Mouse Support** - Full mouse interaction including drag & drop +- ⌨️ **Keyboard Input** - Comprehensive keyboard handling +- 🎬 **Animations** - Smooth transitions and effects +- ⚡ **Performance** - Optimized double-buffered rendering +- 🔧 **Framework Support** - React and Solid.js bindings +- 🎯 **TypeScript** - Full type safety and IntelliSense + +## Architecture + +OpenTUI uses a component-based architecture similar to web frameworks: + +1. **Components** inherit from `Renderable` base class +2. **Layout** calculated using Yoga flexbox engine +3. **Rendering** uses double-buffered `OptimizedBuffer` +4. **Events** bubble up through component tree +5. **Animations** managed by `Timeline` system -OpenTUI provides integration with popular JavaScript frameworks. +## Contributing -### [React Integration](./react/reconciler.md) -- **React Reconciler**: Build terminal UIs using React components and hooks -- **React Components**: Pre-built React components for common UI elements -- **React Hooks**: Custom React hooks for terminal-specific functionality -- **Event Handling**: React-style event handling for terminal events +See [CONTRIBUTING.md](https://github.com/sst/opentui/blob/main/CONTRIBUTING.md) for development setup and guidelines. -## Additional Resources +## License -- [Getting Started Guide](../getting-started.md): A beginner's guide to OpenTUI -- [Guides](../guides/index.md): In-depth guides on specific topics -- [Examples](../examples/index.md): Example applications built with OpenTUI -- [Source Code Examples](https://github.com/yourusername/opentui/tree/main/packages/core/src/examples): Examples in the source code repository +MIT - See [LICENSE](https://github.com/sst/opentui/blob/main/LICENSE) diff --git a/packages/core/docs/api/input/input.md b/packages/core/docs/api/input/input.md deleted file mode 100644 index 74cd10b4c..000000000 --- a/packages/core/docs/api/input/input.md +++ /dev/null @@ -1,742 +0,0 @@ -# Input Handling API - -OpenTUI provides a comprehensive input handling system for keyboard and mouse events, allowing you to create interactive terminal applications. - -## Keyboard Input - -### Key Events - -The renderer emits key events that you can listen for: - -```typescript -import { createCliRenderer } from '@opentui/core'; - -const renderer = await createCliRenderer(); - -// Listen for raw key events -renderer.on('key', (data) => { - console.log('Key data:', data.toString()); -}); -``` - -### ParsedKey - -The `ParsedKey` interface provides a structured representation of keyboard input: - -```typescript -interface ParsedKey { - sequence: string; // Raw key sequence - name: string; // Key name (e.g., 'a', 'return', 'escape') - ctrl: boolean; // Whether Ctrl was pressed - meta: boolean; // Whether Meta/Alt was pressed - shift: boolean; // Whether Shift was pressed - code?: string; // Key code for special keys -} -``` - -### KeyHandler - -The `KeyHandler` class provides a higher-level API for handling keyboard input: - -```typescript -import { getKeyHandler, parseKeypress } from '@opentui/core'; - -// Get the global key handler -const keyHandler = getKeyHandler(); - -// Listen for keypress events -keyHandler.on('keypress', (key) => { - console.log('Key pressed:', key.name); - - if (key.ctrl && key.name === 'c') { - console.log('Ctrl+C pressed'); - } -}); - -// Parse a key sequence manually -const key = parseKeypress('\x1b[A'); // Up arrow key -console.log(key); // { name: 'up', sequence: '\x1b[A', ... } -``` - -### Key Names - -Common key names you can check for: - -| Category | Key Names | -|----------|-----------| -| Letters | `'a'` through `'z'` | -| Numbers | `'0'` through `'9'` | -| Special | `'space'`, `'backspace'`, `'tab'`, `'return'`, `'escape'` | -| Function | `'f1'` through `'f12'` | -| Navigation | `'up'`, `'down'`, `'left'`, `'right'`, `'home'`, `'end'`, `'pageup'`, `'pagedown'` | -| Editing | `'delete'`, `'insert'` | - -### Example: Handling Keyboard Shortcuts - -```typescript -import { getKeyHandler, Renderable } from '@opentui/core'; - -class KeyboardShortcutsComponent extends Renderable { - constructor(id: string, options = {}) { - super(id, options); - this.focusable = true; // Enable focus to receive key events - } - - handleKeyPress(key: ParsedKey): boolean { - // Check for specific keys - if (key.name === 'return') { - console.log('Enter key pressed'); - return true; - } - - // Check for key combinations - if (key.ctrl && key.name === 's') { - console.log('Ctrl+S pressed - Save action'); - return true; - } - - if (key.ctrl && key.shift && key.name === 'p') { - console.log('Ctrl+Shift+P pressed - Print action'); - return true; - } - - // Check for arrow keys - if (key.name === 'up' || key.name === 'down' || - key.name === 'left' || key.name === 'right') { - console.log(`Arrow key pressed: ${key.name}`); - return true; - } - - return false; // Key not handled - } -} -``` - -### Creating a Global Keyboard Shortcut Handler - -```typescript -import { getKeyHandler } from '@opentui/core'; - -// Define a keyboard shortcut handler -function setupGlobalShortcuts() { - const keyHandler = getKeyHandler(); - - const shortcuts = { - 'ctrl+q': () => { - console.log('Quit application'); - process.exit(0); - }, - 'ctrl+s': () => { - console.log('Save action'); - }, - 'ctrl+o': () => { - console.log('Open action'); - }, - 'f1': () => { - console.log('Show help'); - } - }; - - keyHandler.on('keypress', (key) => { - // Build a key identifier string - let keyId = ''; - if (key.ctrl) keyId += 'ctrl+'; - if (key.meta) keyId += 'alt+'; - if (key.shift) keyId += 'shift+'; - keyId += key.name; - - // Check if we have a handler for this shortcut - if (shortcuts[keyId]) { - shortcuts[keyId](); - } - }); -} - -// Call this function to set up global shortcuts -setupGlobalShortcuts(); -``` - -## Mouse Input - -OpenTUI provides comprehensive mouse event handling for creating interactive interfaces. - -### Mouse Event Types - -| Event Type | Description | -|------------|-------------| -| `'down'` | Mouse button pressed | -| `'up'` | Mouse button released | -| `'click'` | Mouse click (down followed by up) | -| `'drag'` | Mouse moved while button pressed | -| `'drag-end'` | Mouse button released after dragging | -| `'move'` | Mouse moved without button pressed | -| `'over'` | Mouse entered a component | -| `'out'` | Mouse left a component | -| `'drop'` | Item dropped on a component | -| `'scroll'` | Mouse wheel scrolled | - -### MouseEvent Class - -The `MouseEvent` class provides information about mouse events: - -```typescript -class MouseEvent { - public readonly type: MouseEventType; // Event type - public readonly button: number; // Button number - public readonly x: number; // X coordinate - public readonly y: number; // Y coordinate - public readonly source?: Renderable; // Source component (for drag operations) - public readonly modifiers: { // Modifier keys - shift: boolean; - alt: boolean; - ctrl: boolean; - }; - public readonly scroll?: ScrollInfo; // Scroll information - public readonly target: Renderable | null; // Target component - - // Prevent event bubbling - public preventDefault(): void; -} -``` - -### MouseButton Enum - -```typescript -enum MouseButton { - LEFT = 0, - MIDDLE = 1, - RIGHT = 2, - WHEEL_UP = 4, - WHEEL_DOWN = 5, -} -``` - -### Handling Mouse Events - -Components can handle mouse events by overriding the `onMouseEvent` method: - -```typescript -import { BoxRenderable, MouseEvent, MouseButton } from '@opentui/core'; - -class ClickableBox extends BoxRenderable { - protected onMouseEvent(event: MouseEvent): void { - switch (event.type) { - case 'over': - this.borderColor = '#00ff00'; - break; - case 'out': - this.borderColor = '#ffffff'; - break; - case 'down': - if (event.button === MouseButton.LEFT) { - this.backgroundColor = '#555555'; - } - break; - case 'up': - this.backgroundColor = 'transparent'; - break; - case 'click': - console.log('Box clicked at', event.x, event.y); - // Emit a custom event - this.emit('activated'); - break; - } - } -} - -// Usage -const clickable = new ClickableBox('clickable', { - width: 20, - height: 5, - borderStyle: 'single' -}); - -// Listen for custom events -clickable.on('activated', () => { - console.log('Box was activated!'); -}); -``` - -### Drag and Drop - -OpenTUI supports drag and drop operations: - -```typescript -import { BoxRenderable, TextRenderable, MouseEvent, MouseButton } from '@opentui/core'; - -// Draggable item -class DraggableItem extends BoxRenderable { - private isDragging = false; - private startX = 0; - private startY = 0; - - constructor(id: string, options = {}) { - super(id, { - width: 10, - height: 3, - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222', - position: 'absolute', - ...options - }); - - // Add a label - const label = new TextRenderable(`${id}-label`, { - content: 'Drag me', - fg: '#ffffff', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 - }); - - this.add(label); - } - - protected onMouseEvent(event: MouseEvent): void { - switch (event.type) { - case 'down': - if (event.button === MouseButton.LEFT) { - this.isDragging = true; - this.startX = event.x - this.x; - this.startY = event.y - this.y; - this.borderColor = '#e74c3c'; - event.preventDefault(); // Capture the mouse - } - break; - - case 'drag': - if (this.isDragging) { - this.x = event.x - this.startX; - this.y = event.y - this.startY; - event.preventDefault(); - } - break; - - case 'drag-end': - this.isDragging = false; - this.borderColor = '#3498db'; - break; - - case 'over': - if (!this.isDragging) { - this.borderColor = '#2ecc71'; - } - break; - - case 'out': - if (!this.isDragging) { - this.borderColor = '#3498db'; - } - break; - } - } -} - -// Drop target -class DropTarget extends BoxRenderable { - constructor(id: string, options = {}) { - super(id, { - width: 20, - height: 10, - borderStyle: 'dashed', - borderColor: '#3498db', - backgroundColor: 'transparent', - ...options - }); - - // Add a label - const label = new TextRenderable(`${id}-label`, { - content: 'Drop here', - fg: '#ffffff', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 - }); - - this.add(label); - } - - protected onMouseEvent(event: MouseEvent): void { - switch (event.type) { - case 'over': - if (event.source) { - this.borderColor = '#2ecc71'; - this.borderStyle = 'double'; - } - break; - - case 'out': - this.borderColor = '#3498db'; - this.borderStyle = 'dashed'; - break; - - case 'drop': - if (event.source) { - this.borderColor = '#e74c3c'; - this.borderStyle = 'single'; - - console.log(`Item ${event.source.id} dropped on ${this.id}`); - this.emit('item-dropped', event.source); - - // Reset after a delay - setTimeout(() => { - this.borderColor = '#3498db'; - this.borderStyle = 'dashed'; - }, 1000); - } - break; - } - } -} - -// Usage -const draggable = new DraggableItem('draggable', { - x: 5, - y: 5 -}); - -const dropTarget = new DropTarget('dropTarget', { - x: 30, - y: 10 -}); - -dropTarget.on('item-dropped', (item) => { - console.log(`Handling drop of ${item.id}`); -}); - -// Add to the renderer -renderer.root.add(draggable); -renderer.root.add(dropTarget); -``` - -### Scroll Events - -Handle scroll events for scrollable components: - -```typescript -import { BoxRenderable, TextRenderable, MouseEvent } from '@opentui/core'; - -class ScrollableContent extends BoxRenderable { - private scrollOffset = 0; - private content: TextRenderable; - private maxScroll = 0; - - constructor(id: string, options = {}) { - super(id, { - width: 40, - height: 10, - borderStyle: 'single', - borderColor: '#3498db', - ...options - }); - - // Create long content - const longText = Array(30).fill(0).map((_, i) => `Line ${i + 1}`).join('\n'); - - this.content = new TextRenderable(`${id}-content`, { - content: longText, - fg: '#ffffff' - }); - - this.add(this.content); - this.maxScroll = 30 - this.height + 2; // Account for borders - } - - protected onMouseEvent(event: MouseEvent): void { - if (event.type === 'scroll' && event.scroll) { - // Handle scroll up/down - if (event.scroll.direction === 'up') { - this.scrollOffset = Math.max(0, this.scrollOffset - 1); - } else if (event.scroll.direction === 'down') { - this.scrollOffset = Math.min(this.maxScroll, this.scrollOffset + 1); - } - - // Update content position - this.content.top = -this.scrollOffset; - - event.preventDefault(); - } - } -} - -// Usage -const scrollable = new ScrollableContent('scrollable', { - x: 5, - y: 5 -}); - -renderer.root.add(scrollable); -``` - -## Focus Management - -OpenTUI provides a focus system for keyboard navigation between components. - -### Making Components Focusable - -```typescript -import { BoxRenderable } from '@opentui/core'; - -class FocusableBox extends BoxRenderable { - constructor(id: string, options = {}) { - super(id, options); - this.focusable = true; // Enable focus - } - - // Optional: Handle focus events - public focus(): void { - super.focus(); - this.borderColor = '#2ecc71'; - console.log(`${this.id} gained focus`); - } - - public blur(): void { - super.blur(); - this.borderColor = '#3498db'; - console.log(`${this.id} lost focus`); - } -} -``` - -### Focus Navigation - -Create a focus manager for keyboard navigation: - -```typescript -import { getKeyHandler, Renderable } from '@opentui/core'; - -class FocusManager { - private focusableElements: Renderable[] = []; - private currentFocusIndex: number = -1; - - constructor() { - const keyHandler = getKeyHandler(); - - keyHandler.on('keypress', (key) => { - if (key.name === 'tab') { - if (key.shift) { - this.focusPrevious(); - } else { - this.focusNext(); - } - } - }); - } - - public register(element: Renderable): void { - if (element.focusable) { - this.focusableElements.push(element); - } - } - - public unregister(element: Renderable): void { - const index = this.focusableElements.indexOf(element); - if (index !== -1) { - this.focusableElements.splice(index, 1); - if (this.currentFocusIndex >= this.focusableElements.length) { - this.currentFocusIndex = this.focusableElements.length - 1; - } - } - } - - public focusNext(): void { - if (this.focusableElements.length === 0) return; - - // Blur current element - if (this.currentFocusIndex !== -1) { - this.focusableElements[this.currentFocusIndex].blur(); - } - - // Move to next element - this.currentFocusIndex = (this.currentFocusIndex + 1) % this.focusableElements.length; - - // Focus new element - this.focusableElements[this.currentFocusIndex].focus(); - } - - public focusPrevious(): void { - if (this.focusableElements.length === 0) return; - - // Blur current element - if (this.currentFocusIndex !== -1) { - this.focusableElements[this.currentFocusIndex].blur(); - } - - // Move to previous element - this.currentFocusIndex = (this.currentFocusIndex - 1 + this.focusableElements.length) % this.focusableElements.length; - - // Focus new element - this.focusableElements[this.currentFocusIndex].focus(); - } - - public focusFirst(): void { - if (this.focusableElements.length === 0) return; - - // Blur current element - if (this.currentFocusIndex !== -1) { - this.focusableElements[this.currentFocusIndex].blur(); - } - - // Focus first element - this.currentFocusIndex = 0; - this.focusableElements[this.currentFocusIndex].focus(); - } -} - -// Usage -const focusManager = new FocusManager(); - -// Register focusable elements -focusManager.register(input1); -focusManager.register(input2); -focusManager.register(button); - -// Focus the first element -focusManager.focusFirst(); -``` - -### Example: Creating a Form with Focus Navigation - -```typescript -import { BoxRenderable, TextRenderable, InputRenderable, createCliRenderer, getKeyHandler } from '@opentui/core'; - -async function createForm() { - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a form container - const form = new BoxRenderable('form', { - width: 40, - height: 15, - borderStyle: 'rounded', - borderColor: '#3498db', - backgroundColor: '#222222', - title: 'Login Form', - titleAlignment: 'center', - padding: 1, - flexDirection: 'column' - }); - - // Username field - const usernameLabel = new TextRenderable('usernameLabel', { - content: 'Username:', - fg: '#ffffff', - marginBottom: 1 - }); - - const usernameInput = new InputRenderable('usernameInput', { - width: '100%', - placeholder: 'Enter username', - borderStyle: 'single', - borderColor: '#3498db', - focusedBorderColor: '#2ecc71', - marginBottom: 2 - }); - - // Password field - const passwordLabel = new TextRenderable('passwordLabel', { - content: 'Password:', - fg: '#ffffff', - marginBottom: 1 - }); - - const passwordInput = new InputRenderable('passwordInput', { - width: '100%', - placeholder: 'Enter password', - borderStyle: 'single', - borderColor: '#3498db', - focusedBorderColor: '#2ecc71', - marginBottom: 2 - }); - - // Login button - const loginButton = new BoxRenderable('loginButton', { - width: 10, - height: 3, - borderStyle: 'single', - borderColor: '#3498db', - focusedBorderColor: '#2ecc71', - alignSelf: 'center', - marginTop: 1 - }); - - loginButton.focusable = true; - - const buttonLabel = new TextRenderable('buttonLabel', { - content: 'Login', - fg: '#ffffff', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 - }); - - loginButton.add(buttonLabel); - - // Handle button click - loginButton.on('mouseEvent', (event) => { - if (event.type === 'click') { - console.log('Login clicked'); - console.log(`Username: ${usernameInput.value}`); - console.log(`Password: ${passwordInput.value}`); - } - }); - - // Handle button keyboard activation - loginButton.handleKeyPress = (key) => { - if (key.name === 'return' || key.name === 'space') { - console.log('Login activated via keyboard'); - console.log(`Username: ${usernameInput.value}`); - console.log(`Password: ${passwordInput.value}`); - return true; - } - return false; - }; - - // Assemble the form - form.add(usernameLabel); - form.add(usernameInput); - form.add(passwordLabel); - form.add(passwordInput); - form.add(loginButton); - - // Add the form to the root - root.add(form); - - // Set up focus navigation - const keyHandler = getKeyHandler(); - const focusableElements = [usernameInput, passwordInput, loginButton]; - let currentFocusIndex = -1; - - keyHandler.on('keypress', (key) => { - if (key.name === 'tab') { - // Blur current element - if (currentFocusIndex !== -1) { - focusableElements[currentFocusIndex].blur(); - } - - // Move to next/previous element - if (key.shift) { - currentFocusIndex = (currentFocusIndex - 1 + focusableElements.length) % focusableElements.length; - } else { - currentFocusIndex = (currentFocusIndex + 1) % focusableElements.length; - } - - // Focus new element - focusableElements[currentFocusIndex].focus(); - } - }); - - // Focus the first input - currentFocusIndex = 0; - focusableElements[currentFocusIndex].focus(); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and show the form -createForm().catch(console.error); -``` diff --git a/packages/core/docs/api/jsdoc/ASCIIFontOptions.js b/packages/core/docs/api/jsdoc/ASCIIFontOptions.js deleted file mode 100644 index eb4ddbdd0..000000000 --- a/packages/core/docs/api/jsdoc/ASCIIFontOptions.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Generated from: ASCIIFontOptions.json - * Date: 2025-08-22T07:06:25.491Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a MouseEventType string - * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType - */ - - - /** - * Represents a ParsedKey object - * @typedef {object} ParsedKey - * @property {string} [code] - * @property {boolean} ctrl - * @property {boolean} meta - * @property {string} name - * @property {boolean} number - * @property {boolean} option - * @property {string} raw - * @property {string} sequence - * @property {boolean} shift - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - /** - * Represents a Renderable object - * @typedef {object} Renderable - * @property {string} id - * @property {number} num - * @property {*} parent - * @property {boolean} selectable - */ - - - /** - * Represents a ScrollInfo object - * @typedef {object} ScrollInfo - * @property {number} delta - * @property {"up"|"down"|"left"|"right"} direction - */ - - diff --git a/packages/core/docs/api/jsdoc/AnimationOptions.js b/packages/core/docs/api/jsdoc/AnimationOptions.js deleted file mode 100644 index 9a8f745c7..000000000 --- a/packages/core/docs/api/jsdoc/AnimationOptions.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Generated from: AnimationOptions.json - * Date: 2025-08-22T07:06:25.492Z - */ - - /** - * Represents a EasingFunctions string - * @typedef {"linear"|"inQuad"|"outQuad"|"inOutQuad"|"inExpo"|"outExpo"|"inOutSine"|"outBounce"|"outElastic"|"inBounce"|"inCirc"|"outCirc"|"inOutCirc"|"inBack"|"outBack"|"inOutBack"} EasingFunctions - */ - - - /** - * Represents a JSAnimation object - * @typedef {object} JSAnimation - * @property {number} currentTime - * @property {number} deltaTime - * @property {number} progress - * @property {array} targets - */ - - diff --git a/packages/core/docs/api/jsdoc/BorderConfig.js b/packages/core/docs/api/jsdoc/BorderConfig.js deleted file mode 100644 index 7c6002240..000000000 --- a/packages/core/docs/api/jsdoc/BorderConfig.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Generated from: BorderConfig.json - * Date: 2025-08-22T07:06:25.492Z - */ - - /** - * Represents a BorderCharacters object - * @typedef {object} BorderCharacters - * @property {string} bottomLeft - * @property {string} bottomRight - * @property {string} bottomT - * @property {string} cross - * @property {string} horizontal - * @property {string} leftT - * @property {string} rightT - * @property {string} topLeft - * @property {string} topRight - * @property {string} topT - * @property {string} vertical - */ - - - /** - * Represents a BorderSides string - * @typedef {"top"|"right"|"bottom"|"left"} BorderSides - */ - - - /** - * Represents a BorderStyle string - * @typedef {"single"|"double"|"rounded"|"heavy"} BorderStyle - */ - - - /** - * Represents a ColorInput undefined - * @typedef {undefined} ColorInput - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - diff --git a/packages/core/docs/api/jsdoc/BoxDrawOptions.js b/packages/core/docs/api/jsdoc/BoxDrawOptions.js deleted file mode 100644 index 8fbb699f3..000000000 --- a/packages/core/docs/api/jsdoc/BoxDrawOptions.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Generated from: BoxDrawOptions.json - * Date: 2025-08-22T07:06:25.496Z - */ - - /** - * Represents a BorderCharacters object - * @typedef {object} BorderCharacters - * @property {string} bottomLeft - * @property {string} bottomRight - * @property {string} bottomT - * @property {string} cross - * @property {string} horizontal - * @property {string} leftT - * @property {string} rightT - * @property {string} topLeft - * @property {string} topRight - * @property {string} topT - * @property {string} vertical - */ - - - /** - * Represents a BorderSides string - * @typedef {"top"|"right"|"bottom"|"left"} BorderSides - */ - - - /** - * Represents a BorderStyle string - * @typedef {"single"|"double"|"rounded"|"heavy"} BorderStyle - */ - - - /** - * Represents a ColorInput undefined - * @typedef {undefined} ColorInput - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - diff --git a/packages/core/docs/api/jsdoc/BoxOptions.js b/packages/core/docs/api/jsdoc/BoxOptions.js deleted file mode 100644 index dbf6e3cd4..000000000 --- a/packages/core/docs/api/jsdoc/BoxOptions.js +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Generated from: BoxOptions.json - * Date: 2025-08-22T07:06:25.496Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a BorderCharacters object - * @typedef {object} BorderCharacters - * @property {string} bottomLeft - * @property {string} bottomRight - * @property {string} bottomT - * @property {string} cross - * @property {string} horizontal - * @property {string} leftT - * @property {string} rightT - * @property {string} topLeft - * @property {string} topRight - * @property {string} topT - * @property {string} vertical - */ - - - /** - * Represents a BorderSides string - * @typedef {"top"|"right"|"bottom"|"left"} BorderSides - */ - - - /** - * Represents a BorderStyle string - * @typedef {"single"|"double"|"rounded"|"heavy"} BorderStyle - */ - - - /** - * Represents a ColorInput undefined - * @typedef {undefined} ColorInput - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a MouseEventType string - * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType - */ - - - /** - * Represents a ParsedKey object - * @typedef {object} ParsedKey - * @property {string} [code] - * @property {boolean} ctrl - * @property {boolean} meta - * @property {string} name - * @property {boolean} number - * @property {boolean} option - * @property {string} raw - * @property {string} sequence - * @property {boolean} shift - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - /** - * Represents a Renderable object - * @typedef {object} Renderable - * @property {string} id - * @property {number} num - * @property {*} parent - * @property {boolean} selectable - */ - - - /** - * Represents a ScrollInfo object - * @typedef {object} ScrollInfo - * @property {number} delta - * @property {"up"|"down"|"left"|"right"} direction - */ - - diff --git a/packages/core/docs/api/jsdoc/CliRendererConfig.js b/packages/core/docs/api/jsdoc/CliRendererConfig.js deleted file mode 100644 index 34745eed9..000000000 --- a/packages/core/docs/api/jsdoc/CliRendererConfig.js +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Generated from: CliRendererConfig.json - * Date: 2025-08-22T07:06:25.497Z - */ - - /** - * Represents a ColorInput undefined - * @typedef {undefined} ColorInput - */ - - - /** - * Represents a ConsolePosition string - * @typedef {"top"|"bottom"|"left"|"right"} ConsolePosition - */ - - - /** - * Represents a CursorStyle string - * @typedef {"block"|"line"|"underline"} CursorStyle - */ - - - /** - * Represents a DebugOverlayCorner number - * @typedef {0|1|2|3} DebugOverlayCorner - */ - - - /** - * Represents a Pointer number - * @typedef {number} Pointer - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - /** - * Represents a SocketReadyState string - * @typedef {"opening"|"open"|"readOnly"|"writeOnly"|"closed"} SocketReadyState - */ - - diff --git a/packages/core/docs/api/jsdoc/ConsoleOptions.js b/packages/core/docs/api/jsdoc/ConsoleOptions.js deleted file mode 100644 index 66a41beb1..000000000 --- a/packages/core/docs/api/jsdoc/ConsoleOptions.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Generated from: ConsoleOptions.json - * Date: 2025-08-22T07:06:25.497Z - */ - - /** - * Represents a ColorInput undefined - * @typedef {undefined} ColorInput - */ - - - /** - * Represents a ConsolePosition string - * @typedef {"top"|"bottom"|"left"|"right"} ConsolePosition - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - diff --git a/packages/core/docs/api/jsdoc/ExplosionEffectParameters.js b/packages/core/docs/api/jsdoc/ExplosionEffectParameters.js deleted file mode 100644 index 8a6d06ac1..000000000 --- a/packages/core/docs/api/jsdoc/ExplosionEffectParameters.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Generated from: ExplosionEffectParameters.json - * Date: 2025-08-22T07:06:25.497Z - */ - - /** - * 3D vector. - -see {@link https://github.com/mrdoob/three.js/blob/master/src/math/Vector3.js } - * @typedef {object} Vector3 - * @property {true} isVector3 - * @property {number} x - * @property {number} y - * @property {number} z - */ - - diff --git a/packages/core/docs/api/jsdoc/FrameBufferOptions.js b/packages/core/docs/api/jsdoc/FrameBufferOptions.js deleted file mode 100644 index 367cfabc9..000000000 --- a/packages/core/docs/api/jsdoc/FrameBufferOptions.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Generated from: FrameBufferOptions.json - * Date: 2025-08-22T07:06:25.497Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a MouseEventType string - * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType - */ - - - /** - * Represents a ParsedKey object - * @typedef {object} ParsedKey - * @property {string} [code] - * @property {boolean} ctrl - * @property {boolean} meta - * @property {string} name - * @property {boolean} number - * @property {boolean} option - * @property {string} raw - * @property {string} sequence - * @property {boolean} shift - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - - /** - * Represents a Renderable object - * @typedef {object} Renderable - * @property {string} id - * @property {number} num - * @property {*} parent - * @property {boolean} selectable - */ - - - /** - * Represents a ScrollInfo object - * @typedef {object} ScrollInfo - * @property {number} delta - * @property {"up"|"down"|"left"|"right"} direction - */ - - diff --git a/packages/core/docs/api/jsdoc/InputRenderableOptions.js b/packages/core/docs/api/jsdoc/InputRenderableOptions.js deleted file mode 100644 index c30646614..000000000 --- a/packages/core/docs/api/jsdoc/InputRenderableOptions.js +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Generated from: InputRenderableOptions.json - * Date: 2025-08-22T07:06:25.497Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a ColorInput undefined - * @typedef {undefined} ColorInput - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a MouseEventType string - * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType - */ - - - /** - * Represents a ParsedKey object - * @typedef {object} ParsedKey - * @property {string} [code] - * @property {boolean} ctrl - * @property {boolean} meta - * @property {string} name - * @property {boolean} number - * @property {boolean} option - * @property {string} raw - * @property {string} sequence - * @property {boolean} shift - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - /** - * Represents a Renderable object - * @typedef {object} Renderable - * @property {string} id - * @property {number} num - * @property {*} parent - * @property {boolean} selectable - */ - - - /** - * Represents a ScrollInfo object - * @typedef {object} ScrollInfo - * @property {number} delta - * @property {"up"|"down"|"left"|"right"} direction - */ - - diff --git a/packages/core/docs/api/jsdoc/LayoutOptions.js b/packages/core/docs/api/jsdoc/LayoutOptions.js deleted file mode 100644 index 96a8f04bf..000000000 --- a/packages/core/docs/api/jsdoc/LayoutOptions.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Generated from: LayoutOptions.json - * Date: 2025-08-22T07:06:25.498Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - diff --git a/packages/core/docs/api/jsdoc/RenderableOptions.js b/packages/core/docs/api/jsdoc/RenderableOptions.js deleted file mode 100644 index 1c42e9887..000000000 --- a/packages/core/docs/api/jsdoc/RenderableOptions.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Generated from: RenderableOptions.json - * Date: 2025-08-22T07:06:25.498Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a MouseEventType string - * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType - */ - - - /** - * Represents a ParsedKey object - * @typedef {object} ParsedKey - * @property {string} [code] - * @property {boolean} ctrl - * @property {boolean} meta - * @property {string} name - * @property {boolean} number - * @property {boolean} option - * @property {string} raw - * @property {string} sequence - * @property {boolean} shift - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - - /** - * Represents a Renderable object - * @typedef {object} Renderable - * @property {string} id - * @property {number} num - * @property {*} parent - * @property {boolean} selectable - */ - - - /** - * Represents a ScrollInfo object - * @typedef {object} ScrollInfo - * @property {number} delta - * @property {"up"|"down"|"left"|"right"} direction - */ - - diff --git a/packages/core/docs/api/jsdoc/SelectRenderableOptions.js b/packages/core/docs/api/jsdoc/SelectRenderableOptions.js deleted file mode 100644 index d1874e73c..000000000 --- a/packages/core/docs/api/jsdoc/SelectRenderableOptions.js +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Generated from: SelectRenderableOptions.json - * Date: 2025-08-22T07:06:25.498Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a ColorInput undefined - * @typedef {undefined} ColorInput - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a MouseEventType string - * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType - */ - - - /** - * Represents a ParsedKey object - * @typedef {object} ParsedKey - * @property {string} [code] - * @property {boolean} ctrl - * @property {boolean} meta - * @property {string} name - * @property {boolean} number - * @property {boolean} option - * @property {string} raw - * @property {string} sequence - * @property {boolean} shift - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - /** - * Represents a Renderable object - * @typedef {object} Renderable - * @property {string} id - * @property {number} num - * @property {*} parent - * @property {boolean} selectable - */ - - - /** - * Represents a ScrollInfo object - * @typedef {object} ScrollInfo - * @property {number} delta - * @property {"up"|"down"|"left"|"right"} direction - */ - - - /** - * Represents a SelectOption object - * @typedef {object} SelectOption - * @property {string} description - * @property {string} name - * @property {*} [value] - */ - - diff --git a/packages/core/docs/api/jsdoc/TabSelectRenderableOptions.js b/packages/core/docs/api/jsdoc/TabSelectRenderableOptions.js deleted file mode 100644 index 2f3ea7c88..000000000 --- a/packages/core/docs/api/jsdoc/TabSelectRenderableOptions.js +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Generated from: TabSelectRenderableOptions.json - * Date: 2025-08-22T07:06:25.498Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a ColorInput undefined - * @typedef {undefined} ColorInput - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a MouseEventType string - * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType - */ - - - /** - * Represents a ParsedKey object - * @typedef {object} ParsedKey - * @property {string} [code] - * @property {boolean} ctrl - * @property {boolean} meta - * @property {string} name - * @property {boolean} number - * @property {boolean} option - * @property {string} raw - * @property {string} sequence - * @property {boolean} shift - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - /** - * Represents a Renderable object - * @typedef {object} Renderable - * @property {string} id - * @property {number} num - * @property {*} parent - * @property {boolean} selectable - */ - - - /** - * Represents a ScrollInfo object - * @typedef {object} ScrollInfo - * @property {number} delta - * @property {"up"|"down"|"left"|"right"} direction - */ - - - /** - * Represents a TabSelectOption object - * @typedef {object} TabSelectOption - * @property {string} description - * @property {string} name - * @property {*} [value] - */ - - diff --git a/packages/core/docs/api/jsdoc/TextOptions.js b/packages/core/docs/api/jsdoc/TextOptions.js deleted file mode 100644 index 3eeea6893..000000000 --- a/packages/core/docs/api/jsdoc/TextOptions.js +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Generated from: TextOptions.json - * Date: 2025-08-22T07:06:25.499Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a MouseEventType string - * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType - */ - - - /** - * Represents a ParsedKey object - * @typedef {object} ParsedKey - * @property {string} [code] - * @property {boolean} ctrl - * @property {boolean} meta - * @property {string} name - * @property {boolean} number - * @property {boolean} option - * @property {string} raw - * @property {string} sequence - * @property {boolean} shift - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - /** - * Represents a Renderable object - * @typedef {object} Renderable - * @property {string} id - * @property {number} num - * @property {*} parent - * @property {boolean} selectable - */ - - - /** - * Represents a ScrollInfo object - * @typedef {object} ScrollInfo - * @property {number} delta - * @property {"up"|"down"|"left"|"right"} direction - */ - - - /** - * Represents a StyledText object - * @typedef {object} StyledText - * @property {array} chunks - */ - - diff --git a/packages/core/docs/api/jsdoc/ThreeCliRendererOptions.js b/packages/core/docs/api/jsdoc/ThreeCliRendererOptions.js deleted file mode 100644 index a392e97f0..000000000 --- a/packages/core/docs/api/jsdoc/ThreeCliRendererOptions.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Generated from: ThreeCliRendererOptions.json - * Date: 2025-08-22T07:06:25.499Z - */ - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - /** - * Represents a SuperSampleType string - * @typedef {"none"|"gpu"|"cpu"} SuperSampleType - */ - - diff --git a/packages/core/docs/api/jsdoc/TimelineOptions.js b/packages/core/docs/api/jsdoc/TimelineOptions.js deleted file mode 100644 index cdf0120c9..000000000 --- a/packages/core/docs/api/jsdoc/TimelineOptions.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Generated from: TimelineOptions.json - * Date: 2025-08-22T07:06:25.499Z - */ - - /** - * Represents a TimelineOptions object - * @typedef {object} TimelineOptions - * @property {boolean} [autoplay] - * @property {number} [duration] - * @property {boolean} [loop] - * @property {*} [onComplete] - * @property {*} [onPause] - */ - - diff --git a/packages/core/docs/api/jsdoc/all-types.js b/packages/core/docs/api/jsdoc/all-types.js deleted file mode 100644 index 8e6eb49e8..000000000 --- a/packages/core/docs/api/jsdoc/all-types.js +++ /dev/null @@ -1,1035 +0,0 @@ -/** - * OpenTUI Complete Type Definitions - * Generated from JSON Schemas - * Date: 2025-08-22T07:06:25.499Z - */ - -/** - * Generated from: ASCIIFontOptions.json - * Date: 2025-08-22T07:06:25.491Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a MouseEventType string - * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType - */ - - - /** - * Represents a ParsedKey object - * @typedef {object} ParsedKey - * @property {string} [code] - * @property {boolean} ctrl - * @property {boolean} meta - * @property {string} name - * @property {boolean} number - * @property {boolean} option - * @property {string} raw - * @property {string} sequence - * @property {boolean} shift - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - /** - * Represents a Renderable object - * @typedef {object} Renderable - * @property {string} id - * @property {number} num - * @property {*} parent - * @property {boolean} selectable - */ - - - /** - * Represents a ScrollInfo object - * @typedef {object} ScrollInfo - * @property {number} delta - * @property {"up"|"down"|"left"|"right"} direction - */ - - - - -/** - * Generated from: AnimationOptions.json - * Date: 2025-08-22T07:06:25.492Z - */ - - /** - * Represents a EasingFunctions string - * @typedef {"linear"|"inQuad"|"outQuad"|"inOutQuad"|"inExpo"|"outExpo"|"inOutSine"|"outBounce"|"outElastic"|"inBounce"|"inCirc"|"outCirc"|"inOutCirc"|"inBack"|"outBack"|"inOutBack"} EasingFunctions - */ - - - /** - * Represents a JSAnimation object - * @typedef {object} JSAnimation - * @property {number} currentTime - * @property {number} deltaTime - * @property {number} progress - * @property {array} targets - */ - - - - -/** - * Generated from: BorderConfig.json - * Date: 2025-08-22T07:06:25.492Z - */ - - /** - * Represents a BorderCharacters object - * @typedef {object} BorderCharacters - * @property {string} bottomLeft - * @property {string} bottomRight - * @property {string} bottomT - * @property {string} cross - * @property {string} horizontal - * @property {string} leftT - * @property {string} rightT - * @property {string} topLeft - * @property {string} topRight - * @property {string} topT - * @property {string} vertical - */ - - - /** - * Represents a BorderSides string - * @typedef {"top"|"right"|"bottom"|"left"} BorderSides - */ - - - /** - * Represents a BorderStyle string - * @typedef {"single"|"double"|"rounded"|"heavy"} BorderStyle - */ - - - /** - * Represents a ColorInput undefined - * @typedef {undefined} ColorInput - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - - -/** - * Generated from: BoxDrawOptions.json - * Date: 2025-08-22T07:06:25.496Z - */ - - /** - * Represents a BorderCharacters object - * @typedef {object} BorderCharacters - * @property {string} bottomLeft - * @property {string} bottomRight - * @property {string} bottomT - * @property {string} cross - * @property {string} horizontal - * @property {string} leftT - * @property {string} rightT - * @property {string} topLeft - * @property {string} topRight - * @property {string} topT - * @property {string} vertical - */ - - - /** - * Represents a BorderSides string - * @typedef {"top"|"right"|"bottom"|"left"} BorderSides - */ - - - /** - * Represents a BorderStyle string - * @typedef {"single"|"double"|"rounded"|"heavy"} BorderStyle - */ - - - /** - * Represents a ColorInput undefined - * @typedef {undefined} ColorInput - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - - -/** - * Generated from: BoxOptions.json - * Date: 2025-08-22T07:06:25.496Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a BorderCharacters object - * @typedef {object} BorderCharacters - * @property {string} bottomLeft - * @property {string} bottomRight - * @property {string} bottomT - * @property {string} cross - * @property {string} horizontal - * @property {string} leftT - * @property {string} rightT - * @property {string} topLeft - * @property {string} topRight - * @property {string} topT - * @property {string} vertical - */ - - - /** - * Represents a BorderSides string - * @typedef {"top"|"right"|"bottom"|"left"} BorderSides - */ - - - /** - * Represents a BorderStyle string - * @typedef {"single"|"double"|"rounded"|"heavy"} BorderStyle - */ - - - /** - * Represents a ColorInput undefined - * @typedef {undefined} ColorInput - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a MouseEventType string - * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType - */ - - - /** - * Represents a ParsedKey object - * @typedef {object} ParsedKey - * @property {string} [code] - * @property {boolean} ctrl - * @property {boolean} meta - * @property {string} name - * @property {boolean} number - * @property {boolean} option - * @property {string} raw - * @property {string} sequence - * @property {boolean} shift - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - /** - * Represents a Renderable object - * @typedef {object} Renderable - * @property {string} id - * @property {number} num - * @property {*} parent - * @property {boolean} selectable - */ - - - /** - * Represents a ScrollInfo object - * @typedef {object} ScrollInfo - * @property {number} delta - * @property {"up"|"down"|"left"|"right"} direction - */ - - - - -/** - * Generated from: CliRendererConfig.json - * Date: 2025-08-22T07:06:25.497Z - */ - - /** - * Represents a ColorInput undefined - * @typedef {undefined} ColorInput - */ - - - /** - * Represents a ConsolePosition string - * @typedef {"top"|"bottom"|"left"|"right"} ConsolePosition - */ - - - /** - * Represents a CursorStyle string - * @typedef {"block"|"line"|"underline"} CursorStyle - */ - - - /** - * Represents a DebugOverlayCorner number - * @typedef {0|1|2|3} DebugOverlayCorner - */ - - - /** - * Represents a Pointer number - * @typedef {number} Pointer - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - /** - * Represents a SocketReadyState string - * @typedef {"opening"|"open"|"readOnly"|"writeOnly"|"closed"} SocketReadyState - */ - - - - -/** - * Generated from: ConsoleOptions.json - * Date: 2025-08-22T07:06:25.497Z - */ - - /** - * Represents a ColorInput undefined - * @typedef {undefined} ColorInput - */ - - - /** - * Represents a ConsolePosition string - * @typedef {"top"|"bottom"|"left"|"right"} ConsolePosition - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - - -/** - * Generated from: ExplosionEffectParameters.json - * Date: 2025-08-22T07:06:25.497Z - */ - - /** - * 3D vector. - -see {@link https://github.com/mrdoob/three.js/blob/master/src/math/Vector3.js } - * @typedef {object} Vector3 - * @property {true} isVector3 - * @property {number} x - * @property {number} y - * @property {number} z - */ - - - - -/** - * Generated from: FrameBufferOptions.json - * Date: 2025-08-22T07:06:25.497Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a MouseEventType string - * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType - */ - - - /** - * Represents a ParsedKey object - * @typedef {object} ParsedKey - * @property {string} [code] - * @property {boolean} ctrl - * @property {boolean} meta - * @property {string} name - * @property {boolean} number - * @property {boolean} option - * @property {string} raw - * @property {string} sequence - * @property {boolean} shift - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - - /** - * Represents a Renderable object - * @typedef {object} Renderable - * @property {string} id - * @property {number} num - * @property {*} parent - * @property {boolean} selectable - */ - - - /** - * Represents a ScrollInfo object - * @typedef {object} ScrollInfo - * @property {number} delta - * @property {"up"|"down"|"left"|"right"} direction - */ - - - - -/** - * Generated from: InputRenderableOptions.json - * Date: 2025-08-22T07:06:25.497Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a ColorInput undefined - * @typedef {undefined} ColorInput - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a MouseEventType string - * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType - */ - - - /** - * Represents a ParsedKey object - * @typedef {object} ParsedKey - * @property {string} [code] - * @property {boolean} ctrl - * @property {boolean} meta - * @property {string} name - * @property {boolean} number - * @property {boolean} option - * @property {string} raw - * @property {string} sequence - * @property {boolean} shift - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - /** - * Represents a Renderable object - * @typedef {object} Renderable - * @property {string} id - * @property {number} num - * @property {*} parent - * @property {boolean} selectable - */ - - - /** - * Represents a ScrollInfo object - * @typedef {object} ScrollInfo - * @property {number} delta - * @property {"up"|"down"|"left"|"right"} direction - */ - - - - -/** - * Generated from: LayoutOptions.json - * Date: 2025-08-22T07:06:25.498Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - - - -/** - * Generated from: RenderableOptions.json - * Date: 2025-08-22T07:06:25.498Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a MouseEventType string - * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType - */ - - - /** - * Represents a ParsedKey object - * @typedef {object} ParsedKey - * @property {string} [code] - * @property {boolean} ctrl - * @property {boolean} meta - * @property {string} name - * @property {boolean} number - * @property {boolean} option - * @property {string} raw - * @property {string} sequence - * @property {boolean} shift - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - - /** - * Represents a Renderable object - * @typedef {object} Renderable - * @property {string} id - * @property {number} num - * @property {*} parent - * @property {boolean} selectable - */ - - - /** - * Represents a ScrollInfo object - * @typedef {object} ScrollInfo - * @property {number} delta - * @property {"up"|"down"|"left"|"right"} direction - */ - - - - -/** - * Generated from: SelectRenderableOptions.json - * Date: 2025-08-22T07:06:25.498Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a ColorInput undefined - * @typedef {undefined} ColorInput - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a MouseEventType string - * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType - */ - - - /** - * Represents a ParsedKey object - * @typedef {object} ParsedKey - * @property {string} [code] - * @property {boolean} ctrl - * @property {boolean} meta - * @property {string} name - * @property {boolean} number - * @property {boolean} option - * @property {string} raw - * @property {string} sequence - * @property {boolean} shift - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - /** - * Represents a Renderable object - * @typedef {object} Renderable - * @property {string} id - * @property {number} num - * @property {*} parent - * @property {boolean} selectable - */ - - - /** - * Represents a ScrollInfo object - * @typedef {object} ScrollInfo - * @property {number} delta - * @property {"up"|"down"|"left"|"right"} direction - */ - - - /** - * Represents a SelectOption object - * @typedef {object} SelectOption - * @property {string} description - * @property {string} name - * @property {*} [value] - */ - - - - -/** - * Generated from: TabSelectRenderableOptions.json - * Date: 2025-08-22T07:06:25.498Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a ColorInput undefined - * @typedef {undefined} ColorInput - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a MouseEventType string - * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType - */ - - - /** - * Represents a ParsedKey object - * @typedef {object} ParsedKey - * @property {string} [code] - * @property {boolean} ctrl - * @property {boolean} meta - * @property {string} name - * @property {boolean} number - * @property {boolean} option - * @property {string} raw - * @property {string} sequence - * @property {boolean} shift - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - /** - * Represents a Renderable object - * @typedef {object} Renderable - * @property {string} id - * @property {number} num - * @property {*} parent - * @property {boolean} selectable - */ - - - /** - * Represents a ScrollInfo object - * @typedef {object} ScrollInfo - * @property {number} delta - * @property {"up"|"down"|"left"|"right"} direction - */ - - - /** - * Represents a TabSelectOption object - * @typedef {object} TabSelectOption - * @property {string} description - * @property {string} name - * @property {*} [value] - */ - - - - -/** - * Generated from: TextOptions.json - * Date: 2025-08-22T07:06:25.499Z - */ - - /** - * Represents a AlignString string - * @typedef {"auto"|"flex-start"|"center"|"flex-end"|"stretch"|"baseline"|"space-between"|"space-around"|"space-evenly"} AlignString - */ - - - /** - * Represents a FlexDirectionString string - * @typedef {"column"|"column-reverse"|"row"|"row-reverse"} FlexDirectionString - */ - - - /** - * Represents a JustifyString string - * @typedef {"flex-start"|"center"|"flex-end"|"space-between"|"space-around"|"space-evenly"} JustifyString - */ - - - /** - * Represents a MouseEventType string - * @typedef {"down"|"up"|"move"|"drag"|"drag-end"|"drop"|"over"|"out"|"scroll"} MouseEventType - */ - - - /** - * Represents a ParsedKey object - * @typedef {object} ParsedKey - * @property {string} [code] - * @property {boolean} ctrl - * @property {boolean} meta - * @property {string} name - * @property {boolean} number - * @property {boolean} option - * @property {string} raw - * @property {string} sequence - * @property {boolean} shift - */ - - - /** - * Represents a PositionTypeString string - * @typedef {"static"|"relative"|"absolute"} PositionTypeString - */ - - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - /** - * Represents a Renderable object - * @typedef {object} Renderable - * @property {string} id - * @property {number} num - * @property {*} parent - * @property {boolean} selectable - */ - - - /** - * Represents a ScrollInfo object - * @typedef {object} ScrollInfo - * @property {number} delta - * @property {"up"|"down"|"left"|"right"} direction - */ - - - /** - * Represents a StyledText object - * @typedef {object} StyledText - * @property {array} chunks - */ - - - - -/** - * Generated from: ThreeCliRendererOptions.json - * Date: 2025-08-22T07:06:25.499Z - */ - - /** - * Represents a RGBA object - * @typedef {object} RGBA - * @property {object} buffer - * @property {number} buffer.BYTES_PER_ELEMENT - * @property {object} buffer.buffer - * @property {number} buffer.buffer.byteLength - * @property {number} buffer.byteLength - * @property {number} buffer.byteOffset - * @property {number} buffer.length - */ - - - /** - * Represents a SuperSampleType string - * @typedef {"none"|"gpu"|"cpu"} SuperSampleType - */ - - - - -/** - * Generated from: TimelineOptions.json - * Date: 2025-08-22T07:06:25.499Z - */ - - /** - * Represents a TimelineOptions object - * @typedef {object} TimelineOptions - * @property {boolean} [autoplay] - * @property {number} [duration] - * @property {boolean} [loop] - * @property {*} [onComplete] - * @property {*} [onPause] - */ - - diff --git a/packages/core/docs/api/lib/border.md b/packages/core/docs/api/lib/border.md deleted file mode 100644 index 5b1a23b9c..000000000 --- a/packages/core/docs/api/lib/border.md +++ /dev/null @@ -1,489 +0,0 @@ -# Border Styles - -OpenTUI provides a variety of border styles for creating visually appealing terminal user interfaces. - -## Overview - -Borders are an important visual element in terminal user interfaces, helping to define and separate different areas of the screen. OpenTUI provides several built-in border styles and allows you to create custom border styles. - -## Border Style API - -### Border Style Types - -OpenTUI supports the following border style types: - -| Style | Description | -|-------|-------------| -| `'none'` | No border | -| `'single'` | Single-line border | -| `'double'` | Double-line border | -| `'rounded'` | Rounded corners with single lines | -| `'dashed'` | Dashed border | -| `'thick'` | Thick border | -| `'block'` | Block border | -| `'custom'` | Custom border defined by the user | - -### Using Border Styles - -```typescript -import { BoxRenderable } from '@opentui/core'; - -// Create a box with a single-line border -const singleBox = new BoxRenderable('single-box', { - width: 20, - height: 5, - borderStyle: 'single', - borderColor: '#ffffff' -}); - -// Create a box with a double-line border -const doubleBox = new BoxRenderable('double-box', { - width: 20, - height: 5, - borderStyle: 'double', - borderColor: '#3498db' -}); - -// Create a box with a rounded border -const roundedBox = new BoxRenderable('rounded-box', { - width: 20, - height: 5, - borderStyle: 'rounded', - borderColor: '#2ecc71' -}); - -// Create a box with a dashed border -const dashedBox = new BoxRenderable('dashed-box', { - width: 20, - height: 5, - borderStyle: 'dashed', - borderColor: '#e74c3c' -}); - -// Create a box with a thick border -const thickBox = new BoxRenderable('thick-box', { - width: 20, - height: 5, - borderStyle: 'thick', - borderColor: '#f39c12' -}); - -// Create a box with a block border -const blockBox = new BoxRenderable('block-box', { - width: 20, - height: 5, - borderStyle: 'block', - borderColor: '#9b59b6' -}); -``` - -### Border Characters - -Each border style defines a set of characters for different parts of the border: - -```typescript -interface BorderChars { - topLeft: string; // Top-left corner - topRight: string; // Top-right corner - bottomLeft: string; // Bottom-left corner - bottomRight: string; // Bottom-right corner - horizontal: string; // Horizontal line - vertical: string; // Vertical line - left: string; // Left T-junction - right: string; // Right T-junction - top: string; // Top T-junction - bottom: string; // Bottom T-junction - cross: string; // Cross junction -} -``` - -### Creating Custom Border Styles - -You can create custom border styles by defining your own border characters: - -```typescript -import { BoxRenderable, registerBorderStyle } from '@opentui/core'; - -// Register a custom border style -registerBorderStyle('stars', { - topLeft: '*', - topRight: '*', - bottomLeft: '*', - bottomRight: '*', - horizontal: '*', - vertical: '*', - left: '*', - right: '*', - top: '*', - bottom: '*', - cross: '*' -}); - -// Use the custom border style -const starsBox = new BoxRenderable('stars-box', { - width: 20, - height: 5, - borderStyle: 'stars', - borderColor: '#f1c40f' -}); -``` - -### Getting Border Characters - -You can get the border characters for a specific style: - -```typescript -import { getBorderChars } from '@opentui/core'; - -// Get the border characters for the 'single' style -const singleBorderChars = getBorderChars('single'); -console.log(singleBorderChars); -``` - -## Example: Creating a Panel with a Title - -```typescript -import { BoxRenderable, TextRenderable } from '@opentui/core'; - -// Create a panel with a title -function createPanel(id: string, title: string, options = {}) { - const panel = new BoxRenderable(id, { - width: 40, - height: 10, - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222', - ...options - }); - - // Create a title bar - const titleBar = new BoxRenderable(`${id}-title-bar`, { - width: '100%', - height: 3, - borderStyle: 'none', - backgroundColor: '#3498db' - }); - - // Create a title text - const titleText = new TextRenderable(`${id}-title-text`, { - content: title, - fg: '#ffffff', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 - }); - - // Create a content area - const contentArea = new BoxRenderable(`${id}-content-area`, { - width: '100%', - height: 'calc(100% - 3)', - y: 3, - borderStyle: 'none', - backgroundColor: 'transparent', - padding: 1 - }); - - // Build the component tree - titleBar.add(titleText); - panel.add(titleBar); - panel.add(contentArea); - - return { - panel, - contentArea - }; -} - -// Usage -const { panel, contentArea } = createPanel('my-panel', 'My Panel'); - -// Add content to the panel -const content = new TextRenderable('content', { - content: 'This is the panel content.', - fg: '#ffffff', - flexGrow: 1 -}); - -contentArea.add(content); -``` - -## Example: Creating a Dialog Box - -```typescript -import { BoxRenderable, TextRenderable } from '@opentui/core'; - -// Create a dialog box -function createDialog(id: string, title: string, message: string, options = {}) { - const dialog = new BoxRenderable(id, { - width: 50, - height: 15, - borderStyle: 'double', - borderColor: '#3498db', - backgroundColor: '#222222', - position: 'absolute', - x: 'center', - y: 'center', - ...options - }); - - // Create a title bar - const titleBar = new BoxRenderable(`${id}-title-bar`, { - width: '100%', - height: 3, - borderStyle: 'none', - backgroundColor: '#3498db' - }); - - // Create a title text - const titleText = new TextRenderable(`${id}-title-text`, { - content: title, - fg: '#ffffff', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 - }); - - // Create a message area - const messageArea = new BoxRenderable(`${id}-message-area`, { - width: '100%', - height: 'calc(100% - 6)', - y: 3, - borderStyle: 'none', - backgroundColor: 'transparent', - padding: 1 - }); - - // Create a message text - const messageText = new TextRenderable(`${id}-message-text`, { - content: message, - fg: '#ffffff', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 - }); - - // Create a button area - const buttonArea = new BoxRenderable(`${id}-button-area`, { - width: '100%', - height: 3, - y: 'calc(100% - 3)', - borderStyle: 'none', - backgroundColor: 'transparent', - padding: 1, - flexDirection: 'row', - justifyContent: 'flex-end', - alignItems: 'center' - }); - - // Create an OK button - const okButton = new BoxRenderable(`${id}-ok-button`, { - width: 10, - height: 1, - borderStyle: 'single', - borderColor: '#2ecc71', - backgroundColor: 'transparent', - marginRight: 1 - }); - - // Create an OK button text - const okButtonText = new TextRenderable(`${id}-ok-button-text`, { - content: 'OK', - fg: '#2ecc71', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 - }); - - // Create a Cancel button - const cancelButton = new BoxRenderable(`${id}-cancel-button`, { - width: 10, - height: 1, - borderStyle: 'single', - borderColor: '#e74c3c', - backgroundColor: 'transparent' - }); - - // Create a Cancel button text - const cancelButtonText = new TextRenderable(`${id}-cancel-button-text`, { - content: 'Cancel', - fg: '#e74c3c', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 - }); - - // Build the component tree - titleBar.add(titleText); - messageArea.add(messageText); - okButton.add(okButtonText); - cancelButton.add(cancelButtonText); - buttonArea.add(okButton); - buttonArea.add(cancelButton); - dialog.add(titleBar); - dialog.add(messageArea); - dialog.add(buttonArea); - - // Make the buttons focusable - okButton.focusable = true; - cancelButton.focusable = true; - - // Focus the OK button by default - okButton.focus(); - - // Handle button clicks - okButton.on('click', () => { - dialog.emit('ok'); - }); - - cancelButton.on('click', () => { - dialog.emit('cancel'); - }); - - return dialog; -} - -// Usage -const dialog = createDialog('my-dialog', 'Confirmation', 'Are you sure you want to proceed?'); - -// Handle dialog events -dialog.on('ok', () => { - console.log('OK button clicked'); - dialog.remove(); -}); - -dialog.on('cancel', () => { - console.log('Cancel button clicked'); - dialog.remove(); -}); -``` - -## Example: Creating a Tabbed Interface - -```typescript -import { BoxRenderable, TextRenderable } from '@opentui/core'; - -// Create a tabbed interface -function createTabbedInterface(id: string, tabs: string[], options = {}) { - const container = new BoxRenderable(id, { - width: 60, - height: 20, - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222', - ...options - }); - - // Create a tab bar - const tabBar = new BoxRenderable(`${id}-tab-bar`, { - width: '100%', - height: 3, - borderStyle: 'none', - backgroundColor: 'transparent', - flexDirection: 'row' - }); - - // Create a content area - const contentArea = new BoxRenderable(`${id}-content-area`, { - width: '100%', - height: 'calc(100% - 3)', - y: 3, - borderStyle: 'none', - backgroundColor: 'transparent', - padding: 1 - }); - - // Create tab buttons and content panels - const tabButtons: BoxRenderable[] = []; - const contentPanels: BoxRenderable[] = []; - - tabs.forEach((tab, index) => { - // Create a tab button - const tabButton = new BoxRenderable(`${id}-tab-${index}`, { - width: Math.floor(100 / tabs.length) + '%', - height: '100%', - borderStyle: index === 0 ? 'bottom-none' : 'single', - borderColor: index === 0 ? '#3498db' : '#bbbbbb', - backgroundColor: index === 0 ? '#222222' : 'transparent' - }); - - // Create a tab button text - const tabButtonText = new TextRenderable(`${id}-tab-${index}-text`, { - content: tab, - fg: index === 0 ? '#ffffff' : '#bbbbbb', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 - }); - - // Create a content panel - const contentPanel = new BoxRenderable(`${id}-content-${index}`, { - width: '100%', - height: '100%', - borderStyle: 'none', - backgroundColor: 'transparent', - visible: index === 0 - }); - - // Create a content panel text - const contentPanelText = new TextRenderable(`${id}-content-${index}-text`, { - content: `Content for ${tab}`, - fg: '#ffffff', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 - }); - - // Build the component tree - tabButton.add(tabButtonText); - contentPanel.add(contentPanelText); - - tabBar.add(tabButton); - contentArea.add(contentPanel); - - tabButtons.push(tabButton); - contentPanels.push(contentPanel); - - // Handle tab button clicks - tabButton.on('click', () => { - // Update tab buttons - tabButtons.forEach((button, i) => { - button.borderStyle = i === index ? 'bottom-none' : 'single'; - button.borderColor = i === index ? '#3498db' : '#bbbbbb'; - button.backgroundColor = i === index ? '#222222' : 'transparent'; - button.children[0].fg = i === index ? '#ffffff' : '#bbbbbb'; - }); - - // Update content panels - contentPanels.forEach((panel, i) => { - panel.visible = i === index; - }); - }); - }); - - // Build the main component tree - container.add(tabBar); - container.add(contentArea); - - return { - container, - tabButtons, - contentPanels - }; -} - -// Usage -const { container, tabButtons, contentPanels } = createTabbedInterface('my-tabs', ['Tab 1', 'Tab 2', 'Tab 3']); - -// Add custom content to a tab -const customContent = new TextRenderable('custom-content', { - content: 'This is custom content for Tab 2', - fg: '#2ecc71', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 -}); - -// Replace the default content -contentPanels[1].children = []; -contentPanels[1].add(customContent); -``` diff --git a/packages/core/docs/api/lib/hast-styled-text.md b/packages/core/docs/api/lib/hast-styled-text.md deleted file mode 100644 index ccd74d699..000000000 --- a/packages/core/docs/api/lib/hast-styled-text.md +++ /dev/null @@ -1,481 +0,0 @@ -# HAST Styled Text - -OpenTUI provides support for Hypertext Abstract Syntax Tree (HAST) for complex text styling, allowing you to create rich text with different styles using a tree-based structure. - -## Overview - -The HAST Styled Text system consists of: - -1. **HASTNode**: A tree structure representing styled text -2. **SyntaxStyle**: A class for defining and merging text styles -3. **hastToStyledText**: A function for converting HAST to StyledText - -## HAST Structure - -HAST (Hypertext Abstract Syntax Tree) is a tree structure that represents HTML-like markup. In OpenTUI, it's used to represent styled text with nested elements and classes. - -```typescript -import { HASTNode, HASTElement, HASTText } from '@opentui/core'; - -// A text node -const textNode: HASTText = { - type: 'text', - value: 'Hello, world!' -}; - -// An element node with a class -const elementNode: HASTElement = { - type: 'element', - tagName: 'span', - properties: { - className: 'keyword' - }, - children: [ - { - type: 'text', - value: 'function' - } - ] -}; - -// A complex HAST tree -const hastTree: HASTNode = { - type: 'element', - tagName: 'div', - children: [ - { - type: 'element', - tagName: 'span', - properties: { - className: 'keyword' - }, - children: [ - { - type: 'text', - value: 'function' - } - ] - }, - { - type: 'text', - value: ' ' - }, - { - type: 'element', - tagName: 'span', - properties: { - className: 'function-name' - }, - children: [ - { - type: 'text', - value: 'example' - } - ] - }, - { - type: 'text', - value: '() {' - } - ] -}; -``` - -## SyntaxStyle API - -The `SyntaxStyle` class defines styles for different class names and provides methods for merging styles: - -```typescript -import { SyntaxStyle, StyleDefinition, RGBA } from '@opentui/core'; - -// Define styles for different classes -const styles: Record = { - default: { - fg: RGBA.fromHex('#ffffff') - }, - keyword: { - fg: RGBA.fromHex('#569cd6'), - bold: true - }, - 'function-name': { - fg: RGBA.fromHex('#dcdcaa') - }, - string: { - fg: RGBA.fromHex('#ce9178') - }, - comment: { - fg: RGBA.fromHex('#6a9955'), - italic: true - } -}; - -// Create a syntax style -const syntaxStyle = new SyntaxStyle(styles); - -// Merge styles -const mergedStyle = syntaxStyle.mergeStyles('keyword', 'bold'); - -// Clear the style cache -syntaxStyle.clearCache(); - -// Get the cache size -const cacheSize = syntaxStyle.getCacheSize(); -``` - -## Converting HAST to StyledText - -The `hastToStyledText` function converts a HAST tree to a `StyledText` instance: - -```typescript -import { hastToStyledText, SyntaxStyle, HASTNode } from '@opentui/core'; - -// Define a syntax style -const syntaxStyle = new SyntaxStyle({ - default: { - fg: RGBA.fromHex('#ffffff') - }, - keyword: { - fg: RGBA.fromHex('#569cd6'), - bold: true - }, - 'function-name': { - fg: RGBA.fromHex('#dcdcaa') - } -}); - -// Define a HAST tree -const hast: HASTNode = { - type: 'element', - tagName: 'div', - children: [ - { - type: 'element', - tagName: 'span', - properties: { - className: 'keyword' - }, - children: [ - { - type: 'text', - value: 'function' - } - ] - }, - { - type: 'text', - value: ' ' - }, - { - type: 'element', - tagName: 'span', - properties: { - className: 'function-name' - }, - children: [ - { - type: 'text', - value: 'example' - } - ] - } - ] -}; - -// Convert HAST to StyledText -const styledText = hastToStyledText(hast, syntaxStyle); - -// Use the styled text -console.log(styledText.toString()); -``` - -## Example: Syntax Highlighting - -Here's an example of using HAST Styled Text for syntax highlighting: - -```typescript -import { - SyntaxStyle, - HASTNode, - hastToStyledText, - RGBA, - TextRenderable -} from '@opentui/core'; - -// Define a syntax style for JavaScript -const jsStyle = new SyntaxStyle({ - default: { - fg: RGBA.fromHex('#d4d4d4') - }, - keyword: { - fg: RGBA.fromHex('#569cd6'), - bold: true - }, - 'function-name': { - fg: RGBA.fromHex('#dcdcaa') - }, - string: { - fg: RGBA.fromHex('#ce9178') - }, - number: { - fg: RGBA.fromHex('#b5cea8') - }, - comment: { - fg: RGBA.fromHex('#6a9955'), - italic: true - }, - punctuation: { - fg: RGBA.fromHex('#d4d4d4') - } -}); - -// Create a HAST tree for JavaScript code -const jsCode: HASTNode = { - type: 'element', - tagName: 'div', - children: [ - { - type: 'element', - tagName: 'span', - properties: { - className: 'comment' - }, - children: [ - { - type: 'text', - value: '// Example function\n' - } - ] - }, - { - type: 'element', - tagName: 'span', - properties: { - className: 'keyword' - }, - children: [ - { - type: 'text', - value: 'function' - } - ] - }, - { - type: 'text', - value: ' ' - }, - { - type: 'element', - tagName: 'span', - properties: { - className: 'function-name' - }, - children: [ - { - type: 'text', - value: 'calculateSum' - } - ] - }, - { - type: 'element', - tagName: 'span', - properties: { - className: 'punctuation' - }, - children: [ - { - type: 'text', - value: '(' - } - ] - }, - { - type: 'element', - tagName: 'span', - properties: { - className: 'parameter' - }, - children: [ - { - type: 'text', - value: 'a, b' - } - ] - }, - { - type: 'element', - tagName: 'span', - properties: { - className: 'punctuation' - }, - children: [ - { - type: 'text', - value: ') {\n ' - } - ] - }, - { - type: 'element', - tagName: 'span', - properties: { - className: 'keyword' - }, - children: [ - { - type: 'text', - value: 'return' - } - ] - }, - { - type: 'text', - value: ' a + b;\n' - }, - { - type: 'element', - tagName: 'span', - properties: { - className: 'punctuation' - }, - children: [ - { - type: 'text', - value: '}' - } - ] - } - ] -}; - -// Convert HAST to StyledText -const styledCode = hastToStyledText(jsCode, jsStyle); - -// Create a text renderable with the styled text -const codeBlock = new TextRenderable('code-block', { - styledContent: styledCode, - borderStyle: 'single', - borderColor: '#3498db', - padding: 1 -}); - -// Add to the renderer -renderer.root.add(codeBlock); -``` - -## Example: Creating a Syntax Highlighter - -Here's an example of creating a simple syntax highlighter that generates HAST from code: - -```typescript -import { - SyntaxStyle, - HASTNode, - HASTElement, - HASTText, - hastToStyledText, - RGBA -} from '@opentui/core'; - -// Define a simple JavaScript syntax highlighter -function highlightJS(code: string): HASTNode { - const root: HASTElement = { - type: 'element', - tagName: 'div', - children: [] - }; - - // Simple regex-based tokenization - const tokens = code.match(/\/\/.*|\/\*[\s\S]*?\*\/|\b(function|return|const|let|var|if|else|for|while)\b|"[^"]*"|'[^']*'|\d+|\w+|[^\s\w]+/g) || []; - - for (const token of tokens) { - let element: HASTNode; - - if (/^(function|return|const|let|var|if|else|for|while)$/.test(token)) { - // Keywords - element = { - type: 'element', - tagName: 'span', - properties: { className: 'keyword' }, - children: [{ type: 'text', value: token }] - }; - } else if (/^\/\/.*/.test(token) || /^\/\*[\s\S]*?\*\/$/.test(token)) { - // Comments - element = { - type: 'element', - tagName: 'span', - properties: { className: 'comment' }, - children: [{ type: 'text', value: token }] - }; - } else if (/^"[^"]*"$/.test(token) || /^'[^']*'$/.test(token)) { - // Strings - element = { - type: 'element', - tagName: 'span', - properties: { className: 'string' }, - children: [{ type: 'text', value: token }] - }; - } else if (/^\d+$/.test(token)) { - // Numbers - element = { - type: 'element', - tagName: 'span', - properties: { className: 'number' }, - children: [{ type: 'text', value: token }] - }; - } else if (/^[^\s\w]+$/.test(token)) { - // Punctuation - element = { - type: 'element', - tagName: 'span', - properties: { className: 'punctuation' }, - children: [{ type: 'text', value: token }] - }; - } else if (/^\w+$/.test(token)) { - // Identifiers - element = { - type: 'element', - tagName: 'span', - properties: { className: 'identifier' }, - children: [{ type: 'text', value: token }] - }; - } else { - // Plain text - element = { type: 'text', value: token }; - } - - root.children.push(element); - } - - return root; -} - -// Usage -const code = ` -// Example function -function calculateSum(a, b) { - return a + b; -} -`; - -const jsStyle = new SyntaxStyle({ - default: { fg: RGBA.fromHex('#d4d4d4') }, - keyword: { fg: RGBA.fromHex('#569cd6'), bold: true }, - comment: { fg: RGBA.fromHex('#6a9955'), italic: true }, - string: { fg: RGBA.fromHex('#ce9178') }, - number: { fg: RGBA.fromHex('#b5cea8') }, - punctuation: { fg: RGBA.fromHex('#d4d4d4') }, - identifier: { fg: RGBA.fromHex('#9cdcfe') } -}); - -const hastTree = highlightJS(code); -const styledText = hastToStyledText(hastTree, jsStyle); - -// Create a text renderable with the styled text -const codeBlock = new TextRenderable('code-block', { - styledContent: styledText, - borderStyle: 'single', - borderColor: '#3498db', - padding: 1 -}); -``` diff --git a/packages/core/docs/api/lib/keyhandler.md b/packages/core/docs/api/lib/keyhandler.md deleted file mode 100644 index 497a189bd..000000000 --- a/packages/core/docs/api/lib/keyhandler.md +++ /dev/null @@ -1,432 +0,0 @@ -# Key Handler - -The `KeyHandler` class provides a high-level API for handling keyboard input in OpenTUI applications. - -## Overview - -The key handler processes raw key events from the terminal and provides a structured representation of keyboard input, including support for key combinations and special keys. - -## Key Handler API - -### Getting the Key Handler - -```typescript -import { getKeyHandler } from '@opentui/core'; - -// Get the global key handler -const keyHandler = getKeyHandler(); -``` - -### Listening for Key Events - -```typescript -// Listen for keypress events -keyHandler.on('keypress', (key) => { - console.log('Key pressed:', key.name); - - if (key.ctrl && key.name === 'c') { - console.log('Ctrl+C pressed'); - } -}); -``` - -### ParsedKey Interface - -The `ParsedKey` interface provides a structured representation of keyboard input: - -```typescript -interface ParsedKey { - sequence: string; // Raw key sequence - name: string; // Key name (e.g., 'a', 'return', 'escape') - ctrl: boolean; // Whether Ctrl was pressed - meta: boolean; // Whether Meta/Alt was pressed - shift: boolean; // Whether Shift was pressed - option: boolean; // Whether Option/Alt was pressed - number: boolean; // Whether this is a number key - raw: string; // Raw key data - code?: string; // Key code for special keys -} -``` - -### Parsing Key Sequences - -You can manually parse key sequences using the `parseKeypress` function: - -```typescript -import { parseKeypress } from '@opentui/core'; - -// Parse a key sequence -const key = parseKeypress('\x1b[A'); // Up arrow key -console.log(key); // { name: 'up', sequence: '\x1b[A', ... } -``` - -## Common Key Names - -Here are some common key names you can check for: - -| Category | Key Names | -|----------|-----------| -| Letters | `'a'` through `'z'` | -| Numbers | `'0'` through `'9'` | -| Special | `'space'`, `'backspace'`, `'tab'`, `'return'`, `'escape'` | -| Function | `'f1'` through `'f12'` | -| Navigation | `'up'`, `'down'`, `'left'`, `'right'`, `'home'`, `'end'`, `'pageup'`, `'pagedown'` | -| Editing | `'delete'`, `'insert'` | - -## Example: Handling Keyboard Shortcuts - -```typescript -import { getKeyHandler } from '@opentui/core'; - -// Define a keyboard shortcut handler -function setupGlobalShortcuts() { - const keyHandler = getKeyHandler(); - - const shortcuts = { - 'ctrl+q': () => { - console.log('Quit application'); - process.exit(0); - }, - 'ctrl+s': () => { - console.log('Save action'); - }, - 'ctrl+o': () => { - console.log('Open action'); - }, - 'f1': () => { - console.log('Show help'); - } - }; - - keyHandler.on('keypress', (key) => { - // Build a key identifier string - let keyId = ''; - if (key.ctrl) keyId += 'ctrl+'; - if (key.meta) keyId += 'alt+'; - if (key.shift) keyId += 'shift+'; - keyId += key.name; - - // Check if we have a handler for this shortcut - if (shortcuts[keyId]) { - shortcuts[keyId](); - } - }); -} - -// Call this function to set up global shortcuts -setupGlobalShortcuts(); -``` - -## Example: Creating a Key-Based Navigation System - -```typescript -import { getKeyHandler, BoxRenderable } from '@opentui/core'; - -class KeyboardNavigationManager { - private focusableElements: BoxRenderable[] = []; - private currentFocusIndex: number = -1; - - constructor() { - const keyHandler = getKeyHandler(); - - keyHandler.on('keypress', (key) => { - if (key.name === 'tab') { - if (key.shift) { - this.focusPrevious(); - } else { - this.focusNext(); - } - } else if (key.name === 'up') { - this.focusUp(); - } else if (key.name === 'down') { - this.focusDown(); - } else if (key.name === 'left') { - this.focusLeft(); - } else if (key.name === 'right') { - this.focusRight(); - } - }); - } - - public addFocusableElement(element: BoxRenderable): void { - this.focusableElements.push(element); - element.focusable = true; - - if (this.currentFocusIndex === -1) { - this.currentFocusIndex = 0; - element.focus(); - } - } - - public removeFocusableElement(element: BoxRenderable): void { - const index = this.focusableElements.indexOf(element); - if (index !== -1) { - this.focusableElements.splice(index, 1); - - if (this.currentFocusIndex >= this.focusableElements.length) { - this.currentFocusIndex = this.focusableElements.length - 1; - } - - if (this.currentFocusIndex !== -1) { - this.focusableElements[this.currentFocusIndex].focus(); - } - } - } - - public focusNext(): void { - if (this.focusableElements.length === 0) return; - - if (this.currentFocusIndex !== -1) { - this.focusableElements[this.currentFocusIndex].blur(); - } - - this.currentFocusIndex = (this.currentFocusIndex + 1) % this.focusableElements.length; - this.focusableElements[this.currentFocusIndex].focus(); - } - - public focusPrevious(): void { - if (this.focusableElements.length === 0) return; - - if (this.currentFocusIndex !== -1) { - this.focusableElements[this.currentFocusIndex].blur(); - } - - this.currentFocusIndex = (this.currentFocusIndex - 1 + this.focusableElements.length) % this.focusableElements.length; - this.focusableElements[this.currentFocusIndex].focus(); - } - - public focusUp(): void { - if (this.focusableElements.length === 0 || this.currentFocusIndex === -1) return; - - const currentElement = this.focusableElements[this.currentFocusIndex]; - let closestElement: BoxRenderable | null = null; - let closestDistance = Infinity; - - for (const element of this.focusableElements) { - if (element === currentElement) continue; - - // Check if the element is above the current element - if (element.y + element.height <= currentElement.y) { - const horizontalDistance = Math.abs((element.x + element.width / 2) - (currentElement.x + currentElement.width / 2)); - const verticalDistance = currentElement.y - (element.y + element.height); - const distance = Math.sqrt(horizontalDistance * horizontalDistance + verticalDistance * verticalDistance); - - if (distance < closestDistance) { - closestDistance = distance; - closestElement = element; - } - } - } - - if (closestElement) { - currentElement.blur(); - this.currentFocusIndex = this.focusableElements.indexOf(closestElement); - closestElement.focus(); - } - } - - public focusDown(): void { - // Similar to focusUp but for elements below - // ... - } - - public focusLeft(): void { - // Similar to focusUp but for elements to the left - // ... - } - - public focusRight(): void { - // Similar to focusUp but for elements to the right - // ... - } -} - -// Usage -const navigationManager = new KeyboardNavigationManager(); - -// Add focusable elements -navigationManager.addFocusableElement(button1); -navigationManager.addFocusableElement(button2); -navigationManager.addFocusableElement(inputField); -``` - -## Example: Creating a Text Editor with Keyboard Shortcuts - -```typescript -import { createCliRenderer, BoxRenderable, TextRenderable, getKeyHandler } from '@opentui/core'; - -class TextEditor extends BoxRenderable { - private content: string = ''; - private cursor: { row: number, col: number } = { row: 0, col: 0 }; - private lines: string[] = ['']; - private textDisplay: TextRenderable; - - constructor(id: string, options = {}) { - super(id, { - width: 60, - height: 20, - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222', - padding: 1, - ...options - }); - - this.focusable = true; - - this.textDisplay = new TextRenderable(`${id}-text`, { - content: '', - fg: '#ffffff', - flexGrow: 1 - }); - - this.add(this.textDisplay); - - // Set up key handler - const keyHandler = getKeyHandler(); - - keyHandler.on('keypress', (key) => { - if (!this.isFocused) return; - - if (key.name === 'return') { - this.insertNewline(); - } else if (key.name === 'backspace') { - this.deleteCharacter(); - } else if (key.name === 'delete') { - this.deleteCharacterForward(); - } else if (key.name === 'left') { - this.moveCursorLeft(); - } else if (key.name === 'right') { - this.moveCursorRight(); - } else if (key.name === 'up') { - this.moveCursorUp(); - } else if (key.name === 'down') { - this.moveCursorDown(); - } else if (key.name === 'home') { - this.moveCursorToLineStart(); - } else if (key.name === 'end') { - this.moveCursorToLineEnd(); - } else if (key.ctrl && key.name === 'a') { - this.moveCursorToLineStart(); - } else if (key.ctrl && key.name === 'e') { - this.moveCursorToLineEnd(); - } else if (key.ctrl && key.name === 'k') { - this.deleteToEndOfLine(); - } else if (key.ctrl && key.name === 'u') { - this.deleteToStartOfLine(); - } else if (key.name.length === 1) { - this.insertCharacter(key.name); - } - - this.updateDisplay(); - }); - } - - private updateDisplay(): void { - // Create a copy of the lines with the cursor - const displayLines = [...this.lines]; - const cursorLine = displayLines[this.cursor.row]; - - // Insert cursor character - displayLines[this.cursor.row] = - cursorLine.substring(0, this.cursor.col) + - '█' + - cursorLine.substring(this.cursor.col); - - // Update the text display - this.textDisplay.content = displayLines.join('\n'); - } - - private insertCharacter(char: string): void { - const line = this.lines[this.cursor.row]; - this.lines[this.cursor.row] = - line.substring(0, this.cursor.col) + - char + - line.substring(this.cursor.col); - - this.cursor.col++; - } - - private insertNewline(): void { - const line = this.lines[this.cursor.row]; - const newLine = line.substring(this.cursor.col); - this.lines[this.cursor.row] = line.substring(0, this.cursor.col); - this.lines.splice(this.cursor.row + 1, 0, newLine); - - this.cursor.row++; - this.cursor.col = 0; - } - - private deleteCharacter(): void { - if (this.cursor.col > 0) { - // Delete character before cursor - const line = this.lines[this.cursor.row]; - this.lines[this.cursor.row] = - line.substring(0, this.cursor.col - 1) + - line.substring(this.cursor.col); - - this.cursor.col--; - } else if (this.cursor.row > 0) { - // Join with previous line - const previousLine = this.lines[this.cursor.row - 1]; - const currentLine = this.lines[this.cursor.row]; - - this.cursor.col = previousLine.length; - this.lines[this.cursor.row - 1] = previousLine + currentLine; - this.lines.splice(this.cursor.row, 1); - - this.cursor.row--; - } - } - - // ... other editing methods ... - - public focus(): void { - super.focus(); - this.borderColor = '#2ecc71'; - this.updateDisplay(); - } - - public blur(): void { - super.blur(); - this.borderColor = '#3498db'; - this.updateDisplay(); - } - - public getText(): string { - return this.lines.join('\n'); - } - - public setText(text: string): void { - this.lines = text.split('\n'); - if (this.lines.length === 0) { - this.lines = ['']; - } - - this.cursor = { row: 0, col: 0 }; - this.updateDisplay(); - } -} - -// Usage -async function createTextEditorDemo() { - const renderer = await createCliRenderer(); - const { root } = renderer; - - const editor = new TextEditor('editor', { - x: 10, - y: 5, - width: 60, - height: 20 - }); - - root.add(editor); - editor.focus(); - - renderer.start(); - - return renderer; -} - -createTextEditorDemo().catch(console.error); -``` diff --git a/packages/core/docs/api/lib/selection.md b/packages/core/docs/api/lib/selection.md deleted file mode 100644 index 7b8ccea40..000000000 --- a/packages/core/docs/api/lib/selection.md +++ /dev/null @@ -1,284 +0,0 @@ -# Text Selection System - -OpenTUI provides a comprehensive text selection system that allows users to select and copy text from the terminal interface. - -## Overview - -The text selection system consists of three main classes: - -1. **Selection**: Manages the global selection state with anchor and focus points -2. **TextSelectionHelper**: Handles text selection for standard text components -3. **ASCIIFontSelectionHelper**: Handles text selection for ASCII font components - -## Selection API - -The `Selection` class represents a selection area in the terminal with anchor and focus points. - -```typescript -import { Selection } from '@opentui/core'; - -// Create a selection with anchor and focus points -const selection = new Selection( - { x: 10, y: 5 }, // anchor point - { x: 20, y: 5 } // focus point -); - -// Get the anchor point -const anchor = selection.anchor; - -// Get the focus point -const focus = selection.focus; - -// Get the selection bounds -const bounds = selection.bounds; -// bounds = { startX: 10, startY: 5, endX: 20, endY: 5 } - -// Update the selected renderables -selection.updateSelectedRenderables(selectedComponents); - -// Get the selected text -const text = selection.getSelectedText(); -``` - -## TextSelectionHelper API - -The `TextSelectionHelper` class helps text components handle selection. - -```typescript -import { TextSelectionHelper, SelectionState } from '@opentui/core'; - -class MyTextComponent extends Renderable { - private selectionHelper: TextSelectionHelper; - - constructor(id: string, options = {}) { - super(id, options); - - // Create a selection helper - this.selectionHelper = new TextSelectionHelper( - () => this.x, // Get component X position - () => this.y, // Get component Y position - () => this.content.length, // Get text length - () => ({ // Get line information for multi-line text - lineStarts: this.lineStarts, - lineWidths: this.lineWidths - }) - ); - } - - // Check if the component should start a selection - shouldStartSelection(x: number, y: number): boolean { - return this.selectionHelper.shouldStartSelection(x, y, this.width, this.height); - } - - // Handle selection changes - onSelectionChanged(selection: SelectionState | null): void { - if (this.selectionHelper.onSelectionChanged(selection, this.width, this.height)) { - this.needsRedraw = true; - } - } - - // Check if the component has a selection - hasSelection(): boolean { - return this.selectionHelper.hasSelection(); - } - - // Get the current selection - getSelection(): { start: number; end: number } | null { - return this.selectionHelper.getSelection(); - } - - // Get the selected text - getSelectedText(): string { - const selection = this.selectionHelper.getSelection(); - if (!selection) return ''; - return this.content.substring(selection.start, selection.end); - } - - // Reevaluate selection after component changes - reevaluateSelection(): void { - if (this.selectionHelper.reevaluateSelection(this.width, this.height)) { - this.needsRedraw = true; - } - } -} -``` - -## ASCIIFontSelectionHelper API - -The `ASCIIFontSelectionHelper` class helps ASCII font components handle selection. - -```typescript -import { ASCIIFontSelectionHelper, SelectionState } from '@opentui/core'; - -class MyASCIIFontComponent extends Renderable { - private selectionHelper: ASCIIFontSelectionHelper; - - constructor(id: string, options = {}) { - super(id, options); - - // Create a selection helper - this.selectionHelper = new ASCIIFontSelectionHelper( - () => this.x, // Get component X position - () => this.y, // Get component Y position - () => this.content, // Get text content - () => this.font // Get font name - ); - } - - // Check if the component should start a selection - shouldStartSelection(x: number, y: number): boolean { - return this.selectionHelper.shouldStartSelection(x, y, this.width, this.height); - } - - // Handle selection changes - onSelectionChanged(selection: SelectionState | null): void { - if (this.selectionHelper.onSelectionChanged(selection, this.width, this.height)) { - this.needsRedraw = true; - } - } - - // Check if the component has a selection - hasSelection(): boolean { - return this.selectionHelper.hasSelection(); - } - - // Get the current selection - getSelection(): { start: number; end: number } | null { - return this.selectionHelper.getSelection(); - } - - // Get the selected text - getSelectedText(): string { - const selection = this.selectionHelper.getSelection(); - if (!selection) return ''; - return this.content.substring(selection.start, selection.end); - } - - // Reevaluate selection after component changes - reevaluateSelection(): void { - if (this.selectionHelper.reevaluateSelection(this.width, this.height)) { - this.needsRedraw = true; - } - } -} -``` - -## Example: Implementing Text Selection - -Here's a complete example of implementing text selection in a custom component: - -```typescript -import { Renderable, TextSelectionHelper, SelectionState } from '@opentui/core'; - -class SelectableText extends Renderable { - private content: string; - private selectionHelper: TextSelectionHelper; - private lineStarts: number[] = []; - private lineWidths: number[] = []; - - constructor(id: string, options: { content: string, width: number, height: number }) { - super(id, options); - this.content = options.content; - - // Calculate line information - this.calculateLineInfo(); - - // Create selection helper - this.selectionHelper = new TextSelectionHelper( - () => this.x, - () => this.y, - () => this.content.length, - () => ({ lineStarts: this.lineStarts, lineWidths: this.lineWidths }) - ); - } - - private calculateLineInfo(): void { - this.lineStarts = [0]; - this.lineWidths = []; - - let currentLine = 0; - let currentLineWidth = 0; - - for (let i = 0; i < this.content.length; i++) { - if (this.content[i] === '\n') { - this.lineWidths.push(currentLineWidth); - this.lineStarts.push(i + 1); - currentLine++; - currentLineWidth = 0; - } else { - currentLineWidth++; - } - } - - // Add the last line - this.lineWidths.push(currentLineWidth); - } - - public render(context: RenderContext): void { - // Render the text - const selection = this.selectionHelper.getSelection(); - - for (let i = 0; i < this.lineStarts.length; i++) { - const lineStart = this.lineStarts[i]; - const lineEnd = i < this.lineStarts.length - 1 ? this.lineStarts[i + 1] - 1 : this.content.length; - const line = this.content.substring(lineStart, lineEnd); - - for (let j = 0; j < line.length; j++) { - const charIndex = lineStart + j; - const isSelected = selection && charIndex >= selection.start && charIndex < selection.end; - - // Render character with selection highlighting if needed - context.setChar(this.x + j, this.y + i, line[j], { - fg: isSelected ? '#000000' : '#ffffff', - bg: isSelected ? '#3498db' : 'transparent' - }); - } - } - } - - public onMouseEvent(event: MouseEvent): void { - if (event.type === 'down') { - if (this.selectionHelper.shouldStartSelection(event.x, event.y, this.width, this.height)) { - // Start selection - this.renderer.startSelection(event.x, event.y); - event.preventDefault(); - } - } - } - - public onSelectionChanged(selection: SelectionState | null): void { - if (this.selectionHelper.onSelectionChanged(selection, this.width, this.height)) { - this.needsRedraw = true; - } - } - - public getSelectedText(): string { - const selection = this.selectionHelper.getSelection(); - if (!selection) return ''; - return this.content.substring(selection.start, selection.end); - } -} -``` - -## Example: Copying Selected Text - -```typescript -import { getKeyHandler } from '@opentui/core'; -import * as clipboard from 'clipboard-polyfill'; - -// Set up keyboard shortcut for copying -const keyHandler = getKeyHandler(); - -keyHandler.on('keypress', (key) => { - if (key.ctrl && key.name === 'c') { - const selection = renderer.getSelection(); - if (selection) { - const text = selection.getSelectedText(); - if (text) { - clipboard.writeText(text); - console.log('Copied to clipboard:', text); - } - } - } -}); -``` diff --git a/packages/core/docs/api/lib/styled-text.md b/packages/core/docs/api/lib/styled-text.md deleted file mode 100644 index 837092894..000000000 --- a/packages/core/docs/api/lib/styled-text.md +++ /dev/null @@ -1,393 +0,0 @@ -# Styled Text - -OpenTUI provides a powerful styled text system for creating rich text with different styles, colors, and attributes. - -## StyledText Class - -The `StyledText` class is the core of OpenTUI's text styling system. It allows you to create rich text with different styles, colors, and attributes. - -```typescript -import { StyledText, RGBA } from '@opentui/core'; - -// Create a styled text -const text = new StyledText(); - -// Add text with different styles -text.pushFg('#ff0000'); // Set foreground color to red -text.pushText('This text is red. '); -text.popFg(); // Restore previous foreground color - -text.pushBg('#0000ff'); // Set background color to blue -text.pushText('This text has a blue background. '); -text.popBg(); // Restore previous background color - -text.pushAttributes(0x01); // Set text to bold (0x01 = bold) -text.pushText('This text is bold. '); -text.popAttributes(); // Restore previous attributes - -// Combine styles -text.pushFg('#00ff00'); // Green -text.pushBg('#000000'); // Black background -text.pushAttributes(0x01 | 0x02); // Bold and italic -text.pushText('This text is bold, italic, green on black. '); -text.popAttributes(); -text.popBg(); -text.popFg(); - -// Get the styled text as a string -const plainText = text.toString(); - -// Get the styled text with ANSI escape sequences -const ansiText = text.toANSI(); -``` - -## Text Attributes - -OpenTUI supports the following text attributes: - -| Attribute | Value | Description | -|-----------|-------|-------------| -| Bold | `0x01` | Bold text | -| Italic | `0x02` | Italic text | -| Underline | `0x04` | Underlined text | -| Strikethrough | `0x08` | Strikethrough text | -| Blink | `0x10` | Blinking text | -| Inverse | `0x20` | Inverted colors | -| Hidden | `0x40` | Hidden text | - -You can combine attributes using the bitwise OR operator (`|`): - -```typescript -// Bold and underlined text -text.pushAttributes(0x01 | 0x04); -text.pushText('Bold and underlined text'); -text.popAttributes(); -``` - -## Colors - -OpenTUI supports various color formats: - -```typescript -// Hex colors -text.pushFg('#ff0000'); // Red -text.pushBg('#00ff00'); // Green - -// RGB colors -text.pushFg(RGBA.fromValues(1.0, 0.0, 0.0, 1.0)); // Red -text.pushBg(RGBA.fromValues(0.0, 1.0, 0.0, 1.0)); // Green - -// Named colors -text.pushFg('red'); -text.pushBg('green'); - -// ANSI colors (0-15) -text.pushFg(RGBA.fromANSI(1)); // ANSI red -text.pushBg(RGBA.fromANSI(2)); // ANSI green -``` - -## HAST Support - -OpenTUI supports the Hypertext Abstract Syntax Tree (HAST) format for complex text styling: - -```typescript -import { StyledText } from '@opentui/core'; - -// Create a styled text from HAST -const hast = { - type: 'root', - children: [ - { - type: 'element', - tagName: 'span', - properties: { - style: 'color: red; font-weight: bold;' - }, - children: [ - { - type: 'text', - value: 'This text is red and bold. ' - } - ] - }, - { - type: 'element', - tagName: 'span', - properties: { - style: 'color: blue; text-decoration: underline;' - }, - children: [ - { - type: 'text', - value: 'This text is blue and underlined.' - } - ] - } - ] -}; - -const text = StyledText.fromHAST(hast); -``` - -## Example: Creating a Syntax Highlighter - -```typescript -import { StyledText } from '@opentui/core'; - -function highlightJavaScript(code: string): StyledText { - const result = new StyledText(); - - // Simple tokenizer for demonstration - const tokens = tokenizeJavaScript(code); - - for (const token of tokens) { - switch (token.type) { - case 'keyword': - result.pushFg('#569cd6'); // Blue - result.pushText(token.value); - result.popFg(); - break; - case 'string': - result.pushFg('#ce9178'); // Orange - result.pushText(token.value); - result.popFg(); - break; - case 'number': - result.pushFg('#b5cea8'); // Light green - result.pushText(token.value); - result.popFg(); - break; - case 'comment': - result.pushFg('#6a9955'); // Green - result.pushText(token.value); - result.popFg(); - break; - case 'function': - result.pushFg('#dcdcaa'); // Yellow - result.pushText(token.value); - result.popFg(); - break; - default: - result.pushText(token.value); - break; - } - } - - return result; -} - -// Simple tokenizer for demonstration -function tokenizeJavaScript(code: string): Array<{ type: string, value: string }> { - // This is a simplified tokenizer for demonstration purposes - // In a real implementation, you would use a proper parser - - const keywords = ['const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while']; - const tokens = []; - - // Split the code into lines - const lines = code.split('\n'); - - for (const line of lines) { - let i = 0; - - while (i < line.length) { - // Skip whitespace - if (/\s/.test(line[i])) { - const start = i; - while (i < line.length && /\s/.test(line[i])) { - i++; - } - tokens.push({ type: 'whitespace', value: line.substring(start, i) }); - continue; - } - - // Comments - if (line[i] === '/' && line[i + 1] === '/') { - tokens.push({ type: 'comment', value: line.substring(i) }); - break; - } - - // Strings - if (line[i] === '"' || line[i] === "'") { - const quote = line[i]; - const start = i; - i++; - while (i < line.length && line[i] !== quote) { - if (line[i] === '\\') { - i += 2; - } else { - i++; - } - } - if (i < line.length) { - i++; - } - tokens.push({ type: 'string', value: line.substring(start, i) }); - continue; - } - - // Numbers - if (/\d/.test(line[i])) { - const start = i; - while (i < line.length && /[\d.]/.test(line[i])) { - i++; - } - tokens.push({ type: 'number', value: line.substring(start, i) }); - continue; - } - - // Identifiers and keywords - if (/[a-zA-Z_$]/.test(line[i])) { - const start = i; - while (i < line.length && /[a-zA-Z0-9_$]/.test(line[i])) { - i++; - } - const word = line.substring(start, i); - - if (keywords.includes(word)) { - tokens.push({ type: 'keyword', value: word }); - } else if (i < line.length && line[i] === '(') { - tokens.push({ type: 'function', value: word }); - } else { - tokens.push({ type: 'identifier', value: word }); - } - continue; - } - - // Punctuation - tokens.push({ type: 'punctuation', value: line[i] }); - i++; - } - - tokens.push({ type: 'whitespace', value: '\n' }); - } - - return tokens; -} - -// Usage -const code = ` -function factorial(n) { - // Calculate factorial - if (n <= 1) { - return 1; - } - return n * factorial(n - 1); -} - -const result = factorial(5); -console.log("The factorial of 5 is: " + result); -`; - -const highlightedCode = highlightJavaScript(code); -``` - -## Example: Creating a Logger with Styled Output - -```typescript -import { StyledText } from '@opentui/core'; - -class Logger { - private static instance: Logger; - - private constructor() {} - - public static getInstance(): Logger { - if (!Logger.instance) { - Logger.instance = new Logger(); - } - return Logger.instance; - } - - public info(message: string): StyledText { - const text = new StyledText(); - - // Add timestamp - text.pushFg('#888888'); - text.pushText(`[${new Date().toISOString()}] `); - text.popFg(); - - // Add level - text.pushFg('#3498db'); - text.pushAttributes(0x01); // Bold - text.pushText('INFO'); - text.popAttributes(); - text.popFg(); - - // Add separator - text.pushText(': '); - - // Add message - text.pushText(message); - - return text; - } - - public warn(message: string): StyledText { - const text = new StyledText(); - - // Add timestamp - text.pushFg('#888888'); - text.pushText(`[${new Date().toISOString()}] `); - text.popFg(); - - // Add level - text.pushFg('#f39c12'); - text.pushAttributes(0x01); // Bold - text.pushText('WARN'); - text.popAttributes(); - text.popFg(); - - // Add separator - text.pushText(': '); - - // Add message - text.pushFg('#f39c12'); - text.pushText(message); - text.popFg(); - - return text; - } - - public error(message: string, error?: Error): StyledText { - const text = new StyledText(); - - // Add timestamp - text.pushFg('#888888'); - text.pushText(`[${new Date().toISOString()}] `); - text.popFg(); - - // Add level - text.pushFg('#e74c3c'); - text.pushAttributes(0x01); // Bold - text.pushText('ERROR'); - text.popAttributes(); - text.popFg(); - - // Add separator - text.pushText(': '); - - // Add message - text.pushFg('#e74c3c'); - text.pushText(message); - text.popFg(); - - // Add error details if provided - if (error) { - text.pushText('\n'); - text.pushFg('#888888'); - text.pushText(error.stack || error.message); - text.popFg(); - } - - return text; - } -} - -// Usage -const logger = Logger.getInstance(); - -console.log(logger.info('Application started').toANSI()); -console.log(logger.warn('Disk space is low').toANSI()); -console.log(logger.error('Failed to connect to database', new Error('Connection timeout')).toANSI()); -``` diff --git a/packages/core/docs/api/lib/tracked-node.md b/packages/core/docs/api/lib/tracked-node.md deleted file mode 100644 index 3dfeb1c54..000000000 --- a/packages/core/docs/api/lib/tracked-node.md +++ /dev/null @@ -1,246 +0,0 @@ -# TrackedNode System - -The TrackedNode system is a core part of OpenTUI's layout engine, providing a TypeScript wrapper around Yoga layout nodes with additional tracking and relationship management. - -## Overview - -The `TrackedNode` class wraps Yoga layout nodes and maintains parent-child relationships, handles percentage-based dimensions, and provides a metadata system for storing additional information. - -## TrackedNode API - -```typescript -import { TrackedNode, createTrackedNode } from '@opentui/core'; - -// Create a new tracked node -const node = createTrackedNode({ - id: 'my-node', - type: 'box' -}); - -// Set dimensions -node.setWidth(100); -node.setHeight(50); - -// Set percentage-based dimensions -node.setWidth('50%'); -node.setHeight('25%'); - -// Set dimensions to auto -node.setWidth('auto'); -node.setHeight('auto'); -``` - -### Creating Nodes - -The `createTrackedNode` function creates a new `TrackedNode` instance with an underlying Yoga node: - -```typescript -import { createTrackedNode } from '@opentui/core'; -import Yoga from 'yoga-layout'; - -// Create a node with metadata -const node = createTrackedNode({ - id: 'my-node', - type: 'box' -}); - -// Create a node with metadata and custom Yoga config -const config = Yoga.Config.create(); -const nodeWithConfig = createTrackedNode({ - id: 'custom-node', - type: 'box' -}, config); -``` - -### Node Dimensions - -The `TrackedNode` class provides methods for setting and parsing dimensions, including support for percentage-based dimensions: - -```typescript -// Set fixed dimensions -node.setWidth(100); -node.setHeight(50); - -// Set percentage-based dimensions (relative to parent) -node.setWidth('50%'); -node.setHeight('25%'); - -// Set dimensions to auto (let Yoga determine the size) -node.setWidth('auto'); -node.setHeight('auto'); - -// Parse dimensions (converts percentages to absolute values) -const parsedWidth = node.parseWidth('50%'); -const parsedHeight = node.parseHeight('25%'); -``` - -### Node Hierarchy - -The `TrackedNode` class provides methods for managing the node hierarchy: - -```typescript -// Add a child node -const childIndex = parentNode.addChild(childNode); - -// Insert a child node at a specific index -const insertedIndex = parentNode.insertChild(childNode, 2); - -// Remove a child node -const removed = parentNode.removeChild(childNode); - -// Remove a child node at a specific index -const removedNode = parentNode.removeChildAtIndex(2); - -// Move a child node to a new index -const newIndex = parentNode.moveChild(childNode, 3); - -// Get the index of a child node -const index = parentNode.getChildIndex(childNode); - -// Check if a node is a child of this node -const isChild = parentNode.hasChild(childNode); - -// Get the number of children -const childCount = parentNode.getChildCount(); - -// Get a child node at a specific index -const child = parentNode.getChildAtIndex(2); -``` - -### Metadata - -The `TrackedNode` class provides methods for managing metadata: - -```typescript -// Set metadata -node.setMetadata('visible', true); - -// Get metadata -const isVisible = node.getMetadata('visible'); - -// Remove metadata -node.removeMetadata('visible'); -``` - -### Cleanup - -The `TrackedNode` class provides a method for cleaning up resources: - -```typescript -// Destroy the node and free resources -node.destroy(); -``` - -## Example: Building a Layout Tree - -```typescript -import { createTrackedNode } from '@opentui/core'; - -// Create a root node -const root = createTrackedNode({ - id: 'root', - type: 'container' -}); - -// Set root dimensions -root.setWidth(800); -root.setHeight(600); - -// Create a header node -const header = createTrackedNode({ - id: 'header', - type: 'box' -}); - -// Set header dimensions -header.setWidth('100%'); -header.setHeight(50); - -// Create a content node -const content = createTrackedNode({ - id: 'content', - type: 'box' -}); - -// Set content dimensions -content.setWidth('100%'); -content.setHeight('auto'); - -// Create a footer node -const footer = createTrackedNode({ - id: 'footer', - type: 'box' -}); - -// Set footer dimensions -footer.setWidth('100%'); -footer.setHeight(50); - -// Build the layout tree -root.addChild(header); -root.addChild(content); -root.addChild(footer); - -// Add some content items -for (let i = 0; i < 3; i++) { - const item = createTrackedNode({ - id: `item-${i}`, - type: 'box' - }); - - item.setWidth('33%'); - item.setHeight(100); - - content.addChild(item); -} - -// Later, clean up resources -root.destroy(); // This will also destroy all child nodes -``` - -## Integration with Yoga Layout - -The `TrackedNode` system is built on top of the Yoga layout engine, which provides a flexible and powerful layout system based on Flexbox. The `TrackedNode` class wraps Yoga nodes and provides additional functionality for managing the node hierarchy and metadata. - -When you set dimensions or add/remove children, the `TrackedNode` class updates the underlying Yoga node accordingly. When the layout is calculated, the Yoga engine determines the final positions and dimensions of all nodes based on the layout constraints. - -```typescript -import { createTrackedNode } from '@opentui/core'; - -// Create a container with flexbox layout -const container = createTrackedNode({ - id: 'container', - type: 'box' -}); - -container.setWidth(500); -container.setHeight(300); - -// Configure flexbox properties on the Yoga node -container.yogaNode.setFlexDirection(Yoga.FLEX_DIRECTION_ROW); -container.yogaNode.setJustifyContent(Yoga.JUSTIFY_SPACE_BETWEEN); -container.yogaNode.setAlignItems(Yoga.ALIGN_CENTER); - -// Add some flex items -for (let i = 0; i < 3; i++) { - const item = createTrackedNode({ - id: `item-${i}`, - type: 'box' - }); - - item.setWidth(100); - item.setHeight(100); - item.yogaNode.setMargin(Yoga.EDGE_ALL, 10); - - container.addChild(item); -} - -// Calculate the layout -container.yogaNode.calculateLayout(); - -// Get the computed layout values -const width = container.yogaNode.getComputedWidth(); -const height = container.yogaNode.getComputedHeight(); -const x = container.yogaNode.getComputedLeft(); -const y = container.yogaNode.getComputedTop(); -``` diff --git a/packages/core/docs/api/native-integration.md b/packages/core/docs/api/native-integration.md deleted file mode 100644 index 229bd6b6e..000000000 --- a/packages/core/docs/api/native-integration.md +++ /dev/null @@ -1,144 +0,0 @@ -# Native Integration (detailed RenderLib API) - -OpenTUI uses native code written in Zig for performance-critical operations. The JavaScript side exposes a RenderLib wrapper (packages/core/src/zig.ts) that converts JS types to the FFI layer and provides convenient helpers. - -This page documents the RenderLib wrapper signatures and the TextBuffer/native buffer helpers in detail so examples can call them correctly. - -Source reference: packages/core/src/zig.ts (FFIRenderLib / RenderLib interface) - ---- - -## Getting the RenderLib - -```ts -import { resolveRenderLib, type RenderLib } from '@opentui/core'; - -const lib: RenderLib = resolveRenderLib(); // throws if native lib cannot load -``` - -The `RenderLib` object provides both renderer-level and buffer-level helpers. Most application code should prefer the high-level JS API (OptimizedBuffer, TextBuffer) but the RenderLib helpers are useful for advanced/native usage. - ---- - -## Renderer-level methods (selected) - -- createRenderer(width: number, height: number): Pointer | null -- destroyRenderer(renderer: Pointer): void -- setUseThread(renderer: Pointer, useThread: boolean): void -- setBackgroundColor(renderer: Pointer, color: RGBA): void -- setRenderOffset(renderer: Pointer, offset: number): void -- updateStats(renderer: Pointer, time: number, fps: number, frameCallbackTime: number): void -- updateMemoryStats(renderer: Pointer, heapUsed: number, heapTotal: number, arrayBuffers: number): void -- render(renderer: Pointer, force: boolean): void -- getNextBuffer(renderer: Pointer): OptimizedBuffer -- getCurrentBuffer(renderer: Pointer): OptimizedBuffer -- resizeRenderer(renderer: Pointer, width: number, height: number): void -- setDebugOverlay(renderer: Pointer, enabled: boolean, corner: DebugOverlayCorner): void -- clearTerminal(renderer: Pointer): void -- addToHitGrid(renderer: Pointer, x: number, y: number, width: number, height: number, id: number): void -- checkHit(renderer: Pointer, x: number, y: number): number -- dumpHitGrid(renderer: Pointer): void -- dumpBuffers(renderer: Pointer, timestamp?: number): void -- dumpStdoutBuffer(renderer: Pointer, timestamp?: number): void - -Notes: -- getNextBuffer/getCurrentBuffer return OptimizedBuffer instances that wrap native buffer pointers and typed arrays. - ---- - -## Optimized buffer / buffer primitives (native-facing helpers) - -These are the RenderLib wrapper methods that operate on native buffer pointers or wrapped OptimizedBuffer objects. Prefer the high-level OptimizedBuffer methods, but this lists the wrapper signatures for precise behavior. - -- createOptimizedBuffer(width: number, height: number, respectAlpha?: boolean): OptimizedBuffer - - Returns an OptimizedBuffer instance wrapping the native buffer pointer and typed arrays. -- destroyOptimizedBuffer(bufferPtr: Pointer): void - -Buffer property helpers: -- getBufferWidth(buffer: Pointer): number -- getBufferHeight(buffer: Pointer): number -- bufferGetCharPtr(buffer: Pointer): Pointer -- bufferGetFgPtr(buffer: Pointer): Pointer -- bufferGetBgPtr(buffer: Pointer): Pointer -- bufferGetAttributesPtr(buffer: Pointer): Pointer -- bufferGetRespectAlpha(buffer: Pointer): boolean -- bufferSetRespectAlpha(buffer: Pointer, respectAlpha: boolean): void -- bufferClear(buffer: Pointer, color: RGBA): void - -Drawing helpers (native/FFI-backed): -- bufferDrawText(buffer: Pointer, text: string, x: number, y: number, color: RGBA, bgColor?: RGBA, attributes?: number): void - - In the wrapper, JS strings are encoded and forwarded to the native symbol with length. - - Use RGBA instances for color arguments. -- bufferSetCellWithAlphaBlending(buffer: Pointer, x: number, y: number, char: string, color: RGBA, bgColor: RGBA, attributes?: number): void - - Accepts a single-character string (the wrapper converts to codepoint). -- bufferFillRect(buffer: Pointer, x: number, y: number, width: number, height: number, color: RGBA): void -- bufferDrawSuperSampleBuffer(buffer: Pointer, x: number, y: number, pixelDataPtr: Pointer, pixelDataLength: number, format: 'bgra8unorm' | 'rgba8unorm', alignedBytesPerRow: number): void - - Format argument in wrapper is converted to an internal format id. -- bufferDrawPackedBuffer(buffer: Pointer, dataPtr: Pointer, dataLen: number, posX: number, posY: number, terminalWidthCells: number, terminalHeightCells: number): void -- bufferDrawBox(buffer: Pointer, x: number, y: number, width: number, height: number, borderChars: Uint32Array, packedOptions: number, borderColor: RGBA, backgroundColor: RGBA, title: string | null): void - - The wrapper accepts a JS string title and encodes it with a length. -- bufferResize(buffer: Pointer, width: number, height: number): { char: Uint32Array; fg: Float32Array; bg: Float32Array; attributes: Uint8Array } - - Returns the new typed arrays mapped to the buffer. - -Notes: -- The wrapper converts JS RGBA objects into underlying Float32Array pointers and encodes strings into Uint8Array payloads for the FFI call. -- There are no native helpers named drawHorizontalLine or drawVerticalLine; use bufferFillRect, bufferDrawText / bufferSetCellWithAlphaBlending, or bufferDrawBox to implement lines. - ---- - -## TextBuffer native helpers (RenderLib wrapper) - -`TextBuffer` is a native-backed rich text buffer; the RenderLib exposes wrapper methods: - -- createTextBuffer(capacity: number): TextBuffer - - Returns a TextBuffer instance wrapping a native pointer and typed arrays. -- destroyTextBuffer(buffer: Pointer): void -- textBufferGetCharPtr(buffer: Pointer): Pointer -- textBufferGetFgPtr(buffer: Pointer): Pointer -- textBufferGetBgPtr(buffer: Pointer): Pointer -- textBufferGetAttributesPtr(buffer: Pointer): Pointer -- textBufferGetLength(buffer: Pointer): number -- textBufferSetCell(buffer: Pointer, index: number, char: number, fg: Float32Array, bg: Float32Array, attr: number): void -- textBufferConcat(buffer1: Pointer, buffer2: Pointer): TextBuffer -- textBufferResize(buffer: Pointer, newLength: number): { char: Uint32Array; fg: Float32Array; bg: Float32Array; attributes: Uint16Array } -- textBufferReset(buffer: Pointer): void -- textBufferSetSelection(buffer: Pointer, start: number, end: number, bgColor: RGBA | null, fgColor: RGBA | null): void -- textBufferResetSelection(buffer: Pointer): void -- textBufferSetDefaultFg(buffer: Pointer, fg: RGBA | null): void -- textBufferSetDefaultBg(buffer: Pointer, bg: RGBA | null): void -- textBufferSetDefaultAttributes(buffer: Pointer, attributes: number | null): void -- textBufferResetDefaults(buffer: Pointer): void -- textBufferWriteChunk(buffer: Pointer, textBytes: Uint8Array, fg: RGBA | null, bg: RGBA | null, attributes: number | null): number - - Returns number of bytes written / used by the chunk write operation. -- textBufferGetCapacity(buffer: Pointer): number -- textBufferFinalizeLineInfo(buffer: Pointer): void -- textBufferGetLineInfo(buffer: Pointer): { lineStarts: number[]; lineWidths: number[] } -- getTextBufferArrays(buffer: Pointer, size: number): { char: Uint32Array; fg: Float32Array; bg: Float32Array; attributes: Uint16Array } -- bufferDrawTextBuffer(buffer: Pointer, textBuffer: Pointer, x: number, y: number, clipRect?: { x: number; y: number; width: number; height: number }): void - -Usage notes: -- The RenderLib wrapper encodes text and forwards typed arrays to native implementations. -- Use `createTextBuffer` and `bufferDrawTextBuffer` to render styled content efficiently. -- `textBufferWriteChunk` accepts a Uint8Array of UTF-8 bytes and optional RGBA buffers for default fg/bg; the wrapper handles encoding. - ---- - -## Examples - -Create an optimized buffer via RenderLib and draw text: -```ts -import { resolveRenderLib, RGBA } from '@opentui/core'; - -const lib = resolveRenderLib(); -const fb = lib.createOptimizedBuffer(80, 24, false); // OptimizedBuffer instance -fb.drawText('Hello via FFI', 0, 0, RGBA.fromValues(1,1,1,1)); -``` - -Create a TextBuffer and render it: -```ts -const tb = lib.createTextBuffer(128); -lib.textBufferWriteChunk(tb.ptr, new TextEncoder().encode('Hello TB'), null, null, 0); -lib.bufferDrawTextBuffer(fb.ptr, tb.ptr, 2, 2); -``` - ---- diff --git a/packages/core/docs/api/post/filters.md b/packages/core/docs/api/post/filters.md deleted file mode 100644 index 653b36435..000000000 --- a/packages/core/docs/api/post/filters.md +++ /dev/null @@ -1,420 +0,0 @@ -# Post-Processing Filters - -OpenTUI provides a set of post-processing filters that can be applied to the terminal output to create various visual effects. - -## Overview - -Post-processing filters allow you to modify the appearance of the terminal output after it has been rendered. This can be used to create visual effects like scanlines, grayscale, sepia tone, and more. - -## Basic Filters - -### Scanlines - -Applies a scanline effect by darkening every nth row. - -```typescript -import { applyScanlines, createCliRenderer, BoxRenderable } from '@opentui/core'; - -// Create a renderer -const renderer = await createCliRenderer(); - -// Create a component -const box = new BoxRenderable('box', { - width: 40, - height: 20, - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222' -}); - -renderer.root.add(box); - -// Apply scanlines to the renderer's buffer -renderer.on('afterRender', () => { - applyScanlines(renderer.buffer, 0.8, 2); -}); -``` - -### Grayscale - -Converts the buffer colors to grayscale. - -```typescript -import { applyGrayscale, createCliRenderer } from '@opentui/core'; - -// Create a renderer -const renderer = await createCliRenderer(); - -// Apply grayscale to the renderer's buffer -renderer.on('afterRender', () => { - applyGrayscale(renderer.buffer); -}); -``` - -### Sepia Tone - -Applies a sepia tone to the buffer. - -```typescript -import { applySepia, createCliRenderer } from '@opentui/core'; - -// Create a renderer -const renderer = await createCliRenderer(); - -// Apply sepia tone to the renderer's buffer -renderer.on('afterRender', () => { - applySepia(renderer.buffer); -}); -``` - -### Invert Colors - -Inverts the colors in the buffer. - -```typescript -import { applyInvert, createCliRenderer } from '@opentui/core'; - -// Create a renderer -const renderer = await createCliRenderer(); - -// Apply color inversion to the renderer's buffer -renderer.on('afterRender', () => { - applyInvert(renderer.buffer); -}); -``` - -### Noise - -Adds random noise to the buffer colors. - -```typescript -import { applyNoise, createCliRenderer } from '@opentui/core'; - -// Create a renderer -const renderer = await createCliRenderer(); - -// Apply noise to the renderer's buffer -renderer.on('afterRender', () => { - applyNoise(renderer.buffer, 0.1); -}); -``` - -### Chromatic Aberration - -Applies a simplified chromatic aberration effect. - -```typescript -import { applyChromaticAberration, createCliRenderer } from '@opentui/core'; - -// Create a renderer -const renderer = await createCliRenderer(); - -// Apply chromatic aberration to the renderer's buffer -renderer.on('afterRender', () => { - applyChromaticAberration(renderer.buffer, 1); -}); -``` - -### ASCII Art - -Converts the buffer to ASCII art based on background brightness. - -```typescript -import { applyAsciiArt, createCliRenderer } from '@opentui/core'; - -// Create a renderer -const renderer = await createCliRenderer(); - -// Apply ASCII art effect to the renderer's buffer -renderer.on('afterRender', () => { - applyAsciiArt(renderer.buffer, " .:-=+*#%@"); -}); -``` - -## Advanced Effects - -### Distortion Effect - -The `DistortionEffect` class provides an animated glitch/distortion effect. - -```typescript -import { DistortionEffect, createCliRenderer } from '@opentui/core'; - -// Create a renderer -const renderer = await createCliRenderer(); - -// Create a distortion effect -const distortion = new DistortionEffect({ - glitchChancePerSecond: 0.5, - maxGlitchLines: 3, - minGlitchDuration: 0.05, - maxGlitchDuration: 0.2, - maxShiftAmount: 10, - shiftFlipRatio: 0.6, - colorGlitchChance: 0.2 -}); - -// Apply the distortion effect in the render loop -renderer.on('afterRender', (context) => { - distortion.apply(renderer.buffer, context.deltaTime); -}); -``` - -### Vignette Effect - -The `VignetteEffect` class adds a vignette (darkened corners) to the buffer. - -```typescript -import { VignetteEffect, createCliRenderer } from '@opentui/core'; - -// Create a renderer -const renderer = await createCliRenderer(); - -// Create a vignette effect -const vignette = new VignetteEffect({ - strength: 0.7, - size: 0.8, - smoothness: 0.5 -}); - -// Apply the vignette effect in the render loop -renderer.on('afterRender', () => { - vignette.apply(renderer.buffer); -}); -``` - -### Brightness Effect - -The `BrightnessEffect` class adjusts the brightness of the buffer. - -```typescript -import { BrightnessEffect, createCliRenderer } from '@opentui/core'; - -// Create a renderer -const renderer = await createCliRenderer(); - -// Create a brightness effect -const brightness = new BrightnessEffect({ - value: 1.2 // Values > 1 increase brightness, < 1 decrease brightness -}); - -// Apply the brightness effect in the render loop -renderer.on('afterRender', () => { - brightness.apply(renderer.buffer); -}); -``` - -### Blur Effect - -The `BlurEffect` class applies a blur effect to the buffer. - -```typescript -import { BlurEffect, createCliRenderer } from '@opentui/core'; - -// Create a renderer -const renderer = await createCliRenderer(); - -// Create a blur effect -const blur = new BlurEffect({ - radius: 1, - passes: 2 -}); - -// Apply the blur effect in the render loop -renderer.on('afterRender', () => { - blur.apply(renderer.buffer); -}); -``` - -### Bloom Effect - -The `BloomEffect` class applies a bloom effect to bright areas of the buffer. - -```typescript -import { BloomEffect, createCliRenderer } from '@opentui/core'; - -// Create a renderer -const renderer = await createCliRenderer(); - -// Create a bloom effect -const bloom = new BloomEffect({ - threshold: 0.7, - intensity: 0.5, - radius: 1 -}); - -// Apply the bloom effect in the render loop -renderer.on('afterRender', () => { - bloom.apply(renderer.buffer); -}); -``` - -## Combining Effects - -You can combine multiple effects to create more complex visual styles. - -```typescript -import { - applyGrayscale, - applyScanlines, - VignetteEffect, - createCliRenderer -} from '@opentui/core'; - -// Create a renderer -const renderer = await createCliRenderer(); - -// Create a vignette effect -const vignette = new VignetteEffect({ - strength: 0.7, - size: 0.8, - smoothness: 0.5 -}); - -// Apply multiple effects in the render loop -renderer.on('afterRender', () => { - // Apply effects in sequence - applyGrayscale(renderer.buffer); - applyScanlines(renderer.buffer, 0.8, 2); - vignette.apply(renderer.buffer); -}); -``` - -## Example: Creating a Retro Terminal Effect - -```typescript -import { - createCliRenderer, - BoxRenderable, - TextRenderable, - applyGrayscale, - applyScanlines, - applyNoise, - VignetteEffect -} from '@opentui/core'; - -async function createRetroTerminal() { - // Create the renderer - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - borderStyle: 'single', - borderColor: '#00ff00', - backgroundColor: '#001100' - }); - - // Create a text element - const text = new TextRenderable('text', { - content: 'TERMINAL READY\n\n> _', - fg: '#00ff00', - padding: 1, - flexGrow: 1 - }); - - // Build the component tree - container.add(text); - root.add(container); - - // Create a vignette effect - const vignette = new VignetteEffect({ - strength: 0.6, - size: 0.75, - smoothness: 0.3 - }); - - // Apply retro effects - renderer.on('afterRender', (context) => { - // Apply green monochrome effect - applyGrayscale(renderer.buffer); - - // Apply scanlines - applyScanlines(renderer.buffer, 0.7, 2); - - // Add some noise - applyNoise(renderer.buffer, 0.05); - - // Add vignette - vignette.apply(renderer.buffer); - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the retro terminal -createRetroTerminal().catch(console.error); -``` - -## Example: Creating a Glitch Effect - -```typescript -import { - createCliRenderer, - BoxRenderable, - TextRenderable, - DistortionEffect, - applyChromaticAberration -} from '@opentui/core'; - -async function createGlitchEffect() { - // Create the renderer - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - borderStyle: 'double', - borderColor: '#ff00ff', - backgroundColor: '#110022' - }); - - // Create a text element - const text = new TextRenderable('text', { - content: 'SYSTEM ERROR\nCORRUPTION DETECTED\nATTEMPTING RECOVERY...', - fg: '#ffffff', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 - }); - - // Build the component tree - container.add(text); - root.add(container); - - // Create a distortion effect - const distortion = new DistortionEffect({ - glitchChancePerSecond: 0.8, - maxGlitchLines: 5, - minGlitchDuration: 0.1, - maxGlitchDuration: 0.3, - maxShiftAmount: 15, - shiftFlipRatio: 0.7, - colorGlitchChance: 0.3 - }); - - // Apply glitch effects - renderer.on('afterRender', (context) => { - // Apply chromatic aberration - applyChromaticAberration(renderer.buffer, 2); - - // Apply distortion - distortion.apply(renderer.buffer, context.deltaTime); - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the glitch effect -createGlitchEffect().catch(console.error); -``` diff --git a/packages/core/docs/api/react/reconciler.md b/packages/core/docs/api/react/reconciler.md deleted file mode 100644 index d6fa14b3b..000000000 --- a/packages/core/docs/api/react/reconciler.md +++ /dev/null @@ -1,601 +0,0 @@ -# React Reconciler - -OpenTUI provides a React reconciler that allows you to build terminal user interfaces using React components and hooks. - -## Overview - -The React reconciler consists of: - -1. **Reconciler**: The core React reconciler that bridges React and OpenTUI -2. **Host Config**: Configuration for the reconciler that defines how React components map to OpenTUI renderables -3. **Components**: Pre-built React components for common UI elements -4. **Hooks**: Custom React hooks for terminal-specific functionality - -## Getting Started - -To use React with OpenTUI, you need to install the React package: - -```bash -npm install @opentui/react -# or -yarn add @opentui/react -# or -bun add @opentui/react -``` - -Then you can create a React application: - -```tsx -import React from 'react'; -import { render, Box, Text } from '@opentui/react'; - -function App() { - return ( - - - - ); -} - -// Render the React application -render(); -``` - -## Components - -The React package provides components that map to OpenTUI renderables: - -### Box Component - -```tsx -import { Box } from '@opentui/react'; - -function MyComponent() { - return ( - - {/* Children go here */} - - ); -} -``` - -### Text Component - -```tsx -import { Text } from '@opentui/react'; - -function MyComponent() { - return ( - - ); -} -``` - -### Input Component - -```tsx -import { Input } from '@opentui/react'; -import { useState } from 'react'; - -function MyComponent() { - const [value, setValue] = useState(''); - - return ( - - ); -} -``` - -### Select Component - -```tsx -import { Select } from '@opentui/react'; -import { useState } from 'react'; - -function MyComponent() { - const [value, setValue] = useState('option1'); - - return ( - - - - - - - - - {todos.length === 0 ? ( - - ) : ( - todos.map((todo, index) => ( - toggleTodo(todo.id)} - > - - - - )) - )} - - - ); -} - -// Render the Todo app -render(); -``` diff --git a/packages/core/docs/api/reference/README.md b/packages/core/docs/api/reference/README.md new file mode 100644 index 000000000..f8dac6327 --- /dev/null +++ b/packages/core/docs/api/reference/README.md @@ -0,0 +1,10 @@ +# API Reference + +This directory contains the complete API reference for OpenTUI, automatically generated from the source code. + +## Structure + +- **[index.md](./index.md)** - Main index of all API components +- **classes/** - Documentation for all classes +- **interfaces/** - Documentation for all interfaces +- **types/** - Documentation for type definitions diff --git a/packages/core/docs/api/reference/classes/ASCIIFontRenderable.md b/packages/core/docs/api/reference/classes/ASCIIFontRenderable.md new file mode 100644 index 000000000..afa183b4c --- /dev/null +++ b/packages/core/docs/api/reference/classes/ASCIIFontRenderable.md @@ -0,0 +1,265 @@ +# ASCIIFontRenderable + +Renders text using ASCII art fonts for decorative headers and titles. Supports both built-in and custom font definitions. + +## Constructor + +```typescript +new ASCIIFontRenderable(id: string, options: ASCIIFontOptions) +``` + +### Parameters + +#### id + +Type: `string` + +Unique identifier for this ASCII font component + +#### options + +Type: `ASCIIFontOptions` + +Configuration options for ASCII font rendering. Key properties include: + +| Property | Type | Description | +|----------|------|-------------| +| `text` | `string` | Text to render in ASCII art | +| `font` | `string \| FontDefinition` | Font name or custom font definition | +| `color` | `string \| RGBA` | Text color | +| `backgroundColor` | `string \| RGBA` | Background color | +| `align` | `'left' \| 'center' \| 'right'` | Text alignment | +| `letterSpacing` | `number` | Extra space between characters | +| `lineHeight` | `number` | Multiplier for line spacing | + +## Properties + +### text + +Type: `string` + +Current text being displayed + +### font + +Type: `string | FontDefinition` + +Active font for rendering + +### height + +Type: `number` + +Calculated height of the ASCII art text + +## Methods + +### setText() + +Update the displayed text + +#### Signature + +```typescript +setText(text: string): void +``` + +#### Parameters + +- **text**: `string` - New text to render + +### setFont() + +Change the font + +#### Signature + +```typescript +setFont(font: string | FontDefinition): void +``` + +#### Parameters + +- **font**: `string | FontDefinition` - Font name or custom definition + +### registerFont() + +Register a custom font for use + +#### Signature + +```typescript +static registerFont(name: string, definition: FontDefinition): void +``` + +#### Parameters + +- **name**: `string` - Name to register the font under +- **definition**: `FontDefinition` - Font character definitions + +## Built-in Fonts + +OpenTUI includes several built-in ASCII fonts: + +### default +Standard block letters, clean and readable + +### bulky +Bold, heavy characters for emphasis + +### chrome +Metallic-style letters with shine effects + +### huge +Extra large letters for maximum impact + +## Examples + +### Basic ASCII Title + +```typescript +const title = new ASCIIFontRenderable('title', { + text: 'WELCOME', + font: 'default', + color: '#00ff00', + align: 'center' +}); +``` + +### Styled Banner + +```typescript +const banner = new ASCIIFontRenderable('banner', { + text: 'GAME OVER', + font: 'huge', + color: '#ff0000', + backgroundColor: '#000000', + align: 'center', + letterSpacing: 1 +}); +``` + +### Custom Font Definition + +```typescript +const customFont: FontDefinition = { + height: 5, + chars: { + 'A': [ + ' █ ', + ' █ █ ', + '█████', + '█ █', + '█ █' + ], + 'B': [ + '████ ', + '█ █', + '████ ', + '█ █', + '████ ' + ] + // ... more characters + } +}; + +const customText = new ASCIIFontRenderable('custom', { + text: 'AB', + font: customFont, + color: '#ffffff' +}); +``` + +### Registering Custom Fonts + +```typescript +// Register a custom font globally +ASCIIFontRenderable.registerFont('pixel', { + height: 3, + chars: { + 'A': ['█▀█', '███', '▀ ▀'], + 'B': ['██▄', '██▄', '██▀'], + 'C': ['▄██', '█ ', '▀██'], + // ... more characters + } +}); + +// Use the registered font +const pixelText = new ASCIIFontRenderable('pixel-text', { + text: 'ABC', + font: 'pixel', + color: '#00ffff' +}); +``` + +### Animated ASCII Text + +```typescript +const animatedTitle = new ASCIIFontRenderable('animated', { + text: 'LOADING', + font: 'chrome', + color: '#ffffff' +}); + +// Animate color +let hue = 0; +setInterval(() => { + hue = (hue + 10) % 360; + animatedTitle.setColor(RGBA.fromHSL(hue, 100, 50)); + animatedTitle.needsUpdate(); +}, 100); + +// Animate text +const frames = ['LOADING', 'LOADING.', 'LOADING..', 'LOADING...']; +let frame = 0; +setInterval(() => { + animatedTitle.setText(frames[frame]); + frame = (frame + 1) % frames.length; +}, 500); +``` + +## Font Definition Structure + +```typescript +interface FontDefinition { + height: number; // Height of each character in lines + chars: { + [char: string]: string[]; // Array of strings, one per line + }; + kerning?: { + [pair: string]: number; // Spacing adjustment for character pairs + }; +} +``` + +### Creating Font Definitions + +1. Each character is an array of strings +2. All strings must have the same width +3. The array length must match the font height +4. Use Unicode box-drawing characters for best results + +```typescript +const miniFont: FontDefinition = { + height: 3, + chars: { + 'H': [ + '█ █', + '███', + '█ █' + ], + 'I': [ + '███', + ' █ ', + '███' + ] + } +}; +``` + +## See Also + +- [ASCIIFontOptions](../interfaces/ASCIIFontOptions.md) - Configuration options +- [FontDefinition](../interfaces/FontDefinition.md) - Custom font structure +- [TextRenderable](./TextRenderable.md) - Standard text component +- [Renderable](./Renderable.md) - Base component class \ No newline at end of file diff --git a/packages/core/docs/api/reference/classes/BoxRenderable.md b/packages/core/docs/api/reference/classes/BoxRenderable.md new file mode 100644 index 000000000..9a5fe6060 --- /dev/null +++ b/packages/core/docs/api/reference/classes/BoxRenderable.md @@ -0,0 +1,344 @@ +# BoxRenderable + +Container component that provides borders, padding, and flexbox layout. The most fundamental building block for OpenTUI interfaces, similar to a `div` element in HTML. + +## Constructor + +```typescript +new BoxRenderable(id: string, options: BoxOptions) +``` + +### Parameters + +#### id + +Type: `string` + +Unique identifier for this box component + +#### options + +Type: `BoxOptions` + +Configuration options for the box. See [BoxOptions](../interfaces/BoxOptions.md) for full details. + +## Properties + +### border + +Type: `boolean | [boolean, boolean, boolean, boolean]` + +Border configuration - either all sides or [top, right, bottom, left] + +### borderStyle + +Type: `BorderStyle` + +Style of border characters ('single', 'double', 'rounded', 'heavy') + +### title + +Type: `string` + +Optional title displayed in the top border + +### padding + +Type: `number | { top: number, right: number, bottom: number, left: number }` + +Internal spacing between border and content + +### children + +Type: `Renderable[]` + +Child components contained within this box + +## Methods + +### setBorderStyle() + +Change the border style + +#### Signature + +```typescript +setBorderStyle(style: BorderStyle): void +``` + +#### Parameters + +- **style**: `BorderStyle` - New border style to apply + +### setTitle() + +Update the box title + +#### Signature + +```typescript +setTitle(title: string): void +``` + +#### Parameters + +- **title**: `string` - New title text + +### setPadding() + +Adjust internal padding + +#### Signature + +```typescript +setPadding(padding: number | { top: number, right: number, bottom: number, left: number }): void +``` + +#### Parameters + +- **padding**: `number | object` - Uniform padding or individual sides + +### showBorder() + +Show or hide the border + +#### Signature + +```typescript +showBorder(show: boolean): void +``` + +#### Parameters + +- **show**: `boolean` - Whether to display the border + +## Layout Properties + +BoxRenderable supports flexbox layout for arranging child components: + +### flexDirection +- `'row'` - Horizontal layout (default) +- `'column'` - Vertical layout +- `'row-reverse'` - Horizontal, reversed +- `'column-reverse'` - Vertical, reversed + +### justifyContent +- `'flex-start'` - Align to start +- `'flex-end'` - Align to end +- `'center'` - Center items +- `'space-between'` - Space between items +- `'space-around'` - Space around items +- `'space-evenly'` - Even spacing + +### alignItems +- `'flex-start'` - Align to cross-axis start +- `'flex-end'` - Align to cross-axis end +- `'center'` - Center on cross-axis +- `'stretch'` - Stretch to fill +- `'baseline'` - Align text baselines + +## Examples + +### Basic Container + +```typescript +const container = new BoxRenderable('container', { + width: 50, + height: 10, + border: true, + borderStyle: 'single' +}); +``` + +### Titled Panel + +```typescript +const panel = new BoxRenderable('panel', { + width: '100%', + height: 20, + border: true, + borderStyle: 'double', + title: '⚙ Settings', + titleAlignment: 'center', + padding: 2, + backgroundColor: '#1e1e1e' +}); +``` + +### Flexbox Layout + +```typescript +const layout = new BoxRenderable('layout', { + width: '100%', + height: '100%', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: 1 +}); + +// Add child components +const sidebar = new BoxRenderable('sidebar', { + width: 20, + height: '100%', + border: true, + borderStyle: 'single' +}); + +const content = new BoxRenderable('content', { + flexGrow: 1, + height: '100%', + border: true, + borderStyle: 'single', + margin: { left: 1 } +}); + +layout.add(sidebar, 0); +layout.add(content, 1); +``` + +### Nested Boxes + +```typescript +const outer = new BoxRenderable('outer', { + width: 60, + height: 30, + border: true, + borderStyle: 'heavy', + borderColor: '#ff0000', + padding: 2 +}); + +const inner = new BoxRenderable('inner', { + width: '100%', + height: '100%', + border: true, + borderStyle: 'rounded', + borderColor: '#00ff00', + backgroundColor: '#002200', + padding: 1 +}); + +outer.add(inner, 0); +``` + +### Custom Border Characters + +```typescript +const customBox = new BoxRenderable('custom', { + width: 40, + height: 10, + border: true, + customBorderChars: { + topLeft: '╔', + topRight: '╗', + bottomLeft: '╚', + bottomRight: '╝', + horizontal: '═', + vertical: '║', + topT: '╦', + bottomT: '╩', + leftT: '╠', + rightT: '╣', + cross: '╬' + } +}); +``` + +### Interactive Box + +```typescript +const interactive = new BoxRenderable('interactive', { + width: 30, + height: 10, + border: true, + borderColor: '#808080', + focusedBorderColor: '#00ff00', + backgroundColor: '#1e1e1e', + padding: 1, + + onMouseDown: (event) => { + console.log('Box clicked at:', event.x, event.y); + }, + + onMouseOver: (event) => { + interactive.setBorderColor('#ffff00'); + interactive.needsUpdate(); + }, + + onMouseOut: (event) => { + interactive.setBorderColor('#808080'); + interactive.needsUpdate(); + }, + + onKeyDown: (key) => { + if (key.name === 'space') { + // Handle spacebar + } + } +}); + +// Make it focusable +interactive.focus(); +``` + +### Responsive Grid + +```typescript +class GridLayout extends BoxRenderable { + constructor() { + super('grid', { + width: '100%', + height: '100%', + flexDirection: 'column' + }); + + // Create rows + for (let row = 0; row < 3; row++) { + const rowBox = new BoxRenderable(`row-${row}`, { + width: '100%', + flexGrow: 1, + flexDirection: 'row' + }); + + // Create columns in each row + for (let col = 0; col < 3; col++) { + const cell = new BoxRenderable(`cell-${row}-${col}`, { + flexGrow: 1, + height: '100%', + border: true, + borderStyle: 'single', + padding: 1, + margin: 0.5 + }); + + rowBox.add(cell, col); + } + + this.add(rowBox, row); + } + } +} +``` + +## Styling Priority + +When multiple style properties conflict, they are applied in this order: +1. Inline styles (set via methods) +2. Focus styles (when component has focus) +3. Hover styles (when mouse is over) +4. Default styles (from options) + +## Performance Tips + +1. Use `buffered: true` for boxes that update frequently +2. Minimize deeply nested boxes for better rendering performance +3. Use percentage-based sizing for responsive layouts +4. Enable `shouldFill: false` if the box doesn't need to clear its background + +## See Also + +- [BoxOptions](../interfaces/BoxOptions.md) - Configuration options +- [BorderStyle](../types/BorderStyle.md) - Border style types +- [Renderable](./Renderable.md) - Base component class +- [TextRenderable](./TextRenderable.md) - Text content component \ No newline at end of file diff --git a/packages/core/docs/api/reference/classes/CliRenderer.md b/packages/core/docs/api/reference/classes/CliRenderer.md new file mode 100644 index 000000000..bd1730848 --- /dev/null +++ b/packages/core/docs/api/reference/classes/CliRenderer.md @@ -0,0 +1,486 @@ +# CliRenderer + +Main renderer class that manages the terminal output, input handling, and render loop. + +## Constructor + +```typescript +new CliRenderer(lib: RenderLib, rendererPtr: Pointer, stdin: NodeJS.ReadStream, stdout: NodeJS.WriteStream, width: number, height: number, config: CliRendererConfig) +``` + +### Parameters + +#### lib + +Type: `RenderLib` + +#### rendererPtr + +Type: `Pointer` + +#### stdin + +Type: `NodeJS.ReadStream` + +#### stdout + +Type: `NodeJS.WriteStream` + +#### width + +Type: `number` + +#### height + +Type: `number` + +#### config + +Type: `CliRendererConfig` + +## Properties + +### rendererPtr + +Type: `Pointer` + +### nextRenderBuffer + +Type: `OptimizedBuffer` + +Buffer being prepared for next frame + +### currentRenderBuffer + +Type: `OptimizedBuffer` + +Currently displayed buffer + +### root + +Type: `RootRenderable` + +Root renderable that contains all UI components + +### width + +Type: `number` + +Current terminal width in characters + +### height + +Type: `number` + +Current terminal height in characters + +### debugOverlay + +Type: `any` + +Debug information overlay component + +## Methods + +### needsUpdate() + +Mark the renderer as needing to re-render + +#### Signature + +```typescript +needsUpdate(): void +``` + +### setMemorySnapshotInterval() + +#### Signature + +```typescript +setMemorySnapshotInterval(interval: number): void +``` + +#### Parameters + +- **interval**: `number` + +### setBackgroundColor() + +Set the default background color + +#### Signature + +```typescript +setBackgroundColor(color: ColorInput): void +``` + +#### Parameters + +- **color**: `ColorInput` + +### toggleDebugOverlay() + +Toggle debug information display + +#### Signature + +```typescript +toggleDebugOverlay(): void +``` + +### configureDebugOverlay() + +#### Signature + +```typescript +configureDebugOverlay(options: { enabled?: boolean; corner?: DebugOverlayCorner }): void +``` + +#### Parameters + +- **options**: `{ enabled?: boolean; corner?: DebugOverlayCorner }` + +### clearTerminal() + +#### Signature + +```typescript +clearTerminal(): void +``` + +### dumpHitGrid() + +#### Signature + +```typescript +dumpHitGrid(): void +``` + +### dumpBuffers() + +#### Signature + +```typescript +dumpBuffers(timestamp: number): void +``` + +#### Parameters + +- **timestamp**: `number` + +### dumpStdoutBuffer() + +#### Signature + +```typescript +dumpStdoutBuffer(timestamp: number): void +``` + +#### Parameters + +- **timestamp**: `number` + +### setCursorPosition() + +#### Signature + +```typescript +setCursorPosition(x: number, y: number, visible: boolean): void +``` + +#### Parameters + +- **x**: `number` +- **y**: `number` +- **visible**: `boolean` + +### setCursorStyle() + +#### Signature + +```typescript +setCursorStyle(style: CursorStyle, blinking: boolean, color: RGBA): void +``` + +#### Parameters + +- **style**: `CursorStyle` +- **blinking**: `boolean` +- **color**: `RGBA` + +### setCursorColor() + +#### Signature + +```typescript +setCursorColor(color: RGBA): void +``` + +#### Parameters + +- **color**: `RGBA` + +### setCursorPosition() + +#### Signature + +```typescript +setCursorPosition(x: number, y: number, visible: boolean): void +``` + +#### Parameters + +- **x**: `number` +- **y**: `number` +- **visible**: `boolean` + +### setCursorStyle() + +#### Signature + +```typescript +setCursorStyle(style: CursorStyle, blinking: boolean, color: RGBA): void +``` + +#### Parameters + +- **style**: `CursorStyle` +- **blinking**: `boolean` +- **color**: `RGBA` + +### setCursorColor() + +#### Signature + +```typescript +setCursorColor(color: RGBA): void +``` + +#### Parameters + +- **color**: `RGBA` + +### addPostProcessFn() + +#### Signature + +```typescript +addPostProcessFn(processFn: (buffer: OptimizedBuffer, deltaTime: number) => void): void +``` + +#### Parameters + +- **processFn**: `(buffer: OptimizedBuffer, deltaTime: number) => void` + +### removePostProcessFn() + +#### Signature + +```typescript +removePostProcessFn(processFn: (buffer: OptimizedBuffer, deltaTime: number) => void): void +``` + +#### Parameters + +- **processFn**: `(buffer: OptimizedBuffer, deltaTime: number) => void` + +### clearPostProcessFns() + +#### Signature + +```typescript +clearPostProcessFns(): void +``` + +### setFrameCallback() + +#### Signature + +```typescript +setFrameCallback(callback: (deltaTime: number) => Promise): void +``` + +#### Parameters + +- **callback**: `(deltaTime: number) => Promise` + +### removeFrameCallback() + +#### Signature + +```typescript +removeFrameCallback(callback: (deltaTime: number) => Promise): void +``` + +#### Parameters + +- **callback**: `(deltaTime: number) => Promise` + +### clearFrameCallbacks() + +#### Signature + +```typescript +clearFrameCallbacks(): void +``` + +### requestLive() + +#### Signature + +```typescript +requestLive(): void +``` + +### dropLive() + +#### Signature + +```typescript +dropLive(): void +``` + +### start() + +Start the render loop + +#### Signature + +```typescript +start(): void +``` + +### pause() + +#### Signature + +```typescript +pause(): void +``` + +### stop() + +Stop the render loop and clean up + +#### Signature + +```typescript +stop(): void +``` + +### destroy() + +#### Signature + +```typescript +destroy(): void +``` + +### intermediateRender() + +#### Signature + +```typescript +intermediateRender(): void +``` + +### getStats() + +#### Signature + +```typescript +getStats(): { fps: number; frameCount: number; frameTimes: number[]; averageFrameTime: number; minFrameTime: number; maxFrameTime: number; } +``` + +#### Returns + +`{ fps: number; frameCount: number; frameTimes: number[]; averageFrameTime: number; minFrameTime: number; maxFrameTime: number; }` + +### resetStats() + +#### Signature + +```typescript +resetStats(): void +``` + +### setGatherStats() + +#### Signature + +```typescript +setGatherStats(enabled: boolean): void +``` + +#### Parameters + +- **enabled**: `boolean` + +### getSelection() + +#### Signature + +```typescript +getSelection(): Selection +``` + +#### Returns + +`Selection` + +### getSelectionContainer() + +#### Signature + +```typescript +getSelectionContainer(): Renderable +``` + +#### Returns + +`Renderable` + +### hasSelection() + +#### Signature + +```typescript +hasSelection(): boolean +``` + +#### Returns + +`boolean` + +### clearSelection() + +#### Signature + +```typescript +clearSelection(): void +``` + +## Examples + +```typescript +// Create and configure renderer +const renderer = new CliRenderer(lib, ptr, stdin, stdout, 80, 24, { + backgroundColor: '#1e1e1e', + showFPS: true, + debugOverlayCorner: DebugOverlayCorner.TOP_RIGHT +}); + +// Add components +renderer.root.add(new BoxRenderable('main', { + width: '100%', + height: '100%', + border: true, + borderStyle: 'rounded' +})); + +// Start rendering +renderer.start(); +``` + +## See Also + diff --git a/packages/core/docs/api/reference/classes/InputRenderable.md b/packages/core/docs/api/reference/classes/InputRenderable.md new file mode 100644 index 000000000..6065c32ae --- /dev/null +++ b/packages/core/docs/api/reference/classes/InputRenderable.md @@ -0,0 +1,311 @@ +# InputRenderable + +Single-line text input component with cursor management, selection, and validation support. Provides a terminal-based text field similar to HTML input elements. + +## Constructor + +```typescript +new InputRenderable(id: string, options: InputRenderableOptions) +``` + +### Parameters + +#### id + +Type: `string` + +Unique identifier for this input component + +#### options + +Type: `InputRenderableOptions` + +Configuration options for the input field. Key properties include: + +| Property | Type | Description | +|----------|------|-------------| +| `value` | `string` | Initial input value | +| `placeholder` | `string` | Placeholder text when empty | +| `maxLength` | `number` | Maximum character limit | +| `password` | `boolean` | Mask input for passwords | +| `cursorStyle` | `'block' \| 'line' \| 'underline'` | Cursor appearance | +| `cursorColor` | `string \| RGBA` | Cursor color | +| `focusedBorderColor` | `string \| RGBA` | Border color when focused | +| `backgroundColor` | `string \| RGBA` | Input background color | +| `textColor` | `string \| RGBA` | Input text color | +| `placeholderColor` | `string \| RGBA` | Placeholder text color | +| `pattern` | `RegExp` | Validation pattern | +| `onChange` | `(value: string) => void` | Value change callback | +| `onSubmit` | `(value: string) => void` | Enter key callback | + +## Properties + +### value + +Type: `string` + +Current input value + +### cursorPosition + +Type: `number` + +Current cursor position in the text + +### selectionStart + +Type: `number` + +Start position of text selection + +### selectionEnd + +Type: `number` + +End position of text selection + +## Methods + +### focus() + +Give keyboard focus to the input field + +#### Signature + +```typescript +focus(): void +``` + +### blur() + +Remove keyboard focus from the input field + +#### Signature + +```typescript +blur(): void +``` + +### setValue() + +Set the input value programmatically + +#### Signature + +```typescript +setValue(value: string): void +``` + +#### Parameters + +- **value**: `string` - New value to set + +### clear() + +Clear the input value + +#### Signature + +```typescript +clear(): void +``` + +### selectAll() + +Select all text in the input + +#### Signature + +```typescript +selectAll(): void +``` + +### setCursorPosition() + +Move cursor to specific position + +#### Signature + +```typescript +setCursorPosition(position: number): void +``` + +#### Parameters + +- **position**: `number` - Character position (0-based) + +### insertText() + +Insert text at cursor position + +#### Signature + +```typescript +insertText(text: string): void +``` + +#### Parameters + +- **text**: `string` - Text to insert + +### deleteSelection() + +Delete currently selected text + +#### Signature + +```typescript +deleteSelection(): void +``` + +### handleKeyPress() + +Process keyboard input + +#### Signature + +```typescript +handleKeyPress(key: ParsedKey | string): boolean +``` + +#### Parameters + +- **key**: `ParsedKey | string` - Keyboard event + +#### Returns + +`boolean` - True if the input handled the key + +### validate() + +Check if current value is valid + +#### Signature + +```typescript +validate(): boolean +``` + +#### Returns + +`boolean` - True if value passes validation + +## Examples + +### Basic Input Field + +```typescript +const nameInput = new InputRenderable('name', { + placeholder: 'Enter your name...', + width: 30, + onChange: (value) => { + console.log('Name:', value); + } +}); +``` + +### Password Input + +```typescript +const passwordInput = new InputRenderable('password', { + placeholder: 'Password', + password: true, + minLength: 8, + maxLength: 32, + onSubmit: (value) => { + login(value); + } +}); +``` + +### Validated Input + +```typescript +const emailInput = new InputRenderable('email', { + placeholder: 'Email address', + pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, + onChange: (value) => { + if (!emailInput.validate()) { + emailInput.setBorderColor('#ff0000'); + } else { + emailInput.setBorderColor('#00ff00'); + } + } +}); +``` + +### Styled Input + +```typescript +const styledInput = new InputRenderable('styled', { + value: 'Initial text', + backgroundColor: '#2e2e2e', + textColor: '#ffffff', + cursorColor: '#00ff00', + cursorStyle: 'block', + focusedBorderColor: '#00aaff', + border: true, + borderStyle: 'rounded', + padding: 1 +}); +``` + +### Form with Multiple Inputs + +```typescript +class LoginForm extends BoxRenderable { + private usernameInput: InputRenderable; + private passwordInput: InputRenderable; + + constructor() { + super('login-form', { + flexDirection: 'column', + gap: 1, + padding: 2, + border: true, + title: 'Login' + }); + + this.usernameInput = new InputRenderable('username', { + placeholder: 'Username', + width: '100%', + onSubmit: () => this.passwordInput.focus() + }); + + this.passwordInput = new InputRenderable('password', { + placeholder: 'Password', + password: true, + width: '100%', + onSubmit: () => this.submit() + }); + + this.add(this.usernameInput, 0); + this.add(this.passwordInput, 1); + } + + private submit() { + const username = this.usernameInput.value; + const password = this.passwordInput.value; + // Handle login + } +} +``` + +## Keyboard Shortcuts + +- **Left/Right Arrow** - Move cursor +- **Home/End** - Jump to start/end +- **Backspace** - Delete before cursor +- **Delete** - Delete after cursor +- **Ctrl+A** - Select all +- **Ctrl+C** - Copy selection +- **Ctrl+V** - Paste +- **Ctrl+X** - Cut selection +- **Enter** - Submit (triggers onSubmit) +- **Escape** - Blur input + +## See Also + +- [InputRenderableOptions](../interfaces/InputRenderableOptions.md) - Configuration options +- [Renderable](./Renderable.md) - Base component class +- [TextRenderable](./TextRenderable.md) - Text display component \ No newline at end of file diff --git a/packages/core/docs/api/reference/classes/MouseEvent.md b/packages/core/docs/api/reference/classes/MouseEvent.md new file mode 100644 index 000000000..eebaf1f38 --- /dev/null +++ b/packages/core/docs/api/reference/classes/MouseEvent.md @@ -0,0 +1,109 @@ +# MouseEvent + +Represents a mouse event in the terminal UI. Handles mouse interactions including clicks, drags, scrolls, and hover events. + +## Constructor + +```typescript +new MouseEvent(target: Renderable | null, attributes: RawMouseEvent & { source?: Renderable }) +``` + +### Parameters + +#### target + +Type: `Renderable | null` + +#### attributes + +Type: `RawMouseEvent & { source?: Renderable }` + +## Properties + +### type + +Type: `MouseEventType` + +The type of mouse event (down, up, move, drag, drag-end, drop, over, out, scroll) + +### button + +Type: `number` + +Which mouse button was pressed (0=left, 1=middle, 2=right) + +### x + +Type: `number` + +X coordinate of the mouse event relative to the terminal + +### y + +Type: `number` + +Y coordinate of the mouse event relative to the terminal + +### source + +Type: `Renderable` + +The renderable that originally received the event + +### modifiers + +Type: `{ + shift: boolean + alt: boolean + ctrl: boolean + }` + +Keyboard modifiers active during the event + +### scroll + +Type: `ScrollInfo` + +Scroll information if this is a scroll event + +### target + +Type: `Renderable | null` + +The renderable that is the target of the event (may be different from source due to bubbling) + +## Methods + +### preventDefault() + +Prevents the default action for this event from occurring + +#### Signature + +```typescript +preventDefault(): void +``` + +## Examples + +```typescript +// Handle mouse clicks +renderable.onMouseDown = (event: MouseEvent) => { + if (event.button === 0) { // Left click + console.log(`Clicked at ${event.x}, ${event.y}`); + event.preventDefault(); + } +}; + +// Handle mouse scroll +renderable.onMouseScroll = (event: MouseEvent) => { + if (event.scroll.direction === 'up') { + scrollUp(); + } +}; +``` + +## See Also + +- [Renderable](./Renderable.md) - Base class that handles mouse events +- [MouseEventType](../types/MouseEventType.md) - Event type enumeration diff --git a/packages/core/docs/api/reference/classes/OptimizedBuffer.md b/packages/core/docs/api/reference/classes/OptimizedBuffer.md new file mode 100644 index 000000000..3cd21c298 --- /dev/null +++ b/packages/core/docs/api/reference/classes/OptimizedBuffer.md @@ -0,0 +1,511 @@ +# OptimizedBuffer + +High-performance buffer for terminal rendering. Manages character cells, colors, and dirty regions for efficient updates. + +## Constructor + +```typescript +new OptimizedBuffer(lib: RenderLib, ptr: Pointer, buffer: { + char: Uint32Array + fg: Float32Array + bg: Float32Array + attributes: Uint8Array + }, width: number, height: number, options: { respectAlpha?: boolean }) +``` + +### Parameters + +#### lib + +Type: `RenderLib` + +#### ptr + +Type: `Pointer` + +#### buffer + +Type: `{ + char: Uint32Array + fg: Float32Array + bg: Float32Array + attributes: Uint8Array + }` + +#### width + +Type: `number` + +#### height + +Type: `number` + +#### options + +Type: `{ respectAlpha?: boolean }` + +## Properties + +### id + +Type: `string` + +### lib + +Type: `RenderLib` + +### respectAlpha + +Type: `boolean` + +## Methods + +### create() + +#### Signature + +```typescript +create(width: number, height: number, options: { respectAlpha?: boolean }): OptimizedBuffer +``` + +#### Parameters + +- **width**: `number` +- **height**: `number` +- **options**: `{ respectAlpha?: boolean }` + +#### Returns + +`OptimizedBuffer` + +### getWidth() + +#### Signature + +```typescript +getWidth(): number +``` + +#### Returns + +`number` + +### getHeight() + +#### Signature + +```typescript +getHeight(): number +``` + +#### Returns + +`number` + +### setRespectAlpha() + +#### Signature + +```typescript +setRespectAlpha(respectAlpha: boolean): void +``` + +#### Parameters + +- **respectAlpha**: `boolean` + +### clear() + +Clear the entire buffer or a region + +#### Signature + +```typescript +clear(bg: RGBA, clearChar: string): void +``` + +#### Parameters + +- **bg**: `RGBA` +- **clearChar**: `string` + +### clearLocal() + +#### Signature + +```typescript +clearLocal(bg: RGBA, clearChar: string): void +``` + +#### Parameters + +- **bg**: `RGBA` +- **clearChar**: `string` + +### setCell() + +Set a single character cell + +#### Signature + +```typescript +setCell(x: number, y: number, char: string, fg: RGBA, bg: RGBA, attributes: number): void +``` + +#### Parameters + +- **x**: `number` +- **y**: `number` +- **char**: `string` +- **fg**: `RGBA` +- **bg**: `RGBA` +- **attributes**: `number` + +### get() + +#### Signature + +```typescript +get(x: number, y: number): { char: number; fg: RGBA; bg: RGBA; attributes: number; } +``` + +#### Parameters + +- **x**: `number` +- **y**: `number` + +#### Returns + +`{ char: number; fg: RGBA; bg: RGBA; attributes: number; }` + +### setCellWithAlphaBlending() + +#### Signature + +```typescript +setCellWithAlphaBlending(x: number, y: number, char: string, fg: RGBA, bg: RGBA, attributes: number): void +``` + +#### Parameters + +- **x**: `number` +- **y**: `number` +- **char**: `string` +- **fg**: `RGBA` +- **bg**: `RGBA` +- **attributes**: `number` + +### setCellWithAlphaBlendingLocal() + +#### Signature + +```typescript +setCellWithAlphaBlendingLocal(x: number, y: number, char: string, fg: RGBA, bg: RGBA, attributes: number): void +``` + +#### Parameters + +- **x**: `number` +- **y**: `number` +- **char**: `string` +- **fg**: `RGBA` +- **bg**: `RGBA` +- **attributes**: `number` + +### drawText() + +Draw text at specified position with color + +#### Signature + +```typescript +drawText(text: string, x: number, y: number, fg: RGBA, bg: RGBA, attributes: number, selection: { start: number; end: number; bgColor?: RGBA; fgColor?: RGBA } | null): void +``` + +#### Parameters + +- **text**: `string` +- **x**: `number` +- **y**: `number` +- **fg**: `RGBA` +- **bg**: `RGBA` +- **attributes**: `number` +- **selection**: `{ start: number; end: number; bgColor?: RGBA; fgColor?: RGBA } | null` + +### fillRect() + +#### Signature + +```typescript +fillRect(x: number, y: number, width: number, height: number, bg: RGBA): void +``` + +#### Parameters + +- **x**: `number` +- **y**: `number` +- **width**: `number` +- **height**: `number` +- **bg**: `RGBA` + +### fillRectLocal() + +#### Signature + +```typescript +fillRectLocal(x: number, y: number, width: number, height: number, bg: RGBA): void +``` + +#### Parameters + +- **x**: `number` +- **y**: `number` +- **width**: `number` +- **height**: `number` +- **bg**: `RGBA` + +### drawFrameBuffer() + +#### Signature + +```typescript +drawFrameBuffer(destX: number, destY: number, frameBuffer: OptimizedBuffer, sourceX: number, sourceY: number, sourceWidth: number, sourceHeight: number): void +``` + +#### Parameters + +- **destX**: `number` +- **destY**: `number` +- **frameBuffer**: `OptimizedBuffer` +- **sourceX**: `number` +- **sourceY**: `number` +- **sourceWidth**: `number` +- **sourceHeight**: `number` + +### drawFrameBufferLocal() + +#### Signature + +```typescript +drawFrameBufferLocal(destX: number, destY: number, frameBuffer: OptimizedBuffer, sourceX: number, sourceY: number, sourceWidth: number, sourceHeight: number): void +``` + +#### Parameters + +- **destX**: `number` +- **destY**: `number` +- **frameBuffer**: `OptimizedBuffer` +- **sourceX**: `number` +- **sourceY**: `number` +- **sourceWidth**: `number` +- **sourceHeight**: `number` + +### destroy() + +#### Signature + +```typescript +destroy(): void +``` + +### drawTextBuffer() + +#### Signature + +```typescript +drawTextBuffer(textBuffer: TextBuffer, x: number, y: number, clipRect: { x: number; y: number; width: number; height: number }): void +``` + +#### Parameters + +- **textBuffer**: `TextBuffer` +- **x**: `number` +- **y**: `number` +- **clipRect**: `{ x: number; y: number; width: number; height: number }` + +### drawSuperSampleBuffer() + +#### Signature + +```typescript +drawSuperSampleBuffer(x: number, y: number, pixelDataPtr: Pointer, pixelDataLength: number, format: "bgra8unorm" | "rgba8unorm", alignedBytesPerRow: number): void +``` + +#### Parameters + +- **x**: `number` +- **y**: `number` +- **pixelDataPtr**: `Pointer` +- **pixelDataLength**: `number` +- **format**: `"bgra8unorm" | "rgba8unorm"` +- **alignedBytesPerRow**: `number` + +### drawSuperSampleBufferFFI() + +#### Signature + +```typescript +drawSuperSampleBufferFFI(x: number, y: number, pixelDataPtr: Pointer, pixelDataLength: number, format: "bgra8unorm" | "rgba8unorm", alignedBytesPerRow: number): void +``` + +#### Parameters + +- **x**: `number` +- **y**: `number` +- **pixelDataPtr**: `Pointer` +- **pixelDataLength**: `number` +- **format**: `"bgra8unorm" | "rgba8unorm"` +- **alignedBytesPerRow**: `number` + +### drawPackedBuffer() + +#### Signature + +```typescript +drawPackedBuffer(dataPtr: Pointer, dataLen: number, posX: number, posY: number, terminalWidthCells: number, terminalHeightCells: number): void +``` + +#### Parameters + +- **dataPtr**: `Pointer` +- **dataLen**: `number` +- **posX**: `number` +- **posY**: `number` +- **terminalWidthCells**: `number` +- **terminalHeightCells**: `number` + +### setCellWithAlphaBlendingFFI() + +#### Signature + +```typescript +setCellWithAlphaBlendingFFI(x: number, y: number, char: string, fg: RGBA, bg: RGBA, attributes: number): void +``` + +#### Parameters + +- **x**: `number` +- **y**: `number` +- **char**: `string` +- **fg**: `RGBA` +- **bg**: `RGBA` +- **attributes**: `number` + +### fillRectFFI() + +#### Signature + +```typescript +fillRectFFI(x: number, y: number, width: number, height: number, bg: RGBA): void +``` + +#### Parameters + +- **x**: `number` +- **y**: `number` +- **width**: `number` +- **height**: `number` +- **bg**: `RGBA` + +### resize() + +#### Signature + +```typescript +resize(width: number, height: number): void +``` + +#### Parameters + +- **width**: `number` +- **height**: `number` + +### clearFFI() + +#### Signature + +```typescript +clearFFI(bg: RGBA): void +``` + +#### Parameters + +- **bg**: `RGBA` + +### drawTextFFI() + +#### Signature + +```typescript +drawTextFFI(text: string, x: number, y: number, fg: RGBA, bg: RGBA, attributes: number): void +``` + +#### Parameters + +- **text**: `string` +- **x**: `number` +- **y**: `number` +- **fg**: `RGBA` +- **bg**: `RGBA` +- **attributes**: `number` + +### drawFrameBufferFFI() + +#### Signature + +```typescript +drawFrameBufferFFI(destX: number, destY: number, frameBuffer: OptimizedBuffer, sourceX: number, sourceY: number, sourceWidth: number, sourceHeight: number): void +``` + +#### Parameters + +- **destX**: `number` +- **destY**: `number` +- **frameBuffer**: `OptimizedBuffer` +- **sourceX**: `number` +- **sourceY**: `number` +- **sourceWidth**: `number` +- **sourceHeight**: `number` + +### drawBox() + +Draw a box with optional border + +#### Signature + +```typescript +drawBox(options: { + x: number + y: number + width: number + height: number + borderStyle?: BorderStyle + customBorderChars?: Uint32Array + border: boolean | BorderSides[] + borderColor: RGBA + backgroundColor: RGBA + shouldFill?: boolean + title?: string + titleAlignment?: "left" | "center" | "right" + }): void +``` + +#### Parameters + +- **options**: `{ + x: number + y: number + width: number + height: number + borderStyle?: BorderStyle + customBorderChars?: Uint32Array + border: boolean | BorderSides[] + borderColor: RGBA + backgroundColor: RGBA + shouldFill?: boolean + title?: string + titleAlignment?: "left" | "center" | "right" + }` + +## See Also + diff --git a/packages/core/docs/api/reference/classes/Renderable.md b/packages/core/docs/api/reference/classes/Renderable.md new file mode 100644 index 000000000..b19ef36bf --- /dev/null +++ b/packages/core/docs/api/reference/classes/Renderable.md @@ -0,0 +1,392 @@ +# Renderable + +Base class for all visual components in OpenTUI. Provides layout, rendering, and event handling capabilities. + +## Constructor + +```typescript +new Renderable(id: string, options: RenderableOptions) +``` + +### Parameters + +#### id + +Type: `string` + +#### options + +Type: `RenderableOptions` + +Available options: + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `alignItems` | `AlignString` | | | +| `bottom` | `any` | | | +| `buffered` | `boolean` | | | +| `enableLayout` | `boolean` | | | +| `flexBasis` | `any` | | | +| `flexDirection` | `FlexDirectionString` | | | +| `flexGrow` | `number` | | | +| `flexShrink` | `number` | | | +| `height` | `any` | | | +| `justifyContent` | `JustifyString` | | | + +...and 32 more properties + +## Properties + +### renderablesByNumber + +Type: `Map` + +Static map for fast renderable lookups by numeric ID + +### id + +Type: `string` + +Unique identifier for this renderable + +### num + +Type: `number` + +Internal numeric identifier used for fast lookups + +### selectable + +Type: `boolean` + +Whether this component can receive text selection + +### parent + +Type: `Renderable | null` + +Parent renderable in the component tree + +## Methods + +### hasSelection() + +Check if this renderable has active text selection + +#### Signature + +```typescript +hasSelection(): boolean +``` + +#### Returns + +`boolean` + +### onSelectionChanged() + +#### Signature + +```typescript +onSelectionChanged(selection: SelectionState | null): boolean +``` + +#### Parameters + +- **selection**: `SelectionState | null` + +#### Returns + +`boolean` + +### getSelectedText() + +Get currently selected text if any + +#### Signature + +```typescript +getSelectedText(): string +``` + +#### Returns + +`string` + +### shouldStartSelection() + +#### Signature + +```typescript +shouldStartSelection(x: number, y: number): boolean +``` + +#### Parameters + +- **x**: `number` +- **y**: `number` + +#### Returns + +`boolean` + +### focus() + +Give keyboard focus to this renderable + +#### Signature + +```typescript +focus(): void +``` + +### blur() + +Remove keyboard focus from this renderable + +#### Signature + +```typescript +blur(): void +``` + +### handleKeyPress() + +Process keyboard input - returns true if handled + +#### Signature + +```typescript +handleKeyPress(key: ParsedKey | string): boolean +``` + +#### Parameters + +- **key**: `ParsedKey | string` + +#### Returns + +`boolean` + +### needsUpdate() + +Mark this renderable as needing re-render + +#### Signature + +```typescript +needsUpdate(): void +``` + +### requestZIndexSort() + +#### Signature + +```typescript +requestZIndexSort(): void +``` + +### setPosition() + +#### Signature + +```typescript +setPosition(position: Position): void +``` + +#### Parameters + +- **position**: `Position` + +### getLayoutNode() + +#### Signature + +```typescript +getLayoutNode(): TrackedNode +``` + +#### Returns + +`TrackedNode` + +### updateFromLayout() + +#### Signature + +```typescript +updateFromLayout(): void +``` + +### add() + +Add a child renderable at the specified index + +#### Signature + +```typescript +add(obj: Renderable, index: number): number +``` + +#### Parameters + +- **obj**: `Renderable` +- **index**: `number` + +#### Returns + +`number` + +### insertBefore() + +#### Signature + +```typescript +insertBefore(obj: Renderable, anchor: Renderable): number +``` + +#### Parameters + +- **obj**: `Renderable` +- **anchor**: `Renderable` + +#### Returns + +`number` + +### propagateContext() + +#### Signature + +```typescript +propagateContext(ctx: RenderContext | null): void +``` + +#### Parameters + +- **ctx**: `RenderContext | null` + +### getRenderable() + +#### Signature + +```typescript +getRenderable(id: string): Renderable +``` + +#### Parameters + +- **id**: `string` + +#### Returns + +`Renderable` + +### remove() + +Remove a child renderable by ID + +#### Signature + +```typescript +remove(id: string): void +``` + +#### Parameters + +- **id**: `string` + +### getChildren() + +#### Signature + +```typescript +getChildren(): Renderable[] +``` + +#### Returns + +`Renderable[]` + +### render() + +Render this component and its children to the buffer + +#### Signature + +```typescript +render(buffer: OptimizedBuffer, deltaTime: number): void +``` + +#### Parameters + +- **buffer**: `OptimizedBuffer` +- **deltaTime**: `number` + +### destroy() + +Clean up resources and remove from parent + +#### Signature + +```typescript +destroy(): void +``` + +### destroyRecursively() + +#### Signature + +```typescript +destroyRecursively(): void +``` + +### processMouseEvent() + +Process mouse events and propagate to children + +#### Signature + +```typescript +processMouseEvent(event: MouseEvent): void +``` + +#### Parameters + +- **event**: `MouseEvent` + +## Examples + +```typescript +// Create a custom renderable +class MyComponent extends Renderable { + constructor(id: string, options: RenderableOptions) { + super(id, options); + } + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + // Custom rendering logic + buffer.drawText('Hello World', this.x, this.y, RGBA.white()); + } +} + +// Add event handlers +const component = new MyComponent('my-comp', { + width: 20, + height: 10, + onMouseDown: (event) => { + console.log('Clicked!'); + }, + onKeyDown: (key) => { + if (key.name === 'escape') { + component.blur(); + } + } +}); +``` + +## See Also + +- [RenderableOptions](../interfaces/RenderableOptions.md) - Configuration options +- [MouseEvent](./MouseEvent.md) - Mouse event handling +- [OptimizedBuffer](./OptimizedBuffer.md) - Rendering target diff --git a/packages/core/docs/api/reference/classes/RootRenderable.md b/packages/core/docs/api/reference/classes/RootRenderable.md new file mode 100644 index 000000000..7349b8657 --- /dev/null +++ b/packages/core/docs/api/reference/classes/RootRenderable.md @@ -0,0 +1,59 @@ +# RootRenderable + +## Constructor + +```typescript +new RootRenderable(width: number, height: number, ctx: RenderContext, rootContext: RootContext) +``` + +### Parameters + +#### width + +Type: `number` + +#### height + +Type: `number` + +#### ctx + +Type: `RenderContext` + +#### rootContext + +Type: `RootContext` + +## Methods + +### requestLayout() + +#### Signature + +```typescript +requestLayout(): void +``` + +### calculateLayout() + +#### Signature + +```typescript +calculateLayout(): void +``` + +### resize() + +#### Signature + +```typescript +resize(width: number, height: number): void +``` + +#### Parameters + +- **width**: `number` +- **height**: `number` + +## See Also + diff --git a/packages/core/docs/api/reference/classes/TextRenderable.md b/packages/core/docs/api/reference/classes/TextRenderable.md new file mode 100644 index 000000000..d23ac25f7 --- /dev/null +++ b/packages/core/docs/api/reference/classes/TextRenderable.md @@ -0,0 +1,228 @@ +# TextRenderable + +Text display component with support for styling, word wrapping, and text selection. Extends Renderable to provide rich text rendering capabilities in the terminal. + +## Constructor + +```typescript +new TextRenderable(id: string, options: TextOptions) +``` + +### Parameters + +#### id + +Type: `string` + +Unique identifier for this text component + +#### options + +Type: `TextOptions` + +Configuration options for the text component. Key properties include: + +| Property | Type | Description | +|----------|------|-------------| +| `text` | `string` | The text content to display | +| `color` | `string \| RGBA` | Text foreground color | +| `backgroundColor` | `string \| RGBA` | Text background color | +| `align` | `'left' \| 'center' \| 'right'` | Text alignment | +| `wrap` | `boolean` | Enable word wrapping | +| `selectable` | `boolean` | Allow text selection | +| `bold` | `boolean` | Bold text style | +| `italic` | `boolean` | Italic text style | +| `underline` | `boolean` | Underline text style | +| `parseMarkup` | `boolean` | Parse style markup in text | + +## Properties + +### selectable + +Type: `boolean` + +Whether text selection is enabled for this component + +### text + +Type: `string` + +The current text content + +### wrap + +Type: `boolean` + +Whether word wrapping is enabled + +## Methods + +### setText() + +Update the text content + +#### Signature + +```typescript +setText(text: string): void +``` + +#### Parameters + +- **text**: `string` - New text content to display + +### shouldStartSelection() + +Determine if selection should start at given coordinates + +#### Signature + +```typescript +shouldStartSelection(x: number, y: number): boolean +``` + +#### Parameters + +- **x**: `number` - X coordinate relative to component +- **y**: `number` - Y coordinate relative to component + +#### Returns + +`boolean` - True if selection can start at this position + +### onSelectionChanged() + +Handle selection state changes + +#### Signature + +```typescript +onSelectionChanged(selection: SelectionState | null): boolean +``` + +#### Parameters + +- **selection**: `SelectionState | null` - New selection state or null to clear + +#### Returns + +`boolean` - True if selection was handled + +### getSelectedText() + +Get currently selected text + +#### Signature + +```typescript +getSelectedText(): string +``` + +#### Returns + +`string` - The selected text content, empty string if no selection + +### hasSelection() + +Check if any text is currently selected + +#### Signature + +```typescript +hasSelection(): boolean +``` + +#### Returns + +`boolean` - True if text is selected + +### destroy() + +Clean up resources and remove from parent + +#### Signature + +```typescript +destroy(): void +``` + +## Examples + +### Basic Text + +```typescript +const label = new TextRenderable('label', { + text: 'Hello World', + color: '#ffffff', + backgroundColor: '#0000ff' +}); +``` + +### Centered Text with Wrapping + +```typescript +const paragraph = new TextRenderable('paragraph', { + text: 'This is a long paragraph that will wrap to multiple lines when displayed in the terminal.', + align: 'center', + wrap: true, + width: 40 +}); +``` + +### Styled Text with Markup + +```typescript +const styledText = new TextRenderable('styled', { + text: '{bold}Important:{/} {red}Error{/} - {underline}Please fix{/}', + parseMarkup: true +}); +``` + +### Selectable Text + +```typescript +const selectableText = new TextRenderable('selectable', { + text: 'You can select this text with the mouse', + selectable: true, + selectionBg: '#ffff00', + selectionFg: '#000000', + onSelectionChanged: (selection) => { + if (selection) { + console.log('Selected:', selectableText.getSelectedText()); + } + } +}); +``` + +### Dynamic Text Updates + +```typescript +const counter = new TextRenderable('counter', { + text: 'Count: 0', + color: '#00ff00' +}); + +let count = 0; +setInterval(() => { + count++; + counter.setText(`Count: ${count}`); + counter.needsUpdate(); +}, 1000); +``` + +## Markup Syntax + +When `parseMarkup` is enabled, you can use inline styles: + +- `{bold}text{/}` - Bold text +- `{italic}text{/}` - Italic text +- `{underline}text{/}` - Underlined text +- `{red}text{/}` - Red text (supports all CSS color names) +- `{#ff0000}text{/}` - Hex color codes +- `{bg:blue}text{/}` - Background colors + +## See Also + +- [TextOptions](../interfaces/TextOptions.md) - Configuration options +- [Renderable](./Renderable.md) - Base component class +- [RGBA](./RGBA.md) - Color utilities \ No newline at end of file diff --git a/packages/core/docs/api/reference/classes/Timeline.md b/packages/core/docs/api/reference/classes/Timeline.md new file mode 100644 index 000000000..3b2abc582 --- /dev/null +++ b/packages/core/docs/api/reference/classes/Timeline.md @@ -0,0 +1,225 @@ +# Timeline + +Animation timeline system for orchestrating complex animations and transitions. + +## Constructor + +```typescript +new Timeline(options: TimelineOptions) +``` + +### Parameters + +#### options + +Type: `TimelineOptions` + +Available options: + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `autoplay` | `boolean` | | | +| `duration` | `number` | | | +| `loop` | `boolean` | | | +| `onComplete` | `any` | | () => void | +| `onPause` | `any` | | () => void | + +## Properties + +### items + +Type: `(TimelineAnimationItem | TimelineCallbackItem)[]` + +### subTimelines + +Type: `TimelineTimelineItem[]` + +### currentTime + +Type: `number` + +Current playback position + +### isPlaying + +Type: `boolean` + +Whether the timeline is currently playing + +### isComplete + +Type: `boolean` + +### duration + +Type: `number` + +Total duration of the timeline in milliseconds + +### loop + +Type: `boolean` + +Whether to loop when reaching the end + +### synced + +Type: `boolean` + +## Methods + +### add() + +Add an animation to the timeline + +#### Signature + +```typescript +add(target: any, properties: AnimationOptions, startTime: number | string): this +``` + +#### Parameters + +- **target**: `any` +- **properties**: `AnimationOptions` +- **startTime**: `number | string` + +#### Returns + +`this` + +### once() + +#### Signature + +```typescript +once(target: any, properties: AnimationOptions): this +``` + +#### Parameters + +- **target**: `any` +- **properties**: `AnimationOptions` + +#### Returns + +`this` + +### call() + +#### Signature + +```typescript +call(callback: () => void, startTime: number | string): this +``` + +#### Parameters + +- **callback**: `() => void` +- **startTime**: `number | string` + +#### Returns + +`this` + +### sync() + +#### Signature + +```typescript +sync(timeline: Timeline, startTime: number): this +``` + +#### Parameters + +- **timeline**: `Timeline` +- **startTime**: `number` + +#### Returns + +`this` + +### play() + +Start or resume playback + +#### Signature + +```typescript +play(): this +``` + +#### Returns + +`this` + +### pause() + +Pause playback + +#### Signature + +```typescript +pause(): this +``` + +#### Returns + +`this` + +### resetItems() + +#### Signature + +```typescript +resetItems(): void +``` + +### restart() + +#### Signature + +```typescript +restart(): this +``` + +#### Returns + +`this` + +### update() + +#### Signature + +```typescript +update(deltaTime: number): void +``` + +#### Parameters + +- **deltaTime**: `number` + +## Examples + +```typescript +// Create animation timeline +const timeline = new Timeline({ + duration: 2000, + loop: true, + autoplay: true +}); + +// Add animations +timeline.add({ + target: myComponent, + properties: { + x: { from: 0, to: 100 }, + opacity: { from: 0, to: 1 } + }, + duration: 1000, + easing: 'easeInOutQuad' +}); +``` + +## See Also + diff --git a/packages/core/docs/api/reference/index.md b/packages/core/docs/api/reference/index.md new file mode 100644 index 000000000..19d543ada --- /dev/null +++ b/packages/core/docs/api/reference/index.md @@ -0,0 +1,43 @@ +# OpenTUI API Reference + +Complete API documentation for OpenTUI, generated from source code. + +## Classes + +- [Renderable](./classes/Renderable.md) - 22 methods +- [RootRenderable](./classes/RootRenderable.md) - 3 methods +- [MouseEvent](./classes/MouseEvent.md) - 1 methods +- [CliRenderer](./classes/CliRenderer.md) - 35 methods +- [OptimizedBuffer](./classes/OptimizedBuffer.md) - 27 methods +- [BoxRenderable](./classes/BoxRenderable.md) - 0 methods +- [TextRenderable](./classes/TextRenderable.md) - 5 methods +- [ASCIIFontRenderable](./classes/ASCIIFontRenderable.md) - 4 methods +- [InputRenderable](./classes/InputRenderable.md) - 3 methods +- [Timeline](./classes/Timeline.md) - 9 methods + +## Interfaces + +- [RootContext](./interfaces/RootContext.md) +- [Position](./interfaces/Position.md) +- [LayoutOptions](./interfaces/LayoutOptions.md) +- [RenderableOptions](./interfaces/RenderableOptions.md) +- [CliRendererConfig](./interfaces/CliRendererConfig.md) +- [BoxOptions](./interfaces/BoxOptions.md) +- [TextOptions](./interfaces/TextOptions.md) +- [ASCIIFontOptions](./interfaces/ASCIIFontOptions.md) +- [InputRenderableOptions](./interfaces/InputRenderableOptions.md) +- [TimelineOptions](./interfaces/TimelineOptions.md) +- [AnimationOptions](./interfaces/AnimationOptions.md) +- [JSAnimation](./interfaces/JSAnimation.md) + +## Type Definitions + +- [BorderConfig](./types/BorderConfig.md) +- [BoxDrawOptions](./types/BoxDrawOptions.md) +- [ConsoleOptions](./types/ConsoleOptions.md) +- [ExplosionEffectParameters](./types/ExplosionEffectParameters.md) +- [FrameBufferOptions](./types/FrameBufferOptions.md) +- [Renderable.class](./types/Renderable.class.md) +- [SelectRenderableOptions](./types/SelectRenderableOptions.md) +- [TabSelectRenderableOptions](./types/TabSelectRenderableOptions.md) +- [ThreeCliRendererOptions](./types/ThreeCliRendererOptions.md) diff --git a/packages/core/docs/api/reference/interfaces/ASCIIFontOptions.md b/packages/core/docs/api/reference/interfaces/ASCIIFontOptions.md new file mode 100644 index 000000000..c2096c703 --- /dev/null +++ b/packages/core/docs/api/reference/interfaces/ASCIIFontOptions.md @@ -0,0 +1,222 @@ +# ASCIIFontOptions + +Interface defining the structure for ASCIIFontOptions. + +## Properties + +### alignItems? + +**Type:** `AlignString` + +### bg? + +**Type:** `RGBA` + +### bottom? + +**Type:** `number | string | string` + +### buffered? + +**Type:** `boolean` + +### enableLayout? + +**Type:** `boolean` + +### fg? + +**Type:** `RGBA | array` + +### flexBasis? + +**Type:** `number | string` + +### flexDirection? + +**Type:** `FlexDirectionString` + +### flexGrow? + +**Type:** `number` + +### flexShrink? + +**Type:** `number` + +### font? + +**Type:** `string` + +### height? + +**Type:** `number | string | string` + +### justifyContent? + +**Type:** `JustifyString` + +### left? + +**Type:** `number | string | string` + +### live? + +**Type:** `boolean` + +### margin? + +**Type:** `number | string | string` + +### marginBottom? + +**Type:** `number | string | string` + +### marginLeft? + +**Type:** `number | string | string` + +### marginRight? + +**Type:** `number | string | string` + +### marginTop? + +**Type:** `number | string | string` + +### maxHeight? + +**Type:** `number` + +### maxWidth? + +**Type:** `number` + +### minHeight? + +**Type:** `number` + +### minWidth? + +**Type:** `number` + +### onKeyDown? + +**Type:** `object` + +(key: ParsedKey) => void + +### onMouseDown? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseDrag? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseDragEnd? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseDrop? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseMove? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseOut? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseOver? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseScroll? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseUp? + +**Type:** `object` + +(event: MouseEvent) => void + +### padding? + +**Type:** `number | string` + +### paddingBottom? + +**Type:** `number | string` + +### paddingLeft? + +**Type:** `number | string` + +### paddingRight? + +**Type:** `number | string` + +### paddingTop? + +**Type:** `number | string` + +### position? + +**Type:** `PositionTypeString` + +### right? + +**Type:** `number | string | string` + +### selectable? + +**Type:** `boolean` + +### selectionBg? + +**Type:** `string | RGBA` + +### selectionFg? + +**Type:** `string | RGBA` + +### text? + +**Type:** `string` + +### top? + +**Type:** `number | string | string` + +### visible? + +**Type:** `boolean` + +### width? + +**Type:** `number | string | string` + +### zIndex? + +**Type:** `number` + diff --git a/packages/core/docs/api/reference/interfaces/AnimationOptions.md b/packages/core/docs/api/reference/interfaces/AnimationOptions.md new file mode 100644 index 000000000..c21c3e101 --- /dev/null +++ b/packages/core/docs/api/reference/interfaces/AnimationOptions.md @@ -0,0 +1,54 @@ +# AnimationOptions + +Interface defining the structure for AnimationOptions. + +## Properties + +### alternate? + +**Type:** `boolean` + +### duration + +**Type:** `number` + +### ease? + +**Type:** `EasingFunctions` + +### loop? + +**Type:** `boolean | number` + +### loopDelay? + +**Type:** `number` + +### onComplete? + +**Type:** `any` + +() => void + +### onLoop? + +**Type:** `any` + +() => void + +### onStart? + +**Type:** `any` + +() => void + +### onUpdate? + +**Type:** `object` + +(animation: JSAnimation) => void + +### once? + +**Type:** `boolean` + diff --git a/packages/core/docs/api/reference/interfaces/BoxOptions.md b/packages/core/docs/api/reference/interfaces/BoxOptions.md new file mode 100644 index 000000000..3325f3817 --- /dev/null +++ b/packages/core/docs/api/reference/interfaces/BoxOptions.md @@ -0,0 +1,305 @@ +# BoxOptions + +Configuration options for creating BoxRenderable components. Extends RenderableOptions with box-specific styling like borders, padding, and title support. + +## Properties + +### alignItems? + +**Type:** `AlignString` + +Controls alignment of child items along the cross axis in flexbox layout. Options: 'flex-start', 'flex-end', 'center', 'stretch', 'baseline' + +### backgroundColor? + +**Type:** `string | RGBA` + +Background color of the box. Accepts CSS color strings ('#ffffff', 'red') or RGBA objects + +### border? + +**Type:** `boolean | array` + +Enable border rendering. Can be boolean for all sides or array to specify individual sides: [top, right, bottom, left] + +### borderColor? + +**Type:** `string | RGBA` + +Color of the border when enabled. Accepts CSS color strings or RGBA objects + +### borderStyle? + +**Type:** `BorderStyle` + +Style of border characters: 'single' (─│), 'double' (═║), 'rounded' (╭╮), 'heavy' (━┃) + +### bottom? + +**Type:** `number | string | string` + +### buffered? + +**Type:** `boolean` + +### customBorderChars? + +**Type:** `BorderCharacters` + +### enableLayout? + +**Type:** `boolean` + +### flexBasis? + +**Type:** `number | string` + +### flexDirection? + +**Type:** `FlexDirectionString` + +### flexGrow? + +**Type:** `number` + +### flexShrink? + +**Type:** `number` + +### focusedBorderColor? + +**Type:** `ColorInput` + +Border color when the box has keyboard focus. Useful for indicating active state + +### height? + +**Type:** `number | string | string` + +### justifyContent? + +**Type:** `JustifyString` + +### left? + +**Type:** `number | string | string` + +### live? + +**Type:** `boolean` + +### margin? + +**Type:** `number | string | string` + +### marginBottom? + +**Type:** `number | string | string` + +### marginLeft? + +**Type:** `number | string | string` + +### marginRight? + +**Type:** `number | string | string` + +### marginTop? + +**Type:** `number | string | string` + +### maxHeight? + +**Type:** `number` + +### maxWidth? + +**Type:** `number` + +### minHeight? + +**Type:** `number` + +### minWidth? + +**Type:** `number` + +### onKeyDown? + +**Type:** `object` + +(key: ParsedKey) => void + +### onMouseDown? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseDrag? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseDragEnd? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseDrop? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseMove? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseOut? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseOver? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseScroll? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseUp? + +**Type:** `object` + +(event: MouseEvent) => void + +### padding? + +**Type:** `number | string` + +### paddingBottom? + +**Type:** `number | string` + +### paddingLeft? + +**Type:** `number | string` + +### paddingRight? + +**Type:** `number | string` + +### paddingTop? + +**Type:** `number | string` + +### position? + +**Type:** `PositionTypeString` + +### right? + +**Type:** `number | string | string` + +### shouldFill? + +**Type:** `boolean` + +### title? + +**Type:** `string` + +Optional title text displayed in the top border of the box + +### titleAlignment? + +**Type:** `string` + +Alignment of the title within the top border: 'left', 'center', 'right' + +### top? + +**Type:** `number | string | string` + +### visible? + +**Type:** `boolean` + +### width? + +**Type:** `number | string | string` + +### zIndex? + +**Type:** `number` + +Layer order for overlapping components. Higher values appear on top + +## Examples + +```typescript +// Basic box with border +const box = new BoxRenderable('my-box', { + width: 40, + height: 10, + border: true, + borderStyle: 'rounded', + borderColor: '#00ff00' +}); + +// Box with title and padding +const titledBox = new BoxRenderable('titled', { + width: '50%', + height: 15, + border: true, + title: 'Settings', + titleAlignment: 'center', + padding: 1, + backgroundColor: '#1e1e1e' +}); + +// Flexbox container +const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: 2 +}); + +// Interactive box with focus highlight +const interactiveBox = new BoxRenderable('interactive', { + width: 30, + height: 5, + border: true, + borderColor: '#808080', + focusedBorderColor: '#00ff00', + onMouseDown: (event) => { + console.log('Box clicked!'); + }, + onKeyDown: (key) => { + if (key.name === 'enter') { + // Handle enter key + } + } +}); +``` + +## See Also + +- [RenderableOptions](./RenderableOptions.md) - Base options inherited by BoxOptions +- [BoxRenderable](../classes/BoxRenderable.md) - Box component class +- [BorderStyle](../types/BorderStyle.md) - Available border styles + diff --git a/packages/core/docs/api/reference/interfaces/CliRendererConfig.md b/packages/core/docs/api/reference/interfaces/CliRendererConfig.md new file mode 100644 index 000000000..96b4f0a52 --- /dev/null +++ b/packages/core/docs/api/reference/interfaces/CliRendererConfig.md @@ -0,0 +1,70 @@ +# CliRendererConfig + +Interface defining the structure for CliRendererConfig. + +## Properties + +### consoleOptions? + +**Type:** `ConsoleOptions` + +### debounceDelay? + +**Type:** `number` + +### enableMouseMovement? + +**Type:** `boolean` + +### exitOnCtrlC? + +**Type:** `boolean` + +### experimental_splitHeight? + +**Type:** `number` + +### gatherStats? + +**Type:** `boolean` + +### maxStatSamples? + +**Type:** `number` + +### memorySnapshotInterval? + +**Type:** `number` + +### postProcessFns? + +**Type:** `array` + +### stdin? + +**Type:** `global.NodeJS.ReadStream` + +### stdout? + +**Type:** `global.NodeJS.WriteStream` + +### targetFps? + +**Type:** `number` + +### useAlternateScreen? + +**Type:** `boolean` + +### useConsole? + +**Type:** `boolean` + +### useMouse? + +**Type:** `boolean` + +### useThread? + +**Type:** `boolean` + diff --git a/packages/core/docs/api/reference/interfaces/InputRenderableOptions.md b/packages/core/docs/api/reference/interfaces/InputRenderableOptions.md new file mode 100644 index 000000000..07c5691e4 --- /dev/null +++ b/packages/core/docs/api/reference/interfaces/InputRenderableOptions.md @@ -0,0 +1,230 @@ +# InputRenderableOptions + +Interface defining the structure for InputRenderableOptions. + +## Properties + +### alignItems? + +**Type:** `AlignString` + +### backgroundColor? + +**Type:** `ColorInput` + +### bottom? + +**Type:** `number | string | string` + +### buffered? + +**Type:** `boolean` + +### cursorColor? + +**Type:** `ColorInput` + +### enableLayout? + +**Type:** `boolean` + +### flexBasis? + +**Type:** `number | string` + +### flexDirection? + +**Type:** `FlexDirectionString` + +### flexGrow? + +**Type:** `number` + +### flexShrink? + +**Type:** `number` + +### focusedBackgroundColor? + +**Type:** `ColorInput` + +### focusedTextColor? + +**Type:** `ColorInput` + +### height? + +**Type:** `number | string | string` + +### justifyContent? + +**Type:** `JustifyString` + +### left? + +**Type:** `number | string | string` + +### live? + +**Type:** `boolean` + +### margin? + +**Type:** `number | string | string` + +### marginBottom? + +**Type:** `number | string | string` + +### marginLeft? + +**Type:** `number | string | string` + +### marginRight? + +**Type:** `number | string | string` + +### marginTop? + +**Type:** `number | string | string` + +### maxHeight? + +**Type:** `number` + +### maxLength? + +**Type:** `number` + +### maxWidth? + +**Type:** `number` + +### minHeight? + +**Type:** `number` + +### minWidth? + +**Type:** `number` + +### onKeyDown? + +**Type:** `object` + +(key: ParsedKey) => void + +### onMouseDown? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseDrag? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseDragEnd? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseDrop? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseMove? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseOut? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseOver? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseScroll? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseUp? + +**Type:** `object` + +(event: MouseEvent) => void + +### padding? + +**Type:** `number | string` + +### paddingBottom? + +**Type:** `number | string` + +### paddingLeft? + +**Type:** `number | string` + +### paddingRight? + +**Type:** `number | string` + +### paddingTop? + +**Type:** `number | string` + +### placeholder? + +**Type:** `string` + +### placeholderColor? + +**Type:** `ColorInput` + +### position? + +**Type:** `PositionTypeString` + +### right? + +**Type:** `number | string | string` + +### textColor? + +**Type:** `ColorInput` + +### top? + +**Type:** `number | string | string` + +### value? + +**Type:** `string` + +### visible? + +**Type:** `boolean` + +### width? + +**Type:** `number | string | string` + +### zIndex? + +**Type:** `number` + diff --git a/packages/core/docs/api/reference/interfaces/JSAnimation.md b/packages/core/docs/api/reference/interfaces/JSAnimation.md new file mode 100644 index 000000000..7160638f4 --- /dev/null +++ b/packages/core/docs/api/reference/interfaces/JSAnimation.md @@ -0,0 +1,22 @@ +# JSAnimation + +Interface defining the structure for JSAnimation. + +## Properties + +### targets + +**Type:** `any[]` + +### deltaTime + +**Type:** `number` + +### progress + +**Type:** `number` + +### currentTime + +**Type:** `number` + diff --git a/packages/core/docs/api/reference/interfaces/LayoutOptions.md b/packages/core/docs/api/reference/interfaces/LayoutOptions.md new file mode 100644 index 000000000..4fb952800 --- /dev/null +++ b/packages/core/docs/api/reference/interfaces/LayoutOptions.md @@ -0,0 +1,110 @@ +# LayoutOptions + +Interface defining the structure for LayoutOptions. + +## Properties + +### alignItems? + +**Type:** `AlignString` + +### bottom? + +**Type:** `number | string | string` + +### enableLayout? + +**Type:** `boolean` + +### flexBasis? + +**Type:** `number | string` + +### flexDirection? + +**Type:** `FlexDirectionString` + +### flexGrow? + +**Type:** `number` + +### flexShrink? + +**Type:** `number` + +### justifyContent? + +**Type:** `JustifyString` + +### left? + +**Type:** `number | string | string` + +### margin? + +**Type:** `number | string | string` + +### marginBottom? + +**Type:** `number | string | string` + +### marginLeft? + +**Type:** `number | string | string` + +### marginRight? + +**Type:** `number | string | string` + +### marginTop? + +**Type:** `number | string | string` + +### maxHeight? + +**Type:** `number` + +### maxWidth? + +**Type:** `number` + +### minHeight? + +**Type:** `number` + +### minWidth? + +**Type:** `number` + +### padding? + +**Type:** `number | string` + +### paddingBottom? + +**Type:** `number | string` + +### paddingLeft? + +**Type:** `number | string` + +### paddingRight? + +**Type:** `number | string` + +### paddingTop? + +**Type:** `number | string` + +### position? + +**Type:** `PositionTypeString` + +### right? + +**Type:** `number | string | string` + +### top? + +**Type:** `number | string | string` + diff --git a/packages/core/docs/api/reference/interfaces/Position.md b/packages/core/docs/api/reference/interfaces/Position.md new file mode 100644 index 000000000..f74a45569 --- /dev/null +++ b/packages/core/docs/api/reference/interfaces/Position.md @@ -0,0 +1,22 @@ +# Position + +Interface defining the structure for Position. + +## Properties + +### top + +**Type:** `number | "auto" | `${number}%`` + +### right + +**Type:** `number | "auto" | `${number}%`` + +### bottom + +**Type:** `number | "auto" | `${number}%`` + +### left + +**Type:** `number | "auto" | `${number}%`` + diff --git a/packages/core/docs/api/reference/interfaces/RenderableOptions.md b/packages/core/docs/api/reference/interfaces/RenderableOptions.md new file mode 100644 index 000000000..f724c86f4 --- /dev/null +++ b/packages/core/docs/api/reference/interfaces/RenderableOptions.md @@ -0,0 +1,194 @@ +# RenderableOptions + +Interface defining the structure for RenderableOptions. + +## Properties + +### alignItems? + +**Type:** `AlignString` + +### bottom? + +**Type:** `number | string | string` + +### buffered? + +**Type:** `boolean` + +### enableLayout? + +**Type:** `boolean` + +### flexBasis? + +**Type:** `number | string` + +### flexDirection? + +**Type:** `FlexDirectionString` + +### flexGrow? + +**Type:** `number` + +### flexShrink? + +**Type:** `number` + +### height? + +**Type:** `number | string | string` + +### justifyContent? + +**Type:** `JustifyString` + +### left? + +**Type:** `number | string | string` + +### live? + +**Type:** `boolean` + +### margin? + +**Type:** `number | string | string` + +### marginBottom? + +**Type:** `number | string | string` + +### marginLeft? + +**Type:** `number | string | string` + +### marginRight? + +**Type:** `number | string | string` + +### marginTop? + +**Type:** `number | string | string` + +### maxHeight? + +**Type:** `number` + +### maxWidth? + +**Type:** `number` + +### minHeight? + +**Type:** `number` + +### minWidth? + +**Type:** `number` + +### onKeyDown? + +**Type:** `object` + +(key: ParsedKey) => void + +### onMouseDown? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseDrag? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseDragEnd? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseDrop? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseMove? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseOut? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseOver? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseScroll? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseUp? + +**Type:** `object` + +(event: MouseEvent) => void + +### padding? + +**Type:** `number | string` + +### paddingBottom? + +**Type:** `number | string` + +### paddingLeft? + +**Type:** `number | string` + +### paddingRight? + +**Type:** `number | string` + +### paddingTop? + +**Type:** `number | string` + +### position? + +**Type:** `PositionTypeString` + +### right? + +**Type:** `number | string | string` + +### top? + +**Type:** `number | string | string` + +### visible? + +**Type:** `boolean` + +### width? + +**Type:** `number | string | string` + +### zIndex? + +**Type:** `number` + diff --git a/packages/core/docs/api/reference/interfaces/RootContext.md b/packages/core/docs/api/reference/interfaces/RootContext.md new file mode 100644 index 000000000..2e32d8226 --- /dev/null +++ b/packages/core/docs/api/reference/interfaces/RootContext.md @@ -0,0 +1,14 @@ +# RootContext + +Interface defining the structure for RootContext. + +## Properties + +### requestLive + +**Type:** `void` + +### dropLive + +**Type:** `void` + diff --git a/packages/core/docs/api/reference/interfaces/TextOptions.md b/packages/core/docs/api/reference/interfaces/TextOptions.md new file mode 100644 index 000000000..4eb91c452 --- /dev/null +++ b/packages/core/docs/api/reference/interfaces/TextOptions.md @@ -0,0 +1,222 @@ +# TextOptions + +Interface defining the structure for TextOptions. + +## Properties + +### alignItems? + +**Type:** `AlignString` + +### attributes? + +**Type:** `number` + +### bg? + +**Type:** `string | RGBA` + +### bottom? + +**Type:** `number | string | string` + +### buffered? + +**Type:** `boolean` + +### content? + +**Type:** `StyledText | string` + +### enableLayout? + +**Type:** `boolean` + +### fg? + +**Type:** `string | RGBA` + +### flexBasis? + +**Type:** `number | string` + +### flexDirection? + +**Type:** `FlexDirectionString` + +### flexGrow? + +**Type:** `number` + +### flexShrink? + +**Type:** `number` + +### height? + +**Type:** `number | string | string` + +### justifyContent? + +**Type:** `JustifyString` + +### left? + +**Type:** `number | string | string` + +### live? + +**Type:** `boolean` + +### margin? + +**Type:** `number | string | string` + +### marginBottom? + +**Type:** `number | string | string` + +### marginLeft? + +**Type:** `number | string | string` + +### marginRight? + +**Type:** `number | string | string` + +### marginTop? + +**Type:** `number | string | string` + +### maxHeight? + +**Type:** `number` + +### maxWidth? + +**Type:** `number` + +### minHeight? + +**Type:** `number` + +### minWidth? + +**Type:** `number` + +### onKeyDown? + +**Type:** `object` + +(key: ParsedKey) => void + +### onMouseDown? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseDrag? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseDragEnd? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseDrop? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseMove? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseOut? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseOver? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseScroll? + +**Type:** `object` + +(event: MouseEvent) => void + +### onMouseUp? + +**Type:** `object` + +(event: MouseEvent) => void + +### padding? + +**Type:** `number | string` + +### paddingBottom? + +**Type:** `number | string` + +### paddingLeft? + +**Type:** `number | string` + +### paddingRight? + +**Type:** `number | string` + +### paddingTop? + +**Type:** `number | string` + +### position? + +**Type:** `PositionTypeString` + +### right? + +**Type:** `number | string | string` + +### selectable? + +**Type:** `boolean` + +### selectionBg? + +**Type:** `string | RGBA` + +### selectionFg? + +**Type:** `string | RGBA` + +### top? + +**Type:** `number | string | string` + +### visible? + +**Type:** `boolean` + +### width? + +**Type:** `number | string | string` + +### zIndex? + +**Type:** `number` + diff --git a/packages/core/docs/api/reference/interfaces/TimelineOptions.md b/packages/core/docs/api/reference/interfaces/TimelineOptions.md new file mode 100644 index 000000000..59dbc0c42 --- /dev/null +++ b/packages/core/docs/api/reference/interfaces/TimelineOptions.md @@ -0,0 +1,30 @@ +# TimelineOptions + +Interface defining the structure for TimelineOptions. + +## Properties + +### autoplay? + +**Type:** `boolean` + +### duration? + +**Type:** `number` + +### loop? + +**Type:** `boolean` + +### onComplete? + +**Type:** `any` + +() => void + +### onPause? + +**Type:** `any` + +() => void + diff --git a/packages/core/docs/api/reference/types/BorderConfig.md b/packages/core/docs/api/reference/types/BorderConfig.md new file mode 100644 index 000000000..f9b55a6ae --- /dev/null +++ b/packages/core/docs/api/reference/types/BorderConfig.md @@ -0,0 +1,27 @@ +# BorderConfig + +## Properties + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `border` | `boolean | array` | ✓ | | +| `borderColor` | `ColorInput` | | | +| `borderStyle` | `BorderStyle` | ✓ | | +| `customBorderChars` | `BorderCharacters` | | | + +## Example + +```typescript +const options: BorderConfig = { + border: undefined, + borderStyle: undefined +}; +``` + +## Related Types + +- `BorderCharacters` +- `BorderSides` +- `BorderStyle` +- `ColorInput` +- `RGBA` diff --git a/packages/core/docs/api/reference/types/BorderStyle.md b/packages/core/docs/api/reference/types/BorderStyle.md new file mode 100644 index 000000000..5002cb416 --- /dev/null +++ b/packages/core/docs/api/reference/types/BorderStyle.md @@ -0,0 +1,71 @@ +# BorderStyle + +Defines the visual style of borders used in box components. + +## Type Definition + +```typescript +type BorderStyle = 'single' | 'double' | 'rounded' | 'heavy'; +``` + +## Values + +### single +Single-line border using standard box-drawing characters: +- Horizontal: ─ +- Vertical: │ +- Corners: ┌ ┐ └ ┘ + +### double +Double-line border for emphasis: +- Horizontal: ═ +- Vertical: ║ +- Corners: ╔ ╗ ╚ ╝ + +### rounded +Single-line border with rounded corners for a softer appearance: +- Horizontal: ─ +- Vertical: │ +- Corners: ╭ ╮ ╰ ╯ + +### heavy +Bold/thick border for strong emphasis: +- Horizontal: ━ +- Vertical: ┃ +- Corners: ┏ ┓ ┗ ┛ + +## Examples + +```typescript +// Basic single border +const box1 = new BoxRenderable('box1', { + border: true, + borderStyle: 'single' +}); + +// Double border for important content +const dialog = new BoxRenderable('dialog', { + border: true, + borderStyle: 'double', + title: 'Confirm Action' +}); + +// Rounded border for friendly UI +const tooltip = new BoxRenderable('tooltip', { + border: true, + borderStyle: 'rounded', + padding: 1 +}); + +// Heavy border for critical alerts +const alert = new BoxRenderable('alert', { + border: true, + borderStyle: 'heavy', + borderColor: '#ff0000' +}); +``` + +## See Also + +- [BoxOptions](../interfaces/BoxOptions.md) - Box configuration options +- [BorderCharacters](../interfaces/BorderCharacters.md) - Custom border character definitions \ No newline at end of file diff --git a/packages/core/docs/api/reference/types/BoxDrawOptions.md b/packages/core/docs/api/reference/types/BoxDrawOptions.md new file mode 100644 index 000000000..f8a12762a --- /dev/null +++ b/packages/core/docs/api/reference/types/BoxDrawOptions.md @@ -0,0 +1,37 @@ +# BoxDrawOptions + +## Properties + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `backgroundColor` | `ColorInput` | ✓ | | +| `border` | `boolean | array` | ✓ | | +| `borderColor` | `ColorInput` | ✓ | | +| `borderStyle` | `BorderStyle` | ✓ | | +| `customBorderChars` | `BorderCharacters` | | | +| `height` | `number` | ✓ | | +| `shouldFill` | `boolean` | | | +| `title` | `string` | | | +| `titleAlignment` | `string` | | | +| `width` | `number` | ✓ | | +| `x` | `number` | ✓ | | +| `y` | `number` | ✓ | | + +## Example + +```typescript +const options: BoxDrawOptions = { + backgroundColor: undefined, + border: undefined, + borderColor: undefined, + borderStyle: undefined +}; +``` + +## Related Types + +- `BorderCharacters` +- `BorderSides` +- `BorderStyle` +- `ColorInput` +- `RGBA` diff --git a/packages/core/docs/api/reference/types/ConsoleOptions.md b/packages/core/docs/api/reference/types/ConsoleOptions.md new file mode 100644 index 000000000..ed68cc446 --- /dev/null +++ b/packages/core/docs/api/reference/types/ConsoleOptions.md @@ -0,0 +1,36 @@ +# ConsoleOptions + +## Properties + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `backgroundColor` | `ColorInput` | | | +| `colorDebug` | `ColorInput` | | | +| `colorDefault` | `ColorInput` | | | +| `colorError` | `ColorInput` | | | +| `colorInfo` | `ColorInput` | | | +| `colorWarn` | `ColorInput` | | | +| `cursorColor` | `ColorInput` | | | +| `maxDisplayLines` | `number` | | | +| `maxStoredLogs` | `number` | | | +| `position` | `ConsolePosition` | | | +| `sizePercent` | `number` | | | +| `startInDebugMode` | `boolean` | | | +| `title` | `string` | | | +| `titleBarColor` | `ColorInput` | | | +| `titleBarTextColor` | `ColorInput` | | | +| `zIndex` | `number` | | | + +## Example + +```typescript +const options: ConsoleOptions = { + +}; +``` + +## Related Types + +- `ColorInput` +- `ConsolePosition` +- `RGBA` diff --git a/packages/core/docs/api/reference/types/ExplosionEffectParameters.md b/packages/core/docs/api/reference/types/ExplosionEffectParameters.md new file mode 100644 index 000000000..4b9b50035 --- /dev/null +++ b/packages/core/docs/api/reference/types/ExplosionEffectParameters.md @@ -0,0 +1,35 @@ +# ExplosionEffectParameters + +## Properties + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `angularVelocityMax` | `Vector3` | ✓ | | +| `angularVelocityMin` | `Vector3` | ✓ | | +| `durationMs` | `number` | ✓ | | +| `fadeOut` | `boolean` | ✓ | | +| `gravity` | `number` | ✓ | | +| `gravityScale` | `number` | ✓ | | +| `initialVelocityYBoost` | `number` | ✓ | | +| `materialFactory` | `any` | ✓ | () => NodeMaterial | +| `numCols` | `number` | ✓ | | +| `numRows` | `number` | ✓ | | +| `strength` | `number` | ✓ | | +| `strengthVariation` | `number` | ✓ | | +| `zVariationStrength` | `number` | ✓ | | + +## Example + +```typescript +const options: ExplosionEffectParameters = { + angularVelocityMax: undefined, + angularVelocityMin: undefined, + durationMs: 0, + fadeOut: false, + gravity: 0 +}; +``` + +## Related Types + +- `Vector3` diff --git a/packages/core/docs/api/reference/types/FrameBufferOptions.md b/packages/core/docs/api/reference/types/FrameBufferOptions.md new file mode 100644 index 000000000..6aaef55c5 --- /dev/null +++ b/packages/core/docs/api/reference/types/FrameBufferOptions.md @@ -0,0 +1,69 @@ +# FrameBufferOptions + +## Properties + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `alignItems` | `AlignString` | | | +| `bottom` | `number | string | string` | | | +| `buffered` | `boolean` | | | +| `enableLayout` | `boolean` | | | +| `flexBasis` | `number | string` | | | +| `flexDirection` | `FlexDirectionString` | | | +| `flexGrow` | `number` | | | +| `flexShrink` | `number` | | | +| `height` | `number` | ✓ | | +| `justifyContent` | `JustifyString` | | | +| `left` | `number | string | string` | | | +| `live` | `boolean` | | | +| `margin` | `number | string | string` | | | +| `marginBottom` | `number | string | string` | | | +| `marginLeft` | `number | string | string` | | | +| `marginRight` | `number | string | string` | | | +| `marginTop` | `number | string | string` | | | +| `maxHeight` | `number` | | | +| `maxWidth` | `number` | | | +| `minHeight` | `number` | | | +| `minWidth` | `number` | | | +| `onKeyDown` | `object` | | (key: ParsedKey) => void | +| `onMouseDown` | `object` | | (event: MouseEvent) => void | +| `onMouseDrag` | `object` | | (event: MouseEvent) => void | +| `onMouseDragEnd` | `object` | | (event: MouseEvent) => void | +| `onMouseDrop` | `object` | | (event: MouseEvent) => void | +| `onMouseMove` | `object` | | (event: MouseEvent) => void | +| `onMouseOut` | `object` | | (event: MouseEvent) => void | +| `onMouseOver` | `object` | | (event: MouseEvent) => void | +| `onMouseScroll` | `object` | | (event: MouseEvent) => void | +| `onMouseUp` | `object` | | (event: MouseEvent) => void | +| `padding` | `number | string` | | | +| `paddingBottom` | `number | string` | | | +| `paddingLeft` | `number | string` | | | +| `paddingRight` | `number | string` | | | +| `paddingTop` | `number | string` | | | +| `position` | `PositionTypeString` | | | +| `respectAlpha` | `boolean` | | | +| `right` | `number | string | string` | | | +| `top` | `number | string | string` | | | +| `visible` | `boolean` | | | +| `width` | `number` | ✓ | | +| `zIndex` | `number` | | | + +## Example + +```typescript +const options: FrameBufferOptions = { + +}; +``` + +## Related Types + +- `AlignString` +- `FlexDirectionString` +- `JustifyString` +- `MouseEvent` +- `MouseEventType` +- `ParsedKey` +- `PositionTypeString` +- `Renderable` +- `ScrollInfo` diff --git a/packages/core/docs/api/reference/types/MouseEventType.md b/packages/core/docs/api/reference/types/MouseEventType.md new file mode 100644 index 000000000..3b8503ec5 --- /dev/null +++ b/packages/core/docs/api/reference/types/MouseEventType.md @@ -0,0 +1,118 @@ +# MouseEventType + +Enumeration of all mouse event types supported by OpenTUI. + +## Type Definition + +```typescript +type MouseEventType = + | 'down' + | 'up' + | 'move' + | 'drag' + | 'drag-end' + | 'drop' + | 'over' + | 'out' + | 'scroll'; +``` + +## Values + +### down +Mouse button pressed down. Triggered when user presses any mouse button. + +### up +Mouse button released. Triggered when user releases a pressed mouse button. + +### move +Mouse cursor moved without any buttons pressed. Used for hover effects. + +### drag +Mouse moved while button is held down. Enables drag operations. + +### drag-end +Drag operation completed. Fired when mouse button is released after dragging. + +### drop +Item dropped onto a target. Fired on drop target when drag-end occurs over it. + +### over +Mouse cursor entered a component's bounds. Used for hover states. + +### out +Mouse cursor left a component's bounds. Used to clear hover states. + +### scroll +Mouse wheel scrolled. Includes scroll direction and delta information. + +## Examples + +```typescript +// Handle different mouse events +renderable.onMouseDown = (event: MouseEvent) => { + if (event.type === 'down' && event.button === 0) { + console.log('Left button pressed'); + } +}; + +renderable.onMouseMove = (event: MouseEvent) => { + if (event.type === 'move') { + updateHoverPosition(event.x, event.y); + } +}; + +renderable.onMouseDrag = (event: MouseEvent) => { + if (event.type === 'drag') { + updateDragPosition(event.x, event.y); + } +}; + +renderable.onMouseScroll = (event: MouseEvent) => { + if (event.type === 'scroll') { + if (event.scroll.direction === 'up') { + scrollUp(event.scroll.delta); + } else { + scrollDown(event.scroll.delta); + } + } +}; + +// Complete drag and drop implementation +class DraggableBox extends BoxRenderable { + private dragging = false; + private dragOffset = { x: 0, y: 0 }; + + constructor(id: string, options: BoxOptions) { + super(id, { + ...options, + onMouseDown: (event) => { + if (event.type === 'down') { + this.dragging = true; + this.dragOffset = { + x: event.x - this.x, + y: event.y - this.y + }; + } + }, + onMouseDrag: (event) => { + if (event.type === 'drag' && this.dragging) { + this.x = event.x - this.dragOffset.x; + this.y = event.y - this.dragOffset.y; + this.needsUpdate(); + } + }, + onMouseDragEnd: (event) => { + if (event.type === 'drag-end') { + this.dragging = false; + } + } + }); + } +} +``` + +## See Also + +- [MouseEvent](../classes/MouseEvent.md) - Mouse event class +- [Renderable](../classes/Renderable.md) - Base class handling mouse events \ No newline at end of file diff --git a/packages/core/docs/api/reference/types/Renderable.class.md b/packages/core/docs/api/reference/types/Renderable.class.md new file mode 100644 index 000000000..6bf196ca1 --- /dev/null +++ b/packages/core/docs/api/reference/types/Renderable.class.md @@ -0,0 +1,22 @@ +# Renderable.class + +## Properties + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `id` | `string` | ✓ | | +| `num` | `number` | ✓ | | +| `parent` | `Renderable | null` | ✓ | | +| `selectable` | `boolean` | ✓ | | + +## Example + +```typescript +const options: Renderable.class = { + id: "example", + num: 0, + parent: undefined, + selectable: false +}; +``` + diff --git a/packages/core/docs/api/reference/types/SelectRenderableOptions.md b/packages/core/docs/api/reference/types/SelectRenderableOptions.md new file mode 100644 index 000000000..15d0209c8 --- /dev/null +++ b/packages/core/docs/api/reference/types/SelectRenderableOptions.md @@ -0,0 +1,86 @@ +# SelectRenderableOptions + +## Properties + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `alignItems` | `AlignString` | | | +| `backgroundColor` | `ColorInput` | | | +| `bottom` | `number | string | string` | | | +| `buffered` | `boolean` | | | +| `descriptionColor` | `ColorInput` | | | +| `enableLayout` | `boolean` | | | +| `fastScrollStep` | `number` | | | +| `flexBasis` | `number | string` | | | +| `flexDirection` | `FlexDirectionString` | | | +| `flexGrow` | `number` | | | +| `flexShrink` | `number` | | | +| `focusedBackgroundColor` | `ColorInput` | | | +| `focusedTextColor` | `ColorInput` | | | +| `font` | `string` | | | +| `height` | `number | string | string` | | | +| `itemSpacing` | `number` | | | +| `justifyContent` | `JustifyString` | | | +| `left` | `number | string | string` | | | +| `live` | `boolean` | | | +| `margin` | `number | string | string` | | | +| `marginBottom` | `number | string | string` | | | +| `marginLeft` | `number | string | string` | | | +| `marginRight` | `number | string | string` | | | +| `marginTop` | `number | string | string` | | | +| `maxHeight` | `number` | | | +| `maxWidth` | `number` | | | +| `minHeight` | `number` | | | +| `minWidth` | `number` | | | +| `onKeyDown` | `object` | | (key: ParsedKey) => void | +| `onMouseDown` | `object` | | (event: MouseEvent) => void | +| `onMouseDrag` | `object` | | (event: MouseEvent) => void | +| `onMouseDragEnd` | `object` | | (event: MouseEvent) => void | +| `onMouseDrop` | `object` | | (event: MouseEvent) => void | +| `onMouseMove` | `object` | | (event: MouseEvent) => void | +| `onMouseOut` | `object` | | (event: MouseEvent) => void | +| `onMouseOver` | `object` | | (event: MouseEvent) => void | +| `onMouseScroll` | `object` | | (event: MouseEvent) => void | +| `onMouseUp` | `object` | | (event: MouseEvent) => void | +| `options` | `array` | | | +| `padding` | `number | string` | | | +| `paddingBottom` | `number | string` | | | +| `paddingLeft` | `number | string` | | | +| `paddingRight` | `number | string` | | | +| `paddingTop` | `number | string` | | | +| `position` | `PositionTypeString` | | | +| `right` | `number | string | string` | | | +| `selectedBackgroundColor` | `ColorInput` | | | +| `selectedDescriptionColor` | `ColorInput` | | | +| `selectedTextColor` | `ColorInput` | | | +| `showDescription` | `boolean` | | | +| `showScrollIndicator` | `boolean` | | | +| `textColor` | `ColorInput` | | | +| `top` | `number | string | string` | | | +| `visible` | `boolean` | | | +| `width` | `number | string | string` | | | +| `wrapSelection` | `boolean` | | | +| `zIndex` | `number` | | | + +## Example + +```typescript +const options: SelectRenderableOptions = { + +}; +``` + +## Related Types + +- `AlignString` +- `ColorInput` +- `FlexDirectionString` +- `JustifyString` +- `MouseEvent` +- `MouseEventType` +- `ParsedKey` +- `PositionTypeString` +- `RGBA` +- `Renderable` +- `ScrollInfo` +- `SelectOption` diff --git a/packages/core/docs/api/reference/types/TabSelectRenderableOptions.md b/packages/core/docs/api/reference/types/TabSelectRenderableOptions.md new file mode 100644 index 000000000..5b2113225 --- /dev/null +++ b/packages/core/docs/api/reference/types/TabSelectRenderableOptions.md @@ -0,0 +1,84 @@ +# TabSelectRenderableOptions + +## Properties + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `alignItems` | `AlignString` | | | +| `backgroundColor` | `ColorInput` | | | +| `bottom` | `number | string | string` | | | +| `buffered` | `boolean` | | | +| `enableLayout` | `boolean` | | | +| `flexBasis` | `number | string` | | | +| `flexDirection` | `FlexDirectionString` | | | +| `flexGrow` | `number` | | | +| `flexShrink` | `number` | | | +| `focusedBackgroundColor` | `ColorInput` | | | +| `focusedTextColor` | `ColorInput` | | | +| `height` | `number` | | | +| `justifyContent` | `JustifyString` | | | +| `left` | `number | string | string` | | | +| `live` | `boolean` | | | +| `margin` | `number | string | string` | | | +| `marginBottom` | `number | string | string` | | | +| `marginLeft` | `number | string | string` | | | +| `marginRight` | `number | string | string` | | | +| `marginTop` | `number | string | string` | | | +| `maxHeight` | `number` | | | +| `maxWidth` | `number` | | | +| `minHeight` | `number` | | | +| `minWidth` | `number` | | | +| `onKeyDown` | `object` | | (key: ParsedKey) => void | +| `onMouseDown` | `object` | | (event: MouseEvent) => void | +| `onMouseDrag` | `object` | | (event: MouseEvent) => void | +| `onMouseDragEnd` | `object` | | (event: MouseEvent) => void | +| `onMouseDrop` | `object` | | (event: MouseEvent) => void | +| `onMouseMove` | `object` | | (event: MouseEvent) => void | +| `onMouseOut` | `object` | | (event: MouseEvent) => void | +| `onMouseOver` | `object` | | (event: MouseEvent) => void | +| `onMouseScroll` | `object` | | (event: MouseEvent) => void | +| `onMouseUp` | `object` | | (event: MouseEvent) => void | +| `options` | `array` | | | +| `padding` | `number | string` | | | +| `paddingBottom` | `number | string` | | | +| `paddingLeft` | `number | string` | | | +| `paddingRight` | `number | string` | | | +| `paddingTop` | `number | string` | | | +| `position` | `PositionTypeString` | | | +| `right` | `number | string | string` | | | +| `selectedBackgroundColor` | `ColorInput` | | | +| `selectedDescriptionColor` | `ColorInput` | | | +| `selectedTextColor` | `ColorInput` | | | +| `showDescription` | `boolean` | | | +| `showScrollArrows` | `boolean` | | | +| `showUnderline` | `boolean` | | | +| `tabWidth` | `number` | | | +| `textColor` | `ColorInput` | | | +| `top` | `number | string | string` | | | +| `visible` | `boolean` | | | +| `width` | `number | string | string` | | | +| `wrapSelection` | `boolean` | | | +| `zIndex` | `number` | | | + +## Example + +```typescript +const options: TabSelectRenderableOptions = { + +}; +``` + +## Related Types + +- `AlignString` +- `ColorInput` +- `FlexDirectionString` +- `JustifyString` +- `MouseEvent` +- `MouseEventType` +- `ParsedKey` +- `PositionTypeString` +- `RGBA` +- `Renderable` +- `ScrollInfo` +- `TabSelectOption` diff --git a/packages/core/docs/api/reference/types/ThreeCliRendererOptions.md b/packages/core/docs/api/reference/types/ThreeCliRendererOptions.md new file mode 100644 index 000000000..e6016062a --- /dev/null +++ b/packages/core/docs/api/reference/types/ThreeCliRendererOptions.md @@ -0,0 +1,27 @@ +# ThreeCliRendererOptions + +## Properties + +| Property | Type | Required | Description | +|----------|------|----------|-------------| +| `alpha` | `boolean` | | | +| `autoResize` | `boolean` | | | +| `backgroundColor` | `RGBA` | | | +| `focalLength` | `number` | | | +| `height` | `number` | ✓ | | +| `libPath` | `string` | | | +| `superSample` | `SuperSampleType` | | | +| `width` | `number` | ✓ | | + +## Example + +```typescript +const options: ThreeCliRendererOptions = { + height: 0 +}; +``` + +## Related Types + +- `RGBA` +- `SuperSampleType` diff --git a/packages/core/docs/api/renderables/ascii-font.md b/packages/core/docs/api/renderables/ascii-font.md deleted file mode 100644 index c2cc797a5..000000000 --- a/packages/core/docs/api/renderables/ascii-font.md +++ /dev/null @@ -1,515 +0,0 @@ -# ASCII Font Renderer - -OpenTUI provides an ASCII font renderer for creating stylized text using ASCII art fonts. - -## Overview - -The `ASCIIFontRenderable` component allows you to render text using ASCII art fonts, which are defined as JSON files. This is useful for creating headers, logos, and other stylized text elements in your terminal applications. - -## ASCII Font API - -### Creating an ASCII Font Renderable - -```typescript -import { ASCIIFontRenderable } from '@opentui/core'; - -// Create an ASCII font renderable with the default font -const asciiText = new ASCIIFontRenderable('ascii-text', { - content: 'Hello', - fg: '#3498db', - alignItems: 'center', - justifyContent: 'center' -}); - -// Create an ASCII font renderable with a specific font -const customFont = new ASCIIFontRenderable('custom-font', { - content: 'OpenTUI', - font: 'block', // Use the 'block' font - fg: '#e74c3c', - alignItems: 'center', - justifyContent: 'center' -}); -``` - -### ASCII Font Options - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `content` | `string` | `''` | The text to render | -| `font` | `string` | `'slick'` | The font to use (e.g., 'slick', 'block', 'tiny', 'shade') | -| `fg` | `string` | `'#ffffff'` | The foreground color | -| `bg` | `string` | `'transparent'` | The background color | -| `alignItems` | `string` | `'flex-start'` | Vertical alignment | -| `justifyContent` | `string` | `'flex-start'` | Horizontal alignment | -| `width` | `number \| string` | `'auto'` | Width of the component | -| `height` | `number \| string` | `'auto'` | Height of the component | - -### Available Fonts - -OpenTUI includes several built-in ASCII art fonts: - -1. **Slick**: A sleek, minimalist font -2. **Block**: A bold, blocky font -3. **Tiny**: A small, compact font -4. **Shade**: A font with shading effects - -```typescript -// Examples of different fonts -const slickFont = new ASCIIFontRenderable('slick-font', { - content: 'Slick', - font: 'slick', - fg: '#3498db' -}); - -const blockFont = new ASCIIFontRenderable('block-font', { - content: 'Block', - font: 'block', - fg: '#e74c3c' -}); - -const tinyFont = new ASCIIFontRenderable('tiny-font', { - content: 'Tiny', - font: 'tiny', - fg: '#2ecc71' -}); - -const shadeFont = new ASCIIFontRenderable('shade-font', { - content: 'Shade', - font: 'shade', - fg: '#f39c12' -}); -``` - -### Changing the Content - -```typescript -// Create an ASCII font renderable -const asciiText = new ASCIIFontRenderable('ascii-text', { - content: 'Hello', - font: 'block', - fg: '#3498db' -}); - -// Change the content -asciiText.content = 'World'; - -// Change the font -asciiText.font = 'slick'; - -// Change the color -asciiText.fg = '#e74c3c'; -``` - -### Creating Custom Fonts - -You can create custom ASCII art fonts by defining them in JSON files: - -```json -{ - "name": "MyCustomFont", - "height": 5, - "chars": { - "A": [ - " # ", - " # # ", - "#####", - "# #", - "# #" - ], - "B": [ - "#### ", - "# #", - "#### ", - "# #", - "#### " - ], - // Define other characters... - } -} -``` - -Then load and use the custom font: - -```typescript -import { ASCIIFontRenderable, loadASCIIFont } from '@opentui/core'; -import * as fs from 'fs'; - -// Load a custom font -const customFontData = JSON.parse(fs.readFileSync('path/to/custom-font.json', 'utf8')); -loadASCIIFont('custom-font', customFontData); - -// Use the custom font -const customFont = new ASCIIFontRenderable('custom-font', { - content: 'Custom', - font: 'custom-font', - fg: '#9b59b6' -}); -``` - -## Example: Creating a Title Screen - -```typescript -import { createCliRenderer, BoxRenderable, TextRenderable, ASCIIFontRenderable } from '@opentui/core'; - -async function createTitleScreenDemo() { - // Create the renderer - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - borderStyle: 'double', - borderColor: '#3498db', - backgroundColor: '#222222', - padding: 2 - }); - - root.add(container); - - // Create a title using ASCII font - const title = new ASCIIFontRenderable('title', { - content: 'OpenTUI', - font: 'block', - fg: '#e74c3c', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - height: '30%' - }); - - container.add(title); - - // Create a subtitle - const subtitle = new TextRenderable('subtitle', { - content: 'Terminal User Interface Framework', - fg: '#3498db', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - height: '10%' - }); - - container.add(subtitle); - - // Create menu options - const menuContainer = new BoxRenderable('menu-container', { - width: '50%', - height: '40%', - x: '25%', - y: '50%', - borderStyle: 'single', - borderColor: '#2ecc71', - backgroundColor: 'transparent', - padding: 1 - }); - - container.add(menuContainer); - - // Add menu items - const menuItems = [ - 'New Game', - 'Load Game', - 'Options', - 'Credits', - 'Exit' - ]; - - let selectedIndex = 0; - - const menuItemRenderables = menuItems.map((item, index) => { - const menuItem = new TextRenderable(`menu-item-${index}`, { - content: `${index === selectedIndex ? '> ' : ' '}${item}`, - fg: index === selectedIndex ? '#ffffff' : '#bbbbbb', - y: index * 2, - width: '100%', - height: 1 - }); - - menuContainer.add(menuItem); - - return menuItem; - }); - - // Handle keyboard input - renderer.on('key', (key) => { - const keyStr = key.toString(); - - if (keyStr === 'up' || keyStr === 'k') { - // Update the previously selected item - menuItemRenderables[selectedIndex].content = ` ${menuItems[selectedIndex]}`; - menuItemRenderables[selectedIndex].fg = '#bbbbbb'; - - // Move selection up - selectedIndex = (selectedIndex - 1 + menuItems.length) % menuItems.length; - - // Update the newly selected item - menuItemRenderables[selectedIndex].content = `> ${menuItems[selectedIndex]}`; - menuItemRenderables[selectedIndex].fg = '#ffffff'; - } else if (keyStr === 'down' || keyStr === 'j') { - // Update the previously selected item - menuItemRenderables[selectedIndex].content = ` ${menuItems[selectedIndex]}`; - menuItemRenderables[selectedIndex].fg = '#bbbbbb'; - - // Move selection down - selectedIndex = (selectedIndex + 1) % menuItems.length; - - // Update the newly selected item - menuItemRenderables[selectedIndex].content = `> ${menuItems[selectedIndex]}`; - menuItemRenderables[selectedIndex].fg = '#ffffff'; - } else if (keyStr === 'return') { - // Handle menu selection - if (selectedIndex === menuItems.length - 1) { - // Exit option - renderer.destroy(); - process.exit(0); - } else { - // Show a message for other options - title.content = menuItems[selectedIndex]; - } - } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C - renderer.destroy(); - process.exit(0); - } - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the title screen demo -createTitleScreenDemo().catch(console.error); -``` - -## Example: Creating a Banner - -```typescript -import { createCliRenderer, BoxRenderable, ASCIIFontRenderable } from '@opentui/core'; - -async function createBannerDemo() { - // Create the renderer - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - borderStyle: 'none', - backgroundColor: '#222222' - }); - - root.add(container); - - // Create a banner using ASCII font - const banner = new ASCIIFontRenderable('banner', { - content: 'WELCOME', - font: 'block', - fg: '#e74c3c', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - height: '30%', - y: '10%' - }); - - container.add(banner); - - // Create a second line - const secondLine = new ASCIIFontRenderable('second-line', { - content: 'TO', - font: 'slick', - fg: '#f39c12', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - height: '20%', - y: '40%' - }); - - container.add(secondLine); - - // Create a third line - const thirdLine = new ASCIIFontRenderable('third-line', { - content: 'OPENTUI', - font: 'shade', - fg: '#2ecc71', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - height: '30%', - y: '60%' - }); - - container.add(thirdLine); - - // Start the renderer - renderer.start(); - - // Animate the banner - let frame = 0; - const colors = [ - '#e74c3c', // Red - '#e67e22', // Orange - '#f1c40f', // Yellow - '#2ecc71', // Green - '#3498db', // Blue - '#9b59b6' // Purple - ]; - - const interval = setInterval(() => { - frame = (frame + 1) % colors.length; - - banner.fg = colors[frame]; - secondLine.fg = colors[(frame + 2) % colors.length]; - thirdLine.fg = colors[(frame + 4) % colors.length]; - }, 500); - - // Handle keyboard input - renderer.on('key', (key) => { - const keyStr = key.toString(); - - if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C - clearInterval(interval); - renderer.destroy(); - process.exit(0); - } - }); - - return renderer; -} - -// Create and run the banner demo -createBannerDemo().catch(console.error); -``` - -## Example: Creating a Loading Screen - -```typescript -import { createCliRenderer, BoxRenderable, TextRenderable, ASCIIFontRenderable } from '@opentui/core'; - -async function createLoadingScreenDemo() { - // Create the renderer - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - borderStyle: 'none', - backgroundColor: '#222222' - }); - - root.add(container); - - // Create a title using ASCII font - const title = new ASCIIFontRenderable('title', { - content: 'LOADING', - font: 'block', - fg: '#3498db', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - height: '30%', - y: '20%' - }); - - container.add(title); - - // Create a loading bar container - const loadingBarContainer = new BoxRenderable('loading-bar-container', { - width: '80%', - height: 3, - x: '10%', - y: '60%', - borderStyle: 'single', - borderColor: '#ffffff', - backgroundColor: 'transparent' - }); - - container.add(loadingBarContainer); - - // Create a loading bar - const loadingBar = new BoxRenderable('loading-bar', { - width: '0%', - height: 1, - x: 0, - y: 1, - borderStyle: 'none', - backgroundColor: '#2ecc71' - }); - - loadingBarContainer.add(loadingBar); - - // Create a loading text - const loadingText = new TextRenderable('loading-text', { - content: 'Loading... 0%', - fg: '#ffffff', - alignItems: 'center', - justifyContent: 'center', - width: '100%', - height: 1, - y: '70%' - }); - - container.add(loadingText); - - // Start the renderer - renderer.start(); - - // Simulate loading - let progress = 0; - - const interval = setInterval(() => { - progress += 1; - - if (progress > 100) { - clearInterval(interval); - - // Change the title - title.content = 'COMPLETE'; - title.fg = '#2ecc71'; - - // Update the loading text - loadingText.content = 'Press any key to continue'; - - return; - } - - // Update the loading bar - loadingBar.width = `${progress}%`; - - // Update the loading text - loadingText.content = `Loading... ${progress}%`; - - // Change the title color based on progress - if (progress < 30) { - title.fg = '#3498db'; - } else if (progress < 60) { - title.fg = '#f39c12'; - } else { - title.fg = '#2ecc71'; - } - }, 50); - - // Handle keyboard input - renderer.on('key', (key) => { - const keyStr = key.toString(); - - if (progress >= 100 || keyStr === 'q' || keyStr === '\u0003') { // Any key after loading or q or Ctrl+C - clearInterval(interval); - renderer.destroy(); - process.exit(0); - } - }); - - return renderer; -} - -// Create and run the loading screen demo -createLoadingScreenDemo().catch(console.error); -``` diff --git a/packages/core/docs/api/schemas/Renderable.class.json b/packages/core/docs/api/schemas/Renderable.class.json new file mode 100644 index 000000000..a5f263ac5 --- /dev/null +++ b/packages/core/docs/api/schemas/Renderable.class.json @@ -0,0 +1,37 @@ +{ + "$ref": "#/definitions/Renderable", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Renderable": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "num": { + "type": "number" + }, + "parent": { + "anyOf": [ + { + "$ref": "#/definitions/Renderable" + }, + { + "type": "null" + } + ] + }, + "selectable": { + "type": "boolean" + } + }, + "required": [ + "id", + "num", + "selectable", + "parent" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/core/docs/api/styling/text-styling.md b/packages/core/docs/api/styling/text-styling.md deleted file mode 100644 index 241196278..000000000 --- a/packages/core/docs/api/styling/text-styling.md +++ /dev/null @@ -1,451 +0,0 @@ -# Text Styling API - -OpenTUI provides a rich text styling system that allows you to create formatted text with colors, attributes, and complex layouts. - -## StyledText - -The `StyledText` class is the core of the text styling system, allowing you to create rich text with different styles. - -### Creating Styled Text - -```typescript -import { StyledText, stringToStyledText } from '@opentui/core'; - -// Create an empty styled text -const text = new StyledText(); - -// Create from a string -const fromString = stringToStyledText('Hello, world!'); - -// Create from a string with ANSI escape codes -const withAnsi = stringToStyledText('\x1b[31mRed text\x1b[0m and \x1b[1mBold text\x1b[0m'); -``` - -### Adding Content - -```typescript -// Add plain text -text.pushText('Hello, '); -text.pushText('world!'); - -// Add styled text -text.pushFg('#ff0000'); // Start red text -text.pushText('This is red'); -text.popFg(); // End red text - -text.pushBg('#0000ff'); // Start blue background -text.pushText('This has a blue background'); -text.popBg(); // End blue background - -text.pushAttributes(0x01); // Start bold text (0x01 is bold) -text.pushText('This is bold'); -text.popAttributes(); // End bold text - -// Combine styles -text.pushFg('#ff0000'); -text.pushBg('#ffffff'); -text.pushAttributes(0x01 | 0x04); // Bold and underline -text.pushText('Bold red text on white background with underline'); -text.popAttributes(); -text.popBg(); -text.popFg(); -``` - -### Text Attributes - -Text attributes can be combined using bitwise OR (`|`). - -| Attribute | Value | Description | -|-----------|-------|-------------| -| `BOLD` | `0x01` | Bold text | -| `DIM` | `0x02` | Dimmed text | -| `ITALIC` | `0x04` | Italic text | -| `UNDERLINE` | `0x08` | Underlined text | -| `BLINK` | `0x10` | Blinking text | -| `INVERSE` | `0x20` | Inverted colors | -| `HIDDEN` | `0x40` | Hidden text | -| `STRIKETHROUGH` | `0x80` | Strikethrough text | - -```typescript -// Example of combining attributes -text.pushAttributes(0x01 | 0x08); // Bold and underlined -text.pushText('Bold and underlined text'); -text.popAttributes(); -``` - -### Converting to String - -```typescript -// Convert to plain text (strips all formatting) -const plainText = text.toString(); - -// Convert to ANSI-encoded string (preserves formatting) -const ansiText = text.toAnsiString(); -``` - -### Example: Creating a Rich Text Message - -```typescript -import { StyledText, TextRenderable } from '@opentui/core'; - -// Create a styled text message -const message = new StyledText(); - -// Add a timestamp -message.pushFg('#888888'); -message.pushText('[12:34:56] '); -message.popFg(); - -// Add a status indicator -message.pushFg('#ff0000'); -message.pushAttributes(0x01); // Bold -message.pushText('ERROR'); -message.popAttributes(); -message.popFg(); - -// Add a separator -message.pushText(': '); - -// Add the message -message.pushText('Failed to connect to server '); - -// Add details -message.pushFg('#3498db'); -message.pushText('example.com'); -message.popFg(); - -// Create a text component with the styled message -const textComponent = new TextRenderable('errorMessage', { - content: message -}); -``` - -## HAST Styled Text - -OpenTUI supports HAST (Hypertext Abstract Syntax Tree) for more complex text styling. - -### Creating HAST Styled Text - -```typescript -import { hastToStyledText } from '@opentui/core'; - -// Create styled text from HAST -const hast = { - type: 'root', - children: [ - { - type: 'element', - tagName: 'span', - properties: { - style: 'color: red; font-weight: bold;' - }, - children: [ - { - type: 'text', - value: 'Important' - } - ] - }, - { - type: 'text', - value: ' message' - } - ] -}; - -const styledText = hastToStyledText(hast); -``` - -### Supported HAST Properties - -| Property | Description | -|----------|-------------| -| `style` | CSS-like style string | -| `color`, `backgroundColor` | Text colors | -| `bold`, `italic`, `underline` | Text formatting | - -### Example: Syntax Highlighting with HAST - -```typescript -import { hastToStyledText, TextRenderable } from '@opentui/core'; - -// HAST representation of syntax-highlighted code -const codeHast = { - type: 'root', - children: [ - { - type: 'element', - tagName: 'span', - properties: { style: 'color: #569cd6;' }, - children: [{ type: 'text', value: 'function' }] - }, - { type: 'text', value: ' ' }, - { - type: 'element', - tagName: 'span', - properties: { style: 'color: #dcdcaa;' }, - children: [{ type: 'text', value: 'greet' }] - }, - { type: 'text', value: '(' }, - { - type: 'element', - tagName: 'span', - properties: { style: 'color: #9cdcfe;' }, - children: [{ type: 'text', value: 'name' }] - }, - { type: 'text', value: ') {\n ' }, - { - type: 'element', - tagName: 'span', - properties: { style: 'color: #c586c0;' }, - children: [{ type: 'text', value: 'return' }] - }, - { type: 'text', value: ' ' }, - { - type: 'element', - tagName: 'span', - properties: { style: 'color: #ce9178;' }, - children: [{ type: 'text', value: '`Hello, ${name}!`' }] - }, - { type: 'text', value: ';\n}' } - ] -}; - -// Convert to styled text -const codeStyledText = hastToStyledText(codeHast); - -// Create a text component with the syntax-highlighted code -const codeBlock = new TextRenderable('codeBlock', { - content: codeStyledText, - bg: '#1e1e1e' -}); -``` - -## Text Buffer - -The `TextBuffer` class provides low-level text rendering capabilities. - -### Creating a Text Buffer - -```typescript -import { TextBuffer, RGBA } from '@opentui/core'; - -// Create a text buffer with initial capacity -const buffer = TextBuffer.create(100); - -// Set default styles -buffer.setDefaultFg(RGBA.fromHex('#ffffff')); -buffer.setDefaultBg(RGBA.fromHex('#000000')); -buffer.setDefaultAttributes(0); // No attributes -``` - -### Adding Text - -```typescript -// Add text at a specific position -buffer.addText(0, 0, 'Hello, world!'); - -// Add text with specific styles -buffer.addText(0, 1, 'Colored text', RGBA.fromHex('#ff0000'), RGBA.fromHex('#000000'), 0x01); - -// Add styled text -buffer.setStyledText(styledTextObject); -``` - -### Selection - -```typescript -// Set selection range -buffer.setSelection(5, 10, RGBA.fromHex('#3498db'), RGBA.fromHex('#ffffff')); - -// Reset selection -buffer.resetSelection(); -``` - -### Example: Creating a Custom Text Component - -```typescript -import { Renderable, TextBuffer, OptimizedBuffer, RGBA } from '@opentui/core'; - -class HighlightedTextRenderable extends Renderable { - private textBuffer: TextBuffer; - private _text: string = ''; - private _highlightRanges: Array<{ start: number; end: number; color: RGBA }> = []; - - constructor(id: string, options: any = {}) { - super(id, options); - - this._text = options.text || ''; - this._highlightRanges = options.highlightRanges || []; - - this.textBuffer = TextBuffer.create(this._text.length + 100); - this.textBuffer.setDefaultFg(RGBA.fromHex('#ffffff')); - this.textBuffer.setDefaultBg(RGBA.fromHex('#000000')); - - this.updateTextBuffer(); - } - - get text(): string { - return this._text; - } - - set text(value: string) { - this._text = value; - this.updateTextBuffer(); - } - - get highlightRanges(): Array<{ start: number; end: number; color: RGBA }> { - return [...this._highlightRanges]; - } - - set highlightRanges(value: Array<{ start: number; end: number; color: RGBA | string }>) { - this._highlightRanges = value.map(range => ({ - start: range.start, - end: range.end, - color: typeof range.color === 'string' ? RGBA.fromHex(range.color) : range.color - })); - this.updateTextBuffer(); - } - - private updateTextBuffer(): void { - this.textBuffer.clear(); - this.textBuffer.addText(0, 0, this._text); - - // Apply highlights - for (const range of this._highlightRanges) { - this.textBuffer.setSelection(range.start, range.end, range.color); - } - - this.needsUpdate(); - } - - protected renderSelf(buffer: OptimizedBuffer): void { - const clipRect = { - x: this.x, - y: this.y, - width: this.width, - height: this.height - }; - - buffer.drawTextBuffer(this.textBuffer, this.x, this.y, clipRect); - } - - destroy(): void { - this.textBuffer.destroy(); - super.destroy(); - } -} - -// Usage -const highlightedText = new HighlightedTextRenderable('highlightedText', { - width: 40, - height: 1, - text: 'This is a text with highlighted parts', - highlightRanges: [ - { start: 10, end: 14, color: '#ff0000' }, // "text" in red - { start: 20, end: 31, color: '#00ff00' } // "highlighted" in green - ] -}); - -// Update text -highlightedText.text = 'New text with different highlights'; - -// Update highlights -highlightedText.highlightRanges = [ - { start: 0, end: 3, color: '#3498db' }, // "New" in blue - { start: 9, end: 18, color: '#e74c3c' } // "different" in red -]; -``` - -## Border Styling - -OpenTUI provides various border styles for boxes and other components. - -### Border Styles - -| Style | Description | -|-------|-------------| -| `'single'` | Single line border | -| `'double'` | Double line border | -| `'rounded'` | Rounded corners with single lines | -| `'bold'` | Bold single line border | -| `'dashed'` | Dashed border | -| `'dotted'` | Dotted border | -| `'ascii'` | ASCII characters (`+`, `-`, `|`) | - -### Border Sides - -You can specify which sides of a component should have borders: - -```typescript -import { BoxRenderable } from '@opentui/core'; - -// All sides (default) -const box1 = new BoxRenderable('box1', { - border: true -}); - -// Specific sides -const box2 = new BoxRenderable('box2', { - border: ['top', 'bottom'] -}); - -const box3 = new BoxRenderable('box3', { - border: ['left', 'right'] -}); - -// No borders -const box4 = new BoxRenderable('box4', { - border: false -}); -``` - -### Custom Border Characters - -You can define custom border characters for unique styling: - -```typescript -import { BoxRenderable } from '@opentui/core'; - -const customBox = new BoxRenderable('customBox', { - customBorderChars: { - topLeft: '╭', - topRight: '╮', - bottomLeft: '╰', - bottomRight: '╯', - horizontal: '─', - vertical: '│', - left: '├', - right: '┤', - top: '┬', - bottom: '┴', - middle: '┼' - } -}); -``` - -### Example: Creating a Custom Panel - -```typescript -import { BoxRenderable, TextRenderable } from '@opentui/core'; - -// Create a panel with custom styling -const panel = new BoxRenderable('panel', { - width: 40, - height: 10, - borderStyle: 'rounded', - borderColor: '#3498db', - backgroundColor: '#222222', - title: 'Custom Panel', - titleAlignment: 'center' -}); - -// Add content -const content = new TextRenderable('content', { - content: 'This panel has rounded corners and a centered title.', - fg: '#ffffff', - padding: 1 -}); - -panel.add(content); -``` diff --git a/packages/core/docs/api/utils/console.md b/packages/core/docs/api/utils/console.md deleted file mode 100644 index d3e8457d2..000000000 --- a/packages/core/docs/api/utils/console.md +++ /dev/null @@ -1,464 +0,0 @@ -# Console Utility - -OpenTUI provides a console utility for debugging and logging in terminal applications. - -## Overview - -The console utility allows you to create a console window within your terminal application for displaying logs, errors, and debug information without interfering with your main UI. - -## Console API - -### Creating a Console - -```typescript -import { TerminalConsole, ConsolePosition } from '@opentui/core'; -import { createCliRenderer } from '@opentui/core'; - -// Create a renderer -const renderer = await createCliRenderer(); - -// Create a console with default options -const console = new TerminalConsole(renderer); - -// Create a console with custom options -const customConsole = new TerminalConsole(renderer, { - position: ConsolePosition.BOTTOM, - sizePercent: 30, - zIndex: 100, - colorInfo: '#00FFFF', - colorWarn: '#FFFF00', - colorError: '#FF0000', - colorDebug: '#808080', - colorDefault: '#FFFFFF', - backgroundColor: 'rgba(0.1, 0.1, 0.1, 0.7)', - startInDebugMode: false, - title: 'Debug Console', - titleBarColor: 'rgba(0.05, 0.05, 0.05, 0.7)', - titleBarTextColor: '#FFFFFF', - cursorColor: '#00A0FF', - maxStoredLogs: 2000, - maxDisplayLines: 3000 -}); -``` - -### Console Options - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `position` | `ConsolePosition` | `ConsolePosition.BOTTOM` | Position of the console window | -| `sizePercent` | `number` | `30` | Size percentage of the console relative to terminal | -| `zIndex` | `number` | `Infinity` | Z-index of the console | -| `colorInfo` | `ColorInput` | `'#00FFFF'` | Color for info messages | -| `colorWarn` | `ColorInput` | `'#FFFF00'` | Color for warning messages | -| `colorError` | `ColorInput` | `'#FF0000'` | Color for error messages | -| `colorDebug` | `ColorInput` | `'#808080'` | Color for debug messages | -| `colorDefault` | `ColorInput` | `'#FFFFFF'` | Default text color | -| `backgroundColor` | `ColorInput` | `RGBA.fromValues(0.1, 0.1, 0.1, 0.7)` | Background color | -| `startInDebugMode` | `boolean` | `false` | Whether to start in debug mode | -| `title` | `string` | `'Console'` | Title of the console window | -| `titleBarColor` | `ColorInput` | `RGBA.fromValues(0.05, 0.05, 0.05, 0.7)` | Title bar color | -| `titleBarTextColor` | `ColorInput` | `'#FFFFFF'` | Title bar text color | -| `cursorColor` | `ColorInput` | `'#00A0FF'` | Cursor color | -| `maxStoredLogs` | `number` | `2000` | Maximum number of logs to store | -| `maxDisplayLines` | `number` | `3000` | Maximum number of lines to display | - -### Logging Messages - -```typescript -// The TerminalConsole captures standard console methods -// These will be displayed in the terminal console -console.log('Hello, world!'); -console.error('Something went wrong'); -console.warn('This is a warning'); -console.info('This is an informational message'); -console.debug('This is a debug message'); - -// Log an object -console.log({ name: 'John', age: 30 }); - -// Log with formatting -console.log('User %s logged in at %s', 'John', new Date().toISOString()); - -// Activate console capture -terminalConsole.activate(); - -// Deactivate console capture -terminalConsole.deactivate(); -``` - -### Clearing the Console - -```typescript -// Clear the console -terminalConsoleCache.clearConsole(); -``` - -### Showing and Hiding the Console - -```typescript -// Show the console -terminalConsole.show(); - -// Hide the console -terminalConsole.hide(); - -// Toggle the console visibility -// Not directly available, but can be implemented: -if (terminalConsole.isVisible) { - terminalConsole.hide(); -} else { - terminalConsole.show(); -} - -// Check if the console is visible -const isVisible = terminalConsole.isVisible; -``` - -### Resizing the Console - -```typescript -// Resize the console -console.resize(100, 30); - -// Get the console dimensions -const { width, height } = console.getDimensions(); -``` - -### Scrolling the Console - -```typescript -// Scroll to the top -console.scrollToTop(); - -// Scroll to the bottom -console.scrollToBottom(); - -// Scroll up by a number of lines -console.scrollUp(5); - -// Scroll down by a number of lines -console.scrollDown(5); - -// Scroll to a specific line -console.scrollToLine(42); -``` - -### Filtering Console Output - -```typescript -// Set a filter function -console.setFilter((message) => { - // Only show messages containing 'error' - return message.text.includes('error'); -}); - -// Clear the filter -console.clearFilter(); -``` - -### Capturing Standard Output - -```typescript -// Capture stdout and stderr -console.captureStdout(); -console.captureStderr(); - -// Stop capturing -console.releaseStdout(); -console.releaseStderr(); -``` - -## Example: Creating a Debug Console - -```typescript -import { createCliRenderer, BoxRenderable, TextRenderable, Console } from '@opentui/core'; - -async function createDebugConsoleDemo() { - // Create the renderer - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container for the main UI - const container = new BoxRenderable('container', { - width: '100%', - height: '70%', - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222' - }); - - root.add(container); - - // Add some content to the main UI - const text = new TextRenderable('text', { - content: 'Press F12 to toggle the debug console\nPress L to log a message\nPress E to log an error\nPress W to log a warning\nPress C to clear the console', - fg: '#ffffff', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 - }); - - container.add(text); - - // Create a debug console - const debugConsole = new Console({ - width: renderer.width, - height: Math.floor(renderer.height * 0.3), - title: 'Debug Console', - position: 'bottom', - borderStyle: 'double', - borderColor: '#e74c3c', - backgroundColor: '#222222', - fg: '#ffffff' - }); - - // Add the console to the renderer - root.add(debugConsole); - - // Hide the console initially - debugConsole.hide(); - - // Log some initial messages - debugConsole.log('Debug console initialized'); - debugConsole.info('Press F12 to toggle the console'); - - // Handle keyboard input - renderer.on('key', (key) => { - const keyStr = key.toString(); - - if (keyStr === 'f12') { - debugConsole.toggle(); - } else if (keyStr === 'l') { - debugConsole.log(`Log message at ${new Date().toISOString()}`); - } else if (keyStr === 'e') { - debugConsole.error(`Error message at ${new Date().toISOString()}`); - } else if (keyStr === 'w') { - debugConsole.warn(`Warning message at ${new Date().toISOString()}`); - } else if (keyStr === 'c') { - debugConsole.clear(); - } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C - renderer.destroy(); - process.exit(0); - } - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the debug console demo -createDebugConsoleDemo().catch(console.error); -``` - -## Example: Capturing Standard Output - -```typescript -import { createCliRenderer, BoxRenderable, TextRenderable, Console } from '@opentui/core'; - -async function createStdoutCaptureDemo() { - // Create the renderer - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container for the main UI - const container = new BoxRenderable('container', { - width: '100%', - height: '70%', - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222' - }); - - root.add(container); - - // Add some content to the main UI - const text = new TextRenderable('text', { - content: 'Press F12 to toggle the console\nPress L to log to stdout\nPress E to log to stderr\nPress C to clear the console', - fg: '#ffffff', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 - }); - - container.add(text); - - // Create a debug console - const debugConsole = new Console({ - width: renderer.width, - height: Math.floor(renderer.height * 0.3), - title: 'Stdout/Stderr Capture', - position: 'bottom', - borderStyle: 'double', - borderColor: '#e74c3c', - backgroundColor: '#222222', - fg: '#ffffff' - }); - - // Add the console to the renderer - root.add(debugConsole); - - // Hide the console initially - debugConsole.hide(); - - // Capture stdout and stderr - debugConsole.captureStdout(); - debugConsole.captureStderr(); - - // Log some initial messages - debugConsole.log('Stdout/stderr capture initialized'); - debugConsole.info('Press F12 to toggle the console'); - - // Handle keyboard input - renderer.on('key', (key) => { - const keyStr = key.toString(); - - if (keyStr === 'f12') { - debugConsole.toggle(); - } else if (keyStr === 'l') { - // This will be captured by the console - console.log(`Stdout message at ${new Date().toISOString()}`); - } else if (keyStr === 'e') { - // This will be captured by the console - console.error(`Stderr message at ${new Date().toISOString()}`); - } else if (keyStr === 'c') { - debugConsole.clear(); - } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C - // Release stdout and stderr before exiting - debugConsole.releaseStdout(); - debugConsole.releaseStderr(); - - renderer.destroy(); - process.exit(0); - } - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the stdout capture demo -createStdoutCaptureDemo().catch(console.error); -``` - -## Example: Creating a Network Monitor - -```typescript -import { createCliRenderer, BoxRenderable, TextRenderable, Console } from '@opentui/core'; -import * as http from 'http'; - -async function createNetworkMonitorDemo() { - // Create the renderer - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container for the main UI - const container = new BoxRenderable('container', { - width: '100%', - height: '70%', - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222' - }); - - root.add(container); - - // Add some content to the main UI - const text = new TextRenderable('text', { - content: 'Network Monitor\n\nPress F12 to toggle the console\nPress R to make a request\nPress C to clear the console', - fg: '#ffffff', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 - }); - - container.add(text); - - // Create a network monitor console - const networkConsole = new Console({ - width: renderer.width, - height: Math.floor(renderer.height * 0.3), - title: 'Network Monitor', - position: 'bottom', - borderStyle: 'double', - borderColor: '#9b59b6', - backgroundColor: '#222222', - fg: '#ffffff' - }); - - // Add the console to the renderer - root.add(networkConsole); - - // Hide the console initially - networkConsole.hide(); - - // Log some initial messages - networkConsole.log('Network monitor initialized'); - networkConsole.info('Press F12 to toggle the console'); - - // Function to make a request - function makeRequest() { - const startTime = Date.now(); - networkConsole.log(`Making request to example.com...`, '#3498db'); - - http.get('http://example.com', (res) => { - const endTime = Date.now(); - const duration = endTime - startTime; - - networkConsole.log(`Response received in ${duration}ms`, '#2ecc71'); - networkConsole.log(`Status: ${res.statusCode} ${res.statusMessage}`, '#2ecc71'); - - // Log headers - networkConsole.log('Headers:', '#f39c12'); - for (const [key, value] of Object.entries(res.headers)) { - networkConsole.log(` ${key}: ${value}`, '#f39c12'); - } - - // Collect response body - let body = ''; - res.on('data', (chunk) => { - body += chunk; - }); - - res.on('end', () => { - networkConsole.log(`Body length: ${body.length} bytes`, '#2ecc71'); - - // Show a preview of the body - if (body.length > 0) { - networkConsole.log('Body preview:', '#f39c12'); - networkConsole.log(body.substring(0, 200) + (body.length > 200 ? '...' : ''), '#f39c12'); - } - }); - }).on('error', (err) => { - networkConsole.error(`Request failed: ${err.message}`); - }); - } - - // Handle keyboard input - renderer.on('key', (key) => { - const keyStr = key.toString(); - - if (keyStr === 'f12') { - networkConsole.toggle(); - } else if (keyStr === 'r') { - makeRequest(); - } else if (keyStr === 'c') { - networkConsole.clear(); - } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C - renderer.destroy(); - process.exit(0); - } - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the network monitor demo -createNetworkMonitorDemo().catch(console.error); -``` diff --git a/packages/core/docs/api/utils/output-capture.md b/packages/core/docs/api/utils/output-capture.md deleted file mode 100644 index 5b000e913..000000000 --- a/packages/core/docs/api/utils/output-capture.md +++ /dev/null @@ -1,428 +0,0 @@ -# Output Capture - -OpenTUI provides utilities for capturing stdout and stderr output, allowing you to redirect console output to your terminal UI. - -## Overview - -The output capture utilities allow you to intercept standard output and error streams and redirect them to your OpenTUI application. This is useful for creating debug consoles, log viewers, and other tools that need to display output from other parts of your application. - -## Output Capture API - -### Capturing Stdout and Stderr - -```typescript -import { capture, CapturedWritableStream } from '@opentui/core'; - -// Create captured writable streams -const mockStdout = new CapturedWritableStream("stdout", capture); -const mockStderr = new CapturedWritableStream("stderr", capture); - -// Listen for captured output -capture.on('write', (stream, data) => { - console.log(`Captured ${stream}:`, data); -}); - -// Write to the streams -mockStdout.write('Hello from stdout'); -mockStderr.write('Error from stderr'); - -// Get all captured output -const allOutput = capture.claimOutput(); -``` - -### Capture API - -The `Capture` class provides methods for capturing and managing output: - -```typescript -import { Capture } from '@opentui/core'; - -// Create a capture instance -const capture = new Capture(); - -// Listen for write events -capture.on('write', (stream, data) => { - console.log(`Captured ${stream}:`, data); -}); - -// Write data to the capture -capture.write('stdout', 'Hello world'); - -// Get the size of captured data -const size = capture.size; - -// Get all captured output and clear the buffer -const output = capture.claimOutput(); -``` - -### CapturedWritableStream API - -The `CapturedWritableStream` class implements Node's Writable stream interface: - -```typescript -import { Capture, CapturedWritableStream } from '@opentui/core'; - -// Create a capture instance -const capture = new Capture(); - -// Create writable streams -const stdout = new CapturedWritableStream('stdout', capture); -const stderr = new CapturedWritableStream('stderr', capture); - -// Write to the streams -stdout.write('Standard output'); -stderr.write('Standard error'); -``` - -## Example: Creating a Log Viewer - -```typescript -import { createCliRenderer, BoxRenderable, TextRenderable, captureStdout, captureStderr } from '@opentui/core'; - -async function createLogViewerDemo() { - // Create the renderer - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container for the main UI - const container = new BoxRenderable('container', { - width: '100%', - height: '40%', - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222' - }); - - root.add(container); - - // Add some content to the main UI - const text = new TextRenderable('text', { - content: 'Log Viewer\n\nPress L to log to stdout\nPress E to log to stderr\nPress Q to quit', - fg: '#ffffff', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 - }); - - container.add(text); - - // Create a log viewer for stdout - const stdoutViewer = new BoxRenderable('stdout-viewer', { - width: '100%', - height: '30%', - borderStyle: 'single', - borderColor: '#2ecc71', - backgroundColor: '#222222', - title: 'Stdout', - padding: 1, - y: '40%' - }); - - const stdoutText = new TextRenderable('stdout-text', { - content: '', - fg: '#ffffff', - flexGrow: 1 - }); - - stdoutViewer.add(stdoutText); - root.add(stdoutViewer); - - // Create a log viewer for stderr - const stderrViewer = new BoxRenderable('stderr-viewer', { - width: '100%', - height: '30%', - borderStyle: 'single', - borderColor: '#e74c3c', - backgroundColor: '#222222', - title: 'Stderr', - padding: 1, - y: '70%' - }); - - const stderrText = new TextRenderable('stderr-text', { - content: '', - fg: '#ffffff', - flexGrow: 1 - }); - - stderrViewer.add(stderrText); - root.add(stderrViewer); - - // Capture stdout - const stdoutRelease = captureStdout((data) => { - // Append the data to the stdout text - stdoutText.content += data; - - // Trim the content if it gets too long - if (stdoutText.content.length > 1000) { - stdoutText.content = stdoutText.content.substring(stdoutText.content.length - 1000); - } - }, { - passthrough: true - }); - - // Capture stderr - const stderrRelease = captureStderr((data) => { - // Append the data to the stderr text - stderrText.content += data; - - // Trim the content if it gets too long - if (stderrText.content.length > 1000) { - stderrText.content = stderrText.content.substring(stderrText.content.length - 1000); - } - }, { - passthrough: true - }); - - // Handle keyboard input - renderer.on('key', (key) => { - const keyStr = key.toString(); - - if (keyStr === 'l') { - // Log to stdout - console.log(`Stdout message at ${new Date().toISOString()}`); - } else if (keyStr === 'e') { - // Log to stderr - console.error(`Stderr message at ${new Date().toISOString()}`); - } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C - // Release stdout and stderr before exiting - stdoutRelease(); - stderrRelease(); - - renderer.destroy(); - process.exit(0); - } - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the log viewer demo -createLogViewerDemo().catch(console.error); -``` - -## Example: Capturing Child Process Output - -```typescript -import { createCliRenderer, BoxRenderable, TextRenderable, captureStdout, captureStderr } from '@opentui/core'; -import { spawn } from 'child_process'; - -async function createProcessMonitorDemo() { - // Create the renderer - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container for the main UI - const container = new BoxRenderable('container', { - width: '100%', - height: '40%', - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222' - }); - - root.add(container); - - // Add some content to the main UI - const text = new TextRenderable('text', { - content: 'Process Monitor\n\nPress R to run a process\nPress C to clear the output\nPress Q to quit', - fg: '#ffffff', - alignItems: 'center', - justifyContent: 'center', - flexGrow: 1 - }); - - container.add(text); - - // Create a process output viewer - const outputViewer = new BoxRenderable('output-viewer', { - width: '100%', - height: '60%', - borderStyle: 'single', - borderColor: '#9b59b6', - backgroundColor: '#222222', - title: 'Process Output', - padding: 1, - y: '40%' - }); - - const outputText = new TextRenderable('output-text', { - content: '', - fg: '#ffffff', - flexGrow: 1 - }); - - outputViewer.add(outputText); - root.add(outputViewer); - - // Function to run a process - function runProcess() { - // Clear the output - outputText.content = ''; - - // Add a header - outputText.content = `Running 'ls -la' at ${new Date().toISOString()}\n\n`; - - // Spawn a process - const process = spawn('ls', ['-la']); - - // Capture stdout - process.stdout.on('data', (data) => { - outputText.content += `[stdout] ${data}`; - }); - - // Capture stderr - process.stderr.on('data', (data) => { - outputText.content += `[stderr] ${data}`; - }); - - // Handle process exit - process.on('close', (code) => { - outputText.content += `\nProcess exited with code ${code}\n`; - }); - } - - // Handle keyboard input - renderer.on('key', (key) => { - const keyStr = key.toString(); - - if (keyStr === 'r') { - runProcess(); - } else if (keyStr === 'c') { - outputText.content = ''; - } else if (keyStr === 'q' || keyStr === '\u0003') { // q or Ctrl+C - renderer.destroy(); - process.exit(0); - } - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the process monitor demo -createProcessMonitorDemo().catch(console.error); -``` - -## Example: Creating a REPL - -```typescript -import { createCliRenderer, BoxRenderable, TextRenderable, InputRenderable, captureStdout, captureStderr } from '@opentui/core'; -import { runInNewContext } from 'vm'; - -async function createReplDemo() { - // Create the renderer - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a container for the output - const outputContainer = new BoxRenderable('output-container', { - width: '100%', - height: '90%', - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222', - title: 'Output', - padding: 1 - }); - - const outputText = new TextRenderable('output-text', { - content: 'JavaScript REPL\nType JavaScript code and press Enter to execute\n\n', - fg: '#ffffff', - flexGrow: 1 - }); - - outputContainer.add(outputText); - root.add(outputContainer); - - // Create an input field - const inputContainer = new BoxRenderable('input-container', { - width: '100%', - height: '10%', - borderStyle: 'single', - borderColor: '#2ecc71', - backgroundColor: '#222222', - title: 'Input', - padding: 1, - y: '90%' - }); - - const inputField = new InputRenderable('input-field', { - placeholder: 'Enter JavaScript code...', - fg: '#ffffff', - flexGrow: 1 - }); - - inputContainer.add(inputField); - root.add(inputContainer); - - // Focus the input field - inputField.focus(); - - // Create a context for the REPL - const context = { - console: { - log: (...args) => { - outputText.content += args.map(arg => String(arg)).join(' ') + '\n'; - }, - error: (...args) => { - outputText.content += '\x1b[31m' + args.map(arg => String(arg)).join(' ') + '\x1b[0m\n'; - }, - warn: (...args) => { - outputText.content += '\x1b[33m' + args.map(arg => String(arg)).join(' ') + '\x1b[0m\n'; - }, - info: (...args) => { - outputText.content += '\x1b[36m' + args.map(arg => String(arg)).join(' ') + '\x1b[0m\n'; - } - } - }; - - // Handle input submission - inputField.on('submit', (value) => { - // Add the input to the output - outputText.content += `> ${value}\n`; - - // Clear the input field - inputField.setValue(''); - - // Execute the code - try { - const result = runInNewContext(value, context); - - // Display the result - if (result !== undefined) { - outputText.content += `${result}\n`; - } - } catch (error) { - // Display the error - outputText.content += `\x1b[31mError: ${error.message}\x1b[0m\n`; - } - - // Add a blank line - outputText.content += '\n'; - }); - - // Handle keyboard input - renderer.on('key', (key) => { - const keyStr = key.toString(); - - if (keyStr === '\u0003') { // Ctrl+C - renderer.destroy(); - process.exit(0); - } - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the REPL demo -createReplDemo().catch(console.error); -``` diff --git a/packages/core/docs/api/utils/utilities.md b/packages/core/docs/api/utils/utilities.md deleted file mode 100644 index 70f399748..000000000 --- a/packages/core/docs/api/utils/utilities.md +++ /dev/null @@ -1,733 +0,0 @@ -# Utilities API - -OpenTUI provides a variety of utility functions and classes to help with common tasks when building terminal user interfaces. - -## Color Utilities - -### RGBA Class - -The `RGBA` class represents a color with red, green, blue, and alpha components. - -```typescript -import { RGBA } from '@opentui/core'; - -// Create a color from RGBA values (0-1 range) -const red = RGBA.fromValues(1, 0, 0, 1); - -// Create a color from hex string -const blue = RGBA.fromHex('#0000ff'); - -// Create a color from RGB values (0-255 range) -const green = RGBA.fromRGB(0, 255, 0); - -// Create a color from RGBA values (0-255 range) -const purple = RGBA.fromRGBA(128, 0, 128, 255); - -// Get color components -const r = red.r; // 1 -const g = red.g; // 0 -const b = red.b; // 0 -const a = red.a; // 1 - -// Convert to integer components (0-255) -const ints = red.toInts(); // [255, 0, 0, 255] - -// Convert to hex string -const hex = red.toHex(); // '#ff0000' - -// Check if two colors are equal -const isEqual = red.equals(RGBA.fromHex('#ff0000')); // true - -// Create a new color with modified alpha -const transparentRed = red.withAlpha(0.5); -``` - -### parseColor Function - -The `parseColor` function converts various color formats to an `RGBA` object. - -```typescript -import { parseColor, RGBA } from '@opentui/core'; - -// Parse a hex string -const red = parseColor('#ff0000'); - -// Parse an RGB string -const green = parseColor('rgb(0, 255, 0)'); - -// Parse an RGBA string -const blue = parseColor('rgba(0, 0, 255, 0.5)'); - -// Pass through an existing RGBA object -const existing = parseColor(RGBA.fromHex('#ff00ff')); - -// Parse a named color -const black = parseColor('black'); - -// Parse 'transparent' -const transparent = parseColor('transparent'); // RGBA with 0 alpha -``` - -### Example: Creating a Color Palette - -```typescript -import { BoxRenderable, TextRenderable, RGBA } from '@opentui/core'; - -// Create a color palette component -class ColorPalette extends BoxRenderable { - constructor(id: string, options = {}) { - super(id, { - width: 40, - height: 20, - borderStyle: 'single', - borderColor: '#ffffff', - padding: 1, - ...options - }); - - // Define colors - const colors = [ - { name: 'Red', hex: '#e74c3c' }, - { name: 'Orange', hex: '#e67e22' }, - { name: 'Yellow', hex: '#f1c40f' }, - { name: 'Green', hex: '#2ecc71' }, - { name: 'Blue', hex: '#3498db' }, - { name: 'Purple', hex: '#9b59b6' }, - { name: 'Gray', hex: '#95a5a6' }, - { name: 'Black', hex: '#000000' }, - { name: 'White', hex: '#ffffff' } - ]; - - // Create color swatches - for (let i = 0; i < colors.length; i++) { - const color = colors[i]; - - // Create a swatch container - const swatch = new BoxRenderable(`swatch-${i}`, { - width: '100%', - height: 2, - marginBottom: 1, - flexDirection: 'row', - border: false - }); - - // Create a color box - const colorBox = new BoxRenderable(`color-${i}`, { - width: 4, - height: 2, - marginRight: 1, - borderStyle: 'single', - borderColor: '#ffffff', - backgroundColor: color.hex - }); - - // Create a label - const label = new TextRenderable(`label-${i}`, { - content: `${color.name} (${color.hex})`, - fg: '#ffffff' - }); - - // Add to swatch - swatch.add(colorBox); - swatch.add(label); - - // Add to palette - this.add(swatch); - } - } -} - -// Usage -const palette = new ColorPalette('palette'); -root.add(palette); -``` - -## ANSI Terminal Utilities - -The `ANSI` class provides utilities for working with ANSI escape sequences. - -```typescript -import { ANSI } from '@opentui/core'; - -// Cursor movement -const moveCursor = ANSI.moveCursor(10, 5); // Move to row 10, column 5 -const moveUp = ANSI.moveUp(2); // Move up 2 lines -const moveDown = ANSI.moveDown(3); // Move down 3 lines -const moveLeft = ANSI.moveLeft(4); // Move left 4 columns -const moveRight = ANSI.moveRight(5); // Move right 5 columns - -// Cursor visibility -const showCursor = ANSI.showCursor; -const hideCursor = ANSI.hideCursor; - -// Screen control -const clearScreen = ANSI.clearScreen; -const clearLine = ANSI.clearLine; -const clearToEndOfLine = ANSI.clearToEndOfLine; -const clearToStartOfLine = ANSI.clearToStartOfLine; - -// Terminal modes -const alternateScreen = ANSI.switchToAlternateScreen; -const mainScreen = ANSI.switchToMainScreen; - -// Colors -const setForeground = ANSI.setRgbForeground(255, 0, 0); // Red text -const setBackground = ANSI.setRgbBackground(0, 0, 255); // Blue background -const resetColors = ANSI.resetColors; - -// Text styles -const bold = ANSI.bold; -const dim = ANSI.dim; -const italic = ANSI.italic; -const underline = ANSI.underline; -const blink = ANSI.blink; -const inverse = ANSI.inverse; -const hidden = ANSI.hidden; -const strikethrough = ANSI.strikethrough; -const resetStyles = ANSI.resetStyles; - -// Mouse support -const enableMouse = ANSI.enableMouseTracking; -const disableMouse = ANSI.disableMouseTracking; -``` - -## Buffer Utilities - -### OptimizedBuffer - -The `OptimizedBuffer` class provides a high-performance buffer for terminal rendering. The public API uses these primary operations (many are FFI-backed for performance). - -```typescript -import { OptimizedBuffer, RGBA } from '@opentui/core'; - -// Create a buffer -const buffer = OptimizedBuffer.create(80, 24); - -// Set a cell (x, y, char, fg, bg, attributes) -buffer.setCell(10, 5, 'A', RGBA.fromHex('#ffffff'), RGBA.fromHex('#000000'), 0); - -// Read a cell (returns { char, fg, bg, attributes } or null) -const cell = buffer.get(10, 5); -if (cell) { - const charCode = cell.char; - const fg = cell.fg; - const bg = cell.bg; - const attrs = cell.attributes; -} - -// Alpha-aware single-cell write -buffer.setCellWithAlphaBlending(5, 5, 'A', RGBA.fromHex('#ffffff'), RGBA.fromValues(0,0,0,0.5), 0); - -// Draw text -// Signature: drawText(text, x, y, fg, bg?, attributes?, selection?) -buffer.drawText('Hello, world!', 0, 0, RGBA.fromHex('#ffffff')); - -// If you have a TextBuffer (styled/rich text), render it with drawTextBuffer -buffer.drawTextBuffer(textBuffer, 2, 2, /*clipRect?*/ undefined); - -// Fill a rectangle area (alpha-aware) -buffer.fillRect(0, 0, 10, 3, RGBA.fromHex('#222222')); - -// Draw a bordered box (uses border chars + title) -buffer.drawBox({ - x: 5, - y: 5, - width: 20, - height: 10, - border: true, - borderStyle: 'single', - borderColor: RGBA.fromHex('#ffffff'), - backgroundColor: RGBA.fromHex('#000000'), - title: 'My Box', - titleAlignment: 'center' -}); - -// Compose / copy another buffer (FFI-preferred) -buffer.drawFrameBuffer(10, 5, otherBuffer); - -// Packed / supersample helpers (for advanced use) -buffer.drawPackedBuffer(dataPtr, dataLen, posX, posY, terminalWidthCells, terminalHeightCells); -buffer.drawSuperSampleBuffer(x, y, pixelDataPtr, pixelDataLength, 'rgba8unorm', alignedBytesPerRow); - -// Clear, resize, destroy -buffer.clear(RGBA.fromHex('#000000')); -buffer.resize(100, 30); -buffer.destroy(); -``` - -- Many drawing operations are implemented in native code and invoked via FFI (the public methods call into FFI where available). Use the high-level public methods above rather than relying on non-existent convenience shims. -- If you need line/rectangle helpers, implement them using `drawText`/`setCell`/`fillRect` or add a helper in your application code. - -### TextBuffer - -The `TextBuffer` class provides specialized buffer for text rendering. - -```typescript -import { TextBuffer, RGBA } from '@opentui/core'; - -// Create a text buffer -const buffer = TextBuffer.create(100); // Initial capacity - -// Set default styles -buffer.setDefaultFg(RGBA.fromHex('#ffffff')); -buffer.setDefaultBg(RGBA.fromHex('#000000')); -buffer.setDefaultAttributes(0x01); // Bold - -// Add text -buffer.addText(0, 0, 'Hello, world!'); - -// Add styled text -buffer.addText(0, 1, 'Colored text', RGBA.fromHex('#ff0000')); - -// Set styled text -buffer.setStyledText(styledTextObject); - -// Get line information -const lineInfo = buffer.lineInfo; -console.log(`Line starts: ${lineInfo.lineStarts}`); -console.log(`Line widths: ${lineInfo.lineWidths}`); - -// Handle selection -buffer.setSelection(5, 10, RGBA.fromHex('#3498db'), RGBA.fromHex('#ffffff')); -buffer.resetSelection(); - -// Clear the buffer -buffer.clear(); - -// Clean up -buffer.destroy(); -``` - -## Layout Utilities - -### TrackedNode - -The `TrackedNode` class provides a wrapper around Yoga layout nodes with additional tracking. - -```typescript -import { TrackedNode, createTrackedNode } from '@opentui/core'; -import Yoga from 'yoga-layout'; - -// Create a tracked node -const node = createTrackedNode(); - -// Create a tracked node with custom data -const nodeWithData = createTrackedNode({ myData: 'value' }); - -// Create a tracked node with custom Yoga config -const config = Yoga.Config.create(); -const nodeWithConfig = createTrackedNode({}, config); - -// Access the Yoga node -const yogaNode = node.yogaNode; - -// Set width and height -node.setWidth(100); -node.setHeight(50); - -// Set width and height with percentage -node.setWidth('50%'); -node.setHeight('25%'); - -// Add a child node -const child = createTrackedNode(); -const childIndex = node.addChild(child); - -// Insert a child at a specific index -const anotherChild = createTrackedNode(); -const insertedIndex = node.insertChild(anotherChild, 0); - -// Remove a child -node.removeChild(child); - -// Clean up -node.destroy(); -``` - -### Yoga Options Utilities - -OpenTUI provides utilities for working with Yoga layout options. - -```typescript -import { - parseFlexDirection, - parseAlign, - parseJustify, - parsePositionType -} from '@opentui/core'; -import { FlexDirection, Align, Justify, PositionType } from 'yoga-layout'; - -// Parse flex direction -const flexDirection = parseFlexDirection('row'); // FlexDirection.Row -const flexDirectionReverse = parseFlexDirection('row-reverse'); // FlexDirection.RowReverse - -// Parse align -const align = parseAlign('center'); // Align.Center -const alignStart = parseAlign('flex-start'); // Align.FlexStart - -// Parse justify -const justify = parseJustify('space-between'); // Justify.SpaceBetween -const justifyCenter = parseJustify('center'); // Justify.Center - -// Parse position type -const position = parsePositionType('absolute'); // PositionType.Absolute -const positionRelative = parsePositionType('relative'); // PositionType.Relative -``` - -## Border Utilities - -OpenTUI provides utilities for working with borders. - -```typescript -import { getBorderSides, borderCharsToArray } from '@opentui/core'; - -// Get border sides configuration -const allSides = getBorderSides(true); // All sides -const topBottom = getBorderSides(['top', 'bottom']); // Only top and bottom -const none = getBorderSides(false); // No sides - -// Convert border characters to array -const borderChars = { - topLeft: '╭', - topRight: '╮', - bottomLeft: '╰', - bottomRight: '╯', - horizontal: '─', - vertical: '│', - left: '├', - right: '┤', - top: '┬', - bottom: '┴', - middle: '┼' -}; - -const borderArray = borderCharsToArray(borderChars); -``` - -## Console Utilities - -OpenTUI provides a terminal console for debugging and logging. - -```typescript -import { createCliRenderer } from '@opentui/core'; - -// Create a renderer -const renderer = await createCliRenderer({ - useConsole: true, // Enable console - consoleOptions: { - maxLines: 100, - showTimestamps: true - } -}); - -// Access the console -const console = renderer.console; - -// Log messages -console.log('Info message'); -console.warn('Warning message'); -console.error('Error message'); -console.debug('Debug message'); - -// Clear the console -console.clear(); - -// Get cached logs -const logs = console.getCachedLogs(); - -// Enable/disable the console -renderer.useConsole = false; // Disable -renderer.useConsole = true; // Enable - -// Activate/deactivate the console -console.activate(); -console.deactivate(); -``` - -## Output Capture - -OpenTUI provides utilities for capturing stdout and stderr output. - -```typescript -import { capture } from '@opentui/core'; - -// Start capturing output -capture.start(); - -// Write to the capture buffer -capture.write('stdout', 'This is captured stdout'); -capture.write('stderr', 'This is captured stderr'); - -// Get the captured output -const output = capture.getOutput(); -console.log(output); // All captured output - -// Claim the output (get and clear) -const claimed = capture.claimOutput(); -console.log(claimed); // All captured output, buffer now empty - -// Check if there's any captured output -const size = capture.size; -console.log(`Captured ${size} bytes`); - -// Listen for write events -capture.on('write', () => { - console.log('New output captured'); -}); - -// Stop capturing -capture.stop(); -``` - -## Miscellaneous Utilities - -### Parsing Keypresses - -```typescript -import { parseKeypress } from '@opentui/core'; - -// Parse a keypress -const key = parseKeypress('\x1b[A'); // Up arrow key -console.log(key); -// { -// name: 'up', -// sequence: '\x1b[A', -// ctrl: false, -// meta: false, -// shift: false -// } - -// Parse a control key -const ctrlC = parseKeypress('\u0003'); // Ctrl+C -console.log(ctrlC); -// { -// name: 'c', -// sequence: '\u0003', -// ctrl: true, -// meta: false, -// shift: false -// } -``` - -### Parsing Mouse Events - -```typescript -import { MouseParser } from '@opentui/core'; - -// Create a mouse parser -const mouseParser = new MouseParser(); - -// Parse a mouse event -const event = mouseParser.parseMouseEvent(Buffer.from('\x1b[M !"')); -console.log(event); -// { -// type: 'down', -// button: 0, // Left button -// x: 33, -// y: 32, -// modifiers: { shift: false, alt: false, ctrl: false } -// } - -// Reset the parser -mouseParser.reset(); -``` - -### Example: Creating a Debug Overlay - -```typescript -import { createCliRenderer, BoxRenderable, TextRenderable, RGBA } from '@opentui/core'; - -async function createDebugOverlay() { - const renderer = await createCliRenderer(); - const { root } = renderer; - - // Create a main content area - const content = new BoxRenderable('content', { - width: '100%', - height: '100%', - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222' - }); - - root.add(content); - - // Create a debug overlay - class DebugOverlay extends BoxRenderable { - private fpsText: TextRenderable; - private memoryText: TextRenderable; - private positionText: TextRenderable; - private mouseX: number = 0; - private mouseY: number = 0; - - constructor(id: string, options = {}) { - super(id, { - width: 30, - height: 10, - position: 'absolute', - x: 2, - y: 2, - borderStyle: 'single', - borderColor: '#e74c3c', - backgroundColor: RGBA.fromValues(0, 0, 0, 0.8), - padding: 1, - flexDirection: 'column', - ...options - }); - - // Create text elements - this.fpsText = new TextRenderable(`${id}-fps`, { - content: 'FPS: 0', - fg: '#ffffff', - marginBottom: 1 - }); - - this.memoryText = new TextRenderable(`${id}-memory`, { - content: 'Memory: 0 MB', - fg: '#ffffff', - marginBottom: 1 - }); - - this.positionText = new TextRenderable(`${id}-position`, { - content: 'Mouse: 0,0', - fg: '#ffffff' - }); - - // Add text elements - this.add(this.fpsText); - this.add(this.memoryText); - this.add(this.positionText); - - // Listen for mouse events - renderer.on('key', (data) => { - // Toggle overlay with F12 - if (data.toString() === '\x1b[24~') { - this.visible = !this.visible; - } - }); - } - - public updateStats(fps: number, memory: number): void { - this.fpsText.content = `FPS: ${fps}`; - this.memoryText.content = `Memory: ${(memory / (1024 * 1024)).toFixed(2)} MB`; - } - - public updateMousePosition(x: number, y: number): void { - this.mouseX = x; - this.mouseY = y; - this.positionText.content = `Mouse: ${x},${y}`; - } - } - - // Create the debug overlay - const debug = new DebugOverlay('debug'); - root.add(debug); - - // Update stats periodically - setInterval(() => { - const stats = renderer.getStats(); - const memory = process.memoryUsage().heapUsed; - debug.updateStats(stats.fps, memory); - }, 1000); - - // Track mouse position - renderer.on('mouseEvent', (event) => { - debug.updateMousePosition(event.x, event.y); - }); - - // Start the renderer - renderer.start(); - - return renderer; -} - -// Create and run the debug overlay -createDebugOverlay().catch(console.error); -``` - -## Performance Utilities - -### Benchmarking - -```typescript -import { createCliRenderer, BoxRenderable, TextRenderable } from '@opentui/core'; - -async function runBenchmark() { - const renderer = await createCliRenderer({ - gatherStats: true, // Enable stats gathering - maxStatSamples: 1000 - }); - - const { root } = renderer; - - // Create a container - const container = new BoxRenderable('container', { - width: '100%', - height: '100%', - borderStyle: 'single', - borderColor: '#3498db', - backgroundColor: '#222222', - padding: 1, - flexDirection: 'column' - }); - - // Create a stats display - const statsText = new TextRenderable('stats', { - content: 'Running benchmark...', - fg: '#ffffff' - }); - - container.add(statsText); - root.add(container); - - // Start the renderer - renderer.start(); - - // Create test components - const testComponents = []; - for (let i = 0; i < 100; i++) { - const box = new BoxRenderable(`box-${i}`, { - width: 10, - height: 3, - position: 'absolute', - x: Math.floor(Math.random() * (renderer.width - 10)), - y: Math.floor(Math.random() * (renderer.height - 3)), - borderStyle: 'single', - borderColor: '#ffffff', - backgroundColor: '#333333' - }); - - testComponents.push(box); - container.add(box); - } - - // Run the benchmark for 5 seconds - setTimeout(() => { - // Get the stats - const stats = renderer.getStats(); - - // Update the display - statsText.content = ` -Benchmark Results: -FPS: ${stats.fps} -Average Frame Time: ${stats.averageFrameTime.toFixed(2)}ms -Min Frame Time: ${stats.minFrameTime.toFixed(2)}ms -Max Frame Time: ${stats.maxFrameTime.toFixed(2)}ms -Total Frames: ${stats.frameCount} - `; - - // Remove test components - for (const component of testComponents) { - container.remove(component.id); - } - - // Reset stats - renderer.resetStats(); - }, 5000); - - return renderer; -} - -// Run the benchmark -runBenchmark().catch(console.error); -``` diff --git a/packages/core/docs/modules/3d.md b/packages/core/docs/modules/3d.md new file mode 100644 index 000000000..d13322053 --- /dev/null +++ b/packages/core/docs/modules/3d.md @@ -0,0 +1,499 @@ +# 3D Module + +The 3D module provides WebGPU and Three.js integration for advanced terminal-based 3D rendering, including sprites, animations, physics, and shader effects. + +## Overview + +OpenTUI's 3D capabilities enable rich visual effects in the terminal through WebGPU rendering and Three.js integration. The module includes sprite management, particle systems, physics simulations, and custom shaders. + +## Module Entry Points + +The 3D functionality is available through a separate import: + +```typescript +// Main 3D exports +import { + ThreeCliRenderer, + WGPURenderer, + Canvas, + SpriteUtils +} from '@opentui/core/3d' + +// Animation effects +import { + SpriteAnimator, + ExplodingSpriteEffect, + PhysicsExplodingSpriteEffect, + SpriteParticleGenerator +} from '@opentui/core/3d/animation' + +// Physics adapters +import { + RapierPhysicsAdapter, + PlanckPhysicsAdapter +} from '@opentui/core/3d/physics' +``` + +## Core Components + +### Canvas + +Terminal-based canvas for 3D rendering: + +```typescript +import { Canvas } from '@opentui/core/3d' + +const canvas = new Canvas(width, height, { + backend: 'webgpu', // or 'threejs' + antialias: true, + alpha: true +}) + +// Render to terminal buffer +canvas.render(buffer) +``` + +### WGPURenderer + +WebGPU-based renderer for high-performance graphics: + +```typescript +import { WGPURenderer } from '@opentui/core/3d' + +const renderer = new WGPURenderer({ + width: 80, + height: 24, + devicePixelRatio: 2 +}) + +// Initialize WebGPU +await renderer.initialize() + +// Render frame +renderer.render(scene, camera) + +// Convert to terminal output +const buffer = renderer.toTerminalBuffer() +``` + +### Three.js Integration + +Terminal rendering with Three.js: + +```typescript +import { ThreeCliRenderer } from '@opentui/core/3d' +import * as THREE from 'three' + +const renderer = new ThreeCliRenderer({ + width: 80, + height: 24, + colors: 256, + dithering: true +}) + +// Create Three.js scene +const scene = new THREE.Scene() +const camera = new THREE.PerspectiveCamera(75, width/height, 0.1, 1000) + +// Add objects +const geometry = new THREE.BoxGeometry() +const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }) +const cube = new THREE.Mesh(geometry, material) +scene.add(cube) + +// Render to terminal +renderer.render(scene, camera) +``` + +## Sprite System + +### Sprite Management + +```typescript +import { SpriteResourceManager } from '@opentui/core/3d' + +const manager = new SpriteResourceManager() + +// Load sprite sheet +await manager.loadSpriteSheet('sprites.png', { + frameWidth: 32, + frameHeight: 32, + animations: { + idle: { frames: [0, 1, 2, 3], duration: 100 }, + walk: { frames: [4, 5, 6, 7], duration: 80 } + } +}) + +// Get sprite instance +const sprite = manager.createSprite('player', 'idle') +``` + +### Sprite Animation + +```typescript +import { SpriteAnimator } from '@opentui/core/3d/animation' + +const animator = new SpriteAnimator(sprite) + +// Play animation +animator.play('walk', { + loop: true, + speed: 1.5, + onComplete: () => console.log('Animation finished') +}) + +// Update animation +animator.update(deltaTime) +``` + +### Sprite Utils + +```typescript +import { SpriteUtils } from '@opentui/core/3d' + +// Convert image to ASCII art +const ascii = SpriteUtils.imageToAscii(imageData, { + width: 40, + height: 20, + characters: ' .:-=+*#%@' +}) + +// Generate sprite from text +const textSprite = SpriteUtils.textToSprite('HELLO', { + font: 'monospace', + size: 24, + color: '#00ff00' +}) +``` + +## Animation Effects + +### Exploding Sprite Effect + +Create explosion animations: + +```typescript +import { ExplodingSpriteEffect } from '@opentui/core/3d/animation' + +const explosion = new ExplodingSpriteEffect({ + particleCount: 100, + spread: 45, + velocity: { min: 2, max: 10 }, + lifetime: 1000, + gravity: 0.5, + colors: ['#ff0000', '#ff8800', '#ffff00'] +}) + +// Trigger explosion at position +explosion.explode(x, y) + +// Update particles +explosion.update(deltaTime) + +// Render to buffer +explosion.render(buffer) +``` + +### Physics-Based Explosions + +Realistic physics explosions: + +```typescript +import { PhysicsExplodingSpriteEffect } from '@opentui/core/3d/animation' +import { RapierPhysicsAdapter } from '@opentui/core/3d/physics' + +const physics = new RapierPhysicsAdapter() +await physics.initialize() + +const explosion = new PhysicsExplodingSpriteEffect({ + physics, + particleCount: 50, + explosionForce: 100, + damping: 0.98, + restitution: 0.8 +}) + +explosion.explode(x, y, sprite) +``` + +### Particle Generator + +Continuous particle effects: + +```typescript +import { SpriteParticleGenerator } from '@opentui/core/3d/animation' + +const generator = new SpriteParticleGenerator({ + emissionRate: 10, // particles per second + lifetime: { min: 1000, max: 2000 }, + velocity: { + angle: { min: -45, max: 45 }, + speed: { min: 1, max: 5 } + }, + size: { start: 1, end: 0.1 }, + opacity: { start: 1, end: 0 }, + colors: { + start: '#ffffff', + end: '#000000' + } +}) + +// Start emitting +generator.start() + +// Update and render +generator.update(deltaTime) +generator.render(buffer) +``` + +## Physics Integration + +### Physics Interface + +Common interface for physics engines: + +```typescript +interface PhysicsAdapter { + initialize(): Promise + createWorld(gravity?: { x: number, y: number }): PhysicsWorld + createBody(world: PhysicsWorld, options: BodyOptions): PhysicsBody + step(world: PhysicsWorld, deltaTime: number): void + dispose(): void +} +``` + +### Rapier Physics + +2D/3D physics with Rapier: + +```typescript +import { RapierPhysicsAdapter } from '@opentui/core/3d/physics' + +const physics = new RapierPhysicsAdapter() +await physics.initialize() + +const world = physics.createWorld({ x: 0, y: 9.8 }) + +// Create rigid body +const body = physics.createBody(world, { + type: 'dynamic', + position: { x: 0, y: 10 }, + shape: 'box', + size: { width: 2, height: 2 }, + mass: 1, + restitution: 0.5 +}) + +// Simulate +physics.step(world, 0.016) // 60 FPS +``` + +### Planck Physics + +2D physics with Planck.js: + +```typescript +import { PlanckPhysicsAdapter } from '@opentui/core/3d/physics' + +const physics = new PlanckPhysicsAdapter() +const world = physics.createWorld({ x: 0, y: -10 }) + +// Create ground +const ground = physics.createBody(world, { + type: 'static', + position: { x: 0, y: 0 }, + shape: 'box', + size: { width: 100, height: 1 } +}) + +// Create dynamic box +const box = physics.createBody(world, { + type: 'dynamic', + position: { x: 0, y: 20 }, + shape: 'box', + size: { width: 2, height: 2 }, + density: 1, + friction: 0.3 +}) +``` + +## Shader Support + +### Custom Shaders + +WebGPU shader integration: + +```typescript +const shader = ` + @vertex + fn vs_main(@location(0) position: vec2) -> @builtin(position) vec4 { + return vec4(position, 0.0, 1.0); + } + + @fragment + fn fs_main() -> @location(0) vec4 { + return vec4(1.0, 0.0, 0.0, 1.0); + } +` + +renderer.addShader('custom', shader) +renderer.useShader('custom') +``` + +### Post-Processing Effects + +Apply shader-based effects: + +```typescript +// Bloom effect +renderer.addPostProcess('bloom', { + threshold: 0.8, + intensity: 1.5, + radius: 4 +}) + +// Chromatic aberration +renderer.addPostProcess('chromaticAberration', { + offset: 0.01 +}) +``` + +## Texture Management + +### Texture Utils + +```typescript +import { TextureUtils } from '@opentui/core/3d' + +// Load texture +const texture = await TextureUtils.loadTexture('texture.png') + +// Convert to terminal colors +const terminalTexture = TextureUtils.toTerminalColors(texture, { + palette: 'ansi256', + dithering: 'floyd-steinberg' +}) + +// Apply to sprite +sprite.setTexture(terminalTexture) +``` + +## Performance Optimization + +### Level of Detail (LOD) + +```typescript +// Automatic LOD based on distance +const lodSprite = new LODSprite([ + { distance: 0, sprite: highDetailSprite }, + { distance: 50, sprite: mediumDetailSprite }, + { distance: 100, sprite: lowDetailSprite } +]) + +// Update based on camera distance +lodSprite.update(camera) +``` + +### Instancing + +Efficient rendering of many objects: + +```typescript +const instances = new InstancedSprites(baseSprite, 1000) + +// Update instance transforms +for (let i = 0; i < 1000; i++) { + instances.setTransform(i, { + position: { x: Math.random() * 100, y: Math.random() * 100 }, + rotation: Math.random() * Math.PI * 2, + scale: Math.random() * 2 + }) +} + +instances.render(buffer) +``` + +## Integration Example + +Complete 3D scene in terminal: + +```typescript +import { + WGPURenderer, + SpriteAnimator, + RapierPhysicsAdapter, + PhysicsExplodingSpriteEffect +} from '@opentui/core/3d' + +// Setup +const renderer = new WGPURenderer({ width: 120, height: 40 }) +await renderer.initialize() + +const physics = new RapierPhysicsAdapter() +await physics.initialize() + +const world = physics.createWorld({ x: 0, y: -9.8 }) + +// Create scene +const sprites = [] +for (let i = 0; i < 10; i++) { + const sprite = createSprite() + const body = physics.createBody(world, { + type: 'dynamic', + position: { x: i * 5, y: 20 } + }) + sprites.push({ sprite, body }) +} + +// Animation loop +function animate(deltaTime: number) { + // Update physics + physics.step(world, deltaTime) + + // Update sprites from physics + sprites.forEach(({ sprite, body }) => { + const pos = body.getPosition() + sprite.position.set(pos.x, pos.y) + sprite.rotation = body.getRotation() + }) + + // Render + renderer.render(scene, camera) + const buffer = renderer.toTerminalBuffer() + terminal.write(buffer) + + requestAnimationFrame(animate) +} + +animate(0) +``` + +## API Reference + +### Main Exports + +- `Canvas` - Terminal canvas for 3D +- `WGPURenderer` - WebGPU renderer +- `ThreeCliRenderer` - Three.js terminal renderer +- `SpriteResourceManager` - Sprite asset management +- `SpriteUtils` - Sprite utilities +- `TextureUtils` - Texture utilities + +### Animation Exports + +- `SpriteAnimator` - Sprite animation controller +- `ExplodingSpriteEffect` - Explosion effect +- `PhysicsExplodingSpriteEffect` - Physics-based explosion +- `SpriteParticleGenerator` - Particle system + +### Physics Exports + +- `RapierPhysicsAdapter` - Rapier physics integration +- `PlanckPhysicsAdapter` - Planck.js integration +- `PhysicsAdapter` - Physics interface + +## Related Modules + +- [Rendering](./rendering.md) - Core rendering system +- [Animation](./animation.md) - Timeline animations +- [Buffer](./buffer.md) - Terminal buffer operations \ No newline at end of file diff --git a/packages/core/docs/modules/animation.md b/packages/core/docs/modules/animation.md new file mode 100644 index 000000000..8cd026b61 --- /dev/null +++ b/packages/core/docs/modules/animation.md @@ -0,0 +1,664 @@ +# Animation + +> **Quick Navigation:** [Getting Started](./getting-started.md) | [Components](./components.md) | [Layout](./layout.md) | [Events](./events.md) | [Rendering](./rendering.md) + +OpenTUI provides a powerful animation system for creating smooth transitions and effects. + +## Timeline Animations + +```typescript +import { Timeline, Easing } from '@opentui/core'; + +// Create a timeline +const timeline = new Timeline({ + duration: 2000, + loop: true, + autoplay: true +}); + +// Add animations +timeline.add({ + target: myBox, + properties: { + x: { from: 0, to: 100 }, + y: { from: 0, to: 50 }, + opacity: { from: 0, to: 1 } + }, + duration: 1000, + easing: Easing.easeInOutQuad, + delay: 0 +}); + +// Chain animations +timeline + .add({ + target: box1, + properties: { x: { to: 100 } }, + duration: 500 + }) + .add({ + target: box2, + properties: { y: { to: 50 } }, + duration: 500, + offset: '-=250' // Start 250ms before previous ends + }); + +// Control playback +timeline.play(); +timeline.pause(); +timeline.stop(); +timeline.seek(500); // Jump to 500ms +``` + +## Property Animations + +```typescript +class AnimatedBox extends BoxRenderable { + private animator = new PropertyAnimator(this); + + async slideIn() { + await this.animator.animate({ + x: { from: -this.width, to: 0 }, + opacity: { from: 0, to: 1 } + }, { + duration: 300, + easing: 'easeOutQuad' + }); + } + + async slideOut() { + await this.animator.animate({ + x: { to: this.parent.width }, + opacity: { to: 0 } + }, { + duration: 300, + easing: 'easeInQuad' + }); + } + + pulse() { + this.animator.animate({ + scale: { from: 1, to: 1.1, to: 1 } + }, { + duration: 200, + repeat: 3 + }); + } +} +``` + +## Easing Functions + +Built-in easing functions: +- Linear: `linear` +- Quad: `easeInQuad`, `easeOutQuad`, `easeInOutQuad` +- Cubic: `easeInCubic`, `easeOutCubic`, `easeInOutCubic` +- Expo: `easeInExpo`, `easeOutExpo`, `easeInOutExpo` +- Bounce: `easeInBounce`, `easeOutBounce`, `easeInOutBounce` +- Elastic: `easeInElastic`, `easeOutElastic`, `easeInOutElastic` +- Back: `easeInBack`, `easeOutBack`, `easeInOutBack` + +```typescript +// Custom easing function +function customEase(t: number): number { + // t is 0 to 1 + return t * t * (3 - 2 * t); // Smooth step +} + +timeline.add({ + target: component, + properties: { x: { to: 100 } }, + easing: customEase +}); +``` + +## Frame-based Animation + +```typescript +class ParticleEffect extends Renderable { + private particles: Particle[] = []; + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + // Update particles + this.particles.forEach(particle => { + particle.x += particle.vx * deltaTime; + particle.y += particle.vy * deltaTime; + particle.vy += 0.1 * deltaTime; // Gravity + particle.life -= deltaTime; + + if (particle.life > 0) { + const opacity = particle.life / particle.maxLife; + buffer.setCell( + Math.round(particle.x), + Math.round(particle.y), + '•', + RGBA.fromValues(1, 1, 1, opacity) + ); + } + }); + + // Remove dead particles + this.particles = this.particles.filter(p => p.life > 0); + + // Keep animating if particles exist + if (this.particles.length > 0) { + this.needsUpdate(); + } + } + + emit(x: number, y: number, count: number) { + for (let i = 0; i < count; i++) { + this.particles.push({ + x, y, + vx: (Math.random() - 0.5) * 10, + vy: Math.random() * -10, + life: 1000, + maxLife: 1000 + }); + } + this.needsUpdate(); + } +} +``` + +## Transition Effects + +```typescript +class TransitionManager { + async fadeTransition(from: Renderable, to: Renderable, duration = 500) { + to.style.opacity = 0; + to.visible = true; + + const timeline = new Timeline({ duration }); + + timeline.add({ + target: from, + properties: { opacity: { to: 0 } }, + duration: duration / 2 + }); + + timeline.add({ + target: to, + properties: { opacity: { to: 1 } }, + duration: duration / 2, + offset: `-=${duration/4}` // Overlap + }); + + await timeline.play(); + from.visible = false; + } + + async slideTransition(from: Renderable, to: Renderable, direction = 'left') { + const parent = from.parent; + const width = parent.computedWidth; + + // Position 'to' off-screen + if (direction === 'left') { + to.x = width; + } else { + to.x = -width; + } + to.visible = true; + + const timeline = new Timeline({ duration: 300 }); + + timeline.add({ + targets: [from, to], + properties: { + x: { + from: (target) => target.x, + to: (target) => { + if (target === from) { + return direction === 'left' ? -width : width; + } else { + return 0; + } + } + } + }, + easing: 'easeInOutQuad' + }); + + await timeline.play(); + from.visible = false; + } +} +``` + +## Sprite Animations + +```typescript +class SpriteAnimation extends Renderable { + private frames: string[] = []; + private currentFrame = 0; + private frameDuration = 100; + private elapsed = 0; + + constructor(id: string, frames: string[]) { + super(id, {}); + this.frames = frames; + } + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + // Update animation + this.elapsed += deltaTime; + if (this.elapsed >= this.frameDuration) { + this.currentFrame = (this.currentFrame + 1) % this.frames.length; + this.elapsed = 0; + } + + // Draw current frame + const frame = this.frames[this.currentFrame]; + const lines = frame.split('\n'); + lines.forEach((line, y) => { + buffer.drawText(line, this.x, this.y + y); + }); + + this.needsUpdate(); // Continue animating + } +} + +// ASCII spinner animation +const spinner = new SpriteAnimation('spinner', [ + '⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏' +]); +``` + +## Performance Best Practices + +### Optimize Animation Updates + +```typescript +class OptimizedAnimation extends Renderable { + private isDirty = false; + private animationFrame = 0; + private targetFPS = 30; + private frameInterval = 1000 / 30; // 33ms per frame + private lastFrame = 0; + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + // Throttle animation to target FPS + this.lastFrame += deltaTime; + if (this.lastFrame < this.frameInterval) { + this.needsUpdate(); + return; + } + + // Only update if changed + if (this.isDirty) { + this.updateAnimation(); + this.isDirty = false; + } + + // Continue if animating + if (this.isAnimating) { + this.needsUpdate(); + } + + this.lastFrame = 0; + } +} +``` + +### Batch Animations + +```typescript +class AnimationBatcher { + private animations: Map = new Map(); + private timeline: Timeline; + + batchAnimate(targets: Renderable[], config: AnimationConfig) { + // Batch multiple targets in single timeline + this.timeline = new Timeline({ duration: config.duration }); + + targets.forEach((target, index) => { + this.timeline.add({ + target, + properties: config.properties, + duration: config.duration, + easing: config.easing, + // Stagger animations + delay: config.stagger ? index * config.stagger : 0 + }); + }); + + return this.timeline.play(); + } +} + +// Usage +const batcher = new AnimationBatcher(); +const boxes = [box1, box2, box3, box4]; + +await batcher.batchAnimate(boxes, { + properties: { + y: { to: 10 }, + opacity: { from: 0, to: 1 } + }, + duration: 500, + stagger: 50, // 50ms delay between each + easing: 'easeOutQuad' +}); +``` + +## Complex Animation Examples + +### Loading Bar Animation + +```typescript +class LoadingBar extends BoxRenderable { + private progress = 0; + private targetProgress = 0; + private animating = false; + + constructor(id: string, options: BoxOptions) { + super(id, { + ...options, + height: 3, + border: true, + borderStyle: 'single' + }); + } + + setProgress(value: number) { + this.targetProgress = Math.max(0, Math.min(100, value)); + if (!this.animating) { + this.animateProgress(); + } + } + + private async animateProgress() { + this.animating = true; + + while (Math.abs(this.progress - this.targetProgress) > 0.1) { + // Smooth interpolation + this.progress += (this.targetProgress - this.progress) * 0.1; + this.needsUpdate(); + await new Promise(resolve => setTimeout(resolve, 16)); + } + + this.progress = this.targetProgress; + this.animating = false; + this.needsUpdate(); + } + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + super.renderSelf(buffer, deltaTime); + + const innerWidth = this.computedWidth - 2; + const filled = Math.floor(innerWidth * (this.progress / 100)); + + // Draw progress bar + for (let x = 0; x < filled; x++) { + buffer.setCell( + this.x + 1 + x, + this.y + 1, + '█', + RGBA.fromHex('#00ff00') + ); + } + + // Draw percentage + const text = `${Math.round(this.progress)}%`; + buffer.drawText( + text, + this.x + Math.floor(innerWidth / 2) - Math.floor(text.length / 2), + this.y + 1, + RGBA.fromHex('#ffffff') + ); + } +} +``` + +### Notification Toast Animation + +```typescript +class Toast extends BoxRenderable { + private timeline: Timeline; + + constructor(message: string, type: 'success' | 'error' | 'info' = 'info') { + const colors = { + success: '#00ff00', + error: '#ff0000', + info: '#00aaff' + }; + + super('toast', { + width: message.length + 4, + height: 3, + border: true, + borderStyle: 'rounded', + borderColor: colors[type], + backgroundColor: '#1e1e1e', + padding: { left: 1, right: 1 }, + position: 'absolute', + right: 2, + top: -5, // Start off-screen + zIndex: 1000 + }); + + const text = new TextRenderable('message', { + text: message, + color: colors[type] + }); + this.add(text, 0); + } + + async show(duration = 3000) { + this.timeline = new Timeline({ duration: duration + 600 }); + + // Slide in + this.timeline.add({ + target: this, + properties: { + top: { from: -5, to: 2 } + }, + duration: 300, + easing: 'easeOutBack' + }); + + // Wait + this.timeline.add({ + target: this, + properties: {}, // No-op + duration: duration + }); + + // Slide out + this.timeline.add({ + target: this, + properties: { + top: { to: -5 }, + opacity: { to: 0 } + }, + duration: 300, + easing: 'easeInBack' + }); + + await this.timeline.play(); + this.destroy(); + } +} + +// Usage +const toast = new Toast('File saved successfully!', 'success'); +renderer.root.add(toast, 999); +await toast.show(); +``` + +### Wave Animation Effect + +```typescript +class WaveText extends Renderable { + private text: string; + private amplitude = 2; + private frequency = 0.5; + private phase = 0; + + constructor(id: string, text: string) { + super(id, { width: text.length, height: 5 }); + this.text = text; + } + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + // Update wave phase + this.phase += deltaTime * 0.003; + + // Draw each character with wave offset + for (let i = 0; i < this.text.length; i++) { + const yOffset = Math.sin(this.phase + i * this.frequency) * this.amplitude; + const y = Math.round(this.y + 2 + yOffset); + + // Color based on height + const hue = (i * 30 + this.phase * 100) % 360; + const color = RGBA.fromHSL(hue, 100, 50); + + buffer.setCell( + this.x + i, + y, + this.text[i], + color + ); + } + + this.needsUpdate(); // Continue animating + } +} +``` + +## Animation Sequencing + +```typescript +class AnimationSequence { + private steps: (() => Promise)[] = []; + + add(step: () => Promise): this { + this.steps.push(step); + return this; + } + + async play() { + for (const step of this.steps) { + await step(); + } + } + + async playParallel() { + await Promise.all(this.steps.map(step => step())); + } +} + +// Complex animation sequence +const sequence = new AnimationSequence(); + +sequence + .add(async () => { + // Fade in title + await titleBox.animate({ opacity: { from: 0, to: 1 } }, 500); + }) + .add(async () => { + // Slide in menu items + const items = [item1, item2, item3]; + for (let i = 0; i < items.length; i++) { + await items[i].animate({ + x: { from: -20, to: 0 }, + opacity: { from: 0, to: 1 } + }, 200); + } + }) + .add(async () => { + // Show content with bounce + await content.animate({ + scale: { from: 0, to: 1.1, to: 1 }, + opacity: { from: 0, to: 1 } + }, { + duration: 400, + easing: 'easeOutBounce' + }); + }); + +await sequence.play(); +``` + +## Troubleshooting + +### Common Issues + +1. **Choppy animations**: Reduce animation complexity or decrease target FPS +2. **Memory leaks**: Always stop/destroy timelines when components unmount +3. **CPU usage**: Use `requestAnimationFrame` pattern or throttle updates +4. **Flickering**: Enable double buffering with `buffered: true` + +### Debug Animation Performance + +```typescript +class AnimationDebugger { + private frameCount = 0; + private lastFPSUpdate = Date.now(); + private currentFPS = 0; + + measureFrame(callback: () => void) { + const start = performance.now(); + callback(); + const duration = performance.now() - start; + + this.frameCount++; + const now = Date.now(); + if (now - this.lastFPSUpdate > 1000) { + this.currentFPS = this.frameCount; + this.frameCount = 0; + this.lastFPSUpdate = now; + + console.log(`FPS: ${this.currentFPS}, Frame time: ${duration.toFixed(2)}ms`); + } + } +} +``` + +## Related Topics + +### Components & Animation +- [Components: Custom](./components.md#custom-components) - Building animated components +- [Components: ASCII Font](./components.md#ascii-font-component) - Animated text effects +- [Getting Started](./getting-started.md) - Animation basics + +### User Interaction +- [Events: Mouse](./events.md#mouse-events) - Trigger animations on interaction +- [Events: Keyboard](./events.md#keyboard-events) - Keyboard-triggered animations +- [Events: Forms](./events.md#form-handling) - Form validation animations + +### Layout Animation +- [Layout: Flexbox](./layout.md#flexbox-properties) - Animating layout properties +- [Layout: Responsive](./layout.md#responsive-techniques) - Smooth responsive transitions +- [Layout: Scrollable](./layout.md#scrollable-content) - Animated scrolling + +### Performance +- [Rendering: Optimization](./rendering.md#performance-monitoring) - Animation performance +- [Rendering: Frame Time](./rendering.md#frame-time-analysis) - Measuring animation FPS +- [Rendering: Techniques](./rendering.md#advanced-rendering-techniques) - Visual effects + +## Animation Patterns + +### Common Animations +- **Loading:** Progress bars, spinners (see examples above) +- **Notifications:** [Toast animations](#notification-toast-animation) +- **Transitions:** Page changes, modal appearances +- **Feedback:** Button presses, hover effects + +### Combining with Events +Create interactive animations: +- [Events: Mouse](./events.md#mouse-events) - Hover and click animations +- [Events: Focus](./events.md#focus-management) - Focus state animations +- [Components: Input](./components.md#input-component) - Input validation animations + +### Performance Optimization +- Use [Rendering: Optimization](./rendering.md#render-optimization-strategies) techniques +- Implement frame throttling (see examples above) +- Cache complex animations with [Rendering: Caching](./rendering.md#caching-complex-renders) + +## Next Steps + +- **Add Interaction:** Trigger animations with [Events](./events.md) +- **Build Components:** Create animated [Components](./components.md) +- **Optimize:** Improve performance with [Rendering](./rendering.md) techniques +- **Layout:** Animate [Layout](./layout.md) changes smoothly diff --git a/packages/core/docs/modules/buffer.md b/packages/core/docs/modules/buffer.md new file mode 100644 index 000000000..ee1baf212 --- /dev/null +++ b/packages/core/docs/modules/buffer.md @@ -0,0 +1,335 @@ +# Buffer Module + +The buffer module provides the high-performance `OptimizedBuffer` class, which serves as the core data structure for terminal rendering operations with native Zig acceleration. + +## Overview + +`OptimizedBuffer` manages terminal display data using typed arrays for optimal performance. It integrates with native Zig code via FFI for accelerated operations while providing JavaScript fallbacks. + +## Core Architecture + +### Buffer Structure + +The buffer uses separate typed arrays for different data types: + +```typescript +{ + char: Uint32Array, // Unicode character codes + fg: Float32Array, // Foreground RGBA (0.0-1.0) + bg: Float32Array, // Background RGBA (0.0-1.0) + attributes: Uint8Array // Text attributes (bold, italic, etc.) +} +``` + +### Creating Buffers + +```typescript +import { OptimizedBuffer } from '@opentui/core' + +// Create a buffer +const buffer = OptimizedBuffer.create(80, 24, { + respectAlpha: true // Enable alpha blending +}) + +// Access dimensions +const width = buffer.getWidth() // 80 +const height = buffer.getHeight() // 24 +``` + +## Cell Operations + +### Basic Cell Manipulation + +```typescript +import { RGBA } from '@opentui/core' + +const fg = RGBA.fromValues(1.0, 1.0, 1.0, 1.0) // White +const bg = RGBA.fromValues(0.0, 0.0, 1.0, 1.0) // Blue + +// Set a cell +buffer.setCell(10, 5, 'A', fg, bg, 0) + +// Get cell data +const cell = buffer.get(10, 5) +// Returns: { char: 65, fg: RGBA, bg: RGBA, attributes: 0 } +``` + +### Alpha Blending + +Advanced alpha blending with perceptual adjustments: + +```typescript +const semitransparent = RGBA.fromValues(1.0, 0.0, 0.0, 0.5) + +// Automatically blends with existing content +buffer.setCellWithAlphaBlending( + 10, 5, + 'B', + fg, + semitransparent, + 0 +) +``` + +Alpha blending features: +- Perceptual alpha curve for natural transparency +- Character preservation when overlaying spaces +- Automatic foreground/background blending + +## Text Drawing + +### Basic Text + +```typescript +const text = "Hello, World!" +const x = 10, y = 5 +const fg = RGBA.fromValues(1.0, 1.0, 1.0, 1.0) +const bg = RGBA.fromValues(0.0, 0.0, 0.0, 1.0) + +buffer.drawText(text, x, y, fg, bg, 0) +``` + +### Text with Selection + +Support for text selection highlighting: + +```typescript +buffer.drawText( + "Select this text", + 10, 5, + fg, bg, 0, + { + start: 7, + end: 11, + bgColor: RGBA.fromValues(0.0, 0.0, 1.0, 1.0), + fgColor: RGBA.fromValues(1.0, 1.0, 1.0, 1.0) + } +) +``` + +## Box Drawing + +### Basic Box + +```typescript +buffer.drawBox( + 5, 2, // x, y + 20, 10, // width, height + fg, bg, + true, // border + true, // fill + 'single' // border style +) +``` + +### Advanced Box Options + +```typescript +// Partial borders +buffer.drawBox( + 5, 2, 20, 10, + fg, bg, + ['top', 'bottom'], // Only top and bottom borders + true, + 'double' +) + +// With title +buffer.drawBoxWithTitle( + 5, 2, 20, 10, + fg, bg, + true, true, + 'rounded', + 'My Box', // title + 'center' // alignment: 'left', 'center', 'right' +) +``` + +### Border Styles + +Available border styles: +- `'single'` - `┌─┐│└┘` +- `'double'` - `╔═╗║╚╝` +- `'rounded'` - `╭─╮│╰╯` +- `'heavy'` - `┏━┓┃┗┛` + +## Buffer Operations + +### Clearing + +```typescript +// Clear entire buffer +buffer.clear( + RGBA.fromValues(0.0, 0.0, 0.0, 1.0), // background + ' ' // clear character +) + +// Clear region +buffer.clearRect(10, 5, 20, 10, bg) +``` + +### Blitting + +Copy regions between buffers: + +```typescript +const source = OptimizedBuffer.create(20, 10) +const dest = OptimizedBuffer.create(80, 24) + +// Basic blit +dest.blit(source, 10, 5) + +// Blit with region +dest.blitRegion( + source, + 0, 0, 10, 5, // source region + 20, 10 // destination position +) + +// Blit with alpha blending +dest.blitWithAlpha(source, 10, 5) +``` + +### Merging + +Merge buffers with transparency: + +```typescript +// Merge overlay onto base +base.merge(overlay, 10, 5) + +// Partial merge +base.mergeRegion( + overlay, + 5, 5, 15, 10, // overlay region + 20, 10 // destination +) +``` + +## Performance Features + +### Native FFI Acceleration + +Most operations have optimized Zig implementations: + +```typescript +// Toggle between FFI and JavaScript implementations +buffer.useFFI = false // Use JavaScript fallback +buffer.clearLocal(bg, ' ') // Explicit local implementation + +buffer.useFFI = true // Use native Zig (default) +buffer.clearFFI(bg) // Explicit FFI implementation +``` + +### Direct Buffer Access + +For custom processing: + +```typescript +const buffers = buffer.buffers + +// Direct manipulation +for (let i = 0; i < buffers.char.length; i++) { + if (buffers.char[i] === 32) { // space + buffers.fg[i * 4] = 0.5 // dim foreground + } +} +``` + +### Memory Management + +Buffers are reference-counted in native code: + +```typescript +// Buffers are automatically managed +const buffer = OptimizedBuffer.create(80, 24) +// Native memory allocated + +// When buffer goes out of scope, +// garbage collection handles cleanup +``` + +## Utility Functions + +### Color Blending + +Internal color blending with perceptual adjustments: + +```typescript +// Perceptual alpha curve: +// - Values > 0.8: Subtle curve for near-opaque +// - Values ≤ 0.8: Power curve for smooth blending +``` + +### Drawing Options Packing + +Border configuration is packed into bitfields for efficiency: + +```typescript +// Internally packed as: +// bits 0-3: border sides (top/right/bottom/left) +// bit 4: fill flag +// bits 5-6: title alignment (0=left, 1=center, 2=right) +``` + +## Integration + +### With Renderables + +```typescript +class CustomRenderable extends Renderable { + render(buffer: OptimizedBuffer) { + buffer.drawText( + this.content, + this.x, this.y, + this.fg, this.bg + ) + } +} +``` + +### With TextBuffer + +```typescript +// Convert TextBuffer to OptimizedBuffer +const textBuffer = new TextBuffer(80, 24) +const optimized = textBuffer.toOptimizedBuffer() +``` + +## API Reference + +### Constructor & Factory + +- `OptimizedBuffer.create(width: number, height: number, options?: { respectAlpha?: boolean })` + +### Properties + +- `buffers` - Direct access to typed arrays +- `respectAlpha` - Alpha blending mode +- `id` - Unique buffer identifier + +### Methods + +- `getWidth(): number` +- `getHeight(): number` +- `setRespectAlpha(respectAlpha: boolean): void` +- `clear(bg?: RGBA, clearChar?: string): void` +- `clearRect(x: number, y: number, width: number, height: number, bg: RGBA): void` +- `setCell(x: number, y: number, char: string, fg: RGBA, bg: RGBA, attributes?: number): void` +- `setCellWithAlphaBlending(...): void` +- `get(x: number, y: number): CellData | null` +- `drawText(...): void` +- `drawBox(...): void` +- `drawBoxWithTitle(...): void` +- `blit(source: OptimizedBuffer, x: number, y: number): void` +- `blitRegion(...): void` +- `blitWithAlpha(...): void` +- `merge(source: OptimizedBuffer, x: number, y: number): void` +- `mergeRegion(...): void` + +## Related Modules + +- [Text Buffer](./text-buffer.md) - Higher-level text operations +- [Rendering](./rendering.md) - Integration with renderer +- [Zig](./zig.md) - Native acceleration layer +- [Filters](./filters.md) - Post-processing effects \ No newline at end of file diff --git a/packages/core/docs/modules/components.md b/packages/core/docs/modules/components.md new file mode 100644 index 000000000..29667c463 --- /dev/null +++ b/packages/core/docs/modules/components.md @@ -0,0 +1,252 @@ +# Components + +> **Quick Navigation:** [Getting Started](./getting-started.md) | [Layout](./layout.md) | [Events](./events.md) | [Animation](./animation.md) | [Rendering](./rendering.md) + +OpenTUI provides several built-in components for building terminal UIs. + +## Box Component + +The most fundamental container component, similar to `div` in HTML. + +```typescript +import { BoxRenderable } from '@opentui/core'; + +const box = new BoxRenderable('my-box', { + // Sizing + width: 50, + height: 20, + + // Border + border: true, + borderStyle: 'double', // 'single' | 'double' | 'rounded' | 'heavy' + borderColor: '#00ff00', + + // Background + backgroundColor: '#1e1e1e', + + // Layout + padding: 2, + margin: 1, + + // Flexbox + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'space-between' +}); + +// Custom border characters +const customBox = new BoxRenderable('custom', { + border: true, + customBorderChars: { + topLeft: '╭', + topRight: '╮', + bottomLeft: '╰', + bottomRight: '╯', + horizontal: '─', + vertical: '│' + } +}); +``` + +## Text Component + +Displays text with optional styling and word wrapping. + +```typescript +import { TextRenderable, RGBA } from '@opentui/core'; + +const text = new TextRenderable('my-text', { + text: 'Hello World', + color: RGBA.fromHex('#ffffff'), + backgroundColor: RGBA.fromHex('#0000ff'), + + // Text alignment + align: 'center', // 'left' | 'center' | 'right' + + // Word wrap + wrap: true, + + // Selection + selectable: true, + selectionBg: '#ffff00', + selectionFg: '#000000' +}); + +// Styled text with markup +const styledText = new TextRenderable('styled', { + text: '{red}Error:{/} {bold}File not found{/}', + parseMarkup: true +}); +``` + +## Input Component + +Single-line text input field. + +```typescript +import { InputRenderable } from '@opentui/core'; + +const input = new InputRenderable('username', { + placeholder: 'Enter username...', + value: '', + + // Styling + focusedBorderColor: '#00ff00', + cursorStyle: 'block', // 'block' | 'line' | 'underline' + + // Validation + maxLength: 20, + pattern: /^[a-zA-Z0-9]+$/, + + // Events + onChange: (value) => { + console.log('Input changed:', value); + }, + onSubmit: (value) => { + console.log('Submitted:', value); + } +}); + +// Password input +const password = new InputRenderable('password', { + type: 'password', + mask: '*' +}); +``` + +## Select Component + +Dropdown selection list. + +```typescript +import { SelectRenderable } from '@opentui/core'; + +const select = new SelectRenderable('color-select', { + options: [ + { name: 'Red', value: '#ff0000' }, + { name: 'Green', value: '#00ff00' }, + { name: 'Blue', value: '#0000ff' } + ], + + selected: 0, + visibleOptions: 5, // Max visible at once + + // Styling + selectedBg: '#333333', + selectedFg: '#ffffff', + + // Events + onChange: (option, index) => { + console.log('Selected:', option.name); + } +}); +``` + +## ASCII Font Component + +Display text using ASCII art fonts. + +```typescript +import { ASCIIFontRenderable, fonts } from '@opentui/core'; + +const title = new ASCIIFontRenderable('title', { + text: 'OpenTUI', + font: fonts.block, // Built-in fonts: tiny, block, shade, slick + + // Colors (supports gradients) + fg: [ + RGBA.fromHex('#ff0000'), + RGBA.fromHex('#00ff00'), + RGBA.fromHex('#0000ff') + ] +}); + +// Custom font +import customFont from './my-font.json'; + +const custom = new ASCIIFontRenderable('custom', { + text: 'Custom', + font: customFont as FontDefinition +}); +``` + +## Creating Custom Components + +Extend the Renderable class to create custom components: + +```typescript +import { Renderable, OptimizedBuffer, RGBA } from '@opentui/core'; + +class ProgressBar extends Renderable { + private progress: number = 0; + + setProgress(value: number) { + this.progress = Math.max(0, Math.min(1, value)); + this.needsUpdate(); + } + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + const width = this.computedWidth; + const filled = Math.floor(width * this.progress); + + // Draw filled portion + for (let x = 0; x < filled; x++) { + buffer.setCell( + this.x + x, + this.y, + '█', + RGBA.fromHex('#00ff00'), + RGBA.fromHex('#000000') + ); + } + + // Draw empty portion + for (let x = filled; x < width; x++) { + buffer.setCell( + this.x + x, + this.y, + '░', + RGBA.fromHex('#333333'), + RGBA.fromHex('#000000') + ); + } + } +} +``` + +## Related Topics + +### Layout & Positioning +- [Layout System](./layout.md) - Learn about flexbox properties used by components +- [Getting Started](./getting-started.md#core-concepts) - Understanding component hierarchy + +### Interactivity +- [Event Handling](./events.md) - Add keyboard and mouse interaction to components +- [Event Handling: Forms](./events.md#form-handling) - Building interactive forms with inputs + +### Visual Effects +- [Animation](./animation.md) - Animate component properties +- [Animation: Transitions](./animation.md#transition-effects) - Page and component transitions +- [Rendering: Gradients](./rendering.md#gradient-rendering) - Advanced visual effects + +### Performance +- [Rendering: Optimization](./rendering.md#performance-tips) - Component rendering best practices +- [Rendering: Virtual Scrolling](./rendering.md#virtual-scrolling) - Efficient list rendering + +## Component Patterns + +### Container Components +Components like `BoxRenderable` are containers that use [flexbox layout](./layout.md#flexbox-properties) to arrange children. See the [Layout Guide](./layout.md) for positioning strategies. + +### Interactive Components +`InputRenderable` and other interactive components rely on the [event system](./events.md). Learn about [focus management](./events.md#focus-management) and [keyboard handling](./events.md#keyboard-events). + +### Custom Components +Build your own components by extending `Renderable`. See [Rendering](./rendering.md#custom-cell-rendering) for custom rendering techniques and [Animation](./animation.md#property-animations) for adding motion. + +## Next Steps + +- **Layout:** Learn [flexbox layout](./layout.md) to arrange components +- **Events:** Add [interactivity](./events.md) with keyboard and mouse handling +- **Animation:** Create [smooth transitions](./animation.md) between states +- **Optimization:** Improve performance with [rendering techniques](./rendering.md) diff --git a/packages/core/docs/modules/console.md b/packages/core/docs/modules/console.md new file mode 100644 index 000000000..1d2d8a58b --- /dev/null +++ b/packages/core/docs/modules/console.md @@ -0,0 +1,374 @@ +# Console Module + +The console module provides a terminal-based console window for debugging and logging, with output capture, filtering, and visual inspection capabilities. + +## Overview + +The console module intercepts standard console output and displays it in a customizable terminal panel. It includes caller information tracking, log level filtering, and automatic scrolling. + +## Core Components + +### Console Capture + +Automatic capture of console output: + +```typescript +import { capture } from '@opentui/core/console' + +// Console output is automatically captured +console.log('This is captured') +console.error('Errors too') + +// Access captured output +const stdout = capture.getStdout() +const stderr = capture.getStderr() + +// Clear capture buffers +capture.clear() +``` + +### Terminal Console + +Visual console window in the terminal: + +```typescript +import { TerminalConsole, ConsolePosition } from '@opentui/core' + +const terminalConsole = new TerminalConsole(renderer, { + position: ConsolePosition.BOTTOM, + sizePercent: 30, + zIndex: Infinity, + backgroundColor: '#1a1a1a', + startInDebugMode: false +}) + +// Show/hide console +terminalConsole.show() +terminalConsole.hide() +terminalConsole.toggle() +``` + +## Configuration + +### Console Options + +```typescript +interface ConsoleOptions { + position?: ConsolePosition // TOP, BOTTOM, LEFT, RIGHT + sizePercent?: number // Size as percentage of screen (default: 30) + zIndex?: number // Layer order (default: Infinity) + + // Colors for log levels + colorInfo?: ColorInput // Cyan default + colorWarn?: ColorInput // Yellow default + colorError?: ColorInput // Red default + colorDebug?: ColorInput // Gray default + colorDefault?: ColorInput // White default + + backgroundColor?: ColorInput // Panel background + titleBarColor?: ColorInput // Title bar color + titleBarTextColor?: ColorInput // Title text color + cursorColor?: ColorInput // Cursor/selection color + + title?: string // Console title (default: "Console") + startInDebugMode?: boolean // Enable debug info on start + maxStoredLogs?: number // Max logs to keep (default: 2000) + maxDisplayLines?: number // Max lines to display (default: 3000) +} +``` + +### Default Configuration + +```typescript +const DEFAULT_CONSOLE_OPTIONS = { + position: ConsolePosition.BOTTOM, + sizePercent: 30, + zIndex: Infinity, + colorInfo: "#00FFFF", // Cyan + colorWarn: "#FFFF00", // Yellow + colorError: "#FF0000", // Red + colorDebug: "#808080", // Gray + colorDefault: "#FFFFFF", // White + backgroundColor: RGBA.fromValues(0.1, 0.1, 0.1, 0.7), + title: "Console", + titleBarColor: RGBA.fromValues(0.05, 0.05, 0.05, 0.7), + titleBarTextColor: "#FFFFFF", + cursorColor: "#00A0FF", + maxStoredLogs: 2000, + maxDisplayLines: 3000 +} +``` + +## Console Cache + +### Log Entry Management + +The console cache stores all console output: + +```typescript +// Internal cache management +class TerminalConsoleCache extends EventEmitter { + private _cachedLogs: [Date, LogLevel, any[], CallerInfo | null][] + private readonly MAX_CACHE_SIZE = 1000 + + // Enable/disable caching + setCachingEnabled(enabled: boolean): void + + // Clear all cached logs + clearConsole(): void + + // Enable caller info collection (debug mode) + setCollectCallerInfo(enabled: boolean): void +} +``` + +### Caller Information + +Debug mode captures detailed caller information: + +```typescript +interface CallerInfo { + functionName: string // Function that called console + fullPath: string // Full file path + fileName: string // Just the filename + lineNumber: number // Line number in file + columnNumber: number // Column number +} + +// Enable debug mode to collect caller info +terminalConsole.setDebugMode(true) +``` + +## Display Management + +### Scrolling + +Console supports smooth scrolling: + +```typescript +// Scroll controls +terminalConsole.scrollUp(lines?: number) // Default: 1 line +terminalConsole.scrollDown(lines?: number) +terminalConsole.scrollToTop() +terminalConsole.scrollToBottom() +terminalConsole.pageUp() // Scroll by visible height +terminalConsole.pageDown() + +// Auto-scroll to bottom on new logs +terminalConsole.setAutoScroll(true) +``` + +### Filtering + +Filter logs by level: + +```typescript +// Show only specific log levels +terminalConsole.setLogLevelFilter([ + LogLevel.ERROR, + LogLevel.WARN +]) + +// Or use convenience methods +terminalConsole.showOnlyErrors() +terminalConsole.showAll() +``` + +### Search + +Search through console output: + +```typescript +// Search for text +terminalConsole.search('error') +terminalConsole.searchNext() +terminalConsole.searchPrevious() +terminalConsole.clearSearch() +``` + +## Keyboard Shortcuts + +Built-in keyboard navigation: + +``` +Escape - Close console +Tab - Toggle focus +D - Toggle debug mode +C - Clear console +↑/↓ - Scroll up/down +Page Up/Dn - Page scroll +Home/End - Jump to top/bottom +/ - Start search +n/N - Next/previous search result +``` + +## Rendering + +### Frame Buffer + +Console uses optimized rendering: + +```typescript +// Console maintains its own frame buffer +private frameBuffer: OptimizedBuffer | null +private _needsFrameBufferUpdate: boolean + +// Mark for re-render +private markNeedsUpdate(): void { + this._needsFrameBufferUpdate = true + this.renderer.needsUpdate() +} +``` + +### Display Lines + +Log entries are formatted for display: + +```typescript +interface DisplayLine { + text: string // Formatted text + level: LogLevel // Log level for coloring + indent: boolean // Whether to indent (for multi-line) +} +``` + +## Output Capture + +### Captured Streams + +Intercept stdout/stderr: + +```typescript +// Automatic stream capture +const mockStdout = new CapturedWritableStream("stdout", capture) +const mockStderr = new CapturedWritableStream("stderr", capture) + +// Global console replacement +global.console = new console.Console({ + stdout: mockStdout, + stderr: mockStderr, + colorMode: true, + inspectOptions: { + compact: false, + breakLength: 80, + depth: 2 + } +}) +``` + +### Environment Variables + +Control console behavior: + +```bash +# Skip console capture +SKIP_CONSOLE_CACHE=true + +# Auto-show console on start +SHOW_CONSOLE=true +``` + +## Integration + +### With Renderer + +```typescript +class MyApp { + private renderer: CliRenderer + private console: TerminalConsole + + constructor() { + this.renderer = new CliRenderer() + this.console = new TerminalConsole(this.renderer, { + position: ConsolePosition.BOTTOM, + sizePercent: 25 + }) + + // Console renders automatically when visible + this.console.show() + } + + handleError(error: Error) { + console.error('Application error:', error) + this.console.show() // Show console on error + } +} +``` + +### With Logging + +```typescript +// Custom log formatting +terminalConsole.on('entry', (entry) => { + const [date, level, args, caller] = entry + + // Save to file + fs.appendFileSync('app.log', + `${date.toISOString()} [${level}] ${args.join(' ')}\n` + ) +}) +``` + +## Advanced Features + +### Debug Mode + +Enhanced debugging information: + +```typescript +// Toggle debug mode +terminalConsole.toggleDebugMode() + +// When enabled, shows: +// - Function names +// - File paths +// - Line/column numbers +// - Stack traces for errors +``` + +### Export Logs + +Export console content: + +```typescript +// Get all logs +const logs = terminalConsole.exportLogs() + +// Get filtered logs +const errors = terminalConsole.exportLogs({ + levels: [LogLevel.ERROR], + limit: 100 +}) + +// Save to file +terminalConsole.saveLogsToFile('debug.log') +``` + +## API Reference + +### Classes + +- `TerminalConsole` - Main console window class +- `TerminalConsoleCache` - Console output cache +- `Capture` - Output capture manager +- `CapturedWritableStream` - Stream interceptor + +### Enums + +- `LogLevel` - LOG, INFO, WARN, ERROR, DEBUG +- `ConsolePosition` - TOP, BOTTOM, LEFT, RIGHT + +### Functions + +- `getCallerInfo(): CallerInfo | null` - Extract caller information + +### Exports + +- `capture` - Global capture instance +- `terminalConsoleCache` - Global cache instance + +## Related Modules + +- [Rendering](./rendering.md) - Console rendering integration +- [Buffer](./buffer.md) - Frame buffer management +- [Lib](./lib.md) - Color parsing and utilities \ No newline at end of file diff --git a/packages/core/docs/modules/events.md b/packages/core/docs/modules/events.md new file mode 100644 index 000000000..db9cbed6b --- /dev/null +++ b/packages/core/docs/modules/events.md @@ -0,0 +1,332 @@ +# Event Handling + +OpenTUI provides comprehensive keyboard and mouse event handling. + +## Keyboard Events + +```typescript +import { Renderable, ParsedKey } from '@opentui/core'; + +class MyComponent extends Renderable { + constructor(id: string) { + super(id, { + onKeyDown: (key: ParsedKey) => { + // Handle key press + if (key.name === 'escape') { + this.handleEscape(); + return true; // Prevent bubbling + } + + // Arrow keys + if (key.name === 'up') this.moveUp(); + if (key.name === 'down') this.moveDown(); + if (key.name === 'left') this.moveLeft(); + if (key.name === 'right') this.moveRight(); + + // Modifiers + if (key.ctrl && key.name === 'c') { + this.copy(); + } + + // Regular characters + if (key.name.length === 1) { + this.handleCharacter(key.name); + } + + return false; // Allow bubbling + } + }); + } +} +``` + +### ParsedKey Structure + +```typescript +interface ParsedKey { + name: string; // Key name ('a', 'enter', 'escape', etc.) + ctrl: boolean; // Ctrl key held + meta: boolean; // Meta/Cmd key held + shift: boolean; // Shift key held + option: boolean; // Alt/Option key held + sequence: string; // Raw escape sequence + raw: string; // Raw input + code?: string; // Key code if available +} +``` + +### Common Key Names +- Letters: 'a' through 'z' +- Numbers: '0' through '9' +- Special: 'enter', 'escape', 'backspace', 'delete', 'tab', 'space' +- Navigation: 'up', 'down', 'left', 'right', 'home', 'end', 'pageup', 'pagedown' +- Function: 'f1' through 'f12' + +## Mouse Events + +```typescript +class ClickableComponent extends Renderable { + private isHovered = false; + private isDragging = false; + private dragStart = { x: 0, y: 0 }; + + constructor(id: string) { + super(id, { + onMouseDown: (event: MouseEvent) => { + if (event.button === 0) { // Left click + this.isDragging = true; + this.dragStart = { x: event.x, y: event.y }; + event.preventDefault(); + } else if (event.button === 2) { // Right click + this.showContextMenu(event.x, event.y); + } + }, + + onMouseUp: (event: MouseEvent) => { + if (this.isDragging) { + this.isDragging = false; + this.handleDrop(event.x, event.y); + } + }, + + onMouseDrag: (event: MouseEvent) => { + if (this.isDragging) { + const dx = event.x - this.dragStart.x; + const dy = event.y - this.dragStart.y; + this.handleDrag(dx, dy); + } + }, + + onMouseOver: (event: MouseEvent) => { + this.isHovered = true; + this.updateHoverState(); + }, + + onMouseOut: (event: MouseEvent) => { + this.isHovered = false; + this.updateHoverState(); + }, + + onMouseScroll: (event: MouseEvent) => { + if (event.scroll.direction === 'up') { + this.scrollUp(event.scroll.delta); + } else { + this.scrollDown(event.scroll.delta); + } + } + }); + } +} +``` + +### MouseEvent Structure + +```typescript +interface MouseEvent { + type: MouseEventType; // Event type + button: number; // 0=left, 1=middle, 2=right + x: number; // X coordinate in terminal + y: number; // Y coordinate in terminal + modifiers: { + shift: boolean; + alt: boolean; + ctrl: boolean; + }; + scroll?: { + direction: 'up' | 'down' | 'left' | 'right'; + delta: number; + }; + target: Renderable | null; // Target component + source: Renderable; // Source component + + preventDefault(): void; // Stop default behavior +} +``` + +## Focus Management + +```typescript +class FocusableList extends Renderable { + private focusedIndex = 0; + private items: Renderable[] = []; + + constructor(id: string) { + super(id, { + onKeyDown: (key) => { + if (key.name === 'tab') { + if (key.shift) { + this.focusPrevious(); + } else { + this.focusNext(); + } + return true; // Prevent default tab behavior + } + } + }); + } + + focusNext() { + this.items[this.focusedIndex]?.blur(); + this.focusedIndex = (this.focusedIndex + 1) % this.items.length; + this.items[this.focusedIndex]?.focus(); + } + + focusPrevious() { + this.items[this.focusedIndex]?.blur(); + this.focusedIndex = (this.focusedIndex - 1 + this.items.length) % this.items.length; + this.items[this.focusedIndex]?.focus(); + } +} +``` + +## Event Delegation + +```typescript +class EventDelegate extends Renderable { + private handlers = new Map(); + + constructor(id: string) { + super(id, { + onMouseDown: (event) => { + // Delegate to child components based on data attributes + const target = event.target; + if (target && target.data?.action) { + const handler = this.handlers.get(target.data.action); + if (handler) { + handler(event); + } + } + } + }); + } + + registerHandler(action: string, handler: Function) { + this.handlers.set(action, handler); + } + + createButton(text: string, action: string) { + const button = new BoxRenderable(`btn-${action}`, { + padding: 1, + border: true, + data: { action } // Custom data for delegation + }); + button.add(new TextRenderable('label', { text })); + return button; + } +} +``` + +## Global Shortcuts + +```typescript +class Application extends Renderable { + private shortcuts = new Map(); + + constructor() { + super('app', { + onKeyDown: (key) => { + const shortcut = this.getShortcutKey(key); + const handler = this.shortcuts.get(shortcut); + + if (handler) { + handler(); + return true; // Handled + } + + return false; // Pass through + } + }); + + // Register shortcuts + this.registerShortcut('ctrl+s', () => this.save()); + this.registerShortcut('ctrl+o', () => this.open()); + this.registerShortcut('ctrl+q', () => this.quit()); + } + + private getShortcutKey(key: ParsedKey): string { + const parts = []; + if (key.ctrl) parts.push('ctrl'); + if (key.shift) parts.push('shift'); + if (key.alt) parts.push('alt'); + parts.push(key.name); + return parts.join('+'); + } + + registerShortcut(keys: string, handler: Function) { + this.shortcuts.set(keys, handler); + } +} +``` + +## Related Topics + +### Interactive Components +- [Components: Input](./components.md#input-component) - Text input with keyboard handling +- [Components: Custom](./components.md#custom-components) - Building interactive components +- [Getting Started](./getting-started.md#core-concepts) - Event system overview + +### Visual Feedback +- [Animation: Properties](./animation.md#property-animations) - Animate on user interaction +- [Animation: Transitions](./animation.md#transition-effects) - Smooth state changes +- [Rendering: Effects](./rendering.md#advanced-rendering-techniques) - Visual feedback effects + +### Layout Integration +- [Layout: Responsive](./layout.md#layout-patterns) - Handling resize events +- [Layout: Scrollable](./layout.md#scrollable-content) - Scroll event handling +- [Components: Box](./components.md#box-component) - Container event bubbling + +## Event Patterns + +### Form Handling +Build interactive forms using: +- [Components: Input](./components.md#input-component) - Text input fields +- [Layout: Forms](./layout.md#layout-patterns) - Form layout patterns +- [Animation: Validation](./animation.md#complex-animation-examples) - Animated validation feedback + +### Drag and Drop +Implement drag operations with: +- Mouse event tracking (see examples above) +- [Animation: Movement](./animation.md#property-animations) - Smooth dragging +- [Rendering: Layers](./rendering.md#advanced-rendering-techniques) - Z-index management + +### Keyboard Navigation +Create accessible interfaces: +- Tab order management +- Focus indicators with [Rendering: Effects](./rendering.md#shadow-effects) +- Keyboard shortcuts (see examples above) + +## Best Practices + +1. **Event Delegation:** Use bubbling for parent containers to handle child events +2. **Debouncing:** Throttle rapid events like scroll or mouse move +3. **Focus Management:** Maintain logical tab order for accessibility +4. **Visual Feedback:** Always provide feedback for user actions +5. **Prevent Default:** Stop propagation when handling events + +## Troubleshooting + +### Common Issues +- **Events not firing:** Check focus state and event bubbling +- **Multiple handlers:** Use `stopPropagation()` to prevent bubbling +- **Performance:** Debounce rapid events like mouse move +- **Focus issues:** Ensure components are focusable + +### Debug Events +```typescript +class EventDebugger extends Renderable { + constructor(id: string) { + super(id, { + onMouseDown: (e) => console.log('MouseDown:', e), + onKeyDown: (k) => console.log('KeyDown:', k), + // Log all events + }); + } +} +``` + +## Next Steps + +- **Build Forms:** Create interactive forms with [Components](./components.md) +- **Add Motion:** Respond to events with [Animations](./animation.md) +- **Optimize:** Handle events efficiently with [Rendering](./rendering.md) techniques +- **Layout:** Create responsive layouts that handle [resize events](./layout.md) diff --git a/packages/core/docs/modules/filters.md b/packages/core/docs/modules/filters.md new file mode 100644 index 000000000..8a38c4266 --- /dev/null +++ b/packages/core/docs/modules/filters.md @@ -0,0 +1,293 @@ +# Post-Processing Filters Module + +The filters module provides real-time post-processing effects for terminal rendering, operating directly on OptimizedBuffer data structures for performance. + +## Overview + +Post-processing filters manipulate the rendered buffer's foreground/background colors and characters to create visual effects. All filters work with normalized color values (0.0-1.0) in Float32Array buffers. + +## Static Filter Functions + +### Scanlines + +Apply retro CRT-style scanline effect: + +```typescript +import { applyScanlines } from '@opentui/core/post/filters' + +// Darken every 2nd row to 80% brightness +applyScanlines(buffer, 0.8, 2) + +// Stronger effect with wider gaps +applyScanlines(buffer, 0.5, 3) +``` + +### Grayscale + +Convert colors to grayscale using luminance calculation: + +```typescript +import { applyGrayscale } from '@opentui/core/post/filters' + +// Convert entire buffer to grayscale +applyGrayscale(buffer) +// Uses formula: 0.299*R + 0.587*G + 0.114*B +``` + +### Sepia + +Apply vintage sepia tone effect: + +```typescript +import { applySepia } from '@opentui/core/post/filters' + +// Apply sepia tone transformation +applySepia(buffer) +// Uses standard sepia matrix transformation +``` + +### Invert + +Invert all colors: + +```typescript +import { applyInvert } from '@opentui/core/post/filters' + +// Invert fg and bg colors +applyInvert(buffer) +// Each channel becomes 1.0 - original +``` + +### Noise + +Add random noise for texture: + +```typescript +import { applyNoise } from '@opentui/core/post/filters' + +// Add subtle noise +applyNoise(buffer, 0.1) + +// Heavy static effect +applyNoise(buffer, 0.3) +``` + +### Chromatic Aberration + +Simulate lens distortion with color channel separation: + +```typescript +import { applyChromaticAberration } from '@opentui/core/post/filters' + +// Subtle aberration +applyChromaticAberration(buffer, 1) + +// Strong RGB separation +applyChromaticAberration(buffer, 3) +``` + +### ASCII Art + +Convert buffer to ASCII art based on brightness: + +```typescript +import { applyAsciiArt } from '@opentui/core/post/filters' + +// Default ramp: " .:-=+*#%@" +applyAsciiArt(buffer) + +// Custom character ramp +applyAsciiArt(buffer, " ░▒▓█") +``` + +## Effect Classes + +### DistortionEffect + +Animated glitch/distortion effect with configurable parameters: + +```typescript +import { DistortionEffect } from '@opentui/core/post/filters' + +const distortion = new DistortionEffect({ + glitchChancePerSecond: 0.5, + maxGlitchLines: 3, + minGlitchDuration: 0.05, + maxGlitchDuration: 0.2, + maxShiftAmount: 10, + shiftFlipRatio: 0.6, + colorGlitchChance: 0.2 +}) + +// Apply with delta time for animation +distortion.apply(buffer, deltaTime) +``` + +Glitch types: +- **shift**: Horizontal pixel shifting with wrap-around +- **flip**: Horizontal line mirroring +- **color**: Random color corruption + +### VignetteEffect + +Darken corners for cinematic framing: + +```typescript +import { VignetteEffect } from '@opentui/core/post/filters' + +const vignette = new VignetteEffect(0.5) + +// Apply vignette +vignette.apply(buffer) + +// Adjust strength dynamically +vignette.strength = 0.8 +``` + +Features: +- Precomputed distance attenuation for performance +- Automatic recalculation on buffer resize +- Non-negative strength clamping + +### BrightnessEffect + +Adjust overall brightness: + +```typescript +import { BrightnessEffect } from '@opentui/core/post/filters' + +const brightness = new BrightnessEffect(1.0) + +// Darken to 50% +brightness.brightness = 0.5 +brightness.apply(buffer) + +// Brighten by 20% +brightness.brightness = 1.2 +brightness.apply(buffer) +``` + +### BlurEffect + +Optimized separable box blur with character modification: + +```typescript +import { BlurEffect } from '@opentui/core/post/filters' + +const blur = new BlurEffect(2) + +// Apply blur +blur.apply(buffer) + +// Adjust radius +blur.radius = 3 +``` + +Features: +- Sliding window optimization for O(n) complexity +- Separate horizontal/vertical passes +- Character ramp based on alpha: `[" ", "░", "▒", "▓", " "]` + +### BloomEffect + +Light bloom based on brightness threshold: + +```typescript +import { BloomEffect } from '@opentui/core/post/filters' + +const bloom = new BloomEffect( + 0.8, // threshold (0-1) + 0.2, // strength + 2 // radius in pixels +) + +// Apply bloom to bright areas +bloom.apply(buffer) + +// Adjust parameters +bloom.threshold = 0.7 +bloom.strength = 0.3 +bloom.radius = 3 +``` + +Features: +- Luminance-based bright pixel detection +- Linear distance falloff +- Additive blending with clamping + +## Performance Considerations + +### Direct Buffer Manipulation + +All filters operate directly on OptimizedBuffer's typed arrays: +- `buffer.buffers.fg` - Float32Array for foreground colors +- `buffer.buffers.bg` - Float32Array for background colors +- `buffer.buffers.char` - Uint32Array for characters +- `buffer.buffers.attributes` - Uint8Array for text attributes + +### Optimization Techniques + +1. **Precomputation**: VignetteEffect caches distance calculations +2. **Separable Filters**: BlurEffect uses two 1D passes instead of 2D +3. **Sliding Window**: Blur uses moving average for O(n) complexity +4. **In-place Operations**: Most filters modify buffers directly +5. **Early Exit**: Effects skip processing when parameters indicate no change + +## Integration Example + +```typescript +import { CliRenderer } from '@opentui/core' +import { + applyGrayscale, + VignetteEffect, + DistortionEffect +} from '@opentui/core/post/filters' + +const renderer = new CliRenderer() +const vignette = new VignetteEffect(0.5) +const distortion = new DistortionEffect() + +let lastTime = Date.now() + +function render() { + const now = Date.now() + const deltaTime = (now - lastTime) / 1000 + lastTime = now + + const buffer = renderer.getBuffer() + + // Apply effects in sequence + applyGrayscale(buffer) + vignette.apply(buffer) + distortion.apply(buffer, deltaTime) + + renderer.render() + requestAnimationFrame(render) +} +``` + +## API Reference + +### Functions + +- `applyScanlines(buffer: OptimizedBuffer, strength?: number, step?: number): void` +- `applyGrayscale(buffer: OptimizedBuffer): void` +- `applySepia(buffer: OptimizedBuffer): void` +- `applyInvert(buffer: OptimizedBuffer): void` +- `applyNoise(buffer: OptimizedBuffer, strength?: number): void` +- `applyChromaticAberration(buffer: OptimizedBuffer, strength?: number): void` +- `applyAsciiArt(buffer: OptimizedBuffer, ramp?: string): void` + +### Classes + +- `DistortionEffect` - Animated glitch effects +- `VignetteEffect` - Corner darkening +- `BrightnessEffect` - Brightness adjustment +- `BlurEffect` - Box blur with character modification +- `BloomEffect` - Light bloom for bright areas + +## Related Modules + +- [Buffer](./buffer.md) - OptimizedBuffer structure +- [Rendering](./rendering.md) - Core rendering pipeline +- [Animation](./animation.md) - Timeline for animating effects \ No newline at end of file diff --git a/packages/core/docs/modules/guide.md b/packages/core/docs/modules/guide.md new file mode 100644 index 000000000..1120e695f --- /dev/null +++ b/packages/core/docs/modules/guide.md @@ -0,0 +1,91 @@ +# Getting Started with OpenTUI + +> **Quick Navigation:** [Components](./components.md) | [Layout](./layout.md) | [Events](./events.md) | [Animation](./animation.md) | [Rendering](./rendering.md) + +OpenTUI is a terminal UI framework for building rich, interactive command-line applications with a React-like component model. + +## Installation + +```bash +npm install @opentui/core +# or +bun add @opentui/core +``` + +## Basic Example + +```typescript +import { CliRenderer, BoxRenderable, TextRenderable } from '@opentui/core'; + +// Create the renderer +const renderer = new CliRenderer( + lib, // Native library handle + rendererPtr, // Renderer pointer + process.stdin, + process.stdout, + 80, // Width + 24, // Height + {} // Config +); + +// Create a box with text inside +const box = new BoxRenderable('main-box', { + width: '100%', + height: '100%', + border: true, + borderStyle: 'rounded', + padding: 2 +}); + +const text = new TextRenderable('hello', { + text: 'Hello, OpenTUI!' +}); + +// Build component tree +box.add(text); +renderer.root.add(box); + +// Start rendering +renderer.start(); +``` + +## Core Concepts + +### Renderables +Everything you see on screen is a Renderable - the base class for all UI components. +→ Learn more: [Components Guide](./components.md) + +### Layout System +OpenTUI uses Yoga (Facebook's flexbox implementation) for layout: +- Supports flexbox properties +- Percentage-based sizing +- Absolute and relative positioning +→ Learn more: [Layout System](./layout.md) + +### Event System +- Keyboard events flow through focused components +- Mouse events use bubbling similar to the web +- Components can prevent default behavior +→ Learn more: [Event Handling](./events.md) + +### Render Loop +1. Input processing +2. Layout calculation +3. Component rendering to buffer +4. Buffer diff and terminal update +→ Learn more: [Rendering System](./rendering.md) + +## Next Steps + +- **Build UI:** Start with the [Components Guide](./components.md) to learn about available components +- **Handle Input:** Read the [Events Guide](./events.md) for keyboard and mouse handling +- **Add Motion:** Check out [Animations](./animation.md) for transitions and effects +- **Optimize:** Learn about performance in the [Rendering Guide](./rendering.md) + +## Related Topics + +- [Components](./components.md) - Built-in UI components and how to use them +- [Layout System](./layout.md) - Flexbox layout and positioning +- [Event Handling](./events.md) - Keyboard and mouse interaction +- [Animation](./animation.md) - Creating smooth animations +- [Rendering](./rendering.md) - Understanding the render pipeline diff --git a/packages/core/docs/modules/index.md b/packages/core/docs/modules/index.md new file mode 100644 index 000000000..c25ec09d9 --- /dev/null +++ b/packages/core/docs/modules/index.md @@ -0,0 +1,134 @@ +# OpenTUI Module Documentation + +Complete documentation for all OpenTUI core modules, organized by functionality. + +## Core Modules + +### [Rendering](./rendering.md) +Core rendering system with double buffering, dirty region tracking, and frame management. + +### [Buffer](./buffer.md) +High-performance `OptimizedBuffer` class with native Zig acceleration for terminal display operations. + +### [Text Buffer](./text-buffer.md) +Efficient text manipulation and buffer management for terminal rendering with styling support. + +### [Components](./components.md) +Built-in UI components including Box, Text, Input, Select, TabSelect, ASCIIFont, and FrameBuffer. + +## Utility Modules + +### [Lib](./lib.md) +Essential utilities including RGBA colors, styled text, borders, ASCII fonts, input handling, and selection management. + +### [Utils](./utils.md) +Helper functions including `createTextAttributes` for simplified text styling. + +### [Types](./types.md) +TypeScript type definitions, enums, and interfaces for type safety across the framework. + +## Visual Effects + +### [Animation](./animation.md) +Timeline-based animation system with easing functions, keyframes, and property interpolation. + +### [Post-Processing Filters](./filters.md) +Real-time visual effects including blur, glow, distortion, vignette, and color transformations. + +### [3D](./3d.md) +WebGPU and Three.js integration for 3D graphics, sprites, particles, and physics in the terminal. + +## System Integration + +### [Events](./events.md) +Event system for keyboard, mouse, resize, and custom events with bubbling and capturing. + +### [Console](./console.md) +Terminal-based debugging console with output capture, filtering, and visual inspection. + +### [Layout](./layout.md) +Flexbox-inspired layout system using Yoga for automatic component positioning. + +## Native Performance + +### [Zig](./zig.md) +High-performance native acceleration through FFI bindings for rendering and buffer operations. + +## Module Categories + +### Rendering Pipeline +1. **Buffer** - Low-level buffer operations +2. **Text Buffer** - Text-specific buffer management +3. **Rendering** - Core rendering loop +4. **Components** - UI component rendering +5. **Post-Processing Filters** - Visual effects + +### Component System +1. **Components** - Built-in UI components +2. **Layout** - Component positioning +3. **Events** - User interaction +4. **Animation** - Component animation + +### Development Tools +1. **Console** - Debug output and logging +2. **Types** - Type definitions +3. **Utils** - Helper functions + +### Advanced Features +1. **3D** - 3D graphics support +2. **Zig** - Native acceleration +3. **Lib** - Core utilities + +## Quick Start Examples + +### Basic Rendering +```typescript +import { CliRenderer, BoxRenderable, TextRenderable } from '@opentui/core' + +const renderer = new CliRenderer() +const box = new BoxRenderable('container', { + width: 40, + height: 10, + border: true +}) + +const text = new TextRenderable('label', { + content: 'Hello, OpenTUI!' +}) + +box.appendChild(text) +renderer.appendChild(box) +renderer.render() +``` + +### With Animation +```typescript +import { Timeline } from '@opentui/core' + +const timeline = new Timeline() +timeline.add({ + target: box, + property: 'x', + from: 0, + to: 40, + duration: 1000, + easing: 'easeInOut' +}) +timeline.play() +``` + +### With Effects +```typescript +import { VignetteEffect, applyGrayscale } from '@opentui/core/post/filters' + +const vignette = new VignetteEffect(0.5) +const buffer = renderer.getBuffer() + +applyGrayscale(buffer) +vignette.apply(buffer) +renderer.render() +``` + +## API Reference + +For detailed API documentation of types and methods, see the [reference](../reference/index.md) directory. diff --git a/packages/core/docs/modules/layout.md b/packages/core/docs/modules/layout.md new file mode 100644 index 000000000..f4c38dfea --- /dev/null +++ b/packages/core/docs/modules/layout.md @@ -0,0 +1,243 @@ +# Layout System + +OpenTUI uses Facebook's Yoga layout engine, providing flexbox layout for terminal UIs. + +## Basic Layout + +```typescript +const container = new BoxRenderable('container', { + width: '100%', + height: '100%', + flexDirection: 'column' +}); + +const header = new BoxRenderable('header', { + height: 3, + width: '100%', + backgroundColor: '#333333' +}); + +const content = new BoxRenderable('content', { + flexGrow: 1, // Take remaining space + width: '100%' +}); + +const footer = new BoxRenderable('footer', { + height: 3, + width: '100%', + backgroundColor: '#333333' +}); + +container.add(header); +container.add(content); +container.add(footer); +``` + +## Flexbox Properties + +### Container Properties + +```typescript +const flex = new BoxRenderable('flex', { + // Direction + flexDirection: 'row', // 'row' | 'column' | 'row-reverse' | 'column-reverse' + + // Alignment + alignItems: 'center', // Cross-axis alignment + justifyContent: 'center', // Main-axis alignment + + // Wrapping + flexWrap: 'wrap', // 'nowrap' | 'wrap' | 'wrap-reverse' + + // Gap between items + gap: 2 +}); +``` + +### Item Properties + +```typescript +const item = new BoxRenderable('item', { + // Flex properties + flexGrow: 1, // Grow factor + flexShrink: 1, // Shrink factor + flexBasis: 100, // Base size before flex + + // Self alignment + alignSelf: 'stretch' // Override parent's alignItems +}); +``` + +## Grid Layout + +Create grid layouts using nested flexbox: + +```typescript +function createGrid(rows: number, cols: number) { + const grid = new BoxRenderable('grid', { + flexDirection: 'column', + width: '100%', + height: '100%' + }); + + for (let r = 0; r < rows; r++) { + const row = new BoxRenderable(`row-${r}`, { + flexDirection: 'row', + height: `${100 / rows}%`, + width: '100%' + }); + + for (let c = 0; c < cols; c++) { + const cell = new BoxRenderable(`cell-${r}-${c}`, { + width: `${100 / cols}%`, + height: '100%', + border: true + }); + row.add(cell); + } + + grid.add(row); + } + + return grid; +} +``` + +## Absolute Positioning + +```typescript +const overlay = new BoxRenderable('overlay', { + position: 'absolute', + top: 10, + left: 10, + width: 40, + height: 10, + backgroundColor: 'rgba(0, 0, 0, 0.8)', + zIndex: 100 +}); +``` + +## Responsive Layout + +```typescript +class ResponsiveContainer extends BoxRenderable { + constructor(id: string) { + super(id, {}); + this.updateLayout(); + } + + updateLayout() { + const width = this.parent?.computedWidth || 80; + + if (width < 40) { + // Mobile layout + this.setOptions({ + flexDirection: 'column', + padding: 1 + }); + } else if (width < 80) { + // Tablet layout + this.setOptions({ + flexDirection: 'row', + flexWrap: 'wrap', + padding: 2 + }); + } else { + // Desktop layout + this.setOptions({ + flexDirection: 'row', + flexWrap: 'nowrap', + padding: 3 + }); + } + } +} +``` + +## Scrollable Content + +```typescript +class ScrollableBox extends BoxRenderable { + private scrollOffset = 0; + + constructor(id: string, options: BoxOptions) { + super(id, { + ...options, + overflow: 'hidden' + }); + + this.onMouseScroll = (event) => { + if (event.scroll.direction === 'up') { + this.scrollOffset = Math.max(0, this.scrollOffset - 1); + } else { + this.scrollOffset = Math.min( + this.getContentHeight() - this.computedHeight, + this.scrollOffset + 1 + ); + } + this.needsUpdate(); + }; + } + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + // Render with scroll offset + const originalY = this.y; + this.y -= this.scrollOffset; + super.renderSelf(buffer, deltaTime); + this.y = originalY; + } +} +``` + +## Related Topics + +### Component Layout +- [Components: Box](./components.md#box-component) - Container component with flexbox support +- [Components: Text](./components.md#text-component) - Text alignment and wrapping +- [Getting Started](./getting-started.md#core-concepts) - Layout fundamentals + +### Dynamic Layouts +- [Events: Window](./events.md#window-events) - Handling terminal resize +- [Animation: Properties](./animation.md#property-animations) - Animating layout changes +- [Animation: Transitions](./animation.md#transition-effects) - Smooth layout transitions + +### Performance +- [Rendering: Optimization](./rendering.md#performance-tips) - Layout performance tips +- [Rendering: Virtual Scrolling](./rendering.md#virtual-scrolling) - Efficient scrollable layouts +- [Rendering: Culling](./rendering.md#render-optimization-strategies) - Skip rendering off-screen elements + +## Layout Patterns + +### Common Patterns +For implementation examples of common layouts: +- **Forms:** See [Events: Form Handling](./events.md#form-handling) and [Components: Input](./components.md#input-component) +- **Grids:** Check [Components: Patterns](./components.md#component-patterns) +- **Modals/Dialogs:** Review [Animation: Toast](./animation.md#notification-toast-animation) +- **Sidebars:** See flexbox examples above + +### Responsive Techniques +- Use percentage widths: `width: '50%'` +- Flexbox grow/shrink: `flexGrow: 1` +- Media queries via terminal size detection +- See [Events](./events.md) for resize handling + +### Layout Animation +Combine layout with smooth animations: +- [Animation: Timeline](./animation.md#timeline-animations) - Orchestrate multiple layout changes +- [Animation: Sequencing](./animation.md#animation-sequencing) - Chain layout animations +- [Components](./components.md) - Animated component examples + +## Best Practices + +1. **Use Flexbox:** Leverage flexbox for responsive layouts instead of absolute positioning +2. **Percentage Sizing:** Use percentages for responsive designs +3. **Container Components:** Wrap related items in [Box components](./components.md#box-component) +4. **Virtual Scrolling:** For long lists, implement [virtual scrolling](./rendering.md#virtual-scrolling) +5. **Layout Caching:** Cache complex layouts to improve [rendering performance](./rendering.md#caching-complex-renders) + +## Next Steps + +- **Build UIs:** Create interfaces with the [component library](./components.md) +- **Add Interaction:** Make layouts interactive with [event handling](./events.md) +- **Animate Changes:** Add motion with the [animation system](./animation.md) +- **Optimize Performance:** Learn [rendering techniques](./rendering.md) diff --git a/packages/core/docs/modules/lib.md b/packages/core/docs/modules/lib.md new file mode 100644 index 000000000..76063e95f --- /dev/null +++ b/packages/core/docs/modules/lib.md @@ -0,0 +1,258 @@ +# Lib Utilities Module + +The lib module provides essential utility functions and classes that power OpenTUI's core functionality, including styling, borders, fonts, and input handling. + +## Overview + +This module contains foundational utilities used throughout OpenTUI for text styling, border rendering, ASCII fonts, color management, and keyboard/mouse input parsing. + +## Core Components + +### RGBA Color Management + +```typescript +import { RGBA } from '@opentui/core/lib' + +const red = new RGBA(255, 0, 0, 255) +const semitransparent = new RGBA(0, 0, 255, 128) + +// Color blending +const blended = RGBA.blend(red, semitransparent) + +// Color interpolation +const gradient = RGBA.interpolate(red, blue, 0.5) +``` + +### Styled Text + +Rich text formatting with ANSI escape sequences: + +```typescript +import { StyledText, parseStyledText } from '@opentui/core/lib' + +const styled = new StyledText('Hello World') + .fg(255, 0, 0) + .bg(0, 0, 255) + .bold() + .underline() + +const ansi = styled.toANSI() + +// Parse existing styled text +const parsed = parseStyledText('\x1b[31mRed Text\x1b[0m') +``` + +### Border Rendering + +Flexible border system with multiple styles: + +```typescript +import { Border, BorderStyle } from '@opentui/core/lib' + +const border = new Border({ + style: BorderStyle.Double, + fg: new RGBA(255, 255, 255, 255), + padding: 2 +}) + +// Render border to buffer +border.render(buffer, x, y, width, height) + +// Available styles +BorderStyle.Single // ┌─┐ +BorderStyle.Double // ╔═╗ +BorderStyle.Rounded // ╭─╮ +BorderStyle.Heavy // ┏━┓ +``` + +### ASCII Fonts + +Decorative text rendering with ASCII art fonts: + +```typescript +import { ASCIIFont } from '@opentui/core/lib' + +const font = new ASCIIFont({ + font: 'block', + fg: new RGBA(255, 255, 0, 255) +}) + +const rendered = font.render('HELLO') +// ██╗ ██╗███████╗██╗ ██╗ ██████╗ +// ██║ ██║██╔════╝██║ ██║ ██╔═══██╗ +// ███████║█████╗ ██║ ██║ ██║ ██║ +// ██╔══██║██╔══╝ ██║ ██║ ██║ ██║ +// ██║ ██║███████╗███████╗███████╗╚██████╔╝ +// ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝ ╚═════╝ +``` + +## Input Handling + +### KeyHandler + +Sophisticated keyboard input management: + +```typescript +import { KeyHandler } from '@opentui/core/lib' + +const handler = new KeyHandler() + +handler.on('ctrl+c', () => process.exit(0)) +handler.on('arrow-up', () => moveCursor(-1)) +handler.on('enter', () => submitForm()) + +// Complex key combinations +handler.on('ctrl+shift+p', () => openCommandPalette()) + +// Key sequences +handler.sequence(['g', 'g'], () => goToTop()) +handler.sequence(['d', 'd'], () => deleteLine()) +``` + +### Mouse Parsing + +Parse terminal mouse events: + +```typescript +import { parseMouse } from '@opentui/core/lib' + +process.stdin.on('data', (data) => { + const mouse = parseMouse(data) + if (mouse) { + console.log(`Click at ${mouse.x}, ${mouse.y}`) + if (mouse.button === 'left') handleLeftClick(mouse) + if (mouse.type === 'wheel') handleScroll(mouse.delta) + } +}) +``` + +## Text Processing + +### HAST Styled Text + +Integration with syntax highlighting using HAST: + +```typescript +import { hastToStyledText } from '@opentui/core/lib' + +const hastTree = { + type: 'element', + tagName: 'span', + properties: { className: ['keyword'] }, + children: [{ type: 'text', value: 'function' }] +} + +const styled = hastToStyledText(hastTree, { + theme: 'monokai', + background: false +}) +``` + +### Selection Management + +Text selection and clipboard operations: + +```typescript +import { Selection } from '@opentui/core/lib' + +const selection = new Selection() +selection.start(10, 5) +selection.extend(25, 8) + +const selected = selection.getSelectedText(buffer) +const coords = selection.getCoordinates() + +// Visual feedback +selection.highlight(buffer, { bg: [100, 100, 100, 255] }) +``` + +## Layout Utilities + +### Yoga Layout Options + +Flexbox layout configuration: + +```typescript +import { yogaOptions } from '@opentui/core/lib' + +const layout = yogaOptions({ + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + padding: 10, + gap: 5 +}) +``` + +### TrackedNode + +DOM-like node tracking for component trees: + +```typescript +import { TrackedNode } from '@opentui/core/lib' + +const root = new TrackedNode('root') +const child = new TrackedNode('child') +root.appendChild(child) + +// Tree traversal +root.traverse((node) => { + console.log(node.id) +}) + +// Find nodes +const found = root.find((node) => node.id === 'child') +``` + +## Utilities + +### Output Capture + +Capture and redirect terminal output: + +```typescript +import { captureOutput } from '@opentui/core/lib' + +const restore = captureOutput((output) => { + // Process captured output + logger.log(output) +}) + +console.log('This will be captured') +restore() // Restore normal output +``` + +### Parse Keypress + +Low-level keypress parsing: + +```typescript +import { parseKeypress } from '@opentui/core/lib' + +const key = parseKeypress(Buffer.from([27, 91, 65])) +// { name: 'up', ctrl: false, shift: false, meta: false } +``` + +## API Reference + +### Exports + +- `RGBA` - Color representation and manipulation +- `StyledText` - Text with ANSI styling +- `Border` - Border rendering utilities +- `BorderStyle` - Border style constants +- `ASCIIFont` - ASCII art text rendering +- `KeyHandler` - Keyboard event management +- `parseMouse` - Mouse event parsing +- `parseKeypress` - Keypress parsing +- `Selection` - Text selection management +- `TrackedNode` - Node tree management +- `hastToStyledText` - HAST to styled text conversion +- `yogaOptions` - Layout configuration helpers +- `captureOutput` - Output redirection + +## Related Modules + +- [Components](./components.md) - Uses lib utilities for rendering +- [Text Buffer](./text-buffer.md) - Text manipulation and styling +- [Events](./events.md) - Input event handling \ No newline at end of file diff --git a/packages/core/docs/modules/rendering.md b/packages/core/docs/modules/rendering.md new file mode 100644 index 000000000..4ce59ff16 --- /dev/null +++ b/packages/core/docs/modules/rendering.md @@ -0,0 +1,713 @@ +# Rendering System + +> **Quick Navigation:** [Getting Started](./getting-started.md) | [Components](./components.md) | [Layout](./layout.md) | [Events](./events.md) | [Animation](./animation.md) + +Understanding how OpenTUI renders to the terminal for optimal performance. + +## Render Pipeline + +``` +Input Events → Layout → Component Render → Buffer Diff → Terminal Update +``` + +## The OptimizedBuffer + +OpenTUI uses a double-buffered rendering system with dirty region tracking. + +```typescript +import { OptimizedBuffer, RGBA } from '@opentui/core'; + +class CustomRenderer extends Renderable { + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + // Clear a region + buffer.clear(this.x, this.y, this.width, this.height); + + // Draw text + buffer.drawText( + 'Hello World', + this.x, + this.y, + RGBA.fromHex('#ffffff'), // Foreground + RGBA.fromHex('#000000') // Background + ); + + // Draw individual cells + for (let x = 0; x < this.width; x++) { + buffer.setCell( + this.x + x, + this.y + 1, + '═', + RGBA.white(), + RGBA.black() + ); + } + + // Draw a box + buffer.drawBox( + this.x, + this.y, + this.width, + this.height, + { + style: 'double', + color: RGBA.fromHex('#00ff00') + } + ); + } +} +``` + +## Color Management + +```typescript +import { RGBA } from '@opentui/core'; + +// Create colors +const red = RGBA.fromHex('#ff0000'); +const green = RGBA.fromValues(0, 1, 0, 1); // r, g, b, a (0-1) +const blue = new RGBA(0, 0, 255, 255); // r, g, b, a (0-255) + +// Color with transparency +const semiTransparent = RGBA.fromValues(1, 1, 1, 0.5); + +// Blend colors +const purple = RGBA.blend(red, blue, 0.5); + +// Common colors +const white = RGBA.white(); +const black = RGBA.black(); +const transparent = RGBA.transparent(); +``` + +## Dirty Region Optimization + +```typescript +class OptimizedComponent extends Renderable { + private isDirty = true; + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + if (!this.isDirty) { + return; // Skip rendering if not dirty + } + + // Mark the region we're about to render as dirty + buffer.markDirty(this.x, this.y, this.width, this.height); + + // Render content + this.renderContent(buffer); + + this.isDirty = false; + } + + // Call when content changes + invalidate() { + this.isDirty = true; + this.needsUpdate(); // Request re-render + } +} +``` + +## Layered Rendering + +```typescript +class LayeredUI extends Renderable { + private layers: Map = new Map(); + + addToLayer(component: Renderable, layer: number) { + if (!this.layers.has(layer)) { + this.layers.set(layer, []); + } + this.layers.get(layer)!.push(component); + component.zIndex = layer; + } + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + // Render layers in order + const sortedLayers = Array.from(this.layers.keys()).sort(); + + for (const layer of sortedLayers) { + const components = this.layers.get(layer)!; + for (const component of components) { + component.render(buffer, deltaTime); + } + } + } +} +``` + +## Performance Tips + +### 1. Use Buffered Rendering + +```typescript +// Good - renders to internal buffer first +const buffered = new BoxRenderable('buffered', { + buffered: true, // Enable internal buffering + width: 100, + height: 50 +}); + +// Updates only re-render this component, not parents +buffered.needsUpdate(); +``` + +### 2. Batch Updates + +```typescript +class BatchUpdater extends Renderable { + private pendingUpdates: Function[] = []; + + queueUpdate(fn: Function) { + this.pendingUpdates.push(fn); + } + + flushUpdates() { + // Batch all updates together + this.pendingUpdates.forEach(fn => fn()); + this.pendingUpdates = []; + this.needsUpdate(); // Single re-render + } +} +``` + +### 3. Virtual Scrolling + +```typescript +class VirtualList extends Renderable { + private items: string[] = []; + private scrollOffset = 0; + private itemHeight = 1; + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + const visibleItems = Math.floor(this.height / this.itemHeight); + const startIndex = this.scrollOffset; + const endIndex = Math.min(startIndex + visibleItems, this.items.length); + + // Only render visible items + for (let i = startIndex; i < endIndex; i++) { + const y = this.y + (i - startIndex) * this.itemHeight; + buffer.drawText(this.items[i], this.x, y); + } + } +} +``` + +### 4. Caching Complex Renders + +```typescript +class CachedComponent extends Renderable { + private cache?: OptimizedBuffer; + private cacheValid = false; + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + if (!this.cacheValid) { + // Render to cache + this.cache = OptimizedBuffer.create(this.width, this.height); + this.renderToCache(this.cache); + this.cacheValid = true; + } + + // Copy from cache + buffer.copyFrom(this.cache, this.x, this.y); + } + + invalidateCache() { + this.cacheValid = false; + this.needsUpdate(); + } +} +``` + +## Debug Overlay + +```typescript +// Enable debug overlay +renderer.toggleDebugOverlay(); + +// Configure overlay +renderer.configureDebugOverlay({ + enabled: true, + corner: DebugOverlayCorner.TOP_RIGHT, + showFPS: true, + showDirtyRegions: true, + showLayoutBounds: true +}); +``` + +## Advanced Rendering Techniques + +### Custom Cell Rendering + +```typescript +class MatrixRain extends Renderable { + private columns: number[] = []; + private chars = 'ハミヒーウシナモニサワツオリアホテマケメエカキムユラセネスタヌヘ0123456789'; + + constructor(id: string, options: RenderableOptions) { + super(id, options); + this.columns = new Array(this.width).fill(0); + } + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + // Fade existing cells + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + const cell = buffer.getCell(this.x + x, this.y + y); + if (cell) { + // Fade to black + const color = cell.fg; + const faded = RGBA.fromValues( + 0, + color.g * 0.9, + 0, + color.a * 0.95 + ); + buffer.setCell( + this.x + x, + this.y + y, + cell.char, + faded + ); + } + } + } + + // Update columns + for (let x = 0; x < this.columns.length; x++) { + if (Math.random() > 0.98) { + this.columns[x] = 0; // Reset column + } + + const y = this.columns[x]; + if (y < this.height) { + const char = this.chars[Math.floor(Math.random() * this.chars.length)]; + const brightness = y === 0 ? 1 : 0.7; + + buffer.setCell( + this.x + x, + this.y + y, + char, + RGBA.fromValues(0, brightness, 0, 1) + ); + + this.columns[x]++; + } + } + + this.needsUpdate(); + } +} +``` + +### Gradient Rendering + +```typescript +class GradientBox extends BoxRenderable { + private gradient: { + startColor: RGBA; + endColor: RGBA; + direction: 'horizontal' | 'vertical' | 'diagonal'; + }; + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + super.renderSelf(buffer, deltaTime); + + const { startColor, endColor, direction } = this.gradient; + + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + let progress: number; + + switch (direction) { + case 'horizontal': + progress = x / this.width; + break; + case 'vertical': + progress = y / this.height; + break; + case 'diagonal': + progress = (x + y) / (this.width + this.height); + break; + } + + const color = RGBA.blend(startColor, endColor, progress); + buffer.setCell( + this.x + x, + this.y + y, + ' ', + color, + color // Use as background + ); + } + } + } +} +``` + +### Shadow Effects + +```typescript +class ShadowBox extends BoxRenderable { + private shadowOffset = { x: 2, y: 1 }; + private shadowColor = RGBA.fromValues(0, 0, 0, 0.5); + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + // Draw shadow first + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + const shadowX = this.x + x + this.shadowOffset.x; + const shadowY = this.y + y + this.shadowOffset.y; + + // Blend shadow with existing content + const existing = buffer.getCell(shadowX, shadowY); + if (existing) { + const blended = RGBA.blend(existing.bg, this.shadowColor, 0.5); + buffer.setCell(shadowX, shadowY, existing.char, existing.fg, blended); + } + } + } + + // Draw box on top + super.renderSelf(buffer, deltaTime); + } +} +``` + +### Texture Patterns + +```typescript +class TexturedBackground extends Renderable { + private patterns = { + dots: ['·', '•', '●'], + lines: ['─', '═', '━'], + crosses: ['┼', '╬', '╋'], + blocks: ['░', '▒', '▓'] + }; + + renderPattern( + buffer: OptimizedBuffer, + pattern: keyof typeof this.patterns, + density: number = 0.3 + ) { + const chars = this.patterns[pattern]; + + for (let y = 0; y < this.height; y++) { + for (let x = 0; x < this.width; x++) { + if (Math.random() < density) { + const char = chars[Math.floor(Math.random() * chars.length)]; + const brightness = 0.2 + Math.random() * 0.3; + + buffer.setCell( + this.x + x, + this.y + y, + char, + RGBA.fromValues(brightness, brightness, brightness, 1) + ); + } + } + } + } +} +``` + +## Performance Monitoring + +### Frame Time Analysis + +```typescript +class RenderProfiler { + private samples: number[] = []; + private maxSamples = 100; + + startFrame(): () => void { + const start = performance.now(); + + return () => { + const duration = performance.now() - start; + this.samples.push(duration); + + if (this.samples.length > this.maxSamples) { + this.samples.shift(); + } + }; + } + + getStats() { + if (this.samples.length === 0) return null; + + const sorted = [...this.samples].sort((a, b) => a - b); + const sum = sorted.reduce((a, b) => a + b, 0); + + return { + avg: sum / sorted.length, + min: sorted[0], + max: sorted[sorted.length - 1], + p50: sorted[Math.floor(sorted.length * 0.5)], + p95: sorted[Math.floor(sorted.length * 0.95)], + p99: sorted[Math.floor(sorted.length * 0.99)] + }; + } +} + +// Usage +const profiler = new RenderProfiler(); + +class ProfiledComponent extends Renderable { + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + const endProfile = profiler.startFrame(); + + // Render logic here + this.renderContent(buffer); + + endProfile(); + + // Log stats periodically + if (Math.random() < 0.01) { + console.log('Render stats:', profiler.getStats()); + } + } +} +``` + +### Memory Usage Tracking + +```typescript +class MemoryMonitor { + private buffers = new WeakMap(); + + trackBuffer(buffer: OptimizedBuffer) { + const size = buffer.width * buffer.height * 4 * 2; // cells × RGBA × 2 (fg+bg) + this.buffers.set(buffer, size); + } + + getEstimatedMemory(): string { + // Note: WeakMap doesn't allow iteration + // This would need a different approach in production + const usage = process.memoryUsage(); + return `Heap: ${(usage.heapUsed / 1024 / 1024).toFixed(2)}MB`; + } +} +``` + +## Render Optimization Strategies + +### 1. Scissor Regions + +```typescript +class ScissoredRenderer extends Renderable { + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + // Set scissor region to limit rendering + buffer.setScissor(this.x, this.y, this.width, this.height); + + // All rendering operations are now clipped to this region + this.renderChildren(buffer, deltaTime); + + // Clear scissor + buffer.clearScissor(); + } +} +``` + +### 2. Level of Detail (LOD) + +```typescript +class LODComponent extends Renderable { + private distanceFromFocus: number = 0; + + protected renderSelf(buffer: OptimizedBuffer, deltaTime: number): void { + if (this.distanceFromFocus > 100) { + // Far away - render simplified version + this.renderLowDetail(buffer); + } else if (this.distanceFromFocus > 50) { + // Medium distance - render medium detail + this.renderMediumDetail(buffer); + } else { + // Close - render full detail + this.renderHighDetail(buffer); + } + } + + private renderLowDetail(buffer: OptimizedBuffer) { + // Just a colored block + buffer.fillRect(this.x, this.y, this.width, this.height, '█'); + } + + private renderMediumDetail(buffer: OptimizedBuffer) { + // Basic box with title + buffer.drawBox(this.x, this.y, this.width, this.height); + buffer.drawText(this.title, this.x + 1, this.y); + } + + private renderHighDetail(buffer: OptimizedBuffer) { + // Full rendering with all details + super.renderSelf(buffer, deltaTime); + } +} +``` + +### 3. Render Culling + +```typescript +class CullingRenderer extends CliRenderer { + private viewport = { x: 0, y: 0, width: 80, height: 24 }; + + protected renderTree(buffer: OptimizedBuffer, deltaTime: number) { + this.cullAndRender(this.root, buffer, deltaTime); + } + + private cullAndRender( + node: Renderable, + buffer: OptimizedBuffer, + deltaTime: number + ) { + // Check if node is visible in viewport + if (!this.isInViewport(node)) { + return; // Skip rendering + } + + // Render node + node.render(buffer, deltaTime); + + // Recursively render visible children + for (const child of node.getChildren()) { + this.cullAndRender(child, buffer, deltaTime); + } + } + + private isInViewport(node: Renderable): boolean { + return !( + node.x + node.width < this.viewport.x || + node.y + node.height < this.viewport.y || + node.x > this.viewport.x + this.viewport.width || + node.y > this.viewport.y + this.viewport.height + ); + } +} +``` + +## Troubleshooting Rendering Issues + +### Common Problems and Solutions + +1. **Flickering** + - Enable double buffering: `buffered: true` + - Reduce update frequency + - Check for unnecessary re-renders + +2. **Tearing** + - Synchronize updates with vsync if available + - Use atomic buffer swaps + - Avoid partial updates during render + +3. **Performance** + - Profile with debug overlay + - Implement dirty region tracking + - Use virtual scrolling for lists + - Cache complex renders + +4. **Color Issues** + - Check terminal color support + - Use fallback colors for limited terminals + - Test with different terminal emulators + +### Debugging Tools + +```typescript +class RenderDebugger { + static highlightDirtyRegions(buffer: OptimizedBuffer) { + const regions = buffer.getDirtyRegions(); + for (const region of regions) { + // Draw red border around dirty regions + buffer.drawBox( + region.x, + region.y, + region.width, + region.height, + { + style: 'single', + color: RGBA.fromHex('#ff0000') + } + ); + } + } + + static showRenderOrder(root: Renderable, buffer: OptimizedBuffer) { + let order = 0; + + function traverse(node: Renderable) { + // Draw render order number + buffer.drawText( + order.toString(), + node.x, + node.y, + RGBA.fromHex('#ffff00') + ); + order++; + + for (const child of node.getChildren()) { + traverse(child); + } + } + + traverse(root); + } +} +``` + +## Related Topics + +### Components & Rendering +- [Components: Custom](./components.md#custom-components) - Custom component rendering +- [Components: Box](./components.md#box-component) - Container rendering +- [Getting Started](./getting-started.md#core-concepts) - Render loop basics + +### Animation Integration +- [Animation: Performance](./animation.md#performance-best-practices) - Optimizing animated renders +- [Animation: Frame-based](./animation.md#frame-based-animation) - Frame-by-frame rendering +- [Animation: Effects](./animation.md#complex-animation-examples) - Visual effects rendering + +### Layout & Rendering +- [Layout: Optimization](./layout.md#best-practices) - Layout performance +- [Layout: Scrollable](./layout.md#scrollable-content) - Virtual scrolling +- [Layout: Responsive](./layout.md#responsive-techniques) - Responsive rendering + +### Event-driven Rendering +- [Events: Mouse](./events.md#mouse-events) - Render on interaction +- [Events: Window](./events.md#window-events) - Handle resize rendering +- [Events: Forms](./events.md#form-handling) - Input field rendering + +## Rendering Patterns + +### Common Techniques +- **Double Buffering:** Prevent flicker with buffered rendering +- **Dirty Regions:** Only update changed areas +- **Virtual Scrolling:** Render only visible items +- **Caching:** Store complex renders for reuse + +### Visual Effects +Implement advanced effects: +- [Gradients](#gradient-rendering) - Color gradients +- [Shadows](#shadow-effects) - Drop shadows +- [Textures](#texture-patterns) - Background patterns +- [Matrix Rain](#custom-cell-rendering) - Custom effects + +### Performance Strategies +- [LOD](#level-of-detail-lod) - Adjust detail by distance +- [Culling](#render-culling) - Skip off-screen elements +- [Batching](#performance-tips) - Batch render operations +- [Profiling](#performance-monitoring) - Measure and optimize + +## Best Practices + +1. **Use Buffering:** Enable `buffered: true` for frequently updating components +2. **Track Dirty Regions:** Only redraw changed areas +3. **Implement Virtual Scrolling:** For long lists +4. **Cache Complex Renders:** Store and reuse expensive renders +5. **Profile Performance:** Measure frame times and optimize bottlenecks + +## Next Steps + +- **Build UIs:** Create efficient [Components](./components.md) +- **Add Motion:** Optimize [Animations](./animation.md) for performance +- **Handle Input:** Render feedback for [Events](./events.md) +- **Layout:** Create performant [Layouts](./layout.md) diff --git a/packages/core/docs/modules/text-buffer.md b/packages/core/docs/modules/text-buffer.md new file mode 100644 index 000000000..2f3e4fd15 --- /dev/null +++ b/packages/core/docs/modules/text-buffer.md @@ -0,0 +1,158 @@ +# Text Buffer Module + +The text-buffer module provides efficient text manipulation and buffer management for terminal rendering operations. + +## Overview + +The TextBuffer class manages character-based content with styling, providing optimized operations for terminal UI rendering including diffing, merging, and viewport management. + +## Core Components + +### TextBuffer + +Main class for managing text content with ANSI styling and efficient updates. + +```typescript +import { TextBuffer } from '@opentui/core' + +const buffer = new TextBuffer(80, 24) +buffer.write(0, 0, 'Hello, World!') +buffer.setStyle(0, 0, 13, { fg: [255, 0, 0, 255] }) +``` + +## Key Features + +### Buffer Operations + +- **Write Operations**: Direct character writing with position control +- **Style Management**: Apply ANSI colors and text decorations +- **Diff Generation**: Efficient change detection between buffer states +- **Viewport Support**: Handle scrolling and partial rendering +- **Clear Operations**: Reset regions or entire buffer + +### Text Manipulation + +```typescript +// Write styled text +buffer.writeStyled(10, 5, 'Important', { + fg: [255, 255, 0, 255], + bold: true, + underline: true +}) + +// Extract regions +const region = buffer.getRegion(0, 0, 40, 10) + +// Merge buffers +buffer.merge(otherBuffer, 20, 10) +``` + +## Performance Optimization + +### Dirty Tracking + +Tracks modified regions to minimize rendering updates: + +```typescript +buffer.write(0, 0, 'Changed') +const dirty = buffer.getDirtyRegions() +// Only re-render dirty regions +``` + +### Buffer Pooling + +Reuse buffer instances to reduce allocations: + +```typescript +const pool = new TextBufferPool(10) +const buffer = pool.acquire(80, 24) +// Use buffer... +pool.release(buffer) +``` + +## Integration + +### With Renderables + +TextBuffer integrates seamlessly with the rendering system: + +```typescript +class CustomRenderable extends Renderable { + private buffer: TextBuffer + + render(target: TextBuffer) { + target.merge(this.buffer, this.x, this.y) + } +} +``` + +### With Console Output + +Direct terminal output support: + +```typescript +const output = buffer.toANSI() +process.stdout.write(output) +``` + +## Advanced Usage + +### Double Buffering + +Implement smooth animations with double buffering: + +```typescript +let frontBuffer = new TextBuffer(80, 24) +let backBuffer = new TextBuffer(80, 24) + +function render() { + // Render to back buffer + backBuffer.clear() + renderScene(backBuffer) + + // Swap buffers + [frontBuffer, backBuffer] = [backBuffer, frontBuffer] + + // Output front buffer + console.write(frontBuffer.toANSI()) +} +``` + +### Custom Encodings + +Support for different character encodings: + +```typescript +const buffer = new TextBuffer(80, 24, { + encoding: 'utf-8', + lineEnding: '\n' +}) +``` + +## API Reference + +### Constructor + +```typescript +new TextBuffer(width: number, height: number, options?: TextBufferOptions) +``` + +### Methods + +- `write(x: number, y: number, text: string): void` +- `writeStyled(x: number, y: number, text: string, style: TextStyle): void` +- `clear(x?: number, y?: number, width?: number, height?: number): void` +- `getChar(x: number, y: number): string` +- `getStyle(x: number, y: number): TextStyle` +- `setStyle(x: number, y: number, length: number, style: TextStyle): void` +- `getRegion(x: number, y: number, width: number, height: number): TextBuffer` +- `merge(buffer: TextBuffer, x: number, y: number): void` +- `getDirtyRegions(): DirtyRegion[]` +- `toANSI(): string` +- `clone(): TextBuffer` + +## Related Modules + +- [Buffer](./buffer.md) - Lower-level buffer operations +- [Rendering](./rendering.md) - Integration with rendering system +- [Components](./components.md) - Usage in UI components \ No newline at end of file diff --git a/packages/core/docs/modules/types.md b/packages/core/docs/modules/types.md new file mode 100644 index 000000000..0cf8a99a3 --- /dev/null +++ b/packages/core/docs/modules/types.md @@ -0,0 +1,408 @@ +# Types Module + +The types module provides TypeScript type definitions, enums, and interfaces used throughout OpenTUI for type safety and consistency. + +## Overview + +This module exports core type definitions that ensure type safety across the OpenTUI framework, including text attributes, cursor styles, render contexts, and configuration options. + +## Text Attributes + +### Attribute Flags + +Bitwise flags for text styling: + +```typescript +import { TextAttributes } from '@opentui/core' + +const TextAttributes = { + NONE: 0, + BOLD: 1 << 0, // 1 + DIM: 1 << 1, // 2 + ITALIC: 1 << 2, // 4 + UNDERLINE: 1 << 3, // 8 + BLINK: 1 << 4, // 16 + INVERSE: 1 << 5, // 32 + HIDDEN: 1 << 6, // 64 + STRIKETHROUGH: 1 << 7 // 128 +} +``` + +### Combining Attributes + +Use bitwise operations to combine attributes: + +```typescript +// Combine multiple attributes +const style = TextAttributes.BOLD | TextAttributes.UNDERLINE + +// Check for attribute +const isBold = (style & TextAttributes.BOLD) !== 0 + +// Remove attribute +const withoutBold = style & ~TextAttributes.BOLD + +// Toggle attribute +const toggled = style ^ TextAttributes.ITALIC +``` + +## Cursor Types + +### CursorStyle + +Terminal cursor appearance: + +```typescript +type CursorStyle = "block" | "line" | "underline" + +// Usage +renderer.setCursorStyle("block") +renderer.setCursorStyle("line") // Vertical bar +renderer.setCursorStyle("underline") // Horizontal underscore +``` + +## Debug Overlay + +### DebugOverlayCorner + +Position for debug information display: + +```typescript +enum DebugOverlayCorner { + topLeft = 0, + topRight = 1, + bottomLeft = 2, + bottomRight = 3 +} + +// Usage +renderer.setDebugOverlay(true, DebugOverlayCorner.topRight) +``` + +## Render Context + +### RenderContext Interface + +Context provided during rendering: + +```typescript +interface RenderContext { + // Add hit detection region + addToHitGrid: ( + x: number, + y: number, + width: number, + height: number, + id: number + ) => void + + // Get viewport dimensions + width: () => number + height: () => number + + // Mark for re-render + needsUpdate: () => void +} + +// Usage in renderable +class MyRenderable extends Renderable { + render(buffer: Buffer, context: RenderContext) { + // Register hit area + context.addToHitGrid(this.x, this.y, this.width, this.height, this.id) + + // Check viewport + if (this.x > context.width()) return + + // Request update + if (this.animated) context.needsUpdate() + } +} +``` + +## Selection State + +### SelectionState Interface + +Text selection tracking: + +```typescript +interface SelectionState { + anchor: { x: number; y: number } // Selection start + focus: { x: number; y: number } // Selection end + isActive: boolean // Selection exists + isSelecting: boolean // Currently selecting +} + +// Usage +const selection: SelectionState = { + anchor: { x: 10, y: 5 }, + focus: { x: 25, y: 7 }, + isActive: true, + isSelecting: false +} + +// Get selection bounds +const minX = Math.min(selection.anchor.x, selection.focus.x) +const maxX = Math.max(selection.anchor.x, selection.focus.x) +const minY = Math.min(selection.anchor.y, selection.focus.y) +const maxY = Math.max(selection.anchor.y, selection.focus.y) +``` + +## Color Types + +### ColorInput + +Flexible color input type: + +```typescript +type ColorInput = string | RGBA | [number, number, number, number] + +// All valid color inputs +const color1: ColorInput = "#ff0000" +const color2: ColorInput = "rgb(255, 0, 0)" +const color3: ColorInput = RGBA.fromValues(1, 0, 0, 1) +const color4: ColorInput = [255, 0, 0, 255] +``` + +## Component Option Types + +Types are provided for all component configurations (imported from type definition files): + +- `BoxOptions` - Box component configuration +- `TextOptions` - Text component options +- `InputRenderableOptions` - Input field configuration +- `SelectRenderableOptions` - Select dropdown options +- `TabSelectRenderableOptions` - Tab selector options +- `ASCIIFontOptions` - ASCII art font settings +- `FrameBufferOptions` - Frame buffer configuration +- `AnimationOptions` - Animation settings +- `TimelineOptions` - Timeline configuration +- `ConsoleOptions` - Console window options +- `CliRendererConfig` - Renderer configuration + +## Layout Types + +### LayoutOptions + +Flexbox-style layout configuration: + +```typescript +interface LayoutOptions { + flexDirection?: 'row' | 'column' | 'row-reverse' | 'column-reverse' + justifyContent?: 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around' + alignItems?: 'flex-start' | 'center' | 'flex-end' | 'stretch' + flexWrap?: 'nowrap' | 'wrap' | 'wrap-reverse' + flex?: number + flexGrow?: number + flexShrink?: number + flexBasis?: number | string + padding?: number | [number, number] | [number, number, number, number] + margin?: number | [number, number] | [number, number, number, number] + gap?: number + width?: number | string + height?: number | string + minWidth?: number | string + minHeight?: number | string + maxWidth?: number | string + maxHeight?: number | string +} +``` + +## Border Types + +### BorderConfig + +Border configuration options: + +```typescript +interface BorderConfig { + style?: BorderStyle + color?: ColorInput + width?: number + padding?: number | [number, number] | [number, number, number, number] + margin?: number | [number, number] | [number, number, number, number] + rounded?: boolean +} + +type BorderStyle = 'single' | 'double' | 'rounded' | 'heavy' +type BorderSides = 'top' | 'right' | 'bottom' | 'left' +``` + +## Event Types + +### Mouse Events + +```typescript +interface MouseEvent { + x: number + y: number + button: 'left' | 'right' | 'middle' | 'none' + type: 'click' | 'move' | 'wheel' | 'down' | 'up' + modifiers: { + shift: boolean + ctrl: boolean + alt: boolean + meta: boolean + } + delta?: number // For wheel events +} +``` + +### Keyboard Events + +```typescript +interface KeyEvent { + key: string + code: string + modifiers: { + shift: boolean + ctrl: boolean + alt: boolean + meta: boolean + } + isComposing: boolean +} +``` + +## Utility Types + +### Dimensions + +```typescript +interface Dimensions { + width: number + height: number +} + +interface Position { + x: number + y: number +} + +interface Bounds { + x: number + y: number + width: number + height: number +} + +interface Padding { + top: number + right: number + bottom: number + left: number +} +``` + +## Type Guards + +Utility functions for type checking: + +```typescript +// Check if value is RGBA +function isRGBA(value: any): value is RGBA { + return value instanceof RGBA +} + +// Check if value is color string +function isColorString(value: any): value is string { + return typeof value === 'string' && + (value.startsWith('#') || value.startsWith('rgb')) +} + +// Check if has selection +function hasSelection(state: SelectionState): boolean { + return state.isActive && + (state.anchor.x !== state.focus.x || + state.anchor.y !== state.focus.y) +} +``` + +## Generic Types + +### Callback Types + +```typescript +type VoidCallback = () => void +type ValueCallback = (value: T) => void +type Predicate = (value: T) => boolean +type Mapper = (value: T) => U +type Reducer = (acc: U, value: T) => U +``` + +### Component Types + +```typescript +type ComponentProps = T & { + id?: string + className?: string + style?: Partial + children?: ReactNode +} + +type RenderFunction = ( + buffer: OptimizedBuffer, + context: RenderContext +) => void +``` + +## Usage Examples + +### Type-Safe Component Creation + +```typescript +import { BoxOptions, TextAttributes, RGBA } from '@opentui/core' + +const boxConfig: BoxOptions = { + width: 40, + height: 20, + border: true, + borderStyle: 'double', + padding: 2, + fg: RGBA.fromValues(1, 1, 1, 1), + bg: RGBA.fromValues(0, 0, 0.5, 0.8) +} + +const textStyle = TextAttributes.BOLD | TextAttributes.UNDERLINE + +const box = new BoxRenderable('myBox', boxConfig) +box.attributes = textStyle +``` + +### Type-Safe Event Handling + +```typescript +function handleMouse(event: MouseEvent): void { + if (event.type === 'click' && event.button === 'left') { + console.log(`Clicked at ${event.x}, ${event.y}`) + } + + if (event.modifiers.ctrl) { + console.log('Ctrl key held') + } +} + +function handleKey(event: KeyEvent): void { + if (event.key === 'Enter' && !event.modifiers.shift) { + submitForm() + } +} +``` + +## API Reference + +### Exports + +- `TextAttributes` - Text attribute flags +- `CursorStyle` - Cursor appearance type +- `DebugOverlayCorner` - Debug overlay positions +- `RenderContext` - Rendering context interface +- `SelectionState` - Text selection state +- All option interfaces from type definition files + +## Related Modules + +- [Components](./components.md) - Uses type definitions +- [Rendering](./rendering.md) - Uses RenderContext +- [Events](./events.md) - Event type definitions +- [Lib](./lib.md) - Color and style utilities \ No newline at end of file diff --git a/packages/core/docs/modules/utils.md b/packages/core/docs/modules/utils.md new file mode 100644 index 000000000..7bb630dd6 --- /dev/null +++ b/packages/core/docs/modules/utils.md @@ -0,0 +1,331 @@ +# Utils Module + +The utils module provides utility functions for common operations in OpenTUI, including text attribute creation and other helper functions. + +## Overview + +This module contains helper functions that simplify common tasks when working with OpenTUI components and rendering. + +## Text Attribute Utilities + +### createTextAttributes + +Create text attributes using a friendly object interface instead of bitwise operations: + +```typescript +import { createTextAttributes } from '@opentui/core' + +// Create attributes with object syntax +const attributes = createTextAttributes({ + bold: true, + underline: true, + italic: false +}) + +// Equivalent to: +// TextAttributes.BOLD | TextAttributes.UNDERLINE +``` + +### Usage Examples + +```typescript +// No attributes (default) +const plain = createTextAttributes() +// Returns: 0 + +// Single attribute +const bold = createTextAttributes({ bold: true }) +// Returns: 1 + +// Multiple attributes +const fancy = createTextAttributes({ + bold: true, + italic: true, + underline: true +}) +// Returns: 13 (1 + 4 + 8) + +// All attributes +const all = createTextAttributes({ + bold: true, + dim: true, + italic: true, + underline: true, + blink: true, + inverse: true, + hidden: true, + strikethrough: true +}) +// Returns: 255 (all bits set) +``` + +### Attribute Options + +All options are optional and default to `false`: + +```typescript +interface TextAttributeOptions { + bold?: boolean // Make text bold + italic?: boolean // Make text italic + underline?: boolean // Underline text + dim?: boolean // Dim/faint text + blink?: boolean // Blinking text + inverse?: boolean // Swap fg/bg colors + hidden?: boolean // Hide text (but preserve space) + strikethrough?: boolean // Strike through text +} +``` + +## Integration with Components + +### With Text Components + +```typescript +import { TextRenderable, createTextAttributes } from '@opentui/core' + +const text = new TextRenderable('myText', { + content: 'Important Message', + attributes: createTextAttributes({ + bold: true, + underline: true + }) +}) +``` + +### With Box Components + +```typescript +import { BoxRenderable, createTextAttributes } from '@opentui/core' + +const box = new BoxRenderable('myBox', { + title: 'Alert', + titleAttributes: createTextAttributes({ + bold: true, + inverse: true + }) +}) +``` + +### Dynamic Attribute Updates + +```typescript +class InteractiveText extends TextRenderable { + private baseAttributes = createTextAttributes({ bold: true }) + private hoverAttributes = createTextAttributes({ + bold: true, + underline: true, + inverse: true + }) + + onMouseEnter() { + this.attributes = this.hoverAttributes + } + + onMouseLeave() { + this.attributes = this.baseAttributes + } +} +``` + +## Attribute Manipulation + +### Combining with Existing Attributes + +```typescript +// Start with some attributes +let attrs = createTextAttributes({ bold: true }) + +// Add more attributes +attrs |= createTextAttributes({ underline: true }) + +// Remove attributes +attrs &= ~TextAttributes.BOLD + +// Toggle attributes +attrs ^= TextAttributes.ITALIC + +// Check for attributes +const isBold = (attrs & TextAttributes.BOLD) !== 0 +``` + +### Attribute Presets + +Create reusable attribute presets: + +```typescript +const styles = { + heading: createTextAttributes({ + bold: true, + underline: true + }), + + error: createTextAttributes({ + bold: true, + inverse: true + }), + + muted: createTextAttributes({ + dim: true + }), + + link: createTextAttributes({ + underline: true + }), + + code: createTextAttributes({ + inverse: true + }) +} + +// Use presets +text.attributes = styles.heading +errorText.attributes = styles.error +``` + +## Performance Considerations + +### Caching Attributes + +Since `createTextAttributes` performs bitwise operations, cache frequently used combinations: + +```typescript +// Good - cache the result +class MyComponent { + private static readonly HIGHLIGHT_ATTRS = createTextAttributes({ + bold: true, + inverse: true + }) + + highlight() { + this.attributes = MyComponent.HIGHLIGHT_ATTRS + } +} + +// Avoid - creates new value each time +class MyComponent { + highlight() { + this.attributes = createTextAttributes({ + bold: true, + inverse: true + }) // Recalculated each call + } +} +``` + +## Best Practices + +### Semantic Naming + +Use descriptive names for attribute combinations: + +```typescript +const semanticStyles = { + primary: createTextAttributes({ bold: true }), + secondary: createTextAttributes({ dim: true }), + success: createTextAttributes({ bold: true }), + warning: createTextAttributes({ bold: true, underline: true }), + danger: createTextAttributes({ bold: true, inverse: true }), + info: createTextAttributes({ italic: true }), + disabled: createTextAttributes({ dim: true, strikethrough: true }) +} +``` + +### Terminal Compatibility + +Not all terminals support all attributes: + +```typescript +// Most compatible +const basic = createTextAttributes({ + bold: true, // Widely supported + underline: true // Widely supported +}) + +// Less compatible +const advanced = createTextAttributes({ + italic: true, // Not all terminals + blink: true, // Often disabled + strikethrough: true // Limited support +}) + +// Check terminal capabilities +const supportsItalic = process.env.TERM_PROGRAM !== 'Apple_Terminal' +const attrs = createTextAttributes({ + bold: true, + italic: supportsItalic +}) +``` + +## Examples + +### Status Indicators + +```typescript +function getStatusAttributes(status: string) { + switch (status) { + case 'running': + return createTextAttributes({ bold: true, blink: true }) + case 'success': + return createTextAttributes({ bold: true }) + case 'error': + return createTextAttributes({ bold: true, inverse: true }) + case 'warning': + return createTextAttributes({ bold: true, underline: true }) + case 'disabled': + return createTextAttributes({ dim: true, strikethrough: true }) + default: + return createTextAttributes() + } +} +``` + +### Progressive Enhancement + +```typescript +// Start with basic styling +let attributes = createTextAttributes({ bold: true }) + +// Add enhancements based on context +if (isImportant) { + attributes |= TextAttributes.UNDERLINE +} + +if (isError) { + attributes |= TextAttributes.INVERSE +} + +if (isDeprecated) { + attributes |= TextAttributes.STRIKETHROUGH +} +``` + +## API Reference + +### Functions + +- `createTextAttributes(options?: TextAttributeOptions): number` + - Creates text attributes from an options object + - Returns a number with appropriate bits set + - All options default to false + +### Types + +```typescript +interface TextAttributeOptions { + bold?: boolean + italic?: boolean + underline?: boolean + dim?: boolean + blink?: boolean + inverse?: boolean + hidden?: boolean + strikethrough?: boolean +} +``` + +## Related Modules + +- [Types](./types.md) - TextAttributes enum definition +- [Components](./components.md) - Components that use attributes +- [Lib](./lib.md) - Additional text styling utilities +- [Rendering](./rendering.md) - How attributes are rendered \ No newline at end of file diff --git a/packages/core/docs/modules/zig.md b/packages/core/docs/modules/zig.md new file mode 100644 index 000000000..a34b14426 --- /dev/null +++ b/packages/core/docs/modules/zig.md @@ -0,0 +1,428 @@ +# Zig Native Module + +The Zig module provides high-performance native acceleration for OpenTUI through Foreign Function Interface (FFI) bindings, enabling fast terminal rendering and buffer operations. + +## Overview + +OpenTUI uses Zig-compiled native libraries for performance-critical operations. The module automatically loads platform-specific binaries and provides TypeScript interfaces to native functions. + +## Architecture + +### Platform Support + +Native binaries are provided for: +- **Darwin (macOS)**: x64, arm64 +- **Linux**: x64, arm64 +- **Windows**: x64, arm64 + +```typescript +// Automatic platform detection +const module = await import(`@opentui/core-${process.platform}-${process.arch}/index.ts`) +const targetLibPath = module.default + +// Verify platform support +if (!existsSync(targetLibPath)) { + throw new Error(`opentui is not supported on: ${process.platform}-${process.arch}`) +} +``` + +### FFI Library Loading + +The module uses Bun's FFI to load native functions: + +```typescript +import { dlopen } from "bun:ffi" + +const lib = dlopen(libPath, { + createRenderer: { + args: ["u32", "u32"], // width, height + returns: "ptr" // renderer pointer + }, + // ... more functions +}) +``` + +## Core Functions + +### Renderer Management + +Create and manage native renderers: + +```typescript +// Create renderer +createRenderer(width: number, height: number): Pointer | null + +// Destroy renderer +destroyRenderer( + renderer: Pointer, + useAlternateScreen: boolean, + splitHeight: number +): void + +// Configure threading +setUseThread(renderer: Pointer, useThread: boolean): void + +// Set background color +setBackgroundColor(renderer: Pointer, color: RGBA): void + +// Update render offset +setRenderOffset(renderer: Pointer, offset: number): void + +// Perform render +render(renderer: Pointer, force: boolean): void +``` + +### Buffer Operations + +Native buffer creation and manipulation: + +```typescript +// Create optimized buffer +createOptimizedBuffer( + width: number, + height: number, + respectAlpha?: boolean +): OptimizedBuffer + +// Destroy buffer +destroyOptimizedBuffer(bufferPtr: Pointer): void + +// Get buffer dimensions +getBufferWidth(buffer: Pointer): number +getBufferHeight(buffer: Pointer): number + +// Clear buffer +bufferClear(buffer: Pointer, color: RGBA): void + +// Get buffer data pointers +bufferGetCharPtr(buffer: Pointer): Pointer // Character array +bufferGetFgPtr(buffer: Pointer): Pointer // Foreground colors +bufferGetBgPtr(buffer: Pointer): Pointer // Background colors +bufferGetAttributesPtr(buffer: Pointer): Pointer // Text attributes +``` + +### Text Rendering + +Accelerated text drawing: + +```typescript +// Draw text to buffer +bufferDrawText( + buffer: Pointer, + text: string, + x: number, + y: number, + color: RGBA, + bgColor?: RGBA, + attributes?: number +): void + +// Set cell with alpha blending +bufferSetCellWithAlphaBlending( + buffer: Pointer, + x: number, + y: number, + char: string, + color: RGBA, + bgColor: RGBA, + attributes?: number +): void +``` + +### Box Drawing + +Native box rendering with borders: + +```typescript +bufferDrawBox( + buffer: Pointer, + x: number, + y: number, + width: number, + height: number, + borderSides: number, // Packed bitfield + borderStyle: Pointer, // Border character array + fg: RGBA, + bg: RGBA, + title: string | null, + titleAlignment: number // 0=left, 1=center, 2=right +): void +``` + +### Frame Buffer Operations + +Efficient buffer copying and blitting: + +```typescript +// Draw frame buffer to target +drawFrameBuffer( + targetBufferPtr: Pointer, + destX: number, + destY: number, + bufferPtr: Pointer, + sourceX?: number, + sourceY?: number, + sourceWidth?: number, + sourceHeight?: number +): void + +// Fill rectangle +bufferFillRect( + buffer: Pointer, + x: number, + y: number, + width: number, + height: number, + color: RGBA +): void +``` + +## TextBuffer Support + +Native text buffer for efficient text management: + +```typescript +// Create/destroy text buffer +createTextBuffer(capacity: number): Pointer +destroyTextBuffer(buffer: Pointer): void + +// Get data pointers +textBufferGetCharPtr(buffer: Pointer): Pointer +textBufferGetFgPtr(buffer: Pointer): Pointer +textBufferGetBgPtr(buffer: Pointer): Pointer +textBufferGetAttributesPtr(buffer: Pointer): Pointer + +// Buffer operations +textBufferGetLength(buffer: Pointer): number +textBufferGetCapacity(buffer: Pointer): number +textBufferResize(buffer: Pointer, newCapacity: number): void +textBufferReset(buffer: Pointer): void + +// Set cell data +textBufferSetCell( + buffer: Pointer, + index: number, + char: number, + fg: RGBA, + bg: RGBA, + attributes: number +): void + +// Concatenate buffers +textBufferConcat(buffer1: Pointer, buffer2: Pointer): Pointer + +// Selection support +textBufferSetSelection( + buffer: Pointer, + start: number, + end: number, + selectionFg: RGBA, + selectionBg: RGBA +): void +textBufferResetSelection(buffer: Pointer): void + +// Write chunks efficiently +textBufferWriteChunk( + buffer: Pointer, + chars: Pointer, + length: number, + fg: RGBA, + bg: RGBA, + attributes: Pointer +): number + +// Line information +textBufferFinalizeLineInfo(buffer: Pointer): void +textBufferGetLineStartsPtr(buffer: Pointer): Pointer +textBufferGetLineWidthsPtr(buffer: Pointer): Pointer +textBufferGetLineCount(buffer: Pointer): number + +// Draw to buffer +bufferDrawTextBuffer( + targetBuffer: Pointer, + textBuffer: Pointer, + x: number, + y: number, + scrollX: number, + scrollY: number, + viewWidth: number, + viewHeight: number, + wrap: boolean +): void +``` + +## Terminal Control + +Native terminal manipulation: + +```typescript +// Clear terminal screen +clearTerminal(renderer: Pointer): void + +// Cursor control +setCursorPosition(x: number, y: number, visible: boolean): void +setCursorStyle(renderer: Pointer, style: CursorStyle, visible: boolean): void +setCursorColor(color: RGBA): void + +// Mouse support +enableMouse(renderer: Pointer, enable: boolean): void +disableMouse(renderer: Pointer): void +``` + +## Hit Testing + +Pixel-perfect hit detection: + +```typescript +// Add hit region +addToHitGrid( + renderer: Pointer, + x: number, + y: number, + width: number, + height: number, + id: number +): void + +// Check hit at position +checkHit(renderer: Pointer, x: number, y: number): number + +// Debug hit grid +dumpHitGrid(renderer: Pointer): void +``` + +## Performance Monitoring + +Native performance statistics: + +```typescript +// Update render stats +updateStats( + renderer: Pointer, + time: number, + fps: number, + frameCallbackTime: number +): void + +// Update memory stats +updateMemoryStats( + renderer: Pointer, + heapUsed: number, + heapTotal: number, + arrayBuffers: number +): void +``` + +## Debug Features + +Development and debugging tools: + +```typescript +// Debug overlay +setDebugOverlay( + renderer: Pointer, + enabled: boolean, + corner: DebugOverlayCorner +): void + +// Dump buffers for debugging +dumpBuffers(renderer: Pointer, timestamp: bigint): void +dumpStdoutBuffer(renderer: Pointer, timestamp: bigint): void +``` + +## RenderLib Interface + +TypeScript interface wrapping native functions: + +```typescript +export interface RenderLib { + createRenderer: (width: number, height: number) => Pointer | null + destroyRenderer: (renderer: Pointer, useAlternateScreen: boolean, splitHeight: number) => void + setUseThread: (renderer: Pointer, useThread: boolean) => void + setBackgroundColor: (renderer: Pointer, color: RGBA) => void + render: (renderer: Pointer, force: boolean) => void + getNextBuffer: (renderer: Pointer) => OptimizedBuffer + getCurrentBuffer: (renderer: Pointer) => OptimizedBuffer + createOptimizedBuffer: (width: number, height: number, respectAlpha?: boolean) => OptimizedBuffer + // ... and many more +} +``` + +## Usage Example + +```typescript +import { resolveRenderLib } from '@opentui/core/zig' +import { RGBA } from '@opentui/core' + +// Get native library +const lib = resolveRenderLib() + +// Create renderer +const renderer = lib.createRenderer(80, 24) + +// Create buffer +const buffer = lib.createOptimizedBuffer(80, 24, true) + +// Draw text +lib.bufferDrawText( + buffer.ptr, + "Hello, Native!", + 10, 5, + RGBA.fromValues(1, 1, 1, 1), + RGBA.fromValues(0, 0, 0, 1), + 0 +) + +// Render +lib.render(renderer, false) + +// Cleanup +lib.destroyOptimizedBuffer(buffer.ptr) +lib.destroyRenderer(renderer, false, 0) +``` + +## Building Native Libraries + +To rebuild the native libraries: + +```bash +# Development build (debug symbols) +bun run build:dev + +# Production build (optimized) +bun run build:prod + +# Build for all platforms +bun run build:all +``` + +Requirements: +- Zig 0.14.0-0.14.1 +- Bun runtime + +## Performance Benefits + +Native acceleration provides: +- **10-100x faster** buffer operations vs pure JavaScript +- **Minimal GC pressure** through direct memory management +- **SIMD optimizations** where available +- **Efficient text rendering** with native string handling +- **Zero-copy operations** for buffer transfers + +## API Reference + +### Exported Functions + +- `getOpenTUILib(libPath?: string)` - Load FFI library +- `resolveRenderLib()` - Get singleton RenderLib instance + +### Types + +- `RenderLib` - TypeScript interface for native functions +- `Pointer` - Native memory pointer type from Bun FFI + +## Related Modules + +- [Buffer](./buffer.md) - OptimizedBuffer that uses native acceleration +- [Text Buffer](./text-buffer.md) - TextBuffer with native support +- [Rendering](./rendering.md) - Renderer using native functions \ No newline at end of file diff --git a/packages/core/src/intellisense.d.ts b/packages/core/src/intellisense.d.ts new file mode 100644 index 000000000..be05615c4 --- /dev/null +++ b/packages/core/src/intellisense.d.ts @@ -0,0 +1,346 @@ +/** + * OpenTUI IntelliSense Definitions + * + * This file provides comprehensive type definitions and IntelliSense support + * for OpenTUI components and APIs. + * + * @packageDocumentation + */ + +/// + +declare module '@opentui/core' { + // Re-export all types + export * from './types'; + + // Component Classes + export class Renderable { + constructor(id: string, options?: RenderableOptions); + + /** Unique identifier */ + id: string; + + /** X position */ + x: number; + + /** Y position */ + y: number; + + /** Width */ + width: number; + + /** Height */ + height: number; + + /** Visibility state */ + visible: boolean; + + /** Focus state */ + focused: boolean; + + /** Parent component */ + parent: Renderable | null; + + /** Child components */ + children: Renderable[]; + + // Methods + add(child: Renderable, index?: number): number; + remove(id: string): void; + focus(): void; + blur(): void; + needsUpdate(): void; + destroy(): void; + handleKeyPress(key: ParsedKey | string): boolean; + getSelectedText(): string; + hasSelection(): boolean; + } + + export class BoxRenderable extends Renderable { + constructor(id: string, options?: BoxOptions); + + /** Border visibility */ + border: boolean | [boolean, boolean, boolean, boolean]; + + /** Border style */ + borderStyle: BorderStyle; + + /** Box title */ + title?: string; + + /** Padding */ + padding: number | { top: number; right: number; bottom: number; left: number }; + + // Methods + setBorderStyle(style: BorderStyle): void; + setTitle(title: string): void; + setPadding(padding: number | { top: number; right: number; bottom: number; left: number }): void; + showBorder(show: boolean): void; + } + + export class TextRenderable extends Renderable { + constructor(id: string, options?: TextOptions); + + /** Text content */ + text: string; + + /** Text color */ + color: string | RGBA; + + /** Background color */ + backgroundColor?: string | RGBA; + + /** Text alignment */ + align: 'left' | 'center' | 'right'; + + /** Word wrap enabled */ + wrap: boolean; + + /** Text is selectable */ + selectable: boolean; + + // Methods + setText(text: string): void; + setColor(color: string | RGBA): void; + setAlign(align: 'left' | 'center' | 'right'): void; + } + + export class InputRenderable extends Renderable { + constructor(id: string, options?: InputRenderableOptions); + + /** Current value */ + value: string; + + /** Placeholder text */ + placeholder?: string; + + /** Maximum length */ + maxLength?: number; + + /** Password mode */ + password?: boolean; + + /** Cursor position */ + cursorPosition: number; + + // Methods + setValue(value: string): void; + clear(): void; + selectAll(): void; + setCursorPosition(position: number): void; + insertText(text: string): void; + deleteSelection(): void; + validate(): boolean; + } + + export class ASCIIFontRenderable extends Renderable { + constructor(id: string, options?: ASCIIFontOptions); + + /** Display text */ + text: string; + + /** Font name or definition */ + font: string | FontDefinition; + + /** Text color */ + color: string | RGBA; + + // Methods + setText(text: string): void; + setFont(font: string | FontDefinition): void; + static registerFont(name: string, definition: FontDefinition): void; + } + + export class CliRenderer { + constructor( + lib: any, + ptr: any, + stdin: NodeJS.ReadStream, + stdout: NodeJS.WriteStream, + width: number, + height: number, + config?: CliRendererConfig + ); + + /** Terminal width */ + width: number; + + /** Terminal height */ + height: number; + + /** Root component */ + root: RootRenderable; + + // Methods + start(): void; + stop(): void; + resize(width: number, height: number): void; + needsUpdate(): void; + toggleDebugOverlay(): void; + setBackgroundColor(color: string | RGBA): void; + } + + export class Timeline { + constructor(options?: TimelineOptions); + + /** Timeline duration */ + duration: number; + + /** Loop enabled */ + loop: boolean; + + /** Playing state */ + isPlaying: boolean; + + /** Current time */ + currentTime: number; + + // Methods + add(animation: AnimationOptions): Timeline; + play(): Promise; + pause(): void; + stop(): void; + seek(time: number): void; + remove(animation: AnimationOptions): void; + } + + export class OptimizedBuffer { + constructor(width: number, height: number); + + /** Buffer width */ + width: number; + + /** Buffer height */ + height: number; + + // Methods + drawText(text: string, x: number, y: number, fg?: RGBA, bg?: RGBA): void; + drawBox(x: number, y: number, width: number, height: number, options?: BoxDrawOptions): void; + setCell(x: number, y: number, char: string, fg?: RGBA, bg?: RGBA): void; + clear(x?: number, y?: number, width?: number, height?: number): void; + copyFrom(source: OptimizedBuffer, x: number, y: number): void; + markDirty(x: number, y: number, width: number, height: number): void; + getDirtyRegion(): { x: number; y: number; width: number; height: number } | null; + } + + // Helper Types + export interface ParsedKey { + name: string; + ctrl: boolean; + meta: boolean; + shift: boolean; + alt: boolean; + sequence?: string; + code?: string; + } + + export interface MouseEvent { + type: MouseEventType; + button: number; + x: number; + y: number; + source: Renderable; + target: Renderable | null; + modifiers: { + shift: boolean; + alt: boolean; + ctrl: boolean; + }; + scroll?: { + direction: 'up' | 'down'; + delta: number; + }; + preventDefault(): void; + } + + export interface FontDefinition { + height: number; + chars: { + [char: string]: string[]; + }; + kerning?: { + [pair: string]: number; + }; + } + + export interface RGBA { + r: number; + g: number; + b: number; + a: number; + + static fromHex(hex: string): RGBA; + static fromValues(r: number, g: number, b: number, a: number): RGBA; + static fromHSL(h: number, s: number, l: number, a?: number): RGBA; + static white(): RGBA; + static black(): RGBA; + static transparent(): RGBA; + static blend(from: RGBA, to: RGBA, alpha: number): RGBA; + } + + // Enums + export type MouseEventType = + | 'down' + | 'up' + | 'move' + | 'drag' + | 'drag-end' + | 'drop' + | 'over' + | 'out' + | 'scroll'; + + export type BorderStyle = 'single' | 'double' | 'rounded' | 'heavy'; + + export type AlignString = 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline'; + + export type JustifyString = 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly'; + + export type FlexDirectionString = 'row' | 'column' | 'row-reverse' | 'column-reverse'; + + export type PositionTypeString = 'relative' | 'absolute'; + + // Easing Functions + export const Easing: { + linear: (t: number) => number; + easeInQuad: (t: number) => number; + easeOutQuad: (t: number) => number; + easeInOutQuad: (t: number) => number; + easeInCubic: (t: number) => number; + easeOutCubic: (t: number) => number; + easeInOutCubic: (t: number) => number; + easeInExpo: (t: number) => number; + easeOutExpo: (t: number) => number; + easeInOutExpo: (t: number) => number; + easeInBounce: (t: number) => number; + easeOutBounce: (t: number) => number; + easeInOutBounce: (t: number) => number; + easeInElastic: (t: number) => number; + easeOutElastic: (t: number) => number; + easeInOutElastic: (t: number) => number; + easeInBack: (t: number) => number; + easeOutBack: (t: number) => number; + easeInOutBack: (t: number) => number; + }; + + // Debug + export enum DebugOverlayCorner { + TOP_LEFT = 'top-left', + TOP_RIGHT = 'top-right', + BOTTOM_LEFT = 'bottom-left', + BOTTOM_RIGHT = 'bottom-right' + } +} + +// Global type augmentations for better IntelliSense +declare global { + namespace NodeJS { + interface ProcessEnv { + OPENTUI_DEBUG?: string; + OPENTUI_THEME?: string; + OPENTUI_RENDERER?: string; + } + } +} + +export {}; \ No newline at end of file diff --git a/packages/core/src/types/ASCIIFontOptions.d.ts b/packages/core/src/types/ASCIIFontOptions.d.ts new file mode 100644 index 000000000..ea4e5d156 --- /dev/null +++ b/packages/core/src/types/ASCIIFontOptions.d.ts @@ -0,0 +1,136 @@ +/** + * ASCIIFontOptions configuration options + * + * @public + * @category Configuration + */ +export interface ASCIIFontOptions { + alignItems?: AlignString; + + bg?: RGBA; + + bottom?: number | string | string; + + buffered?: boolean; + + enableLayout?: boolean; + + fg?: RGBA | RGBA[]; + + flexBasis?: number | string; + + flexDirection?: FlexDirectionString; + + flexGrow?: number; + + flexShrink?: number; + + font?: 'tiny' | 'block' | 'shade' | 'slick'; + + height?: number | string | string; + + justifyContent?: JustifyString; + + left?: number | string | string; + + live?: boolean; + + margin?: number | string | string; + + marginBottom?: number | string | string; + + marginLeft?: number | string | string; + + marginRight?: number | string | string; + + marginTop?: number | string | string; + + maxHeight?: number; + + maxWidth?: number; + + minHeight?: number; + + minWidth?: number; + + /** + * (key: ParsedKey) => void + */ + onKeyDown?: { namedArgs: { key: ParsedKey } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDown?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDrag?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDragEnd?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDrop?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseMove?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseOut?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseOver?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseScroll?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseUp?: { namedArgs: { event: MouseEvent } }; + + padding?: any; + + paddingBottom?: any; + + paddingLeft?: any; + + paddingRight?: any; + + paddingTop?: any; + + position?: PositionTypeString; + + right?: number | string | string; + + selectable?: boolean; + + selectionBg?: string | RGBA; + + selectionFg?: string | RGBA; + + text?: string; + + top?: number | string | string; + + visible?: boolean; + + width?: number | string | string; + + zIndex?: number; + +} diff --git a/packages/core/src/types/AnimationOptions.d.ts b/packages/core/src/types/AnimationOptions.d.ts new file mode 100644 index 000000000..fa799be3b --- /dev/null +++ b/packages/core/src/types/AnimationOptions.d.ts @@ -0,0 +1,40 @@ +/** + * AnimationOptions configuration options + * + * @public + * @category Configuration + */ +export interface AnimationOptions { + alternate?: boolean; + + duration: number; + + ease?: EasingFunctions; + + loop?: any; + + loopDelay?: number; + + /** + * () => void + */ + onComplete?: any; + + /** + * () => void + */ + onLoop?: any; + + /** + * () => void + */ + onStart?: any; + + /** + * (animation: JSAnimation) => void + */ + onUpdate?: { namedArgs: { animation: JSAnimation } }; + + once?: boolean; + +} diff --git a/packages/core/src/types/BorderConfig.d.ts b/packages/core/src/types/BorderConfig.d.ts new file mode 100644 index 000000000..8734ada75 --- /dev/null +++ b/packages/core/src/types/BorderConfig.d.ts @@ -0,0 +1,16 @@ +/** + * BorderConfig configuration options + * + * @public + * @category Configuration + */ +export interface BorderConfig { + border: boolean | BorderSides[]; + + borderColor?: ColorInput; + + borderStyle: BorderStyle; + + customBorderChars?: BorderCharacters; + +} diff --git a/packages/core/src/types/BoxDrawOptions.d.ts b/packages/core/src/types/BoxDrawOptions.d.ts new file mode 100644 index 000000000..41f18aade --- /dev/null +++ b/packages/core/src/types/BoxDrawOptions.d.ts @@ -0,0 +1,32 @@ +/** + * BoxDrawOptions configuration options + * + * @public + * @category Configuration + */ +export interface BoxDrawOptions { + backgroundColor: ColorInput; + + border: boolean | BorderSides[]; + + borderColor: ColorInput; + + borderStyle: BorderStyle; + + customBorderChars?: BorderCharacters; + + height: number; + + shouldFill?: boolean; + + title?: string; + + titleAlignment?: 'left' | 'center' | 'right'; + + width: number; + + x: number; + + y: number; + +} diff --git a/packages/core/src/types/BoxOptions.d.ts b/packages/core/src/types/BoxOptions.d.ts new file mode 100644 index 000000000..f70301ddd --- /dev/null +++ b/packages/core/src/types/BoxOptions.d.ts @@ -0,0 +1,140 @@ +/** + * BoxOptions configuration options + * + * @public + * @category Configuration + */ +export interface BoxOptions { + alignItems?: AlignString; + + backgroundColor?: string | RGBA; + + border?: boolean | BorderSides[]; + + borderColor?: string | RGBA; + + borderStyle?: BorderStyle; + + bottom?: number | string | string; + + buffered?: boolean; + + customBorderChars?: BorderCharacters; + + enableLayout?: boolean; + + flexBasis?: number | string; + + flexDirection?: FlexDirectionString; + + flexGrow?: number; + + flexShrink?: number; + + focusedBorderColor?: ColorInput; + + height?: number | string | string; + + justifyContent?: JustifyString; + + left?: number | string | string; + + live?: boolean; + + margin?: number | string | string; + + marginBottom?: number | string | string; + + marginLeft?: number | string | string; + + marginRight?: number | string | string; + + marginTop?: number | string | string; + + maxHeight?: number; + + maxWidth?: number; + + minHeight?: number; + + minWidth?: number; + + /** + * (key: ParsedKey) => void + */ + onKeyDown?: { namedArgs: { key: ParsedKey } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDown?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDrag?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDragEnd?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDrop?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseMove?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseOut?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseOver?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseScroll?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseUp?: { namedArgs: { event: MouseEvent } }; + + padding?: any; + + paddingBottom?: any; + + paddingLeft?: any; + + paddingRight?: any; + + paddingTop?: any; + + position?: PositionTypeString; + + right?: number | string | string; + + shouldFill?: boolean; + + title?: string; + + titleAlignment?: 'left' | 'center' | 'right'; + + top?: number | string | string; + + visible?: boolean; + + width?: number | string | string; + + zIndex?: number; + +} diff --git a/packages/core/src/types/CliRendererConfig.d.ts b/packages/core/src/types/CliRendererConfig.d.ts new file mode 100644 index 000000000..8495d3bfd --- /dev/null +++ b/packages/core/src/types/CliRendererConfig.d.ts @@ -0,0 +1,40 @@ +/** + * CliRendererConfig configuration options + * + * @public + * @category Configuration + */ +export interface CliRendererConfig { + consoleOptions?: ConsoleOptions; + + debounceDelay?: number; + + enableMouseMovement?: boolean; + + exitOnCtrlC?: boolean; + + experimental_splitHeight?: number; + + gatherStats?: boolean; + + maxStatSamples?: number; + + memorySnapshotInterval?: number; + + postProcessFns?: { namedArgs: { buffer: OptimizedBuffer; deltaTime: number } }[]; + + stdin?: global.NodeJS.ReadStream; + + stdout?: global.NodeJS.WriteStream; + + targetFps?: number; + + useAlternateScreen?: boolean; + + useConsole?: boolean; + + useMouse?: boolean; + + useThread?: boolean; + +} diff --git a/packages/core/src/types/ConsoleOptions.d.ts b/packages/core/src/types/ConsoleOptions.d.ts new file mode 100644 index 000000000..71f056eb1 --- /dev/null +++ b/packages/core/src/types/ConsoleOptions.d.ts @@ -0,0 +1,40 @@ +/** + * ConsoleOptions configuration options + * + * @public + * @category Configuration + */ +export interface ConsoleOptions { + backgroundColor?: ColorInput; + + colorDebug?: ColorInput; + + colorDefault?: ColorInput; + + colorError?: ColorInput; + + colorInfo?: ColorInput; + + colorWarn?: ColorInput; + + cursorColor?: ColorInput; + + maxDisplayLines?: number; + + maxStoredLogs?: number; + + position?: ConsolePosition; + + sizePercent?: number; + + startInDebugMode?: boolean; + + title?: string; + + titleBarColor?: ColorInput; + + titleBarTextColor?: ColorInput; + + zIndex?: number; + +} diff --git a/packages/core/src/types/ExplosionEffectParameters.d.ts b/packages/core/src/types/ExplosionEffectParameters.d.ts new file mode 100644 index 000000000..c58293997 --- /dev/null +++ b/packages/core/src/types/ExplosionEffectParameters.d.ts @@ -0,0 +1,37 @@ +/** + * ExplosionEffectParameters configuration options + * + * @public + * @category Configuration + */ +export interface ExplosionEffectParameters { + angularVelocityMax: Vector3; + + angularVelocityMin: Vector3; + + durationMs: number; + + fadeOut: boolean; + + gravity: number; + + gravityScale: number; + + initialVelocityYBoost: number; + + /** + * () => NodeMaterial + */ + materialFactory: any; + + numCols: number; + + numRows: number; + + strength: number; + + strengthVariation: number; + + zVariationStrength: number; + +} diff --git a/packages/core/src/types/FrameBufferOptions.d.ts b/packages/core/src/types/FrameBufferOptions.d.ts new file mode 100644 index 000000000..5add94fde --- /dev/null +++ b/packages/core/src/types/FrameBufferOptions.d.ts @@ -0,0 +1,124 @@ +/** + * FrameBufferOptions configuration options + * + * @public + * @category Configuration + */ +export interface FrameBufferOptions { + alignItems?: AlignString; + + bottom?: number | string | string; + + buffered?: boolean; + + enableLayout?: boolean; + + flexBasis?: number | string; + + flexDirection?: FlexDirectionString; + + flexGrow?: number; + + flexShrink?: number; + + height: number; + + justifyContent?: JustifyString; + + left?: number | string | string; + + live?: boolean; + + margin?: number | string | string; + + marginBottom?: number | string | string; + + marginLeft?: number | string | string; + + marginRight?: number | string | string; + + marginTop?: number | string | string; + + maxHeight?: number; + + maxWidth?: number; + + minHeight?: number; + + minWidth?: number; + + /** + * (key: ParsedKey) => void + */ + onKeyDown?: { namedArgs: { key: ParsedKey } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDown?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDrag?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDragEnd?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDrop?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseMove?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseOut?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseOver?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseScroll?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseUp?: { namedArgs: { event: MouseEvent } }; + + padding?: any; + + paddingBottom?: any; + + paddingLeft?: any; + + paddingRight?: any; + + paddingTop?: any; + + position?: PositionTypeString; + + respectAlpha?: boolean; + + right?: number | string | string; + + top?: number | string | string; + + visible?: boolean; + + width: number; + + zIndex?: number; + +} diff --git a/packages/core/src/types/InputRenderableOptions.d.ts b/packages/core/src/types/InputRenderableOptions.d.ts new file mode 100644 index 000000000..fcc476cf7 --- /dev/null +++ b/packages/core/src/types/InputRenderableOptions.d.ts @@ -0,0 +1,140 @@ +/** + * InputRenderableOptions configuration options + * + * @public + * @category Configuration + */ +export interface InputRenderableOptions { + alignItems?: AlignString; + + backgroundColor?: ColorInput; + + bottom?: number | string | string; + + buffered?: boolean; + + cursorColor?: ColorInput; + + enableLayout?: boolean; + + flexBasis?: number | string; + + flexDirection?: FlexDirectionString; + + flexGrow?: number; + + flexShrink?: number; + + focusedBackgroundColor?: ColorInput; + + focusedTextColor?: ColorInput; + + height?: number | string | string; + + justifyContent?: JustifyString; + + left?: number | string | string; + + live?: boolean; + + margin?: number | string | string; + + marginBottom?: number | string | string; + + marginLeft?: number | string | string; + + marginRight?: number | string | string; + + marginTop?: number | string | string; + + maxHeight?: number; + + maxLength?: number; + + maxWidth?: number; + + minHeight?: number; + + minWidth?: number; + + /** + * (key: ParsedKey) => void + */ + onKeyDown?: { namedArgs: { key: ParsedKey } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDown?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDrag?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDragEnd?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDrop?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseMove?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseOut?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseOver?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseScroll?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseUp?: { namedArgs: { event: MouseEvent } }; + + padding?: any; + + paddingBottom?: any; + + paddingLeft?: any; + + paddingRight?: any; + + paddingTop?: any; + + placeholder?: string; + + placeholderColor?: ColorInput; + + position?: PositionTypeString; + + right?: number | string | string; + + textColor?: ColorInput; + + top?: number | string | string; + + value?: string; + + visible?: boolean; + + width?: number | string | string; + + zIndex?: number; + +} diff --git a/packages/core/src/types/LayoutOptions.d.ts b/packages/core/src/types/LayoutOptions.d.ts new file mode 100644 index 000000000..9300858df --- /dev/null +++ b/packages/core/src/types/LayoutOptions.d.ts @@ -0,0 +1,60 @@ +/** + * LayoutOptions configuration options + * + * @public + * @category Configuration + */ +export interface LayoutOptions { + alignItems?: AlignString; + + bottom?: number | string | string; + + enableLayout?: boolean; + + flexBasis?: number | string; + + flexDirection?: FlexDirectionString; + + flexGrow?: number; + + flexShrink?: number; + + justifyContent?: JustifyString; + + left?: number | string | string; + + margin?: number | string | string; + + marginBottom?: number | string | string; + + marginLeft?: number | string | string; + + marginRight?: number | string | string; + + marginTop?: number | string | string; + + maxHeight?: number; + + maxWidth?: number; + + minHeight?: number; + + minWidth?: number; + + padding?: any; + + paddingBottom?: any; + + paddingLeft?: any; + + paddingRight?: any; + + paddingTop?: any; + + position?: PositionTypeString; + + right?: number | string | string; + + top?: number | string | string; + +} diff --git a/packages/core/src/types/RenderableOptions.d.ts b/packages/core/src/types/RenderableOptions.d.ts new file mode 100644 index 000000000..2f26cdb9a --- /dev/null +++ b/packages/core/src/types/RenderableOptions.d.ts @@ -0,0 +1,122 @@ +/** + * RenderableOptions configuration options + * + * @public + * @category Configuration + */ +export interface RenderableOptions { + alignItems?: AlignString; + + bottom?: number | string | string; + + buffered?: boolean; + + enableLayout?: boolean; + + flexBasis?: number | string; + + flexDirection?: FlexDirectionString; + + flexGrow?: number; + + flexShrink?: number; + + height?: number | string | string; + + justifyContent?: JustifyString; + + left?: number | string | string; + + live?: boolean; + + margin?: number | string | string; + + marginBottom?: number | string | string; + + marginLeft?: number | string | string; + + marginRight?: number | string | string; + + marginTop?: number | string | string; + + maxHeight?: number; + + maxWidth?: number; + + minHeight?: number; + + minWidth?: number; + + /** + * (key: ParsedKey) => void + */ + onKeyDown?: { namedArgs: { key: ParsedKey } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDown?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDrag?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDragEnd?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDrop?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseMove?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseOut?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseOver?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseScroll?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseUp?: { namedArgs: { event: MouseEvent } }; + + padding?: any; + + paddingBottom?: any; + + paddingLeft?: any; + + paddingRight?: any; + + paddingTop?: any; + + position?: PositionTypeString; + + right?: number | string | string; + + top?: number | string | string; + + visible?: boolean; + + width?: number | string | string; + + zIndex?: number; + +} diff --git a/packages/core/src/types/SelectRenderableOptions.d.ts b/packages/core/src/types/SelectRenderableOptions.d.ts new file mode 100644 index 000000000..36d9c076c --- /dev/null +++ b/packages/core/src/types/SelectRenderableOptions.d.ts @@ -0,0 +1,152 @@ +/** + * SelectRenderableOptions configuration options + * + * @public + * @category Configuration + */ +export interface SelectRenderableOptions { + alignItems?: AlignString; + + backgroundColor?: ColorInput; + + bottom?: number | string | string; + + buffered?: boolean; + + descriptionColor?: ColorInput; + + enableLayout?: boolean; + + fastScrollStep?: number; + + flexBasis?: number | string; + + flexDirection?: FlexDirectionString; + + flexGrow?: number; + + flexShrink?: number; + + focusedBackgroundColor?: ColorInput; + + focusedTextColor?: ColorInput; + + font?: 'tiny' | 'block' | 'shade' | 'slick'; + + height?: number | string | string; + + itemSpacing?: number; + + justifyContent?: JustifyString; + + left?: number | string | string; + + live?: boolean; + + margin?: number | string | string; + + marginBottom?: number | string | string; + + marginLeft?: number | string | string; + + marginRight?: number | string | string; + + marginTop?: number | string | string; + + maxHeight?: number; + + maxWidth?: number; + + minHeight?: number; + + minWidth?: number; + + /** + * (key: ParsedKey) => void + */ + onKeyDown?: { namedArgs: { key: ParsedKey } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDown?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDrag?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDragEnd?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDrop?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseMove?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseOut?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseOver?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseScroll?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseUp?: { namedArgs: { event: MouseEvent } }; + + options?: SelectOption[]; + + padding?: any; + + paddingBottom?: any; + + paddingLeft?: any; + + paddingRight?: any; + + paddingTop?: any; + + position?: PositionTypeString; + + right?: number | string | string; + + selectedBackgroundColor?: ColorInput; + + selectedDescriptionColor?: ColorInput; + + selectedTextColor?: ColorInput; + + showDescription?: boolean; + + showScrollIndicator?: boolean; + + textColor?: ColorInput; + + top?: number | string | string; + + visible?: boolean; + + width?: number | string | string; + + wrapSelection?: boolean; + + zIndex?: number; + +} diff --git a/packages/core/src/types/TabSelectRenderableOptions.d.ts b/packages/core/src/types/TabSelectRenderableOptions.d.ts new file mode 100644 index 000000000..056bff838 --- /dev/null +++ b/packages/core/src/types/TabSelectRenderableOptions.d.ts @@ -0,0 +1,148 @@ +/** + * TabSelectRenderableOptions configuration options + * + * @public + * @category Configuration + */ +export interface TabSelectRenderableOptions { + alignItems?: AlignString; + + backgroundColor?: ColorInput; + + bottom?: number | string | string; + + buffered?: boolean; + + enableLayout?: boolean; + + flexBasis?: number | string; + + flexDirection?: FlexDirectionString; + + flexGrow?: number; + + flexShrink?: number; + + focusedBackgroundColor?: ColorInput; + + focusedTextColor?: ColorInput; + + height?: number; + + justifyContent?: JustifyString; + + left?: number | string | string; + + live?: boolean; + + margin?: number | string | string; + + marginBottom?: number | string | string; + + marginLeft?: number | string | string; + + marginRight?: number | string | string; + + marginTop?: number | string | string; + + maxHeight?: number; + + maxWidth?: number; + + minHeight?: number; + + minWidth?: number; + + /** + * (key: ParsedKey) => void + */ + onKeyDown?: { namedArgs: { key: ParsedKey } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDown?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDrag?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDragEnd?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDrop?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseMove?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseOut?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseOver?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseScroll?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseUp?: { namedArgs: { event: MouseEvent } }; + + options?: TabSelectOption[]; + + padding?: any; + + paddingBottom?: any; + + paddingLeft?: any; + + paddingRight?: any; + + paddingTop?: any; + + position?: PositionTypeString; + + right?: number | string | string; + + selectedBackgroundColor?: ColorInput; + + selectedDescriptionColor?: ColorInput; + + selectedTextColor?: ColorInput; + + showDescription?: boolean; + + showScrollArrows?: boolean; + + showUnderline?: boolean; + + tabWidth?: number; + + textColor?: ColorInput; + + top?: number | string | string; + + visible?: boolean; + + width?: number | string | string; + + wrapSelection?: boolean; + + zIndex?: number; + +} diff --git a/packages/core/src/types/TextOptions.d.ts b/packages/core/src/types/TextOptions.d.ts new file mode 100644 index 000000000..39580b991 --- /dev/null +++ b/packages/core/src/types/TextOptions.d.ts @@ -0,0 +1,136 @@ +/** + * TextOptions configuration options + * + * @public + * @category Configuration + */ +export interface TextOptions { + alignItems?: AlignString; + + attributes?: number; + + bg?: string | RGBA; + + bottom?: number | string | string; + + buffered?: boolean; + + content?: StyledText | string; + + enableLayout?: boolean; + + fg?: string | RGBA; + + flexBasis?: number | string; + + flexDirection?: FlexDirectionString; + + flexGrow?: number; + + flexShrink?: number; + + height?: number | string | string; + + justifyContent?: JustifyString; + + left?: number | string | string; + + live?: boolean; + + margin?: number | string | string; + + marginBottom?: number | string | string; + + marginLeft?: number | string | string; + + marginRight?: number | string | string; + + marginTop?: number | string | string; + + maxHeight?: number; + + maxWidth?: number; + + minHeight?: number; + + minWidth?: number; + + /** + * (key: ParsedKey) => void + */ + onKeyDown?: { namedArgs: { key: ParsedKey } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDown?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDrag?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDragEnd?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseDrop?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseMove?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseOut?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseOver?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseScroll?: { namedArgs: { event: MouseEvent } }; + + /** + * (event: MouseEvent) => void + */ + onMouseUp?: { namedArgs: { event: MouseEvent } }; + + padding?: any; + + paddingBottom?: any; + + paddingLeft?: any; + + paddingRight?: any; + + paddingTop?: any; + + position?: PositionTypeString; + + right?: number | string | string; + + selectable?: boolean; + + selectionBg?: string | RGBA; + + selectionFg?: string | RGBA; + + top?: number | string | string; + + visible?: boolean; + + width?: number | string | string; + + zIndex?: number; + +} diff --git a/packages/core/src/types/ThreeCliRendererOptions.d.ts b/packages/core/src/types/ThreeCliRendererOptions.d.ts new file mode 100644 index 000000000..69d66e8c7 --- /dev/null +++ b/packages/core/src/types/ThreeCliRendererOptions.d.ts @@ -0,0 +1,24 @@ +/** + * ThreeCliRendererOptions configuration options + * + * @public + * @category Configuration + */ +export interface ThreeCliRendererOptions { + alpha?: boolean; + + autoResize?: boolean; + + backgroundColor?: RGBA; + + focalLength?: number; + + height: number; + + libPath?: string; + + superSample?: SuperSampleType; + + width: number; + +} diff --git a/packages/core/src/types/TimelineOptions.d.ts b/packages/core/src/types/TimelineOptions.d.ts new file mode 100644 index 000000000..5c93d3c78 --- /dev/null +++ b/packages/core/src/types/TimelineOptions.d.ts @@ -0,0 +1,24 @@ +/** + * TimelineOptions configuration options + * + * @public + * @category Configuration + */ +export interface TimelineOptions { + autoplay?: boolean; + + duration?: number; + + loop?: boolean; + + /** + * () => void + */ + onComplete?: any; + + /** + * () => void + */ + onPause?: any; + +} diff --git a/packages/core/src/types/index.d.ts b/packages/core/src/types/index.d.ts new file mode 100644 index 000000000..0da0f9a04 --- /dev/null +++ b/packages/core/src/types/index.d.ts @@ -0,0 +1,78 @@ +/** + * OpenTUI Type Definitions + * + * This module exports all configuration interfaces and types used by OpenTUI components. + * + * @module @opentui/core/types + * @packageDocumentation + */ + +// Component Options +export type { ASCIIFontOptions } from './ASCIIFontOptions'; +export type { AnimationOptions } from './AnimationOptions'; +export type { BorderConfig } from './BorderConfig'; +export type { BoxDrawOptions } from './BoxDrawOptions'; +export type { BoxOptions } from './BoxOptions'; +export type { CliRendererConfig } from './CliRendererConfig'; +export type { ConsoleOptions } from './ConsoleOptions'; +export type { ExplosionEffectParameters } from './ExplosionEffectParameters'; +export type { FrameBufferOptions } from './FrameBufferOptions'; +export type { InputRenderableOptions } from './InputRenderableOptions'; +export type { LayoutOptions } from './LayoutOptions'; +export type { RenderableOptions } from './RenderableOptions'; +export type { SelectRenderableOptions } from './SelectRenderableOptions'; +export type { TabSelectRenderableOptions } from './TabSelectRenderableOptions'; +export type { TextOptions } from './TextOptions'; +export type { ThreeCliRendererOptions } from './ThreeCliRendererOptions'; +export type { TimelineOptions } from './TimelineOptions'; + +// Re-export commonly used types +export type { + BoxOptions, + TextOptions, + InputRenderableOptions, + ASCIIFontOptions, + AnimationOptions, + TimelineOptions, + CliRendererConfig +} from './index'; + +/** + * Common color input type + */ +export type ColorInput = string | RGBA; + +/** + * RGBA color representation + */ +export interface RGBA { + r: number; + g: number; + b: number; + a: number; +} + +/** + * Border style options + */ +export type BorderStyle = 'single' | 'double' | 'rounded' | 'heavy'; + +/** + * Flexbox alignment options + */ +export type AlignString = 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline'; + +/** + * Flexbox justification options + */ +export type JustifyString = 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly'; + +/** + * Flexbox direction options + */ +export type FlexDirectionString = 'row' | 'column' | 'row-reverse' | 'column-reverse'; + +/** + * Position type options + */ +export type PositionTypeString = 'relative' | 'absolute'; diff --git a/packages/core/src/types/package.json b/packages/core/src/types/package.json new file mode 100644 index 000000000..279a1a58d --- /dev/null +++ b/packages/core/src/types/package.json @@ -0,0 +1,5 @@ +{ + "name": "@opentui/core/types", + "types": "./index.d.ts", + "description": "TypeScript type definitions for OpenTUI" +} \ No newline at end of file diff --git a/scripts/convert-schema-to-jsdoc.js b/scripts/convert-schema-to-jsdoc.js deleted file mode 100644 index 0db2791fe..000000000 --- a/scripts/convert-schema-to-jsdoc.js +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const path = require('path'); -const { execSync } = require('child_process'); - -// First, let's test with a single schema file -const schemaPath = path.join(__dirname, '../packages/core/docs/api/schemas/ASCIIFontOptions.json'); -const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8')); - -// Since json-schema-to-jsdoc is a library, let's create a simple converter -function schemaToJSDoc(schema, depth = 0) { - const indent = ' '.repeat(depth * 2); - let jsdoc = []; - - if (depth === 0) { - jsdoc.push('/**'); - if (schema.description) { - jsdoc.push(` * ${schema.description}`); - } - if (schema.$ref) { - const typeName = schema.$ref.split('/').pop(); - jsdoc.push(` * @typedef {Object} ${typeName}`); - } - } - - if (schema.definitions) { - Object.entries(schema.definitions).forEach(([name, def]) => { - jsdoc.push('/**'); - if (def.description) { - jsdoc.push(` * ${def.description}`); - } - jsdoc.push(` * @typedef {Object} ${name}`); - - if (def.properties) { - Object.entries(def.properties).forEach(([propName, prop]) => { - let type = 'any'; - if (prop.type) { - type = prop.type === 'integer' ? 'number' : prop.type; - } else if (prop.$ref) { - type = prop.$ref.split('/').pop(); - } else if (prop.anyOf) { - type = prop.anyOf.map(t => { - if (t.type) return t.type; - if (t.$ref) return t.$ref.split('/').pop(); - if (t.const) return `"${t.const}"`; - return 'any'; - }).join('|'); - } else if (prop.enum) { - type = prop.enum.map(v => `"${v}"`).join('|'); - } - - const required = def.required && def.required.includes(propName); - const optionalMark = required ? '' : '?'; - - let description = ''; - if (prop.description) { - description = ` - ${prop.description}`; - } else if (prop.$comment) { - description = ` - ${prop.$comment}`; - } - - jsdoc.push(` * @property {${type}} ${optionalMark}${propName}${description}`); - }); - } - - jsdoc.push(' */'); - jsdoc.push(''); - }); - } - - return jsdoc.join('\n'); -} - -const jsdocOutput = schemaToJSDoc(schema); - -// Write to file -const outputPath = path.join(__dirname, '../packages/core/docs/api/jsdoc/ASCIIFontOptions.js'); -const outputDir = path.dirname(outputPath); - -if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); -} - -fs.writeFileSync(outputPath, jsdocOutput, 'utf8'); -console.log('Generated JSDoc for ASCIIFontOptions'); -console.log('\nSample output:'); -console.log(jsdocOutput.split('\n').slice(0, 50).join('\n')); \ No newline at end of file diff --git a/scripts/generate-api-jsdoc.js b/scripts/generate-api-jsdoc.js deleted file mode 100755 index 87d4a25a6..000000000 --- a/scripts/generate-api-jsdoc.js +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const path = require('path'); - -const schemasDir = path.join(__dirname, '../packages/core/docs/api/schemas'); -const outputDir = path.join(__dirname, '../packages/core/docs/api/jsdoc'); - -// Create output directory if it doesn't exist -if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); -} - -// Helper to convert JSON schema types to JSDoc types -function getJSDocType(prop) { - if (prop.type) { - if (Array.isArray(prop.type)) { - return prop.type.map(t => t === 'integer' ? 'number' : t).join('|'); - } - return prop.type === 'integer' ? 'number' : prop.type; - } - - if (prop.$ref) { - return prop.$ref.split('/').pop(); - } - - if (prop.anyOf) { - return prop.anyOf.map(t => { - if (t.type) return t.type === 'integer' ? 'number' : t.type; - if (t.$ref) return t.$ref.split('/').pop(); - if (t.const) return JSON.stringify(t.const); - if (t.enum) return t.enum.map(v => JSON.stringify(v)).join('|'); - return 'any'; - }).join('|'); - } - - if (prop.enum) { - return prop.enum.map(v => JSON.stringify(v)).join('|'); - } - - if (prop.items) { - const itemType = getJSDocType(prop.items); - return `Array<${itemType}>`; - } - - return 'any'; -} - -// Convert schema to JSDoc -function schemaToJSDoc(schema, fileName) { - const typeName = fileName.replace('.json', ''); - let jsdoc = []; - - // Add file header - jsdoc.push('/**'); - jsdoc.push(` * JSDoc type definitions generated from JSON Schema`); - jsdoc.push(` * Source: ${fileName}`); - jsdoc.push(` * Generated: ${new Date().toISOString()}`); - jsdoc.push(' */'); - jsdoc.push(''); - - // Process main type if referenced - if (schema.$ref) { - const mainType = schema.$ref.split('/').pop(); - const mainDef = schema.definitions[mainType]; - - if (mainDef) { - jsdoc.push('/**'); - jsdoc.push(` * ${mainDef.description || mainType}`); - jsdoc.push(` * @typedef {Object} ${mainType}`); - - if (mainDef.properties) { - Object.entries(mainDef.properties).forEach(([propName, prop]) => { - const type = getJSDocType(prop); - const required = mainDef.required && mainDef.required.includes(propName); - const optionalMark = required ? '' : '['; - const optionalEnd = required ? '' : ']'; - - let description = prop.description || ''; - if (!description && prop.$comment) { - description = prop.$comment; - } - - jsdoc.push(` * @property {${type}} ${optionalMark}${propName}${optionalEnd} ${description ? '- ' + description : ''}`); - }); - } - - jsdoc.push(' */'); - jsdoc.push(''); - } - } - - // Process other definitions - if (schema.definitions) { - Object.entries(schema.definitions).forEach(([name, def]) => { - // Skip if already processed as main type - if (schema.$ref && schema.$ref.endsWith(name)) { - return; - } - - jsdoc.push('/**'); - jsdoc.push(` * ${def.description || name}`); - jsdoc.push(` * @typedef {Object} ${name}`); - - if (def.properties) { - Object.entries(def.properties).forEach(([propName, prop]) => { - const type = getJSDocType(prop); - const required = def.required && def.required.includes(propName); - const optionalMark = required ? '' : '['; - const optionalEnd = required ? '' : ']'; - - let description = prop.description || ''; - if (!description && prop.$comment) { - description = prop.$comment; - } - - jsdoc.push(` * @property {${type}} ${optionalMark}${propName}${optionalEnd} ${description ? '- ' + description : ''}`); - }); - } - - // Handle enums as separate typedef - if (def.enum) { - const enumType = def.enum.map(v => JSON.stringify(v)).join('|'); - jsdoc.push(` * @typedef {${enumType}} ${name}`); - } - - jsdoc.push(' */'); - jsdoc.push(''); - }); - } - - return jsdoc.join('\n'); -} - -// Process all schema files -const schemaFiles = fs.readdirSync(schemasDir).filter(file => file.endsWith('.json')); - -console.log(`Converting ${schemaFiles.length} JSON schemas to JSDoc...`); -console.log(); - -schemaFiles.forEach(file => { - try { - const schemaPath = path.join(schemasDir, file); - const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8')); - - const jsdocContent = schemaToJSDoc(schema, file); - - const outputFile = file.replace('.json', '.js'); - const outputPath = path.join(outputDir, outputFile); - - fs.writeFileSync(outputPath, jsdocContent, 'utf8'); - console.log(`✓ ${file} -> ${outputFile}`); - } catch (error) { - console.error(`✗ Error converting ${file}:`, error.message); - } -}); - -console.log(); -console.log(`JSDoc files generated in ${outputDir}`); \ No newline at end of file diff --git a/scripts/generate-api-schemas.sh b/scripts/generate-api-schemas.sh deleted file mode 100755 index 953c182ee..000000000 --- a/scripts/generate-api-schemas.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -# Generate JSON schemas for OpenTUI API documentation - -SCHEMA_DIR="packages/core/docs/api/schemas" -mkdir -p "$SCHEMA_DIR" - -echo "Generating JSON schemas for OpenTUI API..." - -# Renderables -echo "Generating schemas for renderables..." -npx ts-json-schema-generator --path "packages/core/src/renderables/ASCIIFont.ts" --type "ASCIIFontOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/ASCIIFontOptions.json" 2>/dev/null -npx ts-json-schema-generator --path "packages/core/src/renderables/Box.ts" --type "BoxOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/BoxOptions.json" 2>/dev/null -npx ts-json-schema-generator --path "packages/core/src/renderables/Text.ts" --type "TextOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/TextOptions.json" 2>/dev/null -npx ts-json-schema-generator --path "packages/core/src/renderables/Input.ts" --type "InputRenderableOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/InputRenderableOptions.json" 2>/dev/null -npx ts-json-schema-generator --path "packages/core/src/renderables/Select.ts" --type "SelectRenderableOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/SelectRenderableOptions.json" 2>/dev/null -npx ts-json-schema-generator --path "packages/core/src/renderables/TabSelect.ts" --type "TabSelectRenderableOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/TabSelectRenderableOptions.json" 2>/dev/null -npx ts-json-schema-generator --path "packages/core/src/renderables/FrameBuffer.ts" --type "FrameBufferOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/FrameBufferOptions.json" 2>/dev/null - -# Core types -echo "Generating schemas for core types..." -npx ts-json-schema-generator --path "packages/core/src/Renderable.ts" --type "RenderableOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/RenderableOptions.json" 2>/dev/null -npx ts-json-schema-generator --path "packages/core/src/Renderable.ts" --type "LayoutOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/LayoutOptions.json" 2>/dev/null -npx ts-json-schema-generator --path "packages/core/src/renderer.ts" --type "CliRendererConfig" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/CliRendererConfig.json" 2>/dev/null -npx ts-json-schema-generator --path "packages/core/src/console.ts" --type "ConsoleOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/ConsoleOptions.json" 2>/dev/null - -# 3D/Animation types -echo "Generating schemas for 3D/Animation types..." -npx ts-json-schema-generator --path "packages/core/src/3d/animation/ExplodingSpriteEffect.ts" --type "ExplosionEffectParameters" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/ExplosionEffectParameters.json" 2>/dev/null -npx ts-json-schema-generator --path "packages/core/src/3d/WGPURenderer.ts" --type "ThreeCliRendererOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/ThreeCliRendererOptions.json" 2>/dev/null -npx ts-json-schema-generator --path "packages/core/src/animation/Timeline.ts" --type "TimelineOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/TimelineOptions.json" 2>/dev/null -npx ts-json-schema-generator --path "packages/core/src/animation/Timeline.ts" --type "AnimationOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/AnimationOptions.json" 2>/dev/null - -# Library types -echo "Generating schemas for library types..." -npx ts-json-schema-generator --path "packages/core/src/lib/border.ts" --type "BorderConfig" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/BorderConfig.json" 2>/dev/null -npx ts-json-schema-generator --path "packages/core/src/lib/border.ts" --type "BoxDrawOptions" --tsconfig "packages/core/tsconfig.json" -o "$SCHEMA_DIR/BoxDrawOptions.json" 2>/dev/null - -echo "Schema generation complete! Check $SCHEMA_DIR for JSON schema files." \ No newline at end of file diff --git a/scripts/generate-jsdoc-from-schemas.js b/scripts/generate-jsdoc-from-schemas.js deleted file mode 100755 index 80459c5b1..000000000 --- a/scripts/generate-jsdoc-from-schemas.js +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const path = require('path'); -const generate = require('/home/linuxbrew/.linuxbrew/lib/node_modules/json-schema-to-jsdoc'); - -const schemasDir = path.join(__dirname, '../packages/core/docs/api/schemas'); -const outputDir = path.join(__dirname, '../packages/core/docs/api/jsdoc'); - -// Create output directory if it doesn't exist -if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); -} - -// JSDoc generation options -const options = { - autoDescribe: true, // Adds auto-generated descriptions - hyphenatedDescriptions: true, // Adds hyphen before property descriptions - capitalizeTitle: true, // Capitalizes titles - indent: 2, // Indentation level - maxLength: 100, // Max line length for descriptions - types: { - object: 'Object', - array: 'Array', - string: 'string', - number: 'number', - boolean: 'boolean', - integer: 'number' - }, - formats: { - 'date-time': 'Date', - 'uri': 'string', - 'email': 'string' - } -}; - -// Process all schema files -const schemaFiles = fs.readdirSync(schemasDir).filter(file => file.endsWith('.json')); - -console.log(`Converting ${schemaFiles.length} JSON schemas to JSDoc with json-schema-to-jsdoc...`); -console.log(); - -let allJSDocs = []; - -schemaFiles.forEach(file => { - try { - const schemaPath = path.join(schemasDir, file); - const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8')); - - // Generate JSDoc for each schema - const jsdocContent = generate(schema, options); - - // Add source file comment - const enhancedJSDoc = `/** - * Generated from JSON Schema: ${file} - * Date: ${new Date().toISOString()} - */ - -${jsdocContent}`; - - // Write individual file - const outputFile = file.replace('.json', '.js'); - const outputPath = path.join(outputDir, outputFile); - - fs.writeFileSync(outputPath, enhancedJSDoc, 'utf8'); - console.log(`✓ ${file} -> ${outputFile}`); - - // Also collect for combined file - allJSDocs.push(`// === ${file} ===\n${enhancedJSDoc}`); - } catch (error) { - console.error(`✗ Error converting ${file}:`, error.message); - } -}); - -// Create a combined file with all typedefs -const combinedPath = path.join(outputDir, 'all-types.js'); -const combinedContent = `/** - * OpenTUI Complete Type Definitions - * Generated from JSON Schemas - * Date: ${new Date().toISOString()} - */ - -${allJSDocs.join('\n\n')}`; - -fs.writeFileSync(combinedPath, combinedContent, 'utf8'); - -console.log(); -console.log(`JSDoc files generated in ${outputDir}`); -console.log(`Combined typedef file: all-types.js`); \ No newline at end of file diff --git a/scripts/jsdoc-from-schemas.js b/scripts/jsdoc-from-schemas.js deleted file mode 100755 index b258e2bd4..000000000 --- a/scripts/jsdoc-from-schemas.js +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const path = require('path'); -const jsdoc = require('/home/linuxbrew/.linuxbrew/lib/node_modules/json-schema-to-jsdoc'); - -const schemasDir = path.join(__dirname, '../packages/core/docs/api/schemas'); -const outputDir = path.join(__dirname, '../packages/core/docs/api/jsdoc'); - -// Create output directory if it doesn't exist -if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); -} - -// Process all schema files -const schemaFiles = fs.readdirSync(schemasDir).filter(file => file.endsWith('.json')); - -console.log(`Converting ${schemaFiles.length} JSON schemas to JSDoc...`); -console.log(); - -let allJSDocs = []; - -schemaFiles.forEach(file => { - try { - const schemaPath = path.join(schemasDir, file); - const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8')); - - let jsdocContent = ''; - - // If schema has $ref, extract the main definition - if (schema.$ref && schema.definitions) { - const typeName = schema.$ref.split('/').pop(); - const mainSchema = schema.definitions[typeName]; - - if (mainSchema) { - // Add title if not present - if (!mainSchema.title) { - mainSchema.title = typeName; - } - - // Generate JSDoc for main type - try { - const mainJSDoc = jsdoc(mainSchema, { - autoDescribe: true, - hyphenatedDescriptions: true, - capitalizeTitle: true, - indent: 2 - }); - jsdocContent += mainJSDoc + '\n\n'; - } catch (e) { - console.log(` Note: Could not generate JSDoc for ${typeName}: ${e.message}`); - } - } - - // Generate JSDoc for other definitions - Object.entries(schema.definitions).forEach(([name, def]) => { - if (name !== typeName) { - if (!def.title) { - def.title = name; - } - try { - const defJSDoc = jsdoc(def, { - autoDescribe: true, - hyphenatedDescriptions: true, - capitalizeTitle: true, - indent: 2 - }); - jsdocContent += defJSDoc + '\n\n'; - } catch (e) { - // Skip definitions that can't be converted - } - } - }); - } else { - // Direct schema without $ref - jsdocContent = jsdoc(schema, { - autoDescribe: true, - hyphenatedDescriptions: true, - capitalizeTitle: true, - indent: 2 - }); - } - - if (jsdocContent.trim()) { - // Add header - const fullContent = `/** - * Generated from: ${file} - * Date: ${new Date().toISOString()} - */ - -${jsdocContent}`; - - // Write individual file - const outputFile = file.replace('.json', '.js'); - const outputPath = path.join(outputDir, outputFile); - - fs.writeFileSync(outputPath, fullContent, 'utf8'); - console.log(`✓ ${file} -> ${outputFile}`); - - // Collect for combined file - allJSDocs.push(fullContent); - } else { - console.log(`⚠ ${file} - no JSDoc generated`); - } - } catch (error) { - console.error(`✗ Error processing ${file}:`, error.message); - } -}); - -// Create combined file -if (allJSDocs.length > 0) { - const combinedPath = path.join(outputDir, 'all-types.js'); - const combinedContent = `/** - * OpenTUI Complete Type Definitions - * Generated from JSON Schemas - * Date: ${new Date().toISOString()} - */ - -${allJSDocs.join('\n\n')}`; - - fs.writeFileSync(combinedPath, combinedContent, 'utf8'); - console.log(); - console.log(`Combined typedef file created: all-types.js`); -} - -console.log(); -console.log(`JSDoc files generated in ${outputDir}`); \ No newline at end of file diff --git a/scripts/jsdoc-to-tsdoc.js b/scripts/jsdoc-to-tsdoc.js new file mode 100755 index 000000000..f593c0209 --- /dev/null +++ b/scripts/jsdoc-to-tsdoc.js @@ -0,0 +1,158 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +const jsdocDir = path.join(__dirname, '../packages/core/docs/api/jsdoc'); +const outputDir = path.join(__dirname, '../packages/core/docs/api/types'); + +// Create output directory +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); +} + +// Function to convert JSDoc to TSDoc +function convertJSDocToTSDoc(content, filename) { + const typeName = filename.replace('.js', ''); + + // Extract JSDoc comments and properties + const jsdocRegex = /\/\*\*([\s\S]*?)\*\//g; + const propertyRegex = /@property\s+\{([^}]+)\}\s+(\[?[\w.]+\]?)\s*-?\s*(.*)/g; + + let tsDoc = `/**\n * ${typeName} interface\n`; + + // Extract description from first JSDoc block + const firstMatch = jsdocRegex.exec(content); + if (firstMatch) { + const lines = firstMatch[1].split('\n'); + const description = lines + .filter(line => !line.includes('@') && line.trim().length > 0) + .map(line => line.replace(/^\s*\*\s?/, '')) + .join(' ') + .trim(); + + if (description && !description.includes('Generated from:')) { + tsDoc += ` * ${description}\n`; + } + } + + tsDoc += ` * \n * @public\n */\n`; + tsDoc += `export interface ${typeName} {\n`; + + // Reset regex + propertyRegex.lastIndex = 0; + + // Extract properties + let match; + const properties = []; + + while ((match = propertyRegex.exec(content)) !== null) { + const [, type, name, description] = match; + const isOptional = name.startsWith('[') && name.endsWith(']'); + const propName = isOptional ? name.slice(1, -1) : name; + const cleanName = propName.split('.').pop(); // Handle nested properties + + properties.push({ + name: cleanName, + type: mapJSTypeToTS(type), + description: description.trim(), + optional: isOptional + }); + } + + // Add properties to interface + properties.forEach(prop => { + if (prop.description) { + tsDoc += ` /**\n * ${prop.description}\n */\n`; + } + tsDoc += ` ${prop.name}${prop.optional ? '?' : ''}: ${prop.type};\n\n`; + }); + + tsDoc += '}\n'; + + return tsDoc; +} + +// Map JS types to TypeScript types +function mapJSTypeToTS(jsType) { + const typeMap = { + 'String': 'string', + 'string': 'string', + 'Number': 'number', + 'number': 'number', + 'Boolean': 'boolean', + 'boolean': 'boolean', + 'Object': 'Record', + 'object': 'Record', + 'Array': 'any[]', + 'array': 'any[]', + 'Function': '(...args: any[]) => any', + 'function': '(...args: any[]) => any', + 'any': 'any', + '*': 'any' + }; + + // Handle union types + if (jsType.includes('|')) { + return jsType.split('|') + .map(t => mapJSTypeToTS(t.trim())) + .join(' | '); + } + + // Handle array types + if (jsType.includes('[]')) { + const baseType = jsType.replace('[]', ''); + return `${mapJSTypeToTS(baseType)}[]`; + } + + // Handle specific OpenTUI types + if (jsType.includes('RGBA') || jsType.includes('ColorInput')) { + return jsType; // Keep as-is + } + + return typeMap[jsType] || jsType; +} + +// Process all JSDoc files +const files = fs.readdirSync(jsdocDir).filter(f => f.endsWith('.js')); + +console.log('Converting JSDoc to TSDoc...\n'); + +files.forEach(file => { + if (file === 'all-types.js') { + // Skip the aggregated file + return; + } + + const inputPath = path.join(jsdocDir, file); + const outputFile = file.replace('.js', '.d.ts'); + const outputPath = path.join(outputDir, outputFile); + + const content = fs.readFileSync(inputPath, 'utf8'); + const tsDoc = convertJSDocToTSDoc(content, file); + + fs.writeFileSync(outputPath, tsDoc); + console.log(`✓ Converted ${file} → ${outputFile}`); +}); + +// Create an index file that exports all types +const indexContent = `/** + * OpenTUI Type Definitions + * + * @packageDocumentation + */ + +${files + .filter(f => f !== 'all-types.js') + .map(f => { + const typeName = f.replace('.js', ''); + return `export type { ${typeName} } from './${typeName}';`; + }) + .join('\n')} +`; + +fs.writeFileSync(path.join(outputDir, 'index.d.ts'), indexContent); +console.log('\n✓ Created index.d.ts'); + +console.log(`\n✅ TSDoc conversion complete!`); +console.log(`📁 Output: ${outputDir}`); \ No newline at end of file diff --git a/scripts/schemas-to-jsdoc.js b/scripts/schemas-to-jsdoc.js deleted file mode 100755 index 839f8f926..000000000 --- a/scripts/schemas-to-jsdoc.js +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const path = require('path'); -const jsdoc = require('json-schema-to-jsdoc'); - -const schemasDir = path.join(__dirname, '../packages/core/docs/api/schemas'); -const outputDir = path.join(__dirname, '../packages/core/docs/api/jsdoc'); - -// Create output directory if it doesn't exist -if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); -} - -// Get all JSON schema files -const schemaFiles = fs.readdirSync(schemasDir).filter(file => file.endsWith('.json')); - -console.log(`Converting ${schemaFiles.length} JSON schemas to JSDoc...`); - -schemaFiles.forEach(file => { - const schemaPath = path.join(schemasDir, file); - const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8')); - - try { - // Convert schema to JSDoc - const jsdocComment = jsdoc(schema); - - // Create output filename - const outputFile = file.replace('.json', '.js'); - const outputPath = path.join(outputDir, outputFile); - - // Write JSDoc to file - fs.writeFileSync(outputPath, jsdocComment, 'utf8'); - console.log(`✓ Converted ${file} -> ${outputFile}`); - } catch (error) { - console.error(`✗ Error converting ${file}:`, error.message); - } -}); - -console.log(`\nJSDoc files generated in ${outputDir}`); \ No newline at end of file diff --git a/scripts/schemas-to-tsdoc.cjs b/scripts/schemas-to-tsdoc.cjs new file mode 100755 index 000000000..dd7920f09 --- /dev/null +++ b/scripts/schemas-to-tsdoc.cjs @@ -0,0 +1,226 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); + +const schemasDir = path.join(__dirname, '../packages/core/docs/api/schemas'); +const outputDir = path.join(__dirname, '../packages/core/src/types'); + +// Create output directory +if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); +} + +// Convert JSON Schema to TypeScript with TSDoc +function schemaToTypeScript(schema, typeName) { + let tsContent = ''; + + // Find the main definition + const definition = schema.definitions?.[typeName] || schema; + + if (!definition.properties) { + console.warn(`No properties found for ${typeName}`); + return ''; + } + + // Add TSDoc header + tsContent += `/**\n`; + tsContent += ` * ${typeName} configuration options\n`; + + if (definition.description || definition.$comment) { + tsContent += ` * \n`; + tsContent += ` * ${definition.description || definition.$comment}\n`; + } + + tsContent += ` * \n`; + tsContent += ` * @public\n`; + tsContent += ` * @category Configuration\n`; + tsContent += ` */\n`; + tsContent += `export interface ${typeName} {\n`; + + // Process properties + Object.entries(definition.properties).forEach(([propName, propDef]) => { + const isRequired = definition.required?.includes(propName); + + // Add property documentation + if (propDef.description || propDef.$comment) { + tsContent += ` /**\n`; + tsContent += ` * ${propDef.description || propDef.$comment}\n`; + + if (propDef.default !== undefined) { + tsContent += ` * @defaultValue ${JSON.stringify(propDef.default)}\n`; + } + + if (propDef.enum) { + tsContent += ` * @remarks Possible values: ${propDef.enum.map(v => `'${v}'`).join(', ')}\n`; + } + + tsContent += ` */\n`; + } + + // Add property definition + const propType = jsonSchemaTypeToTS(propDef); + tsContent += ` ${propName}${isRequired ? '' : '?'}: ${propType};\n\n`; + }); + + tsContent += '}\n'; + + return tsContent; +} + +// Convert JSON Schema type to TypeScript type +function jsonSchemaTypeToTS(schema) { + if (schema.$ref) { + // Extract type name from ref + const typeName = schema.$ref.split('/').pop(); + return typeName; + } + + if (schema.enum) { + return schema.enum.map(v => typeof v === 'string' ? `'${v}'` : v).join(' | '); + } + + if (schema.type === 'array') { + const itemType = schema.items ? jsonSchemaTypeToTS(schema.items) : 'any'; + return `${itemType}[]`; + } + + if (schema.type === 'object') { + if (schema.properties) { + const props = Object.entries(schema.properties) + .map(([key, value]) => `${key}: ${jsonSchemaTypeToTS(value)}`) + .join('; '); + return `{ ${props} }`; + } + return 'Record'; + } + + if (schema.anyOf) { + return schema.anyOf.map(s => jsonSchemaTypeToTS(s)).join(' | '); + } + + if (schema.oneOf) { + return schema.oneOf.map(s => jsonSchemaTypeToTS(s)).join(' | '); + } + + const typeMap = { + 'string': 'string', + 'number': 'number', + 'integer': 'number', + 'boolean': 'boolean', + 'null': 'null' + }; + + return typeMap[schema.type] || 'any'; +} + +// Process all schema files +const files = fs.readdirSync(schemasDir).filter(f => f.endsWith('.json')); + +console.log('Converting JSON Schemas to TypeScript with TSDoc...\n'); + +const imports = new Set(); +const typeExports = []; + +files.forEach(file => { + const typeName = file.replace('.json', ''); + const schemaPath = path.join(schemasDir, file); + const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8')); + + const tsContent = schemaToTypeScript(schema, typeName); + + if (tsContent) { + const outputFile = `${typeName}.d.ts`; + const outputPath = path.join(outputDir, outputFile); + + fs.writeFileSync(outputPath, tsContent); + typeExports.push(typeName); + console.log(`✓ Generated ${outputFile}`); + } +}); + +// Create index file +const indexContent = `/** + * OpenTUI Type Definitions + * + * This module exports all configuration interfaces and types used by OpenTUI components. + * + * @module @opentui/core/types + * @packageDocumentation + */ + +// Component Options +${typeExports.map(name => `export type { ${name} } from './${name}';`).join('\n')} + +// Re-export commonly used types +export type { + BoxOptions, + TextOptions, + InputRenderableOptions, + ASCIIFontOptions, + AnimationOptions, + TimelineOptions, + CliRendererConfig +} from './index'; + +/** + * Common color input type + */ +export type ColorInput = string | RGBA; + +/** + * RGBA color representation + */ +export interface RGBA { + r: number; + g: number; + b: number; + a: number; +} + +/** + * Border style options + */ +export type BorderStyle = 'single' | 'double' | 'rounded' | 'heavy'; + +/** + * Flexbox alignment options + */ +export type AlignString = 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline'; + +/** + * Flexbox justification options + */ +export type JustifyString = 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around' | 'space-evenly'; + +/** + * Flexbox direction options + */ +export type FlexDirectionString = 'row' | 'column' | 'row-reverse' | 'column-reverse'; + +/** + * Position type options + */ +export type PositionTypeString = 'relative' | 'absolute'; +`; + +fs.writeFileSync(path.join(outputDir, 'index.d.ts'), indexContent); +console.log('\n✓ Created index.d.ts'); + +// Create a package.json for the types +const packageJson = { + "name": "@opentui/core/types", + "types": "./index.d.ts", + "description": "TypeScript type definitions for OpenTUI" +}; + +fs.writeFileSync( + path.join(outputDir, 'package.json'), + JSON.stringify(packageJson, null, 2) +); +console.log('✓ Created package.json'); + +console.log(`\n✅ TSDoc type definitions generated!`); +console.log(`📁 Output: ${outputDir}`); +console.log('\nUsage in TypeScript:'); +console.log(' import type { BoxOptions, TextOptions } from "@opentui/core/types";'); \ No newline at end of file diff --git a/tsconfig.ide.json b/tsconfig.ide.json new file mode 100644 index 000000000..e6b49d425 --- /dev/null +++ b/tsconfig.ide.json @@ -0,0 +1,67 @@ +{ + "compilerOptions": { + // IDE Support + "plugins": [ + { + "name": "typescript-plugin-css-modules" + } + ], + + // Enhanced IntelliSense + "strict": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "noImplicitAny": true, + + // Module Resolution + "moduleResolution": "node", + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + + // Type Checking + "skipLibCheck": false, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + + // Paths for IntelliSense + "baseUrl": ".", + "paths": { + "@opentui/core": ["./packages/core/src/index.ts"], + "@opentui/core/*": ["./packages/core/src/*"], + "@opentui/react": ["./packages/react/src/index.ts"], + "@opentui/react/*": ["./packages/react/src/*"], + "@opentui/solid": ["./packages/solid/src/index.ts"], + "@opentui/solid/*": ["./packages/solid/src/*"] + }, + + // Include type definitions + "types": [ + "node" + ], + "typeRoots": [ + "./node_modules/@types", + "./packages/core/src/types" + ] + }, + + "include": [ + "packages/*/src/**/*", + "packages/*/docs/**/*", + "scripts/**/*" + ], + + "exclude": [ + "node_modules", + "**/node_modules", + "dist", + "**/dist", + "build", + "**/build" + ] +} \ No newline at end of file From 4ffb04c8f8e0da02b7fc4ebd9d877ffa45eec590 Mon Sep 17 00:00:00 2001 From: entrepeneur4lyf Date: Fri, 22 Aug 2025 04:50:56 -0400 Subject: [PATCH 6/6] update intellisense import --- packages/core/src/intellisense.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/intellisense.d.ts b/packages/core/src/intellisense.d.ts index be05615c4..6bdcebb15 100644 --- a/packages/core/src/intellisense.d.ts +++ b/packages/core/src/intellisense.d.ts @@ -7,7 +7,7 @@ * @packageDocumentation */ -/// +import * as Types from './types/index.d.ts'; declare module '@opentui/core' { // Re-export all types