Moving code around

This might be premature, but *shrug* whatever.
main
Elijah Voigt 2 years ago
parent 3b75fed684
commit 7621db220e

@ -1,105 +0,0 @@
(
resources: {},
entities: {
1: (
components: {
"bevy_render::view::visibility::Visibility": Inherited,
"bevy_transform::components::transform::Transform": (
translation: (
x: 0.0,
y: 0.0,
z: 0.0,
),
rotation: (0.0, 0.0, 0.0, 1.0),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_transform::components::global_transform::GlobalTransform": ((
matrix3: (
x_axis: (
x: 1.0,
y: 0.0,
z: 0.0,
),
y_axis: (
x: 0.0,
y: 1.0,
z: 0.0,
),
z_axis: (
x: 0.0,
y: 0.0,
z: 1.0,
),
),
translation: (
x: 0.0,
y: 0.0,
z: 0.0,
),
)),
"editor::LevelRoot": (),
},
),
2: (
components: {
"editor::AudioRoot": (),
},
),
191: (
components: {
"bevy_asset::handle::Handle<bevy_audio::audio_source::AudioSource>": (
id: AssetPathId(((3014684909402542266), (8823233378563626002))),
),
},
),
200: (
components: {
"bevy_render::view::visibility::Visibility": Inherited,
"bevy_transform::components::transform::Transform": (
translation: (
x: 0.0,
y: 0.0,
z: 0.0,
),
rotation: (0.0, 0.0, 0.0, 1.0),
scale: (
x: 1.0,
y: 1.0,
z: 1.0,
),
),
"bevy_transform::components::global_transform::GlobalTransform": ((
matrix3: (
x_axis: (
x: 1.0,
y: 0.0,
z: 0.0,
),
y_axis: (
x: 0.0,
y: 1.0,
z: 0.0,
),
z_axis: (
x: 0.0,
y: 0.0,
z: 1.0,
),
),
translation: (
x: 0.0,
y: 0.0,
z: 0.0,
),
)),
"bevy_asset::handle::Handle<bevy_scene::scene::Scene>": (
id: AssetPathId(((13241355290950327508), (2370051114748836591))),
),
},
),
},
)

