simple_layout/
border.rs

1use std::marker::PhantomData;
2use std::num::Saturating;
3
4use embedded_graphics::{
5    draw_target::DrawTarget, geometry::Point, pixelcolor::PixelColor, prelude::Size,
6    primitives::Rectangle, Pixel,
7};
8
9use crate::{layoutable::Layoutable, ComponentSize};
10
11pub trait Decorator<C: PixelColor> {
12    fn width(&self) -> u32;
13    fn draw_placed<DrawError>(
14        &self,
15        target: &mut impl DrawTarget<Color = C, Error = DrawError>,
16        position: Rectangle,
17    ) -> Result<(), DrawError>;
18}
19
20struct Bordered<L: Layoutable<C>, C: PixelColor, D: Decorator<C>> {
21    layoutable: L,
22    decorator: D,
23    p: PhantomData<C>,
24}
25
26///
27/// Generate a border around a layouted element
28///
29/// # Arguments
30///
31/// * `layoutable`: element within the border
32/// * `decorator`: definition of the decorator around that element
33///
34/// returns: impl Layoutable<C>+Sized
35///
36/// # Examples
37///
38/// ```
39/// use embedded_graphics::mono_font::iso_8859_1::FONT_6X12;
40/// use embedded_graphics::mono_font::MonoTextStyle;
41/// use embedded_graphics::pixelcolor::BinaryColor;
42/// use simple_layout::prelude::{bordered, center, DashedLine, owned_text};
43/// let temperature = 20.7;
44/// let element = bordered(
45///                 center(owned_text(format!("{temperature:.1}°C"), MonoTextStyle::new(&FONT_6X12, BinaryColor::On))),
46///                 DashedLine::new(2, 2, BinaryColor::On),
47///             );
48/// ```
49pub fn bordered<L: Layoutable<C>, C: PixelColor, D: Decorator<C>>(
50    layoutable: L,
51    decorator: D,
52) -> impl Layoutable<C> {
53    Bordered {
54        layoutable,
55        decorator,
56        p: PhantomData,
57    }
58}
59
60impl<L: Layoutable<C>, C: PixelColor, D: Decorator<C>> Layoutable<C> for Bordered<L, C, D> {
61    fn size(&self) -> ComponentSize {
62        let ComponentSize { width, height } = self.layoutable.size();
63        let offset = Saturating(self.decorator.width() * 2);
64        ComponentSize {
65            width: width + offset,
66            height: height + offset,
67        }
68    }
69
70    fn draw_placed<DrawError>(
71        &self,
72        target: &mut impl DrawTarget<Color = C, Error = DrawError>,
73        position: Rectangle,
74    ) -> Result<(), DrawError> {
75        let border = self.decorator.width();
76        let Rectangle {
77            top_left: Point { x, y },
78            size: Size { width, height },
79        } = position;
80        let inner_position = Rectangle {
81            top_left: Point {
82                x: x + border as i32,
83                y: y + border as i32,
84            },
85            size: Size {
86                width: width - 2 * border,
87                height: height - 2 * border,
88            },
89        };
90        self.decorator.draw_placed(target, position)?;
91        self.layoutable.draw_placed(target, inner_position)
92    }
93}
94pub struct DashedLine<C: PixelColor> {
95    dot_count: u32,
96    gap_count: u32,
97    color: C,
98}
99
100impl<C: PixelColor> DashedLine<C> {
101    /// Create a new dashed line
102    ///
103    /// # Arguments
104    ///
105    /// * `dot_count`: count of dots on the pattern to draw
106    /// * `gap_count`: count of pixels to miss between the strokes
107    /// * `color`: color to paint the dots
108    ///
109    /// returns: DashedLine<C>
110    ///
111    /// # Examples
112    ///
113    /// Draw 2 dots, then skip 2 dots
114    /// ```
115    /// use embedded_graphics::pixelcolor::BinaryColor;
116    /// use simple_layout::prelude::DashedLine;
117    /// DashedLine::new(2, 2, BinaryColor::On);
118    /// ```
119    pub fn new(dot_count: u32, gap_count: u32, color: C) -> Self {
120        Self {
121            dot_count,
122            gap_count,
123            color,
124        }
125    }
126}
127
128impl<C: PixelColor> Decorator<C> for DashedLine<C> {
129    fn width(&self) -> u32 {
130        1
131    }
132
133    fn draw_placed<DrawError>(
134        &self,
135        target: &mut impl DrawTarget<Color = C, Error = DrawError>,
136        position: Rectangle,
137    ) -> Result<(), DrawError> {
138        let sequence_length = self.dot_count + self.gap_count;
139        let Point { x: sx, y: sy } = position.top_left;
140        let Size { width, height } = position.size;
141        let ex = sx + width as i32 - 1;
142        let ey = sy + height as i32 - 1;
143        target.draw_iter(
144            (sx..ex)
145                .map(|x| Pixel(Point { x, y: sy }, self.color))
146                .chain((sy..ey).map(|y| Pixel(Point { x: ex, y }, self.color)))
147                .chain(
148                    (sx..ex)
149                        .rev()
150                        .map(|x| Pixel(Point { x, y: ey }, self.color)),
151                )
152                .chain(
153                    (sy..ey)
154                        .rev()
155                        .map(|y| Pixel(Point { x: sx, y }, self.color)),
156                )
157                .enumerate()
158                .filter(|(i, _)| *i as u32 % sequence_length < self.dot_count)
159                .map(|(_, p)| p),
160        )
161    }
162}
163
164pub struct RoundedLine<C: PixelColor> {
165    color: C,
166}
167
168impl<C: PixelColor> RoundedLine<C> {
169    ///
170    /// Create a pattern of a rounded line around a element
171    ///
172    /// # Arguments
173    ///
174    /// * `color`: Color of the line
175    ///
176    /// returns: RoundedLine<C>
177    ///
178    /// # Examples
179    ///
180    /// defines a plus button by rendering a '+' and draw a rounded line around
181    /// ```
182    /// use embedded_graphics::mono_font::iso_8859_1::FONT_6X12;
183    /// use embedded_graphics::mono_font::MonoTextStyle;
184    /// use embedded_graphics::pixelcolor::BinaryColor;
185    /// use embedded_graphics::prelude::Point;
186    /// use embedded_graphics::text::Text;
187    /// use simple_layout::prelude::{bordered, padding, RoundedLine};
188    /// let plus_button = bordered(
189    ///                     padding(Text::new("+", Point::zero(), MonoTextStyle::new(&FONT_6X12, BinaryColor::On)), -2, 1, -1, 1),
190    ///                     RoundedLine::new(BinaryColor::On),
191    ///                 );
192    /// ```
193    pub fn new(color: C) -> Self {
194        Self { color }
195    }
196}
197
198impl<C: PixelColor> Decorator<C> for RoundedLine<C> {
199    fn width(&self) -> u32 {
200        2
201    }
202
203    fn draw_placed<DrawError>(
204        &self,
205        target: &mut impl DrawTarget<Color = C, Error = DrawError>,
206        position: Rectangle,
207    ) -> Result<(), DrawError> {
208        let Point { x: sx, y: sy } = position.top_left;
209        let Size { width, height } = position.size;
210        let ex = sx + width as i32 - 1;
211        let ey = sy + height as i32 - 1;
212        target.draw_iter(
213            (sx + 2..ex - 1)
214                .map(|x| Pixel(Point { x, y: sy }, self.color))
215                .chain(Some(Pixel(
216                    Point {
217                        x: ex - 1,
218                        y: sy + 1,
219                    },
220                    self.color,
221                )))
222                .chain((sy + 2..ey - 1).map(|y| Pixel(Point { x: ex, y }, self.color)))
223                .chain(Some(Pixel(
224                    Point {
225                        x: ex - 1,
226                        y: ey - 1,
227                    },
228                    self.color,
229                )))
230                .chain(
231                    (sx + 2..ex - 1)
232                        .rev()
233                        .map(|x| Pixel(Point { x, y: ey }, self.color)),
234                )
235                .chain(Some(Pixel(
236                    Point {
237                        x: sx + 1,
238                        y: ey - 1,
239                    },
240                    self.color,
241                )))
242                .chain(
243                    (sy + 2..ey - 1)
244                        .rev()
245                        .map(|y| Pixel(Point { x: sx, y }, self.color)),
246                )
247                .chain(Some(Pixel(
248                    Point {
249                        x: sx + 1,
250                        y: sy + 1,
251                    },
252                    self.color,
253                ))),
254        )
255    }
256}