Compare commits

..

No commits in common. '3b75fed684c70880285632d7ad48365a708b249f' and '27522e2209efb2619b69f8b158d3b7d6e020ef58' have entirely different histories.

@ -42,21 +42,12 @@
), ),
)), )),
"editor::LevelRoot": (), "editor::LevelRoot": (),
"bevy_hierarchy::components::children::Children": ([
198,
]),
}, },
), ),
2: ( 198: (
components: {
"editor::AudioRoot": (),
},
),
191: (
components: {
"bevy_asset::handle::Handle<bevy_audio::audio_source::AudioSource>": (
id: AssetPathId(((3014684909402542266), (8823233378563626002))),
),
},
),
200: (
components: { components: {
"bevy_render::view::visibility::Visibility": Inherited, "bevy_render::view::visibility::Visibility": Inherited,
"bevy_transform::components::transform::Transform": ( "bevy_transform::components::transform::Transform": (
@ -96,6 +87,10 @@
z: 0.0, z: 0.0,
), ),
)), )),
"bevy_hierarchy::components::parent::Parent": (1),
"bevy_hierarchy::components::children::Children": ([
199,
]),
"bevy_asset::handle::Handle<bevy_scene::scene::Scene>": ( "bevy_asset::handle::Handle<bevy_scene::scene::Scene>": (
id: AssetPathId(((13241355290950327508), (2370051114748836591))), id: AssetPathId(((13241355290950327508), (2370051114748836591))),
), ),

File diff suppressed because it is too large Load Diff

@ -1,155 +0,0 @@
use crate::editor::prelude::*;
#[derive(Debug, Component, Default)]
pub struct AnimationWidget;
#[derive(Debug, Component)]
pub struct AnimationPlayAll;
pub fn init_animations_ui(events: Query<Entity, Added<AnimationWidget>>, mut commands: Commands) {
events.iter().for_each(|entity| {
commands.entity(entity).with_children(|parent| {
parent.spawn((
AnimationPlayAll,
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: "Play All".into(),
..default()
},
));
});
})
}
/// When a new scene is loaded, add any newly compatible animations
/// TODO: Add target entity(s) too
pub fn add_animations_ui(
player_spawned: Query<&Name, Added<AnimationPlayer>>,
widget: Query<Entity, With<AnimationWidget>>,
mut commands: Commands,
gltfs: Res<Assets<Gltf>>,
clips: Res<Assets<AnimationClip>>,
) {
player_spawned.iter().for_each(|player_name| {
gltfs
.iter()
.flat_map(|(_, gltf)| gltf.named_animations.iter())
.filter_map(|(clip_name, handle)| {
clips.get(&handle).map(|clip| (clip_name, handle, clip))
})
.filter(|(_, _, clip)| clip.compatible_with(player_name))
.for_each(|(clip_name, handle, _)| {
create_asset_button(
&widget,
&mut commands,
ui::TargetAsset {
handle: handle.clone(),
},
clip_name.clone(),
None,
);
});
});
}
// When a scene is de-selected, remove any outdated animation options
pub fn remove_animations_ui(
mut removed_players: RemovedComponents<Handle<Scene>>,
current: Query<(Entity, &ui::TargetAsset<AnimationClip>)>,
clips: Res<Assets<AnimationClip>>,
targets: Query<(&AnimationPlayer, &Name)>,
mut commands: Commands,
) {
// For each removed scene
removed_players.iter().for_each(|_| {
// Iterate over the current animation buttons
current
.iter()
.filter(|(_, ui::TargetAsset { handle })| {
// Check if this clip is compatible with any remaining entities
// NOTE: We are checking this is *not* compatible with any entities
clips
.get(handle)
.map(|clip| !(targets.iter().any(|(_, name)| clip.compatible_with(name))))
.unwrap_or(true)
})
.for_each(|(_, ui::TargetAsset { handle })| {
// Destroy the buton if it is so
destroy_asset_button(
&current,
&mut commands,
&ui::TargetAsset {
handle: handle.clone(),
},
);
});
});
}
pub fn play_all_animations(
start: Query<Entity, (With<Button>, Added<ui::Active>)>,
mut stop: RemovedComponents<ui::Active>,
play_all_btn: Query<Entity, With<AnimationPlayAll>>,
clip_btns: Query<Entity, With<ui::TargetAsset<AnimationClip>>>,
mut commands: Commands,
) {
stop.iter()
.filter(|&entity| play_all_btn.contains(entity))
.for_each(|_| {
clip_btns.iter().for_each(|entity| {
commands.entity(entity).remove::<ui::Active>();
})
});
start
.iter()
.filter(|&entity| play_all_btn.contains(entity))
.for_each(|_| {
clip_btns.iter().for_each(|entity| {
commands.entity(entity).insert(ui::Active);
})
});
}
pub fn play_animation(
start: Query<Entity, (With<Button>, Added<ui::Active>)>,
mut stop: RemovedComponents<ui::Active>,
clip_refs: Query<&ui::TargetAsset<AnimationClip>>,
mut targets: Query<(&mut AnimationPlayer, &Name), With<Transform>>,
clips: Res<Assets<AnimationClip>>,
) {
stop.iter().for_each(|entity| {
if let Ok(ui::TargetAsset { handle }) = clip_refs.get(entity) {
let clip = clips.get(&handle).expect("Load animation clip");
targets
.iter_mut()
.filter(|(_, name)| clip.compatible_with(name))
.for_each(|(mut player, _)| {
player.pause();
})
}
});
start.iter().for_each(|entity| {
if let Ok(ui::TargetAsset { handle }) = clip_refs.get(entity) {
let clip = clips.get(&handle).expect("Load animation clip");
targets
.iter_mut()
.filter(|(_, name)| clip.compatible_with(name))
.for_each(|(mut player, _)| {
if player.is_paused() {
player.resume();
} else {
player.play(handle.clone()).repeat();
}
})
}
});
}

