diff options
56 files changed, 906 insertions, 835 deletions
diff --git a/native/src/input/button_state.rs b/core/src/button_state.rs index 988043ba..988043ba 100644 --- a/native/src/input/button_state.rs +++ b/core/src/button_state.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index c2887a0b..f0072f61 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -15,12 +15,15 @@  #![forbid(unsafe_code)]  #![forbid(rust_2018_idioms)]  pub mod keyboard; +pub mod mouse;  mod align;  mod background; +mod button_state;  mod color;  mod font;  mod length; +mod mouse_cursor;  mod point;  mod rectangle;  mod size; @@ -28,9 +31,11 @@ mod vector;  pub use align::{Align, HorizontalAlignment, VerticalAlignment};  pub use background::Background; +pub use button_state::ButtonState;  pub use color::Color;  pub use font::Font;  pub use length::Length; +pub use mouse_cursor::MouseCursor;  pub use point::Point;  pub use rectangle::Rectangle;  pub use size::Size; diff --git a/core/src/mouse.rs b/core/src/mouse.rs new file mode 100644 index 00000000..101e04d5 --- /dev/null +++ b/core/src/mouse.rs @@ -0,0 +1,6 @@ +//! Reuse basic mouse types. +mod button; +mod event; + +pub use button::Button; +pub use event::{Event, ScrollDelta}; diff --git a/native/src/input/mouse/button.rs b/core/src/mouse/button.rs index aeb8a55d..aeb8a55d 100644 --- a/native/src/input/mouse/button.rs +++ b/core/src/mouse/button.rs diff --git a/native/src/input/mouse/event.rs b/core/src/mouse/event.rs index aafc4fe3..52e9d851 100644 --- a/native/src/input/mouse/event.rs +++ b/core/src/mouse/event.rs @@ -1,5 +1,5 @@  use super::Button; -use crate::input::ButtonState; +use crate::ButtonState;  /// A mouse event.  /// @@ -26,11 +26,11 @@ pub enum Event {      /// A mouse button was pressed or released.      Input { -        /// The state of the button -        state: ButtonState, -          /// The button identifier          button: Button, + +        /// The state of the button +        state: ButtonState,      },      /// The mouse wheel was scrolled. diff --git a/native/src/mouse_cursor.rs b/core/src/mouse_cursor.rs index 0dad3edc..78ddb0ae 100644 --- a/native/src/mouse_cursor.rs +++ b/core/src/mouse_cursor.rs @@ -1,9 +1,6 @@  /// The state of the mouse cursor.  #[derive(Debug, Eq, PartialEq, Clone, Copy, PartialOrd, Ord)]  pub enum MouseCursor { -    /// The cursor is out of the bounds of the user interface. -    OutOfBounds, -      /// The cursor is over a non-interactive widget.      Idle, @@ -16,12 +13,15 @@ pub enum MouseCursor {      /// The cursor is over a grabbable widget.      Grab, -    /// The cursor is grabbing a widget. -    Grabbing, -      /// The cursor is over a text widget.      Text, +    /// The cursor is over a widget that requires precision. +    Crosshair, + +    /// The cursor is grabbing a widget. +    Grabbing, +      /// The cursor is resizing a widget horizontally.      ResizingHorizontally, @@ -31,6 +31,6 @@ pub enum MouseCursor {  impl Default for MouseCursor {      fn default() -> MouseCursor { -        MouseCursor::OutOfBounds +        MouseCursor::Idle      }  } diff --git a/core/src/point.rs b/core/src/point.rs index 2b5ad154..3714aa2f 100644 --- a/core/src/point.rs +++ b/core/src/point.rs @@ -1,7 +1,7 @@  use crate::Vector;  /// A 2D point. -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Default)]  pub struct Point {      /// The X coordinate.      pub x: f32, @@ -67,3 +67,11 @@ impl std::ops::Sub<Vector> for Point {          }      }  } + +impl std::ops::Sub<Point> for Point { +    type Output = Vector; + +    fn sub(self, point: Point) -> Vector { +        Vector::new(self.x - point.x, self.y - point.y) +    } +} diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index aead6e9a..6f953137 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -1,4 +1,4 @@ -use crate::Point; +use crate::{Point, Size, Vector};  /// A rectangle.  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] @@ -17,6 +17,35 @@ pub struct Rectangle<T = f32> {  }  impl Rectangle<f32> { +    /// Creates a new [`Rectangle`] with its top-left corner in the given +    /// [`Point`] and with the provided [`Size`]. +    /// +    /// [`Rectangle`]: struct.Rectangle.html +    /// [`Point`]: struct.Point.html +    /// [`Size`]: struct.Size.html +    pub fn new(top_left: Point, size: Size) -> Self { +        Self { +            x: top_left.x, +            y: top_left.y, +            width: size.width, +            height: size.height, +        } +    } + +    /// Creates a new [`Rectangle`] with its top-left corner at the origin +    /// and with the provided [`Size`]. +    /// +    /// [`Rectangle`]: struct.Rectangle.html +    /// [`Size`]: struct.Size.html +    pub fn with_size(size: Size) -> Self { +        Self { +            x: 0.0, +            y: 0.0, +            width: size.width, +            height: size.height, +        } +    } +      /// Returns the [`Point`] at the center of the [`Rectangle`].      ///      /// [`Point`]: struct.Point.html @@ -43,6 +72,14 @@ impl Rectangle<f32> {          self.y + self.height / 2.0      } +    /// Returns the [`Size`] of the [`Rectangle`]. +    /// +    /// [`Size`]: struct.Size.html +    /// [`Rectangle`]: struct.Rectangle.html +    pub fn size(&self) -> Size { +        Size::new(self.width, self.height) +    } +      /// Returns true if the given [`Point`] is contained in the [`Rectangle`].      ///      /// [`Point`]: struct.Point.html @@ -112,8 +149,23 @@ impl From<Rectangle<f32>> for Rectangle<u32> {          Rectangle {              x: rectangle.x as u32,              y: rectangle.y as u32, -            width: rectangle.width.ceil() as u32, -            height: rectangle.height.ceil() as u32, +            width: (rectangle.width + 0.5).round() as u32, +            height: (rectangle.height + 0.5).round() as u32, +        } +    } +} + +impl<T> std::ops::Add<Vector<T>> for Rectangle<T> +where +    T: std::ops::Add<Output = T>, +{ +    type Output = Rectangle<T>; + +    fn add(self, translation: Vector<T>) -> Self { +        Rectangle { +            x: self.x + translation.x, +            y: self.y + translation.y, +            ..self          }      }  } diff --git a/examples/README.md b/examples/README.md index 5aea51eb..5d880d71 100644 --- a/examples/README.md +++ b/examples/README.md @@ -69,7 +69,7 @@ cargo run --package styling  ## Extras  A bunch of simpler examples exist: -- [`bezier_tool`](bezier_tool), a Paint-like tool for drawing Bézier curves using [`lyon`]. +- [`bezier_tool`](bezier_tool), a Paint-like tool for drawing Bézier curves using the `Canvas` widget.  - [`clock`](clock), an application that uses the `Canvas` widget to draw a clock and its hands to display the current time.  - [`counter`](counter), the classic counter example explained in the [`README`](../README.md).  - [`custom_widget`](custom_widget), a demonstration of how to build a custom widget that draws a circle. diff --git a/examples/bezier_tool/Cargo.toml b/examples/bezier_tool/Cargo.toml index b13a0aa5..a88975a7 100644 --- a/examples/bezier_tool/Cargo.toml +++ b/examples/bezier_tool/Cargo.toml @@ -6,7 +6,4 @@ edition = "2018"  publish = false  [dependencies] -iced = { path = "../.." } -iced_native = { path = "../../native" } -iced_wgpu = { path = "../../wgpu" } -lyon = "0.15" +iced = { path = "../..", features = ["canvas"] } diff --git a/examples/bezier_tool/README.md b/examples/bezier_tool/README.md index 933f2120..ebbb12cc 100644 --- a/examples/bezier_tool/README.md +++ b/examples/bezier_tool/README.md @@ -1,6 +1,6 @@  ## Bézier tool -A Paint-like tool for drawing Bézier curves using [`lyon`]. +A Paint-like tool for drawing Bézier curves using the `Canvas` widget.  The __[`main`]__ file contains all the code of the example. @@ -16,4 +16,3 @@ cargo run --package bezier_tool  ```  [`main`]: src/main.rs -[`lyon`]: https://github.com/nical/lyon diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index fcb7733c..6473db75 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -1,291 +1,6 @@ -//! This example showcases a simple native custom widget that renders arbitrary -//! path with `lyon`. -mod bezier { -    // For now, to implement a custom native widget you will need to add -    // `iced_native` and `iced_wgpu` to your dependencies. -    // -    // Then, you simply need to define your widget type and implement the -    // `iced_native::Widget` trait with the `iced_wgpu::Renderer`. -    // -    // Of course, you can choose to make the implementation renderer-agnostic, -    // if you wish to, by creating your own `Renderer` trait, which could be -    // implemented by `iced_wgpu` and other renderers. -    use iced_native::{ -        input, layout, Clipboard, Color, Element, Event, Font, Hasher, -        HorizontalAlignment, Layout, Length, MouseCursor, Point, Rectangle, -        Size, Vector, VerticalAlignment, Widget, -    }; -    use iced_wgpu::{ -        triangle::{Mesh2D, Vertex2D}, -        Defaults, Primitive, Renderer, -    }; -    use lyon::tessellation::{ -        basic_shapes, BuffersBuilder, StrokeAttributes, StrokeOptions, -        StrokeTessellator, VertexBuffers, -    }; - -    pub struct Bezier<'a, Message> { -        state: &'a mut State, -        curves: &'a [Curve], -        // [from, to, ctrl] -        on_click: Box<dyn Fn(Curve) -> Message>, -    } - -    #[derive(Debug, Clone, Copy)] -    pub struct Curve { -        from: Point, -        to: Point, -        control: Point, -    } - -    #[derive(Default)] -    pub struct State { -        pending: Option<Pending>, -    } - -    enum Pending { -        One { from: Point }, -        Two { from: Point, to: Point }, -    } - -    impl<'a, Message> Bezier<'a, Message> { -        pub fn new<F>( -            state: &'a mut State, -            curves: &'a [Curve], -            on_click: F, -        ) -> Self -        where -            F: 'static + Fn(Curve) -> Message, -        { -            Self { -                state, -                curves, -                on_click: Box::new(on_click), -            } -        } -    } - -    impl<'a, Message> Widget<Message, Renderer> for Bezier<'a, Message> { -        fn width(&self) -> Length { -            Length::Fill -        } - -        fn height(&self) -> Length { -            Length::Fill -        } - -        fn layout( -            &self, -            _renderer: &Renderer, -            limits: &layout::Limits, -        ) -> layout::Node { -            let size = limits -                .height(Length::Fill) -                .width(Length::Fill) -                .resolve(Size::ZERO); -            layout::Node::new(size) -        } - -        fn draw( -            &self, -            _renderer: &mut Renderer, -            defaults: &Defaults, -            layout: Layout<'_>, -            cursor_position: Point, -        ) -> (Primitive, MouseCursor) { -            let mut buffer: VertexBuffers<Vertex2D, u32> = VertexBuffers::new(); -            let mut path_builder = lyon::path::Path::builder(); - -            let bounds = layout.bounds(); - -            // Draw rectangle border with lyon. -            basic_shapes::stroke_rectangle( -                &lyon::math::Rect::new( -                    lyon::math::Point::new(0.5, 0.5), -                    lyon::math::Size::new( -                        bounds.width - 1.0, -                        bounds.height - 1.0, -                    ), -                ), -                &StrokeOptions::default().with_line_width(1.0), -                &mut BuffersBuilder::new( -                    &mut buffer, -                    |pos: lyon::math::Point, _: StrokeAttributes| Vertex2D { -                        position: pos.to_array(), -                        color: [0.0, 0.0, 0.0, 1.0], -                    }, -                ), -            ) -            .unwrap(); - -            for curve in self.curves { -                path_builder.move_to(lyon::math::Point::new( -                    curve.from.x, -                    curve.from.y, -                )); - -                path_builder.quadratic_bezier_to( -                    lyon::math::Point::new(curve.control.x, curve.control.y), -                    lyon::math::Point::new(curve.to.x, curve.to.y), -                ); -            } - -            match self.state.pending { -                None => {} -                Some(Pending::One { from }) => { -                    path_builder -                        .move_to(lyon::math::Point::new(from.x, from.y)); -                    path_builder.line_to(lyon::math::Point::new( -                        cursor_position.x - bounds.x, -                        cursor_position.y - bounds.y, -                    )); -                } -                Some(Pending::Two { from, to }) => { -                    path_builder -                        .move_to(lyon::math::Point::new(from.x, from.y)); -                    path_builder.quadratic_bezier_to( -                        lyon::math::Point::new( -                            cursor_position.x - bounds.x, -                            cursor_position.y - bounds.y, -                        ), -                        lyon::math::Point::new(to.x, to.y), -                    ); -                } -            } - -            let mut tessellator = StrokeTessellator::new(); - -            // Draw strokes with lyon. -            tessellator -                .tessellate( -                    &path_builder.build(), -                    &StrokeOptions::default().with_line_width(3.0), -                    &mut BuffersBuilder::new( -                        &mut buffer, -                        |pos: lyon::math::Point, _: StrokeAttributes| { -                            Vertex2D { -                                position: pos.to_array(), -                                color: [0.0, 0.0, 0.0, 1.0], -                            } -                        }, -                    ), -                ) -                .unwrap(); - -            let mesh = Primitive::Mesh2D { -                origin: Point::new(bounds.x, bounds.y), -                buffers: Mesh2D { -                    vertices: buffer.vertices, -                    indices: buffer.indices, -                }, -            }; - -            ( -                Primitive::Clip { -                    bounds, -                    offset: Vector::new(0, 0), -                    content: Box::new( -                        if self.curves.is_empty() -                            && self.state.pending.is_none() -                        { -                            let instructions = Primitive::Text { -                                bounds: Rectangle { -                                    x: bounds.center_x(), -                                    y: bounds.center_y(), -                                    ..bounds -                                }, -                                color: Color { -                                    a: defaults.text.color.a * 0.7, -                                    ..defaults.text.color -                                }, -                                content: String::from( -                                    "Click to create bezier curves!", -                                ), -                                font: Font::Default, -                                size: 30.0, -                                horizontal_alignment: -                                    HorizontalAlignment::Center, -                                vertical_alignment: VerticalAlignment::Center, -                            }; - -                            Primitive::Group { -                                primitives: vec![mesh, instructions], -                            } -                        } else { -                            mesh -                        }, -                    ), -                }, -                MouseCursor::OutOfBounds, -            ) -        } - -        fn hash_layout(&self, _state: &mut Hasher) {} - -        fn on_event( -            &mut self, -            event: Event, -            layout: Layout<'_>, -            cursor_position: Point, -            messages: &mut Vec<Message>, -            _renderer: &Renderer, -            _clipboard: Option<&dyn Clipboard>, -        ) { -            let bounds = layout.bounds(); - -            if bounds.contains(cursor_position) { -                match event { -                    Event::Mouse(input::mouse::Event::Input { -                        state: input::ButtonState::Pressed, -                        .. -                    }) => { -                        let new_point = Point::new( -                            cursor_position.x - bounds.x, -                            cursor_position.y - bounds.y, -                        ); - -                        match self.state.pending { -                            None => { -                                self.state.pending = -                                    Some(Pending::One { from: new_point }); -                            } -                            Some(Pending::One { from }) => { -                                self.state.pending = Some(Pending::Two { -                                    from, -                                    to: new_point, -                                }); -                            } -                            Some(Pending::Two { from, to }) => { -                                self.state.pending = None; - -                                messages.push((self.on_click)(Curve { -                                    from, -                                    to, -                                    control: new_point, -                                })); -                            } -                        } -                    } -                    _ => {} -                } -            } -        } -    } - -    impl<'a, Message> Into<Element<'a, Message, Renderer>> for Bezier<'a, Message> -    where -        Message: 'static, -    { -        fn into(self) -> Element<'a, Message, Renderer> { -            Element::new(self) -        } -    } -} - -use bezier::Bezier; +//! This example showcases an interactive `Canvas` for drawing Bézier curves.  use iced::{ -    button, Align, Button, Column, Container, Element, Length, Sandbox, -    Settings, Text, +    button, Align, Button, Column, Element, Length, Sandbox, Settings, Text,  };  pub fn main() { @@ -323,6 +38,7 @@ impl Sandbox for Example {          match message {              Message::AddCurve(curve) => {                  self.curves.push(curve); +                self.bezier.request_redraw();              }              Message::Clear => {                  self.bezier = bezier::State::default(); @@ -332,7 +48,7 @@ impl Sandbox for Example {      }      fn view(&mut self) -> Element<Message> { -        let content = Column::new() +        Column::new()              .padding(20)              .spacing(20)              .align_items(Align::Center) @@ -341,22 +57,178 @@ impl Sandbox for Example {                      .width(Length::Shrink)                      .size(50),              ) -            .push(Bezier::new( -                &mut self.bezier, -                self.curves.as_slice(), -                Message::AddCurve, -            )) +            .push(self.bezier.view(&self.curves).map(Message::AddCurve))              .push(                  Button::new(&mut self.button_state, Text::new("Clear"))                      .padding(8)                      .on_press(Message::Clear), -            ); +            ) +            .into() +    } +} -        Container::new(content) +mod bezier { +    use iced::{ +        canvas::{self, Canvas, Cursor, Event, Frame, Geometry, Path, Stroke}, +        mouse, ButtonState, Element, Length, MouseCursor, Point, Rectangle, +    }; + +    #[derive(Default)] +    pub struct State { +        pending: Option<Pending>, +        cache: canvas::Cache, +    } + +    impl State { +        pub fn view<'a>( +            &'a mut self, +            curves: &'a [Curve], +        ) -> Element<'a, Curve> { +            Canvas::new(Bezier { +                state: self, +                curves, +            })              .width(Length::Fill)              .height(Length::Fill) -            .center_x() -            .center_y()              .into() +        } + +        pub fn request_redraw(&mut self) { +            self.cache.clear() +        } +    } + +    struct Bezier<'a> { +        state: &'a mut State, +        curves: &'a [Curve], +    } + +    impl<'a> canvas::Program<Curve> for Bezier<'a> { +        fn update( +            &mut self, +            event: Event, +            bounds: Rectangle, +            cursor: Cursor, +        ) -> Option<Curve> { +            let cursor_position = cursor.internal_position(&bounds)?; + +            match event { +                Event::Mouse(mouse_event) => match mouse_event { +                    mouse::Event::Input { +                        button: mouse::Button::Left, +                        state: ButtonState::Pressed, +                    } => match self.state.pending { +                        None => { +                            self.state.pending = Some(Pending::One { +                                from: cursor_position, +                            }); +                            None +                        } +                        Some(Pending::One { from }) => { +                            self.state.pending = Some(Pending::Two { +                                from, +                                to: cursor_position, +                            }); + +                            None +                        } +                        Some(Pending::Two { from, to }) => { +                            self.state.pending = None; + +                            Some(Curve { +                                from, +                                to, +                                control: cursor_position, +                            }) +                        } +                    }, +                    _ => None, +                }, +            } +        } + +        fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry> { +            let content = +                self.state.cache.draw(bounds.size(), |frame: &mut Frame| { +                    Curve::draw_all(self.curves, frame); + +                    frame.stroke( +                        &Path::rectangle(Point::ORIGIN, frame.size()), +                        Stroke::default(), +                    ); +                }); + +            if let Some(pending) = &self.state.pending { +                let pending_curve = pending.draw(bounds, cursor); + +                vec![content, pending_curve] +            } else { +                vec![content] +            } +        } + +        fn mouse_cursor( +            &self, +            bounds: Rectangle, +            cursor: Cursor, +        ) -> MouseCursor { +            if cursor.is_over(&bounds) { +                MouseCursor::Crosshair +            } else { +                MouseCursor::default() +            } +        } +    } + +    #[derive(Debug, Clone, Copy)] +    pub struct Curve { +        from: Point, +        to: Point, +        control: Point, +    } + +    impl Curve { +        fn draw_all(curves: &[Curve], frame: &mut Frame) { +            let curves = Path::new(|p| { +                for curve in curves { +                    p.move_to(curve.from); +                    p.quadratic_curve_to(curve.control, curve.to); +                } +            }); + +            frame.stroke(&curves, Stroke::default().with_width(2.0)); +        } +    } + +    #[derive(Debug, Clone, Copy)] +    enum Pending { +        One { from: Point }, +        Two { from: Point, to: Point }, +    } + +    impl Pending { +        fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Geometry { +            let mut frame = Frame::new(bounds.size()); + +            if let Some(cursor_position) = cursor.internal_position(&bounds) { +                match *self { +                    Pending::One { from } => { +                        let line = Path::line(from, cursor_position); +                        frame.stroke(&line, Stroke::default().with_width(2.0)); +                    } +                    Pending::Two { from, to } => { +                        let curve = Curve { +                            from, +                            to, +                            control: cursor_position, +                        }; + +                        Curve::draw_all(&[curve], &mut frame); +                    } +                }; +            } + +            frame.into_geometry() +        }      }  } diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml index 308cbfbb..ab771405 100644 --- a/examples/clock/Cargo.toml +++ b/examples/clock/Cargo.toml @@ -5,9 +5,6 @@ authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]  edition = "2018"  publish = false -[features] -canvas = [] -  [dependencies]  iced = { path = "../..", features = ["canvas", "async-std", "debug"] }  iced_native = { path = "../../native" } diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 827379fa..e6b17d8a 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,6 +1,7 @@  use iced::{ -    canvas, executor, Application, Canvas, Color, Command, Container, Element, -    Length, Point, Settings, Subscription, Vector, +    canvas::{self, Cache, Canvas, Cursor, Geometry, LineCap, Path, Stroke}, +    executor, Application, Color, Command, Container, Element, Length, Point, +    Rectangle, Settings, Subscription, Vector,  };  pub fn main() { @@ -11,8 +12,8 @@ pub fn main() {  }  struct Clock { -    now: LocalTime, -    clock: canvas::layer::Cache<LocalTime>, +    now: chrono::DateTime<chrono::Local>, +    clock: Cache,  }  #[derive(Debug, Clone, Copy)] @@ -28,7 +29,7 @@ impl Application for Clock {      fn new(_flags: ()) -> (Self, Command<Message>) {          (              Clock { -                now: chrono::Local::now().into(), +                now: chrono::Local::now(),                  clock: Default::default(),              },              Command::none(), @@ -59,10 +60,9 @@ impl Application for Clock {      }      fn view(&mut self) -> Element<Message> { -        let canvas = Canvas::new() +        let canvas = Canvas::new(self)              .width(Length::Units(400)) -            .height(Length::Units(400)) -            .push(self.clock.with(&self.now)); +            .height(Length::Units(400));          Container::new(canvas)              .width(Length::Fill) @@ -74,69 +74,54 @@ impl Application for Clock {      }  } -#[derive(Debug, PartialEq, Eq)] -struct LocalTime { -    hour: u32, -    minute: u32, -    second: u32, -} - -impl From<chrono::DateTime<chrono::Local>> for LocalTime { -    fn from(date_time: chrono::DateTime<chrono::Local>) -> LocalTime { +impl canvas::Program<Message> for Clock { +    fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry> {          use chrono::Timelike; -        LocalTime { -            hour: date_time.hour(), -            minute: date_time.minute(), -            second: date_time.second(), -        } -    } -} - -impl canvas::Drawable for LocalTime { -    fn draw(&self, frame: &mut canvas::Frame) { -        use canvas::Path; +        let clock = self.clock.draw(bounds.size(), |frame| { +            let center = frame.center(); +            let radius = frame.width().min(frame.height()) / 2.0; -        let center = frame.center(); -        let radius = frame.width().min(frame.height()) / 2.0; +            let background = Path::circle(center, radius); +            frame.fill(&background, Color::from_rgb8(0x12, 0x93, 0xD8)); -        let clock = Path::circle(center, radius); -        frame.fill(&clock, Color::from_rgb8(0x12, 0x93, 0xD8)); +            let short_hand = +                Path::line(Point::ORIGIN, Point::new(0.0, -0.5 * radius)); -        let short_hand = -            Path::line(Point::ORIGIN, Point::new(0.0, -0.5 * radius)); +            let long_hand = +                Path::line(Point::ORIGIN, Point::new(0.0, -0.8 * radius)); -        let long_hand = -            Path::line(Point::ORIGIN, Point::new(0.0, -0.8 * radius)); +            let thin_stroke = Stroke { +                width: radius / 100.0, +                color: Color::WHITE, +                line_cap: LineCap::Round, +                ..Stroke::default() +            }; -        let thin_stroke = canvas::Stroke { -            width: radius / 100.0, -            color: Color::WHITE, -            line_cap: canvas::LineCap::Round, -            ..canvas::Stroke::default() -        }; +            let wide_stroke = Stroke { +                width: thin_stroke.width * 3.0, +                ..thin_stroke +            }; -        let wide_stroke = canvas::Stroke { -            width: thin_stroke.width * 3.0, -            ..thin_stroke -        }; +            frame.translate(Vector::new(center.x, center.y)); -        frame.translate(Vector::new(center.x, center.y)); +            frame.with_save(|frame| { +                frame.rotate(hand_rotation(self.now.hour(), 12)); +                frame.stroke(&short_hand, wide_stroke); +            }); -        frame.with_save(|frame| { -            frame.rotate(hand_rotation(self.hour, 12)); -            frame.stroke(&short_hand, wide_stroke); -        }); +            frame.with_save(|frame| { +                frame.rotate(hand_rotation(self.now.minute(), 60)); +                frame.stroke(&long_hand, wide_stroke); +            }); -        frame.with_save(|frame| { -            frame.rotate(hand_rotation(self.minute, 60)); -            frame.stroke(&long_hand, wide_stroke); +            frame.with_save(|frame| { +                frame.rotate(hand_rotation(self.now.second(), 60)); +                frame.stroke(&long_hand, thin_stroke); +            })          }); -        frame.with_save(|frame| { -            frame.rotate(hand_rotation(self.second, 60)); -            frame.stroke(&long_hand, thin_stroke); -        }); +        vec![clock]      }  } diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index 0a570745..d0bceb73 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -66,7 +66,7 @@ mod circle {                      border_width: 0,                      border_color: Color::TRANSPARENT,                  }, -                MouseCursor::OutOfBounds, +                MouseCursor::default(),              )          }      } diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 13a687ab..2a3efd4a 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -12,7 +12,7 @@ mod rainbow {      // implemented by `iced_wgpu` and other renderers.      use iced_native::{          layout, Element, Hasher, Layout, Length, MouseCursor, Point, Size, -        Widget, +        Vector, Widget,      };      use iced_wgpu::{          triangle::{Mesh2D, Vertex2D}, @@ -85,60 +85,63 @@ mod rainbow {              let posn_l = [0.0, b.height / 2.0];              ( -                Primitive::Mesh2D { -                    origin: Point::new(b.x, b.y), -                    buffers: Mesh2D { -                        vertices: vec![ -                            Vertex2D { -                                position: posn_center, -                                color: [1.0, 1.0, 1.0, 1.0], -                            }, -                            Vertex2D { -                                position: posn_tl, -                                color: color_r, -                            }, -                            Vertex2D { -                                position: posn_t, -                                color: color_o, -                            }, -                            Vertex2D { -                                position: posn_tr, -                                color: color_y, -                            }, -                            Vertex2D { -                                position: posn_r, -                                color: color_g, -                            }, -                            Vertex2D { -                                position: posn_br, -                                color: color_gb, -                            }, -                            Vertex2D { -                                position: posn_b, -                                color: color_b, -                            }, -                            Vertex2D { -                                position: posn_bl, -                                color: color_i, -                            }, -                            Vertex2D { -                                position: posn_l, -                                color: color_v, -                            }, -                        ], -                        indices: vec![ -                            0, 1, 2, // TL -                            0, 2, 3, // T -                            0, 3, 4, // TR -                            0, 4, 5, // R -                            0, 5, 6, // BR -                            0, 6, 7, // B -                            0, 7, 8, // BL -                            0, 8, 1, // L -                        ], -                    }, +                Primitive::Translate { +                    translation: Vector::new(b.x, b.y), +                    content: Box::new(Primitive::Mesh2D { +                        size: b.size(), +                        buffers: Mesh2D { +                            vertices: vec![ +                                Vertex2D { +                                    position: posn_center, +                                    color: [1.0, 1.0, 1.0, 1.0], +                                }, +                                Vertex2D { +                                    position: posn_tl, +                                    color: color_r, +                                }, +                                Vertex2D { +                                    position: posn_t, +                                    color: color_o, +                                }, +                                Vertex2D { +                                    position: posn_tr, +                                    color: color_y, +                                }, +                                Vertex2D { +                                    position: posn_r, +                                    color: color_g, +                                }, +                                Vertex2D { +                                    position: posn_br, +                                    color: color_gb, +                                }, +                                Vertex2D { +                                    position: posn_b, +                                    color: color_b, +                                }, +                                Vertex2D { +                                    position: posn_bl, +                                    color: color_i, +                                }, +                                Vertex2D { +                                    position: posn_l, +                                    color: color_v, +                                }, +                            ], +                            indices: vec![ +                                0, 1, 2, // TL +                                0, 2, 3, // T +                                0, 3, 4, // TR +                                0, 4, 5, // R +                                0, 5, 6, // BR +                                0, 6, 7, // B +                                0, 7, 8, // BL +                                0, 8, 1, // L +                            ], +                        }, +                    }),                  }, -                MouseCursor::OutOfBounds, +                MouseCursor::default(),              )          }      } diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 7203d4b6..da571ed1 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -63,7 +63,7 @@ pub fn main() {      let mut events = Vec::new();      let mut cache = Some(Cache::default());      let mut renderer = Renderer::new(&mut device, Settings::default()); -    let mut output = (Primitive::None, MouseCursor::OutOfBounds); +    let mut output = (Primitive::None, MouseCursor::default());      let clipboard = Clipboard::new(&window);      // Initialize scene and GUI controls diff --git a/examples/solar_system/Cargo.toml b/examples/solar_system/Cargo.toml index c88cda50..0555aa96 100644 --- a/examples/solar_system/Cargo.toml +++ b/examples/solar_system/Cargo.toml @@ -5,9 +5,6 @@ authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"]  edition = "2018"  publish = false -[features] -canvas = [] -  [dependencies]  iced = { path = "../..", features = ["canvas", "async-std", "debug"] }  iced_native = { path = "../../native" } diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index bcd1dc71..a25e43df 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -7,8 +7,9 @@  //!  //! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system  use iced::{ -    canvas, executor, window, Application, Canvas, Color, Command, Container, -    Element, Length, Point, Settings, Size, Subscription, Vector, +    canvas::{self, Cursor, Path, Stroke}, +    executor, window, Application, Canvas, Color, Command, Element, Length, +    Point, Rectangle, Settings, Size, Subscription, Vector,  };  use std::time::Instant; @@ -22,7 +23,6 @@ pub fn main() {  struct SolarSystem {      state: State, -    solar_system: canvas::layer::Cache<State>,  }  #[derive(Debug, Clone, Copy)] @@ -39,7 +39,6 @@ impl Application for SolarSystem {          (              SolarSystem {                  state: State::new(), -                solar_system: Default::default(),              },              Command::none(),          ) @@ -53,7 +52,6 @@ impl Application for SolarSystem {          match message {              Message::Tick(instant) => {                  self.state.update(instant); -                self.solar_system.clear();              }          } @@ -66,24 +64,20 @@ impl Application for SolarSystem {      }      fn view(&mut self) -> Element<Message> { -        let canvas = Canvas::new() +        Canvas::new(&mut self.state)              .width(Length::Fill)              .height(Length::Fill) -            .push(self.solar_system.with(&self.state)); - -        Container::new(canvas) -            .width(Length::Fill) -            .height(Length::Fill) -            .center_x() -            .center_y()              .into()      }  }  #[derive(Debug)]  struct State { +    space_cache: canvas::Cache, +    system_cache: canvas::Cache, +    cursor_position: Point,      start: Instant, -    current: Instant, +    now: Instant,      stars: Vec<(Point, f32)>,  } @@ -99,102 +93,123 @@ impl State {          let (width, height) = window::Settings::default().size;          State { +            space_cache: Default::default(), +            system_cache: Default::default(), +            cursor_position: Point::ORIGIN,              start: now, -            current: now, -            stars: { -                use rand::Rng; - -                let mut rng = rand::thread_rng(); - -                (0..100) -                    .map(|_| { -                        ( -                            Point::new( -                                rng.gen_range(0.0, width as f32), -                                rng.gen_range(0.0, height as f32), -                            ), -                            rng.gen_range(0.5, 1.0), -                        ) -                    }) -                    .collect() -            }, +            now, +            stars: Self::generate_stars(width, height),          }      }      pub fn update(&mut self, now: Instant) { -        self.current = now; +        self.now = now; +        self.system_cache.clear(); +    } + +    fn generate_stars(width: u32, height: u32) -> Vec<(Point, f32)> { +        use rand::Rng; + +        let mut rng = rand::thread_rng(); + +        (0..100) +            .map(|_| { +                ( +                    Point::new( +                        rng.gen_range( +                            -(width as f32) / 2.0, +                            width as f32 / 2.0, +                        ), +                        rng.gen_range( +                            -(height as f32) / 2.0, +                            height as f32 / 2.0, +                        ), +                    ), +                    rng.gen_range(0.5, 1.0), +                ) +            }) +            .collect()      }  } -impl canvas::Drawable for State { -    fn draw(&self, frame: &mut canvas::Frame) { -        use canvas::{Path, Stroke}; +impl<Message> canvas::Program<Message> for State { +    fn draw( +        &self, +        bounds: Rectangle, +        _cursor: Cursor, +    ) -> Vec<canvas::Geometry> {          use std::f32::consts::PI; -        let center = frame.center(); +        let background = self.space_cache.draw(bounds.size(), |frame| { +            let space = Path::rectangle(Point::new(0.0, 0.0), frame.size()); -        let space = Path::rectangle(Point::new(0.0, 0.0), frame.size()); +            let stars = Path::new(|path| { +                for (p, size) in &self.stars { +                    path.rectangle(*p, Size::new(*size, *size)); +                } +            }); -        let stars = Path::new(|path| { -            for (p, size) in &self.stars { -                path.rectangle(*p, Size::new(*size, *size)); -            } +            frame.fill(&space, Color::BLACK); + +            frame.translate(frame.center() - Point::ORIGIN); +            frame.fill(&stars, Color::WHITE);          }); -        let sun = Path::circle(center, Self::SUN_RADIUS); -        let orbit = Path::circle(center, Self::ORBIT_RADIUS); - -        frame.fill(&space, Color::BLACK); -        frame.fill(&stars, Color::WHITE); -        frame.fill(&sun, Color::from_rgb8(0xF9, 0xD7, 0x1C)); -        frame.stroke( -            &orbit, -            Stroke { -                width: 1.0, -                color: Color::from_rgba8(0, 153, 255, 0.1), -                ..Stroke::default() -            }, -        ); +        let system = self.system_cache.draw(bounds.size(), |frame| { +            let center = frame.center(); -        let elapsed = self.current - self.start; -        let elapsed_seconds = elapsed.as_secs() as f32; -        let elapsed_millis = elapsed.subsec_millis() as f32; +            let sun = Path::circle(center, Self::SUN_RADIUS); +            let orbit = Path::circle(center, Self::ORBIT_RADIUS); -        frame.with_save(|frame| { -            frame.translate(Vector::new(center.x, center.y)); -            frame.rotate( -                (2.0 * PI / 60.0) * elapsed_seconds -                    + (2.0 * PI / 60_000.0) * elapsed_millis, -            ); -            frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0)); - -            let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS); -            let shadow = Path::rectangle( -                Point::new(0.0, -Self::EARTH_RADIUS), -                Size::new(Self::EARTH_RADIUS * 4.0, Self::EARTH_RADIUS * 2.0), +            frame.fill(&sun, Color::from_rgb8(0xF9, 0xD7, 0x1C)); +            frame.stroke( +                &orbit, +                Stroke { +                    width: 1.0, +                    color: Color::from_rgba8(0, 153, 255, 0.1), +                    ..Stroke::default() +                },              ); -            frame.fill(&earth, Color::from_rgb8(0x6B, 0x93, 0xD6)); +            let elapsed = self.now - self.start; +            let rotation = (2.0 * PI / 60.0) * elapsed.as_secs() as f32 +                + (2.0 * PI / 60_000.0) * elapsed.subsec_millis() as f32;              frame.with_save(|frame| { -                frame.rotate( -                    ((2.0 * PI) / 6.0) * elapsed_seconds -                        + ((2.0 * PI) / 6_000.0) * elapsed_millis, +                frame.translate(Vector::new(center.x, center.y)); +                frame.rotate(rotation); +                frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0)); + +                let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS); +                let shadow = Path::rectangle( +                    Point::new(0.0, -Self::EARTH_RADIUS), +                    Size::new( +                        Self::EARTH_RADIUS * 4.0, +                        Self::EARTH_RADIUS * 2.0, +                    ),                  ); -                frame.translate(Vector::new(0.0, Self::MOON_DISTANCE)); -                let moon = Path::circle(Point::ORIGIN, Self::MOON_RADIUS); -                frame.fill(&moon, Color::WHITE); -            }); +                frame.fill(&earth, Color::from_rgb8(0x6B, 0x93, 0xD6)); -            frame.fill( -                &shadow, -                Color { -                    a: 0.7, -                    ..Color::BLACK -                }, -            ); +                frame.with_save(|frame| { +                    frame.rotate(rotation * 10.0); +                    frame.translate(Vector::new(0.0, Self::MOON_DISTANCE)); + +                    let moon = Path::circle(Point::ORIGIN, Self::MOON_RADIUS); +                    frame.fill(&moon, Color::WHITE); +                }); + +                frame.fill( +                    &shadow, +                    Color { +                        a: 0.7, +                        ..Color::BLACK +                    }, +                ); +            });          }); + +        vec![background, system]      }  } diff --git a/native/src/input.rs b/native/src/input.rs index 097fa730..7f5114c3 100644 --- a/native/src/input.rs +++ b/native/src/input.rs @@ -2,6 +2,4 @@  pub mod keyboard;  pub mod mouse; -mod button_state; - -pub use button_state::ButtonState; +pub use iced_core::ButtonState; diff --git a/native/src/input/keyboard.rs b/native/src/input/keyboard.rs index 928bf492..220b7f17 100644 --- a/native/src/input/keyboard.rs +++ b/native/src/input/keyboard.rs @@ -2,4 +2,4 @@  mod event;  pub use event::Event; -pub use iced_core::keyboard::{KeyCode, ModifiersState}; +pub use iced_core::keyboard::*; diff --git a/native/src/input/mouse.rs b/native/src/input/mouse.rs index 7198b233..ae3f1596 100644 --- a/native/src/input/mouse.rs +++ b/native/src/input/mouse.rs @@ -1,9 +1,6 @@  //! Build mouse events. -mod button; -mod event;  pub mod click; -pub use button::Button;  pub use click::Click; -pub use event::{Event, ScrollDelta}; +pub use iced_core::mouse::*; diff --git a/native/src/lib.rs b/native/src/lib.rs index 89612391..a3b581b3 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -50,13 +50,12 @@ mod clipboard;  mod element;  mod event;  mod hasher; -mod mouse_cursor;  mod runtime;  mod user_interface;  pub use iced_core::{ -    Align, Background, Color, Font, HorizontalAlignment, Length, Point, -    Rectangle, Size, Vector, VerticalAlignment, +    Align, Background, Color, Font, HorizontalAlignment, Length, MouseCursor, +    Point, Rectangle, Size, Vector, VerticalAlignment,  };  pub use iced_futures::{executor, futures, Command}; @@ -68,7 +67,6 @@ pub use element::Element;  pub use event::Event;  pub use hasher::Hasher;  pub use layout::Layout; -pub use mouse_cursor::MouseCursor;  pub use renderer::Renderer;  pub use runtime::Runtime;  pub use subscription::Subscription; @@ -185,6 +185,7 @@ mod sandbox;  pub mod executor;  pub mod keyboard; +pub mod mouse;  pub mod settings;  pub mod widget;  pub mod window; @@ -206,5 +207,9 @@ use iced_web as runtime;  pub use runtime::{      futures, Align, Background, Color, Command, Font, HorizontalAlignment, -    Length, Point, Size, Subscription, Vector, VerticalAlignment, +    Length, MouseCursor, Point, Rectangle, Size, Subscription, Vector, +    VerticalAlignment,  }; + +#[cfg(not(target_arch = "wasm32"))] +pub use runtime::input::ButtonState; diff --git a/src/mouse.rs b/src/mouse.rs new file mode 100644 index 00000000..8be36d37 --- /dev/null +++ b/src/mouse.rs @@ -0,0 +1,3 @@ +//! Listen and react to mouse events. +#[cfg(not(target_arch = "wasm32"))] +pub use iced_winit::input::mouse::{Button, Event, ScrollDelta}; diff --git a/web/src/lib.rs b/web/src/lib.rs index 395c0a25..c525021f 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -75,7 +75,7 @@ pub use element::Element;  pub use hasher::Hasher;  pub use iced_core::{      keyboard, Align, Background, Color, Font, HorizontalAlignment, Length, -    Point, Size, Vector, VerticalAlignment, +    MouseCursor, Point, Rectangle, Size, Vector, VerticalAlignment,  };  pub use iced_futures::{executor, futures, Command};  pub use subscription::Subscription; diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 799c1f34..30b5bb4a 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -20,7 +20,7 @@  //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs  //! [WebGPU API]: https://gpuweb.github.io/gpuweb/  //! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph -#![deny(missing_docs)] +//#![deny(missing_docs)]  #![deny(missing_debug_implementations)]  #![deny(unused_results)]  #![forbid(unsafe_code)] diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 46d9e624..e73227ef 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,5 +1,5 @@  use iced_native::{ -    image, svg, Background, Color, Font, HorizontalAlignment, Point, Rectangle, +    image, svg, Background, Color, Font, HorizontalAlignment, Rectangle, Size,      Vector, VerticalAlignment,  }; @@ -70,12 +70,22 @@ pub enum Primitive {          /// The content of the clip          content: Box<Primitive>,      }, +    /// A primitive that applies a translation +    Translate { +        /// The translation vector +        translation: Vector, + +        /// The primitive to translate +        content: Box<Primitive>, +    },      /// A low-level primitive to render a mesh of triangles.      ///      /// It can be used to render many kinds of geometry freely.      Mesh2D { -        /// The top-left coordinate of the mesh -        origin: Point, +        /// The size of the drawable region of the mesh. +        /// +        /// Any geometry that falls out of this region will be clipped. +        size: Size,          /// The vertex and index buffers of the mesh          buffers: triangle::Mesh2D, @@ -85,9 +95,6 @@ pub enum Primitive {      /// This can be useful if you are implementing a widget where primitive      /// generation is expensive.      Cached { -        /// The origin of the coordinate system of the cached primitives -        origin: Point, -          /// The cached primitive          cache: Arc<Primitive>,      }, diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index ca9364c1..dccd0d82 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -29,7 +29,7 @@ pub struct Renderer {  struct Layer<'a> {      bounds: Rectangle<u32>,      quads: Vec<Quad>, -    meshes: Vec<(Point, &'a triangle::Mesh2D)>, +    meshes: Vec<(Vector, Rectangle<u32>, &'a triangle::Mesh2D)>,      text: Vec<wgpu_glyph::Section<'a>>,      #[cfg(any(feature = "image", feature = "svg"))] @@ -48,6 +48,12 @@ impl<'a> Layer<'a> {              images: Vec::new(),          }      } + +    pub fn intersection(&self, rectangle: Rectangle) -> Option<Rectangle<u32>> { +        let layer_bounds: Rectangle<f32> = self.bounds.into(); + +        layer_bounds.intersection(&rectangle).map(Into::into) +    }  }  impl Renderer { @@ -214,10 +220,20 @@ impl Renderer {                      border_color: border_color.into_linear(),                  });              } -            Primitive::Mesh2D { origin, buffers } => { +            Primitive::Mesh2D { size, buffers } => {                  let layer = layers.last_mut().unwrap(); -                layer.meshes.push((*origin + translation, buffers)); +                // Only draw visible content +                if let Some(clip_bounds) = layer.intersection(Rectangle::new( +                    Point::new(translation.x, translation.y), +                    *size, +                )) { +                    layer.meshes.push(( +                        translation, +                        clip_bounds.into(), +                        buffers, +                    )); +                }              }              Primitive::Clip {                  bounds, @@ -226,16 +242,10 @@ impl Renderer {              } => {                  let layer = layers.last_mut().unwrap(); -                let layer_bounds: Rectangle<f32> = layer.bounds.into(); - -                let clip = Rectangle { -                    x: bounds.x + translation.x, -                    y: bounds.y + translation.y, -                    ..*bounds -                }; -                  // Only draw visible content -                if let Some(clip_bounds) = layer_bounds.intersection(&clip) { +                if let Some(clip_bounds) = +                    layer.intersection(*bounds + translation) +                {                      let clip_layer = Layer::new(clip_bounds.into());                      let new_layer = Layer::new(layer.bounds); @@ -249,15 +259,21 @@ impl Renderer {                      layers.push(new_layer);                  }              } - -            Primitive::Cached { origin, cache } => { +            Primitive::Translate { +                translation: new_translation, +                content, +            } => {                  self.draw_primitive( -                    translation + Vector::new(origin.x, origin.y), -                    &cache, +                    translation + *new_translation, +                    &content,                      layers,                  );              } +            Primitive::Cached { cache } => { +                self.draw_primitive(translation, &cache, layers); +            } +              #[cfg(feature = "image")]              Primitive::Image { handle, bounds } => {                  let layer = layers.last_mut().unwrap(); @@ -362,8 +378,8 @@ impl Renderer {                  target_width,                  target_height,                  scaled, +                scale_factor,                  &layer.meshes, -                bounds,              );          } diff --git a/wgpu/src/renderer/widget/button.rs b/wgpu/src/renderer/widget/button.rs index 359b4866..5e55873a 100644 --- a/wgpu/src/renderer/widget/button.rs +++ b/wgpu/src/renderer/widget/button.rs @@ -86,7 +86,7 @@ impl iced_native::button::Renderer for Renderer {              if is_mouse_over {                  MouseCursor::Pointer              } else { -                MouseCursor::OutOfBounds +                MouseCursor::default()              },          )      } diff --git a/wgpu/src/renderer/widget/checkbox.rs b/wgpu/src/renderer/widget/checkbox.rs index c0f1bf21..7f7f6de3 100644 --- a/wgpu/src/renderer/widget/checkbox.rs +++ b/wgpu/src/renderer/widget/checkbox.rs @@ -56,7 +56,7 @@ impl checkbox::Renderer for Renderer {              if is_mouse_over {                  MouseCursor::Pointer              } else { -                MouseCursor::OutOfBounds +                MouseCursor::default()              },          )      } diff --git a/wgpu/src/renderer/widget/column.rs b/wgpu/src/renderer/widget/column.rs index 95a7463a..e6a9d8f0 100644 --- a/wgpu/src/renderer/widget/column.rs +++ b/wgpu/src/renderer/widget/column.rs @@ -9,7 +9,7 @@ impl column::Renderer for Renderer {          layout: Layout<'_>,          cursor_position: Point,      ) -> Self::Output { -        let mut mouse_cursor = MouseCursor::OutOfBounds; +        let mut mouse_cursor = MouseCursor::default();          (              Primitive::Group { diff --git a/wgpu/src/renderer/widget/image.rs b/wgpu/src/renderer/widget/image.rs index 70dc5d97..6b7f1c60 100644 --- a/wgpu/src/renderer/widget/image.rs +++ b/wgpu/src/renderer/widget/image.rs @@ -16,7 +16,7 @@ impl image::Renderer for Renderer {                  handle,                  bounds: layout.bounds(),              }, -            MouseCursor::OutOfBounds, +            MouseCursor::default(),          )      }  } diff --git a/wgpu/src/renderer/widget/pane_grid.rs b/wgpu/src/renderer/widget/pane_grid.rs index 2d201fec..80e2471f 100644 --- a/wgpu/src/renderer/widget/pane_grid.rs +++ b/wgpu/src/renderer/widget/pane_grid.rs @@ -22,7 +22,7 @@ impl pane_grid::Renderer for Renderer {              cursor_position          }; -        let mut mouse_cursor = MouseCursor::OutOfBounds; +        let mut mouse_cursor = MouseCursor::default();          let mut dragged_pane = None;          let mut panes: Vec<_> = content @@ -59,12 +59,12 @@ impl pane_grid::Renderer for Renderer {                      height: bounds.height + 0.5,                  },                  offset: Vector::new(0, 0), -                content: Box::new(Primitive::Cached { -                    origin: Point::new( +                content: Box::new(Primitive::Translate { +                    translation: Vector::new(                          cursor_position.x - bounds.x - bounds.width / 2.0,                          cursor_position.y - bounds.y - bounds.height / 2.0,                      ), -                    cache: std::sync::Arc::new(pane), +                    content: Box::new(pane),                  }),              }; diff --git a/wgpu/src/renderer/widget/progress_bar.rs b/wgpu/src/renderer/widget/progress_bar.rs index 34e33276..fe032fbf 100644 --- a/wgpu/src/renderer/widget/progress_bar.rs +++ b/wgpu/src/renderer/widget/progress_bar.rs @@ -48,7 +48,7 @@ impl progress_bar::Renderer for Renderer {              } else {                  background              }, -            MouseCursor::OutOfBounds, +            MouseCursor::default(),          )      }  } diff --git a/wgpu/src/renderer/widget/radio.rs b/wgpu/src/renderer/widget/radio.rs index 564f066b..551700c8 100644 --- a/wgpu/src/renderer/widget/radio.rs +++ b/wgpu/src/renderer/widget/radio.rs @@ -57,7 +57,7 @@ impl radio::Renderer for Renderer {              if is_mouse_over {                  MouseCursor::Pointer              } else { -                MouseCursor::OutOfBounds +                MouseCursor::default()              },          )      } diff --git a/wgpu/src/renderer/widget/row.rs b/wgpu/src/renderer/widget/row.rs index bd9f1a04..c6a10c07 100644 --- a/wgpu/src/renderer/widget/row.rs +++ b/wgpu/src/renderer/widget/row.rs @@ -9,7 +9,7 @@ impl row::Renderer for Renderer {          layout: Layout<'_>,          cursor_position: Point,      ) -> Self::Output { -        let mut mouse_cursor = MouseCursor::OutOfBounds; +        let mut mouse_cursor = MouseCursor::default();          (              Primitive::Group { diff --git a/wgpu/src/renderer/widget/slider.rs b/wgpu/src/renderer/widget/slider.rs index c8ebd0da..335e1b92 100644 --- a/wgpu/src/renderer/widget/slider.rs +++ b/wgpu/src/renderer/widget/slider.rs @@ -99,7 +99,7 @@ impl slider::Renderer for Renderer {              } else if is_mouse_over {                  MouseCursor::Grab              } else { -                MouseCursor::OutOfBounds +                MouseCursor::default()              },          )      } diff --git a/wgpu/src/renderer/widget/space.rs b/wgpu/src/renderer/widget/space.rs index 28e05437..9ec0ed6d 100644 --- a/wgpu/src/renderer/widget/space.rs +++ b/wgpu/src/renderer/widget/space.rs @@ -3,6 +3,6 @@ use iced_native::{space, MouseCursor, Rectangle};  impl space::Renderer for Renderer {      fn draw(&mut self, _bounds: Rectangle) -> Self::Output { -        (Primitive::None, MouseCursor::OutOfBounds) +        (Primitive::None, MouseCursor::default())      }  } diff --git a/wgpu/src/renderer/widget/svg.rs b/wgpu/src/renderer/widget/svg.rs index 67bc3fe1..4ee983ea 100644 --- a/wgpu/src/renderer/widget/svg.rs +++ b/wgpu/src/renderer/widget/svg.rs @@ -16,7 +16,7 @@ impl svg::Renderer for Renderer {                  handle,                  bounds: layout.bounds(),              }, -            MouseCursor::OutOfBounds, +            MouseCursor::default(),          )      }  } diff --git a/wgpu/src/renderer/widget/text.rs b/wgpu/src/renderer/widget/text.rs index f27cc430..4a4ecef4 100644 --- a/wgpu/src/renderer/widget/text.rs +++ b/wgpu/src/renderer/widget/text.rs @@ -55,7 +55,7 @@ impl text::Renderer for Renderer {                  horizontal_alignment,                  vertical_alignment,              }, -            MouseCursor::OutOfBounds, +            MouseCursor::default(),          )      }  } diff --git a/wgpu/src/renderer/widget/text_input.rs b/wgpu/src/renderer/widget/text_input.rs index 6f72db68..97eb0114 100644 --- a/wgpu/src/renderer/widget/text_input.rs +++ b/wgpu/src/renderer/widget/text_input.rs @@ -234,7 +234,7 @@ impl text_input::Renderer for Renderer {              if is_mouse_over {                  MouseCursor::Text              } else { -                MouseCursor::OutOfBounds +                MouseCursor::default()              },          )      } diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 86c74fcd..246dc7ce 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -1,6 +1,6 @@  //! Draw meshes of triangles.  use crate::{settings, Transformation}; -use iced_native::{Point, Rectangle}; +use iced_native::{Rectangle, Vector};  use std::mem;  use zerocopy::AsBytes; @@ -201,15 +201,15 @@ impl Pipeline {          target_width: u32,          target_height: u32,          transformation: Transformation, -        meshes: &[(Point, &Mesh2D)], -        bounds: Rectangle<u32>, +        scale_factor: f32, +        meshes: &[(Vector, Rectangle<u32>, &Mesh2D)],      ) {          // This looks a bit crazy, but we are just counting how many vertices          // and indices we will need to handle.          // TODO: Improve readability          let (total_vertices, total_indices) = meshes              .iter() -            .map(|(_, mesh)| (mesh.vertices.len(), mesh.indices.len())) +            .map(|(_, _, mesh)| (mesh.vertices.len(), mesh.indices.len()))              .fold((0, 0), |(total_v, total_i), (v, i)| {                  (total_v + v, total_i + i)              }); @@ -230,12 +230,10 @@ impl Pipeline {          let mut last_index = 0;          // We upload everything upfront -        for (origin, mesh) in meshes { -            let transform = Uniforms { -                transform: (transformation -                    * Transformation::translate(origin.x, origin.y)) -                .into(), -            }; +        for (origin, _, mesh) in meshes { +            let transform = (transformation +                * Transformation::translate(origin.x, origin.y)) +            .into();              let vertex_buffer = device.create_buffer_with_data(                  mesh.vertices.as_bytes(), @@ -318,16 +316,19 @@ impl Pipeline {                  });              render_pass.set_pipeline(&self.pipeline); -            render_pass.set_scissor_rect( -                bounds.x, -                bounds.y, -                bounds.width, -                bounds.height, -            );              for (i, (vertex_offset, index_offset, indices)) in                  offsets.into_iter().enumerate()              { +                let bounds = meshes[i].1 * scale_factor; + +                render_pass.set_scissor_rect( +                    bounds.x, +                    bounds.y, +                    bounds.width, +                    bounds.height, +                ); +                  render_pass.set_bind_group(                      0,                      &self.constants, @@ -361,12 +362,28 @@ impl Pipeline {  #[derive(Debug, Clone, Copy, AsBytes)]  struct Uniforms {      transform: [f32; 16], +    // We need to align this to 256 bytes to please `wgpu`... +    // TODO: Be smarter and stop wasting memory! +    _padding_a: [f32; 32], +    _padding_b: [f32; 16],  }  impl Default for Uniforms {      fn default() -> Self {          Self {              transform: *Transformation::identity().as_ref(), +            _padding_a: [0.0; 32], +            _padding_b: [0.0; 16], +        } +    } +} + +impl From<Transformation> for Uniforms { +    fn from(transformation: Transformation) -> Uniforms { +        Self { +            transform: transformation.into(), +            _padding_a: [0.0; 32], +            _padding_b: [0.0; 16],          }      }  } diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index 325f90ce..a5834330 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -9,35 +9,38 @@  use crate::{Defaults, Primitive, Renderer};  use iced_native::{ -    layout, Element, Hasher, Layout, Length, MouseCursor, Point, Size, Widget, +    layout, Clipboard, Element, Hasher, Layout, Length, MouseCursor, Point, +    Size, Vector, Widget,  };  use std::hash::Hash; +use std::marker::PhantomData; -pub mod layer;  pub mod path; -mod drawable; +mod cache; +mod cursor; +mod event;  mod fill;  mod frame; +mod geometry; +mod program;  mod stroke;  mod text; -pub use drawable::Drawable; +pub use cache::Cache; +pub use cursor::Cursor; +pub use event::Event;  pub use fill::Fill;  pub use frame::Frame; -pub use layer::Layer; +pub use geometry::Geometry;  pub use path::Path; +pub use program::Program;  pub use stroke::{LineCap, LineJoin, Stroke};  pub use text::Text;  /// A widget capable of drawing 2D graphics.  /// -/// A [`Canvas`] may contain multiple layers. A [`Layer`] is drawn using the -/// painter's algorithm. In other words, layers will be drawn on top of each -/// other in the same order they are pushed into the [`Canvas`]. -///  /// [`Canvas`]: struct.Canvas.html -/// [`Layer`]: layer/trait.Layer.html  ///  /// # Examples  /// The repository has a couple of [examples] showcasing how to use a @@ -58,10 +61,10 @@ pub use text::Text;  /// ```no_run  /// # mod iced {  /// #     pub use iced_wgpu::canvas; -/// #     pub use iced_native::Color; +/// #     pub use iced_native::{Color, Rectangle};  /// # } -/// use iced::canvas::{self, layer, Canvas, Drawable, Fill, Frame, Path}; -/// use iced::Color; +/// use iced::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program}; +/// use iced::{Color, Rectangle};  ///  /// // First, we define the data we need for drawing  /// #[derive(Debug)] @@ -69,43 +72,46 @@ pub use text::Text;  ///     radius: f32,  /// }  /// -/// // Then, we implement the `Drawable` trait -/// impl Drawable for Circle { -///     fn draw(&self, frame: &mut Frame) { +/// // Then, we implement the `Program` trait +/// impl Program<()> for Circle { +///     fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry>{ +///         // We prepare a new `Frame` +///         let mut frame = Frame::new(bounds.size()); +///  ///         // We create a `Path` representing a simple circle -///         let circle = Path::new(|p| p.circle(frame.center(), self.radius)); +///         let circle = Path::circle(frame.center(), self.radius);  ///  ///         // And fill it with some color  ///         frame.fill(&circle, Fill::Color(Color::BLACK)); +/// +///         // Finally, we produce the geometry +///         vec![frame.into_geometry()]  ///     }  /// }  /// -/// // We can use a `Cache` to avoid unnecessary re-tessellation -/// let cache: layer::Cache<Circle> = layer::Cache::new(); -/// -/// // Finally, we simply provide the data to our `Cache` and push the resulting -/// // layer into a `Canvas` -/// let canvas = Canvas::new() -///     .push(cache.with(&Circle { radius: 50.0 })); +/// // Finally, we simply use our `Cache` to create the `Canvas`! +/// let canvas = Canvas::new(Circle { radius: 50.0 });  /// ```  #[derive(Debug)] -pub struct Canvas<'a> { +pub struct Canvas<Message, P: Program<Message>> {      width: Length,      height: Length, -    layers: Vec<Box<dyn Layer + 'a>>, +    program: P, +    phantom: PhantomData<Message>,  } -impl<'a> Canvas<'a> { +impl<Message, P: Program<Message>> Canvas<Message, P> {      const DEFAULT_SIZE: u16 = 100; -    /// Creates a new [`Canvas`] with no layers. +    /// Creates a new [`Canvas`].      ///      /// [`Canvas`]: struct.Canvas.html -    pub fn new() -> Self { +    pub fn new(program: P) -> Self {          Canvas {              width: Length::Units(Self::DEFAULT_SIZE),              height: Length::Units(Self::DEFAULT_SIZE), -            layers: Vec::new(), +            program, +            phantom: PhantomData,          }      } @@ -124,20 +130,11 @@ impl<'a> Canvas<'a> {          self.height = height;          self      } - -    /// Adds a [`Layer`] to the [`Canvas`]. -    /// -    /// It will be drawn on top of previous layers. -    /// -    /// [`Layer`]: layer/trait.Layer.html -    /// [`Canvas`]: struct.Canvas.html -    pub fn push(mut self, layer: impl Layer + 'a) -> Self { -        self.layers.push(Box::new(layer)); -        self -    }  } -impl<'a, Message> Widget<Message, Renderer> for Canvas<'a> { +impl<Message, P: Program<Message>> Widget<Message, Renderer> +    for Canvas<Message, P> +{      fn width(&self) -> Length {          self.width      } @@ -157,45 +154,77 @@ impl<'a, Message> Widget<Message, Renderer> for Canvas<'a> {          layout::Node::new(size)      } +    fn on_event( +        &mut self, +        event: iced_native::Event, +        layout: Layout<'_>, +        cursor_position: Point, +        messages: &mut Vec<Message>, +        _renderer: &Renderer, +        _clipboard: Option<&dyn Clipboard>, +    ) { +        let bounds = layout.bounds(); + +        let canvas_event = match event { +            iced_native::Event::Mouse(mouse_event) => { +                Some(Event::Mouse(mouse_event)) +            } +            _ => None, +        }; + +        let cursor = Cursor::from_window_position(cursor_position); + +        if let Some(canvas_event) = canvas_event { +            if let Some(message) = +                self.program.update(canvas_event, bounds, cursor) +            { +                messages.push(message); +            } +        } +    } +      fn draw(          &self,          _renderer: &mut Renderer,          _defaults: &Defaults,          layout: Layout<'_>, -        _cursor_position: Point, +        cursor_position: Point,      ) -> (Primitive, MouseCursor) {          let bounds = layout.bounds(); -        let origin = Point::new(bounds.x, bounds.y); -        let size = Size::new(bounds.width, bounds.height); +        let translation = Vector::new(bounds.x, bounds.y); +        let cursor = Cursor::from_window_position(cursor_position);          ( -            Primitive::Group { -                primitives: self -                    .layers -                    .iter() -                    .map(|layer| Primitive::Cached { -                        origin, -                        cache: layer.draw(size), -                    }) -                    .collect(), +            Primitive::Translate { +                translation, +                content: Box::new(Primitive::Group { +                    primitives: self +                        .program +                        .draw(bounds, cursor) +                        .into_iter() +                        .map(Geometry::into_primitive) +                        .collect(), +                }),              }, -            MouseCursor::Idle, +            self.program.mouse_cursor(bounds, cursor),          )      }      fn hash_layout(&self, state: &mut Hasher) { -        std::any::TypeId::of::<Canvas<'static>>().hash(state); +        struct Marker; +        std::any::TypeId::of::<Marker>().hash(state);          self.width.hash(state);          self.height.hash(state);      }  } -impl<'a, Message> From<Canvas<'a>> for Element<'a, Message, Renderer> +impl<'a, Message, P: Program<Message> + 'a> From<Canvas<Message, P>> +    for Element<'a, Message, Renderer>  where      Message: 'static,  { -    fn from(canvas: Canvas<'a>) -> Element<'a, Message, Renderer> { +    fn from(canvas: Canvas<Message, P>) -> Element<'a, Message, Renderer> {          Element::new(canvas)      }  } diff --git a/wgpu/src/widget/canvas/cache.rs b/wgpu/src/widget/canvas/cache.rs new file mode 100644 index 00000000..03643f74 --- /dev/null +++ b/wgpu/src/widget/canvas/cache.rs @@ -0,0 +1,96 @@ +use crate::{ +    canvas::{Frame, Geometry}, +    Primitive, +}; + +use iced_native::Size; +use std::{cell::RefCell, sync::Arc}; + +enum State { +    Empty, +    Filled { +        bounds: Size, +        primitive: Arc<Primitive>, +    }, +} + +impl Default for State { +    fn default() -> Self { +        State::Empty +    } +} +/// A simple cache that stores generated geometry to avoid recomputation. +/// +/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer +/// change or it is explicitly cleared. +/// +/// [`Layer`]: ../trait.Layer.html +/// [`Cache`]: struct.Cache.html +#[derive(Debug, Default)] +pub struct Cache { +    state: RefCell<State>, +} + +impl Cache { +    /// Creates a new empty [`Cache`]. +    /// +    /// [`Cache`]: struct.Cache.html +    pub fn new() -> Self { +        Cache { +            state: Default::default(), +        } +    } + +    /// Clears the cache, forcing a redraw the next time it is used. +    /// +    /// [`Cached`]: struct.Cached.html +    pub fn clear(&mut self) { +        *self.state.borrow_mut() = State::Empty; +    } + +    pub fn draw( +        &self, +        new_bounds: Size, +        draw_fn: impl Fn(&mut Frame), +    ) -> Geometry { +        use std::ops::Deref; + +        if let State::Filled { bounds, primitive } = self.state.borrow().deref() +        { +            if *bounds == new_bounds { +                return Geometry::from_primitive(Primitive::Cached { +                    cache: primitive.clone(), +                }); +            } +        } + +        let mut frame = Frame::new(new_bounds); +        draw_fn(&mut frame); + +        let primitive = { +            let geometry = frame.into_geometry(); + +            Arc::new(geometry.into_primitive()) +        }; + +        *self.state.borrow_mut() = State::Filled { +            bounds: new_bounds, +            primitive: primitive.clone(), +        }; + +        Geometry::from_primitive(Primitive::Cached { cache: primitive }) +    } +} + +impl std::fmt::Debug for State { +    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +        match self { +            State::Empty => write!(f, "Empty"), +            State::Filled { primitive, bounds } => f +                .debug_struct("Filled") +                .field("primitive", primitive) +                .field("bounds", bounds) +                .finish(), +        } +    } +} diff --git a/wgpu/src/widget/canvas/cursor.rs b/wgpu/src/widget/canvas/cursor.rs new file mode 100644 index 00000000..a559782a --- /dev/null +++ b/wgpu/src/widget/canvas/cursor.rs @@ -0,0 +1,50 @@ +use iced_native::{Point, Rectangle}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Cursor { +    Available(Point), +    Unavailable, +} + +impl Cursor { +    // TODO: Remove this once this type is used in `iced_native` to encode +    // proper cursor availability +    pub(crate) fn from_window_position(position: Point) -> Self { +        if position.x < 0.0 || position.y < 0.0 { +            Cursor::Unavailable +        } else { +            Cursor::Available(position) +        } +    } + +    pub fn position(&self) -> Option<Point> { +        match self { +            Cursor::Available(position) => Some(*position), +            Cursor::Unavailable => None, +        } +    } + +    pub fn relative_position(&self, bounds: &Rectangle) -> Option<Point> { +        match self { +            Cursor::Available(position) => { +                Some(Point::new(position.x - bounds.x, position.y - bounds.y)) +            } +            _ => None, +        } +    } + +    pub fn internal_position(&self, bounds: &Rectangle) -> Option<Point> { +        if self.is_over(bounds) { +            self.relative_position(bounds) +        } else { +            None +        } +    } + +    pub fn is_over(&self, bounds: &Rectangle) -> bool { +        match self { +            Cursor::Available(position) => bounds.contains(*position), +            Cursor::Unavailable => false, +        } +    } +} diff --git a/wgpu/src/widget/canvas/drawable.rs b/wgpu/src/widget/canvas/drawable.rs deleted file mode 100644 index 6c74071c..00000000 --- a/wgpu/src/widget/canvas/drawable.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::canvas::Frame; - -/// A type that can be drawn on a [`Frame`]. -/// -/// [`Frame`]: struct.Frame.html -pub trait Drawable { -    /// Draws the [`Drawable`] on the given [`Frame`]. -    /// -    /// [`Drawable`]: trait.Drawable.html -    /// [`Frame`]: struct.Frame.html -    fn draw(&self, frame: &mut Frame); -} diff --git a/wgpu/src/widget/canvas/event.rs b/wgpu/src/widget/canvas/event.rs new file mode 100644 index 00000000..7a8b0829 --- /dev/null +++ b/wgpu/src/widget/canvas/event.rs @@ -0,0 +1,6 @@ +use iced_native::input::mouse; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Event { +    Mouse(mouse::Event), +} diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index de4717f1..1c4a038a 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -1,7 +1,7 @@  use iced_native::{Point, Rectangle, Size, Vector};  use crate::{ -    canvas::{Fill, Path, Stroke, Text}, +    canvas::{Fill, Geometry, Path, Stroke, Text},      triangle, Primitive,  }; @@ -10,8 +10,7 @@ use crate::{  /// [`Canvas`]: struct.Canvas.html  #[derive(Debug)]  pub struct Frame { -    width: f32, -    height: f32, +    size: Size,      buffers: lyon::tessellation::VertexBuffers<triangle::Vertex2D, u32>,      primitives: Vec<Primitive>,      transforms: Transforms, @@ -36,10 +35,9 @@ impl Frame {      /// top-left corner of its bounds.      ///      /// [`Frame`]: struct.Frame.html -    pub fn new(width: f32, height: f32) -> Frame { +    pub fn new(size: Size) -> Frame {          Frame { -            width, -            height, +            size,              buffers: lyon::tessellation::VertexBuffers::new(),              primitives: Vec::new(),              transforms: Transforms { @@ -57,7 +55,7 @@ impl Frame {      /// [`Frame`]: struct.Frame.html      #[inline]      pub fn width(&self) -> f32 { -        self.width +        self.size.width      }      /// Returns the width of the [`Frame`]. @@ -65,7 +63,7 @@ impl Frame {      /// [`Frame`]: struct.Frame.html      #[inline]      pub fn height(&self) -> f32 { -        self.height +        self.size.height      }      /// Returns the dimensions of the [`Frame`]. @@ -73,7 +71,7 @@ impl Frame {      /// [`Frame`]: struct.Frame.html      #[inline]      pub fn size(&self) -> Size { -        Size::new(self.width, self.height) +        self.size      }      /// Returns the coordinate of the center of the [`Frame`]. @@ -81,7 +79,7 @@ impl Frame {      /// [`Frame`]: struct.Frame.html      #[inline]      pub fn center(&self) -> Point { -        Point::new(self.width / 2.0, self.height / 2.0) +        Point::new(self.size.width / 2.0, self.size.height / 2.0)      }      /// Draws the given [`Path`] on the [`Frame`] by filling it with the @@ -262,13 +260,14 @@ impl Frame {          self.transforms.current.is_identity = false;      } -    /// Produces the primitive representing everything drawn on the [`Frame`]. +    /// Produces the [`Geometry`] representing everything drawn on the [`Frame`].      ///      /// [`Frame`]: struct.Frame.html -    pub fn into_primitive(mut self) -> Primitive { +    /// [`Geometry`]: struct.Geometry.html +    pub fn into_geometry(mut self) -> Geometry {          if !self.buffers.indices.is_empty() {              self.primitives.push(Primitive::Mesh2D { -                origin: Point::ORIGIN, +                size: self.size,                  buffers: triangle::Mesh2D {                      vertices: self.buffers.vertices,                      indices: self.buffers.indices, @@ -276,9 +275,9 @@ impl Frame {              });          } -        Primitive::Group { +        Geometry::from_primitive(Primitive::Group {              primitives: self.primitives, -        } +        })      }  } diff --git a/wgpu/src/widget/canvas/geometry.rs b/wgpu/src/widget/canvas/geometry.rs new file mode 100644 index 00000000..12ef828f --- /dev/null +++ b/wgpu/src/widget/canvas/geometry.rs @@ -0,0 +1,20 @@ +use crate::Primitive; + +#[derive(Debug, Clone)] +pub struct Geometry(Primitive); + +impl Geometry { +    pub(crate) fn from_primitive(primitive: Primitive) -> Self { +        Self(primitive) +    } + +    pub fn into_primitive(self) -> Primitive { +        self.0 +    } +} + +impl From<Geometry> for Primitive { +    fn from(geometry: Geometry) -> Primitive { +        geometry.0 +    } +} diff --git a/wgpu/src/widget/canvas/layer.rs b/wgpu/src/widget/canvas/layer.rs deleted file mode 100644 index a46b7fb1..00000000 --- a/wgpu/src/widget/canvas/layer.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Produce, store, and reuse geometry. -mod cache; - -pub use cache::Cache; - -use crate::Primitive; -use iced_native::Size; - -use std::sync::Arc; - -/// A layer that can be presented at a [`Canvas`]. -/// -/// [`Canvas`]: ../struct.Canvas.html -pub trait Layer: std::fmt::Debug { -    /// Draws the [`Layer`] in the given bounds and produces a [`Primitive`] as -    /// a result. -    /// -    /// The [`Layer`] may choose to store the produced [`Primitive`] locally and -    /// only recompute it when the bounds change, its contents change, or is -    /// otherwise explicitly cleared by other means. -    /// -    /// [`Layer`]: trait.Layer.html -    /// [`Primitive`]: ../../../enum.Primitive.html -    fn draw(&self, bounds: Size) -> Arc<Primitive>; -} diff --git a/wgpu/src/widget/canvas/layer/cache.rs b/wgpu/src/widget/canvas/layer/cache.rs deleted file mode 100644 index 4f8c2bec..00000000 --- a/wgpu/src/widget/canvas/layer/cache.rs +++ /dev/null @@ -1,128 +0,0 @@ -use crate::{ -    canvas::{Drawable, Frame, Layer}, -    Primitive, -}; - -use iced_native::Size; -use std::{cell::RefCell, marker::PhantomData, sync::Arc}; - -enum State { -    Empty, -    Filled { -        bounds: Size, -        primitive: Arc<Primitive>, -    }, -} - -impl Default for State { -    fn default() -> Self { -        State::Empty -    } -} -/// A simple cache that stores generated geometry to avoid recomputation. -/// -/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer -/// change or it is explicitly cleared. -/// -/// [`Layer`]: ../trait.Layer.html -/// [`Cache`]: struct.Cache.html -#[derive(Debug)] -pub struct Cache<T: Drawable> { -    input: PhantomData<T>, -    state: RefCell<State>, -} - -impl<T> Default for Cache<T> -where -    T: Drawable, -{ -    fn default() -> Self { -        Self { -            input: PhantomData, -            state: Default::default(), -        } -    } -} - -impl<T> Cache<T> -where -    T: Drawable + std::fmt::Debug, -{ -    /// Creates a new empty [`Cache`]. -    /// -    /// [`Cache`]: struct.Cache.html -    pub fn new() -> Self { -        Cache { -            input: PhantomData, -            state: Default::default(), -        } -    } - -    /// Clears the cache, forcing a redraw the next time it is used. -    /// -    /// [`Cached`]: struct.Cached.html -    pub fn clear(&mut self) { -        *self.state.borrow_mut() = State::Empty; -    } - -    /// Binds the [`Cache`] with some data, producing a [`Layer`] that can be -    /// added to a [`Canvas`]. -    /// -    /// [`Cache`]: struct.Cache.html -    /// [`Layer`]: ../trait.Layer.html -    /// [`Canvas`]: ../../struct.Canvas.html -    pub fn with<'a>(&'a self, input: &'a T) -> impl Layer + 'a { -        Bind { -            cache: self, -            input: input, -        } -    } -} - -#[derive(Debug)] -struct Bind<'a, T: Drawable> { -    cache: &'a Cache<T>, -    input: &'a T, -} - -impl<'a, T> Layer for Bind<'a, T> -where -    T: Drawable + std::fmt::Debug, -{ -    fn draw(&self, current_bounds: Size) -> Arc<Primitive> { -        use std::ops::Deref; - -        if let State::Filled { bounds, primitive } = -            self.cache.state.borrow().deref() -        { -            if *bounds == current_bounds { -                return primitive.clone(); -            } -        } - -        let mut frame = Frame::new(current_bounds.width, current_bounds.height); -        self.input.draw(&mut frame); - -        let primitive = Arc::new(frame.into_primitive()); - -        *self.cache.state.borrow_mut() = State::Filled { -            bounds: current_bounds, -            primitive: primitive.clone(), -        }; - -        primitive -    } -} - -impl std::fmt::Debug for State { -    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -        match self { -            State::Empty => write!(f, "Empty"), -            State::Filled { primitive, bounds } => f -                .debug_struct("Filled") -                .field("primitive", primitive) -                .field("bounds", bounds) -                .finish(), -        } -    } -} diff --git a/wgpu/src/widget/canvas/program.rs b/wgpu/src/widget/canvas/program.rs new file mode 100644 index 00000000..f8e54514 --- /dev/null +++ b/wgpu/src/widget/canvas/program.rs @@ -0,0 +1,41 @@ +use crate::canvas::{Cursor, Event, Geometry}; +use iced_native::{MouseCursor, Rectangle}; + +pub trait Program<Message> { +    fn update( +        &mut self, +        _event: Event, +        _bounds: Rectangle, +        _cursor: Cursor, +    ) -> Option<Message> { +        None +    } + +    fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry>; + +    fn mouse_cursor(&self, _bounds: Rectangle, _cursor: Cursor) -> MouseCursor { +        MouseCursor::default() +    } +} + +impl<T, Message> Program<Message> for &mut T +where +    T: Program<Message>, +{ +    fn update( +        &mut self, +        event: Event, +        bounds: Rectangle, +        cursor: Cursor, +    ) -> Option<Message> { +        T::update(self, event, bounds, cursor) +    } + +    fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry> { +        T::draw(self, bounds, cursor) +    } + +    fn mouse_cursor(&self, bounds: Rectangle, cursor: Cursor) -> MouseCursor { +        T::mouse_cursor(self, bounds, cursor) +    } +} diff --git a/wgpu/src/widget/canvas/stroke.rs b/wgpu/src/widget/canvas/stroke.rs index 46d669c4..e20379cd 100644 --- a/wgpu/src/widget/canvas/stroke.rs +++ b/wgpu/src/widget/canvas/stroke.rs @@ -14,6 +14,24 @@ pub struct Stroke {      pub line_join: LineJoin,  } +impl Stroke { +    pub fn with_color(self, color: Color) -> Stroke { +        Stroke { color, ..self } +    } + +    pub fn with_width(self, width: f32) -> Stroke { +        Stroke { width, ..self } +    } + +    pub fn with_line_cap(self, line_cap: LineCap) -> Stroke { +        Stroke { line_cap, ..self } +    } + +    pub fn with_line_join(self, line_join: LineJoin) -> Stroke { +        Stroke { line_join, ..self } +    } +} +  impl Default for Stroke {      fn default() -> Stroke {          Stroke { diff --git a/winit/src/application.rs b/winit/src/application.rs index b974711c..ae9775f7 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -205,7 +205,7 @@ pub trait Application: Sized {          let mut cache = Some(user_interface.into_cache());          let mut events = Vec::new(); -        let mut mouse_cursor = MouseCursor::OutOfBounds; +        let mut mouse_cursor = MouseCursor::default();          let mut modifiers = winit::event::ModifiersState::default();          debug.startup_finished(); diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index eaa26ace..a9d9b7cd 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -110,12 +110,12 @@ pub fn fullscreen(  /// [`iced_native`]: https://github.com/hecrj/iced/tree/master/native  pub fn mouse_cursor(mouse_cursor: MouseCursor) -> winit::window::CursorIcon {      match mouse_cursor { -        MouseCursor::OutOfBounds => winit::window::CursorIcon::Default,          MouseCursor::Idle => winit::window::CursorIcon::Default,          MouseCursor::Pointer => winit::window::CursorIcon::Hand,          MouseCursor::Working => winit::window::CursorIcon::Progress,          MouseCursor::Grab => winit::window::CursorIcon::Grab,          MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, +        MouseCursor::Crosshair => winit::window::CursorIcon::Crosshair,          MouseCursor::Text => winit::window::CursorIcon::Text,          MouseCursor::ResizingHorizontally => {              winit::window::CursorIcon::EwResize  | 
