buoyant 0.7.0-alpha.1

SwiftUI-like UIs in Rust for embedded devices
Documentation
use crate::primitives::{
    Point, Size,
    geometry::{PathEl, Rectangle, Shape, ShapePathIter},
    transform::{CoordinateSpaceTransform, LinearTransform},
};

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Line {
    pub start: Point,
    pub end: Point,
}

impl Line {
    #[must_use]
    pub const fn new(start: Point, end: Point) -> Self {
        Self { start, end }
    }
}

impl Shape for Line {
    type PathElementsIter<'iter>
        = ShapePathIter<2>
    where
        Self: 'iter;

    fn path_elements(&self, _tolerance: u16) -> Self::PathElementsIter<'_> {
        let elements = [PathEl::MoveTo(self.start), PathEl::LineTo(self.end)];
        ShapePathIter::new(elements)
    }

    fn bounding_box(&self) -> Rectangle {
        let min_x = self.start.x.min(self.end.x);
        let min_y = self.start.y.min(self.end.y);

        Rectangle::new(
            Point::new(min_x, min_y),
            Size::new(
                self.start.x.abs_diff(self.end.x),
                self.start.y.abs_diff(self.end.y),
            ),
        )
    }

    fn as_line(&self) -> Option<Line> {
        Some(self.clone())
    }
}

impl CoordinateSpaceTransform for Line {
    fn applying(&self, transform: &LinearTransform) -> Self {
        Self {
            start: self.start.applying(transform),
            end: self.end.applying(transform),
        }
    }

    fn applying_inverse(&self, transform: &LinearTransform) -> Self {
        Self {
            start: self.start.applying_inverse(transform),
            end: self.end.applying_inverse(transform),
        }
    }
}

#[cfg(feature = "embedded-graphics")]
impl From<Line> for embedded_graphics::primitives::Line {
    fn from(value: Line) -> Self {
        Self::new(value.start.into(), value.end.into())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::primitives::transform::LinearTransform;

    #[test]
    fn applying_transform() {
        let line = Line::new(Point::new(10, 20), Point::new(30, 40));
        let transform = LinearTransform::new(Point::new(5, 10), 2.0);

        let transformed = line.applying(&transform);

        assert_eq!(transformed.start, Point::new(25, 50));
        assert_eq!(transformed.end, Point::new(65, 90));
    }

    #[test]
    fn applying_inverse_transform() {
        let line = Line::new(Point::new(50, 80), Point::new(70, 100));
        let transform = LinearTransform::new(Point::new(5, 10), 2.0);

        let inverse_transformed = line.applying_inverse(&transform);

        assert_eq!(inverse_transformed.start, Point::new(22, 35));
        assert_eq!(inverse_transformed.end, Point::new(32, 45));
    }

    #[test]
    fn transform_roundtrip() {
        let original = Line::new(Point::new(16, 24), Point::new(32, 48));
        let transform = LinearTransform::new(Point::new(4, 8), 2.0);

        let transformed = original.applying(&transform).applying_inverse(&transform);

        assert_eq!(transformed.start, original.start);
        assert_eq!(transformed.end, original.end);
    }

    #[test]
    fn identity_transform() {
        let line = Line::new(Point::new(100, 200), Point::new(150, 250));
        let identity = LinearTransform::identity();

        let transformed = line.applying(&identity);

        assert_eq!(transformed, line);
    }
}