embedded_graphics_core/geometry/mod.rs
1//! Geometry module.
2
3mod point;
4mod size;
5
6pub use point::Point;
7pub use size::Size;
8
9use 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
63pub 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
80pub trait OriginDimensions {
81 /// Returns the size of the bounding box.
82 fn size(&self) -> Size;
83}
84
85impl<T> Dimensions for T
86where
87 T: OriginDimensions,
88{
89 fn bounding_box(&self) -> Rectangle {
90 Rectangle::new(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))]
97pub 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
118impl 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))]
166pub 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))]
178pub enum AnchorY {
179 /// Top.
180 Top,
181 /// Center.
182 Center,
183 /// Bottom.
184 Bottom,
185}
186
187#[cfg(test)]
188mod 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}