From eb5ae6582e984e3317e7bd2894ffd02ea58ea929 Mon Sep 17 00:00:00 2001 From: Lucas Jansen <7199136+staticintlucas@users.noreply.github.com> Date: Wed, 15 May 2024 23:15:09 +0100 Subject: [PATCH] Increase test coverage --- keyset-drawing/src/imp/key.rs | 66 ++++- keyset-drawing/src/svg.rs | 2 + keyset-geom/src/circle.rs | 54 +++- keyset-geom/src/lib.rs | 3 +- keyset-geom/src/path/mod.rs | 483 ++++++++++++++++++++++++++++++-- keyset-geom/src/path/segment.rs | 289 ++++++++++++++++++- keyset-geom/src/path/to_path.rs | 110 ++++++++ keyset-geom/src/round_rect.rs | 124 +++++--- keyset-geom/src/traits.rs | 66 ++++- keyset-geom/src/unit.rs | 15 +- keyset-key/src/legend.rs | 11 +- keyset-key/src/lib.rs | 44 ++- 12 files changed, 1158 insertions(+), 109 deletions(-) diff --git a/keyset-drawing/src/imp/key.rs b/keyset-drawing/src/imp/key.rs index 6ba49f6..83793af 100644 --- a/keyset-drawing/src/imp/key.rs +++ b/keyset-drawing/src/imp/key.rs @@ -200,6 +200,7 @@ fn step_path(rect: RoundRect) -> Path { #[cfg(test)] mod tests { use isclose::assert_is_close; + use key::Key; use super::*; @@ -208,7 +209,7 @@ mod tests { let options = Options::default(); // Regular 1u key - let key = key::Key::example(); + let key = Key::example(); let path = top(&key, &options); let bounds = path.data.bounds; @@ -218,9 +219,30 @@ mod tests { let top_rect = options.profile.top_with_size(Size::new(1.0, 1.0)); assert_is_close!(bounds, top_rect.rect()); + // None + let key = { + let mut key = Key::example(); + key.shape = key::Shape::None(Size::splat(1.0)); + key + }; + let path = top(&key, &options); + let bounds = path.data.bounds; + assert_is_close!(bounds, Rect::zero()); + + // Homing + let key = { + let mut key = Key::example(); + key.shape = key::Shape::Homing(None); + key + }; + let path = top(&key, &options); + let bounds = path.data.bounds; + let top_rect = options.profile.top_with_size(Size::splat(1.0)); + assert_is_close!(bounds, top_rect.rect()); + // Stepped caps let key = { - let mut key = key::Key::example(); + let mut key = Key::example(); key.shape = key::Shape::SteppedCaps; key }; @@ -231,7 +253,7 @@ mod tests { // ISO enter let key = { - let mut key = key::Key::example(); + let mut key = Key::example(); key.shape = key::Shape::IsoVertical; key }; @@ -243,9 +265,10 @@ mod tests { #[test] fn test_bottom() { - let key = key::Key::example(); let options = Options::default(); + // Regular 1u key + let key = Key::example(); let path = bottom(&key, &options); let bounds = path.data.bounds; @@ -255,9 +278,30 @@ mod tests { let bottom_rect = options.profile.bottom_with_size(Size::new(1.0, 1.0)); assert_is_close!(bounds, bottom_rect.rect()); + // None + let key = { + let mut key = Key::example(); + key.shape = key::Shape::None(Size::splat(1.0)); + key + }; + let path = bottom(&key, &options); + let bounds = path.data.bounds; + assert_is_close!(bounds, Rect::zero()); + + // Homing + let key = { + let mut key = Key::example(); + key.shape = key::Shape::Homing(None); + key + }; + let path = bottom(&key, &options); + let bounds = path.data.bounds; + let bottom_rect = options.profile.bottom_with_size(Size::splat(1.0)); + assert_is_close!(bounds, bottom_rect.rect()); + // Stepped caps let key = { - let mut key = key::Key::example(); + let mut key = Key::example(); key.shape = key::Shape::SteppedCaps; key }; @@ -268,7 +312,7 @@ mod tests { // ISO enter let key = { - let mut key = key::Key::example(); + let mut key = Key::example(); key.shape = key::Shape::IsoVertical; key }; @@ -284,7 +328,7 @@ mod tests { // Scoop let scoop = { - let mut key = key::Key::example(); + let mut key = Key::example(); key.shape = key::Shape::Homing(Some(key::Homing::Scoop)); key }; @@ -294,7 +338,7 @@ mod tests { // Bar let bar = { - let mut key = key::Key::example(); + let mut key = Key::example(); key.shape = key::Shape::Homing(Some(key::Homing::Bar)); key }; @@ -319,7 +363,7 @@ mod tests { // Bump let bump = { - let mut key = key::Key::example(); + let mut key = Key::example(); key.shape = key::Shape::Homing(Some(key::Homing::Bump)); key }; @@ -343,7 +387,7 @@ mod tests { assert_is_close!(bounds, expected); // Non-homing key - let none = key::Key::example(); + let none = Key::example(); let path = homing(&none, &options); assert!(path.is_none()); // No additional feature to draw @@ -352,7 +396,7 @@ mod tests { #[test] fn test_step() { let key = { - let mut key = key::Key::example(); + let mut key = Key::example(); key.shape = key::Shape::SteppedCaps; key }; diff --git a/keyset-drawing/src/svg.rs b/keyset-drawing/src/svg.rs index e26b327..21a274d 100644 --- a/keyset-drawing/src/svg.rs +++ b/keyset-drawing/src/svg.rs @@ -56,7 +56,9 @@ fn draw_path(path: &KeyPath) -> SvgPath { PathSegment::CubicBezier(c1, c2, d) => { fmt_num!("c{} {} {} {} {} {}", c1.x, c1.y, c2.x, c2.y, d.x, d.y) } + // GRCOV_EXCL_START - no quads in example PathSegment::QuadraticBezier(c1, d) => fmt_num!("q{} {} {} {}", c1.x, c1.y, d.x, d.y), + // GRCOV_EXCL_STOP PathSegment::Close => "z".into(), }) .collect(); diff --git a/keyset-geom/src/circle.rs b/keyset-geom/src/circle.rs index f905a52..063ea71 100644 --- a/keyset-geom/src/circle.rs +++ b/keyset-geom/src/circle.rs @@ -1,11 +1,11 @@ use std::borrow::Borrow; +use std::fmt; use isclose::IsClose; use crate::{Length, Point}; /// A circle -#[derive(Debug, PartialEq)] pub struct Circle { /// Center point pub center: Point, @@ -13,7 +13,7 @@ pub struct Circle { pub radius: Length, } -// Impl here rather than derive so we don't require U: Clone everywhere +// Impl here rather than derive so we don't require U: Clone impl Clone for Circle { #[inline] fn clone(&self) -> Self { @@ -21,8 +21,26 @@ impl Clone for Circle { } } +// Impl here rather than derive so we don't require U: Copy impl Copy for Circle {} +// Impl here rather than derive so we don't require U: PartialEq +impl PartialEq for Circle { + fn eq(&self, other: &Self) -> bool { + self.center.eq(&other.center) && self.radius.eq(&other.radius) + } +} + +// Impl here rather than derive so we don't require U: Debug +impl fmt::Debug for Circle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Circle") + .field("center", &self.center) + .field("radius", &self.radius) + .finish() + } +} + impl Circle { /// Create a new circle with the given center and radius. #[inline] @@ -63,6 +81,38 @@ mod tests { use super::*; + #[test] + fn circle_clone() { + struct NonCloneable; + let circle = Circle:: { + center: Point::new(1.0, 2.0), + radius: Length::new(1.0), + }; + + #[allow(clippy::clone_on_copy)] // We want to test clone, not copy + let circle2 = circle.clone(); + + assert_is_close!(circle, circle2); + } + + #[test] + fn circle_partial_eq() { + struct NonPartialEq; + let circle = Circle::::new(Point::new(1.0, 2.0), Length::new(1.0)); + let circle2 = circle; + + assert_eq!(circle, circle2); + } + + #[test] + fn circle_debug() { + struct NonDebug; + let circle = Circle::::new(Point::new(1.0, 2.0), Length::new(1.0)); + let dbg = format!("{circle:?}"); + + assert_eq!(dbg, "Circle { center: (1.0, 2.0), radius: 1.0 }"); + } + #[test] fn circle_new() { let circle = Circle::<()>::new(Point::new(1.0, 2.0), Length::new(0.5)); diff --git a/keyset-geom/src/lib.rs b/keyset-geom/src/lib.rs index afc06ab..3c54943 100644 --- a/keyset-geom/src/lib.rs +++ b/keyset-geom/src/lib.rs @@ -14,8 +14,7 @@ pub use path::{Path, PathBuilder, PathSegment, ToPath}; pub use round_rect::RoundRect; pub use traits::*; pub use unit::{ - Dot, Inch, Mm, ToTransform, Unit, DOT_PER_INCH, DOT_PER_MM, DOT_PER_UNIT, INCH_PER_UNIT, - MM_PER_UNIT, + Dot, Inch, Mm, Unit, DOT_PER_INCH, DOT_PER_MM, DOT_PER_UNIT, INCH_PER_UNIT, MM_PER_UNIT, }; /// An angle in radians diff --git a/keyset-geom/src/path/mod.rs b/keyset-geom/src/path/mod.rs index 9230464..9a19658 100644 --- a/keyset-geom/src/path/mod.rs +++ b/keyset-geom/src/path/mod.rs @@ -168,7 +168,7 @@ impl IntoIterator for Path { #[inline] fn into_iter(self) -> Self::IntoIter { - // into_vec is needed here, see rust-lang/rust#59878 + // TODO into_vec is needed here but is essentially free; see rust-lang/rust#59878 self.data.into_vec().into_iter() } } @@ -254,6 +254,13 @@ impl Clone for PathBuilder { } } +impl Default for PathBuilder { + #[inline] + fn default() -> Self { + Self::new() + } +} + impl PathBuilder { /// Create a new [`PathBuilder`] #[inline] @@ -450,13 +457,6 @@ impl PathBuilder { } } -impl Default for PathBuilder { - #[inline] - fn default() -> Self { - Self::new() - } -} - impl Add for PathBuilder { type Output = Self; @@ -498,6 +498,7 @@ fn calculate_bounds(data: &[PathSegment]) -> Rect { #[cfg(test)] mod tests { + use euclid::Scale; use isclose::assert_is_close; use super::*; @@ -505,19 +506,421 @@ mod tests { use crate::{Angle, Size}; #[test] - fn test_path_new() { - let paths: Vec> = vec![PathBuilder::new(), PathBuilder::default()]; + fn test_path_clone() { + let path = Path::<()> { + data: Box::new([ + PathSegment::Move(Point::zero()), + PathSegment::Line(Vector::one()), + PathSegment::CubicBezier( + Vector::new(1.0, 0.0), + Vector::new(2.0, 1.0), + Vector::splat(2.0), + ), + PathSegment::Close, + ]), + bounds: Rect::new(Point::zero(), Point::splat(3.0)), + }; + + #[allow(clippy::redundant_clone)] // We want to test clone + let path2 = path.clone(); + + assert_eq!(path.data.len(), path2.data.len()); + assert_is_close!(path.bounds, path2.bounds); + } + + #[test] + fn test_path_empty() { + let paths = [Path::<()>::default(), Path::empty()]; for path in paths { assert!(path.data.is_empty()); - assert_is_close!(path.start, Point::origin()); - assert_is_close!(path.point, Point::origin()); assert_is_close!(path.bounds, Rect::zero()); } } #[test] - fn test_path_add() { + fn test_path_from_slice() { + let paths = [ + Path::<()>::empty(), + Path { + data: Box::new([ + PathSegment::Move(Point::zero()), + PathSegment::Line(Vector::one()), + ]), + bounds: Rect::from_size(Size::splat(1.0)), + }, + Path { + data: Box::new([PathSegment::CubicBezier( + Vector::new(0.5, 0.0), + Vector::new(1.0, 0.5), + Vector::splat(1.0), + )]), + bounds: Rect::from_size(Size::splat(1.0)), + }, + ]; + + let expected = Path { + data: Box::new([ + PathSegment::Move(Point::zero()), + PathSegment::Move(Point::zero()), + PathSegment::Line(Vector::one()), + PathSegment::Move(Point::zero()), + PathSegment::CubicBezier( + Vector::new(0.5, 0.0), + Vector::new(1.0, 0.5), + Vector::splat(1.0), + ), + ]), + bounds: Rect::from_size(Size::splat(1.0)), + }; + + let path = Path::from_slice(&paths); + + assert_is_close!(path.bounds, expected.bounds); + for (p, e) in path.data.iter().zip(expected.data.iter()) { + assert_is_close!(p, e); + } + } + + #[test] + fn test_path_len() { + let path = Path::<()> { + data: Box::new([ + PathSegment::Move(Point::zero()), + PathSegment::Line(Vector::one()), + PathSegment::CubicBezier( + Vector::new(1.0, 0.0), + Vector::new(2.0, 1.0), + Vector::splat(2.0), + ), + PathSegment::Close, + ]), + bounds: Rect::new(Point::zero(), Point::splat(3.0)), + }; + + assert_eq!(path.len(), path.data.len()); + } + + #[test] + fn test_path_is_empty() { + let path = Path::<()> { + data: Box::new([ + PathSegment::Move(Point::zero()), + PathSegment::Line(Vector::one()), + PathSegment::CubicBezier( + Vector::new(1.0, 0.0), + Vector::new(2.0, 1.0), + Vector::splat(2.0), + ), + PathSegment::Close, + ]), + bounds: Rect::new(Point::zero(), Point::splat(3.0)), + }; + + assert!(Path::<()>::empty().is_empty()); + assert!(!path.is_empty()); + } + + #[test] + fn test_path_translate() { + let path = Path::<()> { + data: Box::new([ + PathSegment::Move(Point::zero()), + PathSegment::Line(Vector::one()), + PathSegment::CubicBezier( + Vector::new(1.0, 0.0), + Vector::new(2.0, 1.0), + Vector::splat(2.0), + ), + PathSegment::Close, + ]), + bounds: Rect::new(Point::zero(), Point::splat(3.0)), + } + .translate(Vector::new(1.0, 2.0)); + + let exp = Path::<()> { + data: Box::new([ + PathSegment::Move(Point::new(1.0, 2.0)), + PathSegment::Line(Vector::one()), + PathSegment::CubicBezier( + Vector::new(1.0, 0.0), + Vector::new(2.0, 1.0), + Vector::splat(2.0), + ), + PathSegment::Close, + ]), + bounds: Rect::new(Point::new(1.0, 2.0), Point::new(4.0, 5.0)), + }; + + assert_is_close!(path.bounds, exp.bounds); + for (p, e) in path.data.iter().zip(exp.data.iter()) { + assert_is_close!(p, e); + } + } + + #[test] + fn test_path_scale() { + let path = Path::<()> { + data: Box::new([ + PathSegment::Move(Point::zero()), + PathSegment::Line(Vector::one()), + PathSegment::CubicBezier( + Vector::new(1.0, 0.0), + Vector::new(2.0, 1.0), + Vector::splat(2.0), + ), + PathSegment::Close, + ]), + bounds: Rect::new(Point::zero(), Point::splat(3.0)), + } + .scale(0.5, 0.5); + + let exp = Path::<()> { + data: Box::new([ + PathSegment::Move(Point::zero()), + PathSegment::Line(Vector::splat(0.5)), + PathSegment::CubicBezier( + Vector::new(0.5, 0.0), + Vector::new(1.0, 0.5), + Vector::splat(1.0), + ), + PathSegment::Close, + ]), + bounds: Rect::new(Point::zero(), Point::splat(1.5)), + }; + + assert_is_close!(path.bounds, exp.bounds); + for (p, e) in path.data.iter().zip(exp.data.iter()) { + assert_is_close!(p, e); + } + } + + #[test] + fn test_path_iter() { + let path = Path::<()> { + data: Box::new([ + PathSegment::Move(Point::zero()), + PathSegment::Line(Vector::one()), + PathSegment::CubicBezier( + Vector::new(1.0, 0.0), + Vector::new(2.0, 1.0), + Vector::splat(2.0), + ), + PathSegment::Close, + ]), + bounds: Rect::new(Point::zero(), Point::splat(3.0)), + }; + + for (p1, p2) in path.iter().zip(path.data.iter()) { + assert!(std::ptr::eq(p1, p2)); + } + } + + #[test] + fn test_path_iter_mut() { + let path = Path::<()> { + data: Box::new([ + PathSegment::Move(Point::zero()), + PathSegment::Line(Vector::one()), + PathSegment::CubicBezier( + Vector::new(1.0, 0.0), + Vector::new(2.0, 1.0), + Vector::splat(2.0), + ), + PathSegment::Close, + ]), + bounds: Rect::new(Point::zero(), Point::splat(3.0)), + }; + + let mut path2 = path.clone(); + path2.iter_mut().for_each(|seg| *seg = seg.scale(2.0, 2.0)); + + for (p1, p2) in path.data.iter().zip(path2.data.iter()) { + assert_is_close!(p1.scale(2.0, 2.0), p2); + } + } + + #[test] + fn test_path_into_iter() { + let path = Path::<()> { + data: Box::new([ + PathSegment::Move(Point::zero()), + PathSegment::Line(Vector::one()), + PathSegment::CubicBezier( + Vector::new(1.0, 0.0), + Vector::new(2.0, 1.0), + Vector::splat(2.0), + ), + PathSegment::Close, + ]), + bounds: Rect::new(Point::zero(), Point::splat(3.0)), + }; + + for (p1, p2) in (&path).into_iter().zip(path.data.iter()) { + assert!(std::ptr::eq(p1, p2)); + } + + let mut path2 = path.clone(); + (&mut path2) + .into_iter() + .for_each(|seg| *seg = seg.scale(2.0, 2.0)); + + for (p1, p2) in path.data.iter().zip(path2.data.iter()) { + assert_is_close!(p1.scale(2.0, 2.0), p2); + } + + let data = path.data.to_vec(); + + for (p1, p2) in path.into_iter().zip(data.iter()) { + assert_is_close!(p1, p2); + } + } + + #[test] + fn test_path_mul() { + let path = Path::<()> { + data: Box::new([ + PathSegment::Move(Point::zero()), + PathSegment::Line(Vector::one()), + PathSegment::CubicBezier( + Vector::new(1.0, 0.0), + Vector::new(2.0, 1.0), + Vector::splat(2.0), + ), + PathSegment::Close, + ]), + bounds: Rect::new(Point::zero(), Point::splat(3.0)), + }; + let scale = Scale::new(2.0); + let transform = Transform::new(2.0, 0.5, 1.5, 1.0, 2.0, 3.0); + + let path2 = path.clone() * scale; + for (p1, p2) in path.data.iter().zip(path2.data.iter()) { + assert_is_close!(p1.scale(scale.get(), scale.get()), p2); + } + + let mut path2 = path.clone(); + path2 *= scale; + + for (p1, p2) in path.data.iter().zip(path2.data.iter()) { + assert_is_close!(p1.scale(scale.get(), scale.get()), p2); + } + + let path2 = path.clone() * transform; + + for (p1, p2) in path.data.iter().zip(path2.data.iter()) { + assert_is_close!(*p1 * transform, p2); + } + + let mut path2 = path.clone(); + path2 *= transform; + + for (p1, p2) in path.data.iter().zip(path2.data.iter()) { + assert_is_close!(*p1 * transform, p2); + } + } + + #[test] + fn test_path_div() { + let path = Path::<()> { + data: Box::new([ + PathSegment::Move(Point::zero()), + PathSegment::Line(Vector::one()), + PathSegment::CubicBezier( + Vector::new(1.0, 0.0), + Vector::new(2.0, 1.0), + Vector::splat(2.0), + ), + PathSegment::Close, + ]), + bounds: Rect::new(Point::zero(), Point::splat(3.0)), + }; + let scale = Scale::new(2.0); + + let path2 = path.clone() / scale; + for (p1, p2) in path.data.iter().zip(path2.data.iter()) { + assert_is_close!(p1.scale(1.0 / scale.get(), 1.0 / scale.get()), p2); + } + + let mut path2 = path.clone(); + path2 /= scale; + + for (p1, p2) in path.data.iter().zip(path2.data.iter()) { + assert_is_close!(p1.scale(1.0 / scale.get(), 1.0 / scale.get()), p2); + } + } + + #[test] + fn test_path_builder_clone() { + let builder = PathBuilder::<()> { + data: vec![ + PathSegment::Move(Point::zero()), + PathSegment::Line(Vector::one()), + PathSegment::CubicBezier( + Vector::new(1.0, 0.0), + Vector::new(2.0, 1.0), + Vector::splat(2.0), + ), + PathSegment::Close, + ], + start: Point::zero(), + point: Point::zero(), + bounds: Rect::new(Point::zero(), Point::splat(3.0)), + }; + + #[allow(clippy::redundant_clone)] // We want to test clone + let builder2 = builder.clone(); + + assert_eq!(builder.data.len(), builder2.data.len()); + assert_is_close!(builder.start, builder2.start); + assert_is_close!(builder.point, builder2.point); + assert_is_close!(builder.bounds, builder2.bounds); + } + + #[test] + fn test_path_builder_new() { + let builders = [ + PathBuilder::<()>::new(), + PathBuilder::default(), + PathBuilder::with_capacity(0), + Path::builder(), + Path::builder_with_capacity(0), + ]; + + for builder in builders { + assert!(builder.data.is_empty()); + assert_is_close!(builder.start, Point::origin()); + assert_is_close!(builder.point, Point::origin()); + assert_is_close!(builder.bounds, Rect::zero()); + } + } + + #[test] + fn test_path_builder_build() { + let builder = PathBuilder::<()> { + data: vec![ + PathSegment::Move(Point::zero()), + PathSegment::Line(Vector::one()), + PathSegment::CubicBezier( + Vector::new(1.0, 0.0), + Vector::new(2.0, 1.0), + Vector::splat(2.0), + ), + PathSegment::Close, + ], + start: Point::zero(), + point: Point::zero(), + bounds: Rect::new(Point::zero(), Point::splat(3.0)), + }; + + let path = builder.clone().build(); + + assert_eq!(builder.data.len(), path.data.len()); + assert_is_close!(builder.bounds, path.bounds); + } + + #[test] + fn test_path_builder_extend() { let empty = PathBuilder::<()>::new(); let mut line1 = PathBuilder::new(); line1.abs_line(Point::new(1.0, 1.0)); @@ -548,13 +951,13 @@ mod tests { (line1.clone(), line3.clone(), cross.clone()), ]; - for (first, second, expected) in params { - let result = first.add(second); + for (mut first, second, expected) in params { + first.extend(second); - assert_eq!(result.data.len(), expected.data.len()); - assert_is_close!(result.start, expected.start); - assert_is_close!(result.point, expected.point); - assert_is_close!(result.bounds, expected.bounds); + assert_eq!(first.data.len(), expected.data.len()); + assert_is_close!(first.start, expected.start); + assert_is_close!(first.point, expected.point); + assert_is_close!(first.bounds, expected.bounds); } } @@ -636,4 +1039,46 @@ mod tests { assert_is_close!(path.bounds, Rect::from_size(Size::splat(2.0))); } } + + #[test] + fn test_path_builder_add() { + let empty = PathBuilder::<()>::new(); + let mut line1 = PathBuilder::new(); + line1.abs_line(Point::new(1.0, 1.0)); + + let mut line2 = PathBuilder::new(); + line2.abs_line(Point::new(1.0, 0.0)); + + let mut line3 = PathBuilder::new(); + line3.abs_move(Point::new(0.0, 1.0)); + line3.abs_line(Point::new(1.0, 0.0)); + + let mut angle = PathBuilder::new(); + angle.abs_line(Point::new(1.0, 1.0)); + angle.abs_move(Point::zero()); + angle.abs_line(Point::new(1.0, 0.0)); + + let mut cross = PathBuilder::new(); + cross.abs_line(Point::new(1.0, 1.0)); + cross.abs_move(Point::new(0.0, 1.0)); + cross.abs_line(Point::new(1.0, 0.0)); + + #[allow(clippy::redundant_clone)] + let params = [ + (empty.clone(), empty.clone(), empty.clone()), + (line1.clone(), empty.clone(), line1.clone()), + (empty.clone(), line1.clone(), line1.clone()), + (line1.clone(), line2.clone(), angle.clone()), + (line1.clone(), line3.clone(), cross.clone()), + ]; + + for (first, second, expected) in params { + let result = first + second; + + assert_eq!(result.data.len(), expected.data.len()); + assert_is_close!(result.start, expected.start); + assert_is_close!(result.point, expected.point); + assert_is_close!(result.bounds, expected.bounds); + } + } } diff --git a/keyset-geom/src/path/segment.rs b/keyset-geom/src/path/segment.rs index 0bcdb5f..a63f4b5 100644 --- a/keyset-geom/src/path/segment.rs +++ b/keyset-geom/src/path/segment.rs @@ -1,5 +1,6 @@ use std::{ borrow::Borrow, + fmt, ops::{Div, DivAssign, Mul, MulAssign}, }; @@ -9,7 +10,6 @@ use PathSegment::{Close, CubicBezier, Line, Move, QuadraticBezier}; use crate::{Point, Scale, Transform, Vector}; /// Enum representing a path segment -#[derive(Debug, PartialEq)] pub enum PathSegment { /// Move to a point Move(Point), @@ -33,6 +33,38 @@ impl Clone for PathSegment { impl Copy for PathSegment {} +impl PartialEq for PathSegment { + fn eq(&self, other: &Self) -> bool { + match (*self, *other) { + (Move(s), Move(o)) => s == o, + (Line(s), Line(o)) => s == o, + (CubicBezier(s1, s2, s), CubicBezier(o1, o2, o)) => s1 == o1 && s2 == o2 && s == o, + (QuadraticBezier(s1, s), QuadraticBezier(o1, o)) => s1 == o1 && s == o, + (Close, Close) => true, + _ => false, + } + } +} + +impl fmt::Debug for PathSegment { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Move(ref p) => f.debug_tuple("Move").field(p).finish(), + Line(ref v) => f.debug_tuple("Line").field(v).finish(), + CubicBezier(ref v1, ref v2, ref v) => f + .debug_tuple("CubicBezier") + .field(v1) + .field(v2) + .field(v) + .finish(), + QuadraticBezier(ref v1, ref v) => { + f.debug_tuple("QuadraticBezier").field(v1).field(v).finish() + } + Close => f.debug_tuple("Close").finish(), + } + } +} + impl IsClose for PathSegment { const ABS_TOL: f32 = ::ABS_TOL; const REL_TOL: f32 = ::REL_TOL; @@ -220,7 +252,120 @@ mod tests { use super::*; #[test] - fn test_translate() { + fn path_seg_clone() { + struct NonCloneable; + let segs = [ + Move(Point::::new(1.0, 1.0)), + Line(Vector::new(1.0, 1.0)), + CubicBezier( + Vector::new(0.0, 0.5), + Vector::new(0.5, 1.0), + Vector::new(1.0, 1.0), + ), + QuadraticBezier(Vector::new(0.0, 1.0), Vector::new(1.0, 1.0)), + Close, + ]; + #[allow(clippy::clone_on_copy)] // We want to test clone, not copy + let result = segs.map(|s| s.clone()); + + for (seg, res) in segs.into_iter().zip(result) { + assert_is_close!(seg, res); + } + } + + #[test] + fn path_seg_partial_eq() { + struct NonPartialEq; + let segs = [ + Move(Point::::new(1.0, 1.0)), + Line(Vector::new(1.0, 1.0)), + CubicBezier( + Vector::new(0.0, 0.5), + Vector::new(0.5, 1.0), + Vector::new(1.0, 1.0), + ), + QuadraticBezier(Vector::new(0.0, 1.0), Vector::new(1.0, 1.0)), + Close, + ]; + let segs2 = segs; + + for (seg, seg2) in segs.into_iter().zip(segs2) { + assert_eq!(seg, seg2); + } + + let segs2 = { + let mut tmp = segs2; + tmp.rotate_right(1); + tmp + }; + + for (seg, seg2) in segs.into_iter().zip(segs2) { + assert_ne!(seg, seg2); + } + } + + #[test] + fn path_seg_debug() { + struct NonDebug; + let segs = [ + Move(Point::::new(1.0, 1.0)), + Line(Vector::new(1.0, 1.0)), + CubicBezier( + Vector::new(0.0, 0.5), + Vector::new(0.5, 1.0), + Vector::new(1.0, 1.0), + ), + QuadraticBezier(Vector::new(0.0, 1.0), Vector::new(1.0, 1.0)), + Close, + ]; + let dbg = segs.map(|s| format!("{s:?}")); + let exp = [ + "Move((1.0, 1.0))", + "Line((1.0, 1.0))", + "CubicBezier((0.0, 0.5), (0.5, 1.0), (1.0, 1.0))", + "QuadraticBezier((0.0, 1.0), (1.0, 1.0))", + "Close", + ]; + + assert_eq!(dbg.len(), exp.len()); + for (d, e) in dbg.into_iter().zip(exp) { + assert_eq!(d, e); + } + } + + #[test] + fn path_seg_is_close() { + struct NonPartialEq; + let segs = [ + Move(Point::::new(1.0, 1.0)), + Line(Vector::new(1.0, 1.0)), + CubicBezier( + Vector::new(0.0, 0.5), + Vector::new(0.5, 1.0), + Vector::new(1.0, 1.0), + ), + QuadraticBezier(Vector::new(0.0, 1.0), Vector::new(1.0, 1.0)), + Close, + ]; + let segs2 = segs.map(|s| s.scale(3.0, 3.0).scale(1.0 / 3.0, 1.0 / 3.0)); + + for (seg, seg2) in segs.into_iter().zip(segs2) { + assert!(seg.is_close(seg2)); + } + + let segs2 = { + let mut tmp = segs2; + tmp.rotate_right(1); + tmp + }; + + for (seg, seg2) in segs.into_iter().zip(segs2) { + assert!(!seg.is_close(seg2)); + } + } + + #[test] + fn path_seg_translate() { let input = vec![ Move(Point::<()>::new(1.0, 1.0)), Line(Vector::new(1.0, 1.0)), @@ -250,4 +395,144 @@ mod tests { assert_is_close!(res, exp); } } + + #[test] + fn path_seg_scale() { + let input = vec![ + Move(Point::<()>::new(1.0, 1.0)), + Line(Vector::new(1.0, 1.0)), + CubicBezier( + Vector::new(0.0, 0.5), + Vector::new(0.5, 1.0), + Vector::new(1.0, 1.0), + ), + QuadraticBezier(Vector::new(0.0, 1.0), Vector::new(1.0, 1.0)), + Close, + ]; + let expected = vec![ + Move(Point::<()>::new(2.0, 3.0)), + Line(Vector::new(2.0, 3.0)), + CubicBezier( + Vector::new(0.0, 1.5), + Vector::new(1.0, 3.0), + Vector::new(2.0, 3.0), + ), + QuadraticBezier(Vector::new(0.0, 3.0), Vector::new(2.0, 3.0)), + Close, + ]; + + assert_eq!(input.len(), expected.len()); + for (inp, exp) in input.into_iter().zip(expected) { + let res = inp.scale(2.0, 3.0); + assert_is_close!(res, exp); + } + } + + #[test] + fn path_seg_mul_scale() { + let input = vec![ + Move(Point::<()>::new(1.0, 1.0)), + Line(Vector::new(1.0, 1.0)), + CubicBezier( + Vector::new(0.0, 0.5), + Vector::new(0.5, 1.0), + Vector::new(1.0, 1.0), + ), + QuadraticBezier(Vector::new(0.0, 1.0), Vector::new(1.0, 1.0)), + Close, + ]; + let expected = vec![ + Move(Point::<()>::new(2.0, 2.0)), + Line(Vector::new(2.0, 2.0)), + CubicBezier( + Vector::new(0.0, 1.0), + Vector::new(1.0, 2.0), + Vector::new(2.0, 2.0), + ), + QuadraticBezier(Vector::new(0.0, 2.0), Vector::new(2.0, 2.0)), + Close, + ]; + + assert_eq!(input.len(), expected.len()); + for (inp, exp) in input.into_iter().zip(expected) { + let res = inp * Scale::new(2.0); + assert_is_close!(res, exp); + + let mut res = inp; + res *= Scale::new(2.0); + assert_is_close!(res, exp); + } + } + + #[test] + fn path_seg_mul_transform() { + let input = vec![ + Move(Point::<()>::new(1.0, 1.0)), + Line(Vector::new(1.0, 1.0)), + CubicBezier( + Vector::new(0.0, 0.5), + Vector::new(0.5, 1.0), + Vector::new(1.0, 1.0), + ), + QuadraticBezier(Vector::new(0.0, 1.0), Vector::new(1.0, 1.0)), + Close, + ]; + let expected = vec![ + Move(Point::<()>::new(3.0, 3.0)), + Line(Vector::new(2.0, 2.0)), + CubicBezier( + Vector::new(0.0, 1.0), + Vector::new(1.0, 2.0), + Vector::new(2.0, 2.0), + ), + QuadraticBezier(Vector::new(0.0, 2.0), Vector::new(2.0, 2.0)), + Close, + ]; + + assert_eq!(input.len(), expected.len()); + for (inp, exp) in input.into_iter().zip(expected) { + let res = inp * Transform::new(2.0, 0.0, 0.0, 2.0, 1.0, 1.0); + assert_is_close!(res, exp); + + let mut res = inp; + res *= Transform::new(2.0, 0.0, 0.0, 2.0, 1.0, 1.0); + assert_is_close!(res, exp); + } + } + + #[test] + fn path_seg_div_scale() { + let input = vec![ + Move(Point::<()>::new(1.0, 1.0)), + Line(Vector::new(1.0, 1.0)), + CubicBezier( + Vector::new(0.0, 0.5), + Vector::new(0.5, 1.0), + Vector::new(1.0, 1.0), + ), + QuadraticBezier(Vector::new(0.0, 1.0), Vector::new(1.0, 1.0)), + Close, + ]; + let expected = vec![ + Move(Point::<()>::new(0.5, 0.5)), + Line(Vector::new(0.5, 0.5)), + CubicBezier( + Vector::new(0.0, 0.25), + Vector::new(0.25, 0.5), + Vector::new(0.5, 0.5), + ), + QuadraticBezier(Vector::new(0.0, 0.5), Vector::new(0.5, 0.5)), + Close, + ]; + + assert_eq!(input.len(), expected.len()); + for (inp, exp) in input.into_iter().zip(expected) { + let res = inp / Scale::new(2.0); + assert_is_close!(res, exp); + + let mut res = inp; + res /= Scale::new(2.0); + assert_is_close!(res, exp); + } + } } diff --git a/keyset-geom/src/path/to_path.rs b/keyset-geom/src/path/to_path.rs index 53fe5da..41839f3 100644 --- a/keyset-geom/src/path/to_path.rs +++ b/keyset-geom/src/path/to_path.rs @@ -67,3 +67,113 @@ impl ToPath for RoundRect { builder.build() } } + +#[cfg(test)] +mod tests { + use isclose::assert_is_close; + + use crate::{PathSegment, Point}; + + use super::*; + + #[test] + fn circle_to_path() { + let circle = Circle::<()>::new(Point::new(1.5, 2.0), Length::new(1.0)); + let path = circle.to_path(); + + let a = (4.0 / 3.0) * Angle::degrees(90.0 / 4.0).radians.tan(); + let exp = [ + PathSegment::<()>::Move(Point::new(0.5, 2.0)), + PathSegment::CubicBezier( + Vector::new(0.0, -a), + Vector::new(1.0 - a, -1.0), + Vector::new(1.0, -1.0), + ), + PathSegment::CubicBezier( + Vector::new(a, 0.0), + Vector::new(1.0, 1.0 - a), + Vector::new(1.0, 1.0), + ), + PathSegment::CubicBezier( + Vector::new(0.0, a), + Vector::new(-(1.0 - a), 1.0), + Vector::new(-1.0, 1.0), + ), + PathSegment::CubicBezier( + Vector::new(-a, 0.0), + Vector::new(-1.0, -(1.0 - a)), + Vector::new(-1.0, -1.0), + ), + PathSegment::Close, + ]; + let bounds = Rect::new(Point::new(0.5, 1.0), Point::new(2.5, 3.0)); + + assert_eq!(path.data.len(), exp.len()); + assert_is_close!(path.bounds, bounds); + for (el, ex) in path.data.iter().zip(exp) { + assert_is_close!(el, ex); + } + } + + #[test] + fn rect_to_path() { + let rect = Rect::<()>::new(Point::new(1.0, 2.0), Point::new(3.0, 4.0)); + let path = rect.to_path(); + + let exp = [ + PathSegment::<()>::Move(Point::new(1.0, 2.0)), + PathSegment::Line(Vector::new(2.0, 0.0)), + PathSegment::Line(Vector::new(0.0, 2.0)), + PathSegment::Line(Vector::new(-2.0, 0.0)), + PathSegment::Close, + ]; + + assert_eq!(path.data.len(), exp.len()); + assert_is_close!(path.bounds, rect); + for (el, ex) in path.data.iter().zip(exp) { + assert_is_close!(el, ex); + } + } + + #[test] + fn round_rect_to_path() { + let rect = + RoundRect::<()>::new(Point::new(2.0, 4.0), Point::new(6.0, 8.0), Length::new(1.0)); + let path = rect.to_path(); + + let a = (4.0 / 3.0) * Angle::degrees(90.0 / 4.0).radians.tan(); + let exp = [ + PathSegment::<()>::Move(Point::new(2.0, 5.0)), + PathSegment::CubicBezier( + Vector::new(0.0, -a), + Vector::new(1.0 - a, -1.0), + Vector::new(1.0, -1.0), + ), + PathSegment::Line(Vector::new(2.0, 0.0)), + PathSegment::CubicBezier( + Vector::new(a, 0.0), + Vector::new(1.0, 1.0 - a), + Vector::new(1.0, 1.0), + ), + PathSegment::Line(Vector::new(0.0, 2.0)), + PathSegment::CubicBezier( + Vector::new(0.0, a), + Vector::new(-(1.0 - a), 1.0), + Vector::new(-1.0, 1.0), + ), + PathSegment::Line(Vector::new(-2.0, 0.0)), + PathSegment::CubicBezier( + Vector::new(-a, 0.0), + Vector::new(-1.0, -(1.0 - a)), + Vector::new(-1.0, -1.0), + ), + PathSegment::Close, + ]; + + assert_eq!(path.data.len(), exp.len()); + assert_is_close!(path.bounds, rect.rect()); + for (el, ex) in path.data.iter().zip(exp) { + assert_is_close!(el, ex); + } + } +} diff --git a/keyset-geom/src/round_rect.rs b/keyset-geom/src/round_rect.rs index 9fbfb1a..f240811 100644 --- a/keyset-geom/src/round_rect.rs +++ b/keyset-geom/src/round_rect.rs @@ -1,12 +1,11 @@ use std::borrow::Borrow; +use std::fmt; use isclose::IsClose; use crate::{ExtRect, Length, Point, Rect, Size}; -/// A rectangle with rounded corners. Unlike [`kurbo::RoundedRect`] this has one set of radii -/// shared between all corners, but it does support elliptical corners. -#[derive(Debug, PartialEq)] +/// A rectangle with rounded corners pub struct RoundRect { /// Minimum point pub min: Point, @@ -16,7 +15,7 @@ pub struct RoundRect { pub radius: Length, } -// Impl here rather than derive so we don't require U: Clone everywhere +// Impl here rather than derive so we don't require U: Clone impl Clone for RoundRect { #[inline] fn clone(&self) -> Self { @@ -24,8 +23,27 @@ impl Clone for RoundRect { } } +// Impl here rather than derive so we don't require U: Copy impl Copy for RoundRect {} +// Impl here rather than derive so we don't require U: PartialEq +impl PartialEq for RoundRect { + fn eq(&self, other: &Self) -> bool { + self.min.eq(&other.min) && self.max.eq(&other.max) && self.radius.eq(&other.radius) + } +} + +// Impl here rather than derive so we don't require U: Debug +impl fmt::Debug for RoundRect { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RoundRect") + .field("min", &self.min) + .field("max", &self.max) + .field("radius", &self.radius) + .finish() + } +} + impl RoundRect { /// Create a new rounded rectangle from minimum and maximum coordinates. #[inline] @@ -34,7 +52,7 @@ impl RoundRect { Self { min, max, radius } } - /// Create a new rounded rectangle from a [`kurbo::Rect`] and its radii. + /// Create a new rounded rectangle from a [`crate::Rect`] and its radii. #[inline] #[must_use] pub const fn from_rect(rect: Rect, radius: Length) -> Self { @@ -127,63 +145,99 @@ mod tests { use super::*; #[test] - fn test_round_rect_new() { + fn round_rect_clone() { + struct NonCloneable; + let rect = RoundRect:: { + min: Point::origin(), + max: Point::new(1.0, 2.0), + radius: Length::new(0.5), + }; + + #[allow(clippy::clone_on_copy)] // We want to test clone, not copy + let rect2 = rect.clone(); + + assert_is_close!(rect, rect2); + } + + #[test] + fn round_rect_partial_eq() { + struct NonPartialEq; + let rect = RoundRect:: { + min: Point::origin(), + max: Point::new(1.0, 2.0), + radius: Length::new(0.5), + }; + let rect2 = rect; + + assert_eq!(rect, rect2); + } + + #[test] + fn round_rect_debug() { + struct NonDebug; + let rect = RoundRect::::new( + Point::new(1.0, 2.0), + Point::new(3.0, 5.0), + Length::new(0.5), + ); + let dbg = format!("{rect:?}"); + + assert_eq!( + dbg, + "RoundRect { min: (1.0, 2.0), max: (3.0, 5.0), radius: 0.5 }" + ); + } + + #[test] + fn round_rect_new() { let rect = RoundRect::<()>::new(Point::new(1.0, 2.0), Point::new(3.0, 5.0), Length::new(0.5)); - assert_is_close!(rect.min.x, 1.0); - assert_is_close!(rect.min.y, 2.0); - assert_is_close!(rect.max.x, 3.0); - assert_is_close!(rect.max.y, 5.0); - assert_is_close!(rect.radius.0, 0.5); + assert_is_close!(rect.min, Point::new(1.0, 2.0)); + assert_is_close!(rect.max, Point::new(3.0, 5.0)); + assert_is_close!(rect.radius, Length::new(0.5)); } #[test] - fn test_round_rect_from_rect() { + fn round_rect_from_rect() { let rect = RoundRect::<()>::from_rect( Rect::new(Point::new(1.0, 2.0), Point::new(3.0, 5.0)), Length::new(0.5), ); - assert_is_close!(rect.min.x, 1.0); - assert_is_close!(rect.min.y, 2.0); - assert_is_close!(rect.max.x, 3.0); - assert_is_close!(rect.max.y, 5.0); - assert_is_close!(rect.radius.0, 0.5); + assert_is_close!(rect.min, Point::new(1.0, 2.0)); + assert_is_close!(rect.max, Point::new(3.0, 5.0)); + assert_is_close!(rect.radius, Length::new(0.5)); } #[test] - fn test_round_rect_from_origin_and_size() { + fn round_rect_from_origin_and_size() { let rect = RoundRect::<()>::from_origin_and_size( Point::new(1.0, 2.0), Size::new(2.0, 3.0), Length::new(0.5), ); - assert_is_close!(rect.min.x, 1.0); - assert_is_close!(rect.min.y, 2.0); - assert_is_close!(rect.max.x, 3.0); - assert_is_close!(rect.max.y, 5.0); - assert_is_close!(rect.radius.0, 0.5); + assert_is_close!(rect.min, Point::new(1.0, 2.0)); + assert_is_close!(rect.max, Point::new(3.0, 5.0)); + assert_is_close!(rect.radius, Length::new(0.5)); } #[test] - fn test_round_rect_from_center_and_size() { + fn round_rect_from_center_and_size() { let rect = RoundRect::<()>::from_center_and_size( Point::new(2.0, 3.5), Size::new(2.0, 3.0), Length::new(0.5), ); - assert_is_close!(rect.min.x, 1.0); - assert_is_close!(rect.min.y, 2.0); - assert_is_close!(rect.max.x, 3.0); - assert_is_close!(rect.max.y, 5.0); - assert_is_close!(rect.radius.0, 0.5); + assert_is_close!(rect.min, Point::new(1.0, 2.0)); + assert_is_close!(rect.max, Point::new(3.0, 5.0)); + assert_is_close!(rect.radius, Length::new(0.5)); } #[test] - fn test_round_rect_width() { + fn round_rect_width() { let rect = RoundRect::<()>::new(Point::new(1.0, 2.0), Point::new(3.0, 5.0), Length::new(0.5)); @@ -191,7 +245,7 @@ mod tests { } #[test] - fn test_round_rect_height() { + fn round_rect_height() { let rect = RoundRect::<()>::new(Point::new(1.0, 2.0), Point::new(3.0, 5.0), Length::new(0.5)); @@ -199,7 +253,7 @@ mod tests { } #[test] - fn test_round_rect_radius() { + fn round_rect_radius() { let rect = RoundRect::<()>::new(Point::new(1.0, 2.0), Point::new(3.0, 5.0), Length::new(0.5)); @@ -207,7 +261,7 @@ mod tests { } #[test] - fn test_round_rect_rect() { + fn round_rect_rect() { let rect = RoundRect::<()>::new(Point::new(1.0, 2.0), Point::new(3.0, 5.0), Length::new(0.5)); @@ -218,7 +272,7 @@ mod tests { } #[test] - fn test_round_rect_center() { + fn round_rect_center() { let rect = RoundRect::<()>::new(Point::new(1.0, 2.0), Point::new(3.0, 5.0), Length::new(0.5)); @@ -226,7 +280,7 @@ mod tests { } #[test] - fn test_round_rect_size() { + fn round_rect_size() { let rect = RoundRect::<()>::new(Point::new(1.0, 2.0), Point::new(3.0, 5.0), Length::new(0.5)); diff --git a/keyset-geom/src/traits.rs b/keyset-geom/src/traits.rs index 087f603..ba6bb63 100644 --- a/keyset-geom/src/traits.rs +++ b/keyset-geom/src/traits.rs @@ -1,4 +1,4 @@ -use crate::{Angle, Point, Rect, Size, Vector}; +use crate::{Angle, Point, Rect, Scale, Size, Transform, Vector}; /// Trait to add additional constructor to `Rect` pub trait ExtRect { @@ -48,22 +48,62 @@ impl ExtVec for Vector { } } -impl ExtVec, U> for euclid::Vector2D { +/// Trait to allow conversion from a [`Scale`] to a [`Transform`] +pub trait ToTransform { + /// Convert a [`Scale`] to a [`Transform`] + fn to_transform(self) -> Transform; +} + +impl ToTransform for Scale { #[inline] - fn rotate(self, angle: euclid::Angle) -> Self { - let (sin, cos) = angle.sin_cos(); - Self::new(self.x * cos - self.y * sin, self.x * sin + self.y * cos) + fn to_transform(self) -> Transform { + Transform::scale(self.get(), self.get()) } +} - #[inline] - fn neg_x(self) -> Self { - let (x, y) = self.to_tuple(); - Self::new(-x, y) +#[cfg(test)] +mod tests { + use isclose::assert_is_close; + + use super::*; + + #[test] + fn rect_from_center_and_size() { + let rect = Rect::<()>::from_center_and_size(Point::new(1.0, 2.0), Size::new(3.0, 4.0)); + let exp = Rect::new(Point::new(-0.5, 0.0), Point::new(2.5, 4.0)); + + assert_is_close!(rect, exp); } - #[inline] - fn neg_y(self) -> Self { - let (x, y) = self.to_tuple(); - Self::new(x, -y) + #[test] + fn vector_rotate() { + let vector = Vector::<()>::new(1.0, 0.0); + let exp = Vector::splat(std::f32::consts::FRAC_1_SQRT_2); + + assert_is_close!(vector.rotate(Angle::degrees(45.0)), exp); + } + + #[test] + fn vector_neg_x() { + let vector = Vector::<()>::new(1.0, 1.0); + let exp = Vector::new(-1.0, 1.0); + + assert_is_close!(vector.neg_x(), exp); + } + + #[test] + fn vector_neg_y() { + let vector = Vector::<()>::new(1.0, 1.0); + let exp = Vector::new(1.0, -1.0); + + assert_is_close!(vector.neg_y(), exp); + } + + #[test] + fn scale_to_transform() { + let scale = Scale::<(), ()>::new(2.0); + let exp = Transform::new(2.0, 0.0, 0.0, 2.0, 0.0, 0.0); + + assert_is_close!(scale.to_transform(), exp); } } diff --git a/keyset-geom/src/unit.rs b/keyset-geom/src/unit.rs index c03bdcc..dc905a2 100644 --- a/keyset-geom/src/unit.rs +++ b/keyset-geom/src/unit.rs @@ -1,4 +1,4 @@ -use crate::{Scale, Transform}; +use crate::Scale; /// Keyboard Unit, usually 19.05 mm or 0.75 in #[derive(Clone, Copy, Debug, Default)] @@ -27,16 +27,3 @@ pub const INCH_PER_UNIT: Scale = Scale::new(0.75); pub const DOT_PER_MM: Scale = Scale::new(DOT_PER_UNIT.0 / MM_PER_UNIT.0); /// Conversion factor for Inches to Drawing Units pub const DOT_PER_INCH: Scale = Scale::new(DOT_PER_UNIT.0 / INCH_PER_UNIT.0); - -/// Trait to allow conversion from a [`Scale`] to a [`Transform`] -pub trait ToTransform { - /// Convert a [`Scale`] to a [`Transform`] - fn to_transform(self) -> Transform; -} - -impl ToTransform for Scale { - #[inline] - fn to_transform(self) -> Transform { - Transform::scale(self.get(), self.get()) - } -} diff --git a/keyset-key/src/legend.rs b/keyset-key/src/legend.rs index 405088a..b72b7ae 100644 --- a/keyset-key/src/legend.rs +++ b/keyset-key/src/legend.rs @@ -213,7 +213,16 @@ pub mod tests { #[test] fn legends_into_iter() { - let mut iter = Legends::default().into_iter(); + let legends = Legends::default(); + let mut iter = legends.into_iter(); + + for _ in 0..9 { + assert!(iter.next().is_some()); + } + assert!(iter.next().is_none()); + + let legends = Legends::default(); + let mut iter = (&legends).into_iter(); for _ in 0..9 { assert!(iter.next().is_some()); diff --git a/keyset-key/src/lib.rs b/keyset-key/src/lib.rs index a82521b..4de8551 100644 --- a/keyset-key/src/lib.rs +++ b/keyset-key/src/lib.rs @@ -138,42 +138,66 @@ pub mod tests { use super::*; #[test] - fn shape_outer_size() { + fn shape_outer_rect() { + assert_eq!( + Shape::None(Size::new(1.0, 1.0)).outer_rect(), + Rect::new(Point::zero(), Point::new(1.0, 1.0)) + ); assert_eq!( Shape::Normal(Size::new(2.25, 1.0)).outer_rect(), - Rect::from_origin_and_size((0.0, 0.0).into(), (2.25, 1.0).into()) + Rect::new(Point::zero(), Point::new(2.25, 1.0)) + ); + assert_eq!( + Shape::Space(Size::new(6.25, 1.0)).outer_rect(), + Rect::new(Point::zero(), Point::new(6.25, 1.0)) + ); + assert_eq!( + Shape::Homing(None).outer_rect(), + Rect::new(Point::zero(), Point::new(1.0, 1.0)) ); assert_eq!( Shape::IsoVertical.outer_rect(), - Rect::from_origin_and_size((0.0, 0.0).into(), (1.5, 2.0).into()) + Rect::new(Point::zero(), Point::new(1.5, 2.0)) ); assert_eq!( Shape::IsoHorizontal.outer_rect(), - Rect::from_origin_and_size((0.0, 0.0).into(), (1.5, 2.0).into()) + Rect::new(Point::zero(), Point::new(1.5, 2.0)) ); assert_eq!( Shape::SteppedCaps.outer_rect(), - Rect::from_origin_and_size((0.0, 0.0).into(), (1.75, 1.0).into()) + Rect::new(Point::zero(), Point::new(1.75, 1.0)) ); } #[test] - fn shape_inner_size() { + fn shape_inner_rect() { + assert_eq!( + Shape::None(Size::new(1.0, 1.0)).inner_rect(), + Rect::new(Point::zero(), Point::new(1.0, 1.0)) + ); assert_eq!( Shape::Normal(Size::new(2.25, 1.0)).inner_rect(), - Rect::new((0.0, 0.0).into(), (2.25, 1.0).into()) + Rect::new(Point::zero(), Point::new(2.25, 1.0)) + ); + assert_eq!( + Shape::Space(Size::new(6.25, 1.0)).inner_rect(), + Rect::new(Point::zero(), Point::new(6.25, 1.0)) + ); + assert_eq!( + Shape::Homing(None).inner_rect(), + Rect::new(Point::zero(), Point::new(1.0, 1.0)) ); assert_eq!( Shape::IsoVertical.inner_rect(), - Rect::new((0.25, 0.0).into(), (1.5, 2.0).into()) + Rect::new(Point::new(0.25, 0.0), Point::new(1.5, 2.0)) ); assert_eq!( Shape::IsoHorizontal.inner_rect(), - Rect::new((0.0, 0.0).into(), (1.5, 1.0).into()) + Rect::new(Point::zero(), Point::new(1.5, 1.0)) ); assert_eq!( Shape::SteppedCaps.inner_rect(), - Rect::new((0.0, 0.0).into(), (1.25, 1.0).into()) + Rect::new(Point::zero(), Point::new(1.25, 1.0)) ); }