diff --git a/psst-gui/src/controller/playback.rs b/psst-gui/src/controller/playback.rs index 38fe0846..cd1db0ae 100644 --- a/psst-gui/src/controller/playback.rs +++ b/psst-gui/src/controller/playback.rs @@ -274,6 +274,22 @@ impl PlaybackController { self.send(PlayerEvent::Command(PlayerCommand::Seek { position })); } + fn seek_relative(&mut self, data: &AppState, forward: bool) { + if let Some(now_playing) = &data.playback.now_playing { + let seek_duration = Duration::from_secs(data.config.seek_duration as u64); + + // Calculate new position, ensuring it does not exceed duration for forward seeks. + let seek_position = if forward { + now_playing.progress + seek_duration + } else { + now_playing.progress.saturating_sub(seek_duration) + } + .min(now_playing.item.duration()); // Safeguard to not exceed the track duration. + + self.seek(seek_position); + } + } + fn set_volume(&mut self, volume: f64) { self.send(PlayerEvent::Command(PlayerCommand::SetVolume { volume })); } @@ -412,11 +428,19 @@ where ctx.set_handled(); } Event::KeyDown(key) if key.code == Code::ArrowRight => { - self.next(); + if key.mods.shift() { + self.next(); + } else { + self.seek_relative(data, true); + } ctx.set_handled(); } Event::KeyDown(key) if key.code == Code::ArrowLeft => { - self.previous(); + if key.mods.shift() { + self.previous(); + } else { + self.seek_relative(data, false); + } ctx.set_handled(); } Event::KeyDown(key) if key.key == KbKey::Character("+".to_string()) => { diff --git a/psst-gui/src/data/config.rs b/psst-gui/src/data/config.rs index 093cbf32..62fcb526 100644 --- a/psst-gui/src/data/config.rs +++ b/psst-gui/src/data/config.rs @@ -94,6 +94,7 @@ pub struct Config { pub sort_order: SortOrder, pub sort_criteria: SortCriteria, pub paginated_limit: usize, + pub seek_duration: usize, } impl Default for Config { @@ -111,6 +112,7 @@ impl Default for Config { sort_order: Default::default(), sort_criteria: Default::default(), paginated_limit: 500, + seek_duration: 10, } } } diff --git a/psst-gui/src/ui/playback.rs b/psst-gui/src/ui/playback.rs index 22cadb36..b238e646 100644 --- a/psst-gui/src/ui/playback.rs +++ b/psst-gui/src/ui/playback.rs @@ -90,29 +90,27 @@ fn playing_item_widget() -> impl Widget { Flex::row() .with_child(cover_art) .with_flex_child( - Flex::row() - .with_spacer(theme::grid(2.0)) - .with_flex_child( - Flex::column() - .cross_axis_alignment(CrossAxisAlignment::Start) - .with_child(name) - .with_spacer(2.0) - .with_child(detail) - .with_spacer(2.0) - .with_child(origin) - .on_click(|ctx, now_playing, _| { - ctx.submit_command(cmd::NAVIGATE.with(now_playing.origin.to_nav())); - }) - .context_menu(|now_playing| match &now_playing.item { - Playable::Track(track) => { - track::track_menu(track, &now_playing.library, &now_playing.origin) - } - Playable::Episode(episode) => { - episode::episode_menu(episode, &now_playing.library) - } - }), - 1.0 - ), + Flex::row().with_spacer(theme::grid(2.0)).with_flex_child( + Flex::column() + .cross_axis_alignment(CrossAxisAlignment::Start) + .with_child(name) + .with_spacer(2.0) + .with_child(detail) + .with_spacer(2.0) + .with_child(origin) + .on_click(|ctx, now_playing, _| { + ctx.submit_command(cmd::NAVIGATE.with(now_playing.origin.to_nav())); + }) + .context_menu(|now_playing| match &now_playing.item { + Playable::Track(track) => { + track::track_menu(track, &now_playing.library, &now_playing.origin) + } + Playable::Episode(episode) => { + episode::episode_menu(episode, &now_playing.library) + } + }), + 1.0, + ), 1.0, ) .with_child(ViewSwitcher::new( diff --git a/psst-gui/src/ui/preferences.rs b/psst-gui/src/ui/preferences.rs index 73159b35..1192db92 100644 --- a/psst-gui/src/ui/preferences.rs +++ b/psst-gui/src/ui/preferences.rs @@ -212,6 +212,22 @@ fn general_tab_widget() -> impl Widget { col = col.with_spacer(theme::grid(3.0)); + col = col + .with_child(Label::new("Seek Duration").with_font(theme::UI_FONT_MEDIUM)) + .with_spacer(theme::grid(2.0)) + .with_child( + Flex::row() + .with_child( + TextBox::new().with_formatter(ParseFormatter::with_format_fn( + |usize: &usize| usize.to_string(), + )), + ) + .padding((theme::grid(1.5), 0.0)) + .lens(AppState::config.then(Config::seek_duration)), + ); + + col = col.with_spacer(theme::grid(3.0)); + col = col .with_child( Label::new("Max Loaded Tracks (requires restart)").with_font(theme::UI_FONT_MEDIUM), diff --git a/psst-gui/src/widget/mod.rs b/psst-gui/src/widget/mod.rs index 5cb3a437..c6407d2f 100644 --- a/psst-gui/src/widget/mod.rs +++ b/psst-gui/src/widget/mod.rs @@ -23,7 +23,7 @@ use druid_shell::Cursor; pub use empty::Empty; pub use link::Link; pub use maybe::Maybe; -pub use overlay::{Overlay}; +pub use overlay::Overlay; pub use promise::Async; pub use remote_image::RemoteImage; pub use theme::ThemeScope;