ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
59.31k stars 10.11k forks source link

Drag'n drop support #143

Closed damqui closed 4 years ago

damqui commented 9 years ago

hi, I'm considering using ImGui (to convert ooold mfc game tool), but one lacking thing is the support for drag'n'drop. Is this feature planned ? If no, I'll try to hack something in..

thanks for your work !

Ps: Side Question : concerning the uniqueness of Ids, err.. relying on a string crc32 seems weak to me. If I use ImGui for editing data, collisions might occur, and if so, I have no way of knowing if it happened. Am I missing something ?

ocornut commented 9 years ago

1) Do you mean drag and dropping of files into your application window? That's OS dependant and not likely to be part of core imgui. On Windows you only need to call DragAcceptFiles() and implement the WM_DROPFILES message.

I suppose we can consider to add helpers for common known platforms (such as Windows) to help setting up drag'n drop if there was an easy way to do so. But ImGui doesn't know about concept of windows messages. So it'll probably be a little convoluted for what's essentially replacing 10 lines of code.

2) If you are asking from the angle of "I may have two widgets with same string", the ID are built from a stack of strings that include the window, tree nodes, and which you can push into. So typically when creating repeated widgets in a loop you need to use PushID/PopID to manipulate the ID stack and make the sub-elements unique.

If you are asking from the angle of "what if even with the precaution above, CRC32 collision occurs ". It's not strong if you are thinking of long-lived data or are thinking of cryptography. But in practice for the purpose of UI i don't think you'll ever see a problem.

In fact, if anything, CRC32 is too slow and we should try to replace it with a faster hash function.

damqui commented 9 years ago

1) No I mean dragging a widget into another widget, like during edition, for connecting things (putting a file from a file list widget into a file slot of struct widget)

2) " the ID are built from a stack of strings that include the window, tree nodes, and which you can push into". from the code, the stack of strings is actually a stack of 32 bits values used as the crc seed. I am not really familiar with that "32bit crc as a unique identifier", but the fact that in a editing context, a collision may occur at random without knowing it (except than keeping a list of generated ids for this frame, and testing for collisions at each new id generation), makes me feel it could - randomly - cause edition errors. If such a collision occurs, what happens ? the widget becomes unselectable ? (the first one with same id is selected instead ? in that case, we have a feedback, and that's okay)

ocornut commented 9 years ago

1) That's be interesting. Some ideas for a simple version:

The draggable object can behave as a button, but when active it draws itself following the mouse cursor. That needs to be be done by pushing a fullscreen clipping rectangle.

Along with setting g,ActiveId it can set g.DraggingId and g.DraggingData.

(Bonus: It also need to be in a front-most ImDrawList. There's no explicit mechanism for that yet. Things like Tooltip are implicitly high-up in the draw order. Need to improve on explicitly deciding of a sort order. But for a start you don't need that anyway because the parent window of the draggable object will front-most already as soon as you click it. That is, until we implement "hold dragged object on another window to bring it to top". )

Pseudo-code

bool  IsDraggedInto(void** out_user_data)
{
if (IsItemHovered() && g.DraggingId && mouse_released)
{
   out_user_data = g.DraggingId; 
    return true;
}
return false;
}

2) I don't think there will be a problem. The values in the stack are CRC32 themselves (CRC32 are often seeded this way). The widgets needs to be visible to meaningfully collide. Yes one will be unselectable or clicking on one will trigger the others. The detail may vary depending on the exact widget type. You can easily test by doing:

ImGui::Button("hello");
ImGui::Button("hello");

See what happens. But I don't think you'll stumble on a accidental / random collision. The only collision you'll realistically fall into will be the common "duplicate label" type of collision which can be solved using ##prefix in strings or PushID/PopID.

ocornut commented 9 years ago

About CRC32 collision: http://stackoverflow.com/questions/14210298/probability-of-collision-crc32 294 hash values : probability of collision 1 in 100000 I think it's a fair risk to take. You rarely have 300 widgets on screen. I haven't seen a collision happen yet. I've used CRC32 to identify data in games and collision happens rarely - we are talking dozens/hundreds of thousands of persistent, long-lived identifiers. Here we talking hundreds of live identifiers in an application that runs interactively (and here I mean interactively as in: slow pace of update).

While it's not impossible that a collision happens my bet is that:

