use std::hash::Hash; use std::time::Duration; use anyhow::Result; use tuirealm::terminal::TerminalBridge; use tuirealm::tui::layout::Rect; use tuirealm::Frame; use tuirealm::{Application, EventListenerCfg, NoUserEvent}; pub mod cob; pub mod ui; use ui::context::Context; use ui::theme::Theme; /// Trait that must be implemented by client applications in order to be run /// as tui-application using tui-realm. Implementors act as models to the /// tui-realm application that can be polled for new messages, updated /// accordingly and rendered with new state. /// /// Please see `examples/` for further information on how to use it. pub trait Tui where Id: Eq + PartialEq + Clone + Hash, Message: Eq, { /// Should initialize an application by mounting and activating components. fn init(&mut self, app: &mut Application) -> Result<()>; /// Should update the current state by handling a message from the view. Returns true /// if view should be updated (e.g. a message was received and the current state changed). fn update(&mut self, app: &mut Application) -> Result; /// Should draw the application to a frame. fn view(&mut self, app: &mut Application, frame: &mut Frame); /// Should return true if the application is requested to quit. fn quit(&self) -> bool; } /// A tui-window using the cross-platform Terminal helper provided /// by tui-realm. pub struct Window { /// Helper around `Terminal` to quickly setup and perform on terminal. pub terminal: TerminalBridge, } impl Default for Window { fn default() -> Self { Self::new() } } /// Provides a way to create and run a new tui-application. impl Window { /// Creates a tui-window using the default cross-platform Terminal /// helper and panics if its creation fails. pub fn new() -> Self { Self { terminal: TerminalBridge::new().expect("Cannot create terminal bridge"), } } /// Runs this tui-window with the tui-application given and performs the /// following steps: /// 1. Enter alternative terminal screen /// 2. Run main loop until application should quit and with each iteration /// - poll new events (tick or user event) /// - update application state /// - redraw view /// 3. Leave alternative terminal screen pub fn run(&mut self, tui: &mut T, interval: u64) -> Result<()> where T: Tui, Id: Eq + PartialEq + Clone + Hash, Message: Eq, { let mut update = true; let mut resize = false; let mut size = Rect::default(); let mut app = Application::init( EventListenerCfg::default().default_input_listener(Duration::from_millis(interval)), ); tui.init(&mut app)?; while !tui.quit() { if update || resize { self.terminal .raw_mut() .draw(|frame| tui.view(&mut app, frame))?; } update = tui.update(&mut app)?; resize = size != self.terminal.raw().size()?; size = self.terminal.raw().size()?; } Ok(()) } } /// `tuirealm`'s event and prop system is designed to work with flat component hierarchies. /// Building deep nested component hierarchies would need a lot more additional effort to /// properly pass events and props down these hierarchies. This makes it hard to implement /// full app views (home, patch details etc) as components. /// /// View pages take into account these flat component hierarchies, and provide /// switchable sets of components. pub trait ViewPage where Id: Eq + PartialEq + Clone + Hash, Message: Eq, { /// Will be called whenever a view page is pushed onto the page stack. Should create and mount all widgets. fn mount( &self, app: &mut Application, context: &Context, theme: &Theme, ) -> Result<()>; /// Will be called whenever a view page is popped from the page stack. Should unmount all widgets. fn unmount(&self, app: &mut Application) -> Result<()>; /// Will be called whenever a view page is on top of the stack and can be used to update its internal /// state depending on the message passed. fn update( &mut self, app: &mut Application, context: &Context, theme: &Theme, message: Message, ) -> Result>; /// Will be called whenever a view page is on top of the page stack and needs to be rendered. fn view(&mut self, app: &mut Application, frame: &mut Frame); /// Will be called whenever this view page is pushed to the stack, or it is on top of the stack again /// after another view page was popped from the stack. fn subscribe(&self, app: &mut Application) -> Result<()>; /// Will be called whenever this view page is on top of the stack and another view page is pushed /// to the stack, or if this is popped from the stack. fn unsubscribe(&self, app: &mut Application) -> Result<()>; } /// View pages need to preserve their state (e.g. selected navigation tab, contents /// and the selected row of a table). Therefor they should not be (re-)created /// each time they are displayed. /// Instead the application can push a new page onto the page stack if it needs to /// be displayed. Its components are then created using the internal state. If a /// new page needs to be displayed, it will also be pushed onto the stack. Leaving /// that page again will pop it from the stack. The application can then return to /// the previously displayed page in the state it was left. #[derive(Default)] pub struct PageStack where Id: Eq + PartialEq + Clone + Hash, Message: Eq, { pages: Vec>>, } impl PageStack where Id: Eq + PartialEq + Clone + Hash, Message: Eq, { pub fn push( &mut self, page: Box>, app: &mut Application, context: &Context, theme: &Theme, ) -> Result<()> { if let Some(page) = self.pages.last() { page.unsubscribe(app)?; } page.mount(app, context, theme)?; page.subscribe(app)?; self.pages.push(page); Ok(()) } pub fn pop(&mut self, app: &mut Application) -> Result<()> { self.peek_mut()?.unsubscribe(app)?; self.peek_mut()?.unmount(app)?; self.pages.pop(); self.peek_mut()?.subscribe(app)?; Ok(()) } pub fn peek_mut(&mut self) -> Result<&mut Box>> { match self.pages.last_mut() { Some(page) => Ok(page), None => Err(anyhow::anyhow!( "Could not peek active page. Page stack is empty." )), } } }