cotis-utils 0.1.0-alpha

Modular Rust UI framework core
Documentation
#[cfg(feature = "serialization")]
use serde::{Deserialize, Serialize};

/// 2D vector in Cotis pixel space.
///
/// Coordinates are interpreted as screen-space values (origin in the top-left,
/// positive Y downward) by the default Cotis layout/render pipeline.
///
/// # Serialization
///
/// When the `serialization` feature is enabled this type derives
/// `serde::Serialize` and `serde::Deserialize`.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub struct Vector2 {
    /// Horizontal component in pixels.
    pub x: f32,
    /// Vertical component in pixels.
    pub y: f32,
}

impl Vector2 {
    /// Creates a new vector from `x` and `y` components.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use cotis_utils::math::Vector2;
    ///
    /// let p = Vector2::new(10.0, 20.0);
    /// assert_eq!(p.x, 10.0);
    /// assert_eq!(p.y, 20.0);
    /// ```
    pub fn new(x: f32, y: f32) -> Self {
        Self { x, y }
    }
}

impl From<(f32, f32)> for Vector2 {
    fn from(value: (f32, f32)) -> Self {
        Self::new(value.0, value.1)
    }
}

/// Width/height pair in pixels.
///
/// # Serialization
///
/// When the `serialization` feature is enabled this type derives
/// `serde::Serialize` and `serde::Deserialize`.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(feature = "serialization", derive(Serialize, Deserialize))]
pub struct Dimensions {
    /// Width in pixels.
    pub width: f32,
    /// Height in pixels.
    pub height: f32,
}

impl Dimensions {
    /// Creates dimensions from width and height in pixels.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use cotis_utils::math::Dimensions;
    ///
    /// let size = Dimensions::new(800.0, 600.0);
    /// assert_eq!(size.width, 800.0);
    /// assert_eq!(size.height, 600.0);
    /// ```
    pub fn new(width: f32, height: f32) -> Self {
        Self { width, height }
    }
}

impl From<(f32, f32)> for Dimensions {
    /// Builds [`Dimensions`] from `(width, height)`.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use cotis_utils::math::Dimensions;
    ///
    /// let size = Dimensions::from((320.0, 240.0));
    /// assert_eq!(size.width, 320.0);
    /// assert_eq!(size.height, 240.0);
    /// ```
    fn from(value: (f32, f32)) -> Self {
        Self::new(value.0, value.1)
    }
}

/// Axis-aligned rectangle in Cotis pixel space.
///
/// This struct is commonly used for hit-testing, clipping and debug overlays.
///
/// # Serialization
///
/// When the `serialization` feature is enabled this type derives
/// `serde::Serialize` and `serde::Deserialize`.
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(
    feature = "serialization",
    derive(serde::Serialize, serde::Deserialize)
)]
pub struct BoundingBox {
    /// Left coordinate in pixels.
    pub x: f32,
    /// Top coordinate in pixels.
    pub y: f32,
    /// Width in pixels.
    pub width: f32,
    /// Height in pixels.
    pub height: f32,
}

impl BoundingBox {
    /// Creates a new bounding box in pixel coordinates.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use cotis_utils::math::BoundingBox;
    ///
    /// let bounds = BoundingBox::new(8.0, 16.0, 100.0, 40.0);
    /// assert_eq!(bounds.width, 100.0);
    /// assert_eq!(bounds.height, 40.0);
    /// ```
    pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
        Self {
            x,
            y,
            width,
            height,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn vector2_and_dimensions_from_tuple() {
        let vector = Vector2::from((3.0, 4.0));
        assert_eq!(vector.x, 3.0);
        assert_eq!(vector.y, 4.0);

        let size = Dimensions::from((800.0, 600.0));
        assert_eq!(size.width, 800.0);
        assert_eq!(size.height, 600.0);
    }

    #[cfg(feature = "serialization")]
    #[test]
    fn math_types_roundtrip_through_json() {
        use serde_json;

        let vector = Vector2::new(1.0, 2.0);
        let decoded: Vector2 =
            serde_json::from_str(&serde_json::to_string(&vector).unwrap()).unwrap();
        assert_eq!(decoded, vector);

        let size = Dimensions::new(10.0, 20.0);
        let decoded: Dimensions =
            serde_json::from_str(&serde_json::to_string(&size).unwrap()).unwrap();
        assert_eq!(decoded, size);

        let bounds = BoundingBox::new(1.0, 2.0, 3.0, 4.0);
        let decoded: BoundingBox =
            serde_json::from_str(&serde_json::to_string(&bounds).unwrap()).unwrap();
        assert_eq!(decoded, bounds);
    }
}