summaryrefslogtreecommitdiff
path: root/engine
diff options
context:
space:
mode:
Diffstat (limited to 'engine')
-rw-r--r--engine/res/ui/delete.pngbin0 -> 424 bytes
-rw-r--r--engine/src/camera.rs7
-rw-r--r--engine/src/camera/fly.rs3
-rw-r--r--engine/src/collision.rs5
-rw-r--r--engine/src/data_types/color.rs143
-rw-r--r--engine/src/data_types/dimens.rs8
-rw-r--r--engine/src/data_types/vector.rs29
-rw-r--r--engine/src/draw_flags.rs15
-rw-r--r--engine/src/file_format/wavefront/mtl.rs10
-rw-r--r--engine/src/image.rs70
-rw-r--r--engine/src/lighting.rs13
-rw-r--r--engine/src/material.rs15
-rw-r--r--engine/src/projection.rs11
-rw-r--r--engine/src/rendering/backend/opengl.rs49
-rw-r--r--engine/src/rendering/shader/cursor.rs10
-rw-r--r--engine/src/rendering/shader/default.rs28
-rw-r--r--engine/src/texture.rs8
-rw-r--r--engine/src/transform.rs5
-rw-r--r--engine/src/ui.rs1
-rw-r--r--engine/src/ui/dear_imgui.rs9
-rw-r--r--engine/src/ui/view.rs1
-rw-r--r--engine/src/ui/view/world.rs1550
-rw-r--r--engine/src/windowing/dpi.rs87
-rw-r--r--engine/src/windowing/mouse.rs5
-rw-r--r--engine/src/windowing/window.rs10
25 files changed, 1850 insertions, 242 deletions
diff --git a/engine/res/ui/delete.png b/engine/res/ui/delete.png
new file mode 100644
index 0000000..f85ba1c
--- /dev/null
+++ b/engine/res/ui/delete.png
Binary files differ
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(