xocdr / tui
Terminal UI framework for PHP
Installs: 18
Dependents: 1
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/xocdr/tui
Requires
- php: ^8.4
- ext-tui: *
Requires (Dev)
- laravel/pint: ^1.26
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.0
README
xocdr/tui
A Terminal UI framework for PHP. Build beautiful, interactive terminal applications with a component-based architecture and hooks for state management.
Features
- ๐จ Component-based - Build UIs with composable components (Box, Text, etc.)
- โก Hooks - state, onRender, memo, onInput, and more
- ๐ฆ Flexbox layout - Powered by Yoga layout engine via ext-tui
- ๐ฏ Focus management - Tab navigation and focus tracking
- ๐ Event system - Priority-based event dispatching with propagation control
- ๐งช Testable - Interface-based design with mock implementations
Requirements
- PHP 8.4+
- ext-tui (C extension)
Installation
composer require xocdr/tui
Quick Start
<?php use Xocdr\Tui\Tui; use Xocdr\Tui\Components\Box; use Xocdr\Tui\Components\Text; use Xocdr\Tui\Hooks\Hooks; $app = function () { $hooks = new Hooks(Tui::getApplication()); [$count, $setCount] = $hooks->state(0); ['exit' => $exit] = $hooks->app(); $hooks->onInput(function ($key) use ($setCount, $exit) { if ($key === 'q') { $exit(); } if ($key === ' ') { $setCount(fn($c) => $c + 1); } }); return Box::create() ->flexDirection('column') ->padding(1) ->border('round') ->children([ Text::create("Count: {$count}")->bold(), Text::create('Press SPACE to increment, Q to quit')->dim(), ]); }; Tui::render($app)->waitUntilExit();
Components
Box
Flexbox container for layout:
use Xocdr\Tui\Components\Box; Box::create() ->flexDirection('column') // 'row' | 'column' ->alignItems('center') // 'flex-start' | 'center' | 'flex-end' ->justifyContent('center') // 'flex-start' | 'center' | 'flex-end' | 'space-between' ->padding(1) ->paddingX(2) ->margin(1) ->gap(1) ->width(50) ->height(10) ->aspectRatio(16/9) // Width/height ratio ->direction('ltr') // 'ltr' | 'rtl' layout direction ->border('single') // 'single' | 'double' | 'round' | 'bold' ->borderColor('blue') ->children([...]); // Shortcuts Box::column([...]); // flexDirection('column') Box::row([...]); // flexDirection('row') // Tailwind-like utility classes Box::create() ->styles('border border-round border-blue-500') // Border style + color ->styles('bg-slate-900 p-2') // Background + padding ->styles('flex-col items-center gap-1') // Layout utilities ->styles(fn() => $hasBorder ? 'border' : ''); // Conditional
Text
Styled text content:
use Xocdr\Tui\Components\Text; Text::create('Hello World') ->bold() ->italic() ->underline() ->strikethrough() ->dim() ->inverse() ->color('#ff0000') // Hex color ->bgColor('#0000ff') // Background color ->color('blue', 500) // Tailwind palette + shade ->bgColor('slate', 100) // Background palette + shade ->wrap('word'); // 'word' | 'none' // Color shortcuts Text::create('Error')->red(); Text::create('Success')->green(); Text::create('Info')->blue()->bold(); // Unified color API (accepts Color enum, hex, or palette name with shade) use Xocdr\Tui\Ext\Color; Text::create('Palette')->color('red', 500); // Palette name + shade Text::create('Palette')->color(Color::Red, 500); // Color enum + shade Text::create('Palette')->color(Color::Coral); // CSS color via enum // Tailwind-like utility classes Text::create('Hello') ->styles('bold text-green-500') // Multiple utilities ->styles('text-red bg-slate-900 underline'); // Colors + styles // Bare colors as text color shorthand Text::create('Error')->styles('red'); // Same as text-red Text::create('Success')->styles('green-500'); // Same as text-green-500 // Dynamic styles with callables Text::create('Status') ->styles(fn() => $active ? 'green' : 'red') // Conditional styling ->styles('bold', ['italic', 'underline']); // Mixed arguments
Other Components
use Xocdr\Tui\Components\Fragment; use Xocdr\Tui\Components\Spacer; use Xocdr\Tui\Components\Newline; use Xocdr\Tui\Components\Static_; // Fragment - group without extra node Fragment::create([ Text::create('Line 1'), Text::create('Line 2'), ]); // Spacer - fills available space (flexGrow: 1) Box::row([ Text::create('Left'), Spacer::create(), Text::create('Right'), ]); // Newline - line breaks Newline::create(2); // Two line breaks // Static - non-rerendering content (logs, history) Static_::create($logItems);
Hooks
The Hooks class provides state management and side effects for components.
use Xocdr\Tui\Hooks\Hooks; $hooks = new Hooks($instance);
state
Manage component state:
[$count, $setCount] = $hooks->state(0); // Direct value $setCount(5); // Functional update $setCount(fn($prev) => $prev + 1);
onRender
Run side effects:
$hooks->onRender(function () { // Effect runs when deps change $timer = startTimer(); // Return cleanup function return fn() => $timer->stop(); }, [$dependency]);
memo / callback
Memoize values and callbacks:
$expensive = $hooks->memo(fn() => computeExpensiveValue($data), [$data]); $handler = $hooks->callback(fn($e) => handleEvent($e), [$dependency]);
ref
Create mutable references:
$ref = $hooks->ref(null); $ref->current = 'new value'; // Doesn't trigger re-render
reducer
Complex state with reducer pattern:
$reducer = fn($state, $action) => match($action['type']) { 'increment' => $state + 1, 'decrement' => $state - 1, default => $state, }; [$count, $dispatch] = $hooks->reducer($reducer, 0); $dispatch(['type' => 'increment']);
onInput
Handle keyboard input:
$hooks->onInput(function ($key, $nativeKey) { if ($key === 'q') { // Handle quit } if ($nativeKey->upArrow) { // Handle arrow key } if ($nativeKey->ctrl && $key === 'c') { // Handle Ctrl+C } }, ['isActive' => true]);
app
Access application controls:
['exit' => $exit] = $hooks->app(); $exit(0); // Exit with code 0
focus / focusManager
Manage focus:
// Check focus state ['isFocused' => $isFocused, 'focus' => $focus] = $hooks->focus([ 'autoFocus' => true, ]); // Navigate focus ['focusNext' => $next, 'focusPrevious' => $prev] = $hooks->focusManager();
stdout
Get terminal info:
['columns' => $cols, 'rows' => $rows, 'write' => $write] = $hooks->stdout();
HooksAware Trait
For components, use the HooksAwareTrait for convenient access:
use Xocdr\Tui\Contracts\HooksAwareInterface; use Xocdr\Tui\Hooks\HooksAwareTrait; class MyComponent implements HooksAwareInterface { use HooksAwareTrait; public function render(): mixed { [$count, $setCount] = $this->hooks()->state(0); // ... } }
Events
Listen to events on the application:
$app = Tui::render($myApp); // Input events $app->onInput(function ($key, $nativeKey) { echo "Key pressed: $key"; }, priority: 10); // Resize events $app->onResize(function ($event) { echo "New size: {$event->width}x{$event->height}"; }); // Focus events $app->onFocus(function ($event) { echo "Focus changed to: {$event->currentId}"; }); // Remove handler $handlerId = $app->onInput($handler); $app->off($handlerId);
Advanced Usage
Application Builder
Configure with fluent API:
use Xocdr\Tui\Tui; $app = Tui::builder() ->component($myApp) ->fullscreen(true) ->exitOnCtrlC(true) ->eventDispatcher($customDispatcher) ->hookContext($customHooks) ->renderer($customRenderer) ->start();
Dependency Injection
For testing or custom configurations:
use Xocdr\Tui\Application; use Xocdr\Tui\Terminal\Events\EventDispatcher; use Xocdr\Tui\Hooks\HookContext; use Xocdr\Tui\Rendering\Render\ComponentRenderer; use Xocdr\Tui\Rendering\Render\ExtensionRenderTarget; $app = new Application( $component, ['fullscreen' => true], new EventDispatcher(), new HookContext(), new ComponentRenderer(new ExtensionRenderTarget()) );
Testing Without C Extension
Use mock implementations:
use Xocdr\Tui\Tests\Mocks\MockRenderTarget; use Xocdr\Tui\Rendering\Render\ComponentRenderer; $target = new MockRenderTarget(); $renderer = new ComponentRenderer($target); $node = $renderer->render($component); // Inspect created nodes $this->assertCount(2, $target->createdNodes);
Style Utilities
Style Builder
use Xocdr\Tui\Styling\Style\Style; $style = Style::create() ->bold() ->color('#ff0000') ->bgColor('#000000') ->toArray();
Color Utilities
use Xocdr\Tui\Styling\Style\Color; // Conversions $rgb = Color::hexToRgb('#ff0000'); // ['r' => 255, 'g' => 0, 'b' => 0] $hex = Color::rgbToHex(255, 0, 0); // '#ff0000' $lerped = Color::lerp('#000000', '#ffffff', 0.5); // '#808080' // CSS Named Colors (141 colors via ext-tui Color enum) $hex = Color::css('coral'); // '#ff7f50' $hex = Color::css('dodgerblue'); // '#1e90ff' Color::isCssColor('salmon'); // true $names = Color::cssNames(); // All 141 color names // Tailwind Palette $blue500 = Color::palette('blue', 500); // '#3b82f6' // Universal resolver $hex = Color::resolve('coral'); // CSS name $hex = Color::resolve('#ff0000'); // Hex passthrough $hex = Color::resolve('red-500'); // Tailwind palette // Custom color aliases Color::defineColor('dusty-orange', 'orange', 700); // From palette + shade Color::defineColor('brand-primary', '#3498db'); // From hex Color::defineColor('accent', 'coral'); // From CSS name // Use custom colors anywhere Text::create('Hello')->styles('dusty-orange'); Box::create()->styles('bg-brand-primary border-accent'); $hex = Color::custom('dusty-orange'); // Get hex value Color::isCustomColor('brand-primary'); // true // Custom palettes (auto-generates shades 50-950) Color::define('brand', '#3498db'); // Base color becomes 500 Text::create('Hello')->color('brand', 300); // Use lighter shade
Gradients
use Xocdr\Tui\Styling\Animation\Gradient; use Xocdr\Tui\Ext\Color; // Create gradient between colors (hex, palette, or Color enum) $gradient = Gradient::between('#ff0000', '#0000ff', 10); $gradient = Gradient::between(['red', 500], ['blue', 500], 10); $gradient = Gradient::between(Color::Red, Color::Blue, 10); // Fluent builder API $gradient = Gradient::from('red', 500) ->to('blue', 300) ->steps(10) ->hsl() // Use HSL interpolation (default: RGB) ->circular() // Make gradient loop back ->build(); // Get colors from gradient $colors = $gradient->getColors(); // Array of hex strings $color = $gradient->at(0.5); // Color at position (0-1)
Border Styles
use Xocdr\Tui\Styling\Style\Border; Border::SINGLE; // โโโโโโโ Border::DOUBLE; // โโโโโโโ Border::ROUND; // โญโโฎโโฐโโฏ Border::BOLD; // โโโโโโโ $chars = Border::getChars('round');
Terminal Control
Access terminal features via TerminalManager:
$app = Tui::render($myComponent); $terminal = $app->getTerminalManager(); // Window title $terminal->setTitle('My TUI App'); $terminal->resetTitle(); // Cursor control $terminal->hideCursor(); $terminal->showCursor(); $terminal->setCursorShape('bar'); // 'block', 'underline', 'bar', etc. // Terminal capabilities $terminal->supportsTrueColor(); // 24-bit color support $terminal->supportsHyperlinks(); // OSC 8 support $terminal->supportsMouse(); // Mouse input $terminal->getTerminalType(); // 'kitty', 'iterm2', 'wezterm', etc. $terminal->getColorDepth(); // 8, 256, or 16777216
Scrolling
SmoothScroller
Spring physics-based smooth scrolling:
use Xocdr\Tui\Scroll\SmoothScroller; // Create with default spring physics $scroller = SmoothScroller::create(); // Or with custom settings $scroller = new SmoothScroller(stiffness: 170.0, damping: 26.0); // Preset configurations $scroller = SmoothScroller::fast(); // Quick animations $scroller = SmoothScroller::slow(); // Smooth, slow animations $scroller = SmoothScroller::bouncy(); // Bouncy effect // Set target position $scroller->setTarget(0.0, 100.0); // Or scroll by delta $scroller->scrollBy(0, 10); // In render loop while ($scroller->isAnimating()) { $scroller->update(1.0 / 60.0); // 60 FPS $pos = $scroller->getPosition(); // Render at $pos['y'] }
VirtualList
Efficient rendering for large lists (windowing/virtualization):
use Xocdr\Tui\Scroll\VirtualList; // Create for 100,000 items with 1-row height, 20-row viewport $vlist = VirtualList::create( itemCount: 100000, viewportHeight: 20, itemHeight: 1, overscan: 5 ); // Get visible range (only render these!) $range = $vlist->getVisibleRange(); for ($i = $range['start']; $i < $range['end']; $i++) { $offset = $vlist->getItemOffset($i); // Render item at Y = $offset } // Navigation $vlist->scrollItems(1); // Arrow down $vlist->scrollItems(-1); // Arrow up $vlist->pageDown(); // Page down $vlist->pageUp(); // Page up $vlist->scrollToTop(); // Home $vlist->scrollToBottom(); // End $vlist->ensureVisible($i); // Scroll to make item visible
Architecture
The package follows SOLID principles with a clean separation of concerns:
src/
โโโ Application/ # Manager classes for Application
โ โโโ TimerManager.php # Timer and interval management
โ โโโ OutputManager.php # Terminal output operations
โ โโโ TerminalManager.php # Cursor, title, capabilities
โโโ Scroll/ # Scrolling utilities
โ โโโ SmoothScroller.php # Spring physics scrolling
โ โโโ VirtualList.php # Virtual list for large datasets
โโโ Components/ # UI components
โ โโโ Component.php # Base interface
โ โโโ Box.php # Flexbox container
โ โโโ Text.php # Styled text
โ โโโ ...
โโโ Contracts/ # Interfaces for loose coupling
โ โโโ NodeInterface.php
โ โโโ RenderTargetInterface.php
โ โโโ RendererInterface.php
โ โโโ EventDispatcherInterface.php
โ โโโ HookContextInterface.php
โ โโโ InstanceInterface.php
โ โโโ TimerManagerInterface.php
โ โโโ OutputManagerInterface.php
โ โโโ InputManagerInterface.php
โ โโโ TerminalManagerInterface.php
โโโ Hooks/ # State management hooks
โ โโโ HookContext.php
โ โโโ HookRegistry.php
โ โโโ Hooks.php # Primary hooks API
โ โโโ HooksAwareTrait.php
โโโ Rendering/ # Rendering subsystem
โ โโโ Lifecycle/ # Application lifecycle
โ โโโ Render/ # Component rendering
โ โโโ Focus/ # Focus management
โโโ Styling/ # Styling subsystem
โ โโโ Style/ # Colors, styles, borders
โ โโโ Animation/ # Easing, gradients, tweens
โ โโโ Drawing/ # Canvas, buffer, sprites
โ โโโ Text/ # Text utilities
โโโ Support/ # Support utilities
โ โโโ Exceptions/ # Exception classes
โ โโโ Testing/ # Mock implementations
โ โโโ Debug/ # Runtime inspection
โ โโโ Telemetry/ # Performance metrics
โโโ Terminal/ # Terminal subsystem
โ โโโ Input/ # Keyboard input (InputManager, Key, Modifier)
โ โโโ Events/ # Event system
โ โโโ Capabilities.php # Terminal feature detection
โโโ Widgets/ # Pre-built widgets
โโโ Container.php # DI container
โโโ Application.php # Application wrapper with manager getters
โโโ InstanceBuilder.php # Fluent builder
โโโ Tui.php # Main entry point
Development
# Install dependencies composer install # Run tests composer test # Format code (PSR-12) composer format # Check formatting composer format:check # Static analysis composer analyse
License
MIT
Related
- xocdr/ext-tui - Required C extension