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                let clicked = self
118                    .selectable_config(label_fn(item).as_ref())
119                    .selected(is_selected)
120                    .build();
121                if is_selected {
122                    self.set_item_default_focus();
123                }
124
125                if clicked {
126                    *current_item = idx;
127                    result = true;
128                }
129            }
130            combo_token.end();
131        }
132
133        result
134    }
135
136    /// Builds a simple combo box using an `i32` index (ImGui-style).
137    ///
138    /// This is useful when you want to represent \"no selection\" with `-1`, matching Dear ImGui's
139    /// `Combo()` API.
140    #[doc(alias = "Combo")]
141    pub fn combo_i32<V, L>(
142        &self,
143        label: impl AsRef<str>,
144        current_item: &mut i32,
145        items: &[V],
146        label_fn: L,
147    ) -> bool
148    where
149        for<'b> L: Fn(&'b V) -> Cow<'b, str>,
150    {
151        let label_fn = &label_fn;
152        let mut result = false;
153
154        let preview_value = if *current_item >= 0 {
155            items.get(*current_item as usize).map(|v| label_fn(v))
156        } else {
157            None
158        };
159
160        if let Some(combo_token) = self.begin_combo(
161            label,
162            preview_value.as_ref().map(|s| s.as_ref()).unwrap_or(""),
163        ) {
164            for (idx, item) in items.iter().enumerate() {
165                if idx > i32::MAX as usize {
166                    break;
167                }
168                let idx_i32 = idx as i32;
169                let is_selected = idx_i32 == *current_item;
170                let clicked = self
171                    .selectable_config(label_fn(item).as_ref())
172                    .selected(is_selected)
173                    .build();
174                if is_selected {
175                    self.set_item_default_focus();
176                }
177                if clicked {
178                    *current_item = idx_i32;
179                    result = true;
180                }
181            }
182            combo_token.end();
183        }
184
185        result
186    }
187
188    /// Builds a simple combo box for choosing from a slice of strings
189    #[doc(alias = "Combo")]
190    pub fn combo_simple_string(
191        &self,
192        label: impl AsRef<str>,
193        current_item: &mut usize,
194        items: &[impl AsRef<str>],
195    ) -> bool {
196        self.combo(label, current_item, items, |s| Cow::Borrowed(s.as_ref()))
197    }
198
199    /// Builds a simple combo box for choosing from a slice of strings using an `i32` index.
200    #[doc(alias = "Combo")]
201    pub fn combo_simple_string_i32(
202        &self,
203        label: impl AsRef<str>,
204        current_item: &mut i32,
205        items: &[impl AsRef<str>],
206    ) -> bool {
207        self.combo_i32(label, current_item, items, |s| Cow::Borrowed(s.as_ref()))
208    }
209
210    /// Makes the last submitted item the default focus of a newly appearing window.
211    #[doc(alias = "SetItemDefaultFocus")]
212    pub fn set_item_default_focus(&self) {
213        self.run_with_bound_context(|| unsafe { sys::igSetItemDefaultFocus() });
214    }
215}