use bevy_ecs::component::Component;
use bevy_ecs::entity::Entity;
use bevy_ecs::prelude::World;
use crate::runtime::{
AmbientLight, Camera, DirectionalLight, MeshRenderer, Name, PointLight,
SceneBackground, SkyLight, SpotLight, ToneMapping, AMBIENT_LIGHT_COMPONENT,
BACKGROUND_COMPONENT, HUD_ELEMENT_COMPONENT, SKY_LIGHT_COMPONENT,
TILE_MAP_COMPONENT, TONE_MAPPING_COMPONENT,
};
pub(in crate::editor) const ENVIRONMENT_COMPONENTS: [&str; 3] = [
SKY_LIGHT_COMPONENT,
AMBIENT_LIGHT_COMPONENT,
TONE_MAPPING_COMPONENT,
];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Kind {
Behavior,
Environment,
Hud,
TileMap,
}
fn kind(name: &str) -> Kind {
match name {
AMBIENT_LIGHT_COMPONENT
| SKY_LIGHT_COMPONENT
| TONE_MAPPING_COMPONENT
| BACKGROUND_COMPONENT => Kind::Environment,
HUD_ELEMENT_COMPONENT => Kind::Hud,
TILE_MAP_COMPONENT => Kind::TileMap,
_ => Kind::Behavior,
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(in crate::editor) enum Placement {
Allowed,
Blocked(String),
Hidden,
}
pub(in crate::editor) fn placement(
world: &World,
entity: Entity,
name: &str,
present: &[&str],
) -> Placement {
let wanted = kind(name);
if wanted == Kind::Behavior {
return Placement::Allowed;
}
let object = world.entity(entity);
let built_in = object.contains::<MeshRenderer>()
|| object.contains::<Camera>()
|| object.contains::<DirectionalLight>()
|| object.contains::<PointLight>()
|| object.contains::<SpotLight>();
let other_kind = present.iter().any(|present| {
let present = kind(present);
present != Kind::Behavior && present != wanted
});
if built_in || other_kind {
return Placement::Hidden;
}
let holder = match name {
SKY_LIGHT_COMPONENT => first_with::<SkyLight>(world),
AMBIENT_LIGHT_COMPONENT => first_with::<AmbientLight>(world),
TONE_MAPPING_COMPONENT => first_with::<ToneMapping>(world),
BACKGROUND_COMPONENT => first_with::<SceneBackground>(world),
_ => None,
};
match holder {
Some(holder) if holder != entity => {
let holder = world
.get::<Name>(holder)
.map_or("another object", |name| name.0.as_str());
Placement::Blocked(format!(
"The scene already has a {} on {holder}",
component_label(name)
))
}
_ => Placement::Allowed,
}
}
pub(in crate::editor) fn scene_has_environment(world: &World) -> bool {
first_with::<SkyLight>(world).is_some()
}
fn first_with<T: Component>(world: &World) -> Option<Entity> {
let mut query = world.try_query::<(Entity, &T)>()?;
query
.iter(world)
.map(|(entity, _)| entity)
.min_by_key(|entity| entity.index_u32())
}
pub(in crate::editor) fn component_label(name: &str) -> String {
match name {
HUD_ELEMENT_COMPONENT => return "HUD Element".to_owned(),
BACKGROUND_COMPONENT => return "Background Color".to_owned(),
_ => {}
}
let short = name.rsplit('.').next().unwrap_or(name);
short
.split(['_', '-'])
.filter(|word| !word.is_empty())
.map(|word| {
let mut chars = word.chars();
chars.next().map_or_else(String::new, |first| {
first.to_uppercase().chain(chars).collect()
})
})
.collect::<Vec<_>>()
.join(" ")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime::App;
use crate::runtime::{
PICKUP_COMPONENT, SCENE_INSTANCE_COMPONENT, TWEEN_COMPONENT,
};
use crate::Transform;
#[test]
fn component_labels_drop_the_namespace() {
assert_eq!(component_label(SKY_LIGHT_COMPONENT), "Sky Light");
assert_eq!(component_label(HUD_ELEMENT_COMPONENT), "HUD Element");
assert_eq!(component_label(SCENE_INSTANCE_COMPONENT), "Scene Instance");
assert_eq!(component_label("my_game.health"), "Health");
assert_eq!(component_label("score"), "Score");
}
#[test]
fn environment_and_hud_stay_off_meshes_and_each_other() {
let mut app = App::new();
let world = app.world_mut();
let lamp = world
.spawn((Transform::default(), PointLight::default()))
.id();
let empty = world.spawn(Transform::default()).id();
let sky = world
.spawn((Name("Environment".into()), SkyLight::default()))
.id();
let world = app.world();
for name in [SKY_LIGHT_COMPONENT, HUD_ELEMENT_COMPONENT] {
assert_eq!(placement(world, lamp, name, &[]), Placement::Hidden);
}
for name in [TWEEN_COMPONENT, PICKUP_COMPONENT] {
assert_eq!(placement(world, lamp, name, &[]), Placement::Allowed);
}
assert_eq!(
placement(world, empty, HUD_ELEMENT_COMPONENT, &[]),
Placement::Allowed
);
assert_eq!(
placement(world, empty, TONE_MAPPING_COMPONENT, &[]),
Placement::Allowed
);
assert_eq!(
placement(world, empty, SKY_LIGHT_COMPONENT, &[]),
Placement::Blocked(
"The scene already has a Sky Light on Environment".into()
)
);
let present = [SKY_LIGHT_COMPONENT];
assert_eq!(
placement(world, sky, AMBIENT_LIGHT_COMPONENT, &present),
Placement::Allowed
);
assert_eq!(
placement(world, sky, HUD_ELEMENT_COMPONENT, &present),
Placement::Hidden
);
}
}