8.19.8. Layout primitives

ImGui’s layout cursor advances after every widget — each button, text, or other rail bumps the cursor to the next line by default. Four boost rails move the cursor without rendering anything visible:

  • same_line(IDENT) — keep the next widget on the current row (wraps ImGui::SameLine).

  • spacing(IDENT) — insert one line of vertical gap.

  • new_line(IDENT) — advance one full text-line of vertical space.

  • dummy((size = float2(x, y))) — reserve arbitrary cursor space without rendering.

All four accept the [widget]-style optional IDENT; the tutorial shows IDENTs on same_line / spacing / new_line (useful when playwright tests assert their snapshot entries) and the anonymous dummy form (cursor reservation that no test targets).

All four share EmptyMarkerState — the payload is {}, but the snapshot still records that the marker fired, so playwright tests can assert "spacing#3 was rendered between button A and button B".

if (button(BTN_A, (text = "A"))) { /* ... */ }
same_line(SL_AB)
if (button(BTN_B, (text = "B"))) { /* ... */ }
same_line(SL_BC)
if (button(BTN_C, (text = "C"))) { /* ... */ }

spacing(SP_TOP)
new_line(NL_INSERT)
dummy((size = float2(0.0f, 40.0f)))

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

8.19.8.1. Walkthrough

The markers render nothing, so the recording’s self-check is that each named marker fired: it narrates the same_line row while asserting the three buttons and both same_line markers are on screen, then the spacing and new_line markers in turn (record_check_rendered on each). A marker that silently dropped out of the layout would abort the recording.

  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: layout_primitives — boost v2 cursor-positioning widgets.
 22//
 23// Layout markers move the imgui cursor without rendering anything. They share
 24// EmptyMarkerState — payload is `{}`, but the snapshot still records the
 25// widget existed, so playwright can assert "spacing#3 was rendered".
 26//
 27// Covers: same_line / new_line / spacing / dummy
 28//
 29// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/layout_primitives.das
 30// LIVE:       daslang-live modules/dasImgui/examples/tutorial/layout_primitives.das
 31// =============================================================================
 32
 33[export]
 34def init() {
 35    live_create_window("dasImgui layout primitives", 1000, 640)
 36    live_imgui_init(live_window)
 37    let io & = unsafe(GetIO())
 38    GetStyle().FontScaleMain = 1.4
 39}
 40
 41[export]
 42def update() {
 43    if (!live_begin_frame()) return
 44    begin_frame()
 45
 46    ImGui_ImplGlfw_NewFrame()
 47    apply_synth_io_override()
 48    NewFrame()
 49
 50    SetNextWindowPos(ImVec2(60.0, 60.0), ImGuiCond.Always)
 51    SetNextWindowSize(ImVec2(600.0, 360.0), ImGuiCond.Always)
 52    window(LAYOUT_WIN, (text = "Layout primitives", closable = false,
 53                        flags = ImGuiWindowFlags.None)) {
 54        text("Three buttons on one row - same_line() keeps them inline.")
 55        if (button(BTN_A, (text = "A"))) {
 56            print("A\n")
 57        }
 58        same_line(SL_AB)
 59        if (button(BTN_B, (text = "B"))) {
 60            print("B\n")
 61        }
 62        same_line(SL_BC)
 63        if (button(BTN_C, (text = "C"))) {
 64            print("C\n")
 65        }
 66
 67        spacing(SP_TOP)
 68        text("spacing() inserts a small vertical gap (1 line).")
 69
 70        spacing(SP_MID)
 71        spacing(SP_MID2)
 72        spacing(SP_MID3)
 73        text("Three spacing() calls stack; cheap if you don't want a Dummy.")
 74
 75        new_line(NL_INSERT)
 76        text("new_line() == one full text-line of vertical space.")
 77
 78        dummy((size = float2(0.0f, 40.0f)))
 79        text("dummy(size) reserves arbitrary cursor space.")
 80    }
 81
 82    end_of_frame()
 83    Render()
 84    var w, h : int
 85    live_get_framebuffer_size(w, h)
 86    glViewport(0, 0, w, h)
 87    glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
 88    glClear(GL_COLOR_BUFFER_BIT)
 89    live_imgui_render()
 90
 91    live_end_frame()
 92}
 93
 94[export]
 95def shutdown() {
 96    live_imgui_shutdown()
 97    live_destroy_window()
 98}
 99
100[export]
101def main() {
102    init()
103    while (!exit_requested()) {
104        update()
105        maybe_collect_gc()
106    }
107    shutdown()
108}

8.19.8.1.1. Requires

Baseline boost layer. All four rails live in imgui/imgui_widgets_builtin alongside the ordinary widgets — no extra modules. (imgui/imgui_layout_builtin is a different rail: the split_h / split_v / dock_left helpers of Layout.)

8.19.8.1.2. When to reach for each

same_line is the workhorse — every multi-column row, every label-then-input pattern uses it. Pass an explicit offset_from_start_x if the next widget needs a column-aligned position; the default (0.0f) packs against the previous item. A second spacing argument overrides the horizontal gap (negative = the style’s ItemSpacing.x).

spacing is a minimal 1-line gap — cheaper to read than dummy when you just want breathing room between sections. Stack three of them if you want a slightly larger gap without committing to a fixed pixel height.

new_line is conceptually \n at the layout level — one full text line. Useful when the current row had a tall widget and you want the next row to start fresh from the left margin without accumulating Y from the tall content.

dummy(size) reserves an arbitrary rectangle. Pass size = float2(0, 40) for a 40-pixel-tall invisible spacer. The X component can pre-allocate a horizontal slot too — useful for grid-like alignment when widgets vary in width.

8.19.8.1.3. Snapshot shape

Each layout marker registers an entry under its ident, with kind set to the rail that fired — "same_line", "spacing", "new_line", "dummy" (EmptyMarkerState is the state struct behind all four, not the reported kind):

curl -X POST -d '{"name":"imgui_snapshot"}' localhost:9090/command \
    | jq '.globals."LAYOUT_WIN/SL_AB"'

Tests that want to verify “this layout was produced” can walk the snapshot and confirm the markers fired in the expected order — see modules/dasImgui/tests/test_layout_primitives.das for the assertion shape.

See also

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

Companion tutorial: Layout — the higher-level layout helpers (with_indent, with_item_width, with_text_wrap_pos).

Integration test: modules/dasImgui/tests/test_layout_primitives.das.

Boost macros — the macro layer.