8.18.1.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.18.1.44.1. Walkthrough
1options gen2
2options _comment_hygiene = true
3
4require math
5require strings
6require imgui
7require imgui_app
8require opengl/opengl_boost
9require live/glfw_live
10require live/live_api
11require live/live_commands
12require live/live_vars
13require live_host
14require imgui/imgui_live
15require imgui/imgui_boost_runtime
16require imgui/imgui_boost_v2
17require imgui/imgui_widgets_builtin
18require imgui/imgui_containers_builtin
19require imgui/imgui_visual_aids
20
21// =============================================================================
22// TUTORIAL: dropdown_select — four shapes of pick-one-from-a-list.
23//
24// combo(IDENT, (text = "..", items <- ["A", "B", ...]))
25// Items-array sugar — pass the strings, get a single-call dropdown.
26// combo_getter(IDENT, (text = "..", items_count = N,
27// getter = @(idx; var result : string&) : bool { ... }))
28// Procedural list — the callback supplies row text on demand. Use when
29// items live in an unusual container (table, generator, large dataset).
30// selectable(IDENT, (text = "..", flags = ..., size = ...))
31// One row, self-toggles via ToggleState. Stack N to make a list.
32// selectable_label(IDENT, text, selected, (flags = ..., size = ...))
33// Row whose selected-look is driven by a CALLER-owned bool. Pair with
34// list_box (+ external index) when only one row may be active.
35//
36// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/dropdown_select.das
37// LIVE: daslang-live modules/dasImgui/examples/tutorial/dropdown_select.das
38//
39// DRIVE (when running live):
40// curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DS_WIN/D_FRUIT","value":2}}' \
41// localhost:9090/command
42// curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DS_WIN/D_FONT","value":1}}' \
43// localhost:9090/command
44// curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DS_WIN/D_SHIRT","value":3}}' \
45// localhost:9090/command
46// =============================================================================
47
48let FRUITS : array<string> <- ["Apple", "Banana", "Cherry", "Durian", "Elderberry"]
49let FONTS = fixed_array("Serif", "Sans", "Mono", "Cursive")
50let TAGS = fixed_array("urgent", "blocked", "review")
51let SHIRTS = fixed_array("XS", "S", "M", "L", "XL", "XXL")
52
53var private D_TAG : table<int; ToggleState>
54var private D_SHIRT_ROW : table<int; ClickState>
55
56[export]
57def init() {
58 live_create_window("dasImgui dropdown_select tutorial", 760, 640)
59 live_imgui_init(live_window)
60 let io & = unsafe(GetIO())
61 GetStyle().FontScaleMain = 1.4
62}
63
64[export]
65def update() {
66 if (!live_begin_frame()) return
67 begin_frame()
68
69 ImGui_ImplGlfw_NewFrame()
70 apply_synth_io_override()
71 NewFrame()
72
73 SetNextWindowPos(ImVec2(20.0f, 20.0f), ImGuiCond.Always)
74 SetNextWindowSize(ImVec2(720.0f, 600.0f), ImGuiCond.Always)
75 window(DS_WIN, (text = "dropdown_select tutorial", closable = false,
76 flags = ImGuiWindowFlags.None)) {
77
78 text("Four shapes of pick-one-from-a-list.")
79 text(D_HINT, (text = "combo collapses; selectable rows stay open."))
80 separator()
81
82 // ---- Stage 1: combo — items as a string array ----
83 combo(D_FRUIT, (text = "Fruit", items <- FRUITS))
84 text("D_FRUIT.value = {D_FRUIT.value} ('{FRUITS[clamp(D_FRUIT.value, 0, length(FRUITS) - 1)]}')")
85 spacing()
86
87 // ---- Stage 2: combo_getter — callback supplies row text on demand ----
88 combo_getter(D_FONT, (text = "Font", items_count = length(FONTS),
89 getter = @(idx : int; var result : string&) : bool {
90 if (idx >= 0 && idx < length(FONTS)) {
91 result = FONTS[idx]
92 return true
93 }
94 return false
95 }))
96 text("D_FONT.value = {D_FONT.value} ('{FONTS[clamp(D_FONT.value, 0, length(FONTS) - 1)]}')")
97 spacing()
98
99 // ---- Stage 3: selectable — N rows, each self-toggles independently ----
100 text("Tags (multi-select - each row toggles its own ToggleState):")
101 for (i in range(length(TAGS))) {
102 selectable(D_TAG[i], (text = TAGS[i]))
103 }
104 text("active tags: {tags_summary()}")
105 spacing()
106
107 // ---- Stage 4: selectable_label inside list_box — single-select ----
108 text("Shirt size (single-select - caller-owned int picks active row):")
109 list_box(D_SHIRT, (text = "size", size = float2(0.0f, 0.0f))) {
110 for (i in range(length(SHIRTS))) {
111 let is_sel = (i == D_SHIRT.value)
112 if (selectable_label(D_SHIRT_ROW[i], SHIRTS[i], is_sel)) {
113 D_SHIRT.value = i
114 }
115 }
116 }
117 text("D_SHIRT.value = {D_SHIRT.value} ('{SHIRTS[clamp(D_SHIRT.value, 0, length(SHIRTS) - 1)]}')")
118 }
119
120 end_of_frame()
121 Render()
122 var w, h : int
123 live_get_framebuffer_size(w, h)
124 glViewport(0, 0, w, h)
125 glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
126 glClear(GL_COLOR_BUFFER_BIT)
127 live_imgui_render()
128
129 live_end_frame()
130}
131
132def tags_summary() : string {
133 var first = true
134 return build_string() $(var w : StringBuilderWriter) {
135 for (i in range(length(TAGS))) {
136 if (D_TAG[i].value) {
137 if (!first) {
138 w |> write(", ")
139 }
140 w |> write(TAGS[i])
141 first = false
142 }
143 }
144 if (first) {
145 w |> write("(none)")
146 }
147 }
148}
149
150[export]
151def shutdown() {
152 live_imgui_shutdown()
153 live_destroy_window()
154}
155
156[export]
157def main() {
158 init()
159 while (!exit_requested()) {
160 update()
161 }
162 shutdown()
163}
8.18.1.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.18.1.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.18.1.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.18.1.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.18.1.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.18.1.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.18.1.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.