From 9c067562fa765cfc49d09cd9b12fbba96d5619fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 18 Feb 2020 08:48:54 +0100 Subject: [PATCH] Write documentation for new `canvas` module --- examples/clock/src/main.rs | 6 +- examples/solar_system/src/main.rs | 6 +- src/application.rs | 2 +- wgpu/src/lib.rs | 2 +- wgpu/src/settings.rs | 33 ++-- wgpu/src/triangle.rs | 2 +- wgpu/src/triangle/msaa.rs | 5 +- wgpu/src/widget/canvas.rs | 35 +++- wgpu/src/widget/canvas/drawable.rs | 12 ++ wgpu/src/widget/canvas/fill.rs | 2 + wgpu/src/widget/canvas/frame.rs | 51 ++++- wgpu/src/widget/canvas/layer.rs | 23 ++- .../canvas/layer/{cached.rs => cache.rs} | 45 +++-- wgpu/src/widget/canvas/path.rs | 168 ++--------------- wgpu/src/widget/canvas/path/arc.rs | 44 +++++ wgpu/src/widget/canvas/path/builder.rs | 177 ++++++++++++++++++ wgpu/src/widget/canvas/stroke.rs | 17 ++ 17 files changed, 434 insertions(+), 196 deletions(-) create mode 100644 wgpu/src/widget/canvas/drawable.rs rename wgpu/src/widget/canvas/layer/{cached.rs => cache.rs} (50%) create mode 100644 wgpu/src/widget/canvas/path/arc.rs create mode 100644 wgpu/src/widget/canvas/path/builder.rs diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index d0995e0c..559f0192 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -12,7 +12,7 @@ pub fn main() { struct Clock { now: LocalTime, - clock: canvas::layer::Cached, + clock: canvas::layer::Cache, } #[derive(Debug, Clone, Copy)] @@ -28,7 +28,7 @@ impl Application for Clock { ( Clock { now: chrono::Local::now().into(), - clock: canvas::layer::Cached::new(), + clock: canvas::layer::Cache::new(), }, Command::none(), ) @@ -91,7 +91,7 @@ impl From> for LocalTime { } } -impl canvas::layer::Drawable for LocalTime { +impl canvas::Drawable for LocalTime { fn draw(&self, frame: &mut canvas::Frame) { let center = frame.center(); let radius = frame.width().min(frame.height()) / 2.0; diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 3cabedbb..eee51dc2 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -22,7 +22,7 @@ pub fn main() { struct SolarSystem { state: State, - solar_system: canvas::layer::Cached, + solar_system: canvas::layer::Cache, } #[derive(Debug, Clone, Copy)] @@ -38,7 +38,7 @@ impl Application for SolarSystem { ( SolarSystem { state: State::new(), - solar_system: canvas::layer::Cached::new(), + solar_system: canvas::layer::Cache::new(), }, Command::none(), ) @@ -125,7 +125,7 @@ impl State { } } -impl canvas::layer::Drawable for State { +impl canvas::Drawable for State { fn draw(&self, frame: &mut canvas::Frame) { use canvas::{Fill, Path, Stroke}; use std::f32::consts::PI; diff --git a/src/application.rs b/src/application.rs index 8a88b55a..a569163b 100644 --- a/src/application.rs +++ b/src/application.rs @@ -179,7 +179,7 @@ pub trait Application: Sized { iced_wgpu::Settings { default_font: _settings.default_font, antialiasing: if _settings.use_antialiasing { - Some(iced_wgpu::settings::MSAA::X4) + Some(iced_wgpu::settings::Antialiasing::MSAAx4) } else { None }, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 90d28353..4f2b732d 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -19,7 +19,7 @@ //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs //! [WebGPU API]: https://gpuweb.github.io/gpuweb/ //! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph -//#![deny(missing_docs)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index c8a0cadf..65853ce2 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -1,6 +1,10 @@ +//! Configure a [`Renderer`]. +//! +//! [`Renderer`]: struct.Renderer.html + /// The settings of a [`Renderer`]. /// -/// [`Renderer`]: struct.Renderer.html +/// [`Renderer`]: ../struct.Renderer.html #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Settings { /// The bytes of the font that will be used by default. @@ -9,24 +13,29 @@ pub struct Settings { pub default_font: Option<&'static [u8]>, /// The antialiasing strategy that will be used for triangle primitives. - pub antialiasing: Option, + pub antialiasing: Option, } +/// An antialiasing strategy. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum MSAA { - X2, - X4, - X8, - X16, +pub enum Antialiasing { + /// Multisample AA with 2 samples + MSAAx2, + /// Multisample AA with 4 samples + MSAAx4, + /// Multisample AA with 8 samples + MSAAx8, + /// Multisample AA with 16 samples + MSAAx16, } -impl MSAA { +impl Antialiasing { pub(crate) fn sample_count(&self) -> u32 { match self { - MSAA::X2 => 2, - MSAA::X4 => 4, - MSAA::X8 => 8, - MSAA::X16 => 16, + Antialiasing::MSAAx2 => 2, + Antialiasing::MSAAx4 => 4, + Antialiasing::MSAAx8 => 8, + Antialiasing::MSAAx16 => 16, } } } diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 9159b0a2..d149eebc 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -61,7 +61,7 @@ impl Buffer { impl Pipeline { pub fn new( device: &mut wgpu::Device, - antialiasing: Option, + antialiasing: Option, ) -> Pipeline { let constant_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index 93fbe49b..0def5352 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -10,7 +10,10 @@ pub struct Blit { } impl Blit { - pub fn new(device: &wgpu::Device, antialiasing: settings::MSAA) -> Blit { + pub fn new( + device: &wgpu::Device, + antialiasing: settings::Antialiasing, + ) -> Blit { let sampler = device.create_sampler(&wgpu::SamplerDescriptor { address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index e8fdc1e8..38c1ce62 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -1,4 +1,11 @@ -//! Draw freely in 2D. +//! 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::{ @@ -9,17 +16,26 @@ 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 2D drawable region. +/// 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, @@ -30,6 +46,9 @@ pub struct Canvas<'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), @@ -38,16 +57,28 @@ impl<'a> Canvas<'a> { } } + /// 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 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 index 9c23f997..5ce24cf3 100644 --- a/wgpu/src/widget/canvas/fill.rs +++ b/wgpu/src/widget/canvas/fill.rs @@ -1,7 +1,9 @@ use iced_native::Color; +/// The style used to fill geometry. #[derive(Debug, Clone, Copy)] pub enum Fill { + /// Fill with a color. Color(Color), } diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index 27d676d6..fa6d8c0a 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -5,12 +5,14 @@ use crate::{ triangle, }; +/// The frame of a [`Canvas`]. +/// +/// [`Canvas`]: struct.Canvas.html #[derive(Debug)] pub struct Frame { width: f32, height: f32, buffers: lyon::tessellation::VertexBuffers, - transforms: Transforms, } @@ -27,6 +29,12 @@ struct Transform { } 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, @@ -42,26 +50,43 @@ impl Frame { } } + /// 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, @@ -95,6 +120,11 @@ impl Frame { 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, @@ -124,6 +154,13 @@ impl Frame { 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); @@ -133,6 +170,9 @@ impl Frame { 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 @@ -146,6 +186,9 @@ impl Frame { 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 @@ -156,6 +199,9 @@ impl Frame { 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 = @@ -163,6 +209,9 @@ impl Frame { 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, diff --git a/wgpu/src/widget/canvas/layer.rs b/wgpu/src/widget/canvas/layer.rs index 8c069f18..82d647bb 100644 --- a/wgpu/src/widget/canvas/layer.rs +++ b/wgpu/src/widget/canvas/layer.rs @@ -1,16 +1,25 @@ -mod cached; +//! Produce, store, and reuse geometry. +mod cache; -pub use cached::Cached; +pub use cache::Cache; -use crate::{canvas::Frame, triangle}; +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; } - -pub trait Drawable { - fn draw(&self, frame: &mut Frame); -} diff --git a/wgpu/src/widget/canvas/layer/cached.rs b/wgpu/src/widget/canvas/layer/cache.rs similarity index 50% rename from wgpu/src/widget/canvas/layer/cached.rs rename to wgpu/src/widget/canvas/layer/cache.rs index c6741372..3071cce0 100644 --- a/wgpu/src/widget/canvas/layer/cached.rs +++ b/wgpu/src/widget/canvas/layer/cache.rs @@ -1,5 +1,5 @@ use crate::{ - canvas::{layer::Drawable, Frame, Layer}, + canvas::{Drawable, Frame, Layer}, triangle, }; @@ -8,14 +8,21 @@ 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 Cached { +pub struct Cache { input: PhantomData, - cache: RefCell, + state: RefCell, } #[derive(Debug)] -enum Cache { +enum State { Empty, Filled { mesh: Arc, @@ -23,24 +30,36 @@ enum Cache { }, } -impl Cached +impl Cache where T: Drawable + std::fmt::Debug, { + /// Creates a new empty [`Cache`]. + /// + /// [`Cache`]: struct.Cache.html pub fn new() -> Self { - Cached { + Cache { input: PhantomData, - cache: RefCell::new(Cache::Empty), + 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.cache.borrow_mut() = Cache::Empty; + *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 { - layer: self, + cache: self, input: input, } } @@ -48,7 +67,7 @@ where #[derive(Debug)] struct Bind<'a, T: Drawable> { - layer: &'a Cached, + cache: &'a Cache, input: &'a T, } @@ -59,8 +78,8 @@ where fn draw(&self, current_bounds: Size) -> Arc { use std::ops::Deref; - if let Cache::Filled { mesh, bounds } = - self.layer.cache.borrow().deref() + if let State::Filled { mesh, bounds } = + self.cache.state.borrow().deref() { if *bounds == current_bounds { return mesh.clone(); @@ -72,7 +91,7 @@ where let mesh = Arc::new(frame.into_mesh()); - *self.layer.cache.borrow_mut() = Cache::Filled { + *self.cache.state.borrow_mut() = State::Filled { mesh: mesh.clone(), bounds: current_bounds, }; diff --git a/wgpu/src/widget/canvas/path.rs b/wgpu/src/widget/canvas/path.rs index b70d0aef..15c2e853 100644 --- a/wgpu/src/widget/canvas/path.rs +++ b/wgpu/src/widget/canvas/path.rs @@ -1,13 +1,28 @@ -use iced_native::{Point, Size, Vector}; +//! Build different kinds of 2D shapes. +pub mod arc; -use lyon::path::builder::{Build, FlatPathBuilder, PathBuilder, SvgBuilder}; +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(); @@ -32,152 +47,3 @@ impl Path { } } } - -#[allow(missing_debug_implementations)] -pub struct Builder { - raw: lyon::path::builder::SvgPathBuilder, -} - -impl Builder { - pub fn new() -> Builder { - Builder { - raw: lyon::path::Path::builder().with_svg(), - } - } - - #[inline] - pub fn move_to(&mut self, point: Point) { - let _ = self.raw.move_to(lyon::math::Point::new(point.x, point.y)); - } - - #[inline] - pub fn line_to(&mut self, point: Point) { - let _ = self.raw.line_to(lyon::math::Point::new(point.x, point.y)); - } - - #[inline] - pub fn arc(&mut self, arc: Arc) { - self.ellipse(arc.into()); - } - - 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), - ); - } - - pub fn ellipse(&mut self, ellipse: Ellipse) { - use lyon::{geom, math}; - - let arc = geom::Arc { - center: math::Point::new(ellipse.center.x, ellipse.center.y), - radii: math::Vector::new(ellipse.radii.x, ellipse.radii.y), - x_rotation: math::Angle::radians(ellipse.rotation), - start_angle: math::Angle::radians(ellipse.start_angle), - sweep_angle: math::Angle::radians(ellipse.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); - }); - } - - #[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), - ); - } - - #[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), - ); - } - - #[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(); - } - - #[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, - }); - } - - #[inline] - pub fn close(&mut self) { - self.raw.close() - } - - #[inline] - pub fn build(self) -> Path { - Path { - raw: self.raw.build(), - } - } -} - -#[derive(Debug, Clone, Copy)] -pub struct Arc { - pub center: Point, - pub radius: f32, - pub start_angle: f32, - pub end_angle: f32, -} - -#[derive(Debug, Clone, Copy)] -pub struct Ellipse { - pub center: Point, - pub radii: Vector, - pub rotation: f32, - pub start_angle: f32, - pub end_angle: f32, -} - -impl From for Ellipse { - fn from(arc: Arc) -> Ellipse { - Ellipse { - 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/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 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, +} + +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 index 9bb260b2..46d669c4 100644 --- a/wgpu/src/widget/canvas/stroke.rs +++ b/wgpu/src/widget/canvas/stroke.rs @@ -1,10 +1,16 @@ 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, } @@ -19,10 +25,16 @@ impl Default for Stroke { } } +/// 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, } @@ -42,10 +54,15 @@ impl From for lyon::tessellation::LineCap { } } +/// 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, }