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