Audio and interactive button states worksgit status

main
Elijah Voigt 2 years ago
parent e159346b89
commit eef3acdade

@ -5,15 +5,14 @@
// TODO:
// * Tree Organization: GLTF contains Animations and Scenes
// * Camera can only select one at a time.
// * (easy) Load audios like current GLTFs
// * (easy) Loop audio enable/disable
// * (easy) Better Colorscheme
// * (easy) Interactive buttons (hover/click)
// * (medium) Visual errors for bad GLTFs
// * (medium) Collapsable containers (Gltfs, Animations, Scenes, Audio Clips, Cameras)
// * (medium) Spawn clicked scene
// * (medium) Play clicked animation
// * (idea) Use enum instead of markers for exclusive UI
// * (medium) Add fonts similar to Audios based on inspect-fonts
// * (hard) Add Dialogs (requires text box UI, saving, loading)
use bevy::{
asset::{AssetPath, Assets},
@ -57,6 +56,7 @@ fn main() {
// Audio Systems
load_audio,
unload_audio,
manage_audio_ui,
play_audio,
// Level Import/Export systems
export_level,
@ -72,7 +72,8 @@ fn main() {
#[derive(Debug, Component)]
enum UiRef<T> {
Handle(T),
Entity(T),
// Entity(T),
// Event(T),
}
/// UI:
@ -135,7 +136,7 @@ fn initialize_ui(mut commands: Commands) {
fn load_bogus(
mut events: EventReader<KeyboardInput>,
root: Query<Entity, With<AnimationsUi>>,
root: Query<Entity, (With<AnimationsUi>, Without<UiRef<Handle<AnimationClip>>>)>,
mut commands: Commands,
) {
events
@ -197,7 +198,7 @@ fn get_fname(asset_path: AssetPath, suffixes: &[&str]) -> String {
/// This should be a separate async system
fn manage_gltf_ui(
mut events: EventReader<AssetEvent<Gltf>>,
root: Query<Entity, With<GltfsUi>>,
root: Query<Entity, (With<GltfsUi>, Without<UiRef<Handle<Gltf>>>)>,
mut commands: Commands,
server: Res<AssetServer>,
) {
@ -214,6 +215,9 @@ fn manage_gltf_ui(
_ => None,
})
.for_each(|(handle, name)| {
root.iter().for_each(|entity| {
commands.entity(entity).log_components();
});
commands
.spawn((
GameUiButton,
@ -236,7 +240,7 @@ struct ScenesUi;
/// Sync scene assets with UI
fn manage_scene_ui(
mut events: EventReader<AssetEvent<Scene>>,
root: Query<Entity, With<ScenesUi>>,
root: Query<Entity, (With<ScenesUi>, Without<UiRef<Handle<Scene>>>)>,
mut commands: Commands,
gltfs: Res<Assets<Gltf>>,
registry: Res<AssetRegistry>,
@ -252,10 +256,6 @@ fn manage_scene_ui(
|gltf_handle| match gltfs.get(&gltf_handle.clone().typed::<Gltf>()) {
Some(gltf) => {
gltf.named_scenes.iter().find_map(|(name, scene_handle)| {
info!(
"scene_handle({:?}) == handle({:?})",
scene_handle, handle
);
(scene_handle == handle).then_some(name)
})
}
@ -292,7 +292,7 @@ struct AnimationsUi;
fn manage_animation_ui(
mut events: EventReader<AssetEvent<AnimationClip>>,
root: Query<Entity, With<AnimationsUi>>,
root: Query<Entity, (With<AnimationsUi>, Without<UiRef<Handle<AnimationClip>>>)>,
mut commands: Commands,
gltfs: Res<Assets<Gltf>>,
registry: Res<AssetRegistry>,
@ -309,10 +309,6 @@ fn manage_animation_ui(
match gltfs.get(&gltf_handle.clone().typed::<Gltf>()) {
Some(gltf) => gltf.named_animations.iter().find_map(
|(name, animation_handle)| {
info!(
"animation_handle({:?}) == handle({:?})",
animation_handle, handle
);
(animation_handle == handle).then_some(name)
},
),
@ -342,7 +338,83 @@ fn manage_animation_ui(
struct AudioClipsUi;
/// Drag+Drop import Audio to editor
fn load_audio() {}
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<UiRef<Handle<AudioSource>>>)>,
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((
GameUiButton,
Name::new(name),
NodeBundle { ..default() },
AudioClipsUi,
AudioBundle {
source: handle.clone(),
settings: PlaybackSettings {
mode: bevy::audio::PlaybackMode::Loop,
paused: true,
..default()
},
},
))
.set_parent(root.single());
});
}
/// Play/Loop Audio
fn play_audio(
mut events: Query<
(&Interaction, &AudioSink, &mut UiElementState),
(Changed<Interaction>, With<AudioClipsUi>),
>,
) {
events
.iter_mut()
.for_each(|(interaction, sink, mut state)| match interaction {
Interaction::Pressed => {
sink.toggle();
*state = match *state {
UiElementState::Enabled => UiElementState::Active,
_ => UiElementState::Enabled,
}
}
_ => (),
});
}
/// Remove audio from editor
fn unload_audio() {}
@ -350,9 +422,6 @@ fn unload_audio() {}
/// Spawn Scene
fn spawn_scene() {}
/// Play/Loop Audio
fn play_audio() {}
/// Export level
fn export_level() {}

@ -1,3 +1,5 @@
/// TODO: Sorted list/set
///
use bevy::{prelude::*, window::PrimaryWindow};
pub struct GameUiPlugin;
@ -11,11 +13,21 @@ impl Plugin for GameUiPlugin {
manage_ui_set,
manage_ui_button,
manage_cursor,
manage_button_interaction,
),
);
}
}
/// Describes the state of an element
#[derive(Debug, Component)]
pub enum UiElementState {
Enabled,
Disabled,
Active,
Error,
}
/// GameUiList for holding ordered collections of objects
#[derive(Debug, Component)]
pub struct GameUiList;
@ -58,7 +70,7 @@ fn manage_ui_set(events: Query<(Entity, &Name), Added<GameUiSet>>, mut commands:
.entity(entity)
.insert(NodeBundle {
style: Style {
// width: Val::Px(100.0),
width: Val::Px(200.0),
margin: UiRect::all(Val::Px(2.0)),
padding: UiRect::all(Val::Px(2.0)),
border: UiRect::all(Val::Px(2.0)),
@ -87,7 +99,8 @@ fn manage_ui_button(events: Query<(Entity, &Name), Added<GameUiButton>>, mut com
events.iter().for_each(|(entity, name)| {
commands
.entity(entity)
.insert(ButtonBundle {
.insert((
ButtonBundle {
style: Style {
margin: UiRect::all(Val::Px(2.0)),
padding: UiRect::all(Val::Px(2.0)),
@ -98,7 +111,9 @@ fn manage_ui_button(events: Query<(Entity, &Name), Added<GameUiButton>>, mut com
background_color: BackgroundColor(Color::GREEN),
border_color: BorderColor(Color::BLACK),
..default()
})
},
UiElementState::Enabled,
))
.with_children(|parent| {
parent.spawn(TextBundle::from_section(name, TextStyle::default()));
});
@ -108,14 +123,38 @@ fn manage_ui_button(events: Query<(Entity, &Name), Added<GameUiButton>>, mut com
/// Manage the cursor icon for better immersion
fn manage_cursor(
mut primary_window: Query<&mut Window, With<PrimaryWindow>>,
events: Query<&Interaction, With<Interaction>>,
events: Query<&Interaction, (Changed<Interaction>, With<Button>)>,
) {
events.iter().for_each(|event| {
if !events.is_empty() {
let mut window = primary_window.single_mut();
window.cursor.icon = match event {
Interaction::Pressed => CursorIcon::Grabbing,
Interaction::Hovered => CursorIcon::Hand,
Interaction::None => CursorIcon::Default,
window.cursor.icon = events
.iter()
.find(|&event| *event != Interaction::None)
.map_or(CursorIcon::Default, |event| match event {
Interaction::Hovered | Interaction::Pressed => CursorIcon::Hand,
Interaction::None => CursorIcon::Help, // Shouldn't be reachable
});
}
}
fn manage_button_interaction(
mut events: Query<
(&Interaction, &UiElementState, &mut BackgroundColor),
(Changed<Interaction>, With<Button>),
>,
) {
events
.iter_mut()
.for_each(|(interaction, state, mut bg_color)| {
bg_color.0 = match state {
UiElementState::Enabled => match interaction {
Interaction::Pressed => Color::BLACK,
Interaction::Hovered => Color::ORANGE,
Interaction::None => Color::GREEN,
},
UiElementState::Active => Color::PINK,
UiElementState::Error => Color::RED,
UiElementState::Disabled => Color::GRAY,
}
});
}

Loading…
Cancel
Save