@ -24,26 +24,7 @@
// * (???) Better handle hide/close monologue
use bevy::{asset::ChangeWatcher, gltf::Gltf, prelude::*, utils::Duration};
use monologue_trees::{
debug::*,
editor::{
animation::*, asset_sync::*, assets::*, audio::*, camera::*, font::*, gltf::*, level::*,
lighting::*, monologue::*, quit::*, reset::*, scene::*, timeline::*, *,
},
ui,
};
const WELCOME_MESSAGES: &'static [&'static str] = &[
"Welcome to the Monologue Trees editor!",
"Import assets by dragging and dropping files or folders into the editor!",
concat!(
"Supported file types (for now):\n",
"* 3D: .gltf, .glb\n",
"* Audio: .ogg\n",
"* Font: .ttf, .otf\n",
"* Monologues: .monologue.txt",
),
];
use monologue_trees::{debug::*, editor::plugin::EditorPlugin, ui};
fn main() {
App::new()
@ -65,493 +46,7 @@ fn main() {
ui::GameUiPlugin {
enable_alerts: true,
},
EditorPlugin::default(),
))
.register_type::<LevelRoot>()
.register_type::<AudioRoot>()
.init_resource::<AssetRegistry>()
.init_resource::<FontInfo>()
.add_asset::<Monologue>()
.init_asset_loader::<MonologueLoader>()
.add_event::<ControlAudio>()
.add_event::<ControlMonologue>()
.add_systems(Startup, (initialize_ui, init_texts_ui, welcome_message))
.add_systems(Update, quit.run_if(ui::activated::<QuitAction>))
.add_systems(
Update,
(
init_animations_ui,
remove_animations_ui,
add_animations_ui,
play_all_animations,
play_animation,
),
)
.add_systems(
Update,
(remove_scenes_ui, add_scenes_ui, control_active_scenes),
)
.add_systems(Update, (cameras_ui, manage_active_camera, fallback_camera))
.add_systems(
Update,
(
audio_ui,
ui_control_audio,
ui_active::<AudioSource>,
ui_inactive::<AudioSource>,
control_audio,
),
)
.add_systems(
Update,
(
gltf_ui,
texts_ui,
control_active_gltf,
control_monologue,
ui_control_monologue,
ui_active::<Monologue>,
ui_inactive::<Monologue>,
sync_monologue_font,
),
)
.add_systems(Update, (fonts_ui, ui_control_font, sync_font))
.add_systems(Startup, reload_assets)
.add_systems(
Update,
(
reload_assets.run_if(ui::activated::<ReloadAssets>),
clear_assets.run_if(ui::activated::<ClearAssets>),
),
)
.add_systems(
Update,
(
point_light_force_shadows,
spot_light_force_shadows,
directional_light_force_shadows,
),
)
.add_systems(Update, clear_level)
.add_systems(
Update,
(
level_ui,
load_level,
export_level.run_if(ui::activated::<ExportLevel>),
rehydrate_level::<Visibility, ComputedVisibility>,
rehydrate_level::<Handle<AudioSource>, PlaybackSettings>,
),
)
.add_systems(
Update,
(
sync_asset_buttons::<Font>,
sync_remove_asset_buttons::<Font>,
sync_asset_buttons::<AudioSource>,
sync_remove_asset_buttons::<AudioSource>,
sync_asset_buttons::<Monologue>,
sync_remove_asset_buttons::<Monologue>,
sync_asset_buttons::<Level>,
sync_remove_asset_buttons::<Level>,
sync_asset_buttons::<Gltf>,
sync_remove_asset_buttons::<Gltf>,
sync_asset_buttons::<Scene>,
sync_remove_asset_buttons::<Scene>,
sync_asset_buttons::<AnimationClip>,
sync_remove_asset_buttons::<AnimationClip>,
),
)
.add_systems(
Update,
(
add_timeline_epoch.run_if(ui::activated::<AddEpoch>),
set_epoch_gltf,
load_epoch_gltf,
set_epoch_scene,
load_epoch_scene,
set_epoch_camera,
load_epoch_camera,
set_epoch_music,
load_epoch_music,
set_epoch_monologue,
load_epoch_monologue,
set_epoch_font,
load_epoch_font,
set_epoch_sfx,
load_epoch_sfx,
set_epoch_animations,
load_epoch_animations,
),
)
.run();
}
#[derive(Debug, Component)]
pub struct TabRoot;
fn initialize_ui(mut commands: Commands) {
// Empty entity for populating the level being edited
commands.spawn((SpatialBundle { ..default() }, LevelRoot));
commands.spawn(AudioRoot);
commands.spawn((
Camera2dBundle { ..default() },
UiCameraConfig { show_ui: true },
Name::new("Editor Camera"),
EditorCamera,
ui::Active,
));
let base_style = Style {
border: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
overflow: Overflow::clip(),
flex_direction: FlexDirection::Column,
..default()
};
let simple_button = ButtonBundle {
style: Style {
..base_style.clone()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
};
// Assets widget
commands
.spawn(NodeBundle {
style: Style {
top: Val::Px(0.0),
left: Val::Px(0.0),
position_type: PositionType::Absolute,
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
})
.with_children(|parent| {
let container = parent
.spawn((NodeBundle {
style: Style {
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Row,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
},))
.with_children(|parent| {
// HACK: This is super janky but I think we need it like this for UI layout rules
let mut content_containers: Vec<(String, Entity)> = Vec::new();
// Containers with asset content
parent
.spawn((
NodeBundle {
style: Style {
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
overflow: Overflow::clip(),
justify_content: JustifyContent::FlexStart,
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
},
ui::Sorting(2),
))
.with_children(|parent| {
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
parent
.spawn((
NodeBundle {
style: Style {
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
},
ui::Sorting(1),
ui::Select::Single,
))
.with_children(|parent| {
content_containers.iter().enumerate().for_each(
|(i, (name, target))| {
parent.spawn((
simple_button.clone(),
ui::Title {
text: name.clone(),
..default()
},
ui::Collapse { target: *target },
ui::Sorting(i as u8),
));
},
);
});
})
.id();
parent.spawn((
ui::TitleBarBase::new(Color::WHITE).bundle(),
ui::Title {
text: "Assets".into(),
..default()
},
ui::Minimize { target: container },
ui::Sorting(0),
));
});
// Actions widget
commands
.spawn(NodeBundle {
style: Style {
bottom: Val::Px(0.0),
left: Val::Px(0.0),
position_type: PositionType::Absolute,
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
})
.with_children(|parent| {
let container = parent
.spawn((
NodeBundle {
style: Style {
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
},
ui::Sorting(99),
ui::Select::Action,
))
.with_children(|parent| {
parent.spawn((
simple_button.clone(),
ReloadAssets,
ui::Sorting(1),
ui::Title {
text: "Reload Assets".into(),
..default()
},
));
parent.spawn((
simple_button.clone(),
ClearAssets,
ui::Sorting(2),
ui::Title {
text: "Clear Assets".into(),
..default()
},
));
parent.spawn((
simple_button.clone(),
ExportLevel,
ui::Sorting(3),
ui::Title {
text: "Export Level".into(),
..default()
},
));
parent.spawn((
simple_button.clone(),
ClearLevel,
ui::Sorting(3),
ui::Title {
text: "Clear Level".into(),
..default()
},
));
parent.spawn((
simple_button.clone(),
QuitAction,
ui::Sorting(3),
ui::Title {
text: "Quit".into(),
..default()
},
));
})
.id();
parent.spawn((
ui::TitleBarBase::new(Color::WHITE).bundle(),
ui::Title {
text: "Actions".into(),
..default()
},
ui::Minimize { target: container },
ui::Sorting(0),
));
});
// Actions widget
commands
.spawn(NodeBundle {
style: Style {
bottom: Val::Px(0.0),
right: Val::Px(0.0),
position_type: PositionType::Absolute,
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
})
.with_children(|parent| {
let container = parent
.spawn((
NodeBundle {
style: Style {
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Row,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
},
ui::Sorting(99),
ui::Select::Single,
TimelineWidget,
))
.with_children(|parent| {
// "Add Epoch" button
parent
.spawn((
NodeBundle {
style: Style {
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Row,
align_items: AlignItems::Center,
justify_items: JustifyItems::Center,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
},
ui::Select::Action,
))
.with_children(|parent| {
parent.spawn((
simple_button.clone(),
AddEpoch,
ui::Title {
text: "+".into(),
..default()
},
));
});
})
.id();
parent.spawn((
ui::TitleBarBase::new(Color::WHITE).bundle(),
ui::Title {
text: "Timeline".into(),
..default()
},
ui::Minimize { target: container },
ui::Sorting(0),
));
});
}
fn welcome_message(mut writer: EventWriter<ui::Alert>) {
WELCOME_MESSAGES
.iter()
.for_each(|&msg| writer.send(ui::Alert::Info(msg.into())))
}
fn spawn_tab_container<T: Default + Component>(
title: &'static str,
parent: &mut ChildBuilder,
) -> (String, Entity) {
(
title.into(),
// Content node
parent
.spawn((
NodeBundle {
style: Style {
display: Display::None,
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
},
T::default(),
ui::Scroll,
Interaction::default(),
))
.id(),
)
}

@ -1,5 +1,22 @@
use crate::editor::prelude::*;
#[derive(Debug, Default)]
pub struct EditorAnimationPlugin;
impl Plugin for EditorAnimationPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, sync_asset_buttons::<AnimationClip>)
.add_systems(Update, sync_remove_asset_buttons::<AnimationClip>)
.add_systems(Update, set_epoch_animations)
.add_systems(Update, load_epoch_animations)
.add_systems(Update, init_animations_ui)
.add_systems(Update, remove_animations_ui)
.add_systems(Update, add_animations_ui)
.add_systems(Update, play_all_animations)
.add_systems(Update, play_animation);
}
}
#[derive(Debug, Component, Default)]
pub struct AnimationWidget;

@ -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,5 +1,17 @@
use crate::editor::prelude::*;
#[derive(Debug, Default)]
pub struct EditorAssetsPlugin;
impl Plugin for EditorAssetsPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, reload_assets)
.add_systems(Update, reload_assets.run_if(ui::activated::<ReloadAssets>))
.init_resource::<AssetRegistry>()
.add_systems(Update, clear_assets.run_if(ui::activated::<ClearAssets>));
}
}
#[derive(Resource, Default)]
pub struct AssetRegistry(pub Vec<HandleUntyped>);
@ -118,3 +130,92 @@ pub fn destroy_entity_button(
commands.entity(entity).despawn_recursive();
}
}
// 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>();
});
});
}
#[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,6 +1,23 @@
use crate::editor::prelude::*;
use bevy::audio::PlaybackMode;
#[derive(Debug, Default)]
pub struct EditorAudioPlugin;
impl Plugin for EditorAudioPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, audio_ui)
.add_systems(Update, ui_control_audio)
.add_systems(Update, ui_active::<AudioSource>)
.add_systems(Update, ui_inactive::<AudioSource>)
.add_systems(Update, control_audio)
.add_systems(Update, sync_asset_buttons::<AudioSource>)
.register_type::<AudioRoot>()
.add_event::<ControlAudio>()
.add_systems(Update, sync_remove_asset_buttons::<AudioSource>);
}
}
#[derive(Debug, Component, Reflect, Default)]
#[reflect(Component)]
pub struct AudioRoot;

