diff --git a/include/Engine/Core/EntityWrapper.h b/include/Engine/Core/EntityWrapper.h index 4643e28b..cd58f993 100644 --- a/include/Engine/Core/EntityWrapper.h +++ b/include/Engine/Core/EntityWrapper.h @@ -27,6 +27,7 @@ struct EntityWrapper bool HasComponent(const std::string& componentType); void AttachComponent(const char* componentName); EntityWrapper Parent(); + EntityWrapper BaseParent(); EntityWrapper FirstChildByName(const std::string& name); EntityWrapper FirstParentWithComponent(const std::string& componentType); EntityWrapper Clone(EntityWrapper parent = EntityWrapper::Invalid); diff --git a/include/Engine/Core/World.h b/include/Engine/Core/World.h index 05cd3f41..df5aca72 100644 --- a/include/Engine/Core/World.h +++ b/include/Engine/Core/World.h @@ -18,7 +18,7 @@ public: World(const World& other); // Create empty entity - EntityID CreateEntity(EntityID parent = 0); + EntityID CreateEntity(EntityID parent = EntityID_Invalid); // Delete entity and all components within void DeleteEntity(EntityID entity); // Check if an entity exists diff --git a/include/Engine/Editor/EditorGUI.h b/include/Engine/Editor/EditorGUI.h index 57574e66..c2551012 100644 --- a/include/Engine/Editor/EditorGUI.h +++ b/include/Engine/Editor/EditorGUI.h @@ -91,7 +91,13 @@ public: // Called when the user selects a widget space. typedef std::function OnWidgetSpace_t; void SetWidgetSpaceCallback(OnWidgetSpace_t f) { m_OnWidgetSpace = f; } - + // Called when anything is modified making the world dirty + // @param EntityWrapper The entity that was changed and marked as dirty + typedef std::function OnDirty_t; + void SetDirtyCallback(OnDirty_t f) { m_OnDirty = f; } + // Called when the user wishes to undo + typedef std::function OnUndo_t; + void SetUndoCallback(OnUndo_t f) { m_OnUndo = f; } private: World* m_World; EventBroker* m_EventBroker; @@ -132,6 +138,8 @@ private: OnWidgetMode_t m_OnWidgetMode = nullptr; OnWidgetSpace_t m_OnWidgetSpace = nullptr; OnEntityPaste_t m_OnEntityPaste = nullptr; + OnDirty_t m_OnDirty = nullptr; + OnUndo_t m_OnUndo = nullptr; // Events EventRelay m_EKeyDown; diff --git a/include/Engine/Editor/EditorSystem.h b/include/Engine/Editor/EditorSystem.h index 06ee53b6..eb90b594 100644 --- a/include/Engine/Editor/EditorSystem.h +++ b/include/Engine/Editor/EditorSystem.h @@ -36,6 +36,7 @@ private: EditorCameraInputController* m_EditorCameraInputController; EditorGUI* m_EditorGUI; EditorStats* m_EditorStats; + std::vector m_UndoLevels; // State double m_LastTime = 0.f; @@ -44,6 +45,7 @@ private: EditorGUI::WidgetSpace m_WidgetSpace = EditorGUI::WidgetSpace::Global; EntityWrapper m_Widget = EntityWrapper::Invalid; EntityWrapper m_CurrentSelection = EntityWrapper::Invalid; + bool m_SaveUndoLevel = false; // Only save undo state once per update // Utility functions EntityWrapper importEntity(EntityWrapper parent, boost::filesystem::path filePath); @@ -60,6 +62,8 @@ private: void OnComponentAttach(EntityWrapper entity, const std::string& componentType); void OnComponentDelete(EntityWrapper entity, const std::string& componentType); void OnWidgetSpace(EditorGUI::WidgetSpace widgetSpace); + void OnDirty(EntityWrapper entity); + void OnUndo(); // Events EventRelay m_EMousePress; diff --git a/resources/Schema/Entities/Empty.xml b/resources/Schema/Entities/Empty.xml index 6efd8318..ed588c52 100644 --- a/resources/Schema/Entities/Empty.xml +++ b/resources/Schema/Entities/Empty.xml @@ -5,6 +5,20 @@ - + + + + + + + + + + + + + + + diff --git a/src/Engine/Core/EntityWrapper.cpp b/src/Engine/Core/EntityWrapper.cpp index ace1f4a7..4e3919b2 100644 --- a/src/Engine/Core/EntityWrapper.cpp +++ b/src/Engine/Core/EntityWrapper.cpp @@ -34,6 +34,15 @@ EntityWrapper EntityWrapper::Parent() } } +EntityWrapper EntityWrapper::BaseParent() +{ + EntityWrapper baseParent = Parent(); + while (baseParent.Parent().Valid()) { + baseParent = baseParent.Parent(); + } + return baseParent; +} + EntityWrapper EntityWrapper::FirstChildByName(const std::string& name) { return firstChildByNameRecursive(name, this->ID); diff --git a/src/Engine/Editor/EditorGUI.cpp b/src/Engine/Editor/EditorGUI.cpp index f2690f13..e73d16d2 100644 --- a/src/Engine/Editor/EditorGUI.cpp +++ b/src/Engine/Editor/EditorGUI.cpp @@ -602,6 +602,15 @@ bool EditorGUI::OnKeyDown(const Events::KeyDown& e) m_CopyTarget = m_CurrentSelection; } + if (e.ModCtrl && e.KeyCode == GLFW_KEY_Z) { + if (m_OnUndo != nullptr) { + m_OnUndo(); + if (!m_CurrentSelection.Valid()) { + SelectEntity(EntityWrapper::Invalid); + } + } + } + if (e.ModCtrl && e.KeyCode == GLFW_KEY_V) { if (m_OnEntityPaste != nullptr) { EntityWrapper copy = m_OnEntityPaste(m_CopyTarget, m_CurrentSelection); @@ -774,13 +783,13 @@ bool EditorGUI::compareCharArray(const char* c1, const char* c2) void EditorGUI::SetDirty(EntityWrapper entity) { - EntityWrapper baseParent = entity; - while (baseParent.Parent().Valid()) { - baseParent = baseParent.Parent(); - } + EntityWrapper baseParent = entity.BaseParent(); if (m_EntityFiles.find(baseParent) != m_EntityFiles.end()) { m_EntityFiles.at(baseParent).Dirty = true; } + if (m_OnDirty != nullptr) { + m_OnDirty(entity); + } } void EditorGUI::entityImport(World* world) @@ -830,6 +839,7 @@ void EditorGUI::entityCreate(World* world, EntityWrapper parent) parent.World = world; } EntityWrapper newEntity = m_OnEntityCreate(parent); + SetDirty(newEntity); SelectEntity(newEntity); } } @@ -845,9 +855,9 @@ void EditorGUI::entityDelete(EntityWrapper entity) if (boost::any_cast(m_ModalData[modalName]) == entity) { EntityWrapper parent = entity.Parent(); if (m_OnEntityDelete != nullptr) { - SetDirty(entity); m_OnEntityDelete(entity); m_EntityFiles.erase(entity); + SetDirty(parent); } if (!m_CurrentSelection.Valid()) { SelectEntity(parent); @@ -864,8 +874,8 @@ void EditorGUI::entityChangeParent(EntityWrapper entity, EntityWrapper parent) } if (m_OnEntityChangeParent != nullptr) { - SetDirty(entity); m_OnEntityChangeParent(entity, parent); + SetDirty(entity); LOG_DEBUG("Changed parent of %i to %i", entity.ID, parent.ID); } } diff --git a/src/Engine/Editor/EditorSystem.cpp b/src/Engine/Editor/EditorSystem.cpp index 4bee1427..f019c5b3 100644 --- a/src/Engine/Editor/EditorSystem.cpp +++ b/src/Engine/Editor/EditorSystem.cpp @@ -33,6 +33,8 @@ EditorSystem::EditorSystem(SystemParams params, IRenderer* renderer, RenderFrame m_EditorGUI->SetComponentDeleteCallback(std::bind(&EditorSystem::OnComponentDelete, this, std::placeholders::_1, std::placeholders::_2)); m_EditorGUI->SetWidgetModeCallback(std::bind(&EditorSystem::setWidgetMode, this, std::placeholders::_1)); m_EditorGUI->SetWidgetSpaceCallback(std::bind(&EditorSystem::OnWidgetSpace, this, std::placeholders::_1)); + m_EditorGUI->SetDirtyCallback(std::bind(&EditorSystem::OnDirty, this, std::placeholders::_1)); + m_EditorGUI->SetUndoCallback(std::bind(&EditorSystem::OnUndo, this)); EVENT_SUBSCRIBE_MEMBER(m_EMousePress, &EditorSystem::OnMousePress); EVENT_SUBSCRIBE_MEMBER(m_EWidgetDelta, &EditorSystem::OnWidgetDelta); @@ -87,6 +89,11 @@ void EditorSystem::Update(double dt) glm::vec3& pos = cameraTransform["Position"]; pos += m_EditorCameraInputController->Movement() * glm::inverse(glm::quat(ori)) * (float)actualDelta; } + + if (m_SaveUndoLevel) { + m_UndoLevels.push_back(*m_World); + m_SaveUndoLevel = false; + } } void EditorSystem::Enable() @@ -185,6 +192,19 @@ void EditorSystem::OnWidgetSpace(EditorGUI::WidgetSpace widgetSpace) m_WidgetSpace = widgetSpace; } +void EditorSystem::OnDirty(EntityWrapper entity) +{ + m_SaveUndoLevel = true; +} + +void EditorSystem::OnUndo() +{ + // The last "undo level" is always the most recent change + if (m_UndoLevels.size() >= 2) { + *m_World = m_UndoLevels.at(m_UndoLevels.size() - 2); + } +} + bool EditorSystem::OnMousePress(const Events::MousePress& e) { ImGuiIO& io = ImGui::GetIO();