Converted macros for script field inspectors to use templates

This commit is contained in:
Kah Wei 2022-11-10 16:20:04 +08:00
parent e8d2179d76
commit bdc7297937
6 changed files with 361 additions and 240 deletions

View File

@ -266,10 +266,10 @@ namespace SHADE
static const std::vector<std::string> COMPONENT_LABELS = { "X", "Y" }; static const std::vector<std::string> COMPONENT_LABELS = { "X", "Y" };
return SHEditorWidgets::DragN<float, 2>(label, COMPONENT_LABELS, { &value.x, &value.y }, 0.1f, "%.3f", float{}, float{}, 0, isHovered); return SHEditorWidgets::DragN<float, 2>(label, COMPONENT_LABELS, { &value.x, &value.y }, 0.1f, "%.3f", float{}, float{}, 0, isHovered);
} }
bool SHEditorUI::InputVec3(const std::string& label, SHVec3& value, bool* isHovered, float speed) bool SHEditorUI::InputVec3(const std::string& label, SHVec3& value, bool* isHovered)
{ {
static const std::vector<std::string> COMPONENT_LABELS = { "X", "Y", "Z"}; static const std::vector<std::string> COMPONENT_LABELS = { "X", "Y", "Z"};
return SHEditorWidgets::DragN<float, 3>(label, COMPONENT_LABELS, { &value.x, &value.y, &value.z }, speed, "%.3f", float{}, float{}, 0, isHovered); return SHEditorWidgets::DragN<float, 3>(label, COMPONENT_LABELS, { &value.x, &value.y, &value.z }, 0.1f, "%.3f", float{}, float{}, 0, isHovered);
} }
bool SHEditorUI::InputTextField(const std::string& label, std::string& value, bool* isHovered) bool SHEditorUI::InputTextField(const std::string& label, std::string& value, bool* isHovered)

View File

@ -296,7 +296,7 @@ namespace SHADE
/// <param name="value">Reference to the variable to store the result.</param> /// <param name="value">Reference to the variable to store the result.</param>
/// <param name="isHovered>If set, stores the hover state of this widget.</param> /// <param name="isHovered>If set, stores the hover state of this widget.</param>
/// <returns>True if the value was changed.</returns> /// <returns>True if the value was changed.</returns>
static bool InputVec3(const std::string& label, SHVec3& value, bool* isHovered = nullptr, float speed = 0.1f); static bool InputVec3(const std::string& label, SHVec3& value, bool* isHovered = nullptr);
/// <summary> /// <summary>
/// Creates a text field widget for string input. /// Creates a text field widget for string input.
/// <br/> /// <br/>

View File

