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
2options _comment_hygiene = true
3
4require imgui/imgui_harness
5require imgui/imgui_containers_builtin
6require imgui/imgui_widgets_builtin
7require imgui/imgui_implot_boost_v2
8require implot
9require math
10
11// =============================================================================
12// TUTORIAL: drag_tools — interactive draggable handles inside a v2 `plot` scope.
13//
14// drag_point(state, id, col) — a draggable (x,y) point.
15// drag_line_x(state, id, col) — a draggable vertical line (e.g. a threshold).
16// drag_rect(state, id, col) — a draggable rectangle region (e.g. an ROI).
17//
18// Each tool owns a PERSISTENT state struct (a global): its value updates in place
19// as you drag, and serializes into the snapshot under `<plot>/<id>` — so a test can
20// read the handle's bbox, synth-drag it, and assert the bound value moved. Built on
21// the dasImgui harness: runs standalone (windowed), headless (--headless), and live.
22//
23// STANDALONE: daslang.exe modules/dasImguiImplot/examples/tutorial/drag_tools.das
24// LIVE: daslang-live modules/dasImguiImplot/examples/tutorial/drag_tools.das
25// =============================================================================
26
27var g_ctx : ImPlotContext?
28var g_sin : array<double>
29var g_pt : DragPointState
30var g_thresh : DragLineState
31var g_roi : DragRectState
32
33[export]
34def init() {
35 harness_init("dasImguiImplot — drag_tools", 1100, 760)
36 g_ctx = implot::CreateContext()
37 g_sin <- [for (i in range(200)); double(sin(float(i) * 0.05f))]
38 // Keep the three handles spatially separated so their grab rects never overlap
39 // (an overlapping grab rect steals the active-id and the wrong tool moves).
40 g_pt = DragPointState(x = 150.0lf, y = 0.0lf, held = false)
41 g_thresh = DragLineState(value = 100.0lf, held = false)
42 g_roi = DragRectState(x_min = 20.0lf, y_min = -0.9lf, x_max = 70.0lf, y_max = -0.3lf,
43 held = false)
44}
45
46[export]
47def update() {
48 if (!harness_begin_frame()) return
49 harness_new_frame()
50
51 SetNextWindowPos(float2(20.0, 20.0), ImGuiCond.Always)
52 SetNextWindowSize(float2(1060.0, 720.0), ImGuiCond.Always)
53 window(PLOT_WIN, (text = "drag tools", closable = false,
54 flags = ImGuiWindowFlags.None)) {
55 text("Drag the yellow point, the green threshold line, or the magenta ROI box.")
56 plot(WAVES, (title = "interactive", size = float2(-1.0f, 620.0f),
57 flags = ImPlotFlags.None)) {
58 setup_axes("sample", "value")
59 setup_axes_limits(0.0lf, 200.0lf, -1.5lf, 1.5lf)
60 plot_line("sin", g_sin)
61 // Drawn back-to-front so the point sits on top and grabs first.
62 drag_rect(g_roi, "roi", float4(1.0f, 0.0f, 1.0f, 0.35f))
63 drag_line_x(g_thresh, "thresh", float4(0.2f, 1.0f, 0.3f, 1.0f), 2.0f)
64 drag_point(g_pt, "p0", float4(1.0f, 0.9f, 0.1f, 1.0f), 6.0f)
65 }
66 }
67
68 harness_end_frame()
69}
70
71[export]
72def shutdown() {
73 if (g_ctx != null) {
74 DestroyContext(g_ctx)
75 }
76 harness_shutdown()
77}
78
79[export]
80def main() {
81 init()
82 while (!exit_requested()) {
83 update()
84 }
85 shutdown()
86}
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.