diff --git a/res/resources.xml b/res/resources.xml index cc45cd4..e91eca3 100644 --- a/res/resources.xml +++ b/res/resources.xml @@ -3,16 +3,22 @@ ui/ensemble_editor.ui ui/ensemble_selector.ui + ui/ensemble_screen.ui ui/instrument_editor.ui ui/instrument_selector.ui ui/part_editor.ui ui/performance_editor.ui ui/person_editor.ui + ui/person_list.ui + ui/person_screen.ui ui/person_selector.ui + ui/poe_list.ui ui/recording_editor.ui + ui/recording_screen.ui ui/section_editor.ui ui/window.ui ui/work_editor.ui + ui/work_screen.ui ui/work_selector.ui diff --git a/res/ui/ensemble_screen.ui b/res/ui/ensemble_screen.ui new file mode 100644 index 0000000..87b6626 --- /dev/null +++ b/res/ui/ensemble_screen.ui @@ -0,0 +1,206 @@ + + + + + + + True + False + vertical + + + True + False + True + + + True + True + True + + + True + False + go-previous-symbolic + + + + + + + True + True + False + True + + + True + False + view-more-symbolic + + + + + end + 1 + + + + + True + True + True + + + True + False + edit-find-symbolic + + + + + end + 1 + + + + + False + True + 0 + + + + + True + False + False + + + True + False + 400 + + + True + True + edit-find-symbolic + False + False + Search recordings … + + + + + + + False + True + 1 + + + + + True + False + + + True + False + True + + + loading + + + + + True + True + + + True + False + none + + + True + False + 18 + vertical + 18 + + + True + False + vertical + 12 + + + True + False + start + Recordings + + + + + + False + True + 0 + + + + + True + False + 0 + in + + + + + + False + True + 1 + + + + + False + True + 1 + + + + + + + + + content + 1 + + + + + True + False + No recordings found. + + + nothing + 2 + + + + + True + True + 2 + + + + diff --git a/res/ui/person_list.ui b/res/ui/person_list.ui new file mode 100644 index 0000000..0346312 --- /dev/null +++ b/res/ui/person_list.ui @@ -0,0 +1,75 @@ + + + + + + + True + False + vertical + + + True + False + True + + + True + False + 400 + 300 + + + True + True + edit-find-symbolic + False + False + Search persons … + + + + + + + False + True + 0 + + + + + True + False + + + True + False + True + + + loading + + + + + True + True + + + + + + content + 1 + + + + + True + True + 1 + + + + diff --git a/res/ui/person_screen.ui b/res/ui/person_screen.ui new file mode 100644 index 0000000..0bfcaf6 --- /dev/null +++ b/res/ui/person_screen.ui @@ -0,0 +1,251 @@ + + + + + + + True + False + vertical + + + True + False + True + + + True + True + True + + + True + False + go-previous-symbolic + + + + + + + True + True + False + True + + + True + False + view-more-symbolic + + + + + end + 1 + + + + + True + True + True + + + True + False + edit-find-symbolic + + + + + end + 1 + + + + + False + True + 0 + + + + + True + False + False + + + True + False + 400 + + + True + True + edit-find-symbolic + False + False + Search works and recordings … + + + + + + + False + True + 1 + + + + + True + False + + + True + False + True + + + loading + + + + + True + True + + + True + False + none + + + True + False + 18 + vertical + 18 + + + True + False + vertical + 12 + + + True + False + start + Works + + + + + + False + True + 0 + + + + + True + False + 0 + in + + + + + + False + True + 1 + + + + + False + True + 0 + + + + + True + False + vertical + 12 + + + True + False + start + Recordings + + + + + + False + True + 0 + + + + + True + False + 0 + in + + + + + + False + True + 1 + + + + + False + True + 1 + + + + + + + + + content + 1 + + + + + True + False + No works or recordings found. + + + nothing + 2 + + + + + True + True + 2 + + + + diff --git a/res/ui/person_selector.ui b/res/ui/person_selector.ui index 06eb55e..eea780b 100644 --- a/res/ui/person_selector.ui +++ b/res/ui/person_selector.ui @@ -1,86 +1,33 @@ - + - False + False True - 350 - 300 - True - dialog + 350 + 300 + True + dialog - - True - False - vertical - - - True - True - 6 - 6 - 6 - 6 - edit-find-symbolic - False - False - - - False - True - 0 - - - - - True - True - in - - - True - False - - - True - False - - - True - False - No persons found. - - - - - - - - - True - True - 1 - - - + True - False + False Select person - True + True True - True - True + True + True True - False - list-add-symbolic + False + list-add-symbolic diff --git a/res/ui/poe_list.ui b/res/ui/poe_list.ui new file mode 100644 index 0000000..fce2b39 --- /dev/null +++ b/res/ui/poe_list.ui @@ -0,0 +1,75 @@ + + + + + + + True + False + vertical + + + True + False + True + + + True + False + 400 + 300 + + + True + True + edit-find-symbolic + False + False + Search persons and ensembles … + + + + + + + False + True + 0 + + + + + True + False + + + True + False + True + + + loading + + + + + True + True + + + + + + content + 1 + + + + + True + True + 1 + + + + diff --git a/res/ui/recording_screen.ui b/res/ui/recording_screen.ui new file mode 100644 index 0000000..b5c17c6 --- /dev/null +++ b/res/ui/recording_screen.ui @@ -0,0 +1,59 @@ + + + + + + + True + False + vertical + + + True + False + True + + + True + True + True + + + True + False + go-previous-symbolic + + + + + + + True + True + False + True + + + True + False + view-more-symbolic + + + + + end + 1 + + + + + False + True + 0 + + + + + + + diff --git a/res/ui/window.ui b/res/ui/window.ui index b34d908..68dd11b 100644 --- a/res/ui/window.ui +++ b/res/ui/window.ui @@ -3,6 +3,86 @@ + + True + False + vertical + + + True + False + True + Musicus Editor + True + + + False + True + 0 + + + + + True + False + center + center + vertical + 18 + + + True + False + 0.5 + 80 + folder-music-symbolic + + + False + True + 0 + + + + + True + False + 0.5 + Welcome to Musicus Editor! + + + + + + False + True + 1 + + + + + True + False + 0.5 + Get startet by selecting something from the sidebar or adding new things to your library using the button in the top left corner. + center + True + 40 + + + False + True + 2 + + + + + True + True + 1 + + + False 800 @@ -21,10 +101,10 @@ False vertical - + True False - True + False True @@ -67,87 +147,6 @@ 0 - - - True - False - True - - - True - False - 400 - - - True - True - edit-find-symbolic - False - False - Search persons and ensembles … - - - - - - - False - True - 1 - - - - - True - False - crossfade - - - True - False - True - - - loading - - - - - True - True - - - True - False - none - - - True - False - - - True - False - No persons or ensembles found. - - - - - - - - - content - 1 - - - - - True - True - 2 - - sidebar @@ -167,753 +166,11 @@ - - True - False - True - crossfade - - - True - False - vertical - - - True - False - True - Musicus Editor - True - - - False - True - 0 - - - - - True - False - center - center - vertical - 18 - - - True - False - 0.5 - 80 - folder-music-symbolic - - - False - True - 0 - - - - - True - False - 0.5 - Welcome to Musicus Editor! - - - - - - False - True - 1 - - - - - True - False - 0.5 - Get startet by selecting something from the sidebar or adding new things to your library using the button in the top left corner. - center - True - 40 - - - False - True - 2 - - - - - True - True - 1 - - - - - empty_screen - - - - - True - False - vertical - - - True - False - True - - - True - False - crossfade - 0 - False - - - True - True - True - win.back - - - True - False - go-previous-symbolic - - - - - - - - - True - True - False - True - - - True - False - view-more-symbolic - - - - - end - 1 - - - - - True - True - True - - - True - False - edit-find-symbolic - - - - - end - 1 - - - - - False - True - 0 - - - - - True - False - False - - - True - False - 400 - - - True - True - edit-find-symbolic - False - False - Search works and recordings … - - - - - - - False - True - 1 - - - - - True - False - crossfade - - - True - False - True - - - loading - - - - - True - True - - - True - False - none - - - True - False - 18 - vertical - 18 - - - True - False - vertical - 12 - - - True - False - start - Works - - - - - - False - True - 0 - - - - - True - False - 0 - in - - - True - False - - - True - False - 6 - 6 - 6 - 6 - 6 - 6 - No works found. - - - - - - - False - True - 1 - - - - - False - True - 0 - - - - - True - False - vertical - 12 - - - True - False - start - Recordings - - - - - - False - True - 0 - - - - - True - False - 0 - in - - - True - False - - - True - False - 6 - 6 - 6 - 6 - 6 - 6 - No recordings found. - - - - - - - False - True - 1 - - - - - False - True - 1 - - - - - - - - - content - 1 - - - - - True - True - 2 - - - - - overview_screen - page0 - 1 - - - - - True - False - vertical - - - True - False - True - - - True - True - True - - - True - False - go-previous-symbolic - - - - - - - True - True - False - True - - - True - False - view-more-symbolic - - - - - end - 1 - - - - - True - True - True - - - True - False - edit-find-symbolic - - - - - end - 1 - - - - - False - True - 0 - - - - - True - False - False - - - True - False - 400 - - - True - True - edit-find-symbolic - False - False - Search recordings … - - - - - - - False - True - 1 - - - - - True - False - crossfade - - - True - False - True - - - loading - - - - - True - True - - - True - False - none - - - True - False - 18 - vertical - 12 - - - True - False - start - Recordings - - - - - - False - True - 0 - - - - - True - False - 0 - in - - - True - False - - - True - False - 6 - 6 - 6 - 6 - 6 - 6 - No recordings found. - - - - - - - False - True - 1 - - - - - - - - - content - 1 - - - - - True - True - 2 - - - - - work_details_screen - 2 - - - - - True - False - vertical - - - True - False - True - - - True - True - True - - - True - False - go-previous-symbolic - - - - - - - True - True - False - True - - - True - False - view-more-symbolic - - - - - end - 1 - - - - - False - True - 0 - - - - - True - False - crossfade - - - True - False - True - - - loading - - - - - True - True - - - True - False - none - - - True - False - 18 - vertical - 12 - - - True - False - start - Tracks - - - - - - False - True - 0 - - - - - True - False - 0 - in - - - True - False - - - True - False - 6 - 6 - 6 - 6 - 6 - 6 - No tracks found. - - - - - - - False - True - 1 - - - - - - - - - content - 1 - - - - - True - True - 2 - - - - - recording_details_screen - 3 - - - - - content - + - - - - - - - - - - - - - -
diff --git a/res/ui/work_screen.ui b/res/ui/work_screen.ui new file mode 100644 index 0000000..87b6626 --- /dev/null +++ b/res/ui/work_screen.ui @@ -0,0 +1,206 @@ + + + + + + + True + False + vertical + + + True + False + True + + + True + True + True + + + True + False + go-previous-symbolic + + + + + + + True + True + False + True + + + True + False + view-more-symbolic + + + + + end + 1 + + + + + True + True + True + + + True + False + edit-find-symbolic + + + + + end + 1 + + + + + False + True + 0 + + + + + True + False + False + + + True + False + 400 + + + True + True + edit-find-symbolic + False + False + Search recordings … + + + + + + + False + True + 1 + + + + + True + False + + + True + False + True + + + loading + + + + + True + True + + + True + False + none + + + True + False + 18 + vertical + 18 + + + True + False + vertical + 12 + + + True + False + start + Recordings + + + + + + False + True + 0 + + + + + True + False + 0 + in + + + + + + False + True + 1 + + + + + False + True + 1 + + + + + + + + + content + 1 + + + + + True + False + No recordings found. + + + nothing + 2 + + + + + True + True + 2 + + + + diff --git a/src/dialogs/ensemble_selector.rs b/src/dialogs/ensemble_selector.rs index b3ee5dd..808aba5 100644 --- a/src/dialogs/ensemble_selector.rs +++ b/src/dialogs/ensemble_selector.rs @@ -1,7 +1,7 @@ -use super::selector_row::SelectorRow; use super::EnsembleEditor; use crate::backend::Backend; use crate::database::*; +use crate::widgets::*; use gio::prelude::*; use glib::clone; use gtk::prelude::*; diff --git a/src/dialogs/instrument_selector.rs b/src/dialogs/instrument_selector.rs index 02fea94..c147236 100644 --- a/src/dialogs/instrument_selector.rs +++ b/src/dialogs/instrument_selector.rs @@ -1,7 +1,7 @@ -use super::selector_row::SelectorRow; use super::InstrumentEditor; use crate::backend::Backend; use crate::database::*; +use crate::widgets::*; use gio::prelude::*; use glib::clone; use gtk::prelude::*; diff --git a/src/dialogs/mod.rs b/src/dialogs/mod.rs index f862abb..3e2ec7f 100644 --- a/src/dialogs/mod.rs +++ b/src/dialogs/mod.rs @@ -28,9 +28,6 @@ pub use recording_editor::*; pub mod section_editor; pub use section_editor::*; -pub mod selector_row; -pub use selector_row::*; - pub mod work_editor; pub use work_editor::*; diff --git a/src/dialogs/part_editor.rs b/src/dialogs/part_editor.rs index 350d456..cb7f721 100644 --- a/src/dialogs/part_editor.rs +++ b/src/dialogs/part_editor.rs @@ -1,7 +1,7 @@ -use super::selector_row::SelectorRow; use super::{InstrumentSelector, PersonSelector}; use crate::backend::*; use crate::database::*; +use crate::widgets::*; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; diff --git a/src/dialogs/person_selector.rs b/src/dialogs/person_selector.rs index 379e18e..d01cd49 100644 --- a/src/dialogs/person_selector.rs +++ b/src/dialogs/person_selector.rs @@ -1,104 +1,58 @@ -use super::selector_row::SelectorRow; use super::PersonEditor; use crate::backend::Backend; use crate::database::*; +use crate::widgets::*; use gio::prelude::*; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; -use std::convert::TryInto; use std::rc::Rc; -pub struct PersonSelector -where - F: Fn(Person) -> () + 'static, -{ - backend: Rc, +pub struct PersonSelector { window: gtk::Window, - callback: F, - list: gtk::ListBox, - search_entry: gtk::SearchEntry, } -impl PersonSelector -where - F: Fn(Person) -> () + 'static, -{ - pub fn new>(backend: Rc, parent: &P, callback: F) -> Rc { +impl PersonSelector { + pub fn new(backend: Rc, parent: &P, callback: F) -> Self + where + P: IsA, + F: Fn(Person) -> () + 'static, + { let builder = gtk::Builder::from_resource("/de/johrpan/musicus_editor/ui/person_selector.ui"); get_widget!(builder, gtk::Window, window); get_widget!(builder, gtk::Button, add_button); - get_widget!(builder, gtk::SearchEntry, search_entry); - get_widget!(builder, gtk::ListBox, list); - let result = Rc::new(PersonSelector { - backend: backend, - window: window, - callback: callback, - search_entry: search_entry, - list: list, - }); + let callback = Rc::new(callback); - let c = glib::MainContext::default(); - let clone = result.clone(); - c.spawn_local(async move { - let persons = clone.backend.get_persons().await.unwrap(); + let list = PersonList::new(backend.clone()); - for (index, person) in persons.iter().enumerate() { - let label = gtk::Label::new(Some(&person.name_lf())); - label.set_halign(gtk::Align::Start); - let row = SelectorRow::new(index.try_into().unwrap(), &label); - row.show_all(); - clone.list.insert(&row, -1); - } - - clone.list.connect_row_activated( - clone!(@strong clone, @strong persons => move |_, row| { - clone.window.close(); - let row = row.get_child().unwrap().downcast::().unwrap(); - let index: usize = row.get_index().try_into().unwrap(); - (clone.callback)(persons[index].clone()); - }), - ); - - clone - .list - .set_filter_func(Some(Box::new(clone!(@strong clone => move |row| { - let row = row.get_child().unwrap().downcast::().unwrap(); - let index: usize = row.get_index().try_into().unwrap(); - let search = clone.search_entry.get_text().to_string().to_lowercase(); - search.is_empty() || persons[index] - .name_lf() - .to_lowercase() - .contains(&search) - })))); - }); - - result - .search_entry - .connect_search_changed(clone!(@strong result => move |_| { - result.list.invalidate_filter(); - })); - - add_button.connect_clicked(clone!(@strong result => move |_| { - let editor = PersonEditor::new( - result.backend.clone(), - &result.window, - None, - clone!(@strong result => move |person| { - result.window.close(); - (result.callback)(person); - }), - ); - - editor.show(); + list.set_selected(clone!(@strong window, @strong callback => move |person| { + window.close(); + callback(person.clone()); })); - result.window.set_transient_for(Some(parent)); + window.set_transient_for(Some(parent)); + window.add(&list.widget); - result + add_button.connect_clicked( + clone!(@strong backend, @strong window, @strong callback => move |_| { + let editor = PersonEditor::new( + backend.clone(), + &window, + None, + clone!(@strong window, @strong callback => move |person| { + window.close(); + callback(person); + }), + ); + + editor.show(); + }), + ); + + Self { window } } pub fn show(&self) { diff --git a/src/dialogs/recording_editor.rs b/src/dialogs/recording_editor.rs index 862943f..07f000b 100644 --- a/src/dialogs/recording_editor.rs +++ b/src/dialogs/recording_editor.rs @@ -1,6 +1,7 @@ use super::*; use crate::backend::Backend; use crate::database::*; +use crate::widgets::*; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; diff --git a/src/dialogs/work_editor.rs b/src/dialogs/work_editor.rs index 56b7ab0..47532cd 100644 --- a/src/dialogs/work_editor.rs +++ b/src/dialogs/work_editor.rs @@ -1,7 +1,7 @@ -use super::selector_row::SelectorRow; use super::{InstrumentSelector, PersonSelector, PartEditor, SectionEditor}; use crate::backend::*; use crate::database::*; +use crate::widgets::*; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; diff --git a/src/dialogs/work_selector.rs b/src/dialogs/work_selector.rs index f71a1fe..95c6112 100644 --- a/src/dialogs/work_selector.rs +++ b/src/dialogs/work_selector.rs @@ -1,6 +1,7 @@ use super::*; use crate::backend::Backend; use crate::database::*; +use crate::widgets::*; use gio::prelude::*; use glib::clone; use gtk::prelude::*; diff --git a/src/main.rs b/src/main.rs index c0d2711..fadced3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,8 @@ use std::rc::Rc; mod backend; mod database; mod dialogs; +mod screens; +mod widgets; mod window; use window::Window; diff --git a/src/screens/ensemble_screen.rs b/src/screens/ensemble_screen.rs new file mode 100644 index 0000000..0c68173 --- /dev/null +++ b/src/screens/ensemble_screen.rs @@ -0,0 +1,110 @@ +use crate::backend::*; +use crate::database::*; +use crate::widgets::*; +use glib::clone; +use gtk::prelude::*; +use gtk_macros::get_widget; +use libhandy::HeaderBarExt; +use std::cell::RefCell; +use std::rc::Rc; + +pub struct EnsembleScreen { + pub widget: gtk::Box, + stack: gtk::Stack, + recording_list: Rc>, + back: RefCell () + 'static>>>, +} + +impl EnsembleScreen { + pub fn new(backend: Rc, ensemble: Ensemble) -> Rc { + let builder = + gtk::Builder::from_resource("/de/johrpan/musicus_editor/ui/ensemble_screen.ui"); + + get_widget!(builder, gtk::Box, widget); + get_widget!(builder, libhandy::HeaderBar, header); + get_widget!(builder, gtk::Button, back_button); + get_widget!(builder, gtk::MenuButton, menu_button); + get_widget!(builder, gtk::SearchEntry, search_entry); + get_widget!(builder, gtk::Stack, stack); + get_widget!(builder, gtk::Frame, recording_frame); + + header.set_title(Some(&ensemble.name)); + + let recording_list = List::new( + |recording: &RecordingDescription| { + let work_label = gtk::Label::new(Some(&recording.work.get_title())); + + work_label.set_ellipsize(pango::EllipsizeMode::End); + work_label.set_halign(gtk::Align::Start); + + let performers_label = gtk::Label::new(Some(&recording.get_performers())); + performers_label.set_ellipsize(pango::EllipsizeMode::End); + performers_label.set_opacity(0.5); + performers_label.set_halign(gtk::Align::Start); + + let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); + vbox.add(&work_label); + vbox.add(&performers_label); + + vbox.upcast() + }, + clone!(@strong search_entry => move |recording: &RecordingDescription| { + let search = search_entry.get_text().to_string().to_lowercase(); + let text = recording.work.get_title() + &recording.get_performers(); + search.is_empty() || text.contains(&search) + }), + "No recordings found.", + ); + + recording_frame.add(&recording_list.widget.clone()); + + let result = Rc::new(Self { + widget, + stack, + recording_list, + back: RefCell::new(None), + }); + + search_entry.connect_search_changed(clone!(@strong result => move |_| { + result.recording_list.invalidate_filter(); + })); + + back_button.connect_clicked(clone!(@strong result => move |_| { + if let Some(back) = &*result.back.borrow() { + back(); + } + })); + + let context = glib::MainContext::default(); + let clone = result.clone(); + context.spawn_local(async move { + let recordings = backend + .get_recordings_for_ensemble(ensemble.id) + .await + .unwrap(); + + if recordings.is_empty() { + clone.stack.set_visible_child_name("nothing"); + } else { + clone.recording_list.show_items(recordings); + clone.stack.set_visible_child_name("content"); + } + }); + + result + } + + pub fn set_back(&self, back: B) + where + B: Fn() -> () + 'static, + { + self.back.replace(Some(Box::new(back))); + } + + pub fn set_recording_selected(&self, selected: S) + where + S: Fn(&RecordingDescription) -> () + 'static, + { + self.recording_list.set_selected(selected); + } +} diff --git a/src/screens/mod.rs b/src/screens/mod.rs new file mode 100644 index 0000000..8efd741 --- /dev/null +++ b/src/screens/mod.rs @@ -0,0 +1,11 @@ +pub mod ensemble_screen; +pub use ensemble_screen::*; + +pub mod person_screen; +pub use person_screen::*; + +pub mod work_screen; +pub use work_screen::*; + +pub mod recording_screen; +pub use recording_screen::*; diff --git a/src/screens/person_screen.rs b/src/screens/person_screen.rs new file mode 100644 index 0000000..0dc6b95 --- /dev/null +++ b/src/screens/person_screen.rs @@ -0,0 +1,146 @@ +use crate::backend::*; +use crate::database::*; +use crate::widgets::*; +use glib::clone; +use gtk::prelude::*; +use gtk_macros::get_widget; +use libhandy::HeaderBarExt; +use std::cell::RefCell; +use std::rc::Rc; + +pub struct PersonScreen { + pub widget: gtk::Box, + stack: gtk::Stack, + work_list: Rc>, + recording_list: Rc>, + back: RefCell () + 'static>>>, +} + +impl PersonScreen { + pub fn new(backend: Rc, person: Person) -> Rc { + let builder = gtk::Builder::from_resource("/de/johrpan/musicus_editor/ui/person_screen.ui"); + + get_widget!(builder, gtk::Box, widget); + get_widget!(builder, libhandy::HeaderBar, header); + get_widget!(builder, gtk::Button, back_button); + get_widget!(builder, gtk::MenuButton, menu_button); + get_widget!(builder, gtk::SearchEntry, search_entry); + get_widget!(builder, gtk::Stack, stack); + get_widget!(builder, gtk::Box, work_box); + get_widget!(builder, gtk::Frame, work_frame); + get_widget!(builder, gtk::Box, recording_box); + get_widget!(builder, gtk::Frame, recording_frame); + + header.set_title(Some(&person.name_fl())); + + let work_list = List::new( + |work: &WorkDescription| { + let label = gtk::Label::new(Some(&work.title)); + label.set_halign(gtk::Align::Start); + label.upcast() + }, + clone!(@strong search_entry => move |work: &WorkDescription| { + let search = search_entry.get_text().to_string().to_lowercase(); + let title = work.title.to_lowercase(); + search.is_empty() || title.contains(&search) + }), + "No works found.", + ); + + let recording_list = List::new( + |recording: &RecordingDescription| { + let work_label = gtk::Label::new(Some(&recording.work.get_title())); + + work_label.set_ellipsize(pango::EllipsizeMode::End); + work_label.set_halign(gtk::Align::Start); + + let performers_label = gtk::Label::new(Some(&recording.get_performers())); + performers_label.set_ellipsize(pango::EllipsizeMode::End); + performers_label.set_opacity(0.5); + performers_label.set_halign(gtk::Align::Start); + + let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); + vbox.add(&work_label); + vbox.add(&performers_label); + + vbox.upcast() + }, + clone!(@strong search_entry => move |recording: &RecordingDescription| { + let search = search_entry.get_text().to_string().to_lowercase(); + let text = recording.work.get_title() + &recording.get_performers(); + search.is_empty() || text.contains(&search) + }), + "No recordings found.", + ); + + work_frame.add(&work_list.widget); + recording_frame.add(&recording_list.widget); + + let result = Rc::new(Self { + widget, + stack, + work_list, + recording_list, + back: RefCell::new(None), + }); + + search_entry.connect_search_changed(clone!(@strong result => move |_| { + result.work_list.invalidate_filter(); + result.recording_list.invalidate_filter(); + })); + + back_button.connect_clicked(clone!(@strong result => move |_| { + if let Some(back) = &*result.back.borrow() { + back(); + } + })); + + let context = glib::MainContext::default(); + let clone = result.clone(); + context.spawn_local(async move { + let works = backend.get_work_descriptions(person.id).await.unwrap(); + let recordings = backend.get_recordings_for_person(person.id).await.unwrap(); + + if works.is_empty() && recordings.is_empty() { + clone.stack.set_visible_child_name("nothing"); + } else { + if works.is_empty() { + work_box.hide(); + } else { + clone.work_list.show_items(works); + } + + if recordings.is_empty() { + recording_box.hide(); + } else { + clone.recording_list.show_items(recordings); + } + + clone.stack.set_visible_child_name("content"); + } + }); + + result + } + + pub fn set_back(&self, back: B) + where + B: Fn() -> () + 'static, + { + self.back.replace(Some(Box::new(back))); + } + + pub fn set_work_selected(&self, selected: S) + where + S: Fn(&WorkDescription) -> () + 'static, + { + self.work_list.set_selected(selected); + } + + pub fn set_recording_selected(&self, selected: S) + where + S: Fn(&RecordingDescription) -> () + 'static, + { + self.recording_list.set_selected(selected); + } +} diff --git a/src/screens/recording_screen.rs b/src/screens/recording_screen.rs new file mode 100644 index 0000000..1159a58 --- /dev/null +++ b/src/screens/recording_screen.rs @@ -0,0 +1,48 @@ +use crate::backend::*; +use crate::database::*; +use glib::clone; +use gtk::prelude::*; +use gtk_macros::get_widget; +use libhandy::HeaderBarExt; +use std::cell::RefCell; +use std::rc::Rc; + +pub struct RecordingScreen { + pub widget: gtk::Box, + back: RefCell () + 'static>>>, +} + +impl RecordingScreen { + pub fn new(backend: Rc, recording: RecordingDescription) -> Rc { + let builder = + gtk::Builder::from_resource("/de/johrpan/musicus_editor/ui/recording_screen.ui"); + + get_widget!(builder, gtk::Box, widget); + get_widget!(builder, libhandy::HeaderBar, header); + get_widget!(builder, gtk::Button, back_button); + get_widget!(builder, gtk::MenuButton, menu_button); + + header.set_title(Some(&recording.work.get_title())); + header.set_subtitle(Some(&recording.get_performers())); + + let result = Rc::new(Self { + widget, + back: RefCell::new(None), + }); + + back_button.connect_clicked(clone!(@strong result => move |_| { + if let Some(back) = &*result.back.borrow() { + back(); + } + })); + + result + } + + pub fn set_back(&self, back: B) + where + B: Fn() -> () + 'static, + { + self.back.replace(Some(Box::new(back))); + } +} diff --git a/src/screens/work_screen.rs b/src/screens/work_screen.rs new file mode 100644 index 0000000..37b3a38 --- /dev/null +++ b/src/screens/work_screen.rs @@ -0,0 +1,107 @@ +use crate::backend::*; +use crate::database::*; +use crate::widgets::*; +use glib::clone; +use gtk::prelude::*; +use gtk_macros::get_widget; +use libhandy::HeaderBarExt; +use std::cell::RefCell; +use std::rc::Rc; + +pub struct WorkScreen { + pub widget: gtk::Box, + stack: gtk::Stack, + recording_list: Rc>, + back: RefCell () + 'static>>>, +} + +impl WorkScreen { + pub fn new(backend: Rc, work: WorkDescription) -> Rc { + let builder = gtk::Builder::from_resource("/de/johrpan/musicus_editor/ui/work_screen.ui"); + + get_widget!(builder, gtk::Box, widget); + get_widget!(builder, libhandy::HeaderBar, header); + get_widget!(builder, gtk::Button, back_button); + get_widget!(builder, gtk::MenuButton, menu_button); + get_widget!(builder, gtk::SearchEntry, search_entry); + get_widget!(builder, gtk::Stack, stack); + get_widget!(builder, gtk::Frame, recording_frame); + + header.set_title(Some(&work.title)); + header.set_subtitle(Some(&work.composer.name_fl())); + + let recording_list = List::new( + |recording: &RecordingDescription| { + let work_label = gtk::Label::new(Some(&recording.work.get_title())); + + work_label.set_ellipsize(pango::EllipsizeMode::End); + work_label.set_halign(gtk::Align::Start); + + let performers_label = gtk::Label::new(Some(&recording.get_performers())); + performers_label.set_ellipsize(pango::EllipsizeMode::End); + performers_label.set_opacity(0.5); + performers_label.set_halign(gtk::Align::Start); + + let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); + vbox.add(&work_label); + vbox.add(&performers_label); + + vbox.upcast() + }, + clone!(@strong search_entry => move |recording: &RecordingDescription| { + let search = search_entry.get_text().to_string().to_lowercase(); + let text = recording.work.get_title() + &recording.get_performers(); + search.is_empty() || text.contains(&search) + }), + "No recordings found.", + ); + + recording_frame.add(&recording_list.widget); + + let result = Rc::new(Self { + widget, + stack, + recording_list, + back: RefCell::new(None), + }); + + search_entry.connect_search_changed(clone!(@strong result => move |_| { + result.recording_list.invalidate_filter(); + })); + + back_button.connect_clicked(clone!(@strong result => move |_| { + if let Some(back) = &*result.back.borrow() { + back(); + } + })); + + let context = glib::MainContext::default(); + let clone = result.clone(); + context.spawn_local(async move { + let recordings = backend.get_recordings_for_work(work.id).await.unwrap(); + + if recordings.is_empty() { + clone.stack.set_visible_child_name("nothing"); + } else { + clone.recording_list.show_items(recordings); + clone.stack.set_visible_child_name("content"); + } + }); + + result + } + + pub fn set_back(&self, back: B) + where + B: Fn() -> () + 'static, + { + self.back.replace(Some(Box::new(back))); + } + + pub fn set_recording_selected(&self, selected: S) + where + S: Fn(&RecordingDescription) -> () + 'static, + { + self.recording_list.set_selected(selected); + } +} diff --git a/src/widgets/list.rs b/src/widgets/list.rs new file mode 100644 index 0000000..a77549f --- /dev/null +++ b/src/widgets/list.rs @@ -0,0 +1,90 @@ +use super::*; +use glib::clone; +use gtk::prelude::*; +use std::cell::RefCell; +use std::convert::TryInto; +use std::rc::Rc; + +pub struct List +where + T: 'static, +{ + pub widget: gtk::ListBox, + items: RefCell>, + make_widget: Box gtk::Widget + 'static>, + selected: RefCell () + 'static>>>, +} + +impl List +where + T: 'static, +{ + pub fn new(make_widget: M, filter: F, placeholder_text: &str) -> Rc + where + M: Fn(&T) -> gtk::Widget + 'static, + F: Fn(&T) -> bool + 'static, + { + let placeholder_label = gtk::Label::new(Some(placeholder_text)); + placeholder_label.set_margin_top(6); + placeholder_label.set_margin_bottom(6); + placeholder_label.set_margin_start(6); + placeholder_label.set_margin_end(6); + placeholder_label.show(); + + let widget = gtk::ListBox::new(); + widget.set_placeholder(Some(&placeholder_label)); + widget.show(); + + let result = Rc::new(Self { + widget, + items: RefCell::new(Vec::new()), + make_widget: Box::new(make_widget), + selected: RefCell::new(None), + }); + + result + .widget + .connect_row_activated(clone!(@strong result => move |_, row| { + if let Some(selected) = &*result.selected.borrow() { + let row = row.get_child().unwrap().downcast::().unwrap(); + let index: usize = row.get_index().try_into().unwrap(); + selected(&result.items.borrow()[index]); + } + })); + + result + .widget + .set_filter_func(Some(Box::new(clone!(@strong result => move |row| { + let row = row.get_child().unwrap().downcast::().unwrap(); + let index: usize = row.get_index().try_into().unwrap(); + filter(&result.items.borrow()[index]) + })))); + + result + } + + pub fn set_selected(&self, selected: S) + where + S: Fn(&T) -> () + 'static, + { + self.selected.replace(Some(Box::new(selected))); + } + + pub fn show_items(&self, items: Vec) { + self.items.replace(items); + + for child in self.widget.get_children() { + self.widget.remove(&child); + } + + for (index, item) in self.items.borrow().iter().enumerate() { + let row = SelectorRow::new(index.try_into().unwrap(), &(self.make_widget)(item)); + row.show_all(); + self.widget.insert(&row, -1); + } + } + + pub fn invalidate_filter(&self) { + self.widget.invalidate_filter(); + } +} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs new file mode 100644 index 0000000..353ebc4 --- /dev/null +++ b/src/widgets/mod.rs @@ -0,0 +1,14 @@ +pub mod list; +pub use list::*; + +pub mod person_list; +pub use person_list::*; + +pub mod poe_list; +pub use poe_list::*; + +pub mod selector_row; +pub use selector_row::*; + +pub mod stack; +pub use stack::*; diff --git a/src/widgets/person_list.rs b/src/widgets/person_list.rs new file mode 100644 index 0000000..4e283e8 --- /dev/null +++ b/src/widgets/person_list.rs @@ -0,0 +1,77 @@ +use super::*; +use crate::backend::Backend; +use crate::database::*; +use glib::clone; +use gtk::prelude::*; +use gtk_macros::get_widget; +use std::rc::Rc; + +pub struct PersonList { + pub widget: gtk::Box, + list: Rc>, + backend: Rc, + stack: gtk::Stack, +} + +impl PersonList { + pub fn new(backend: Rc) -> Rc { + let builder = gtk::Builder::from_resource("/de/johrpan/musicus_editor/ui/person_list.ui"); + + get_widget!(builder, gtk::Box, widget); + get_widget!(builder, gtk::SearchEntry, search_entry); + get_widget!(builder, gtk::Stack, stack); + get_widget!(builder, gtk::ScrolledWindow, scrolled_window); + + let list = List::new( + |person: &Person| { + let label = gtk::Label::new(Some(&person.name_lf())); + label.set_halign(gtk::Align::Start); + label.upcast() + }, + clone!(@strong search_entry => move |person: &Person| { + let search = search_entry.get_text().to_string().to_lowercase(); + let name = person.name_fl().to_lowercase(); + search.is_empty() || name.contains(&search) + }), + "No persons found.", + ); + + scrolled_window.add(&list.widget); + + let result = Rc::new(Self { + widget, + list, + backend, + stack, + }); + + search_entry.connect_search_changed(clone!(@strong result => move |_| { + result.list.invalidate_filter(); + })); + + result.clone().reload(); + + result + } + + pub fn set_selected(&self, selected: S) + where + S: Fn(&Person) -> () + 'static, + { + self.list.set_selected(selected); + } + + pub fn reload(self: Rc) { + self.stack.set_visible_child_name("loading"); + + let context = glib::MainContext::default(); + let backend = self.backend.clone(); + let list = self.list.clone(); + + context.spawn_local(async move { + let persons = backend.get_persons().await.unwrap(); + list.show_items(persons); + self.stack.set_visible_child_name("content"); + }); + } +} diff --git a/src/widgets/poe_list.rs b/src/widgets/poe_list.rs new file mode 100644 index 0000000..44b9cc3 --- /dev/null +++ b/src/widgets/poe_list.rs @@ -0,0 +1,104 @@ +use super::*; +use crate::backend::Backend; +use crate::database::*; +use glib::clone; +use gtk::prelude::*; +use gtk_macros::get_widget; +use std::rc::Rc; + +#[derive(Clone)] +pub enum PersonOrEnsemble { + Person(Person), + Ensemble(Ensemble), +} + +impl PersonOrEnsemble { + pub fn get_title(&self) -> String { + match self { + PersonOrEnsemble::Person(person) => person.name_lf(), + PersonOrEnsemble::Ensemble(ensemble) => ensemble.name.clone(), + } + } +} + +pub struct PoeList { + pub widget: gtk::Box, + list: Rc>, + backend: Rc, + stack: gtk::Stack, +} + +impl PoeList { + pub fn new(backend: Rc) -> Rc { + let builder = gtk::Builder::from_resource("/de/johrpan/musicus_editor/ui/poe_list.ui"); + + get_widget!(builder, gtk::Box, widget); + get_widget!(builder, gtk::SearchEntry, search_entry); + get_widget!(builder, gtk::Stack, stack); + get_widget!(builder, gtk::ScrolledWindow, scrolled_window); + + let list = List::new( + |poe: &PersonOrEnsemble| { + let label = gtk::Label::new(Some(&poe.get_title())); + label.set_halign(gtk::Align::Start); + label.upcast() + }, + clone!(@strong search_entry => move |poe: &PersonOrEnsemble| { + let search = search_entry.get_text().to_string().to_lowercase(); + let title = poe.get_title().to_lowercase(); + search.is_empty() || title.contains(&search) + }), + "No persons or ensembles found.", + ); + + scrolled_window.add(&list.widget); + + let result = Rc::new(Self { + widget, + list, + backend, + stack, + }); + + search_entry.connect_search_changed(clone!(@strong result => move |_| { + result.list.invalidate_filter(); + })); + + result.clone().reload(); + + result + } + + pub fn set_selected(&self, selected: S) + where + S: Fn(&PersonOrEnsemble) -> () + 'static, + { + self.list.set_selected(selected); + } + + pub fn reload(self: Rc) { + self.stack.set_visible_child_name("loading"); + + let context = glib::MainContext::default(); + let backend = self.backend.clone(); + let list = self.list.clone(); + + context.spawn_local(async move { + let persons = backend.get_persons().await.unwrap(); + let ensembles = backend.get_ensembles().await.unwrap(); + let mut poes: Vec = Vec::new(); + + for person in persons { + poes.push(PersonOrEnsemble::Person(person)); + } + + for ensemble in ensembles { + poes.push(PersonOrEnsemble::Ensemble(ensemble)); + } + + list.show_items(poes); + + self.stack.set_visible_child_name("content"); + }); + } +} diff --git a/src/dialogs/selector_row.rs b/src/widgets/selector_row.rs similarity index 100% rename from src/dialogs/selector_row.rs rename to src/widgets/selector_row.rs diff --git a/src/widgets/stack.rs b/src/widgets/stack.rs new file mode 100644 index 0000000..2dd0fa4 --- /dev/null +++ b/src/widgets/stack.rs @@ -0,0 +1,77 @@ +use glib::clone; +use gtk::prelude::*; +use std::cell::RefCell; + +pub struct Stack { + pub widget: gtk::Stack, + old_children: RefCell>, + current_child: RefCell>, +} + +impl Stack { + pub fn new(empty_screen: &W) -> Self + where + W: IsA, + { + let old_children = RefCell::new(Vec::new()); + + let widget = gtk::Stack::new(); + widget.set_transition_type(gtk::StackTransitionType::Crossfade); + widget.set_hexpand(true); + widget.add_named(empty_screen, "empty_screen"); + + unsafe { + widget.connect_notify_unsafe( + Some("transition-running"), + clone!(@strong old_children => move |stack, _| { + for child in old_children.borrow().iter() { + stack.remove(child); + } + + old_children.borrow_mut().clear(); + }), + ); + } + + widget.show(); + + Self { + widget: widget.clone(), + old_children, + current_child: RefCell::new(None), + } + } + + pub fn set_child(&self, child: W) + where + W: IsA, + { + if let Some(child) = self.current_child.borrow_mut().take() { + self.old_children.borrow_mut().push(child); + } + + self.current_child.replace(Some(child.clone().upcast())); + self.widget.add(&child); + self.widget.set_visible_child(&child); + + if !self.widget.get_transition_running() { + for child in self.old_children.borrow().iter() { + self.widget.remove(child); + } + + self.old_children.borrow_mut().clear(); + } + } + + pub fn reset_child(&self) { + self.widget.set_visible_child_name("empty_screen"); + + if !self.widget.get_transition_running() { + for child in self.old_children.borrow().iter() { + self.widget.remove(child); + } + + self.old_children.borrow_mut().clear(); + } + } +} diff --git a/src/window.rs b/src/window.rs index c0d36d3..2581ced 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1,152 +1,125 @@ -use super::backend::Backend; -use super::database::*; -use super::dialogs::*; +use crate::backend::*; +use crate::dialogs::*; +use crate::screens::*; +use crate::widgets::*; use gio::prelude::*; use glib::clone; use gtk::prelude::*; use gtk_macros::{action, get_widget}; use libhandy::prelude::*; -use libhandy::HeaderBarExt; -use std::cell::{Cell, RefCell}; -use std::convert::TryInto; use std::rc::Rc; -#[derive(Clone)] -enum PersonOrEnsemble { - Person(Person), - Ensemble(Ensemble), -} - -impl PersonOrEnsemble { - pub fn get_title(&self) -> String { - match self { - PersonOrEnsemble::Person(person) => person.name_lf(), - PersonOrEnsemble::Ensemble(ensemble) => ensemble.name.clone(), - } - } -} - -#[derive(Clone)] -enum WindowState { - Loading, - Selection(Vec), - OverviewScreenLoading(PersonOrEnsemble), - OverviewScreen( - PersonOrEnsemble, - Vec, - Vec, - String, - ), - WorkScreenLoading(PersonOrEnsemble, WorkDescription), - WorkScreen( - PersonOrEnsemble, - WorkDescription, - Vec, - String, - ), - RecordingScreenLoading(PersonOrEnsemble, RecordingDescription), -} - pub struct Window { - window: libhandy::ApplicationWindow, - state: RefCell, backend: Rc, + window: libhandy::ApplicationWindow, leaflet: libhandy::Leaflet, - sidebar_stack: gtk::Stack, - person_search_entry: gtk::SearchEntry, - sidebar_list: gtk::ListBox, - main_stack: gtk::Stack, - overview_header: libhandy::HeaderBar, - overview_header_menu_button: gtk::MenuButton, - overview_search_entry: gtk::SearchEntry, - overview_stack: gtk::Stack, - overview_work_box: gtk::Box, - overview_work_list: gtk::ListBox, - overview_recording_box: gtk::Box, - overview_recording_list: gtk::ListBox, - work_details_header: libhandy::HeaderBar, - work_details_stack: gtk::Stack, - work_details_recording_list: gtk::ListBox, - recording_details_header: libhandy::HeaderBar, - recording_details_stack: gtk::Stack, - sidebar_list_row_activated_handler_id: Cell>, - overview_work_list_row_activated_handler_id: Cell>, - overview_recording_list_row_activated_handler_id: Cell>, - work_details_recording_list_row_activated_handler_id: Cell>, + sidebar_box: gtk::Box, + poe_list: Rc, + stack: Stack, } impl Window { pub fn new(app: >k::Application) -> Rc { - use WindowState::*; - let builder = gtk::Builder::from_resource("/de/johrpan/musicus_editor/ui/window.ui"); get_widget!(builder, libhandy::ApplicationWindow, window); get_widget!(builder, libhandy::Leaflet, leaflet); - get_widget!(builder, gtk::SearchEntry, person_search_entry); - get_widget!(builder, gtk::Stack, sidebar_stack); - get_widget!(builder, gtk::ListBox, sidebar_list); - get_widget!(builder, gtk::Stack, main_stack); - get_widget!(builder, libhandy::HeaderBar, overview_header); - get_widget!(builder, gtk::MenuButton, overview_header_menu_button); - get_widget!(builder, gtk::SearchEntry, overview_search_entry); - get_widget!(builder, gtk::Stack, overview_stack); - get_widget!(builder, gtk::Box, overview_work_box); - get_widget!(builder, gtk::ListBox, overview_work_list); - get_widget!(builder, gtk::Box, overview_recording_box); - get_widget!(builder, gtk::ListBox, overview_recording_list); - get_widget!(builder, libhandy::HeaderBar, work_details_header); - get_widget!(builder, gtk::Button, work_details_back_button); - get_widget!(builder, gtk::Stack, work_details_stack); - get_widget!(builder, gtk::ListBox, work_details_recording_list); - get_widget!(builder, libhandy::HeaderBar, recording_details_header); - get_widget!(builder, gtk::Button, recording_details_back_button); - get_widget!(builder, gtk::Stack, recording_details_stack); + get_widget!(builder, gtk::Box, sidebar_box); + get_widget!(builder, gtk::Box, empty_screen); - let backend = Backend::new("test.sqlite"); + let backend = Rc::new(Backend::new("test.sqlite")); + let poe_list = PoeList::new(backend.clone()); + let stack = Stack::new(&empty_screen); - let result = Rc::new(Window { - window: window, - state: RefCell::new(Loading), - backend: Rc::new(backend), - leaflet: leaflet, - sidebar_stack: sidebar_stack, - sidebar_list: sidebar_list, - person_search_entry: person_search_entry, - main_stack: main_stack, - overview_header: overview_header, - overview_header_menu_button: overview_header_menu_button, - overview_search_entry: overview_search_entry, - overview_stack: overview_stack, - overview_work_box: overview_work_box, - overview_work_list: overview_work_list, - overview_recording_box: overview_recording_box, - overview_recording_list: overview_recording_list, - work_details_header: work_details_header, - work_details_stack: work_details_stack, - work_details_recording_list: work_details_recording_list, - recording_details_header: recording_details_header, - recording_details_stack: recording_details_stack, - sidebar_list_row_activated_handler_id: Cell::new(None), - overview_work_list_row_activated_handler_id: Cell::new(None), - overview_recording_list_row_activated_handler_id: Cell::new(None), - work_details_recording_list_row_activated_handler_id: Cell::new(None), + let result = Rc::new(Self { + backend, + window, + leaflet, + sidebar_box, + poe_list, + stack, }); - action!( - result.window, - "back", - clone!(@strong result => move |_, _| { - result.back(); - }) - ); + result + .poe_list + .set_selected(clone!(@strong result => move |poe| { + result.leaflet.set_visible_child(&result.stack.widget); + match poe { + PersonOrEnsemble::Person(person) => { + let person_screen = Rc::new(PersonScreen::new(result.backend.clone(), person.clone())); + + person_screen.set_back(clone!(@strong result => move || { + result.leaflet.set_visible_child(&result.sidebar_box); + result.stack.reset_child(); + })); + + person_screen.set_work_selected(clone!(@strong result, @strong person_screen => move |work| { + let work_screen = Rc::new(WorkScreen::new(result.backend.clone(), work.clone())); + + work_screen.set_back(clone!(@strong result, @strong person_screen => move || { + result.stack.set_child(person_screen.widget.clone()); + })); + + work_screen.set_recording_selected(clone!(@strong result, @strong work_screen => move |recording| { + let recording_screen = RecordingScreen::new(result.backend.clone(), recording.clone()); + + recording_screen.set_back(clone!(@strong result, @strong work_screen => move || { + result.stack.set_child(work_screen.widget.clone()); + })); + + result.stack.set_child(recording_screen.widget.clone()); + })); + + result.stack.set_child(work_screen.widget.clone()); + })); + + person_screen.set_recording_selected(clone!(@strong result, @strong person_screen => move |recording| { + let recording_screen = Rc::new(RecordingScreen::new(result.backend.clone(), recording.clone())); + + recording_screen.set_back(clone!(@strong result, @strong person_screen => move || { + result.stack.set_child(person_screen.widget.clone()); + })); + + result.stack.set_child(recording_screen.widget.clone()); + })); + + result.stack.set_child(person_screen.widget.clone()); + } + PersonOrEnsemble::Ensemble(ensemble) => { + let ensemble_screen = EnsembleScreen::new(result.backend.clone(), ensemble.clone()); + + ensemble_screen.set_back(clone!(@strong result => move || { + result.leaflet.set_visible_child(&result.sidebar_box); + result.stack.reset_child(); + })); + + ensemble_screen.set_recording_selected(clone!(@strong result, @strong ensemble_screen => move |recording| { + let recording_screen = Rc::new(RecordingScreen::new(result.backend.clone(), recording.clone())); + + recording_screen.set_back(clone!(@strong result, @strong ensemble_screen => move || { + result.stack.set_child(ensemble_screen.widget.clone()); + })); + + result.stack.set_child(recording_screen.widget.clone()); + })); + + result.stack.set_child(ensemble_screen.widget.clone()); + } + } + })); + + result.leaflet.add(&result.stack.widget); + result + .sidebar_box + .pack_start(&result.poe_list.widget, true, true, 0); + result.window.set_application(Some(app)); action!( result.window, "add-person", clone!(@strong result => move |_, _| { PersonEditor::new(result.backend.clone(), &result.window, None, clone!(@strong result => move |_| { - result.clone().set_state(Loading); + result.reload(); })).show(); }) ); @@ -166,7 +139,7 @@ impl Window { "add-work", clone!(@strong result => move |_, _| { WorkEditor::new(result.backend.clone(), &result.window, None, clone!(@strong result => move |_| { - result.clone().set_state(Loading); + result.reload(); })).show(); }) ); @@ -175,9 +148,9 @@ impl Window { result.window, "add-ensemble", clone!(@strong result => move |_, _| { - EnsembleEditor::new(result.backend.clone(), &result.window, None, |ensemble| { - println!("{:?}", ensemble); - }).show(); + EnsembleEditor::new(result.backend.clone(), &result.window, None, clone!(@strong result => move |_| { + result.reload(); + })).show(); }) ); @@ -186,7 +159,7 @@ impl Window { "add-recording", clone!(@strong result => move |_, _| { RecordingEditor::new(result.backend.clone(), &result.window, None, clone!(@strong result => move |_| { - result.clone().set_state(Loading); + result.reload(); })).show(); }) ); @@ -202,7 +175,7 @@ impl Window { c.spawn_local(async move { let person = result.backend.get_person(id).await.unwrap(); PersonEditor::new(result.backend.clone(), &result.window, Some(person), clone!(@strong result => move |_| { - result.clone().set_state(Loading); + result.reload(); })).show(); }); }) @@ -218,7 +191,7 @@ impl Window { let c = glib::MainContext::default(); c.spawn_local(async move { result.backend.delete_person(id).await.unwrap(); - result.clone().set_state(Loading); + result.reload(); }); }) ); @@ -234,7 +207,7 @@ impl Window { c.spawn_local(async move { let ensemble = result.backend.get_ensemble(id).await.unwrap(); EnsembleEditor::new(result.backend.clone(), &result.window, Some(ensemble), clone!(@strong result => move |_| { - result.clone().set_state(Loading); + result.reload(); })).show(); }); }) @@ -250,50 +223,11 @@ impl Window { let c = glib::MainContext::default(); c.spawn_local(async move { result.backend.delete_ensemble(id).await.unwrap(); - result.clone().set_state(Loading); + result.reload(); }); }) ); - result - .person_search_entry - .connect_search_changed(clone!(@strong result => move |_| { - result.sidebar_list.invalidate_filter(); - })); - - result.overview_search_entry.connect_search_changed(clone!(@strong result => move |_| { - match result.get_state() { - OverviewScreen(poe, works, recordings, _) => { - result.clone().set_state(OverviewScreen(poe, works.clone(), recordings.clone(), result.overview_search_entry.get_text().to_string())); - }, - _ => (), - } - })); - - work_details_back_button.connect_clicked(clone!(@strong result => move |_| { - match result.get_state() { - WorkScreenLoading(poe, _) => { - result.clone().set_state(OverviewScreenLoading(poe)); - }, - WorkScreen(poe, _, _, _) => { - result.clone().set_state(OverviewScreenLoading(poe)); - }, - _ => (), - } - })); - - recording_details_back_button.connect_clicked(clone!(@strong result => move |_| { - match result.get_state() { - RecordingScreenLoading(poe, _) => { - result.clone().set_state(OverviewScreenLoading(poe)); - }, - _ => (), - } - })); - - result.window.set_application(Some(app)); - result.clone().set_state(Loading); - result } @@ -301,385 +235,9 @@ impl Window { self.window.present(); } - fn get_state(&self) -> WindowState { - self.state.borrow().clone() - } - - fn set_state(self: Rc, state: WindowState) { - use WindowState::*; - - self.state.replace(state.clone()); - - match state { - Loading => { - let self_ = self.clone(); - let c = glib::MainContext::default(); - c.spawn_local(async move { - let persons = self_.backend.get_persons().await.unwrap(); - let ensembles = self_.backend.get_ensembles().await.unwrap(); - - let mut poes: Vec = Vec::new(); - - for person in &persons { - poes.push(PersonOrEnsemble::Person(person.clone())); - } - - for ensemble in &ensembles { - poes.push(PersonOrEnsemble::Ensemble(ensemble.clone())); - } - - self_.clone().set_state(Selection(poes)); - }); - - self.sidebar_stack.set_visible_child_name("loading"); - self.main_stack.set_visible_child_name("empty_screen"); - self.leaflet.set_visible_child_name("sidebar"); - } - Selection(poes) => { - for child in self.sidebar_list.get_children() { - self.sidebar_list.remove(&child); - } - - for (index, poe) in poes.iter().enumerate() { - let label = gtk::Label::new(Some(&poe.get_title())); - label.set_ellipsize(pango::EllipsizeMode::End); - label.set_halign(gtk::Align::Start); - let row = SelectorRow::new(index.try_into().unwrap(), &label); - row.show_all(); - self.sidebar_list.insert(&row, -1); - } - - match self.sidebar_list_row_activated_handler_id.take() { - Some(id) => self.sidebar_list.disconnect(id), - None => (), - } - - let handler_id = self.sidebar_list.connect_row_activated( - clone!(@strong self as self_, @strong poes => move |_, row| { - let row = row.get_child().unwrap().downcast::().unwrap(); - let index: usize = row.get_index().try_into().unwrap(); - let poe = poes[index].clone(); - self_.clone().set_state(OverviewScreenLoading(poe)); - }), - ); - - self.sidebar_list_row_activated_handler_id - .set(Some(handler_id)); - - self.sidebar_list.set_filter_func(Some(Box::new( - clone!(@strong self as self_, @strong poes => move |row| { - let row = row.get_child().unwrap().downcast::().unwrap(); - let index: usize = row.get_index().try_into().unwrap(); - let search = self_.person_search_entry.get_text().to_string().to_lowercase(); - - search.is_empty() || poes[index] - .get_title() - .to_lowercase() - .contains(&search) - }), - ))); - - self.sidebar_stack.set_visible_child_name("content"); - self.main_stack.set_visible_child_name("empty_screen"); - self.leaflet.set_visible_child_name("sidebar"); - } - OverviewScreenLoading(poe) => { - match poe.clone() { - PersonOrEnsemble::Person(person) => { - self.overview_header.set_title(Some(&person.name_fl())); - - let edit_menu_item = gio::MenuItem::new(Some("Edit person"), None); - edit_menu_item.set_action_and_target_value( - Some("win.edit-person"), - Some(&glib::Variant::from(person.id)), - ); - - let delete_menu_item = gio::MenuItem::new(Some("Delete person"), None); - delete_menu_item.set_action_and_target_value( - Some("win.delete-person"), - Some(&glib::Variant::from(person.id)), - ); - - let menu = gio::Menu::new(); - menu.append_item(&edit_menu_item); - menu.append_item(&delete_menu_item); - - self.overview_header_menu_button.set_menu_model(Some(&menu)); - - let self_ = self.clone(); - let c = glib::MainContext::default(); - c.spawn_local(async move { - let works = self_ - .backend - .get_work_descriptions(person.id) - .await - .unwrap(); - let recordings = self_ - .backend - .get_recordings_for_person(person.id) - .await - .unwrap(); - self_.clone().set_state(OverviewScreen( - poe.clone(), - works.clone(), - recordings, - String::from(""), - )); - }); - } - PersonOrEnsemble::Ensemble(ensemble) => { - self.overview_header.set_title(Some(&ensemble.name)); - - let edit_menu_item = gio::MenuItem::new(Some("Edit ensemble"), None); - edit_menu_item.set_action_and_target_value( - Some("win.edit-ensemble"), - Some(&glib::Variant::from(ensemble.id)), - ); - - let delete_menu_item = gio::MenuItem::new(Some("Delete ensemble"), None); - delete_menu_item.set_action_and_target_value( - Some("win.delete-ensemble"), - Some(&glib::Variant::from(ensemble.id)), - ); - - let menu = gio::Menu::new(); - menu.append_item(&edit_menu_item); - menu.append_item(&delete_menu_item); - - self.overview_header_menu_button.set_menu_model(Some(&menu)); - - let self_ = self.clone(); - let c = glib::MainContext::default(); - c.spawn_local(async move { - let recordings = self_ - .backend - .get_recordings_for_ensemble(ensemble.id) - .await - .unwrap(); - self_.clone().set_state(OverviewScreen( - poe.clone(), - Vec::new(), - recordings, - String::from(""), - )); - }); - } - } - - self.overview_search_entry.set_text(""); - - self.overview_stack.set_visible_child_name("loading"); - self.main_stack.set_visible_child_name("overview_screen"); - self.leaflet.set_visible_child_name("content"); - } - OverviewScreen(poe, works, recordings, search) => { - for child in self.overview_work_list.get_children() { - self.overview_work_list.remove(&child); - } - - for child in self.overview_recording_list.get_children() { - self.overview_recording_list.remove(&child); - } - - if works.is_empty() { - self.overview_work_box.hide(); - } else { - self.overview_work_box.show(); - } - - for (index, work) in works.iter().enumerate() { - if search.is_empty() || work.title.to_lowercase().contains(&search) { - let label = gtk::Label::new(Some(&work.title)); - label.set_ellipsize(pango::EllipsizeMode::End); - label.set_halign(gtk::Align::Start); - let row = SelectorRow::new(index.try_into().unwrap(), &label); - row.show_all(); - self.overview_work_list.insert(&row, -1); - } - } - - match self.overview_work_list_row_activated_handler_id.take() { - Some(id) => self.overview_work_list.disconnect(id), - None => (), - } - - let handler_id = self.overview_work_list.connect_row_activated( - clone!(@strong self as self_, @strong works, @strong poe => move |_, row| { - self_.overview_recording_list.unselect_all(); - - let row = row.get_child().unwrap().downcast::().unwrap(); - let index: usize = row.get_index().try_into().unwrap(); - let work = works[index].clone(); - - self_.clone().set_state(WorkScreenLoading(poe.clone(), work)); - }), - ); - - self.overview_work_list_row_activated_handler_id - .set(Some(handler_id)); - - if recordings.is_empty() { - self.overview_recording_box.hide(); - } else { - self.overview_recording_box.show(); - } - - for (index, recording) in recordings.iter().enumerate() { - let work_text = recording.work.get_title(); - let performers_text = recording.get_performers(); - - if search.is_empty() - || (work_text.to_lowercase().contains(&search) - || performers_text.to_lowercase().contains(&search)) - { - let work_label = gtk::Label::new(Some(&work_text)); - - work_label.set_ellipsize(pango::EllipsizeMode::End); - work_label.set_halign(gtk::Align::Start); - - let performers_label = gtk::Label::new(Some(&performers_text)); - performers_label.set_ellipsize(pango::EllipsizeMode::End); - performers_label.set_opacity(0.5); - performers_label.set_halign(gtk::Align::Start); - - let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); - vbox.add(&work_label); - vbox.add(&performers_label); - - let row = SelectorRow::new(index.try_into().unwrap(), &vbox); - row.show_all(); - self.overview_recording_list.insert(&row, -1); - } - } - - match self.overview_recording_list_row_activated_handler_id.take() { - Some(id) => self.overview_recording_list.disconnect(id), - None => (), - } - - let handler_id = self.overview_recording_list.connect_row_activated( - clone!(@strong self as self_, @strong recordings, @strong poe => move |_, row| { - self_.overview_work_list.unselect_all(); - - let row = row.get_child().unwrap().downcast::().unwrap(); - let index: usize = row.get_index().try_into().unwrap(); - let recording = recordings[index].clone(); - - self_.clone().set_state(RecordingScreenLoading(poe.clone(), recording)); - }), - ); - - self.overview_recording_list_row_activated_handler_id - .set(Some(handler_id)); - - self.overview_stack.set_visible_child_name("content"); - self.main_stack.set_visible_child_name("overview_screen"); - self.leaflet.set_visible_child_name("content"); - } - WorkScreenLoading(poe, work) => { - self.work_details_header - .set_title(Some(&work.composer.name_fl())); - self.work_details_header.set_subtitle(Some(&work.title)); - - let c = glib::MainContext::default(); - let self_ = self.clone(); - c.spawn_local(async move { - let recordings = self_ - .backend - .get_recordings_for_work(work.id) - .await - .unwrap(); - self_.clone().set_state(WorkScreen( - poe.clone(), - work.clone(), - recordings, - String::new(), - )); - }); - - self.work_details_stack.set_visible_child_name("loading"); - self.main_stack - .set_visible_child_name("work_details_screen"); - self.leaflet.set_visible_child_name("content"); - } - WorkScreen(poe, _, recordings, search) => { - for child in self.work_details_recording_list.get_children() { - self.work_details_recording_list.remove(&child); - } - - for (index, recording) in recordings.iter().enumerate() { - let work_text = recording.work.get_title(); - let performers_text = recording.get_performers(); - - if search.is_empty() - || (work_text.to_lowercase().contains(&search) - || performers_text.to_lowercase().contains(&search)) - { - let work_label = gtk::Label::new(Some(&work_text)); - - work_label.set_ellipsize(pango::EllipsizeMode::End); - work_label.set_halign(gtk::Align::Start); - - let performers_label = gtk::Label::new(Some(&performers_text)); - performers_label.set_ellipsize(pango::EllipsizeMode::End); - performers_label.set_opacity(0.5); - performers_label.set_halign(gtk::Align::Start); - - let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0); - vbox.add(&work_label); - vbox.add(&performers_label); - - let row = SelectorRow::new(index.try_into().unwrap(), &vbox); - row.show_all(); - self.work_details_recording_list.insert(&row, -1); - } - } - - match self - .work_details_recording_list_row_activated_handler_id - .take() - { - Some(id) => self.work_details_recording_list.disconnect(id), - None => (), - } - - let handler_id = self.work_details_recording_list.connect_row_activated( - clone!(@strong self as self_, @strong recordings, @strong poe => move |_, row| { - self_.overview_work_list.unselect_all(); - - let row = row.get_child().unwrap().downcast::().unwrap(); - let index: usize = row.get_index().try_into().unwrap(); - let recording = recordings[index].clone(); - - self_.clone().set_state(RecordingScreenLoading(poe.clone(), recording)); - }), - ); - - self.work_details_recording_list_row_activated_handler_id - .set(Some(handler_id)); - - self.work_details_stack.set_visible_child_name("content"); - self.main_stack - .set_visible_child_name("work_details_screen"); - self.leaflet.set_visible_child_name("content"); - } - RecordingScreenLoading(_, recording) => { - self.recording_details_header - .set_title(Some(&recording.work.get_title())); - self.recording_details_header - .set_subtitle(Some(&recording.get_performers())); - - self.recording_details_stack - .set_visible_child_name("loading"); - self.main_stack - .set_visible_child_name("recording_details_screen"); - self.leaflet.set_visible_child_name("content"); - } - } - } - - fn back(&self) { - self.main_stack.set_visible_child_name("empty_screen"); - self.leaflet.set_visible_child_name("sidebar"); + fn reload(&self) { + self.poe_list.clone().reload(); + self.stack.reset_child(); + self.leaflet.set_visible_child(&self.sidebar_box); } }