mirui 0.14.3

A lightweight, no_std ECS-driven UI framework for embedded, desktop, and WebAssembly
Documentation
use super::Fixed;

/// Dimension specification for layout properties.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum Dimension {
    /// Fixed pixel value
    Px(Fixed),
    /// Percentage of parent size (0-100 stored as Fixed)
    Percent(Fixed),
    /// Determined by layout algorithm
    #[default]
    Auto,
    /// Sized to fit content
    Content,
}

impl Dimension {
    /// Resolve this dimension to a concrete Fixed value given the parent's size.
    /// - Px: returns the value directly
    /// - Percent: parent_size * percent / 100
    /// - Auto/Content: returns None (caller must handle)
    #[inline(always)]
    pub fn resolve(self, parent_size: Fixed) -> Option<Fixed> {
        match self {
            Self::Px(v) => Some(v),
            Self::Percent(pct) => Some(parent_size * pct / Fixed::from_int(100)),
            Self::Auto | Self::Content => None,
        }
    }

    #[inline]
    pub fn resolve_or(self, parent_size: Fixed, default: Fixed) -> Fixed {
        self.resolve(parent_size).unwrap_or(default)
    }

    /// Shorthand: `Dimension::px(100)` == `Dimension::Px(Fixed::from_int(100))`
    #[inline]
    pub const fn px(v: i32) -> Self {
        Self::Px(Fixed::from_int(v))
    }

    /// Shorthand: `Dimension::percent(50)` == `Dimension::Percent(Fixed::from_int(50))`
    #[inline]
    pub const fn percent(v: i32) -> Self {
        Self::Percent(Fixed::from_int(v))
    }
}

impl From<i32> for Dimension {
    #[inline]
    fn from(v: i32) -> Self {
        Self::Px(Fixed::from_int(v))
    }
}

impl From<u16> for Dimension {
    #[inline]
    fn from(v: u16) -> Self {
        Self::Px(v.into())
    }
}

impl From<Fixed> for Dimension {
    #[inline]
    fn from(v: Fixed) -> Self {
        Self::Px(v)
    }
}

impl core::ops::Add for Dimension {
    type Output = Self;
    /// Px + Px = Px, Percent + Percent = Percent, otherwise panics.
    #[inline]
    fn add(self, rhs: Self) -> Self {
        match (self, rhs) {
            (Self::Px(a), Self::Px(b)) => Self::Px(a + b),
            (Self::Percent(a), Self::Percent(b)) => Self::Percent(a + b),
            _ => self,
        }
    }
}

impl core::ops::Sub for Dimension {
    type Output = Self;
    #[inline]
    fn sub(self, rhs: Self) -> Self {
        match (self, rhs) {
            (Self::Px(a), Self::Px(b)) => Self::Px(a - b),
            (Self::Percent(a), Self::Percent(b)) => Self::Percent(a - b),
            _ => self,
        }
    }
}

impl core::ops::Mul<Fixed> for Dimension {
    type Output = Self;
    #[inline]
    fn mul(self, rhs: Fixed) -> Self {
        match self {
            Self::Px(v) => Self::Px(v * rhs),
            Self::Percent(v) => Self::Percent(v * rhs),
            other => other,
        }
    }
}

impl core::ops::Div<Fixed> for Dimension {
    type Output = Self;
    #[inline]
    fn div(self, rhs: Fixed) -> Self {
        match self {
            Self::Px(v) => Self::Px(v / rhs),
            Self::Percent(v) => Self::Percent(v / rhs),
            other => other,
        }
    }
}

/// 2D point in `Dimension` space — px / percent / auto. Resolves to a
/// concrete `(Fixed, Fixed)` pair against a parent size at use time.
#[derive(Clone, Copy, Debug, Default)]
pub struct DimPoint {
    pub x: Dimension,
    pub y: Dimension,
}

impl From<crate::types::Point> for DimPoint {
    fn from(p: crate::types::Point) -> Self {
        Self {
            x: Dimension::Px(p.x),
            y: Dimension::Px(p.y),
        }
    }
}

