Layout

Three boost helpers — dock_left, split_h, split_v — compose into an IDE-style layout on a single panel. Each helper takes a state struct, an init value, optional bounds, and a block per pane; the resulting splits are draggable at runtime and survive live reload.

Source: examples/tutorial/layout.das.

Walkthrough

The recording drives the layout with real synthetic drags on the splitter handles — no imgui_force_set. It grabs the sidebar’s right edge and pulls it wider (asserting SIDEBAR’s pixel width rose), drags the vertical handle between the Files and Editor panes (left-pane fraction rose), then drags the horizontal handle down so the editor grows and the output shrinks (top fraction rose). Each handle surfaces its own bbox as a <container>/HANDLE alias in the snapshot, so the cursor targets the real 8 px InvisibleButton; a drag that moved nothing aborts the recording at teardown.

  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_layout_builtin
 17require imgui/imgui_visual_aids
 18
 19// =============================================================================
 20// TUTORIAL: layout — split_h / split_v / dock_left on a single panel.
 21//
 22// Three boost layout helpers compose into a classic IDE-style layout:
 23//   SIDEBAR     : dock_left   — fixed-width left rail with a draggable edge
 24//   SPLIT_VERT  : split_v     — splits the main area into top + bottom
 25//   SPLIT_MAIN  : split_h     — splits the top area into left + right
 26//
 27// Each helper takes a state struct (registered automatically), an `init`
 28// value, optional `bounds`, and a block (`${ ... }`) per pane. The state's
 29// `value` field holds either the split fraction (split_*) or the pane width
 30// in pixels (dock_*). All three are draggable at runtime.
 31//
 32// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/layout.das
 33// LIVE:       daslang-live modules/dasImgui/examples/tutorial/layout.das
 34//
 35// DRIVE (when running live):
 36//   curl -X POST -d '{"name":"imgui_force_set","args":{"target":"LAYOUT_WIN/SPLIT_VERT/SPLIT_MAIN","value":0.7}}' localhost:9090/command
 37//   curl -X POST -d '{"name":"imgui_force_set","args":{"target":"LAYOUT_WIN/SPLIT_VERT","value":0.4}}'           localhost:9090/command
 38//   curl -X POST -d '{"name":"imgui_force_set","args":{"target":"LAYOUT_WIN/SIDEBAR","value":220.0}}'            localhost:9090/command
 39//
 40// Note: the boost ``window(LAYOUT_WIN, ...)`` wrapper pushes the window's
 41// name as a path prefix on every nested widget, so live targets are
 42// path-qualified (no bare ``SIDEBAR``).
 43// =============================================================================
 44
 45[export]
 46def init() {
 47    live_create_window("dasImgui layout tutorial", 1024, 720)
 48    live_imgui_init(live_window)
 49    let io & = unsafe(GetIO())
 50    GetStyle().FontScaleMain = 1.5
 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    // One window holds the whole layout. dock_left carves out the sidebar;
 63    // split_v then split_h compose the main pane.
 64    SetNextWindowPos(ImVec2(60.0f, 60.0f), ImGuiCond.Always)
 65    SetNextWindowSize(ImVec2(900.0f, 600.0f), ImGuiCond.Always)
 66    window(LAYOUT_WIN, (text = "IDE layout",
 67                        closable = false,
 68                        flags = ImGuiWindowFlags.None)) {
 69
 70        // Left rail: 200 px by default, draggable between 80 and 320 px.
 71        dock_left(SIDEBAR, (init = 200.0f, bounds = (80.0f, 320.0f))) {
 72            text("Sidebar")
 73            spacing(LO_SP_1)
 74            button(EXPLORE_BTN, (text = "Explore"))
 75            button(SEARCH_BTN,  (text = "Search"))
 76            button(SOURCE_BTN,  (text = "Source"))
 77        }
 78
 79        // Main pane: top/bottom (split_v) where top is left/right (split_h).
 80        split_v(SPLIT_VERT, (init = 0.65f, bounds = (0.1f, 0.9f)),
 81                ${
 82                    split_h(SPLIT_MAIN, (init = 0.4f, bounds = (0.1f, 0.9f)),
 83                            ${
 84                                text("Files")
 85                                button(FILE_A_BTN, (text = "main.das"))
 86                                button(FILE_B_BTN, (text = "lib.das"))
 87                            },
 88                            ${
 89                                text("Editor")
 90                                text("// drag the splitters -")
 91                                text("// the pane bounds")
 92                                text("// stay clipped.")
 93                            })
 94                },
 95                ${
 96                    text("Output")
 97                    text("> ready.")
 98                    button(CLEAR_BTN, (text = "Clear output"))
 99                })
100    }
101
102    end_of_frame()
103    Render()
104    var w, h : int
105    live_get_framebuffer_size(w, h)
106    glViewport(0, 0, w, h)
107    glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
108    glClear(GL_COLOR_BUFFER_BIT)
109    live_imgui_render()
110
111    live_end_frame()
112}
113
114[export]
115def shutdown() {
116    live_imgui_shutdown()
117    live_destroy_window()
118}
119
120[export]
121def main() {
122    init()
123    while (!exit_requested()) {
124        update()
125    }
126    shutdown()
127}

