166 lines
4.1 KiB
C++
166 lines
4.1 KiB
C++
#include "Core/UndoManager.h"
|
|
|
|
#include "Core/ISelectionManager.h"
|
|
#include "Managers/SceneManager.h"
|
|
|
|
#include <algorithm>
|
|
|
|
namespace XCEngine {
|
|
namespace Editor {
|
|
|
|
namespace {
|
|
|
|
constexpr size_t kMaxUndoHistory = 128;
|
|
|
|
} // namespace
|
|
|
|
UndoManager::UndoManager(SceneManager& sceneManager, ISelectionManager& selectionManager)
|
|
: m_sceneManager(sceneManager)
|
|
, m_selectionManager(selectionManager) {}
|
|
|
|
void UndoManager::ClearHistory() {
|
|
m_history.clear();
|
|
m_nextIndex = 0;
|
|
m_pendingInteractiveChange.reset();
|
|
}
|
|
|
|
bool UndoManager::CanUndo() const {
|
|
return m_nextIndex > 0;
|
|
}
|
|
|
|
bool UndoManager::CanRedo() const {
|
|
return m_nextIndex < m_history.size();
|
|
}
|
|
|
|
const std::string& UndoManager::GetUndoLabel() const {
|
|
return CanUndo() ? m_history[m_nextIndex - 1].label : m_emptyLabel;
|
|
}
|
|
|
|
const std::string& UndoManager::GetRedoLabel() const {
|
|
return CanRedo() ? m_history[m_nextIndex].label : m_emptyLabel;
|
|
}
|
|
|
|
void UndoManager::Undo() {
|
|
if (HasPendingInteractiveChange()) {
|
|
FinalizeInteractiveChange();
|
|
}
|
|
|
|
if (!CanUndo()) {
|
|
return;
|
|
}
|
|
|
|
--m_nextIndex;
|
|
ApplyState(m_history[m_nextIndex].before);
|
|
}
|
|
|
|
void UndoManager::Redo() {
|
|
if (HasPendingInteractiveChange()) {
|
|
FinalizeInteractiveChange();
|
|
}
|
|
|
|
if (!CanRedo()) {
|
|
return;
|
|
}
|
|
|
|
ApplyState(m_history[m_nextIndex].after);
|
|
++m_nextIndex;
|
|
}
|
|
|
|
UndoStateSnapshot UndoManager::CaptureCurrentState() const {
|
|
UndoStateSnapshot snapshot;
|
|
snapshot.scene = m_sceneManager.CaptureSceneSnapshot();
|
|
|
|
if (!snapshot.scene.hasScene) {
|
|
return snapshot;
|
|
}
|
|
|
|
for (uint64_t entityId : m_selectionManager.GetSelectedEntities()) {
|
|
if (m_sceneManager.GetEntity(entityId)) {
|
|
snapshot.selectionIds.push_back(entityId);
|
|
}
|
|
}
|
|
|
|
return snapshot;
|
|
}
|
|
|
|
void UndoManager::PushCommand(const std::string& label, UndoStateSnapshot before, UndoStateSnapshot after) {
|
|
if (AreStatesEqual(before, after)) {
|
|
return;
|
|
}
|
|
|
|
if (m_nextIndex < m_history.size()) {
|
|
m_history.erase(m_history.begin() + static_cast<std::ptrdiff_t>(m_nextIndex), m_history.end());
|
|
}
|
|
|
|
m_history.push_back(CommandEntry{ label, std::move(before), std::move(after) });
|
|
if (m_history.size() > kMaxUndoHistory) {
|
|
m_history.erase(m_history.begin());
|
|
if (m_nextIndex > 0) {
|
|
--m_nextIndex;
|
|
}
|
|
}
|
|
|
|
m_nextIndex = m_history.size();
|
|
}
|
|
|
|
void UndoManager::BeginInteractiveChange(const std::string& label) {
|
|
if (m_pendingInteractiveChange.has_value()) {
|
|
return;
|
|
}
|
|
|
|
m_pendingInteractiveChange = PendingInteractiveChange{ label, CaptureCurrentState() };
|
|
}
|
|
|
|
bool UndoManager::HasPendingInteractiveChange() const {
|
|
return m_pendingInteractiveChange.has_value();
|
|
}
|
|
|
|
void UndoManager::FinalizeInteractiveChange() {
|
|
if (!m_pendingInteractiveChange.has_value()) {
|
|
return;
|
|
}
|
|
|
|
PushCommand(
|
|
m_pendingInteractiveChange->label,
|
|
std::move(m_pendingInteractiveChange->before),
|
|
CaptureCurrentState());
|
|
m_pendingInteractiveChange.reset();
|
|
}
|
|
|
|
void UndoManager::CancelInteractiveChange() {
|
|
m_pendingInteractiveChange.reset();
|
|
}
|
|
|
|
bool UndoManager::ApplyState(const UndoStateSnapshot& state) {
|
|
if (!m_sceneManager.RestoreSceneSnapshot(state.scene)) {
|
|
return false;
|
|
}
|
|
|
|
std::vector<uint64_t> validSelection;
|
|
validSelection.reserve(state.selectionIds.size());
|
|
for (uint64_t entityId : state.selectionIds) {
|
|
if (m_sceneManager.GetEntity(entityId)) {
|
|
validSelection.push_back(entityId);
|
|
}
|
|
}
|
|
|
|
if (validSelection.empty()) {
|
|
m_selectionManager.ClearSelection();
|
|
} else {
|
|
m_selectionManager.SetSelectedEntities(validSelection);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UndoManager::AreStatesEqual(const UndoStateSnapshot& lhs, const UndoStateSnapshot& rhs) {
|
|
return lhs.scene.hasScene == rhs.scene.hasScene &&
|
|
lhs.scene.scenePath == rhs.scene.scenePath &&
|
|
lhs.scene.sceneData == rhs.scene.sceneData &&
|
|
lhs.scene.dirty == rhs.scene.dirty &&
|
|
lhs.selectionIds == rhs.selectionIds;
|
|
}
|
|
|
|
} // namespace Editor
|
|
} // namespace XCEngine
|