Adds linear gradient support to 2D meshes in the canvas widget.

This commit is contained in:
shan 2022-09-29 10:52:58 -07:00
parent 97f385e093
commit 40f45d7b7e
40 changed files with 2041 additions and 655 deletions

View file

@ -5,6 +5,7 @@
//! and more!
pub mod event;
pub mod gradient;
pub mod path;
mod cache;
@ -19,12 +20,13 @@ mod text;
pub use cache::Cache;
pub use cursor::Cursor;
pub use event::Event;
pub use fill::{Fill, FillRule};
pub use fill::{Fill, FillRule, FillStyle};
pub use frame::Frame;
pub use geometry::Geometry;
pub use gradient::Gradient;
pub use path::Path;
pub use program::Program;
pub use stroke::{LineCap, LineDash, LineJoin, Stroke};
pub use stroke::{LineCap, LineDash, LineJoin, Stroke, StrokeStyle};
pub use text::Text;
use crate::{Backend, Primitive, Renderer};

View file

@ -1,12 +1,14 @@
use iced_native::Color;
use crate::widget::canvas::Gradient;
/// 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: FillStyle<'a>,
/// The fill rule defines how to determine what is inside and what is
/// outside of a shape.
@ -19,24 +21,33 @@ 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: FillStyle::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: FillStyle::Solid(color),
..Fill::default()
}
}
}
/// The color or gradient of a [`Fill`].
#[derive(Debug, Clone)]
pub enum FillStyle<'a> {
/// A solid color
Solid(Color),
/// A color gradient
Gradient(&'a Gradient),
}
/// The fill rule defines how to determine what is inside and what is outside of
/// a shape.
///

View file

