diff options
Diffstat (limited to 'graphics/src')
| -rw-r--r-- | graphics/src/gradient.rs | 23 | ||||
| -rw-r--r-- | graphics/src/layer.rs | 47 | ||||
| -rw-r--r-- | graphics/src/lib.rs | 2 | ||||
| -rw-r--r-- | graphics/src/primitive.rs | 5 | ||||
| -rw-r--r-- | graphics/src/shader.rs | 42 | ||||
| -rw-r--r-- | graphics/src/transformation.rs | 8 | ||||
| -rw-r--r-- | graphics/src/triangle.rs | 18 | ||||
| -rw-r--r-- | graphics/src/widget/canvas.rs | 6 | ||||
| -rw-r--r-- | graphics/src/widget/canvas/fill.rs | 33 | ||||
| -rw-r--r-- | graphics/src/widget/canvas/frame.rs | 111 | ||||
| -rw-r--r-- | graphics/src/widget/canvas/gradient.rs | 21 | ||||
| -rw-r--r-- | graphics/src/widget/canvas/gradient/linear.rs | 73 | ||||
| -rw-r--r-- | graphics/src/widget/canvas/stroke.rs | 26 | 
13 files changed, 307 insertions, 108 deletions
| diff --git a/graphics/src/gradient.rs b/graphics/src/gradient.rs new file mode 100644 index 00000000..4d1eff62 --- /dev/null +++ b/graphics/src/gradient.rs @@ -0,0 +1,23 @@ +//! For creating a Gradient. + +use iced_native::Color; +use crate::widget::canvas::gradient::Linear; + +#[derive(Debug, Clone, PartialEq)] +/// A fill which transitions colors progressively along a direction, either linearly, radially, +/// or conically. +pub enum Gradient { +    /// A linear gradient interpolates colors along a direction from its [`start`] to its [`end`] +    /// point. +    Linear(Linear), +} + + +#[derive(Debug, Clone, Copy, PartialEq)] +/// A point along the gradient vector where the specified [`color`] is unmixed. +pub struct ColorStop { +    /// Offset along the gradient vector. +    pub offset: f32, +    /// The color of the gradient at the specified [`offset`]. +    pub color: Color, +}
\ No newline at end of file diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index af545713..b7731922 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -7,9 +7,10 @@ use crate::{  use iced_native::image;  use iced_native::svg; +use crate::shader::Shader;  /// A group of primitives that should be clipped together. -#[derive(Debug, Clone)] +#[derive(Debug)]  pub struct Layer<'a> {      /// The clipping bounds of the [`Layer`].      pub bounds: Rectangle, @@ -18,7 +19,7 @@ pub struct Layer<'a> {      pub quads: Vec<Quad>,      /// The triangle meshes of the [`Layer`]. -    pub meshes: Vec<Mesh<'a>>, +    pub meshes: Meshes<'a>,      /// The text of the [`Layer`].      pub text: Vec<Text<'a>>, @@ -33,7 +34,7 @@ impl<'a> Layer<'a> {          Self {              bounds,              quads: Vec::new(), -            meshes: Vec::new(), +            meshes: Meshes(Vec::new()),              text: Vec::new(),              images: Vec::new(),          } @@ -159,7 +160,11 @@ impl<'a> Layer<'a> {                      border_color: border_color.into_linear(),                  });              } -            Primitive::Mesh2D { buffers, size } => { +            Primitive::Mesh2D { +                buffers, +                size, +                shader, +            } => {                  let layer = &mut layers[current_layer];                  let bounds = Rectangle::new( @@ -169,11 +174,14 @@ impl<'a> Layer<'a> {                  // Only draw visible content                  if let Some(clip_bounds) = layer.bounds.intersection(&bounds) { -                    layer.meshes.push(Mesh { -                        origin: Point::new(translation.x, translation.y), -                        buffers, -                        clip_bounds, -                    }); +                    layer.meshes.0.push( +                        Mesh { +                            origin: Point::new(translation.x, translation.y), +                            buffers, +                            clip_bounds, +                            shader, +                        } +                    );                  }              }              Primitive::Clip { bounds, content } => { @@ -270,6 +278,9 @@ pub struct Mesh<'a> {      /// The clipping bounds of the [`Mesh`].      pub clip_bounds: Rectangle<f32>, + +    /// The shader of the [`Mesh`]. +    pub shader: &'a Shader,  }  /// A paragraph of text. @@ -323,3 +334,21 @@ unsafe impl bytemuck::Zeroable for Quad {}  #[allow(unsafe_code)]  unsafe impl bytemuck::Pod for Quad {} + +#[derive(Debug)] +/// A collection of meshes. +pub struct Meshes<'a>(pub Vec<Mesh<'a>>); + +impl<'a> Meshes<'a> { +    /// Returns the number of total vertices & total indices of all [`Mesh`]es. +    pub fn attribute_count(&self) -> (usize, usize) { +        self.0 +            .iter() +            .map(|Mesh { buffers, .. }| { +                (buffers.vertices.len(), buffers.indices.len()) +            }) +            .fold((0, 0), |(total_v, total_i), (v, i)| { +                (total_v + v, total_i + i) +            }) +    } +}
\ No newline at end of file diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 11082472..ce9b1b07 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -35,6 +35,8 @@ pub mod renderer;  pub mod triangle;  pub mod widget;  pub mod window; +pub mod shader; +pub mod gradient;  pub use antialiasing::Antialiasing;  pub use backend::Backend; diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 5f7a344d..4f79a74c 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -2,7 +2,7 @@ use iced_native::image;  use iced_native::svg;  use iced_native::{Background, Color, Font, Rectangle, Size, Vector}; -use crate::alignment; +use crate::{alignment, shader};  use crate::triangle;  use std::sync::Arc; @@ -88,6 +88,9 @@ pub enum Primitive {          ///          /// Any geometry that falls out of this region will be clipped.          size: Size, + +        /// The shader of the mesh +        shader: shader::Shader,      },      /// A cached primitive.      /// diff --git a/graphics/src/shader.rs b/graphics/src/shader.rs new file mode 100644 index 00000000..b9071c74 --- /dev/null +++ b/graphics/src/shader.rs @@ -0,0 +1,42 @@ +//! Supported shaders; + +use crate::{Color, widget}; +use crate::gradient::Gradient; +use crate::widget::canvas::{FillStyle, StrokeStyle}; + +#[derive(Debug, Clone)] +/// Supported shaders for primitives. +pub enum Shader { +    /// Fill a primitive with a solid color. +    Solid(Color), +    /// Fill a primitive with an interpolated color. +    Gradient(Gradient) +} + +impl <'a> Into<Shader> for StrokeStyle<'a> { +    fn into(self) -> Shader { +        match self { +            StrokeStyle::Solid(color) => Shader::Solid(color), +            StrokeStyle::Gradient(gradient) => gradient.clone().into() +        } +    } +} + +impl <'a> Into<Shader> for FillStyle<'a> { +    fn into(self) -> Shader { +        match self { +            FillStyle::Solid(color) => Shader::Solid(color), +            FillStyle::Gradient(gradient) => gradient.clone().into() +        } +    } +} + +impl <'a> Into<Shader> for widget::canvas::Gradient { +    fn into(self) -> Shader { +        match self { +            widget::canvas::Gradient::Linear(linear) => { +                Shader::Gradient(Gradient::Linear(linear)) +            } +        } +    } +}
\ No newline at end of file diff --git a/graphics/src/transformation.rs b/graphics/src/transformation.rs index 2a19caed..03f453db 100644 --- a/graphics/src/transformation.rs +++ b/graphics/src/transformation.rs @@ -8,7 +8,7 @@ pub struct Transformation(Mat4);  impl Transformation {      /// Get the identity transformation.      pub fn identity() -> Transformation { -        Transformation(Mat4::identity()) +        Transformation(Mat4::IDENTITY)      }      /// Creates an orthographic projection. @@ -51,3 +51,9 @@ impl From<Transformation> for [f32; 16] {          *t.as_ref()      }  } + +impl Into<Mat4> for Transformation { +    fn into(self) -> Mat4 { +        self.0 +    } +}
\ No newline at end of file diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs index 05028f51..92709fe2 100644 --- a/graphics/src/triangle.rs +++ b/graphics/src/triangle.rs @@ -6,20 +6,22 @@ use bytemuck::{Pod, Zeroable};  pub struct Mesh2D {      /// The vertices of the mesh      pub vertices: Vec<Vertex2D>, -      /// The list of vertex indices that defines the triangles of the mesh. -    /// -    /// Therefore, this list should always have a length that is a multiple of -    /// 3.      pub indices: Vec<u32>,  } -/// A two-dimensional vertex with some color in __linear__ RGBA. +/// A two-dimensional vertex.  #[derive(Copy, Clone, Debug, Zeroable, Pod)]  #[repr(C)]  pub struct Vertex2D { -    /// The vertex position +    /// The vertex position in 2D space.      pub position: [f32; 2], -    /// The vertex color in __linear__ RGBA. -    pub color: [f32; 4], +} + +/// Convert from lyon's position data to Iced's Vertex2D type. +impl Vertex2D { +    /// Converts from [`lyon::math::Point`] to [`Vertex2D`]. Used for generating primitives. +    pub fn from(points: Vec<lyon::math::Point>) -> Vec<Vertex2D> { +        points.iter().map(|p| Vertex2D { position: [p.x, p.y]}).collect() +    }  } diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index 88403fd7..09aad98d 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -5,6 +5,7 @@  //! and more!  pub mod event; +pub mod gradient;  pub mod path;  mod cache; @@ -19,12 +20,13 @@ mod text;  pub use cache::Cache;  pub use cursor::Cursor;  pub use event::Event; -pub use fill::{Fill, FillRule}; +pub use fill::{Fill, FillRule, FillStyle};  pub use frame::Frame;  pub use geometry::Geometry; +pub use gradient::Gradient;  pub use path::Path;  pub use program::Program; -pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; +pub use stroke::{LineCap, LineDash, LineJoin, Stroke, StrokeStyle};  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..02d2311f 100644 --- a/graphics/src/widget/canvas/fill.rs +++ b/graphics/src/widget/canvas/fill.rs @@ -1,12 +1,14 @@  use iced_native::Color; +use crate::widget::canvas::Gradient; +  /// The style used to fill geometry. -#[derive(Debug, Clone, Copy)] -pub struct Fill { -    /// The color used to fill geometry. +#[derive(Debug, Clone)] +pub struct Fill<'a> { +    /// The color or gradient of the fill.      /// -    /// By default, it is set to `BLACK`. -    pub color: Color, +    /// By default, it is set to [`FillStyle::Solid`] `BLACK`. +    pub style: FillStyle<'a>,      /// The fill rule defines how to determine what is inside and what is      /// outside of a shape. @@ -19,24 +21,33 @@ pub struct Fill {      pub rule: FillRule,  } -impl Default for Fill { -    fn default() -> Fill { +impl <'a> Default for Fill<'a> { +    fn default() -> Fill<'a> {          Fill { -            color: Color::BLACK, +            style: FillStyle::Solid(Color::BLACK),              rule: FillRule::NonZero,          }      }  } -impl From<Color> for Fill { -    fn from(color: Color) -> Fill { +impl<'a> From<Color> for Fill<'a> { +    fn from(color: Color) -> Fill<'a> {          Fill { -            color, +            style: FillStyle::Solid(color),              ..Fill::default()          }      }  } +/// The color or gradient of a [`Fill`]. +#[derive(Debug, Clone)] +pub enum FillStyle<'a> { +    /// A solid color +    Solid(Color), +    /// A color gradient +    Gradient(&'a Gradient), +} +  /// 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..df2db98f 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/graphics/src/widget/canvas/frame.rs @@ -3,11 +3,13 @@ use std::borrow::Cow;  use iced_native::{Point, Rectangle, Size, Vector};  use crate::triangle; -use crate::widget::canvas::path; -use crate::widget::canvas::{Fill, Geometry, Path, Stroke, Text}; +use crate::widget::canvas::{Fill, Geometry, Path, Stroke, Text, path};  use crate::Primitive; +use crate::triangle::{Vertex2D}; +use crate::shader::Shader;  use lyon::tessellation; +use lyon::tessellation::geometry_builder::Positions;  /// The frame of a [`Canvas`].  /// @@ -15,7 +17,7 @@ use lyon::tessellation;  #[allow(missing_debug_implementations)]  pub struct Frame {      size: Size, -    buffers: lyon::tessellation::VertexBuffers<triangle::Vertex2D, u32>, +    buffers: Vec<(tessellation::VertexBuffers<lyon::math::Point, u32>, Shader)>,      primitives: Vec<Primitive>,      transforms: Transforms,      fill_tessellator: tessellation::FillTessellator, @@ -42,7 +44,7 @@ impl Frame {      pub fn new(size: Size) -> Frame {          Frame {              size, -            buffers: lyon::tessellation::VertexBuffers::new(), +            buffers: Vec::new(),              primitives: Vec::new(),              transforms: Transforms {                  previous: Vec::new(), @@ -82,18 +84,18 @@ 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(); +    pub fn fill<'a>(&mut self, path: &Path, fill: impl Into<Fill<'a>>) { +        let Fill { style, rule } = fill.into(); -        let mut buffers = tessellation::BuffersBuilder::new( -            &mut self.buffers, -            FillVertex(color.into_linear()), -        ); +        let mut buf = tessellation::VertexBuffers::new(); + +        let mut buffers = +            tessellation::BuffersBuilder::new(&mut buf, Positions);          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, @@ -107,25 +109,24 @@ impl Frame {                  &options,                  &mut buffers,              ) -        }; +        }.expect("Tessellate path."); -        result.expect("Tessellate path"); +        self.buffers.push((buf, style.into()))      }      /// Draws an axis-aligned rectangle given its top-left corner coordinate and      /// its `Size` on the [`Frame`] by filling it with the provided style. -    pub fn fill_rectangle( +    pub fn fill_rectangle<'a>(          &mut self,          top_left: Point,          size: Size, -        fill: impl Into<Fill>, +        fill: impl Into<Fill<'a>>,      ) { -        let Fill { color, rule } = fill.into(); +        let Fill { style, rule } = fill.into(); + +        let mut buf = tessellation::VertexBuffers::new(); -        let mut buffers = tessellation::BuffersBuilder::new( -            &mut self.buffers, -            FillVertex(color.into_linear()), -        ); +        let mut buffers = tessellation::BuffersBuilder::new(&mut buf, Positions);          let top_left =              self.transforms.current.raw.transform_point( @@ -147,6 +148,8 @@ impl Frame {                  &mut buffers,              )              .expect("Fill rectangle"); + +        self.buffers.push((buf, style.into()))      }      /// Draws the stroke of the given [`Path`] on the [`Frame`] with the @@ -154,10 +157,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 buf = tessellation::VertexBuffers::new(); + +        let mut buffers = tessellation::BuffersBuilder::new(&mut buf, Positions);          let mut options = tessellation::StrokeOptions::default();          options.line_width = stroke.width; @@ -171,7 +173,7 @@ 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, @@ -185,9 +187,9 @@ impl Frame {                  &options,                  &mut buffers,              ) -        }; +        }.expect("Stroke path"); -        result.expect("Stroke path"); +        self.buffers.push((buf, stroke.style.into()))      }      /// Draws the characters of the given [`Text`] on the [`Frame`], filling @@ -331,52 +333,19 @@ 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, -            }); +        if !self.primitives.is_empty() { +            for (buffer, shader) in self.buffers { +                self.primitives.push(Primitive::Mesh2D { +                    buffers: triangle::Mesh2D { +                        vertices: Vertex2D::from(buffer.vertices), +                        indices: buffer.indices +                    }, +                    size: self.size, +                    shader +                }) +            }          }          self.primitives      }  } - -struct FillVertex([f32; 4]); - -impl lyon::tessellation::FillVertexConstructor<triangle::Vertex2D> -    for FillVertex -{ -    fn new_vertex( -        &mut self, -        vertex: lyon::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 -{ -    fn new_vertex( -        &mut self, -        vertex: lyon::tessellation::StrokeVertex<'_, '_>, -    ) -> triangle::Vertex2D { -        let position = vertex.position(); - -        triangle::Vertex2D { -            position: [position.x, position.y], -            color: self.0, -        } -    } -} diff --git a/graphics/src/widget/canvas/gradient.rs b/graphics/src/widget/canvas/gradient.rs new file mode 100644 index 00000000..7d2daabc --- /dev/null +++ b/graphics/src/widget/canvas/gradient.rs @@ -0,0 +1,21 @@ +//! Define a color gradient. +use iced_native::Point; + +pub mod linear; + +pub use linear::Linear; + +/// A gradient that can be used in the style of [`super::Fill`] or [`super::Stroke`]. +#[derive(Debug, Clone)] +pub enum Gradient { +    /// A linear gradient +    Linear(Linear), +    //TODO: radial, conical +} + +impl Gradient { +    /// Creates a new linear [`linear::Builder`]. +    pub fn linear(start: Point, end: Point) -> linear::Builder { +        linear::Builder::new(start, end) +    } +}
\ No newline at end of file diff --git a/graphics/src/widget/canvas/gradient/linear.rs b/graphics/src/widget/canvas/gradient/linear.rs new file mode 100644 index 00000000..37533e19 --- /dev/null +++ b/graphics/src/widget/canvas/gradient/linear.rs @@ -0,0 +1,73 @@ +//! A linear color gradient. +use iced_native::{Color, Point}; + +use crate::gradient::ColorStop; + +use super::Gradient; + +/// A linear gradient that can be used in the style of [`super::Fill`] or [`super::Stroke`]. +#[derive(Debug, Clone, PartialEq)] +pub struct Linear { +    /// The point where the linear gradient begins. +    pub start: Point, +    /// The point where the linear gradient ends. +    pub end: Point, +    /// [`ColorStop`]s along the linear gradient path. +    pub color_stops: Vec<ColorStop>, +} + +/// A [`Linear`] builder. +#[derive(Debug)] +pub struct Builder { +    start: Point, +    end: Point, +    stops: Vec<(f32, Color)>, +    valid: bool, +} + +impl Builder { +    /// Creates a new [`Builder`]. +    pub fn new(start: Point, end: Point) -> Self { +        Self { +            start, +            end, +            stops: vec![], +            valid: true, +        } +    } + +    /// Adds a new stop, defined by an offset and a color, to the gradient. +    /// +    /// `offset` must be between `0.0` and `1.0`. +    pub fn add_stop(mut self, offset: f32, color: Color) -> Self { +        if !(0.0..=1.0).contains(&offset) { +            self.valid = false; +        } + +        self.stops.push((offset, color)); +        self +    } + +    /// Builds the linear [`Gradient`] of this [`Builder`]. +    /// +    /// Returns `None` if no stops were added to the builder or +    /// if stops not between 0.0 and 1.0 were added. +    pub fn build(self) -> Option<Gradient> { +        if self.stops.is_empty() || !self.valid { +            return None; +        } + +        Some(Gradient::Linear(Linear { +            start: self.start, +            end: self.end, +            color_stops: self +                .stops +                .into_iter() +                .map(|f| ColorStop { +                    offset: f.0, +                    color: f.1, +                }) +                .collect(), +        })) +    } +} diff --git a/graphics/src/widget/canvas/stroke.rs b/graphics/src/widget/canvas/stroke.rs index 6accc2fb..c319b398 100644 --- a/graphics/src/widget/canvas/stroke.rs +++ b/graphics/src/widget/canvas/stroke.rs @@ -1,10 +1,14 @@  use iced_native::Color; +use crate::widget::canvas::Gradient; +  /// 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 [`StrokeStyle::Solid`] `BLACK`. +    pub style: StrokeStyle<'a>,      /// 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 +23,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: StrokeStyle::Solid(color), +            ..self +        }      }      /// Sets the width of the [`Stroke`]. @@ -41,7 +48,7 @@ impl<'a> Stroke<'a> {  impl<'a> Default for Stroke<'a> {      fn default() -> Self {          Stroke { -            color: Color::BLACK, +            style: StrokeStyle::Solid(Color::BLACK),              width: 1.0,              line_cap: LineCap::default(),              line_join: LineJoin::default(), @@ -50,6 +57,15 @@ impl<'a> Default for Stroke<'a> {      }  } +/// The color or gradient of a [`Stroke`]. +#[derive(Debug, Clone, Copy)] +pub enum StrokeStyle<'a> { +    /// A solid color +    Solid(Color), +    /// A color gradient +    Gradient(&'a Gradient), +} +  /// The shape used at the end of open subpaths when they are stroked.  #[derive(Debug, Clone, Copy)]  pub enum LineCap { | 
