Added undo and redo support for script changes in the inspector

This commit is contained in:
Kah Wei 2022-10-01 22:56:15 +08:00
parent 1477aaaead
commit f717b61c88
10 changed files with 204 additions and 44 deletions

View File

@ -5,9 +5,13 @@
//#==============================================================# //#==============================================================#
#include <functional> #include <functional>
#include "SH_API.h"
#include "Scripting/SHScriptEngine.h"
#include "ECS_Base/Managers/SHSystemManager.h"
namespace SHADE namespace SHADE
{ {
class SHBaseCommand class SH_API SHBaseCommand
{ {
public: public:
virtual ~SHBaseCommand() = default; virtual ~SHBaseCommand() = default;
@ -48,4 +52,20 @@ namespace SHADE
T newValue; T newValue;
SetterFunction set; SetterFunction set;
}; };
class SH_API SHCLICommand : SHBaseCommand
{
public:
SHCLICommand() = default;
void Execute() override
{
SHScriptEngine* scriptEngine = static_cast<SHScriptEngine*>(SHSystemManager::GetSystem<SHScriptEngine>());
scriptEngine->RedoScriptInspectorChanges();
}
void Undo() override
{
SHScriptEngine* scriptEngine = static_cast<SHScriptEngine*>(SHSystemManager::GetSystem<SHScriptEngine>());
scriptEngine->UndoScriptInspectorChanges();
}
};
}//namespace SHADE }//namespace SHADE

View File

@ -27,6 +27,11 @@ namespace SHADE
} }
} }
void SHCommandManager::RegisterCommand(CommandPtr commandPtr)
{
undoStack.push(commandPtr);
}
void SHCommandManager::UndoCommand() void SHCommandManager::UndoCommand()
{ {
if (undoStack.empty()) if (undoStack.empty())

View File

@ -9,10 +9,11 @@
//|| SHADE Includes || //|| SHADE Includes ||
//#==============================================================# //#==============================================================#
#include "SHCommand.hpp" #include "SHCommand.hpp"
#include "SH_API.h"
namespace SHADE namespace SHADE
{ {
class SHCommandManager class SH_API SHCommandManager
{ {
public: public:
//#==============================================================# //#==============================================================#
@ -22,6 +23,7 @@ namespace SHADE
using CommandStack = std::stack<CommandPtr>; using CommandStack = std::stack<CommandPtr>;
static void PerformCommand(CommandPtr commandPtr, bool const& overrideValue = false); static void PerformCommand(CommandPtr commandPtr, bool const& overrideValue = false);
static void RegisterCommand(CommandPtr commandPtr);
static void UndoCommand(); static void UndoCommand();
static void RedoCommand(); static void RedoCommand();
static std::size_t GetUndoStackSize(); static std::size_t GetUndoStackSize();

View File

@ -153,6 +153,16 @@ namespace SHADE
csEditorRenderScripts(entity); csEditorRenderScripts(entity);
} }
void SHScriptEngine::UndoScriptInspectorChanges() const
{
csEditorUndo();
}
void SHScriptEngine::RedoScriptInspectorChanges() const
{
csEditorRedo();
}
/*-----------------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------------*/
/* Static Utility Functions */ /* Static Utility Functions */
/*-----------------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------------*/
@ -400,6 +410,18 @@ namespace SHADE
DEFAULT_CSHARP_NAMESPACE + ".Editor", DEFAULT_CSHARP_NAMESPACE + ".Editor",
"RenderScriptsInInspector" "RenderScriptsInInspector"
); );
csEditorUndo = dotNet.GetFunctionPtr<CsFuncPtr>
(
DEFAULT_CSHARP_LIB_NAME,
DEFAULT_CSHARP_NAMESPACE + ".Editor",
"Undo"
);
csEditorRedo = dotNet.GetFunctionPtr<CsFuncPtr>
(
DEFAULT_CSHARP_LIB_NAME,
DEFAULT_CSHARP_NAMESPACE + ".Editor",
"Redo"
);
} }
void SHScriptEngine::registerEvents() void SHScriptEngine::registerEvents()

View File