@ -1,55 +0,0 @@
use crate::editor::prelude::*;
// This sets buttons to active when their associated handle is spawned
pub fn sync_asset_buttons<T: Asset>(
events: Query<&Handle<T>, Added<Handle<T>>>,
buttons: Query<(Entity, &ui::TargetAsset<T>)>,
mut commands: Commands,
) {
events.iter().for_each(|this_handle| {
info!("Syncing {:?}", this_handle);
buttons
.iter()
.find_map(|(entity, ui::TargetAsset { handle })| {
if handle == this_handle {
Some(entity)
} else {
None
}
})
.iter()
.for_each(|&entity| {
commands.entity(entity).insert(ui::Active);
});
});
}
// Remove active when handle is despawned?
// ONLY IF there are no instances of that handle [!any(*)]
pub fn sync_remove_asset_buttons<T: Asset>(
mut events: RemovedComponents<Handle<T>>,
asset_entities: Query<&Handle<T>>,
buttons: Query<(Entity, &ui::TargetAsset<T>)>,
mut commands: Commands,
) {
events
.iter()
.find_map(|this_asset_entity| asset_entities.get(this_asset_entity).ok())
.iter()
.for_each(|this_handle| {
info!("Syncing removal of {:?}", this_handle);
buttons
.iter()
.find_map(|(entity, ui::TargetAsset { handle })| {
if handle == *this_handle {
Some(entity)
} else {
None
}
})
.iter()
.for_each(|&entity| {
commands.entity(entity).remove::<ui::Active>();
});
});
}

@ -1,120 +0,0 @@
use crate::editor::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();
}
}

@ -1,138 +0,0 @@
use crate::editor::prelude::*;
use bevy::audio::PlaybackMode;
#[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())),
});
}

@ -1,66 +0,0 @@
use crate::editor::prelude::*;
#[derive(Debug, Component)]
pub struct EditorCamera;
#[derive(Debug, Component, Default)]
pub struct CameraWidget;
pub fn cameras_ui(
mut added: Query<(Entity, &mut Camera, &Name), Added<Camera>>,
mut removed: RemovedComponents<Camera>,
editor_camera: Query<Entity, With<EditorCamera>>,
widget: Query<Entity, With<CameraWidget>>,
current: Query<(Entity, &ui::TargetEntity)>,
mut commands: Commands,
) {
removed.iter().for_each(|entity| {
info!("Destroy button for {:?}", entity);
destroy_entity_button(&current, &mut commands, &ui::TargetEntity { entity });
});
added.iter_mut().for_each(|(entity, mut camera, name)| {
info!("Camera added {:?} {:?}", entity, name);
create_entity_button(
&widget,
&mut commands,
ui::TargetEntity { entity },
name.as_str().into(),
);
camera.is_active = entity == editor_camera.single();
});
}
/// Set the camera active component based on button clicks
pub fn manage_active_camera(
events: Query<&ui::TargetEntity, Added<ui::Active>>,
mut cameras: Query<(Entity, &mut Camera)>,
) {
events.iter().for_each(|ui::TargetEntity { entity }| {
cameras.iter_mut().for_each(|(this_entity, mut camera)| {
if this_entity == *entity {
info!("Marking {:?} as active camera", entity);
camera.is_active = true;
} else {
info!("Marking {:?} as inactive camera", entity);
camera.is_active = false;
}
});
});
}
// In the event that an active camera is despawned, fall back to the editor camera
pub fn fallback_camera(
modified: Query<Entity, (Changed<Camera>, Without<EditorCamera>)>,
mut removed: RemovedComponents<Camera>,
other_cameras: Query<&Camera, Without<EditorCamera>>,
mut editor_camera: Query<&mut Camera, With<EditorCamera>>,
) {
// Any time a camera is modified
modified.iter().chain(removed.iter()).for_each(|_| {
// If no other cameras are active
if !other_cameras.iter().any(|camera| camera.is_active) {
// Make the editor camera active
editor_camera.single_mut().is_active = true;
}
})
}

