From 8daf798e5760a0d35d5491027d51a5dd96898b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 11 Feb 2020 23:14:25 +0100 Subject: [PATCH 01/27] Add `canvas` feature to `iced_wgpu` And prepare `canvas` module --- wgpu/Cargo.toml | 16 ++++++++++++++-- wgpu/src/lib.rs | 2 +- wgpu/src/widget.rs | 7 +++++++ wgpu/src/widget/canvas.rs | 5 +++++ 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 wgpu/src/widget/canvas.rs 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/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..ebb84135 --- /dev/null +++ b/wgpu/src/widget/canvas.rs @@ -0,0 +1,5 @@ +//! Draw freely in 2D. + +/// A 2D drawable region. +#[derive(Debug)] +pub struct Canvas; From f436f20eb86b2324126a54d4164b4cedf2134a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Feb 2020 03:47:36 +0100 Subject: [PATCH 02/27] Draft `Canvas` types and `clock` example --- Cargo.toml | 3 + examples/clock/Cargo.toml | 13 +++ examples/clock/src/main.rs | 145 ++++++++++++++++++++++++++++++ src/lib.rs | 2 +- wgpu/src/widget/canvas.rs | 155 +++++++++++++++++++++++++++++++- wgpu/src/widget/canvas/data.rs | 20 +++++ wgpu/src/widget/canvas/frame.rs | 39 ++++++++ wgpu/src/widget/canvas/layer.rs | 41 +++++++++ wgpu/src/widget/canvas/path.rs | 78 ++++++++++++++++ 9 files changed, 494 insertions(+), 2 deletions(-) create mode 100644 examples/clock/Cargo.toml create mode 100644 examples/clock/src/main.rs create mode 100644 wgpu/src/widget/canvas/data.rs create mode 100644 wgpu/src/widget/canvas/frame.rs create mode 100644 wgpu/src/widget/canvas/layer.rs create mode 100644 wgpu/src/widget/canvas/path.rs diff --git a/Cargo.toml b/Cargo.toml index 11cca8b3..dd099c32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ categories = ["gui"] image = ["iced_wgpu/image"] # Enables the `Svg` widget svg = ["iced_wgpu/svg"] +# Enables the `Canvas` widget +canvas = ["iced_wgpu/canvas"] # Enables a debug view in native platforms (press F12) debug = ["iced_winit/debug"] # Enables `tokio` as the `executor::Default` on native platforms @@ -36,6 +38,7 @@ members = [ "wgpu", "winit", "examples/bezier_tool", + "examples/clock", "examples/counter", "examples/custom_widget", "examples/events", diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml new file mode 100644 index 00000000..941e2bd0 --- /dev/null +++ b/examples/clock/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "clock" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[features] +canvas = [] + +[dependencies] +iced = { path = "../..", features = ["canvas", "async-std"] } +chrono = "0.4" diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs new file mode 100644 index 00000000..958846f4 --- /dev/null +++ b/examples/clock/src/main.rs @@ -0,0 +1,145 @@ +use iced::{ + canvas, executor, Application, Canvas, Color, Command, Element, Length, + Point, Settings, +}; + +use std::sync::Arc; + +pub fn main() { + Clock::run(Settings::default()) +} + +struct Clock { + local_time: Arc, + clock: canvas::layer::Cached, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + Tick(chrono::DateTime), +} + +impl Application for Clock { + type Executor = executor::Default; + type Message = Message; + + fn new() -> (Self, Command) { + let now: LocalTime = chrono::Local::now().into(); + + ( + Clock { + local_time: Arc::new(now), + clock: canvas::layer::Cached::new(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Clock - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Tick(local_time) => {} + } + + Command::none() + } + + fn view(&mut self) -> Element { + Canvas::new() + .width(Length::Fill) + .height(Length::Fill) + .push(self.clock.with(&self.local_time)) + .into() + } +} + +#[derive(Debug)] +struct LocalTime { + hour: u32, + minute: u32, + second: u32, +} + +impl From> for LocalTime { + fn from(date_time: chrono::DateTime) -> LocalTime { + use chrono::Timelike; + + LocalTime { + hour: date_time.hour(), + minute: date_time.minute(), + second: date_time.second(), + } + } +} + +impl canvas::layer::Drawable for LocalTime { + fn draw(&self, frame: &mut canvas::Frame) { + let center = frame.center(); + let radius = frame.width().min(frame.height()) as f32 / 2.0; + + let mut path = canvas::Path::new(); + + path.arc(canvas::path::Arc { + center, + radius, + start_angle: 0.0, + end_angle: 360.0 * 2.0 * std::f32::consts::PI, + }); + + frame.fill( + path, + canvas::Fill::Color(Color::from_rgb8(0x12, 0x93, 0xD8)), + ); + + fn draw_handle( + n: u32, + total: u32, + length: f32, + path: &mut canvas::Path, + ) { + let turns = n as f32 / total as f32; + let t = 2.0 * std::f32::consts::PI * (turns - 0.25); + + let x = length * t.cos(); + let y = length * t.sin(); + + path.line_to(Point::new(x, y)); + } + + let mut path = canvas::Path::new(); + + path.move_to(center); + draw_handle(self.hour, 12, 0.6 * radius, &mut path); + + path.move_to(center); + draw_handle(self.minute, 60, 0.9 * radius, &mut path); + + frame.stroke( + path, + canvas::Stroke { + width: 4.0, + color: Color::WHITE, + line_cap: canvas::LineCap::Round, + ..canvas::Stroke::default() + }, + ); + + let mut path = canvas::Path::new(); + + path.move_to(center); + draw_handle(self.second, 60, 0.9 * radius, &mut path); + + frame.stroke( + path, + canvas::Stroke { + width: 2.0, + color: Color::WHITE, + line_cap: canvas::LineCap::Round, + ..canvas::Stroke::default() + }, + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 7e658ca2..59870692 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -204,5 +204,5 @@ use iced_web as common; pub use common::{ futures, Align, Background, Color, Command, Font, HorizontalAlignment, - Length, Space, Subscription, Vector, VerticalAlignment, + Length, Point, Space, Subscription, Vector, VerticalAlignment, }; diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index ebb84135..7b84f36c 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -1,5 +1,158 @@ //! Draw freely in 2D. +use crate::{Defaults, Primitive, Renderer}; + +use iced_native::{ + layout, Color, Element, Hasher, Layout, Length, MouseCursor, Point, Size, + Widget, +}; +use std::hash::Hash; + +pub mod layer; +pub mod path; + +mod data; +mod frame; + +pub use data::Data; +pub use frame::Frame; +pub use layer::Layer; +pub use path::Path; /// A 2D drawable region. #[derive(Debug)] -pub struct Canvas; +pub struct Canvas<'a> { + width: Length, + height: Length, + layers: Vec>, +} + +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 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) { + (Primitive::None, MouseCursor::Idle) + } + + fn hash_layout(&self, state: &mut Hasher) { + std::any::TypeId::of::>().hash(state); + + self.width.hash(state); + self.height.hash(state); + } +} + +impl<'a, Message> From> for Element<'a, Message, Renderer> +where + Message: 'static, +{ + fn from(canvas: Canvas<'a>) -> Element<'a, Message, Renderer> { + Element::new(canvas) + } +} + +#[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 + } +} + +#[derive(Debug, Clone, Copy)] +pub enum LineJoin { + Miter, + Round, + Bevel, +} + +impl Default for LineJoin { + fn default() -> LineJoin { + LineJoin::Miter + } +} + +#[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/data.rs b/wgpu/src/widget/canvas/data.rs new file mode 100644 index 00000000..25d94f4c --- /dev/null +++ b/wgpu/src/widget/canvas/data.rs @@ -0,0 +1,20 @@ +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub struct Data { + raw: T, + version: usize, +} + +impl Data { + pub fn new(data: T) -> Self { + Data { + raw: data, + version: 0, + } + } + + pub fn update(&mut self, f: impl FnOnce(&mut T)) { + f(&mut self.raw); + + self.version += 1; + } +} diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs new file mode 100644 index 00000000..65c22c0c --- /dev/null +++ b/wgpu/src/widget/canvas/frame.rs @@ -0,0 +1,39 @@ +use iced_native::Point; + +use crate::{ + canvas::{Fill, Path, Stroke}, + triangle, +}; + +#[derive(Debug)] +pub struct Frame { + width: u32, + height: u32, + buffers: lyon::tessellation::VertexBuffers, +} + +impl Frame { + pub(crate) fn new(width: u32, height: u32) -> Frame { + Frame { + width, + height, + buffers: lyon::tessellation::VertexBuffers::new(), + } + } + + pub fn width(&self) -> u32 { + self.width + } + + pub fn height(&self) -> u32 { + self.height + } + + pub fn center(&self) -> Point { + Point::new(self.width as f32 / 2.0, self.height as f32 / 2.0) + } + + pub fn fill(&mut self, path: Path, fill: Fill) {} + + pub fn stroke(&mut self, path: Path, stroke: Stroke) {} +} diff --git a/wgpu/src/widget/canvas/layer.rs b/wgpu/src/widget/canvas/layer.rs new file mode 100644 index 00000000..f97634e4 --- /dev/null +++ b/wgpu/src/widget/canvas/layer.rs @@ -0,0 +1,41 @@ +use crate::canvas::Frame; + +pub trait Layer: std::fmt::Debug {} + +use std::marker::PhantomData; +use std::sync::{Arc, Weak}; + +#[derive(Debug)] +pub struct Cached { + input: PhantomData, +} + +impl Cached +where + T: Drawable + std::fmt::Debug, +{ + pub fn new() -> Self { + Cached { input: PhantomData } + } + + pub fn clear(&mut self) {} + + 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 Cached, + input: &'a T, +} + +impl<'a, T> Layer for Bind<'a, T> where T: Drawable + std::fmt::Debug {} + +pub trait Drawable { + fn draw(&self, frame: &mut Frame); +} diff --git a/wgpu/src/widget/canvas/path.rs b/wgpu/src/widget/canvas/path.rs new file mode 100644 index 00000000..2732b1bd --- /dev/null +++ b/wgpu/src/widget/canvas/path.rs @@ -0,0 +1,78 @@ +use iced_native::{Point, Vector}; + +#[allow(missing_debug_implementations)] +pub struct Path { + raw: lyon::path::Builder, +} + +impl Path { + pub fn new() -> Path { + Path { + raw: lyon::path::Path::builder(), + } + } + + #[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()) + } + + #[inline] + pub fn ellipse(&mut self, ellipse: Ellipse) { + let arc = lyon::geom::Arc { + center: lyon::math::Point::new(ellipse.center.x, ellipse.center.y), + radii: lyon::math::Vector::new(ellipse.radii.x, ellipse.radii.y), + x_rotation: lyon::math::Angle::radians(ellipse.rotation), + start_angle: lyon::math::Angle::radians(ellipse.start_angle), + sweep_angle: lyon::math::Angle::radians(ellipse.end_angle), + }; + + arc.for_each_quadratic_bezier(&mut |curve| { + let _ = self.raw.quadratic_bezier_to(curve.ctrl, curve.to); + }); + } + + #[inline] + pub fn close(&mut self) { + self.raw.close() + } +} + +#[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, + } + } +} From 4777f5d787e262aa9093a3099ae62be2b8c0c82f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Feb 2020 04:00:13 +0100 Subject: [PATCH 03/27] Remove unnecessary `Arc` from `clock` example --- examples/clock/src/main.rs | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 958846f4..dc2c06cf 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -3,14 +3,12 @@ use iced::{ Point, Settings, }; -use std::sync::Arc; - pub fn main() { Clock::run(Settings::default()) } struct Clock { - local_time: Arc, + now: LocalTime, clock: canvas::layer::Cached, } @@ -28,7 +26,7 @@ impl Application for Clock { ( Clock { - local_time: Arc::new(now), + now, clock: canvas::layer::Cached::new(), }, Command::none(), @@ -41,7 +39,15 @@ impl Application for Clock { fn update(&mut self, message: Message) -> Command { match message { - Message::Tick(local_time) => {} + Message::Tick(local_time) => { + let now = local_time.into(); + + if now != self.now { + self.now = now; + + self.clock.clear(); + } + } } Command::none() @@ -51,12 +57,12 @@ impl Application for Clock { Canvas::new() .width(Length::Fill) .height(Length::Fill) - .push(self.clock.with(&self.local_time)) + .push(self.clock.with(&self.now)) .into() } } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] struct LocalTime { hour: u32, minute: u32, From 64097983f195ac1e923b6a1bd8cb0fc2c109fabc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Feb 2020 04:03:24 +0100 Subject: [PATCH 04/27] Expose `Point` in `iced_web` --- web/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/lib.rs b/web/src/lib.rs index 7b54a07a..4dc7aba7 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -73,7 +73,7 @@ pub use dodrio; pub use element::Element; pub use hasher::Hasher; pub use iced_core::{ - Align, Background, Color, Font, HorizontalAlignment, Length, Vector, + Align, Background, Color, Font, HorizontalAlignment, Length, Point, Vector, VerticalAlignment, }; pub use iced_futures::{executor, futures, Command}; From 74dd79e97f83d3e9e13d87444740edeb353f9be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Feb 2020 06:41:24 +0100 Subject: [PATCH 05/27] Rename current `Path` to `path::Builder` --- examples/clock/src/main.rs | 42 ++++++++++++++++----------------- wgpu/src/widget/canvas/frame.rs | 4 ++-- wgpu/src/widget/canvas/path.rs | 32 +++++++++++++++++++++---- 3 files changed, 50 insertions(+), 28 deletions(-) diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index dc2c06cf..76752d20 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -86,17 +86,17 @@ impl canvas::layer::Drawable for LocalTime { let center = frame.center(); let radius = frame.width().min(frame.height()) as f32 / 2.0; - let mut path = canvas::Path::new(); - - path.arc(canvas::path::Arc { - center, - radius, - start_angle: 0.0, - end_angle: 360.0 * 2.0 * std::f32::consts::PI, + let path = canvas::Path::new(|path| { + path.arc(canvas::path::Arc { + center, + radius, + start_angle: 0.0, + end_angle: 360.0 * 2.0 * std::f32::consts::PI, + }) }); frame.fill( - path, + &path, canvas::Fill::Color(Color::from_rgb8(0x12, 0x93, 0xD8)), ); @@ -104,7 +104,7 @@ impl canvas::layer::Drawable for LocalTime { n: u32, total: u32, length: f32, - path: &mut canvas::Path, + path: &mut canvas::path::Builder, ) { let turns = n as f32 / total as f32; let t = 2.0 * std::f32::consts::PI * (turns - 0.25); @@ -115,16 +115,16 @@ impl canvas::layer::Drawable for LocalTime { path.line_to(Point::new(x, y)); } - let mut path = canvas::Path::new(); + let path = canvas::Path::new(|path| { + path.move_to(center); + draw_handle(self.hour, 12, 0.6 * radius, path); - path.move_to(center); - draw_handle(self.hour, 12, 0.6 * radius, &mut path); - - path.move_to(center); - draw_handle(self.minute, 60, 0.9 * radius, &mut path); + path.move_to(center); + draw_handle(self.minute, 60, 0.9 * radius, path) + }); frame.stroke( - path, + &path, canvas::Stroke { width: 4.0, color: Color::WHITE, @@ -133,13 +133,13 @@ impl canvas::layer::Drawable for LocalTime { }, ); - let mut path = canvas::Path::new(); - - path.move_to(center); - draw_handle(self.second, 60, 0.9 * radius, &mut path); + let path = canvas::Path::new(|path| { + path.move_to(center); + draw_handle(self.second, 60, 0.9 * radius, path) + }); frame.stroke( - path, + &path, canvas::Stroke { width: 2.0, color: Color::WHITE, diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index 65c22c0c..e5daeedb 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -33,7 +33,7 @@ impl Frame { Point::new(self.width as f32 / 2.0, self.height as f32 / 2.0) } - pub fn fill(&mut self, path: Path, fill: Fill) {} + pub fn fill(&mut self, path: &Path, fill: Fill) {} - pub fn stroke(&mut self, path: Path, stroke: Stroke) {} + pub fn stroke(&mut self, path: &Path, stroke: Stroke) {} } diff --git a/wgpu/src/widget/canvas/path.rs b/wgpu/src/widget/canvas/path.rs index 2732b1bd..86326e8e 100644 --- a/wgpu/src/widget/canvas/path.rs +++ b/wgpu/src/widget/canvas/path.rs @@ -1,13 +1,28 @@ use iced_native::{Point, Vector}; -#[allow(missing_debug_implementations)] +#[derive(Debug, Clone)] pub struct Path { - raw: lyon::path::Builder, + raw: lyon::path::Path, } impl Path { - pub fn new() -> Path { - Path { + pub fn new(f: impl FnOnce(&mut Builder)) -> Self { + let mut builder = Builder::new(); + + f(&mut builder); + + builder.build() + } +} + +#[allow(missing_debug_implementations)] +pub struct Builder { + raw: lyon::path::Builder, +} + +impl Builder { + pub fn new() -> Builder { + Builder { raw: lyon::path::Path::builder(), } } @@ -24,7 +39,7 @@ impl Path { #[inline] pub fn arc(&mut self, arc: Arc) { - self.ellipse(arc.into()) + self.ellipse(arc.into()); } #[inline] @@ -46,6 +61,13 @@ impl Path { pub fn close(&mut self) { self.raw.close() } + + #[inline] + pub fn build(self) -> Path { + Path { + raw: self.raw.build(), + } + } } #[derive(Debug, Clone, Copy)] From f34407bfdaf06c4bf204dc31b152be9451c243b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Feb 2020 07:08:49 +0100 Subject: [PATCH 06/27] Implement `Frame::fill` and `Frame::stroke` --- wgpu/src/widget/canvas.rs | 20 +++++++++ wgpu/src/widget/canvas/frame.rs | 76 ++++++++++++++++++++++++++++++++- wgpu/src/widget/canvas/path.rs | 6 +++ 3 files changed, 100 insertions(+), 2 deletions(-) diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index 7b84f36c..6bfeed9a 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -133,6 +133,16 @@ impl Default for LineCap { } } +impl From 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, @@ -146,6 +156,16 @@ impl Default for LineJoin { } } +impl From 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, + } + } +} + #[derive(Debug, Clone, Copy)] pub enum Fill { Color(Color), diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index e5daeedb..82ff526b 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -33,7 +33,79 @@ impl Frame { Point::new(self.width as f32 / 2.0, self.height as f32 / 2.0) } - pub fn fill(&mut self, path: &Path, fill: Fill) {} + pub fn fill(&mut self, path: &Path, fill: Fill) { + use lyon::tessellation::{ + BuffersBuilder, FillOptions, FillTessellator, + }; - pub fn stroke(&mut self, path: &Path, stroke: Stroke) {} + let mut buffers = BuffersBuilder::new( + &mut self.buffers, + FillVertex(match fill { + Fill::Color(color) => color.into_linear(), + }), + ); + + let mut tessellator = FillTessellator::new(); + + let _ = tessellator + .tessellate_path(path.raw(), &FillOptions::default(), &mut buffers) + .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 _ = tessellator + .tessellate_path(path.raw(), &options, &mut buffers) + .expect("Stroke path"); + } +} + +struct FillVertex([f32; 4]); + +impl lyon::tessellation::FillVertexConstructor + 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 + 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/path.rs b/wgpu/src/widget/canvas/path.rs index 86326e8e..96206256 100644 --- a/wgpu/src/widget/canvas/path.rs +++ b/wgpu/src/widget/canvas/path.rs @@ -9,10 +9,16 @@ 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 + } } #[allow(missing_debug_implementations)] From 578ea4abb8a2dd0d53d7087322796bf9ad541b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Feb 2020 08:49:42 +0100 Subject: [PATCH 07/27] Finish `clock` example --- examples/clock/Cargo.toml | 4 +- examples/clock/src/main.rs | 65 +++++++++++++++++++++---- wgpu/src/primitive.rs | 10 +++- wgpu/src/renderer.rs | 12 ++--- wgpu/src/shader/triangle.vert | 4 +- wgpu/src/shader/triangle.vert.spv | Bin 1468 -> 1256 bytes wgpu/src/triangle.rs | 76 +++++++++++++++--------------- wgpu/src/widget/canvas.rs | 21 +++++++-- wgpu/src/widget/canvas/frame.rs | 19 +++++--- wgpu/src/widget/canvas/layer.rs | 63 +++++++++++++++++++++---- wgpu/src/widget/canvas/path.rs | 2 + 11 files changed, 200 insertions(+), 76 deletions(-) diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml index 941e2bd0..308cbfbb 100644 --- a/examples/clock/Cargo.toml +++ b/examples/clock/Cargo.toml @@ -9,5 +9,7 @@ publish = false canvas = [] [dependencies] -iced = { path = "../..", features = ["canvas", "async-std"] } +iced = { path = "../..", features = ["canvas", "async-std", "debug"] } +iced_native = { path = "../../native" } chrono = "0.4" +async-std = { version = "1.0", features = ["unstable"] } diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 76752d20..1b8d1ee6 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - canvas, executor, Application, Canvas, Color, Command, Element, Length, - Point, Settings, + canvas, executor, Application, Canvas, Color, Command, Container, Element, + Length, Point, Settings, Subscription, Vector, }; pub fn main() { @@ -53,11 +53,21 @@ impl Application for Clock { Command::none() } + fn subscription(&self) -> Subscription { + time::every(std::time::Duration::from_millis(500)).map(Message::Tick) + } + fn view(&mut self) -> Element { - Canvas::new() + let canvas = Canvas::new() + .width(Length::Units(400)) + .height(Length::Units(400)) + .push(self.clock.with(&self.now)); + + Container::new(canvas) .width(Length::Fill) .height(Length::Fill) - .push(self.clock.with(&self.now)) + .center_x() + .center_y() .into() } } @@ -85,6 +95,7 @@ impl canvas::layer::Drawable for LocalTime { fn draw(&self, frame: &mut canvas::Frame) { let center = frame.center(); let radius = frame.width().min(frame.height()) as f32 / 2.0; + let offset = Vector::new(center.x, center.y); let path = canvas::Path::new(|path| { path.arc(canvas::path::Arc { @@ -104,6 +115,7 @@ impl canvas::layer::Drawable for LocalTime { n: u32, total: u32, length: f32, + offset: Vector, path: &mut canvas::path::Builder, ) { let turns = n as f32 / total as f32; @@ -112,15 +124,15 @@ impl canvas::layer::Drawable for LocalTime { let x = length * t.cos(); let y = length * t.sin(); - path.line_to(Point::new(x, y)); + path.line_to(Point::new(x, y) + offset); } let path = canvas::Path::new(|path| { path.move_to(center); - draw_handle(self.hour, 12, 0.6 * radius, path); + draw_handle(self.hour, 12, 0.5 * radius, offset, path); path.move_to(center); - draw_handle(self.minute, 60, 0.9 * radius, path) + draw_handle(self.minute, 60, 0.8 * radius, offset, path) }); frame.stroke( @@ -135,7 +147,7 @@ impl canvas::layer::Drawable for LocalTime { let path = canvas::Path::new(|path| { path.move_to(center); - draw_handle(self.second, 60, 0.9 * radius, path) + draw_handle(self.second, 60, 0.8 * radius, offset, path) }); frame.stroke( @@ -149,3 +161,40 @@ impl canvas::layer::Drawable for LocalTime { ); } } + +mod time { + use iced::futures; + + pub fn every( + duration: std::time::Duration, + ) -> iced::Subscription> { + iced::Subscription::from_recipe(Every(duration)) + } + + struct Every(std::time::Duration); + + impl iced_native::subscription::Recipe for Every + where + H: std::hash::Hasher, + { + type Output = chrono::DateTime; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box, + _input: futures::stream::BoxStream<'static, I>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + async_std::stream::interval(self.0) + .map(|_| chrono::Local::now()) + .boxed() + } + } +} 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), + Mesh2D { + /// The top-left coordinate of the mesh + origin: Point, + + /// The vertex and index buffers of the mesh + buffers: Arc, + }, } 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, quads: Vec, images: Vec, - meshes: Vec>, + meshes: Vec<(Point, Arc)>, text: Vec>, } @@ -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 index bc39c451e34ba68c71f821afee0a1d4e140c4274..871f4f5520592ea4f48c239b871aafe46d1c01ef 100644 GIT binary patch literal 1256 zcmYk4%We}v5Jek1b^?Js2zda(*l~abQp5rf5(0#RyGLf2sx#ycP5vbT=qyDKR{T+>>+250X>{= z4|65lt6;>=+_{A9eHlBi*f}=nYTq7bsM~mpsEzMp+nlEkvGZ*5Ucz@V*W()dx7lwO zd<}2CT!+Z{Qx9`mm%8itbXPBH?Dv-QoRwHt_4OR_D(2b&dpGt~SBG8Rk(|A6;+;j# z-eT`f&it)9w%!@H@oG-_9b)H^cfZ(sl~aqgcQIGOyAl0*WB(qpbEwb#;(pDU-zTTu6P%-PI&2jYL6_yYS6q!LXn literal 1468 zcmY+E%We}v5Jj8#kw732LLNYH?3j0mVF3sU@lueSMHWFuY%LQE8foH*{E!eUz5t0| z;;Yyoan5+!kdA7q@2%>p>f2UIbIth>&V>1}5FUoX$h{>ush6L<4fzKpM9_S`A(N`al{3VU;{XZ{Ahj=9ViU&TDX3BHCmSFXlR?c&VFH0y5S z)yTMWiT$m~#W|wR=5N6}hj%k>k0$1d_-2m2zcT+Gdu!R-`eNTs&iDhoGsn1ix8R=g zd$E_Ax;*n6#^15Ed^7(S_(m_l`tz9Y^oA@oTgB8_EzNP>HB9~oIi6X^JSYE>F4mN@ zhWD*w)=&p)c=rZYFR(SXG0(l@j6H4@9B#(lE;#RdNN4YS2Q$~0I;!1IY8bnV*~c7b dQM>z?@8c}aDmKr#Jk!F|&zL%k|Df@N(rTT}o5 diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 38157d00..6f3adbe4 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)] @@ -128,47 +128,28 @@ impl Pipeline { encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, transformation: Transformation, - scale: f32, - meshes: &Vec>, + meshes: &Vec<(Point, Arc)>, bounds: Rectangle, ) { - let uniforms = Uniforms { - transform: transformation.into(), - scale, - }; + 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]); + 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::() as u64, - ); + encoder.copy_buffer_to_buffer( + &constants_buffer, + 0, + &self.constants_buffer, + 0, + std::mem::size_of::() 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 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, } } } diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index 6bfeed9a..c984fee9 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -68,7 +68,6 @@ impl<'a, Message> Widget for Canvas<'a> { limits: &layout::Limits, ) -> layout::Node { let limits = limits.width(self.width).height(self.height); - let size = limits.resolve(Size::ZERO); layout::Node::new(size) @@ -78,10 +77,26 @@ impl<'a, Message> Widget for Canvas<'a> { &self, _renderer: &mut Renderer, _defaults: &Defaults, - _layout: Layout<'_>, + layout: Layout<'_>, _cursor_position: Point, ) -> (Primitive, MouseCursor) { - (Primitive::None, MouseCursor::Idle) + 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) { diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index 82ff526b..3c667426 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -7,13 +7,13 @@ use crate::{ #[derive(Debug)] pub struct Frame { - width: u32, - height: u32, + width: f32, + height: f32, buffers: lyon::tessellation::VertexBuffers, } impl Frame { - pub(crate) fn new(width: u32, height: u32) -> Frame { + pub fn new(width: f32, height: f32) -> Frame { Frame { width, height, @@ -21,16 +21,16 @@ impl Frame { } } - pub fn width(&self) -> u32 { + pub fn width(&self) -> f32 { self.width } - pub fn height(&self) -> u32 { + pub fn height(&self) -> f32 { self.height } pub fn center(&self) -> Point { - Point::new(self.width as f32 / 2.0, self.height as f32 / 2.0) + Point::new(self.width / 2.0, self.height / 2.0) } pub fn fill(&mut self, path: &Path, fill: Fill) { @@ -74,6 +74,13 @@ impl Frame { .tessellate_path(path.raw(), &options, &mut buffers) .expect("Stroke path"); } + + pub fn into_mesh(self) -> triangle::Mesh2D { + triangle::Mesh2D { + vertices: self.buffers.vertices, + indices: self.buffers.indices, + } + } } struct FillVertex([f32; 4]); diff --git a/wgpu/src/widget/canvas/layer.rs b/wgpu/src/widget/canvas/layer.rs index f97634e4..c239a254 100644 --- a/wgpu/src/widget/canvas/layer.rs +++ b/wgpu/src/widget/canvas/layer.rs @@ -1,13 +1,28 @@ -use crate::canvas::Frame; +use crate::{canvas::Frame, triangle}; -pub trait Layer: std::fmt::Debug {} +use iced_native::Size; +use std::cell::RefCell; +use std::sync::Arc; + +pub trait Layer: std::fmt::Debug { + fn draw(&self, bounds: Size) -> Arc; +} use std::marker::PhantomData; -use std::sync::{Arc, Weak}; #[derive(Debug)] pub struct Cached { input: PhantomData, + cache: RefCell, +} + +#[derive(Debug)] +enum Cache { + Empty, + Filled { + mesh: Arc, + bounds: Size, + }, } impl Cached @@ -15,14 +30,19 @@ where T: Drawable + std::fmt::Debug, { pub fn new() -> Self { - Cached { input: PhantomData } + Cached { + input: PhantomData, + cache: RefCell::new(Cache::Empty), + } } - pub fn clear(&mut self) {} + pub fn clear(&mut self) { + *self.cache.borrow_mut() = Cache::Empty; + } pub fn with<'a>(&'a self, input: &'a T) -> impl Layer + 'a { Bind { - cache: self, + layer: self, input: input, } } @@ -30,11 +50,38 @@ where #[derive(Debug)] struct Bind<'a, T: Drawable> { - cache: &'a Cached, + layer: &'a Cached, input: &'a T, } -impl<'a, T> Layer for Bind<'a, T> where T: Drawable + std::fmt::Debug {} +impl<'a, T> Layer for Bind<'a, T> +where + T: Drawable + std::fmt::Debug, +{ + fn draw(&self, current_bounds: Size) -> Arc { + 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 + } +} pub trait Drawable { fn draw(&self, frame: &mut Frame); diff --git a/wgpu/src/widget/canvas/path.rs b/wgpu/src/widget/canvas/path.rs index 96206256..c8ba10e1 100644 --- a/wgpu/src/widget/canvas/path.rs +++ b/wgpu/src/widget/canvas/path.rs @@ -58,6 +58,8 @@ impl Builder { sweep_angle: lyon::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); }); From 96b36d0f9e4beba2ac9cbe9c873adf1b89d31f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Feb 2020 09:00:44 +0100 Subject: [PATCH 08/27] Increase line width in `clock` example --- examples/clock/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 1b8d1ee6..e70ce91a 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -54,7 +54,7 @@ impl Application for Clock { } fn subscription(&self) -> Subscription { - time::every(std::time::Duration::from_millis(500)).map(Message::Tick) + time::every(std::time::Duration::from_millis(1000)).map(Message::Tick) } fn view(&mut self) -> Element { @@ -138,7 +138,7 @@ impl canvas::layer::Drawable for LocalTime { frame.stroke( &path, canvas::Stroke { - width: 4.0, + width: 6.0, color: Color::WHITE, line_cap: canvas::LineCap::Round, ..canvas::Stroke::default() @@ -153,7 +153,7 @@ impl canvas::layer::Drawable for LocalTime { frame.stroke( &path, canvas::Stroke { - width: 2.0, + width: 3.0, color: Color::WHITE, line_cap: canvas::LineCap::Round, ..canvas::Stroke::default() From 1beeaf9db5e4d9bcf9c1fce89463ad47c708b07d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Feb 2020 09:12:15 +0100 Subject: [PATCH 09/27] Fix examples using `Mesh2D` --- examples/bezier_tool/src/main.rs | 52 ++++++------- examples/geometry/src/main.rs | 123 ++++++++++++++++--------------- 2 files changed, 84 insertions(+), 91 deletions(-) diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 043d265c..fbb6fa24 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -102,7 +102,7 @@ mod bezier { // Draw rectangle border with lyon. basic_shapes::stroke_rectangle( &lyon::math::Rect::new( - lyon::math::Point::new(bounds.x + 0.5, bounds.y + 0.5), + lyon::math::Point::new(0.5, 0.5), lyon::math::Size::new( bounds.width - 1.0, bounds.height - 1.0, @@ -121,48 +121,35 @@ mod bezier { for curve in self.curves { path_builder.move_to(lyon::math::Point::new( - curve.from.x + bounds.x, - curve.from.y + bounds.y, + curve.from.x, + curve.from.y, )); path_builder.quadratic_bezier_to( - lyon::math::Point::new( - curve.control.x + bounds.x, - curve.control.y + bounds.y, - ), - lyon::math::Point::new( - curve.to.x + bounds.x, - curve.to.y + bounds.y, - ), + lyon::math::Point::new(curve.control.x, curve.control.y), + lyon::math::Point::new(curve.to.x, curve.to.y), ); } match self.state.pending { None => {} Some(Pending::One { from }) => { - path_builder.move_to(lyon::math::Point::new( - from.x + bounds.x, - from.y + bounds.y, - )); + path_builder + .move_to(lyon::math::Point::new(from.x, from.y)); path_builder.line_to(lyon::math::Point::new( - cursor_position.x, - cursor_position.y, + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, )); } Some(Pending::Two { from, to }) => { - path_builder.move_to(lyon::math::Point::new( - from.x + bounds.x, - from.y + bounds.y, - )); + path_builder + .move_to(lyon::math::Point::new(from.x, from.y)); path_builder.quadratic_bezier_to( lyon::math::Point::new( - cursor_position.x, - cursor_position.y, - ), - lyon::math::Point::new( - to.x + bounds.x, - to.y + bounds.y, + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, ), + lyon::math::Point::new(to.x, to.y), ); } } @@ -186,10 +173,13 @@ mod bezier { ) .unwrap(); - let mesh = Primitive::Mesh2D(Arc::new(Mesh2D { - vertices: buffer.vertices, - indices: buffer.indices, - })); + let mesh = Primitive::Mesh2D { + origin: Point::new(bounds.x, bounds.y), + buffers: Arc::new(Mesh2D { + vertices: buffer.vertices, + indices: buffer.indices, + }), + }; ( Primitive::Clip { diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 9d5fd611..795c6a71 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -69,72 +69,75 @@ mod rainbow { let posn_center = { if b.contains(cursor_position) { - [cursor_position.x, cursor_position.y] + [cursor_position.x - b.x, cursor_position.y - b.y] } else { - [b.x + (b.width / 2.0), b.y + (b.height / 2.0)] + [b.width / 2.0, b.height / 2.0] } }; - let posn_tl = [b.x, b.y]; - let posn_t = [b.x + (b.width / 2.0), b.y]; - let posn_tr = [b.x + b.width, b.y]; - let posn_r = [b.x + b.width, b.y + (b.height / 2.0)]; - let posn_br = [b.x + b.width, b.y + b.height]; - let posn_b = [b.x + (b.width / 2.0), b.y + b.height]; - let posn_bl = [b.x, b.y + b.height]; - let posn_l = [b.x, b.y + (b.height / 2.0)]; + let posn_tl = [0.0, 0.0]; + let posn_t = [b.width / 2.0, 0.0]; + let posn_tr = [b.width, 0.0]; + let posn_r = [b.width, b.height / 2.0]; + let posn_br = [b.width, b.height]; + let posn_b = [(b.width / 2.0), b.height]; + let posn_bl = [0.0, b.height]; + let posn_l = [0.0, b.height / 2.0]; ( - Primitive::Mesh2D(std::sync::Arc::new(Mesh2D { - vertices: vec![ - Vertex2D { - position: posn_center, - color: [1.0, 1.0, 1.0, 1.0], - }, - Vertex2D { - position: posn_tl, - color: color_r, - }, - Vertex2D { - position: posn_t, - color: color_o, - }, - Vertex2D { - position: posn_tr, - color: color_y, - }, - Vertex2D { - position: posn_r, - color: color_g, - }, - Vertex2D { - position: posn_br, - color: color_gb, - }, - Vertex2D { - position: posn_b, - color: color_b, - }, - Vertex2D { - position: posn_bl, - color: color_i, - }, - Vertex2D { - position: posn_l, - color: color_v, - }, - ], - indices: vec![ - 0, 1, 2, // TL - 0, 2, 3, // T - 0, 3, 4, // TR - 0, 4, 5, // R - 0, 5, 6, // BR - 0, 6, 7, // B - 0, 7, 8, // BL - 0, 8, 1, // L - ], - })), + Primitive::Mesh2D { + origin: Point::new(b.x, b.y), + buffers: std::sync::Arc::new(Mesh2D { + vertices: vec![ + Vertex2D { + position: posn_center, + color: [1.0, 1.0, 1.0, 1.0], + }, + Vertex2D { + position: posn_tl, + color: color_r, + }, + Vertex2D { + position: posn_t, + color: color_o, + }, + Vertex2D { + position: posn_tr, + color: color_y, + }, + Vertex2D { + position: posn_r, + color: color_g, + }, + Vertex2D { + position: posn_br, + color: color_gb, + }, + Vertex2D { + position: posn_b, + color: color_b, + }, + Vertex2D { + position: posn_bl, + color: color_i, + }, + Vertex2D { + position: posn_l, + color: color_v, + }, + ], + indices: vec![ + 0, 1, 2, // TL + 0, 2, 3, // T + 0, 3, 4, // TR + 0, 4, 5, // R + 0, 5, 6, // BR + 0, 6, 7, // B + 0, 7, 8, // BL + 0, 8, 1, // L + ], + }), + }, MouseCursor::OutOfBounds, ) } From de8f06b512ec65f625417e334dca30990248c968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Feb 2020 09:12:35 +0100 Subject: [PATCH 10/27] Split `Fill` and `Stroke` into their own modules --- wgpu/src/widget/canvas.rs | 83 ++------------------------------ wgpu/src/widget/canvas/fill.rs | 12 +++++ wgpu/src/widget/canvas/stroke.rs | 66 +++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 78 deletions(-) create mode 100644 wgpu/src/widget/canvas/fill.rs create mode 100644 wgpu/src/widget/canvas/stroke.rs diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index c984fee9..1fc3ff01 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -2,8 +2,7 @@ use crate::{Defaults, Primitive, Renderer}; use iced_native::{ - layout, Color, Element, Hasher, Layout, Length, MouseCursor, Point, Size, - Widget, + layout, Element, Hasher, Layout, Length, MouseCursor, Point, Size, Widget, }; use std::hash::Hash; @@ -11,12 +10,16 @@ pub mod layer; pub mod path; mod data; +mod fill; mod frame; +mod stroke; pub use data::Data; +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)] @@ -115,79 +118,3 @@ where Element::new(canvas) } } - -#[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 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 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, - } - } -} - -#[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/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/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 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 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, + } + } +} From 4a24392c9c078c56ea26b49c455c9ef183fa79a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Feb 2020 09:21:13 +0100 Subject: [PATCH 11/27] Simplify `Clock::new` in `clock` example --- examples/clock/src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index e70ce91a..e30158f7 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -22,11 +22,9 @@ impl Application for Clock { type Message = Message; fn new() -> (Self, Command) { - let now: LocalTime = chrono::Local::now().into(); - ( Clock { - now, + now: chrono::Local::now().into(), clock: canvas::layer::Cached::new(), }, Command::none(), From 629153582f1a43516092d941d2b4e2372a6ea054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Feb 2020 09:24:22 +0100 Subject: [PATCH 12/27] Remove `canvas::Data` leftover --- wgpu/src/widget/canvas.rs | 2 -- wgpu/src/widget/canvas/data.rs | 20 -------------------- 2 files changed, 22 deletions(-) delete mode 100644 wgpu/src/widget/canvas/data.rs diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index 1fc3ff01..e8fdc1e8 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -9,12 +9,10 @@ use std::hash::Hash; pub mod layer; pub mod path; -mod data; mod fill; mod frame; mod stroke; -pub use data::Data; pub use fill::Fill; pub use frame::Frame; pub use layer::Layer; diff --git a/wgpu/src/widget/canvas/data.rs b/wgpu/src/widget/canvas/data.rs deleted file mode 100644 index 25d94f4c..00000000 --- a/wgpu/src/widget/canvas/data.rs +++ /dev/null @@ -1,20 +0,0 @@ -#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] -pub struct Data { - raw: T, - version: usize, -} - -impl Data { - pub fn new(data: T) -> Self { - Data { - raw: data, - version: 0, - } - } - - pub fn update(&mut self, f: impl FnOnce(&mut T)) { - f(&mut self.raw); - - self.version += 1; - } -} From 265c08661ca7943473c2ef305246a75bfc7847ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Feb 2020 22:08:49 +0100 Subject: [PATCH 13/27] Fix circle `end_angle` in `clock` example --- examples/clock/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index e30158f7..25849d1a 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -100,7 +100,7 @@ impl canvas::layer::Drawable for LocalTime { center, radius, start_angle: 0.0, - end_angle: 360.0 * 2.0 * std::f32::consts::PI, + end_angle: 2.0 * std::f32::consts::PI, }) }); From 9dc9305d93b9c03d5ea309ecce148857f9bb0745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Feb 2020 22:10:00 +0100 Subject: [PATCH 14/27] Remove redundant conversion in `clock` example --- examples/clock/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 25849d1a..8259e382 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -92,7 +92,7 @@ impl From> for LocalTime { impl canvas::layer::Drawable for LocalTime { fn draw(&self, frame: &mut canvas::Frame) { let center = frame.center(); - let radius = frame.width().min(frame.height()) as f32 / 2.0; + let radius = frame.width().min(frame.height()) / 2.0; let offset = Vector::new(center.x, center.y); let path = canvas::Path::new(|path| { From e7c400a0aaa01320e80aeed726e7ba702af9380b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Feb 2020 22:13:59 +0100 Subject: [PATCH 15/27] Improve naming in `clock` example --- examples/clock/src/main.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 8259e382..160adffd 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -42,7 +42,6 @@ impl Application for Clock { if now != self.now { self.now = now; - self.clock.clear(); } } @@ -95,7 +94,7 @@ impl canvas::layer::Drawable for LocalTime { let radius = frame.width().min(frame.height()) / 2.0; let offset = Vector::new(center.x, center.y); - let path = canvas::Path::new(|path| { + let clock = canvas::Path::new(|path| { path.arc(canvas::path::Arc { center, radius, @@ -105,11 +104,11 @@ impl canvas::layer::Drawable for LocalTime { }); frame.fill( - &path, + &clock, canvas::Fill::Color(Color::from_rgb8(0x12, 0x93, 0xD8)), ); - fn draw_handle( + fn draw_hand( n: u32, total: u32, length: f32, @@ -125,16 +124,16 @@ impl canvas::layer::Drawable for LocalTime { path.line_to(Point::new(x, y) + offset); } - let path = canvas::Path::new(|path| { + let hour_and_minute_hands = canvas::Path::new(|path| { path.move_to(center); - draw_handle(self.hour, 12, 0.5 * radius, offset, path); + draw_hand(self.hour, 12, 0.5 * radius, offset, path); path.move_to(center); - draw_handle(self.minute, 60, 0.8 * radius, offset, path) + draw_hand(self.minute, 60, 0.8 * radius, offset, path) }); frame.stroke( - &path, + &hour_and_minute_hands, canvas::Stroke { width: 6.0, color: Color::WHITE, @@ -143,13 +142,13 @@ impl canvas::layer::Drawable for LocalTime { }, ); - let path = canvas::Path::new(|path| { + let second_hand = canvas::Path::new(|path| { path.move_to(center); - draw_handle(self.second, 60, 0.8 * radius, offset, path) + draw_hand(self.second, 60, 0.8 * radius, offset, path) }); frame.stroke( - &path, + &second_hand, canvas::Stroke { width: 3.0, color: Color::WHITE, From 979edeb213c2ca0dd394a9a6c68a30dfab371263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 12 Feb 2020 22:38:36 +0100 Subject: [PATCH 16/27] Fix `clock` example eventually skipping a second --- examples/clock/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 160adffd..25a213cd 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -51,7 +51,7 @@ impl Application for Clock { } fn subscription(&self) -> Subscription { - time::every(std::time::Duration::from_millis(1000)).map(Message::Tick) + time::every(std::time::Duration::from_millis(500)).map(Message::Tick) } fn view(&mut self) -> Element { From df90c478e28892537efc0009d468a521d4d7fee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 13 Feb 2020 03:45:07 +0100 Subject: [PATCH 17/27] Move `layer::Cached` to its own module --- wgpu/src/widget/canvas/layer.rs | 80 ++----------------------- wgpu/src/widget/canvas/layer/cached.rs | 82 ++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 76 deletions(-) create mode 100644 wgpu/src/widget/canvas/layer/cached.rs diff --git a/wgpu/src/widget/canvas/layer.rs b/wgpu/src/widget/canvas/layer.rs index c239a254..8c069f18 100644 --- a/wgpu/src/widget/canvas/layer.rs +++ b/wgpu/src/widget/canvas/layer.rs @@ -1,88 +1,16 @@ +mod cached; + +pub use cached::Cached; + use crate::{canvas::Frame, triangle}; use iced_native::Size; -use std::cell::RefCell; use std::sync::Arc; pub trait Layer: std::fmt::Debug { fn draw(&self, bounds: Size) -> Arc; } -use std::marker::PhantomData; - -#[derive(Debug)] -pub struct Cached { - input: PhantomData, - cache: RefCell, -} - -#[derive(Debug)] -enum Cache { - Empty, - Filled { - mesh: Arc, - bounds: Size, - }, -} - -impl Cached -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, - input: &'a T, -} - -impl<'a, T> Layer for Bind<'a, T> -where - T: Drawable + std::fmt::Debug, -{ - fn draw(&self, current_bounds: Size) -> Arc { - 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 - } -} - 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 { + input: PhantomData, + cache: RefCell, +} + +#[derive(Debug)] +enum Cache { + Empty, + Filled { + mesh: Arc, + bounds: Size, + }, +} + +impl Cached +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, + input: &'a T, +} + +impl<'a, T> Layer for Bind<'a, T> +where + T: Drawable + std::fmt::Debug, +{ + fn draw(&self, current_bounds: Size) -> Arc { + 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 + } +} From 76df374624e5d82dcb2670789a6c4ff228dda9e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 14 Feb 2020 02:23:41 +0100 Subject: [PATCH 18/27] Implement additional methods in `path::Builder` --- examples/clock/src/main.rs | 9 +--- wgpu/src/widget/canvas/path.rs | 85 ++++++++++++++++++++++++++++++---- 2 files changed, 76 insertions(+), 18 deletions(-) diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 25a213cd..0a70709f 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -94,14 +94,7 @@ impl canvas::layer::Drawable for LocalTime { let radius = frame.width().min(frame.height()) / 2.0; let offset = Vector::new(center.x, center.y); - let clock = canvas::Path::new(|path| { - path.arc(canvas::path::Arc { - center, - radius, - start_angle: 0.0, - end_angle: 2.0 * std::f32::consts::PI, - }) - }); + let clock = canvas::Path::new(|path| path.circle(center, radius)); frame.fill( &clock, diff --git a/wgpu/src/widget/canvas/path.rs b/wgpu/src/widget/canvas/path.rs index c8ba10e1..8847ea29 100644 --- a/wgpu/src/widget/canvas/path.rs +++ b/wgpu/src/widget/canvas/path.rs @@ -1,4 +1,6 @@ -use iced_native::{Point, Vector}; +use iced_native::{Point, Size, Vector}; + +use lyon::path::builder::{Build, FlatPathBuilder, PathBuilder, SvgBuilder}; #[derive(Debug, Clone)] pub struct Path { @@ -23,13 +25,13 @@ impl Path { #[allow(missing_debug_implementations)] pub struct Builder { - raw: lyon::path::Builder, + raw: lyon::path::builder::SvgPathBuilder, } impl Builder { pub fn new() -> Builder { Builder { - raw: lyon::path::Path::builder(), + raw: lyon::path::Path::builder().with_svg(), } } @@ -48,14 +50,32 @@ impl Builder { self.ellipse(arc.into()); } - #[inline] + 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) { - let arc = lyon::geom::Arc { - center: lyon::math::Point::new(ellipse.center.x, ellipse.center.y), - radii: lyon::math::Vector::new(ellipse.radii.x, ellipse.radii.y), - x_rotation: lyon::math::Angle::radians(ellipse.rotation), - start_angle: lyon::math::Angle::radians(ellipse.start_angle), - sweep_angle: lyon::math::Angle::radians(ellipse.end_angle), + 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)); @@ -65,6 +85,51 @@ impl Builder { }); } + #[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() From 558abf648bdeb86d92e7092f4b023d5e55cc673c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 14 Feb 2020 04:59:31 +0100 Subject: [PATCH 19/27] Add transform stack to `canvas::Frame` --- core/src/color.rs | 9 ++- core/src/point.rs | 7 ++- src/lib.rs | 2 +- web/src/lib.rs | 4 +- wgpu/src/widget/canvas/frame.rs | 102 +++++++++++++++++++++++++++++--- wgpu/src/widget/canvas/path.rs | 10 ++++ 6 files changed, 122 insertions(+), 12 deletions(-) diff --git a/core/src/color.rs b/core/src/color.rs index d6bdd365..db509b88 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -44,11 +44,18 @@ impl Color { /// /// [`Color`]: struct.Color.html pub fn from_rgb8(r: u8, g: u8, b: u8) -> Color { + Color::from_rgba8(r, g, b, 1.0) + } + + /// Creates a [`Color`] from its RGB8 components and an alpha value. + /// + /// [`Color`]: struct.Color.html + pub fn from_rgba8(r: u8, g: u8, b: u8, a: f32) -> Color { Color { r: f32::from(r) / 255.0, g: f32::from(g) / 255.0, b: f32::from(b) / 255.0, - a: 1.0, + a, } } diff --git a/core/src/point.rs b/core/src/point.rs index 47c8b142..b9a8149c 100644 --- a/core/src/point.rs +++ b/core/src/point.rs @@ -11,10 +11,15 @@ pub struct Point { } impl Point { + /// The origin (i.e. a [`Point`] with both X=0 and Y=0). + /// + /// [`Point`]: struct.Point.html + pub const ORIGIN: Point = Point::new(0.0, 0.0); + /// Creates a new [`Point`] with the given coordinates. /// /// [`Point`]: struct.Point.html - pub fn new(x: f32, y: f32) -> Self { + pub const fn new(x: f32, y: f32) -> Self { Self { x, y } } } diff --git a/src/lib.rs b/src/lib.rs index 59870692..d492db02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -204,5 +204,5 @@ use iced_web as common; pub use common::{ futures, Align, Background, Color, Command, Font, HorizontalAlignment, - Length, Point, Space, Subscription, Vector, VerticalAlignment, + Length, Point, Size, Space, Subscription, Vector, VerticalAlignment, }; diff --git a/web/src/lib.rs b/web/src/lib.rs index 4dc7aba7..258ad9e7 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -73,8 +73,8 @@ pub use dodrio; pub use element::Element; pub use hasher::Hasher; pub use iced_core::{ - Align, Background, Color, Font, HorizontalAlignment, Length, Point, Vector, - VerticalAlignment, + Align, Background, Color, Font, HorizontalAlignment, Length, Point, Size, + Vector, VerticalAlignment, }; pub use iced_futures::{executor, futures, Command}; pub use subscription::Subscription; diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index 3c667426..687f6c37 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -1,4 +1,4 @@ -use iced_native::Point; +use iced_native::{Point, Size, Vector}; use crate::{ canvas::{Fill, Path, Stroke}, @@ -10,6 +10,20 @@ pub struct Frame { width: f32, height: f32, buffers: lyon::tessellation::VertexBuffers, + + transforms: Transforms, +} + +#[derive(Debug)] +struct Transforms { + previous: Vec, + current: Transform, +} + +#[derive(Debug, Clone, Copy)] +struct Transform { + raw: lyon::math::Transform, + is_identity: bool, } impl Frame { @@ -18,17 +32,32 @@ impl 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) } @@ -47,9 +76,23 @@ impl Frame { let mut tessellator = FillTessellator::new(); - let _ = tessellator - .tessellate_path(path.raw(), &FillOptions::default(), &mut buffers) - .expect("Tessellate path"); + 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) { @@ -70,9 +113,54 @@ impl Frame { options.end_cap = stroke.line_cap.into(); options.line_join = stroke.line_join.into(); - let _ = tessellator - .tessellate_path(path.raw(), &options, &mut buffers) - .expect("Stroke path"); + 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 { diff --git a/wgpu/src/widget/canvas/path.rs b/wgpu/src/widget/canvas/path.rs index 8847ea29..b70d0aef 100644 --- a/wgpu/src/widget/canvas/path.rs +++ b/wgpu/src/widget/canvas/path.rs @@ -21,6 +21,16 @@ impl Path { 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)] From ad3a0a184f877cbca3db4006e802231e201138ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 14 Feb 2020 05:33:58 +0100 Subject: [PATCH 20/27] Add `solar_system` example --- Cargo.toml | 1 + examples/solar_system/Cargo.toml | 15 ++ examples/solar_system/src/main.rs | 244 ++++++++++++++++++++++++++++++ 3 files changed, 260 insertions(+) create mode 100644 examples/solar_system/Cargo.toml create mode 100644 examples/solar_system/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index dd099c32..01231b70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ members = [ "examples/integration", "examples/pokedex", "examples/progress_bar", + "examples/solar_system", "examples/stopwatch", "examples/styling", "examples/svg", diff --git a/examples/solar_system/Cargo.toml b/examples/solar_system/Cargo.toml new file mode 100644 index 00000000..c88cda50 --- /dev/null +++ b/examples/solar_system/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "solar_system" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[features] +canvas = [] + +[dependencies] +iced = { path = "../..", features = ["canvas", "async-std", "debug"] } +iced_native = { path = "../../native" } +async-std = { version = "1.0", features = ["unstable"] } +rand = "0.7" diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs new file mode 100644 index 00000000..d05acf84 --- /dev/null +++ b/examples/solar_system/src/main.rs @@ -0,0 +1,244 @@ +//! An animated solar system. +//! +//! This example showcases how to use a `Canvas` widget with transforms to draw +//! using different coordinate systems. +//! +//! Inspired by the example found in the MDN docs[1]. +//! +//! [1]: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations#An_animated_solar_system +use iced::{ + canvas, executor, Application, Canvas, Color, Command, Container, Element, + Length, Point, Settings, Size, Subscription, Vector, +}; + +use std::time::Instant; + +pub fn main() { + SolarSystem::run(Settings::default()) +} + +struct SolarSystem { + state: State, + solar_system: canvas::layer::Cached, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + Tick(Instant), +} + +impl Application for SolarSystem { + type Executor = executor::Default; + type Message = Message; + + fn new() -> (Self, Command) { + ( + SolarSystem { + state: State::new(), + solar_system: canvas::layer::Cached::new(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Solar system - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Tick(instant) => { + self.state.update(instant); + self.solar_system.clear(); + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription { + time::every(std::time::Duration::from_millis(10)) + .map(|instant| Message::Tick(instant)) + } + + fn view(&mut self) -> Element { + let canvas = Canvas::new() + .width(Length::Fill) + .height(Length::Fill) + .push(self.solar_system.with(&self.state)); + + Container::new(canvas) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +#[derive(Debug)] +struct State { + start: Instant, + current: Instant, + stars: Vec<(Point, f32)>, +} + +impl State { + const SUN_RADIUS: f32 = 70.0; + const ORBIT_RADIUS: f32 = 150.0; + const EARTH_RADIUS: f32 = 12.0; + const MOON_RADIUS: f32 = 4.0; + const MOON_DISTANCE: f32 = 28.0; + + pub fn new() -> State { + let now = Instant::now(); + let (width, height) = Settings::default().window.size; + + State { + start: now, + current: now, + stars: { + use rand::Rng; + + let mut rng = rand::thread_rng(); + + (0..100) + .map(|_| { + ( + Point::new( + rng.gen_range(0.0, width as f32), + rng.gen_range(0.0, height as f32), + ), + rng.gen_range(0.5, 1.0), + ) + }) + .collect() + }, + } + } + + pub fn update(&mut self, now: Instant) { + self.current = now; + } +} + +impl canvas::layer::Drawable for State { + fn draw(&self, frame: &mut canvas::Frame) { + use canvas::{Fill, Path, Stroke}; + use std::f32::consts::PI; + + let center = frame.center(); + + let space = Path::new(|path| { + path.rectangle(Point::new(0.0, 0.0), frame.size()) + }); + + let stars = Path::new(|path| { + for (p, size) in &self.stars { + path.rectangle(*p, Size::new(*size, *size)); + } + }); + + let sun = Path::new(|path| path.circle(center, Self::SUN_RADIUS)); + let orbit = Path::new(|path| path.circle(center, Self::ORBIT_RADIUS)); + + frame.fill(&space, Fill::Color(Color::BLACK)); + frame.fill(&stars, Fill::Color(Color::WHITE)); + frame.fill(&sun, Fill::Color(Color::from_rgb8(0xF9, 0xD7, 0x1C))); + frame.stroke( + &orbit, + Stroke { + width: 1.0, + color: Color::from_rgba8(0, 153, 255, 0.1), + ..Stroke::default() + }, + ); + + let elapsed = self.current - self.start; + let elapsed_seconds = elapsed.as_secs() as f32; + let elapsed_millis = elapsed.subsec_millis() as f32; + + frame.with_save(|frame| { + frame.translate(Vector::new(center.x, center.y)); + frame.rotate( + (2.0 * PI / 60.0) * elapsed_seconds + + (2.0 * PI / 60_000.0) * elapsed_millis, + ); + frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0)); + + let earth = Path::new(|path| { + path.circle(Point::ORIGIN, Self::EARTH_RADIUS) + }); + + let shadow = Path::new(|path| { + path.rectangle( + Point::new(0.0, -Self::EARTH_RADIUS), + Size::new( + Self::EARTH_RADIUS * 4.0, + Self::EARTH_RADIUS * 2.0, + ), + ) + }); + + frame.fill(&earth, Fill::Color(Color::from_rgb8(0x6B, 0x93, 0xD6))); + + frame.with_save(|frame| { + frame.rotate( + ((2.0 * PI) / 6.0) * elapsed_seconds + + ((2.0 * PI) / 6_000.0) * elapsed_millis, + ); + frame.translate(Vector::new(0.0, Self::MOON_DISTANCE)); + + let moon = Path::new(|path| { + path.circle(Point::ORIGIN, Self::MOON_RADIUS) + }); + + frame.fill(&moon, Fill::Color(Color::WHITE)); + }); + + frame.fill( + &shadow, + Fill::Color(Color { + a: 0.7, + ..Color::BLACK + }), + ); + }); + } +} + +mod time { + use iced::futures; + use std::time::Instant; + + pub fn every(duration: std::time::Duration) -> iced::Subscription { + iced::Subscription::from_recipe(Every(duration)) + } + + struct Every(std::time::Duration); + + impl iced_native::subscription::Recipe for Every + where + H: std::hash::Hasher, + { + type Output = Instant; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box, + _input: futures::stream::BoxStream<'static, I>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + async_std::stream::interval(self.0) + .map(|_| Instant::now()) + .boxed() + } + } +} From 945dfabd7135d1bd44a14e54d95b716642651ed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 14 Feb 2020 05:35:42 +0100 Subject: [PATCH 21/27] Move `Size` to `iced_core` --- core/src/lib.rs | 2 ++ {native => core}/src/size.rs | 0 native/src/lib.rs | 4 +--- 3 files changed, 3 insertions(+), 3 deletions(-) rename {native => core}/src/size.rs (100%) diff --git a/core/src/lib.rs b/core/src/lib.rs index 3cbce743..ea5e8b43 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -22,6 +22,7 @@ mod font; mod length; mod point; mod rectangle; +mod size; mod vector; pub use align::{Align, HorizontalAlignment, VerticalAlignment}; @@ -31,4 +32,5 @@ pub use font::Font; pub use length::Length; pub use point::Point; pub use rectangle::Rectangle; +pub use size::Size; pub use vector::Vector; diff --git a/native/src/size.rs b/core/src/size.rs similarity index 100% rename from native/src/size.rs rename to core/src/size.rs diff --git a/native/src/lib.rs b/native/src/lib.rs index 3b81ef71..e4e7baee 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -52,12 +52,11 @@ mod event; mod hasher; mod mouse_cursor; mod runtime; -mod size; mod user_interface; pub use iced_core::{ Align, Background, Color, Font, HorizontalAlignment, Length, Point, - Rectangle, Vector, VerticalAlignment, + Rectangle, Size, Vector, VerticalAlignment, }; pub use iced_futures::{executor, futures, Command}; @@ -72,7 +71,6 @@ pub use layout::Layout; pub use mouse_cursor::MouseCursor; pub use renderer::Renderer; pub use runtime::Runtime; -pub use size::Size; pub use subscription::Subscription; pub use user_interface::{Cache, UserInterface}; pub use widget::*; From f5c80a6d75d5022b175d3562f0965598b6398bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 14 Feb 2020 05:42:19 +0100 Subject: [PATCH 22/27] Upgrade `Mesh2D` indices from `u16` to `u32` --- examples/bezier_tool/src/main.rs | 2 +- wgpu/src/triangle.rs | 4 ++-- wgpu/src/widget/canvas/frame.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index fbb6fa24..efdb3924 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -94,7 +94,7 @@ mod bezier { layout: Layout<'_>, cursor_position: Point, ) -> (Primitive, MouseCursor) { - let mut buffer: VertexBuffers = VertexBuffers::new(); + let mut buffer: VertexBuffers = VertexBuffers::new(); let mut path_builder = lyon::path::Path::builder(); let bounds = layout.bounds(); diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 6f3adbe4..3cc1d3fb 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -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::() as u64, step_mode: wgpu::InputStepMode::Vertex, @@ -233,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, + pub indices: Vec, } diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index 687f6c37..27d676d6 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -9,7 +9,7 @@ use crate::{ pub struct Frame { width: f32, height: f32, - buffers: lyon::tessellation::VertexBuffers, + buffers: lyon::tessellation::VertexBuffers, transforms: Transforms, } From dadae122533ae0916bebd04d6efab3de145263d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 15 Feb 2020 10:08:27 +0100 Subject: [PATCH 23/27] Implement MSAA for `triangle` pipeline in `iced_wgpu` --- examples/bezier_tool/src/main.rs | 5 +- examples/clock/src/main.rs | 5 +- examples/solar_system/src/main.rs | 5 +- src/application.rs | 5 + src/settings.rs | 9 ++ wgpu/src/lib.rs | 2 +- wgpu/src/renderer.rs | 9 +- wgpu/src/settings.rs | 22 +++ wgpu/src/shader/blit.frag | 12 ++ wgpu/src/shader/blit.frag.spv | Bin 0 -> 684 bytes wgpu/src/shader/blit.vert | 26 +++ wgpu/src/shader/blit.vert.spv | Bin 0 -> 1384 bytes wgpu/src/shader/image.vert | 24 --- wgpu/src/triangle.rs | 233 +++++++++++++++++++++------ wgpu/src/triangle/msaa.rs | 255 ++++++++++++++++++++++++++++++ 15 files changed, 539 insertions(+), 73 deletions(-) create mode 100644 wgpu/src/shader/blit.frag create mode 100644 wgpu/src/shader/blit.frag.spv create mode 100644 wgpu/src/shader/blit.vert create mode 100644 wgpu/src/shader/blit.vert.spv delete mode 100644 wgpu/src/shader/image.vert create mode 100644 wgpu/src/triangle/msaa.rs diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index efdb3924..023eb0f7 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -286,7 +286,10 @@ use iced::{ }; pub fn main() { - Example::run(Settings::default()) + Example::run(Settings { + antialiasing: true, + ..Settings::default() + }); } #[derive(Default)] diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 0a70709f..f7fb6f2d 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -4,7 +4,10 @@ use iced::{ }; pub fn main() { - Clock::run(Settings::default()) + Clock::run(Settings { + antialiasing: true, + ..Settings::default() + }) } struct Clock { diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index d05acf84..9e7dba2f 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -14,7 +14,10 @@ use iced::{ use std::time::Instant; pub fn main() { - SolarSystem::run(Settings::default()) + SolarSystem::run(Settings { + antialiasing: true, + ..Settings::default() + }) } struct SolarSystem { diff --git a/src/application.rs b/src/application.rs index 0a4b6d9e..1b73101a 100644 --- a/src/application.rs +++ b/src/application.rs @@ -178,6 +178,11 @@ pub trait Application: Sized { _settings.into(), iced_wgpu::Settings { default_font: _settings.default_font, + antialiasing: if _settings.antialiasing { + Some(iced_wgpu::settings::MSAA::X4) + } else { + None + }, }, ); diff --git a/src/settings.rs b/src/settings.rs index 77c7e0b9..f70e577f 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -16,6 +16,15 @@ pub struct Settings { /// If `None` is provided, a default system font will be chosen. // TODO: Add `name` for web compatibility pub default_font: Option<&'static [u8]>, + + /// If set to true, the renderer will try to use antialiasing for some + /// primitives. + /// + /// Enabling it can produce a smoother result in some widgets, like the + /// `Canvas`, at a performance cost. + /// + /// By default, it is disabled. + pub antialiasing: bool, } #[cfg(not(target_arch = "wasm32"))] diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index d38e2a31..90d28353 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -25,6 +25,7 @@ #![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] pub mod defaults; +pub mod settings; pub mod triangle; pub mod widget; pub mod window; @@ -33,7 +34,6 @@ mod image; mod primitive; mod quad; mod renderer; -mod settings; mod target; mod text; mod transformation; diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 25b2e99a..29adcfb6 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -51,7 +51,8 @@ impl Renderer { let text_pipeline = text::Pipeline::new(device, settings.default_font); let quad_pipeline = quad::Pipeline::new(device); let image_pipeline = crate::image::Pipeline::new(device); - let triangle_pipeline = triangle::Pipeline::new(device); + let triangle_pipeline = + triangle::Pipeline::new(device, settings.antialiasing); Self { quad_pipeline, @@ -105,6 +106,8 @@ impl Renderer { &layer, encoder, target.texture, + width, + height, ); } @@ -308,6 +311,8 @@ impl Renderer { layer: &Layer<'_>, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, + target_width: u32, + target_height: u32, ) { let bounds = layer.bounds * scale_factor; @@ -323,6 +328,8 @@ impl Renderer { device, encoder, target, + target_width, + target_height, translated, &layer.meshes, bounds, diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index dbe81830..c8a0cadf 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -7,4 +7,26 @@ pub struct Settings { /// /// If `None` is provided, a default system font will be chosen. pub default_font: Option<&'static [u8]>, + + /// The antialiasing strategy that will be used for triangle primitives. + pub antialiasing: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MSAA { + X2, + X4, + X8, + X16, +} + +impl MSAA { + pub(crate) fn sample_count(&self) -> u32 { + match self { + MSAA::X2 => 2, + MSAA::X4 => 4, + MSAA::X8 => 8, + MSAA::X16 => 16, + } + } } diff --git a/wgpu/src/shader/blit.frag b/wgpu/src/shader/blit.frag new file mode 100644 index 00000000..dfed960f --- /dev/null +++ b/wgpu/src/shader/blit.frag @@ -0,0 +1,12 @@ +#version 450 + +layout(location = 0) in vec2 v_Uv; + +layout(set = 0, binding = 0) uniform sampler u_Sampler; +layout(set = 1, binding = 0) uniform texture2D u_Texture; + +layout(location = 0) out vec4 o_Color; + +void main() { + o_Color = texture(sampler2D(u_Texture, u_Sampler), v_Uv); +} diff --git a/wgpu/src/shader/blit.frag.spv b/wgpu/src/shader/blit.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..2c5638b5dd682be63a5eb64d225284d399e85418 GIT binary patch literal 684 zcmYk3Pfx-?5XA>d0R{OZ3WA9Ny%P^=OpIJOaN&mFu_2)c60oKM@$>nqyqNfYOPh4a zF#F!Vo!Phb@^>Y(oR#(K+A*4z7h_hjqN|hXY&!hft%eVe_b6)SIU$<5m8_sjZkOv1 zHcpgnWkWi64baL{SF;}-^2KKLV9rFrINC(9_I;}g?}NASd$*56t>GGun=QAWn=p1< z!Ob}Y^MjyGl0G*81(-g!O75ECfz||=p6nAT&hFzQ?bnh6JPe#0bL+O&MR9#)NB!^$ z#$R9mWmR4a{w2Y9h3m@osVe%MG8-wTM18{dRMn}|)z#Irt>)HT2#s)pzek~3OQ8u9!hGnKcCfRz)1MRv{l`xps8|TC zQmAH;eXch6Hq3JUlVAmGf);;G>>tD(RI@r`wd4JdKQZLH<-V_@{ZHv(ksiIz57Hy~ zPPjxqGaP;%rL{J}qTNjoD>)$*xa{6-4bwDP;s$1s2b^}h0{t8|fn}Ahz@bG19E+O_M)yhAwW8X*a0`Vzq0$kg5 z?Ag{_BR-9Fubg@LGr-m3uI3hioQRr5{1A>`Ma@|v^YW2fs^eMiT+ONN@g;fwGVly* zxJO@|zIQl}O+cfxZMe#p_s+(?QOsY&w{L4Zqsw(H=M1l6y{Ge!Gx5FM?@X>?&C9Lh zCD^#foUrZ&);TA@K8!bkoOSJQ3%KIku9wmA?>M6y#Bw5XH|uzIZnxl!D_u3W+r;iy z@2vFI>D!+_e0#C?r@W=#OdGi5|H5>z_U_F5y*i^9`@I{#sPD3h@0?$ajbO*Ki2Xb?-FZm, constants: wgpu::BindGroup, - constants_buffer: wgpu::Buffer, + uniforms_buffer: Buffer, + vertex_buffer: Buffer, + index_buffer: Buffer, +} + +#[derive(Debug)] +struct Buffer { + raw: wgpu::Buffer, + size: usize, + usage: wgpu::BufferUsage, + _type: std::marker::PhantomData, +} + +impl Buffer { + pub fn new( + device: &wgpu::Device, + size: usize, + usage: wgpu::BufferUsage, + ) -> Self { + let raw = device.create_buffer(&wgpu::BufferDescriptor { + size: (std::mem::size_of::() * size) as u64, + usage, + }); + + Buffer { + raw, + size, + usage, + _type: std::marker::PhantomData, + } + } + + pub fn ensure_capacity(&mut self, device: &wgpu::Device, size: usize) { + if self.size < size { + self.raw = device.create_buffer(&wgpu::BufferDescriptor { + size: (std::mem::size_of::() * size) as u64, + usage: self.usage, + }); + + self.size = size; + } + } } impl Pipeline { - pub fn new(device: &mut wgpu::Device) -> Pipeline { + pub fn new( + device: &mut wgpu::Device, + antialiasing: Option, + ) -> Pipeline { let constant_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { bindings: &[wgpu::BindGroupLayoutBinding { binding: 0, visibility: wgpu::ShaderStage::VERTEX, - ty: wgpu::BindingType::UniformBuffer { dynamic: false }, + ty: wgpu::BindingType::UniformBuffer { dynamic: true }, }], }); - let constants_buffer = device - .create_buffer_mapped( - 1, - wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, - ) - .fill_from_slice(&[Uniforms::default()]); + let constants_buffer = Buffer::new( + device, + UNIFORM_BUFFER_SIZE, + wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, + ); let constant_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { @@ -34,7 +84,7 @@ impl Pipeline { bindings: &[wgpu::Binding { binding: 0, resource: wgpu::BindingResource::Buffer { - buffer: &constants_buffer, + buffer: &constants_buffer.raw, range: 0..std::mem::size_of::() as u64, }, }], @@ -110,15 +160,28 @@ impl Pipeline { }, ], }], - sample_count: 1, + sample_count: antialiasing + .map(|a| a.sample_count()) + .unwrap_or(1), sample_mask: !0, alpha_to_coverage_enabled: false, }); Pipeline { pipeline, + blit: antialiasing.map(|a| msaa::Blit::new(device, a)), constants: constant_bind_group, - constants_buffer, + uniforms_buffer: constants_buffer, + vertex_buffer: Buffer::new( + device, + VERTEX_BUFFER_SIZE, + wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, + ), + index_buffer: Buffer::new( + device, + INDEX_BUFFER_SIZE, + wgpu::BufferUsage::INDEX | wgpu::BufferUsage::COPY_DST, + ), } } @@ -127,50 +190,116 @@ impl Pipeline { device: &mut wgpu::Device, encoder: &mut wgpu::CommandEncoder, target: &wgpu::TextureView, + target_width: u32, + target_height: u32, transformation: Transformation, meshes: &Vec<(Point, Arc)>, bounds: Rectangle, ) { + // This looks a bit crazy, but we are just counting how many vertices + // and indices we will need to handle. + // TODO: Improve readability + let (total_vertices, total_indices) = meshes + .iter() + .map(|(_, mesh)| (mesh.vertices.len(), mesh.indices.len())) + .fold((0, 0), |(total_v, total_i), (v, i)| { + (total_v + v, total_i + i) + }); + + // Then we ensure the current buffers are big enough, resizing if + // necessary + self.uniforms_buffer.ensure_capacity(device, meshes.len()); + self.vertex_buffer.ensure_capacity(device, total_vertices); + self.index_buffer.ensure_capacity(device, total_indices); + + let mut uniforms: Vec = Vec::with_capacity(meshes.len()); + let mut offsets: Vec<( + wgpu::BufferAddress, + wgpu::BufferAddress, + usize, + )> = Vec::with_capacity(meshes.len()); + let mut last_vertex = 0; + let mut last_index = 0; + + // We upload everything upfront for (origin, mesh) in meshes { - let uniforms = Uniforms { + let transform = 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::() as u64, - ); - - let vertices_buffer = device + let vertex_buffer = device .create_buffer_mapped( mesh.vertices.len(), - wgpu::BufferUsage::VERTEX, + wgpu::BufferUsage::COPY_SRC, ) .fill_from_slice(&mesh.vertices); - let indices_buffer = device + let index_buffer = device .create_buffer_mapped( mesh.indices.len(), - wgpu::BufferUsage::INDEX, + wgpu::BufferUsage::COPY_SRC, ) .fill_from_slice(&mesh.indices); + encoder.copy_buffer_to_buffer( + &vertex_buffer, + 0, + &self.vertex_buffer.raw, + last_vertex as u64, + (std::mem::size_of::() * mesh.vertices.len()) as u64, + ); + + encoder.copy_buffer_to_buffer( + &index_buffer, + 0, + &self.index_buffer.raw, + last_index as u64, + (std::mem::size_of::() * mesh.indices.len()) as u64, + ); + + uniforms.push(transform); + offsets.push(( + last_vertex as u64, + last_index as u64, + mesh.indices.len(), + )); + + last_vertex += mesh.vertices.len(); + last_index += mesh.indices.len(); + } + + let uniforms_buffer = device + .create_buffer_mapped(uniforms.len(), wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&uniforms); + + encoder.copy_buffer_to_buffer( + &uniforms_buffer, + 0, + &self.uniforms_buffer.raw, + 0, + (std::mem::size_of::() * uniforms.len()) as u64, + ); + + { + let (attachment, resolve_target, load_op) = + if let Some(blit) = &mut self.blit { + let (attachment, resolve_target) = + blit.targets(device, target_width, target_height); + + (attachment, Some(resolve_target), wgpu::LoadOp::Clear) + } else { + (target, None, wgpu::LoadOp::Load) + }; + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { color_attachments: &[ wgpu::RenderPassColorAttachmentDescriptor { - attachment: target, - resolve_target: None, - load_op: wgpu::LoadOp::Load, + attachment, + resolve_target, + load_op, store_op: wgpu::StoreOp::Store, clear_color: wgpu::Color { r: 0.0, @@ -183,18 +312,34 @@ impl Pipeline { 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); - render_pass.set_vertex_buffers(0, &[(&vertices_buffer, 0)]); - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); + for (i, (vertex_offset, index_offset, indices)) in + offsets.drain(..).enumerate() + { + render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group( + 0, + &self.constants, + &[(std::mem::size_of::() * i) as u64], + ); + render_pass + .set_index_buffer(&self.index_buffer.raw, index_offset); + render_pass.set_vertex_buffers( + 0, + &[(&self.vertex_buffer.raw, vertex_offset)], + ); + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); - render_pass.draw_indexed(0..mesh.indices.len() as u32, 0, 0..1); + render_pass.draw_indexed(0..indices as u32, 0, 0..1); + } + } + + if let Some(blit) = &mut self.blit { + blit.draw(encoder, target); } } } diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs new file mode 100644 index 00000000..93fbe49b --- /dev/null +++ b/wgpu/src/triangle/msaa.rs @@ -0,0 +1,255 @@ +use crate::settings; + +#[derive(Debug)] +pub struct Blit { + pipeline: wgpu::RenderPipeline, + constants: wgpu::BindGroup, + texture_layout: wgpu::BindGroupLayout, + sample_count: u32, + targets: Option, +} + +impl Blit { + pub fn new(device: &wgpu::Device, antialiasing: settings::MSAA) -> Blit { + let sampler = device.create_sampler(&wgpu::SamplerDescriptor { + address_mode_u: wgpu::AddressMode::ClampToEdge, + address_mode_v: wgpu::AddressMode::ClampToEdge, + address_mode_w: wgpu::AddressMode::ClampToEdge, + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + mipmap_filter: wgpu::FilterMode::Linear, + lod_min_clamp: -100.0, + lod_max_clamp: 100.0, + compare_function: wgpu::CompareFunction::Always, + }); + + let constant_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::Sampler, + }], + }); + + let constant_bind_group = + device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &constant_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::Sampler(&sampler), + }], + }); + + let texture_layout = + device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + bindings: &[wgpu::BindGroupLayoutBinding { + binding: 0, + visibility: wgpu::ShaderStage::FRAGMENT, + ty: wgpu::BindingType::SampledTexture { + multisampled: false, + dimension: wgpu::TextureViewDimension::D2, + }, + }], + }); + + let layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&constant_layout, &texture_layout], + }); + + let vs = include_bytes!("../shader/blit.vert.spv"); + let vs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&vs[..])) + .expect("Read blit vertex shader as SPIR-V"), + ); + + let fs = include_bytes!("../shader/blit.frag.spv"); + let fs_module = device.create_shader_module( + &wgpu::read_spirv(std::io::Cursor::new(&fs[..])) + .expect("Read blit fragment shader as SPIR-V"), + ); + + let pipeline = + device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + layout: &layout, + vertex_stage: wgpu::ProgrammableStageDescriptor { + module: &vs_module, + entry_point: "main", + }, + fragment_stage: Some(wgpu::ProgrammableStageDescriptor { + module: &fs_module, + entry_point: "main", + }), + rasterization_state: Some(wgpu::RasterizationStateDescriptor { + front_face: wgpu::FrontFace::Cw, + cull_mode: wgpu::CullMode::None, + depth_bias: 0, + depth_bias_slope_scale: 0.0, + depth_bias_clamp: 0.0, + }), + primitive_topology: wgpu::PrimitiveTopology::TriangleList, + color_states: &[wgpu::ColorStateDescriptor { + format: wgpu::TextureFormat::Bgra8UnormSrgb, + color_blend: wgpu::BlendDescriptor { + src_factor: wgpu::BlendFactor::SrcAlpha, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + alpha_blend: wgpu::BlendDescriptor { + src_factor: wgpu::BlendFactor::One, + dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, + operation: wgpu::BlendOperation::Add, + }, + write_mask: wgpu::ColorWrite::ALL, + }], + depth_stencil_state: None, + index_format: wgpu::IndexFormat::Uint16, + vertex_buffers: &[], + sample_count: 1, + sample_mask: !0, + alpha_to_coverage_enabled: false, + }); + + Blit { + pipeline, + constants: constant_bind_group, + texture_layout: texture_layout, + sample_count: antialiasing.sample_count(), + targets: None, + } + } + + pub fn targets( + &mut self, + device: &wgpu::Device, + width: u32, + height: u32, + ) -> (&wgpu::TextureView, &wgpu::TextureView) { + match &mut self.targets { + None => { + self.targets = Some(Targets::new( + &device, + &self.texture_layout, + self.sample_count, + width, + height, + )); + } + Some(targets) => { + if targets.width != width || targets.height != height { + self.targets = Some(Targets::new( + &device, + &self.texture_layout, + self.sample_count, + width, + height, + )); + } + } + } + + let targets = self.targets.as_ref().unwrap(); + + (&targets.attachment, &targets.resolve) + } + + pub fn draw( + &self, + encoder: &mut wgpu::CommandEncoder, + target: &wgpu::TextureView, + ) { + 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_bind_group( + 1, + &self.targets.as_ref().unwrap().bind_group, + &[], + ); + render_pass.draw(0..6, 0..1); + } +} + +#[derive(Debug)] +struct Targets { + attachment: wgpu::TextureView, + resolve: wgpu::TextureView, + bind_group: wgpu::BindGroup, + width: u32, + height: u32, +} + +impl Targets { + pub fn new( + device: &wgpu::Device, + texture_layout: &wgpu::BindGroupLayout, + sample_count: u32, + width: u32, + height: u32, + ) -> Targets { + let extent = wgpu::Extent3d { + width, + height, + depth: 1, + }; + + let attachment = device.create_texture(&wgpu::TextureDescriptor { + size: extent, + array_layer_count: 1, + mip_level_count: 1, + sample_count, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + }); + + let resolve = device.create_texture(&wgpu::TextureDescriptor { + size: extent, + array_layer_count: 1, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT + | wgpu::TextureUsage::SAMPLED, + }); + + let attachment = attachment.create_default_view(); + let resolve = resolve.create_default_view(); + + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: texture_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::TextureView(&resolve), + }], + }); + + Targets { + attachment, + resolve, + bind_group, + width, + height, + } + } +} From fe61d2fd676beea9b0b6b30471fe595f4f88496d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 15 Feb 2020 10:45:45 +0100 Subject: [PATCH 24/27] Request high performance adapter if MSAA is enabled --- wgpu/src/window/backend.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wgpu/src/window/backend.rs b/wgpu/src/window/backend.rs index 6f8a0bb0..4c9f289b 100644 --- a/wgpu/src/window/backend.rs +++ b/wgpu/src/window/backend.rs @@ -18,7 +18,11 @@ impl iced_native::window::Backend for Backend { fn new(settings: Self::Settings) -> (Backend, Renderer) { let adapter = wgpu::Adapter::request(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::Default, + power_preference: if settings.antialiasing.is_none() { + wgpu::PowerPreference::Default + } else { + wgpu::PowerPreference::HighPerformance + }, backends: wgpu::BackendBit::all(), }) .expect("Request adapter"); From 570f769744aabce2d9d9618feadb47e4b92f50ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 15 Feb 2020 10:50:07 +0100 Subject: [PATCH 25/27] Rename `Settings::antialiasing` to `use_antialiasing` --- examples/bezier_tool/src/main.rs | 2 +- examples/clock/src/main.rs | 2 +- examples/solar_system/src/main.rs | 2 +- src/application.rs | 2 +- src/settings.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 023eb0f7..01f8f847 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -287,7 +287,7 @@ use iced::{ pub fn main() { Example::run(Settings { - antialiasing: true, + use_antialiasing: true, ..Settings::default() }); } diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index f7fb6f2d..d0995e0c 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -5,7 +5,7 @@ use iced::{ pub fn main() { Clock::run(Settings { - antialiasing: true, + use_antialiasing: true, ..Settings::default() }) } diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 9e7dba2f..3cabedbb 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -15,7 +15,7 @@ use std::time::Instant; pub fn main() { SolarSystem::run(Settings { - antialiasing: true, + use_antialiasing: true, ..Settings::default() }) } diff --git a/src/application.rs b/src/application.rs index 1b73101a..8a88b55a 100644 --- a/src/application.rs +++ b/src/application.rs @@ -178,7 +178,7 @@ pub trait Application: Sized { _settings.into(), iced_wgpu::Settings { default_font: _settings.default_font, - antialiasing: if _settings.antialiasing { + antialiasing: if _settings.use_antialiasing { Some(iced_wgpu::settings::MSAA::X4) } else { None diff --git a/src/settings.rs b/src/settings.rs index f70e577f..757dc72f 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -24,7 +24,7 @@ pub struct Settings { /// `Canvas`, at a performance cost. /// /// By default, it is disabled. - pub antialiasing: bool, + pub use_antialiasing: bool, } #[cfg(not(target_arch = "wasm32"))] 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 26/27] 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, } From 6f7247ca13181bcdfe1a3065215c1b3204723b84 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 09:54:24 +0100 Subject: [PATCH 27/27] Rename `Settings::use_antialiasing` to `antialiasing` --- examples/bezier_tool/src/main.rs | 2 +- examples/clock/src/main.rs | 2 +- examples/solar_system/src/main.rs | 2 +- src/application.rs | 2 +- src/settings.rs | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 01f8f847..023eb0f7 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -287,7 +287,7 @@ use iced::{ pub fn main() { Example::run(Settings { - use_antialiasing: true, + antialiasing: true, ..Settings::default() }); } diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 559f0192..d8266f06 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -5,7 +5,7 @@ use iced::{ pub fn main() { Clock::run(Settings { - use_antialiasing: true, + antialiasing: true, ..Settings::default() }) } diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index eee51dc2..4c239806 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -15,7 +15,7 @@ use std::time::Instant; pub fn main() { SolarSystem::run(Settings { - use_antialiasing: true, + antialiasing: true, ..Settings::default() }) } diff --git a/src/application.rs b/src/application.rs index a569163b..374810cb 100644 --- a/src/application.rs +++ b/src/application.rs @@ -178,7 +178,7 @@ pub trait Application: Sized { _settings.into(), iced_wgpu::Settings { default_font: _settings.default_font, - antialiasing: if _settings.use_antialiasing { + antialiasing: if _settings.antialiasing { Some(iced_wgpu::settings::Antialiasing::MSAAx4) } else { None diff --git a/src/settings.rs b/src/settings.rs index 757dc72f..32ec583c 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -17,14 +17,14 @@ pub struct Settings { // TODO: Add `name` for web compatibility pub default_font: Option<&'static [u8]>, - /// If set to true, the renderer will try to use antialiasing for some + /// If set to true, the renderer will try to perform antialiasing for some /// primitives. /// /// Enabling it can produce a smoother result in some widgets, like the /// `Canvas`, at a performance cost. /// /// By default, it is disabled. - pub use_antialiasing: bool, + pub antialiasing: bool, } #[cfg(not(target_arch = "wasm32"))]