Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion psst-gui/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::sync::Arc;
use std::time::Duration;

use crate::{
data::{Nav, PlaybackPayload, QueueBehavior, QueueEntry},
data::{AlbumLink, Nav, PlaybackPayload, PlaylistLink, QueueBehavior, QueueEntry},
ui::find::Find,
};

Expand Down Expand Up @@ -46,6 +46,8 @@ pub const PLAYBACK_BLOCKED: Selector = Selector::new("app.playback-blocked");
pub const PLAYBACK_STOPPED: Selector = Selector::new("app.playback-stopped");

// Playback control
pub const PLAY_ALBUM: Selector<AlbumLink> = Selector::new("app.play-album");
pub const PLAY_PLAYLIST: Selector<PlaylistLink> = Selector::new("app.play-playlist");
pub const PLAY: Selector<usize> = Selector::new("app.play-index");
pub const PLAY_TRACKS: Selector<PlaybackPayload> = Selector::new("app.play-tracks");
pub const PLAY_PREVIOUS: Selector = Selector::new("app.play-previous");
Expand Down
59 changes: 59 additions & 0 deletions psst-gui/src/delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ use druid::{
commands, AppDelegate, Application, Command, DelegateCtx, Env, Event, Handled, Target,
WindowDesc, WindowId,
};
use rand::seq::IndexedRandom;
use std::fs;
use std::sync::Arc;
use threadpool::ThreadPool;

use crate::data::Track;
use crate::ui::playlist::{
RENAME_PLAYLIST, RENAME_PLAYLIST_CONFIRM, UNFOLLOW_PLAYLIST, UNFOLLOW_PLAYLIST_CONFIRM,
};
Expand Down Expand Up @@ -139,6 +142,30 @@ impl AppDelegate<AppState> for Delegate {
data: &mut AppState,
_env: &Env,
) -> Handled {
if let Some(playlist_link) = cmd.get(cmd::PLAY_PLAYLIST) {
if let Some(tracks) = data.playlist_detail.tracks.resolved() {
play_items_with_mode(
ctx,
&tracks.tracks.iter().collect::<Vec<_>>(),
crate::data::PlaybackOrigin::Playlist(playlist_link.clone()),
data.playback.queue_behavior,
|t: &&Arc<Track>| crate::data::Playable::Track((*t).clone()),
);
}
return Handled::Yes;
}
if let Some(album_link) = cmd.get(cmd::PLAY_ALBUM) {
if let Some(album) = data.album_detail.album.resolved() {
play_items_with_mode(
ctx,
&album.data.tracks.iter().collect::<Vec<_>>(),
crate::data::PlaybackOrigin::Album(album_link.clone()),
data.playback.queue_behavior,
|t: &&Arc<Track>| crate::data::Playable::Track((*t).clone()),
);
}
return Handled::Yes;
}
if cmd.is(cmd::SHOW_CREDITS_WINDOW) {
let _window_id = self.show_credits(ctx);
if let Some(track) = cmd.get(cmd::SHOW_CREDITS_WINDOW) {
Expand Down Expand Up @@ -325,3 +352,35 @@ impl Delegate {
}
}
}

fn play_items_with_mode<T, F>(
ctx: &mut DelegateCtx,
items: &[T],
origin: crate::data::PlaybackOrigin,
queue_behavior: crate::data::QueueBehavior,
to_playable: F,
) where
F: Fn(&T) -> crate::data::Playable,
{
if items.is_empty() {
return;
}

let playables: Vec<_> = items.iter().map(&to_playable).collect();
let position = if queue_behavior == crate::data::QueueBehavior::Random {
let mut rng = rand::rng();
(0..playables.len())
.collect::<Vec<_>>()
.choose(&mut rng)
.copied()
.unwrap_or(0)
} else {
0
};
let payload = crate::data::PlaybackPayload {
items: playables.into(),
origin,
position,
};
ctx.submit_command(crate::cmd::PLAY_TRACKS.with(payload));
}
28 changes: 28 additions & 0 deletions psst-gui/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,9 @@ fn root_widget() -> impl Widget<AppState> {
.must_fill_main_axis(true)
.with_child(topbar_back_button_widget())
.with_child(topbar_title_widget())
.with_flex_spacer(1.0)
.with_child(topbar_sort_widget())
.with_child(topbar_play_button_widget())
.background(Border::Bottom.with_color(theme::BACKGROUND_DARK));

let main = Flex::column()
Expand Down Expand Up @@ -467,6 +469,32 @@ fn volume_slider() -> impl Widget<AppState> {
)
}

fn topbar_play_button_widget() -> impl Widget<AppState> {
let play_button = icons::PLAY
.scale((theme::grid(2.0), theme::grid(2.0)))
.padding(theme::grid(1.0))
.link()
.rounded(theme::BUTTON_BORDER_RADIUS)
.on_left_click(|ctx, _, data: &mut AppState, _| match &data.nav {
Nav::AlbumDetail(link, _) => {
ctx.submit_command(cmd::PLAY_ALBUM.with(link.clone()));
}
Nav::PlaylistDetail(link) => {
ctx.submit_command(cmd::PLAY_PLAYLIST.with(link.clone()));
}
_ => {}
});

Either::new(
|data: &AppState, _| {
matches!(data.nav, Nav::AlbumDetail(..)) || matches!(data.nav, Nav::PlaylistDetail(..))
},
play_button,
Empty,
)
.padding((0.0, 0.0, theme::grid(1.0), 0.0))
}

fn topbar_sort_widget() -> impl Widget<AppState> {
let up_icon = icons::UP.scale((10.0, theme::grid(2.0)));
let down_icon = icons::DOWN.scale((10.0, theme::grid(2.0)));
Expand Down