diff options
Diffstat (limited to 'graphics/src/widget/canvas/frame.rs')
-rw-r--r-- | graphics/src/widget/canvas/frame.rs | 260 |
1 files changed, 204 insertions, 56 deletions
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, } } |