Requires

Same backend + boost layer as Widgets tour, with two extra modules pulled in:

  • imgui/imgui_containers_builtin — the window macro that wraps the layout (boost window is more featureful than raw Begin/End; it provides closable, flags, registered state, and an implicit End at block exit).

  • imgui/imgui_layout_builtin — the layout helpers themselves (split_h, split_v, dock_left).

Init and shutdown

Identical to Widgets tour. 1024x720 window, font scale 1.5, standard live-reload pair.

The frame loop

Standard dasImgui v2 shape. apply_synth_io_override() between ImGui_ImplGlfw_NewFrame and NewFrame is required again so the driver script can synth drags against the splitters without the real GLFW mouse winning the IO race.

Layout helpers

The panel composes three helpers, nested:

window(LAYOUT_WIN, (text = "IDE layout", ...)) {
    dock_left(SIDEBAR, (init = 200.0f, bounds = (80.0f, 320.0f))) {
        // sidebar contents
    }
    split_v(SPLIT_VERT, (init = 0.65f, bounds = (0.1f, 0.9f)),
        ${ split_h(SPLIT_MAIN, ...) { ... } },
        ${ /* bottom pane */ })
}

dock_left carves a fixed-width column off the left edge. Its state.value is the pane width in pixels — clamped to the bounds tuple at drag time. After dock_left’s block, ImGui’s cursor is on the SameLine to the right of the rail, so subsequent content flows into the remaining area without explicit positioning.

split_v and split_h each take two block arguments rather than one. The ${ ... } literal is a block-with-no-args, and the boost macro takes the helpers’ panes by position. Their state.value is the first pane’s fraction of the available space, clamped to bounds (so init = 0.65f means the top half occupies 65% of the height).

Standalone vs live

Same as Widgets tourmain() runs the loop standalone; daslang-live invokes init / update / shutdown directly.

Driving from outside

Every helper’s state struct is targetable by name. Drag without a mouse:

curl -X POST -d '{"name":"imgui_force_set","args":{"target":"LAYOUT_WIN/SIDEBAR","value":260.0}}' \
     localhost:9090/command
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"LAYOUT_WIN/SPLIT_VERT/SPLIT_MAIN","value":0.6}}' \
     localhost:9090/command

value is the same type the user would set by dragging — pixels for dock_*, fraction for split_*. Targets are path-qualified by the enclosing window/split chain (no bare SIDEBAR).

Each helper also exposes its drag handle’s geometry as a <container>/HANDLE alias in the snapshot, so a driver can perform a real drag instead of setting the fraction directly — LAYOUT_WIN/SIDEBAR/HANDLE and likewise for each split. The drag playwright helper targets it by bbox centre; this is how the recording above drives every split.

Scope wrappers

Three stateless block-arg wrappers in imgui/imgui_scope_builtin cover the leftover Push/Pop idioms ImGui uses for ad-hoc layout overrides. Each brackets the block with the corresponding ImGui Push/Pop pair and takes no state — they read like inline scopes:

require imgui/imgui_scope_builtin

// Indent / Unindent — nest content under a heading.
with_indent(0.0f) {       // 0.0f defers to style IndentSpacing
    Text("Bullet child")
}
with_indent(40.0f) {      // explicit pixel offset
    Text("Hard-indented")
}

// PushItemWidth / PopItemWidth — scope a widget-width override.
with_item_width(120.0f) {
    slider_float(NARROW, (text = "narrow", bounds = (0.0f, 1.0f)))
}
with_item_width(-60.0f) { // negative = right-edge minus N
    slider_float(STRETCH, (text = "stretch", bounds = (0.0f, 1.0f)))
}

// PushTextWrapPos / PopTextWrapPos — scope where long text wraps.
with_text_wrap_pos(0.0f) { TextUnformatted(LIPSUM) }   // window right edge
with_text_wrap_pos(200.0f) { TextUnformatted(LIPSUM) } // wrap at 200 px

Feature demos: examples/features/with_indent.das, examples/features/with_item_width.das, examples/features/with_text_wrap_pos.das.

Next steps

Docking is next — full ImGui dockspaces and the dock helpers that ride on top of them.

See also

Full source: examples/tutorial/layout.das

Richer reference: examples/features/layout_helpers.das — same three helpers exercised with every option.

Previous tutorial: Widgets tour

Boost macros — the macro layer.

Builtin widgets — widget reference.