use crate::{ album_tile::MusicusAlbumTile, db::models::*, editor::{person_editor::MusicusPersonEditor, work_editor::MusicusWorkEditor}, library::{LibraryQuery, MusicusLibrary}, player::MusicusPlayer, playlist_item::PlaylistItem, program::Program, program_tile::MusicusProgramTile, recording_tile::MusicusRecordingTile, search_entry::MusicusSearchEntry, search_tag::Tag, tag_tile::MusicusTagTile, }; use adw::subclass::{navigation_page::NavigationPageImpl, prelude::*}; use gtk::{ gio, glib::{self, clone, Properties}, prelude::*, }; use std::cell::{OnceCell, RefCell}; mod imp { use super::*; #[derive(Properties, Debug, Default, gtk::CompositeTemplate)] #[properties(wrapper_type = super::MusicusHomePage)] #[template(file = "data/ui/home_page.blp")] pub struct MusicusHomePage { #[property(get, construct_only)] pub navigation: OnceCell, #[property(get, construct_only)] pub library: OnceCell, #[property(get, construct_only)] pub player: OnceCell, pub composers: RefCell>, pub performers: RefCell>, pub ensembles: RefCell>, pub works: RefCell>, pub recordings: RefCell>, pub albums: RefCell>, #[template_child] pub search_entry: TemplateChild, #[template_child] pub stack: TemplateChild, #[template_child] pub header_box: TemplateChild, #[template_child] pub title_label: TemplateChild, #[template_child] pub subtitle_label: TemplateChild, #[template_child] pub programs_flow_box: TemplateChild, #[template_child] pub composers_flow_box: TemplateChild, #[template_child] pub performers_flow_box: TemplateChild, #[template_child] pub ensembles_flow_box: TemplateChild, #[template_child] pub works_flow_box: TemplateChild, #[template_child] pub recordings_flow_box: TemplateChild, #[template_child] pub albums_flow_box: TemplateChild, #[template_child] pub play_button: TemplateChild, } #[glib::object_subclass] impl ObjectSubclass for MusicusHomePage { const NAME: &'static str = "MusicusHomePage"; type Type = super::MusicusHomePage; type ParentType = adw::NavigationPage; 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 MusicusHomePage { fn constructed(&self) { self.parent_constructed(); self.search_entry.set_key_capture_widget(&*self.obj()); self.search_entry .connect_query_changed(clone!(@weak self as _self => move |entry| { _self.obj().query(&entry.query()); })); self.player .get() .unwrap() .bind_property("active", &self.play_button.get(), "visible") .invert_boolean() .sync_create() .build(); let settings = gio::Settings::new("de.johrpan.musicus"); let program1 = Program::deserialize(&settings.string("program1")).unwrap(); let program2 = Program::deserialize(&settings.string("program2")).unwrap(); let program3 = Program::deserialize(&settings.string("program3")).unwrap(); self.programs_flow_box .append(&MusicusProgramTile::new(program1)); self.programs_flow_box .append(&MusicusProgramTile::new(program2)); self.programs_flow_box .append(&MusicusProgramTile::new(program3)); self.obj().query(&LibraryQuery::default()); } } impl WidgetImpl for MusicusHomePage {} impl NavigationPageImpl for MusicusHomePage {} } glib::wrapper! { pub struct MusicusHomePage(ObjectSubclass) @extends gtk::Widget, adw::NavigationPage; } #[gtk::template_callbacks] impl MusicusHomePage { pub fn new( navigation: &adw::NavigationView, library: &MusicusLibrary, player: &MusicusPlayer, ) -> Self { glib::Object::builder() .property("navigation", navigation) .property("library", library) .property("player", player) .build() } #[template_callback] fn back_button_clicked(&self, _: >k::Button) { self.imp().search_entry.reset(); } #[template_callback] fn edit_button_clicked(&self, _: >k::Button) { if let Some(tag) = self.imp().search_entry.tags().first() { match tag { Tag::Composer(person) | Tag::Performer(person) => { self.navigation().push(&MusicusPersonEditor::new( &self.navigation(), &self.library(), Some(person), )); } Tag::Ensemble(_) => todo!(), Tag::Work(work) => self.navigation().push(&MusicusWorkEditor::new( &self.navigation(), &self.library(), Some(work), )), } } } #[template_callback] fn play(&self, _: >k::Button) { log::info!("Play button clicked"); self.player().play(); } #[template_callback] fn select(&self, search_entry: &MusicusSearchEntry) { let imp = self.imp(); if imp.programs_flow_box.is_visible() { log::info!("Program selected"); } else { let (composer, performer, ensemble, work, recording, album) = { ( imp.composers.borrow().first().cloned(), imp.performers.borrow().first().cloned(), imp.ensembles.borrow().first().cloned(), imp.works.borrow().first().cloned(), imp.recordings.borrow().first().cloned(), imp.albums.borrow().first().cloned(), ) }; if let Some(person) = composer { search_entry.add_tag(Tag::Composer(person)); } else if let Some(person) = performer { search_entry.add_tag(Tag::Performer(person)); } else if let Some(ensemble) = ensemble { search_entry.add_tag(Tag::Ensemble(ensemble)); } else if let Some(work) = work { search_entry.add_tag(Tag::Work(work)); } else if let Some(recording) = recording { self.play_recording(&recording); } else if let Some(album) = album { self.show_album(&album); } } } #[template_callback] fn program_selected(&self, tile: >k::FlowBoxChild, _: >k::FlowBox) { log::info!( "Program selected: {:?}", tile.downcast_ref::().unwrap().program() ); } #[template_callback] fn tile_selected(&self, tile: >k::FlowBoxChild, _: >k::FlowBox) { self.imp() .search_entry .add_tag(tile.downcast_ref::().unwrap().tag().clone()) } #[template_callback] fn recording_selected(&self, tile: >k::FlowBoxChild, _: >k::FlowBox) { self.play_recording( tile.downcast_ref::() .unwrap() .recording(), ); } #[template_callback] fn album_selected(&self, tile: >k::FlowBoxChild, _: >k::FlowBox) { self.show_album(tile.downcast_ref::().unwrap().album()); } fn play_recording(&self, recording: &Recording) { let tracks = &recording.tracks; if tracks.is_empty() { log::warn!("Ignoring recording without tracks being added to the playlist."); return; } let title = format!( "{}: {}", recording.work.composers_string(), recording.work.name.get(), ); let performances = recording.performers_string(); let mut items = Vec::new(); if tracks.len() == 1 { items.push(PlaylistItem::new( true, &title, Some(&performances), None, &tracks[0].path, )); } else { let mut tracks = tracks.into_iter(); let first_track = tracks.next().unwrap(); let track_title = |track: &Track, number: usize| -> String { let title = track .works .iter() .map(|w| w.name.get().to_string()) .collect::>() .join(", "); if title.is_empty() { format!("Track {number}") } else { title } }; items.push(PlaylistItem::new( true, &title, Some(&performances), Some(&track_title(&first_track, 1)), &first_track.path, )); for (index, track) in tracks.enumerate() { items.push(PlaylistItem::new( false, &title, Some(&performances), // track number = track index + 1 (first track) + 1 (zero based) Some(&track_title(&track, index + 2)), &track.path, )); } } self.player().append(items); } fn show_album(&self, _album: &Album) { todo!("Show album"); } fn query(&self, query: &LibraryQuery) { let imp = self.imp(); let results = self.library().query(query).unwrap(); for flowbox in [ &imp.composers_flow_box, &imp.performers_flow_box, &imp.ensembles_flow_box, &imp.works_flow_box, &imp.recordings_flow_box, &imp.albums_flow_box, ] { while let Some(widget) = flowbox.first_child() { flowbox.remove(&widget); } } imp.programs_flow_box.set_visible(query.is_empty()); if let Some(tag) = imp.search_entry.tags().first() { match tag { Tag::Composer(person) | Tag::Performer(person) => { imp.title_label.set_text(&person.name.get()); imp.subtitle_label.set_visible(false); } Tag::Ensemble(ensemble) => { imp.title_label.set_text(&ensemble.name.get()); imp.subtitle_label.set_visible(false); } Tag::Work(work) => { imp.title_label.set_text(&work.name.get()); imp.subtitle_label.set_text(&work.composers_string()); imp.subtitle_label.set_visible(true); } } imp.header_box.set_visible(true); } else { imp.header_box.set_visible(false); } if results.is_empty() { imp.stack.set_visible_child_name("empty"); } else { imp.stack.set_visible_child_name("results"); imp.composers_flow_box .set_visible(!results.composers.is_empty()); imp.performers_flow_box .set_visible(!results.performers.is_empty()); imp.ensembles_flow_box .set_visible(!results.ensembles.is_empty()); imp.works_flow_box.set_visible(!results.works.is_empty()); imp.recordings_flow_box .set_visible(!results.recordings.is_empty()); imp.albums_flow_box.set_visible(!results.albums.is_empty()); for composer in &results.composers { imp.composers_flow_box .append(&MusicusTagTile::new(Tag::Composer(composer.clone()))); } for performer in &results.performers { imp.performers_flow_box .append(&MusicusTagTile::new(Tag::Performer(performer.clone()))); } for ensemble in &results.ensembles { imp.ensembles_flow_box .append(&MusicusTagTile::new(Tag::Ensemble(ensemble.clone()))); } for work in &results.works { imp.works_flow_box .append(&MusicusTagTile::new(Tag::Work(work.clone()))); } for recording in &results.recordings { imp.recordings_flow_box .append(&MusicusRecordingTile::new(recording)); } for album in &results.albums { imp.albums_flow_box.append(&MusicusAlbumTile::new(album)); } imp.composers.replace(results.composers); imp.performers.replace(results.performers); imp.ensembles.replace(results.ensembles); imp.works.replace(results.works); imp.recordings.replace(results.recordings); imp.albums.replace(results.albums); } } }