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 | |
184 | mod color_mapping; |
185 | mod fancy_panic; |
186 | |
187 | use crate::{ |
188 | draw_target::DrawTarget, |
189 | geometry::{Dimensions, OriginDimensions, Point, Size}, |
190 | pixelcolor::{PixelColor, Rgb888, RgbColor}, |
191 | primitives::{PointsIter, Rectangle}, |
192 | Pixel, |
193 | }; |
194 | pub use color_mapping::ColorMapping; |
195 | use core::{ |
196 | fmt::{self, Write}, |
197 | iter, |
198 | }; |
199 | use fancy_panic::FancyPanic; |
200 | |
201 | const SIZE: usize = 64; |
202 | const DISPLAY_AREA: Rectangle = Rectangle::new(top_left: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)] |
208 | pub struct MockDisplay<C> |
209 | where |
210 | C: PixelColor, |
211 | { |
212 | pixels: [Option<C>; SIZE * SIZE], |
213 | allow_overdraw: bool, |
214 | allow_out_of_bounds_drawing: bool, |
215 | } |
216 | |
217 | impl<C> MockDisplay<C> |
218 | where |
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 | |
497 | impl<C: PixelColor> PartialEq for MockDisplay<C> { |
498 | fn eq(&self, other: &Self) -> bool { |
499 | self.pixels.iter().eq(other.pixels.iter()) |
500 | } |
501 | } |
502 | |
503 | impl<C> MockDisplay<C> |
504 | where |
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 | |
660 | impl<C> Default for MockDisplay<C> |
661 | where |
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 | |
673 | impl<C> fmt::Debug for MockDisplay<C> |
674 | where |
675 | C: PixelColor + ColorMapping, |
676 | { |
677 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
678 | let empty_rows: usize = self |
679 | .pixels |
680 | .rchunks(SIZE) |
681 | .take_while(|row: &&[Option]| row.iter().all(Option::is_none)) |
682 | .count(); |
683 | |
684 | writeln!(f, "MockDisplay[" )?; |
685 | for row: &[Option] in self.pixels.chunks(SIZE).take(SIZE - empty_rows) { |
686 | for color: &Option in row { |
687 | f.write_char(color.map_or(default:' ' , 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 | |
700 | impl<C> DrawTarget for MockDisplay<C> |
701 | where |
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: Pixel in pixels.into_iter() { |
712 | let Pixel(point: Point, color: C) = pixel; |
713 | |
714 | self.draw_pixel(point, color); |
715 | } |
716 | |
717 | Ok(()) |
718 | } |
719 | } |
720 | |
721 | impl<C> OriginDimensions for MockDisplay<C> |
722 | where |
723 | C: PixelColor, |
724 | { |
725 | fn size(&self) -> Size { |
726 | DISPLAY_AREA.size |
727 | } |
728 | } |
729 | |
730 | /// Wrapper to implement `Display` for formatting function. |
731 | struct MessageWrapper<F>(F); |
732 | |
733 | impl<F> fmt::Display for MessageWrapper<F> |
734 | where |
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)] |
743 | mod 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 | } |
805 | |