Fixed moving bookmarks

This commit is contained in:
Yousef Khadadeh 2026-04-07 10:24:02 +03:00
parent 915fbe598b
commit c97266769b
3 changed files with 191 additions and 88 deletions

View file

@ -125,7 +125,22 @@ void BookmarkManager::setup_list_gui(const char* id) {
else
nameToEdit.clear();
},
.moveObj = [&](const std::vector<TreeListingObjIndexList>& objectIndices, const TreeListingObjIndexList& newObjIndex) {
.moveObj = [&](const std::vector<TreeListingObjIndexList>& objectIndicesTreeListing, const TreeListingObjIndexList& newObjIndexTreeListing) {
NetObjID listObj;
size_t index;
struct ParentObjectIDPair {
NetObjID parent;
NetObjID object;
};
std::vector<ParentObjectIDPair> 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<WorldUndoManager::UndoObjectID, std::pair<std::vector<uint32_t>, std::vector<WorldUndoManager::UndoObjectID>>> 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<NetObjID, std::vector<NetObjOrderedListIterator<BookmarkListItem>>> toEraseMap;
uint32_t newIndex = newObjIndex.back();
uint32_t newIndex = index;
std::vector<NetObjOwnerPtr<BookmarkListItem>> toInsertObjPtrs;
auto& listPtr = get_bookmark_parent_from_obj_index(newObjIndex)->get_folder_list();
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 < 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<BookmarkListItem>(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<std::pair<NetObjOrderedListIterator<BookmarkListItem>, NetObjOwnerPtr<BookmarkListItem>>> 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<BookmarkListItem>& 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<BookmarkListItem>& 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<NetObjOrderedListIterator<BookmarkListItem>> 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()));

View file

@ -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<LayoutElement>("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<LayoutElement>("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<LayoutElement>("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<Clay_Color>(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<Clay_Color>(backgroundColor),
.border = {
.color = convert_vec4<Clay_Color>(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<TreeListingObjIndexList> 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<ObjInfo>& objs, const TreeListingObjIndexList& objIndex) {
std::optional<DirectoryInfo> dirInfo = d.dirInfo(objIndex);
bool isRoot = objIndex.empty();

View file

@ -5,6 +5,7 @@
namespace GUIStuff {
typedef std::vector<size_t> 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<ObjInfo>& objs, const TreeListingObjIndexList& objIndex);
void move_selected_objects();
bool isDragging = false;
size_t dragIndexStart;
size_t dragIndexEnd;
std::vector<ObjInfo> flattenedIndexList;
Data d;
};