use bitflags::bitflags; use std::borrow::Cow; use crate::sys; use crate::Ui; // TODO: support size constraints /// Combo box height mode. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ComboBoxHeight { /// Max ~4 items visible. Small, /// Max ~8 items visible. Regular, /// Max ~20 items visible. Large, /// As many fitting items as possible visible. Largest, } /// Combo box preview mode. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ComboBoxPreviewMode { /// Show only a box with the preview value Label, /// Show only an arrow button ArrowButton, /// Show a box with the preview value and an arrow button Full, } bitflags!( /// Flags for combo boxes #[repr(transparent)] pub struct ComboBoxFlags: u32 { /// Align the popup toward the left by default const POPUP_ALIGN_LEFT = sys::ImGuiComboFlags_PopupAlignLeft; /// Max ~4 items visible. const HEIGHT_SMALL = sys::ImGuiComboFlags_HeightSmall; /// Max ~8 items visible (default) const HEIGHT_REGULAR = sys::ImGuiComboFlags_HeightRegular; /// Max ~20 items visible const HEIGHT_LARGE = sys::ImGuiComboFlags_HeightLarge; /// As many fitting items as possible const HEIGHT_LARGEST = sys::ImGuiComboFlags_HeightLargest; /// Display on the preview box without the square arrow button const NO_ARROW_BUTTON = sys::ImGuiComboFlags_NoArrowButton; /// Display only a square arrow button const NO_PREVIEW = sys::ImGuiComboFlags_NoPreview; } ); /// Builder for a combo box widget #[derive(Copy, Clone, Debug)] #[must_use] pub struct ComboBox { label: T, preview_value: Option

