smudge-core 0.1.0

Widget tree, reactive bindings, layout, paint and damage tracking for the Smudge GUI library
Documentation
use super::*;
use proptest::prelude::*;

fn any_rect() -> impl Strategy<Value = Rect> {
    (
        -1_000.0f32..1_000.0,
        -1_000.0f32..1_000.0,
        -1_000.0f32..1_000.0,
        -1_000.0f32..1_000.0,
    )
        .prop_map(|(x0, y0, x1, y1)| Rect::new(x0, y0, x1, y1))
}

fn any_non_empty_rect() -> impl Strategy<Value = Rect> {
    (
        -1_000.0f32..1_000.0,
        -1_000.0f32..1_000.0,
        0.001f32..2_000.0,
        0.001f32..2_000.0,
    )
        .prop_map(|(x, y, width, height)| {
            Rect::from_origin_size(Point { x, y }, Size { width, height })
        })
}

fn corners(rect: Rect) -> [Point; 4] {
    [
        Point {
            x: rect.x0,
            y: rect.y0,
        },
        Point {
            x: rect.x1,
            y: rect.y0,
        },
        Point {
            x: rect.x0,
            y: rect.y1,
        },
        Point {
            x: rect.x1,
            y: rect.y1,
        },
    ]
}

fn contains_rect(outer: Rect, inner: Rect) -> bool {
    !inner.is_empty()
        && inner.x0 >= outer.x0
        && inner.y0 >= outer.y0
        && inner.x1 <= outer.x1
        && inner.y1 <= outer.y1
}

proptest! {
    #[test]
    fn union_contains_every_corner_of_both_rects(
        a in any_non_empty_rect(),
        b in any_non_empty_rect(),
    ) {
        let union = a.union(b);
        for corner in corners(a).into_iter().chain(corners(b)) {
            prop_assert!(
                union.contains(corner),
                "union {:?} of {:?} and {:?} misses {:?}",
                union,
                a,
                b,
                corner
            );
        }
    }

    #[test]
    fn intersect_is_empty_or_inside_both_rects(a in any_rect(), b in any_rect()) {
        let intersection = a.intersect(b);
        if !intersection.is_empty() {
            prop_assert!(
                contains_rect(a, intersection),
                "intersection {:?} of {:?} and {:?} escapes the first",
                intersection,
                a,
                b
            );
            prop_assert!(
                contains_rect(b, intersection),
                "intersection {:?} of {:?} and {:?} escapes the second",
                intersection,
                a,
                b
            );
        }
    }

    #[test]
    fn from_logical_covers_every_pixel_the_rect_touches(
        rect in (-16.0f32..48.0, -16.0f32..48.0, -16.0f32..48.0, -16.0f32..48.0)
            .prop_map(|(x0, y0, x1, y1)| Rect::new(x0, y0, x1, y1)),
        scale in 0.5f32..4.0,
        surface_width in 1u32..64,
        surface_height in 1u32..64,
    ) {
        let scaled = Rect::new(
            rect.x0 * scale,
            rect.y0 * scale,
            rect.x1 * scale,
            rect.y1 * scale,
        );
        let covered = PixelRect::from_logical(rect, scale, surface_width, surface_height);
        for y in 0..surface_height {
            for x in 0..surface_width {
                let pixel = Rect::new(x as f32, y as f32, x as f32 + 1.0, y as f32 + 1.0);
                if scaled.intersect(pixel).is_empty() {
                    continue;
                }
                let touched = PixelRect { x, y, width: 1, height: 1 };
                prop_assert!(
                    covered.is_some_and(|covered| covered.contains_rect(touched)),
                    "damage {:?} misses pixel {:?} touched by {:?} at scale {}",
                    covered,
                    (x, y),
                    scaled,
                    scale
                );
            }
        }
    }

    #[test]
    fn from_logical_never_extends_beyond_the_surface(
        rect in any_rect(),
        scale in 0.5f32..4.0,
        surface_width in 1u32..512,
        surface_height in 1u32..512,
    ) {
        if let Some(pixel_rect) = PixelRect::from_logical(rect, scale, surface_width, surface_height) {
            prop_assert!(pixel_rect.width > 0 && pixel_rect.height > 0, "empty damage {:?}", pixel_rect);
            prop_assert!(
                u64::from(pixel_rect.x) + u64::from(pixel_rect.width) <= u64::from(surface_width),
                "damage {:?} overflows the surface width {}",
                pixel_rect,
                surface_width
            );
            prop_assert!(
                u64::from(pixel_rect.y) + u64::from(pixel_rect.height) <= u64::from(surface_height),
                "damage {:?} overflows the surface height {}",
                pixel_rect,
                surface_height
            );
        }
    }
}

