/************************************************************************************//*! \file UndoRedoStack.cxx \author Tng Kah Wei, kahwei.tng, 390009620 \par email: kahwei.tng\@digipen.edu \date Sep 29, 2022 \brief Contains the definition of the functions for the UndoRedoStack managed 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. *//*************************************************************************************/ // Precompiled Headers #include "SHpch.h" // Primary Header #include "UndoRedoStack.hxx" // External Dependencies #include "Editor/SHEditorUI.h" // Project Headers #include "Utility/Debug.hxx" #include "Utility/Convert.hxx" #include "Scripts/ScriptStore.hxx" #include "Serialisation/SerialisationUtilities.hxx" namespace SHADE { /*---------------------------------------------------------------------------------*/ /* UndoRedoStack - Properties */ /*---------------------------------------------------------------------------------*/ bool UndoRedoStack::UndoActionPresent::get() { return commandStack->Count > 0 && latestActionIndex >= 0; } bool UndoRedoStack::RedoActionPresent::get() { const int REDO_ACTION_INDEX = latestActionIndex + 1; return REDO_ACTION_INDEX >= 0 && REDO_ACTION_INDEX < commandStack->Count; } /*---------------------------------------------------------------------------------*/ /* UndoRedoStack - Usage Functions */ /*---------------------------------------------------------------------------------*/ void UndoRedoStack::Add(ICommand^ command) { // Erase any other actions ahead of the current action if (latestActionIndex >= 0 && latestActionIndex < commandStack->Count - 1) { commandStack->RemoveRange(latestActionIndex, commandStack->Count - latestActionIndex); } // Add the command commandStack->Add(command); // Set the latest command latestActionIndex = commandStack->Count - 1; } void UndoRedoStack::Undo() { if (!UndoActionPresent) return; ICommand^ cmd = commandStack[latestActionIndex]; cmd->Unexceute(); --latestActionIndex; } void UndoRedoStack::Redo() { if (!RedoActionPresent) return; const int REDO_ACTION_INDEX = latestActionIndex + 1; ICommand^ cmd = commandStack[REDO_ACTION_INDEX]; cmd->Execute(); ++latestActionIndex; } /*---------------------------------------------------------------------------------*/ /* FieldChangeCommand - Constructor */ /*---------------------------------------------------------------------------------*/ FieldChangeCommand::FieldChangeCommand(System::Object^ obj, System::Reflection::FieldInfo^ field, System::Object^ newData, System::Object^ oldData) : objectToChange { obj } , field { field } , newData { newData } , oldData { oldData } {} /*---------------------------------------------------------------------------------*/ /* FieldChangeCommand - ICommand Functions */ /*---------------------------------------------------------------------------------*/ 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(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; } /*---------------------------------------------------------------------------------*/ /* ListElementChangeCommand - Constructor */ /*---------------------------------------------------------------------------------*/ ListElementChangeCommand::ListElementChangeCommand(System::Collections::IList^ list, int index, System::Object^ newData, System::Object^ oldData) : list { list } , index { index } , newData { newData } , oldData { oldData } {} /*---------------------------------------------------------------------------------*/ /* ListElementChangeCommand - ICommand Functions */ /*---------------------------------------------------------------------------------*/ bool ListElementChangeCommand::Execute() { if (list && index < list->Count) { list[index] = newData; return true; } return false; } bool ListElementChangeCommand::Unexceute() { if (list && index < list->Count) { list[index] = oldData; return true; } return false; } bool ListElementChangeCommand::Merge(ICommand^ command) { ListElementChangeCommand^ otherCommand = safe_cast(command); if (otherCommand == nullptr) { Debug::LogWarning("[Field Change Command] Attempted to merge two incompatible commands!"); return false; } if (command && list == otherCommand->list && index == otherCommand->index) { newData = otherCommand->newData; return true; } } /*---------------------------------------------------------------------------------*/ /* ListElementAddCommand - Constructor */ /*---------------------------------------------------------------------------------*/ ListElementAddCommand::ListElementAddCommand(System::Collections::IList^ list, int addIndex, System::Object^ data) : list { list } , addIndex { addIndex } , data { data } {} /*---------------------------------------------------------------------------------*/ /* ListElementAddCommand - ICommand Functions */ /*---------------------------------------------------------------------------------*/ bool ListElementAddCommand::Execute() { if (list) { list->Insert(addIndex, data); return true; } return false; } bool ListElementAddCommand::Unexceute() { if (list && addIndex < list->Count) { list->RemoveAt(addIndex); return true; } return false; } bool ListElementAddCommand::Merge(ICommand^) { // Not allowed return false; } /*---------------------------------------------------------------------------------*/ /* ListElementRemoveCommand - Constructor */ /*---------------------------------------------------------------------------------*/ ListElementRemoveCommand::ListElementRemoveCommand(System::Collections::IList^ list, int removeIndex, System::Object^ data) : list { list } , removeIndex { removeIndex } , data { data } {} /*---------------------------------------------------------------------------------*/ /* ListElementRemoveCommand - ICommand Functions */ /*---------------------------------------------------------------------------------*/ bool ListElementRemoveCommand::Execute() { if (list && removeIndex < list->Count) { list->RemoveAt(removeIndex); return true; } return false; } bool ListElementRemoveCommand::Unexceute() { if (list) { list->Insert(removeIndex, data); return true; } return false; } bool ListElementRemoveCommand::Merge(ICommand^) { // Not allowed return false; } /*---------------------------------------------------------------------------------*/ /* ScriptAddCommand - Constructor */ /*---------------------------------------------------------------------------------*/ ScriptAddCommand::ScriptAddCommand(EntityID id, Script^ script) : entity { id } , typeName { script->GetType()->FullName } , serialisedScript { SerialisationUtilities::Serialise(script) } , insertedIndex { ScriptStore::GetScriptIndex(script) } {} /*---------------------------------------------------------------------------------*/ /* ScriptAddCommand - ICommand Functions */ /*---------------------------------------------------------------------------------*/ bool ScriptAddCommand::Execute() { Script^ script = nullptr; if (ScriptStore::AddScriptViaNameWithRef(entity, typeName, script)) { SerialisationUtilities::Deserialise(script, serialisedScript); insertedIndex = ScriptStore::GetScriptIndex(script); return true; } return false; } bool ScriptAddCommand::Unexceute() { return ScriptStore::RemoveScript(entity, insertedIndex); } bool ScriptAddCommand::Merge(ICommand^) { // Not allowed return false; } /*---------------------------------------------------------------------------------*/ /* ScriptRemoveCommand - Constructor */ /*---------------------------------------------------------------------------------*/ ScriptRemoveCommand::ScriptRemoveCommand(EntityID id, Script^ script, int index) : entity{ id } , typeName{ script->GetType()->FullName } , serialisedScript{ SerialisationUtilities::Serialise(script) } , originalIndex { index } {} /*---------------------------------------------------------------------------------*/ /* ScriptRemoveCommand - ICommand Functions */ /*---------------------------------------------------------------------------------*/ bool ScriptRemoveCommand::Execute() { return ScriptStore::RemoveScript(entity, originalIndex); } bool ScriptRemoveCommand::Unexceute() { Script^ script = nullptr; if (ScriptStore::AddScriptViaNameWithRef(entity, typeName, script, originalIndex)) { SerialisationUtilities::Deserialise(script, serialisedScript); return true; } return false; } bool ScriptRemoveCommand::Merge(ICommand^) { // Not allowed return false; } }