use std::vec;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct MainMenu {
pub items: Vec<MainMenuItem>,
pub title: String,
pub initial: bool,
pub toggle_key: String,
pub dim: [f32; 4],
pub centered: bool,
pub x: f32,
pub y: f32,
pub button_width: f32,
pub button_height: f32,
pub row_gap: f32,
pub font: String,
pub font_px: f32,
pub text_color: [f32; 3],
pub text_scale: f32,
pub hover_color: [f32; 3],
pub hover_scale: f32,
pub cursor: bool,
pub cursor_color: [f32; 4],
pub cursor_size: f32,
pub settings_profile: SettingsProfile,
pub settings_back_action: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SettingsProfile {
#[default]
Full,
Minimal,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct MainMenuItem {
pub label: String,
pub action: String,
}
impl Default for MainMenu {
fn default() -> Self {
Self {
items: vec![
MainMenuItem {
label: "Return".to_string(),
action: "return".to_string(),
},
MainMenuItem {
label: "Settings".to_string(),
action: "settings".to_string(),
},
MainMenuItem {
label: "Quit".to_string(),
action: "quit".to_string(),
},
],
title: String::new(),
initial: false,
toggle_key: "Escape".to_string(),
dim: [0.0, 0.0, 0.0, 1.0],
centered: true,
x: 640.0,
y: 300.0,
button_width: 360.0,
button_height: 60.0,
row_gap: 14.0,
font: String::new(),
font_px: 48.0,
text_color: [0.85, 0.85, 0.85],
text_scale: 1.1,
hover_color: [1.0, 0.85, 0.3],
hover_scale: 1.0,
cursor: true,
cursor_color: [1.0, 1.0, 1.0, 1.0],
cursor_size: 22.0,
settings_profile: SettingsProfile::Full,
settings_back_action: String::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_default_menu_can_resume_configure_and_quit() {
let m = MainMenu::default();
let actions: Vec<&str> = m.items.iter().map(|i| i.action.as_str()).collect();
assert_eq!(actions, ["return", "settings", "quit"]);
assert_eq!(m.items[0].label, "Return");
assert_eq!(m.toggle_key, "Escape");
assert_eq!(m.settings_profile, SettingsProfile::Full);
assert!(!m.initial);
assert!(m.centered);
assert!(m.cursor);
}
#[test]
fn an_authored_item_list_replaces_the_defaults_wholesale() {
let m: MainMenu = serde_json::from_str(
r#"{"title":"Ash","initial":true,"items":[{"label":"Play","action":"start"}],
"settings_profile":"minimal","toggle_key":"Tab"}"#,
)
.unwrap();
assert_eq!(m.items.len(), 1);
assert_eq!(m.items[0].label, "Play");
assert_eq!(m.items[0].action, "start");
assert_eq!(m.title, "Ash");
assert!(m.initial);
assert_eq!(m.settings_profile, SettingsProfile::Minimal);
assert_eq!(m.toggle_key, "Tab");
assert_eq!((m.x, m.y), (640.0, 300.0));
assert_eq!(m.button_width, 360.0);
}
#[test]
fn a_blank_item_carries_neither_label_nor_action() {
let item = MainMenuItem::default();
assert!(item.label.is_empty());
assert!(item.action.is_empty());
}
#[test]
fn settings_profile_names_parse_in_lowercase() {
assert_eq!(SettingsProfile::default(), SettingsProfile::Full);
assert_eq!(
serde_json::from_str::<SettingsProfile>(r#""full""#).unwrap(),
SettingsProfile::Full
);
assert_eq!(
serde_json::to_string(&SettingsProfile::Minimal).unwrap(),
r#""minimal""#
);
}
#[test]
fn an_authored_menu_round_trips_through_postcard() {
let m: MainMenu = serde_json::from_str(
r#"{"items":[{"label":"Play","action":"start"},{"label":"Quit","action":"quit"}],
"dim":[0,0,0,0.7],"font":"body","font_px":32,"hover_scale":1.2,
"settings_back_action":"pause"}"#,
)
.unwrap();
let bytes = postcard::to_allocvec(&m).unwrap();
let back: MainMenu = postcard::from_bytes(&bytes).unwrap();
assert_eq!(back.items.len(), 2);
assert_eq!(back.items[1].action, "quit");
assert_eq!(back.dim, [0.0, 0.0, 0.0, 0.7]);
assert_eq!(back.font, "body");
assert_eq!(back.font_px, 32.0);
assert_eq!(back.hover_scale, 1.2);
assert_eq!(back.settings_back_action, "pause");
}
}