diff --git a/resources/window.blp b/resources/window.blp index fe3cb13..0d0b58d 100644 --- a/resources/window.blp +++ b/resources/window.blp @@ -16,7 +16,7 @@ template $AudreyUiWindow: Adw.ApplicationWindow { Button { icon-name: "media-playlist-shuffle"; sensitive: bind template.can_click_shuffle_all; - clicked => $shuffle_all (); + clicked => $shuffle_all () swapped; } title-widget: Adw.ViewSwitcher { @@ -27,7 +27,7 @@ template $AudreyUiWindow: Adw.ApplicationWindow { [end] Button { icon-name: "applications-system"; - clicked => $show_setup_dialog (); + clicked => $show_setup_dialog () swapped; } } diff --git a/src/application.rs b/src/application.rs index b4c7f32..3166521 100644 --- a/src/application.rs +++ b/src/application.rs @@ -46,12 +46,9 @@ mod imp { crate::Mpris::setup(conn.object_server(), &window) .await .expect("could not serve mpris"); - crate::mpris::Player::setup( - conn.object_server(), - &window.playbin().unwrap(), - ) - .await - .expect("could not serve mpris player"); + crate::mpris::Player::setup(conn.object_server(), &window.playbin()) + .await + .expect("could not serve mpris player"); drop(window); // don't keep this alive diff --git a/src/meson.build b/src/meson.build index 6345583..afe6c1b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -3,7 +3,6 @@ audrey_sources = [ 'playbin.vala', 'rust.vapi', 'subsonic.vala', - 'ui/window.vala', ] audrey_deps = [ diff --git a/src/playbin.rs b/src/playbin.rs index 7ea2f26..1d76a24 100644 --- a/src/playbin.rs +++ b/src/playbin.rs @@ -19,12 +19,18 @@ pub mod ffi { extern "C" { pub fn audrey_playbin_get_type() -> glib::ffi::GType; + pub fn audrey_playbin_new() -> *mut AudreyPlaybin; pub fn audrey_playbin_get_state(self_: *mut AudreyPlaybin) -> super::state::ffi::State; pub fn audrey_playbin_pause(self_: *mut AudreyPlaybin); pub fn audrey_playbin_play(self_: *mut AudreyPlaybin); pub fn audrey_playbin_stop(self_: *mut AudreyPlaybin); + pub fn audrey_playbin_clear(self_: *mut AudreyPlaybin); pub fn audrey_playbin_get_volume(self_: *mut AudreyPlaybin) -> std::ffi::c_int; pub fn audrey_playbin_set_volume(self_: *mut AudreyPlaybin, volume: std::ffi::c_int); + pub fn audrey_playbin_set_api( + self_: *mut AudreyPlaybin, + api: *mut crate::subsonic_vala::client::ffi::AudreySubsonicClient, + ); pub fn audrey_playbin_seek(self_: *mut AudreyPlaybin, position: f64); pub fn audrey_playbin_go_to_next_track(self_: *mut AudreyPlaybin); pub fn audrey_playbin_go_to_prev_track(self_: *mut AudreyPlaybin); @@ -61,6 +67,12 @@ glib::wrapper! { } } +impl Default for Playbin { + fn default() -> Self { + unsafe { from_glib_none(ffi::audrey_playbin_new()) } + } +} + impl Playbin { pub fn state(&self) -> State { unsafe { glib::translate::from_glib(ffi::audrey_playbin_get_state(self.to_glib_none().0)) } @@ -134,6 +146,10 @@ impl Playbin { unsafe { ffi::audrey_playbin_stop(self.to_glib_none().0) } } + pub fn clear(&self) { + unsafe { ffi::audrey_playbin_clear(self.to_glib_none().0) } + } + pub fn play_queue(&self) -> gio::ListModel { unsafe { from_glib_none(ffi::audrey_playbin_get_play_queue(self.to_glib_none().0)) } } @@ -167,4 +183,8 @@ impl Playbin { glib::spawn_future_local(f(self_, param_spec)); }); } + + pub fn set_api(&self, api: &crate::subsonic_vala::Client) { + unsafe { ffi::audrey_playbin_set_api(self.to_glib_none().0, api.to_glib_none().0) } + } } diff --git a/src/rust.vapi b/src/rust.vapi index 40942f2..90b9dfd 100644 --- a/src/rust.vapi +++ b/src/rust.vapi @@ -43,4 +43,20 @@ namespace Audrey { public void save (); } + public class Ui.Window : Adw.ApplicationWindow { + public int volume { get; set; } + public bool mute { get; set; } + + public PlaybinSong? song { get; } + public Gdk.Paintable? playing_cover_art { get; set; } + + public bool cover_art_loading { get; set; } + + public Playbin playbin { get; } + + public Window (Gtk.Application app); + + public bool can_click_shuffle_all { get; } + } + } diff --git a/src/subsonic_vala.rs b/src/subsonic_vala.rs index b00ba5f..a8a3367 100644 --- a/src/subsonic_vala.rs +++ b/src/subsonic_vala.rs @@ -1,4 +1,4 @@ -mod client; +pub mod client; pub use client::Client; mod song; diff --git a/src/subsonic_vala/client.rs b/src/subsonic_vala/client.rs index 4de764d..c4647b2 100644 --- a/src/subsonic_vala/client.rs +++ b/src/subsonic_vala/client.rs @@ -1,4 +1,4 @@ -mod ffi { +pub mod ffi { use gtk::glib; use std::ffi::c_char; diff --git a/src/ui/setup.rs b/src/ui/setup.rs index fa21bbb..a8b3872 100644 --- a/src/ui/setup.rs +++ b/src/ui/setup.rs @@ -158,6 +158,12 @@ glib::wrapper! { @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; } +impl Default for Setup { + fn default() -> Self { + glib::Object::new() + } +} + impl Setup { pub fn load(&self) { glib::spawn_future_local(glib::clone!( diff --git a/src/ui/window.rs b/src/ui/window.rs index 5376ec6..9665941 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -1,45 +1,184 @@ -pub mod ffi { - use gtk::glib; +mod imp { + use adw::prelude::*; + use adw::subclass::prelude::*; + use glib::subclass::InitializingObject; + use gtk::{gdk, glib}; + use std::cell::{Cell, RefCell}; - #[repr(C)] - pub struct AudreyUiWindow { - parent_instance: adw::ffi::AdwApplicationWindow, + #[derive(gtk::CompositeTemplate, glib::Properties, Default)] + #[template(resource = "/eu/callcc/audrey/window.ui")] + #[properties(wrapper_type = super::Window)] + pub struct Window { + #[template_child] + pub(super) playbar: TemplateChild, + + #[property(get, set)] + playbin: RefCell, + + #[property(get, set, default = false)] + can_click_shuffle_all: Cell, + + #[property(get, set, nullable)] + playing_cover_art: RefCell>, + + #[property(get, set, nullable)] + song: RefCell>, + + pub(super) setup: crate::ui::Setup, + pub(super) api: RefCell>, } - #[repr(C)] - pub struct AudreyUiWindowClass { - parent_class: adw::ffi::AdwApplicationWindowClass, + #[glib::object_subclass] + impl ObjectSubclass for Window { + const NAME: &'static str = "AudreyUiWindow"; + type Type = super::Window; + type ParentType = adw::ApplicationWindow; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + klass.bind_template_callbacks(); + } + + fn instance_init(obj: &InitializingObject) { + obj.init_template(); + } } - extern "C" { - pub fn audrey_ui_window_get_type() -> glib::ffi::GType; - pub fn audrey_ui_window_new(app: *mut gtk::ffi::GtkApplication) -> *mut AudreyUiWindow; - pub fn audrey_ui_window_get_playbin( - self_: *mut AudreyUiWindow, - ) -> *mut crate::playbin::ffi::AudreyPlaybin; + #[glib::derived_properties] + impl ObjectImpl for Window {} + + impl WidgetImpl for Window {} + + impl WindowImpl for Window {} + + impl ApplicationWindowImpl for Window {} + + impl AdwApplicationWindowImpl for Window {} + + #[gtk::template_callbacks] + impl Window { + #[template_callback] + fn show_playbar_cover_art(&self, stack_child: Option<&str>) -> bool { + stack_child != Some("play-queue") + } + + #[template_callback] + async fn shuffle_all(&self) { + /* + this.can_click_shuffle_all = false; + this.playbin.clear (); + api.get_random_songs.begin (null, (song) => { + this.playbin.append_track (song); + }, (obj, res) => { + try { + api.get_random_songs.end (res); + } catch (Error e) { + error ("could not get random songs: %s", e.message); + } + this.can_click_shuffle_all = true; + + this.playbin.select_track (0); + });*/ + todo!() + } + + #[template_callback] + fn show_setup_dialog(&self) { + self.setup.present(Some(self.obj().as_ref())); + } + + pub(super) fn now_playing(&self, _song: &crate::playbin::Song) { + /* + this.song = song; + // api.scrobble.begin (this.song.id); TODO + + if (this.cancel_loading_art != null) { + this.cancel_loading_art.cancel (); + } + this.cancel_loading_art = new GLib.Cancellable (); + + this.playing_cover_art = null; // TODO: preload next art somehow + this.cover_art_loading = true; + + string song_id = this.song.id; + this.api.cover_art.begin (song_id, -1, Priority.DEFAULT, this.cancel_loading_art, (obj, res) => { + try { + this.playing_cover_art = Gdk.Texture.for_pixbuf (this.api.cover_art.end (res)); + this.cover_art_loading = false; + } catch (Error e) { + if (!(e is IOError.CANCELLED)) { + warning ("could not load cover for %s: %s", song_id, e.message); + this.cover_art_loading = false; + } + } + }); + */ + todo!() + } } } use adw::prelude::*; -use glib::translate::{from_glib_none, ToGlibPtr}; -use gtk::{gio, glib}; +use adw::subclass::prelude::*; +use gtk::{gdk, gio, glib}; glib::wrapper! { - pub struct Window(Object) + pub struct Window(ObjectSubclass) @extends adw::ApplicationWindow, gtk::ApplicationWindow, gtk::Window, gtk::Widget, @implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager; - - match fn { - type_ => || ffi::audrey_ui_window_get_type(), - } } impl Window { pub fn new(app: &impl IsA) -> Self { - unsafe { from_glib_none(ffi::audrey_ui_window_new(app.as_ref().to_glib_none().0)) } - } + let window: Self = glib::Object::builder().property("application", app).build(); - pub fn playbin(&self) -> Option { - unsafe { from_glib_none(ffi::audrey_ui_window_get_playbin(self.to_glib_none().0)) } + window + .playbin() + .bind_property("volume", &*window.imp().playbar, "volume") + .bidirectional() + .sync_create() + .build(); + + window.imp().setup.connect_closure( + "connected", + false, + glib::closure_local!( + #[weak] + window, + move |_setup: crate::ui::Setup, api: crate::subsonic_vala::Client| { + window.imp().api.replace(Some(api.clone())); + window.playbin().set_api(&api); + window.set_can_click_shuffle_all(true); + } + ), + ); + window.imp().setup.load(); + + window.playbin().connect_closure( + "new-track", + false, + glib::closure_local!( + #[weak] + window, + move |_playbin: crate::Playbin, song: crate::playbin::Song| { + window.imp().now_playing(&song); + } + ), + ); + + window.playbin().connect_closure( + "stopped", + false, + glib::closure_local!( + #[weak] + window, + move |_playbin: crate::Playbin| { + window.set_playing_cover_art(None::); + window.set_song(None::); + } + ), + ); + + window } } diff --git a/src/ui/window.vala b/src/ui/window.vala deleted file mode 100644 index 78b57b3..0000000 --- a/src/ui/window.vala +++ /dev/null @@ -1,135 +0,0 @@ -[GtkTemplate (ui = "/eu/callcc/audrey/window.ui")] -public class Audrey.Ui.Window : Adw.ApplicationWindow { - [GtkChild] public unowned PlayQueue play_queue; - [GtkChild] public unowned Playbar playbar; - //[GtkChild] public unowned Adw.ButtonRow shuffle_all_tracks; - - private Setup setup; - - private Subsonic.Client api; - - public int volume { - get { return this.playbin.volume; } - set { this.playbin.volume = value; } - } - public bool mute { - get { return this.playbin.mute; } - set { this.playbin.mute = value; } - } - - public PlaybinSong? song { get; private set; } - public Gdk.Paintable? playing_cover_art { get; set; default = null; } - - private Cancellable cancel_loading_art; - public bool cover_art_loading { get; set; default = false; } - - public Playbin playbin { get; private set; default = new Playbin (); } - - public Window (Gtk.Application app) { - Object (application: app); - - this.playbin.bind_property("volume", this.playbar, "volume", BindingFlags.BIDIRECTIONAL | BindingFlags.SYNC_CREATE); - } - - private void now_playing (PlaybinSong song) { - this.song = song; - // api.scrobble.begin (this.song.id); TODO - - if (this.cancel_loading_art != null) { - this.cancel_loading_art.cancel (); - } - this.cancel_loading_art = new GLib.Cancellable (); - - this.playing_cover_art = null; // TODO: preload next art somehow - this.cover_art_loading = true; - - string song_id = this.song.id; - this.api.cover_art.begin (song_id, -1, Priority.DEFAULT, this.cancel_loading_art, (obj, res) => { - try { - this.playing_cover_art = Gdk.Texture.for_pixbuf (this.api.cover_art.end (res)); - this.cover_art_loading = false; - } catch (Error e) { - if (!(e is IOError.CANCELLED)) { - warning ("could not load cover for %s: %s", song_id, e.message); - this.cover_art_loading = false; - } - } - }); - } - - construct { - /* - Bus.own_name ( - BusType.SESSION, - "org.mpris.MediaPlayer2.audrey", - BusNameOwnerFlags.NONE, - (conn) => { - try { - this.mpris_player = new MprisPlayer (conn, this.playbin); - - conn.register_object ("/org/mpris/MediaPlayer2", this.mpris_player); - } catch (IOError e) { - error ("could not register dbus service: %s", e.message); - } - }, - () => {}, - () => { error ("could not acquire dbus name"); }); - */ - - this.setup = new Setup (); - - this.setup.connected.connect ((api) => { - this.api = api; - this.playbin.api = api; - //this.mpris_player.api = api; - this.can_click_shuffle_all = true; - }); - this.setup.load (); - - this.playbin.new_track.connect (() => { - this.now_playing (this.playbin.play_queue.get_item (this.playbin.play_queue_position) as PlaybinSong); - }); - - this.playbin.stopped.connect (() => { - this.playing_cover_art = null; - this.song = null; - }); - } - - [GtkCallback] private void show_setup_dialog () { - this.setup.present (this); - } - - public bool can_click_shuffle_all { get; private set; default = false; } - - [GtkCallback] private void shuffle_all () { - this.can_click_shuffle_all = false; - this.playbin.clear (); - api.get_random_songs.begin (null, (song) => { - this.playbin.append_track (song); - }, (obj, res) => { - try { - api.get_random_songs.end (res); - } catch (Error e) { - error ("could not get random songs: %s", e.message); - } - this.can_click_shuffle_all = true; - - this.playbin.select_track (0); - }); - } - - [GtkCallback] private bool show_playbar_cover_art (string? stack_child) { - return stack_child != "play-queue"; - } - - public override bool close_request () { - // stop playback on close - this.playbin.stop (); - return false; - } - - ~Window () { - debug ("destroying main window"); - } -}