From c97266769bf6d5aa234e195500f9cb8ccd449d19 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Tue, 7 Apr 2026 10:24:02 +0300 Subject: [PATCH] Fixed moving bookmarks --- src/Bookmarks/BookmarkManager.cpp | 38 +++-- src/GUIStuff/Elements/TreeListing.cpp | 234 +++++++++++++++++--------- src/GUIStuff/Elements/TreeListing.hpp | 7 + 3 files changed, 191 insertions(+), 88 deletions(-) diff --git a/src/Bookmarks/BookmarkManager.cpp b/src/Bookmarks/BookmarkManager.cpp index 332ef60..d8c7fce 100644 --- a/src/Bookmarks/BookmarkManager.cpp +++ b/src/Bookmarks/BookmarkManager.cpp @@ -125,7 +125,22 @@ void BookmarkManager::setup_list_gui(const char* id) { else nameToEdit.clear(); }, - .moveObj = [&](const std::vector& objectIndices, const TreeListingObjIndexList& newObjIndex) { + .moveObj = [&](const std::vector& objectIndicesTreeListing, const TreeListingObjIndexList& newObjIndexTreeListing) { + NetObjID listObj; + size_t index; + struct ParentObjectIDPair { + NetObjID parent; + NetObjID object; + }; + std::vector objsToInsert; + + { + listObj = get_bookmark_parent_from_obj_index(newObjIndexTreeListing).get_net_id(); + index = newObjIndexTreeListing.back(); + for(auto& objIndex : objectIndicesTreeListing) + objsToInsert.emplace_back(get_bookmark_parent_from_obj_index(objIndex).get_net_id(), get_bookmark_from_obj_index(objIndex).get_net_id()); + } + std::unordered_map, std::vector>> toEraseMapUndo; WorldUndoManager::UndoObjectID insertParentUndoID; uint32_t insertUndoPosition; @@ -133,15 +148,15 @@ void BookmarkManager::setup_list_gui(const char* id) { world.netObjMan.send_multi_update_messsage([&]() { std::unordered_map>> toEraseMap; - uint32_t newIndex = newObjIndex.back(); + uint32_t newIndex = index; std::vector> toInsertObjPtrs; - auto& listPtr = get_bookmark_parent_from_obj_index(newObjIndex)->get_folder_list(); + auto& listPtr = world.netObjMan.get_obj_temporary_ref_from_id(listObj)->get_folder_list(); // Map parent folders to list of objects that should be erased from them - for(size_t i = 0; i < objectIndices.size(); i++) { - auto parentBookmarkList = get_bookmark_parent_from_obj_index(objectIndices[i]); - toEraseMap[parentBookmarkList.get_net_id()].emplace_back(parentBookmarkList->get_folder_list()->at(objectIndices[i].back())); - if(newIndex != 0 && parentBookmarkList == listPtr && objectIndices[i].back() <= newObjIndex.back()) + for(size_t i = 0; i < objsToInsert.size(); i++) { + auto& parentListPtr = world.netObjMan.get_obj_temporary_ref_from_id(objsToInsert[i].parent)->get_folder_list(); + toEraseMap[objsToInsert[i].parent].emplace_back(parentListPtr->get(objsToInsert[i].object)); + if(newIndex != 0 && objsToInsert[i].parent == listObj && listPtr->get(objsToInsert[i].object)->pos <= index) newIndex--; } @@ -165,10 +180,9 @@ void BookmarkManager::setup_list_gui(const char* id) { // Insert objects in toInsertObjPtrs into the folder we have selected std::vector, NetObjOwnerPtr>> toInsert; auto insertIt = listPtr->at(newIndex); - for(uint32_t i = 0; i < objectIndices.size(); i++) { - auto o = get_bookmark_from_obj_index(objectIndices[i]); - auto it = std::find_if(toInsertObjPtrs.begin(), toInsertObjPtrs.end(), [&o](NetObjOwnerPtr& objPtr) { - return objPtr == o; + for(uint32_t i = 0; i < objsToInsert.size(); i++) { + auto it = std::find_if(toInsertObjPtrs.begin(), toInsertObjPtrs.end(), [id = objsToInsert[i].object](NetObjOwnerPtr& objPtr) { + return objPtr.get_net_id() == id; }); toInsert.emplace_back(insertIt, std::move(*it)); toInsert.back().second.reassign_ids(); @@ -176,7 +190,7 @@ void BookmarkManager::setup_list_gui(const char* id) { std::vector> insertedIterators = listPtr->insert_sorted_list_and_send_create(listPtr, toInsert); // Prepare insert list for undo - insertParentUndoID = world.undo.get_undoid_from_netid(listPtr.get_net_id()); + 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())); diff --git a/src/GUIStuff/Elements/TreeListing.cpp b/src/GUIStuff/Elements/TreeListing.cpp index 73e0d39..867137d 100644 --- a/src/GUIStuff/Elements/TreeListing.cpp +++ b/src/GUIStuff/Elements/TreeListing.cpp @@ -26,6 +26,16 @@ bool operator<(const GUIStuff::TreeListingObjIndexList& a, const GUIStuff::TreeL namespace GUIStuff { +bool is_tree_listing_obj_index_parent(const TreeListingObjIndexList& parentToCheck, const TreeListingObjIndexList& obj) { + if(parentToCheck.size() >= obj.size()) + return false; + for(size_t i = 0; i < parentToCheck.size(); i++) { + if(parentToCheck[i] != obj[i]) + return false; + } + return true; +} + using namespace ElementHelpers; TreeListing::TreeListing(GUIManager& gui): @@ -40,95 +50,167 @@ void TreeListing::layout(const Clay_ElementId& id, const Data& newDisplayData) { CLAY(id, { .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}} }) { - scroll_area_many_entries(gui, "scroll", { - .entryHeight = ENTRY_HEIGHT, - .entryCount = flattenedIndexList.size(), - .elementContent = [&](size_t i) { - const ObjInfo& objInfo = flattenedIndexList[i]; - gui.element("elem", [&](LayoutElement*, const Clay_ElementId& lId) { - SkColor4f backgroundColor; - if(d.selectedIndices && d.selectedIndices->contains(flattenedIndexList[i].objIndex)) - backgroundColor = gui.io.theme->backColor1; - else - backgroundColor = gui.io.theme->backColor2; + gui.element("scroll parent", [&] (LayoutElement*, const Clay_ElementId& lIdParent) { + CLAY(lIdParent, { + .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}} + }) { + scroll_area_many_entries(gui, "scroll", { + .entryHeight = ENTRY_HEIGHT, + .entryCount = flattenedIndexList.size(), + .elementContent = [&](size_t i) { + const ObjInfo& objInfo = flattenedIndexList[i]; + gui.element("elem", [&](LayoutElement*, const Clay_ElementId& lId) { + SkColor4f backgroundColor; + if(d.selectedIndices && d.selectedIndices->contains(flattenedIndexList[i].objIndex)) + backgroundColor = gui.io.theme->backColor1; + else + backgroundColor = gui.io.theme->backColor2; - CLAY(lId, { - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, - .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER} - }, - .backgroundColor = convert_vec4(backgroundColor) - }) { - if(objInfo.objIndex.size() > 1) { - CLAY_AUTO_ID({ - .layout = {.sizing = {.width = CLAY_SIZING_FIXED((objInfo.objIndex.size() - 1) * ICON_SIZE), .height = CLAY_SIZING_GROW(0)}} - }) {} - } - CLAY_AUTO_ID({ - .layout = {.sizing = {.width = CLAY_SIZING_FIXED(ICON_SIZE), .height = CLAY_SIZING_FIXED(ICON_SIZE)}} - }) { - if(!objInfo.isDirectory) - d.drawNonDirectoryObjIconGUI(objInfo.objIndex); - else { - gui.set_z_index_keep_clipping_region(gui.get_z_index() + 1, [&] { - svg_icon_button(gui, "open dir button", objInfo.isOpen ? "data/icons/droparrow.svg" : "data/icons/droparrowclose.svg", { - .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, - .size = ICON_SIZE, - .onClick = [&] { - d.setDirectoryOpen(objInfo.objIndex, !objInfo.isOpen); - if(d.selectedIndices) { - d.selectedIndices->clear(); + uint16_t borderWidth = (isDragging && i == dragIndexEnd && dragIndexStart != dragIndexEnd) ? 1 : 0; + + CLAY(lId, { + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, + .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER} + }, + .backgroundColor = convert_vec4(backgroundColor), + .border = { + .color = convert_vec4(gui.io.theme->frontColor1), + .width = { + .left = 0, + .right = 0, + .top = borderWidth, + .bottom = 0, + .betweenChildren = 0 + } + } + }) { + if(objInfo.objIndex.size() > 1) { + CLAY_AUTO_ID({ + .layout = {.sizing = {.width = CLAY_SIZING_FIXED((objInfo.objIndex.size() - 1) * ICON_SIZE), .height = CLAY_SIZING_GROW(0)}} + }) {} + } + CLAY_AUTO_ID({ + .layout = {.sizing = {.width = CLAY_SIZING_FIXED(ICON_SIZE), .height = CLAY_SIZING_FIXED(ICON_SIZE)}} + }) { + if(!objInfo.isDirectory) + d.drawNonDirectoryObjIconGUI(objInfo.objIndex); + else { + gui.set_z_index_keep_clipping_region(gui.get_z_index() + 1, [&] { + svg_icon_button(gui, "open dir button", objInfo.isOpen ? "data/icons/droparrow.svg" : "data/icons/droparrowclose.svg", { + .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, + .size = ICON_SIZE, + .onClick = [&] { + d.setDirectoryOpen(objInfo.objIndex, !objInfo.isOpen); + if(d.selectedIndices) { + d.selectedIndices->clear(); + if(d.onSelectChange) d.onSelectChange(); + } + } + }); + }); + } + } + d.drawObjGUI(objInfo.objIndex); + } + }, LayoutElement::Callbacks { + .mouseButton = [&, i] (LayoutElement* l, const InputManager::MouseButtonCallbackArgs& button) { + if(l->mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down) { + gui.set_post_callback_func([&, i, buttonClicks = button.clicks]{ + auto& objIndex = flattenedIndexList[i].objIndex; + if(d.selectedIndices) { + auto it = d.selectedIndices->find(objIndex); + bool wasSelected = it != d.selectedIndices->end(); + + if(gui.io.input->key(InputManager::KEY_GENERIC_LSHIFT).held) { + if(!wasSelected) { + d.selectedIndices->emplace(objIndex); + if(d.onSelectChange) d.onSelectChange(); + } + } + else if(gui.io.input->key(InputManager::KEY_GENERIC_LCTRL).held) { + if(!wasSelected) + d.selectedIndices->emplace(objIndex); + else + d.selectedIndices->erase(it); if(d.onSelectChange) d.onSelectChange(); } + else if(buttonClicks == 2) { + if(d.onDoubleClick) d.onDoubleClick(objIndex); + } + else { + if(buttonClicks == 2 && d.onDoubleClick) d.onDoubleClick(objIndex); + if(!wasSelected) { + d.selectedIndices->clear(); + d.selectedIndices->emplace(objIndex); + if(d.onSelectChange) d.onSelectChange(); + } + isDragging = true; + dragIndexStart = dragIndexEnd = i; + } } + else if(buttonClicks == 2) + if(d.onDoubleClick) d.onDoubleClick(objIndex); }); - }); - } - } - d.drawObjGUI(objInfo.objIndex); - } - }, LayoutElement::Callbacks { - .mouseButton = [&, i] (LayoutElement* l, const InputManager::MouseButtonCallbackArgs& button) { - if(l->mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down) { - gui.set_post_callback_func([&]{ - auto& objIndex = flattenedIndexList[i].objIndex; - if(d.selectedIndices) { - auto it = d.selectedIndices->find(objIndex); - bool wasSelected = it != d.selectedIndices->end(); - - if(gui.io.input->key(InputManager::KEY_GENERIC_LSHIFT).held) { - if(!wasSelected) { - d.selectedIndices->emplace(objIndex); - if(d.onSelectChange) d.onSelectChange(); - } - } - else if(gui.io.input->key(InputManager::KEY_GENERIC_LCTRL).held) { - if(!wasSelected) - d.selectedIndices->emplace(objIndex); - else - d.selectedIndices->erase(it); - if(d.onSelectChange) d.onSelectChange(); - } - else if(!wasSelected || d.selectedIndices->size() != 1) { - d.selectedIndices->clear(); - d.selectedIndices->emplace(objIndex); - if(d.onSelectChange) d.onSelectChange(); - } - else if(button.clicks == 2) - if(d.onDoubleClick) d.onDoubleClick(objIndex); + gui.set_to_layout(); } - else if(button.clicks == 2) - if(d.onDoubleClick) d.onDoubleClick(objIndex); - }); - gui.set_to_layout(); - } + }, + .mouseMotion = [&, i] (LayoutElement* l, const InputManager::MouseMotionCallbackArgs& motion) { + if(isDragging && l->mouseHovering && dragIndexEnd != i) { + dragIndexEnd = i; + gui.set_to_layout(); + } + } + }); } }); } + }, LayoutElement::Callbacks { + .mouseButton = [&] (LayoutElement* l, const InputManager::MouseButtonCallbackArgs& button) { + if(button.button == InputManager::MouseButton::LEFT && !button.down) { + if(isDragging) { + gui.set_to_layout(); + isDragging = false; + if(l->mouseHovering || l->childMouseHovering) { + if(dragIndexStart != dragIndexEnd) + gui.set_post_callback_func([&] {move_selected_objects();}); + else if(!gui.io.input->key(InputManager::KEY_GENERIC_LSHIFT).held && !gui.io.input->key(InputManager::KEY_GENERIC_LCTRL).held) { + gui.set_post_callback_func([&, i = dragIndexEnd] { + if(d.selectedIndices->size() != 1 || !d.selectedIndices->contains(flattenedIndexList[i].objIndex)) { + d.selectedIndices->clear(); + d.selectedIndices->emplace(flattenedIndexList[i].objIndex); + if(d.onSelectChange) d.onSelectChange(); + } + }); + } + } + } + } + } }); } } +void TreeListing::move_selected_objects() { + if(d.selectedIndices && dragIndexEnd < flattenedIndexList.size()) { + TreeListingObjIndexList objIndexToMoveTo = flattenedIndexList[dragIndexEnd].objIndex; + // Check if object is about to move into itself (wrong movement) + bool wrongMovement = false; + for(const TreeListingObjIndexList& o : *d.selectedIndices) { + if(is_tree_listing_obj_index_parent(o, objIndexToMoveTo)) { + wrongMovement = true; + break; + } + } + if(!wrongMovement) { + std::vector toMove(d.selectedIndices->begin(), d.selectedIndices->end()); + d.selectedIndices->clear(); + if(d.onSelectChange) d.onSelectChange(); + if(d.moveObj) d.moveObj(toMove, objIndexToMoveTo); + } + } +} + void TreeListing::recursive_visible_flattened_obj_list(std::vector& objs, const TreeListingObjIndexList& objIndex) { std::optional dirInfo = d.dirInfo(objIndex); bool isRoot = objIndex.empty(); diff --git a/src/GUIStuff/Elements/TreeListing.hpp b/src/GUIStuff/Elements/TreeListing.hpp index d356539..f5da59d 100644 --- a/src/GUIStuff/Elements/TreeListing.hpp +++ b/src/GUIStuff/Elements/TreeListing.hpp @@ -5,6 +5,7 @@ namespace GUIStuff { typedef std::vector TreeListingObjIndexList; +bool is_tree_listing_obj_index_parent(const TreeListingObjIndexList& parentToCheck, const TreeListingObjIndexList& obj); } bool operator<(const GUIStuff::TreeListingObjIndexList& a, const GUIStuff::TreeListingObjIndexList& b); @@ -45,6 +46,12 @@ class TreeListing : public Element { private: void recursive_visible_flattened_obj_list(std::vector& objs, const TreeListingObjIndexList& objIndex); + void move_selected_objects(); + + bool isDragging = false; + size_t dragIndexStart; + size_t dragIndexEnd; + std::vector flattenedIndexList; Data d; };