agb_eb_ext 0.25.0

AGB Extension methods
use crate::menu_cursor::{CursorResult, MenuCursor};
use agb::fixnum::Vector2D;
use agb::input::ButtonController;

/// Cursor for vertically scrolling menu
///
/// Use [`MenuCursor`] if all options are on screen at once
///
/// # Usage
/// Draw every item in each column from `first_visible_row` to `first_visible_row+visible_row_count`
#[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 {}, {}, {}",
            column_count,
            item_count,
            visible_row_count
        );
        Self {
            cursor: MenuCursor::new(column_count, item_count),
            first_visible_row: 0,
            visible_row_count,
        }
    }
}

impl ScrollingCursor {
    fn update_first_row(&mut self) {
        let (_, row) = self.cursor.pos();
        if row < self.first_visible_row {
            self.first_visible_row = row;
        } else if row >= self.first_visible_row + self.visible_row_count {
            self.first_visible_row = row.saturating_sub(self.visible_row_count - 1);
        }
    }
}

impl ScrollingCursor {
    #[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 set_idx(&mut self, idx: usize) {
        self.cursor.set_idx(idx);
        self.update_first_row();
    }

    pub fn update(&mut self, button_controller: &ButtonController) -> CursorResult {
        let result = self.cursor.update(button_controller);
        if result != CursorResult::NoChange {
            self.update_first_row();
        }
        result
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test_case]
    fn scrolls_to_keep_cursor_visible(_gba: &mut agb::Gba) {
        // 1 column, 10 items, 3 visible rows
        let mut cursor = ScrollingCursor::new(1, 10, 3);
        assert_eq!(cursor.first_visible_row(), 0);
        cursor.set_idx(2);
        assert_eq!(cursor.first_visible_row(), 0);
        cursor.set_idx(5);
        assert_eq!(cursor.first_visible_row(), 3);
        cursor.set_idx(9);
        assert_eq!(cursor.first_visible_row(), 7);
        cursor.set_idx(7);
        assert_eq!(cursor.first_visible_row(), 7);
        cursor.set_idx(0);
        assert_eq!(cursor.first_visible_row(), 0);
    }
}