diff options
author | 2020-02-20 05:51:18 +0100 | |
---|---|---|
committer | 2020-02-20 05:51:18 +0100 | |
commit | 17271eae671a933a862dc85aa5b9956a7da70b28 (patch) | |
tree | 1a24e3eebb544b0bc8b0ebf2d92f330141f699bf /wgpu/src/widget | |
parent | 8d63c49ba1aa43407e0dab0a8e69d3f316a79279 (diff) | |
parent | 6f7247ca13181bcdfe1a3065215c1b3204723b84 (diff) | |
download | iced-17271eae671a933a862dc85aa5b9956a7da70b28.tar.gz iced-17271eae671a933a862dc85aa5b9956a7da70b28.tar.bz2 iced-17271eae671a933a862dc85aa5b9956a7da70b28.zip |
Merge pull request #193 from hecrj/feature/canvas
Canvas widget for 2D graphics
Diffstat (limited to 'wgpu/src/widget')
-rw-r--r-- | wgpu/src/widget/canvas.rs | 149 | ||||
-rw-r--r-- | wgpu/src/widget/canvas/drawable.rs | 12 | ||||
-rw-r--r-- | wgpu/src/widget/canvas/fill.rs | 14 | ||||
-rw-r--r-- | wgpu/src/widget/canvas/frame.rs | 255 | ||||
-rw-r--r-- | wgpu/src/widget/canvas/layer.rs | 25 | ||||
-rw-r--r-- | wgpu/src/widget/canvas/layer/cache.rs | 101 | ||||
-rw-r--r-- | wgpu/src/widget/canvas/path.rs | 49 | ||||
-rw-r--r-- | wgpu/src/widget/canvas/path/arc.rs | 44 | ||||
-rw-r--r-- | wgpu/src/widget/canvas/path/builder.rs | 177 | ||||
-rw-r--r-- | wgpu/src/widget/canvas/stroke.rs | 83 |
10 files changed, 909 insertions, 0 deletions
diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs new file mode 100644 index 00000000..38c1ce62 --- /dev/null +++ b/wgpu/src/widget/canvas.rs @@ -0,0 +1,149 @@ +//! Draw 2D graphics for your users. +//! +//! 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! +//! +//! [`Canvas`]: struct.Canvas.html +//! [`Frame`]: struct.Frame.html +use crate::{Defaults, Primitive, Renderer}; + +use iced_native::{ + layout, Element, Hasher, Layout, Length, MouseCursor, Point, Size, Widget, +}; +use std::hash::Hash; + +pub mod layer; +pub mod path; + +mod drawable; +mod fill; +mod frame; +mod stroke; + +pub use drawable::Drawable; +pub use fill::Fill; +pub use frame::Frame; +pub use layer::Layer; +pub use path::Path; +pub use stroke::{LineCap, LineJoin, Stroke}; + +/// A widget capable of drawing 2D graphics. +/// +/// A [`Canvas`] may contain multiple layers. A [`Layer`] is drawn using the +/// painter's algorithm. In other words, layers will be drawn on top of each in +/// the same order they are pushed into the [`Canvas`]. +/// +/// [`Canvas`]: struct.Canvas.html +/// [`Layer`]: layer/trait.Layer.html +#[derive(Debug)] +pub struct Canvas<'a> { + width: Length, + height: Length, + layers: Vec<Box<dyn Layer + 'a>>, +} + +impl<'a> Canvas<'a> { + const DEFAULT_SIZE: u16 = 100; + + /// Creates a new [`Canvas`] with no layers. + /// + /// [`Canvas`]: struct.Canvas.html + pub fn new() -> Self { + Canvas { + width: Length::Units(Self::DEFAULT_SIZE), + height: Length::Units(Self::DEFAULT_SIZE), + layers: Vec::new(), + } + } + + /// Sets the width of the [`Canvas`]. + /// + /// [`Canvas`]: struct.Canvas.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Canvas`]. + /// + /// [`Canvas`]: struct.Canvas.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Adds a [`Layer`] to the [`Canvas`]. + /// + /// It will be drawn on top of previous layers. + /// + /// [`Layer`]: layer/trait.Layer.html + /// [`Canvas`]: struct.Canvas.html + pub fn push(mut self, layer: impl Layer + 'a) -> Self { + self.layers.push(Box::new(layer)); + self + } +} + +impl<'a, Message> Widget<Message, Renderer> for Canvas<'a> { + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.width(self.width).height(self.height); + let size = limits.resolve(Size::ZERO); + + layout::Node::new(size) + } + + fn draw( + &self, + _renderer: &mut Renderer, + _defaults: &Defaults, + layout: Layout<'_>, + _cursor_position: Point, + ) -> (Primitive, MouseCursor) { + let bounds = layout.bounds(); + let origin = Point::new(bounds.x, bounds.y); + let size = Size::new(bounds.width, bounds.height); + + ( + Primitive::Group { + primitives: self + .layers + .iter() + .map(|layer| Primitive::Mesh2D { + origin, + buffers: layer.draw(size), + }) + .collect(), + }, + MouseCursor::Idle, + ) + } + + fn hash_layout(&self, state: &mut Hasher) { + std::any::TypeId::of::<Canvas<'static>>().hash(state); + + self.width.hash(state); + self.height.hash(state); + } +} + +impl<'a, Message> From<Canvas<'a>> for Element<'a, Message, Renderer> +where + Message: 'static, +{ + fn from(canvas: Canvas<'a>) -> Element<'a, Message, Renderer> { + Element::new(canvas) + } +} diff --git a/wgpu/src/widget/canvas/drawable.rs b/wgpu/src/widget/canvas/drawable.rs new file mode 100644 index 00000000..6c74071c --- /dev/null +++ b/wgpu/src/widget/canvas/drawable.rs @@ -0,0 +1,12 @@ +use crate::canvas::Frame; + +/// A type that can be drawn on a [`Frame`]. +/// +/// [`Frame`]: struct.Frame.html +pub trait Drawable { + /// Draws the [`Drawable`] on the given [`Frame`]. + /// + /// [`Drawable`]: trait.Drawable.html + /// [`Frame`]: struct.Frame.html + fn draw(&self, frame: &mut Frame); +} diff --git a/wgpu/src/widget/canvas/fill.rs b/wgpu/src/widget/canvas/fill.rs new file mode 100644 index 00000000..5ce24cf3 --- /dev/null +++ b/wgpu/src/widget/canvas/fill.rs @@ -0,0 +1,14 @@ +use iced_native::Color; + +/// The style used to fill geometry. +#[derive(Debug, Clone, Copy)] +pub enum Fill { + /// Fill with a color. + Color(Color), +} + +impl Default for Fill { + fn default() -> Fill { + Fill::Color(Color::BLACK) + } +} diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs new file mode 100644 index 00000000..fa6d8c0a --- /dev/null +++ b/wgpu/src/widget/canvas/frame.rs @@ -0,0 +1,255 @@ +use iced_native::{Point, Size, Vector}; + +use crate::{ + canvas::{Fill, Path, Stroke}, + triangle, +}; + +/// The frame of a [`Canvas`]. +/// +/// [`Canvas`]: struct.Canvas.html +#[derive(Debug)] +pub struct Frame { + width: f32, + height: f32, + buffers: lyon::tessellation::VertexBuffers<triangle::Vertex2D, u32>, + transforms: Transforms, +} + +#[derive(Debug)] +struct Transforms { + previous: Vec<Transform>, + current: Transform, +} + +#[derive(Debug, Clone, Copy)] +struct Transform { + raw: lyon::math::Transform, + is_identity: bool, +} + +impl Frame { + /// Creates a new empty [`Frame`] with the given dimensions. + /// + /// The default coordinate system of a [`Frame`] has its origin at the + /// top-left corner of its bounds. + /// + /// [`Frame`]: struct.Frame.html + pub fn new(width: f32, height: f32) -> Frame { + Frame { + width, + height, + buffers: lyon::tessellation::VertexBuffers::new(), + transforms: Transforms { + previous: Vec::new(), + current: Transform { + raw: lyon::math::Transform::identity(), + is_identity: true, + }, + }, + } + } + + /// Returns the width of the [`Frame`]. + /// + /// [`Frame`]: struct.Frame.html + #[inline] + pub fn width(&self) -> f32 { + self.width + } + + /// Returns the width of the [`Frame`]. + /// + /// [`Frame`]: struct.Frame.html + #[inline] + pub fn height(&self) -> f32 { + self.height + } + + /// Returns the dimensions of the [`Frame`]. + /// + /// [`Frame`]: struct.Frame.html + #[inline] + pub fn size(&self) -> Size { + Size::new(self.width, self.height) + } + + /// Returns the coordinate of the center of the [`Frame`]. + /// + /// [`Frame`]: struct.Frame.html + #[inline] + pub fn center(&self) -> Point { + Point::new(self.width / 2.0, self.height / 2.0) + } + + /// Draws the given [`Path`] on the [`Frame`] by filling it with the + /// provided style. + /// + /// [`Path`]: path/struct.Path.html + /// [`Frame`]: struct.Frame.html + pub fn fill(&mut self, path: &Path, fill: Fill) { + use lyon::tessellation::{ + BuffersBuilder, FillOptions, FillTessellator, + }; + + let mut buffers = BuffersBuilder::new( + &mut self.buffers, + FillVertex(match fill { + Fill::Color(color) => color.into_linear(), + }), + ); + + let mut tessellator = FillTessellator::new(); + + let result = if self.transforms.current.is_identity { + tessellator.tessellate_path( + path.raw(), + &FillOptions::default(), + &mut buffers, + ) + } else { + let path = path.transformed(&self.transforms.current.raw); + + tessellator.tessellate_path( + path.raw(), + &FillOptions::default(), + &mut buffers, + ) + }; + + let _ = result.expect("Tessellate path"); + } + + /// Draws the stroke of the given [`Path`] on the [`Frame`] with the + /// provided style. + /// + /// [`Path`]: path/struct.Path.html + /// [`Frame`]: struct.Frame.html + pub fn stroke(&mut self, path: &Path, stroke: Stroke) { + use lyon::tessellation::{ + BuffersBuilder, StrokeOptions, StrokeTessellator, + }; + + let mut buffers = BuffersBuilder::new( + &mut self.buffers, + StrokeVertex(stroke.color.into_linear()), + ); + + let mut tessellator = StrokeTessellator::new(); + + let mut options = StrokeOptions::default(); + options.line_width = stroke.width; + options.start_cap = stroke.line_cap.into(); + options.end_cap = stroke.line_cap.into(); + options.line_join = stroke.line_join.into(); + + let result = if self.transforms.current.is_identity { + tessellator.tessellate_path(path.raw(), &options, &mut buffers) + } else { + let path = path.transformed(&self.transforms.current.raw); + + tessellator.tessellate_path(path.raw(), &options, &mut buffers) + }; + + let _ = result.expect("Stroke path"); + } + + /// Stores the current transform of the [`Frame`] and executes the given + /// drawing operations, restoring the transform afterwards. + /// + /// This method is useful to compose transforms and perform drawing + /// operations in different coordinate systems. + /// + /// [`Frame`]: struct.Frame.html + #[inline] + pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) { + self.transforms.previous.push(self.transforms.current); + + f(self); + + self.transforms.current = self.transforms.previous.pop().unwrap(); + } + + /// Applies a translation to the current transform of the [`Frame`]. + /// + /// [`Frame`]: struct.Frame.html + #[inline] + pub fn translate(&mut self, translation: Vector) { + self.transforms.current.raw = self + .transforms + .current + .raw + .pre_translate(lyon::math::Vector::new( + translation.x, + translation.y, + )); + self.transforms.current.is_identity = false; + } + + /// Applies a rotation to the current transform of the [`Frame`]. + /// + /// [`Frame`]: struct.Frame.html + #[inline] + pub fn rotate(&mut self, angle: f32) { + self.transforms.current.raw = self + .transforms + .current + .raw + .pre_rotate(lyon::math::Angle::radians(-angle)); + self.transforms.current.is_identity = false; + } + + /// Applies a scaling to the current transform of the [`Frame`]. + /// + /// [`Frame`]: struct.Frame.html + #[inline] + pub fn scale(&mut self, scale: f32) { + self.transforms.current.raw = + self.transforms.current.raw.pre_scale(scale, scale); + self.transforms.current.is_identity = false; + } + + /// Produces the geometry that has been drawn on the [`Frame`]. + /// + /// [`Frame`]: struct.Frame.html + pub fn into_mesh(self) -> triangle::Mesh2D { + triangle::Mesh2D { + vertices: self.buffers.vertices, + indices: self.buffers.indices, + } + } +} + +struct FillVertex([f32; 4]); + +impl lyon::tessellation::FillVertexConstructor<triangle::Vertex2D> + for FillVertex +{ + fn new_vertex( + &mut self, + position: lyon::math::Point, + _attributes: lyon::tessellation::FillAttributes<'_>, + ) -> triangle::Vertex2D { + 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, + position: lyon::math::Point, + _attributes: lyon::tessellation::StrokeAttributes<'_, '_>, + ) -> triangle::Vertex2D { + triangle::Vertex2D { + position: [position.x, position.y], + color: self.0, + } + } +} diff --git a/wgpu/src/widget/canvas/layer.rs b/wgpu/src/widget/canvas/layer.rs new file mode 100644 index 00000000..82d647bb --- /dev/null +++ b/wgpu/src/widget/canvas/layer.rs @@ -0,0 +1,25 @@ +//! Produce, store, and reuse geometry. +mod cache; + +pub use cache::Cache; + +use crate::triangle; + +use iced_native::Size; +use std::sync::Arc; + +/// A layer that can be presented at a [`Canvas`]. +/// +/// [`Canvas`]: ../struct.Canvas.html +pub trait Layer: std::fmt::Debug { + /// Draws the [`Layer`] in the given bounds and produces [`Mesh2D`] as a + /// result. + /// + /// The [`Layer`] may choose to store the produced [`Mesh2D`] locally and + /// only recompute it when the bounds change, its contents change, or is + /// otherwise explicitly cleared by other means. + /// + /// [`Layer`]: trait.Layer.html + /// [`Mesh2D`]: ../../../triangle/struct.Mesh2D.html + fn draw(&self, bounds: Size) -> Arc<triangle::Mesh2D>; +} diff --git a/wgpu/src/widget/canvas/layer/cache.rs b/wgpu/src/widget/canvas/layer/cache.rs new file mode 100644 index 00000000..3071cce0 --- /dev/null +++ b/wgpu/src/widget/canvas/layer/cache.rs @@ -0,0 +1,101 @@ +use crate::{ + canvas::{Drawable, Frame, Layer}, + triangle, +}; + +use iced_native::Size; +use std::cell::RefCell; +use std::marker::PhantomData; +use std::sync::Arc; + +/// A simple cache that stores generated geometry to avoid recomputation. +/// +/// A [`Cache`] will not redraw its geometry unless the dimensions of its layer +/// change or it is explicitly cleared. +/// +/// [`Layer`]: ../trait.Layer.html +/// [`Cached`]: struct.Cached.html +#[derive(Debug)] +pub struct Cache<T: Drawable> { + input: PhantomData<T>, + state: RefCell<State>, +} + +#[derive(Debug)] +enum State { + Empty, + Filled { + mesh: Arc<triangle::Mesh2D>, + bounds: Size, + }, +} + +impl<T> Cache<T> +where + T: Drawable + std::fmt::Debug, +{ + /// Creates a new empty [`Cache`]. + /// + /// [`Cache`]: struct.Cache.html + pub fn new() -> Self { + Cache { + input: PhantomData, + state: RefCell::new(State::Empty), + } + } + + /// Clears the cache, forcing a redraw the next time it is used. + /// + /// [`Cached`]: struct.Cached.html + pub fn clear(&mut self) { + *self.state.borrow_mut() = State::Empty; + } + + /// Binds the [`Cache`] with some data, producing a [`Layer`] that can be + /// added to a [`Canvas`]. + /// + /// [`Cache`]: struct.Cache.html + /// [`Layer`]: ../trait.Layer.html + /// [`Canvas`]: ../../struct.Canvas.html + pub fn with<'a>(&'a self, input: &'a T) -> impl Layer + 'a { + Bind { + cache: self, + input: input, + } + } +} + +#[derive(Debug)] +struct Bind<'a, T: Drawable> { + cache: &'a Cache<T>, + input: &'a T, +} + +impl<'a, T> Layer for Bind<'a, T> +where + T: Drawable + std::fmt::Debug, +{ + fn draw(&self, current_bounds: Size) -> Arc<triangle::Mesh2D> { + use std::ops::Deref; + + if let State::Filled { mesh, bounds } = + self.cache.state.borrow().deref() + { + if *bounds == current_bounds { + return mesh.clone(); + } + } + + let mut frame = Frame::new(current_bounds.width, current_bounds.height); + self.input.draw(&mut frame); + + let mesh = Arc::new(frame.into_mesh()); + + *self.cache.state.borrow_mut() = State::Filled { + mesh: mesh.clone(), + bounds: current_bounds, + }; + + mesh + } +} diff --git a/wgpu/src/widget/canvas/path.rs b/wgpu/src/widget/canvas/path.rs new file mode 100644 index 00000000..15c2e853 --- /dev/null +++ b/wgpu/src/widget/canvas/path.rs @@ -0,0 +1,49 @@ +//! Build different kinds of 2D shapes. +pub mod arc; + +mod builder; + +pub use arc::Arc; +pub use builder::Builder; + +/// An immutable set of points that may or may not be connected. +/// +/// A single [`Path`] can represent different kinds of 2D shapes! +/// +/// [`Path`]: struct.Path.html +#[derive(Debug, Clone)] +pub struct Path { + raw: lyon::path::Path, +} + +impl Path { + /// Creates a new [`Path`] with the provided closure. + /// + /// Use the [`Builder`] to configure your [`Path`]. + /// + /// [`Path`]: struct.Path.html + /// [`Builder`]: struct.Builder.html + pub fn new(f: impl FnOnce(&mut Builder)) -> Self { + let mut builder = Builder::new(); + + // TODO: Make it pure instead of side-effect-based (?) + f(&mut builder); + + builder.build() + } + + #[inline] + pub(crate) fn raw(&self) -> &lyon::path::Path { + &self.raw + } + + #[inline] + pub(crate) fn transformed( + &self, + transform: &lyon::math::Transform, + ) -> Path { + Path { + raw: self.raw.transformed(transform), + } + } +} diff --git a/wgpu/src/widget/canvas/path/arc.rs b/wgpu/src/widget/canvas/path/arc.rs new file mode 100644 index 00000000..343191f1 --- /dev/null +++ b/wgpu/src/widget/canvas/path/arc.rs @@ -0,0 +1,44 @@ +//! Build and draw curves. +use iced_native::{Point, Vector}; + +/// A segment of a differentiable curve. +#[derive(Debug, Clone, Copy)] +pub struct Arc { + /// The center of the arc. + pub center: Point, + /// The radius of the arc. + pub radius: f32, + /// The start of the segment's angle, clockwise rotation. + pub start_angle: f32, + /// The end of the segment's angle, clockwise rotation. + pub end_angle: f32, +} + +/// An elliptical [`Arc`]. +/// +/// [`Arc`]: struct.Arc.html +#[derive(Debug, Clone, Copy)] +pub struct Elliptical { + /// The center of the arc. + pub center: Point, + /// The radii of the arc's ellipse, defining its axes. + pub radii: Vector, + /// The rotation of the arc's ellipse. + pub rotation: f32, + /// The start of the segment's angle, clockwise rotation. + pub start_angle: f32, + /// The end of the segment's angle, clockwise rotation. + pub end_angle: f32, +} + +impl From<Arc> for Elliptical { + fn from(arc: Arc) -> Elliptical { + Elliptical { + center: arc.center, + radii: Vector::new(arc.radius, arc.radius), + rotation: 0.0, + start_angle: arc.start_angle, + end_angle: arc.end_angle, + } + } +} diff --git a/wgpu/src/widget/canvas/path/builder.rs b/wgpu/src/widget/canvas/path/builder.rs new file mode 100644 index 00000000..a013149e --- /dev/null +++ b/wgpu/src/widget/canvas/path/builder.rs @@ -0,0 +1,177 @@ +use crate::canvas::path::{arc, Arc, Path}; + +use iced_native::{Point, Size}; +use lyon::path::builder::{Build, FlatPathBuilder, PathBuilder, SvgBuilder}; + +/// A [`Path`] builder. +/// +/// Once a [`Path`] is built, it can no longer be mutated. +/// +/// [`Path`]: struct.Path.html +#[allow(missing_debug_implementations)] +pub struct Builder { + raw: lyon::path::builder::SvgPathBuilder<lyon::path::Builder>, +} + +impl Builder { + /// Creates a new [`Builder`]. + /// + /// [`Builder`]: struct.Builder.html + pub fn new() -> Builder { + Builder { + raw: lyon::path::Path::builder().with_svg(), + } + } + + /// Moves the starting point of a new sub-path to the given `Point`. + #[inline] + pub fn move_to(&mut self, point: Point) { + let _ = self.raw.move_to(lyon::math::Point::new(point.x, point.y)); + } + + /// Connects the last point in the [`Path`] to the given `Point` with a + /// straight line. + /// + /// [`Path`]: struct.Path.html + #[inline] + pub fn line_to(&mut self, point: Point) { + let _ = self.raw.line_to(lyon::math::Point::new(point.x, point.y)); + } + + /// Adds an [`Arc`] to the [`Path`] from `start_angle` to `end_angle` in + /// a clockwise direction. + /// + /// [`Arc`]: struct.Arc.html + /// [`Path`]: struct.Path.html + #[inline] + pub fn arc(&mut self, arc: Arc) { + self.ellipse(arc.into()); + } + + /// Adds a circular arc to the [`Path`] with the given control points and + /// radius. + /// + /// The arc is connected to the previous point by a straight line, if + /// necessary. + /// + /// [`Path`]: struct.Path.html + pub fn arc_to(&mut self, a: Point, b: Point, radius: f32) { + use lyon::{math, path}; + + let a = math::Point::new(a.x, a.y); + + if self.raw.current_position() != a { + let _ = self.raw.line_to(a); + } + + let _ = self.raw.arc_to( + math::Vector::new(radius, radius), + math::Angle::radians(0.0), + path::ArcFlags::default(), + math::Point::new(b.x, b.y), + ); + } + + /// Adds an [`Ellipse`] to the [`Path`] using a clockwise direction. + /// + /// [`Ellipse`]: struct.Arc.html + /// [`Path`]: struct.Path.html + pub fn ellipse(&mut self, arc: arc::Elliptical) { + use lyon::{geom, math}; + + let arc = geom::Arc { + center: math::Point::new(arc.center.x, arc.center.y), + radii: math::Vector::new(arc.radii.x, arc.radii.y), + x_rotation: math::Angle::radians(arc.rotation), + start_angle: math::Angle::radians(arc.start_angle), + sweep_angle: math::Angle::radians(arc.end_angle), + }; + + let _ = self.raw.move_to(arc.sample(0.0)); + + arc.for_each_quadratic_bezier(&mut |curve| { + let _ = self.raw.quadratic_bezier_to(curve.ctrl, curve.to); + }); + } + + /// Adds a cubic Bézier curve to the [`Path`] given its two control points + /// and its end point. + /// + /// [`Path`]: struct.Path.html + #[inline] + pub fn bezier_curve_to( + &mut self, + control_a: Point, + control_b: Point, + to: Point, + ) { + use lyon::math; + + let _ = self.raw.cubic_bezier_to( + math::Point::new(control_a.x, control_a.y), + math::Point::new(control_b.x, control_b.y), + math::Point::new(to.x, to.y), + ); + } + + /// Adds a quadratic Bézier curve to the [`Path`] given its control point + /// and its end point. + /// + /// [`Path`]: struct.Path.html + #[inline] + pub fn quadratic_curve_to(&mut self, control: Point, to: Point) { + use lyon::math; + + let _ = self.raw.quadratic_bezier_to( + math::Point::new(control.x, control.y), + math::Point::new(to.x, to.y), + ); + } + + /// Adds a rectangle to the [`Path`] given its top-left corner coordinate + /// and its `Size`. + /// + /// [`Path`]: struct.Path.html + #[inline] + pub fn rectangle(&mut self, p: Point, size: Size) { + self.move_to(p); + self.line_to(Point::new(p.x + size.width, p.y)); + self.line_to(Point::new(p.x + size.width, p.y + size.height)); + self.line_to(Point::new(p.x, p.y + size.height)); + self.close(); + } + + /// Adds a circle to the [`Path`] given its center coordinate and its + /// radius. + /// + /// [`Path`]: struct.Path.html + #[inline] + pub fn circle(&mut self, center: Point, radius: f32) { + self.arc(Arc { + center, + radius, + start_angle: 0.0, + end_angle: 2.0 * std::f32::consts::PI, + }); + } + + /// Closes the current sub-path in the [`Path`] with a straight line to + /// the starting point. + /// + /// [`Path`]: struct.Path.html + #[inline] + pub fn close(&mut self) { + self.raw.close() + } + + /// Builds the [`Path`] of this [`Builder`]. + /// + /// [`Path`]: struct.Path.html + /// [`Builder`]: struct.Builder.html + #[inline] + pub fn build(self) -> Path { + Path { + raw: self.raw.build(), + } + } +} diff --git a/wgpu/src/widget/canvas/stroke.rs b/wgpu/src/widget/canvas/stroke.rs new file mode 100644 index 00000000..46d669c4 --- /dev/null +++ b/wgpu/src/widget/canvas/stroke.rs @@ -0,0 +1,83 @@ +use iced_native::Color; + +/// The style of a stroke. +#[derive(Debug, Clone, Copy)] +pub struct Stroke { + /// The color of the stroke. + pub color: Color, + /// 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. + pub line_cap: LineCap, + /// The shape to be used at the corners of paths or basic shapes when they + /// are stroked. + pub line_join: LineJoin, +} + +impl Default for Stroke { + fn default() -> Stroke { + Stroke { + color: Color::BLACK, + width: 1.0, + line_cap: LineCap::default(), + line_join: LineJoin::default(), + } + } +} + +/// The shape used at the end of open subpaths when they are stroked. +#[derive(Debug, Clone, Copy)] +pub enum LineCap { + /// The stroke for each sub-path does not extend beyond its two endpoints. + Butt, + /// At the end of each sub-path, the shape representing the stroke will be + /// extended by a square. + Square, + /// At the end of each sub-path, the shape representing the stroke will be + /// extended by a semicircle. + Round, +} + +impl Default for LineCap { + fn default() -> LineCap { + LineCap::Butt + } +} + +impl From<LineCap> for lyon::tessellation::LineCap { + fn from(line_cap: LineCap) -> lyon::tessellation::LineCap { + match line_cap { + LineCap::Butt => lyon::tessellation::LineCap::Butt, + LineCap::Square => lyon::tessellation::LineCap::Square, + LineCap::Round => lyon::tessellation::LineCap::Round, + } + } +} + +/// The shape used at the corners of paths or basic shapes when they are +/// stroked. +#[derive(Debug, Clone, Copy)] +pub enum LineJoin { + /// A sharp corner. + Miter, + /// A round corner. + Round, + /// A bevelled corner. + Bevel, +} + +impl Default for LineJoin { + fn default() -> LineJoin { + LineJoin::Miter + } +} + +impl From<LineJoin> for lyon::tessellation::LineJoin { + fn from(line_join: LineJoin) -> lyon::tessellation::LineJoin { + match line_join { + LineJoin::Miter => lyon::tessellation::LineJoin::Miter, + LineJoin::Round => lyon::tessellation::LineJoin::Round, + LineJoin::Bevel => lyon::tessellation::LineJoin::Bevel, + } + } +} |