1 | use crate::{ |
2 | draw_target::{DrawTarget, DrawTargetExt, Translated}, |
3 | geometry::{OriginDimensions, Size}, |
4 | primitives::Rectangle, |
5 | Pixel, |
6 | }; |
7 | |
8 | /// Cropped draw target. |
9 | /// |
10 | /// Created by calling [`cropped`] on any [`DrawTarget`]. |
11 | /// See the [`cropped`] method documentation for more. |
12 | /// |
13 | /// [`cropped`]: DrawTargetExt::cropped |
14 | #[derive (Debug)] |
15 | pub struct Cropped<'a, T> |
16 | where |
17 | T: DrawTarget, |
18 | { |
19 | parent: Translated<'a, T>, |
20 | size: Size, |
21 | } |
22 | |
23 | impl<'a, T> Cropped<'a, T> |
24 | where |
25 | T: DrawTarget, |
26 | { |
27 | pub(super) fn new(parent: &'a mut T, area: &Rectangle) -> Self { |
28 | let area: Rectangle = area.intersection(&parent.bounding_box()); |
29 | |
30 | Self { |
31 | parent: parent.translated(offset:area.top_left), |
32 | size: area.size, |
33 | } |
34 | } |
35 | } |
36 | |
37 | impl<T> DrawTarget for Cropped<'_, T> |
38 | where |
39 | T: DrawTarget, |
40 | { |
41 | type Color = T::Color; |
42 | type Error = T::Error; |
43 | |
44 | fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error> |
45 | where |
46 | I: IntoIterator<Item = Pixel<Self::Color>>, |
47 | { |
48 | self.parent.draw_iter(pixels) |
49 | } |
50 | |
51 | fn fill_contiguous<I>(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error> |
52 | where |
53 | I: IntoIterator<Item = Self::Color>, |
54 | { |
55 | self.parent.fill_contiguous(area, colors) |
56 | } |
57 | |
58 | fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> { |
59 | self.parent.fill_solid(area, color) |
60 | } |
61 | } |
62 | |
63 | impl<T> OriginDimensions for Cropped<'_, T> |
64 | where |
65 | T: DrawTarget, |
66 | { |
67 | fn size(&self) -> Size { |
68 | self.size |
69 | } |
70 | } |
71 | |
72 | #[cfg (test)] |
73 | mod tests { |
74 | use crate::{ |
75 | draw_target::{DrawTarget, DrawTargetExt}, |
76 | geometry::Dimensions, |
77 | geometry::{Point, Size}, |
78 | mock_display::MockDisplay, |
79 | pixelcolor::BinaryColor, |
80 | primitives::{Primitive, PrimitiveStyle, Rectangle}, |
81 | Drawable, Pixel, |
82 | }; |
83 | |
84 | #[test ] |
85 | fn draw_iter() { |
86 | let mut display = MockDisplay::new(); |
87 | |
88 | let area = Rectangle::new(Point::new(2, 3), Size::new(10, 10)); |
89 | let mut cropped = display.cropped(&area); |
90 | |
91 | let pixels = [ |
92 | Pixel(Point::new(0, 0), BinaryColor::On), |
93 | Pixel(Point::new(1, 2), BinaryColor::Off), |
94 | ]; |
95 | cropped.draw_iter(pixels.iter().copied()).unwrap(); |
96 | |
97 | display.assert_pattern(&[ |
98 | " " , // |
99 | " " , // |
100 | " " , // |
101 | " # " , // |
102 | " " , // |
103 | " ." , // |
104 | ]); |
105 | } |
106 | |
107 | #[test ] |
108 | fn fill_contiguous() { |
109 | let mut display = MockDisplay::new(); |
110 | |
111 | let area = Rectangle::new(Point::new(3, 2), Size::new(10, 10)); |
112 | let mut cropped = display.cropped(&area); |
113 | |
114 | let colors = [ |
115 | 1, 1, 1, 1, 1, // |
116 | 0, 0, 0, 0, 1, // |
117 | 0, 1, 0, 1, 1, // |
118 | 1, 0, 1, 0, 1, // |
119 | ]; |
120 | let area = Rectangle::new(Point::new(1, 2), Size::new(5, 4)); |
121 | cropped |
122 | .fill_contiguous(&area, colors.iter().map(|c| BinaryColor::from(*c != 0))) |
123 | .unwrap(); |
124 | |
125 | display.assert_pattern(&[ |
126 | " " , // |
127 | " " , // |
128 | " " , // |
129 | " " , // |
130 | " #####" , // |
131 | " ....#" , // |
132 | " .#.##" , // |
133 | " #.#.#" , // |
134 | ]); |
135 | } |
136 | |
137 | #[test ] |
138 | fn fill_solid() { |
139 | let mut display = MockDisplay::new(); |
140 | |
141 | let area = Rectangle::new(Point::new(1, 3), Size::new(10, 10)); |
142 | let mut cropped = display.cropped(&area); |
143 | |
144 | let area = Rectangle::new(Point::new(2, 1), Size::new(3, 4)); |
145 | cropped.fill_solid(&area, BinaryColor::On).unwrap(); |
146 | |
147 | display.assert_pattern(&[ |
148 | " " , // |
149 | " " , // |
150 | " " , // |
151 | " " , // |
152 | " ###" , // |
153 | " ###" , // |
154 | " ###" , // |
155 | " ###" , // |
156 | ]); |
157 | } |
158 | |
159 | #[test ] |
160 | fn clear() { |
161 | let mut display = MockDisplay::new(); |
162 | |
163 | let area = Rectangle::new(Point::new(1, 3), Size::new(3, 4)); |
164 | let mut cropped = display.cropped(&area); |
165 | cropped.clear(BinaryColor::On).unwrap(); |
166 | |
167 | let mut expected = MockDisplay::new(); |
168 | area.into_styled(PrimitiveStyle::with_fill(BinaryColor::On)) |
169 | .draw(&mut expected) |
170 | .unwrap(); |
171 | |
172 | display.assert_eq(&expected); |
173 | } |
174 | |
175 | #[test ] |
176 | fn bounding_box() { |
177 | let mut display: MockDisplay<BinaryColor> = MockDisplay::new(); |
178 | |
179 | let size = Size::new(3, 4); |
180 | let area = Rectangle::new(Point::new(1, 3), size); |
181 | let cropped = display.cropped(&area); |
182 | |
183 | assert_eq!(cropped.bounding_box(), Rectangle::new(Point::zero(), size)); |
184 | } |
185 | |
186 | #[test ] |
187 | fn bounding_box_is_clipped() { |
188 | let mut display: MockDisplay<BinaryColor> = MockDisplay::new(); |
189 | let display_bb = display.bounding_box(); |
190 | |
191 | let top_left = Point::new(10, 20); |
192 | let size = Size::new(1000, 1000); |
193 | let area = Rectangle::new(top_left, size); |
194 | let cropped = display.cropped(&area); |
195 | |
196 | let expected_size = display_bb.size - Size::new(top_left.x as u32, top_left.y as u32); |
197 | |
198 | assert_eq!( |
199 | cropped.bounding_box(), |
200 | Rectangle::new(Point::zero(), expected_size), |
201 | ); |
202 | } |
203 | } |
204 | |