use ratatui::{ buffer::Buffer, layout::Rect, style::{Color, Modifier, Style}, widgets::{Block, Borders, Widget}, }; use crate::model::Model; use crate::ui::app::AppMode; use crate::ui::cat_tree::{build_cat_tree, CatTreeEntry}; use crate::view::Axis; fn axis_display(axis: Axis) -> (&'static str, Color) { match axis { Axis::Row => ("Row ↕", Color::Green), Axis::Column => ("Col ↔", Color::Blue), Axis::Page => ("Page ☰", Color::Magenta), Axis::None => ("None ∅", Color::DarkGray), } } pub struct CategoryPanel<'a> { pub model: &'a Model, pub mode: &'a AppMode, pub cursor: usize, pub expanded: &'a std::collections::HashSet, } impl<'a> CategoryPanel<'a> { pub fn new( model: &'a Model, mode: &'a AppMode, cursor: usize, expanded: &'a std::collections::HashSet, ) -> Self { Self { model, mode, cursor, expanded, } } } impl<'a> Widget for CategoryPanel<'a> { fn render(self, area: Rect, buf: &mut Buffer) { let is_item_add = matches!(self.mode, AppMode::ItemAdd { .. }); let is_cat_add = matches!(self.mode, AppMode::CategoryAdd { .. }); let is_active = matches!(self.mode, AppMode::CategoryPanel) || is_item_add || is_cat_add; let (border_color, title) = if is_active { (Color::Cyan, " Categories ") } else { (Color::DarkGray, " Categories ") }; let block = Block::default() .borders(Borders::ALL) .border_style(Style::default().fg(border_color)) .title(title); let inner = block.inner(area); block.render(area, buf); let view = self.model.active_view(); let tree = build_cat_tree(self.model, self.expanded); if tree.is_empty() { buf.set_string( inner.x, inner.y, "(no categories — use :add-cat )", Style::default().fg(Color::DarkGray), ); return; } for (i, entry) in tree.iter().enumerate() { if i as u16 >= inner.height { break; } let y = inner.y + i as u16; let is_selected = i == self.cursor && is_active; let base_style = if is_selected { Style::default() .fg(Color::Black) .bg(Color::Cyan) .add_modifier(Modifier::BOLD) } else { Style::default() }; if is_selected { let fill = " ".repeat(inner.width as usize); buf.set_string(inner.x, y, &fill, base_style); } match entry { CatTreeEntry::Category { name, item_count, expanded, } => { let indicator = if *expanded { "▼" } else { "▶" }; let (axis_str, axis_color) = axis_display(view.axis_of(name)); let name_part = format!("{indicator} {name} ({item_count})"); let axis_part = format!(" [{axis_str}]"); buf.set_string(inner.x, y, &name_part, base_style); if name_part.len() + axis_part.len() < inner.width as usize { buf.set_string( inner.x + name_part.len() as u16, y, &axis_part, if is_selected { base_style } else { Style::default().fg(axis_color) }, ); } } CatTreeEntry::Item { item_name, .. } => { let label = format!(" · {item_name}"); buf.set_string(inner.x, y, &label, base_style); } } } } }