### Run Tui-Realm Demo Example Source: https://github.com/veeso/tui-realm/blob/main/README.md Execute the demo example using Cargo to see tui-realm in action. ```sh cargo run --example demo ``` -------------------------------- ### Initialize TerminalBridge Source: https://github.com/veeso/tui-realm/blob/main/docs/en/migrating-legacy.md Replace manual terminal setup with the TerminalBridge abstraction layer. ```rust use tuirealm::terminal::TerminalBridge; Context { // ... terminal: TerminalBridge::new().expect("Could not initialize terminal"), } ``` -------------------------------- ### Full Tui-Realm Application Example Source: https://context7.com/veeso/tui-realm/llms.txt This Rust code implements a complete tui-realm application. It defines components, messages, and the application model, demonstrating how to initialize, run, and update a terminal UI application. Ensure you have the `tuirealm` crate and its dependencies set up. ```rust use std::time::Duration; use tuirealm::{ Application, AttrValue, Attribute, Component, Event, EventListenerCfg, MockComponent, NoUserEvent, Props, State, StateValue, Sub, SubClause, SubEventClause, Update, }; use tuirealm::application::PollStrategy; use tuirealm::command::{Cmd, CmdResult}; use tuirealm::event::{Key, KeyEvent, KeyModifiers}; use tuirealm::props::{Alignment, Borders, Color, TextModifiers}; use tuirealm::ratatui::layout::{Constraint, Direction, Layout, Rect}; use tuirealm::ratatui::widgets::{Block, BorderType, Paragraph}; use tuirealm::terminal::TerminalBridge; // Component IDs #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub enum Id { Counter, Label } // Messages #[derive(Debug, PartialEq)] pub enum Msg { AppClose, CounterChanged(isize), CounterBlur } // MockComponent implementation #[derive(Default)] struct CounterComponent { props: Props, value: isize } impl MockComponent for CounterComponent { fn view(&mut self, frame: &mut tuirealm::Frame, area: Rect) { let focused = self.props.get_or(Attribute::Focus, AttrValue::Flag(false)).unwrap_flag(); let block = Block::default() .borders(tuirealm::ratatui::widgets::Borders::ALL) .border_type(if focused { BorderType::Thick } else { BorderType::Rounded }) .title("Counter"); frame.render_widget( Paragraph::new(format!("Value: {}", self.value)).block(block), area, ); } fn query(&self, attr: Attribute) -> Option { self.props.get(attr) } fn attr(&mut self, attr: Attribute, value: AttrValue) { self.props.set(attr, value); } fn state(&self) -> State { State::One(StateValue::Isize(self.value)) } fn perform(&mut self, cmd: Cmd) -> CmdResult { match cmd { Cmd::Submit => { self.value += 1; CmdResult::Changed(self.state()) } _ => CmdResult::None, } } } // Component wrapper #[derive(MockComponent)] pub struct Counter { component: CounterComponent } impl Counter { pub fn new(initial: isize) -> Self { Self { component: CounterComponent { props: Props::default(), value: initial } } } } impl Component for Counter { fn on(&mut self, ev: Event) -> Option { match ev { Event::Keyboard(KeyEvent { code: Key::Enter, .. }) => { if let CmdResult::Changed(State::One(StateValue::Isize(v))) = self.perform(Cmd::Submit) { return Some(Msg::CounterChanged(v)); } None } Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => Some(Msg::CounterBlur), Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => Some(Msg::AppClose), _ => None, } } } // Application Model pub struct Model { app: Application, quit: bool, terminal: TerminalBridge, } impl Model { pub fn new() -> Self { let mut app = Application::init( EventListenerCfg::default() .crossterm_input_listener(Duration::from_millis(20), 3) .poll_timeout(Duration::from_millis(10)), ); app.mount(Id::Counter, Box::new(Counter::new(0)), vec![]).unwrap(); app.active(&Id::Counter).unwrap(); Self { app, quit: false, terminal: TerminalBridge::init_crossterm().unwrap(), } } pub fn run(&mut self) { while !self.quit { if let Ok(msgs) = self.app.tick(PollStrategy::Once) { for msg in msgs { self.update(Some(msg)); } } self.view(); } self.terminal.restore().ok(); } fn view(&mut self) { let app = &mut self.app; self.terminal.draw(|f| { app.view(&Id::Counter, f, f.area()); }).ok(); } } impl Update for Model { fn update(&mut self, msg: Option) -> Option { match msg { Some(Msg::AppClose) => { self.quit = true; None } Some(Msg::CounterChanged(_)) => None, Some(Msg::CounterBlur) => None, None => None, } } } fn main() { Model::new().run(); } ``` -------------------------------- ### Implement a custom component Source: https://github.com/veeso/tui-realm/blob/main/docs/en/get-started.md Example structure for a custom component that wraps a MockComponent. ```rust pub struct UsernameInput { component: Input, // Where input implements `MockComponent` } impl Component for UsernameInput { ... } ``` -------------------------------- ### Application::init - Initialize the Application Source: https://context7.com/veeso/tui-realm/llms.txt Initializes a new tui-realm application with a configured event listener. This function sets up how events like keyboard input and tick events are polled and forwarded to components. ```APIDOC ## Application::init - Initialize the Application ### Description Initializes a new tui-realm application with a configured event listener. This function sets up how events like keyboard input and tick events are polled and forwarded to components. ### Method ```rust Application::init ``` ### Parameters #### Request Body - **event_listener_cfg** (EventListenerCfg) - Required - Configuration for the event listener, defining polling intervals and types. ### Request Example ```rust use std::time::Duration; use tuirealm::{Application, EventListenerCfg, NoUserEvent}; // Define component IDs and messages #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub enum Id { Counter, Label, } #[derive(Debug, PartialEq)] pub enum Msg { AppClose, CounterChanged(isize), } // Initialize application with crossterm backend let mut app: Application = Application::init( EventListenerCfg::default() .crossterm_input_listener(Duration::from_millis(20), 3) // Poll keyboard every 20ms, max 3 events .poll_timeout(Duration::from_millis(10)) // Timeout for event polling .tick_interval(Duration::from_secs(1)), // Emit Tick event every second ); ``` ### Response #### Success Response (200) - **Application** (Application) - The initialized tui-realm application instance. #### Response Example ```rust // Application is now ready to mount components and run tick loop ``` ``` -------------------------------- ### Initialize tui-realm Application Source: https://context7.com/veeso/tui-realm/llms.txt Sets up a new tui-realm application with a configured event listener. Requires an EventListenerCfg to define event polling behavior for keyboard input, tick events, and custom ports. ```rust use std::time::Duration; use tuirealm::{Application, EventListenerCfg, NoUserEvent}; // Define component IDs and messages #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub enum Id { Counter, Label, } #[derive(Debug, PartialEq)] pub enum Msg { AppClose, CounterChanged(isize), } // Initialize application with crossterm backend let mut app: Application = Application::init( EventListenerCfg::default() .crossterm_input_listener(Duration::from_millis(20), 3) // Poll keyboard every 20ms, max 3 events .poll_timeout(Duration::from_millis(10)) // Timeout for event polling .tick_interval(Duration::from_secs(1)), // Emit Tick event every second ); // Application is now ready to mount components and run tick loop ``` -------------------------------- ### Initialize tui-realm Application Source: https://github.com/veeso/tui-realm/blob/main/docs/en/get-started.md Sets up the tui-realm application with a default crossterm input listener and a tick interval of 1 second. NoUserEvent is used when custom user events are not needed. ```rust fn init_app() -> Application { // Setup application // NOTE: NoUserEvent is a shorthand to tell tui-realm we're not going to use any custom user event // NOTE: the event listener is configured to use the default crossterm input listener and to raise a Tick event each second // which we will use to update the clock let mut app: Application = Application::init( EventListenerCfg::default() .default_input_listener(Duration::from_millis(20)) .poll_timeout(Duration::from_millis(10)) .tick_interval(Duration::from_secs(1)), ); } ``` -------------------------------- ### Manage Subscriptions Source: https://github.com/veeso/tui-realm/blob/main/docs/en/advanced.md Methods for mounting, adding, and removing subscriptions from an application. ```rust app.mount( Id::Clock, Box::new( Clock::new(SystemTime::now()) .alignment(Alignment::Center) .background(Color::Reset) .foreground(Color::Cyan) .modifiers(TextModifiers::BOLD) ), vec![Sub::new(SubEventClause::Tick, SubClause::Always)] ); ``` ```rust app.subscribe(&Id::Clock, Sub::new(SubEventClause::Tick, SubClause::Always)); ``` ```rust app.unsubscribe(&Id::Clock, SubEventClause::Tick); ``` -------------------------------- ### Implement Command API Source: https://github.com/veeso/tui-realm/blob/main/docs/en/advanced.md Handles incoming commands to update component state and return results. ```rust impl MockComponent for Radio { // ... fn perform(&mut self, cmd: Cmd) -> CmdResult { match cmd { Cmd::Move(Direction::Right) => { // Increment choice self.states.next_choice(); // Return CmdResult On Change CmdResult::Changed(self.state()) } Cmd::Move(Direction::Left) => { // Decrement choice self.states.prev_choice(); // Return CmdResult On Change CmdResult::Changed(self.state()) } Cmd::Submit => { // Return Submit CmdResult::Submit(self.state()) } _ => CmdResult::None, } } // ... } ``` -------------------------------- ### Implement Counter Builder Methods Source: https://github.com/veeso/tui-realm/blob/main/docs/en/get-started.md Provides fluent builder methods to configure component attributes like title, value, and styling. ```rust impl Counter { pub fn label(mut self, label: S) -> Self where S: AsRef, { self.attr( Attribute::Title, AttrValue::Title((label.as_ref().to_string(), Alignment::Center)), ); self } pub fn value(mut self, n: isize) -> Self { self.attr(Attribute::Value, AttrValue::Number(n)); self } pub fn alignment(mut self, a: Alignment) -> Self { self.attr(Attribute::TextAlign, AttrValue::Alignment(a)); self } pub fn foreground(mut self, c: Color) -> Self { self.attr(Attribute::Foreground, AttrValue::Color(c)); self } pub fn background(mut self, c: Color) -> Self { self.attr(Attribute::Background, AttrValue::Color(c)); self } pub fn modifiers(mut self, m: TextModifiers) -> Self { self.attr(Attribute::TextProps, AttrValue::TextModifiers(m)); self } pub fn borders(mut self, b: Borders) -> Self { self.attr(Attribute::Borders, AttrValue::Borders(b)); self } } ``` -------------------------------- ### Implement Model View Method Source: https://github.com/veeso/tui-realm/blob/main/docs/en/get-started.md Implements the `view` method for the `Model` struct. This method is responsible for rendering the application's user interface using the provided terminal and layout constraints. It requires the `ratatui` crate. ```rust impl Model { pub fn view(&mut self, app: &mut Application) { assert!(self .terminal .raw_mut() .draw(|f| { let chunks = Layout::default() .direction(Direction::Vertical) .margin(1) .constraints( [ Constraint::Length(3), // Letter Counter Constraint::Length(3), // Digit Counter ] .as_ref(), ) .split(f.size()); app.view(&Id::LetterCounter, f, chunks[0]); app.view(&Id::DigitCounter, f, chunks[1]); }) .is_ok()); } } ``` -------------------------------- ### Application::mount - Mount Components to View Source: https://context7.com/veeso/tui-realm/llms.txt Adds a component to the application view with an optional list of subscriptions. Components are identified by a unique ID and can only be mounted once. ```APIDOC ## Application::mount - Mount Components to View ### Description Adds a component to the application view with an optional list of subscriptions. Components are identified by a unique ID and can only be mounted once. Subscriptions define which events should be forwarded to the component even when it doesn't have focus. ### Method ```rust app.mount(id: Id, component: Box>, subscriptions: Vec>) ``` ### Parameters #### Path Parameters - **id** (Id) - Required - A unique identifier for the component. - **component** (Box>) - Required - The component instance to mount. - **subscriptions** (Vec>) - Optional - A list of subscriptions defining which events the component should receive. ### Request Example ```rust use tuirealm::{Sub, SubClause, SubEventClause}; // Mount a component without subscriptions app.mount( Id::Counter, Box::new(MyCounter::new(0)), Vec::default(), // No subscriptions ).expect("Failed to mount counter"); // Mount a component with tick subscription (receives Event::Tick even without focus) app.mount( Id::Clock, Box::new(Clock::new()), vec![Sub::new(SubEventClause::Tick, SubClause::Always)], ).expect("Failed to mount clock"); // Mount with conditional subscription (only forward when condition is met) app.mount( Id::StatusBar, Box::new(StatusBar::new()), vec![Sub::new( SubEventClause::Any, // Any event type SubClause::HasAttrValue( Id::Counter, Attribute::Focus, AttrValue::Flag(true) ), // Only when Counter has focus )], ).expect("Failed to mount status bar"); ``` ### Response #### Success Response (200) - **()** - Indicates successful mounting of the component. #### Response Example ```rust // Component successfully mounted. ``` ``` -------------------------------- ### Implement View Rendering Source: https://github.com/veeso/tui-realm/blob/main/docs/en/advanced.md Renders the component using the current state and properties. ```rust impl MockComponent for Radio { fn view(&mut self, render: &mut Frame, area: Rect) { if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) { // Make choices let choices: Vec = self .states .choices .iter() .map(|x| Spans::from(x.clone())) .collect(); let foreground = self .props .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset)) .unwrap_color(); let background = self .props .get_or(Attribute::Background, AttrValue::Color(Color::Reset)) .unwrap_color(); let borders = self .props .get_or(Attribute::Borders, AttrValue::Borders(Borders::default())) .unwrap_borders(); let title = self.props.get(Attribute::Title).map(|x| x.unwrap_title()); let focus = self .props .get_or(Attribute::Focus, AttrValue::Flag(false)) .unwrap_flag(); let div = crate::utils::get_block(borders, title, focus, None); // Make colors let (bg, fg, block_color): (Color, Color, Color) = match focus { true => (foreground, background, foreground), false => (Color::Reset, foreground, Color::Reset), }; let radio: Tabs = Tabs::new(choices) .block(div) .select(self.states.choice) .style(Style::default().fg(block_color)) .highlight_style(Style::default().fg(fg).bg(bg)); render.render_widget(radio, area); } } // ... } ``` -------------------------------- ### Mount Components to Application Source: https://github.com/veeso/tui-realm/blob/main/docs/en/get-started.md Mounts two components, LetterCounter and DigitCounter, into the tui-realm application. The empty vectors represent no subscriptions for these components. ```rust assert!(app .mount( Id::LetterCounter, Box::new(LetterCounter::new(0)), Vec::default() ) .is_ok()); assert!(app .mount( Id::DigitCounter, Box::new(DigitCounter::new(5)), Vec::default() ) .is_ok()); ``` -------------------------------- ### Implement State Method Source: https://github.com/veeso/tui-realm/blob/main/docs/en/advanced.md Exposes the current component state for the tui-realm framework. ```rust impl MockComponent for Radio { // ... fn state(&self) -> State { State::One(StateValue::Usize(self.states.choice)) } // ... } ``` -------------------------------- ### Mount Components to tui-realm View Source: https://context7.com/veeso/tui-realm/llms.txt Adds components to the application view with optional subscriptions. Components are identified by a unique ID and can only be mounted once. Subscriptions determine which events are forwarded to a component. ```rust use tuirealm::{Sub, SubClause, SubEventClause}; // Mount a component without subscriptions app.mount( Id::Counter, Box::new(MyCounter::new(0)), Vec::default(), // No subscriptions ).expect("Failed to mount counter"); // Mount a component with tick subscription (receives Event::Tick even without focus) app.mount( Id::Clock, Box::new(Clock::new()), vec![Sub::new(SubEventClause::Tick, SubClause::Always)], ).expect("Failed to mount clock"); // Mount with conditional subscription (only forward when condition is met) app.mount( Id::StatusBar, Box::new(StatusBar::new()), vec![Sub::new( SubEventClause::Any, // Any event type SubClause::HasAttrValue( Id::Counter, Attribute::Focus, AttrValue::Flag(true) ), // Only when Counter has focus )], ).expect("Failed to mount status bar"); ``` -------------------------------- ### Configure Event Subscriptions for Components Source: https://context7.com/veeso/tui-realm/llms.txt Set up subscriptions to allow components to receive events, even when unfocused. Subscriptions use `EventClause` and `SubClause` for flexible event filtering and conditional logic. Ensure correct imports for `Sub`, `SubClause`, and `SubEventClause`. ```rust use tuirealm::{Sub, SubClause, SubEventClause}; use tuirealm::event::{Key, KeyEvent}; // Always receive Tick events (useful for clocks, animations) let tick_sub = Sub::new(SubEventClause::Tick, SubClause::Always); // Receive specific key events let esc_sub = Sub::new( SubEventClause::Keyboard(KeyEvent::from(Key::Esc)), SubClause::Always, ); // Conditional subscription (only when another component has focus) let conditional_sub = Sub::new( SubEventClause::Any, SubClause::HasAttrValue( Id::MainInput, Attribute::Focus, AttrValue::Flag(true), ), ); // Complex conditions with And/Or/Not let complex_sub = Sub::new( SubEventClause::Tick, SubClause::And( Box::new(SubClause::IsMounted(Id::StatusBar)), Box::new(SubClause::Not( Box::new(SubClause::HasState( Id::Counter, State::One(StateValue::Isize(0)) )) )), ), ); // Mount component with subscriptions app.mount(Id::GlobalHandler, Box::new(handler), vec![tick_sub, esc_sub])?; // Add subscription after mounting app.subscribe(&Id::StatusBar, conditional_sub)?; // Remove subscription app.unsubscribe(&Id::StatusBar, SubEventClause::Tick)?; ``` -------------------------------- ### Render Components with view Source: https://context7.com/veeso/tui-realm/llms.txt The view method renders components into specific terminal layout chunks during the draw phase. ```rust use tuirealm::terminal::TerminalBridge; use tuirealm::ratatui::layout::{Layout, Constraint, Direction}; // Initialize terminal let mut terminal = TerminalBridge::init_crossterm().expect("Cannot init terminal"); // Render function fn view(app: &mut Application, terminal: &mut TerminalBridge) { terminal.draw(|f| { // Create layout let chunks = Layout::default() .direction(Direction::Vertical) .margin(1) .constraints([ Constraint::Length(3), // Clock Constraint::Length(3), // Counter Constraint::Length(1), // Label ]) .split(f.area()); // Render each component in its area app.view(&Id::Clock, f, chunks[0]); app.view(&Id::Counter, f, chunks[1]); app.view(&Id::Label, f, chunks[2]); }).expect("Failed to draw"); } ``` -------------------------------- ### Add Tui-Realm with Termion Backend Source: https://github.com/veeso/tui-realm/blob/main/README.md Configure Cargo.toml to use the termion backend along with derive and disabling default features. ```toml tuirealm = { version = "3", default-features = false, features = [ "derive", "termion" ] } ``` -------------------------------- ### Add Tui-Realm with Crossterm Backend Source: https://github.com/veeso/tui-realm/blob/main/README.md Configure Cargo.toml to use the crossterm backend along with derive and disabling default features. ```toml tuirealm = { version = "3", default-features = false, features = [ "derive", "crossterm" ]} ``` -------------------------------- ### Configure Terminal for tui-realm Source: https://github.com/veeso/tui-realm/blob/main/docs/en/get-started.md Enables alternate screen mode and raw mode for the terminal to prepare it for tui-realm rendering. ```rust let _ = model.terminal.enter_alternate_screen(); let _ = model.terminal.enable_raw_mode(); ``` -------------------------------- ### Component Properties and State Source: https://context7.com/veeso/tui-realm/llms.txt Methods for setting and retrieving component attributes and state values. ```APIDOC ## Application::attr ### Description Sets a specific attribute for a component. ### Parameters - **id** (Id) - Required - The component ID. - **attr** (Attribute) - Required - The attribute to set. - **value** (AttrValue) - Required - The value to assign to the attribute. ## Application::query ### Description Retrieves the value of a specific attribute from a component. ## Application::state ### Description Retrieves the current state of a component. ``` -------------------------------- ### Implement Component Trait Source: https://github.com/veeso/tui-realm/blob/main/docs/en/migrating-legacy.md Transition from props builders to the Component trait implementation. ```rust InputPropsBuilder::default() .with_foreground(fg) .with_borders(Borders::ALL, BorderType::Rounded, fg) .with_label(label, Alignment::Left) .with_input(typ); ``` -------------------------------- ### MockComponent perform method signature Source: https://github.com/veeso/tui-realm/blob/main/docs/en/get-started.md The `perform` method of `MockComponent` consumes a `Cmd` and produces a `CmdResult`. ```rust fn perform(&mut self, cmd: Cmd) -> CmdResult; ``` -------------------------------- ### Application::view - Render Components Source: https://context7.com/veeso/tui-realm/llms.txt Renders a mounted component within a specified terminal area. ```APIDOC ## Application::view ### Description Renders a component within a designated layout region during the draw phase. ### Parameters - **id** (Id) - Required - The component ID to render. - **f** (Frame) - Required - The terminal frame. - **area** (Rect) - Required - The area within the frame to render the component. ``` -------------------------------- ### Initialize and Manage Terminal with TerminalBridge Source: https://context7.com/veeso/tui-realm/llms.txt Use `TerminalBridge` for cross-platform terminal operations. It handles raw mode, alternate screen, and drawing. Ensure proper initialization and cleanup to restore the terminal state. ```rust use tuirealm::terminal::TerminalBridge; // Quick initialization with defaults (raw mode, alternate screen, panic hook) let mut terminal = TerminalBridge::init_crossterm() .expect("Cannot initialize terminal"); // Or manual setup for more control let mut terminal = TerminalBridge::new_crossterm() .expect("Cannot create terminal"); terminal.enable_raw_mode().expect("Cannot enable raw mode"); terminal.enter_alternate_screen().expect("Cannot enter alternate screen"); // Enable mouse capture (optional) terminal.enable_mouse_capture().ok(); // Draw to terminal terminal.draw(|frame| { // Render widgets here frame.render_widget(paragraph, frame.area()); }).expect("Cannot draw"); // Cleanup on exit terminal.restore().expect("Cannot restore terminal"); // Or manually: terminal.leave_alternate_screen().ok(); terminal.disable_raw_mode().ok(); terminal.clear_screen().ok(); ``` -------------------------------- ### Implement Component for Event Handling Source: https://context7.com/veeso/tui-realm/llms.txt Wraps a MockComponent to handle application-specific events and map command results to application messages. ```rust use tuirealm::{Component, Event, NoUserEvent, MockComponent}; use tuirealm::event::{Key, KeyEvent, KeyModifiers}; use tuirealm::command::{Cmd, CmdResult, Direction}; // Use derive macro to auto-implement MockComponent delegation #[derive(MockComponent)] pub struct MyCounter { component: Counter, // The underlying MockComponent } impl MyCounter { pub fn new(initial: isize) -> Self { Self { component: Counter::default() .value(initial) .foreground(Color::Green), } } } impl Component for MyCounter { fn on(&mut self, ev: Event) -> Option { // Convert Event to Cmd let cmd = match ev { Event::Keyboard(KeyEvent { code: Key::Up, .. }) => Cmd::Move(Direction::Up), Event::Keyboard(KeyEvent { code: Key::Down, .. }) => Cmd::Move(Direction::Down), Event::Keyboard(KeyEvent { code: Key::Enter, .. }) => Cmd::Submit, Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => { return Some(Msg::CounterBlur); // Directly return message } Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { return Some(Msg::AppClose); } _ => Cmd::None, }; // Perform command and map result to message match self.perform(cmd) { CmdResult::Changed(State::One(StateValue::Isize(v))) => { Some(Msg::CounterChanged(v)) } _ => None, } } } ``` -------------------------------- ### Update Cargo.toml dependencies Source: https://github.com/veeso/tui-realm/blob/main/docs/en/migrating-legacy.md Configure dependencies for crossterm or termion backends, and include the standard library. ```toml tuirealm = "^1.0.0" ``` ```toml tuirealm = { "version" = "^1.0.0", default-features = false, features = [ "derive", "with-termion" ] } ``` ```toml tui-realm-stdlib = "^1.0.0" ``` ```toml tui-realm-stdlib = { "version" = "^1.0.0", default-features = false, features = [ "with-termion" ] } ``` -------------------------------- ### Finalize Terminal Configuration Source: https://github.com/veeso/tui-realm/blob/main/docs/en/get-started.md Restores the terminal to its original state by leaving the alternate screen, disabling raw mode, and clearing the screen. ```rust let _ = model.terminal.leave_alternate_screen(); let _ = model.terminal.disable_raw_mode(); let _ = model.terminal.clear_screen(); ``` -------------------------------- ### Define Subscription Structure Source: https://github.com/veeso/tui-realm/blob/main/docs/en/advanced.md The base structure for a subscription, requiring an EventClause and a SubClause. ```rust pub struct Sub(EventClause, SubClause) where UserEvent: Eq + PartialEq + Clone + PartialOrd; ``` -------------------------------- ### AddressInput Component Implementation Source: https://github.com/veeso/tui-realm/blob/main/docs/en/migrating-legacy.md Defines an AddressInput component using tui_realm_stdlib's Input. This component is configured with specific styling, borders, input type, placeholder, and title. It handles various keyboard events for text input and navigation. ```rust use tui_realm_stdlib::Input; #[derive(MockComponent)] pub struct AddressInput { component: Input, } impl Default for AddressInput { fn default() -> Self { Self { component: Input::default() .foreground(Color::LightBlue) .borders( Borders::default() .color(Color::LightBlue) .modifiers(BorderType::Rounded), ) .input_type(InputType::Text) .placeholder( "192.168.1.10", Style::default().fg(Color::Rgb(120, 120, 120)), ) .title("Remote address", Alignment::Left), } } } impl Component for AddressInput { fn on(&mut self, ev: Event) -> Option { let result = match ev { Event::Keyboard(KeyEvent { code: Key::Enter, modifiers: KeyModifiers::NONE, }) => return Some(Msg::FormSubmit), Event::Keyboard(KeyEvent { code: Key::Char(ch), modifiers: KeyModifiers::NONE, }) => self.perform(Cmd::Type(ch)), Event::Keyboard(KeyEvent { code: Key::Left, modifiers: KeyModifiers::NONE, }) => self.perform(Cmd::Move(Direction::Left)), Event::Keyboard(KeyEvent { code: Key::Right, modifiers: KeyModifiers::NONE, }) => self.perform(Cmd::Move(Direction::Right)), Event::Keyboard(KeyEvent { code: Key::Home, modifiers: KeyModifiers::NONE, }) => self.perform(Cmd::GoTo(Position::Begin)), Event::Keyboard(KeyEvent { code: Key::End, modifiers: KeyModifiers::NONE, }) => self.perform(Cmd::GoTo(Position::End)), Event::Keyboard(KeyEvent { code: Key::Delete, modifiers: KeyModifiers::NONE, }) => self.perform(Cmd::Cancel), Event::Keyboard(KeyEvent { code: Key::Backspace, modifiers: KeyModifiers::NONE, }) => self.perform(Cmd::Delete), Event::Keyboard(KeyEvent { code: Key::Tab, modifiers: KeyModifiers::NONE, }) => return Some(Msg::AddressInputBlur), _ => return None, }; Some(Msg::None) } } ``` -------------------------------- ### Add Tui-Realm with Default Features Source: https://github.com/veeso/tui-realm/blob/main/README.md Include this in your Cargo.toml to add the default features of tui-realm. ```toml tuirealm = "3" ``` -------------------------------- ### Configure Application with Custom Port Source: https://github.com/veeso/tui-realm/blob/main/docs/en/advanced.md Set up the tui-realm application with custom ports by providing a boxed implementation of the Poll trait and an associated polling interval. ```rust let mut app: Application = Application::init( EventListenerCfg::default() .default_input_listener(Duration::from_millis(10)) .port( Box::new(MyHttpClient::new(/* ... */)), Duration::from_millis(100), ), ); ``` -------------------------------- ### Main Application Loop Source: https://github.com/veeso/tui-realm/blob/main/docs/en/get-started.md The main loop of the tui-realm application. It handles ticks, processes messages, and redraws the view when necessary. The loop continues until the 'quit' flag is set to true. ```rust while !model.quit { // Tick match app.tick(&mut model, PollStrategy::Once) { Err(err) => { // Handle error... } Ok(messages) if messages.len() > 0 => { // NOTE: redraw if at least one msg has been processed model.redraw = true; for msg in messages.into_iter() { let mut msg = Some(msg); while msg.is_some() { msg = model.update(msg); } } } _ => {} } // Redraw if model.redraw { model.view(&mut app); model.redraw = false; } } ``` -------------------------------- ### Component Focus Management Source: https://context7.com/veeso/tui-realm/llms.txt Methods to manage component focus, including setting active components, blurring, and querying the current focus. ```APIDOC ## Application::active ### Description Sets the focus to a specific component by its ID. ### Parameters #### Path Parameters - **id** (Id) - Required - The unique identifier of the component to activate. ## Application::blur ### Description Removes focus from the currently active component, restoring focus to the previous component in the stack. ## Application::focus ### Description Retrieves the ID of the currently focused component. ``` -------------------------------- ### Set Initial Component Focus Source: https://github.com/veeso/tui-realm/blob/main/docs/en/get-started.md Sets the initial focus to the LetterCounter component within the tui-realm application. ```rust assert!(app.active(&Id::LetterCounter).is_ok()); ``` -------------------------------- ### MockComponent Trait Definition Source: https://github.com/veeso/tui-realm/blob/main/docs/en/get-started.md This section outlines the `MockComponent` trait and its required methods for implementing UI components. ```APIDOC ## MockComponent Trait ### Description The `MockComponent` trait defines the interface for generic and reusable UI components with a single responsibility. It handles states and props, and requires the implementation of several methods for rendering, querying attributes, updating attributes, managing state, and performing commands. ### Methods - `view(&mut self, frame: &mut Frame, area: Rect)`: Renders the component within the provided `Frame` and `Rect` area using `ratatui` widgets. - `query(&self, attr: Attribute) -> Option`: Returns the value for a given attribute. - `attr(&mut self, attr: Attribute, value: AttrValue)`: Assigns a value to a specific attribute. - `state(&self) -> State`: Retrieves the current state of the component. Returns `State::None` if the component has no state. - `perform(&mut self, cmd: Cmd) -> CmdResult`: Processes a given command, potentially changing the component's state, and returns a `CmdResult`. ### Example Usage (Conceptual) ```rust // Assuming a Label component implementing MockComponent struct Label { ... } impl MockComponent for Label { fn view(&mut self, frame: &mut Frame, area: Rect) { // Render label using ratatui widgets } fn query(&self, attr: Attribute) -> Option { // Handle attribute queries } fn attr(&mut self, attr: Attribute, value: AttrValue) { // Handle attribute assignments } fn state(&self) -> State { State::None // Labels typically have no state } fn perform(&mut self, cmd: Cmd) -> CmdResult { // Handle commands, though labels might not perform actions CmdResult::None } } ``` ``` -------------------------------- ### tui-realm Cmd enum definition Source: https://github.com/veeso/tui-realm/blob/main/docs/en/get-started.md Defines command types for UI logic, independent of hardware or terminal specifics. Includes type, move, scroll, submit, delete, change, tick, custom, and none commands. ```rust pub enum Cmd { /// Describes a "user" typed a character Type(char), /// Describes a "cursor" movement, or a movement of another kind Move(Direction), /// An expansion of `Move` which defines the scroll. The step should be defined in props, if any. Scroll(Direction), /// User submit field Submit, /// User "deleted" something Delete, /// User toggled something Toggle, /// User changed something Change, /// A user defined amount of time has passed and the component should be updated Tick, /// A user defined command type. You won't find these kind of Command in the stdlib, but you can use them in your own components. Custom(&'static str), /// `None` won't do anything None, } ``` -------------------------------- ### Implement Radio Component Properties Source: https://github.com/veeso/tui-realm/blob/main/docs/en/advanced.md Define properties for a custom Radio component, including background, borders, foreground color, content (options), title, and the currently selected value. ```rust pub struct Radio { props: Props, // ... } impl Radio { // Constructors... pub fn foreground(mut self, fg: Color) -> Self { self.attr(Attribute::Foreground, AttrValue::Color(fg)); self } // ... } ``` -------------------------------- ### Manage Component Focus with active and blur Source: https://context7.com/veeso/tui-realm/llms.txt Use active to set component focus and blur to remove it. The framework automatically manages a focus stack for restoration. ```rust // Give focus to a component app.active(&Id::Counter).expect("Component not found"); // Check which component has focus if let Some(focused_id) = app.focus() { println!("Currently focused: {:?}", focused_id); } // Remove focus from current component (previous component regains focus) app.blur().expect("No component has focus"); // Switch focus between components (common pattern in update routine) impl Update for Model { fn update(&mut self, msg: Option) -> Option { match msg { Some(Msg::CounterBlur) => { // Switch focus to another component self.app.active(&Id::Label).ok(); None } Some(Msg::LabelBlur) => { self.app.active(&Id::Counter).ok(); None } _ => None, } } } ``` -------------------------------- ### Define Model Struct Source: https://github.com/veeso/tui-realm/blob/main/docs/en/get-started.md Defines the `Model` struct which holds the application state, including the application instance, quit status, redraw flag, and terminal bridge. ```rust pub struct Model { /// Application pub app: Application, /// Indicates that the application must quit pub quit: bool, /// Tells whether to redraw interface pub redraw: bool, /// Used to draw to terminal pub terminal: TerminalBridge, } ``` -------------------------------- ### Separate Model and View Source: https://github.com/veeso/tui-realm/blob/main/docs/en/migrating-legacy.md Refactor the application structure to decouple the model data from the application view. ```rust struct Activity { context: Context, protocol: FileTransferProtocol, address: String, view: View, // Replaced by application at user-level } ``` ```rust struct Activity { model: Model, application: Application, } struct Model { context: Context, protocol: FileTransferProtocol, address: String, } impl Update for Model { // ... (will be using the view passed by application, that's why model cannot hold view) } ``` -------------------------------- ### Define Application Message Enum Source: https://github.com/veeso/tui-realm/blob/main/docs/en/get-started.md Defines the message types that the application will handle. Includes messages for closing the app, changing counter values, and blurring input fields. ```rust pub enum Msg { AppClose, DigitCounterChanged(isize), DigitCounterBlur, LetterCounterChanged(isize), LetterCounterBlur, /// Used to unwrap on update() None, } ``` -------------------------------- ### Implement MockComponent for Counter Source: https://github.com/veeso/tui-realm/blob/main/docs/en/get-started.md Implements the MockComponent trait to handle rendering, attribute querying, and state updates. ```rust impl MockComponent for Counter { fn view(&mut self, frame: &mut Frame, area: Rect) { // Check if visible if self.props.get_or(Attribute::Display, AttrValue::Flag(true)) == AttrValue::Flag(true) { // Get properties let text = self.states.counter.to_string(); let alignment = self .props .get_or(Attribute::TextAlign, AttrValue::Alignment(Alignment::Left)) .unwrap_alignment(); let foreground = self .props .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset)) .unwrap_color(); let background = self .props .get_or(Attribute::Background, AttrValue::Color(Color::Reset)) .unwrap_color(); let modifiers = self .props .get_or( Attribute::TextProps, AttrValue::TextModifiers(TextModifiers::empty()), ) .unwrap_text_modifiers(); let title = self .props .get_or( Attribute::Title, AttrValue::Title((String::default(), Alignment::Center)), ) .unwrap_title(); let borders = self .props .get_or(Attribute::Borders, AttrValue::Borders(Borders::default())) .unwrap_borders(); let focus = self .props .get_or(Attribute::Focus, AttrValue::Flag(false)) .unwrap_flag(); frame.render_widget( Paragraph::new(text) .block(get_block(borders, title, focus)) .style( Style::default() .fg(foreground) .bg(background) .add_modifier(modifiers), ) .alignment(alignment), area, ); } } fn query(&self, attr: Attribute) -> Option { self.props.get(attr) } fn attr(&mut self, attr: Attribute, value: AttrValue) { self.props.set(attr, value); } fn state(&self) -> State { State::One(StateValue::Isize(self.states.counter)) } fn perform(&mut self, cmd: Cmd) -> CmdResult { match cmd { Cmd::Submit => { self.states.incr(); CmdResult::Changed(self.state()) } _ => CmdResult::None, } } } ``` -------------------------------- ### Tui-Realm Application Struct Definition Source: https://github.com/veeso/tui-realm/blob/main/docs/en/get-started.md Defines the core Application struct, which holds the event listener, subscriptions, and view. It requires generic types for ComponentId, Msg, and UserEvent. ```rust pub struct Application where ComponentId: Eq + PartialEq + Clone + Hash, Msg: PartialEq, UserEvent: Eq + PartialEq + Clone + PartialOrd + Send + 'static, { listener: EventListener, subs: Vec>, view: View, } ``` -------------------------------- ### Define EventClause Source: https://github.com/veeso/tui-realm/blob/main/docs/en/advanced.md Specifies the types of events that trigger a subscription. ```rust pub enum EventClause where UserEvent: Eq + PartialEq + Clone + PartialOrd, { /// Forward, no matter what kind of event Any, /// Check whether a certain key has been pressed Keyboard(KeyEvent), /// Check whether window has been resized WindowResize, /// The event will be forwarded on a tick Tick, /// Event will be forwarded on this specific user event. /// The way user event is matched, depends on its partialEq implementation User(UserEvent), } ``` -------------------------------- ### Define Component States Source: https://github.com/veeso/tui-realm/blob/main/docs/en/advanced.md Tracks the current selection and available choices for an interactive component. ```rust struct OwnStates { choice: usize, // Selected option choices: Vec, // Available choices } impl OwnStates { /// Move choice index to next choice pub fn next_choice(&mut self) { if self.choice + 1 < self.choices.len() { self.choice += 1; } } /// Move choice index to previous choice pub fn prev_choice(&mut self) { if self.choice > 0 { self.choice -= 1; } } /// Set OwnStates choices from a vector of text spans /// In addition resets current selection and keep index if possible or set it to the first value /// available pub fn set_choices(&mut self, spans: &[String]) { self.choices = spans.to_vec(); // Keep index if possible if self.choice >= self.choices.len() { self.choice = match self.choices.len() { 0 => 0, l => l - 1, }; } } pub fn select(&mut self, i: usize) { if i < self.choices.len() { self.choice = i; } } } ``` -------------------------------- ### Configure Tick Interval Source: https://github.com/veeso/tui-realm/blob/main/docs/en/advanced.md Use the tick_interval() method to set the frequency at which the application runtime throws an Event::Tick. This is useful for scheduling periodic actions. ```rust let mut app: Application = Application::init( EventListenerCfg::default() .tick_interval(Duration::from_secs(1)), ); ``` -------------------------------- ### Tui-Realm Application Tick Method Source: https://github.com/veeso/tui-realm/blob/main/docs/en/get-started.md The tick() method executes a single cycle of the application's lifecycle. It polls for events, forwards them to the active component and subscriptions, and collects resulting messages. ```rust pub fn tick(&mut self, strategy: PollStrategy) -> ApplicationResult> { // Poll event listener let events = self.poll(strategy)?; // Forward to active element let mut messages: Vec = events .iter() .map(|x| self.forward_to_active_component(x.clone())) .flatten() .collect(); // Forward to subscriptions and extend vector messages.extend(self.forward_to_subscriptions(events)); Ok(messages) } ```