git/grrs/main

feature: add clipboard copy functionality and volume hud timeout

closes 4fabd63
diff --git a/Cargo.lock b/Cargo.lock
index c87d799..9bdd188 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -44,6 +44,27 @@ version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
+[[package]]
+name = "arboard"
+version = "3.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
+dependencies = [
+ "clipboard-win",
+ "image",
+ "log",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+ "objc2-foundation",
+ "parking_lot",
+ "percent-encoding",
+ "windows-sys 0.59.0",
+ "wl-clipboard-rs",
+ "x11rb",
+]
+
[[package]]
name = "arg_enum_proc_macro"
version = "0.3.4"
@@ -248,6 +269,15 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+[[package]]
+name = "clipboard-win"
+version = "5.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
+dependencies = [
+ "error-code",
+]
+
[[package]]
name = "cocoa"
version = "0.24.1"
@@ -508,6 +538,16 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
+[[package]]
+name = "dispatch2"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
+dependencies = [
+ "bitflags 2.11.1",
+ "objc2",
+]
+
[[package]]
name = "displaydoc"
version = "0.2.5"
@@ -519,6 +559,12 @@ dependencies = [
"syn",
]
+[[package]]
+name = "downcast-rs"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
+
[[package]]
name = "either"
version = "1.15.0"
@@ -570,6 +616,12 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "error-code"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7"
+
[[package]]
name = "exr"
version = "1.74.0"
@@ -612,6 +664,12 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+[[package]]
+name = "fixedbitset"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
+
[[package]]
name = "flate2"
version = "1.1.9"
@@ -720,6 +778,16 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "gethostname"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8"
+dependencies = [
+ "rustix 1.1.4",
+ "windows-link",
+]
+
[[package]]
name = "getrandom"
version = "0.2.17"
@@ -1348,6 +1416,7 @@ dependencies = [
name = "myn-grrs"
version = "0.1.2"
dependencies = [
+ "arboard",
"crossterm",
"futures-util",
"image",
@@ -1467,6 +1536,79 @@ dependencies = [
"malloc_buf",
]
+[[package]]
+name = "objc2"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
+dependencies = [
+ "objc2-encode",
+]
+
+[[package]]
+name = "objc2-app-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
+dependencies = [
+ "bitflags 2.11.1",
+ "objc2",
+ "objc2-core-graphics",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-core-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
+dependencies = [
+ "bitflags 2.11.1",
+ "dispatch2",
+ "objc2",
+]
+
+[[package]]
+name = "objc2-core-graphics"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
+dependencies = [
+ "bitflags 2.11.1",
+ "dispatch2",
+ "objc2",
+ "objc2-core-foundation",
+ "objc2-io-surface",
+]
+
+[[package]]
+name = "objc2-encode"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
+
+[[package]]
+name = "objc2-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
+dependencies = [
+ "bitflags 2.11.1",
+ "objc2",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-io-surface"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
+dependencies = [
+ "bitflags 2.11.1",
+ "objc2",
+ "objc2-core-foundation",
+]
+
[[package]]
name = "once_cell"
version = "1.21.4"
@@ -1516,6 +1658,16 @@ dependencies = [
"vcpkg",
]
+[[package]]
+name = "os_pipe"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "parking_lot"
version = "0.12.5"
@@ -1557,6 +1709,17 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+[[package]]
+name = "petgraph"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
+dependencies = [
+ "fixedbitset",
+ "hashbrown 0.15.5",
+ "indexmap",
+]
+
[[package]]
name = "pin-project-lite"
version = "0.2.17"
@@ -1659,6 +1822,15 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
+[[package]]
+name = "quick-xml"
+version = "0.41.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "quote"
version = "1.0.45"
@@ -2502,6 +2674,17 @@ dependencies = [
"once_cell",
]
+[[package]]
+name = "tree_magic_mini"
+version = "3.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6"
+dependencies = [
+ "memchr",
+ "nom",
+ "petgraph",
+]
+
[[package]]
name = "try-lock"
version = "0.2.5"
@@ -2750,6 +2933,76 @@ dependencies = [
"semver",
]
+[[package]]
+name = "wayland-backend"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078"
+dependencies = [
+ "cc",
+ "downcast-rs",
+ "rustix 1.1.4",
+ "smallvec",
+ "wayland-sys",
+]
+
+[[package]]
+name = "wayland-client"
+version = "0.31.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073"
+dependencies = [
+ "bitflags 2.11.1",
+ "rustix 1.1.4",
+ "wayland-backend",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols"
+version = "0.32.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6"
+dependencies = [
+ "bitflags 2.11.1",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-protocols-wlr"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
+dependencies = [
+ "bitflags 2.11.1",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-scanner",
+]
+
+[[package]]
+name = "wayland-scanner"
+version = "0.31.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0"
+dependencies = [
+ "proc-macro2",
+ "quick-xml",
+ "quote",
+]
+
+[[package]]
+name = "wayland-sys"
+version = "0.31.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be"
+dependencies = [
+ "pkg-config",
+]
+
[[package]]
name = "web-sys"
version = "0.3.97"
@@ -3138,12 +3391,47 @@ dependencies = [
"wasmparser",
]
+[[package]]
+name = "wl-clipboard-rs"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9651471a32e87d96ef3a127715382b2d11cc7c8bb9822ded8a7cc94072eb0a3"
+dependencies = [
+ "libc",
+ "log",
+ "os_pipe",
+ "rustix 1.1.4",
+ "thiserror 2.0.18",
+ "tree_magic_mini",
+ "wayland-backend",
+ "wayland-client",
+ "wayland-protocols",
+ "wayland-protocols-wlr",
+]
+
[[package]]
name = "writeable"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
+[[package]]
+name = "x11rb"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414"
+dependencies = [
+ "gethostname",
+ "rustix 1.1.4",
+ "x11rb-protocol",
+]
+
+[[package]]
+name = "x11rb-protocol"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
+
[[package]]
name = "y4m"
version = "0.8.0"
diff --git a/Cargo.toml b/Cargo.toml
index fd48524..d95b6fd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,6 +18,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
image = "0.25"
ratatui-image = { version = "3.0", features = ["crossterm"] }
+arboard = { version = "3.6.1", features = ["wayland-data-control"] }
[[bin]]
name = "grrs"
diff --git a/src/app.rs b/src/app.rs
index f9cbf14..2e59d98 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -45,6 +45,7 @@ pub struct AppState {
pub likes: HashMap<i64, LikedSong>,
pub likes_file: PathBuf,
pub status_message: String,
+ pub status_message_time: Option<Instant>,
pub is_muted: bool,
pub mode: AppMode,
pub liked_list_state: ListState,
@@ -59,6 +60,7 @@ pub struct AppState {
pub mpv_song: MpvSong,
pub refresh_until: Option<Instant>,
pub volume: u8,
+ pub volume_time: Option<Instant>,
}
impl AppState {
@@ -96,7 +98,6 @@ impl AppState {
let _ = fs::rename(&old_art, &new_art);
}
- // clean up the orphaned current_art.jpg from the old config dir
let _ = fs::remove_file(config_dir.join("current_art.jpg"));
let likes_file = new_likes;
@@ -132,6 +133,7 @@ impl AppState {
likes,
likes_file,
status_message: "Connecting to Gensokyo Radio...".to_string(),
+ status_message_time: Some(Instant::now()),
is_muted: false,
mode: AppMode::Playing,
liked_list_state: ListState::default(),
@@ -146,13 +148,12 @@ impl AppState {
mpv_song: MpvSong::default(),
refresh_until: None,
volume: 100,
+ volume_time: None,
}
}
pub fn toggle_like(&mut self) {
- if self.song.id == 0 {
- return;
- }
+ if self.song.id == 0 { return; }
if self.likes.remove(&self.song.id).is_none() {
let ts = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
self.likes.insert(
@@ -189,22 +190,16 @@ impl AppState {
}
pub fn matches_mpv(&self) -> bool {
- if self.mpv_song.title.is_empty() {
- return true;
- }
+ if self.mpv_song.title.is_empty() { return true; }
let tui_title = self.song.title.trim().to_lowercase();
let tui_artist = self.song.artist.trim().to_lowercase();
let mpv_title = self.mpv_song.title.trim().to_lowercase();
let mpv_artist = self.mpv_song.artist.trim().to_lowercase();
- if tui_title == mpv_title && (tui_artist == mpv_artist || mpv_artist.is_empty()) {
- return true;
- }
+ if tui_title == mpv_title && (tui_artist == mpv_artist || mpv_artist.is_empty()) { return true; }
let combined = format!("{} - {}", self.song.artist, self.song.title).to_lowercase();
- if combined == mpv_title || combined == mpv_artist {
- return true;
- }
+ if combined == mpv_title || combined == mpv_artist { return true; }
false
}
diff --git a/src/input.rs b/src/input.rs
index c82b24f..eb460bb 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -139,16 +139,29 @@ pub async fn handle_input(
if let Ok(mut stream) = UnixStream::connect(socket_path) { let _ = stream.write_all(b"{\"command\": [\"cycle\", \"mute\"]}\n"); }
} else if key.code == KeyCode::Char('-') {
state.volume = state.volume.saturating_sub(5);
+ state.volume_time = Some(std::time::Instant::now());
if let Ok(mut stream) = UnixStream::connect(socket_path) {
let cmd = format!("{{\"command\": [\"set_property\", \"volume\", {}]}}\n", state.volume);
let _ = stream.write_all(cmd.as_bytes());
}
} else if key.code == KeyCode::Char('=') || key.code == KeyCode::Char('+') {
state.volume = state.volume.saturating_add(5).min(100);
+ state.volume_time = Some(std::time::Instant::now());
if let Ok(mut stream) = UnixStream::connect(socket_path) {
let cmd = format!("{{\"command\": [\"set_property\", \"volume\", {}]}}\n", state.volume);
let _ = stream.write_all(cmd.as_bytes());
}
+ } else if key.code == KeyCode::Char('c') || key.code == KeyCode::Char('C') {
+ if let Ok(mut clipboard) = arboard::Clipboard::new() {
+ let text = if key.code == KeyCode::Char('c') {
+ format!("{} - {}", state.song.artist, state.song.title)
+ } else {
+ format!("Title: {}\nArtist: {}\nAlbum: {}\nCircle: {}\nYear: {}", state.song.title, state.song.artist, state.song.album, state.song.circle, state.song.year)
+ };
+ let _ = clipboard.set_text(text);
+ state.status_message = if key.code == KeyCode::Char('c') { "copied track/artist!".to_string() } else { "copied full metadata!".to_string() };
+ state.status_message_time = Some(std::time::Instant::now());
+ }
} else if key.code == KeyCode::Char('t') {
state.cycle_theme();
} else if key.code == KeyCode::Enter {
@@ -226,16 +239,34 @@ pub async fn handle_input(
if let Ok(mut stream) = UnixStream::connect(socket_path) { let _ = stream.write_all(b"{\"command\": [\"cycle\", \"mute\"]}\n"); }
} else if key.code == KeyCode::Char('-') {
state.volume = state.volume.saturating_sub(5);
+ state.volume_time = Some(std::time::Instant::now());
if let Ok(mut stream) = UnixStream::connect(socket_path) {
let cmd = format!("{{\"command\": [\"set_property\", \"volume\", {}]}}\n", state.volume);
let _ = stream.write_all(cmd.as_bytes());
}
} else if key.code == KeyCode::Char('=') || key.code == KeyCode::Char('+') {
state.volume = state.volume.saturating_add(5).min(100);
+ state.volume_time = Some(std::time::Instant::now());
if let Ok(mut stream) = UnixStream::connect(socket_path) {
let cmd = format!("{{\"command\": [\"set_property\", \"volume\", {}]}}\n", state.volume);
let _ = stream.write_all(cmd.as_bytes());
}
+ } else if key.code == KeyCode::Char('c') || key.code == KeyCode::Char('C') {
+ if let Some(idx) = state.liked_list_state.selected() {
+ let sorted_likes = state.get_sorted_likes();
+ if let Some(song) = sorted_likes.get(idx) {
+ if let Ok(mut clipboard) = arboard::Clipboard::new() {
+ let text = if key.code == KeyCode::Char('c') {
+ format!("{} - {}", song.artist, song.title)
+ } else {
+ format!("Title: {}\nArtist: {}\nAlbum: {}\nCircle: {}\nYear: {}", song.title, song.artist, song.album, song.circle, song.year)
+ };
+ let _ = clipboard.set_text(text);
+ state.status_message = if key.code == KeyCode::Char('c') { "copied track/artist!".to_string() } else { "copied full metadata!".to_string() };
+ state.status_message_time = Some(std::time::Instant::now());
+ }
+ }
+ }
} else if key.code == KeyCode::Char('l') {
if let Some(idx) = state.liked_list_state.selected() {
let sorted_likes = state.get_sorted_likes();
diff --git a/src/main.rs b/src/main.rs
index 0d9dc38..5c4d72b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -307,7 +307,19 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
if busy { state.refresh_until = Some(Instant::now() + REFRESH_WINDOW); }
else { state.refresh_until = None; }
}
- AppEvent::Tick => {}
+ AppEvent::Tick => {
+ if let Some(t) = state.status_message_time {
+ if t.elapsed().as_secs() >= 3 {
+ state.status_message.clear();
+ state.status_message_time = None;
+ }
+ }
+ if let Some(t) = state.volume_time {
+ if t.elapsed().as_secs() >= 3 {
+ state.volume_time = None;
+ }
+ }
+ }
}
}
}
diff --git a/src/ui.rs b/src/ui.rs
index f68e92e..930eb48 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -38,18 +38,13 @@ pub fn draw_ui(f: &mut Frame, state: &mut AppState) {
fn draw_player(f: &mut Frame, state: &mut AppState, content: Rc<[Rect]>, bottom: Rect, available_height: u16) {
if let Some(ref mut protocol) = state.art_protocol { f.render_stateful_widget(StatefulImage::new(None), content[0], protocol); }
- else { f.render_widget(Paragraph::new("Loading Art..."), content[0]); }
+ else { f.render_widget(Paragraph::new("Loading Art...").alignment(Alignment::Center), content[0]); }
- let mut basic_text = vec![
+ let basic_text = vec![
Line::from(vec![Span::styled(&state.song.title, Style::default().fg(state.theme_color).bold())]),
Line::from(vec![Span::styled("Artist: ", Style::default().bold()), Span::raw(&state.song.artist)]),
Line::from(vec![Span::styled("Album: ", Style::default().bold()), Span::raw(&state.song.album)]),
];
- let mut basic_info_height = 3;
- if !state.status_message.is_empty() {
- basic_text.push(Line::from(vec![Span::styled(&state.status_message, Style::default().fg(Color::Red))]));
- basic_info_height += 1;
- }
let mut ext_text = vec![];
if available_height >= 16 {
@@ -66,7 +61,7 @@ fn draw_player(f: &mut Frame, state: &mut AppState, content: Rc<[Rect]>, bottom:
let refresh_height = state.is_refreshing() as u16;
let right_chunks = Layout::default().direction(Direction::Vertical).constraints([
- Constraint::Length(basic_info_height),
+ Constraint::Length(3),
Constraint::Length(1),
Constraint::Length(1),
Constraint::Length(1),
@@ -95,8 +90,12 @@ fn draw_player(f: &mut Frame, state: &mut AppState, content: Rc<[Rect]>, bottom:
if state.is_muted || state.volume == 0 {
status_spans.push(Span::styled(" [Muted]", Style::default().fg(Color::Red)));
- } else {
- let vol_color = if state.volume >= 66 { Color::Green } else if state.volume >= 33 { Color::Yellow } else { Color::Red };
+ }
+
+ if !state.status_message.is_empty() {
+ status_spans.push(Span::styled(format!(" [{}]", state.status_message), Style::default().fg(state.theme_color)));
+ } else if !(state.is_muted || state.volume == 0) && state.volume_time.is_some() {
+ let vol_color = if state.volume >= 66 { state.theme_color } else if state.volume >= 33 { Color::Yellow } else { Color::Red };
status_spans.push(Span::styled(format!(" [Vol: {}%]", state.volume), Style::default().fg(vol_color)));
}
@@ -106,7 +105,7 @@ fn draw_player(f: &mut Frame, state: &mut AppState, content: Rc<[Rect]>, bottom:
fn draw_library(f: &mut Frame, state: &mut AppState, content: Rc<[Rect]>, bottom: Rect) {
if let Some(ref mut protocol) = state.selected_art { f.render_stateful_widget(StatefulImage::new(None), content[0], protocol); }
- else { f.render_widget(Paragraph::new("No Art Saved"), content[0]); }
+ else { f.render_widget(Paragraph::new("No Art Saved").alignment(Alignment::Center), content[0]); }
let sorted = state.get_sorted_likes();
let mut items: Vec<ListItem> = sorted.iter().enumerate().map(|(i, s)| {
@@ -122,8 +121,12 @@ fn draw_library(f: &mut Frame, state: &mut AppState, content: Rc<[Rect]>, bottom
if state.is_muted || state.volume == 0 {
status_spans.push(Span::styled(" [Muted]", Style::default().fg(Color::Red)));
- } else {
- let vol_color = if state.volume >= 66 { Color::Green } else if state.volume >= 33 { Color::Yellow } else { Color::Red };
+ }
+
+ if !state.status_message.is_empty() {
+ status_spans.push(Span::styled(format!(" [{}]", state.status_message), Style::default().fg(state.theme_color)));
+ } else if !(state.is_muted || state.volume == 0) && state.volume_time.is_some() {
+ let vol_color = if state.volume >= 66 { state.theme_color } else if state.volume >= 33 { Color::Yellow } else { Color::Red };
status_spans.push(Span::styled(format!(" [Vol: {}%]", state.volume), Style::default().fg(vol_color)));
}
@@ -139,6 +142,7 @@ fn draw_help_popup(f: &mut Frame, state: &AppState) {
Line::from(vec![Span::styled(" [m] ", Style::default().bold()), Span::raw("mute/unmute stream")]),
Line::from(vec![Span::styled(" [-] ", Style::default().bold()), Span::raw("volume down")]),
Line::from(vec![Span::styled(" [=]/[+] ", Style::default().bold()), Span::raw("volume up")]),
+ Line::from(vec![Span::styled(" [c]/[C] ", Style::default().bold()), Span::raw("copy short/full metadata")]),
Line::from(vec![Span::styled(" [t] ", Style::default().bold()), Span::raw("cycle theme color")]),
Line::from(vec![Span::styled(" [enter] ", Style::default().bold()), Span::raw("open song info")]),
Line::from(vec![Span::styled(" [tab] ", Style::default().bold()), Span::raw("switch between library and player")]),