diff --git a/CHANGELOG.md b/CHANGELOG.md index ef823e9..a8c0506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- New start menu with "New Game", "Settings", "Credits" and "Quit" buttons + +### Changed + +- Changed basic English (US) translations for start menu +- Changed basic Russian (RU) translations for start menu +- Changed basic German (DE) translations for start menu + ## [0.0.22] - 2023-11-03 ### Added diff --git a/Cargo.lock b/Cargo.lock index 1ef4433..51d1f82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -350,6 +350,17 @@ dependencies = [ "syn 2.0.38", ] +[[package]] +name = "bevy-ui-navigation" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea489c1999072befdd555259cd85038b1ed46d09577ad6d2b759f81a2ef4e281" +dependencies = [ + "bevy", + "bevy_mod_picking", + "non-empty-vec", +] + [[package]] name = "bevy_a11y" version = "0.11.3" @@ -600,6 +611,37 @@ dependencies = [ "encase_derive_impl", ] +[[package]] +name = "bevy_eventlistener" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233f729a5a2c7182f89b17c5d603cd604d095ee68d22c81b29be3cd30324f77d" +dependencies = [ + "bevy_eventlistener_core", + "bevy_eventlistener_derive", +] + +[[package]] +name = "bevy_eventlistener_core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd344e21cb61878a24169bff3f2e0bc5584df77b06732b5223a13a96a92dfe9" +dependencies = [ + "bevy", +] + +[[package]] +name = "bevy_eventlistener_derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "407110082f2a861eb198c254226aa4a24c239fcff235f2aec41b0b0db5e8937f" +dependencies = [ + "bevy_eventlistener_core", + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "bevy_fluent" version = "0.7.0" @@ -806,6 +848,19 @@ dependencies = [ "glam", ] +[[package]] +name = "bevy_mod_picking" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce0837727f451ec62dad98a7bd35a8ff929e5a148bb0044122aba02683c09df" +dependencies = [ + "bevy", + "bevy_eventlistener", + "bevy_picking_core", + "bevy_picking_input", + "bevy_picking_ui", +] + [[package]] name = "bevy_pbr" version = "0.11.3" @@ -829,6 +884,36 @@ dependencies = [ "radsort", ] +[[package]] +name = "bevy_picking_core" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b10f159071accb3f17f86b70a2cafed27f96a828f155721d8f82706d5a65af" +dependencies = [ + "bevy", + "bevy_eventlistener", +] + +[[package]] +name = "bevy_picking_input" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc165dccadf85856cd713f1dabf5c845c7b6fd56e2c901a53f69f3693c5149" +dependencies = [ + "bevy", + "bevy_picking_core", +] + +[[package]] +name = "bevy_picking_ui" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1533a7723939a1dfad933da76a4cfb0d37eb15abd64519c0db2477f04de1ee8b" +dependencies = [ + "bevy", + "bevy_picking_core", +] + [[package]] name = "bevy_ptr" version = "0.11.3" @@ -2940,6 +3025,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "non-empty-vec" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceeba69aa8d4c53cdceeac8f17eb2656bb88b468bbe6c0889d34edfdea26ec8b" + [[package]] name = "notify" version = "6.1.1" @@ -4294,6 +4385,7 @@ version = "0.0.22" dependencies = [ "bevy", "bevy-inspector-egui", + "bevy-ui-navigation", "bevy_asset_loader", "bevy_common_assets", "bevy_fluent", diff --git a/Cargo.toml b/Cargo.toml index 27320bd..fcb7f00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] bevy = "0.11.3" bevy-inspector-egui = "0.20.0" +bevy-ui-navigation = "0.32.0" bevy_asset_loader = { version = "0.17.0", features = ["2d"] } bevy_common_assets = { version = "0.7.0", features = ["ron"] } bevy_fluent = "0.7.0" diff --git a/src/inputs/menu.rs b/src/inputs/menu.rs index 876736e..0768a71 100644 --- a/src/inputs/menu.rs +++ b/src/inputs/menu.rs @@ -3,15 +3,26 @@ use leafwing_input_manager::prelude::*; #[derive(Actionlike, PartialEq, Eq, Clone, Copy, Hash, Debug, Reflect)] pub enum MenuAction { + Select, Start, + Settings, Credits, + Quit, } pub fn menu_input_map() -> InputMap { let mut input_map = InputMap::::new([ - (KeyCode::Return, MenuAction::Start), + // Action Keys + (KeyCode::Return, MenuAction::Select), + // Hotkeys + (KeyCode::N, MenuAction::Start), + (KeyCode::S, MenuAction::Settings), (KeyCode::C, MenuAction::Credits), + (KeyCode::Q, MenuAction::Quit), ]); + // Action Buttons + input_map.insert(GamepadButtonType::South, MenuAction::Select); + // Hotkey Buttons input_map.insert(GamepadButtonType::Start, MenuAction::Start); input_map.insert(GamepadButtonType::North, MenuAction::Credits); diff --git a/src/systems/mod.rs b/src/systems/mod.rs index 2067a58..8e6efd0 100644 --- a/src/systems/mod.rs +++ b/src/systems/mod.rs @@ -1,4 +1,5 @@ use bevy::prelude::*; +use bevy_ui_navigation::prelude::NavRequestSystem; pub mod events; pub mod states; @@ -133,6 +134,12 @@ impl Plugin for SystemsPlugin { Update, start_menu::menu_input_system.run_if(is_in_menu_state), ); + app.add_systems( + Update, + start_menu::menu_focus_system + .after(NavRequestSystem) + .run_if(is_in_menu_state), + ); app.add_systems( Update, diff --git a/src/ui/menus/start_menu.rs b/src/ui/menus/start_menu.rs index 4fdb7f8..7418d79 100644 --- a/src/ui/menus/start_menu.rs +++ b/src/ui/menus/start_menu.rs @@ -1,7 +1,9 @@ use bevy::{ + app::AppExit, audio::{PlaybackMode, Volume}, prelude::*, }; +use bevy_ui_navigation::{prelude::*, systems::InputMapping}; use fluent_content::Content; use leafwing_input_manager::prelude::{ActionState, InputManagerPlugin}; @@ -15,11 +17,30 @@ use crate::{ pub struct MenuPlugin; impl Plugin for MenuPlugin { fn build(&self, app: &mut App) { - app.add_plugins(InputManagerPlugin::::default()); + app.add_plugins(( + InputManagerPlugin::::default(), + DefaultNavigationPlugins, + )); } } -pub(crate) fn init_start_menu(mut commands: Commands, audios: Res) { +#[derive(Component)] +pub enum MenuButton { + NewGame, + Settings, + Credits, + Quit, +} + +pub(crate) fn init_start_menu( + mut commands: Commands, + audios: Res, + mut input_mapping: ResMut, +) { + // TODO: We might prefer to do this part ourselves... maybe. + input_mapping.keyboard_navigation = true; + // input_mapping.focus_follows_mouse = true; + commands.insert_resource(menu_input_map()); commands.insert_resource(ActionState::::default()); @@ -76,35 +97,33 @@ pub(crate) fn spawn_start_menu(mut commands: Commands, ui: Res, i18n: Name::new("Title"), )); - for string in ["new-game", "settings", "credits", "quit"] { - parent - .spawn(ButtonBundle { + for (string, marker) in [ + ("new-game", MenuButton::NewGame), + ("settings", MenuButton::Settings), + ("credits", MenuButton::Credits), + ("quit", MenuButton::Quit), + ] { + parent.spawn(( + TextBundle { + text: Text::from_section( + i18n.content(string).unwrap().to_ascii_uppercase(), + TextStyle { + font: ui.font.clone(), + font_size: 25.0, + color: Color::rgb_u8(0x00, 0x88, 0x88), + }, + ), style: Style { margin: UiRect::top(Val::Px(25.)), justify_content: JustifyContent::Center, align_items: AlignItems::Center, ..default() }, - background_color: BackgroundColor(Color::Rgba { - red: 0., - green: 0., - blue: 0., - alpha: 0., - }), ..default() - }) - .with_children(|parent| { - parent.spawn( - TextBundle::from_section( - i18n.content(string).unwrap().to_ascii_uppercase(), - TextStyle { - font: ui.font.clone(), - font_size: 25.0, - color: Color::rgb_u8(0x00, 0x88, 0x88), - }, - ), // DrawBlinkTimer(Timer::from_seconds(0.65, TimerMode::Repeating)), - ); - }); + }, // DrawBlinkTimer(Timer::from_seconds(0.65, TimerMode::Repeating)), + Focusable::default(), + marker, + )); } }); } @@ -113,7 +132,32 @@ pub(crate) fn menu_input_system( state: Res>, mut next_state: ResMut>, inputs: Res>, + mut requests: EventWriter, + mut buttons: Query<&mut MenuButton>, + mut events: EventReader, + mut exit: EventWriter, ) { + events.nav_iter().activated_in_query_foreach_mut( + &mut buttons, + |mut button| match &mut *button { + MenuButton::NewGame => { + next_state.set(GameState::GameCreate); + } + MenuButton::Settings => { + // start the game + } + MenuButton::Credits => { + next_state.set(GameState::Credits); + } + MenuButton::Quit => { + exit.send(AppExit); + } + }, + ); + + if inputs.just_pressed(MenuAction::Select) { + requests.send(NavRequest::Action); + } if inputs.just_pressed(MenuAction::Start) { next_state.set(GameState::GameCreate); } @@ -129,3 +173,16 @@ pub(crate) fn menu_input_system( } } } + +// TODO: This is very general. It can be moved to a less specific location. +pub(crate) fn menu_focus_system( + mut interaction_query: Query<(&Focusable, &mut Text), Changed>, +) { + for (focusable, mut text) in interaction_query.iter_mut() { + if let FocusState::Focused = focusable.state() { + text.sections[0].style.color = Color::rgb_u8(0x00, 0x88, 0x88); + } else { + text.sections[0].style.color = Color::rgb_u8(0x00, 0x44, 0x44); + } + } +}