Replace stack with new navigator widget

This commit is contained in:
Elias Projahn 2020-10-17 11:31:43 +02:00
parent cefd7dad95
commit 9a9a181739
8 changed files with 268 additions and 206 deletions

View file

@ -1,3 +1,4 @@
use super::*;
use crate::backend::*; use crate::backend::*;
use crate::database::*; use crate::database::*;
use crate::widgets::*; use crate::widgets::*;
@ -9,10 +10,11 @@ use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
pub struct EnsembleScreen { pub struct EnsembleScreen {
pub widget: gtk::Box, backend: Rc<Backend>,
widget: gtk::Box,
stack: gtk::Stack, stack: gtk::Stack,
recording_list: Rc<List<RecordingDescription>>, recording_list: Rc<List<RecordingDescription>>,
back: RefCell<Option<Box<dyn Fn() -> () + 'static>>>, navigator: RefCell<Option<Rc<Navigator>>>,
} }
impl EnsembleScreen { impl EnsembleScreen {
@ -59,10 +61,11 @@ impl EnsembleScreen {
recording_frame.add(&recording_list.widget.clone()); recording_frame.add(&recording_list.widget.clone());
let result = Rc::new(Self { let result = Rc::new(Self {
backend,
widget, widget,
stack, stack,
recording_list, recording_list,
back: RefCell::new(None), navigator: RefCell::new(None),
}); });
search_entry.connect_search_changed(clone!(@strong result => move |_| { search_entry.connect_search_changed(clone!(@strong result => move |_| {
@ -70,15 +73,26 @@ impl EnsembleScreen {
})); }));
back_button.connect_clicked(clone!(@strong result => move |_| { back_button.connect_clicked(clone!(@strong result => move |_| {
if let Some(back) = &*result.back.borrow() { let navigator = result.navigator.borrow().clone();
back(); 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 context = glib::MainContext::default();
let clone = result.clone(); let clone = result.clone();
context.spawn_local(async move { context.spawn_local(async move {
let recordings = backend let recordings = clone
.backend
.get_recordings_for_ensemble(ensemble.id) .get_recordings_for_ensemble(ensemble.id)
.await .await
.unwrap(); .unwrap();
@ -93,18 +107,18 @@ impl EnsembleScreen {
result result
} }
}
pub fn set_back<B>(&self, back: B) impl NavigatorScreen for EnsembleScreen {
where fn attach_navigator(&self, navigator: Rc<Navigator>) {
B: Fn() -> () + 'static, self.navigator.replace(Some(navigator));
{
self.back.replace(Some(Box::new(back)));
} }
pub fn set_recording_selected<S>(&self, selected: S) fn get_widget(&self) -> gtk::Widget {
where self.widget.clone().upcast()
S: Fn(&RecordingDescription) -> () + 'static, }
{
self.recording_list.set_selected(selected); fn detach_navigator(&self) {
self.navigator.replace(None);
} }
} }

View file

@ -1,3 +1,4 @@
use super::*;
use crate::backend::*; use crate::backend::*;
use crate::database::*; use crate::database::*;
use crate::widgets::*; use crate::widgets::*;
@ -9,11 +10,12 @@ use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
pub struct PersonScreen { pub struct PersonScreen {
pub widget: gtk::Box, backend: Rc<Backend>,
widget: gtk::Box,
stack: gtk::Stack, stack: gtk::Stack,
work_list: Rc<List<WorkDescription>>, work_list: Rc<List<WorkDescription>>,
recording_list: Rc<List<RecordingDescription>>, recording_list: Rc<List<RecordingDescription>>,
back: RefCell<Option<Box<dyn Fn() -> () + 'static>>>, navigator: RefCell<Option<Rc<Navigator>>>,
} }
impl PersonScreen { impl PersonScreen {
@ -77,11 +79,12 @@ impl PersonScreen {
recording_frame.add(&recording_list.widget); recording_frame.add(&recording_list.widget);
let result = Rc::new(Self { let result = Rc::new(Self {
backend,
widget, widget,
stack, stack,
work_list, work_list,
recording_list, recording_list,
back: RefCell::new(None), navigator: RefCell::new(None),
}); });
search_entry.connect_search_changed(clone!(@strong result => move |_| { search_entry.connect_search_changed(clone!(@strong result => move |_| {
@ -90,16 +93,43 @@ impl PersonScreen {
})); }));
back_button.connect_clicked(clone!(@strong result => move |_| { back_button.connect_clicked(clone!(@strong result => move |_| {
if let Some(back) = &*result.back.borrow() { let navigator = result.navigator.borrow().clone();
back(); 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 context = glib::MainContext::default();
let clone = result.clone(); let clone = result.clone();
context.spawn_local(async move { context.spawn_local(async move {
let works = backend.get_work_descriptions(person.id).await.unwrap(); let works = clone
let recordings = backend.get_recordings_for_person(person.id).await.unwrap(); .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() { if works.is_empty() && recordings.is_empty() {
clone.stack.set_visible_child_name("nothing"); clone.stack.set_visible_child_name("nothing");
@ -122,25 +152,18 @@ impl PersonScreen {
result result
} }
}
pub fn set_back<B>(&self, back: B) impl NavigatorScreen for PersonScreen {
where fn attach_navigator(&self, navigator: Rc<Navigator>) {
B: Fn() -> () + 'static, self.navigator.replace(Some(navigator));
{
self.back.replace(Some(Box::new(back)));
} }
pub fn set_work_selected<S>(&self, selected: S) fn get_widget(&self) -> gtk::Widget {
where self.widget.clone().upcast()
S: Fn(&WorkDescription) -> () + 'static,
{
self.work_list.set_selected(selected);
} }
pub fn set_recording_selected<S>(&self, selected: S) fn detach_navigator(&self) {
where self.navigator.replace(None);
S: Fn(&RecordingDescription) -> () + 'static,
{
self.recording_list.set_selected(selected);
} }
} }

View file

@ -1,5 +1,6 @@
use crate::backend::*; use crate::backend::*;
use crate::database::*; use crate::database::*;
use crate::widgets::*;
use glib::clone; use glib::clone;
use gtk::prelude::*; use gtk::prelude::*;
use gtk_macros::get_widget; use gtk_macros::get_widget;
@ -8,8 +9,8 @@ use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
pub struct RecordingScreen { pub struct RecordingScreen {
pub widget: gtk::Box, widget: gtk::Box,
back: RefCell<Option<Box<dyn Fn() -> () + 'static>>>, navigator: RefCell<Option<Rc<Navigator>>>,
} }
impl RecordingScreen { impl RecordingScreen {
@ -27,22 +28,30 @@ impl RecordingScreen {
let result = Rc::new(Self { let result = Rc::new(Self {
widget, widget,
back: RefCell::new(None), navigator: RefCell::new(None),
}); });
back_button.connect_clicked(clone!(@strong result => move |_| { back_button.connect_clicked(clone!(@strong result => move |_| {
if let Some(back) = &*result.back.borrow() { let navigator = result.navigator.borrow().clone();
back(); if let Some(navigator) = navigator {
navigator.clone().pop();
} }
})); }));
result result
} }
}
pub fn set_back<B>(&self, back: B) impl NavigatorScreen for RecordingScreen {
where fn attach_navigator(&self, navigator: Rc<Navigator>) {
B: Fn() -> () + 'static, self.navigator.replace(Some(navigator));
{ }
self.back.replace(Some(Box::new(back)));
fn get_widget(&self) -> gtk::Widget {
self.widget.clone().upcast()
}
fn detach_navigator(&self) {
self.navigator.replace(None);
} }
} }

View file

@ -1,3 +1,4 @@
use super::*;
use crate::backend::*; use crate::backend::*;
use crate::database::*; use crate::database::*;
use crate::widgets::*; use crate::widgets::*;
@ -9,10 +10,11 @@ use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
pub struct WorkScreen { pub struct WorkScreen {
pub widget: gtk::Box, backend: Rc<Backend>,
widget: gtk::Box,
stack: gtk::Stack, stack: gtk::Stack,
recording_list: Rc<List<RecordingDescription>>, recording_list: Rc<List<RecordingDescription>>,
back: RefCell<Option<Box<dyn Fn() -> () + 'static>>>, navigator: RefCell<Option<Rc<Navigator>>>,
} }
impl WorkScreen { impl WorkScreen {
@ -59,10 +61,11 @@ impl WorkScreen {
recording_frame.add(&recording_list.widget); recording_frame.add(&recording_list.widget);
let result = Rc::new(Self { let result = Rc::new(Self {
backend,
widget, widget,
stack, stack,
recording_list, recording_list,
back: RefCell::new(None), navigator: RefCell::new(None),
}); });
search_entry.connect_search_changed(clone!(@strong result => move |_| { search_entry.connect_search_changed(clone!(@strong result => move |_| {
@ -70,15 +73,29 @@ impl WorkScreen {
})); }));
back_button.connect_clicked(clone!(@strong result => move |_| { back_button.connect_clicked(clone!(@strong result => move |_| {
if let Some(back) = &*result.back.borrow() { let navigator = result.navigator.borrow().clone();
back(); 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 context = glib::MainContext::default();
let clone = result.clone(); let clone = result.clone();
context.spawn_local(async move { 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() { if recordings.is_empty() {
clone.stack.set_visible_child_name("nothing"); clone.stack.set_visible_child_name("nothing");
@ -90,18 +107,18 @@ impl WorkScreen {
result result
} }
}
pub fn set_back<B>(&self, back: B) impl NavigatorScreen for WorkScreen {
where fn attach_navigator(&self, navigator: Rc<Navigator>) {
B: Fn() -> () + 'static, self.navigator.replace(Some(navigator));
{
self.back.replace(Some(Box::new(back)));
} }
pub fn set_recording_selected<S>(&self, selected: S) fn get_widget(&self) -> gtk::Widget {
where self.widget.clone().upcast()
S: Fn(&RecordingDescription) -> () + 'static, }
{
self.recording_list.set_selected(selected); fn detach_navigator(&self) {
self.navigator.replace(None);
} }
} }

View file

@ -1,6 +1,9 @@
pub mod list; pub mod list;
pub use list::*; pub use list::*;
pub mod navigator;
pub use navigator::*;
pub mod person_list; pub mod person_list;
pub use person_list::*; pub use person_list::*;
@ -9,6 +12,3 @@ pub use poe_list::*;
pub mod selector_row; pub mod selector_row;
pub use selector_row::*; pub use selector_row::*;
pub mod stack;
pub use stack::*;

130
src/widgets/navigator.rs Normal file
View file

@ -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<Navigator>);
fn get_widget(&self) -> gtk::Widget;
fn detach_navigator(&self);
}
pub struct Navigator {
pub widget: gtk::Stack,
screens: RefCell<Vec<Rc<dyn NavigatorScreen>>>,
old_screens: RefCell<Vec<Rc<dyn NavigatorScreen>>>,
}
impl Navigator {
pub fn new<W>(empty_screen: &W) -> Rc<Self>
where
W: IsA<gtk::Widget>,
{
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<S>(self: Rc<Self>, screen: Rc<S>)
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<Self>) {
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<S>(self: Rc<Self>, screen: Rc<S>)
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();
}
}

View file

@ -1,77 +0,0 @@
use glib::clone;
use gtk::prelude::*;
use std::cell::RefCell;
pub struct Stack {
pub widget: gtk::Stack,
old_children: RefCell<Vec<gtk::Widget>>,
current_child: RefCell<Option<gtk::Widget>>,
}
impl Stack {
pub fn new<W>(empty_screen: &W) -> Self
where
W: IsA<gtk::Widget>,
{
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<W>(&self, child: W)
where
W: IsA<gtk::Widget>,
{
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();
}
}
}

View file

@ -15,7 +15,7 @@ pub struct Window {
leaflet: libhandy::Leaflet, leaflet: libhandy::Leaflet,
sidebar_box: gtk::Box, sidebar_box: gtk::Box,
poe_list: Rc<PoeList>, poe_list: Rc<PoeList>,
stack: Stack, navigator: Rc<Navigator>,
} }
impl Window { impl Window {
@ -29,7 +29,7 @@ impl Window {
let backend = Rc::new(Backend::new("test.sqlite")); let backend = Rc::new(Backend::new("test.sqlite"));
let poe_list = PoeList::new(backend.clone()); let poe_list = PoeList::new(backend.clone());
let stack = Stack::new(&empty_screen); let navigator = Navigator::new(&empty_screen);
let result = Rc::new(Self { let result = Rc::new(Self {
backend, backend,
@ -37,78 +37,24 @@ impl Window {
leaflet, leaflet,
sidebar_box, sidebar_box,
poe_list, poe_list,
stack, navigator,
}); });
result result
.poe_list .poe_list
.set_selected(clone!(@strong result => move |poe| { .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 { match poe {
PersonOrEnsemble::Person(person) => { PersonOrEnsemble::Person(person) => {
let person_screen = Rc::new(PersonScreen::new(result.backend.clone(), person.clone())); result.navigator.clone().replace(PersonScreen::new(result.backend.clone(), person.clone()));
person_screen.set_back(clone!(@strong result => move || {
result.leaflet.set_visible_child(&result.sidebar_box);
result.stack.reset_child();
}));
person_screen.set_work_selected(clone!(@strong result, @strong person_screen => move |work| {
let work_screen = Rc::new(WorkScreen::new(result.backend.clone(), work.clone()));
work_screen.set_back(clone!(@strong result, @strong person_screen => move || {
result.stack.set_child(person_screen.widget.clone());
}));
work_screen.set_recording_selected(clone!(@strong result, @strong work_screen => move |recording| {
let recording_screen = RecordingScreen::new(result.backend.clone(), recording.clone());
recording_screen.set_back(clone!(@strong result, @strong work_screen => move || {
result.stack.set_child(work_screen.widget.clone());
}));
result.stack.set_child(recording_screen.widget.clone());
}));
result.stack.set_child(work_screen.widget.clone());
}));
person_screen.set_recording_selected(clone!(@strong result, @strong person_screen => move |recording| {
let recording_screen = Rc::new(RecordingScreen::new(result.backend.clone(), recording.clone()));
recording_screen.set_back(clone!(@strong result, @strong person_screen => move || {
result.stack.set_child(person_screen.widget.clone());
}));
result.stack.set_child(recording_screen.widget.clone());
}));
result.stack.set_child(person_screen.widget.clone());
} }
PersonOrEnsemble::Ensemble(ensemble) => { PersonOrEnsemble::Ensemble(ensemble) => {
let ensemble_screen = EnsembleScreen::new(result.backend.clone(), ensemble.clone()); result.navigator.clone().replace(EnsembleScreen::new(result.backend.clone(), ensemble.clone()));
ensemble_screen.set_back(clone!(@strong result => move || {
result.leaflet.set_visible_child(&result.sidebar_box);
result.stack.reset_child();
}));
ensemble_screen.set_recording_selected(clone!(@strong result, @strong ensemble_screen => move |recording| {
let recording_screen = Rc::new(RecordingScreen::new(result.backend.clone(), recording.clone()));
recording_screen.set_back(clone!(@strong result, @strong ensemble_screen => move || {
result.stack.set_child(ensemble_screen.widget.clone());
}));
result.stack.set_child(recording_screen.widget.clone());
}));
result.stack.set_child(ensemble_screen.widget.clone());
} }
} }
})); }));
result.leaflet.add(&result.stack.widget); result.leaflet.add(&result.navigator.widget);
result result
.sidebar_box .sidebar_box
.pack_start(&result.poe_list.widget, true, true, 0); .pack_start(&result.poe_list.widget, true, true, 0);
@ -237,7 +183,7 @@ impl Window {
fn reload(&self) { fn reload(&self) {
self.poe_list.clone().reload(); self.poe_list.clone().reload();
self.stack.reset_child(); self.navigator.reset();
self.leaflet.set_visible_child(&self.sidebar_box); self.leaflet.set_visible_child(&self.sidebar_box);
} }
} }