Audio vertical slice done

main
Elijah Voigt 2 years ago
parent a100fa8f4a
commit b119019e30

@ -22,7 +22,7 @@ use bevy::{
prelude::*,
utils::HashSet,
};
use monologue_trees::debug::*;
use monologue_trees::{debug::*, ui};
fn main() {
App::new()
@ -36,361 +36,249 @@ fn main() {
..default()
}),
DebugInfoPlugin,
ui::GameUiPlugin,
))
.init_resource::<AssetRegistry>()
.add_systems(Startup, (initialize_ui,))
.add_systems(
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
),
)
.add_systems(Startup, initialize_ui)
.add_systems(Update, (import_files, import_audio, play_audio))
.run();
}
/// A generic referenece used for UI elements to point to assets or entities
#[derive(Debug, Component)]
enum UiRef<T> {
Handle(T),
// Entity(T),
// Event(T),
#[derive(Resource, Default)]
struct AssetRegistry(Vec<HandleUntyped>);
#[derive(Resource)]
struct Styles {
button: Style,
button_hovered: Style,
container: Style,
}
/// UI:
/// * GLTFs
/// * Scenes
/// * Cameras
/// * Animations
/// * Audios
fn initialize_ui(mut commands: Commands) {
commands.spawn((
Camera2dBundle { ..default() },
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
.spawn(NodeBundle {
style: Style {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
..default()
width: Val::Percent(50.0),
height: Val::Percent(50.0),
..base_style.clone()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
z_index: ZIndex::Local(100),
..default()
})
.with_children(|parent| {
parent
.spawn((
Name::new("GLTFs"),
GltfsUi,
NodeBundle {
{
parent
.spawn(NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
..default()
..base_style.clone()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
z_index: ZIndex::Local(20),
..default()
},
))
.with_children(|parent| {
parent.spawn((Name::new("Scenes"), ScenesUi, NodeBundle { ..default() }));
parent.spawn((Name::new("Cameras"), CamerasUi, NodeBundle { ..default() }));
parent.spawn((
Name::new("Animations"),
AnimationsUi,
NodeBundle { ..default() },
));
});
parent.spawn((
Name::new("Audio Clips"),
AudioClipsUi,
NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
..default()
},
..default()
},
));
});
}
#[derive(Resource, Default, Debug)]
struct AssetRegistry(HashSet<HandleUntyped>);
/// Component marking UI for loaded Gltf assets
#[derive(Component)]
struct GltfsUi;
})
.with_children(|parent| {
// Spawn button container
let container = parent
.spawn((
NodeBundle {
style: Style {
..base_style.clone()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
z_index: ZIndex::Local(10),
..default()
},
AudioWidget,
ui::Scroll,
ui::Sorting(2),
))
.id();
/// Drag+Drop import GLTF to editor
fn load_gltf(
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);
// Spawn widget
parent.spawn((
ButtonBundle {
style: Style {
..base_style.clone()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
z_index: ZIndex::Local(15),
..default()
},
ui::Title {
name: "Audio".into(),
},
ui::Collapse { target: container },
ui::Sorting(1),
));
});
}
});
}
/// Helper method to extract the stripped filename given a full asset path
fn get_fname(asset_path: AssetPath, suffixes: &[&str]) -> String {
let path = asset_path.path().file_name().expect("Filename");
let path_str = path.to_str().expect("Asset Path to Str");
let name_str = suffixes
.iter()
.rfold(path_str, |acc, &suffix| acc.trim_end_matches(suffix));
String::from(name_str)
commands.insert_resource(Styles {
button: Style { ..default() },
button_hovered: Style { ..default() },
container: Style { ..default() },
})
}
/// Sync GLTF assets with UI
///
/// 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,
fn import_files(
mut events: EventReader<FileDragAndDrop>,
server: Res<AssetServer>,
mut registry: ResMut<AssetRegistry>,
) {
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, &[".gltf", ".glb"]);
Some((handle.clone(), String::from(name)))
}
_ => 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());
});
events.iter().for_each(|event| match event {
FileDragAndDrop::DroppedFile { path_buf, .. } => {
registry.0.push(
server.load_untyped(
path_buf
.clone()
.into_os_string()
.into_string()
.expect("Path converts to string"),
),
);
}
_ => (),
})
}
/// Component marking UI for Camera assets
#[derive(Component)]
struct CamerasUi;
use audio::*;
mod audio {
use bevy::audio::PlaybackMode;
fn manage_camera_ui() {}
use super::*;
/// Component marking UI for Animation assets
#[derive(Component)]
struct AnimationsUi;
#[derive(Debug, Component)]
pub struct AudioWidget;
fn manage_animation_ui(
mut events: EventReader<AssetEvent<AnimationClip>>,
root: Query<Entity, (With<AnimationsUi>, Without<UiRef<Handle<AnimationClip>>>)>,
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_animations.iter().find_map(
|(name, animation_handle)| {
(animation_handle == handle).then_some(name)
},
),
None => None,
pub fn import_audio(
mut events: EventReader<AssetEvent<AudioSource>>,
mut commands: Commands,
root: Query<Entity, With<AudioWidget>>,
current: Query<(Entity, &Handle<AudioSource>)>,
server: Res<AssetServer>,
) {
// TODO: Filter to just audio files
events
.iter()
.filter(|event| match event {
AssetEvent::Created { handle }
| AssetEvent::Removed { handle }
| AssetEvent::Modified { handle } => {
if let Some(asset_path) = server.get_handle_path(handle.clone()) {
if let Some(extension) = asset_path.path().extension() {
extension == "ogg"
} else {
false
}
} else {
false
}
}
})
.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");
Some((handle.clone(), String::from(name)))
}
_ => 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,
};
let settings = PlaybackSettings {
mode: PlaybackMode::Loop,
paused: true,
..default()
},
},
))
.set_parent(root.single());
});
}
};
parent.spawn((
AudioSourceBundle {
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
fn play_audio(
mut events: Query<(&Interaction, &AudioSink), (Changed<Interaction>, With<AudioClipsUi>)>,
) {
events
.iter_mut()
.for_each(|(interaction, sink)| match interaction {
Interaction::Pressed => {
pub fn play_audio(
events: Query<(Entity, &Interaction, &AudioSink), (With<Button>, Changed<Interaction>)>,
mut commands: Commands,
) {
events
.iter()
.filter(|(_, &interaction, _)| interaction == Interaction::Pressed)
.for_each(|(entity, _, sink)| {
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 {
fn build(&self, app: &mut App) {
app.add_systems(Update, (select_tab, select_textbox, text_editor, scroll))
.add_systems(
PreUpdate,
(
spawn_container,
spawn_button,
/* manage_labels */ selection,
),
);
app.add_systems(
Update,
(
init_titles,
manage_button_interaction,
manage_cursor,
manage_scroll,
manage_collapse,
manage_sort,
),
);
}
}
pub use self::container::*;
mod container {
pub use title::*;
mod title {
use super::*;
#[derive(Debug, Component)]
pub struct UiKitContainer {
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 struct Title {
pub name: String,
}
// TODO: Handle modified labels
pub fn manage_labels(
events: Query<(Entity, &UiKitLabel), Added<UiKitLabel>>,
mut commands: Commands,
) {
events.iter().for_each(|(entity, label)| {
pub fn init_titles(events: Query<(Entity, &Title), Added<Title>>, mut commands: Commands) {
events.for_each(|(entity, Title { name })| {
commands.entity(entity).with_children(|parent| {
parent.spawn(
TextBundle::from_section(
label.name.clone(),
TextStyle {
color: Color::BLACK,
..default()
},
)
.with_style(Style {
align_self: AlignSelf::Center,
justify_self: JustifySelf::Center,
parent.spawn(TextBundle {
text: Text {
sections: vec![TextSection {
value: name.clone(),
style: TextStyle {
color: Color::BLACK,
..default()
},
}],
..default()
},
style: Style {
margin: UiRect::all(Val::Px(5.0)),
padding: UiRect::all(Val::Px(5.0)),
..default()
}),
);
},
..default()
});
});
});
}
}
pub use self::button::*;
mod button {
pub use collapse::*;
mod collapse {
use super::*;
#[derive(Debug, Component)]
pub struct UiKitButton {
pub color: Color,
pub struct Collapse {
pub target: Entity,
}
pub fn spawn_button(
events: Query<(Entity, &UiKitButton, Option<&UiKitLabel>), Added<UiKitButton>>,
mut commands: Commands,
pub fn manage_collapse(
events: Query<(Entity, &Collapse, &Interaction), (Changed<Interaction>, With<Button>)>,
mut styles: Query<&mut Style>,
) {
events.iter().for_each(|(entity, button, label)| {
commands
.entity(entity)
.insert(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(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()
}),
);
}
});
});
events
.iter()
.filter(|(_, _, &interaction)| interaction == Interaction::Pressed)
.for_each(|(entity, collapse, _)| {
info!("Collapse press on {:?}", entity);
if let Ok(mut style) = styles.get_mut(collapse.target) {
style.display = match style.display {
Display::None => Display::Flex,
Display::Flex | Display::Grid => Display::None,
};
}
})
}
}
pub use self::select::*;
mod select {
pub use buttons::*;
mod buttons {
use super::*;
#[derive(Debug, Component, Copy, Clone, PartialEq)]
pub enum UiKitSelect {
Active,
None,
}
impl Default for UiKitSelect {
fn default() -> Self {
UiKitSelect::None
}
}
#[derive(Debug, Component)]
pub struct Active;
/// When an item is selected/de-selected change it's display accordingly
pub fn selection(
pub fn manage_button_interaction(
mut events: Query<
(&mut BackgroundColor, &UiKitSelect, &Children),
(Changed<UiKitSelect>, With<Button>),
(&Interaction, &mut BackgroundColor, Option<&Active>),
(Changed<Interaction>, 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,
}
}
});
});
events.for_each_mut(|(interaction, mut bg_color, active)| 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::GRAY.into(),
Interaction::Pressed => *bg_color = Color::WHITE.into(),
Interaction::Hovered => *bg_color = Color::DARK_GRAY.into(),
},
})
}
}
pub use self::text_input::*;
mod text_input {
pub use scroll::*;
mod scroll {
use super::*;
#[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,
}
}
}
#[derive(Debug, Component)]
pub struct Scroll;
pub fn select_textbox(
mut events: Query<(&Interaction, &mut UiKitSelect), (With<Text>, Changed<Interaction>)>,
pub fn manage_scroll(
mut events: EventReader<MouseWheel>,
mut query: Query<&mut Style, With<Scroll>>,
) {
events.iter_mut().for_each(|(interaction, mut select)| {
*select = match interaction {
Interaction::Pressed => UiKitSelect::Active,
Interaction::Hovered => UiKitSelect::Active,
Interaction::None => UiKitSelect::None,
}
events.iter().for_each(|MouseWheel { unit, y, .. }| {
query.iter_mut().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));
});
});
}
}
pub 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()
};
use cursor::*;
mod cursor {
use super::*;
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(),
pub fn manage_cursor(
events: Query<&Interaction, Changed<Interaction>>,
mut window: Query<&mut Window, With<PrimaryWindow>>,
) {
events.for_each(|interaction| {
let mut win = window.single_mut();
// 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));
}
}
});
}
}
_ => (),
}
},
)
win.cursor.icon = match interaction {
Interaction::None => CursorIcon::Default,
Interaction::Hovered => CursorIcon::Hand,
Interaction::Pressed => CursorIcon::Grabbing,
}
})
}
}
/// 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.
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);
pub use sort::*;
mod sort {
use std::cmp::Ordering;
// 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);
use super::*;
// 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
}
}
}
}
});
}
#[derive(Debug, Component)]
pub struct Sorting(pub u8);
// 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, _)| {
// 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
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 {
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