dear-imgui-rs 0.13.0

High-level Rust bindings to Dear ImGui v1.92.7 with docking, WGPU/GL backends, and extensions (ImPlot/ImPlot3D, ImNodes, ImGuizmo, file browser, reflection-based UI)
Documentation
//! Combo boxes
//!
//! Single-selection dropdowns with optional height and popup alignment flags.
//! Builders provide both string and custom item sources.
//!
use std::borrow::Cow;

use crate::sys;
use crate::ui::Ui;
use crate::widget::{ComboBoxFlags, ComboBoxHeight, ComboBoxOptions, ComboBoxPreviewMode};

/// # Combo Box Widgets
impl Ui {
    /// 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]
    #[doc(alias = "BeginCombo")]
    pub fn begin_combo(
        &self,
        label: impl AsRef<str>,
        preview_value: impl AsRef<str>,
    ) -> Option<ComboBoxToken<'_>> {
        self.begin_combo_with_flags(label, preview_value, ComboBoxFlags::NONE)
    }

    /// Creates a combo box with flags 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]
    #[doc(alias = "BeginCombo")]
    pub fn begin_combo_with_flags(
        &self,
        label: impl AsRef<str>,
        preview_value: impl AsRef<str>,
        flags: impl Into<ComboBoxOptions>,
    ) -> Option<ComboBoxToken<'_>> {
        let options = flags.into();
        options.validate("Ui::begin_combo_with_flags()");
        let (label_ptr, preview_ptr) = self.scratch_txt_two(label, preview_value);

        let should_render = unsafe { sys::igBeginCombo(label_ptr, preview_ptr, options.raw()) };

        if should_render {
            Some(ComboBoxToken::new(self))
        } else {
            None
        }
    }

    /// Creates a combo box without preview value.
    ///
    /// 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<str>) -> Option<ComboBoxToken<'_>> {
        self.begin_combo_no_preview_with_flags(label, ComboBoxFlags::NONE)
    }

    /// Creates a combo box without preview value and 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_with_flags(
        &self,
        label: impl AsRef<str>,
        flags: impl Into<ComboBoxOptions>,
    ) -> Option<ComboBoxToken<'_>> {
        let mut options = flags.into();
        options.preview_mode = ComboBoxPreviewMode::NoPreview;
        options.validate("Ui::begin_combo_no_preview_with_flags()");
        let label_ptr = self.scratch_txt(label);

        let should_render =
            unsafe { sys::igBeginCombo(label_ptr, std::ptr::null(), options.raw()) };

        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<V, L>(
        &self,
        label: impl AsRef<str>,
        current_item: &mut usize,
        items: &[V],
        label_fn: L,
    ) -> bool
    where
        for<'b> L: Fn(&'b V) -> Cow<'b, str>,
    {
        let label_fn = &label_fn;
        let mut result = false;
        let preview_value = items.get(*current_item).map(label_fn);

        if let Some(combo_token) = self.begin_combo(
            label,
            preview_value.as_ref().map(|s| s.as_ref()).unwrap_or(""),
        ) {
            for (idx, item) in items.iter().enumerate() {
                let is_selected = idx == *current_item;
                if is_selected {
                    self.set_item_default_focus();
                }

                let clicked = self.selectable(label_fn(item).as_ref());

                if clicked {
                    *current_item = idx;
                    result = true;
                }
            }
            combo_token.end();
        }

        result
    }

    /// Builds a simple combo box using an `i32` index (ImGui-style).
    ///
    /// This is useful when you want to represent \"no selection\" with `-1`, matching Dear ImGui's
    /// `Combo()` API.
    #[doc(alias = "Combo")]
    pub fn combo_i32<V, L>(
        &self,
        label: impl AsRef<str>,
        current_item: &mut i32,
        items: &[V],
        label_fn: L,
    ) -> bool
    where
        for<'b> L: Fn(&'b V) -> Cow<'b, str>,
    {
        let label_fn = &label_fn;
        let mut result = false;

        let preview_value = if *current_item >= 0 {
            items.get(*current_item as usize).map(|v| label_fn(v))
        } else {
            None
        };

        if let Some(combo_token) = self.begin_combo(
            label,
            preview_value.as_ref().map(|s| s.as_ref()).unwrap_or(""),
        ) {
            for (idx, item) in items.iter().enumerate() {
                if idx > i32::MAX as usize {
                    break;
                }
                let idx_i32 = idx as i32;
                let is_selected = idx_i32 == *current_item;
                if is_selected {
                    self.set_item_default_focus();
                }

                let clicked = self.selectable(label_fn(item).as_ref());
                if clicked {
                    *current_item = idx_i32;
                    result = true;
                }
            }
            combo_token.end();
        }

        result
    }

    /// Builds a simple combo box for choosing from a slice of strings
    #[doc(alias = "Combo")]
    pub fn combo_simple_string(
        &self,
        label: impl AsRef<str>,
        current_item: &mut usize,
        items: &[impl AsRef<str>],
    ) -> 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 using an `i32` index.
    #[doc(alias = "Combo")]
    pub fn combo_simple_string_i32(
        &self,
        label: impl AsRef<str>,
        current_item: &mut i32,
        items: &[impl AsRef<str>],
    ) -> bool {
        self.combo_i32(label, current_item, items, |s| Cow::Borrowed(s.as_ref()))
    }

    /// Sets the default focus for the next item
    pub fn set_item_default_focus(&self) {
        unsafe {
            sys::igSetItemDefaultFocus();
        }
    }
}

