How to Implement Drag and Drop Functionality in Dear ImGui: A Complete API Guide
Dear ImGui provides an immediate-mode drag-and-drop system where BeginDragDropSource() initiates drags from any widget, SetDragDropPayload() packages the data, and BeginDragDropTarget() with AcceptDragDropPayload() handles the drop on compatible targets.
Dear ImGui's drag-and-drop implementation operates entirely within the immediate-mode paradigm, requiring no persistent state management or complex event registration. According to the ocornut/imgui source code, the system relies on a simple three-phase workflow executed frame-by-frame: detecting drag initiation on source widgets, transporting payload data via the ImGuiPayload structure, and accepting drops on target widgets. This architecture allows developers to implement complex interactions like item reordering, file imports, or property transfers with minimal boilerplate.
Understanding the Drag and Drop Workflow
The API divides drag-and-drop operations into distinct source and target phases, each bracketed by begin/end functions declared in imgui.h.
Source Phase: Initiating the Drag
To make a widget draggable, call BeginDragDropSource() immediately after submitting the widget. This function returns true once the user holds the mouse button and begins dragging.
BeginDragDropSource([flags])– Defined inimgui.hat lines 73‑79. Returnstruewhen the widget becomes active drag source.SetDragDropPayload(type, data, size, cond)– Defined inimgui.hat lines 80‑81. Copies the payload data (max 32 characters for the type string) into internal storage, allowing the original buffer to be freed immediately after the call.EndDragDropSource()– Defined inimgui.hat line 82. Must be called only whenBeginDragDropSource()returnedtrue.
Target Phase: Receiving the Drop
To accept drops, submit your target widget (using any standard item call like Button() or InvisibleButton()), then call BeginDragDropTarget().
BeginDragDropTarget()– Defined inimgui.hat lines 84‑85. Returns true while a compatible payload hovers over the item's bounding box.AcceptDragDropPayload(type, [flags])– Defined inimgui.hat lines 86‑87. Returns a pointer toImGuiPayload(defined inimgui_internal.h) if the hovering payload matches the specified type string; otherwise returnsNULL.EndDragDropTarget()– Defined inimgui.hat line 88. Must be called only whenBeginDragDropTarget()returnedtrue.
The Payload Structure
The ImGuiPayload structure (defined in imgui_internal.h) contains the Data pointer, DataSize, and Type string. According to the implementation in imgui.cpp around line 15292, the system copies payload data when SetDragDropPayload is called, ensuring the payload remains valid even if the source widget is destroyed during the drag operation.
Practical Implementation Examples
Swapping Items Between Buttons
This pattern from imgui_demo.cpp demonstrates a complete copy/move/swap interaction using button widgets as both sources and targets. Notice the use of PushID()/PopID() to ensure unique ImGuiIDs for drag detection.
static const char* names[3] = { "Alice", "Bob", "Carol" };
for (int n = 0; n < IM_ARRAYSIZE(names); n++)
{
ImGui::PushID(n);
ImGui::Button(names[n], ImVec2(80, 30));
// ---- Drag source -------------------------------------------------
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None))
{
// Payload carries the index of the item.
ImGui::SetDragDropPayload("MY_DND_PAYLOAD", &n, sizeof(int));
ImGui::Text("Dragging %s", names[n]); // optional preview tooltip
ImGui::EndDragDropSource();
}
// ---- Drop target -------------------------------------------------
if (ImGui::BeginDragDropTarget())
{
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_DND_PAYLOAD"))
{
int src_idx = *(const int*)payload->Data;
// Example: swap the two entries.
ImGui::Swap(names[n], names[src_idx]);
}
ImGui::EndDragDropTarget();
}
ImGui::PopID();
}
Dragging Colors Between Widgets
This minimal example shows dragging an ImVec4 color from a color button to a custom rectangle target using InvisibleButton() as the drop zone.
static ImVec4 source_color = ImVec4(0.4f, 0.7f, 0.2f, 1.0f);
static ImVec4 target_color = ImVec4(0.2f, 0.3f, 0.8f, 1.0f);
ImGui::ColorButton("Source", source_color, ImGuiColorEditFlags_NoLabel);
if (ImGui::BeginDragDropSource())
{
ImGui::SetDragDropPayload("COL_DND", &source_color, sizeof(ImVec4));
ImGui::Text("Drag Color");
ImGui::EndDragDropSource();
}
ImGui::SameLine();
ImGui::InvisibleButton("Target", ImVec2(80, 80));
if (ImGui::BeginDragDropTarget())
{
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("COL_DND"))
target_color = *(const ImVec4*)payload->Data;
ImGui::EndDragDropTarget();
}
ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetItemRectMin(),
ImGui::GetItemRectMax(),
ImGui::ColorConvertFloat4ToU32(target_color));
Key Implementation Details
Widget IDs are mandatory – Drag-and-drop operations depend on ImGuiID. If you attempt to use BeginDragDropSource() after a widget without a natural ID (such as ImGui::Text), you must wrap the content with PushID()/PopID() pairs to generate a unique identifier.
Available flags – The ImGuiDragDropFlags enumeration (defined in imgui.h) provides several control options:
ImGuiDragDropFlags_SourceNoPreviewTooltip– Disables the default preview tooltip on the source side.ImGuiDragDropFlags_AcceptNoPreviewTooltip– Hides the tooltip when hovering over a target.ImGuiDragDropFlags_AcceptBeforeDelivery– Allows the target to accept and preview the payload before the mouse button is released (peek mode).
Nesting restrictions – The implementation in imgui.cpp (around line 15037) asserts that sources cannot be nested inside other sources or targets. Calling BeginDragDropSource() while another drag operation is active triggers IM_ASSERT(g.DragDropWithinSource && "Not after a BeginDragDropSource()").
Payload lifetime – Because SetDragDropPayload() copies the data immediately, the source buffer can be stack-allocated or reused once the function returns. The internal copy persists until the drag operation completes or the payload is accepted by a target.
Summary
- Dear ImGui's drag-and-drop system is immediate-mode, utilizing
BeginDragDropSource()andBeginDragDropTarget()bracketed calls defined inimgui.h. - Payload data is copied and transported via
SetDragDropPayload(), with type strings limited to 32 characters. - Widget IDs are essential for source detection; use
PushID()/PopID()for text or custom draw widgets. - The
ImGuiPayloadstructure provides the data pointer and size, accessible viaAcceptDragDropPayload()as implemented inimgui.cpparound line 15292. - Source and target blocks cannot be nested, and the API provides flags to control tooltip visibility and early acceptance behavior.
Frequently Asked Questions
What is the maximum size for drag-and-drop payload type strings?
Dear ImGui limits payload type strings to 32 characters (including the null terminator). This is enforced internally in imgui.cpp during the call to SetDragDropPayload(). Exceeding this limit results in the type being truncated, potentially causing type mismatches between source and target.
Can I implement drag and drop on text labels or custom drawn elements?
Yes, but you must explicitly provide an ImGuiID using PushID() before the text or drawing command, and PopID() after. The drag-and-drop detection mechanism in imgui.cpp relies on the last item ID returned by GetID(), so widgets without automatic ID generation (like ImGui::Text or raw draw lists) require manual ID management to serve as valid sources or targets.
How do I preview the payload data before the user releases the mouse button?
Use the ImGuiDragDropFlags_AcceptBeforeDelivery flag when calling AcceptDragDropPayload(). This allows the target to access payload->Data during the hover state (while the mouse button is still held), enabling real-time visual feedback such as highlighting insertion points or displaying preview overlays before the drop is finalized.
Why does my drag operation trigger an assertion error?
The most common cause is nesting drag-and-drop source or target blocks, which triggers the IM_ASSERT(g.DragDropWithinSource) check in imgui.cpp. Ensure you never call BeginDragDropSource() inside another source or target block, and always verify the return value of begin functions before calling the corresponding EndDragDropSource() or EndDragDropTarget().
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →