diff options
| author | Joe Carstairs <me@joeac.net> | 2025-08-25 18:22:00 +0100 |
|---|---|---|
| committer | Joe Carstairs <me@joeac.net> | 2025-08-25 18:22:00 +0100 |
| commit | 86487b8ff6e691f2b261a93733df8f4cc5dc7320 (patch) | |
| tree | 8d116acb31fe3bf8ce5d4201ae0bbc36ce6cc38a /schist_desktop_gui/src | |
| parent | fe85652e4d3e8906824baddaafd05d19105579b8 (diff) | |
refactor navigation
Diffstat (limited to 'schist_desktop_gui/src')
9 files changed, 265 insertions, 283 deletions
diff --git a/schist_desktop_gui/src/gui/components/bucket_name_and_balance.rs b/schist_desktop_gui/src/gui/components/bucket_name_and_balance.rs index 24a3952..b08729c 100644 --- a/schist_desktop_gui/src/gui/components/bucket_name_and_balance.rs +++ b/schist_desktop_gui/src/gui/components/bucket_name_and_balance.rs @@ -14,11 +14,18 @@ impl BucketNameAndBalance { } } -impl<'a> Into<Element<'a, navigation::Message<BucketNameAndBalance>>> for BucketNameAndBalance { - fn into(self) -> Element<'a, navigation::Message<BucketNameAndBalance>> { +impl<'a> From<&'a BucketNameAndBalance> for Element<'a, navigation::Message<BucketNameAndBalance>> { + fn from(bucket_name_and_balance: &'a BucketNameAndBalance) -> Self { row![ - Text::new(&self.bucket.name).as_element(), - Text::new(&self.bucket.balance.unwrap_or(0).to_string()).as_element(), + Text::new(&bucket_name_and_balance.bucket.name).as_element(), + Text::new( + &bucket_name_and_balance + .bucket + .balance + .unwrap_or(0) + .to_string() + ) + .as_element(), ] .into() } diff --git a/schist_desktop_gui/src/gui/components/button.rs b/schist_desktop_gui/src/gui/components/button.rs index e071f33..6882273 100644 --- a/schist_desktop_gui/src/gui/components/button.rs +++ b/schist_desktop_gui/src/gui/components/button.rs @@ -2,9 +2,10 @@ use iced::{widget::Button, Element}; use crate::style::*; -pub fn active_button<'a, Content: Into<Element<'a, Message>>, Message>( - content: Content, -) -> Button<'a, Message> { +pub fn active_button<'a, Content, Message>(content: &'a Content) -> Button<'a, Message> +where + Element<'a, Message>: From<&'a Content>, +{ iced::widget::button(content) .style(|theme: &iced::Theme, _status| iced::widget::button::Style { background: Some(iced::Background::Color( diff --git a/schist_desktop_gui/src/gui/components/navigation.rs b/schist_desktop_gui/src/gui/components/navigation.rs index bed3221..6dbe622 100644 --- a/schist_desktop_gui/src/gui/components/navigation.rs +++ b/schist_desktop_gui/src/gui/components/navigation.rs @@ -1,19 +1,9 @@ -mod new_navigation; mod update_navigation; mod view_navigation; mod view_navigation_button; -use new_navigation::new_navigation; -use update_navigation::update_navigation; -use view_navigation::view_navigation; -use view_navigation_button::view_navigation_button; - use std::fmt::Debug; -use iced::Element; - -use crate::traits::{Component, Viewable}; - #[derive(Clone, Debug)] pub struct Navigation<TOption> { options_before_active: Vec<TOption>, @@ -36,33 +26,67 @@ pub enum Action<TOption> { impl<TOption: Clone + Debug + PartialEq> Navigation<TOption> { pub fn new(active_option: Option<TOption>, options: Vec<TOption>) -> Self { - new_navigation(active_option, options) + let active_option_index = active_option.clone().map_or(Option::None, |active_option| { + options.iter().position(|option| option.eq(&active_option)) + }); + if let Some(active_option_index) = active_option_index { + let split = options.split_at(active_option_index); + Navigation { + options_before_active: split.0.to_vec(), + active_option: active_option, + options_after_active: split.1.split_at(1).1.to_vec(), + } + } else { + Navigation { + options_before_active: options, + active_option: active_option, + options_after_active: vec![], + } + } } } -impl<'a, TOption> Navigation<TOption> -where - TOption: Clone + PartialEq + Into<Element<'a, Message<TOption>>>, -{ - pub fn button(&'a self, option: &'a TOption) -> Element<'a, Message<TOption>> { - view_navigation_button(self, option) +#[cfg(test)] +mod test { + use super::Navigation; + + #[test] + fn when_active_option_at_start_of_options_then_finds_active_option() { + let navigation = Navigation::new(Some(1), vec![1, 2, 3, 4, 5]); + assert_eq!(Vec::<i32>::new(), navigation.options_before_active); + assert_eq!(Some(1), navigation.active_option); + assert_eq!(vec![2, 3, 4, 5], navigation.options_after_active); } -} -impl<'a, TOption> Viewable<'a, Message<TOption>> for Navigation<TOption> -where - TOption: Clone + PartialEq + Into<Element<'a, Message<TOption>>>, -{ - fn view(&'a self) -> iced::Element<'a, Message<TOption>> { - view_navigation(self) + #[test] + fn when_active_option_in_middle_of_options_then_finds_active_option() { + let navigation = Navigation::new(Some(3), vec![1, 2, 3, 4, 5]); + assert_eq!(vec![1, 2], navigation.options_before_active); + assert_eq!(Some(3), navigation.active_option); + assert_eq!(vec![4, 5], navigation.options_after_active); + } + + #[test] + fn when_active_option_at_end_of_options_then_finds_active_option() { + let navigation = Navigation::new(Some(5), vec![1, 2, 3, 4, 5]); + assert_eq!(vec![1, 2, 3, 4], navigation.options_before_active); + assert_eq!(Some(5), navigation.active_option); + assert_eq!(Vec::<i32>::new(), navigation.options_after_active); + } + + #[test] + fn when_active_option_not_in_options_then_appends_active_option() { + let navigation = Navigation::new(Some(6), vec![1, 2, 3, 4, 5]); + assert_eq!(vec![1, 2, 3, 4, 5], navigation.options_before_active); + assert_eq!(Some(6), navigation.active_option); + assert_eq!(Vec::<i32>::new(), navigation.options_after_active); } -} -impl<'a, TOption> Component<'a, Message<TOption>, Action<TOption>> for Navigation<TOption> -where - TOption: Clone + PartialEq + Into<Element<'a, Message<TOption>>>, -{ - fn update(&mut self, message: Message<TOption>) -> Action<TOption> { - update_navigation(self, message) + #[test] + fn when_no_active_option_then_all_options_are_before_active_option() { + let navigation = Navigation::new(None, vec![1, 2, 3, 4, 5]); + assert_eq!(vec![1, 2, 3, 4, 5], navigation.options_before_active); + assert_eq!(None, navigation.active_option); + assert_eq!(Vec::<i32>::new(), navigation.options_after_active); } } diff --git a/schist_desktop_gui/src/gui/components/navigation/new_navigation.rs b/schist_desktop_gui/src/gui/components/navigation/new_navigation.rs deleted file mode 100644 index b5c0c19..0000000 --- a/schist_desktop_gui/src/gui/components/navigation/new_navigation.rs +++ /dev/null @@ -1,72 +0,0 @@ -use super::Navigation; - -pub fn new_navigation<TOption>( - active_option: Option<TOption>, - options: Vec<TOption>, -) -> Navigation<TOption> -where - TOption: Clone + PartialEq, -{ - let active_option_index = active_option.clone().map_or(Option::None, |active_option| { - options.iter().position(|option| option.eq(&active_option)) - }); - if let Some(active_option_index) = active_option_index { - let split = options.split_at(active_option_index); - Navigation { - options_before_active: split.0.to_vec(), - active_option, - options_after_active: split.1.split_at(1).1.to_vec(), - } - } else { - Navigation { - options_before_active: options, - active_option, - options_after_active: vec![], - } - } -} - -#[cfg(test)] -mod test { - use super::Navigation; - - #[test] - fn when_active_option_at_start_of_options_then_finds_active_option() { - let navigation = Navigation::new(Some(1), vec![1, 2, 3, 4, 5]); - assert_eq!(Vec::<i32>::new(), navigation.options_before_active); - assert_eq!(Some(1), navigation.active_option); - assert_eq!(vec![2, 3, 4, 5], navigation.options_after_active); - } - - #[test] - fn when_active_option_in_middle_of_options_then_finds_active_option() { - let navigation = Navigation::new(Some(3), vec![1, 2, 3, 4, 5]); - assert_eq!(vec![1, 2], navigation.options_before_active); - assert_eq!(Some(3), navigation.active_option); - assert_eq!(vec![4, 5], navigation.options_after_active); - } - - #[test] - fn when_active_option_at_end_of_options_then_finds_active_option() { - let navigation = Navigation::new(Some(5), vec![1, 2, 3, 4, 5]); - assert_eq!(vec![1, 2, 3, 4], navigation.options_before_active); - assert_eq!(Some(5), navigation.active_option); - assert_eq!(Vec::<i32>::new(), navigation.options_after_active); - } - - #[test] - fn when_active_option_not_in_options_then_appends_active_option() { - let navigation = Navigation::new(Some(6), vec![1, 2, 3, 4, 5]); - assert_eq!(vec![1, 2, 3, 4, 5], navigation.options_before_active); - assert_eq!(Some(6), navigation.active_option); - assert_eq!(Vec::<i32>::new(), navigation.options_after_active); - } - - #[test] - fn when_no_active_option_then_all_options_are_before_active_option() { - let navigation = Navigation::new(None, vec![1, 2, 3, 4, 5]); - assert_eq!(vec![1, 2, 3, 4, 5], navigation.options_before_active); - assert_eq!(None, navigation.active_option); - assert_eq!(Vec::<i32>::new(), navigation.options_after_active); - } -} diff --git a/schist_desktop_gui/src/gui/components/navigation/update_navigation.rs b/schist_desktop_gui/src/gui/components/navigation/update_navigation.rs index f86867f..c44ddd8 100644 --- a/schist_desktop_gui/src/gui/components/navigation/update_navigation.rs +++ b/schist_desktop_gui/src/gui/components/navigation/update_navigation.rs @@ -1,127 +1,130 @@ use iced::Element; +use crate::traits::Component; + use super::{Action, Message, Navigation}; -pub fn update_navigation<'a, TOption>( - navigation: &mut Navigation<TOption>, - message: Message<TOption>, -) -> Action<TOption> +impl<'a, TOption> Component<'a, Message<TOption>, Action<TOption>> for Navigation<TOption> where - TOption: Clone + PartialEq + Into<Element<'a, Message<TOption>>>, + TOption: Clone + PartialEq + 'a, + Element<'a, Message<TOption>>: From<&'a TOption>, { - match message { - Message::SelectOption(option) => { - if navigation - .active_option - .as_ref() - .is_some_and(|active_option| active_option.clone() == option) - { - Action::None - } else if let Some(index) = navigation - .options_before_active - .iter() - .position(|o| o.clone() == option) - { - let split = navigation.options_before_active.split_at(index); - navigation.options_after_active = vec![ + fn update(&mut self, message: Message<TOption>) -> Action<TOption> { + let navigation: &mut Navigation<TOption> = self; + match message { + Message::SelectOption(option) => { + if navigation + .active_option + .as_ref() + .is_some_and(|active_option| TOption::eq(active_option, &option)) + { + Action::None + } else if let Some(index) = navigation + .options_before_active + .iter() + .position(|o| TOption::eq(o, &option)) + { + let split = navigation.options_before_active.split_at(index); + navigation.options_after_active = vec![ + navigation + .active_option + .clone() + .map_or(vec![], |active_option| vec![active_option]), + split.1.split_at(1).1.to_vec(), + navigation.options_after_active.clone(), + ] + .concat(); + navigation.options_before_active = split.0.to_vec(); + navigation.active_option = Some(option.clone()); + Action::SelectOption(option) + } else if let Some(index) = navigation + .options_after_active + .iter() + .position(|o| TOption::eq(o, &option)) + { + let split = navigation.options_after_active.split_at(index); + navigation.options_before_active = vec![ + navigation.options_before_active.clone(), + split.0.to_vec(), + navigation + .active_option + .clone() + .map_or(vec![], |active_option| vec![active_option]), + ] + .concat(); + navigation.active_option = Some(option.clone()); + navigation.options_after_active = split.1.split_at(1).1.to_vec(); + Action::SelectOption(option) + } else { + if let Some(active_option) = &navigation.active_option { + navigation.options_before_active.push(active_option.clone()); + } navigation - .active_option - .clone() - .map_or(vec![], |active_option| vec![active_option]), - split.1.split_at(1).1.to_vec(), - navigation.options_after_active.clone(), - ] - .concat(); - navigation.options_before_active = split.0.to_vec(); - navigation.active_option = Some(option.clone()); - Action::SelectOption(option) - } else if let Some(index) = navigation - .options_after_active - .iter() - .position(|o| o.clone() == option) - { - let split = navigation.options_after_active.split_at(index); - navigation.options_before_active = vec![ - navigation.options_before_active.clone(), - split.0.to_vec(), + .options_before_active + .append(&mut navigation.options_after_active); + navigation.active_option = Some(option.clone()); + navigation.options_after_active = vec![]; + Action::SelectOption(option) + } + } + Message::SetOptions(options) => { + let active_option_index = navigation .active_option .clone() - .map_or(vec![], |active_option| vec![active_option]), - ] - .concat(); - navigation.active_option = Some(option.clone()); - navigation.options_after_active = split.1.split_at(1).1.to_vec(); - Action::SelectOption(option) - } else { - if let Some(active_option) = &navigation.active_option { - navigation.options_before_active.push(active_option.clone()); + .map_or(Option::None, |active_option| { + options + .iter() + .position(|option| active_option == option.clone()) + }); + if let Some(active_option_index) = active_option_index { + let split = options.split_at(active_option_index); + navigation.options_before_active = split.0.to_vec(); + navigation.options_after_active = split.1.split_at(1).1.to_vec(); + Action::None + } else { + navigation.options_before_active = options + .split_last() + .map_or_else(Vec::new, |(_last, rest)| rest.to_vec()); + navigation.active_option = options.last().cloned(); + options + .last() + .cloned() + .map_or(Action::None, Action::SelectOption) } - navigation - .options_before_active - .append(&mut navigation.options_after_active); - navigation.active_option = Some(option.clone()); - navigation.options_after_active = vec![]; - Action::SelectOption(option) } - } - Message::SetOptions(options) => { - let active_option_index = - navigation - .active_option - .clone() - .map_or(Option::None, |active_option| { - options - .iter() - .position(|option| active_option == option.clone()) - }); - if let Some(active_option_index) = active_option_index { - let split = options.split_at(active_option_index); - navigation.options_before_active = split.0.to_vec(); - navigation.options_after_active = split.1.split_at(1).1.to_vec(); - Action::None - } else { - navigation.options_before_active = options - .split_last() - .map_or_else(Vec::new, |(_last, rest)| rest.to_vec()); - navigation.active_option = options.last().cloned(); - options - .last() - .cloned() - .map_or(Action::None, Action::SelectOption) - } - } - Message::SelectNext => { - if let Some(next) = navigation.options_after_active.clone().first() { - navigation.options_before_active = vec![ - navigation.options_before_active.clone(), - navigation.active_option.clone().map_or(vec![], |o| vec![o]), - ] - .concat(); - navigation.active_option = Some(next.clone()); - navigation.options_after_active = - navigation.options_after_active.split_at(1).1.to_vec(); - Action::SelectOption(next.clone()) - } else { - Action::None + Message::SelectNext => { + if let Some(next) = navigation.options_after_active.clone().first() { + navigation.options_before_active = vec![ + navigation.options_before_active.clone(), + navigation.active_option.clone().map_or(vec![], |o| vec![o]), + ] + .concat(); + navigation.active_option = Some(next.clone()); + navigation.options_after_active = + navigation.options_after_active.split_at(1).1.to_vec(); + Action::SelectOption(next.clone()) + } else { + Action::None + } } - } - Message::SelectPrev => { - if let Some(prev) = navigation.options_before_active.clone().last() { - navigation.options_after_active = vec![ - navigation.active_option.clone().map_or(vec![], |o| vec![o]), - navigation.options_after_active.clone(), - ] - .concat(); - navigation.active_option = Some(prev.clone()); - navigation.options_before_active = navigation - .options_before_active - .split_last() - .map(|split| split.1.to_vec()) - .unwrap_or_else(Vec::new); - Action::SelectOption(prev.clone()) - } else { - Action::None + Message::SelectPrev => { + if let Some(prev) = navigation.options_before_active.clone().last() { + navigation.options_after_active = vec![ + navigation.active_option.clone().map_or(vec![], |o| vec![o]), + navigation.options_after_active.clone(), + ] + .concat(); + navigation.active_option = Some(prev.clone()); + navigation.options_before_active = navigation + .options_before_active + .split_last() + .map(|split| split.1.to_vec()) + .unwrap_or_else(Vec::new); + Action::SelectOption(prev.clone()) + } else { + Action::None + } } } } diff --git a/schist_desktop_gui/src/gui/components/navigation/view_navigation.rs b/schist_desktop_gui/src/gui/components/navigation/view_navigation.rs index beec818..3f1da18 100644 --- a/schist_desktop_gui/src/gui/components/navigation/view_navigation.rs +++ b/schist_desktop_gui/src/gui/components/navigation/view_navigation.rs @@ -1,26 +1,27 @@ use iced::{widget::column, Element}; +use crate::traits::Viewable; + use super::{Message, Navigation}; -pub fn view_navigation<'a, TOption>( - navigation: &'a Navigation<TOption>, -) -> Element<'a, Message<TOption>> +impl<'a, TOption> Viewable<'a, Message<TOption>> for Navigation<TOption> where - TOption: Clone + PartialEq + Into<Element<'a, Message<TOption>>>, + TOption: Clone + PartialEq + 'a, + Element<'a, Message<TOption>>: From<&'a TOption>, { - let mut options = Vec::new(); - navigation - .options_before_active - .iter() - .map(|option| navigation.button(&option)) - .for_each(|option| options.push(option)); - if let Some(active_option) = navigation.active_option.as_ref() { - options.push(navigation.button(&active_option)); + fn view(&'a self) -> iced::Element<'a, Message<TOption>> { + let mut options = Vec::new(); + self.options_before_active + .iter() + .map(|option| self.view_navigation_button(&option)) + .for_each(|option| options.push(option)); + if let Some(active_option) = self.active_option.as_ref() { + options.push(self.view_navigation_button(&active_option)); + } + self.options_after_active + .iter() + .map(|option| self.view_navigation_button(&option)) + .for_each(|option| options.push(option)); + column(options).into() } - navigation - .options_after_active - .iter() - .map(|option| navigation.button(&option)) - .for_each(|option| options.push(option)); - column(options).into() } diff --git a/schist_desktop_gui/src/gui/components/navigation/view_navigation_button.rs b/schist_desktop_gui/src/gui/components/navigation/view_navigation_button.rs index 3132bf1..d65328c 100644 --- a/schist_desktop_gui/src/gui/components/navigation/view_navigation_button.rs +++ b/schist_desktop_gui/src/gui/components/navigation/view_navigation_button.rs @@ -4,24 +4,24 @@ use crate::gui::components::{active_button, inactive_button}; use super::{Message, Navigation}; -pub fn view_navigation_button<'a, TOption>( - navigation: &'a Navigation<TOption>, - option: &'a TOption, -) -> Element<'a, Message<TOption>> +impl<'a, TOption> Navigation<TOption> where - TOption: Clone + PartialEq + Into<Element<'a, Message<TOption>>>, + TOption: Clone + PartialEq + 'a, + Element<'a, Message<TOption>>: From<&'a TOption>, { - if navigation - .active_option - .as_ref() - .is_some_and(|o| *o == *option) - { - active_button(Into::<Element<'a, Message<TOption>>>::into(option.clone())).into() - } else { - inactive_button( - Into::<Element<'a, Message<TOption>>>::into(option.clone()), - Message::SelectOption(option.clone()), - ) + pub(super) fn view_navigation_button( + &'a self, + option: &'a TOption, + ) -> Element<'a, Message<TOption>> { + if self + .active_option + .as_ref() + .is_some_and(|o| TOption::eq(o, option)) + { + active_button(option) + } else { + inactive_button(option, Message::SelectOption(option.clone())) + } .into() } } diff --git a/schist_desktop_gui/src/gui/components/text.rs b/schist_desktop_gui/src/gui/components/text.rs index 90cd7bc..a3093ac 100644 --- a/schist_desktop_gui/src/gui/components/text.rs +++ b/schist_desktop_gui/src/gui/components/text.rs @@ -1,3 +1,5 @@ +use std::borrow::Borrow; + use crate::style::*; #[derive(Clone, Debug, PartialEq)] @@ -71,30 +73,46 @@ impl Text { } } -impl<'a, Message> Into<iced::Element<'a, Message>> for Text { - fn into(self) -> iced::Element<'a, Message> { - iced::widget::text(self.content.clone()) - .size(match self.size { - Some(Size::Small) => TEXT_SIZE_SM, - Some(Size::Base) | None => TEXT_SIZE_BASE, - }) - .style(move |theme: &iced::Theme| iced::widget::text::Style { - color: match (&self.colour, &self.strength) { - (Some(Colour::Danger), Some(Strength::Base) | None) => { - Some(theme.extended_palette().danger.base.text) - } - (Some(Colour::Danger), Some(Strength::Weak)) => { - Some(theme.extended_palette().danger.weak.text) - } - (Some(Colour::Primary), Some(Strength::Base) | None) => { - Some(theme.extended_palette().primary.base.text) - } - (Some(Colour::Primary), Some(Strength::Weak)) => { - Some(theme.extended_palette().primary.weak.text) - } - (None, _) => None, - }, - }) - .into() +impl<'a, Message> From<Text> for iced::Element<'a, Message> { + fn from(text: Text) -> Self { + text_as_element(text) + } +} + +impl<'a, Message> From<&'a Text> for iced::Element<'a, Message> { + fn from(text: &'a Text) -> Self { + text_as_element(text) } } + +fn text_as_element<'a, T, Message>(text: T) -> iced::Element<'a, Message> +where + T: Borrow<Text> + 'a, +{ + iced::widget::text(text.borrow().content.clone()) + .size(match text.borrow().size { + Some(Size::Small) => TEXT_SIZE_SM, + Some(Size::Base) | None => TEXT_SIZE_BASE, + }) + .style(move |theme: &iced::Theme| iced::widget::text::Style { + color: match ( + &text.borrow().borrow().colour, + &text.borrow().borrow().strength, + ) { + (Some(Colour::Danger), Some(Strength::Base) | None) => { + Some(theme.extended_palette().danger.base.text) + } + (Some(Colour::Danger), Some(Strength::Weak)) => { + Some(theme.extended_palette().danger.weak.text) + } + (Some(Colour::Primary), Some(Strength::Base) | None) => { + Some(theme.extended_palette().primary.base.text) + } + (Some(Colour::Primary), Some(Strength::Weak)) => { + Some(theme.extended_palette().primary.weak.text) + } + (None, _) => None, + }, + }) + .into() +} diff --git a/schist_desktop_gui/src/gui/screens/main_screen/view.rs b/schist_desktop_gui/src/gui/screens/main_screen/view.rs index 2be427b..5f2df4e 100644 --- a/schist_desktop_gui/src/gui/screens/main_screen/view.rs +++ b/schist_desktop_gui/src/gui/screens/main_screen/view.rs @@ -41,8 +41,8 @@ impl View { } } -impl<'a> Into<Element<'a, navigation::Message<View>>> for View { - fn into(self) -> Element<'a, navigation::Message<View>> { - Text::new(self.name()).into() +impl<'a> From<&'a View> for Element<'a, navigation::Message<View>> { + fn from(val: &'a View) -> Self { + Text::new(val.name()).into() } } |
