Compare commits

..

No commits in common. '3fc6a0663e9d11d731f8d8b444a50a9d25b32adb' and 'cc05c7c434f6312012c80e1ffc0882042aa20367' have entirely different histories.

@ -1,10 +0,0 @@
Voluptatem culpa quo quia alias minima amet. Consequatur fugit et vitae qui dolor. Aut ea dolorum dicta quas ex et recusandae et. Nostrum eos quia quis est consequuntur.
Ratione facilis aliquid et. Dolores expedita magni suscipit minima. Voluptatem in pariatur vitae laboriosam aliquam non ducimus. Laudantium illum provident et libero assumenda et sunt similique. Corporis tenetur repellat enim perferendis ut minus omnis.
Blanditiis vitae quae ut ipsum consequatur. Ratione dignissimos exercitationem autem accusamus. Qui molestiae ipsam pariatur quis amet quia voluptate sunt. In voluptate dolorum in quia. Sit ut non voluptatem qui placeat quis. Ducimus ipsa adipisci eligendi dolor id.
Ex totam nam laudantium quis. Omnis saepe mollitia eligendi unde rerum. Odit voluptatum repellat rem est iure neque saepe.
Nulla est consequatur sint amet nesciunt quam. Qui fuga excepturi veritatis quia. Saepe natus et enim eveniet voluptates velit quod sint. Dolores reprehenderit eligendi aut. Et voluptate ea aliquam.

@ -1 +0,0 @@
This is a placeholder monologue

