8.18.1.14. Containers

The boost layer ships a family of containers — block-arg wrappers around ImGui’s Begin*/End* pairs that share three properties:

  • Block-arg shape — the body runs once per frame inside the begin/end pair. No manual End() to forget.

  • Path push — leaf widgets inside the block register under <container>/<leaf>. Every container contributes a path segment, just like window and with_id did in earlier tutorials.

  • Open-state via pending flagsstate.pending_open = true queues the container to open next frame; pending_close = true closes it. Live commands imgui_open / imgui_close mutate the same flag, so app code, external drivers, and the chrome’s close-button share one channel.

This tutorial covers four representative containers: menu_bar + menu + menu_item, tab_bar + tab_item, popup, and item_tooltip. The features-side demos (modules/dasImgui/examples/features/containers_*.das) cover the rest: child / group (window family), tree_node / collapsing_header (layout family), popup_modal / tooltip / combo_select / list_box (overlay family).

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

8.18.1.14.1. Walkthrough

The recording drives all four containers with real synthetic input and self-verifies each step. It flips the General tab’s WIRE checkbox, then clicks the Audio tab header and asserts the switch by its effect: AUDIO_TAB’s MUTE starts rendering while GENERAL_TAB’s WIRE stops — a direct proof that only the open tab’s body runs. It opens the popup from the button’s pending_open flag (asserting the popup body appears), toggles the VSync checkbox inside it, closes it with the popup’s own Close button (asserting the body stops), and finally hovers the button to bring up the item_tooltip. Any step that failed to land aborts 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_visual_aids
 18
 19// =============================================================================
 20// TUTORIAL: containers — tab_bar, menu_bar, popup, item_tooltip.
 21//
 22// Containers wrap an ImGui begin/end pair around a daslang block. They
 23// share three properties:
 24//
 25//   1. Block-arg shape: the body runs once per frame inside the begin/end.
 26//   2. Path push: leaf widgets register under "<container>/<leaf>" — every
 27//      container contributes a segment to the registry path, just like
 28//      `window` and `with_id` did in earlier tutorials.
 29//   3. Open-state via pending flags: `(state).pending_open = true` queues
 30//      the container to open next frame; `pending_close = true` reverses
 31//      it. The same flag is what `imgui_open` / `imgui_close` mutate from
 32//      outside, so all three control surfaces (app code, live commands,
 33//      and the close-button on the chrome) share one channel.
 34//
 35// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/containers.das
 36// LIVE:       daslang-live modules/dasImgui/examples/tutorial/containers.das
 37//
 38// DRIVE (when running live):
 39//   curl -X POST -d '{"name":"imgui_snapshot"}'                                                            localhost:9090/command
 40//   curl -X POST -d '{"name":"imgui_open","args":{"target":"CONT_WIN/OPTIONS_POPUP"}}'                     localhost:9090/command
 41//   curl -X POST -d '{"name":"imgui_close","args":{"target":"CONT_WIN/OPTIONS_POPUP"}}'                    localhost:9090/command
 42// =============================================================================
 43
 44[export]
 45def init() {
 46    live_create_window("dasImgui containers tutorial", 760, 560)
 47    live_imgui_init(live_window)
 48    let io & = unsafe(GetIO())
 49    GetStyle().FontScaleMain = 1.5
 50}
 51
 52[export]
 53def update() { // nolint:STYLE038
 54    if (!live_begin_frame()) return
 55    begin_frame()
 56
 57    ImGui_ImplGlfw_NewFrame()
 58    apply_synth_io_override()
 59    NewFrame()
 60
 61    SetNextWindowPos(ImVec2(30.0f, 30.0f), ImGuiCond.FirstUseEver)
 62    SetNextWindowSize(ImVec2(680.0f, 480.0f), ImGuiCond.FirstUseEver)
 63    window(CONT_WIN, (text = "containers", closable = false,
 64                      flags = ImGuiWindowFlags.MenuBar)) {
 65
 66        // ---- menu_bar (window must have ImGuiWindowFlags.MenuBar) ----
 67        // Each menu_item registers under "CONT_WIN/MAIN_BAR/FILE_MENU/NEW_ITEM".
 68        menu_bar(MAIN_BAR) {
 69            menu(FILE_MENU, (text = "File", enabled = true)) {
 70                menu_item(NEW_ITEM,  (text = "New",  shortcut = "Ctrl+N"))
 71                menu_item(OPEN_ITEM, (text = "Open", shortcut = "Ctrl+O"))
 72            }
 73        }
 74
 75        // ---- tab_bar — three tabs, each its own block ----
 76
 77        // Only the active tab's block runs each frame; inactive tabs never enter
 78        // their body, so their widgets aren't in the registry that frame.
 79        tab_bar(MAIN_TABS, (text = "MainTabs", flags = ImGuiTabBarFlags.None)) {
 80            tab_item(GENERAL_TAB, (text = "General", closable = false,
 81                                   flags = ImGuiTabItemFlags.None)) {
 82                text("Tabs share a window; only the active tab renders.")
 83                checkbox(WIRE, (text = "Wireframe"))
 84            }
 85            tab_item(AUDIO_TAB, (text = "Audio", closable = false,
 86                                 flags = ImGuiTabItemFlags.None)) {
 87                checkbox(MUTE, (text = "Mute"))
 88                slider_float(VOL, (text = "Volume"))
 89            }
 90            tab_item(INFO_TAB, (text = "Info", closable = false,
 91                                flags = ImGuiTabItemFlags.None)) {
 92                text("containers tutorial")
 93                text("each tab is a block - exclusive render")
 94            }
 95        }
 96
 97        separator(CT_SEP_1)
 98
 99        // ---- popup — opened by a pending flag ----
100
101        // The button only flips the flag; the popup renders next frame when ImGui
102        // sees `pending_open = true` and runs OpenPopup(). The same channel handles
103        // imgui_open from outside (curl example in the DRIVE block).
104        text("Popup - open via pending_open or imgui_open:")
105        if (button(OPEN_POPUP_BTN, (text = "Open options"))) {
106            OPTIONS_POPUP.pending_open = true
107        }
108        popup(OPTIONS_POPUP, (text = "OptionsPopup",
109                              flags = ImGuiWindowFlags.None)) {
110            text("Options")
111            separator(CT_SEP_2)
112            checkbox(OPT_VSYNC, (text = "VSync"))
113            checkbox(OPT_HIDPI, (text = "HiDPI"))
114            if (button(POPUP_CLOSE_BTN, (text = "Close"))) {
115                OPTIONS_POPUP.pending_close = true
116            }
117        }
118
119        separator(CT_SEP_3)
120
121        // ---- item_tooltip — hover-gated overlay ----
122        // BeginItemTooltip auto-checks IsItemHovered() under the hood, so
123        // the block only runs while the previous widget is hovered.
124        text("Tooltip - hover the button below:")
125        button(HOVER_BTN, (text = "Hover me"))
126        item_tooltip(HOVER_TIP) {
127            text("This text appears on hover.")
128            text("Driven by BeginItemTooltip (auto-gated).")
129        }
130    }
131
132    end_of_frame()
133    Render()
134    var w, h : int
135    live_get_framebuffer_size(w, h)
136    glViewport(0, 0, w, h)
137    glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
138    glClear(GL_COLOR_BUFFER_BIT)
139    live_imgui_render()
140
141    live_end_frame()
142}
143
144[export]
145def shutdown() {
146    live_imgui_shutdown()
147    live_destroy_window()
148}
149
150[export]
151def main() {
152    init()
153    while (!exit_requested()) {
154        update()
155    }
156    shutdown()
157}

