Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Little Drag and Drop demo #1931

Open
sonoro1234 opened this issue Jul 6, 2018 · 9 comments
Open

Little Drag and Drop demo #1931

sonoro1234 opened this issue Jul 6, 2018 · 9 comments
Labels
drag drop drag and drop

Comments

@sonoro1234
Copy link

sonoro1234 commented Jul 6, 2018

LuaJIT code with a drag and drop demo
drag

We have a butnum array

local butnum = ffi.new("int[16]")
for i=0,15 do butnum[i] = i end

and use this code

for i = 0,15 do
    ig.Button("but"..butnum[i], ig.ImVec2(50,50))
    if ig.BeginDragDropSource() then
        anchor.data = ffi.new("int[1]",i)
        ig.SetDragDropPayload("ITEMN",anchor.data, ffi.sizeof"int", C.ImGuiCond_Once);
        ig.Button("drag"..butnum[i], ig.ImVec2(50,50));
        ig.EndDragDropSource();
    end
    if ig.BeginDragDropTarget() then
        local payload = ig.AcceptDragDropPayload("ITEMN")
        if (payload~=nil) then
            assert(payload.DataSize == ffi.sizeof"int");
            local numptr = ffi.cast("int*",payload.Data)
            --swap numbers
            butnum[numptr[0]], butnum[i] = butnum[i], butnum[numptr[0]]
        end
        ig.EndDragDropTarget();
    end
    if ((i % 4) < 3) then ig.SameLine() end
end
@ocornut ocornut added the drag drop drag and drop label Jul 7, 2018
@ocornut
Copy link
Owner

ocornut commented Jul 8, 2018

I now added an equivalent example in ShowDemoWindow(), with a little more options

image

Also did some other work on drag and drop. However some things I couldn't do:

  1. Dynamically change the "Swap" tooltip to show both names. Even though there is a ImGuiDragDropFlags_AcceptNoPreviewTooltip flag to disable the source tooltip from the target site (this is working), it is tricky to have a "Swap XXX with ..." on the source site and "Swap XXX with YYY" on the target site because the tooltips may be submitted out of order. Added a todo note to try solving that later.

  2. I wanted to add an example of re-ordering selectables.
    Although it works I didn't commit it because a) there is a frame where no Selectable() is highlited because Selectable() by default doesn't display highlight when holding on an item that is not also hovered. Will have to think how best to improve that. b) It feels like a little misleading use case for the drag and drop API, this is possibly easier to implement without the api? I'll have to experiment with that idea first.

Here's the code for reference
image

// Reordering is actually a rather odd use case for the drag and drop API which is meant to carry data around. 
// Here we implement a little demo using the drag and drop primitives, but we could perfectly achieve the same results by using a mixture of
//  IsItemActive() on the source item + IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem) on target items.
// This demo however serves as a pretext to demonstrate some of the flags you can use with BeginDragDropSource() and AcceptDragDropPayload().
ImGui::BulletText("Drag and drop to re-order");
ImGui::Indent();
static const char* names[6] = { "1. Adbul", "2. Alfonso", "3. Aline", "4. Amelie", "5. Anna", "6. Arthur" };
int move_from = -1, move_to = -1;
for (int n = 0; n < IM_ARRAYSIZE(names); n++)
{
    ImGui::Selectable(names[n]);

    ImGuiDragDropFlags src_flags = 0;
    src_flags |= ImGuiDragDropFlags_SourceNoDisableHover;     // Keep the source displayed as hovered
    src_flags |= ImGuiDragDropFlags_SourceNoHoldToOpenOthers; // Because our dragging is local, we disable the feature of opening foreign treenodes/tabs while dragging
    //src_flags |= ImGuiDragDropFlags_SourceNoPreviewTooltip; // Hide the tooltip
    if (ImGui::BeginDragDropSource(src_flags))
    {
        if (!(src_flags & ImGuiDragDropFlags_SourceNoPreviewTooltip))
            ImGui::Text("Moving \"%s\"", names[n]);
        ImGui::SetDragDropPayload("DND_DEMO_NAME", &n, sizeof(int));
        ImGui::EndDragDropSource();
    }

    if (ImGui::BeginDragDropTarget())
    {
        ImGuiDragDropFlags target_flags = 0;
        target_flags |= ImGuiDragDropFlags_AcceptBeforeDelivery;    // Don't wait until the delivery (release mouse button on a target) to do something
        target_flags |= ImGuiDragDropFlags_AcceptNoDrawDefaultRect; // Don't display the yellow rectangle
        if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_DEMO_NAME", target_flags))
        {
            move_from = *(const int*)payload->Data;
            move_to = n;
        }
        ImGui::EndDragDropTarget();
    }
}

