refactor some editor functions to files

Focusing on getting button -> event refactor.
As well as spawned thing -> button active refactor.

Both should allow levels to work propery, setting buttons active and
such.

Once that's working just iron out bugs like double spawn.
main
Elijah Voigt 2 years ago
parent 2433f6386d
commit 2b9f96d4b4

@ -2,6 +2,13 @@
//
// Editor for creating Monologue Trees levels
//
// REFACTOR:
// * Monologue -> Event -> Active -> UI
// * Scene -> Event -> Active -> UI
// * Animation -> Event -> Active -> UI
// * Font -> Event -> Active -> UI
// * Gltf -> Event -> Active -> UI
//
// BUGS:
// * When Handle<T> is loaded, the button for TargetAsset<T> should load as well
// * Exported level should preserve active camera
@ -18,12 +25,15 @@
use bevy::{
asset::{Asset, AssetLoader, Assets, ChangeWatcher, LoadContext, LoadedAsset},
audio::PlaybackMode,
gltf::Gltf,
prelude::*,
utils::{BoxedFuture, Duration},
};
use monologue_trees::{debug::*, ui};
use monologue_trees::{
debug::*,
editor::{assets::*, audio::*, *},
ui,
};
const WELCOME_MESSAGES: &'static [&'static str] = &[
"Welcome to the Monologue Trees editor!",
@ -82,7 +92,16 @@ fn main() {
(remove_scenes_ui, add_scenes_ui, control_active_scenes),
)
.add_systems(Update, (cameras_ui, manage_active_camera, fallback_camera))
.add_systems(Update, (audio_ui, play_audio, pause_audio, control_audio))
.add_systems(
Update,
(
audio_ui,
ui_control_audio,
ui_active::<AudioSource>,
ui_inactive::<AudioSource>,
control_audio,
),
)
.add_systems(
Update,
(
@ -167,9 +186,6 @@ fn main() {
.run();
}
#[derive(Resource, Default)]
pub struct AssetRegistry(Vec<HandleUntyped>);
#[derive(Debug, Component)]
pub struct TabRoot;
@ -177,10 +193,6 @@ pub struct TabRoot;
#[reflect(Component)]
pub struct LevelRoot;
#[derive(Debug, Component, Reflect, Default)]
#[reflect(Component)]
pub struct AudioRoot;
#[derive(Debug, Component)]
pub struct EditorCamera;
@ -271,46 +283,22 @@ fn initialize_ui(mut commands: Commands) {
ui::Sorting(2),
))
.with_children(|parent| {
content_containers.push(spawn_tab_container::<FontWidget>(
"Font",
parent,
ui::Select::Single,
));
content_containers.push(spawn_tab_container::<MonologueWidget>(
"Monologue",
parent,
ui::Select::Single,
));
content_containers.push(spawn_tab_container::<AudioWidget>(
"Audio",
parent,
ui::Select::Multi,
));
content_containers.push(spawn_tab_container::<LevelWidget>(
"Level",
parent,
ui::Select::Single,
));
content_containers.push(spawn_tab_container::<GltfWidget>(
"Gltf",
parent,
ui::Select::Single,
));
content_containers.push(spawn_tab_container::<SceneWidget>(
"Scene",
parent,
ui::Select::Single,
));
content_containers.push(spawn_tab_container::<CameraWidget>(
"Camera",
parent,
ui::Select::Single,
));
content_containers.push(spawn_tab_container::<AnimationWidget>(
"Animation",
parent,
ui::Select::Multi,
));
content_containers
.push(spawn_tab_container::<FontWidget>("Font", parent));
content_containers
.push(spawn_tab_container::<MonologueWidget>("Monologue", parent));
content_containers
.push(spawn_tab_container::<AudioWidget>("Audio", parent));
content_containers
.push(spawn_tab_container::<LevelWidget>("Level", parent));
content_containers
.push(spawn_tab_container::<GltfWidget>("Gltf", parent));
content_containers
.push(spawn_tab_container::<SceneWidget>("Scene", parent));
content_containers
.push(spawn_tab_container::<CameraWidget>("Camera", parent));
content_containers
.push(spawn_tab_container::<AnimationWidget>("Animation", parent));
});
// Container for tabs that open/close containers
@ -548,7 +536,6 @@ fn welcome_message(mut writer: EventWriter<ui::Alert>) {
fn spawn_tab_container<T: Default + Component>(
title: &'static str,
parent: &mut ChildBuilder,
select: ui::Select,
) -> (String, Entity) {
(
title.into(),
@ -571,277 +558,11 @@ fn spawn_tab_container<T: Default + Component>(
T::default(),
ui::Scroll,
Interaction::default(),
select,
))
.id(),
)
}
use audio::*;
mod audio {
use super::*;
#[derive(Debug, Component, Default)]
pub struct AudioWidget;
pub fn audio_ui(
mut events: EventReader<AssetEvent<AudioSource>>,
mut commands: Commands,
widget: Query<Entity, With<AudioWidget>>,
current: Query<(Entity, &ui::TargetAsset<AudioSource>)>,
server: Res<AssetServer>,
) {
events.iter().for_each(|event| match event {
AssetEvent::Created { handle } => {
info!("Asset created! {:?}", event);
create_asset_button(
&widget,
&mut commands,
ui::TargetAsset {
handle: handle.clone(),
},
get_asset_name(&server, handle.clone()),
None,
);
}
AssetEvent::Removed { handle } => {
info!("Asset removed! {:?}", event);
destroy_asset_button(
&current,
&mut commands,
&ui::TargetAsset {
handle: handle.clone(),
},
);
}
AssetEvent::Modified { handle } => {
info!("Asset modified! {:?}", event);
destroy_asset_button(
&current,
&mut commands,
&ui::TargetAsset {
handle: handle.clone(),
},
);
create_asset_button(
&widget,
&mut commands,
ui::TargetAsset {
handle: handle.clone(),
},
get_asset_name(&server, handle.clone()),
None,
);
}
});
}
#[derive(Debug, Event)]
pub enum ControlAudio {
Play(Handle<AudioSource>),
Loop(Handle<AudioSource>),
Stop(Handle<AudioSource>),
}
pub fn control_audio(
mut events: EventReader<ControlAudio>,
root: Query<Entity, With<AudioRoot>>,
mut commands: Commands,
sources: Query<(Entity, &Handle<AudioSource>), With<AudioSink>>,
) {
events.iter().for_each(|event| match event {
ControlAudio::Play(handle) => (),
ControlAudio::Loop(handle) => {
info!("Looping audio {:?}", handle);
let root = if let Ok(entity) = root.get_single() {
entity
} else {
commands.spawn(AudioRoot).id()
};
commands.entity(root).with_children(|parent| {
parent.spawn(AudioSourceBundle {
source: handle.clone(),
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
paused: false,
..default()
},
});
info!("Done spawning");
});
}
ControlAudio::Stop(handle) => {
info!("Stopping audio {:?}", handle);
sources
.iter()
.find_map(|(entity, source_handle)| {
if source_handle == handle {
Some(entity)
} else {
None
}
})
.iter()
.for_each(|&entity| {
commands.entity(entity).despawn_recursive();
info!("Done despawning");
});
}
});
}
pub fn play_audio(
events: Query<&ui::TargetAsset<AudioSource>, (With<Button>, Added<ui::Active>)>,
mut writer: EventWriter<ControlAudio>,
) {
events.iter().for_each(|ui::TargetAsset { handle }| {
writer.send(ControlAudio::Loop(handle.clone()));
})
}
pub fn pause_audio(
mut events: RemovedComponents<ui::Active>,
targets: Query<&ui::TargetAsset<AudioSource>>,
mut writer: EventWriter<ControlAudio>,
) {
// Iterate over the audio button events
events.iter().for_each(|entity| {
// Get the handle for the audio source we want to despawn
if let Ok(ui::TargetAsset { handle }) = targets.get(entity) {
writer.send(ControlAudio::Stop(handle.clone()));
}
});
}
}
use assets::*;
mod assets {
use super::*;
#[derive(Debug, Component)]
pub struct ReloadAssets;
pub fn reload_assets(
server: Res<AssetServer>,
mut registry: ResMut<AssetRegistry>,
mut writer: EventWriter<ui::Alert>,
) {
match server.load_folder(".") {
Ok(handles) => registry.0 = handles,
Err(e) => writer.send(ui::Alert::Warn(format!(
"Could not find `assets` folder!\n{:?}",
e
))),
}
}
pub fn get_asset_name<T: Asset>(server: &AssetServer, handle: Handle<T>) -> String {
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("???")
}
}
pub fn create_asset_button<A: Asset, C: Component>(
root: &Query<Entity, With<C>>,
commands: &mut Commands,
target: ui::TargetAsset<A>,
name: String,
font: Option<Handle<Font>>,
) -> Entity {
commands
.spawn((
target,
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()
},
border_color: Color::BLACK.into(),
..default()
},
ui::Title {
text: name,
font: font.clone(),
},
))
.set_parent(root.single())
.id()
}
pub fn create_entity_button<C: Component>(
root: &Query<Entity, With<C>>,
commands: &mut Commands,
target: ui::TargetEntity,
name: String,
) -> Entity {
commands
.spawn((
target,
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()
},
border_color: Color::BLACK.into(),
..default()
},
ui::Title {
text: name,
..default()
},
))
.set_parent(root.single())
.id()
}
pub fn destroy_asset_button<A: Asset>(
current: &Query<(Entity, &ui::TargetAsset<A>)>,
commands: &mut Commands,
target: &ui::TargetAsset<A>,
) {
if let Some(entity) = current.iter().find_map(|(entity, this)| {
if this.handle == target.handle {
Some(entity)
} else {
None
}
}) {
commands.entity(entity).despawn_recursive();
}
}
pub fn destroy_entity_button(
current: &Query<(Entity, &ui::TargetEntity)>,
commands: &mut Commands,
target: &ui::TargetEntity,
) {
if let Some(entity) = current.iter().find_map(|(entity, this)| {
if this.entity == target.entity {
Some(entity)
} else {
None
}
}) {
commands.entity(entity).despawn_recursive();
}
}
}
use gltf::*;
mod gltf {
use super::*;
@ -979,30 +700,34 @@ mod scenes {
// A scene button was marked inactive
removed.iter().for_each(|entity| {
// Get the handle associated with that button
if let Ok(ui::TargetAsset { handle }) = scene_refs.get(entity) {
if let Some(entity) = scenes.iter().find_map(|(entity, this_handle)| {
if this_handle == handle {
Some(entity)
} else {
None
}
}) {
commands.entity(entity).despawn_recursive();
}
}
scene_refs
.get(entity)
.iter()
.for_each(|ui::TargetAsset { handle }| {
scenes
.iter()
.find_map(|(entity, this_handle)| (this_handle == handle).then_some(entity))
.iter()
.for_each(|&entity| {
commands.entity(entity).despawn_recursive();
});
});
});
added.iter().for_each(|entity| {
if let Ok(ui::TargetAsset { handle }) = scene_refs.get(entity) {
info!("Spawning Scene {:?}", handle);
commands
.entity(level_root.single())
.with_children(|parent| {
parent.spawn(SceneBundle {
scene: handle.clone(),
..default()
scene_refs
.get(entity)
.iter()
.for_each(|ui::TargetAsset { handle }| {
info!("Spawning Scene {:?}", handle);
commands
.entity(level_root.single())
.with_children(|parent| {
parent.spawn(SceneBundle {
scene: handle.clone(),
..default()
});
});
});
}
});
});
}
}
@ -1058,11 +783,7 @@ mod animations {
.iter()
.flat_map(|(_, gltf)| gltf.named_animations.iter())
.filter_map(|(clip_name, handle)| {
if let Some(clip) = clips.get(&handle) {
Some((clip_name, handle, clip))
} else {
None
}
clips.get(&handle).map(|clip| (clip_name, handle, clip))
})
.filter(|(_, _, clip)| clip.compatible_with(player_name))
.for_each(|(clip_name, handle, _)| {

@ -0,0 +1,121 @@
use crate::ui;
use bevy::{asset::Asset, prelude::*};
#[derive(Resource, Default)]
pub struct AssetRegistry(pub Vec<HandleUntyped>);
#[derive(Debug, Component)]
pub struct ReloadAssets;
pub fn reload_assets(
server: Res<AssetServer>,
mut registry: ResMut<AssetRegistry>,
mut writer: EventWriter<ui::Alert>,
) {
match server.load_folder(".") {
Ok(handles) => registry.0 = handles,
Err(e) => writer.send(ui::Alert::Warn(format!(
"Could not find `assets` folder!\n{:?}",
e
))),
}
}
pub fn get_asset_name<T: Asset>(server: &AssetServer, handle: Handle<T>) -> String {
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("???")
}
}
pub fn create_asset_button<A: Asset, C: Component>(
root: &Query<Entity, With<C>>,
commands: &mut Commands,
target: ui::TargetAsset<A>,
name: String,
font: Option<Handle<Font>>,
) -> Entity {
commands
.spawn((
target,
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()
},
border_color: Color::BLACK.into(),
..default()
},
ui::Title {
text: name,
font: font.clone(),
},
))
.set_parent(root.single())
.id()
}
pub fn create_entity_button<C: Component>(
root: &Query<Entity, With<C>>,
commands: &mut Commands,
target: ui::TargetEntity,
name: String,
) -> Entity {
commands
.spawn((
target,
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()
},
border_color: Color::BLACK.into(),
..default()
},
ui::Title {
text: name,
..default()
},
))
.set_parent(root.single())
.id()
}
pub fn destroy_asset_button<A: Asset>(
current: &Query<(Entity, &ui::TargetAsset<A>)>,
commands: &mut Commands,
target: &ui::TargetAsset<A>,
) {
if let Some(entity) = current
.iter()
.find_map(|(entity, this)| (this.handle == target.handle).then_some(entity))
{
commands.entity(entity).despawn_recursive();
}
}
pub fn destroy_entity_button(
current: &Query<(Entity, &ui::TargetEntity)>,
commands: &mut Commands,
target: &ui::TargetEntity,
) {
if let Some(entity) = current
.iter()
.find_map(|(entity, this)| (this.entity == target.entity).then_some(entity))
{
commands.entity(entity).despawn_recursive();
}
}

