From 9a9a1817394402dcffc533bcc269cef6a153060a Mon Sep 17 00:00:00 2001 From: Elias Projahn Date: Sat, 17 Oct 2020 11:31:43 +0200 Subject: [PATCH] Replace stack with new navigator widget --- src/screens/ensemble_screen.rs | 46 +++++++---- src/screens/person_screen.rs | 67 ++++++++++------ src/screens/recording_screen.rs | 29 ++++--- src/screens/work_screen.rs | 49 ++++++++---- src/widgets/mod.rs | 6 +- src/widgets/navigator.rs | 130 ++++++++++++++++++++++++++++++++ src/widgets/stack.rs | 77 ------------------- src/window.rs | 70 ++--------------- 8 files changed, 268 insertions(+), 206 deletions(-) create mode 100644 src/widgets/navigator.rs delete mode 100644 src/widgets/stack.rs diff --git a/src/screens/ensemble_screen.rs b/src/screens/ensemble_screen.rs index 0c68173..ccd2446 100644 --- a/src/screens/ensemble_screen.rs +++ b/src/screens/ensemble_screen.rs @@ -1,3 +1,4 @@ +use super::*; use crate::backend::*; use crate::database::*; use crate::widgets::*; @@ -9,10 +10,11 @@ use std::cell::RefCell; use std::rc::Rc; pub struct EnsembleScreen { - pub widget: gtk::Box, + backend: Rc, + widget: gtk::Box, stack: gtk::Stack, recording_list: Rc>, - back: RefCell () + 'static>>>, + navigator: RefCell>>, } impl EnsembleScreen { @@ -59,10 +61,11 @@ impl EnsembleScreen { recording_frame.add(&recording_list.widget.clone()); let result = Rc::new(Self { + backend, widget, stack, recording_list, - back: RefCell::new(None), + navigator: RefCell::new(None), }); search_entry.connect_search_changed(clone!(@strong result => move |_| { @@ -70,15 +73,26 @@ impl EnsembleScreen { })); back_button.connect_clicked(clone!(@strong result => move |_| { - if let Some(back) = &*result.back.borrow() { - back(); + let navigator = result.navigator.borrow().clone(); + if let Some(navigator) = navigator { + navigator.pop(); } })); + result + .recording_list + .set_selected(clone!(@strong result => move |recording| { + let navigator = result.navigator.borrow().clone(); + if let Some(navigator) = navigator { + navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone())); + } + })); + let context = glib::MainContext::default(); let clone = result.clone(); context.spawn_local(async move { - let recordings = backend + let recordings = clone + .backend .get_recordings_for_ensemble(ensemble.id) .await .unwrap(); @@ -93,18 +107,18 @@ impl EnsembleScreen { result } +} - pub fn set_back(&self, back: B) - where - B: Fn() -> () + 'static, - { - self.back.replace(Some(Box::new(back))); +impl NavigatorScreen for EnsembleScreen { + fn attach_navigator(&self, navigator: Rc) { + self.navigator.replace(Some(navigator)); } - pub fn set_recording_selected(&self, selected: S) - where - S: Fn(&RecordingDescription) -> () + 'static, - { - self.recording_list.set_selected(selected); + fn get_widget(&self) -> gtk::Widget { + self.widget.clone().upcast() + } + + fn detach_navigator(&self) { + self.navigator.replace(None); } } diff --git a/src/screens/person_screen.rs b/src/screens/person_screen.rs index 0dc6b95..a596a1c 100644 --- a/src/screens/person_screen.rs +++ b/src/screens/person_screen.rs @@ -1,3 +1,4 @@ +use super::*; use crate::backend::*; use crate::database::*; use crate::widgets::*; @@ -9,11 +10,12 @@ use std::cell::RefCell; use std::rc::Rc; pub struct PersonScreen { - pub widget: gtk::Box, + backend: Rc, + widget: gtk::Box, stack: gtk::Stack, work_list: Rc>, recording_list: Rc>, - back: RefCell () + 'static>>>, + navigator: RefCell>>, } impl PersonScreen { @@ -77,11 +79,12 @@ impl PersonScreen { recording_frame.add(&recording_list.widget); let result = Rc::new(Self { + backend, widget, stack, work_list, recording_list, - back: RefCell::new(None), + navigator: RefCell::new(None), }); search_entry.connect_search_changed(clone!(@strong result => move |_| { @@ -90,16 +93,43 @@ impl PersonScreen { })); back_button.connect_clicked(clone!(@strong result => move |_| { - if let Some(back) = &*result.back.borrow() { - back(); + let navigator = result.navigator.borrow().clone(); + if let Some(navigator) = navigator { + navigator.clone().pop(); } })); + result + .work_list + .set_selected(clone!(@strong result => move |work| { + let navigator = result.navigator.borrow().clone(); + if let Some(navigator) = navigator { + navigator.push(WorkScreen::new(result.backend.clone(), work.clone())); + } + })); + + result + .recording_list + .set_selected(clone!(@strong result => move |recording| { + let navigator = result.navigator.borrow().clone(); + if let Some(navigator) = navigator { + navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone())); + } + })); + let context = glib::MainContext::default(); let clone = result.clone(); context.spawn_local(async move { - let works = backend.get_work_descriptions(person.id).await.unwrap(); - let recordings = backend.get_recordings_for_person(person.id).await.unwrap(); + let works = clone + .backend + .get_work_descriptions(person.id) + .await + .unwrap(); + let recordings = clone + .backend + .get_recordings_for_person(person.id) + .await + .unwrap(); if works.is_empty() && recordings.is_empty() { clone.stack.set_visible_child_name("nothing"); @@ -122,25 +152,18 @@ impl PersonScreen { result } +} - pub fn set_back(&self, back: B) - where - B: Fn() -> () + 'static, - { - self.back.replace(Some(Box::new(back))); +impl NavigatorScreen for PersonScreen { + fn attach_navigator(&self, navigator: Rc) { + self.navigator.replace(Some(navigator)); } - pub fn set_work_selected(&self, selected: S) - where - S: Fn(&WorkDescription) -> () + 'static, - { - self.work_list.set_selected(selected); + fn get_widget(&self) -> gtk::Widget { + self.widget.clone().upcast() } - pub fn set_recording_selected(&self, selected: S) - where - S: Fn(&RecordingDescription) -> () + 'static, - { - self.recording_list.set_selected(selected); + fn detach_navigator(&self) { + self.navigator.replace(None); } } diff --git a/src/screens/recording_screen.rs b/src/screens/recording_screen.rs index 1159a58..2c1d503 100644 --- a/src/screens/recording_screen.rs +++ b/src/screens/recording_screen.rs @@ -1,5 +1,6 @@ use crate::backend::*; use crate::database::*; +use crate::widgets::*; use glib::clone; use gtk::prelude::*; use gtk_macros::get_widget; @@ -8,8 +9,8 @@ use std::cell::RefCell; use std::rc::Rc; pub struct RecordingScreen { - pub widget: gtk::Box, - back: RefCell () + 'static>>>, + widget: gtk::Box, + navigator: RefCell>>, } impl RecordingScreen { @@ -27,22 +28,30 @@ impl RecordingScreen { let result = Rc::new(Self { widget, - back: RefCell::new(None), + navigator: RefCell::new(None), }); back_button.connect_clicked(clone!(@strong result => move |_| { - if let Some(back) = &*result.back.borrow() { - back(); + let navigator = result.navigator.borrow().clone(); + if let Some(navigator) = navigator { + navigator.clone().pop(); } })); result } +} - pub fn set_back(&self, back: B) - where - B: Fn() -> () + 'static, - { - self.back.replace(Some(Box::new(back))); +impl NavigatorScreen for RecordingScreen { + fn attach_navigator(&self, navigator: Rc) { + self.navigator.replace(Some(navigator)); + } + + fn get_widget(&self) -> gtk::Widget { + self.widget.clone().upcast() + } + + fn detach_navigator(&self) { + self.navigator.replace(None); } } diff --git a/src/screens/work_screen.rs b/src/screens/work_screen.rs index 37b3a38..1c38f30 100644 --- a/src/screens/work_screen.rs +++ b/src/screens/work_screen.rs @@ -1,3 +1,4 @@ +use super::*; use crate::backend::*; use crate::database::*; use crate::widgets::*; @@ -9,10 +10,11 @@ use std::cell::RefCell; use std::rc::Rc; pub struct WorkScreen { - pub widget: gtk::Box, + backend: Rc, + widget: gtk::Box, stack: gtk::Stack, recording_list: Rc>, - back: RefCell () + 'static>>>, + navigator: RefCell>>, } impl WorkScreen { @@ -59,10 +61,11 @@ impl WorkScreen { recording_frame.add(&recording_list.widget); let result = Rc::new(Self { + backend, widget, stack, recording_list, - back: RefCell::new(None), + navigator: RefCell::new(None), }); search_entry.connect_search_changed(clone!(@strong result => move |_| { @@ -70,15 +73,29 @@ impl WorkScreen { })); back_button.connect_clicked(clone!(@strong result => move |_| { - if let Some(back) = &*result.back.borrow() { - back(); + let navigator = result.navigator.borrow().clone(); + if let Some(navigator) = navigator { + navigator.clone().pop(); } })); + result + .recording_list + .set_selected(clone!(@strong result => move |recording| { + let navigator = result.navigator.borrow().clone(); + if let Some(navigator) = navigator { + navigator.push(RecordingScreen::new(result.backend.clone(), recording.clone())); + } + })); + let context = glib::MainContext::default(); let clone = result.clone(); context.spawn_local(async move { - let recordings = backend.get_recordings_for_work(work.id).await.unwrap(); + let recordings = clone + .backend + .get_recordings_for_work(work.id) + .await + .unwrap(); if recordings.is_empty() { clone.stack.set_visible_child_name("nothing"); @@ -90,18 +107,18 @@ impl WorkScreen { result } +} - pub fn set_back(&self, back: B) - where - B: Fn() -> () + 'static, - { - self.back.replace(Some(Box::new(back))); +impl NavigatorScreen for WorkScreen { + fn attach_navigator(&self, navigator: Rc) { + self.navigator.replace(Some(navigator)); } - pub fn set_recording_selected(&self, selected: S) - where - S: Fn(&RecordingDescription) -> () + 'static, - { - self.recording_list.set_selected(selected); + fn get_widget(&self) -> gtk::Widget { + self.widget.clone().upcast() + } + + fn detach_navigator(&self) { + self.navigator.replace(None); } } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 353ebc4..b637738 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -1,6 +1,9 @@ pub mod list; pub use list::*; +pub mod navigator; +pub use navigator::*; + pub mod person_list; pub use person_list::*; @@ -9,6 +12,3 @@ pub use poe_list::*; pub mod selector_row; pub use selector_row::*; - -pub mod stack; -pub use stack::*; diff --git a/src/widgets/navigator.rs b/src/widgets/navigator.rs new file mode 100644 index 0000000..9274a31 --- /dev/null +++ b/src/widgets/navigator.rs @@ -0,0 +1,130 @@ +use glib::clone; +use gtk::prelude::*; +use std::cell::RefCell; +use std::rc::Rc; + +pub trait NavigatorScreen { + fn attach_navigator(&self, navigator: Rc); + fn get_widget(&self) -> gtk::Widget; + fn detach_navigator(&self); +} + +pub struct Navigator { + pub widget: gtk::Stack, + screens: RefCell>>, + old_screens: RefCell>>, +} + +impl Navigator { + pub fn new(empty_screen: &W) -> Rc + where + W: IsA, + { + let widget = gtk::Stack::new(); + widget.set_transition_type(gtk::StackTransitionType::Crossfade); + widget.set_hexpand(true); + widget.add_named(empty_screen, "empty_screen"); + widget.show(); + + let result = Rc::new(Self { + widget, + screens: RefCell::new(Vec::new()), + old_screens: RefCell::new(Vec::new()), + }); + + unsafe { + result.widget.connect_notify_unsafe( + Some("transition-running"), + clone!(@strong result => move |_, _| { + if !result.widget.get_transition_running() { + result.clear_old_screens(); + } + }), + ); + } + + result + } + + pub fn push(self: Rc, screen: Rc) + where + S: NavigatorScreen + 'static, + { + if let Some(screen) = self.screens.borrow().last() { + screen.detach_navigator(); + } + + let widget = screen.get_widget(); + self.widget.add(&widget); + self.widget.set_visible_child(&widget); + + screen.attach_navigator(self.clone()); + self.screens.borrow_mut().push(screen); + } + + pub fn pop(self: Rc) { + let popped = if let Some(screen) = self.screens.borrow_mut().pop() { + screen.detach_navigator(); + self.old_screens.borrow_mut().push(screen); + + true + } else { + false + }; + + if popped { + if let Some(screen) = self.screens.borrow().last() { + let widget = screen.get_widget(); + self.widget.set_visible_child(&widget); + + screen.attach_navigator(self.clone()); + } else { + self.widget.set_visible_child_name("empty_screen"); + } + + if !self.widget.get_transition_running() { + self.clear_old_screens(); + } + } + } + + pub fn replace(self: Rc, screen: Rc) + where + S: NavigatorScreen + 'static, + { + for screen in self.screens.replace(Vec::new()) { + screen.detach_navigator(); + self.old_screens.borrow_mut().push(screen); + } + + let widget = screen.get_widget(); + self.widget.add(&widget); + self.widget.set_visible_child(&widget); + + screen.attach_navigator(self.clone()); + self.screens.borrow_mut().push(screen); + + if !self.widget.get_transition_running() { + self.clear_old_screens(); + } + } + + pub fn reset(&self) { + for screen in self.screens.replace(Vec::new()) { + screen.detach_navigator(); + self.old_screens.borrow_mut().push(screen); + } + + if !self.widget.get_transition_running() { + self.clear_old_screens(); + } + } + + fn clear_old_screens(&self) { + for screen in self.old_screens.borrow().iter() { + self.widget.remove(&screen.get_widget()); + } + + self.old_screens.borrow_mut().clear(); + } +} diff --git a/src/widgets/stack.rs b/src/widgets/stack.rs deleted file mode 100644 index 2dd0fa4..0000000 --- a/src/widgets/stack.rs +++ /dev/null @@ -1,77 +0,0 @@ -use glib::clone; -use gtk::prelude::*; -use std::cell::RefCell; - -pub struct Stack { - pub widget: gtk::Stack, - old_children: RefCell>, - current_child: RefCell>, -} - -impl Stack { - pub fn new(empty_screen: &W) -> Self - where - W: IsA, - { - let old_children = RefCell::new(Vec::new()); - - let widget = gtk::Stack::new(); - widget.set_transition_type(gtk::StackTransitionType::Crossfade); - widget.set_hexpand(true); - widget.add_named(empty_screen, "empty_screen"); - - unsafe { - widget.connect_notify_unsafe( - Some("transition-running"), - clone!(@strong old_children => move |stack, _| { - for child in old_children.borrow().iter() { - stack.remove(child); - } - - old_children.borrow_mut().clear(); - }), - ); - } - - widget.show(); - - Self { - widget: widget.clone(), - old_children, - current_child: RefCell::new(None), - } - } - - pub fn set_child(&self, child: W) - where - W: IsA, - { - if let Some(child) = self.current_child.borrow_mut().take() { - self.old_children.borrow_mut().push(child); - } - - self.current_child.replace(Some(child.clone().upcast())); - self.widget.add(&child); - self.widget.set_visible_child(&child); - - if !self.widget.get_transition_running() { - for child in self.old_children.borrow().iter() { - self.widget.remove(child); - } - - self.old_children.borrow_mut().clear(); - } - } - - pub fn reset_child(&self) { - self.widget.set_visible_child_name("empty_screen"); - - if !self.widget.get_transition_running() { - for child in self.old_children.borrow().iter() { - self.widget.remove(child); - } - - self.old_children.borrow_mut().clear(); - } - } -} diff --git a/src/window.rs b/src/window.rs index 2581ced..31a7c71 100644 --- a/src/window.rs +++ b/src/window.rs @@ -15,7 +15,7 @@ pub struct Window { leaflet: libhandy::Leaflet, sidebar_box: gtk::Box, poe_list: Rc, - stack: Stack, + navigator: Rc, } impl Window { @@ -29,7 +29,7 @@ impl Window { let backend = Rc::new(Backend::new("test.sqlite")); let poe_list = PoeList::new(backend.clone()); - let stack = Stack::new(&empty_screen); + let navigator = Navigator::new(&empty_screen); let result = Rc::new(Self { backend, @@ -37,78 +37,24 @@ impl Window { leaflet, sidebar_box, poe_list, - stack, + navigator, }); result .poe_list .set_selected(clone!(@strong result => move |poe| { - result.leaflet.set_visible_child(&result.stack.widget); + result.leaflet.set_visible_child(&result.navigator.widget); match poe { PersonOrEnsemble::Person(person) => { - let person_screen = Rc::new(PersonScreen::new(result.backend.clone(), person.clone())); - - person_screen.set_back(clone!(@strong result => move || { - result.leaflet.set_visible_child(&result.sidebar_box); - result.stack.reset_child(); - })); - - person_screen.set_work_selected(clone!(@strong result, @strong person_screen => move |work| { - let work_screen = Rc::new(WorkScreen::new(result.backend.clone(), work.clone())); - - work_screen.set_back(clone!(@strong result, @strong person_screen => move || { - result.stack.set_child(person_screen.widget.clone()); - })); - - work_screen.set_recording_selected(clone!(@strong result, @strong work_screen => move |recording| { - let recording_screen = RecordingScreen::new(result.backend.clone(), recording.clone()); - - recording_screen.set_back(clone!(@strong result, @strong work_screen => move || { - result.stack.set_child(work_screen.widget.clone()); - })); - - result.stack.set_child(recording_screen.widget.clone()); - })); - - result.stack.set_child(work_screen.widget.clone()); - })); - - person_screen.set_recording_selected(clone!(@strong result, @strong person_screen => move |recording| { - let recording_screen = Rc::new(RecordingScreen::new(result.backend.clone(), recording.clone())); - - recording_screen.set_back(clone!(@strong result, @strong person_screen => move || { - result.stack.set_child(person_screen.widget.clone()); - })); - - result.stack.set_child(recording_screen.widget.clone()); - })); - - result.stack.set_child(person_screen.widget.clone()); + result.navigator.clone().replace(PersonScreen::new(result.backend.clone(), person.clone())); } PersonOrEnsemble::Ensemble(ensemble) => { - let ensemble_screen = EnsembleScreen::new(result.backend.clone(), ensemble.clone()); - - ensemble_screen.set_back(clone!(@strong result => move || { - result.leaflet.set_visible_child(&result.sidebar_box); - result.stack.reset_child(); - })); - - ensemble_screen.set_recording_selected(clone!(@strong result, @strong ensemble_screen => move |recording| { - let recording_screen = Rc::new(RecordingScreen::new(result.backend.clone(), recording.clone())); - - recording_screen.set_back(clone!(@strong result, @strong ensemble_screen => move || { - result.stack.set_child(ensemble_screen.widget.clone()); - })); - - result.stack.set_child(recording_screen.widget.clone()); - })); - - result.stack.set_child(ensemble_screen.widget.clone()); + result.navigator.clone().replace(EnsembleScreen::new(result.backend.clone(), ensemble.clone())); } } })); - result.leaflet.add(&result.stack.widget); + result.leaflet.add(&result.navigator.widget); result .sidebar_box .pack_start(&result.poe_list.widget, true, true, 0); @@ -237,7 +183,7 @@ impl Window { fn reload(&self) { self.poe_list.clone().reload(); - self.stack.reset_child(); + self.navigator.reset(); self.leaflet.set_visible_child(&self.sidebar_box); } }