1 | //! Geometry module. |
2 | |
3 | mod point; |
4 | mod size; |
5 | |
6 | pub use point::Point; |
7 | pub use size::Size; |
8 | |
9 | use crate::primitives::Rectangle; |
10 | |
11 | /// Adds the ability to get the bounding box of an item. |
12 | /// |
13 | /// The exact definition of the bounding box depends on the item: |
14 | /// |
15 | /// * Primitives ([`Rectangle`], [`Circle`], ...) |
16 | /// |
17 | /// For unstyled [primitives] the bounding box is defined as the smallest rectangle that surrounds the entire primitive. |
18 | /// * Styled primitives and other [`Drawable`]s ([`Image`], [`Text`], ...) |
19 | /// |
20 | /// The bounding box of a drawable is defined as the smallest rectangle that contains all drawn pixels. |
21 | /// While all builtin [`Drawable`]s in embedded-graphics provide an implementation of this trait, this might |
22 | /// not be true for third party drawables. |
23 | /// |
24 | /// Note that a styled primitive can have a different bounding box than the underlying unstyled primitive; |
25 | /// depending on the stroke width and alignment the bounding box of the styled primitive may be larger. |
26 | /// * [`DrawTarget`]s (displays, simulator, ...) |
27 | /// |
28 | /// The bounding box of a draw target is defined as the area that should be used for drawing operations. |
29 | /// For most display drivers the top left corner of the bounding box will be at the origin but other draw targets |
30 | /// can have different positions of the top left corner. |
31 | /// |
32 | /// The bounding box will be returned as a [`Rectangle`]. The methods provided by [`Rectangle`] make |
33 | /// it easy to implement additional functions like hit testing (by using [`contains`]) or drawing a focus |
34 | /// rectangle around a drawable (by converting the rectangle into a [`Styled`]). |
35 | /// |
36 | /// # Implementation notes |
37 | /// |
38 | /// `Dimensions` should be implemented for `Drawable`s if the bounding box is known before [`Drawable::draw`] is |
39 | /// executed. The implementation must return a rectangle that contains all drawn pixels. |
40 | /// [`MockDisplay::affected_area`] can be a used in unit tests to make sure a drawable returns a bounding box with |
41 | /// the correct dimensions. |
42 | /// |
43 | /// [`DrawTarget`]s (display drivers, etc) are required to implement `Dimensions`. The |
44 | /// implementation must return a rectangle representing the drawing area. For display |
45 | /// drivers it is recommended to implement [`OriginDimensions`] instead of implementing `Dimensions` directly, |
46 | /// if the top left corner of the display area is at the origin `(0, 0)`. |
47 | /// |
48 | /// The bounding box of [`ImageDrawable`]s must always start at the origin, therefore [`OriginDimensions`] must be implemented instead of this trait. |
49 | /// |
50 | /// [`Drawable`]: super::Drawable |
51 | /// [`Drawable::draw`]: super::Drawable::draw |
52 | /// [`DrawTarget`]: super::draw_target::DrawTarget |
53 | /// [`ImageDrawable`]: super::image::ImageDrawable |
54 | /// [`Rectangle`]: super::primitives::rectangle::Rectangle |
55 | /// [`points`]: super::primitives::PointsIter |
56 | /// [`MockDisplay::affected_area`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/mock_display/struct.MockDisplay.html#method.affected_area |
57 | /// [`contains`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/primitives/trait.ContainsPoint.html#tymethod.contains |
58 | /// [primitives]: https://docs.rs/embedded-graphics/latest/embedded_graphics/primitives/index.html |
59 | /// [`Circle`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/primitives/circle/struct.Circle.html |
60 | /// [`Image`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/image/struct.Image.html |
61 | /// [`Text`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/fonts/struct.Text.html |
62 | /// [`Styled`]: https://docs.rs/embedded-graphics/latest/embedded_graphics/style/styled/struct.Styled.html |
63 | pub trait Dimensions { |
64 | /// Returns the bounding box. |
65 | fn bounding_box(&self) -> Rectangle; |
66 | } |
67 | |
68 | /// Dimensions with `top_left` of the bounding box at `(0, 0)`. |
69 | /// |
70 | /// A blanket implementation of `Dimensions` is provided for all types that implement this trait. |
71 | /// See the [`Dimensions`] trait documentation for more information about bounding boxes. |
72 | /// |
73 | /// # Implementation notes |
74 | /// |
75 | /// This trait should be implemented instead of [`Dimensions`] if the top left corner of the bounding box |
76 | /// will always be at the origin, which will be the case for most display drivers. Some types, like [`ImageDrawable`], |
77 | /// require a bounding box that starts at the origin and can only be used if [`OriginDimensions`] is implemented. |
78 | /// |
79 | /// [`ImageDrawable`]: super::image::ImageDrawable |
80 | pub trait OriginDimensions { |
81 | /// Returns the size of the bounding box. |
82 | fn size(&self) -> Size; |
83 | } |
84 | |
85 | impl<T> Dimensions for T |
86 | where |
87 | T: OriginDimensions, |
88 | { |
89 | fn bounding_box(&self) -> Rectangle { |
90 | Rectangle::new(top_left:Point::zero(), self.size()) |
91 | } |
92 | } |
93 | |
94 | /// Anchor point. |
95 | #[derive (Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Copy, Clone)] |
96 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
97 | pub enum AnchorPoint { |
98 | /// Top left. |
99 | TopLeft, |
100 | /// Top center. |
101 | TopCenter, |
102 | /// Top right. |
103 | TopRight, |
104 | /// Center left. |
105 | CenterLeft, |
106 | /// Center. |
107 | Center, |
108 | /// Center right. |
109 | CenterRight, |
110 | /// Bottom left. |
111 | BottomLeft, |
112 | /// Bottom center. |
113 | BottomCenter, |
114 | /// Bottom right. |
115 | BottomRight, |
116 | } |
117 | |
118 | impl AnchorPoint { |
119 | /// Creates an anchor point from an X and Y component. |
120 | pub fn from_xy(x: AnchorX, y: AnchorY) -> Self { |
121 | match (y, x) { |
122 | (AnchorY::Top, AnchorX::Left) => AnchorPoint::TopLeft, |
123 | (AnchorY::Top, AnchorX::Center) => AnchorPoint::TopCenter, |
124 | (AnchorY::Top, AnchorX::Right) => AnchorPoint::TopRight, |
125 | (AnchorY::Center, AnchorX::Left) => AnchorPoint::CenterLeft, |
126 | (AnchorY::Center, AnchorX::Center) => AnchorPoint::Center, |
127 | (AnchorY::Center, AnchorX::Right) => AnchorPoint::CenterRight, |
128 | (AnchorY::Bottom, AnchorX::Left) => AnchorPoint::BottomLeft, |
129 | (AnchorY::Bottom, AnchorX::Center) => AnchorPoint::BottomCenter, |
130 | (AnchorY::Bottom, AnchorX::Right) => AnchorPoint::BottomRight, |
131 | } |
132 | } |
133 | |
134 | /// Returns the X axis component. |
135 | pub fn x(self) -> AnchorX { |
136 | match self { |
137 | AnchorPoint::TopLeft | AnchorPoint::CenterLeft | AnchorPoint::BottomLeft => { |
138 | AnchorX::Left |
139 | } |
140 | AnchorPoint::TopCenter | AnchorPoint::Center | AnchorPoint::BottomCenter => { |
141 | AnchorX::Center |
142 | } |
143 | AnchorPoint::TopRight | AnchorPoint::CenterRight | AnchorPoint::BottomRight => { |
144 | AnchorX::Right |
145 | } |
146 | } |
147 | } |
148 | |
149 | /// Returns the Y axis component. |
150 | pub fn y(self) -> AnchorY { |
151 | match self { |
152 | AnchorPoint::TopLeft | AnchorPoint::TopCenter | AnchorPoint::TopRight => AnchorY::Top, |
153 | AnchorPoint::CenterLeft | AnchorPoint::Center | AnchorPoint::CenterRight => { |
154 | AnchorY::Center |
155 | } |
156 | AnchorPoint::BottomLeft | AnchorPoint::BottomCenter | AnchorPoint::BottomRight => { |
157 | AnchorY::Bottom |
158 | } |
159 | } |
160 | } |
161 | } |
162 | |
163 | /// X axis anchor point. |
164 | #[derive (Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Copy, Clone)] |
165 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
166 | pub enum AnchorX { |
167 | /// Left. |
168 | Left, |
169 | /// Center. |
170 | Center, |
171 | /// Right. |
172 | Right, |
173 | } |
174 | |
175 | /// Y axis anchor point. |
176 | #[derive (Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Copy, Clone)] |
177 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
178 | pub enum AnchorY { |
179 | /// Top. |
180 | Top, |
181 | /// Center. |
182 | Center, |
183 | /// Bottom. |
184 | Bottom, |
185 | } |
186 | |
187 | #[cfg (test)] |
188 | mod tests { |
189 | use super::*; |
190 | |
191 | #[rustfmt::skip] |
192 | const ANCHOR_TESTS: &[((AnchorY, AnchorX), AnchorPoint)] = &[ |
193 | ((AnchorY::Top, AnchorX::Left), AnchorPoint::TopLeft), |
194 | ((AnchorY::Top, AnchorX::Center), AnchorPoint::TopCenter), |
195 | ((AnchorY::Top, AnchorX::Right), AnchorPoint::TopRight), |
196 | ((AnchorY::Center, AnchorX::Left), AnchorPoint::CenterLeft), |
197 | ((AnchorY::Center, AnchorX::Center), AnchorPoint::Center), |
198 | ((AnchorY::Center, AnchorX::Right), AnchorPoint::CenterRight), |
199 | ((AnchorY::Bottom, AnchorX::Left), AnchorPoint::BottomLeft), |
200 | ((AnchorY::Bottom, AnchorX::Center), AnchorPoint::BottomCenter), |
201 | ((AnchorY::Bottom, AnchorX::Right), AnchorPoint::BottomRight), |
202 | ]; |
203 | |
204 | #[test ] |
205 | fn anchor_conversion() { |
206 | for ((y, x), p) in ANCHOR_TESTS.iter().copied() { |
207 | assert_eq!(p.x(), x); |
208 | assert_eq!(p.y(), y); |
209 | |
210 | assert_eq!(AnchorPoint::from_xy(x, y), p); |
211 | } |
212 | } |
213 | } |
214 | |