diff options
author | 2022-11-03 18:57:09 +0100 | |
---|---|---|
committer | 2022-11-03 18:57:09 +0100 | |
commit | d222b5c8b0befab665c20ba0112b28199df0ae44 (patch) | |
tree | 0ac3a59f04e1c1ca89ff43800efbefd825b015ea /graphics/src | |
parent | a8f510c39917b2ac42fcc854f0a7eff13aee9838 (diff) | |
parent | f31c8f2504ea7c004c5caed8913e5da28d2e50e2 (diff) | |
download | iced-d222b5c8b0befab665c20ba0112b28199df0ae44.tar.gz iced-d222b5c8b0befab665c20ba0112b28199df0ae44.tar.bz2 iced-d222b5c8b0befab665c20ba0112b28199df0ae44.zip |
Merge pull request #1448 from bungoboingo/fear/linear-gradients
Add linear gradient support to canvas widget
Diffstat (limited to 'graphics/src')
-rw-r--r-- | graphics/src/gradient.rs | 112 | ||||
-rw-r--r-- | graphics/src/gradient/linear.rs | 109 | ||||
-rw-r--r-- | graphics/src/layer.rs | 114 | ||||
-rw-r--r-- | graphics/src/layer/image.rs | 23 | ||||
-rw-r--r-- | graphics/src/layer/mesh.rs | 31 | ||||
-rw-r--r-- | graphics/src/layer/quad.rs | 30 | ||||
-rw-r--r-- | graphics/src/layer/text.rs | 26 | ||||
-rw-r--r-- | graphics/src/lib.rs | 2 | ||||
-rw-r--r-- | graphics/src/primitive.rs | 3 | ||||
-rw-r--r-- | graphics/src/transformation.rs | 8 | ||||
-rw-r--r-- | graphics/src/triangle.rs | 33 | ||||
-rw-r--r-- | graphics/src/widget/canvas.rs | 6 | ||||
-rw-r--r-- | graphics/src/widget/canvas/fill.rs | 30 | ||||
-rw-r--r-- | graphics/src/widget/canvas/frame.rs | 173 | ||||
-rw-r--r-- | graphics/src/widget/canvas/stroke.rs | 18 |
15 files changed, 532 insertions, 186 deletions
diff --git a/graphics/src/gradient.rs b/graphics/src/gradient.rs new file mode 100644 index 00000000..64f9e4b3 --- /dev/null +++ b/graphics/src/gradient.rs @@ -0,0 +1,112 @@ +//! For creating a Gradient. +pub mod linear; + +pub use linear::Linear; + +use crate::{Color, Point, Size}; + +#[derive(Debug, Clone, PartialEq)] +/// A fill which transitions colors progressively along a direction, either linearly, radially (TBD), +/// or conically (TBD). +pub enum Gradient { + /// A linear gradient interpolates colors along a direction from its [`start`] to its [`end`] + /// point. + Linear(Linear), +} + +impl Gradient { + /// Creates a new linear [`linear::Builder`]. + pub fn linear(position: impl Into<Position>) -> linear::Builder { + linear::Builder::new(position.into()) + } +} + +#[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, +} + +#[derive(Debug)] +/// The position of the gradient within its bounds. +pub enum Position { + /// The gradient will be positioned with respect to two points. + Absolute { + /// The starting point of the gradient. + start: Point, + /// The ending point of the gradient. + end: Point, + }, + /// The gradient will be positioned relative to the provided bounds. + Relative { + /// The top left position of the bounds. + top_left: Point, + /// The width & height of the bounds. + size: Size, + /// The start [Location] of the gradient. + start: Location, + /// The end [Location] of the gradient. + end: Location, + }, +} + +impl From<(Point, Point)> for Position { + fn from((start, end): (Point, Point)) -> Self { + Self::Absolute { start, end } + } +} + +#[derive(Debug)] +/// The location of a relatively-positioned gradient. +pub enum Location { + /// Top left. + TopLeft, + /// Top. + Top, + /// Top right. + TopRight, + /// Right. + Right, + /// Bottom right. + BottomRight, + /// Bottom. + Bottom, + /// Bottom left. + BottomLeft, + /// Left. + Left, +} + +impl Location { + fn to_absolute(&self, top_left: Point, size: Size) -> Point { + match self { + Location::TopLeft => top_left, + Location::Top => { + Point::new(top_left.x + size.width / 2.0, top_left.y) + } + Location::TopRight => { + Point::new(top_left.x + size.width, top_left.y) + } + Location::Right => Point::new( + top_left.x + size.width, + top_left.y + size.height / 2.0, + ), + Location::BottomRight => { + Point::new(top_left.x + size.width, top_left.y + size.height) + } + Location::Bottom => Point::new( + top_left.x + size.width / 2.0, + top_left.y + size.height, + ), + Location::BottomLeft => { + Point::new(top_left.x, top_left.y + size.height) + } + Location::Left => { + Point::new(top_left.x, top_left.y + size.height / 2.0) + } + } + } +} diff --git a/graphics/src/gradient/linear.rs b/graphics/src/gradient/linear.rs new file mode 100644 index 00000000..9928c1eb --- /dev/null +++ b/graphics/src/gradient/linear.rs @@ -0,0 +1,109 @@ +//! Linear gradient builder & definition. +use crate::gradient::{ColorStop, Gradient, Position}; +use crate::{Color, Point}; + +/// 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<ColorStop>, + error: Option<BuilderError>, +} + +impl Builder { + /// Creates a new [`Builder`]. + pub fn new(position: Position) -> Self { + let (start, end) = match position { + Position::Absolute { start, end } => (start, end), + Position::Relative { + top_left, + size, + start, + end, + } => ( + start.to_absolute(top_left, size), + end.to_absolute(top_left, size), + ), + }; + + Self { + start, + end, + stops: vec![], + error: None, + } + } + + /// Adds a new stop, defined by an offset and a color, to the gradient. + /// + /// `offset` must be between `0.0` and `1.0` or the gradient cannot be built. + /// + /// Note: when using the [`glow`] backend, any color stop added after the 16th + /// will not be displayed. + /// + /// On the [`wgpu`] backend this limitation does not exist (technical limit is 524,288 stops). + /// + /// [`glow`]: https://docs.rs/iced_glow + /// [`wgpu`]: https://docs.rs/iced_wgpu + pub fn add_stop(mut self, offset: f32, color: Color) -> Self { + if offset.is_finite() && (0.0..=1.0).contains(&offset) { + match self.stops.binary_search_by(|stop| { + stop.offset.partial_cmp(&offset).unwrap() + }) { + Ok(_) => { + self.error = Some(BuilderError::DuplicateOffset(offset)) + } + Err(index) => { + self.stops.insert(index, ColorStop { offset, color }); + } + } + } else { + self.error = Some(BuilderError::InvalidOffset(offset)) + }; + + self + } + + /// Builds the linear [`Gradient`] of this [`Builder`]. + /// + /// Returns `BuilderError` if gradient in invalid. + pub fn build(self) -> Result<Gradient, BuilderError> { + if self.stops.is_empty() { + Err(BuilderError::MissingColorStop) + } else if let Some(error) = self.error { + Err(error) + } else { + Ok(Gradient::Linear(Linear { + start: self.start, + end: self.end, + color_stops: self.stops, + })) + } + } +} + +/// An error that happened when building a [`Linear`] gradient. +#[derive(Debug, thiserror::Error)] +pub enum BuilderError { + #[error("Gradients must contain at least one color stop.")] + /// Gradients must contain at least one color stop. + MissingColorStop, + #[error("Offset {0} must be a unique, finite number.")] + /// Offsets in a gradient must all be unique & finite. + DuplicateOffset(f32), + #[error("Offset {0} must be between 0.0..=1.0.")] + /// Offsets in a gradient must be between 0.0..=1.0. + InvalidOffset(f32), +} diff --git a/graphics/src/layer.rs b/graphics/src/layer.rs index af545713..e95934b0 100644 --- a/graphics/src/layer.rs +++ b/graphics/src/layer.rs @@ -1,15 +1,22 @@ //! Organize rendering primitives into a flattened list of layers. +mod image; +mod quad; +mod text; + +pub mod mesh; + +pub use image::Image; +pub use mesh::Mesh; +pub use quad::Quad; +pub use text::Text; + use crate::alignment; -use crate::triangle; use crate::{ Background, Font, Point, Primitive, Rectangle, Size, Vector, Viewport, }; -use iced_native::image; -use iced_native::svg; - /// 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, @@ -159,7 +166,11 @@ impl<'a> Layer<'a> { border_color: border_color.into_linear(), }); } - Primitive::Mesh2D { buffers, size } => { + Primitive::Mesh2D { + buffers, + size, + style, + } => { let layer = &mut layers[current_layer]; let bounds = Rectangle::new( @@ -173,6 +184,7 @@ impl<'a> Layer<'a> { origin: Point::new(translation.x, translation.y), buffers, clip_bounds, + style, }); } } @@ -233,93 +245,3 @@ impl<'a> Layer<'a> { } } } - -/// A colored rectangle with a border. -/// -/// This type can be directly uploaded to GPU memory. -#[derive(Debug, Clone, Copy)] -#[repr(C)] -pub struct Quad { - /// The position of the [`Quad`]. - pub position: [f32; 2], - - /// The size of the [`Quad`]. - pub size: [f32; 2], - - /// The color of the [`Quad`], in __linear RGB__. - pub color: [f32; 4], - - /// The border color of the [`Quad`], in __linear RGB__. - pub border_color: [f32; 4], - - /// The border radius of the [`Quad`]. - pub border_radius: f32, - - /// The border width of the [`Quad`]. - pub border_width: f32, -} - -/// A mesh of triangles. -#[derive(Debug, Clone, Copy)] -pub struct Mesh<'a> { - /// The origin of the vertices of the [`Mesh`]. - pub origin: Point, - - /// The vertex and index buffers of the [`Mesh`]. - pub buffers: &'a triangle::Mesh2D, - - /// The clipping bounds of the [`Mesh`]. - pub clip_bounds: Rectangle<f32>, -} - -/// A paragraph of text. -#[derive(Debug, Clone, Copy)] -pub struct Text<'a> { - /// The content of the [`Text`]. - pub content: &'a str, - - /// The layout bounds of the [`Text`]. - pub bounds: Rectangle, - - /// The color of the [`Text`], in __linear RGB_. - pub color: [f32; 4], - - /// The size of the [`Text`]. - pub size: f32, - - /// The font of the [`Text`]. - pub font: Font, - - /// The horizontal alignment of the [`Text`]. - pub horizontal_alignment: alignment::Horizontal, - - /// The vertical alignment of the [`Text`]. - pub vertical_alignment: alignment::Vertical, -} - -/// A raster or vector image. -#[derive(Debug, Clone)] -pub enum Image { - /// A raster image. - Raster { - /// The handle of a raster image. - handle: image::Handle, - - /// The bounds of the image. - bounds: Rectangle, - }, - /// A vector image. - Vector { - /// The handle of a vector image. - handle: svg::Handle, - - /// The bounds of the image. - bounds: Rectangle, - }, -} - -#[allow(unsafe_code)] -unsafe impl bytemuck::Zeroable for Quad {} - -#[allow(unsafe_code)] -unsafe impl bytemuck::Pod for Quad {} diff --git a/graphics/src/layer/image.rs b/graphics/src/layer/image.rs new file mode 100644 index 00000000..045ec665 --- /dev/null +++ b/graphics/src/layer/image.rs @@ -0,0 +1,23 @@ +use crate::Rectangle; +use iced_native::{image, svg}; + +/// A raster or vector image. +#[derive(Debug, Clone)] +pub enum Image { + /// A raster image. + Raster { + /// The handle of a raster image. + handle: image::Handle, + + /// The bounds of the image. + bounds: Rectangle, + }, + /// A vector image. + Vector { + /// The handle of a vector image. + handle: svg::Handle, + + /// The bounds of the image. + bounds: Rectangle, + }, +} diff --git a/graphics/src/layer/mesh.rs b/graphics/src/layer/mesh.rs new file mode 100644 index 00000000..979081f1 --- /dev/null +++ b/graphics/src/layer/mesh.rs @@ -0,0 +1,31 @@ +//! A collection of triangle primitives. +use crate::triangle; +use crate::{Point, Rectangle}; + +/// A mesh of triangles. +#[derive(Debug, Clone, Copy)] +pub struct Mesh<'a> { + /// The origin of the vertices of the [`Mesh`]. + pub origin: Point, + + /// The vertex and index buffers of the [`Mesh`]. + pub buffers: &'a triangle::Mesh2D, + + /// The clipping bounds of the [`Mesh`]. + pub clip_bounds: Rectangle<f32>, + + /// The shader of the [`Mesh`]. + pub style: &'a triangle::Style, +} + +/// Returns the number of total vertices & total indices of all [`Mesh`]es. +pub fn attribute_count_of<'a>(meshes: &'a [Mesh<'a>]) -> (usize, usize) { + meshes + .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) + }) +} diff --git a/graphics/src/layer/quad.rs b/graphics/src/layer/quad.rs new file mode 100644 index 00000000..4def8427 --- /dev/null +++ b/graphics/src/layer/quad.rs @@ -0,0 +1,30 @@ +/// A colored rectangle with a border. +/// +/// This type can be directly uploaded to GPU memory. +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct Quad { + /// The position of the [`Quad`]. + pub position: [f32; 2], + + /// The size of the [`Quad`]. + pub size: [f32; 2], + + /// The color of the [`Quad`], in __linear RGB__. + pub color: [f32; 4], + + /// The border color of the [`Quad`], in __linear RGB__. + pub border_color: [f32; 4], + + /// The border radius of the [`Quad`]. + pub border_radius: f32, + + /// The border width of the [`Quad`]. + pub border_width: f32, +} + +#[allow(unsafe_code)] +unsafe impl bytemuck::Zeroable for Quad {} + +#[allow(unsafe_code)] +unsafe impl bytemuck::Pod for Quad {} diff --git a/graphics/src/layer/text.rs b/graphics/src/layer/text.rs new file mode 100644 index 00000000..74f7a676 --- /dev/null +++ b/graphics/src/layer/text.rs @@ -0,0 +1,26 @@ +use crate::{alignment, Font, Rectangle}; + +/// A paragraph of text. +#[derive(Debug, Clone, Copy)] +pub struct Text<'a> { + /// The content of the [`Text`]. + pub content: &'a str, + + /// The layout bounds of the [`Text`]. + pub bounds: Rectangle, + + /// The color of the [`Text`], in __linear RGB_. + pub color: [f32; 4], + + /// The size of the [`Text`]. + pub size: f32, + + /// The font of the [`Text`]. + pub font: Font, + + /// The horizontal alignment of the [`Text`]. + pub horizontal_alignment: alignment::Horizontal, + + /// The vertical alignment of the [`Text`]. + pub vertical_alignment: alignment::Vertical, +} diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 11082472..ec28ee58 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -29,6 +29,7 @@ mod viewport; pub mod backend; pub mod font; +pub mod gradient; pub mod layer; pub mod overlay; pub mod renderer; @@ -39,6 +40,7 @@ pub mod window; pub use antialiasing::Antialiasing; pub use backend::Backend; pub use error::Error; +pub use gradient::Gradient; pub use layer::Layer; pub use primitive::Primitive; pub use renderer::Renderer; diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 5f7a344d..b481ac0b 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -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 + style: triangle::Style, }, /// A cached primitive. /// diff --git a/graphics/src/transformation.rs b/graphics/src/transformation.rs index 2a19caed..cf0457a4 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 From<Transformation> for Mat4 { + fn from(transformation: Transformation) -> Self { + transformation.0 + } +} diff --git a/graphics/src/triangle.rs b/graphics/src/triangle.rs index 05028f51..04ff6d21 100644 --- a/graphics/src/triangle.rs +++ b/graphics/src/triangle.rs @@ -1,4 +1,6 @@ //! Draw geometry using meshes of triangles. +use crate::{Color, Gradient}; + use bytemuck::{Pod, Zeroable}; /// A set of [`Vertex2D`] and indices representing a list of triangles. @@ -6,20 +8,37 @@ 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. + /// 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], +} + +#[derive(Debug, Clone, PartialEq)] +/// Supported shaders for triangle primitives. +pub enum Style { + /// Fill a primitive with a solid color. + Solid(Color), + /// Fill a primitive with an interpolated 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) + } } diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index b4afd998..a14940d9 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -3,19 +3,19 @@ //! 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 text; +pub use crate::gradient::{self, Gradient}; pub use cache::Cache; pub use cursor::Cursor; pub use event::Event; diff --git a/graphics/src/widget/canvas/fill.rs b/graphics/src/widget/canvas/fill.rs index 56495435..c69fc0d7 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::triangle::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 [`FillStyle::Solid`] `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..cf6c6928 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/graphics/src/widget/canvas/frame.rs @@ -1,13 +1,14 @@ -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::triangle::Vertex2D; +use crate::widget::canvas::{path, Fill, Geometry, Path, Stroke, 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 +16,41 @@ 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, } +struct BufferStack { + stack: Vec<(tessellation::VertexBuffers<Vertex2D, u32>, triangle::Style)>, +} + +impl BufferStack { + fn new() -> Self { + Self { stack: Vec::new() } + } + + fn get( + &mut self, + mesh_style: triangle::Style, + ) -> tessellation::BuffersBuilder<'_, Vertex2D, u32, Vertex2DBuilder> { + match self.stack.last_mut() { + Some((_, current_style)) if current_style == &mesh_style => {} + _ => { + self.stack + .push((tessellation::VertexBuffers::new(), mesh_style)); + } + }; + + tessellation::BuffersBuilder::new( + &mut self.stack.last_mut().unwrap().0, + Vertex2DBuilder, + ) + } +} + #[derive(Debug)] struct Transforms { previous: Vec<Transform>, @@ -34,6 +63,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: triangle::Style) -> triangle::Style { + match style { + triangle::Style::Solid(color) => triangle::Style::Solid(color), + triangle::Style::Gradient(gradient) => { + triangle::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 +100,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 +141,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(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, + &mut buffer, ) } else { let path = path.transformed(&self.transforms.current.raw); @@ -105,11 +162,10 @@ impl Frame { self.fill_tessellator.tessellate_path( path.raw(), &options, - &mut buffers, + &mut buffer, ) - }; - - result.expect("Tessellate path"); + } + .expect("Tessellate path."); } /// Draws an axis-aligned rectangle given its top-left corner coordinate and @@ -120,12 +176,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(self.transforms.current.transform_style(style)); let top_left = self.transforms.current.raw.transform_point( @@ -144,7 +199,7 @@ impl Frame { .tessellate_rectangle( &lyon::math::Box2D::new(top_left, top_left + size), &options, - &mut buffers, + &mut buffer, ) .expect("Fill rectangle"); } @@ -154,10 +209,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(self.transforms.current.transform_style(stroke.style)); let mut options = tessellation::StrokeOptions::default(); options.line_width = stroke.width; @@ -171,11 +225,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, + &mut buffer, ) } else { let path = path.transformed(&self.transforms.current.raw); @@ -183,11 +237,10 @@ impl Frame { self.stroke_tessellator.tessellate_path( path.raw(), &options, - &mut buffers, + &mut buffer, ) - }; - - result.expect("Stroke path"); + } + .expect("Stroke path"); } /// Draws the characters of the given [`Text`] on the [`Frame`], filling @@ -206,8 +259,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 +355,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,52 +382,44 @@ 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, style) in self.buffers.stack { + if !buffer.indices.is_empty() { + self.primitives.push(Primitive::Mesh2D { + buffers: triangle::Mesh2D { + vertices: buffer.vertices, + indices: buffer.indices, + }, + size: self.size, + style, + }) + } } self.primitives } } -struct FillVertex([f32; 4]); +struct Vertex2DBuilder; -impl lyon::tessellation::FillVertexConstructor<triangle::Vertex2D> - for FillVertex -{ - fn new_vertex( - &mut self, - vertex: lyon::tessellation::FillVertex<'_>, - ) -> triangle::Vertex2D { +impl tessellation::FillVertexConstructor<Vertex2D> for Vertex2DBuilder { + fn new_vertex(&mut self, vertex: tessellation::FillVertex<'_>) -> Vertex2D { let position = vertex.position(); - triangle::Vertex2D { + Vertex2D { position: [position.x, position.y], - color: self.0, } } } -struct StrokeVertex([f32; 4]); - -impl lyon::tessellation::StrokeVertexConstructor<triangle::Vertex2D> - for StrokeVertex -{ +impl tessellation::StrokeVertexConstructor<Vertex2D> for Vertex2DBuilder { fn new_vertex( &mut self, - vertex: lyon::tessellation::StrokeVertex<'_, '_>, - ) -> triangle::Vertex2D { + vertex: tessellation::StrokeVertex<'_, '_>, + ) -> Vertex2D { let position = vertex.position(); - triangle::Vertex2D { + Vertex2D { 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..f9b8e447 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::triangle::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 [`StrokeStyle::Solid`] `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(), |