diff options
Diffstat (limited to 'graphics/src/widget')
-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 |
4 files changed, 145 insertions, 82 deletions
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(), |