How to Implement Drag and Drop Functionality Between Widgets in ImGui
Dear ImGui provides a lightweight, immediate-mode drag and drop system using BeginDragDropSource(), SetDragDropPayload(), and BeginDragDropTarget() to transfer custom data between any widgets.
The drag and drop functionality between widgets in ImGui is defined in the public header imgui.h (around line 976) and implemented in imgui.cpp. This stateless API allows you to attach custom payloads to any widget and accept them on any other, making it ideal for rearranging lists, creating node graphs, or moving items between panels.
Understanding the Drag and Drop Architecture
The ImGui drag and drop system is built around three core concepts that work together to create a seamless user experience.
The Three Core Components
Every drag operation involves:
- Drag Source – The widget that initiates the drag, defined by wrapping its rendering code with
BeginDragDropSource()andEndDragDropSource(). - Payload – The user-defined data attached via
SetDragDropPayload(), stored in anImGuiPayloadstructure (defined inimgui.haround line 199). - Drop Target – The destination widget that receives the payload, created using
BeginDragDropTarget()andEndDragDropTarget().
State Management
Unlike retained-mode GUI libraries, ImGui's drag and drop system is completely stateless from the library's perspective. The payload lives only while the mouse button is held down (or until explicitly released), meaning you don't need to maintain complex state machines to track drag operations across frames.
Implementing a Drag Source
To make a widget draggable, you must call BeginDragDropSource() immediately after submitting the item. This function returns true only when the user is actively dragging the item.
// Submit the draggable item
if (ImGui::Selectable("Item Label")) {
// Handle selection click
}
// Set up drag source
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
// Define the payload type and data
int item_id = 42;
ImGui::SetDragDropPayload("MY_ITEM", &item_id, sizeof(int));
// Optional: Display tooltip while dragging
ImGui::Text("Dragging Item %d", item_id);
ImGui::EndDragDropSource();
}
Key details:
SetDragDropPayload()takes a null-terminated string identifier (e.g.,"MY_ITEM") that targets will use to filter accepted types.- The payload data is copied into an internal buffer managed by ImGui, so you don't need to keep the original variable alive.
- You must call
EndDragDropSource()only ifBeginDragDropSource()returned true.
Creating a Drop Target
Any widget can become a drop target by wrapping it with BeginDragDropTarget(). According to the source code in imgui.cpp, this function checks if a compatible payload is hovering over the last submitted item's bounding box.
// Submit the potential drop target
ImGui::Button("Drop Zone");
if (ImGui::BeginDragDropTarget()) {
// Check for specific payload type
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_ITEM")) {
// Verify data size before casting
IM_ASSERT(payload->DataSize == sizeof(int));
int received_id = *static_cast<const int*>(payload->Data);
// Handle the drop action
OnItemDropped(received_id);
}
ImGui::EndDragDropTarget();
}
Important: Always verify payload->DataSize matches your expected type before casting the void pointer to prevent memory corruption.
Advanced Configuration and Flags
The API provides several flags in imgui.h (lines 976-1002) to customize behavior for complex interactions.
Suppressing Preview Tooltips
By default, ImGui shows a tooltip with "..." while dragging. To disable this or provide a custom preview:
// Disable default tooltip
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceNoPreviewTooltip)) {
ImGui::SetDragDropPayload("MY_ITEM", &data, sizeof(data));
// Custom preview Tooltip
ImGui::Text("Custom Preview");
ImGui::EndDragDropSource();
}
Conditional Payload Lifetime
Normally, the payload disappears when the source widget stops being rendered. To keep the payload alive after the source disappears (useful for dragging from toolbars that scroll away), pass ImGuiCond_Once or ImGuiCond_Always as the fourth parameter to SetDragDropPayload():
ImGui::SetDragDropPayload("TOOLBAR_ITEM", &data, sizeof(data), ImGuiCond_Once);
Accepting Payloads Before Delivery
For "drag-over" previews while the mouse button is still held, use ImGuiDragDropFlags_AcceptBeforeDelivery:
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(
"MY_ITEM", ImGuiDragDropFlags_AcceptBeforeDelivery)) {
// Preview mode: payload is valid but mouse button not released yet
}
Complete Working Example
This example demonstrates moving items between two lists, referencing the implementation patterns found in examples/example_demo.cpp:
static int left_items[] = {1, 2, 3};
static int right_items[] = {4, 5, 6};
// Left list (Drag sources)
ImGui::BeginGroup();
for (int i = 0; i < IM_ARRAYSIZE(left_items); i++) {
ImGui::PushID(i);
ImGui::Selectable("Item %d", left_items[i]);
if (ImGui::BeginDragDropSource()) {
ImGui::SetDragDropPayload("DND_ITEM", &left_items[i], sizeof(int));
ImGui::Text("Moving Item %d", left_items[i]);
ImGui::EndDragDropSource();
}
ImGui::PopID();
}
ImGui::EndGroup();
ImGui::SameLine();
// Right list (Drop target)
ImGui::BeginGroup();
ImGui::Button("Drop Here", ImVec2(100, 200));
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_ITEM")) {
int new_item = *static_cast<const int*>(payload->Data);
// Add to right_items array...
}
ImGui::EndDragDropTarget();
}
ImGui::EndGroup();
Summary
- Source API: Wrap widgets with
BeginDragDropSource()/EndDragDropSource()and attach data viaSetDragDropPayload(). - Target API: Wrap widgets with
BeginDragDropTarget()/EndDragDropTarget()and retrieve data usingAcceptDragDropPayload(). - Payload Types: Use unique string identifiers (e.g.,
"MY_ITEM") to filter compatible drag and drop operations. - Implementation: Core logic resides in
imgui.h(definitions) andimgui.cpp(state machine), withimgui_internal.hhandling payload storage. - Flags: Use
ImGuiDragDropFlags_SourceNoPreviewTooltipto hide default tooltips andImGuiCond_Onceto persist payloads after source destruction.
Frequently Asked Questions
What is the maximum size of a drag and drop payload?
ImGui does not enforce a hard limit on payload size, but the payload is memcpy'd into an internal buffer managed by the ImGuiPayload structure. For large data transfers, store pointers to heap-allocated data rather than copying the data directly, and ensure the pointed-to memory remains valid for the duration of the drag operation.
Can I drag from a widget that disappears after starting the drag?
Yes. Pass ImGuiCond_Once or ImGuiCond_Always as the fourth argument to SetDragDropPayload(). This keeps the payload alive in the internal drag and drop state even if the source widget is no longer rendered in subsequent frames, which is essential for scrolling lists or toolbars where the source may scroll out of view.
How do I handle multiple item types in a single drop target?
Call AcceptDragDropPayload() multiple times with different type strings, or pass NULL as the type to accept any payload. The function returns a pointer to ImGuiPayload only if the hovering payload matches the requested type, allowing you to implement different logic for different data types within the same BeginDragDropTarget() block.
Where can I find the official demo implementation?
The official demo is located in examples/example_demo.cpp under the "Widgets -> Drag and Drop" section. This file, part of the ocornut/imgui repository, provides comprehensive examples including dragging colors, text, and tree nodes, demonstrating the full capabilities of the API as implemented in imgui.cpp.
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 →