From caf1c31c25349d29011d5772bba7eb2709879eb5 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Thu, 21 Jan 2021 16:46:28 +0100 Subject: [PATCH] add ReflectedUI wrapper type for implementing Inspectable based on Reflect --- examples/reflect.rs | 25 ++++++++ src/lib.rs | 3 + src/reflect/mod.rs | 143 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 examples/reflect.rs create mode 100644 src/reflect/mod.rs diff --git a/examples/reflect.rs b/examples/reflect.rs new file mode 100644 index 00000000..9577ba33 --- /dev/null +++ b/examples/reflect.rs @@ -0,0 +1,25 @@ +use bevy::prelude::*; +use bevy_inspector_egui::{reflect::ReflectedUI, Inspectable, InspectorPlugin}; + +#[derive(Inspectable, Default, Debug)] +struct Data { + // it works for custom reflect types + custom: ReflectedUI, + // also for builtin implementations + color: ReflectedUI, + // and for most of bevy's types + timer: ReflectedUI, +} + +#[derive(Reflect, Default, Debug)] +struct MyComponent { + a: f32, + b: Vec2, +} + +fn main() { + App::build() + .add_plugins(DefaultPlugins) + .add_plugin(InspectorPlugin::::new()) + .run(); +} diff --git a/src/lib.rs b/src/lib.rs index fbd646a7..b1c863f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,6 +41,9 @@ mod impls; mod plugin; +/// `Inspectable` implementation for foreign types implementing `Reflect` +pub mod reflect; + pub use bevy_egui::egui; /// Derives the [`Inspectable`](Inspectable) trait. diff --git a/src/reflect/mod.rs b/src/reflect/mod.rs new file mode 100644 index 00000000..510ef6bb --- /dev/null +++ b/src/reflect/mod.rs @@ -0,0 +1,143 @@ +use std::ops::{Deref, DerefMut}; + +use bevy::prelude::*; +use bevy::reflect::{List, Map}; +use bevy_egui::egui; +use egui::Grid; + +use crate::Inspectable; + +/// Wrapper type for displaying inspector UI based on the types [`Reflect`](bevy::reflect::Reflect) implementation. +/// +/// Say you wanted to display a type defined in another crate in the inspector, and that type implements `Reflect`. +/// ```rust +/// # use bevy::prelude::*; +/// #[derive(Reflect, Default)] +/// struct SomeComponent { +/// a: f32, +/// b: Vec2, +/// } +/// ``` +/// +/// Using the `ReflectedUI` wrapper type, you can include it in your inspector +/// and edit the fields like you would expect: +/// +/// ```rust +/// # use bevy::prelude::*; +/// # use bevy_inspector_egui::{Inspectable, reflect::ReflectedUI}; +/// # #[derive(Reflect, Default)] struct SomeComponent; +/// #[derive(Inspectable, Default)] +/// struct Data { +/// component: ReflectedUI, +/// // it also works for bevy's types +/// timer: ReflectedUI, +/// } +/// ``` +#[derive(Debug, Default)] +pub struct ReflectedUI(T); +impl ReflectedUI { + pub fn new(val: T) -> Self { + ReflectedUI(val) + } +} + +impl Deref for ReflectedUI { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl DerefMut for ReflectedUI { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Inspectable for ReflectedUI { + type Attributes = (); + + fn ui(&mut self, ui: &mut egui::Ui, _: Self::Attributes) { + ui_for_reflect(&mut self.0, ui); + } +} + +macro_rules! try_downcast_ui { + ($value:ident $ui:ident => $ty:ty) => { + if let Some(v) = $value.downcast_mut::<$ty>() { + <$ty as Inspectable>::ui(v, $ui, <$ty as Inspectable>::Attributes::default()); + return; + } + }; + + ( $value:ident $ui:ident => $( $ty:ty ),+ $(,)? ) => { + $(try_downcast_ui!($value $ui => $ty);)* + }; +} + +fn ui_for_reflect(value: &mut dyn Reflect, ui: &mut egui::Ui) { + try_downcast_ui!(value ui => Color); + + match value.reflect_mut() { + bevy::reflect::ReflectMut::Struct(s) => ui_for_reflect_struct(s, ui), + bevy::reflect::ReflectMut::TupleStruct(value) => ui_for_tuple_struct(value, ui), + bevy::reflect::ReflectMut::List(value) => ui_for_list(value, ui), + bevy::reflect::ReflectMut::Map(value) => ui_for_map(value, ui), + bevy::reflect::ReflectMut::Value(value) => ui_for_reflect_value(value, ui), + } +} + +fn ui_for_reflect_struct(value: &mut dyn Struct, ui: &mut egui::Ui) { + ui.vertical_centered(|ui| { + let grid = Grid::new(value.type_id()); + grid.show(ui, |ui| { + for i in 0..value.field_len() { + match value.name_at(i) { + Some(name) => ui.label(name), + None => ui.label(""), + }; + if let Some(field) = value.field_at_mut(i) { + ui_for_reflect(field, ui); + } else { + ui.label(""); + } + ui.end_row(); + } + }); + }); +} + +fn ui_for_tuple_struct(value: &mut dyn TupleStruct, ui: &mut egui::Ui) { + let grid = Grid::new(value.type_id()); + grid.show(ui, |ui| { + for i in 0..value.field_len() { + ui.label(i.to_string()); + if let Some(field) = value.field_mut(i) { + ui_for_reflect(field, ui); + } else { + ui.label(""); + } + ui.end_row(); + } + }); +} + +fn ui_for_list(_value: &mut dyn List, ui: &mut egui::Ui) { + ui.label("List not yet implemented"); +} + +fn ui_for_map(_value: &mut dyn Map, ui: &mut egui::Ui) { + ui.label("Map not yet implemented"); +} + +fn ui_for_reflect_value(value: &mut dyn Reflect, ui: &mut egui::Ui) { + try_downcast_ui!( + value ui => + f32, f64, u8, u16, u32, u64, i8, i16, i32, i64, + String, bool, + Vec2, Vec3, Vec4, Mat3, Mat4, + Transform, Quat, + ); + + ui.label(format!("Not implemented: {}", value.type_name())); +}