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. type is a short string (max 32 chars) that the target compares against; data is 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-null ImGuiPayload? on the frame the user releases over the target with a payload whose type matches type; otherwise it returns null (including on every frame the drag is hovering but not yet dropped).

  • The returned payload’s Data field is a void? constreinterpret<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.