diff options
Diffstat (limited to '')
| -rw-r--r-- | graphics/src/widget/canvas.rs | 8 | ||||
| -rw-r--r-- | graphics/src/widget/canvas/fill.rs | 30 | ||||
| -rw-r--r-- | graphics/src/widget/canvas/frame.rs | 260 | ||||
| -rw-r--r-- | graphics/src/widget/canvas/stroke.rs | 18 | ||||
| -rw-r--r-- | graphics/src/widget/canvas/style.rs | 23 | 
5 files changed, 266 insertions, 73 deletions
| diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index b4afd998..b070d0a6 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -3,19 +3,20 @@  //! A [`Canvas`] widget can be used to draw different kinds of 2D shapes in a  //! [`Frame`]. It can be used for animation, data visualization, game graphics,  //! and more! -  pub mod event; +pub mod fill;  pub mod path; +pub mod stroke;  mod cache;  mod cursor; -mod fill;  mod frame;  mod geometry;  mod program; -mod stroke; +mod style;  mod text; +pub use crate::gradient::{self, Gradient};  pub use cache::Cache;  pub use cursor::Cursor;  pub use event::Event; @@ -25,6 +26,7 @@ pub use geometry::Geometry;  pub use path::Path;  pub use program::Program;  pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; +pub use style::Style;  pub use text::Text;  use crate::{Backend, Primitive, Renderer}; diff --git a/graphics/src/widget/canvas/fill.rs b/graphics/src/widget/canvas/fill.rs index 56495435..e954ebb5 100644 --- a/graphics/src/widget/canvas/fill.rs +++ b/graphics/src/widget/canvas/fill.rs @@ -1,12 +1,15 @@ -use iced_native::Color; +//! Fill [crate::widget::canvas::Geometry] with a certain style. +use crate::{Color, Gradient}; + +pub use crate::widget::canvas::Style;  /// The style used to fill geometry. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)]  pub struct Fill { -    /// The color used to fill geometry. +    /// The color or gradient of the fill.      /// -    /// By default, it is set to `BLACK`. -    pub color: Color, +    /// By default, it is set to [`Style::Solid`] with [`Color::BLACK`]. +    pub style: Style,      /// The fill rule defines how to determine what is inside and what is      /// outside of a shape. @@ -20,9 +23,9 @@ pub struct Fill {  }  impl Default for Fill { -    fn default() -> Fill { -        Fill { -            color: Color::BLACK, +    fn default() -> Self { +        Self { +            style: Style::Solid(Color::BLACK),              rule: FillRule::NonZero,          }      } @@ -31,12 +34,21 @@ impl Default for Fill {  impl From<Color> for Fill {      fn from(color: Color) -> Fill {          Fill { -            color, +            style: Style::Solid(color),              ..Fill::default()          }      }  } +impl From<Gradient> for Fill { +    fn from(gradient: Gradient) -> Self { +        Fill { +            style: Style::Gradient(gradient), +            ..Default::default() +        } +    } +} +  /// The fill rule defines how to determine what is inside and what is outside of  /// a shape.  /// diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs index 516539ca..d68548ae 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/graphics/src/widget/canvas/frame.rs @@ -1,13 +1,13 @@ -use std::borrow::Cow; - -use iced_native::{Point, Rectangle, Size, Vector}; - +use crate::gradient::Gradient;  use crate::triangle; -use crate::widget::canvas::path; -use crate::widget::canvas::{Fill, Geometry, Path, Stroke, Text}; +use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, Style, Text};  use crate::Primitive; +use iced_native::{Point, Rectangle, Size, Vector}; + +use lyon::geom::euclid;  use lyon::tessellation; +use std::borrow::Cow;  /// The frame of a [`Canvas`].  /// @@ -15,13 +15,91 @@ use lyon::tessellation;  #[allow(missing_debug_implementations)]  pub struct Frame {      size: Size, -    buffers: lyon::tessellation::VertexBuffers<triangle::Vertex2D, u32>, +    buffers: BufferStack,      primitives: Vec<Primitive>,      transforms: Transforms,      fill_tessellator: tessellation::FillTessellator,      stroke_tessellator: tessellation::StrokeTessellator,  } +enum Buffer { +    Solid(tessellation::VertexBuffers<triangle::ColoredVertex2D, u32>), +    Gradient( +        tessellation::VertexBuffers<triangle::Vertex2D, u32>, +        Gradient, +    ), +} + +struct BufferStack { +    stack: Vec<Buffer>, +} + +impl BufferStack { +    fn new() -> Self { +        Self { stack: Vec::new() } +    } + +    fn get_mut(&mut self, style: &Style) -> &mut Buffer { +        match style { +            Style::Solid(_) => match self.stack.last() { +                Some(Buffer::Solid(_)) => {} +                _ => { +                    self.stack.push(Buffer::Solid( +                        tessellation::VertexBuffers::new(), +                    )); +                } +            }, +            Style::Gradient(gradient) => match self.stack.last() { +                Some(Buffer::Gradient(_, last)) if gradient == last => {} +                _ => { +                    self.stack.push(Buffer::Gradient( +                        tessellation::VertexBuffers::new(), +                        gradient.clone(), +                    )); +                } +            }, +        } + +        self.stack.last_mut().unwrap() +    } + +    fn get_fill<'a>( +        &'a mut self, +        style: &Style, +    ) -> Box<dyn tessellation::FillGeometryBuilder + 'a> { +        match (style, self.get_mut(style)) { +            (Style::Solid(color), Buffer::Solid(buffer)) => { +                Box::new(tessellation::BuffersBuilder::new( +                    buffer, +                    TriangleVertex2DBuilder(color.into_linear()), +                )) +            } +            (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( +                tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), +            ), +            _ => unreachable!(), +        } +    } + +    fn get_stroke<'a>( +        &'a mut self, +        style: &Style, +    ) -> Box<dyn tessellation::StrokeGeometryBuilder + 'a> { +        match (style, self.get_mut(style)) { +            (Style::Solid(color), Buffer::Solid(buffer)) => { +                Box::new(tessellation::BuffersBuilder::new( +                    buffer, +                    TriangleVertex2DBuilder(color.into_linear()), +                )) +            } +            (Style::Gradient(_), Buffer::Gradient(buffer, _)) => Box::new( +                tessellation::BuffersBuilder::new(buffer, Vertex2DBuilder), +            ), +            _ => unreachable!(), +        } +    } +} +  #[derive(Debug)]  struct Transforms {      previous: Vec<Transform>, @@ -34,6 +112,35 @@ struct Transform {      is_identity: bool,  } +impl Transform { +    /// Transforms the given [Point] by the transformation matrix. +    fn transform_point(&self, point: &mut Point) { +        let transformed = self +            .raw +            .transform_point(euclid::Point2D::new(point.x, point.y)); +        point.x = transformed.x; +        point.y = transformed.y; +    } + +    fn transform_style(&self, style: Style) -> Style { +        match style { +            Style::Solid(color) => Style::Solid(color), +            Style::Gradient(gradient) => { +                Style::Gradient(self.transform_gradient(gradient)) +            } +        } +    } + +    fn transform_gradient(&self, mut gradient: Gradient) -> Gradient { +        let (start, end) = match &mut gradient { +            Gradient::Linear(linear) => (&mut linear.start, &mut linear.end), +        }; +        self.transform_point(start); +        self.transform_point(end); +        gradient +    } +} +  impl Frame {      /// Creates a new empty [`Frame`] with the given dimensions.      /// @@ -42,7 +149,7 @@ impl Frame {      pub fn new(size: Size) -> Frame {          Frame {              size, -            buffers: lyon::tessellation::VertexBuffers::new(), +            buffers: BufferStack::new(),              primitives: Vec::new(),              transforms: Transforms {                  previous: Vec::new(), @@ -83,21 +190,20 @@ impl Frame {      /// Draws the given [`Path`] on the [`Frame`] by filling it with the      /// provided style.      pub fn fill(&mut self, path: &Path, fill: impl Into<Fill>) { -        let Fill { color, rule } = fill.into(); +        let Fill { style, rule } = fill.into(); -        let mut buffers = tessellation::BuffersBuilder::new( -            &mut self.buffers, -            FillVertex(color.into_linear()), -        ); +        let mut buffer = self +            .buffers +            .get_fill(&self.transforms.current.transform_style(style));          let options =              tessellation::FillOptions::default().with_fill_rule(rule.into()); -        let result = if self.transforms.current.is_identity { +        if self.transforms.current.is_identity {              self.fill_tessellator.tessellate_path(                  path.raw(),                  &options, -                &mut buffers, +                buffer.as_mut(),              )          } else {              let path = path.transformed(&self.transforms.current.raw); @@ -105,11 +211,10 @@ impl Frame {              self.fill_tessellator.tessellate_path(                  path.raw(),                  &options, -                &mut buffers, +                buffer.as_mut(),              ) -        }; - -        result.expect("Tessellate path"); +        } +        .expect("Tessellate path.");      }      /// Draws an axis-aligned rectangle given its top-left corner coordinate and @@ -120,12 +225,11 @@ impl Frame {          size: Size,          fill: impl Into<Fill>,      ) { -        let Fill { color, rule } = fill.into(); +        let Fill { style, rule } = fill.into(); -        let mut buffers = tessellation::BuffersBuilder::new( -            &mut self.buffers, -            FillVertex(color.into_linear()), -        ); +        let mut buffer = self +            .buffers +            .get_fill(&self.transforms.current.transform_style(style));          let top_left =              self.transforms.current.raw.transform_point( @@ -144,7 +248,7 @@ impl Frame {              .tessellate_rectangle(                  &lyon::math::Box2D::new(top_left, top_left + size),                  &options, -                &mut buffers, +                buffer.as_mut(),              )              .expect("Fill rectangle");      } @@ -154,10 +258,9 @@ impl Frame {      pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into<Stroke<'a>>) {          let stroke = stroke.into(); -        let mut buffers = tessellation::BuffersBuilder::new( -            &mut self.buffers, -            StrokeVertex(stroke.color.into_linear()), -        ); +        let mut buffer = self +            .buffers +            .get_stroke(&self.transforms.current.transform_style(stroke.style));          let mut options = tessellation::StrokeOptions::default();          options.line_width = stroke.width; @@ -171,11 +274,11 @@ impl Frame {              Cow::Owned(path::dashed(path, stroke.line_dash))          }; -        let result = if self.transforms.current.is_identity { +        if self.transforms.current.is_identity {              self.stroke_tessellator.tessellate_path(                  path.raw(),                  &options, -                &mut buffers, +                buffer.as_mut(),              )          } else {              let path = path.transformed(&self.transforms.current.raw); @@ -183,11 +286,10 @@ impl Frame {              self.stroke_tessellator.tessellate_path(                  path.raw(),                  &options, -                &mut buffers, +                buffer.as_mut(),              ) -        }; - -        result.expect("Stroke path"); +        } +        .expect("Stroke path");      }      /// Draws the characters of the given [`Text`] on the [`Frame`], filling @@ -206,8 +308,6 @@ impl Frame {      ///      /// [`Canvas`]: crate::widget::Canvas      pub fn fill_text(&mut self, text: impl Into<Text>) { -        use std::f32; -          let text = text.into();          let position = if self.transforms.current.is_identity { @@ -304,7 +404,7 @@ impl Frame {          self.transforms.current.is_identity = false;      } -    /// Applies a rotation to the current transform of the [`Frame`]. +    /// Applies a rotation in radians to the current transform of the [`Frame`].      #[inline]      pub fn rotate(&mut self, angle: f32) {          self.transforms.current.raw = self @@ -331,51 +431,99 @@ impl Frame {      }      fn into_primitives(mut self) -> Vec<Primitive> { -        if !self.buffers.indices.is_empty() { -            self.primitives.push(Primitive::Mesh2D { -                buffers: triangle::Mesh2D { -                    vertices: self.buffers.vertices, -                    indices: self.buffers.indices, -                }, -                size: self.size, -            }); +        for buffer in self.buffers.stack { +            match buffer { +                Buffer::Solid(buffer) => { +                    if !buffer.indices.is_empty() { +                        self.primitives.push(Primitive::SolidMesh { +                            buffers: triangle::Mesh2D { +                                vertices: buffer.vertices, +                                indices: buffer.indices, +                            }, +                            size: self.size, +                        }) +                    } +                } +                Buffer::Gradient(buffer, gradient) => { +                    if !buffer.indices.is_empty() { +                        self.primitives.push(Primitive::GradientMesh { +                            buffers: triangle::Mesh2D { +                                vertices: buffer.vertices, +                                indices: buffer.indices, +                            }, +                            size: self.size, +                            gradient, +                        }) +                    } +                } +            }          }          self.primitives      }  } -struct FillVertex([f32; 4]); +struct Vertex2DBuilder; -impl lyon::tessellation::FillVertexConstructor<triangle::Vertex2D> -    for FillVertex +impl tessellation::FillVertexConstructor<triangle::Vertex2D> +    for Vertex2DBuilder  {      fn new_vertex(          &mut self, -        vertex: lyon::tessellation::FillVertex<'_>, +        vertex: tessellation::FillVertex<'_>,      ) -> triangle::Vertex2D {          let position = vertex.position();          triangle::Vertex2D {              position: [position.x, position.y], -            color: self.0,          }      }  } -struct StrokeVertex([f32; 4]); - -impl lyon::tessellation::StrokeVertexConstructor<triangle::Vertex2D> -    for StrokeVertex +impl tessellation::StrokeVertexConstructor<triangle::Vertex2D> +    for Vertex2DBuilder  {      fn new_vertex(          &mut self, -        vertex: lyon::tessellation::StrokeVertex<'_, '_>, +        vertex: tessellation::StrokeVertex<'_, '_>,      ) -> triangle::Vertex2D {          let position = vertex.position();          triangle::Vertex2D {              position: [position.x, position.y], +        } +    } +} + +struct TriangleVertex2DBuilder([f32; 4]); + +impl tessellation::FillVertexConstructor<triangle::ColoredVertex2D> +    for TriangleVertex2DBuilder +{ +    fn new_vertex( +        &mut self, +        vertex: tessellation::FillVertex<'_>, +    ) -> triangle::ColoredVertex2D { +        let position = vertex.position(); + +        triangle::ColoredVertex2D { +            position: [position.x, position.y], +            color: self.0, +        } +    } +} + +impl tessellation::StrokeVertexConstructor<triangle::ColoredVertex2D> +    for TriangleVertex2DBuilder +{ +    fn new_vertex( +        &mut self, +        vertex: tessellation::StrokeVertex<'_, '_>, +    ) -> triangle::ColoredVertex2D { +        let position = vertex.position(); + +        triangle::ColoredVertex2D { +            position: [position.x, position.y],              color: self.0,          }      } diff --git a/graphics/src/widget/canvas/stroke.rs b/graphics/src/widget/canvas/stroke.rs index 6accc2fb..4c19251d 100644 --- a/graphics/src/widget/canvas/stroke.rs +++ b/graphics/src/widget/canvas/stroke.rs @@ -1,10 +1,15 @@ +//! Create lines from a [crate::widget::canvas::Path] and assigns them various attributes/styles. +pub use crate::widget::canvas::Style; +  use iced_native::Color;  /// The style of a stroke. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)]  pub struct Stroke<'a> { -    /// The color of the stroke. -    pub color: Color, +    /// The color or gradient of the stroke. +    /// +    /// By default, it is set to a [`Style::Solid`] with [`Color::BLACK`]. +    pub style: Style,      /// The distance between the two edges of the stroke.      pub width: f32,      /// The shape to be used at the end of open subpaths when they are stroked. @@ -19,7 +24,10 @@ pub struct Stroke<'a> {  impl<'a> Stroke<'a> {      /// Sets the color of the [`Stroke`].      pub fn with_color(self, color: Color) -> Self { -        Stroke { color, ..self } +        Stroke { +            style: Style::Solid(color), +            ..self +        }      }      /// Sets the width of the [`Stroke`]. @@ -41,7 +49,7 @@ impl<'a> Stroke<'a> {  impl<'a> Default for Stroke<'a> {      fn default() -> Self {          Stroke { -            color: Color::BLACK, +            style: Style::Solid(Color::BLACK),              width: 1.0,              line_cap: LineCap::default(),              line_join: LineJoin::default(), diff --git a/graphics/src/widget/canvas/style.rs b/graphics/src/widget/canvas/style.rs new file mode 100644 index 00000000..6794f2e7 --- /dev/null +++ b/graphics/src/widget/canvas/style.rs @@ -0,0 +1,23 @@ +use crate::{Color, Gradient}; + +/// The coloring style of some drawing. +#[derive(Debug, Clone, PartialEq)] +pub enum Style { +    /// A solid [`Color`]. +    Solid(Color), + +    /// A [`Gradient`] color. +    Gradient(Gradient), +} + +impl From<Color> for Style { +    fn from(color: Color) -> Self { +        Self::Solid(color) +    } +} + +impl From<Gradient> for Style { +    fn from(gradient: Gradient) -> Self { +        Self::Gradient(gradient) +    } +} | 
