use crate::direction::Direction;
use agb::fixnum::{Vector2D, vec2};
use agb::input::ButtonController;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct MenuCursor {
idx: u8,
x: u8,
y: u8,
max_x: u8,
total: u8,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum CursorResult {
NoChange,
Invalid,
Moved,
}
impl MenuCursor {
pub fn new(column_count: u8, count: u8) -> Self {
assert!(
column_count > 0,
"MenuCursor created with no columns {}, {}",
column_count,
count
);
assert!(count > 0, "MenuCursor created with no items");
Self {
idx: 0,
x: 0,
y: 0,
max_x: column_count,
total: count,
}
}
}
impl MenuCursor {
#[inline]
pub fn idx(&self) -> usize {
self.idx as usize
}
#[inline]
pub fn pos(&self) -> (u8, u8) {
(self.x, self.y)
}
#[inline]
pub fn vec_pos(&self) -> Vector2D<i32> {
vec2(self.x as i32, self.y as i32)
}
#[inline]
pub fn pos_usize(&self) -> (usize, usize) {
(self.x as usize, self.y as usize)
}
pub fn set_idx(&mut self, idx: usize) {
self.idx = idx.min((self.total - 1) as usize) as u8;
let mut x = self.idx;
let mut y = 0;
while x >= self.max_x {
x -= self.max_x;
y += 1;
}
self.x = x;
self.y = y;
}
pub fn update(&mut self, button_controller: &ButtonController) -> CursorResult {
match Direction::from_recent_input(button_controller) {
Some(dir) => self.apply(dir),
None => CursorResult::NoChange,
}
}
pub fn apply(&mut self, dir: Direction) -> CursorResult {
match dir {
Direction::Up => {
if self.y > 0 {
self.y -= 1;
self.idx -= self.max_x;
CursorResult::Moved
} else {
CursorResult::Invalid
}
}
Direction::Down => {
if (self.idx as u16 + self.max_x as u16) < self.total as u16 {
self.y += 1;
self.idx += self.max_x;
CursorResult::Moved
} else {
CursorResult::Invalid
}
}
Direction::Left => {
if self.x > 0 {
self.x -= 1;
self.idx -= 1;
CursorResult::Moved
} else {
CursorResult::Invalid
}
}
Direction::Right => {
if (self.x + 1 < self.max_x) && (self.idx + 1 < self.total) {
self.x += 1;
self.idx += 1;
CursorResult::Moved
} else {
CursorResult::Invalid
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test_case]
fn moves_within_grid(_gba: &mut agb::Gba) {
let mut cursor = MenuCursor::new(3, 9);
assert_eq!(cursor.apply(Direction::Up), CursorResult::Invalid);
assert_eq!(cursor.apply(Direction::Left), CursorResult::Invalid);
assert_eq!(cursor.apply(Direction::Right), CursorResult::Moved);
assert_eq!(cursor.pos(), (1, 0));
assert_eq!(cursor.apply(Direction::Down), CursorResult::Moved);
assert_eq!(cursor.idx(), 4);
assert_eq!(cursor.vec_pos(), vec2(1, 1));
assert_eq!(cursor.apply(Direction::Up), CursorResult::Moved);
assert_eq!(cursor.apply(Direction::Left), CursorResult::Moved);
assert_eq!(cursor.pos(), (0, 0));
}
#[test_case]
fn blocked_on_partial_last_row(_gba: &mut agb::Gba) {
let mut cursor = MenuCursor::new(3, 8);
cursor.set_idx(5);
assert_eq!(cursor.apply(Direction::Down), CursorResult::Invalid);
cursor.set_idx(4);
assert_eq!(cursor.apply(Direction::Down), CursorResult::Moved);
assert_eq!(cursor.idx(), 7);
assert_eq!(cursor.apply(Direction::Right), CursorResult::Invalid);
}
#[test_case]
fn no_overflow_on_large_grid(_gba: &mut agb::Gba) {
let mut cursor = MenuCursor::new(128, 255);
cursor.set_idx(200);
assert_eq!(cursor.apply(Direction::Down), CursorResult::Invalid);
assert_eq!(cursor.idx(), 200);
}
#[test_case]
fn set_idx_clamps(_gba: &mut agb::Gba) {
let mut cursor = MenuCursor::new(3, 9);
cursor.set_idx(500);
assert_eq!(cursor.idx(), 8);
assert_eq!(cursor.pos(), (2, 2));
cursor.set_idx(4);
assert_eq!(cursor.pos_usize(), (1, 1));
}
}