nightshade 0.8.0

A cross-platform data-oriented game engine.
Documentation
use super::helpers::material_properties_ui;
use super::{ComponentInspector, InspectorContext};
use crate::ecs::generational_registry::{registry_entry_by_name, registry_entry_by_name_mut};
use crate::mosaic::ToastKind;
use crate::prelude::*;

#[derive(Default)]
pub struct MaterialInspector {
    editing: bool,
}

fn color_swatch(ui: &mut egui::Ui, color: egui::Color32) {
    let (rect, _) = ui.allocate_exact_size(egui::vec2(48.0, 18.0), egui::Sense::hover());
    ui.painter().rect_filled(rect, 2.0, color);
    ui.painter().rect_stroke(
        rect,
        2.0,
        egui::Stroke::new(1.0, egui::Color32::GRAY),
        egui::StrokeKind::Inside,
    );
}

fn material_display_section(
    ui: &mut egui::Ui,
    material: &Material,
    material_name: &str,
    context: &mut InspectorContext,
    ref_count: usize,
    editing: &mut bool,
) {
    ui.horizontal(|ui| {
        ui.label("Base Color:");
        let color = egui::Color32::from_rgba_unmultiplied(
            (material.base_color[0] * 255.0) as u8,
            (material.base_color[1] * 255.0) as u8,
            (material.base_color[2] * 255.0) as u8,
            (material.base_color[3] * 255.0) as u8,
        );
        color_swatch(ui, color);
    });

    ui.horizontal(|ui| {
        ui.label("Alpha Mode:");
        ui.label(match material.alpha_mode {
            AlphaMode::Opaque => "Opaque",
            AlphaMode::Mask => "Mask",
            AlphaMode::Blend => "Blend",
        });
    });

    if material.alpha_mode == AlphaMode::Mask {
        ui.horizontal(|ui| {
            ui.label("Alpha Cutoff:");
            ui.label(format!("{:.2}", material.alpha_cutoff));
        });
    }

    ui.horizontal(|ui| {
        ui.label("Roughness:");
        ui.label(format!("{:.2}", material.roughness));
        ui.label("Metallic:");
        ui.label(format!("{:.2}", material.metallic));
    });

    if material.unlit {
        ui.label("Unlit: Yes");
    }

    if material.emissive_factor[0] > 0.0
        || material.emissive_factor[1] > 0.0
        || material.emissive_factor[2] > 0.0
    {
        ui.horizontal(|ui| {
            ui.label("Emissive:");
            let color = egui::Color32::from_rgb(
                (material.emissive_factor[0] * 255.0) as u8,
                (material.emissive_factor[1] * 255.0) as u8,
                (material.emissive_factor[2] * 255.0) as u8,
            );
            color_swatch(ui, color);
        });
    }

    if let Some(texture) = &material.base_texture {
        ui.horizontal(|ui| {
            ui.label("Base Texture:");
            ui.label(texture);
        });
    }

    if let Some(texture) = &material.emissive_texture {
        ui.horizontal(|ui| {
            ui.label("Emissive Texture:");
            ui.label(texture);
        });
    }

    ui.separator();

    ui.small(format!("Used by {} entities", ref_count));

    ui.horizontal(|ui| {
        if ui.button("Lookup Material").clicked() {
            context.actions.push(super::InspectorAction::LookupMaterial(
                material_name.to_string(),
            ));
        }
        if ui.button("Edit Material").clicked() {
            *editing = true;
        }
    });
}

impl ComponentInspector for MaterialInspector {
    fn name(&self) -> &str {
        "Material"
    }

    fn has_component(&self, world: &World, entity: Entity) -> bool {
        world.get_material_ref(entity).is_some()
    }

    fn add_component(&self, world: &mut World, entity: Entity) {
        if let Some(&index) = world
            .resources
            .material_registry
            .registry
            .name_to_index
            .get("Default")
        {
            world
                .resources
                .material_registry
                .registry
                .add_reference(index);
        }
        world.set_material_ref(entity, MaterialRef::new("Default".to_string()));
    }

    fn remove_component(&self, world: &mut World, entity: Entity) {
        let material_name = world.get_material_ref(entity).map(|r| r.name.clone());
        if let Some(name) = material_name
            && let Some(&index) = world
                .resources
                .material_registry
                .registry
                .name_to_index
                .get(&name)
        {
            world
                .resources
                .material_registry
                .registry
                .remove_reference(index);
        }
        world.remove_material_ref(entity);
    }

    fn ui(
        &mut self,
        world: &mut World,
        entity: Entity,
        ui: &mut egui::Ui,
        context: &mut InspectorContext,
    ) {
        ui.label("Material");

        let material_name = if let Some(material_ref) = world.get_material_ref(entity) {
            material_ref.name.clone()
        } else {
            return;
        };

        let available_materials: Vec<String> = world
            .resources
            .material_registry
            .registry
            .index_to_name
            .iter()
            .filter_map(|opt| opt.clone())
            .collect();

        let mut selected_name = material_name.clone();
        ui.horizontal(|ui| {
            ui.label("Material:");
            egui::ComboBox::from_id_salt("material_selector")
                .selected_text(&selected_name)
                .show_ui(ui, |ui| {
                    for name in &available_materials {
                        ui.selectable_value(&mut selected_name, name.clone(), name);
                    }
                });
        });

        if selected_name != material_name {
            if let Some(&index) = world
                .resources
                .material_registry
                .registry
                .name_to_index
                .get(&material_name)
            {
                world
                    .resources
                    .material_registry
                    .registry
                    .remove_reference(index);
            }
            if let Some(&index) = world
                .resources
                .material_registry
                .registry
                .name_to_index
                .get(&selected_name)
            {
                world
                    .resources
                    .material_registry
                    .registry
                    .add_reference(index);
            }
            world.set_material_ref(entity, MaterialRef::new(selected_name.clone()));
            self.editing = false;
            return;
        }

        ui.separator();

        if self.editing {
            if let Some(material) = registry_entry_by_name_mut(
                &mut world.resources.material_registry.registry,
                &material_name,
            ) {
                let changed = material_properties_ui(ui, material, "inspector");

                if changed {
                    context.pending_notifications.push((
                        ToastKind::Success,
                        format!("Material '{}' modified", material_name),
                    ));
                }

                ui.separator();

                if ui.button("Done Editing").clicked() {
                    self.editing = false;
                }
            }
        } else if let Some(material) =
            registry_entry_by_name(&world.resources.material_registry.registry, &material_name)
        {
            let ref_count = world
                .resources
                .material_registry
                .registry
                .name_to_index
                .get(&material_name)
                .map(|&index| {
                    world.resources.material_registry.registry.reference_counts[index as usize]
                })
                .unwrap_or(0);

            material_display_section(
                ui,
                material,
                &material_name,
                context,
                ref_count,
                &mut self.editing,
            );
        }
    }
}