summaryrefslogtreecommitdiff
path: root/schist_desktop_gui/src/gui
diff options
context:
space:
mode:
authorJoe Carstairs <me@joeac.net>2026-02-01 21:00:37 +0000
committerJoe Carstairs <me@joeac.net>2026-02-01 21:00:37 +0000
commit1e6ebcbf7d8a3bb53562a4d73ed110bc4cd77d8c (patch)
tree1b9e97162bc0a397d95ee8927459de2ac0e8986c /schist_desktop_gui/src/gui
parent2c11ff0935ecf8f63d98509965c1a3ae94edce93 (diff)
Navigation::insert_after_first()
Diffstat (limited to 'schist_desktop_gui/src/gui')
-rw-r--r--schist_desktop_gui/src/gui/components/navigation.rs3
-rw-r--r--schist_desktop_gui/src/gui/components/navigation/navigation_insert_options.rs378
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"]);
+ }
+}