frus_widgets/
custompaint.rs1use std::marker::PhantomData;
10
11use frus_core::{Rect, Scene};
12use frus_layout::{Dimension, Style};
13
14use crate::interaction::Status;
15use crate::theme::Theme;
16use crate::widget::Widget;
17
18type PaintFn = dyn Fn(&mut Scene, Rect, &Theme);
20
21pub struct CustomPaint<Msg = crate::callback::Callback> {
23 width: f32,
24 height: f32,
25 painter: Box<PaintFn>,
26 _msg: PhantomData<fn() -> Msg>,
28}
29
30impl<Msg> CustomPaint<Msg> {
31 pub fn new(
33 width: f32,
34 height: f32,
35 painter: impl Fn(&mut Scene, Rect, &Theme) + 'static,
36 ) -> Self {
37 Self {
38 width,
39 height,
40 painter: Box::new(painter),
41 _msg: PhantomData,
42 }
43 }
44}
45
46impl<Msg> Widget<Msg> for CustomPaint<Msg> {
47 fn style(&self) -> Style {
48 Style {
49 width: Dimension::Length(self.width),
50 height: Dimension::Length(self.height),
51 ..Default::default()
52 }
53 }
54
55 fn children(&self) -> &[Box<dyn Widget<Msg>>] {
56 &[]
57 }
58
59 fn paint(&self, bounds: Rect, _status: Status, theme: &Theme, scene: &mut Scene) {
60 (self.painter)(scene, bounds, theme);
61 }
62
63 fn on_click(&self) -> Option<Msg> {
64 None
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use frus_core::{Color, Path, Point, Primitive};
72
73 #[test]
74 fn invokes_the_closure_with_its_box() {
75 let widget: CustomPaint<()> = CustomPaint::new(50.0, 30.0, |scene, bounds, _theme| {
76 let tri = Path::new()
77 .move_to(Point::new(bounds.x, bounds.y))
78 .line_to(Point::new(bounds.x + bounds.width, bounds.y))
79 .line_to(Point::new(bounds.x, bounds.y + bounds.height))
80 .close();
81 scene.fill_path(&tri, Color::rgb(0.2, 0.4, 0.6));
82 });
83
84 let mut scene = Scene::new();
85 Widget::<()>::paint(
86 &widget,
87 Rect::new(10.0, 20.0, 50.0, 30.0),
88 Status::default(),
89 &Theme::default(),
90 &mut scene,
91 );
92
93 assert_eq!(scene.len(), 1);
94 match &scene.primitives()[0] {
95 Primitive::Path {
96 path,
97 fill: Some(_),
98 ..
99 } => {
100 assert_eq!(
102 path.verbs().first(),
103 Some(&frus_core::PathVerb::MoveTo(Point::new(10.0, 20.0)))
104 );
105 }
106 _ => panic!("expected a filled path"),
107 }
108 }
109}