mirror of
https://codeberg.org/schrottkatze/mgd2-tram-championships.git
synced 2025-06-10 02:07:39 +00:00
menu system: ITS ALIVE
This commit is contained in:
parent
2b18cdea38
commit
acbcfe5956
7 changed files with 216 additions and 30 deletions
|
@ -5,7 +5,7 @@ use bevy_inspector_egui::{
|
||||||
quick::{StateInspectorPlugin, WorldInspectorPlugin},
|
quick::{StateInspectorPlugin, WorldInspectorPlugin},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::AppState;
|
use crate::{AppState, menus};
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
app.add_plugins(EguiPlugin {
|
app.add_plugins(EguiPlugin {
|
||||||
|
@ -14,5 +14,6 @@ pub fn plugin(app: &mut App) {
|
||||||
.add_plugins((
|
.add_plugins((
|
||||||
WorldInspectorPlugin::new(),
|
WorldInspectorPlugin::new(),
|
||||||
StateInspectorPlugin::<AppState>::new(),
|
StateInspectorPlugin::<AppState>::new(),
|
||||||
|
StateInspectorPlugin::<menus::CurrentMenu>::new(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@ mod menus;
|
||||||
#[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash, Reflect)]
|
#[derive(States, Default, Debug, Clone, PartialEq, Eq, Hash, Reflect)]
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
enum AppState {
|
enum AppState {
|
||||||
Menus,
|
|
||||||
#[default]
|
#[default]
|
||||||
|
Menus,
|
||||||
Ingame,
|
Ingame,
|
||||||
PostGame,
|
PostGame,
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,10 @@ fn main() {
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
.add_systems(Startup, camera::setup)
|
.add_systems(Startup, camera::setup)
|
||||||
.add_plugins(game::plugin)
|
.add_plugins(game::plugin)
|
||||||
|
.add_plugins(menus::plugin)
|
||||||
.init_state::<AppState>()
|
.init_state::<AppState>()
|
||||||
.register_type::<AppState>()
|
|
||||||
.add_plugins(debugging::plugin)
|
.add_plugins(debugging::plugin)
|
||||||
|
.register_type::<AppState>()
|
||||||
|
// .insert_state(AppState::Ingame)
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
16
src/menus.rs
16
src/menus.rs
|
@ -1,18 +1,21 @@
|
||||||
use bevy::{input::ButtonState, prelude::*};
|
use bevy::{input::ButtonState, prelude::*};
|
||||||
use lib::{ItemPosition, Menu, MenuItemType, Menus, OnPressAction};
|
use lib::{ItemPosition, Menu, MenuItemType, Menus, OnPressAction};
|
||||||
|
|
||||||
|
use crate::AppState;
|
||||||
|
|
||||||
mod lib;
|
mod lib;
|
||||||
|
|
||||||
#[derive(States, Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(States, Debug, Copy, Clone, PartialEq, Eq, Hash, Default, Reflect)]
|
||||||
enum CurrentMenu {
|
pub enum CurrentMenu {
|
||||||
NotInMenus,
|
NotInMenus,
|
||||||
|
#[default]
|
||||||
MainMenu,
|
MainMenu,
|
||||||
Settings,
|
Settings,
|
||||||
HighScores,
|
HighScores,
|
||||||
StartGame,
|
StartGame,
|
||||||
}
|
}
|
||||||
pub fn todo_meow() {
|
pub fn plugin(app: &mut App) {
|
||||||
let plugin = Menus::<CurrentMenu>::new()
|
let plugin = Menus::<AppState, CurrentMenu>::new(AppState::Menus)
|
||||||
.add_menu(
|
.add_menu(
|
||||||
CurrentMenu::MainMenu,
|
CurrentMenu::MainMenu,
|
||||||
Menu::new().add_items(&[
|
Menu::new().add_items(&[
|
||||||
|
@ -31,7 +34,7 @@ pub fn todo_meow() {
|
||||||
CurrentMenu::Settings,
|
CurrentMenu::Settings,
|
||||||
Menu::new()
|
Menu::new()
|
||||||
.add_items(&[
|
.add_items(&[
|
||||||
&MenuItemType::Text(String::from("Hello, World!")),
|
&MenuItemType::Text(String::from("Settings")),
|
||||||
&(
|
&(
|
||||||
MenuItemType::Button(String::from("Sample setting")),
|
MenuItemType::Button(String::from("Sample setting")),
|
||||||
OnPressAction::SampleAction,
|
OnPressAction::SampleAction,
|
||||||
|
@ -46,5 +49,6 @@ pub fn todo_meow() {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
dbg!(plugin);
|
dbg!(&plugin);
|
||||||
|
app.add_plugins(plugin);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,30 +6,39 @@ use std::marker::PhantomData;
|
||||||
|
|
||||||
use bevy::{platform::collections::HashMap, prelude::*};
|
use bevy::{platform::collections::HashMap, prelude::*};
|
||||||
|
|
||||||
type ToDo = ();
|
mod item;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Menus<NavState: States> {
|
|
||||||
menus: HashMap<NavState, Menu<NavState>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Menu<ParentNavState: States> {
|
|
||||||
menu_items: Vec<MenuItemInternal<ParentNavState>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use item::types::*;
|
pub use item::types::*;
|
||||||
pub use item::*;
|
pub use item::*;
|
||||||
|
|
||||||
mod item;
|
type ToDo = ();
|
||||||
|
|
||||||
impl<NavState: States> Menus<NavState> {
|
#[derive(Debug, Clone)]
|
||||||
pub fn new() -> Self {
|
pub struct Menus<TriggerState: States, NavState: States> {
|
||||||
|
start: NavState,
|
||||||
|
/// The state needed to trigger this set of menus
|
||||||
|
trigger: TriggerState,
|
||||||
|
menus: HashMap<NavState, Menu<NavState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Menu<ParentNavState: States> {
|
||||||
|
menu_items: Vec<MenuItemInternal<ParentNavState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod plugin;
|
||||||
|
|
||||||
|
impl<TriggerState: States, NavState: States + Default> Menus<TriggerState, NavState> {
|
||||||
|
pub fn new(trigger: TriggerState) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
trigger,
|
||||||
|
start: NavState::default(),
|
||||||
menus: HashMap::new(),
|
menus: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: States, NavState: States> Menus<T, NavState> {
|
||||||
pub fn add_menu(mut self, state: NavState, menu: Menu<NavState>) -> Self {
|
pub fn add_menu(mut self, state: NavState, menu: Menu<NavState>) -> Self {
|
||||||
self.menus.insert(state, menu);
|
self.menus.insert(state, menu);
|
||||||
self
|
self
|
||||||
|
|
|
@ -3,11 +3,11 @@ use types::{ItemPosition, MenuItemType, OnPressAction};
|
||||||
|
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub(super) struct MenuItemInternal<NavState: States> {
|
pub(super) struct MenuItemInternal<NavState: States> {
|
||||||
r#type: MenuItemType,
|
pub(super) r#type: MenuItemType,
|
||||||
pos: ItemPosition,
|
pub(super) pos: ItemPosition,
|
||||||
action: Option<OnPressAction<NavState>>,
|
pub(super) action: Option<OnPressAction<NavState>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) trait IntoMenuItemInternal<NavState: States> {
|
pub(super) trait IntoMenuItemInternal<NavState: States> {
|
||||||
|
|
|
@ -3,7 +3,7 @@ use bevy::state::state::States;
|
||||||
use crate::menus::lib::ToDo;
|
use crate::menus::lib::ToDo;
|
||||||
|
|
||||||
/// positions the item in the menu
|
/// positions the item in the menu
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum ItemPosition {
|
pub enum ItemPosition {
|
||||||
#[default]
|
#[default]
|
||||||
MainView,
|
MainView,
|
||||||
|
@ -22,12 +22,12 @@ pub enum ItemPosition {
|
||||||
pub enum MenuItemType {
|
pub enum MenuItemType {
|
||||||
Text(String),
|
Text(String),
|
||||||
Button(String),
|
Button(String),
|
||||||
Subsection(Vec<MenuItemType>),
|
// Subsection(Vec<MenuItemType>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum OnPressAction<NavState: States> {
|
pub enum OnPressAction<NavState: States> {
|
||||||
NavigateTo(NavState),
|
NavigateTo(NavState),
|
||||||
RunSystem(ToDo),
|
DispatchEvent(ToDo),
|
||||||
SampleAction,
|
SampleAction,
|
||||||
}
|
}
|
||||||
|
|
170
src/menus/lib/plugin.rs
Normal file
170
src/menus/lib/plugin.rs
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use bevy::{
|
||||||
|
ecs::spawn::SpawnIter, platform::collections::HashMap, prelude::*,
|
||||||
|
state::state::FreelyMutableState,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{ItemPosition, Menu, MenuItemInternal, MenuItemType, Menus, OnPressAction};
|
||||||
|
|
||||||
|
impl<TriggerState: States, NavState: States + FreelyMutableState + Copy> Plugin
|
||||||
|
for Menus<TriggerState, NavState>
|
||||||
|
{
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.insert_state(self.start)
|
||||||
|
.insert_resource(self.get_store())
|
||||||
|
.add_observer(build_ui::<NavState>)
|
||||||
|
// .add_observer(destroy_ui::<NavStates>)
|
||||||
|
.add_systems(
|
||||||
|
Update,
|
||||||
|
(
|
||||||
|
update_ui_trigger::<NavState>,
|
||||||
|
handle_press_actions::<NavState>.run_if(in_state(self.trigger.clone())),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Event)]
|
||||||
|
struct DestroyUi<S: States>(S);
|
||||||
|
#[derive(Event)]
|
||||||
|
struct BuildUi<S: States>(S);
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct UiParent<S: States>(PhantomData<S>);
|
||||||
|
#[derive(Component)]
|
||||||
|
struct UiComponent;
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Action<S: States>(OnPressAction<S>);
|
||||||
|
|
||||||
|
impl<T: States, S: States> Menus<T, S> {
|
||||||
|
fn get_store(&self) -> MenusStore<S> {
|
||||||
|
MenusStore(self.menus.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct MenusStore<S: States>(HashMap<S, Menu<S>>);
|
||||||
|
|
||||||
|
fn build_ui<S: States>(
|
||||||
|
trigger: Trigger<BuildUi<S>>,
|
||||||
|
ui_parent: Query<Entity, With<UiParent<S>>>,
|
||||||
|
mut c: Commands,
|
||||||
|
menus_data: Res<MenusStore<S>>,
|
||||||
|
) {
|
||||||
|
let e = trigger.event();
|
||||||
|
let Some(menu_structure) = menus_data.0.get(&e.0) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let items = menu_structure
|
||||||
|
.menu_items
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
// TODO: implement other item positions
|
||||||
|
.filter(|it| it.pos == ItemPosition::MainView);
|
||||||
|
|
||||||
|
if let Ok(parent) = ui_parent.single_inner() {
|
||||||
|
c.entity(parent).despawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
c.spawn((
|
||||||
|
UiParent(PhantomData::<S>),
|
||||||
|
Node {
|
||||||
|
width: Val::Percent(100.0),
|
||||||
|
height: Val::Percent(100.0),
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
flex_direction: FlexDirection::Column,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.with_children(|parent| {
|
||||||
|
for MenuItemInternal { r#type, action, .. } in items {
|
||||||
|
let mut e = match r#type {
|
||||||
|
MenuItemType::Text(s) => parent.spawn(components::text(s)),
|
||||||
|
MenuItemType::Button(s) => parent.spawn(components::button(s)),
|
||||||
|
};
|
||||||
|
e.insert(UiComponent);
|
||||||
|
|
||||||
|
if let Some(action) = action {
|
||||||
|
e.insert(Action(action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_press_actions<S: States + FreelyMutableState>(
|
||||||
|
mut interaction_query: Query<(&Interaction, &Action<S>), Changed<Interaction>>,
|
||||||
|
mut nav_state: ResMut<NextState<S>>,
|
||||||
|
) {
|
||||||
|
for (interaction, Action(action)) in interaction_query {
|
||||||
|
if *interaction != Interaction::Pressed {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match action {
|
||||||
|
OnPressAction::NavigateTo(to) => nav_state.set(to.clone()),
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod components {
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::menus::lib::OnPressAction;
|
||||||
|
|
||||||
|
use super::Action;
|
||||||
|
|
||||||
|
pub fn text(t: String) -> impl Bundle {
|
||||||
|
(
|
||||||
|
Text::new(t),
|
||||||
|
TextFont {
|
||||||
|
// font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||||
|
font_size: 33.0,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
TextColor(Color::srgb(0.9, 0.9, 0.9)),
|
||||||
|
TextShadow::default(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn button(t: String) -> impl Bundle {
|
||||||
|
(
|
||||||
|
Button,
|
||||||
|
Node {
|
||||||
|
border: UiRect::all(Val::Px(5.0)),
|
||||||
|
// horizontally center child text
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
// vertically center child text
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
padding: UiRect::all(Val::Px(10.0)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BorderColor(Color::BLACK),
|
||||||
|
BorderRadius::MAX,
|
||||||
|
BackgroundColor(Color::srgb(0.2, 0.2, 0.2)),
|
||||||
|
children![text(t)],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_ui_trigger<S: States>(
|
||||||
|
mut c: Commands,
|
||||||
|
mut trans_reader: EventReader<StateTransitionEvent<S>>,
|
||||||
|
menus_data: Res<MenusStore<S>>,
|
||||||
|
) {
|
||||||
|
for trans in trans_reader.read() {
|
||||||
|
info!("{trans:?}");
|
||||||
|
|
||||||
|
// if let Some(to_delete) = trans.exited {}
|
||||||
|
if let Some(to_build) = trans.entered.clone() {
|
||||||
|
c.trigger(BuildUi(to_build));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn build_ui<S: States>(mut c: Commands) {
|
||||||
|
|
||||||
|
// }
|
Loading…
Add table
Add a link
Reference in a new issue