For persistent storage such as TreeNode open/close state and Column offsets they are stored per-window so won't collide accross windows, and could potentially be stored in separate maps per usage (e.g. tree nodes vs columns). This is all a non-problem really.

damqui commented 9 years ago

For drag'n drop : yes, it might be something as you propose. Drag'ndrop is really at the heart of some editors ( unity3d, unreal engine 4). But, if not available, there is still a poor-man way of making "connections" whithout true drag'n drop, using copy-pasting.

For crc, okay, i'll try (though I don't like voodoo :) )

Thanks for your answers. When I have time, I'll try to convert the generic editor that uses our own data type information to imgui, to see if it is viable.

ocornut commented 9 years ago

FYI with IsItemActive() and GetMouseDragDelta() there's a few stuff that can already be done now.

I did a quick hacky test for someone who wanted to know if re-ordering was possible:

imgui_dragging

// User state
const int COUNT = 5;
static const char* items_data[COUNT] = { "Item One", "Item Two", "Item Three", "Item Four", "Item Five" };
static int items_list[COUNT] = { 0, 1, 2, 3, 4 };

// Render + dragging
for (int n = 0; n < COUNT; n++)
{
    int item_no = items_list[n];
    ImGui::Selectable(items_data[item_no]);

    if (ImGui::IsItemActive() && !ImGui::IsItemHovered())
    {
        float drag_dy = ImGui::GetMouseDragDelta(0).y;
        if (drag_dy < 0.0f && n > 0)
        {
            // Swap
            items_list[n] = items_list[n-1];
            items_list[n-1] = item_no;
            ImGui::ResetMouseDragDelta();
        }
        else if (drag_dy > 0.0f && n < COUNT-1)
        {
            items_list[n] = items_list[n+1];
            items_list[n+1] = item_no;
            ImGui::ResetMouseDragDelta();
        }
    }
}

That could be done in a nicer and more sturdy way within the core library eventually.

Another possibility is to render out of order by manipulating the cursor position, but I think the way above fits better with the typical data structure.

extrawurst commented 9 years ago

these sort of things shouldn't be buried inside of tickets but be a listed in some kind of wiki

ocornut commented 9 years ago

Yes definitively, but it's still experimental here, and pretty hacky/broken (won't work with scrolling, in fact it will even exhibit a bug where active items aren't releasing the active flag when they are clipped even when the mouse button is released).

I really want to do a wiki with lots of demos. I wlll eventually.

ocornut commented 9 years ago

@Roflraging posted his own use of dragging to reorder widgets: https://www.youtube.com/watch?v=pmx4fltxTKg https://gist.github.com/Roflraging/f4af1d688237a7d367f9

In spite of the extra faff with the copying of his data structures, his approach of recording the index of the hovered fields is more robust and flexible. There's actually very little ImGui code too. I'll rewrite an example using this technique.

I think the post-it could be moved to match the original widget position pretty easily. In one of my test (not published) I actually pushed a no-clipping-rectangle and redrawn the same widget following the mouse, which can also work. Following this base if it's deemed robust enough we can probably come up with helpers.

extrawurst commented 9 years ago

awesome. showing the widget at the mouse is a must-have for drag/drop imho

unpacklo commented 9 years ago

I noticed in the code I copied into the gist that one of the comments was wrong, sorry if anybody was confused! https://gist.github.com/Roflraging/f4af1d688237a7d367f9#file-gistfile1-cpp-L93 https://gist.github.com/Roflraging/f4af1d688237a7d367f9/revisions

extrawurst commented 8 years ago

@ocornut did you rewrite that example that you mentioned ?

ocornut commented 8 years ago

Sorry I haven't got around to write an example for that (that's why I kept the issue opened for now)

extrawurst commented 8 years ago

I just implemented drag and drop using that concept: 2016-02-13 dragdrop

ftsf commented 8 years ago

I'm curious how do you do something on a "drop"?

ocornut commented 8 years ago

Right now you would have to track the dragging state, and then on mouse release test for item overlapping the mouse cursor. Something like if (WasDragging && MouseRelease(0) && IsItemHovered())

It could/should probably be wrapped into a helper function but we haven't got a standard way of storing the dragging state yet, so this is left to you to do the test above.

paniq commented 8 years ago

+1 for a working example similar to the GIF above, with perhaps a more complex drag object that demonstrates layouting as well?

What also interests me is highlighting the insertion point upon hover, depending on proximity, or changing a drop targets appearance when someone is dragging over it.

xaxxon commented 8 years ago

Adding something more interesting to the examples would be nice. Currently the only thing I can find is the "dragging" example under "keyboard/mouse/focus" which seems pretty buried - and not very clear as to how it would be useful, unlike the examples in this issue.

Would a patch to the examples code implementing your draggable list example be something you'd be interested in?

ocornut commented 8 years ago

Will do eventually. Some recent links:

LumixEngine https://twitter.com/mikulasflorek/status/740545248359813121 https://github.com/nem0/LumixEngine/blob/master/src/editor/asset_browser.cpp#L251

Paniq using his crazy language https://twitter.com/paniq/status/741170371504799745 implementation https://gist.github.com/paniq/4bf7848fc33ccf24ec1c39a114897da1 usage https://gist.github.com/paniq/d120eba2cf757c71d3e9ba191fad2c60

VinnyVicious commented 7 years ago

@Extrawurst, are you going to share that snippet? Seems very useful for the imgui community!

extrawurst commented 7 years ago

It is not a snippet, it's buried in my engine but it was based on the gist above

ocornut commented 6 years ago

Everyone: I will maybe be able to look at drag'n drop in the upcoming weeks/months, or at least just enough to contemplate using a standard drag'n drop api for Tabs and Docking. Maybe the early versions of course feature won't use a standard drag'n drop api but ultimately this may be the ideal goal so I'll keep that in mind.

If anyone has feedback or request on drag'n drop features please voice them! Also happy to see your usage (gif, code, etc. whatever you are happy to share or link to). Tagging for potential feedback @Extrawurst @Roflraging @paniq @nem0.

ocornut commented 6 years ago

If you are using drag'n drop idioms in your codebase, please read this thread: https://github.com/ocornut/imgui/issues/1382

Bonicx commented 6 years ago

Hi @ocornut I tried using ImGui::TreeNodeEx together with if (ImGui::IsItemActive() && !ImGui::IsItemHovered()) and have encountered a problem. I converted your example code of array into std::list style I loop through std::list<GameObject*> children image

https://youtu.be/mq94qBWaHrk As you can see that the reordering only works per item active, hover over, and then drag it down. I am not sure is it because of my game loop update that causes this issue where I can only move up/down of 1 item per drag and click.

ocornut commented 6 years ago

FYI there is a public Drag and Drop branch https://github.com/ocornut/imgui/tree/drag_and_drop With the current Drag and Drop API that I've been using in my other WIP branches (docking).

There's no example for it yet, but e.g you can try this. In ColorButton() (source)

if (g.ActiveId == id) // Optional micro-optimization you can do if you are in a low-level context where you already have access to this stuff (e.g. own widget), otherwise prob not worth bothering
{
    // With default flags (= no flags) to BeginDragDropSource(), by default it will Begin a tooltip with a transparent background so you can display a "preview" of your drag source.
    // For assets it could be just a filename, or a thumbnail, etc.
    // - Optional flag: ImGuiDragDropFlags_SourceNoAutoTooltip: disable the default tooltip if you want to do something custom.
    if (BeginDragDropSource())
    {
        // - Type is a user defined string of maximum 8 characters. Data is copied and held by imgui.
        // - Function returns true if someone is currently accepting our payload, which may be useful to react visually in the preview
        SetDragDropPayload("_COL4F", &col, sizeof(col), ImGuiCond_Once);

        ColorButton(desc_id, col, flags);
        SameLine();
        TextUnformatted("Color");

        EndDragDropSource();
    }
}

In ColorEdit4() (target)

if (window->DC.LastItemRectHoveredRect) // Optional micro-optimization you can do if you are in a low-level context where you already have access to this stuff (e.g. own widget), otherwise not worth bothering
    if (BeginDragDropTarget())
    {
        // - We can accept multiple payload
        // - Optional flag: ImGuiDragDropFlags_AcceptBeforeDelivery to peek into payload prior to delivery (~mouse release) so you can preview something.. Then test payload->IsDelivery() before copying data.
        // - Optional flag: ImGuiDragDropFlags_AcceptNoDrawDefaultRect to disable the default yellow highlight rectangle (NB: needs some better styling anyway, color currently hardcoded)
        if (const ImGuiPayload* payload = AcceptDragDropPayload("_COL4F"))
        {
            IM_ASSERT(payload->DataSize == sizeof(ImVec4));
            memcpy((float*)col, payload->Data, components * sizeof(float));
            value_changed = true;
        }
        EndDragDropTarget();
    }

