diff --git a/SHADE_Engine/src/Editor/SHEditorUI.cpp b/SHADE_Engine/src/Editor/SHEditorUI.cpp index 49cfbfd6..ba394f77 100644 --- a/SHADE_Engine/src/Editor/SHEditorUI.cpp +++ b/SHADE_Engine/src/Editor/SHEditorUI.cpp @@ -266,10 +266,10 @@ namespace SHADE static const std::vector COMPONENT_LABELS = { "X", "Y" }; return SHEditorWidgets::DragN(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 COMPONENT_LABELS = { "X", "Y", "Z"}; - return SHEditorWidgets::DragN(label, COMPONENT_LABELS, { &value.x, &value.y, &value.z }, speed, "%.3f", float{}, float{}, 0, isHovered); + return SHEditorWidgets::DragN(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) diff --git a/SHADE_Engine/src/Editor/SHEditorUI.h b/SHADE_Engine/src/Editor/SHEditorUI.h index 4e8f4400..e0ea0521 100644 --- a/SHADE_Engine/src/Editor/SHEditorUI.h +++ b/SHADE_Engine/src/Editor/SHEditorUI.h @@ -296,7 +296,7 @@ namespace SHADE /// Reference to the variable to store the result. /// -/// 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. -///
-/// This only works for primitive types that have the same types for managed and native. -/// -/// The managed type of the object to edit. -/// The native type of the object to edit. -/// The SHEditorUI:: function to use for editing. -#define RENDER_FIELD(MANAGED_TYPE, NATIVE_TYPE, FUNC) \ -(field->FieldType == MANAGED_TYPE::typeid) \ -{ \ - NATIVE_TYPE val = safe_cast(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); \ - } \ -} \ -/// -/// Alternative to RENDER_FIELD that checks for RangeAttribute and switches to a slider -/// instead. -/// -/// The managed type of the object to edit. -/// The native type of the object to edit. -/// The SHEditorUI:: function to use for editing. -#define RENDER_FIELD_RANGE(MANAGED_TYPE, NATIVE_TYPE, FUNC) \ -(field->FieldType == MANAGED_TYPE::typeid) \ -{ \ - NATIVE_TYPE val = safe_cast(field->GetValue(object)); \ - NATIVE_TYPE oldVal = val; \ - \ - RangeAttribute^ rangeAttrib = hasAttribute(field);\ - const std::string FIELD_NAME = Convert::ToNative(field->Name); \ - bool changed = false; \ - if (rangeAttrib) \ - { \ - changed = SHEditorUI::InputSlider \ - ( \ - FIELD_NAME, \ - static_cast(rangeAttrib->Min), \ - static_cast(rangeAttrib->Max), \ - val, &isHovered \ - ); \ - } \ - else \ - { \ - changed = SHEditorUI::FUNC(FIELD_NAME, val, &isHovered); \ - } \ - \ - if (changed) \ - { \ - field->SetValue(object, val); \ - registerUndoAction(object, field, val, oldVal); \ - } \ -} \ -/// -/// 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. -///
-/// This only works for types that have an implementation of Convert::ToNative and -/// Convert::ToCLI. -///
-/// The managed type of the object to edit. -/// The native type of the object to edit. -/// The SHEditorUI:: function to use for editing. -#define RENDER_FIELD_CASTED(MANAGED_TYPE, NATIVE_TYPE, FUNC) \ -(field->FieldType == MANAGED_TYPE::typeid) \ -{ \ - NATIVE_TYPE val = Convert::ToNative(safe_cast(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 */ /*-------------------------------------------------------------------------------------*/ @@ -238,161 +153,166 @@ namespace SHADE { bool isHovered = false; - if RENDER_FIELD_RANGE (Int16, int, InputInt) - else if RENDER_FIELD_RANGE (Int32, int, InputInt) - else if RENDER_FIELD_RANGE (Int64, int, InputInt) - else if RENDER_FIELD_RANGE (UInt16, unsigned int, InputUnsignedInt) - else if RENDER_FIELD_RANGE (UInt32, unsigned int, InputUnsignedInt) - else if RENDER_FIELD_RANGE (UInt64, unsigned int, InputUnsignedInt) - else if RENDER_FIELD_RANGE (Byte, int, InputInt) - else if RENDER_FIELD (bool, bool, InputCheckbox) - else if RENDER_FIELD_RANGE (float, float, InputFloat) - else if RENDER_FIELD_RANGE (double, double, InputDouble) - else if (field->FieldType->IsSubclassOf(Enum::typeid)) - { - // Get all the names of the enums - const array^ ENUM_NAMES = field->FieldType->GetEnumNames(); - std::vector nativeEnumNames; - for each (String^ str in ENUM_NAMES) - { - nativeEnumNames.emplace_back(Convert::ToNative(str)); - } + const bool MODIFIED_PRIMITIVE = + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputInt , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputCheckbox, &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputFloat , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputDouble , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputVec2 , &isHovered) || + renderFieldInInspector(field, object, SHEditorUI::InputVec3 , &isHovered); - int val = safe_cast(field->GetValue(object)); - int oldVal = val; - if (SHEditorUI::InputEnumCombo(Convert::ToNative(field->Name), val, nativeEnumNames, &isHovered)) - { - field->SetValue(object, val); - 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) + if (!MODIFIED_PRIMITIVE) { - // Prevent issues where String^ is null due to being empty - String^ stringVal = safe_cast(field->GetValue(object)); - if (stringVal == nullptr) + if (field->FieldType->IsSubclassOf(Enum::typeid)) { - stringVal = ""; - } - - // Actual Field - std::string val = Convert::ToNative(stringVal); - std::string oldVal = val; - if (SHEditorUI::InputTextField(Convert::ToNative(field->Name), val, &isHovered)) - { - field->SetValue(object, Convert::ToCLI(val)); - registerUndoAction(object, field, Convert::ToCLI(val), Convert::ToCLI(oldVal)); - } - } - else if (field->FieldType == GameObject::typeid) - { - GameObject gameObj = safe_cast(field->GetValue(object)); - uint32_t entityId = gameObj.GetEntity(); - if (SHEditorUI::InputGameObjectField(Convert::ToNative(field->Name), entityId, &isHovered, !gameObj)) - { - GameObject newVal = GameObject(entityId); - if (entityId != MAX_EID) + // Get all the names of the enums + const array^ ENUM_NAMES = field->FieldType->GetEnumNames(); + std::vector nativeEnumNames; + for each (String ^ str in ENUM_NAMES) { - // Null GameObject set - newVal = GameObject(entityId); + nativeEnumNames.emplace_back(Convert::ToNative(str)); + } + + int val = safe_cast(field->GetValue(object)); + int oldVal = val; + if (SHEditorUI::InputEnumCombo(Convert::ToNative(field->Name), val, nativeEnumNames, &isHovered)) + { + field->SetValue(object, val); + registerUndoAction(object, field, val, oldVal); } - field->SetValue(object, newVal); - registerUndoAction(object, field, newVal, gameObj); } - } - // Any List - else if (field->FieldType->IsGenericType && field->FieldType->GetGenericTypeDefinition() == System::Collections::Generic::List::typeid->GetGenericTypeDefinition()) - { - System::Type^ listType = field->FieldType->GenericTypeArguments[0]; - System::Collections::IEnumerable^ listEnummerable = safe_cast(field->GetValue(object)); - - - SHEditorUI::Text(Convert::ToNative(field->Name)); - SHEditorUI::SameLine(); - SHEditorUI::Button("+"); - - SHEditorUI::Indent(); - int i = 0; - for each (System::Object^ obj in listEnummerable) + else if (field->FieldType == String::typeid) { - int val = safe_cast(obj); - SHEditorUI::InputInt(std::to_string(i), val, &isHovered); + // Prevent issues where String^ is null due to being empty + String^ stringVal = safe_cast(field->GetValue(object)); + if (stringVal == nullptr) + { + stringVal = ""; + } + + // Actual Field + std::string val = Convert::ToNative(stringVal); + std::string oldVal = val; + if (SHEditorUI::InputTextField(Convert::ToNative(field->Name), val, &isHovered)) + { + field->SetValue(object, Convert::ToCLI(val)); + registerUndoAction(object, field, Convert::ToCLI(val), Convert::ToCLI(oldVal)); + } + } + else if (field->FieldType == GameObject::typeid) + { + GameObject gameObj = safe_cast(field->GetValue(object)); + uint32_t entityId = gameObj.GetEntity(); + if (SHEditorUI::InputGameObjectField(Convert::ToNative(field->Name), entityId, &isHovered, !gameObj)) + { + GameObject newVal = GameObject(entityId); + if (entityId != MAX_EID) + { + // Null GameObject set + newVal = GameObject(entityId); + } + field->SetValue(object, newVal); + registerUndoAction(object, field, newVal, gameObj); + } + } + // Any List + else if (field->FieldType->IsGenericType && field->FieldType->GetGenericTypeDefinition() == System::Collections::Generic::List::typeid->GetGenericTypeDefinition()) + { + System::Type^ listType = field->FieldType->GenericTypeArguments[0]; + System::Collections::IEnumerable^ listEnummerable = safe_cast(field->GetValue(object)); + + + SHEditorUI::Text(Convert::ToNative(field->Name)); SHEditorUI::SameLine(); - SHEditorUI::Button("-"); - ++i; - } - SHEditorUI::Unindent(); - } - else - { - array^ interfaces = field->FieldType->GetInterfaces(); - if (interfaces->Length > 0 && interfaces[0] == ICallbackEvent::typeid) - { - array^ typeArgs = field->FieldType->GenericTypeArguments; - System::String^ title = field->Name + " : CallbackEvent<"; - for (int i = 0; i < typeArgs->Length; ++i) + SHEditorUI::Button("+"); + + SHEditorUI::Indent(); + int i = 0; + for each (System::Object ^ obj in listEnummerable) { - title += typeArgs[i]->Name; - if (i < typeArgs->Length - 1) - title += ", "; - } - title += ">"; - if (SHEditorUI::CollapsingHeader(Convert::ToNative(title))) - { - // Constants - const std::string LABEL = Convert::ToNative(field->Name); - SHEditorUI::PushID(LABEL); - - ICallbackEvent^ callbackEvent = safe_cast(field->GetValue(object)); - if (callbackEvent == nullptr) - { - // Construct one since it was not constructed before - callbackEvent = safe_cast(System::Activator::CreateInstance(field->FieldType)); - } - for each (ICallbackAction ^ action in callbackEvent->Actions) - { - if (action->IsRuntimeAction) - continue; - - // Attempt to get the object if any - int entityId = static_cast(-1); - if (action->TargetObject) - { - Script^ script = safe_cast(action->TargetObject); - if (script) - { - entityId = static_cast(script->Owner.GetEntity()); - } - } - SHEditorUI::InputInt("", entityId); - SHEditorUI::SameLine(); - System::String^ methodName = ""; - if (action->TargetMethodName != nullptr) - { - methodName = action->TargetMethodName; - } - std::string methodNameNative = Convert::ToNative(methodName); - SHEditorUI::InputTextField("", methodNameNative); - SHEditorUI::SameLine(); - if (SHEditorUI::Button("-")) - { - callbackEvent->DeregisterAction(action); - break; - } - } - if (SHEditorUI::Button("Add Action")) - { - callbackEvent->RegisterAction(); - } - - SHEditorUI::PopID(); + int val = safe_cast(obj); + SHEditorUI::InputInt(std::to_string(i), val, &isHovered); + SHEditorUI::SameLine(); + SHEditorUI::Button("-"); + ++i; } + SHEditorUI::Unindent(); } else { - return; + array^ interfaces = field->FieldType->GetInterfaces(); + if (interfaces->Length > 0 && interfaces[0] == ICallbackEvent::typeid) + { + array^ typeArgs = field->FieldType->GenericTypeArguments; + System::String^ title = field->Name + " : CallbackEvent<"; + for (int i = 0; i < typeArgs->Length; ++i) + { + title += typeArgs[i]->Name; + if (i < typeArgs->Length - 1) + title += ", "; + } + title += ">"; + if (SHEditorUI::CollapsingHeader(Convert::ToNative(title))) + { + // Constants + const std::string LABEL = Convert::ToNative(field->Name); + SHEditorUI::PushID(LABEL); + + ICallbackEvent^ callbackEvent = safe_cast(field->GetValue(object)); + if (callbackEvent == nullptr) + { + // Construct one since it was not constructed before + callbackEvent = safe_cast(System::Activator::CreateInstance(field->FieldType)); + } + for each (ICallbackAction ^ action in callbackEvent->Actions) + { + if (action->IsRuntimeAction) + continue; + + // Attempt to get the object if any + int entityId = static_cast(-1); + if (action->TargetObject) + { + Script^ script = safe_cast(action->TargetObject); + if (script) + { + entityId = static_cast(script->Owner.GetEntity()); + } + } + SHEditorUI::InputInt("", entityId); + SHEditorUI::SameLine(); + System::String^ methodName = ""; + if (action->TargetMethodName != nullptr) + { + methodName = action->TargetMethodName; + } + std::string methodNameNative = Convert::ToNative(methodName); + SHEditorUI::InputTextField("", methodNameNative); + SHEditorUI::SameLine(); + if (SHEditorUI::Button("-")) + { + callbackEvent->DeregisterAction(action); + break; + } + } + if (SHEditorUI::Button("Add Action")) + { + callbackEvent->RegisterAction(); + } + + SHEditorUI::PopID(); + } + } + else + { + return; + } } } diff --git a/SHADE_Managed/src/Editor/Editor.h++ b/SHADE_Managed/src/Editor/Editor.h++ new file mode 100644 index 00000000..2cda78e7 --- /dev/null +++ b/SHADE_Managed/src/Editor/Editor.h++ @@ -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 + bool Editor::renderFieldInInspector(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, EditorFieldFunc fieldEditor, bool* isHovered) + { + if (fieldInfo->FieldType == ManagedType::typeid) + { + RangeAttribute^ rangeAttrib; + if constexpr (std::is_arithmetic_v && !std::is_same_v) + { + rangeAttrib = hasAttribute(fieldInfo); + } + + ManagedType val = safe_cast(fieldInfo->GetValue(object)); + if (renderFieldInInspector + ( + Convert::ToNative(fieldInfo->Name), + val, + fieldEditor, + isHovered, + rangeAttrib + )) + { + fieldInfo->SetValue(object, val); + // TODO: Register undo + } + + return true; + } + + return false; + } + + template + bool Editor::renderFieldInInspector(const std::string& fieldName, ManagedType% managedVal, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib) + { + // Retrieve the native version of the object + NativeType val; + if constexpr (IsPrimitiveTypeMatches_V) + { + val = safe_cast(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 && !std::is_same_v) + { + changed = SHEditorUI::InputSlider + ( + fieldName, + static_cast(rangeAttrib->Min), + static_cast(rangeAttrib->Max), + val, isHovered + ); + } + } + else + { + changed = fieldEditor(fieldName, val, isHovered); + } + + if (changed) + { + if constexpr (IsPrimitiveTypeMatches_V) + { + //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; + } +} diff --git a/SHADE_Managed/src/Editor/Editor.hxx b/SHADE_Managed/src/Editor/Editor.hxx index 109842b5..c7e86622 100644 --- a/SHADE_Managed/src/Editor/Editor.hxx +++ b/SHADE_Managed/src/Editor/Editor.hxx @@ -17,9 +17,14 @@ of DigiPen Institute of Technology is prohibited. #include "Engine/Entity.hxx" #include "Scripts/Script.hxx" #include "UndoRedoStack.hxx" +#include "RangeAttribute.hxx" namespace SHADE { + + template + using EditorFieldFunc = bool(*)(const std::string& label, NativeType& val, bool* isHovered); + /// /// Static class for Editor-related functions /// @@ -91,8 +96,59 @@ namespace SHADE /// The Entity to render the Scripts of. /// The Script to render the inspector for. static void renderScriptContextMenu(Entity entity, Script^ script); + /// + /// Adds changes to a variable as an undo-able/redo-able action on the Undo-Redo + /// stack. + /// + /// The object that changes are applied to. + /// The field that was changed. + /// New data to set. + /// Data that was overriden. static void registerUndoAction(System::Object^ object, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData); + /// + /// Checks if a specific field has the specified attribute + /// + /// Type of Attribute to check for. + /// The field to check. + /// The attribute to check for if it exists. Null otherwise. generic where Attribute : System::Attribute static Attribute hasAttribute(System::Reflection::FieldInfo^ field); + /// + /// 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. + /// + /// Native type of the field. + /// Managed type of the field. + /// Describes the field to modify. + /// Object to modify that has the specified field. + /// ImGui field editor function to use. + /// + /// Pointer to a bool that stores if the field editor was hovered over. + /// + /// True if the field is modified. + template + static bool renderFieldInInspector(System::Reflection::FieldInfo^ fieldInfo, System::Object^ object, EditorFieldFunc fieldEditor, bool* isHovered); + /// + /// Renders a ImGui field editor based on the type of parameters specified. + /// + /// Native type of the field. + /// Managed type of the field. + /// Label to use for the field editor. + /// + /// Tracking reference for the managed variable to modify. + /// + /// ImGui field editor function to use. + /// + /// Pointer to a bool that stores if the field editor was hovered over. + /// + /// + /// If provided and the type supports it, the field will be rendered with a + /// slider instead. + /// + /// True if the field is modified. + template + static bool renderFieldInInspector(const std::string& fieldName, ManagedType% managedVal, EditorFieldFunc fieldEditor, bool* isHovered, RangeAttribute^ rangeAttrib); }; } +#include "Editor.h++" diff --git a/SHADE_Managed/src/Utility/Convert.hxx b/SHADE_Managed/src/Utility/Convert.hxx index 666b5062..4d0c5b59 100644 --- a/SHADE_Managed/src/Utility/Convert.hxx +++ b/SHADE_Managed/src/Utility/Convert.hxx @@ -152,6 +152,40 @@ namespace SHADE }; + /// + /// Checks if the specified type is matching between native C++ and the managed type. + /// + /// Type to check. + template + struct IsPrimitiveTypeMatches : public std::integral_constant + < + bool, + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> || + std::is_same_v> + > + {}; + /// + /// Short hand for IsPrimitiveTypeMatches::value + /// + /// Type to check. + template + inline constexpr bool IsPrimitiveTypeMatches_V = IsPrimitiveTypeMatches::value; + /// /// Type Transformer for managed types to native types. /// @@ -163,6 +197,7 @@ namespace SHADE { public: using Value = void; + static bool IsDefined() { return is_same_v; } }; template<> struct ToNativeType { using Value = int16_t; }; template<> struct ToNativeType { using Value = int32_t; }; @@ -193,19 +228,20 @@ namespace SHADE template struct ToManagedType { - public: + public: using Value = void; + static bool IsDefined() { return is_same_v; } }; - template<> struct ToManagedType { using Value = System::Byte; }; - template<> struct ToManagedType { using Value = System::Int16; }; - template<> struct ToManagedType { using Value = System::Int32; }; - template<> struct ToManagedType { using Value = System::Int64; }; + template<> struct ToManagedType { using Value = System::Byte; }; + template<> struct ToManagedType { using Value = System::Int16; }; + template<> struct ToManagedType { using Value = System::Int32; }; + template<> struct ToManagedType { using Value = System::Int64; }; template<> struct ToManagedType { using Value = System::UInt16; }; template<> struct ToManagedType { using Value = System::UInt32; }; template<> struct ToManagedType { using Value = System::UInt64; }; - template<> struct ToManagedType { using Value = bool; }; - template<> struct ToManagedType { using Value = double; }; - template<> struct ToManagedType { using Value = float; }; + template<> struct ToManagedType { using Value = bool; }; + template<> struct ToManagedType { using Value = double; }; + template<> struct ToManagedType { using Value = float; }; /// /// Alias for ToManagedType::Value