boing 0.7.0

A safe wrapper over libui-ng-sys
Documentation
// SPDX-License-Identifier: MPL-2.0

//! A drop-down menu of mutually-exclusive selectable items.

use crate::prelude::*;

impl Ui {
    /// Creates a new [`Combobox`].
    pub fn create_combobox<'ui>(&'ui self) -> Result<&'ui mut Combobox, crate::Error> {
        unsafe { call_libui_new_fn!(ui: self, fn: uiNewCombobox() -> Combobox) }
    }
}

/// A drop-down menu of mutually-exclusive selectable items.
///
/// Comboboxes in *libui-ng* may only select a single item at a time. They are similar to
/// [radio buttons](crate::RadioButtons) in this regard, and in some cases they may be used
/// interchangeably, though radio buttons are better suited for four and fewer items.
#[subcontrol(handle = "uiCombobox")]
#[derive(Container)]
#[container(
    child_count = "uiComboboxNumItems",
    remove_child = "uiComboboxDelete",
    clear = "uiComboboxClear",
)]
pub struct Combobox;

impl<'ui> Combobox<'ui> {
    /// Appends a new item with the given text, returning its index.
    ///
    /// The new item is appended to the bottom of the menu.
    pub fn push_item(&self, text: impl Into<Vec<u8>>) -> Result<NonNegativeInt, crate::Error> {
        let index = self.child_count();
        let text = self.ui.make_cstring(text)?;
        unsafe { uiComboboxAppend(self.as_ptr(), text.as_ptr()) };

        Ok(index)
    }

    /// Inserts a new item with the given text before an existing item, returning the index of the
    /// new item.
    ///
    /// # Arguments
    ///
    /// `before` is the index of the existing item that follows the new item. The returned index is
    /// equivalent to `before`.
    ///
    /// # Panics
    ///
    /// Panics if `before` is out of bounds.
    pub fn insert_item(
        &self,
        before: impl Into<NonNegativeInt>,
        text: impl Into<Vec<u8>>,
    ) -> Result<NonNegativeInt, crate::Error> {
        let before = before.into();
        assert!(self.contains_child(before));
        let text = self.ui.make_cstring(text)?;
        unsafe { uiComboboxInsertAt(self.as_ptr(), before.to_libui(), text.as_ptr()) };

        Ok(before)
    }

    /// The index of the currently-selected item.
    ///
    /// If no item is selected, this returns `None`.
    pub fn selected_item(&self) -> Option<NonNegativeInt> {
        match unsafe { uiComboboxSelected(self.as_ptr()) } {
            -1 => None,
            it => Some(unsafe { NonNegativeInt::from_libui(it) }),
        }
    }

    /// Selects the item with the given index.
    ///
    /// This overrides any selections made by the user.
    ///
    /// # Panics
    ///
    /// Panics if `index` is out of bounds.
    pub fn select_item(&self, index: impl Into<NonNegativeInt>) {
        let index = index.into();
        assert!(self.contains_child(index));

        unsafe { uiComboboxSetSelected(self.as_ptr(), index.to_libui()) };
    }

    /// De-selects the currently-selected item.
    ///
    /// This overrides any selections made by the user. If no item is selected, this does nothing.
    #[inline]
    pub fn deselect_item(&self) {
        unsafe { uiComboboxSetSelected(self.as_ptr(), -1) };
    }

    /// Sets a callback for when the user selects an item.
    ///
    /// This callback is unset by default. This is not activated when
    /// [`select_item`](Self::select_item) is called.
    #[bind_callback(fn = "uiComboboxOnSelected")]
    pub fn on_item_selected(&self, f: fn()) {
        f();
    }
}