mirror of
https://codeberg.org/unspeaker/perch.git
synced 2025-12-06 09:36:42 +01:00
move trimmed strings to tengri
This commit is contained in:
parent
c67d3c8803
commit
6e0bf1e4b6
7 changed files with 135 additions and 200 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
|
@ -1178,9 +1178,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rust_decimal"
|
||||
version = "1.36.0"
|
||||
version = "1.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555"
|
||||
checksum = "5c24af6e7ac43c88a8a458d1139d0246fdce2f6cd2f1ac6cb51eb88b29c978af"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"borsh",
|
||||
|
|
@ -1433,7 +1433,6 @@ dependencies = [
|
|||
"pad",
|
||||
"rusty-chromaprint",
|
||||
"tengri",
|
||||
"unicode-width 0.2.0",
|
||||
"walkdir",
|
||||
"xxhash-rust",
|
||||
]
|
||||
|
|
@ -1446,8 +1445,8 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
|||
|
||||
[[package]]
|
||||
name = "tengri"
|
||||
version = "0.2.0"
|
||||
source = "git+https://codeberg.org/unspeaker/tengri?rev=10a2d17#10a2d17b487fb6a8690d807d7698f04da4267129"
|
||||
version = "0.3.2"
|
||||
source = "git+https://codeberg.org/unspeaker/tengri?rev=96ff10c#96ff10c4b0fda0d71308f488f5715c2f87f782d0"
|
||||
dependencies = [
|
||||
"tengri_input",
|
||||
"tengri_output",
|
||||
|
|
@ -1456,18 +1455,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tengri_input"
|
||||
version = "0.2.0"
|
||||
source = "git+https://codeberg.org/unspeaker/tengri?rev=10a2d17#10a2d17b487fb6a8690d807d7698f04da4267129"
|
||||
version = "0.3.2"
|
||||
source = "git+https://codeberg.org/unspeaker/tengri?rev=96ff10c#96ff10c4b0fda0d71308f488f5715c2f87f782d0"
|
||||
|
||||
[[package]]
|
||||
name = "tengri_output"
|
||||
version = "0.2.0"
|
||||
source = "git+https://codeberg.org/unspeaker/tengri?rev=10a2d17#10a2d17b487fb6a8690d807d7698f04da4267129"
|
||||
version = "0.3.2"
|
||||
source = "git+https://codeberg.org/unspeaker/tengri?rev=96ff10c#96ff10c4b0fda0d71308f488f5715c2f87f782d0"
|
||||
|
||||
[[package]]
|
||||
name = "tengri_tui"
|
||||
version = "0.2.0"
|
||||
source = "git+https://codeberg.org/unspeaker/tengri?rev=10a2d17#10a2d17b487fb6a8690d807d7698f04da4267129"
|
||||
version = "0.3.2"
|
||||
source = "git+https://codeberg.org/unspeaker/tengri?rev=96ff10c#96ff10c4b0fda0d71308f488f5715c2f87f782d0"
|
||||
dependencies = [
|
||||
"atomic_float",
|
||||
"better-panic",
|
||||
|
|
@ -1479,6 +1478,7 @@ dependencies = [
|
|||
"ratatui",
|
||||
"tengri_input",
|
||||
"tengri_output",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
[package]
|
||||
name = "taggart"
|
||||
name = "taggart"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies.tengri]
|
||||
git = "https://codeberg.org/unspeaker/tengri"
|
||||
rev = "10a2d17"
|
||||
rev = "96ff10c"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.4", features = [ "cargo" ] }
|
||||
|
|
@ -19,7 +19,6 @@ opener = "0.7"
|
|||
opus_headers = "0.1.2"
|
||||
pad = "0.1"
|
||||
rusty-chromaprint = "0.3.0"
|
||||
unicode-width = "0.2"
|
||||
walkdir = "2"
|
||||
xxhash-rust = { version = "0.8.5", features = ["xxh3"] }
|
||||
|
||||
|
|
|
|||
70
src/view.rs
70
src/view.rs
|
|
@ -1,10 +1,9 @@
|
|||
use crate::*;
|
||||
use tengri::tui::ratatui::{style::{Color, Style}, prelude::Stylize};
|
||||
use std::sync::atomic::{AtomicU16, Ordering::Relaxed};
|
||||
pub(crate) use tengri::tui::ratatui::style::Color;//, Style}, prelude::Stylize};
|
||||
pub(crate) use pad::PadStr;
|
||||
|
||||
mod column; pub use self::column::*;
|
||||
mod string; pub use self::string::*;
|
||||
mod status; pub use self::status::*;
|
||||
mod table; pub use self::table::*;
|
||||
|
||||
impl Content<TuiOut> for Taggart {
|
||||
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 {
|
||||
pub fn name (&self) -> Option<Arc<str>> {
|
||||
let indent = "".pad_to_width((self.depth - 1) * 2);
|
||||
|
|
@ -79,15 +33,11 @@ impl Entry {
|
|||
"⁇"
|
||||
}
|
||||
}
|
||||
fn style (&self) -> Option<Style> {
|
||||
if self.is_dir() {
|
||||
None
|
||||
} else {
|
||||
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))))
|
||||
//fn style (&self) -> Option<Style> {
|
||||
//if self.is_dir() {
|
||||
//None
|
||||
//} else {
|
||||
//Some(Style::default().bold())
|
||||
//}
|
||||
//}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
8
src/view/status.rs
Normal 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)))
|
||||
)
|
||||
}
|
||||
|
|
@ -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
104
src/view/table.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue