diff --git a/src/game/scene.rs b/src/game/scene.rs index fdcb076..ca24f52 100644 --- a/src/game/scene.rs +++ b/src/game/scene.rs @@ -1,6 +1,5 @@ use bevy::prelude::*; use bevy_third_person_camera::ThirdPersonCameraTarget; -use log::trace; use crate::cleanup; diff --git a/src/menus.rs b/src/menus.rs index 79c0185..56d44b3 100644 --- a/src/menus.rs +++ b/src/menus.rs @@ -1,5 +1,8 @@ -use bevy::{input::ButtonState, prelude::*}; -use lib::{ItemPosition, Menu, MenuItemType, Menus, OnPressAction}; +use bevy::{prelude::*, state::state::FreelyMutableState}; +use lib::{ + ItemPosition, MenuItemType, OnPressAction, + menus::{Menu, Menus}, +}; use crate::AppState; @@ -14,15 +17,26 @@ pub enum CurrentMenu { HighScores, StartGame, } + +fn switch_state(target: S) -> impl Fn(ResMut>) { + move |mut next_state: ResMut>| { + next_state.set(target.clone()); + } +} + pub fn plugin(app: &mut App) { - let plugin = Menus::::new(AppState::Menus) + let start_game_state = app.register_system(switch_state(AppState::Ingame)); + let plugin = Menus::::new(AppState::Menus, CurrentMenu::NotInMenus) .add_menu( CurrentMenu::MainMenu, Menu::new().add_items(&[ &MenuItemType::Text(String::from("Hello, World!")), &( MenuItemType::Button(String::from("Start Game")), - OnPressAction::SampleAction, + OnPressAction::Multiple(vec![ + OnPressAction::DispatchSystem(start_game_state), + OnPressAction::Close, + ]), ), &( MenuItemType::Button(String::from("Settings")), @@ -33,13 +47,7 @@ pub fn plugin(app: &mut App) { .add_menu( CurrentMenu::Settings, Menu::new() - .add_items(&[ - &MenuItemType::Text(String::from("Settings")), - &( - MenuItemType::Button(String::from("Sample setting")), - OnPressAction::SampleAction, - ), - ]) + .add_items(&[&MenuItemType::Text(String::from("Settings"))]) .add_items_positioned( ItemPosition::SouthEast, &[&( diff --git a/src/menus/lib.rs b/src/menus/lib.rs index 61da438..52e8959 100644 --- a/src/menus/lib.rs +++ b/src/menus/lib.rs @@ -1,71 +1,23 @@ #![allow(unused, reason = "Temporary.")] //! goal is a custom ui/menu library that allows simple and declarative menus. //! bevy ui is annoying. +//! +//! TODOs: +//! - [ ] cleanup +//! - [ ] trigger game events (trigger or normal eventwriter?) +//! - [ ] styling +//! - [ ] more components +//! - [ ] more shorthands use std::marker::PhantomData; use bevy::{platform::collections::HashMap, prelude::*}; mod item; +pub mod plugin; +pub mod menus; pub use item::types::*; pub use item::*; type ToDo = (); - -#[derive(Debug, Clone)] -pub struct Menus { - start: NavState, - /// The state needed to trigger this set of menus - trigger: TriggerState, - menus: HashMap>, -} - -#[derive(Debug, Clone)] -pub struct Menu { - menu_items: Vec>, -} - -pub mod plugin; - -impl Menus { - pub fn new(trigger: TriggerState) -> Self { - Self { - trigger, - start: NavState::default(), - menus: HashMap::new(), - } - } -} - -impl Menus { - pub fn add_menu(mut self, state: NavState, menu: Menu) -> Self { - self.menus.insert(state, menu); - self - } -} - -impl Menu { - 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]) -> Self { - self.add_items_positioned(ItemPosition::MainView, items) - } - - pub fn add_items_positioned( - mut self, - position: ItemPosition, - items: &[&dyn MenuItem], - ) -> Self { - items - .iter() - .map(|it| (*it).item()) - .collect_into(&mut self.menu_items); - self - } -} diff --git a/src/menus/lib/item/types.rs b/src/menus/lib/item/types.rs index 87e4995..ff9770d 100644 --- a/src/menus/lib/item/types.rs +++ b/src/menus/lib/item/types.rs @@ -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; @@ -28,6 +36,24 @@ pub enum MenuItemType { #[derive(Clone, Debug)] pub enum OnPressAction { NavigateTo(NavState), - DispatchEvent(ToDo), - SampleAction, + DispatchSystem(SystemId), + Multiple(Vec>), + Close, +} + +impl OnPressAction { + pub fn run(&self, c: &mut Commands, mut nav_state: &mut NextState, 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()), + } + } } diff --git a/src/menus/lib/menus.rs b/src/menus/lib/menus.rs new file mode 100644 index 0000000..db12b1b --- /dev/null +++ b/src/menus/lib/menus.rs @@ -0,0 +1,72 @@ +use bevy::{platform::collections::HashMap, prelude::*}; + +use super::{IntoMenuItemInternal, ItemPosition, MenuItem, MenuItemInternal}; + +#[derive(Debug, Clone)] +pub struct Menus +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>, +} + +#[derive(Debug, Clone)] +pub struct Menu { + pub(super) menu_items: Vec>, +} + +impl Menus +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 Menus +where + T: States, + NavState: States, +{ + pub fn add_menu(mut self, state: NavState, menu: Menu) -> Self { + self.menus.insert(state, menu); + self + } +} + +impl Menu { + 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]) -> Self { + self.add_items_positioned(ItemPosition::MainView, items) + } + + pub fn add_items_positioned( + mut self, + position: ItemPosition, + items: &[&dyn MenuItem], + ) -> Self { + items + .iter() + .map(|it| (*it).item()) + .collect_into(&mut self.menu_items); + self + } +} diff --git a/src/menus/lib/plugin.rs b/src/menus/lib/plugin.rs index 087efb8..022919e 100644 --- a/src/menus/lib/plugin.rs +++ b/src/menus/lib/plugin.rs @@ -5,15 +5,24 @@ use bevy::{ state::state::FreelyMutableState, }; -use super::{ItemPosition, Menu, MenuItemInternal, MenuItemType, Menus, OnPressAction}; +use super::{ + ItemPosition, MenuItemInternal, MenuItemType, OnPressAction, + menus::{Menu, Menus}, +}; -impl Plugin - for Menus +mod components; + +impl Plugin for Menus +where + TriggerState: States, + NavState: States + FreelyMutableState + Copy, { fn build(&self, app: &mut App) { app.insert_state(self.start) .insert_resource(self.get_store()) + .insert_resource(MenusClosedWhen(self.closed_when)) .add_observer(build_ui::) + .add_observer(destroy_ui::) // .add_observer(destroy_ui::) .add_systems( Update, @@ -26,7 +35,7 @@ impl Plugin } #[derive(Event)] -struct DestroyUi(S); +struct DestroyUi(PhantomData); #[derive(Event)] struct BuildUi(S); @@ -46,6 +55,19 @@ impl Menus { #[derive(Resource)] struct MenusStore(HashMap>); +#[derive(Resource)] +struct MenusClosedWhen(S); + +fn destroy_ui( + trigger: Trigger>, + ui_parent: Query>>, + mut c: Commands, +) { + trace!("destroy_ui called"); + if let Ok(parent) = ui_parent.single_inner() { + c.entity(parent).despawn(); + } +} fn build_ui( trigger: Trigger>, @@ -63,11 +85,7 @@ fn build_ui( .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(); - } + .filter(|it| ItemPosition::MainView == it.pos); c.spawn(( UiParent(PhantomData::), @@ -96,75 +114,33 @@ fn build_ui( } fn handle_press_actions( + mut c: Commands, mut interaction_query: Query<(&Interaction, &Action), Changed>, mut nav_state: ResMut>, + closed_when: Res>, ) { for (interaction, Action(action)) in interaction_query { if *interaction != Interaction::Pressed { continue; } - match action { - OnPressAction::NavigateTo(to) => nav_state.set(to.clone()), - _ => todo!(), - } + action.run(&mut c, &mut nav_state, &closed_when.0); } } -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( +fn update_ui_trigger( mut c: Commands, mut trans_reader: EventReader>, menus_data: Res>, + closed_when: Res>, ) { for trans in trans_reader.read() { info!("{trans:?}"); - // if let Some(to_delete) = trans.exited {} + c.trigger(DestroyUi(PhantomData::)); if let Some(to_build) = trans.entered.clone() { - c.trigger(BuildUi(to_build)); + if to_build != closed_when.0 { + c.trigger(BuildUi(to_build)); + } } } } - -// fn build_ui(mut c: Commands) { - -// } diff --git a/src/menus/lib/plugin/components.rs b/src/menus/lib/plugin/components.rs new file mode 100644 index 0000000..80736f2 --- /dev/null +++ b/src/menus/lib/plugin/components.rs @@ -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)], + ) +}