From 8d3ea6fb5549f0af96f594bd4302f8fc7e37c17f Mon Sep 17 00:00:00 2001 From: Jonni Liljamo Date: Fri, 16 Dec 2022 15:56:20 +0200 Subject: [PATCH] Basic menu layout, missing functionality still --- sdbclient/src/main.rs | 77 +++++++- sdbclient/src/menu/accountloginui.rs | 43 +++++ sdbclient/src/menu/accountscreenloggedin.rs | 99 ++++++++++ sdbclient/src/menu/accountscreenloggedout.rs | 100 ++++++++++ sdbclient/src/menu/mainmenuscreen.rs | 90 +++++++++ sdbclient/src/menu/mod.rs | 193 +++++++++++++++++++ sdbclient/src/menu/settingsaudioscreen.rs | 79 ++++++++ sdbclient/src/menu/settingsdisplayscreen.rs | 79 ++++++++ sdbclient/src/menu/settingsmenuscreen.rs | 101 ++++++++++ sdbclient/src/menu/settingsmiscscreen.rs | 79 ++++++++ sdbclient/src/splash/mod.rs | 80 ++++++++ sdbclient/src/util/eguipwd.rs | 38 ++++ sdbclient/src/util/mod.rs | 9 + 13 files changed, 1060 insertions(+), 7 deletions(-) create mode 100644 sdbclient/src/menu/accountloginui.rs create mode 100644 sdbclient/src/menu/accountscreenloggedin.rs create mode 100644 sdbclient/src/menu/accountscreenloggedout.rs create mode 100644 sdbclient/src/menu/mainmenuscreen.rs create mode 100644 sdbclient/src/menu/mod.rs create mode 100644 sdbclient/src/menu/settingsaudioscreen.rs create mode 100644 sdbclient/src/menu/settingsdisplayscreen.rs create mode 100644 sdbclient/src/menu/settingsmenuscreen.rs create mode 100644 sdbclient/src/menu/settingsmiscscreen.rs create mode 100644 sdbclient/src/splash/mod.rs create mode 100644 sdbclient/src/util/eguipwd.rs create mode 100644 sdbclient/src/util/mod.rs diff --git a/sdbclient/src/main.rs b/sdbclient/src/main.rs index a53c8bf..5fb170a 100644 --- a/sdbclient/src/main.rs +++ b/sdbclient/src/main.rs @@ -7,14 +7,50 @@ */ use bevy::{ - app::{App, PluginGroup}, - window::{ - CompositeAlphaMode, CursorGrabMode, MonitorSelection, PresentMode, WindowDescriptor, - WindowMode, WindowPlugin, WindowPosition, WindowResizeConstraints, - }, - DefaultPlugins, + prelude::*, + window::{CompositeAlphaMode, CursorGrabMode, PresentMode, WindowResizeConstraints}, }; +use bevy_egui::EguiPlugin; +use bevy_inspector_egui::WorldInspectorPlugin; + +mod menu; +mod splash; +mod util; + +/// Used to control the state of the game +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum GameState { + Splash, + MainMenu, + Game, +} + +// Various settings that can be changed from the... Settings. +/// Volume +#[derive(Resource, Debug, Component, PartialEq, Eq, Clone, Copy)] +pub struct CfgVolume(u32); +/// Fullscreen +#[derive(Resource, Debug, Component, PartialEq, Eq, Clone, Copy)] +pub struct CfgFullscreen(bool); +/// Resolution +#[derive(Resource, Debug, Component, PartialEq, Clone, Copy)] +pub struct CfgResolution(f32, f32); + +// Settings that the user has no access to, or can only access through developer settings +/// API Server +#[derive(Resource, Debug, Component, PartialEq, Eq, Clone)] +pub struct CfgAPIServer(String); +/// User logged in status +#[derive(Resource, Debug, Component, PartialEq, Eq, Clone, Copy)] +pub struct CfgLoggedIn(bool); +/// Login username inputs +#[derive(Resource, Debug, Component, PartialEq, Eq, Clone)] +pub struct CfgUserLoginInputs(String, String); +/// User Token +#[derive(Resource, Debug, Component, PartialEq, Eq, Clone)] +pub struct CfgUserToken(String); + fn main() { let mut app = App::new(); @@ -33,7 +69,7 @@ fn main() { scale_factor_override: Some(1.), title: "Deck Builder".to_string(), present_mode: PresentMode::Fifo, - resizable: true, + resizable: false, decorations: true, cursor_visible: true, cursor_grab_mode: CursorGrabMode::None, @@ -46,5 +82,32 @@ fn main() { ..Default::default() })); + app.add_plugin(EguiPlugin); + app.add_plugin(WorldInspectorPlugin::new()); + + app.insert_resource(CfgVolume(7)) + .insert_resource(CfgFullscreen(false)) + .insert_resource(CfgResolution(1280., 720.)) + .insert_resource(CfgAPIServer("http://localhost:8080".to_string())) + .insert_resource(CfgUserLoginInputs("".to_string(), "".to_string())) + .insert_resource(CfgLoggedIn(false)) + .insert_resource(CfgUserToken("".to_string())); + + app.add_startup_system(setup).add_state(GameState::Splash); + + app.add_plugin(splash::SplashPlugin) + .add_plugin(menu::MenuPlugin); + app.run(); } + +fn setup(mut commands: Commands) { + commands.spawn(Camera3dBundle::default()); +} + +/// Utility function do despawn an entity and all its children +pub fn despawn_screen(to_despawn: Query>, mut commands: Commands) { + for entity in &to_despawn { + commands.entity(entity).despawn_recursive(); + } +} diff --git a/sdbclient/src/menu/accountloginui.rs b/sdbclient/src/menu/accountloginui.rs new file mode 100644 index 0000000..953ab17 --- /dev/null +++ b/sdbclient/src/menu/accountloginui.rs @@ -0,0 +1,43 @@ +/* + * This file is part of sdbclient + * Copyright (C) 2022 Jonni Liljamo + * + * Licensed under GPL-3.0-only. + * See LICENSE for licensing information. + */ + +use bevy::prelude::*; +use bevy_egui::{egui, EguiContext}; + +use crate::{util::eguipwd, CfgUserLoginInputs}; + +use super::MenuState; + +pub fn account_login_ui( + mut egui_context: ResMut, + mut menu_state: ResMut>, + mut inputs: ResMut, +) { + egui::Window::new("Login") + .collapsible(false) + .show(egui_context.ctx_mut(), |ui| { + ui.horizontal(|ui| { + ui.label("Username: "); + ui.text_edit_singleline(&mut inputs.0); + }); + + ui.horizontal(|ui| { + ui.label("Password: "); + ui.add(eguipwd::password(&mut inputs.1)); + }); + + ui.with_layout(egui::Layout::right_to_left(egui::Align::Min), |ui| { + if ui.button("Cancel").clicked() { + menu_state.set(MenuState::Main).unwrap(); + } + if ui.button("Login").clicked() { + info!("todo"); + } + }) + }); +} diff --git a/sdbclient/src/menu/accountscreenloggedin.rs b/sdbclient/src/menu/accountscreenloggedin.rs new file mode 100644 index 0000000..03bd2a9 --- /dev/null +++ b/sdbclient/src/menu/accountscreenloggedin.rs @@ -0,0 +1,99 @@ +/* + * This file is part of sdbclient + * Copyright (C) 2022 Jonni Liljamo + * + * Licensed under GPL-3.0-only. + * See LICENSE for licensing information. + */ + +use bevy::{ + prelude::*, + ui::{JustifyContent, Size, Style, Val}, +}; + +use super::{MenuButtonAction, NORMAL_BUTTON, TEXT_COLOR}; + +/// Tag component for tagging entities on settings menu screen +#[derive(Component)] +pub struct OnAccountLoggedInScreen; + +pub fn account_loggedin_setup(mut commands: Commands, asset_server: Res) { + let font = asset_server.load("fonts/FiraMono-Regular.ttf"); + + let button_style = Style { + size: Size::new(Val::Px(200.0), Val::Px(65.0)), + margin: UiRect::all(Val::Px(20.0)), + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + ..default() + }; + + let button_text_style = TextStyle { + font: font.clone(), + font_size: 40.0, + color: TEXT_COLOR, + }; + + commands + .spawn(( + NodeBundle { + style: Style { + size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + flex_direction: FlexDirection::Column, + ..default() + }, + ..default() + }, + OnAccountLoggedInScreen, + )) + .with_children(|parent| { + parent.spawn( + TextBundle::from_section( + "Account", + TextStyle { + font: font.clone(), + font_size: 60.0, + color: TEXT_COLOR, + }, + ) + .with_style(Style { + margin: UiRect::all(Val::Px(50.)), + ..Default::default() + }), + ); + parent + .spawn(NodeBundle { + style: Style { + flex_direction: FlexDirection::Column, + align_items: AlignItems::Center, + ..default() + }, + background_color: Color::GRAY.into(), + ..default() + }) + .with_children(|parent| { + for (action, text) in [ + (MenuButtonAction::AccountLogin, "Logout"), + (MenuButtonAction::BackToMainMenu, "Back"), + ] { + parent + .spawn(( + ButtonBundle { + style: button_style.clone(), + background_color: NORMAL_BUTTON.into(), + ..default() + }, + action, + )) + .with_children(|parent| { + parent.spawn(TextBundle::from_section( + text, + button_text_style.clone(), + )); + }); + } + }); + }); +} diff --git a/sdbclient/src/menu/accountscreenloggedout.rs b/sdbclient/src/menu/accountscreenloggedout.rs new file mode 100644 index 0000000..311d576 --- /dev/null +++ b/sdbclient/src/menu/accountscreenloggedout.rs @@ -0,0 +1,100 @@ +/* + * This file is part of sdbclient + * Copyright (C) 2022 Jonni Liljamo + * + * Licensed under GPL-3.0-only. + * See LICENSE for licensing information. + */ + +use bevy::{ + prelude::*, + ui::{JustifyContent, Size, Style, Val}, +}; + +use super::{MenuButtonAction, NORMAL_BUTTON, TEXT_COLOR}; + +/// Tag component for tagging entities on settings menu screen +#[derive(Component)] +pub struct OnAccountLoggedOutScreen; + +pub fn account_loggedout_setup(mut commands: Commands, asset_server: Res) { + let font = asset_server.load("fonts/FiraMono-Regular.ttf"); + + let button_style = Style { + size: Size::new(Val::Px(200.0), Val::Px(65.0)), + margin: UiRect::all(Val::Px(20.0)), + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + ..default() + }; + + let button_text_style = TextStyle { + font: font.clone(), + font_size: 40.0, + color: TEXT_COLOR, + }; + + commands + .spawn(( + NodeBundle { + style: Style { + size: Size::new(Val::Percent(100.0), Val::Percent(100.0)), + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + flex_direction: FlexDirection::Column, + ..default() + }, + ..default() + }, + OnAccountLoggedOutScreen, + )) + .with_children(|parent| { + parent.spawn( + TextBundle::from_section( + "Account", + TextStyle { + font: font.clone(), + font_size: 60.0, + color: TEXT_COLOR, + }, + ) + .with_style(Style { + margin: UiRect::all(Val::Px(50.)), + ..Default::default() + }), + ); + parent + .spawn(NodeBundle { + style: Style { + flex_direction: FlexDirection::Column, + align_items: AlignItems::Center, + ..default() + }, + background_color: Color::GRAY.into(), + ..default() + }) + .with_children(|parent| { + for (action, text) in [ + (MenuButtonAction::AccountLogin, "Login"), + (MenuButtonAction::AccountRegister, "Register"), + (MenuButtonAction::BackToMainMenu, "Back"), + ] { + parent + .spawn(( + ButtonBundle { + style: button_style.clone(), + background_color: NORMAL_BUTTON.into(), + ..default() + }, + action, + )) + .with_children(|parent| { + parent.spawn(TextBundle::from_section( + text, + button_text_style.clone(), + )); + }); + } + }); + }); +} diff --git a/sdbclient/src/menu/mainmenuscreen.rs b/sdbclient/src/menu/mainmenuscreen.rs new file mode 100644 index 0000000..bbff306 --- /dev/null +++ b/sdbclient/src/menu/mainmenuscreen.rs @@ -0,0 +1,90 @@ +/* + * This file is part of sdbclient + * Copyright (C) 2022 Jonni Liljamo + * + * Licensed under GPL-3.0-only. + * See LICENSE for licensing information. + */ + +use bevy::{ + prelude::*, + ui::{JustifyContent, Size, Style, Val}, +}; + +use super::{MenuButtonAction, NORMAL_BUTTON, TEXT_COLOR}; + +/// Tag component for tagging entities on menu screen +#[derive(Component)] +pub struct OnMainMenuScreen; + +pub fn main_menu_setup(mut commands: Commands, asset_server: Res) { + let font = asset_server.load("fonts/FiraMono-Regular.ttf"); + + let button_style = Style { + size: Size::new(Val::Px(250.), Val::Px(65.)), + margin: UiRect::all(Val::Px(20.)), + justify_content: JustifyContent::Center, + align_items: AlignItems::Center, + ..Default::default() + }; + + let button_text_style = TextStyle { + font: font.clone(), + font_size: 40.0, + color: TEXT_COLOR, + }; + + commands + .spawn(( + NodeBundle { + style: Style { + size: Size::new(Val::Percent(100.), Val::Percent(100.)), + align_items: AlignItems::Center, + justify_content: JustifyContent::Center, + flex_direction: FlexDirection::Column, + ..Default::default() + }, + ..Default::default() + }, + OnMainMenuScreen, + )) + .with_children(|parent| { + // Game title, currently text + // TODO: Change to a fancy image logo + parent.spawn( + TextBundle::from_section( + "Deck Builder", + TextStyle { + font: font.clone(), + font_size: 80.0, + color: TEXT_COLOR, + }, + ) + .with_style(Style { + margin: UiRect::all(Val::Px(50.)), + ..Default::default() + }), + ); + + // Main menu buttons + for (action, text) in [ + (MenuButtonAction::Play, "Play"), + (MenuButtonAction::Account, "Account"), + (MenuButtonAction::Settings, "Settings"), + (MenuButtonAction::Exit, "Exit"), + ] { + parent + .spawn(( + ButtonBundle { + style: button_style.clone(), + background_color: NORMAL_BUTTON.into(), + ..Default::default() + }, + action, + )) + .with_children(|parent| { + parent.spawn(TextBundle::from_section(text, button_text_style.clone())); + }); + } + }); +} diff --git a/sdbclient/src/menu/mod.rs b/sdbclient/src/menu/mod.rs new file mode 100644 index 0000000..6257e95 --- /dev/null +++ b/sdbclient/src/menu/mod.rs @@ -0,0 +1,193 @@ +/* + * This file is part of sdbclient + * Copyright (C) 2022 Jonni Liljamo + * + * Licensed under GPL-3.0-only. + * See LICENSE for licensing information. + */ + +use bevy::{app::AppExit, prelude::*}; + +use crate::CfgLoggedIn; + +use super::{despawn_screen, GameState}; + +mod mainmenuscreen; +use mainmenuscreen::*; + +mod settingsmenuscreen; +use settingsmenuscreen::*; + +mod settingsdisplayscreen; +use settingsdisplayscreen::*; + +mod settingsaudioscreen; +use settingsaudioscreen::*; + +mod settingsmiscscreen; +use settingsmiscscreen::*; + +mod accountscreenloggedout; +use accountscreenloggedout::*; + +mod accountscreenloggedin; +use accountscreenloggedin::*; + +mod accountloginui; +use accountloginui::*; + +const TEXT_COLOR: Color = Color::rgb(0.9, 0.9, 0.9); + +pub struct MenuPlugin; + +impl Plugin for MenuPlugin { + fn build(&self, app: &mut App) { + app. + // Start with no menu. The menu is loaded when the GameState::MainMenu is entered. + add_state(MenuState::None) + .add_system_set(SystemSet::on_enter(GameState::MainMenu).with_system(menu_setup)) + // Systems for main menu screen + .add_system_set(SystemSet::on_enter(MenuState::Main).with_system(main_menu_setup)) + .add_system_set(SystemSet::on_exit(MenuState::Main).with_system(despawn_screen::)) + // Systems for settings menu screen + .add_system_set(SystemSet::on_enter(MenuState::Settings).with_system(settings_menu_setup)) + .add_system_set(SystemSet::on_exit(MenuState::Settings).with_system(despawn_screen::)) + // Systems for settings display screen + .add_system_set(SystemSet::on_enter(MenuState::SettingsDisplay).with_system(settings_display_setup)) + .add_system_set(SystemSet::on_exit(MenuState::SettingsDisplay).with_system(despawn_screen::)) + // Systems for settings audio screen + .add_system_set(SystemSet::on_enter(MenuState::SettingsAudio).with_system(settings_audio_setup)) + .add_system_set(SystemSet::on_exit(MenuState::SettingsAudio).with_system(despawn_screen::)) + // Systems for settings misc screen + .add_system_set(SystemSet::on_enter(MenuState::SettingsMisc).with_system(settings_misc_setup)) + .add_system_set(SystemSet::on_exit(MenuState::SettingsMisc).with_system(despawn_screen::)) + // Systems for account loggedout screen + .add_system_set(SystemSet::on_enter(MenuState::AccountLoggedOut).with_system(account_loggedout_setup)) + .add_system_set(SystemSet::on_exit(MenuState::AccountLoggedOut).with_system(despawn_screen::)) + // Systems for account loggedin screen + .add_system_set(SystemSet::on_enter(MenuState::AccountLoggedIn).with_system(account_loggedin_setup)) + .add_system_set(SystemSet::on_exit(MenuState::AccountLoggedIn).with_system(despawn_screen::)) + // Account login and register systems + .add_system_set(SystemSet::on_update(MenuState::AccountLogin).with_system(account_login_ui)) + // Common systems + .add_system_set(SystemSet::on_update(GameState::MainMenu).with_system(menu_action).with_system(button_system)); + } +} + +/// Menu State +#[derive(Clone, Eq, PartialEq, Debug, Hash)] +pub enum MenuState { + None, + Main, + Settings, + SettingsDisplay, + SettingsAudio, + SettingsMisc, + AccountLoggedIn, + AccountLoggedOut, + AccountLogin, + AccountRegister, +} + +/// Tag component for tagging entities on account screen +#[derive(Component)] +struct OnAccountScreen; + +/// Tag component for tagging entities on login screen +#[derive(Component)] +struct OnLoginScreen; + +/// Tag component for tagging entities on register screen +#[derive(Component)] +struct OnRegisterScreen; + +const NORMAL_BUTTON: Color = Color::rgb(0.15, 0.15, 0.15); +const HOVERED_BUTTON: Color = Color::rgb(0.25, 0.25, 0.25); +const HOVERED_PRESSED_BUTTON: Color = Color::rgb(0.25, 0.65, 0.25); +const PRESSED_BUTTON: Color = Color::rgb(0.35, 0.75, 0.35); + +/// Tag component for tagging currently selected settings tab +#[derive(Component)] +struct SelectedSettingsTab; + +/// All button actions +#[derive(Component)] +enum MenuButtonAction { + Play, + Settings, + SettingsDisplay, + SettingsAudio, + SettingsMisc, + Account, + AccountLogin, + AccountRegister, + BackToMainMenu, + BackToSettings, + Exit, +} + +fn menu_action( + mut interaction_query: Query< + (&Interaction, &MenuButtonAction), + (Changed, With