@ -1,109 +0,0 @@
use crate::editor::prelude::*;
#[derive(Debug, Component, Default)]
pub struct FontWidget;
#[derive(Debug, Resource, Default)]
pub struct FontInfo {
pub default: Option<Handle<Font>>,
}
pub fn fonts_ui(
mut events: EventReader<AssetEvent<Font>>,
mut commands: Commands,
widget: Query<Entity, With<FontWidget>>,
current: Query<(Entity, &ui::TargetAsset<Font>)>,
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()),
Some(handle.clone()),
);
}
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()),
Some(handle.clone()),
);
}
});
}
pub fn ui_control_font(
events: Query<
(&Interaction, &ui::TargetAsset<Font>, Option<&ui::Active>),
(With<Button>, Changed<Interaction>),
>,
mut font: ResMut<FontInfo>,
) {
events
.iter()
.filter_map(
|(interaction, ui::TargetAsset { handle }, active)| match interaction {
Interaction::Pressed => Some((handle, active)),
_ => None,
},
)
.for_each(|(handle, active)| match active {
Some(_) => font.default = None,
None => font.default = Some(handle.clone()),
});
}
pub fn sync_font(
query: Query<(Entity, &ui::TargetAsset<Font>)>,
font: Res<FontInfo>,
mut commands: Commands,
) {
if font.is_changed() || font.is_added() {
match &font.default {
Some(current_handle) => {
query
.iter()
.for_each(|(entity, ui::TargetAsset { handle })| {
if *handle == *current_handle {
commands.entity(entity).insert(ui::Active);
} else {
commands.entity(entity).remove::<ui::Active>();
}
});
}
None => {
query.iter().for_each(|(entity, _)| {
commands.entity(entity).remove::<ui::Active>();
});
}
}
}
}

@ -1,68 +0,0 @@
use crate::editor::prelude::*;
#[derive(Debug, Component, Default)]
pub struct GltfWidget;
pub fn gltf_ui(
mut events: EventReader<AssetEvent<Gltf>>,
mut commands: Commands,
widget: Query<Entity, With<GltfWidget>>,
current: Query<(Entity, &ui::TargetAsset<Gltf>)>,
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,
);
}
});
}
pub fn control_active_gltf(
events: Query<Entity, (With<ui::TargetAsset<Gltf>>, Added<ui::Active>)>,
root: Query<Entity, With<LevelRoot>>,
mut commands: Commands,
) {
events.iter().for_each(|_| {
root.iter().for_each(|entity| {
commands.entity(entity).despawn_descendants();
});
});
}

