use std::{ error::Error, io, time::{Duration, Instant}, }; use crossterm::{ event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind}, execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, }; use ratatui::{ backend::{Backend, CrosstermBackend}, Terminal, }; use crate::{ui, App}; pub fn run() -> Result<(), Box> { let original_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(move |panic| { disable_raw_mode().unwrap(); crossterm::execute!(io::stdout(), LeaveAlternateScreen).unwrap(); original_hook(panic); })); // setup terminal enable_raw_mode()?; let mut stdout = io::stdout(); execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; // TODO: set app.picker window size when PR is merged. let app = App::new("Crossterm demo", &mut terminal); // run app let res = run_app(&mut terminal, app); // restore terminal disable_raw_mode()?; execute!( terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture )?; terminal.show_cursor()?; if let Err(err) = res { println!("{err:?}"); } Ok(()) } fn run_app(terminal: &mut Terminal, mut app: App) -> io::Result<()> { let mut last_tick = Instant::now(); loop { terminal.draw(|f| ui(f, &mut app))?; let timeout = app .tick_rate .checked_sub(last_tick.elapsed()) .unwrap_or_else(|| Duration::from_secs(0)); if crossterm::event::poll(timeout)? { if let Event::Key(key) = event::read()? { if key.kind == KeyEventKind::Press { if let KeyCode::Char(c) = key.code { app.on_key(c); } } } } if last_tick.elapsed() >= app.tick_rate { app.on_tick(); last_tick = Instant::now(); } if app.should_quit { return Ok(()); } } }