diff --git a/src/editors/ensemble.rs b/src/editors/ensemble.rs index 6415f66..1d2267e 100644 --- a/src/editors/ensemble.rs +++ b/src/editors/ensemble.rs @@ -1,7 +1,8 @@ use crate::backend::Backend; use crate::database::generate_id; use crate::database::Ensemble; -use crate::widgets::{Editor, EntryRow, Navigator, NavigatorScreen, Section, UploadSection}; +use crate::navigator::{NavigationHandle, Screen}; +use crate::widgets::{Editor, EntryRow, Section, UploadSection, Widget}; use anyhow::Result; use gettextrs::gettext; use glib::clone; @@ -11,7 +12,7 @@ use std::rc::Rc; /// A dialog for creating or editing a ensemble. pub struct EnsembleEditor { - backend: Rc, + handle: NavigationHandle, /// The ID of the ensemble that is edited or a newly generated one. id: String, @@ -19,15 +20,13 @@ pub struct EnsembleEditor { editor: Editor, name: EntryRow, upload: UploadSection, - saved_cb: RefCell ()>>>, - navigator: RefCell>>, } -impl EnsembleEditor { +impl Screen, Ensemble> for EnsembleEditor { /// Create a new ensemble editor and optionally initialize it. - pub fn new(backend: Rc, ensemble: Option) -> Rc { + fn new(ensemble: Option, handle: NavigationHandle) -> Rc { let editor = Editor::new(); - editor.set_title("Ensemble"); + editor.set_title("Ensemble/Role"); let list = gtk::ListBoxBuilder::new() .selection_mode(gtk::SelectionMode::None) @@ -51,55 +50,41 @@ impl EnsembleEditor { }; let this = Rc::new(Self { - backend, + handle, id, editor, name, upload, - saved_cb: RefCell::new(None), - navigator: RefCell::new(None), }); // Connect signals and callbacks - this.editor.set_back_cb(clone!(@strong this => move || { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + this.editor.set_back_cb(clone!(@weak this => move || { + this.handle.pop(None); })); - this.editor.set_save_cb(clone!(@strong this => move || { - let context = glib::MainContext::default(); - let clone = this.clone(); - context.spawn_local(async move { - clone.editor.loading(); - match clone.clone().save().await { - Ok(_) => { - let navigator = clone.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + this.editor.set_save_cb(clone!(@weak this => move || { + spawn!(@clone this, async move { + this.editor.loading(); + match this.save().await { + Ok(ensemble) => { + this.handle.pop(Some(ensemble)); } Err(err) => { let description = gettext!("Cause: {}", err); - clone.editor.error(&gettext("Failed to save ensemble!"), &description); + this.editor.error(&gettext("Failed to save ensemble!"), &description); } } - }); })); this } +} - /// Set the closure to be called if the ensemble was saved. - pub fn set_saved_cb () + 'static>(&self, cb: F) { - self.saved_cb.replace(Some(Box::new(cb))); - } - +impl EnsembleEditor { /// Save the ensemble and possibly upload it to the server. - async fn save(self: Rc) -> Result<()> { + async fn save(&self) -> Result { let name = self.name.get_text(); let ensemble = Ensemble { @@ -108,31 +93,19 @@ impl EnsembleEditor { }; if self.upload.get_active() { - self.backend.post_ensemble(&ensemble).await?; + self.handle.backend.post_ensemble(&ensemble).await?; } - self.backend.db().update_ensemble(ensemble.clone()).await?; - self.backend.library_changed(); + self.handle.backend.db().update_ensemble(ensemble.clone()).await?; + self.handle.backend.library_changed(); - if let Some(cb) = &*self.saved_cb.borrow() { - cb(ensemble.clone()); - } - - Ok(()) + Ok(ensemble) } } -impl NavigatorScreen for EnsembleEditor { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - +impl Widget for EnsembleEditor { fn get_widget(&self) -> gtk::Widget { self.editor.widget.clone().upcast() } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } } diff --git a/src/editors/instrument.rs b/src/editors/instrument.rs index 4f7a058..03ee26c 100644 --- a/src/editors/instrument.rs +++ b/src/editors/instrument.rs @@ -1,7 +1,8 @@ use crate::backend::Backend; use crate::database::generate_id; use crate::database::Instrument; -use crate::widgets::{Editor, EntryRow, Navigator, NavigatorScreen, Section, UploadSection}; +use crate::navigator::{NavigationHandle, Screen}; +use crate::widgets::{Editor, EntryRow, Section, UploadSection, Widget}; use anyhow::Result; use gettextrs::gettext; use glib::clone; @@ -11,7 +12,7 @@ use std::rc::Rc; /// A dialog for creating or editing a instrument. pub struct InstrumentEditor { - backend: Rc, + handle: NavigationHandle, /// The ID of the instrument that is edited or a newly generated one. id: String, @@ -19,13 +20,11 @@ pub struct InstrumentEditor { editor: Editor, name: EntryRow, upload: UploadSection, - saved_cb: RefCell ()>>>, - navigator: RefCell>>, } -impl InstrumentEditor { +impl Screen, Instrument> for InstrumentEditor { /// Create a new instrument editor and optionally initialize it. - pub fn new(backend: Rc, instrument: Option) -> Rc { + fn new(instrument: Option, handle: NavigationHandle) -> Rc { let editor = Editor::new(); editor.set_title("Instrument/Role"); @@ -51,55 +50,41 @@ impl InstrumentEditor { }; let this = Rc::new(Self { - backend, + handle, id, editor, name, upload, - saved_cb: RefCell::new(None), - navigator: RefCell::new(None), }); // Connect signals and callbacks - this.editor.set_back_cb(clone!(@strong this => move || { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + this.editor.set_back_cb(clone!(@weak this => move || { + this.handle.pop(None); })); - this.editor.set_save_cb(clone!(@strong this => move || { - let context = glib::MainContext::default(); - let clone = this.clone(); - context.spawn_local(async move { - clone.editor.loading(); - match clone.clone().save().await { - Ok(_) => { - let navigator = clone.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + this.editor.set_save_cb(clone!(@weak this => move || { + spawn!(@clone this, async move { + this.editor.loading(); + match this.save().await { + Ok(instrument) => { + this.handle.pop(Some(instrument)); } Err(err) => { let description = gettext!("Cause: {}", err); - clone.editor.error(&gettext("Failed to save instrument!"), &description); + this.editor.error(&gettext("Failed to save instrument!"), &description); } } - }); })); this } +} - /// Set the closure to be called if the instrument was saved. - pub fn set_saved_cb () + 'static>(&self, cb: F) { - self.saved_cb.replace(Some(Box::new(cb))); - } - +impl InstrumentEditor { /// Save the instrument and possibly upload it to the server. - async fn save(self: Rc) -> Result<()> { + async fn save(&self) -> Result { let name = self.name.get_text(); let instrument = Instrument { @@ -108,31 +93,19 @@ impl InstrumentEditor { }; if self.upload.get_active() { - self.backend.post_instrument(&instrument).await?; + self.handle.backend.post_instrument(&instrument).await?; } - self.backend.db().update_instrument(instrument.clone()).await?; - self.backend.library_changed(); + self.handle.backend.db().update_instrument(instrument.clone()).await?; + self.handle.backend.library_changed(); - if let Some(cb) = &*self.saved_cb.borrow() { - cb(instrument.clone()); - } - - Ok(()) + Ok(instrument) } } -impl NavigatorScreen for InstrumentEditor { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - +impl Widget for InstrumentEditor { fn get_widget(&self) -> gtk::Widget { self.editor.widget.clone().upcast() } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } } diff --git a/src/editors/performance.rs b/src/editors/performance.rs index 80cd7e4..9a5b8ea 100644 --- a/src/editors/performance.rs +++ b/src/editors/performance.rs @@ -1,7 +1,8 @@ use crate::backend::Backend; use crate::database::*; +use crate::navigator::{NavigationHandle, Screen}; use crate::selectors::{EnsembleSelector, InstrumentSelector, PersonSelector}; -use crate::widgets::{Editor, Navigator, NavigatorScreen, Section, ButtonRow, Widget}; +use crate::widgets::{Editor, Section, ButtonRow, Widget}; use gettextrs::gettext; use glib::clone; use gtk::prelude::*; @@ -11,7 +12,7 @@ use std::rc::Rc; /// A dialog for editing a performance within a recording. pub struct PerformanceEditor { - backend: Rc, + handle: NavigationHandle, editor: Editor, person_row: ButtonRow, ensemble_row: ButtonRow, @@ -20,13 +21,11 @@ pub struct PerformanceEditor { person: RefCell>, ensemble: RefCell>, role: RefCell>, - selected_cb: RefCell ()>>>, - navigator: RefCell>>, } -impl PerformanceEditor { +impl Screen, Performance> for PerformanceEditor { /// Create a new performance editor. - pub fn new(backend: Rc, performance: Option) -> Rc { + fn new(performance: Option, handle: NavigationHandle) -> Rc { let editor = Editor::new(); editor.set_title("Performance"); editor.set_may_save(false); @@ -68,7 +67,7 @@ impl PerformanceEditor { editor.add_content(&role_section); let this = Rc::new(PerformanceEditor { - backend, + handle, editor, person_row, ensemble_row, @@ -77,79 +76,51 @@ impl PerformanceEditor { person: RefCell::new(None), ensemble: RefCell::new(None), role: RefCell::new(None), - selected_cb: RefCell::new(None), - navigator: RefCell::new(None), }); - this.editor.set_back_cb(clone!(@strong this => move || { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + this.editor.set_back_cb(clone!(@weak this => move || { + this.handle.pop(None); })); this.editor.set_save_cb(clone!(@weak this => move || { - if let Some(cb) = &*this.selected_cb.borrow() { - cb(Performance { - person: this.person.borrow().clone(), - ensemble: this.ensemble.borrow().clone(), - role: this.role.borrow().clone(), - }); - } + let performance = Performance { + person: this.person.borrow().clone(), + ensemble: this.ensemble.borrow().clone(), + role: this.role.borrow().clone(), + }; - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + this.handle.pop(Some(performance)); })); this.person_row.set_cb(clone!(@weak this => move || { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let selector = PersonSelector::new(this.backend.clone()); - - selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| { + spawn!(@clone this, async move { + if let Some(person) = push!(this.handle, PersonSelector).await { this.show_person(Some(&person)); this.person.replace(Some(person.clone())); this.show_ensemble(None); this.ensemble.replace(None); - navigator.clone().pop(); - })); - - navigator.push(selector); - } + } + }); })); this.ensemble_row.set_cb(clone!(@weak this => move || { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let selector = EnsembleSelector::new(this.backend.clone()); - - selector.set_selected_cb(clone!(@strong this, @strong navigator => move |ensemble| { + spawn!(@clone this, async move { + if let Some(ensemble) = push!(this.handle, EnsembleSelector).await { this.show_person(None); this.person.replace(None); this.show_ensemble(Some(&ensemble)); - this.ensemble.replace(Some(ensemble.clone())); - navigator.clone().pop(); - })); - - navigator.push(selector); - } + this.ensemble.replace(None); + } + }); })); this.role_row.set_cb(clone!(@weak this => move || { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let selector = InstrumentSelector::new(this.backend.clone()); - - selector.set_selected_cb(clone!(@strong this, @strong navigator => move |role| { + spawn!(@clone this, async move { + if let Some(role) = push!(this.handle, InstrumentSelector).await { this.show_role(Some(&role)); - this.role.replace(Some(role.clone())); - navigator.clone().pop(); - })); - - navigator.push(selector); - } + this.role.replace(Some(role)); + } + }); })); this.reset_role_button.connect_clicked(clone!(@weak this => move |_| { @@ -176,12 +147,9 @@ impl PerformanceEditor { this } +} - /// Set a closure to be called when the user has chosen to save the performance. - pub fn set_selected_cb () + 'static>(&self, cb: F) { - self.selected_cb.replace(Some(Box::new(cb))); - } - +impl PerformanceEditor { /// Update the UI according to person. fn show_person(&self, person: Option<&Person>) { if let Some(person) = person { @@ -214,16 +182,8 @@ impl PerformanceEditor { } } -impl NavigatorScreen for PerformanceEditor { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - +impl Widget for PerformanceEditor { fn get_widget(&self) -> gtk::Widget { self.editor.widget.clone().upcast() } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } } diff --git a/src/editors/person.rs b/src/editors/person.rs index d3af4e9..b7b726b 100644 --- a/src/editors/person.rs +++ b/src/editors/person.rs @@ -1,7 +1,8 @@ use crate::backend::Backend; use crate::database::generate_id; use crate::database::Person; -use crate::widgets::{Editor, EntryRow, Navigator, NavigatorScreen, Section, UploadSection}; +use crate::navigator::{NavigationHandle, Screen}; +use crate::widgets::{Editor, EntryRow, Section, UploadSection, Widget}; use anyhow::Result; use gettextrs::gettext; use glib::clone; @@ -11,7 +12,7 @@ use std::rc::Rc; /// A dialog for creating or editing a person. pub struct PersonEditor { - backend: Rc, + handle: NavigationHandle, /// The ID of the person that is edited or a newly generated one. id: String, @@ -20,13 +21,11 @@ pub struct PersonEditor { first_name: EntryRow, last_name: EntryRow, upload: UploadSection, - saved_cb: RefCell ()>>>, - navigator: RefCell>>, } -impl PersonEditor { +impl Screen, Person> for PersonEditor { /// Create a new person editor and optionally initialize it. - pub fn new(backend: Rc, person: Option) -> Rc { + fn new(person: Option, handle: NavigationHandle) -> Rc { let editor = Editor::new(); editor.set_title("Person"); @@ -57,56 +56,42 @@ impl PersonEditor { }; let this = Rc::new(Self { - backend, + handle, id, editor, first_name, last_name, upload, - saved_cb: RefCell::new(None), - navigator: RefCell::new(None), }); // Connect signals and callbacks - this.editor.set_back_cb(clone!(@strong this => move || { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + this.editor.set_back_cb(clone!(@weak this => move || { + this.handle.pop(None); })); this.editor.set_save_cb(clone!(@strong this => move || { - let context = glib::MainContext::default(); - let clone = this.clone(); - context.spawn_local(async move { - clone.editor.loading(); - match clone.clone().save().await { - Ok(_) => { - let navigator = clone.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + spawn!(@clone this, async move { + this.editor.loading(); + match this.save().await { + Ok(person) => { + this.handle.pop(Some(person)); } Err(err) => { let description = gettext!("Cause: {}", err); - clone.editor.error(&gettext("Failed to save person!"), &description); + this.editor.error(&gettext("Failed to save person!"), &description); } } - }); })); this } +} - /// Set the closure to be called if the person was saved. - pub fn set_saved_cb () + 'static>(&self, cb: F) { - self.saved_cb.replace(Some(Box::new(cb))); - } - +impl PersonEditor { /// Save the person and possibly upload it to the server. - async fn save(self: Rc) -> Result<()> { + async fn save(self: &Rc) -> Result { let first_name = self.first_name.get_text(); let last_name = self.last_name.get_text(); @@ -117,31 +102,19 @@ impl PersonEditor { }; if self.upload.get_active() { - self.backend.post_person(&person).await?; + self.handle.backend.post_person(&person).await?; } - self.backend.db().update_person(person.clone()).await?; - self.backend.library_changed(); + self.handle.backend.db().update_person(person.clone()).await?; + self.handle.backend.library_changed(); - if let Some(cb) = &*self.saved_cb.borrow() { - cb(person.clone()); - } - - Ok(()) + Ok(person) } } -impl NavigatorScreen for PersonEditor { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - +impl Widget for PersonEditor { fn get_widget(&self) -> gtk::Widget { self.editor.widget.clone().upcast() } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } } diff --git a/src/editors/recording.rs b/src/editors/recording.rs index d3a7677..a187f13 100644 --- a/src/editors/recording.rs +++ b/src/editors/recording.rs @@ -1,8 +1,9 @@ use super::performance::PerformanceEditor; use crate::backend::Backend; use crate::database::*; -use crate::selectors::{PersonSelector, WorkSelector}; -use crate::widgets::{List, Navigator, NavigatorScreen}; +use crate::selectors::PersonSelector; +use crate::widgets::{List, Widget}; +use crate::navigator::{NavigationHandle, Screen}; use anyhow::Result; use gettextrs::gettext; use glib::clone; @@ -14,8 +15,8 @@ use std::rc::Rc; /// A widget for creating or editing a recording. pub struct RecordingEditor { - pub widget: gtk::Stack, - backend: Rc, + handle: NavigationHandle, + widget: gtk::Stack, save_button: gtk::Button, info_bar: gtk::InfoBar, work_row: libadwaita::ActionRow, @@ -25,13 +26,11 @@ pub struct RecordingEditor { id: String, work: RefCell>, performances: RefCell>, - selected_cb: RefCell ()>>>, - navigator: RefCell>>, } -impl RecordingEditor { +impl Screen, Recording> for RecordingEditor { /// Create a new recording editor widget and optionally initialize it. - pub fn new(backend: Rc, recording: Option) -> Rc { + fn new(recording: Option, handle: NavigationHandle) -> Rc { // Create UI let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/recording_editor.ui"); @@ -59,8 +58,8 @@ impl RecordingEditor { }; let this = Rc::new(RecordingEditor { + handle, widget, - backend, save_button, info_bar, work_row, @@ -70,107 +69,67 @@ impl RecordingEditor { id, work: RefCell::new(work), performances: RefCell::new(performances), - selected_cb: RefCell::new(None), - navigator: RefCell::new(None), }); // Connect signals and callbacks - back_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.clone().pop(); - } + back_button.connect_clicked(clone!(@weak this => move |_| { + this.handle.pop(None); })); - this.save_button - .connect_clicked(clone!(@strong this => move |_| { - let context = glib::MainContext::default(); - let clone = this.clone(); - context.spawn_local(async move { - clone.widget.set_visible_child_name("loading"); - match clone.clone().save().await { - Ok(_) => { - let navigator = clone.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.clone().pop(); - } - } - Err(_) => { - clone.info_bar.set_revealed(true); - clone.widget.set_visible_child_name("content"); - } + this.save_button.connect_clicked(clone!(@weak this => move |_| { + spawn!(@clone this, async move { + this.widget.set_visible_child_name("loading"); + match this.save().await { + Ok(recording) => { + this.handle.pop(Some(recording)); } - - }); - })); - - work_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let person_selector = PersonSelector::new(this.backend.clone()); - - person_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| { - let work_selector = WorkSelector::new(this.backend.clone(), person.clone()); - - work_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |work| { - this.work_selected(&work); - this.work.replace(Some(work.clone())); - - navigator.clone().pop(); - navigator.clone().pop(); - })); - - navigator.clone().push(work_selector); - })); - - navigator.push(person_selector); - } + Err(_) => { + this.info_bar.set_revealed(true); + this.widget.set_visible_child_name("content"); + } + } + }); })); - this.performance_list.set_make_widget_cb(clone!(@strong this => move |index| { + work_button.connect_clicked(clone!(@weak this => move |_| { + spawn!(@clone this, async move { + // TODO: We need the pushed screen to return a work here. + }); + })); + + this.performance_list.set_make_widget_cb(clone!(@weak this => move |index| { let performance = &this.performances.borrow()[index]; let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic")); delete_button.set_valign(gtk::Align::Center); - delete_button.connect_clicked(clone!(@strong this => move |_| { - let length = { - let mut performances = this.performances.borrow_mut(); - performances.remove(index); - performances.len() - }; + delete_button.connect_clicked(clone!(@weak this => move |_| { + let length = { + let mut performances = this.performances.borrow_mut(); + performances.remove(index); + performances.len() + }; - this.performance_list.update(length); + this.performance_list.update(length); })); let edit_button = gtk::Button::from_icon_name(Some("document-edit-symbolic")); edit_button.set_valign(gtk::Align::Center); - edit_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let performance = &this.performances.borrow()[index]; + edit_button.connect_clicked(clone!(@weak this => move |_| { + spawn!(@clone this, async move { + let performance = &this.performances.borrow()[index]; + if let Some(performance) = push!(this.handle, PerformanceEditor, Some(performance.to_owned())).await { + let length = { + let mut performances = this.performances.borrow_mut(); + performances[index] = performance; + performances.len() + }; - let editor = PerformanceEditor::new( - this.backend.clone(), - Some(performance.clone()), - ); - - editor.set_selected_cb(clone!(@strong this, @strong navigator => move |performance| { - let length = { - let mut performances = this.performances.borrow_mut(); - performances[index] = performance; - performances.len() - }; - - this.performance_list.update(length); - - navigator.clone().pop(); - })); - - navigator.push(editor); + this.performance_list.update(length); } + }); })); let row = libadwaita::ActionRow::new(); @@ -184,11 +143,8 @@ impl RecordingEditor { })); add_performer_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let editor = PerformanceEditor::new(this.backend.clone(), None); - - editor.set_selected_cb(clone!(@strong this, @strong navigator => move |performance| { + spawn!(@clone this, async move { + if let Some(performance) = push!(this.handle, PerformanceEditor, None).await { let length = { let mut performances = this.performances.borrow_mut(); performances.push(performance); @@ -196,12 +152,8 @@ impl RecordingEditor { }; this.performance_list.update(length); - - navigator.clone().pop(); - })); - - navigator.push(editor); - } + } + }); })); // Initialize @@ -215,12 +167,9 @@ impl RecordingEditor { this } +} - /// Set the closure to be called if the recording was created. - pub fn set_selected_cb () + 'static>(&self, cb: F) { - self.selected_cb.replace(Some(Box::new(cb))); - } - +impl RecordingEditor { /// Update the UI according to work. fn work_selected(&self, work: &Work) { self.work_row.set_title(Some(&gettext("Work"))); @@ -229,7 +178,7 @@ impl RecordingEditor { } /// Save the recording and possibly upload it to the server. - async fn save(self: Rc) -> Result<()> { + async fn save(self: &Rc) -> Result { let recording = Recording { id: self.id.clone(), work: self @@ -243,40 +192,23 @@ impl RecordingEditor { let upload = self.upload_switch.get_active(); if upload { - self.backend.post_recording(&recording).await?; + self.handle.backend.post_recording(&recording).await?; } - self.backend + self.handle.backend .db() .update_recording(recording.clone().into()) .await .unwrap(); - self.backend.library_changed(); + self.handle.backend.library_changed(); - if let Some(cb) = &*self.selected_cb.borrow() { - cb(recording.clone()); - } - - let navigator = self.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.clone().pop(); - } - - Ok(()) + Ok(recording) } } -impl NavigatorScreen for RecordingEditor { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - +impl Widget for RecordingEditor { fn get_widget(&self) -> gtk::Widget { self.widget.clone().upcast() } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } } diff --git a/src/editors/work.rs b/src/editors/work.rs index 1f3ebd2..9aa0fee 100644 --- a/src/editors/work.rs +++ b/src/editors/work.rs @@ -1,9 +1,9 @@ use super::work_part::WorkPartEditor; use super::work_section::WorkSectionEditor; -use crate::backend::Backend; use crate::database::*; use crate::selectors::{InstrumentSelector, PersonSelector}; -use crate::widgets::{List, Navigator, NavigatorScreen}; +use crate::navigator::{NavigationHandle, Screen}; +use crate::widgets::{List, Widget}; use anyhow::Result; use gettextrs::gettext; use glib::clone; @@ -32,8 +32,8 @@ impl PartOrSection { /// A widget for editing and creating works. pub struct WorkEditor { + handle: NavigationHandle, widget: gtk::Stack, - backend: Rc, save_button: gtk::Button, title_entry: gtk::Entry, info_bar: gtk::InfoBar, @@ -45,13 +45,11 @@ pub struct WorkEditor { composer: RefCell>, instruments: RefCell>, structure: RefCell>, - saved_cb: RefCell ()>>>, - navigator: RefCell>>, } -impl WorkEditor { +impl Screen, Work> for WorkEditor { /// Create a new work editor widget and optionally initialize it. - pub fn new(backend: Rc, work: Option) -> Rc { + fn new(work: Option, handle: NavigationHandle) -> Rc { // Create UI let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_editor.ui"); @@ -100,8 +98,8 @@ impl WorkEditor { }; let this = Rc::new(Self { + handle, widget, - backend, save_button, id, info_bar, @@ -113,57 +111,39 @@ impl WorkEditor { composer: RefCell::new(composer), instruments: RefCell::new(instruments), structure: RefCell::new(structure), - saved_cb: RefCell::new(None), - navigator: RefCell::new(None), }); // Connect signals and callbacks - back_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + back_button.connect_clicked(clone!(@weak this => move |_| { + this.handle.pop(None); })); - this.save_button - .connect_clicked(clone!(@strong this => move |_| { - let context = glib::MainContext::default(); - let clone = this.clone(); - context.spawn_local(async move { - clone.widget.set_visible_child_name("loading"); - match clone.clone().save().await { - Ok(_) => { - let navigator = clone.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } - } - Err(_) => { - clone.info_bar.set_revealed(true); - clone.widget.set_visible_child_name("content"); - } + this.save_button.connect_clicked(clone!(@weak this => move |_| { + spawn!(@clone this, async move { + this.widget.set_visible_child_name("loading"); + match this.save().await { + Ok(work) => { + this.handle.pop(Some(work)); } - - }); - })); - - composer_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let selector = PersonSelector::new(this.backend.clone()); - - selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| { - this.show_composer(person); - this.composer.replace(Some(person.clone())); - navigator.clone().pop(); - })); - - navigator.push(selector); - } + Err(_) => { + this.info_bar.set_revealed(true); + this.widget.set_visible_child_name("content"); + } + } + }); })); - this.instrument_list.set_make_widget_cb(clone!(@strong this => move |index| { + composer_button.connect_clicked(clone!(@weak this => move |_| { + spawn!(@clone this, async move { + if let Some(person) = push!(this.handle, PersonSelector).await { + this.show_composer(&person); + this.composer.replace(Some(person.to_owned())); + } + }); + })); + + this.instrument_list.set_make_widget_cb(clone!(@weak this => move |index| { let instrument = &this.instruments.borrow()[index]; let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic")); @@ -186,12 +166,9 @@ impl WorkEditor { row.upcast() })); - add_instrument_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let selector = InstrumentSelector::new(this.backend.clone()); - - selector.set_selected_cb(clone!(@strong this, @strong navigator => move |instrument| { + add_instrument_button.connect_clicked(clone!(@weak this => move |_| { + spawn!(@clone this, async move { + if let Some(instrument) = push!(this.handle, InstrumentSelector).await { let length = { let mut instruments = this.instruments.borrow_mut(); instruments.push(instrument.clone()); @@ -199,20 +176,17 @@ impl WorkEditor { }; this.instrument_list.update(length); - navigator.clone().pop(); - })); - - navigator.push(selector); - } + } + }); })); - this.part_list.set_make_widget_cb(clone!(@strong this => move |index| { + this.part_list.set_make_widget_cb(clone!(@weak this => move |index| { let pos = &this.structure.borrow()[index]; let delete_button = gtk::Button::from_icon_name(Some("user-trash-symbolic")); delete_button.set_valign(gtk::Align::Center); - delete_button.connect_clicked(clone!(@strong this => move |_| { + delete_button.connect_clicked(clone!(@weak this => move |_| { let length = { let mut structure = this.structure.borrow_mut(); structure.remove(index); @@ -225,14 +199,11 @@ impl WorkEditor { let edit_button = gtk::Button::from_icon_name(Some("document-edit-symbolic")); edit_button.set_valign(gtk::Align::Center); - edit_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { + edit_button.connect_clicked(clone!(@weak this => move |_| { + spawn!(@clone this, async move { match this.structure.borrow()[index].clone() { PartOrSection::Part(part) => { - let editor = WorkPartEditor::new(this.backend.clone(), Some(part)); - - editor.set_ready_cb(clone!(@strong this, @strong navigator => move |part| { + if let Some(part) = push!(this.handle, WorkPartEditor, Some(part)).await { let length = { let mut structure = this.structure.borrow_mut(); structure[index] = PartOrSection::Part(part); @@ -240,15 +211,10 @@ impl WorkEditor { }; this.part_list.update(length); - navigator.clone().pop(); - })); - - navigator.push(editor); + } } PartOrSection::Section(section) => { - let editor = WorkSectionEditor::new(Some(section)); - - editor.set_ready_cb(clone!(@strong this, @strong navigator => move |section| { + if let Some(section) = push!(this.handle, WorkSectionEditor, Some(section)).await { let length = { let mut structure = this.structure.borrow_mut(); structure[index] = PartOrSection::Section(section); @@ -256,13 +222,10 @@ impl WorkEditor { }; this.part_list.update(length); - navigator.clone().pop(); - })); - - navigator.push(editor); + } } } - } + }); })); let row = libadwaita::ActionRow::new(); @@ -280,7 +243,7 @@ impl WorkEditor { row.upcast() })); - this.part_list.set_move_cb(clone!(@strong this => move |old_index, new_index| { + this.part_list.set_move_cb(clone!(@weak this => move |old_index, new_index| { let length = { let mut structure = this.structure.borrow_mut(); structure.swap(old_index, new_index); @@ -290,12 +253,9 @@ impl WorkEditor { this.part_list.update(length); })); - add_part_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let editor = WorkPartEditor::new(this.backend.clone(), None); - - editor.set_ready_cb(clone!(@strong this, @strong navigator => move |part| { + add_part_button.connect_clicked(clone!(@weak this => move |_| { + spawn!(@clone this, async move { + if let Some(part) = push!(this.handle, WorkPartEditor, None).await { let length = { let mut structure = this.structure.borrow_mut(); structure.push(PartOrSection::Part(part)); @@ -303,19 +263,13 @@ impl WorkEditor { }; this.part_list.update(length); - navigator.clone().pop(); - })); - - navigator.push(editor); - } + } + }); })); add_section_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let editor = WorkSectionEditor::new(None); - - editor.set_ready_cb(clone!(@strong this, @strong navigator => move |section| { + spawn!(@clone this, async move { + if let Some(section) = push!(this.handle, WorkSectionEditor, None).await { let length = { let mut structure = this.structure.borrow_mut(); structure.push(PartOrSection::Section(section)); @@ -323,11 +277,8 @@ impl WorkEditor { }; this.part_list.update(length); - navigator.clone().pop(); - })); - - navigator.push(editor); - } + } + }); })); // Initialization @@ -341,12 +292,9 @@ impl WorkEditor { this } +} - /// The closure to call when a work was created. - pub fn set_saved_cb () + 'static>(&self, cb: F) { - self.saved_cb.replace(Some(Box::new(cb))); - } - +impl WorkEditor { /// Update the UI according to person. fn show_composer(&self, person: &Person) { self.composer_row.set_title(Some(&gettext("Composer"))); @@ -355,7 +303,7 @@ impl WorkEditor { } /// Save the work and possibly upload it to the server. - async fn save(self: Rc) -> Result<()> { + async fn save(self: &Rc) -> Result { let mut section_count: usize = 0; let mut parts = Vec::new(); let mut sections = Vec::new(); @@ -387,35 +335,23 @@ impl WorkEditor { let upload = self.upload_switch.get_active(); if upload { - self.backend.post_work(&work).await?; + self.handle.backend.post_work(&work).await?; } - self.backend + self.handle.backend .db() .update_work(work.clone().into()) .await .unwrap(); - self.backend.library_changed(); + self.handle.backend.library_changed(); - if let Some(cb) = &*self.saved_cb.borrow() { - cb(work.clone()); - } - - Ok(()) + Ok(work) } } -impl NavigatorScreen for WorkEditor { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - +impl Widget for WorkEditor { fn get_widget(&self) -> gtk::Widget { self.widget.clone().upcast() } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } } diff --git a/src/editors/work_part.rs b/src/editors/work_part.rs index 86255ab..5288457 100644 --- a/src/editors/work_part.rs +++ b/src/editors/work_part.rs @@ -1,7 +1,7 @@ -use crate::backend::Backend; use crate::database::*; use crate::selectors::PersonSelector; -use crate::widgets::{Navigator, NavigatorScreen}; +use crate::navigator::{NavigationHandle, Screen}; +use crate::widgets::Widget; use gettextrs::gettext; use glib::clone; use gtk::prelude::*; @@ -12,19 +12,17 @@ use std::rc::Rc; /// A dialog for creating or editing a work part. pub struct WorkPartEditor { - backend: Rc, + handle: NavigationHandle, widget: gtk::Box, title_entry: gtk::Entry, composer_row: libadwaita::ActionRow, reset_composer_button: gtk::Button, composer: RefCell>, - ready_cb: RefCell ()>>>, - navigator: RefCell>>, } -impl WorkPartEditor { +impl Screen, WorkPart> for WorkPartEditor { /// Create a new part editor and optionally initialize it. - pub fn new(backend: Rc, part: Option) -> Rc { + fn new(part: Option, handle: NavigationHandle) -> Rc { // Create UI let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_part_editor.ui"); @@ -46,53 +44,36 @@ impl WorkPartEditor { }; let this = Rc::new(Self { - backend, + handle, widget, title_entry, composer_row, reset_composer_button, composer: RefCell::new(composer), - ready_cb: RefCell::new(None), - navigator: RefCell::new(None), }); // Connect signals and callbacks - back_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + back_button.connect_clicked(clone!(@weak this => move |_| { + this.handle.pop(None); })); - save_button.connect_clicked(clone!(@strong this => move |_| { - if let Some(cb) = &*this.ready_cb.borrow() { - cb(WorkPart { - title: this.title_entry.get_text().unwrap().to_string(), - composer: this.composer.borrow().clone(), - }); - } + save_button.connect_clicked(clone!(@weak this => move |_| { + let part = WorkPart { + title: this.title_entry.get_text().unwrap().to_string(), + composer: this.composer.borrow().clone(), + }; - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + this.handle.pop(Some(part)); })); composer_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let selector = PersonSelector::new(this.backend.clone()); - - selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| { - this.show_composer(Some(person)); - this.composer.replace(Some(person.clone())); - navigator.clone().pop(); - })); - - navigator.push(selector); - } - + spawn!(@clone this, async move { + if let Some(person) = push!(this.handle, PersonSelector).await { + this.show_composer(Some(&person)); + this.composer.replace(Some(person.to_owned())); + } + }); })); this.reset_composer_button @@ -109,12 +90,9 @@ impl WorkPartEditor { this } +} - /// Set the closure to be called when the user wants to save the part. - pub fn set_ready_cb () + 'static>(&self, cb: F) { - self.ready_cb.replace(Some(Box::new(cb))); - } - +impl WorkPartEditor { /// Update the UI according to person. fn show_composer(&self, person: Option<&Person>) { if let Some(person) = person { @@ -129,16 +107,8 @@ impl WorkPartEditor { } } -impl NavigatorScreen for WorkPartEditor { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - +impl Widget for WorkPartEditor { fn get_widget(&self) -> gtk::Widget { self.widget.clone().upcast() } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } } diff --git a/src/editors/work_section.rs b/src/editors/work_section.rs index 7b09b26..a40cefc 100644 --- a/src/editors/work_section.rs +++ b/src/editors/work_section.rs @@ -1,5 +1,6 @@ use crate::database::*; -use crate::widgets::{Navigator, NavigatorScreen}; +use crate::navigator::{NavigationHandle, Screen}; +use crate::widgets::Widget; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; @@ -8,15 +9,14 @@ use std::rc::Rc; /// A dialog for creating or editing a work section. pub struct WorkSectionEditor { + handle: NavigationHandle, widget: gtk::Box, title_entry: gtk::Entry, - ready_cb: RefCell ()>>>, - navigator: RefCell>>, } -impl WorkSectionEditor { +impl Screen, WorkSection> for WorkSectionEditor { /// Create a new section editor and optionally initialize it. - pub fn new(section: Option) -> Rc { + fn new(section: Option, handle: NavigationHandle) -> Rc { // Create UI let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/work_section_editor.ui"); @@ -31,56 +31,32 @@ impl WorkSectionEditor { } let this = Rc::new(Self { + handle, widget, title_entry, - ready_cb: RefCell::new(None), - navigator: RefCell::new(None), }); // Connect signals and callbacks - back_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + back_button.connect_clicked(clone!(@weak this => move |_| { + this.handle.pop(None); })); - save_button.connect_clicked(clone!(@strong this => move |_| { - if let Some(cb) = &*this.ready_cb.borrow() { - cb(WorkSection { - before_index: 0, - title: this.title_entry.get_text().unwrap().to_string(), - }); - } + save_button.connect_clicked(clone!(@weak this => move |_| { + let section = WorkSection { + before_index: 0, + title: this.title_entry.get_text().unwrap().to_string(), + }; - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + this.handle.pop(Some(section)); })); this } - - /// Set the closure to be called when the user wants to save the section. Note that the - /// resulting object will always have `before_index` set to 0. The caller is expected to - /// change that later before adding the section to the database. - pub fn set_ready_cb () + 'static>(&self, cb: F) { - self.ready_cb.replace(Some(Box::new(cb))); - } } -impl NavigatorScreen for WorkSectionEditor { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - +impl Widget for WorkSectionEditor { fn get_widget(&self) -> gtk::Widget { self.widget.clone().upcast() } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } } diff --git a/src/import/medium_editor.rs b/src/import/medium_editor.rs index 95ddeb2..f4772e8 100644 --- a/src/import/medium_editor.rs +++ b/src/import/medium_editor.rs @@ -2,7 +2,8 @@ use super::source::Source; use super::track_set_editor::{TrackSetData, TrackSetEditor}; use crate::database::{generate_id, Medium, Track, TrackSet}; use crate::backend::Backend; -use crate::widgets::{List, Navigator, NavigatorScreen}; +use crate::navigator::{NavigationHandle, Screen}; +use crate::widgets::{List, Widget}; use anyhow::{anyhow, Result}; use glib::clone; use glib::prelude::*; @@ -14,7 +15,7 @@ use std::rc::Rc; /// A dialog for editing metadata while importing music into the music library. pub struct MediumEditor { - backend: Rc, + handle: NavigationHandle<()>, source: Rc>, widget: gtk::Stack, done_button: gtk::Button, @@ -24,12 +25,11 @@ pub struct MediumEditor { publish_switch: gtk::Switch, track_set_list: Rc, track_sets: RefCell>, - navigator: RefCell>>, } -impl MediumEditor { +impl Screen>, ()> for MediumEditor { /// Create a new medium editor. - pub fn new(backend: Rc, source: Rc>) -> Rc { + fn new(source: Rc>, handle: NavigationHandle<()>) -> Rc { // Create UI let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/medium_editor.ui"); @@ -48,7 +48,7 @@ impl MediumEditor { frame.set_child(Some(&list.widget)); let this = Rc::new(Self { - backend, + handle, source, widget, done_button, @@ -58,40 +58,30 @@ impl MediumEditor { publish_switch, track_set_list: list, track_sets: RefCell::new(Vec::new()), - navigator: RefCell::new(None), }); // Connect signals and callbacks - back_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + back_button.connect_clicked(clone!(@weak this => move |_| { + this.handle.pop(None); })); - this.done_button.connect_clicked(clone!(@strong this => move |_| { - let context = glib::MainContext::default(); - let clone = this.clone(); - context.spawn_local(async move { - clone.widget.set_visible_child_name("loading"); - match clone.clone().save().await { + this.done_button.connect_clicked(clone!(@weak this => move |_| { + this.widget.set_visible_child_name("loading"); + spawn!(@clone this, async move { + match this.save().await { Ok(_) => (), Err(err) => { + // TODO: Display errors. println!("{:?}", err); - // clone.info_bar.set_revealed(true); } } - }); })); - add_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let editor = TrackSetEditor::new(this.backend.clone(), Rc::clone(&this.source)); - - editor.set_done_cb(clone!(@strong this => move |track_set| { + add_button.connect_clicked(clone!(@weak this => move |_| { + spawn!(@clone this, async move { + if let Some(track_set) = push!(this.handle, TrackSetEditor, Rc::clone(&this.source)).await { let length = { let mut track_sets = this.track_sets.borrow_mut(); track_sets.push(track_set); @@ -99,13 +89,11 @@ impl MediumEditor { }; this.track_set_list.update(length); - })); - - navigator.push(editor); - } + } + }); })); - this.track_set_list.set_make_widget_cb(clone!(@strong this => move |index| { + this.track_set_list.set_make_widget_cb(clone!(@weak this => move |index| { let track_set = &this.track_sets.borrow()[index]; let title = track_set.recording.work.get_title(); @@ -124,39 +112,38 @@ impl MediumEditor { row.add_suffix(&edit_button); row.set_activatable_widget(Some(&edit_button)); - edit_button.connect_clicked(clone!(@strong this => move |_| { - + edit_button.connect_clicked(clone!(@weak this => move |_| { + // TODO: Implement editing. })); row.upcast() })); - // Copy the source in the background. - let context = glib::MainContext::default(); - let clone = this.clone(); - context.spawn_local(async move { - match clone.source.copy().await { + spawn!(@clone this, async move { + match this.source.copy().await { Err(error) => { // TODO: Present error. println!("Failed to copy source: {}", error); }, Ok(_) => { - clone.done_stack.set_visible_child(&clone.done); - clone.done_button.set_sensitive(true); + this.done_stack.set_visible_child(&this.done); + this.done_button.set_sensitive(true); } } }); this } +} +impl MediumEditor { /// Save the medium and possibly upload it to the server. - async fn save(self: Rc) -> Result<()> { + async fn save(&self) -> Result<()> { let name = self.name_entry.get_text().unwrap().to_string(); // Create a new directory in the music library path for the imported medium. - let mut path = self.backend.get_music_library_path().unwrap().clone(); + let mut path = self.handle.backend.get_music_library_path().unwrap().clone(); path.push(&name); std::fs::create_dir(&path)?; @@ -205,35 +192,22 @@ impl MediumEditor { let upload = self.publish_switch.get_active(); if upload { - self.backend.post_medium(&medium).await?; + self.handle.backend.post_medium(&medium).await?; } - self.backend + self.handle.backend .db() .update_medium(medium.clone()) .await?; - self.backend.library_changed(); - - let navigator = self.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.clone().pop(); - } + self.handle.backend.library_changed(); Ok(()) } } -impl NavigatorScreen for MediumEditor { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - +impl Widget for MediumEditor { fn get_widget(&self) -> gtk::Widget { self.widget.clone().upcast() } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } } diff --git a/src/import/source_selector.rs b/src/import/source_selector.rs index da95ba3..49e2703 100644 --- a/src/import/source_selector.rs +++ b/src/import/source_selector.rs @@ -3,7 +3,8 @@ use super::disc_source::DiscSource; use super::folder_source::FolderSource; use super::source::Source; use crate::backend::Backend; -use crate::widgets::{Navigator, NavigatorScreen}; +use crate::navigator::{NavigationHandle, Screen}; +use crate::widgets::Widget; use gettextrs::gettext; use glib::clone; use gtk::prelude::*; @@ -14,16 +15,15 @@ use std::rc::Rc; /// A dialog for starting to import music. pub struct SourceSelector { - backend: Rc, + handle: NavigationHandle<()>, widget: gtk::Box, stack: gtk::Stack, info_bar: gtk::InfoBar, - navigator: RefCell>>, } -impl SourceSelector { +impl Screen<(), ()> for SourceSelector { /// Create a new source selector. - pub fn new(backend: Rc) -> Rc { + fn new(_: (), handle: NavigationHandle<()>) -> Rc { // Create UI let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/source_selector.ui"); @@ -36,60 +36,47 @@ impl SourceSelector { get_widget!(builder, gtk::Button, disc_button); let this = Rc::new(Self { - backend, + handle, widget, stack, info_bar, - navigator: RefCell::new(None), }); // Connect signals and callbacks - back_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + back_button.connect_clicked(clone!(@weak this => move |_| { + this.handle.pop(None); })); - folder_button.connect_clicked(clone!(@strong this => move |_| { - let window = this.navigator.borrow().clone().unwrap().window.clone(); + folder_button.connect_clicked(clone!(@weak this => move |_| { let dialog = gtk::FileChooserDialog::new( Some(&gettext("Select folder")), - Some(&window), + Some(&this.handle.window), gtk::FileChooserAction::SelectFolder, &[ (&gettext("Cancel"), gtk::ResponseType::Cancel), (&gettext("Select"), gtk::ResponseType::Accept), ]); - dialog.connect_response(clone!(@strong this => move |dialog, response| { + dialog.connect_response(clone!(@weak this => move |dialog, response| { this.stack.set_visible_child_name("loading"); dialog.hide(); if let gtk::ResponseType::Accept = response { if let Some(file) = dialog.get_file() { if let Some(path) = file.get_path() { - let context = glib::MainContext::default(); - let clone = this.clone(); - context.spawn_local(async move { + spawn!(@clone this, async move { let folder = FolderSource::new(PathBuf::from(path)); match folder.load().await { Ok(_) => { - let navigator = clone.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let source = Rc::new(Box::new(folder) as Box); - let editor = MediumEditor::new(clone.backend.clone(), source); - navigator.push(editor); - } - - clone.info_bar.set_revealed(false); - clone.stack.set_visible_child_name("start"); + let source = Rc::new(Box::new(folder) as Box); + push!(this.handle, MediumEditor, source).await; + this.handle.pop(Some(())); } Err(_) => { // TODO: Present error. - clone.info_bar.set_revealed(true); - clone.stack.set_visible_child_name("start"); + this.info_bar.set_revealed(true); + this.stack.set_visible_child_name("start"); } } }); @@ -101,29 +88,21 @@ impl SourceSelector { dialog.show(); })); - disc_button.connect_clicked(clone!(@strong this => move |_| { + disc_button.connect_clicked(clone!(@weak this => move |_| { this.stack.set_visible_child_name("loading"); - let context = glib::MainContext::default(); - let clone = this.clone(); - context.spawn_local(async move { + spawn!(@clone this, async move { let disc = DiscSource::new().unwrap(); match disc.load().await { Ok(_) => { - let navigator = clone.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let source = Rc::new(Box::new(disc) as Box); - let editor = MediumEditor::new(clone.backend.clone(), source); - navigator.push(editor); - } - - clone.info_bar.set_revealed(false); - clone.stack.set_visible_child_name("start"); + let source = Rc::new(Box::new(disc) as Box); + push!(this.handle, MediumEditor, source).await; + this.handle.pop(Some(())); } Err(_) => { // TODO: Present error. - clone.info_bar.set_revealed(true); - clone.stack.set_visible_child_name("start"); + this.info_bar.set_revealed(true); + this.stack.set_visible_child_name("start"); } } }); @@ -133,16 +112,8 @@ impl SourceSelector { } } -impl NavigatorScreen for SourceSelector { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - +impl Widget for SourceSelector { fn get_widget(&self) -> gtk::Widget { self.widget.clone().upcast() } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } } diff --git a/src/import/track_editor.rs b/src/import/track_editor.rs index 177b791..739bb6c 100644 --- a/src/import/track_editor.rs +++ b/src/import/track_editor.rs @@ -1,5 +1,6 @@ use crate::database::Recording; -use crate::widgets::{Navigator, NavigatorScreen}; +use crate::navigator::{NavigationHandle, Screen}; +use crate::widgets::Widget; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; @@ -9,15 +10,14 @@ use std::rc::Rc; /// A screen for editing a single track. pub struct TrackEditor { + handle: NavigationHandle>, widget: gtk::Box, selection: RefCell>, - selected_cb: RefCell)>>>, - navigator: RefCell>>, } -impl TrackEditor { +impl Screen<(Recording, Vec), Vec> for TrackEditor { /// Create a new track editor. - pub fn new(recording: Recording, selection: Vec) -> Rc { + fn new((recording, selection): (Recording, Vec), handle: NavigationHandle>) -> Rc { // Create UI let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/track_editor.ui"); @@ -34,38 +34,27 @@ impl TrackEditor { parts_frame.set_child(Some(&parts_list)); let this = Rc::new(Self { + handle, widget, selection: RefCell::new(selection), - selected_cb: RefCell::new(None), - navigator: RefCell::new(None), }); // Connect signals and callbacks - back_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + back_button.connect_clicked(clone!(@weak this => move |_| { + this.handle.pop(None); })); - select_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } - - if let Some(cb) = &*this.selected_cb.borrow() { - let selection = this.selection.borrow().clone(); - cb(selection); - } + select_button.connect_clicked(clone!(@weak this => move |_| { + let selection = this.selection.borrow().clone(); + this.handle.pop(Some(selection)); })); for (index, part) in recording.work.parts.iter().enumerate() { let check = gtk::CheckButton::new(); check.set_active(this.selection.borrow().contains(&index)); - check.connect_toggled(clone!(@strong this => move |check| { + check.connect_toggled(clone!(@weak this => move |check| { let mut selection = this.selection.borrow_mut(); if check.get_active() { selection.push(index); @@ -86,23 +75,10 @@ impl TrackEditor { this } - - /// Set the closure to be called when the user has edited the track. - pub fn set_selected_cb) + 'static>(&self, cb: F) { - self.selected_cb.replace(Some(Box::new(cb))); - } } -impl NavigatorScreen for TrackEditor { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - +impl Widget for TrackEditor { fn get_widget(&self) -> gtk::Widget { self.widget.clone().upcast() } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } } diff --git a/src/import/track_selector.rs b/src/import/track_selector.rs index 068fa73..abc61a3 100644 --- a/src/import/track_selector.rs +++ b/src/import/track_selector.rs @@ -1,5 +1,6 @@ use super::source::Source; -use crate::widgets::{Navigator, NavigatorScreen}; +use crate::navigator::{NavigationHandle, Screen}; +use crate::widgets::Widget; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; @@ -9,17 +10,16 @@ use std::rc::Rc; /// A screen for selecting tracks from a source. pub struct TrackSelector { + handle: NavigationHandle>, source: Rc>, widget: gtk::Box, select_button: gtk::Button, selection: RefCell>, - selected_cb: RefCell)>>>, - navigator: RefCell>>, } -impl TrackSelector { +impl Screen>, Vec> for TrackSelector { /// Create a new track selector. - pub fn new(source: Rc>) -> Rc { + fn new(source: Rc>, handle: NavigationHandle>) -> Rc { // Create UI let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/track_selector.ui"); @@ -36,33 +36,22 @@ impl TrackSelector { tracks_frame.set_child(Some(&track_list)); let this = Rc::new(Self { + handle, source, widget, select_button, selection: RefCell::new(Vec::new()), - selected_cb: RefCell::new(None), - navigator: RefCell::new(None), }); // Connect signals and callbacks - back_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + back_button.connect_clicked(clone!(@weak this => move |_| { + this.handle.pop(None); })); - this.select_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } - - if let Some(cb) = &*this.selected_cb.borrow() { - let selection = this.selection.borrow().clone(); - cb(selection); - } + this.select_button.connect_clicked(clone!(@weak this => move |_| { + let selection = this.selection.borrow().clone(); + this.handle.pop(Some(selection)); })); let tracks = this.source.tracks().unwrap(); @@ -70,7 +59,7 @@ impl TrackSelector { for (index, track) in tracks.iter().enumerate() { let check = gtk::CheckButton::new(); - check.connect_toggled(clone!(@strong this => move |check| { + check.connect_toggled(clone!(@weak this => move |check| { let mut selection = this.selection.borrow_mut(); if check.get_active() { selection.push(index); @@ -98,25 +87,10 @@ impl TrackSelector { this } - - /// Set the closure to be called when the user has selected tracks. The - /// closure will be called with the indices of the selected tracks as its - /// argument. - pub fn set_selected_cb) + 'static>(&self, cb: F) { - self.selected_cb.replace(Some(Box::new(cb))); - } } -impl NavigatorScreen for TrackSelector { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - +impl Widget for TrackSelector { fn get_widget(&self) -> gtk::Widget { self.widget.clone().upcast() } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } } diff --git a/src/import/track_set_editor.rs b/src/import/track_set_editor.rs index ed79c70..95febd4 100644 --- a/src/import/track_set_editor.rs +++ b/src/import/track_set_editor.rs @@ -3,8 +3,9 @@ use super::track_editor::TrackEditor; use super::track_selector::TrackSelector; use crate::backend::Backend; use crate::database::Recording; -use crate::selectors::{PersonSelector, RecordingSelector, WorkSelector}; -use crate::widgets::{List, Navigator, NavigatorScreen}; +use crate::navigator::{NavigationHandle, Screen}; +use crate::selectors::PersonSelector; +use crate::widgets::{List, Widget}; use gettextrs::gettext; use glib::clone; use gtk::prelude::*; @@ -32,7 +33,7 @@ pub struct TrackData { /// A screen for editing a set of tracks for one recording. pub struct TrackSetEditor { - backend: Rc, + handle: NavigationHandle, source: Rc>, widget: gtk::Box, save_button: gtk::Button, @@ -40,13 +41,11 @@ pub struct TrackSetEditor { track_list: Rc, recording: RefCell>, tracks: RefCell>, - done_cb: RefCell>>, - navigator: RefCell>>, } -impl TrackSetEditor { +impl Screen>, TrackSetData> for TrackSetEditor { /// Create a new track set editor. - pub fn new(backend: Rc, source: Rc>) -> Rc { + fn new(source: Rc>, handle: NavigationHandle) -> Rc { // Create UI let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/track_set_editor.ui"); @@ -63,7 +62,7 @@ impl TrackSetEditor { tracks_frame.set_child(Some(&track_list.widget)); let this = Rc::new(Self { - backend, + handle, source, widget, save_button, @@ -71,71 +70,30 @@ impl TrackSetEditor { track_list, recording: RefCell::new(None), tracks: RefCell::new(Vec::new()), - done_cb: RefCell::new(None), - navigator: RefCell::new(None), }); // Connect signals and callbacks - back_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + back_button.connect_clicked(clone!(@weak this => move |_| { + this.handle.pop(None); })); - this.save_button.connect_clicked(clone!(@strong this => move |_| { - if let Some(cb) = &*this.done_cb.borrow() { - let data = TrackSetData { - recording: this.recording.borrow().clone().unwrap(), - tracks: this.tracks.borrow().clone(), - }; + this.save_button.connect_clicked(clone!(@weak this => move |_| { + let data = TrackSetData { + recording: this.recording.borrow().clone().unwrap(), + tracks: this.tracks.borrow().clone(), + }; - cb(data); - } - - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + this.handle.pop(Some(data)); })); - select_recording_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let person_selector = PersonSelector::new(this.backend.clone()); - - person_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |person| { - let work_selector = WorkSelector::new(this.backend.clone(), person.clone()); - - work_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |work| { - let recording_selector = RecordingSelector::new(this.backend.clone(), work.clone()); - - recording_selector.set_selected_cb(clone!(@strong this, @strong navigator => move |recording| { - this.recording.replace(Some(recording.clone())); - this.recording_selected(); - - navigator.clone().pop(); - navigator.clone().pop(); - navigator.clone().pop(); - })); - - navigator.clone().push(recording_selector); - })); - - navigator.clone().push(work_selector); - })); - - navigator.clone().push(person_selector); - } + select_recording_button.connect_clicked(clone!(@weak this => move |_| { + // TODO: We need to push a screen returning a recording here. })); - edit_tracks_button.connect_clicked(clone!(@strong this => move |_| { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let selector = TrackSelector::new(Rc::clone(&this.source)); - - selector.set_selected_cb(clone!(@strong this => move |selection| { + edit_tracks_button.connect_clicked(clone!(@weak this => move |_| { + spawn!(@clone this, async move { + if let Some(selection) = push!(this.handle, TrackSelector, Rc::clone(&this.source)).await { let mut tracks = Vec::new(); for index in selection { @@ -151,13 +109,11 @@ impl TrackSetEditor { this.tracks.replace(tracks); this.track_list.update(length); this.autofill_parts(); - })); - - navigator.push(selector); - } + } + }); })); - this.track_list.set_make_widget_cb(clone!(@strong this => move |index| { + this.track_list.set_make_widget_cb(clone!(@weak this => move |index| { let track = &this.tracks.borrow()[index]; let mut title_parts = Vec::::new(); @@ -190,26 +146,21 @@ impl TrackSetEditor { row.add_suffix(&edit_button); row.set_activatable_widget(Some(&edit_button)); - edit_button.connect_clicked(clone!(@strong this => move |_| { + edit_button.connect_clicked(clone!(@weak this => move |_| { let recording = this.recording.borrow().clone(); - let navigator = this.navigator.borrow().clone(); + if let Some(recording) = recording { + spawn!(@clone this, async move { + let track = &this.tracks.borrow()[index]; + if let Some(selection) = push!(this.handle, TrackEditor, (recording, track.work_parts.clone())).await { + { + let mut tracks = this.tracks.borrow_mut(); + let mut track = &mut tracks[index]; + track.work_parts = selection; + }; - if let (Some(recording), Some(navigator)) = (recording, navigator) { - let track = &this.tracks.borrow()[index]; - - let editor = TrackEditor::new(recording, track.work_parts.clone()); - - editor.set_selected_cb(clone!(@strong this => move |selection| { - { - let mut tracks = this.tracks.borrow_mut(); - let mut track = &mut tracks[index]; - track.work_parts = selection; - }; - - this.update_tracks(); - })); - - navigator.push(editor); + this.update_tracks(); + } + }); } })); @@ -218,12 +169,9 @@ impl TrackSetEditor { this } +} - /// Set the closure to be called when the user has created the track set. - pub fn set_done_cb(&self, cb: F) { - self.done_cb.replace(Some(Box::new(cb))); - } - +impl TrackSetEditor { /// Set everything up after selecting a recording. fn recording_selected(&self) { if let Some(recording) = &*self.recording.borrow() { @@ -260,21 +208,9 @@ impl TrackSetEditor { } } -impl NavigatorScreen for TrackSetEditor { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - +impl Widget for TrackSetEditor { fn get_widget(&self) -> gtk::Widget { self.widget.clone().upcast() } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } } - - - - diff --git a/src/macros.rs b/src/macros.rs index d281714..1a80193 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -18,7 +18,7 @@ macro_rules! push { ($handle:expr, $screen:ty) => { $handle.push::<_, _, $screen>(()) }; - ($handle:expr, $screen:ty, $input:ident) => { + ($handle:expr, $screen:ty, $input:expr) => { $handle.push::<_, _, $screen>($input) }; } @@ -43,7 +43,7 @@ macro_rules! replace { ($navigator:expr, $screen:ty) => { $navigator.replace::<_, _, $screen>(()) }; - ($navigator:expr, $screen:ty, $input:ident) => { + ($navigator:expr, $screen:ty, $input:expr) => { $navigator.replace::<_, _, $screen>($input) }; } diff --git a/src/screens/ensemble.rs b/src/screens/ensemble.rs index dcd5d91..01bee0a 100644 --- a/src/screens/ensemble.rs +++ b/src/screens/ensemble.rs @@ -3,7 +3,8 @@ use super::RecordingScreen; use crate::backend::Backend; use crate::database::{Ensemble, Recording}; use crate::editors::EnsembleEditor; -use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow, Screen, Section}; +use crate::navigator::NavigatorWindow; +use crate::widgets::{List, Navigator, NavigatorScreen, Screen, Section}; use gettextrs::gettext; use glib::clone; @@ -49,9 +50,10 @@ impl EnsembleScreen { this.widget.add_action(&gettext("Edit ensemble"), clone!(@strong this => move || { - let editor = EnsembleEditor::new(this.backend.clone(), Some(this.ensemble.clone())); - let window = NavigatorWindow::new(editor); - window.show(); + spawn!(@clone this, async move { + let window = NavigatorWindow::new(this.backend.clone()); + replace!(window.navigator, EnsembleEditor, None).await; + }); })); this.widget.add_action(&gettext("Delete ensemble"), clone!(@strong this => move || { diff --git a/src/screens/person.rs b/src/screens/person.rs index 6f0c2e1..a005cad 100644 --- a/src/screens/person.rs +++ b/src/screens/person.rs @@ -3,7 +3,8 @@ use super::{WorkScreen, RecordingScreen}; use crate::backend::Backend; use crate::database::{Person, Recording, Work}; use crate::editors::PersonEditor; -use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow, Screen, Section}; +use crate::navigator::NavigatorWindow; +use crate::widgets::{List, Navigator, NavigatorScreen, Screen, Section}; use gettextrs::gettext; use glib::clone; @@ -54,9 +55,10 @@ impl PersonScreen { this.widget.add_action(&gettext("Edit person"), clone!(@strong this => move || { - let editor = PersonEditor::new(this.backend.clone(), Some(this.person.clone())); - let window = NavigatorWindow::new(editor); - window.show(); + spawn!(@clone this, async move { + let window = NavigatorWindow::new(this.backend.clone()); + replace!(window.navigator, PersonEditor, None).await; + }); })); this.widget.add_action(&gettext("Delete person"), clone!(@strong this => move || { diff --git a/src/screens/recording.rs b/src/screens/recording.rs index e363356..241e8ce 100644 --- a/src/screens/recording.rs +++ b/src/screens/recording.rs @@ -1,7 +1,8 @@ use crate::backend::Backend; use crate::database::Recording; use crate::editors::RecordingEditor; -use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow, Screen, Section}; +use crate::navigator::NavigatorWindow; +use crate::widgets::{List, Navigator, NavigatorScreen, Screen, Section}; use gettextrs::gettext; use glib::clone; @@ -48,9 +49,10 @@ impl RecordingScreen { this.widget.add_action(&gettext("Edit recording"), clone!(@strong this => move || { - let editor = RecordingEditor::new(this.backend.clone(), Some(this.recording.clone())); - let window = NavigatorWindow::new(editor); - window.show(); + spawn!(@clone this, async move { + let window = NavigatorWindow::new(this.backend.clone()); + replace!(window.navigator, RecordingEditor, None).await; + }); })); this.widget.add_action(&gettext("Delete recording"), clone!(@strong this => move || { diff --git a/src/screens/work.rs b/src/screens/work.rs index b969426..7e1558b 100644 --- a/src/screens/work.rs +++ b/src/screens/work.rs @@ -3,7 +3,8 @@ use super::RecordingScreen; use crate::backend::Backend; use crate::database::{Work, Recording}; use crate::editors::WorkEditor; -use crate::widgets::{List, Navigator, NavigatorScreen, NavigatorWindow, Screen, Section}; +use crate::navigator::NavigatorWindow; +use crate::widgets::{List, Navigator, NavigatorScreen, Screen, Section}; use gettextrs::gettext; use glib::clone; @@ -50,9 +51,10 @@ impl WorkScreen { this.widget.add_action(&gettext("Edit work"), clone!(@strong this => move || { - let editor = WorkEditor::new(this.backend.clone(), Some(this.work.clone())); - let window = NavigatorWindow::new(editor); - window.show(); + spawn!(@clone this, async move { + let window = NavigatorWindow::new(this.backend.clone()); + replace!(window.navigator, WorkEditor, None).await; + }); })); this.widget.add_action(&gettext("Delete work"), clone!(@strong this => move || { diff --git a/src/selectors/ensemble.rs b/src/selectors/ensemble.rs index b3ec260..39ac8c3 100644 --- a/src/selectors/ensemble.rs +++ b/src/selectors/ensemble.rs @@ -2,7 +2,8 @@ use super::selector::Selector; use crate::backend::Backend; use crate::database::Ensemble; use crate::editors::EnsembleEditor; -use crate::widgets::{Navigator, NavigatorScreen}; +use crate::navigator::{NavigationHandle, Screen}; +use crate::widgets::Widget; use gettextrs::gettext; use glib::clone; use gtk::prelude::*; @@ -12,66 +13,55 @@ use std::rc::Rc; /// A screen for selecting a ensemble. pub struct EnsembleSelector { - backend: Rc, + handle: NavigationHandle, selector: Rc>, - selected_cb: RefCell ()>>>, - navigator: RefCell>>, } -impl EnsembleSelector { +impl Screen<(), Ensemble> for EnsembleSelector { /// Create a new ensemble selector. - pub fn new(backend: Rc) -> Rc { + fn new(_: (), handle: NavigationHandle) -> Rc { // Create UI let selector = Selector::::new(); selector.set_title(&gettext("Select ensemble")); let this = Rc::new(Self { - backend, + handle, selector, - selected_cb: RefCell::new(None), - navigator: RefCell::new(None), }); // Connect signals and callbacks - this.selector.set_back_cb(clone!(@strong this => move || { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + this.selector.set_back_cb(clone!(@weak this => move || { + this.handle.pop(None); })); - this.selector.set_add_cb(clone!(@strong this => move || { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let editor = EnsembleEditor::new(this.backend.clone(), None); - editor - .set_saved_cb(clone!(@strong this => move |ensemble| this.select(&ensemble))); - navigator.push(editor); - } + this.selector.set_add_cb(clone!(@weak this => move || { + spawn!(@clone this, async move { + if let Some(ensemble) = push!(this.handle, EnsembleEditor, None).await { + this.handle.pop(Some(ensemble)); + } + }); })); - this.selector - .set_load_online(clone!(@strong this => move || { - let clone = this.clone(); - async move { clone.backend.get_ensembles().await } - })); + this.selector.set_load_online(clone!(@weak this => move || { + let clone = this.clone(); + async move { clone.handle.backend.get_ensembles().await } + })); - this.selector - .set_load_local(clone!(@strong this => move || { - let clone = this.clone(); - async move { clone.backend.db().get_ensembles().await.unwrap() } - })); + this.selector.set_load_local(clone!(@weak this => move || { + let clone = this.clone(); + async move { clone.handle.backend.db().get_ensembles().await.unwrap() } + })); - this.selector.set_make_widget(clone!(@strong this => move |ensemble| { + this.selector.set_make_widget(clone!(@weak this => move |ensemble| { let row = libadwaita::ActionRow::new(); row.set_activatable(true); row.set_title(Some(&ensemble.name)); let ensemble = ensemble.to_owned(); - row.connect_activated(clone!(@strong this => move |_| { - this.select(&ensemble); + row.connect_activated(clone!(@weak this => move |_| { + this.handle.pop(Some(ensemble.clone())) })); row.upcast() @@ -82,31 +72,10 @@ impl EnsembleSelector { this } - - /// Set the closure to be called when an item is selected. - pub fn set_selected_cb () + 'static>(&self, cb: F) { - self.selected_cb.replace(Some(Box::new(cb))); - } - - /// Select an ensemble. - fn select(&self, ensemble: &Ensemble) { - if let Some(cb) = &*self.selected_cb.borrow() { - cb(&ensemble); - } - - } } -impl NavigatorScreen for EnsembleSelector { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - +impl Widget for EnsembleSelector { fn get_widget(&self) -> gtk::Widget { self.selector.widget.clone().upcast() } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } } diff --git a/src/selectors/instrument.rs b/src/selectors/instrument.rs index f4b41a5..a1cbf29 100644 --- a/src/selectors/instrument.rs +++ b/src/selectors/instrument.rs @@ -2,7 +2,8 @@ use super::selector::Selector; use crate::backend::Backend; use crate::database::Instrument; use crate::editors::InstrumentEditor; -use crate::widgets::{Navigator, NavigatorScreen}; +use crate::navigator::{NavigationHandle, Screen}; +use crate::widgets::Widget; use gettextrs::gettext; use glib::clone; use gtk::prelude::*; @@ -12,66 +13,55 @@ use std::rc::Rc; /// A screen for selecting a instrument. pub struct InstrumentSelector { - backend: Rc, + handle: NavigationHandle, selector: Rc>, - selected_cb: RefCell ()>>>, - navigator: RefCell>>, } -impl InstrumentSelector { +impl Screen<(), Instrument> for InstrumentSelector { /// Create a new instrument selector. - pub fn new(backend: Rc) -> Rc { + fn new(_: (), handle: NavigationHandle) -> Rc { // Create UI let selector = Selector::::new(); selector.set_title(&gettext("Select instrument")); let this = Rc::new(Self { - backend, + handle, selector, - selected_cb: RefCell::new(None), - navigator: RefCell::new(None), }); // Connect signals and callbacks - this.selector.set_back_cb(clone!(@strong this => move || { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + this.selector.set_back_cb(clone!(@weak this => move || { + this.handle.pop(None); })); - this.selector.set_add_cb(clone!(@strong this => move || { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let editor = InstrumentEditor::new(this.backend.clone(), None); - editor - .set_saved_cb(clone!(@strong this => move |instrument| this.select(&instrument))); - navigator.push(editor); - } + this.selector.set_add_cb(clone!(@weak this => move || { + spawn!(@clone this, async move { + if let Some(instrument) = push!(this.handle, InstrumentEditor, None).await { + this.handle.pop(Some(instrument)); + } + }); })); - this.selector - .set_load_online(clone!(@strong this => move || { - let clone = this.clone(); - async move { clone.backend.get_instruments().await } - })); + this.selector.set_load_online(clone!(@weak this => move || { + let clone = this.clone(); + async move { clone.handle.backend.get_instruments().await } + })); - this.selector - .set_load_local(clone!(@strong this => move || { - let clone = this.clone(); - async move { clone.backend.db().get_instruments().await.unwrap() } - })); + this.selector.set_load_local(clone!(@weak this => move || { + let clone = this.clone(); + async move { clone.handle.backend.db().get_instruments().await.unwrap() } + })); - this.selector.set_make_widget(clone!(@strong this => move |instrument| { + this.selector.set_make_widget(clone!(@weak this => move |instrument| { let row = libadwaita::ActionRow::new(); row.set_activatable(true); row.set_title(Some(&instrument.name)); let instrument = instrument.to_owned(); - row.connect_activated(clone!(@strong this => move |_| { - this.select(&instrument); + row.connect_activated(clone!(@weak this => move |_| { + this.handle.pop(Some(instrument.clone())) })); row.upcast() @@ -82,30 +72,10 @@ impl InstrumentSelector { this } - - /// Set the closure to be called when an item is selected. - pub fn set_selected_cb () + 'static>(&self, cb: F) { - self.selected_cb.replace(Some(Box::new(cb))); - } - - /// Select an instrument. - fn select(&self, instrument: &Instrument) { - if let Some(cb) = &*self.selected_cb.borrow() { - cb(&instrument); - } - } } -impl NavigatorScreen for InstrumentSelector { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - +impl Widget for InstrumentSelector { fn get_widget(&self) -> gtk::Widget { self.selector.widget.clone().upcast() } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } } diff --git a/src/selectors/mod.rs b/src/selectors/mod.rs index 0083998..e42d9ab 100644 --- a/src/selectors/mod.rs +++ b/src/selectors/mod.rs @@ -7,10 +7,12 @@ pub use instrument::*; pub mod person; pub use person::*; -pub mod recording; -pub use recording::*; +// TODO: Readd a better version of these. +// +// pub mod recording; +// pub use recording::*; +// +// pub mod work; +// pub use work::*; -pub mod work; -pub use work::*; - -mod selector; \ No newline at end of file +mod selector; diff --git a/src/selectors/person.rs b/src/selectors/person.rs index 456a679..cfe3332 100644 --- a/src/selectors/person.rs +++ b/src/selectors/person.rs @@ -2,7 +2,8 @@ use super::selector::Selector; use crate::backend::Backend; use crate::database::Person; use crate::editors::PersonEditor; -use crate::widgets::{Navigator, NavigatorScreen}; +use crate::navigator::{NavigationHandle, Screen}; +use crate::widgets::Widget; use gettextrs::gettext; use glib::clone; use gtk::prelude::*; @@ -12,66 +13,55 @@ use std::rc::Rc; /// A screen for selecting a person. pub struct PersonSelector { - backend: Rc, + handle: NavigationHandle, selector: Rc>, - selected_cb: RefCell ()>>>, - navigator: RefCell>>, } -impl PersonSelector { +impl Screen<(), Person> for PersonSelector { /// Create a new person selector. - pub fn new(backend: Rc) -> Rc { + fn new(_: (), handle: NavigationHandle) -> Rc { // Create UI let selector = Selector::::new(); selector.set_title(&gettext("Select person")); let this = Rc::new(Self { - backend, + handle, selector, - selected_cb: RefCell::new(None), - navigator: RefCell::new(None), }); // Connect signals and callbacks - this.selector.set_back_cb(clone!(@strong this => move || { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } + this.selector.set_back_cb(clone!(@weak this => move || { + this.handle.pop(None); })); - this.selector.set_add_cb(clone!(@strong this => move || { - let navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - let editor = PersonEditor::new(this.backend.clone(), None); - editor - .set_saved_cb(clone!(@strong this => move |person| this.select(&person))); - navigator.push(editor); - } + this.selector.set_add_cb(clone!(@weak this => move || { + spawn!(@clone this, async move { + if let Some(person) = push!(this.handle, PersonEditor, None).await { + this.handle.pop(Some(person)); + } + }); })); - this.selector - .set_load_online(clone!(@strong this => move || { - let clone = this.clone(); - async move { clone.backend.get_persons().await } - })); + this.selector.set_load_online(clone!(@weak this => move || { + let clone = this.clone(); + async move { clone.handle.backend.get_persons().await } + })); - this.selector - .set_load_local(clone!(@strong this => move || { - let clone = this.clone(); - async move { clone.backend.db().get_persons().await.unwrap() } - })); + this.selector.set_load_local(clone!(@weak this => move || { + let clone = this.clone(); + async move { clone.handle.backend.db().get_persons().await.unwrap() } + })); - this.selector.set_make_widget(clone!(@strong this => move |person| { + this.selector.set_make_widget(clone!(@weak this => move |person| { let row = libadwaita::ActionRow::new(); row.set_activatable(true); row.set_title(Some(&person.name_lf())); let person = person.to_owned(); - row.connect_activated(clone!(@strong this => move |_| { - this.select(&person); + row.connect_activated(clone!(@weak this => move |_| { + this.handle.pop(Some(person.clone())); })); row.upcast() @@ -82,30 +72,10 @@ impl PersonSelector { this } - - /// Set the closure to be called when an item is selected. - pub fn set_selected_cb () + 'static>(&self, cb: F) { - self.selected_cb.replace(Some(Box::new(cb))); - } - - /// Select a person. - fn select(&self, person: &Person) { - if let Some(cb) = &*self.selected_cb.borrow() { - cb(&person); - } - } } -impl NavigatorScreen for PersonSelector { - fn attach_navigator(&self, navigator: Rc) { - self.navigator.replace(Some(navigator)); - } - +impl Widget for PersonSelector { fn get_widget(&self) -> gtk::Widget { self.selector.widget.clone().upcast() } - - fn detach_navigator(&self) { - self.navigator.replace(None); - } } diff --git a/src/window.rs b/src/window.rs index f0db8ee..145eca8 100644 --- a/src/window.rs +++ b/src/window.rs @@ -4,6 +4,7 @@ use crate::import::SourceSelector; use crate::preferences::Preferences; use crate::screens::*; use crate::widgets::*; +use crate::navigator::NavigatorWindow; use futures::prelude::*; use gettextrs::gettext; use gio::prelude::*; @@ -95,18 +96,10 @@ impl Window { })); add_button.connect_clicked(clone!(@strong result => move |_| { - // let editor = TracksEditor::new(result.backend.clone(), None, Vec::new()); - - // editor.set_callback(clone!(@strong result => move || { - // result.reload(); - // })); - - // let window = NavigatorWindow::new(editor); - // window.show(); - - let dialog = SourceSelector::new(result.backend.clone()); - let window = NavigatorWindow::new(dialog); - window.show(); + spawn!(@clone result, async move { + let window = NavigatorWindow::new(result.backend.clone()); + replace!(window.navigator, SourceSelector).await; + }); })); result