From 279801623d456c420bab948f5751803d2c3b79f3 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Sun, 4 Jan 2026 16:50:47 +0300 Subject: [PATCH] Added erase many and place many canvas undo --- .../CanvasComponentContainer.cpp | 4 + .../CanvasComponentContainer.hpp | 1 + .../Layers/DrawingProgramLayerListItem.cpp | 4 + .../Layers/DrawingProgramLayerListItem.hpp | 1 + .../Layers/DrawingProgramLayerManager.cpp | 269 +++++++++++++++++- .../Layers/DrawingProgramLayerManager.hpp | 21 +- src/DrawingProgram/Tools/BrushTool.cpp | 1 + 7 files changed, 291 insertions(+), 10 deletions(-) diff --git a/src/CanvasComponents/CanvasComponentContainer.cpp b/src/CanvasComponents/CanvasComponentContainer.cpp index 0a3f1fc..7f43015 100644 --- a/src/CanvasComponents/CanvasComponentContainer.cpp +++ b/src/CanvasComponents/CanvasComponentContainer.cpp @@ -45,6 +45,10 @@ void CanvasComponentContainer::register_class(World& w) { }); } +void CanvasComponentContainer::reassign_netobj_ids_call() { + compAllocator.reassign_ids(); +} + std::unique_ptr CanvasComponentContainer::get_data_copy() const { auto toRet = std::make_unique(); toRet->coords = coords; diff --git a/src/CanvasComponents/CanvasComponentContainer.hpp b/src/CanvasComponents/CanvasComponentContainer.hpp index 803eac7..f71e43c 100644 --- a/src/CanvasComponents/CanvasComponentContainer.hpp +++ b/src/CanvasComponents/CanvasComponentContainer.hpp @@ -38,6 +38,7 @@ class CanvasComponentContainer { CanvasComponentContainer(NetworkingObjects::NetObjManager& objMan, const CopyData& copyData); static void register_class(World& w); + void reassign_netobj_ids_call(); 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); diff --git a/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp b/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp index 7507d87..869530c 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayerListItem.cpp @@ -39,6 +39,7 @@ DrawingProgramLayerListItem::DrawingProgramLayerListItem(World& w, const Drawing auto& initFolderList = initData.folderData.value(); folderData = std::make_unique(); folderData->folderList = w.netObjMan.make_obj>(); + w.undo.register_new_netid_to_existing_undoid(initData.containerUndoID, folderData->folderList.get_net_id()); 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()); @@ -48,6 +49,7 @@ DrawingProgramLayerListItem::DrawingProgramLayerListItem(World& w, const Drawing auto& initComponentList = initData.layerData.value(); layerData = std::make_unique(); layerData->components = w.netObjMan.make_obj(); + w.undo.register_new_netid_to_existing_undoid(initData.containerUndoID, layerData->components.get_net_id()); 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()); @@ -66,6 +68,7 @@ DrawingProgramLayerListItemUndoData DrawingProgramLayerListItem::get_undo_data(W toRet.metaInfo.blendMode = displayData->blendMode; toRet.metaInfo.alpha = displayData->alpha; if(folderData) { + toRet.containerUndoID = u.get_undoid_from_netid(folderData->folderList.get_net_id()); toRet.folderData = std::vector(); auto& folderListToRet = toRet.folderData.value(); for(auto& f : *folderData->folderList) { @@ -74,6 +77,7 @@ DrawingProgramLayerListItemUndoData DrawingProgramLayerListItem::get_undo_data(W } } else { + toRet.containerUndoID = u.get_undoid_from_netid(layerData->components.get_net_id()); toRet.layerData = std::vector(); auto& layerListToRet = toRet.layerData.value(); for(auto& c : *layerData->components) diff --git a/src/DrawingProgram/Layers/DrawingProgramLayerListItem.hpp b/src/DrawingProgram/Layers/DrawingProgramLayerListItem.hpp index 1cb14c4..70c65d4 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayerListItem.hpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayerListItem.hpp @@ -22,6 +22,7 @@ struct DrawingProgramLayerListItemMetaInfo { struct DrawingProgramLayerListItemUndoData { WorldUndoManager::UndoObjectID undoID; + WorldUndoManager::UndoObjectID containerUndoID; // Contains either the undoID of folderData->folderList, or layerData->components. Allows for undo to directly point to those two containers DrawingProgramLayerListItemMetaInfo metaInfo; std::optional> folderData; std::optional> layerData; diff --git a/src/DrawingProgram/Layers/DrawingProgramLayerManager.cpp b/src/DrawingProgram/Layers/DrawingProgramLayerManager.cpp index 57c92fa..afe119d 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayerManager.cpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayerManager.cpp @@ -1,7 +1,9 @@ #include "DrawingProgramLayerManager.hpp" #include "../../World.hpp" +#include "Helpers/NetworkingObjects/NetObjID.hpp" #include "Helpers/NetworkingObjects/NetObjOrderedList.hpp" #include "Helpers/NetworkingObjects/NetObjOwnerPtr.decl.hpp" +#include "Helpers/NetworkingObjects/NetObjTemporaryPtr.decl.hpp" #include "Helpers/Parallel.hpp" #include "../../CanvasComponents/CanvasComponentContainer.hpp" #include "../../CanvasComponents/CanvasComponentAllocator.hpp" @@ -94,10 +96,14 @@ bool DrawingProgramLayerManager::component_passes_layer_selector(CanvasComponent } std::vector DrawingProgramLayerManager::add_many_components_to_layer_being_edited(const std::vector>& newObjs) { - auto editLayerPtr = editingLayer.lock(); - if(!editLayerPtr) - return {}; - return editLayerPtr->get_layer().components->insert_ordered_list_and_send_create(editLayerPtr->get_layer().components, newObjs); + if(!newObjs.empty()) { + auto editLayerPtr = editingLayer.lock(); + if(!editLayerPtr) + return {}; + auto toRet = editLayerPtr->get_layer().components->insert_ordered_list_and_send_create(editLayerPtr->get_layer().components, newObjs); + add_undo_place_components(editingLayer.lock().get(), toRet); + } + return {}; } CanvasComponentContainer::ObjInfo* DrawingProgramLayerManager::add_component_to_layer_being_edited(CanvasComponentContainer* newObj) { @@ -144,3 +150,258 @@ void DrawingProgramLayerManager::save_file(cereal::PortableBinaryOutputArchive& void DrawingProgramLayerManager::get_used_resources(std::unordered_set& resourceSet) { layerTreeRoot->get_used_resources(resourceSet); } + +void DrawingProgramLayerManager::add_undo_place_component(CanvasComponentContainer::ObjInfo* objInfo) { + using namespace NetworkingObjects; + class AddCanvasComponentWorldUndoAction : public WorldUndoAction { + public: + AddCanvasComponentWorldUndoAction(std::unique_ptr initCopyData, uint32_t newPos, WorldUndoManager::UndoObjectID initUndoID, WorldUndoManager::UndoObjectID initComponentListUndoID): + copyData(std::move(initCopyData)), + pos(newPos), + undoID(initUndoID), + componentListUndoID(initComponentListUndoID) + {} + std::string get_name() const override { + return "Add Canvas Object"; + } + bool undo(WorldUndoManager& undoMan) override { + std::optional toEraseID = undoMan.get_netid_from_undoid(undoID); + if(!toEraseID.has_value()) + return false; + std::optional toEraseCompListID = undoMan.get_netid_from_undoid(componentListUndoID); + if(!toEraseCompListID.has_value()) + return false; + auto compList = undoMan.world.netObjMan.get_obj_temporary_ref_from_id>(toEraseCompListID.value()); + compList->erase(compList, compList->get(toEraseID.value())); + return true; + } + bool redo(WorldUndoManager& undoMan) override { + std::optional toInsertID = undoMan.get_netid_from_undoid(undoID); + if(toInsertID.has_value()) + return false; + std::optional toEraseCompListID = undoMan.get_netid_from_undoid(componentListUndoID); + if(!toEraseCompListID.has_value()) + return false; + auto compList = undoMan.world.netObjMan.get_obj_temporary_ref_from_id>(toEraseCompListID.value()); + auto insertedIt = compList->emplace_direct(compList, compList->at(pos), undoMan.world.netObjMan, *copyData); + undoMan.register_new_netid_to_existing_undoid(undoID, insertedIt->obj.get_net_id()); + return true; + } + void scale_up(const WorldScalar& scaleAmount) override { + copyData->scale_up(scaleAmount); + } + ~AddCanvasComponentWorldUndoAction() {} + + std::unique_ptr copyData; + uint32_t pos; + WorldUndoManager::UndoObjectID undoID; + WorldUndoManager::UndoObjectID componentListUndoID; + }; + + drawP.world.undo.push(std::make_unique(objInfo->obj->get_data_copy(), objInfo->pos, drawP.world.undo.get_undoid_from_netid(objInfo->obj.get_net_id()), drawP.world.undo.get_undoid_from_netid(objInfo->obj->parentLayer->get_layer().components.get_net_id()))); +} + +void DrawingProgramLayerManager::erase_component_map(const std::unordered_map>& eraseMap) { + add_undo_erase_components(eraseMap); + drawP.world.netObjMan.send_multi_update_messsage([&]() { + for(auto& [layerListItem, netObjSetToErase] : eraseMap) { + auto& layerComponentList = layerListItem->get_layer().components; + layerComponentList->erase_list(layerComponentList, netObjSetToErase); + } + }, NetworkingObjects::NetObjManager::SendUpdateType::SEND_TO_ALL, nullptr); +} + +void DrawingProgramLayerManager::add_undo_erase_components(const std::unordered_map>& eraseMap) { + using namespace NetworkingObjects; + + struct EraseListData { + std::vector undoID; + std::vector pos; + std::vector> copyData; + }; + + class EraseCanvasComponentsWorldUndoAction : public WorldUndoAction { + public: + EraseCanvasComponentsWorldUndoAction(std::unordered_map initEraseMap): + undoEraseMap(std::move(initEraseMap)) + {} + std::string get_name() const override { + return "Erase Canvas Objects"; + } + bool undo(WorldUndoManager& undoMan) override { + struct InsertData { + std::vector> it; + std::vector>* copyData; + std::vector* undoID; + }; + + std::unordered_map insertMap; + + { + for(auto& [parentUndoID, eData] : undoEraseMap) { + std::optional parentNetID = undoMan.get_netid_from_undoid(parentUndoID); + if(!parentNetID) + return false; + auto& insertData = insertMap[parentNetID.value()]; + auto parentListPtr = undoMan.world.netObjMan.get_obj_temporary_ref_from_id>(parentNetID.value()); + insertData.it = parentListPtr->at_ordered_indices(eData.pos); + insertData.copyData = &eData.copyData; + insertData.undoID = &eData.undoID; + } + } + + undoMan.world.netObjMan.send_multi_update_messsage([&]() { + for(auto& [parentNetID, insertData] : insertMap) { + auto parentListPtr = undoMan.world.netObjMan.get_obj_temporary_ref_from_id>(parentNetID); + std::vector, NetObjOwnerPtr>> toInsert(insertData.it.size()); + for(size_t i = 0; i < insertData.it.size(); i++) { + toInsert[i].first = insertData.it[i]; + toInsert[i].second = undoMan.world.netObjMan.make_obj_direct(undoMan.world.netObjMan, *insertData.copyData->at(i)); + undoMan.register_new_netid_to_existing_undoid(insertData.undoID->at(i), toInsert[i].second.get_net_id()); + } + parentListPtr->insert_ordered_list_and_send_create(parentListPtr, toInsert); + } + }, NetworkingObjects::NetObjManager::SendUpdateType::SEND_TO_ALL, nullptr); + + return true; + } + bool redo(WorldUndoManager& undoMan) override { + std::unordered_map>> eraseMap; + + { + for(auto& [parentUndoID, eData] : undoEraseMap) { + std::optional parentNetID = undoMan.get_netid_from_undoid(parentUndoID); + if(!parentNetID) + return false; + auto& eraseData = eraseMap[parentNetID.value()]; + auto parentListPtr = undoMan.world.netObjMan.get_obj_temporary_ref_from_id>(parentNetID.value()); + std::vector netIDList; + if(!undoMan.fill_netid_list_from_undoid_list(netIDList, eData.undoID)) + return false; + eraseData = parentListPtr->get_list(netIDList); + } + } + + undoMan.world.netObjMan.send_multi_update_messsage([&]() { + for(auto& [parentNetID, eData] : eraseMap) { + auto parentListPtr = undoMan.world.netObjMan.get_obj_temporary_ref_from_id>(parentNetID); + parentListPtr->erase_list(parentListPtr, eData); + } + }, NetworkingObjects::NetObjManager::SendUpdateType::SEND_TO_ALL, nullptr); + + return true; + } + void scale_up(const WorldScalar& scaleAmount) override { + for(auto& [undoID, eData] : undoEraseMap) { + for(auto& c : eData.copyData) + c->scale_up(scaleAmount); + } + } + ~EraseCanvasComponentsWorldUndoAction() {} + + std::unordered_map undoEraseMap; + }; + + auto& undoMan = drawP.world.undo; + std::unordered_map undoMap; + for(auto& [item, compContainer] : eraseMap) { + auto& undoData = undoMap[undoMan.get_undoid_from_netid(item->get_layer().components.get_net_id())]; + // Vectors must be sorted + uint32_t i = 0; + for(auto& comp : compContainer) { + undoData.undoID.emplace_back(undoMan.get_undoid_from_netid(comp->obj.get_net_id())); + undoData.copyData.emplace_back(comp->obj->get_data_copy()); + undoData.pos.emplace_back(comp->pos - i); + ++i; + } + } + undoMan.push(std::make_unique(std::move(undoMap))); +} + +void DrawingProgramLayerManager::add_undo_place_components(DrawingProgramLayerListItem* parent, const std::vector& placeList) { + using namespace NetworkingObjects; + + struct PlaceListData { + std::vector undoID; + std::vector pos; + std::vector> copyData; + }; + + class PlaceCanvasComponentsWorldUndoAction : public WorldUndoAction { + public: + PlaceCanvasComponentsWorldUndoAction(WorldUndoManager::UndoObjectID parentList, PlaceListData initPlaceData): + parentUndoID(parentList), + undoPlaceData(std::move(initPlaceData)) + {} + std::string get_name() const override { + return "Place Canvas Objects"; + } + bool undo(WorldUndoManager& undoMan) override { + NetObjTemporaryPtr> parentListPtr; + std::vector> eraseData; + + { + std::optional parentNetID = undoMan.get_netid_from_undoid(parentUndoID); + if(!parentNetID) + return false; + parentListPtr = undoMan.world.netObjMan.get_obj_temporary_ref_from_id>(parentNetID.value()); + } + + { + std::vector netIDList; + if(!undoMan.fill_netid_list_from_undoid_list(netIDList, undoPlaceData.undoID)) + return false; + eraseData = parentListPtr->get_list(netIDList); + } + + parentListPtr->erase_list(parentListPtr, eraseData); + + return true; + } + bool redo(WorldUndoManager& undoMan) override { + std::vector> it; + NetObjTemporaryPtr> parentListPtr; + + { + std::optional parentNetID = undoMan.get_netid_from_undoid(parentUndoID); + if(!parentNetID) + return false; + parentListPtr = undoMan.world.netObjMan.get_obj_temporary_ref_from_id>(parentNetID.value()); + } + + { + it = parentListPtr->at_ordered_indices(undoPlaceData.pos); + } + + std::vector, NetObjOwnerPtr>> toInsert(it.size()); + for(size_t i = 0; i < it.size(); i++) { + toInsert[i].first = it[i]; + toInsert[i].second = undoMan.world.netObjMan.make_obj_direct(undoMan.world.netObjMan, *undoPlaceData.copyData.at(i)); + undoMan.register_new_netid_to_existing_undoid(undoPlaceData.undoID.at(i), toInsert[i].second.get_net_id()); + } + parentListPtr->insert_ordered_list_and_send_create(parentListPtr, toInsert); + + return true; + } + void scale_up(const WorldScalar& scaleAmount) override { + for(auto& c : undoPlaceData.copyData) + c->scale_up(scaleAmount); + } + ~PlaceCanvasComponentsWorldUndoAction() {} + + WorldUndoManager::UndoObjectID parentUndoID; + PlaceListData undoPlaceData; + }; + + auto& undoMan = drawP.world.undo; + PlaceListData undoData; + // Vectors must be sorted + uint32_t i = 0; + for(auto& comp : placeList) { + undoData.undoID.emplace_back(undoMan.get_undoid_from_netid(comp->obj.get_net_id())); + undoData.copyData.emplace_back(comp->obj->get_data_copy()); + undoData.pos.emplace_back(comp->pos - i); + ++i; + } + undoMan.push(std::make_unique(undoMan.get_undoid_from_netid(parent->get_layer().components.get_net_id()), std::move(undoData))); +} diff --git a/src/DrawingProgram/Layers/DrawingProgramLayerManager.hpp b/src/DrawingProgram/Layers/DrawingProgramLayerManager.hpp index 2b0d7f6..c4a4468 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayerManager.hpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayerManager.hpp @@ -13,6 +13,9 @@ class DrawingProgramLayerManager { NetworkingObjects::NetObjOwnerPtr layerTreeRoot; NetworkingObjects::NetObjWeakPtr editingLayer; void set_initial_editing_layer(); + void add_undo_erase_components(const std::unordered_map>& eraseMap); + void add_undo_place_components(DrawingProgramLayerListItem* parent, const std::vector& placeList); + void erase_component_map(const std::unordered_map>& eraseMap); bool commitUpdateOnComponentInsert = true; public: enum class LayerSelector { @@ -28,12 +31,16 @@ class DrawingProgramLayerManager { bool is_a_layer_being_edited(); void scale_up(const WorldScalar& scaleUpAmount); template void erase_component_container(const List& compsToErase) { - std::unordered_map> idsToEraseInSpecificLayers; - for(auto& c : compsToErase) - idsToEraseInSpecificLayers[c->obj->parentLayer].emplace_back(c->obj->objInfo); - for(auto& [layerListItem, netObjSetToErase] : idsToEraseInSpecificLayers) { - auto& layerComponentList = layerListItem->get_layer().components; - layerComponentList->erase_list(layerComponentList, netObjSetToErase); + if(!compsToErase.empty()) { + std::unordered_map> idsToEraseInSpecificLayers; + for(auto& c : compsToErase) + idsToEraseInSpecificLayers[c->obj->parentLayer].emplace_back(c->obj->objInfo); + for(auto& [parent, its] : idsToEraseInSpecificLayers) { + std::sort(its.begin(), its.end(), [](auto& a, auto& b) { + return a->pos < b->pos; + }); + } + erase_component_map(idsToEraseInSpecificLayers); } } uint32_t edited_layer_component_count(); @@ -52,5 +59,7 @@ class DrawingProgramLayerManager { std::vector add_many_components_to_layer_being_edited(const std::vector>& newObjs); std::vector get_flattened_component_list() const; std::vector get_flattened_layer_list(); + + void add_undo_place_component(CanvasComponentContainer::ObjInfo* objInfo); DrawingProgramLayerManagerGUI listGUI; }; diff --git a/src/DrawingProgram/Tools/BrushTool.cpp b/src/DrawingProgram/Tools/BrushTool.cpp index d546601..7517991 100644 --- a/src/DrawingProgram/Tools/BrushTool.cpp +++ b/src/DrawingProgram/Tools/BrushTool.cpp @@ -154,6 +154,7 @@ void BrushTool::commit_stroke() { NetworkingObjects::NetObjOwnerPtr& containerPtr = objInfoBeingEdited->obj; containerPtr->commit_update(drawP); containerPtr->send_comp_update(drawP, true); + drawP.layerMan.add_undo_place_component(objInfoBeingEdited); objInfoBeingEdited = nullptr; } }