Reworked Undo-Redo system to be more flexible and stable
This commit is contained in:
parent
bdc7297937
commit
cf5cc41a3f
|
@ -149,7 +149,7 @@ namespace SHADE
|
||||||
}
|
}
|
||||||
SHEditorUI::PopID();
|
SHEditorUI::PopID();
|
||||||
}
|
}
|
||||||
void Editor::renderFieldInInspector(Reflection::FieldInfo^ field, Object^ object)
|
void Editor::renderFieldInInspector(Reflection::FieldInfo^ field, System::Object^ object)
|
||||||
{
|
{
|
||||||
bool isHovered = false;
|
bool isHovered = false;
|
||||||
|
|
||||||
|
@ -343,12 +343,7 @@ namespace SHADE
|
||||||
void Editor::registerUndoAction(System::Object^ object, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData)
|
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
|
// Create command and add it into the undo stack
|
||||||
UndoRedoStack::Command cmd;
|
actionStack.Add(gcnew FieldChangeCommand(object, field, newData, oldData));
|
||||||
cmd.Field = field;
|
|
||||||
cmd.Object = object;
|
|
||||||
cmd.NewData = newData;
|
|
||||||
cmd.OldData = oldData;
|
|
||||||
actionStack.Add(cmd);
|
|
||||||
|
|
||||||
// Inform the C++ Undo-Redo stack
|
// Inform the C++ Undo-Redo stack
|
||||||
SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast<SHBaseCommand>(std::make_shared<SHCLICommand>()));
|
SHCommandManager::RegisterCommand(std::reinterpret_pointer_cast<SHBaseCommand>(std::make_shared<SHCLICommand>()));
|
||||||
|
|
|
@ -30,7 +30,8 @@ namespace SHADE
|
||||||
rangeAttrib = hasAttribute<RangeAttribute^>(fieldInfo);
|
rangeAttrib = hasAttribute<RangeAttribute^>(fieldInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
ManagedType val = safe_cast<ManagedType>(fieldInfo->GetValue(object));
|
ManagedType oldVal = safe_cast<ManagedType>(fieldInfo->GetValue(object));
|
||||||
|
ManagedType val = oldVal;
|
||||||
if (renderFieldInInspector<NativeType, ManagedType>
|
if (renderFieldInInspector<NativeType, ManagedType>
|
||||||
(
|
(
|
||||||
Convert::ToNative(fieldInfo->Name),
|
Convert::ToNative(fieldInfo->Name),
|
||||||
|
@ -41,7 +42,7 @@ namespace SHADE
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
fieldInfo->SetValue(object, val);
|
fieldInfo->SetValue(object, val);
|
||||||
// TODO: Register undo
|
registerUndoAction(object, fieldInfo, fieldInfo->GetValue(object), oldVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -90,15 +91,12 @@ namespace SHADE
|
||||||
{
|
{
|
||||||
if constexpr (IsPrimitiveTypeMatches_V<NativeType>)
|
if constexpr (IsPrimitiveTypeMatches_V<NativeType>)
|
||||||
{
|
{
|
||||||
//field->SetValue(object, val);
|
|
||||||
managedVal = val;
|
managedVal = val;
|
||||||
//registerUndoAction(object, field, val, oldVal);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
managedVal = Convert::ToCLI(val);
|
managedVal = Convert::ToCLI(val);
|
||||||
//registerUndoAction(object, field, Convert::ToCLI(val), Convert::ToCLI(oldVal));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -89,7 +89,7 @@ namespace SHADE
|
||||||
/// <param name="object">
|
/// <param name="object">
|
||||||
/// The object that contains the data of the field to render.
|
/// The object that contains the data of the field to render.
|
||||||
/// </param>
|
/// </param>
|
||||||
static void renderFieldInInspector(System::Reflection::FieldInfo^ field, Object^ object);
|
static void renderFieldInInspector(System::Reflection::FieldInfo^ field, System::Object^ object);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Renders a context menu when right clicked for the scripts
|
/// Renders a context menu when right clicked for the scripts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -19,6 +19,8 @@ of DigiPen Institute of Technology is prohibited.
|
||||||
// External Dependencies
|
// External Dependencies
|
||||||
#include "Editor/SHEditorUI.h"
|
#include "Editor/SHEditorUI.h"
|
||||||
// Project Headers
|
// Project Headers
|
||||||
|
#include "Utility/Debug.hxx"
|
||||||
|
#include "Utility/Convert.hxx"
|
||||||
|
|
||||||
namespace SHADE
|
namespace SHADE
|
||||||
{
|
{
|
||||||
|
@ -32,7 +34,7 @@ namespace SHADE
|
||||||
return latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1;
|
return latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UndoRedoStack::Add(Command command)
|
void UndoRedoStack::Add(ICommand^ command)
|
||||||
{
|
{
|
||||||
// Erase any other actions ahead of the current action
|
// Erase any other actions ahead of the current action
|
||||||
if (latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1)
|
if (latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1)
|
||||||
|
@ -52,8 +54,8 @@ namespace SHADE
|
||||||
if (!UndoActionPresent)
|
if (!UndoActionPresent)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Command cmd = commandStack[latestActionIndex];
|
ICommand^ cmd = commandStack[latestActionIndex];
|
||||||
cmd.Field->SetValue(cmd.Object, cmd.OldData);
|
cmd->Unexceute();
|
||||||
--latestActionIndex;
|
--latestActionIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,8 +64,57 @@ namespace SHADE
|
||||||
if (!RedoActionPresent)
|
if (!RedoActionPresent)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Command cmd = commandStack[latestActionIndex];
|
ICommand^ cmd = commandStack[latestActionIndex];
|
||||||
cmd.Field->SetValue(cmd.Object, cmd.NewData);
|
cmd->Execute();
|
||||||
++latestActionIndex;
|
++latestActionIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FieldChangeCommand::FieldChangeCommand(System::Object^ obj, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData)
|
||||||
|
: objectToChange { obj }
|
||||||
|
, field { field }
|
||||||
|
, newData { newData }
|
||||||
|
, oldData { oldData }
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool FieldChangeCommand::Execute()
|
||||||
|
{
|
||||||
|
if (field && objectToChange)
|
||||||
|
{
|
||||||
|
field->SetValue(objectToChange, newData);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldChangeCommand::Unexceute()
|
||||||
|
{
|
||||||
|
if (field && objectToChange)
|
||||||
|
{
|
||||||
|
field->SetValue(objectToChange, oldData);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FieldChangeCommand::Merge(ICommand^ command)
|
||||||
|
{
|
||||||
|
FieldChangeCommand^ otherCommand = safe_cast<FieldChangeCommand^>(command);
|
||||||
|
if (otherCommand == nullptr)
|
||||||
|
{
|
||||||
|
Debug::LogWarning("[Field Change Command] Attempted to merge two incompatible commands!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only merge if they are workng on the same object and field
|
||||||
|
if (field == otherCommand->field && objectToChange == otherCommand->objectToChange)
|
||||||
|
{
|
||||||
|
newData = otherCommand->newData;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,27 +15,52 @@ of DigiPen Institute of Technology is prohibited.
|
||||||
|
|
||||||
namespace SHADE
|
namespace SHADE
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface for command that fits into the UndoRedoStack which can perform
|
||||||
|
/// undo-able and redo-able operations.
|
||||||
|
/// </summary>
|
||||||
|
private interface class ICommand
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Executes an action. This is called when a "Redo" is performed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Whether the action was successful or not.</returns>
|
||||||
|
bool Execute();
|
||||||
|
/// <summary>
|
||||||
|
/// Undoes an action. This is called when an "Undo" is performed.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Whether the action was successful or not.</returns>
|
||||||
|
bool Unexceute();
|
||||||
|
/// <summary>
|
||||||
|
/// Merges this command with another command.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="command"></param>
|
||||||
|
/// <returns>Whether the merge was successful or not.</returns>
|
||||||
|
bool Merge(ICommand^ command);
|
||||||
|
};
|
||||||
|
|
||||||
|
private ref class FieldChangeCommand sealed : public ICommand
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FieldChangeCommand(System::Object^ obj, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData);
|
||||||
|
|
||||||
|
bool Execute() override;
|
||||||
|
bool Unexceute() override;
|
||||||
|
bool Merge(ICommand^ command) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
System::Object^ objectToChange;
|
||||||
|
System::Reflection::FieldInfo^ field;
|
||||||
|
System::Object^ newData;
|
||||||
|
System::Object^ oldData;
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class that is able to store a stack of actions that can be done and redone.
|
/// Class that is able to store a stack of actions that can be done and redone.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private ref class UndoRedoStack sealed
|
private ref class UndoRedoStack sealed
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/*-----------------------------------------------------------------------------*/
|
|
||||||
/* Type Definitions */
|
|
||||||
/*-----------------------------------------------------------------------------*/
|
|
||||||
/// <summary>
|
|
||||||
/// Command for the stack that represents a data modification.
|
|
||||||
/// </summary>
|
|
||||||
value struct Command
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
System::Object^ Object;
|
|
||||||
System::Reflection::FieldInfo^ Field;
|
|
||||||
System::Object^ NewData;
|
|
||||||
System::Object^ OldData;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*-----------------------------------------------------------------------------*/
|
/*-----------------------------------------------------------------------------*/
|
||||||
/* Properties */
|
/* Properties */
|
||||||
/*-----------------------------------------------------------------------------*/
|
/*-----------------------------------------------------------------------------*/
|
||||||
|
@ -55,7 +80,7 @@ namespace SHADE
|
||||||
/// Adds a command onto the stack.
|
/// Adds a command onto the stack.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="command"></param>
|
/// <param name="command"></param>
|
||||||
void Add(Command command);
|
void Add(ICommand^ command);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Undos the last added command if it exists.
|
/// Undos the last added command if it exists.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -70,6 +95,6 @@ namespace SHADE
|
||||||
/* 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<ICommand^>^ commandStack = gcnew System::Collections::Generic::List<ICommand^>();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue