Closed damqui closed 4 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.
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)
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.
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.
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.
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:
// 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.
these sort of things shouldn't be buried inside of tickets but be a listed in some kind of wiki
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.
@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.
awesome. showing the widget at the mouse is a must-have for drag/drop imho
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
@ocornut did you rewrite that example that you mentioned ?
Sorry I haven't got around to write an example for that (that's why I kept the issue opened for now)
I just implemented drag and drop using that concept:
I'm curious how do you do something on a "drop"?
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.
+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.
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?
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
@Extrawurst, are you going to share that snippet? Seems very useful for the imgui community!
It is not a snippet, it's buried in my engine but it was based on the gist above
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.
If you are using drag'n drop idioms in your codebase, please read this thread: https://github.com/ocornut/imgui/issues/1382
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
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.
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
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:
Testing drag and drop patterns of "moving" nodes, e.g. moving tree nodes. In particular to be able to differentiate "drag node A into node B to parent it" vs "drag node A below or above node B to make it an ordered sibling", it would be helpful if imgui provided helpers so you don't have to poll every possible target slots.
Testing drag and drop between multiple imgui windows or a same application, and between windows different applications. It is possible to use the current API to easily transport data across apps? Perhaps it is, but until I tried I don't know.
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.
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.
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?
_Channels[0]
should use memcpy
. Looklike _ClipRectStack
should not be copied.
But may I ask why are you trying to do that?
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.
@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.
Okay fair enough, I'm not against writing the new code myself as i already started.
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).
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.
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.
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?
@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.
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!
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..
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.
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 ?