diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 61f2623..da82283 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -77,6 +77,10 @@ multiple keybinds. | `left` | move left | `` | d | h | | `right` | move right | `` | n | l | | `toggle_playpause` | toggles between play and pause | p | | | +| `next song` | jumps to the next song in the queue | | | | +| `previous song` | jumps to the previous song in the queue | | | | +| `seek` | seeks forward by 5 seconds | | | | +| `seek_backwards` | seeks backwards by 5 seconds | | | | | `select` | act on the selected entry | `` | | | | `quit` | close the program | q | | | | `switch_to_library` | switch to library screen | 1 | | | @@ -93,8 +97,8 @@ multiple keybinds. | `toggle_single` | toggle single | s | | | | `toggle_consume` | toggle consume | c | | | | `toggle_random` | toggle random | z | | | -| `top` | jump to top | `` | < | g g | -| `bottom` | jump to bottom | `` | > | G | +| `top` | jump to top | `` | < | g g | +| `bottom` | jump to bottom | `` | > | G | Note that the dvorak/qwerty sets *do not* delete the default keybindings. diff --git a/src/config.rs b/src/config.rs index 47fd0a5..f6e9455 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,6 +12,7 @@ use keybind::{get_message, KeybindMap}; pub struct Config { pub keybindings: KeybindMap, pub theme: Theme, + pub seek_seconds: i64, } impl Config { @@ -19,6 +20,7 @@ impl Config { Config { keybindings: KeybindMap::default(), theme: Theme::new(), + seek_seconds: 5, } } pub fn try_read_config(mut self) -> Self { @@ -31,6 +33,9 @@ impl Config { for (key, value) in toml { match (key.as_str(), value) { ("keybindings", Value::Table(t)) => self.read_keybinds(t), + ("seek_seconds", Value::Integer(k)) if k > 0 => { + self.seek_seconds = k + } ("theme", Value::Table(t)) => self.read_theme(t), ("dvorak_keybindings", Value::Boolean(true)) => { self.keybindings = self.keybindings.with_dvorak_style(); diff --git a/src/config/keybind.rs b/src/config/keybind.rs index 7b1dec2..9c58d95 100644 --- a/src/config/keybind.rs +++ b/src/config/keybind.rs @@ -39,6 +39,10 @@ pub fn get_message(s: &str) -> Option { "toggle_single" => Some(Message::Set(Toggle::Single)), "toggle_consume" => Some(Message::Set(Toggle::Consume)), "toggle_random" => Some(Message::Set(Toggle::Random)), + "next_song" => Some(Message::NextSong), + "previous_song" => Some(Message::PreviousSong), + "seek" => Some(Message::Seek(SeekDirection::Forward)), + "seek_backwards" => Some(Message::Seek(SeekDirection::Backward)), _ => None, } } diff --git a/src/update.rs b/src/update.rs index 56ad03d..244da6f 100644 --- a/src/update.rs +++ b/src/update.rs @@ -4,8 +4,10 @@ use crate::model::proto::Searchable; use crate::model::{Model, Screen, State}; use crate::util::{safe_decrement, safe_increment}; use bitflags::bitflags; +use mpd::status::State as PlayState; use ratatui::crossterm::event::{self, KeyCode, KeyEvent}; use std::option::Option; +use std::time::Duration; pub mod build_library; mod handlers; @@ -55,10 +57,19 @@ pub enum Toggle { Consume, } +#[derive(PartialEq, Clone, Debug)] +pub enum SeekDirection { + Forward, + Backward, +} + #[derive(Clone, Debug)] pub enum Message { Direction(Dirs), PlayPause, + NextSong, + PreviousSong, + Seek(SeekDirection), Select, SwitchState(State), SwitchScreen(Screen), @@ -169,6 +180,47 @@ pub fn handle_msg(model: &mut Model, m: Message) -> Result { model.conn.toggle_pause()?; Ok(Update::STATUS) } + Message::NextSong => match model.status.state { + PlayState::Stop => Ok(Update::empty()), + _ => { + model.conn.next()?; + Ok(Update::CURRENT_SONG | Update::STATUS) + } + }, + Message::PreviousSong => match model.status.state { + PlayState::Stop => Ok(Update::empty()), + _ => { + model.conn.prev()?; + Ok(Update::CURRENT_SONG | Update::STATUS) + } + }, + Message::Seek(direction) => { + let mut update_flags = Update::empty(); + + if let (Some((current_pos, total)), Some(queue_pos)) = + (model.status.time, model.status.song) + { + let delta = Duration::from_secs( + model.config.seek_seconds.unsigned_abs(), + ); + + let new_pos = if direction == SeekDirection::Backward { + (current_pos.checked_sub(delta)) + .unwrap_or(Duration::default()) + } else { + (current_pos.checked_add(delta)).unwrap_or(total).min(total) + }; + + if new_pos >= total { + model.conn.next()?; + update_flags |= Update::CURRENT_SONG | Update::STATUS; + } else { + model.conn.seek(queue_pos.pos, new_pos)?; + update_flags |= Update::STATUS; + } + } + Ok(update_flags) + } Message::Set(t) => { match t { Toggle::Repeat => model.conn.repeat(!model.status.repeat),