How to Implement Drag and Drop Functionality Between Widgets Using ImGuiPayload
Use ImGui::BeginDragDropSource() to initiate dragging from a widget, attach data with ImGui::SetDragDropPayload(), then call ImGui::BeginDragDropTarget() on destination widgets and retrieve the payload using ImGui::AcceptDragDropPayload() to enable drag-and-drop interactions in Dear ImGui.
Dear ImGui (ocornut/imgui) provides a lightweight, immediate-mode drag-and-drop system built around the ImGuiPayload structure. By wrapping your widget rendering code with specific API calls defined in imgui.h, you can implement drag and drop functionality between widgets using ImGuiPayload without maintaining complex state machines. This guide demonstrates the complete pipeline with production-ready code patterns drawn directly from the source.
Core Architecture of ImGui Drag-and-Drop
The system revolves around three distinct roles working together statelessly:
- Drag Source: The widget initiating the drag operation
- Payload: The
ImGuiPayloadstructure containing user-defined data and type identifiers - Drop Target: The widget accepting the payload
According to the source code in imgui.h (around line 976), the API is deliberately non-intrusive—you wrap existing widget code rather than inheriting from specialized classes. The payload lives only while the source is active or until the user releases the mouse, making the system trivial to integrate with any widget.
Implementing the Drag Source
To make any widget draggable, wrap its rendering code with BeginDragDropSource() and EndDragDropSource().
Starting the Drag Operation
Call ImGui::BeginDragDropSource() immediately after submitting the widget item. This function returns true only when the user is actively dragging the item with the mouse button held:
if (ImGui::Selectable("Draggable Item")) { /* selection logic */ }
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
// Drag operation is active
ImGui::EndDragDropSource();
}
Attaching the Payload
Use ImGui::SetDragDropPayload() to attach data to the drag operation. The type parameter is a null-terminated string identifier (e.g., "MY_ITEM"), and the cond parameter controls payload persistence:
int item_id = 42;
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
ImGui::SetDragDropPayload("MY_ITEM", &item_id, sizeof(item_id), ImGuiCond_Always);
ImGui::Text("Dragging Item %d", item_id);
ImGui::EndDragDropSource();
}
Passing ImGuiCond_Always ensures the payload remains available even if the source widget disappears during the drag operation, which is essential for dragging from scrolling lists or transient toolbars.
Configuring the Drop Target
Detecting Compatible Payloads
Call ImGui::BeginDragDropTarget() after submitting the target widget. This function returns true when a compatible payload hovers over the item:
ImGui::Button("Drop Zone");
if (ImGui::BeginDragDropTarget()) {
// Payload is hovering over this button
ImGui::EndDragDropTarget();
}
Accepting and Processing the Payload
Retrieve the ImGuiPayload pointer using ImGui::AcceptDragDropPayload(). Always verify the DataSize field matches your expected structure before casting the Data pointer:
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_ITEM")) {
IM_ASSERT(payload->DataSize == sizeof(int));
int received_id = *static_cast<const int*>(payload->Data);
OnItemDropped(received_id);
}
ImGui::EndDragDropTarget();
}
The ImGuiPayload structure (defined in imgui.h around line 199) contains the Data and DataSize fields pointing to an internal buffer managed by the drag-and-drop state machine in imgui.cpp.
Complete Working Example
Here is a complete implementation demonstrating drag-and-drop between a list item and a drop zone, following the pattern found in examples/example_demo.cpp:
// Drag source implementation
if (ImGui::Selectable(item_label)) { /* handle selection */ }
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
ImGui::SetDragDropPayload("MY_ITEM", &item_id, sizeof(item_id));
ImGui::Text("Dragging %s", item_label);
ImGui::EndDragDropSource();
}
// Drop target implementation
ImGui::Button("Drop Here");
if (ImGui::BeginDragDropTarget()) {
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_ITEM")) {
IM_ASSERT(payload->DataSize == sizeof(int));
int received_id = *static_cast<const int*>(payload->Data);
OnItemDropped(received_id);
}
ImGui::EndDragDropTarget();
}
Advanced Configuration Options
Controlling Tooltip Visibility
Use specific flags to customize preview behavior during drag operations:
ImGuiDragDropFlags_SourceNoPreviewTooltip: Hides the default "..." tooltip on the source sideImGuiDragDropFlags_AcceptNoPreviewTooltip: Hides the source tooltip when hovering over the target
Preview Before Delivery
Enable ImGuiDragDropFlags_AcceptBeforeDelivery to receive the payload while the mouse button is still held, allowing you to render "drag-over" previews before the user releases:
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("MY_ITEM",
ImGuiDragDropFlags_AcceptBeforeDelivery)) {
// Show preview while dragging without finalizing the drop
}
Supporting Multiple Payload Types
You can call SetDragDropPayload() multiple times with different type strings during a single drag operation to offer different data formats. The target can then accept any compatible type it recognizes by calling AcceptDragDropPayload() with the appropriate type identifier.
Summary
- Wrap source widgets with
BeginDragDropSource()andEndDragDropSource()defined inimgui.h(line 976) - Attach data using
SetDragDropPayload()with a unique type string andImGuiCond_Alwaysfor cross-widget persistence - Wrap target widgets with
BeginDragDropTarget()and retrieve data viaAcceptDragDropPayload() - Always verify
payload->DataSizematches your expected structure before casting theDatapointer - Reference the complete implementation in
examples/example_demo.cppunder the Demo Window's Drag & Drop section
Frequently Asked Questions
Is the ImGuiPayload data copied or referenced?
Dear ImGui copies the payload data into an internal buffer managed by the drag-and-drop state machine in imgui.cpp. The ImGuiPayload structure (defined around line 199 in imgui.h) provides Data and DataSize fields pointing to this internal copy, ensuring the data remains valid even if the source widget is destroyed during the drag operation.
Can I drag and drop between different window contexts?
Yes. The drag-and-drop system is global within the ImGui context. As long as both the source and target are rendered within the same ImGui::NewFrame() scope, the payload persists across window boundaries. The system stores the payload in the internal ImGuiContext structure, making it accessible anywhere in the frame.
How do I handle multiple item types in one drop target?
Call AcceptDragDropPayload() with different type strings for each supported format. Check the return value to determine which type was dropped and handle accordingly:
if (const ImGuiPayload* p = ImGui::AcceptDragDropPayload("FILE_PATH")) {
HandleFile(p);
} else if (const ImGuiPayload* p = ImGui::AcceptDragDropPayload("TEXT")) {
HandleText(p);
}
Why does my payload disappear when the source widget scrolls out of view?
By default, the payload lifetime is tied to the source widget's existence. Pass ImGuiCond_Always as the fourth parameter to SetDragDropPayload() to keep the payload alive regardless of the source widget's visibility state. This is essential for drag operations originating from scrolling lists, collapsing headers, or transient UI elements.
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 →