You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
martian-chess/src/menu.rs

415 lines
15 KiB
Rust

use crate::prelude::*;
pub(crate) struct MenuPlugin;
impl Plugin for MenuPlugin {
fn build(&self, app: &mut App) {
app.init_state::<MenuState>()
// Initialize menus
.add_systems(
// TODO: move this to game.rs or display3d.rs
OnExit(GameState::Loading),
init_play_menu,
)
// Manage visible/hidden states
.add_systems(
Update,
manage_state_entities::<MenuState>().run_if(state_changed::<MenuState>),
)
.add_systems(
PostUpdate,
(
handle_button_press::<GameState>,
handle_button_press::<MenuState>,
handle_button_press::<TutorialState>,
handle_button_press::<PlayState>,
)
.run_if(any_component_changed::<Interaction>()),
)
.add_systems(
Update,
manage_ai_button.run_if(state_changed::<ai::PlayState>),
)
.add_systems(Update, handle_escape.run_if(just_pressed(KeyCode::Escape)));
}
}
#[derive(Debug, States, Hash, Default, PartialEq, Eq, Clone, Component)]
pub(crate) enum MenuState {
#[default]
Off,
On,
}
#[derive(Debug, Component)]
pub(crate) struct ButtonAction<S: States + Clone + Component>(pub S);
fn init_play_menu(
mut commands: Commands,
tweaks_file: Res<tweak::GameTweaks>,
tweaks: Res<Assets<tweak::Tweaks>>,
) {
debug!("Initializing Play menu");
let tweak = tweaks.get(tweaks_file.handle.clone()).expect("Load tweaks");
let button_handle = tweak.get_handle::<Image>("buttons_image_resting").unwrap();
let font_handle = tweak.get_handle::<Font>("buttons_font").unwrap();
let title_handle = tweak.get_handle::<Image>("title_image").unwrap();
let title_width_px = tweak.get::<f32>("title_width_px").unwrap();
commands
.spawn((
MenuState::On,
NodeBundle {
style: Style {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
flex_direction: FlexDirection::Column,
position_type: PositionType::Absolute,
..default()
},
background_color: Color::NONE.into(),
visibility: Visibility::Hidden,
..default()
},
))
.with_children(|parent| {
// Title
parent.spawn(ImageBundle {
style: Style {
width: Val::Px(title_width_px),
..default()
},
image: UiImage {
texture: title_handle.clone(),
..default()
},
..default()
});
// Continue button
parent
.spawn((
ButtonAction(GameState::Play),
ButtonAction(MenuState::Off),
ButtonBundle {
style: Style {
padding: UiRect::all(Val::Px(2.0)),
margin: UiRect::all(Val::Px(2.0)),
..default()
},
image: UiImage {
texture: button_handle.clone(),
..default()
},
..default()
},
))
.with_children(|parent| {
parent.spawn(TextBundle {
text: Text {
sections: vec![TextSection {
value: "Continue".into(),
style: TextStyle {
color: Color::WHITE,
font_size: 12.0,
font: font_handle.clone(),
},
}],
..default()
},
style: Style {
margin: UiRect::all(Val::Px(10.0)),
..default()
},
..default()
});
});
// Restart button
parent
.spawn((
ButtonAction(GameState::Restart),
ButtonAction(MenuState::Off),
ButtonBundle {
style: Style {
padding: UiRect::all(Val::Px(2.0)),
margin: UiRect::all(Val::Px(2.0)),
..default()
},
image: UiImage {
texture: button_handle.clone(),
..default()
},
..default()
},
))
.with_children(|parent| {
parent.spawn(TextBundle {
text: Text {
sections: vec![TextSection {
value: "New Game".into(),
style: TextStyle {
color: Color::WHITE,
font_size: 12.0,
font: font_handle.clone(),
},
}],
..default()
},
style: Style {
margin: UiRect::all(Val::Px(10.0)),
..default()
},
..default()
});
});
// Tutorial button
parent
.spawn((
ButtonAction(TutorialState::Intro),
ButtonAction(MenuState::Off),
ButtonBundle {
style: Style {
padding: UiRect::all(Val::Px(2.0)),
margin: UiRect::all(Val::Px(2.0)),
..default()
},
image: UiImage {
texture: button_handle.clone(),
..default()
},
..default()
},
))
.with_children(|parent| {
parent.spawn(TextBundle {
text: Text {
sections: vec![TextSection {
value: "Tutorial".into(),
style: TextStyle {
color: Color::WHITE,
font_size: 12.0,
font: font_handle.clone(),
},
}],
..default()
},
style: Style {
margin: UiRect::all(Val::Px(10.0)),
..default()
},
..default()
});
});
// Opponent button
parent
.spawn((
ButtonAction(ai::PlayState::Human),
ButtonBundle {
style: Style {
padding: UiRect::all(Val::Px(2.0)),
margin: UiRect::all(Val::Px(2.0)),
..default()
},
image: UiImage {
texture: button_handle.clone(),
..default()
},
..default()
},
))
.with_children(|parent| {
parent.spawn(TextBundle {
text: Text {
sections: vec![TextSection {
value: "Human Opponent".into(),
style: TextStyle {
color: Color::WHITE,
font_size: 12.0,
font: font_handle.clone(),
},
}],
..default()
},
style: Style {
margin: UiRect::all(Val::Px(10.0)),
..default()
},
..default()
});
});
// Credits button
parent
.spawn((
ButtonAction(GameState::Credits),
ButtonAction(MenuState::Off),
ButtonBundle {
style: Style {
padding: UiRect::all(Val::Px(2.0)),
margin: UiRect::all(Val::Px(2.0)),
..default()
},
image: UiImage {
texture: button_handle.clone(),
..default()
},
..default()
},
))
.with_children(|parent| {
parent.spawn(TextBundle {
text: Text {
sections: vec![TextSection {
value: "Credits".into(),
style: TextStyle {
color: Color::WHITE,
font_size: 12.0,
font: font_handle.clone(),
},
}],
..default()
},
style: Style {
margin: UiRect::all(Val::Px(10.0)),
..default()
},
..default()
});
});
// Quit button
parent
.spawn((
ButtonAction(GameState::Quit),
ButtonAction(MenuState::Off),
ButtonBundle {
style: Style {
padding: UiRect::all(Val::Px(2.0)),
margin: UiRect::all(Val::Px(2.0)),
..default()
},
image: UiImage {
texture: button_handle.clone(),
..default()
},
..default()
},
))
.with_children(|parent| {
parent.spawn(TextBundle {
text: Text {
sections: vec![TextSection {
value: "Quit".into(),
style: TextStyle {
color: Color::WHITE,
font_size: 12.0,
font: font_handle.clone(),
},
}],
..default()
},
style: Style {
margin: UiRect::all(Val::Px(10.0)),
..default()
},
..default()
});
});
});
}
fn handle_escape(
game_state: Res<State<GameState>>,
tutorial_state: Res<State<TutorialState>>,
menu_state: Res<State<MenuState>>,
mut next_game_state: ResMut<NextState<GameState>>,
mut next_menu_state: ResMut<NextState<MenuState>>,
mut next_tutorial_state: ResMut<NextState<TutorialState>>,
) {
match game_state.get() {
GameState::Loading => error!("No menu while loading!"),
// State(Play): ...
GameState::Play => {
match tutorial_state.get() {
// State(Tutorial::None): Escape -> Play Menu
TutorialState::None => {
match menu_state.get() {
// State(Menu::None): Escape -> Show Menu
MenuState::Off => next_menu_state.set(MenuState::On),
// State(Menu::Play): Escape -> Hide menu
MenuState::On => next_menu_state.set(MenuState::Off),
}
}
// State(Tutorial::*): Escape -> Quit Tutorial
_ => {
next_tutorial_state.set(TutorialState::None);
}
}
}
// State(Endgame): Escape -> Quit game
GameState::Endgame => {
next_game_state.set(GameState::Quit);
}
// State(Credits): Escape -> Play + Menu
GameState::Credits => {
next_game_state.set(GameState::Play);
next_menu_state.set(MenuState::On);
}
// State(Intro): Escape -> Play
GameState::Intro => {
next_game_state.set(GameState::Title);
}
GameState::Title => {
next_game_state.set(GameState::Play);
}
GameState::Restart | GameState::Quit => {
panic!("This shouldn't be possible!");
}
}
}
fn handle_button_press<S: States + Clone + Component>(
events: Query<(&Interaction, &ButtonAction<S>), Changed<Interaction>>,
mut next_state: ResMut<NextState<S>>,
) {
events
.iter()
.filter_map(|(interaction, button_action)| {
(*interaction == Interaction::Pressed).then_some(button_action)
})
.for_each(|ButtonAction(ba)| {
debug!("Button press: {:?} => {:?}", next_state, ba);
next_state.set(ba.clone())
});
}
fn manage_ai_button(
curr: Res<State<ai::PlayState>>,
mut query: Query<(&mut ButtonAction<PlayState>, &Children)>,
mut texts: Query<&mut Text>,
) {
query.iter_mut().for_each(|(mut ba, children)| {
ba.0 = match curr.get() {
PlayState::AiBogo => PlayState::Human,
PlayState::Human => PlayState::AiBogo,
};
children.iter().for_each(|c| {
texts.get_mut(*c).iter_mut().for_each(|t| {
t.sections.iter_mut().for_each(|s| {
s.value = match ba.0 {
PlayState::AiBogo => "AI Opponent".into(),
PlayState::Human => "Human Opponent".into(),
}
});
});
});
});
}