diff --git a/crates/musicus/src/screens/ensemble.rs b/crates/musicus/src/screens/ensemble.rs index 8737dda..083a1c0 100644 --- a/crates/musicus/src/screens/ensemble.rs +++ b/crates/musicus/src/screens/ensemble.rs @@ -1,4 +1,4 @@ -use super::RecordingScreen; +use super::{MediumScreen, RecordingScreen}; use crate::editors::EnsembleEditor; use crate::navigator::{NavigatorWindow, NavigationHandle, Screen}; use crate::widgets; @@ -99,8 +99,12 @@ impl Screen for EnsembleScreen { row.set_activatable(true); row.set_title(Some(&medium.name)); + let medium = medium.to_owned(); row.connect_activated(clone!(@weak this => move |_| { - // TODO: Show medium screen. + let medium = medium.clone(); + spawn!(@clone this, async move { + push!(this.handle, MediumScreen, medium.clone()).await; + }); })); row.upcast() diff --git a/crates/musicus/src/screens/medium.rs b/crates/musicus/src/screens/medium.rs new file mode 100644 index 0000000..7b3db52 --- /dev/null +++ b/crates/musicus/src/screens/medium.rs @@ -0,0 +1,151 @@ +use crate::navigator::{NavigationHandle, Screen}; +use crate::widgets; +use crate::widgets::{List, Section, Widget}; +use gettextrs::gettext; +use glib::clone; +use gtk::prelude::*; +use libadwaita::prelude::*; +use musicus_backend::PlaylistItem; +use musicus_backend::db::Medium; +use std::rc::Rc; + +/// Elements for visually representing the contents of the medium. +enum ListItem { + /// A header shown on top of a track set. The value is the index of the corresponding track set + /// within the medium. + Header(usize), + + /// A track. The indices are from the track set and the track. + Track(usize, usize), + + /// A separator shown between track sets. + Separator, +} + +/// A screen for showing the contents of a medium. +pub struct MediumScreen { + handle: NavigationHandle<()>, + medium: Medium, + widget: widgets::Screen, + list: Rc, + items: Vec, +} + +impl Screen for MediumScreen { + /// Create a new medium screen for the specified medium and load the + /// contents asynchronously. + fn new(medium: Medium, handle: NavigationHandle<()>) -> Rc { + let mut items = Vec::new(); + let mut first = true; + + for (track_set_index, track_set) in medium.tracks.iter().enumerate() { + if !first { + items.push(ListItem::Separator); + } else { + first = false; + } + + items.push(ListItem::Header(track_set_index)); + + for (track_index, _) in track_set.tracks.iter().enumerate() { + items.push(ListItem::Track(track_set_index, track_index)); + } + } + + let widget = widgets::Screen::new(); + widget.set_title(&medium.name); + + let list = List::new(); + let section = Section::new("Recordings", &list.widget); + widget.add_content(§ion.widget); + widget.ready(); + + let this = Rc::new(Self { + handle, + medium, + widget, + list, + items, + }); + + this.widget.set_back_cb(clone!(@weak this => move || { + this.handle.pop(None); + })); + + + this.widget.add_action(&gettext("Edit medium"), clone!(@weak this => move || { + // TODO: Show medium editor. + })); + + this.widget.add_action(&gettext("Delete medium"), clone!(@weak this => move || { + // TODO: Delete medium and maybe also the tracks? + })); + + section.add_action("media-playback-start-symbolic", clone!(@weak this => move || { + for track_set in &this.medium.tracks { + let indices = (0..track_set.tracks.len()).collect(); + + let playlist_item = PlaylistItem { + track_set: track_set.clone(), + indices, + }; + + this.handle.backend.pl().add_item(playlist_item).unwrap(); + } + })); + + this.list.set_make_widget_cb(clone!(@weak this => move |index| { + match this.items[index] { + ListItem::Header(index) => { + let track_set = &this.medium.tracks[index]; + let recording = &track_set.recording; + + let row = libadwaita::ActionRow::new(); + row.set_activatable(false); + row.set_selectable(false); + row.set_title(Some(&recording.work.get_title())); + row.set_subtitle(Some(&recording.get_performers())); + + row.upcast() + } + ListItem::Track(track_set_index, track_index) => { + let track_set = &this.medium.tracks[track_set_index]; + let track = &track_set.tracks[track_index]; + + let mut parts = Vec::::new(); + for part in &track.work_parts { + parts.push(track_set.recording.work.parts[*part].title.clone()); + } + + let title = if parts.is_empty() { + gettext("Unknown") + } else { + parts.join(", ") + }; + + let row = libadwaita::ActionRow::new(); + row.set_selectable(false); + row.set_activatable(false); + row.set_title(Some(&title)); + row.set_margin_start(12); + + row.upcast() + } + ListItem::Separator => { + let separator = gtk::Separator::new(gtk::Orientation::Horizontal); + separator.upcast() + } + } + })); + + this.list.update(this.items.len()); + + this + } +} + +impl Widget for MediumScreen { + fn get_widget(&self) -> gtk::Widget { + self.widget.widget.clone().upcast() + } +} diff --git a/crates/musicus/src/screens/mod.rs b/crates/musicus/src/screens/mod.rs index e1e0d57..f3922ab 100644 --- a/crates/musicus/src/screens/mod.rs +++ b/crates/musicus/src/screens/mod.rs @@ -4,6 +4,9 @@ pub use ensemble::*; pub mod main; pub use main::*; +pub mod medium; +pub use medium::*; + pub mod person; pub use person::*; diff --git a/crates/musicus/src/screens/person.rs b/crates/musicus/src/screens/person.rs index 9a96a7a..94ca099 100644 --- a/crates/musicus/src/screens/person.rs +++ b/crates/musicus/src/screens/person.rs @@ -1,4 +1,4 @@ -use super::{WorkScreen, RecordingScreen}; +use super::{MediumScreen, WorkScreen, RecordingScreen}; use crate::editors::PersonEditor; use crate::navigator::{NavigatorWindow, NavigationHandle, Screen}; use crate::widgets; @@ -130,8 +130,12 @@ impl Screen for PersonScreen { row.set_activatable(true); row.set_title(Some(&medium.name)); + let medium = medium.to_owned(); row.connect_activated(clone!(@weak this => move |_| { - // TODO: Show medium screen. + let medium = medium.clone(); + spawn!(@clone this, async move { + push!(this.handle, MediumScreen, medium.clone()).await; + }); })); row.upcast()