diff options
| author | Joe Carstairs <me@joeac.net> | 2025-08-13 07:50:21 +0000 |
|---|---|---|
| committer | joeac <me@joeac.net> | 2025-08-13 07:50:21 +0000 |
| commit | ee3cd780e21260257b401c3f5cbababd9e27a15a (patch) | |
| tree | b0571649a1a1a7644dbc50183cc4c3a29966a963 /schist_desktop_gui/src/gui/components/navigation | |
| parent | 49342e8b553d5c92f1c043b3b0ce18bdf1c2cd44 (diff) | |
task-073
Co-authored-by: Joe Carstairs <jcarstairs@scottlogic.com>
Reviewed-on: https://git.joeac.net/joeac/schist/pulls/1
Co-authored-by: Joe Carstairs <me@joeac.net>
Co-committed-by: Joe Carstairs <me@joeac.net>
Diffstat (limited to 'schist_desktop_gui/src/gui/components/navigation')
4 files changed, 253 insertions, 0 deletions
diff --git a/schist_desktop_gui/src/gui/components/navigation/new_navigation.rs b/schist_desktop_gui/src/gui/components/navigation/new_navigation.rs new file mode 100644 index 0000000..b5c0c19 --- /dev/null +++ b/schist_desktop_gui/src/gui/components/navigation/new_navigation.rs @@ -0,0 +1,72 @@ +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 new file mode 100644 index 0000000..f86867f --- /dev/null +++ b/schist_desktop_gui/src/gui/components/navigation/update_navigation.rs @@ -0,0 +1,128 @@ +use iced::Element; + +use super::{Action, Message, Navigation}; + +pub fn update_navigation<'a, TOption>( + navigation: &mut Navigation<TOption>, + message: Message<TOption>, +) -> Action<TOption> +where + TOption: Clone + PartialEq + Into<Element<'a, Message<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![ + 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(), + 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 + .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::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 new file mode 100644 index 0000000..beec818 --- /dev/null +++ b/schist_desktop_gui/src/gui/components/navigation/view_navigation.rs @@ -0,0 +1,26 @@ +use iced::{widget::column, Element}; + +use super::{Message, Navigation}; + +pub fn view_navigation<'a, TOption>( + navigation: &'a Navigation<TOption>, +) -> Element<'a, Message<TOption>> +where + TOption: Clone + PartialEq + Into<Element<'a, Message<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)); + } + 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 new file mode 100644 index 0000000..3132bf1 --- /dev/null +++ b/schist_desktop_gui/src/gui/components/navigation/view_navigation_button.rs @@ -0,0 +1,27 @@ +use iced::Element; + +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>> +where + TOption: Clone + PartialEq + Into<Element<'a, Message<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()), + ) + .into() + } +} |
