Search code examples
c++imgui

Drag and Drop Item list not working properly on ImGUI


Im using ImGUI and I want to implement a layer menu for the images and to move them im using Drag to reorder items in a vector.

Sometimes it works just fine but others the images just jumps from the current position to a random one.

enter image description here

for (int i = 0; i < this->Images->size(); i++) {
    ImGui::Image((void*)(intptr_t)this->Images->at(i).texture, ImVec2(100 * temp_percentage, 100 * temp_percentage));
    ImGui::SameLine();
    ImGui::Selectable(this->Images->at(i).name.c_str());
    if (ImGui::IsItemActive() && !ImGui::IsItemHovered())
    {
        int n_next = i + (ImGui::GetMouseDragDelta(0).y < 0.f ? -1 : 1);
        if (n_next >= 0 && n_next < this->Images->size())
        {
            std::swap(this->Images->at(i), this->Images->at(n_next));
            *this->CurrentImage = this->Images->front();
            centerImage();
            ImGui::ResetMouseDragDelta();
        }
    }
    ImGui::Separator();
}

Solution

  • The problem lies at !ImGui::IsItemHovered(), there is small spacing between the lines (cell, selectable,... ), so when the mouse hovers over that spacing, the item isn't hovered but still is actived, and therefore will execute the swap and reset mouse delta multiple times making it goes to the top or bottom of the list. This will also happen if the mouse goes out of the table/window bounds.

    To make the problem more visible, you can make the spacing bigger using ImGui::GetStyle().ItemSpacing.y = 50.f;.

    To actually fix the problem, you'll have to calculate the item index using the mouse position, here is a way to do it, tho not perfect but it works.

    
    ImGuiStyle& style = ImGui::GetStyle();
    
    ImVec2 windowPosition = ImGui::GetWindowPos();
    ImVec2 cursorPosition = ImGui::GetCursorPos();
    
    // this is not a pixel perfect position
    // you can try to make it more accurate by adding some offset
    ImVec2 itemPosition (
        windowPosition.x + cursorPosition.x, 
        windowPosition.y + cursorPosition.y - style.ItemSpacing.y
    );
    
    for (int i = 0; i < this->Images->size(); i++) {
        ImGui::Image((void*)(intptr_t)this->Images->at(i).texture, ImVec2(100 * temp_percentage, 100 * temp_percentage));
        ImGui::SameLine();
        ImGui::Selectable(this->Images->at(i).name.c_str());
    
        if (ImGui::IsItemActive() && ImGui::IsMouseDragging(0))
        {
            int n_next = floorf((ImGui::GetMousePos().y - itemPosition.y) / itemHeight);
    
            if (n_next != i && n_next >= 0 && n_next < this->Images->size())
            {
                std::swap(this->Images->at(i), this->Images->at(n_next));
                *this->CurrentImage = this->Images->front();
                centerImage();
            }
        }
        ImGui::Separator();
    }
    

    There is also another problem in your code, if there are multiple items with the same name, ImGui::IsItemActive() will return true for all of them if one is actived. You can fix this easily by adding ##some_unique_string after the name, for example ImGui::Selectable("Image#image_1") will just display Image.