Drawlist rail
ImGui exposes three viewport-level draw lists — window, foreground, background —
plus a family of low-level Add* primitives that paint directly to them
(AddLine, AddRect, AddCircle, etc.). The boost layer wraps them
behind three scope wrappers and a primitive set covering the geometric basics
(line / rect / circle / triangle / text) plus the v2 extras
(rect_filled_multi_color / ellipse / ngon / polyline / bezier). The
path-building (path_*), channel-splitting (channels_*), and
clip-rect rails are control-flow helpers, not registered [drawlist_prim]
entries — they shape what the primitives render but do not themselves
create snapshot rows:
with_window_drawlist() $(var dl) {
dl |> add_rect_filled(p0, p1, rgba(40u, 80u, 200u, 200u))
dl |> add_line(a, b, rgba(255u, 255u, 0u, 255u), 2.0f)
}
Every primitive registers a per-frame snapshot entry under a synthesized
<window>/<mod>:<line>:<col> path key, so playwright / mouse-cards can
target a specific call site by path without the caller declaring a state
global. Drawlist primitives are decoration-only — no hover / active / focus
state is captured.
Source: examples/tutorial/drawlist.das.
Walkthrough
The recording is a display-only tour - no gesture to drive - so the self-check
is on the output: it narrates each scope wrapper while asserting the registry
holds the painted primitives (record_check_kind_count over the add_*
kind family, since drawlist call sites register under synthesized path keys
rather than caller idents) plus the window / foreground-label widgets on screen.
A rail that stopped painting would abort the recording.
1options gen2
2
3require imgui
4require imgui_app
5require opengl/opengl_boost
6require live/glfw_live
7require live/live_api
8require live/live_commands
9require live/live_vars
10require live_host
11require imgui/imgui_live
12require imgui/imgui_boost_runtime
13require imgui/imgui_boost_v2
14require imgui/imgui_widgets_builtin
15require imgui/imgui_containers_builtin
16require imgui/imgui_scope_builtin
17require imgui/imgui_drawlist_builtin
18require imgui/imgui_colors
19require imgui/imgui_visual_aids
20
21// =============================================================================
22// TUTORIAL: drawlist — three scope wrappers (with_window_drawlist /
23// with_foreground_drawlist / with_background_drawlist) and the
24// full primitive set: 8 originals (add_line, add_rect,
25// add_rect_filled, add_circle, add_circle_filled, add_triangle,
26// add_triangle_filled, add_text) plus the v2 additions
27// (add_rect_filled_multi_color, add_ellipse, add_ellipse_filled,
28// add_ngon, add_ngon_filled, add_polyline, add_bezier_cubic,
29// add_bezier_quadratic). For the path-building, channel-splitting,
30// and clip-rect rails see the dedicated feature files
31// (examples/features/drawlist_path.das,
32// examples/features/drawlist_channels.das,
33// examples/features/clip_rect.das).
34//
35// The rail bridges to ImGui's three viewport-level draw lists with the same
36// telemetry surface as `[widget]`: path-keyed entries land in `g_registry`
37// so mouse-cards / playwright can target a specific call site by
38// `<window>/<mod>:<line>:<col>`. No per-call state global is registered —
39// primitives are decoration-only, last write wins on overlapping draws.
40//
41// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/drawlist.das
42// LIVE: daslang-live modules/dasImgui/examples/tutorial/drawlist.das
43// =============================================================================
44
45[export]
46def init() {
47 live_create_window("dasImgui drawlist tutorial", 1000, 800)
48 live_imgui_init(live_window)
49 let io & = unsafe(GetIO())
50 GetStyle().FontScaleMain = 1.2
51}
52
53[export]
54def update() {
55 if (!live_begin_frame()) return
56 begin_frame()
57
58 ImGui_ImplGlfw_NewFrame()
59 apply_synth_io_override()
60 NewFrame()
61
62 SetNextWindowPos(ImVec2(30.0f, 30.0f), ImGuiCond.Always)
63 SetNextWindowSize(ImVec2(700.0f, 700.0f), ImGuiCond.Always)
64 window(DRAW_WIN, (text = "drawlist - rail tour", closable = false,
65 flags = ImGuiWindowFlags.None)) {
66 text(DRAW_INTRO, (text = "Three scope wrappers + shape primitives (8 originals + v2 extras)."))
67 separator()
68
69 let origin = GetCursorScreenPos()
70 let red = rgba(220u, 60u, 60u, 255u)
71 let green = rgba(60u, 220u, 100u, 255u)
72 let blue = rgba(70u, 130u, 240u, 255u)
73 let yellow = rgba(240u, 220u, 60u, 255u)
74 let gray = rgba(200u, 200u, 210u, 255u)
75 let bg_tint = rgba(20u, 30u, 50u, 120u)
76
77 with_window_drawlist() $(var dl) {
78 dl |> add_rect_filled(float2(origin.x + 8.0f, origin.y + 8.0f),
79 float2(origin.x + 660.0f, origin.y + 80.0f),
80 rgba(35u, 35u, 50u, 200u), 6.0f)
81 dl |> add_rect(float2(origin.x + 16.0f, origin.y + 16.0f),
82 float2(origin.x + 652.0f, origin.y + 72.0f),
83 gray, 4.0f, ImDrawFlags.None, 1.5f)
84 dl |> add_line(float2(origin.x + 16.0f, origin.y + 16.0f),
85 float2(origin.x + 652.0f, origin.y + 72.0f),
86 yellow, 2.0f)
87 dl |> add_circle_filled(float2(origin.x + 60.0f, origin.y + 160.0f), 32.0f, blue, 0)
88 dl |> add_circle(float2(origin.x + 150.0f, origin.y + 160.0f), 32.0f, blue, 0, 2.0f)
89 dl |> add_triangle_filled(float2(origin.x + 260.0f, origin.y + 124.0f),
90 float2(origin.x + 226.0f, origin.y + 196.0f),
91 float2(origin.x + 294.0f, origin.y + 196.0f), green)
92 dl |> add_triangle(float2(origin.x + 380.0f, origin.y + 124.0f),
93 float2(origin.x + 346.0f, origin.y + 196.0f),
94 float2(origin.x + 414.0f, origin.y + 196.0f), green, 2.0f)
95 dl |> add_text(float2(origin.x + 16.0f, origin.y + 220.0f), gray,
96 "add_text - rendered onto the window drawlist")
97
98 // ----- v2 additions: shape extras -----
99 // Multi-color gradient rect.
100 dl |> add_rect_filled_multi_color(
101 float2(origin.x + 16.0f, origin.y + 260.0f),
102 float2(origin.x + 240.0f, origin.y + 290.0f),
103 rgba(255u, 0u, 0u, 220u), rgba(0u, 255u, 0u, 220u),
104 rgba(0u, 0u, 255u, 220u), rgba(255u, 255u, 0u, 220u))
105 // Stroked + filled ellipse.
106 dl |> add_ellipse(float2(origin.x + 300.0f, origin.y + 275.0f),
107 float2(36.0f, 16.0f), yellow, 0.0f, 0, 1.5f)
108 dl |> add_ellipse_filled(float2(origin.x + 400.0f, origin.y + 275.0f),
109 float2(36.0f, 16.0f), blue, 0.0f, 0)
110 // Stroked + filled N-gon (hexagon).
111 dl |> add_ngon(float2(origin.x + 500.0f, origin.y + 275.0f),
112 22.0f, green, 6, 2.0f)
113 dl |> add_ngon_filled(float2(origin.x + 580.0f, origin.y + 275.0f),
114 22.0f, green, 6)
115 // Cubic + quadratic bezier curves.
116 dl |> add_bezier_cubic(
117 float2(origin.x + 16.0f, origin.y + 330.0f),
118 float2(origin.x + 110.0f, origin.y + 290.0f),
119 float2(origin.x + 200.0f, origin.y + 360.0f),
120 float2(origin.x + 300.0f, origin.y + 310.0f),
121 yellow, 2.0f, 0)
122 dl |> add_bezier_quadratic(
123 float2(origin.x + 320.0f, origin.y + 330.0f),
124 float2(origin.x + 430.0f, origin.y + 290.0f),
125 float2(origin.x + 540.0f, origin.y + 330.0f),
126 blue, 2.0f, 0)
127 // Polyline (open M-shape) below.
128 var poly <- [
129 float2(origin.x + 16.0f, origin.y + 380.0f),
130 float2(origin.x + 60.0f, origin.y + 350.0f),
131 float2(origin.x + 100.0f, origin.y + 370.0f),
132 float2(origin.x + 140.0f, origin.y + 350.0f),
133 float2(origin.x + 180.0f, origin.y + 380.0f)
134 ]
135 dl |> add_polyline(poly, gray, ImDrawFlags.None, 1.5f)
136 delete poly
137 }
138
139 dummy(DRAW_BAND_SLOT, (size = float2(660.0f, 400.0f)))
140
141 text(DRAW_FG_LABEL, (text = "Foreground swatch (overlays everything):"))
142
143 let vp_pos = GetMainViewport().Pos
144 let vp_size = GetMainViewport().Size
145 with_foreground_drawlist() $(var fdl) {
146 fdl |> add_rect_filled(float2(vp_pos.x + 12.0f, vp_pos.y + 12.0f),
147 float2(vp_pos.x + 100.0f, vp_pos.y + 26.0f), red)
148 fdl |> add_text(float2(vp_pos.x + 20.0f, vp_pos.y + 12.0f),
149 rgba(255u, 255u, 255u, 255u), "FG")
150 }
151
152 with_background_drawlist() $(var bdl) {
153 bdl |> add_rect_filled(float2(vp_pos.x, vp_pos.y),
154 float2(vp_pos.x + vp_size.x, vp_pos.y + vp_size.y),
155 bg_tint)
156 }
157 }
158
159 end_of_frame()
160 Render()
161 var w, h : int
162 live_get_framebuffer_size(w, h)
163 glViewport(0, 0, w, h)
164 glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
165 glClear(GL_COLOR_BUFFER_BIT)
166 live_imgui_render()
167
168 live_end_frame()
169}
170
171[export]
172def shutdown() {
173 live_imgui_shutdown()
174 live_destroy_window()
175}
176
177[export]
178def main() {
179 init()
180 while (!exit_requested()) {
181 update()
182 }
183 shutdown()
184}
Scope wrappers
Three thin wrappers cover the three drawlist scopes:
with_window_drawlist() $(var dl) { ... }— current window’s drawlist. Painting is clipped to the window’s content region and stacks under any subsequent widgets rendered in this window.with_foreground_drawlist() $(var dl) { ... }— top-most foreground drawlist. Paints over every window on the viewport. Use for HUD overlays, drag indicators, tooltips that must escape window clipping.with_background_drawlist() $(var dl) { ... }— bottom-most background drawlist. Paints under every window on the viewport. Use for full-viewport backdrops (gradients, vignettes, debug grids).
Each wrapper acquires the drawlist pointer, yields it to the block as dl,
and exits. There’s no push / pop pairing under the hood — drawlists are
viewport-level state that any ImGui code can reach via GetWindowDrawList
/ GetForegroundDrawList / GetBackgroundDrawList; the wrappers exist
to give the rail a uniform body shape (with_X() $(var dl) { dl |> add_*(...) })
and to keep raw GetWindowDrawList calls off the boost surface.
Primitives
Eight [drawlist_prim]-tagged painters cover the geometric basics:
add_line(dl, a, b, col, thickness = 1.0f)add_rect(dl, a, b, col, rounding = 0.0f, flags = ImDrawFlags.None, thickness = 1.0f)add_rect_filled(dl, a, b, col, rounding = 0.0f, flags = ImDrawFlags.None)add_circle(dl, center, radius, col, num_segments = 0, thickness = 1.0f)add_circle_filled(dl, center, radius, col, num_segments = 0)add_triangle(dl, a, b, c, col, thickness = 1.0f)add_triangle_filled(dl, a, b, c, col)add_text(dl, pos, col, text)
col is a packed ABGR uint — use rgba(r, g, b, a) to build one,
or GetColorU32(ImGuiCol.*) for style-colour references. Positions are
screen-space pixels; widget-relative drawing typically anchors on
GetCursorScreenPos / GetItemRectMin / GetItemRectMax.
Shape extras (v2 additions)
Eight additional [drawlist_prim] painters round out the shape set:
add_rect_filled_multi_color(dl, a, b, col_ul, col_ur, col_br, col_bl)— per-corner gradient (4 colours, bilinearly interpolated).add_ellipse(dl, center, radii, col, rot = 0.0f, num_segments = 0, thickness = 1.0f)/add_ellipse_filled(dl, center, radii, col, rot = 0.0f, num_segments = 0)—radii=(rx, ry)half-axes;rotrotates the ellipse aboutcenter(radians).add_ngon(dl, center, radius, col, num_segments, thickness = 1.0f)/add_ngon_filled(dl, center, radius, col, num_segments)— regular N-gons;num_segmentsrequired (no adaptive default).add_polyline(dl, points : array<float2>, col, flags = ImDrawFlags.None, thickness = 1.0f)— open or closed polyline throughpoints(useImDrawFlags.Closedto close).add_bezier_cubic(dl, p1, p2, p3, p4, col, thickness = 1.0f, num_segments = 0)/add_bezier_quadratic(dl, p1, p2, p3, col, thickness = 1.0f, num_segments = 0)— bezier curves through anchor + control points.
Path building
For complex one-off shapes (rounded rects assembled from arcs, custom
polygons, etc.) the path family lets the caller build a vertex stack
incrementally then terminate with one of path_stroke /
path_fill_convex / path_fill_concave:
path_clear(dl)— discard pending path vertices.path_line_to(dl, pos)— append a straight-line segment.path_arc_to(dl, center, radius, a_min, a_max, num_segments = 0)— append a circular arc.path_bezier_quadratic_curve_to(dl, p2, p3, num_segments = 0)— append a quadratic bezier curve from the path’s current endpoint.path_fill_convex(dl, col)/path_fill_concave(dl, col)— close + fill (convex is faster; concave handles non-convex polygons).path_stroke(dl, col, flags = ImDrawFlags.None, thickness = 1.0f)— stroke the pending path (open by default). PassImDrawFlags.Closedto join the last vertex back to the first.
See examples/features/drawlist_path.das for a stroked rounded
rect, filled convex pentagon, and filled concave arrow built path-style.
Channel splitting
ImGui drawlists are append-only — once a vertex lands, later draws sit
on top. channels_split defers ordering: split the drawlist into N
channels, submit each layer in convenient order, then channels_merge
flattens them in channel-index order (channel 0 first, channel N-1 last).
channels_split(dl, count)— pushcountchannels.channels_set_current(dl, idx)— switch the active channel.channels_merge(dl)— flatten back to the main draw stream.
Typical use: draw a card’s foreground content first to measure its
bounding box, then go back to channel 0 to paint the background card
behind it. See examples/features/drawlist_channels.das.
Clip rect scope
with_clip_rect(min, max, intersect_with_current) { ... } (lives in
imgui_scope_builtin) restricts every widget and drawlist primitive
submitted inside the block to the given screen-space rect. Set
intersect_with_current=true (typical) to intersect with the existing
clip; false replaces it. See examples/features/clip_rect.das.
Path-key telemetry
The [drawlist_prim] annotation synthesizes a stable
<mod>:<line>:<col> path key at every call site (no per-call state
global is registered). Each primitive’s body publishes a lightweight
WidgetEntry — kind ("add_line" / "add_rect" / …) plus
bbox — into the per-frame registry under that key. Playwright /
mouse-cards can then target a specific call site by path:
var snap = wait_for_widget(d, "MY_WIN/<mod>:42:8", 15.0f)
t |> equal(find_widget(snap, "MY_WIN/<mod>:42:8")?["kind"] ?? "", "add_rect", ...)
Because the synthesized key shifts on edits, tests typically enumerate
drawlist entries by kind ("add_rect" etc.) rather than hardcoding
the line / column — see tests/integration/test_drawlist_path_key.das for
the walker pattern.
Standalone vs live
Same convention as the other tutorials. daslang.exe runs the demo
headlessly to frame N; daslang-live keeps the window open and reloads
on source edits.
See also
Full source: examples/tutorial/drawlist.das
Integration tests: tests/integration/test_drawlist_primitives.das,
tests/integration/test_with_window_drawlist.das,
tests/integration/test_drawlist_path_key.das.
Boost macros — the macro layer.