Audio vertical slice done

main
Elijah Voigt 2 years ago
parent a100fa8f4a
commit b119019e30

@ -22,7 +22,7 @@ use bevy::{
prelude::*, prelude::*,
utils::HashSet, utils::HashSet,
}; };
use monologue_trees::debug::*; use monologue_trees::{debug::*, ui};
fn main() { fn main() {
App::new() App::new()
@ -36,361 +36,249 @@ fn main() {
..default() ..default()
}), }),
DebugInfoPlugin, DebugInfoPlugin,
ui::GameUiPlugin,
)) ))
.init_resource::<AssetRegistry>() .init_resource::<AssetRegistry>()
.add_systems(Startup, (initialize_ui,)) .add_systems(Startup, initialize_ui)
.add_systems( .add_systems(Update, (import_files, import_audio, play_audio))
Update,
(
// GLTF Systems
load_gltf,
unload_gltf,
manage_gltf_ui,
// Scene Systems
manage_scene_ui,
spawn_scene,
// Animation systems
manage_animation_ui,
// Camera systems
manage_camera_ui,
// Audio Systems
load_audio,
unload_audio,
manage_audio_ui,
play_audio,
// Level Import/Export systems
export_level,
import_level,
// Misc/Debug Systems
),
)
.run(); .run();
} }
/// A generic referenece used for UI elements to point to assets or entities #[derive(Resource, Default)]
#[derive(Debug, Component)] struct AssetRegistry(Vec<HandleUntyped>);
enum UiRef<T> {
Handle(T), #[derive(Resource)]
// Entity(T), struct Styles {
// Event(T), button: Style,
button_hovered: Style,
container: Style,
} }
/// UI:
/// * GLTFs
/// * Scenes
/// * Cameras
/// * Animations
/// * Audios
fn initialize_ui(mut commands: Commands) { fn initialize_ui(mut commands: Commands) {
commands.spawn(( commands.spawn((
Camera2dBundle { ..default() }, Camera2dBundle { ..default() },
UiCameraConfig { show_ui: true }, UiCameraConfig { show_ui: true },
)); ));
let base_style = Style {
border: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(5.0)),
flex_direction: FlexDirection::Column,
overflow: Overflow::clip(),
..default()
};
commands commands
.spawn(NodeBundle { .spawn(NodeBundle {
style: Style { style: Style {
width: Val::Percent(100.0), width: Val::Percent(50.0),
height: Val::Percent(100.0), height: Val::Percent(50.0),
..default() ..base_style.clone()
}, },
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
z_index: ZIndex::Local(100),
..default() ..default()
}) })
.with_children(|parent| { .with_children(|parent| {
parent {
.spawn(( parent
Name::new("GLTFs"), .spawn(NodeBundle {
GltfsUi,
NodeBundle {
style: Style { style: Style {
flex_direction: FlexDirection::Column, ..base_style.clone()
..default()
}, },
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
z_index: ZIndex::Local(20),
..default() ..default()
}, })
)) .with_children(|parent| {
.with_children(|parent| { // Spawn button container
parent.spawn((Name::new("Scenes"), ScenesUi, NodeBundle { ..default() })); let container = parent
parent.spawn((Name::new("Cameras"), CamerasUi, NodeBundle { ..default() })); .spawn((
parent.spawn(( NodeBundle {
Name::new("Animations"), style: Style {
AnimationsUi, ..base_style.clone()
NodeBundle { ..default() }, },
)); background_color: Color::WHITE.into(),
}); border_color: Color::BLACK.into(),
parent.spawn(( z_index: ZIndex::Local(10),
Name::new("Audio Clips"), ..default()
AudioClipsUi, },
NodeBundle { AudioWidget,
style: Style { ui::Scroll,
flex_direction: FlexDirection::Column, ui::Sorting(2),
..default() ))
}, .id();
..default()
},
));
});
}
#[derive(Resource, Default, Debug)]
struct AssetRegistry(HashSet<HandleUntyped>);
/// Component marking UI for loaded Gltf assets
#[derive(Component)]
struct GltfsUi;
/// Drag+Drop import GLTF to editor // Spawn widget
fn load_gltf( parent.spawn((
mut events: EventReader<FileDragAndDrop>, ButtonBundle {
server: Res<AssetServer>, style: Style {
mut assets: ResMut<AssetRegistry>, ..base_style.clone()
) { },
events background_color: Color::WHITE.into(),
.iter() border_color: Color::BLACK.into(),
.filter_map(|event| match event { z_index: ZIndex::Local(15),
FileDragAndDrop::DroppedFile { path_buf, .. } => Some(path_buf), ..default()
_ => None, },
}) ui::Title {
.for_each(|path_buf| { name: "Audio".into(),
let path = path_buf.as_path(); },
let handle = server.load_untyped(path); ui::Collapse { target: container },
assets.0.insert(handle); ui::Sorting(1),
));
});
}
}); });
}
/// Helper method to extract the stripped filename given a full asset path commands.insert_resource(Styles {
fn get_fname(asset_path: AssetPath, suffixes: &[&str]) -> String { button: Style { ..default() },
let path = asset_path.path().file_name().expect("Filename"); button_hovered: Style { ..default() },
let path_str = path.to_str().expect("Asset Path to Str"); container: Style { ..default() },
let name_str = suffixes })
.iter()
.rfold(path_str, |acc, &suffix| acc.trim_end_matches(suffix));
String::from(name_str)
} }
/// Sync GLTF assets with UI fn import_files(
/// mut events: EventReader<FileDragAndDrop>,
/// TODO: Handle failed load events
/// Options:
/// * Show Error message, do not add to UI
/// * Add to UI with visual indicator
/// This should be a separate async system
fn manage_gltf_ui(
mut events: EventReader<AssetEvent<Gltf>>,
root: Query<Entity, (With<GltfsUi>, Without<UiRef<Handle<Gltf>>>)>,
mut commands: Commands,
server: Res<AssetServer>, server: Res<AssetServer>,
mut registry: ResMut<AssetRegistry>,
) { ) {
events events.iter().for_each(|event| match event {
.iter() FileDragAndDrop::DroppedFile { path_buf, .. } => {
.filter_map(|event| match event { registry.0.push(
AssetEvent::Created { handle } => { server.load_untyped(
let asset_path = server path_buf
.get_handle_path(handle.clone()) .clone()
.expect("Fetch Asset Path"); .into_os_string()
let name = get_fname(asset_path, &[".gltf", ".glb"]); .into_string()
Some((handle.clone(), String::from(name))) .expect("Path converts to string"),
} ),
_ => None, );
}) }
.for_each(|(handle, name)| { _ => (),
root.iter().for_each(|entity| { })
commands.entity(entity).log_components();
});
commands
.spawn((
Name::new(name),
GltfsUi,
ButtonBundle { ..default() },
UiRef::Handle(handle.clone()),
))
.set_parent(root.single());
});
}
/// Remove gltf from editor
fn unload_gltf() {}
/// Component marking UI for Scene assets
#[derive(Component)]
struct ScenesUi;
/// Sync scene assets with UI
fn manage_scene_ui(
mut events: EventReader<AssetEvent<Scene>>,
root: Query<Entity, (With<ScenesUi>, Without<UiRef<Handle<Scene>>>)>,
mut commands: Commands,
gltfs: Res<Assets<Gltf>>,
registry: Res<AssetRegistry>,
) {
events
.iter()
.filter_map(|event| match event {
AssetEvent::Created { handle } => {
let name = registry
.0
.iter()
.find_map(
|gltf_handle| match gltfs.get(&gltf_handle.clone().typed::<Gltf>()) {
Some(gltf) => {
gltf.named_scenes.iter().find_map(|(name, scene_handle)| {
(scene_handle == handle).then_some(name)
})
}
None => None,
},
)
.expect("Find scene name");
Some((handle.clone(), String::from(name)))
}
_ => None,
})
.for_each(|(handle, name)| {
commands
.spawn((
Name::new(name),
ScenesUi,
ButtonBundle { ..default() },
UiRef::Handle(handle.clone()),
))
.set_parent(root.single());
});
} }
/// Component marking UI for Camera assets use audio::*;
#[derive(Component)] mod audio {
struct CamerasUi; use bevy::audio::PlaybackMode;
fn manage_camera_ui() {} use super::*;
/// Component marking UI for Animation assets #[derive(Debug, Component)]
#[derive(Component)] pub struct AudioWidget;
struct AnimationsUi;
fn manage_animation_ui( pub fn import_audio(
mut events: EventReader<AssetEvent<AnimationClip>>, mut events: EventReader<AssetEvent<AudioSource>>,
root: Query<Entity, (With<AnimationsUi>, Without<UiRef<Handle<AnimationClip>>>)>, mut commands: Commands,
mut commands: Commands, root: Query<Entity, With<AudioWidget>>,
gltfs: Res<Assets<Gltf>>, current: Query<(Entity, &Handle<AudioSource>)>,
registry: Res<AssetRegistry>, server: Res<AssetServer>,
) { ) {
events // TODO: Filter to just audio files
.iter() events
.filter_map(|event| match event { .iter()
AssetEvent::Created { handle } => { .filter(|event| match event {
let name = AssetEvent::Created { handle }
registry | AssetEvent::Removed { handle }
.0 | AssetEvent::Modified { handle } => {
.iter() if let Some(asset_path) = server.get_handle_path(handle.clone()) {
.find_map(|gltf_handle| { if let Some(extension) = asset_path.path().extension() {
match gltfs.get(&gltf_handle.clone().typed::<Gltf>()) { extension == "ogg"
Some(gltf) => gltf.named_animations.iter().find_map( } else {
|(name, animation_handle)| { false
(animation_handle == handle).then_some(name) }
}, } else {
), false
None => None, }
}
})
.for_each(|event| {
let create = |commands: &mut Commands, handle: Handle<AudioSource>| {
commands.entity(root.single()).with_children(|parent| {
let name = {
if let Some(asset_path) = server.get_handle_path(handle.clone()) {
if let Some(stem) = asset_path.path().file_stem() {
if let Some(val) = stem.to_str() {
String::from(val)
} else {
String::from("???")
}
} else {
String::from("???")
}
} else {
String::from("???")
} }
}) };
.expect("Find animation name"); let settings = PlaybackSettings {
Some((handle.clone(), String::from(name))) mode: PlaybackMode::Loop,
}
_ => None,
})
.for_each(|(handle, name)| {
commands
.spawn((
Name::new(name),
AnimationsUi,
ButtonBundle { ..default() },
UiRef::Handle(handle.clone()),
))
.set_parent(root.single());
});
}
/// Component marking UI for Audio Clip assets
#[derive(Component)]
struct AudioClipsUi;
/// Drag+Drop import Audio to editor
fn load_audio(
mut events: EventReader<FileDragAndDrop>,
server: Res<AssetServer>,
mut assets: ResMut<AssetRegistry>,
) {
events
.iter()
.filter_map(|event| match event {
FileDragAndDrop::DroppedFile { path_buf, .. } => Some(path_buf),
_ => None,
})
.for_each(|path_buf| {
let path = path_buf.as_path();
let handle = server.load_untyped(path);
assets.0.insert(handle);
});
}
fn manage_audio_ui(
mut events: EventReader<AssetEvent<AudioSource>>,
root: Query<Entity, (With<AudioClipsUi>, Without<AudioSink>)>,
mut commands: Commands,
server: Res<AssetServer>,
) {
events
.iter()
.filter_map(|event| match event {
AssetEvent::Created { handle } => {
let asset_path = server
.get_handle_path(handle.clone())
.expect("Fetch Asset Path");
let name = get_fname(asset_path, &[".ogg"]);
Some((handle.clone(), String::from(name)))
}
_ => None,
})
.for_each(|(handle, name)| {
commands
.spawn((
Name::new(name),
ButtonBundle { ..default() },
AudioClipsUi,
AudioBundle {
source: handle.clone(),
settings: PlaybackSettings {
mode: bevy::audio::PlaybackMode::Loop,
paused: true, paused: true,
..default() ..default()
}, };
}, parent.spawn((
)) AudioSourceBundle {
.set_parent(root.single()); source: handle,
}); settings,
} },
ButtonBundle {
style: Style {
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(5.0)),
padding: UiRect::all(Val::Px(5.0)),
..default()
},
border_color: Color::BLACK.into(),
..default()
},
ui::Title { name },
));
});
};
let destroy = |commands: &mut Commands, handle: Handle<AudioSource>| {
if let Some(entity) = current.iter().find_map(|(entity, current)| {
if *current == handle {
Some(entity)
} else {
None
}
}) {
commands.entity(entity).despawn_recursive();
}
};
match event {
AssetEvent::Created { handle } => {
info!("Asset created! {:?}", event);
create(&mut commands, handle.clone());
}
AssetEvent::Removed { handle } => {
info!("Asset removed! {:?}", event);
destroy(&mut commands, handle.clone());
}
AssetEvent::Modified { handle } => {
info!("Asset modified! {:?}", event);
destroy(&mut commands, handle.clone());
create(&mut commands, handle.clone());
}
}
});
}
/// Play/Loop Audio pub fn play_audio(
fn play_audio( events: Query<(Entity, &Interaction, &AudioSink), (With<Button>, Changed<Interaction>)>,
mut events: Query<(&Interaction, &AudioSink), (Changed<Interaction>, With<AudioClipsUi>)>, mut commands: Commands,
) { ) {
events events
.iter_mut() .iter()
.for_each(|(interaction, sink)| match interaction { .filter(|(_, &interaction, _)| interaction == Interaction::Pressed)
Interaction::Pressed => { .for_each(|(entity, _, sink)| {
sink.toggle(); sink.toggle();
} if sink.is_paused() {
_ => (), commands.entity(entity).remove::<ui::Active>();
}); } else {
commands.entity(entity).insert(ui::Active);
}
});
}
} }
/// Remove audio from editor
fn unload_audio() {}
/// Spawn Scene
fn spawn_scene() {}
/// Export level
fn export_level() {}
/// Import Level
fn import_level() {}

