//! ## Demo //! //! `Demo` shows how to use tui-realm in a real case use std::time::Duration; use tui_realm_stdlib::Input; use tuirealm::command::{Cmd, CmdResult, Direction, Position}; use tuirealm::event::KeyModifiers; use tuirealm::props::{ Alignment, AttrValue, Attribute, BorderType, Borders, Color, InputType, Style, }; use tuirealm::terminal::{CrosstermTerminalAdapter, TerminalBridge}; use tuirealm::{ application::PollStrategy, event::{Key, KeyEvent}, Application, Component, Event, EventListenerCfg, MockComponent, NoUserEvent, State, StateValue, Update, }; // tui use tuirealm::ratatui::layout::{Constraint, Direction as LayoutDirection, Layout}; #[derive(Debug, PartialEq)] pub enum Msg { AppClose, ColorBlur, EmailBlur, NumberBlur, PasswordBlur, PhoneBlur, TextBlur, None, } // Let's define the component ids for our application #[derive(Debug, Eq, PartialEq, Clone, Hash)] pub enum Id { Color, Email, Number, Password, Phone, Text, } struct Model { app: Application, quit: bool, // Becomes true when the user presses redraw: bool, // Tells whether to refresh the UI; performance optimization } impl Default for Model { fn default() -> Self { // Setup app let mut app: Application = Application::init( EventListenerCfg::default().crossterm_input_listener(Duration::from_millis(10), 10), ); assert!(app .mount(Id::Text, Box::new(InputText::default()), vec![]) .is_ok()); assert!(app .mount(Id::Email, Box::new(InputEmail::default()), vec![]) .is_ok()); assert!(app .mount(Id::Number, Box::new(InputNumber::default()), vec![]) .is_ok()); assert!(app .mount(Id::Password, Box::new(InputPassword::default()), vec![]) .is_ok()); assert!(app .mount(Id::Phone, Box::new(InputPhone::default()), vec![]) .is_ok()); assert!(app .mount(Id::Color, Box::new(InputColor::default()), vec![]) .is_ok()); // We need to give focus to input then assert!(app.active(&Id::Text).is_ok()); Self { quit: false, redraw: true, app, } } } impl Model { fn view(&mut self, terminal: &mut TerminalBridge) { let _ = terminal.raw_mut().draw(|f| { // Prepare chunks let chunks = Layout::default() .direction(LayoutDirection::Vertical) .margin(1) .constraints( [ Constraint::Length(3), // Text Constraint::Length(3), // Email Constraint::Length(3), // Number Constraint::Length(3), // Password Constraint::Length(3), // Phone Constraint::Length(3), // Color Constraint::Length(1), ] .as_ref(), ) .split(f.area()); self.app.view(&Id::Text, f, chunks[0]); self.app.view(&Id::Email, f, chunks[1]); self.app.view(&Id::Number, f, chunks[2]); self.app.view(&Id::Password, f, chunks[3]); self.app.view(&Id::Phone, f, chunks[4]); self.app.view(&Id::Color, f, chunks[5]); }); } } fn main() { let mut model = Model::default(); let mut terminal = TerminalBridge::init_crossterm().expect("Cannot create terminal bridge"); let _ = terminal.enable_raw_mode(); let _ = terminal.enter_alternate_screen(); // Now we use the Model struct to keep track of some states // let's loop until quit is true while !model.quit { // Tick if let Ok(messages) = model.app.tick(PollStrategy::Once) { 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 terminal); model.redraw = false; } } // Terminate terminal let _ = terminal.leave_alternate_screen(); let _ = terminal.disable_raw_mode(); let _ = terminal.clear_screen(); } impl Update for Model { fn update(&mut self, msg: Option) -> Option { self.redraw = true; match msg.unwrap_or(Msg::None) { Msg::AppClose => { self.quit = true; None } Msg::TextBlur => { assert!(self.app.active(&Id::Email).is_ok()); None } Msg::EmailBlur => { assert!(self.app.active(&Id::Number).is_ok()); None } Msg::NumberBlur => { assert!(self.app.active(&Id::Password).is_ok()); None } Msg::PasswordBlur => { assert!(self.app.active(&Id::Phone).is_ok()); None } Msg::PhoneBlur => { assert!(self.app.active(&Id::Color).is_ok()); None } Msg::ColorBlur => { assert!(self.app.active(&Id::Text).is_ok()); None } Msg::None => None, } } } // -- components #[derive(MockComponent)] struct InputText { component: Input, } impl Default for InputText { fn default() -> Self { Self { component: Input::default() .borders( Borders::default() .modifiers(BorderType::Rounded) .color(Color::LightYellow), ) .foreground(Color::LightYellow) .input_type(InputType::Text) .title("Username", Alignment::Left) .value("veeso") .invalid_style(Style::default().fg(Color::Red)), } } } impl Component for InputText { fn on(&mut self, ev: Event) -> Option { let _ = match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. }) => self.perform(Cmd::Move(Direction::Left)), Event::Keyboard(KeyEvent { code: Key::Right, .. }) => self.perform(Cmd::Move(Direction::Right)), Event::Keyboard(KeyEvent { code: Key::Home, .. }) => self.perform(Cmd::GoTo(Position::Begin)), Event::Keyboard(KeyEvent { code: Key::End, .. }) => { self.perform(Cmd::GoTo(Position::End)) } Event::Keyboard(KeyEvent { code: Key::Delete, .. }) => self.perform(Cmd::Cancel), Event::Keyboard(KeyEvent { code: Key::Backspace, .. }) => self.perform(Cmd::Delete), Event::Keyboard(KeyEvent { code: Key::Char(ch), modifiers: KeyModifiers::NONE, }) => self.perform(Cmd::Type(ch)), Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => return Some(Msg::TextBlur), Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => return Some(Msg::AppClose), _ => CmdResult::None, }; Some(Msg::None) } } #[derive(MockComponent)] struct InputEmail { component: Input, } impl Default for InputEmail { fn default() -> Self { Self { component: Input::default() .borders( Borders::default() .modifiers(BorderType::Rounded) .color(Color::LightCyan), ) .foreground(Color::LightCyan) .input_type(InputType::Email) .title("Email", Alignment::Left) .placeholder( "test@example.com", Style::default().fg(Color::Rgb(120, 120, 120)), ) .invalid_style(Style::default().fg(Color::Red)), } } } impl Component for InputEmail { fn on(&mut self, ev: Event) -> Option { let _ = match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. }) => self.perform(Cmd::Move(Direction::Left)), Event::Keyboard(KeyEvent { code: Key::Right, .. }) => self.perform(Cmd::Move(Direction::Right)), Event::Keyboard(KeyEvent { code: Key::Home, .. }) => self.perform(Cmd::GoTo(Position::Begin)), Event::Keyboard(KeyEvent { code: Key::End, .. }) => { self.perform(Cmd::GoTo(Position::End)) } Event::Keyboard(KeyEvent { code: Key::Delete, .. }) => self.perform(Cmd::Cancel), Event::Keyboard(KeyEvent { code: Key::Backspace, .. }) => self.perform(Cmd::Delete), Event::Keyboard(KeyEvent { code: Key::Char(ch), modifiers: KeyModifiers::NONE, }) => self.perform(Cmd::Type(ch)), Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => return Some(Msg::EmailBlur), Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => return Some(Msg::AppClose), _ => CmdResult::None, }; Some(Msg::None) } } #[derive(MockComponent)] struct InputNumber { component: Input, } impl Default for InputNumber { fn default() -> Self { Self { component: Input::default() .borders( Borders::default() .modifiers(BorderType::Rounded) .color(Color::LightGreen), ) .foreground(Color::LightGreen) .input_type(InputType::UnsignedInteger) .input_len(2) .title("What's your age", Alignment::Left) .invalid_style(Style::default().fg(Color::Red)), } } } impl Component for InputNumber { fn on(&mut self, ev: Event) -> Option { let _ = match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. }) => self.perform(Cmd::Move(Direction::Left)), Event::Keyboard(KeyEvent { code: Key::Right, .. }) => self.perform(Cmd::Move(Direction::Right)), Event::Keyboard(KeyEvent { code: Key::Home, .. }) => self.perform(Cmd::GoTo(Position::Begin)), Event::Keyboard(KeyEvent { code: Key::End, .. }) => { self.perform(Cmd::GoTo(Position::End)) } Event::Keyboard(KeyEvent { code: Key::Delete, .. }) => self.perform(Cmd::Cancel), Event::Keyboard(KeyEvent { code: Key::Backspace, .. }) => self.perform(Cmd::Delete), Event::Keyboard(KeyEvent { code: Key::Char(ch), modifiers: KeyModifiers::NONE, }) => self.perform(Cmd::Type(ch)), Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => return Some(Msg::NumberBlur), Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => return Some(Msg::AppClose), _ => CmdResult::None, }; Some(Msg::None) } } #[derive(MockComponent)] struct InputPassword { component: Input, } impl Default for InputPassword { fn default() -> Self { Self { component: Input::default() .borders( Borders::default() .modifiers(BorderType::Rounded) .color(Color::LightMagenta), ) .foreground(Color::LightMagenta) .input_type(InputType::Password('●')) .title("Password", Alignment::Left) .invalid_style(Style::default().fg(Color::Red)), } } } impl Component for InputPassword { fn on(&mut self, ev: Event) -> Option { let _ = match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. }) => self.perform(Cmd::Move(Direction::Left)), Event::Keyboard(KeyEvent { code: Key::Right, .. }) => self.perform(Cmd::Move(Direction::Right)), Event::Keyboard(KeyEvent { code: Key::Home, .. }) => self.perform(Cmd::GoTo(Position::Begin)), Event::Keyboard(KeyEvent { code: Key::End, .. }) => { self.perform(Cmd::GoTo(Position::End)) } Event::Keyboard(KeyEvent { code: Key::Delete, .. }) => self.perform(Cmd::Cancel), Event::Keyboard(KeyEvent { code: Key::Backspace, .. }) => self.perform(Cmd::Delete), Event::Keyboard(KeyEvent { code: Key::Char(ch), modifiers: KeyModifiers::NONE, }) => self.perform(Cmd::Type(ch)), Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => return Some(Msg::PasswordBlur), Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => return Some(Msg::AppClose), _ => CmdResult::None, }; Some(Msg::None) } } #[derive(MockComponent)] struct InputPhone { component: Input, } impl Default for InputPhone { fn default() -> Self { Self { component: Input::default() .borders( Borders::default() .modifiers(BorderType::Rounded) .color(Color::LightBlue), ) .foreground(Color::LightBlue) .input_type(InputType::Telephone) .input_len(14) .title("Phone number", Alignment::Left) .placeholder( "+39366123123", Style::default().fg(Color::Rgb(120, 120, 120)), ) .invalid_style(Style::default().fg(Color::Red)), } } } impl Component for InputPhone { fn on(&mut self, ev: Event) -> Option { let _ = match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. }) => self.perform(Cmd::Move(Direction::Left)), Event::Keyboard(KeyEvent { code: Key::Right, .. }) => self.perform(Cmd::Move(Direction::Right)), Event::Keyboard(KeyEvent { code: Key::Home, .. }) => self.perform(Cmd::GoTo(Position::Begin)), Event::Keyboard(KeyEvent { code: Key::End, .. }) => { self.perform(Cmd::GoTo(Position::End)) } Event::Keyboard(KeyEvent { code: Key::Delete, .. }) => self.perform(Cmd::Cancel), Event::Keyboard(KeyEvent { code: Key::Backspace, .. }) => self.perform(Cmd::Delete), Event::Keyboard(KeyEvent { code: Key::Char(ch), modifiers: KeyModifiers::NONE, }) => self.perform(Cmd::Type(ch)), Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => return Some(Msg::PhoneBlur), Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => return Some(Msg::AppClose), _ => CmdResult::None, }; Some(Msg::None) } } #[derive(MockComponent)] struct InputColor { component: Input, } impl Default for InputColor { fn default() -> Self { Self { component: Input::default() .borders( Borders::default() .modifiers(BorderType::Rounded) .color(Color::White), ) .foreground(Color::White) .input_type(InputType::Color) .title("What's your favourite color", Alignment::Left) .invalid_style(Style::default().fg(Color::Red)), } } } impl Component for InputColor { fn on(&mut self, ev: Event) -> Option { let _ = match ev { Event::Keyboard(KeyEvent { code: Key::Left, .. }) => self.perform(Cmd::Move(Direction::Left)), Event::Keyboard(KeyEvent { code: Key::Right, .. }) => self.perform(Cmd::Move(Direction::Right)), Event::Keyboard(KeyEvent { code: Key::Home, .. }) => self.perform(Cmd::GoTo(Position::Begin)), Event::Keyboard(KeyEvent { code: Key::End, .. }) => { self.perform(Cmd::GoTo(Position::End)) } Event::Keyboard(KeyEvent { code: Key::Delete, .. }) => self.perform(Cmd::Cancel), Event::Keyboard(KeyEvent { code: Key::Backspace, .. }) => self.perform(Cmd::Delete), Event::Keyboard(KeyEvent { code: Key::Char(ch), modifiers: KeyModifiers::NONE, }) => { if let CmdResult::Changed(State::One(StateValue::String(color))) = self.perform(Cmd::Type(ch)) { let color = tuirealm::utils::parser::parse_color(&color).unwrap(); self.attr(Attribute::Foreground, AttrValue::Color(color)); self.attr( Attribute::Borders, AttrValue::Borders( Borders::default() .modifiers(BorderType::Rounded) .color(color), ), ); } CmdResult::None } Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => return Some(Msg::ColorBlur), Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => return Some(Msg::AppClose), _ => CmdResult::None, }; Some(Msg::None) } }