diff options
| author | 2020-05-05 00:05:47 +0200 | |
|---|---|---|
| committer | 2020-05-05 00:05:47 +0200 | |
| commit | 7dc02a5e16a3143b7c3ba9270207e3ebda71d567 (patch) | |
| tree | dd727f138641fbda008af8e7827369cc99420749 /wgpu/src | |
| parent | 27aad74a32fd8ac2b12f9d32df8a3b61a3175457 (diff) | |
| parent | 93c6be5eef577f0778b5787dac37351c035ed471 (diff) | |
| download | iced-7dc02a5e16a3143b7c3ba9270207e3ebda71d567.tar.gz iced-7dc02a5e16a3143b7c3ba9270207e3ebda71d567.tar.bz2 iced-7dc02a5e16a3143b7c3ba9270207e3ebda71d567.zip | |
Merge pull request #325 from hecrj/feature/canvas-interaction
Canvas interactivity and Game of Life example
Diffstat (limited to '')
30 files changed, 650 insertions, 353 deletions
| 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..71b4af38 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -7,8 +7,7 @@ use crate::{  use crate::image::{self, Image};  use iced_native::{ -    layout, Background, Color, Layout, MouseCursor, Point, Rectangle, Vector, -    Widget, +    layout, mouse, Background, Color, Layout, Point, Rectangle, Vector, Widget,  };  mod widget; @@ -29,7 +28,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 +47,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 { @@ -88,10 +93,10 @@ impl Renderer {          device: &wgpu::Device,          encoder: &mut wgpu::CommandEncoder,          target: Target<'_>, -        (primitive, mouse_cursor): &(Primitive, MouseCursor), +        (primitive, mouse_interaction): &(Primitive, mouse::Interaction),          scale_factor: f64,          overlay: &[T], -    ) -> MouseCursor { +    ) -> mouse::Interaction {          log::debug!("Drawing");          let (width, height) = target.viewport.dimensions(); @@ -126,7 +131,7 @@ impl Renderer {          #[cfg(any(feature = "image", feature = "svg"))]          self.image_pipeline.trim_cache(); -        *mouse_cursor +        *mouse_interaction      }      fn draw_primitive<'a>( @@ -214,10 +219,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 +241,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 +258,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 +377,8 @@ impl Renderer {                  target_width,                  target_height,                  scaled, +                scale_factor,                  &layer.meshes, -                bounds,              );          } @@ -437,7 +452,7 @@ impl Renderer {  }  impl iced_native::Renderer for Renderer { -    type Output = (Primitive, MouseCursor); +    type Output = (Primitive, mouse::Interaction);      type Defaults = Defaults;      fn layout<'a, Message>( diff --git a/wgpu/src/renderer/widget/button.rs b/wgpu/src/renderer/widget/button.rs index 359b4866..eb225038 100644 --- a/wgpu/src/renderer/widget/button.rs +++ b/wgpu/src/renderer/widget/button.rs @@ -1,6 +1,6 @@  use crate::{button::StyleSheet, defaults, Defaults, Primitive, Renderer};  use iced_native::{ -    Background, Color, Element, Layout, MouseCursor, Point, Rectangle, Vector, +    mouse, Background, Color, Element, Layout, Point, Rectangle, Vector,  };  impl iced_native::button::Renderer for Renderer { @@ -84,9 +84,9 @@ impl iced_native::button::Renderer for Renderer {                  content              },              if is_mouse_over { -                MouseCursor::Pointer +                mouse::Interaction::Pointer              } else { -                MouseCursor::OutOfBounds +                mouse::Interaction::default()              },          )      } diff --git a/wgpu/src/renderer/widget/checkbox.rs b/wgpu/src/renderer/widget/checkbox.rs index c0f1bf21..0340bf62 100644 --- a/wgpu/src/renderer/widget/checkbox.rs +++ b/wgpu/src/renderer/widget/checkbox.rs @@ -1,6 +1,6 @@  use crate::{checkbox::StyleSheet, Primitive, Renderer};  use iced_native::{ -    checkbox, HorizontalAlignment, MouseCursor, Rectangle, VerticalAlignment, +    checkbox, mouse, HorizontalAlignment, Rectangle, VerticalAlignment,  };  impl checkbox::Renderer for Renderer { @@ -54,9 +54,9 @@ impl checkbox::Renderer for Renderer {                  },              },              if is_mouse_over { -                MouseCursor::Pointer +                mouse::Interaction::Pointer              } else { -                MouseCursor::OutOfBounds +                mouse::Interaction::default()              },          )      } diff --git a/wgpu/src/renderer/widget/column.rs b/wgpu/src/renderer/widget/column.rs index 95a7463a..b853276d 100644 --- a/wgpu/src/renderer/widget/column.rs +++ b/wgpu/src/renderer/widget/column.rs @@ -1,5 +1,5 @@  use crate::{Primitive, Renderer}; -use iced_native::{column, Element, Layout, MouseCursor, Point}; +use iced_native::{column, mouse, Element, Layout, Point};  impl column::Renderer for Renderer {      fn draw<Message>( @@ -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_interaction = mouse::Interaction::default();          (              Primitive::Group { @@ -17,18 +17,18 @@ impl column::Renderer for Renderer {                      .iter()                      .zip(layout.children())                      .map(|(child, layout)| { -                        let (primitive, new_mouse_cursor) = +                        let (primitive, new_mouse_interaction) =                              child.draw(self, defaults, layout, cursor_position); -                        if new_mouse_cursor > mouse_cursor { -                            mouse_cursor = new_mouse_cursor; +                        if new_mouse_interaction > mouse_interaction { +                            mouse_interaction = new_mouse_interaction;                          }                          primitive                      })                      .collect(),              }, -            mouse_cursor, +            mouse_interaction,          )      }  } diff --git a/wgpu/src/renderer/widget/container.rs b/wgpu/src/renderer/widget/container.rs index dda7dc8a..30cc3f07 100644 --- a/wgpu/src/renderer/widget/container.rs +++ b/wgpu/src/renderer/widget/container.rs @@ -21,7 +21,7 @@ impl iced_native::container::Renderer for Renderer {              },          }; -        let (content, mouse_cursor) = +        let (content, mouse_interaction) =              content.draw(self, &defaults, content_layout, cursor_position);          if style.background.is_some() || style.border_width > 0 { @@ -39,10 +39,10 @@ impl iced_native::container::Renderer for Renderer {                  Primitive::Group {                      primitives: vec![quad, content],                  }, -                mouse_cursor, +                mouse_interaction,              )          } else { -            (content, mouse_cursor) +            (content, mouse_interaction)          }      }  } diff --git a/wgpu/src/renderer/widget/image.rs b/wgpu/src/renderer/widget/image.rs index 70dc5d97..c4c04984 100644 --- a/wgpu/src/renderer/widget/image.rs +++ b/wgpu/src/renderer/widget/image.rs @@ -1,5 +1,5 @@  use crate::{Primitive, Renderer}; -use iced_native::{image, Layout, MouseCursor}; +use iced_native::{image, mouse, Layout};  impl image::Renderer for Renderer {      fn dimensions(&self, handle: &image::Handle) -> (u32, u32) { @@ -16,7 +16,7 @@ impl image::Renderer for Renderer {                  handle,                  bounds: layout.bounds(),              }, -            MouseCursor::OutOfBounds, +            mouse::Interaction::default(),          )      }  } diff --git a/wgpu/src/renderer/widget/pane_grid.rs b/wgpu/src/renderer/widget/pane_grid.rs index 2d201fec..2253e4af 100644 --- a/wgpu/src/renderer/widget/pane_grid.rs +++ b/wgpu/src/renderer/widget/pane_grid.rs @@ -1,7 +1,8 @@  use crate::{Primitive, Renderer};  use iced_native::{ +    mouse,      pane_grid::{self, Axis, Pane}, -    Element, Layout, MouseCursor, Point, Rectangle, Vector, +    Element, Layout, Point, Rectangle, Vector,  };  impl pane_grid::Renderer for Renderer { @@ -22,7 +23,7 @@ impl pane_grid::Renderer for Renderer {              cursor_position          }; -        let mut mouse_cursor = MouseCursor::OutOfBounds; +        let mut mouse_interaction = mouse::Interaction::default();          let mut dragged_pane = None;          let mut panes: Vec<_> = content @@ -30,11 +31,11 @@ impl pane_grid::Renderer for Renderer {              .zip(layout.children())              .enumerate()              .map(|(i, ((id, pane), layout))| { -                let (primitive, new_mouse_cursor) = +                let (primitive, new_mouse_interaction) =                      pane.draw(self, defaults, layout, pane_cursor_position); -                if new_mouse_cursor > mouse_cursor { -                    mouse_cursor = new_mouse_cursor; +                if new_mouse_interaction > mouse_interaction { +                    mouse_interaction = new_mouse_interaction;                  }                  if Some(*id) == dragging { @@ -59,12 +60,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),                  }),              }; @@ -78,14 +79,14 @@ impl pane_grid::Renderer for Renderer {          (              Primitive::Group { primitives },              if dragging.is_some() { -                MouseCursor::Grabbing +                mouse::Interaction::Grabbing              } else if let Some(axis) = resizing {                  match axis { -                    Axis::Horizontal => MouseCursor::ResizingVertically, -                    Axis::Vertical => MouseCursor::ResizingHorizontally, +                    Axis::Horizontal => mouse::Interaction::ResizingVertically, +                    Axis::Vertical => mouse::Interaction::ResizingHorizontally,                  }              } else { -                mouse_cursor +                mouse_interaction              },          )      } diff --git a/wgpu/src/renderer/widget/progress_bar.rs b/wgpu/src/renderer/widget/progress_bar.rs index 34e33276..2baeeb14 100644 --- a/wgpu/src/renderer/widget/progress_bar.rs +++ b/wgpu/src/renderer/widget/progress_bar.rs @@ -1,5 +1,5 @@  use crate::{progress_bar::StyleSheet, Primitive, Renderer}; -use iced_native::{progress_bar, Color, MouseCursor, Rectangle}; +use iced_native::{mouse, progress_bar, Color, Rectangle};  impl progress_bar::Renderer for Renderer {      type Style = Box<dyn StyleSheet>; @@ -48,7 +48,7 @@ impl progress_bar::Renderer for Renderer {              } else {                  background              }, -            MouseCursor::OutOfBounds, +            mouse::Interaction::default(),          )      }  } diff --git a/wgpu/src/renderer/widget/radio.rs b/wgpu/src/renderer/widget/radio.rs index 564f066b..2f1461db 100644 --- a/wgpu/src/renderer/widget/radio.rs +++ b/wgpu/src/renderer/widget/radio.rs @@ -1,5 +1,5 @@  use crate::{radio::StyleSheet, Primitive, Renderer}; -use iced_native::{radio, Background, Color, MouseCursor, Rectangle}; +use iced_native::{mouse, radio, Background, Color, Rectangle};  const SIZE: f32 = 28.0;  const DOT_SIZE: f32 = SIZE / 2.0; @@ -55,9 +55,9 @@ impl radio::Renderer for Renderer {                  },              },              if is_mouse_over { -                MouseCursor::Pointer +                mouse::Interaction::Pointer              } else { -                MouseCursor::OutOfBounds +                mouse::Interaction::default()              },          )      } diff --git a/wgpu/src/renderer/widget/row.rs b/wgpu/src/renderer/widget/row.rs index bd9f1a04..d0b7ef09 100644 --- a/wgpu/src/renderer/widget/row.rs +++ b/wgpu/src/renderer/widget/row.rs @@ -1,5 +1,5 @@  use crate::{Primitive, Renderer}; -use iced_native::{row, Element, Layout, MouseCursor, Point}; +use iced_native::{mouse, row, Element, Layout, Point};  impl row::Renderer for Renderer {      fn draw<Message>( @@ -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_interaction = mouse::Interaction::default();          (              Primitive::Group { @@ -17,18 +17,18 @@ impl row::Renderer for Renderer {                      .iter()                      .zip(layout.children())                      .map(|(child, layout)| { -                        let (primitive, new_mouse_cursor) = +                        let (primitive, new_mouse_interaction) =                              child.draw(self, defaults, layout, cursor_position); -                        if new_mouse_cursor > mouse_cursor { -                            mouse_cursor = new_mouse_cursor; +                        if new_mouse_interaction > mouse_interaction { +                            mouse_interaction = new_mouse_interaction;                          }                          primitive                      })                      .collect(),              }, -            mouse_cursor, +            mouse_interaction,          )      }  } diff --git a/wgpu/src/renderer/widget/scrollable.rs b/wgpu/src/renderer/widget/scrollable.rs index 732523e3..8a400b82 100644 --- a/wgpu/src/renderer/widget/scrollable.rs +++ b/wgpu/src/renderer/widget/scrollable.rs @@ -1,7 +1,5 @@  use crate::{Primitive, Renderer}; -use iced_native::{ -    scrollable, Background, Color, MouseCursor, Rectangle, Vector, -}; +use iced_native::{mouse, scrollable, Background, Color, Rectangle, Vector};  const SCROLLBAR_WIDTH: u16 = 10;  const SCROLLBAR_MARGIN: u16 = 2; @@ -56,7 +54,7 @@ impl scrollable::Renderer for Renderer {          scrollbar: Option<scrollable::Scrollbar>,          offset: u32,          style_sheet: &Self::Style, -        (content, mouse_cursor): Self::Output, +        (content, mouse_interaction): Self::Output,      ) -> Self::Output {          (              if let Some(scrollbar) = scrollbar { @@ -118,9 +116,9 @@ impl scrollable::Renderer for Renderer {                  content              },              if is_mouse_over_scrollbar || state.is_scroller_grabbed() { -                MouseCursor::Idle +                mouse::Interaction::Idle              } else { -                mouse_cursor +                mouse_interaction              },          )      } diff --git a/wgpu/src/renderer/widget/slider.rs b/wgpu/src/renderer/widget/slider.rs index c8ebd0da..220feace 100644 --- a/wgpu/src/renderer/widget/slider.rs +++ b/wgpu/src/renderer/widget/slider.rs @@ -2,7 +2,7 @@ use crate::{      slider::{HandleShape, StyleSheet},      Primitive, Renderer,  }; -use iced_native::{slider, Background, Color, MouseCursor, Point, Rectangle}; +use iced_native::{mouse, slider, Background, Color, Point, Rectangle};  const HANDLE_HEIGHT: f32 = 22.0; @@ -95,11 +95,11 @@ impl slider::Renderer for Renderer {                  primitives: vec![rail_top, rail_bottom, handle],              },              if is_dragging { -                MouseCursor::Grabbing +                mouse::Interaction::Grabbing              } else if is_mouse_over { -                MouseCursor::Grab +                mouse::Interaction::Grab              } else { -                MouseCursor::OutOfBounds +                mouse::Interaction::default()              },          )      } diff --git a/wgpu/src/renderer/widget/space.rs b/wgpu/src/renderer/widget/space.rs index 28e05437..225f7e6c 100644 --- a/wgpu/src/renderer/widget/space.rs +++ b/wgpu/src/renderer/widget/space.rs @@ -1,8 +1,8 @@  use crate::{Primitive, Renderer}; -use iced_native::{space, MouseCursor, Rectangle}; +use iced_native::{mouse, space, Rectangle};  impl space::Renderer for Renderer {      fn draw(&mut self, _bounds: Rectangle) -> Self::Output { -        (Primitive::None, MouseCursor::OutOfBounds) +        (Primitive::None, mouse::Interaction::default())      }  } diff --git a/wgpu/src/renderer/widget/svg.rs b/wgpu/src/renderer/widget/svg.rs index 67bc3fe1..f6d6d0ba 100644 --- a/wgpu/src/renderer/widget/svg.rs +++ b/wgpu/src/renderer/widget/svg.rs @@ -1,5 +1,5 @@  use crate::{Primitive, Renderer}; -use iced_native::{svg, Layout, MouseCursor}; +use iced_native::{mouse, svg, Layout};  impl svg::Renderer for Renderer {      fn dimensions(&self, handle: &svg::Handle) -> (u32, u32) { @@ -16,7 +16,7 @@ impl svg::Renderer for Renderer {                  handle,                  bounds: layout.bounds(),              }, -            MouseCursor::OutOfBounds, +            mouse::Interaction::default(),          )      }  } diff --git a/wgpu/src/renderer/widget/text.rs b/wgpu/src/renderer/widget/text.rs index f27cc430..4605ed06 100644 --- a/wgpu/src/renderer/widget/text.rs +++ b/wgpu/src/renderer/widget/text.rs @@ -1,6 +1,6 @@  use crate::{Primitive, Renderer};  use iced_native::{ -    text, Color, Font, HorizontalAlignment, MouseCursor, Rectangle, Size, +    mouse, text, Color, Font, HorizontalAlignment, Rectangle, Size,      VerticalAlignment,  }; @@ -55,7 +55,7 @@ impl text::Renderer for Renderer {                  horizontal_alignment,                  vertical_alignment,              }, -            MouseCursor::OutOfBounds, +            mouse::Interaction::default(),          )      }  } diff --git a/wgpu/src/renderer/widget/text_input.rs b/wgpu/src/renderer/widget/text_input.rs index 6f72db68..57be6692 100644 --- a/wgpu/src/renderer/widget/text_input.rs +++ b/wgpu/src/renderer/widget/text_input.rs @@ -1,9 +1,10 @@  use crate::{text_input::StyleSheet, Primitive, Renderer};  use iced_native::{ +    mouse,      text_input::{self, cursor}, -    Background, Color, Font, HorizontalAlignment, MouseCursor, Point, -    Rectangle, Size, Vector, VerticalAlignment, +    Background, Color, Font, HorizontalAlignment, Point, Rectangle, Size, +    Vector, VerticalAlignment,  };  use std::f32; @@ -232,9 +233,9 @@ impl text_input::Renderer for Renderer {                  primitives: vec![input, contents],              },              if is_mouse_over { -                MouseCursor::Text +                mouse::Interaction::Text              } else { -                MouseCursor::OutOfBounds +                mouse::Interaction::default()              },          )      } diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 86c74fcd..3e68a269 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -1,14 +1,14 @@  //! Draw meshes of triangles.  use crate::{settings, Transformation}; -use iced_native::{Point, Rectangle}; +use iced_native::{Rectangle, Vector};  use std::mem;  use zerocopy::AsBytes;  mod msaa;  const UNIFORM_BUFFER_SIZE: usize = 100; -const VERTEX_BUFFER_SIZE: usize = 100_000; -const INDEX_BUFFER_SIZE: usize = 100_000; +const VERTEX_BUFFER_SIZE: usize = 10_000; +const INDEX_BUFFER_SIZE: usize = 10_000;  #[derive(Debug)]  pub(crate) struct Pipeline { @@ -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..2fc10ea0 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, mouse, Clipboard, Element, Hasher, Layout, Length, 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 @@ -45,12 +48,15 @@ pub use text::Text;  ///  /// - [`clock`], an application that uses the [`Canvas`] widget to draw a clock  /// and its hands to display the current time. +/// - [`game_of_life`], an interactive version of the Game of Life, invented by +/// John Conway.  /// - [`solar_system`], an animated solar system drawn using the [`Canvas`] widget  /// and showcasing how to compose different transforms.  /// -/// [examples]: https://github.com/hecrj/iced/tree/0.1/examples -/// [`clock`]: https://github.com/hecrj/iced/tree/0.1/examples/clock -/// [`solar_system`]: https://github.com/hecrj/iced/tree/0.1/examples/solar_system +/// [examples]: https://github.com/hecrj/iced/tree/master/examples +/// [`clock`]: https://github.com/hecrj/iced/tree/master/examples/clock +/// [`game_of_life`]: https://github.com/hecrj/iced/tree/master/examples/game_of_life +/// [`solar_system`]: https://github.com/hecrj/iced/tree/master/examples/solar_system  ///  /// ## Drawing a simple circle  /// If you want to get a quick overview, here's how we can draw a simple circle: @@ -58,10 +64,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 +75,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 `Circle` 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 +133,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 +157,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, -    ) -> (Primitive, MouseCursor) { +        cursor_position: Point, +    ) -> (Primitive, mouse::Interaction) {          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_interaction(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..4b28d164 --- /dev/null +++ b/wgpu/src/widget/canvas/cache.rs @@ -0,0 +1,108 @@ +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 +/// [`Geometry`]: struct.Geometry.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. +    /// +    /// [`Cache`]: struct.Cache.html +    pub fn clear(&mut self) { +        *self.state.borrow_mut() = State::Empty; +    } + +    /// Draws [`Geometry`] using the provided closure and stores it in the +    /// [`Cache`]. +    /// +    /// The closure will only be called when +    /// - the bounds have changed since the previous draw call. +    /// - the [`Cache`] is empty or has been explicitly cleared. +    /// +    /// Otherwise, the previously stored [`Geometry`] will be returned. The +    /// [`Cache`] is not cleared in this case. In other words, it will keep +    /// returning the stored [`Geometry`] if needed. +    /// +    /// [`Cache`]: struct.Cache.html +    pub fn draw(&self, bounds: Size, draw_fn: impl Fn(&mut Frame)) -> Geometry { +        use std::ops::Deref; + +        if let State::Filled { +            bounds: cached_bounds, +            primitive, +        } = self.state.borrow().deref() +        { +            if *cached_bounds == bounds { +                return Geometry::from_primitive(Primitive::Cached { +                    cache: primitive.clone(), +                }); +            } +        } + +        let mut frame = Frame::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, +            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..456760ea --- /dev/null +++ b/wgpu/src/widget/canvas/cursor.rs @@ -0,0 +1,72 @@ +use iced_native::{Point, Rectangle}; + +/// The mouse cursor state. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Cursor { +    /// The cursor has a defined position. +    Available(Point), + +    /// The cursor is currently unavailable (i.e. out of bounds or busy). +    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) +        } +    } + +    /// Returns the absolute position of the [`Cursor`], if available. +    /// +    /// [`Cursor`]: enum.Cursor.html +    pub fn position(&self) -> Option<Point> { +        match self { +            Cursor::Available(position) => Some(*position), +            Cursor::Unavailable => None, +        } +    } + +    /// Returns the relative position of the [`Cursor`] inside the given bounds, +    /// if available. +    /// +    /// If the [`Cursor`] is not over the provided bounds, this method will +    /// return `None`. +    /// +    /// [`Cursor`]: enum.Cursor.html +    pub fn position_in(&self, bounds: &Rectangle) -> Option<Point> { +        if self.is_over(bounds) { +            self.position_from(bounds.position()) +        } else { +            None +        } +    } + +    /// Returns the relative position of the [`Cursor`] from the given origin, +    /// if available. +    /// +    /// [`Cursor`]: enum.Cursor.html +    pub fn position_from(&self, origin: Point) -> Option<Point> { +        match self { +            Cursor::Available(position) => { +                Some(Point::new(position.x - origin.x, position.y - origin.y)) +            } +            Cursor::Unavailable => None, +        } +    } + +    /// Returns whether the [`Cursor`] is currently over the provided bounds +    /// or not. +    /// +    /// [`Cursor`]: enum.Cursor.html +    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..ad11f51e --- /dev/null +++ b/wgpu/src/widget/canvas/event.rs @@ -0,0 +1,10 @@ +use iced_native::mouse; + +/// A [`Canvas`] event. +/// +/// [`Canvas`]: struct.Event.html +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Event { +    /// A mouse event. +    Mouse(mouse::Event), +} diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index de4717f1..5262ab4e 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 @@ -122,6 +120,43 @@ impl Frame {          let _ = result.expect("Tessellate path");      } +    /// Draws an axis-aligned rectangle given its top-left corner coordinate and +    /// its `Size` on the [`Frame`] by filling it with the provided style. +    /// +    /// [`Frame`]: struct.Frame.html +    pub fn fill_rectangle( +        &mut self, +        top_left: Point, +        size: Size, +        fill: impl Into<Fill>, +    ) { +        use lyon::tessellation::{BuffersBuilder, FillOptions}; + +        let mut buffers = BuffersBuilder::new( +            &mut self.buffers, +            FillVertex(match fill.into() { +                Fill::Color(color) => color.into_linear(), +            }), +        ); + +        let top_left = +            self.transforms.current.raw.transform_point( +                lyon::math::Point::new(top_left.x, top_left.y), +            ); + +        let size = +            self.transforms.current.raw.transform_vector( +                lyon::math::Vector::new(size.width, size.height), +            ); + +        let _ = lyon::tessellation::basic_shapes::fill_rectangle( +            &lyon::math::Rect::new(top_left, size.into()), +            &FillOptions::default(), +            &mut buffers, +        ) +        .expect("Fill rectangle"); +    } +      /// Draws the stroke of the given [`Path`] on the [`Frame`] with the      /// provided style.      /// @@ -262,13 +297,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,14 +312,28 @@ impl Frame {              });          } -        Primitive::Group { +        Geometry::from_primitive(Primitive::Group {              primitives: self.primitives, -        } +        })      }  }  struct FillVertex([f32; 4]); +impl lyon::tessellation::BasicVertexConstructor<triangle::Vertex2D> +    for FillVertex +{ +    fn new_vertex( +        &mut self, +        position: lyon::math::Point, +    ) -> triangle::Vertex2D { +        triangle::Vertex2D { +            position: [position.x, position.y], +            color: self.0, +        } +    } +} +  impl lyon::tessellation::FillVertexConstructor<triangle::Vertex2D>      for FillVertex  { diff --git a/wgpu/src/widget/canvas/geometry.rs b/wgpu/src/widget/canvas/geometry.rs new file mode 100644 index 00000000..4cadee39 --- /dev/null +++ b/wgpu/src/widget/canvas/geometry.rs @@ -0,0 +1,34 @@ +use crate::Primitive; + +/// A bunch of shapes that can be drawn. +/// +/// [`Geometry`] can be easily generated with a [`Frame`] or stored in a +/// [`Cache`]. +/// +/// [`Geometry`]: struct.Geometry.html +/// [`Frame`]: struct.Frame.html +/// [`Cache`]: struct.Cache.html +#[derive(Debug, Clone)] +pub struct Geometry(Primitive); + +impl Geometry { +    pub(crate) fn from_primitive(primitive: Primitive) -> Self { +        Self(primitive) +    } + +    /// Turns the [`Geometry`] into a [`Primitive`]. +    /// +    /// This can be useful if you are building a custom widget. +    /// +    /// [`Geometry`]: struct.Geometry.html +    /// [`Primitive`]: ../enum.Primitive.html +    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..725d9d72 --- /dev/null +++ b/wgpu/src/widget/canvas/program.rs @@ -0,0 +1,85 @@ +use crate::canvas::{Cursor, Event, Geometry}; +use iced_native::{mouse, Rectangle}; + +/// The state and logic of a [`Canvas`]. +/// +/// A [`Program`] can mutate internal state and produce messages for an +/// application. +/// +/// [`Canvas`]: struct.Canvas.html +/// [`Program`]: trait.Program.html +pub trait Program<Message> { +    /// Updates the state of the [`Program`]. +    /// +    /// When a [`Program`] is used in a [`Canvas`], the runtime will call this +    /// method for each [`Event`]. +    /// +    /// This method can optionally return a `Message` to notify an application +    /// of any meaningful interactions. +    /// +    /// By default, this method does and returns nothing. +    /// +    /// [`Program`]: trait.Program.html +    /// [`Canvas`]: struct.Canvas.html +    /// [`Event`]: enum.Event.html +    fn update( +        &mut self, +        _event: Event, +        _bounds: Rectangle, +        _cursor: Cursor, +    ) -> Option<Message> { +        None +    } + +    /// Draws the state of the [`Program`], producing a bunch of [`Geometry`]. +    /// +    /// [`Geometry`] can be easily generated with a [`Frame`] or stored in a +    /// [`Cache`]. +    /// +    /// [`Program`]: trait.Program.html +    /// [`Geometry`]: struct.Geometry.html +    /// [`Frame`]: struct.Frame.html +    /// [`Cache`]: struct.Cache.html +    fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry>; + +    /// Returns the current mouse interaction of the [`Program`]. +    /// +    /// The interaction returned will be in effect even if the cursor position +    /// is out of bounds of the program's [`Canvas`]. +    /// +    /// [`Program`]: trait.Program.html +    /// [`Canvas`]: struct.Canvas.html +    fn mouse_interaction( +        &self, +        _bounds: Rectangle, +        _cursor: Cursor, +    ) -> mouse::Interaction { +        mouse::Interaction::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_interaction( +        &self, +        bounds: Rectangle, +        cursor: Cursor, +    ) -> mouse::Interaction { +        T::mouse_interaction(self, bounds, cursor) +    } +} diff --git a/wgpu/src/widget/canvas/stroke.rs b/wgpu/src/widget/canvas/stroke.rs index 46d669c4..5b6fc56a 100644 --- a/wgpu/src/widget/canvas/stroke.rs +++ b/wgpu/src/widget/canvas/stroke.rs @@ -14,6 +14,38 @@ pub struct Stroke {      pub line_join: LineJoin,  } +impl Stroke { +    /// Sets the color of the [`Stroke`]. +    /// +    /// [`Stroke`]: struct.Stroke.html +    pub fn with_color(self, color: Color) -> Stroke { +        Stroke { color, ..self } +    } + +    /// Sets the width of the [`Stroke`]. +    /// +    /// [`Stroke`]: struct.Stroke.html +    pub fn with_width(self, width: f32) -> Stroke { +        Stroke { width, ..self } +    } + +    /// Sets the [`LineCap`] of the [`Stroke`]. +    /// +    /// [`LineCap`]: enum.LineCap.html +    /// [`Stroke`]: struct.Stroke.html +    pub fn with_line_cap(self, line_cap: LineCap) -> Stroke { +        Stroke { line_cap, ..self } +    } + +    /// Sets the [`LineJoin`] of the [`Stroke`]. +    /// +    /// [`LineJoin`]: enum.LineJoin.html +    /// [`Stroke`]: struct.Stroke.html +    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/wgpu/src/window/backend.rs b/wgpu/src/window/backend.rs index e1b77700..7e40ae0c 100644 --- a/wgpu/src/window/backend.rs +++ b/wgpu/src/window/backend.rs @@ -1,6 +1,6 @@  use crate::{window::SwapChain, Renderer, Settings, Target}; -use iced_native::{futures, MouseCursor}; +use iced_native::{futures, mouse};  use raw_window_handle::HasRawWindowHandle;  /// A window graphics backend for iced powered by `wgpu`. @@ -78,7 +78,7 @@ impl iced_native::window::Backend for Backend {          output: &<Self::Renderer as iced_native::Renderer>::Output,          scale_factor: f64,          overlay: &[T], -    ) -> MouseCursor { +    ) -> mouse::Interaction {          let (frame, viewport) = swap_chain.next_frame().expect("Next frame");          let mut encoder = self.device.create_command_encoder( @@ -101,7 +101,7 @@ impl iced_native::window::Backend for Backend {              depth_stencil_attachment: None,          }); -        let mouse_cursor = renderer.draw( +        let mouse_interaction = renderer.draw(              &mut self.device,              &mut encoder,              Target { @@ -115,6 +115,6 @@ impl iced_native::window::Backend for Backend {          self.queue.submit(&[encoder.finish()]); -        mouse_cursor +        mouse_interaction      }  } | 
