323 lines
8.1 KiB
Markdown
323 lines
8.1 KiB
Markdown
# 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
|
|
|
|
```rust
|
|
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
|
|
|
|
```rust
|
|
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.
|
|
|
|
```rust
|
|
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:
|
|
|
|
```rust
|
|
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).
|
|
|
|
```rust
|
|
impl ModeResolver for CustomResolver {
|
|
fn resolve(&self, focus: &dyn Any) -> Vec<ModeName> { ... }
|
|
}
|
|
```
|
|
|
|
### OverlayManager
|
|
|
|
Customize overlay types (dialogs, command palettes, search).
|
|
|
|
```rust
|
|
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.
|
|
|
|
```rust
|
|
impl EventHandler<AppEvent> for CustomHandler {
|
|
fn handle(&mut self, event: AppEvent) -> Result<HandleResult> { ... }
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Example: Multi-Page App
|
|
|
|
```rust
|
|
#[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:
|
|
```rust
|
|
orch.navigate_to("home")?;
|
|
orch.navigate_to("settings")?;
|
|
orch.back()? // Return to home
|
|
```
|
|
|
|
---
|
|
|
|
## Feature Flags
|
|
|
|
```toml
|
|
[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](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
|
|
|
|
- [PLAN.md](PLAN.md) - Complete implementation plan
|
|
- [REDESIGN.md](REDESIGN.md) - Framework architecture deep dive
|
|
- [INTEGRATION_GUIDE.md](INTEGRATION_GUIDE.md) - Integration examples and patterns
|
|
|
|
---
|
|
|
|
## License
|
|
|
|
MIT OR Apache-2.0
|