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 bc39c451..871f4f55 100644 Binary files a/wgpu/src/shader/triangle.vert.spv and b/wgpu/src/shader/triangle.vert.spv differ 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); });