use crate::app::launcher::App;
use crate::app::renderer;
use crate::term::event::EventHandler;
use anyhow::{anyhow, Result};
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
use crossterm::terminal::{self, EnterAlternateScreen, LeaveAlternateScreen};
use ratatui::backend::{Backend, CrosstermBackend};
use ratatui::Terminal;
use std::io;
use std::panic;
use std::sync::atomic::Ordering;
#[derive(Debug)]
pub struct Tui<B: Backend> {
terminal: Terminal<B>,
pub events: EventHandler,
pub paused: bool,
}
impl<B: Backend> Tui<B> {
pub fn new(terminal: Terminal<B>, events: EventHandler) -> Self {
Self {
terminal,
events,
paused: false,
}
}
pub fn init(&mut self) -> Result<()> {
terminal::enable_raw_mode()?;
crossterm::execute!(
io::stderr(),
EnterAlternateScreen,
EnableMouseCapture
)?;
panic::set_hook(Box::new(move |panic| {
Self::reset().expect("failed to reset the terminal");
better_panic::Settings::auto()
.most_recent_first(false)
.lineno_suffix(true)
.create_panic_handler()(panic);
std::process::exit(1);
}));
self.terminal
.hide_cursor()
.map_err(|e| anyhow!("failed to hide cursor: {e}"))?;
self.terminal
.clear()
.map_err(|e| anyhow!("failed to clear terminal: {e}"))?;
Ok(())
}
pub fn toggle_pause(&mut self) -> Result<()> {
self.paused = !self.paused;
if self.paused {
Self::reset()?;
} else {
self.init()?;
}
self.events
.key_input_disabled
.store(self.paused, Ordering::Relaxed);
Ok(())
}
pub fn enable_mouse_capture(&mut self) -> Result<()> {
Ok(crossterm::execute!(io::stderr(), EnableMouseCapture)?)
}
pub fn disable_mouse_capture(&mut self) -> Result<()> {
Ok(crossterm::execute!(io::stderr(), DisableMouseCapture)?)
}
pub fn draw(&mut self, app: &mut App) -> Result<()> {
self.terminal
.draw(|frame| renderer::render(app, frame))
.map_err(|e| anyhow!("failed to draw TUI: {e}"))?;
Ok(())
}
pub fn reset() -> Result<()> {
terminal::disable_raw_mode()?;
crossterm::execute!(
io::stderr(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
Terminal::new(CrosstermBackend::new(io::stderr()))?.show_cursor()?;
Ok(())
}
}
#[cfg(feature = "tui-tests")]
#[cfg(test)]
mod tests {
use super::*;
use ratatui::backend::TestBackend;
#[test]
fn test_term_tui() -> Result<()> {
let backend = TestBackend::new(10, 10);
let terminal = Terminal::new(backend)?;
let mut tui = Tui::new(terminal, EventHandler::new(10));
tui.init()?;
tui.exit()?;
Ok(())
}
}