1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
use crate::{Context, Inspectable};
use bevy::asset::HandleId;
use bevy::math::{DVec2, DVec3, DVec4, Vec3A};
use bevy::prelude::*;
use bevy::render::camera::{DepthCalculation, ScalingMode, WindowOrigin};
use bevy::render::primitives::{CubemapFrusta, Frustum, Plane};
use bevy::render::render_resource::PrimitiveTopology;
use bevy::render::view::VisibleEntities;
use bevy::utils::HashMap;
use bevy_egui::egui;
use std::any::{Any, TypeId};

pub(crate) type InspectCallback =
    Box<dyn Fn(*mut u8, &mut egui::Ui, &mut Context) -> bool + Send + Sync>;

macro_rules! register {
    ($this:ident $($ty:ty),* $(,)?) => {
        $($this.register::<$ty>();)*
        $($this.register::<Option<$ty>>();)*
    };
}

/// The `InspectableRegistry` can be used to tell the [`WorldInspectorPlugin`](crate::WorldInspectorPlugin)
/// how to display a type.
/// ```rust,no_run
/// use bevy::prelude::*;
/// use bevy_inspector_egui::{Inspectable, InspectableRegistry};
///
/// #[derive(Inspectable)]
/// struct CustomType;
///
/// fn main() {
///     let mut app = App::new();
///     let mut registry = app
///         .world
///         .get_resource_mut::<InspectableRegistry>()
///         .unwrap();
///     registry.register::<CustomType>();
/// }
/// ```
pub struct InspectableRegistry {
    pub(crate) impls: HashMap<TypeId, InspectCallback>,
}

impl InspectableRegistry {
    /// Register type `T` so that it can be displayed by the [`WorldInspectorPlugin`](crate::WorldInspectorPlugin).
    pub fn register<T: Inspectable + 'static>(&mut self) {
        self.register_raw::<T, _>(|value, ui, context| {
            value.ui(ui, <T as Inspectable>::Attributes::default(), context)
        });
    }

    /// Registers a type that doesn't need to implement [`Inspectable`](crate::Inspectable)
    pub fn register_raw<T: 'static, F>(&mut self, f: F)
    where
        F: Fn(&mut T, &mut egui::Ui, &mut Context<'_>) -> bool + Send + Sync + 'static,
    {
        let type_id = TypeId::of::<T>();
        let callback = Box::new(
            move |ptr: *mut u8, ui: &mut egui::Ui, context: &mut Context| {
                let value: &mut T = unsafe { &mut *(ptr as *mut T) };
                f(value, ui, context)
            },
        ) as InspectCallback;
        self.impls.insert(type_id, callback);
    }

    /// Variant of [`InspectableRegistry::register`] which returns self by-value.
    /// Allows
    /// ```rust,no_run
    /// # use bevy::prelude::*;
    /// # use bevy_inspector_egui::InspectableRegistry;
    /// # #[derive(bevy_inspector_egui::Inspectable)] struct MyType;
    /// App::new()
    ///   .insert_resource(InspectableRegistry::default().with::<MyType>())
    ///   .run();
    /// ```
    #[must_use]
    pub fn with<T: Inspectable + 'static>(mut self) -> Self {
        self.register::<T>();
        self
    }

    /// Try to run the provided inspectable callback and return whether it has changed the value.
    pub fn try_execute(
        &self,
        value: &mut dyn Any,
        ui: &mut egui::Ui,
        context: &mut Context,
    ) -> Result<bool, ()> {
        let type_id = (*value).type_id();
        if let Some(inspect_callback) = self.impls.get(&type_id) {
            // SAFETY: we maintain the invariant that any callback in the hashmap receives a the type with the type_id specified in the key
            let ptr = value as *mut dyn Any as *mut u8;
            let changed = inspect_callback(ptr, ui, context);
            Ok(changed)
        } else {
            Err(())
        }
    }
}

impl Default for InspectableRegistry {
    fn default() -> Self {
        let mut this = InspectableRegistry {
            impls: HashMap::default(),
        };

        // #[reflect_value]
        register!(this Entity, HandleId);
        register!(this IVec2, IVec3, IVec4, UVec2, UVec3, Vec2, DVec2, DVec3, DVec4, Vec3, Vec3A, Vec4, Mat3, Mat4, Quat);
        register!(this u8, u16, u32, u64, usize, i8, i16, i32, i64, isize, bool, f32, f64, String);
        this.register::<Transform>();
        this.register::<GlobalTransform>();

        this.register::<std::ops::Range<f32>>();
        this.register::<std::time::Duration>();

        this.register::<Color>();

        #[cfg(feature = "bevy_pbr")]
        {
            use bevy::pbr::{Clusters, CubemapVisibleEntities, VisiblePointLights};

            this.register::<DirectionalLight>();
            this.register::<VisiblePointLights>();
            this.register::<CubemapVisibleEntities>();
            this.register::<Clusters>();
            this.register::<PointLight>();
            this.register::<StandardMaterial>();
            this.register::<Handle<StandardMaterial>>();
            this.register::<AmbientLight>();
        }
        #[cfg(feature = "bevy_text")]
        {
            use bevy::text::Text2dSize;
            register!(this VerticalAlign, HorizontalAlign, TextAlignment, TextStyle, TextSection, Text);
            this.register::<Text2dSize>();
        }

        #[cfg(feature = "bevy_sprite")]
        {
            this.register::<TextureAtlasSprite>();
            this.register::<TextureAtlas>();
            this.register::<bevy::sprite::Rect>();
            this.register::<Sprite>();
            this.register::<Handle<TextureAtlas>>();
            this.register::<ColorMaterial>();
            this.register::<Handle<ColorMaterial>>();
        }

        #[cfg(feature = "bevy_ui")]
        {
            register!(this Display, Style, Size<f32>, Size<Val>, Val, bevy::ui::FocusPolicy);
            register!(this PositionType, Direction, FlexDirection, FlexWrap, AlignItems, AlignSelf, JustifyContent);
        }

        this.register::<PrimitiveTopology>();
        this.register::<bevy::render::view::RenderLayers>();

        this.register::<WindowOrigin>();
        this.register::<ScalingMode>();
        this.register::<DepthCalculation>();
        this.register::<VisibleEntities>();
        this.register::<CubemapFrusta>();
        this.register::<Frustum>();
        this.register::<Plane>();

        // this.register::<Image>();
        this.register::<Handle<Image>>();
        // this.register::<Handle<TextureAtlas>>();
        this.register::<Mesh>();
        this.register::<Handle<Mesh>>();
        this.register::<Shader>();
        this.register::<Handle<Shader>>();

        this.register::<ClearColor>();
        this.register::<Shader>();

        this
    }
}