use crate::*; pub(crate) use tengri::tui::ratatui::style::{Color, Modifier}; pub(crate) use pad::PadStr; mod table; pub use self::table::*; impl Content for Taggart { fn content (&self) -> impl Render { let w = self.display.w(); let h = self.display.h(); let sizer = Fill::xy(&self.display); let value_bar = |x|Bsp::n(self.value_bar(), x); let mode_bar = |x|Bsp::n(self.mode_bar(format!("{w}x{h}")), x); let title_bar = |x|Bsp::s(self.title_bar(), x); let sized = |x|Bsp::b(sizer, Fill::xy(x)); value_bar(mode_bar(title_bar(sized(TreeTable(self))))) } fn render (&self, to: &mut TuiOut) { self.content().render(to); match self.mode { Some(Mode::Save { value }) => { to.tint_all( Color::Rgb(96,96,96), Color::Rgb(48,48,48), Modifier::DIM ); let options = [ if value == 0 { "[ Clear changes ]" } else { " Clear changes " }, if value == 1 { "[ Continue editing ]" } else { " Continue editing " }, if value == 2 { "[ Write and continue ]" } else { " Write and continue " }, ]; let modal = Self::modal(Bsp::s( format!("Save {} change(s)?", self.tasks.len()), Bsp::s("", Bsp::e(options[0], Bsp::e(options[1], options[2]))))); Content::render(&modal, to) }, Some(Mode::Quit { value }) => { to.tint_all( Color::Rgb(96,96,96), Color::Rgb(48,48,48), Modifier::DIM ); let options = [ if value == 0 { "[ Exit without saving ]" } else { " Exit without saving " }, if value == 1 { "[ Cancel ]" } else { " Cancel " }, if value == 2 { "[ Write and exit ]" } else { " Write and exit " }, ]; let modal = Self::modal(Bsp::s( format!("Save {} change(s) before exiting?", self.tasks.len()), Bsp::s("", Bsp::e(options[0], Bsp::e(options[1], options[2]))))); Content::render(&modal, to) }, _ => {}, } } } impl Taggart { pub(crate) const FG_BROWSE: Color = Color::Rgb(255, 192, 0); pub(crate) const BG_BROWSE: Color = Color::Rgb(0, 0, 0); pub(crate) const BG_EDIT: Color = Color::Rgb(48, 128, 0); pub(crate) const FG_EDIT: Color = Color::Rgb(255, 255, 255); pub(crate) const BG_SAVE: Color = Color::Rgb(192, 96, 0); pub(crate) const FG_SAVE: Color = Color::Rgb(255, 255, 255); pub(crate) const BG_QUIT: Color = Color::Rgb(128, 0, 0); pub(crate) const FG_QUIT: Color = Color::Rgb(255, 255, 255); pub(crate) const FG_MODAL: Color = Color::Rgb(255, 255, 255); pub(crate) const BG_MODAL: Color = Color::Rgb(0, 0, 0); fn modal (content: impl Content) -> impl Content { let position = |x|Fill::xy( Align::c(x)); let style = |x|Tui::modify(false, Modifier::DIM, Tui::fg_bg(Self::FG_MODAL, Self::BG_MODAL, x)); let border = |x|Margin::xy(1, 1, Bsp::b( Border(true, Lozenge(true, Default::default())), x)); position(style(border(content))) } fn title_bar (&self) -> impl Content { status_bar( Color::Rgb(0, 0, 0), Color::Rgb(192, 192, 192), Align::w(self.columns.header()) ) } fn value_bar (&self) -> impl Content { status_bar( Color::Rgb(192, 192, 192), Color::Rgb(0, 0, 0), Fill::x( Bsp::a( Fill::x(Align::w(format!( " {}/{} {}", self.cursor + 1, self.entries.len(), (self.columns.0[self.column].getter)(&self.entries[self.cursor]) .map(|value|format!("{}: {value}", self.columns.0[self.column].title,)) .unwrap_or(String::default()) ))), Fill::x(Align::e(format!( " {} unsaved changes ", self.tasks.len() ))) ) ) ) } fn mode_bar (&self, size: String) -> impl Content { let mode = match self.mode { Some(Mode::Save { .. }) => Tui::bg(Self::BG_SAVE, Tui::fg(Self::FG_SAVE, " SAVE ")), Some(Mode::Quit { .. }) => Tui::bg(Self::BG_QUIT, Tui::fg(Self::FG_QUIT, " QUIT ")), Some(Mode::Edit { .. }) => Tui::bg(Self::BG_EDIT, Tui::fg(Self::FG_EDIT, " EDIT ")), _ => Tui::bg(Self::BG_BROWSE, Tui::fg(Self::FG_BROWSE, " BROWSE ")) }; let help = match self.mode { Some(Mode::Save { .. }) => " Esc: cancel, Arrows: select, Enter: confirm", Some(Mode::Quit { .. }) => " Esc: cancel, Arrows: select, Enter: confirm", Some(Mode::Edit { .. }) => " Esc: cancel, Enter: set value", _ => " Q: exit, W: save, Arrows: select, Space: open, Enter: edit", }; status_bar( Color::Rgb(0, 0, 0), Color::Rgb(192, 192, 192), Fill::x(Bsp::a( Fill::x(Align::w(Tui::bold(true, Bsp::e(mode, help)))), Fill::x(Align::e(size)), )) ) } } pub fn status_bar ( fg: Color, bg: Color, content: impl Content ) -> impl Content { Fixed::y(1, Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, content)))) } impl Entry { pub const ICON_DIRECTORY: &'static str = ""; pub const ICON_IMAGE: &'static str = "󰋩"; pub const ICON_MUSIC: &'static str = ""; pub const ICON_MUSIC_NO_META: &'static str = "󰎇"; pub const ICON_UNKNOWN: &'static str = ""; 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_directory() { Self::ICON_DIRECTORY } else if self.is_image() { Self::ICON_IMAGE } else if self.is_music() { Self::ICON_MUSIC } else { Self::ICON_UNKNOWN } } }