diff options
29 files changed, 2115 insertions, 271 deletions
diff --git a/engine-macros/src/reflection/enum_impl.rs b/engine-macros/src/reflection/enum_impl.rs index b637d9f..14d91a7 100644 --- a/engine-macros/src/reflection/enum_impl.rs +++ b/engine-macros/src/reflection/enum_impl.rs @@ -352,24 +352,47 @@ fn generate_variants<'a>( Ok(()) }, + try_get_field: |target, field_index| { { + #![allow(unreachable_code)] + + use std::any::Any; + use #engine_crate_path::reflection::GetError; + + let target = target + .downcast_ref::<Self>() + .ok_or(GetError::WrongTargetType)?; + + let #variant_pattern = target else { + return Err(GetError::WrongTargetEnumVariant); + }; + + let field: &dyn Any = match field_index { + #field_index_match_arms + _ => { + return Err(GetError::IndexOutOfBounds); + } + }; + + Ok(field) + } }, try_get_field_mut: |target, field_index| { { #![allow(unreachable_code)] use std::any::Any; - use #engine_crate_path::reflection::EnumVariantGetFieldError; + use #engine_crate_path::reflection::GetError; let target = target .downcast_mut::<Self>() - .ok_or(EnumVariantGetFieldError::WrongTargetType)?; + .ok_or(GetError::WrongTargetType)?; let #variant_pattern = target else { - return Err(EnumVariantGetFieldError::WrongTargetVariant); + return Err(GetError::WrongTargetEnumVariant); }; let field: &mut dyn Any = match field_index { #field_index_match_arms _ => { - return Err(EnumVariantGetFieldError::FieldIndexOutOfBounds); + return Err(GetError::IndexOutOfBounds); } }; diff --git a/engine-macros/src/reflection/struct_impl.rs b/engine-macros/src/reflection/struct_impl.rs index 4fbf88b..0ba426d 100644 --- a/engine-macros/src/reflection/struct_impl.rs +++ b/engine-macros/src/reflection/struct_impl.rs @@ -1,4 +1,4 @@ -use quote::quote; +use quote::{format_ident, quote}; use crate::reflection::default_value::gen_get_default_value_fn; use crate::reflection::field::{generate as generate_field, ReflectionFieldGenOptions}; @@ -73,12 +73,64 @@ fn gen_impl( } }); + let struct_pattern = match &input.fields { + syn::Fields::Unit => quote! { Self }, + syn::Fields::Named(syn::FieldsNamed { named: named_fields, .. }) => { + let field_idents = named_fields.iter().map(|field| { + let Some(field_ident) = &field.ident else { + unreachable!(); + }; + + field_ident + }); + + quote! { Self { #(#field_idents),* } } + } + syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed: unnamed_fields, .. }) => { + let field_idents = unnamed_fields + .iter() + .enumerate() + .map(|(field_index, _)| format_ident!("field_{field_index}")); + + quote! { Self(#(#field_idents),*) } + } + }; + + let field_index_match_arms = match &input.fields { + syn::Fields::Unit => quote! {}, + syn::Fields::Named(syn::FieldsNamed { named: named_fields, .. }) => { + let match_arms = + named_fields.iter().enumerate().map(|(field_index, field)| { + let Some(field_ident) = &field.ident else { + unreachable!(); + }; + + quote! { #field_index => { #field_ident } } + }); + + quote! { #(#match_arms)* } + } + syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed: unnamed_fields, .. }) => { + let match_arms = unnamed_fields.iter().enumerate().map(|(field_index, _)| { + let field_ident = format_ident!("field_{field_index}"); + + quote! { #field_index => { #field_ident } } + }); + + quote! { #(#match_arms)* } + } + }; + quote! { unsafe impl #engine_crate_path::reflection::Reflection for #input_ident #generic_args { const TYPE_REFLECTION: &#engine_crate_path::reflection::Type = &const { + use std::any::Any; + + use #engine_crate_path::reflection::GetError; + #(#generics_type_aliases)* #engine_crate_path::reflection::Type::Struct( @@ -89,7 +141,43 @@ fn gen_impl( get_default_value: || { #get_default_value_fn }, - cast_dyn_any: |ptr| ptr.cast::<Self>() + cast_dyn_any: |ptr| ptr.cast::<Self>(), + try_get_field: |target, field_index| { { + #![allow(unreachable_code)] + + let target = target + .downcast_ref::<Self>() + .ok_or(GetError::WrongTargetType)?; + + let #struct_pattern = target; + + let field: &dyn Any = match field_index { + #field_index_match_arms + _ => { + return Err(GetError::IndexOutOfBounds); + } + }; + + Ok(field) + } }, + try_get_field_mut: |target, field_index| { { + #![allow(unreachable_code)] + + let target = target + .downcast_mut::<Self>() + .ok_or(GetError::WrongTargetType)?; + + let #struct_pattern = target; + + let field: &mut dyn Any = match field_index { + #field_index_match_arms + _ => { + return Err(GetError::IndexOutOfBounds); + } + }; + + Ok(field) + } }, } ) }; diff --git a/engine-reflection/src/lib.rs b/engine-reflection/src/lib.rs index 79a7417..dedfee7 100644 --- a/engine-reflection/src/lib.rs +++ b/engine-reflection/src/lib.rs @@ -109,6 +109,10 @@ pub struct Struct pub fields: &'static [Field], pub get_default_value: fn() -> Option<DefaultValueFn>, pub cast_dyn_any: CastDynAnyFn, + pub try_get_field: + fn(target: &dyn Any, field_index: usize) -> Result<&dyn Any, GetError>, + pub try_get_field_mut: + fn(target: &mut dyn Any, field_index: usize) -> Result<&mut dyn Any, GetError>, } impl Struct @@ -169,10 +173,11 @@ pub struct EnumVariant fields: &mut dyn Iterator<Item = Box<dyn Any>>, ) -> Result<(), EnumVariantWriteNewToError>, - pub try_get_field_mut: fn( - target: &mut dyn Any, - field_index: usize, - ) -> Result<&mut dyn Any, EnumVariantGetFieldError>, + pub try_get_field: + fn(target: &dyn Any, field_index: usize) -> Result<&dyn Any, GetError>, + + pub try_get_field_mut: + fn(target: &mut dyn Any, field_index: usize) -> Result<&mut dyn Any, GetError>, } #[derive(Debug, thiserror::Error)] @@ -192,16 +197,16 @@ pub enum EnumVariantWriteNewToError } #[derive(Debug, thiserror::Error)] -pub enum EnumVariantGetFieldError +pub enum GetError { - #[error("Target is not the same type as the variant's enum")] + #[error("Target is not the correct type")] WrongTargetType, - #[error("Target is not the correct variant")] - WrongTargetVariant, + #[error("Target is not the correct enum variant")] + WrongTargetEnumVariant, - #[error("Field index is out of bounds")] - FieldIndexOutOfBounds, + #[error("Index is out of bounds")] + IndexOutOfBounds, } #[derive(Debug, Clone)] @@ -288,6 +293,9 @@ pub struct Array pub get_item_type_name: FnWithDebug<&'static str>, pub length: usize, pub cast_dyn_any: CastDynAnyFn, + pub try_get_item: fn(target: &dyn Any, index: usize) -> Result<&dyn Any, GetError>, + pub try_get_item_mut: + fn(target: &mut dyn Any, index: usize) -> Result<&mut dyn Any, GetError>, } impl Array @@ -304,6 +312,8 @@ pub struct Slice pub item_type: &'static Type, pub item_layout: Layout, pub get_item_type_name: FnWithDebug<&'static str>, + pub try_get_item: fn(target: &dyn Any, index: usize) -> Result<&dyn Any, GetError>, + pub try_get_len: fn(target: &dyn Any) -> Option<usize>, } impl Slice @@ -346,6 +356,7 @@ pub struct Reference { pub ty: &'static Type, pub cast_dyn_any: CastDynAnyFn, + pub try_deref: fn(target: &dyn Any) -> Option<&dyn Any>, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -432,6 +443,24 @@ unsafe impl<T: Reflection, const LEN: usize> Reflection for [T; LEN] get_item_type_name: FnWithDebug::new(|| type_name::<T>()), length: LEN, cast_dyn_any: |ptr| ptr.cast::<Self>(), + try_get_item: |target, index| { + let target = target + .downcast_ref::<Self>() + .ok_or(GetError::WrongTargetType)?; + + let item = target.get(index).ok_or(GetError::IndexOutOfBounds)?; + + Ok(item) + }, + try_get_item_mut: |target, index| { + let target = target + .downcast_mut::<Self>() + .ok_or(GetError::WrongTargetType)?; + + let item = target.get_mut(index).ok_or(GetError::IndexOutOfBounds)?; + + Ok(item) + }, }); } @@ -441,6 +470,20 @@ unsafe impl<T: Reflection> Reflection for &'static [T] item_type: T::TYPE_REFLECTION, item_layout: Layout::new::<T>(), get_item_type_name: FnWithDebug::new(|| type_name::<T>()), + try_get_item: |target, index| { + let target = target + .downcast_ref::<Self>() + .ok_or(GetError::WrongTargetType)?; + + let item = target.get(index).ok_or(GetError::IndexOutOfBounds)?; + + Ok(item) + }, + try_get_len: |target| { + let target = target.downcast_ref::<Self>()?; + + Some(target.len()) + }, }); } @@ -449,6 +492,11 @@ unsafe impl<T: Reflection> Reflection for &'static T const TYPE_REFLECTION: &Type = &Type::Reference(Reference { ty: T::TYPE_REFLECTION, cast_dyn_any: |ptr| ptr.cast::<Self>(), + try_deref: |target| { + let target = target.downcast_ref::<Self>()?; + + Some(*target) + }, }); } @@ -486,17 +534,32 @@ unsafe impl<T: Reflection> Reflection for Option<T> Ok(()) }, + try_get_field: |target, field_index| { + if field_index != 0 { + return Err(GetError::IndexOutOfBounds); + } + + let target = target + .downcast_ref::<Self>() + .ok_or(GetError::WrongTargetType)?; + + let Some(value_field) = target else { + return Err(GetError::WrongTargetEnumVariant); + }; + + Ok(value_field) + }, try_get_field_mut: |target, field_index| { if field_index != 0 { - return Err(EnumVariantGetFieldError::FieldIndexOutOfBounds); + return Err(GetError::IndexOutOfBounds); } let target = target .downcast_mut::<Self>() - .ok_or(EnumVariantGetFieldError::WrongTargetType)?; + .ok_or(GetError::WrongTargetType)?; let Some(value_field) = target else { - return Err(EnumVariantGetFieldError::WrongTargetVariant); + return Err(GetError::WrongTargetEnumVariant); }; Ok(value_field) @@ -518,9 +581,8 @@ unsafe impl<T: Reflection> Reflection for Option<T> Ok(()) }, - try_get_field_mut: |_, _| { - Err(EnumVariantGetFieldError::FieldIndexOutOfBounds) - }, + try_get_field: |_, _| Err(GetError::IndexOutOfBounds), + try_get_field_mut: |_, _| Err(GetError::IndexOutOfBounds), }, ], is_unit_only: false, diff --git a/engine/res/ui/delete.png b/engine/res/ui/delete.png Binary files differnew file mode 100644 index 0000000..f85ba1c --- /dev/null +++ b/engine/res/ui/delete.png diff --git a/engine/src/camera.rs b/engine/src/camera.rs index 1eb1246..b697ee3 100644 --- a/engine/src/camera.rs +++ b/engine/src/camera.rs @@ -1,10 +1,11 @@ use crate::ecs::Component; use crate::projection::{Perspective, Projection}; +use crate::reflection::Reflection; use crate::vector::Vec3; pub mod fly; -#[derive(Debug, Clone, Component)] +#[derive(Debug, Clone, Component, Reflection)] pub struct Camera { pub target: Vec3<f32>, @@ -25,11 +26,11 @@ impl Default for Camera } /// Marker component for cameras that are active. -#[derive(Debug, Default, Clone, Copy, Component)] +#[derive(Debug, Default, Clone, Copy, Component, Reflection)] pub struct Active; /// Cameras that can be controlled have this component. -#[derive(Debug, Clone, Copy, Component)] +#[derive(Debug, Clone, Copy, Component, Reflection)] pub struct Controllable { pub control_enabled: bool, diff --git a/engine/src/camera/fly.rs b/engine/src/camera/fly.rs index 9d5658b..18afcb8 100644 --- a/engine/src/camera/fly.rs +++ b/engine/src/camera/fly.rs @@ -11,13 +11,14 @@ use crate::ecs::system::Into; use crate::ecs::{Component, Query}; use crate::input::keyboard::{Key, Keyboard}; use crate::input::mouse::Mouse; +use crate::reflection::Reflection; use crate::transform::WorldPosition; use crate::vector::{Vec2, Vec3}; builder! { /// A fly camera. #[builder(name = Builder, derives = (Debug))] -#[derive(Debug, Component)] +#[derive(Debug, Component, Reflection)] #[non_exhaustive] pub struct Fly { pub current_pitch: f64, diff --git a/engine/src/collision.rs b/engine/src/collision.rs index c053a7f..015ab58 100644 --- a/engine/src/collision.rs +++ b/engine/src/collision.rs @@ -1,5 +1,6 @@ use crate::ecs::Component; use crate::mesh::Mesh; +use crate::reflection::Reflection; use crate::vector::Vec3; pub trait Collider<Other> @@ -7,7 +8,7 @@ pub trait Collider<Other> fn intersects(&self, other: &Other) -> bool; } -#[derive(Debug, Default, Clone, Component)] +#[derive(Debug, Default, Clone, Component, Reflection)] #[non_exhaustive] pub struct BoxCollider { @@ -78,7 +79,7 @@ impl Collider<Vec3<f32>> for BoxCollider } } -#[derive(Debug, Default, Clone, Component)] +#[derive(Debug, Default, Clone, Component, Reflection)] pub struct SphereCollider { pub center: Vec3<f32>, diff --git a/engine/src/data_types/color.rs b/engine/src/data_types/color.rs index 06df450..18d5334 100644 --- a/engine/src/data_types/color.rs +++ b/engine/src/data_types/color.rs @@ -1,4 +1,4 @@ -use std::ops::{Add, Div, Mul, Neg, Sub}; +use std::ops::{Add, Div, Mul, Sub}; pub trait Pixel: sealed::Sealed { @@ -7,12 +7,32 @@ pub trait Pixel: sealed::Sealed macro_rules! gen_color { ($ident: ident, components=($($component: ident),+)) => { - #[derive(Debug, Clone)] + #[derive(Debug, Clone, Default)] pub struct $ident<Component> { $(pub $component: Component),+ } + impl $ident<u8> + { + pub fn white() -> Self + { + Self { + $($component: 255),* + } + } + } + + impl $ident<f32> + { + pub fn white() -> Self + { + Self { + $($component: 1.0),* + } + } + } + impl<Component> Pixel for $ident<Component> { type Component = Component; @@ -27,59 +47,74 @@ gen_color!(Rgba, components = (r, g, b, a)); gen_color!(Luma, components = (l)); gen_color!(LumaA, components = (l, a)); -#[derive(Debug, Clone, Default)] -pub struct Color<Value> -{ - pub red: Value, - pub green: Value, - pub blue: Value, -} - -impl Color<f32> -{ - pub const WHITE_F32: Self = Self { red: 1.0, green: 1.0, blue: 1.0 }; -} - -impl Color<u8> +#[derive(Debug, Clone)] +pub enum Color<Component> { - pub const WHITE_U8: Self = Self { red: 255, green: 255, blue: 255 }; + Rgb(Rgb<Component>), + Rgba(Rgba<Component>), + Luma(Luma<Component>), + LumaA(LumaA<Component>), } -impl<Value: Clone> From<Value> for Color<Value> +impl<Component: Default> Default for Color<Component> { - fn from(value: Value) -> Self + fn default() -> Self { - Self { - red: value.clone(), - green: value.clone(), - blue: value, - } + Self::Rgb(Rgb::default()) } } -macro_rules! impl_math_op { - ($math_op_trait: ident, $function: ident) => { - impl<Value> $math_op_trait for Color<Value> +macro_rules! gen_scalar_math_op_impl { + ( + $ident: ident, + $math_op_trait: ident, + $function: ident, + components=($($component: ident),+) + ) => { + impl<Value> $math_op_trait<Value> for $ident<Value> where - Value: $math_op_trait<Output = Value>, + Value: $math_op_trait<Output = Value> + Clone, { type Output = Self; - fn $function(self, rhs: Self) -> Self::Output + fn $function(self, rhs: Value) -> Self::Output { Self { - red: self.red.$function(rhs.red), - green: self.green.$function(rhs.green), - blue: self.blue.$function(rhs.blue), + $($component: self.$component.$function(rhs.clone())),+ } } } }; } -macro_rules! impl_scalar_math_op { - ($math_op_trait: ident, $function: ident) => { - impl<Value> $math_op_trait<Value> for Color<Value> +gen_scalar_math_op_impl!(Rgb, Add, add, components = (r, g, b)); +gen_scalar_math_op_impl!(Rgb, Sub, sub, components = (r, g, b)); +gen_scalar_math_op_impl!(Rgb, Mul, mul, components = (r, g, b)); +gen_scalar_math_op_impl!(Rgb, Div, div, components = (r, g, b)); + +gen_scalar_math_op_impl!(Rgba, Add, add, components = (r, g, b, a)); +gen_scalar_math_op_impl!(Rgba, Sub, sub, components = (r, g, b, a)); +gen_scalar_math_op_impl!(Rgba, Mul, mul, components = (r, g, b, a)); +gen_scalar_math_op_impl!(Rgba, Div, div, components = (r, g, b, a)); + +gen_scalar_math_op_impl!(Luma, Add, add, components = (l)); +gen_scalar_math_op_impl!(Luma, Sub, sub, components = (l)); +gen_scalar_math_op_impl!(Luma, Mul, mul, components = (l)); +gen_scalar_math_op_impl!(Luma, Div, div, components = (l)); + +gen_scalar_math_op_impl!(LumaA, Add, add, components = (l, a)); +gen_scalar_math_op_impl!(LumaA, Sub, sub, components = (l, a)); +gen_scalar_math_op_impl!(LumaA, Mul, mul, components = (l, a)); +gen_scalar_math_op_impl!(LumaA, Div, div, components = (l, a)); + +macro_rules! gen_enum_scalar_math_op_impl { + ( + $ident: ident, + $math_op_trait: ident, + $function: ident, + variants=($($variant: ident),+) + ) => { + impl<Value> $math_op_trait<Value> for $ident<Value> where Value: $math_op_trait<Output = Value> + Clone, { @@ -87,41 +122,21 @@ macro_rules! impl_scalar_math_op { fn $function(self, rhs: Value) -> Self::Output { - Self { - red: self.red.$function(rhs.clone()), - green: self.green.$function(rhs.clone()), - blue: self.blue.$function(rhs), + match self { + $( + Self::$variant(inner) => + Self::$variant(inner.$function(rhs.clone())) + ),+ } } } }; } -impl_math_op!(Add, add); -impl_math_op!(Sub, sub); -impl_math_op!(Mul, mul); -impl_math_op!(Div, div); - -impl_scalar_math_op!(Add, add); -impl_scalar_math_op!(Sub, sub); -impl_scalar_math_op!(Mul, mul); -impl_scalar_math_op!(Div, div); - -impl<Value> Neg for Color<Value> -where - Value: Neg<Output = Value>, -{ - type Output = Self; - - fn neg(self) -> Self::Output - { - Self { - red: -self.red, - green: -self.green, - blue: -self.blue, - } - } -} +gen_enum_scalar_math_op_impl!(Color, Add, add, variants = (Rgb, Rgba, Luma, LumaA)); +gen_enum_scalar_math_op_impl!(Color, Sub, sub, variants = (Rgb, Rgba, Luma, LumaA)); +gen_enum_scalar_math_op_impl!(Color, Mul, mul, variants = (Rgb, Rgba, Luma, LumaA)); +gen_enum_scalar_math_op_impl!(Color, Div, div, variants = (Rgb, Rgba, Luma, LumaA)); mod sealed { diff --git a/engine/src/data_types/dimens.rs b/engine/src/data_types/dimens.rs index a197560..64a30d3 100644 --- a/engine/src/data_types/dimens.rs +++ b/engine/src/data_types/dimens.rs @@ -1,8 +1,11 @@ use std::num::NonZeroU32; use std::ops::Div; +use crate::reflection::Reflection; + /// 2D dimensions. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Reflection)] +#[reflection(impl_with_generics(<f32>, <f64>, <u32>, <u16>))] pub struct Dimens<Value> { pub width: Value, @@ -51,7 +54,8 @@ impl Dimens<u32> } /// 3D dimensions. -#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Reflection)] +#[reflection(impl_with_generics(<f32>, <f64>, <u32>, <u16>))] pub struct Dimens3<Value> { pub width: Value, diff --git a/engine/src/data_types/vector.rs b/engine/src/data_types/vector.rs index 980ca59..8644dd2 100644 --- a/engine/src/data_types/vector.rs +++ b/engine/src/data_types/vector.rs @@ -1,6 +1,6 @@ use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; -use crate::color::Color; +use crate::reflection::Reflection; macro_rules! impl_math_op_traits { (impl $op_trait: ident for $vector: ident, ($($vec_field: ident),+)) => { @@ -64,8 +64,9 @@ macro_rules! impl_math_op_traits { }; } -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] -pub struct Vec2<Value> +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Reflection)] +#[reflection(impl_with_generics(<f32>, <f64>, <u32>, <u16>))] +pub struct Vec2<Value: 'static> { pub x: Value, pub y: Value, @@ -116,8 +117,9 @@ impl<Value> From<Vec2<Value>> for [Value; 2] } } -#[derive(Debug, Default, Clone, Copy, PartialEq)] -pub struct Vec3<Value> +#[derive(Debug, Default, Clone, Copy, PartialEq, Reflection)] +#[reflection(impl_with_generics(<f32>, <f64>, <u32>, <u16>))] +pub struct Vec3<Value: 'static> { pub x: Value, pub y: Value, @@ -223,18 +225,6 @@ impl From<f32> for Vec3<f32> } } -impl<Value> From<Color<Value>> for Vec3<Value> -{ - fn from(color: Color<Value>) -> Self - { - Self { - x: color.red, - y: color.green, - z: color.blue, - } - } -} - impl<Value> From<[Value; 3]> for Vec3<Value> { fn from([x, y, z]: [Value; 3]) -> Self @@ -251,8 +241,9 @@ impl<Value> From<Vec3<Value>> for [Value; 3] } } -#[derive(Debug, Default, Clone, Copy, PartialEq)] -pub struct Vec4<Value> +#[derive(Debug, Default, Clone, Copy, PartialEq, Reflection)] +#[reflection(impl_with_generics(<f32>, <f64>, <u32>, <u16>))] +pub struct Vec4<Value: 'static> { pub x: Value, pub y: Value, diff --git a/engine/src/draw_flags.rs b/engine/src/draw_flags.rs index ecc94a8..e58eea0 100644 --- a/engine/src/draw_flags.rs +++ b/engine/src/draw_flags.rs @@ -1,10 +1,11 @@ use crate::builder; use crate::ecs::Component; +use crate::reflection::Reflection; builder! { /// Flags for how a object should be drawn. #[builder(name = Builder, derives = (Debug, Default, Clone))] -#[derive(Debug, Default, Clone, Component)] +#[derive(Debug, Default, Clone, Component, Reflection)] #[non_exhaustive] pub struct DrawFlags { @@ -21,14 +22,16 @@ impl DrawFlags } } -#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Reflection)] pub struct PolygonModeConfig { pub face: PolygonModeFace, pub mode: PolygonMode, } -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive( + Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Reflection, +)] pub enum PolygonMode { Point, @@ -38,7 +41,9 @@ pub enum PolygonMode Fill, } -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive( + Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Reflection, +)] pub enum PolygonModeFace { Front, @@ -49,5 +54,5 @@ pub enum PolygonModeFace } /// Component that makes a object not be drawn. -#[derive(Debug, Clone, Copy, Component)] +#[derive(Debug, Clone, Copy, Component, Reflection)] pub struct NoDraw; diff --git a/engine/src/file_format/wavefront/mtl.rs b/engine/src/file_format/wavefront/mtl.rs index f3c7a64..24387b9 100644 --- a/engine/src/file_format/wavefront/mtl.rs +++ b/engine/src/file_format/wavefront/mtl.rs @@ -4,7 +4,7 @@ use std::path::{Path, PathBuf}; -use crate::color::Color; +use crate::color::{Color, Rgb}; use crate::file_format::wavefront::common::{ keyword, parse_statement_line, @@ -67,9 +67,9 @@ impl Default for NamedMaterial { Self { name: String::new(), - ambient: Color::WHITE_F32, - diffuse: Color::WHITE_F32, - specular: Color::WHITE_F32, + ambient: Color::Rgb(Rgb::<f32>::white()), + diffuse: Color::Rgb(Rgb::<f32>::white()), + specular: Color::Rgb(Rgb::<f32>::white()), ambient_map: None, diffuse_map: None, specular_map: None, @@ -276,7 +276,7 @@ fn get_color_from_statement( let green = statement.get_float_arg(1, line_no)?; let blue = statement.get_float_arg(2, line_no)?; - Ok(Color { red, green, blue }) + Ok(Color::Rgb(Rgb { r: red, g: green, b: blue })) } keyword! { diff --git a/engine/src/image.rs b/engine/src/image.rs index 68eac65..18d3015 100644 --- a/engine/src/image.rs +++ b/engine/src/image.rs @@ -6,7 +6,7 @@ use std::path::Path; use image_rs::GenericImageView as _; -use crate::color::{Color, Luma, LumaA, Pixel as ColorPixel, Rgb, Rgba}; +use crate::color::{Luma, LumaA, Pixel as ColorPixel, Rgb, Rgba}; use crate::data_types::dimens::Dimens; use crate::vector::Vec2; @@ -60,41 +60,11 @@ impl Image Self::try_from(pixels) } - pub fn from_color(dimens: impl Into<Dimens<u32>>, color: impl Into<Color<u8>>) - -> Self - { - let dimens: Dimens<u32> = dimens.into(); - - let color: Color<u8> = color.into(); - - Self { - inner: image_rs::RgbImage::from_pixel( - dimens.width, - dimens.height, - image_rs::Rgb([color.red, color.green, color.blue]), - ) - .into(), - } - } - - pub fn from_color_and_alpha( - dimens: impl Into<Dimens<u32>>, - color: impl Into<Color<u8>>, - alpha: u8, - ) -> Self + pub fn from_color<PixelT: ColorPixel>(color: PixelT, size: Dimens<u32>) -> Self + where + Self: From<(PixelT, Dimens<u32>)>, { - let dimens: Dimens<u32> = dimens.into(); - - let color: Color<u8> = color.into(); - - Self { - inner: image_rs::RgbaImage::from_pixel( - dimens.width, - dimens.height, - image_rs::Rgba([color.red, color.green, color.blue, alpha]), - ) - .into(), - } + Self::from((color, size)) } pub fn dimensions(&self) -> Dimens<u32> @@ -153,6 +123,36 @@ impl Image } } +impl From<(Rgb<u8>, Dimens<u32>)> for Image +{ + fn from((color, size): (Rgb<u8>, Dimens<u32>)) -> Self + { + Self { + inner: image_rs::RgbImage::from_pixel( + size.width, + size.height, + image_rs::Rgb([color.r, color.g, color.b]), + ) + .into(), + } + } +} + +impl From<(Rgba<u8>, Dimens<u32>)> for Image +{ + fn from((color, size): (Rgba<u8>, Dimens<u32>)) -> Self + { + Self { + inner: image_rs::RgbaImage::from_pixel( + size.width, + size.height, + image_rs::Rgba([color.r, color.g, color.b, color.a]), + ) + .into(), + } + } +} + macro_rules! gen_try_from_pixel_buffer_impl { ($pixel: ident<$component: ty>) => { impl<Buf> TryFrom<PixelBuffer<$pixel<$component>, Buf>> for Image diff --git a/engine/src/lighting.rs b/engine/src/lighting.rs index 2a07541..abe3d56 100644 --- a/engine/src/lighting.rs +++ b/engine/src/lighting.rs @@ -1,11 +1,12 @@ use crate::builder; -use crate::color::Color; +use crate::color::{Color, Rgb}; use crate::data_types::vector::Vec3; use crate::ecs::{Component, Sole}; +use crate::reflection::Reflection; builder! { #[builder(name = PointLightBuilder, derives = (Debug, Clone))] -#[derive(Debug, Clone, Component)] +#[derive(Debug, Clone, Component, Reflection)] #[non_exhaustive] pub struct PointLight { @@ -32,8 +33,8 @@ impl Default for PointLight { Self { local_position: Vec3::default(), - diffuse: Color { red: 0.5, green: 0.5, blue: 0.5 }, - specular: Color { red: 1.0, green: 1.0, blue: 1.0 }, + diffuse: Color::Rgb(Rgb { r: 0.5, g: 0.5, b: 0.5 }), + specular: Color::Rgb(Rgb::<f32>::white()), attenuation_params: AttenuationParams::default(), } } @@ -48,7 +49,7 @@ impl Default for PointLightBuilder } /// Parameters for light [attenuation](https://en.wikipedia.org/wiki/Attenuation). -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Reflection)] pub struct AttenuationParams { pub constant: f32, @@ -70,7 +71,7 @@ impl Default for AttenuationParams builder! { #[builder(name = DirectionalLightBuilder, derives = (Debug, Default, Clone))] -#[derive(Debug, Default, Clone, Component)] +#[derive(Debug, Default, Clone, Component, Reflection)] #[non_exhaustive] pub struct DirectionalLight { diff --git a/engine/src/material.rs b/engine/src/material.rs index 59f0a6a..dd5a460 100644 --- a/engine/src/material.rs +++ b/engine/src/material.rs @@ -1,7 +1,8 @@ use crate::asset::Handle as AssetHandle; use crate::builder; -use crate::color::Color; +use crate::color::{Color, Rgb}; use crate::ecs::Component; +use crate::reflection::Reflection; use crate::texture::Texture; pub mod asset; @@ -21,7 +22,7 @@ pub struct Material impl Material { - pub const fn builder() -> Builder + pub fn builder() -> Builder { Builder::new() } @@ -51,12 +52,12 @@ pub struct Builder impl Builder { #[must_use] - pub const fn new() -> Self + pub fn new() -> Self { Self { - ambient: Color::WHITE_F32, - diffuse: Color::WHITE_F32, - specular: Color::WHITE_F32, + ambient: Color::Rgb(Rgb::<f32>::white()), + diffuse: Color::Rgb(Rgb::<f32>::white()), + specular: Color::Rgb(Rgb::<f32>::white()), ambient_map: None, diffuse_map: None, specular_map: None, @@ -150,7 +151,7 @@ impl Default for Builder builder! { /// Material flags. #[builder(name = FlagsBuilder, derives = (Debug, Clone))] -#[derive(Debug, Clone, Component)] +#[derive(Debug, Clone, Component, Reflection)] #[non_exhaustive] pub struct Flags { diff --git a/engine/src/projection.rs b/engine/src/projection.rs index e4f5898..ea3d7ed 100644 --- a/engine/src/projection.rs +++ b/engine/src/projection.rs @@ -1,9 +1,10 @@ use crate::builder; use crate::data_types::dimens::Dimens; use crate::matrix::Matrix; +use crate::reflection::Reflection; use crate::vector::Vec2; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Reflection)] #[non_exhaustive] pub enum Projection { @@ -12,7 +13,7 @@ pub enum Projection } /// Perspective projection parameters. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Reflection)] pub struct Perspective { pub fov_radians: f32, @@ -55,16 +56,18 @@ impl Default for Perspective } } -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone, Reflection)] pub enum OrthographicSize { FixedSize(Dimens<f32>), + + #[default] WindowSize, } builder! { #[builder(name = OrthographicBuilder, derives=(Debug, Clone))] -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Reflection)] #[non_exhaustive] pub struct Orthographic { diff --git a/engine/src/rendering/backend/opengl.rs b/engine/src/rendering/backend/opengl.rs index 95aef27..bab916a 100644 --- a/engine/src/rendering/backend/opengl.rs +++ b/engine/src/rendering/backend/opengl.rs @@ -66,9 +66,10 @@ use opengl_bindings::{ }; use raw_window_handle::WindowHandle; use safer_ffi::layout::ReprC; -use zerocopy::{Immutable, IntoBytes}; +use zerocopy::IntoBytes; use crate::asset::Assets; +use crate::data_types::color::Color; use crate::data_types::dimens::Dimens; use crate::ecs::actions::Actions; use crate::ecs::query::term::Without; @@ -676,6 +677,7 @@ fn handle_commands( ); let fvec3_value; + let mut color_value = [0.0; 4]; uniform_buffer .store_at_byte_offset( @@ -686,10 +688,34 @@ fn handle_commands( ShaderBindingValue::Int(ref value) => value.as_bytes(), ShaderBindingValue::Float(ref value) => value.as_bytes(), ShaderBindingValue::FVec3(value) => { - fvec3_value = CF32Vec3::from(value); - + fvec3_value = [value.x, value.y, value.z]; fvec3_value.as_bytes() } + ShaderBindingValue::Color(value) => match value { + Color::Rgb(value) => { + color_value[..3] + .copy_from_slice(&[value.r, value.g, value.b]); + + color_value[..3].as_bytes() + } + Color::Rgba(value) => { + color_value[..4].copy_from_slice(&[ + value.r, value.g, value.b, value.a, + ]); + + color_value[..4].as_bytes() + } + Color::Luma(ref value) => { + color_value[0] = value.l; + + color_value[0].as_bytes() + } + Color::LumaA(value) => { + color_value[..2].copy_from_slice(&[value.l, value.a]); + + color_value[..2].as_bytes() + } + }, ShaderBindingValue::FMat4x4(ref value) => { value.items().as_bytes() } @@ -1503,23 +1529,6 @@ impl From<crate::draw_flags::PolygonModeFace> for opengl_bindings::misc::Polygon } } -#[derive(Debug, IntoBytes, Immutable)] -#[repr(C)] -pub struct CF32Vec3 -{ - x: f32, - y: f32, - z: f32, -} - -impl From<Vec3<f32>> for CF32Vec3 -{ - fn from(src: Vec3<f32>) -> Self - { - Self { x: src.x, y: src.y, z: src.z } - } -} - fn blending_factor_to_gl(blending_factor: BlendingFactor) -> GlBlendingFactor { match blending_factor { diff --git a/engine/src/rendering/shader/cursor.rs b/engine/src/rendering/shader/cursor.rs index 17f5748..8aab4f2 100644 --- a/engine/src/rendering/shader/cursor.rs +++ b/engine/src/rendering/shader/cursor.rs @@ -1,3 +1,4 @@ +use crate::color::Color; use crate::data_types::matrix::Matrix; use crate::data_types::vector::Vec3; use crate::rendering::object::Id as RenderingObjectId; @@ -114,6 +115,7 @@ pub enum BindingValue Int(i32), Float(f32), FVec3(Vec3<f32>), + Color(Color<f32>), FMat4x4(Matrix<f32, 4, 4>), Texture(RenderingObjectId), } @@ -150,6 +152,14 @@ impl From<Vec3<f32>> for BindingValue } } +impl From<Color<f32>> for BindingValue +{ + fn from(color: Color<f32>) -> Self + { + BindingValue::Color(color) + } +} + impl From<Matrix<f32, 4, 4>> for BindingValue { fn from(matrix: Matrix<f32, 4, 4>) -> Self diff --git a/engine/src/rendering/shader/default.rs b/engine/src/rendering/shader/default.rs index 3194706..bb5c366 100644 --- a/engine/src/rendering/shader/default.rs +++ b/engine/src/rendering/shader/default.rs @@ -142,7 +142,7 @@ pub fn enqueue_set_shader_bindings( MaterialSearchResult::NotFound => { continue; } - MaterialSearchResult::NoMaterials => &const { Material::builder().build() }, + MaterialSearchResult::NoMaterials => &Material::builder().build(), }; if [ @@ -181,23 +181,21 @@ pub fn enqueue_set_shader_bindings( ), ( material_shader_cursor.field("ambient"), - Vec3::from( - if material_flags.use_ambient_color { - &model_material.ambient - } else { - &global_light.ambient - } - .clone(), - ) + (if material_flags.use_ambient_color { + &model_material.ambient + } else { + &global_light.ambient + } + .clone()) .into(), ), ( material_shader_cursor.field("diffuse"), - Vec3::from(model_material.diffuse.clone()).into(), + model_material.diffuse.clone().into(), ), ( material_shader_cursor.field("specular"), - Vec3::from(model_material.specular.clone()).into(), + model_material.specular.clone().into(), ), ( material_shader_cursor.field("shininess"), @@ -243,11 +241,11 @@ pub fn enqueue_set_shader_bindings( [ ( phong_shader_cursor.field("diffuse"), - Vec3::from(point_light.diffuse.clone()).into(), + point_light.diffuse.clone().into(), ), ( phong_shader_cursor.field("specular"), - Vec3::from(point_light.specular.clone()).into(), + point_light.specular.clone().into(), ), ( point_light_shader_cursor.field("position"), @@ -281,11 +279,11 @@ pub fn enqueue_set_shader_bindings( [ ( phong_shader_cursor.field("diffuse"), - Vec3::from(directional_light.diffuse.clone()).into(), + directional_light.diffuse.clone().into(), ), ( phong_shader_cursor.field("specular"), - Vec3::from(directional_light.specular.clone()).into(), + directional_light.specular.clone().into(), ), ( directional_light_shader_cursor.field("direction"), diff --git a/engine/src/texture.rs b/engine/src/texture.rs index b069228..af56e97 100644 --- a/engine/src/texture.rs +++ b/engine/src/texture.rs @@ -3,7 +3,7 @@ use std::sync::LazyLock; use crate::asset::{Assets, Label as AssetLabel, Submitter as AssetSubmitter}; use crate::builder; -use crate::color::Color; +use crate::color::Rgba; use crate::data_types::dimens::Dimens; use crate::image::{Error as ImageError, Image}; @@ -111,11 +111,7 @@ pub(crate) fn initialize(assets: &mut Assets) assets.store_with_label( WHITE_1X1_ASSET_LABEL.clone(), Texture { - image: Image::from_color_and_alpha( - Dimens { width: 1, height: 1 }, - Color::WHITE_U8, - 1, - ), + image: Image::from_color(Rgba::<u8>::white(), Dimens { width: 1, height: 1 }), properties: Properties::default(), }, ); diff --git a/engine/src/transform.rs b/engine/src/transform.rs index 642fb72..7f89807 100644 --- a/engine/src/transform.rs +++ b/engine/src/transform.rs @@ -1,5 +1,6 @@ use crate::builder; use crate::ecs::Component; +use crate::reflection::Reflection; use crate::vector::Vec3; builder!( @@ -41,7 +42,7 @@ impl Default for Builder } /// A position in world space. -#[derive(Debug, Default, Clone, Copy, Component)] +#[derive(Debug, Default, Clone, Copy, Component, Reflection)] pub struct WorldPosition { pub position: Vec3<f32>, @@ -56,7 +57,7 @@ impl From<Vec3<f32>> for WorldPosition } /// Scaling of a 3D object. -#[derive(Debug, Clone, Copy, Component)] +#[derive(Debug, Clone, Copy, Component, Reflection)] pub struct Scale { pub scale: Vec3<f32>, diff --git a/engine/src/ui.rs b/engine/src/ui.rs index 939c408..626c810 100644 --- a/engine/src/ui.rs +++ b/engine/src/ui.rs @@ -1 +1,2 @@ pub mod dear_imgui; +pub mod view; diff --git a/engine/src/ui/dear_imgui.rs b/engine/src/ui/dear_imgui.rs index 7c5dbc3..1e2a3fd 100644 --- a/engine/src/ui/dear_imgui.rs +++ b/engine/src/ui/dear_imgui.rs @@ -43,6 +43,7 @@ use crate::projection::{ ClipVolume as ProjectionClipVolume, Orthographic as OrthographicProjection, }; +use crate::reflection::Reflection; use crate::rendering::blending::{ Config as RenderingBlendingConfiguration, Equation as RenderingBlendingEquation, @@ -80,7 +81,7 @@ use crate::windowing::window::Window; mod reexports { - pub use dear_imgui_rs as dear_imgui; + pub use dear_imgui_rs as bindings; } pub use reexports::*; @@ -101,7 +102,7 @@ pub struct Context impl Context { - pub fn frame(&mut self) -> Option<&mut dear_imgui_rs::Ui> + pub fn frame(&mut self) -> Option<&mut bindings::Ui> { if !self.enabled { return None; @@ -110,7 +111,7 @@ impl Context self.ctx.get_frame() } - pub fn register_texture(&mut self, texture_data: &mut dear_imgui_rs::OwnedTextureData) + pub fn register_texture(&mut self, texture_data: &mut bindings::OwnedTextureData) { self.ctx.register_texture(texture_data); } @@ -290,7 +291,7 @@ fn update( Ok(()) } -#[derive(Debug, Component)] +#[derive(Debug, Component, Reflection)] pub struct TargetWindow; #[derive(Debug, Default, Component)] diff --git a/engine/src/ui/view.rs b/engine/src/ui/view.rs new file mode 100644 index 0000000..ed714d6 --- /dev/null +++ b/engine/src/ui/view.rs @@ -0,0 +1 @@ +pub mod world; diff --git a/engine/src/ui/view/world.rs b/engine/src/ui/view/world.rs new file mode 100644 index 0000000..9803328 --- /dev/null +++ b/engine/src/ui/view/world.rs @@ -0,0 +1,1550 @@ +use std::any::{Any, TypeId}; +use std::borrow::Cow; +use std::io::Cursor; + +use crate::ecs::actions::Actions; +use crate::ecs::component::local::Local; +use crate::ecs::component::{Info as ComponentInfo, Parts as ComponentParts}; +use crate::ecs::entity::{Handle as EntityHandle, Name as EntityName}; +use crate::ecs::sole::Single; +use crate::ecs::uid::Uid; +use crate::ecs::util::Either; +use crate::ecs::{Component, Query, World}; +use crate::image::{Format as ImageFormat, Image}; +use crate::reflection::{ + Enum as EnumReflection, + LiteralType, + Type as TypeReflection, + Visibility, +}; +use crate::ui::dear_imgui::{ + bindings as dear_imgui_bindings, + Context as DearImguiContext, +}; + +const BUTTON_RED_NORMAL: [f32; 4] = [124.0, 52.0, 39.0, 255.0]; +const BUTTON_RED_HOVERED: [f32; 4] = [181.0, 76.0, 57.0, 255.0]; +const BUTTON_RED_ACTIVE: [f32; 4] = [162.0, 68.0, 51.0, 255.0]; + +#[derive(Component)] +pub struct State +{ + spawning_entity: Option<Uid>, + add_component_popup_state: Option<AddComponentPopupState>, + despawn_icon_tex: Option<dear_imgui_bindings::OwnedTextureData>, +} + +impl Default for State +{ + fn default() -> Self + { + Self { + spawning_entity: None, + add_component_popup_state: None, + despawn_icon_tex: None, + } + } +} + +#[derive(Debug)] +pub struct AddComponentPopupState +{ + popup_name: String, + target_entity_id: Uid, + search_text: String, + searched_components: Vec<(Uid, ComponentInfo, ComponentUserCreatable)>, + searchable_components: Vec<(Uid, ComponentInfo, ComponentUserCreatable)>, + selected_search_result: i32, + new_component: Option<(Box<dyn Any>, ComponentInfo, Uid)>, +} + +pub fn show( + query: Query<()>, + mut dear_imgui_context: Single<DearImguiContext>, + mut state: Local<State>, + world: &World, + mut actions: Actions, +) -> Result<(), crate::Error> +{ + let dear_imgui_context = dear_imgui_context.get_mut()?; + + let State { + spawning_entity, + add_component_popup_state, + despawn_icon_tex, + } = &mut *state; + + let despawn_icon_tex = despawn_icon_tex + .get_or_insert_with(|| create_despawn_icon_texture(dear_imgui_context)); + + let Some(frame) = dear_imgui_context.frame() else { + return Ok(()); + }; + + frame + .window("World") + .size([600.0, 400.0], dear_imgui_bindings::Condition::Once) + .build(|| { + create_spawn_button_widgets( + frame, + spawning_entity, + add_component_popup_state, + world, + &mut actions, + ); + + frame.spacing(); + frame.spacing(); + + for ent_handle in &query.into_flexible_query() { + create_entity_widgets( + frame, + add_component_popup_state, + despawn_icon_tex, + &ent_handle, + world, + &mut actions, + ); + } + + if let Some(popup_state) = add_component_popup_state { + if show_add_component_popup(frame, popup_state, &mut actions) { + *add_component_popup_state = None; + } + } + }); + + Ok(()) +} + +fn create_despawn_icon_texture( + dear_imgui_context: &mut DearImguiContext, +) -> dear_imgui_bindings::OwnedTextureData +{ + let despawn_icon_image = Image::from_reader( + Cursor::new(include_bytes!("../../../res/ui/delete.png")), + ImageFormat::Png, + ) + .unwrap() + .into_rgba8(); + + let mut despawn_icon_tex = dear_imgui_bindings::OwnedTextureData::new(); + + despawn_icon_tex.create( + dear_imgui_bindings::TextureFormat::RGBA32, + despawn_icon_image.dimensions().width, + despawn_icon_image.dimensions().height, + ); + + despawn_icon_tex.set_data(despawn_icon_image.as_bytes()); + + despawn_icon_tex.set_status(dear_imgui_bindings::TextureStatus::WantCreate); + + dear_imgui_context.register_texture(&mut despawn_icon_tex); + + despawn_icon_tex +} + +fn create_spawn_button_widgets( + frame: &dear_imgui_bindings::Ui, + spawning_entity: &mut Option<Uid>, + add_component_popup_state: &mut Option<AddComponentPopupState>, + world: &World, + actions: &mut Actions, +) +{ + let _disabled_token = frame.begin_disabled_with_cond(spawning_entity.is_some()); + + if frame.button("Spawn") { + let new_ent_id = actions.spawn(()); + + *spawning_entity = Some(new_ent_id); + } + + if let Some(spawning_ent) = &spawning_entity { + if let Some(spawned_ent) = world.get_entity(*spawning_ent) { + *spawning_entity = None; + + let spawned_ent_title = create_entity_title(&spawned_ent); + + enter_add_component_popup_state( + add_component_popup_state, + &spawned_ent, + &spawned_ent_title, + world, + ); + } + } +} + +fn create_entity_widgets( + frame: &dear_imgui_bindings::Ui, + add_component_popup_state: &mut Option<AddComponentPopupState>, + despawn_icon_tex: &mut dear_imgui_bindings::OwnedTextureData, + ent_handle: &EntityHandle, + world: &World, + actions: &mut Actions, +) +{ + if ent_handle.component_ids().next().is_some() + && !ent_handle.component_ids().any(|comp_id| { + world.get_entity(comp_id).is_some_and(|comp_ent| { + comp_ent + .get::<ComponentInfo>() + .is_some_and(|comp_info| comp_info.type_reflection.is_some()) + }) + }) + { + return; + } + + let ent_title = create_entity_title(&ent_handle); + + let mut is_open = false; + + frame + .table(&format!("ent_{}_table", ent_handle.uid())) + .headers(false) + .sizing_policy(dear_imgui_bindings::TableSizingPolicy::FixedFit) + .columns([ + dear_imgui_bindings::TableColumnSetup::new("a") + .fixed_width(frame.content_region_avail_width() * 0.8), + dear_imgui_bindings::TableColumnSetup::new("b"), + dear_imgui_bindings::TableColumnSetup::new("c"), + ]) + .build(|frame| { + frame.table_next_row(); + + frame.table_next_column(); + + is_open = frame + .collapsing_header(&ent_title, dear_imgui_bindings::TreeNodeFlags::NONE); + + frame.table_next_column(); + + create_add_component_button_widget( + frame, + &ent_handle, + &ent_title, + add_component_popup_state, + world, + ); + + frame.table_next_column(); + + create_despawn_button_widget(frame, despawn_icon_tex, ent_handle, actions); + }); + + if is_open { + for component_id in ent_handle.component_ids() { + create_component_widgets(frame, ent_handle, component_id, world, actions); + } + } +} + +fn create_component_widgets( + frame: &dear_imgui_bindings::Ui, + ent_handle: &EntityHandle, + component_id: Uid, + world: &World, + actions: &mut Actions, +) +{ + let Some(component_info) = world + .get_entity(component_id) + .and_then(|comp_ent| comp_ent.get::<ComponentInfo>()) + else { + return; + }; + + let Some(component_type) = component_info.type_reflection else { + return; + }; + + let Some(mut component) = ent_handle.get_any_by_id_mut(component_id) else { + unreachable!(); + }; + + { + let _color_token = frame.push_style_color( + dear_imgui_bindings::StyleColor::Button, + BUTTON_RED_NORMAL.map(|num| num / 255.0), + ); + + let _color_token_b = frame.push_style_color( + dear_imgui_bindings::StyleColor::ButtonHovered, + BUTTON_RED_HOVERED.map(|num| num / 255.0), + ); + + let _color_token_c = frame.push_style_color( + dear_imgui_bindings::StyleColor::ButtonActive, + BUTTON_RED_ACTIVE.map(|num| num / 255.0), + ); + + if frame.small_button(format!( + "X##world_view_entity_{}_remove_component_button_{}", + ent_handle.uid(), + component_info.name + )) { + actions.remove_components(ent_handle.uid(), [component_id]); + } + } + + frame.same_line(); + + let mut component_changed = false; + + add_item_to_frame( + frame, + ItemRef::Mutable(&mut *component), + &mut component_changed, + ItemInfo { + item_title: component_info.name, + item_tag: &format!( + "world_view_entity_{}_component_{}", + ent_handle.uid(), + component_info.name, + ), + item_type_name: None, + item_type: ItemType::Reflected(component_type), + item_is_read_only: false, + }, + &[], + ); + + frame.spacing(); + + if component_changed { + component.set_changed(); + } +} + +fn create_add_component_button_widget( + frame: &dear_imgui_bindings::Ui, + ent_handle: &EntityHandle, + ent_title: &str, + add_component_popup_state: &mut Option<AddComponentPopupState>, + world: &World, +) +{ + let _color_token = frame.push_style_color( + dear_imgui_bindings::StyleColor::Button, + [49.0, 89.0, 28.0, 255.0].map(|num| num / 255.0), + ); + + let _color_token_b = frame.push_style_color( + dear_imgui_bindings::StyleColor::ButtonHovered, + [100.0, 181.0, 57.0, 255.0].map(|num| num / 255.0), + ); + + let _color_token_c = frame.push_style_color( + dear_imgui_bindings::StyleColor::ButtonActive, + [90.0, 162.0, 51.0, 255.0].map(|num| num / 255.0), + ); + + let is_button_clicked = frame.button(format!( + "+##add_component_to_entity_button_{}", + ent_handle.uid() + )); + + if add_component_popup_state + .as_ref() + .is_some_and(|add_component_popup_state| { + add_component_popup_state.target_entity_id != ent_handle.uid() + }) + { + return; + } + + if is_button_clicked { + enter_add_component_popup_state( + add_component_popup_state, + ent_handle, + ent_title, + world, + ); + } +} + +fn create_despawn_button_widget( + frame: &dear_imgui_bindings::Ui, + despawn_icon_tex: &mut dear_imgui_bindings::OwnedTextureData, + ent_handle: &EntityHandle, + actions: &mut Actions, +) +{ + let _color_token = frame.push_style_color( + dear_imgui_bindings::StyleColor::Button, + BUTTON_RED_NORMAL.map(|num| num / 255.0), + ); + + let _color_token_b = frame.push_style_color( + dear_imgui_bindings::StyleColor::ButtonHovered, + BUTTON_RED_HOVERED.map(|num| num / 255.0), + ); + + let _color_token_c = frame.push_style_color( + dear_imgui_bindings::StyleColor::ButtonActive, + BUTTON_RED_ACTIVE.map(|num| num / 255.0), + ); + + if frame + .image_button_config( + &format!("ent_{}_despawn_button", ent_handle.uid()), + despawn_icon_tex.as_mut(), + [14.0, 14.0], + ) + .build() + { + actions.despawn(ent_handle.uid()); + } +} + +fn enter_add_component_popup_state( + add_component_popup_state: &mut Option<AddComponentPopupState>, + ent_handle: &EntityHandle, + ent_title: &str, + world: &World, +) +{ + let popup_name = format!("Add component to {ent_title}"); + + let mut searchable_components = world + .query::<(&ComponentInfo,), ()>() + .iter_with_euids() + .map(|(id, (component_info,))| { + let user_creatable = if component_info.type_reflection.is_none() { + ComponentUserCreatable::No { reason: "no type reflection" } + } else if component_info + .type_reflection + .is_some_and(|ty| !ty.has_default_value()) + { + ComponentUserCreatable::No { reason: "no default value" } + } else if !get_whole_type_is_user_editable( + component_info.type_reflection, + component_info.ty_id, + ) { + ComponentUserCreatable::No { + reason: "whole component is not user editable", + } + } else { + ComponentUserCreatable::Yes + }; + + (id, component_info.clone(), user_creatable) + }) + .collect::<Vec<_>>(); + + searchable_components.sort_by_key(|(_, _, user_creatable)| *user_creatable); + + *add_component_popup_state = Some(AddComponentPopupState { + popup_name: popup_name.clone(), + target_entity_id: ent_handle.uid(), + search_text: String::with_capacity(20), + searched_components: searchable_components.clone(), + searchable_components, + selected_search_result: -1, + new_component: None, + }); +} + +fn show_add_component_popup( + frame: &dear_imgui_bindings::Ui, + add_component_popup_state: &mut AddComponentPopupState, + actions: &mut Actions, +) -> bool +{ + frame.open_popup(&add_component_popup_state.popup_name); + + let mut is_opened = true; + + let should_close = frame.modal_popup_with_opened( + &add_component_popup_state.popup_name, + &mut is_opened, + || { + frame.set_window_size([700.0, 600.0]); + + frame.spacing(); + frame.spacing(); + + frame.text("Search:"); + frame.same_line(); + + if frame + .input_text( + "##add_component_popup_search_input", + &mut add_component_popup_state.search_text, + ) + .build() + { + add_component_popup_state.searched_components = add_component_popup_state + .searchable_components + .iter() + .cloned() + .filter(|(_, searchable_comp_info, _)| { + searchable_comp_info + .name + .contains(&add_component_popup_state.search_text) + }) + .collect(); + + add_component_popup_state.selected_search_result = -1; + } + + frame.spacing(); + frame.spacing(); + + let _item_width_token = frame.push_item_width(-1.0); + + if frame + .list_box_config("##add_component_popup_search_results") + .build_extended( + frame, + &mut add_component_popup_state.selected_search_result, + &add_component_popup_state.searched_components, + &|(_, comp_info, comp_user_creatable)| ListBoxItem { + label: comp_info.name.into(), + disabled: matches!( + comp_user_creatable, + ComponentUserCreatable::No { .. } + ), + hover_tooltip: match comp_user_creatable { + ComponentUserCreatable::Yes => None, + ComponentUserCreatable::No { reason } => Some( + format!("Component cannot be selected: {reason}").into(), + ), + }, + }, + ) + { + let (selected_comp_id, selected_comp_info, _) = add_component_popup_state + .searched_components + [add_component_popup_state.selected_search_result as usize] + .clone(); + + let Some(selected_component_type) = selected_comp_info.type_reflection + else { + unreachable!(); + }; + + let Some(selected_comp_default_value) = + selected_component_type.default_value() + else { + unreachable!(); // Only components with default values are selectable + }; + + add_component_popup_state.new_component = Some(( + selected_comp_default_value, + selected_comp_info, + selected_comp_id, + )); + } + + frame.spacing(); + frame.spacing(); + frame.spacing(); + + if let Some((new_component, new_component_info, _)) = + add_component_popup_state.new_component.as_mut() + { + let mut component_changed = false; + + let Some(new_component_type) = new_component_info.type_reflection else { + unreachable!(); + }; + + add_item_to_frame( + frame, + ItemRef::Mutable(&mut **new_component), + &mut component_changed, + ItemInfo { + item_title: new_component_info.name, + item_tag: &format!( + "add_component_popup_component_{}", + new_component_info.name, + ), + item_type_name: None, + item_type: ItemType::Reflected(new_component_type), + item_is_read_only: false, + }, + &[], + ); + + frame.spacing(); + frame.spacing(); + frame.spacing(); + } + + let _disabled = frame.begin_disabled_with_cond( + add_component_popup_state.new_component.is_none(), + ); + + let add_text_size = frame.current_font().calc_text_size( + frame.clone_style().font_size_base(), + frame.content_region_avail_width(), + 0.0, + "Add", + ); + + if frame.button_with_size( + "Add##add_component_popup_add_button", + [add_text_size[0] * 2.0, add_text_size[1] * 2.0], + ) { + if let Some((new_component_data, new_component_info, new_component_id)) = + add_component_popup_state.new_component.take() + { + actions.add_components( + add_component_popup_state.target_entity_id, + [ComponentParts::builder() + .name(new_component_info.name) + .type_reflection(new_component_info.type_reflection) + .build_with_any_data(new_component_id, new_component_data)], + ); + + return true; + } + } + + false + }, + ); + + if let Some(true) = should_close { + return true; + } + + if !is_opened { + return true; + } + + false +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum ComponentUserCreatable +{ + Yes, + No + { + reason: &'static str, + }, +} + +struct ItemInfo<'a> +{ + item_title: &'a str, + item_tag: &'a str, + item_type_name: Option<&'static str>, + item_type: ItemType, + item_is_read_only: bool, +} + +enum ItemType +{ + Reflected(&'static TypeReflection), + String, + CowStr, +} + +fn get_item_type(ty: Option<&'static TypeReflection>, type_id: TypeId) + -> Option<ItemType> +{ + if let Some(ty) = ty { + Some(ItemType::Reflected(ty)) + } else if type_id == TypeId::of::<String>() { + Some(ItemType::String) + } else if type_id == TypeId::of::<Cow<'static, str>>() { + Some(ItemType::CowStr) + } else { + None + } +} + +enum ItemRef<'item> +{ + Mutable(&'item mut dyn Any), + Immutable(&'item dyn Any), +} + +impl AsRef<dyn Any> for ItemRef<'_> +{ + fn as_ref(&self) -> &dyn Any + { + match self { + Self::Mutable(item) => &**item, + Self::Immutable(item) => *item, + } + } +} + +fn add_item_to_frame<'a>( + frame: &dear_imgui_bindings::Ui, + mut item: ItemRef<'_>, + data_changed: &mut bool, + ItemInfo { + item_title, + item_tag, + item_type_name, + item_type, + item_is_read_only, + }: ItemInfo<'a>, + prev_item_tags: &[&'a str], +) +{ + if !matches!(item_type, ItemType::Reflected(TypeReflection::Reference(_))) { + frame.text(&item_title); + + if let Some(item_type_name) = item_type_name { + if frame.is_item_hovered() { + frame.tooltip_text(item_type_name); + } + } + } + + let _disabled = frame.begin_disabled_with_cond( + !matches!( + item_type, + ItemType::Reflected( + TypeReflection::Struct(_) + | TypeReflection::Enum(EnumReflection { is_unit_only: false, .. }) + | TypeReflection::Array(_) + | TypeReflection::Slice(_) + ) + ) && item_is_read_only, + ); + + match item_type { + ItemType::Reflected(TypeReflection::Struct(struct_reflection)) => { + frame.indent(); + + let mut prev_item_tags = prev_item_tags.to_vec(); + + prev_item_tags.push(item_tag.as_ref()); + + for (field_index, field) in struct_reflection.fields.iter().enumerate() { + let field_name = field + .name + .map(Cow::Borrowed) + .unwrap_or_else(|| field_index.to_string().into()); + + let Some(field_item_type) = + get_item_type(field.type_reflection(), field.type_id) + else { + continue; + }; + + let Ok(field_item) = (match &mut item { + ItemRef::Mutable(item) => { + (struct_reflection.try_get_field_mut)(*item, field_index) + .map(ItemRef::Mutable) + } + ItemRef::Immutable(item) => { + (struct_reflection.try_get_field)(*item, field_index) + .map(ItemRef::Immutable) + } + }) else { + unreachable!(); + }; + + add_item_to_frame( + frame, + field_item, + data_changed, + ItemInfo { + item_title: field_name.as_ref(), + item_tag: field_name.as_ref(), + item_type_name: Some(field.type_name()), + item_type: field_item_type, + item_is_read_only: item_is_read_only + || matches!( + field.visibility, + Visibility::Private | Visibility::PubScoped(_) + ), + }, + &prev_item_tags, + ); + } + + frame.unindent(); + } + ItemType::Reflected(TypeReflection::Enum(enum_type)) => { + if enum_type.is_unit_only { + frame.same_line(); + } else { + frame.indent(); + } + + create_enum_widgets( + frame, + &mut item, + data_changed, + item_tag, + item_is_read_only, + enum_type, + prev_item_tags, + ); + + if !enum_type.is_unit_only { + frame.unindent(); + } + } + ItemType::Reflected(TypeReflection::Literal(literal_reflection)) => { + frame.same_line(); + + match literal_reflection.ty { + LiteralType::U8 => create_scalar_item_input::<u8>( + frame, + &mut item, + data_changed, + &item_tag, + &prev_item_tags, + ), + LiteralType::I8 => create_scalar_item_input::<i8>( + frame, + &mut item, + data_changed, + &item_tag, + &prev_item_tags, + ), + LiteralType::U16 => create_scalar_item_input::<u16>( + frame, + &mut item, + data_changed, + &item_tag, + &prev_item_tags, + ), + LiteralType::I16 => create_scalar_item_input::<i16>( + frame, + &mut item, + data_changed, + &item_tag, + &prev_item_tags, + ), + LiteralType::U32 => create_scalar_item_input::<u32>( + frame, + &mut item, + data_changed, + &item_tag, + &prev_item_tags, + ), + LiteralType::I32 => create_scalar_item_input::<i32>( + frame, + &mut item, + data_changed, + &item_tag, + &prev_item_tags, + ), + LiteralType::U64 => create_scalar_item_input::<u64>( + frame, + &mut item, + data_changed, + &item_tag, + &prev_item_tags, + ), + LiteralType::I64 => create_scalar_item_input::<i64>( + frame, + &mut item, + data_changed, + &item_tag, + &prev_item_tags, + ), + LiteralType::F32 => create_scalar_item_input::<f32>( + frame, + &mut item, + data_changed, + &item_tag, + &prev_item_tags, + ), + LiteralType::F64 => create_scalar_item_input::<f64>( + frame, + &mut item, + data_changed, + &item_tag, + &prev_item_tags, + ), + LiteralType::Usize => create_scalar_item_input::<usize>( + frame, + &mut item, + data_changed, + &item_tag, + &prev_item_tags, + ), + LiteralType::Isize => create_scalar_item_input::<isize>( + frame, + &mut item, + data_changed, + &item_tag, + &prev_item_tags, + ), + LiteralType::U128 => { + let item = match item { + ItemRef::Mutable(item) => { + let Some(item) = (*item).downcast_mut::<u128>() else { + unreachable!(); + }; + + item + } + ItemRef::Immutable(item) => { + let Some(item) = (*item).downcast_ref::<u128>() else { + unreachable!(); + }; + + &mut item.clone() + } + }; + + frame.text(item.to_string()); + } + LiteralType::I128 => { + let item = match item { + ItemRef::Mutable(item) => { + let Some(item) = (*item).downcast_mut::<i128>() else { + unreachable!(); + }; + + item + } + ItemRef::Immutable(item) => { + let Some(item) = (*item).downcast_ref::<i128>() else { + unreachable!(); + }; + + &mut item.clone() + } + }; + + frame.text(item.to_string()); + } + LiteralType::Bool => { + let item = match item { + ItemRef::Mutable(item) => { + let Some(item) = (*item).downcast_mut::<bool>() else { + unreachable!(); + }; + + item + } + ItemRef::Immutable(item) => { + let Some(item) = (*item).downcast_ref::<bool>() else { + unreachable!(); + }; + + &mut item.clone() + } + }; + + if frame.checkbox( + create_item_label(&item_tag, "value_input", &prev_item_tags), + item, + ) { + *data_changed = true; + } + } + LiteralType::Str => { + let item = match item { + ItemRef::Mutable(item) => { + let Some(item) = (*item).downcast_ref::<&'static str>() + else { + unreachable!(); + }; + + *item + } + ItemRef::Immutable(item) => { + let Some(item) = (*item).downcast_ref::<&'static str>() + else { + unreachable!(); + }; + + *item + } + }; + + frame.text(item); + } + _ => unimplemented!(), + } + } + ItemType::Reflected(TypeReflection::Array(array_type)) => { + frame.indent(); + + let mut prev_item_tags = prev_item_tags.to_vec(); + + prev_item_tags.push(item_tag); + + for array_item_index in 0..array_type.length { + let array_item_name = array_item_index.to_string(); + + let Ok(item_item) = (match &mut item { + ItemRef::Mutable(item) => { + (array_type.try_get_item_mut)(*item, array_item_index) + .map(ItemRef::Mutable) + } + ItemRef::Immutable(item) => { + (array_type.try_get_item)(*item, array_item_index) + .map(ItemRef::Immutable) + } + }) else { + unreachable!(); + }; + + add_item_to_frame( + frame, + item_item, + data_changed, + ItemInfo { + item_title: array_item_name.as_ref(), + item_tag: array_item_name.as_ref(), + item_type_name: Some(array_type.item_type_name()), + item_type: ItemType::Reflected(array_type.item_type), + item_is_read_only, + }, + &prev_item_tags, + ); + } + + frame.unindent(); + } + ItemType::Reflected(TypeReflection::Slice(slice_type)) => { + frame.indent(); + + let mut prev_item_tags = prev_item_tags.to_vec(); + + prev_item_tags.push(item_tag); + + let Some(len) = (slice_type.try_get_len)(item.as_ref()) else { + unreachable!(); + }; + + for index in 0..len { + let item_name = index.to_string(); + + let Ok(item_item) = (slice_type.try_get_item)(item.as_ref(), index) + else { + unreachable!(); + }; + + let mut slice_item_changed = false; + + add_item_to_frame( + frame, + ItemRef::Immutable(item_item), + &mut slice_item_changed, + ItemInfo { + item_title: &item_name, + item_tag: &item_name, + item_type_name: Some(slice_type.item_type_name()), + item_type: ItemType::Reflected(slice_type.item_type), + item_is_read_only: true, + }, + &prev_item_tags, + ); + + assert!(!slice_item_changed); + } + + frame.unindent(); + } + ItemType::Reflected(TypeReflection::Reference(ref_type)) => { + let mut derefed_changed = false; + + let Some(derefed_item) = (ref_type.try_deref)(item.as_ref()) else { + unreachable!(); + }; + + add_item_to_frame( + frame, + ItemRef::Immutable(derefed_item), + &mut derefed_changed, + ItemInfo { + item_title, + item_tag: "derefed".into(), + item_type_name, + item_type: ItemType::Reflected(ref_type.ty), + item_is_read_only: true, + }, + prev_item_tags, + ); + + assert!(!derefed_changed); + } + ItemType::String => { + frame.same_line(); + + let item = match item { + ItemRef::Mutable(item) => { + let Some(item) = (*item).downcast_mut::<String>() else { + unreachable!(); + }; + + item + } + ItemRef::Immutable(item) => { + let Some(item) = (*item).downcast_ref::<String>() else { + unreachable!(); + }; + + &mut item.clone() + } + }; + + if frame + .input_text( + create_item_label(&item_tag, "value_input", &prev_item_tags), + item, + ) + .build() + { + *data_changed = true; + } + } + ItemType::CowStr => { + frame.same_line(); + + let item = match item { + ItemRef::Mutable(item) => { + let Some(item) = (*item).downcast_mut::<Cow<'static, str>>() else { + unreachable!(); + }; + + item + } + ItemRef::Immutable(item) => { + let Some(item) = (*item).downcast_ref::<Cow<'static, str>>() else { + unreachable!(); + }; + + &mut item.clone() + } + }; + + if frame + .input_text( + create_item_label(&item_tag, "value_input", &prev_item_tags), + item.to_mut(), + ) + .build() + { + *data_changed = true; + } + } + ItemType::Reflected(_) => unimplemented!(), + } +} + +fn create_scalar_item_input<Scalar>( + frame: &dear_imgui_bindings::Ui, + item: &mut ItemRef<'_>, + data_changed: &mut bool, + item_tag: &str, + prev_item_tags: &[&str], +) where + Scalar: dear_imgui_bindings::internal::DataTypeKind + Clone + 'static, +{ + let item = match item { + ItemRef::Mutable(item) => { + let Some(item) = (*item).downcast_mut::<Scalar>() else { + unreachable!(); + }; + + item + } + ItemRef::Immutable(item) => { + let Some(item) = (*item).downcast_ref::<Scalar>() else { + unreachable!(); + }; + + &mut item.clone() + } + }; + + if frame + .input_scalar( + create_item_label(item_tag, "value_input", &prev_item_tags), + item, + ) + .build() + { + *data_changed = true; + } +} + +fn create_enum_widgets( + frame: &dear_imgui_bindings::Ui, + item: &mut ItemRef<'_>, + data_changed: &mut bool, + item_tag: &str, + item_is_read_only: bool, + enum_type: &EnumReflection, + prev_item_tags: &[&str], +) +{ + let Some(mut curr_variant_index) = (enum_type.get_variant_index)(item.as_ref()) + else { + unreachable!(); + }; + + if create_combo_box( + frame, + create_item_label(&item_tag, "variant_select", &prev_item_tags), + &mut curr_variant_index, + enum_type.variants, + |variant| { + let disabled = variant.fields.as_ref().and_then(|fields| { + fields.fields().iter().find_map(|field| { + let field_name = match field.name { + Some(field_name) => Either::A(field_name), + None => Either::B(field.index), + }; + + let Some(field_type) = field.type_reflection() else { + return Some((field_name, "has no type reflection")); + }; + + if !field_type.has_default_value() { + return Some((field_name, "has no default value")); + } + + None + }) + }); + + ComboBoxItem { + label: variant.name.into(), + disabled: disabled.is_some(), + hover_tooltip: disabled.map(|(bad_field_name, bad_field_reason)| { + format!( + "Variant cannot be selected since field {} {}", + bad_field_name, bad_field_reason + ) + .into() + }), + } + }, + ) { + 'used: { + let ItemRef::Mutable(item) = item else { + break 'used; + }; + + let new_variant = &enum_type.variants[curr_variant_index]; + + let Ok(()) = (new_variant.try_write_new_to)( + *item, + &mut new_variant + .fields + .iter() + .map(|fields| fields.fields()) + .flatten() + .map(|field| { + let Some(field_type) = field.type_reflection() else { + // Variants with any field that is missing type reflection is + // disabled so that the user cannot select it + unreachable!(); + }; + + let Some(default_value) = field_type.default_value() else { + unreachable!(); + }; + + default_value + }), + ) else { + unreachable!(); + }; + + *data_changed = true; + } + } + + let mut prev_item_tags = prev_item_tags.to_vec(); + + prev_item_tags.push(item_tag); + + let curr_variant = &enum_type.variants[curr_variant_index]; + + if let Some(curr_variant_fields) = &curr_variant.fields { + for (field_index, field) in curr_variant_fields.fields().iter().enumerate() { + let Some(field_item_type) = + get_item_type(field.type_reflection(), field.type_id) + else { + continue; + }; + + let field_name: Cow<str> = match field.name { + Some(field_name) => field_name.into(), + None => field_index.to_string().into(), + }; + + let Ok(field_item) = (match item { + ItemRef::Mutable(item) => { + (curr_variant.try_get_field_mut)(*item, field_index) + .map(ItemRef::Mutable) + } + ItemRef::Immutable(item) => { + (curr_variant.try_get_field)(*item, field_index) + .map(ItemRef::Immutable) + } + }) else { + unreachable!(); + }; + + add_item_to_frame( + frame, + field_item, + data_changed, + ItemInfo { + item_title: field_name.as_ref(), + item_tag: field_name.as_ref(), + item_type_name: Some(field.type_name()), + item_type: field_item_type, + item_is_read_only: item_is_read_only, + }, + &prev_item_tags, + ); + } + }; +} + +fn create_item_label(item_tag: &str, value_name: &str, prev_item_tags: &[&str]) + -> String +{ + let mut item_label = String::with_capacity( + 2 + item_tag.len() + + prev_item_tags.len() + + prev_item_tags.iter().max().unwrap_or(&"".into()).len(), + ); + + item_label.push_str("##"); + + for prev_item_tag in prev_item_tags { + item_label.push('/'); + item_label.push_str(prev_item_tag); + } + + item_label.push('/'); + item_label.push_str(item_tag); + item_label.push(':'); + item_label.push_str(value_name); + + item_label +} + +fn get_whole_type_is_user_editable( + ty: Option<&'static TypeReflection>, + type_id: TypeId, +) -> bool +{ + let Some(item_type) = get_item_type(ty, type_id) else { + return false; + }; + + match item_type { + ItemType::Reflected(TypeReflection::Struct(struct_ty)) => { + for field in struct_ty.fields { + if matches!( + field.visibility, + Visibility::Private | Visibility::PubScoped(_) + ) { + continue; + } + + if !get_whole_type_is_user_editable( + field.type_reflection(), + field.type_id, + ) { + return false; + } + } + + true + } + ItemType::Reflected(TypeReflection::Enum(enum_ty)) => { + enum_ty.variants.iter().any(|variant| { + for field in variant.fields.iter().flat_map(|fields| fields.fields()) { + if !get_whole_type_is_user_editable( + field.type_reflection(), + field.type_id, + ) { + return false; + } + } + + true + }) + } + ItemType::Reflected( + TypeReflection::Slice(_) + | TypeReflection::Array(_) + | TypeReflection::Literal(_), + ) + | ItemType::String + | ItemType::CowStr => true, + ItemType::Reflected(_) => unimplemented!(), + } +} + +struct ComboBoxItem<'b> +{ + label: Cow<'b, str>, + disabled: bool, + hover_tooltip: Option<Cow<'b, str>>, +} + +fn create_combo_box<Item, ItemFn>( + frame: &dear_imgui_bindings::Ui, + label: impl AsRef<str>, + current_item: &mut usize, + items: &[Item], + item_fn: ItemFn, +) -> bool +where + for<'b> ItemFn: Fn(&'b Item) -> ComboBoxItem<'b>, +{ + let item_fn = &item_fn; + + let mut result = false; + + let preview_label = items.get(*current_item).map(|item| item_fn(item).label); + + if let Some(combo_token) = frame.begin_combo( + label, + preview_label + .as_ref() + .map(|preview_label| preview_label.as_ref()) + .unwrap_or(""), + ) { + for (idx, item) in items.iter().enumerate() { + let is_selected = idx == *current_item; + + if is_selected { + frame.set_item_default_focus(); + } + + let ComboBoxItem { + label: item_label, + disabled: item_disabled, + hover_tooltip: item_hover_tooltip, + } = item_fn(item); + + let disabled_token = frame.begin_disabled_with_cond(item_disabled); + + let clicked = frame.selectable(item_label.as_ref()); + + if clicked { + *current_item = idx; + result = true; + } + + disabled_token.end(); + + if let Some(item_hover_tooltip) = item_hover_tooltip { + if frame.is_item_hovered_with_flags( + dear_imgui_bindings::ItemHoveredFlags::ALLOW_WHEN_DISABLED, + ) { + frame.tooltip_text(item_hover_tooltip.as_ref()); + } + } + } + + combo_token.end(); + } + + result +} + +struct ListBoxItem<'b> +{ + label: Cow<'b, str>, + disabled: bool, + hover_tooltip: Option<Cow<'b, str>>, +} + +trait ListBoxExt +{ + fn build_extended<Item, ItemFn>( + self, + ui: &dear_imgui_bindings::Ui, + current_item: &mut i32, + items: &[Item], + item_fn: &ItemFn, + ) -> bool + where + for<'b> ItemFn: Fn(&'b Item) -> ListBoxItem<'b>; +} + +impl<Label> ListBoxExt for dear_imgui_bindings::ListBox<Label> +where + Label: AsRef<str>, +{ + fn build_extended<Item, ItemFn>( + self, + ui: &dear_imgui_bindings::Ui, + current_item: &mut i32, + items: &[Item], + item_fn: &ItemFn, + ) -> bool + where + for<'b> ItemFn: Fn(&'b Item) -> ListBoxItem<'b>, + { + let mut result = false; + + let lb = self; + + if let Some(_cb) = lb.begin(ui) { + for (idx, item) in items.iter().enumerate() { + if idx > i32::MAX as usize { + break; + } + + let idx_i32 = idx as i32; + + let ListBoxItem { + label: item_label, + disabled: item_disabled, + hover_tooltip: item_hover_tooltip, + } = item_fn(item); + + let is_selected = idx_i32 == *current_item; + + let disabled_token = ui.begin_disabled_with_cond(item_disabled); + + if ui + .selectable_config(&item_label) + .selected(is_selected) + .build() + { + *current_item = idx_i32; + result = true; + } + + disabled_token.end(); + + if let Some(item_hover_tooltip) = item_hover_tooltip { + if ui.is_item_hovered_with_flags( + dear_imgui_bindings::ItemHoveredFlags::ALLOW_WHEN_DISABLED, + ) { + ui.tooltip_text(item_hover_tooltip.as_ref()); + } + } + } + } + + result + } +} + +fn create_entity_title(ent_handle: &EntityHandle) -> String +{ + let ent_name = ent_handle.get::<EntityName>(); + + format!( + "{} ({})", + ent_name + .as_ref() + .map(|ent_name| ent_name.name.as_ref()) + .unwrap_or("<unnamed>"), + ent_handle.uid() + ) +} diff --git a/engine/src/windowing/dpi.rs b/engine/src/windowing/dpi.rs index e11fa25..d628709 100644 --- a/engine/src/windowing/dpi.rs +++ b/engine/src/windowing/dpi.rs @@ -1,3 +1,5 @@ +use crate::reflection::Reflection; + macro_rules! gen_struct_from_to_impls { ($struct: ident, fields=($($field: ident),*)) => { impl<Pixel> From<winit::dpi::$struct<Pixel>> for $struct<Pixel> @@ -46,78 +48,88 @@ macro_rules! gen_enum_from_to_impls { }; } -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Reflection)] +#[reflection(impl_with_generics(<u32>, <i32>, <f32>, <f64>))] pub struct PhysicalPosition<Pixel> { pub x: Pixel, - pub y: Pixel + pub y: Pixel, } impl<Pixel> PhysicalPosition<Pixel> { - pub fn to_logical<LogicalPixel>(&self, scale_factor: f64) -> LogicalPosition<LogicalPixel> + pub fn to_logical<LogicalPixel>( + &self, + scale_factor: f64, + ) -> LogicalPosition<LogicalPixel> where Pixel: Into<f64> + Clone, - LogicalPixel: From<f64> + LogicalPixel: From<f64>, { let x = self.x.clone().into(); let y = self.y.clone().into(); LogicalPosition { x: (x / scale_factor).into(), - y: (y / scale_factor).into() + y: (y / scale_factor).into(), } } - pub fn try_convert_from<SourcePixel>(source: PhysicalPosition<SourcePixel>) -> Result<Self, Pixel::Error> + pub fn try_convert_from<SourcePixel>( + source: PhysicalPosition<SourcePixel>, + ) -> Result<Self, Pixel::Error> where - Pixel: TryFrom<SourcePixel> + Pixel: TryFrom<SourcePixel>, { Ok(Self { x: source.x.try_into()?, - y: source.y.try_into()? + y: source.y.try_into()?, }) } } -gen_struct_from_to_impls!(PhysicalPosition, fields=(x, y)); +gen_struct_from_to_impls!(PhysicalPosition, fields = (x, y)); -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Reflection)] +#[reflection(impl_with_generics(<f64>))] pub struct LogicalPosition<Pixel> { pub x: Pixel, - pub y: Pixel + pub y: Pixel, } impl<Pixel> LogicalPosition<Pixel> { - pub fn try_convert_from<SourcePixel>(source: LogicalPosition<SourcePixel>) -> Result<Self, Pixel::Error> + pub fn try_convert_from<SourcePixel>( + source: LogicalPosition<SourcePixel>, + ) -> Result<Self, Pixel::Error> where - Pixel: TryFrom<SourcePixel> + Pixel: TryFrom<SourcePixel>, { Ok(Self { x: source.x.try_into()?, - y: source.y.try_into()? + y: source.y.try_into()?, }) } } -gen_struct_from_to_impls!(LogicalPosition, fields=(x, y)); +gen_struct_from_to_impls!(LogicalPosition, fields = (x, y)); -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Reflection)] pub enum Position { Physical(PhysicalPosition<i32>), - Logical(LogicalPosition<f64>) + Logical(LogicalPosition<f64>), } -gen_enum_from_to_impls!(Position, variants=(Physical, Logical)); +gen_enum_from_to_impls!(Position, variants = (Physical, Logical)); -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Reflection)] +#[reflection(impl_with_generics(<u32>))] pub struct PhysicalSize<Pixel> { pub width: Pixel, - pub height: Pixel + pub height: Pixel, } impl<Pixel> PhysicalSize<Pixel> @@ -125,60 +137,65 @@ impl<Pixel> PhysicalSize<Pixel> pub fn to_logical<LogicalPixel>(&self, scale_factor: f64) -> LogicalSize<LogicalPixel> where Pixel: Into<f64> + Clone, - LogicalPixel: From<f64> + LogicalPixel: From<f64>, { let width = self.width.clone().into(); let height = self.height.clone().into(); LogicalSize { width: (width / scale_factor).into(), - height: (height / scale_factor).into() + height: (height / scale_factor).into(), } } } impl<Pixel> PhysicalSize<Pixel> { - pub fn try_convert_from<SourcePixel>(source: PhysicalSize<SourcePixel>) -> Result<Self, Pixel::Error> + pub fn try_convert_from<SourcePixel>( + source: PhysicalSize<SourcePixel>, + ) -> Result<Self, Pixel::Error> where - Pixel: TryFrom<SourcePixel> + Pixel: TryFrom<SourcePixel>, { Ok(Self { width: source.width.try_into()?, - height: source.height.try_into()? + height: source.height.try_into()?, }) } } -gen_struct_from_to_impls!(PhysicalSize, fields=(width, height)); +gen_struct_from_to_impls!(PhysicalSize, fields = (width, height)); -#[derive(Debug, Default, Clone, PartialEq)] +#[derive(Debug, Default, Clone, PartialEq, Reflection)] +#[reflection(impl_with_generics(<f64>))] pub struct LogicalSize<Pixel> { pub width: Pixel, - pub height: Pixel + pub height: Pixel, } impl<Pixel> LogicalSize<Pixel> { - pub fn try_convert_from<SourcePixel>(source: LogicalSize<SourcePixel>) -> Result<Self, Pixel::Error> + pub fn try_convert_from<SourcePixel>( + source: LogicalSize<SourcePixel>, + ) -> Result<Self, Pixel::Error> where - Pixel: TryFrom<SourcePixel> + Pixel: TryFrom<SourcePixel>, { Ok(Self { width: source.width.try_into()?, - height: source.height.try_into()? + height: source.height.try_into()?, }) } } -gen_struct_from_to_impls!(LogicalSize, fields=(width, height)); +gen_struct_from_to_impls!(LogicalSize, fields = (width, height)); -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Reflection)] pub enum Size { Physical(PhysicalSize<u32>), - Logical(LogicalSize<f64>) + Logical(LogicalSize<f64>), } -gen_enum_from_to_impls!(Size, variants=(Physical, Logical)); +gen_enum_from_to_impls!(Size, variants = (Physical, Logical)); diff --git a/engine/src/windowing/mouse.rs b/engine/src/windowing/mouse.rs index 3145a17..93a0d93 100644 --- a/engine/src/windowing/mouse.rs +++ b/engine/src/windowing/mouse.rs @@ -1,10 +1,11 @@ use std::collections::HashMap; use crate::ecs::Sole; +use crate::reflection::Reflection; use crate::vector::Vec2; use crate::windowing::dpi::PhysicalPosition; -#[derive(Debug, Default, Clone, Sole)] +#[derive(Debug, Default, Clone, Sole, Reflection)] #[non_exhaustive] pub struct Mouse { @@ -21,7 +22,7 @@ pub struct Mouse pub curr_tick_scroll_delta: ScrollDelta, } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, Reflection)] pub struct ScrollDelta { pub vert_lines: f32, diff --git a/engine/src/windowing/window.rs b/engine/src/windowing/window.rs index 8f95dc8..54c24ac 100644 --- a/engine/src/windowing/window.rs +++ b/engine/src/windowing/window.rs @@ -21,7 +21,7 @@ impl Id } } -#[derive(Debug, Component, Clone)] +#[derive(Debug, Component, Clone, Reflection)] #[non_exhaustive] pub struct CreationAttributes { @@ -165,7 +165,7 @@ impl Default for CreationAttributes } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Reflection)] #[non_exhaustive] pub enum Fullscreen { @@ -173,7 +173,7 @@ pub enum Fullscreen } /// Window icon -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Reflection)] #[non_exhaustive] pub enum Icon { @@ -183,7 +183,7 @@ pub enum Icon WindowsResource(Cow<'static, str>), } -#[derive(Debug, Component, Clone, Copy)] +#[derive(Debug, Default, Component, Clone, Copy, Reflection)] pub struct CreationReady; #[derive(Debug, Component, Reflection)] @@ -254,7 +254,7 @@ impl Window } } -#[derive(Debug, Component)] +#[derive(Debug, Component, Reflection)] pub struct Closed; #[derive( diff --git a/src/main.rs b/src/main.rs index de3e835..51d50d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,14 +8,23 @@ use engine::camera::fly::{ Fly as FlyCamera, Options as FlyCameraOptions, }; -use engine::camera::{Active as ActiveCamera, Camera}; -use engine::color::Color; +use engine::camera::{ + Active as ActiveCamera, + Camera, + Controllable as ControllableCamera, +}; +use engine::color::{Color, Rgb}; use engine::data_types::dimens::Dimens3; use engine::ecs::actions::Actions; use engine::ecs::error::Context as _; use engine::ecs::phase::START as START_PHASE; +use engine::ecs::query::term::With; use engine::ecs::sole::Single; +use engine::ecs::system::initializable::Initializable; +use engine::ecs::system::Into; +use engine::ecs::Query; use engine::image::{Format as ImageFormat, Image}; +use engine::input::keyboard::Keyboard; use engine::input::Extension as InputExtension; use engine::lighting::{AttenuationParams, GlobalLight, PointLight}; use engine::material::{Flags as MaterialFlags, Material}; @@ -30,11 +39,17 @@ use engine::rendering::{ TargetWindow as RenderingTargetWindow, }; use engine::transform::WorldPosition; +use engine::ui::dear_imgui::{ + Context as DearImguiContext, + Extension as DearImguiExtension, + TargetWindow as DearImguiTargetWindow, +}; use engine::vector::Vec3; use engine::windowing::window::{ CreationAttributes as WindowCreationAttributes, CursorGrabMode as WindowCursorGrabMode, Icon as WindowIcon, + Window, }; use engine::{Engine, Error as EngineError}; use tracing::level_filters::LevelFilter; @@ -43,11 +58,11 @@ use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::EnvFilter; -const YELLOW: Color<f32> = Color { - red: 0.988235294118, - green: 0.941176470588, - blue: 0.0274509803922, -}; +const YELLOW: Color<f32> = Color::Rgb(Rgb { + r: 0.988235294118, + g: 0.941176470588, + b: 0.0274509803922, +}); const RESOURCE_DIR: &str = "res"; @@ -70,6 +85,7 @@ fn main() -> Result<(), EngineError> .with_extension(FlyCameraExtension(FlyCameraOptions { mouse_sensitivity: 0.2, })) + .with_extension(DearImguiExtension::default()) .with_extension(Application) .start(); @@ -85,6 +101,15 @@ impl engine::ecs::extension::Extension for Application let _ = collector.add_sole(GlobalLight::default()); collector.add_system(*START_PHASE, init); + + collector.add_system(*engine::ecs::phase::UPDATE, toggle_ui); + + collector.add_system( + *engine::ecs::phase::UPDATE, + engine::ui::view::world::show + .into_system() + .initialize((engine::ui::view::world::State::default(),)), + ); } } @@ -107,6 +132,7 @@ fn init(mut assets: Single<Assets>, mut actions: Actions) -> Result<(), EngineEr .with_cursor_visible(false) .with_cursor_grab_mode(WindowCursorGrabMode::Locked), RenderingTargetWindow, + DearImguiTargetWindow, ), ); @@ -199,3 +225,40 @@ fn configure_logging() -> Result<(), EngineError> Ok(()) } + +fn toggle_ui( + window_query: Query<(&mut Window,), (With<DearImguiTargetWindow>,)>, + camera_query: Query<(&mut ControllableCamera,), (With<Camera>, With<ActiveCamera>)>, + mut dear_imgui_context: Single<DearImguiContext>, + keyboard: Single<Keyboard>, +) -> Result<(), EngineError> +{ + let dear_imgui_context = dear_imgui_context.get_mut()?; + let keyboard = keyboard.get()?; + + let Some((mut window,)) = window_query.iter().next() else { + return Ok(()); + }; + + let Some((mut controllable_camera,)) = camera_query.iter().next() else { + return Ok(()); + }; + + if keyboard.just_pressed(engine::input::keyboard::Key::Escape) { + if dear_imgui_context.enabled { + dear_imgui_context.enabled = false; + window.cursor_visible = false; + window.cursor_grab_mode = WindowCursorGrabMode::Locked; + controllable_camera.control_enabled = true; + } else { + dear_imgui_context.enabled = true; + window.cursor_visible = true; + window.cursor_grab_mode = WindowCursorGrabMode::None; + controllable_camera.control_enabled = false; + } + + window.set_changed(); + } + + Ok(()) +} |