@ -171,6 +171,14 @@ namespace SHADE
/// </summary> /// </summary>
/// <param name="entity">The Entity to render the Scripts of.</param> /// <param name="entity">The Entity to render the Scripts of.</param>
void RenderScriptsInInspector(EntityID entity) const; void RenderScriptsInInspector(EntityID entity) const;
/// <summary>
/// Performs an undo for script inspector changes if it exists.
/// </summary>
void UndoScriptInspectorChanges() const;
/// <summary>
/// Performs a redo for script inspector changes if it exists.
/// </summary>
void RedoScriptInspectorChanges() const;
/*-----------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------*/
/* Static Utility Functions */ /* Static Utility Functions */
@ -243,9 +251,8 @@ namespace SHADE
CsScriptSerialiseYamlFuncPtr csScriptDeserialiseYaml = nullptr; CsScriptSerialiseYamlFuncPtr csScriptDeserialiseYaml = nullptr;
// - Editor // - Editor
CsScriptEditorFuncPtr csEditorRenderScripts = nullptr; CsScriptEditorFuncPtr csEditorRenderScripts = nullptr;
// Delegates CsFuncPtr csEditorUndo = nullptr;
/*ECS::EntityEvent::Delegate onEntityCreate; CsFuncPtr csEditorRedo = nullptr;
ECS::EntityEvent::Delegate onEntityDestroy;*/
/*-----------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------*/
/* Event Handler Functions */ /* Event Handler Functions */

View File

@ -30,7 +30,8 @@ project "SHADE_Managed"
"%{IncludeDir.imguizmo}", "%{IncludeDir.imguizmo}",
"%{IncludeDir.imnodes}", "%{IncludeDir.imnodes}",
"%{IncludeDir.yamlcpp}", "%{IncludeDir.yamlcpp}",
"%{IncludeDir.RTTR}/include", "%{IncludeDir.RTTR}/include",
"%{IncludeDir.dotnet}\\include",
"%{wks.location}/SHADE_Engine/src" "%{wks.location}/SHADE_Engine/src"
} }

View File

