# TUI Orchestrator Integration Guide This guide shows how to use the TUI Orchestrator framework to build terminal user interfaces with minimal boilerplate. --- ## Quick Start: Your First TUI App ### Step 1: Define Your Component A component represents a page or UI section with focusable elements: ```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> { 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> { match focus { LoginFocus::Username => { self.username.push(ch); Ok(None) } LoginFocus::Password => { self.password.push(ch); Ok(None) } _ => Ok(None), } } fn on_enter(&mut self) -> Result<()> { self.username.clear(); self.password.clear(); Ok(()) } } ``` ### Step 2: 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 - Focus management (Tab/Shift+Tab navigation) - Button activation (Enter key) - Text input typing - Page lifecycle (on_enter/on_exit) --- ## Component Trait Deep Dive ### Associated Types ```rust pub trait Component { type Focus: FocusId + Clone; // What can be focused in this component type Action: Action + Clone; // What actions this component handles type Event: Clone + Debug; // Events this component emits } ``` ### Required Methods **`targets(&self) -> &[Self::Focus]`** Returns all focusable elements. Order determines navigation sequence (next/prev). ```rust fn targets(&self) -> &[Self::Focus] { &[ Focus::Username, Focus::Password, Focus::LoginButton, Focus::CancelButton, ] } ``` **`handle(&mut self, focus: &Self::Focus, action: Self::Action) -> Result>`** Called when a bound action occurs. Returns optional event for application to handle. ```rust fn handle(&mut self, focus: &Self::Focus, action: Self::Action) -> Result> { match (focus, action) { (Focus::Submit, ComponentAction::Select) => Ok(Some(Event::Submit)), _ => Ok(None), } } ``` ### Optional Methods All have default implementations—only override what you need. **`on_enter(&mut self) -> Result<()>`** Called when component becomes active (page is navigated to). Good for resetting state. **`on_exit(&mut self) -> Result<()>`** Called when component becomes inactive (page is navigated away). Good for cleanup. **`on_focus(&mut self, focus: &Self::Focus) -> Result<()>`** Called when a specific focus target gains focus. **`on_blur(&mut self, focus: &Self::Focus) -> Result<()>`** Called when a specific focus target loses focus. **`handle_text(&mut self, focus: &Self::Focus, ch: char) -> Result>`** Called when character is typed (not a bound action). Only called for text-friendly focus targets. **`can_navigate_forward(&self, focus: &Self::Focus) -> bool`** Return false to prevent Next action from moving focus (useful for boundary detection). **`can_navigate_backward(&self, focus: &Self::Focus) -> bool`** Return false to prevent Prev action from moving focus. --- ## Standard Component Actions The library provides these actions automatically bound to keys: | Action | Default Key | Description | |--------|--------------|-------------| | `ComponentAction::Next` | Tab | Move focus to next target | | `ComponentAction::Prev` | Shift+Tab | Move focus to previous target | | `ComponentAction::First` | Home | Move focus to first target | | `ComponentAction::Last` | End | Move focus to last target | | `ComponentAction::Select` | Enter | Activate current focus target | | `ComponentAction::Cancel` | Esc | Cancel or close | | `ComponentAction::TypeChar(c)` | Any character | Type character | | `ComponentAction::Backspace` | Backspace | Delete character before cursor | | `ComponentAction::Delete` | Delete | Delete character at cursor | | `ComponentAction::Custom(n)` | None | User-defined action | ### Customizing Bindings ```rust let mut orch = Orchestrator::new(); // Override default bindings orch.bindings().bind(Key::ctrl('s'), ComponentAction::Custom(0)); // Custom save action orch.bindings().bind(Key::char(':'), ComponentAction::Custom(1)); // Enter command mode ``` --- ## Orchestrator API ### Basic Setup ```rust let mut orch = Orchestrator::new(); // Register pages orch.register_page("login", LoginPage::new())?; orch.register_page("home", HomePage::new())?; // Navigation orch.navigate_to("login")?; ``` ### Processing Input ```rust loop { let key = read_key()?; let events = orch.process_frame(key)?; for event in events { match event { LoginEvent::AttemptLogin => do_login(username, password), LoginEvent::Cancel => return Ok(()), } } render(&orch)?; } ``` ### Reading State ```rust // Get current page if let Some(page) = orch.current_page() { // Access page... } // Get current focus if let Some(focus) = orch.focus().current() { // Check what's focused... } // Create query snapshot let query = orch.focus().query(); if query.is_focused(&LoginFocus::Username) { // Username field is focused... } ``` --- ## Multiple Pages Example ```rust #[derive(Debug, Clone)] enum MyPage { Login(LoginPage), Home(HomePage), Settings(SettingsPage), } fn main() -> Result<()> { let mut orch = Orchestrator::new(); orch.register_page("login", LoginPage::new())?; orch.register_page("home", HomePage::new())?; orch.register_page("settings", SettingsPage::new())?; orch.navigate_to("login")?; orch.run()?; } ``` Navigation with history: ```rust orch.navigate_to("home")?; orch.navigate_to("settings")?; orch.back()? // Return to home ``` --- ## Extension: Custom Mode Resolution For apps with complex mode systems (like komp_ac): ```rust pub struct CustomModeResolver { state: AppState, } impl ModeResolver for CustomModeResolver { fn resolve(&self, focus: &dyn Any) -> alloc::vec::Vec { match focus.downcast_ref::() { Some(FocusTarget::CanvasField(_)) => { // Dynamic mode based on editor state vec![self.state.editor_mode(), ModeName::Common, ModeName::Global] } _ => vec![ModeName::General, ModeName::Global], } } } let mut orch = Orchestrator::new(); orch.set_mode_resolver(CustomModeResolver::new(state)); ``` --- ## Extension: Custom Overlays For apps with complex overlay types (command palette, dialogs): ```rust pub struct CustomOverlayManager { command_palette: CommandPalette, dialogs: Vec, } impl OverlayManager for CustomOverlayManager { fn is_active(&self) -> bool { self.command_palette.is_active() || !self.dialogs.is_empty() } fn handle_input(&mut self, key: Key) -> Option { if let Some(result) = self.command_palette.handle_input(key) { return Some(result); } // Handle dialogs... None } } let mut orch = Orchestrator::new(); orch.set_overlay_manager(CustomOverlayManager::new()); ``` --- ## Integration with External Libraries ### Reading Input from crossterm ```rust use crossterm::event; use tui_orchestrator::input::Key; impl InputSource for CrosstermInput { fn read_key(&mut self) -> Result { match event::read()? { event::Event::Key(key_event) => { let code = match key_event.code { event::KeyCode::Char(c) => KeyCode::Char(c), event::KeyCode::Enter => KeyCode::Enter, event::KeyCode::Tab => KeyCode::Tab, event::KeyCode::Esc => KeyCode::Esc, // ... map all codes ... }; let modifiers = KeyModifiers { control: key_event.modifiers.contains(event::KeyModifiers::CONTROL), alt: key_event.modifiers.contains(event::KeyModifiers::ALT), shift: key_event.modifiers.contains(event::KeyModifiers::SHIFT), }; Ok(Key::new(code, modifiers)) } _ => Err(Error::NotAKeyEvent), } } } ``` ### Using with ratatui for Rendering The library is backend-agnostic—you can render with any framework: ```rust use ratatui::backend::CrosstermBackend; use ratatui::Terminal; use tui_orchestrator::prelude::*; struct MyApp { orch: Orchestrator<...>, terminal: Terminal>, } impl MyApp { fn render(&mut self) -> Result<()> { self.terminal.draw(|f| { let focus = self.orch.focus().query(); let page = self.orch.current_page().unwrap(); // Render page with focus context page.render(f, &focus); })?; } fn run(&mut self) -> Result<()> { loop { let key = self.orch.read_key()?; let events = self.orch.process_frame(key)?; for event in events { self.handle_event(event)?; } self.render()?; } } } ``` --- ## Testing Components ### Unit Tests Test component logic in isolation: ```rust #[cfg(test)] mod tests { use super::*; #[test] fn test_login_button_action() { let mut page = LoginPage::new(); let focus = LoginFocus::LoginButton; let action = ComponentAction::Select; let event = page.handle(&focus, action).unwrap(); assert!(matches!(event, Some(LoginEvent::AttemptLogin { .. }))); } } ``` ### Integration Tests Test with orchestrator: ```rust #[test] fn test_full_login_flow() { let mut orch = Orchestrator::new(); orch.register_page("login", LoginPage::new()).unwrap(); // Simulate tab navigation let _ = orch.process_frame(Key::tab()).unwrap(); assert_eq!(orch.focus().current(), Some(&LoginFocus::Password)); // Simulate typing let _ = orch.process_frame(Key::char('p')).unwrap(); let _ = orch.process_frame(Key::char('a')).unwrap(); let _ = orch.process_frame(Key::char('s')).unwrap(); let _ = orch.process_frame(Key::char('s')).unwrap(); // Simulate enter let events = orch.process_frame(Key::enter()).unwrap(); assert_eq!(events.len(), 1); assert!(matches!(events[0], LoginEvent::AttemptLogin { .. })); } ``` --- ## Migration from Existing Code ### Migrating from Manual Wiring **Before:** ```rust // Manual setup let mut focus = FocusManager::new(); let mut bindings = Bindings::new(); let mut router = Router::new(); let mut page = LoginPage::new(); focus.set_targets(page.targets()); bindings.bind(Key::tab(), MyAction::Next); // Manual loop loop { let key = read_key()?; if let Some(action) = bindings.handle(key) { match action { MyAction::Next => focus.next(), MyAction::Select => { let focused = focus.current()?; page.handle_button(focused)?; } } } } ``` **After:** ```rust // Framework setup let mut orch = Orchestrator::builder() .with_page("login", LoginPage::new()) .build()?; orch.run()?; ``` ### Keeping Custom Behavior If your existing code has custom behavior (like komp_ac's mode resolution), use extension points: ```rust let mut orch = Orchestrator::new() .with_mode_resolver(CustomModeResolver::new(state)) .with_overlay_manager(CustomOverlayManager::new()) .with_event_handler(CustomEventHandler::new(router)); ``` --- ## Best Practices ### 1. Keep Components Focused Components should handle their own logic only. Don't directly manipulate other components. ### 2. Use Events for Communication Components should emit events, not directly call methods on other components. ### 3. Respect Optional Methods Only override lifecycle hooks when you need them. Default implementations are fine for most cases. ### 4. Test Component Isolation Test components without orchestrator to ensure logic is correct. ### 5. Leverage Default Bindings Use `with_default_bindings()` unless you have specific keybinding requirements. ### 6. Use Extension Points Wisely Only implement custom resolvers/handlers when default behavior doesn't meet your needs. --- ## Summary The TUI Orchestrator framework provides: 1. **Zero boilerplate** - Define components, library handles the rest 2. **Sensible defaults** - Works without configuration 3. **Full extension** - Customize via traits when needed 4. **Backend-agnostic** - Works with any rendering library 5. **no_std compatible** - Runs on embedded systems and WASM Your job: define components with buttons and logic. Our job: make it just work.