8.18.1.14.1.1. Requires

One extra module on top of the baseline boost layer:

  • imgui/imgui_containers_builtin — defines every container macro used here. The window/child/group/menu/tab/popup/tooltip surface all lives in this one module.

8.18.1.14.1.3. tab_bar / tab_item

tab_bar holds one or more tab_item blocks; ImGui owns the active-tab selection:

tab_bar(MAIN_TABS, (text = "MainTabs", flags = ImGuiTabBarFlags.None)) {
    tab_item(GENERAL_TAB, (text = "General", closable = false,
                           flags = ImGuiTabItemFlags.None)) {
        Text("Tabs share a window; only the active tab renders.")
        checkbox(WIRE, (text = "Wireframe"))
    }
    tab_item(AUDIO_TAB, (text = "Audio", ...)) { ... }
    tab_item(INFO_TAB,  (text = "Info",  ...)) { ... }
}

Only the active tab’s block runs each frame — widgets inside inactive tabs aren’t in the registry that frame, so a snapshot taken while GENERAL_TAB is active won’t list any AUDIO_TAB children. TabItemState.pending_open controls the closable-tab visibility (skip BeginTabItem entirely when open=false), but it does NOT programmatically select the active tab — that’s an ImGui internal state, set by clicking the tab header. Each tab_item registers its header’s bbox, so a driver switches tabs the way a user does: an imgui_click on the tab_item target (e.g. CONT_WIN/MAIN_TABS/AUDIO_TAB) lands on the header and selects it.

