diff --git a/data/ui/album_tile.blp b/data/ui/album_tile.blp new file mode 100644 index 0000000..e3c9fdb --- /dev/null +++ b/data/ui/album_tile.blp @@ -0,0 +1,25 @@ +using Gtk 4.0; +using Adw 1; + +template $MusicusAlbumTile: Gtk.FlowBoxChild { + styles [ + "card", + "activatable", + "tile" + ] + + Gtk.Box { + valign: center; + + Gtk.Label title_label { + styles [ + "title" + ] + + valign: center; + halign: start; + lines: 1; + ellipsize: end; + } + } +} diff --git a/data/ui/home_page.blp b/data/ui/home_page.blp index 0b36fc3..e1df41f 100644 --- a/data/ui/home_page.blp +++ b/data/ui/home_page.blp @@ -127,6 +127,23 @@ template $MusicusHomePage : Adw.NavigationPage { selection-mode: none; child-activated => $recording_selected() swapped; } + + Gtk.Label { + styles ["heading"] + visible: bind albums_flow_box.visible; + halign: start; + label: _("Albums"); + } + + Gtk.FlowBox albums_flow_box { + margin-top: 12; + margin-bottom: 24; + column-spacing: 12; + row-spacing: 12; + homogeneous: true; + selection-mode: none; + child-activated => $album_selected() swapped; + } } } }; diff --git a/src/album_tile.rs b/src/album_tile.rs new file mode 100644 index 0000000..4ddda14 --- /dev/null +++ b/src/album_tile.rs @@ -0,0 +1,56 @@ +use gtk::{glib, subclass::prelude::*}; +use std::cell::OnceCell; + +use crate::db::models::Album; + +mod imp { + use super::*; + + #[derive(Debug, Default, gtk::CompositeTemplate)] + #[template(file = "data/ui/album_tile.blp")] + pub struct MusicusAlbumTile { + pub album: OnceCell, + + #[template_child] + pub title_label: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for MusicusAlbumTile { + const NAME: &'static str = "MusicusAlbumTile"; + type Type = super::MusicusAlbumTile; + type ParentType = gtk::FlowBoxChild; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for MusicusAlbumTile {} + impl WidgetImpl for MusicusAlbumTile {} + impl FlowBoxChildImpl for MusicusAlbumTile {} +} + +glib::wrapper! { + pub struct MusicusAlbumTile(ObjectSubclass) + @extends gtk::Widget, gtk::FlowBoxChild; +} + +impl MusicusAlbumTile { + pub fn new(album: &Album) -> Self { + let obj: Self = glib::Object::new(); + + obj.imp().title_label.set_label(&album.name.get()); + obj.imp().album.set(album.clone()).unwrap(); + + obj + } + + pub fn album(&self) -> &Album { + self.imp().album.get().unwrap() + } +} diff --git a/src/db/models.rs b/src/db/models.rs index 3bbe304..5c7680f 100644 --- a/src/db/models.rs +++ b/src/db/models.rs @@ -9,7 +9,7 @@ use diesel::prelude::*; use super::{schema::*, tables, TranslatedString}; // Re-exports for tables that don't need additional information. -pub use tables::{Instrument, Person, Role}; +pub use tables::{Album, Instrument, Person, Role}; #[derive(Clone, Debug)] pub struct Work { @@ -371,3 +371,10 @@ impl Track { }) } } + +impl Eq for Album {} +impl PartialEq for Album { + fn eq(&self, other: &Self) -> bool { + self.album_id == other.album_id + } +} \ No newline at end of file diff --git a/src/home_page.rs b/src/home_page.rs index 7372924..4937d59 100644 --- a/src/home_page.rs +++ b/src/home_page.rs @@ -1,4 +1,5 @@ use crate::{ + album_tile::MusicusAlbumTile, db::models::*, library::{LibraryQuery, MusicusLibrary}, player::MusicusPlayer, @@ -8,11 +9,13 @@ use crate::{ search_tag::Tag, tag_tile::MusicusTagTile, }; + use adw::subclass::{navigation_page::NavigationPageImpl, prelude::*}; use gtk::{ glib::{self, clone, Properties}, prelude::*, }; + use std::cell::{OnceCell, RefCell}; mod imp { @@ -33,6 +36,7 @@ mod imp { pub ensembles: RefCell>, pub works: RefCell>, pub recordings: RefCell>, + pub albums: RefCell>, #[template_child] pub search_entry: TemplateChild, @@ -49,6 +53,8 @@ mod imp { #[template_child] pub recordings_flow_box: TemplateChild, #[template_child] + pub albums_flow_box: TemplateChild, + #[template_child] pub play_button: TemplateChild, } @@ -120,13 +126,14 @@ impl MusicusHomePage { fn select(&self, search_entry: &MusicusSearchEntry) { let imp = self.imp(); - let (composer, performer, ensemble, work, recording) = { + 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(), ) }; @@ -140,6 +147,8 @@ impl MusicusHomePage { 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); } } @@ -159,6 +168,11 @@ impl MusicusHomePage { ); } + #[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; @@ -227,6 +241,10 @@ impl MusicusHomePage { 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(); @@ -237,6 +255,7 @@ impl MusicusHomePage { &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); @@ -257,6 +276,7 @@ impl MusicusHomePage { 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 @@ -283,11 +303,16 @@ impl MusicusHomePage { .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); } } } diff --git a/src/library.rs b/src/library.rs index 41fdea3..9b270ad 100644 --- a/src/library.rs +++ b/src/library.rs @@ -109,11 +109,17 @@ impl MusicusLibrary { .map(|w| Work::from_table(w, connection)) .collect::>>()?; + let albums: Vec = albums::table + .filter(albums::name.like(&search)) + .limit(9) + .load(connection)?; + LibraryResults { composers, performers, ensembles, works, + albums, ..Default::default() } } @@ -219,9 +225,24 @@ impl MusicusLibrary { .map(|r| Recording::from_table(r, &&self.folder(), connection)) .collect::>>()?; + let albums = albums::table + .inner_join( + album_recordings::table + .inner_join(recordings::table.inner_join(recording_ensembles::table)), + ) + .filter( + recording_ensembles::ensemble_id + .eq(&ensemble.ensemble_id) + .and(albums::name.like(&search)), + ) + .select(albums::all_columns) + .distinct() + .load(connection)?; + LibraryResults { composers, recordings, + albums, ..Default::default() } } @@ -265,9 +286,24 @@ impl MusicusLibrary { .map(|r| Recording::from_table(r, &self.folder(), connection)) .collect::>>()?; + let albums = albums::table + .inner_join( + album_recordings::table + .inner_join(recordings::table.inner_join(recording_persons::table)), + ) + .filter( + recording_persons::person_id + .eq(&performer.person_id) + .and(albums::name.like(&search)), + ) + .select(albums::all_columns) + .distinct() + .load(connection)?; + LibraryResults { composers, recordings, + albums, ..Default::default() } } @@ -426,6 +462,7 @@ pub struct LibraryResults { pub ensembles: Vec, pub works: Vec, pub recordings: Vec, + pub albums: Vec, } impl LibraryResults { @@ -435,5 +472,6 @@ impl LibraryResults { && self.ensembles.is_empty() && self.works.is_empty() && self.recordings.is_empty() + && self.albums.is_empty() } } diff --git a/src/main.rs b/src/main.rs index f336ae4..d903ecf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,11 @@ +mod album_tile; mod application; mod config; mod db; mod editor; mod home_page; -mod library_manager; mod library; +mod library_manager; mod player; mod player_bar; mod playlist_item;