nightshade 0.8.0

A cross-platform data-oriented game engine.
Documentation
pub mod animation;
pub mod audio;
pub mod bounding_volume;
pub mod camera;
#[cfg(feature = "physics")]
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;
#[cfg(feature = "navmesh")]
pub mod navmesh;
pub mod navmesh_agent;
pub mod particle_emitter;
#[cfg(feature = "physics")]
pub mod physics;
pub mod render_layer;
#[cfg(feature = "scripting")]
pub mod script;
pub mod text;
pub mod transform;
pub mod visibility;
pub mod water;

use super::selection::EntitySelection;
use super::undo::UndoHistory;
use crate::mosaic::ToastKind;
use crate::prelude::*;
use animation::*;
use audio::*;
use bounding_volume::*;
use camera::*;
#[cfg(feature = "physics")]
use character_controller::*;
use decal::*;
use grass::*;
use instanced_mesh::*;
use light::*;
use lines::*;
use material::*;
use mesh::*;
use name::*;
use navmesh_agent::*;
use particle_emitter::*;
#[cfg(feature = "physics")]
use physics::*;
use render_layer::*;
#[cfg(feature = "scripting")]
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,
    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.$has(entity)
            }

            fn add_component(&self, world: &mut World, entity: Entity) {
                world.$set(entity, $default_val);
            }

            fn remove_component(&self, world: &mut World, entity: Entity) {
                world.$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.$has(entity)
            }

            fn add_component(&self, world: &mut World, entity: Entity) {
                world.$set(entity, $default_val);
            }

            fn remove_component(&self, world: &mut World, entity: Entity) {
                world.$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.$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.$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),
            #[cfg(feature = "physics")]
            Box::new(CharacterControllerInspector),
            #[cfg(feature = "physics")]
            Box::new(PhysicsInspector),
            #[cfg(feature = "scripting")]
            Box::new(ScriptInspector::default()),
        ];

        Self { inspectors }
    }
}

impl ComponentInspectorUi {
    pub fn add_inspector(&mut self, inspector: Box<dyn ComponentInspector>) {
        self.inspectors.push(inspector);
    }

    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();
                }
            }

            #[cfg(feature = "navmesh")]
            {
                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
    }
}