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//! # use iced::widget::Widget;
7//! # pub type State = ();
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<Message>(_state: &State) -> impl Widget<Message> {
48//!     canvas(Circle { radius: 50.0 })
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::Meta;
68use crate::core::widget::tree::{self, Tree};
69use crate::core::window;
70use crate::core::{Length, Rectangle, Shell, Size, Vector, Widget};
71use crate::graphics::geometry;
72
73use std::marker::PhantomData;
74
75/// A simple cache that stores generated [`Geometry`] to avoid recomputation.
76///
77/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer
78/// change or it is explicitly cleared.
79pub type Cache<Renderer = crate::Renderer> = geometry::Cache<Renderer>;
80
81/// The geometry supported by a renderer.
82pub type Geometry<Renderer = crate::Renderer> = <Renderer as geometry::Renderer>::Geometry;
83
84/// The frame supported by a renderer.
85pub type Frame<Renderer = crate::Renderer> = geometry::Frame<Renderer>;
86
87/// A widget capable of drawing 2D graphics.
88///
89/// # Example: Drawing a Simple Circle
90/// ```no_run
91/// # mod iced { pub mod widget { pub use iced_widget::*; } pub use iced_widget::Renderer; pub use iced_widget::core::*; }
92/// # use iced::widget::Widget;
93/// # pub type State = ();
94/// #
95/// use iced::mouse;
96/// use iced::widget::canvas;
97/// use iced::{Color, Rectangle, Renderer, Theme};
98///
99/// // First, we define the data we need for drawing
100/// #[derive(Debug)]
101/// struct Circle {
102///     radius: f32,
103/// }
104///
105/// // Then, we implement the `Program` trait
106/// impl<Message> canvas::Program<Message> for Circle {
107///     // No internal state
108///     type State = ();
109///
110///     fn draw(
111///         &self,
112///         _state: &(),
113///         renderer: &Renderer,
114///         _theme: &Theme,
115///         bounds: Rectangle,
116///         _cursor: mouse::Cursor
117///     ) -> Vec<canvas::Geometry> {
118///         // We prepare a new `Frame`
119///         let mut frame = canvas::Frame::new(renderer, bounds.size());
120///
121///         // We create a `Path` representing a simple circle
122///         let circle = canvas::Path::circle(frame.center(), self.radius);
123///
124///         // And fill it with some color
125///         frame.fill(&circle, Color::BLACK);
126///
127///         // Then, we produce the geometry
128///         vec![frame.into_geometry()]
129///     }
130/// }
131///
132/// // Finally, we simply use our `Circle` to create the `Canvas`!
133/// fn view<Message>(_state: &State) -> impl Widget<Message> {
134///     canvas(Circle { radius: 50.0 })
135/// }
136/// ```
137#[derive(Debug)]
138pub struct Canvas<P, Message, Theme = crate::Theme, Renderer = crate::Renderer>
139where
140    Renderer: geometry::Renderer,
141    P: Program<Message, Theme, Renderer>,
142{
143    width: Length,
144    height: Length,
145    program: P,
146    message_: PhantomData<Message>,
147    theme_: PhantomData<Theme>,
148    renderer_: PhantomData<Renderer>,
149    last_mouse_interaction: Option<mouse::Interaction>,
150}
151
152impl<P, Message, Theme, Renderer> Canvas<P, Message, Theme, Renderer>
153where
154    P: Program<Message, Theme, Renderer>,
155    Renderer: geometry::Renderer,
156{
157    const DEFAULT_SIZE: f32 = 100.0;
158
159    /// Creates a new [`Canvas`].
160    pub fn new(program: P) -> Self {
161        Canvas {
162            width: Length::Fixed(Self::DEFAULT_SIZE),
163            height: Length::Fixed(Self::DEFAULT_SIZE),
164            program,
165            message_: PhantomData,
166            theme_: PhantomData,
167            renderer_: PhantomData,
168            last_mouse_interaction: None,
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> Meta for Canvas<P, Message, Theme, Renderer>
186where
187    Renderer: geometry::Renderer,
188    P: Program<Message, Theme, Renderer>,
189{
190}
191
192impl<P, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
193    for Canvas<P, Message, Theme, Renderer>
194where
195    Renderer: geometry::Renderer,
196    P: Program<Message, Theme, Renderer>,
197{
198    fn tag(&self) -> tree::Tag {
199        struct Tag<T>(T);
200        tree::Tag::of::<Tag<P::State>>()
201    }
202
203    fn state(&self) -> tree::State {
204        tree::State::new(P::State::default())
205    }
206
207    fn size(&self) -> Size<Length> {
208        Size {
209            width: self.width,
210            height: self.height,
211        }
212    }
213
214    fn layout(&mut self, tree: &mut Tree, _renderer: &Renderer, limits: &layout::Limits) {
215        tree.size = 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        shell: &mut Shell<'_, Message>,
226        viewport: &Rectangle,
227    ) {
228        let bounds = layout.bounds();
229
230        let state = tree.state.downcast_mut::<P::State>();
231        let is_redraw_request =
232            matches!(event, Event::Window(window::Event::RedrawRequested(_now)),);
233
234        if let Some(action) = self.program.update(state, event, bounds, cursor) {
235            let (message, redraw_request, event_status) = action.into_inner();
236
237            shell.request_redraw_at(redraw_request);
238
239            if let Some(message) = message {
240                shell.publish(message);
241            }
242
243            if event_status == event::Status::Captured {
244                shell.capture_event();
245            }
246        }
247
248        if shell.redraw_request() != window::RedrawRequest::NextFrame {
249            let mouse_interaction =
250                self.mouse_interaction(tree, layout, cursor, viewport, renderer);
251
252            if is_redraw_request {
253                self.last_mouse_interaction = Some(mouse_interaction);
254            } else if self
255                .last_mouse_interaction
256                .is_some_and(|last_mouse_interaction| last_mouse_interaction != mouse_interaction)
257            {
258                shell.request_redraw();
259            }
260        }
261    }
262
263    fn mouse_interaction(
264        &self,
265        tree: &Tree,
266        layout: Layout,
267        cursor: mouse::Cursor,
268        _viewport: &Rectangle,
269        _renderer: &Renderer,
270    ) -> mouse::Interaction {
271        let bounds = layout.bounds();
272        let state = tree.state.downcast_ref::<P::State>();
273
274        self.program.mouse_interaction(state, bounds, cursor)
275    }
276
277    fn draw(
278        &self,
279        tree: &Tree,
280        renderer: &mut Renderer,
281        theme: &Theme,
282        _style: &renderer::Style,
283        layout: Layout,
284        cursor: mouse::Cursor,
285        _viewport: &Rectangle,
286    ) {
287        let bounds = layout.bounds();
288
289        if bounds.width < 1.0 || bounds.height < 1.0 {
290            return;
291        }
292
293        let state = tree.state.downcast_ref::<P::State>();
294
295        renderer.with_translation(Vector::new(bounds.x, bounds.y), |renderer| {
296            let layers = self.program.draw(state, renderer, theme, bounds, cursor);
297
298            for layer in layers {
299                renderer.draw_geometry(layer);
300            }
301        });
302    }
303}