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: examples/tutorial/tab_bar.das.
Walkthrough
1options gen2
2
3require imgui
4require imgui_app
5require opengl/opengl_boost
6require live/glfw_live
7require live/live_api
8require live/live_commands
9require live/live_vars
10require live_host
11require imgui/imgui_live
12require imgui/imgui_boost_runtime
13require imgui/imgui_boost_v2
14require imgui/imgui_widgets_builtin
15require imgui/imgui_containers_builtin
16require imgui/imgui_visual_aids
17
18// =============================================================================
19// TUTORIAL: tab_bar — BeginTabBar / EndTabBar with nested tab_item blocks.
20//
21// tab_bar(IDENT, (text = "...", flags = ImGuiTabBarFlags....)) {
22// tab_item(TAB_A, (text = "A", closable = false, flags = ...)) { ... }
23// tab_item(TAB_B, (text = "B", closable = true, flags = ...)) { ... }
24// }
25//
26// Key behaviors:
27//
28// 1. Only the ACTIVE tab's body block runs each frame — widgets inside
29// inactive tabs are NOT in the registry that frame. A driver doing
30// imgui_force_set against an inactive tab's child path gets a 404.
31//
32// 2. Closable tabs (closable=true) render an X-button. ImGui flips
33// state.open=false; the wrapper then skips BeginTabItem entirely so
34// the tab disappears from the strip until pending_open is set true
35// again.
36//
37// 3. ImGui owns active-tab selection — there is NO state.pending_select.
38// Drive it from outside by imgui_click against the tab_item path
39// (the click hits the tab strip header).
40//
41// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/tab_bar.das
42// LIVE: daslang-live modules/dasImgui/examples/tutorial/tab_bar.das
43//
44// DRIVE (when running live):
45// curl -X POST -d '{"name":"imgui_click","args":{"target":"TB_WIN/MAIN_TABS/AUDIO_TAB"}}' \
46// localhost:9090/command
47// curl -X POST -d '{"name":"imgui_close","args":{"target":"TB_WIN/MAIN_TABS/DRAFT_TAB"}}' \
48// localhost:9090/command
49// =============================================================================
50
51[export]
52def init() {
53 live_create_window("dasImgui tab_bar tutorial", 760, 540)
54 live_imgui_init(live_window)
55 let io & = unsafe(GetIO())
56 GetStyle().FontScaleMain = 1.4
57}
58
59[export]
60def update() {
61 if (!live_begin_frame()) return
62 begin_frame()
63
64 ImGui_ImplGlfw_NewFrame()
65 apply_synth_io_override()
66 NewFrame()
67
68 SetNextWindowPos(ImVec2(20.0f, 20.0f), ImGuiCond.Always)
69 SetNextWindowSize(ImVec2(720.0f, 500.0f), ImGuiCond.Always)
70 window(TB_WIN, (text = "tab_bar tutorial", closable = false,
71 flags = ImGuiWindowFlags.None)) {
72
73 text("Three tabs plus one closable Draft tab. Click a header to switch.")
74 separator()
75
76 // Reorderable + TabListPopupButton (the ▼ menu on the right).
77 tab_bar(MAIN_TABS, (text = "MainTabs",
78 flags = ImGuiTabBarFlags.Reorderable |
79 ImGuiTabBarFlags.TabListPopupButton)) {
80
81 // ---- General tab (non-closable, default selection) ----
82 tab_item(GENERAL_TAB, (text = "General", closable = false,
83 flags = ImGuiTabItemFlags.None)) {
84 text("Only the active tab's body block runs each frame.")
85 text("Switch tabs - checkbox state below survives because the")
86 text("[container] state lives outside the body.")
87 checkbox(G_WIRE, (text = "Wireframe"))
88 checkbox(G_LIVE, (text = "Live preview"))
89 }
90
91 // ---- Audio tab ----
92 tab_item(AUDIO_TAB, (text = "Audio", closable = false,
93 flags = ImGuiTabItemFlags.None)) {
94 slider_int(A_VOLUME, (text = "Volume"))
95 checkbox(A_MUTE, (text = "Mute"))
96 text("VOLUME = {A_VOLUME.value} MUTE = {A_MUTE.value}")
97 }
98
99 // ---- Info tab ----
100 tab_item(INFO_TAB, (text = "Info", closable = false,
101 flags = ImGuiTabItemFlags.None)) {
102 text(INFO_LEAD, (text = "Tab metadata - read-only."))
103 text("MAIN_TABS.flags = Reorderable | TabListPopupButton")
104 text("Inactive tab bodies do NOT register child widgets.")
105 }
106
107 // ---- Draft tab (closable, has X-button) ----
108 tab_item(DRAFT_TAB, (text = "Draft", closable = true,
109 flags = ImGuiTabItemFlags.None)) {
110 text("Draft. Click the x on the tab header to close it.")
111 text("DRAFT_TAB.open = {DRAFT_TAB.open}")
112 slider_int(D_LINE_LEN, (text = "Line length"))
113 }
114 }
115
116 separator()
117 text("Outside the tab_bar - runs every frame regardless of active tab.")
118 text("DRAFT_TAB.open is observable from out here: {DRAFT_TAB.open}")
119 }
120
121 end_of_frame()
122 Render()
123 var w, h : int
124 live_get_framebuffer_size(w, h)
125 glViewport(0, 0, w, h)
126 glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
127 glClear(GL_COLOR_BUFFER_BIT)
128 live_imgui_render()
129
130 live_end_frame()
131}
132
133[export]
134def shutdown() {
135 live_imgui_shutdown()
136 live_destroy_window()
137}
138
139[export]
140def main() {
141 init()
142 while (!exit_requested()) {
143 update()
144 }
145 shutdown()
146}
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.
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, ...) { ... }
tab_item(INFO_TAB, ...) { ... }
}
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.
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.
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.
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.FittingPolicyResizeDown/FittingPolicyScroll— what happens when the strip doesn’t fit horizontally.
Per-tab flags via ImGuiTabItemFlags: UnsavedDocument (marks the
header with a dot), SetSelected (a one-shot select-this-tab nudge),
NoCloseButton (closable=true but no X), Leading/Trailing
(pin to bar edges), NoReorder.
Standalone vs live
Same convention as the other tutorials.
See also
Full source: examples/tutorial/tab_bar.das
Features-side demos: examples/features/containers_layout.das —
tab_bar + tree_node + collapsing_header composition;
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.