Skip to content

Commit

Permalink
232 show album name in playlists and queue (#333)
Browse files Browse the repository at this point in the history
* Pass worker to SongWidget so it can be later used to load song album covers

* Show cover art for each song in playlist

* Show cover art for songs per default, but not in Albums

Albums instead show the songs' index.
  • Loading branch information
abegert committed Nov 17, 2021
1 parent 0656876 commit 183a835
Show file tree
Hide file tree
Showing 11 changed files with 108 additions and 18 deletions.
4 changes: 3 additions & 1 deletion src/app/components/artist_details/artist_details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ impl ArtistDetails {

if let Some(store) = model.get_list_store() {
widget.bind_artist_releases(
worker,
worker.clone(),
&*store,
clone!(@weak model => move |id| {
model.open_album(id);
Expand All @@ -143,6 +143,8 @@ impl ArtistDetails {
let playlist = Box::new(Playlist::new(
widget.top_tracks_widget().clone(),
Rc::clone(&model),
worker,
true,
));

Self {
Expand Down
2 changes: 2 additions & 0 deletions src/app/components/details/details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ impl Details {
let playlist = Box::new(Playlist::new(
widget.album_tracks_widget().clone(),
model.clone(),
worker.clone(),
false,
));

let modal = ReleaseDetailsWindow::new();
Expand Down
10 changes: 8 additions & 2 deletions src/app/components/navigation/factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,21 @@ impl ScreenFactory {
Rc::clone(&self.app_model),
self.dispatcher.box_clone(),
));
SelectionTools::new(NowPlaying::new(Rc::clone(&model)), model)
SelectionTools::new(
NowPlaying::new(Rc::clone(&model), self.worker.clone()),
model,
)
}

pub fn make_saved_tracks(&self) -> impl ListenerComponent {
let model = Rc::new(SavedTracksModel::new(
Rc::clone(&self.app_model),
self.dispatcher.box_clone(),
));
SelectionTools::new(SavedTracks::new(Rc::clone(&model)), model)
SelectionTools::new(
SavedTracks::new(Rc::clone(&model), self.worker.clone()),
model,
)
}

pub fn make_album_details(&self, id: String) -> impl ListenerComponent {
Expand Down
11 changes: 8 additions & 3 deletions src/app/components/now_playing/now_playing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use gtk::CompositeTemplate;
use std::rc::Rc;

use crate::app::components::{display_add_css_provider, Component, EventListener, Playlist};
use crate::app::{state::PlaybackEvent, AppEvent};
use crate::app::{state::PlaybackEvent, AppEvent, Worker};

use super::NowPlayingModel;

Expand Down Expand Up @@ -79,14 +79,19 @@ pub struct NowPlaying {
}

impl NowPlaying {
pub fn new(model: Rc<NowPlayingModel>) -> Self {
pub fn new(model: Rc<NowPlayingModel>, worker: Worker) -> Self {
let widget = NowPlayingWidget::new();

widget.connect_bottom_edge(clone!(@weak model => move || {
model.load_more();
}));

let playlist = Playlist::new(widget.song_list_widget().clone(), model.clone());
let playlist = Playlist::new(
widget.song_list_widget().clone(),
model.clone(),
worker,
true,
);

Self {
widget,
Expand Down
11 changes: 8 additions & 3 deletions src/app/components/playlist/playlist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::app::components::{Component, EventListener, SongWidget};
use crate::app::models::SongModel;
use crate::app::{
state::{PlaybackEvent, SelectionEvent, SelectionState},
AppEvent, ListDiff, ListStore,
AppEvent, ListDiff, ListStore, Worker,
};

#[derive(Clone, Copy, Debug)]
Expand Down Expand Up @@ -64,7 +64,12 @@ impl<Model> Playlist<Model>
where
Model: PlaylistModel + 'static,
{
pub fn new(listview: gtk::ListView, model: Rc<Model>) -> Self {
pub fn new(
listview: gtk::ListView,
model: Rc<Model>,
worker: Worker,
show_song_covers: bool,
) -> Self {
let list_model = ListStore::new();
let selection_model = gtk::NoSelection::new(Some(list_model.unsafe_store()));
let factory = gtk::SignalListItemFactory::new();
Expand All @@ -84,7 +89,7 @@ where
song_model.set_state(Self::get_item_state(&*model, &song_model));

let widget = item.child().unwrap().downcast::<SongWidget>().unwrap();
widget.bind(&song_model);
widget.bind(&song_model, worker.clone(), show_song_covers);

let id = &song_model.get_id();
widget.set_actions(model.actions_for(id).as_ref());
Expand Down
36 changes: 32 additions & 4 deletions src/app/components/playlist/song.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::app::components::display_add_css_provider;
use crate::app::loader::ImageLoader;
use crate::app::models::SongModel;

use crate::app::Worker;
use gio::MenuModel;
use gtk::prelude::*;
use gtk::subclass::prelude::*;
Expand Down Expand Up @@ -32,6 +34,9 @@ mod imp {

#[template_child]
pub menu_btn: TemplateChild<gtk::MenuButton>,

#[template_child]
pub song_cover: TemplateChild<gtk::Image>,
}

#[glib::object_subclass]
Expand Down Expand Up @@ -70,9 +75,9 @@ impl SongWidget {
glib::Object::new(&[]).expect("Failed to create an instance of SongWidget")
}

pub fn for_model(model: SongModel) -> Self {
pub fn for_model(model: SongModel, worker: Worker) -> Self {
let _self = Self::new();
_self.bind(&model);
_self.bind(&model, worker, true);
_self
}

Expand Down Expand Up @@ -114,13 +119,36 @@ impl SongWidget {
}
}

pub fn bind(&self, model: &SongModel) {
fn set_image(&self, pixbuf: Option<&gdk_pixbuf::Pixbuf>) {
imp::SongWidget::from_instance(self)
.song_cover
.set_from_pixbuf(pixbuf);
}

pub fn bind_art(&self, model: &SongModel, worker: Worker) {
if let Some(url) = model.cover_url() {
let _self = self.downgrade();
worker.send_local_task(async move {
if let Some(_self) = _self.upgrade() {
let loader = ImageLoader::new();
let result = loader.load_remote(&url, "jpg", 100, 100).await;
_self.set_image(result.as_ref());
}
});
}
}

pub fn bind(&self, model: &SongModel, worker: Worker, show_cover: bool) {
let widget = imp::SongWidget::from_instance(self);

model.bind_index(&*widget.song_index, "label");
model.bind_title(&*widget.song_title, "label");
model.bind_artist(&*widget.song_artist, "label");
model.bind_duration(&*widget.song_length, "label");
if show_cover {
self.bind_art(model, worker);
} else {
model.bind_index(&*widget.song_index, "label");
}

self.set_playing(model.get_playing());
model.connect_playing_local(clone!(@weak self as _self => move |song| {
Expand Down
8 changes: 8 additions & 0 deletions src/app/components/playlist/song.ui
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@
</style>
</object>
</child>
<child type="overlay">
<object class="GtkImage" id="song_cover">
<property name="pixel-size">30</property>
<style>
<class name="song__cover"/>
</style>
</object>
</child>
<child type="overlay">
<object class="GtkImage" id="song_icon">
<property name="icon-name">media-playback-start-symbolic</property>
Expand Down
2 changes: 2 additions & 0 deletions src/app/components/playlist_details/playlist_details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ impl PlaylistDetails {
let playlist = Box::new(Playlist::new(
widget.playlist_tracks_widget().clone(),
model.clone(),
worker.clone(),
true,
));

widget.connect_header();
Expand Down
11 changes: 8 additions & 3 deletions src/app/components/saved_tracks/saved_tracks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::rc::Rc;

use crate::app::components::{display_add_css_provider, Component, EventListener, Playlist};
use crate::app::state::LoginEvent;
use crate::app::AppEvent;
use crate::app::{AppEvent, Worker};

use super::SavedTracksModel;

Expand Down Expand Up @@ -80,14 +80,19 @@ pub struct SavedTracks {
}

impl SavedTracks {
pub fn new(model: Rc<SavedTracksModel>) -> Self {
pub fn new(model: Rc<SavedTracksModel>, worker: Worker) -> Self {
let widget = SavedTracksWidget::new();

widget.connect_bottom_edge(clone!(@weak model => move || {
model.load_more();
}));

let playlist = Playlist::new(widget.song_list_widget().clone(), model.clone());
let playlist = Playlist::new(
widget.song_list_widget().clone(),
model.clone(),
worker,
true,
);

Self {
widget,
Expand Down
30 changes: 28 additions & 2 deletions src/app/models/gtypes/song_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,33 @@ glib::wrapper! {
// Constructor for new instances. This simply calls glib::Object::new() with
// initial values for our two properties and then returns the new instance
impl SongModel {
pub fn new(id: &str, index: u32, title: &str, artist: &str, duration: &str) -> SongModel {
pub fn new(
id: &str,
index: u32,
title: &str,
artist: &str,
duration: &str,
art: &Option<String>,
) -> SongModel {
glib::Object::new::<SongModel>(&[
("index", &index),
("title", &title),
("artist", &artist),
("id", &id),
("duration", &duration),
("art", &art),
])
.expect("Failed to create")
}

pub fn cover_url(&self) -> Option<String> {
self.property("art")
.unwrap()
.get::<&str>()
.ok()
.map(|s| s.to_string())
}

pub fn set_playing(&self, is_playing: bool) {
self.set_property("playing", is_playing)
.expect("set 'playing' failed");
Expand Down Expand Up @@ -128,6 +144,7 @@ mod imp {
title: RefCell<Option<String>>,
artist: RefCell<Option<String>>,
duration: RefCell<Option<String>>,
art: RefCell<Option<String>>,
playing: RefCell<bool>,
selected: RefCell<bool>,
bindings: RefCell<BindingsInner>,
Expand Down Expand Up @@ -176,6 +193,7 @@ mod imp {
title: RefCell::new(None),
artist: RefCell::new(None),
duration: RefCell::new(None),
art: RefCell::new(None),
playing: RefCell::new(false),
selected: RefCell::new(false),
bindings: RefCell::new(Default::default()),
Expand All @@ -185,7 +203,7 @@ mod imp {

// Static array for defining the properties of the new type.
lazy_static! {
static ref PROPERTIES: [glib::ParamSpec; 7] = [
static ref PROPERTIES: [glib::ParamSpec; 8] = [
glib::ParamSpec::new_uint(
"index",
"Index",
Expand Down Expand Up @@ -219,6 +237,7 @@ mod imp {
false,
glib::ParamFlags::READWRITE,
),
glib::ParamSpec::new_string("art", "Art", "", None, glib::ParamFlags::READWRITE,),
];
}

Expand Down Expand Up @@ -268,6 +287,12 @@ mod imp {
.expect("type conformity checked by `Object::set_property`");
self.duration.replace(dur);
}
"art" => {
let art = value
.get()
.expect("type conformity checked by `Object::set_property`");
self.art.replace(art);
}
"playing" => {
let playing = value
.get()
Expand All @@ -293,6 +318,7 @@ mod imp {
"artist" => self.artist.borrow().to_value(),
"id" => self.id.borrow().to_value(),
"duration" => self.duration.borrow().to_value(),
"art" => self.art.borrow().to_value(),
"playing" => self.playing.borrow().to_value(),
"selected" => self.selected.borrow().to_value(),
_ => unimplemented!(),
Expand Down
1 change: 1 addition & 0 deletions src/app/models/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ impl From<&SongDescription> for SongModel {
&song.title,
&song.artists_name(),
&format_duration(song.duration.into()),
&song.art,
)
}
}
Expand Down

0 comments on commit 183a835

Please sign in to comment.