Compare commits
2 Commits
fc02df8638
...
6ae7b094ad
| Author | SHA1 | Date |
|---|---|---|
|
|
6ae7b094ad | 2 years ago |
|
|
dd912f420f | 2 years ago |
@ -0,0 +1,55 @@
|
|||||||
|
use bevy::input::keyboard::KeyboardInput;
|
||||||
|
use bevy::input::ButtonState;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.add_systems(Startup, init)
|
||||||
|
.add_systems(Update, update)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(mut commands: Commands) {
|
||||||
|
// UI Camera
|
||||||
|
commands.spawn((
|
||||||
|
Camera2dBundle { ..default() },
|
||||||
|
UiCameraConfig { show_ui: true },
|
||||||
|
));
|
||||||
|
|
||||||
|
// Text Box
|
||||||
|
commands.spawn(TextBundle::from_section(
|
||||||
|
"Start typing! (Escape to clear)\n",
|
||||||
|
TextStyle {
|
||||||
|
color: Color::WHITE,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(mut query: Query<&mut Text>, mut events: EventReader<KeyboardInput>) {
|
||||||
|
for KeyboardInput {
|
||||||
|
key_code,
|
||||||
|
scan_code,
|
||||||
|
state,
|
||||||
|
..
|
||||||
|
} in events.iter()
|
||||||
|
{
|
||||||
|
if *state == ButtonState::Pressed {
|
||||||
|
let mut text = query.single_mut();
|
||||||
|
if *key_code == Some(KeyCode::Escape) {
|
||||||
|
text.sections.drain(1..);
|
||||||
|
} else {
|
||||||
|
let style = TextStyle {
|
||||||
|
color: Color::WHITE,
|
||||||
|
..default()
|
||||||
|
};
|
||||||
|
let added = format!(
|
||||||
|
"Pressed: KeyCode: {:?} / ScanCode {:?}\n",
|
||||||
|
key_code, scan_code
|
||||||
|
);
|
||||||
|
text.sections.push(TextSection::new(added, style));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,15 +1,399 @@
|
|||||||
/// TODO:
|
/// TODO:
|
||||||
/// * Titles on top left of window/tab
|
/// * Text box w/ clear button
|
||||||
/// *
|
/// * Names/Labels management
|
||||||
use bevy::{prelude::*, window::PrimaryWindow};
|
/// * Button color management
|
||||||
|
/// * Move code to submodules
|
||||||
|
///
|
||||||
|
/// BUGS:
|
||||||
|
/// * When selecting one tree, possible to select another without the first closing.
|
||||||
|
///
|
||||||
|
use bevy::{
|
||||||
|
input::{
|
||||||
|
keyboard::KeyboardInput,
|
||||||
|
mouse::{MouseScrollUnit, MouseWheel},
|
||||||
|
ButtonState,
|
||||||
|
},
|
||||||
|
prelude::*,
|
||||||
|
window::PrimaryWindow,
|
||||||
|
};
|
||||||
|
|
||||||
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;
|
app.add_systems(Update, (select_tab, select_textbox, text_editor, scroll))
|
||||||
// .add_systems(PreUpdate, )
|
.add_systems(PostUpdate, selection);
|
||||||
// .add_systems(Update,)
|
|
||||||
// .add_systems(PostUpdate, );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Bundle)]
|
||||||
|
pub struct UiKitContainer {
|
||||||
|
node_bundle: NodeBundle,
|
||||||
|
select: UiKitSelect,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum UiKitPosition {
|
||||||
|
Top,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiKitContainer {
|
||||||
|
pub fn new(position: UiKitPosition) -> Self {
|
||||||
|
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, 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()
|
||||||
|
},
|
||||||
|
background_color: BackgroundColor(color),
|
||||||
|
border_color: BorderColor(Color::BLACK),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
select: UiKitSelect::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Component, Copy, Clone, PartialEq)]
|
||||||
|
pub enum UiKitSelect {
|
||||||
|
Active,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Bundle)]
|
||||||
|
pub struct UiKitTextInput {
|
||||||
|
text_bundle: TextBundle,
|
||||||
|
interaction: Interaction,
|
||||||
|
select: UiKitSelect,
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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>,
|
||||||
|
) {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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>,
|
||||||
|
) {
|
||||||
|
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()
|
||||||
|
.filter(|&e| *e != entity)
|
||||||
|
.for_each(|sibling| {
|
||||||
|
if let Ok(mut select) = selects.get_mut(*sibling) {
|
||||||
|
*select = UiKitSelect::None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select_textbox(
|
||||||
|
mut events: Query<(&Interaction, &mut UiKitSelect), (With<Text>, Changed<Interaction>)>,
|
||||||
|
) {
|
||||||
|
events.iter_mut().for_each(|(interaction, mut select)| {
|
||||||
|
*select = match interaction {
|
||||||
|
Interaction::Pressed => UiKitSelect::Active,
|
||||||
|
Interaction::Hovered => UiKitSelect::Active,
|
||||||
|
Interaction::None => UiKitSelect::None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_editor(
|
||||||
|
keyboard: Res<Input<KeyCode>>,
|
||||||
|
mut events: EventReader<KeyboardInput>,
|
||||||
|
mut query: Query<&mut Text, With<UiKitSelect>>,
|
||||||
|
) {
|
||||||
|
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()
|
||||||
|
};
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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>>,
|
||||||
|
) {
|
||||||
|
// 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