use crate::graphics::display;
use crate::graphics::display::{Display, Renderer};
use crate::input::Input;
pub struct Menu<'a> {
top: usize,
selected: usize,
rows: usize,
items: Vec<MenuItem<'a>>,
}
struct MenuItem<'a> {
name: String,
callback: &'a dyn Fn(),
}
impl<'a> Menu<'a> {
pub fn new(rows: usize) -> Self {
Self {
top: 0,
selected: 0,
rows,
items: vec![],
}
}
pub fn push<F: Fn() + 'a>(&mut self, name: &str, code: &'a F) {
self.items.push(MenuItem {
name: name.to_owned(),
callback: code,
});
}
fn update_bounds(&mut self) {
self.selected %= self.items.len();
let offset = self.selected as isize - self.top as isize;
if offset < 0 {
self.top = self.selected;
} else if offset >= self.rows as isize {
self.top = self.selected - self.rows + 1;
}
}
}
impl Menu<'_> {
pub fn render<D: Display>(&self, renderer: &mut Renderer<D>) {
let start = self.top;
let end = usize::min(start + self.rows, self.items.len());
let row_height = renderer.height() / self.rows as u32;
renderer.clear();
for idx in start..end {
let name = &self.items[idx].name;
if idx == self.selected {
renderer.draw_rectangle_solid(
0,
row_height as i32 * (idx as i32 - start as i32),
renderer.width(),
row_height,
display::BLACK,
);
renderer.draw_text(
name,
10,
row_height as i32 * (idx as i32 - start as i32),
row_height as f32,
display::WHITE,
);
} else {
renderer.draw_text(
name,
10,
row_height as i32 * (idx as i32 - start as i32),
row_height as f32,
display::BLACK,
);
}
}
renderer.update();
}
pub fn notify_input(&mut self, input: &Input) -> bool {
if input.is_down() {
self.selected += 1;
}
if input.is_up() {
self.selected += self.items.len().max(1) - 1;
}
self.update_bounds();
if input.is_enter() {
if let Some(item) = self.items.get_mut(self.selected) {
(item.callback)();
} else {
eprintln!("Menu cursor in invalid position");
}
}
!input.is_left()
}
}