From a07c03d7f9d69a08355e76d0f9866e0e01897d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Wed, 23 Aug 2023 19:27:28 +0200 Subject: [PATCH] Fix aligning to 0 sized reference --- CHANGELOG.md | 8 +- src/align/horizontal/mod.rs | 141 ++++++++++++++++++++++----- src/align/vertical/mod.rs | 187 +++++++++++++++++++++++++++--------- src/layout/linear/mod.rs | 73 ++++++++++++++ 4 files changed, 341 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa13015..6c2bfa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,17 @@ Unreleased ========== +## New + * `ViewGroup` macro can now be used on enums * `ViewGroup` macro now supports empty enum variants and unit structs -* `embedded-layout` no longer panicks when accessing an out-of-bounds view in a `ViewGroup` * `LinearLayout` can now be used in macro-generated view groups +## Fixed + +* `embedded-layout` no longer panicks when accessing an out-of-bounds view in a `ViewGroup` +* Fixed aligning to a 0-sized reference object + 0.3.1 (2023-05-14) ================== diff --git a/src/align/horizontal/mod.rs b/src/align/horizontal/mod.rs index be131bf..ffde8f2 100644 --- a/src/align/horizontal/mod.rs +++ b/src/align/horizontal/mod.rs @@ -66,7 +66,12 @@ impl HorizontalAlignment for LeftToRight {} impl Alignment for LeftToRight { #[inline] fn align_with_offset(&self, object: Rectangle, reference: Rectangle, offset: i32) -> i32 { - (reference.anchor_point(AnchorPoint::BottomRight).x + 1) - object.top_left.x + offset + let offset = if object.size.width == 0 { + offset + } else { + offset + 1 + }; + reference.anchor_point(AnchorPoint::BottomRight).x - object.top_left.x + offset } } @@ -78,7 +83,12 @@ impl HorizontalAlignment for RightToLeft {} impl Alignment for RightToLeft { #[inline] fn align_with_offset(&self, object: Rectangle, reference: Rectangle, offset: i32) -> i32 { - (reference.top_left.x - 1) - object.anchor_point(AnchorPoint::BottomRight).x + offset + let offset = if object.size.width == 0 { + offset + } else { + offset - 1 + }; + reference.top_left.x - object.anchor_point(AnchorPoint::BottomRight).x + offset } } @@ -87,6 +97,7 @@ mod test { use crate::prelude::*; use embedded_graphics::{ geometry::{AnchorPoint, Point}, + prelude::Size, primitives::Rectangle, }; @@ -206,36 +217,120 @@ mod test { } #[test] - fn test_right_to_left() { - fn check_right_to_left_alignment( - source: Rectangle, - reference: Rectangle, - result: Rectangle, - ) { - // The size hasn't changed - assert_eq!(result.size(), source.size()); + fn test_left_to_right_empty() { + let rect1 = Rectangle::new(Point::new(0, 0), Size::zero()); + let rect2 = Rectangle::with_corners(Point::new(30, 20), Point::new(40, 50)); - // Left is at right + 1 - assert_eq!( - result.anchor_point(AnchorPoint::BottomRight).x, - reference.top_left.x - 1 - ); + let result = rect1.align_to(&rect2, horizontal::LeftToRight, vertical::NoAlignment); + // The size hasn't changed + assert_eq!(result.size(), rect1.size()); - // Vertical coordinate is unchanged - assert_eq!( - result.anchor_point(AnchorPoint::BottomRight).y, - source.anchor_point(AnchorPoint::BottomRight).y - ); - } + // Left is at right + assert_eq!( + result.top_left.x, + rect2.anchor_point(AnchorPoint::BottomRight).x + ); + + // Vertical coordinate is unchanged + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).y, + rect1.anchor_point(AnchorPoint::BottomRight).y + ); + + // Test the other direction + let result = rect2.align_to(&rect1, horizontal::LeftToRight, vertical::NoAlignment); + + // The size hasn't changed + assert_eq!(result.size(), rect2.size()); + + // Left is at right + assert_eq!( + result.top_left.x, + rect1.anchor_point(AnchorPoint::BottomRight).x + 1 + ); + + // Vertical coordinate is unchanged + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).y, + rect2.anchor_point(AnchorPoint::BottomRight).y + ); + } + #[test] + fn test_right_to_left() { let rect1 = Rectangle::with_corners(Point::new(0, 0), Point::new(10, 10)); let rect2 = Rectangle::with_corners(Point::new(30, 20), Point::new(40, 50)); let result = rect1.align_to(&rect2, horizontal::RightToLeft, vertical::NoAlignment); - check_right_to_left_alignment(rect1, rect2, result); + // The size hasn't changed + assert_eq!(result.size(), rect1.size()); + + // Left is at right - 1 + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).x, + rect2.top_left.x - 1 + ); + + // Vertical coordinate is unchanged + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).y, + rect1.anchor_point(AnchorPoint::BottomRight).y + ); + + // Test the other direction + let result = rect2.align_to(&rect1, horizontal::RightToLeft, vertical::NoAlignment); + // The size hasn't changed + assert_eq!(result.size(), rect2.size()); + + // Left is at right + 1 + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).x, + rect1.top_left.x - 1 + ); + + // Vertical coordinate is unchanged + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).y, + rect2.anchor_point(AnchorPoint::BottomRight).y + ); + } + + #[test] + fn test_right_to_left_empty() { + let rect1 = Rectangle::new(Point::new(0, 0), Size::zero()); + let rect2 = Rectangle::with_corners(Point::new(30, 20), Point::new(40, 50)); + + let result = rect1.align_to(&rect2, horizontal::RightToLeft, vertical::NoAlignment); + // The size hasn't changed + assert_eq!(result.size(), rect1.size()); + + // Left is at right + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).x, + rect2.top_left.x + ); + + // Vertical coordinate is unchanged + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).y, + rect1.anchor_point(AnchorPoint::BottomRight).y + ); // Test the other direction let result = rect2.align_to(&rect1, horizontal::RightToLeft, vertical::NoAlignment); - check_right_to_left_alignment(rect2, rect1, result); + // The size hasn't changed + assert_eq!(result.size(), rect2.size()); + + // Left is at right + 1 + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).x, + rect1.top_left.x - 1 + ); + + // Vertical coordinate is unchanged + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).y, + rect2.anchor_point(AnchorPoint::BottomRight).y + ); } } diff --git a/src/align/vertical/mod.rs b/src/align/vertical/mod.rs index f81d4b8..102f1df 100644 --- a/src/align/vertical/mod.rs +++ b/src/align/vertical/mod.rs @@ -66,7 +66,12 @@ impl VerticalAlignment for TopToBottom {} impl Alignment for TopToBottom { #[inline] fn align_with_offset(&self, object: Rectangle, reference: Rectangle, offset: i32) -> i32 { - (reference.anchor_point(AnchorPoint::BottomRight).y + 1) - object.top_left.y + offset + let offset = if object.size.height == 0 { + offset + } else { + offset + 1 + }; + reference.anchor_point(AnchorPoint::BottomRight).y - object.top_left.y + offset } } @@ -78,7 +83,12 @@ impl VerticalAlignment for BottomToTop {} impl Alignment for BottomToTop { #[inline] fn align_with_offset(&self, object: Rectangle, reference: Rectangle, offset: i32) -> i32 { - (reference.top_left.y - 1) - object.anchor_point(AnchorPoint::BottomRight).y + offset + let offset = if object.size.height == 0 { + offset + } else { + offset - 1 + }; + reference.top_left.y - object.anchor_point(AnchorPoint::BottomRight).y + offset } } @@ -87,6 +97,7 @@ mod test { use crate::prelude::*; use embedded_graphics::{ geometry::{AnchorPoint, Point}, + prelude::Size, primitives::Rectangle, }; @@ -174,69 +185,157 @@ mod test { #[test] fn test_top_to_bottom() { - fn check_top_to_bottom_alignment( - source: Rectangle, - reference: Rectangle, - result: Rectangle, - ) { - // The size hasn't changed - assert_eq!(result.size(), source.size()); + let rect1 = Rectangle::with_corners(Point::new(0, 0), Point::new(10, 10)); + let rect2 = Rectangle::with_corners(Point::new(30, 20), Point::new(40, 50)); - // Top is at bottom + 1 - assert_eq!( - result.top_left.y, - reference.anchor_point(AnchorPoint::BottomRight).y + 1 - ); + let result = rect1.align_to(&rect2, horizontal::NoAlignment, vertical::TopToBottom); + // The size hasn't changed + assert_eq!(result.size(), rect1.size()); - // Horizontal coordinate is unchanged - assert_eq!( - result.anchor_point(AnchorPoint::BottomRight).x, - source.anchor_point(AnchorPoint::BottomRight).x - ); - } + // Top is at bottom + 1 + assert_eq!( + result.top_left.y, + rect2.anchor_point(AnchorPoint::BottomRight).y + 1 + ); - let rect1 = Rectangle::with_corners(Point::new(0, 0), Point::new(10, 10)); + // Horizontal coordinate is unchanged + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).x, + rect1.anchor_point(AnchorPoint::BottomRight).x + ); + + // Test the other direction + let result = rect2.align_to(&rect1, horizontal::NoAlignment, vertical::TopToBottom); + // The size hasn't changed + assert_eq!(result.size(), rect2.size()); + + // Top is at bottom + 1 + assert_eq!( + result.top_left.y, + rect1.anchor_point(AnchorPoint::BottomRight).y + 1 + ); + + // Horizontal coordinate is unchanged + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).x, + rect2.anchor_point(AnchorPoint::BottomRight).x + ); + } + + #[test] + fn test_top_to_bottom_empty() { + let rect1 = Rectangle::new(Point::new(0, 0), Size::zero()); let rect2 = Rectangle::with_corners(Point::new(30, 20), Point::new(40, 50)); let result = rect1.align_to(&rect2, horizontal::NoAlignment, vertical::TopToBottom); - check_top_to_bottom_alignment(rect1, rect2, result); + // The size hasn't changed + assert_eq!(result.size(), rect1.size()); + + // Top is at bottom + assert_eq!( + result.top_left.y, + rect2.anchor_point(AnchorPoint::BottomRight).y + ); + + // Horizontal coordinate is unchanged + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).x, + rect1.anchor_point(AnchorPoint::BottomRight).x + ); // Test the other direction let result = rect2.align_to(&rect1, horizontal::NoAlignment, vertical::TopToBottom); - check_top_to_bottom_alignment(rect2, rect1, result); + // The size hasn't changed + assert_eq!(result.size(), rect2.size()); + + // Top is at bottom + 1 + assert_eq!( + result.top_left.y, + rect1.anchor_point(AnchorPoint::BottomRight).y + 1 + ); + + // Horizontal coordinate is unchanged + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).x, + rect2.anchor_point(AnchorPoint::BottomRight).x + ); } #[test] fn test_bottom_to_top() { - fn check_bottom_to_top_alignment( - source: Rectangle, - reference: Rectangle, - result: Rectangle, - ) { - // The size hasn't changed - assert_eq!(result.size(), source.size()); + let rect1 = Rectangle::with_corners(Point::new(0, 0), Point::new(10, 10)); + let rect2 = Rectangle::with_corners(Point::new(30, 20), Point::new(40, 50)); - // Bottom is at top - 1 - assert_eq!( - result.anchor_point(AnchorPoint::BottomRight).y, - reference.top_left.y - 1 - ); + let result = rect1.align_to(&rect2, horizontal::NoAlignment, vertical::BottomToTop); + // The size hasn't changed + assert_eq!(result.size(), rect1.size()); - // Horizontal coordinate is unchanged - assert_eq!( - result.anchor_point(AnchorPoint::BottomRight).x, - source.anchor_point(AnchorPoint::BottomRight).x - ); - } + // Bottom is at top - 1 + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).y, + rect2.top_left.y - 1 + ); - let rect1 = Rectangle::with_corners(Point::new(0, 0), Point::new(10, 10)); + // Horizontal coordinate is unchanged + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).x, + rect1.anchor_point(AnchorPoint::BottomRight).x + ); + + // Test the other direction + let result = rect2.align_to(&rect1, horizontal::NoAlignment, vertical::BottomToTop); + // The size hasn't changed + assert_eq!(result.size(), rect2.size()); + + // Bottom is at top - 1 + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).y, + rect1.top_left.y - 1 + ); + + // Horizontal coordinate is unchanged + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).x, + rect2.anchor_point(AnchorPoint::BottomRight).x + ); + } + + #[test] + fn test_bottom_to_top_empty() { + let rect1 = Rectangle::new(Point::new(0, 0), Size::zero()); let rect2 = Rectangle::with_corners(Point::new(30, 20), Point::new(40, 50)); let result = rect1.align_to(&rect2, horizontal::NoAlignment, vertical::BottomToTop); - check_bottom_to_top_alignment(rect1, rect2, result); + // The size hasn't changed + assert_eq!(result.size(), rect1.size()); + + // Bottom is at top + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).y, + rect2.top_left.y + ); + + // Horizontal coordinate is unchanged + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).x, + rect1.anchor_point(AnchorPoint::BottomRight).x + ); // Test the other direction let result = rect2.align_to(&rect1, horizontal::NoAlignment, vertical::BottomToTop); - check_bottom_to_top_alignment(rect2, rect1, result); + // The size hasn't changed + assert_eq!(result.size(), rect2.size()); + + // Bottom is at top - 1 + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).y, + rect1.top_left.y - 1 + ); + + // Horizontal coordinate is unchanged + assert_eq!( + result.anchor_point(AnchorPoint::BottomRight).x, + rect2.anchor_point(AnchorPoint::BottomRight).x + ); } } diff --git a/src/layout/linear/mod.rs b/src/layout/linear/mod.rs index b8419c8..4bea178 100644 --- a/src/layout/linear/mod.rs +++ b/src/layout/linear/mod.rs @@ -438,6 +438,45 @@ mod test { ); } + #[test] + fn empty_rectangle_takes_up_no_vertical_space() { + let mut disp: MockDisplay = MockDisplay::new(); + + let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1); + let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style); + let rect_empty = Rectangle::new(Point::new(-50, 10), Size::zero()).into_styled(style); + let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style); + + LinearLayout::vertical(Chain::new(rect).append(rect_empty).append(rect2)) + .arrange() + .translate(Point::new(1, 2)) + .draw(&mut disp) + .unwrap(); + + assert_eq!( + disp, + MockDisplay::from_pattern(&[ + " ", + " ", + " ##########", + " # #", + " # #", + " # #", + " ##########", + " ##### ", + " # # ", + " # # ", + " # # ", + " # # ", + " # # ", + " # # ", + " # # ", + " # # ", + " ##### ", + ]) + ); + } + #[test] fn layout_arrange_vertical_secondary() { let mut disp: MockDisplay = MockDisplay::new(); @@ -510,6 +549,40 @@ mod test { ); } + #[test] + fn empty_rectangle_takes_up_no_horizontal_space() { + let mut disp: MockDisplay = MockDisplay::new(); + + let style = PrimitiveStyle::with_stroke(BinaryColor::On, 1); + let rect = Rectangle::new(Point::new(10, 30), Size::new(10, 5)).into_styled(style); + let rect_empty = Rectangle::new(Point::new(-50, 10), Size::zero()).into_styled(style); + let rect2 = Rectangle::new(Point::new(-50, 10), Size::new(5, 10)).into_styled(style); + + LinearLayout::horizontal(Chain::new(rect).append(rect_empty).append(rect2)) + .arrange() + .translate(Point::new(1, 2)) + .draw(&mut disp) + .unwrap(); + + assert_eq!( + disp, + MockDisplay::from_pattern(&[ + " ", + " ", + " #####", + " # #", + " # #", + " # #", + " # #", + " ########### #", + " # ## #", + " # ## #", + " # ## #", + " ###############", + ]) + ); + } + #[test] fn layout_arrange_horizontal_secondary() { let mut disp: MockDisplay = MockDisplay::new();