@ -1,167 +0,0 @@
use crate::editor::prelude::*;
use bevy::tasks::IoTaskPool;
#[derive(Debug, Component, Reflect, Default)]
#[reflect(Component)]
pub struct LevelRoot;
pub type Level = DynamicScene;
#[derive(Debug, Component, Default)]
pub struct LevelWidget;
#[derive(Debug, Component)]
pub struct ExportLevel;
pub fn level_ui(
mut events: EventReader<AssetEvent<Level>>,
mut commands: Commands,
widget: Query<Entity, With<LevelWidget>>,
current: Query<(Entity, &ui::TargetAsset<Level>)>,
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,
);
}
});
}
pub fn load_level(
events: Query<
&ui::TargetAsset<DynamicScene>,
(Added<ui::Active>, With<ui::TargetAsset<DynamicScene>>),
>,
root: Query<Entity, With<LevelRoot>>,
mut commands: Commands,
) {
events.iter().for_each(|ui::TargetAsset { handle }| {
root.iter().for_each(|entity| {
commands.entity(entity).despawn_recursive();
});
commands.spawn(DynamicSceneBundle {
scene: handle.clone(),
..default()
});
});
}
pub fn export_level(
level_root: Query<Entity, With<LevelRoot>>,
audio_root: Query<Entity, With<AudioRoot>>,
children: Query<&Children>,
world: &World,
) {
let app_type_registry = world.resource::<AppTypeRegistry>().clone();
let mut builder = DynamicSceneBuilder::from_world(world.clone());
builder.deny_all_resources();
// Exclude computed visibility
builder.deny_all();
// Level administrivia
builder.allow::<LevelRoot>();
builder.allow::<AudioRoot>();
// TODO: Serialize Timeline
// Scene components
builder.allow::<Handle<Scene>>();
// Spatial components
builder.allow::<Transform>();
builder.allow::<GlobalTransform>();
builder.allow::<Visibility>();
// Audio components
builder.allow::<Handle<AudioSource>>();
builder.allow::<PlaybackSettings>();
// Text components
builder.allow::<Handle<Font>>();
builder.allow::<Handle<Monologue>>();
level_root.iter().for_each(|level| {
// Extract the level root
builder.extract_entity(level);
if let Ok(kids) = children.get(level) {
builder.extract_entities(kids.into_iter().map(|&e| e));
} else {
warn!("Level is empty!");
}
});
audio_root.iter().for_each(|audio| {
// Extract the level root
builder.extract_entity(audio);
if let Ok(kids) = children.get(audio) {
builder.extract_entities(kids.into_iter().map(|&e| e));
} else {
warn!("Audio is empty!");
}
});
let scene = builder.build();
let serialized = scene
.serialize_ron(&app_type_registry)
.expect("Serialize scene");
IoTaskPool::get()
.spawn(async move {
// Write the scene RON data to file
std::fs::write(format!("assets/output.scn.ron"), serialized.as_bytes())
.expect("Error while writing scene to file");
})
.detach();
}
pub fn rehydrate_level<W: Component, WO: Component + Default>(
events: Query<Entity, (Added<W>, Without<WO>)>,
mut commands: Commands,
) {
events.iter().for_each(|entity| {
commands.entity(entity).insert(WO::default());
});
}

@ -1,21 +0,0 @@
use crate::editor::prelude::*;
pub fn spot_light_force_shadows(mut spot_lights: Query<&mut SpotLight, Added<SpotLight>>) {
spot_lights.iter_mut().for_each(|mut light| {
light.shadows_enabled = true;
})
}
pub fn directional_light_force_shadows(
mut directional_lights: Query<&mut DirectionalLight, Added<DirectionalLight>>,
) {
directional_lights.iter_mut().for_each(|mut light| {
light.shadows_enabled = true;
})
}
pub fn point_light_force_shadows(mut point_lights: Query<&mut PointLight, Added<PointLight>>) {
point_lights.iter_mut().for_each(|mut light| {
light.shadows_enabled = true;
})
}

@ -1,59 +0,0 @@
pub mod animation;
pub mod asset_sync;
pub mod assets;
pub mod audio;
pub mod camera;
pub mod font;
pub mod gltf;
pub mod level;
pub mod lighting;
pub mod monologue;
mod prelude;
pub mod quit;
pub mod reset;
pub mod scene;
pub mod timeline;
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(())
});
});
}

