egui-probe 0.12.0

Trait and derive macro for exposing value editing in egui
Documentation
//! This example shows how to control entities in the ECS world using egui-probe.

use edict::{
    component::{Component, Value},
    entity::EntityId,
    query::{BorrowAll, Entities},
    world::World,
};
use egui_probe::EguiProbe;

fn main() {
    let native_options = eframe::NativeOptions::default();
    eframe::run_native(
        "egui-probe demo app",
        native_options,
        Box::new(|cc| Ok(Box::new(EguiProbeEdictApp::new(cc)))),
    )
    .unwrap();
}

trait Inspect: EguiProbe + Value {
    fn inspect(&mut self, ui: &mut egui::Ui);
}

impl<T> Inspect for T
where
    T: EguiProbe + Value,
{
    fn inspect(&mut self, ui: &mut egui::Ui) {
        let name: egui::WidgetText = self.name().into();
        egui_probe::Probe::new(self).with_header(name).show(ui);
    }
}

#[derive(Component, EguiProbe, Default)]
#[edict(borrow(dyn Inspect))]
struct Number {
    value: u32,
}

#[derive(Component, EguiProbe, Default)]
#[edict(borrow(dyn Inspect))]
struct Text {
    text: String,
}

macro_rules! entity_component {
    ($c:ident in $e:ident; $world:ident, $ui:ident) => {
        let s = $e.has_component::<$c>();
        if $ui.selectable_label(s, stringify!($c)).clicked() {
            if s {
                $e.remove::<$c>();
            } else {
                let id = $e.id();
                $e.insert::<$c>($c::default());
                $e = $world.entity(id).unwrap();
            }
        }
    };
}

struct EguiProbeEdictApp {
    world: World,
    selected: Option<EntityId>,
}

impl EguiProbeEdictApp {
    fn new(_cc: &eframe::CreationContext<'_>) -> Self {
        EguiProbeEdictApp {
            world: World::new(),
            selected: None,
        }
    }
}

impl eframe::App for EguiProbeEdictApp {
    fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
        let Self { world, selected } = self;

        egui::TopBottomPanel::top("header").show(ctx, |ui| {
            egui::widgets::global_theme_preference_switch(ui);
        });

        egui::SidePanel::left("entities").show(ctx, |ui| {
            ui.vertical(|ui| {
                if ui.small_button("+").clicked() {
                    world.spawn_empty();
                }

                let mut entities = world
                    .view::<Entities>()
                    .iter()
                    .map(|e| e.id())
                    .collect::<Vec<_>>();

                entities.sort();

                for e in entities {
                    ui.horizontal(|ui| {
                        if ui.weak(format!("{e}")).clicked() {
                            *selected = Some(e);
                        }
                        let mut e = world.entity(e).unwrap();
                        entity_component!(Number in e; world, ui);
                        entity_component!(Text in e; world, ui);

                        if ui.small_button("-").clicked() {
                            if *selected == Some(e.id()) {
                                *selected = None;
                            }
                            e.despawn();
                        }
                    });
                }
            })
        });

        egui::CentralPanel::default().show(ctx, |ui| {
            let mut entities = world
                .view::<Entities>()
                .iter()
                .map(|e| e.id())
                .collect::<Vec<_>>();

            entities.sort();

            ui.vertical(|ui| {
                let mut cbox = egui::ComboBox::from_id_salt("selected-entity");
                if let Some(e) = *selected {
                    if !entities.contains(&e) {
                        *selected = None;
                    } else {
                        cbox = cbox.selected_text(format!("{e}"));
                    }
                }

                cbox.show_ui(ui, |ui| {
                    for e in entities {
                        if ui
                            .selectable_label(*selected == Some(e), format!("{e}"))
                            .clicked()
                        {
                            *selected = Some(e);
                        }
                    }
                });

                if let Some(e) = *selected {
                    let e = world.entity(e).unwrap();
                    ui.vertical(|ui| {
                        let mut view = e.view_one::<BorrowAll<&mut (dyn Inspect + Send)>>();
                        if let Some(components) = view.get_mut() {
                            for c in components {
                                ui.separator();
                                c.inspect(ui);
                            }
                        }
                    });
                }
            });
        });
    }
}