diff --git a/src/skeletondocument.cpp b/src/skeletondocument.cpp index fbf60e19..fc1a5372 100644 --- a/src/skeletondocument.cpp +++ b/src/skeletondocument.cpp @@ -5,6 +5,8 @@ #include "skeletondocument.h" #include "util.h" +unsigned long SkeletonDocument::m_maxSnapshot = 1000; + SkeletonDocument::SkeletonDocument() : m_resultMeshIsObsolete(false), m_resultMesh(nullptr), @@ -33,7 +35,7 @@ void SkeletonDocument::removeEdge(QUuid edgeId) qDebug() << "Find edge failed:" << edgeId; return; } - if (isPartLocked(edge->partId)) + if (isPartReadonly(edge->partId)) return; const SkeletonPart *oldPart = findPart(edge->partId); if (nullptr == oldPart) { @@ -94,7 +96,7 @@ void SkeletonDocument::removeNode(QUuid nodeId) qDebug() << "Find node failed:" << nodeId; return; } - if (isPartLocked(node->partId)) + if (isPartReadonly(node->partId)) return; const SkeletonPart *oldPart = findPart(node->partId); if (nullptr == oldPart) { @@ -174,7 +176,7 @@ void SkeletonDocument::addNode(float x, float y, float z, float radius, QUuid fr return; } partId = fromNode->partId; - if (isPartLocked(partId)) + if (isPartReadonly(partId)) return; } SkeletonNode node; @@ -243,7 +245,7 @@ void SkeletonDocument::addEdge(QUuid fromNodeId, QUuid toNodeId) return; } - if (isPartLocked(fromNode->partId)) + if (isPartReadonly(fromNode->partId)) return; toNode = findNode(toNodeId); @@ -252,7 +254,7 @@ void SkeletonDocument::addEdge(QUuid fromNodeId, QUuid toNodeId) return; } - if (isPartLocked(toNode->partId)) + if (isPartReadonly(toNode->partId)) return; QUuid toPartId = toNode->partId; @@ -339,21 +341,21 @@ void SkeletonDocument::scaleNodeByAddRadius(QUuid nodeId, float amount) qDebug() << "Find node failed:" << nodeId; return; } - if (isPartLocked(it->second.partId)) + if (isPartReadonly(it->second.partId)) return; it->second.radius += amount; emit nodeRadiusChanged(nodeId); emit skeletonChanged(); } -bool SkeletonDocument::isPartLocked(QUuid partId) +bool SkeletonDocument::isPartReadonly(QUuid partId) { const SkeletonPart *part = findPart(partId); if (nullptr == part) { qDebug() << "Find part failed:" << partId; return true; } - return part->locked; + return part->locked || !part->visible; } void SkeletonDocument::moveNodeBy(QUuid nodeId, float x, float y, float z) @@ -363,7 +365,7 @@ void SkeletonDocument::moveNodeBy(QUuid nodeId, float x, float y, float z) qDebug() << "Find node failed:" << nodeId; return; } - if (isPartLocked(it->second.partId)) + if (isPartReadonly(it->second.partId)) return; it->second.x += x; it->second.y += y; @@ -379,7 +381,7 @@ void SkeletonDocument::setNodeOrigin(QUuid nodeId, float x, float y, float z) qDebug() << "Find node failed:" << nodeId; return; } - if (isPartLocked(it->second.partId)) + if (isPartReadonly(it->second.partId)) return; it->second.x = x; it->second.y = y; @@ -395,7 +397,7 @@ void SkeletonDocument::setNodeRadius(QUuid nodeId, float radius) qDebug() << "Find node failed:" << nodeId; return; } - if (isPartLocked(it->second.partId)) + if (isPartReadonly(it->second.partId)) return; it->second.radius = radius; emit nodeRadiusChanged(nodeId); @@ -555,6 +557,8 @@ void SkeletonDocument::fromSnapshot(const SkeletonSnapshot &snapshot) nodeMap.clear(); edgeMap.clear(); partMap.clear(); + partIds.clear(); + emit partListChanged(); for (const auto &nodeKv : snapshot.nodes) { SkeletonNode node(QUuid(nodeKv.first)); @@ -603,6 +607,7 @@ void SkeletonDocument::fromSnapshot(const SkeletonSnapshot &snapshot) } emit partListChanged(); + emit skeletonChanged(); } const char *SkeletonNodeRootMarkModeToString(SkeletonNodeRootMarkMode mode) @@ -772,3 +777,33 @@ void SkeletonDocument::setPartSubdivState(QUuid partId, bool subdived) emit skeletonChanged(); } +void SkeletonDocument::saveSnapshot() +{ + if (m_undoItems.size() + 1 > m_maxSnapshot) + m_undoItems.pop_front(); + SkeletonHistoryItem item; + toSnapshot(&item.snapshot); + m_undoItems.push_back(item); + qDebug() << "Undo items:" << m_undoItems.size(); +} + +void SkeletonDocument::undo() +{ + if (m_undoItems.empty()) + return; + m_redoItems.push_back(m_undoItems.back()); + fromSnapshot(m_undoItems.back().snapshot); + m_undoItems.pop_back(); + qDebug() << "Undo/Redo items:" << m_undoItems.size() << m_redoItems.size(); +} + +void SkeletonDocument::redo() +{ + if (m_redoItems.empty()) + return; + m_undoItems.push_back(m_redoItems.back()); + fromSnapshot(m_redoItems.back().snapshot); + m_redoItems.pop_back(); + qDebug() << "Undo/Redo items:" << m_undoItems.size() << m_redoItems.size(); +} + diff --git a/src/skeletondocument.h b/src/skeletondocument.h index 75e0209b..f87e6f8c 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "skeletonsnapshot.h" #include "mesh.h" @@ -148,7 +149,6 @@ public: std::map partMap; std::map nodeMap; std::map edgeMap; - std::vector historyItems; std::vector partIds; QImage turnaround; SkeletonDocumentEditMode editMode; @@ -180,15 +180,21 @@ public slots: void setPartLockState(QUuid partId, bool locked); void setPartVisibleState(QUuid partId, bool visible); void setPartSubdivState(QUuid partId, bool subdived); + void saveSnapshot(); + void undo(); + void redo(); private: void splitPartByNode(std::vector> *groups, QUuid nodeId); void joinNodeAndNeiborsToGroup(std::vector *group, QUuid nodeId, std::set *visitMap, QUuid noUseEdgeId=QUuid()); void splitPartByEdge(std::vector> *groups, QUuid edgeId); - bool isPartLocked(QUuid partId); + bool isPartReadonly(QUuid partId); private: bool m_resultMeshIsObsolete; MeshGenerator *m_meshGenerator; Mesh *m_resultMesh; + static unsigned long m_maxSnapshot; + std::deque m_undoItems; + std::deque m_redoItems; }; #endif diff --git a/src/skeletondocumentwindow.cpp b/src/skeletondocumentwindow.cpp index 6210acfa..93161842 100644 --- a/src/skeletondocumentwindow.cpp +++ b/src/skeletondocumentwindow.cpp @@ -157,6 +157,10 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(changeTurnaroundButton, &QPushButton::clicked, this, &SkeletonDocumentWindow::changeTurnaround); + connect(undoButton, &QPushButton::clicked, [=]() { + m_document->undo(); + }); + connect(addButton, &QPushButton::clicked, [=]() { m_document->setEditMode(SkeletonDocumentEditMode::Add); }); @@ -186,6 +190,9 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(graphicsWidget, &SkeletonGraphicsWidget::setEditMode, m_document, &SkeletonDocument::setEditMode); connect(graphicsWidget, &SkeletonGraphicsWidget::removeEdge, m_document, &SkeletonDocument::removeEdge); connect(graphicsWidget, &SkeletonGraphicsWidget::addEdge, m_document, &SkeletonDocument::addEdge); + connect(graphicsWidget, &SkeletonGraphicsWidget::groupOperationAdded, m_document, &SkeletonDocument::saveSnapshot); + connect(graphicsWidget, &SkeletonGraphicsWidget::undo, m_document, &SkeletonDocument::undo); + connect(graphicsWidget, &SkeletonGraphicsWidget::redo, m_document, &SkeletonDocument::redo); connect(m_document, &SkeletonDocument::nodeAdded, graphicsWidget, &SkeletonGraphicsWidget::nodeAdded); connect(m_document, &SkeletonDocument::nodeRemoved, graphicsWidget, &SkeletonGraphicsWidget::nodeRemoved); diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index d7f4024c..fa21596a 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -359,10 +359,12 @@ bool SkeletonGraphicsWidget::wheel(QWheelEvent *event) emit scaleNodeByAddRadius(nodeItem->id(), unifiedDelta); } } + emit groupOperationAdded(); return true; } else if (m_hoveredNodeItem) { float unifiedDelta = sceneRadiusToUnified(delta); emit scaleNodeByAddRadius(m_hoveredNodeItem->id(), unifiedDelta); + emit groupOperationAdded(); return true; } } @@ -379,6 +381,7 @@ bool SkeletonGraphicsWidget::mouseRelease(QMouseEvent *event) } if (m_moveStarted) { m_moveStarted = false; + emit groupOperationAdded(); } if (m_rangeSelectionStarted) { m_selectionItem->hide(); @@ -428,6 +431,7 @@ bool SkeletonGraphicsWidget::mousePress(QMouseEvent *event) if (m_document->findEdgeByNodes(m_addFromNodeItem->id(), m_hoveredNodeItem->id())) return true; emit addEdge(m_addFromNodeItem->id(), m_hoveredNodeItem->id()); + emit groupOperationAdded(); return true; } } @@ -452,6 +456,7 @@ bool SkeletonGraphicsWidget::mousePress(QMouseEvent *event) m_lastAddedZ = unifiedSidePos.x(); qDebug() << "Emit add node " << m_lastAddedX << m_lastAddedY << m_lastAddedZ; emit addNode(unifiedMainPos.x(), unifiedMainPos.y(), unifiedSidePos.x(), sceneRadiusToUnified(m_cursorNodeItem->radius()), nullptr == m_addFromNodeItem ? QUuid() : m_addFromNodeItem->id()); + emit groupOperationAdded(); return true; } } else if (SkeletonDocumentEditMode::Select == m_document->editMode) { @@ -533,6 +538,7 @@ bool SkeletonGraphicsWidget::keyPress(QKeyEvent *event) } } if (processed) { + emit groupOperationAdded(); return true; } } else if (event->key() == Qt::Key_A) { @@ -542,6 +548,20 @@ bool SkeletonGraphicsWidget::keyPress(QKeyEvent *event) emit setEditMode(SkeletonDocumentEditMode::Add); } return true; + } else if (event->key() == Qt::Key_Z) { + if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier)) { + if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) { + emit redo(); + } else { + emit undo(); + } + } + } else if (event->key() == Qt::Key_Y) { + if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier)) { + if (!QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) { + emit redo(); + } + } } return false; } diff --git a/src/skeletongraphicswidget.h b/src/skeletongraphicswidget.h index 1d02c5c9..21a07eba 100644 --- a/src/skeletongraphicswidget.h +++ b/src/skeletongraphicswidget.h @@ -259,6 +259,9 @@ signals: void removeEdge(QUuid edgeId); void addEdge(QUuid fromNodeId, QUuid toNodeId); void cursorChanged(); + void groupOperationAdded(); + void undo(); + void redo(); public: SkeletonGraphicsWidget(const SkeletonDocument *document); std::map> nodeItemMap;