@ -1,229 +0,0 @@
use crate::editor::prelude::*;
use bevy::{
asset::{AssetLoader, LoadContext, LoadedAsset},
reflect::{TypePath, TypeUuid},
ui::FocusPolicy,
utils::BoxedFuture,
};
use serde::Deserialize;
#[derive(Debug, Component, Default)]
pub struct MonologueWidget;
#[derive(Debug, Deserialize, TypeUuid, TypePath, PartialEq)]
#[uuid = "216a570b-d142-4026-baed-d7feb0250458"]
pub struct Monologue {
text: String,
}
#[derive(Debug, Event)]
pub enum ControlMonologue {
Show(Handle<Monologue>),
Hide,
}
#[derive(Debug, Component)]
pub struct MonologueModal;
#[derive(Debug, Component)]
pub struct MonologueContainer;
#[derive(Default)]
pub struct MonologueLoader;
impl AssetLoader for MonologueLoader {
fn load<'a>(
&'a self,
bytes: &'a [u8],
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<(), bevy::asset::Error>> {
Box::pin(async move {
let asset = Monologue {
text: String::from_utf8(bytes.to_vec())?,
};
load_context.set_default_asset(LoadedAsset::new(asset));
Ok(())
})
}
fn extensions(&self) -> &[&str] {
&["monologue.txt"]
}
}
pub fn init_texts_ui(mut commands: Commands) {
commands.spawn((
NodeBundle {
style: Style {
width: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
..default()
},
focus_policy: FocusPolicy::Pass,
..default()
},
MonologueContainer,
));
}
pub fn texts_ui(
mut events: EventReader<AssetEvent<Monologue>>,
mut commands: Commands,
widget: Query<Entity, With<MonologueWidget>>,
current: Query<(Entity, &ui::TargetAsset<Monologue>)>,
server: Res<AssetServer>,
) {
events.iter().for_each(|event| match event {
AssetEvent::Created { handle } => {
info!("Monologue created! {:?}", event);
create_asset_button(
&widget,
&mut commands,
ui::TargetAsset {
handle: handle.clone(),
},
get_asset_name(&server, handle.clone()),
None,
);
}
AssetEvent::Removed { handle } => {
info!("Monologue removed! {:?}", event);
destroy_asset_button(
&current,
&mut commands,
&ui::TargetAsset {
handle: handle.clone(),
},
);
}
AssetEvent::Modified { handle } => {
info!("Monologue 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,
);
}
});
}
pub fn control_monologue(
mut events: EventReader<ControlMonologue>,
monologues: Res<Assets<Monologue>>,
container: Query<Entity, With<MonologueContainer>>,
mut commands: Commands,
font: Res<FontInfo>, // Not convinced we need this...
) {
events.iter().for_each(|event| match event {
ControlMonologue::Hide => {
commands.entity(container.single()).despawn_descendants();
}
ControlMonologue::Show(handle) => {
monologues.get(handle).iter().for_each(|&monologue| {
commands
.entity(container.single())
.despawn_descendants()
.with_children(|parent| {
parent
.spawn(NodeBundle {
style: Style {
max_width: Val::Percent(50.0),
padding: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
border: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
})
.with_children(|parent| {
parent.spawn((
ui::TitleBarBase::new(Color::VIOLET).bundle(),
ui::Title {
text: "Monologue".into(),
..default()
},
ui::Close {
target: parent.parent_entity(),
},
ui::Sorting(0),
));
let style = match &font.default {
Some(handle) => TextStyle {
color: Color::BLACK.into(),
font_size: 16.0,
font: handle.clone(),
..default()
},
None => TextStyle {
color: Color::BLACK.into(),
font_size: 16.0,
..default()
},
};
parent.spawn((
TextBundle::from_section(monologue.text.clone(), style),
handle.clone(),
));
});
});
});
}
});
}
pub fn ui_control_monologue(
events: Query<
(
&Interaction,
&ui::TargetAsset<Monologue>,
Option<&ui::Active>,
),
(With<Button>, Changed<Interaction>),
>,
mut writer: EventWriter<ControlMonologue>,
) {
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(ControlMonologue::Hide),
None => writer.send(ControlMonologue::Show(handle.clone())),
});
}
// TODO: Sync Handle<Monologue> and TextStyle components to automagically generate and sync text
pub fn sync_monologue_font(
mut texts: Query<&mut Text, With<Handle<Monologue>>>,
font: Res<FontInfo>,
) {
if font.is_changed() || font.is_added() {
texts.iter_mut().for_each(|mut text| {
text.sections
.iter_mut()
.for_each(|section| match &font.default {
Some(handle) => section.style.font = handle.clone(),
None => section.style.font = Handle::default(),
});
});
}
}

@ -1,8 +0,0 @@
pub use crate::{
editor::{
animation::*, asset_sync::*, assets::*, audio::*, camera::*, font::*, gltf::*, level::*,
lighting::*, monologue::*, quit::*, reset::*, scene::*, timeline::*, *,
},
ui,
};
pub use bevy::{gltf::Gltf, prelude::*};

@ -1,10 +0,0 @@
use crate::editor::prelude::*;
use bevy::app::AppExit;
#[derive(Debug, Component, Default)]
pub struct QuitAction;
pub fn quit(mut exit: EventWriter<AppExit>) {
exit.send(AppExit);
}

