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//! ```
51pub mod event;
52
53mod program;
54
55pub use event::Event;
56pub use program::Program;
57
58pub use crate::graphics::cache::Group;
59pub use crate::graphics::geometry::{
60    fill, gradient, path, stroke, Fill, Gradient, Image, LineCap, LineDash,
61    LineJoin, Path, Stroke, Style, Text,
62};
63
64use crate::core;
65use crate::core::layout::{self, Layout};
66use crate::core::mouse;
67use crate::core::renderer;
68use crate::core::widget::tree::{self, Tree};
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}
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        }
170    }
171
172    /// Sets the width of the [`Canvas`].
173    pub fn width(mut self, width: impl Into<Length>) -> Self {
174        self.width = width.into();
175        self
176    }
177
178    /// Sets the height of the [`Canvas`].
179    pub fn height(mut self, height: impl Into<Length>) -> Self {
180        self.height = height.into();
181        self
182    }
183}
184
185impl<P, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
186    for Canvas<P, Message, Theme, Renderer>
187where
188    Renderer: geometry::Renderer,
189    P: Program<Message, Theme, Renderer>,
190{
191    fn tag(&self) -> tree::Tag {
192        struct Tag<T>(T);
193        tree::Tag::of::<Tag<P::State>>()
194    }
195
196    fn state(&self) -> tree::State {
197        tree::State::new(P::State::default())
198    }
199
200    fn size(&self) -> Size<Length> {
201        Size {
202            width: self.width,
203            height: self.height,
204        }
205    }
206
207    fn layout(
208        &self,
209        _tree: &mut Tree,
210        _renderer: &Renderer,
211        limits: &layout::Limits,
212    ) -> layout::Node {
213        layout::atomic(limits, self.width, self.height)
214    }
215
216    fn on_event(
217        &mut self,
218        tree: &mut Tree,
219        event: core::Event,
220        layout: Layout<'_>,
221        cursor: mouse::Cursor,
222        _renderer: &Renderer,
223        _clipboard: &mut dyn Clipboard,
224        shell: &mut Shell<'_, Message>,
225        _viewport: &Rectangle,
226    ) -> event::Status {
227        let bounds = layout.bounds();
228
229        let canvas_event = match event {
230            core::Event::Mouse(mouse_event) => Some(Event::Mouse(mouse_event)),
231            core::Event::Touch(touch_event) => Some(Event::Touch(touch_event)),
232            core::Event::Keyboard(keyboard_event) => {
233                Some(Event::Keyboard(keyboard_event))
234            }
235            core::Event::Window(_) => None,
236            _ => None,
237        };
238
239        if let Some(canvas_event) = canvas_event {
240            let state = tree.state.downcast_mut::<P::State>();
241
242            let (event_status, message) =
243                self.program.update(state, canvas_event, bounds, cursor);
244
245            if let Some(message) = message {
246                shell.publish(message);
247            }
248
249            return event_status;
250        }
251
252        event::Status::Ignored
253    }
254
255    fn mouse_interaction(
256        &self,
257        tree: &Tree,
258        layout: Layout<'_>,
259        cursor: mouse::Cursor,
260        _viewport: &Rectangle,
261        _renderer: &Renderer,
262    ) -> mouse::Interaction {
263        let bounds = layout.bounds();
264        let state = tree.state.downcast_ref::<P::State>();
265
266        self.program.mouse_interaction(state, bounds, cursor)
267    }
268
269    fn draw(
270        &self,
271        tree: &Tree,
272        renderer: &mut Renderer,
273        theme: &Theme,
274        _style: &renderer::Style,
275        layout: Layout<'_>,
276        cursor: mouse::Cursor,
277        _viewport: &Rectangle,
278    ) {
279        let bounds = layout.bounds();
280
281        if bounds.width < 1.0 || bounds.height < 1.0 {
282            return;
283        }
284
285        let state = tree.state.downcast_ref::<P::State>();
286
287        renderer.with_translation(
288            Vector::new(bounds.x, bounds.y),
289            |renderer| {
290                let layers =
291                    self.program.draw(state, renderer, theme, bounds, cursor);
292
293                for layer in layers {
294                    renderer.draw_geometry(layer);
295                }
296            },
297        );
298    }
299}
300
301impl<'a, P, Message, Theme, Renderer> From<Canvas<P, Message, Theme, Renderer>>
302    for Element<'a, Message, Theme, Renderer>
303where
304    Message: 'a,
305    Theme: 'a,
306    Renderer: 'a + geometry::Renderer,
307    P: 'a + Program<Message, Theme, Renderer>,
308{
309    fn from(
310        canvas: Canvas<P, Message, Theme, Renderer>,
311    ) -> Element<'a, Message, Theme, Renderer> {
312        Element::new(canvas)
313    }
314}