kanban-tui 0.7.2

Terminal user interface for the kanban project management tool
Documentation
use crossterm::event::{self, Event as CrosstermEvent, KeyCode, KeyEvent, KeyEventKind};
use std::time::Duration;
use tokio::sync::mpsc;

#[derive(Debug, Clone)]
pub enum Event {
    Key(KeyEvent),
    Tick,
}

pub struct EventHandler {
    rx: mpsc::UnboundedReceiver<Event>,
    shutdown_tx: mpsc::UnboundedSender<()>,
}

impl Default for EventHandler {
    fn default() -> Self {
        Self::new()
    }
}

impl EventHandler {
    pub fn new() -> Self {
        let (tx, rx) = mpsc::unbounded_channel();
        let (shutdown_tx, mut shutdown_rx) = mpsc::unbounded_channel();

        tokio::spawn(async move {
            loop {
                tokio::select! {
                    _ = shutdown_rx.recv() => {
                        break;
                    }
                    _ = tokio::time::sleep(Duration::from_millis(16)) => {
                        let mut had_key = false;
                        while event::poll(Duration::from_millis(0)).unwrap_or(false) {
                            if let Ok(CrosstermEvent::Key(key)) = event::read() {
                                tracing::trace!(code = ?key.code, kind = ?key.kind, modifiers = ?key.modifiers, "raw key event");
                                if key.kind != KeyEventKind::Press {
                                    continue;
                                }

                                had_key = true;
                                if tx.send(Event::Key(key)).is_err() {
                                    break;
                                }
                            }
                        }
                        if !had_key && tx.send(Event::Tick).is_err() {
                            break;
                        }
                    }
                }
            }
        });

        Self { rx, shutdown_tx }
    }

    pub async fn next(&mut self) -> Option<Event> {
        self.rx.recv().await
    }

    pub fn try_next(&mut self) -> Option<Event> {
        self.rx.try_recv().ok()
    }

    pub fn stop(&self) {
        let _ = self.shutdown_tx.send(());
    }
}

pub fn should_quit(key: &KeyEvent) -> bool {
    matches!(key.code, KeyCode::Char('q') | KeyCode::Char('Q'))
}