8.19.39. Drag widgets

The drag family is click-and-scrub numeric editing: press inside the widget, drag horizontally, release. The value tracks pixel movement scaled by speed. Same call shape spans scalar / vector / range and float / int — one mental model, ten widgets.

drag_float(IDENT, (text = "..", speed = 0.01f, format = "%.3f",
                   flags = ImGuiSliderFlags....))
drag_int(IDENT, (text = "..", speed = 1.0f, format = "%d"))
drag_float2 / drag_float3 / drag_float4        // vector forms
drag_int2   / drag_int3   / drag_int4
drag_float_range2(IDENT, (text, speed, format)) // paired lo / hi handles
drag_int_range2(IDENT, (text, speed, format))

Bounds live on the state struct (IDENT.bounds = (lo, hi)), set once per frame from app code. Zero-init = unclamped — scrub goes anywhere.

Source: modules/dasImgui/examples/tutorial/drag.das.

8.19.39.1. Walkthrough

  1options gen2
  2options _comment_hygiene = true
  3options gc
  4
  5require imgui
  6require imgui_app
  7require opengl/opengl_boost
  8require live/glfw_live
  9require live/live_api
 10require live/live_commands
 11require live/live_vars
 12require live_host
 13require imgui/imgui_live
 14require imgui/imgui_boost_runtime
 15require imgui/imgui_boost_v2
 16require imgui/imgui_widgets_builtin
 17require imgui/imgui_containers_builtin
 18require imgui/imgui_visual_aids
 19
 20// =============================================================================
 21// TUTORIAL: drag widgets — click-and-scrub numeric editing.
 22//
 23//   drag_float(IDENT, (text = "..", speed = 0.01f, format = "%.3f",
 24//                      flags = ImGuiSliderFlags....))
 25//   drag_int / drag_float2/3/4 / drag_int2/3/4 / drag_float_range2 /
 26//   drag_int_range2 — same shape; scalar / vector / range variants.
 27//
 28// Bounds live on the state struct (`IDENT.bounds = (lo, hi)`), set once per
 29// frame from app code. Zero-init = unclamped (drag scrubs to any value).
 30// `speed` is units-per-pixel of horizontal drag; `format` is the printf-style
 31// label format.
 32//
 33// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/drag.das
 34// LIVE:       daslang-live modules/dasImgui/examples/tutorial/drag.das
 35//
 36// DRIVE (when running live):
 37//   # Set scalar value directly:
 38//   curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRAG_WIN/D_FLOAT","value":0.75}}' \
 39//        localhost:9090/command
 40//   # Set vector value (one number per component):
 41//   curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRAG_WIN/D_VEC3","value":[1.0,2.0,3.0]}}' \
 42//        localhost:9090/command
 43//   # Set range (lo, hi):
 44//   curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRAG_WIN/D_RANGE","value":[10.0,40.0]}}' \
 45//        localhost:9090/command
 46// =============================================================================
 47
 48[export]
 49def init() {
 50    live_create_window("dasImgui drag tutorial", 760, 520)
 51    live_imgui_init(live_window)
 52    let io & = unsafe(GetIO())
 53    GetStyle().FontScaleMain = 1.4
 54}
 55
 56[export]
 57def update() {
 58    if (!live_begin_frame()) return
 59    begin_frame()
 60
 61    ImGui_ImplGlfw_NewFrame()
 62    apply_synth_io_override()
 63    NewFrame()
 64
 65    SetNextWindowPos(ImVec2(20.0f, 20.0f), ImGuiCond.Always)
 66    SetNextWindowSize(ImVec2(720.0f, 480.0f), ImGuiCond.Always)
 67    window(DRAG_WIN, (text = "drag tutorial", closable = false,
 68                      flags = ImGuiWindowFlags.None)) {
 69
 70        text("Click and drag horizontally to scrub the value.")
 71        text(D_HINT, (text = "Bounds set via IDENT.bounds = (lo, hi). Zero-init = unclamped."))
 72        separator()
 73
 74        // ---- Stage 1: scalar float, clamped 0..1 ----
 75        D_FLOAT.bounds = (0.0f, 1.0f)
 76        drag_float(D_FLOAT, (text = "opacity", speed = 0.005f, format = "%.3f"))
 77        text("D_FLOAT.value = {D_FLOAT.value}")
 78        spacing()
 79
 80        // ---- Stage 2: scalar int ----
 81        D_INT.bounds = (0, 100)
 82        drag_int(D_INT, (text = "score", speed = 1.0f, format = "%d"))
 83        text("D_INT.value = {D_INT.value}")
 84        spacing()
 85
 86        // ---- Stage 3: vector — three components on one row ----
 87        D_VEC3.bounds = (-10.0f, 10.0f)
 88        drag_float3(D_VEC3, (text = "position", speed = 0.05f, format = "%.2f"))
 89        text("D_VEC3.value = ({D_VEC3.value.x}, {D_VEC3.value.y}, {D_VEC3.value.z})")
 90        spacing()
 91
 92        // ---- Stage 4: range — paired min/max scrubbing, lo <= hi enforced ----
 93        D_RANGE.bounds = (0.0f, 100.0f)
 94        drag_float_range2(D_RANGE, (text = "band", speed = 0.5f, format = "%.1f"))
 95        text("D_RANGE.value = [{D_RANGE.value.x}, {D_RANGE.value.y}]")
 96    }
 97
 98    end_of_frame()
 99    Render()
100    var w, h : int
101    live_get_framebuffer_size(w, h)
102    glViewport(0, 0, w, h)
103    glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
104    glClear(GL_COLOR_BUFFER_BIT)
105    live_imgui_render()
106
107    live_end_frame()
108}
109
110[export]
111def shutdown() {
112    live_imgui_shutdown()
113    live_destroy_window()
114}
115
116[export]
117def main() {
118    init()
119    while (!exit_requested()) {
120        update()
121        maybe_collect_gc()
122    }
123    shutdown()
124}