@ -1,5 +1,16 @@
use crate::editor::prelude::*;
#[derive(Debug, Default)]
pub struct EditorCameraPlugin;
impl Plugin for EditorCameraPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, cameras_ui)
.add_systems(Update, manage_active_camera)
.add_systems(Update, fallback_camera);
}
}
#[derive(Debug, Component)]
pub struct EditorCamera;

@ -1,5 +1,19 @@
use crate::editor::prelude::*;
#[derive(Debug, Default)]
pub struct EditorFontPlugin;
impl Plugin for EditorFontPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, sync_asset_buttons::<Font>)
.add_systems(Update, sync_remove_asset_buttons::<Font>)
.add_systems(Update, fonts_ui)
.add_systems(Update, ui_control_font)
.init_resource::<FontInfo>()
.add_systems(Update, sync_font);
}
}
#[derive(Debug, Component, Default)]
pub struct FontWidget;

@ -1,5 +1,16 @@
use crate::editor::prelude::*;
#[derive(Debug, Default)]
pub struct EditorGltfPlugin;
impl Plugin for EditorGltfPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, sync_asset_buttons::<Gltf>)
.add_systems(Update, sync_remove_asset_buttons::<Gltf>)
.add_systems(Update, gltf_ui);
}
}
#[derive(Debug, Component, Default)]
pub struct GltfWidget;