@ -1,69 +0,0 @@
use crate::editor::prelude::*;
#[derive(Debug, Component)]
pub struct ClearLevel;
pub fn clear_level(
events: Query<Entity, (With<ClearLevel>, Added<ui::Active>)>,
actives: Query<
Entity,
(
With<ui::Active>,
Or<(
With<ui::TargetEntity>,
With<ui::TargetAsset<Gltf>>,
With<ui::TargetAsset<Scene>>,
With<ui::TargetAsset<AnimationClip>>,
With<ui::TargetAsset<AudioSource>>,
With<ui::TargetAsset<Monologue>>,
With<ui::TargetAsset<Font>>,
)>,
),
>,
root: Query<Entity, With<LevelRoot>>,
mut commands: Commands,
) {
events.iter().for_each(|_| {
actives.iter().for_each(|entity| {
commands.entity(entity).remove::<ui::Active>();
});
root.iter().for_each(|entity| {
commands.entity(entity).despawn_descendants();
});
})
}
#[derive(Debug, Component)]
pub struct ClearAssets;
pub fn clear_assets(
asset_holders: Query<
Entity,
Or<(
With<ui::TargetAsset<Gltf>>,
With<Handle<Gltf>>,
With<ui::TargetAsset<Scene>>,
With<Handle<Scene>>,
With<ui::TargetAsset<AnimationClip>>,
With<Handle<AnimationClip>>,
With<ui::TargetAsset<AudioSource>>,
With<Handle<AudioSource>>,
With<ui::TargetAsset<Monologue>>,
With<Handle<Monologue>>,
With<ui::TargetAsset<Font>>,
With<Handle<Font>>,
)>,
>,
mut registry: ResMut<AssetRegistry>,
mut commands: Commands,
) {
info!("Clearing assets");
// Clear buttons holding asset references
asset_holders
.iter()
.for_each(|entity| commands.entity(entity).despawn_recursive());
// Empty asset registry
registry.0.clear();
}

@ -1,93 +0,0 @@
use crate::editor::prelude::*;
#[derive(Debug, Component, Default)]
pub struct SceneWidget;
pub fn add_scenes_ui(
gltf_selected: Query<&ui::TargetAsset<Gltf>, Added<ui::Active>>,
mut commands: Commands,
gltfs: Res<Assets<Gltf>>,
widget: Query<Entity, With<SceneWidget>>,
) {
gltf_selected.iter().for_each(|ui::TargetAsset { handle }| {
if let Some(gltf) = gltfs.get(&handle.clone()) {
gltf.named_scenes.iter().for_each(|(name, handle)| {
create_asset_button(
&widget,
&mut commands,
ui::TargetAsset {
handle: handle.clone(),
},
name.clone(),
None,
);
})
}
});
}
pub fn remove_scenes_ui(
mut gltf_unselected: RemovedComponents<ui::Active>,
target_assets: Query<&ui::TargetAsset<Gltf>>,
current: Query<(Entity, &ui::TargetAsset<Scene>)>,
gltfs: Res<Assets<Gltf>>,
mut commands: Commands,
) {
gltf_unselected
.iter()
.filter_map(|entity| target_assets.get(entity).ok())
.filter_map(|ui::TargetAsset { handle }| gltfs.get(handle))
.for_each(|gltf| {
gltf.scenes.iter().for_each(|handle| {
destroy_asset_button(
&current,
&mut commands,
&ui::TargetAsset {
handle: handle.clone(),
},
);
});
});
}
pub fn control_active_scenes(
added: Query<Entity, (With<Button>, Added<ui::Active>)>,
mut removed: RemovedComponents<ui::Active>,
scene_refs: Query<&ui::TargetAsset<Scene>>,
scenes: Query<(Entity, &Handle<Scene>)>,
level_root: Query<Entity, With<LevelRoot>>,
mut commands: Commands,
) {
// A scene button was marked inactive
removed.iter().for_each(|entity| {
// Get the handle associated with that button
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| {
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()
});
});
});
});
}

