2025-05-23 19:20:57 +02:00
|
|
|
use std::marker::PhantomData;
|
|
|
|
|
|
|
|
use bevy::{
|
|
|
|
ecs::spawn::SpawnIter, platform::collections::HashMap, prelude::*,
|
|
|
|
state::state::FreelyMutableState,
|
|
|
|
};
|
|
|
|
|
2025-06-02 22:20:25 +02:00
|
|
|
use super::{
|
|
|
|
ItemPosition, MenuItemInternal, MenuItemType, OnPressAction,
|
|
|
|
menus::{Menu, Menus},
|
|
|
|
};
|
|
|
|
|
|
|
|
mod components;
|
2025-05-23 19:20:57 +02:00
|
|
|
|
2025-06-02 22:20:25 +02:00
|
|
|
impl<TriggerState, NavState> Plugin for Menus<TriggerState, NavState>
|
|
|
|
where
|
|
|
|
TriggerState: States,
|
|
|
|
NavState: States + FreelyMutableState + Copy,
|
2025-05-23 19:20:57 +02:00
|
|
|
{
|
|
|
|
fn build(&self, app: &mut App) {
|
|
|
|
app.insert_state(self.start)
|
|
|
|
.insert_resource(self.get_store())
|
2025-06-02 22:20:25 +02:00
|
|
|
.insert_resource(MenusClosedWhen(self.closed_when))
|
2025-05-23 19:20:57 +02:00
|
|
|
.add_observer(build_ui::<NavState>)
|
2025-06-02 22:20:25 +02:00
|
|
|
.add_observer(destroy_ui::<NavState>)
|
2025-05-23 19:20:57 +02:00
|
|
|
// .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)]
|
2025-06-02 22:20:25 +02:00
|
|
|
struct DestroyUi<S: States>(PhantomData<S>);
|
2025-05-23 19:20:57 +02:00
|
|
|
#[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>>);
|
2025-06-02 22:20:25 +02:00
|
|
|
#[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();
|
|
|
|
}
|
|
|
|
}
|
2025-05-23 19:20:57 +02:00
|
|
|
|
|
|
|
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
|
2025-06-02 22:20:25 +02:00
|
|
|
.filter(|it| ItemPosition::MainView == it.pos);
|
2025-05-23 19:20:57 +02:00
|
|
|
|
|
|
|
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>(
|
2025-06-02 22:20:25 +02:00
|
|
|
mut c: Commands,
|
2025-05-23 19:20:57 +02:00
|
|
|
mut interaction_query: Query<(&Interaction, &Action<S>), Changed<Interaction>>,
|
|
|
|
mut nav_state: ResMut<NextState<S>>,
|
2025-06-02 22:20:25 +02:00
|
|
|
closed_when: Res<MenusClosedWhen<S>>,
|
2025-05-23 19:20:57 +02:00
|
|
|
) {
|
|
|
|
for (interaction, Action(action)) in interaction_query {
|
|
|
|
if *interaction != Interaction::Pressed {
|
|
|
|
continue;
|
|
|
|
}
|
2025-06-02 22:20:25 +02:00
|
|
|
action.run(&mut c, &mut nav_state, &closed_when.0);
|
2025-05-23 19:20:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-06-02 22:20:25 +02:00
|
|
|
fn update_ui_trigger<S: States + PartialEq>(
|
2025-05-23 19:20:57 +02:00
|
|
|
mut c: Commands,
|
|
|
|
mut trans_reader: EventReader<StateTransitionEvent<S>>,
|
|
|
|
menus_data: Res<MenusStore<S>>,
|
2025-06-02 22:20:25 +02:00
|
|
|
closed_when: Res<MenusClosedWhen<S>>,
|
2025-05-23 19:20:57 +02:00
|
|
|
) {
|
|
|
|
for trans in trans_reader.read() {
|
|
|
|
info!("{trans:?}");
|
|
|
|
|
2025-06-02 22:20:25 +02:00
|
|
|
c.trigger(DestroyUi(PhantomData::<S>));
|
2025-05-23 19:20:57 +02:00
|
|
|
if let Some(to_build) = trans.entered.clone() {
|
2025-06-02 22:20:25 +02:00
|
|
|
if to_build != closed_when.0 {
|
|
|
|
c.trigger(BuildUi(to_build));
|
|
|
|
}
|
2025-05-23 19:20:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|