1use crate::{
2 draw_target::DrawTarget,
3 geometry::{Dimensions, Point},
4 pixelcolor::PixelColor,
5 primitives::{
6 common::{Scanline, StyledScanline},
7 rounded_rectangle::{points::Scanlines, RoundedRectangle},
8 styled::{StyledDimensions, StyledDrawable, StyledPixels},
9 PrimitiveStyle, Rectangle,
10 },
11 Pixel,
12};
13use az::SaturatingAs;
14
15use super::RoundedRectangleContains;
16
17#[derive(Clone, Eq, PartialEq, Hash, Debug)]
19#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
20pub struct StyledPixelsIterator<C> {
21 styled_scanlines: StyledScanlines,
22
23 stroke_left: Scanline,
24 fill: Scanline,
25 stroke_right: Scanline,
26
27 stroke_color: Option<C>,
28 fill_color: Option<C>,
29}
30
31impl<C: PixelColor> StyledPixelsIterator<C> {
32 pub(in crate::primitives) fn new(
33 primitive: &RoundedRectangle,
34 style: &PrimitiveStyle<C>,
35 ) -> Self {
36 let stroke_area = style.stroke_area(primitive);
37 let fill_area = style.fill_area(primitive);
38
39 Self {
40 styled_scanlines: StyledScanlines::new(&stroke_area, &fill_area),
41 stroke_left: Scanline::new_empty(0),
42 fill: Scanline::new_empty(0),
43 stroke_right: Scanline::new_empty(0),
44 stroke_color: style.stroke_color,
45 fill_color: style.fill_color,
46 }
47 }
48}
49
50impl<C: PixelColor> Iterator for StyledPixelsIterator<C> {
51 type Item = Pixel<C>;
52
53 fn next(&mut self) -> Option<Self::Item> {
54 match (self.stroke_color, self.fill_color) {
55 (Some(stroke_color), None) => loop {
56 if let Some(pixel) = self
57 .stroke_left
58 .next()
59 .or_else(|| self.stroke_right.next())
60 .map(|p| Pixel(p, stroke_color))
61 {
62 return Some(pixel);
63 }
64
65 let scanline = self.styled_scanlines.next()?;
66 self.stroke_left = scanline.stroke_left();
67 self.stroke_right = scanline.stroke_right();
68 },
69 (Some(stroke_color), Some(fill_color)) => loop {
70 if let Some(pixel) = self
71 .stroke_left
72 .next()
73 .map(|p| Pixel(p, stroke_color))
74 .or_else(|| self.fill.next().map(|p| Pixel(p, fill_color)))
75 .or_else(|| self.stroke_right.next().map(|p| Pixel(p, stroke_color)))
76 {
77 return Some(pixel);
78 }
79
80 let scanline = self.styled_scanlines.next()?;
81 self.stroke_left = scanline.stroke_left();
82 self.fill = scanline.fill();
83 self.stroke_right = scanline.stroke_right();
84 },
85 (None, Some(fill_color)) => loop {
86 if let Some(pixel) = self.fill.next().map(|p| Pixel(p, fill_color)) {
87 return Some(pixel);
88 }
89
90 let scanline = self.styled_scanlines.next()?;
91 self.fill = scanline.fill();
92 },
93 (None, None) => None,
94 }
95 }
96}
97
98impl<C: PixelColor> StyledPixels<PrimitiveStyle<C>> for RoundedRectangle {
99 type Iter = StyledPixelsIterator<C>;
100
101 fn pixels(&self, style: &PrimitiveStyle<C>) -> Self::Iter {
102 StyledPixelsIterator::new(self, style)
103 }
104}
105
106impl<C: PixelColor> StyledDrawable<PrimitiveStyle<C>> for RoundedRectangle {
107 type Color = C;
108 type Output = ();
109
110 fn draw_styled<D>(
111 &self,
112 style: &PrimitiveStyle<C>,
113 target: &mut D,
114 ) -> Result<Self::Output, D::Error>
115 where
116 D: DrawTarget<Color = C>,
117 {
118 match (style.effective_stroke_color(), style.fill_color) {
119 (Some(stroke_color), None) => {
120 for scanline in
121 StyledScanlines::new(&style.stroke_area(self), &style.fill_area(self))
122 {
123 scanline.draw_stroke(target, stroke_color)?;
124 }
125 }
126 (Some(stroke_color), Some(fill_color)) => {
127 for scanline in
128 StyledScanlines::new(&style.stroke_area(self), &style.fill_area(self))
129 {
130 scanline.draw_stroke_and_fill(target, stroke_color, fill_color)?;
131 }
132 }
133 (None, Some(fill_color)) => {
134 for scanline in Scanlines::new(&style.fill_area(self)) {
135 scanline.draw(target, fill_color)?;
136 }
137 }
138 (None, None) => {}
139 }
140
141 Ok(())
142 }
143}
144
145impl<C: PixelColor> StyledDimensions<PrimitiveStyle<C>> for RoundedRectangle {
146 fn styled_bounding_box(&self, style: &PrimitiveStyle<C>) -> Rectangle {
147 let offset = style.outside_stroke_width().saturating_as();
148
149 self.bounding_box().offset(offset)
150 }
151}
152
153#[derive(Clone, Eq, PartialEq, Hash, Debug)]
154#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
155struct StyledScanlines {
156 scanlines: Scanlines,
157 fill_area: RoundedRectangleContains,
158}
159
160impl StyledScanlines {
161 pub fn new(stroke_area: &RoundedRectangle, fill_area: &RoundedRectangle) -> Self {
162 Self {
163 scanlines: Scanlines::new(stroke_area),
164 fill_area: RoundedRectangleContains::new(fill_area),
165 }
166 }
167}
168
169impl Iterator for StyledScanlines {
170 type Item = StyledScanline;
171
172 fn next(&mut self) -> Option<Self::Item> {
173 self.scanlines.next().map(|scanline| {
174 if self.fill_area.rows.contains(&scanline.y) {
175 let fill_start = scanline
176 .x
177 .clone()
178 .find(|x| self.fill_area.contains(Point::new(*x, scanline.y)))
179 .unwrap_or(scanline.x.start);
180
181 let fill_end = scanline
182 .x
183 .clone()
184 .rfind(|x| self.fill_area.contains(Point::new(*x, scanline.y)))
185 .map(|x| x + 1)
186 .unwrap_or(scanline.x.end);
187
188 StyledScanline::new(scanline.y, scanline.x, Some(fill_start..fill_end))
189 } else {
190 StyledScanline::new(scanline.y, scanline.x, None)
191 }
192 })
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199 use crate::{
200 geometry::{Dimensions, Point, Size},
201 iterator::PixelIteratorExt,
202 mock_display::MockDisplay,
203 pixelcolor::{BinaryColor, Rgb888, RgbColor},
204 primitives::{
205 rectangle::Rectangle, CornerRadii, Primitive, PrimitiveStyleBuilder, StrokeAlignment,
206 },
207 Drawable,
208 };
209
210 #[test]
211 fn transparent_style_no_render() {
212 let rounded_rect = RoundedRectangle::with_equal_corners(
213 Rectangle::new(Point::zero(), Size::new(10, 20)),
214 Size::new(4, 8),
215 )
216 .into_styled(PrimitiveStyleBuilder::<BinaryColor>::new().build());
217
218 assert!(rounded_rect.pixels().eq(core::iter::empty()));
219 }
220
221 #[test]
222 fn thin_line_zero_radius_equals_rectangle() {
223 let style = PrimitiveStyleBuilder::new()
224 .stroke_color(Rgb888::RED)
225 .stroke_width(1)
226 .fill_color(Rgb888::RED)
227 .build();
228
229 let mut expected = MockDisplay::new();
230 Rectangle::new(Point::zero(), Size::new(20, 30))
231 .into_styled(style)
232 .draw(&mut expected)
233 .unwrap();
234
235 let rounded_rect = RoundedRectangle::with_equal_corners(
236 Rectangle::new(Point::zero(), Size::new(20, 30)),
237 Size::zero(),
238 )
239 .into_styled(style);
240
241 let mut drawable = MockDisplay::new();
242 rounded_rect.draw(&mut drawable).unwrap();
243 drawable.assert_eq(&expected);
244
245 let mut pixels = MockDisplay::new();
246 rounded_rect.pixels().draw(&mut pixels).unwrap();
247 pixels.assert_eq(&expected);
248 }
249
250 #[test]
251 fn styled_unequal_corners() {
252 let expected_pattern = &[
253 " GGGGGGGGGGGGGGGG ",
254 " GGGGGGGGGGGGGGGGGGG ",
255 " GGGGGGGGGGGGGGGGGGGGG ",
256 "GGGGGGGGGGGGGGGGGGGGGGG ",
257 "GGGGGGGGGGGGGGGGGGGGGGG ",
258 "GGGGGRRRRRRRRRRRRRGGGGGG",
259 "GGGGGRRRRRRRRRRRRRRGGGGG",
260 "GGGGGRRRRRRRRRRRRRRGGGGG",
261 "GGGGGRRRRRRRRRRRRRRGGGGG",
262 "GGGGGRRRRRRRRRRRRRRGGGGG",
263 "GGGGGRRRRRRRRRRRRRRGGGGG",
264 "GGGGGRRRRRRRRRRRRRRGGGGG",
265 "GGGGGRRRRRRRRRRRRRRGGGGG",
266 "GGGGGRRRRRRRRRRRRRRGGGGG",
267 "GGGGGRRRRRRRRRRRRRRGGGGG",
268 "GGGGGGRRRRRRRRRRRRRGGGGG",
269 " GGGGGRRRRRRRRRRRRGGGGGG",
270 " GGGGGGRRRRRRRRRRRGGGGG ",
271 " GGGGGGGRRRRRRRRGGGGGG ",
272 " GGGGGGGGGGGGGGGGGGGGG ",
273 " GGGGGGGGGGGGGGGGGGG ",
274 " GGGGGGGGGGGGGGGGG ",
275 " GGGGGGGGGGGGGG ",
276 " GGGGGGGGGG ",
277 ];
278
279 let rounded_rect = RoundedRectangle::new(
280 Rectangle::new(Point::new_equal(2), Size::new(20, 20)),
281 CornerRadii {
282 top_left: Size::new(3, 4),
283 top_right: Size::new(5, 6),
284 bottom_right: Size::new(7, 8),
285 bottom_left: Size::new(9, 10),
286 },
287 )
288 .into_styled(
289 PrimitiveStyleBuilder::new()
290 .stroke_width(5)
291 .fill_color(Rgb888::RED)
292 .stroke_color(Rgb888::GREEN)
293 .build(),
294 );
295
296 let mut drawable = MockDisplay::new();
297 rounded_rect.draw(&mut drawable).unwrap();
298 drawable.assert_pattern(expected_pattern);
299
300 let mut pixels = MockDisplay::new();
301 rounded_rect.pixels().draw(&mut pixels).unwrap();
302 pixels.assert_pattern(expected_pattern);
303 }
304
305 #[test]
306 fn styled_unfilled() {
307 let expected_pattern = &[
308 " BBBBBBBBBBBBBBB ",
309 " B B ",
310 "B B ",
311 "B BB",
312 "B B",
313 "B B",
314 "B B",
315 "B B",
316 "B B",
317 "B B",
318 "B B",
319 "B B",
320 "B B",
321 " B B",
322 " B B",
323 " BB B ",
324 " B B ",
325 " BB B ",
326 " BB BB ",
327 " BBBBBBBBB ",
328 ];
329
330 let rounded_rect = RoundedRectangle::new(
331 Rectangle::new(Point::zero(), Size::new(20, 20)),
332 CornerRadii {
333 top_left: Size::new(3, 4),
334 top_right: Size::new(5, 6),
335 bottom_right: Size::new(7, 8),
336 bottom_left: Size::new(9, 10),
337 },
338 )
339 .into_styled(
340 PrimitiveStyleBuilder::new()
341 .stroke_width(1)
342 .stroke_color(Rgb888::BLUE)
343 .build(),
344 );
345
346 let mut drawable = MockDisplay::new();
347 rounded_rect.draw(&mut drawable).unwrap();
348 drawable.assert_pattern(expected_pattern);
349
350 let mut pixels = MockDisplay::new();
351 rounded_rect.pixels().draw(&mut pixels).unwrap();
352 pixels.assert_pattern(expected_pattern);
353 }
354
355 #[test]
356 fn full_height_corners() {
357 let expected_pattern = &[
358 " RRRRRRRR ",
359 " RRRRRRRRRRRRRRRR ",
360 " RRRRRRRRRRRRRRRRRRRR ",
361 " RRRRRRRRRRRRRRRRRRRRRR ",
362 " RRRRRRRRRRRRRRRRRRRRRRRRRR ",
363 " RRRRRRRRRRRRRRRRRRRRRRRRRRRR ",
364 " RRRRRRRRRRRRRRRRRRRRRRRRRRRRRR ",
365 " RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR ",
366 " RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR ",
367 " RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR ",
368 " RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR ",
369 " RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR ",
370 " RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR ",
371 " RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR ",
372 " RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR ",
373 " RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR ",
374 "RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR",
375 "RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR",
376 "RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR",
377 "RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR",
378 ];
379
380 let rounded_rect = RoundedRectangle::new(
381 Rectangle::new(Point::zero(), Size::new(40, 20)),
382 CornerRadii {
383 top_left: Size::new(20, 20),
384 top_right: Size::new(20, 20),
385 bottom_right: Size::new(0, 0),
386 bottom_left: Size::new(0, 0),
387 },
388 )
389 .into_styled(PrimitiveStyleBuilder::new().fill_color(Rgb888::RED).build());
390
391 let mut drawable = MockDisplay::new();
392 rounded_rect.draw(&mut drawable).unwrap();
393 drawable.assert_pattern(expected_pattern);
394
395 let mut pixels = MockDisplay::new();
396 rounded_rect.pixels().draw(&mut pixels).unwrap();
397 pixels.assert_pattern(expected_pattern);
398 }
399
400 #[test]
401 fn styled_dimensions() {
402 let base = PrimitiveStyleBuilder::new()
403 .stroke_width(10)
404 .stroke_color(Rgb888::RED);
405
406 let inside = base.stroke_alignment(StrokeAlignment::Inside).build();
407 let outside = base.stroke_alignment(StrokeAlignment::Outside).build();
408 let center = base.stroke_alignment(StrokeAlignment::Center).build();
409
410 let item = RoundedRectangle::new(
411 Rectangle::new(Point::new(10, 10), Size::new(40, 20)),
412 CornerRadii {
413 top_left: Size::new(20, 20),
414 top_right: Size::new(20, 20),
415 bottom_right: Size::new(0, 0),
416 bottom_left: Size::new(0, 0),
417 },
418 );
419
420 let center = item.into_styled(center);
421 let inside = item.into_styled(inside);
422 let outside = item.into_styled(outside);
423
424 assert_eq!(center.bounding_box(), item.bounding_box().offset(5));
425 assert_eq!(inside.bounding_box(), item.bounding_box());
426 assert_eq!(outside.bounding_box(), item.bounding_box().offset(10));
427
428 let mut display = MockDisplay::new();
429 center.draw(&mut display).unwrap();
430 assert_eq!(display.affected_area(), center.bounding_box());
431 let mut display = MockDisplay::new();
432 inside.draw(&mut display).unwrap();
433 assert_eq!(display.affected_area(), inside.bounding_box());
434 let mut display = MockDisplay::new();
435 outside.draw(&mut display).unwrap();
436 assert_eq!(display.affected_area(), outside.bounding_box());
437 }
438
439 #[test]
440 fn bounding_box_is_independent_of_colors() {
441 let rect = RoundedRectangle::new(
442 Rectangle::new(Point::new(5, 5), Size::new(11, 14)),
443 CornerRadii {
444 top_left: Size::new(20, 20),
445 top_right: Size::new(20, 20),
446 bottom_right: Size::new(0, 0),
447 bottom_left: Size::new(0, 0),
448 },
449 );
450
451 let transparent_rect = rect.into_styled(PrimitiveStyle::<Rgb888>::new());
452 let filled_rect = rect.into_styled(PrimitiveStyle::with_fill(Rgb888::RED));
453
454 assert_eq!(transparent_rect.bounding_box(), filled_rect.bounding_box(),);
455 }
456}