arcdps_imgui\widget/
combo_box.rs

1use bitflags::bitflags;
2use std::borrow::Cow;
3
4use crate::sys;
5use crate::Ui;
6
7// TODO: support size constraints
8
9/// Combo box height mode.
10#[derive(Copy, Clone, Debug, PartialEq, Eq)]
11pub enum ComboBoxHeight {
12    /// Max ~4 items visible.
13    Small,
14    /// Max ~8 items visible.
15    Regular,
16    /// Max ~20 items visible.
17    Large,
18    /// As many fitting items as possible visible.
19    Largest,
20}
21
22/// Combo box preview mode.
23#[derive(Copy, Clone, Debug, PartialEq, Eq)]
24pub enum ComboBoxPreviewMode {
25    /// Show only a box with the preview value
26    Label,
27    /// Show only an arrow button
28    ArrowButton,
29    /// Show a box with the preview value and an arrow button
30    Full,
31}
32
33bitflags!(
34/// Flags for combo boxes
35#[repr(transparent)]
36pub struct ComboBoxFlags: u32 {
37    /// Align the popup toward the left by default
38    const POPUP_ALIGN_LEFT = sys::ImGuiComboFlags_PopupAlignLeft;
39    /// Max ~4 items visible.
40    const HEIGHT_SMALL = sys::ImGuiComboFlags_HeightSmall;
41    /// Max ~8 items visible (default)
42    const HEIGHT_REGULAR = sys::ImGuiComboFlags_HeightRegular;
43    /// Max ~20 items visible
44    const HEIGHT_LARGE = sys::ImGuiComboFlags_HeightLarge;
45    /// As many fitting items as possible
46    const HEIGHT_LARGEST = sys::ImGuiComboFlags_HeightLargest;
47    /// Display on the preview box without the square arrow button
48    const NO_ARROW_BUTTON = sys::ImGuiComboFlags_NoArrowButton;
49    /// Display only a square arrow button
50    const NO_PREVIEW = sys::ImGuiComboFlags_NoPreview;
51}
52);
53
54/// Builder for a combo box widget
55#[derive(Copy, Clone, Debug)]
56#[must_use]
57pub struct ComboBox<Label, Preview = &'static str> {
58    label: Label,
59    preview_value: Option<Preview>,
60    flags: ComboBoxFlags,
61}
62
63impl<Label: AsRef<str>> ComboBox<Label> {
64    /// Constructs a new combo box builder.
65    #[doc(alias = "BeginCombo")]
66    pub fn new(label: Label) -> Self {
67        ComboBox {
68            label,
69            preview_value: None,
70            flags: ComboBoxFlags::empty(),
71        }
72    }
73}
74
75impl<T: AsRef<str>, Preview: AsRef<str>> ComboBox<T, Preview> {
76    pub fn preview_value<Preview2: AsRef<str>>(
77        self,
78        preview_value: Preview2,
79    ) -> ComboBox<T, Preview2> {
80        ComboBox {
81            label: self.label,
82            preview_value: Some(preview_value),
83            flags: self.flags,
84        }
85    }
86
87    /// Replaces all current settings with the given flags.
88    pub fn flags(mut self, flags: ComboBoxFlags) -> Self {
89        self.flags = flags;
90        self
91    }
92
93    /// Enables/disables aligning the combo box popup toward the left.
94    ///
95    /// Disabled by default.
96    pub fn popup_align_left(mut self, popup_align_left: bool) -> Self {
97        self.flags
98            .set(ComboBoxFlags::POPUP_ALIGN_LEFT, popup_align_left);
99        self
100    }
101
102    /// Sets the combo box height.
103    ///
104    /// Default: `ComboBoxHeight::Regular`
105    #[inline]
106    pub fn height(mut self, height: ComboBoxHeight) -> Self {
107        self.flags
108            .set(ComboBoxFlags::HEIGHT_SMALL, height == ComboBoxHeight::Small);
109        self.flags.set(
110            ComboBoxFlags::HEIGHT_REGULAR,
111            height == ComboBoxHeight::Regular,
112        );
113        self.flags
114            .set(ComboBoxFlags::HEIGHT_LARGE, height == ComboBoxHeight::Large);
115        self.flags.set(
116            ComboBoxFlags::HEIGHT_LARGEST,
117            height == ComboBoxHeight::Largest,
118        );
119        self
120    }
121
122    /// Sets the combo box preview mode.
123    ///
124    /// Default: `ComboBoxPreviewMode::Full`
125    #[inline]
126    pub fn preview_mode(mut self, preview_mode: ComboBoxPreviewMode) -> Self {
127        self.flags.set(
128            ComboBoxFlags::NO_ARROW_BUTTON,
129            preview_mode == ComboBoxPreviewMode::Label,
130        );
131        self.flags.set(
132            ComboBoxFlags::NO_PREVIEW,
133            preview_mode == ComboBoxPreviewMode::ArrowButton,
134        );
135        self
136    }
137
138    /// Creates a combo box and starts appending to it.
139    ///
140    /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been
141    /// rendered, the token must be ended by calling `.end()`.
142    ///
143    /// Returns `None` if the combo box is not open and no content should be rendered.
144    #[must_use]
145    pub fn begin<'ui>(self, ui: &Ui<'ui>) -> Option<ComboBoxToken<'ui>> {
146        let should_render = unsafe {
147            if let Some(preview_value) = self.preview_value {
148                let (ptr_one, ptr_two) = ui.scratch_txt_two(self.label, preview_value);
149                sys::igBeginCombo(ptr_one, ptr_two, self.flags.bits() as i32)
150            } else {
151                let ptr_one = ui.scratch_txt(self.label);
152                sys::igBeginCombo(ptr_one, std::ptr::null(), self.flags.bits() as i32)
153            }
154        };
155        if should_render {
156            Some(ComboBoxToken::new(ui))
157        } else {
158            None
159        }
160    }
161    /// Creates a combo box and runs a closure to construct the popup contents.
162    /// Returns the result of the closure, if it is called.
163    ///
164    /// Note: the closure is not called if the combo box is not open.
165    pub fn build<R, F: FnOnce() -> R>(self, ui: &Ui<'_>, f: F) -> Option<R> {
166        self.begin(ui).map(|_combo| f())
167    }
168}
169
170create_token!(
171    /// Tracks a combo box that can be ended by calling `.end()`
172    /// or by dropping.
173    pub struct ComboBoxToken<'ui>;
174
175    /// Ends a combo box
176    drop { sys::igEndCombo() }
177);
178
179/// # Convenience functions
180impl<'ui> Ui<'ui> {
181    /// Creates a combo box which can be appended to with `Selectable::new`.
182    ///
183    /// If you do not want to provide a preview, use [`begin_combo_no_preview`]. If you want
184    /// to pass flags, use [`begin_combo_with_flags`].
185    ///
186    /// Returns `None` if the combo box is not open and no content should be rendered.
187    ///
188    /// [`begin_combo_no_preview`]: Ui::begin_combo_no_preview
189    /// [`begin_combo_with_flags`]: Ui::begin_combo_with_flags
190    #[must_use]
191    #[doc(alias = "BeginCombo")]
192    pub fn begin_combo(
193        &self,
194        label: impl AsRef<str>,
195        preview_value: impl AsRef<str>,
196    ) -> Option<ComboBoxToken<'ui>> {
197        self.begin_combo_with_flags(label, preview_value, ComboBoxFlags::empty())
198    }
199
200    /// Creates a combo box which can be appended to with `Selectable::new`.
201    ///
202    /// If you do not want to provide a preview, use [begin_combo_no_preview].
203    /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been
204    /// rendered, the token must be ended by calling `.end()`.
205    ///
206    /// Returns `None` if the combo box is not open and no content should be rendered.
207    ///
208    /// [begin_combo_no_preview]: Ui::begin_combo_no_preview
209    #[must_use]
210    #[doc(alias = "BeginCombo")]
211    pub fn begin_combo_with_flags(
212        &self,
213        label: impl AsRef<str>,
214        preview_value: impl AsRef<str>,
215        flags: ComboBoxFlags,
216    ) -> Option<ComboBoxToken<'ui>> {
217        self._begin_combo(label, Some(preview_value), flags)
218    }
219
220    /// Creates a combo box which can be appended to with `Selectable::new`.
221    ///
222    /// If you want to provide a preview, use [begin_combo]. If you want
223    /// to pass flags, use [begin_combo_no_preview_with_flags].
224    ///
225    /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been
226    /// rendered, the token must be ended by calling `.end()`.
227    ///
228    /// Returns `None` if the combo box is not open and no content should be rendered.
229    ///
230    /// [begin_combo]: Ui::begin_combo
231    /// [begin_combo_no_preview_with_flags]: Ui::begin_combo_no_preview_with_flags
232    #[must_use]
233    #[doc(alias = "BeginCombo")]
234    pub fn begin_combo_no_preview(&self, label: impl AsRef<str>) -> Option<ComboBoxToken<'ui>> {
235        self.begin_combo_no_preview_with_flags(label, ComboBoxFlags::empty())
236    }
237
238    /// Creates a combo box which can be appended to with `Selectable::new`.
239    ///
240    /// If you do not want to provide a preview, use [begin_combo_no_preview].
241    /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been
242    /// rendered, the token must be ended by calling `.end()`.
243    ///
244    /// Returns `None` if the combo box is not open and no content should be rendered.
245    ///
246    /// [begin_combo_no_preview]: Ui::begin_combo_no_preview
247    #[must_use]
248    #[doc(alias = "BeginCombo")]
249    pub fn begin_combo_no_preview_with_flags(
250        &self,
251        label: impl AsRef<str>,
252        flags: ComboBoxFlags,
253    ) -> Option<ComboBoxToken<'ui>> {
254        self._begin_combo(label, Option::<&'static str>::None, flags)
255    }
256
257    /// This is the internal begin combo method that they all...eventually call.
258    fn _begin_combo(
259        &self,
260        label: impl AsRef<str>,
261        preview_value: Option<impl AsRef<str>>,
262        flags: ComboBoxFlags,
263    ) -> Option<ComboBoxToken<'ui>> {
264        let should_render = unsafe {
265            let (ptr_one, ptr_two) = self.scratch_txt_with_opt(label, preview_value);
266            sys::igBeginCombo(ptr_one, ptr_two, flags.bits() as i32)
267        };
268        if should_render {
269            Some(ComboBoxToken::new(self))
270        } else {
271            None
272        }
273    }
274    /// Builds a simple combo box for choosing from a slice of values
275    #[doc(alias = "Combo")]
276    pub fn combo<V, L>(
277        &self,
278        label: impl AsRef<str>,
279        current_item: &mut usize,
280        items: &[V],
281        label_fn: L,
282    ) -> bool
283    where
284        for<'b> L: Fn(&'b V) -> Cow<'b, str>,
285    {
286        use crate::widget::selectable::Selectable;
287        let label_fn = &label_fn;
288        let mut result = false;
289        let preview_value = items.get(*current_item).map(label_fn);
290
291        if let Some(_cb) = self._begin_combo(label, preview_value, ComboBoxFlags::empty()) {
292            for (idx, item) in items.iter().enumerate() {
293                let text = label_fn(item);
294                let selected = idx == *current_item;
295                if Selectable::new(&text).selected(selected).build(self) {
296                    *current_item = idx;
297                    result = true;
298                }
299                if selected {
300                    self.set_item_default_focus();
301                }
302            }
303        }
304        result
305    }
306
307    /// Builds a simple combo box for choosing from a slice of values
308    #[doc(alias = "Combo")]
309    pub fn combo_simple_string(
310        &self,
311        label: impl AsRef<str>,
312        current_item: &mut usize,
313        items: &[impl AsRef<str>],
314    ) -> bool {
315        self.combo(label, current_item, items, |s| Cow::Borrowed(s.as_ref()))
316    }
317}