diff options
Diffstat (limited to 'graphics/src/widget')
-rw-r--r-- | graphics/src/widget/canvas.rs | 21 | ||||
-rw-r--r-- | graphics/src/widget/canvas/fill.rs | 57 | ||||
-rw-r--r-- | graphics/src/widget/canvas/frame.rs | 168 | ||||
-rw-r--r-- | graphics/src/widget/canvas/stroke.rs | 41 |
4 files changed, 194 insertions, 93 deletions
diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index b4afd998..b9ccb721 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -9,11 +9,11 @@ pub mod path; mod cache; mod cursor; -mod fill; -mod frame; +pub mod fill; +pub(crate) mod frame; mod geometry; mod program; -mod stroke; +pub mod stroke; mod text; pub use cache::Cache; @@ -27,6 +27,7 @@ pub use program::Program; pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; pub use text::Text; +pub use crate::gradient::{self, Gradient}; use crate::{Backend, Primitive, Renderer}; use iced_native::layout::{self, Layout}; @@ -45,16 +46,12 @@ use std::marker::PhantomData; /// If you want to get a quick overview, here's how we can draw a simple circle: /// /// ```no_run -/// # mod iced { -/// # pub mod widget { -/// # pub use iced_graphics::widget::canvas; -/// # } -/// # pub use iced_native::{Color, Rectangle, Theme}; -/// # } -/// use iced::widget::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program}; -/// use iced::{Color, Rectangle, Theme}; -/// /// // First, we define the data we need for drawing +/// use iced_graphics::{Color, Rectangle}; +/// use iced_graphics::widget::Canvas; +/// use iced_graphics::widget::canvas::{Cursor, Frame, Geometry, Path, Program}; +/// use iced_style::Theme; +/// /// #[derive(Debug)] /// struct Circle { /// radius: f32, diff --git a/graphics/src/widget/canvas/fill.rs b/graphics/src/widget/canvas/fill.rs index 56495435..2be8ed41 100644 --- a/graphics/src/widget/canvas/fill.rs +++ b/graphics/src/widget/canvas/fill.rs @@ -1,12 +1,17 @@ +//! Fill [crate::widget::canvas::Geometry] with a certain style. + +use crate::gradient::Gradient; +use crate::layer::mesh; +use crate::widget::canvas::frame::Transform; use iced_native::Color; /// 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: Style<'a>, /// The fill rule defines how to determine what is inside and what is /// outside of a shape. @@ -19,24 +24,54 @@ 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: Style::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: Style::Solid(color), ..Fill::default() } } } +impl<'a> From<&'a Gradient> for Fill<'a> { + fn from(gradient: &'a Gradient) -> Self { + Fill { + style: Style::Gradient(gradient), + ..Default::default() + } + } +} + +/// The style of a [`Fill`]. +#[derive(Debug, Clone)] +pub enum Style<'a> { + /// A solid color + Solid(Color), + /// A color gradient + Gradient(&'a Gradient), +} + +impl<'a> Style<'a> { + /// Converts a fill's [Style] to a [mesh::Style] for use in the renderer's shader. + pub(crate) fn as_mesh_style(&self, transform: &Transform) -> mesh::Style { + match self { + Style::Solid(color) => mesh::Style::Solid(*color), + Style::Gradient(gradient) => mesh::Style::Gradient( + transform.transform_gradient((*gradient).clone()), + ), + } + } +} + /// 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..677d7bc5 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/graphics/src/widget/canvas/frame.rs @@ -1,10 +1,13 @@ +use lyon::geom::euclid::Point2D; use std::borrow::Cow; use iced_native::{Point, Rectangle, Size, Vector}; +use crate::gradient::Gradient; +use crate::layer::mesh; 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 lyon::tessellation; @@ -15,13 +18,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>, mesh::Style)>, +} + +impl BufferStack { + fn new() -> Self { + Self { stack: Vec::new() } + } + + fn get( + &mut self, + mesh_style: mesh::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>, @@ -29,11 +60,33 @@ struct Transforms { } #[derive(Debug, Clone, Copy)] -struct Transform { +pub(crate) struct Transform { raw: lyon::math::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(Point2D::new(point.x, point.y)); + point.x = transformed.x; + point.y = transformed.y; + } + + pub(crate) 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 +95,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(), @@ -82,22 +135,21 @@ 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 buffer = self + .buffers + .get(style.as_mesh_style(&self.transforms.current)); 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,27 +157,25 @@ 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 /// 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 buffers = tessellation::BuffersBuilder::new( - &mut self.buffers, - FillVertex(color.into_linear()), - ); + let mut buffer = self + .buffers + .get(style.as_mesh_style(&self.transforms.current)); let top_left = self.transforms.current.raw.transform_point( @@ -144,7 +194,7 @@ impl Frame { .tessellate_rectangle( &lyon::math::Box2D::new(top_left, top_left + size), &options, - &mut buffers, + &mut buffer, ) .expect("Fill rectangle"); } @@ -154,10 +204,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.style.as_mesh_style(&self.transforms.current)); let mut options = tessellation::StrokeOptions::default(); options.line_width = stroke.width; @@ -171,11 +220,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 +232,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 +254,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 +350,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 +377,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..2f02a2f3 100644 --- a/graphics/src/widget/canvas/stroke.rs +++ b/graphics/src/widget/canvas/stroke.rs @@ -1,10 +1,17 @@ +//! Create lines from a [crate::widget::canvas::Path] and assigns them various attributes/styles. + +use crate::gradient::Gradient; +use crate::layer::mesh; +use crate::widget::canvas::frame::Transform; 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<'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 +26,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 +51,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(), @@ -50,6 +60,27 @@ impl<'a> Default for Stroke<'a> { } } +/// The style of a [`Stroke`]. +#[derive(Debug, Clone, Copy)] +pub enum Style<'a> { + /// A solid color + Solid(Color), + /// A color gradient + Gradient(&'a Gradient), +} + +impl<'a> Style<'a> { + /// Converts a fill's [Style] to a [mesh::Style] for use in the renderer's shader. + pub(crate) fn as_mesh_style(&self, transform: &Transform) -> mesh::Style { + match self { + Style::Solid(color) => mesh::Style::Solid(*color), + Style::Gradient(gradient) => mesh::Style::Gradient( + transform.transform_gradient((*gradient).clone()), + ), + } + } +} + /// The shape used at the end of open subpaths when they are stroked. #[derive(Debug, Clone, Copy)] pub enum LineCap { |