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’svaluepayload.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_iteminvokes its block only while its index is active — an inactive tab’s leaves aren’t submitted to ImGui, so a snapshot shows themrendered: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 theharness_*lifecycle.wrap_tab_bar/wrap_tab_itemlive inimgui/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.