@ -1,6 +1,25 @@
use crate::editor::prelude::*;
use bevy::tasks::IoTaskPool;
#[derive(Debug, Default)]
pub struct EditorLevelPlugin;
impl Plugin for EditorLevelPlugin {
fn build(&self, app: &mut App) {
app.register_type::<LevelRoot>()
.add_systems(Update, level_ui)
.add_systems(Update, load_level)
.add_systems(Update, export_level.run_if(ui::activated::<ExportLevel>))
.add_systems(Update, rehydrate_level::<Visibility, ComputedVisibility>)
.add_systems(
Update,
rehydrate_level::<Handle<AudioSource>, PlaybackSettings>,
)
.add_systems(Update, sync_asset_buttons::<Level>)
.add_systems(Update, sync_remove_asset_buttons::<Level>);
}
}
#[derive(Debug, Component, Reflect, Default)]
#[reflect(Component)]
pub struct LevelRoot;
@ -165,3 +184,36 @@ pub fn rehydrate_level<W: Component, WO: Component + Default>(
commands.entity(entity).insert(WO::default());
});
}
#[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();
});
})
}

@ -1,5 +1,16 @@
use crate::editor::prelude::*;
#[derive(Debug, Default)]
pub struct EditorLightingPlugin;
impl Plugin for EditorLightingPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, point_light_force_shadows)
.add_systems(Update, spot_light_force_shadows)
.add_systems(Update, directional_light_force_shadows);
}
}
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;