@ -1,52 +1,47 @@
// Monologe Trees Editor // Monologue Trees Editor
// //
// Editor for creating Monologue Trees levels // Editor for creating Monologue Trees levels
// //
// BUGS: // BUGS:
// * Camera order ambiguity // * Cannot view scene when selected, WTF
// * Multi-GLTF UX is bad. // * Scene and Animation tabs are whacked
// * Consider GLTF hierarchy (GLTF1 > Scene1a/Scene1b, GlTF2 > Scene2a/Scene2b, etc)
// * Easy despawn when de-selecting gltf
// //
// TODO: // TODO:
// * (medium) Spawn gltf scene
// * (medium) Load default scene when gltf selected
// * (medium) Set gltf to active/inactive
// * (medium) Play individual animation(s)
// * Only select one at a time.
// * (hard) Better Colorscheme
// * (medium) Visual errors for bad GLTFs
// * (medium) Spawn clicked scene
// * (medium) Play clicked animation
// * (easy) Play all animations // * (easy) Play all animations
// * (easy) Clear button to wipe spawned scene // * (medium) Add fonts similar to Audios based on inspect-fonts
// * (brutal) export level // * (hard) Add Dialogs (requires text box UI, saving, loading).
// * (hard) import level
// //
// Asset types: // Asset types:
// * Audios (done) // * Audios (done)
// * Loop individual (done) // * Loop individual
// * Stop all
// * Gltfs (doing) // * Gltfs (doing)
// * Scenes // * Scenes
// * Animations // * Animations
// * Play/Pause all // * Play/Pause all
// * Fonts (done) // * Fonts
// * Monologues (done) // * Monologues
use bevy::{ use bevy::{
asset::{Asset, Assets}, asset::{Asset, Assets},
asset::{AssetLoader, LoadContext, LoadedAsset}, asset::{AssetLoader, LoadContext, LoadedAsset},
audio::PlaybackMode, audio::PlaybackMode,
gltf::Gltf, gltf::Gltf,
input::{keyboard::KeyboardInput, ButtonState},
prelude::*, prelude::*,
utils::BoxedFuture, utils::BoxedFuture,
}; };
use monologue_trees::{debug::*, ui}; use monologue_trees::{debug::*, ui};
const WELCOME_MESSAGES: &'static [&'static str] = &[
"Welcome to the Monologue Trees editor!",
concat!(
"Import assets by dragging and dropping files into the editor\n",
"\n",
"Supported file types (for now):\n",
"* 3D: .gltf, .glb\n",
"* Audio: .ogg\n",
"* Font: .ttf, .otf\n",
"* Monologues: .monologue.txt",
),
];
fn main() { fn main() {
App::new() App::new()
.add_plugins(( .add_plugins((
@ -66,41 +61,25 @@ fn main() {
.init_asset_loader::<MonologueLoader>() .init_asset_loader::<MonologueLoader>()
.add_event::<CustomAssetEvent<Scene>>() .add_event::<CustomAssetEvent<Scene>>()
.add_event::<CustomAssetEvent<AnimationClip>>() .add_event::<CustomAssetEvent<AnimationClip>>()
.add_systems(Startup, (initialize_ui, init_texts_ui, welcome_message)) .add_systems(Startup, (initialize_ui, welcome_message))
.add_systems(
Update,
(
manage_gltf_animation_ui,
init_animations_ui,
animations_ui,
play_all_animations,
play_animation,
),
)
.add_systems(
Update,
(manage_gltf_scene_ui, scenes_ui, control_active_scenes),
)
.add_systems(
Update,
(
cameras_ui,
manage_active_camera,
control_active_camera,
fallback_camera,
),
)
.add_systems(Update, (audio_ui, play_audio))
.add_systems( .add_systems(
Update, Update,
( (
import_files, import_files,
audio_ui,
gltf_ui, gltf_ui,
fonts_ui, fonts_ui,
texts_ui, texts_ui,
cameras_ui,
manage_active_gltf, manage_active_gltf,
show_preview_text, manage_gltf_scene_ui,
sync_monologue_font, manage_gltf_animation_ui,
scenes_ui,
animations_ui,
spawn_scenes,
manage_camera,
play_animation,
play_audio,
), ),
) )
.run(); .run();
@ -127,14 +106,13 @@ pub struct EditorCamera;
fn initialize_ui(mut commands: Commands) { fn initialize_ui(mut commands: Commands) {
// Empty entity for populating the level being edited // Empty entity for populating the level being edited
commands.spawn((SpatialBundle { ..default() }, LevelRoot)); commands.spawn((TransformBundle { ..default() }, LevelRoot));
commands.spawn(( commands.spawn((
Camera2dBundle { ..default() }, Camera3dBundle { ..default() },
UiCameraConfig { show_ui: true }, UiCameraConfig { show_ui: true },
Name::new("Editor Camera"), Name::new("Editor Camera"),
EditorCamera, EditorCamera,
ui::Active,
)); ));
let base_style = Style { let base_style = Style {
@ -206,11 +184,6 @@ fn initialize_ui(mut commands: Commands) {
parent, parent,
ui::Select::Single, ui::Select::Single,
)); ));
content_containers.push(spawn_tab_container::<MonologueWidget>(
"Monologue",
parent,
ui::Select::Single,
));
content_containers.push(spawn_tab_container::<AudioWidget>( content_containers.push(spawn_tab_container::<AudioWidget>(
"Audio", "Audio",
parent, parent,
@ -224,7 +197,7 @@ fn initialize_ui(mut commands: Commands) {
content_containers.push(spawn_tab_container::<SceneWidget>( content_containers.push(spawn_tab_container::<SceneWidget>(
"Scene", "Scene",
parent, parent,
ui::Select::Multi, ui::Select::Single,
)); ));
content_containers.push(spawn_tab_container::<AnimationWidget>( content_containers.push(spawn_tab_container::<AnimationWidget>(
"Animation", "Animation",
@ -269,10 +242,7 @@ fn initialize_ui(mut commands: Commands) {
content_containers.iter().for_each(|(name, target)| { content_containers.iter().for_each(|(name, target)| {
parent.spawn(( parent.spawn((
b.clone(), b.clone(),
ui::Title { ui::Title { text: name.clone() },
text: name.clone(),
..default()
},
ui::Collapse { target: *target }, ui::Collapse { target: *target },
)); ));
}); });
@ -280,10 +250,24 @@ fn initialize_ui(mut commands: Commands) {
}) })
.id(); .id();
parent.spawn(( parent.spawn((
ui::TitleBarBase::new(Color::WHITE).bundle(), 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(),
align_items: AlignItems::Center,
align_content: AlignContent::Center,
justify_content: JustifyContent::SpaceBetween,
..default()
},
background_color: Color::ALICE_BLUE.into(),
border_color: Color::BLACK.into(),
..default()
},
ui::Title { ui::Title {
text: "Assets".into(), text: "Assets".into(),
..default()
}, },
ui::Minimize { target: container }, ui::Minimize { target: container },
ui::Sorting(0), ui::Sorting(0),
@ -292,9 +276,21 @@ fn initialize_ui(mut commands: Commands) {
} }
fn welcome_message(mut writer: EventWriter<ui::Alert>) { fn welcome_message(mut writer: EventWriter<ui::Alert>) {
WELCOME_MESSAGES writer.send(ui::Alert::Info(
.iter() "Welcome to the Monologue Trees editor!".into(),
.for_each(|&msg| writer.send(ui::Alert::Info(msg.into()))) ));
writer.send(ui::Alert::Info(
[
"Import assets by dragging and dropping files into the editor",
"",
"Supported file types (for now):",
"* 3D: .gltf, .glb",
"* Audio: .ogg",
"* Font: .ttf, .otf",
]
.join("\n")
.into(),
));
} }
fn spawn_tab_container<T: Default + Component>( fn spawn_tab_container<T: Default + Component>(
@ -365,7 +361,16 @@ mod audio {
current: Query<(Entity, &ui::TargetAsset<AudioSource>)>, current: Query<(Entity, &ui::TargetAsset<AudioSource>)>,
server: Res<AssetServer>, server: Res<AssetServer>,
) { ) {
events.iter().for_each(|event| match event { events
.iter()
.filter(|&event| match event {
AssetEvent::Created { handle }
| AssetEvent::Removed { handle }
| AssetEvent::Modified { handle } => {
has_extensions(&server, handle.clone(), &["ogg"])
}
})
.for_each(|event| match event {
AssetEvent::Created { handle } => { AssetEvent::Created { handle } => {
info!("Asset created! {:?}", event); info!("Asset created! {:?}", event);
let id = create_asset_button( let id = create_asset_button(
@ -375,7 +380,6 @@ mod audio {
handle: handle.clone(), handle: handle.clone(),
}, },
get_asset_name(&server, handle.clone()), get_asset_name(&server, handle.clone()),
None,
); );
commands.entity(id).insert(AudioSourceBundle { commands.entity(id).insert(AudioSourceBundle {
source: handle.clone(), source: handle.clone(),
@ -412,7 +416,6 @@ mod audio {
handle: handle.clone(), handle: handle.clone(),
}, },
get_asset_name(&server, handle.clone()), get_asset_name(&server, handle.clone()),
None,
); );
commands.entity(id).insert(AudioSourceBundle { commands.entity(id).insert(AudioSourceBundle {
source: handle.clone(), source: handle.clone(),
@ -470,7 +473,6 @@ mod assets {
commands: &mut Commands, commands: &mut Commands,
target: ui::TargetAsset<A>, target: ui::TargetAsset<A>,
name: String, name: String,
font: Option<Handle<Font>>,
) -> Entity { ) -> Entity {
commands commands
.spawn(( .spawn((
@ -485,10 +487,7 @@ mod assets {
border_color: Color::BLACK.into(), border_color: Color::BLACK.into(),
..default() ..default()
}, },
ui::Title { ui::Title { text: name },
text: name,
font: font.clone(),
},
)) ))
.set_parent(root.single()) .set_parent(root.single())
.id() .id()
@ -513,10 +512,7 @@ mod assets {
border_color: Color::BLACK.into(), border_color: Color::BLACK.into(),
..default() ..default()
}, },
ui::Title { ui::Title { text: name },
text: name,
..default()
},
)) ))
.set_parent(root.single()) .set_parent(root.single())
.id() .id()
@ -538,22 +534,6 @@ mod assets {
} }
} }
pub fn destroy_entity_button(
current: &Query<(Entity, &ui::TargetEntity)>,
commands: &mut Commands,
target: &ui::TargetEntity,
) {
if let Some(entity) = current.iter().find_map(|(entity, this)| {
if this.entity == target.entity {
Some(entity)
} else {
None
}
}) {
commands.entity(entity).despawn_recursive();
}
}
pub fn has_extensions<T: Asset>( pub fn has_extensions<T: Asset>(
server: &AssetServer, server: &AssetServer,
handle: Handle<T>, handle: Handle<T>,
@ -586,7 +566,16 @@ mod gltf {
current: Query<(Entity, &ui::TargetAsset<Gltf>)>, current: Query<(Entity, &ui::TargetAsset<Gltf>)>,
server: Res<AssetServer>, server: Res<AssetServer>,
) { ) {
events.iter().for_each(|event| match event { events
.iter()
.filter(|&event| match event {
AssetEvent::Created { handle }
| AssetEvent::Removed { handle }
| AssetEvent::Modified { handle } => {
has_extensions(&server, handle.clone(), &["gltf", "glb"])
}
})
.for_each(|event| match event {
AssetEvent::Created { handle } => { AssetEvent::Created { handle } => {
info!("Asset created! {:?}", event); info!("Asset created! {:?}", event);
create_asset_button( create_asset_button(
@ -596,7 +585,6 @@ mod gltf {
handle: handle.clone(), handle: handle.clone(),
}, },
get_asset_name(&server, handle.clone()), get_asset_name(&server, handle.clone()),
None,
); );
} }
AssetEvent::Removed { handle } => { AssetEvent::Removed { handle } => {
@ -625,7 +613,6 @@ mod gltf {
handle: handle.clone(), handle: handle.clone(),
}, },
get_asset_name(&server, handle.clone()), get_asset_name(&server, handle.clone()),
None,
); );
} }
}); });
@ -764,25 +751,18 @@ mod scenes {
current: Query<(Entity, &ui::TargetAsset<Scene>)>, current: Query<(Entity, &ui::TargetAsset<Scene>)>,
) { ) {
events.iter().for_each(|event| { events.iter().for_each(|event| {
let empty = current.iter().len() == 0;
match event { match event {
CustomAssetEvent::Add { name, handle } => { CustomAssetEvent::Add { name, handle } => {
info!("Asset loading! {:?}({:?})", name, handle); info!("Asset loading! {:?}({:?})", name, handle);
// Spawn new tree // Spawn new tree
let e = create_asset_button( create_asset_button(
&widget, &widget,
&mut commands, &mut commands,
ui::TargetAsset { ui::TargetAsset {
handle: handle.clone(), handle: handle.clone(),
}, },
name.clone(), name.clone(),
None,
); );
// If this is the first scene being added, set it as active
if empty {
commands.entity(e).insert(ui::Active);
}
} }
CustomAssetEvent::Remove { handle } => { CustomAssetEvent::Remove { handle } => {
destroy_asset_button( destroy_asset_button(
@ -800,42 +780,25 @@ mod scenes {
}); });
} }
pub fn control_active_scenes( pub fn spawn_scenes(
added: Query<Entity, (With<Button>, Added<ui::Active>)>, events: Query<
mut removed: RemovedComponents<ui::Active>, (&Interaction, &ui::TargetAsset<Scene>),
scene_refs: Query<&ui::TargetAsset<Scene>>, (With<Button>, Changed<Interaction>),
scenes: Query<(Entity, &Handle<Scene>)>, >,
level_root: Query<Entity, With<LevelRoot>>, level_root: Query<Entity, With<LevelRoot>>,
mut commands: Commands, mut commands: Commands,
) { ) {
// A scene button was marked inactive events
removed.iter().for_each(|entity| { .iter()
// Get the handle associated with that button .filter(|(&interaction, _)| interaction == Interaction::Pressed)
if let Ok(ui::TargetAsset { handle }) = scene_refs.get(entity) { .for_each(|(_, ui::TargetAsset { handle })| {
if let Some(entity) = scenes.iter().find_map(|(entity, this_handle)| { info!("Spawning {:?}", handle);
if this_handle == handle {
Some(entity)
} else {
None
}
}) {
commands.entity(entity).despawn_recursive();
}
}
});
added.iter().for_each(|entity| {
if let Ok(ui::TargetAsset { handle }) = scene_refs.get(entity) {
info!("Spawning Scene {:?}", handle);
commands commands
.entity(level_root.single()) .entity(level_root.single())
.with_children(|parent| { .with_children(|parent| {
parent.spawn(SceneBundle { parent.spawn((handle.clone(), TransformBundle { ..default() }));
scene: handle.clone(),
..default()
});
});
}
}); });
})
} }
} }
@ -848,36 +811,6 @@ mod animations {
#[derive(Debug, Component, Default)] #[derive(Debug, Component, Default)]
pub struct AnimationWidget; 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()
},
));
});
})
}
pub fn animations_ui( pub fn animations_ui(
mut events: EventReader<CustomAssetEvent<AnimationClip>>, mut events: EventReader<CustomAssetEvent<AnimationClip>>,
mut commands: Commands, mut commands: Commands,
@ -896,7 +829,6 @@ mod animations {
handle: handle.clone(), handle: handle.clone(),
}, },
name.clone(), name.clone(),
None,
); );
} }
CustomAssetEvent::Remove { handle } => { CustomAssetEvent::Remove { handle } => {
@ -915,62 +847,25 @@ mod animations {
}); });
} }
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( pub fn play_animation(
start: Query<Entity, (With<Button>, Added<ui::Active>)>, events: Query<
mut stop: RemovedComponents<ui::Active>, (&Interaction, &ui::TargetAsset<AnimationClip>),
clip_refs: Query<&ui::TargetAsset<AnimationClip>>, (With<Button>, Changed<Interaction>),
>,
mut targets: Query<(&mut AnimationPlayer, &Name), With<Transform>>, mut targets: Query<(&mut AnimationPlayer, &Name), With<Transform>>,
clips: Res<Assets<AnimationClip>>, clips: Res<Assets<AnimationClip>>,
) { ) {
stop.iter().for_each(|entity| { events
if let Ok(ui::TargetAsset { handle }) = clip_refs.get(entity) { .iter()
let clip = clips.get(&handle).expect("Load animation clip"); .filter(|(&interaction, _)| interaction == Interaction::Pressed)
targets .for_each(|(_, ui::TargetAsset { handle })| {
.iter_mut() let clip = clips.get(handle).expect("Load animation clip");
.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 targets
.iter_mut() .iter_mut()
.filter(|(_, name)| clip.compatible_with(name)) .filter(|(_, name)| clip.compatible_with(name))
.for_each(|(mut player, _)| { .for_each(|(mut player, _)| {
if player.is_paused() {
player.resume();
} else {
player.play(handle.clone()).repeat(); player.play(handle.clone()).repeat();
}
}) })
}
}); });
} }
} }
@ -982,6 +877,7 @@ mod fonts {
#[derive(Debug, Component, Default)] #[derive(Debug, Component, Default)]
pub struct FontWidget; pub struct FontWidget;
// TODO: Make each button have the font
pub fn fonts_ui( pub fn fonts_ui(
mut events: EventReader<AssetEvent<Font>>, mut events: EventReader<AssetEvent<Font>>,
mut commands: Commands, mut commands: Commands,
@ -989,7 +885,16 @@ mod fonts {
current: Query<(Entity, &ui::TargetAsset<Font>)>, current: Query<(Entity, &ui::TargetAsset<Font>)>,
server: Res<AssetServer>, server: Res<AssetServer>,
) { ) {
events.iter().for_each(|event| match event { events
.iter()
.filter(|&event| match event {
AssetEvent::Created { handle }
| AssetEvent::Removed { handle }
| AssetEvent::Modified { handle } => {
has_extensions(&server, handle.clone(), &["ttf", "otf"])
}
})
.for_each(|event| match event {
AssetEvent::Created { handle } => { AssetEvent::Created { handle } => {
info!("Asset created! {:?}", event); info!("Asset created! {:?}", event);
create_asset_button( create_asset_button(
@ -999,7 +904,6 @@ mod fonts {
handle: handle.clone(), handle: handle.clone(),
}, },
get_asset_name(&server, handle.clone()), get_asset_name(&server, handle.clone()),
Some(handle.clone()),
); );
} }
AssetEvent::Removed { handle } => { AssetEvent::Removed { handle } => {
@ -1028,7 +932,6 @@ mod fonts {
handle: handle.clone(), handle: handle.clone(),
}, },
get_asset_name(&server, handle.clone()), get_asset_name(&server, handle.clone()),
Some(handle.clone()),
); );
} }
}); });
@ -1038,10 +941,7 @@ mod fonts {
use monologues::*; use monologues::*;
mod monologues { mod monologues {
use super::*; use super::*;
use bevy::{ use bevy::reflect::{TypePath, TypeUuid};
reflect::{TypePath, TypeUuid},
ui::FocusPolicy,
};
use serde::Deserialize; use serde::Deserialize;
#[derive(Debug, Component, Default)] #[derive(Debug, Component, Default)]
@ -1053,12 +953,6 @@ mod monologues {
text: String, text: String,
} }
#[derive(Debug, Component)]
pub struct MonologueModal;
#[derive(Debug, Component)]
pub struct MonologueContainer;
#[derive(Default)] #[derive(Default)]
pub struct MonologueLoader; pub struct MonologueLoader;
@ -1069,160 +963,29 @@ mod monologues {
load_context: &'a mut LoadContext, load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<(), bevy::asset::Error>> { ) -> BoxedFuture<'a, Result<(), bevy::asset::Error>> {
Box::pin(async move { Box::pin(async move {
let asset = Monologue { load_context.set_default_asset(LoadedAsset::new(
text: String::from_utf8(bytes.to_vec())?, String::from_utf8(bytes.to_vec()).expect("Convert bytes to String"),
}; ));
load_context.set_default_asset(LoadedAsset::new(asset));
Ok(()) Ok(())
}) })
} }
fn extensions(&self) -> &[&str] { fn extensions(&self) -> &[&str] {
&["monologue.txt"] &[".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,
));
}
// TODO: Load .txt files for monologues // TODO: Load .txt files for monologues
pub fn texts_ui( pub fn texts_ui(
mut events: EventReader<AssetEvent<Monologue>>, mut events: EventReader<AssetEvent<Monologue>>,
mut commands: Commands, mut _commands: Commands,
widget: Query<Entity, With<MonologueWidget>>, _widget: Query<Entity, With<MonologueWidget>>,
current: Query<(Entity, &ui::TargetAsset<Monologue>)>, _current: Query<(Entity, &ui::TargetAsset<Monologue>)>,
server: Res<AssetServer>, _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,
);
}
});
}
// TODO(BUG): Better handle hide/close monologue
pub fn show_preview_text(
added: Query<Entity, (With<Button>, Added<ui::Active>)>,
mut removed: RemovedComponents<ui::Active>,
monologue_handles: Query<&ui::TargetAsset<Monologue>>,
monologues: Res<Assets<Monologue>>,
container: Query<Entity, With<MonologueContainer>>,
mut commands: Commands,
) { ) {
added events.iter().for_each(|_event| {
.iter() info!("Loading monologue");
.filter_map(|entity| monologue_handles.get(entity).ok())
.for_each(|ui::TargetAsset { handle }| {
let monologue = monologues.get(handle).expect("Preview loaded 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),
));
parent.spawn((
TextBundle::from_section(
monologue.text.clone(),
TextStyle {
color: Color::BLACK.into(),
font_size: 16.0,
..default()
},
),
handle.clone(),
));
});
});
});
}
// TODO: Sync Handle<Monologue> and TextStyle components to automagically generate and sync text
pub fn sync_monologue_font(
events: Query<&ui::TargetAsset<Font>, Added<ui::Active>>,
mut texts: Query<&mut Text, With<Handle<Monologue>>>,
) {
events.iter().for_each(|ui::TargetAsset { handle }| {
texts.iter_mut().for_each(|mut text| {
text.sections.iter_mut().for_each(|section| {
section.style.font = handle.clone();
});
});
});
} }
} }
@ -1235,90 +998,33 @@ mod cameras {
// TODO: Despawn camera button when camera removed // TODO: Despawn camera button when camera removed
pub fn cameras_ui( pub fn cameras_ui(
mut added: Query<(Entity, &mut Camera, &Name), (Added<Camera>, Without<EditorCamera>)>, mut events: Query<(Entity, &mut Camera, &Name), (Added<Camera>, Without<EditorCamera>)>,
mut removed: RemovedComponents<Camera>,
widget: Query<Entity, With<CameraWidget>>, widget: Query<Entity, With<CameraWidget>>,
current: Query<(Entity, &ui::TargetEntity)>,
mut commands: Commands, mut commands: Commands,
) { ) {
removed.iter().for_each(|entity| { events.iter_mut().for_each(|(entity, mut camera, name)| {
info!("Destroy button for {:?}", entity);
destroy_entity_button(&current, &mut commands, &ui::TargetEntity { entity });
});
added.iter_mut().for_each(|(entity, mut camera, name)| {
let empty = current.iter().len() == 0;
info!("Camera added {:?} {:?}", entity, name); info!("Camera added {:?} {:?}", entity, name);
let e = create_entity_button( create_entity_button(
&widget, &widget,
&mut commands, &mut commands,
ui::TargetEntity { entity }, ui::TargetEntity { entity },
name.as_str().into(), name.as_str().into(),
); );
if empty {
commands.entity(e).insert(ui::Active);
} else {
camera.is_active = false; camera.is_active = false;
} })
});
} }
/// Set the camera active component based on button clicks pub fn manage_camera(
pub fn manage_active_camera(
events: Query<(&Interaction, &ui::TargetEntity), Changed<Interaction>>, events: Query<(&Interaction, &ui::TargetEntity), Changed<Interaction>>,
cameras: Query<Entity, With<Camera>>, mut cameras: Query<(Entity, &mut Camera)>,
mut commands: Commands,
) { ) {
events events
.iter() .iter()
.filter(|(&interaction, _)| interaction == Interaction::Pressed) .filter(|(&interaction, _)| interaction == Interaction::Pressed)
.for_each(|(_, ui::TargetEntity { entity })| { .for_each(|(_, ui::TargetEntity { entity })| {
cameras.iter().for_each(|this_entity| { cameras.iter_mut().for_each(|(this_entity, mut camera)| {
if this_entity == *entity { camera.is_active = this_entity == *entity;
info!("Marking {:?} as active camera", entity);
commands.entity(this_entity).insert(ui::Active);
} else {
info!("Marking {:?} as inactive camera", entity);
commands.entity(this_entity).remove::<ui::Active>();
}
}); });
}); });
} }
/// Set the active camera based on the Active marker component
pub fn control_active_camera(
added: Query<Entity, (Added<ui::Active>, With<Camera>)>,
mut removed: RemovedComponents<ui::Active>,
mut cameras: Query<&mut Camera>,
) {
removed.iter().for_each(|entity| {
info!("Setting {:?} to inactive camera", entity);
if let Ok(mut camera) = cameras.get_mut(entity) {
camera.is_active = false;
}
});
added.iter().for_each(|entity| {
info!("Setting {:?} to active camera", entity);
if let Ok(mut camera) = cameras.get_mut(entity) {
camera.is_active = true;
}
});
}
// 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;
}
})
}
} }