@ -16,6 +16,8 @@ of DigiPen Institute of Technology is prohibited.
#include "SHpch.h" #include "SHpch.h"
// Primary Header // Primary Header
#include "Editor/Editor.hxx" #include "Editor/Editor.hxx"
// STL Includes
#include <memory>
// External Dependencies // External Dependencies
#include "Editor/SHEditorUI.h" #include "Editor/SHEditorUI.h"
// Project Headers // Project Headers
@ -25,6 +27,8 @@ of DigiPen Institute of Technology is prohibited.
#include "Utility/Debug.hxx" #include "Utility/Debug.hxx"
#include "Serialisation/ReflectionUtilities.hxx" #include "Serialisation/ReflectionUtilities.hxx"
#include "Editor/IconsMaterialDesign.h" #include "Editor/IconsMaterialDesign.h"
#include "Editor/Command/SHCommandManager.h"
#include "Editor/Command/SHCommand.hpp"
// Using Directives // Using Directives
using namespace System; using namespace System;
@ -48,9 +52,11 @@ using namespace System::Collections::Generic;
(field->FieldType == MANAGED_TYPE::typeid) \ (field->FieldType == MANAGED_TYPE::typeid) \
{ \ { \
NATIVE_TYPE val = safe_cast<NATIVE_TYPE>(field->GetValue(object)); \ NATIVE_TYPE val = safe_cast<NATIVE_TYPE>(field->GetValue(object)); \
NATIVE_TYPE oldVal = val; \
if (SHEditorUI::FUNC(Convert::ToNative(field->Name), val)) \ if (SHEditorUI::FUNC(Convert::ToNative(field->Name), val)) \
{ \ { \
field->SetValue(object, val); \ field->SetValue(object, val); \
registerUndoAction(object, field, val, oldVal); \
} \ } \
} \ } \
/// <summary> /// <summary>
@ -69,9 +75,11 @@ using namespace System::Collections::Generic;
(field->FieldType == MANAGED_TYPE::typeid) \ (field->FieldType == MANAGED_TYPE::typeid) \
{ \ { \
NATIVE_TYPE val = Convert::ToNative(safe_cast<MANAGED_TYPE>(field->GetValue(object))); \ NATIVE_TYPE val = Convert::ToNative(safe_cast<MANAGED_TYPE>(field->GetValue(object))); \
NATIVE_TYPE oldVal = val; \
if (SHEditorUI::FUNC(Convert::ToNative(field->Name), val)) \ if (SHEditorUI::FUNC(Convert::ToNative(field->Name), val)) \
{ \ { \
field->SetValue(object, Convert::ToCLI(val)); \ field->SetValue(object, Convert::ToCLI(val)); \
registerUndoAction(object, field, Convert::ToCLI(val), Convert::ToCLI(oldVal)); \
} \ } \
} \ } \
@ -105,26 +113,43 @@ namespace SHADE
SAFE_NATIVE_CALL_END_N("SHADE_Managed.Editor.RenderScriptsInInspector") SAFE_NATIVE_CALL_END_N("SHADE_Managed.Editor.RenderScriptsInInspector")
} }
void Editor::RenderScriptAddButton(Entity entity) void Editor::RenderScriptAddButton(Entity entity)
{ {
// Get list of Scripts // Get list of Scripts
auto scriptTypes = ScriptStore::GetAvailableScriptList(); auto scriptTypes = ScriptStore::GetAvailableScriptList();
// Define pop up // Define pop up
if (SHEditorUI::BeginMenu("Add Script", ICON_MD_LIBRARY_ADD)) if (SHEditorUI::BeginMenu("Add Script", ICON_MD_LIBRARY_ADD))
{ {
for each (Type ^ type in scriptTypes) for each (Type ^ type in scriptTypes)
{ {
if (SHEditorUI::Selectable(Convert::ToNative(type->Name))) if (SHEditorUI::Selectable(Convert::ToNative(type->Name)))
{ {
// Add the script // Add the script
ScriptStore::AddScriptViaName(entity, type->Name); ScriptStore::AddScriptViaName(entity, type->Name);
} }
} }
SHEditorUI::EndMenu(); SHEditorUI::EndMenu();
} }
} }
/*---------------------------------------------------------------------------------*/
/* UndoRedoStack Functions */
/*---------------------------------------------------------------------------------*/
void Editor::Undo()
{
SAFE_NATIVE_CALL_BEGIN
actionStack.Undo();
SAFE_NATIVE_CALL_END_N("SHADE_Managed.Editor.Undo")
}
void Editor::Redo()
{
SAFE_NATIVE_CALL_BEGIN
actionStack.Redo();
SAFE_NATIVE_CALL_END_N("SHADE_Managed.Editor.Redo")
}
/*---------------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------------*/
/* Helper Functions */ /* Helper Functions */
@ -192,9 +217,11 @@ namespace SHADE
} }
int val = safe_cast<int>(field->GetValue(object)); int val = safe_cast<int>(field->GetValue(object));
int oldVal = val;
if (SHEditorUI::InputEnumCombo(Convert::ToNative(field->Name), val, nativeEnumNames)) if (SHEditorUI::InputEnumCombo(Convert::ToNative(field->Name), val, nativeEnumNames))
{ {
field->SetValue(object, val); field->SetValue(object, val);
registerUndoAction(object, field, val, oldVal);
} }
} }
else if RENDER_FIELD_CASTED(Vector2, SHVec2, InputVec2) else if RENDER_FIELD_CASTED(Vector2, SHVec2, InputVec2)
@ -210,9 +237,11 @@ namespace SHADE
// Actual Field // Actual Field
std::string val = Convert::ToNative(stringVal); std::string val = Convert::ToNative(stringVal);
std::string oldVal = val;
if (SHEditorUI::InputTextField(Convert::ToNative(field->Name), val)) if (SHEditorUI::InputTextField(Convert::ToNative(field->Name), val))
{ {
field->SetValue(object, Convert::ToCLI(val)); field->SetValue(object, Convert::ToCLI(val));
registerUndoAction(object, field, Convert::ToCLI(val), Convert::ToCLI(oldVal));
} }
} }
} }
@ -231,4 +260,18 @@ namespace SHADE
} }
} }
void Editor::registerUndoAction(System::Object^ object, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData)
{
// Create command and add it into the undo stack
UndoRedoStack::Command cmd;
cmd.Field = field;
cmd.Object = object;
cmd.NewData = newData;
cmd.OldData = oldData;
actionStack.Add(cmd);
// Inform the C++ Undo-Redo stack
SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast<SHBaseCommand>(std::make_shared<SHCLICommand>()));
}
} }

View File

