infinipaint/src/DrawingProgram/Layers/DrawingProgramLayerManager.cpp
copilot 1c1d63f376
Some checks are pending
Windows Release / windows-x86_64 (push) Waiting to run
Ship 0.8 fog/freistellen and fix Windows CI for friends
Publish the portable zip to public Forgejo from the private GitHub
builder, and force Ninja so MSVC 195 deps build without CMake 4.2.
2026-10-25 18:07:47 +01:00

699 lines
32 KiB
C++

/*
* 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 <https://www.gnu.org/licenses/>.
*/
#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 <chrono>
#include <functional>
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<DrawingProgramLayerListItem>(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<DrawingProgramLayerListItem>(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<CanvasComponentContainer::ObjInfo*> DrawingProgramLayerManager::get_flattened_component_list() const {
std::vector<CanvasComponentContainer::ObjInfo*> toRet;
layerTreeRoot->get_flattened_component_list(toRet);
return toRet;
}
std::vector<DrawingProgramLayerListItem*> DrawingProgramLayerManager::get_flattened_layer_list() {
std::vector<DrawingProgramLayerListItem*> 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<CanvasComponentContainer::ObjInfo*>& objs, bool beginIfTrueEndIfFalse) {
using namespace NetworkingObjects;
if(objs.empty())
return;
struct MoveUndoLayerData {
std::vector<uint32_t> oldPos;
std::vector<WorldUndoManager::UndoObjectID> objs;
};
std::unordered_map<WorldUndoManager::UndoObjectID, MoveUndoLayerData> toMoveMapUndo;
auto& undoMan = drawP.world.undo;
drawP.world.send_reliable_multi_command_to_all([&]() {
std::unordered_map<DrawingProgramLayerListItem*, std::vector<CanvasComponentContainer::ObjInfo*>> toEraseMap;
std::vector<NetObjOwnerPtr<DrawingProgramLayerListItem>> toInsertObjPtrs;
DrawingProgramLayerListItem* parentLayer = nullptr;
std::vector<CanvasComponentContainer::ObjInfoIterator> 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<NetObjOwnerPtr<CanvasComponentContainer>> erasedObjects;
auto& components = parentLayer->get_layer().components;
components->erase_list(components, toEraseList, &erasedObjects);
std::vector<std::pair<CanvasComponentContainer::ObjInfoIterator, NetObjOwnerPtr<CanvasComponentContainer>>> 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<WorldUndoManager::UndoObjectID, MoveUndoLayerData> 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<uint32_t>* oldPos;
std::vector<NetObjID> objs;
};
std::unordered_map<NetObjID, MoveLayerData> toMoveObjs;
{
for(auto& [undoID, moveUndoData] : toMoveMapUndo) {
std::optional<NetObjID> 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<CanvasComponentContainer::NetList>(compNetID);
std::vector<std::pair<CanvasComponentContainer::ObjInfoIterator, NetObjOwnerPtr<CanvasComponentContainer>>> toInsert;
std::vector<NetObjOrderedListIterator<CanvasComponentContainer>> 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<NetObjOwnerPtr<CanvasComponentContainer>> erasedObjs;
std::vector<uint32_t> newPos;
{
uint32_t i = 0;
for(auto& it : toErase) {
newPos.emplace_back(it->pos - i);
++i;
}
}
components->erase_list(components, toErase, &erasedObjs);
std::vector<NetObjOrderedListIterator<CanvasComponentContainer>> 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<WorldUndoManager::UndoObjectID, MoveUndoLayerData> toMoveMapUndo;
};
undoMan.push(std::make_unique<MoveComponentsUndoAction>(std::move(toMoveMapUndo)));
}
std::vector<CanvasComponentContainer::ObjInfoIterator> DrawingProgramLayerManager::add_many_components_to_layer_being_edited(const std::vector<std::pair<CanvasComponentContainer::ObjInfoIterator, CanvasComponentContainer*>>& 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<CanvasComponentContainer::ObjInfoIterator> DrawingProgramLayerManager::add_many_components_to_layer(DrawingProgramLayerListItem* layer, const std::vector<std::pair<CanvasComponentContainer::ObjInfoIterator, CanvasComponentContainer*>>& 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<void()>& toRun) {
addToCacheOnComponentInsert = false;
toRun();
addToCacheOnComponentInsert = true;
}
void DrawingProgramLayerManager::disable_commit_update_block(const std::function<void()>& toRun) {
commitUpdateOnComponentInsert = false;
toRun();
commitUpdateOnComponentInsert = true;
}
void DrawingProgramLayerManager::disable_add_to_cache_and_commit_update_block(const std::function<void()>& 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<NetworkingObjects::NetObjWeakPtr<DrawingProgramLayerListItem>(const DrawingProgramLayerFolder&)> findEditable;
findEditable = [&](const DrawingProgramLayerFolder& folder) -> NetworkingObjects::NetObjWeakPtr<DrawingProgramLayerListItem> {
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<DrawingProgramLayerListItem>(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<NetworkingObjects::NetObjID>& 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<NetObjID> toEraseID = undoMan.get_netid_from_undoid(undoID);
if(!toEraseID.has_value())
return false;
std::optional<NetObjID> toEraseCompListID = undoMan.get_netid_from_undoid(componentListUndoID);
if(!toEraseCompListID.has_value())
return false;
auto compList = undoMan.world.netObjMan.get_obj_temporary_ref_from_id<NetObjOrderedList<CanvasComponentContainer>>(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<NetworkingObjects::NetObjID> toInsertID = undoMan.get_netid_from_undoid(undoID);
if(toInsertID.has_value())
return false;
std::optional<NetObjID> toEraseCompListID = undoMan.get_netid_from_undoid(componentListUndoID);
if(!toEraseCompListID.has_value())
return false;
auto compList = undoMan.world.netObjMan.get_obj_temporary_ref_from_id<NetObjOrderedList<CanvasComponentContainer>>(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<CanvasComponentContainer::CopyData> copyData;
uint32_t pos;
WorldUndoManager::UndoObjectID undoID;
WorldUndoManager::UndoObjectID componentListUndoID;
};
drawP.world.undo.push(std::make_unique<AddCanvasComponentWorldUndoAction>(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<DrawingProgramLayerListItem*, std::vector<CanvasComponentContainer::ObjInfoIterator>>& 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<DrawingProgramLayerListItem*, std::vector<CanvasComponentContainer::ObjInfoIterator>>& eraseMap, bool newUndo) {
using namespace NetworkingObjects;
struct EraseListData {
std::vector<WorldUndoManager::UndoObjectID> undoID;
std::vector<uint32_t> pos;
std::vector<std::unique_ptr<CanvasComponentContainer::CopyData>> copyData;
};
class EraseCanvasComponentsWorldUndoAction : public WorldUndoAction {
public:
EraseCanvasComponentsWorldUndoAction(std::unordered_map<WorldUndoManager::UndoObjectID, EraseListData> initEraseMap):
undoEraseMap(std::move(initEraseMap))
{}
std::string get_name() const override {
return "Erase Canvas Objects";
}
bool undo(WorldUndoManager& undoMan) override {
struct InsertData {
std::vector<NetObjOrderedListIterator<CanvasComponentContainer>> it;
std::vector<std::unique_ptr<CanvasComponentContainer::CopyData>>* copyData;
std::vector<WorldUndoManager::UndoObjectID>* undoID;
};
std::unordered_map<NetObjID, InsertData> insertMap;
{
for(auto& [parentUndoID, eData] : undoEraseMap) {
std::optional<NetObjID> 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<NetObjOrderedList<CanvasComponentContainer>>(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<NetObjOrderedList<CanvasComponentContainer>>(parentNetID);
std::vector<std::pair<NetObjOrderedListIterator<CanvasComponentContainer>, NetObjOwnerPtr<CanvasComponentContainer>>> 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<CanvasComponentContainer>(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<NetObjID, std::vector<NetObjOrderedListIterator<CanvasComponentContainer>>> eraseMap;
{
for(auto& [parentUndoID, eData] : undoEraseMap) {
std::optional<NetObjID> 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<NetObjOrderedList<CanvasComponentContainer>>(parentNetID.value());
std::vector<NetObjID> 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<NetObjOrderedList<CanvasComponentContainer>>(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<WorldUndoManager::UndoObjectID, EraseListData> undoEraseMap;
};
auto& undoMan = drawP.world.undo;
std::unordered_map<WorldUndoManager::UndoObjectID, EraseListData> 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<EraseCanvasComponentsWorldUndoAction>(std::move(undoMap)));
else
undoMan.push_on_last(std::make_unique<EraseCanvasComponentsWorldUndoAction>(std::move(undoMap)));
}
void DrawingProgramLayerManager::add_undo_place_components(DrawingProgramLayerListItem* parent, const std::vector<CanvasComponentContainer::ObjInfoIterator>& placeList, bool newUndo) {
using namespace NetworkingObjects;
struct PlaceListData {
std::vector<WorldUndoManager::UndoObjectID> undoID;
std::vector<uint32_t> pos;
std::vector<std::unique_ptr<CanvasComponentContainer::CopyData>> 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<NetObjOrderedList<CanvasComponentContainer>> parentListPtr;
std::vector<NetObjOrderedListIterator<CanvasComponentContainer>> eraseData;
{
std::optional<NetObjID> parentNetID = undoMan.get_netid_from_undoid(parentUndoID);
if(!parentNetID)
return false;
parentListPtr = undoMan.world.netObjMan.get_obj_temporary_ref_from_id<NetObjOrderedList<CanvasComponentContainer>>(parentNetID.value());
}
{
std::vector<NetObjID> 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<NetObjOrderedListIterator<CanvasComponentContainer>> it;
NetObjTemporaryPtr<NetObjOrderedList<CanvasComponentContainer>> parentListPtr;
auto p = std::chrono::steady_clock::now();
{
std::optional<NetObjID> parentNetID = undoMan.get_netid_from_undoid(parentUndoID);
if(!parentNetID)
return false;
parentListPtr = undoMan.world.netObjMan.get_obj_temporary_ref_from_id<NetObjOrderedList<CanvasComponentContainer>>(parentNetID.value());
}
p = std::chrono::steady_clock::now();
{
it = parentListPtr->at_ordered_indices(undoPlaceData.pos);
}
p = std::chrono::steady_clock::now();
std::vector<std::pair<NetObjOrderedListIterator<CanvasComponentContainer>, NetObjOwnerPtr<CanvasComponentContainer>>> 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<CanvasComponentContainer>(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<PlaceCanvasComponentsWorldUndoAction>(undoMan.get_undoid_from_netid(parent->get_layer().components.get_net_id()), std::move(undoData)));
else
undoMan.push_on_last(std::make_unique<PlaceCanvasComponentsWorldUndoAction>(undoMan.get_undoid_from_netid(parent->get_layer().components.get_net_id()), std::move(undoData)));
}