1use crate::error::{RenderError, RenderResult};
4use crate::style::{FillStyle, GradientDirection, LineStyle, StrokeStyle};
5use embedded_graphics::{
6 draw_target::DrawTarget,
7 prelude::*,
8 primitives::{Circle, Line, PrimitiveStyle, PrimitiveStyleBuilder, Rectangle},
9};
10
11#[cfg(not(feature = "std"))]
12use micromath::F32Ext;
13
14pub struct ChartRenderer;
16
17impl ChartRenderer {
18 pub fn draw_line<C, D>(
20 start: Point,
21 end: Point,
22 style: &LineStyle<C>,
23 target: &mut D,
24 ) -> RenderResult<()>
25 where
26 C: PixelColor,
27 D: DrawTarget<Color = C>,
28 {
29 let primitive_style = PrimitiveStyleBuilder::new()
30 .stroke_color(style.color)
31 .stroke_width(style.width)
32 .build();
33
34 Line::new(start, end)
35 .into_styled(primitive_style)
36 .draw(target)
37 .map_err(|_| RenderError::DrawingFailed)?;
38
39 Ok(())
40 }
41
42 pub fn draw_polyline<C, D>(
44 points: &[Point],
45 style: &LineStyle<C>,
46 target: &mut D,
47 ) -> RenderResult<()>
48 where
49 C: PixelColor,
50 D: DrawTarget<Color = C>,
51 {
52 if points.len() < 2 {
53 return Ok(());
54 }
55
56 for window in points.windows(2) {
57 if let [p1, p2] = window {
58 Self::draw_line(*p1, *p2, style, target)?;
59 }
60 }
61
62 Ok(())
63 }
64
65 pub fn draw_filled_rectangle<C, D>(
67 rect: Rectangle,
68 fill_style: &FillStyle<C>,
69 target: &mut D,
70 ) -> RenderResult<()>
71 where
72 C: PixelColor,
73 D: DrawTarget<Color = C>,
74 {
75 use crate::style::FillPattern;
76
77 match &fill_style.pattern {
78 FillPattern::Solid(color) => {
79 let primitive_style = PrimitiveStyle::with_fill(*color);
80 rect.into_styled(primitive_style)
81 .draw(target)
82 .map_err(|_| RenderError::DrawingFailed)?;
83 }
84 FillPattern::LinearGradient(gradient) => {
85 Self::draw_linear_gradient_rect(rect, gradient, target)?;
86 }
87 FillPattern::RadialGradient(gradient) => {
88 Self::draw_radial_gradient_rect(rect, gradient, target)?;
89 }
90 FillPattern::Pattern(pattern) => {
91 Self::draw_pattern_rect(rect, pattern, target)?;
92 }
93 }
94 Ok(())
95 }
96
97 pub fn draw_rectangle<C, D>(
99 rect: Rectangle,
100 stroke_style: Option<&StrokeStyle<C>>,
101 fill_style: Option<&FillStyle<C>>,
102 target: &mut D,
103 ) -> RenderResult<()>
104 where
105 C: PixelColor,
106 D: DrawTarget<Color = C>,
107 {
108 let mut style_builder = PrimitiveStyleBuilder::new();
109
110 if let Some(fill) = fill_style {
111 if let Some(color) = fill.solid_color() {
112 style_builder = style_builder.fill_color(color);
113 }
114 }
115
116 if let Some(stroke) = stroke_style {
117 style_builder = style_builder
118 .stroke_color(stroke.color)
119 .stroke_width(stroke.width);
120 }
121
122 rect.into_styled(style_builder.build())
123 .draw(target)
124 .map_err(|_| RenderError::DrawingFailed)?;
125
126 Ok(())
127 }
128
129 pub fn draw_circle<C, D>(
131 center: Point,
132 radius: u32,
133 stroke_style: Option<&StrokeStyle<C>>,
134 fill_style: Option<&FillStyle<C>>,
135 target: &mut D,
136 ) -> RenderResult<()>
137 where
138 C: PixelColor,
139 D: DrawTarget<Color = C>,
140 {
141 let mut style_builder = PrimitiveStyleBuilder::new();
142
143 if let Some(fill) = fill_style {
144 if let Some(color) = fill.solid_color() {
145 style_builder = style_builder.fill_color(color);
146 }
147 }
148
149 if let Some(stroke) = stroke_style {
150 style_builder = style_builder
151 .stroke_color(stroke.color)
152 .stroke_width(stroke.width);
153 }
154
155 let circle = Circle::new(
156 Point::new(center.x - radius as i32, center.y - radius as i32),
157 radius * 2,
158 );
159
160 circle
161 .into_styled(style_builder.build())
162 .draw(target)
163 .map_err(|_| RenderError::DrawingFailed)?;
164
165 Ok(())
166 }
167
168 pub fn draw_grid<C, D>(
170 area: Rectangle,
171 grid_spacing: Size,
172 style: &LineStyle<C>,
173 target: &mut D,
174 ) -> RenderResult<()>
175 where
176 C: PixelColor,
177 D: DrawTarget<Color = C>,
178 {
179 let mut x = area.top_left.x;
181 while x <= area.top_left.x + area.size.width as i32 {
182 let start = Point::new(x, area.top_left.y);
183 let end = Point::new(x, area.top_left.y + area.size.height as i32);
184 Self::draw_line(start, end, style, target)?;
185 x += grid_spacing.width as i32;
186 }
187
188 let mut y = area.top_left.y;
190 while y <= area.top_left.y + area.size.height as i32 {
191 let start = Point::new(area.top_left.x, y);
192 let end = Point::new(area.top_left.x + area.size.width as i32, y);
193 Self::draw_line(start, end, style, target)?;
194 y += grid_spacing.height as i32;
195 }
196
197 Ok(())
198 }
199
200 pub fn clear_area<C, D>(area: Rectangle, color: C, target: &mut D) -> RenderResult<()>
202 where
203 C: PixelColor,
204 D: DrawTarget<Color = C>,
205 {
206 let fill_style = FillStyle::solid(color);
207 Self::draw_filled_rectangle(area, &fill_style, target)
208 }
209
210 fn draw_linear_gradient_rect<C, D, const N: usize>(
212 rect: Rectangle,
213 gradient: &crate::style::LinearGradient<C, N>,
214 target: &mut D,
215 ) -> RenderResult<()>
216 where
217 C: PixelColor,
218 D: DrawTarget<Color = C>,
219 {
220 if !gradient.is_valid() {
221 return Ok(());
222 }
223
224 match gradient.direction() {
225 GradientDirection::Horizontal => {
226 for x in 0..rect.size.width {
228 let t = x as f32 / (rect.size.width - 1) as f32;
229 if let Some(color) = gradient.color_at(t) {
230 let line_start = Point::new(rect.top_left.x + x as i32, rect.top_left.y);
231 let line_end = Point::new(
232 rect.top_left.x + x as i32,
233 rect.top_left.y + rect.size.height as i32 - 1,
234 );
235 Line::new(line_start, line_end)
236 .into_styled(PrimitiveStyle::with_stroke(color, 1))
237 .draw(target)
238 .map_err(|_| RenderError::DrawingFailed)?;
239 }
240 }
241 }
242 GradientDirection::Vertical => {
243 for y in 0..rect.size.height {
245 let t = y as f32 / (rect.size.height - 1) as f32;
246 if let Some(color) = gradient.color_at(t) {
247 Self::draw_horizontal_line(
248 Point::new(rect.top_left.x, rect.top_left.y + y as i32),
249 rect.size.width,
250 color,
251 target,
252 )?;
253 }
254 }
255 }
256 GradientDirection::Diagonal | GradientDirection::ReverseDiagonal => {
257 let total = rect.size.width + rect.size.height - 2;
259 let step_size = if total > 100 { 2 } else { 1 }; for y in (0..rect.size.height).step_by(step_size as usize) {
262 for x in (0..rect.size.width).step_by(step_size as usize) {
263 let t = if gradient.direction() == GradientDirection::Diagonal {
264 (x + y) as f32 / total as f32
265 } else {
266 (rect.size.width - 1 - x + y) as f32 / total as f32
267 };
268
269 if let Some(color) = gradient.color_at(t) {
270 let pixel_rect = Rectangle::new(
272 Point::new(rect.top_left.x + x as i32, rect.top_left.y + y as i32),
273 Size::new(step_size, step_size),
274 );
275 pixel_rect
276 .into_styled(PrimitiveStyle::with_fill(color))
277 .draw(target)
278 .map_err(|_| RenderError::DrawingFailed)?;
279 }
280 }
281 }
282 }
283 }
284 Ok(())
285 }
286
287 fn draw_radial_gradient_rect<C, D, const N: usize>(
289 rect: Rectangle,
290 gradient: &crate::style::RadialGradient<C, N>,
291 target: &mut D,
292 ) -> RenderResult<()>
293 where
294 C: PixelColor,
295 D: DrawTarget<Color = C>,
296 {
297 if !gradient.is_valid() {
298 return Ok(());
299 }
300
301 let center = gradient.center();
302 let center_x = rect.top_left.x + (rect.size.width as i32 * center.x / 100);
303 let center_y = rect.top_left.y + (rect.size.height as i32 * center.y / 100);
304
305 let max_dist = {
307 let dx1 = (rect.top_left.x - center_x).abs();
308 let dx2 = (rect.top_left.x + rect.size.width as i32 - center_x).abs();
309 let dy1 = (rect.top_left.y - center_y).abs();
310 let dy2 = (rect.top_left.y + rect.size.height as i32 - center_y).abs();
311 let max_dx = dx1.max(dx2) as f32;
312 let max_dy = dy1.max(dy2) as f32;
313 (max_dx * max_dx + max_dy * max_dy).sqrt()
314 };
315
316 for y in 0..rect.size.height {
318 for x in 0..rect.size.width {
319 let px = rect.top_left.x + x as i32;
320 let py = rect.top_left.y + y as i32;
321 let dx = (px - center_x) as f32;
322 let dy = (py - center_y) as f32;
323 let dist = (dx * dx + dy * dy).sqrt();
324 let t = (dist / max_dist).clamp(0.0, 1.0);
325
326 if let Some(color) = gradient.color_at_distance(t) {
327 Pixel(Point::new(px, py), color)
328 .draw(target)
329 .map_err(|_| RenderError::DrawingFailed)?;
330 }
331 }
332 }
333 Ok(())
334 }
335
336 fn draw_pattern_rect<C, D>(
338 rect: Rectangle,
339 pattern: &crate::style::PatternFill<C>,
340 target: &mut D,
341 ) -> RenderResult<()>
342 where
343 C: PixelColor,
344 D: DrawTarget<Color = C>,
345 {
346 for y in 0..rect.size.height {
348 for x in 0..rect.size.width {
349 let color = pattern.color_at(x as i32, y as i32);
350 Pixel(
351 Point::new(rect.top_left.x + x as i32, rect.top_left.y + y as i32),
352 color,
353 )
354 .draw(target)
355 .map_err(|_| RenderError::DrawingFailed)?;
356 }
357 }
358 Ok(())
359 }
360
361 fn draw_horizontal_line<C, D>(
363 start: Point,
364 width: u32,
365 color: C,
366 target: &mut D,
367 ) -> RenderResult<()>
368 where
369 C: PixelColor,
370 D: DrawTarget<Color = C>,
371 {
372 Line::new(start, Point::new(start.x + width as i32 - 1, start.y))
373 .into_styled(PrimitiveStyle::with_stroke(color, 1))
374 .draw(target)
375 .map_err(|_| RenderError::DrawingFailed)?;
376 Ok(())
377 }
378
379 #[cfg(feature = "color-support")]
381 pub fn draw_linear_gradient_rect_rgb565<D, const N: usize>(
382 rect: Rectangle,
383 gradient: &crate::style::LinearGradient<embedded_graphics::pixelcolor::Rgb565, N>,
384 target: &mut D,
385 ) -> RenderResult<()>
386 where
387 D: DrawTarget<Color = embedded_graphics::pixelcolor::Rgb565>,
388 {
389 use crate::style::GradientInterpolation;
390
391 if !gradient.is_valid() {
392 return Ok(());
393 }
394
395 match gradient.direction() {
396 GradientDirection::Horizontal => {
397 for x in 0..rect.size.width {
399 let t = x as f32 / (rect.size.width - 1) as f32;
400 if let Some(color) = gradient.interpolated_color_at(t) {
401 let line_start = Point::new(rect.top_left.x + x as i32, rect.top_left.y);
402 let line_end = Point::new(
403 rect.top_left.x + x as i32,
404 rect.top_left.y + rect.size.height as i32 - 1,
405 );
406 Line::new(line_start, line_end)
407 .into_styled(PrimitiveStyle::with_stroke(color, 1))
408 .draw(target)
409 .map_err(|_| RenderError::DrawingFailed)?;
410 }
411 }
412 }
413 GradientDirection::Vertical => {
414 for y in 0..rect.size.height {
416 let t = y as f32 / (rect.size.height - 1) as f32;
417 if let Some(color) = gradient.interpolated_color_at(t) {
418 Self::draw_horizontal_line(
419 Point::new(rect.top_left.x, rect.top_left.y + y as i32),
420 rect.size.width,
421 color,
422 target,
423 )?;
424 }
425 }
426 }
427 GradientDirection::Diagonal | GradientDirection::ReverseDiagonal => {
428 let step = 3; for y in (0..rect.size.height).step_by(step) {
432 for x in (0..rect.size.width).step_by(step) {
433 let t = if gradient.direction() == GradientDirection::Diagonal {
435 (x + y) as f32 / (rect.size.width + rect.size.height - 2) as f32
436 } else {
437 (rect.size.width - 1 - x + y) as f32
438 / (rect.size.width + rect.size.height - 2) as f32
439 };
440
441 if let Some(color) = gradient.interpolated_color_at(t) {
442 Rectangle::new(
443 Point::new(rect.top_left.x + x as i32, rect.top_left.y + y as i32),
444 Size::new(step as u32, step as u32),
445 )
446 .into_styled(PrimitiveStyle::with_fill(color))
447 .draw(target)
448 .map_err(|_| RenderError::DrawingFailed)?;
449 }
450 }
451 }
452 }
453 }
454 Ok(())
455 }
456
457 #[cfg(feature = "color-support")]
459 pub fn draw_radial_gradient_rect_rgb565<D, const N: usize>(
460 rect: Rectangle,
461 gradient: &crate::style::RadialGradient<embedded_graphics::pixelcolor::Rgb565, N>,
462 target: &mut D,
463 ) -> RenderResult<()>
464 where
465 D: DrawTarget<Color = embedded_graphics::pixelcolor::Rgb565>,
466 {
467 use crate::style::RadialGradientInterpolation;
468
469 if !gradient.is_valid() {
470 return Ok(());
471 }
472
473 let center = gradient.center();
474 let center_x = rect.top_left.x + (rect.size.width as i32 * center.x / 100);
475 let center_y = rect.top_left.y + (rect.size.height as i32 * center.y / 100);
476
477 let max_dist = {
479 let dx1 = (rect.top_left.x - center_x).abs();
480 let dx2 = (rect.top_left.x + rect.size.width as i32 - center_x).abs();
481 let dy1 = (rect.top_left.y - center_y).abs();
482 let dy2 = (rect.top_left.y + rect.size.height as i32 - center_y).abs();
483 let max_dx = dx1.max(dx2) as f32;
484 let max_dy = dy1.max(dy2) as f32;
485 (max_dx * max_dx + max_dy * max_dy).sqrt()
486 };
487
488 let step_size = 3;
491
492 for y in (0..rect.size.height).step_by(step_size) {
493 for x in (0..rect.size.width).step_by(step_size) {
494 let px = rect.top_left.x + x as i32;
495 let py = rect.top_left.y + y as i32;
496 let dx = (px - center_x) as f32;
497 let dy = (py - center_y) as f32;
498 let dist = (dx * dx + dy * dy).sqrt();
499 let t = (dist / max_dist).clamp(0.0, 1.0);
500
501 if let Some(color) = gradient.interpolated_color_at_distance(t) {
502 Rectangle::new(
503 Point::new(px, py),
504 Size::new(step_size as u32, step_size as u32),
505 )
506 .into_styled(PrimitiveStyle::with_fill(color))
507 .draw(target)
508 .map_err(|_| RenderError::DrawingFailed)?;
509 }
510 }
511 }
512 Ok(())
513 }
514}
515
516pub struct ClippingRenderer;
518
519impl ClippingRenderer {
520 pub fn is_point_visible(point: Point, bounds: Rectangle) -> bool {
522 point.x >= bounds.top_left.x
523 && point.x < bounds.top_left.x + bounds.size.width as i32
524 && point.y >= bounds.top_left.y
525 && point.y < bounds.top_left.y + bounds.size.height as i32
526 }
527
528 pub fn is_rectangle_visible(rect: Rectangle, bounds: Rectangle) -> bool {
530 !(rect.top_left.x >= bounds.top_left.x + bounds.size.width as i32
531 || rect.top_left.x + rect.size.width as i32 <= bounds.top_left.x
532 || rect.top_left.y >= bounds.top_left.y + bounds.size.height as i32
533 || rect.top_left.y + rect.size.height as i32 <= bounds.top_left.y)
534 }
535
536 pub fn clip_line(start: Point, end: Point, bounds: Rectangle) -> Option<(Point, Point)> {
538 let mut x1 = start.x;
539 let mut y1 = start.y;
540 let mut x2 = end.x;
541 let mut y2 = end.y;
542
543 let xmin = bounds.top_left.x;
544 let ymin = bounds.top_left.y;
545 let xmax = bounds.top_left.x + bounds.size.width as i32;
546 let ymax = bounds.top_left.y + bounds.size.height as i32;
547
548 let mut outcode1 = Self::compute_outcode(x1, y1, xmin, ymin, xmax, ymax);
550 let mut outcode2 = Self::compute_outcode(x2, y2, xmin, ymin, xmax, ymax);
551
552 loop {
553 if (outcode1 | outcode2) == 0 {
554 return Some((Point::new(x1, y1), Point::new(x2, y2)));
556 } else if (outcode1 & outcode2) != 0 {
557 return None;
559 } else {
560 let outcode_out = if outcode1 != 0 { outcode1 } else { outcode2 };
562
563 let (x, y) = if (outcode_out & 8) != 0 {
564 let x = x1 + (x2 - x1) * (ymax - y1) / (y2 - y1);
566 (x, ymax)
567 } else if (outcode_out & 4) != 0 {
568 let x = x1 + (x2 - x1) * (ymin - y1) / (y2 - y1);
570 (x, ymin)
571 } else if (outcode_out & 2) != 0 {
572 let y = y1 + (y2 - y1) * (xmax - x1) / (x2 - x1);
574 (xmax, y)
575 } else {
576 let y = y1 + (y2 - y1) * (xmin - x1) / (x2 - x1);
578 (xmin, y)
579 };
580
581 if outcode_out == outcode1 {
582 x1 = x;
583 y1 = y;
584 outcode1 = Self::compute_outcode(x1, y1, xmin, ymin, xmax, ymax);
585 } else {
586 x2 = x;
587 y2 = y;
588 outcode2 = Self::compute_outcode(x2, y2, xmin, ymin, xmax, ymax);
589 }
590 }
591 }
592 }
593
594 fn compute_outcode(x: i32, y: i32, xmin: i32, ymin: i32, xmax: i32, ymax: i32) -> u8 {
596 let mut code = 0;
597
598 if x < xmin {
599 code |= 1; } else if x > xmax {
601 code |= 2; }
603
604 if y < ymin {
605 code |= 4; } else if y > ymax {
607 code |= 8; }
609
610 code
611 }
612}
613
614pub mod text {
616 use super::*;
617 use embedded_graphics::mono_font::{MonoFont, MonoTextStyle};
618 use embedded_graphics::text::{Baseline, Text};
619
620 pub struct TextRenderer;
622
623 impl TextRenderer {
624 pub fn draw_text<C, D>(
626 text: &str,
627 position: Point,
628 style: &MonoTextStyle<C>,
629 target: &mut D,
630 ) -> RenderResult<()>
631 where
632 C: PixelColor,
633 D: DrawTarget<Color = C>,
634 {
635 Text::with_baseline(text, position, *style, Baseline::Top)
636 .draw(target)
637 .map_err(|_| RenderError::TextRenderingFailed)?;
638
639 Ok(())
640 }
641
642 pub fn text_size<C>(text: &str, font: &MonoFont) -> Size {
644 let char_size = font.character_size;
645 Size::new(char_size.width * text.len() as u32, char_size.height)
646 }
647
648 pub fn draw_centered_text<C, D>(
650 text: &str,
651 container: Rectangle,
652 style: &MonoTextStyle<C>,
653 font: &MonoFont,
654 target: &mut D,
655 ) -> RenderResult<()>
656 where
657 C: PixelColor,
658 D: DrawTarget<Color = C>,
659 {
660 let text_size = Self::text_size::<C>(text, font);
661 let x =
662 container.top_left.x + (container.size.width as i32 - text_size.width as i32) / 2;
663 let y =
664 container.top_left.y + (container.size.height as i32 - text_size.height as i32) / 2;
665
666 Self::draw_text(text, Point::new(x, y), style, target)
667 }
668 }
669}
670
671pub struct PrimitiveRenderer;
673
674impl PrimitiveRenderer {
675 pub fn draw_triangle<C, D>(
677 p1: Point,
678 p2: Point,
679 p3: Point,
680 stroke_style: Option<&StrokeStyle<C>>,
681 fill_style: Option<&FillStyle<C>>,
682 target: &mut D,
683 ) -> RenderResult<()>
684 where
685 C: PixelColor,
686 D: DrawTarget<Color = C>,
687 {
688 if let Some(stroke) = stroke_style {
691 let line_style = LineStyle::solid(stroke.color).width(stroke.width);
692 ChartRenderer::draw_line(p1, p2, &line_style, target)?;
693 ChartRenderer::draw_line(p2, p3, &line_style, target)?;
694 ChartRenderer::draw_line(p3, p1, &line_style, target)?;
695 }
696
697 if let Some(fill) = fill_style {
699 Self::fill_triangle(p1, p2, p3, fill, target)?;
700 }
701
702 Ok(())
703 }
704
705 fn fill_triangle<C, D>(
707 p1: Point,
708 p2: Point,
709 p3: Point,
710 fill_style: &FillStyle<C>,
711 target: &mut D,
712 ) -> RenderResult<()>
713 where
714 C: PixelColor,
715 D: DrawTarget<Color = C>,
716 {
717 let mut points = [p1, p2, p3];
719 if points[0].y > points[1].y {
721 points.swap(0, 1);
722 }
723 if points[1].y > points[2].y {
724 points.swap(1, 2);
725 }
726 if points[0].y > points[1].y {
727 points.swap(0, 1);
728 }
729 let [top, mid, bottom] = points;
730
731 if top.y == bottom.y {
733 let min_x = top.x.min(mid.x).min(bottom.x);
735 let max_x = top.x.max(mid.x).max(bottom.x);
736 if let Some(color) = fill_style.solid_color() {
737 Self::draw_horizontal_line(min_x, max_x, top.y, color, target)?;
738 }
739 return Ok(());
740 }
741
742 let total_height = bottom.y - top.y;
744
745 if mid.y > top.y {
747 let segment_height = mid.y - top.y;
748 for y in top.y..mid.y {
749 let alpha = (y - top.y) as f32 / total_height as f32;
750 let beta = (y - top.y) as f32 / segment_height as f32;
751
752 let x1 = top.x + ((bottom.x - top.x) as f32 * alpha) as i32;
753 let x2 = top.x + ((mid.x - top.x) as f32 * beta) as i32;
754
755 let (min_x, max_x) = if x1 < x2 { (x1, x2) } else { (x2, x1) };
756 if let Some(color) = fill_style.solid_color() {
757 Self::draw_horizontal_line(min_x, max_x, y, color, target)?;
758 }
759 }
760 }
761
762 if bottom.y > mid.y {
764 let segment_height = bottom.y - mid.y;
765 for y in mid.y..=bottom.y {
766 let alpha = (y - top.y) as f32 / total_height as f32;
767 let beta = (y - mid.y) as f32 / segment_height as f32;
768
769 let x1 = top.x + ((bottom.x - top.x) as f32 * alpha) as i32;
770 let x2 = mid.x + ((bottom.x - mid.x) as f32 * beta) as i32;
771
772 let (min_x, max_x) = if x1 < x2 { (x1, x2) } else { (x2, x1) };
773 if let Some(color) = fill_style.solid_color() {
774 Self::draw_horizontal_line(min_x, max_x, y, color, target)?;
775 }
776 }
777 }
778
779 Ok(())
780 }
781
782 fn draw_horizontal_line<C, D>(
784 x1: i32,
785 x2: i32,
786 y: i32,
787 color: C,
788 target: &mut D,
789 ) -> RenderResult<()>
790 where
791 C: PixelColor,
792 D: DrawTarget<Color = C>,
793 {
794 if x1 == x2 {
795 target
797 .draw_iter(core::iter::once(Pixel(Point::new(x1, y), color)))
798 .map_err(|_| RenderError::DrawingFailed)?;
799 } else {
800 let line_style = PrimitiveStyle::with_stroke(color, 1);
802 Line::new(Point::new(x1, y), Point::new(x2, y))
803 .into_styled(line_style)
804 .draw(target)
805 .map_err(|_| RenderError::DrawingFailed)?;
806 }
807 Ok(())
808 }
809
810 pub fn draw_diamond<C, D>(
812 center: Point,
813 size: u32,
814 stroke_style: Option<&StrokeStyle<C>>,
815 fill_style: Option<&FillStyle<C>>,
816 target: &mut D,
817 ) -> RenderResult<()>
818 where
819 C: PixelColor,
820 D: DrawTarget<Color = C>,
821 {
822 let half_size = size as i32 / 2;
823 let top = Point::new(center.x, center.y - half_size);
824 let right = Point::new(center.x + half_size, center.y);
825 let bottom = Point::new(center.x, center.y + half_size);
826 let left = Point::new(center.x - half_size, center.y);
827
828 Self::draw_triangle(top, right, bottom, stroke_style, fill_style, target)?;
829 Self::draw_triangle(top, bottom, left, stroke_style, fill_style, target)?;
830
831 Ok(())
832 }
833}
834
835#[cfg(feature = "animations")]
837pub struct AnimationFrameRenderer {
838 frame_rate: u32,
840 time_accumulator: crate::time::Milliseconds,
842 last_frame_time: Option<crate::time::Milliseconds>,
844}
845
846#[cfg(feature = "animations")]
847impl AnimationFrameRenderer {
848 pub fn new(frame_rate: u32) -> Self {
850 Self {
851 frame_rate: frame_rate.clamp(1, 120),
852 time_accumulator: 0,
853 last_frame_time: None,
854 }
855 }
856
857 pub fn update(&mut self, current_time: crate::time::Milliseconds) -> bool {
859 let frame_duration = 1000 / self.frame_rate;
860
861 if let Some(last_time) = self.last_frame_time {
862 let delta = current_time.saturating_sub(last_time);
863 self.time_accumulator += delta;
864 }
865
866 self.last_frame_time = Some(current_time);
867
868 if self.time_accumulator >= frame_duration {
869 self.time_accumulator = self.time_accumulator.saturating_sub(frame_duration);
870 true } else {
872 false
873 }
874 }
875
876 pub fn frame_rate(&self) -> u32 {
878 self.frame_rate
879 }
880
881 pub fn set_frame_rate(&mut self, fps: u32) {
883 self.frame_rate = fps.clamp(1, 120);
884 }
885
886 pub fn reset(&mut self) {
888 self.time_accumulator = 0;
889 self.last_frame_time = None;
890 }
891
892 pub fn render_animated_chart<C, D, T>(
894 &self,
895 chart: &T,
896 data: &T::Data,
897 config: &T::Config,
898 viewport: embedded_graphics::primitives::Rectangle,
899 target: &mut D,
900 ) -> crate::error::RenderResult<()>
901 where
902 C: PixelColor,
903 D: DrawTarget<Color = C>,
904 T: crate::chart::traits::AnimatedChart<C> + crate::chart::traits::AnimationRenderer<C>,
905 {
906 if chart.needs_frame_update() {
907 chart
908 .draw_animated(data, config, viewport, target, 0)
909 .map_err(|_| crate::error::RenderError::DrawingFailed)?;
910 }
911 Ok(())
912 }
913
914 pub fn charts_need_update<C>(
916 &self,
917 charts: &[&dyn crate::chart::traits::AnimationRenderer<C>],
918 ) -> bool
919 where
920 C: PixelColor,
921 {
922 charts.iter().any(|chart| chart.needs_frame_update())
923 }
924}
925
926#[cfg(feature = "animations")]
927impl Default for AnimationFrameRenderer {
928 fn default() -> Self {
929 Self::new(60) }
931}
932
933pub struct EnhancedChartRenderer;
935
936impl EnhancedChartRenderer {
937 pub fn render_chart<C, D, T>(
939 chart: &T,
940 data: &T::Data,
941 config: &T::Config,
942 viewport: embedded_graphics::primitives::Rectangle,
943 target: &mut D,
944 ) -> crate::error::RenderResult<()>
945 where
946 C: PixelColor,
947 D: DrawTarget<Color = C>,
948 T: crate::chart::traits::Chart<C>,
949 T::Data: crate::data::DataSeries,
950 <T::Data as crate::data::DataSeries>::Item: crate::data::DataPoint,
951 <<T::Data as crate::data::DataSeries>::Item as crate::data::DataPoint>::X:
952 Into<f32> + Copy + PartialOrd,
953 <<T::Data as crate::data::DataSeries>::Item as crate::data::DataPoint>::Y:
954 Into<f32> + Copy + PartialOrd,
955 {
956 chart
957 .draw(data, config, viewport, target)
958 .map_err(|_| crate::error::RenderError::DrawingFailed)
959 }
960
961 #[cfg(feature = "animations")]
963 pub fn render_animated_chart<C, D, T>(
964 chart: &T,
965 data: &T::Data,
966 config: &T::Config,
967 viewport: embedded_graphics::primitives::Rectangle,
968 target: &mut D,
969 ) -> crate::error::RenderResult<()>
970 where
971 C: PixelColor,
972 D: DrawTarget<Color = C>,
973 T: crate::chart::traits::AnimatedChart<C>,
974 {
975 chart
976 .draw_animated(data, config, viewport, target, 0)
977 .map_err(|_| crate::error::RenderError::DrawingFailed)
978 }
979
980 #[cfg(feature = "animations")]
982 pub fn update_and_render<C, D, T>(
983 chart: &mut T,
984 data: &T::Data,
985 _delta_time: crate::time::Milliseconds,
986 config: &T::Config,
987 viewport: embedded_graphics::primitives::Rectangle,
988 target: &mut D,
989 ) -> crate::error::RenderResult<()>
990 where
991 C: PixelColor,
992 D: DrawTarget<Color = C>,
993 T: crate::chart::traits::AnimatedChart<C>,
994 {
995 Self::render_animated_chart(chart, data, config, viewport, target)
997 }
998
999 pub fn clear_viewport<C, D>(
1001 viewport: embedded_graphics::primitives::Rectangle,
1002 color: C,
1003 target: &mut D,
1004 ) -> crate::error::RenderResult<()>
1005 where
1006 C: PixelColor,
1007 D: DrawTarget<Color = C>,
1008 {
1009 ChartRenderer::clear_area(viewport, color, target)
1010 }
1011}
1012
1013#[cfg(test)]
1014mod tests {
1015 use super::*;
1016 use embedded_graphics::mock_display::MockDisplay;
1017 use embedded_graphics::pixelcolor::Rgb565;
1018
1019 #[test]
1020 fn test_clipping_point_visibility() {
1021 let bounds = Rectangle::new(Point::new(10, 10), Size::new(100, 80));
1022
1023 assert!(ClippingRenderer::is_point_visible(
1024 Point::new(50, 50),
1025 bounds
1026 ));
1027 assert!(!ClippingRenderer::is_point_visible(
1028 Point::new(5, 50),
1029 bounds
1030 ));
1031 assert!(!ClippingRenderer::is_point_visible(
1032 Point::new(150, 50),
1033 bounds
1034 ));
1035 }
1036
1037 #[test]
1038 fn test_clipping_rectangle_visibility() {
1039 let bounds = Rectangle::new(Point::new(0, 0), Size::new(100, 100));
1040
1041 let inside = Rectangle::new(Point::new(10, 10), Size::new(20, 20));
1043 assert!(ClippingRenderer::is_rectangle_visible(inside, bounds));
1044
1045 let outside = Rectangle::new(Point::new(150, 150), Size::new(20, 20));
1047 assert!(!ClippingRenderer::is_rectangle_visible(outside, bounds));
1048
1049 let overlapping = Rectangle::new(Point::new(90, 90), Size::new(20, 20));
1051 assert!(ClippingRenderer::is_rectangle_visible(overlapping, bounds));
1052 }
1053
1054 #[test]
1055 fn test_line_clipping() {
1056 let bounds = Rectangle::new(Point::new(0, 0), Size::new(100, 100));
1057
1058 let inside_line =
1060 ClippingRenderer::clip_line(Point::new(10, 10), Point::new(50, 50), bounds);
1061 assert!(inside_line.is_some());
1062
1063 let outside_line =
1065 ClippingRenderer::clip_line(Point::new(150, 150), Point::new(200, 200), bounds);
1066 assert!(outside_line.is_none());
1067 }
1068
1069 #[test]
1070 fn test_chart_renderer_line() {
1071 let mut display = MockDisplay::<Rgb565>::new();
1072 let style = LineStyle::solid(Rgb565::RED).width(1);
1073
1074 let result =
1075 ChartRenderer::draw_line(Point::new(0, 0), Point::new(10, 10), &style, &mut display);
1076
1077 assert!(result.is_ok());
1078 }
1079
1080 #[test]
1081 fn test_chart_renderer_rectangle() {
1082 let mut display = MockDisplay::<Rgb565>::new();
1083 let stroke = StrokeStyle::new(Rgb565::BLUE, 1);
1084 let fill = FillStyle::solid(Rgb565::GREEN);
1085
1086 let rect = Rectangle::new(Point::new(5, 5), Size::new(20, 15));
1087 let result = ChartRenderer::draw_rectangle(rect, Some(&stroke), Some(&fill), &mut display);
1088
1089 assert!(result.is_ok());
1090 }
1091}