mirror of
https://codeberg.org/schrottkatze/mgd2-tram-championships.git
synced 2025-06-10 02:07:39 +00:00
menu system: system dispatching (and the menus can now be closed)
This commit is contained in:
parent
acbcfe5956
commit
fc83ab8452
7 changed files with 202 additions and 132 deletions
|
@ -1,6 +1,5 @@
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_third_person_camera::ThirdPersonCameraTarget;
|
use bevy_third_person_camera::ThirdPersonCameraTarget;
|
||||||
use log::trace;
|
|
||||||
|
|
||||||
use crate::cleanup;
|
use crate::cleanup;
|
||||||
|
|
||||||
|
|
30
src/menus.rs
30
src/menus.rs
|
@ -1,5 +1,8 @@
|
||||||
use bevy::{input::ButtonState, prelude::*};
|
use bevy::{prelude::*, state::state::FreelyMutableState};
|
||||||
use lib::{ItemPosition, Menu, MenuItemType, Menus, OnPressAction};
|
use lib::{
|
||||||
|
ItemPosition, MenuItemType, OnPressAction,
|
||||||
|
menus::{Menu, Menus},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
|
|
||||||
|
@ -14,15 +17,26 @@ pub enum CurrentMenu {
|
||||||
HighScores,
|
HighScores,
|
||||||
StartGame,
|
StartGame,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn switch_state<S: States + FreelyMutableState>(target: S) -> impl Fn(ResMut<NextState<S>>) {
|
||||||
|
move |mut next_state: ResMut<NextState<S>>| {
|
||||||
|
next_state.set(target.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn plugin(app: &mut App) {
|
pub fn plugin(app: &mut App) {
|
||||||
let plugin = Menus::<AppState, CurrentMenu>::new(AppState::Menus)
|
let start_game_state = app.register_system(switch_state(AppState::Ingame));
|
||||||
|
let plugin = Menus::<AppState, CurrentMenu>::new(AppState::Menus, CurrentMenu::NotInMenus)
|
||||||
.add_menu(
|
.add_menu(
|
||||||
CurrentMenu::MainMenu,
|
CurrentMenu::MainMenu,
|
||||||
Menu::new().add_items(&[
|
Menu::new().add_items(&[
|
||||||
&MenuItemType::Text(String::from("Hello, World!")),
|
&MenuItemType::Text(String::from("Hello, World!")),
|
||||||
&(
|
&(
|
||||||
MenuItemType::Button(String::from("Start Game")),
|
MenuItemType::Button(String::from("Start Game")),
|
||||||
OnPressAction::SampleAction,
|
OnPressAction::Multiple(vec![
|
||||||
|
OnPressAction::DispatchSystem(start_game_state),
|
||||||
|
OnPressAction::Close,
|
||||||
|
]),
|
||||||
),
|
),
|
||||||
&(
|
&(
|
||||||
MenuItemType::Button(String::from("Settings")),
|
MenuItemType::Button(String::from("Settings")),
|
||||||
|
@ -33,13 +47,7 @@ pub fn plugin(app: &mut App) {
|
||||||
.add_menu(
|
.add_menu(
|
||||||
CurrentMenu::Settings,
|
CurrentMenu::Settings,
|
||||||
Menu::new()
|
Menu::new()
|
||||||
.add_items(&[
|
.add_items(&[&MenuItemType::Text(String::from("Settings"))])
|
||||||
&MenuItemType::Text(String::from("Settings")),
|
|
||||||
&(
|
|
||||||
MenuItemType::Button(String::from("Sample setting")),
|
|
||||||
OnPressAction::SampleAction,
|
|
||||||
),
|
|
||||||
])
|
|
||||||
.add_items_positioned(
|
.add_items_positioned(
|
||||||
ItemPosition::SouthEast,
|
ItemPosition::SouthEast,
|
||||||
&[&(
|
&[&(
|
||||||
|
|
|
@ -1,71 +1,23 @@
|
||||||
#![allow(unused, reason = "Temporary.")]
|
#![allow(unused, reason = "Temporary.")]
|
||||||
//! goal is a custom ui/menu library that allows simple and declarative menus.
|
//! goal is a custom ui/menu library that allows simple and declarative menus.
|
||||||
//! bevy ui is annoying.
|
//! bevy ui is annoying.
|
||||||
|
//!
|
||||||
|
//! TODOs:
|
||||||
|
//! - [ ] cleanup
|
||||||
|
//! - [ ] trigger game events (trigger or normal eventwriter?)
|
||||||
|
//! - [ ] styling
|
||||||
|
//! - [ ] more components
|
||||||
|
//! - [ ] more shorthands
|
||||||
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use bevy::{platform::collections::HashMap, prelude::*};
|
use bevy::{platform::collections::HashMap, prelude::*};
|
||||||
|
|
||||||
mod item;
|
mod item;
|
||||||
|
pub mod plugin;
|
||||||
|
|
||||||
|
pub mod menus;
|
||||||
pub use item::types::*;
|
pub use item::types::*;
|
||||||
pub use item::*;
|
pub use item::*;
|
||||||
|
|
||||||
type ToDo = ();
|
type ToDo = ();
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
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 {
|
|
||||||
trigger,
|
|
||||||
start: NavState::default(),
|
|
||||||
menus: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: States, NavState: States> Menus<T, NavState> {
|
|
||||||
pub fn add_menu(mut self, state: NavState, menu: Menu<NavState>) -> Self {
|
|
||||||
self.menus.insert(state, menu);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<ParentNavState: States> Menu<ParentNavState> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
menu_items: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds `items` in the default `ItemPosition::MainView`.
|
|
||||||
pub fn add_items(mut self, items: &[&dyn MenuItem<ParentNavState>]) -> Self {
|
|
||||||
self.add_items_positioned(ItemPosition::MainView, items)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_items_positioned(
|
|
||||||
mut self,
|
|
||||||
position: ItemPosition,
|
|
||||||
items: &[&dyn MenuItem<ParentNavState>],
|
|
||||||
) -> Self {
|
|
||||||
items
|
|
||||||
.iter()
|
|
||||||
.map(|it| (*it).item())
|
|
||||||
.collect_into(&mut self.menu_items);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
use bevy::state::state::States;
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use bevy::{
|
||||||
|
ecs::{
|
||||||
|
event::Event,
|
||||||
|
system::{Commands, SystemId},
|
||||||
|
},
|
||||||
|
state::state::{FreelyMutableState, NextState, States},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::menus::lib::ToDo;
|
use crate::menus::lib::ToDo;
|
||||||
|
|
||||||
|
@ -28,6 +36,24 @@ pub enum MenuItemType {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum OnPressAction<NavState: States> {
|
pub enum OnPressAction<NavState: States> {
|
||||||
NavigateTo(NavState),
|
NavigateTo(NavState),
|
||||||
DispatchEvent(ToDo),
|
DispatchSystem(SystemId),
|
||||||
SampleAction,
|
Multiple(Vec<OnPressAction<NavState>>),
|
||||||
|
Close,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: States + FreelyMutableState> OnPressAction<S> {
|
||||||
|
pub fn run(&self, c: &mut Commands, mut nav_state: &mut NextState<S>, closed_state: &S) {
|
||||||
|
dbg!(self);
|
||||||
|
match self {
|
||||||
|
OnPressAction::NavigateTo(to) => nav_state.set(to.clone()),
|
||||||
|
OnPressAction::DispatchSystem(system_id) => c.run_system(*system_id),
|
||||||
|
OnPressAction::Multiple(actions) => {
|
||||||
|
let mut actions = actions.clone();
|
||||||
|
while let Some(action) = actions.pop() {
|
||||||
|
action.run(c, nav_state, closed_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OnPressAction::Close => nav_state.set(closed_state.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
72
src/menus/lib/menus.rs
Normal file
72
src/menus/lib/menus.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
use bevy::{platform::collections::HashMap, prelude::*};
|
||||||
|
|
||||||
|
use super::{IntoMenuItemInternal, ItemPosition, MenuItem, MenuItemInternal};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Menus<TriggerState, NavState>
|
||||||
|
where
|
||||||
|
TriggerState: States,
|
||||||
|
NavState: States,
|
||||||
|
{
|
||||||
|
pub(super) start: NavState,
|
||||||
|
pub(super) closed_when: NavState,
|
||||||
|
/// The state needed to trigger this set of menus
|
||||||
|
pub(super) trigger: TriggerState,
|
||||||
|
pub(super) menus: HashMap<NavState, Menu<NavState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Menu<ParentNavState: States> {
|
||||||
|
pub(super) menu_items: Vec<MenuItemInternal<ParentNavState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<TriggerState, NavState> Menus<TriggerState, NavState>
|
||||||
|
where
|
||||||
|
TriggerState: States,
|
||||||
|
NavState: States + Default,
|
||||||
|
{
|
||||||
|
pub fn new(trigger: TriggerState, closed_when: NavState) -> Self {
|
||||||
|
Self {
|
||||||
|
trigger,
|
||||||
|
closed_when,
|
||||||
|
start: NavState::default(),
|
||||||
|
menus: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, NavState> Menus<T, NavState>
|
||||||
|
where
|
||||||
|
T: States,
|
||||||
|
NavState: States,
|
||||||
|
{
|
||||||
|
pub fn add_menu(mut self, state: NavState, menu: Menu<NavState>) -> Self {
|
||||||
|
self.menus.insert(state, menu);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<ParentNavState: States> Menu<ParentNavState> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
menu_items: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds `items` in the default `ItemPosition::MainView`.
|
||||||
|
pub fn add_items(mut self, items: &[&dyn MenuItem<ParentNavState>]) -> Self {
|
||||||
|
self.add_items_positioned(ItemPosition::MainView, items)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_items_positioned(
|
||||||
|
mut self,
|
||||||
|
position: ItemPosition,
|
||||||
|
items: &[&dyn MenuItem<ParentNavState>],
|
||||||
|
) -> Self {
|
||||||
|
items
|
||||||
|
.iter()
|
||||||
|
.map(|it| (*it).item())
|
||||||
|
.collect_into(&mut self.menu_items);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,15 +5,24 @@ use bevy::{
|
||||||
state::state::FreelyMutableState,
|
state::state::FreelyMutableState,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{ItemPosition, Menu, MenuItemInternal, MenuItemType, Menus, OnPressAction};
|
use super::{
|
||||||
|
ItemPosition, MenuItemInternal, MenuItemType, OnPressAction,
|
||||||
|
menus::{Menu, Menus},
|
||||||
|
};
|
||||||
|
|
||||||
impl<TriggerState: States, NavState: States + FreelyMutableState + Copy> Plugin
|
mod components;
|
||||||
for Menus<TriggerState, NavState>
|
|
||||||
|
impl<TriggerState, NavState> Plugin for Menus<TriggerState, NavState>
|
||||||
|
where
|
||||||
|
TriggerState: States,
|
||||||
|
NavState: States + FreelyMutableState + Copy,
|
||||||
{
|
{
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.insert_state(self.start)
|
app.insert_state(self.start)
|
||||||
.insert_resource(self.get_store())
|
.insert_resource(self.get_store())
|
||||||
|
.insert_resource(MenusClosedWhen(self.closed_when))
|
||||||
.add_observer(build_ui::<NavState>)
|
.add_observer(build_ui::<NavState>)
|
||||||
|
.add_observer(destroy_ui::<NavState>)
|
||||||
// .add_observer(destroy_ui::<NavStates>)
|
// .add_observer(destroy_ui::<NavStates>)
|
||||||
.add_systems(
|
.add_systems(
|
||||||
Update,
|
Update,
|
||||||
|
@ -26,7 +35,7 @@ impl<TriggerState: States, NavState: States + FreelyMutableState + Copy> Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Event)]
|
#[derive(Event)]
|
||||||
struct DestroyUi<S: States>(S);
|
struct DestroyUi<S: States>(PhantomData<S>);
|
||||||
#[derive(Event)]
|
#[derive(Event)]
|
||||||
struct BuildUi<S: States>(S);
|
struct BuildUi<S: States>(S);
|
||||||
|
|
||||||
|
@ -46,6 +55,19 @@ impl<T: States, S: States> Menus<T, S> {
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
struct MenusStore<S: States>(HashMap<S, Menu<S>>);
|
struct MenusStore<S: States>(HashMap<S, Menu<S>>);
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct MenusClosedWhen<S: States>(S);
|
||||||
|
|
||||||
|
fn destroy_ui<S: States>(
|
||||||
|
trigger: Trigger<DestroyUi<S>>,
|
||||||
|
ui_parent: Query<Entity, With<UiParent<S>>>,
|
||||||
|
mut c: Commands,
|
||||||
|
) {
|
||||||
|
trace!("destroy_ui called");
|
||||||
|
if let Ok(parent) = ui_parent.single_inner() {
|
||||||
|
c.entity(parent).despawn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn build_ui<S: States>(
|
fn build_ui<S: States>(
|
||||||
trigger: Trigger<BuildUi<S>>,
|
trigger: Trigger<BuildUi<S>>,
|
||||||
|
@ -63,11 +85,7 @@ fn build_ui<S: States>(
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
// TODO: implement other item positions
|
// TODO: implement other item positions
|
||||||
.filter(|it| it.pos == ItemPosition::MainView);
|
.filter(|it| ItemPosition::MainView == it.pos);
|
||||||
|
|
||||||
if let Ok(parent) = ui_parent.single_inner() {
|
|
||||||
c.entity(parent).despawn();
|
|
||||||
}
|
|
||||||
|
|
||||||
c.spawn((
|
c.spawn((
|
||||||
UiParent(PhantomData::<S>),
|
UiParent(PhantomData::<S>),
|
||||||
|
@ -96,75 +114,33 @@ fn build_ui<S: States>(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_press_actions<S: States + FreelyMutableState>(
|
fn handle_press_actions<S: States + FreelyMutableState>(
|
||||||
|
mut c: Commands,
|
||||||
mut interaction_query: Query<(&Interaction, &Action<S>), Changed<Interaction>>,
|
mut interaction_query: Query<(&Interaction, &Action<S>), Changed<Interaction>>,
|
||||||
mut nav_state: ResMut<NextState<S>>,
|
mut nav_state: ResMut<NextState<S>>,
|
||||||
|
closed_when: Res<MenusClosedWhen<S>>,
|
||||||
) {
|
) {
|
||||||
for (interaction, Action(action)) in interaction_query {
|
for (interaction, Action(action)) in interaction_query {
|
||||||
if *interaction != Interaction::Pressed {
|
if *interaction != Interaction::Pressed {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match action {
|
action.run(&mut c, &mut nav_state, &closed_when.0);
|
||||||
OnPressAction::NavigateTo(to) => nav_state.set(to.clone()),
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod components {
|
fn update_ui_trigger<S: States + PartialEq>(
|
||||||
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 c: Commands,
|
||||||
mut trans_reader: EventReader<StateTransitionEvent<S>>,
|
mut trans_reader: EventReader<StateTransitionEvent<S>>,
|
||||||
menus_data: Res<MenusStore<S>>,
|
menus_data: Res<MenusStore<S>>,
|
||||||
|
closed_when: Res<MenusClosedWhen<S>>,
|
||||||
) {
|
) {
|
||||||
for trans in trans_reader.read() {
|
for trans in trans_reader.read() {
|
||||||
info!("{trans:?}");
|
info!("{trans:?}");
|
||||||
|
|
||||||
// if let Some(to_delete) = trans.exited {}
|
c.trigger(DestroyUi(PhantomData::<S>));
|
||||||
if let Some(to_build) = trans.entered.clone() {
|
if let Some(to_build) = trans.entered.clone() {
|
||||||
|
if to_build != closed_when.0 {
|
||||||
c.trigger(BuildUi(to_build));
|
c.trigger(BuildUi(to_build));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn build_ui<S: States>(mut c: Commands) {
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
37
src/menus/lib/plugin/components.rs
Normal file
37
src/menus/lib/plugin/components.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
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)],
|
||||||
|
)
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue