#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Point {
x: f32,
y: f32,
}
impl Default for Point {
fn default() -> Self {
Self::zero()
}
}
impl Point {
pub fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
pub fn zero() -> Self {
Self { x: 0.0, y: 0.0 }
}
pub fn x(self) -> f32 {
self.x
}
pub fn y(self) -> f32 {
self.y
}
pub fn with_x(mut self, x: f32) -> Self {
self.x = x;
self
}
pub fn with_y(mut self, y: f32) -> Self {
self.y = y;
self
}
pub fn is_zero(self) -> bool {
self.x == 0.0 && self.y == 0.0
}
pub fn add_point(self, other: Point) -> Self {
Self {
x: self.x + other.x,
y: self.y + other.y,
}
}
pub fn sub_point(self, other: Point) -> Self {
Self {
x: self.x - other.x,
y: self.y - other.y,
}
}
pub fn midpoint(self, other: Point) -> Self {
Self {
x: (self.x + other.x) / 2.0,
y: (self.y + other.y) / 2.0,
}
}
pub fn hypot(self) -> f32 {
self.x.hypot(self.y)
}
pub fn scale(self, factor: f32) -> Self {
Self {
x: self.x * factor,
y: self.y * factor,
}
}
pub fn abs(self) -> Self {
Self {
x: self.x.abs(),
y: self.y.abs(),
}
}
pub fn to_bounds(self, size: Size) -> Bounds {
Bounds::new_from_center(self, size)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Size {
width: f32,
height: f32,
}
impl Default for Size {
fn default() -> Self {
Self::zero()
}
}
impl Size {
pub fn new(width: f32, height: f32) -> Self {
Self { width, height }
}
pub fn zero() -> Self {
Self {
width: 0.0,
height: 0.0,
}
}
pub fn width(self) -> f32 {
self.width
}
pub fn height(self) -> f32 {
self.height
}
pub fn max(self, other: Size) -> Self {
Self {
width: self.width.max(other.width),
height: self.height.max(other.height),
}
}
pub fn add_padding(self, insets: Insets) -> Self {
Self {
width: self.width + insets.horizontal_sum(),
height: self.height + insets.vertical_sum(),
}
}
pub fn scale(self, factor: f32) -> Self {
Self {
width: self.width * factor,
height: self.height * factor,
}
}
pub fn is_zero(self) -> bool {
self.width == 0.0 && self.height == 0.0
}
pub fn merge_horizontal(self, other: Size) -> Self {
Self {
width: self.width + other.width,
height: self.height.max(other.height),
}
}
pub fn merge_vertical(self, other: Size) -> Self {
Self {
width: self.width.max(other.width),
height: self.height + other.height,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct Bounds {
min_x: f32,
min_y: f32,
max_x: f32,
max_y: f32,
}
impl Bounds {
pub fn new_from_center(center: Point, size: Size) -> Self {
let half_width = size.width / 2.0;
let half_height = size.height / 2.0;
Self {
min_x: center.x - half_width,
min_y: center.y - half_height,
max_x: center.x + half_width,
max_y: center.y + half_height,
}
}
pub fn new_from_top_left(top_left: Point, size: Size) -> Self {
Self {
min_x: top_left.x,
min_y: top_left.y,
max_x: top_left.x + size.width,
max_y: top_left.y + size.height,
}
}
pub fn min_x(self) -> f32 {
self.min_x
}
pub fn min_y(self) -> f32 {
self.min_y
}
#[allow(dead_code)]
pub fn max_x(self) -> f32 {
self.max_x
}
pub fn max_y(self) -> f32 {
self.max_y
}
pub fn center(self) -> Point {
Point::new(
(self.min_x + self.max_x) / 2.0,
(self.min_y + self.max_y) / 2.0,
)
}
pub fn with_max_y(mut self, max_y: f32) -> Self {
self.max_y = max_y;
self
}
pub fn width(self) -> f32 {
self.max_x - self.min_x
}
pub fn height(self) -> f32 {
self.max_y - self.min_y
}
pub fn min_point(self) -> Point {
Point {
x: self.min_x,
y: self.min_y,
}
}
pub fn to_size(self) -> Size {
Size {
width: self.width(),
height: self.height(),
}
}
pub fn merge(&self, other: &Self) -> Self {
Self {
min_x: self.min_x.min(other.min_x),
min_y: self.min_y.min(other.min_y),
max_x: self.max_x.max(other.max_x),
max_y: self.max_y.max(other.max_y),
}
}
pub fn translate(&self, offset: Point) -> Self {
Self {
min_x: self.min_x + offset.x,
min_y: self.min_y + offset.y,
max_x: self.max_x + offset.x,
max_y: self.max_y + offset.y,
}
}
pub fn inverse_translate(&self, offset: Point) -> Self {
Self {
min_x: self.min_x - offset.x,
min_y: self.min_y - offset.y,
max_x: self.max_x - offset.x,
max_y: self.max_y - offset.y,
}
}
pub fn add_padding(&self, insets: Insets) -> Self {
Self {
min_x: self.min_x - insets.left(),
min_y: self.min_y - insets.top(),
max_x: self.max_x + insets.right(),
max_y: self.max_y + insets.bottom(),
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct Insets {
top: f32,
right: f32,
bottom: f32,
left: f32,
}
impl Insets {
pub fn new(top: f32, right: f32, bottom: f32, left: f32) -> Self {
Self {
top,
right,
bottom,
left,
}
}
pub fn uniform(value: f32) -> Self {
Self {
top: value,
right: value,
bottom: value,
left: value,
}
}
pub fn top(self) -> f32 {
self.top
}
pub fn right(self) -> f32 {
self.right
}
pub fn bottom(self) -> f32 {
self.bottom
}
pub fn left(self) -> f32 {
self.left
}
pub fn with_top(self, top: f32) -> Self {
Self { top, ..self }
}
pub fn horizontal_sum(self) -> f32 {
self.left + self.right
}
pub fn vertical_sum(self) -> f32 {
self.top + self.bottom
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_point_new() {
let point = Point::new(3.5, 4.2);
assert_eq!(point.x(), 3.5);
assert_eq!(point.y(), 4.2);
}
#[test]
fn test_point_zero() {
let point = Point::zero();
assert_eq!(point.x(), 0.0);
assert_eq!(point.y(), 0.0);
assert!(point.is_zero());
}
#[test]
fn test_point_default() {
assert_eq!(Point::default(), Point::zero());
}
#[test]
fn test_point_is_zero() {
assert!(Point::new(0.0, 0.0).is_zero());
assert!(!Point::new(1.0, 0.0).is_zero());
assert!(!Point::new(0.0, 1.0).is_zero());
assert!(!Point::new(1.0, 1.0).is_zero());
}
#[test]
fn test_point_add() {
let p1 = Point::new(1.0, 2.0);
let p2 = Point::new(3.0, 4.0);
let result = p1.add_point(p2);
assert_eq!(result.x(), 4.0);
assert_eq!(result.y(), 6.0);
}
#[test]
fn test_point_sub() {
let p1 = Point::new(5.0, 8.0);
let p2 = Point::new(2.0, 3.0);
let result = p1.sub_point(p2);
assert_eq!(result.x(), 3.0);
assert_eq!(result.y(), 5.0);
}
#[test]
fn test_point_midpoint() {
let p1 = Point::new(0.0, 0.0);
let p2 = Point::new(4.0, 6.0);
let midpoint = p1.midpoint(p2);
assert_eq!(midpoint.x(), 2.0);
assert_eq!(midpoint.y(), 3.0);
}
#[test]
fn test_point_hypot() {
let point = Point::new(3.0, 4.0);
assert_eq!(point.hypot(), 5.0);
let origin = Point::new(0.0, 0.0);
assert_eq!(origin.hypot(), 0.0);
}
#[test]
fn test_point_scale() {
let point = Point::new(2.0, 3.0);
let scaled = point.scale(2.5);
assert_eq!(scaled.x(), 5.0);
assert_eq!(scaled.y(), 7.5);
}
#[test]
fn test_point_abs() {
let point = Point::new(-2.5, 3.0);
let abs_point = point.abs();
assert_eq!(abs_point.x(), 2.5);
assert_eq!(abs_point.y(), 3.0);
let point2 = Point::new(1.0, -4.0);
let abs_point2 = point2.abs();
assert_eq!(abs_point2.x(), 1.0);
assert_eq!(abs_point2.y(), 4.0);
}
#[test]
fn test_point_to_bounds() {
let center = Point::new(10.0, 20.0);
let size = Size::new(6.0, 8.0);
let bounds = center.to_bounds(size);
assert_eq!(bounds.min_x(), 7.0); assert_eq!(bounds.min_y(), 16.0); assert_eq!(bounds.max_x(), 13.0); assert_eq!(bounds.max_y(), 24.0); }
#[test]
fn test_bounds_new_from_center() {
let center = Point::new(50.0, 60.0);
let size = Size::new(20.0, 30.0);
let bounds = Bounds::new_from_center(center, size);
assert_eq!(bounds.min_x(), 40.0);
assert_eq!(bounds.min_y(), 45.0);
assert_eq!(bounds.max_x(), 60.0);
assert_eq!(bounds.max_y(), 75.0);
assert_eq!(bounds.width(), 20.0);
assert_eq!(bounds.height(), 30.0);
assert_eq!(bounds.center(), center);
}
#[test]
fn test_bounds_new_from_center_zero_size() {
let center = Point::new(10.0, 20.0);
let size = Size::new(0.0, 0.0);
let bounds = Bounds::new_from_center(center, size);
assert_eq!(bounds.min_x(), 10.0);
assert_eq!(bounds.min_y(), 20.0);
assert_eq!(bounds.max_x(), 10.0);
assert_eq!(bounds.max_y(), 20.0);
assert_eq!(bounds.width(), 0.0);
assert_eq!(bounds.height(), 0.0);
}
#[test]
fn test_bounds_new_from_top_left() {
let top_left = Point::new(10.0, 20.0);
let size = Size::new(30.0, 40.0);
let bounds = Bounds::new_from_top_left(top_left, size);
assert_eq!(bounds.min_x(), 10.0);
assert_eq!(bounds.min_y(), 20.0);
assert_eq!(bounds.max_x(), 40.0);
assert_eq!(bounds.max_y(), 60.0);
assert_eq!(bounds.width(), 30.0);
assert_eq!(bounds.height(), 40.0);
assert_eq!(bounds.min_point(), top_left);
}
#[test]
fn test_bounds_new_from_top_left_zero_size() {
let top_left = Point::new(5.0, 15.0);
let size = Size::new(0.0, 0.0);
let bounds = Bounds::new_from_top_left(top_left, size);
assert_eq!(bounds.min_x(), 5.0);
assert_eq!(bounds.min_y(), 15.0);
assert_eq!(bounds.max_x(), 5.0);
assert_eq!(bounds.max_y(), 15.0);
assert_eq!(bounds.width(), 0.0);
assert_eq!(bounds.height(), 0.0);
}
#[test]
fn test_size_new() {
let size = Size::new(100.0, 200.0);
assert_eq!(size.width(), 100.0);
assert_eq!(size.height(), 200.0);
}
#[test]
fn test_size_zero() {
let size = Size::zero();
assert_eq!(size.width(), 0.0);
assert_eq!(size.height(), 0.0);
}
#[test]
fn test_size_default() {
assert_eq!(Size::default(), Size::zero());
}
#[test]
fn test_size_max() {
let size1 = Size::new(10.0, 20.0);
let size2 = Size::new(15.0, 18.0);
let max_size = size1.max(size2);
assert_eq!(max_size.width(), 15.0);
assert_eq!(max_size.height(), 20.0);
}
#[test]
fn test_size_add_padding() {
let size = Size::new(10.0, 20.0);
let padded = size.add_padding(Insets::uniform(5.0));
assert_eq!(padded.width(), 20.0); assert_eq!(padded.height(), 30.0); }
#[test]
fn test_size_scale() {
let size = Size::new(10.0, 20.0);
let scaled = size.scale(2.0);
assert_eq!(scaled.width(), 20.0);
assert_eq!(scaled.height(), 40.0);
let scaled_half = size.scale(0.5);
assert_eq!(scaled_half.width(), 5.0);
assert_eq!(scaled_half.height(), 10.0);
let scaled_zero = size.scale(0.0);
assert_eq!(scaled_zero.width(), 0.0);
assert_eq!(scaled_zero.height(), 0.0);
let scaled_neg = size.scale(-1.0);
assert_eq!(scaled_neg.width(), -10.0);
assert_eq!(scaled_neg.height(), -20.0);
let scaled_one = size.scale(1.0);
assert_eq!(scaled_one.width(), size.width());
assert_eq!(scaled_one.height(), size.height());
}
#[test]
fn test_bounds_accessors() {
let bounds = Bounds {
min_x: 1.0,
min_y: 2.0,
max_x: 5.0,
max_y: 8.0,
};
assert_eq!(bounds.min_x(), 1.0);
assert_eq!(bounds.min_y(), 2.0);
assert_eq!(bounds.max_x(), 5.0);
assert_eq!(bounds.max_y(), 8.0);
}
#[test]
fn test_bounds_with_max_y() {
let bounds = Bounds {
min_x: 2.0,
min_y: 5.0,
max_x: 10.0,
max_y: 12.0,
};
let new_bounds = bounds.with_max_y(15.0);
assert_eq!(
new_bounds,
Bounds {
min_x: 2.0,
min_y: 5.0,
max_x: 10.0,
max_y: 15.0,
}
);
}
#[test]
fn test_bounds_dimensions() {
let bounds = Bounds {
min_x: 2.0,
min_y: 3.0,
max_x: 7.0,
max_y: 11.0,
};
assert_eq!(bounds.width(), 5.0);
assert_eq!(bounds.height(), 8.0);
}
#[test]
fn test_bounds_min_point() {
let bounds = Bounds {
min_x: 2.0,
min_y: 3.0,
max_x: 7.0,
max_y: 11.0,
};
let min_point = bounds.min_point();
assert_eq!(min_point.x(), 2.0);
assert_eq!(min_point.y(), 3.0);
}
#[test]
fn test_bounds_to_size() {
let bounds = Bounds {
min_x: 1.0,
min_y: 2.0,
max_x: 6.0,
max_y: 9.0,
};
let size = bounds.to_size();
assert_eq!(size.width(), 5.0);
assert_eq!(size.height(), 7.0);
}
#[test]
fn test_bounds_merge() {
let bounds1 = Bounds {
min_x: 1.0,
min_y: 2.0,
max_x: 5.0,
max_y: 6.0,
};
let bounds2 = Bounds {
min_x: 3.0,
min_y: 0.0,
max_x: 8.0,
max_y: 4.0,
};
let merged = bounds1.merge(&bounds2);
assert_eq!(merged.min_x(), 1.0);
assert_eq!(merged.min_y(), 0.0);
assert_eq!(merged.max_x(), 8.0);
assert_eq!(merged.max_y(), 6.0);
}
#[test]
fn test_bounds_translate() {
let bounds = Bounds {
min_x: 1.0,
min_y: 2.0,
max_x: 5.0,
max_y: 6.0,
};
let offset = Point::new(3.0, -1.0);
let translated = bounds.translate(offset);
assert_eq!(translated.min_x(), 4.0);
assert_eq!(translated.min_y(), 1.0);
assert_eq!(translated.max_x(), 8.0);
assert_eq!(translated.max_y(), 5.0);
}
#[test]
fn test_bounds_inverse_translate() {
let bounds = Bounds {
min_x: 5.0,
min_y: 3.0,
max_x: 9.0,
max_y: 7.0,
};
let offset = Point::new(2.0, 1.0);
let inverse_translated = bounds.inverse_translate(offset);
assert_eq!(inverse_translated.min_x(), 3.0);
assert_eq!(inverse_translated.min_y(), 2.0);
assert_eq!(inverse_translated.max_x(), 7.0);
assert_eq!(inverse_translated.max_y(), 6.0);
}
#[test]
fn test_bounds_add_padding() {
let bounds = Bounds {
min_x: 2.0,
min_y: 3.0,
max_x: 6.0,
max_y: 8.0,
};
let padded = bounds.add_padding(Insets::uniform(1.0));
assert_eq!(padded.min_x(), 1.0);
assert_eq!(padded.min_y(), 2.0);
assert_eq!(padded.max_x(), 7.0);
assert_eq!(padded.max_y(), 9.0);
}
#[test]
fn test_bounds_default() {
let bounds = Bounds::default();
assert_eq!(bounds.min_x(), 0.0);
assert_eq!(bounds.min_y(), 0.0);
assert_eq!(bounds.max_x(), 0.0);
assert_eq!(bounds.max_y(), 0.0);
}
#[test]
fn test_component_bounds() {
let position = Point::new(10.0, 15.0);
let size = Size::new(8.0, 12.0);
let bounds = position.to_bounds(size);
assert_eq!(bounds.min_x(), 6.0); assert_eq!(bounds.min_y(), 9.0); assert_eq!(bounds.max_x(), 14.0); assert_eq!(bounds.max_y(), 21.0); assert_eq!(bounds.width(), 8.0);
assert_eq!(bounds.height(), 12.0);
}
#[test]
fn test_edge_cases() {
let zero_point = Point::new(0.0, 0.0);
let zero_size = Size::new(0.0, 0.0);
let zero_bounds = zero_point.to_bounds(zero_size);
assert_eq!(zero_bounds.width(), 0.0);
assert_eq!(zero_bounds.height(), 0.0);
let neg_point = Point::new(-5.0, -3.0);
let abs_neg = neg_point.abs();
assert_eq!(abs_neg.x(), 5.0);
assert_eq!(abs_neg.y(), 3.0);
let point = Point::new(10.0, 20.0);
let scaled_zero = point.scale(0.0);
assert!(scaled_zero.is_zero());
let scaled_neg = point.scale(-1.0);
assert_eq!(scaled_neg.x(), -10.0);
assert_eq!(scaled_neg.y(), -20.0);
}
#[test]
fn test_mathematical_properties() {
let p1 = Point::new(3.0, 4.0);
let p2 = Point::new(1.0, 2.0);
assert_eq!(p1.add_point(p2).x(), p2.add_point(p1).x());
assert_eq!(p1.add_point(p2).y(), p2.add_point(p1).y());
let diff = p1.sub_point(p2);
let sum_back = diff.add_point(p2);
assert!((sum_back.x() - p1.x()).abs() < f32::EPSILON);
assert!((sum_back.y() - p1.y()).abs() < f32::EPSILON);
let mid = p1.midpoint(p2);
let dist1 = p1.sub_point(mid).hypot();
let dist2 = p2.sub_point(mid).hypot();
assert!((dist1 - dist2).abs() < f32::EPSILON);
}
#[test]
fn test_insets_new() {
let insets = Insets::new(1.0, 2.0, 3.0, 4.0);
assert_eq!(insets.top(), 1.0);
assert_eq!(insets.right(), 2.0);
assert_eq!(insets.bottom(), 3.0);
assert_eq!(insets.left(), 4.0);
}
#[test]
fn test_bounds_add_insets() {
let bounds = Bounds {
min_x: 2.0,
min_y: 3.0,
max_x: 6.0,
max_y: 8.0,
};
let insets = Insets::new(1.0, 2.0, 3.0, 4.0);
let padded_custom = bounds.add_padding(insets);
assert_eq!(padded_custom.min_x(), -2.0); assert_eq!(padded_custom.min_y(), 2.0); assert_eq!(padded_custom.max_x(), 8.0); assert_eq!(padded_custom.max_y(), 11.0); }
#[test]
fn test_insets_uniform() {
let insets = Insets::uniform(5.0);
assert_eq!(insets.top(), 5.0);
assert_eq!(insets.right(), 5.0);
assert_eq!(insets.bottom(), 5.0);
assert_eq!(insets.left(), 5.0);
}
#[test]
fn test_insets_default() {
let insets = Insets::default();
assert_eq!(insets.top(), 0.0);
assert_eq!(insets.right(), 0.0);
assert_eq!(insets.bottom(), 0.0);
assert_eq!(insets.left(), 0.0);
}
#[test]
fn test_insets_sums() {
let insets = Insets::new(1.0, 2.0, 3.0, 4.0);
assert_eq!(insets.horizontal_sum(), 6.0); assert_eq!(insets.vertical_sum(), 4.0); }
#[test]
fn test_size_is_zero() {
let new_size = Size::new(0.0, 0.0);
assert!(new_size.is_zero());
let zero_size = Size::zero();
assert!(zero_size.is_zero());
let default_size = Size::default();
assert!(default_size.is_zero());
let non_zero_width = Size::new(1.0, 0.0);
assert!(!non_zero_width.is_zero());
let non_zero_height = Size::new(0.0, 1.0);
assert!(!non_zero_height.is_zero());
let non_zero_both = Size::new(5.0, 3.0);
assert!(!non_zero_both.is_zero());
let negative_size = Size::new(-1.0, -1.0);
assert!(!negative_size.is_zero());
let mixed_size = Size::new(-1.0, 0.0);
assert!(!mixed_size.is_zero());
}
#[test]
fn test_insets_with_top() {
let original = Insets::new(1.0, 2.0, 3.0, 4.0);
let modified = original.with_top(10.0);
assert_eq!(modified.top(), 10.0);
assert_eq!(modified.right(), 2.0); assert_eq!(modified.bottom(), 3.0); assert_eq!(modified.left(), 4.0);
let with_zero = original.with_top(0.0);
assert_eq!(with_zero.top(), 0.0);
assert_eq!(with_zero.right(), 2.0);
assert_eq!(with_zero.bottom(), 3.0);
assert_eq!(with_zero.left(), 4.0);
let with_negative = original.with_top(-5.0);
assert_eq!(with_negative.top(), -5.0);
assert_eq!(with_negative.right(), 2.0);
assert_eq!(with_negative.bottom(), 3.0);
assert_eq!(with_negative.left(), 4.0);
let original_copy = Insets::new(1.0, 2.0, 3.0, 4.0);
let chained = original_copy.with_top(15.0).with_top(20.0);
assert_eq!(chained.top(), 20.0);
assert_eq!(chained.right(), 2.0);
assert_eq!(chained.bottom(), 3.0);
assert_eq!(chained.left(), 4.0);
assert_eq!(original.top(), 1.0);
assert_eq!(original.right(), 2.0);
assert_eq!(original.bottom(), 3.0);
assert_eq!(original.left(), 4.0);
let uniform = Insets::uniform(5.0);
let uniform_modified = uniform.with_top(10.0);
assert_eq!(uniform_modified.top(), 10.0);
assert_eq!(uniform_modified.right(), 5.0);
assert_eq!(uniform_modified.bottom(), 5.0);
assert_eq!(uniform_modified.left(), 5.0);
}
}
#[cfg(test)]
mod proptest_tests {
use float_cmp::approx_eq;
use proptest::prelude::*;
use super::*;
fn bounds_strategy() -> impl Strategy<Value = Bounds> {
(
-1000.0f32..1000.0,
-1000.0f32..1000.0,
1.0f32..500.0,
1.0f32..500.0,
)
.prop_map(|(x, y, w, h)| Bounds::new_from_top_left(Point::new(x, y), Size::new(w, h)))
}
fn size_strategy() -> impl Strategy<Value = Size> {
(0.0f32..1000.0, 0.0f32..1000.0).prop_map(|(w, h)| Size::new(w, h))
}
fn point_strategy() -> impl Strategy<Value = Point> {
(-1000.0f32..1000.0, -1000.0f32..1000.0).prop_map(|(x, y)| Point::new(x, y))
}
fn scale_strategy() -> impl Strategy<Value = f32> {
0.1f32..10.0
}
fn check_point_add_is_commutative(p1: Point, p2: Point) -> Result<(), TestCaseError> {
let result1 = p1.add_point(p2);
let result2 = p2.add_point(p1);
prop_assert!(approx_eq!(f32, result1.x(), result2.x()));
prop_assert!(approx_eq!(f32, result1.y(), result2.y()));
Ok(())
}
fn check_midpoint_is_between_points(p1: Point, p2: Point) -> Result<(), TestCaseError> {
let mid = p1.midpoint(p2);
let min_x = p1.x().min(p2.x());
let max_x = p1.x().max(p2.x());
let min_y = p1.y().min(p2.y());
let max_y = p1.y().max(p2.y());
prop_assert!(mid.x() >= min_x && mid.x() <= max_x);
prop_assert!(mid.y() >= min_y && mid.y() <= max_y);
Ok(())
}
fn check_scale_inverse_roundtrip(p: Point, scale: f32) -> Result<(), TestCaseError> {
let scaled = p.scale(scale);
let unscaled = scaled.scale(1.0 / scale);
prop_assert!(approx_eq!(f32, unscaled.x(), p.x()));
prop_assert!(approx_eq!(f32, unscaled.y(), p.y()));
Ok(())
}
fn check_add_sub_inverse(p1: Point, p2: Point) -> Result<(), TestCaseError> {
let result = p1.add_point(p2).sub_point(p2);
prop_assert!(approx_eq!(f32, result.x(), p1.x(), epsilon = 0.001));
prop_assert!(approx_eq!(f32, result.y(), p1.y(), epsilon = 0.001));
Ok(())
}
fn check_bounds_merge_is_commutative(b1: Bounds, b2: Bounds) -> Result<(), TestCaseError> {
let merged1 = b1.merge(&b2);
let merged2 = b2.merge(&b1);
prop_assert!(approx_eq!(f32, merged1.min_x(), merged2.min_x()));
prop_assert!(approx_eq!(f32, merged1.min_y(), merged2.min_y()));
prop_assert!(approx_eq!(f32, merged1.max_x(), merged2.max_x()));
prop_assert!(approx_eq!(f32, merged1.max_y(), merged2.max_y()));
Ok(())
}
fn check_bounds_merge_is_associative(
b1: Bounds,
b2: Bounds,
b3: Bounds,
) -> Result<(), TestCaseError> {
let left_assoc = b1.merge(&b2).merge(&b3);
let right_assoc = b1.merge(&b2.merge(&b3));
prop_assert!(approx_eq!(f32, left_assoc.min_x(), right_assoc.min_x()));
prop_assert!(approx_eq!(f32, left_assoc.min_y(), right_assoc.min_y()));
prop_assert!(approx_eq!(f32, left_assoc.max_x(), right_assoc.max_x()));
prop_assert!(approx_eq!(f32, left_assoc.max_y(), right_assoc.max_y()));
Ok(())
}
fn check_bounds_merge_contains_both(b1: Bounds, b2: Bounds) -> Result<(), TestCaseError> {
let merged = b1.merge(&b2);
prop_assert!(merged.min_x() <= b1.min_x() + 0.001);
prop_assert!(merged.min_y() <= b1.min_y() + 0.001);
prop_assert!(merged.max_x() >= b1.max_x() - 0.001);
prop_assert!(merged.max_y() >= b1.max_y() - 0.001);
prop_assert!(merged.min_x() <= b2.min_x() + 0.001);
prop_assert!(merged.min_y() <= b2.min_y() + 0.001);
prop_assert!(merged.max_x() >= b2.max_x() - 0.001);
prop_assert!(merged.max_y() >= b2.max_y() - 0.001);
Ok(())
}
fn check_translate_inverse_roundtrip(
bounds: Bounds,
offset: Point,
) -> Result<(), TestCaseError> {
let roundtrip = bounds.translate(offset).inverse_translate(offset);
prop_assert!(approx_eq!(
f32,
roundtrip.min_x(),
bounds.min_x(),
epsilon = 0.001
));
prop_assert!(approx_eq!(
f32,
roundtrip.min_y(),
bounds.min_y(),
epsilon = 0.001
));
prop_assert!(approx_eq!(
f32,
roundtrip.max_x(),
bounds.max_x(),
epsilon = 0.001
));
prop_assert!(approx_eq!(
f32,
roundtrip.max_y(),
bounds.max_y(),
epsilon = 0.001
));
Ok(())
}
fn check_size_max_is_commutative(s1: Size, s2: Size) -> Result<(), TestCaseError> {
let max1 = s1.max(s2);
let max2 = s2.max(s1);
prop_assert!(approx_eq!(f32, max1.width(), max2.width()));
prop_assert!(approx_eq!(f32, max1.height(), max2.height()));
Ok(())
}
fn check_size_max_is_idempotent(s: Size) -> Result<(), TestCaseError> {
let max_self = s.max(s);
prop_assert!(approx_eq!(f32, max_self.width(), s.width()));
prop_assert!(approx_eq!(f32, max_self.height(), s.height()));
Ok(())
}
proptest! {
#[test]
fn point_add_is_commutative(p1 in point_strategy(), p2 in point_strategy()) {
check_point_add_is_commutative(p1, p2)?;
}
#[test]
fn midpoint_is_between_points(p1 in point_strategy(), p2 in point_strategy()) {
check_midpoint_is_between_points(p1, p2)?;
}
#[test]
fn scale_inverse_roundtrip(p in point_strategy(), scale in scale_strategy()) {
check_scale_inverse_roundtrip(p, scale)?;
}
#[test]
fn add_sub_inverse(p1 in point_strategy(), p2 in point_strategy()) {
check_add_sub_inverse(p1, p2)?;
}
#[test]
fn bounds_merge_is_commutative(b1 in bounds_strategy(), b2 in bounds_strategy()) {
check_bounds_merge_is_commutative(b1, b2)?;
}
#[test]
fn bounds_merge_is_associative(b1 in bounds_strategy(), b2 in bounds_strategy(), b3 in bounds_strategy()) {
check_bounds_merge_is_associative(b1, b2, b3)?;
}
#[test]
fn bounds_merge_contains_both(b1 in bounds_strategy(), b2 in bounds_strategy()) {
check_bounds_merge_contains_both(b1, b2)?;
}
#[test]
fn translate_inverse_roundtrip(bounds in bounds_strategy(), offset in point_strategy()) {
check_translate_inverse_roundtrip(bounds, offset)?;
}
#[test]
fn size_max_is_commutative(s1 in size_strategy(), s2 in size_strategy()) {
check_size_max_is_commutative(s1, s2)?;
}
#[test]
fn size_max_is_idempotent(s in size_strategy()) {
check_size_max_is_idempotent(s)?;
}
}
}