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 activeon_exit()- Called when component becomes inactiveon_focus()- Called when a focus target gains focuson_blur()- Called when a focus target loses focushandle_text()- Called when character is typedcan_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 trackingFocusQuery- 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 structprocess_frame()- Process one input framerun()- 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 supportalloc- Enable alloc support (needed for collections)
Design Philosophy
- Plugin-play model - Library is runtime, components are plugins
- Sensible defaults - Zero configuration works
- Optional everything - Define only what you need
- Extension points - Override defaults when needed
- User-focused - "register page" not "bind chord to registry"
- no_std first - Works on embedded, opt-in std
For komp_ac Integration
komp_ac can:
- Implement
Componenttrait for all pages - Use library's
Orchestratoras runtime - Extend with custom
ModeResolverfor dynamic Canvas-style modes - Extend with custom
OverlayManagerfor command palette, find file, search - Extend with custom
EventHandlerfor 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:
- Identify components - What are your pages/sections?
- Implement Component trait -
targets(),handle(), optional hooks - Remove manual orchestration - Delete manual focus/binding/router setup
- Use Orchestrator - Register pages and run
- 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 TUIform_app.rs- Form with text inputextended_app.rs- Using extension points
Documentation
- PLAN.md - Complete implementation plan
- REDESIGN.md - Framework architecture deep dive
- INTEGRATION_GUIDE.md - Integration examples and patterns
License
MIT OR Apache-2.0