diff --git a/resources/window.blp b/resources/window.blp index 6182185..a3d7f49 100644 --- a/resources/window.blp +++ b/resources/window.blp @@ -41,24 +41,28 @@ template $AudreyUiWindow: Adw.ApplicationWindow { child: Box { orientation: vertical; - $AudreyUiAlbumCarousel carousel1 { + $AudreyUiAlbumCarousel { title: _("Explore from your library"); type: random; + client: bind template.client; } - $AudreyUiAlbumCarousel carousel2 { + $AudreyUiAlbumCarousel { title: _("Newly added releases"); type: newest; + client: bind template.client; } - $AudreyUiAlbumCarousel carousel3 { + $AudreyUiAlbumCarousel { title: _("Recently played"); type: recent; + client: bind template.client; } - $AudreyUiAlbumCarousel carousel4 { + $AudreyUiAlbumCarousel { title: _("Most played"); type: frequent; + client: bind template.client; } }; }; diff --git a/src/main.rs b/src/main.rs index 2431a46..f755a07 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,7 @@ use gtk::{gio, glib}; pub mod mpv; pub mod util; +pub mod wrappers; fn init_tracing() { #[cfg(debug_assertions)] diff --git a/src/model/song.rs b/src/model/song.rs index 027805a..7109ad8 100644 --- a/src/model/song.rs +++ b/src/model/song.rs @@ -74,8 +74,7 @@ glib::wrapper! { } impl Song { - pub fn from_child(window: &crate::ui::Window, song: &subsonic::schema::Child) -> Self { - let api = window.api(); + pub fn from_child(client: &crate::wrappers::Client, song: &subsonic::schema::Child) -> Self { let song: Self = Object::builder() .property("id", &song.id) .property("title", &song.title) @@ -84,20 +83,20 @@ impl Song { .property("genre", &song.genre) .property("duration", song.duration as i64) .property("track", song.track.map(i64::from).unwrap_or(-1)) - .property("stream-url", api.stream_url(&song.id).as_str()) + .property("stream-url", client.stream_url(&song.id).as_str()) .property("cover-art", &song.cover_art) .build(); song } - pub fn need_thumbnail(&self, window: &crate::ui::Window) { - let api = window.api(); + pub fn need_thumbnail(&self, client: &crate::wrappers::Client, scale_factor: u32) { + let api = client.clone(); let mut thumbnail_loading = self.imp().thumbnail_loading.borrow_mut(); if thumbnail_loading.is_none() { let id = self.id(); let song_weak = self.downgrade(); - let scale_factor = window.scale_factor() as u32; // NOTE: assumed constant + let scale_factor = scale_factor; // NOTE: assumed constant *thumbnail_loading = Some(glib::spawn_future_local(async move { let _permit = SEM.acquire().await.unwrap(); diff --git a/src/ui/album_carousel.rs b/src/ui/album_carousel.rs index 069854b..3f2cd05 100644 --- a/src/ui/album_carousel.rs +++ b/src/ui/album_carousel.rs @@ -4,7 +4,6 @@ use adw::subclass::prelude::*; use adw::{gio, glib}; use glib::subclass::InitializingObject; use std::cell::{Cell, OnceCell, RefCell}; -use std::rc::Rc; use std::sync::OnceLock; use tracing::{event, Level}; @@ -29,10 +28,10 @@ mod imp { #[derive(gtk::CompositeTemplate, Default)] #[template(resource = "/eu/callcc/audrey/album_carousel.ui")] pub struct AlbumCarousel { - pub api: RefCell>>, title: RefCell, model: OnceCell, r#type: Cell, + client: RefCell>, } #[glib::object_subclass] @@ -61,6 +60,7 @@ mod imp { glib::ParamSpecObject::builder::("model") .read_only() .build(), + glib::ParamSpecObject::builder::("client").build(), ] }) } @@ -70,6 +70,7 @@ mod imp { 1 => self.title.borrow().clone().into(), 2 => self.r#type.get().into(), 3 => self.model().into(), + 4 => self.client.borrow().clone().into(), _ => unreachable!(), } } @@ -83,6 +84,10 @@ mod imp { self.r#type.replace(value.get_owned().unwrap()); self.update_model(); } + 4 => { + self.client.replace(value.get_owned().unwrap()); + self.update_model(); + } _ => unreachable!(), } } @@ -102,13 +107,13 @@ mod imp { pub fn update_model(&self) { self.model().remove_all(); - let api = self.api.borrow(); + let api = self.client.borrow(); let api = match api.as_ref() { Some(api) => api, None => return, }; - let api = Rc::clone(&api); + let api = api.clone(); let carousel = self.obj().downgrade(); let type_ = match self.r#type.get() { @@ -145,10 +150,3 @@ glib::wrapper! { @extends adw::Bin, gtk::Widget, @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; } - -impl AlbumCarousel { - pub fn set_api(&self, api: Option<&Rc>) { - self.imp().api.replace(api.map(Rc::clone)); - self.imp().update_model(); - } -} diff --git a/src/ui/play_queue/song.rs b/src/ui/play_queue/song.rs index ee00b82..5a47508 100644 --- a/src/ui/play_queue/song.rs +++ b/src/ui/play_queue/song.rs @@ -200,7 +200,10 @@ impl Song { self.set_position(position); self.set_playlist_pos(window.playlist_pos()); - song.need_thumbnail(window); + song.need_thumbnail( + window.client().as_ref().unwrap(), + window.scale_factor() as u32, + ); } pub const fn unbind(&self) {} diff --git a/src/ui/window.rs b/src/ui/window.rs index 32b10e1..29d7468 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -1,5 +1,4 @@ use crate::model::Song; -use crate::ui::AlbumCarousel; use crate::util::buffering::BufferingPulseController; use crate::{mpris, mpv}; use adw::prelude::*; @@ -49,7 +48,8 @@ mod imp { pub(super) setup: crate::ui::Setup, - pub(super) api: RefCell>>, + #[property(get, set, nullable)] + pub client: RefCell>, pub(super) mpv: mpv::Handle, #[property(get)] @@ -91,15 +91,6 @@ mod imp { refreshing_albums: Cell, css_provider: gtk::CssProvider, - - #[template_child] - pub carousel1: TemplateChild, - #[template_child] - pub carousel2: TemplateChild, - #[template_child] - pub carousel3: TemplateChild, - #[template_child] - pub carousel4: TemplateChild, } impl Default for Window { @@ -131,7 +122,7 @@ mod imp { background: Default::default(), _song: (), setup: Default::default(), - api: Default::default(), + client: Default::default(), mpv, playlist_model: gio::ListStore::new::(), @@ -162,11 +153,6 @@ mod imp { refreshing_albums: Cell::new(true), css_provider: gtk::CssProvider::new(), - - carousel1: Default::default(), - carousel2: Default::default(), - carousel3: Default::default(), - carousel4: Default::default(), } } } @@ -416,11 +402,11 @@ mod imp { self.set_pause(false); let api = { - let api = self.api.borrow(); - Rc::clone(api.as_ref().unwrap()) + let api = self.client.borrow(); + api.as_ref().unwrap().clone() }; for song in api.random_songs(50).await.unwrap().into_iter() { - let song = Song::from_child(&self.obj(), &song); + let song = Song::from_child(&api, &song); self.mpv .command(["loadfile", &song.stream_url(), "append-play"]) .unwrap(); @@ -757,7 +743,7 @@ mod imp { if let Some(handle) = self .loading_cover_handle .replace(Some(glib::spawn_future_local(async move { - let api = window.imp().api.borrow().as_ref().unwrap().clone(); + let api = window.imp().client.borrow().as_ref().unwrap().clone(); let image = match api .cover_art(&song_id, Some(800 * scale_factor)) // 800px times ui scale .await @@ -1032,14 +1018,15 @@ impl Window { self.set_can_click_shuffle_all(true); - self.imp().carousel1.set_api(Some(&api)); - self.imp().carousel2.set_api(Some(&api)); - self.imp().carousel3.set_api(Some(&api)); - self.imp().carousel4.set_api(Some(&api)); - - if self.imp().api.replace(Some(api)).is_some() { + if self + .imp() + .client + .replace(Some(crate::wrappers::Client::new((*api).clone()))) + .is_some() + { unimplemented!("changing the api object"); } + self.notify("client"); } pub fn playlist_next(&self) { @@ -1153,8 +1140,4 @@ impl Window { self.imp().mpv.command(["stop"]).unwrap(); self.playlist_model().remove_all(); } - - pub fn api(&self) -> Rc { - Rc::clone(self.imp().api.borrow().as_ref().unwrap()) - } } diff --git a/src/wrappers.rs b/src/wrappers.rs new file mode 100644 index 0000000..79286a4 --- /dev/null +++ b/src/wrappers.rs @@ -0,0 +1,2 @@ +pub mod client; +pub use client::Client; diff --git a/src/wrappers/client.rs b/src/wrappers/client.rs new file mode 100644 index 0000000..97e86c9 --- /dev/null +++ b/src/wrappers/client.rs @@ -0,0 +1,44 @@ +// simple wrapper class so we can use the client object as a glib property + +use crate::subsonic; +use glib::subclass::prelude::*; +use std::cell::OnceCell; +use std::ops::Deref; + +mod imp { + use super::*; + + #[derive(Default)] + pub struct Client(pub OnceCell); + + #[glib::object_subclass] + impl ObjectSubclass for Client { + const NAME: &'static str = "AudreyClient"; + type Type = super::Client; + } + + impl ObjectImpl for Client {} +} + +glib::wrapper! { + pub struct Client(ObjectSubclass); +} + +impl Client { + pub fn new(inner: subsonic::Client) -> Self { + let client: Self = glib::Object::new(); + client.imp().0.set(inner).map_err(|_| ()).unwrap(); + client + } +} + +impl Deref for Client { + type Target = subsonic::Client; + + fn deref(&self) -> &Self::Target { + self.imp() + .0 + .get() + .expect("tried to deref uninitialized client object") + } +}