Compare commits
No commits in common. 'cc05c7c434f6312012c80e1ffc0882042aa20367' and '6ae7b094ad5cd6219f745375745fcd22138a9e18' have entirely different histories.
cc05c7c434
...
6ae7b094ad
File diff suppressed because it is too large
Load Diff
@ -1,605 +1,399 @@
|
||||
/// TODO:
|
||||
/// * Text box w/ "reset" button
|
||||
/// * Generic Minimize/Close Components
|
||||
/// * Title bar w/ min/close controls
|
||||
/// * Notice/Warning/Error Popups
|
||||
/// * Textbox (ReadOnly)
|
||||
/// * Textbox (ReadWrite)
|
||||
/// * Text box w/ clear button
|
||||
/// * Names/Labels management
|
||||
/// * Button color management
|
||||
/// * Move code to submodules
|
||||
///
|
||||
/// BUGS:
|
||||
/// * When selecting one tree, possible to select another without the first closing.
|
||||
///
|
||||
use bevy::{
|
||||
asset::Asset,
|
||||
input::mouse::{MouseScrollUnit, MouseWheel},
|
||||
input::{
|
||||
keyboard::KeyboardInput,
|
||||
mouse::{MouseScrollUnit, MouseWheel},
|
||||
ButtonState,
|
||||
},
|
||||
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_event::<Alert>()
|
||||
.add_systems(Startup, init_alerts)
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
create_titles,
|
||||
manage_titles,
|
||||
manage_button_interaction,
|
||||
manage_select_active,
|
||||
manage_cursor,
|
||||
manage_scroll,
|
||||
manage_collapse_hiding,
|
||||
manage_collapse_active,
|
||||
show_child_number,
|
||||
manage_sort,
|
||||
spawn_alert,
|
||||
),
|
||||
)
|
||||
.add_systems(PostUpdate, close_window);
|
||||
}
|
||||
}
|
||||
|
||||
pub use title::*;
|
||||
mod title {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Component)]
|
||||
pub struct Title {
|
||||
pub text: String,
|
||||
app.add_systems(Update, (select_tab, select_textbox, text_editor, scroll))
|
||||
.add_systems(PostUpdate, selection);
|
||||
}
|
||||
|
||||
#[derive(Debug, Component)]
|
||||
pub struct Note {
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Component)]
|
||||
pub struct TitleText;
|
||||
|
||||
#[derive(Debug, Component)]
|
||||
pub struct Minimize {
|
||||
pub target: Entity,
|
||||
#[derive(Debug, Bundle)]
|
||||
pub struct UiKitContainer {
|
||||
node_bundle: NodeBundle,
|
||||
select: UiKitSelect,
|
||||
}
|
||||
|
||||
#[derive(Debug, Component)]
|
||||
pub struct Close {
|
||||
pub target: Entity,
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum UiKitPosition {
|
||||
Top,
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
pub fn create_titles(
|
||||
events: Query<
|
||||
(
|
||||
Entity,
|
||||
&Title,
|
||||
Option<&Note>,
|
||||
Option<&Minimize>,
|
||||
Option<&Close>,
|
||||
),
|
||||
Added<Title>,
|
||||
>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
events.for_each(|(entity, title, note, minimize, close)| {
|
||||
commands.entity(entity).with_children(|parent| {
|
||||
parent.spawn((
|
||||
TextBundle {
|
||||
text: Text {
|
||||
sections: vec![
|
||||
TextSection {
|
||||
value: title.text.clone(),
|
||||
style: TextStyle {
|
||||
color: Color::BLACK,
|
||||
..default()
|
||||
},
|
||||
},
|
||||
TextSection {
|
||||
value: note
|
||||
.unwrap_or(&Note {
|
||||
text: String::new(),
|
||||
})
|
||||
.text
|
||||
.clone(),
|
||||
style: TextStyle {
|
||||
color: Color::BLACK,
|
||||
..default()
|
||||
},
|
||||
},
|
||||
],
|
||||
..default()
|
||||
},
|
||||
style: Style {
|
||||
margin: UiRect::all(Val::Px(3.0)),
|
||||
padding: UiRect::all(Val::Px(3.0)),
|
||||
left: Val::Px(0.0),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
Sorting(0),
|
||||
TitleText,
|
||||
));
|
||||
if minimize.is_some() || close.is_some() {
|
||||
parent
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
// border: UiRect::all(Val::Px(1.0)),
|
||||
// margin: UiRect::all(Val::Px(1.0)),
|
||||
// padding: UiRect::all(Val::Px(1.0)),
|
||||
right: Val::Px(0.0),
|
||||
..default()
|
||||
},
|
||||
background_color: Color::WHITE.into(),
|
||||
border_color: Color::BLACK.into(),
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
if let Some(Minimize { target }) = minimize {
|
||||
parent
|
||||
.spawn((
|
||||
ButtonBundle {
|
||||
style: Style {
|
||||
impl UiKitContainer {
|
||||
pub fn new(position: UiKitPosition) -> Self {
|
||||
let style = match position {
|
||||
UiKitPosition::Top => Style {
|
||||
border: UiRect::all(Val::Px(1.0)),
|
||||
// margin: UiRect::all(Val::Px(1.0)),
|
||||
padding: UiRect::all(Val::Px(1.0)),
|
||||
..default()
|
||||
},
|
||||
background_color: Color::WHITE.into(),
|
||||
border_color: Color::BLACK.into(),
|
||||
..default()
|
||||
},
|
||||
Collapse { target: *target },
|
||||
Active,
|
||||
))
|
||||
.with_children(|parent| {
|
||||
parent.spawn(TextBundle::from_section(
|
||||
"-",
|
||||
TextStyle {
|
||||
color: Color::BLACK,
|
||||
top: Val::Percent(102.0),
|
||||
left: Val::Px(-2.0),
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::FlexStart,
|
||||
display: Display::None,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
}
|
||||
if let Some(Close { target }) = close {
|
||||
parent
|
||||
.spawn((
|
||||
ButtonBundle {
|
||||
style: Style {
|
||||
UiKitPosition::Left => Style {
|
||||
border: UiRect::all(Val::Px(1.0)),
|
||||
// margin: UiRect::all(Val::Px(1.0)),
|
||||
padding: UiRect::all(Val::Px(1.0)),
|
||||
left: Val::Percent(100.0),
|
||||
top: Val::Px(-2.0),
|
||||
flex_direction: FlexDirection::Column,
|
||||
justify_items: JustifyItems::Start,
|
||||
display: Display::None,
|
||||
..default()
|
||||
},
|
||||
background_color: Color::WHITE.into(),
|
||||
border_color: Color::BLACK.into(),
|
||||
UiKitPosition::Right => Style {
|
||||
border: UiRect::all(Val::Px(1.0)),
|
||||
right: Val::Percent(104.0),
|
||||
top: Val::Px(-2.0),
|
||||
flex_direction: FlexDirection::Column,
|
||||
justify_items: JustifyItems::Start,
|
||||
display: Display::None,
|
||||
..default()
|
||||
},
|
||||
Close { target: *target },
|
||||
))
|
||||
.with_children(|parent| {
|
||||
parent.spawn(TextBundle::from_section(
|
||||
"x",
|
||||
TextStyle {
|
||||
color: Color::BLACK,
|
||||
};
|
||||
UiKitContainer {
|
||||
node_bundle: NodeBundle {
|
||||
style,
|
||||
background_color: BackgroundColor(Color::PURPLE),
|
||||
border_color: BorderColor(Color::BLACK),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
select: UiKitSelect::None,
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pub fn manage_titles(
|
||||
events: Query<(&Title, Option<&Note>, &Children), Or<(Changed<Title>, Changed<Note>)>>,
|
||||
mut texts: Query<&mut Text, With<TitleText>>,
|
||||
) {
|
||||
events.iter().for_each(|(title, note, children)| {
|
||||
children.iter().for_each(|child| {
|
||||
if let Ok(mut text) = texts.get_mut(*child) {
|
||||
*text = Text {
|
||||
sections: vec![
|
||||
TextSection {
|
||||
value: title.text.clone(),
|
||||
style: TextStyle {
|
||||
color: Color::BLACK,
|
||||
#[derive(Debug, Bundle)]
|
||||
pub struct UiKitButton {
|
||||
button_bundle: ButtonBundle,
|
||||
select: UiKitSelect,
|
||||
}
|
||||
|
||||
impl UiKitButton {
|
||||
pub fn new(color: Color) -> Self {
|
||||
UiKitButton {
|
||||
button_bundle: ButtonBundle {
|
||||
style: Style {
|
||||
border: UiRect::all(Val::Px(1.0)),
|
||||
width: Val::Px(100.0),
|
||||
height: Val::Px(50.0),
|
||||
flex_direction: FlexDirection::Column,
|
||||
..default()
|
||||
},
|
||||
},
|
||||
TextSection {
|
||||
value: note
|
||||
.unwrap_or(&Note {
|
||||
text: String::new(),
|
||||
})
|
||||
.text
|
||||
.clone(),
|
||||
style: TextStyle {
|
||||
color: Color::BLACK,
|
||||
background_color: BackgroundColor(color),
|
||||
border_color: BorderColor(Color::BLACK),
|
||||
..default()
|
||||
},
|
||||
},
|
||||
],
|
||||
..default()
|
||||
select: UiKitSelect::None,
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub fn close_window(
|
||||
events: Query<(&Interaction, &Close), (With<Button>, Changed<Interaction>)>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
events
|
||||
.iter()
|
||||
.filter(|(&interaction, _)| interaction == Interaction::Pressed)
|
||||
.for_each(|(_, Close { target })| {
|
||||
commands.entity(*target).despawn_recursive();
|
||||
});
|
||||
}
|
||||
#[derive(Debug, Component, Copy, Clone, PartialEq)]
|
||||
pub enum UiKitSelect {
|
||||
Active,
|
||||
None,
|
||||
}
|
||||
|
||||
pub use collapse::*;
|
||||
mod collapse {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Component)]
|
||||
pub struct Collapse {
|
||||
pub target: Entity,
|
||||
#[derive(Debug, Bundle)]
|
||||
pub struct UiKitTextInput {
|
||||
text_bundle: TextBundle,
|
||||
interaction: Interaction,
|
||||
select: UiKitSelect,
|
||||
}
|
||||
|
||||
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>();
|
||||
impl UiKitTextInput {
|
||||
pub fn new() -> Self {
|
||||
UiKitTextInput {
|
||||
text_bundle: TextBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
interaction: Interaction::None,
|
||||
select: UiKitSelect::None,
|
||||
}
|
||||
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>>,
|
||||
/// When an item is selected/de-selected change it's display accordingly
|
||||
fn selection(
|
||||
mut events: Query<
|
||||
(&mut BackgroundColor, &UiKitSelect, &Children),
|
||||
(Changed<UiKitSelect>, 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;
|
||||
events
|
||||
.iter_mut()
|
||||
.for_each(|(mut bg_color, select, children)| {
|
||||
bg_color.0 = match select {
|
||||
UiKitSelect::Active => Color::RED,
|
||||
UiKitSelect::None => Color::WHITE,
|
||||
};
|
||||
children.iter().for_each(|&child| {
|
||||
if let Ok(mut style) = styles.get_mut(child) {
|
||||
style.display = match select {
|
||||
UiKitSelect::Active => Display::Flex,
|
||||
UiKitSelect::None => Display::None,
|
||||
}
|
||||
}
|
||||
});
|
||||
// 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<(Entity, &Collapse)>,
|
||||
mut commands: Commands,
|
||||
/// Toggle a UI Nav tree open/closed
|
||||
///
|
||||
/// PERF: This is hella not performant, we just usually don't have many elements to iterate over so
|
||||
/// it's tolerable.
|
||||
///
|
||||
// TODO: Should not be able to select multiple children in branch of tree
|
||||
// TODO: Port to ui.rs
|
||||
fn select_tab(
|
||||
events: Query<Entity, (Changed<Interaction>, With<Button>)>,
|
||||
interactions: Query<&Interaction, With<Button>>,
|
||||
mut selects: Query<&mut UiKitSelect>,
|
||||
parents: Query<&Parent>,
|
||||
children: Query<&Children>,
|
||||
) {
|
||||
// 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(|(button, collapse)| {
|
||||
if entity == collapse.target {
|
||||
Some(button)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
events.iter().for_each(|entity| {
|
||||
// Otherwise, update nav tree(s)
|
||||
if let Ok(interaction) = interactions.get(entity) {
|
||||
match interaction {
|
||||
Interaction::Pressed | Interaction::Hovered => {
|
||||
{
|
||||
let parent = parents.get(entity).expect("entity has parent");
|
||||
children
|
||||
.get(parent.get())
|
||||
.expect("parent has children")
|
||||
.iter()
|
||||
.for_each(|button| {
|
||||
let num_children = children.len();
|
||||
commands.entity(*button).insert(Note {
|
||||
text: format!("({})", num_children),
|
||||
});
|
||||
});
|
||||
});
|
||||
.filter(|&e| *e != entity)
|
||||
.for_each(|sibling| {
|
||||
if let Ok(mut select) = selects.get_mut(*sibling) {
|
||||
*select = UiKitSelect::None
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub use buttons::*;
|
||||
mod buttons {
|
||||
use super::*;
|
||||
if let Ok(mut select) = selects.get_mut(entity) {
|
||||
*select = UiKitSelect::Active
|
||||
}
|
||||
}
|
||||
Interaction::None => {
|
||||
// Find the ancestor which does not have a parent
|
||||
let root_parent = parents
|
||||
.iter_ancestors(entity)
|
||||
.find(|&e| parents.get(e).is_err())
|
||||
.expect("entity has root parent");
|
||||
let family: Vec<Entity> = children.iter_descendants(root_parent).collect();
|
||||
let family_inactive = family
|
||||
.iter()
|
||||
.filter_map(|member| interactions.get(*member).ok())
|
||||
.all(|&interaction| interaction == Interaction::None);
|
||||
|
||||
#[derive(Debug, Component)]
|
||||
pub struct Active;
|
||||
// Zero out children
|
||||
let descendants: Vec<Entity> = children.iter_descendants(entity).collect();
|
||||
let descendants_inactive = descendants
|
||||
.iter()
|
||||
.filter_map(|child| interactions.get(*child).ok())
|
||||
.all(|&interaction| interaction == Interaction::None);
|
||||
|
||||
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();
|
||||
// The entire tree is inactive
|
||||
if family_inactive {
|
||||
family.iter().for_each(|member| {
|
||||
if let Ok(mut select) = selects.get_mut(*member) {
|
||||
*select = UiKitSelect::None
|
||||
}
|
||||
});
|
||||
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(),
|
||||
},
|
||||
if let Ok(mut select) = selects.get_mut(entity) {
|
||||
*select = UiKitSelect::None
|
||||
}
|
||||
// Just the sub-tree is inactive
|
||||
} else if descendants_inactive {
|
||||
descendants.iter().for_each(|child| {
|
||||
if let Ok(mut select) = selects.get_mut(*child) {
|
||||
*select = UiKitSelect::None
|
||||
}
|
||||
})
|
||||
});
|
||||
if let Ok(mut select) = selects.get_mut(entity) {
|
||||
*select = UiKitSelect::None
|
||||
}
|
||||
|
||||
/// Marks a container node as having single- or multi-active components
|
||||
#[derive(Debug, Component)]
|
||||
pub enum Select {
|
||||
Multi,
|
||||
Single,
|
||||
// This node is active (usually a parent of an active child)
|
||||
} else if let Ok(mut select) = selects.get_mut(entity) {
|
||||
*select = UiKitSelect::Active
|
||||
}
|
||||
|
||||
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>,
|
||||
fn select_textbox(
|
||||
mut events: Query<(&Interaction, &mut UiKitSelect), (With<Text>, Changed<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
|
||||
events.iter_mut().for_each(|(interaction, mut select)| {
|
||||
*select = match interaction {
|
||||
Interaction::Pressed => UiKitSelect::Active,
|
||||
Interaction::Hovered => UiKitSelect::Active,
|
||||
Interaction::None => UiKitSelect::None,
|
||||
}
|
||||
});
|
||||
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>>,
|
||||
fn text_editor(
|
||||
keyboard: Res<Input<KeyCode>>,
|
||||
mut events: EventReader<KeyboardInput>,
|
||||
mut query: Query<&mut Text, With<UiKitSelect>>,
|
||||
) {
|
||||
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);
|
||||
events.iter().for_each(
|
||||
|KeyboardInput {
|
||||
key_code,
|
||||
state,
|
||||
scan_code,
|
||||
..
|
||||
}| {
|
||||
match state {
|
||||
ButtonState::Pressed => {
|
||||
if let Some(kc) = key_code {
|
||||
query.iter_mut().for_each(|mut text| {
|
||||
use KeyCode::*;
|
||||
|
||||
let style = TextStyle {
|
||||
color: Color::BLACK,
|
||||
..default()
|
||||
};
|
||||
|
||||
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
|
||||
if *kc == Back {
|
||||
text.sections.pop();
|
||||
} else {
|
||||
let c = match kc {
|
||||
// Letters
|
||||
A | B | C | D | E | F | G | H | I | J | K | L | M | N | O
|
||||
| P | Q | R | S | T | U | V | W | X | Y | Z => {
|
||||
if keyboard.any_pressed([ShiftLeft, ShiftRight]) {
|
||||
format!("{:?}", kc).to_uppercase()
|
||||
} else {
|
||||
Ordering::Equal
|
||||
format!("{:?}", kc).to_lowercase()
|
||||
}
|
||||
}
|
||||
// Top Row
|
||||
Grave => "`".to_string(),
|
||||
Key1 | Numpad1 => "1".to_string(),
|
||||
Key2 | Numpad2 => "2".to_string(),
|
||||
Key3 | Numpad3 => "3".to_string(),
|
||||
Key4 | Numpad4 => "4".to_string(),
|
||||
Key5 | Numpad5 => "5".to_string(),
|
||||
Key6 | Numpad6 => "6".to_string(),
|
||||
Key7 | Numpad7 => "7".to_string(),
|
||||
Key8 | Numpad8 => "8".to_string(),
|
||||
Key9 | Numpad9 => "9".to_string(),
|
||||
Key0 | Numpad0 => "0".to_string(),
|
||||
Minus => "-".to_string(),
|
||||
Equals => "=".to_string(),
|
||||
|
||||
// Left side
|
||||
Tab => "\t".to_string(),
|
||||
// Right side
|
||||
// Row 2
|
||||
BracketLeft => "[".to_string(),
|
||||
BracketRight => "]".to_string(),
|
||||
Backslash => "\\".to_string(),
|
||||
// Row 3
|
||||
Semicolon => ";".to_string(),
|
||||
Apostrophe => "'".to_string(),
|
||||
Return => "\n".to_string(),
|
||||
// Row 4
|
||||
Comma => ",".to_string(),
|
||||
Period => ".".to_string(),
|
||||
Slash => "/".to_string(),
|
||||
// Space
|
||||
Space => " ".to_string(),
|
||||
// None
|
||||
_ => "".to_string(),
|
||||
};
|
||||
if c.len() > 0 {
|
||||
text.sections.push(TextSection::new(c, style));
|
||||
}
|
||||
}
|
||||
(Ok(Some(_)), Err(_)) | (Ok(Some(_)), Ok(None)) => Ordering::Less,
|
||||
(Err(_), Ok(Some(_))) | (Ok(None), Ok(Some(_))) => Ordering::Greater,
|
||||
_ => Ordering::Equal,
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub use alert::*;
|
||||
pub mod alert {
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Event)]
|
||||
pub enum Alert {
|
||||
Info(String),
|
||||
Warn(String),
|
||||
Danger(String),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
#[derive(Debug, Component)]
|
||||
pub struct AlertsWidget;
|
||||
|
||||
pub fn init_alerts(mut commands: Commands) {
|
||||
commands.spawn((
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
top: Val::Px(0.0),
|
||||
right: Val::Px(0.0),
|
||||
position_type: PositionType::Absolute,
|
||||
width: Val::Percent(33.0),
|
||||
padding: UiRect::all(Val::Px(1.0)),
|
||||
margin: UiRect::all(Val::Px(1.0)),
|
||||
border: UiRect::all(Val::Px(1.0)),
|
||||
flex_direction: FlexDirection::Column,
|
||||
justify_items: JustifyItems::Center,
|
||||
..default()
|
||||
},
|
||||
border_color: Color::WHITE.into(),
|
||||
..default()
|
||||
},
|
||||
AlertsWidget,
|
||||
));
|
||||
)
|
||||
}
|
||||
|
||||
pub fn spawn_alert(
|
||||
mut events: EventReader<Alert>,
|
||||
root: Query<Entity, With<AlertsWidget>>,
|
||||
mut commands: Commands,
|
||||
// TODO: Reset default position when de-activated
|
||||
fn scroll(
|
||||
mut scrolls: EventReader<MouseWheel>,
|
||||
mut query: Query<(&mut Style, &UiKitSelect, Entity, &Children, &Parent)>,
|
||||
changes: Query<Entity, Changed<UiKitSelect>>,
|
||||
) {
|
||||
events.iter().for_each(|alert| {
|
||||
info!("Processing alert {:?}", alert);
|
||||
|
||||
let (color, text) = match alert {
|
||||
Alert::Info(text) => (Color::BLUE, text),
|
||||
Alert::Warn(text) => (Color::ORANGE, text),
|
||||
Alert::Danger(text) => (Color::RED, text),
|
||||
};
|
||||
commands.entity(root.single()).with_children(|parent| {
|
||||
parent
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(100.0),
|
||||
padding: UiRect::all(Val::Px(1.0)),
|
||||
margin: UiRect::all(Val::Px(1.0)),
|
||||
border: UiRect::all(Val::Px(2.0)),
|
||||
flex_direction: FlexDirection::Column,
|
||||
justify_self: JustifySelf::Center,
|
||||
..default()
|
||||
},
|
||||
border_color: color.into(),
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
parent.spawn((
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
padding: UiRect::all(Val::Px(1.0)),
|
||||
margin: UiRect::all(Val::Px(1.0)),
|
||||
border: UiRect::all(Val::Px(1.0)),
|
||||
flex_direction: FlexDirection::Row,
|
||||
align_items: AlignItems::Center,
|
||||
align_content: AlignContent::Center,
|
||||
justify_content: JustifyContent::SpaceBetween,
|
||||
..default()
|
||||
},
|
||||
background_color: color.into(),
|
||||
..default()
|
||||
},
|
||||
Title {
|
||||
text: "Alert".into(),
|
||||
},
|
||||
Close {
|
||||
target: parent.parent_entity(),
|
||||
},
|
||||
Sorting(0),
|
||||
));
|
||||
parent.spawn(TextBundle::from_section(text, TextStyle { ..default() }));
|
||||
// Brute force: When all Actives are set to None (event) reset all style tops...
|
||||
changes.iter().for_each(|_| {
|
||||
let all_inactive = query
|
||||
.iter_mut()
|
||||
.all(|(_, select, _, _, _)| *select == UiKitSelect::None);
|
||||
all_inactive.then(|| {
|
||||
query
|
||||
.iter_mut()
|
||||
.for_each(|(mut style, _, _, _, _)| style.top = Val::Px(0.0));
|
||||
});
|
||||
});
|
||||
scrolls.iter().for_each(|MouseWheel { unit, y, .. }| {
|
||||
// Find the leaf selected entity
|
||||
let leaf = query
|
||||
.iter()
|
||||
.find(|(_, select, _, children, parent)| {
|
||||
// This node is active
|
||||
let self_active = **select == UiKitSelect::Active;
|
||||
// All children are not selected
|
||||
let children_inactive = children.iter().all(|&child| {
|
||||
if let Ok((_, select, _, _, _)) = query.get(child) {
|
||||
*select == UiKitSelect::None
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
// Both must be true
|
||||
self_active && children_inactive
|
||||
})
|
||||
.map(|(_, _, _, _, parent)| parent.get());
|
||||
|
||||
if let Some(l) = leaf {
|
||||
if let Ok((mut style, _, _, _, _)) = query.get_mut(l) {
|
||||
if *y != 0.0 {
|
||||
let delta = match unit {
|
||||
MouseScrollUnit::Line => *y,
|
||||
MouseScrollUnit::Pixel => 5.0,
|
||||
};
|
||||
style.top.try_sub_assign(Val::Px(delta));
|
||||
info!("Top: {:?}", style.top);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue