/*
* 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)));
}