@ -31,98 +31,13 @@ of DigiPen Institute of Technology is prohibited.
#include "Editor/Command/SHCommand.hpp" #include "Editor/Command/SHCommand.hpp"
#include "TooltipAttribute.hxx" #include "TooltipAttribute.hxx"
#include "RangeAttribute.hxx" #include "RangeAttribute.hxx"
#include "Math/Vector2.hxx"
#include "Math/Vector3.hxx"
// Using Directives // Using Directives
using namespace System; using namespace System;
using namespace System::Collections::Generic; using namespace System::Collections::Generic;
/*-------------------------------------------------------------------------------------*/
/* Macro Functions */
/*-------------------------------------------------------------------------------------*/
/// <summary>
/// Macro expansion that is used in renderFieldInInspector() to check the type of a field
/// named "field" against the specified type and if it matches, retrieves the value of
/// that field from an object named "object" and pass it into the specified SHEditorUI::
/// function named "FUNC" by casting it into the NATIVE_TYPE specified.
/// <br/>
/// This only works for primitive types that have the same types for managed and native.
/// </summary>
/// <param name="MANAGED_TYPE">The managed type of the object to edit.</param>
/// <param name="NATIVE_TYPE">The native type of the object to edit.</param>
/// <param name="FUNC">The SHEditorUI:: function to use for editing.</param>
#define RENDER_FIELD(MANAGED_TYPE, NATIVE_TYPE, FUNC) \
(field->FieldType == MANAGED_TYPE::typeid) \
{ \
NATIVE_TYPE val = safe_cast<NATIVE_TYPE>(field->GetValue(object)); \
NATIVE_TYPE oldVal = val; \
if (SHEditorUI::FUNC(Convert::ToNative(field->Name), val, &isHovered))\
{ \
field->SetValue(object, val); \
registerUndoAction(object, field, val, oldVal); \
} \
} \
/// <summary>
/// Alternative to RENDER_FIELD that checks for RangeAttribute and switches to a slider
/// instead.
/// </summary>
/// <param name="MANAGED_TYPE">The managed type of the object to edit.</param>
/// <param name="NATIVE_TYPE">The native type of the object to edit.</param>
/// <param name="FUNC">The SHEditorUI:: function to use for editing.</param>
#define RENDER_FIELD_RANGE(MANAGED_TYPE, NATIVE_TYPE, FUNC) \
(field->FieldType == MANAGED_TYPE::typeid) \
{ \
NATIVE_TYPE val = safe_cast<NATIVE_TYPE>(field->GetValue(object)); \
NATIVE_TYPE oldVal = val; \
\
RangeAttribute^ rangeAttrib = hasAttribute<RangeAttribute^>(field);\
const std::string FIELD_NAME = Convert::ToNative(field->Name); \
bool changed = false; \
if (rangeAttrib) \
{ \
changed = SHEditorUI::InputSlider \
( \
FIELD_NAME, \
static_cast<NATIVE_TYPE>(rangeAttrib->Min), \
static_cast<NATIVE_TYPE>(rangeAttrib->Max), \
val, &isHovered \
); \
} \
else \
{ \
changed = SHEditorUI::FUNC(FIELD_NAME, val, &isHovered); \
} \
\
if (changed) \
{ \
field->SetValue(object, val); \
registerUndoAction(object, field, val, oldVal); \
} \
} \
/// <summary>
/// Macro expansion that is used in renderFieldInInspector() to check the type of a field
/// named "field" against the specified type and if it matches, retrieves the value of
/// that field from an object named "object" and pass it into the specified SHEditorUI::
/// function named "FUNC" by casting it into the NATIVE_TYPE specified.
/// <br/>
/// This only works for types that have an implementation of Convert::ToNative and
/// Convert::ToCLI.
/// </summary>
/// <param name="MANAGED_TYPE">The managed type of the object to edit.</param>
/// <param name="NATIVE_TYPE">The native type of the object to edit.</param>
/// <param name="FUNC">The SHEditorUI:: function to use for editing.</param>
#define RENDER_FIELD_CASTED(MANAGED_TYPE, NATIVE_TYPE, FUNC) \
(field->FieldType == MANAGED_TYPE::typeid) \
{ \
NATIVE_TYPE val = Convert::ToNative(safe_cast<MANAGED_TYPE>(field->GetValue(object))); \
NATIVE_TYPE oldVal = val; \
\
if (SHEditorUI::FUNC(Convert::ToNative(field->Name), val, &isHovered)) \
{ \
field->SetValue(object, Convert::ToCLI(val)); \
registerUndoAction(object, field, Convert::ToCLI(val), Convert::ToCLI(oldVal)); \
} \
} \
/*-------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------*/
/* Function Definitions */ /* Function Definitions */
/*-------------------------------------------------------------------------------------*/ /*-------------------------------------------------------------------------------------*/
@ -238,22 +153,28 @@ namespace SHADE
{ {
bool isHovered = false; bool isHovered = false;
if RENDER_FIELD_RANGE (Int16, int, InputInt) const bool MODIFIED_PRIMITIVE =
else if RENDER_FIELD_RANGE (Int32, int, InputInt) renderFieldInInspector<int , Int16 >(field, object, SHEditorUI::InputInt , &isHovered) ||
else if RENDER_FIELD_RANGE (Int64, int, InputInt) renderFieldInInspector<int , Int32 >(field, object, SHEditorUI::InputInt , &isHovered) ||
else if RENDER_FIELD_RANGE (UInt16, unsigned int, InputUnsignedInt) renderFieldInInspector<int , Int64 >(field, object, SHEditorUI::InputInt , &isHovered) ||
else if RENDER_FIELD_RANGE (UInt32, unsigned int, InputUnsignedInt) renderFieldInInspector<int , UInt16 >(field, object, SHEditorUI::InputInt , &isHovered) ||
else if RENDER_FIELD_RANGE (UInt64, unsigned int, InputUnsignedInt) renderFieldInInspector<int , UInt32 >(field, object, SHEditorUI::InputInt , &isHovered) ||
else if RENDER_FIELD_RANGE (Byte, int, InputInt) renderFieldInInspector<int , UInt64 >(field, object, SHEditorUI::InputInt , &isHovered) ||
else if RENDER_FIELD (bool, bool, InputCheckbox) renderFieldInInspector<int , Byte >(field, object, SHEditorUI::InputInt , &isHovered) ||
else if RENDER_FIELD_RANGE (float, float, InputFloat) renderFieldInInspector<bool , bool >(field, object, SHEditorUI::InputCheckbox, &isHovered) ||
else if RENDER_FIELD_RANGE (double, double, InputDouble) renderFieldInInspector<float , float >(field, object, SHEditorUI::InputFloat , &isHovered) ||
else if (field->FieldType->IsSubclassOf(Enum::typeid)) renderFieldInInspector<double, double >(field, object, SHEditorUI::InputDouble , &isHovered) ||
renderFieldInInspector<SHVec2, Vector2>(field, object, SHEditorUI::InputVec2 , &isHovered) ||
renderFieldInInspector<SHVec3, Vector3>(field, object, SHEditorUI::InputVec3 , &isHovered);
if (!MODIFIED_PRIMITIVE)
{
if (field->FieldType->IsSubclassOf(Enum::typeid))
{ {
// Get all the names of the enums // Get all the names of the enums
const array<String^>^ ENUM_NAMES = field->FieldType->GetEnumNames(); const array<String^>^ ENUM_NAMES = field->FieldType->GetEnumNames();
std::vector<std::string> nativeEnumNames; std::vector<std::string> nativeEnumNames;
for each (String^ str in ENUM_NAMES) for each (String ^ str in ENUM_NAMES)
{ {
nativeEnumNames.emplace_back(Convert::ToNative(str)); nativeEnumNames.emplace_back(Convert::ToNative(str));
} }
@ -266,8 +187,6 @@ namespace SHADE
registerUndoAction(object, field, val, oldVal); registerUndoAction(object, field, val, oldVal);
} }
} }
else if RENDER_FIELD_CASTED(Vector2, SHVec2, InputVec2)
else if RENDER_FIELD_CASTED(Vector3, SHVec3, InputVec3)
else if (field->FieldType == String::typeid) else if (field->FieldType == String::typeid)
{ {
// Prevent issues where String^ is null due to being empty // Prevent issues where String^ is null due to being empty
@ -315,7 +234,7 @@ namespace SHADE
SHEditorUI::Indent(); SHEditorUI::Indent();
int i = 0; int i = 0;
for each (System::Object^ obj in listEnummerable) for each (System::Object ^ obj in listEnummerable)
{ {
int val = safe_cast<int>(obj); int val = safe_cast<int>(obj);
SHEditorUI::InputInt(std::to_string(i), val, &isHovered); SHEditorUI::InputInt(std::to_string(i), val, &isHovered);
@ -395,6 +314,7 @@ namespace SHADE
return; return;
} }
} }
}
// Check if the field has a specific attribute // Check if the field has a specific attribute
TooltipAttribute^ toolTip = hasAttribute<TooltipAttribute^>(field); TooltipAttribute^ toolTip = hasAttribute<TooltipAttribute^>(field);

