1pub fn next_active_index(
12 disabled: &[bool],
13 current: Option<usize>,
14 forward: bool,
15 wrap: bool,
16) -> Option<usize> {
17 let len = disabled.len();
18 if len == 0 {
19 return None;
20 }
21
22 let is_enabled = |idx: usize| disabled.get(idx).copied() == Some(false);
23 let first = disabled.iter().position(|d| !*d)?;
24 let last = disabled.iter().rposition(|d| !*d)?;
25
26 let Some(current) = current.filter(|&i| i < len && is_enabled(i)) else {
27 return Some(if forward { first } else { last });
28 };
29
30 if wrap {
31 for step in 1..=len {
32 let idx = if forward {
33 (current + step) % len
34 } else {
35 (current + len - (step % len)) % len
36 };
37 if is_enabled(idx) {
38 return Some(idx);
39 }
40 }
41 None
42 } else if forward {
43 ((current + 1)..len)
44 .find(|&i| is_enabled(i))
45 .or(Some(current))
46 } else if current > 0 {
47 (0..current)
48 .rev()
49 .find(|&i| is_enabled(i))
50 .or(Some(current))
51 } else {
52 Some(current)
53 }
54}
55
56pub fn clamp_active_index(disabled: &[bool], current: Option<usize>) -> Option<usize> {
60 if let Some(current) = current
61 && disabled.get(current).copied() == Some(false)
62 {
63 return Some(current);
64 }
65 disabled.iter().position(|d| !*d)
66}
67
68pub fn first_enabled(disabled: &[bool]) -> Option<usize> {
70 disabled.iter().position(|d| !*d)
71}
72
73pub fn last_enabled(disabled: &[bool]) -> Option<usize> {
75 disabled.iter().rposition(|d| !*d)
76}
77
78pub fn advance_active_index(
82 disabled: &[bool],
83 current: Option<usize>,
84 forward: bool,
85 wrap: bool,
86 amount: usize,
87) -> Option<usize> {
88 let mut cur = clamp_active_index(disabled, current);
89 if amount == 0 {
90 return cur;
91 }
92
93 for _ in 0..amount {
94 let next = next_active_index(disabled, cur, forward, wrap);
95 if next == cur {
96 break;
97 }
98 cur = next;
99 }
100 cur
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn next_picks_first_or_last_when_none() {
109 let disabled = [true, false, false];
110 assert_eq!(next_active_index(&disabled, None, true, true), Some(1));
111 assert_eq!(next_active_index(&disabled, None, false, true), Some(2));
112 }
113
114 #[test]
115 fn next_wraps_and_skips_disabled() {
116 let disabled = [false, true, false];
117 assert_eq!(next_active_index(&disabled, Some(0), true, true), Some(2));
118 assert_eq!(next_active_index(&disabled, Some(2), true, true), Some(0));
119 assert_eq!(next_active_index(&disabled, Some(0), false, true), Some(2));
120 }
121
122 #[test]
123 fn next_does_not_wrap_and_clamps_to_edges() {
124 let disabled = [false, true, false];
125 assert_eq!(next_active_index(&disabled, Some(0), false, false), Some(0));
126 assert_eq!(next_active_index(&disabled, Some(2), true, false), Some(2));
127 assert_eq!(next_active_index(&disabled, Some(0), true, false), Some(2));
128 }
129
130 #[test]
131 fn clamp_falls_back_to_first_enabled() {
132 let disabled = [true, false, true];
133 assert_eq!(clamp_active_index(&disabled, Some(0)), Some(1));
134 assert_eq!(clamp_active_index(&disabled, Some(2)), Some(1));
135 assert_eq!(clamp_active_index(&disabled, None), Some(1));
136 }
137
138 #[test]
139 fn all_disabled_returns_none() {
140 let disabled = [true, true];
141 assert_eq!(next_active_index(&disabled, None, true, true), None);
142 assert_eq!(clamp_active_index(&disabled, Some(0)), None);
143 }
144
145 #[test]
146 fn first_and_last_enabled_work() {
147 let disabled = [true, false, true, false];
148 assert_eq!(first_enabled(&disabled), Some(1));
149 assert_eq!(last_enabled(&disabled), Some(3));
150 assert_eq!(first_enabled(&[true, true]), None);
151 assert_eq!(last_enabled(&[true, true]), None);
152 }
153
154 #[test]
155 fn advance_moves_multiple_steps() {
156 let disabled = [false, true, false, false];
157 assert_eq!(
158 advance_active_index(&disabled, Some(0), true, true, 2),
159 Some(3)
160 );
161 assert_eq!(
162 advance_active_index(&disabled, Some(3), false, true, 3),
163 Some(3)
164 );
165 }
166}