diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 5f9724f386..12184dd19d 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -163,6 +163,10 @@ impl canvas::Program for State { Stroke { width: 1.0, color: Color::from_rgba8(0, 153, 255, 0.1), + line_dash: canvas::LineDash { + offset: 0, + segments: &[3.0, 6.0], + }, ..Stroke::default() }, ); diff --git a/graphics/src/widget/canvas.rs b/graphics/src/widget/canvas.rs index f9722f33ca..1016bbe3d0 100644 --- a/graphics/src/widget/canvas.rs +++ b/graphics/src/widget/canvas.rs @@ -35,7 +35,7 @@ pub use frame::Frame; pub use geometry::Geometry; pub use path::Path; pub use program::Program; -pub use stroke::{LineCap, LineJoin, Stroke}; +pub use stroke::{LineCap, LineDash, LineJoin, Stroke}; pub use text::Text; /// A widget capable of drawing 2D graphics. diff --git a/graphics/src/widget/canvas/frame.rs b/graphics/src/widget/canvas/frame.rs index 4873e7fb8a..357dfa6286 100644 --- a/graphics/src/widget/canvas/frame.rs +++ b/graphics/src/widget/canvas/frame.rs @@ -1,6 +1,9 @@ +use std::borrow::Cow; + use iced_native::{Point, Rectangle, Size, Vector}; use crate::{ + canvas::path, canvas::{Fill, Geometry, Path, Stroke, Text}, triangle, Primitive, }; @@ -150,7 +153,7 @@ impl Frame { /// Draws the stroke of the given [`Path`] on the [`Frame`] with the /// provided style. - pub fn stroke(&mut self, path: &Path, stroke: impl Into) { + pub fn stroke<'a>(&mut self, path: &Path, stroke: impl Into>) { let stroke = stroke.into(); let mut buffers = tessellation::BuffersBuilder::new( @@ -164,6 +167,12 @@ impl Frame { options.end_cap = stroke.line_cap.into(); options.line_join = stroke.line_join.into(); + let path = if stroke.line_dash.segments.is_empty() { + Cow::Borrowed(path) + } else { + Cow::Owned(path::dashed(path, stroke.line_dash)) + }; + let result = if self.transforms.current.is_identity { self.stroke_tessellator.tessellate_path( path.raw(), diff --git a/graphics/src/widget/canvas/path.rs b/graphics/src/widget/canvas/path.rs index 4e4fd7344b..1728f0607c 100644 --- a/graphics/src/widget/canvas/path.rs +++ b/graphics/src/widget/canvas/path.rs @@ -7,7 +7,11 @@ mod builder; pub use arc::Arc; pub use builder::Builder; +use crate::canvas::LineDash; + use iced_native::{Point, Size}; +use lyon::algorithms::walk::{walk_along_path, RepeatedPattern}; +use lyon::path::iterator::PathIterator; /// An immutable set of points that may or may not be connected. /// @@ -66,3 +70,43 @@ impl Path { } } } + +pub(super) fn dashed(path: &Path, line_dash: LineDash<'_>) -> Path { + Path::new(|builder| { + let segments_odd = (line_dash.segments.len() % 2 == 1).then(|| { + [&line_dash.segments[..], &line_dash.segments[..]].concat() + }); + + let mut draw_line = false; + + walk_along_path( + path.raw().iter().flattened(0.01), + 0.0, + &mut RepeatedPattern { + callback: |position: lyon::algorithms::math::Point, + _tangent, + _distance| { + let point = Point { + x: position.x, + y: position.y, + }; + + if draw_line { + builder.line_to(point); + } else { + builder.move_to(point); + } + + draw_line = !draw_line; + + true + }, + index: line_dash.offset, + intervals: segments_odd + .as_ref() + .map(Vec::as_slice) + .unwrap_or(line_dash.segments), + }, + ); + }) +} diff --git a/graphics/src/widget/canvas/stroke.rs b/graphics/src/widget/canvas/stroke.rs index 9f0449d0a5..6accc2fbde 100644 --- a/graphics/src/widget/canvas/stroke.rs +++ b/graphics/src/widget/canvas/stroke.rs @@ -2,7 +2,7 @@ use iced_native::Color; /// The style of a stroke. #[derive(Debug, Clone, Copy)] -pub struct Stroke { +pub struct Stroke<'a> { /// The color of the stroke. pub color: Color, /// The distance between the two edges of the stroke. @@ -12,37 +12,40 @@ pub struct Stroke { /// The shape to be used at the corners of paths or basic shapes when they /// are stroked. pub line_join: LineJoin, + /// The dash pattern used when stroking the line. + pub line_dash: LineDash<'a>, } -impl Stroke { +impl<'a> Stroke<'a> { /// Sets the color of the [`Stroke`]. - pub fn with_color(self, color: Color) -> Stroke { + pub fn with_color(self, color: Color) -> Self { Stroke { color, ..self } } /// Sets the width of the [`Stroke`]. - pub fn with_width(self, width: f32) -> Stroke { + pub fn with_width(self, width: f32) -> Self { Stroke { width, ..self } } /// Sets the [`LineCap`] of the [`Stroke`]. - pub fn with_line_cap(self, line_cap: LineCap) -> Stroke { + pub fn with_line_cap(self, line_cap: LineCap) -> Self { Stroke { line_cap, ..self } } /// Sets the [`LineJoin`] of the [`Stroke`]. - pub fn with_line_join(self, line_join: LineJoin) -> Stroke { + pub fn with_line_join(self, line_join: LineJoin) -> Self { Stroke { line_join, ..self } } } -impl Default for Stroke { - fn default() -> Stroke { +impl<'a> Default for Stroke<'a> { + fn default() -> Self { Stroke { color: Color::BLACK, width: 1.0, line_cap: LineCap::default(), line_join: LineJoin::default(), + line_dash: LineDash::default(), } } } @@ -103,3 +106,13 @@ impl From for lyon::tessellation::LineJoin { } } } + +/// The dash pattern used when stroking the line. +#[derive(Debug, Clone, Copy, Default)] +pub struct LineDash<'a> { + /// The alternating lengths of lines and gaps which describe the pattern. + pub segments: &'a [f32], + + /// The offset of [`LineDash::segments`] to start the pattern. + pub offset: usize, +}