From 3b7f6263944830765ac64f25cac171540d881cf8 Mon Sep 17 00:00:00 2001 From: "Elijah C. Voigt" Date: Wed, 5 Jun 2024 21:48:08 -0700 Subject: [PATCH] Adding some foundational UI elements --- src/ecs.rs | 19 +++++++++ src/game/dice.rs | 14 +++++++ src/game/mod.rs | 32 +++++++++++++++ src/main.rs | 36 +++++++++++++++-- src/menu.rs | 102 ++++++++++++++++++++++------------------------- src/prelude.rs | 7 ++++ src/ui.rs | 57 ++++++++++++++++++++++++++ 7 files changed, 209 insertions(+), 58 deletions(-) create mode 100644 src/ecs.rs create mode 100644 src/game/dice.rs create mode 100644 src/game/mod.rs create mode 100644 src/prelude.rs create mode 100644 src/ui.rs diff --git a/src/ecs.rs b/src/ecs.rs new file mode 100644 index 0000000..c2e1ff8 --- /dev/null +++ b/src/ecs.rs @@ -0,0 +1,19 @@ +use bevy::prelude::*; + +/// Module mirroring bevy::ecs::schedule +pub(crate) mod schedule { + use super::*; + + /// Module mirroring bevy::ecs::schedule::common_conditions + pub(crate) mod common_conditions { + use super::*; + + /// Missing (in my opinion) condition for checking if any of a given component have changed + pub(crate) fn any_component_changed(q: Query>) -> bool + where + T: Component, + { + !q.is_empty() + } + } +} diff --git a/src/game/dice.rs b/src/game/dice.rs new file mode 100644 index 0000000..2bbe3e7 --- /dev/null +++ b/src/game/dice.rs @@ -0,0 +1,14 @@ +use crate::prelude::*; + +/// Menu Plugin; empty struct for Plugin impl +pub(crate) struct DicePlugin; + +impl Plugin for DicePlugin { + fn build(&self, app: &mut App) { + app.add_systems(Startup, init_dice_ui); + } +} + +fn init_dice_ui(mut commands: Commands) { + todo!() +} diff --git a/src/game/mod.rs b/src/game/mod.rs new file mode 100644 index 0000000..d66954c --- /dev/null +++ b/src/game/mod.rs @@ -0,0 +1,32 @@ +use crate::prelude::*; + +/// Dice game module +pub(crate) mod dice; + +/// Menu Plugin; empty struct for Plugin impl +pub(crate) struct GamePlugin; + +impl Plugin for GamePlugin { + fn build(&self, app: &mut App) { + app.init_state::(); + app.add_systems(Startup, init_camera); + } +} + +#[derive(States, Debug, Clone, PartialEq, Eq, Hash, Default)] +enum GameChoice { + #[default] + None, + Dice, +} + +/// Main game camera +fn init_camera(mut commands: Commands) { + commands.spawn(Camera2dBundle { + camera: Camera { + clear_color: ClearColorConfig::Custom(Color::WHITE), + ..default() + }, + ..default() + }); +} diff --git a/src/main.rs b/src/main.rs index 68b5308..643bb27 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,44 @@ -extern crate wee_alloc; - +// TODO: Should we use wee_alloc? // Use `wee_alloc` as the global allocator. #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; +extern crate wee_alloc; -use bevy::prelude::*; +/// Game Menu +pub(crate) mod menu; + +/// Generic UI elements +pub(crate) mod ui; + +/// Module mirroring bevy::ecs +pub(crate) mod ecs; -mod menu; +/// Imports used across the project +pub(crate) mod prelude; + +pub(crate) mod game; + +use bevy::prelude::*; fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins); app.add_plugins(menu::MenuPlugin); + app.add_plugins(ui::UiPlugin); + app.add_plugins(game::GamePlugin); app.run(); } + +/// Hide entities with a given component +pub(crate) fn manage_visibility( + mut q: Query<(&mut Visibility, &SC)>, + r: Res>, +) { + q.iter_mut().for_each(|(mut v, sc)| { + if *sc == *r.get() { + *v = Visibility::Inherited; + } else { + *v = Visibility::Hidden; + } + }); +} diff --git a/src/menu.rs b/src/menu.rs index a821517..4ee9374 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -1,67 +1,61 @@ -use bevy::prelude::*; +use crate::{manage_visibility, prelude::*}; /// Menu Plugin; empty struct for Plugin impl pub(crate) struct MenuPlugin; impl Plugin for MenuPlugin { - fn build(&self, app: &mut App) { - app.init_state::(); - app.add_systems(Startup, init_menu); - app.add_systems(OnEnter(MenuState::Open), open_menu); - app.add_systems(OnExit(MenuState::Open), close_menu); - } + fn build(&self, app: &mut App) { + app.init_state::(); + app.add_systems(Startup, init_menu_ui); + app.add_systems( + Update, + manage_visibility::.run_if(state_changed::), + ); + } } /// State tracking if the menu is open or closed -#[derive(States, Debug, Clone, PartialEq, Eq, Hash, Default)] +#[derive(States, Debug, Clone, PartialEq, Eq, Hash, Default, Component)] enum MenuState { - #[default] - Closed, - Open, + #[default] + Open, + Closed, } -#[derive(Component)] -struct MenuUi; - /// Initialize menu UI nodes at startup -fn init_menu( - mut commands: Commands, -) { - commands.spawn(Camera2dBundle { ..default() }); - - commands.spawn(( - MenuUi, - NodeBundle { - ..default() - } - )).with_children(|parent| { - parent.spawn( - ButtonBundle { - ..default() - } - ).with_children(|parent| { - parent.spawn(TextBundle { - text: Text::from_section("START", TextStyle { color: Color::BLACK, ..default() }), - ..default() - }); - }); - }); +fn init_menu_ui(mut commands: Commands) { + commands + .spawn(( + MenuState::Open, + NodeBundle { + style: Style { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + align_items: AlignItems::Center, + align_content: AlignContent::Center, + justify_items: JustifyItems::Center, + justify_content: JustifyContent::Center, + flex_direction: FlexDirection::Column, + ..default() + }, + ..default() + }, + )) + .with_children(|parent| { + let title_text_style = TextStyle { + color: Color::BLACK, + font_size: 24.0, + ..default() + }; + + parent.spawn(TextBundle { + text: Text::from_section("Game Jam Casino", title_text_style), + style: Style { + margin: UiRect::all(Val::Px(5.0)), + ..default() + }, + ..default() + }); + parent.spawn_empty().add(UiButton { label: "Dice" }); + }); } - -/// Make menu UI visible -fn open_menu( - mut query: Query<&mut Visibility, With>, -) { - query.iter_mut().for_each(|mut v| { - *v = Visibility::Hidden - }) -} - -/// Hide menu UI -fn close_menu( - mut query: Query<&mut Visibility, With>, -) { - query.iter_mut().for_each(|mut v| { - *v = Visibility::Inherited - }) -} \ No newline at end of file diff --git a/src/prelude.rs b/src/prelude.rs new file mode 100644 index 0000000..1aa70bf --- /dev/null +++ b/src/prelude.rs @@ -0,0 +1,7 @@ +/// Bevy imports +pub(crate) use bevy::{ecs::system::EntityCommand, prelude::*}; + +/// Intra-project imports +pub(crate) use crate::ecs::schedule::common_conditions::*; +pub(crate) use crate::manage_visibility; +pub(crate) use crate::ui::button::UiButton; diff --git a/src/ui.rs b/src/ui.rs new file mode 100644 index 0000000..a852732 --- /dev/null +++ b/src/ui.rs @@ -0,0 +1,57 @@ +use crate::prelude::*; + +/// Menu Plugin; empty struct for Plugin impl +pub(crate) struct UiPlugin; + +impl Plugin for UiPlugin { + fn build(&self, app: &mut App) { + app.add_systems( + Update, + button_interaction.run_if(any_component_changed::), + ); + } +} + +fn button_interaction(mut query: Query<(&mut BackgroundColor, &Interaction)>) { + query.iter_mut().for_each(|(mut bg, i)| match i { + Interaction::Hovered => bg.0 = Color::ORANGE, + Interaction::Pressed => bg.0 = Color::GREEN, + Interaction::None => bg.0 = Color::WHITE, + }); +} + +pub(crate) mod button { + use super::*; + + pub(crate) struct UiButton { + pub label: &'static str, + } + + impl EntityCommand for UiButton { + fn apply(self, id: Entity, world: &mut World) { + let button_text_style = TextStyle { + color: Color::BLACK, + font_size: 16.0, + ..default() + }; + world + .entity_mut(id) + .insert(ButtonBundle { + style: Style { + margin: UiRect::all(Val::Px(5.0)), + padding: UiRect::all(Val::Px(5.0)), + border: UiRect::all(Val::Px(1.0)), + ..default() + }, + border_color: BorderColor(Color::BLACK), + ..default() + }) + .with_children(|parent| { + parent.spawn(TextBundle { + text: Text::from_section(self.label, button_text_style), + ..default() + }); + }); + } + } +}