mod imp { use crate::model::Song; use adw::prelude::*; use adw::subclass::prelude::*; use glib::subclass::InitializingObject; use gtk::{gdk, glib}; use std::cell::{Cell, RefCell}; use tracing::{event, Level}; #[derive(glib::Properties, gtk::CompositeTemplate, Default)] #[properties(wrapper_type = super::Playbar)] #[template(resource = "/eu/callcc/audrey/playbar.ui")] pub struct Playbar { #[template_child] #[property(get)] pulse_bar: TemplateChild, #[property(get, set, nullable)] song: RefCell>, #[property(get, set)] playing_cover_art: RefCell>, #[property(get, set, default = true)] show_cover_art: Cell, // synced with root window #[property(get, set)] _volume: Cell, #[property(get, set)] _mute: Cell, #[property(get, set)] _pause: Cell, #[property(get, set)] _idle_active: Cell, #[property(get, set)] _playlist_count: Cell, #[property(get, set)] position: Cell, #[property(get, set)] duration: Cell, #[property(get, set)] _show_pulse_bar: Cell, } #[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(); } } #[glib::derived_properties] impl ObjectImpl for Playbar {} impl WidgetImpl for Playbar {} impl BinImpl for Playbar {} #[gtk::template_callbacks] impl Playbar { #[template_callback] fn format_timestamp(&self, s: f64) -> String { format!("{:02}:{:02}", (s as i64) / 60, (s as i64) % 60) } #[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, _scroll_type: gtk::ScrollType, value: f64, _range: >k::Range, ) -> bool { self.window().seek(value); false } #[template_callback] fn on_skip_forward_clicked(&self) { if self.window().playlist_pos() + 1 < self.window().playlist_count() { self.window().playlist_next(); } else { self.window().playlist_play_index(None); } } #[template_callback] fn on_skip_backward_clicked(&self) { if self.window().playlist_pos() > 0 { self.window().playlist_prev(); } else { self.window().playlist_play_index(None); } } #[template_callback] fn on_play_pause_clicked(&self, _button: >k::Button) { if self.window().idle_active() { self.window().playlist_play_index(Some(0)); self.window().set_pause(false); } else { self.window().set_pause(!self.window().pause()); } } #[template_callback] fn on_mute_toggle(&self) { self.obj().set_mute(!self.obj().mute()); } #[template_callback] fn play_pause_icon_name(&self, idle_active: bool, pause: bool) -> &'static str { match (idle_active, pause) { (true, _) => "media-playback-start", (false, true) => "media-playback-start", (false, false) => "media-playback-pause", } } #[template_callback] fn play_pause_sensitive(&self, playlist_count: u32) -> bool { playlist_count > 0 } fn window(&self) -> crate::ui::Window { self.obj().root().unwrap().dynamic_cast().unwrap() } // these are nedeed because with regular bindings, if song becomes None, the labes are not // updated #[template_callback] fn song_title(&self, song: Option<&Song>) -> String { song.map(Song::title).unwrap_or("".to_string()) } #[template_callback] fn song_artist(&self, song: Option<&Song>) -> String { song.map(Song::artist).unwrap_or("".to_string()) } #[template_callback] fn song_album(&self, song: Option<&Song>) -> String { song.map(Song::album).unwrap_or("".to_string()) } } impl Drop for Playbar { fn drop(&mut self) { event!(Level::DEBUG, "dropping AudreyUiPlaybar"); } } } use gtk::glib; glib::wrapper! { pub struct Playbar(ObjectSubclass) @extends adw::Bin, gtk::Widget, @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; }