1mod program;
52
53pub use program::Program;
54
55pub use crate::core::event::Event;
56pub use crate::graphics::cache::Group;
57pub use crate::graphics::geometry::{
58 fill, gradient, path, stroke, Fill, Gradient, Image, LineCap, LineDash,
59 LineJoin, Path, Stroke, Style, Text,
60};
61pub use crate::Action;
62
63use crate::core::event;
64use crate::core::layout::{self, Layout};
65use crate::core::mouse;
66use crate::core::renderer;
67use crate::core::widget::tree::{self, Tree};
68use crate::core::window;
69use crate::core::{
70 Clipboard, Element, Length, Rectangle, Shell, Size, Vector, Widget,
71};
72use crate::graphics::geometry;
73
74use std::marker::PhantomData;
75
76pub type Cache<Renderer = crate::Renderer> = geometry::Cache<Renderer>;
81
82pub type Geometry<Renderer = crate::Renderer> =
84 <Renderer as geometry::Renderer>::Geometry;
85
86pub type Frame<Renderer = crate::Renderer> = geometry::Frame<Renderer>;
88
89#[derive(Debug)]
140pub struct Canvas<P, Message, Theme = crate::Theme, Renderer = crate::Renderer>
141where
142 Renderer: geometry::Renderer,
143 P: Program<Message, Theme, Renderer>,
144{
145 width: Length,
146 height: Length,
147 program: P,
148 message_: PhantomData<Message>,
149 theme_: PhantomData<Theme>,
150 renderer_: PhantomData<Renderer>,
151 last_mouse_interaction: Option<mouse::Interaction>,
152}
153
154impl<P, Message, Theme, Renderer> Canvas<P, Message, Theme, Renderer>
155where
156 P: Program<Message, Theme, Renderer>,
157 Renderer: geometry::Renderer,
158{
159 const DEFAULT_SIZE: f32 = 100.0;
160
161 pub fn new(program: P) -> Self {
163 Canvas {
164 width: Length::Fixed(Self::DEFAULT_SIZE),
165 height: Length::Fixed(Self::DEFAULT_SIZE),
166 program,
167 message_: PhantomData,
168 theme_: PhantomData,
169 renderer_: PhantomData,
170 last_mouse_interaction: None,
171 }
172 }
173
174 pub fn width(mut self, width: impl Into<Length>) -> Self {
176 self.width = width.into();
177 self
178 }
179
180 pub fn height(mut self, height: impl Into<Length>) -> Self {
182 self.height = height.into();
183 self
184 }
185}
186
187impl<P, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
188 for Canvas<P, Message, Theme, Renderer>
189where
190 Renderer: geometry::Renderer,
191 P: Program<Message, Theme, Renderer>,
192{
193 fn tag(&self) -> tree::Tag {
194 struct Tag<T>(T);
195 tree::Tag::of::<Tag<P::State>>()
196 }
197
198 fn state(&self) -> tree::State {
199 tree::State::new(P::State::default())
200 }
201
202 fn size(&self) -> Size<Length> {
203 Size {
204 width: self.width,
205 height: self.height,
206 }
207 }
208
209 fn layout(
210 &self,
211 _tree: &mut Tree,
212 _renderer: &Renderer,
213 limits: &layout::Limits,
214 ) -> layout::Node {
215 layout::atomic(limits, self.width, self.height)
216 }
217
218 fn update(
219 &mut self,
220 tree: &mut Tree,
221 event: &Event,
222 layout: Layout<'_>,
223 cursor: mouse::Cursor,
224 renderer: &Renderer,
225 _clipboard: &mut dyn Clipboard,
226 shell: &mut Shell<'_, Message>,
227 viewport: &Rectangle,
228 ) {
229 let bounds = layout.bounds();
230
231 let state = tree.state.downcast_mut::<P::State>();
232 let is_redraw_request = matches!(
233 event,
234 Event::Window(window::Event::RedrawRequested(_now)),
235 );
236
237 if let Some(action) = self.program.update(state, event, bounds, cursor)
238 {
239 let (message, redraw_request, event_status) = action.into_inner();
240
241 shell.request_redraw_at(redraw_request);
242
243 if let Some(message) = message {
244 shell.publish(message);
245 }
246
247 if event_status == event::Status::Captured {
248 shell.capture_event();
249 }
250 }
251
252 if shell.redraw_request() != window::RedrawRequest::NextFrame {
253 let mouse_interaction = self
254 .mouse_interaction(tree, layout, cursor, viewport, renderer);
255
256 if is_redraw_request {
257 self.last_mouse_interaction = Some(mouse_interaction);
258 } else if self.last_mouse_interaction.is_some_and(
259 |last_mouse_interaction| {
260 last_mouse_interaction != mouse_interaction
261 },
262 ) {
263 shell.request_redraw();
264 }
265 }
266 }
267
268 fn mouse_interaction(
269 &self,
270 tree: &Tree,
271 layout: Layout<'_>,
272 cursor: mouse::Cursor,
273 _viewport: &Rectangle,
274 _renderer: &Renderer,
275 ) -> mouse::Interaction {
276 let bounds = layout.bounds();
277 let state = tree.state.downcast_ref::<P::State>();
278
279 self.program.mouse_interaction(state, bounds, cursor)
280 }
281
282 fn draw(
283 &self,
284 tree: &Tree,
285 renderer: &mut Renderer,
286 theme: &Theme,
287 _style: &renderer::Style,
288 layout: Layout<'_>,
289 cursor: mouse::Cursor,
290 _viewport: &Rectangle,
291 ) {
292 let bounds = layout.bounds();
293
294 if bounds.width < 1.0 || bounds.height < 1.0 {
295 return;
296 }
297
298 let state = tree.state.downcast_ref::<P::State>();
299
300 renderer.with_translation(
301 Vector::new(bounds.x, bounds.y),
302 |renderer| {
303 let layers =
304 self.program.draw(state, renderer, theme, bounds, cursor);
305
306 for layer in layers {
307 renderer.draw_geometry(layer);
308 }
309 },
310 );
311 }
312}
313
314impl<'a, P, Message, Theme, Renderer> From<Canvas<P, Message, Theme, Renderer>>
315 for Element<'a, Message, Theme, Renderer>
316where
317 Message: 'a,
318 Theme: 'a,
319 Renderer: 'a + geometry::Renderer,
320 P: 'a + Program<Message, Theme, Renderer>,
321{
322 fn from(
323 canvas: Canvas<P, Message, Theme, Renderer>,
324 ) -> Element<'a, Message, Theme, Renderer> {
325 Element::new(canvas)
326 }
327}