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: examples/features/drag_drop.das.
Walkthrough
Note
APNG recording forthcoming — run
tests/integration/record_drag_drop.das against a live
daslang-live instance to regenerate it.
1options gen2
2
3require imgui/imgui_harness
4
5// =============================================================================
6// FEATURE: drag_drop — [container]s drag_drop_source + drag_drop_target.
7// Both gate their body on ImGui's internal drag-drop state machine.
8// Bodies call raw SetDragDropPayload / AcceptDragDropPayload —
9// payload data is caller-owned and crosses widget boundaries.
10// SHOWS: drag SOURCE_BTN onto TARGET_BTN; TARGET_BTN's body accepts the
11// "MY_INT" payload and writes it into RECEIVED. DROP_COUNT
12// increments on each successful release-over-target.
13// STANDALONE: daslang.exe modules/dasImgui/examples/features/drag_drop.das
14// HEADLESS: daslang.exe modules/dasImgui/examples/features/drag_drop.das -- --headless --headless-frames=60
15// LIVE: daslang-live modules/dasImgui/examples/features/drag_drop.das
16// =============================================================================
17
18var PAYLOAD_VALUE : int = 42
19var RECEIVED : int = 0
20var DROP_COUNT : int = 0
21
22[export]
23def init() {
24 harness_init("drag_drop — source + target", 720, 480)
25 var io & = unsafe(GetIO())
26 io.FontGlobalScale = 1.5
27}
28
29[export]
30def update() {
31 if (!harness_begin_frame()) return
32 harness_new_frame()
33
34 SetNextWindowSize(ImVec2(660.0, 420.0), ImGuiCond.Always)
35 window(MAIN_WIN, (text = "drag_drop", closable = false,
36 flags = ImGuiWindowFlags.None)) {
37 text("Drag the SOURCE button onto the TARGET button.")
38 separator()
39
40 // Source: a normal button. Body runs only on active drag.
41 button(SOURCE_BTN, (text = "SOURCE"))
42 drag_drop_source(SOURCE_DD, (flags = ImGuiDragDropFlags.None)) {
43 unsafe {
44 SetDragDropPayload("MY_INT", addr(PAYLOAD_VALUE),
45 uint64(typeinfo sizeof(PAYLOAD_VALUE)),
46 ImGuiCond.Once)
47 }
48 text("Dragging int: {PAYLOAD_VALUE}")
49 }
50
51 // 40 px gap between the source and target buttons.
52 same_line((spacing = 40.0f))
53
54 // Target: a normal button. Body runs only when payload-bearing drag hovers.
55 button(TARGET_BTN, (text = "TARGET"))
56 drag_drop_target(TARGET_DD) {
57 let payload = AcceptDragDropPayload("MY_INT", ImGuiDragDropFlags.None)
58 if (payload != null) {
59 unsafe {
60 RECEIVED = (reinterpret<int? const>(payload.Data))[0]
61 }
62 DROP_COUNT++
63 }
64 }
65
66 separator()
67 text("Source payload value: {PAYLOAD_VALUE}")
68 text("Target received value: {RECEIVED}")
69 text("Drops accepted: {DROP_COUNT}")
70 }
71
72 harness_end_frame()
73}
74
75[export]
76def shutdown() {
77 harness_shutdown()
78}
79
80[export]
81def main() {
82 init()
83 while (!exit_requested()) {
84 update()
85 }
86 shutdown()
87}
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).
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.
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.
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 dispatches the L1 imgui_drag
coroutine — press at the source, steps interpolated moves, release
at the target. 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.
Next steps
That’s the last container tutorial. The next walkthrough switches focus to live-reload internals.
See also
Full source: examples/features/drag_drop.das
Integration test: tests/integration/test_drag_drop.das.
Boost macros — the macro layer.
Containers — the container family in general.