8.19.39.1.1. Requires

Already in the baseline boost layer:

  • imgui/imgui_widgets_builtin — every drag_* rail.

  • imgui/imgui_boost_runtimeDragStateFloat / DragStateInt / DragStateFloat3 / DragStateRangeFloat state structs.

The caller-owned form at the end of this page also needs daslib/safe_addr for safe_addr.

8.19.39.1.2. Speed and format

speed is units-per-pixel of horizontal cursor movement:

drag_float(OPACITY, (text = "opacity", speed = 0.005f))  // 0.005 / px → 200 px = 1.0
drag_int(SCORE,     (text = "score", speed = 1.0f))      // 1 / px → ImGui clamps to int

format is the printf-style label format. The default "%.3f" / "%d" covers most cases; bump to "%.6f" for fine-grained float values or "%+04d" for signed-padded ints.

8.19.39.1.3. Bounds

Bounds are a property of the state, not the call:

D_FLOAT.bounds = (0.0f, 1.0f)
drag_float(D_FLOAT, (text = "opacity", speed = 0.005f))

Set them every frame (idempotent assignment, no branching needed). The wrapper passes (min, max) to ImGui’s DragFloat which clamps the scrub. Zero-initialized bounds = (0, 0) = special-cased to mean unclamped.

8.19.39.1.4. Vector forms

The 2 / 3 / 4 suffix puts that many components on one row, each its own drag handle. state.value becomes float2 / float3 / float4 (or int2 / int3 / int4):

D_VEC3.bounds = (-10.0f, 10.0f)   // applies to every component
drag_float3(D_VEC3, (text = "position", speed = 0.05f))
// D_VEC3.value.x, D_VEC3.value.y, D_VEC3.value.z

8.19.39.1.5. Range forms

drag_float_range2 / drag_int_range2 render two handles sharing one bar — useful for “filter between lo and hi” widgets:

D_RANGE.bounds = (0.0f, 100.0f)
drag_float_range2(D_RANGE, (text = "band", speed = 0.5f))
// D_RANGE.value.x = lo, D_RANGE.value.y = hi (ImGui enforces lo <= hi)

8.19.39.1.6. Driving from outside

Every drag widget exposes the same telemetry channel — imgui_force_set writes state.pending_value which the next frame consumes:

# Scalar:
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRAG_WIN/D_FLOAT","value":0.75}}' \
     localhost:9090/command
# Vector — one number per component:
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRAG_WIN/D_VEC3","value":[1.0,2.0,3.0]}}' \
     localhost:9090/command
# Range — (lo, hi) tuple:
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRAG_WIN/D_RANGE","value":[10.0,40.0]}}' \
     localhost:9090/command

The dispatcher ([widget_dispatch] on the state struct) accepts the right JSON shape per state type — scalar number, array of numbers, or two-element array for ranges.

8.19.39.1.7. Drag vs slider vs input

The three numeric-edit families differ in interaction shape:

  • drag — click and scrub, no fixed track. Best for “tweak this value” where the absolute range is large or open-ended.

  • slider — click and drag along a fixed-width track between v_min / v_max. Best for bounded percentages, settings sliders.

  • input — type the number, optionally with + / - step buttons. Best for precise values where the user knows the number.

All three families share the same vector / scalar / format conventions. See Slider widgets.

8.19.39.1.8. Caller-owned variant

For sites where the value lives on an external scalar (not a widget state struct), use the edit_drag_* rail instead — it takes a T? pointer via safe_addr and skips the state-struct allocation:

var g_opacity : float = 0.5f
edit_drag_float(safe_addr(g_opacity), (id = "OPACITY",
                                       text = "opacity", speed = 0.005f))

See External-pointer editing rail.

See also

Full source: modules/dasImgui/examples/tutorial/drag.das

Features-side demo: modules/dasImgui/examples/features/inputs_drag.das — every drag widget in one window, useful for imgui_force_set smoke testing.

Sibling tutorial: Slider widgets.

Boost macros — the macro layer.