impl<X: Into<Dimension>, Y: Into<Dimension>> From<(X, Y)> for DimPoint {
    fn from((x, y): (X, Y)) -> Self {
        Self {
            x: x.into(),
            y: y.into(),
        }
    }
}

impl DimPoint {
    pub const ZERO: Self = Self {
        x: Dimension::Px(Fixed::ZERO),
        y: Dimension::Px(Fixed::ZERO),
    };

    /// `(50, 50)` percent → centre of any rect.
    pub const CENTER: Self = Self {
        x: Dimension::Percent(Fixed::from_int(50)),
        y: Dimension::Percent(Fixed::from_int(50)),
    };

    pub const fn px(x: i32, y: i32) -> Self {
        Self {
            x: Dimension::px(x),
            y: Dimension::px(y),
        }
    }

    pub const fn percent(x: i32, y: i32) -> Self {
        Self {
            x: Dimension::percent(x),
            y: Dimension::percent(y),
        }
    }

    /// Resolve against a `(parent_w, parent_h)` rect. `Auto` / `Content`
    /// fall back to zero — they're meaningless for a tap target.
    pub fn resolve(self, parent_w: Fixed, parent_h: Fixed) -> (Fixed, Fixed) {
        (
            self.x.resolve_or(parent_w, Fixed::ZERO),
            self.y.resolve_or(parent_h, Fixed::ZERO),
        )
    }
}

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

    #[test]
    fn dim_point_centre_resolves_to_half_rect() {
        let (x, y) = DimPoint::CENTER.resolve(Fixed::from_int(100), Fixed::from_int(40));
        assert_eq!(x.to_int(), 50);
        assert_eq!(y.to_int(), 20);
    }

    #[test]
    fn dim_point_px_passthrough() {
        let (x, y) = DimPoint::px(30, 10).resolve(Fixed::from_int(100), Fixed::from_int(100));
        assert_eq!(x.to_int(), 30);
        assert_eq!(y.to_int(), 10);
    }

    #[test]
    fn resolve_px() {
        let d = Dimension::Px(Fixed::from_int(50));
        assert_eq!(d.resolve(Fixed::from_int(200)), Some(Fixed::from_int(50)));
    }

    #[test]
    fn resolve_percent() {
        let d = Dimension::Percent(Fixed::from_int(50));
        let result = d.resolve(Fixed::from_int(200)).unwrap();
        assert_eq!(result.to_int(), 100);
    }

    #[test]
    fn resolve_percent_large_parent() {
        // Pre-i64-div, these parents all overflowed to negative Fixed.
        for parent in [640, 1280, 1920, 4096, 8192] {
            let got = Dimension::percent(100)
                .resolve(Fixed::from_int(parent))
                .unwrap();
            assert_eq!(got.to_int(), parent, "Percent(100) on parent={parent}");
        }
    }

    #[test]
    fn resolve_percent_fraction_large_parent() {
        let d = Dimension::percent(33);
        let got = d.resolve(Fixed::from_int(1200)).unwrap();
        assert_eq!(got.to_int(), 396);
    }

    #[test]
    fn resolve_auto() {
        assert_eq!(Dimension::Auto.resolve(Fixed::from_int(200)), None);
    }

    #[test]
    fn from_i32() {
        let d: Dimension = 100.into();
        assert_eq!(d, Dimension::Px(Fixed::from_int(100)));
    }

    #[test]
    fn add_px() {
        let a = Dimension::Px(Fixed::from_int(10));
        let b = Dimension::Px(Fixed::from_int(20));
        assert_eq!((a + b), Dimension::Px(Fixed::from_int(30)));
    }

    #[test]
    fn mul_fixed() {
        let d = Dimension::Px(Fixed::from_int(10));
        let result = d * Fixed::from_f32(1.5);
        assert_eq!(result, Dimension::Px(Fixed::from_f32(15.0)));
    }
}