8.18.1.14.1.5. item_tooltip — hover-gated overlay

ImGui’s BeginItemTooltip checks IsItemHovered() internally, so the block only runs while the immediately-preceding widget is hovered. No manual gate:

button(HOVER_BTN, (text = "Hover me"))
item_tooltip(HOVER_TIP) {
    Text("This text appears on hover.")
    Text("Driven by BeginItemTooltip (auto-gated).")
}

For tooltips whose own gating logic differs from “previous item hovered” — say, tooltips on an entire window or a custom hover state — use the lower-level tooltip(...) container and gate it manually (see modules/dasImgui/examples/features/containers_overlay.das).

8.18.1.14.1.6. Standalone vs live

Same convention as previous tutorials.

8.18.1.14.1.7. Driving from outside

Path-qualified targets for every container leaf:

curl -X POST -d '{"name":"imgui_snapshot"}' localhost:9090/command
# Registers: CONT_WIN/MAIN_BAR/FILE_MENU/NEW_ITEM, CONT_WIN/MAIN_TABS/GENERAL_TAB/WIRE, ...

curl -X POST -d '{"name":"imgui_open","args":{"target":"CONT_WIN/OPTIONS_POPUP"}}' \
     localhost:9090/command
curl -X POST -d '{"name":"imgui_close","args":{"target":"CONT_WIN/OPTIONS_POPUP"}}' \
     localhost:9090/command

curl -X POST -d '{"name":"imgui_click","args":{"target":"CONT_WIN/MAIN_BAR/FILE_MENU/NEW_ITEM"}}' \
     localhost:9090/command

Note that menu items receive imgui_click directly — they’re click targets, not open/close targets.

8.18.1.14.1.8. Context popups

popup_context_item is the right-click-context sibling of popup — ImGui drives open/close internally based on the previous item receiving a right-click; the wrapper just gates the body on Begin returning true:

require imgui/imgui_containers_builtin

button(TARGET_BTN, (text = "Right-click me"))
popup_context_item(TARGET_CTX, (str_id = "target_ctx",
                                flags = ImGuiPopupFlags.MouseButtonRight)) {
    if (menu_item(ACTION_RENAME, (text = "Rename", shortcut = "F2"))) {
        // ...
    }
    if (menu_item(ACTION_DELETE, (text = "Delete", shortcut = "Del"))) {
        // ...
    }
}

The popup is keyed off the previously submitted item — submission order matters, and popup_context_item registers under its own path in the snapshot. Use imgui_click to drive menu items from outside.

Feature demo: modules/dasImgui/examples/features/popup_context_item.das.

8.18.1.14.1.9. Next steps

So far every tutorial has assumed the standard standalone/live run. Next up is live-reload itself — the daslang-live workflow, how [live_command] / [before_reload] / [after_reload] plumb in, and what survives a reload (state structs, ImGui context, the HTTP server) versus what gets rebuilt.

See also

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

Richer references:

  • modules/dasImgui/examples/features/containers_window.das — window / child / group with closable second window

  • modules/dasImgui/examples/features/containers_layout.das — tab_bar plus tree_node, collapsing_header

  • modules/dasImgui/examples/features/containers_overlay.das — popup_modal, tooltip, combo_select, list_box

Previous tutorial: State & telemetry

Boost macros — the macro layer.