@ -21,475 +21,190 @@ 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_systems(Update, (select_tab, select_textbox, text_editor, scroll)) app.add_systems(
.add_systems( Update,
PreUpdate, (
( init_titles,
spawn_container, manage_button_interaction,
spawn_button, manage_cursor,
/* manage_labels */ selection, manage_scroll,
), manage_collapse,
); manage_sort,
),
);
} }
} }
pub use self::container::*; pub use title::*;
mod container { mod title {
use super::*; use super::*;
#[derive(Debug, Component)] #[derive(Debug, Component)]
pub struct UiKitContainer { pub struct Title {
pub position: UiKitPosition,
}
#[derive(Debug, Copy, Clone)]
pub enum UiKitPosition {
Top,
Left,
Right,
}
pub fn spawn_container(
events: Query<(Entity, &UiKitContainer, Option<&UiKitLabel>), Added<UiKitContainer>>,
mut commands: Commands,
) {
events.iter().for_each(|(entity, container, label)| {
let base_style = Style {
border: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
display: Display::None,
..default()
};
let style = match container.position {
UiKitPosition::Top => Style {
top: Val::Percent(100.0),
align_items: AlignItems::Start,
..base_style
},
UiKitPosition::Left => Style {
left: Val::Percent(100.0),
justify_items: JustifyItems::Start,
..base_style
},
UiKitPosition::Right => Style {
right: Val::Percent(104.0),
justify_items: JustifyItems::Start,
..base_style
},
};
commands
.entity(entity)
.insert(NodeBundle {
style,
background_color: BackgroundColor(Color::PURPLE),
border_color: BorderColor(Color::BLACK),
..default()
})
.with_children(|parent| {
if let Some(label) = label {
parent.spawn(
TextBundle::from_section(
label.name.clone(),
TextStyle {
color: Color::BLACK,
..default()
},
)
.with_style(Style {
align_self: AlignSelf::Center,
justify_self: JustifySelf::Center,
..default()
}),
);
}
});
})
}
}
pub use self::label::*;
mod label {
use super::*;
#[derive(Debug, Component)]
pub struct UiKitLabel {
pub name: String, pub name: String,
} }
// TODO: Handle modified labels pub fn init_titles(events: Query<(Entity, &Title), Added<Title>>, mut commands: Commands) {
pub fn manage_labels( events.for_each(|(entity, Title { name })| {
events: Query<(Entity, &UiKitLabel), Added<UiKitLabel>>,
mut commands: Commands,
) {
events.iter().for_each(|(entity, label)| {
commands.entity(entity).with_children(|parent| { commands.entity(entity).with_children(|parent| {
parent.spawn( parent.spawn(TextBundle {
TextBundle::from_section( text: Text {
label.name.clone(), sections: vec![TextSection {
TextStyle { value: name.clone(),
color: Color::BLACK, style: TextStyle {
..default() color: Color::BLACK,
}, ..default()
) },
.with_style(Style { }],
align_self: AlignSelf::Center, ..default()
justify_self: JustifySelf::Center, },
style: Style {
margin: UiRect::all(Val::Px(5.0)),
padding: UiRect::all(Val::Px(5.0)),
..default() ..default()
}), },
); ..default()
});
}); });
}); });
} }
} }
pub use self::button::*; pub use collapse::*;
mod button { mod collapse {
use super::*; use super::*;
#[derive(Debug, Component)] #[derive(Debug, Component)]
pub struct UiKitButton { pub struct Collapse {
pub color: Color, pub target: Entity,
} }
pub fn spawn_button( pub fn manage_collapse(
events: Query<(Entity, &UiKitButton, Option<&UiKitLabel>), Added<UiKitButton>>, events: Query<(Entity, &Collapse, &Interaction), (Changed<Interaction>, With<Button>)>,
mut commands: Commands, mut styles: Query<&mut Style>,
) { ) {
events.iter().for_each(|(entity, button, label)| { events
commands .iter()
.entity(entity) .filter(|(_, _, &interaction)| interaction == Interaction::Pressed)
.insert(ButtonBundle { .for_each(|(entity, collapse, _)| {
style: Style { info!("Collapse press on {:?}", entity);
border: UiRect::all(Val::Px(1.0)), if let Ok(mut style) = styles.get_mut(collapse.target) {
// width: Val::Px(100.0), style.display = match style.display {
// height: Val::Px(50.0), Display::None => Display::Flex,
flex_direction: FlexDirection::Column, Display::Flex | Display::Grid => Display::None,
..default() };
}, }
background_color: BackgroundColor(button.color), })
border_color: BorderColor(Color::BLACK),
..default()
})
.with_children(|parent| {
if let Some(label) = label {
parent.spawn(
TextBundle::from_section(
label.name.clone(),
TextStyle {
color: Color::BLACK,
..default()
},
)
.with_style(Style {
align_self: AlignSelf::Center,
justify_self: JustifySelf::Center,
..default()
}),
);
}
});
});
} }
} }
pub use self::select::*; pub use buttons::*;
mod select { mod buttons {
use super::*; use super::*;
#[derive(Debug, Component, Copy, Clone, PartialEq)] #[derive(Debug, Component)]
pub enum UiKitSelect { pub struct Active;
Active,
None,
}
impl Default for UiKitSelect {
fn default() -> Self {
UiKitSelect::None
}
}
/// When an item is selected/de-selected change it's display accordingly pub fn manage_button_interaction(
pub fn selection(
mut events: Query< mut events: Query<
(&mut BackgroundColor, &UiKitSelect, &Children), (&Interaction, &mut BackgroundColor, Option<&Active>),
(Changed<UiKitSelect>, With<Button>), (Changed<Interaction>, With<Button>),
>, >,
mut styles: Query<&mut Style>,
) { ) {
events events.for_each_mut(|(interaction, mut bg_color, active)| match active {
.iter_mut() Some(_) => match interaction {
.for_each(|(mut bg_color, select, children)| { Interaction::None => *bg_color = Color::ORANGE.into(),
bg_color.0 = match select { Interaction::Pressed => *bg_color = Color::YELLOW.into(),
UiKitSelect::Active => Color::RED, Interaction::Hovered => *bg_color = Color::RED.into(),
UiKitSelect::None => Color::WHITE, },
}; None => match interaction {
children.iter().for_each(|&child| { Interaction::None => *bg_color = Color::GRAY.into(),
if let Ok(mut style) = styles.get_mut(child) { Interaction::Pressed => *bg_color = Color::WHITE.into(),
style.display = match select { Interaction::Hovered => *bg_color = Color::DARK_GRAY.into(),
UiKitSelect::Active => Display::Flex, },
UiKitSelect::None => Display::None, })
}
}
});
});
} }
} }
pub use self::text_input::*; pub use scroll::*;
mod text_input { mod scroll {
use super::*; use super::*;
#[derive(Debug, Bundle)] #[derive(Debug, Component)]
pub struct UiKitTextInput { pub struct Scroll;
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,
}
}
}
pub fn select_textbox( pub fn manage_scroll(
mut events: Query<(&Interaction, &mut UiKitSelect), (With<Text>, Changed<Interaction>)>, mut events: EventReader<MouseWheel>,
mut query: Query<&mut Style, With<Scroll>>,
) { ) {
events.iter_mut().for_each(|(interaction, mut select)| { events.iter().for_each(|MouseWheel { unit, y, .. }| {
*select = match interaction { query.iter_mut().for_each(|mut style| {
Interaction::Pressed => UiKitSelect::Active, let factor = 2.0;
Interaction::Hovered => UiKitSelect::Active, let val = match unit {
Interaction::None => UiKitSelect::None, 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));
});
}); });
} }
}
pub fn text_editor( use cursor::*;
keyboard: Res<Input<KeyCode>>, mod cursor {
mut events: EventReader<KeyboardInput>, use super::*;
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 { pub fn manage_cursor(
text.sections.pop(); events: Query<&Interaction, Changed<Interaction>>,
} else { mut window: Query<&mut Window, With<PrimaryWindow>>,
let c = match kc { ) {
// Letters events.for_each(|interaction| {
A | B | C | D | E | F | G | H | I | J | K | L | M | N let mut win = window.single_mut();
| 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 win.cursor.icon = match interaction {
Tab => "\t".to_string(), Interaction::None => CursorIcon::Default,
// Right side Interaction::Hovered => CursorIcon::Hand,
// Row 2 Interaction::Pressed => CursorIcon::Grabbing,
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));
}
}
});
}
}
_ => (),
}
},
)
} }
} }
/// Toggle a UI Nav tree open/closed pub use sort::*;
/// mod sort {
/// PERF: This is hella not performant, we just usually don't have many elements to iterate over so use std::cmp::Ordering;
/// it's tolerable.
pub 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 use super::*;
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 #[derive(Debug, Component)]
if family_inactive { pub struct Sorting(pub u8);
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
}
}
}
}
});
}
// TODO: Reset default position when de-activated pub fn manage_sort(
fn scroll( mut events: Query<&mut Children, Changed<Children>>,
mut scrolls: EventReader<MouseWheel>, sorting: Query<Option<&Sorting>>,
mut query: Query<(&mut Style, &UiKitSelect, Entity, &Children, &Parent)>, ) {
changes: Query<Entity, Changed<UiKitSelect>>, events.iter_mut().for_each(|mut children| {
) { children.sort_by(|&a, &b| match (sorting.get(a), sorting.get(b)) {
// Brute force: When all Actives are set to None (event) reset all style tops... (Ok(Some(Sorting(ord_a))), Ok(Some(Sorting(ord_b)))) => {
changes.iter().for_each(|_| { if ord_a > ord_b {
let all_inactive = query Ordering::Greater
.iter_mut() } else if ord_a < ord_b {
.all(|(_, select, _, _, _)| *select == UiKitSelect::None); Ordering::Less
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, _)| {
// 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 { } else {
true info!("Equal");
Ordering::Equal
} }
});
// 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));
} }
} (Ok(Some(_)), Err(_)) | (Ok(Some(_)), Ok(None)) => Ordering::Less,
}; (Err(_), Ok(Some(_))) | (Ok(None), Ok(Some(_))) => Ordering::Greater,
}); _ => Ordering::Equal,
});
})
}
} }

Loading…
Cancel
Save