8.18.1.24. Drag and drop
ImGui’s drag-drop API is two paired Begin*/End* calls: one
attached to the previously submitted item that’s being dragged, one
attached to the previously submitted item that’s receiving. Both
Begin* calls return false unless ImGui’s internal drag state
machine says “drag-active” or “hover-over-target with payload”; calling
End* when Begin* returned false is undefined behavior.
The boost layer ships two [container]``s — ``drag_drop_source and
drag_drop_target — that gate their bodies on the corresponding
Begin* return. Payload set/accept stay raw calls inside the body
because the data is caller-owned and crosses widget boundaries.
Source: modules/dasImgui/examples/features/drag_drop.das.
8.18.1.24.1. Walkthrough
The recording introduces the source button and the drop target, then performs a
real drag of the source onto the target. On release the target accepts the
MY_INT payload — Target received value flips to 42 and
Drops accepted ticks to 1. The drag is synthesized through the same
synth-mouse pipeline a test would use, and the recording asserts the drop
actually landed (see Driving from outside below).
1options gen2
2options _comment_hygiene = true
3
4require imgui/imgui_harness
5
6// =============================================================================
7// FEATURE: drag_drop — [container]s drag_drop_source + drag_drop_target.
8// Both gate their body on ImGui's internal drag-drop state machine.
9// Bodies call raw SetDragDropPayload / AcceptDragDropPayload —
10// payload data is caller-owned and crosses widget boundaries.
11// SHOWS: drag SOURCE_BTN onto TARGET_BTN; TARGET_BTN's body accepts the
12// "MY_INT" payload and writes it into RECEIVED. DROP_COUNT
13// increments on each successful release-over-target.
14// STANDALONE: daslang.exe modules/dasImgui/examples/features/drag_drop.das
15// HEADLESS: daslang.exe modules/dasImgui/examples/features/drag_drop.das -- --headless --headless-frames=60
16// LIVE: daslang-live modules/dasImgui/examples/features/drag_drop.das
17// =============================================================================
18
19var PAYLOAD_VALUE : int = 42
20var RECEIVED : int = 0
21var DROP_COUNT : int = 0
22
23[export]
24def init() {
25 harness_init("drag_drop — source + target", 720, 480)
26 let io & = unsafe(GetIO())
27 GetStyle().FontScaleMain = 1.5
28}
29
30[export]
31def update() {
32 if (!harness_begin_frame()) return
33 harness_new_frame()
34
35 SetNextWindowSize(ImVec2(660.0, 420.0), ImGuiCond.Always)
36 window(MAIN_WIN, (text = "drag_drop", closable = false,
37 flags = ImGuiWindowFlags.None)) {
38 text("Drag the SOURCE button onto the TARGET button.")
39 separator()
40
41 // Source: a normal button. Body runs only on active drag.
42 button(SOURCE_BTN, (text = "SOURCE"))
43 drag_drop_source(SOURCE_DD, (flags = ImGuiDragDropFlags.None)) {
44 unsafe {
45 SetDragDropPayload("MY_INT", addr(PAYLOAD_VALUE),
46 uint64(typeinfo sizeof(PAYLOAD_VALUE)),
47 ImGuiCond.Once)
48 }
49 text("Dragging int: {PAYLOAD_VALUE}")
50 }
51
52 // 40 px gap: same_line's named-tuple destructure is POSITIONAL
53 // (offset_from_start_x, spacing). `(spacing = 40.0f)` alone binds 40 to
54 // offset and overlaps TARGET onto SOURCE, so the drag never starts.
55 same_line((offset_from_start_x = 0.0f, spacing = 40.0f))
56
57 // Target: a normal button. Body runs only when payload-bearing drag hovers.
58 button(TARGET_BTN, (text = "TARGET"))
59 drag_drop_target(TARGET_DD) {
60 let payload = AcceptDragDropPayload("MY_INT", ImGuiDragDropFlags.None)
61 if (payload != null) {
62 unsafe {
63 RECEIVED = (reinterpret<int? const>(payload.Data))[0]
64 }
65 DROP_COUNT++
66 }
67 }
68
69 separator()
70 text("Source payload value: {PAYLOAD_VALUE}")
71 text("Target received value: {RECEIVED}")
72 text("Drops accepted: {DROP_COUNT}")
73 }
74
75 harness_end_frame()
76}
77
78[export]
79def shutdown() {
80 harness_shutdown()
81}
82
83[export]
84def main() {
85 init()
86 while (!exit_requested()) {
87 update()
88 }
89 shutdown()
90}
8.18.1.24.1.1. Requires
Same backend + boost layer as the other container tutorials, plus the
drag_drop_source / drag_drop_target macros from
imgui/imgui_containers_builtin (which is already required by the
container family).
8.18.1.24.1.2. Source side
Attach a drag_drop_source immediately after the source widget. ImGui
implicitly binds it to the previously submitted item:
button(SOURCE_BTN, (text = "SOURCE"))
drag_drop_source(SOURCE_DD, (flags = ImGuiDragDropFlags.None)) {
unsafe {
SetDragDropPayload("MY_INT", addr(PAYLOAD_VALUE),
uint64(typeinfo sizeof(PAYLOAD_VALUE)),
ImGuiCond.Once)
}
Text("Dragging: {PAYLOAD_VALUE}")
}
The body runs only while the drag is active — after the user has
pressed the mouse on SOURCE_BTN and dragged past ImGui’s
io.MouseDragThreshold. Inside the body:
SetDragDropPayload(type, data, size, cond)publishes the payload.typeis a short string (max 32 chars) that the target compares against;datais a raw pointer with caller-controlled lifetime (ImGui copies it into its internal buffer on each call, so a stack pointer is fine inside the body).Any rendering call (
Text,Image, etc.) draws the drag preview tooltip that follows the cursor.
8.18.1.24.1.3. Target side
Attach a drag_drop_target immediately after the target widget:
button(TARGET_BTN, (text = "TARGET"))
drag_drop_target(TARGET_DD) {
let payload = AcceptDragDropPayload("MY_INT", ImGuiDragDropFlags.None)
if (payload != null) {
unsafe {
RECEIVED = (reinterpret<int? const>(payload.Data))[0]
}
}
}
The body runs only while a drag is hovering this widget. Inside:
AcceptDragDropPayload(type, flags)returns a non-nullImGuiPayload?on the frame the user releases over the target with a payload whose type matchestype; otherwise it returns null (including on every frame the drag is hovering but not yet dropped).The returned payload’s
Datafield is avoid? const—reinterpret<T? const>(payload.Data)[0]reads the typed value back.
8.18.1.24.1.4. Driving from outside
Playwright exposes a high-level drag_to that bridges source-bbox →
target-bbox automatically:
require imgui/imgui_playwright
drag_to(app, "MAIN_WIN/SOURCE_BTN", "MAIN_WIN/TARGET_BTN", steps = 8)
It reads both bboxes from a fresh snapshot, computes (dx, dy) =
target_center - source_center, and composes an imgui_mouse_play
timeline — warp to the source, press, steps-scaled interpolated
moves to the target, release. ImGui’s drag state machine activates around
the second or third move (depending on io.MouseDragThreshold) and
resolves on release.
The lower-level drag(app, target, dx, dy, steps) is available when
you want absolute offsets — useful for testing drag-without-drop or
drag-cancel paths.
Verifying the drop. drag_drop_target surfaces a running accepted
count in its telemetry payload — it counts the drops ImGui actually delivers
(GetDragDropPayload().Delivery), so a driver or test can assert the drop
landed without reaching into the example’s caller-owned globals:
drag_to(app, "MAIN_WIN/SOURCE_BTN", "MAIN_WIN/TARGET_BTN")
// accepted ticks 0 -> 1 on a real delivery over the target
wait_for_payload_value(app, "MAIN_WIN/TARGET_DD", "accepted", 1, 300)
8.18.1.24.1.5. Next steps
That’s the last container tutorial. The next walkthrough switches focus to live-reload internals.
See also
Full source: modules/dasImgui/examples/features/drag_drop.das
Integration test: modules/dasImgui/tests/test_drag_drop.das.
Boost macros — the macro layer.
Containers — the container family in general.