View File

@ -0,0 +1,109 @@
/************************************************************************************//*!
\file Editor.h++
\author Tng Kah Wei, kahwei.tng, 390009620
\par email: kahwei.tng\@digipen.edu
\date Nov 10, 2022
\brief Contains the definition of templated functions for the managed Editor
static class.
Note: This file is written in C++17/CLI.
Copyright (C) 2022 DigiPen Institute of Technology.
Reproduction or disclosure of this file or its contents without the prior written consent
of DigiPen Institute of Technology is prohibited.
*//*************************************************************************************/
#pragma once
// Primary Include
#include "Editor.hxx"
namespace SHADE
{
template<typename NativeType, typename ManagedType>
bool Editor::renderFieldInInspector(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, EditorFieldFunc<NativeType> fieldEditor, bool* isHovered)
{
if (fieldInfo->FieldType == ManagedType::typeid)
{
RangeAttribute^ rangeAttrib;
if constexpr (std::is_arithmetic_v<NativeType> && !std::is_same_v<NativeType, bool>)
{
rangeAttrib = hasAttribute<RangeAttribute^>(fieldInfo);
}
ManagedType val = safe_cast<ManagedType>(fieldInfo->GetValue(object));
if (renderFieldInInspector<NativeType, ManagedType>
(
Convert::ToNative(fieldInfo->Name),
val,
fieldEditor,
isHovered,
rangeAttrib
))
{
fieldInfo->SetValue(object, val);
// TODO: Register undo
}
return true;
}
return false;
}
template<typename NativeType, typename ManagedType>
bool Editor::renderFieldInInspector(const std::string& fieldName, ManagedType% managedVal, EditorFieldFunc<NativeType> fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib)
{
// Retrieve the native version of the object
NativeType val;
if constexpr (IsPrimitiveTypeMatches_V<NativeType>)
{
val = safe_cast<NativeType>(managedVal);
}
else
{
val = Convert::ToNative(managedVal);
}
// Throw into the SHEditorUI function
NativeType oldVal = val;
bool changed = false;
if (rangeAttrib)
{
// Do not allow bools for Sliders just in case
if constexpr (std::is_arithmetic_v<NativeType> && !std::is_same_v<NativeType, bool>)
{
changed = SHEditorUI::InputSlider
(
fieldName,
static_cast<NativeType>(rangeAttrib->Min),
static_cast<NativeType>(rangeAttrib->Max),
val, isHovered
);
}
}
else
{
changed = fieldEditor(fieldName, val, isHovered);
}
if (changed)
{
if constexpr (IsPrimitiveTypeMatches_V<NativeType>)
{
//field->SetValue(object, val);
managedVal = val;
//registerUndoAction(object, field, val, oldVal);
}
else
{
managedVal = Convert::ToCLI(val);
//registerUndoAction(object, field, Convert::ToCLI(val), Convert::ToCLI(oldVal));
}
return true;
}
return false;
}
}