@ -1,325 +0,0 @@
use crate::editor::prelude::*;
/// Timeline widget marker
#[derive(Debug, Component)]
pub struct TimelineWidget;
/// Add Epoch component, used on a button to trigger a new epoch addition
#[derive(Debug, Component)]
pub struct AddEpoch;
/// Epoch ID Component
#[derive(Debug, Reflect, Component, Clone)]
pub struct EpochId {
id: usize,
}
/// Epoch GLTF Component
#[derive(Debug, Reflect, Component, Clone)]
pub struct EpochGltf {
gltf: Handle<Gltf>,
}
/// Epoch Scene Component
#[derive(Debug, Reflect, Component, Clone)]
pub struct EpochScene {
scene: Handle<Scene>,
}
/// Epoch Camera Component, marking the current camera
#[derive(Debug, Reflect, Component, Clone)]
pub struct EpochCamera {
camera: Entity,
}
/// Epoch music component, marking the opening track for this epoch
#[derive(Debug, Reflect, Default, Component, Clone)]
pub struct EpochMusic {
music: Handle<AudioSource>,
}
/// Epoch monologue, marking the dialog spoken this epoch
#[derive(Debug, Reflect, Component, Clone)]
pub struct EpochMonologue {
monologue: Handle<Monologue>,
}
/// Epoch font, marking the font used for this epoch's monologue
#[derive(Debug, Reflect, Component, Clone)]
pub struct EpochFont {
font: Handle<Font>,
}
/// A vector of audios looping this epoch as background tracks
#[derive(Debug, Reflect, Component, Clone)]
pub struct EpochSfx {
sfx: Vec<Handle<AudioSource>>,
}
/// Epoch animations, looping this epoch
#[derive(Debug, Reflect, Component, Clone)]
pub struct EpochAnimations {
animations: Vec<Handle<AnimationClip>>,
}
/// System for adding an epoch to the level's timeline
/// Triggered when a button with the AddEpoch marker is Active
pub fn add_timeline_epoch(
root: Query<(Entity, &Children), With<TimelineWidget>>,
mut commands: Commands,
) {
info!("Adding timeline epoch");
root.iter().for_each(|(entity, children)| {
let id = children.iter().len();
let name = format!("{}", id);
commands.entity(entity).with_children(|parent| {
parent.spawn((
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()
},
EpochId { id },
));
});
});
}
/// Set the GLTF for the current epoch
pub fn set_epoch_gltf(
events: Query<&ui::TargetAsset<Gltf>, Added<ui::Active>>,
active_epoch: Query<Entity, (With<ui::Active>, With<EpochId>)>,
mut commands: Commands,
) {
// Each time a GLTF is selected in the editor
events.iter().for_each(|ui::TargetAsset { handle }| {
// Iterate over all (0 or 1) active epochs
active_epoch.iter().for_each(|entity| {
// Set the GLTF (overwrite existing GLTF selections)
commands.entity(entity).insert(EpochGltf {
gltf: handle.clone(),
});
// TODO: Unset Scene, Camera, Animations
});
});
}
pub fn load_epoch_gltf(events: Query<Option<&EpochGltf>, (Added<ui::Active>, With<EpochId>)>) {
events.iter().for_each(|epoch_gltf| {
warn!("TODO: Load epoch GLTF!");
})
}
pub fn set_epoch_scene(
events: Query<&ui::TargetAsset<Scene>, Added<ui::Active>>,
active_epoch: Query<Entity, (With<ui::Active>, With<EpochId>)>,
mut commands: Commands,
) {
// Each time a Scene is selected in the editor
events.iter().for_each(|ui::TargetAsset { handle }| {
// Iterate over all (0 or 1) active epochs
active_epoch.iter().for_each(|entity| {
// Set the Scene (overwrite existing Scene selections)
commands.entity(entity).insert(EpochScene {
scene: handle.clone(),
});
});
});
}
pub fn load_epoch_scene(events: Query<Option<&EpochScene>, (Added<ui::Active>, With<EpochId>)>) {
events.iter().for_each(|epoch_scene| {
warn!("TODO: Load epoch Scene!");
})
}
pub fn set_epoch_camera(
events: Query<&ui::TargetEntity, Added<ui::Active>>,
active_epoch: Query<Entity, (With<ui::Active>, With<EpochId>)>,
mut commands: Commands,
) {
// Each time a Scene is selected in the editor
events.iter().for_each(|ui::TargetEntity { entity }| {
// Iterate over all (0 or 1) active epochs
active_epoch.iter().for_each(|this_entity| {
// Set the Scene (overwrite existing Scene selections)
commands
.entity(this_entity)
.insert(EpochCamera { camera: *entity });
});
});
}
pub fn load_epoch_camera(events: Query<Option<&EpochCamera>, (Added<ui::Active>, With<EpochId>)>) {
events.iter().for_each(|epoch_camera| {
warn!("TODO: Load epoch Camera");
})
}
pub fn set_epoch_music(
events: Query<&ui::TargetAsset<AudioSource>, Added<ui::Active>>,
active_epoch: Query<Entity, (With<ui::Active>, With<EpochId>)>,
mut commands: Commands,
) {
// Each time a Scene is selected in the editor
events.iter().for_each(|ui::TargetAsset { handle }| {
info!("TODO: Select scene music");
// // Iterate over all (0 or 1) active epochs
// active_epoch.iter().for_each(|entity| {
// // Set the Scene (overwrite existing Scene selections)
// commands.entity(entity).insert(EpochMusic { music: handle.clone() });
// });
});
}
pub fn load_epoch_music(events: Query<Option<&EpochMusic>, (Added<ui::Active>, With<EpochId>)>) {
events.iter().for_each(|epoch_music| {
warn!("TODO: Load epoch music!");
})
}
pub fn set_epoch_monologue(
events: Query<&ui::TargetAsset<Monologue>, Added<ui::Active>>,
active_epoch: Query<Entity, (With<ui::Active>, With<EpochId>)>,
mut commands: Commands,
) {
// Each time a Scene is selected in the editor
events.iter().for_each(|ui::TargetAsset { handle }| {
// Iterate over all (0 or 1) active epochs
active_epoch.iter().for_each(|entity| {
// Set the Scene (overwrite existing Scene selections)
commands.entity(entity).insert(EpochMonologue {
monologue: handle.clone(),
});
});
});
}
pub fn load_epoch_monologue(
events: Query<Option<&EpochMonologue>, (Added<ui::Active>, With<EpochId>)>,
) {
events.iter().for_each(|epoch_monologue| {
warn!("TODO: unset epoch Monologue!");
epoch_monologue
.iter()
.for_each(|EpochMonologue { monologue }| {
warn!("TODO: Set level epoch");
});
});
}
pub fn set_epoch_font(
events: Query<&ui::TargetAsset<Font>, Added<ui::Active>>,
active_epoch: Query<Entity, (With<ui::Active>, With<EpochId>)>,
mut commands: Commands,
) {
// Each time a Scene is selected in the editor
events.iter().for_each(|ui::TargetAsset { handle }| {
// Iterate over all (0 or 1) active epochs
active_epoch.iter().for_each(|entity| {
// Set the Scene (overwrite existing Scene selections)
commands.entity(entity).insert(EpochFont {
font: handle.clone(),
});
});
});
}
pub fn load_epoch_font(
events: Query<Option<&EpochFont>, (Added<ui::Active>, With<EpochId>)>,
mut font_info: ResMut<FontInfo>,
) {
events.iter().for_each(|epoch_font| {
font_info.default = epoch_font.map(|EpochFont { font }| font.clone());
});
}
pub fn set_epoch_sfx(
events: Query<&ui::TargetAsset<AudioSource>, Added<ui::Active>>,
mut active_epoch: Query<(Entity, Option<&mut EpochSfx>), (With<ui::Active>, With<EpochId>)>,
mut commands: Commands,
) {
// Each time a Scene is selected in the editor
events.iter().for_each(|ui::TargetAsset { handle }| {
// Iterate over all (0 or 1) active epochs
active_epoch.iter_mut().for_each(|(entity, maybe_sfx)| {
info!("Adding sfx {:?} to epoch {:?}", maybe_sfx, entity);
if let Some(mut epoch_sfx) = maybe_sfx {
epoch_sfx.sfx.push(handle.clone());
} else {
// Set the Scene (overwrite existing Scene selections)
commands.entity(entity).insert(EpochSfx {
sfx: vec![handle.clone()],
});
}
});
});
}
pub fn load_epoch_sfx(
added: Query<Entity, (Added<ui::Active>, With<EpochId>)>,
mut removed: RemovedComponents<ui::Active>,
epoch_sfx: Query<&EpochSfx>,
mut writer: EventWriter<ControlAudio>,
) {
removed.iter().for_each(|entity| {
epoch_sfx.get(entity).iter().for_each(|EpochSfx { sfx }| {
sfx.iter().for_each(|handle| {
writer.send(ControlAudio::Stop(handle.clone()));
});
});
});
added.iter().for_each(|entity| {
epoch_sfx.get(entity).iter().for_each(|EpochSfx { sfx }| {
sfx.iter().for_each(|handle| {
writer.send(ControlAudio::Loop(handle.clone()));
});
});
});
}
pub fn set_epoch_animations(
events: Query<&ui::TargetAsset<AnimationClip>, Added<ui::Active>>,
mut active_epoch: Query<
(Entity, Option<&mut EpochAnimations>),
(With<ui::Active>, With<EpochId>),
>,
mut commands: Commands,
) {
// Each time a Scene is selected in the editor
events.iter().for_each(|ui::TargetAsset { handle }| {
// Iterate over all (0 or 1) active epochs
active_epoch
.iter_mut()
.for_each(|(entity, maybe_animations)| {
if let Some(mut epoch_animations) = maybe_animations {
epoch_animations.animations.push(handle.clone());
} else {
// Set the Scene (overwrite existing Scene selections)
commands.entity(entity).insert(EpochAnimations {
animations: vec![handle.clone()],
});
}
});
});
}
pub fn load_epoch_animations(
events: Query<Option<&EpochAnimations>, (Added<ui::Active>, With<EpochId>)>,
) {
events.iter().for_each(|epoch_animations| {
warn!("TODO: Load epoch Animations!");
})
}

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

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

Loading…
Cancel
Save