pub mod animation;
pub mod audio;
pub mod bounding_volume;
pub mod camera;
pub mod character_controller;
pub mod decal;
pub mod grass;
pub mod helpers;
pub mod instanced_mesh;
pub mod light;
pub mod lines;
pub mod material;
pub mod mesh;
pub mod name;
pub mod navmesh;
pub mod navmesh_agent;
pub mod particle_emitter;
pub mod physics;
pub mod render_layer;
pub mod script;
pub mod text;
pub mod transform;
pub mod visibility;
pub mod water;
use super::selection::EntitySelection;
use super::undo::UndoHistory;
#[cfg(not(target_arch = "wasm32"))]
use crate::mosaic::ToastKind;
use animation::*;
use audio::*;
use bounding_volume::*;
use camera::*;
use character_controller::*;
use decal::*;
use grass::*;
use instanced_mesh::*;
use light::*;
use lines::*;
use material::*;
use mesh::*;
use name::*;
use navmesh_agent::*;
use nightshade::prelude::*;
use particle_emitter::*;
use physics::*;
use render_layer::*;
use script::*;
use text::*;
use transform::*;
use visibility::*;
use water::*;
#[derive(Clone)]
pub enum InspectorAction {
LookupMaterial(String),
}
pub struct InspectorContext<'a> {
pub transform_edit_pending: &'a mut Option<(Entity, LocalTransform)>,
pub undo_history: &'a mut UndoHistory,
#[cfg(not(target_arch = "wasm32"))]
pub pending_notifications: &'a mut Vec<(ToastKind, String)>,
pub actions: &'a mut Vec<InspectorAction>,
pub selection: &'a EntitySelection,
}
pub trait ComponentInspector {
fn name(&self) -> &str;
fn has_component(&self, world: &World, entity: Entity) -> bool;
fn add_component(&self, world: &mut World, entity: Entity);
fn remove_component(&self, world: &mut World, entity: Entity);
fn ui(
&mut self,
world: &mut World,
entity: Entity,
ui: &mut egui::Ui,
context: &mut InspectorContext,
);
}
macro_rules! impl_simple_inspector {
($struct_name:ident, $display_name:expr, $has:ident, $set:ident, $remove:ident, $default_val:expr, $ui_fn:path) => {
pub struct $struct_name;
impl ComponentInspector for $struct_name {
fn name(&self) -> &str {
$display_name
}
fn has_component(&self, world: &World, entity: Entity) -> bool {
world.core.$has(entity)
}
fn add_component(&self, world: &mut World, entity: Entity) {
world.core.$set(entity, $default_val);
}
fn remove_component(&self, world: &mut World, entity: Entity) {
world.core.$remove(entity);
}
fn ui(
&mut self,
world: &mut World,
entity: Entity,
ui: &mut egui::Ui,
context: &mut InspectorContext,
) {
$ui_fn(world, entity, ui, context);
}
}
};
($struct_name:ident, $display_name:expr, $has:ident, $set:ident, $remove:ident, $default_val:expr) => {
pub struct $struct_name;
impl ComponentInspector for $struct_name {
fn name(&self) -> &str {
$display_name
}
fn has_component(&self, world: &World, entity: Entity) -> bool {
world.core.$has(entity)
}
fn add_component(&self, world: &mut World, entity: Entity) {
world.core.$set(entity, $default_val);
}
fn remove_component(&self, world: &mut World, entity: Entity) {
world.core.$remove(entity);
}
fn ui(
&mut self,
_world: &mut World,
_entity: Entity,
ui: &mut egui::Ui,
_context: &mut InspectorContext,
) {
ui.label(concat!($display_name, " (no inspector)"));
}
}
};
($struct_name:ident, $display_name:expr, $has:ident, $remove:ident, $ui_fn:path, custom_add => $add_body:expr) => {
pub struct $struct_name;
impl ComponentInspector for $struct_name {
fn name(&self) -> &str {
$display_name
}
fn has_component(&self, world: &World, entity: Entity) -> bool {
world.core.$has(entity)
}
fn add_component(&self, world: &mut World, entity: Entity) {
$add_body(world, entity);
}
fn remove_component(&self, world: &mut World, entity: Entity) {
world.core.$remove(entity);
}
fn ui(
&mut self,
world: &mut World,
entity: Entity,
ui: &mut egui::Ui,
context: &mut InspectorContext,
) {
$ui_fn(world, entity, ui, context);
}
}
};
}
pub(crate) use impl_simple_inspector;
pub struct ComponentInspectorUi {
inspectors: Vec<Box<dyn ComponentInspector>>,
}
impl Default for ComponentInspectorUi {
fn default() -> Self {
let inspectors: Vec<Box<dyn ComponentInspector>> = vec![
Box::new(NameInspector),
Box::new(TransformInspector::default()),
Box::new(VisibilityInspector),
Box::new(CameraInspector),
Box::new(LightInspector),
Box::new(LinesInspector),
Box::new(MeshInspector),
Box::new(InstancedMeshInspector),
Box::new(MaterialInspector::default()),
Box::new(BoundingVolumeInspector),
Box::new(WaterInspector),
Box::new(DecalInspector),
Box::new(TextInspector),
Box::new(RenderLayerInspector),
Box::new(ParticleEmitterInspector),
Box::new(AudioSourceInspector),
Box::new(AudioListenerInspector),
Box::new(GrassRegionInspector),
Box::new(GrassInteractorInspector),
Box::new(NavMeshAgentInspector),
Box::new(AnimationInspector),
Box::new(CharacterControllerInspector),
Box::new(PhysicsInspector),
Box::new(ScriptInspector::default()),
];
Self { inspectors }
}
}
impl ComponentInspectorUi {
pub fn ui(
&mut self,
context: &mut InspectorContext,
world: &mut World,
ui: &mut egui::Ui,
) -> bool {
let mut project_modified = false;
let selection_count = context.selection.len();
if selection_count > 1 {
ui.vertical_centered(|ui| {
ui.add_space(8.0);
ui.label(format!("{} entities selected", selection_count));
ui.add_space(8.0);
});
return false;
}
if let Some(entity) = context.selection.primary() {
ui.group(|ui| {
ui.horizontal(|ui| {
ui.label("Add Component:");
egui::ComboBox::new("add_component", "").show_ui(ui, |ui| {
for inspector in self.inspectors.iter() {
if !inspector.has_component(world, entity)
&& ui.button(inspector.name()).clicked()
{
inspector.add_component(world, entity);
project_modified = true;
}
}
});
});
});
ui.separator();
for inspector in self.inspectors.iter_mut() {
if inspector.has_component(world, entity) {
ui.group(|ui| {
inspector.ui(world, entity, ui, context);
if ui.button("Remove Component").clicked() {
inspector.remove_component(world, entity);
project_modified = true;
}
});
ui.separator();
}
}
tags_metadata_section_ui(entity, world, ui);
navmesh::navmesh_section_ui(context.selection, world, ui);
navmesh::colliders_section_ui(context.selection, world, ui);
} else {
ui.vertical_centered(|ui| {
ui.add_space(8.0);
ui.label(
egui::RichText::new("No entity selected").color(egui::Color32::from_gray(128)),
);
ui.add_space(8.0);
});
}
project_modified
}
}
fn tags_metadata_section_ui(entity: Entity, world: &mut World, ui: &mut egui::Ui) {
let has_tags = world.resources.entity_tags.contains_key(&entity);
let has_metadata = world.resources.entity_metadata.contains_key(&entity);
if !has_tags && !has_metadata {
return;
}
ui.group(|ui| {
if has_tags {
ui.label(egui::RichText::new("Tags").strong());
let tags = world.resources.entity_tags.get(&entity).cloned();
if let Some(tags) = tags {
let mut remove_index = None;
for (index, tag) in tags.iter().enumerate() {
ui.horizontal(|ui| {
ui.label(tag);
if ui.small_button("\u{2715}").clicked() {
remove_index = Some(index);
}
});
}
if let Some(index) = remove_index
&& let Some(entity_tags) = world.resources.entity_tags.get_mut(&entity)
{
entity_tags.remove(index);
if entity_tags.is_empty() {
world.resources.entity_tags.remove(&entity);
}
}
}
ui.add_space(4.0);
}
if has_metadata {
ui.label(egui::RichText::new("Metadata").strong());
let metadata = world.resources.entity_metadata.get(&entity).cloned();
if let Some(metadata) = metadata {
for (key, value) in &metadata {
ui.horizontal(|ui| {
ui.label(egui::RichText::new(key).monospace());
ui.label(format_metadata_value(value));
});
}
}
}
});
ui.separator();
}
fn format_metadata_value(value: &nightshade::ecs::scene::MetadataValue) -> String {
match value {
nightshade::ecs::scene::MetadataValue::Bool(v) => v.to_string(),
nightshade::ecs::scene::MetadataValue::Integer(v) => v.to_string(),
nightshade::ecs::scene::MetadataValue::Float(v) => format!("{:.3}", v),
nightshade::ecs::scene::MetadataValue::String(v) => format!("\"{}\"", v),
nightshade::ecs::scene::MetadataValue::Array(arr) => {
format!("[{} items]", arr.len())
}
nightshade::ecs::scene::MetadataValue::Map(map) => {
format!("{{{} entries}}", map.len())
}
}
}