8.18.1.38. 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: modules/dasImgui/examples/tutorial/drawlist.das.

8.18.1.38.1. 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
  2options _comment_hygiene = true
  3
  4require imgui
  5require imgui_app
  6require opengl/opengl_boost
  7require live/glfw_live
  8require live/live_api
  9require live/live_commands
 10require live/live_vars
 11require live_host
 12require imgui/imgui_live
 13require imgui/imgui_boost_runtime
 14require imgui/imgui_boost_v2
 15require imgui/imgui_widgets_builtin
 16require imgui/imgui_containers_builtin
 17require imgui/imgui_scope_builtin
 18require imgui/imgui_drawlist_builtin
 19require imgui/imgui_colors
 20require imgui/imgui_visual_aids
 21
 22// =============================================================================
 23// TUTORIAL: drawlist — three scope wrappers (with_window_drawlist /
 24//           with_foreground_drawlist / with_background_drawlist) and the
 25//           full primitive set: 8 originals (add_line, add_rect,
 26//           add_rect_filled, add_circle, add_circle_filled, add_triangle,
 27//           add_triangle_filled, add_text) plus the v2 additions
 28//           (add_rect_filled_multi_color, add_ellipse, add_ellipse_filled,
 29//           add_ngon, add_ngon_filled, add_polyline, add_bezier_cubic,
 30//           add_bezier_quadratic). For the path-building, channel-splitting,
 31//           and clip-rect rails see the dedicated feature files
 32//           (examples/features/drawlist_path.das,
 33//           examples/features/drawlist_channels.das,
 34//           examples/features/clip_rect.das).
 35//
 36// The rail bridges to ImGui's three viewport-level draw lists with the same
 37// telemetry surface as `[widget]`: path-keyed entries land in `g_registry`
 38// so mouse-cards / playwright can target a specific call site by
 39// `<window>/<mod>:<line>:<col>`. No per-call state global is registered —
 40// primitives are decoration-only, last write wins on overlapping draws.
 41//
 42// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/drawlist.das
 43// LIVE:       daslang-live modules/dasImgui/examples/tutorial/drawlist.das
 44// =============================================================================
 45
 46[export]
 47def init() {
 48    live_create_window("dasImgui drawlist tutorial", 1000, 800)
 49    live_imgui_init(live_window)
 50    let io & = unsafe(GetIO())
 51    GetStyle().FontScaleMain = 1.2
 52}
 53
 54[export]
 55def update() { // nolint:STYLE038
 56    if (!live_begin_frame()) return
 57    begin_frame()
 58
 59    ImGui_ImplGlfw_NewFrame()
 60    apply_synth_io_override()
 61    NewFrame()
 62
 63    SetNextWindowPos(ImVec2(30.0f, 30.0f), ImGuiCond.Always)
 64    SetNextWindowSize(ImVec2(700.0f, 700.0f), ImGuiCond.Always)
 65    window(DRAW_WIN, (text = "drawlist - rail tour", closable = false,
 66                      flags = ImGuiWindowFlags.None)) {
 67        text(DRAW_INTRO, (text = "Three scope wrappers + shape primitives (8 originals + v2 extras)."))
 68        separator()
 69
 70        let origin = GetCursorScreenPos()
 71        let red    = rgba(220u,  60u,  60u, 255u)
 72        let green  = rgba(60u, 220u, 100u, 255u)
 73        let blue   = rgba(70u, 130u, 240u, 255u)
 74        let yellow = rgba(240u, 220u,  60u, 255u)
 75        let gray   = rgba(200u, 200u, 210u, 255u)
 76        let bg_tint = rgba(20u, 30u, 50u, 120u)
 77
 78        with_window_drawlist() $(var dl) {
 79            dl |> add_rect_filled(float2(origin.x +   8.0f, origin.y +  8.0f),
 80                                  float2(origin.x + 660.0f, origin.y + 80.0f),
 81                                  rgba(35u, 35u, 50u, 200u), 6.0f)
 82            dl |> add_rect(float2(origin.x +  16.0f, origin.y + 16.0f),
 83                           float2(origin.x + 652.0f, origin.y + 72.0f),
 84                           gray, 4.0f, ImDrawFlags.None, 1.5f)
 85            dl |> add_line(float2(origin.x +  16.0f, origin.y + 16.0f),
 86                           float2(origin.x + 652.0f, origin.y + 72.0f),
 87                           yellow, 2.0f)
 88            dl |> add_circle_filled(float2(origin.x +  60.0f, origin.y + 160.0f), 32.0f, blue, 0)
 89            dl |> add_circle(float2(origin.x + 150.0f, origin.y + 160.0f), 32.0f, blue, 0, 2.0f)
 90            dl |> add_triangle_filled(float2(origin.x + 260.0f, origin.y + 124.0f),
 91                                      float2(origin.x + 226.0f, origin.y + 196.0f),
 92                                      float2(origin.x + 294.0f, origin.y + 196.0f), green)
 93            dl |> add_triangle(float2(origin.x + 380.0f, origin.y + 124.0f),
 94                               float2(origin.x + 346.0f, origin.y + 196.0f),
 95                               float2(origin.x + 414.0f, origin.y + 196.0f), green, 2.0f)
 96            dl |> add_text(float2(origin.x +  16.0f, origin.y + 220.0f), gray,
 97                           "add_text - rendered onto the window drawlist")
 98
 99            // ----- v2 additions: shape extras -----
100            // Multi-color gradient rect.
101            dl |> add_rect_filled_multi_color(
102                float2(origin.x +  16.0f, origin.y + 260.0f),
103                float2(origin.x + 240.0f, origin.y + 290.0f),
104                rgba(255u, 0u, 0u, 220u), rgba(0u, 255u, 0u, 220u),
105                rgba(0u, 0u, 255u, 220u), rgba(255u, 255u, 0u, 220u))
106            // Stroked + filled ellipse.
107            dl |> add_ellipse(float2(origin.x + 300.0f, origin.y + 275.0f),
108                              float2(36.0f, 16.0f), yellow, 0.0f, 0, 1.5f)
109            dl |> add_ellipse_filled(float2(origin.x + 400.0f, origin.y + 275.0f),
110                                     float2(36.0f, 16.0f), blue, 0.0f, 0)
111            // Stroked + filled N-gon (hexagon).
112            dl |> add_ngon(float2(origin.x + 500.0f, origin.y + 275.0f),
113                           22.0f, green, 6, 2.0f)
114            dl |> add_ngon_filled(float2(origin.x + 580.0f, origin.y + 275.0f),
115                                  22.0f, green, 6)
116            // Cubic + quadratic bezier curves.
117            dl |> add_bezier_cubic(
118                float2(origin.x +  16.0f, origin.y + 330.0f),
119                float2(origin.x + 110.0f, origin.y + 290.0f),
120                float2(origin.x + 200.0f, origin.y + 360.0f),
121                float2(origin.x + 300.0f, origin.y + 310.0f),
122                yellow, 2.0f, 0)
123            dl |> add_bezier_quadratic(
124                float2(origin.x + 320.0f, origin.y + 330.0f),
125                float2(origin.x + 430.0f, origin.y + 290.0f),
126                float2(origin.x + 540.0f, origin.y + 330.0f),
127                blue, 2.0f, 0)
128            // Polyline (open M-shape) below.
129            var poly <- [
130                float2(origin.x +  16.0f, origin.y + 380.0f),
131                float2(origin.x +  60.0f, origin.y + 350.0f),
132                float2(origin.x + 100.0f, origin.y + 370.0f),
133                float2(origin.x + 140.0f, origin.y + 350.0f),
134                float2(origin.x + 180.0f, origin.y + 380.0f)
135            ]
136            dl |> add_polyline(poly, gray, ImDrawFlags.None, 1.5f)
137            delete poly
138        }
139
140        dummy(DRAW_BAND_SLOT, (size = float2(660.0f, 400.0f)))
141
142        text(DRAW_FG_LABEL, (text = "Foreground swatch (overlays everything):"))
143
144        let vp_pos = GetMainViewport().Pos
145        let vp_size = GetMainViewport().Size
146        with_foreground_drawlist() $(var fdl) {
147            fdl |> add_rect_filled(float2(vp_pos.x + 12.0f, vp_pos.y + 12.0f),
148                                   float2(vp_pos.x + 100.0f, vp_pos.y + 26.0f), red)
149            fdl |> add_text(float2(vp_pos.x + 20.0f, vp_pos.y + 12.0f),
150                            rgba(255u, 255u, 255u, 255u), "FG")
151        }
152
153        with_background_drawlist() $(var bdl) {
154            bdl |> add_rect_filled(float2(vp_pos.x, vp_pos.y),
155                                   float2(vp_pos.x + vp_size.x, vp_pos.y + vp_size.y),
156                                   bg_tint)
157        }
158    }
159
160    end_of_frame()
161    Render()
162    var w, h : int
163    live_get_framebuffer_size(w, h)
164    glViewport(0, 0, w, h)
165    glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
166    glClear(GL_COLOR_BUFFER_BIT)
167    live_imgui_render()
168
169    live_end_frame()
170}
171
172[export]
173def shutdown() {
174    live_imgui_shutdown()
175    live_destroy_window()
176}
177
178[export]
179def main() {
180    init()
181    while (!exit_requested()) {
182        update()
183    }
184    shutdown()
185}

