summaryrefslogtreecommitdiffstats
path: root/graphics
diff options
context:
space:
mode:
Diffstat (limited to 'graphics')
-rw-r--r--graphics/Cargo.toml2
-rw-r--r--graphics/src/gradient.rs112
-rw-r--r--graphics/src/gradient/linear.rs109
-rw-r--r--graphics/src/layer.rs114
-rw-r--r--graphics/src/layer/image.rs23
-rw-r--r--graphics/src/layer/mesh.rs31
-rw-r--r--graphics/src/layer/quad.rs30
-rw-r--r--graphics/src/layer/text.rs26
-rw-r--r--graphics/src/lib.rs2
-rw-r--r--graphics/src/primitive.rs3
-rw-r--r--graphics/src/transformation.rs8
-rw-r--r--graphics/src/triangle.rs33
-rw-r--r--graphics/src/widget/canvas.rs6
-rw-r--r--graphics/src/widget/canvas/fill.rs30
-rw-r--r--graphics/src/widget/canvas/frame.rs173
-rw-r--r--graphics/src/widget/canvas/stroke.rs18
16 files changed, 533 insertions, 187 deletions
diff --git a/graphics/Cargo.toml b/graphics/Cargo.toml
index 18fef54f..3b0e5236 100644
--- a/graphics/Cargo.toml
+++ b/graphics/Cargo.toml
@@ -19,7 +19,7 @@ font-icons = []
opengl = []
[dependencies]
-glam = "0.10"
+glam = "0.21.3"
raw-window-handle = "0.5"
thiserror = "1.0"
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(),