use crate::*; pub struct Tui { pub exited: Arc, pub backend: CrosstermBackend, pub buffer: Buffer, pub area: [u16;4], } 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, }))) } /// 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) } }