Adds linear gradient support to 2D meshes in the canvas widget.
This commit is contained in:
parent
97f385e093
commit
00a8a16712
40 changed files with 2041 additions and 653 deletions
|
|
@ -19,7 +19,7 @@ font-icons = []
|
|||
opengl = []
|
||||
|
||||
[dependencies]
|
||||
glam = "0.10"
|
||||
glam = "0.21.3"
|
||||
raw-window-handle = "0.4"
|
||||
thiserror = "1.0"
|
||||
|
||||
|
|
|
|||
23
graphics/src/gradient.rs
Normal file
23
graphics/src/gradient.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
//! For creating a Gradient.
|
||||
|
||||
use iced_native::Color;
|
||||
use crate::widget::canvas::gradient::Linear;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
/// A fill which transitions colors progressively along a direction, either linearly, radially,
|
||||
/// or conically.
|
||||
pub enum Gradient {
|
||||
/// A linear gradient interpolates colors along a direction from its [`start`] to its [`end`]
|
||||
/// point.
|
||||
Linear(Linear),
|
||||
}
|
||||
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
|
@ -7,9 +7,10 @@ use crate::{
|
|||
|
||||
use iced_native::image;
|
||||
use iced_native::svg;
|
||||
use crate::shader::Shader;
|
||||
|
||||
/// 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,
|
||||
|
|
@ -18,7 +19,7 @@ pub struct Layer<'a> {
|
|||
pub quads: Vec<Quad>,
|
||||
|
||||
/// The triangle meshes of the [`Layer`].
|
||||
pub meshes: Vec<Mesh<'a>>,
|
||||
pub meshes: Meshes<'a>,
|
||||
|
||||
/// The text of the [`Layer`].
|
||||
pub text: Vec<Text<'a>>,
|
||||
|
|
@ -33,7 +34,7 @@ impl<'a> Layer<'a> {
|
|||
Self {
|
||||
bounds,
|
||||
quads: Vec::new(),
|
||||
meshes: Vec::new(),
|
||||
meshes: Meshes(Vec::new()),
|
||||
text: Vec::new(),
|
||||
images: Vec::new(),
|
||||
}
|
||||
|
|
@ -159,7 +160,11 @@ impl<'a> Layer<'a> {
|
|||
border_color: border_color.into_linear(),
|
||||
});
|
||||
}
|
||||
Primitive::Mesh2D { buffers, size } => {
|
||||
Primitive::Mesh2D {
|
||||
buffers,
|
||||
size,
|
||||
shader,
|
||||
} => {
|
||||
let layer = &mut layers[current_layer];
|
||||
|
||||
let bounds = Rectangle::new(
|
||||
|
|
@ -169,11 +174,14 @@ impl<'a> Layer<'a> {
|
|||
|
||||
// Only draw visible content
|
||||
if let Some(clip_bounds) = layer.bounds.intersection(&bounds) {
|
||||
layer.meshes.push(Mesh {
|
||||
origin: Point::new(translation.x, translation.y),
|
||||
buffers,
|
||||
clip_bounds,
|
||||
});
|
||||
layer.meshes.0.push(
|
||||
Mesh {
|
||||
origin: Point::new(translation.x, translation.y),
|
||||
buffers,
|
||||
clip_bounds,
|
||||
shader,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
Primitive::Clip { bounds, content } => {
|
||||
|
|
@ -270,6 +278,9 @@ pub struct Mesh<'a> {
|
|||
|
||||
/// The clipping bounds of the [`Mesh`].
|
||||
pub clip_bounds: Rectangle<f32>,
|
||||
|
||||
/// The shader of the [`Mesh`].
|
||||
pub shader: &'a Shader,
|
||||
}
|
||||
|
||||
/// A paragraph of text.
|
||||
|
|
@ -323,3 +334,21 @@ unsafe impl bytemuck::Zeroable for Quad {}
|
|||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe impl bytemuck::Pod for Quad {}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// A collection of meshes.
|
||||
pub struct Meshes<'a>(pub Vec<Mesh<'a>>);
|
||||
|
||||
impl<'a> Meshes<'a> {
|
||||
/// Returns the number of total vertices & total indices of all [`Mesh`]es.
|
||||
pub fn attribute_count(&self) -> (usize, usize) {
|
||||
self.0
|
||||
.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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -35,6 +35,8 @@ pub mod renderer;
|
|||
pub mod triangle;
|
||||
pub mod widget;
|
||||
pub mod window;
|
||||
pub mod shader;
|
||||
pub mod gradient;
|
||||
|
||||
pub use antialiasing::Antialiasing;
|
||||
pub use backend::Backend;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use iced_native::image;
|
|||
use iced_native::svg;
|
||||
use iced_native::{Background, Color, Font, Rectangle, Size, Vector};
|
||||
|
||||
use crate::alignment;
|
||||
use crate::{alignment, shader};
|
||||
use crate::triangle;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
|
@ -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
|
||||
shader: shader::Shader,
|
||||
},
|
||||
/// A cached primitive.
|
||||
///
|
||||
|
|
|
|||
42
graphics/src/shader.rs
Normal file
42
graphics/src/shader.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
//! Supported shaders;
|
||||
|
||||
use crate::{Color, widget};
|
||||
use crate::gradient::Gradient;
|
||||
use crate::widget::canvas::{FillStyle, StrokeStyle};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Supported shaders for primitives.
|
||||
pub enum Shader {
|
||||
/// Fill a primitive with a solid color.
|
||||
Solid(Color),
|
||||
/// Fill a primitive with an interpolated color.
|
||||
Gradient(Gradient)
|
||||
}
|
||||
|
||||
impl <'a> Into<Shader> for StrokeStyle<'a> {
|
||||
fn into(self) -> Shader {
|
||||
match self {
|
||||
StrokeStyle::Solid(color) => Shader::Solid(color),
|
||||
StrokeStyle::Gradient(gradient) => gradient.clone().into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl <'a> Into<Shader> for FillStyle<'a> {
|
||||
fn into(self) -> Shader {
|
||||
match self {
|
||||
FillStyle::Solid(color) => Shader::Solid(color),
|
||||
FillStyle::Gradient(gradient) => gradient.clone().into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl <'a> Into<Shader> for widget::canvas::Gradient {
|
||||
fn into(self) -> Shader {
|
||||
match self {
|
||||
widget::canvas::Gradient::Linear(linear) => {
|
||||
Shader::Gradient(Gradient::Linear(linear))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 Into<Mat4> for Transformation {
|
||||
fn into(self) -> Mat4 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
|
@ -6,20 +6,22 @@ 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.
|
||||
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],
|
||||
}
|
||||
|
||||
/// Convert from lyon's position data to Iced's Vertex2D type.
|
||||
impl Vertex2D {
|
||||
/// Converts from [`lyon::math::Point`] to [`Vertex2D`]. Used for generating primitives.
|
||||
pub fn from(points: Vec<lyon::math::Point>) -> Vec<Vertex2D> {
|
||||
points.iter().map(|p| Vertex2D { position: [p.x, p.y]}).collect()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -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::{Fill, Geometry, Path, Stroke, Text, path};
|
||||
use crate::Primitive;
|
||||
|
||||
use crate::triangle::{Vertex2D};
|
||||
use crate::shader::Shader;
|
||||
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
|
||||
|
|
@ -331,52 +333,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,
|
||||
});
|
||||
if !self.primitives.is_empty() {
|
||||
for (buffer, shader) in self.buffers {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
21
graphics/src/widget/canvas/gradient.rs
Normal file
21
graphics/src/widget/canvas/gradient.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
73
graphics/src/widget/canvas/gradient/linear.rs
Normal file
73
graphics/src/widget/canvas/gradient/linear.rs
Normal 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(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue