Write documentation for new `canvas` module

This commit is contained in:
Héctor Ramón Jiménez 2020-02-18 08:48:54 +01:00
parent 570f769744
commit 9c067562fa
17 changed files with 434 additions and 196 deletions

View File

@ -12,7 +12,7 @@ pub fn main() {
struct Clock { struct Clock {
now: LocalTime, now: LocalTime,
clock: canvas::layer::Cached<LocalTime>, clock: canvas::layer::Cache<LocalTime>,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -28,7 +28,7 @@ impl Application for Clock {
( (
Clock { Clock {
now: chrono::Local::now().into(), now: chrono::Local::now().into(),
clock: canvas::layer::Cached::new(), clock: canvas::layer::Cache::new(),
}, },
Command::none(), Command::none(),
) )
@ -91,7 +91,7 @@ impl From<chrono::DateTime<chrono::Local>> for LocalTime {
} }
} }
impl canvas::layer::Drawable for LocalTime { impl canvas::Drawable for LocalTime {
fn draw(&self, frame: &mut canvas::Frame) { fn draw(&self, frame: &mut canvas::Frame) {
let center = frame.center(); let center = frame.center();
let radius = frame.width().min(frame.height()) / 2.0; let radius = frame.width().min(frame.height()) / 2.0;

View File

@ -22,7 +22,7 @@ pub fn main() {
struct SolarSystem { struct SolarSystem {
state: State, state: State,
solar_system: canvas::layer::Cached<State>, solar_system: canvas::layer::Cache<State>,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -38,7 +38,7 @@ impl Application for SolarSystem {
( (
SolarSystem { SolarSystem {
state: State::new(), state: State::new(),
solar_system: canvas::layer::Cached::new(), solar_system: canvas::layer::Cache::new(),
}, },
Command::none(), 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) { fn draw(&self, frame: &mut canvas::Frame) {
use canvas::{Fill, Path, Stroke}; use canvas::{Fill, Path, Stroke};
use std::f32::consts::PI; use std::f32::consts::PI;

View File

@ -179,7 +179,7 @@ pub trait Application: Sized {
iced_wgpu::Settings { iced_wgpu::Settings {
default_font: _settings.default_font, default_font: _settings.default_font,
antialiasing: if _settings.use_antialiasing { antialiasing: if _settings.use_antialiasing {
Some(iced_wgpu::settings::MSAA::X4) Some(iced_wgpu::settings::Antialiasing::MSAAx4)
} else { } else {
None None
}, },

View File

@ -19,7 +19,7 @@
//! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs
//! [WebGPU API]: https://gpuweb.github.io/gpuweb/ //! [WebGPU API]: https://gpuweb.github.io/gpuweb/
//! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph //! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph
//#![deny(missing_docs)] #![deny(missing_docs)]
#![deny(missing_debug_implementations)] #![deny(missing_debug_implementations)]
#![deny(unused_results)] #![deny(unused_results)]
#![forbid(unsafe_code)] #![forbid(unsafe_code)]

View File

@ -1,6 +1,10 @@
//! Configure a [`Renderer`].
//!
//! [`Renderer`]: struct.Renderer.html
/// The settings of a [`Renderer`]. /// The settings of a [`Renderer`].
/// ///
/// [`Renderer`]: struct.Renderer.html /// [`Renderer`]: ../struct.Renderer.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Settings { pub struct Settings {
/// The bytes of the font that will be used by default. /// The bytes of the font that will be used by default.
@ -9,24 +13,29 @@ pub struct Settings {
pub default_font: Option<&'static [u8]>, pub default_font: Option<&'static [u8]>,
/// The antialiasing strategy that will be used for triangle primitives. /// The antialiasing strategy that will be used for triangle primitives.
pub antialiasing: Option<MSAA>, pub antialiasing: Option<Antialiasing>,
} }
/// An antialiasing strategy.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MSAA { pub enum Antialiasing {
X2, /// Multisample AA with 2 samples
X4, MSAAx2,
X8, /// Multisample AA with 4 samples
X16, MSAAx4,
/// Multisample AA with 8 samples
MSAAx8,
/// Multisample AA with 16 samples
MSAAx16,
} }
impl MSAA { impl Antialiasing {
pub(crate) fn sample_count(&self) -> u32 { pub(crate) fn sample_count(&self) -> u32 {
match self { match self {
MSAA::X2 => 2, Antialiasing::MSAAx2 => 2,
MSAA::X4 => 4, Antialiasing::MSAAx4 => 4,
MSAA::X8 => 8, Antialiasing::MSAAx8 => 8,
MSAA::X16 => 16, Antialiasing::MSAAx16 => 16,
} }
} }
} }

View File

@ -61,7 +61,7 @@ impl<T> Buffer<T> {
impl Pipeline { impl Pipeline {
pub fn new( pub fn new(
device: &mut wgpu::Device, device: &mut wgpu::Device,
antialiasing: Option<settings::MSAA>, antialiasing: Option<settings::Antialiasing>,
) -> Pipeline { ) -> Pipeline {
let constant_layout = let constant_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {

View File

@ -10,7 +10,10 @@ pub struct Blit {
} }
impl 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 { let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge,

View File

@ -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 crate::{Defaults, Primitive, Renderer};
use iced_native::{ use iced_native::{
@ -9,17 +16,26 @@ use std::hash::Hash;
pub mod layer; pub mod layer;
pub mod path; pub mod path;
mod drawable;
mod fill; mod fill;
mod frame; mod frame;
mod stroke; mod stroke;
pub use drawable::Drawable;
pub use fill::Fill; pub use fill::Fill;
pub use frame::Frame; pub use frame::Frame;
pub use layer::Layer; pub use layer::Layer;
pub use path::Path; pub use path::Path;
pub use stroke::{LineCap, LineJoin, Stroke}; 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)] #[derive(Debug)]
pub struct Canvas<'a> { pub struct Canvas<'a> {
width: Length, width: Length,
@ -30,6 +46,9 @@ pub struct Canvas<'a> {
impl<'a> Canvas<'a> { impl<'a> Canvas<'a> {
const DEFAULT_SIZE: u16 = 100; const DEFAULT_SIZE: u16 = 100;
/// Creates a new [`Canvas`] with no layers.
///
/// [`Canvas`]: struct.Canvas.html
pub fn new() -> Self { pub fn new() -> Self {
Canvas { Canvas {
width: Length::Units(Self::DEFAULT_SIZE), 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 { pub fn width(mut self, width: Length) -> Self {
self.width = width; self.width = width;
self self
} }
/// Sets the height of the [`Canvas`].
///
/// [`Canvas`]: struct.Canvas.html
pub fn height(mut self, height: Length) -> Self { pub fn height(mut self, height: Length) -> Self {
self.height = height; self.height = height;
self 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 { pub fn push(mut self, layer: impl Layer + 'a) -> Self {
self.layers.push(Box::new(layer)); self.layers.push(Box::new(layer));
self self

View File

@ -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);
}

View File

@ -1,7 +1,9 @@
use iced_native::Color; use iced_native::Color;
/// The style used to fill geometry.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Fill { pub enum Fill {
/// Fill with a color.
Color(Color), Color(Color),
} }

View File

@ -5,12 +5,14 @@ use crate::{
triangle, triangle,
}; };
/// The frame of a [`Canvas`].
///
/// [`Canvas`]: struct.Canvas.html
#[derive(Debug)] #[derive(Debug)]
pub struct Frame { pub struct Frame {
width: f32, width: f32,
height: f32, height: f32,
buffers: lyon::tessellation::VertexBuffers<triangle::Vertex2D, u32>, buffers: lyon::tessellation::VertexBuffers<triangle::Vertex2D, u32>,
transforms: Transforms, transforms: Transforms,
} }
@ -27,6 +29,12 @@ struct Transform {
} }
impl Frame { 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 { pub fn new(width: f32, height: f32) -> Frame {
Frame { Frame {
width, width,
@ -42,26 +50,43 @@ impl Frame {
} }
} }
/// Returns the width of the [`Frame`].
///
/// [`Frame`]: struct.Frame.html
#[inline] #[inline]
pub fn width(&self) -> f32 { pub fn width(&self) -> f32 {
self.width self.width
} }
/// Returns the width of the [`Frame`].
///
/// [`Frame`]: struct.Frame.html
#[inline] #[inline]
pub fn height(&self) -> f32 { pub fn height(&self) -> f32 {
self.height self.height
} }
/// Returns the dimensions of the [`Frame`].
///
/// [`Frame`]: struct.Frame.html
#[inline] #[inline]
pub fn size(&self) -> Size { pub fn size(&self) -> Size {
Size::new(self.width, self.height) Size::new(self.width, self.height)
} }
/// Returns the coordinate of the center of the [`Frame`].
///
/// [`Frame`]: struct.Frame.html
#[inline] #[inline]
pub fn center(&self) -> Point { pub fn center(&self) -> Point {
Point::new(self.width / 2.0, self.height / 2.0) 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) { pub fn fill(&mut self, path: &Path, fill: Fill) {
use lyon::tessellation::{ use lyon::tessellation::{
BuffersBuilder, FillOptions, FillTessellator, BuffersBuilder, FillOptions, FillTessellator,
@ -95,6 +120,11 @@ impl Frame {
let _ = result.expect("Tessellate path"); 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) { pub fn stroke(&mut self, path: &Path, stroke: Stroke) {
use lyon::tessellation::{ use lyon::tessellation::{
BuffersBuilder, StrokeOptions, StrokeTessellator, BuffersBuilder, StrokeOptions, StrokeTessellator,
@ -124,6 +154,13 @@ impl Frame {
let _ = result.expect("Stroke path"); 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] #[inline]
pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) { pub fn with_save(&mut self, f: impl FnOnce(&mut Frame)) {
self.transforms.previous.push(self.transforms.current); self.transforms.previous.push(self.transforms.current);
@ -133,6 +170,9 @@ impl Frame {
self.transforms.current = self.transforms.previous.pop().unwrap(); self.transforms.current = self.transforms.previous.pop().unwrap();
} }
/// Applies a translation to the current transform of the [`Frame`].
///
/// [`Frame`]: struct.Frame.html
#[inline] #[inline]
pub fn translate(&mut self, translation: Vector) { pub fn translate(&mut self, translation: Vector) {
self.transforms.current.raw = self self.transforms.current.raw = self
@ -146,6 +186,9 @@ impl Frame {
self.transforms.current.is_identity = false; self.transforms.current.is_identity = false;
} }
/// Applies a rotation to the current transform of the [`Frame`].
///
/// [`Frame`]: struct.Frame.html
#[inline] #[inline]
pub fn rotate(&mut self, angle: f32) { pub fn rotate(&mut self, angle: f32) {
self.transforms.current.raw = self self.transforms.current.raw = self
@ -156,6 +199,9 @@ impl Frame {
self.transforms.current.is_identity = false; self.transforms.current.is_identity = false;
} }
/// Applies a scaling to the current transform of the [`Frame`].
///
/// [`Frame`]: struct.Frame.html
#[inline] #[inline]
pub fn scale(&mut self, scale: f32) { pub fn scale(&mut self, scale: f32) {
self.transforms.current.raw = self.transforms.current.raw =
@ -163,6 +209,9 @@ impl Frame {
self.transforms.current.is_identity = false; 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 { pub fn into_mesh(self) -> triangle::Mesh2D {
triangle::Mesh2D { triangle::Mesh2D {
vertices: self.buffers.vertices, vertices: self.buffers.vertices,

View File

@ -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 iced_native::Size;
use std::sync::Arc; use std::sync::Arc;
/// A layer that can be presented at a [`Canvas`].
///
/// [`Canvas`]: ../struct.Canvas.html
pub trait Layer: std::fmt::Debug { 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>; fn draw(&self, bounds: Size) -> Arc<triangle::Mesh2D>;
} }
pub trait Drawable {
fn draw(&self, frame: &mut Frame);
}

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
canvas::{layer::Drawable, Frame, Layer}, canvas::{Drawable, Frame, Layer},
triangle, triangle,
}; };
@ -8,14 +8,21 @@ use std::cell::RefCell;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::Arc; 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)] #[derive(Debug)]
pub struct Cached<T: Drawable> { pub struct Cache<T: Drawable> {
input: PhantomData<T>, input: PhantomData<T>,
cache: RefCell<Cache>, state: RefCell<State>,
} }
#[derive(Debug)] #[derive(Debug)]
enum Cache { enum State {
Empty, Empty,
Filled { Filled {
mesh: Arc<triangle::Mesh2D>, mesh: Arc<triangle::Mesh2D>,
@ -23,24 +30,36 @@ enum Cache {
}, },
} }
impl<T> Cached<T> impl<T> Cache<T>
where where
T: Drawable + std::fmt::Debug, T: Drawable + std::fmt::Debug,
{ {
/// Creates a new empty [`Cache`].
///
/// [`Cache`]: struct.Cache.html
pub fn new() -> Self { pub fn new() -> Self {
Cached { Cache {
input: PhantomData, 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) { 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 { pub fn with<'a>(&'a self, input: &'a T) -> impl Layer + 'a {
Bind { Bind {
layer: self, cache: self,
input: input, input: input,
} }
} }
@ -48,7 +67,7 @@ where
#[derive(Debug)] #[derive(Debug)]
struct Bind<'a, T: Drawable> { struct Bind<'a, T: Drawable> {
layer: &'a Cached<T>, cache: &'a Cache<T>,
input: &'a T, input: &'a T,
} }
@ -59,8 +78,8 @@ where
fn draw(&self, current_bounds: Size) -> Arc<triangle::Mesh2D> { fn draw(&self, current_bounds: Size) -> Arc<triangle::Mesh2D> {
use std::ops::Deref; use std::ops::Deref;
if let Cache::Filled { mesh, bounds } = if let State::Filled { mesh, bounds } =
self.layer.cache.borrow().deref() self.cache.state.borrow().deref()
{ {
if *bounds == current_bounds { if *bounds == current_bounds {
return mesh.clone(); return mesh.clone();
@ -72,7 +91,7 @@ where
let mesh = Arc::new(frame.into_mesh()); let mesh = Arc::new(frame.into_mesh());
*self.layer.cache.borrow_mut() = Cache::Filled { *self.cache.state.borrow_mut() = State::Filled {
mesh: mesh.clone(), mesh: mesh.clone(),
bounds: current_bounds, bounds: current_bounds,
}; };

View File

@ -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)] #[derive(Debug, Clone)]
pub struct Path { pub struct Path {
raw: lyon::path::Path, raw: lyon::path::Path,
} }
impl 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 { pub fn new(f: impl FnOnce(&mut Builder)) -> Self {
let mut builder = Builder::new(); let mut builder = Builder::new();
@ -32,152 +47,3 @@ impl Path {
} }
} }
} }
#[allow(missing_debug_implementations)]
pub struct Builder {
raw: lyon::path::builder::SvgPathBuilder<lyon::path::Builder>,
}
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<Arc> 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,
}
}
}

View File

@ -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,
}
}
}

View File

@ -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(),
}
}
}

View File

@ -1,10 +1,16 @@
use iced_native::Color; use iced_native::Color;
/// The style of a stroke.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Stroke { pub struct Stroke {
/// The color of the stroke.
pub color: Color, pub color: Color,
/// The distance between the two edges of the stroke.
pub width: f32, pub width: f32,
/// The shape to be used at the end of open subpaths when they are stroked.
pub line_cap: LineCap, 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, 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)] #[derive(Debug, Clone, Copy)]
pub enum LineCap { pub enum LineCap {
/// The stroke for each sub-path does not extend beyond its two endpoints.
Butt, Butt,
/// At the end of each sub-path, the shape representing the stroke will be
/// extended by a square.
Square, Square,
/// At the end of each sub-path, the shape representing the stroke will be
/// extended by a semicircle.
Round, Round,
} }
@ -42,10 +54,15 @@ impl From<LineCap> for lyon::tessellation::LineCap {
} }
} }
/// The shape used at the corners of paths or basic shapes when they are
/// stroked.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum LineJoin { pub enum LineJoin {
/// A sharp corner.
Miter, Miter,
/// A round corner.
Round, Round,
/// A bevelled corner.
Bevel, Bevel,
} }