embedded_graphics/mock_display/mod.rs
1//! Mock display for use in tests.
2//!
3//! [`MockDisplay`] can be used to replace a real display in tests. The internal
4//! framebuffer wraps the color values in `Option` to be able to test which
5//! pixels were modified by drawing operations.
6//!
7//! The [`from_pattern`] method provides a convenient way of creating expected
8//! test results. The same patterns are used by the implementation of `Debug`
9//! and will be shown in failing tests.
10//!
11//! The display is internally capped at 64x64px.
12//!
13//! # Assertions
14//!
15//! [`MockDisplay`] provides the [`assert_eq`] and [`assert_pattern`] methods to check if the
16//! display is in the correct state after some drawing operations were executed. It's recommended
17//! to use these methods instead of the standard `assert_eq!` macro, because they provide an
18//! optional improved debug output for failing tests. If the `EG_FANCY_PANIC` environment variable
19//! is set to `1` at compile time a graphic representation of the display content and a diff of the
20//! display and the expected output will be shown:
21//!
22//! ```bash
23//! EG_FANCY_PANIC=1 cargo test
24//! ```
25//!
26//! Enabling the advanced test output requires a terminal that supports 24 BPP colors and a font
27//! that includes the upper half block character `'\u{2580}'`.
28//!
29//! The color code used to show the difference between the display and the expected output is shown
30//! in the documentation of the [`diff`] method.
31//!
32//! # Additional out of bounds and overdraw checks
33//!
34//! [`MockDisplay`] implements additional checks during drawing operations that will cause a panic if
35//! any pixel is drawn outside the framebuffer or if a pixel is drawn more than once. These
36//! stricter checks were added to help with testing and shouldn't be implemented by normal
37//! [`DrawTarget`]s.
38//!
39//! If a test relies on out of bounds drawing or overdrawing the additional checks can explicitly
40//! be disabled by using [`set_allow_out_of_bounds_drawing`] and [`set_allow_overdraw`].
41//!
42//! # Characters used in `BinaryColor` patterns
43//!
44//! The following mappings are available for [`BinaryColor`]:
45//!
46//! | Character | Color | Description |
47//! |-----------|--------------------------|-----------------------------------------|
48//! | `' '` | `None` | No drawing operation changed the pixel |
49//! | `'.'` | `Some(BinaryColor::Off)` | Pixel was changed to `BinaryColor::Off` |
50//! | `'#'` | `Some(BinaryColor::On)` | Pixel was changed to `BinaryColor::On` |
51//!
52//! # Characters used in [`Gray2`] patterns
53//!
54//! The following mappings are available for [`Gray2`]:
55//!
56//! | Character | Color | Description |
57//! |-----------|--------------------------|-----------------------------------------|
58//! | `' '` | `None` | No drawing operation changed the pixel |
59//! | `'0'` | `Some(Gray2::new(0x0))` | Pixel was changed to `Gray2::new(0x0)` |
60//! | `'1'` | `Some(Gray2::new(0x1))` | Pixel was changed to `Gray2::new(0x1)` |
61//! | `'2'` | `Some(Gray2::new(0x2))` | Pixel was changed to `Gray2::new(0x2)` |
62//! | `'3'` | `Some(Gray2::new(0x3))` | Pixel was changed to `Gray2::new(0x3)` |
63//!
64//! # Characters used in [`Gray4`] patterns
65//!
66//! The following mappings are available for [`Gray4`]:
67//!
68//! | Character | Color | Description |
69//! |-----------|--------------------------|-----------------------------------------|
70//! | `' '` | `None` | No drawing operation changed the pixel |
71//! | `'0'` | `Some(Gray4::new(0x0))` | Pixel was changed to `Gray4::new(0x0)` |
72//! | `'1'` | `Some(Gray4::new(0x1))` | Pixel was changed to `Gray4::new(0x1)` |
73//! | ⋮ | ⋮ | ⋮ |
74//! | `'E'` | `Some(Gray4::new(0xE))` | Pixel was changed to `Gray4::new(0xE)` |
75//! | `'F'` | `Some(Gray4::new(0xF))` | Pixel was changed to `Gray4::new(0xF)` |
76//!
77//! # Characters used in [`Gray8`] patterns
78//!
79//! The following mappings are available for [`Gray8`]:
80//!
81//! | Character | Color | Description |
82//! |-----------|--------------------------|-----------------------------------------|
83//! | `' '` | `None` | No drawing operation changed the pixel |
84//! | `'0'` | `Some(Gray8::new(0x00))` | Pixel was changed to `Gray8::new(0x00)` |
85//! | `'1'` | `Some(Gray8::new(0x11))` | Pixel was changed to `Gray8::new(0x11)` |
86//! | ⋮ | ⋮ | ⋮ |
87//! | `'E'` | `Some(Gray8::new(0xEE))` | Pixel was changed to `Gray8::new(0xEE)` |
88//! | `'F'` | `Some(Gray8::new(0xFF))` | Pixel was changed to `Gray8::new(0xFF)` |
89//!
90//! Note: `Gray8` uses a different mapping than `Gray2` and `Gray4`, by duplicating the pattern
91//! value into the high and low nibble. This allows using a single digit to test luma values ranging
92//! from 0 to 255.
93//!
94//! # Characters used in RGB color patterns
95//!
96//! The following mappings are available for all RGB color types in the [`pixelcolor`] module,
97//! like [`Rgb565`] or [`Rgb888`]:
98//!
99//! | Character | Color | Description |
100//! |-----------|--------------------------|-----------------------------------------|
101//! | `' '` | `None` | No drawing operation changed the pixel |
102//! | `'K'` | `Some(C::BLACK)` | Pixel was changed to `C::BLACK` |
103//! | `'R'` | `Some(C::RED)` | Pixel was changed to `C::RED` |
104//! | `'G'` | `Some(C::GREEN)` | Pixel was changed to `C::GREEN` |
105//! | `'B'` | `Some(C::BLUE)` | Pixel was changed to `C::BLUE` |
106//! | `'Y'` | `Some(C::YELLOW)` | Pixel was changed to `C::YELLOW` |
107//! | `'M'` | `Some(C::MAGENTA)` | Pixel was changed to `C::MAGENTA` |
108//! | `'C'` | `Some(C::CYAN)` | Pixel was changed to `C::CYAN` |
109//! | `'W'` | `Some(C::WHITE)` | Pixel was changed to `C::WHITE` |
110//!
111//! Note: The table used `C` as a placeholder for the actual color type, like `Rgb565::BLACK`.
112//!
113//! # Examples
114//!
115//! ## Assert that a modified display matches the expected value
116//!
117//! This example sets three pixels on the display and checks that they're turned on.
118//!
119//! ```rust
120//! use embedded_graphics::{mock_display::MockDisplay, pixelcolor::BinaryColor, prelude::*};
121//!
122//! let mut display = MockDisplay::new();
123//!
124//! Pixel(Point::new(0, 0), BinaryColor::On).draw(&mut display);
125//! Pixel(Point::new(2, 1), BinaryColor::On).draw(&mut display);
126//! Pixel(Point::new(1, 2), BinaryColor::On).draw(&mut display);
127//!
128//! display.assert_pattern(&[
129//! "# ", //
130//! " #", //
131//! " # ", //
132//! ]);
133//! ```
134//!
135//! ## Load and validate a 16BPP image
136//!
137//! This example loads the following test image (scaled 10x to make it visible) and tests the
138//! returned pixels against an expected pattern.
139//!
140//! 
141//!
142//! ```rust
143//! use embedded_graphics::{
144//! image::{Image, ImageRaw, ImageRawBE},
145//! mock_display::MockDisplay,
146//! pixelcolor::{Rgb565, RgbColor},
147//! prelude::*,
148//! };
149//!
150//! let data = [
151//! 0x00, 0x00, 0xF8, 0x00, 0x07, 0xE0, 0xFF, 0xE0, //
152//! 0x00, 0x1F, 0x07, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, //
153//! ];
154//!
155//! let raw: ImageRawBE<Rgb565> = ImageRaw::new(&data, 4);
156//!
157//! let image = Image::new(&raw, Point::zero());
158//!
159//! let mut display: MockDisplay<Rgb565> = MockDisplay::new();
160//!
161//! image.draw(&mut display);
162//!
163//! display.assert_pattern(&[
164//! "KRGY", //
165//! "BCMW", //
166//! ]);
167//! ```
168//!
169//! [`pixelcolor`]: super::pixelcolor#structs
170//! [`BinaryColor`]: super::pixelcolor::BinaryColor
171//! [`Gray2`]: super::pixelcolor::Gray2
172//! [`Gray4`]: super::pixelcolor::Gray4
173//! [`Gray8`]: super::pixelcolor::Gray8
174//! [`Rgb565`]: super::pixelcolor::Rgb565
175//! [`Rgb888`]: super::pixelcolor::Rgb888
176//! [`DrawTarget`]: super::draw_target::DrawTarget
177//! [`assert_eq`]: MockDisplay::assert_eq()
178//! [`assert_pattern`]: MockDisplay::assert_pattern()
179//! [`diff`]: MockDisplay::diff()
180//! [`from_pattern`]: MockDisplay::from_pattern()
181//! [`set_allow_overdraw`]: MockDisplay::set_allow_overdraw()
182//! [`set_allow_out_of_bounds_drawing`]: MockDisplay::set_allow_out_of_bounds_drawing()
183
184mod color_mapping;
185mod fancy_panic;
186
187use crate::{
188 draw_target::DrawTarget,
189 geometry::{Dimensions, OriginDimensions, Point, Size},
190 pixelcolor::{PixelColor, Rgb888, RgbColor},
191 primitives::{PointsIter, Rectangle},
192 Pixel,
193};
194pub use color_mapping::ColorMapping;
195use core::{
196 fmt::{self, Write},
197 iter,
198};
199use fancy_panic::FancyPanic;
200
201const SIZE: usize = 64;
202const DISPLAY_AREA: Rectangle = Rectangle::new(Point::zero(), Size::new_equal(SIZE as u32));
203
204/// Mock display struct
205///
206/// See the [module documentation](self) for usage and examples.
207#[derive(Clone)]
208pub struct MockDisplay<C>
209where
210 C: PixelColor,
211{
212 pixels: [Option<C>; SIZE * SIZE],
213 allow_overdraw: bool,
214 allow_out_of_bounds_drawing: bool,
215}
216
217impl<C> MockDisplay<C>
218where
219 C: PixelColor,
220{
221 /// Creates a new empty mock display.
222 pub fn new() -> Self {
223 Self::default()
224 }
225
226 /// Create a mock display from an iterator of [`Point`]s.
227 ///
228 /// This method can be used to create a mock display from the iterator produced by the
229 /// [`PointsIter::points`] method.
230 ///
231 /// # Panics
232 ///
233 /// This method will panic if the iterator returns a point that is outside the display bounding
234 /// box.
235 ///
236 /// # Examples
237 ///
238 /// ```rust
239 /// use embedded_graphics::{prelude::*, pixelcolor::BinaryColor, primitives::Circle, mock_display::MockDisplay};
240 ///
241 /// let circle = Circle::new(Point::new(0, 0), 4);
242 ///
243 /// let mut display = MockDisplay::from_points(circle.points(), BinaryColor::On);
244 ///
245 /// display.assert_pattern(&[
246 /// " ## ",
247 /// "####",
248 /// "####",
249 /// " ## ",
250 /// ]);
251 /// ```
252 ///
253 /// [`Point`]: super::geometry::Point
254 /// [`PointsIter::points`]: super::primitives::PointsIter::points
255 /// [`map`]: MockDisplay::map()
256 /// [`BinaryColor`]: super::pixelcolor::BinaryColor
257 pub fn from_points<I>(points: I, color: C) -> Self
258 where
259 I: IntoIterator<Item = Point>,
260 {
261 let mut display = Self::new();
262 display.set_pixels(points, Some(color));
263
264 display
265 }
266
267 /// Sets if out of bounds drawing is allowed.
268 ///
269 /// If this is set to `true` the bounds checks during drawing are disabled.
270 pub fn set_allow_out_of_bounds_drawing(&mut self, value: bool) {
271 self.allow_out_of_bounds_drawing = value;
272 }
273
274 /// Sets if overdrawing is allowed.
275 ///
276 /// If this is set to `true` the overdrawing is allowed.
277 pub fn set_allow_overdraw(&mut self, value: bool) {
278 self.allow_overdraw = value;
279 }
280
281 /// Returns the color of a pixel.
282 pub const fn get_pixel(&self, p: Point) -> Option<C> {
283 let Point { x, y } = p;
284
285 self.pixels[x as usize + y as usize * SIZE]
286 }
287
288 /// Changes the value of a pixel without bounds checking.
289 ///
290 /// # Panics
291 ///
292 /// This method will panic if `point` is outside the display bounding box.
293 pub fn set_pixel(&mut self, point: Point, color: Option<C>) {
294 assert!(
295 point.x >= 0 && point.y >= 0 && point.x < SIZE as i32 && point.y < SIZE as i32,
296 "point must be inside display bounding box: {:?}",
297 point
298 );
299
300 let i = point.x + point.y * SIZE as i32;
301 self.pixels[i as usize] = color;
302 }
303
304 /// Changes the value of a pixel without bounds checking.
305 ///
306 /// # Panics
307 ///
308 /// This method will panic if `point` is outside the display bounding box.
309 fn set_pixel_unchecked(&mut self, point: Point, color: Option<C>) {
310 let i = point.x + point.y * SIZE as i32;
311 self.pixels[i as usize] = color;
312 }
313
314 /// Sets the points in an iterator to the given color.
315 ///
316 /// # Panics
317 ///
318 /// This method will panic if the iterator returns points outside the display bounding box.
319 pub fn set_pixels(&mut self, points: impl IntoIterator<Item = Point>, color: Option<C>) {
320 for point in points {
321 self.set_pixel(point, color);
322 }
323 }
324
325 /// Returns the area that was affected by drawing operations.
326 pub fn affected_area(&self) -> Rectangle {
327 let (tl, br) = self
328 .bounding_box()
329 .points()
330 .zip(self.pixels.iter())
331 .filter_map(|(point, color)| color.map(|_| point))
332 .fold(
333 (None, None),
334 |(tl, br): (Option<Point>, Option<Point>), point| {
335 (
336 tl.map(|tl| tl.component_min(point)).or(Some(point)),
337 br.map(|br| br.component_max(point)).or(Some(point)),
338 )
339 },
340 );
341
342 if let (Some(tl), Some(br)) = (tl, br) {
343 Rectangle::with_corners(tl, br)
344 } else {
345 Rectangle::zero()
346 }
347 }
348
349 /// Returns the `affected_area` with the top left corner extended to `(0, 0)`.
350 fn affected_area_origin(&self) -> Rectangle {
351 self.affected_area()
352 .bottom_right()
353 .map(|bottom_right| Rectangle::with_corners(Point::zero(), bottom_right))
354 .unwrap_or_default()
355 }
356
357 /// Changes the color of a pixel.
358 ///
359 /// # Panics
360 ///
361 /// If out of bounds draw checking is enabled (default), this method will panic if the point
362 /// lies outside the display area. This behavior can be disabled by calling
363 /// [`set_allow_out_of_bounds_drawing(true)`].
364 ///
365 /// Similarly, overdraw is checked by default and will panic if a point is drawn to the same
366 /// coordinate twice. This behavior can be disabled by calling [`set_allow_overdraw(true)`].
367 ///
368 /// [`set_allow_out_of_bounds_drawing(true)`]: MockDisplay::set_allow_out_of_bounds_drawing()
369 /// [`set_allow_overdraw(true)`]: MockDisplay::set_allow_overdraw()
370 pub fn draw_pixel(&mut self, point: Point, color: C) {
371 if !DISPLAY_AREA.contains(point) {
372 if !self.allow_out_of_bounds_drawing {
373 panic!(
374 "tried to draw pixel outside the display area (x: {}, y: {})",
375 point.x, point.y
376 );
377 } else {
378 return;
379 }
380 }
381
382 if !self.allow_overdraw && self.get_pixel(point).is_some() {
383 panic!("tried to draw pixel twice (x: {}, y: {})", point.x, point.y);
384 }
385
386 self.set_pixel_unchecked(point, Some(color));
387 }
388
389 /// Returns a copy of with the content mirrored by swapping x and y.
390 ///
391 /// # Examples
392 ///
393 /// ```
394 /// use embedded_graphics::{mock_display::MockDisplay, pixelcolor::BinaryColor};
395 ///
396 /// let display: MockDisplay<BinaryColor> = MockDisplay::from_pattern(&[
397 /// "#### ####",
398 /// "# # ",
399 /// "### # ##",
400 /// "# # #",
401 /// "#### ####",
402 /// ]);
403 ///
404 /// let mirrored = display.swap_xy();
405 /// mirrored.assert_pattern(&[
406 /// "#####",
407 /// "# # #",
408 /// "# # #",
409 /// "# #",
410 /// " ",
411 /// "#####",
412 /// "# #",
413 /// "# # #",
414 /// "# ###",
415 /// ]);
416 /// ```
417 pub fn swap_xy(&self) -> MockDisplay<C> {
418 let mut mirrored = MockDisplay::new();
419
420 for point in self.bounding_box().points() {
421 mirrored.set_pixel_unchecked(point, self.get_pixel(Point::new(point.y, point.x)));
422 }
423
424 mirrored
425 }
426
427 /// Maps a `MockDisplay<C>` to a `MockDisplay<CT>` by applying a function
428 /// to each pixel.
429 ///
430 /// # Examples
431 ///
432 /// Invert a `MockDisplay` by applying [`BinaryColor::invert`] to the color of each pixel.
433 ///
434 /// [`BinaryColor::invert`]: super::pixelcolor::BinaryColor::invert()
435 ///
436 /// ```
437 /// use embedded_graphics::{mock_display::MockDisplay, pixelcolor::BinaryColor};
438 ///
439 /// let display: MockDisplay<BinaryColor> = MockDisplay::from_pattern(&[
440 /// "####",
441 /// "# .",
442 /// "....",
443 /// ]);
444 ///
445 /// let inverted = display.map(|c| c.invert());
446 /// inverted.assert_pattern(&[
447 /// "....",
448 /// ". #",
449 /// "####",
450 /// ]);
451 /// ```
452 pub fn map<CT, F>(&self, f: F) -> MockDisplay<CT>
453 where
454 CT: PixelColor,
455 F: Fn(C) -> CT + Copy,
456 {
457 let mut target = MockDisplay::new();
458
459 for point in self.bounding_box().points() {
460 target.set_pixel_unchecked(point, self.get_pixel(point).map(f))
461 }
462
463 target
464 }
465
466 /// Compares the display to another display.
467 ///
468 /// The following color code is used to show the difference between the displays:
469 ///
470 /// | Color | Description |
471 /// |---------------------|---------------------------------------------------------------|
472 /// | None | The color of the pixel is equal in both displays. |
473 /// | Some(Rgb888::GREEN) | The pixel was only set in `self` |
474 /// | Some(Rgb888::RED) | The pixel was only set in `other` |
475 /// | Some(Rgb888::BLUE) | The pixel was set to a different colors in `self` and `other` |
476 pub fn diff(&self, other: &MockDisplay<C>) -> MockDisplay<Rgb888> {
477 let mut display = MockDisplay::new();
478
479 for point in display.bounding_box().points() {
480 let self_color = self.get_pixel(point);
481 let other_color = other.get_pixel(point);
482
483 let diff_color = match (self_color, other_color) {
484 (Some(_), None) => Some(Rgb888::GREEN),
485 (None, Some(_)) => Some(Rgb888::RED),
486 (Some(s), Some(o)) if s != o => Some(Rgb888::BLUE),
487 _ => None,
488 };
489
490 display.set_pixel_unchecked(point, diff_color);
491 }
492
493 display
494 }
495}
496
497impl<C: PixelColor> PartialEq for MockDisplay<C> {
498 fn eq(&self, other: &Self) -> bool {
499 self.pixels.iter().eq(other.pixels.iter())
500 }
501}
502
503impl<C> MockDisplay<C>
504where
505 C: PixelColor + ColorMapping,
506{
507 /// Creates a new mock display from a character pattern.
508 ///
509 /// The color pattern is specified by a slice of string slices. Each string
510 /// slice represents a row of pixels and every character a single pixel.
511 ///
512 /// A space character in the pattern represents a pixel which wasn't
513 /// modified by any drawing routine and is left in the default state.
514 /// All other characters are converted by implementations of the
515 /// [`ColorMapping`] trait.
516 ///
517 pub fn from_pattern(pattern: &[&str]) -> MockDisplay<C> {
518 // Check pattern dimensions.
519 let pattern_width = pattern.first().map_or(0, |row| row.len());
520 let pattern_height = pattern.len();
521 assert!(
522 pattern_width <= SIZE,
523 "Test pattern must not be wider than {} columns",
524 SIZE
525 );
526 assert!(
527 pattern_height <= SIZE,
528 "Test pattern must not be taller than {} rows",
529 SIZE
530 );
531 for (row_idx, row) in pattern.iter().enumerate() {
532 assert_eq!(
533 row.len(),
534 pattern_width,
535 "Row #{} is {} characters wide (must be {} characters to match previous rows)",
536 row_idx + 1,
537 row.len(),
538 pattern_width
539 );
540 }
541
542 // Convert pattern to colors and pad pattern with None.
543 let pattern_colors = pattern
544 .iter()
545 .flat_map(|row| {
546 row.chars()
547 .map(|c| match c {
548 ' ' => None,
549 _ => Some(C::char_to_color(c)),
550 })
551 .chain(iter::repeat(None))
552 .take(SIZE)
553 })
554 .chain(iter::repeat(None))
555 .take(SIZE * SIZE);
556
557 // Copy pattern to display.
558 let mut display = MockDisplay::new();
559 for (i, color) in pattern_colors.enumerate() {
560 display.pixels[i] = color;
561 }
562
563 display
564 }
565
566 /// Checks if the displays are equal.
567 ///
568 /// An advanced output for failing tests can be enabled by setting the environment variable
569 /// `EG_FANCY_PANIC=1`. See the [module-level documentation] for more details.
570 ///
571 /// # Panics
572 ///
573 /// Panics if the displays aren't equal.
574 ///
575 /// [module-level documentation]: self#assertions
576 #[track_caller]
577 pub fn assert_eq(&self, other: &MockDisplay<C>) {
578 if !self.eq(other) {
579 if option_env!("EG_FANCY_PANIC") == Some("1") {
580 let fancy_panic = FancyPanic::new(self, other, 30);
581 panic!("\n{}", fancy_panic);
582 } else {
583 panic!("\ndisplay\n{:?}\nexpected\n{:?}", self, other);
584 }
585 }
586 }
587
588 /// Checks if the displays are equal.
589 ///
590 /// An advanced output for failing tests can be enabled by setting the environment variable
591 /// `EG_FANCY_PANIC=1`. See the [module-level documentation] for more details.
592 ///
593 /// The output of the `msg` function will be prepended to the output if the assertion fails.
594 ///
595 /// # Panics
596 ///
597 /// Panics if the displays aren't equal.
598 ///
599 /// [module-level documentation]: self#assertions
600 #[track_caller]
601 pub fn assert_eq_with_message<F>(&self, other: &MockDisplay<C>, msg: F)
602 where
603 F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
604 {
605 if !self.eq(other) {
606 if option_env!("EG_FANCY_PANIC") == Some("1") {
607 let fancy_panic = FancyPanic::new(self, other, 30);
608 panic!("\n{}\n\n{}", MessageWrapper(msg), fancy_panic);
609 } else {
610 panic!(
611 "\n{}\n\ndisplay:\n{:?}\nexpected:\n{:?}",
612 MessageWrapper(msg),
613 self,
614 other
615 );
616 }
617 }
618 }
619
620 /// Checks if the display is equal to the given pattern.
621 ///
622 /// An advanced output for failing tests can be enabled, see the [module-level documentation]
623 /// for more details.
624 ///
625 /// # Panics
626 ///
627 /// Panics if the display content isn't equal to the pattern.
628 ///
629 /// [module-level documentation]: self#assertions
630 #[track_caller]
631 pub fn assert_pattern(&self, pattern: &[&str]) {
632 let other = MockDisplay::<C>::from_pattern(pattern);
633
634 self.assert_eq(&other);
635 }
636
637 /// Checks if the display is equal to the given pattern.
638 ///
639 /// An advanced output for failing tests can be enabled, see the [module-level documentation]
640 /// for more details.
641 ///
642 /// The output of the `msg` function will be prepended to the output if the assertion fails.
643 ///
644 /// # Panics
645 ///
646 /// Panics if the display content isn't equal to the pattern.
647 ///
648 /// [module-level documentation]: self#assertions
649 #[track_caller]
650 pub fn assert_pattern_with_message<F>(&self, pattern: &[&str], msg: F)
651 where
652 F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
653 {
654 let other = MockDisplay::<C>::from_pattern(pattern);
655
656 self.assert_eq_with_message(&other, msg);
657 }
658}
659
660impl<C> Default for MockDisplay<C>
661where
662 C: PixelColor,
663{
664 fn default() -> Self {
665 Self {
666 pixels: [None; SIZE * SIZE],
667 allow_overdraw: false,
668 allow_out_of_bounds_drawing: false,
669 }
670 }
671}
672
673impl<C> fmt::Debug for MockDisplay<C>
674where
675 C: PixelColor + ColorMapping,
676{
677 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
678 let empty_rows = self
679 .pixels
680 .rchunks(SIZE)
681 .take_while(|row| row.iter().all(Option::is_none))
682 .count();
683
684 writeln!(f, "MockDisplay[")?;
685 for row in self.pixels.chunks(SIZE).take(SIZE - empty_rows) {
686 for color in row {
687 f.write_char(color.map_or(' ', C::color_to_char))?;
688 }
689 writeln!(f)?;
690 }
691 if empty_rows > 0 {
692 writeln!(f, "({} empty rows skipped)", empty_rows)?;
693 }
694 writeln!(f, "]")?;
695
696 Ok(())
697 }
698}
699
700impl<C> DrawTarget for MockDisplay<C>
701where
702 C: PixelColor,
703{
704 type Color = C;
705 type Error = core::convert::Infallible;
706
707 fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
708 where
709 I: IntoIterator<Item = Pixel<Self::Color>>,
710 {
711 for pixel in pixels.into_iter() {
712 let Pixel(point, color) = pixel;
713
714 self.draw_pixel(point, color);
715 }
716
717 Ok(())
718 }
719}
720
721impl<C> OriginDimensions for MockDisplay<C>
722where
723 C: PixelColor,
724{
725 fn size(&self) -> Size {
726 DISPLAY_AREA.size
727 }
728}
729
730/// Wrapper to implement `Display` for formatting function.
731struct MessageWrapper<F>(F);
732
733impl<F> fmt::Display for MessageWrapper<F>
734where
735 F: Fn(&mut fmt::Formatter<'_>) -> fmt::Result,
736{
737 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
738 self.0(f)
739 }
740}
741
742#[cfg(test)]
743mod tests {
744 use super::*;
745 use crate::{
746 pixelcolor::{BinaryColor, Rgb565},
747 Drawable,
748 };
749
750 #[test]
751 #[should_panic(expected = "tried to draw pixel outside the display area (x: 65, y: 0)")]
752 fn panic_on_out_of_bounds_drawing() {
753 let mut display = MockDisplay::new();
754
755 Pixel(Point::new(65, 0), BinaryColor::On)
756 .draw(&mut display)
757 .unwrap();
758 }
759
760 #[test]
761 fn allow_out_of_bounds_drawing() {
762 let mut display = MockDisplay::new();
763 display.set_allow_out_of_bounds_drawing(true);
764
765 Pixel(Point::new(65, 0), BinaryColor::On)
766 .draw(&mut display)
767 .unwrap();
768 }
769
770 #[test]
771 #[should_panic(expected = "tried to draw pixel twice (x: 1, y: 2)")]
772 fn panic_on_overdraw() {
773 let mut display = MockDisplay::new();
774
775 let p = Pixel(Point::new(1, 2), BinaryColor::On);
776 p.draw(&mut display).unwrap();
777 p.draw(&mut display).unwrap();
778 }
779
780 #[test]
781 fn allow_overdraw() {
782 let mut display = MockDisplay::new();
783 display.set_allow_overdraw(true);
784
785 let p = Pixel(Point::new(1, 2), BinaryColor::On);
786 p.draw(&mut display).unwrap();
787 p.draw(&mut display).unwrap();
788 }
789
790 #[test]
791 fn zero_sized_affected_area() {
792 let disp: MockDisplay<BinaryColor> = MockDisplay::new();
793 assert!(disp.affected_area().is_zero_sized(),);
794 }
795
796 #[test]
797 fn diff() {
798 let display1 = MockDisplay::<Rgb565>::from_pattern(&[" R RR"]);
799 let display2 = MockDisplay::<Rgb565>::from_pattern(&[" RR B"]);
800 let expected = MockDisplay::<Rgb888>::from_pattern(&[" RGB"]);
801
802 display1.diff(&display2).assert_eq(&expected);
803 }
804}