diff options
Diffstat (limited to 'wgpu/src')
28 files changed, 428 insertions, 294 deletions
diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 799c1f34..30b5bb4a 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -20,7 +20,7 @@ //! [`wgpu`]: https://github.com/gfx-rs/wgpu-rs //! [WebGPU API]: https://gpuweb.github.io/gpuweb/ //! [`wgpu_glyph`]: https://github.com/hecrj/wgpu_glyph -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 46d9e624..e73227ef 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -1,5 +1,5 @@ use iced_native::{ - image, svg, Background, Color, Font, HorizontalAlignment, Point, Rectangle, + image, svg, Background, Color, Font, HorizontalAlignment, Rectangle, Size, Vector, VerticalAlignment, }; @@ -70,12 +70,22 @@ pub enum Primitive { /// The content of the clip content: Box<Primitive>, }, + /// A primitive that applies a translation + Translate { + /// The translation vector + translation: Vector, + + /// The primitive to translate + content: Box<Primitive>, + }, /// A low-level primitive to render a mesh of triangles. /// /// It can be used to render many kinds of geometry freely. Mesh2D { - /// The top-left coordinate of the mesh - origin: Point, + /// The size of the drawable region of the mesh. + /// + /// Any geometry that falls out of this region will be clipped. + size: Size, /// The vertex and index buffers of the mesh buffers: triangle::Mesh2D, @@ -85,9 +95,6 @@ pub enum Primitive { /// This can be useful if you are implementing a widget where primitive /// generation is expensive. Cached { - /// The origin of the coordinate system of the cached primitives - origin: Point, - /// The cached primitive cache: Arc<Primitive>, }, diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index ca9364c1..dccd0d82 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -29,7 +29,7 @@ pub struct Renderer { struct Layer<'a> { bounds: Rectangle<u32>, quads: Vec<Quad>, - meshes: Vec<(Point, &'a triangle::Mesh2D)>, + meshes: Vec<(Vector, Rectangle<u32>, &'a triangle::Mesh2D)>, text: Vec<wgpu_glyph::Section<'a>>, #[cfg(any(feature = "image", feature = "svg"))] @@ -48,6 +48,12 @@ impl<'a> Layer<'a> { images: Vec::new(), } } + + pub fn intersection(&self, rectangle: Rectangle) -> Option<Rectangle<u32>> { + let layer_bounds: Rectangle<f32> = self.bounds.into(); + + layer_bounds.intersection(&rectangle).map(Into::into) + } } impl Renderer { @@ -214,10 +220,20 @@ impl Renderer { border_color: border_color.into_linear(), }); } - Primitive::Mesh2D { origin, buffers } => { + Primitive::Mesh2D { size, buffers } => { let layer = layers.last_mut().unwrap(); - layer.meshes.push((*origin + translation, buffers)); + // Only draw visible content + if let Some(clip_bounds) = layer.intersection(Rectangle::new( + Point::new(translation.x, translation.y), + *size, + )) { + layer.meshes.push(( + translation, + clip_bounds.into(), + buffers, + )); + } } Primitive::Clip { bounds, @@ -226,16 +242,10 @@ impl Renderer { } => { let layer = layers.last_mut().unwrap(); - let layer_bounds: Rectangle<f32> = layer.bounds.into(); - - let clip = Rectangle { - x: bounds.x + translation.x, - y: bounds.y + translation.y, - ..*bounds - }; - // Only draw visible content - if let Some(clip_bounds) = layer_bounds.intersection(&clip) { + if let Some(clip_bounds) = + layer.intersection(*bounds + translation) + { let clip_layer = Layer::new(clip_bounds.into()); let new_layer = Layer::new(layer.bounds); @@ -249,15 +259,21 @@ impl Renderer { layers.push(new_layer); } } - - Primitive::Cached { origin, cache } => { + Primitive::Translate { + translation: new_translation, + content, + } => { self.draw_primitive( - translation + Vector::new(origin.x, origin.y), - &cache, + translation + *new_translation, + &content, layers, ); } + Primitive::Cached { cache } => { + self.draw_primitive(translation, &cache, layers); + } + #[cfg(feature = "image")] Primitive::Image { handle, bounds } => { let layer = layers.last_mut().unwrap(); @@ -362,8 +378,8 @@ impl Renderer { target_width, target_height, scaled, + scale_factor, &layer.meshes, - bounds, ); } diff --git a/wgpu/src/renderer/widget/button.rs b/wgpu/src/renderer/widget/button.rs index 359b4866..5e55873a 100644 --- a/wgpu/src/renderer/widget/button.rs +++ b/wgpu/src/renderer/widget/button.rs @@ -86,7 +86,7 @@ impl iced_native::button::Renderer for Renderer { if is_mouse_over { MouseCursor::Pointer } else { - MouseCursor::OutOfBounds + MouseCursor::default() }, ) } diff --git a/wgpu/src/renderer/widget/checkbox.rs b/wgpu/src/renderer/widget/checkbox.rs index c0f1bf21..7f7f6de3 100644 --- a/wgpu/src/renderer/widget/checkbox.rs +++ b/wgpu/src/renderer/widget/checkbox.rs @@ -56,7 +56,7 @@ impl checkbox::Renderer for Renderer { if is_mouse_over { MouseCursor::Pointer } else { - MouseCursor::OutOfBounds + MouseCursor::default() }, ) } diff --git a/wgpu/src/renderer/widget/column.rs b/wgpu/src/renderer/widget/column.rs index 95a7463a..e6a9d8f0 100644 --- a/wgpu/src/renderer/widget/column.rs +++ b/wgpu/src/renderer/widget/column.rs @@ -9,7 +9,7 @@ impl column::Renderer for Renderer { layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { - let mut mouse_cursor = MouseCursor::OutOfBounds; + let mut mouse_cursor = MouseCursor::default(); ( Primitive::Group { diff --git a/wgpu/src/renderer/widget/image.rs b/wgpu/src/renderer/widget/image.rs index 70dc5d97..6b7f1c60 100644 --- a/wgpu/src/renderer/widget/image.rs +++ b/wgpu/src/renderer/widget/image.rs @@ -16,7 +16,7 @@ impl image::Renderer for Renderer { handle, bounds: layout.bounds(), }, - MouseCursor::OutOfBounds, + MouseCursor::default(), ) } } diff --git a/wgpu/src/renderer/widget/pane_grid.rs b/wgpu/src/renderer/widget/pane_grid.rs index 2d201fec..80e2471f 100644 --- a/wgpu/src/renderer/widget/pane_grid.rs +++ b/wgpu/src/renderer/widget/pane_grid.rs @@ -22,7 +22,7 @@ impl pane_grid::Renderer for Renderer { cursor_position }; - let mut mouse_cursor = MouseCursor::OutOfBounds; + let mut mouse_cursor = MouseCursor::default(); let mut dragged_pane = None; let mut panes: Vec<_> = content @@ -59,12 +59,12 @@ impl pane_grid::Renderer for Renderer { height: bounds.height + 0.5, }, offset: Vector::new(0, 0), - content: Box::new(Primitive::Cached { - origin: Point::new( + content: Box::new(Primitive::Translate { + translation: Vector::new( cursor_position.x - bounds.x - bounds.width / 2.0, cursor_position.y - bounds.y - bounds.height / 2.0, ), - cache: std::sync::Arc::new(pane), + content: Box::new(pane), }), }; diff --git a/wgpu/src/renderer/widget/progress_bar.rs b/wgpu/src/renderer/widget/progress_bar.rs index 34e33276..fe032fbf 100644 --- a/wgpu/src/renderer/widget/progress_bar.rs +++ b/wgpu/src/renderer/widget/progress_bar.rs @@ -48,7 +48,7 @@ impl progress_bar::Renderer for Renderer { } else { background }, - MouseCursor::OutOfBounds, + MouseCursor::default(), ) } } diff --git a/wgpu/src/renderer/widget/radio.rs b/wgpu/src/renderer/widget/radio.rs index 564f066b..551700c8 100644 --- a/wgpu/src/renderer/widget/radio.rs +++ b/wgpu/src/renderer/widget/radio.rs @@ -57,7 +57,7 @@ impl radio::Renderer for Renderer { if is_mouse_over { MouseCursor::Pointer } else { - MouseCursor::OutOfBounds + MouseCursor::default() }, ) } diff --git a/wgpu/src/renderer/widget/row.rs b/wgpu/src/renderer/widget/row.rs index bd9f1a04..c6a10c07 100644 --- a/wgpu/src/renderer/widget/row.rs +++ b/wgpu/src/renderer/widget/row.rs @@ -9,7 +9,7 @@ impl row::Renderer for Renderer { layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { - let mut mouse_cursor = MouseCursor::OutOfBounds; + let mut mouse_cursor = MouseCursor::default(); ( Primitive::Group { diff --git a/wgpu/src/renderer/widget/slider.rs b/wgpu/src/renderer/widget/slider.rs index c8ebd0da..335e1b92 100644 --- a/wgpu/src/renderer/widget/slider.rs +++ b/wgpu/src/renderer/widget/slider.rs @@ -99,7 +99,7 @@ impl slider::Renderer for Renderer { } else if is_mouse_over { MouseCursor::Grab } else { - MouseCursor::OutOfBounds + MouseCursor::default() }, ) } diff --git a/wgpu/src/renderer/widget/space.rs b/wgpu/src/renderer/widget/space.rs index 28e05437..9ec0ed6d 100644 --- a/wgpu/src/renderer/widget/space.rs +++ b/wgpu/src/renderer/widget/space.rs @@ -3,6 +3,6 @@ use iced_native::{space, MouseCursor, Rectangle}; impl space::Renderer for Renderer { fn draw(&mut self, _bounds: Rectangle) -> Self::Output { - (Primitive::None, MouseCursor::OutOfBounds) + (Primitive::None, MouseCursor::default()) } } diff --git a/wgpu/src/renderer/widget/svg.rs b/wgpu/src/renderer/widget/svg.rs index 67bc3fe1..4ee983ea 100644 --- a/wgpu/src/renderer/widget/svg.rs +++ b/wgpu/src/renderer/widget/svg.rs @@ -16,7 +16,7 @@ impl svg::Renderer for Renderer { handle, bounds: layout.bounds(), }, - MouseCursor::OutOfBounds, + MouseCursor::default(), ) } } diff --git a/wgpu/src/renderer/widget/text.rs b/wgpu/src/renderer/widget/text.rs index f27cc430..4a4ecef4 100644 --- a/wgpu/src/renderer/widget/text.rs +++ b/wgpu/src/renderer/widget/text.rs @@ -55,7 +55,7 @@ impl text::Renderer for Renderer { horizontal_alignment, vertical_alignment, }, - MouseCursor::OutOfBounds, + MouseCursor::default(), ) } } diff --git a/wgpu/src/renderer/widget/text_input.rs b/wgpu/src/renderer/widget/text_input.rs index 6f72db68..97eb0114 100644 --- a/wgpu/src/renderer/widget/text_input.rs +++ b/wgpu/src/renderer/widget/text_input.rs @@ -234,7 +234,7 @@ impl text_input::Renderer for Renderer { if is_mouse_over { MouseCursor::Text } else { - MouseCursor::OutOfBounds + MouseCursor::default() }, ) } diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 86c74fcd..246dc7ce 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -1,6 +1,6 @@ //! Draw meshes of triangles. use crate::{settings, Transformation}; -use iced_native::{Point, Rectangle}; +use iced_native::{Rectangle, Vector}; use std::mem; use zerocopy::AsBytes; @@ -201,15 +201,15 @@ impl Pipeline { target_width: u32, target_height: u32, transformation: Transformation, - meshes: &[(Point, &Mesh2D)], - bounds: Rectangle<u32>, + scale_factor: f32, + meshes: &[(Vector, Rectangle<u32>, &Mesh2D)], ) { // 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())) + .map(|(_, _, mesh)| (mesh.vertices.len(), mesh.indices.len())) .fold((0, 0), |(total_v, total_i), (v, i)| { (total_v + v, total_i + i) }); @@ -230,12 +230,10 @@ impl Pipeline { let mut last_index = 0; // We upload everything upfront - for (origin, mesh) in meshes { - let transform = Uniforms { - transform: (transformation - * Transformation::translate(origin.x, origin.y)) - .into(), - }; + for (origin, _, mesh) in meshes { + let transform = (transformation + * Transformation::translate(origin.x, origin.y)) + .into(); let vertex_buffer = device.create_buffer_with_data( mesh.vertices.as_bytes(), @@ -318,16 +316,19 @@ impl Pipeline { }); render_pass.set_pipeline(&self.pipeline); - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); for (i, (vertex_offset, index_offset, indices)) in offsets.into_iter().enumerate() { + let bounds = meshes[i].1 * scale_factor; + + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + render_pass.set_bind_group( 0, &self.constants, @@ -361,12 +362,28 @@ impl Pipeline { #[derive(Debug, Clone, Copy, AsBytes)] struct Uniforms { transform: [f32; 16], + // We need to align this to 256 bytes to please `wgpu`... + // TODO: Be smarter and stop wasting memory! + _padding_a: [f32; 32], + _padding_b: [f32; 16], } impl Default for Uniforms { fn default() -> Self { Self { transform: *Transformation::identity().as_ref(), + _padding_a: [0.0; 32], + _padding_b: [0.0; 16], + } + } +} + +impl From<Transformation> for Uniforms { + fn from(transformation: Transformation) -> Uniforms { + Self { + transform: transformation.into(), + _padding_a: [0.0; 32], + _padding_b: [0.0; 16], } } } diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index 325f90ce..a5834330 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -9,35 +9,38 @@ use crate::{Defaults, Primitive, Renderer}; use iced_native::{ - layout, Element, Hasher, Layout, Length, MouseCursor, Point, Size, Widget, + layout, Clipboard, Element, Hasher, Layout, Length, MouseCursor, Point, + Size, Vector, Widget, }; use std::hash::Hash; +use std::marker::PhantomData; -pub mod layer; pub mod path; -mod drawable; +mod cache; +mod cursor; +mod event; mod fill; mod frame; +mod geometry; +mod program; mod stroke; mod text; -pub use drawable::Drawable; +pub use cache::Cache; +pub use cursor::Cursor; +pub use event::Event; pub use fill::Fill; pub use frame::Frame; -pub use layer::Layer; +pub use geometry::Geometry; pub use path::Path; +pub use program::Program; pub use stroke::{LineCap, LineJoin, Stroke}; pub use text::Text; /// 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 -/// other in the same order they are pushed into the [`Canvas`]. -/// /// [`Canvas`]: struct.Canvas.html -/// [`Layer`]: layer/trait.Layer.html /// /// # Examples /// The repository has a couple of [examples] showcasing how to use a @@ -58,10 +61,10 @@ pub use text::Text; /// ```no_run /// # mod iced { /// # pub use iced_wgpu::canvas; -/// # pub use iced_native::Color; +/// # pub use iced_native::{Color, Rectangle}; /// # } -/// use iced::canvas::{self, layer, Canvas, Drawable, Fill, Frame, Path}; -/// use iced::Color; +/// use iced::canvas::{self, Canvas, Cursor, Fill, Frame, Geometry, Path, Program}; +/// use iced::{Color, Rectangle}; /// /// // First, we define the data we need for drawing /// #[derive(Debug)] @@ -69,43 +72,46 @@ pub use text::Text; /// radius: f32, /// } /// -/// // Then, we implement the `Drawable` trait -/// impl Drawable for Circle { -/// fn draw(&self, frame: &mut Frame) { +/// // Then, we implement the `Program` trait +/// impl Program<()> for Circle { +/// fn draw(&self, bounds: Rectangle, _cursor: Cursor) -> Vec<Geometry>{ +/// // We prepare a new `Frame` +/// let mut frame = Frame::new(bounds.size()); +/// /// // We create a `Path` representing a simple circle -/// let circle = Path::new(|p| p.circle(frame.center(), self.radius)); +/// let circle = Path::circle(frame.center(), self.radius); /// /// // And fill it with some color /// frame.fill(&circle, Fill::Color(Color::BLACK)); +/// +/// // Finally, we produce the geometry +/// vec![frame.into_geometry()] /// } /// } /// -/// // We can use a `Cache` to avoid unnecessary re-tessellation -/// let cache: layer::Cache<Circle> = layer::Cache::new(); -/// -/// // Finally, we simply provide the data to our `Cache` and push the resulting -/// // layer into a `Canvas` -/// let canvas = Canvas::new() -/// .push(cache.with(&Circle { radius: 50.0 })); +/// // Finally, we simply use our `Cache` to create the `Canvas`! +/// let canvas = Canvas::new(Circle { radius: 50.0 }); /// ``` #[derive(Debug)] -pub struct Canvas<'a> { +pub struct Canvas<Message, P: Program<Message>> { width: Length, height: Length, - layers: Vec<Box<dyn Layer + 'a>>, + program: P, + phantom: PhantomData<Message>, } -impl<'a> Canvas<'a> { +impl<Message, P: Program<Message>> Canvas<Message, P> { const DEFAULT_SIZE: u16 = 100; - /// Creates a new [`Canvas`] with no layers. + /// Creates a new [`Canvas`]. /// /// [`Canvas`]: struct.Canvas.html - pub fn new() -> Self { + pub fn new(program: P) -> Self { Canvas { width: Length::Units(Self::DEFAULT_SIZE), height: Length::Units(Self::DEFAULT_SIZE), - layers: Vec::new(), + program, + phantom: PhantomData, } } @@ -124,20 +130,11 @@ impl<'a> Canvas<'a> { 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 - } } -impl<'a, Message> Widget<Message, Renderer> for Canvas<'a> { +impl<Message, P: Program<Message>> Widget<Message, Renderer> + for Canvas<Message, P> +{ fn width(&self) -> Length { self.width } @@ -157,45 +154,77 @@ impl<'a, Message> Widget<Message, Renderer> for Canvas<'a> { layout::Node::new(size) } + fn on_event( + &mut self, + event: iced_native::Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec<Message>, + _renderer: &Renderer, + _clipboard: Option<&dyn Clipboard>, + ) { + let bounds = layout.bounds(); + + let canvas_event = match event { + iced_native::Event::Mouse(mouse_event) => { + Some(Event::Mouse(mouse_event)) + } + _ => None, + }; + + let cursor = Cursor::from_window_position(cursor_position); + + if let Some(canvas_event) = canvas_event { + if let Some(message) = + self.program.update(canvas_event, bounds, cursor) + { + messages.push(message); + } + } + } + fn draw( &self, _renderer: &mut Renderer, _defaults: &Defaults, layout: Layout<'_>, - _cursor_position: Point, + cursor_position: Point, ) -> (Primitive, MouseCursor) { let bounds = layout.bounds(); - let origin = Point::new(bounds.x, bounds.y); - let size = Size::new(bounds.width, bounds.height); + let translation = Vector::new(bounds.x, bounds.y); + let cursor = Cursor::from_window_position(cursor_position); ( - Primitive::Group { - primitives: self - .layers - .iter() - .map(|layer| Primitive::Cached { - origin, - cache: layer.draw(size), - }) - .collect(), + Primitive::Translate { + translation, + content: Box::new(Primitive::Group { + primitives: self + .program + .draw(bounds, cursor) + .into_iter() + .map(Geometry::into_primitive) + .collect(), + }), }, - MouseCursor::Idle, + self.program.mouse_cursor(bounds, cursor), ) } fn hash_layout(&self, state: &mut Hasher) { - std::any::TypeId::of::<Canvas<'static>>().hash(state); + struct Marker; + std::any::TypeId::of::<Marker>().hash(state); self.width.hash(state); self.height.hash(state); } } -impl<'a, Message> From<Canvas<'a>> for Element<'a, Message, Renderer> +impl<'a, Message, P: Program<Message> + 'a> From<Canvas<Message, P>> + for Element<'a, Message, Renderer> where Message: 'static, { - fn from(canvas: Canvas<'a>) -> Element<'a, Message, Renderer> { + fn from(canvas: Canvas<Message, P>) -> Element<'a, Message, Renderer> { Element::new(canvas) } } diff --git a/wgpu/src/widget/canvas/cache.rs b/wgpu/src/widget/canvas/cache.rs new file mode 100644 index 00000000..03643f74 --- /dev/null +++ b/wgpu/src/widget/canvas/cache.rs @@ -0,0 +1,96 @@ +use crate::{ + canvas::{Frame, Geometry}, + Primitive, +}; + +use iced_native::Size; +use std::{cell::RefCell, sync::Arc}; + +enum State { + Empty, + Filled { + bounds: Size, + primitive: Arc<Primitive>, + }, +} + +impl Default for State { + fn default() -> Self { + State::Empty + } +} +/// 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 +/// [`Cache`]: struct.Cache.html +#[derive(Debug, Default)] +pub struct Cache { + state: RefCell<State>, +} + +impl Cache { + /// Creates a new empty [`Cache`]. + /// + /// [`Cache`]: struct.Cache.html + pub fn new() -> Self { + Cache { + state: Default::default(), + } + } + + /// Clears the cache, forcing a redraw the next time it is used. + /// + /// [`Cached`]: struct.Cached.html + pub fn clear(&mut self) { + *self.state.borrow_mut() = State::Empty; + } + + pub fn draw( + &self, + new_bounds: Size, + draw_fn: impl Fn(&mut Frame), + ) -> Geometry { + use std::ops::Deref; + + if let State::Filled { bounds, primitive } = self.state.borrow().deref() + { + if *bounds == new_bounds { + return Geometry::from_primitive(Primitive::Cached { + cache: primitive.clone(), + }); + } + } + + let mut frame = Frame::new(new_bounds); + draw_fn(&mut frame); + + let primitive = { + let geometry = frame.into_geometry(); + + Arc::new(geometry.into_primitive()) + }; + + *self.state.borrow_mut() = State::Filled { + bounds: new_bounds, + primitive: primitive.clone(), + }; + + Geometry::from_primitive(Primitive::Cached { cache: primitive }) + } +} + +impl std::fmt::Debug for State { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + State::Empty => write!(f, "Empty"), + State::Filled { primitive, bounds } => f + .debug_struct("Filled") + .field("primitive", primitive) + .field("bounds", bounds) + .finish(), + } + } +} diff --git a/wgpu/src/widget/canvas/cursor.rs b/wgpu/src/widget/canvas/cursor.rs new file mode 100644 index 00000000..a559782a --- /dev/null +++ b/wgpu/src/widget/canvas/cursor.rs @@ -0,0 +1,50 @@ +use iced_native::{Point, Rectangle}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Cursor { + Available(Point), + Unavailable, +} + +impl Cursor { + // TODO: Remove this once this type is used in `iced_native` to encode + // proper cursor availability + pub(crate) fn from_window_position(position: Point) -> Self { + if position.x < 0.0 || position.y < 0.0 { + Cursor::Unavailable + } else { + Cursor::Available(position) + } + } + + pub fn position(&self) -> Option<Point> { + match self { + Cursor::Available(position) => Some(*position), + Cursor::Unavailable => None, + } + } + + pub fn relative_position(&self, bounds: &Rectangle) -> Option<Point> { + match self { + Cursor::Available(position) => { + Some(Point::new(position.x - bounds.x, position.y - bounds.y)) + } + _ => None, + } + } + + pub fn internal_position(&self, bounds: &Rectangle) -> Option<Point> { + if self.is_over(bounds) { + self.relative_position(bounds) + } else { + None + } + } + + pub fn is_over(&self, bounds: &Rectangle) -> bool { + match self { + Cursor::Available(position) => bounds.contains(*position), + Cursor::Unavailable => false, + } + } +} diff --git a/wgpu/src/widget/canvas/drawable.rs b/wgpu/src/widget/canvas/drawable.rs deleted file mode 100644 index 6c74071c..00000000 --- a/wgpu/src/widget/canvas/drawable.rs +++ /dev/null @@ -1,12 +0,0 @@ -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/event.rs b/wgpu/src/widget/canvas/event.rs new file mode 100644 index 00000000..7a8b0829 --- /dev/null +++ b/wgpu/src/widget/canvas/event.rs @@ -0,0 +1,6 @@ +use iced_native::input::mouse; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Event { + Mouse(mouse::Event), +} diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index de4717f1..1c4a038a 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -1,7 +1,7 @@ use iced_native::{Point, Rectangle, Size, Vector}; use crate::{ - canvas::{Fill, Path, Stroke, Text}, + canvas::{Fill, Geometry, Path, Stroke, Text}, triangle, Primitive, }; @@ -10,8 +10,7 @@ use crate::{ /// [`Canvas`]: struct.Canvas.html #[derive(Debug)] pub struct Frame { - width: f32, - height: f32, + size: Size, buffers: lyon::tessellation::VertexBuffers<triangle::Vertex2D, u32>, primitives: Vec<Primitive>, transforms: Transforms, @@ -36,10 +35,9 @@ impl Frame { /// top-left corner of its bounds. /// /// [`Frame`]: struct.Frame.html - pub fn new(width: f32, height: f32) -> Frame { + pub fn new(size: Size) -> Frame { Frame { - width, - height, + size, buffers: lyon::tessellation::VertexBuffers::new(), primitives: Vec::new(), transforms: Transforms { @@ -57,7 +55,7 @@ impl Frame { /// [`Frame`]: struct.Frame.html #[inline] pub fn width(&self) -> f32 { - self.width + self.size.width } /// Returns the width of the [`Frame`]. @@ -65,7 +63,7 @@ impl Frame { /// [`Frame`]: struct.Frame.html #[inline] pub fn height(&self) -> f32 { - self.height + self.size.height } /// Returns the dimensions of the [`Frame`]. @@ -73,7 +71,7 @@ impl Frame { /// [`Frame`]: struct.Frame.html #[inline] pub fn size(&self) -> Size { - Size::new(self.width, self.height) + self.size } /// Returns the coordinate of the center of the [`Frame`]. @@ -81,7 +79,7 @@ impl Frame { /// [`Frame`]: struct.Frame.html #[inline] pub fn center(&self) -> Point { - Point::new(self.width / 2.0, self.height / 2.0) + Point::new(self.size.width / 2.0, self.size.height / 2.0) } /// Draws the given [`Path`] on the [`Frame`] by filling it with the @@ -262,13 +260,14 @@ impl Frame { self.transforms.current.is_identity = false; } - /// Produces the primitive representing everything drawn on the [`Frame`]. + /// Produces the [`Geometry`] representing everything drawn on the [`Frame`]. /// /// [`Frame`]: struct.Frame.html - pub fn into_primitive(mut self) -> Primitive { + /// [`Geometry`]: struct.Geometry.html + pub fn into_geometry(mut self) -> Geometry { if !self.buffers.indices.is_empty() { self.primitives.push(Primitive::Mesh2D { - origin: Point::ORIGIN, + size: self.size, buffers: triangle::Mesh2D { vertices: self.buffers.vertices, indices: self.buffers.indices, @@ -276,9 +275,9 @@ impl Frame { }); } - Primitive::Group { + Geometry::from_primitive(Primitive::Group { primitives: self.primitives, - } + }) } } diff --git a/wgpu/src/widget/canvas/geometry.rs b/wgpu/src/widget/canvas/geometry.rs new file mode 100644 index 00000000..12ef828f --- /dev/null +++ b/wgpu/src/widget/canvas/geometry.rs @@ -0,0 +1,20 @@ +use crate::Primitive; + +#[derive(Debug, Clone)] +pub struct Geometry(Primitive); + +impl Geometry { + pub(crate) fn from_primitive(primitive: Primitive) -> Self { + Self(primitive) + } + + pub fn into_primitive(self) -> Primitive { + self.0 + } +} + +impl From<Geometry> for Primitive { + fn from(geometry: Geometry) -> Primitive { + geometry.0 + } +} diff --git a/wgpu/src/widget/canvas/layer.rs b/wgpu/src/widget/canvas/layer.rs deleted file mode 100644 index a46b7fb1..00000000 --- a/wgpu/src/widget/canvas/layer.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Produce, store, and reuse geometry. -mod cache; - -pub use cache::Cache; - -use crate::Primitive; -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 a [`Primitive`] as - /// a result. - /// - /// The [`Layer`] may choose to store the produced [`Primitive`] locally and - /// only recompute it when the bounds change, its contents change, or is - /// otherwise explicitly cleared by other means. - /// - /// [`Layer`]: trait.Layer.html - /// [`Primitive`]: ../../../enum.Primitive.html - fn draw(&self, bounds: Size) -> Arc<Primitive>; -} diff --git a/wgpu/src/widget/canvas/layer/cache.rs b/wgpu/src/widget/canvas/layer/cache.rs deleted file mode 100644 index 4f8c2bec..00000000 --- a/wgpu/src/widget/canvas/layer/cache.rs +++ /dev/null @@ -1,128 +0,0 @@ -use crate::{ - canvas::{Drawable, Frame, Layer}, - Primitive, -}; - -use iced_native::Size; -use std::{cell::RefCell, marker::PhantomData, sync::Arc}; - -enum State { - Empty, - Filled { - bounds: Size, - primitive: Arc<Primitive>, - }, -} - -impl Default for State { - fn default() -> Self { - State::Empty - } -} -/// 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 -/// [`Cache`]: struct.Cache.html -#[derive(Debug)] -pub struct Cache<T: Drawable> { - input: PhantomData<T>, - state: RefCell<State>, -} - -impl<T> Default for Cache<T> -where - T: Drawable, -{ - fn default() -> Self { - Self { - input: PhantomData, - state: Default::default(), - } - } -} - -impl<T> Cache<T> -where - T: Drawable + std::fmt::Debug, -{ - /// Creates a new empty [`Cache`]. - /// - /// [`Cache`]: struct.Cache.html - pub fn new() -> Self { - Cache { - input: PhantomData, - state: Default::default(), - } - } - - /// Clears the cache, forcing a redraw the next time it is used. - /// - /// [`Cached`]: struct.Cached.html - pub fn clear(&mut self) { - *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 { - cache: self, - input: input, - } - } -} - -#[derive(Debug)] -struct Bind<'a, T: Drawable> { - cache: &'a Cache<T>, - input: &'a T, -} - -impl<'a, T> Layer for Bind<'a, T> -where - T: Drawable + std::fmt::Debug, -{ - fn draw(&self, current_bounds: Size) -> Arc<Primitive> { - use std::ops::Deref; - - if let State::Filled { bounds, primitive } = - self.cache.state.borrow().deref() - { - if *bounds == current_bounds { - return primitive.clone(); - } - } - - let mut frame = Frame::new(current_bounds.width, current_bounds.height); - self.input.draw(&mut frame); - - let primitive = Arc::new(frame.into_primitive()); - - *self.cache.state.borrow_mut() = State::Filled { - bounds: current_bounds, - primitive: primitive.clone(), - }; - - primitive - } -} - -impl std::fmt::Debug for State { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - State::Empty => write!(f, "Empty"), - State::Filled { primitive, bounds } => f - .debug_struct("Filled") - .field("primitive", primitive) - .field("bounds", bounds) - .finish(), - } - } -} diff --git a/wgpu/src/widget/canvas/program.rs b/wgpu/src/widget/canvas/program.rs new file mode 100644 index 00000000..f8e54514 --- /dev/null +++ b/wgpu/src/widget/canvas/program.rs @@ -0,0 +1,41 @@ +use crate::canvas::{Cursor, Event, Geometry}; +use iced_native::{MouseCursor, Rectangle}; + +pub trait Program<Message> { + fn update( + &mut self, + _event: Event, + _bounds: Rectangle, + _cursor: Cursor, + ) -> Option<Message> { + None + } + + fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry>; + + fn mouse_cursor(&self, _bounds: Rectangle, _cursor: Cursor) -> MouseCursor { + MouseCursor::default() + } +} + +impl<T, Message> Program<Message> for &mut T +where + T: Program<Message>, +{ + fn update( + &mut self, + event: Event, + bounds: Rectangle, + cursor: Cursor, + ) -> Option<Message> { + T::update(self, event, bounds, cursor) + } + + fn draw(&self, bounds: Rectangle, cursor: Cursor) -> Vec<Geometry> { + T::draw(self, bounds, cursor) + } + + fn mouse_cursor(&self, bounds: Rectangle, cursor: Cursor) -> MouseCursor { + T::mouse_cursor(self, bounds, cursor) + } +} diff --git a/wgpu/src/widget/canvas/stroke.rs b/wgpu/src/widget/canvas/stroke.rs index 46d669c4..e20379cd 100644 --- a/wgpu/src/widget/canvas/stroke.rs +++ b/wgpu/src/widget/canvas/stroke.rs @@ -14,6 +14,24 @@ pub struct Stroke { pub line_join: LineJoin, } +impl Stroke { + pub fn with_color(self, color: Color) -> Stroke { + Stroke { color, ..self } + } + + pub fn with_width(self, width: f32) -> Stroke { + Stroke { width, ..self } + } + + pub fn with_line_cap(self, line_cap: LineCap) -> Stroke { + Stroke { line_cap, ..self } + } + + pub fn with_line_join(self, line_join: LineJoin) -> Stroke { + Stroke { line_join, ..self } + } +} + impl Default for Stroke { fn default() -> Stroke { Stroke { |