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
26pub 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 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 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}