git/grrs/main

feature: add volume control keybinds and UI indicator

closes 2787ea4
diff --git a/Cargo.toml b/Cargo.toml
index bb239d6..fd48524 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "myn-grrs"
-version = "0.1.1"
+version = "0.1.2"
edition = "2024"
repository = "https://seed.radicle.xyz/rad:z3dnwE7h9y4ndGdqeafNLP8eDPn3Y.git"
description = "a minimal tui for gensokyo radio"
diff --git a/src/app.rs b/src/app.rs
index 4d36202..f9cbf14 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -58,6 +58,7 @@ pub struct AppState {
pub theme_file: PathBuf,
pub mpv_song: MpvSong,
pub refresh_until: Option<Instant>,
+ pub volume: u8,
}
impl AppState {
@@ -144,6 +145,7 @@ impl AppState {
theme_file,
mpv_song: MpvSong::default(),
refresh_until: None,
+ volume: 100,
}
}
diff --git a/src/input.rs b/src/input.rs
index 936cb90..c82b24f 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -137,6 +137,18 @@ pub async fn handle_input(
} else if key.code == KeyCode::Char('m') {
state.is_muted = !state.is_muted;
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);
+ 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);
+ 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('t') {
state.cycle_theme();
} else if key.code == KeyCode::Enter {
@@ -212,6 +224,18 @@ pub async fn handle_input(
} else if key.code == KeyCode::Char('m') {
state.is_muted = !state.is_muted;
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);
+ 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);
+ 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('l') {
if let Some(idx) = state.liked_list_state.selected() {
let sorted_likes = state.get_sorted_likes();
diff --git a/src/ui.rs b/src/ui.rs
index 9a028b3..f68e92e 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -92,7 +92,14 @@ fn draw_player(f: &mut Frame, state: &mut AppState, content: Rc<[Rect]>, bottom:
let bottom_layout = Layout::default().direction(Direction::Horizontal).constraints([Constraint::Min(40), Constraint::Length(25)]).split(bottom);
let mut status_spans = vec![Span::styled("[?] Help", Style::default().fg(Color::DarkGray))];
if state.song.id != 0 && state.likes.contains_key(&state.song.id) { status_spans.push(Span::styled(" [Liked]", Style::default().fg(state.theme_color))); }
- if state.is_muted { status_spans.push(Span::styled(" [Muted]", Style::default().fg(Color::Red))); }
+
+ 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 };
+ status_spans.push(Span::styled(format!(" [Vol: {}%]", state.volume), Style::default().fg(vol_color)));
+ }
+
f.render_widget(Paragraph::new(Line::from(status_spans)).alignment(Alignment::Left), bottom_layout[0]);
f.render_widget(Paragraph::new(Line::from(vec![Span::styled(if state.song.listeners > 0 { format!("{} Listeners ", state.song.listeners) } else { "LIVE ".to_string() }, Style::default().fg(state.theme_color).bold())])).alignment(Alignment::Right), bottom_layout[1]);
}
@@ -112,17 +119,26 @@ fn draw_library(f: &mut Frame, state: &mut AppState, content: Rc<[Rect]>, bottom
let mut status_spans = vec![Span::styled("[?] Help", Style::default().fg(Color::DarkGray))];
if state.song.id != 0 && state.likes.contains_key(&state.song.id) { status_spans.push(Span::styled(" [Liked]", Style::default().fg(state.theme_color))); }
- if state.is_muted { status_spans.push(Span::styled(" [Muted]", Style::default().fg(Color::Red))); }
+
+ 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 };
+ status_spans.push(Span::styled(format!(" [Vol: {}%]", state.volume), Style::default().fg(vol_color)));
+ }
+
f.render_widget(Paragraph::new(Line::from(status_spans)).alignment(Alignment::Left), bottom);
}
fn draw_help_popup(f: &mut Frame, state: &AppState) {
- let area = centered_rect(50, 40, f.area());
+ let area = centered_rect(50, 50, f.area());
f.render_widget(Clear, area);
f.render_widget(Paragraph::new(vec![
Line::from(""),
Line::from(vec![Span::styled(" [l] ", Style::default().bold()), Span::raw("like/unlike song")]),
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(" [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")]),