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 (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: examples/tutorial/containers.das.

Walkthrough

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

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.

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.

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 examples/features/containers_overlay.das).

Standalone vs live

Same convention as previous tutorials.

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.

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: examples/features/popup_context_item.das.

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: examples/tutorial/containers.das

Richer references:

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

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

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

Previous tutorial: State & telemetry

Boost macros — the macro layer.