Compare commits

..

No commits in common. 'cc05c7c434f6312012c80e1ffc0882042aa20367' and '6ae7b094ad5cd6219f745375745fcd22138a9e18' have entirely different histories.

1
Cargo.lock generated

@ -2226,7 +2226,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bevy", "bevy",
"bevy_rapier3d", "bevy_rapier3d",
"serde",
] ]
[[package]] [[package]]

@ -43,7 +43,6 @@ path = "bin/chars.rs"
[dependencies] [dependencies]
bevy = "0.11" bevy = "0.11"
bevy_rapier3d = "*" bevy_rapier3d = "*"
serde = "1"
# From rapier docs # From rapier docs
[profile.dev.package.bevy_rapier3d] [profile.dev.package.bevy_rapier3d]

File diff suppressed because it is too large Load Diff

@ -68,27 +68,140 @@ fn spawn_tree(parent: &mut ChildBuilder, pos: UiKitPosition, depth: u8, length:
}; };
(0..length).for_each(|_| { (0..length).for_each(|_| {
parent parent
.spawn(( .spawn(UiKitButton::new(Color::PINK))
UiKitButton { color: Color::PINK },
UiKitSelect::default(),
UiKitLabel {
name: "Button".into(),
},
))
.with_children(|parent| { .with_children(|parent| {
if depth > 1 { if depth > 1 {
parent parent
.spawn(( .spawn(UiKitContainer::new(pos))
UiKitContainer { position: pos },
UiKitSelect::default(),
UiKitLabel { name: "Tab".into() },
))
.with_children(|parent| { .with_children(|parent| {
spawn_tree(parent, pos2, depth - 1, length); spawn_tree(parent, pos2, depth - 1, length);
}); });
} }
}); });
}); });
// parent
// .spawn(UiKitButton::new(Color::PINK))
// .with_children(|parent| {
// parent
// .spawn(UiKitContainer::new(pos))
// .with_children(|parent| {
// parent
// .spawn(UiKitButton::new(Color::PINK))
// .with_children(|parent| {
// parent
// .spawn(UiKitContainer::new(pos2))
// .with_children(|parent| {
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// });
// });
// parent
// .spawn(UiKitButton::new(Color::PINK))
// .with_children(|parent| {
// parent
// .spawn(UiKitContainer::new(pos2))
// .with_children(|parent| {
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// });
// });
// parent
// .spawn(UiKitButton::new(Color::PINK))
// .with_children(|parent| {
// parent
// .spawn(UiKitContainer::new(pos2))
// .with_children(|parent| {
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// });
// });
// });
// });
// parent
// .spawn(UiKitButton::new(Color::PINK))
// .with_children(|parent| {
// parent
// .spawn(UiKitContainer::new(pos))
// .with_children(|parent| {
// parent
// .spawn(UiKitButton::new(Color::PINK))
// .with_children(|parent| {
// parent
// .spawn(UiKitContainer::new(pos2))
// .with_children(|parent| {
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// });
// });
// parent
// .spawn(UiKitButton::new(Color::PINK))
// .with_children(|parent| {
// parent
// .spawn(UiKitContainer::new(pos2))
// .with_children(|parent| {
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// });
// });
// parent
// .spawn(UiKitButton::new(Color::PINK))
// .with_children(|parent| {
// parent
// .spawn(UiKitContainer::new(pos2))
// .with_children(|parent| {
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// });
// });
// });
// });
// parent
// .spawn(UiKitButton::new(Color::PINK))
// .with_children(|parent| {
// parent
// .spawn(UiKitContainer::new(pos))
// .with_children(|parent| {
// parent
// .spawn(UiKitButton::new(Color::PINK))
// .with_children(|parent| {
// parent
// .spawn(UiKitContainer::new(pos2))
// .with_children(|parent| {
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// });
// });
// parent
// .spawn(UiKitButton::new(Color::PINK))
// .with_children(|parent| {
// parent
// .spawn(UiKitContainer::new(pos2))
// .with_children(|parent| {
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// });
// });
// parent
// .spawn(UiKitButton::new(Color::PINK))
// .with_children(|parent| {
// parent
// .spawn(UiKitContainer::new(pos2))
// .with_children(|parent| {
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// parent.spawn(UiKitButton::new(Color::PINK));
// });
// });
// });
// });
} }
fn init_ui2(mut commands: Commands) { fn init_ui2(mut commands: Commands) {
@ -113,8 +226,8 @@ fn init_ui2(mut commands: Commands) {
.spawn(ButtonBundle { .spawn(ButtonBundle {
style: Style { style: Style {
border: UiRect::all(Val::Px(1.0)), border: UiRect::all(Val::Px(1.0)),
// width: Val::Px(200.0), width: Val::Px(200.0),
// height: Val::Px(150.0), height: Val::Px(150.0),
align_self: AlignSelf::Center, align_self: AlignSelf::Center,
..default() ..default()
}, },
@ -141,7 +254,7 @@ fn init_ui2(mut commands: Commands) {
..default() ..default()
}) })
.with_children(|parent| { .with_children(|parent| {
spawn_tree(parent, UiKitPosition::Left, 3, 4); spawn_tree(parent, UiKitPosition::Left, 3, 7);
}); });
parent parent
@ -176,7 +289,7 @@ fn init_ui2(mut commands: Commands) {
..default() ..default()
}) })
.with_children(|parent| { .with_children(|parent| {
spawn_tree(parent, UiKitPosition::Top, 3, 3); spawn_tree(parent, UiKitPosition::Top, 3, 7);
}); });
}); });
} }

