How to Implement Drag and Drop Between Widgets in Dear ImGui
Dear ImGui's drag and drop system uses BeginDragDropSource() and BeginDragDropTarget() to convert any widget into a data source or destination, with payloads identified by unique type strings.
Dear ImGui provides a lightweight, state-driven drag and drop system that can turn any widget into a drag source, a drop target, or both simultaneously. According to the ocornut/imgui source code, the entire mechanism is declared in imgui.h and implemented in imgui.cpp, allowing you to move arbitrary data between color buttons, list items, or tree nodes with minimal overhead.
Core Drag and Drop API Stages
The system operates through a defined workflow that tracks the active drag, source ID, and payload buffer internally. Because ImGui copies payload data into its own storage, you can safely pass pointers to temporary variables without lifetime concerns.
Starting a Drag Operation
Call ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) immediately after submitting the widget you want to make draggable. This function returns true when the user has initiated a drag (typically by holding the mouse button). Follow with ImGui::SetDragDropPayload(const char* type, const void* data, size_t size, ImGuiCond cond) to store your data under a custom type string (maximum 32 characters), then close the block with ImGui::EndDragDropSource().
Accepting a Drop Operation
To receive data, call ImGui::BeginDragDropTarget() after submitting the target widget. This returns true when a compatible payload is hovering over the item. Retrieve the payload using ImGui::AcceptDragDropPayload(const char* type, ImGuiDragDropFlags flags), which returns a pointer to an ImGuiPayload structure if the type matches. Always close with ImGui::EndDragDropTarget() when the function returns true.
Example: Dragging Between Color Buttons
This example demonstrates exchanging color data between two buttons. The implementation in imgui_demo.cpp (lines 1689-1703) illustrates this pattern.
// Source widget
ImGui::ColorButton("##src", ImVec4(0.8f, 0.2f, 0.2f, 1.0f));
if (ImGui::BeginDragDropSource()) {
ImVec4 color = ImVec4(0.8f, 0.2f, 0.2f, 1.0f);
ImGui::SetDragDropPayload("DND_COLOR", &color, sizeof(color));
ImGui::Text("Dragging color");
ImGui::EndDragDropSource();
}
// Target widget
ImGui::SameLine();
ImGui::ColorButton("##dst", ImVec4(0.2f, 0.2f, 0.8f, 1.0f));
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_COLOR")) {
ImVec4* received = (ImVec4*)payload->Data;
// Apply received color to your application state
}
ImGui::EndDragDropTarget();
}
The payload lives in ImGui's internal buffer until EndDragDropSource() is called, making it safe to use stack-allocated data as shown above.
Advanced Pattern: Reordering Lists
For reordering list items, each element must serve as both source and target. The imgui_demo.cpp implementation (lines 3495-3513) demonstrates swapping items by passing the source index as payload.
for (int i = 0; i < MyList.Size; ++i) {
ImGui::PushID(i);
ImGui::Selectable(MyList[i].c_str());
// Source
if (ImGui::BeginDragDropSource()) {
ImGui::SetDragDropPayload("DND_ITEM", &i, sizeof(i));
ImGui::Text("Moving %s", MyList[i].c_str());
ImGui::EndDragDropSource();
}
// Target
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_ITEM")) {
int src_idx = *(int*)payload->Data;
std::swap(MyList[src_idx], MyList[i]);
}
ImGui::EndDragDropTarget();
}
ImGui::PopID();
}
Dragging Tree Nodes
Tree nodes can be re-parented by dragging them onto other nodes. The source supplies its own ID, allowing the target to modify the hierarchy when the payload is delivered.
void ShowTreeNode(const char* label, int node_id) {
ImGui::PushID(node_id);
bool opened = ImGui::TreeNode(label);
if (ImGui::BeginDragDropSource()) {
ImGui::SetDragDropPayload("DND_TREENODE", &node_id, sizeof(node_id));
ImGui::Text("Moving %s", label);
ImGui::EndDragDropSource();
}
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("DND_TREENODE")) {
int src_id = *(int*)payload->Data;
AttachAsChild(src_id, node_id); // Your custom logic
}
ImGui::EndDragDropTarget();
}
if (opened) {
// Render children recursively
ImGui::TreePop();
}
ImGui::PopID();
}
Key Implementation Files
Understanding where the drag and drop system lives helps when debugging or extending functionality:
imgui.h(lines 73-84): DeclaresBeginDragDropSource,SetDragDropPayload,BeginDragDropTarget, andAcceptDragDropPayloadimgui.cpp(lines 15037-15292): Contains the core state machine implementation handling source/target detection and payload storageimgui_internal.h: DefinesImGuiPayloadand internal drag drop flags such asImGuiDragDropFlags_SourceNoPreviewTooltipimgui_demo.cpp: Provides working examples including color exchange and list reordering
Summary
- Initiate drags by calling
BeginDragDropSource()after the widget and closing withEndDragDropSource() - Store data using
SetDragDropPayload()with a unique 32-character type identifier; ImGui copies and manages the memory internally - Receive data by calling
BeginDragDropTarget()thenAcceptDragDropPayload()with matching type strings - Enable complex interactions by making widgets dual-purpose sources and targets, using
PushID()/PopID()to maintain stable identifiers - Reference the demo code in
imgui_demo.cppfor production-ready patterns
Frequently Asked Questions
What data types can I pass through a drag and drop payload?
You can pass any data type because SetDragDropPayload() accepts a const void* and size in bytes. The system copies your data into an internal buffer, so you can safely drag primitive types like int or float, structs like ImVec4, or complex objects. Ensure the type string uniquely identifies the data format so targets can cast the payload->Data pointer correctly.
How do I hide the default tooltip during a drag operation?
Pass the ImGuiDragDropFlags_SourceNoPreviewTooltip flag to BeginDragDropSource(). By default, ImGui displays "..." while dragging, but this flag suppresses it. If you want a custom preview, draw your own content between BeginDragDropSource() and EndDragDropSource()—ImGui will render it as the drag preview.
Can a single widget act as both a drag source and drop target?
Yes. Submit your widget, then call both BeginDragDropSource() and BeginDragDropTarget() sequentially. This pattern is essential for reordering lists where each item can be dragged to a new position and also receive other items. Use PushID() to give each widget a stable unique ID so ImGui can track the state correctly.
Where does ImGui store the drag and drop state?
The state lives in the ImGuiContext structure defined in imgui_internal.h and managed in imgui.cpp. Key fields include DragDropActive, DragDropSourceId, and the payload buffer DragDropPayload. Because the system is immediate-mode, you must call the Begin/End functions every frame; ImGui handles the persistence of the payload data internally between frames while a drag is active.
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 →