@ -0,0 +1,138 @@
use crate::{editor::assets::*, ui};
use bevy::{audio::PlaybackMode, prelude::*};
#[derive(Debug, Component, Reflect, Default)]
#[reflect(Component)]
pub struct AudioRoot;
#[derive(Debug, Component, Default)]
pub struct AudioWidget;
pub fn audio_ui(
mut events: EventReader<AssetEvent<AudioSource>>,
mut commands: Commands,
widget: Query<Entity, With<AudioWidget>>,
current: Query<(Entity, &ui::TargetAsset<AudioSource>)>,
server: Res<AssetServer>,
) {
events.iter().for_each(|event| match event {
AssetEvent::Created { handle } => {
info!("Asset created! {:?}", event);
create_asset_button(
&widget,
&mut commands,
ui::TargetAsset {
handle: handle.clone(),
},
get_asset_name(&server, handle.clone()),
None,
);
}
AssetEvent::Removed { handle } => {
info!("Asset removed! {:?}", event);
destroy_asset_button(
&current,
&mut commands,
&ui::TargetAsset {
handle: handle.clone(),
},
);
}
AssetEvent::Modified { handle } => {
info!("Asset modified! {:?}", event);
destroy_asset_button(
&current,
&mut commands,
&ui::TargetAsset {
handle: handle.clone(),
},
);
create_asset_button(
&widget,
&mut commands,
ui::TargetAsset {
handle: handle.clone(),
},
get_asset_name(&server, handle.clone()),
None,
);
}
});
}
#[derive(Debug, Event)]
pub enum ControlAudio {
Loop(Handle<AudioSource>),
Stop(Handle<AudioSource>),
}
pub fn control_audio(
mut events: EventReader<ControlAudio>,
root: Query<Entity, With<AudioRoot>>,
mut commands: Commands,
sources: Query<(Entity, &Handle<AudioSource>), With<AudioSink>>,
) {
events.iter().for_each(|event| match event {
ControlAudio::Loop(handle) => {
info!("Looping audio {:?}", handle);
let root = if let Ok(entity) = root.get_single() {
entity
} else {
commands.spawn(AudioRoot).id()
};
commands.entity(root).with_children(|parent| {
parent.spawn(AudioSourceBundle {
source: handle.clone(),
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
paused: false,
..default()
},
});
info!("Done spawning");
});
}
ControlAudio::Stop(handle) => {
info!("Stopping audio {:?}", handle);
sources
.iter()
.find_map(|(entity, source_handle)| {
if source_handle == handle {
Some(entity)
} else {
None
}
})
.iter()
.for_each(|&entity| {
commands.entity(entity).despawn_recursive();
info!("Done despawning");
});
}
});
}
pub fn ui_control_audio(
events: Query<
(
&Interaction,
&ui::TargetAsset<AudioSource>,
Option<&ui::Active>,
),
(With<Button>, Changed<Interaction>),
>,
mut writer: EventWriter<ControlAudio>,
) {
events
.iter()
.filter_map(
|(interaction, ui::TargetAsset { handle }, active)| match interaction {
Interaction::Pressed => Some((handle, active)),
_ => None,
},
)
.for_each(|(handle, active)| match active {
Some(_) => writer.send(ControlAudio::Stop(handle.clone())),
None => writer.send(ControlAudio::Loop(handle.clone())),
});
}

