From 9b9b93a183bd1d3fd519d7ff57e880b96e798c4e Mon Sep 17 00:00:00 2001 From: SweetId <2630750+SweetId@users.noreply.github.com> Date: Mon, 16 Oct 2023 19:31:59 -0400 Subject: [PATCH] Add ActionStack and automatically register actions in menubar --- include/NazaraEditor/Core.hpp | 1 + .../NazaraEditor/Core/Application/Action.hpp | 46 ++++++++--- .../Core/Application/ActionStack.hpp | 82 +++++++++++++++++++ .../Core/Application/BaseApplication.hpp | 13 ++- .../Core/Application/ActionStack.cpp | 41 ++++++++++ src/NazaraEditor/Core/UI/Window.cpp | 4 + 6 files changed, 176 insertions(+), 11 deletions(-) create mode 100644 include/NazaraEditor/Core/Application/ActionStack.hpp create mode 100644 src/NazaraEditor/Core/Application/ActionStack.cpp diff --git a/include/NazaraEditor/Core.hpp b/include/NazaraEditor/Core.hpp index 20fd0db..e73034b 100644 --- a/include/NazaraEditor/Core.hpp +++ b/include/NazaraEditor/Core.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include diff --git a/include/NazaraEditor/Core/Application/Action.hpp b/include/NazaraEditor/Core/Application/Action.hpp index a406d89..fbf6e1c 100644 --- a/include/NazaraEditor/Core/Application/Action.hpp +++ b/include/NazaraEditor/Core/Application/Action.hpp @@ -6,25 +6,31 @@ #include +#include #include namespace Nz { - class EditorAction + class NAZARAEDITOR_CORE_API EditorAction { public: struct Properties { - std::string name; + std::string className; std::string description; + std::string path; + std::string category; Nz::Shortcut shortcut; - Nz::Texture* icon; + std::shared_ptr icon; }; EditorAction(const Properties& properties) : m_properties(std::make_shared(properties)) {} + EditorAction(const std::shared_ptr&properties) + : m_properties(properties) + {} virtual ~EditorAction() = default; EditorAction(const EditorAction&) = delete; @@ -32,15 +38,35 @@ namespace Nz EditorAction(EditorAction&&) = delete; EditorAction& operator=(EditorAction&&) = delete; - virtual void Execute() = 0; - virtual void Revert() = 0; - virtual EditorAction* Clone() const = 0; + virtual std::unique_ptr Clone() const = 0; + virtual const std::string& GetName() const = 0; + virtual bool IsUndoRedoable() const = 0; + static const char* GetClassName() { return "EditorAction"; } + + virtual void Execute() {}; + virtual void Revert() {}; + + template + TAction* As() { return static_cast(this); } + + template + const TAction* As() const { return static_cast(this); } protected: - EditorAction(const std::shared_ptr& properties) - : m_properties(properties) - {} - std::shared_ptr m_properties; }; + +#define EDITORACTION_BODY(Typename, bUndoRedoable) \ + public: \ + Typename(const Properties& properties) \ + : EditorAction(properties) \ + {} \ + Typename(const std::shared_ptr& properties) \ + : EditorAction(properties) \ + {} \ + ~Typename() = default; \ + std::unique_ptr Clone() const override { return std::make_unique(m_properties); } \ + const std::string& GetName() const override { return m_properties->className; } \ + bool IsUndoRedoable() const override { return bUndoRedoable; } \ + static const char* GetClassName() { return #Typename; } } \ No newline at end of file diff --git a/include/NazaraEditor/Core/Application/ActionStack.hpp b/include/NazaraEditor/Core/Application/ActionStack.hpp new file mode 100644 index 0000000..aaad0c4 --- /dev/null +++ b/include/NazaraEditor/Core/Application/ActionStack.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +#include +#include + +namespace Nz +{ + class NAZARAEDITOR_CORE_API ActionStack final + { + public: + static ActionStack* Instance(); + + void ExecuteAction(const std::string& name) + { + ExecuteAction(CreateAction(name)); + } + + void ExecuteAction(const std::shared_ptr& action) + { + if (!action) + return; + + if (!action->IsUndoRedoable()) + { + action->Execute(); + return; + } + + if (CanRedo()) + { + // erase stack content after current index + m_undoRedoStack.resize(m_currentIndex); + } + + m_undoRedoStack.push_back(std::move(action)); + Redo(); + } + + std::shared_ptr CreateAction(const std::string& name) + { + auto it = std::find_if(m_availableActions.begin(), m_availableActions.end(), [&name](auto&& action) { return action->GetName() == name; }); + if (it == m_availableActions.end()) + return {}; + + return std::shared_ptr((*it)->Clone()); + } + + void Undo(); + bool CanUndo() const; + + void Redo(); + bool CanRedo() const; + + + protected: + ActionStack(); + ~ActionStack(); + + ActionStack(ActionStack&) = delete; + ActionStack& operator=(ActionStack&) = delete; + ActionStack(ActionStack&&) = delete; + ActionStack& operator=(ActionStack&&) = delete; + + template + void RegisterAction(const EditorAction::Properties& properties) + { + m_availableActions.push_back(std::make_unique(properties)); + } + + static ActionStack* s_instance; + + int64_t m_currentIndex; + std::vector> m_undoRedoStack; + + std::vector> m_availableActions; + + friend class EditorBaseApplication; + }; +} \ No newline at end of file diff --git a/include/NazaraEditor/Core/Application/BaseApplication.hpp b/include/NazaraEditor/Core/Application/BaseApplication.hpp index 13c22bc..e45ebaa 100644 --- a/include/NazaraEditor/Core/Application/BaseApplication.hpp +++ b/include/NazaraEditor/Core/Application/BaseApplication.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -29,6 +30,8 @@ namespace Nz NazaraSignal(OnEntitySelected, entt::handle); NazaraSignal(OnEntityDeselected, entt::handle); + // Editor events + NazaraSignal(OnActionRegistered, const EditorAction::Properties&); EditorBaseApplication(); virtual ~EditorBaseApplication() = default; @@ -46,11 +49,19 @@ namespace Nz m_windows.push_back(std::make_unique(this)); } + template + void RegisterAction(EditorAction::Properties properties) + { + properties.className = TAction::GetClassName(); + m_actionStack.RegisterAction(properties); + OnActionRegistered(properties); + } + private: std::unique_ptr m_windowSwapchain; std::vector> m_windows; - std::vector> m_actions; + Nz::ActionStack m_actionStack; Nz::Level m_level; }; } \ No newline at end of file diff --git a/src/NazaraEditor/Core/Application/ActionStack.cpp b/src/NazaraEditor/Core/Application/ActionStack.cpp new file mode 100644 index 0000000..4f1b0a1 --- /dev/null +++ b/src/NazaraEditor/Core/Application/ActionStack.cpp @@ -0,0 +1,41 @@ +#include + +namespace Nz +{ + ActionStack* ActionStack::s_instance = nullptr; + ActionStack::ActionStack() + : m_currentIndex(0) + { + NazaraAssert(s_instance == nullptr, "ActionStack already exists"); + s_instance = this; + } + + ActionStack::~ActionStack() + { + s_instance = nullptr; + } + + ActionStack* ActionStack::Instance() + { + return s_instance; + } + + + bool ActionStack::CanUndo() const { return !m_undoRedoStack.empty() && m_currentIndex >= 0; } + void ActionStack::Undo() + { + if (!CanUndo()) + return; + + m_undoRedoStack[m_currentIndex--]->Revert(); + } + + bool ActionStack::CanRedo() const { return !m_undoRedoStack.empty() && m_currentIndex < m_undoRedoStack.size(); } + void ActionStack::Redo() + { + if (!CanRedo()) + return; + + m_undoRedoStack[m_currentIndex++]->Execute(); + } +} \ No newline at end of file diff --git a/src/NazaraEditor/Core/UI/Window.cpp b/src/NazaraEditor/Core/UI/Window.cpp index 873d311..6be015b 100644 --- a/src/NazaraEditor/Core/UI/Window.cpp +++ b/src/NazaraEditor/Core/UI/Window.cpp @@ -9,6 +9,10 @@ namespace Nz , m_windowName(name) { Nz::Imgui::Instance()->AddHandler(this); + app->OnActionRegistered.Connect([this](auto&& prop) { + auto name = prop.className; + AddMenuAction(prop.path, prop.shortcut.ToString(), [name]() { Nz::ActionStack::Instance()->ExecuteAction(name); }, prop.icon); + }); } EditorWindow::~EditorWindow()