if (move_from != -1 && move_to != -1)
{
    // Reorder items
    int copy_dst = (move_from < move_to) ? move_from : move_to + 1;
    int copy_src = (move_from < move_to) ? move_from + 1 : move_to;
    int copy_count = (move_from < move_to) ? move_to - move_from : move_from - move_to;
    const char* tmp = names[move_from];
    //printf("[%05d] move %d->%d (copy %d..%d to %d..%d)\n", ImGui::GetFrameCount(), move_from, move_to, copy_src, copy_src + copy_count - 1, copy_dst, copy_dst + copy_count - 1);
    memmove(&names[copy_dst], &names[copy_src], (size_t)copy_count * sizeof(const char*));
    names[move_to] = tmp;
    ImGui::SetDragDropPayload("DND_DEMO_NAME", &move_to, sizeof(int)); // Update payload immediately so on the next frame if we move the mouse to an earlier item our index payload will be correct. This is odd and showcase how the DnD api isn't best presented in this example.
}
ImGui::Unindent();

@sonoro1234
Copy link
Author

sonoro1234 commented Jul 10, 2018

dragreorder

this is a reordering without move_from and move_to but with two extra data: first flag and dragdata
(seems that SetDragDropPayload data would not be necessary here!! )

local butnum = {} 
for i=1,16 do butnum[i] = i end
local first = true
local dragdata = nil
local anchor ={} --to avoid gc 

function GL.imgui()
for i = 1,16 do
    if butnum[i] then
        ig.Button("but"..butnum[i].."###"..i, ig.ImVec2(50,50))
    else
        ig.Button("".."###"..i, ig.ImVec2(50,50))
    end

    if ig.BeginDragDropSource() then
        if (first) then
            first = false
            dragdata = butnum[i]
            table.remove(butnum,i)
        end
        anchor.data = ffi.new("int[1]",dragdata)
        ig.SetDragDropPayload("ITEMN",anchor.data, ffi.sizeof"int")
        ig.Button("drag"..dragdata, ig.ImVec2(50,50));
        ig.EndDragDropSource();
    end
    if ig.BeginDragDropTarget() then
        local payload = ig.AcceptDragDropPayload("ITEMN")
        if (payload~=nil) then
            first = true
            assert(payload.DataSize == ffi.sizeof"int");
            local num = ffi.cast("int*",payload.Data)[0]
            table.insert(butnum, i,num)
        end
        ig.EndDragDropTarget();
    end
    if (((i-1) % 4) < 3) then ig.SameLine() end
end
end

@ocornut
Copy link
Owner

ocornut commented Jul 10, 2018

this is a reordering without move_from and move_to but with two extra data: first flag and dragdata
(seems that SetDragDropPayload data would not be necessary here!! )

I don't think it is good from a UX perspective to shift everything on the start of the drag though, it's very confusing. You could leave the source slot in place or empty, and then update the order either on move either on release.

I was trying to get the re-ordering demo to live-order items (and here the tool-tip isn't necessary)
drag2
It works except the highlight on the drag objects is inconsistent during the frame the mouse strays away from it, resulting in a small flicker.

@sonoro1234
Copy link
Author

Do you mean?
dragreorder2

local butnum = {} 
for i=1,16 do butnum[i] = i end

local anchor ={}
function GL.imgui()
    
    for i = 1,16 do
    
        ig.Button("but"..butnum[i].."###"..i, ig.ImVec2(50,50))
    
        if ig.BeginDragDropSource() then
            anchor.data = ffi.new("int[1]",i)
            ig.SetDragDropPayload("ITEMN",anchor.data, ffi.sizeof"int")--, C.ImGuiCond_Once);
            ig.Button("drag"..butnum[i], ig.ImVec2(50,50));
            ig.EndDragDropSource();
        end
        if ig.BeginDragDropTarget() then
            local payload = ig.AcceptDragDropPayload("ITEMN")
            if (payload~=nil) then
                assert(payload.DataSize == ffi.sizeof"int");
                local num = ffi.cast("int*",payload.Data)[0]
                local tmp = butnum[num]
                table.remove(butnum,num)
                table.insert(butnum, i,tmp)
            end
            ig.EndDragDropTarget();
        end
        if (((i-1) % 4) < 3) then ig.SameLine() end
    end
end

@ocornut
Copy link
Owner

ocornut commented Jul 10, 2018

If the movement isn't reflected immediately then to be proper we should be displaying a target location in the form of a yellow LINE separating two items. This is a common pattern we'll also want to nicely resolve for tree nodes.

@zeyangl
Copy link

zeyangl commented Nov 11, 2020

Any plan to add this yellow LINE separating two items? Just tried implementing reorder today and found this issue.

@ebkgne
Copy link

ebkgne commented Sep 11, 2022

@zeyangl : I think adding Imgui::Separator as a drop target between elements can do it with some tweaks

@timbeaudet
Copy link

Jumping in as I'd love a yellow line when drag&drop separating two options for reordering... Please!

@ocornut
Copy link
Owner

ocornut commented Nov 21, 2023

I think some of it is discussed in #2823 but I am not sure what would be a good and lightweight API for it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
drag drop drag and drop
Projects
None yet
Development

No branches or pull requests

5 participants