Skip to main content

floem/views/
canvas.rs

1use floem_reactive::SignalTracker;
2use peniko::kurbo::Size;
3
4use crate::{
5    context::{LayoutChanged, LayoutChangedListener, PaintCx},
6    event::listener::EventListenerTrait,
7    view::{View, ViewId},
8};
9
10/// A Canvas view. See the docs for [canvas()].
11#[allow(clippy::type_complexity)]
12pub struct Canvas {
13    id: ViewId,
14    paint_fn: Box<dyn Fn(&mut PaintCx, Size)>,
15    size: Size,
16    tracker: Option<SignalTracker>,
17}
18
19/// Creates a new Canvas view that can be used for custom painting
20///
21/// A [`Canvas`] provides a low-level interface for custom drawing operations. The supplied
22/// paint function will be called whenever the view needs to be rendered, and any signals accessed
23/// within the paint function will automatically trigger repaints when they change.
24///
25///
26/// # Example
27/// ```rust
28/// use floem::prelude::*;
29/// use palette::css;
30/// use peniko::kurbo::Rect;
31/// canvas(move |cx, size| {
32///     cx.fill(
33///         &Rect::ZERO
34///             .with_size(size)
35///             .to_rounded_rect(8.),
36///         css::PURPLE,
37///         0.,
38///     );
39/// })
40/// .style(|s| s.size(100, 300));
41/// ```
42pub fn canvas(paint: impl Fn(&mut PaintCx, Size) + 'static) -> Canvas {
43    let id = ViewId::new();
44    id.register_listener(LayoutChangedListener::listener_key());
45
46    Canvas {
47        id,
48        paint_fn: Box::new(paint),
49        size: Default::default(),
50        tracker: None,
51    }
52}
53
54impl Canvas {
55    fn post_layout(&mut self, new_layout: &LayoutChanged) {
56        self.size = new_layout.new_box.size();
57    }
58}
59
60impl View for Canvas {
61    fn id(&self) -> ViewId {
62        self.id
63    }
64
65    fn debug_name(&self) -> std::borrow::Cow<'static, str> {
66        "Canvas".into()
67    }
68
69    fn event(&mut self, cx: &mut crate::event::EventCx) -> crate::event::EventPropagation {
70        // in order to use this we had to set `id.has_layout_listener`.
71        if let Some(new_layout) = LayoutChangedListener::extract(&cx.event) {
72            self.post_layout(new_layout);
73        }
74        crate::event::EventPropagation::Continue
75    }
76
77    fn paint(&mut self, cx: &mut PaintCx) {
78        let id = self.id;
79        let paint = &self.paint_fn;
80
81        if self.tracker.is_none() {
82            self.tracker = Some(SignalTracker::new(move || {
83                id.request_paint();
84            }));
85        }
86
87        let tracker = self.tracker.as_ref().unwrap();
88        tracker.track(|| {
89            paint(cx, self.size);
90        });
91    }
92}