| 1 | //! Shared list-selection navigation (#4755). |
| 2 | //! |
| 3 | //! Modal lists and config screens should wrap at the ends so Down on the last |
| 4 | //! row returns to the top and Up on the first row returns to the bottom. |
| 5 | //! Centralizing the arithmetic keeps that behavior consistent without each |
| 6 | //! picker inventing its own clamp. |
| 7 | |
| 8 | /// Move a 0-based selection by `delta`, wrapping at both ends. |
| 9 | /// |
| 10 | /// Empty lists leave the selection at `0`. A zero `len` is treated as empty. |
| 11 | #[must_use] |
| 12 | pub fn wrap_index(selected: usize, len: usize, delta: isize) -> usize { |
| 13 | if len == 0 { |
| 14 | return 0; |
| 15 | } |
| 16 | (selected as isize + delta).rem_euclid(len as isize) as usize |
| 17 | } |
| 18 | |
| 19 | #[cfg(test)] |
| 20 | mod tests { |
| 21 | use super::wrap_index; |
| 22 | |
| 23 | #[test] |
| 24 | fn wraps_forward_and_backward() { |
| 25 | assert_eq!(wrap_index(0, 3, -1), 2); |
| 26 | assert_eq!(wrap_index(2, 3, 1), 0); |
| 27 | assert_eq!(wrap_index(1, 3, 1), 2); |
| 28 | assert_eq!(wrap_index(1, 3, -1), 0); |
| 29 | } |
| 30 | |
| 31 | #[test] |
| 32 | fn empty_list_stays_at_zero() { |
| 33 | assert_eq!(wrap_index(5, 0, 1), 0); |
| 34 | assert_eq!(wrap_index(0, 0, -1), 0); |
| 35 | } |
| 36 | } |
| 37 |