Add separate recording tile

This commit is contained in:
Elias Projahn 2023-10-08 16:40:59 +02:00
parent a790d913af
commit 2143d6333b
11 changed files with 319 additions and 125 deletions

View file

@ -20,4 +20,18 @@
.tile .subtitle {
font-size: smaller;
}
.tile .work {
margin-top: 3px;
}
.tile .composer {
font-size: smaller;
}
.tile .performances {
margin-top: 3px;
margin-bottom: 3px;
font-size: smaller;
}

View file

@ -0,0 +1,39 @@
using Gtk 4.0;
using Adw 1;
template $MusicusRecordingTile : Gtk.FlowBoxChild {
styles ["card", "activatable", "tile"]
Gtk.Box {
spacing: 12;
Gtk.Image {
icon-name: "media-playback-start-symbolic";
valign: start;
margin-top: 12;
}
Gtk.Box {
orientation: vertical;
hexpand: true;
Gtk.Label work_label {
styles ["work"]
halign: start;
wrap: true;
}
Gtk.Label composer_label {
styles ["composer"]
halign: start;
wrap: true;
}
Gtk.Label performances_label {
styles ["performances", "dim-label"]
halign: start;
wrap: true;
}
}
}
}

View file

@ -1,7 +1,7 @@
using Gtk 4.0;
using Adw 1;
template $MusicusTile : Gtk.FlowBoxChild {
template $MusicusTagTile : Gtk.FlowBoxChild {
styles ["card", "activatable", "tile"]
Gtk.Box {
@ -11,26 +11,16 @@ template $MusicusTile : Gtk.FlowBoxChild {
Gtk.Label title_label {
styles ["title"]
halign: start;
label: _("Title");
lines: 1;
ellipsize: end;
}
Gtk.Label subtitle_label {
visible: false;
styles ["subtitle", "dim-label"]
halign: start;
label: _("Subtitle");
lines: 1;
ellipsize: end;
}
}
}
menu item_menu {
item {
label: _("_Play");
}
item {
label: _("_Edit");
}
item {
label: _("_Delete");
}
}

View file

@ -1,10 +1,11 @@
data/res/home_page.blp
data/res/playlist_page.blp
data/ui/home_page.blp
data/ui/playlist_page.blp
data/ui/recording_tile.blp
data/ui/search_entry.blp
data/ui/search_tag.blp
data/res/tile.blp
data/res/welcome_page.blp
data/res/window.blp
data/ui/tag_tile.blp
data/ui/welcome_page.blp
data/ui/window.blp
data/de.johrpan.musicus.desktop.in
data/de.johrpan.musicus.appdata.xml.in
data/de.johrpan.musicus.gschema.xml

View file

@ -1,9 +1,10 @@
use crate::{
library::{Ensemble, LibraryQuery, MusicusLibrary, Person, Recording, Work},
player::MusicusPlayer,
recording_tile::MusicusRecordingTile,
search_entry::MusicusSearchEntry,
search_tag::Tag,
tile::MusicusTile,
tag_tile::MusicusTagTile,
};
use adw::subclass::{navigation_page::NavigationPageImpl, prelude::*};
use gtk::{
@ -170,31 +171,28 @@ impl MusicusHomePage {
for composer in &results.composers {
imp.composers_flow_box
.append(&MusicusTile::with_title(&composer.name_fl()));
.append(&MusicusTagTile::new(Tag::Composer(composer.clone())));
}
for performer in &results.performers {
imp.performers_flow_box
.append(&MusicusTile::with_title(&performer.name_fl()));
.append(&MusicusTagTile::new(Tag::Performer(performer.clone())));
}
for ensemble in &results.ensembles {
imp.ensembles_flow_box
.append(&MusicusTile::with_title(&ensemble.name));
.append(&MusicusTagTile::new(Tag::Ensemble(ensemble.clone())));
}
for work in &results.works {
imp.works_flow_box.append(&MusicusTile::with_subtitle(
&work.title,
&work.composer.name_fl(),
));
imp.works_flow_box
.append(&MusicusTagTile::new(Tag::Work(work.clone())));
}
for recording in &results.recordings {
imp.recordings_flow_box.append(&MusicusTile::with_subtitle(
&recording.work.title,
&recording.work.composer.name_fl(),
));
let performances = self.library().performances(recording);
imp.recordings_flow_box
.append(&MusicusRecordingTile::new(recording, performances));
}
imp.composers.replace(results.composers);

View file

@ -63,7 +63,7 @@ impl MusicusLibrary {
.unwrap()
.collect::<rusqlite::Result<Vec<Person>>>()
.unwrap();
let performers = self.con()
.prepare("SELECT DISTINCT persons.id, persons.first_name, persons.last_name FROM persons INNER JOIN performances ON performances.person = persons.id WHERE persons.first_name LIKE ?1 OR persons.last_name LIKE ?1 LIMIT 9")
.unwrap()
@ -167,7 +167,7 @@ impl MusicusLibrary {
recordings,
..Default::default()
}
},
}
LibraryQuery {
composer: None,
performer: Some(performer),
@ -196,7 +196,7 @@ impl MusicusLibrary {
recordings,
..Default::default()
}
},
}
LibraryQuery {
composer: Some(composer),
ensemble: Some(ensemble),
@ -216,7 +216,7 @@ impl MusicusLibrary {
recordings,
..Default::default()
}
},
}
LibraryQuery {
composer: Some(composer),
performer: Some(performer),
@ -236,10 +236,9 @@ impl MusicusLibrary {
recordings,
..Default::default()
}
},
}
LibraryQuery {
work: Some(work),
..
work: Some(work), ..
} => {
let recordings = self
.con()
@ -258,6 +257,28 @@ impl MusicusLibrary {
}
}
pub fn performances(&self, recording: &Recording) -> Vec<Performance> {
let mut performances = self
.con()
.prepare("SELECT persons.id, persons.first_name, persons.last_name, instruments.id, instruments.name FROM performances INNER JOIN persons ON persons.id = performances.person LEFT JOIN instruments ON instruments.id = performances.role INNER JOIN recordings ON performances.recording = recordings.id WHERE recordings.id IS ?1")
.unwrap()
.query_map([&recording.id], Performance::from_person_row)
.unwrap()
.collect::<rusqlite::Result<Vec<Performance>>>()
.unwrap();
performances.append(&mut self
.con()
.prepare("SELECT ensembles.id, ensembles.name, instruments.id, instruments.name FROM performances INNER JOIN ensembles ON ensembles.id = performances.ensemble LEFT JOIN instruments ON instruments.id = performances.role INNER JOIN recordings ON performances.recording = recordings.id WHERE recordings.id IS ?1")
.unwrap()
.query_map([&recording.id], Performance::from_ensemble_row)
.unwrap()
.collect::<rusqlite::Result<Vec<Performance>>>()
.unwrap());
performances
}
fn con(&self) -> &Connection {
self.imp().connection.get().unwrap()
}
@ -394,3 +415,60 @@ impl Recording {
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Performance {
Person(Person, Option<Role>),
Ensemble(Ensemble, Option<Role>),
}
impl Performance {
pub fn from_person_row(row: &Row) -> rusqlite::Result<Self> {
let person = Person {
id: row.get(0)?,
first_name: row.get(1)?,
last_name: row.get(2)?,
};
Ok(match row.get::<_, Option<String>>(3)? {
None => Self::Person(person, None),
Some(role_id) => Self::Person(
person,
Some(Role {
id: role_id,
name: row.get(4)?,
}),
),
})
}
pub fn from_ensemble_row(row: &Row) -> rusqlite::Result<Self> {
let ensemble = Ensemble {
id: row.get(0)?,
name: row.get(1)?,
};
Ok(match row.get::<_, Option<String>>(2)? {
None => Self::Ensemble(ensemble, None),
Some(role_id) => Self::Ensemble(
ensemble,
Some(Role {
id: role_id,
name: row.get(3)?,
}),
),
})
}
}
#[derive(Debug, Clone, Eq)]
pub struct Role {
pub id: String,
pub name: String,
}
impl PartialEq for Role {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}

View file

@ -4,9 +4,10 @@ mod home_page;
mod library;
mod player;
mod playlist_page;
mod recording_tile;
mod search_entry;
mod search_tag;
mod tile;
mod tag_tile;
mod welcome_page;
mod window;

86
src/recording_tile.rs Normal file
View file

@ -0,0 +1,86 @@
use crate::library::{Performance, Recording};
use gtk::{glib, subclass::prelude::*};
use std::cell::OnceCell;
mod imp {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(file = "data/ui/recording_tile.blp")]
pub struct MusicusRecordingTile {
#[template_child]
pub composer_label: TemplateChild<gtk::Label>,
#[template_child]
pub work_label: TemplateChild<gtk::Label>,
#[template_child]
pub performances_label: TemplateChild<gtk::Label>,
pub recording: OnceCell<Recording>,
}
#[glib::object_subclass]
impl ObjectSubclass for MusicusRecordingTile {
const NAME: &'static str = "MusicusRecordingTile";
type Type = super::MusicusRecordingTile;
type ParentType = gtk::FlowBoxChild;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for MusicusRecordingTile {}
impl WidgetImpl for MusicusRecordingTile {}
impl FlowBoxChildImpl for MusicusRecordingTile {}
}
glib::wrapper! {
pub struct MusicusRecordingTile(ObjectSubclass<imp::MusicusRecordingTile>)
@extends gtk::Widget, gtk::FlowBoxChild;
}
impl MusicusRecordingTile {
pub fn new(recording: &Recording, performances: Vec<Performance>) -> Self {
let obj: Self = glib::Object::new();
let imp = obj.imp();
imp.work_label.set_label(&recording.work.title);
imp.composer_label
.set_label(&recording.work.composer.name_fl());
imp.performances_label.set_label(
&performances
.into_iter()
.map(|performance| match performance {
Performance::Person(person, role) => {
let mut result = person.name_fl();
if let Some(role) = role {
result.push_str(&format!(" ({})", role.name));
}
result
}
Performance::Ensemble(ensemble, role) => {
let mut result = ensemble.name;
if let Some(role) = role {
result.push_str(&format!(" ({})", role.name));
}
result
}
})
.collect::<Vec<String>>()
.join(", "),
);
imp.recording.set(recording.clone()).unwrap();
obj
}
pub fn recording(&self) -> &Recording {
self.imp().recording.get().unwrap()
}
}

View file

@ -134,10 +134,11 @@ impl MusicusSearchEntry {
}
pub fn reset(&self) {
let mut tags = self.imp().tags.borrow_mut();
while let Some(tag) = tags.pop() {
self.imp().tags_box.remove(&tag);
{
let mut tags = self.imp().tags.borrow_mut();
while let Some(tag) = tags.pop() {
self.imp().tags_box.remove(&tag);
}
}
self.imp().text.set_text("");

67
src/tag_tile.rs Normal file
View file

@ -0,0 +1,67 @@
use crate::search_tag::Tag;
use gtk::{glib, prelude::*, subclass::prelude::*};
use std::cell::OnceCell;
mod imp {
use super::*;
#[derive(Debug, Default, gtk::CompositeTemplate)]
#[template(file = "data/ui/tag_tile.blp")]
pub struct MusicusTagTile {
#[template_child]
pub title_label: TemplateChild<gtk::Label>,
#[template_child]
pub subtitle_label: TemplateChild<gtk::Label>,
pub tag: OnceCell<Tag>,
}
#[glib::object_subclass]
impl ObjectSubclass for MusicusTagTile {
const NAME: &'static str = "MusicusTagTile";
type Type = super::MusicusTagTile;
type ParentType = gtk::FlowBoxChild;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
impl ObjectImpl for MusicusTagTile {}
impl WidgetImpl for MusicusTagTile {}
impl FlowBoxChildImpl for MusicusTagTile {}
}
glib::wrapper! {
pub struct MusicusTagTile(ObjectSubclass<imp::MusicusTagTile>)
@extends gtk::Widget, gtk::FlowBoxChild;
}
impl MusicusTagTile {
pub fn new(tag: Tag) -> Self {
let obj: Self = glib::Object::new();
let imp = obj.imp();
match &tag {
Tag::Composer(person) | Tag::Performer(person) => {
imp.title_label.set_label(&person.name_fl());
}
Tag::Ensemble(ensemble) => {
imp.title_label.set_label(&ensemble.name);
}
Tag::Work(work) => {
imp.title_label.set_label(&work.title);
imp.subtitle_label.set_label(&work.composer.name_fl());
imp.subtitle_label.set_visible(true);
}
}
imp.tag.set(tag).unwrap();
obj
}
}

View file

@ -1,81 +0,0 @@
use gtk::{glib, glib::Properties, prelude::*, subclass::prelude::*};
use std::cell::RefCell;
mod imp {
use super::*;
#[derive(Properties, Debug, Default, gtk::CompositeTemplate)]
#[properties(wrapper_type = super::MusicusTile)]
#[template(file = "data/ui/tile.blp")]
pub struct MusicusTile {
#[property(get, set)]
pub title: RefCell<String>,
#[property(get, set)]
pub subtitle: RefCell<Option<String>>,
#[template_child]
pub title_label: TemplateChild<gtk::Label>,
#[template_child]
pub subtitle_label: TemplateChild<gtk::Label>,
}
#[glib::object_subclass]
impl ObjectSubclass for MusicusTile {
const NAME: &'static str = "MusicusTile";
type Type = super::MusicusTile;
type ParentType = gtk::FlowBoxChild;
fn class_init(klass: &mut Self::Class) {
klass.bind_template();
}
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
obj.init_template();
}
}
#[glib::derived_properties]
impl ObjectImpl for MusicusTile {
fn constructed(&self) {
self.parent_constructed();
self.obj()
.bind_property("title", &self.title_label.get(), "label")
.sync_create()
.build();
self.obj()
.bind_property("subtitle", &self.subtitle_label.get(), "visible")
.sync_create()
.transform_to(|_, s: Option<String>| Some(s.is_some()))
.build();
self.obj()
.bind_property("subtitle", &self.subtitle_label.get(), "label")
.sync_create()
.build();
}
}
impl WidgetImpl for MusicusTile {}
impl FlowBoxChildImpl for MusicusTile {}
}
glib::wrapper! {
pub struct MusicusTile(ObjectSubclass<imp::MusicusTile>)
@extends gtk::Widget, gtk::FlowBoxChild;
}
impl MusicusTile {
pub fn with_title(title: &str) -> Self {
glib::Object::builder().property("title", title).build()
}
pub fn with_subtitle(title: &str, subtitle: &str) -> Self {
glib::Object::builder()
.property("title", title)
.property("subtitle", subtitle)
.build()
}
}