8.19.44. Dropdown + select
Four shapes of pick-one-from-a-list. combo collapses choices
into a dropdown popup; selectable stacks them as always-visible
rows. combo_getter serves a procedural list from a lambda;
selectable_label lets the caller own the selected-flag (so you
can wire it through a list_box for single-select):
combo(IDENT, (text = "..", items <- ["A", "B", ...])) // dropdown, items array
combo_getter(IDENT, (text = "..", items_count = N, // dropdown, procedural list
getter = @(idx; var r : string&) : bool { ... }))
selectable(IDENT, (text = "..")) // row, self-toggles
selectable_label(IDENT, text, selected, (flags = ...)) // row, caller owns 'selected'
Source: modules/dasImgui/examples/tutorial/dropdown_select.das.
8.19.44.1. Walkthrough
1options gen2
2options _comment_hygiene = true
3options gc
4
5require math
6require strings
7require imgui
8require imgui_app
9require opengl/opengl_boost
10require live/glfw_live
11require live/live_api
12require live/live_commands
13require live/live_vars
14require live_host
15require imgui/imgui_live
16require imgui/imgui_boost_runtime
17require imgui/imgui_boost_v2
18require imgui/imgui_widgets_builtin
19require imgui/imgui_containers_builtin
20require imgui/imgui_visual_aids
21
22// =============================================================================
23// TUTORIAL: dropdown_select — four shapes of pick-one-from-a-list.
24//
25// combo(IDENT, (text = "..", items <- ["A", "B", ...]))
26// Items-array sugar — pass the strings, get a single-call dropdown.
27// combo_getter(IDENT, (text = "..", items_count = N,
28// getter = @(idx; var result : string&) : bool { ... }))
29// Procedural list — the callback supplies row text on demand. Use when
30// items live in an unusual container (table, generator, large dataset).
31// selectable(IDENT, (text = "..", flags = ..., size = ...))
32// One row, self-toggles via ToggleState. Stack N to make a list.
33// selectable_label(IDENT, text, selected, (flags = ..., size = ...))
34// Row whose selected-look is driven by a CALLER-owned bool. Pair with
35// list_box (+ external index) when only one row may be active.
36//
37// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/dropdown_select.das
38// LIVE: daslang-live modules/dasImgui/examples/tutorial/dropdown_select.das
39//
40// DRIVE (when running live):
41// curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DS_WIN/D_FRUIT","value":2}}' \
42// localhost:9090/command
43// curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DS_WIN/D_FONT","value":1}}' \
44// localhost:9090/command
45// curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DS_WIN/D_SHIRT","value":3}}' \
46// localhost:9090/command
47// =============================================================================
48
49let FRUITS : array<string> <- ["Apple", "Banana", "Cherry", "Durian", "Elderberry"]
50let FONTS = fixed_array("Serif", "Sans", "Mono", "Cursive")
51let TAGS = fixed_array("urgent", "blocked", "review")
52let SHIRTS = fixed_array("XS", "S", "M", "L", "XL", "XXL")
53
54var private D_TAG : table<int; ToggleState>
55var private D_SHIRT_ROW : table<int; ClickState>
56
57[export]
58def init() {
59 live_create_window("dasImgui dropdown_select tutorial", 760, 640)
60 live_imgui_init(live_window)
61 let io & = unsafe(GetIO())
62 GetStyle().FontScaleMain = 1.4
63}
64
65[export]
66def update() {
67 if (!live_begin_frame()) return
68 begin_frame()
69
70 ImGui_ImplGlfw_NewFrame()
71 apply_synth_io_override()
72 NewFrame()
73
74 SetNextWindowPos(ImVec2(20.0f, 20.0f), ImGuiCond.Always)
75 SetNextWindowSize(ImVec2(720.0f, 600.0f), ImGuiCond.Always)
76 window(DS_WIN, (text = "dropdown_select tutorial", closable = false,
77 flags = ImGuiWindowFlags.None)) {
78
79 text("Four shapes of pick-one-from-a-list.")
80 text(D_HINT, (text = "combo collapses; selectable rows stay open."))
81 separator()
82
83 // ---- Stage 1: combo — items as a string array ----
84 combo(D_FRUIT, (text = "Fruit", items <- FRUITS))
85 text("D_FRUIT.value = {D_FRUIT.value} ('{FRUITS[clamp(D_FRUIT.value, 0, length(FRUITS) - 1)]}')")
86 spacing()
87
88 // ---- Stage 2: combo_getter — callback supplies row text on demand ----
89 combo_getter(D_FONT, (text = "Font", items_count = length(FONTS),
90 getter = @(idx : int; var result : string&) : bool {
91 if (idx >= 0 && idx < length(FONTS)) {
92 result = FONTS[idx]
93 return true
94 }
95 return false
96 }))
97 text("D_FONT.value = {D_FONT.value} ('{FONTS[clamp(D_FONT.value, 0, length(FONTS) - 1)]}')")
98 spacing()
99
100 // ---- Stage 3: selectable — N rows, each self-toggles independently ----
101 text("Tags (multi-select - each row toggles its own ToggleState):")
102 for (i in range(length(TAGS))) {
103 selectable(D_TAG[i], (text = TAGS[i]))
104 }
105 text("active tags: {tags_summary()}")
106 spacing()
107
108 // ---- Stage 4: selectable_label inside list_box — single-select ----
109 text("Shirt size (single-select - caller-owned int picks active row):")
110 list_box(D_SHIRT, (text = "size", size = float2(0.0f, 0.0f))) {
111 for (i in range(length(SHIRTS))) {
112 let is_sel = (i == D_SHIRT.value)
113 if (selectable_label(D_SHIRT_ROW[i], SHIRTS[i], is_sel)) {
114 D_SHIRT.value = i
115 }
116 }
117 }
118 text("D_SHIRT.value = {D_SHIRT.value} ('{SHIRTS[clamp(D_SHIRT.value, 0, length(SHIRTS) - 1)]}')")
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
133def tags_summary() : string {
134 var first = true
135 return build_string() $(var w : StringBuilderWriter) {
136 for (i in range(length(TAGS))) {
137 if (D_TAG[i].value) {
138 if (!first) {
139 w |> write(", ")
140 }
141 w |> write(TAGS[i])
142 first = false
143 }
144 }
145 if (first) {
146 w |> write("(none)")
147 }
148 }
149}
150
151[export]
152def shutdown() {
153 live_imgui_shutdown()
154 live_destroy_window()
155}
156
157[export]
158def main() {
159 init()
160 while (!exit_requested()) {
161 update()
162 maybe_collect_gc()
163 }
164 shutdown()
165}
8.19.44.1.1. Requires
Already in the baseline boost layer:
imgui/imgui_widgets_builtin—combo/combo_getter/selectable/selectable_labelrails.imgui/imgui_containers_builtin—list_boxblock-arg container.imgui/imgui_boost_runtime—ComboState/ToggleState/ClickState/ListBoxStatestate structs.
8.19.44.1.2. combo — items array
The simplest form. Pass the labels, get a dropdown; state.value is
the chosen index.
combo(FRUIT, (text = "Fruit",
items <- ["Apple", "Banana", "Cherry"]))
// FRUIT.value == 0 / 1 / 2
items is non-copyable (it’s a moved array<string>) — pass it
inline with <-, or move from a local. Cap a long popup with
popup_max_height_in_items = N to limit the scroller height.
8.19.44.1.3. combo_getter — procedural list
When labels don’t live in a flat array<string> (table-backed,
generator-driven, computed on the fly), use the getter form. The
callback returns one label per idx:
combo_getter(FONT, (text = "Font", items_count = length(FONTS),
getter = @(idx : int; var result : string&) : bool {
if (idx >= 0 && idx < length(FONTS)) {
result = FONTS[idx]
return true
}
return false
}))
Return false for out-of-range indices — ImGui treats that as an
empty row, useful for sparse lists. items_count is the upper bound
the dropdown will probe.
8.19.44.1.4. selectable — multi-toggle rows
Each selectable owns its own ToggleState — rows toggle
independently, which falls out as multi-select naturally:
var private TAG : table<int; ToggleState>
let TAGS = fixed_array("urgent", "blocked", "review")
for (i in range(length(TAGS))) {
selectable(TAG[i], (text = TAGS[i]))
}
// any subset can be on at once — read TAG[i].value to filter.
The table<int; ToggleState> pattern is required for any indexed
call (one IDENT, many rows). The state allocates lazily on first
touch of each index.
8.19.44.1.5. selectable_label inside list_box — single-select
For one-of-N, switch to selectable_label with a caller-owned
selected bool, wrapped in list_box (which adds the bordered
frame + scroller). The list_box’s state.value holds the chosen
index; the loop tags each row’s selected from it:
var private SHIRT_ROW : table<int; ClickState>
let SHIRTS = fixed_array("XS", "S", "M", "L", "XL", "XXL")
list_box(SHIRT, (text = "size", size = float2(0.0f, 0.0f))) {
for (i in range(length(SHIRTS))) {
let is_sel = (i == SHIRT.value)
if (selectable_label(SHIRT_ROW[i], SHIRTS[i], is_sel)) {
SHIRT.value = i
}
}
}
The ClickState rows carry only edge-trigger info — no per-row
toggle persistence. The list_box’s state.value is the single
source of truth, so imgui_force_set on the list_box target works the
same as a combo.
8.19.44.1.6. Driving from outside
# combo / combo_getter — int value (the chosen index)
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DS_WIN/D_FRUIT","value":2}}' \
localhost:9090/command
# list_box single-select — int value (the chosen row)
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DS_WIN/D_SHIRT","value":3}}' \
localhost:9090/command
# selectable multi-select — bool per row (use the indexed IDENT)
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DS_WIN/D_TAG[0]","value":true}}' \
localhost:9090/command
8.19.44.1.7. When to use which
combo — known list of choices, screen real estate matters.
itemsis a one-liner.combo_getter — list is procedural / huge / sparse. Same UX, callback-driven content.
selectable — multi-select. Rows always visible (no popup). Owns its own state per row.
selectable_label + list_box — single-select that needs to stay visible (sidebar, settings panel). The framed list_box is the always-visible counterpart to combo’s collapsing popup.
If a strip of 2-5 mutually exclusive options would fit inline,
radio_button_int (Toggles) is lighter than a
combo or list_box.
See also
Full source: modules/dasImgui/examples/tutorial/dropdown_select.das
Features-side demo: modules/dasImgui/examples/features/inputs_choice.das —
combo + list_box in one window for imgui_force_set smoke testing.
Sibling tutorials: Toggles, Selectable hover (hover-only variant of selectable).
Boost macros — the macro layer.