/* * InfiniPaint * Copyright (C) 2025-2026 Yousef Khadadeh * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "DrawingProgramLayerManager.hpp" #include "FogStripper.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" #include "../../CanvasComponents/CanvasComponent.hpp" #include #include DrawingProgramLayerManager::DrawingProgramLayerManager(DrawingProgram& drawProg): drawP(drawProg), // initialize drawP first for next objects to be initialized properly listGUI(*this) {} void DrawingProgramLayerManager::server_init_no_file() { layerTreeRoot = drawP.world.netObjMan.make_obj_from_ptr(new DrawingProgramLayerListItem(drawP.world.netObjMan, "ROOT", true)); layerTreeRoot->get_folder().set_component_list_callbacks(*this); // Only need to call set_component_list_callbacks on root, and the rest will get the callbacks set as well editingLayer = layerTreeRoot->get_folder().folderList->emplace_back_direct(layerTreeRoot->get_folder().folderList, drawP.world.netObjMan, "First Layer", false)->obj; } void DrawingProgramLayerManager::scale_up(const WorldScalar& scaleUpAmount) { layerTreeRoot->scale_up(scaleUpAmount); } void DrawingProgramLayerManager::write_components_server(cereal::PortableBinaryOutputArchive& a) { auto hidden = FogStripper::compute_hidden_component_ids(*this); clientHiddenComponentIds = hidden; a(layerTreeRoot.get_net_id()); layerTreeRoot->write_stripped_constructor(a, hidden); } void DrawingProgramLayerManager::read_components_client(cereal::PortableBinaryInputArchive& a) { layerTreeRoot = drawP.world.netObjMan.read_create_message(a, nullptr); disable_add_to_cache_and_commit_update_block([&]() { layerTreeRoot->set_component_list_callbacks(*this); }); auto flattenedCompList = get_flattened_component_list(); parallel_loop_container(flattenedCompList, [&drawP = drawP](CanvasComponentContainer::ObjInfo* comp) { comp->obj->commit_update_dont_invalidate_cache(drawP); }); drawP.rebuild_cache(); editingLayer = layerTreeRoot->get_folder().get_initial_editing_layer(); ensure_editing_layer_is_editable(); } bool DrawingProgramLayerManager::is_a_layer_being_edited() { return !editingLayer.expired(); } uint32_t DrawingProgramLayerManager::edited_layer_component_count() { return editingLayer.lock()->get_layer().components->size(); } uint32_t DrawingProgramLayerManager::total_component_count() { if(layerTreeRoot) return layerTreeRoot->get_component_count(); return 0; } CanvasComponentContainer::ObjInfoIterator DrawingProgramLayerManager::get_edited_layer_end_iterator() { return editingLayer.lock()->get_layer().components->end(); } void DrawingProgramLayerManager::draw(SkCanvas* canvas, const DrawData& drawData) { if(layerTreeRoot) layerTreeRoot->draw(canvas, drawData); } std::vector DrawingProgramLayerManager::get_flattened_component_list() const { std::vector toRet; layerTreeRoot->get_flattened_component_list(toRet); return toRet; } std::vector DrawingProgramLayerManager::get_flattened_layer_list() { std::vector toRet; layerTreeRoot->get_flattened_layer_list(toRet); return toRet; } bool DrawingProgramLayerManager::layer_tree_root_exists() { return layerTreeRoot; } const DrawingProgramLayerListItem& DrawingProgramLayerManager::get_layer_root() { return *layerTreeRoot; } bool DrawingProgramLayerManager::component_passes_layer_selector(CanvasComponentContainer::ObjInfo* c, LayerSelector layerSelector) { if(!c || !c->obj || !c->obj->parentLayer) return false; if(!can_current_peer_edit_layer(c->obj->parentLayer)) return false; switch(layerSelector) { case LayerSelector::ALL_VISIBLE_LAYERS: return c->obj->parentLayer->get_visible(); case LayerSelector::LAYER_BEING_EDITED: return is_a_layer_being_edited() && c->obj->parentLayer == editingLayer.lock().get(); } return false; } void DrawingProgramLayerManager::push_components_to(const std::vector& objs, bool beginIfTrueEndIfFalse) { using namespace NetworkingObjects; if(objs.empty()) return; struct MoveUndoLayerData { std::vector oldPos; std::vector objs; }; std::unordered_map toMoveMapUndo; auto& undoMan = drawP.world.undo; drawP.world.send_reliable_multi_command_to_all([&]() { std::unordered_map> toEraseMap; std::vector> toInsertObjPtrs; DrawingProgramLayerListItem* parentLayer = nullptr; std::vector toEraseList; auto commitMoveOnLayer = [&]() { if(toEraseList.empty()) return; { MoveUndoLayerData moveUndoData; uint32_t i = 0; for(auto& e : toEraseList) { moveUndoData.objs.emplace_back(drawP.world.undo.get_undoid_from_netid(e->obj.get_net_id())); moveUndoData.oldPos.emplace_back(e->pos - i); ++i; } toMoveMapUndo[undoMan.get_undoid_from_netid(parentLayer->get_layer().components.get_net_id())] = moveUndoData; } std::vector> erasedObjects; auto& components = parentLayer->get_layer().components; components->erase_list(components, toEraseList, &erasedObjects); std::vector>> toInsert; auto insertIt = beginIfTrueEndIfFalse ? components->begin() : components->end(); for(auto& e : erasedObjects) { toInsert.emplace_back(insertIt, std::move(e)); toInsert.back().second.reassign_ids(); } components->insert_sorted_list_and_send_create(components, toInsert); toEraseList.clear(); }; for(auto& obj : objs) { if(parentLayer != obj->obj->parentLayer) { commitMoveOnLayer(); parentLayer = obj->obj->parentLayer; } toEraseList.emplace_back(obj->obj->objInfo); } commitMoveOnLayer(); }); class MoveComponentsUndoAction : public WorldUndoAction { public: MoveComponentsUndoAction(std::unordered_map initToMoveMapUndo): toMoveMapUndo(std::move(initToMoveMapUndo)) {} std::string get_name() const override { return "Move Components"; } bool undo(WorldUndoManager& undoMan) override { return undo_redo(undoMan); } bool redo(WorldUndoManager& undoMan) override { return undo_redo(undoMan); } bool undo_redo(WorldUndoManager& undoMan) { struct MoveLayerData { std::vector* oldPos; std::vector objs; }; std::unordered_map toMoveObjs; { for(auto& [undoID, moveUndoData] : toMoveMapUndo) { std::optional netID = undoMan.get_netid_from_undoid(undoID); if(!netID.has_value()) return false; auto& moveMapEntry = toMoveObjs[netID.value()]; moveMapEntry.oldPos = &moveUndoData.oldPos; if(!undoMan.fill_netid_list_from_undoid_list(moveMapEntry.objs, moveUndoData.objs)) return false; } } { for(auto& [compNetID, moveData] : toMoveObjs) { auto components = undoMan.world.netObjMan.get_obj_temporary_ref_from_id(compNetID); std::vector>> toInsert; std::vector> toErase = components->get_list(moveData.objs); components->sort_netobj_ordered_list_iterator_list(components, toErase); // There's a chance that objects could have changed order on the client side, so sort them std::vector> erasedObjs; std::vector newPos; { uint32_t i = 0; for(auto& it : toErase) { newPos.emplace_back(it->pos - i); ++i; } } components->erase_list(components, toErase, &erasedObjs); std::vector> insertIteratorList = components->at_ordered_indices(*moveData.oldPos); for(uint32_t i = 0; i < toErase.size(); i++) { toInsert.emplace_back(insertIteratorList[i], std::move(erasedObjs[i])); toInsert.back().second.reassign_ids(); } components->insert_sorted_list_and_send_create(components, toInsert); *moveData.oldPos = newPos; } } return true; } ~MoveComponentsUndoAction() {} std::unordered_map toMoveMapUndo; }; undoMan.push(std::make_unique(std::move(toMoveMapUndo))); } std::vector DrawingProgramLayerManager::add_many_components_to_layer_being_edited(const std::vector>& newObjs) { if(!newObjs.empty()) { auto editLayerPtr = editingLayer.lock(); if(!editLayerPtr) throw std::runtime_error("[DrawingProgramLayerManager::add_many_components_to_layer_being_edited] No layer selected!"); if(!can_current_peer_edit_layer(editLayerPtr.get())) { for(auto& [it, obj] : newObjs) delete obj; return {}; } auto toRet = editLayerPtr->get_layer().components->insert_sorted_list_and_send_create(editLayerPtr->get_layer().components, newObjs); add_undo_place_components(editingLayer.lock().get(), toRet); return toRet; } return {}; } std::vector DrawingProgramLayerManager::add_many_components_to_layer(DrawingProgramLayerListItem* layer, const std::vector>& newObjs, bool newUndo) { auto toRet = layer->get_layer().components->insert_sorted_list_and_send_create(layer->get_layer().components, newObjs); add_undo_place_components(layer, toRet, newUndo); return toRet; } void DrawingProgramLayerManager::disable_add_to_cache_block(const std::function& toRun) { addToCacheOnComponentInsert = false; toRun(); addToCacheOnComponentInsert = true; } void DrawingProgramLayerManager::disable_commit_update_block(const std::function& toRun) { commitUpdateOnComponentInsert = false; toRun(); commitUpdateOnComponentInsert = true; } void DrawingProgramLayerManager::disable_add_to_cache_and_commit_update_block(const std::function& toRun) { addToCacheOnComponentInsert = false; commitUpdateOnComponentInsert = false; toRun(); commitUpdateOnComponentInsert = true; addToCacheOnComponentInsert = true; } bool DrawingProgramLayerManager::can_current_peer_edit_layer(const DrawingProgramLayerListItem* layer) const { if(!layer) return false; if(FogStripper::world_is_host(drawP.world)) return true; return !layer->is_player_edit_blocked(); } bool DrawingProgramLayerManager::client_may_modify_component(const CanvasComponentContainer* comp) const { // Host-side authority check for CLIENT messages — must NOT use can_current_peer_edit_layer // (that always returns true on the host). if(!comp || !comp->parentLayer) return false; return !comp->parentLayer->is_player_edit_blocked(); } void DrawingProgramLayerManager::ensure_editing_layer_is_editable() { auto cur = editingLayer.lock(); if(cur && can_current_peer_edit_layer(cur.get())) return; if(!layerTreeRoot) return; std::function(const DrawingProgramLayerFolder&)> findEditable; findEditable = [&](const DrawingProgramLayerFolder& folder) -> NetworkingObjects::NetObjWeakPtr { for(auto& c : *folder.folderList) { if(!c.obj->is_folder() && can_current_peer_edit_layer(c.obj.get())) return c.obj; } for(auto& c : *folder.folderList) { if(c.obj->is_folder()) { auto found = findEditable(c.obj->get_folder()); if(!found.expired()) return found; } } return {}; }; editingLayer = findEditable(layerTreeRoot->get_folder()); drawP.selection.remove_uneditable_from_selection(); } void DrawingProgramLayerManager::refresh_fog_client_projection() { fogClientProjectionDirty = false; FogStripper::sync_hidden_set_to_clients(drawP.world, *this); } void DrawingProgramLayerManager::mark_fog_client_projection_dirty() { if(drawP.world.netServer) fogClientProjectionDirty = true; } void DrawingProgramLayerManager::flush_fog_client_projection_if_dirty() { if(!fogClientProjectionDirty) return; refresh_fog_client_projection(); } void DrawingProgramLayerManager::defer_reject_component(DrawingProgramLayerListItem* layer, NetworkingObjects::NetObjID compId) { deferredRejectInserts.emplace_back(layer, compId); } void DrawingProgramLayerManager::flush_deferred_rejects() { if(deferredRejectInserts.empty()) return; auto pending = std::move(deferredRejectInserts); deferredRejectInserts.clear(); for(auto& [layer, compId] : pending) { if(!layer || layer->is_folder()) continue; auto& comps = layer->get_layer().components; if(!comps->contains(compId)) continue; comps->erase(comps, comps->get(compId)); } } CanvasComponentContainer::ObjInfo* DrawingProgramLayerManager::add_component_to_layer_being_edited(CanvasComponentContainer* newObj) { auto editLayerPtr = editingLayer.lock(); if(!editLayerPtr) throw std::runtime_error("[DrawingProgramLayerManager::add_component_to_layer_being_edited] No layer selected!"); if(!can_current_peer_edit_layer(editLayerPtr.get())) { delete newObj; return nullptr; } return &(*editLayerPtr->get_layer().components->push_back_and_send_create(editLayerPtr->get_layer().components, newObj)); } void DrawingProgramLayerManager::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) { if(version >= VersionNumber(0, 4, 0)) { layerTreeRoot = drawP.world.netObjMan.make_obj_from_ptr(new DrawingProgramLayerListItem()); layerTreeRoot->load_file(a, version, *this); disable_add_to_cache_and_commit_update_block([&]() { layerTreeRoot->get_folder().set_component_list_callbacks(*this); // Only need to call set_component_list_callbacks on root, and the rest will get the callbacks set as well }); } else { server_init_no_file(); disable_add_to_cache_and_commit_update_block([&]() { CanvasComponentContainer::NetListTemporaryPtr editingLayerTmpPtrComponents = editingLayer.lock()->get_layer().components; uint64_t compCount; a(compCount); for(uint64_t i = 0; i < compCount; i++) { CanvasComponentContainer* newContainer = new CanvasComponentContainer(); newContainer->load_file(a, version, drawP.world.netObjMan); editingLayerTmpPtrComponents->push_back_and_send_create(editingLayerTmpPtrComponents, newContainer); } }); } auto flattenedCompList = get_flattened_component_list(); parallel_loop_container(flattenedCompList, [&drawP = drawP](CanvasComponentContainer::ObjInfo* comp) { comp->obj->commit_update_dont_invalidate_cache(drawP); }); layerTreeRoot->erase_invalid_components(); drawP.rebuild_cache(); editingLayer = layerTreeRoot->get_folder().get_initial_editing_layer(); ensure_editing_layer_is_editable(); } void DrawingProgramLayerManager::save_file(cereal::PortableBinaryOutputArchive& a) const { layerTreeRoot->save_file(a); } 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(uint32_t newPos, WorldUndoManager::UndoObjectID initUndoID, WorldUndoManager::UndoObjectID initComponentListUndoID): 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()); auto toEraseIt = compList->get(toEraseID.value()); copyData = toEraseIt->obj->get_data_copy(); compList->erase(compList, toEraseIt); 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()); copyData = nullptr; return true; } void scale_up(const WorldScalar& scaleAmount) override { if(copyData) 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->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, bool newUndo) { add_undo_erase_components(eraseMap, newUndo); drawP.world.send_reliable_multi_command_to_all([&]() { for(auto& [layerListItem, netObjSetToErase] : eraseMap) { auto& layerComponentList = layerListItem->get_layer().components; layerComponentList->erase_list(layerComponentList, netObjSetToErase); } }); } void DrawingProgramLayerManager::add_undo_erase_components(const std::unordered_map>& eraseMap, bool newUndo) { 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; } } { 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_sorted_list_and_send_create(parentListPtr, toInsert); insertData.copyData->clear(); } } 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); for(auto& it : eraseData) eData.copyData.emplace_back(it->obj->get_data_copy()); } } { for(auto& [parentNetID, eData] : eraseMap) { auto parentListPtr = undoMan.world.netObjMan.get_obj_temporary_ref_from_id>(parentNetID); parentListPtr->erase_list(parentListPtr, eData); } } 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; } } if(newUndo) undoMan.push(std::make_unique(std::move(undoMap))); else undoMan.push_on_last(std::make_unique(std::move(undoMap))); } void DrawingProgramLayerManager::add_undo_place_components(DrawingProgramLayerListItem* parent, const std::vector& placeList, bool newUndo) { 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); for(auto& it : eraseData) undoPlaceData.copyData.emplace_back(it->obj->get_data_copy()); } parentListPtr->erase_list(parentListPtr, eraseData); return true; } bool redo(WorldUndoManager& undoMan) override { std::vector> it; NetObjTemporaryPtr> parentListPtr; auto p = std::chrono::steady_clock::now(); { 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()); } p = std::chrono::steady_clock::now(); { it = parentListPtr->at_ordered_indices(undoPlaceData.pos); } p = std::chrono::steady_clock::now(); 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()); } p = std::chrono::steady_clock::now(); parentListPtr->insert_sorted_list_and_send_create(parentListPtr, toInsert); p = std::chrono::steady_clock::now(); undoPlaceData.copyData.clear(); 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.pos.emplace_back(comp->pos - i); ++i; } if(newUndo) undoMan.push(std::make_unique(undoMan.get_undoid_from_netid(parent->get_layer().components.get_net_id()), std::move(undoData))); else undoMan.push_on_last(std::make_unique(undoMan.get_undoid_from_netid(parent->get_layer().components.get_net_id()), std::move(undoData))); }