diff --git a/src/main.rs b/src/main.rs index bc8a2d2..8e2ab9d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,6 +28,8 @@ extern "C" {} fn main() -> glib::ExitCode { gio::resources_register_include!("audrey.gresource").expect("could not register resources"); + ui::Playbar::ensure_type(); + gtk::disable_setlocale(); bindtextdomain("audrey", meson_config::LOCALEDIR).expect("failed to bind text domain"); bind_textdomain_codeset("audrey", "UTF-8").expect("failed to bind textdomaincodeset"); diff --git a/src/meson.build b/src/meson.build index ad7aa18..e34728e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -5,7 +5,7 @@ audrey_sources = [ 'playbin.vala', 'subsonic.vala', 'ui/play_queue.vala', - 'ui/playbar.vala', + 'ui/playbar.vapi', 'ui/setup.vala', 'ui/window.vala', ] diff --git a/src/playbin.rs b/src/playbin.rs index 8130af9..fd41c69 100644 --- a/src/playbin.rs +++ b/src/playbin.rs @@ -1,6 +1,9 @@ mod song; pub use song::Song; +mod state; +pub use state::State; + mod ffi { use gtk::glib; diff --git a/src/playbin/song.rs b/src/playbin/song.rs index adfbba2..3547de2 100644 --- a/src/playbin/song.rs +++ b/src/playbin/song.rs @@ -13,6 +13,15 @@ mod ffi { extern "C" { pub fn audrey_playbin_song_get_type() -> glib::ffi::GType; + pub fn audrey_playbin_song_get_title( + self_: *mut AudreyPlaybinSong, + ) -> *const std::ffi::c_char; + pub fn audrey_playbin_song_get_artist( + self_: *mut AudreyPlaybinSong, + ) -> *const std::ffi::c_char; + pub fn audrey_playbin_song_get_album( + self_: *mut AudreyPlaybinSong, + ) -> *const std::ffi::c_char; } } @@ -25,3 +34,35 @@ glib::wrapper! { type_ => || ffi::audrey_playbin_song_get_type(), } } + +impl Song { + pub fn get_title(&self) -> std::borrow::Cow<'_, str> { + use glib::translate::ToGlibPtr; + + // TODO: memory management.... + String::from_utf8_lossy(unsafe { + std::ffi::CStr::from_ptr(ffi::audrey_playbin_song_get_title(self.to_glib_none().0)) + .to_bytes() + }) + } + + pub fn get_artist(&self) -> std::borrow::Cow<'_, str> { + use glib::translate::ToGlibPtr; + + // TODO: memory management.... + String::from_utf8_lossy(unsafe { + std::ffi::CStr::from_ptr(ffi::audrey_playbin_song_get_artist(self.to_glib_none().0)) + .to_bytes() + }) + } + + pub fn get_album(&self) -> std::borrow::Cow<'_, str> { + use glib::translate::ToGlibPtr; + + // TODO: memory management.... + String::from_utf8_lossy(unsafe { + std::ffi::CStr::from_ptr(ffi::audrey_playbin_song_get_album(self.to_glib_none().0)) + .to_bytes() + }) + } +} diff --git a/src/playbin/state.rs b/src/playbin/state.rs new file mode 100644 index 0000000..ef09b6e --- /dev/null +++ b/src/playbin/state.rs @@ -0,0 +1,38 @@ +mod ffi { + use gtk::glib; + + extern "C" { + pub fn audrey_playbin_state_get_type() -> glib::ffi::GType; + } +} + +use gtk::glib; +use glib::prelude::*; + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum State { + Stopped, + Paused, + Playing, +} + +unsafe impl<'a> glib::value::FromValue<'a> for State { + type Checker = glib::value::GenericValueTypeChecker; + + unsafe fn from_value(value: &'a glib::Value) -> Self { + use glib::translate::ToGlibPtr; + + match glib::gobject_ffi::g_value_get_enum(value.to_glib_none().0) { + 0 => Self::Stopped, + 1 => Self::Paused, + 2 => Self::Playing, + _ => unreachable!(), + } + } +} + +impl StaticType for State { + fn static_type() -> glib::Type { + unsafe { glib::translate::from_glib(ffi::audrey_playbin_state_get_type()) } + } +} diff --git a/src/subsonic/song.rs b/src/subsonic/song.rs index 0294681..6b315bf 100644 --- a/src/subsonic/song.rs +++ b/src/subsonic/song.rs @@ -1,6 +1,6 @@ mod ffi { - use std::ffi::*; use gtk::glib; + use std::ffi::*; #[repr(C)] #[derive(Copy, Clone)] @@ -19,7 +19,8 @@ mod ffi { } extern "C" { - pub fn audrey_subsonic_song_copy(ptr: *const AudreySubsonicSong) -> *mut AudreySubsonicSong; + pub fn audrey_subsonic_song_copy(ptr: *const AudreySubsonicSong) + -> *mut AudreySubsonicSong; pub fn audrey_subsonic_song_free(ptr: *mut AudreySubsonicSong); pub fn audrey_subsonic_song_get_type() -> glib::ffi::GType; } diff --git a/src/ui/playbar.h b/src/ui/playbar.h new file mode 100644 index 0000000..8d18585 --- /dev/null +++ b/src/ui/playbar.h @@ -0,0 +1 @@ +typedef struct _AudreyUiPlaybar AudreyUiPlaybar; diff --git a/src/ui/playbar.rs b/src/ui/playbar.rs index 8de4eb7..fb8bee1 100644 --- a/src/ui/playbar.rs +++ b/src/ui/playbar.rs @@ -1,29 +1,178 @@ -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 AudreyUiPlaybar { - parent_instance: adw::ffi::AdwBin, + #[derive(glib::Properties, gtk::CompositeTemplate, Default)] + #[properties(wrapper_type = super::Playbar)] + #[template(resource = "/eu/callcc/audrey/playbar.ui")] + pub struct Playbar { + #[property(get, set)] + song: RefCell>, + #[property(get, set)] + playing_cover_art: RefCell>, + #[property(get, set)] + playbin: RefCell>, // TODO: weak + #[property(get, set, default = true)] + show_cover_art: Cell, + + #[property(get, set)] + volume: Cell, } - #[repr(C)] - pub struct AudreyUiPlaybarClass { - parent_class: adw::ffi::AdwBinClass, + #[glib::object_subclass] + impl ObjectSubclass for Playbar { + const NAME: &'static str = "AudreyUiPlaybar"; + type Type = super::Playbar; + type ParentType = adw::Bin; + + 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_playbar_get_type() -> glib::ffi::GType; + #[glib::derived_properties] + impl ObjectImpl for Playbar {} + + impl WidgetImpl for Playbar {} + + impl BinImpl for Playbar {} + + #[gtk::template_callbacks] + impl Playbar { + #[template_callback] + fn song_title(&self, song: Option<&crate::playbin::Song>) -> String { + match song { + None => "".to_owned(), + Some(song) => song.get_title().to_string(), + } + } + + #[template_callback] + fn song_artist(&self, song: Option<&crate::playbin::Song>) -> String { + match song { + None => "".to_owned(), + Some(song) => song.get_artist().to_string(), + } + } + + #[template_callback] + fn song_album(&self, song: Option<&crate::playbin::Song>) -> String { + match song { + None => "".to_owned(), + Some(song) => song.get_album().to_string(), + } + } + + #[template_callback] + fn format_timestamp(&self, s: f64) -> String { + format!("{:02}:{:02}", (s as i64) / 64, (s as i64) % 60) + } + + #[template_callback] + fn playbin_active(&self, state: crate::playbin::State) -> bool { + state != crate::playbin::State::Stopped + } + + #[template_callback] + fn can_press_play(&self, state: crate::playbin::State, n_items: u32) -> bool { + !(state == crate::playbin::State::Stopped && n_items == 0) + } + + #[template_callback] + fn play_pause_icon_name(&self, state: crate::playbin::State) -> &'static str { + match state { + crate::playbin::State::Playing => "media-playback-pause", + _ => "media-playback-start", + } + } + + #[template_callback] + fn mute_button_icon_name(&self, mute: bool) -> &'static str { + if mute { "audio-volume-muted" } else { "audio-volume-high" } + } + + #[template_callback] + fn on_play_position_seek(&self, _range: >k::Range, _scroll_type: gtk::ScrollType, _value: f64) -> bool { + /* + if (range.adjustment.lower < range.adjustment.upper) { + this.playbin.seek ((int64) value); + } + return false; + */ + todo!() + } + + #[template_callback] + fn on_skip_forward_clicked(&self) { + // this.playbin.go_to_next_track (); + todo!() + } + + #[template_callback] + fn on_skip_backward_clicked(&self) { + // this.playbin.go_to_prev_track (); + todo!() + } + + #[template_callback] + fn seek_backward(&self) { + // 10 seconds + // double new_position = playbin.position - 10.0; + // if (new_position < 0.0) new_position = 0.0; + // this.playbin.seek (new_position); + todo!() + } + + #[template_callback] + fn seek_forward(&self) { + // 10 seconds + // double new_position = playbin.position + 10.0; + // if (new_position > this.playbin.duration) new_position = this.playbin.duration; + // this.playbin.seek (new_position); + todo!() + } + + #[template_callback] + fn on_play_pause_clicked(&self) { + // if (this.playbin.state == PlaybinState.PLAYING) { + // this.playbin.pause(); + // } else { + // this.playbin.play(); + // } + todo!() + } + + #[template_callback] + fn on_mute_toggle(&self) { + //this.playbin.mute = !this.playbin.mute; + todo!() + } } } use gtk::glib; glib::wrapper! { - pub struct Playbar(Object) + pub struct Playbar(ObjectSubclass) @extends adw::Bin, gtk::Widget, @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; +} - match fn { - type_ => || ffi::audrey_ui_playbar_get_type(), +mod ffi { + use glib::translate::IntoGlib; + use gtk::glib; + use gtk::prelude::*; + + #[no_mangle] + pub extern "C" fn audrey_ui_playbar_get_type() -> glib::ffi::GType { + super::Playbar::static_type().into_glib() } } diff --git a/src/ui/playbar.vala b/src/ui/playbar.vapi similarity index 89% rename from src/ui/playbar.vala rename to src/ui/playbar.vapi index eedfbaa..2bdea75 100644 --- a/src/ui/playbar.vala +++ b/src/ui/playbar.vapi @@ -1,15 +1,21 @@ -[GtkTemplate (ui = "/eu/callcc/audrey/playbar.ui")] -class Audrey.Ui.Playbar : Adw.Bin { - public PlaybinSong? song { get; set; } +// [GtkTemplate (ui = "/eu/callcc/audrey/playbar.ui")] +[CCode (cheader_filename = "ui/playbar.h")] +public class Audrey.Ui.Playbar : Adw.Bin { + public PlaybinSong? song { + get; + set; + } public Gdk.Paintable? playing_cover_art { get; set; } public weak Playbin playbin { get; set; } - public bool show_cover_art { get; set; default = true; } + public bool show_cover_art { get; set; /*default = true;*/ } - public int volume { + public int volume { get; set; } + /*{ get { return playbin == null ? 100 : playbin.volume; } set { playbin.volume = value; } - } + }*/ + /* [GtkCallback] private string format_timestamp (double s) { return "%02d:%02d".printf (((int) s)/60, ((int) s)%60); } @@ -90,4 +96,5 @@ class Audrey.Ui.Playbar : Adw.Bin { ~Playbar () { debug ("destroying playbar widget"); } + */ }