add stylers; dim column separators; shrink columns; refactor handlers

This commit is contained in:
🪞👃🪞 2025-04-06 23:38:23 +03:00
parent 38f97558a7
commit 004cf96c2e
5 changed files with 70 additions and 43 deletions

View file

@ -39,18 +39,19 @@ impl Handle<TuiIn> for Taggart {
press!(F(1)) => { self.help_begin() }, press!(F(1)) => { self.help_begin() },
press!(Char('q')) => { self.quit_begin(&input) }, press!(Char('q')) => { self.quit_begin(&input) },
press!(Char('w')) => { self.save_begin() }, press!(Char('w')) => { self.save_begin() },
press!(Enter) => { self.edit_begin() }, press!(Up) => { self.cursor_select(-1); },
press!(Up) => { self.cursor = self.cursor.saturating_sub(1); }, press!(Down) => { self.cursor_select( 1); },
press!(Down) => { self.cursor = self.cursor + 1; }, press!(PageUp) => { self.cursor_select(-(PAGE_SIZE as isize)); },
press!(PageUp) => { self.cursor = self.cursor.saturating_sub(PAGE_SIZE); }, press!(PageDown) => { self.cursor_select( PAGE_SIZE as isize); },
press!(PageDown) => { self.cursor += PAGE_SIZE; }, press!(Left) => { self.column_select(-1); },
press!(Char(' ')) => { self.open_in_player()?; }, press!(Right) => { self.column_select( 1); },
press!(Left) => { self.column_prev(); }, press!(Char('[')) => { self.column_resize(-1); },
press!(Right) => { self.column_next(); }, press!(Char(']')) => { self.column_resize( 1); },
press!(Char('[')) => { self.column_resize(-1); },
press!(Char(']')) => { self.column_resize( 1); },
press!(Char('{')) => { self.column_collapse(true, 1); }, press!(Char('{')) => { self.column_collapse(true, 1); },
press!(Char('}')) => { self.column_collapse(false, -1); }, press!(Char('}')) => { self.column_collapse(false, -1); },
press!(Delete) => { self.clear() },
press!(Enter) => { self.edit_begin() },
press!(Char(' ')) => { self.open_in_player()?; },
_ => {} _ => {}
}, },
} }
@ -60,15 +61,18 @@ impl Handle<TuiIn> for Taggart {
} }
impl Taggart { impl Taggart {
fn clear (&self) {
todo!("clear")
}
fn open_in_player (&self) -> Usually<()> { fn open_in_player (&self) -> Usually<()> {
open(self.entries[self.cursor].path.as_ref())?; open(self.entries[self.cursor].path.as_ref())?;
Ok(()) Ok(())
} }
fn column_prev (&mut self) { fn cursor_select (&mut self, amount: isize) {
self.column = self.column.saturating_sub(1); self.cursor = ((self.cursor as isize) + amount).max(0) as usize;
} }
fn column_next (&mut self) { fn column_select (&mut self, amount: isize) {
self.column = self.column + 1; self.column = ((self.column as isize) + amount).max(0) as usize;
} }
fn column_resize (&mut self, amount: isize) { fn column_resize (&mut self, amount: isize) {
let column = &mut self.columns.0[self.column]; let column = &mut self.columns.0[self.column];

View file

@ -1,4 +1,5 @@
use crate::*; use crate::*;
use crate::ratatui::style::Style;
mod column; pub use self::column::*; mod column; pub use self::column::*;
mod entry; pub use self::entry::*; mod entry; pub use self::entry::*;
@ -10,12 +11,17 @@ pub struct Taggart {
pub cursor: usize, pub cursor: usize,
pub offset: usize, pub offset: usize,
pub column: usize, pub column: usize,
pub columns: Columns<fn(&Entry)->Option<Arc<str>>, fn(&mut Self, usize, &str)>,
pub display: Measure<TuiOut>, pub display: Measure<TuiOut>,
/// State of modal dialog of editing field /// State of modal dialog of editing field
pub mode: Option<Mode>, pub mode: Option<Mode>,
/// Count of changes to items /// Count of changes to items
pub changes: usize, pub changes: usize,
/// Table columns to display
pub columns: Columns<
fn(&Entry)->Option<Arc<str>>,
fn(&mut Self, usize, &str),
fn(&Entry)->Option<Style>,
>,
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -1,18 +1,20 @@
use crate::*; use crate::*;
use crate::ratatui::style::Style;
pub struct Column<G, S> { pub struct Column<G, S, U> {
pub title: Arc<str>, pub title: Arc<str>,
pub width: usize, pub width: usize,
pub collapsed: bool, pub collapsed: bool,
pub getter: G, pub getter: G,
pub setter: Option<S>, pub setter: Option<S>,
//pub styler: Option<U>, pub styler: Option<U>,
} }
type Getter<T> = fn(&T)->Option<Arc<str>>; type Getter<T> = fn(&T)->Option<Arc<str>>;
type Setter<T> = fn(&mut T, usize, &str); type Setter<T> = fn(&mut T, usize, &str);
type Styler<T> = fn(&T)->Option<Style>;
impl<G, S> Column<Getter<G>, Setter<S>> { impl<G, S, U> Column<Getter<G>, Setter<S>, Styler<U>> {
pub fn new ( pub fn new (
title: &impl AsRef<str>, title: &impl AsRef<str>,
width: usize, width: usize,
@ -23,6 +25,7 @@ impl<G, S> Column<Getter<G>, Setter<S>> {
title: title.as_ref().into(), title: title.as_ref().into(),
getter, getter,
setter: None, setter: None,
styler: None,
collapsed: false, collapsed: false,
} }
} }
@ -30,6 +33,10 @@ impl<G, S> Column<Getter<G>, Setter<S>> {
self.setter = Some(setter); self.setter = Some(setter);
self self
} }
fn styler (mut self, styler: Styler<U>) -> Self {
self.styler = Some(styler);
self
}
} }
pub(crate) fn entries_under ( pub(crate) fn entries_under (
@ -56,7 +63,7 @@ pub(crate) fn entries_under (
} }
} }
pub struct Columns<G, S>(pub Vec<Column<G, S>>); pub struct Columns<G, S, U>(pub Vec<Column<G, S, U>>);
macro_rules! setter { macro_rules! setter {
($set:ident) => {{ ($set:ident) => {{
@ -82,7 +89,11 @@ macro_rules! setter {
}} }}
} }
impl Default for Columns<fn(&Entry)->Option<Arc<str>>, fn(&mut Taggart, usize, &str)> { impl Default for Columns<
fn(&Entry)->Option<Arc<str>>,
fn(&mut Taggart, usize, &str),
fn(&Entry)->Option<Style>,
> {
fn default () -> Self { fn default () -> Self {
Self(vec![ Self(vec![
// Computed file hash // Computed file hash
@ -92,18 +103,18 @@ impl Default for Columns<fn(&Entry)->Option<Arc<str>>, fn(&mut Taggart, usize, &
// Selected? // Selected?
Column::new(&"", 1, |entry: &Entry|Some(" ".into())), Column::new(&"", 1, |entry: &Entry|Some(" ".into())),
// File name // File name
Column::new(&"File", 80, |entry: &Entry|entry.name()), Column::new(&"File", 40, |entry: &Entry|entry.name()),
// Modified tags? // Modified tags?
Column::new(&"~", 1, |entry: &Entry|if entry.is_modified() { Column::new(&"~", 1, |entry: &Entry|if entry.is_modified() {
Some("~".into()) Some("~".into())
} else { } else {
None None
}), }),
Column::new(&"Artist", 30, |entry: &Entry|entry.artist()).setter(setter!(set_artist)), Column::new(&"Artist", 15, |entry: &Entry|entry.artist()).setter(setter!(set_artist)),
Column::new(&"Year", 5, |entry: &Entry|entry.year()).setter(setter!(set_year)), Column::new(&"Year", 4, |entry: &Entry|entry.year()).setter(setter!(set_year)),
Column::new(&"Release", 30, |entry: &Entry|entry.album()).setter(setter!(set_album)), Column::new(&"Release", 15, |entry: &Entry|entry.album()).setter(setter!(set_album)),
Column::new(&"Track", 5, |entry: &Entry|entry.track()).setter(setter!(set_track)), Column::new(&"Track", 5, |entry: &Entry|entry.track()).setter(setter!(set_track)),
Column::new(&"Title", 80, |entry: &Entry|entry.title()).setter(setter!(set_title)), Column::new(&"Title", 40, |entry: &Entry|entry.title()).setter(setter!(set_title)),
]) ])
} }
} }

View file

@ -224,9 +224,13 @@ macro_rules! music_tag_field {
} }
pub fn $set (&mut self, value: &impl AsRef<str>) -> bool { pub fn $set (&mut self, value: &impl AsRef<str>) -> bool {
let value = value.as_ref().trim(); let value = value.as_ref().trim();
if let &mut Metadata::Music { ref mut modified_tag, .. } = self { if let &mut Metadata::Music {
match (value.len(), &modified_tag) { ref original_tag,
(0, Some(new_tag)) => { ref mut modified_tag,
..
} = self {
match (value.len(), &original_tag, &modified_tag) {
(0, _, Some(new_tag)) => {
if new_tag.read().unwrap().item_count() <= 1 { if new_tag.read().unwrap().item_count() <= 1 {
// removing last entry removes modified_tag // removing last entry removes modified_tag
*modified_tag = None; *modified_tag = None;
@ -236,16 +240,16 @@ macro_rules! music_tag_field {
} }
return true; return true;
}, },
(_, Some(new_tag)) => { (_, _, Some(new_tag)) => {
// add entry to modified_tag // add entry to modified_tag
new_tag.write().unwrap().$set(value.into()); new_tag.write().unwrap().$set(value.into());
return true; return true;
}, },
(0, None) => { (0, _, None) => {
// leave modified_tag empty // leave modified_tag empty
return false; return false;
}, },
(_, None) => { (_, _, None) => {
// first entry creates modified_tag // first entry creates modified_tag
let mut new_tag = Tag::new(TagType::Id3v2); // FIXME other types let mut new_tag = Tag::new(TagType::Id3v2); // FIXME other types
new_tag.$set(value.into()); new_tag.$set(value.into());
@ -281,11 +285,13 @@ macro_rules! music_tag_field {
} }
pub fn $set (&mut self, value: &impl AsRef<str>) -> bool { pub fn $set (&mut self, value: &impl AsRef<str>) -> bool {
let value = value.as_ref().trim(); let value = value.as_ref().trim();
if let &mut Metadata::Music { ref mut modified_tag, .. } = self if let Ok(numeric_value) = value.parse::<u32>() && let &mut Metadata::Music {
&& let Ok(numeric_value) = value.parse::<u32>() ref original_tag,
{ ref mut modified_tag,
match (value.len(), &modified_tag) { ..
(0, Some(new_tag)) => { } = self {
match (value.len(), &original_tag, &modified_tag) {
(0, _, Some(new_tag)) => {
if new_tag.read().unwrap().item_count() <= 1 { if new_tag.read().unwrap().item_count() <= 1 {
// removing last entry removes modified_tag // removing last entry removes modified_tag
*modified_tag = None; *modified_tag = None;
@ -295,16 +301,16 @@ macro_rules! music_tag_field {
} }
return true; return true;
}, },
(_, Some(new_tag)) => { (_, _, Some(new_tag)) => {
// add entry to modified_tag // add entry to modified_tag
new_tag.write().unwrap().$set(numeric_value); new_tag.write().unwrap().$set(numeric_value);
return true; return true;
}, },
(0, None) => { (0, _, None) => {
// leave modified_tag empty // leave modified_tag empty
return false; return false;
}, },
(_, None) => { (_, _, None) => {
// first entry creates modified_tag // first entry creates modified_tag
let mut new_tag = Tag::new(TagType::Id3v2); // FIXME other types let mut new_tag = Tag::new(TagType::Id3v2); // FIXME other types
new_tag.$set(numeric_value); new_tag.$set(numeric_value);
@ -325,7 +331,6 @@ generated_field!(hash = |self|match self {
Metadata::Unknown { hash, .. } => Some(hash.clone()), Metadata::Unknown { hash, .. } => Some(hash.clone()),
_ => None _ => None
}); });
generated_field!(size = |self|match self { generated_field!(size = |self|match self {
Metadata::Image { size, .. } => Some(size.clone()), Metadata::Image { size, .. } => Some(size.clone()),
Metadata::Music { size, .. } => Some(size.clone()), Metadata::Music { size, .. } => Some(size.clone()),

View file

@ -1,4 +1,5 @@
use crate::*; use crate::*;
use crate::ratatui::style::{Style, Stylize};
pub struct TreeTable<'a>(pub(crate) &'a Taggart); pub struct TreeTable<'a>(pub(crate) &'a Taggart);
@ -69,12 +70,12 @@ impl<'a> TreeTable<'a> {
} }
} }
*x += width as u16 + 1; *x += width as u16 + 1;
to.blit(&"", *x - 1, y, None); to.blit(&"", *x - 1, y, Some(Style::default().fg(Color::Rgb(64,64,64))));
} }
} }
} }
impl<G, S> Columns<G, S> { impl<G, S, U> Columns<G, S, U> {
pub fn header (&self) -> Arc<str> { pub fn header (&self) -> Arc<str> {
let mut output = String::new(); let mut output = String::new();
for Column { width, collapsed, title, .. } in self.0.iter() { for Column { width, collapsed, title, .. } in self.0.iter() {