Started work on layers undo/redo, and implemented undo action for moving bookmark list items

This commit is contained in:
Yousef Khadadeh 2026-01-03 19:05:00 +03:00
parent 974985ffcd
commit cba5c3abc9
12 changed files with 199 additions and 30 deletions

View file

@ -125,11 +125,11 @@ namespace NetworkingObjects {
std::vector<NetObjOrderedListIterator<T>> toRet(orderedIndices.size());
auto lastIt = data.end();
uint32_t lastDataIndex = data.size();
for(std::pair<uint32_t&, NetObjOrderedListIterator<T>&> p : std::views::zip(orderedIndices, toRet)) {
uint32_t orderedIndex = std::min(p.first, data.size());
for(std::pair<const uint32_t&, NetObjOrderedListIterator<T>&> p : std::views::zip(orderedIndices, toRet) | std::views::reverse) {
uint32_t orderedIndex = std::min<uint32_t>(p.first, data.size());
NetObjOrderedListIterator<T>& it = p.second;
for(;;) {
if(lastDataIndex >= orderedIndex) {
if(orderedIndex == lastDataIndex) {
it = lastIt;
break;
}
@ -145,6 +145,12 @@ namespace NetworkingObjects {
return data_list().end();
return it->second;
}
std::vector<NetObjOrderedListIterator<T>> get_list(const std::vector<NetObjID>& ids) {
std::vector<NetObjOrderedListIterator<T>> toRet;
for(auto& id : ids)
toRet.emplace_back(get(id));
return toRet;
}
NetObjOrderedListConstIterator<T> begin() const {
return data_list().begin();
}
@ -162,8 +168,8 @@ namespace NetworkingObjects {
std::vector<NetObjOrderedListConstIterator<T>> toRet(orderedIndices.size());
auto lastIt = data.end();
uint32_t lastDataIndex = data.size();
for(std::pair<uint32_t&, NetObjOrderedListConstIterator<T>&> p : std::views::zip(orderedIndices, toRet)) {
uint32_t orderedIndex = std::min(p.first, data.size());
for(std::pair<const uint32_t&, NetObjOrderedListConstIterator<T>&> p : std::views::zip(orderedIndices, toRet)) {
uint32_t orderedIndex = std::min<uint32_t>(p.first, data.size());
NetObjOrderedListConstIterator<T>& it = p.second;
for(;;) {
if(lastDataIndex >= orderedIndex) {
@ -182,6 +188,12 @@ namespace NetworkingObjects {
return data_list().end();
return it->second;
}
std::vector<NetObjOrderedListConstIterator<T>> get_list(const std::vector<NetObjID>& ids) const {
std::vector<NetObjOrderedListConstIterator<T>> toRet;
for(auto& id : ids)
toRet.emplace_back(get(id));
return toRet;
}
bool empty() const {
return data_list().empty();
}

View file

@ -89,12 +89,18 @@ void BookmarkManager::setup_list_gui(const std::string& id) {
return isButtonClicked;
},
.moveObjectsToListAtIndex = [&](NetObjID listObj, size_t index, const std::vector<GUIStuff::TreeListing::ParentObjectIDPair>& objsToInsert) {
std::unordered_map<WorldUndoManager::UndoObjectID, std::pair<std::vector<uint32_t>, std::vector<WorldUndoManager::UndoObjectID>>> toEraseMapUndo;
WorldUndoManager::UndoObjectID insertParentUndoID;
uint32_t insertUndoPosition;
std::vector<WorldUndoManager::UndoObjectID> insertedUndoIDs;
world.netObjMan.send_multi_update_messsage([&]() {
std::unordered_map<NetObjID, std::vector<NetObjOrderedListIterator<BookmarkListItem>>> toEraseMap;
uint32_t newIndex = index;
std::vector<NetObjOwnerPtr<BookmarkListItem>> toInsertObjPtrs;
auto& listPtr = world.netObjMan.get_obj_temporary_ref_from_id<BookmarkListItem>(listObj)->get_folder_list();
// Map parent folders to list of objects that should be erased from them
for(size_t i = 0; i < objsToInsert.size(); i++) {
auto& parentListPtr = world.netObjMan.get_obj_temporary_ref_from_id<BookmarkListItem>(objsToInsert[i].parent)->get_folder_list();
toEraseMap[objsToInsert[i].parent].emplace_back(parentListPtr->get(objsToInsert[i].object));
@ -102,11 +108,24 @@ void BookmarkManager::setup_list_gui(const std::string& id) {
newIndex--;
}
// Prepare erase map for undo
for(auto& [parentNetID, objsToEraseList] : toEraseMap) {
auto& undoEraseListPairForParent = toEraseMapUndo[world.undo.get_undoid_from_netid(parentNetID)];
uint32_t i = 0;
for(auto& objToErase : objsToEraseList) {
undoEraseListPairForParent.first.emplace_back(objToErase->pos - i);
undoEraseListPairForParent.second.emplace_back(world.undo.get_undoid_from_netid(objToErase->obj.get_net_id()));
i++;
}
}
// Erase the list of objects we previously mapped to each parent, and move the erased objects to vector toInsertObjPtrs
for(auto& [listToEraseFrom, setToErase] : toEraseMap) {
auto& listToEraseFromPtr = world.netObjMan.get_obj_temporary_ref_from_id<BookmarkListItem>(listToEraseFrom)->get_folder_list();
listToEraseFromPtr->erase_list(listToEraseFromPtr, setToErase, &toInsertObjPtrs);
}
// Insert objects in toInsertObjPtrs into the folder we have selected
std::vector<std::pair<NetObjOrderedListIterator<BookmarkListItem>, NetObjOwnerPtr<BookmarkListItem>>> toInsert;
auto insertIt = listPtr->at(newIndex);
for(uint32_t i = 0; i < objsToInsert.size(); i++) {
@ -116,8 +135,95 @@ void BookmarkManager::setup_list_gui(const std::string& id) {
toInsert.emplace_back(insertIt, std::move(*it));
toInsert.back().second.reassign_ids();
}
listPtr->insert_ordered_list_and_send_create(listPtr, toInsert);
std::vector<NetObjOrderedListIterator<BookmarkListItem>> insertedIterators = listPtr->insert_ordered_list_and_send_create(listPtr, toInsert);
// Prepare insert list for undo
insertParentUndoID = world.undo.get_undoid_from_netid(listObj);
insertUndoPosition = newIndex;
for(auto& it : insertedIterators)
insertedUndoIDs.emplace_back(world.undo.get_undoid_from_netid(it->obj.get_net_id()));
}, NetObjManager::SendUpdateType::SEND_TO_ALL, nullptr);
class MoveBookmarksWorldUndoAction : public WorldUndoAction {
public:
// If the undo ID maps to the net id, we can also assume that the object is definitely inside the list we want to move it from, and not inside some other list
// If it was moved to another list, there would be an undo move that would move it back to this list, and if it was moved by another player the undo id wouldnt map back to the net id, and the undo would fail
// We can also assume all objects in all lists remain in the same order for the same reason, so no need to sort anything if it was sorted when the undo action was generated
MoveBookmarksWorldUndoAction(const std::unordered_map<WorldUndoManager::UndoObjectID, std::pair<std::vector<uint32_t>, std::vector<WorldUndoManager::UndoObjectID>>>& initUndoEraseMap,
WorldUndoManager::UndoObjectID initInsertParentUndoID,
uint32_t initInsertPosition,
std::vector<WorldUndoManager::UndoObjectID> initInsertedUndoIDs):
undoEraseMap(initUndoEraseMap),
insertParentUndoID(initInsertParentUndoID),
insertPosition(initInsertPosition),
insertedUndoIDs(initInsertedUndoIDs)
{}
std::string get_name() const override {
return "Move Bookmarks";
}
bool undo(WorldUndoManager& undoMan) override {
std::vector<NetObjOwnerPtr<BookmarkListItem>> toMoveObjs;
std::vector<NetObjOrderedListIterator<BookmarkListItem>> toEraseList;
NetObjTemporaryPtr<NetObjOrderedList<BookmarkListItem>> eraseListPtr;
std::unordered_map<NetObjID, std::pair<std::vector<uint32_t>*, std::vector<NetObjID>>> toInsertToMap;
{
std::optional<NetObjID> eraseListNetID = undoMan.get_netid_from_undoid(insertParentUndoID);
if(!eraseListNetID.has_value())
return false;
eraseListPtr = undoMan.world.netObjMan.get_obj_temporary_ref_from_id<BookmarkListItem>(eraseListNetID.value())->get_folder_list();
}
{
std::vector<NetObjID> toEraseNetIDList;
if(!undoMan.fill_netid_list_from_undoid_list(toEraseNetIDList, insertedUndoIDs))
return false;
toEraseList = eraseListPtr->get_list(toEraseNetIDList);
}
{
for(auto& [undoID, indexUndoIdListPair] : undoEraseMap) {
std::optional<NetObjID> netID = undoMan.get_netid_from_undoid(undoID);
if(!netID.has_value())
return false;
auto& insertMapEntry = toInsertToMap[netID.value()];
insertMapEntry.first = &indexUndoIdListPair.first;
if(!undoMan.fill_netid_list_from_undoid_list(insertMapEntry.second, indexUndoIdListPair.second))
return false;
}
}
undoMan.world.netObjMan.send_multi_update_messsage([&]() {
eraseListPtr->erase_list(eraseListPtr, toEraseList, &toMoveObjs);
for(auto& [parentNetID, indexNetIDListPair] : toInsertToMap) {
auto& parentListPtr = undoMan.world.netObjMan.get_obj_temporary_ref_from_id<BookmarkListItem>(parentNetID)->get_folder_list();
std::vector<NetObjOrderedListIterator<BookmarkListItem>> insertIteratorList = parentListPtr->at_ordered_indices(*indexNetIDListPair.first);
auto& insertNetIDList = indexNetIDListPair.second;
std::vector<std::pair<NetObjOrderedListIterator<BookmarkListItem>, NetObjOwnerPtr<BookmarkListItem>>> toInsert;
for(auto [insertIt, netID] : std::views::zip(insertIteratorList, insertNetIDList)) {
auto it = std::find_if(toMoveObjs.begin(), toMoveObjs.end(), [id = netID](NetObjOwnerPtr<BookmarkListItem>& objPtr) {
return objPtr.get_net_id() == id;
});
toInsert.emplace_back(insertIt, std::move(*it));
toInsert.back().second.reassign_ids();
}
parentListPtr->insert_ordered_list_and_send_create(parentListPtr, toInsert);
}
}, NetObjManager::SendUpdateType::SEND_TO_ALL, nullptr);
return true;
}
bool redo(WorldUndoManager& undoMan) override {
return false;
}
~MoveBookmarksWorldUndoAction() {}
std::unordered_map<WorldUndoManager::UndoObjectID, std::pair<std::vector<uint32_t>, std::vector<WorldUndoManager::UndoObjectID>>> undoEraseMap;
WorldUndoManager::UndoObjectID insertParentUndoID;
uint32_t insertPosition;
std::vector<WorldUndoManager::UndoObjectID> insertedUndoIDs;
};
world.undo.push(std::make_unique<MoveBookmarksWorldUndoAction>(toEraseMapUndo, insertParentUndoID, insertUndoPosition, insertedUndoIDs));
}
}, selectionData);

View file

@ -41,8 +41,8 @@ void CanvasComponentContainer::register_class(World& w) {
});
}
std::shared_ptr<CanvasComponentContainer::CopyData> CanvasComponentContainer::get_data_copy() const {
auto toRet = std::make_shared<CopyData>();
std::unique_ptr<CanvasComponentContainer::CopyData> CanvasComponentContainer::get_data_copy() const {
auto toRet = std::make_unique<CanvasComponentContainer::CopyData>();
toRet->coords = coords;
toRet->obj = compAllocator->comp->get_data_copy();
return toRet;

View file

@ -6,6 +6,7 @@
#include <Helpers/NetworkingObjects/NetObjOrderedList.hpp>
#include <Helpers/NetworkingObjects/NetObjOwnerPtr.hpp>
#include "CanvasComponentAllocator.hpp"
#include "../WorldUndoManager.hpp"
class DrawingProgram;
class DrawingProgramLayerListItem;
@ -36,7 +37,7 @@ class CanvasComponentContainer {
CanvasComponentContainer(NetworkingObjects::NetObjManager& objMan, const CopyData& copyData);
static void register_class(World& w);
std::shared_ptr<CopyData> get_data_copy() const;
std::unique_ptr<CopyData> get_data_copy() const;
void save_file(cereal::PortableBinaryOutputArchive& a) const;
void load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version, NetworkingObjects::NetObjManager& objMan);
CanvasComponent& get_comp() const;

View file

@ -9,13 +9,6 @@ void DrawingProgramLayer::draw(SkCanvas* canvas, const DrawData& drawData) const
p.obj->draw(canvas, drawData);
}
DrawingProgramLayerInitData DrawingProgramLayer::get_init_data() const {
DrawingProgramLayerInitData toRet;
for(auto& p : *components)
toRet.components.emplace_back(p.obj->get_data_copy());
return toRet;
}
void DrawingProgramLayer::set_component_list_callbacks(DrawingProgramLayerListItem& layerListItem, DrawingProgramLayerManager& layerMan) {
auto insertCallback = [&](const CanvasComponentContainer::ObjInfoIterator& c) {
c->obj->objInfo = c;

View file

@ -6,8 +6,9 @@
class DrawingProgramLayerListItem;
class DrawingProgramLayerManager;
struct DrawingProgramLayerInitData {
std::vector<std::shared_ptr<CanvasComponentContainer::CopyData>> components;
struct DrawingProgramComponentUndoData {
WorldUndoManager::UndoObjectID undoID;
std::unique_ptr<CanvasComponentContainer::CopyData> copyData;
};
class DrawingProgramLayer {
@ -17,7 +18,6 @@ class DrawingProgramLayer {
void get_flattened_component_list(std::vector<CanvasComponentContainer::ObjInfo*>& objList) const;
void set_to_erase();
void scale_up(const WorldScalar& scaleUpAmount);
DrawingProgramLayerInitData get_init_data() const;
CanvasComponentContainer::NetListOwnerPtr components;

View file

@ -7,10 +7,6 @@
class DrawingProgramLayerListItem;
class DrawingProgramLayerManager;
struct DrawingProgramLayerFolderInitData {
std::vector<DrawingProgramLayerFolderInitData> folderList;
};
class DrawingProgramLayerFolder {
public:
void draw(SkCanvas* canvas, const DrawData& drawData) const;

View file

@ -23,6 +23,54 @@ DrawingProgramLayerListItem::DrawingProgramLayerListItem(NetworkingObjects::NetO
displayData = netObjMan.make_obj<DisplayData>();
}
DrawingProgramLayerListItem::DrawingProgramLayerListItem(World& w, const DrawingProgramLayerListItemUndoData& initData) {
if(initData.folderData) {
auto& initFolderList = initData.folderData.value();
folderData = std::make_unique<DrawingProgramLayerFolder>();
folderData->folderList = w.netObjMan.make_obj<NetworkingObjects::NetObjOrderedList<DrawingProgramLayerListItem>>();
for(auto& initFolder : initFolderList) {
auto it = folderData->folderList->emplace_back_direct(folderData->folderList, w, initFolder);
w.undo.register_new_netid_to_existing_undoid(initFolder.undoID, it->obj.get_net_id());
}
}
else {
auto& initComponentList = initData.layerData.value();
layerData = std::make_unique<DrawingProgramLayer>();
layerData->components = w.netObjMan.make_obj<CanvasComponentContainer::NetList>();
for(auto& initComponent : initComponentList) {
auto it = layerData->components->emplace_back_direct(layerData->components, w.netObjMan, *initComponent.copyData);
w.undo.register_new_netid_to_existing_undoid(initComponent.undoID, it->obj.get_net_id());
}
}
nameData = w.netObjMan.make_obj<NameData>();
nameData->name = initData.metaInfo.name;
displayData = w.netObjMan.make_obj<DisplayData>();
displayData->alpha = initData.metaInfo.alpha;
displayData->blendMode = initData.metaInfo.blendMode;
}
DrawingProgramLayerListItemUndoData DrawingProgramLayerListItem::get_undo_data(WorldUndoManager& u) const {
DrawingProgramLayerListItemUndoData toRet;
toRet.metaInfo.name = nameData->name;
toRet.metaInfo.blendMode = displayData->blendMode;
toRet.metaInfo.alpha = displayData->alpha;
if(folderData) {
toRet.folderData = std::vector<DrawingProgramLayerListItemUndoData>();
auto& folderListToRet = toRet.folderData.value();
for(auto& f : *folderData->folderList) {
folderListToRet.emplace_back(f.obj->get_undo_data(u));
folderListToRet.back().undoID = u.get_undoid_from_netid(f.obj.get_net_id());
}
}
else {
toRet.layerData = std::vector<DrawingProgramComponentUndoData>();
auto& layerListToRet = toRet.layerData.value();
for(auto& c : *layerData->components)
layerListToRet.emplace_back(u.get_undoid_from_netid(c.obj.get_net_id()), c.obj->get_data_copy());
}
return toRet;
}
void DrawingProgramLayerListItem::reassign_netobj_ids_call() {
if(folderData)
folderData->folderList.reassign_ids();

View file

@ -13,23 +13,25 @@ class DrawingProgramLayer;
class World;
class DrawingProgramLayerManager;
struct DrawingProgramLayerListItemMetaInfoInitData {
struct DrawingProgramLayerListItemMetaInfoUndoData {
std::string name;
float alpha = 1.0f;
SerializedBlendMode blendMode = SerializedBlendMode::SRC_OVER;
};
struct DrawingProgramLayerListItemInitData {
DrawingProgramLayerListItemMetaInfoInitData metaInfo;
std::optional<DrawingProgramLayerFolderInitData> folderData;
std::optional<DrawingProgramLayerInitData> layerData;
struct DrawingProgramLayerListItemUndoData {
WorldUndoManager::UndoObjectID undoID;
DrawingProgramLayerListItemMetaInfoUndoData metaInfo;
std::optional<std::vector<DrawingProgramLayerListItemUndoData>> folderData;
std::optional<std::vector<DrawingProgramComponentUndoData>> layerData;
};
class DrawingProgramLayerListItem {
public:
DrawingProgramLayerListItem();
DrawingProgramLayerListItem(NetworkingObjects::NetObjManager& netObjMan, const std::string& initName, bool isFolder);
DrawingProgramLayerListItem(NetworkingObjects::NetObjManager& netObjMan, const DrawingProgramLayerListItemInitData& initData);
DrawingProgramLayerListItem(World& w, const DrawingProgramLayerListItemUndoData& undoData);
DrawingProgramLayerListItemUndoData get_undo_data(WorldUndoManager& u) const;
bool is_folder() const;
DrawingProgramLayerFolder& get_folder() const;
DrawingProgramLayer& get_layer() const;

View file

@ -63,7 +63,7 @@ class MainProgram {
} window;
struct Clipboard {
std::vector<std::shared_ptr<CanvasComponentContainer::CopyData>> components;
std::vector<std::unique_ptr<CanvasComponentContainer::CopyData>> components;
std::unordered_map<NetworkingObjects::NetObjID, ResourceData> resources;
WorldVec pos;
WorldScalar inverseScale;

View file

@ -123,6 +123,16 @@ std::optional<NetworkingObjects::NetObjID> WorldUndoManager::get_netid_from_undo
return it->second;
}
bool WorldUndoManager::fill_netid_list_from_undoid_list(std::vector<NetworkingObjects::NetObjID>& netIDList, const std::vector<UndoObjectID>& undoIDList) {
for(UndoObjectID undoID : undoIDList) {
auto netIDOpt = get_netid_from_undoid(undoID);
if(!netIDOpt.has_value())
return false;
netIDList.emplace_back(netIDOpt.value());
}
return true;
}
std::vector<std::string> WorldUndoManager::get_front_undo_queue_names(unsigned count) {
std::vector<std::string> toRet;
for(auto& u : undoQueue | std::views::reverse) {

View file

@ -35,6 +35,7 @@ class WorldUndoManager {
UndoObjectID get_undoid_from_netid(const NetworkingObjects::NetObjID& netObjID);
void register_new_netid_to_existing_undoid(UndoObjectID existingUndoID, const NetworkingObjects::NetObjID& netObjID);
std::optional<NetworkingObjects::NetObjID> get_netid_from_undoid(UndoObjectID undoID);
bool fill_netid_list_from_undoid_list(std::vector<NetworkingObjects::NetObjID>& netIDList, const std::vector<UndoObjectID>& undoIDList);
std::vector<std::string> get_front_undo_queue_names(unsigned count);
World& world;