You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

367 lines
12 KiB
Rust

/// TODO:
/// * Text box w/ "reset" button
/// * Generic Minimize/Close Components
/// * Title bar w/ min/close controls
/// * Notice/Warning/Error Popups
/// * Textbox (ReadOnly)
/// * Textbox (ReadWrite)
/// * Move code to submodules
///
use bevy::{
asset::Asset,
input::mouse::{MouseScrollUnit, MouseWheel},
prelude::*,
window::PrimaryWindow,
};
use std::cmp::PartialEq;
#[derive(Debug, Component, PartialEq)]
pub struct TargetAsset<T: Asset> {
pub handle: Handle<T>,
}
#[derive(Debug, Component, PartialEq)]
pub struct TargetEntity {
pub entity: Entity,
}
pub struct GameUiPlugin;
impl Plugin for GameUiPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
(
init_titles,
manage_button_interaction,
manage_select_active,
manage_cursor,
manage_scroll,
manage_collapse_active,
manage_collapse_hiding,
show_child_number,
manage_sort,
),
);
}
}
pub use title::*;
mod title {
use super::*;
#[derive(Debug, Component, Default)]
pub struct Title {
pub name: String,
pub note: Option<String>,
}
pub fn init_titles(
events: Query<(Entity, &Title), Or<(Changed<Title>, Added<Title>)>>,
mut commands: Commands,
) {
events.for_each(|(entity, Title { name, note })| {
commands
.entity(entity)
.despawn_descendants()
.with_children(|parent| {
parent
.spawn((
NodeBundle {
style: Style {
padding: UiRect::all(Val::Px(5.0)),
margin: UiRect::all(Val::Px(5.0)),
border: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Row,
..default()
},
..default()
},
Sorting(0),
))
.with_children(|parent| {
parent.spawn(TextBundle {
text: Text {
sections: vec![
TextSection {
value: name.clone(),
style: TextStyle {
color: Color::BLACK,
..default()
},
},
TextSection {
value: note.clone().unwrap_or(String::new()),
style: TextStyle {
color: Color::BLACK,
..default()
},
},
],
..default()
},
style: Style {
margin: UiRect::all(Val::Px(5.0)),
padding: UiRect::all(Val::Px(5.0)),
..default()
},
..default()
});
});
});
});
}
}
pub use collapse::*;
mod collapse {
use super::*;
#[derive(Debug, Component)]
pub struct Collapse {
pub target: Entity,
}
pub fn manage_collapse_active(
events: Query<
(Entity, Option<&Active>, &Interaction),
(Changed<Interaction>, With<Button>, With<Collapse>),
>,
mut commands: Commands,
) {
events
.iter()
.filter(|(_, _, &interaction)| interaction == Interaction::Pressed)
.for_each(|(entity, active, _)| {
match active {
Some(_) => {
commands.entity(entity).remove::<Active>();
}
None => {
// Set this butotn to active
commands.entity(entity).insert(Active);
}
};
});
}
pub fn manage_collapse_hiding(
added: Query<Entity, (Added<Active>, With<Collapse>)>,
mut removed: RemovedComponents<Active>,
collapses: Query<&Collapse, With<Button>>,
mut styles: Query<&mut Style>,
) {
// Added collapse, display the target entity
added.iter().for_each(|e| {
if let Ok(Collapse { target }) = collapses.get(e) {
if let Ok(mut style) = styles.get_mut(*target) {
style.display = Display::Flex;
}
}
});
// Removed collapse, hide the target entity
removed.iter().for_each(|e| {
if let Ok(Collapse { target }) = collapses.get(e) {
if let Ok(mut style) = styles.get_mut(*target) {
style.display = Display::None;
}
}
});
}
pub fn show_child_number(
events: Query<(Entity, &Children), (Changed<Children>, With<Node>)>,
mut tabs: Query<(&Collapse, &mut Title)>,
) {
// Any time a widget changes
events.iter().for_each(|(entity, children)| {
// Find any tabs which have this as a target
tabs.iter_mut()
.find_map(|(collapse, title)| {
if entity == collapse.target {
Some(title)
} else {
None
}
})
.iter_mut()
.for_each(|title| {
title.note = Some(format!("({})", children.len().saturating_sub(1)))
});
});
}
}
pub use buttons::*;
mod buttons {
use super::*;
#[derive(Debug, Component)]
pub struct Active;
pub fn manage_button_interaction(
events: Query<
(Entity, &Interaction, Option<&Active>),
(Changed<Interaction>, With<Button>),
>,
added_active: Query<Entity, Added<Active>>,
mut removed_active: RemovedComponents<Active>,
mut bg_colors: Query<&mut BackgroundColor>,
) {
added_active.for_each(|e| {
if let Ok(mut bg_color) = bg_colors.get_mut(e) {
*bg_color = Color::ORANGE.into();
}
});
removed_active.iter().for_each(|e| {
if let Ok(mut bg_color) = bg_colors.get_mut(e) {
*bg_color = Color::WHITE.into();
}
});
events.for_each(|(entity, interaction, active)| {
if let Ok(mut bg_color) = bg_colors.get_mut(entity) {
match active {
Some(_) => match interaction {
Interaction::None => *bg_color = Color::ORANGE.into(),
Interaction::Pressed => *bg_color = Color::YELLOW.into(),
Interaction::Hovered => *bg_color = Color::RED.into(),
},
None => match interaction {
Interaction::None => *bg_color = Color::WHITE.into(),
Interaction::Pressed => *bg_color = Color::WHITE.into(),
Interaction::Hovered => *bg_color = Color::GRAY.into(),
},
}
}
})
}
/// Marks a container node as having single- or multi-active components
#[derive(Debug, Component)]
pub enum Select {
Multi,
Single,
}
pub fn manage_select_active(
events: Query<(Entity, &Parent), Added<Active>>,
children: Query<(&Select, &Children)>,
mut commands: Commands,
) {
events.iter().for_each(|(entity, parent)| {
if let Ok((select, childs)) = children.get(parent.get()) {
match select {
Select::Single => {
childs
.iter()
.filter(|&child| *child != entity)
.for_each(|&child| {
commands.entity(child).remove::<Active>();
})
}
Select::Multi => (),
}
}
});
}
}
pub use scroll::*;
mod scroll {
use super::*;
#[derive(Debug, Component)]
pub struct Scroll;
pub fn manage_scroll(
mut events: EventReader<MouseWheel>,
mut query: Query<(&Interaction, &Parent, &Children, &mut Style), With<Scroll>>,
interactions: Query<&Interaction>,
) {
events.iter().for_each(|MouseWheel { unit, y, .. }| {
query
.iter_mut()
.filter(|(&interaction, parent, children, _)| {
let self_hover = interaction == Interaction::Hovered;
let child_hover = children.iter().any(|&child| {
if let Ok(&interaction) = interactions.get(child) {
interaction == Interaction::Hovered
} else {
false
}
});
let parent_hover = interactions
.get(parent.get())
.map_or(false, |&interaction| interaction == Interaction::Hovered);
self_hover || child_hover || parent_hover
})
.for_each(|(_, _, _, mut style)| {
let factor = 2.0;
let val = match unit {
MouseScrollUnit::Line => match style.top {
Val::Px(v) => v + (y * factor),
_ => (*y) * factor,
},
MouseScrollUnit::Pixel => match style.top {
Val::Px(v) => v + (y * factor),
_ => (*y) * factor,
},
};
style.top = Val::Px(val.min(0.0));
});
});
}
}
use cursor::*;
mod cursor {
use super::*;
pub fn manage_cursor(
events: Query<&Interaction, Changed<Interaction>>,
mut window: Query<&mut Window, With<PrimaryWindow>>,
) {
events.for_each(|interaction| {
let mut win = window.single_mut();
win.cursor.icon = match interaction {
Interaction::None => CursorIcon::Default,
Interaction::Hovered => CursorIcon::Hand,
Interaction::Pressed => CursorIcon::Grabbing,
}
})
}
}
pub use sort::*;
mod sort {
use std::cmp::Ordering;
use super::*;
#[derive(Debug, Component)]
pub struct Sorting(pub u8);
pub fn manage_sort(
mut events: Query<&mut Children, Changed<Children>>,
sorting: Query<Option<&Sorting>>,
) {
events.iter_mut().for_each(|mut children| {
children.sort_by(|&a, &b| match (sorting.get(a), sorting.get(b)) {
(Ok(Some(Sorting(ord_a))), Ok(Some(Sorting(ord_b)))) => {
if ord_a > ord_b {
Ordering::Greater
} else if ord_a < ord_b {
Ordering::Less
} else {
Ordering::Equal
}
}
(Ok(Some(_)), Err(_)) | (Ok(Some(_)), Ok(None)) => Ordering::Less,
(Err(_), Ok(Some(_))) | (Ok(None), Ok(Some(_))) => Ordering::Greater,
_ => Ordering::Equal,
});
})
}
}