zinit 0.3.9

Process supervisor with dependency management
Documentation
//! Event handling for the async TUI.

use std::time::Duration;

use crossterm::event::{Event as CrosstermEvent, EventStream, KeyEvent, MouseEvent};
use futures::StreamExt;
use tokio::sync::mpsc;
use tokio::time::{Interval, interval};
use tokio_util::sync::CancellationToken;

use super::actions::ActionResult;

/// Application events from various sources
#[derive(Debug)]
pub enum AppEvent {
    /// Periodic tick for updates (fetch data, clear old status)
    Tick,
    /// Render tick for screen updates
    Render,
    /// Keyboard input
    Key(KeyEvent),
    /// Mouse input
    #[allow(dead_code)]
    Mouse(MouseEvent),
    /// Terminal resize
    #[allow(dead_code)]
    Resize(u16, u16),
    /// Result from a background action
    ActionResult(ActionResult),
    /// Error from event system
    Error(String),
    /// Quit signal
    Quit,
}

/// Handles events from multiple async sources
pub struct EventHandler {
    /// Receiver for action results from background tasks
    result_rx: mpsc::UnboundedReceiver<ActionResult>,
    /// Cancellation token for shutdown
    cancel: CancellationToken,
    /// Tick interval timer
    tick_interval: Interval,
    /// Render interval timer
    render_interval: Interval,
    /// Crossterm event stream
    crossterm_events: EventStream,
}

impl EventHandler {
    /// Create a new event handler
    pub fn new(result_rx: mpsc::UnboundedReceiver<ActionResult>) -> Self {
        let tick_rate = Duration::from_millis(250); // 4 ticks per second
        let render_rate = Duration::from_millis(50); // 20 fps

        Self {
            result_rx,
            cancel: CancellationToken::new(),
            tick_interval: interval(tick_rate),
            render_interval: interval(render_rate),
            crossterm_events: EventStream::new(),
        }
    }

    /// Get a clone of the cancellation token
    #[allow(dead_code)]
    pub fn cancel_token(&self) -> CancellationToken {
        self.cancel.clone()
    }

    /// Request cancellation (triggers quit)
    #[allow(dead_code)]
    pub fn cancel(&self) {
        self.cancel.cancel();
    }

    /// Wait for and return the next event
    ///
    /// This uses tokio::select! to handle multiple async sources:
    /// - Tick timer for periodic updates
    /// - Render timer for screen refreshes
    /// - Crossterm events for user input
    /// - Action results from background tasks
    /// - Cancellation for shutdown
    pub async fn next(&mut self) -> AppEvent {
        loop {
            tokio::select! {
                // Biased ensures we check action results first (higher priority)
                biased;

                // Results from background tasks - highest priority
                Some(result) = self.result_rx.recv() => {
                    return AppEvent::ActionResult(result);
                }

                // User input events from crossterm - high priority
                maybe_event = self.crossterm_events.next() => {
                    match maybe_event {
                        Some(Ok(event)) => {
                            match event {
                                CrosstermEvent::Key(key) => {
                                    return AppEvent::Key(key);
                                }
                                CrosstermEvent::Mouse(mouse) => {
                                    return AppEvent::Mouse(mouse);
                                }
                                CrosstermEvent::Resize(w, h) => {
                                    return AppEvent::Resize(w, h);
                                }
                                CrosstermEvent::FocusGained | CrosstermEvent::FocusLost => {
                                    // Ignore focus events, continue loop
                                    continue;
                                }
                                CrosstermEvent::Paste(_) => {
                                    // Ignore paste events
                                    continue;
                                }
                            }
                        }
                        Some(Err(e)) => {
                            return AppEvent::Error(format!("Input error: {}", e));
                        }
                        None => {
                            // Stream ended
                            return AppEvent::Quit;
                        }
                    }
                }

                // Tick for periodic updates
                _ = self.tick_interval.tick() => {
                    return AppEvent::Tick;
                }

                // Render tick for smooth UI
                _ = self.render_interval.tick() => {
                    return AppEvent::Render;
                }

                // Cancellation requested
                _ = self.cancel.cancelled() => {
                    return AppEvent::Quit;
                }
            }
        }
    }
}