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