/// Builder for a combo box widget
#[derive(Clone, Debug)]
#[must_use]
pub struct ComboBox<'ui, Label, Preview = &'static str> {
    pub label: Label,
    pub preview_value: Option<Preview>,
    pub options: ComboBoxOptions,
    pub ui: &'ui Ui,
}

impl<'ui, Label: AsRef<str>> ComboBox<'ui, Label> {
    /// Sets the preview value
    pub fn preview_value<P: AsRef<str>>(self, preview: P) -> ComboBox<'ui, Label, P> {
        ComboBox {
            label: self.label,
            preview_value: Some(preview),
            options: self.options,
            ui: self.ui,
        }
    }

    /// Sets the flags
    pub fn flags(mut self, flags: ComboBoxFlags) -> Self {
        self.options.flags = flags;
        self
    }

    /// Sets the popup height policy.
    pub fn height(mut self, height: ComboBoxHeight) -> Self {
        self.options.height = Some(height);
        self
    }

    /// Sets the preview/arrow layout.
    pub fn preview_mode(mut self, mode: ComboBoxPreviewMode) -> Self {
        self.options.preview_mode = mode;
        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(self) -> Option<ComboBoxToken<'ui>> {
        self.options.validate("ComboBox::begin()");
        let (label_ptr, preview_ptr) = self
            .ui
            .scratch_txt_with_opt(self.label.as_ref(), self.preview_value.as_deref());

        let should_render =
            unsafe { sys::igBeginCombo(label_ptr, preview_ptr, self.options.raw()) };

        if should_render {
            Some(ComboBoxToken::new(self.ui))
        } else {
            None
        }
    }
}

/// Tracks a combo box that can be ended by calling `.end()` or by dropping
#[must_use]
pub struct ComboBoxToken<'ui> {
    _ui: &'ui Ui,
}

impl<'ui> ComboBoxToken<'ui> {
    /// Creates a new combo box token
    fn new(ui: &'ui Ui) -> Self {
        ComboBoxToken { _ui: ui }
    }

    /// Ends the combo box
    pub fn end(self) {
        // The drop implementation will handle the actual ending
    }
}

impl<'ui> Drop for ComboBoxToken<'ui> {
    fn drop(&mut self) {
        unsafe {
            sys::igEndCombo();
        }
    }
}