use crate::state::MenuState;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct MenuBarState {
open: usize,
menu: MenuState,
hover: Option<usize>,
}
impl MenuBarState {
#[must_use]
pub const fn new() -> Self {
Self {
open: 0,
menu: MenuState::new(),
hover: None,
}
}
#[must_use]
pub const fn open_label(&self) -> Option<usize> {
if self.menu.is_open() {
Some(self.open)
} else {
None
}
}
#[must_use]
pub const fn menu(&self) -> &MenuState {
&self.menu
}
pub const fn menu_mut(&mut self) -> &mut MenuState {
&mut self.menu
}
pub fn open(&mut self, label: usize, len: usize, selectable: impl Fn(usize) -> bool) {
self.menu.close();
self.menu.open(len, selectable);
self.open = label;
}
pub const fn close(&mut self) {
self.menu.close();
}
pub fn toggle(&mut self, label: usize, len: usize, selectable: impl Fn(usize) -> bool) {
if self.open_label() == Some(label) {
self.close();
} else {
self.open(label, len, selectable);
}
}
pub fn hover(&mut self, label: Option<usize>, len: usize, selectable: impl Fn(usize) -> bool) {
if label != self.hover {
if let Some(label) = label
&& self.open_label().is_some_and(|open| open != label)
{
self.open(label, len, selectable);
}
self.hover = label;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn selectable_at(indices: &'static [usize]) -> impl Fn(usize) -> bool {
move |i| indices.contains(&i)
}
#[test]
fn starts_with_nothing_open() {
let state = MenuBarState::new();
assert_eq!(state.open_label(), None);
assert!(!state.menu().is_open());
}
#[test]
fn open_selects_the_first_selectable_row() {
let mut state = MenuBarState::new();
state.open(1, 3, selectable_at(&[1, 2]));
assert_eq!(state.open_label(), Some(1));
assert_eq!(state.menu().selected(), Some(1));
}
#[test]
fn opening_a_different_label_closes_the_first() {
let mut state = MenuBarState::new();
state.open(0, 2, selectable_at(&[0, 1]));
state.open(1, 2, selectable_at(&[0, 1]));
assert_eq!(state.open_label(), Some(1));
assert_eq!(state.menu().selected(), Some(0));
}
#[test]
fn a_label_with_no_selectable_row_never_opens() {
let mut state = MenuBarState::new();
state.open(0, 2, selectable_at(&[]));
assert_eq!(state.open_label(), None);
}
#[test]
fn close_clears_open_label() {
let mut state = MenuBarState::new();
state.open(0, 1, selectable_at(&[0]));
state.close();
assert_eq!(state.open_label(), None);
assert!(!state.menu().is_open());
}
#[test]
fn toggle_closes_the_same_label_but_opens_a_different_one() {
let mut state = MenuBarState::new();
state.toggle(0, 1, selectable_at(&[0]));
assert_eq!(state.open_label(), Some(0));
state.toggle(0, 1, selectable_at(&[0]));
assert_eq!(state.open_label(), None);
state.toggle(0, 1, selectable_at(&[0]));
state.toggle(1, 1, selectable_at(&[0]));
assert_eq!(state.open_label(), Some(1));
}
#[test]
fn open_label_reads_none_once_the_inner_menu_state_closes_directly() {
let mut state = MenuBarState::new();
state.open(2, 1, selectable_at(&[0]));
state.menu_mut().close();
assert_eq!(state.open_label(), None);
}
}