8.18.1.38.1.1. 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.

8.18.1.38.1.2. 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.

8.18.1.38.1.3. 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; rot rotates the ellipse about center (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_segments required (no adaptive default).

  • add_polyline(dl, points : array<float2>, col, flags = ImDrawFlags.None, thickness = 1.0f) — open or closed polyline through points (use ImDrawFlags.Closed to 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.

8.18.1.38.1.4. 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). Pass ImDrawFlags.Closed to join the last vertex back to the first.

See modules/dasImgui/examples/features/drawlist_path.das for a stroked rounded rect, filled convex pentagon, and filled concave arrow built path-style.

8.18.1.38.1.5. 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) — push count channels.

  • 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 modules/dasImgui/examples/features/drawlist_channels.das.

8.18.1.38.1.6. 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 modules/dasImgui/examples/features/clip_rect.das.

8.18.1.38.1.7. 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 WidgetEntrykind ("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 modules/dasImgui/tests/test_drawlist_path_key.das for the walker pattern.

8.18.1.38.1.8. 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: modules/dasImgui/examples/tutorial/drawlist.das

Integration tests: modules/dasImgui/tests/test_drawlist_primitives.das, modules/dasImgui/tests/test_with_window_drawlist.das, modules/dasImgui/tests/test_drawlist_path_key.das.

Boost macros — the macro layer.