Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement undo system (WIP) #115

Merged
merged 5 commits into from
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion editor/ImGui/Implementation/ImGui_TransformGizmo.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,19 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "World/Entity.h"
#include "Rendering/Renderer.h"
#include "Input/Input.h"
#include "Commands/CommandStack.h"
#include "Commands/TransformEntity.h"
#include "Engine.h"
//======================================

namespace ImGui::TransformGizmo
{
static inline bool previous_handled = false;

static inline Spartan::Math::Vector3 begin_position;
static inline Spartan::Math::Quaternion begin_rotation;
static inline Spartan::Math::Vector3 begin_scale;

static void apply_style()
{
ImGuizmo::Style& style = ImGuizmo::GetStyle();
Expand Down Expand Up @@ -61,7 +69,9 @@ namespace ImGui::TransformGizmo
// Enable/disable gizmo
ImGuizmo::Enable(entity != nullptr);
if (!entity)
{
return;
}

// Switch between position, rotation and scale operations, with W, E and R respectively
static ImGuizmo::OPERATION transform_operation = ImGuizmo::TRANSLATE;
Expand Down Expand Up @@ -104,16 +114,32 @@ namespace ImGui::TransformGizmo
ImGuizmo::SetDrawlist();
ImGuizmo::SetRect(ImGui::GetWindowPos().x, ImGui::GetWindowPos().y, ImGui::GetWindowWidth(), ImGui::GetWindowHeight());

ImGuizmo::Manipulate(&matrix_view.m00, &matrix_projection.m00, transform_operation, transform_space, &transform_matrix.m00, nullptr, nullptr);
bool this_handled = ImGuizmo::Manipulate(&matrix_view.m00, &matrix_projection.m00, transform_operation, transform_space, &transform_matrix.m00, nullptr, nullptr);
bool began_handling = !previous_handled && this_handled;
bool ended_handling = previous_handled && !this_handled;
previous_handled = this_handled;

// Map ImGuizmo to transform
if (ImGuizmo::IsUsing())
{
transform_matrix.Transposed().Decompose(scale, rotation, position);

if (began_handling)
{
begin_position = position;
begin_rotation = rotation;
begin_scale = scale;
}

transform->SetPosition(position);
transform->SetRotation(rotation);
transform->SetScale(scale);

if (ended_handling)
{
SP_LOG_INFO("Applying command");
Spartan::CommandStack::Apply<Spartan::TransformEntity>(entity.get(), begin_position, begin_rotation, begin_scale);
}
}
}

Expand Down
19 changes: 17 additions & 2 deletions editor/Widgets/WorldViewer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include "World/Components/Constraint.h"
#include "World/Components/Terrain.h"
#include "World/Components/ReflectionProbe.h"
#include "World/Components/Transform.h"
#include "Commands/CommandStack.h"
//===========================================

//= NAMESPACES =====
Expand Down Expand Up @@ -71,7 +71,7 @@ namespace
"6. Sponza",
"7. Doom E1M1"
};
static int item_index = 4; // forestWorld]
static int item_index = 4; // forest
static int item_count = IM_ARRAYSIZE(items);
ImGui::PushItemWidth(450.0f * Spartan::Window::GetDpiScale());
ImGui::ListBox("##list_box", &item_index, items, item_count, item_count);
Expand Down Expand Up @@ -595,6 +595,21 @@ void WorldViewer::HandleKeyShortcuts()
{
m_editor->GetWidget<MenuBar>()->ShowWorldLoadDialog();
}

// Undo and Redo: Ctrl + Z, Ctrl+Shift+Z
if (Spartan::Input::GetKey(Spartan::KeyCode::Ctrl_Left) && Spartan::Input::GetKeyDown(Spartan::KeyCode::Z))
{
if (Spartan::Input::GetKey(Spartan::KeyCode::Shift_Left))
{
Spartan::CommandStack::Redo();
}
else
{
Spartan::CommandStack::Undo();
}
}


}

