1 use crate::app::{App, AppResult}; 2 use crate::event::EventHandler; 3 use crate::ui; 4 use crossterm::event::DisableMouseCapture; 5 use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen}; 6 use std::io; 7 use std::panic; 8 9 use ratatui::backend::Backend; 10 use ratatui::Terminal; 11 12 /// Representation of a terminal user interface. 13 /// 14 /// It is responsible for setting up the terminal, 15 /// initializing the interface and handling the draw events. 16 #[derive(Debug)] 17 pub struct Tui<B: Backend> { 18 /// Interface to the Terminal. 19 terminal: Terminal<B>, 20 /// Terminal event handler. 21 pub events: EventHandler, 22 } 23 24 impl<B: Backend> Tui<B> { 25 /// Constructs a new instance of [`Tui`]. 26 pub fn new(terminal: Terminal<B>, events: EventHandler) -> Self { 27 Self { terminal, events } 28 } 29 30 /// Initializes the terminal interface. 31 /// 32 /// It enables the raw mode and sets terminal properties. 33 pub fn init(&mut self) -> AppResult<()> { 34 terminal::enable_raw_mode()?; 35 crossterm::execute!(io::stderr(), EnterAlternateScreen, DisableMouseCapture)?; 36 37 // Define a custom panic hook to reset the terminal properties. 38 // This way, you won't have your terminal messed up if an unexpected error happens. 39 let panic_hook = panic::take_hook(); 40 panic::set_hook(Box::new(move |panic| { 41 Self::reset().expect("failed to reset the terminal"); 42 panic_hook(panic); 43 })); 44 45 self.terminal.hide_cursor()?; 46 self.terminal.clear()?; 47 Ok(()) 48 } 49 50 /// [`Draw`] the terminal interface by [`rendering`] the widgets. 51 /// 52 /// [`Draw`]: ratatui::Terminal::draw 53 /// [`rendering`]: crate::ui:render 54 pub fn draw(&mut self, app: &mut App) -> AppResult<()> { 55 self.terminal.draw(|frame| ui::render(app, frame))?; 56 Ok(()) 57 } 58 59 /// Resets the terminal interface. 60 /// 61 /// This function is also used for the panic hook to revert 62 /// the terminal properties if unexpected errors occur. 63 fn reset() -> AppResult<()> { 64 terminal::disable_raw_mode()?; 65 crossterm::execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture)?; 66 Ok(()) 67 } 68 69 /// Exits the terminal interface. 70 /// 71 /// It disables the raw mode and reverts back the terminal properties. 72 pub fn exit(&mut self) -> AppResult<()> { 73 Self::reset()?; 74 self.terminal.show_cursor()?; 75 Ok(()) 76 } 77 } 78