From 87cf3ffa6176b9cacb97259fa52b9fd0ce361202 Mon Sep 17 00:00:00 2001 From: Kah Wei Date: Tue, 18 Oct 2022 20:09:50 +0800 Subject: [PATCH] Added script inspector tooltips support via Tooltip attribute --- SHADE_Engine/src/Editor/SHEditorUI.cpp | 59 +++++++++++++++---- SHADE_Engine/src/Editor/SHEditorUI.h | 45 ++++++++++---- SHADE_Engine/src/Editor/SHEditorUI.hpp | 7 ++- SHADE_Engine/src/Editor/SHEditorWidgets.hpp | 4 +- SHADE_Managed/src/Editor/Editor.cxx | 56 ++++++++++++++---- SHADE_Managed/src/Editor/Editor.hxx | 10 +++- .../TooltipAttribute.cxx} | 29 +++++---- SHADE_Managed/src/Editor/TooltipAttribute.hxx | 53 +++++++++++++++++ .../Serialisation/SerializeFieldAttribute.hxx | 11 +--- TempScriptsFolder/RaccoonShowcase.cs | 8 ++- TempScriptsFolder/RaccoonSpin.cs | 4 +- 11 files changed, 221 insertions(+), 65 deletions(-) rename SHADE_Managed/src/{Serialisation/SerialiseFieldAttribute.cxx => Editor/TooltipAttribute.cxx} (54%) create mode 100644 SHADE_Managed/src/Editor/TooltipAttribute.hxx diff --git a/SHADE_Engine/src/Editor/SHEditorUI.cpp b/SHADE_Engine/src/Editor/SHEditorUI.cpp index 65b65827..76f7bac6 100644 --- a/SHADE_Engine/src/Editor/SHEditorUI.cpp +++ b/SHADE_Engine/src/Editor/SHEditorUI.cpp @@ -67,6 +67,11 @@ namespace SHADE ImGui::Separator(); } + bool SHEditorUI::IsItemHovered() + { + return ImGui::IsItemHovered(); + } + bool SHEditorUI::BeginMenu(const std::string& label) { return ImGui::BeginMenu(label.data()); @@ -82,6 +87,16 @@ namespace SHADE ImGui::EndMenu(); } + void SHEditorUI::BeginTooltip() + { + ImGui::BeginTooltip(); + } + + void SHEditorUI::EndTooltip() + { + ImGui::EndTooltip(); + } + /*-----------------------------------------------------------------------------------*/ /* ImGui Wrapper Functions - Pop Ups */ /*-----------------------------------------------------------------------------------*/ @@ -135,24 +150,30 @@ namespace SHADE return ImGui::Selectable(std::format("{} {}", icon, label).data()); } - bool SHEditorUI::InputCheckbox(const std::string& label, bool& value) + bool SHEditorUI::InputCheckbox(const std::string& label, bool& value, bool* isHovered) { ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); ImGui::SameLine(); return ImGui::Checkbox("#", &value); } - bool SHEditorUI::InputInt(const std::string& label, int& value) + bool SHEditorUI::InputInt(const std::string& label, int& value, bool* isHovered) { ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); ImGui::SameLine(); return ImGui::InputInt("#", &value, 1, 10, ImGuiInputTextFlags_EnterReturnsTrue); } - bool SHEditorUI::InputUnsignedInt(const std::string& label, unsigned int& value) + bool SHEditorUI::InputUnsignedInt(const std::string& label, unsigned int& value, bool* isHovered) { int signedVal = static_cast(value); ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); ImGui::SameLine(); const bool CHANGED = InputInt("#", signedVal); if (CHANGED) @@ -162,35 +183,43 @@ namespace SHADE } return CHANGED; } - bool SHEditorUI::InputFloat(const std::string& label, float& value) + bool SHEditorUI::InputFloat(const std::string& label, float& value, bool* isHovered) { ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); ImGui::SameLine(); return ImGui::InputFloat("#", &value, 0.1f, 1.0f, "%.3f", ImGuiInputTextFlags_EnterReturnsTrue); } - bool SHEditorUI::InputDouble(const std::string& label, double& value) + bool SHEditorUI::InputDouble(const std::string& label, double& value, bool* isHovered) { ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); ImGui::SameLine(); return ImGui::InputDouble("#", &value, 0.1, 1.0, "%.3f", ImGuiInputTextFlags_EnterReturnsTrue); } - bool SHEditorUI::InputAngle(const std::string& label, double& value) + bool SHEditorUI::InputAngle(const std::string& label, double& value, bool* isHovered) { ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); ImGui::SameLine(); return ImGui::InputDouble("#", &value, 1.0, 45.0, "%.3f", ImGuiInputTextFlags_EnterReturnsTrue); } - bool SHEditorUI::InputSlider(const std::string& label, double min, double max, double& value) + bool SHEditorUI::InputSlider(const std::string& label, double min, double max, double& value, bool* isHovered) { float val = static_cast(value); ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); ImGui::SameLine(); const bool CHANGED = ImGui::SliderFloat("#", &val, static_cast(min), static_cast(max), "%.3f", @@ -204,22 +233,24 @@ namespace SHADE return CHANGED; } - bool SHEditorUI::InputVec2(const std::string& label, SHVec2& value) + bool SHEditorUI::InputVec2(const std::string& label, SHVec2& value, bool* isHovered) { static const std::vector COMPONENT_LABELS = { "X", "Y" }; - return SHEditorWidgets::DragN(label, COMPONENT_LABELS, { &value.x, &value.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, float speed) + bool SHEditorUI::InputVec3(const std::string& label, SHVec3& value, bool* isHovered, float speed) { static const std::vector COMPONENT_LABELS = { "X", "Y", "Z"}; - return SHEditorWidgets::DragN(label, COMPONENT_LABELS, { &value.x, &value.y, &value.z }, speed, "%.3f"); + return SHEditorWidgets::DragN(label, COMPONENT_LABELS, { &value.x, &value.y, &value.z }, speed, "%.3f", float{}, float{}, 0, isHovered); } - bool SHEditorUI::InputTextField(const std::string& label, std::string& value) + bool SHEditorUI::InputTextField(const std::string& label, std::string& value, bool* isHovered) { std::array buffer = { '\0' }; strcpy_s(buffer.data(), TEXT_FIELD_MAX_LENGTH, value.c_str()); ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); ImGui::SameLine(); const bool CHANGED = ImGui::InputText("#", &buffer[0], TEXT_FIELD_MAX_LENGTH); if (CHANGED) @@ -229,13 +260,15 @@ namespace SHADE return CHANGED; } - bool SHEditorUI::InputEnumCombo(const std::string& label, int& v, const std::vector& enumNames) + bool SHEditorUI::InputEnumCombo(const std::string& label, int& v, const std::vector& enumNames, bool* isHovered) { // Clamp input value const std::string& INITIAL_NAME = v >= static_cast(enumNames.size()) ? "Unknown" : enumNames[v]; bool b = false; ImGui::Text(label.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); ImGui::SameLine(); if (ImGui::BeginCombo("#", INITIAL_NAME.c_str(), ImGuiComboFlags_None)) { diff --git a/SHADE_Engine/src/Editor/SHEditorUI.h b/SHADE_Engine/src/Editor/SHEditorUI.h index 13468215..b8451765 100644 --- a/SHADE_Engine/src/Editor/SHEditorUI.h +++ b/SHADE_Engine/src/Editor/SHEditorUI.h @@ -90,12 +90,19 @@ namespace SHADE static void SameLine(); static void Separator(); - /*-----------------------------------------------------------------------------*/ + /*-----------------------------------------------------------------------------*/ + /* ImGui Wrapper Functions - Queries */ + /*-----------------------------------------------------------------------------*/ + static bool IsItemHovered(); + + /*-----------------------------------------------------------------------------*/ /* ImGui Wrapper Functions - Menu */ /*-----------------------------------------------------------------------------*/ static bool BeginMenu(const std::string& label); static bool BeginMenu(const std::string& label, const char* icon); static void EndMenu(); + static void BeginTooltip(); + static void EndTooltip(); /*-----------------------------------------------------------------------------*/ /* ImGui Wrapper Functions - Pop Ups */ @@ -165,8 +172,9 @@ namespace SHADE /// /// Label used to identify this widget. /// Reference to the variable to store the result. + /// Label used to identify this widget. /// Reference to the variable to store the result. + /// Label used to identify this widget. /// Reference to the variable to store the result. + /// Label used to identify this widget. /// Reference to the variable to store the result. + /// Label used to identify this widget. /// Reference to the variable to store the result. + /// Label used to identify this widget. /// Reference to the variable to store the result. + /// Minimum value of the slider. /// Maximum value of the slider. /// Reference to the variable to store the result. + /// Label used to identify this widget. /// Reference to the variable to store the result. + /// Label used to identify this widget. /// Reference to the variable to store the result. + /// Label used to identify this widget. /// Reference to the variable to store the result. + /// /// Conversion function from the type of enum to C-style string. /// + /// The name of the input. /// The reference to the value to modify. /// Vector of names for each enumeration value. + /// SkipItems) @@ -174,6 +174,8 @@ namespace SHADE ImGui::BeginColumns("DragVecCol", 2, ImGuiOldColumnFlags_NoBorder | ImGuiOldColumnFlags_NoResize); ImGui::SetColumnWidth(-1, 80.0f); ImGui::Text(fieldLabel.c_str()); + if (isHovered) + *isHovered = ImGui::IsItemHovered(); ImGui::NextColumn(); for (std::size_t i = 0; i < N; ++i) { diff --git a/SHADE_Managed/src/Editor/Editor.cxx b/SHADE_Managed/src/Editor/Editor.cxx index 735f8c2c..8d105fce 100644 --- a/SHADE_Managed/src/Editor/Editor.cxx +++ b/SHADE_Managed/src/Editor/Editor.cxx @@ -29,6 +29,7 @@ of DigiPen Institute of Technology is prohibited. #include "Editor/IconsMaterialDesign.h" #include "Editor/Command/SHCommandManager.h" #include "Editor/Command/SHCommand.hpp" +#include "TooltipAttribute.hxx" // Using Directives using namespace System; @@ -48,17 +49,17 @@ using namespace System::Collections::Generic; /// 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)) \ - { \ - field->SetValue(object, val); \ - registerUndoAction(object, field, val, oldVal); \ - } \ -} \ +#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); \ + } \ +} \ /// /// 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 @@ -76,7 +77,7 @@ using namespace System::Collections::Generic; { \ NATIVE_TYPE val = Convert::ToNative(safe_cast(field->GetValue(object))); \ NATIVE_TYPE oldVal = val; \ - if (SHEditorUI::FUNC(Convert::ToNative(field->Name), 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)); \ @@ -196,6 +197,8 @@ namespace SHADE } void Editor::renderFieldInInspector(Reflection::FieldInfo^ field, Object^ object) { + bool isHovered = false; + if RENDER_FIELD (Int16, int, InputInt) else if RENDER_FIELD (Int32, int, InputInt) else if RENDER_FIELD (Int64, int, InputInt) @@ -244,6 +247,15 @@ namespace SHADE registerUndoAction(object, field, Convert::ToCLI(val), Convert::ToCLI(oldVal)); } } + + // Check if the field has a specific attribute + TooltipAttribute^ toolTip = hasAttribute(field); + if (toolTip && isHovered) + { + SHEditorUI::BeginTooltip(); + SHEditorUI::Text(Convert::ToNative(toolTip->Description)); + SHEditorUI::EndTooltip(); + } } void Editor::renderScriptContextMenu(Entity entity, Script^ script) @@ -274,4 +286,24 @@ namespace SHADE SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast(std::make_shared())); } + generic + Attribute Editor::hasAttribute(System::Reflection::FieldInfo^ field) + { + array^ attributes = field->GetCustomAttributes(true); + for each (System::Object^ attrib in attributes) + { + try + { + Attribute attribute = safe_cast(attrib); + if (attribute != nullptr) + return attribute; + } + catch (System::InvalidCastException^) + { + continue; + } + } + // Failed to find + return Attribute{}; + } } diff --git a/SHADE_Managed/src/Editor/Editor.hxx b/SHADE_Managed/src/Editor/Editor.hxx index c4800645..6b59589a 100644 --- a/SHADE_Managed/src/Editor/Editor.hxx +++ b/SHADE_Managed/src/Editor/Editor.hxx @@ -23,7 +23,7 @@ namespace SHADE /// /// Static class for Editor-related functions /// - public ref class Editor abstract sealed + private ref class Editor abstract sealed { public: /*-----------------------------------------------------------------------------*/ @@ -48,7 +48,13 @@ namespace SHADE /*-----------------------------------------------------------------------------*/ /* UndoRedoStack Functions */ /*-----------------------------------------------------------------------------*/ + /// + /// Undoes the last script inspector change if there is any. + /// static void Undo(); + /// + /// Redoes the last script inspector change if there is any. + /// static void Redo(); private: @@ -86,5 +92,7 @@ namespace SHADE /// The Script to render the inspector for. static void renderScriptContextMenu(Entity entity, Script^ script); static void registerUndoAction(System::Object^ object, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData); + generic where Attribute : System::Attribute + static Attribute hasAttribute(System::Reflection::FieldInfo^ field); }; } diff --git a/SHADE_Managed/src/Serialisation/SerialiseFieldAttribute.cxx b/SHADE_Managed/src/Editor/TooltipAttribute.cxx similarity index 54% rename from SHADE_Managed/src/Serialisation/SerialiseFieldAttribute.cxx rename to SHADE_Managed/src/Editor/TooltipAttribute.cxx index c371d200..8d5a5d55 100644 --- a/SHADE_Managed/src/Serialisation/SerialiseFieldAttribute.cxx +++ b/SHADE_Managed/src/Editor/TooltipAttribute.cxx @@ -1,27 +1,34 @@ /************************************************************************************//*! -\file SerializeFieldAttribute.cxx +\file TooltipAttribute.cxx \author Tng Kah Wei, kahwei.tng, 390009620 \par email: kahwei.tng\@digipen.edu -\date Nov 5, 2021 -\brief Contains the definition of the functions of the managed SerializeField - Attribute class. +\date Oct 18, 2022 +\brief Contains the definition of the functions of the managed Tooltip Attribute + class. Note: This file is written in C++17/CLI. - -Copyright (C) 2021 DigiPen Institute of Technology. -Reproduction or disclosure of this file or its contents without the prior written consent + +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. *//*************************************************************************************/ -// Precompiled Headers #include "SHpch.h" -// Primary Header -#include "SerializeFieldAttribute.hxx" +#include "TooltipAttribute.hxx" namespace SHADE { + /*---------------------------------------------------------------------------------*/ + /* Properties */ + /*---------------------------------------------------------------------------------*/ + System::String^ TooltipAttribute::Description::get() + { + return desc; + } + /*---------------------------------------------------------------------------------*/ /* Constructors */ /*---------------------------------------------------------------------------------*/ - SerializeField::SerializeField() + TooltipAttribute::TooltipAttribute(System::String^ description) + : desc { description } {} } diff --git a/SHADE_Managed/src/Editor/TooltipAttribute.hxx b/SHADE_Managed/src/Editor/TooltipAttribute.hxx new file mode 100644 index 00000000..e7cd168c --- /dev/null +++ b/SHADE_Managed/src/Editor/TooltipAttribute.hxx @@ -0,0 +1,53 @@ +/************************************************************************************//*! +\file TooltipAttribute.hxx +\author Tng Kah Wei, kahwei.tng, 390009620 +\par email: kahwei.tng\@digipen.edu +\date Oct 18, 2022 +\brief Contains the definition of the managed Tooltip Attribute class with + the declaration of functions for working with it. + + 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 + +namespace SHADE +{ + /// + /// Simple attribute to mark that a field in a Script should be serialised. + /// + [System::AttributeUsage(System::AttributeTargets::Field)] + public ref class TooltipAttribute : public System::Attribute + { + public: + /*-----------------------------------------------------------------------------*/ + /* Properties */ + /*-----------------------------------------------------------------------------*/ + /// + /// Description that is to be shown in the Tooltip. + /// + property System::String^ Description + { + System::String^ get(); + } + + /*-----------------------------------------------------------------------------*/ + /* Constructors */ + /*-----------------------------------------------------------------------------*/ + /// + /// Constructor for a Tooltip attribute that fills in the description. + /// + /// Text to be shown when a field is hovered. + TooltipAttribute(System::String^ description); + + private: + /*-----------------------------------------------------------------------------*/ + /* Data Members */ + /*-----------------------------------------------------------------------------*/ + System::String^ desc; + }; +} + diff --git a/SHADE_Managed/src/Serialisation/SerializeFieldAttribute.hxx b/SHADE_Managed/src/Serialisation/SerializeFieldAttribute.hxx index 533ded2a..7a7bb83c 100644 --- a/SHADE_Managed/src/Serialisation/SerializeFieldAttribute.hxx +++ b/SHADE_Managed/src/Serialisation/SerializeFieldAttribute.hxx @@ -21,15 +21,6 @@ namespace SHADE /// [System::AttributeUsage(System::AttributeTargets::Field)] public ref class SerializeField : public System::Attribute - { - public: - /*-----------------------------------------------------------------------------*/ - /* Constructors */ - /*-----------------------------------------------------------------------------*/ - /// - /// Default Constructor - /// - SerializeField(); - }; + {}; } diff --git a/TempScriptsFolder/RaccoonShowcase.cs b/TempScriptsFolder/RaccoonShowcase.cs index e2d6454d..93ea53eb 100644 --- a/TempScriptsFolder/RaccoonShowcase.cs +++ b/TempScriptsFolder/RaccoonShowcase.cs @@ -3,8 +3,12 @@ using System; public class RaccoonShowcase : Script { - public double RotateSpeed = 1.0; - public Vector3 ScaleSpeed = new Vector3(1.0, 1.0, 0.0); + [SerializeField] + [Tooltip("Speed of the rotation in radians per second.")] + private double RotateSpeed = 1.0; + [SerializeField] + [Tooltip("Speed of the scaling in radians per second around each axis.")] + private Vector3 ScaleSpeed = new Vector3(1.0, 1.0, 0.0); private Transform Transform; private double rotation = 0.0; private Vector3 scale = Vector3.Zero; diff --git a/TempScriptsFolder/RaccoonSpin.cs b/TempScriptsFolder/RaccoonSpin.cs index 7785cfd5..d6ee1c9f 100644 --- a/TempScriptsFolder/RaccoonSpin.cs +++ b/TempScriptsFolder/RaccoonSpin.cs @@ -3,7 +3,9 @@ using System; public class RaccoonSpin : Script { - public double RotateSpeed = 1.0; + [SerializeField] + [Tooltip("Speed of the rotation in radians per second.")] + private double RotateSpeed = 1.0; private double rotation = 0.0; private Transform Transform; public RaccoonSpin(GameObject gameObj) : base(gameObj) { }