use crate::direction::Direction;
use crate::menu_cursor::{CursorResult, MenuCursor};
use agb::fixnum::Vector2D;
use agb::input::ButtonController;
use core::ops::Range;
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ScrollingCursor {
cursor: MenuCursor,
first_visible_row: u8,
visible_row_count: u8,
}
impl ScrollingCursor {
pub fn new(column_count: u8, item_count: u8, visible_row_count: u8) -> Self {
assert!(
visible_row_count > 0,
"ScrollingCursor created with no visible rows"
);
Self {
cursor: MenuCursor::new(column_count, item_count),
first_visible_row: 0,
visible_row_count,
}
}
fn update_first_row(&mut self) {
let (_, row) = self.cursor.pos();
let end = self.first_visible_row as u16 + self.visible_row_count as u16;
if row < self.first_visible_row {
self.first_visible_row = row;
} else if row as u16 >= end {
self.first_visible_row = row - (self.visible_row_count - 1);
}
}
#[inline]
pub fn cursor(&self) -> &MenuCursor {
&self.cursor
}
#[inline]
pub fn idx(&self) -> usize {
self.cursor.idx()
}
#[inline]
pub fn pos(&self) -> (u8, u8) {
self.cursor.pos()
}
#[inline]
pub fn pos_usize(&self) -> (usize, usize) {
self.cursor.pos_usize()
}
#[inline]
pub fn vec_pos(&self) -> Vector2D<i32> {
self.cursor.vec_pos()
}
#[inline]
pub fn first_visible_row(&self) -> u8 {
self.first_visible_row
}
#[inline]
pub fn last_visible_row(&self) -> u8 {
(self.first_visible_row as u16 + self.visible_row_count as u16 - 1)
.min(self.cursor.row_count() as u16 - 1) as u8
}
#[inline]
pub fn visible_row_count(&self) -> u8 {
self.visible_row_count
}
#[inline]
pub fn visible_rows(&self) -> Range<u8> {
self.first_visible_row..self.last_visible_row() + 1
}
#[inline]
pub fn screen_row(&self) -> u8 {
self.cursor.pos().1 - self.first_visible_row
}
#[inline]
pub fn set_idx(&mut self, idx: usize) {
self.cursor.set_idx(idx);
self.update_first_row();
}
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 {
let result = self.cursor.apply(dir);
if result.is_moved() {
self.update_first_row();
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test_case]
fn scrolls_to_keep_cursor_visible(_gba: &mut agb::Gba) {
let mut cursor = ScrollingCursor::new(1, 10, 3);
assert_eq!(cursor.first_visible_row(), 0);
assert_eq!(cursor.visible_rows(), 0..3);
cursor.set_idx(2);
assert_eq!(cursor.first_visible_row(), 0);
assert_eq!(cursor.screen_row(), 2);
cursor.set_idx(5);
assert_eq!(cursor.first_visible_row(), 3);
assert_eq!(cursor.screen_row(), 2);
cursor.set_idx(9);
assert_eq!(cursor.first_visible_row(), 7);
assert_eq!(cursor.last_visible_row(), 9);
cursor.set_idx(7);
assert_eq!(cursor.first_visible_row(), 7);
assert_eq!(cursor.screen_row(), 0);
cursor.set_idx(0);
assert_eq!(cursor.first_visible_row(), 0);
}
#[test_case]
fn apply_scrolls(_gba: &mut agb::Gba) {
let mut cursor = ScrollingCursor::new(2, 7, 2);
assert_eq!(cursor.apply(Direction::Down), CursorResult::Moved);
assert_eq!(cursor.first_visible_row(), 0);
assert_eq!(cursor.apply(Direction::Down), CursorResult::Moved);
assert_eq!(cursor.first_visible_row(), 1);
assert_eq!(cursor.apply(Direction::Right), CursorResult::Moved);
assert_eq!(cursor.apply(Direction::Down), CursorResult::Invalid);
assert_eq!(cursor.first_visible_row(), 1);
assert_eq!(cursor.apply(Direction::Left), CursorResult::Moved);
assert_eq!(cursor.apply(Direction::Down), CursorResult::Moved);
assert_eq!(cursor.idx(), 6);
assert_eq!(cursor.first_visible_row(), 2);
assert_eq!(cursor.visible_rows(), 2..4);
}
#[test_case]
fn no_overflow_near_end_of_long_list(_gba: &mut agb::Gba) {
let mut cursor = ScrollingCursor::new(1, 255, 3);
cursor.set_idx(254);
assert_eq!(cursor.first_visible_row(), 252);
assert_eq!(cursor.apply(Direction::Down), CursorResult::Invalid);
assert_eq!(cursor.visible_rows(), 252..255);
assert_eq!(cursor.screen_row(), 2);
}
#[test_case]
fn visible_rows_clamped_to_items(_gba: &mut agb::Gba) {
let cursor = ScrollingCursor::new(1, 2, 5);
assert_eq!(cursor.visible_rows(), 0..2);
assert_eq!(cursor.last_visible_row(), 1);
}
}