move trimmed strings to tengri

This commit is contained in:
🪞👃🪞 2025-03-18 01:19:10 +02:00
parent c67d3c8803
commit 6e0bf1e4b6
7 changed files with 135 additions and 200 deletions

22
Cargo.lock generated
View file

@ -1178,9 +1178,9 @@ dependencies = [
[[package]] [[package]]
name = "rust_decimal" name = "rust_decimal"
version = "1.36.0" version = "1.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" checksum = "5c24af6e7ac43c88a8a458d1139d0246fdce2f6cd2f1ac6cb51eb88b29c978af"
dependencies = [ dependencies = [
"arrayvec", "arrayvec",
"borsh", "borsh",
@ -1433,7 +1433,6 @@ dependencies = [
"pad", "pad",
"rusty-chromaprint", "rusty-chromaprint",
"tengri", "tengri",
"unicode-width 0.2.0",
"walkdir", "walkdir",
"xxhash-rust", "xxhash-rust",
] ]
@ -1446,8 +1445,8 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]] [[package]]
name = "tengri" name = "tengri"
version = "0.2.0" version = "0.3.2"
source = "git+https://codeberg.org/unspeaker/tengri?rev=10a2d17#10a2d17b487fb6a8690d807d7698f04da4267129" source = "git+https://codeberg.org/unspeaker/tengri?rev=96ff10c#96ff10c4b0fda0d71308f488f5715c2f87f782d0"
dependencies = [ dependencies = [
"tengri_input", "tengri_input",
"tengri_output", "tengri_output",
@ -1456,18 +1455,18 @@ dependencies = [
[[package]] [[package]]
name = "tengri_input" name = "tengri_input"
version = "0.2.0" version = "0.3.2"
source = "git+https://codeberg.org/unspeaker/tengri?rev=10a2d17#10a2d17b487fb6a8690d807d7698f04da4267129" source = "git+https://codeberg.org/unspeaker/tengri?rev=96ff10c#96ff10c4b0fda0d71308f488f5715c2f87f782d0"
[[package]] [[package]]
name = "tengri_output" name = "tengri_output"
version = "0.2.0" version = "0.3.2"
source = "git+https://codeberg.org/unspeaker/tengri?rev=10a2d17#10a2d17b487fb6a8690d807d7698f04da4267129" source = "git+https://codeberg.org/unspeaker/tengri?rev=96ff10c#96ff10c4b0fda0d71308f488f5715c2f87f782d0"
[[package]] [[package]]
name = "tengri_tui" name = "tengri_tui"
version = "0.2.0" version = "0.3.2"
source = "git+https://codeberg.org/unspeaker/tengri?rev=10a2d17#10a2d17b487fb6a8690d807d7698f04da4267129" source = "git+https://codeberg.org/unspeaker/tengri?rev=96ff10c#96ff10c4b0fda0d71308f488f5715c2f87f782d0"
dependencies = [ dependencies = [
"atomic_float", "atomic_float",
"better-panic", "better-panic",
@ -1479,6 +1478,7 @@ dependencies = [
"ratatui", "ratatui",
"tengri_input", "tengri_input",
"tengri_output", "tengri_output",
"unicode-width 0.2.0",
] ]
[[package]] [[package]]

View file

@ -5,7 +5,7 @@ edition = "2024"
[dependencies.tengri] [dependencies.tengri]
git = "https://codeberg.org/unspeaker/tengri" git = "https://codeberg.org/unspeaker/tengri"
rev = "10a2d17" rev = "96ff10c"
[dependencies] [dependencies]
clap = { version = "4.5.4", features = [ "cargo" ] } clap = { version = "4.5.4", features = [ "cargo" ] }
@ -19,7 +19,6 @@ opener = "0.7"
opus_headers = "0.1.2" opus_headers = "0.1.2"
pad = "0.1" pad = "0.1"
rusty-chromaprint = "0.3.0" rusty-chromaprint = "0.3.0"
unicode-width = "0.2"
walkdir = "2" walkdir = "2"
xxhash-rust = { version = "0.8.5", features = ["xxh3"] } xxhash-rust = { version = "0.8.5", features = ["xxh3"] }

View file

@ -1,10 +1,9 @@
use crate::*; use crate::*;
use tengri::tui::ratatui::{style::{Color, Style}, prelude::Stylize}; pub(crate) use tengri::tui::ratatui::style::Color;//, Style}, prelude::Stylize};
use std::sync::atomic::{AtomicU16, Ordering::Relaxed};
pub(crate) use pad::PadStr; pub(crate) use pad::PadStr;
mod column; pub use self::column::*; mod status; pub use self::status::*;
mod string; pub use self::string::*; mod table; pub use self::table::*;
impl Content<TuiOut> for Taggart { impl Content<TuiOut> for Taggart {
fn content (&self) -> impl Render<TuiOut> { fn content (&self) -> impl Render<TuiOut> {
@ -16,51 +15,6 @@ impl Content<TuiOut> for Taggart {
} }
} }
struct TreeTable<'a>(&'a Taggart);
impl<'a> Content<TuiOut> for TreeTable<'a> {
fn render (&self, to: &mut TuiOut) {
let area = to.area();
let Taggart { offset, paths, cursor, column, .. } = self.0;
let (x, w) = self.0.columns.xw(*column);
to.fill_bg([area.x() + x, area.y(), w, area.h()], Color::Rgb(0, 0, 0));
for (i, y) in area.iter_y().enumerate() {
let i_offset = i + offset;
let selected = *cursor == i_offset;
let mut x = area.x();
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 { " " };
to.area[1] = y;
for Column { width, value, .. } in self.0.columns.0.iter() {
to.area[0] = x;
if let Some(value) = value(entry) {
Content::render(&TrimStringRef(*width as u16, &value), to);
}
x += *width as u16;
}
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(192, 128, 0));
let fill = [area.x() + x as u16, y, w, 1];
to.fill_bg(fill, Color::Rgb(224, 192, 0));
if let Some((_index, value)) = &self.0.editing {
let x = area.x() + if x > 0 { x + 1 } else { x } as u16;
to.blit(&value, x, y, None)
}
}
}
}
} else {
break
}
}
to.area = area;
}
}
impl Entry { impl Entry {
pub fn name (&self) -> Option<Arc<str>> { pub fn name (&self) -> Option<Arc<str>> {
let indent = "".pad_to_width((self.depth - 1) * 2); let indent = "".pad_to_width((self.depth - 1) * 2);
@ -79,15 +33,11 @@ impl Entry {
"" ""
} }
} }
fn style (&self) -> Option<Style> { //fn style (&self) -> Option<Style> {
if self.is_dir() { //if self.is_dir() {
None //None
} else { //} else {
Some(Style::default().bold()) //Some(Style::default().bold())
} //}
} //}
}
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))))
} }

