dear_imgui_rs/widget/
combo.rs

1//! Combo boxes
2//!
3//! Single-selection dropdowns with optional height and popup alignment flags.
4//! Builders provide both string and custom item sources.
5//!
6use std::borrow::Cow;
7
8use crate::sys;
9use crate::ui::Ui;
10use crate::widget::ComboBoxFlags;
11
12/// # Combo Box Widgets
13impl Ui {
14    /// Creates a combo box and starts appending to it.
15    ///
16    /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been
17    /// rendered, the token must be ended by calling `.end()`.
18    ///
19    /// Returns `None` if the combo box is not open and no content should be rendered.
20    #[must_use]
21    #[doc(alias = "BeginCombo")]
22    pub fn begin_combo(
23        &self,
24        label: impl AsRef<str>,
25        preview_value: impl AsRef<str>,
26    ) -> Option<ComboBoxToken<'_>> {
27        self.begin_combo_with_flags(label, preview_value, ComboBoxFlags::NONE)
28    }
29
30    /// Creates a combo box with flags and starts appending to it.
31    ///
32    /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been
33    /// rendered, the token must be ended by calling `.end()`.
34    ///
35    /// Returns `None` if the combo box is not open and no content should be rendered.
36    #[must_use]
37    #[doc(alias = "BeginCombo")]
38    pub fn begin_combo_with_flags(
39        &self,
40        label: impl AsRef<str>,
41        preview_value: impl AsRef<str>,
42        flags: ComboBoxFlags,
43    ) -> Option<ComboBoxToken<'_>> {
44        let label_ptr = self.scratch_txt(label);
45        let preview_ptr = self.scratch_txt(preview_value);
46
47        let should_render = unsafe { sys::igBeginCombo(label_ptr, preview_ptr, flags.bits()) };
48
49        if should_render {
50            Some(ComboBoxToken::new(self))
51        } else {
52            None
53        }
54    }
55
56    /// Creates a combo box without preview value.
57    ///
58    /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been
59    /// rendered, the token must be ended by calling `.end()`.
60    ///
61    /// Returns `None` if the combo box is not open and no content should be rendered.
62    #[must_use]
63    #[doc(alias = "BeginCombo")]
64    pub fn begin_combo_no_preview(&self, label: impl AsRef<str>) -> Option<ComboBoxToken<'_>> {
65        self.begin_combo_no_preview_with_flags(label, ComboBoxFlags::NONE)
66    }
67
68    /// Creates a combo box without preview value and with flags.
69    ///
70    /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been
71    /// rendered, the token must be ended by calling `.end()`.
72    ///
73    /// Returns `None` if the combo box is not open and no content should be rendered.
74    #[must_use]
75    #[doc(alias = "BeginCombo")]
76    pub fn begin_combo_no_preview_with_flags(
77        &self,
78        label: impl AsRef<str>,
79        flags: ComboBoxFlags,
80    ) -> Option<ComboBoxToken<'_>> {
81        let label_ptr = self.scratch_txt(label);
82
83        let should_render = unsafe { sys::igBeginCombo(label_ptr, std::ptr::null(), flags.bits()) };
84
85        if should_render {
86            Some(ComboBoxToken::new(self))
87        } else {
88            None
89        }
90    }
91
92    /// Builds a simple combo box for choosing from a slice of values.
93    #[doc(alias = "Combo")]
94    pub fn combo<V, L>(
95        &self,
96        label: impl AsRef<str>,
97        current_item: &mut usize,
98        items: &[V],
99        label_fn: L,
100    ) -> bool
101    where
102        for<'b> L: Fn(&'b V) -> Cow<'b, str>,
103    {
104        let label_fn = &label_fn;
105        let mut result = false;
106        let preview_value = items.get(*current_item).map(label_fn);
107
108        if let Some(combo_token) = self.begin_combo(
109            label,
110            preview_value.as_ref().map(|s| s.as_ref()).unwrap_or(""),
111        ) {
112            for (idx, item) in items.iter().enumerate() {
113                let is_selected = idx == *current_item;
114                if is_selected {
115                    self.set_item_default_focus();
116                }
117
118                let clicked = self.selectable(label_fn(item).as_ref());
119
120                if clicked {
121                    *current_item = idx;
122                    result = true;
123                }
124            }
125            combo_token.end();
126        }
127
128        result
129    }
130
131    /// Builds a simple combo box for choosing from a slice of strings
132    #[doc(alias = "Combo")]
133    pub fn combo_simple_string(
134        &self,
135        label: impl AsRef<str>,
136        current_item: &mut usize,
137        items: &[impl AsRef<str>],
138    ) -> bool {
139        self.combo(label, current_item, items, |s| Cow::Borrowed(s.as_ref()))
140    }
141
142    /// Sets the default focus for the next item
143    pub fn set_item_default_focus(&self) {
144        unsafe {
145            sys::igSetItemDefaultFocus();
146        }
147    }
148}
149
150/// Builder for a combo box widget
151#[derive(Clone, Debug)]
152#[must_use]
153pub struct ComboBox<'ui, Label, Preview = &'static str> {
154    pub label: Label,
155    pub preview_value: Option<Preview>,
156    pub flags: ComboBoxFlags,
157    pub ui: &'ui Ui,
158}
159
160impl<'ui, Label: AsRef<str>> ComboBox<'ui, Label> {
161    /// Sets the preview value
162    pub fn preview_value<P: AsRef<str>>(self, preview: P) -> ComboBox<'ui, Label, P> {
163        ComboBox {
164            label: self.label,
165            preview_value: Some(preview),
166            flags: self.flags,
167            ui: self.ui,
168        }
169    }
170
171    /// Sets the flags
172    pub fn flags(mut self, flags: ComboBoxFlags) -> Self {
173        self.flags = flags;
174        self
175    }
176
177    /// Creates a combo box and starts appending to it.
178    ///
179    /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been
180    /// rendered, the token must be ended by calling `.end()`.
181    ///
182    /// Returns `None` if the combo box is not open and no content should be rendered.
183    #[must_use]
184    pub fn begin(self) -> Option<ComboBoxToken<'ui>> {
185        let label_ptr = self.ui.scratch_txt(&self.label);
186        let preview_ptr = self
187            .preview_value
188            .as_ref()
189            .map(|p| self.ui.scratch_txt(p))
190            .unwrap_or(std::ptr::null());
191
192        let should_render = unsafe { sys::igBeginCombo(label_ptr, preview_ptr, self.flags.bits()) };
193
194        if should_render {
195            Some(ComboBoxToken::new(self.ui))
196        } else {
197            None
198        }
199    }
200}
201
202/// Tracks a combo box that can be ended by calling `.end()` or by dropping
203#[must_use]
204pub struct ComboBoxToken<'ui> {
205    ui: &'ui Ui,
206}
207
208impl<'ui> ComboBoxToken<'ui> {
209    /// Creates a new combo box token
210    fn new(ui: &'ui Ui) -> Self {
211        ComboBoxToken { ui }
212    }
213
214    /// Ends the combo box
215    pub fn end(self) {
216        // The drop implementation will handle the actual ending
217    }
218}
219
220impl<'ui> Drop for ComboBoxToken<'ui> {
221    fn drop(&mut self) {
222        unsafe {
223            sys::igEndCombo();
224        }
225    }
226}