diff --git a/client/src/main.rs b/client/src/main.rs index 4c58adb..8d955a4 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -8,10 +8,21 @@ use bevy::{ use thiserror::Error; use tungstenite::{WebSocket, http::Response, stream::MaybeTlsStream}; +use crate::ui::{despawn_main_menu, spawn_main_menu}; + +mod ui; + fn main() { App::new() .add_plugins(DefaultPlugins) - .add_systems(Startup, setup) + .insert_state(GameState::MainMenu) + .add_systems(Startup, spawn_camera) + .add_systems(OnEnter(GameState::MainMenu), spawn_main_menu) + .add_systems(OnExit(GameState::MainMenu), despawn_main_menu) + .add_observer(ui::button_hover_start) + .add_observer(ui::button_hover_stop) + // TODO: System to operate buttons & other UI widgets + // .add_systems(Update, ) .add_systems( Update, ( @@ -20,7 +31,8 @@ fn main() { handle_tasks, send_info, recv_info, - ), + ) + .run_if(in_state(GameState::ConnectionDemo)), ) .add_message::() .insert_resource(ChatTimer { @@ -29,6 +41,29 @@ fn main() { .run(); } +/// Main game state indicator +#[derive(Debug, Hash, PartialEq, Eq, Clone, States)] +enum GameState { + MainMenu, + Playing, + ConnectionDemo, // TODO: Remove this state. +} + +/// Utility to despawn entities with a given component. Useful for scene +/// changes. +/// +/// I'm using this primarily with marker components to delete UI elements when +/// they are no longer needed. E.g.: Removing the main menu after starting. +pub fn despawn(mut commands: Commands, targets: Query>) { + targets + .iter() + .for_each(|entt| commands.entity(entt).despawn()); +} + +fn spawn_camera(mut commands: Commands) { + commands.spawn(Camera2d); +} + /// Initialize the scene fn setup( mut commands: Commands, diff --git a/client/src/ui.rs b/client/src/ui.rs new file mode 100644 index 0000000..d4acd0b --- /dev/null +++ b/client/src/ui.rs @@ -0,0 +1,92 @@ +//! All the UI elements for the Pong game client. + +use bevy::{ + color::palettes::css::{DARK_GRAY, GRAY}, + prelude::*, +}; + +pub const BTN_BORDER_COLOR: Color = Color::WHITE; +pub const BTN_BG_COLOR: Color = Color::BLACK; +pub const BTN_BG_SELECTED: Color = bevy::prelude::Color::Srgba(DARK_GRAY); + +pub fn spawn_main_menu(mut commands: Commands) { + commands + .spawn((Node { + width: Val::Percent(100.0), + height: Val::Percent(100.0), + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + flex_direction: FlexDirection::Column, + ..Default::default() + },)) + .with_children(|cmds| { + cmds.spawn(( + // TODO: A more sharp and square font, maybe "pixel art" bitmap + // to really get the chunky feel of Pong. + Text::new("Robert's Bad Pong Game"), + TextFont::from_font_size(50.0), + TextLayout::new_with_justify(Justify::Center), + TextShadow::default(), + )); + let mut start_button = cmds.spawn(button_bundle("Start game")); + start_button.observe(|_trigger: On>| { + info!("The start button was pressed."); + }); + let mut quit_button = cmds.spawn(button_bundle("Quit Game")); + quit_button.observe( + |_trigger: On>, mut messages: MessageWriter| { + info!("The quit button was pressed."); + // Quit the game if the quit button was pressed. + messages.write(AppExit::Success); + }, + ); + }); +} + +pub fn despawn_main_menu(mut commands: Commands) { + warn!("->> ui.rs: despawn_main_menu() is not implemented."); +} + +/// The basic bundle for generic buttons. +/// +/// It's mostly so I don't have to copy & paste this everywhere I want to use it. +fn button_bundle(text: &str) -> impl Bundle { + ( + Button, + Node { + width: Val::Px(150.0), + height: Val::Px(65.0), + border: UiRect::all(Val::Px(4.0)), + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + margin: UiRect::all(Val::Px(10.0)), + ..Default::default() + }, + BorderColor::all(BTN_BORDER_COLOR), + BorderRadius::ZERO, + BackgroundColor(BTN_BG_COLOR), + children![( + Text::new(text), + TextColor(GRAY.into()), + TextShadow::default(), + )], + ) +} + +pub fn button_hover_start( + trigger: On>, + mut button_colors: Query<(&mut BackgroundColor, &mut BorderColor), With