View file

@ -1,77 +0,0 @@
use crate::*;
pub struct Column<T> {
pub title: Arc<str>,
pub width: usize,
pub value: Box<dyn Fn(&T)->Option<Arc<str>> + Send + Sync>,
}
impl<T> Column<T> {
pub fn new (
title: &impl AsRef<str>,
width: usize,
value: impl Fn(&T)->Option<Arc<str>> + Send + Sync + 'static
) -> Self {
Self { width, value: Box::new(value), title: title.as_ref().into() }
}
}
pub struct Columns<T>(pub Vec<Column<T>>);
impl Default for Columns<Entry> {
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<T> Columns<T> {
pub fn header (&self) -> Arc<str> {
let mut output = String::new();
for Column { width, 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<str> {
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()
}
pub fn row_content (&self, entry: &T) -> impl Content<TuiOut> where T: Send + Sync {
Map::new(
||self.0.iter(),
|&Column { width, ref value, .. }, index|map_east(
0u16,
width as u16,
value(entry).map(|x|TrimString(width as u16, x))
)
)
}
pub fn xw (&self, column: usize) -> (u16, u16) {
let mut x: u16 = 0;
for (index, Column { width, .. }) in self.0.iter().enumerate() {
let w = *width as u16 + 1;
if index == column {
if x > 0 {
return (x - 1, w + 1)
} else {
return (x, w)
}
} else {
x += w;
}
}
(0, 0)
}
}

8
src/view/status.rs Normal file
View file

@ -0,0 +1,8 @@
use crate::*;
pub 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)))
)
}

