ratatui-toolkit 0.2.6

DEPRECATED: this crate was renamed to `ratkit`. Please migrate to `ratkit`.
Documentation
use crate::primitives::toast::{Toast, ToastLevel, ToastManager};
use ratatui::layout::Rect;
use std::time::Duration;

impl Toast {
    pub fn is_expired(&self) -> bool {
        self.created_at.elapsed() >= self.duration
    }

    pub fn new(message: impl Into<String>, level: ToastLevel, duration: Option<Duration>) -> Self {
        Self {
            message: message.into(),
            level,
            created_at: std::time::Instant::now(),
            duration: duration.unwrap_or(crate::primitives::toast::DEFAULT_TOAST_DURATION),
        }
    }

    pub fn with_duration(
        message: impl Into<String>,
        level: ToastLevel,
        duration: Duration,
    ) -> Self {
        Self {
            message: message.into(),
            level,
            created_at: std::time::Instant::now(),
            duration,
        }
    }

    pub fn lifetime_percent(&self) -> f32 {
        let elapsed = self.created_at.elapsed().as_secs_f32();
        let total = self.duration.as_secs_f32();
        (total - elapsed) / total
    }
}

impl ToastManager {
    pub fn new() -> Self {
        Self {
            toasts: Vec::new(),
            max_toasts: 5,
        }
    }

    pub fn add(&mut self, toast: Toast) {
        self.remove_expired();

        self.toasts.push(toast);

        if self.toasts.len() > self.max_toasts {
            self.toasts.drain(0..self.toasts.len() - self.max_toasts);
        }

        tracing::debug!("Toast added, total toasts: {}", self.toasts.len());
    }

    pub fn clear(&mut self) {
        self.toasts.clear();
    }

    pub fn error(&mut self, message: impl Into<String>) {
        self.add(Toast::new(message, ToastLevel::Error, None));
    }

    pub fn get_active(&self) -> &[Toast] {
        &self.toasts
    }

    pub fn handle_click(&mut self, x: u16, y: u16, frame_area: Rect) -> bool {
        const TOAST_WIDTH: u16 = 40;
        const TOAST_HEIGHT: u16 = 3;
        const TOAST_MARGIN: u16 = 2;
        const TOAST_SPACING: u16 = 1;

        let active_count = self.toasts.iter().filter(|t| !t.is_expired()).count();
        if active_count == 0 {
            return false;
        }

        let mut y_offset = frame_area.height.saturating_sub(TOAST_MARGIN);

        for i in (0..self.toasts.len()).rev() {
            if self.toasts[i].is_expired() {
                continue;
            }

            let toast_y = y_offset.saturating_sub(TOAST_HEIGHT);
            let toast_x = frame_area.width.saturating_sub(TOAST_WIDTH + TOAST_MARGIN);

            if x >= toast_x
                && x < toast_x + TOAST_WIDTH
                && y >= toast_y
                && y < toast_y + TOAST_HEIGHT
            {
                self.toasts.remove(i);
                return true;
            }

            y_offset = toast_y.saturating_sub(TOAST_SPACING);

            if toast_y == 0 || toast_x == 0 {
                break;
            }
        }

        false
    }

    pub fn has_toasts(&self) -> bool {
        !self.toasts.is_empty()
    }

    pub fn info(&mut self, message: impl Into<String>) {
        self.add(Toast::new(message, ToastLevel::Info, None));
    }

    pub fn remove_expired(&mut self) {
        let before = self.toasts.len();
        self.toasts.retain(|toast| !toast.is_expired());
        let removed = before - self.toasts.len();
        if removed > 0 {
            tracing::debug!("Removed {} expired toasts", removed);
        }
    }

    pub fn success(&mut self, message: impl Into<String>) {
        self.add(Toast::new(message, ToastLevel::Success, None));
    }

    pub fn warning(&mut self, message: impl Into<String>) {
        self.add(Toast::new(message, ToastLevel::Warning, None));
    }
}