use crate::*; use std::time::Duration; mod tui_buffer; pub use self::tui_buffer::*; mod tui_input; pub use self::tui_input::*; mod tui_event; pub use self::tui_event::*; mod tui_output; pub use self::tui_output::*; mod tui_perf; pub use self::tui_perf::*; pub struct Tui { pub exited: Arc, pub backend: CrosstermBackend, pub buffer: Buffer, pub area: [u16;4], pub perf: PerfModel, } impl Tui { /// Construct a new TUI engine and wrap it for shared ownership. pub fn new () -> Usually>> { let backend = CrosstermBackend::new(stdout()); let Size { width, height } = backend.size()?; Ok(Arc::new(RwLock::new(Self { exited: Arc::new(AtomicBool::new(false)), buffer: Buffer::empty(Rect { x: 0, y: 0, width, height }), area: [0, 0, width, height], backend, perf: Default::default(), }))) } /// True if done pub fn exited (&self) -> bool { self.exited.fetch_and(true, Relaxed) } /// Prepare before run pub fn setup (&mut self) -> Usually<()> { let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler(); std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{ stdout().execute(LeaveAlternateScreen).unwrap(); CrosstermBackend::new(stdout()).show_cursor().unwrap(); disable_raw_mode().unwrap(); better_panic_handler(info); })); stdout().execute(EnterAlternateScreen)?; self.backend.hide_cursor()?; enable_raw_mode().map_err(Into::into) } /// Update the display buffer. pub fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer { if self.buffer.area != size { self.backend.clear_region(ClearType::All).unwrap(); self.buffer.resize(size); self.buffer.reset(); } let updates = self.buffer.diff(&buffer); self.backend.draw(updates.into_iter()).expect("failed to render"); self.backend.flush().expect("failed to flush output buffer"); std::mem::swap(&mut self.buffer, &mut buffer); buffer.reset(); buffer } /// Clean up after run pub fn teardown (&mut self) -> Usually<()> { stdout().execute(LeaveAlternateScreen)?; self.backend.show_cursor()?; disable_raw_mode().map_err(Into::into) } } pub trait TuiRun + Handle + 'static> { /// Run an app in the main loop. fn run (&self, state: &Arc>) -> Usually<()>; } impl + Handle + Send + Sync + 'static> TuiRun for Arc> { fn run (&self, state: &Arc>) -> Usually<()> { let _input_thread = TuiIn::run_input(self, state, Duration::from_millis(100)); self.write().unwrap().setup()?; let render_thread = TuiOut::run_output(self, state, Duration::from_millis(10)); match render_thread.join() { Ok(result) => { self.write().unwrap().teardown()?; println!("\n\rRan successfully: {result:?}\n\r"); }, Err(error) => { self.write().unwrap().teardown()?; panic!("\n\rRender thread failed: {error:?}.\n\r") }, } Ok(()) } }