use std::fmt::{self, Debug};
use std::ops::{Add, BitAnd, Div, Mul, Neg, Rem, Sub};
use super::lerp::lerp_double_non_null;
use super::math::clamp_double;
#[derive(Clone, Copy, PartialEq)]
pub struct Offset {
dx: f64,
dy: f64,
}
impl Offset {
pub const fn new(dx: f64, dy: f64) -> Offset {
Offset { dx, dy }
}
pub fn from_direction(direction: f64, distance: f64) -> Offset {
Offset::new(distance * direction.cos(), distance * direction.sin())
}
pub const fn dx(&self) -> f64 {
self.dx
}
pub const fn dy(&self) -> f64 {
self.dy
}
pub fn is_infinite(&self) -> bool {
self.dx >= f64::INFINITY || self.dy >= f64::INFINITY
}
pub fn is_finite(&self) -> bool {
self.dx.is_finite() && self.dy.is_finite()
}
pub fn lt(&self, other: &Offset) -> bool {
self.dx < other.dx && self.dy < other.dy
}
pub fn le(&self, other: &Offset) -> bool {
self.dx <= other.dx && self.dy <= other.dy
}
pub fn gt(&self, other: &Offset) -> bool {
self.dx > other.dx && self.dy > other.dy
}
pub fn ge(&self, other: &Offset) -> bool {
self.dx >= other.dx && self.dy >= other.dy
}
pub fn distance(&self) -> f64 {
(self.dx * self.dx + self.dy * self.dy).sqrt()
}
pub fn distance_squared(&self) -> f64 {
self.dx * self.dx + self.dy * self.dy
}
pub fn direction(&self) -> f64 {
self.dy.atan2(self.dx)
}
pub const ZERO: Offset = Offset::new(0.0, 0.0);
pub const INFINITE: Offset = Offset::new(f64::INFINITY, f64::INFINITY);
pub fn scale(&self, scale_x: f64, scale_y: f64) -> Offset {
Offset::new(self.dx * scale_x, self.dy * scale_y)
}
pub fn translate(&self, translate_x: f64, translate_y: f64) -> Offset {
Offset::new(self.dx + translate_x, self.dy + translate_y)
}
pub fn truncating_div(&self, operand: f64) -> Offset {
Offset::new((self.dx / operand).trunc(), (self.dy / operand).trunc())
}
pub fn lerp(a: Option<Offset>, b: Option<Offset>, t: f64) -> Option<Offset> {
match (a, b) {
(None, None) => None,
(Some(a), None) => Some(a * (1.0 - t)),
(None, Some(b)) => Some(b * t),
(Some(a), Some(b)) => Some(Offset::new(
lerp_double_non_null(a.dx, b.dx, t),
lerp_double_non_null(a.dy, b.dy, t),
)),
}
}
}
impl Neg for Offset {
type Output = Offset;
fn neg(self) -> Offset {
Offset::new(-self.dx, -self.dy)
}
}
impl Sub for Offset {
type Output = Offset;
fn sub(self, other: Offset) -> Offset {
Offset::new(self.dx - other.dx, self.dy - other.dy)
}
}
impl Add for Offset {
type Output = Offset;
fn add(self, other: Offset) -> Offset {
Offset::new(self.dx + other.dx, self.dy + other.dy)
}
}
impl Mul<f64> for Offset {
type Output = Offset;
fn mul(self, operand: f64) -> Offset {
Offset::new(self.dx * operand, self.dy * operand)
}
}
impl Div<f64> for Offset {
type Output = Offset;
fn div(self, operand: f64) -> Offset {
Offset::new(self.dx / operand, self.dy / operand)
}
}
impl Rem<f64> for Offset {
type Output = Offset;
fn rem(self, operand: f64) -> Offset {
Offset::new(self.dx.rem_euclid(operand), self.dy.rem_euclid(operand))
}
}
impl Debug for Offset {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Offset({:.1}, {:.1})", self.dx, self.dy)
}
}
#[derive(Clone, Copy, PartialEq)]
pub struct Size {
width: f64,
height: f64,
}
impl Size {
pub const fn new(width: f64, height: f64) -> Size {
Size { width, height }
}
pub const fn square(dimension: f64) -> Size {
Size::new(dimension, dimension)
}
pub const fn from_width(width: f64) -> Size {
Size::new(width, f64::INFINITY)
}
pub const fn from_height(height: f64) -> Size {
Size::new(f64::INFINITY, height)
}
pub const fn from_radius(radius: f64) -> Size {
Size::new(radius * 2.0, radius * 2.0)
}
pub const fn width(&self) -> f64 {
self.width
}
pub const fn height(&self) -> f64 {
self.height
}
pub fn is_infinite(&self) -> bool {
self.width >= f64::INFINITY || self.height >= f64::INFINITY
}
pub fn is_finite(&self) -> bool {
self.width.is_finite() && self.height.is_finite()
}
pub fn lt(&self, other: &Size) -> bool {
self.width < other.width && self.height < other.height
}
pub fn le(&self, other: &Size) -> bool {
self.width <= other.width && self.height <= other.height
}
pub fn gt(&self, other: &Size) -> bool {
self.width > other.width && self.height > other.height
}
pub fn ge(&self, other: &Size) -> bool {
self.width >= other.width && self.height >= other.height
}
pub fn aspect_ratio(&self) -> f64 {
if self.height != 0.0 {
return self.width / self.height;
}
if self.width > 0.0 {
return f64::INFINITY;
}
if self.width < 0.0 {
return f64::NEG_INFINITY;
}
0.0
}
pub const ZERO: Size = Size::new(0.0, 0.0);
pub const INFINITE: Size = Size::new(f64::INFINITY, f64::INFINITY);
pub fn is_empty(&self) -> bool {
self.width <= 0.0 || self.height <= 0.0
}
pub fn truncating_div(&self, operand: f64) -> Size {
Size::new(
(self.width / operand).trunc(),
(self.height / operand).trunc(),
)
}
pub fn shortest_side(&self) -> f64 {
self.width.abs().min(self.height.abs())
}
pub fn longest_side(&self) -> f64 {
self.width.abs().max(self.height.abs())
}
pub fn top_left(&self, origin: Offset) -> Offset {
origin
}
pub fn top_center(&self, origin: Offset) -> Offset {
Offset::new(origin.dx() + self.width / 2.0, origin.dy())
}
pub fn top_right(&self, origin: Offset) -> Offset {
Offset::new(origin.dx() + self.width, origin.dy())
}
pub fn center_left(&self, origin: Offset) -> Offset {
Offset::new(origin.dx(), origin.dy() + self.height / 2.0)
}
pub fn center(&self, origin: Offset) -> Offset {
Offset::new(
origin.dx() + self.width / 2.0,
origin.dy() + self.height / 2.0,
)
}
pub fn center_right(&self, origin: Offset) -> Offset {
Offset::new(origin.dx() + self.width, origin.dy() + self.height / 2.0)
}
pub fn bottom_left(&self, origin: Offset) -> Offset {
Offset::new(origin.dx(), origin.dy() + self.height)
}
pub fn bottom_center(&self, origin: Offset) -> Offset {
Offset::new(origin.dx() + self.width / 2.0, origin.dy() + self.height)
}
pub fn bottom_right(&self, origin: Offset) -> Offset {
Offset::new(origin.dx() + self.width, origin.dy() + self.height)
}
pub fn contains(&self, offset: Offset) -> bool {
offset.dx() >= 0.0
&& offset.dx() < self.width
&& offset.dy() >= 0.0
&& offset.dy() < self.height
}
pub fn flipped(&self) -> Size {
Size::new(self.height, self.width)
}
pub fn lerp(a: Option<Size>, b: Option<Size>, t: f64) -> Option<Size> {
match (a, b) {
(None, None) => None,
(Some(a), None) => Some(a * (1.0 - t)),
(None, Some(b)) => Some(b * t),
(Some(a), Some(b)) => Some(Size::new(
lerp_double_non_null(a.width, b.width, t),
lerp_double_non_null(a.height, b.height, t),
)),
}
}
}
impl Sub<Size> for Size {
type Output = Offset;
fn sub(self, other: Size) -> Offset {
Offset::new(self.width - other.width, self.height - other.height)
}
}
impl Sub<Offset> for Size {
type Output = Size;
fn sub(self, other: Offset) -> Size {
Size::new(self.width - other.dx(), self.height - other.dy())
}
}
impl Add<Offset> for Size {
type Output = Size;
fn add(self, other: Offset) -> Size {
Size::new(self.width + other.dx(), self.height + other.dy())
}
}
impl Mul<f64> for Size {
type Output = Size;
fn mul(self, operand: f64) -> Size {
Size::new(self.width * operand, self.height * operand)
}
}
impl Div<f64> for Size {
type Output = Size;
fn div(self, operand: f64) -> Size {
Size::new(self.width / operand, self.height / operand)
}
}
impl Rem<f64> for Size {
type Output = Size;
fn rem(self, operand: f64) -> Size {
Size::new(
self.width.rem_euclid(operand),
self.height.rem_euclid(operand),
)
}
}
impl BitAnd<Size> for Offset {
type Output = Rect;
fn bitand(self, other: Size) -> Rect {
Rect::from_ltwh(self.dx(), self.dy(), other.width, other.height)
}
}
impl Debug for Size {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Size({:.1}, {:.1})", self.width, self.height)
}
}
#[derive(Clone, Copy, PartialEq)]
pub struct Rect {
pub left: f64,
pub top: f64,
pub right: f64,
pub bottom: f64,
}
impl Rect {
pub const fn from_ltrb(left: f64, top: f64, right: f64, bottom: f64) -> Rect {
Rect {
left,
top,
right,
bottom,
}
}
pub const fn from_ltwh(left: f64, top: f64, width: f64, height: f64) -> Rect {
Rect::from_ltrb(left, top, left + width, top + height)
}
pub fn from_circle(center: Offset, radius: f64) -> Rect {
Rect::from_center(center, radius * 2.0, radius * 2.0)
}
pub fn from_center(center: Offset, width: f64, height: f64) -> Rect {
Rect::from_ltrb(
center.dx() - width / 2.0,
center.dy() - height / 2.0,
center.dx() + width / 2.0,
center.dy() + height / 2.0,
)
}
pub fn from_points(a: Offset, b: Offset) -> Rect {
Rect::from_ltrb(
a.dx().min(b.dx()),
a.dy().min(b.dy()),
a.dx().max(b.dx()),
a.dy().max(b.dy()),
)
}
pub fn width(&self) -> f64 {
self.right - self.left
}
pub fn height(&self) -> f64 {
self.bottom - self.top
}
pub fn size(&self) -> Size {
Size::new(self.width(), self.height())
}
pub fn has_nan(&self) -> bool {
self.left.is_nan() || self.top.is_nan() || self.right.is_nan() || self.bottom.is_nan()
}
pub const ZERO: Rect = Rect::from_ltrb(0.0, 0.0, 0.0, 0.0);
const GIANT_SCALAR: f64 = 1.0E+9;
pub const LARGEST: Rect = Rect::from_ltrb(
-Rect::GIANT_SCALAR,
-Rect::GIANT_SCALAR,
Rect::GIANT_SCALAR,
Rect::GIANT_SCALAR,
);
pub fn is_infinite(&self) -> bool {
self.left >= f64::INFINITY
|| self.top >= f64::INFINITY
|| self.right >= f64::INFINITY
|| self.bottom >= f64::INFINITY
}
pub fn is_finite(&self) -> bool {
self.left.is_finite()
&& self.top.is_finite()
&& self.right.is_finite()
&& self.bottom.is_finite()
}
pub fn is_empty(&self) -> bool {
self.left >= self.right || self.top >= self.bottom
}
pub fn shift(&self, offset: Offset) -> Rect {
Rect::from_ltrb(
self.left + offset.dx(),
self.top + offset.dy(),
self.right + offset.dx(),
self.bottom + offset.dy(),
)
}
pub fn translate(&self, translate_x: f64, translate_y: f64) -> Rect {
Rect::from_ltrb(
self.left + translate_x,
self.top + translate_y,
self.right + translate_x,
self.bottom + translate_y,
)
}
pub fn inflate(&self, delta: f64) -> Rect {
Rect::from_ltrb(
self.left - delta,
self.top - delta,
self.right + delta,
self.bottom + delta,
)
}
pub fn deflate(&self, delta: f64) -> Rect {
self.inflate(-delta)
}
pub fn intersect(&self, other: Rect) -> Rect {
Rect::from_ltrb(
self.left.max(other.left),
self.top.max(other.top),
self.right.min(other.right),
self.bottom.min(other.bottom),
)
}
pub fn expand_to_include(&self, other: Rect) -> Rect {
Rect::from_ltrb(
self.left.min(other.left),
self.top.min(other.top),
self.right.max(other.right),
self.bottom.max(other.bottom),
)
}
pub fn overlaps(&self, other: Rect) -> bool {
if self.right <= other.left || other.right <= self.left {
return false;
}
if self.bottom <= other.top || other.bottom <= self.top {
return false;
}
true
}
pub fn shortest_side(&self) -> f64 {
self.width().abs().min(self.height().abs())
}
pub fn longest_side(&self) -> f64 {
self.width().abs().max(self.height().abs())
}
pub fn top_left(&self) -> Offset {
Offset::new(self.left, self.top)
}
pub fn top_center(&self) -> Offset {
Offset::new(self.left + self.width() / 2.0, self.top)
}
pub fn top_right(&self) -> Offset {
Offset::new(self.right, self.top)
}
pub fn center_left(&self) -> Offset {
Offset::new(self.left, self.top + self.height() / 2.0)
}
pub fn center(&self) -> Offset {
Offset::new(
self.left + self.width() / 2.0,
self.top + self.height() / 2.0,
)
}
pub fn center_right(&self) -> Offset {
Offset::new(self.right, self.top + self.height() / 2.0)
}
pub fn bottom_left(&self) -> Offset {
Offset::new(self.left, self.bottom)
}
pub fn bottom_center(&self) -> Offset {
Offset::new(self.left + self.width() / 2.0, self.bottom)
}
pub fn bottom_right(&self) -> Offset {
Offset::new(self.right, self.bottom)
}
pub fn contains(&self, offset: Offset) -> bool {
offset.dx() >= self.left
&& offset.dx() < self.right
&& offset.dy() >= self.top
&& offset.dy() < self.bottom
}
pub fn lerp(a: Option<Rect>, b: Option<Rect>, t: f64) -> Option<Rect> {
match (a, b) {
(None, None) => None,
(Some(a), None) => {
let k = 1.0 - t;
Some(Rect::from_ltrb(
a.left * k,
a.top * k,
a.right * k,
a.bottom * k,
))
}
(None, Some(b)) => Some(Rect::from_ltrb(
b.left * t,
b.top * t,
b.right * t,
b.bottom * t,
)),
(Some(a), Some(b)) => Some(Rect::from_ltrb(
lerp_double_non_null(a.left, b.left, t),
lerp_double_non_null(a.top, b.top, t),
lerp_double_non_null(a.right, b.right, t),
lerp_double_non_null(a.bottom, b.bottom, t),
)),
}
}
}
impl Debug for Rect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Rect.fromLTRB({:.1}, {:.1}, {:.1}, {:.1})",
self.left, self.top, self.right, self.bottom
)
}
}
#[derive(Clone, Copy, PartialEq)]
pub struct Radius {
pub x: f64,
pub y: f64,
}
impl Radius {
pub const fn circular(radius: f64) -> Radius {
Radius::elliptical(radius, radius)
}
pub const fn elliptical(x: f64, y: f64) -> Radius {
Radius { x, y }
}
pub const ZERO: Radius = Radius::circular(0.0);
pub fn clamp(&self, minimum: Option<Radius>, maximum: Option<Radius>) -> Radius {
let minimum = minimum.unwrap_or(Radius::circular(f64::NEG_INFINITY));
let maximum = maximum.unwrap_or(Radius::circular(f64::INFINITY));
Radius::elliptical(
clamp_double(self.x, minimum.x, maximum.x),
clamp_double(self.y, minimum.y, maximum.y),
)
}
pub fn clamp_values(
&self,
minimum_x: Option<f64>,
minimum_y: Option<f64>,
maximum_x: Option<f64>,
maximum_y: Option<f64>,
) -> Radius {
Radius::elliptical(
clamp_double(
self.x,
minimum_x.unwrap_or(f64::NEG_INFINITY),
maximum_x.unwrap_or(f64::INFINITY),
),
clamp_double(
self.y,
minimum_y.unwrap_or(f64::NEG_INFINITY),
maximum_y.unwrap_or(f64::INFINITY),
),
)
}
pub fn truncating_div(&self, operand: f64) -> Radius {
Radius::elliptical((self.x / operand).trunc(), (self.y / operand).trunc())
}
pub fn lerp(a: Option<Radius>, b: Option<Radius>, t: f64) -> Option<Radius> {
match (a, b) {
(None, None) => None,
(Some(a), None) => {
let k = 1.0 - t;
Some(Radius::elliptical(a.x * k, a.y * k))
}
(None, Some(b)) => Some(Radius::elliptical(b.x * t, b.y * t)),
(Some(a), Some(b)) => Some(Radius::elliptical(
lerp_double_non_null(a.x, b.x, t),
lerp_double_non_null(a.y, b.y, t),
)),
}
}
}
impl Neg for Radius {
type Output = Radius;
fn neg(self) -> Radius {
Radius::elliptical(-self.x, -self.y)
}
}
impl Sub for Radius {
type Output = Radius;
fn sub(self, other: Radius) -> Radius {
Radius::elliptical(self.x - other.x, self.y - other.y)
}
}
impl Add for Radius {
type Output = Radius;
fn add(self, other: Radius) -> Radius {
Radius::elliptical(self.x + other.x, self.y + other.y)
}
}
impl Mul<f64> for Radius {
type Output = Radius;
fn mul(self, operand: f64) -> Radius {
Radius::elliptical(self.x * operand, self.y * operand)
}
}
impl Div<f64> for Radius {
type Output = Radius;
fn div(self, operand: f64) -> Radius {
Radius::elliptical(self.x / operand, self.y / operand)
}
}
impl Rem<f64> for Radius {
type Output = Radius;
fn rem(self, operand: f64) -> Radius {
Radius::elliptical(self.x.rem_euclid(operand), self.y.rem_euclid(operand))
}
}
impl Debug for Radius {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.x == self.y {
write!(f, "Radius.circular({:.1})", self.x)
} else {
write!(f, "Radius.elliptical({:.1}, {:.1})", self.x, self.y)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_offset_is_equal_to_another_with_the_same_components() {
assert_eq!(Offset::new(1.0, 2.0), Offset::new(1.0, 2.0));
assert_ne!(Offset::new(1.0, 2.0), Offset::new(2.0, 1.0));
}
#[test]
fn arithmetic_operators_work_component_wise() {
let a = Offset::new(10.0, 20.0);
let b = Offset::new(1.0, 2.0);
assert_eq!(a + b, Offset::new(11.0, 22.0));
assert_eq!(a - b, Offset::new(9.0, 18.0));
assert_eq!(a * 2.0, Offset::new(20.0, 40.0));
assert_eq!(a / 2.0, Offset::new(5.0, 10.0));
assert_eq!(-a, Offset::new(-10.0, -20.0));
assert_eq!(a.scale(2.0, 3.0), Offset::new(20.0, 60.0));
assert_eq!(a.translate(1.0, -1.0), Offset::new(11.0, 19.0));
}
#[test]
fn remainder_follows_dart_and_stays_non_negative() {
assert_eq!(Offset::new(-5.0, 5.0) % 3.0, Offset::new(1.0, 2.0));
assert_eq!(-5.0_f64 % 3.0, -2.0);
}
#[test]
fn truncating_division_rounds_towards_zero() {
assert_eq!(
Offset::new(-5.0, 5.0).truncating_div(3.0),
Offset::new(-1.0, 1.0)
);
}
#[test]
fn distance_and_direction_describe_the_same_vector_as_the_components() {
let offset = Offset::new(3.0, 4.0);
assert_eq!(offset.distance(), 5.0);
assert_eq!(offset.distance_squared(), 25.0);
let rebuilt = Offset::from_direction(offset.direction(), offset.distance());
assert!((rebuilt.dx() - 3.0).abs() < 1e-12);
assert!((rebuilt.dy() - 4.0).abs() < 1e-12);
}
#[test]
fn comparison_is_component_wise_and_only_partial() {
let a = Offset::new(1.0, 2.0);
let b = Offset::new(2.0, 3.0);
assert!(a.lt(&b));
assert!(a.le(&b));
assert!(b.gt(&a));
assert!(b.ge(&a));
let c = Offset::new(1.0, 3.0);
let d = Offset::new(2.0, 2.0);
assert!(!c.lt(&d) && !c.le(&d) && !c.gt(&d) && !c.ge(&d));
let e = Offset::new(1.0, 2.0);
let f = Offset::new(1.0, 3.0);
assert!(e.le(&f));
assert!(!e.lt(&f));
assert_ne!(e, f);
}
#[test]
fn finiteness_asks_about_either_component_or_both() {
assert!(Offset::INFINITE.is_infinite());
assert!(Offset::new(0.0, f64::INFINITY).is_infinite());
assert!(!Offset::new(0.0, f64::NEG_INFINITY).is_infinite());
assert!(!Offset::new(0.0, f64::NAN).is_infinite());
assert!(Offset::ZERO.is_finite());
assert!(!Offset::new(0.0, f64::NAN).is_finite());
}
#[test]
fn lerp_defaults_a_missing_end_to_zero() {
let a = Offset::new(0.0, 10.0);
let b = Offset::new(10.0, 0.0);
assert_eq!(
Offset::lerp(Some(a), Some(b), 0.5),
Some(Offset::new(5.0, 5.0))
);
assert_eq!(
Offset::lerp(Some(a), None, 0.5),
Some(Offset::new(0.0, 5.0))
);
assert_eq!(
Offset::lerp(None, Some(b), 0.5),
Some(Offset::new(5.0, 0.0))
);
assert_eq!(Offset::lerp(None, None, 0.5), None);
}
#[test]
fn debug_matches_flutter_to_string() {
assert_eq!(format!("{:?}", Offset::new(1.0, 2.5)), "Offset(1.0, 2.5)");
}
#[test]
fn size_aspect_ratio_covers_the_zero_and_infinite_arms() {
assert_eq!(Size::new(0.0, 0.0).aspect_ratio(), 0.0);
assert_eq!(Size::new(-0.0, 0.0).aspect_ratio(), 0.0);
assert_eq!(Size::new(1.0, 0.0).aspect_ratio(), f64::INFINITY);
assert_eq!(Size::new(-1.0, 0.0).aspect_ratio(), f64::NEG_INFINITY);
assert_eq!(Size::new(3.0, 2.0).aspect_ratio(), 1.5);
assert_eq!(Size::new(3.0, -2.0).aspect_ratio(), -1.5);
assert_eq!(Size::new(-3.0, 2.0).aspect_ratio(), -1.5);
assert_eq!(Size::new(0.0, -1.0).aspect_ratio(), -0.0);
}
#[test]
fn size_shortest_and_longest_side_use_magnitudes() {
let size = Size::new(-9.0, 4.0);
assert_eq!(size.shortest_side(), 4.0);
assert_eq!(size.longest_side(), 9.0);
}
#[test]
fn subtracting_sizes_gives_the_offset_between_their_corners() {
let offset: Offset = Size::new(5.0, 7.0) - Size::new(3.0, 2.0);
assert_eq!(offset, Offset::new(2.0, 5.0));
let size: Size = Size::new(5.0, 7.0) - Offset::new(3.0, 2.0);
assert_eq!(size, Size::new(2.0, 5.0));
assert_eq!(
Size::new(5.0, 7.0) + Offset::new(3.0, 2.0),
Size::new(8.0, 9.0)
);
}
#[test]
fn size_comparison_operators_are_component_wise() {
let small = Size::new(1.0, 2.0);
let large = Size::new(3.0, 4.0);
let mixed = Size::new(5.0, 1.0);
assert!(small.lt(&large));
assert!(small.le(&small));
assert!(large.gt(&small));
assert!(!small.lt(&mixed), "only one component is smaller");
assert!(!mixed.gt(&small));
}
#[test]
fn size_lerp_matches_darts_null_arms() {
assert_eq!(Size::lerp(None, None, 0.5), None);
assert_eq!(
Size::lerp(Some(Size::new(4.0, 8.0)), None, 0.25),
Some(Size::new(3.0, 6.0))
);
assert_eq!(
Size::lerp(None, Some(Size::new(4.0, 8.0)), 0.25),
Some(Size::new(1.0, 2.0))
);
assert_eq!(
Size::lerp(Some(Size::ZERO), Some(Size::new(4.0, 8.0)), 0.5),
Some(Size::new(2.0, 4.0))
);
}
#[test]
fn size_contains_includes_top_left_and_excludes_bottom_right() {
let size = Size::new(2.0, 2.0);
assert!(size.contains(Offset::ZERO));
assert!(size.contains(Offset::new(1.9, 1.9)));
assert!(!size.contains(Offset::new(2.0, 1.0)));
assert!(!size.contains(Offset::new(-0.1, 1.0)));
}
#[test]
fn rect_accessors() {
let rect = Rect::from_ltrb(1.0, 3.0, 5.0, 7.0);
assert_eq!(rect.left, 1.0);
assert_eq!(rect.top, 3.0);
assert_eq!(rect.right, 5.0);
assert_eq!(rect.bottom, 7.0);
assert_eq!(rect.width(), 4.0);
assert_eq!(rect.height(), 4.0);
assert_eq!(rect.size(), Size::new(4.0, 4.0));
assert_eq!(rect.center(), Offset::new(3.0, 5.0));
}
#[test]
fn rect_from_ltwh_matches_ltrb() {
assert_eq!(
Rect::from_ltwh(1.0, 3.0, 4.0, 4.0),
Rect::from_ltrb(1.0, 3.0, 5.0, 7.0)
);
}
#[test]
fn rect_from_center() {
let rect = Rect::from_center(Offset::new(1.0, 3.0), 5.0, 7.0);
assert_eq!(rect, Rect::from_ltrb(-1.5, -0.5, 3.5, 6.5));
let nan = Rect::from_center(Offset::new(f64::NAN, 3.0), 5.0, 7.0);
assert!(nan.has_nan());
assert_eq!(
Rect::from_points(Offset::new(5.0, 7.0), Offset::new(1.0, 3.0)),
Rect::from_ltrb(1.0, 3.0, 5.0, 7.0)
);
}
#[test]
fn rect_debug_matches_darts_to_string() {
assert_eq!(
format!("{:?}", Rect::from_ltrb(1.0, 3.0, 5.0, 7.0)),
"Rect.fromLTRB(1.0, 3.0, 5.0, 7.0)"
);
assert_eq!(format!("{:?}", Size::new(2.0, 3.0)), "Size(2.0, 3.0)");
}
#[test]
fn an_offset_and_a_size_make_a_rect() {
let from_origin = Offset::ZERO & Size::new(100.0, 100.0);
assert_eq!(from_origin, Rect::from_ltwh(0.0, 0.0, 100.0, 100.0));
let rect = Offset::new(1.0, 2.0) & Size::new(3.0, 4.0);
assert_eq!(rect, Rect::from_ltwh(1.0, 2.0, 3.0, 4.0));
}
#[test]
fn rect_intersect_and_overlaps_agree_on_the_shared_edge() {
let a = Rect::from_ltrb(0.0, 0.0, 10.0, 10.0);
let b = Rect::from_ltrb(5.0, 5.0, 15.0, 15.0);
assert_eq!(a.intersect(b), Rect::from_ltrb(5.0, 5.0, 10.0, 10.0));
assert!(a.overlaps(b));
let touching = Rect::from_ltrb(10.0, 0.0, 20.0, 10.0);
assert!(!a.overlaps(touching), "a shared edge is not an overlap");
assert!(a.intersect(touching).is_empty());
assert_eq!(
a.expand_to_include(touching),
Rect::from_ltrb(0.0, 0.0, 20.0, 10.0)
);
let zero_area = Rect::from_ltrb(-5.0, 2.0, -5.0, 2.0);
assert_eq!(
a.expand_to_include(zero_area),
Rect::from_ltrb(-5.0, 0.0, 10.0, 10.0)
);
}
#[test]
fn rect_contains_includes_top_left_and_excludes_bottom_right() {
let rect = Rect::from_ltrb(1.0, 1.0, 2.0, 2.0);
assert!(rect.contains(Offset::new(1.0, 1.0)));
assert!(!rect.contains(Offset::new(2.0, 2.0)));
assert!(!rect.contains(Offset::new(2.0, 1.5)));
}
#[test]
fn rect_lerp_matches_darts_null_arms() {
let rect = Rect::from_ltrb(4.0, 8.0, 12.0, 16.0);
assert_eq!(Rect::lerp(None, None, 0.5), None);
assert_eq!(
Rect::lerp(Some(rect), None, 0.5),
Some(Rect::from_ltrb(2.0, 4.0, 6.0, 8.0))
);
assert_eq!(
Rect::lerp(None, Some(rect), 0.5),
Some(Rect::from_ltrb(2.0, 4.0, 6.0, 8.0))
);
assert_eq!(
Rect::lerp(Some(Rect::ZERO), Some(rect), 0.5),
Some(Rect::from_ltrb(2.0, 4.0, 6.0, 8.0))
);
}
#[test]
fn rect_shift_translate_inflate_deflate() {
let rect = Rect::from_ltrb(0.0, 0.0, 4.0, 4.0);
assert_eq!(
rect.shift(Offset::new(1.0, 2.0)),
Rect::from_ltrb(1.0, 2.0, 5.0, 6.0)
);
assert_eq!(rect.translate(1.0, 2.0), rect.shift(Offset::new(1.0, 2.0)));
assert_eq!(rect.inflate(1.0), Rect::from_ltrb(-1.0, -1.0, 5.0, 5.0));
assert_eq!(rect.inflate(1.0).deflate(1.0), rect);
}
#[test]
fn radius_clamp_operates_as_expected() {
assert_eq!(
Radius::circular(-100.0).clamp(Some(Radius::ZERO), None),
Radius::circular(0.0)
);
assert_eq!(
Radius::circular(100.0).clamp(None, Some(Radius::circular(10.0))),
Radius::circular(10.0)
);
assert_eq!(
Radius::elliptical(-100.0, 100.0)
.clamp(Some(Radius::ZERO), Some(Radius::circular(10.0))),
Radius::elliptical(0.0, 10.0)
);
assert_eq!(
Radius::elliptical(100.0, -100.0)
.clamp(Some(Radius::ZERO), Some(Radius::circular(10.0))),
Radius::elliptical(10.0, 0.0)
);
}
#[test]
fn radius_clamp_values_operates_as_expected() {
assert_eq!(
Radius::circular(-100.0).clamp_values(Some(0.0), Some(0.0), None, None),
Radius::circular(0.0)
);
assert_eq!(
Radius::circular(100.0).clamp_values(None, None, Some(10.0), Some(20.0)),
Radius::elliptical(10.0, 20.0)
);
assert_eq!(
Radius::elliptical(-100.0, 100.0).clamp_values(
Some(5.0),
Some(6.0),
Some(10.0),
Some(20.0)
),
Radius::elliptical(5.0, 20.0)
);
assert_eq!(
Radius::elliptical(100.0, -100.0).clamp_values(
Some(5.0),
Some(6.0),
Some(10.0),
Some(20.0)
),
Radius::elliptical(10.0, 6.0)
);
}
#[test]
fn radius_arithmetic_is_component_wise() {
let a = Radius::elliptical(10.0, 20.0);
let b = Radius::elliptical(1.0, 2.0);
assert_eq!(a + b, Radius::elliptical(11.0, 22.0));
assert_eq!(a - b, Radius::elliptical(9.0, 18.0));
assert_eq!(a * 2.0, Radius::elliptical(20.0, 40.0));
assert_eq!(a / 2.0, Radius::elliptical(5.0, 10.0));
assert_eq!(-a, Radius::elliptical(-10.0, -20.0));
assert_eq!(a.truncating_div(3.0), Radius::elliptical(3.0, 6.0));
assert_eq!(
Radius::elliptical(-5.0, 5.0) % 3.0,
Radius::elliptical(1.0, 2.0)
);
assert_eq!(
Radius::elliptical(-5.0, 5.0).truncating_div(3.0),
Radius::elliptical(-1.0, 1.0)
);
}
#[test]
fn radius_circular_equals_elliptical_when_axes_match() {
assert_eq!(Radius::circular(5.0), Radius::elliptical(5.0, 5.0));
assert_ne!(Radius::circular(5.0), Radius::elliptical(5.0, 6.0));
assert_eq!(Radius::ZERO, Radius::circular(0.0));
}
#[test]
fn radius_lerp_defaults_a_missing_end_to_zero() {
let a = Radius::elliptical(0.0, 10.0);
let b = Radius::elliptical(10.0, 0.0);
assert_eq!(
Radius::lerp(Some(a), Some(b), 0.5),
Some(Radius::elliptical(5.0, 5.0))
);
assert_eq!(
Radius::lerp(Some(a), None, 0.5),
Some(Radius::elliptical(0.0, 5.0))
);
assert_eq!(
Radius::lerp(None, Some(b), 0.5),
Some(Radius::elliptical(5.0, 0.0))
);
assert_eq!(Radius::lerp(None, None, 0.5), None);
}
#[test]
fn radius_debug_matches_darts_to_string() {
assert_eq!(
format!("{:?}", Radius::circular(1.5)),
"Radius.circular(1.5)"
);
assert_eq!(
format!("{:?}", Radius::elliptical(1.0, 2.5)),
"Radius.elliptical(1.0, 2.5)"
);
}
}