1use 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
15pub trait CanvasProgram: std::fmt::Debug + Send + 'static {
20 fn draw(&self, pixmap: &mut Pixmap);
28
29 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 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 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 #[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 #[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 #[inline]
133 #[must_use]
134 pub fn with_size(mut self, size: LogicalSize) -> Self {
135 self.scaling.size = size;
136 self
137 }
138
139 #[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 #[inline]
155 pub fn set_scaling(&mut self, cx: &mut EventState, f: impl FnOnce(&mut PixmapScaling)) {
156 f(&mut self.scaling);
157 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 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}