boing 0.7.0

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

//! An RGBA image.

use std::ops::Deref;

use crate::prelude::*;

impl Ui {
    /// Creates a new [`Image`] with the given size.
    ///
    /// # Arguments
    ///
    /// `width` and `height` are given in points, which is commonly the pixel size of the `1x`
    /// scaled image.
    ///
    /// # Panics
    ///
    /// Panics if `width` or `height` are negative or [non-normal](f64::is_normal).
    pub fn create_image<'ui>(
        &'ui self,
        width: f64,
        height: f64,
    ) -> Result<&'ui mut Image, crate::Error> {
        let check_dim = |dim: f64| assert!(dim.is_normal() && dim.is_sign_positive());
        check_dim(width);
        check_dim(height);

        // SAFETY: `width` and `height` are probably OK.
        // FIXME: what are the upper limits for `width` and `height`?
        unsafe { call_libui_new_fn!(ui: self, fn: uiNewImage(width, height) -> Image) }
    }
}

impl Image {
    unsafe fn from_ptr(_: &Ui, ptr: *mut uiImage) -> Self {
        Self { ptr }
    }
}

/// An RGBA image.
#[derive(Widget)]
#[widget(handle = "uiImage")]
pub struct Image {
    ptr: *mut uiImage,
}

impl Drop for Image {
    #[inline]
    fn drop(&mut self) {
        unsafe { uiFreeImage(self.as_ptr()) };
    }
}

impl Image {
    pub fn push(&self, repr: Repr) {
        let [row_stride, height] = [repr.row_stride, repr.height]
            .map(|dim| usize::try_from(dim.as_u32()).unwrap());
        // SAFETY: `pixels` must be at least `row_stride * height` bytes.
        assert!(repr.pixels.len() >= (row_stride * height));
        // SAFETY: `pixels` is dropped at the end of scope, but that's OK as *libui-ng* copies it.
        let pixels = repr.pixels.as_mut_ptr().cast();

        unsafe {
            uiImageAppend(
                self.as_ptr(),
                pixels,
                repr.width.to_libui(),
                repr.height.to_libui(),
                repr.row_stride.to_libui(),
            );
        }
    }
}

pub enum IntoReprError {
    /// The width of the given representation exceeds that allowed by *libui-ng*.
    TooWide,
    /// The height of the given representation exceeds that allowed by *libui-ng*.
    TooTall,
    /// The row stride of the given representation exceeds that allowed by *libui-ng*.
    StrideTooWide,

}

// FIXME: can we generalize this impl more? Maybe impl on a trait instead of a struct?
#[cfg(feature = "image")]
impl<'a, C> TryFrom<&'a mut image::ImageBuffer<image::Rgba<u8>, C>> for Repr<'a>
where
    C: AsRef<[u8]> + Deref<Target = [u8]> + DerefMut,
{
    type Error = IntoReprError;

    fn try_from(buf: &'a mut image::ImageBuffer<image::Rgba<u8>, C>) -> Result<Self, Self::Error> {
        let (width, height) = buf.dimensions();
        let [maybe_width, maybe_height] = [width, height].map(|dim| NonNegativeInt::try_from(dim));
        let width = maybe_width.map_err(|_| IntoReprError::TooWide)?;
        let height = maybe_height.map_err(|_| IntoReprError::TooTall)?;

        let row_stride = NonNegativeInt::try_from(buf.as_flat_samples().layout.width_stride)
            .map_err(|_| IntoReprError::StrideTooWide)?;

        Ok(Self {
            pixels: buf.deref_mut(),
            width,
            height,
            row_stride,
        })
    }
}

pub struct Repr<'a> {
    /// An array of 8-bit RGBA components.
    pub pixels: &'a mut [u8],
    /// The width, in pixels.
    pub width: NonNegativeInt,
    /// The height, in pixels.
    pub height: NonNegativeInt,
    /// The row stride, in bytes.
    pub row_stride: NonNegativeInt,
}