@ -1,605 +1,399 @@
/// TODO: /// TODO:
/// * Text box w/ "reset" button /// * Text box w/ clear button
/// * Generic Minimize/Close Components /// * Names/Labels management
/// * Title bar w/ min/close controls /// * Button color management
/// * Notice/Warning/Error Popups
/// * Textbox (ReadOnly)
/// * Textbox (ReadWrite)
/// * Move code to submodules /// * Move code to submodules
/// ///
/// BUGS:
/// * When selecting one tree, possible to select another without the first closing.
///
use bevy::{ use bevy::{
asset::Asset, input::{
input::mouse::{MouseScrollUnit, MouseWheel}, keyboard::KeyboardInput,
mouse::{MouseScrollUnit, MouseWheel},
ButtonState,
},
prelude::*, prelude::*,
window::PrimaryWindow, 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; pub struct GameUiPlugin;
impl Plugin for GameUiPlugin { impl Plugin for GameUiPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_event::<Alert>() app.add_systems(Update, (select_tab, select_textbox, text_editor, scroll))
.add_systems(Startup, init_alerts) .add_systems(PostUpdate, selection);
.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::*; #[derive(Debug, Bundle)]
mod title { pub struct UiKitContainer {
use super::*; node_bundle: NodeBundle,
select: UiKitSelect,
}
#[derive(Debug, Component)] #[derive(Copy, Clone)]
pub struct Title { pub enum UiKitPosition {
pub text: String, Top,
} Left,
Right,
}
#[derive(Debug, Component)] impl UiKitContainer {
pub struct Note { pub fn new(position: UiKitPosition) -> Self {
pub text: String, let style = match position {
UiKitPosition::Top => Style {
border: UiRect::all(Val::Px(1.0)),
top: Val::Percent(102.0),
left: Val::Px(-2.0),
flex_direction: FlexDirection::Column,
align_items: AlignItems::FlexStart,
display: Display::None,
..default()
},
UiKitPosition::Left => Style {
border: 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()
},
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()
},
};
UiKitContainer {
node_bundle: NodeBundle {
style,
background_color: BackgroundColor(Color::PURPLE),
border_color: BorderColor(Color::BLACK),
..default()
},
select: UiKitSelect::None,
}
} }
}
#[derive(Debug, Component)] #[derive(Debug, Bundle)]
pub struct TitleText; pub struct UiKitButton {
button_bundle: ButtonBundle,
select: UiKitSelect,
}
#[derive(Debug, Component)] impl UiKitButton {
pub struct Minimize { pub fn new(color: Color) -> Self {
pub target: Entity, 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()
},
background_color: BackgroundColor(color),
border_color: BorderColor(Color::BLACK),
..default()
},
select: UiKitSelect::None,
}
} }
}
#[derive(Debug, Component)] #[derive(Debug, Component, Copy, Clone, PartialEq)]
pub struct Close { pub enum UiKitSelect {
pub target: Entity, Active,
} None,
}
pub fn create_titles( #[derive(Debug, Bundle)]
events: Query< pub struct UiKitTextInput {
( text_bundle: TextBundle,
Entity, interaction: Interaction,
&Title, select: UiKitSelect,
Option<&Note>, }
Option<&Minimize>,
Option<&Close>, impl UiKitTextInput {
), pub fn new() -> Self {
Added<Title>, UiKitTextInput {
>, text_bundle: TextBundle {
mut commands: Commands, style: Style {
) { width: Val::Percent(100.0),
events.for_each(|(entity, title, note, minimize, close)| { height: Val::Percent(100.0),
commands.entity(entity).with_children(|parent| { ..default()
parent.spawn(( },
TextBundle { ..default()
text: Text { },
sections: vec![ interaction: Interaction::None,
TextSection { select: UiKitSelect::None,
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 {
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,
..default()
},
));
});
}
if let Some(Close { target }) = close {
parent
.spawn((
ButtonBundle {
style: 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()
},
Close { target: *target },
))
.with_children(|parent| {
parent.spawn(TextBundle::from_section(
"x",
TextStyle {
color: Color::BLACK,
..default()
},
));
});
}
});
}
});
});
} }
}
pub fn manage_titles( /// When an item is selected/de-selected change it's display accordingly
events: Query<(&Title, Option<&Note>, &Children), Or<(Changed<Title>, Changed<Note>)>>, fn selection(
mut texts: Query<&mut Text, With<TitleText>>, mut events: Query<
) { (&mut BackgroundColor, &UiKitSelect, &Children),
events.iter().for_each(|(title, note, children)| { (Changed<UiKitSelect>, With<Button>),
children.iter().for_each(|child| { >,
if let Ok(mut text) = texts.get_mut(*child) { mut styles: Query<&mut Style>,
*text = Text { ) {
sections: vec![ events
TextSection { .iter_mut()
value: title.text.clone(), .for_each(|(mut bg_color, select, children)| {
style: TextStyle { bg_color.0 = match select {
color: Color::BLACK, UiKitSelect::Active => Color::RED,
..default() UiKitSelect::None => Color::WHITE,
}, };
}, children.iter().for_each(|&child| {
TextSection { if let Ok(mut style) = styles.get_mut(child) {
value: note style.display = match select {
.unwrap_or(&Note { UiKitSelect::Active => Display::Flex,
text: String::new(), UiKitSelect::None => Display::None,
})
.text
.clone(),
style: TextStyle {
color: Color::BLACK,
..default()
},
},
],
..default()
} }
} }
})
})
}
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();
}); });
} });
} }
pub use collapse::*; /// Toggle a UI Nav tree open/closed
mod collapse { ///
use super::*; /// PERF: This is hella not performant, we just usually don't have many elements to iterate over so
/// it's tolerable.
#[derive(Debug, Component)] ///
pub struct Collapse { // TODO: Should not be able to select multiple children in branch of tree
pub target: Entity, // TODO: Port to ui.rs
} fn select_tab(
events: Query<Entity, (Changed<Interaction>, With<Button>)>,
pub fn manage_collapse_active( interactions: Query<&Interaction, With<Button>>,
events: Query< mut selects: Query<&mut UiKitSelect>,
(Entity, Option<&Active>, &Interaction), parents: Query<&Parent>,
(Changed<Interaction>, With<Button>, With<Collapse>), children: Query<&Children>,
>, ) {
mut commands: Commands, events.iter().for_each(|entity| {
) { // Otherwise, update nav tree(s)
events if let Ok(interaction) = interactions.get(entity) {
.iter() match interaction {
.filter(|(_, _, &interaction)| interaction == Interaction::Pressed) Interaction::Pressed | Interaction::Hovered => {
.for_each(|(entity, active, _)| { {
match active { let parent = parents.get(entity).expect("entity has parent");
Some(_) => { children
commands.entity(entity).remove::<Active>(); .get(parent.get())
} .expect("parent has children")
None => { .iter()
// Set this butotn to active .filter(|&e| *e != entity)
commands.entity(entity).insert(Active); .for_each(|sibling| {
if let Ok(mut select) = selects.get_mut(*sibling) {
*select = UiKitSelect::None
}
});
} }
};
});
}
pub fn manage_collapse_hiding( if let Ok(mut select) = selects.get_mut(entity) {
added: Query<Entity, (Added<Active>, With<Collapse>)>, *select = UiKitSelect::Active
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;
} }
} Interaction::None => {
}); // Find the ancestor which does not have a parent
} let root_parent = parents
.iter_ancestors(entity)
pub fn show_child_number( .find(|&e| parents.get(e).is_err())
events: Query<(Entity, &Children), (Changed<Children>, With<Node>)>, .expect("entity has root parent");
mut tabs: Query<(Entity, &Collapse)>, let family: Vec<Entity> = children.iter_descendants(root_parent).collect();
mut commands: Commands, let family_inactive = family
) { .iter()
// Any time a widget changes .filter_map(|member| interactions.get(*member).ok())
events.iter().for_each(|(entity, children)| { .all(|&interaction| interaction == Interaction::None);
// Find any tabs which have this as a target
tabs.iter_mut() // Zero out children
.find_map(|(button, collapse)| { let descendants: Vec<Entity> = children.iter_descendants(entity).collect();
if entity == collapse.target { let descendants_inactive = descendants
Some(button) .iter()
} else { .filter_map(|child| interactions.get(*child).ok())
None .all(|&interaction| interaction == Interaction::None);
// 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
}
});
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
}
// This node is active (usually a parent of an active child)
} else if let Ok(mut select) = selects.get_mut(entity) {
*select = UiKitSelect::Active
} }
})
.iter()
.for_each(|button| {
let num_children = children.len();
commands.entity(*button).insert(Note {
text: format!("({})", num_children),
});
});
});
}
}
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 fn select_textbox(
#[derive(Debug, Component)] mut events: Query<(&Interaction, &mut UiKitSelect), (With<Text>, Changed<Interaction>)>,
pub enum Select { ) {
Multi, events.iter_mut().for_each(|(interaction, mut select)| {
Single, *select = match interaction {
} Interaction::Pressed => UiKitSelect::Active,
Interaction::Hovered => UiKitSelect::Active,
Interaction::None => UiKitSelect::None,
}
});
}
pub fn manage_select_active( fn text_editor(
events: Query<(Entity, &Parent), Added<Active>>, keyboard: Res<Input<KeyCode>>,
children: Query<(&Select, &Children)>, mut events: EventReader<KeyboardInput>,
mut commands: Commands, mut query: Query<&mut Text, With<UiKitSelect>>,
) { ) {
events.iter().for_each(|(entity, parent)| { events.iter().for_each(
if let Ok((select, childs)) = children.get(parent.get()) { |KeyboardInput {
match select { key_code,
Select::Single => { state,
childs scan_code,
.iter() ..
.filter(|&child| *child != entity) }| {
.for_each(|&child| { match state {
commands.entity(child).remove::<Active>(); ButtonState::Pressed => {
}) if let Some(kc) = key_code {
query.iter_mut().for_each(|mut text| {
use KeyCode::*;
let style = TextStyle {
color: Color::BLACK,
..default()
};
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 {
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));
}
}
});
} }
Select::Multi => (),
} }
_ => (),
} }
}); },
} )
} }
pub use scroll::*; // TODO: Reset default position when de-activated
mod scroll { fn scroll(
use super::*; mut scrolls: EventReader<MouseWheel>,
mut query: Query<(&mut Style, &UiKitSelect, Entity, &Children, &Parent)>,
#[derive(Debug, Component)] changes: Query<Entity, Changed<UiKitSelect>>,
pub struct Scroll; ) {
// Brute force: When all Actives are set to None (event) reset all style tops...
pub fn manage_scroll( changes.iter().for_each(|_| {
mut events: EventReader<MouseWheel>, let all_inactive = query
mut query: Query<(&Interaction, &Parent, &Children, &mut Style), With<Scroll>>, .iter_mut()
interactions: Query<&Interaction>, .all(|(_, select, _, _, _)| *select == UiKitSelect::None);
) { all_inactive.then(|| {
events.iter().for_each(|MouseWheel { unit, y, .. }| {
query query
.iter_mut() .iter_mut()
.filter(|(&interaction, parent, children, _)| { .for_each(|(mut style, _, _, _, _)| style.top = Val::Px(0.0));
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));
});
}); });
} });
} scrolls.iter().for_each(|MouseWheel { unit, y, .. }| {
// Find the leaf selected entity
use cursor::*; let leaf = query
mod cursor { .iter()
use super::*; .find(|(_, select, _, children, parent)| {
// This node is active
pub fn manage_cursor( let self_active = **select == UiKitSelect::Active;
events: Query<&Interaction, Changed<Interaction>>, // All children are not selected
mut window: Query<&mut Window, With<PrimaryWindow>>, let children_inactive = children.iter().all(|&child| {
) { if let Ok((_, select, _, _, _)) = query.get(child) {
events.for_each(|interaction| { *select == UiKitSelect::None
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 { } else {
Ordering::Equal 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);
} }
(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,
) {
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() }));
});
});
});
}
} }

Loading…
Cancel
Save