| 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 | |