1 | use super::{Drawable, PointCollection}; |
2 | use crate::style::{Color, ShapeStyle, SizeDesc}; |
3 | use plotters_backend::{BackendCoord, DrawingBackend, DrawingErrorKind}; |
4 | |
5 | #[inline ] |
6 | fn to_i((x: f32, y: f32): (f32, f32)) -> (i32, i32) { |
7 | (x.round() as i32, y.round() as i32) |
8 | } |
9 | |
10 | #[inline ] |
11 | fn to_f((x: i32, y: i32): (i32, i32)) -> (f32, f32) { |
12 | (x as f32, y as f32) |
13 | } |
14 | |
15 | /** |
16 | An element representing a single pixel. |
17 | |
18 | See [`crate::element::EmptyElement`] for more information and examples. |
19 | */ |
20 | pub struct Pixel<Coord> { |
21 | pos: Coord, |
22 | style: ShapeStyle, |
23 | } |
24 | |
25 | impl<Coord> Pixel<Coord> { |
26 | /** |
27 | Creates a new pixel. |
28 | |
29 | See [`crate::element::EmptyElement`] for more information and examples. |
30 | */ |
31 | pub fn new<P: Into<Coord>, S: Into<ShapeStyle>>(pos: P, style: S) -> Self { |
32 | Self { |
33 | pos: pos.into(), |
34 | style: style.into(), |
35 | } |
36 | } |
37 | } |
38 | |
39 | impl<'a, Coord> PointCollection<'a, Coord> for &'a Pixel<Coord> { |
40 | type Point = &'a Coord; |
41 | type IntoIter = std::iter::Once<&'a Coord>; |
42 | fn point_iter(self) -> Self::IntoIter { |
43 | std::iter::once(&self.pos) |
44 | } |
45 | } |
46 | |
47 | impl<Coord, DB: DrawingBackend> Drawable<DB> for Pixel<Coord> { |
48 | fn draw<I: Iterator<Item = BackendCoord>>( |
49 | &self, |
50 | mut points: I, |
51 | backend: &mut DB, |
52 | _: (u32, u32), |
53 | ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { |
54 | if let Some((x: i32, y: i32)) = points.next() { |
55 | return backend.draw_pixel((x, y), self.style.color.to_backend_color()); |
56 | } |
57 | Ok(()) |
58 | } |
59 | } |
60 | |
61 | #[cfg (test)] |
62 | #[test ] |
63 | fn test_pixel_element() { |
64 | use crate::prelude::*; |
65 | let da = crate::create_mocked_drawing_area(300, 300, |m| { |
66 | m.check_draw_pixel(|c, (x, y)| { |
67 | assert_eq!(x, 150); |
68 | assert_eq!(y, 152); |
69 | assert_eq!(c, RED.to_rgba()); |
70 | }); |
71 | |
72 | m.drop_check(|b| { |
73 | assert_eq!(b.num_draw_pixel_call, 1); |
74 | assert_eq!(b.draw_count, 1); |
75 | }); |
76 | }); |
77 | da.draw(&Pixel::new((150, 152), RED)) |
78 | .expect("Drawing Failure" ); |
79 | } |
80 | |
81 | /// This is a deprecated type. Please use new name [`PathElement`] instead. |
82 | #[deprecated (note = "Use new name PathElement instead" )] |
83 | pub type Path<Coord> = PathElement<Coord>; |
84 | |
85 | /// An element of a series of connected lines |
86 | pub struct PathElement<Coord> { |
87 | points: Vec<Coord>, |
88 | style: ShapeStyle, |
89 | } |
90 | impl<Coord> PathElement<Coord> { |
91 | /// Create a new path |
92 | /// - `points`: The iterator of the points |
93 | /// - `style`: The shape style |
94 | /// - returns the created element |
95 | pub fn new<P: Into<Vec<Coord>>, S: Into<ShapeStyle>>(points: P, style: S) -> Self { |
96 | Self { |
97 | points: points.into(), |
98 | style: style.into(), |
99 | } |
100 | } |
101 | } |
102 | |
103 | impl<'a, Coord> PointCollection<'a, Coord> for &'a PathElement<Coord> { |
104 | type Point = &'a Coord; |
105 | type IntoIter = &'a [Coord]; |
106 | fn point_iter(self) -> &'a [Coord] { |
107 | &self.points |
108 | } |
109 | } |
110 | |
111 | impl<Coord, DB: DrawingBackend> Drawable<DB> for PathElement<Coord> { |
112 | fn draw<I: Iterator<Item = BackendCoord>>( |
113 | &self, |
114 | points: I, |
115 | backend: &mut DB, |
116 | _: (u32, u32), |
117 | ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { |
118 | backend.draw_path(path:points, &self.style) |
119 | } |
120 | } |
121 | |
122 | #[cfg (test)] |
123 | #[test ] |
124 | fn test_path_element() { |
125 | use crate::prelude::*; |
126 | let da = crate::create_mocked_drawing_area(300, 300, |m| { |
127 | m.check_draw_path(|c, s, path| { |
128 | assert_eq!(c, BLUE.to_rgba()); |
129 | assert_eq!(s, 5); |
130 | assert_eq!(path, vec![(100, 101), (105, 107), (150, 157)]); |
131 | }); |
132 | m.drop_check(|b| { |
133 | assert_eq!(b.num_draw_path_call, 1); |
134 | assert_eq!(b.draw_count, 1); |
135 | }); |
136 | }); |
137 | da.draw(&PathElement::new( |
138 | vec![(100, 101), (105, 107), (150, 157)], |
139 | Into::<ShapeStyle>::into(BLUE).stroke_width(5), |
140 | )) |
141 | .expect("Drawing Failure" ); |
142 | } |
143 | |
144 | /// An element of a series of connected lines in dash style. |
145 | /// |
146 | /// It's similar to [`PathElement`] but has a dash style. |
147 | pub struct DashedPathElement<I: Iterator + Clone, Size: SizeDesc> { |
148 | points: I, |
149 | size: Size, |
150 | spacing: Size, |
151 | style: ShapeStyle, |
152 | } |
153 | |
154 | impl<I: Iterator + Clone, Size: SizeDesc> DashedPathElement<I, Size> { |
155 | /// Create a new path |
156 | /// - `points`: The iterator of the points |
157 | /// - `size`: The dash size |
158 | /// - `spacing`: The dash-to-dash spacing (gap size) |
159 | /// - `style`: The shape style |
160 | /// - returns the created element |
161 | pub fn new<I0, S>(points: I0, size: Size, spacing: Size, style: S) -> Self |
162 | where |
163 | I0: IntoIterator<IntoIter = I>, |
164 | S: Into<ShapeStyle>, |
165 | { |
166 | Self { |
167 | points: points.into_iter(), |
168 | size, |
169 | spacing, |
170 | style: style.into(), |
171 | } |
172 | } |
173 | } |
174 | |
175 | impl<'a, I: Iterator + Clone, Size: SizeDesc> PointCollection<'a, I::Item> |
176 | for &'a DashedPathElement<I, Size> |
177 | { |
178 | type Point = I::Item; |
179 | type IntoIter = I; |
180 | fn point_iter(self) -> Self::IntoIter { |
181 | self.points.clone() |
182 | } |
183 | } |
184 | |
185 | impl<I0: Iterator + Clone, Size: SizeDesc, DB: DrawingBackend> Drawable<DB> |
186 | for DashedPathElement<I0, Size> |
187 | { |
188 | fn draw<I: Iterator<Item = BackendCoord>>( |
189 | &self, |
190 | mut points: I, |
191 | backend: &mut DB, |
192 | ps: (u32, u32), |
193 | ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { |
194 | let mut start = match points.next() { |
195 | Some(c) => to_f(c), |
196 | None => return Ok(()), |
197 | }; |
198 | let size = self.size.in_pixels(&ps).max(0) as f32; |
199 | if size == 0. { |
200 | return Ok(()); |
201 | } |
202 | let spacing = self.spacing.in_pixels(&ps).max(0) as f32; |
203 | let mut dist = 0.; |
204 | let mut is_solid = true; |
205 | let mut queue = vec![to_i(start)]; |
206 | for curr in points { |
207 | let end = to_f(curr); |
208 | // Loop for solid and spacing |
209 | while start != end { |
210 | let (dx, dy) = (end.0 - start.0, end.1 - start.1); |
211 | let d = dx.hypot(dy); |
212 | let size = if is_solid { size } else { spacing }; |
213 | let left = size - dist; |
214 | // Set next point to `start` |
215 | if left < d { |
216 | let t = left / d; |
217 | start = (start.0 + dx * t, start.1 + dy * t); |
218 | dist += left; |
219 | } else { |
220 | start = end; |
221 | dist += d; |
222 | } |
223 | // Draw if needed |
224 | if is_solid { |
225 | queue.push(to_i(start)); |
226 | } |
227 | if size <= dist { |
228 | if is_solid { |
229 | backend.draw_path(queue.drain(..), &self.style)?; |
230 | } else { |
231 | queue.push(to_i(start)); |
232 | } |
233 | dist = 0.; |
234 | is_solid = !is_solid; |
235 | } |
236 | } |
237 | } |
238 | if queue.len() > 1 { |
239 | backend.draw_path(queue, &self.style)?; |
240 | } |
241 | Ok(()) |
242 | } |
243 | } |
244 | |
245 | #[cfg (test)] |
246 | #[test ] |
247 | fn test_dashed_path_element() { |
248 | use crate::prelude::*; |
249 | let check_list = std::cell::RefCell::new(vec![ |
250 | vec![(100, 100), (100, 103), (100, 105)], |
251 | vec![(100, 107), (100, 112)], |
252 | vec![(100, 114), (100, 119)], |
253 | vec![(100, 119), (100, 120)], |
254 | ]); |
255 | let da = crate::create_mocked_drawing_area(300, 300, |m| { |
256 | m.check_draw_path(move |c, s, path| { |
257 | assert_eq!(c, BLUE.to_rgba()); |
258 | assert_eq!(s, 7); |
259 | assert_eq!(path, check_list.borrow_mut().remove(0)); |
260 | }); |
261 | m.drop_check(|b| { |
262 | assert_eq!(b.num_draw_path_call, 3); |
263 | assert_eq!(b.draw_count, 3); |
264 | }); |
265 | }); |
266 | da.draw(&DashedPathElement::new( |
267 | vec![(100, 100), (100, 103), (100, 120)], |
268 | 5., |
269 | 2., |
270 | BLUE.stroke_width(7), |
271 | )) |
272 | .expect("Drawing Failure" ); |
273 | } |
274 | |
275 | /// An element of a series of connected lines in dot style for any markers. |
276 | /// |
277 | /// It's similar to [`PathElement`] but use a marker function to draw markers with spacing. |
278 | pub struct DottedPathElement<I: Iterator + Clone, Size: SizeDesc, Marker> { |
279 | points: I, |
280 | shift: Size, |
281 | spacing: Size, |
282 | func: Box<dyn Fn(BackendCoord) -> Marker>, |
283 | } |
284 | |
285 | impl<I: Iterator + Clone, Size: SizeDesc, Marker> DottedPathElement<I, Size, Marker> { |
286 | /// Create a new path |
287 | /// - `points`: The iterator of the points |
288 | /// - `shift`: The shift of the first marker |
289 | /// - `spacing`: The spacing between markers |
290 | /// - `func`: The marker function |
291 | /// - returns the created element |
292 | pub fn new<I0, F>(points: I0, shift: Size, spacing: Size, func: F) -> Self |
293 | where |
294 | I0: IntoIterator<IntoIter = I>, |
295 | F: Fn(BackendCoord) -> Marker + 'static, |
296 | { |
297 | Self { |
298 | points: points.into_iter(), |
299 | shift, |
300 | spacing, |
301 | func: Box::new(func), |
302 | } |
303 | } |
304 | } |
305 | |
306 | impl<'a, I: Iterator + Clone, Size: SizeDesc, Marker> PointCollection<'a, I::Item> |
307 | for &'a DottedPathElement<I, Size, Marker> |
308 | { |
309 | type Point = I::Item; |
310 | type IntoIter = I; |
311 | fn point_iter(self) -> Self::IntoIter { |
312 | self.points.clone() |
313 | } |
314 | } |
315 | |
316 | impl<I0, Size, DB, Marker> Drawable<DB> for DottedPathElement<I0, Size, Marker> |
317 | where |
318 | I0: Iterator + Clone, |
319 | Size: SizeDesc, |
320 | DB: DrawingBackend, |
321 | Marker: crate::element::IntoDynElement<'static, DB, BackendCoord>, |
322 | { |
323 | fn draw<I: Iterator<Item = BackendCoord>>( |
324 | &self, |
325 | mut points: I, |
326 | backend: &mut DB, |
327 | ps: (u32, u32), |
328 | ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { |
329 | let mut shift = self.shift.in_pixels(&ps).max(0) as f32; |
330 | let mut start = match points.next() { |
331 | Some(start_i) => { |
332 | // Draw the first marker if no shift |
333 | if shift == 0. { |
334 | let mk = (self.func)(start_i).into_dyn(); |
335 | mk.draw(mk.point_iter().iter().copied(), backend, ps)?; |
336 | } |
337 | to_f(start_i) |
338 | } |
339 | None => return Ok(()), |
340 | }; |
341 | let spacing = self.spacing.in_pixels(&ps).max(0) as f32; |
342 | let mut dist = 0.; |
343 | for curr in points { |
344 | let end = to_f(curr); |
345 | // Loop for spacing |
346 | while start != end { |
347 | let (dx, dy) = (end.0 - start.0, end.1 - start.1); |
348 | let d = dx.hypot(dy); |
349 | let spacing = if shift == 0. { spacing } else { shift }; |
350 | let left = spacing - dist; |
351 | // Set next point to `start` |
352 | if left < d { |
353 | let t = left / d; |
354 | start = (start.0 + dx * t, start.1 + dy * t); |
355 | dist += left; |
356 | } else { |
357 | start = end; |
358 | dist += d; |
359 | } |
360 | // Draw if needed |
361 | if spacing <= dist { |
362 | let mk = (self.func)(to_i(start)).into_dyn(); |
363 | mk.draw(mk.point_iter().iter().copied(), backend, ps)?; |
364 | shift = 0.; |
365 | dist = 0.; |
366 | } |
367 | } |
368 | } |
369 | Ok(()) |
370 | } |
371 | } |
372 | |
373 | #[cfg (test)] |
374 | #[test ] |
375 | fn test_dotted_path_element() { |
376 | use crate::prelude::*; |
377 | let da = crate::create_mocked_drawing_area(300, 300, |m| { |
378 | m.drop_check(|b| { |
379 | assert_eq!(b.num_draw_path_call, 0); |
380 | assert_eq!(b.draw_count, 7); |
381 | }); |
382 | }); |
383 | da.draw(&DottedPathElement::new( |
384 | vec![(100, 100), (105, 105), (150, 150)], |
385 | 5, |
386 | 10, |
387 | |c| Circle::new(c, 5, Into::<ShapeStyle>::into(RED).filled()), |
388 | )) |
389 | .expect("Drawing Failure" ); |
390 | } |
391 | |
392 | /// A rectangle element |
393 | pub struct Rectangle<Coord> { |
394 | points: [Coord; 2], |
395 | style: ShapeStyle, |
396 | margin: (u32, u32, u32, u32), |
397 | } |
398 | |
399 | impl<Coord> Rectangle<Coord> { |
400 | /// Create a new path |
401 | /// - `points`: The left upper and right lower corner of the rectangle |
402 | /// - `style`: The shape style |
403 | /// - returns the created element |
404 | pub fn new<S: Into<ShapeStyle>>(points: [Coord; 2], style: S) -> Self { |
405 | Self { |
406 | points, |
407 | style: style.into(), |
408 | margin: (0, 0, 0, 0), |
409 | } |
410 | } |
411 | |
412 | /// Set the margin of the rectangle |
413 | /// - `t`: The top margin |
414 | /// - `b`: The bottom margin |
415 | /// - `l`: The left margin |
416 | /// - `r`: The right margin |
417 | pub fn set_margin(&mut self, t: u32, b: u32, l: u32, r: u32) -> &mut Self { |
418 | self.margin = (t, b, l, r); |
419 | self |
420 | } |
421 | |
422 | /// Get the points of the rectangle |
423 | /// - returns the element points |
424 | pub fn get_points(&self) -> (&Coord, &Coord) { |
425 | (&self.points[0], &self.points[1]) |
426 | } |
427 | |
428 | /// Set the style of the rectangle |
429 | /// - `style`: The shape style |
430 | /// - returns a mut reference to the rectangle |
431 | pub fn set_style<S: Into<ShapeStyle>>(&mut self, style: S) -> &mut Self { |
432 | self.style = style.into(); |
433 | self |
434 | } |
435 | } |
436 | |
437 | impl<'a, Coord> PointCollection<'a, Coord> for &'a Rectangle<Coord> { |
438 | type Point = &'a Coord; |
439 | type IntoIter = &'a [Coord]; |
440 | fn point_iter(self) -> &'a [Coord] { |
441 | &self.points |
442 | } |
443 | } |
444 | |
445 | impl<Coord, DB: DrawingBackend> Drawable<DB> for Rectangle<Coord> { |
446 | fn draw<I: Iterator<Item = BackendCoord>>( |
447 | &self, |
448 | mut points: I, |
449 | backend: &mut DB, |
450 | _: (u32, u32), |
451 | ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { |
452 | match (points.next(), points.next()) { |
453 | (Some(a: (i32, i32)), Some(b: (i32, i32))) => { |
454 | let (mut a: (i32, i32), mut b: (i32, i32)) = ((a.0.min(b.0), a.1.min(b.1)), (a.0.max(b.0), a.1.max(b.1))); |
455 | a.1 += self.margin.0 as i32; |
456 | b.1 -= self.margin.1 as i32; |
457 | a.0 += self.margin.2 as i32; |
458 | b.0 -= self.margin.3 as i32; |
459 | backend.draw_rect(upper_left:a, bottom_right:b, &self.style, self.style.filled) |
460 | } |
461 | _ => Ok(()), |
462 | } |
463 | } |
464 | } |
465 | |
466 | #[cfg (test)] |
467 | #[test ] |
468 | fn test_rect_element() { |
469 | use crate::prelude::*; |
470 | { |
471 | let da = crate::create_mocked_drawing_area(300, 300, |m| { |
472 | m.check_draw_rect(|c, s, f, u, d| { |
473 | assert_eq!(c, BLUE.to_rgba()); |
474 | assert!(!f); |
475 | assert_eq!(s, 5); |
476 | assert_eq!([u, d], [(100, 101), (105, 107)]); |
477 | }); |
478 | m.drop_check(|b| { |
479 | assert_eq!(b.num_draw_rect_call, 1); |
480 | assert_eq!(b.draw_count, 1); |
481 | }); |
482 | }); |
483 | da.draw(&Rectangle::new( |
484 | [(100, 101), (105, 107)], |
485 | Color::stroke_width(&BLUE, 5), |
486 | )) |
487 | .expect("Drawing Failure" ); |
488 | } |
489 | |
490 | { |
491 | let da = crate::create_mocked_drawing_area(300, 300, |m| { |
492 | m.check_draw_rect(|c, _, f, u, d| { |
493 | assert_eq!(c, BLUE.to_rgba()); |
494 | assert!(f); |
495 | assert_eq!([u, d], [(100, 101), (105, 107)]); |
496 | }); |
497 | m.drop_check(|b| { |
498 | assert_eq!(b.num_draw_rect_call, 1); |
499 | assert_eq!(b.draw_count, 1); |
500 | }); |
501 | }); |
502 | da.draw(&Rectangle::new([(100, 101), (105, 107)], BLUE.filled())) |
503 | .expect("Drawing Failure" ); |
504 | } |
505 | } |
506 | |
507 | /// A circle element |
508 | pub struct Circle<Coord, Size: SizeDesc> { |
509 | center: Coord, |
510 | size: Size, |
511 | style: ShapeStyle, |
512 | } |
513 | |
514 | impl<Coord, Size: SizeDesc> Circle<Coord, Size> { |
515 | /// Create a new circle element |
516 | /// - `coord` The center of the circle |
517 | /// - `size` The radius of the circle |
518 | /// - `style` The style of the circle |
519 | /// - Return: The newly created circle element |
520 | pub fn new<S: Into<ShapeStyle>>(coord: Coord, size: Size, style: S) -> Self { |
521 | Self { |
522 | center: coord, |
523 | size, |
524 | style: style.into(), |
525 | } |
526 | } |
527 | } |
528 | |
529 | impl<'a, Coord, Size: SizeDesc> PointCollection<'a, Coord> for &'a Circle<Coord, Size> { |
530 | type Point = &'a Coord; |
531 | type IntoIter = std::iter::Once<&'a Coord>; |
532 | fn point_iter(self) -> std::iter::Once<&'a Coord> { |
533 | std::iter::once(&self.center) |
534 | } |
535 | } |
536 | |
537 | impl<Coord, DB: DrawingBackend, Size: SizeDesc> Drawable<DB> for Circle<Coord, Size> { |
538 | fn draw<I: Iterator<Item = BackendCoord>>( |
539 | &self, |
540 | mut points: I, |
541 | backend: &mut DB, |
542 | ps: (u32, u32), |
543 | ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { |
544 | if let Some((x: i32, y: i32)) = points.next() { |
545 | let size: u32 = self.size.in_pixels(&ps).max(0) as u32; |
546 | return backend.draw_circle((x, y), radius:size, &self.style, self.style.filled); |
547 | } |
548 | Ok(()) |
549 | } |
550 | } |
551 | |
552 | #[cfg (test)] |
553 | #[test ] |
554 | fn test_circle_element() { |
555 | use crate::prelude::*; |
556 | let da = crate::create_mocked_drawing_area(300, 300, |m| { |
557 | m.check_draw_circle(|c, _, f, s, r| { |
558 | assert_eq!(c, BLUE.to_rgba()); |
559 | assert!(!f); |
560 | assert_eq!(s, (150, 151)); |
561 | assert_eq!(r, 20); |
562 | }); |
563 | m.drop_check(|b| { |
564 | assert_eq!(b.num_draw_circle_call, 1); |
565 | assert_eq!(b.draw_count, 1); |
566 | }); |
567 | }); |
568 | da.draw(&Circle::new((150, 151), 20, BLUE)) |
569 | .expect("Drawing Failure" ); |
570 | } |
571 | |
572 | /// An element of a filled polygon |
573 | pub struct Polygon<Coord> { |
574 | points: Vec<Coord>, |
575 | style: ShapeStyle, |
576 | } |
577 | impl<Coord> Polygon<Coord> { |
578 | /// Create a new polygon |
579 | /// - `points`: The iterator of the points |
580 | /// - `style`: The shape style |
581 | /// - returns the created element |
582 | pub fn new<P: Into<Vec<Coord>>, S: Into<ShapeStyle>>(points: P, style: S) -> Self { |
583 | Self { |
584 | points: points.into(), |
585 | style: style.into(), |
586 | } |
587 | } |
588 | } |
589 | |
590 | impl<'a, Coord> PointCollection<'a, Coord> for &'a Polygon<Coord> { |
591 | type Point = &'a Coord; |
592 | type IntoIter = &'a [Coord]; |
593 | fn point_iter(self) -> &'a [Coord] { |
594 | &self.points |
595 | } |
596 | } |
597 | |
598 | impl<Coord, DB: DrawingBackend> Drawable<DB> for Polygon<Coord> { |
599 | fn draw<I: Iterator<Item = BackendCoord>>( |
600 | &self, |
601 | points: I, |
602 | backend: &mut DB, |
603 | _: (u32, u32), |
604 | ) -> Result<(), DrawingErrorKind<DB::ErrorType>> { |
605 | backend.fill_polygon(vert:points, &self.style.color.to_backend_color()) |
606 | } |
607 | } |
608 | |
609 | #[cfg (test)] |
610 | #[test ] |
611 | fn test_polygon_element() { |
612 | use crate::prelude::*; |
613 | let points = vec![(100, 100), (50, 500), (300, 400), (200, 300), (550, 200)]; |
614 | let expected_points = points.clone(); |
615 | |
616 | let da = crate::create_mocked_drawing_area(800, 800, |m| { |
617 | m.check_fill_polygon(move |c, p| { |
618 | assert_eq!(c, BLUE.to_rgba()); |
619 | assert_eq!(expected_points.len(), p.len()); |
620 | assert_eq!(expected_points, p); |
621 | }); |
622 | m.drop_check(|b| { |
623 | assert_eq!(b.num_fill_polygon_call, 1); |
624 | assert_eq!(b.draw_count, 1); |
625 | }); |
626 | }); |
627 | |
628 | da.draw(&Polygon::new(points.clone(), BLUE)) |
629 | .expect("Drawing Failure" ); |
630 | } |
631 | |