envx_tui/
lib.rs

1mod app;
2mod ui;
3
4pub use app::App;
5
6use color_eyre::Result;
7use ratatui::{
8    Terminal,
9    backend::CrosstermBackend,
10    crossterm::event::{self, Event},
11};
12use std::{
13    io,
14    time::{Duration, Instant},
15};
16
17/// Run the TUI application
18///
19/// # Errors
20///
21/// Returns an error if:
22/// - Terminal setup fails
23/// - App initialization fails
24/// - Terminal operations fail during execution
25/// - Cleanup operations fail
26pub fn run() -> Result<()> {
27    // Setup terminal
28    let backend = CrosstermBackend::new(io::stdout());
29    let mut terminal = Terminal::new(backend)?;
30
31    // Create app
32    let mut app = App::new()?;
33
34    // Setup panic hook
35    let panic_hook = std::panic::take_hook();
36    std::panic::set_hook(Box::new(move |panic_info| {
37        crossterm::execute!(io::stdout(), crossterm::terminal::LeaveAlternateScreen).ok();
38        crossterm::terminal::disable_raw_mode().ok();
39        panic_hook(panic_info);
40    }));
41
42    // Enter alternate screen and enable raw mode
43    crossterm::terminal::enable_raw_mode()?;
44    crossterm::execute!(
45        io::stdout(),
46        crossterm::terminal::EnterAlternateScreen,
47        crossterm::event::EnableMouseCapture
48    )?;
49
50    terminal.clear()?;
51
52    // Simple event loop with debouncing
53    let mut last_key_time: Option<Instant> = None;
54
55    loop {
56        // Draw UI
57        terminal.draw(|f| ui::draw(f, &mut app))?;
58
59        // Handle events with timeout
60        if event::poll(Duration::from_millis(50))? {
61            if let Ok(Event::Key(key)) = event::read() {
62                // Only handle key press events, ignore key release
63                if key.kind == event::KeyEventKind::Press {
64                    let now = Instant::now();
65
66                    // Debounce: ignore if key pressed within 100ms
67                    if let Some(last_time) = last_key_time {
68                        if now.duration_since(last_time) < Duration::from_millis(50) {
69                            continue;
70                        }
71                    }
72
73                    last_key_time = Some(now);
74
75                    if app.handle_key_event(key)? {
76                        break;
77                    }
78                }
79            }
80        }
81
82        // Tick for status message timeout
83        app.tick();
84    }
85
86    // Cleanup
87    crossterm::terminal::disable_raw_mode()?;
88    crossterm::execute!(
89        io::stdout(),
90        crossterm::terminal::LeaveAlternateScreen,
91        crossterm::event::DisableMouseCapture
92    )?;
93
94    terminal.show_cursor()?;
95
96    Ok(())
97}