View file

@ -1,49 +0,0 @@
use crate::*;
use crate::ratatui::prelude::Position;
use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
/// Displays an owned [str]-like with fixed maximum width.
///
/// Width is computed using [unicode_width].
pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
impl<'a, T: AsRef<str>> TrimString<T> {
fn as_ref (&self) -> TrimStringRef<'_, T> {
TrimStringRef(self.0, &self.1)
}
}
impl<'a, T: AsRef<str>> Content<TuiOut> for TrimString<T> {
fn layout (&self, to: [u16; 4]) -> [u16;4] {
Content::layout(&self.as_ref(), to)
}
fn render (&self, to: &mut TuiOut) {
Content::render(&self.as_ref(), to)
}
}
/// Displays a borrowed [str]-like with fixed maximum width
///
/// Width is computed using [unicode_width].
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
impl<T: AsRef<str>> Content<TuiOut> for TrimStringRef<'_, T> {
fn layout (&self, to: [u16; 4]) -> [u16;4] {
[to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h()]
}
fn render (&self, target: &mut TuiOut) {
let area = target.area();
let mut width: u16 = 0;
let mut chars = self.1.as_ref().chars();
while let Some(c) = chars.next() {
width += c.width().unwrap_or(0) as u16;
if width > self.0 || width > area.w() {
break
}
if let Some(cell) = target.buffer.cell_mut(Position {
x: area.x() + width,
y: area.y()
}) {
cell.set_char(c);
}
}
}
}

104
src/view/table.rs Normal file
View file

@ -0,0 +1,104 @@
use crate::*;
pub struct TreeTable<'a>(pub(crate) &'a Taggart);
impl<'a> Content<TuiOut> for TreeTable<'a> {
fn render (&self, to: &mut TuiOut) {
let area = to.area();
let Taggart { offset, paths, cursor, column, .. } = self.0;
let (x, w) = self.0.columns.xw(*column);
to.fill_bg([area.x() + x, area.y(), w, area.h()], Color::Rgb(0, 0, 0));
for (i, y) in area.iter_y().enumerate() {
let i_offset = i + offset;
let selected = *cursor == i_offset;
let mut x = area.x();
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 { " " };
to.area[1] = y;
for Column { width, value, .. } in self.0.columns.0.iter() {
to.area[0] = x;
if let Some(value) = value(entry) {
Content::render(&TrimStringRef(*width as u16, &value), to);
}
x += *width as u16 + 1;
}
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(192, 128, 0));
let fill = [area.x() + x as u16, y, w, 1];
to.fill_bg(fill, Color::Rgb(224, 192, 0));
if let Some((_index, value)) = &self.0.editing {
let x = area.x() + if x > 0 { x + 1 } else { x } as u16;
to.blit(&value, x, y, None)
}
}
}
}
} else {
break
}
}
to.area = area;
}
}
pub struct Column<T> {
pub title: Arc<str>,
pub width: usize,
pub value: Box<dyn Fn(&T)->Option<Arc<str>> + Send + Sync>,
}
impl<T> Column<T> {
pub fn new (
title: &impl AsRef<str>,
width: usize,
value: impl Fn(&T)->Option<Arc<str>> + Send + Sync + 'static
) -> Self {
Self { width, value: Box::new(value), title: title.as_ref().into() }
}
}
pub struct Columns<T>(pub Vec<Column<T>>);
impl Default for Columns<Entry> {
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<T> Columns<T> {
pub fn header (&self) -> Arc<str> {
let mut output = String::new();
for Column { width, title, .. } in self.0.iter() {
let cell = title.pad_to_width(*width);
output = format!("{output}{cell}");
}
output.into()
}
pub fn xw (&self, column: usize) -> (u16, u16) {
let mut x: u16 = 0;
for (index, Column { width, .. }) in self.0.iter().enumerate() {
let w = *width as u16 + 1;
if index == column {
if x > 0 {
return (x - 1, w + 1)
} else {
return (x, w)
}
} else {
x += w;
}
}
(0, 0)
}
}