From 7eff62b5a4eeac699aac5ccc4f20564940cd6b1c Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Tue, 2 Feb 2021 12:18:42 +0100 Subject: [PATCH] Use editor widget for performance editor --- res/ui/section.ui | 27 +++++--- src/editors/performance.rs | 122 ++++++++++++++++++++----------------- src/meson.build | 1 + src/widgets/button_row.rs | 51 ++++++++++++++++ src/widgets/editor.rs | 10 ++- src/widgets/mod.rs | 3 + src/widgets/section.rs | 22 ++++++- 7 files changed, 170 insertions(+), 66 deletions(-) create mode 100644 src/widgets/button_row.rs diff --git a/res/ui/section.ui b/res/ui/section.ui index 2d354d1..2ba5a53 100644 --- a/res/ui/section.ui +++ b/res/ui/section.ui @@ -8,15 +8,28 @@ 12 - - end - 0.0 + + vertical + 18 end true - 18 - - - + + + end + 0.0 + + + + + + + + true + 0.0 + false + 6 + + diff --git a/src/editors/performance.rs b/src/editors/performance.rs index b54e9d0..80cd7e4 100644 --- a/src/editors/performance.rs +++ b/src/editors/performance.rs @@ -1,11 +1,10 @@ use crate::backend::Backend; use crate::database::*; use crate::selectors::{EnsembleSelector, InstrumentSelector, PersonSelector}; -use crate::widgets::{Navigator, NavigatorScreen}; +use crate::widgets::{Editor, Navigator, NavigatorScreen, Section, ButtonRow, Widget}; use gettextrs::gettext; use glib::clone; use gtk::prelude::*; -use gtk_macros::get_widget; use libadwaita::prelude::*; use std::cell::RefCell; use std::rc::Rc; @@ -13,11 +12,10 @@ use std::rc::Rc; /// A dialog for editing a performance within a recording. pub struct PerformanceEditor { backend: Rc, - widget: gtk::Box, - save_button: gtk::Button, - person_row: libadwaita::ActionRow, - ensemble_row: libadwaita::ActionRow, - role_row: libadwaita::ActionRow, + editor: Editor, + person_row: ButtonRow, + ensemble_row: ButtonRow, + role_row: ButtonRow, reset_role_button: gtk::Button, person: RefCell>, ensemble: RefCell>, @@ -29,25 +27,49 @@ pub struct PerformanceEditor { impl PerformanceEditor { /// Create a new performance editor. pub fn new(backend: Rc, performance: Option) -> Rc { - // Create UI + let editor = Editor::new(); + editor.set_title("Performance"); + editor.set_may_save(false); - let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/performance_editor.ui"); + let performer_list = gtk::ListBoxBuilder::new() + .selection_mode(gtk::SelectionMode::None) + .build(); - get_widget!(builder, gtk::Box, widget); - get_widget!(builder, gtk::Button, back_button); - get_widget!(builder, gtk::Button, save_button); - get_widget!(builder, gtk::Button, person_button); - get_widget!(builder, gtk::Button, ensemble_button); - get_widget!(builder, gtk::Button, role_button); - get_widget!(builder, gtk::Button, reset_role_button); - get_widget!(builder, libadwaita::ActionRow, person_row); - get_widget!(builder, libadwaita::ActionRow, ensemble_row); - get_widget!(builder, libadwaita::ActionRow, role_row); + let person_row = ButtonRow::new("Person", "Select"); + let ensemble_row = ButtonRow::new("Ensemble", "Select"); + + performer_list.append(&person_row.get_widget()); + performer_list.append(&ensemble_row.get_widget()); + + let performer_section = Section::new(&gettext("Performer"), &performer_list); + performer_section.set_subtitle( + &gettext("Select either a person or an ensemble as a performer.")); + + let role_list = gtk::ListBoxBuilder::new() + .selection_mode(gtk::SelectionMode::None) + .build(); + + let reset_role_button = gtk::ButtonBuilder::new() + .icon_name("user-trash-symbolic") + .valign(gtk::Align::Center) + .visible(false) + .build(); + + let role_row = ButtonRow::new("Role", "Select"); + role_row.widget.add_suffix(&reset_role_button); + + role_list.append(&role_row.get_widget()); + + let role_section = Section::new(&gettext("Role"), &role_list); + role_section.set_subtitle( + &gettext("Optionally, choose a role to specify what the performer does.")); + + editor.add_content(&performer_section); + editor.add_content(&role_section); let this = Rc::new(PerformanceEditor { backend, - widget, - save_button, + editor, person_row, ensemble_row, role_row, @@ -59,32 +81,29 @@ impl PerformanceEditor { navigator: RefCell::new(None), }); - // Connect signals and callbacks - - back_button.connect_clicked(clone!(@strong this => move |_| { + this.editor.set_back_cb(clone!(@strong this => move || { let navigator = this.navigator.borrow().clone(); if let Some(navigator) = navigator { navigator.pop(); } })); - this.save_button - .connect_clicked(clone!(@strong 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(), - }); - } + 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 navigator = this.navigator.borrow().clone(); - if let Some(navigator) = navigator { - navigator.pop(); - } - })); + let navigator = this.navigator.borrow().clone(); + if let Some(navigator) = navigator { + navigator.pop(); + } + })); - person_button.connect_clicked(clone!(@strong this => move |_| { + 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()); @@ -101,7 +120,7 @@ impl PerformanceEditor { } })); - ensemble_button.connect_clicked(clone!(@strong this => move |_| { + 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()); @@ -118,7 +137,7 @@ impl PerformanceEditor { } })); - role_button.connect_clicked(clone!(@strong this => move |_| { + 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()); @@ -133,11 +152,10 @@ impl PerformanceEditor { } })); - this.reset_role_button - .connect_clicked(clone!(@strong this => move |_| { - this.show_role(None); - this.role.replace(None); - })); + this.reset_role_button.connect_clicked(clone!(@weak this => move |_| { + this.show_role(None); + this.role.replace(None); + })); // Initialize @@ -167,11 +185,9 @@ impl PerformanceEditor { /// Update the UI according to person. fn show_person(&self, person: Option<&Person>) { if let Some(person) = person { - self.person_row.set_title(Some(&gettext("Person"))); self.person_row.set_subtitle(Some(&person.name_fl())); - self.save_button.set_sensitive(true); + self.editor.set_may_save(true); } else { - self.person_row.set_title(Some(&gettext("Select a person"))); self.person_row.set_subtitle(None); } } @@ -179,11 +195,9 @@ impl PerformanceEditor { /// Update the UI according to ensemble. fn show_ensemble(&self, ensemble: Option<&Ensemble>) { if let Some(ensemble) = ensemble { - self.ensemble_row.set_title(Some(&gettext("Ensemble"))); self.ensemble_row.set_subtitle(Some(&ensemble.name)); - self.save_button.set_sensitive(true); + self.editor.set_may_save(true); } else { - self.ensemble_row.set_title(Some(&gettext("Select an ensemble"))); self.ensemble_row.set_subtitle(None); } } @@ -191,11 +205,9 @@ impl PerformanceEditor { /// Update the UI according to role. fn show_role(&self, role: Option<&Instrument>) { if let Some(role) = role { - self.role_row.set_title(Some(&gettext("Role"))); self.role_row.set_subtitle(Some(&role.name)); self.reset_role_button.show(); } else { - self.role_row.set_title(Some(&gettext("Select a role"))); self.role_row.set_subtitle(None); self.reset_role_button.hide(); } @@ -208,7 +220,7 @@ impl NavigatorScreen for PerformanceEditor { } fn get_widget(&self) -> gtk::Widget { - self.widget.clone().upcast() + self.editor.widget.clone().upcast() } fn detach_navigator(&self) { diff --git a/src/meson.build b/src/meson.build index e83e7e8..e4179f0 100644 --- a/src/meson.build +++ b/src/meson.build @@ -90,6 +90,7 @@ sources = files( 'selectors/recording.rs', 'selectors/selector.rs', 'selectors/work.rs', + 'widgets/button_row.rs', 'widgets/editor.rs', 'widgets/entry_row.rs', 'widgets/upload_section.rs', diff --git a/src/widgets/button_row.rs b/src/widgets/button_row.rs new file mode 100644 index 0000000..7ffe142 --- /dev/null +++ b/src/widgets/button_row.rs @@ -0,0 +1,51 @@ +use super::Widget; +use gtk::prelude::*; +use libadwaita::prelude::*; + +/// A list box row with a single button. +pub struct ButtonRow { + /// The actual GTK widget. + pub widget: libadwaita::ActionRow, + + /// The managed button. + button: gtk::Button, +} + +impl ButtonRow { + /// Create a new button row. + pub fn new(title: &str, label: &str) -> Self { + let button = gtk::ButtonBuilder::new() + .valign(gtk::Align::Center) + .label(label) + .build(); + + let widget = libadwaita::ActionRowBuilder::new() + .activatable(true) + .activatable_widget(&button) + .title(title) + .build(); + + widget.add_suffix(&button); + + Self { + widget, + button, + } + } + + /// Set the subtitle of the row. + pub fn set_subtitle(&self, subtitle: Option<&str>) { + self.widget.set_subtitle(subtitle); + } + + /// Set the closure to be called on activation + pub fn set_cb(&self, cb: F) { + self.button.connect_clicked(move |_| cb()); + } +} + +impl Widget for ButtonRow { + fn get_widget(&self) -> gtk::Widget { + self.widget.clone().upcast() + } +} diff --git a/src/widgets/editor.rs b/src/widgets/editor.rs index 2844c1d..7af32ab 100644 --- a/src/widgets/editor.rs +++ b/src/widgets/editor.rs @@ -1,3 +1,4 @@ +use super::Widget; use gio::prelude::*; use glib::clone; use gtk::prelude::*; @@ -61,6 +62,11 @@ impl Editor { self.window_title.set_title(Some(title)); } + /// Set whether the user should be able to click the save button. + pub fn set_may_save(&self, save: bool) { + self.save_button.set_sensitive(save); + } + pub fn set_save_cb(&self, cb: F) { self.save_button.connect_clicked(move |_| cb()); } @@ -79,7 +85,7 @@ impl Editor { } /// Add content to the bottom of the content area. - pub fn add_content>(&self, content: &W) { - self.content_box.append(content); + pub fn add_content(&self, content: &W) { + self.content_box.append(&content.get_widget()); } } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index d94d67e..5a0626a 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,5 +1,8 @@ use gtk::prelude::*; +pub mod button_row; +pub use button_row::*; + pub mod editor; pub use editor::*; diff --git a/src/widgets/section.rs b/src/widgets/section.rs index 6b4f37a..ea33777 100644 --- a/src/widgets/section.rs +++ b/src/widgets/section.rs @@ -1,3 +1,4 @@ +use super::Widget; use gtk::prelude::*; use gtk_macros::get_widget; @@ -9,27 +10,38 @@ pub struct Section { /// The box containing the title and action buttons. title_box: gtk::Box, + + /// An optional subtitle below the title. + subtitle_label: gtk::Label, } impl Section { /// Create a new section. - pub fn new>(title: &str, content: &W) -> Self { + pub fn new(title: &str, content: &W) -> Self { let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/section.ui"); get_widget!(builder, gtk::Box, widget); get_widget!(builder, gtk::Box, title_box); get_widget!(builder, gtk::Label, title_label); + get_widget!(builder, gtk::Label, subtitle_label); get_widget!(builder, gtk::Frame, frame); title_label.set_label(title); - frame.set_child(Some(content)); + frame.set_child(Some(&content.get_widget())); Self { widget, title_box, + subtitle_label, } } + /// Add a subtitle below the title. + pub fn set_subtitle(&self, subtitle: &str) { + self.subtitle_label.set_label(subtitle); + self.subtitle_label.show(); + } + /// Add an action button. This should by definition be something that is /// doing something with the child widget that is applicable in all /// situations where the widget is visible. The new button will be packed @@ -46,3 +58,9 @@ impl Section { self.title_box.append(&button); } } + +impl Widget for Section { + fn get_widget(&self) -> gtk::Widget { + self.widget.clone().upcast() + } +}