fret_ui_headless/
menu_nav.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum NavAction {
8 Prev,
9 Next,
10 Home,
11 End,
12}
13
14pub fn next_enabled_index(
15 disabled: &[bool],
16 current: Option<usize>,
17 action: NavAction,
18 wrap: bool,
19) -> Option<usize> {
20 let len = disabled.len();
21 if len == 0 {
22 return None;
23 }
24
25 let is_disabled = |idx: usize| disabled.get(idx).copied().unwrap_or(false);
26 let first = || (0..len).find(|&i| !is_disabled(i));
27 let last = || (0..len).rev().find(|&i| !is_disabled(i));
28
29 match action {
30 NavAction::Home => first(),
31 NavAction::End => last(),
32 NavAction::Next => {
33 let Some(cur) = current else {
34 return first();
35 };
36 if cur >= len {
37 return first();
38 }
39 if wrap {
40 for step in 1..=len {
41 let idx = (cur + step) % len;
42 if !is_disabled(idx) {
43 return Some(idx);
44 }
45 }
46 None
47 } else {
48 ((cur + 1)..len).find(|&i| !is_disabled(i))
49 }
50 }
51 NavAction::Prev => {
52 let Some(cur) = current else {
53 return last();
54 };
55 if cur >= len {
56 return last();
57 }
58 if wrap {
59 for step in 1..=len {
60 let idx = (cur + len - (step % len)) % len;
61 if !is_disabled(idx) {
62 return Some(idx);
63 }
64 }
65 None
66 } else if cur > 0 {
67 (0..cur).rev().find(|&i| !is_disabled(i))
68 } else {
69 None
70 }
71 }
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn wraps_and_skips_disabled() {
81 let disabled = [false, true, false];
82 assert_eq!(
83 next_enabled_index(&disabled, Some(0), NavAction::Next, true),
84 Some(2)
85 );
86 assert_eq!(
87 next_enabled_index(&disabled, Some(2), NavAction::Next, true),
88 Some(0)
89 );
90 assert_eq!(
91 next_enabled_index(&disabled, Some(0), NavAction::Prev, true),
92 Some(2)
93 );
94 }
95
96 #[test]
97 fn non_wrapping_stops_at_edges() {
98 let disabled = [false, false, false];
99 assert_eq!(
100 next_enabled_index(&disabled, Some(2), NavAction::Next, false),
101 None
102 );
103 assert_eq!(
104 next_enabled_index(&disabled, Some(0), NavAction::Prev, false),
105 None
106 );
107 }
108
109 #[test]
110 fn home_end_pick_first_last_enabled() {
111 let disabled = [true, false, true, false];
112 assert_eq!(
113 next_enabled_index(&disabled, None, NavAction::Home, true),
114 Some(1)
115 );
116 assert_eq!(
117 next_enabled_index(&disabled, None, NavAction::End, true),
118 Some(3)
119 );
120 }
121}