feat(editor): undoing and redoing
This commit is contained in:
parent
6800ac27f3
commit
b5fc91eea0
11 changed files with 228 additions and 12 deletions
|
@ -2,12 +2,13 @@
|
|||
|
||||
#include "base.h"
|
||||
#include "error/data.h"
|
||||
#include "utils.h"
|
||||
#include <memory>
|
||||
|
||||
class Instance;
|
||||
|
||||
class InstanceRef {
|
||||
std::shared_ptr<Instance> ref;
|
||||
nullable std::shared_ptr<Instance> ref;
|
||||
public:
|
||||
InstanceRef();
|
||||
InstanceRef(std::weak_ptr<Instance>);
|
||||
|
|
10
core/src/utils.h
Normal file
10
core/src/utils.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic ignored "-Wnullability-extension"
|
||||
#define nullable _Nullable
|
||||
#define notnull _Nonnull
|
||||
#else
|
||||
#define nullable
|
||||
#define notnull
|
||||
#endif
|
|
@ -26,6 +26,8 @@ set(PROJECT_SOURCES
|
|||
mainglwidget.cpp
|
||||
placedocument.h
|
||||
placedocument.cpp
|
||||
undohistory.h
|
||||
undohistory.cpp
|
||||
panes/explorerview.h
|
||||
panes/explorerview.cpp
|
||||
panes/explorermodel.h
|
||||
|
|
|
@ -8,10 +8,12 @@
|
|||
#include "objects/service/selection.h"
|
||||
#include "placedocument.h"
|
||||
#include "script/scriptdocument.h"
|
||||
#include "undohistory.h"
|
||||
#include <memory>
|
||||
#include <qclipboard.h>
|
||||
#include <qevent.h>
|
||||
#include <qglobal.h>
|
||||
#include <qkeysequence.h>
|
||||
#include <qmessagebox.h>
|
||||
#include <qmimedata.h>
|
||||
#include <qnamespace.h>
|
||||
|
@ -45,15 +47,6 @@ inline bool isDarkMode() {
|
|||
|
||||
QtMessageHandler defaultMessageHandler = nullptr;
|
||||
|
||||
// std::map<QtMsgType, Logger::LogLevel> QT_MESSAGE_TYPE_TO_LOG_LEVEL = {
|
||||
// { QtMsgType::QtInfoMsg, Logger::LogLevel::INFO },
|
||||
// { QtMsgType::QtSystemMsg, Logger::LogLevel::INFO },
|
||||
// { QtMsgType::QtDebugMsg, Logger::LogLevel::DEBUG },
|
||||
// { QtMsgType::QtWarningMsg, Logger::LogLevel::WARNING },
|
||||
// { QtMsgType::QtCriticalMsg, Logger::LogLevel::ERROR },
|
||||
// { QtMsgType::QtFatalMsg, Logger::LogLevel::FATAL_ERROR },
|
||||
// };
|
||||
|
||||
void logQtMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
|
||||
// Logger::log("[Qt] " + msg.toStdString(), QT_MESSAGE_TYPE_TO_LOG_LEVEL[type]);
|
||||
Logger::LogLevel logLevel = type == QtMsgType::QtFatalMsg ? Logger::LogLevel::FATAL_ERROR : Logger::LogLevel::DEBUG;
|
||||
|
@ -78,6 +71,8 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
ui->setupUi(this);
|
||||
setMouseTracking(true);
|
||||
|
||||
ui->actionRedo->setShortcuts({QKeySequence("Ctrl+Shift+Z"), QKeySequence("Ctrl+Y")});
|
||||
|
||||
QIcon::setThemeSearchPaths(QIcon::themeSearchPaths() + QStringList { "./assets/icons" });
|
||||
if (isDarkMode())
|
||||
QIcon::setFallbackThemeName("editor-dark");
|
||||
|
@ -105,6 +100,7 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
ui->mdiArea->currentSubWindow()->showMaximized();
|
||||
ui->mdiArea->findChild<QTabBar*>()->setExpanding(false);
|
||||
placeDocument->init();
|
||||
ui->propertiesView->init();
|
||||
|
||||
ui->mdiArea->setTabsClosable(true);
|
||||
}
|
||||
|
@ -278,12 +274,16 @@ void MainWindow::connectActionHandlers() {
|
|||
});
|
||||
|
||||
connect(ui->actionDelete, &QAction::triggered, this, [&]() {
|
||||
UndoState historyState;
|
||||
std::shared_ptr<Selection> selection = gDataModel->GetService<Selection>();
|
||||
for (std::weak_ptr<Instance> inst : selection->Get()) {
|
||||
if (inst.expired()) continue;
|
||||
historyState.push_back(UndoStateInstanceRemoved { inst.lock(), inst.lock()->GetParent().value() });
|
||||
inst.lock()->SetParent(std::nullopt);
|
||||
}
|
||||
selection->Set({});
|
||||
historyState.push_back(UndoStateSelectionChanged {selection->Get(), {}});
|
||||
undoManager.PushState(historyState);
|
||||
});
|
||||
|
||||
connect(ui->actionCopy, &QAction::triggered, this, [&]() {
|
||||
|
@ -301,15 +301,20 @@ void MainWindow::connectActionHandlers() {
|
|||
mimeData->setData("application/xml", QByteArray::fromStdString(encoded.str()));
|
||||
QApplication::clipboard()->setMimeData(mimeData);
|
||||
});
|
||||
|
||||
connect(ui->actionCut, &QAction::triggered, this, [&]() {
|
||||
UndoState historyState;
|
||||
pugi::xml_document rootDoc;
|
||||
std::shared_ptr<Selection> selection = gDataModel->GetService<Selection>();
|
||||
for (std::weak_ptr<Instance> inst : selection->Get()) {
|
||||
if (inst.expired()) continue;
|
||||
historyState.push_back(UndoStateInstanceRemoved { inst.lock(), inst.lock()->GetParent().value() });
|
||||
inst.lock()->Serialize(rootDoc);
|
||||
inst.lock()->SetParent(std::nullopt);
|
||||
}
|
||||
selection->Set({});
|
||||
historyState.push_back(UndoStateSelectionChanged {selection->Get(), {}});
|
||||
undoManager.PushState(historyState);
|
||||
|
||||
std::ostringstream encoded;
|
||||
rootDoc.save(encoded);
|
||||
|
@ -320,6 +325,7 @@ void MainWindow::connectActionHandlers() {
|
|||
});
|
||||
|
||||
connect(ui->actionPaste, &QAction::triggered, this, [&]() {
|
||||
UndoState historyState;
|
||||
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
|
||||
if (!mimeData || !mimeData->hasFormat("application/xml")) return;
|
||||
QByteArray bytes = mimeData->data("application/xml");
|
||||
|
@ -331,11 +337,15 @@ void MainWindow::connectActionHandlers() {
|
|||
for (pugi::xml_node instNode : rootDoc.children()) {
|
||||
result<std::shared_ptr<Instance>, NoSuchInstance> inst = Instance::Deserialize(instNode);
|
||||
if (!inst) { inst.logError(); continue; }
|
||||
historyState.push_back(UndoStateInstanceCreated { inst.expect(), gWorkspace() });
|
||||
gWorkspace()->AddChild(inst.expect());
|
||||
}
|
||||
|
||||
undoManager.PushState(historyState);
|
||||
});
|
||||
|
||||
connect(ui->actionPasteInto, &QAction::triggered, this, [&]() {
|
||||
UndoState historyState;
|
||||
std::shared_ptr<Selection> selection = gDataModel->GetService<Selection>();
|
||||
if (selection->Get().size() != 1) return;
|
||||
|
||||
|
@ -352,17 +362,22 @@ void MainWindow::connectActionHandlers() {
|
|||
for (pugi::xml_node instNode : rootDoc.children()) {
|
||||
result<std::shared_ptr<Instance>, NoSuchInstance> inst = Instance::Deserialize(instNode);
|
||||
if (!inst) { inst.logError(); continue; }
|
||||
historyState.push_back(UndoStateInstanceCreated { inst.expect(), selectedParent });
|
||||
selectedParent->AddChild(inst.expect());
|
||||
}
|
||||
|
||||
undoManager.PushState(historyState);
|
||||
});
|
||||
|
||||
connect(ui->actionGroupObjects, &QAction::triggered, this, [&]() {
|
||||
UndoState historyState;
|
||||
auto model = Model::New();
|
||||
std::shared_ptr<Instance> firstParent;
|
||||
|
||||
std::shared_ptr<Selection> selection = gDataModel->GetService<Selection>();
|
||||
for (auto object : selection->Get()) {
|
||||
if (firstParent == nullptr && object->GetParent().has_value()) firstParent = object->GetParent().value();
|
||||
historyState.push_back(UndoStateInstanceReparented { object, object->GetParent().value(), model });
|
||||
object->SetParent(model);
|
||||
}
|
||||
|
||||
|
@ -372,13 +387,16 @@ void MainWindow::connectActionHandlers() {
|
|||
// Technically not how it works in the actual studio, but it's not an API-breaking change
|
||||
// and I think this implementation is more useful so I'm sticking with it
|
||||
if (firstParent == nullptr) firstParent = gWorkspace();
|
||||
historyState.push_back(UndoStateInstanceCreated { model, firstParent });
|
||||
model->SetParent(firstParent);
|
||||
|
||||
historyState.push_back(UndoStateSelectionChanged { selection->Get(), { model } });
|
||||
selection->Set({ model });
|
||||
playSound("./assets/excluded/electronicpingshort.wav");
|
||||
});
|
||||
|
||||
connect(ui->actionUngroupObjects, &QAction::triggered, this, [&]() {
|
||||
UndoState historyState;
|
||||
std::vector<std::shared_ptr<Instance>> newSelection;
|
||||
|
||||
std::shared_ptr<Selection> selection = gDataModel->GetService<Selection>();
|
||||
|
@ -387,13 +405,16 @@ void MainWindow::connectActionHandlers() {
|
|||
if (!model->IsA<Model>()) { newSelection.push_back(model); continue; }
|
||||
|
||||
for (auto object : model->GetChildren()) {
|
||||
historyState.push_back(UndoStateInstanceReparented { object, object->GetParent().value(), model->GetParent().value() });
|
||||
object->SetParent(model->GetParent());
|
||||
newSelection.push_back(object);
|
||||
}
|
||||
|
||||
model->Destroy();
|
||||
historyState.push_back(UndoStateInstanceRemoved { model, model->GetParent().value() });
|
||||
model->SetParent(std::nullopt);
|
||||
}
|
||||
|
||||
historyState.push_back(UndoStateSelectionChanged { selection->Get(), newSelection });
|
||||
selection->Set(newSelection);
|
||||
playSound("./assets/excluded/electronicpingshort.wav");
|
||||
});
|
||||
|
@ -417,6 +438,7 @@ void MainWindow::connectActionHandlers() {
|
|||
});
|
||||
|
||||
connect(ui->actionInsertModel, &QAction::triggered, this, [&]() {
|
||||
UndoState historyState;
|
||||
std::shared_ptr<Selection> selection = gDataModel->GetService<Selection>();
|
||||
if (selection->Get().size() != 1) return;
|
||||
std::shared_ptr<Instance> selectedParent = selection->Get()[0];
|
||||
|
@ -431,10 +453,19 @@ void MainWindow::connectActionHandlers() {
|
|||
for (pugi::xml_node instNode : modelDoc.child("openblocks").children("Item")) {
|
||||
result<std::shared_ptr<Instance>, NoSuchInstance> inst = Instance::Deserialize(instNode);
|
||||
if (!inst) { inst.logError(); continue; }
|
||||
historyState.push_back(UndoStateInstanceCreated { inst.expect(), selectedParent });
|
||||
selectedParent->AddChild(inst.expect());
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->actionUndo, &QAction::triggered, this, [&]() {
|
||||
undoManager.Undo();
|
||||
});
|
||||
|
||||
connect(ui->actionRedo, &QAction::triggered, this, [&]() {
|
||||
undoManager.Redo();
|
||||
});
|
||||
|
||||
connect(ui->actionAbout, &QAction::triggered, this, [this]() {
|
||||
AboutDialog* aboutDialog = new AboutDialog(this);
|
||||
aboutDialog->open();
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "qbasictimer.h"
|
||||
#include "qcoreevent.h"
|
||||
#include "script/scriptdocument.h"
|
||||
#include "undohistory.h"
|
||||
#include <QMainWindow>
|
||||
#include <QLineEdit>
|
||||
#include <map>
|
||||
|
@ -49,6 +50,8 @@ class MainWindow : public QMainWindow
|
|||
public:
|
||||
MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
UndoHistory undoManager;
|
||||
|
||||
SelectedTool selectedTool;
|
||||
GridSnappingMode snappingMode;
|
||||
|
|
|
@ -151,6 +151,8 @@
|
|||
<addaction name="actionNew"/>
|
||||
<addaction name="actionOpen"/>
|
||||
<addaction name="actionSave"/>
|
||||
<addaction name="actionUndo"/>
|
||||
<addaction name="actionRedo"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="transformTools">
|
||||
<property name="windowTitle">
|
||||
|
@ -816,6 +818,34 @@
|
|||
<string>About...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionUndo">
|
||||
<property name="icon">
|
||||
<iconset theme="edit-undo"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Undo</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Z</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionRedo">
|
||||
<property name="icon">
|
||||
<iconset theme="edit-redo"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Redo</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Y</string>
|
||||
</property>
|
||||
<property name="menuRole">
|
||||
<enum>QAction::MenuRole::NoRole</enum>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "objects/meta.h"
|
||||
#include "objects/script.h"
|
||||
#include "objects/service/selection.h"
|
||||
#include "undohistory.h"
|
||||
#include <memory>
|
||||
#include <qaction.h>
|
||||
#include <qtreeview.h>
|
||||
|
@ -116,6 +117,7 @@ void ExplorerView::buildContextMenu() {
|
|||
std::shared_ptr<Instance> instParent = selection->Get()[0];
|
||||
std::shared_ptr<Instance> newInst = type->constructor();
|
||||
newInst->SetParent(instParent);
|
||||
M_mainWindow->undoManager.PushState({ UndoStateInstanceCreated { newInst, instParent } });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
#include "datatypes/variant.h"
|
||||
#include "datatypes/primitives.h"
|
||||
#include "error/data.h"
|
||||
#include "mainwindow.h"
|
||||
#include "objects/base/member.h"
|
||||
#include "undohistory.h"
|
||||
|
||||
#include <QColorDialog>
|
||||
#include <QComboBox>
|
||||
|
@ -206,6 +208,8 @@ public:
|
|||
: view->itemFromIndex(index.parent())->data(0, Qt::DisplayRole).toString().toStdString();
|
||||
PropertyMeta meta = inst->GetPropertyMeta(propertyName).expect();
|
||||
|
||||
Variant oldValue = inst->GetProperty(propertyName).expect();
|
||||
|
||||
if (isComposite) {
|
||||
if (meta.type.descriptor == &Vector3::TYPE) {
|
||||
QDoubleSpinBox* spinBox = dynamic_cast<QDoubleSpinBox*>(editor);
|
||||
|
@ -264,12 +268,15 @@ public:
|
|||
model->setData(index, QString::fromStdString(parsedValue.ToString()));
|
||||
view->rebuildCompositeProperty(view->itemFromIndex(index), meta.type.descriptor, parsedValue);
|
||||
}
|
||||
|
||||
Variant newValue = inst->GetProperty(propertyName).expect();
|
||||
view->undoManager->PushState({ UndoStatePropertyChanged { inst, propertyName, oldValue, newValue } });
|
||||
}
|
||||
};
|
||||
|
||||
PropertiesView::PropertiesView(QWidget* parent):
|
||||
QTreeWidget(parent) {
|
||||
|
||||
|
||||
clear();
|
||||
setHeaderHidden(true);
|
||||
setColumnCount(2);
|
||||
|
@ -300,6 +307,10 @@ QStringList PROPERTY_CATEGORY_NAMES {
|
|||
"Surface Inputs",
|
||||
};
|
||||
|
||||
void PropertiesView::init() {
|
||||
undoManager = &dynamic_cast<MainWindow*>(window())->undoManager;
|
||||
}
|
||||
|
||||
QModelIndex PropertiesView::indexAt(const QPoint &point) const {
|
||||
return QTreeWidget::indexAt(point + QPoint(indentation(), 0));
|
||||
}
|
||||
|
@ -402,6 +413,7 @@ void PropertiesView::propertyChanged(QTreeWidgetItem *item, int column) {
|
|||
|
||||
if (meta.type.descriptor == &BOOL_TYPE) {
|
||||
inst->SetProperty(propertyName, item->checkState(1) == Qt::Checked).expect();
|
||||
undoManager->PushState({ UndoStatePropertyChanged { inst, propertyName, item->checkState(1) != Qt::Checked, item->checkState(1) == Qt::Checked } });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <QTreeWidget>
|
||||
#include "datatypes/base.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "undohistory.h"
|
||||
|
||||
class Ui_MainWindow;
|
||||
class PropertiesItemDelegate;
|
||||
|
@ -18,6 +19,8 @@ class PropertiesView : public QTreeWidget {
|
|||
void rebuildCompositeProperty(QTreeWidgetItem *item, const TypeDesc*, Variant);
|
||||
void onPropertyUpdated(std::shared_ptr<Instance> instance, std::string property, Variant newValue);
|
||||
|
||||
UndoHistory* undoManager;
|
||||
|
||||
friend PropertiesItemDelegate;
|
||||
protected:
|
||||
void drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const override;
|
||||
|
@ -26,5 +29,7 @@ public:
|
|||
PropertiesView(QWidget* parent = nullptr);
|
||||
~PropertiesView() override;
|
||||
|
||||
void init();
|
||||
|
||||
void setSelected(std::optional<std::shared_ptr<Instance>> instance);
|
||||
};
|
67
editor/undohistory.cpp
Normal file
67
editor/undohistory.cpp
Normal file
|
@ -0,0 +1,67 @@
|
|||
#include "undohistory.h"
|
||||
#include "common.h"
|
||||
#include "objects/service/selection.h"
|
||||
|
||||
void UndoHistory::PushState(UndoState state) {
|
||||
if (processingUndo) return; // Ignore PushState requests when changes are initiated by us
|
||||
redoHistory = {};
|
||||
|
||||
if (maxBufferSize != -1 && (int)undoHistory.size() > maxBufferSize)
|
||||
undoHistory.erase(undoHistory.begin(), undoHistory.begin()+maxBufferSize-(int)undoHistory.size()-1);
|
||||
|
||||
undoHistory.push_back(state);
|
||||
}
|
||||
|
||||
void UndoHistory::Undo() {
|
||||
if (undoHistory.size() == 0) return;
|
||||
UndoState state = undoHistory.back();
|
||||
undoHistory.pop_back();
|
||||
redoHistory.push(state);
|
||||
|
||||
processingUndo = true;
|
||||
|
||||
for (UndoStateChange& change : state) {
|
||||
// https://stackoverflow.com/a/63483353
|
||||
if (auto v = std::get_if<UndoStatePropertyChanged>(&change)) {
|
||||
// The old value used to be valid, so it still should be...
|
||||
v->affectedInstance->SetProperty(v->property, v->oldValue).expect();
|
||||
} else if (auto v = std::get_if<UndoStateInstanceCreated>(&change)) {
|
||||
v->instance->SetParent(std::nullopt);
|
||||
} else if (auto v = std::get_if<UndoStateInstanceRemoved>(&change)) {
|
||||
v->instance->SetParent(v->oldParent);
|
||||
} else if (auto v = std::get_if<UndoStateInstanceReparented>(&change)) {
|
||||
v->instance->SetParent(v->oldParent);
|
||||
} else if (auto v = std::get_if<UndoStateSelectionChanged>(&change)) {
|
||||
gDataModel->GetService<Selection>()->Set(v->oldSelection);
|
||||
}
|
||||
}
|
||||
|
||||
processingUndo = false;
|
||||
}
|
||||
|
||||
void UndoHistory::Redo() {
|
||||
if (redoHistory.size() == 0) return;
|
||||
UndoState state = redoHistory.top();
|
||||
redoHistory.pop();
|
||||
undoHistory.push_back(state);
|
||||
|
||||
processingUndo = true;
|
||||
|
||||
for (UndoStateChange& change : state) {
|
||||
// https://stackoverflow.com/a/63483353
|
||||
if (auto v = std::get_if<UndoStatePropertyChanged>(&change)) {
|
||||
// The old value used to be valid, so it still should be...
|
||||
v->affectedInstance->SetProperty(v->property, v->newValue).expect();
|
||||
} else if (auto v = std::get_if<UndoStateInstanceCreated>(&change)) {
|
||||
v->instance->SetParent(v->newParent);
|
||||
} else if (auto v = std::get_if<UndoStateInstanceRemoved>(&change)) {
|
||||
v->instance->SetParent(std::nullopt);
|
||||
} else if (auto v = std::get_if<UndoStateInstanceReparented>(&change)) {
|
||||
v->instance->SetParent(v->newParent);
|
||||
} else if (auto v = std::get_if<UndoStateSelectionChanged>(&change)) {
|
||||
gDataModel->GetService<Selection>()->Set(v->newSelection);
|
||||
}
|
||||
}
|
||||
|
||||
processingUndo = false;
|
||||
}
|
53
editor/undohistory.h
Normal file
53
editor/undohistory.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
#pragma once
|
||||
|
||||
#include "datatypes/signal.h"
|
||||
#include "datatypes/variant.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "utils.h"
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <stack>
|
||||
|
||||
struct UndoStatePropertyChanged {
|
||||
std::shared_ptr<Instance> affectedInstance;
|
||||
std::string property;
|
||||
Variant oldValue;
|
||||
Variant newValue;
|
||||
};
|
||||
|
||||
struct UndoStateInstanceCreated {
|
||||
std::shared_ptr<Instance> instance;
|
||||
std::shared_ptr<Instance> newParent;
|
||||
};
|
||||
|
||||
struct UndoStateInstanceRemoved {
|
||||
std::shared_ptr<Instance> instance;
|
||||
std::shared_ptr<Instance> oldParent;
|
||||
};
|
||||
|
||||
struct UndoStateInstanceReparented {
|
||||
std::shared_ptr<Instance> instance;
|
||||
nullable std::shared_ptr<Instance> oldParent;
|
||||
nullable std::shared_ptr<Instance> newParent;
|
||||
};
|
||||
|
||||
struct UndoStateSelectionChanged {
|
||||
std::vector<std::shared_ptr<Instance>> oldSelection;
|
||||
std::vector<std::shared_ptr<Instance>> newSelection;
|
||||
};
|
||||
|
||||
typedef std::variant<UndoStatePropertyChanged, UndoStateInstanceCreated, UndoStateInstanceRemoved, UndoStateInstanceReparented, UndoStateSelectionChanged> UndoStateChange;
|
||||
typedef std::vector<UndoStateChange> UndoState;
|
||||
|
||||
class UndoHistory {
|
||||
// Ignore PushState requests
|
||||
bool processingUndo = false;
|
||||
std::deque<UndoState> undoHistory;
|
||||
std::stack<UndoState> redoHistory;
|
||||
public:
|
||||
int maxBufferSize = 100;
|
||||
|
||||
void PushState(UndoState);
|
||||
void Undo();
|
||||
void Redo();
|
||||
};
|
Loading…
Add table
Reference in a new issue