8.19.19. Tab bar
tab_bar hosts one or more tab_item blocks under a strip of
clickable headers. ImGui owns active-tab selection internally — the
boost wrapper just observes — and routes click events to the right
body. Three things make tabs subtle:
Only the active tab’s body runs each frame. An inactive tab’s leaves aren’t submitted to ImGui, so a snapshot shows them
rendered:falseand their container-path key (e.g.AUDIO_TAB/A_VOLUME) only resolves once that tab has been active — switch the tab before reading or writing leaves under it. The container’s state (TabBarState / TabItemState) lives outside the body and persists.There is no ``pending_select`` channel. ImGui owns selection;
imgui_clickagainst a tab_item path is how external drivers switch tabs.Closable tabs add an X-button. The wrapper feeds
&state.opento BeginTabItem’sp_openparameter; ImGui flips it false on X-click and the wrapper skips the tab next frame.
Source: modules/dasImgui/examples/tutorial/tab_bar.das.
8.19.19.1. Walkthrough
1options gen2
2options _comment_hygiene = true
3options gc
4
5require imgui
6require imgui_app
7require opengl/opengl_boost
8require live/glfw_live
9require live/live_api
10require live/live_commands
11require live/live_vars
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: tab_bar — BeginTabBar / EndTabBar with nested tab_item blocks.
22//
23// tab_bar(IDENT, (text = "...", flags = ImGuiTabBarFlags....)) {
24// tab_item(TAB_A, (text = "A", closable = false, flags = ...)) { ... }
25// tab_item(TAB_B, (text = "B", closable = true, flags = ...)) { ... }
26// }
27//
28// Key behaviors:
29//
30// 1. Only the ACTIVE tab's body block runs each frame — widgets inside
31// inactive tabs are NOT in the registry that frame. A driver doing
32// imgui_force_set against an inactive tab's child path gets a 404.
33//
34// 2. Closable tabs (closable=true) render an X-button. ImGui flips
35// state.open=false; the wrapper then skips BeginTabItem entirely so
36// the tab disappears from the strip until pending_open is set true
37// again.
38//
39// 3. ImGui owns active-tab selection — there is NO state.pending_select.
40// Drive it from outside by imgui_click against the tab_item path
41// (the click hits the tab strip header).
42//
43// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/tab_bar.das
44// LIVE: daslang-live modules/dasImgui/examples/tutorial/tab_bar.das
45//
46// DRIVE (when running live):
47// curl -X POST -d '{"name":"imgui_click","args":{"target":"TB_WIN/MAIN_TABS/AUDIO_TAB"}}' \
48// localhost:9090/command
49// curl -X POST -d '{"name":"imgui_close","args":{"target":"TB_WIN/MAIN_TABS/DRAFT_TAB"}}' \
50// localhost:9090/command
51// =============================================================================
52
53[export]
54def init() {
55 live_create_window("dasImgui tab_bar tutorial", 760, 540)
56 live_imgui_init(live_window)
57 let io & = unsafe(GetIO())
58 GetStyle().FontScaleMain = 1.4
59}
60
61[export]
62def update() {
63 if (!live_begin_frame()) return
64 begin_frame()
65
66 ImGui_ImplGlfw_NewFrame()
67 apply_synth_io_override()
68 NewFrame()
69
70 SetNextWindowPos(ImVec2(20.0f, 20.0f), ImGuiCond.Always)
71 SetNextWindowSize(ImVec2(720.0f, 500.0f), ImGuiCond.Always)
72 window(TB_WIN, (text = "tab_bar tutorial", closable = false,
73 flags = ImGuiWindowFlags.None)) {
74
75 text("Three tabs plus one closable Draft tab. Click a header to switch.")
76 separator()
77
78 // Reorderable + TabListPopupButton (the ▼ menu on the right).
79 tab_bar(MAIN_TABS, (text = "MainTabs",
80 flags = ImGuiTabBarFlags.Reorderable |
81 ImGuiTabBarFlags.TabListPopupButton)) {
82
83 // ---- General tab (non-closable, default selection) ----
84 tab_item(GENERAL_TAB, (text = "General", closable = false,
85 flags = ImGuiTabItemFlags.None)) {
86 text("Only the active tab's body block runs each frame.")
87 text("Switch tabs - checkbox state below survives because the")
88 text("[container] state lives outside the body.")
89 checkbox(G_WIRE, (text = "Wireframe"))
90 checkbox(G_LIVE, (text = "Live preview"))
91 }
92
93 // ---- Audio tab ----
94 tab_item(AUDIO_TAB, (text = "Audio", closable = false,
95 flags = ImGuiTabItemFlags.None)) {
96 slider_int(A_VOLUME, (text = "Volume"))
97 checkbox(A_MUTE, (text = "Mute"))
98 text("VOLUME = {A_VOLUME.value} MUTE = {A_MUTE.value}")
99 }
100
101 // ---- Info tab ----
102 tab_item(INFO_TAB, (text = "Info", closable = false,
103 flags = ImGuiTabItemFlags.None)) {
104 text(INFO_LEAD, (text = "Tab metadata - read-only."))
105 text("MAIN_TABS.flags = Reorderable | TabListPopupButton")
106 text("Inactive tab bodies do NOT register child widgets.")
107 }
108
109 // ---- Draft tab (closable, has X-button) ----
110 tab_item(DRAFT_TAB, (text = "Draft", closable = true,
111 flags = ImGuiTabItemFlags.None)) {
112 text("Draft. Click the x on the tab header to close it.")
113 text("DRAFT_TAB.open = {DRAFT_TAB.open}")
114 slider_int(D_LINE_LEN, (text = "Line length"))
115 }
116 }
117
118 separator()
119 text("Outside the tab_bar - runs every frame regardless of active tab.")
120 text("DRAFT_TAB.open is observable from out here: {DRAFT_TAB.open}")
121 }
122
123 end_of_frame()
124 Render()
125 var w, h : int
126 live_get_framebuffer_size(w, h)
127 glViewport(0, 0, w, h)
128 glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
129 glClear(GL_COLOR_BUFFER_BIT)
130 live_imgui_render()
131
132 live_end_frame()
133}
134
135[export]
136def shutdown() {
137 live_imgui_shutdown()
138 live_destroy_window()
139}
140
141[export]
142def main() {
143 init()
144 while (!exit_requested()) {
145 update()
146 maybe_collect_gc()
147 }
148 shutdown()
149}
8.19.19.1.1. Requires
Already in the baseline boost layer:
imgui/imgui_containers_builtin—tab_bar,tab_item,edit_tab_item.imgui/imgui_widgets_builtin— leaves used inside each tab body.
8.19.19.1.2. Active tab + body gating
ImGui drives active-tab selection internally. Each tab_item’s body
only runs while that tab is active:
tab_bar(MAIN_TABS, (text = "MainTabs",
flags = ImGuiTabBarFlags.Reorderable)) {
tab_item(GENERAL_TAB, (text = "General", closable = false,
flags = ImGuiTabItemFlags.None)) {
checkbox(G_WIRE, (text = "Wireframe")) // only registered while active
}
tab_item(AUDIO_TAB, (text = "Audio", closable = false,
flags = ImGuiTabItemFlags.None)) {
slider_int(A_VOLUME, (text = "Volume"))
}
tab_item(INFO_TAB, (text = "Info", closable = false,
flags = ImGuiTabItemFlags.None)) {
text("Tab metadata - read-only.")
}
}
A snapshot taken while AUDIO_TAB is active shows G_WIRE under
GENERAL_TAB as rendered:false — the leaf wasn’t submitted to
ImGui that frame. Drivers must switch the active tab first before
reading or writing leaves underneath.
8.19.19.1.3. Switching tabs from outside
imgui_click against a tab_item path hits the tab strip header (the
boost wrapper captures the header bbox separately so click resolves to
the strip, not the body):
curl -X POST -d '{"name":"imgui_click","args":{"target":"TB_WIN/MAIN_TABS/AUDIO_TAB"}}' \
localhost:9090/command
This is the only programmatic way to switch tabs — there’s no
imgui_set_active because ImGui’s internal selection isn’t exposed
as a struct field. imgui_click is enough: it produces a real ImGui
mouse event against the header, which is exactly what manual user
interaction does.
8.19.19.1.4. Closable tabs
closable=true makes the X-button appear:
tab_item(DRAFT_TAB, (text = "Draft", closable = true,
flags = ImGuiTabItemFlags.None)) {
text("DRAFT_TAB.open = {DRAFT_TAB.open}")
}
The wrapper threads &state.open into BeginTabItem’s p_open.
When the user clicks X, ImGui flips state.open=false. Next frame
the wrapper sees !state.open and skips BeginTabItem entirely —
the tab disappears from the strip. Bring it back with
DRAFT_TAB.pending_open = true from app code or imgui_open
from a driver.
state.open defaults to true so closable tabs appear from frame 1.
@live open : bool = true in TabItemState carries the value across
live reload — the user’s “I closed this tab” state survives a recompile.
8.19.19.1.6. ImGuiTabBarFlags
Bar-level chrome:
Reorderable— drag tab headers to reorder them.AutoSelectNewTabs— newly-submitted tabs become the active selection.TabListPopupButton— adds a ▼ menu on the right with all tabs as a list (useful when there are many tabs).NoCloseWithMiddleMouseButton— disable the middle-mouse shortcut for closable tabs.NoTooltip— suppress hover tooltips on tab headers.DrawSelectedOverline— draw an overline on the selected tab.FittingPolicyShrink/FittingPolicyScroll— what happens when the strip doesn’t fit horizontally (FittingPolicyShrinkis the default;FittingPolicyMixedcombines the two).
Per-tab flags via ImGuiTabItemFlags: UnsavedDocument (marks the
header with a dot), SetSelected (a one-shot select-this-tab nudge),
Leading/Trailing (pin to bar edges), NoReorder,
NoPushId, NoTooltip, NoCloseWithMiddleMouseButton,
NoAssumedClosure. There is no public “closable but no X” flag —
ImGui’s NoCloseButton is internal bookkeeping and is not bound; drop
closable instead.
8.19.19.1.7. Standalone vs live
Same convention as the other tutorials.
See also
Full source: modules/dasImgui/examples/tutorial/tab_bar.das
Features-side demos: modules/dasImgui/examples/features/containers_layout.das —
tab_bar + tree_node + collapsing_header composition;
modules/dasImgui/examples/features/tab_item_button.das — the tab_item_button
widget for an action-button styled as a tab.
Sibling: Containers — umbrella container tour.
Boost macros — the macro layer.