summaryrefslogtreecommitdiffstats
path: root/graphics/src/widget
diff options
context:
space:
mode:
Diffstat (limited to 'graphics/src/widget')
-rw-r--r--graphics/src/widget/canvas.rs21
-rw-r--r--graphics/src/widget/canvas/fill.rs57
-rw-r--r--graphics/src/widget/canvas/frame.rs168
-rw-r--r--graphics/src/widget/canvas/stroke.rs41
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 {