Wrapping tab bar

ImGui’s native tab_bar keeps its headers on one row: when they overflow the width it shrinks and ellipsizes the labels, or scrolls them behind arrows. It never wraps. wrap_tab_bar / wrap_tab_item are the container that does — the header strip flows onto as many rows as the width needs, and only the active tab’s body is drawn.

wrap_tab_bar(WTB_BAR) {
    wrap_tab_item("physics") { text("...") }
    wrap_tab_item("camera")  { text("...") }
    // ... as many as you like; they wrap across rows
}

Three things make it tick:

  • The active tab is an INDEX (like combo), carried in the container’s value payload. imgui_force_set {"value":N} selects tab N programmatically; a real click on a header selects it interactively (see below).

  • Only the active tab’s body renders. Each wrap_tab_item invokes its block only while its index is active — an inactive tab’s leaves aren’t submitted to ImGui, so a snapshot shows them rendered:false.

  • The strip is laid out a frame behind. The body renders after the whole header strip, so the strip uses the labels collected on the previous frame. A changed label set appears one frame late (never visible for a stable list). Keep tab declaration order stable.

Source: examples/tutorial/wrap_tab_bar.das.

Walkthrough

 1options gen2
 2
 3require imgui/imgui_harness
 4
 5// =============================================================================
 6// TUTORIAL: wrap_tab_bar / wrap_tab_item — a multi-row, WRAPPING tab bar.
 7//
 8//   wrap_tab_bar(WTB_BAR) {
 9//       wrap_tab_item("physics") { <body> }
10//       wrap_tab_item("camera")  { <body> }
11//       ...
12//   }
13//
14// ImGui's native `tab_bar` shrinks/ellipsizes or scrolls its headers when they
15// overflow the width; `wrap_tab_bar` instead wraps them onto as many rows as
16// needed and shows only the active tab's body. The active tab is an INDEX
17// (like `combo`) — `imgui_force_set {"value":N}` selects tab N. Because the
18// body renders after the whole header strip, the strip is laid out from the
19// labels seen on the PREVIOUS frame: a changed label set appears one frame late
20// (never visible for a stable list). Keep tab declaration order stable.
21//
22// The narrow window below forces the headers to wrap across rows.
23//
24// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/wrap_tab_bar.das
25// LIVE:       daslang-live modules/dasImgui/examples/tutorial/wrap_tab_bar.das
26//
27// DRIVE (when running live) — select the "lava" tab (index 2):
28//   curl -X POST -d '{"name":"imgui_force_set","args":{"target":"WTB_WIN/WTB_BAR","value":2}}' \
29//        localhost:9090/command
30// =============================================================================
31
32[export]
33def init() {
34    harness_init("wrap_tab_bar tutorial", 380, 520)
35}
36
37[export]
38def update() {
39    if (!harness_begin_frame()) {
40        return
41    }
42    harness_new_frame()
43
44    // deliberately narrow so the eight headers wrap onto multiple rows
45    SetNextWindowSize(ImVec2(340.0f, 480.0f), ImGuiCond.FirstUseEver)
46    window(WTB_WIN, (text = "wrap_tab_bar tutorial", closable = false, flags = ImGuiWindowFlags.None)) {
47        text(WTB_LEAD, (text = "Eight categories in a narrow panel - the native"))
48        text(WTB_LEAD2, (text = "TabBar would ellipsize/scroll; wrap_tab_bar flows them."))
49        separator()
50
51        wrap_tab_bar(WTB_BAR) {
52            wrap_tab_item("physics") {
53                text(WTB_B0, (text = "physics - gravity, restitution, drag"))
54            }
55            wrap_tab_item("camera") {
56                text(WTB_B1, (text = "camera - zoom, follow, lookahead"))
57            }
58            wrap_tab_item("lava") {
59                text(WTB_B2, (text = "lava - speed, scale, warp, crust"))
60            }
61            wrap_tab_item("cinders") {
62                text(WTB_B3, (text = "cinders - brightness, density, rise"))
63            }
64            wrap_tab_item("audio") {
65                text(WTB_B4, (text = "audio - master gain, music, sfx"))
66            }
67            wrap_tab_item("lighting") {
68                text(WTB_B5, (text = "lighting - ambient, noise, scale"))
69            }
70            wrap_tab_item("grid") {
71                text(WTB_B6, (text = "grid - size, angle, snap step"))
72            }
73            wrap_tab_item("misc") {
74                text(WTB_B7, (text = "misc - everything else"))
75            }
76        }
77    }
78
79    harness_end_frame()
80}
81
82[export]
83def shutdown() {
84    harness_shutdown()
85}
86
87[export]
88def main() {
89    init()
90    while (!exit_requested()) {
91        update()
92    }
93    shutdown()
94}

Requires

  • imgui/imgui_harness — the tutorial uses the harness_* lifecycle. wrap_tab_bar / wrap_tab_item live in imgui/imgui_containers_builtin (pulled in by the harness).

Selecting a tab by name

Each header is a real button, and the strip registers it as a clickable alias under the bar’s path — WTB_WIN/WTB_BAR/<label> — so a driver, test, or recording can select a tab with a real synthetic click, by name, exactly like a user clicking the header:

curl -X POST -d '{"name":"imgui_click","args":{"target":"WTB_WIN/WTB_BAR/lava"}}' \
     localhost:9090/command

The click drives ImGui’s own input path into the header button, which sets the active index — no different from a mouse click. This is the do-what-it-teaches form the tutorial recording uses (clicking lava, then grid on the second row, then back to physics), and each switch is verified by the selected tab’s body appearing. The index is also settable directly with imgui_force_set {"value":N} when you want to drive it without a gesture.

Why an index, not ImGui’s tab state

Native tab_bar lets ImGui own selection internally; the boost wrapper only observes it. wrap_tab_bar is drawn from primitives (a wrapped strip of buttons), so it owns its own active index as plain state — which is why it can expose value for snapshot + imgui_force_set, and why each header can be a named click target. The trade is that it is not ImGui’s native tab visual; it is the right tool when you have more tab categories than fit one row.

Standalone vs live

Same convention as the other tutorials.

See also

Full source: examples/tutorial/wrap_tab_bar.das

Sibling: Tab bar — ImGui’s native single-row tab bar, with closable tabs and edit_tab_item bidirectional binding.

Boost macros — the macro layer.