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