kas_resvg/
canvas.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Canvas widget
7
8use kas::draw::{ImageFormat, ImageHandle};
9use kas::layout::{LogicalSize, PixmapScaling};
10use kas::prelude::*;
11use std::cell::RefCell;
12use std::future::Future;
13use tiny_skia::{Color, Pixmap};
14
15/// Draws to a [`Canvas`]'s [`Pixmap`]
16///
17/// Note: the value is sometimes moved between threads, hence [`Send`] bound.
18/// If the type is large it should be boxed.
19pub trait CanvasProgram: std::fmt::Debug + Send + 'static {
20    /// Draw image
21    ///
22    /// This method should draw an image to the canvas. It is called when the
23    /// pixmap is created and resized and when requested by [`Self::need_redraw`].
24    ///
25    /// Note that [`Layout::draw`] does not call this method, but instead draws
26    /// from a copy of the `pixmap` (updated each time this method completes).
27    fn draw(&self, pixmap: &mut Pixmap);
28
29    /// This method is called each time a frame is drawn. Note that since
30    /// redrawing is async and non-blocking, the result is expected to be at
31    /// least one frame late.
32    ///
33    /// The default implementation returns `false`.
34    fn need_redraw(&mut self) -> bool {
35        false
36    }
37}
38
39async fn draw<P: CanvasProgram>(program: P, mut pixmap: Pixmap) -> (P, Pixmap) {
40    pixmap.fill(Color::TRANSPARENT);
41    program.draw(&mut pixmap);
42    (program, pixmap)
43}
44
45#[derive(Clone)]
46enum State<P: CanvasProgram> {
47    Initial(P),
48    Rendering,
49    Ready(P, Pixmap),
50}
51
52impl<P: CanvasProgram> State<P> {
53    /// Redraw if requested
54    fn maybe_redraw(&mut self) -> Option<impl Future<Output = (P, Pixmap)> + use<P>> {
55        if let State::Ready(p, _) = self
56            && p.need_redraw()
57            && let State::Ready(p, px) = std::mem::replace(self, State::Rendering)
58        {
59            return Some(draw(p, px));
60        }
61
62        None
63    }
64
65    /// Resize if required, redrawing on resize
66    ///
67    /// Returns a future to redraw. Does nothing if currently redrawing.
68    fn resize(&mut self, (w, h): (u32, u32)) -> Option<impl Future<Output = (P, Pixmap)> + use<P>> {
69        let old_state = std::mem::replace(self, State::Rendering);
70        let (program, pixmap) = match old_state {
71            State::Ready(p, px) if (px.width(), px.height()) == (w, h) => {
72                *self = State::Ready(p, px);
73                return None;
74            }
75            State::Rendering => return None,
76            State::Initial(p) | State::Ready(p, _) => {
77                if let Some(px) = Pixmap::new(w, h) {
78                    (p, px)
79                } else {
80                    *self = State::Initial(p);
81                    return None;
82                }
83            }
84        };
85
86        Some(draw(program, pixmap))
87    }
88}
89
90#[impl_self]
91mod Canvas {
92    /// A canvas widget over the `tiny-skia` library
93    ///
94    /// The widget is essentially a cached image drawn from a [`Pixmap`]
95    /// controlled through an implementation of [`CanvasProgram`].
96    /// Note that the `tiny-skia` API is re-exported as [`crate::tiny_skia`].
97    ///
98    /// By default, a `Canvas` has a minimum size of 128x128 pixels and a high
99    /// stretch factor (i.e. will greedily occupy extra space). To adjust this
100    /// call one of the sizing/scaling methods.
101    #[autoimpl(Debug ignore self.inner)]
102    #[derive(Clone)]
103    #[widget]
104    pub struct Canvas<P: CanvasProgram> {
105        core: widget_core!(),
106        scaling: PixmapScaling,
107        inner: RefCell<State<P>>,
108        image: Option<ImageHandle>,
109    }
110
111    impl Self {
112        /// Construct
113        ///
114        /// Use [`Self::with_size`] or [`Self::with_scaling`] to set the initial size.
115        #[inline]
116        pub fn new(program: P) -> Self {
117            Canvas {
118                core: Default::default(),
119                scaling: PixmapScaling {
120                    size: LogicalSize(128.0, 128.0),
121                    stretch: Stretch::High,
122                    ..Default::default()
123                },
124                inner: RefCell::new(State::Initial(program)),
125                image: None,
126            }
127        }
128
129        /// Assign size
130        ///
131        /// Default size is 128 × 128.
132        #[inline]
133        #[must_use]
134        pub fn with_size(mut self, size: LogicalSize) -> Self {
135            self.scaling.size = size;
136            self
137        }
138
139        /// Adjust scaling
140        ///
141        /// Default size is 128 × 128; default stretch is [`Stretch::High`].
142        /// Other fields use [`PixmapScaling`]'s default values.
143        #[inline]
144        #[must_use]
145        pub fn with_scaling(mut self, f: impl FnOnce(&mut PixmapScaling)) -> Self {
146            f(&mut self.scaling);
147            self
148        }
149
150        /// Adjust scaling
151        ///
152        /// Default size is 128 × 128; default stretch is [`Stretch::High`].
153        /// Other fields use [`PixmapScaling`]'s default values.
154        #[inline]
155        pub fn set_scaling(&mut self, cx: &mut EventState, f: impl FnOnce(&mut PixmapScaling)) {
156            f(&mut self.scaling);
157            // NOTE: if only `aspect` is changed, REDRAW is enough
158            cx.resize(self);
159        }
160    }
161
162    impl Layout for Self {
163        fn rect(&self) -> Rect {
164            self.scaling.rect
165        }
166
167        fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
168            self.scaling.size_rules(sizer, axis)
169        }
170
171        fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
172            let align = hints.complete_default();
173            let scale_factor = cx.size_cx().scale_factor();
174            self.scaling.set_rect(rect, align, scale_factor);
175
176            let size = self.rect().size.cast();
177            if let Some(fut) = self.inner.get_mut().resize(size) {
178                cx.send_spawn(self.id(), fut);
179            }
180        }
181
182        fn draw(&self, mut draw: DrawCx) {
183            if let Ok(mut state) = self.inner.try_borrow_mut()
184                && let Some(fut) = state.maybe_redraw()
185            {
186                draw.ev_state().send_spawn(self.id(), fut);
187            }
188
189            if let Some(id) = self.image.as_ref().map(|h| h.id()) {
190                draw.image(self.rect(), id);
191            }
192        }
193    }
194
195    impl Tile for Self {
196        fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
197            Role::Canvas
198        }
199    }
200
201    impl Events for Self {
202        type Data = ();
203
204        fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
205            if let Some((program, mut pixmap)) = cx.try_pop::<(P, Pixmap)>() {
206                debug_assert!(matches!(self.inner.get_mut(), State::Rendering));
207                let size = (pixmap.width(), pixmap.height());
208                let ds = cx.draw_shared();
209
210                if let Some(im_size) = self.image.as_ref().and_then(|h| ds.image_size(h))
211                    && im_size != Size::conv(size)
212                    && let Some(handle) = self.image.take()
213                {
214                    ds.image_free(handle);
215                }
216
217                if self.image.is_none() {
218                    self.image = ds.image_alloc(size).ok();
219                }
220
221                if let Some(handle) = self.image.as_ref() {
222                    ds.image_upload(handle, pixmap.data(), ImageFormat::Rgba8);
223                }
224
225                cx.redraw(&self);
226
227                let rect_size: (u32, u32) = self.rect().size.cast();
228                let state = self.inner.get_mut();
229                if rect_size != size {
230                    // Possible if a redraw was in progress when set_rect was called
231
232                    pixmap = if let Some(px) = Pixmap::new(rect_size.0, rect_size.1) {
233                        px
234                    } else {
235                        *state = State::Initial(program);
236                        return;
237                    };
238                    cx.send_spawn(self.id(), draw(program, pixmap));
239                } else {
240                    *state = State::Ready(program, pixmap);
241                }
242            }
243        }
244    }
245}