Drag tools
Interactive handles you drag inside a plot. drag_point is a draggable
(x, y) marker, drag_line_x a draggable vertical line (a threshold),
drag_rect a draggable rectangle (a region of interest). Unlike the plot items,
a drag tool owns mutable state — the value being dragged — so the caller passes a
persistent state struct (a global). The value updates in place each frame and
serializes into the snapshot under <plot>/<id>, so a test can read the handle’s
screen bbox, synth-drag it, and assert the bound value moved.
var g_pt : DragPointState // persistent: holds the dragged (x, y)
...
plot(WAVES, (title = "interactive", size = float2(-1.0f, 620.0f), flags = ImPlotFlags.None)) {
plot_line("sin", g_sin)
drag_rect(g_roi, "roi", float4(1.0f, 0.0f, 1.0f, 0.35f))
drag_line_x(g_thresh, "thresh", float4(0.2f, 1.0f, 0.3f, 1.0f), 2.0f)
drag_point(g_pt, "p0", float4(1.0f, 0.9f, 0.1f, 1.0f), 6.0f) // drawn last -> grabs first
}
Source: examples/tutorial/drag_tools.das.
1options gen2
2
3require imgui/imgui_harness
4require imgui/imgui_containers_builtin
5require imgui/imgui_widgets_builtin
6require imgui/imgui_implot_boost_v2
7require implot
8require math
9
10// =============================================================================
11// TUTORIAL: drag_tools — interactive draggable handles inside a v2 `plot` scope.
12//
13// drag_point(state, id, col) — a draggable (x,y) point.
14// drag_line_x(state, id, col) — a draggable vertical line (e.g. a threshold).
15// drag_rect(state, id, col) — a draggable rectangle region (e.g. an ROI).
16//
17// Each tool owns a PERSISTENT state struct (a global): its value updates in place
18// as you drag, and serializes into the snapshot under `<plot>/<id>` — so a test can
19// read the handle's bbox, synth-drag it, and assert the bound value moved. Built on
20// the dasImgui harness: runs standalone (windowed), headless (--headless), and live.
21//
22// STANDALONE: daslang.exe modules/dasImguiImplot/examples/tutorial/drag_tools.das
23// LIVE: daslang-live modules/dasImguiImplot/examples/tutorial/drag_tools.das
24// =============================================================================
25
26var g_ctx : ImPlotContext?
27var g_sin : array<double>
28var g_pt : DragPointState
29var g_thresh : DragLineState
30var g_roi : DragRectState
31
32[export]
33def init() {
34 harness_init("dasImguiImplot — drag_tools", 1100, 760)
35 g_ctx = implot::CreateContext()
36 g_sin <- [for (i in range(200)); double(sin(float(i) * 0.05f))]
37 // Keep the three handles spatially separated so their grab rects never overlap
38 // (an overlapping grab rect steals the active-id and the wrong tool moves).
39 g_pt = DragPointState(x = 150.0lf, y = 0.0lf, held = false)
40 g_thresh = DragLineState(value = 100.0lf, held = false)
41 g_roi = DragRectState(x_min = 20.0lf, y_min = -0.9lf, x_max = 70.0lf, y_max = -0.3lf,
42 held = false)
43}
44
45[export]
46def update() {
47 if (!harness_begin_frame()) return
48 harness_new_frame()
49
50 SetNextWindowPos(float2(20.0, 20.0), ImGuiCond.Always)
51 SetNextWindowSize(float2(1060.0, 720.0), ImGuiCond.Always)
52 window(PLOT_WIN, (text = "drag tools", closable = false,
53 flags = ImGuiWindowFlags.None)) {
54 text("Drag the yellow point, the green threshold line, or the magenta ROI box.")
55 plot(WAVES, (title = "interactive", size = float2(-1.0f, 620.0f),
56 flags = ImPlotFlags.None)) {
57 setup_axes("sample", "value")
58 setup_axes_limits(0.0lf, 200.0lf, -1.5lf, 1.5lf)
59 plot_line("sin", g_sin)
60 // Drawn back-to-front so the point sits on top and grabs first.
61 drag_rect(g_roi, "roi", float4(1.0f, 0.0f, 1.0f, 0.35f))
62 drag_line_x(g_thresh, "thresh", float4(0.2f, 1.0f, 0.3f, 1.0f), 2.0f)
63 drag_point(g_pt, "p0", float4(1.0f, 0.9f, 0.1f, 1.0f), 6.0f)
64 }
65 }
66
67 harness_end_frame()
68}
69
70[export]
71def shutdown() {
72 if (g_ctx != null) {
73 DestroyContext(g_ctx)
74 }
75 harness_shutdown()
76}
77
78[export]
79def main() {
80 init()
81 while (!exit_requested()) {
82 update()
83 }
84 shutdown()
85}
Walkthrough
The recording drives each handle with real synthetic input and self-verifies the
bound value moved. The yellow point follows the cursor in x and y; the green
threshold line slides along x; and the magenta region shows its two modes —
grab the body (near the center) to move the whole rectangle, with all four bounds
travelling together, or grab a corner to resize it, where only that corner moves.
A missed grab or a frozen handle fails the recording at teardown.
Persistent state
Each tool takes a var state struct the caller keeps alive across frames —
DragPointState{x, y, held}, DragLineState{value, held},
DragRectState{x_min, y_min, x_max, y_max, held}. The wrapper writes the new
value back through it each frame, and held is true on the frames the handle is
grabbed. Initialize them in init and read them wherever you need the current
value.
Overlap and draw order
Drag tools grab by an invisible rect around the handle. If two handles’ grab rects overlap, one steals the active-id and the wrong tool moves — so the example keeps the point, the threshold line, and the ROI spatially separated, and draws them back-to-front so the point (drawn last) sits on top and grabs first.
Driving them in tests
The handle’s snapshot path is handle_path(session, id) (e.g.
PLOT_WIN/WAVES/p0). The playwright layer
reads drag_point_value / drag_line_value / drag_rect_value from the
payload, and wait_for_point_moved gates a synthetic drag actually landing —
which is how test_drag_tools proves the synthetic drag moves the bound value
exactly as a real mouse would.