dear_imgui/widget/
combo.rs

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