@ -3,11 +3,13 @@ use std::borrow::Cow;
use iced_native::{Point, Rectangle, Size, Vector};
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, Text};
use crate::Primitive;
use crate::shader::Shader;
use crate::triangle::Vertex2D;
use lyon::tessellation;
use lyon::tessellation::geometry_builder::Positions;
/// The frame of a [`Canvas`].
///
@ -15,7 +17,7 @@ use lyon::tessellation;
#[allow(missing_debug_implementations)]
pub struct Frame {
size: Size,
buffers: lyon::tessellation::VertexBuffers<triangle::Vertex2D, u32>,
buffers: Vec<(tessellation::VertexBuffers<lyon::math::Point, u32>, Shader)>,
primitives: Vec<Primitive>,
transforms: Transforms,
fill_tessellator: tessellation::FillTessellator,
@ -42,7 +44,7 @@ impl Frame {
pub fn new(size: Size) -> Frame {
Frame {
size,
buffers: lyon::tessellation::VertexBuffers::new(),
buffers: Vec::new(),
primitives: Vec::new(),
transforms: Transforms {
previous: Vec::new(),
@ -82,18 +84,18 @@ 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 buf = tessellation::VertexBuffers::new();
let mut buffers =
tessellation::BuffersBuilder::new(&mut buf, Positions);
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,
@ -107,25 +109,24 @@ impl Frame {
&options,
&mut buffers,
)
};
}.expect("Tessellate path.");
result.expect("Tessellate path");
self.buffers.push((buf, style.into()))
}
/// 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 buf = tessellation::VertexBuffers::new();
let mut buffers = tessellation::BuffersBuilder::new(&mut buf, Positions);
let top_left =
self.transforms.current.raw.transform_point(
@ -147,6 +148,8 @@ impl Frame {
&mut buffers,
)
.expect("Fill rectangle");
self.buffers.push((buf, style.into()))
}
/// Draws the stroke of the given [`Path`] on the [`Frame`] with the
@ -154,10 +157,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 buf = tessellation::VertexBuffers::new();
let mut buffers = tessellation::BuffersBuilder::new(&mut buf, Positions);
let mut options = tessellation::StrokeOptions::default();
options.line_width = stroke.width;
@ -171,7 +173,7 @@ 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,
@ -185,9 +187,9 @@ impl Frame {
&options,
&mut buffers,
)
};
}.expect("Stroke path");
result.expect("Stroke path");
self.buffers.push((buf, stroke.style.into()))
}
/// Draws the characters of the given [`Text`] on the [`Frame`], filling
@ -206,8 +208,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 {
@ -331,52 +331,19 @@ 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, shader) in self.buffers {
if !buffer.indices.is_empty() {
self.primitives.push(Primitive::Mesh2D {
buffers: triangle::Mesh2D {
vertices: Vertex2D::from(buffer.vertices),
indices: buffer.indices,
},
size: self.size,
shader,
})
}
}
self.primitives
}
}
struct FillVertex([f32; 4]);
impl lyon::tessellation::FillVertexConstructor<triangle::Vertex2D>
for FillVertex
{
fn new_vertex(
&mut self,
vertex: lyon::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
{
fn new_vertex(
&mut self,
vertex: lyon::tessellation::StrokeVertex<'_, '_>,
) -> triangle::Vertex2D {
let position = vertex.position();
triangle::Vertex2D {
position: [position.x, position.y],
color: self.0,
}
}
}

View file

@ -0,0 +1,21 @@
//! Define a color gradient.
use iced_native::Point;
pub mod linear;
pub use linear::Linear;
/// A gradient that can be used in the style of [`super::Fill`] or [`super::Stroke`].
#[derive(Debug, Clone)]
pub enum Gradient {
/// A linear gradient
Linear(Linear),
//TODO: radial, conical
}
impl Gradient {
/// Creates a new linear [`linear::Builder`].
pub fn linear(start: Point, end: Point) -> linear::Builder {
linear::Builder::new(start, end)
}
}

View file

@ -0,0 +1,73 @@
//! A linear color gradient.
use iced_native::{Color, Point};
use crate::gradient::ColorStop;
use super::Gradient;
/// 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<(f32, Color)>,
valid: bool,
}
impl Builder {
/// Creates a new [`Builder`].
pub fn new(start: Point, end: Point) -> Self {
Self {
start,
end,
stops: vec![],
valid: true,
}
}
/// Adds a new stop, defined by an offset and a color, to the gradient.
///
/// `offset` must be between `0.0` and `1.0`.
pub fn add_stop(mut self, offset: f32, color: Color) -> Self {
if !(0.0..=1.0).contains(&offset) {
self.valid = false;
}
self.stops.push((offset, color));
self
}
/// Builds the linear [`Gradient`] of this [`Builder`].
///
/// Returns `None` if no stops were added to the builder or
/// if stops not between 0.0 and 1.0 were added.
pub fn build(self) -> Option<Gradient> {
if self.stops.is_empty() || !self.valid {
return None;
}
Some(Gradient::Linear(Linear {
start: self.start,
end: self.end,
color_stops: self
.stops
.into_iter()
.map(|f| ColorStop {
offset: f.0,
color: f.1,
})
.collect(),
}))
}
}

View file

@ -1,10 +1,14 @@
use iced_native::Color;
use crate::widget::canvas::Gradient;
/// 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: StrokeStyle<'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 +23,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: StrokeStyle::Solid(color),
..self
}
}
/// Sets the width of the [`Stroke`].
@ -41,7 +48,7 @@ impl<'a> Stroke<'a> {
impl<'a> Default for Stroke<'a> {
fn default() -> Self {
Stroke {
color: Color::BLACK,
style: StrokeStyle::Solid(Color::BLACK),
width: 1.0,
line_cap: LineCap::default(),
line_join: LineJoin::default(),
@ -50,6 +57,15 @@ impl<'a> Default for Stroke<'a> {
}
}
/// The color or gradient of a [`Stroke`].
#[derive(Debug, Clone, Copy)]
pub enum StrokeStyle<'a> {
/// A solid color
Solid(Color),
/// A color gradient
Gradient(&'a Gradient),
}
/// The shape used at the end of open subpaths when they are stroked.
#[derive(Debug, Clone, Copy)]
pub enum LineCap {