use crate::*; use tek_tui::ratatui::{style::{Color, Style}, prelude::Stylize}; use pad::PadStr; use std::fmt::Display; pub struct Column { title: Arc, width: usize, value: BoxOption> + Send + Sync>, } impl Column { pub fn new ( title: &impl AsRef, width: usize, value: impl Fn(&T)->Option> + Send + Sync + 'static ) -> Self { Self { width, value: Box::new(value), title: title.as_ref().into() } } } pub struct Columns(pub Vec>); impl Default for Columns { fn default () -> Self { Self(vec![ Column::new(&"HASH", 16, |entry: &Entry|entry.hash()), Column::new(&"FILE", 80, |entry: &Entry|entry.name()), Column::new(&"ARTIST", 30, |entry: &Entry|entry.artist()), Column::new(&"RELEASE", 30, |entry: &Entry|entry.album()), Column::new(&"TRACK", 5, |entry: &Entry|entry.track()), Column::new(&"TITLE", 80, |entry: &Entry|entry.title()), ]) } } impl Columns { pub fn header (&self) -> Arc { let mut output = String::new(); for Column { width, value, title } in self.0.iter() { let cell = title.pad_to_width(*width); output = format!("{output}{cell}│"); } output.into() } pub fn row (&self, entry: &T) -> Arc { let mut output = String::new(); for Column { width, value, .. } in self.0.iter() { let cell = value(entry).unwrap_or_default().pad_to_width(*width); output = format!("{output}{cell}│"); } output.into() } } impl Content for Taggart { fn content (&self) -> impl Render { let sizer = Fill::xy(&self.size); let size = format!("{}x{}", self.size.w(), self.size.h()); let size_bar = status_bar(Align::e(size)); let titlebar = status_bar(Align::w(self.columns.header())); Bsp::n(size_bar, Bsp::s(titlebar, Bsp::b(sizer, Fill::xy(TreeTable(self))))) } } struct TreeTable<'a>(&'a Taggart); impl<'a> Content for TreeTable<'a> { fn render (&self, to: &mut TuiOut) { let area = to.area(); let Taggart { offset, paths, cursor, column, .. } = self.0; let mut x = 0; for (index, Column { width, .. }) in self.0.columns.0.iter().enumerate() { let w = *width as u16 + 1; if index == *column { to.fill_bg([area.x() + x, area.y(), w, area.h()], Color::Rgb(0, 0, 0)); break } else { x += w; } } for (i, y) in area.iter_y().enumerate() { let i_offset = i + offset; let selected = *cursor == i_offset; if let Some(entry) = paths.get(i_offset) { for (index, fragment) in entry.path.iter().enumerate() { if index == entry.depth - 1 { let cursor = if selected { ">" } else { " " }; let label = self.0.columns.row(&entry); to.blit(&label, area.x(), y, entry.style()); if selected { let fill = [area.x(), y, area.w(), 1]; to.fill_fg(fill, Color::Rgb(0, 0, 0)); to.fill_bg(fill, Color::Rgb(224, 192, 0)); } } } } else { break } } } } impl Entry { pub fn name (&self) -> Option> { let indent = "".pad_to_width((self.depth - 1) * 2); let icon = self.icon(); let name = self.path.iter().last().expect("empty path").display(); Some(format!("{indent}{icon} {name}").into()) } fn icon (&self) -> &'static str { if self.is_dir() { "" //"+" } else if self.is_img() { "" } else if self.is_mus() { "" } else { " " } } fn style (&self) -> Option