GIF of that in action drag and drop colors

Current API:

// Drag and Drop
bool          BeginDragDropSource(ImGuiDragDropFlags flags = 0, int mouse_button = 0);                      // Call when the current item is active. If this return true, you can call SetDragDropPayload() + EndDragDropSource()
bool          SetDragDropPayload(const char* type, const void* data, size_t data_size, ImGuiCond cond = 0); // Type is a user defined string of maximum 8 characters. Strings starting with '_' are reserved for dear imgui internal types. Data is copied and held by imgui.
void          EndDragDropSource();
bool          BeginDragDropTarget();                                                                        // Call after submitting an item that may receive an item. If this returns true, you can call AcceptDragDropPayload() + EndDragDropTarget()
const ImGuiPayload* AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags = 0);                  // Accept contents of a given type. If ImGuiDragDropFlags_AcceptBeforeDelivery is set you can peek into the payload before the mouse button is released.
void          EndDragDropTarget();

Current flags:

// Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload()
enum ImGuiDragDropFlags_
{
    // BeginDragDropSource() flags
    ImGuiDragDropFlags_SourceNoAutoTooltip          = 1 << 0,       // By default, a successful call to BeginDragDropSource opens a tooltip so you can display a preview or description of the dragged contents. This flag disable this behavior.
    ImGuiDragDropFlags_SourceNoDisableHover         = 1 << 1,       // By default, when dragging we clear data so that IsItemHovered() will return true, to avoid subsequent user code submitting tooltips. This flag disable this behavior so you can still call IsItemHovered() on the source item.
    ImGuiDragDropFlags_SourceNoHoldToOpenOthers     = 1 << 2,       // Disable the behavior that allows to open tree nodes and collapsing header by holding over them while dragging a source item.
    ImGuiDragDropFlags_SourceAllowNullID            = 1 << 3,       // Allow items such as Text(), Image() that have no unique identifier to be used as drag source, by manufacturing a temporary identifier based on their window-relative position. This is extremely unusual within the dear imgui ecosystem and so we made it explicit.
    // AcceptDragDropPayload() flags
    ImGuiDragDropFlags_AcceptBeforeDelivery         = 1 << 10,      // AcceptDragDropPayload() will returns true even before the mouse button is released. You can then call IsDelivery() to test if the payload needs to be delivered.
    ImGuiDragDropFlags_AcceptNoDrawDefaultRect      = 1 << 11,      // Do not draw the default highlight rectangle when hovering over target.
    ImGuiDragDropFlags_AcceptPeekOnly               = ImGuiDragDropFlags_AcceptBeforeDelivery | ImGuiDragDropFlags_AcceptNoDrawDefaultRect  // For peeking ahead and inspecting the payload before delivery.
};

I have improved and iterated this code based on the need of my (yet unpushed) docking/tabs code and it handles some subtleties such as nested drop targets, or drag source/target without known identifiers.

I think the API is fairly stable but can probably only be finalized once I can look at those two use cases:

ocornut commented 6 years ago

I have merged the drag_and_drop branch into master now.

I consider the feature Beta (and marked it as such in imgui.h) as it is missing a few thing: A) There's no real demo yet (but the color widgets now support drag and drop). That's easy to fix, will work on demo code soonish. B) I haven't established that the api will be good enough to handle re-ordering/moving nodes, in a list or in a tree. C) I haven't established that the api will be good enough to build a bridge with OS native drag'n drop.

When we have proof-on-concepts to confirm that both B and C are faisible I'll mark the api as non-beta. If someone is interested in tackling either for their project let me know.

heroboy commented 6 years ago

Cool! I think it is interesting if I can copy a draw list as the content of dragging source. Currently clone a draw list is hard. default

ocornut commented 6 years ago

To clone a ImDrawList you need to deep copy the fields, I think ImVector is missing a = operator we could add one.

But may I ask why are you trying to do that?

heroboy commented 6 years ago

_Channels[0] should use memcpy. Looklike _ClipRectStack should not be copied.

But may I ask why are you trying to do that?

  1. Make an effect that dragging a chrome or firefox tab.
  2. Reuse the drawlist to avoid run the code that processing the user interaction.
  3. Add some effect. Scaling or fading out along y axis.
