diff --git a/assets/output.scn.ron b/assets/output.scn.ron deleted file mode 100644 index 8059569..0000000 --- a/assets/output.scn.ron +++ /dev/null @@ -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": ( - 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": ( - id: AssetPathId(((13241355290950327508), (2370051114748836591))), - ), - }, - ), - }, -) \ No newline at end of file diff --git a/bin/editor.rs b/bin/editor.rs index e44ee0e..022a181 100644 --- a/bin/editor.rs +++ b/bin/editor.rs @@ -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::() - .register_type::() - .init_resource::() - .init_resource::() - .add_asset::() - .init_asset_loader::() - .add_event::() - .add_event::() - .add_systems(Startup, (initialize_ui, init_texts_ui, welcome_message)) - .add_systems(Update, quit.run_if(ui::activated::)) - .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::, - ui_inactive::, - control_audio, - ), - ) - .add_systems( - Update, - ( - gltf_ui, - texts_ui, - control_active_gltf, - control_monologue, - ui_control_monologue, - ui_active::, - ui_inactive::, - 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::), - clear_assets.run_if(ui::activated::), - ), - ) - .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::), - rehydrate_level::, - rehydrate_level::, PlaybackSettings>, - ), - ) - .add_systems( - Update, - ( - sync_asset_buttons::, - sync_remove_asset_buttons::, - sync_asset_buttons::, - sync_remove_asset_buttons::, - sync_asset_buttons::, - sync_remove_asset_buttons::, - sync_asset_buttons::, - sync_remove_asset_buttons::, - sync_asset_buttons::, - sync_remove_asset_buttons::, - sync_asset_buttons::, - sync_remove_asset_buttons::, - sync_asset_buttons::, - sync_remove_asset_buttons::, - ), - ) - .add_systems( - Update, - ( - add_timeline_epoch.run_if(ui::activated::), - 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::("Font", parent)); - content_containers - .push(spawn_tab_container::("Monologue", parent)); - content_containers - .push(spawn_tab_container::("Audio", parent)); - content_containers - .push(spawn_tab_container::("Level", parent)); - content_containers - .push(spawn_tab_container::("Gltf", parent)); - content_containers - .push(spawn_tab_container::("Scene", parent)); - content_containers - .push(spawn_tab_container::("Camera", parent)); - content_containers - .push(spawn_tab_container::("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) { - WELCOME_MESSAGES - .iter() - .for_each(|&msg| writer.send(ui::Alert::Info(msg.into()))) -} - -fn spawn_tab_container( - 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(), - ) -} diff --git a/src/editor/animation.rs b/src/editor/animation.rs index 7fd7ff5..95f1c0b 100644 --- a/src/editor/animation.rs +++ b/src/editor/animation.rs @@ -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::) + .add_systems(Update, sync_remove_asset_buttons::) + .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; diff --git a/src/editor/asset_sync.rs b/src/editor/asset_sync.rs deleted file mode 100644 index 2483d19..0000000 --- a/src/editor/asset_sync.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::editor::prelude::*; - -// This sets buttons to active when their associated handle is spawned -pub fn sync_asset_buttons( - events: Query<&Handle, Added>>, - buttons: Query<(Entity, &ui::TargetAsset)>, - 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( - mut events: RemovedComponents>, - asset_entities: Query<&Handle>, - buttons: Query<(Entity, &ui::TargetAsset)>, - 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::(); - }); - }); -} diff --git a/src/editor/assets.rs b/src/editor/assets.rs index 52b0cdf..88c7a49 100644 --- a/src/editor/assets.rs +++ b/src/editor/assets.rs @@ -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::)) + .init_resource::() + .add_systems(Update, clear_assets.run_if(ui::activated::)); + } +} + #[derive(Resource, Default)] pub struct AssetRegistry(pub Vec); @@ -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( + events: Query<&Handle, Added>>, + buttons: Query<(Entity, &ui::TargetAsset)>, + 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( + mut events: RemovedComponents>, + asset_entities: Query<&Handle>, + buttons: Query<(Entity, &ui::TargetAsset)>, + 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::(); + }); + }); +} + +#[derive(Debug, Component)] +pub struct ClearAssets; + +pub fn clear_assets( + asset_holders: Query< + Entity, + Or<( + With>, + With>, + With>, + With>, + With>, + With>, + With>, + With>, + With>, + With>, + With>, + With>, + )>, + >, + mut registry: ResMut, + 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(); +} diff --git a/src/editor/audio.rs b/src/editor/audio.rs index 3878490..74e70a4 100644 --- a/src/editor/audio.rs +++ b/src/editor/audio.rs @@ -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::) + .add_systems(Update, ui_inactive::) + .add_systems(Update, control_audio) + .add_systems(Update, sync_asset_buttons::) + .register_type::() + .add_event::() + .add_systems(Update, sync_remove_asset_buttons::); + } +} + #[derive(Debug, Component, Reflect, Default)] #[reflect(Component)] pub struct AudioRoot; diff --git a/src/editor/camera.rs b/src/editor/camera.rs index f19ce19..099cd87 100644 --- a/src/editor/camera.rs +++ b/src/editor/camera.rs @@ -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; diff --git a/src/editor/font.rs b/src/editor/font.rs index 18699de..db1f298 100644 --- a/src/editor/font.rs +++ b/src/editor/font.rs @@ -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::) + .add_systems(Update, sync_remove_asset_buttons::) + .add_systems(Update, fonts_ui) + .add_systems(Update, ui_control_font) + .init_resource::() + .add_systems(Update, sync_font); + } +} + #[derive(Debug, Component, Default)] pub struct FontWidget; diff --git a/src/editor/gltf.rs b/src/editor/gltf.rs index d79317d..cc8e038 100644 --- a/src/editor/gltf.rs +++ b/src/editor/gltf.rs @@ -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::) + .add_systems(Update, sync_remove_asset_buttons::) + .add_systems(Update, gltf_ui); + } +} + #[derive(Debug, Component, Default)] pub struct GltfWidget; diff --git a/src/editor/level.rs b/src/editor/level.rs index f9dbebb..c8a9162 100644 --- a/src/editor/level.rs +++ b/src/editor/level.rs @@ -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::() + .add_systems(Update, level_ui) + .add_systems(Update, load_level) + .add_systems(Update, export_level.run_if(ui::activated::)) + .add_systems(Update, rehydrate_level::) + .add_systems( + Update, + rehydrate_level::, PlaybackSettings>, + ) + .add_systems(Update, sync_asset_buttons::) + .add_systems(Update, sync_remove_asset_buttons::); + } +} + #[derive(Debug, Component, Reflect, Default)] #[reflect(Component)] pub struct LevelRoot; @@ -165,3 +184,36 @@ pub fn rehydrate_level( commands.entity(entity).insert(WO::default()); }); } + +#[derive(Debug, Component)] +pub struct ClearLevel; + +pub fn clear_level( + events: Query, Added)>, + actives: Query< + Entity, + ( + With, + Or<( + With, + With>, + With>, + With>, + With>, + With>, + With>, + )>, + ), + >, + root: Query>, + mut commands: Commands, +) { + events.iter().for_each(|_| { + actives.iter().for_each(|entity| { + commands.entity(entity).remove::(); + }); + root.iter().for_each(|entity| { + commands.entity(entity).despawn_descendants(); + }); + }) +} diff --git a/src/editor/lighting.rs b/src/editor/lighting.rs index 1a18283..97cfbe8 100644 --- a/src/editor/lighting.rs +++ b/src/editor/lighting.rs @@ -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>) { spot_lights.iter_mut().for_each(|mut light| { light.shadows_enabled = true; diff --git a/src/editor/mod.rs b/src/editor/mod.rs index 1fc0b3c..943891a 100644 --- a/src/editor/mod.rs +++ b/src/editor/mod.rs @@ -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( events: Query<&Handle, Added>>, @@ -57,3 +57,385 @@ pub fn ui_inactive( }); }); } + +#[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::("Font", parent)); + content_containers + .push(spawn_tab_container::("Monologue", parent)); + content_containers + .push(spawn_tab_container::("Audio", parent)); + content_containers + .push(spawn_tab_container::("Level", parent)); + content_containers + .push(spawn_tab_container::("Gltf", parent)); + content_containers + .push(spawn_tab_container::("Scene", parent)); + content_containers + .push(spawn_tab_container::("Camera", parent)); + content_containers + .push(spawn_tab_container::("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) { + WELCOME_MESSAGES + .iter() + .for_each(|&msg| writer.send(ui::Alert::Info(msg.into()))) +} + +fn spawn_tab_container( + 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(), + ) +} diff --git a/src/editor/monologue.rs b/src/editor/monologue.rs index f73e591..a72b36b 100644 --- a/src/editor/monologue.rs +++ b/src/editor/monologue.rs @@ -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::() + .init_asset_loader::() + .add_systems(Update, sync_asset_buttons::) + .add_systems(Update, sync_remove_asset_buttons::) + .add_systems(Update, control_active_gltf) + .add_systems(Update, control_monologue) + .add_systems(Update, ui_control_monologue) + .add_systems(Update, ui_active::) + .add_systems(Update, ui_inactive::) + .add_systems(Update, sync_monologue_font) + .add_event::(); + } +} + #[derive(Debug, Component, Default)] pub struct MonologueWidget; diff --git a/src/editor/plugin.rs b/src/editor/plugin.rs new file mode 100644 index 0000000..c42bd9d --- /dev/null +++ b/src/editor/plugin.rs @@ -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); + } +} diff --git a/src/editor/prelude.rs b/src/editor/prelude.rs index 7b1604f..866245d 100644 --- a/src/editor/prelude.rs +++ b/src/editor/prelude.rs @@ -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, }; diff --git a/src/editor/quit.rs b/src/editor/quit.rs index c3c0b87..983a944 100644 --- a/src/editor/quit.rs +++ b/src/editor/quit.rs @@ -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::)); + } +} + #[derive(Debug, Component, Default)] pub struct QuitAction; diff --git a/src/editor/reset.rs b/src/editor/reset.rs deleted file mode 100644 index 2408c83..0000000 --- a/src/editor/reset.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::editor::prelude::*; - -#[derive(Debug, Component)] -pub struct ClearLevel; - -pub fn clear_level( - events: Query, Added)>, - actives: Query< - Entity, - ( - With, - Or<( - With, - With>, - With>, - With>, - With>, - With>, - With>, - )>, - ), - >, - root: Query>, - mut commands: Commands, -) { - events.iter().for_each(|_| { - actives.iter().for_each(|entity| { - commands.entity(entity).remove::(); - }); - 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>, - With>, - With>, - With>, - With>, - With>, - With>, - With>, - With>, - With>, - With>, - With>, - )>, - >, - mut registry: ResMut, - 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(); -} diff --git a/src/editor/scene.rs b/src/editor/scene.rs index 7eddf59..5125689 100644 --- a/src/editor/scene.rs +++ b/src/editor/scene.rs @@ -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::) + .add_systems(Update, sync_remove_asset_buttons::); + } +} + #[derive(Debug, Component, Default)] pub struct SceneWidget; diff --git a/src/editor/timeline.rs b/src/editor/timeline.rs index cb70ea6..240ff49 100644 --- a/src/editor/timeline.rs +++ b/src/editor/timeline.rs @@ -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::)) + .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; diff --git a/src/text.rs b/src/text.rs index 67b5221..c810d33 100644 --- a/src/text.rs +++ b/src/text.rs @@ -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); } }