diff options
| -rw-r--r-- | schist_desktop_gui/src/gui/components/navigation.rs | 3 | ||||
| -rw-r--r-- | schist_desktop_gui/src/gui/components/navigation/navigation_insert_options.rs | 378 |
2 files changed, 381 insertions, 0 deletions
diff --git a/schist_desktop_gui/src/gui/components/navigation.rs b/schist_desktop_gui/src/gui/components/navigation.rs index a89a761..b7abda4 100644 --- a/schist_desktop_gui/src/gui/components/navigation.rs +++ b/schist_desktop_gui/src/gui/components/navigation.rs @@ -1,4 +1,5 @@ mod navigate_navigation; +mod navigation_insert_options; mod navigation_replace_options; mod navigation_select_option; mod navigation_set_options; @@ -11,6 +12,8 @@ mod dummy_navigation_option; use std::fmt::Debug; +pub use navigation_insert_options::InsertOptionResult; + #[derive(Clone, Debug)] pub struct Navigation<TOption> { options_before_active: Vec<TOption>, diff --git a/schist_desktop_gui/src/gui/components/navigation/navigation_insert_options.rs b/schist_desktop_gui/src/gui/components/navigation/navigation_insert_options.rs new file mode 100644 index 0000000..1e6d8cf --- /dev/null +++ b/schist_desktop_gui/src/gui/components/navigation/navigation_insert_options.rs @@ -0,0 +1,378 @@ +use iced::Element; + +use super::{Message, Navigation}; + +#[derive(PartialEq)] +pub enum InsertOptionResult { + InsertedOptions, + NoSuchOption, +} + +impl<'a, TOption> Navigation<TOption> +where + TOption: Clone + PartialEq + 'a, + Element<'a, Message<TOption>>: From<&'a TOption>, +{ + pub fn insert_after_first<P>(&'a mut self, pred: P, new: &Vec<TOption>) -> InsertOptionResult + where + P: Fn(&TOption) -> bool, + { + if let Some(index) = self.options_before_active.iter().position(&pred) { + self.options_before_active + .splice((index + 1)..(index + 1), new.clone()); + InsertOptionResult::InsertedOptions + } else if self + .active_option + .as_ref() + .is_some_and(|active_option| pred(&active_option)) + { + self.options_after_active.splice(0..0, new.clone()); + InsertOptionResult::InsertedOptions + } else if let Some(index) = self.options_after_active.iter().position(&pred) { + self.options_after_active + .splice((index + 1)..(index + 1), new.clone()); + InsertOptionResult::InsertedOptions + } else { + InsertOptionResult::NoSuchOption + } + } +} + +#[cfg(test)] +mod test { + use itertools::Itertools; + + use crate::{ + gui::components::{ + navigation::{self, dummy_navigation_option::DummyNavigationOption}, + Navigation, + }, + traits::Component, + }; + + macro_rules! assert_active_option_eq { + ($nav:expr, $option:expr) => { + if !$nav + .active_option + .as_ref() + .is_some_and(|o| o.name == $option) + { + panic!( + "navigation active option should have been Some(\"{}\"), but was {:?}", + $option, $nav.active_option + ); + } + }; + } + + macro_rules! assert_options_before_eq { + ($nav:expr, $options:expr) => { + let options_before_eq = $options.len() == $nav.options_before_active.len() + && (|| { + (0..$options.len()).all(|i| $options[i] == $nav.options_before_active[i].name) + })(); + + if !options_before_eq { + panic!( + "navigation should have had options ({}) before active option, but were ({})", + $options.iter().join(", "), + $nav.options_before_active.iter().join(", "), + ); + } + }; + } + + macro_rules! assert_options_after_eq { + ($nav:expr, $options:expr) => { + let options_after_eq = $options.len() == $nav.options_after_active.len() + && (|| { + (0..$options.len()).all(|i| $options[i] == $nav.options_after_active[i].name) + })(); + + if !options_after_eq { + panic!( + "navigation should have had options ({}) after active option, but were ({})", + $options.iter().join(", "), + $nav.options_after_active.iter().join(", "), + ); + } + }; + } + + fn make_dummy_nav_with_options_a_to_g() -> Navigation<DummyNavigationOption> { + Navigation::new( + None, + vec![ + DummyNavigationOption::new("a"), + DummyNavigationOption::new("b"), + DummyNavigationOption::new("c"), + DummyNavigationOption::new("d"), + DummyNavigationOption::new("e"), + DummyNavigationOption::new("f"), + DummyNavigationOption::new("g"), + ], + ) + } + + fn select_dummy_nav_option(nav: &mut Navigation<DummyNavigationOption>, option: &str) { + nav.update(navigation::Message::SelectOption( + DummyNavigationOption::new(option), + )); + } + + #[test] + fn insert_after_first_option_before_active_with_one() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "d"); + let new = vec![DummyNavigationOption::new("z")]; + nav.insert_after_first(|o| o.name == "a", &new); + assert_options_before_eq!(nav, ["a", "z", "b", "c"]); + assert_active_option_eq!(nav, "d"); + assert_options_after_eq!(nav, ["e", "f", "g"]); + } + + #[test] + fn insert_after_first_option_before_active_with_many() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "d"); + let new = vec![ + DummyNavigationOption::new("x"), + DummyNavigationOption::new("y"), + DummyNavigationOption::new("z"), + ]; + nav.insert_after_first(|o| o.name == "a", &new); + assert_options_before_eq!(nav, ["a", "x", "y", "z", "b", "c"]); + assert_active_option_eq!(nav, "d"); + assert_options_after_eq!(nav, ["e", "f", "g"]); + } + + #[test] + fn insert_after_last_option_before_active_with_one() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "d"); + let new = vec![DummyNavigationOption::new("z")]; + nav.insert_after_first(|o| o.name == "c", &new); + assert_options_before_eq!(nav, ["a", "b", "c", "z"]); + assert_active_option_eq!(nav, "d"); + assert_options_after_eq!(nav, ["e", "f", "g"]); + } + + #[test] + fn insert_after_last_option_before_active_with_many() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "d"); + let new = vec![ + DummyNavigationOption::new("x"), + DummyNavigationOption::new("y"), + DummyNavigationOption::new("z"), + ]; + nav.insert_after_first(|o| o.name == "c", &new); + assert_options_before_eq!(nav, ["a", "b", "c", "x", "y", "z"]); + assert_active_option_eq!(nav, "d"); + assert_options_after_eq!(nav, ["e", "f", "g"]); + } + + #[test] + fn insert_after_active_option_with_one() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "d"); + let new = vec![DummyNavigationOption::new("z")]; + nav.insert_after_first(|o| o.name == "d", &new); + assert_options_before_eq!(nav, ["a", "b", "c"]); + assert_active_option_eq!(nav, "d"); + assert_options_after_eq!(nav, ["z", "e", "f", "g"]); + } + + #[test] + fn insert_after_active_option_with_one_when_active_option_is_first() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "a"); + let new = vec![DummyNavigationOption::new("z")]; + nav.insert_after_first(|o| o.name == "a", &new); + assert_options_before_eq!(nav, [] as [String; 0]); + assert_active_option_eq!(nav, "a"); + assert_options_after_eq!(nav, ["z", "b", "c", "d", "e", "f", "g"]); + } + + #[test] + fn insert_after_active_option_with_one_when_active_option_is_last() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "g"); + let new = vec![DummyNavigationOption::new("z")]; + nav.insert_after_first(|o| o.name == "g", &new); + assert_options_before_eq!(nav, ["a", "b", "c", "d", "e", "f",]); + assert_active_option_eq!(nav, "g"); + assert_options_after_eq!(nav, ["z"]); + } + + #[test] + fn insert_after_active_option_with_one_when_active_option_is_only() { + let active_option = DummyNavigationOption::new("a"); + let mut nav = Navigation::new(Some(active_option.clone()), vec![active_option]); + let new = vec![DummyNavigationOption::new("z")]; + nav.insert_after_first(|o| o.name == "a", &new); + assert_options_before_eq!(nav, [] as [String; 0]); + assert_active_option_eq!(nav, "a"); + assert_options_after_eq!(nav, ["z"]); + } + + #[test] + fn insert_after_active_option_with_many() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "d"); + let new = vec![ + DummyNavigationOption::new("x"), + DummyNavigationOption::new("y"), + DummyNavigationOption::new("z"), + ]; + nav.insert_after_first(|o| o.name == "d", &new); + assert_options_before_eq!(nav, ["a", "b", "c"]); + assert_active_option_eq!(nav, "d"); + assert_options_after_eq!(nav, ["x", "y", "z", "e", "f", "g"]); + } + + #[test] + fn insert_after_active_option_with_many_when_active_option_is_first() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "a"); + let new = vec![ + DummyNavigationOption::new("x"), + DummyNavigationOption::new("y"), + DummyNavigationOption::new("z"), + ]; + nav.insert_after_first(|o| o.name == "a", &new); + assert_options_before_eq!(nav, [] as [String; 0]); + assert_active_option_eq!(nav, "a"); + assert_options_after_eq!(nav, ["x", "y", "z", "b", "c", "d", "e", "f", "g"]); + } + + #[test] + fn insert_after_active_option_with_many_when_active_option_is_last() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "g"); + let new = vec![ + DummyNavigationOption::new("x"), + DummyNavigationOption::new("y"), + DummyNavigationOption::new("z"), + ]; + nav.insert_after_first(|o| o.name == "g", &new); + assert_options_before_eq!(nav, ["a", "b", "c", "d", "e", "f"]); + assert_active_option_eq!(nav, "g"); + assert_options_after_eq!(nav, ["x", "y", "z"]); + } + + #[test] + fn insert_after_active_option_with_many_when_active_option_is_only() { + let active_option = DummyNavigationOption::new("a"); + let mut nav = Navigation::new(Some(active_option.clone()), vec![active_option]); + let new = vec![ + DummyNavigationOption::new("x"), + DummyNavigationOption::new("y"), + DummyNavigationOption::new("z"), + ]; + nav.insert_after_first(|o| o.name == "a", &new); + assert_options_before_eq!(nav, [] as [String; 0]); + assert_active_option_eq!(nav, "a"); + assert_options_after_eq!(nav, ["x", "y", "z"]); + } + + #[test] + fn insert_after_first_option_after_active_with_one() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "d"); + let new = vec![DummyNavigationOption::new("z")]; + nav.insert_after_first(|o| o.name == "e", &new); + assert_options_before_eq!(nav, ["a", "b", "c"]); + assert_active_option_eq!(nav, "d"); + assert_options_after_eq!(nav, ["e", "z", "f", "g"]); + } + + #[test] + fn insert_after_first_option_after_active_with_many() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "d"); + let new = vec![ + DummyNavigationOption::new("x"), + DummyNavigationOption::new("y"), + DummyNavigationOption::new("z"), + ]; + nav.insert_after_first(|o| o.name == "e", &new); + assert_options_before_eq!(nav, ["a", "b", "c"]); + assert_active_option_eq!(nav, "d"); + assert_options_after_eq!(nav, ["e", "x", "y", "z", "f", "g"]); + } + + #[test] + fn insert_after_last_option_after_active_with_one() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "d"); + let new = vec![DummyNavigationOption::new("z")]; + nav.insert_after_first(|o| o.name == "g", &new); + assert_options_before_eq!(nav, ["a", "b", "c"]); + assert_active_option_eq!(nav, "d"); + assert_options_after_eq!(nav, ["e", "f", "g", "z"]); + } + + #[test] + fn insert_after_last_option_after_active_with_many() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "d"); + let new = vec![ + DummyNavigationOption::new("x"), + DummyNavigationOption::new("y"), + DummyNavigationOption::new("z"), + ]; + nav.insert_after_first(|o| o.name == "g", &new); + assert_options_before_eq!(nav, ["a", "b", "c"]); + assert_active_option_eq!(nav, "d"); + assert_options_after_eq!(nav, ["e", "f", "g", "x", "y", "z"]); + } + + #[test] + fn insert_after_first_option_before_active_when_duplicates() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "d"); + nav.insert_after_first(|o| o.name == "c", &vec![DummyNavigationOption::new("a")]); + let new = vec![DummyNavigationOption::new("z")]; + nav.insert_after_first(|o| o.name == "a", &new); + assert_options_before_eq!(nav, ["a", "z", "b", "c", "a"]); + assert_active_option_eq!(nav, "d"); + assert_options_after_eq!(nav, ["e", "f", "g"]); + } + + #[test] + fn insert_after_first_option_before_active_when_duplicate_in_active_option() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "d"); + nav.insert_after_first(|o| o.name == "a", &vec![DummyNavigationOption::new("d")]); + let new = vec![DummyNavigationOption::new("z")]; + nav.insert_after_first(|o| o.name == "d", &new); + assert_options_before_eq!(nav, ["a", "d", "z", "b", "c"]); + assert_active_option_eq!(nav, "d"); + assert_options_after_eq!(nav, ["e", "f", "g"]); + } + + #[test] + fn insert_after_first_option_before_active_when_duplicate_in_options_after_active() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "d"); + nav.insert_after_first(|o| o.name == "f", &vec![DummyNavigationOption::new("a")]); + let new = vec![DummyNavigationOption::new("z")]; + nav.insert_after_first(|o| o.name == "a", &new); + assert_options_before_eq!(nav, ["a", "z", "b", "c"]); + assert_active_option_eq!(nav, "d"); + assert_options_after_eq!(nav, ["e", "f", "a", "g"]); + } + + #[test] + fn insert_after_active_option_when_duplicate_in_options_after_active() { + let mut nav = make_dummy_nav_with_options_a_to_g(); + select_dummy_nav_option(&mut nav, "d"); + nav.insert_after_first(|o| o.name == "f", &vec![DummyNavigationOption::new("d")]); + let new = vec![DummyNavigationOption::new("z")]; + nav.insert_after_first(|o| o.name == "d", &new); + assert_options_before_eq!(nav, ["a", "b", "c"]); + assert_active_option_eq!(nav, "d"); + assert_options_after_eq!(nav, ["z", "e", "f", "d", "g"]); + } +} |