@ -16,6 +16,7 @@ of DigiPen Institute of Technology is prohibited.
// Project Includes // Project Includes
#include "Engine/Entity.hxx" #include "Engine/Entity.hxx"
#include "Scripts/Script.hxx" #include "Scripts/Script.hxx"
#include "UndoRedoStack.hxx"
namespace SHADE namespace SHADE
{ {
@ -36,15 +37,26 @@ namespace SHADE
/// rendering code. /// rendering code.
/// </summary> /// </summary>
/// <param name="entity">The Entity to render the Scripts of.</param> /// <param name="entity">The Entity to render the Scripts of.</param>
static void RenderScriptsInInspector(Entity entity); static void RenderScriptsInInspector(Entity entity);
/// <summary> /// <summary>
/// Renders a dropdown button that allows for the addition of PlushieScripts /// Renders a dropdown button that allows for the addition of PlushieScripts
/// onto the specified Entity. /// onto the specified Entity.
/// </summary> /// </summary>
/// <param name="entity">The Entity to add PlushieScripts to.</param> /// <param name="entity">The Entity to add PlushieScripts to.</param>
static void RenderScriptAddButton(Entity entity); static void RenderScriptAddButton(Entity entity);
/*-----------------------------------------------------------------------------*/
/* UndoRedoStack Functions */
/*-----------------------------------------------------------------------------*/
static void Undo();
static void Redo();
private:
/*-----------------------------------------------------------------------------*/
/* Data Members */
/*-----------------------------------------------------------------------------*/
static UndoRedoStack actionStack;
private:
/*-----------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------*/
/* Helper Functions */ /* Helper Functions */
/*-----------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------*/
@ -73,5 +85,6 @@ 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);
static void registerUndoAction(System::Object^ object, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData);
}; };
} }

View File

@ -22,28 +22,48 @@ of DigiPen Institute of Technology is prohibited.
namespace SHADE namespace SHADE
{ {
bool UndoRedoStack::UndoActionPresent::get()
{
return commandStack->Count > 0 && latestActionIndex >= 0;
}
bool UndoRedoStack::RedoActionPresent::get()
{
return latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1;
}
void UndoRedoStack::Add(Command command) void UndoRedoStack::Add(Command command)
{ {
// Erase any other actions ahead of the current action // Erase any other actions ahead of the current action
if (latestActionIndex < commandStack->Count - 1) if (latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1)
{ {
commandStack->RemoveRange(latestActionIndex + 1, commandStack->Count - latestActionIndex); commandStack->RemoveRange(latestActionIndex, commandStack->Count - latestActionIndex);
} }
// Add the command // Add the command
commandStack->Add(command); commandStack->Add(command);
// Increment latest command // Set the latest command
++latestActionIndex; latestActionIndex = commandStack->Count - 1;
} }
void UndoRedoStack::Undo() void UndoRedoStack::Undo()
{ {
if (!UndoActionPresent)
return;
Command cmd = commandStack[latestActionIndex];
cmd.Field->SetValue(cmd.Object, cmd.OldData);
--latestActionIndex;
} }
void UndoRedoStack::Redo() void UndoRedoStack::Redo()
{ {
if (!RedoActionPresent)
return;
Command cmd = commandStack[latestActionIndex];
cmd.Field->SetValue(cmd.Object, cmd.NewData);
++latestActionIndex;
} }
} }

View File

@ -24,25 +24,52 @@ namespace SHADE
/*-----------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------*/
/* Type Definitions */ /* Type Definitions */
/*-----------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------*/
/// <summary>
/// Command for the stack that represents a data modification.
/// </summary>
value struct Command value struct Command
{ {
public: public:
System::Action^ UndoAction; System::Object^ Object;
System::Action^ RedoAction; System::Reflection::FieldInfo^ Field;
}; System::Object^ NewData;
System::Object^ OldData;
};
/*-----------------------------------------------------------------------------*/
/* Properties */
/*-----------------------------------------------------------------------------*/
/// <summary>
/// True if there is an undoable action in the stack.
/// </summary>
property bool UndoActionPresent { bool get(); }
/// <summary>
/// True if there is a redoable action in the stack.
/// </summary>
property bool RedoActionPresent { bool get(); }
/*-----------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------*/
/* Usage Functions */ /* Usage Functions */
/*-----------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------*/
void Add(Command command); /// <summary>
/// Adds a command onto the stack.
/// </summary>
/// <param name="command"></param>
void Add(Command command);
/// <summary>
/// Undos the last added command if it exists.
/// </summary>
void Undo(); void Undo();
void Redo(); /// <summary>
/// Redoes the last undo-ed command if it exists.
/// </summary>
void Redo();
private: private:
/*-----------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------*/
/* Data Members */ /* Data Members */
/*-----------------------------------------------------------------------------*/ /*-----------------------------------------------------------------------------*/
int latestActionIndex = -1; int latestActionIndex = -1;
System::Collections::Generic::List<Command>^ commandStack = gcnew System::Collections::Generic::List<Command>(); System::Collections::Generic::List<Command>^ commandStack = gcnew System::Collections::Generic::List<Command>();
}; };
} }