diff --git a/resources/play_queue_song.blp b/resources/play_queue_song.blp index 7ddb3fe..05f8dd5 100644 --- a/resources/play_queue_song.blp +++ b/resources/play_queue_song.blp @@ -110,8 +110,8 @@ template $AudreyUiPlayQueueSong: Box { actions: move; propagation-phase: capture; - prepare => $on_drag_prepare (); - drag-begin => $on_drag_begin (); + prepare => $on_drag_prepare () swapped; + drag-begin => $on_drag_begin () swapped; } DropTarget { @@ -119,7 +119,7 @@ template $AudreyUiPlayQueueSong: Box { formats: "AudreyUiPlayQueueSong"; preload: true; - drop => $on_drop (); + drop => $on_drop () swapped; } } diff --git a/src/playbin.rs b/src/playbin.rs index 7c521ef..f2814dd 100644 --- a/src/playbin.rs +++ b/src/playbin.rs @@ -1,10 +1,10 @@ -mod song; +pub mod song; pub use song::Song; mod state; pub use state::State; -mod ffi { +pub mod ffi { use gtk::glib; #[repr(C)] @@ -31,6 +31,15 @@ mod ffi { pub fn audrey_playbin_get_duration(self_: *mut AudreyPlaybin) -> f64; pub fn audrey_playbin_get_mute(self_: *mut AudreyPlaybin) -> glib::ffi::gboolean; pub fn audrey_playbin_set_mute(self_: *mut AudreyPlaybin, mute: glib::ffi::gboolean); + pub fn audrey_playbin_get_play_queue_position( + self_: *mut AudreyPlaybin, + ) -> std::ffi::c_uint; + pub fn audrey_playbin_move_track( + self_: *mut AudreyPlaybin, + from: std::ffi::c_uint, + to: std::ffi::c_uint, + ); + pub fn audrey_playbin_remove_track(self_: *mut AudreyPlaybin, position: std::ffi::c_uint); } } @@ -95,4 +104,16 @@ impl Playbin { pub fn set_mute(&self, mute: bool) { unsafe { ffi::audrey_playbin_set_mute(self.to_glib_none().0, mute.into_glib()) } } + + pub fn play_queue_position(&self) -> u32 { + unsafe { ffi::audrey_playbin_get_play_queue_position(self.to_glib_none().0) } + } + + pub fn move_track(&self, from: u32, to: u32) { + unsafe { ffi::audrey_playbin_move_track(self.to_glib_none().0, from, to) } + } + + pub fn remove_track(&self, position: u32) { + unsafe { ffi::audrey_playbin_remove_track(self.to_glib_none().0, position) } + } } diff --git a/src/playbin/song.rs b/src/playbin/song.rs index 0edadab..09c8ffd 100644 --- a/src/playbin/song.rs +++ b/src/playbin/song.rs @@ -1,14 +1,14 @@ -mod ffi { +pub mod ffi { use gtk::glib; #[repr(C)] pub struct AudreyPlaybinSong { - parent_instance: glib::gobject_ffi::GObject, + _data: [u8; 0], } #[repr(C)] pub struct AudreyPlaybinSongClass { - parent_class: glib::gobject_ffi::GObjectClass, + _data: [u8; 0], } extern "C" { diff --git a/src/rust.h b/src/rust.h index 8d18585..e7cc8db 100644 --- a/src/rust.h +++ b/src/rust.h @@ -1 +1,17 @@ -typedef struct _AudreyUiPlaybar AudreyUiPlaybar; +#include +#include + +// ui::playbar +typedef void AudreyUiPlaybar; + +// ui::play_queue::Song +typedef void AudreyUiPlayQueueSong; +#define AUDREY_UI_TYPE_PLAY_QUEUE_SONG (audrey_ui_play_queue_song_get_type ()) +GType audrey_ui_play_queue_song_get_type(void); +AudreyUiPlayQueueSong *audrey_ui_play_queue_song_new(void *playbin); +void audrey_ui_play_queue_song_set_draggable(AudreyUiPlayQueueSong *self, gboolean draggable); +void audrey_ui_play_queue_song_set_show_position(AudreyUiPlayQueueSong *self, gboolean show_position); +void audrey_ui_play_queue_song_set_show_artist(AudreyUiPlayQueueSong *self, gboolean show_artist); +void audrey_ui_play_queue_song_set_show_cover(AudreyUiPlayQueueSong *self, gboolean show_cover); +void audrey_ui_play_queue_song_bind(AudreyUiPlayQueueSong *self, guint position, void *song); +void audrey_ui_play_queue_song_unbind(AudreyUiPlayQueueSong *self); diff --git a/src/rust.vapi b/src/rust.vapi index 11abf34..13969c5 100644 --- a/src/rust.vapi +++ b/src/rust.vapi @@ -9,4 +9,18 @@ namespace Audrey { public int volume { get; set; } } + public class Ui.PlayQueueSong : Gtk.Box { + public bool draggable { get; set; } + public bool show_position { get; set; } + public bool show_artist { get; set; } + public bool show_cover { get; set; } + public bool current { get; set; } + public uint displayed_position { get; set; } + public PlaybinSong song { get; set; } + + public PlayQueueSong (Playbin playbin); + public void bind (uint position, PlaybinSong song); + public void unbind (); + } + } diff --git a/src/ui.rs b/src/ui.rs index 7241dcf..de3ec65 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -6,3 +6,5 @@ pub use playbar::Playbar; mod setup; pub use setup::Setup; + +pub mod play_queue; diff --git a/src/ui/play_queue.vala b/src/ui/play_queue.vala index 132382a..15638e1 100644 --- a/src/ui/play_queue.vala +++ b/src/ui/play_queue.vala @@ -1,116 +1,3 @@ -// song widget+drag behavior taken from gnome music - -[GtkTemplate (ui = "/eu/callcc/audrey/play_queue_song.ui")] -class Audrey.Ui.PlayQueueSong : Gtk.Box { - public bool draggable { get; set; default = false; } - public bool show_position { get; set; default = false; } - public bool show_artist { get; set; default = false; } - public bool show_cover { get; set; default = false; } - - private bool _current = false; - public bool current { - get { return _current; } - set { - this._current = value; - if (value) { - this.add_css_class ("playing"); - } else { - this.remove_css_class ("playing"); - } - } - } - public uint displayed_position { get; set; } - public PlaybinSong song { get; set; } - - private weak Playbin playbin; - public PlayQueueSong (Playbin playbin) { - this.playbin = playbin; - - var action_group = new SimpleActionGroup (); - - var remove = new SimpleAction ("remove", null); - remove.activate.connect (() => { - this.playbin.remove_track (this.displayed_position-1); - }); - action_group.add_action (remove); - - this.insert_action_group ("song", action_group); - } - - private ulong connection; - public void bind (uint position, PlaybinSong song) { - this.displayed_position = position+1; - this.song = song; - this.current = this.playbin.play_queue_position == position; - this.connection = this.playbin.notify["play-queue-position"].connect (() => { - this.current = this.playbin.play_queue_position == position; - }); - - song.need_cover_art (); - } - - public void unbind () { - this.playbin.disconnect (this.connection); - } - - [GtkCallback] private string format_duration (int duration) { - return "%02d:%02d".printf(duration/60, duration%60); - } - - [GtkCallback] private string star_button_icon_name (DateTime? starred) { - return starred == null ? "non-starred" : "starred"; - } - - private double drag_x; - private double drag_y; - - [GtkCallback] private Gdk.ContentProvider? on_drag_prepare (double x, double y) { - if (this.draggable) { - this.drag_x = x; - this.drag_y = y; - return new Gdk.ContentProvider.for_value (this); - } - else return null; - } - - private Gtk.ListBox? drag_widget; - - [GtkCallback] private void on_drag_begin (Gtk.DragSource source, Gdk.Drag drag) { - this.drag_widget = new Gtk.ListBox (); - - var drag_row = new PlayQueueSong (this.playbin); - drag_row.draggable = false; - drag_row.show_position = this.show_position; - drag_row.show_artist = this.show_artist; - drag_row.show_cover = this.show_cover; - drag_row.current = false; - drag_row.displayed_position = this.displayed_position; - drag_row.song = this.song; - drag_row.set_size_request (this.get_width (), this.get_height ()); - - var drag_row_real = new Gtk.ListBoxRow (); - drag_row_real.child = drag_row; - - this.drag_widget.append (drag_row_real); - this.drag_widget.drag_highlight_row (drag_row_real); - - var drag_icon = Gtk.DragIcon.get_for_drag (drag); - drag_icon.set("child", this.drag_widget); - drag.set_hotspot ((int) this.drag_x, (int) this.drag_y); - } - - [GtkCallback] private bool on_drop (Value value, double x, double y) { - this.drag_widget = null; - this.drag_x = 0.0; - this.drag_y = 0.0; - - var source = value as PlayQueueSong; - debug ("dropped %u on %u", source.displayed_position, this.displayed_position); - this.playbin.move_track (source.displayed_position-1, this.displayed_position-1); - return false; - } -} - [GtkTemplate (ui = "/eu/callcc/audrey/play_queue.ui")] public class Audrey.Ui.PlayQueue : Adw.Bin { private weak Playbin _playbin; diff --git a/src/ui/play_queue/song.rs b/src/ui/play_queue/song.rs index e1bd87b..c7146bf 100644 --- a/src/ui/play_queue/song.rs +++ b/src/ui/play_queue/song.rs @@ -1,32 +1,35 @@ mod imp { - use std::cell::RefCell; - //use crate::playbin; - use gtk::{glib, prelude::*, subclass::prelude::*}; + use glib::{subclass::InitializingObject, WeakRef}; + use gtk::{gdk, gio, glib, prelude::*, subclass::prelude::*}; + use std::cell::{Cell, RefCell}; #[derive(gtk::CompositeTemplate, glib::Properties, Default)] #[template(resource = "/eu/callcc/audrey/play_queue_song.ui")] #[properties(wrapper_type = super::Song)] pub struct Song { - #[property(get, set, default = false)] - draggable: RefCell, - #[property(get, set, default = false)] - show_position: RefCell, - #[property(get, set, default = false)] - show_artist: RefCell, - #[property(get, set, default = false)] - show_cover: RefCell, - #[property(get, set = Self::set_current, default = false)] - current: RefCell, - #[property(get, set)] - displayed_position: RefCell, - //#[property(get, set)] - //song: playbin::Song, + pub(super) playbin: RefCell>>, - //playbin: playbin::Playbin, - connection: RefCell, - drag_x: RefCell, - drag_y: RefCell, - drag_widget: RefCell>, + #[property(set, get)] + draggable: Cell, + #[property(set, get)] + show_position: Cell, + #[property(set, get)] + show_artist: Cell, + #[property(set, get)] + show_cover: Cell, + + #[property(set = Self::set_current, get)] + current: Cell, + + #[property(set, get)] + displayed_position: Cell, + #[property(get, set)] + song: RefCell>, + + pub(super) connection: Cell>, + + drag_pos: Cell<(i32, i32)>, + drag_widget: Cell>, } #[glib::object_subclass] @@ -34,10 +37,40 @@ mod imp { const NAME: &'static str = "AudreyUiPlayQueueSong"; type Type = super::Song; type ParentType = gtk::Box; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + klass.bind_template_callbacks(); + } + + fn instance_init(obj: &InitializingObject) { + obj.init_template(); + } } #[glib::derived_properties] - impl ObjectImpl for Song {} + impl ObjectImpl for Song { + fn constructed(&self) { + self.parent_constructed(); + + let action_remove = gio::ActionEntry::builder("remove") + .activate(glib::clone!( + #[weak(rename_to = self_)] + self, + move |_, _, _| { + self_ + .obj() + .playbin() + .remove_track(self_.obj().displayed_position() - 1) + } + )) + .build(); + + let actions = gio::SimpleActionGroup::new(); + actions.add_action_entries([action_remove]); + self.obj().insert_action_group("song", Some(&actions)); + } + } impl WidgetImpl for Song {} @@ -46,16 +79,93 @@ mod imp { #[gtk::template_callbacks] impl Song { fn set_current(&self, value: bool) { - *self.current.borrow_mut() = value; + self.current.replace(value); if value { self.obj().add_css_class("playing"); } else { self.obj().remove_css_class("playing"); } } + + #[template_callback] + fn format_duration(&self, duration: i64) -> String { + format!("{:02}:{:02}", duration / 60, duration % 60) + } + + #[template_callback] + fn star_button_icon_name( + &self, /* TODO , starred: Option<&glib::DateTime> */ + ) -> &'static str { + /* TODO + match starred { + None => "non-starred", + Some(_) => "starred", + } */ + "non-starred" + } + + // song widget+drag behavior taken from gnome music + + #[template_callback] + fn on_drag_prepare(&self, x: f64, y: f64) -> Option { + if self.draggable.get() { + self.drag_pos.replace((x as i32, y as i32)); + let value = self.obj().to_value(); + Some(gdk::ContentProvider::for_value(&value)) + } else { + None + } + } + + #[template_callback] + fn on_drag_begin(&self, drag: &gdk::Drag) { + let drag_widget = gtk::ListBox::new(); + + let drag_row = super::Song::new(self.obj().playbin()); + drag_row.set_draggable(false); + drag_row.set_show_position(self.obj().show_position()); + drag_row.set_show_artist(self.obj().show_artist()); + drag_row.set_show_cover(self.obj().show_cover()); + drag_row.set_current(self.obj().current()); + drag_row.set_displayed_position(self.obj().displayed_position()); + drag_row.set_song(self.obj().song().unwrap()); + drag_row.set_size_request(self.obj().width(), self.obj().height()); + + let drag_row_real = gtk::ListBoxRow::new(); + drag_row_real.set_child(Some(&drag_row)); + + drag_widget.append(&drag_row_real); + drag_widget.drag_highlight_row(&drag_row_real); + + let drag_icon = gtk::DragIcon::for_drag(drag); + drag_icon.set_child(Some(&drag_widget)); + let (drag_x, drag_y) = self.drag_pos.get(); + drag.set_hotspot(drag_x, drag_y); + + self.drag_widget.replace(Some(drag_widget)); + } + + #[template_callback] + fn on_drop(&self, _value: glib::Value, _x: f64, _y: f64) -> bool { + /* FIXME: WrongValueType(ValueTypeMismatchError { actual: GValue, requested: AudreyUiPlayQueueSong }) + let source: super::Song = value.get().unwrap(); + + source.imp().drag_widget.set(None); + + self.obj().playbin().move_track( + source.displayed_position() - 1, + self.obj().displayed_position() - 1, + ); + + true */ + false + } } } +use adw::prelude::*; +use adw::subclass::prelude::*; +use glib::Object; use gtk::glib; glib::wrapper! { @@ -63,3 +173,116 @@ glib::wrapper! { @extends gtk::Box, gtk::Widget, @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable; } + +impl Song { + pub fn new(playbin: crate::Playbin) -> Self { + let song: Self = Object::new(); + song.imp().playbin.replace(Some(playbin.downgrade())); + song + } + + fn playbin(&self) -> crate::Playbin { + let playbin = self.imp().playbin.borrow(); + let playbin = playbin.as_ref().unwrap(); // weak + playbin.upgrade().unwrap().clone() // strong + } + + pub fn bind(&self, position: u32, song: &crate::playbin::Song) { + self.set_displayed_position(position + 1); + self.set_song(song); + self.set_current(self.playbin().play_queue_position() == position); + self.imp() + .connection + .replace(Some(self.playbin().connect_notify_local( + Some("play-queue-position"), + glib::clone!( + #[weak(rename_to = self_)] + self, + move |playbin: &crate::Playbin, _| { + self_.set_current(playbin.play_queue_position() == position) + } + ), + ))); + } + + pub fn unbind(&self) { + self.playbin() + .disconnect(self.imp().connection.take().unwrap()); + } +} + +pub mod ffi { + use adw::prelude::*; + use glib::ffi::{gboolean, GType}; + use glib::subclass::basic::InstanceStruct; + use glib::translate::{from_glib, from_glib_none, IntoGlib, IntoGlibPtr}; + use gtk::glib; + use std::ffi::c_uint; + + type AudreyUiPlayQueueSong = InstanceStruct; + + #[no_mangle] + extern "C" fn audrey_ui_play_queue_song_get_type() -> GType { + super::Song::static_type().into_glib() + } + + #[no_mangle] + extern "C" fn audrey_ui_play_queue_song_new( + playbin: *mut crate::playbin::ffi::AudreyPlaybin, + ) -> *mut AudreyUiPlayQueueSong { + unsafe { super::Song::new(from_glib_none(playbin)).into_glib_ptr() } + } + + #[no_mangle] + extern "C" fn audrey_ui_play_queue_song_set_draggable( + self_: *mut AudreyUiPlayQueueSong, + draggable: gboolean, + ) { + let self_: super::Song = unsafe { from_glib_none(self_) }; + self_.set_draggable(unsafe { from_glib::<_, bool>(draggable) }); + } + + #[no_mangle] + extern "C" fn audrey_ui_play_queue_song_set_show_position( + self_: *mut AudreyUiPlayQueueSong, + show_position: gboolean, + ) { + let self_: super::Song = unsafe { from_glib_none(self_) }; + self_.set_show_position(unsafe { from_glib::<_, bool>(show_position) }); + } + + #[no_mangle] + extern "C" fn audrey_ui_play_queue_song_set_show_artist( + self_: *mut AudreyUiPlayQueueSong, + show_artist: gboolean, + ) { + let self_: super::Song = unsafe { from_glib_none(self_) }; + self_.set_show_artist(unsafe { from_glib::<_, bool>(show_artist) }); + } + + #[no_mangle] + extern "C" fn audrey_ui_play_queue_song_set_show_cover( + self_: *mut AudreyUiPlayQueueSong, + show_cover: gboolean, + ) { + let self_: super::Song = unsafe { from_glib_none(self_) }; + self_.set_show_cover(unsafe { from_glib::<_, bool>(show_cover) }); + } + + #[no_mangle] + extern "C" fn audrey_ui_play_queue_song_bind( + self_: *mut AudreyUiPlayQueueSong, + position: c_uint, + song: *mut crate::playbin::song::ffi::AudreyPlaybinSong, + ) { + let self_: super::Song = unsafe { from_glib_none(self_) }; + let song: crate::playbin::Song = unsafe { from_glib_none(song) }; + self_.bind(position, &song); + } + + #[no_mangle] + extern "C" fn audrey_ui_play_queue_song_unbind(self_: *mut AudreyUiPlayQueueSong) { + let self_: super::Song = unsafe { from_glib_none(self_) }; + self_.unbind(); + } +}