@ -1,5 +1,4 @@
pub mod animation;
pub mod asset_sync;
pub mod assets;
pub mod audio;
pub mod camera;
@ -8,14 +7,15 @@ pub mod gltf;
pub mod level;
pub mod lighting;
pub mod monologue;
pub mod plugin;
mod prelude;
pub mod quit;
pub mod reset;
pub mod scene;
pub mod timeline;
use crate::editor::prelude::*;
use crate::ui;
use bevy::{asset::Asset, prelude::*};
use bevy::asset::Asset;
pub fn ui_active<T: Asset>(
events: Query<&Handle<T>, Added<Handle<T>>>,
@ -57,3 +57,385 @@ pub fn ui_inactive<T: Asset>(
});
});
}
#[derive(Debug, Component)]
pub struct TabRoot;
fn initialize_ui(mut commands: Commands) {
// Empty entity for populating the level being edited
commands.spawn((SpatialBundle { ..default() }, LevelRoot));
commands.spawn(AudioRoot);
commands.spawn((
Camera2dBundle { ..default() },
UiCameraConfig { show_ui: true },
Name::new("Editor Camera"),
EditorCamera,
ui::Active,
));
let base_style = Style {
border: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
overflow: Overflow::clip(),
flex_direction: FlexDirection::Column,
..default()
};
let simple_button = ButtonBundle {
style: Style {
..base_style.clone()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
};
// Assets widget
commands
.spawn(NodeBundle {
style: Style {
top: Val::Px(0.0),
left: Val::Px(0.0),
position_type: PositionType::Absolute,
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
})
.with_children(|parent| {
let container = parent
.spawn((NodeBundle {
style: Style {
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Row,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
},))
.with_children(|parent| {
// HACK: This is super janky but I think we need it like this for UI layout rules
let mut content_containers: Vec<(String, Entity)> = Vec::new();
// Containers with asset content
parent
.spawn((
NodeBundle {
style: Style {
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
overflow: Overflow::clip(),
justify_content: JustifyContent::FlexStart,
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
},
ui::Sorting(2),
))
.with_children(|parent| {
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
parent
.spawn((
NodeBundle {
style: Style {
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
},
ui::Sorting(1),
ui::Select::Single,
))
.with_children(|parent| {
content_containers.iter().enumerate().for_each(
|(i, (name, target))| {
parent.spawn((
simple_button.clone(),
ui::Title {
text: name.clone(),
..default()
},
ui::Collapse { target: *target },
ui::Sorting(i as u8),
));
},
);
});
})
.id();
parent.spawn((
ui::TitleBarBase::new(Color::WHITE).bundle(),
ui::Title {
text: "Assets".into(),
..default()
},
ui::Minimize { target: container },
ui::Sorting(0),
));
});
// Actions widget
commands
.spawn(NodeBundle {
style: Style {
bottom: Val::Px(0.0),
left: Val::Px(0.0),
position_type: PositionType::Absolute,
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
})
.with_children(|parent| {
let container = parent
.spawn((
NodeBundle {
style: Style {
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
},
ui::Sorting(99),
ui::Select::Action,
))
.with_children(|parent| {
parent.spawn((
simple_button.clone(),
ReloadAssets,
ui::Sorting(1),
ui::Title {
text: "Reload Assets".into(),
..default()
},
));
parent.spawn((
simple_button.clone(),
ClearAssets,
ui::Sorting(2),
ui::Title {
text: "Clear Assets".into(),
..default()
},
));
parent.spawn((
simple_button.clone(),
ExportLevel,
ui::Sorting(3),
ui::Title {
text: "Export Level".into(),
..default()
},
));
parent.spawn((
simple_button.clone(),
ClearLevel,
ui::Sorting(3),
ui::Title {
text: "Clear Level".into(),
..default()
},
));
parent.spawn((
simple_button.clone(),
QuitAction,
ui::Sorting(3),
ui::Title {
text: "Quit".into(),
..default()
},
));
})
.id();
parent.spawn((
ui::TitleBarBase::new(Color::WHITE).bundle(),
ui::Title {
text: "Actions".into(),
..default()
},
ui::Minimize { target: container },
ui::Sorting(0),
));
});
// Actions widget
commands
.spawn(NodeBundle {
style: Style {
bottom: Val::Px(0.0),
right: Val::Px(0.0),
position_type: PositionType::Absolute,
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
})
.with_children(|parent| {
let container = parent
.spawn((
NodeBundle {
style: Style {
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Row,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
},
ui::Sorting(99),
ui::Select::Single,
TimelineWidget,
))
.with_children(|parent| {
// "Add Epoch" button
parent
.spawn((
NodeBundle {
style: Style {
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Row,
align_items: AlignItems::Center,
justify_items: JustifyItems::Center,
overflow: Overflow::clip(),
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
},
ui::Select::Action,
))
.with_children(|parent| {
parent.spawn((
simple_button.clone(),
AddEpoch,
ui::Title {
text: "+".into(),
..default()
},
));
});
})
.id();
parent.spawn((
ui::TitleBarBase::new(Color::WHITE).bundle(),
ui::Title {
text: "Timeline".into(),
..default()
},
ui::Minimize { target: container },
ui::Sorting(0),
));
});
}
const WELCOME_MESSAGES: &'static [&'static str] = &[
"Welcome to the Monologue Trees editor!",
"Import assets by dragging and dropping files or folders into the editor!",
concat!(
"Supported file types (for now):\n",
"* 3D: .gltf, .glb\n",
"* Audio: .ogg\n",
"* Font: .ttf, .otf\n",
"* Monologues: .monologue.txt",
),
];
fn welcome_message(mut writer: EventWriter<ui::Alert>) {
WELCOME_MESSAGES
.iter()
.for_each(|&msg| writer.send(ui::Alert::Info(msg.into())))
}
fn spawn_tab_container<T: Default + Component>(
title: &'static str,
parent: &mut ChildBuilder,
) -> (String, Entity) {
(
title.into(),
// Content node
parent
.spawn((
NodeBundle {
style: Style {
display: Display::None,
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Column,
..default()
},
background_color: Color::WHITE.into(),
border_color: Color::BLACK.into(),
..default()
},
T::default(),
ui::Scroll,
Interaction::default(),
))
.id(),
)
}

@ -7,6 +7,25 @@ use bevy::{
};
use serde::Deserialize;
#[derive(Debug, Default)]
pub struct EditorMonologuePlugin;
impl Plugin for EditorMonologuePlugin {
fn build(&self, app: &mut App) {
app.add_asset::<Monologue>()
.init_asset_loader::<MonologueLoader>()
.add_systems(Update, sync_asset_buttons::<Monologue>)
.add_systems(Update, sync_remove_asset_buttons::<Monologue>)
.add_systems(Update, control_active_gltf)
.add_systems(Update, control_monologue)
.add_systems(Update, ui_control_monologue)
.add_systems(Update, ui_active::<Monologue>)
.add_systems(Update, ui_inactive::<Monologue>)
.add_systems(Update, sync_monologue_font)
.add_event::<ControlMonologue>();
}
}
#[derive(Debug, Component, Default)]
pub struct MonologueWidget;

@ -0,0 +1,24 @@
use crate::editor::prelude::*;
#[derive(Debug, Default)]
pub struct EditorPlugin;
impl Plugin for EditorPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(EditorAssetsPlugin::default())
.add_plugins(EditorAnimationPlugin::default())
.add_plugins(EditorAudioPlugin::default())
.add_plugins(EditorCameraPlugin::default())
.add_plugins(EditorFontPlugin::default())
.add_plugins(EditorGltfPlugin::default())
.add_plugins(EditorLevelPlugin::default())
.add_plugins(EditorLightingPlugin::default())
.add_plugins(EditorMonologuePlugin::default())
.add_plugins(EditorQuitPlugin::default())
.add_plugins(EditorScenePlugin::default())
.add_plugins(EditorTimelinePlugin::default())
.add_systems(Startup, initialize_ui)
.add_systems(Startup, init_texts_ui)
.add_systems(Startup, welcome_message);
}
}

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

@ -2,6 +2,15 @@ use crate::editor::prelude::*;
use bevy::app::AppExit;
#[derive(Debug, Default)]
pub struct EditorQuitPlugin;
impl Plugin for EditorQuitPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, quit.run_if(ui::activated::<QuitAction>));
}
}
#[derive(Debug, Component, Default)]
pub struct QuitAction;

@ -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,5 +1,18 @@
use crate::editor::prelude::*;
#[derive(Debug, Default)]
pub struct EditorScenePlugin;
impl Plugin for EditorScenePlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, remove_scenes_ui)
.add_systems(Update, add_scenes_ui)
.add_systems(Update, control_active_scenes)
.add_systems(Update, sync_asset_buttons::<Scene>)
.add_systems(Update, sync_remove_asset_buttons::<Scene>);
}
}
#[derive(Debug, Component, Default)]
pub struct SceneWidget;

@ -1,5 +1,28 @@
use crate::editor::prelude::*;
#[derive(Debug, Default)]
pub struct EditorTimelinePlugin;
impl Plugin for EditorTimelinePlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, add_timeline_epoch.run_if(ui::activated::<AddEpoch>))
.add_systems(Update, set_epoch_gltf)
.add_systems(Update, load_epoch_gltf)
.add_systems(Update, set_epoch_scene)
.add_systems(Update, load_epoch_scene)
.add_systems(Update, set_epoch_camera)
.add_systems(Update, load_epoch_camera)
.add_systems(Update, set_epoch_music)
.add_systems(Update, load_epoch_music)
.add_systems(Update, set_epoch_monologue)
.add_systems(Update, load_epoch_monologue)
.add_systems(Update, set_epoch_font)
.add_systems(Update, load_epoch_font)
.add_systems(Update, set_epoch_sfx)
.add_systems(Update, load_epoch_sfx);
}
}
/// Timeline widget marker
#[derive(Debug, Component)]
pub struct TimelineWidget;

@ -17,7 +17,8 @@ pub struct AnimatedTextPlugin;
impl Plugin for AnimatedTextPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, (manage_texts, animate_texts));
app.add_systems(Update, manage_texts)
.add_systems(Update, animate_texts);
}
}

Loading…
Cancel
Save