nyado 0.3.1

A Rust todo-list manager with TUI, inspired by meowdo
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use ratatui::{
    layout::Rect,
    style::{Color, Style},
    text::{Line, Span},
    widgets::Paragraph,
    Frame,
};

pub const CAT_ASCII: [&str; 16] = [
    "⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
    "⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣇⠀⠀⠀⠀⠀⠀⠀⠀⣼⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀",
    "⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣄⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀",
    "⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⡀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀",
    "⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀",
    "⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⡀⠀⠀⠀⠀",
    "⠀⠀⠀⠀⣾⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀",
    "⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀",
    "⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀",
    "⠀⠀⠀⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣿⠀⠀⠀",
    "⠀⠀⠀⣿⡟⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⣿⣿⠀⠀⠀",
    "⠀⠀⠀⢿⣇⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠀⣿⣿⠀⠀⠀",
    "⠀⠀⠀⠘⣿⣄⡙⠟⠿⠋⢀⣿⣿⣿⣿⣿⣿⣿⣿⡘⠛⠋⠋⣀⣾⡿⠃⠀⠀⠀",
    "⠀⠀⠀⠀⠘⢻⣿⣷⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠁⠀⠀⠀⠀",
    "⠀⠀⠀ ⠔⠁⡠⠋⠛⡿⠿⠿⠿⠿⠿⠿⠿⠿⠿⠿⢿⠋⠙⢄⠈⠂⠀⠀⠀⠀",
    "⠀⠀⠀⠀⠀⠀⠀⠀⠈  ⠀⠀⠀⠀⠀⠀⠀⠀⠀ ⠁ ⠀⠀⠀⠀⠀⠀⠀",
];

pub const CAT_HEIGHT: usize = CAT_ASCII.len();

pub fn draw_bongo(frame: &mut Frame, area: Rect) {
    if area.width == 0 || area.height == 0 {
        return;
    }
    let cat_width = CAT_ASCII.iter().map(|line| line.chars().count()).max().unwrap_or(20) as u16;
    if area.width < cat_width || area.height < CAT_HEIGHT as u16 {
        return;
    }
    let x = area.right().saturating_sub(cat_width + 1);
    let mut y = area.top();
    for line in CAT_ASCII.iter() {
        let line_len = line.chars().count() as u16;
        if line_len == 0 || y >= area.bottom() - 1 {
            continue;
        }
        frame.render_widget(
            Paragraph::new(Line::from(Span::styled(
                *line,
                Style::default().fg(color::BONGO),
            ))),
            Rect::new(x, y, line_len, 1),
        );
        y += 1;
    }
}

pub mod color {
    use ratatui::style::Color;
    pub const BORDER: Color = Color::Cyan;
    pub const TOPBAR_BG: Color = Color::Magenta;
    pub const TOPBAR_FG: Color = Color::Black;
    pub const SELECTED_BG: Color = Color::Cyan;
    pub const SELECTED_FG: Color = Color::Black;
    pub const DONE: Color = Color::White;
    pub const PENDING: Color = Color::Yellow;
    pub const BONGO: Color = Color::Magenta;
    pub const STATUSBAR_BG: Color = Color::Blue;
    pub const STATUSBAR_FG: Color = Color::Black;
    pub const HEADER: Color = Color::Cyan;
    pub const GREEN: Color = Color::Green;
    pub const PIN: Color = Color::Red;
    pub const TAG1: Color = Color::Green;
    pub const TAG2: Color = Color::Yellow;
    pub const TAG3: Color = Color::Cyan;
    pub const TAG4: Color = Color::Magenta;
    pub const TAG5: Color = Color::Red;
    pub const TAG6: Color = Color::Blue;
    pub const TAG7: Color = Color::White;
    pub const TAG8: Color = Color::Green;
    pub const SEARCH: Color = Color::Yellow;
}

pub fn tag_color(tag: &str) -> Color {
    let mut h = 0u64;
    for ch in tag.chars() {
        h = h.wrapping_mul(31).wrapping_add(ch as u64);
    }
    let colors = [
        color::TAG1, color::TAG2, color::TAG3, color::TAG4,
        color::TAG5, color::TAG6, color::TAG7, color::TAG8,
    ];
    colors[(h % 8) as usize]
}

pub fn truncate_text(text: &str, max_chars: usize) -> String {
    if max_chars == 0 {
        return String::new();
    }
    let mut chars = text.chars();
    let mut result = String::with_capacity(max_chars);
    let mut count = 0;
    for ch in chars.by_ref() {
        if count + 1 > max_chars {
            result.push('');
            return result;
        }
        result.push(ch);
        count += 1;
    }
    result
}

pub fn visual_width(s: &str) -> usize {
    s.width()
}

pub fn truncate_text_by_width(text: &str, max_width: usize) -> String {
    if max_width == 0 {
        return String::new();
    }
    let mut result = String::new();
    let mut current_width = 0;
    for ch in text.chars() {
        let ch_width = ch.width().unwrap_or(1);
        if current_width + ch_width > max_width {
            if current_width + 1 <= max_width {
                result.push('');
            }
            return result;
        }
        result.push(ch);
        current_width += ch_width;
    }
    result
}