diff --git a/assets/fonts/FiraSans-Bold.ttf b/assets/fonts/FiraSans-Bold.ttf new file mode 100644 index 0000000..95e1660 Binary files /dev/null and b/assets/fonts/FiraSans-Bold.ttf differ diff --git a/src/main_menu/components.rs b/src/main_menu/components.rs new file mode 100644 index 0000000..5b8856c --- /dev/null +++ b/src/main_menu/components.rs @@ -0,0 +1,10 @@ +use bevy::prelude::Component; + +#[derive(Component)] +pub struct MainMenu {} + +#[derive(Component)] +pub struct PlayButton {} + +#[derive(Component)] +pub struct QuitButton {} diff --git a/src/main_menu/mod.rs b/src/main_menu/mod.rs index 01b4f92..a156797 100644 --- a/src/main_menu/mod.rs +++ b/src/main_menu/mod.rs @@ -1,13 +1,27 @@ +mod components; +mod styles; +mod systems; + +use systems::interactions::*; +use systems::layout::*; + use bevy::prelude::*; +use crate::AppState; + pub struct MainMenuPlugin; impl Plugin for MainMenuPlugin { fn build(&self, app: &mut App) { - app.add_startup_system(main_menu); + app + // OnEnter State Systems + .add_system(spawn_main_menu.in_schedule(OnEnter(AppState::MainMenu))) + // Systems + .add_systems( + (interact_with_play_button, interact_with_quit_button) + .in_set(OnUpdate(AppState::MainMenu)), + ) + // OnExit State Systems + .add_system(despawn_main_menu.in_schedule(OnExit(AppState::MainMenu))); } } - -pub fn main_menu() { - println!("You are on the main menu."); -} diff --git a/src/main_menu/styles.rs b/src/main_menu/styles.rs new file mode 100644 index 0000000..cfadb98 --- /dev/null +++ b/src/main_menu/styles.rs @@ -0,0 +1,51 @@ +use bevy::prelude::*; + +pub const NORMAL_BUTTON_COLOR: Color = Color::rgb(0.15, 0.15, 0.15); +pub const HOVERED_BUTTON_COLOR: Color = Color::rgb(0.25, 0.25, 0.25); +pub const PRESSED_BUTTON_COLOR: Color = Color::rgb(0.35, 0.75, 0.35); + +pub const MAIN_MENU_STYLE: Style = Style { + flex_direction: FlexDirection::Column, + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), + gap: Size::new(Val::Px(8.0), Val::Px(8.0)), + ..Style::DEFAULT +}; + +pub const BUTTON_STYLE: Style = Style { + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + size: Size::new(Val::Px(200.0), Val::Px(80.0)), + ..Style::DEFAULT +}; + +pub const IMAGE_STYLE: Style = Style { + size: Size::new(Val::Px(64.0), Val::Px(64.0)), + margin: UiRect::new(Val::Px(8.0), Val::Px(8.0), Val::Px(8.0), Val::Px(8.0)), + ..Style::DEFAULT +}; + +pub const TITLE_STYLE: Style = Style { + flex_direction: FlexDirection::Row, + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + size: Size::new(Val::Px(300.0), Val::Px(120.0)), + ..Style::DEFAULT +}; + +pub fn get_title_text_style(asset_server: &Res) -> TextStyle { + TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 64.0, + color: Color::WHITE, + } +} + +pub fn get_button_text_style(asset_server: &Res) -> TextStyle { + TextStyle { + font: asset_server.load("fonts/FiraSans-Bold.ttf"), + font_size: 32.0, + color: Color::WHITE, + } +} diff --git a/src/main_menu/systems/interactions.rs b/src/main_menu/systems/interactions.rs new file mode 100644 index 0000000..f6dbbcf --- /dev/null +++ b/src/main_menu/systems/interactions.rs @@ -0,0 +1,52 @@ +use bevy::app::AppExit; +use bevy::prelude::*; + +use crate::main_menu::components::*; +use crate::main_menu::styles::{HOVERED_BUTTON_COLOR, NORMAL_BUTTON_COLOR, PRESSED_BUTTON_COLOR}; +use crate::AppState; + +pub fn interact_with_play_button( + mut button_query: Query< + (&Interaction, &mut BackgroundColor), + (Changed, With), + >, + mut app_state_next_state: ResMut>, +) { + if let Ok((interaction, mut background_color)) = button_query.get_single_mut() { + match *interaction { + Interaction::Clicked => { + *background_color = PRESSED_BUTTON_COLOR.into(); + app_state_next_state.set(AppState::Game); + } + Interaction::Hovered => { + *background_color = HOVERED_BUTTON_COLOR.into(); + } + Interaction::None => { + *background_color = NORMAL_BUTTON_COLOR.into(); + } + } + } +} + +pub fn interact_with_quit_button( + mut app_exit_event_writer: EventWriter, + mut button_query: Query< + (&Interaction, &mut BackgroundColor), + (Changed, With), + >, +) { + if let Ok((interaction, mut background_color)) = button_query.get_single_mut() { + match *interaction { + Interaction::Clicked => { + *background_color = PRESSED_BUTTON_COLOR.into(); + app_exit_event_writer.send(AppExit); + } + Interaction::Hovered => { + *background_color = HOVERED_BUTTON_COLOR.into(); + } + Interaction::None => { + *background_color = NORMAL_BUTTON_COLOR.into(); + } + } + } +} diff --git a/src/main_menu/systems/layout.rs b/src/main_menu/systems/layout.rs new file mode 100644 index 0000000..cac2ec7 --- /dev/null +++ b/src/main_menu/systems/layout.rs @@ -0,0 +1,108 @@ +use bevy::prelude::*; + +use crate::main_menu::components::*; +use crate::main_menu::styles::*; + +pub fn spawn_main_menu(mut commands: Commands, asset_server: Res) { + let main_menu_entity = build_main_menu(&mut commands, &asset_server); +} + +pub fn despawn_main_menu(mut commands: Commands, main_menu_query: Query>) { + if let Ok(main_menu_entity) = main_menu_query.get_single() { + commands.entity(main_menu_entity).despawn_recursive(); + } +} + +pub fn build_main_menu(commands: &mut Commands, asset_server: &Res) -> Entity { + let main_menu_entity = commands + .spawn(( + NodeBundle { + style: MAIN_MENU_STYLE, + ..default() + }, + MainMenu {}, + )) + .with_children(|parent| { + // === Title === + parent + .spawn(NodeBundle { + style: TITLE_STYLE, + ..default() + }) + .with_children(|parent| { + // Image 1 + parent.spawn(ImageBundle { + style: IMAGE_STYLE, + image: asset_server.load("sprites/ball_blue_large.png").into(), + ..default() + }); + // Text + parent.spawn(TextBundle { + text: Text { + sections: vec![TextSection::new( + "Bevy Ball Game", + get_title_text_style(&asset_server), + )], + alignment: TextAlignment::Center, + ..default() + }, + ..default() + }); + // Image 2 + parent.spawn(ImageBundle { + style: IMAGE_STYLE, + image: asset_server.load("sprites/ball_red_large.png").into(), + ..default() + }); + }); + // === Play Button === + parent + .spawn(( + ButtonBundle { + style: BUTTON_STYLE, + background_color: NORMAL_BUTTON_COLOR.into(), + ..default() + }, + PlayButton {}, + )) + .with_children(|parent| { + parent.spawn(TextBundle { + text: Text { + sections: vec![TextSection::new( + "Play", + get_button_text_style(&asset_server), + )], + alignment: TextAlignment::Center, + ..default() + }, + ..default() + }); + }); + // === Quit Button === + parent + .spawn(( + ButtonBundle { + style: BUTTON_STYLE, + background_color: NORMAL_BUTTON_COLOR.into(), + ..default() + }, + QuitButton {}, + )) + .with_children(|parent| { + parent.spawn(TextBundle { + text: Text { + sections: vec![TextSection::new( + "Quit", + get_button_text_style(&asset_server), + )], + alignment: TextAlignment::Center, + ..default() + }, + ..default() + }); + }); + }) + .id(); + + main_menu_entity +} diff --git a/src/main_menu/systems/mod.rs b/src/main_menu/systems/mod.rs new file mode 100644 index 0000000..9e6c6a1 --- /dev/null +++ b/src/main_menu/systems/mod.rs @@ -0,0 +1,2 @@ +pub mod interactions; +pub mod layout;