Compare commits

...

9 Commits

@ -0,0 +1,10 @@
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.

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

@ -1,47 +1,52 @@
// Monologue Trees Editor
// Monologe Trees Editor
//
// Editor for creating Monologue Trees levels
//
// BUGS:
// * Cannot view scene when selected, WTF
// * Scene and Animation tabs are whacked
// * Camera order ambiguity
// * Multi-GLTF UX is bad.
// * Consider GLTF hierarchy (GLTF1 > Scene1a/Scene1b, GlTF2 > Scene2a/Scene2b, etc)
// * Easy despawn when de-selecting gltf
//
// 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
// * (medium) Add fonts similar to Audios based on inspect-fonts
// * (hard) Add Dialogs (requires text box UI, saving, loading).
// * (easy) Clear button to wipe spawned scene
// * (brutal) export level
// * (hard) import level
//
// Asset types:
// * Audios (done)
// * Loop individual
// * Stop all
// * Loop individual (done)
// * Gltfs (doing)
// * Scenes
// * Animations
// * Play/Pause all
// * Fonts
// * Monologues
// * Fonts (done)
// * Monologues (done)
use bevy::{
asset::{Asset, Assets},
asset::{AssetLoader, LoadContext, LoadedAsset},
audio::PlaybackMode,
gltf::Gltf,
input::{keyboard::KeyboardInput, ButtonState},
prelude::*,
utils::BoxedFuture,
};
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() {
App::new()
.add_plugins((
@ -61,25 +66,41 @@ fn main() {
.init_asset_loader::<MonologueLoader>()
.add_event::<CustomAssetEvent<Scene>>()
.add_event::<CustomAssetEvent<AnimationClip>>()
.add_systems(Startup, (initialize_ui, welcome_message))
.add_systems(Startup, (initialize_ui, init_texts_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(
Update,
(
import_files,
audio_ui,
gltf_ui,
fonts_ui,
texts_ui,
cameras_ui,
manage_active_gltf,
manage_gltf_scene_ui,
manage_gltf_animation_ui,
scenes_ui,
animations_ui,
spawn_scenes,
manage_camera,
play_animation,
play_audio,
show_preview_text,
sync_monologue_font,
),
)
.run();
@ -106,13 +127,14 @@ pub struct EditorCamera;
fn initialize_ui(mut commands: Commands) {
// Empty entity for populating the level being edited
commands.spawn((TransformBundle { ..default() }, LevelRoot));
commands.spawn((SpatialBundle { ..default() }, LevelRoot));
commands.spawn((
Camera3dBundle { ..default() },
Camera2dBundle { ..default() },
UiCameraConfig { show_ui: true },
Name::new("Editor Camera"),
EditorCamera,
ui::Active,
));
let base_style = Style {
@ -184,6 +206,11 @@ fn initialize_ui(mut commands: Commands) {
parent,
ui::Select::Single,
));
content_containers.push(spawn_tab_container::<MonologueWidget>(
"Monologue",
parent,
ui::Select::Single,
));
content_containers.push(spawn_tab_container::<AudioWidget>(
"Audio",
parent,
@ -197,7 +224,7 @@ fn initialize_ui(mut commands: Commands) {
content_containers.push(spawn_tab_container::<SceneWidget>(
"Scene",
parent,
ui::Select::Single,
ui::Select::Multi,
));
content_containers.push(spawn_tab_container::<AnimationWidget>(
"Animation",
@ -242,7 +269,10 @@ fn initialize_ui(mut commands: Commands) {
content_containers.iter().for_each(|(name, target)| {
parent.spawn((
b.clone(),
ui::Title { text: name.clone() },
ui::Title {
text: name.clone(),
..default()
},
ui::Collapse { target: *target },
));
});
@ -250,24 +280,10 @@ fn initialize_ui(mut commands: Commands) {
})
.id();
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(),
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::TitleBarBase::new(Color::WHITE).bundle(),
ui::Title {
text: "Assets".into(),
..default()
},
ui::Minimize { target: container },
ui::Sorting(0),
@ -276,21 +292,9 @@ fn initialize_ui(mut commands: Commands) {
}
fn welcome_message(mut writer: EventWriter<ui::Alert>) {
writer.send(ui::Alert::Info(
"Welcome to the Monologue Trees editor!".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(),
));
WELCOME_MESSAGES
.iter()
.for_each(|&msg| writer.send(ui::Alert::Info(msg.into())))
}
fn spawn_tab_container<T: Default + Component>(
@ -361,16 +365,7 @@ mod audio {
current: Query<(Entity, &ui::TargetAsset<AudioSource>)>,
server: Res<AssetServer>,
) {
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 {
events.iter().for_each(|event| match event {
AssetEvent::Created { handle } => {
info!("Asset created! {:?}", event);
let id = create_asset_button(
@ -380,6 +375,7 @@ mod audio {
handle: handle.clone(),
},
get_asset_name(&server, handle.clone()),
None,
);
commands.entity(id).insert(AudioSourceBundle {
source: handle.clone(),
@ -416,6 +412,7 @@ mod audio {
handle: handle.clone(),
},
get_asset_name(&server, handle.clone()),
None,
);
commands.entity(id).insert(AudioSourceBundle {
source: handle.clone(),
@ -473,6 +470,7 @@ mod assets {
commands: &mut Commands,
target: ui::TargetAsset<A>,
name: String,
font: Option<Handle<Font>>,
) -> Entity {
commands
.spawn((
@ -487,7 +485,10 @@ mod assets {
border_color: Color::BLACK.into(),
..default()
},
ui::Title { text: name },
ui::Title {
text: name,
font: font.clone(),
},
))
.set_parent(root.single())
.id()
@ -512,7 +513,10 @@ mod assets {
border_color: Color::BLACK.into(),
..default()
},
ui::Title { text: name },
ui::Title {
text: name,
..default()
},
))
.set_parent(root.single())
.id()
@ -534,6 +538,22 @@ 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>(
server: &AssetServer,
handle: Handle<T>,
@ -566,16 +586,7 @@ mod gltf {
current: Query<(Entity, &ui::TargetAsset<Gltf>)>,
server: Res<AssetServer>,
) {
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 {
events.iter().for_each(|event| match event {
AssetEvent::Created { handle } => {
info!("Asset created! {:?}", event);
create_asset_button(
@ -585,6 +596,7 @@ mod gltf {
handle: handle.clone(),
},
get_asset_name(&server, handle.clone()),
None,
);
}
AssetEvent::Removed { handle } => {
@ -613,6 +625,7 @@ mod gltf {
handle: handle.clone(),
},
get_asset_name(&server, handle.clone()),
None,
);
}
});
@ -751,18 +764,25 @@ mod scenes {
current: Query<(Entity, &ui::TargetAsset<Scene>)>,
) {
events.iter().for_each(|event| {
let empty = current.iter().len() == 0;
match event {
CustomAssetEvent::Add { name, handle } => {
info!("Asset loading! {:?}({:?})", name, handle);
// Spawn new tree
create_asset_button(
let e = create_asset_button(
&widget,
&mut commands,
ui::TargetAsset {
handle: handle.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 } => {
destroy_asset_button(
@ -780,25 +800,42 @@ mod scenes {
});
}
pub fn spawn_scenes(
events: Query<
(&Interaction, &ui::TargetAsset<Scene>),
(With<Button>, Changed<Interaction>),
>,
pub fn control_active_scenes(
added: Query<Entity, (With<Button>, Added<ui::Active>)>,
mut removed: RemovedComponents<ui::Active>,
scene_refs: Query<&ui::TargetAsset<Scene>>,
scenes: Query<(Entity, &Handle<Scene>)>,
level_root: Query<Entity, With<LevelRoot>>,
mut commands: Commands,
) {
events
.iter()
.filter(|(&interaction, _)| interaction == Interaction::Pressed)
.for_each(|(_, ui::TargetAsset { handle })| {
info!("Spawning {:?}", handle);
// A scene button was marked inactive
removed.iter().for_each(|entity| {
// Get the handle associated with that button
if let Ok(ui::TargetAsset { handle }) = scene_refs.get(entity) {
if let Some(entity) = scenes.iter().find_map(|(entity, this_handle)| {
if this_handle == handle {
Some(entity)
} else {
None
}
}) {
commands.entity(entity).despawn_recursive();
}
}
});
added.iter().for_each(|entity| {
if let Ok(ui::TargetAsset { handle }) = scene_refs.get(entity) {
info!("Spawning Scene {:?}", handle);
commands
.entity(level_root.single())
.with_children(|parent| {
parent.spawn((handle.clone(), TransformBundle { ..default() }));
parent.spawn(SceneBundle {
scene: handle.clone(),
..default()
});
});
}
});
})
}
}
@ -811,6 +848,36 @@ mod animations {
#[derive(Debug, Component, Default)]
pub struct AnimationWidget;
#[derive(Debug, Component)]
pub struct AnimationPlayAll;
pub fn init_animations_ui(
events: Query<Entity, Added<AnimationWidget>>,
mut commands: Commands,
) {
events.iter().for_each(|entity| {
commands.entity(entity).with_children(|parent| {
parent.spawn((
AnimationPlayAll,
ButtonBundle {
style: Style {
border: UiRect::all(Val::Px(1.0)),
margin: UiRect::all(Val::Px(1.0)),
padding: UiRect::all(Val::Px(1.0)),
..default()
},
border_color: Color::BLACK.into(),
..default()
},
ui::Title {
text: "Play All".into(),
..default()
},
));
});
})
}
pub fn animations_ui(
mut events: EventReader<CustomAssetEvent<AnimationClip>>,
mut commands: Commands,
@ -829,6 +896,7 @@ mod animations {
handle: handle.clone(),
},
name.clone(),
None,
);
}
CustomAssetEvent::Remove { handle } => {
@ -847,25 +915,62 @@ 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(
events: Query<
(&Interaction, &ui::TargetAsset<AnimationClip>),
(With<Button>, Changed<Interaction>),
>,
start: Query<Entity, (With<Button>, Added<ui::Active>)>,
mut stop: RemovedComponents<ui::Active>,
clip_refs: Query<&ui::TargetAsset<AnimationClip>>,
mut targets: Query<(&mut AnimationPlayer, &Name), With<Transform>>,
clips: Res<Assets<AnimationClip>>,
) {
events
.iter()
.filter(|(&interaction, _)| interaction == Interaction::Pressed)
.for_each(|(_, ui::TargetAsset { handle })| {
let clip = clips.get(handle).expect("Load animation clip");
stop.iter().for_each(|entity| {
if let Ok(ui::TargetAsset { handle }) = clip_refs.get(entity) {
let clip = clips.get(&handle).expect("Load animation clip");
targets
.iter_mut()
.filter(|(_, name)| clip.compatible_with(name))
.for_each(|(mut player, _)| {
player.pause();
})
}
});
start.iter().for_each(|entity| {
if let Ok(ui::TargetAsset { handle }) = clip_refs.get(entity) {
let clip = clips.get(&handle).expect("Load animation clip");
targets
.iter_mut()
.filter(|(_, name)| clip.compatible_with(name))
.for_each(|(mut player, _)| {
if player.is_paused() {
player.resume();
} else {
player.play(handle.clone()).repeat();
}
})
}
});
}
}
@ -877,7 +982,6 @@ mod fonts {
#[derive(Debug, Component, Default)]
pub struct FontWidget;
// TODO: Make each button have the font
pub fn fonts_ui(
mut events: EventReader<AssetEvent<Font>>,
mut commands: Commands,
@ -885,16 +989,7 @@ mod fonts {
current: Query<(Entity, &ui::TargetAsset<Font>)>,
server: Res<AssetServer>,
) {
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 {
events.iter().for_each(|event| match event {
AssetEvent::Created { handle } => {
info!("Asset created! {:?}", event);
create_asset_button(
@ -904,6 +999,7 @@ mod fonts {
handle: handle.clone(),
},
get_asset_name(&server, handle.clone()),
Some(handle.clone()),
);
}
AssetEvent::Removed { handle } => {
@ -932,6 +1028,7 @@ mod fonts {
handle: handle.clone(),
},
get_asset_name(&server, handle.clone()),
Some(handle.clone()),
);
}
});
@ -941,7 +1038,10 @@ mod fonts {
use monologues::*;
mod monologues {
use super::*;
use bevy::reflect::{TypePath, TypeUuid};
use bevy::{
reflect::{TypePath, TypeUuid},
ui::FocusPolicy,
};
use serde::Deserialize;
#[derive(Debug, Component, Default)]
@ -953,6 +1053,12 @@ mod monologues {
text: String,
}
#[derive(Debug, Component)]
pub struct MonologueModal;
#[derive(Debug, Component)]
pub struct MonologueContainer;
#[derive(Default)]
pub struct MonologueLoader;
@ -963,29 +1069,160 @@ mod monologues {
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, Result<(), bevy::asset::Error>> {
Box::pin(async move {
load_context.set_default_asset(LoadedAsset::new(
String::from_utf8(bytes.to_vec()).expect("Convert bytes to String"),
));
let asset = Monologue {
text: String::from_utf8(bytes.to_vec())?,
};
load_context.set_default_asset(LoadedAsset::new(asset));
Ok(())
})
}
fn extensions(&self) -> &[&str] {
&[".monologue.txt"]
&["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
pub fn texts_ui(
mut events: EventReader<AssetEvent<Monologue>>,
mut _commands: Commands,
_widget: Query<Entity, With<MonologueWidget>>,
_current: Query<(Entity, &ui::TargetAsset<Monologue>)>,
_server: Res<AssetServer>,
mut commands: Commands,
widget: Query<Entity, With<MonologueWidget>>,
current: Query<(Entity, &ui::TargetAsset<Monologue>)>,
server: Res<AssetServer>,
) {
events.iter().for_each(|event| match event {
AssetEvent::Created { handle } => {
info!("Monologue created! {:?}", event);
create_asset_button(
&widget,
&mut commands,
ui::TargetAsset {
handle: handle.clone(),
},
get_asset_name(&server, handle.clone()),
None,
);
}
AssetEvent::Removed { handle } => {
info!("Monologue removed! {:?}", event);
destroy_asset_button(
&current,
&mut commands,
&ui::TargetAsset {
handle: handle.clone(),
},
);
}
AssetEvent::Modified { handle } => {
info!("Monologue modified! {:?}", event);
destroy_asset_button(
&current,
&mut commands,
&ui::TargetAsset {
handle: handle.clone(),
},
);
create_asset_button(
&widget,
&mut commands,
ui::TargetAsset {
handle: handle.clone(),
},
get_asset_name(&server, handle.clone()),
None,
);
}
});
}
// 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,
) {
events.iter().for_each(|_event| {
info!("Loading monologue");
added
.iter()
.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();
});
});
});
}
}
@ -998,33 +1235,90 @@ mod cameras {
// TODO: Despawn camera button when camera removed
pub fn cameras_ui(
mut events: Query<(Entity, &mut Camera, &Name), (Added<Camera>, Without<EditorCamera>)>,
mut added: Query<(Entity, &mut Camera, &Name), (Added<Camera>, Without<EditorCamera>)>,
mut removed: RemovedComponents<Camera>,
widget: Query<Entity, With<CameraWidget>>,
current: Query<(Entity, &ui::TargetEntity)>,
mut commands: Commands,
) {
events.iter_mut().for_each(|(entity, mut camera, name)| {
removed.iter().for_each(|entity| {
info!("Destroy button for {:?}", entity);
destroy_entity_button(&current, &mut commands, &ui::TargetEntity { entity });
});
added.iter_mut().for_each(|(entity, mut camera, name)| {
let empty = current.iter().len() == 0;
info!("Camera added {:?} {:?}", entity, name);
create_entity_button(
let e = create_entity_button(
&widget,
&mut commands,
ui::TargetEntity { entity },
name.as_str().into(),
);
if empty {
commands.entity(e).insert(ui::Active);
} else {
camera.is_active = false;
})
}
});
}
pub fn manage_camera(
/// Set the camera active component based on button clicks
pub fn manage_active_camera(
events: Query<(&Interaction, &ui::TargetEntity), Changed<Interaction>>,
mut cameras: Query<(Entity, &mut Camera)>,
cameras: Query<Entity, With<Camera>>,
mut commands: Commands,
) {
events
.iter()
.filter(|(&interaction, _)| interaction == Interaction::Pressed)
.for_each(|(_, ui::TargetEntity { entity })| {
cameras.iter_mut().for_each(|(this_entity, mut camera)| {
camera.is_active = this_entity == *entity;
cameras.iter().for_each(|this_entity| {
if 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) {
app.add_event::<Alert>()
.add_systems(Startup, init_alerts)
.add_systems(PreUpdate, create_titles)
.add_systems(
Update,
(
create_titles,
manage_titles,
manage_button_interaction,
manage_select_active,
@ -55,9 +55,39 @@ pub use title::*;
mod title {
use super::*;
#[derive(Debug, Component)]
#[derive(Debug, Component, Default)]
pub struct Title {
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)]
@ -93,16 +123,24 @@ mod title {
) {
events.for_each(|(entity, title, note, minimize, close)| {
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((
TextBundle {
text: Text {
sections: vec![
TextSection {
value: title.text.clone(),
style: TextStyle {
color: Color::BLACK,
..default()
},
style,
},
TextSection {
value: note
@ -291,19 +329,19 @@ mod collapse {
collapses: Query<&Collapse, With<Button>>,
mut styles: Query<&mut Style>,
) {
// Added collapse, display the target entity
added.iter().for_each(|e| {
// Removed collapse, hide the target entity
removed.iter().for_each(|e| {
if let Ok(Collapse { target }) = collapses.get(e) {
if let Ok(mut style) = styles.get_mut(*target) {
style.display = Display::Flex;
style.display = Display::None;
}
}
});
// Removed collapse, hide the target entity
removed.iter().for_each(|e| {
// Added collapse, display the target entity
added.iter().for_each(|e| {
if let Ok(Collapse { target }) = collapses.get(e) {
if let Ok(mut style) = styles.get_mut(*target) {
style.display = Display::None;
style.display = Display::Flex;
}
}
});
@ -352,14 +390,14 @@ mod buttons {
mut removed_active: RemovedComponents<Active>,
mut bg_colors: Query<&mut BackgroundColor>,
) {
added_active.for_each(|e| {
removed_active.iter().for_each(|e| {
if let Ok(mut bg_color) = bg_colors.get_mut(e) {
*bg_color = Color::ORANGE.into();
*bg_color = Color::WHITE.into();
}
});
removed_active.iter().for_each(|e| {
added_active.for_each(|e| {
if let Ok(mut bg_color) = bg_colors.get_mut(e) {
*bg_color = Color::WHITE.into();
*bg_color = Color::ORANGE.into();
}
});
events.for_each(|(entity, interaction, active)| {
@ -570,36 +608,43 @@ pub mod alert {
justify_self: JustifySelf::Center,
..default()
},
background_color: Color::WHITE.into(),
border_color: color.into(),
..default()
})
.with_children(|parent| {
parent.spawn((
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()
},
TitleBarBase::new(color).bundle(),
Title {
text: "Alert".into(),
..default()
},
Close {
target: parent.parent_entity(),
},
Sorting(0),
));
parent.spawn(TextBundle::from_section(text, TextStyle { ..default() }));
parent.spawn(TextBundle::from_section(
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