Skip to main content

dear_imgui_rs/widget/combo/
ui.rs

1use std::borrow::Cow;
2
3use crate::sys;
4use crate::ui::Ui;
5
6use super::{ComboBoxFlags, ComboBoxOptions, ComboBoxPreviewMode, ComboBoxToken};
7
8/// # Combo Box Widgets
9impl Ui {
10    /// Creates a combo box and starts appending to it.
11    ///
12    /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been
13    /// rendered, the token must be ended by calling `.end()`.
14    ///
15    /// Returns `None` if the combo box is not open and no content should be rendered.
16    #[must_use]
17    #[doc(alias = "BeginCombo")]
18    pub fn begin_combo(
19        &self,
20        label: impl AsRef<str>,
21        preview_value: impl AsRef<str>,
22    ) -> Option<ComboBoxToken<'_>> {
23        self.begin_combo_with_flags(label, preview_value, ComboBoxFlags::NONE)
24    }
25
26    /// Creates a combo box with flags and starts appending to it.
27    ///
28    /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been
29    /// rendered, the token must be ended by calling `.end()`.
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: impl Into<ComboBoxOptions>,
38    ) -> Option<ComboBoxToken<'_>> {
39        let options = flags.into();
40        options.validate("Ui::begin_combo_with_flags()");
41        let (label_ptr, preview_ptr) = self.scratch_txt_two(label, preview_value);
42
43        let should_render = unsafe { sys::igBeginCombo(label_ptr, preview_ptr, options.raw()) };
44
45        if should_render {
46            Some(ComboBoxToken::new(self))
47        } else {
48            None
49        }
50    }
51
52    /// Creates a combo box without preview value.
53    ///
54    /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been
55    /// rendered, the token must be ended by calling `.end()`.
56    ///
57    /// Returns `None` if the combo box is not open and no content should be rendered.
58    #[must_use]
59    #[doc(alias = "BeginCombo")]
60    pub fn begin_combo_no_preview(&self, label: impl AsRef<str>) -> Option<ComboBoxToken<'_>> {
61        self.begin_combo_no_preview_with_flags(label, ComboBoxFlags::NONE)
62    }
63
64    /// Creates a combo box without preview value and with flags.
65    ///
66    /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been
67    /// rendered, the token must be ended by calling `.end()`.
68    ///
69    /// Returns `None` if the combo box is not open and no content should be rendered.
70    #[must_use]
71    #[doc(alias = "BeginCombo")]
72    pub fn begin_combo_no_preview_with_flags(
73        &self,
74        label: impl AsRef<str>,
75        flags: impl Into<ComboBoxOptions>,
76    ) -> Option<ComboBoxToken<'_>> {
77        let mut options = flags.into();
78        options.preview_mode = ComboBoxPreviewMode::NoPreview;
79        options.validate("Ui::begin_combo_no_preview_with_flags()");
80        let label_ptr = self.scratch_txt(label);
81
82        let should_render =
83            unsafe { sys::igBeginCombo(label_ptr, std::ptr::null(), options.raw()) };
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 using an `i32` index (ImGui-style).
132    ///
133    /// This is useful when you want to represent \"no selection\" with `-1`, matching Dear ImGui's
134    /// `Combo()` API.
135    #[doc(alias = "Combo")]
136    pub fn combo_i32<V, L>(
137        &self,
138        label: impl AsRef<str>,
139        current_item: &mut i32,
140        items: &[V],
141        label_fn: L,
142    ) -> bool
143    where
144        for<'b> L: Fn(&'b V) -> Cow<'b, str>,
145    {
146        let label_fn = &label_fn;
147        let mut result = false;
148
149        let preview_value = if *current_item >= 0 {
150            items.get(*current_item as usize).map(|v| label_fn(v))
151        } else {
152            None
153        };
154
155        if let Some(combo_token) = self.begin_combo(
156            label,
157            preview_value.as_ref().map(|s| s.as_ref()).unwrap_or(""),
158        ) {
159            for (idx, item) in items.iter().enumerate() {
160                if idx > i32::MAX as usize {
161                    break;
162                }
163                let idx_i32 = idx as i32;
164                let is_selected = idx_i32 == *current_item;
165                if is_selected {
166                    self.set_item_default_focus();
167                }
168
169                let clicked = self.selectable(label_fn(item).as_ref());
170                if clicked {
171                    *current_item = idx_i32;
172                    result = true;
173                }
174            }
175            combo_token.end();
176        }
177
178        result
179    }
180
181    /// Builds a simple combo box for choosing from a slice of strings
182    #[doc(alias = "Combo")]
183    pub fn combo_simple_string(
184        &self,
185        label: impl AsRef<str>,
186        current_item: &mut usize,
187        items: &[impl AsRef<str>],
188    ) -> bool {
189        self.combo(label, current_item, items, |s| Cow::Borrowed(s.as_ref()))
190    }
191
192    /// Builds a simple combo box for choosing from a slice of strings using an `i32` index.
193    #[doc(alias = "Combo")]
194    pub fn combo_simple_string_i32(
195        &self,
196        label: impl AsRef<str>,
197        current_item: &mut i32,
198        items: &[impl AsRef<str>],
199    ) -> bool {
200        self.combo_i32(label, current_item, items, |s| Cow::Borrowed(s.as_ref()))
201    }
202
203    /// Sets the default focus for the next item
204    pub fn set_item_default_focus(&self) {
205        unsafe {
206            sys::igSetItemDefaultFocus();
207        }
208    }
209}