diff options
Diffstat (limited to 'wgpu')
-rw-r--r-- | wgpu/Cargo.toml | 16 | ||||
-rw-r--r-- | wgpu/src/lib.rs | 2 | ||||
-rw-r--r-- | wgpu/src/primitive.rs | 10 | ||||
-rw-r--r-- | wgpu/src/renderer.rs | 12 | ||||
-rw-r--r-- | wgpu/src/shader/triangle.vert | 4 | ||||
-rw-r--r-- | wgpu/src/shader/triangle.vert.spv | bin | 1468 -> 1256 bytes | |||
-rw-r--r-- | wgpu/src/triangle.rs | 84 | ||||
-rw-r--r-- | wgpu/src/widget.rs | 7 | ||||
-rw-r--r-- | wgpu/src/widget/canvas.rs | 118 | ||||
-rw-r--r-- | wgpu/src/widget/canvas/fill.rs | 12 | ||||
-rw-r--r-- | wgpu/src/widget/canvas/frame.rs | 206 | ||||
-rw-r--r-- | wgpu/src/widget/canvas/layer.rs | 16 | ||||
-rw-r--r-- | wgpu/src/widget/canvas/layer/cached.rs | 82 | ||||
-rw-r--r-- | wgpu/src/widget/canvas/path.rs | 183 | ||||
-rw-r--r-- | wgpu/src/widget/canvas/stroke.rs | 66 |
15 files changed, 761 insertions, 57 deletions
diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 60b98b40..887c2d21 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -9,6 +9,7 @@ repository = "https://github.com/hecrj/iced" [features] svg = ["resvg"] +canvas = ["lyon"] [dependencies] iced_native = { version = "0.1.0", path = "../native" } @@ -20,5 +21,16 @@ raw-window-handle = "0.3" glam = "0.8" font-kit = "0.4" log = "0.4" -resvg = { version = "0.8", features = ["raqote-backend"], optional = true } -image = { version = "0.22", optional = true } + +[dependencies.image] +version = "0.22" +optional = true + +[dependencies.resvg] +version = "0.8" +features = ["raqote-backend"] +optional = true + +[dependencies.lyon] +version = "0.15" +optional = true diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index e4834818..d38e2a31 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/primitive.rs b/wgpu/src/primitive.rs index 481252ef..823b4b72 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,5 +1,5 @@ use iced_native::{ - image, svg, Background, Color, Font, HorizontalAlignment, Rectangle, + image, svg, Background, Color, Font, HorizontalAlignment, Point, Rectangle, Vector, VerticalAlignment, }; @@ -73,7 +73,13 @@ pub enum Primitive { /// A low-level primitive to render a mesh of triangles. /// /// It can be used to render many kinds of geometry freely. - Mesh2D(Arc<triangle::Mesh2D>), + Mesh2D { + /// The top-left coordinate of the mesh + origin: Point, + + /// The vertex and index buffers of the mesh + buffers: Arc<triangle::Mesh2D>, + }, } impl Default for Primitive { diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index e93090b8..25b2e99a 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -26,7 +26,7 @@ struct Layer<'a> { offset: Vector<u32>, quads: Vec<Quad>, images: Vec<Image>, - meshes: Vec<Arc<triangle::Mesh2D>>, + meshes: Vec<(Point, Arc<triangle::Mesh2D>)>, text: Vec<wgpu_glyph::Section<'a>>, } @@ -229,8 +229,8 @@ impl Renderer { scale: [bounds.width, bounds.height], }); } - Primitive::Mesh2D(mesh) => { - layer.meshes.push(mesh.clone()); + Primitive::Mesh2D { origin, buffers } => { + layer.meshes.push((*origin, buffers.clone())); } Primitive::Clip { bounds, @@ -313,9 +313,10 @@ impl Renderer { if layer.meshes.len() > 0 { let translated = transformation + * Transformation::scale(scale_factor, scale_factor) * Transformation::translate( - -(layer.offset.x as f32) * scale_factor, - -(layer.offset.y as f32) * scale_factor, + -(layer.offset.x as f32), + -(layer.offset.y as f32), ); self.triangle_pipeline.draw( @@ -323,7 +324,6 @@ impl Renderer { encoder, target, translated, - scale_factor, &layer.meshes, bounds, ); diff --git a/wgpu/src/shader/triangle.vert b/wgpu/src/shader/triangle.vert index fd86ecd6..1f2c009b 100644 --- a/wgpu/src/shader/triangle.vert +++ b/wgpu/src/shader/triangle.vert @@ -7,11 +7,9 @@ layout(location = 0) out vec4 o_Color; layout (set = 0, binding = 0) uniform Globals { mat4 u_Transform; - float u_Scale; }; void main() { - vec2 p_Position = i_Position * u_Scale; - gl_Position = u_Transform * vec4(p_Position, 0.0, 1.0); + gl_Position = u_Transform * vec4(i_Position, 0.0, 1.0); o_Color = i_Color; } diff --git a/wgpu/src/shader/triangle.vert.spv b/wgpu/src/shader/triangle.vert.spv Binary files differindex bc39c451..871f4f55 100644 --- a/wgpu/src/shader/triangle.vert.spv +++ b/wgpu/src/shader/triangle.vert.spv diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 38157d00..3cc1d3fb 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -1,6 +1,6 @@ //! Draw meshes of triangles. use crate::Transformation; -use iced_native::Rectangle; +use iced_native::{Point, Rectangle}; use std::{mem, sync::Arc}; #[derive(Debug)] @@ -91,7 +91,7 @@ impl Pipeline { write_mask: wgpu::ColorWrite::ALL, }], depth_stencil_state: None, - index_format: wgpu::IndexFormat::Uint16, + index_format: wgpu::IndexFormat::Uint32, vertex_buffers: &[wgpu::VertexBufferDescriptor { stride: mem::size_of::<Vertex2D>() as u64, step_mode: wgpu::InputStepMode::Vertex, @@ -128,47 +128,28 @@ impl Pipeline { encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, transformation: Transformation, - scale: f32, - meshes: &Vec<Arc<Mesh2D>>, + meshes: &Vec<(Point, Arc<Mesh2D>)>, bounds: Rectangle<u32>, ) { - let uniforms = Uniforms { - transform: transformation.into(), - scale, - }; - - let constants_buffer = device - .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) - .fill_from_slice(&[uniforms]); - - encoder.copy_buffer_to_buffer( - &constants_buffer, - 0, - &self.constants_buffer, - 0, - std::mem::size_of::<Uniforms>() as u64, - ); - - let mut render_pass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - color_attachments: &[ - wgpu::RenderPassColorAttachmentDescriptor { - attachment: target, - resolve_target: None, - load_op: wgpu::LoadOp::Load, - store_op: wgpu::StoreOp::Store, - clear_color: wgpu::Color { - r: 0.0, - g: 0.0, - b: 0.0, - a: 0.0, - }, - }, - ], - depth_stencil_attachment: None, - }); + for (origin, mesh) in meshes { + let uniforms = Uniforms { + transform: (transformation + * Transformation::translate(origin.x, origin.y)) + .into(), + }; + + let constants_buffer = device + .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&[uniforms]); + + encoder.copy_buffer_to_buffer( + &constants_buffer, + 0, + &self.constants_buffer, + 0, + std::mem::size_of::<Uniforms>() as u64, + ); - for mesh in meshes { let vertices_buffer = device .create_buffer_mapped( mesh.vertices.len(), @@ -183,6 +164,25 @@ impl Pipeline { ) .fill_from_slice(&mesh.indices); + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[ + wgpu::RenderPassColorAttachmentDescriptor { + attachment: target, + resolve_target: None, + load_op: wgpu::LoadOp::Load, + store_op: wgpu::StoreOp::Store, + clear_color: wgpu::Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.0, + }, + }, + ], + depth_stencil_attachment: None, + }); + render_pass.set_pipeline(&self.pipeline); render_pass.set_bind_group(0, &self.constants, &[]); render_pass.set_index_buffer(&indices_buffer, 0); @@ -203,14 +203,12 @@ impl Pipeline { #[derive(Debug, Clone, Copy)] struct Uniforms { transform: [f32; 16], - scale: f32, } impl Default for Uniforms { fn default() -> Self { Self { transform: *Transformation::identity().as_ref(), - scale: 1.0, } } } @@ -235,5 +233,5 @@ pub struct Mesh2D { /// The list of vertex indices that defines the triangles of the mesh. /// /// Therefore, this list should always have a length that is a multiple of 3. - pub indices: Vec<u16>, + pub indices: Vec<u32>, } diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index e3edda0b..73cce7e2 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -32,3 +32,10 @@ pub use scrollable::Scrollable; pub use slider::Slider; #[doc(no_inline)] pub use text_input::TextInput; + +#[cfg(feature = "canvas")] +pub mod canvas; + +#[cfg(feature = "canvas")] +#[doc(no_inline)] +pub use canvas::Canvas; diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs new file mode 100644 index 00000000..e8fdc1e8 --- /dev/null +++ b/wgpu/src/widget/canvas.rs @@ -0,0 +1,118 @@ +//! Draw freely in 2D. +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 fill; +mod frame; +mod stroke; + +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. +#[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; + + pub fn new() -> Self { + Canvas { + width: Length::Units(Self::DEFAULT_SIZE), + height: Length::Units(Self::DEFAULT_SIZE), + layers: Vec::new(), + } + } + + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + 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/fill.rs b/wgpu/src/widget/canvas/fill.rs new file mode 100644 index 00000000..9c23f997 --- /dev/null +++ b/wgpu/src/widget/canvas/fill.rs @@ -0,0 +1,12 @@ +use iced_native::Color; + +#[derive(Debug, Clone, Copy)] +pub enum Fill { + 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..27d676d6 --- /dev/null +++ b/wgpu/src/widget/canvas/frame.rs @@ -0,0 +1,206 @@ +use iced_native::{Point, Size, Vector}; + +use crate::{ + canvas::{Fill, Path, Stroke}, + triangle, +}; + +#[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 { + 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, + }, + }, + } + } + + #[inline] + pub fn width(&self) -> f32 { + self.width + } + + #[inline] + pub fn height(&self) -> f32 { + self.height + } + + #[inline] + pub fn size(&self) -> Size { + Size::new(self.width, self.height) + } + + #[inline] + pub fn center(&self) -> Point { + Point::new(self.width / 2.0, self.height / 2.0) + } + + 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"); + } + + 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"); + } + + #[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(); + } + + #[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; + } + + #[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; + } + + #[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; + } + + 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..8c069f18 --- /dev/null +++ b/wgpu/src/widget/canvas/layer.rs @@ -0,0 +1,16 @@ +mod cached; + +pub use cached::Cached; + +use crate::{canvas::Frame, triangle}; + +use iced_native::Size; +use std::sync::Arc; + +pub trait Layer: std::fmt::Debug { + fn draw(&self, bounds: Size) -> Arc<triangle::Mesh2D>; +} + +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/cached.rs new file mode 100644 index 00000000..c6741372 --- /dev/null +++ b/wgpu/src/widget/canvas/layer/cached.rs @@ -0,0 +1,82 @@ +use crate::{ + canvas::{layer::Drawable, Frame, Layer}, + triangle, +}; + +use iced_native::Size; +use std::cell::RefCell; +use std::marker::PhantomData; +use std::sync::Arc; + +#[derive(Debug)] +pub struct Cached<T: Drawable> { + input: PhantomData<T>, + cache: RefCell<Cache>, +} + +#[derive(Debug)] +enum Cache { + Empty, + Filled { + mesh: Arc<triangle::Mesh2D>, + bounds: Size, + }, +} + +impl<T> Cached<T> +where + T: Drawable + std::fmt::Debug, +{ + pub fn new() -> Self { + Cached { + input: PhantomData, + cache: RefCell::new(Cache::Empty), + } + } + + pub fn clear(&mut self) { + *self.cache.borrow_mut() = Cache::Empty; + } + + pub fn with<'a>(&'a self, input: &'a T) -> impl Layer + 'a { + Bind { + layer: self, + input: input, + } + } +} + +#[derive(Debug)] +struct Bind<'a, T: Drawable> { + layer: &'a Cached<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 Cache::Filled { mesh, bounds } = + self.layer.cache.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.layer.cache.borrow_mut() = Cache::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..b70d0aef --- /dev/null +++ b/wgpu/src/widget/canvas/path.rs @@ -0,0 +1,183 @@ +use iced_native::{Point, Size, Vector}; + +use lyon::path::builder::{Build, FlatPathBuilder, PathBuilder, SvgBuilder}; + +#[derive(Debug, Clone)] +pub struct Path { + raw: lyon::path::Path, +} + +impl Path { + 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), + } + } +} + +#[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, + } + } +} diff --git a/wgpu/src/widget/canvas/stroke.rs b/wgpu/src/widget/canvas/stroke.rs new file mode 100644 index 00000000..9bb260b2 --- /dev/null +++ b/wgpu/src/widget/canvas/stroke.rs @@ -0,0 +1,66 @@ +use iced_native::Color; + +#[derive(Debug, Clone, Copy)] +pub struct Stroke { + pub color: Color, + pub width: f32, + pub line_cap: LineCap, + 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(), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum LineCap { + Butt, + Square, + 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, + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum LineJoin { + Miter, + Round, + 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, + } + } +} |