|
|
|
|
@ -1,250 +1,210 @@
|
|
|
|
|
/// TODO: Sorted list/set
|
|
|
|
|
///
|
|
|
|
|
/// TODO:
|
|
|
|
|
/// * Titles on top left of window/tab
|
|
|
|
|
/// *
|
|
|
|
|
use bevy::{prelude::*, window::PrimaryWindow};
|
|
|
|
|
|
|
|
|
|
pub struct GameUiPlugin;
|
|
|
|
|
|
|
|
|
|
impl Plugin for GameUiPlugin {
|
|
|
|
|
fn build(&self, app: &mut App) {
|
|
|
|
|
app.add_systems(
|
|
|
|
|
Update,
|
|
|
|
|
(
|
|
|
|
|
init_ui_nav,
|
|
|
|
|
init_ui_tab,
|
|
|
|
|
// UI lists
|
|
|
|
|
init_ui_list,
|
|
|
|
|
// UI Set
|
|
|
|
|
init_ui_set,
|
|
|
|
|
// Buttons
|
|
|
|
|
init_ui_button,
|
|
|
|
|
manage_button_interaction,
|
|
|
|
|
// Cursor
|
|
|
|
|
manage_cursor,
|
|
|
|
|
// Initialize name labels
|
|
|
|
|
init_name,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
app.add_systems(PreUpdate, (spawn_nav, spawn_tab, spawn_set, spawn_button))
|
|
|
|
|
.add_systems(Update, (manage_names, manage_tab));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn init_name(
|
|
|
|
|
events: Query<
|
|
|
|
|
(Entity, &Name),
|
|
|
|
|
(
|
|
|
|
|
Added<Name>,
|
|
|
|
|
Or<(
|
|
|
|
|
With<GameUiNav>,
|
|
|
|
|
With<GameUiTab>,
|
|
|
|
|
With<GameUiSet>,
|
|
|
|
|
With<GameUiList>,
|
|
|
|
|
With<GameUiButton>,
|
|
|
|
|
)>,
|
|
|
|
|
),
|
|
|
|
|
>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
) {
|
|
|
|
|
events.iter().for_each(|(entity, name)| {
|
|
|
|
|
commands.entity(entity).with_children(|parent| {
|
|
|
|
|
parent.spawn(
|
|
|
|
|
TextBundle::from_section(name, TextStyle { ..default() }).with_style(Style {
|
|
|
|
|
top: Val::Px(0.0),
|
|
|
|
|
left: Val::Px(0.0),
|
|
|
|
|
..default()
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
/// Component for marking UI Nodes
|
|
|
|
|
#[derive(Component, PartialEq)]
|
|
|
|
|
pub enum GameUi {
|
|
|
|
|
/// Navigation container
|
|
|
|
|
Nav,
|
|
|
|
|
/// Tab container
|
|
|
|
|
Tab,
|
|
|
|
|
/// Game UI Set
|
|
|
|
|
Set,
|
|
|
|
|
/// Game UI Button
|
|
|
|
|
Button,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Ui Navigation
|
|
|
|
|
#[derive(Debug, Component)]
|
|
|
|
|
pub struct GameUiNav;
|
|
|
|
|
|
|
|
|
|
fn init_ui_nav(events: Query<Entity, Added<GameUiNav>>, mut commands: Commands) {
|
|
|
|
|
events.iter().for_each(|entity| {
|
|
|
|
|
let parent = commands
|
|
|
|
|
.spawn(NodeBundle {
|
|
|
|
|
fn spawn_nav(events: Query<(Entity, &GameUi), Added<GameUi>>, mut commands: Commands) {
|
|
|
|
|
events
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|(_, ui)| **ui == GameUi::Nav)
|
|
|
|
|
.for_each(|(entity, _)| {
|
|
|
|
|
info!("Spawning Nav UI");
|
|
|
|
|
commands.entity(entity).insert(NodeBundle {
|
|
|
|
|
style: Style {
|
|
|
|
|
flex_direction: FlexDirection::Row,
|
|
|
|
|
flex_direction: FlexDirection::Row, // ?
|
|
|
|
|
padding: UiRect::all(Val::Px(3.0)),
|
|
|
|
|
margin: UiRect::all(Val::Px(3.0)),
|
|
|
|
|
width: Val::Percent(80.0),
|
|
|
|
|
max_height: Val::Percent(80.0),
|
|
|
|
|
overflow: Overflow::clip(),
|
|
|
|
|
justify_content: JustifyContent::Center,
|
|
|
|
|
align_items: AlignItems::Center,
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
background_color: BackgroundColor(Color::PINK),
|
|
|
|
|
..default()
|
|
|
|
|
})
|
|
|
|
|
.id();
|
|
|
|
|
|
|
|
|
|
commands
|
|
|
|
|
.entity(entity)
|
|
|
|
|
.insert(NodeBundle {
|
|
|
|
|
style: Style { ..default() },
|
|
|
|
|
background_color: BackgroundColor(Color::PURPLE),
|
|
|
|
|
..default()
|
|
|
|
|
})
|
|
|
|
|
.set_parent(parent);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Ui Tab Element
|
|
|
|
|
#[derive(Debug, Component)]
|
|
|
|
|
pub struct GameUiTab;
|
|
|
|
|
|
|
|
|
|
fn init_ui_tab(events: Query<Entity, Added<GameUiTab>>, mut commands: Commands) {
|
|
|
|
|
events.iter().for_each(|entity| {
|
|
|
|
|
let parent = commands
|
|
|
|
|
.spawn(NodeBundle {
|
|
|
|
|
style: Style {
|
|
|
|
|
flex_direction: FlexDirection::Column,
|
|
|
|
|
fn spawn_tab(
|
|
|
|
|
events: Query<(Entity, &GameUi, &Name, &Parent), Added<GameUi>>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
) {
|
|
|
|
|
events
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|(_, ui, _, _)| **ui == GameUi::Tab)
|
|
|
|
|
.for_each(|(entity, _, name, parent)| {
|
|
|
|
|
info!("Spawning Tab");
|
|
|
|
|
// Create container for this tab
|
|
|
|
|
let parent_id = commands
|
|
|
|
|
.spawn(NodeBundle {
|
|
|
|
|
style: Style {
|
|
|
|
|
flex_direction: FlexDirection::Column,
|
|
|
|
|
padding: UiRect::all(Val::Px(3.0)),
|
|
|
|
|
margin: UiRect::all(Val::Px(3.0)),
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
background_color: BackgroundColor(Color::GREEN),
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
..default()
|
|
|
|
|
})
|
|
|
|
|
.id();
|
|
|
|
|
|
|
|
|
|
commands
|
|
|
|
|
.entity(entity)
|
|
|
|
|
.insert(NodeBundle {
|
|
|
|
|
style: Style {
|
|
|
|
|
flex_wrap: FlexWrap::Wrap,
|
|
|
|
|
})
|
|
|
|
|
.set_parent(parent.get())
|
|
|
|
|
.with_children(|parent| {
|
|
|
|
|
parent.spawn((GameUi::Button, name.clone(), NodeBundle { ..default() }));
|
|
|
|
|
})
|
|
|
|
|
.id();
|
|
|
|
|
|
|
|
|
|
// Insert spawn container with tab
|
|
|
|
|
commands
|
|
|
|
|
.entity(entity)
|
|
|
|
|
.remove::<Name>()
|
|
|
|
|
.insert(NodeBundle {
|
|
|
|
|
style: Style {
|
|
|
|
|
flex_direction: FlexDirection::Column,
|
|
|
|
|
padding: UiRect::all(Val::Px(3.0)),
|
|
|
|
|
margin: UiRect::all(Val::Px(3.0)),
|
|
|
|
|
display: Display::None,
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
background_color: BackgroundColor(Color::PINK),
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
background_color: BackgroundColor(Color::TEAL),
|
|
|
|
|
..default()
|
|
|
|
|
})
|
|
|
|
|
.set_parent(parent);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Describes the state of an element
|
|
|
|
|
#[derive(Debug, Component)]
|
|
|
|
|
pub enum UiElementState {
|
|
|
|
|
Enabled,
|
|
|
|
|
Disabled,
|
|
|
|
|
Active,
|
|
|
|
|
Error,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// GameUiList for holding ordered collections of objects
|
|
|
|
|
#[derive(Debug, Component)]
|
|
|
|
|
pub struct GameUiList;
|
|
|
|
|
|
|
|
|
|
/// Manage UI Lists: lists of UI entities.
|
|
|
|
|
fn init_ui_list(events: Query<Entity, Added<GameUiList>>, mut commands: Commands) {
|
|
|
|
|
events.iter().for_each(|entity| {
|
|
|
|
|
commands.entity(entity).insert(NodeBundle {
|
|
|
|
|
style: Style {
|
|
|
|
|
flex_direction: FlexDirection::Column,
|
|
|
|
|
justify_items: JustifyItems::Center,
|
|
|
|
|
border: UiRect::all(Val::Px(2.0)),
|
|
|
|
|
margin: UiRect::all(Val::Px(2.0)),
|
|
|
|
|
padding: UiRect::all(Val::Px(2.0)),
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
background_color: BackgroundColor(Color::RED),
|
|
|
|
|
border_color: BorderColor(Color::BLACK),
|
|
|
|
|
..default()
|
|
|
|
|
})
|
|
|
|
|
.set_parent(parent_id);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// GameUiSet Component for holding collections of objects
|
|
|
|
|
#[derive(Debug, Component)]
|
|
|
|
|
pub struct GameUiSet;
|
|
|
|
|
|
|
|
|
|
/// Manage UI Sets: collections of UI entities.
|
|
|
|
|
fn init_ui_set(events: Query<Entity, Added<GameUiSet>>, mut commands: Commands) {
|
|
|
|
|
events.iter().for_each(|entity| {
|
|
|
|
|
let parent = commands
|
|
|
|
|
.spawn(NodeBundle {
|
|
|
|
|
fn spawn_set(events: Query<(Entity, &GameUi), Added<GameUi>>, mut commands: Commands) {
|
|
|
|
|
events
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|(_, ui)| **ui == GameUi::Set)
|
|
|
|
|
.for_each(|(entity, _)| {
|
|
|
|
|
info!("Spawning UI Set");
|
|
|
|
|
commands.entity(entity).insert(NodeBundle {
|
|
|
|
|
style: Style {
|
|
|
|
|
flex_direction: FlexDirection::Column,
|
|
|
|
|
padding: UiRect::all(Val::Px(5.0)),
|
|
|
|
|
margin: UiRect::all(Val::Px(5.0)),
|
|
|
|
|
flex_direction: FlexDirection::Row,
|
|
|
|
|
flex_wrap: FlexWrap::Wrap,
|
|
|
|
|
padding: UiRect::all(Val::Px(3.0)),
|
|
|
|
|
margin: UiRect::all(Val::Px(3.0)),
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
background_color: BackgroundColor(Color::BLUE),
|
|
|
|
|
border_color: BorderColor(Color::BLACK),
|
|
|
|
|
background_color: BackgroundColor(Color::RED),
|
|
|
|
|
..default()
|
|
|
|
|
})
|
|
|
|
|
.id();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
commands
|
|
|
|
|
.entity(entity)
|
|
|
|
|
.insert(NodeBundle {
|
|
|
|
|
fn spawn_button(events: Query<(Entity, &GameUi), Added<GameUi>>, mut commands: Commands) {
|
|
|
|
|
events
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|(_, ui)| **ui == GameUi::Button)
|
|
|
|
|
.for_each(|(entity, _)| {
|
|
|
|
|
info!("Spawning UI Button");
|
|
|
|
|
commands.entity(entity).insert(ButtonBundle {
|
|
|
|
|
style: Style {
|
|
|
|
|
flex_direction: FlexDirection::Row,
|
|
|
|
|
flex_wrap: FlexWrap::Wrap,
|
|
|
|
|
border: UiRect::all(Val::Px(2.0)),
|
|
|
|
|
margin: UiRect::all(Val::Px(2.0)),
|
|
|
|
|
padding: UiRect::all(Val::Px(2.0)),
|
|
|
|
|
padding: UiRect::all(Val::Px(3.0)),
|
|
|
|
|
margin: UiRect::all(Val::Px(3.0)),
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
background_color: BackgroundColor(Color::BLUE),
|
|
|
|
|
border_color: BorderColor(Color::BLACK),
|
|
|
|
|
..default()
|
|
|
|
|
})
|
|
|
|
|
.set_parent(parent);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// GameUiButton for interactive elements
|
|
|
|
|
#[derive(Debug, Component)]
|
|
|
|
|
pub struct GameUiButton;
|
|
|
|
|
|
|
|
|
|
/// Manage UI Buttons. interactive buttons.
|
|
|
|
|
fn init_ui_button(events: Query<Entity, Added<GameUiButton>>, mut commands: Commands) {
|
|
|
|
|
events.iter().for_each(|entity| {
|
|
|
|
|
commands.entity(entity).insert((
|
|
|
|
|
ButtonBundle {
|
|
|
|
|
style: Style {
|
|
|
|
|
margin: UiRect::all(Val::Px(2.0)),
|
|
|
|
|
padding: UiRect::all(Val::Px(2.0)),
|
|
|
|
|
border: UiRect::all(Val::Px(2.0)),
|
|
|
|
|
fn manage_names(
|
|
|
|
|
events: Query<(Entity, &Name), (With<GameUi>, Or<(Added<Name>, Changed<Name>)>)>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
) {
|
|
|
|
|
events.iter().for_each(|(entity, name)| {
|
|
|
|
|
commands.entity(entity).with_children(|parent| {
|
|
|
|
|
parent
|
|
|
|
|
.spawn(NodeBundle {
|
|
|
|
|
style: Style {
|
|
|
|
|
align_self: AlignSelf::FlexStart,
|
|
|
|
|
padding: UiRect::all(Val::Px(3.0)),
|
|
|
|
|
margin: UiRect::all(Val::Px(3.0)),
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
background_color: BackgroundColor(Color::CRIMSON),
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
background_color: BackgroundColor(Color::GREEN),
|
|
|
|
|
border_color: BorderColor(Color::BLACK),
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
UiElementState::Enabled,
|
|
|
|
|
));
|
|
|
|
|
})
|
|
|
|
|
.with_children(|parent| {
|
|
|
|
|
parent.spawn(TextBundle::from_section(
|
|
|
|
|
name.as_str(),
|
|
|
|
|
TextStyle { ..default() },
|
|
|
|
|
));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Manage button style for interactivity
|
|
|
|
|
fn manage_button_interaction(
|
|
|
|
|
mut events: Query<
|
|
|
|
|
(&Interaction, &UiElementState, &mut BackgroundColor),
|
|
|
|
|
(Changed<Interaction>, With<Button>),
|
|
|
|
|
fn manage_tab(
|
|
|
|
|
events: Query<
|
|
|
|
|
(&Interaction, Entity, &Parent),
|
|
|
|
|
(Changed<Interaction>, With<Button>, With<GameUi>),
|
|
|
|
|
>,
|
|
|
|
|
children: Query<&Children>,
|
|
|
|
|
parents: Query<&Parent>,
|
|
|
|
|
ui_elements: Query<(Entity, &GameUi)>,
|
|
|
|
|
mut styles: Query<&mut Style>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
) {
|
|
|
|
|
events
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.for_each(|(interaction, state, mut bg_color)| {
|
|
|
|
|
bg_color.0 = match state {
|
|
|
|
|
UiElementState::Enabled => match interaction {
|
|
|
|
|
Interaction::Pressed => Color::BLACK,
|
|
|
|
|
Interaction::Hovered => Color::ORANGE,
|
|
|
|
|
Interaction::None => Color::GREEN,
|
|
|
|
|
},
|
|
|
|
|
UiElementState::Active => Color::PINK,
|
|
|
|
|
UiElementState::Error => Color::RED,
|
|
|
|
|
UiElementState::Disabled => Color::GRAY,
|
|
|
|
|
.iter()
|
|
|
|
|
.for_each(|(interaction, interacted_entity, parent)| {
|
|
|
|
|
// Checks:
|
|
|
|
|
// entity pressed
|
|
|
|
|
// entity is button
|
|
|
|
|
// entity parent is a tab
|
|
|
|
|
//
|
|
|
|
|
// Find all other tabs
|
|
|
|
|
// Display::None all except for tab button
|
|
|
|
|
|
|
|
|
|
match interaction {
|
|
|
|
|
Interaction::Pressed => {
|
|
|
|
|
children.get(parent.get()).iter().for_each(|&children| {
|
|
|
|
|
children
|
|
|
|
|
.iter()
|
|
|
|
|
.filter(|&child| *child != interacted_entity)
|
|
|
|
|
.for_each(|&child| {
|
|
|
|
|
let mut style = styles.get_mut(child).expect("child has style");
|
|
|
|
|
style.display = match style.display {
|
|
|
|
|
Display::Flex => Display::None,
|
|
|
|
|
Display::None => Display::Flex,
|
|
|
|
|
_ => Display::Flex, // Not Reachable
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
_ => (),
|
|
|
|
|
}
|
|
|
|
|
// When tab button is clicked
|
|
|
|
|
// For all children of parent (the tab)
|
|
|
|
|
// If selected:
|
|
|
|
|
// display: Display::Flex,
|
|
|
|
|
// else:
|
|
|
|
|
// Set display: Display::None,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Manage the cursor icon for better immersion
|
|
|
|
|
fn manage_cursor(
|
|
|
|
|
mut primary_window: Query<&mut Window, With<PrimaryWindow>>,
|
|
|
|
|
events: Query<&Interaction, (Changed<Interaction>, With<Button>)>,
|
|
|
|
|
) {
|
|
|
|
|
if !events.is_empty() {
|
|
|
|
|
let mut window = primary_window.single_mut();
|
|
|
|
|
window.cursor.icon = events
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|&event| *event != Interaction::None)
|
|
|
|
|
.map_or(CursorIcon::Default, |event| match event {
|
|
|
|
|
Interaction::Hovered | Interaction::Pressed => CursorIcon::Hand,
|
|
|
|
|
Interaction::None => CursorIcon::Help, // Shouldn't be reachable
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|