diff --git a/data/ui/player_bar.blp b/data/ui/player_bar.blp new file mode 100644 index 0000000..0a3b77e --- /dev/null +++ b/data/ui/player_bar.blp @@ -0,0 +1,90 @@ +using Gtk 4.0; +using Adw 1; + +template $MusicusPlayerBar : Gtk.Box { + styles ["playerbar"] + orientation: vertical; + margin-start: 6; + margin-end: 6; + margin-top: 12; + margin-bottom: 12; + spacing: 6; + + Gtk.Box { + spacing: 6; + + Gtk.Box { + valign: center; + hexpand: true; + margin-start: 10; + orientation: vertical; + + Gtk.Label title_label { + styles ["title"] + halign: start; + label: _("Title"); + ellipsize: end; + } + + Gtk.Label subtitle_label { + styles ["subtitle", "dim-label"] + halign: start; + label: _("Subtitle"); + ellipsize: end; + } + } + + Gtk.Button back_button { + styles ["circular", "flat"] + valign: center; + icon-name: "media-skip-backward-symbolic"; + } + + Gtk.ToggleButton playlist_button { + styles ["flat", "circular"] + valign: center; + icon-name: "playlist-symbolic"; + clicked => $show_playlist() swapped; + } + + Gtk.Button forward_button { + styles ["circular", "flat"] + valign: center; + icon-name: "media-skip-forward-symbolic"; + } + } + + Gtk.Box { + spacing: 6; + + Gtk.Button play_button { + styles ["circular", "flat"] + valign: center; + icon-name: "media-playback-start-symbolic"; + clicked => $play_pause() swapped; + } + + Gtk.Label current_time_label { + styles ["caption", "numeric"] + valign: center; + label: "00:00"; + } + + Gtk.Scale slider { + valign: center; + hexpand: true; + adjustment: Gtk.Adjustment { + lower: 0; + upper: 1; + value: 0.2; + step-increment: 0.01; + }; + } + + Gtk.Label remaining_time_label { + styles ["caption", "numeric"] + valign: center; + label: "01:00"; + } + } +} \ No newline at end of file diff --git a/data/ui/window.blp b/data/ui/window.blp index b6079f9..3009ca0 100644 --- a/data/ui/window.blp +++ b/data/ui/window.blp @@ -22,93 +22,6 @@ template $MusicusWindow : Adw.ApplicationWindow { Gtk.Revealer player_bar_revealer { reveal-child: true; transition-type: slide_up; - - Gtk.Box { - styles ["playerbar"] - orientation: vertical; - margin-start: 6; - margin-end: 6; - margin-top: 12; - margin-bottom: 12; - spacing: 6; - - Gtk.Box { - spacing: 6; - - Gtk.Box { - valign: center; - hexpand: true; - margin-start: 10; - orientation: vertical; - - Gtk.Label title_label { - styles ["title"] - halign: start; - label: _("Title"); - ellipsize: end; - } - - Gtk.Label subtitle_label { - styles ["subtitle", "dim-label"] - halign: start; - label: _("Subtitle"); - ellipsize: end; - } - } - - Gtk.Button back_button { - styles ["circular", "flat"] - valign: center; - icon-name: "media-skip-backward-symbolic"; - } - - Gtk.ToggleButton playlist_button { - styles ["flat", "circular"] - valign: center; - icon-name: "playlist-symbolic"; - toggled => $show_playlist() swapped; - } - - Gtk.Button forward_button { - styles ["circular", "flat"] - valign: center; - icon-name: "media-skip-forward-symbolic"; - } - } - - Gtk.Box { - spacing: 6; - - Gtk.Button play_button { - styles ["circular", "flat"] - valign: center; - icon-name: "media-playback-start-symbolic"; - } - - Gtk.Label current_time_label { - styles ["caption", "numeric"] - valign: center; - label: "00:00"; - } - - Gtk.Scale slider { - valign: center; - hexpand: true; - adjustment: Gtk.Adjustment { - lower: 0; - upper: 1; - value: 0.2; - step-increment: 0.01; - }; - } - - Gtk.Label remaining_time_label { - styles ["caption", "numeric"] - valign: center; - label: "01:00"; - } - } - } } } } \ No newline at end of file diff --git a/po/POTFILES b/po/POTFILES index ef5adb1..c58a81c 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -1,4 +1,5 @@ data/ui/home_page.blp +data/ui/player_bar.blp data/ui/playlist_page.blp data/ui/playlist_tile.blp data/ui/recording_tile.blp diff --git a/src/main.rs b/src/main.rs index a63cbf7..44c1411 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod config; mod home_page; mod library; mod player; +mod player_bar; mod playlist_item; mod playlist_page; mod playlist_tile; diff --git a/src/player.rs b/src/player.rs index 7b0d200..ce52ee7 100644 --- a/src/player.rs +++ b/src/player.rs @@ -21,15 +21,19 @@ mod imp { impl MusicusPlayer { pub fn set_current_index(&self, index: u32) { let playlist = self.playlist.get().unwrap(); - + if let Some(item) = playlist.item(self.current_index.get()) { - item.downcast::().unwrap().set_is_playing(false); + item.downcast::() + .unwrap() + .set_is_playing(false); } self.current_index.set(index); - + if let Some(item) = playlist.item(index) { - item.downcast::().unwrap().set_is_playing(true); + item.downcast::() + .unwrap() + .set_is_playing(true); } } } @@ -60,7 +64,7 @@ impl MusicusPlayer { pub fn append(&self, tracks: Vec) { let playlist = self.playlist(); - + for track in tracks { playlist.append(&track); } @@ -75,6 +79,15 @@ impl MusicusPlayer { pub fn pause(&self) { self.set_playing(false) } + + pub fn current_item(&self) -> Option { + let imp = self.imp(); + imp.playlist + .get() + .unwrap() + .item(imp.current_index.get()) + .and_downcast::() + } } impl Default for MusicusPlayer { diff --git a/src/player_bar.rs b/src/player_bar.rs new file mode 100644 index 0000000..85cded8 --- /dev/null +++ b/src/player_bar.rs @@ -0,0 +1,128 @@ +use crate::player::MusicusPlayer; +use gtk::{ + glib::{self, subclass::Signal, Properties}, + prelude::*, + subclass::prelude::*, +}; +use once_cell::sync::Lazy; +use std::cell::RefCell; + +mod imp { + use super::*; + + #[derive(Properties, Debug, Default, gtk::CompositeTemplate)] + #[properties(wrapper_type = super::PlayerBar)] + #[template(file = "data/ui/player_bar.blp")] + pub struct PlayerBar { + #[property(get, construct_only)] + pub player: RefCell, + + #[template_child] + pub title_label: TemplateChild, + #[template_child] + pub subtitle_label: TemplateChild, + #[template_child] + pub back_button: TemplateChild, + #[template_child] + pub playlist_button: TemplateChild, + #[template_child] + pub forward_button: TemplateChild, + #[template_child] + pub play_button: TemplateChild, + #[template_child] + pub current_time_label: TemplateChild, + #[template_child] + pub slider: TemplateChild, + #[template_child] + pub remaining_time_label: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for PlayerBar { + const NAME: &'static str = "MusicusPlayerBar"; + type Type = super::PlayerBar; + type ParentType = gtk::Box; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + klass.bind_template_instance_callbacks(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + #[glib::derived_properties] + impl ObjectImpl for PlayerBar { + fn signals() -> &'static [Signal] { + static SIGNALS: Lazy> = Lazy::new(|| { + vec![Signal::builder("show-playlist") + .param_types([glib::Type::BOOL]) + .build()] + }); + + SIGNALS.as_ref() + } + + fn constructed(&self) { + self.parent_constructed(); + + self.player + .borrow() + .bind_property("playing", &self.play_button.get(), "icon-name") + .transform_to(|_, playing| { + Some(if playing { + "media-playback-pause-symbolic" + } else { + "media-playback-start-symbolic" + }) + }) + .sync_create() + .build(); + } + } + + impl WidgetImpl for PlayerBar {} + impl BoxImpl for PlayerBar {} +} + +glib::wrapper! { + pub struct PlayerBar(ObjectSubclass) + @extends gtk::Widget, adw::Bin; +} + +#[gtk::template_callbacks] +impl PlayerBar { + pub fn new(player: &MusicusPlayer) -> Self { + glib::Object::builder().property("player", player).build() + } + + pub fn connect_show_playlist(&self, f: F) -> glib::SignalHandlerId { + self.connect_local("show-playlist", true, move |values| { + let obj = values[0].get::().unwrap(); + let show = values[1].get::().unwrap(); + f(&obj, show); + None + }) + } + + pub fn playlist_hidden(&self) { + self.imp().playlist_button.set_active(false); + } + + #[template_callback] + fn show_playlist(&self, button: >k::ToggleButton) { + self.emit_by_name::<()>("show-playlist", &[&button.is_active()]); + } + + #[template_callback] + fn play_pause(&self, _: >k::Button) { + let player = self.player(); + if player.playing() { + player.pause(); + } else { + player.play(); + } + } +} diff --git a/src/playlist_tile.rs b/src/playlist_tile.rs index c26d9da..011eb13 100644 --- a/src/playlist_tile.rs +++ b/src/playlist_tile.rs @@ -42,7 +42,7 @@ mod imp { glib::wrapper! { pub struct PlaylistTile(ObjectSubclass) - @extends gtk::Widget, gtk::FlowBoxChild; + @extends gtk::Widget, gtk::Box; } impl PlaylistTile { diff --git a/src/window.rs b/src/window.rs index 8d72a7a..ee4055f 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,10 +1,9 @@ use crate::{ home_page::MusicusHomePage, library::MusicusLibrary, player::MusicusPlayer, - playlist_page::MusicusPlaylistPage, welcome_page::MusicusWelcomePage, + player_bar::PlayerBar, playlist_page::MusicusPlaylistPage, welcome_page::MusicusWelcomePage, }; use adw::subclass::prelude::*; use gtk::{gio, glib, glib::clone, prelude::*}; -use std::cell::OnceCell; mod imp { use super::*; @@ -13,7 +12,6 @@ mod imp { #[template(file = "data/ui/window.blp")] pub struct MusicusWindow { pub player: MusicusPlayer, - pub playlist_page: OnceCell, #[template_child] pub stack: TemplateChild, @@ -21,10 +19,6 @@ mod imp { pub navigation_view: TemplateChild, #[template_child] pub player_bar_revealer: TemplateChild, - #[template_child] - pub play_button: TemplateChild, - #[template_child] - pub playlist_button: TemplateChild, } #[glib::object_subclass] @@ -48,39 +42,31 @@ mod imp { fn constructed(&self) { self.parent_constructed(); self.obj().load_window_state(); + + let player_bar = PlayerBar::new(&self.player); + self.player_bar_revealer.set_child(Some(&player_bar)); + + let playlist_page = MusicusPlaylistPage::new(&self.player); + self.stack.add_named(&playlist_page, Some("playlist")); + + playlist_page.connect_close(clone!(@weak player_bar => move |_| { + player_bar.playlist_hidden(); + })); + + let stack = self.stack.get(); + player_bar.connect_show_playlist(clone!(@weak playlist_page => move |_, show| { + if show { + playlist_page.scroll_to_current(); + stack.set_visible_child_name("playlist"); + } else { + stack.set_visible_child_name("navigation"); + }; + })); + self.player .bind_property("active", &self.player_bar_revealer.get(), "reveal-child") .sync_create() .build(); - - let play_button = self.play_button.get(); - - self.player - .connect_playing_notify(clone!(@weak play_button => move |player| { - play_button.set_icon_name(if player.playing() { - "media-playback-pause-symbolic" - } else { - "media-playback-start-symbolic" - }); - })); - - self.play_button - .connect_clicked(clone!(@weak self.player as player => move |_| { - if player.playing() { - player.pause(); - } else { - player.play(); - } - })); - - let playlist_page = MusicusPlaylistPage::new(&self.player); - let playlist_button = self.playlist_button.get(); - playlist_page.connect_close(move |_| { - playlist_button.set_active(false); - }); - - self.stack.add_named(&playlist_page, Some("playlist")); - self.playlist_page.set(playlist_page).unwrap(); } } @@ -139,16 +125,4 @@ impl MusicusWindow { .navigation_view .replace(&[MusicusHomePage::new(&library, &self.imp().player).into()]); } - - #[template_callback] - fn show_playlist(&self, button: >k::ToggleButton) { - let imp = self.imp(); - - if button.is_active() { - imp.playlist_page.get().unwrap().scroll_to_current(); - imp.stack.set_visible_child_name("playlist"); - } else { - imp.stack.set_visible_child_name("navigation"); - }; - } }