boing 0.7.0

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

//! An item within a [`Menu`].

use super::Menu;
use crate::prelude::*;

// Contrary to intuition, it is perfectly acceptable for the lifetime of a menu item to be different
// or even longer than that of its parent menu. This is because the [`Drop`] implementation of
// [`Menu`] is a stub, so drop order is irrelevant.
//
// However, the lifetime of a menu item *is* bound to the [`Ui`] object that created its menu, as
// menu items contain callbacks that are executed in [`Ui::run`]. For this reason, like normal
// controls, menu items are allocated in the [`Ui`] object's arena with [`Ui::alloc_object`].

impl<'ui> Menu<'ui> {
    /// Appends a new item with the given text and returns it.
    pub fn push_item(&self, text: impl Into<Vec<u8>>) -> Result<&'ui mut Item, crate::Error> {
        let text = self.ui.make_cstring(text)?;

        unsafe {
            call_libui_new_fn!(
                ui: self.ui,
                fn: uiMenuAppendItem(self.as_ptr(), text.as_ptr()) -> Item,
            )
        }
    }

    /// Appends a new check item with the given text and returns it.
    pub fn push_check_item(&self, text: impl Into<Vec<u8>>) -> Result<&'ui mut Item, crate::Error> {
        let text = self.ui.make_cstring(text)?;

        unsafe {
            call_libui_new_fn!(
                ui: self.ui,
                fn: uiMenuAppendCheckItem(self.as_ptr(), text.as_ptr()) -> Item,
            )
        }
    }

    /// Appends a new item that exits the application when clicked and returns it.
    ///
    /// The text of the item is "Quit" in English, and it is pre-configured with an
    /// [`Item::on_clicked`] callback to exit the application.
    ///
    /// It is idiomatic to append this item to a menu named "File".
    pub fn push_quit_item(&self) -> Result<&'ui mut Item, crate::Error> {
        unsafe { call_libui_new_fn!(ui: self.ui, fn: uiMenuAppendQuitItem(self.as_ptr()) -> Item) }
    }

    /// Appends a new item with the text "Preferences..." in English and returns it.
    ///
    /// This item is ideal for opening a preferences window that allows users to view and modify
    /// options related to the application's functionality. See [`Item::on_clicked`] for information
    /// on how this can be implemented.
    ///
    /// It is idiomatic to append this item to a menu named "Edit".
    pub fn push_preferences_item(&self) -> Result<&'ui mut Item, crate::Error> {
        unsafe {
            call_libui_new_fn!(ui: self.ui, fn: uiMenuAppendPreferencesItem(self.as_ptr()) -> Item)
        }
    }

    /// Appends a new item with the text "About" in English.
    ///
    /// This item is ideal for opening a window that tells users about the application when clicked.
    /// See [`Item::on_clicked`] for information on how this can be implemented.
    ///
    /// It is idiomatic to append this item to a menu named "Help".
    pub fn push_about_item(&self) -> Result<&'ui mut Item, crate::Error> {
        unsafe { call_libui_new_fn!(ui: self.ui, fn: uiMenuAppendAboutItem(self.as_ptr()) -> Item) }
    }
}

impl<'ui> Item<'ui> {
    /// Creates a new `Item`.
    unsafe fn from_ptr(ui: &'ui Ui, ptr: *mut uiMenuItem) -> Self {
        Self { ui, ptr }
    }
}

/// An item within a [`Menu`].
///
/// # Examples
///
/// ```no_run
/// # fn main() -> Result<(), boing::Error> {
/// use boing::Menu;
/// # use boing::Ui;
/// #
/// # let ui = Ui::new()?;
/// let menu: Menu;
/// # menu = ui.create_menu("")?;
///
/// let checkable = menu.push_new_item("Checkable")?;
/// checkable.set_checked(true);
/// checkable.on_clicked(|item| {
///     // Toggle the item.
///     item.set_checked(!item.is_checked());
/// });
///
/// let disabled = menu.push_new_item("Disabled")?;
/// disabled.disable();
/// #
/// # Ok(())
/// # }
/// ```
#[derive(Widget)]
#[widget(handle = "uiMenuItem")]
pub struct Item<'ui> {
    ui: &'ui Ui,
    ptr: *mut uiMenuItem,
}

impl<'ui> Item<'ui> {
    /// Makes this item interactive.
    ///
    /// Items are enabled by default.
    pub fn enable(&self) {
        unsafe { uiMenuItemEnable(self.as_ptr()) };
    }

    /// Makes this item not interactive.
    ///
    /// By convention, the item is greyed-out as a visual indicator.
    pub fn disable(&self) {
        unsafe { uiMenuItemDisable(self.as_ptr()) };
    }

    /// Sets a callback for when the user clicks this item.
    ///
    /// This callback is unset by default.
    #[bind_callback(fn = "uiMenuItemOnClicked")]
    pub fn on_clicked(&self, f: fn(*mut uiWindow)) {
        f();
    }

    /// Determines if this item is checked.
    ///
    /// Items are not checked by default.
    pub fn is_checked(&self) -> bool {
        bool_from_libui(unsafe { uiMenuItemChecked(self.as_ptr()) })
    }

    /// Sets whether or not this item is checked.
    pub fn set_checked(&self, value: bool) {
        unsafe { uiMenuItemSetChecked(self.as_ptr(), value.into()) };
    }
}