#[test]
fn union_ignores_an_empty_rect() {
    let rect = Rect::new(1.0, 2.0, 3.0, 4.0);
    assert_eq!(rect.union(Rect::default()), rect);
    assert_eq!(Rect::default().union(rect), rect);
    assert!(Rect::default().union(Rect::default()).is_empty());
}

#[test]
fn pixel_union_ignores_an_empty_rect() {
    let rect = PixelRect {
        x: 10,
        y: 20,
        width: 5,
        height: 5,
    };
    assert_eq!(PixelRect::default().union(rect), rect);
    assert_eq!(rect.union(PixelRect::default()), rect);
}

#[test]
fn from_logical_returns_none_for_an_unusable_scale() {
    let rect = Rect::new(0.0, 0.0, 10.0, 10.0);
    for scale in [0.0, -1.0, f32::NAN, f32::INFINITY] {
        assert_eq!(PixelRect::from_logical(rect, scale, 50, 50), None);
    }
}

#[test]
fn from_logical_returns_none_for_a_rect_outside_the_surface() {
    let right_of_surface = Rect::new(100.0, 100.0, 200.0, 200.0);
    let left_of_surface = Rect::new(-200.0, -200.0, -100.0, -100.0);
    assert_eq!(PixelRect::from_logical(right_of_surface, 1.0, 50, 50), None);
    assert_eq!(PixelRect::from_logical(left_of_surface, 1.0, 50, 50), None);
}

#[test]
fn from_logical_returns_none_for_a_non_finite_coordinate() {
    let nan_x0 = Rect::new(f32::NAN, 0.0, 10.0, 10.0);
    let infinite_x1 = Rect::new(0.0, 0.0, f32::INFINITY, 10.0);
    assert_eq!(PixelRect::from_logical(nan_x0, 1.0, 50, 50), None);
    assert_eq!(PixelRect::from_logical(infinite_x1, 1.0, 50, 50), None);
}

#[test]
fn from_logical_returns_none_for_a_zero_width_rect() {
    let zero_width = Rect::new(10.0, 10.0, 10.0, 20.0);
    let zero_height = Rect::new(10.0, 10.0, 20.0, 10.0);
    assert_eq!(PixelRect::from_logical(zero_width, 1.0, 50, 50), None);
    assert_eq!(PixelRect::from_logical(zero_height, 1.0, 50, 50), None);
}

#[test]
fn from_logical_clamps_a_partly_visible_rect() {
    let rect = Rect::new(-10.0, -10.0, 5.0, 5.0);
    assert_eq!(
        PixelRect::from_logical(rect, 1.0, 20, 20),
        Some(PixelRect {
            x: 0,
            y: 0,
            width: 5,
            height: 5,
        })
    );
}

#[test]
fn from_logical_rounds_outward_at_a_fractional_scale() {
    let rect = Rect::new(1.0, 1.0, 2.0, 2.0);
    assert_eq!(
        PixelRect::from_logical(rect, 0.5, 10, 10),
        Some(PixelRect {
            x: 0,
            y: 0,
            width: 1,
            height: 1,
        })
    );
}