boing 0.7.0

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

//! A type-erased control.

use std::{cell::Cell, os::raw::c_void};

#[cfg(all(target_os = "windows", feature = "raw-window-handle"))]
use raw_window_handle::{
    HasRawDisplayHandle,
    HasRawWindowHandle,
    RawDisplayHandle,
    RawWindowHandle,
};

use crate::prelude::*;

impl Control {
    /// Creates a new `Control` from a pointer.
    ///
    /// By default, the new control is not a child of any parent control and will be destroyed on
    /// drop.
    ///
    /// # Safety
    ///
    /// `ptr` must point to a valid `uiControl`.
    #[inline]
    pub(crate) unsafe fn from_ptr<'ui>(ptr: *mut uiControl) -> Self {
        Self { ptr, is_child: Cell::new(false) }
    }
}

/// A type-erased control.
///
/// This type provides functionality common to all controls and is accessible via
/// [`DerefMut::deref_mut`] on a valid control. When this type is dropped, the control is destroyed.
#[derive(Debug, Widget)]
#[widget(handle = "uiControl")]
pub struct Control {
    ptr: *mut uiControl,
    is_child: Cell<bool>,
}

impl Drop for Control {
    fn drop(&mut self) {
        // *libui-ng* automatically frees children controls, but's it our responsibility to free
        // everything else.
        if !self.is_child.get() {
            let ptr = self.as_ptr();
            tracing::debug!("Destroying control @ {:#?}", ptr);
            unsafe { uiControlDestroy(ptr) };
        }
    }
}

impl Control {
    /// Determines if this control is visible.
    ///
    /// Controls are visible by default *except* for [`Window`](crate::Window)s, which are invisible
    /// by default.
    #[inline]
    pub fn is_visible(&self) -> bool {
        bool_from_libui(unsafe { uiControlVisible(self.as_ptr()) })
    }

    /// Determines if this control responds to user interaction.
    ///
    /// Controls are enabled by default.
    #[inline]
    pub fn is_enabled(&self) -> bool {
        bool_from_libui(unsafe { uiControlEnabled(self.as_ptr()) })
    }

    /// Determines if this control, and all of its parent controls, are enabled.
    ///
    /// If this control is not a child of a parent control, then this function is equivalent to
    /// [`is_enabled`](Self::is_enabled).
    #[inline]
    pub fn is_enabled_to_user(&self) -> bool {
        bool_from_libui(unsafe { uiControlEnabledToUser(self.as_ptr()) })
    }

    /// A handle to the underlying OS object.
    #[inline]
    pub fn native_handle(&self) -> *mut c_void {
        unsafe { uiControlHandle(self.as_ptr()) as *mut _ }
    }

    /// Makes this control [visible](Self::is_visible).
    #[inline]
    pub fn show(&self) {
        unsafe { uiControlShow(self.as_ptr()) };
    }

    /// Makes this control not [visible](Self::is_visible).
    #[inline]
    pub fn hide(&self) {
        unsafe { uiControlHide(self.as_ptr()) };
    }

    /// Makes this control [enabled](Self::is_enabled).
    #[inline]
    pub fn enable(&self) {
        unsafe { uiControlEnable(self.as_ptr()) }
    }

    /// Makes this control not [enabled](Self::is_enabled).
    #[inline]
    pub fn disable(&self) {
        unsafe { uiControlDisable(self.as_ptr()) }
    }

    /// Makes this control a child of another control.
    ///
    /// This method **must** be called on child controls or else a double-free will occur. This is
    /// because *libui-ng* automatically manages the memory of child controls, freeing them when
    /// their parents are destroyed.
    pub(crate) fn make_child(&self) {
        // It's not strictly unsound to call this function twice, but it's probably indicative of a
        // bug.
        assert!(!self.is_child.get(), "double make_child()");
        // *libui-ng* says windows can't be children.
        assert_ne!(
            unsafe { (*self.ptr).TypeSignature },
            uiWindowSignature,
            "uiWindows cannot be children",
        );

        self.is_child.set(true);
    }
}

// On Windows (IDK about other OSes), all controls are windows. Hence, we should provide raw window
// handles for controls. This makes *boing* controls automagically compatible with dependents of
// *raw-window-handle* such as *wgpu*.
#[cfg(all(target_os = "windows", feature = "raw-window-handle"))]
unsafe impl HasRawWindowHandle for Control {
    fn raw_window_handle(&self) -> RawWindowHandle {
        use windows::Win32::UI::WindowsAndMessaging as wm;

        let hwnd = self.native_handle();
        let mut handle = raw_window_handle::Win32WindowHandle::empty();
        handle.hinstance = unsafe {
            wm::GetWindowLongW(
                windows::Win32::Foundation::HWND(hwnd as isize),
                wm::GWL_HINSTANCE,
            )
        } as *mut _;
        handle.hwnd = hwnd;

        RawWindowHandle::Win32(handle)
    }
}

#[cfg(all(target_os = "windows", feature = "raw-window-handle"))]
unsafe impl HasRawDisplayHandle for Control {
    #[inline]
    fn raw_display_handle(&self) -> RawDisplayHandle {
        RawDisplayHandle::Windows(raw_window_handle::WindowsDisplayHandle::empty())
    }
}