summaryrefslogtreecommitdiff
path: root/schist_desktop_gui/src/gui/components/navigation
diff options
context:
space:
mode:
authorJoe Carstairs <me@joeac.net>2025-08-13 07:50:21 +0000
committerjoeac <me@joeac.net>2025-08-13 07:50:21 +0000
commitee3cd780e21260257b401c3f5cbababd9e27a15a (patch)
treeb0571649a1a1a7644dbc50183cc4c3a29966a963 /schist_desktop_gui/src/gui/components/navigation
parent49342e8b553d5c92f1c043b3b0ce18bdf1c2cd44 (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')
-rw-r--r--schist_desktop_gui/src/gui/components/navigation/new_navigation.rs72
-rw-r--r--schist_desktop_gui/src/gui/components/navigation/update_navigation.rs128
-rw-r--r--schist_desktop_gui/src/gui/components/navigation/view_navigation.rs26
-rw-r--r--schist_desktop_gui/src/gui/components/navigation/view_navigation_button.rs27
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()
+ }
+}