@ -0,0 +1,47 @@
pub mod audio;
pub mod assets;
use crate::ui;
use bevy::{asset::Asset, prelude::*};
pub fn ui_active<T: Asset>(
events: Query<&Handle<T>, Added<Handle<T>>>,
buttons: Query<(Entity, &ui::TargetAsset<T>)>,
mut commands: Commands,
) {
events.iter().for_each(|this_handle| {
buttons
.iter()
.find_map(|(entity, ui::TargetAsset { handle })| {
if handle == this_handle {
Some(entity)
} else {
None
}
})
.and_then(|entity| {
commands.entity(entity).insert(ui::Active);
Some(())
});
});
}
pub fn ui_inactive<T: Asset>(
mut events: RemovedComponents<Handle<T>>,
targets: Query<(Entity, &ui::TargetAsset<T>), With<ui::Active>>,
sources: Query<&Handle<T>>,
mut commands: Commands,
) {
events.iter().for_each(|_| {
targets
.iter()
.find_map(|(entity, ui::TargetAsset { handle })| {
(!sources.iter().any(|this_handle| this_handle == handle)).then_some(entity)
})
.and_then(|entity| {
commands.entity(entity).remove::<ui::Active>();
Some(())
});
});
}

@ -3,3 +3,5 @@ pub mod debug;
pub mod text;
pub mod ui;
pub mod editor;

@ -452,26 +452,28 @@ mod buttons {
.get(parent.get())
.iter()
.for_each(|(select, children)| match interaction {
Interaction::Pressed => {
match active {
Some(_) => {
Interaction::Pressed => match select {
Select::None => (),
Select::Multi | Select::Action => {
if active.is_some() {
commands.entity(entity).remove::<Active>();
}
None => {
} else {
commands.entity(entity).insert(Active);
}
}
match select {
Select::Multi | Select::Action => (),
Select::Single => {
children.iter().filter(|&child| *child != entity).for_each(
|&child| {
commands.entity(child).remove::<Active>();
},
);
Select::Single => {
if active.is_some() {
commands.entity(entity).remove::<Active>();
} else {
commands.entity(entity).insert(Active);
}
children.iter().filter(|&child| *child != entity).for_each(
|&child| {
commands.entity(child).remove::<Active>();
},
);
}
}
},
// A silly hack to get actions to maintain the active tag for 1+ frames
Interaction::None => match select {
Select::Action => {

Loading…
Cancel
Save