read id3 tags

This commit is contained in:
🪞👃🪞 2025-03-07 23:49:08 +02:00
parent 72bd6148d6
commit b29511c23e
6 changed files with 251 additions and 272 deletions

87
Cargo.lock generated
View file

@ -109,12 +109,6 @@ dependencies = [
"windows-targets", "windows-targets",
] ]
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]] [[package]]
name = "better-panic" name = "better-panic"
version = "0.3.0" version = "0.3.0"
@ -131,15 +125,6 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.17.0" version = "3.17.0"
@ -253,15 +238,6 @@ dependencies = [
"unicode-segmentation", "unicode-segmentation",
] ]
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.4.2" version = "1.4.2"
@ -302,16 +278,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]] [[package]]
name = "darling" name = "darling"
version = "0.20.10" version = "0.20.10"
@ -347,16 +313,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.14.0" version = "1.14.0"
@ -422,16 +378,6 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.15" version = "0.2.15"
@ -947,17 +893,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]] [[package]]
name = "signal-hook" name = "signal-hook"
version = "0.3.17" version = "0.3.17"
@ -1049,14 +984,12 @@ dependencies = [
name = "taggart" name = "taggart"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"base64",
"clap", "clap",
"file_type", "file_type",
"hex", "hex",
"id3", "id3",
"moku", "moku",
"pad", "pad",
"sha2",
"tek_tui", "tek_tui",
"walkdir", "walkdir",
"xxhash-rust", "xxhash-rust",
@ -1065,7 +998,7 @@ dependencies = [
[[package]] [[package]]
name = "tek_edn" name = "tek_edn"
version = "0.1.0" version = "0.1.0"
source = "git+https://codeberg.org/unspeaker/tengri#5352a9d5484198e35760d62a2cd6ef202990fb2c" source = "git+https://codeberg.org/unspeaker/tengri?rev=6cd85ef#6cd85efe503135cc5a20da9366ff81a0bbe5f56c"
dependencies = [ dependencies = [
"itertools 0.14.0", "itertools 0.14.0",
"konst", "konst",
@ -1075,7 +1008,7 @@ dependencies = [
[[package]] [[package]]
name = "tek_input" name = "tek_input"
version = "0.2.0" version = "0.2.0"
source = "git+https://codeberg.org/unspeaker/tengri#5352a9d5484198e35760d62a2cd6ef202990fb2c" source = "git+https://codeberg.org/unspeaker/tengri?rev=6cd85ef#6cd85efe503135cc5a20da9366ff81a0bbe5f56c"
dependencies = [ dependencies = [
"tek_edn", "tek_edn",
] ]
@ -1083,7 +1016,7 @@ dependencies = [
[[package]] [[package]]
name = "tek_output" name = "tek_output"
version = "0.2.0" version = "0.2.0"
source = "git+https://codeberg.org/unspeaker/tengri#5352a9d5484198e35760d62a2cd6ef202990fb2c" source = "git+https://codeberg.org/unspeaker/tengri?rev=6cd85ef#6cd85efe503135cc5a20da9366ff81a0bbe5f56c"
dependencies = [ dependencies = [
"tek_edn", "tek_edn",
] ]
@ -1091,7 +1024,7 @@ dependencies = [
[[package]] [[package]]
name = "tek_tui" name = "tek_tui"
version = "0.2.0" version = "0.2.0"
source = "git+https://codeberg.org/unspeaker/tengri#5352a9d5484198e35760d62a2cd6ef202990fb2c" source = "git+https://codeberg.org/unspeaker/tengri?rev=6cd85ef#6cd85efe503135cc5a20da9366ff81a0bbe5f56c"
dependencies = [ dependencies = [
"atomic_float", "atomic_float",
"better-panic", "better-panic",
@ -1126,12 +1059,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "typenum"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]] [[package]]
name = "typewit" name = "typewit"
version = "1.11.0" version = "1.11.0"
@ -1188,12 +1115,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.5.0" version = "2.5.0"

View file

@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
tek_tui = { git = "https://codeberg.org/unspeaker/tengri", ref = "5352a9d" } tek_tui = { git = "https://codeberg.org/unspeaker/tengri", rev = "6cd85ef" }
clap = { version = "4.5.4", features = [ "cargo" ] } clap = { version = "4.5.4", features = [ "cargo" ] }
walkdir = "2" walkdir = "2"
@ -12,7 +12,7 @@ id3 = "1.16"
moku = "0.2" moku = "0.2"
file_type = "0.7" file_type = "0.7"
pad = "0.1" pad = "0.1"
sha2 = "0.10" #sha2 = "0.10"
hex = "0.4" hex = "0.4"
xxhash-rust = { version = "0.8.5", features = ["xxh3"] } xxhash-rust = { version = "0.8.5", features = ["xxh3"] }
base64 = "0.22" #base64 = "0.22"

View file

@ -1,34 +1,13 @@
#!/usr/bin/env nix-shell #!/usr/bin/env nix-shell
{pkgs?import<nixpkgs>{}}:let {pkgs?import<nixpkgs>{}}:let
stdenv = pkgs.clang19Stdenv; stdenv = pkgs.clang19Stdenv;
name = "tek"; name = "taggart";
nativeBuildInputs = with pkgs; [ pkg-config freetype libclang ]; nativeBuildInputs = with pkgs; [ pkg-config libclang ];
buildInputs = with pkgs; let buildInputs = with pkgs; [ libclang ];
#suil = pkgs.enableDebugging (pkgs.suil.overrideAttrs (a: b: { LIBCLANG_PATH = "${pkgs.libclang.lib.outPath}/lib";
#dontStrip = true; separateDebugInfo = true; LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath (with pkgs; []);
#}));
in [ jack2 lilv serd libclang /*suil*/ glib gtk3 ];
VST3_SDK_DIR = "/home/user/Lab/Music/tek/vst3sdk/";
LIBCLANG_PATH = "${pkgs.libclang.lib.outPath}/lib";
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath (with pkgs; [
pipewire.jack
# for ChowKick.lv2:
freetype
libgcc.lib
# for Panagement
xorg.libX11
xorg.libXcursor
xorg.libXi
libxkbcommon
#suil
# for Helm:
alsa-lib
curl
libglvnd
#xorg_sys_opengl
]);
in pkgs.mkShell.override { in pkgs.mkShell.override {
inherit stdenv; inherit stdenv;
} { } {
inherit name nativeBuildInputs buildInputs VST3_SDK_DIR LIBCLANG_PATH LD_LIBRARY_PATH; inherit name nativeBuildInputs buildInputs LIBCLANG_PATH LD_LIBRARY_PATH;
} }

View file

@ -10,15 +10,14 @@ use tek_tui::tek_output::*;
use tek_tui::tek_input::*; use tek_tui::tek_input::*;
use crate::crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventState, KeyEventKind}; use crate::crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventState, KeyEventKind};
use clap::{arg, command, value_parser, ArgAction, Command}; use clap::{arg, command, value_parser};
use walkdir::WalkDir; use walkdir::WalkDir;
use sha2::{Sha256, Digest};
use xxhash_rust::xxh3::xxh3_64; use xxhash_rust::xxh3::xxh3_64;
use base64::prelude::*;
use file_type::FileType; use file_type::FileType;
mod keys; mod keys;
mod view; mod view;
mod model; pub(crate) use self::model::*;
pub(crate) type Usually<T> = std::result::Result<T, Box<dyn std::error::Error>>; pub(crate) type Usually<T> = std::result::Result<T, Box<dyn std::error::Error>>;
pub(crate) type Perhaps<T> = Usually<Option<T>>; pub(crate) type Perhaps<T> = Usually<Option<T>>;
@ -31,49 +30,6 @@ fn cli () -> clap::Command {
command!() command!()
.arg(arg!([path] "Path to root directory").value_parser(value_parser!(PathBuf))) .arg(arg!([path] "Path to root directory").value_parser(value_parser!(PathBuf)))
} }
struct Taggart {
root: PathBuf,
paths: Vec<Entry>,
cursor: usize,
offset: usize,
column: usize,
size: Measure<TuiOut>,
editing: Option<(usize, usize)>,
show_hash: bool,
}
#[derive(Default, Ord, Eq, PartialEq, PartialOrd)]
struct Entry {
path: PathBuf,
is_dir: Option<DirInfo>,
is_mus: Option<MusInfo>,
is_img: Option<ImgInfo>,
depth: usize,
hash: Option<String>,
file_type: Option<&'static FileType>,
}
#[derive(Default, Ord, Eq, PartialEq, PartialOrd)]
struct DirInfo {
hash_file: Option<()>,
catalog_file: Option<()>,
artist_file: Option<()>,
release_file: Option<()>,
}
#[derive(Default, Ord, Eq, PartialEq, PartialOrd)]
struct MusInfo {
artist: Option<String>,
release: Option<String>,
track: Option<usize>,
title: Option<String>,
date: Option<String>,
year: Option<String>,
people: Option<Vec<String>>,
publisher: Option<String>,
key: Option<String>,
}
#[derive(Default, Ord, Eq, PartialEq, PartialOrd)]
struct ImgInfo {
author: Option<String>,
}
fn main () -> Usually<()> { fn main () -> Usually<()> {
let args = cli().get_matches(); let args = cli().get_matches();
@ -109,78 +65,11 @@ impl Taggart {
if entry.depth() == 0 { if entry.depth() == 0 {
continue continue
} }
let depth = entry.depth(); if let Some(entry) = Entry::new(root, &entry)? {
let path = entry.into_path(); paths.push(entry);
let short_path: PathBuf = path.strip_prefix(&root)?.into(); }
let (is_dir, is_mus, is_img, hash, file_type) = if path.is_dir() {
(Some(Default::default()), None, None, None, None)
} else {
let bytes = read(&path)?;
let hash = hex::encode(xxh3_64(&bytes).to_be_bytes());
let file_type = FileType::try_from_reader(&*bytes)?;
let mime_type = file_type.media_types().get(0);
let is_mus = match mime_type {
Some(&"audio/mpeg3") => Some(Default::default()),
_ => None,
};
let is_img = match mime_type {
Some(&"image/png") => Some(Default::default()),
_ => None,
};
println!("{hash} {:>10}b {}", bytes.len(), short_path.display());
(None, is_mus, is_img, Some(hash), Some(file_type))
};
paths.push(Entry {
path: short_path,
is_dir,
is_mus,
is_img,
depth,
hash,
file_type
});
} }
paths.sort(); paths.sort();
Ok(paths) Ok(paths)
} }
} }
//pub enum Entry {
//Dir {
//path: PathBuf,
//name: OsString,
//entries: Vec<Box<FileTree>>,
//},
//File {
//path: PathBuf,
//name: OsString,
//}
//}
//impl Entry {
//fn new (path: &impl AsRef<Path>) -> Usually<Self> {
//let mut paths = vec![];
//for entry in WalkDir::new(&root)
//.into_iter()
//.filter_entry(|e|!e
//.file_name()
//.to_str()
//.map(|s|s.starts_with("."))
//.unwrap_or(false))
//{
//let path = entry?.into_path().strip_prefix(&root)?.into();
//paths.push(path);
//}
//paths.sort();
//}
//}
//struct FileTree {
//path: PathBuf,
//name: OsString,
//entries: Vec<Box<FileTree>>,
//}
//impl FileTree {
//}

168
src/model.rs Normal file
View file

@ -0,0 +1,168 @@
use crate::*;
use walkdir::DirEntry;
use id3::{Tag, TagLike};
use std::io::Read;
pub struct Taggart {
pub root: PathBuf,
pub paths: Vec<Entry>,
pub cursor: usize,
pub offset: usize,
pub column: usize,
pub size: Measure<TuiOut>,
pub editing: Option<(usize, usize)>,
pub show_hash: bool,
}
#[derive(Ord, Eq, PartialEq, PartialOrd)]
pub struct Entry {
pub path: PathBuf,
pub depth: usize,
pub info: EntryInfo,
}
#[derive(Ord, Eq, PartialEq, PartialOrd)]
pub enum EntryInfo {
Directory {
hash_file: Option<()>,
catalog_file: Option<()>,
artist_file: Option<()>,
release_file: Option<()>,
},
Music {
hash: Arc<str>,
file_type: &'static FileType,
artist: Option<Arc<str>>,
album: Option<Arc<str>>,
track: Option<u32>,
title: Option<Arc<str>>,
date: Option<Arc<str>>,
year: Option<i32>,
people: Option<Vec<Arc<str>>>,
publisher: Option<Arc<str>>,
key: Option<Arc<str>>,
bpm: Option<Arc<str>>,
invalid: bool,
},
Image {
hash: Arc<str>,
file_type: &'static FileType,
title: Option<String>,
author: Option<String>,
invalid: bool,
},
}
impl Entry {
pub fn new (root: &impl AsRef<Path>, entry: &DirEntry) -> Perhaps<Self> {
if entry.path().is_dir() {
Self::new_dir(root, entry)
} else if entry.path().is_file() {
Self::new_file(root, entry)
} else {
Ok(None)
}
}
fn new_dir (root: &impl AsRef<Path>, entry: &DirEntry) -> Perhaps<Self> {
Ok(Some(Self {
depth: entry.depth(),
path: entry.path().into(),
info: EntryInfo::Directory {
hash_file: None,
catalog_file: None,
artist_file: None,
release_file: None,
},
}))
}
fn new_file (root: &impl AsRef<Path>, entry: &DirEntry) -> Perhaps<Self> {
let bytes = read(entry.path())?;
let hash = hex::encode(xxh3_64(&bytes).to_be_bytes());
let file_type = FileType::try_from_reader(&*bytes)?;
let mime_type = file_type.media_types().get(0);
return Ok(Some(Self {
depth: entry.depth(),
path: entry.path().into(),
info: match mime_type {
Some(&"audio/mpeg3") => {
let id3 = Tag::read_from_path(entry.path())?;
EntryInfo::Music {
file_type,
hash: hash.into(),
artist: id3.artist().map(|x|x.into()),
album: id3.album().map(|x|x.into()),
track: id3.track().map(|x|x.into()),
title: id3.title().map(|x|x.into()),
date: None,
year: id3.year().map(|x|x.into()),
people: None,
publisher: None,
key: None,
bpm: None,
invalid: false,
}
},
Some(&"image/png") => EntryInfo::Image {
file_type,
hash: hash.into(),
title: None,
author: None,
invalid: false,
},
Some(&"image/jpeg") => EntryInfo::Image {
file_type,
hash: hash.into(),
title: None,
author: None,
invalid: false,
},
_ => return Ok(None)
},
}))
}
pub fn short_path (&self, root: &impl AsRef<Path>) -> Usually<&Path> {
Ok(self.path.strip_prefix(root.as_ref())?)
}
pub fn is_dir (&self) -> bool {
matches!(self.info, EntryInfo::Directory { .. })
}
pub fn is_mus (&self) -> bool {
matches!(self.info, EntryInfo::Music { .. })
}
pub fn is_img (&self) -> bool {
matches!(self.info, EntryInfo::Image { .. })
}
pub fn hash (&self) -> Option<Arc<str>> {
match self.info {
EntryInfo::Image { ref hash, .. } => Some(hash.clone()),
EntryInfo::Music { ref hash, .. } => Some(hash.clone()),
_ => None
}
}
pub fn artist (&self) -> Option<Arc<str>> {
match self.info {
EntryInfo::Music { ref artist, .. } => artist.clone(),
_ => None
}
}
pub fn album (&self) -> Option<Arc<str>> {
match self.info {
EntryInfo::Music { ref album, .. } => album.clone(),
_ => None
}
}
pub fn title (&self) -> Option<Arc<str>> {
match self.info {
EntryInfo::Music { ref title, .. } => title.clone(),
_ => None
}
}
}

View file

@ -1,22 +1,7 @@
use crate::*; use crate::*;
use tek_tui::ratatui::{style::{Color, Style}, prelude::Stylize}; use tek_tui::ratatui::{style::{Color, Style}, prelude::Stylize};
use pad::PadStr; use pad::PadStr;
use std::fmt::Display;
fn table_row (
hash: Option<&str>, label: &str, artist: &str, album: &str, track: &str, title: &str
) -> String {
let hash = hash.unwrap_or("").pad_to_width(COLUMN_WIDTHS[0] as usize);
let label = label.pad_to_width(COLUMN_WIDTHS[1] as usize);
let artist = artist.pad_to_width(COLUMN_WIDTHS[2] as usize);
let album = album.pad_to_width(COLUMN_WIDTHS[3] as usize);
let track = track.pad_to_width(COLUMN_WIDTHS[4] as usize);
let title = title.pad_to_width(COLUMN_WIDTHS[5] as usize);
format!("{hash}{label}{artist}{album}{track}{title}")
}
fn status_bar (content: impl Content<TuiOut>) -> impl Content<TuiOut> {
Fixed::y(1, Fill::x(Tui::bold(true, Tui::fg_bg(Color::Rgb(0,0,0), Color::Rgb(255,255,255), content))))
}
impl Content<TuiOut> for Taggart { impl Content<TuiOut> for Taggart {
fn content (&self) -> impl Render<TuiOut> { fn content (&self) -> impl Render<TuiOut> {
@ -24,7 +9,12 @@ impl Content<TuiOut> for Taggart {
let size = format!("{}x{}", self.size.w(), self.size.h()); let size = format!("{}x{}", self.size.w(), self.size.h());
let size_bar = status_bar(Align::e(size)); let size_bar = status_bar(Align::e(size));
let titlebar = status_bar(Align::w(table_row( let titlebar = status_bar(Align::w(table_row(
Some("HASH"), "FILE", "ARTIST", "RELEASE", "TRACK", "TITLE" Some("HASH".into()),
"FILE",
Some("ARTIST".into()),
Some("RELEASE".into()),
"TRACK",
Some("TITLE".into())
))); )));
let table = Fill::xy(TreeTable(self)); let table = Fill::xy(TreeTable(self));
Bsp::n(size_bar, Bsp::s(titlebar, Bsp::b(sizer, table))) Bsp::n(size_bar, Bsp::s(titlebar, Bsp::b(sizer, table)))
@ -38,7 +28,7 @@ impl<'a> Content<TuiOut> for TreeTable<'a> {
let area = to.area(); let area = to.area();
let Taggart { offset, paths, cursor, column, .. } = self.0; let Taggart { offset, paths, cursor, column, .. } = self.0;
let mut x = 0; let mut x = 0;
for (index, width) in COLUMN_WIDTHS.iter().enumerate() { for (index, _width) in COLUMN_WIDTHS.iter().enumerate() {
let w = COLUMN_WIDTHS[index] + 1; let w = COLUMN_WIDTHS[index] + 1;
if index == *column { if index == *column {
to.fill_bg([area.x() + x, area.y(), w, area.h()], Color::Rgb(0, 0, 0)); to.fill_bg([area.x() + x, area.y(), w, area.h()], Color::Rgb(0, 0, 0));
@ -54,30 +44,9 @@ impl<'a> Content<TuiOut> for TreeTable<'a> {
for (index, fragment) in entry.path.iter().enumerate() { for (index, fragment) in entry.path.iter().enumerate() {
if index == entry.depth - 1 { if index == entry.depth - 1 {
let cursor = if selected { ">" } else { " " }; let cursor = if selected { ">" } else { " " };
let icon = if entry.is_dir.is_some() { let icon = entry.icon();
"" //"+" let style = entry.style();
} else if entry.is_img.is_some() { let label = entry.label(icon, &fragment.display());
""
} else if entry.is_mus.is_some() {
""
} else {
" "
};
let style = if entry.is_dir.is_some() {
None
} else {
Some(Style::default().bold())
};
let name = fragment.display();
let indent = "".pad_to_width((entry.depth - 1) * 2);
let label = table_row(
entry.hash.as_deref(),
&format!("{indent}{icon} {name}"),
"",
"",
"",
""
);
to.blit(&label, area.x(), y, style); to.blit(&label, area.x(), y, style);
if selected { if selected {
let fill = [area.x(), y, area.w(), 1]; let fill = [area.x(), y, area.w(), 1];
@ -92,3 +61,56 @@ impl<'a> Content<TuiOut> for TreeTable<'a> {
} }
} }
} }
impl Entry {
fn icon (&self) -> &'static str {
if self.is_dir() {
"" //"+"
} else if self.is_img() {
""
} else if self.is_mus() {
""
} else {
" "
}
}
fn style (&self) -> Option<Style> {
if self.is_dir() {
None
} else {
Some(Style::default().bold())
}
}
fn label (&self, icon: &str, name: &impl Display) -> String {
let indent = "".pad_to_width((self.depth - 1) * 2);
table_row(
self.hash(),
&format!("{indent}{icon} {name}"),
self.artist(),
self.album(),
"",
self.title()
)
}
}
fn table_row (
hash: Option<Arc<str>>,
label: &str,
artist: Option<Arc<str>>,
album: Option<Arc<str>>,
track: &str,
title: Option<Arc<str>>,
) -> String {
let hash = hash.unwrap_or_default().pad_to_width(COLUMN_WIDTHS[0] as usize);
let label = label.pad_to_width(COLUMN_WIDTHS[1] as usize);
let artist = artist.unwrap_or_default().pad_to_width(COLUMN_WIDTHS[2] as usize);
let album = album.unwrap_or_default().pad_to_width(COLUMN_WIDTHS[3] as usize);
let track = track.pad_to_width(COLUMN_WIDTHS[4] as usize);
let title = title.unwrap_or_default().pad_to_width(COLUMN_WIDTHS[5] as usize);
format!("{hash}{label}{artist}{album}{track}{title}")
}
fn status_bar (content: impl Content<TuiOut>) -> impl Content<TuiOut> {
Fixed::y(1, Fill::x(Tui::bold(true, Tui::fg_bg(Color::Rgb(0,0,0), Color::Rgb(255,255,255), content))))
}