From cba5c3abc9bfac44b72ba42b728c35fc4b31a2f1 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Sat, 3 Jan 2026 19:05:00 +0300 Subject: [PATCH] Started work on layers undo/redo, and implemented undo action for moving bookmark list items --- .../NetworkingObjects/NetObjOrderedList.hpp | 22 +++- src/Bookmarks/BookmarkManager.cpp | 108 +++++++++++++++++- .../CanvasComponentContainer.cpp | 4 +- .../CanvasComponentContainer.hpp | 3 +- .../Layers/DrawingProgramLayer.cpp | 7 -- .../Layers/DrawingProgramLayer.hpp | 6 +- .../Layers/DrawingProgramLayerFolder.hpp | 4 - .../Layers/DrawingProgramLayerListItem.cpp | 48 ++++++++ .../Layers/DrawingProgramLayerListItem.hpp | 14 ++- src/MainProgram.hpp | 2 +- src/WorldUndoManager.cpp | 10 ++ src/WorldUndoManager.hpp | 1 + 12 files changed, 199 insertions(+), 30 deletions(-) diff --git a/include/Helpers/NetworkingObjects/NetObjOrderedList.hpp b/include/Helpers/NetworkingObjects/NetObjOrderedList.hpp index 4b3456c..56be6f0 100644 --- a/include/Helpers/NetworkingObjects/NetObjOrderedList.hpp +++ b/include/Helpers/NetworkingObjects/NetObjOrderedList.hpp @@ -125,11 +125,11 @@ namespace NetworkingObjects { std::vector> toRet(orderedIndices.size()); auto lastIt = data.end(); uint32_t lastDataIndex = data.size(); - for(std::pair&> p : std::views::zip(orderedIndices, toRet)) { - uint32_t orderedIndex = std::min(p.first, data.size()); + for(std::pair&> p : std::views::zip(orderedIndices, toRet) | std::views::reverse) { + uint32_t orderedIndex = std::min(p.first, data.size()); NetObjOrderedListIterator& it = p.second; for(;;) { - if(lastDataIndex >= orderedIndex) { + if(orderedIndex == lastDataIndex) { it = lastIt; break; } @@ -145,6 +145,12 @@ namespace NetworkingObjects { return data_list().end(); return it->second; } + std::vector> get_list(const std::vector& ids) { + std::vector> toRet; + for(auto& id : ids) + toRet.emplace_back(get(id)); + return toRet; + } NetObjOrderedListConstIterator begin() const { return data_list().begin(); } @@ -162,8 +168,8 @@ namespace NetworkingObjects { std::vector> toRet(orderedIndices.size()); auto lastIt = data.end(); uint32_t lastDataIndex = data.size(); - for(std::pair&> p : std::views::zip(orderedIndices, toRet)) { - uint32_t orderedIndex = std::min(p.first, data.size()); + for(std::pair&> p : std::views::zip(orderedIndices, toRet)) { + uint32_t orderedIndex = std::min(p.first, data.size()); NetObjOrderedListConstIterator& it = p.second; for(;;) { if(lastDataIndex >= orderedIndex) { @@ -182,6 +188,12 @@ namespace NetworkingObjects { return data_list().end(); return it->second; } + std::vector> get_list(const std::vector& ids) const { + std::vector> toRet; + for(auto& id : ids) + toRet.emplace_back(get(id)); + return toRet; + } bool empty() const { return data_list().empty(); } diff --git a/src/Bookmarks/BookmarkManager.cpp b/src/Bookmarks/BookmarkManager.cpp index 4584a17..7aaaa4c 100644 --- a/src/Bookmarks/BookmarkManager.cpp +++ b/src/Bookmarks/BookmarkManager.cpp @@ -89,12 +89,18 @@ void BookmarkManager::setup_list_gui(const std::string& id) { return isButtonClicked; }, .moveObjectsToListAtIndex = [&](NetObjID listObj, size_t index, const std::vector& objsToInsert) { + std::unordered_map, std::vector>> toEraseMapUndo; + WorldUndoManager::UndoObjectID insertParentUndoID; + uint32_t insertUndoPosition; + std::vector insertedUndoIDs; + world.netObjMan.send_multi_update_messsage([&]() { std::unordered_map>> toEraseMap; uint32_t newIndex = index; std::vector> toInsertObjPtrs; auto& listPtr = world.netObjMan.get_obj_temporary_ref_from_id(listObj)->get_folder_list(); + // Map parent folders to list of objects that should be erased from them for(size_t i = 0; i < objsToInsert.size(); i++) { auto& parentListPtr = world.netObjMan.get_obj_temporary_ref_from_id(objsToInsert[i].parent)->get_folder_list(); toEraseMap[objsToInsert[i].parent].emplace_back(parentListPtr->get(objsToInsert[i].object)); @@ -102,11 +108,24 @@ void BookmarkManager::setup_list_gui(const std::string& id) { newIndex--; } + // Prepare erase map for undo + for(auto& [parentNetID, objsToEraseList] : toEraseMap) { + auto& undoEraseListPairForParent = toEraseMapUndo[world.undo.get_undoid_from_netid(parentNetID)]; + uint32_t i = 0; + for(auto& objToErase : objsToEraseList) { + undoEraseListPairForParent.first.emplace_back(objToErase->pos - i); + undoEraseListPairForParent.second.emplace_back(world.undo.get_undoid_from_netid(objToErase->obj.get_net_id())); + i++; + } + } + + // Erase the list of objects we previously mapped to each parent, and move the erased objects to vector toInsertObjPtrs for(auto& [listToEraseFrom, setToErase] : toEraseMap) { auto& listToEraseFromPtr = world.netObjMan.get_obj_temporary_ref_from_id(listToEraseFrom)->get_folder_list(); listToEraseFromPtr->erase_list(listToEraseFromPtr, setToErase, &toInsertObjPtrs); } + // Insert objects in toInsertObjPtrs into the folder we have selected std::vector, NetObjOwnerPtr>> toInsert; auto insertIt = listPtr->at(newIndex); for(uint32_t i = 0; i < objsToInsert.size(); i++) { @@ -116,8 +135,95 @@ void BookmarkManager::setup_list_gui(const std::string& id) { toInsert.emplace_back(insertIt, std::move(*it)); toInsert.back().second.reassign_ids(); } - listPtr->insert_ordered_list_and_send_create(listPtr, toInsert); + std::vector> insertedIterators = listPtr->insert_ordered_list_and_send_create(listPtr, toInsert); + + // Prepare insert list for undo + insertParentUndoID = world.undo.get_undoid_from_netid(listObj); + insertUndoPosition = newIndex; + for(auto& it : insertedIterators) + insertedUndoIDs.emplace_back(world.undo.get_undoid_from_netid(it->obj.get_net_id())); }, NetObjManager::SendUpdateType::SEND_TO_ALL, nullptr); + + class MoveBookmarksWorldUndoAction : public WorldUndoAction { + public: + // If the undo ID maps to the net id, we can also assume that the object is definitely inside the list we want to move it from, and not inside some other list + // If it was moved to another list, there would be an undo move that would move it back to this list, and if it was moved by another player the undo id wouldnt map back to the net id, and the undo would fail + // We can also assume all objects in all lists remain in the same order for the same reason, so no need to sort anything if it was sorted when the undo action was generated + MoveBookmarksWorldUndoAction(const std::unordered_map, std::vector>>& initUndoEraseMap, + WorldUndoManager::UndoObjectID initInsertParentUndoID, + uint32_t initInsertPosition, + std::vector initInsertedUndoIDs): + undoEraseMap(initUndoEraseMap), + insertParentUndoID(initInsertParentUndoID), + insertPosition(initInsertPosition), + insertedUndoIDs(initInsertedUndoIDs) + {} + std::string get_name() const override { + return "Move Bookmarks"; + } + bool undo(WorldUndoManager& undoMan) override { + std::vector> toMoveObjs; + std::vector> toEraseList; + NetObjTemporaryPtr> eraseListPtr; + std::unordered_map*, std::vector>> toInsertToMap; + + { + std::optional eraseListNetID = undoMan.get_netid_from_undoid(insertParentUndoID); + if(!eraseListNetID.has_value()) + return false; + eraseListPtr = undoMan.world.netObjMan.get_obj_temporary_ref_from_id(eraseListNetID.value())->get_folder_list(); + } + + { + std::vector toEraseNetIDList; + if(!undoMan.fill_netid_list_from_undoid_list(toEraseNetIDList, insertedUndoIDs)) + return false; + toEraseList = eraseListPtr->get_list(toEraseNetIDList); + } + + { + for(auto& [undoID, indexUndoIdListPair] : undoEraseMap) { + std::optional netID = undoMan.get_netid_from_undoid(undoID); + if(!netID.has_value()) + return false; + auto& insertMapEntry = toInsertToMap[netID.value()]; + insertMapEntry.first = &indexUndoIdListPair.first; + if(!undoMan.fill_netid_list_from_undoid_list(insertMapEntry.second, indexUndoIdListPair.second)) + return false; + } + } + + undoMan.world.netObjMan.send_multi_update_messsage([&]() { + eraseListPtr->erase_list(eraseListPtr, toEraseList, &toMoveObjs); + + for(auto& [parentNetID, indexNetIDListPair] : toInsertToMap) { + auto& parentListPtr = undoMan.world.netObjMan.get_obj_temporary_ref_from_id(parentNetID)->get_folder_list(); + std::vector> insertIteratorList = parentListPtr->at_ordered_indices(*indexNetIDListPair.first); + auto& insertNetIDList = indexNetIDListPair.second; + std::vector, NetObjOwnerPtr>> toInsert; + for(auto [insertIt, netID] : std::views::zip(insertIteratorList, insertNetIDList)) { + auto it = std::find_if(toMoveObjs.begin(), toMoveObjs.end(), [id = netID](NetObjOwnerPtr& objPtr) { + return objPtr.get_net_id() == id; + }); + toInsert.emplace_back(insertIt, std::move(*it)); + toInsert.back().second.reassign_ids(); + } + parentListPtr->insert_ordered_list_and_send_create(parentListPtr, toInsert); + } + }, NetObjManager::SendUpdateType::SEND_TO_ALL, nullptr); + return true; + } + bool redo(WorldUndoManager& undoMan) override { + return false; + } + ~MoveBookmarksWorldUndoAction() {} + + std::unordered_map, std::vector>> undoEraseMap; + WorldUndoManager::UndoObjectID insertParentUndoID; + uint32_t insertPosition; + std::vector insertedUndoIDs; + }; + world.undo.push(std::make_unique(toEraseMapUndo, insertParentUndoID, insertUndoPosition, insertedUndoIDs)); } }, selectionData); diff --git a/src/CanvasComponents/CanvasComponentContainer.cpp b/src/CanvasComponents/CanvasComponentContainer.cpp index beb579b..77a8496 100644 --- a/src/CanvasComponents/CanvasComponentContainer.cpp +++ b/src/CanvasComponents/CanvasComponentContainer.cpp @@ -41,8 +41,8 @@ void CanvasComponentContainer::register_class(World& w) { }); } -std::shared_ptr CanvasComponentContainer::get_data_copy() const { - auto toRet = std::make_shared(); +std::unique_ptr CanvasComponentContainer::get_data_copy() const { + auto toRet = std::make_unique(); toRet->coords = coords; toRet->obj = compAllocator->comp->get_data_copy(); return toRet; diff --git a/src/CanvasComponents/CanvasComponentContainer.hpp b/src/CanvasComponents/CanvasComponentContainer.hpp index 792583c..e09cd35 100644 --- a/src/CanvasComponents/CanvasComponentContainer.hpp +++ b/src/CanvasComponents/CanvasComponentContainer.hpp @@ -6,6 +6,7 @@ #include #include #include "CanvasComponentAllocator.hpp" +#include "../WorldUndoManager.hpp" class DrawingProgram; class DrawingProgramLayerListItem; @@ -36,7 +37,7 @@ class CanvasComponentContainer { CanvasComponentContainer(NetworkingObjects::NetObjManager& objMan, const CopyData& copyData); static void register_class(World& w); - std::shared_ptr get_data_copy() const; + std::unique_ptr get_data_copy() const; void save_file(cereal::PortableBinaryOutputArchive& a) const; void load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version, NetworkingObjects::NetObjManager& objMan); CanvasComponent& get_comp() const; diff --git a/src/DrawingProgram/Layers/DrawingProgramLayer.cpp b/src/DrawingProgram/Layers/DrawingProgramLayer.cpp index 919837d..d1426fd 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayer.cpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayer.cpp @@ -9,13 +9,6 @@ void DrawingProgramLayer::draw(SkCanvas* canvas, const DrawData& drawData) const p.obj->draw(canvas, drawData); } -DrawingProgramLayerInitData DrawingProgramLayer::get_init_data() const { - DrawingProgramLayerInitData toRet; - for(auto& p : *components) - toRet.components.emplace_back(p.obj->get_data_copy()); - return toRet; -} - void DrawingProgramLayer::set_component_list_callbacks(DrawingProgramLayerListItem& layerListItem, DrawingProgramLayerManager& layerMan) { auto insertCallback = [&](const CanvasComponentContainer::ObjInfoIterator& c) { c->obj->objInfo = c; diff --git a/src/DrawingProgram/Layers/DrawingProgramLayer.hpp b/src/DrawingProgram/Layers/DrawingProgramLayer.hpp index ee1b217..ea60377 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayer.hpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayer.hpp @@ -6,8 +6,9 @@ class DrawingProgramLayerListItem; class DrawingProgramLayerManager; -struct DrawingProgramLayerInitData { - std::vector> components; +struct DrawingProgramComponentUndoData { + WorldUndoManager::UndoObjectID undoID; + std::unique_ptr copyData; }; class DrawingProgramLayer { @@ -17,7 +18,6 @@ class DrawingProgramLayer { void get_flattened_component_list(std::vector& objList) const; void set_to_erase(); void scale_up(const WorldScalar& scaleUpAmount); - DrawingProgramLayerInitData get_init_data() const; CanvasComponentContainer::NetListOwnerPtr components; diff --git a/src/DrawingProgram/Layers/DrawingProgramLayerFolder.hpp b/src/DrawingProgram/Layers/DrawingProgramLayerFolder.hpp index 8ccc89d..06ecfbd 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayerFolder.hpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayerFolder.hpp @@ -7,10 +7,6 @@ class DrawingProgramLayerListItem; class DrawingProgramLayerManager; -struct DrawingProgramLayerFolderInitData { - std::vector folderList; -}; - class DrawingProgramLayerFolder { public: void draw(SkCanvas* canvas, const DrawData& drawData) const; diff --git a/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp b/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp index 1053ce0..3910d64 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp @@ -23,6 +23,54 @@ DrawingProgramLayerListItem::DrawingProgramLayerListItem(NetworkingObjects::NetO displayData = netObjMan.make_obj(); } +DrawingProgramLayerListItem::DrawingProgramLayerListItem(World& w, const DrawingProgramLayerListItemUndoData& initData) { + if(initData.folderData) { + auto& initFolderList = initData.folderData.value(); + folderData = std::make_unique(); + folderData->folderList = w.netObjMan.make_obj>(); + for(auto& initFolder : initFolderList) { + auto it = folderData->folderList->emplace_back_direct(folderData->folderList, w, initFolder); + w.undo.register_new_netid_to_existing_undoid(initFolder.undoID, it->obj.get_net_id()); + } + } + else { + auto& initComponentList = initData.layerData.value(); + layerData = std::make_unique(); + layerData->components = w.netObjMan.make_obj(); + for(auto& initComponent : initComponentList) { + auto it = layerData->components->emplace_back_direct(layerData->components, w.netObjMan, *initComponent.copyData); + w.undo.register_new_netid_to_existing_undoid(initComponent.undoID, it->obj.get_net_id()); + } + } + nameData = w.netObjMan.make_obj(); + nameData->name = initData.metaInfo.name; + displayData = w.netObjMan.make_obj(); + displayData->alpha = initData.metaInfo.alpha; + displayData->blendMode = initData.metaInfo.blendMode; +} + +DrawingProgramLayerListItemUndoData DrawingProgramLayerListItem::get_undo_data(WorldUndoManager& u) const { + DrawingProgramLayerListItemUndoData toRet; + toRet.metaInfo.name = nameData->name; + toRet.metaInfo.blendMode = displayData->blendMode; + toRet.metaInfo.alpha = displayData->alpha; + if(folderData) { + toRet.folderData = std::vector(); + auto& folderListToRet = toRet.folderData.value(); + for(auto& f : *folderData->folderList) { + folderListToRet.emplace_back(f.obj->get_undo_data(u)); + folderListToRet.back().undoID = u.get_undoid_from_netid(f.obj.get_net_id()); + } + } + else { + toRet.layerData = std::vector(); + auto& layerListToRet = toRet.layerData.value(); + for(auto& c : *layerData->components) + layerListToRet.emplace_back(u.get_undoid_from_netid(c.obj.get_net_id()), c.obj->get_data_copy()); + } + return toRet; +} + void DrawingProgramLayerListItem::reassign_netobj_ids_call() { if(folderData) folderData->folderList.reassign_ids(); diff --git a/src/DrawingProgram/Layers/DrawingProgramLayerListItem.hpp b/src/DrawingProgram/Layers/DrawingProgramLayerListItem.hpp index bcc60a3..be422ec 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayerListItem.hpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayerListItem.hpp @@ -13,23 +13,25 @@ class DrawingProgramLayer; class World; class DrawingProgramLayerManager; -struct DrawingProgramLayerListItemMetaInfoInitData { +struct DrawingProgramLayerListItemMetaInfoUndoData { std::string name; float alpha = 1.0f; SerializedBlendMode blendMode = SerializedBlendMode::SRC_OVER; }; -struct DrawingProgramLayerListItemInitData { - DrawingProgramLayerListItemMetaInfoInitData metaInfo; - std::optional folderData; - std::optional layerData; +struct DrawingProgramLayerListItemUndoData { + WorldUndoManager::UndoObjectID undoID; + DrawingProgramLayerListItemMetaInfoUndoData metaInfo; + std::optional> folderData; + std::optional> layerData; }; class DrawingProgramLayerListItem { public: DrawingProgramLayerListItem(); DrawingProgramLayerListItem(NetworkingObjects::NetObjManager& netObjMan, const std::string& initName, bool isFolder); - DrawingProgramLayerListItem(NetworkingObjects::NetObjManager& netObjMan, const DrawingProgramLayerListItemInitData& initData); + DrawingProgramLayerListItem(World& w, const DrawingProgramLayerListItemUndoData& undoData); + DrawingProgramLayerListItemUndoData get_undo_data(WorldUndoManager& u) const; bool is_folder() const; DrawingProgramLayerFolder& get_folder() const; DrawingProgramLayer& get_layer() const; diff --git a/src/MainProgram.hpp b/src/MainProgram.hpp index 14c03d8..6b3e846 100644 --- a/src/MainProgram.hpp +++ b/src/MainProgram.hpp @@ -63,7 +63,7 @@ class MainProgram { } window; struct Clipboard { - std::vector> components; + std::vector> components; std::unordered_map resources; WorldVec pos; WorldScalar inverseScale; diff --git a/src/WorldUndoManager.cpp b/src/WorldUndoManager.cpp index cb81728..38ea625 100644 --- a/src/WorldUndoManager.cpp +++ b/src/WorldUndoManager.cpp @@ -123,6 +123,16 @@ std::optional WorldUndoManager::get_netid_from_undo return it->second; } +bool WorldUndoManager::fill_netid_list_from_undoid_list(std::vector& netIDList, const std::vector& undoIDList) { + for(UndoObjectID undoID : undoIDList) { + auto netIDOpt = get_netid_from_undoid(undoID); + if(!netIDOpt.has_value()) + return false; + netIDList.emplace_back(netIDOpt.value()); + } + return true; +} + std::vector WorldUndoManager::get_front_undo_queue_names(unsigned count) { std::vector toRet; for(auto& u : undoQueue | std::views::reverse) { diff --git a/src/WorldUndoManager.hpp b/src/WorldUndoManager.hpp index 6a33aa0..5c1d337 100644 --- a/src/WorldUndoManager.hpp +++ b/src/WorldUndoManager.hpp @@ -35,6 +35,7 @@ class WorldUndoManager { UndoObjectID get_undoid_from_netid(const NetworkingObjects::NetObjID& netObjID); void register_new_netid_to_existing_undoid(UndoObjectID existingUndoID, const NetworkingObjects::NetObjID& netObjID); std::optional get_netid_from_undoid(UndoObjectID undoID); + bool fill_netid_list_from_undoid_list(std::vector& netIDList, const std::vector& undoIDList); std::vector get_front_undo_queue_names(unsigned count); World& world;