Files
pages-tui/README.md

8.1 KiB

TUI Orchestrator

A complete, ready-to-use TUI framework that handles input routing, focus management, page navigation, and lifecycle hooks—so you can define your pages, buttons, and logic, and it just works.

Features

  • Zero boilerplate - Define components, library handles everything else
  • Ready to use - Register pages and run, no manual wiring needed
  • Sensible defaults - Works without configuration
  • Fully extendable - Customize via traits when needed
  • no_std compatible - Works on embedded systems and WebAssembly
  • Backend-agnostic - No crossterm/ratatui dependencies
  • Zero unsafe - Pure Rust, no unsafe code

Quick Start

Define Your Component

extern crate alloc;

use tui_orchestrator::prelude::*;

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum LoginFocus {
    Username,
    Password,
    LoginButton,
    CancelButton,
}

#[derive(Debug, Clone)]
enum LoginEvent {
    AttemptLogin { username: String, password: String },
    Cancel,
}

struct LoginPage {
    username: alloc::string::String,
    password: alloc::string::String,
}

impl Component for LoginPage {
    type Focus = LoginFocus;
    type Action = ComponentAction;
    type Event = LoginEvent;
    
    fn targets(&self) -> &[Self::Focus] {
        &[
            LoginFocus::Username,
            LoginFocus::Password,
            LoginFocus::LoginButton,
            LoginFocus::CancelButton,
        ]
    }
    
    fn handle(&mut self, focus: &Self::Focus, action: Self::Action) -> Result<Option<Self::Event>> {
        match (focus, action) {
            (LoginFocus::LoginButton, ComponentAction::Select) => {
                Ok(Some(LoginEvent::AttemptLogin {
                    username: self.username.clone(),
                    password: self.password.clone(),
                }))
            }
            (LoginFocus::CancelButton, ComponentAction::Select) => {
                Ok(Some(LoginEvent::Cancel))
            }
            _ => Ok(None),
        }
    }
    
    fn handle_text(&mut self, focus: &Self::Focus, ch: char) -> Result<Option<Self::Event>> {
        match focus {
            LoginFocus::Username => {
                self.username.push(ch);
                Ok(None)
            }
            LoginFocus::Password => {
                self.password.push(ch);
                Ok(None)
            }
            _ => Ok(None),
        }
    }
}

Register and Run

use tui_orchestrator::prelude::*;

fn main() -> Result<()> {
    let mut orch = Orchestrator::builder()
        .with_page("login", LoginPage::new())
        .with_default_bindings()
        .build()?;
    
    orch.navigate_to("login")?;
    
    orch.run(&mut MyInputSource)?;
}

That's it. The library handles:

  • Input processing (read keys, route to actions)
  • Focus management (next/prev navigation)
  • Page navigation (on_exit, swap, on_enter)
  • Default keybindings (Tab=Next, Enter=Select)
  • Event collection and routing

Core Concepts

Component

The main abstraction in tui_orchestrator. A component represents a page or UI section with focusable elements.

pub trait Component {
    type Focus: FocusId;         // What can receive focus
    type Action: Action;          // What actions this handles
    type Event: Clone + Debug;     // Events this component emits
    
    fn targets(&self) -> &[Self::Focus];
    fn handle(&mut self, focus: &Self::Focus, action: Self::Action) -> Result<Option<Self::Event>>;
}

Optional methods (all have defaults):

  • on_enter() - Called when component becomes active
  • on_exit() - Called when component becomes inactive
  • on_focus() - Called when a focus target gains focus
  • on_blur() - Called when a focus target loses focus
  • handle_text() - Called when character is typed
  • can_navigate_forward/backward() - Control focus movement

Component Actions

Standard actions the library provides:

pub enum ComponentAction {
    Next,           // Tab
    Prev,            // Shift+Tab
    First,           // Home
    Last,            // End
    Select,          // Enter
    Cancel,          // Esc
    TypeChar(char),  // Any character
    Backspace,       // Backspace
    Delete,          // Delete
    Custom(usize),   // User-defined
}

Focus Management

Focus tracks which element is currently active. The library provides:

  • FocusManager<F> - Generic focus tracking
  • FocusQuery - Read-only focus state for rendering
  • Automatic navigation (next, prev, first, last)

Orchestrator

The complete TUI runtime that wires everything together:

  • Orchestrator<C> - Main framework struct
  • process_frame() - Process one input frame
  • run() - Complete main loop
  • Extension points for custom behavior

Extension Points

For complex applications (like komp_ac), the library provides extension points to customize behavior:

ModeResolver

Customize how modes are resolved (dynamic vs static).

impl ModeResolver for CustomResolver {
    fn resolve(&self, focus: &dyn Any) -> Vec<ModeName> { ... }
}

OverlayManager

Customize overlay types (dialogs, command palettes, search).

impl OverlayManager for CustomOverlayManager {
    fn is_active(&self) -> bool { ... }
    fn handle_input(&mut self, key: Key) -> Option<OverlayResult> { ... }
}

EventHandler

Customize how events are routed to handlers.

impl EventHandler<AppEvent> for CustomHandler {
    fn handle(&mut self, event: AppEvent) -> Result<HandleResult> { ... }
}

Example: Multi-Page App

#[derive(Debug, Clone)]
enum MyPage {
    Login(LoginPage),
    Home(HomePage),
    Settings(SettingsPage),
}

fn main() -> Result<()> {
    let mut orch = Orchestrator::builder()
        .with_page("login", LoginPage::new())
        .with_page("home", HomePage::new())
        .with_page("settings", SettingsPage::new())
        .with_default_bindings()
        .build()?;
    
    orch.navigate_to("login")?;
    
    orch.run()?;
}

Navigation with history:

orch.navigate_to("home")?;
orch.navigate_to("settings")?;
orch.back()?  // Return to home

Feature Flags

[dependencies]
tui_orchestrator = { version = "0.1", features = ["std"] }

# Optional features
sequences = ["alloc"]  # Enable multi-key sequences
  • default - No features (pure no_std)
  • std - Enable std library support
  • alloc - Enable alloc support (needed for collections)

Design Philosophy

  1. Plugin-play model - Library is runtime, components are plugins
  2. Sensible defaults - Zero configuration works
  3. Optional everything - Define only what you need
  4. Extension points - Override defaults when needed
  5. User-focused - "register page" not "bind chord to registry"
  6. no_std first - Works on embedded, opt-in std

For komp_ac Integration

komp_ac can:

  1. Implement Component trait for all pages
  2. Use library's Orchestrator as runtime
  3. Extend with custom ModeResolver for dynamic Canvas-style modes
  4. Extend with custom OverlayManager for command palette, find file, search
  5. Extend with custom EventHandler for page/global/canvas routing

Result: komp_ac uses library's core while keeping all custom behavior.

See INTEGRATION_GUIDE.md for details.


Migration Guide

If you're migrating from a TUI built with manual wiring:

  1. Identify components - What are your pages/sections?
  2. Implement Component trait - targets(), handle(), optional hooks
  3. Remove manual orchestration - Delete manual focus/binding/router setup
  4. Use Orchestrator - Register pages and run
  5. Add extensions if needed - ModeResolver, OverlayManager, EventHandler

The library handles everything else.


Examples

See examples/ directory for complete working applications:

  • simple_app.rs - Basic multi-page TUI
  • form_app.rs - Form with text input
  • extended_app.rs - Using extension points

Documentation


License

MIT OR Apache-2.0