View File

@ -17,9 +17,14 @@ of DigiPen Institute of Technology is prohibited.
#include "Engine/Entity.hxx" #include "Engine/Entity.hxx"
#include "Scripts/Script.hxx" #include "Scripts/Script.hxx"
#include "UndoRedoStack.hxx" #include "UndoRedoStack.hxx"
#include "RangeAttribute.hxx"
namespace SHADE namespace SHADE
{ {
template<typename NativeType>
using EditorFieldFunc = bool(*)(const std::string& label, NativeType& val, bool* isHovered);
/// <summary> /// <summary>
/// Static class for Editor-related functions /// Static class for Editor-related functions
/// </summary> /// </summary>
@ -91,8 +96,59 @@ namespace SHADE
/// <param name="entity">The Entity to render the Scripts of.</param> /// <param name="entity">The Entity to render the Scripts of.</param>
/// <param name="script">The Script to render the inspector for.</param> /// <param name="script">The Script to render the inspector for.</param>
static void renderScriptContextMenu(Entity entity, Script^ script); static void renderScriptContextMenu(Entity entity, Script^ script);
/// <summary>
/// Adds changes to a variable as an undo-able/redo-able action on the Undo-Redo
/// stack.
/// </summary>
/// <param name="object">The object that changes are applied to.</param>
/// <param name="field">The field that was changed.</param>
/// <param name="newData">New data to set.</param>
/// <param name="oldData">Data that was overriden.</param>
static void registerUndoAction(System::Object^ object, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData); static void registerUndoAction(System::Object^ object, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData);
/// <summary>
/// Checks if a specific field has the specified attribute
/// </summary>
/// <typeparam name="Attribute">Type of Attribute to check for.</typeparam>
/// <param name="field">The field to check.</param>
/// <returns>The attribute to check for if it exists. Null otherwise.</returns>
generic<typename Attribute> where Attribute : System::Attribute generic<typename Attribute> where Attribute : System::Attribute
static Attribute hasAttribute(System::Reflection::FieldInfo^ field); static Attribute hasAttribute(System::Reflection::FieldInfo^ field);
/// <summary>
/// Checks if the specified field is of the specified native and managed type
/// equivalent and renders a ImGui field editor based on the specified field
/// editor function. Also handles fields that contain a RangeAttribute.
/// </summary>
/// <typeparam name="NativeType">Native type of the field.</typeparam>
/// <typeparam name="ManagedType">Managed type of the field.</typeparam>
/// <param name="fieldInfo">Describes the field to modify.</param>
/// <param name="object">Object to modify that has the specified field.</param>
/// <param name="fieldEditor">ImGui field editor function to use.</param>
/// <param name="isHovered">
/// Pointer to a bool that stores if the field editor was hovered over.
/// </param>
/// <returns>True if the field is modified.</returns>
template<typename NativeType, typename ManagedType>
static bool renderFieldInInspector(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, EditorFieldFunc<NativeType> fieldEditor, bool* isHovered);
/// <summary>
/// Renders a ImGui field editor based on the type of parameters specified.
/// </summary>
/// <typeparam name="NativeType">Native type of the field.</typeparam>
/// <typeparam name="ManagedType">Managed type of the field.</typeparam>
/// <param name="fieldName">Label to use for the field editor.</param>
/// <param name="managedVal">
/// Tracking reference for the managed variable to modify.
/// </param>
/// <param name="fieldEditor">ImGui field editor function to use.</param>
/// <param name="isHovered">
/// Pointer to a bool that stores if the field editor was hovered over.
/// </param>
/// <param name="rangeAttrib">
/// If provided and the type supports it, the field will be rendered with a
/// slider instead.
/// </param>
/// <returns>True if the field is modified.</returns>
template<typename NativeType, typename ManagedType>
static bool renderFieldInInspector(const std::string& fieldName, ManagedType% managedVal, EditorFieldFunc<NativeType> fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib);
}; };
} }
#include "Editor.h++"

