iced_widget/
canvas.rs

1//! Canvases can be leveraged to draw interactive 2D graphics.
2//!
3//! # Example: Drawing a Simple Circle
4//! ```no_run
5//! # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; }
6//! # pub type State = ();
7//! # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
8//! #
9//! use iced::mouse;
10//! use iced::widget::canvas;
11//! use iced::{Color, Rectangle, Renderer, Theme};
12//!
13//! // First, we define the data we need for drawing
14//! #[derive(Debug)]
15//! struct Circle {
16//!     radius: f32,
17//! }
18//!
19//! // Then, we implement the `Program` trait
20//! impl<Message> canvas::Program<Message> for Circle {
21//!     // No internal state
22//!     type State = ();
23//!
24//!     fn draw(
25//!         &self,
26//!         _state: &(),
27//!         renderer: &Renderer,
28//!         _theme: &Theme,
29//!         bounds: Rectangle,
30//!         _cursor: mouse::Cursor
31//!     ) -> Vec<canvas::Geometry> {
32//!         // We prepare a new `Frame`
33//!         let mut frame = canvas::Frame::new(renderer, bounds.size());
34//!
35//!         // We create a `Path` representing a simple circle
36//!         let circle = canvas::Path::circle(frame.center(), self.radius);
37//!
38//!         // And fill it with some color
39//!         frame.fill(&circle, Color::BLACK);
40//!
41//!         // Then, we produce the geometry
42//!         vec![frame.into_geometry()]
43//!     }
44//! }
45//!
46//! // Finally, we simply use our `Circle` to create the `Canvas`!
47//! fn view<'a, Message: 'a>(_state: &'a State) -> Element<'a, Message> {
48//!     canvas(Circle { radius: 50.0 }).into()
49//! }
50//! ```
51mod 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
76/// A simple cache that stores generated [`Geometry`] to avoid recomputation.
77///
78/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
79/// change or it is explicitly cleared.
80pub type Cache<Renderer = crate::Renderer> = geometry::Cache<Renderer>;
81
82/// The geometry supported by a renderer.
83pub type Geometry<Renderer = crate::Renderer> =
84    <Renderer as geometry::Renderer>::Geometry;
85
86/// The frame supported by a renderer.
87pub type Frame<Renderer = crate::Renderer> = geometry::Frame<Renderer>;
88
89/// A widget capable of drawing 2D graphics.
90///
91/// # Example: Drawing a Simple Circle
92/// ```no_run
93/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; }
94/// # pub type State = ();
95/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
96/// #
97/// use iced::mouse;
98/// use iced::widget::canvas;
99/// use iced::{Color, Rectangle, Renderer, Theme};
100///
101/// // First, we define the data we need for drawing
102/// #[derive(Debug)]
103/// struct Circle {
104///     radius: f32,
105/// }
106///
107/// // Then, we implement the `Program` trait
108/// impl<Message> canvas::Program<Message> for Circle {
109///     // No internal state
110///     type State = ();
111///
112///     fn draw(
113///         &self,
114///         _state: &(),
115///         renderer: &Renderer,
116///         _theme: &Theme,
117///         bounds: Rectangle,
118///         _cursor: mouse::Cursor
119///     ) -> Vec<canvas::Geometry> {
120///         // We prepare a new `Frame`
121///         let mut frame = canvas::Frame::new(renderer, bounds.size());
122///
123///         // We create a `Path` representing a simple circle
124///         let circle = canvas::Path::circle(frame.center(), self.radius);
125///
126///         // And fill it with some color
127///         frame.fill(&circle, Color::BLACK);
128///
129///         // Then, we produce the geometry
130///         vec![frame.into_geometry()]
131///     }
132/// }
133///
134/// // Finally, we simply use our `Circle` to create the `Canvas`!
135/// fn view<'a, Message: 'a>(_state: &'a State) -> Element<'a, Message> {
136///     canvas(Circle { radius: 50.0 }).into()
137/// }
138/// ```
139#[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    /// Creates a new [`Canvas`].
162    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    /// Sets the width of the [`Canvas`].
175    pub fn width(mut self, width: impl Into<Length>) -> Self {
176        self.width = width.into();
177        self
178    }
179
180    /// Sets the height of the [`Canvas`].
181    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}