embedded_layout/align/horizontal/
mod.rs

1//! Horizontal alignment options
2//!
3//! Horizontal alignment types must implement [`HorizontalAlignment`].
4use crate::align::{Alignment, HorizontalAlignment};
5use embedded_graphics::{geometry::AnchorPoint, primitives::Rectangle};
6
7/// Keep the objects' horizontal alignment unchanged
8#[derive(Copy, Clone, Default)]
9pub struct NoAlignment;
10impl HorizontalAlignment for NoAlignment {}
11
12impl Alignment for NoAlignment {
13    #[inline]
14    fn align_with_offset(&self, _object: Rectangle, _reference: Rectangle, _offset: i32) -> i32 {
15        0
16    }
17}
18
19/// Center the objects horizontally
20///
21/// *Note:* in certain cases it's not possible to center objects perfectly because of
22///         the integer coordinates used.
23#[derive(Copy, Clone, Default)]
24pub struct Center;
25impl HorizontalAlignment for Center {}
26
27impl Alignment for Center {
28    #[inline]
29    fn align_with_offset(&self, object: Rectangle, reference: Rectangle, offset: i32) -> i32 {
30        reference.anchor_point(AnchorPoint::Center).x - object.anchor_point(AnchorPoint::Center).x
31            + offset
32    }
33}
34
35/// Align the left edge of the object to the left edge of the reference
36#[derive(Copy, Clone, Default)]
37pub struct Left;
38impl HorizontalAlignment for Left {}
39
40impl Alignment for Left {
41    #[inline]
42    fn align_with_offset(&self, object: Rectangle, reference: Rectangle, offset: i32) -> i32 {
43        reference.top_left.x - object.top_left.x + offset
44    }
45}
46
47/// Align the right edge of the object to the right edge of the reference
48#[derive(Copy, Clone, Default)]
49pub struct Right;
50impl HorizontalAlignment for Right {}
51
52impl Alignment for Right {
53    #[inline]
54    fn align_with_offset(&self, object: Rectangle, reference: Rectangle, offset: i32) -> i32 {
55        reference.anchor_point(AnchorPoint::BottomRight).x
56            - object.anchor_point(AnchorPoint::BottomRight).x
57            + offset
58    }
59}
60
61/// Align the left edge of the object to the right edge of the reference, non-overlapping
62#[derive(Copy, Clone, Default)]
63pub struct LeftToRight;
64impl HorizontalAlignment for LeftToRight {}
65
66impl Alignment for LeftToRight {
67    #[inline]
68    fn align_with_offset(&self, object: Rectangle, reference: Rectangle, offset: i32) -> i32 {
69        let offset = if object.size.width == 0 {
70            offset
71        } else {
72            offset + 1
73        };
74        reference.anchor_point(AnchorPoint::BottomRight).x - object.top_left.x + offset
75    }
76}
77
78/// Align the right edge of the object to the left edge of the reference, non-overlapping
79#[derive(Copy, Clone, Default)]
80pub struct RightToLeft;
81impl HorizontalAlignment for RightToLeft {}
82
83impl Alignment for RightToLeft {
84    #[inline]
85    fn align_with_offset(&self, object: Rectangle, reference: Rectangle, offset: i32) -> i32 {
86        let offset = if object.size.width == 0 {
87            offset
88        } else {
89            offset - 1
90        };
91        reference.top_left.x - object.anchor_point(AnchorPoint::BottomRight).x + offset
92    }
93}
94
95#[cfg(test)]
96mod test {
97    use crate::prelude::*;
98    use embedded_graphics::{
99        geometry::{AnchorPoint, Point},
100        prelude::Size,
101        primitives::Rectangle,
102    };
103
104    #[test]
105    fn test_center() {
106        fn check_center_alignment(source: Rectangle, reference: Rectangle, result: Rectangle) {
107            let center_of_reference = reference.top_left + reference.size() / 2;
108            let center_of_result = result.top_left + result.size() / 2;
109
110            // The size hasn't changed
111            assert_eq!(result.size(), source.size());
112
113            // Horizontal coordinate matches reference
114            assert_eq!(center_of_result.x, center_of_reference.x);
115
116            // Vertical coordinate is unchanged
117            assert_eq!(result.top_left.y, source.top_left.y);
118        }
119
120        let rect1 = Rectangle::with_corners(Point::new(0, 0), Point::new(10, 10));
121        let rect2 = Rectangle::with_corners(Point::new(30, 20), Point::new(40, 50));
122
123        let result = rect1.align_to(&rect2, horizontal::Center, vertical::NoAlignment);
124        check_center_alignment(rect1, rect2, result);
125
126        // Test the other direction
127        let result = rect2.align_to(&rect1, horizontal::Center, vertical::NoAlignment);
128        check_center_alignment(rect2, rect1, result);
129    }
130
131    #[test]
132    fn test_left() {
133        fn check_left_alignment(source: Rectangle, reference: Rectangle, result: Rectangle) {
134            // The size hasn't changed
135            assert_eq!(result.size(), source.size());
136
137            // Horizontal coordinate matches reference
138            assert_eq!(result.top_left.x, reference.top_left.x);
139
140            // Vertical coordinate is unchanged
141            assert_eq!(result.top_left.y, source.top_left.y);
142        }
143
144        let rect1 = Rectangle::with_corners(Point::new(0, 0), Point::new(10, 10));
145        let rect2 = Rectangle::with_corners(Point::new(30, 20), Point::new(40, 50));
146
147        let result = rect1.align_to(&rect2, horizontal::Left, vertical::NoAlignment);
148        check_left_alignment(rect1, rect2, result);
149
150        // Test the other direction
151        let result = rect2.align_to(&rect1, horizontal::Left, vertical::NoAlignment);
152        check_left_alignment(rect2, rect1, result);
153    }
154
155    #[test]
156    fn test_right() {
157        fn check_right_alignment(source: Rectangle, reference: Rectangle, result: Rectangle) {
158            // The size hasn't changed
159            assert_eq!(result.size(), source.size());
160
161            // Horizontal coordinate matches reference
162            assert_eq!(
163                result.anchor_point(AnchorPoint::BottomRight).x,
164                reference.anchor_point(AnchorPoint::BottomRight).x
165            );
166
167            // Vertical coordinate is unchanged
168            assert_eq!(
169                result.anchor_point(AnchorPoint::BottomRight).y,
170                source.anchor_point(AnchorPoint::BottomRight).y
171            );
172        }
173
174        let rect1 = Rectangle::with_corners(Point::new(0, 0), Point::new(10, 10));
175        let rect2 = Rectangle::with_corners(Point::new(30, 20), Point::new(40, 50));
176
177        let result = rect1.align_to(&rect2, horizontal::Right, vertical::NoAlignment);
178        check_right_alignment(rect1, rect2, result);
179
180        // Test the other direction
181        let result = rect2.align_to(&rect1, horizontal::Right, vertical::NoAlignment);
182        check_right_alignment(rect2, rect1, result);
183    }
184
185    #[test]
186    fn test_left_to_right() {
187        fn check_left_to_right_alignment(
188            source: Rectangle,
189            reference: Rectangle,
190            result: Rectangle,
191        ) {
192            // The size hasn't changed
193            assert_eq!(result.size(), source.size());
194
195            // Left is at right + 1
196            assert_eq!(
197                result.top_left.x,
198                reference.anchor_point(AnchorPoint::BottomRight).x + 1
199            );
200
201            // Vertical coordinate is unchanged
202            assert_eq!(
203                result.anchor_point(AnchorPoint::BottomRight).y,
204                source.anchor_point(AnchorPoint::BottomRight).y
205            );
206        }
207
208        let rect1 = Rectangle::with_corners(Point::new(0, 0), Point::new(10, 10));
209        let rect2 = Rectangle::with_corners(Point::new(30, 20), Point::new(40, 50));
210
211        let result = rect1.align_to(&rect2, horizontal::LeftToRight, vertical::NoAlignment);
212        check_left_to_right_alignment(rect1, rect2, result);
213
214        // Test the other direction
215        let result = rect2.align_to(&rect1, horizontal::LeftToRight, vertical::NoAlignment);
216        check_left_to_right_alignment(rect2, rect1, result);
217    }
218
219    #[test]
220    fn test_left_to_right_empty() {
221        let rect1 = Rectangle::new(Point::new(0, 0), Size::zero());
222        let rect2 = Rectangle::with_corners(Point::new(30, 20), Point::new(40, 50));
223
224        let result = rect1.align_to(&rect2, horizontal::LeftToRight, vertical::NoAlignment);
225        // The size hasn't changed
226        assert_eq!(result.size(), rect1.size());
227
228        // Left is at right
229        assert_eq!(
230            result.top_left.x,
231            rect2.anchor_point(AnchorPoint::BottomRight).x
232        );
233
234        // Vertical coordinate is unchanged
235        assert_eq!(
236            result.anchor_point(AnchorPoint::BottomRight).y,
237            rect1.anchor_point(AnchorPoint::BottomRight).y
238        );
239
240        // Test the other direction
241        let result = rect2.align_to(&rect1, horizontal::LeftToRight, vertical::NoAlignment);
242
243        // The size hasn't changed
244        assert_eq!(result.size(), rect2.size());
245
246        // Left is at right
247        assert_eq!(
248            result.top_left.x,
249            rect1.anchor_point(AnchorPoint::BottomRight).x + 1
250        );
251
252        // Vertical coordinate is unchanged
253        assert_eq!(
254            result.anchor_point(AnchorPoint::BottomRight).y,
255            rect2.anchor_point(AnchorPoint::BottomRight).y
256        );
257    }
258
259    #[test]
260    fn test_right_to_left() {
261        let rect1 = Rectangle::with_corners(Point::new(0, 0), Point::new(10, 10));
262        let rect2 = Rectangle::with_corners(Point::new(30, 20), Point::new(40, 50));
263
264        let result = rect1.align_to(&rect2, horizontal::RightToLeft, vertical::NoAlignment);
265        // The size hasn't changed
266        assert_eq!(result.size(), rect1.size());
267
268        // Left is at right - 1
269        assert_eq!(
270            result.anchor_point(AnchorPoint::BottomRight).x,
271            rect2.top_left.x - 1
272        );
273
274        // Vertical coordinate is unchanged
275        assert_eq!(
276            result.anchor_point(AnchorPoint::BottomRight).y,
277            rect1.anchor_point(AnchorPoint::BottomRight).y
278        );
279
280        // Test the other direction
281        let result = rect2.align_to(&rect1, horizontal::RightToLeft, vertical::NoAlignment);
282        // The size hasn't changed
283        assert_eq!(result.size(), rect2.size());
284
285        // Left is at right + 1
286        assert_eq!(
287            result.anchor_point(AnchorPoint::BottomRight).x,
288            rect1.top_left.x - 1
289        );
290
291        // Vertical coordinate is unchanged
292        assert_eq!(
293            result.anchor_point(AnchorPoint::BottomRight).y,
294            rect2.anchor_point(AnchorPoint::BottomRight).y
295        );
296    }
297
298    #[test]
299    fn test_right_to_left_empty() {
300        let rect1 = Rectangle::new(Point::new(0, 0), Size::zero());
301        let rect2 = Rectangle::with_corners(Point::new(30, 20), Point::new(40, 50));
302
303        let result = rect1.align_to(&rect2, horizontal::RightToLeft, vertical::NoAlignment);
304        // The size hasn't changed
305        assert_eq!(result.size(), rect1.size());
306
307        // Left is at right
308        assert_eq!(
309            result.anchor_point(AnchorPoint::BottomRight).x,
310            rect2.top_left.x
311        );
312
313        // Vertical coordinate is unchanged
314        assert_eq!(
315            result.anchor_point(AnchorPoint::BottomRight).y,
316            rect1.anchor_point(AnchorPoint::BottomRight).y
317        );
318
319        // Test the other direction
320        let result = rect2.align_to(&rect1, horizontal::RightToLeft, vertical::NoAlignment);
321        // The size hasn't changed
322        assert_eq!(result.size(), rect2.size());
323
324        // Left is at right + 1
325        assert_eq!(
326            result.anchor_point(AnchorPoint::BottomRight).x,
327            rect1.top_left.x - 1
328        );
329
330        // Vertical coordinate is unchanged
331        assert_eq!(
332            result.anchor_point(AnchorPoint::BottomRight).y,
333            rect2.anchor_point(AnchorPoint::BottomRight).y
334        );
335    }
336}