evan-gordon commented 6 years ago

Hey @ocornut Is there an ETA on drag and drop support for the navigation branch? I'm trying to use this for item swapping inside the player inventory for my game. For now I've been tinkering with your code to get it working on my end, but as it is now a lot of your code is pretty heavily tied to mouse use.

ocornut commented 6 years ago

@evan-gordon It's already merged into the navigation branch, but isn't the point of drag and drop to a be mouse thing? Most of the api makes no sense with keyboard.

You can use custom code if you need custom control, which is likely if you want the way of using dear imgui for in-game stuff. Humble reminder that dear imgui isn't designed for that use case at all.

evan-gordon commented 6 years ago

Okay fair enough, I'm not against writing the new code myself as i already started.

sherief commented 6 years ago

I have been spending some time on drag-and-drop and I think a write up is in order.

I think I can split interest in drag-and-drop into two categories:

Both cases outlined in the First Class DnD description above are concrete things that I ran into - in one tool I support dragging objects within the same instance and across instances, and also to other external editors (paint.exe for textures, etc.). In the former case I need multiple drag formats (I drag the object as both an in-process pointer and a "promise" to generate a serialized version, for whether the drag ends inside or outside the same process), and the drag also exposes a filesystem path that's used when it's dropped on paint.exe. I absolutely need First Class DnD support, and prior to this branch / API I had it hacked into an earlier version of imgui. I can go on forever about NSPasteboard and IDataObjects in a multithreaded app but it'd probably be overkill.

The Good Enough case seems to be sufficiently covered by the current API, but for First Class DnD support I think some changes need to be made:

I'm interested in hearing any comments and answering any questions you might have. First Class DnD support is extremely important to me and the tool I'm working on exercises some less frequently used paths (including running the OS DnD code async in a separate thread).

s-ol commented 5 years ago

This is working super well for me in it's current state.

A small note is that the held-item preview tooltip turns into ... when the DragDropSource goes out of scope. I understand that this is a really difficult design problem to properly solve with the immediate-mode UI approach, but trying out the new docking branch I noticed that this situation might become common as dragging and dropping from different tabs inside the same docking node seems like something you would want to do to me.

I am not sure if that would violate some ImGui internal rules or limitations, but couldn't the draw list for the node be cached? That behavior could be toggle-able via a Source-DragDropFlag in case you know that you would rather want no tooltip than a non-realtime one.

ocornut commented 5 years ago

Hello @s-ol. This problem is discussed in #1725. I think a better solution would be to allow the user to peak into the payload and submit the tooltip outside of the BeginDragSource() scope.

ChugunovRoman commented 5 years ago

Do you mean drag and dropping of files into your application window? That's OS dependant and not likely to be part of core imgui. On Windows you only need to call DragAcceptFiles() and implement the WM_DROPFILES message.

Hi. Is dropping of files into a app window does support? How to implement this?

rokups commented 4 years ago

@ChugunovRoman this is backend-specific. For example SDL sends SDL_DROPFILE when file is dropped on to window. You have to handle this event. You most likely also want to check mouse cursor at the time event is sent in order to detect on which control it was dropped.

@ocornut shouldnt this issue be closed? Drag & drop is implemented.

ocornut commented 4 years ago

I tend to keep issue opens when some of the posts are discussing interesting/useful ideas that are still yet to implement and not covered elsewhere.

There are a few things discussed here that are still unfullfilled:

I'll close this issue as the points raised above are all listed in TODO.txt, when there is an impulse/attempt to solve them it we can open a new issue to discuss them.

Thanks all!

rokups commented 4 years ago

we are lacking a "reorder items in a list" demo (and eventually "in a tree")

I implemented exactly this in my engine editor by adding extra few pixel high drop areas between list items. For this however i had to modify imgui to have smaller drop area border margins. I should make a PR for this margin to be configurable via style..

ocornut commented 4 years ago

we are lacking a "reorder items in a list" demo (and eventually "in a tree")

I implemented exactly this in my engine editor by adding extra few pixel high drop areas between list items. For this however i had to modify imgui to have smaller drop area border margins. I should make a PR for this margin to be configurable via style..

PR of a small demo would be helpful, I can think of two ways to do it:

Feel free to open an issue/PR to discuss this further.