Skip to main content

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::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
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> = <Renderer as geometry::Renderer>::Geometry;
84
85/// The frame supported by a renderer.
86pub type Frame<Renderer = crate::Renderer> = geometry::Frame<Renderer>;
87
88/// A widget capable of drawing 2D graphics.
89///
90/// # Example: Drawing a Simple Circle
91/// ```no_run
92/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; }
93/// # pub type State = ();
94/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>;
95/// #
96/// use iced::mouse;
97/// use iced::widget::canvas;
98/// use iced::{Color, Rectangle, Renderer, Theme};
99///
100/// // First, we define the data we need for drawing
101/// #[derive(Debug)]
102/// struct Circle {
103///     radius: f32,
104/// }
105///
106/// // Then, we implement the `Program` trait
107/// impl<Message> canvas::Program<Message> for Circle {
108///     // No internal state
109///     type State = ();
110///
111///     fn draw(
112///         &self,
113///         _state: &(),
114///         renderer: &Renderer,
115///         _theme: &Theme,
116///         bounds: Rectangle,
117///         _cursor: mouse::Cursor
118///     ) -> Vec<canvas::Geometry> {
119///         // We prepare a new `Frame`
120///         let mut frame = canvas::Frame::new(renderer, bounds.size());
121///
122///         // We create a `Path` representing a simple circle
123///         let circle = canvas::Path::circle(frame.center(), self.radius);
124///
125///         // And fill it with some color
126///         frame.fill(&circle, Color::BLACK);
127///
128///         // Then, we produce the geometry
129///         vec![frame.into_geometry()]
130///     }
131/// }
132///
133/// // Finally, we simply use our `Circle` to create the `Canvas`!
134/// fn view<'a, Message: 'a>(_state: &'a State) -> Element<'a, Message> {
135///     canvas(Circle { radius: 50.0 }).into()
136/// }
137/// ```
138#[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    /// Creates a new [`Canvas`].
161    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    /// Sets the width of the [`Canvas`].
174    pub fn width(mut self, width: impl Into<Length>) -> Self {
175        self.width = width.into();
176        self
177    }
178
179    /// Sets the height of the [`Canvas`].
180    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}