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 = self.run_with_bound_context(|| unsafe {
44            sys::igBeginCombo(label_ptr, preview_ptr, options.raw())
45        });
46
47        if should_render {
48            Some(ComboBoxToken::new(self))
49        } else {
50            None
51        }
52    }
53
54    /// Creates a combo box without preview value.
55    ///
56    /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been
57    /// rendered, the token must be ended by calling `.end()`.
58    ///
59    /// Returns `None` if the combo box is not open and no content should be rendered.
60    #[must_use]
61    #[doc(alias = "BeginCombo")]
62    pub fn begin_combo_no_preview(&self, label: impl AsRef<str>) -> Option<ComboBoxToken<'_>> {
63        self.begin_combo_no_preview_with_flags(label, ComboBoxFlags::NONE)
64    }
65
66    /// Creates a combo box without preview value and with flags.
67    ///
68    /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been
69    /// rendered, the token must be ended by calling `.end()`.
70    ///
71    /// Returns `None` if the combo box is not open and no content should be rendered.
72    #[must_use]
73    #[doc(alias = "BeginCombo")]
74    pub fn begin_combo_no_preview_with_flags(
75        &self,
76        label: impl AsRef<str>,
77        flags: impl Into<ComboBoxOptions>,
78    ) -> Option<ComboBoxToken<'_>> {
79        let mut options = flags.into();
80        options.preview_mode = ComboBoxPreviewMode::NoPreview;
81        options.validate("Ui::begin_combo_no_preview_with_flags()");
82        let label_ptr = self.scratch_txt(label);
83
84        let should_render = self.run_with_bound_context(|| unsafe {
85            sys::igBeginCombo(label_ptr, std::ptr::null(), options.raw())
86        });
87
88        if should_render {
89            Some(ComboBoxToken::new(self))
90        } else {
91            None
92        }
93    }
94
95    /// Builds a simple combo box for choosing from a slice of values.
96    #[doc(alias = "Combo")]
97    pub fn combo<V, L>(
98        &self,
99        label: impl AsRef<str>,
100        current_item: &mut usize,
101        items: &[V],
102        label_fn: L,
103    ) -> bool
104    where
105        for<'b> L: Fn(&'b V) -> Cow<'b, str>,
106    {
107        let label_fn = &label_fn;
108        let mut result = false;
109        let preview_value = items.get(*current_item).map(label_fn);
110
111        if let Some(combo_token) = self.begin_combo(
112            label,
113            preview_value.as_ref().map(|s| s.as_ref()).unwrap_or(""),
114        ) {
115            for (idx, item) in items.iter().enumerate() {
116                let is_selected = idx == *current_item;
117                if is_selected {
118                    self.set_item_default_focus();
119                }
120
121                let clicked = self.selectable(label_fn(item).as_ref());
122
123                if clicked {
124                    *current_item = idx;
125                    result = true;
126                }
127            }
128            combo_token.end();
129        }
130
131        result
132    }
133
134    /// Builds a simple combo box using an `i32` index (ImGui-style).
135    ///
136    /// This is useful when you want to represent \"no selection\" with `-1`, matching Dear ImGui's
137    /// `Combo()` API.
138    #[doc(alias = "Combo")]
139    pub fn combo_i32<V, L>(
140        &self,
141        label: impl AsRef<str>,
142        current_item: &mut i32,
143        items: &[V],
144        label_fn: L,
145    ) -> bool
146    where
147        for<'b> L: Fn(&'b V) -> Cow<'b, str>,
148    {
149        let label_fn = &label_fn;
150        let mut result = false;
151
152        let preview_value = if *current_item >= 0 {
153            items.get(*current_item as usize).map(|v| label_fn(v))
154        } else {
155            None
156        };
157
158        if let Some(combo_token) = self.begin_combo(
159            label,
160            preview_value.as_ref().map(|s| s.as_ref()).unwrap_or(""),
161        ) {
162            for (idx, item) in items.iter().enumerate() {
163                if idx > i32::MAX as usize {
164                    break;
165                }
166                let idx_i32 = idx as i32;
167                let is_selected = idx_i32 == *current_item;
168                if is_selected {
169                    self.set_item_default_focus();
170                }
171
172                let clicked = self.selectable(label_fn(item).as_ref());
173                if clicked {
174                    *current_item = idx_i32;
175                    result = true;
176                }
177            }
178            combo_token.end();
179        }
180
181        result
182    }
183
184    /// Builds a simple combo box for choosing from a slice of strings
185    #[doc(alias = "Combo")]
186    pub fn combo_simple_string(
187        &self,
188        label: impl AsRef<str>,
189        current_item: &mut usize,
190        items: &[impl AsRef<str>],
191    ) -> bool {
192        self.combo(label, current_item, items, |s| Cow::Borrowed(s.as_ref()))
193    }
194
195    /// Builds a simple combo box for choosing from a slice of strings using an `i32` index.
196    #[doc(alias = "Combo")]
197    pub fn combo_simple_string_i32(
198        &self,
199        label: impl AsRef<str>,
200        current_item: &mut i32,
201        items: &[impl AsRef<str>],
202    ) -> bool {
203        self.combo_i32(label, current_item, items, |s| Cow::Borrowed(s.as_ref()))
204    }
205
206    /// Sets the default focus for the next item
207    pub fn set_item_default_focus(&self) {
208        unsafe {
209            sys::igSetItemDefaultFocus();
210        }
211    }
212}