@ -31,10 +31,10 @@ impl Plugin for GameUiPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_event::<Alert>() app.add_event::<Alert>()
.add_systems(Startup, init_alerts) .add_systems(Startup, init_alerts)
.add_systems(PreUpdate, create_titles)
.add_systems( .add_systems(
Update, Update,
( (
create_titles,
manage_titles, manage_titles,
manage_button_interaction, manage_button_interaction,
manage_select_active, manage_select_active,
@ -55,39 +55,9 @@ pub use title::*;
mod title { mod title {
use super::*; use super::*;
#[derive(Debug, Component, Default)] #[derive(Debug, Component)]
pub struct Title { pub struct Title {
pub text: String, pub text: String,
pub font: Option<Handle<Font>>,
}
#[derive(Debug)]
pub struct TitleBarBase {
bg_color: Color,
}
impl TitleBarBase {
pub fn new(bg_color: Color) -> Self {
Self { bg_color }
}
pub fn bundle(&self) -> NodeBundle {
NodeBundle {
style: Style {
padding: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
border: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Row,
align_items: AlignItems::Center,
align_content: AlignContent::Center,
justify_content: JustifyContent::SpaceBetween,
..default()
},
background_color: self.bg_color.into(),
border_color: Color::BLACK.into(),
..default()
}
}
} }
#[derive(Debug, Component)] #[derive(Debug, Component)]
@ -123,24 +93,16 @@ mod title {
) { ) {
events.for_each(|(entity, title, note, minimize, close)| { events.for_each(|(entity, title, note, minimize, close)| {
commands.entity(entity).with_children(|parent| { commands.entity(entity).with_children(|parent| {
let style = match &title.font {
Some(handle) => TextStyle {
color: Color::BLACK,
font: handle.clone(),
..default()
},
None => TextStyle {
color: Color::BLACK,
..default()
},
};
parent.spawn(( parent.spawn((
TextBundle { TextBundle {
text: Text { text: Text {
sections: vec![ sections: vec![
TextSection { TextSection {
value: title.text.clone(), value: title.text.clone(),
style, style: TextStyle {
color: Color::BLACK,
..default()
},
}, },
TextSection { TextSection {
value: note value: note
@ -329,19 +291,19 @@ mod collapse {
collapses: Query<&Collapse, With<Button>>, collapses: Query<&Collapse, With<Button>>,
mut styles: Query<&mut Style>, mut styles: Query<&mut Style>,
) { ) {
// Removed collapse, hide the target entity // Added collapse, display the target entity
removed.iter().for_each(|e| { added.iter().for_each(|e| {
if let Ok(Collapse { target }) = collapses.get(e) { if let Ok(Collapse { target }) = collapses.get(e) {
if let Ok(mut style) = styles.get_mut(*target) { if let Ok(mut style) = styles.get_mut(*target) {
style.display = Display::None; style.display = Display::Flex;
} }
} }
}); });
// Added collapse, display the target entity // Removed collapse, hide the target entity
added.iter().for_each(|e| { removed.iter().for_each(|e| {
if let Ok(Collapse { target }) = collapses.get(e) { if let Ok(Collapse { target }) = collapses.get(e) {
if let Ok(mut style) = styles.get_mut(*target) { if let Ok(mut style) = styles.get_mut(*target) {
style.display = Display::Flex; style.display = Display::None;
} }
} }
}); });
@ -390,14 +352,14 @@ mod buttons {
mut removed_active: RemovedComponents<Active>, mut removed_active: RemovedComponents<Active>,
mut bg_colors: Query<&mut BackgroundColor>, mut bg_colors: Query<&mut BackgroundColor>,
) { ) {
removed_active.iter().for_each(|e| { added_active.for_each(|e| {
if let Ok(mut bg_color) = bg_colors.get_mut(e) { if let Ok(mut bg_color) = bg_colors.get_mut(e) {
*bg_color = Color::WHITE.into(); *bg_color = Color::ORANGE.into();
} }
}); });
added_active.for_each(|e| { removed_active.iter().for_each(|e| {
if let Ok(mut bg_color) = bg_colors.get_mut(e) { if let Ok(mut bg_color) = bg_colors.get_mut(e) {
*bg_color = Color::ORANGE.into(); *bg_color = Color::WHITE.into();
} }
}); });
events.for_each(|(entity, interaction, active)| { events.for_each(|(entity, interaction, active)| {
@ -608,43 +570,36 @@ pub mod alert {
justify_self: JustifySelf::Center, justify_self: JustifySelf::Center,
..default() ..default()
}, },
background_color: Color::WHITE.into(),
border_color: color.into(), border_color: color.into(),
..default() ..default()
}) })
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
TitleBarBase::new(color).bundle(), NodeBundle {
style: Style {
padding: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
border: UiRect::all(Val::Px(1.0)),
flex_direction: FlexDirection::Row,
align_items: AlignItems::Center,
align_content: AlignContent::Center,
justify_content: JustifyContent::SpaceBetween,
..default()
},
background_color: color.into(),
..default()
},
Title { Title {
text: "Alert".into(), text: "Alert".into(),
..default()
}, },
Close { Close {
target: parent.parent_entity(), target: parent.parent_entity(),
}, },
Sorting(0), Sorting(0),
)); ));
parent.spawn(TextBundle::from_section( parent.spawn(TextBundle::from_section(text, TextStyle { ..default() }));
text,
TextStyle {
color: Color::BLACK.into(),
..default()
},
));
}); });
}); });
}); });
} }
} }
use dragndrop::*;
pub mod dragndrop {
use super::*;
#[derive(Debug, Component)]
struct DragNDrop;
pub fn dragndrop() {
todo!()
}
}

Loading…
Cancel
Save