View File

@ -152,6 +152,40 @@ namespace SHADE
}; };
/// <summary>
/// Checks if the specified type is matching between native C++ and the managed type.
/// </summary>
/// <typeparam name="T">Type to check.</typeparam>
template<typename T>
struct IsPrimitiveTypeMatches : public std::integral_constant
<
bool,
std::is_same_v<System::Int16 , typename std::remove_cv_t<T>> ||
std::is_same_v<System::Int32 , typename std::remove_cv_t<T>> ||
std::is_same_v<System::Int64 , typename std::remove_cv_t<T>> ||
std::is_same_v<System::UInt16, typename std::remove_cv_t<T>> ||
std::is_same_v<System::UInt32, typename std::remove_cv_t<T>> ||
std::is_same_v<System::UInt64, typename std::remove_cv_t<T>> ||
std::is_same_v<System::Byte , typename std::remove_cv_t<T>> ||
std::is_same_v<bool , typename std::remove_cv_t<T>> ||
std::is_same_v<double , typename std::remove_cv_t<T>> ||
std::is_same_v<float , typename std::remove_cv_t<T>> ||
std::is_same_v<int8_t , typename std::remove_cv_t<T>> ||
std::is_same_v<int16_t , typename std::remove_cv_t<T>> ||
std::is_same_v<int32_t , typename std::remove_cv_t<T>> ||
std::is_same_v<int64_t , typename std::remove_cv_t<T>> ||
std::is_same_v<uint16_t , typename std::remove_cv_t<T>> ||
std::is_same_v<uint32_t , typename std::remove_cv_t<T>> ||
std::is_same_v<uint64_t , typename std::remove_cv_t<T>>
>
{};
/// <summary>
/// Short hand for IsPrimitiveTypeMatches::value
/// </summary>
/// <typeparam name="T">Type to check.</typeparam>
template<typename T>
inline constexpr bool IsPrimitiveTypeMatches_V = IsPrimitiveTypeMatches<T>::value;
/// <summary> /// <summary>
/// Type Transformer for managed types to native types. /// Type Transformer for managed types to native types.
/// </summary> /// </summary>
@ -163,6 +197,7 @@ namespace SHADE
{ {
public: public:
using Value = void; using Value = void;
static bool IsDefined() { return is_same_v<ManagedType, Value>; }
}; };
template<> struct ToNativeType<System::Int16> { using Value = int16_t; }; template<> struct ToNativeType<System::Int16> { using Value = int16_t; };
template<> struct ToNativeType<System::Int32> { using Value = int32_t; }; template<> struct ToNativeType<System::Int32> { using Value = int32_t; };
@ -195,17 +230,18 @@ namespace SHADE
{ {
public: public:
using Value = void; using Value = void;
static bool IsDefined() { return is_same_v<NativeType, Value>; }
}; };
template<> struct ToManagedType<int8_t> { using Value = System::Byte; }; template<> struct ToManagedType<int8_t > { using Value = System::Byte; };
template<> struct ToManagedType<int16_t> { using Value = System::Int16; }; template<> struct ToManagedType<int16_t > { using Value = System::Int16; };
template<> struct ToManagedType<int32_t> { using Value = System::Int32; }; template<> struct ToManagedType<int32_t > { using Value = System::Int32; };
template<> struct ToManagedType<int64_t> { using Value = System::Int64; }; template<> struct ToManagedType<int64_t > { using Value = System::Int64; };
template<> struct ToManagedType<uint16_t> { using Value = System::UInt16; }; template<> struct ToManagedType<uint16_t> { using Value = System::UInt16; };
template<> struct ToManagedType<uint32_t> { using Value = System::UInt32; }; template<> struct ToManagedType<uint32_t> { using Value = System::UInt32; };
template<> struct ToManagedType<uint64_t> { using Value = System::UInt64; }; template<> struct ToManagedType<uint64_t> { using Value = System::UInt64; };
template<> struct ToManagedType<bool> { using Value = bool; }; template<> struct ToManagedType<bool > { using Value = bool; };
template<> struct ToManagedType<double> { using Value = double; }; template<> struct ToManagedType<double > { using Value = double; };
template<> struct ToManagedType<float> { using Value = float; }; template<> struct ToManagedType<float > { using Value = float; };
/// <summary> /// <summary>
/// Alias for ToManagedType::Value /// Alias for ToManagedType::Value