nyado 0.3.1

A Rust todo-list manager with TUI, inspired by meowdo
use super::common::{color, truncate_text};
use ratatui::{
    layout::Rect,
    style::{Modifier, Style},
    text::Span,
    widgets::Paragraph,
    Frame,
};

pub struct ProgressState {
    pub animated_filled: usize,
    pub last_done: usize,
    pub last_total: usize,
    pub last_bar_len: usize,
}

impl ProgressState {
    pub fn new() -> Self {
        Self {
            animated_filled: 0,
            last_done: 0,
            last_total: 0,
            last_bar_len: 0,
        }
    }
}

pub fn draw_progress_bar(
    frame: &mut Frame,
    area: Rect,
    done: usize,
    total: usize,
    state: &mut ProgressState,
) {
    if total == 0 || area.width == 0 {
        return;
    }
    let width = area.width as usize;
    if width < 10 {
        return;
    }
    let percent = (done * 100) / total;
    let bar_len = width.saturating_sub(12);
    if bar_len < 5 {
        return;
    }
    let target_filled = (bar_len * done) / total;

    let need_animation = (done != state.last_done) || (total != state.last_total);
    if need_animation {
        state.last_done = done;
        state.last_total = total;
        state.last_bar_len = bar_len;
        if state.animated_filled < target_filled {
            state.animated_filled = (state.animated_filled + 1).min(target_filled);
        } else if state.animated_filled > target_filled {
            state.animated_filled = state.animated_filled.saturating_sub(1).max(target_filled);
        }
    } else {
        if bar_len != state.last_bar_len {
            state.last_bar_len = bar_len;
        }
        state.animated_filled = target_filled;
    }

    let bar: String = (0..bar_len)
        .map(|i| if i < state.animated_filled { '' } else { '' })
        .collect();
    let label = format!("{}% {}/{}", percent, done, total);
    let line = format!("[{}] {}", bar, label);
    let display = truncate_text(&line, width);
    let line_len = display.chars().count() as u16;
    if line_len <= area.width {
        frame.render_widget(
            Paragraph::new(Span::styled(display, Style::default().fg(color::GREEN).add_modifier(Modifier::BOLD))),
            Rect::new(area.left(), area.top(), line_len, 1),
        );
    }
}