tabiew 0.13.1

A lightweight TUI application to view and query tabular data files, such as CSV, TSV, and parquet.
use crate::AppResult;
use crate::misc::type_ext::UnwrapOrGracefulShutdown;
use crossterm::event::{self, Event as CrosstermEvent, KeyEvent, MouseEvent};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Mutex, OnceLock, mpsc};
use std::thread;
use std::time::{Duration, Instant};

static READ_EVENT: AtomicBool = AtomicBool::new(true);
pub fn enable_event_read() {
    READ_EVENT.store(true, Ordering::Relaxed);
}
pub fn disable_event_read() {
    READ_EVENT.store(false, Ordering::Relaxed);
}

pub fn read_event() -> AppResult<Event> {
    static RECV: OnceLock<Mutex<EventHandler>> = OnceLock::new();
    RECV.get_or_init(|| Mutex::new(EventHandler::new(100)))
        .lock()
        .unwrap_or_graceful_shutdown()
        .next()
}

/// Terminal events.
#[derive(Clone, Debug)]
pub enum Event {
    Tick,
    Key(KeyEvent),
    Mouse(MouseEvent),
    Resize(u16, u16),
    FocusGained,
    FocusLost,
    Paste(String),
}

impl From<CrosstermEvent> for Event {
    fn from(value: CrosstermEvent) -> Self {
        match value {
            CrosstermEvent::FocusGained => Event::FocusGained,
            CrosstermEvent::FocusLost => Event::FocusLost,
            CrosstermEvent::Key(e) => Event::Key(e),
            CrosstermEvent::Mouse(e) => Event::Mouse(e),
            CrosstermEvent::Paste(s) => Event::Paste(s),
            CrosstermEvent::Resize(c, r) => Event::Resize(c, r),
        }
    }
}

#[derive(Debug)]
struct EventHandler {
    receiver: mpsc::Receiver<Event>,
}

impl EventHandler {
    fn new(tick_rate: u64) -> Self {
        let tick_rate = Duration::from_millis(tick_rate);
        let (sender, receiver) = mpsc::channel();
        thread::spawn(move || {
            let mut last_tick = Instant::now();
            loop {
                let timeout = tick_rate
                    .checked_sub(last_tick.elapsed())
                    .unwrap_or(tick_rate);
                if READ_EVENT.load(Ordering::Relaxed) {
                    if matches!(event::poll(timeout), Ok(true))
                        && let Ok(event) = event::read()
                        && sender.send(event.into()).is_err()
                    {
                        break;
                    }
                } else {
                    std::thread::sleep(timeout);
                }

                if last_tick.elapsed() >= tick_rate {
                    if sender.send(Event::Tick).is_err() {
                        break;
                    }
                    last_tick = Instant::now();
                }
            }
        });
        Self { receiver }
    }

    fn next(&self) -> AppResult<Event> {
        Ok(self.receiver.recv()?)
    }
}