pub fn next_focusable<T>(
rows: &[T],
current: usize,
delta: i32,
is_focusable: impl Fn(&T) -> bool,
) -> Option<usize> {
if rows.is_empty() || delta == 0 {
return None;
}
let len = rows.len() as i32;
let mut idx = current as i32 + delta;
while (0..len).contains(&idx) {
let i = idx as usize;
if is_focusable(&rows[i]) {
return Some(i);
}
idx += delta;
}
None
}
pub fn next_focusable_wrapping<T>(
rows: &[T],
current: usize,
delta: i32,
is_focusable: impl Fn(&T) -> bool,
) -> Option<usize> {
if rows.is_empty() || delta == 0 {
return None;
}
let len = rows.len() as i32;
let mut idx = current as i32;
for _ in 0..len {
idx = (idx + delta).rem_euclid(len);
let i = idx as usize;
if is_focusable(&rows[i]) {
return Some(i);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn skips_non_focusable_rows_forward() {
let rows = [true, false, false, true, false, true];
assert_eq!(next_focusable(&rows, 0, 1, |r| *r), Some(3));
assert_eq!(next_focusable(&rows, 3, 1, |r| *r), Some(5));
}
#[test]
fn returns_none_when_no_focusable_in_direction() {
let rows = [true, false, false];
assert_eq!(next_focusable(&rows, 0, 1, |r| *r), None);
assert_eq!(next_focusable(&rows, 0, -1, |r| *r), None);
}
#[test]
fn empty_rows_returns_none() {
let rows: [bool; 0] = [];
assert_eq!(next_focusable(&rows, 0, 1, |r| *r), None);
}
#[test]
fn zero_delta_returns_none() {
let rows = [true, true, true];
assert_eq!(next_focusable(&rows, 1, 0, |r| *r), None);
}
#[test]
fn skips_backward() {
let rows = [true, false, false, true];
assert_eq!(next_focusable(&rows, 3, -1, |r| *r), Some(0));
}
#[test]
fn wrapping_wraps_forward_off_the_end() {
let rows = [true, true, true];
assert_eq!(next_focusable_wrapping(&rows, 2, 1, |r| *r), Some(0));
}
#[test]
fn wrapping_wraps_backward_off_the_start() {
let rows = [true, true, true];
assert_eq!(next_focusable_wrapping(&rows, 0, -1, |r| *r), Some(2));
}
#[test]
fn wrapping_skips_non_focusable_then_wraps() {
let rows = [true, false, true, false, false];
assert_eq!(next_focusable_wrapping(&rows, 2, 1, |r| *r), Some(0));
assert_eq!(next_focusable_wrapping(&rows, 0, -1, |r| *r), Some(2));
}
#[test]
fn wrapping_lone_focusable_returns_itself() {
let rows = [false, true, false];
assert_eq!(next_focusable_wrapping(&rows, 1, 1, |r| *r), Some(1));
assert_eq!(next_focusable_wrapping(&rows, 1, -1, |r| *r), Some(1));
}
#[test]
fn wrapping_all_non_focusable_returns_none() {
let rows = [false, false, false];
assert_eq!(next_focusable_wrapping(&rows, 0, 1, |r| *r), None);
}
#[test]
fn wrapping_zero_delta_and_empty_return_none() {
let rows = [true, true];
assert_eq!(next_focusable_wrapping(&rows, 0, 0, |r| *r), None);
let empty: [bool; 0] = [];
assert_eq!(next_focusable_wrapping(&empty, 0, 1, |r| *r), None);
}
}