void WorldViewer::ActionEntityDelete(const shared_ptr<Spartan::Entity> entity)
Expand Down
26 changes: 26 additions & 0 deletions runtime/Commands/Command.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
Copyright(c) 2016-2023 Fredrik Svantesson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions :

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

//= INCLUDES ==========================
#include "pch.h"
#include "Command.h"
//=====================================

39 changes: 39 additions & 0 deletions runtime/Commands/Command.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
Copyright(c) 2023 Fredrik Svantesson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions :

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#pragma once

//= INCLUDES =============================
#include "Definitions.h"
//========================================

namespace Spartan
{
/**
* Baseclass for commands to be used in a CommandStack.
*/
class SP_CLASS Command
{
public:
virtual void OnApply() = 0;
virtual void OnRevert() = 0;
};
}
67 changes: 67 additions & 0 deletions runtime/Commands/CommandStack.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
Copyright(c) 2016-2023 Fredrik Svantesson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions :

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

//= INCLUDES ==========================
#include "pch.h"
#include "CommandStack.h"
//=====================================


namespace Spartan
{

void CommandStack::Undo()
{
if (m_undo_buffer.size() == 0)
return;

// Fetch the latest applied command, so we know what we are undoing
std::shared_ptr<Command> undo_command = m_undo_buffer.back();
m_undo_buffer.pop_back();

// Actually undo
undo_command->OnRevert();

// Push it to the top of the redo stack
m_redo_buffer.push_back(undo_command);
}

void CommandStack::Redo()
{
if (m_redo_buffer.size() == 0)
return;

// Fetch the latest undoed command, so we know what we are redoing
std::shared_ptr<Command> redo_command = m_redo_buffer.back();
m_redo_buffer.pop_back();

// Actually redo
redo_command->OnApply();

// Push it to the top of the undo stack
m_undo_buffer.push_back(redo_command);
}

std::vector<std::shared_ptr<Spartan::Command>> CommandStack::m_undo_buffer;

std::vector<std::shared_ptr<Spartan::Command>> CommandStack::m_redo_buffer;

}
74 changes: 74 additions & 0 deletions runtime/Commands/CommandStack.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
Copyright(c) 2023 Fredrik Svantesson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions :

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#pragma once

//= INCLUDES =============================
#include "Definitions.h"
#include "../Commands/Command.h"
#include "../World/Components/Transform.h"
//========================================

namespace Spartan
{
// @todo make editor setting instead of compile time constant expression
constexpr uint64_t max_undo_steps = 128;

class SP_CLASS CommandStack
{
public:
static void Initialize();
static void Shutdown();

/** Creates and applies a new command (Note: this clears the redo buffer) */
template<typename CommandType, typename... Args>
static void Apply(Args&&... args)
{
// @todo this is garbage for performance, as it has to copy the entire buffer when it's full
// could be solved by using linked lists instead of dynamic arrays (vectors)
// optimal solution may be to preallocate an array instead, and use a cursor to manage undo/redo <-- probably do this
// luckily we only store pointers so should be decent performance for now (as long as max_undo_steps doesn't grow too large)
if (m_undo_buffer.size() >= max_undo_steps)
{
m_undo_buffer.erase(m_undo_buffer.begin());
}

std::shared_ptr<Command> new_command = std::make_shared<CommandType>(std::forward<Args>(args)...);
new_command->OnApply();

m_undo_buffer.push_back(new_command);

// Make sure to clear the redo buffer if you apply a new command, to preserve the time continuum.
m_redo_buffer.clear();
}

/** Undoes the latest applied command */
static void Undo();

/** Redoes the latest undone command */
static void Redo();

protected:

static std::vector<std::shared_ptr<Command>> m_undo_buffer;
static std::vector<std::shared_ptr<Command>> m_redo_buffer;
};
}
Loading