, flags: ComboBoxFlags, } impl<'a, T: AsRef, P: AsRef> ComboBox { /// Constructs a new combo box builder. #[doc(alias = "BeginCombo")] pub fn new(label: T, preview_value: Option

) -> Self { ComboBox { label, preview_value, flags: ComboBoxFlags::empty(), } } /// Replaces all current settings with the given flags. #[inline] pub fn flags(mut self, flags: ComboBoxFlags) -> Self { self.flags = flags; self } /// Enables/disables aligning the combo box popup toward the left. /// /// Disabled by default. #[inline] pub fn popup_align_left(mut self, popup_align_left: bool) -> Self { self.flags .set(ComboBoxFlags::POPUP_ALIGN_LEFT, popup_align_left); self } /// Sets the combo box height. /// /// Default: `ComboBoxHeight::Regular` #[inline] pub fn height(mut self, height: ComboBoxHeight) -> Self { self.flags .set(ComboBoxFlags::HEIGHT_SMALL, height == ComboBoxHeight::Small); self.flags.set( ComboBoxFlags::HEIGHT_REGULAR, height == ComboBoxHeight::Regular, ); self.flags .set(ComboBoxFlags::HEIGHT_LARGE, height == ComboBoxHeight::Large); self.flags.set( ComboBoxFlags::HEIGHT_LARGEST, height == ComboBoxHeight::Largest, ); self } /// Sets the combo box preview mode. /// /// Default: `ComboBoxPreviewMode::Full` #[inline] pub fn preview_mode(mut self, preview_mode: ComboBoxPreviewMode) -> Self { self.flags.set( ComboBoxFlags::NO_ARROW_BUTTON, preview_mode == ComboBoxPreviewMode::Label, ); self.flags.set( ComboBoxFlags::NO_PREVIEW, preview_mode == ComboBoxPreviewMode::ArrowButton, ); self } /// Creates a combo box and starts appending to it. /// /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been /// rendered, the token must be ended by calling `.end()`. /// /// Returns `None` if the combo box is not open and no content should be rendered. #[must_use] pub fn begin<'ui>(self, ui: &Ui<'ui>) -> Option> { let should_render = unsafe { if let Some(preview_value) = self.preview_value { let (ptr_one, ptr_two) = ui.scratch_txt_two(self.label, preview_value); sys::igBeginCombo(ptr_one, ptr_two, self.flags.bits() as i32) } else { let ptr_one = ui.scratch_txt(self.label); sys::igBeginCombo(ptr_one, std::ptr::null(), self.flags.bits() as i32) } }; if should_render { Some(ComboBoxToken::new(ui)) } else { None } } /// Creates a combo box and runs a closure to construct the popup contents. /// Returns the result of the closure, if it is called. /// /// Note: the closure is not called if the combo box is not open. pub fn build R>(self, ui: &Ui, f: F) -> Option { self.begin(ui).map(|_combo| f()) } } create_token!( /// Tracks a combo box that can be ended by calling `.end()` /// or by dropping. pub struct ComboBoxToken<'ui>; /// Ends a combo box drop { sys::igEndCombo() } ); /// # Convenience functions impl<'ui> Ui<'ui> { /// Creates a combo box which can be appended to with `Selectable::new`. /// /// If you do not want to provide a preview, use [begin_combo_no_preview]. If you want /// to pass flags, use [begin_combo_with_flags]. /// /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been /// rendered, the token must be ended by calling `.end()`. /// /// Returns `None` if the combo box is not open and no content should be rendered. #[must_use] #[doc(alias = "BeginCombo")] pub fn begin_combo( &self, label: impl AsRef, preview_value: impl AsRef, ) -> Option> { self.begin_combo_with_flags(label, preview_value, ComboBoxFlags::empty()) } /// Creates a combo box which can be appended to with `Selectable::new`. /// /// If you do not want to provide a preview, use [begin_combo_no_preview]. /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been /// rendered, the token must be ended by calling `.end()`. /// /// Returns `None` if the combo box is not open and no content should be rendered. #[must_use] #[doc(alias = "BeginCombo")] pub fn begin_combo_with_flags( &self, label: impl AsRef, preview_value: impl AsRef, flags: ComboBoxFlags, ) -> Option> { self._begin_combo(label, Some(preview_value), flags) } /// Creates a combo box which can be appended to with `Selectable::new`. /// /// If you want to provide a preview, use [begin_combo]. If you want /// to pass flags, use [begin_combo_no_preview_with_flags]. /// /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been /// rendered, the token must be ended by calling `.end()`. /// /// Returns `None` if the combo box is not open and no content should be rendered. #[must_use] #[doc(alias = "BeginCombo")] pub fn begin_combo_no_preview(&self, label: impl AsRef) -> Option> { self.begin_combo_no_preview_with_flags(label, ComboBoxFlags::empty()) } /// Creates a combo box which can be appended to with `Selectable::new`. /// /// If you do not want to provide a preview, use [begin_combo_no_preview]. /// Returns `Some(ComboBoxToken)` if the combo box is open. After content has been /// rendered, the token must be ended by calling `.end()`. /// /// Returns `None` if the combo box is not open and no content should be rendered. #[must_use] #[doc(alias = "BeginCombo")] pub fn begin_combo_no_preview_with_flags( &self, label: impl AsRef, flags: ComboBoxFlags, ) -> Option> { self._begin_combo(label, Option::<&'static str>::None, flags) } /// This is the internal begin combo method that they all...eventually call. fn _begin_combo( &self, label: impl AsRef, preview_value: Option>, flags: ComboBoxFlags, ) -> Option> { let should_render = unsafe { let (ptr_one, ptr_two) = self.scratch_txt_with_opt(label, preview_value); sys::igBeginCombo(ptr_one, ptr_two, flags.bits() as i32) }; if should_render { Some(ComboBoxToken::new(self)) } else { None } } /// Builds a simple combo box for choosing from a slice of values #[doc(alias = "Combo")] pub fn combo( &self, label: impl AsRef, current_item: &mut usize, items: &[V], label_fn: L, ) -> bool where for<'b> L: Fn(&'b V) -> Cow<'b, str>, { use crate::widget::selectable::Selectable; let label_fn = &label_fn; let mut result = false; let preview_value = items.get(*current_item).map(label_fn); if let Some(_cb) = self._begin_combo(label, preview_value, ComboBoxFlags::empty()) { for (idx, item) in items.iter().enumerate() { let text = label_fn(item); let selected = idx == *current_item; if Selectable::new(&text).selected(selected).build(self) { *current_item = idx; result = true; } if selected { self.set_item_default_focus(); } } } result } /// Builds a simple combo box for choosing from a slice of values #[doc(alias = "Combo")] pub fn combo_simple_string( &self, label: impl AsRef, current_item: &mut usize, items: &[impl AsRef], ) -> bool { self.combo(label, current_item, items, |s| Cow::Borrowed(s.as_ref())) } // /// Builds a simple combo box for choosing from a slice of strings // #[doc(alias = "BeginCombo")] // pub fn build_simple_string(self, ui: &Ui, current_item: &mut usize, items: &[&S]) -> bool // where // S: AsRef + ?Sized, // { // self.build_simple(ui, current_item, items, &|&s| s.as_ref().into()) // } }