From 267e242238fab0aba14fb4c2e27269ce3a3e3951 Mon Sep 17 00:00:00 2001 From: Nikolai Vazquez Date: Fri, 29 Nov 2019 21:24:52 -0500 Subject: Make many functions `const` The point is to set up repeated components or boilerplate before their use sites. The majority of these make sense as `const`. However, some functions such as those regarding state may not make sense as `const`. --- core/src/point.rs | 2 +- core/src/vector.rs | 2 +- native/src/layout/limits.rs | 6 +++--- native/src/layout/node.rs | 8 ++++---- native/src/widget/column.rs | 16 ++++++++-------- native/src/widget/image.rs | 4 ++-- native/src/widget/row.rs | 16 ++++++++-------- native/src/widget/slider.rs | 4 ++-- native/src/widget/text_input.rs | 11 +++++++---- web/src/widget/button.rs | 4 ++-- web/src/widget/column.rs | 16 ++++++++-------- web/src/widget/row.rs | 16 ++++++++-------- web/src/widget/scrollable.rs | 4 ++-- web/src/widget/slider.rs | 2 +- web/src/widget/text_input.rs | 4 ++-- wgpu/src/renderer.rs | 2 +- 16 files changed, 60 insertions(+), 57 deletions(-) diff --git a/core/src/point.rs b/core/src/point.rs index 183998dd..52307bba 100644 --- a/core/src/point.rs +++ b/core/src/point.rs @@ -14,7 +14,7 @@ impl Point { /// 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/core/src/vector.rs b/core/src/vector.rs index 7d87343a..e0c5f073 100644 --- a/core/src/vector.rs +++ b/core/src/vector.rs @@ -16,7 +16,7 @@ impl Vector { /// Creates a new [`Vector`] with the given components. /// /// [`Vector`]: struct.Vector.html - pub fn new(x: T, y: T) -> Self { + pub const fn new(x: T, y: T) -> Self { Self { x, y } } } diff --git a/native/src/layout/limits.rs b/native/src/layout/limits.rs index 2705a47d..5f456871 100644 --- a/native/src/layout/limits.rs +++ b/native/src/layout/limits.rs @@ -20,7 +20,7 @@ impl Limits { /// /// [`Limits`]: struct.Limits.html /// [`Size`]: ../struct.Size.html - pub fn new(min: Size, max: Size) -> Limits { + pub const fn new(min: Size, max: Size) -> Limits { Limits { min, max, @@ -32,7 +32,7 @@ impl Limits { /// /// [`Limits`]: struct.Limits.html /// [`Size`]: ../struct.Size.html - pub fn min(&self) -> Size { + pub const fn min(&self) -> Size { self.min } @@ -40,7 +40,7 @@ impl Limits { /// /// [`Limits`]: struct.Limits.html /// [`Size`]: ../struct.Size.html - pub fn max(&self) -> Size { + pub const fn max(&self) -> Size { self.max } diff --git a/native/src/layout/node.rs b/native/src/layout/node.rs index ed1cd3da..3b63914e 100644 --- a/native/src/layout/node.rs +++ b/native/src/layout/node.rs @@ -12,7 +12,7 @@ impl Node { /// /// [`Node`]: struct.Node.html /// [`Size`]: ../struct.Size.html - pub fn new(size: Size) -> Self { + pub const fn new(size: Size) -> Self { Self::with_children(size, Vec::new()) } @@ -20,7 +20,7 @@ impl Node { /// /// [`Node`]: struct.Node.html /// [`Size`]: ../struct.Size.html - pub fn with_children(size: Size, children: Vec) -> Self { + pub const fn with_children(size: Size, children: Vec) -> Self { Node { bounds: Rectangle { x: 0.0, @@ -36,14 +36,14 @@ impl Node { /// /// [`Node`]: struct.Node.html /// [`Size`]: ../struct.Size.html - pub fn size(&self) -> Size { + pub const fn size(&self) -> Size { Size::new(self.bounds.width, self.bounds.height) } /// Returns the bounds of the [`Node`]. /// /// [`Node`]: struct.Node.html - pub fn bounds(&self) -> Rectangle { + pub const fn bounds(&self) -> Rectangle { self.bounds } diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index cdcf25af..104fdb94 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -28,7 +28,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Creates an empty [`Column`]. /// /// [`Column`]: struct.Column.html - pub fn new() -> Self { + pub const fn new() -> Self { Column { spacing: 0, padding: 0, @@ -46,7 +46,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Custom margins per element do not exist in Iced. You should use this /// method instead! While less flexible, it helps you keep spacing between /// elements consistent. - pub fn spacing(mut self, units: u16) -> Self { + pub const fn spacing(mut self, units: u16) -> Self { self.spacing = units; self } @@ -54,7 +54,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Sets the padding of the [`Column`]. /// /// [`Column`]: struct.Column.html - pub fn padding(mut self, units: u16) -> Self { + pub const fn padding(mut self, units: u16) -> Self { self.padding = units; self } @@ -62,7 +62,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Sets the width of the [`Column`]. /// /// [`Column`]: struct.Column.html - pub fn width(mut self, width: Length) -> Self { + pub const fn width(mut self, width: Length) -> Self { self.width = width; self } @@ -70,7 +70,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Sets the height of the [`Column`]. /// /// [`Column`]: struct.Column.html - pub fn height(mut self, height: Length) -> Self { + pub const fn height(mut self, height: Length) -> Self { self.height = height; self } @@ -78,7 +78,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Sets the maximum width of the [`Column`]. /// /// [`Column`]: struct.Column.html - pub fn max_width(mut self, max_width: u32) -> Self { + pub const fn max_width(mut self, max_width: u32) -> Self { self.max_width = max_width; self } @@ -86,7 +86,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Sets the maximum height of the [`Column`] in pixels. /// /// [`Column`]: struct.Column.html - pub fn max_height(mut self, max_height: u32) -> Self { + pub const fn max_height(mut self, max_height: u32) -> Self { self.max_height = max_height; self } @@ -94,7 +94,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Sets the horizontal alignment of the contents of the [`Column`] . /// /// [`Column`]: struct.Column.html - pub fn align_items(mut self, align: Align) -> Self { + pub const fn align_items(mut self, align: Align) -> Self { self.align_items = align; self } diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 4c588c9d..5cfe074f 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -37,7 +37,7 @@ impl Image { /// Sets the width of the [`Image`] boundaries. /// /// [`Image`]: struct.Image.html - pub fn width(mut self, width: Length) -> Self { + pub const fn width(mut self, width: Length) -> Self { self.width = width; self } @@ -45,7 +45,7 @@ impl Image { /// Sets the height of the [`Image`] boundaries. /// /// [`Image`]: struct.Image.html - pub fn height(mut self, height: Length) -> Self { + pub const fn height(mut self, height: Length) -> Self { self.height = height; self } diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index c854aff7..e9b8654c 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -28,7 +28,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Creates an empty [`Row`]. /// /// [`Row`]: struct.Row.html - pub fn new() -> Self { + pub const fn new() -> Self { Row { spacing: 0, padding: 0, @@ -46,7 +46,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Custom margins per element do not exist in Iced. You should use this /// method instead! While less flexible, it helps you keep spacing between /// elements consistent. - pub fn spacing(mut self, units: u16) -> Self { + pub const fn spacing(mut self, units: u16) -> Self { self.spacing = units; self } @@ -54,7 +54,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Sets the padding of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub fn padding(mut self, units: u16) -> Self { + pub const fn padding(mut self, units: u16) -> Self { self.padding = units; self } @@ -62,7 +62,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Sets the width of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub fn width(mut self, width: Length) -> Self { + pub const fn width(mut self, width: Length) -> Self { self.width = width; self } @@ -70,7 +70,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Sets the height of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub fn height(mut self, height: Length) -> Self { + pub const fn height(mut self, height: Length) -> Self { self.height = height; self } @@ -78,7 +78,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Sets the maximum width of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub fn max_width(mut self, max_width: u32) -> Self { + pub const fn max_width(mut self, max_width: u32) -> Self { self.max_width = max_width; self } @@ -86,7 +86,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Sets the maximum height of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub fn max_height(mut self, max_height: u32) -> Self { + pub const fn max_height(mut self, max_height: u32) -> Self { self.max_height = max_height; self } @@ -94,7 +94,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Sets the vertical alignment of the contents of the [`Row`] . /// /// [`Row`]: struct.Row.html - pub fn align_items(mut self, align: Align) -> Self { + pub const fn align_items(mut self, align: Align) -> Self { self.align_items = align; self } diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index f07ea7cd..a8915da1 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -95,8 +95,8 @@ impl State { /// Creates a new [`State`]. /// /// [`State`]: struct.State.html - pub fn new() -> State { - State::default() + pub const fn new() -> State { + State { is_dragging: false } } } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index f97ed424..0246f0d5 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -326,14 +326,17 @@ impl State { /// Creates a new [`State`], representing an unfocused [`TextInput`]. /// /// [`State`]: struct.State.html - pub fn new() -> Self { - Self::default() + pub const fn new() -> Self { + Self { + is_focused: false, + cursor_position: 0, + } } /// Creates a new [`State`], representing a focused [`TextInput`]. /// /// [`State`]: struct.State.html - pub fn focused() -> Self { + pub const fn focused() -> Self { use std::usize; Self { @@ -345,7 +348,7 @@ impl State { /// Returns whether the [`TextInput`] is currently focused or not. /// /// [`TextInput`]: struct.TextInput.html - pub fn is_focused(&self) -> bool { + pub const fn is_focused(&self) -> bool { self.is_focused } diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs index 889c0ab1..13b47077 100644 --- a/web/src/widget/button.rs +++ b/web/src/widget/button.rs @@ -113,8 +113,8 @@ impl State { /// Creates a new [`State`]. /// /// [`State`]: struct.State.html - pub fn new() -> State { - State::default() + pub const fn new() -> State { + State } } diff --git a/web/src/widget/column.rs b/web/src/widget/column.rs index cc850f5f..82b533dd 100644 --- a/web/src/widget/column.rs +++ b/web/src/widget/column.rs @@ -24,7 +24,7 @@ impl<'a, Message> Column<'a, Message> { /// Creates an empty [`Column`]. /// /// [`Column`]: struct.Column.html - pub fn new() -> Self { + pub const fn new() -> Self { Column { spacing: 0, padding: 0, @@ -42,7 +42,7 @@ impl<'a, Message> Column<'a, Message> { /// Custom margins per element do not exist in Iced. You should use this /// method instead! While less flexible, it helps you keep spacing between /// elements consistent. - pub fn spacing(mut self, units: u16) -> Self { + pub const fn spacing(mut self, units: u16) -> Self { self.spacing = units; self } @@ -50,7 +50,7 @@ impl<'a, Message> Column<'a, Message> { /// Sets the padding of the [`Column`]. /// /// [`Column`]: struct.Column.html - pub fn padding(mut self, units: u16) -> Self { + pub const fn padding(mut self, units: u16) -> Self { self.padding = units; self } @@ -58,7 +58,7 @@ impl<'a, Message> Column<'a, Message> { /// Sets the width of the [`Column`]. /// /// [`Column`]: struct.Column.html - pub fn width(mut self, width: Length) -> Self { + pub const fn width(mut self, width: Length) -> Self { self.width = width; self } @@ -66,7 +66,7 @@ impl<'a, Message> Column<'a, Message> { /// Sets the height of the [`Column`]. /// /// [`Column`]: struct.Column.html - pub fn height(mut self, height: Length) -> Self { + pub const fn height(mut self, height: Length) -> Self { self.height = height; self } @@ -74,7 +74,7 @@ impl<'a, Message> Column<'a, Message> { /// Sets the maximum width of the [`Column`]. /// /// [`Column`]: struct.Column.html - pub fn max_width(mut self, max_width: u32) -> Self { + pub const fn max_width(mut self, max_width: u32) -> Self { self.max_width = max_width; self } @@ -82,7 +82,7 @@ impl<'a, Message> Column<'a, Message> { /// Sets the maximum height of the [`Column`] in pixels. /// /// [`Column`]: struct.Column.html - pub fn max_height(mut self, max_height: u32) -> Self { + pub const fn max_height(mut self, max_height: u32) -> Self { self.max_height = max_height; self } @@ -90,7 +90,7 @@ impl<'a, Message> Column<'a, Message> { /// Sets the horizontal alignment of the contents of the [`Column`] . /// /// [`Column`]: struct.Column.html - pub fn align_items(mut self, align: Align) -> Self { + pub const fn align_items(mut self, align: Align) -> Self { self.align_items = align; self } diff --git a/web/src/widget/row.rs b/web/src/widget/row.rs index e47478be..0b4cec54 100644 --- a/web/src/widget/row.rs +++ b/web/src/widget/row.rs @@ -24,7 +24,7 @@ impl<'a, Message> Row<'a, Message> { /// Creates an empty [`Row`]. /// /// [`Row`]: struct.Row.html - pub fn new() -> Self { + pub const fn new() -> Self { Row { spacing: 0, padding: 0, @@ -42,7 +42,7 @@ impl<'a, Message> Row<'a, Message> { /// Custom margins per element do not exist in Iced. You should use this /// method instead! While less flexible, it helps you keep spacing between /// elements consistent. - pub fn spacing(mut self, units: u16) -> Self { + pub const fn spacing(mut self, units: u16) -> Self { self.spacing = units; self } @@ -50,7 +50,7 @@ impl<'a, Message> Row<'a, Message> { /// Sets the padding of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub fn padding(mut self, units: u16) -> Self { + pub const fn padding(mut self, units: u16) -> Self { self.padding = units; self } @@ -58,7 +58,7 @@ impl<'a, Message> Row<'a, Message> { /// Sets the width of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub fn width(mut self, width: Length) -> Self { + pub const fn width(mut self, width: Length) -> Self { self.width = width; self } @@ -66,7 +66,7 @@ impl<'a, Message> Row<'a, Message> { /// Sets the height of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub fn height(mut self, height: Length) -> Self { + pub const fn height(mut self, height: Length) -> Self { self.height = height; self } @@ -74,7 +74,7 @@ impl<'a, Message> Row<'a, Message> { /// Sets the maximum width of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub fn max_width(mut self, max_width: u32) -> Self { + pub const fn max_width(mut self, max_width: u32) -> Self { self.max_width = max_width; self } @@ -82,7 +82,7 @@ impl<'a, Message> Row<'a, Message> { /// Sets the maximum height of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub fn max_height(mut self, max_height: u32) -> Self { + pub const fn max_height(mut self, max_height: u32) -> Self { self.max_height = max_height; self } @@ -90,7 +90,7 @@ impl<'a, Message> Row<'a, Message> { /// Sets the vertical alignment of the contents of the [`Row`] . /// /// [`Row`]: struct.Row.html - pub fn align_items(mut self, align: Align) -> Self { + pub const fn align_items(mut self, align: Align) -> Self { self.align_items = align; self } diff --git a/web/src/widget/scrollable.rs b/web/src/widget/scrollable.rs index 710bb70a..65766d74 100644 --- a/web/src/widget/scrollable.rs +++ b/web/src/widget/scrollable.rs @@ -151,7 +151,7 @@ impl State { /// Creates a new [`State`] with the scrollbar located at the top. /// /// [`State`]: struct.State.html - pub fn new() -> Self { - State::default() + pub const fn new() -> Self { + State } } diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs index 5b203e07..55848084 100644 --- a/web/src/widget/slider.rs +++ b/web/src/widget/slider.rs @@ -147,7 +147,7 @@ impl State { /// Creates a new [`State`]. /// /// [`State`]: struct.State.html - pub fn new() -> Self { + pub const fn new() -> Self { Self } } diff --git a/web/src/widget/text_input.rs b/web/src/widget/text_input.rs index d6357512..04c3b287 100644 --- a/web/src/widget/text_input.rs +++ b/web/src/widget/text_input.rs @@ -191,7 +191,7 @@ impl State { /// Creates a new [`State`], representing an unfocused [`TextInput`]. /// /// [`State`]: struct.State.html - pub fn new() -> Self { - Self::default() + pub const fn new() -> Self { + Self } } diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index f27a4b8a..da2c0d8c 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -35,7 +35,7 @@ struct Layer<'a> { } impl<'a> Layer<'a> { - pub fn new(bounds: Rectangle, offset: Vector) -> Self { + pub const fn new(bounds: Rectangle, offset: Vector) -> Self { Self { bounds, offset, -- cgit From 126133ead775fda064a6c23503e9a552a10dc2c5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 22 Feb 2020 18:03:49 +0100 Subject: Fix `Clip` primitive intersection in `iced_wgpu` --- core/src/rectangle.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ wgpu/src/renderer.rs | 29 +++++++++++------------------ 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/core/src/rectangle.rs b/core/src/rectangle.rs index ee1e3807..7ed3d2df 100644 --- a/core/src/rectangle.rs +++ b/core/src/rectangle.rs @@ -27,6 +27,34 @@ impl Rectangle { && self.y <= point.y && point.y <= self.y + self.height } + + /// Computes the intersection with the given [`Rectangle`]. + /// + /// [`Rectangle`]: struct.Rectangle.html + pub fn intersection( + &self, + other: &Rectangle, + ) -> Option> { + let x = self.x.max(other.x); + let y = self.y.max(other.y); + + let lower_right_x = (self.x + self.width).min(other.x + other.width); + let lower_right_y = (self.y + self.height).min(other.y + other.height); + + let width = lower_right_x - x; + let height = lower_right_y - y; + + if width > 0.0 && height > 0.0 { + Some(Rectangle { + x, + y, + width, + height, + }) + } else { + None + } + } } impl std::ops::Mul for Rectangle { @@ -41,3 +69,25 @@ impl std::ops::Mul for Rectangle { } } } + +impl From> for Rectangle { + fn from(rectangle: Rectangle) -> Rectangle { + Rectangle { + x: rectangle.x as f32, + y: rectangle.y as f32, + width: rectangle.width as f32, + height: rectangle.height as f32, + } + } +} + +impl From> for Rectangle { + fn from(rectangle: Rectangle) -> Rectangle { + Rectangle { + x: rectangle.x as u32, + y: rectangle.y as u32, + width: rectangle.width.ceil() as u32, + height: rectangle.height.ceil() as u32, + } + } +} diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 29adcfb6..af61804e 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -240,25 +240,18 @@ impl Renderer { offset, content, } => { - let x = bounds.x - layer.offset.x as f32; - let y = bounds.y - layer.offset.y as f32; - let width = (bounds.width + x).min(bounds.width); - let height = (bounds.height + y).min(bounds.height); - - // Only draw visible content on-screen - // TODO: Also, check for parent layer bounds to avoid further - // drawing in some circumstances. - if width > 0.0 && height > 0.0 { - let clip_layer = Layer::new( - Rectangle { - x: x.max(0.0).floor() as u32, - y: y.max(0.0).floor() as u32, - width: width.ceil() as u32, - height: height.ceil() as u32, - }, - layer.offset + *offset, - ); + let layer_bounds: Rectangle = layer.bounds.into(); + let clip = Rectangle { + x: bounds.x - layer.offset.x as f32, + y: bounds.y - layer.offset.y as f32, + ..*bounds + }; + + // Only draw visible content + if let Some(clip_bounds) = layer_bounds.intersection(&clip) { + let clip_layer = + Layer::new(clip_bounds.into(), layer.offset + *offset); let new_layer = Layer::new(layer.bounds, layer.offset); layers.push(clip_layer); -- cgit From be14aca07506385a209e89cd99256744a7ec3c0f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 24 Feb 2020 20:08:40 +0100 Subject: Make output format of `iced_wgpu` configurable --- examples/integration/src/main.rs | 4 +++- src/application.rs | 1 + wgpu/src/image.rs | 4 ++-- wgpu/src/quad.rs | 7 +++++-- wgpu/src/renderer.rs | 15 ++++++++++----- wgpu/src/settings.rs | 17 ++++++++++++++++- wgpu/src/text.rs | 8 ++++++-- wgpu/src/triangle.rs | 5 +++-- wgpu/src/triangle/msaa.rs | 12 +++++++++--- wgpu/src/window/backend.rs | 12 ++++++++++-- wgpu/src/window/swap_chain.rs | 6 ++++-- 11 files changed, 69 insertions(+), 22 deletions(-) diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index ed36f736..4be913c1 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -40,11 +40,12 @@ pub fn main() { }); let surface = wgpu::Surface::create(&window); + let format = wgpu::TextureFormat::Bgra8UnormSrgb; let mut swap_chain = { let size = window.inner_size(); - SwapChain::new(&device, &surface, size.width, size.height) + SwapChain::new(&device, &surface, format, size.width, size.height) }; let mut resized = false; @@ -163,6 +164,7 @@ pub fn main() { swap_chain = SwapChain::new( &device, &surface, + format, size.width, size.height, ); diff --git a/src/application.rs b/src/application.rs index 374810cb..2ee3337f 100644 --- a/src/application.rs +++ b/src/application.rs @@ -183,6 +183,7 @@ pub trait Application: Sized { } else { None }, + ..iced_wgpu::Settings::default() }, ); diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index ccc956a9..f7ed67c3 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -28,7 +28,7 @@ pub struct Pipeline { } impl Pipeline { - pub fn new(device: &wgpu::Device) -> Self { + pub fn new(device: &wgpu::Device, format: wgpu::TextureFormat) -> Self { let sampler = device.create_sampler(&wgpu::SamplerDescriptor { address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, @@ -135,7 +135,7 @@ impl Pipeline { }), primitive_topology: wgpu::PrimitiveTopology::TriangleList, color_states: &[wgpu::ColorStateDescriptor { - format: wgpu::TextureFormat::Bgra8UnormSrgb, + format, color_blend: wgpu::BlendDescriptor { src_factor: wgpu::BlendFactor::SrcAlpha, dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, diff --git a/wgpu/src/quad.rs b/wgpu/src/quad.rs index fe3276a3..9047080d 100644 --- a/wgpu/src/quad.rs +++ b/wgpu/src/quad.rs @@ -14,7 +14,10 @@ pub struct Pipeline { } impl Pipeline { - pub fn new(device: &mut wgpu::Device) -> Pipeline { + pub fn new( + device: &mut wgpu::Device, + format: wgpu::TextureFormat, + ) -> Pipeline { let constant_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { bindings: &[wgpu::BindGroupLayoutBinding { @@ -79,7 +82,7 @@ impl Pipeline { }), primitive_topology: wgpu::PrimitiveTopology::TriangleList, color_states: &[wgpu::ColorStateDescriptor { - format: wgpu::TextureFormat::Bgra8UnormSrgb, + format, color_blend: wgpu::BlendDescriptor { src_factor: wgpu::BlendFactor::SrcAlpha, dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index af61804e..f67dd1eb 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -48,11 +48,16 @@ impl Renderer { /// /// [`Renderer`]: struct.Renderer.html pub fn new(device: &mut wgpu::Device, settings: Settings) -> Self { - 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, settings.antialiasing); + let text_pipeline = + text::Pipeline::new(device, settings.format, settings.default_font); + let quad_pipeline = quad::Pipeline::new(device, settings.format); + let image_pipeline = + crate::image::Pipeline::new(device, settings.format); + let triangle_pipeline = triangle::Pipeline::new( + device, + settings.format, + settings.antialiasing, + ); Self { quad_pipeline, diff --git a/wgpu/src/settings.rs b/wgpu/src/settings.rs index 65853ce2..f946ce0d 100644 --- a/wgpu/src/settings.rs +++ b/wgpu/src/settings.rs @@ -5,8 +5,13 @@ /// The settings of a [`Renderer`]. /// /// [`Renderer`]: ../struct.Renderer.html -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Settings { + /// The output format of the [`Renderer`]. + /// + /// [`Renderer`]: ../struct.Renderer.html + pub format: wgpu::TextureFormat, + /// The bytes of the font that will be used by default. /// /// If `None` is provided, a default system font will be chosen. @@ -16,6 +21,16 @@ pub struct Settings { pub antialiasing: Option, } +impl Default for Settings { + fn default() -> Settings { + Settings { + format: wgpu::TextureFormat::Bgra8UnormSrgb, + default_font: None, + antialiasing: None, + } + } +} + /// An antialiasing strategy. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Antialiasing { diff --git a/wgpu/src/text.rs b/wgpu/src/text.rs index ab9a2f71..c5670102 100644 --- a/wgpu/src/text.rs +++ b/wgpu/src/text.rs @@ -22,7 +22,11 @@ pub struct Pipeline { } impl Pipeline { - pub fn new(device: &mut wgpu::Device, default_font: Option<&[u8]>) -> Self { + pub fn new( + device: &mut wgpu::Device, + format: wgpu::TextureFormat, + default_font: Option<&[u8]>, + ) -> Self { // TODO: Font customization let font_source = font::Source::new(); @@ -54,7 +58,7 @@ impl Pipeline { let draw_brush = brush_builder .initial_cache_size((2048, 2048)) - .build(device, wgpu::TextureFormat::Bgra8UnormSrgb); + .build(device, format); Pipeline { draw_brush: RefCell::new(draw_brush), diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index d149eebc..fe34040e 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -61,6 +61,7 @@ impl Buffer { impl Pipeline { pub fn new( device: &mut wgpu::Device, + format: wgpu::TextureFormat, antialiasing: Option, ) -> Pipeline { let constant_layout = @@ -127,7 +128,7 @@ impl Pipeline { }), primitive_topology: wgpu::PrimitiveTopology::TriangleList, color_states: &[wgpu::ColorStateDescriptor { - format: wgpu::TextureFormat::Bgra8UnormSrgb, + format, color_blend: wgpu::BlendDescriptor { src_factor: wgpu::BlendFactor::SrcAlpha, dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, @@ -169,7 +170,7 @@ impl Pipeline { Pipeline { pipeline, - blit: antialiasing.map(|a| msaa::Blit::new(device, a)), + blit: antialiasing.map(|a| msaa::Blit::new(device, format, a)), constants: constant_bind_group, uniforms_buffer: constants_buffer, vertex_buffer: Buffer::new( diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index 0def5352..7ccfb062 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -2,6 +2,7 @@ use crate::settings; #[derive(Debug)] pub struct Blit { + format: wgpu::TextureFormat, pipeline: wgpu::RenderPipeline, constants: wgpu::BindGroup, texture_layout: wgpu::BindGroupLayout, @@ -12,6 +13,7 @@ pub struct Blit { impl Blit { pub fn new( device: &wgpu::Device, + format: wgpu::TextureFormat, antialiasing: settings::Antialiasing, ) -> Blit { let sampler = device.create_sampler(&wgpu::SamplerDescriptor { @@ -93,7 +95,7 @@ impl Blit { }), primitive_topology: wgpu::PrimitiveTopology::TriangleList, color_states: &[wgpu::ColorStateDescriptor { - format: wgpu::TextureFormat::Bgra8UnormSrgb, + format, color_blend: wgpu::BlendDescriptor { src_factor: wgpu::BlendFactor::SrcAlpha, dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha, @@ -115,6 +117,7 @@ impl Blit { }); Blit { + format, pipeline, constants: constant_bind_group, texture_layout: texture_layout, @@ -133,6 +136,7 @@ impl Blit { None => { self.targets = Some(Targets::new( &device, + self.format, &self.texture_layout, self.sample_count, width, @@ -143,6 +147,7 @@ impl Blit { if targets.width != width || targets.height != height { self.targets = Some(Targets::new( &device, + self.format, &self.texture_layout, self.sample_count, width, @@ -204,6 +209,7 @@ struct Targets { impl Targets { pub fn new( device: &wgpu::Device, + format: wgpu::TextureFormat, texture_layout: &wgpu::BindGroupLayout, sample_count: u32, width: u32, @@ -221,7 +227,7 @@ impl Targets { mip_level_count: 1, sample_count, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bgra8UnormSrgb, + format, usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, }); @@ -231,7 +237,7 @@ impl Targets { mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bgra8UnormSrgb, + format, usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT | wgpu::TextureUsage::SAMPLED, }); diff --git a/wgpu/src/window/backend.rs b/wgpu/src/window/backend.rs index 4c9f289b..5b269f36 100644 --- a/wgpu/src/window/backend.rs +++ b/wgpu/src/window/backend.rs @@ -8,6 +8,7 @@ use raw_window_handle::HasRawWindowHandle; pub struct Backend { device: wgpu::Device, queue: wgpu::Queue, + format: wgpu::TextureFormat, } impl iced_native::window::Backend for Backend { @@ -37,7 +38,14 @@ impl iced_native::window::Backend for Backend { let renderer = Renderer::new(&mut device, settings); - (Backend { device, queue }, renderer) + ( + Backend { + device, + queue, + format: settings.format, + }, + renderer, + ) } fn create_surface( @@ -53,7 +61,7 @@ impl iced_native::window::Backend for Backend { width: u32, height: u32, ) -> SwapChain { - SwapChain::new(&self.device, surface, width, height) + SwapChain::new(&self.device, surface, self.format, width, height) } fn draw>( diff --git a/wgpu/src/window/swap_chain.rs b/wgpu/src/window/swap_chain.rs index 6f545fce..4ca2901b 100644 --- a/wgpu/src/window/swap_chain.rs +++ b/wgpu/src/window/swap_chain.rs @@ -18,11 +18,12 @@ impl SwapChain { pub fn new( device: &wgpu::Device, surface: &wgpu::Surface, + format: wgpu::TextureFormat, width: u32, height: u32, ) -> SwapChain { SwapChain { - raw: new_swap_chain(surface, width, height, device), + raw: new_swap_chain(surface, format, width, height, device), viewport: Viewport::new(width, height), } } @@ -38,6 +39,7 @@ impl SwapChain { fn new_swap_chain( surface: &wgpu::Surface, + format: wgpu::TextureFormat, width: u32, height: u32, device: &wgpu::Device, @@ -46,7 +48,7 @@ fn new_swap_chain( &surface, &wgpu::SwapChainDescriptor { usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, - format: wgpu::TextureFormat::Bgra8UnormSrgb, + format, width, height, present_mode: wgpu::PresentMode::Vsync, -- cgit From 1bcfc9a5cce0b30c3ad9983e407c06e237b491f3 Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Fri, 10 Jan 2020 14:39:29 +0100 Subject: Implemented a texture atlas for images and svgs. --- native/src/widget/image.rs | 2 +- wgpu/Cargo.toml | 1 + wgpu/src/image.rs | 177 ++++++++++++++++---------- wgpu/src/image/raster.rs | 283 +++++++++++++++++++++++++++-------------- wgpu/src/image/vector.rs | 198 +++++++++++++++++++++------- wgpu/src/shader/image.vert | 26 ++++ wgpu/src/shader/image.vert.spv | Bin 2136 -> 2324 bytes 7 files changed, 476 insertions(+), 211 deletions(-) create mode 100644 wgpu/src/shader/image.vert diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 200401f9..9b92c7f1 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -18,7 +18,7 @@ use std::{ /// ``` /// /// -#[derive(Debug)] +#[derive(Debug, Hash)] pub struct Image { handle: Handle, width: Length, diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 887c2d21..56839cf0 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -21,6 +21,7 @@ raw-window-handle = "0.3" glam = "0.8" font-kit = "0.4" log = "0.4" +guillotiere = "0.4" [dependencies.image] version = "0.22" diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index f7ed67c3..65780886 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -3,13 +3,16 @@ mod raster; #[cfg(feature = "svg")] mod vector; +#[cfg(feature = "image")] +use crate::image::raster::Memory; + use crate::Transformation; use iced_native::{image, svg, Rectangle}; use std::mem; #[cfg(any(feature = "image", feature = "svg"))] -use std::cell::RefCell; +use std::{cell::RefCell, collections::HashMap}; #[derive(Debug)] pub struct Pipeline { @@ -174,6 +177,16 @@ impl Pipeline { format: wgpu::VertexFormat::Float2, offset: 4 * 2, }, + wgpu::VertexAttributeDescriptor { + shader_location: 3, + format: wgpu::VertexFormat::Float2, + offset: 4 * 4, + }, + wgpu::VertexAttributeDescriptor { + shader_location: 4, + format: wgpu::VertexFormat::Float2, + offset: 4 * 6, + }, ], }, ], @@ -197,9 +210,10 @@ impl Pipeline { Pipeline { #[cfg(feature = "image")] - raster_cache: RefCell::new(raster::Cache::new()), + raster_cache: RefCell::new(raster::Cache::new(&device)), + #[cfg(feature = "svg")] - vector_cache: RefCell::new(vector::Cache::new()), + vector_cache: RefCell::new(vector::Cache::new(&device)), pipeline, uniforms: uniforms_buffer, @@ -251,50 +265,72 @@ impl Pipeline { std::mem::size_of::() as u64, ); - // TODO: Batch draw calls using a texture atlas - // Guillotière[1] by @nical can help us a lot here. - // - // [1]: https://github.com/nical/guillotiere - for image in instances { - let uploaded_texture = match &image.handle { + #[cfg(any(feature = "image", feature = "svg"))] + let mut recs = HashMap::new(); + + for (index, image) in instances.iter().enumerate() { + match &image.handle { Handle::Raster(_handle) => { #[cfg(feature = "image")] { - let mut cache = self.raster_cache.borrow_mut(); - let memory = cache.load(&_handle); + let mut raster_cache = self.raster_cache.borrow_mut(); - memory.upload(device, encoder, &self.texture_layout) - } + if let Memory::Device(allocation) = raster_cache.upload( + _handle, + device, + encoder) + { + let rec = allocation.rectangle; - #[cfg(not(feature = "image"))] - None + let _ = recs.insert(index, rec); + } + } } Handle::Vector(_handle) => { #[cfg(feature = "svg")] { - let mut cache = self.vector_cache.borrow_mut(); + let mut vector_cache = self.vector_cache.borrow_mut(); - cache.upload( + if let Some(allocation) = vector_cache.upload( _handle, image.scale, _scale, device, encoder, - &self.texture_layout, - ) - } + ) { + let rec = allocation.rectangle; - #[cfg(not(feature = "svg"))] - None + let _ = recs.insert(index, rec); + } + } } - }; + } + } + + #[cfg(feature = "image")] + let raster_atlas = self.raster_cache.borrow().atlas(device, &self.texture_layout); + + #[cfg(feature = "svg")] + let vector_atlas = self.vector_cache.borrow().atlas(device, &self.texture_layout); + + #[cfg(any(feature = "image", feature = "svg"))] + for (index, image) in instances.iter().enumerate() { + if let Some(rec) = recs.get(&index) { + let atlas_size = match image.handle { + #[cfg(feature = "image")] + Handle::Raster(_) => self.raster_cache.borrow().atlas_size(), + #[cfg(feature = "svg")] + Handle::Vector(_) => self.vector_cache.borrow().atlas_size(), + _ => guillotiere::Size::new(0, 0) + }; - if let Some(texture) = uploaded_texture { let instance_buffer = device .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) .fill_from_slice(&[Instance { _position: image.position, _scale: image.scale, + _position_in_atlas: [rec.min.x as f32 / atlas_size.width as f32, rec.min.y as f32 / atlas_size.height as f32], + _scale_in_atlas: [rec.size().width as f32 / atlas_size.width as f32, rec.size().height as f32 / atlas_size.height as f32] }]); encoder.copy_buffer_to_buffer( @@ -305,48 +341,57 @@ impl Pipeline { 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, - }, + let texture = match &image.handle { + #[cfg(feature = "image")] + Handle::Raster(_) => &raster_atlas, + #[cfg(feature = "svg")] + Handle::Vector(_) => &vector_atlas, + #[cfg(feature = "image")] + _ => &raster_atlas, + #[cfg(feature = "svg")] + _ => &vector_atlas, + }; + + 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, &texture, &[]); - render_pass.set_index_buffer(&self.indices, 0); - render_pass.set_vertex_buffers( - 0, - &[(&self.vertices, 0), (&self.instances, 0)], - ); - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); - - render_pass.draw_indexed( - 0..QUAD_INDICES.len() as u32, - 0, - 0..1 as u32, - ); - } + }, + ], + depth_stencil_attachment: None, + }, + ); + + render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group(0, &self.constants, &[]); + render_pass.set_bind_group(1, &texture, &[]); + render_pass.set_index_buffer(&self.indices, 0); + render_pass.set_vertex_buffers( + 0, + &[(&self.vertices, 0), (&self.instances, 0)], + ); + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + + render_pass.draw_indexed( + 0..QUAD_INDICES.len() as u32, + 0, + 0..1 as u32, + ); } } } @@ -399,6 +444,8 @@ const QUAD_VERTS: [Vertex; 4] = [ struct Instance { _position: [f32; 2], _scale: [f32; 2], + _position_in_atlas: [f32; 2], + _scale_in_atlas: [f32; 2], } #[repr(C)] diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index fa107879..0418bc0b 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -1,17 +1,13 @@ use iced_native::image; use std::{ collections::{HashMap, HashSet}, - rc::Rc, + fmt, }; +use guillotiere::{Allocation, AtlasAllocator, Size}; -#[derive(Debug)] pub enum Memory { Host(::image::ImageBuffer<::image::Bgra, Vec>), - Device { - bind_group: Rc, - width: u32, - height: u32, - }, + Device(Allocation), NotFound, Invalid, } @@ -20,108 +16,59 @@ impl Memory { pub fn dimensions(&self) -> (u32, u32) { match self { Memory::Host(image) => image.dimensions(), - Memory::Device { width, height, .. } => (*width, *height), + Memory::Device(allocation) => { + let size = &allocation.rectangle.size(); + (size.width as u32, size.height as u32) + }, Memory::NotFound => (1, 1), Memory::Invalid => (1, 1), } } - - pub fn upload( - &mut self, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - texture_layout: &wgpu::BindGroupLayout, - ) -> Option> { - match self { - Memory::Host(image) => { - let (width, height) = image.dimensions(); - - let extent = wgpu::Extent3d { - width, - height, - depth: 1, - }; - - let texture = 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::COPY_DST - | wgpu::TextureUsage::SAMPLED, - }); - - let temp_buf = { - let flat_samples = image.as_flat_samples(); - let slice = flat_samples.as_slice(); - - device - .create_buffer_mapped( - slice.len(), - wgpu::BufferUsage::COPY_SRC, - ) - .fill_from_slice(slice) - }; - - encoder.copy_buffer_to_texture( - wgpu::BufferCopyView { - buffer: &temp_buf, - offset: 0, - row_pitch: 4 * width as u32, - image_height: height as u32, - }, - wgpu::TextureCopyView { - texture: &texture, - array_layer: 0, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, - z: 0.0, - }, - }, - extent, - ); - - let bind_group = - device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: texture_layout, - bindings: &[wgpu::Binding { - binding: 0, - resource: wgpu::BindingResource::TextureView( - &texture.create_default_view(), - ), - }], - }); - - let bind_group = Rc::new(bind_group); - - *self = Memory::Device { - bind_group: bind_group.clone(), - width, - height, - }; - - Some(bind_group) - } - Memory::Device { bind_group, .. } => Some(bind_group.clone()), - Memory::NotFound => None, - Memory::Invalid => None, - } - } } -#[derive(Debug)] pub struct Cache { + allocator: AtlasAllocator, + atlas: wgpu::Texture, map: HashMap, hits: HashSet, } +impl fmt::Debug for Cache { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("Vector Cache") + .field("allocator", &String::from("AtlasAllocator")) + .field("atlas", &self.atlas) + .field("map", &String::from("HashMap")) + .field("hits", &self.hits) + .finish() + } +} + impl Cache { - pub fn new() -> Self { + pub fn new(device: &wgpu::Device) -> Self { + let (width, height) = (1000, 1000); + + let extent = wgpu::Extent3d { + width, + height, + depth: 1, + }; + + let atlas = 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::COPY_DST + | wgpu::TextureUsage::COPY_SRC + | wgpu::TextureUsage::SAMPLED, + }); + Self { + allocator: AtlasAllocator::new(Size::new(width as i32, height as i32)), + atlas, map: HashMap::new(), hits: HashSet::new(), } @@ -153,9 +100,153 @@ impl Cache { self.get(handle).unwrap() } + pub fn atlas_size(&self) -> guillotiere::Size { + self.allocator.size() + } + + pub fn upload( + &mut self, + handle: &image::Handle, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + ) -> &Memory { + let _ = self.load(handle); + + let memory = self.map.get_mut(&handle.id()).unwrap(); + + if let Memory::Host(image) = memory { + let (width, height) = image.dimensions(); + let size = Size::new(width as i32, height as i32); + + let old_atlas_size = self.allocator.size(); + let allocation; + + loop { + if let Some(a) = self.allocator.allocate(size) { + allocation = a; + break; + } + + self.allocator.grow(self.allocator.size() * 2); + } + + let new_atlas_size = self.allocator.size(); + + if new_atlas_size != old_atlas_size { + let new_atlas = device.create_texture(&wgpu::TextureDescriptor { + size: wgpu::Extent3d { + width: new_atlas_size.width as u32, + height: new_atlas_size.height as u32, + depth: 1, + }, + array_layer_count: 1, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsage::COPY_DST + | wgpu::TextureUsage::COPY_SRC + | wgpu::TextureUsage::SAMPLED, + }); + + encoder.copy_texture_to_texture( + wgpu::TextureCopyView { + texture: &self.atlas, + array_layer: 0, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + wgpu::TextureCopyView { + texture: &new_atlas, + array_layer: 0, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + wgpu::Extent3d { + width: old_atlas_size.width as u32, + height: old_atlas_size.height as u32, + depth: 1, + } + ); + + self.atlas = new_atlas; + } + + let extent = wgpu::Extent3d { + width, + height, + depth: 1, + }; + + let temp_buf = { + let flat_samples = image.as_flat_samples(); + let slice = flat_samples.as_slice(); + + device + .create_buffer_mapped( + slice.len(), + wgpu::BufferUsage::COPY_SRC, + ) + .fill_from_slice(slice) + }; + + encoder.copy_buffer_to_texture( + wgpu::BufferCopyView { + buffer: &temp_buf, + offset: 0, + row_pitch: 4 * width, + image_height: height, + }, + wgpu::TextureCopyView { + texture: &self.atlas, + array_layer: 0, + mip_level: 0, + origin: wgpu::Origin3d { + x: allocation.rectangle.min.x as f32, + y: allocation.rectangle.min.y as f32, + z: 0.0, + }, + }, + extent, + ); + + *memory = Memory::Device(allocation); + } + + memory + } + + pub fn atlas(&self, device: &wgpu::Device, texture_layout: &wgpu::BindGroupLayout) -> wgpu::BindGroup { + device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: texture_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::TextureView( + &self.atlas.create_default_view(), + ), + }], + }) + } + pub fn trim(&mut self) { let hits = &self.hits; + for (id, mem) in &mut self.map { + if let Memory::Device(allocation) = mem { + if !hits.contains(&id) { + self.allocator.deallocate(allocation.id); + } + } + } + self.map.retain(|k, _| hits.contains(k)); self.hits.clear(); } diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 713978f5..1a9352f2 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -1,8 +1,9 @@ use iced_native::svg; use std::{ collections::{HashMap, HashSet}, - rc::Rc, + fmt, }; +use guillotiere::{Allocation, AtlasAllocator, Size}; pub enum Svg { Loaded { tree: resvg::usvg::Tree }, @@ -22,27 +23,63 @@ impl Svg { } } -impl std::fmt::Debug for Svg { +impl fmt::Debug for Svg { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Svg") } } -#[derive(Debug)] pub struct Cache { + allocator: AtlasAllocator, + atlas: wgpu::Texture, svgs: HashMap, - rasterized: HashMap<(u64, u32, u32), Rc>, + rasterized: HashMap<(u64, u32, u32), Allocation>, svg_hits: HashSet, rasterized_hits: HashSet<(u64, u32, u32)>, } +impl fmt::Debug for Cache { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("Vector Cache") + .field("allocator", &String::from("AtlasAllocator")) + .field("atlas", &self.atlas) + .field("svgs", &self.svgs) + .field("rasterized", &String::from("HashMap<(u64, u32, u32), Allocation>")) + .field("svg_hits", &self.svg_hits) + .field("rasterized_hits", &self.rasterized_hits) + .finish() + } +} + impl Cache { - pub fn new() -> Self { + pub fn new(device: &wgpu::Device) -> Self { + let (width, height) = (512, 512); + + let extent = wgpu::Extent3d { + width, + height, + depth: 1, + }; + + let atlas = 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::COPY_DST + | wgpu::TextureUsage::COPY_SRC + | wgpu::TextureUsage::SAMPLED, + }); + Self { svgs: HashMap::new(), rasterized: HashMap::new(), svg_hits: HashSet::new(), rasterized_hits: HashSet::new(), + allocator: AtlasAllocator::new(Size::new(width as i32, height as i32)), + atlas, } } @@ -62,6 +99,10 @@ impl Cache { self.svgs.get(&handle.id()).unwrap() } + pub fn atlas_size(&self) -> guillotiere::Size { + self.allocator.size() + } + pub fn upload( &mut self, handle: &svg::Handle, @@ -69,8 +110,7 @@ impl Cache { scale: f32, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, - texture_layout: &wgpu::BindGroupLayout, - ) -> Option> { + ) -> Option<&Allocation> { let id = handle.id(); let (width, height) = ( @@ -82,36 +122,88 @@ impl Cache { // We currently rerasterize the SVG when its size changes. This is slow // as heck. A GPU rasterizer like `pathfinder` may perform better. // It would be cool to be able to smooth resize the `svg` example. - if let Some(bind_group) = self.rasterized.get(&(id, width, height)) { + if self.rasterized.get(&(id, width, height)).is_some() { let _ = self.svg_hits.insert(id); let _ = self.rasterized_hits.insert((id, width, height)); - return Some(bind_group.clone()); + return self.rasterized.get(&(id, width, height)); } - match self.load(handle) { + let _ = self.load(handle); + + match self.svgs.get(&handle.id()).unwrap() { Svg::Loaded { tree } => { if width == 0 || height == 0 { return None; } - let extent = wgpu::Extent3d { - width, - height, - depth: 1, - }; + let size = Size::new(width as i32, height as i32); + let old_atlas_size = self.allocator.size(); + let allocation; + + loop { + if let Some(a) = self.allocator.allocate(size) { + allocation = a; + break; + } - let texture = 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::COPY_DST - | wgpu::TextureUsage::SAMPLED, - }); + self.allocator.grow(self.allocator.size() * 2); + } + let new_atlas_size = self.allocator.size(); + + if new_atlas_size != old_atlas_size { + let new_atlas = device.create_texture(&wgpu::TextureDescriptor { + size: wgpu::Extent3d { + width: new_atlas_size.width as u32, + height: new_atlas_size.height as u32, + depth: 1, + }, + array_layer_count: 1, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsage::COPY_DST + | wgpu::TextureUsage::COPY_SRC + | wgpu::TextureUsage::SAMPLED, + }); + + encoder.copy_texture_to_texture( + wgpu::TextureCopyView { + texture: &self.atlas, + array_layer: 0, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + wgpu::TextureCopyView { + texture: &new_atlas, + array_layer: 0, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + wgpu::Extent3d { + width: old_atlas_size.width as u32, + height: old_atlas_size.height as u32, + depth: 1, + } + ); + + self.atlas = new_atlas; + } + + // TODO: Optimize! + // We currently rerasterize the SVG when its size changes. This is slow + // as heck. A GPU rasterizer like `pathfinder` may perform better. + // It would be cool to be able to smooth resize the `svg` example. let temp_buf = { let screen_size = resvg::ScreenSize::new(width, height).unwrap(); @@ -122,7 +214,7 @@ impl Cache { ); resvg::backend_raqote::render_to_canvas( - &tree, + tree, &resvg::Options::default(), screen_size, &mut canvas, @@ -146,48 +238,56 @@ impl Cache { image_height: height as u32, }, wgpu::TextureCopyView { - texture: &texture, + texture: &self.atlas, array_layer: 0, mip_level: 0, origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, + x: allocation.rectangle.min.x as f32, + y: allocation.rectangle.min.y as f32, z: 0.0, }, }, - extent, + wgpu::Extent3d { + width, + height, + depth: 1, + }, ); - let bind_group = - device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: texture_layout, - bindings: &[wgpu::Binding { - binding: 0, - resource: wgpu::BindingResource::TextureView( - &texture.create_default_view(), - ), - }], - }); - - let bind_group = Rc::new(bind_group); - - let _ = self - .rasterized - .insert((id, width, height), bind_group.clone()); - let _ = self.svg_hits.insert(id); let _ = self.rasterized_hits.insert((id, width, height)); + let _ = self + .rasterized + .insert((id, width, height), allocation); - Some(bind_group) + self.rasterized.get(&(id, width, height)) } - Svg::NotFound => None, + Svg::NotFound => None } } + pub fn atlas(&self, device: &wgpu::Device, texture_layout: &wgpu::BindGroupLayout) -> wgpu::BindGroup { + device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: texture_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::TextureView( + &self.atlas.create_default_view(), + ), + }], + }) + } + pub fn trim(&mut self) { let svg_hits = &self.svg_hits; let rasterized_hits = &self.rasterized_hits; + for (k, alloc) in &mut self.rasterized { + if !rasterized_hits.contains(&k) { + self.allocator.deallocate(alloc.id); + } + } + self.svgs.retain(|k, _| svg_hits.contains(k)); self.rasterized.retain(|k, _| rasterized_hits.contains(k)); self.svg_hits.clear(); diff --git a/wgpu/src/shader/image.vert b/wgpu/src/shader/image.vert new file mode 100644 index 00000000..953840d2 --- /dev/null +++ b/wgpu/src/shader/image.vert @@ -0,0 +1,26 @@ +#version 450 + +layout(location = 0) in vec2 v_Pos; +layout(location = 1) in vec2 i_Pos; +layout(location = 2) in vec2 i_Scale; +layout(location = 3) in vec2 i_Atlas_Pos; +layout(location = 4) in vec2 i_Atlas_Scale; + +layout (set = 0, binding = 0) uniform Globals { + mat4 u_Transform; +}; + +layout(location = 0) out vec2 o_Uv; + +void main() { + o_Uv = v_Pos * i_Atlas_Scale + i_Atlas_Pos; + + mat4 i_Transform = mat4( + vec4(i_Scale.x, 0.0, 0.0, 0.0), + vec4(0.0, i_Scale.y, 0.0, 0.0), + vec4(0.0, 0.0, 1.0, 0.0), + vec4(i_Pos, 0.0, 1.0) + ); + + gl_Position = u_Transform * i_Transform * vec4(v_Pos, 0.0, 1.0); +} diff --git a/wgpu/src/shader/image.vert.spv b/wgpu/src/shader/image.vert.spv index 9ba702bc..da76eca0 100644 Binary files a/wgpu/src/shader/image.vert.spv and b/wgpu/src/shader/image.vert.spv differ -- cgit From 743637ebda8c3e4ba30f41e755ee1079ee66a86c Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Fri, 10 Jan 2020 16:07:09 +0100 Subject: Merged image and svg texture atlases into one owned by the image pipeline. --- wgpu/Cargo.toml | 1 + wgpu/src/image.rs | 92 +++++++++++++++++++++++++++++++----------------- wgpu/src/image/raster.rs | 79 +++++++++-------------------------------- wgpu/src/image/vector.rs | 78 ++++++++-------------------------------- 4 files changed, 91 insertions(+), 159 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 56839cf0..f8b5bb8c 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -22,6 +22,7 @@ glam = "0.8" font-kit = "0.4" log = "0.4" guillotiere = "0.4" +debug_stub_derive = "0.3" [dependencies.image] version = "0.22" diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 65780886..aaecd492 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -14,7 +14,10 @@ use std::mem; #[cfg(any(feature = "image", feature = "svg"))] use std::{cell::RefCell, collections::HashMap}; -#[derive(Debug)] +use guillotiere::{AtlasAllocator, Size}; +use debug_stub_derive::*; + +#[derive(DebugStub)] pub struct Pipeline { #[cfg(feature = "image")] raster_cache: RefCell, @@ -28,6 +31,9 @@ pub struct Pipeline { instances: wgpu::Buffer, constants: wgpu::BindGroup, texture_layout: wgpu::BindGroupLayout, + #[debug_stub="ReplacementValue"] + allocator: AtlasAllocator, + atlas: wgpu::Texture, } impl Pipeline { @@ -208,12 +214,32 @@ impl Pipeline { usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, }); + let (width, height) = (512, 512); + + let extent = wgpu::Extent3d { + width, + height, + depth: 1, + }; + + let atlas = 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::COPY_DST + | wgpu::TextureUsage::COPY_SRC + | wgpu::TextureUsage::SAMPLED, + }); + Pipeline { #[cfg(feature = "image")] - raster_cache: RefCell::new(raster::Cache::new(&device)), + raster_cache: RefCell::new(raster::Cache::new()), #[cfg(feature = "svg")] - vector_cache: RefCell::new(vector::Cache::new(&device)), + vector_cache: RefCell::new(vector::Cache::new()), pipeline, uniforms: uniforms_buffer, @@ -222,6 +248,8 @@ impl Pipeline { instances, constants: constant_bind_group, texture_layout, + allocator: AtlasAllocator::new(Size::new(width as i32, height as i32)), + atlas, } } @@ -276,10 +304,12 @@ impl Pipeline { let mut raster_cache = self.raster_cache.borrow_mut(); if let Memory::Device(allocation) = raster_cache.upload( - _handle, + handle, device, - encoder) - { + encoder, + &mut self.allocator, + &mut self.atlas + ) { let rec = allocation.rectangle; let _ = recs.insert(index, rec); @@ -291,12 +321,15 @@ impl Pipeline { { let mut vector_cache = self.vector_cache.borrow_mut(); + // Upload rasterized svg to texture atlas if let Some(allocation) = vector_cache.upload( _handle, image.scale, _scale, device, encoder, + &mut self.allocator, + &mut self.atlas, ) { let rec = allocation.rectangle; @@ -307,30 +340,34 @@ impl Pipeline { } } - #[cfg(feature = "image")] - let raster_atlas = self.raster_cache.borrow().atlas(device, &self.texture_layout); - - #[cfg(feature = "svg")] - let vector_atlas = self.vector_cache.borrow().atlas(device, &self.texture_layout); + let atlas_width = self.allocator.size().width as f32; + let atlas_height = self.allocator.size().height as f32; + + let texture = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &self.texture_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::TextureView( + &self.atlas.create_default_view(), + ), + }], + }); #[cfg(any(feature = "image", feature = "svg"))] for (index, image) in instances.iter().enumerate() { if let Some(rec) = recs.get(&index) { - let atlas_size = match image.handle { - #[cfg(feature = "image")] - Handle::Raster(_) => self.raster_cache.borrow().atlas_size(), - #[cfg(feature = "svg")] - Handle::Vector(_) => self.vector_cache.borrow().atlas_size(), - _ => guillotiere::Size::new(0, 0) - }; + let x = rec.min.x as f32 / atlas_width; + let y = rec.min.y as f32 / atlas_height; + let w = (rec.size().width - 1) as f32 / atlas_width; + let h = (rec.size().height - 1) as f32 / atlas_height; let instance_buffer = device .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) .fill_from_slice(&[Instance { _position: image.position, _scale: image.scale, - _position_in_atlas: [rec.min.x as f32 / atlas_size.width as f32, rec.min.y as f32 / atlas_size.height as f32], - _scale_in_atlas: [rec.size().width as f32 / atlas_size.width as f32, rec.size().height as f32 / atlas_size.height as f32] + _position_in_atlas: [x, y], + _scale_in_atlas: [w, h] }]); encoder.copy_buffer_to_buffer( @@ -341,17 +378,6 @@ impl Pipeline { mem::size_of::() as u64, ); - let texture = match &image.handle { - #[cfg(feature = "image")] - Handle::Raster(_) => &raster_atlas, - #[cfg(feature = "svg")] - Handle::Vector(_) => &vector_atlas, - #[cfg(feature = "image")] - _ => &raster_atlas, - #[cfg(feature = "svg")] - _ => &vector_atlas, - }; - let mut render_pass = encoder.begin_render_pass( &wgpu::RenderPassDescriptor { color_attachments: &[ @@ -398,10 +424,10 @@ impl Pipeline { pub fn trim_cache(&mut self) { #[cfg(feature = "image")] - self.raster_cache.borrow_mut().trim(); + self.raster_cache.borrow_mut().trim(&mut self.allocator); #[cfg(feature = "svg")] - self.vector_cache.borrow_mut().trim(); + self.vector_cache.borrow_mut().trim(&mut self.allocator); } } diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 0418bc0b..651ec078 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -1,13 +1,14 @@ use iced_native::image; use std::{ collections::{HashMap, HashSet}, - fmt, }; use guillotiere::{Allocation, AtlasAllocator, Size}; +use debug_stub_derive::*; +#[derive(DebugStub)] pub enum Memory { Host(::image::ImageBuffer<::image::Bgra, Vec>), - Device(Allocation), + Device(#[debug_stub="ReplacementValue"]Allocation), NotFound, Invalid, } @@ -26,49 +27,15 @@ impl Memory { } } +#[derive(Debug)] pub struct Cache { - allocator: AtlasAllocator, - atlas: wgpu::Texture, map: HashMap, hits: HashSet, } -impl fmt::Debug for Cache { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_struct("Vector Cache") - .field("allocator", &String::from("AtlasAllocator")) - .field("atlas", &self.atlas) - .field("map", &String::from("HashMap")) - .field("hits", &self.hits) - .finish() - } -} - impl Cache { - pub fn new(device: &wgpu::Device) -> Self { - let (width, height) = (1000, 1000); - - let extent = wgpu::Extent3d { - width, - height, - depth: 1, - }; - - let atlas = 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::COPY_DST - | wgpu::TextureUsage::COPY_SRC - | wgpu::TextureUsage::SAMPLED, - }); - + pub fn new() -> Self { Self { - allocator: AtlasAllocator::new(Size::new(width as i32, height as i32)), - atlas, map: HashMap::new(), hits: HashSet::new(), } @@ -100,15 +67,13 @@ impl Cache { self.get(handle).unwrap() } - pub fn atlas_size(&self) -> guillotiere::Size { - self.allocator.size() - } - pub fn upload( &mut self, handle: &image::Handle, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, + allocator: &mut AtlasAllocator, + atlas: &mut wgpu::Texture, ) -> &Memory { let _ = self.load(handle); @@ -118,19 +83,19 @@ impl Cache { let (width, height) = image.dimensions(); let size = Size::new(width as i32, height as i32); - let old_atlas_size = self.allocator.size(); + let old_atlas_size = allocator.size(); let allocation; loop { - if let Some(a) = self.allocator.allocate(size) { + if let Some(a) = allocator.allocate(size) { allocation = a; break; } - self.allocator.grow(self.allocator.size() * 2); + allocator.grow(allocator.size() * 2); } - let new_atlas_size = self.allocator.size(); + let new_atlas_size = allocator.size(); if new_atlas_size != old_atlas_size { let new_atlas = device.create_texture(&wgpu::TextureDescriptor { @@ -151,7 +116,7 @@ impl Cache { encoder.copy_texture_to_texture( wgpu::TextureCopyView { - texture: &self.atlas, + texture: atlas, array_layer: 0, mip_level: 0, origin: wgpu::Origin3d { @@ -177,7 +142,7 @@ impl Cache { } ); - self.atlas = new_atlas; + *atlas = new_atlas; } let extent = wgpu::Extent3d { @@ -206,7 +171,7 @@ impl Cache { image_height: height, }, wgpu::TextureCopyView { - texture: &self.atlas, + texture: atlas, array_layer: 0, mip_level: 0, origin: wgpu::Origin3d { @@ -224,25 +189,13 @@ impl Cache { memory } - pub fn atlas(&self, device: &wgpu::Device, texture_layout: &wgpu::BindGroupLayout) -> wgpu::BindGroup { - device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: texture_layout, - bindings: &[wgpu::Binding { - binding: 0, - resource: wgpu::BindingResource::TextureView( - &self.atlas.create_default_view(), - ), - }], - }) - } - - pub fn trim(&mut self) { + pub fn trim(&mut self, allocator: &mut AtlasAllocator) { let hits = &self.hits; for (id, mem) in &mut self.map { if let Memory::Device(allocation) = mem { if !hits.contains(&id) { - self.allocator.deallocate(allocation.id); + allocator.deallocate(allocation.id); } } } diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 1a9352f2..89477877 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -4,6 +4,7 @@ use std::{ fmt, }; use guillotiere::{Allocation, AtlasAllocator, Size}; +use debug_stub_derive::*; pub enum Svg { Loaded { tree: resvg::usvg::Tree }, @@ -29,57 +30,22 @@ impl fmt::Debug for Svg { } } +#[derive(DebugStub)] pub struct Cache { - allocator: AtlasAllocator, - atlas: wgpu::Texture, svgs: HashMap, + #[debug_stub="ReplacementValue"] rasterized: HashMap<(u64, u32, u32), Allocation>, svg_hits: HashSet, rasterized_hits: HashSet<(u64, u32, u32)>, } -impl fmt::Debug for Cache { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_struct("Vector Cache") - .field("allocator", &String::from("AtlasAllocator")) - .field("atlas", &self.atlas) - .field("svgs", &self.svgs) - .field("rasterized", &String::from("HashMap<(u64, u32, u32), Allocation>")) - .field("svg_hits", &self.svg_hits) - .field("rasterized_hits", &self.rasterized_hits) - .finish() - } -} - impl Cache { - pub fn new(device: &wgpu::Device) -> Self { - let (width, height) = (512, 512); - - let extent = wgpu::Extent3d { - width, - height, - depth: 1, - }; - - let atlas = 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::COPY_DST - | wgpu::TextureUsage::COPY_SRC - | wgpu::TextureUsage::SAMPLED, - }); - + pub fn new() -> Self { Self { svgs: HashMap::new(), rasterized: HashMap::new(), svg_hits: HashSet::new(), rasterized_hits: HashSet::new(), - allocator: AtlasAllocator::new(Size::new(width as i32, height as i32)), - atlas, } } @@ -99,10 +65,6 @@ impl Cache { self.svgs.get(&handle.id()).unwrap() } - pub fn atlas_size(&self) -> guillotiere::Size { - self.allocator.size() - } - pub fn upload( &mut self, handle: &svg::Handle, @@ -110,6 +72,8 @@ impl Cache { scale: f32, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, + allocator: &mut AtlasAllocator, + atlas: &mut wgpu::Texture, ) -> Option<&Allocation> { let id = handle.id(); @@ -138,19 +102,19 @@ impl Cache { } let size = Size::new(width as i32, height as i32); - let old_atlas_size = self.allocator.size(); + let old_atlas_size = allocator.size(); let allocation; loop { - if let Some(a) = self.allocator.allocate(size) { + if let Some(a) = allocator.allocate(size) { allocation = a; break; } - self.allocator.grow(self.allocator.size() * 2); + allocator.grow(allocator.size() * 2); } - let new_atlas_size = self.allocator.size(); + let new_atlas_size = allocator.size(); if new_atlas_size != old_atlas_size { let new_atlas = device.create_texture(&wgpu::TextureDescriptor { @@ -171,7 +135,7 @@ impl Cache { encoder.copy_texture_to_texture( wgpu::TextureCopyView { - texture: &self.atlas, + texture: atlas, array_layer: 0, mip_level: 0, origin: wgpu::Origin3d { @@ -197,7 +161,7 @@ impl Cache { } ); - self.atlas = new_atlas; + *atlas = new_atlas; } // TODO: Optimize! @@ -238,7 +202,7 @@ impl Cache { image_height: height as u32, }, wgpu::TextureCopyView { - texture: &self.atlas, + texture: atlas, array_layer: 0, mip_level: 0, origin: wgpu::Origin3d { @@ -266,25 +230,13 @@ impl Cache { } } - pub fn atlas(&self, device: &wgpu::Device, texture_layout: &wgpu::BindGroupLayout) -> wgpu::BindGroup { - device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: texture_layout, - bindings: &[wgpu::Binding { - binding: 0, - resource: wgpu::BindingResource::TextureView( - &self.atlas.create_default_view(), - ), - }], - }) - } - - pub fn trim(&mut self) { + pub fn trim(&mut self, allocator: &mut AtlasAllocator) { let svg_hits = &self.svg_hits; let rasterized_hits = &self.rasterized_hits; for (k, alloc) in &mut self.rasterized { if !rasterized_hits.contains(&k) { - self.allocator.deallocate(alloc.id); + allocator.deallocate(alloc.id); } } -- cgit From 82e0675c071eee6ee71a989f4c8fb9a9fc18fcfe Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Sat, 11 Jan 2020 21:50:42 +0100 Subject: Some small debug changes --- wgpu/src/image/raster.rs | 5 ++++- wgpu/src/image/vector.rs | 19 ++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 651ec078..33750cac 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -8,7 +8,10 @@ use debug_stub_derive::*; #[derive(DebugStub)] pub enum Memory { Host(::image::ImageBuffer<::image::Bgra, Vec>), - Device(#[debug_stub="ReplacementValue"]Allocation), + Device( + #[debug_stub="ReplacementValue"] + Allocation + ), NotFound, Invalid, } diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 89477877..2afe7d92 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -1,20 +1,23 @@ use iced_native::svg; use std::{ collections::{HashMap, HashSet}, - fmt, }; use guillotiere::{Allocation, AtlasAllocator, Size}; use debug_stub_derive::*; +#[derive(DebugStub)] pub enum Svg { - Loaded { tree: resvg::usvg::Tree }, + Loaded( + #[debug_stub="ReplacementValue"] + resvg::usvg::Tree + ), NotFound, } impl Svg { pub fn viewport_dimensions(&self) -> (u32, u32) { match self { - Svg::Loaded { tree } => { + Svg::Loaded(tree) => { let size = tree.svg_node().size; (size.width() as u32, size.height() as u32) @@ -24,12 +27,6 @@ impl Svg { } } -impl fmt::Debug for Svg { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Svg") - } -} - #[derive(DebugStub)] pub struct Cache { svgs: HashMap, @@ -57,7 +54,7 @@ impl Cache { let opt = resvg::Options::default(); let svg = match resvg::usvg::Tree::from_file(handle.path(), &opt.usvg) { - Ok(tree) => Svg::Loaded { tree }, + Ok(tree) => Svg::Loaded(tree), Err(_) => Svg::NotFound, }; @@ -96,7 +93,7 @@ impl Cache { let _ = self.load(handle); match self.svgs.get(&handle.id()).unwrap() { - Svg::Loaded { tree } => { + Svg::Loaded(tree) => { if width == 0 || height == 0 { return None; } -- cgit From 8562a4c986ff48d478be794c8c4268047a9a57d7 Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Sun, 12 Jan 2020 02:37:46 +0100 Subject: Fixed texture bleeding --- wgpu/src/image.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index aaecd492..59257399 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -356,10 +356,10 @@ impl Pipeline { #[cfg(any(feature = "image", feature = "svg"))] for (index, image) in instances.iter().enumerate() { if let Some(rec) = recs.get(&index) { - let x = rec.min.x as f32 / atlas_width; - let y = rec.min.y as f32 / atlas_height; - let w = (rec.size().width - 1) as f32 / atlas_width; - let h = (rec.size().height - 1) as f32 / atlas_height; + let x = (rec.min.x as f32 + 0.5) / atlas_width; + let y = (rec.min.y as f32 + 0.5) / atlas_height; + let w = (rec.size().width as f32 - 0.5) / atlas_width; + let h = (rec.size().height as f32 - 0.5) / atlas_height; let instance_buffer = device .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) -- cgit From 2f77a6bf5ac1b657c1f54ea0b589b1e115b95e6b Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Mon, 13 Jan 2020 15:33:12 +0100 Subject: Use array of atlases instead of one growing indefinitely. --- wgpu/src/image.rs | 252 ++++++++++++++++++++++++++++++++++++--------- wgpu/src/image/raster.rs | 129 ++++------------------- wgpu/src/image/vector.rs | 149 ++++++--------------------- wgpu/src/shader/image.frag | 6 +- wgpu/src/shader/image.vert | 5 +- 5 files changed, 260 insertions(+), 281 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 59257399..bda24280 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -9,15 +9,15 @@ use crate::image::raster::Memory; use crate::Transformation; use iced_native::{image, svg, Rectangle}; -use std::mem; +use std::{collections::{HashMap, HashSet}, mem}; #[cfg(any(feature = "image", feature = "svg"))] -use std::{cell::RefCell, collections::HashMap}; +use std::cell::RefCell; -use guillotiere::{AtlasAllocator, Size}; +use guillotiere::{Allocation, AtlasAllocator, Size}; use debug_stub_derive::*; -#[derive(DebugStub)] +#[derive(Debug)] pub struct Pipeline { #[cfg(feature = "image")] raster_cache: RefCell, @@ -31,9 +31,7 @@ pub struct Pipeline { instances: wgpu::Buffer, constants: wgpu::BindGroup, texture_layout: wgpu::BindGroupLayout, - #[debug_stub="ReplacementValue"] - allocator: AtlasAllocator, - atlas: wgpu::Texture, + atlas_array: AtlasArray, } impl Pipeline { @@ -193,6 +191,11 @@ impl Pipeline { format: wgpu::VertexFormat::Float2, offset: 4 * 6, }, + wgpu::VertexAttributeDescriptor { + shader_location: 5, + format: wgpu::VertexFormat::Float, + offset: 4 * 8, + }, ], }, ], @@ -214,25 +217,7 @@ impl Pipeline { usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, }); - let (width, height) = (512, 512); - - let extent = wgpu::Extent3d { - width, - height, - depth: 1, - }; - - let atlas = 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::COPY_DST - | wgpu::TextureUsage::COPY_SRC - | wgpu::TextureUsage::SAMPLED, - }); + let atlas_array = AtlasArray::new(1, device); Pipeline { #[cfg(feature = "image")] @@ -248,8 +233,7 @@ impl Pipeline { instances, constants: constant_bind_group, texture_layout, - allocator: AtlasAllocator::new(Size::new(width as i32, height as i32)), - atlas, + atlas_array, } } @@ -303,14 +287,13 @@ impl Pipeline { { let mut raster_cache = self.raster_cache.borrow_mut(); - if let Memory::Device(allocation) = raster_cache.upload( - handle, + if let Memory::Device { layer, allocation } = raster_cache.upload( + _handle, device, encoder, - &mut self.allocator, - &mut self.atlas + &mut self.atlas_array, ) { - let rec = allocation.rectangle; + let rec = (*layer, allocation.rectangle); let _ = recs.insert(index, rec); } @@ -322,16 +305,15 @@ impl Pipeline { let mut vector_cache = self.vector_cache.borrow_mut(); // Upload rasterized svg to texture atlas - if let Some(allocation) = vector_cache.upload( + if let Some((layer, allocation)) = vector_cache.upload( _handle, image.scale, _scale, device, encoder, - &mut self.allocator, - &mut self.atlas, + &mut self.atlas_array, ) { - let rec = allocation.rectangle; + let rec = (*layer, allocation.rectangle); let _ = recs.insert(index, rec); } @@ -340,26 +322,23 @@ impl Pipeline { } } - let atlas_width = self.allocator.size().width as f32; - let atlas_height = self.allocator.size().height as f32; - let texture = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &self.texture_layout, bindings: &[wgpu::Binding { binding: 0, resource: wgpu::BindingResource::TextureView( - &self.atlas.create_default_view(), + &self.atlas_array.texture().create_default_view(), ), }], }); #[cfg(any(feature = "image", feature = "svg"))] for (index, image) in instances.iter().enumerate() { - if let Some(rec) = recs.get(&index) { - let x = (rec.min.x as f32 + 0.5) / atlas_width; - let y = (rec.min.y as f32 + 0.5) / atlas_height; - let w = (rec.size().width as f32 - 0.5) / atlas_width; - let h = (rec.size().height as f32 - 0.5) / atlas_height; + if let Some((layer, rec)) = recs.get(&index) { + let x = (rec.min.x as f32 + 0.5) / (ATLAS_SIZE as f32); + let y = (rec.min.y as f32 + 0.5) / (ATLAS_SIZE as f32); + let w = (rec.size().width as f32 - 0.5) / (ATLAS_SIZE as f32); + let h = (rec.size().height as f32 - 0.5) / (ATLAS_SIZE as f32); let instance_buffer = device .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) @@ -367,7 +346,8 @@ impl Pipeline { _position: image.position, _scale: image.scale, _position_in_atlas: [x, y], - _scale_in_atlas: [w, h] + _scale_in_atlas: [w, h], + _layer: *layer as f32, }]); encoder.copy_buffer_to_buffer( @@ -424,10 +404,10 @@ impl Pipeline { pub fn trim_cache(&mut self) { #[cfg(feature = "image")] - self.raster_cache.borrow_mut().trim(&mut self.allocator); + self.raster_cache.borrow_mut().trim(&mut self.atlas_array); #[cfg(feature = "svg")] - self.vector_cache.borrow_mut().trim(&mut self.allocator); + self.vector_cache.borrow_mut().trim(&mut self.atlas_array); } } @@ -442,6 +422,177 @@ pub enum Handle { Vector(svg::Handle), } +#[derive(DebugStub)] +pub struct AtlasArray { + texture: wgpu::Texture, + #[debug_stub="ReplacementValue"] + allocators: HashMap, + layers_without_allocators: HashSet, + size: u32, +} + +impl AtlasArray { + pub fn new(array_size: u32, device: &wgpu::Device) -> Self { + let (width, height) = (ATLAS_SIZE, ATLAS_SIZE); + + let extent = wgpu::Extent3d { + width, + height, + depth: 1, + }; + + let texture = device.create_texture(&wgpu::TextureDescriptor { + size: extent, + array_layer_count: array_size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsage::COPY_DST + | wgpu::TextureUsage::COPY_SRC + | wgpu::TextureUsage::SAMPLED, + }); + + AtlasArray { + texture, + allocators: HashMap::new(), + layers_without_allocators: HashSet::new(), + size: array_size, + } + } + + pub fn texture(&self) -> &wgpu::Texture { + &self.texture + } + + pub fn allocate(&mut self, size: Size) -> Option<(u32, Allocation)> { + for layer in 0..self.size { + if self.layers_without_allocators.contains(&layer) { + continue; + } + + let allocator = self.allocators.entry(layer) + .or_insert_with(|| AtlasAllocator::new( + Size::new(ATLAS_SIZE as i32, ATLAS_SIZE as i32) + )); + + if let Some(a) = allocator.allocate(size.clone()) { + return Some((layer, a)); + } + } + + None + } + + pub fn deallocate(&mut self, layer: u32, allocation: &Allocation) { + if let Some(allocator) = self.allocators.get_mut(&layer) { + allocator.deallocate(allocation.id); + } + } + + pub fn upload( + &mut self, + data: &[T], + layer: u32, + allocation: &guillotiere::Allocation, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + ) { + let size = allocation.rectangle.size(); + let (width, height) = (size.width as u32, size.height as u32); + + let extent = wgpu::Extent3d { + width, + height, + depth: 1, + }; + + let temp_buf = device + .create_buffer_mapped( + data.len(), + wgpu::BufferUsage::COPY_SRC, + ) + .fill_from_slice(data); + + encoder.copy_buffer_to_texture( + wgpu::BufferCopyView { + buffer: &temp_buf, + offset: 0, + row_pitch: 4 * width, + image_height: height, + }, + wgpu::TextureCopyView { + texture: &self.texture, + array_layer: layer as u32, + mip_level: 0, + origin: wgpu::Origin3d { + x: allocation.rectangle.min.x as f32, + y: allocation.rectangle.min.y as f32, + z: 0.0, + }, + }, + extent, + ); + } + + pub fn grow( + &mut self, + grow_by: u32, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + ) { + let old_atlas_array_size = self.size; + + let new_texture = device.create_texture(&wgpu::TextureDescriptor { + size: wgpu::Extent3d { + width: ATLAS_SIZE, + height: ATLAS_SIZE, + depth: 1, + }, + array_layer_count: old_atlas_array_size + grow_by, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsage::COPY_DST + | wgpu::TextureUsage::COPY_SRC + | wgpu::TextureUsage::SAMPLED, + }); + + for i in 0..old_atlas_array_size { + encoder.copy_texture_to_texture( + wgpu::TextureCopyView { + texture: &self.texture, + array_layer: i, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + wgpu::TextureCopyView { + texture: &new_texture, + array_layer: i, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + wgpu::Extent3d { + width: ATLAS_SIZE, + height: ATLAS_SIZE, + depth: 1, + } + ); + } + + self.texture = new_texture; + } +} + #[repr(C)] #[derive(Clone, Copy)] pub struct Vertex { @@ -465,6 +616,8 @@ const QUAD_VERTS: [Vertex; 4] = [ }, ]; +const ATLAS_SIZE: u32 = 8192; + #[repr(C)] #[derive(Clone, Copy)] struct Instance { @@ -472,6 +625,7 @@ struct Instance { _scale: [f32; 2], _position_in_atlas: [f32; 2], _scale_in_atlas: [f32; 2], + _layer: f32, } #[repr(C)] diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 33750cac..32afb749 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -1,17 +1,19 @@ +use crate::image::AtlasArray; use iced_native::image; use std::{ collections::{HashMap, HashSet}, }; -use guillotiere::{Allocation, AtlasAllocator, Size}; +use guillotiere::{Allocation, Size}; use debug_stub_derive::*; #[derive(DebugStub)] pub enum Memory { Host(::image::ImageBuffer<::image::Bgra, Vec>), - Device( + Device { + layer: u32, #[debug_stub="ReplacementValue"] - Allocation - ), + allocation: Allocation, + }, NotFound, Invalid, } @@ -20,7 +22,7 @@ impl Memory { pub fn dimensions(&self) -> (u32, u32) { match self { Memory::Host(image) => image.dimensions(), - Memory::Device(allocation) => { + Memory::Device { allocation, .. } => { let size = &allocation.rectangle.size(); (size.width as u32, size.height as u32) }, @@ -75,8 +77,7 @@ impl Cache { handle: &image::Handle, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, - allocator: &mut AtlasAllocator, - atlas: &mut wgpu::Texture, + atlas_array: &mut AtlasArray, ) -> &Memory { let _ = self.load(handle); @@ -86,119 +87,29 @@ impl Cache { let (width, height) = image.dimensions(); let size = Size::new(width as i32, height as i32); - let old_atlas_size = allocator.size(); - let allocation; + let (layer, allocation) = atlas_array.allocate(size).unwrap_or_else(|| { + atlas_array.grow(1, device, encoder); + atlas_array.allocate(size).unwrap() + }); - loop { - if let Some(a) = allocator.allocate(size) { - allocation = a; - break; - } - - allocator.grow(allocator.size() * 2); - } + let flat_samples = image.as_flat_samples(); + let slice = flat_samples.as_slice(); - let new_atlas_size = allocator.size(); - - if new_atlas_size != old_atlas_size { - let new_atlas = device.create_texture(&wgpu::TextureDescriptor { - size: wgpu::Extent3d { - width: new_atlas_size.width as u32, - height: new_atlas_size.height as u32, - depth: 1, - }, - array_layer_count: 1, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bgra8UnormSrgb, - usage: wgpu::TextureUsage::COPY_DST - | wgpu::TextureUsage::COPY_SRC - | wgpu::TextureUsage::SAMPLED, - }); - - encoder.copy_texture_to_texture( - wgpu::TextureCopyView { - texture: atlas, - array_layer: 0, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, - z: 0.0, - }, - }, - wgpu::TextureCopyView { - texture: &new_atlas, - array_layer: 0, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, - z: 0.0, - }, - }, - wgpu::Extent3d { - width: old_atlas_size.width as u32, - height: old_atlas_size.height as u32, - depth: 1, - } - ); - - *atlas = new_atlas; - } + atlas_array.upload(slice, layer, &allocation, device, encoder); - let extent = wgpu::Extent3d { - width, - height, - depth: 1, - }; - - let temp_buf = { - let flat_samples = image.as_flat_samples(); - let slice = flat_samples.as_slice(); - - device - .create_buffer_mapped( - slice.len(), - wgpu::BufferUsage::COPY_SRC, - ) - .fill_from_slice(slice) - }; - - encoder.copy_buffer_to_texture( - wgpu::BufferCopyView { - buffer: &temp_buf, - offset: 0, - row_pitch: 4 * width, - image_height: height, - }, - wgpu::TextureCopyView { - texture: atlas, - array_layer: 0, - mip_level: 0, - origin: wgpu::Origin3d { - x: allocation.rectangle.min.x as f32, - y: allocation.rectangle.min.y as f32, - z: 0.0, - }, - }, - extent, - ); - - *memory = Memory::Device(allocation); + *memory = Memory::Device { layer, allocation }; } memory } - pub fn trim(&mut self, allocator: &mut AtlasAllocator) { + pub fn trim(&mut self, atlas_array: &mut AtlasArray) { let hits = &self.hits; - for (id, mem) in &mut self.map { - if let Memory::Device(allocation) = mem { + for (id, mem) in &self.map { + if let Memory::Device { layer, allocation } = mem { if !hits.contains(&id) { - allocator.deallocate(allocation.id); + atlas_array.deallocate(*layer, allocation); } } } diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 2afe7d92..2972bc4a 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -1,8 +1,9 @@ +use crate::image::AtlasArray; use iced_native::svg; use std::{ collections::{HashMap, HashSet}, }; -use guillotiere::{Allocation, AtlasAllocator, Size}; +use guillotiere::{Allocation, Size}; use debug_stub_derive::*; #[derive(DebugStub)] @@ -31,7 +32,7 @@ impl Svg { pub struct Cache { svgs: HashMap, #[debug_stub="ReplacementValue"] - rasterized: HashMap<(u64, u32, u32), Allocation>, + rasterized: HashMap<(u64, u32, u32), (u32, Allocation)>, svg_hits: HashSet, rasterized_hits: HashSet<(u64, u32, u32)>, } @@ -69,9 +70,8 @@ impl Cache { scale: f32, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, - allocator: &mut AtlasAllocator, - atlas: &mut wgpu::Texture, - ) -> Option<&Allocation> { + atlas_array: &mut AtlasArray, + ) -> Option<&(u32, Allocation)> { let id = handle.id(); let (width, height) = ( @@ -99,127 +99,40 @@ impl Cache { } let size = Size::new(width as i32, height as i32); - let old_atlas_size = allocator.size(); - let allocation; - loop { - if let Some(a) = allocator.allocate(size) { - allocation = a; - break; - } - - allocator.grow(allocator.size() * 2); - } - - let new_atlas_size = allocator.size(); - - if new_atlas_size != old_atlas_size { - let new_atlas = device.create_texture(&wgpu::TextureDescriptor { - size: wgpu::Extent3d { - width: new_atlas_size.width as u32, - height: new_atlas_size.height as u32, - depth: 1, - }, - array_layer_count: 1, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bgra8UnormSrgb, - usage: wgpu::TextureUsage::COPY_DST - | wgpu::TextureUsage::COPY_SRC - | wgpu::TextureUsage::SAMPLED, - }); - - encoder.copy_texture_to_texture( - wgpu::TextureCopyView { - texture: atlas, - array_layer: 0, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, - z: 0.0, - }, - }, - wgpu::TextureCopyView { - texture: &new_atlas, - array_layer: 0, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, - z: 0.0, - }, - }, - wgpu::Extent3d { - width: old_atlas_size.width as u32, - height: old_atlas_size.height as u32, - depth: 1, - } - ); - - *atlas = new_atlas; - } + let (layer, allocation) = atlas_array.allocate(size).unwrap_or_else(|| { + atlas_array.grow(1, device, encoder); + atlas_array.allocate(size).unwrap() + }); // TODO: Optimize! // We currently rerasterize the SVG when its size changes. This is slow // as heck. A GPU rasterizer like `pathfinder` may perform better. // It would be cool to be able to smooth resize the `svg` example. - let temp_buf = { - let screen_size = - resvg::ScreenSize::new(width, height).unwrap(); - - let mut canvas = resvg::raqote::DrawTarget::new( - width as i32, - height as i32, - ); - - resvg::backend_raqote::render_to_canvas( - tree, - &resvg::Options::default(), - screen_size, - &mut canvas, - ); - - let slice = canvas.get_data(); - - device - .create_buffer_mapped( - slice.len(), - wgpu::BufferUsage::COPY_SRC, - ) - .fill_from_slice(slice) - }; - - encoder.copy_buffer_to_texture( - wgpu::BufferCopyView { - buffer: &temp_buf, - offset: 0, - row_pitch: 4 * width as u32, - image_height: height as u32, - }, - wgpu::TextureCopyView { - texture: atlas, - array_layer: 0, - mip_level: 0, - origin: wgpu::Origin3d { - x: allocation.rectangle.min.x as f32, - y: allocation.rectangle.min.y as f32, - z: 0.0, - }, - }, - wgpu::Extent3d { - width, - height, - depth: 1, - }, + let screen_size = + resvg::ScreenSize::new(width, height).unwrap(); + + let mut canvas = resvg::raqote::DrawTarget::new( + width as i32, + height as i32, ); + resvg::backend_raqote::render_to_canvas( + tree, + &resvg::Options::default(), + screen_size, + &mut canvas, + ); + + let slice = canvas.get_data(); + + atlas_array.upload(slice, layer, &allocation, device, encoder); + let _ = self.svg_hits.insert(id); let _ = self.rasterized_hits.insert((id, width, height)); let _ = self .rasterized - .insert((id, width, height), allocation); + .insert((id, width, height), (layer, allocation)); self.rasterized.get(&(id, width, height)) } @@ -227,13 +140,13 @@ impl Cache { } } - pub fn trim(&mut self, allocator: &mut AtlasAllocator) { + pub fn trim(&mut self, atlas_array: &mut AtlasArray) { let svg_hits = &self.svg_hits; let rasterized_hits = &self.rasterized_hits; - for (k, alloc) in &mut self.rasterized { - if !rasterized_hits.contains(&k) { - allocator.deallocate(alloc.id); + for (k, (layer, allocation)) in &self.rasterized { + if !rasterized_hits.contains(k) { + atlas_array.deallocate(*layer, allocation); } } diff --git a/wgpu/src/shader/image.frag b/wgpu/src/shader/image.frag index e35e455a..2809e9e6 100644 --- a/wgpu/src/shader/image.frag +++ b/wgpu/src/shader/image.frag @@ -1,12 +1,12 @@ #version 450 -layout(location = 0) in vec2 v_Uv; +layout(location = 0) in vec3 v_Uv; layout(set = 0, binding = 1) uniform sampler u_Sampler; -layout(set = 1, binding = 0) uniform texture2D u_Texture; +layout(set = 1, binding = 0) uniform texture2DArray u_Texture; layout(location = 0) out vec4 o_Color; void main() { - o_Color = texture(sampler2D(u_Texture, u_Sampler), v_Uv); + o_Color = texture(sampler2DArray(u_Texture, u_Sampler), v_Uv); } diff --git a/wgpu/src/shader/image.vert b/wgpu/src/shader/image.vert index 953840d2..0ce7dd6b 100644 --- a/wgpu/src/shader/image.vert +++ b/wgpu/src/shader/image.vert @@ -5,15 +5,16 @@ layout(location = 1) in vec2 i_Pos; layout(location = 2) in vec2 i_Scale; layout(location = 3) in vec2 i_Atlas_Pos; layout(location = 4) in vec2 i_Atlas_Scale; +layout(location = 5) in float i_Layer; layout (set = 0, binding = 0) uniform Globals { mat4 u_Transform; }; -layout(location = 0) out vec2 o_Uv; +layout(location = 0) out vec3 o_Uv; void main() { - o_Uv = v_Pos * i_Atlas_Scale + i_Atlas_Pos; + o_Uv = vec3(v_Pos * i_Atlas_Scale + i_Atlas_Pos, i_Layer); mat4 i_Transform = mat4( vec4(i_Scale.x, 0.0, 0.0, 0.0), -- cgit From 3f388351054c0961923b5f78e7dcf42289565a48 Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Thu, 16 Jan 2020 14:03:46 +0100 Subject: Implement allocating large images across multiple texture array layers. --- wgpu/src/image.rs | 685 +++++++++++++++++++++++++++++++++++------------ wgpu/src/image/raster.rs | 35 +-- wgpu/src/image/vector.rs | 27 +- 3 files changed, 539 insertions(+), 208 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index bda24280..f8faa543 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -9,7 +9,7 @@ use crate::image::raster::Memory; use crate::Transformation; use iced_native::{image, svg, Rectangle}; -use std::{collections::{HashMap, HashSet}, mem}; +use std::mem; #[cfg(any(feature = "image", feature = "svg"))] use std::cell::RefCell; @@ -31,7 +31,7 @@ pub struct Pipeline { instances: wgpu::Buffer, constants: wgpu::BindGroup, texture_layout: wgpu::BindGroupLayout, - atlas_array: AtlasArray, + texture_array: TextureArray, } impl Pipeline { @@ -217,7 +217,7 @@ impl Pipeline { usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, }); - let atlas_array = AtlasArray::new(1, device); + let texture_array = TextureArray::new(device); Pipeline { #[cfg(feature = "image")] @@ -233,7 +233,7 @@ impl Pipeline { instances, constants: constant_bind_group, texture_layout, - atlas_array, + texture_array, } } @@ -259,8 +259,8 @@ impl Pipeline { encoder: &mut wgpu::CommandEncoder, instances: &[Image], transformation: Transformation, - bounds: Rectangle, - target: &wgpu::TextureView, + _bounds: Rectangle, + _target: &wgpu::TextureView, _scale: f32, ) { let uniforms_buffer = device @@ -277,25 +277,27 @@ impl Pipeline { std::mem::size_of::() as u64, ); - #[cfg(any(feature = "image", feature = "svg"))] - let mut recs = HashMap::new(); - - for (index, image) in instances.iter().enumerate() { + for image in instances { match &image.handle { Handle::Raster(_handle) => { #[cfg(feature = "image")] { let mut raster_cache = self.raster_cache.borrow_mut(); - if let Memory::Device { layer, allocation } = raster_cache.upload( + if let Memory::Device(allocation) = raster_cache.upload( _handle, device, encoder, - &mut self.atlas_array, + &mut self.texture_array, ) { - let rec = (*layer, allocation.rectangle); - - let _ = recs.insert(index, rec); + self.draw_image( + device, + encoder, + image, + allocation, + _bounds, + _target, + ); } } } @@ -305,109 +307,173 @@ impl Pipeline { let mut vector_cache = self.vector_cache.borrow_mut(); // Upload rasterized svg to texture atlas - if let Some((layer, allocation)) = vector_cache.upload( + if let Some(allocation) = vector_cache.upload( _handle, image.scale, _scale, device, encoder, - &mut self.atlas_array, + &mut self.texture_array, ) { - let rec = (*layer, allocation.rectangle); - - let _ = recs.insert(index, rec); + self.draw_image( + device, + encoder, + image, + allocation, + _bounds, + _target, + ); } } } } } + } + + pub fn trim_cache(&mut self) { + #[cfg(feature = "image")] + self.raster_cache.borrow_mut().trim(&mut self.texture_array); + + #[cfg(feature = "svg")] + self.vector_cache.borrow_mut().trim(&mut self.texture_array); + } + fn draw_image( + &self, + device: &mut wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + image: &Image, + allocation: &ImageAllocation, + bounds: Rectangle, + target: &wgpu::TextureView, + ) { let texture = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &self.texture_layout, bindings: &[wgpu::Binding { binding: 0, resource: wgpu::BindingResource::TextureView( - &self.atlas_array.texture().create_default_view(), + &self.texture_array.texture.create_default_view(), ), }], }); - #[cfg(any(feature = "image", feature = "svg"))] - for (index, image) in instances.iter().enumerate() { - if let Some((layer, rec)) = recs.get(&index) { - let x = (rec.min.x as f32 + 0.5) / (ATLAS_SIZE as f32); - let y = (rec.min.y as f32 + 0.5) / (ATLAS_SIZE as f32); - let w = (rec.size().width as f32 - 0.5) / (ATLAS_SIZE as f32); - let h = (rec.size().height as f32 - 0.5) / (ATLAS_SIZE as f32); - - let instance_buffer = device - .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) - .fill_from_slice(&[Instance { - _position: image.position, - _scale: image.scale, - _position_in_atlas: [x, y], - _scale_in_atlas: [w, h], - _layer: *layer as f32, - }]); - - encoder.copy_buffer_to_buffer( - &instance_buffer, - 0, - &self.instances, - 0, - 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, - }, - ); - - render_pass.set_pipeline(&self.pipeline); - render_pass.set_bind_group(0, &self.constants, &[]); - render_pass.set_bind_group(1, &texture, &[]); - render_pass.set_index_buffer(&self.indices, 0); - render_pass.set_vertex_buffers( - 0, - &[(&self.vertices, 0), (&self.instances, 0)], - ); - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); - - render_pass.draw_indexed( - 0..QUAD_INDICES.len() as u32, - 0, - 0..1 as u32, - ); + match allocation { + ImageAllocation::SingleAllocation(allocation) => { + self.draw_allocation( + device, + encoder, + image.position, + image.scale, + allocation, + &texture, + bounds, + target, + ) + } + ImageAllocation::MultipleAllocations { mappings, size } => { + let scaling_x = image.scale[0] / size.0 as f32; + let scaling_y = image.scale[1] / size.1 as f32; + + for mapping in mappings { + let mut position = image.position; + let mut scale = image.scale; + + position[0] += mapping.src_pos.0 as f32 * scaling_x; + position[1] += mapping.src_pos.1 as f32 * scaling_y; + scale[0] = mapping.allocation.size().0 as f32 * scaling_x; + scale[1] = mapping.allocation.size().1 as f32 * scaling_y; + + self.draw_allocation( + device, + encoder, + position, + scale, + &mapping.allocation, + &texture, + bounds, + target, + ) + } } + _ => {} } } - pub fn trim_cache(&mut self) { - #[cfg(feature = "image")] - self.raster_cache.borrow_mut().trim(&mut self.atlas_array); + fn draw_allocation( + &self, + device: &mut wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + position: [f32; 2], + scale: [f32; 2], + allocation: &ArrayAllocation, + texture: &wgpu::BindGroup, + bounds: Rectangle, + target: &wgpu::TextureView, + ) { + let x = (allocation.position().0 as f32 + 0.5) / (ATLAS_SIZE as f32); + let y = (allocation.position().1 as f32 + 0.5) / (ATLAS_SIZE as f32); + let w = (allocation.size().0 as f32 - 0.5) / (ATLAS_SIZE as f32); + let h = (allocation.size().1 as f32 - 0.5) / (ATLAS_SIZE as f32); + let layer = allocation.layer() as f32; - #[cfg(feature = "svg")] - self.vector_cache.borrow_mut().trim(&mut self.atlas_array); + let instance_buffer = device + .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&[Instance { + _position: position, + _scale: scale, + _position_in_atlas: [x, y], + _scale_in_atlas: [w, h], + _layer: layer, + }]); + + encoder.copy_buffer_to_buffer( + &instance_buffer, + 0, + &self.instances, + 0, + 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, + }, + ); + + render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group(0, &self.constants, &[]); + render_pass.set_bind_group(1, &texture, &[]); + render_pass.set_index_buffer(&self.indices, 0); + render_pass.set_vertex_buffers( + 0, + &[(&self.vertices, 0), (&self.instances, 0)], + ); + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + + render_pass.draw_indexed( + 0..QUAD_INDICES.len() as u32, + 0, + 0..1 as u32, + ); } } @@ -422,17 +488,96 @@ pub enum Handle { Vector(svg::Handle), } +#[derive(Debug)] +pub struct ArrayAllocationMapping { + src_pos: (u32, u32), + allocation: ArrayAllocation, +} + +#[derive(Debug)] +pub enum ImageAllocation { + SingleAllocation(ArrayAllocation), + MultipleAllocations { + mappings: Vec, + size: (u32, u32), + }, + Error, +} + +impl ImageAllocation { + pub fn size(&self) -> (u32, u32) { + match self { + ImageAllocation::SingleAllocation(allocation) => { + allocation.size() + } + ImageAllocation::MultipleAllocations { size, .. } => { + *size + } + _ => (0, 0) + } + } +} + +#[derive(DebugStub)] +pub enum ArrayAllocation { + AtlasAllocation { + layer: usize, + #[debug_stub = "ReplacementValue"] + allocation: Allocation, + }, + WholeLayer { + layer: usize, + } +} + +impl ArrayAllocation { + pub fn size(&self) -> (u32, u32) { + match self { + ArrayAllocation::AtlasAllocation { allocation, .. } => { + let size = allocation.rectangle.size(); + (size.width as u32, size.height as u32) + } + ArrayAllocation::WholeLayer { .. } => (ATLAS_SIZE, ATLAS_SIZE) + } + } + + pub fn position(&self) -> (u32, u32) { + match self { + ArrayAllocation::AtlasAllocation { allocation, .. } => { + let min = &allocation.rectangle.min; + (min.x as u32, min.y as u32) + } + ArrayAllocation::WholeLayer { .. } => (0, 0) + } + } + + pub fn layer(&self) -> usize { + match self { + ArrayAllocation::AtlasAllocation { layer, .. } => *layer, + ArrayAllocation::WholeLayer { layer } => *layer, + } + } +} + #[derive(DebugStub)] -pub struct AtlasArray { +pub enum TextureLayer { + Whole, + Atlas( + #[debug_stub="ReplacementValue"] + AtlasAllocator + ), + Empty, +} + +#[derive(Debug)] +pub struct TextureArray { texture: wgpu::Texture, - #[debug_stub="ReplacementValue"] - allocators: HashMap, - layers_without_allocators: HashSet, - size: u32, + texture_array_size: u32, + layers: Vec, } -impl AtlasArray { - pub fn new(array_size: u32, device: &wgpu::Device) -> Self { +impl TextureArray { + pub fn new(device: &wgpu::Device) -> Self { let (width, height) = (ATLAS_SIZE, ATLAS_SIZE); let extent = wgpu::Extent3d { @@ -443,7 +588,7 @@ impl AtlasArray { let texture = device.create_texture(&wgpu::TextureDescriptor { size: extent, - array_layer_count: array_size, + array_layer_count: 1, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, @@ -453,53 +598,217 @@ impl AtlasArray { | wgpu::TextureUsage::SAMPLED, }); - AtlasArray { + let size = Size::new(ATLAS_SIZE as i32, ATLAS_SIZE as i32); + + TextureArray { texture, - allocators: HashMap::new(), - layers_without_allocators: HashSet::new(), - size: array_size, + texture_array_size: 1, + layers: vec!(TextureLayer::Atlas(AtlasAllocator::new(size))), } } - pub fn texture(&self) -> &wgpu::Texture { - &self.texture - } + pub fn allocate(&mut self, size: Size) -> ImageAllocation { + // Allocate one layer if allocation fits perfectly + if size.width == ATLAS_SIZE as i32 && size.height == ATLAS_SIZE as i32 { + for (i, layer) in &mut self.layers.iter_mut().enumerate() { + if let TextureLayer::Empty = layer + { + *layer = TextureLayer::Whole; + return ImageAllocation::SingleAllocation( + ArrayAllocation::WholeLayer { layer: i } + ); + } + } + + self.layers.push(TextureLayer::Whole); + return ImageAllocation::SingleAllocation( + ArrayAllocation::WholeLayer { layer: self.layers.len() - 1 } + ); + } - pub fn allocate(&mut self, size: Size) -> Option<(u32, Allocation)> { - for layer in 0..self.size { - if self.layers_without_allocators.contains(&layer) { - continue; + // Split big allocations across multiple layers + if size.width > ATLAS_SIZE as i32 || size.height > ATLAS_SIZE as i32 { + let mut mappings = Vec::new(); + + let mut y = 0; + while y < size.height { + let height = std::cmp::min(size.height - y, ATLAS_SIZE as i32); + let mut x = 0; + + while x < size.width { + let width = std::cmp::min(size.width - x, ATLAS_SIZE as i32); + if let ImageAllocation::SingleAllocation(allocation) = self.allocate(Size::new(width, height)) { + let src_pos = (x as u32, y as u32); + mappings.push(ArrayAllocationMapping { src_pos, allocation }); + } + + x += width; + } + y += height; } - let allocator = self.allocators.entry(layer) - .or_insert_with(|| AtlasAllocator::new( - Size::new(ATLAS_SIZE as i32, ATLAS_SIZE as i32) - )); + return ImageAllocation::MultipleAllocations { + mappings, + size: (size.width as u32, size.height as u32), + }; + } - if let Some(a) = allocator.allocate(size.clone()) { - return Some((layer, a)); + // Try allocating on an existing layer + for (i, layer) in self.layers.iter_mut().enumerate() { + if let TextureLayer::Atlas(allocator) = layer { + if let Some(allocation) = allocator.allocate(size.clone()) { + let array_allocation = ArrayAllocation::AtlasAllocation { layer: i, allocation }; + return ImageAllocation::SingleAllocation(array_allocation); + } } } - None + // Create new layer with atlas allocator + let mut allocator = AtlasAllocator::new(Size::new(ATLAS_SIZE as i32, ATLAS_SIZE as i32)); + if let Some(allocation) = allocator.allocate(size) { + self.layers.push(TextureLayer::Atlas(allocator)); + + return ImageAllocation::SingleAllocation( + ArrayAllocation::AtlasAllocation { + layer: self.layers.len() - 1, + allocation, + } + ); + } + + // One of the above should have worked + ImageAllocation::Error } - pub fn deallocate(&mut self, layer: u32, allocation: &Allocation) { - if let Some(allocator) = self.allocators.get_mut(&layer) { - allocator.deallocate(allocation.id); + pub fn deallocate(&mut self, allocation: &ImageAllocation) { + match allocation { + ImageAllocation::SingleAllocation(allocation) => { + if let Some(layer) = self.layers.get_mut(allocation.layer()) { + match allocation { + ArrayAllocation::WholeLayer { .. } => { + *layer = TextureLayer::Empty; + } + ArrayAllocation::AtlasAllocation { allocation, .. } => { + if let TextureLayer::Atlas(allocator) = layer { + allocator.deallocate(allocation.id); + } + } + } + } + } + ImageAllocation::MultipleAllocations { mappings, .. } => { + for mapping in mappings { + if let Some(layer) = self.layers.get_mut(mapping.allocation.layer()) { + match &mapping.allocation { + ArrayAllocation::WholeLayer { .. } => { + *layer = TextureLayer::Empty; + } + ArrayAllocation::AtlasAllocation { allocation, .. } => { + if let TextureLayer::Atlas(allocator) = layer { + allocator.deallocate(allocation.id); + } + } + } + } + } + } + _ => {} } + } - pub fn upload( + fn upload( &mut self, - data: &[T], - layer: u32, - allocation: &guillotiere::Allocation, + image: &I, + allocation: &ImageAllocation, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, + ) + where + I: RawImageData, + C: Copy + 'static, + { + match allocation { + ImageAllocation::SingleAllocation(allocation) => { + let data = image.data(); + let buffer = device + .create_buffer_mapped( + data.len(), + wgpu::BufferUsage::COPY_SRC, + ) + .fill_from_slice(data); + + if allocation.layer() >= self.texture_array_size as usize { + self.grow(1, device, encoder); + } + + self.upload_texture( + &buffer, + allocation, + encoder, + ); + } + ImageAllocation::MultipleAllocations { mappings, .. } => { + let chunks_per_pixel = 4 / std::mem::size_of::(); + let chunks_per_line = chunks_per_pixel * image.width() as usize; + + for mapping in mappings { + let sub_width = mapping.allocation.size().0 as usize; + let sub_height = mapping.allocation.size().1 as usize; + let sub_line_start = mapping.src_pos.0 as usize * chunks_per_pixel; + let sub_line_end = (mapping.src_pos.0 as usize + sub_width) * chunks_per_pixel; + + let mut sub_lines = image + .data() + .chunks(chunks_per_line) + .skip(mapping.src_pos.1 as usize) + .take(sub_height) + .map(|line| &line[sub_line_start..sub_line_end]); + + let buffer = device + .create_buffer_mapped( + chunks_per_pixel * sub_width * sub_height, + wgpu::BufferUsage::COPY_SRC, + ); + + let mut buffer_lines = buffer.data.chunks_mut(sub_width * chunks_per_pixel); + + while let (Some(buffer_line), Some(sub_line)) = (buffer_lines.next(), sub_lines.next()) { + buffer_line.copy_from_slice(sub_line); + } + + let highest_layer = mappings + .iter() + .map(|m| m.allocation.layer() as u32) + .max() + .unwrap_or(0); + + if highest_layer >= self.texture_array_size { + let grow_by = 1 + highest_layer - self.texture_array_size; + self.grow(grow_by, device, encoder); + } + + self.upload_texture( + &buffer.finish(), + &mapping.allocation, + encoder, + ); + } + } + _ => {} + } + + } + + fn upload_texture( + &mut self, + buffer: &wgpu::Buffer, + allocation: &ArrayAllocation, + encoder: &mut wgpu::CommandEncoder, ) { - let size = allocation.rectangle.size(); - let (width, height) = (size.width as u32, size.height as u32); + let array_layer = allocation.layer() as u32; + + let (width, height) = allocation.size(); let extent = wgpu::Extent3d { width, @@ -507,27 +816,22 @@ impl AtlasArray { depth: 1, }; - let temp_buf = device - .create_buffer_mapped( - data.len(), - wgpu::BufferUsage::COPY_SRC, - ) - .fill_from_slice(data); + let (x, y) = allocation.position(); encoder.copy_buffer_to_texture( wgpu::BufferCopyView { - buffer: &temp_buf, + buffer, offset: 0, row_pitch: 4 * width, image_height: height, }, wgpu::TextureCopyView { texture: &self.texture, - array_layer: layer as u32, + array_layer, mip_level: 0, origin: wgpu::Origin3d { - x: allocation.rectangle.min.x as f32, - y: allocation.rectangle.min.y as f32, + x: x as f32, + y: y as f32, z: 0.0, }, }, @@ -535,13 +839,17 @@ impl AtlasArray { ); } - pub fn grow( + fn grow( &mut self, grow_by: u32, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, ) { - let old_atlas_array_size = self.size; + if grow_by == 0 { + return; + } + + let old_texture_array_size = self.texture_array_size; let new_texture = device.create_texture(&wgpu::TextureDescriptor { size: wgpu::Extent3d { @@ -549,7 +857,7 @@ impl AtlasArray { height: ATLAS_SIZE, depth: 1, }, - array_layer_count: old_atlas_array_size + grow_by, + array_layer_count: old_texture_array_size + grow_by, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, @@ -559,40 +867,81 @@ impl AtlasArray { | wgpu::TextureUsage::SAMPLED, }); - for i in 0..old_atlas_array_size { - encoder.copy_texture_to_texture( - wgpu::TextureCopyView { - texture: &self.texture, - array_layer: i, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, - z: 0.0, - }, + encoder.copy_texture_to_texture( + wgpu::TextureCopyView { + texture: &self.texture, + array_layer: 0, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, }, - wgpu::TextureCopyView { - texture: &new_texture, - array_layer: i, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, - z: 0.0, - }, + }, + wgpu::TextureCopyView { + texture: &new_texture, + array_layer: 0, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, }, - wgpu::Extent3d { - width: ATLAS_SIZE, - height: ATLAS_SIZE, - depth: 1, - } - ); - } + }, + wgpu::Extent3d { + width: ATLAS_SIZE, + height: ATLAS_SIZE, + depth: self.texture_array_size, + } + ); + self.texture_array_size += grow_by; self.texture = new_texture; } } +trait RawImageData { + type Chunk; + + fn data(&self) -> &[Self::Chunk]; + fn width(&self) -> u32; + fn height(&self) -> u32; +} + +#[cfg(feature = "image")] +impl RawImageData for ::image::ImageBuffer<::image::Bgra, Vec> { + type Chunk = u8; + + fn data(&self) -> &[Self::Chunk] { + &self + } + + fn width(&self) -> u32 { + self.dimensions().0 + } + + fn height(&self) -> u32 { + self.dimensions().1 + } +} + +#[cfg(feature = "svg")] +impl RawImageData for resvg::raqote::DrawTarget { + type Chunk = u32; + + fn data(&self) -> &[Self::Chunk] { + self.get_data() + } + + fn width(&self) -> u32 { + self.width() as u32 + } + + fn height(&self) -> u32 { + self.height() as u32 + } +} + #[repr(C)] #[derive(Clone, Copy)] pub struct Vertex { @@ -616,7 +965,7 @@ const QUAD_VERTS: [Vertex; 4] = [ }, ]; -const ATLAS_SIZE: u32 = 8192; +const ATLAS_SIZE: u32 = 4096; #[repr(C)] #[derive(Clone, Copy)] diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 32afb749..387bd23a 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -1,19 +1,15 @@ -use crate::image::AtlasArray; +use crate::image::{TextureArray, ImageAllocation}; use iced_native::image; use std::{ collections::{HashMap, HashSet}, }; -use guillotiere::{Allocation, Size}; +use guillotiere::Size; use debug_stub_derive::*; #[derive(DebugStub)] pub enum Memory { Host(::image::ImageBuffer<::image::Bgra, Vec>), - Device { - layer: u32, - #[debug_stub="ReplacementValue"] - allocation: Allocation, - }, + Device(ImageAllocation), NotFound, Invalid, } @@ -22,10 +18,7 @@ impl Memory { pub fn dimensions(&self) -> (u32, u32) { match self { Memory::Host(image) => image.dimensions(), - Memory::Device { allocation, .. } => { - let size = &allocation.rectangle.size(); - (size.width as u32, size.height as u32) - }, + Memory::Device(allocation) => allocation.size(), Memory::NotFound => (1, 1), Memory::Invalid => (1, 1), } @@ -77,7 +70,7 @@ impl Cache { handle: &image::Handle, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, - atlas_array: &mut AtlasArray, + atlas_array: &mut TextureArray, ) -> &Memory { let _ = self.load(handle); @@ -87,29 +80,23 @@ impl Cache { let (width, height) = image.dimensions(); let size = Size::new(width as i32, height as i32); - let (layer, allocation) = atlas_array.allocate(size).unwrap_or_else(|| { - atlas_array.grow(1, device, encoder); - atlas_array.allocate(size).unwrap() - }); + let allocation = atlas_array.allocate(size); - let flat_samples = image.as_flat_samples(); - let slice = flat_samples.as_slice(); + atlas_array.upload(image, &allocation, device, encoder); - atlas_array.upload(slice, layer, &allocation, device, encoder); - - *memory = Memory::Device { layer, allocation }; + *memory = Memory::Device(allocation); } memory } - pub fn trim(&mut self, atlas_array: &mut AtlasArray) { + pub fn trim(&mut self, texture_array: &mut TextureArray) { let hits = &self.hits; for (id, mem) in &self.map { - if let Memory::Device { layer, allocation } = mem { + if let Memory::Device(allocation) = mem { if !hits.contains(&id) { - atlas_array.deallocate(*layer, allocation); + texture_array.deallocate(allocation); } } } diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 2972bc4a..a83d1a0a 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -1,9 +1,9 @@ -use crate::image::AtlasArray; +use crate::image::{TextureArray, ImageAllocation}; use iced_native::svg; use std::{ collections::{HashMap, HashSet}, }; -use guillotiere::{Allocation, Size}; +use guillotiere::Size; use debug_stub_derive::*; #[derive(DebugStub)] @@ -32,7 +32,7 @@ impl Svg { pub struct Cache { svgs: HashMap, #[debug_stub="ReplacementValue"] - rasterized: HashMap<(u64, u32, u32), (u32, Allocation)>, + rasterized: HashMap<(u64, u32, u32), ImageAllocation>, svg_hits: HashSet, rasterized_hits: HashSet<(u64, u32, u32)>, } @@ -70,8 +70,8 @@ impl Cache { scale: f32, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, - atlas_array: &mut AtlasArray, - ) -> Option<&(u32, Allocation)> { + texture_array: &mut TextureArray, + ) -> Option<&ImageAllocation> { let id = handle.id(); let (width, height) = ( @@ -100,10 +100,7 @@ impl Cache { let size = Size::new(width as i32, height as i32); - let (layer, allocation) = atlas_array.allocate(size).unwrap_or_else(|| { - atlas_array.grow(1, device, encoder); - atlas_array.allocate(size).unwrap() - }); + let array_allocation = texture_array.allocate(size); // TODO: Optimize! // We currently rerasterize the SVG when its size changes. This is slow @@ -124,15 +121,13 @@ impl Cache { &mut canvas, ); - let slice = canvas.get_data(); - - atlas_array.upload(slice, layer, &allocation, device, encoder); + texture_array.upload(&canvas, &array_allocation, device, encoder); let _ = self.svg_hits.insert(id); let _ = self.rasterized_hits.insert((id, width, height)); let _ = self .rasterized - .insert((id, width, height), (layer, allocation)); + .insert((id, width, height), array_allocation); self.rasterized.get(&(id, width, height)) } @@ -140,13 +135,13 @@ impl Cache { } } - pub fn trim(&mut self, atlas_array: &mut AtlasArray) { + pub fn trim(&mut self, texture_array: &mut TextureArray) { let svg_hits = &self.svg_hits; let rasterized_hits = &self.rasterized_hits; - for (k, (layer, allocation)) in &self.rasterized { + for (k, allocation) in &self.rasterized { if !rasterized_hits.contains(k) { - atlas_array.deallocate(*layer, allocation); + texture_array.deallocate(allocation); } } -- cgit From c0996923c6aab39bb61ca6d3310149c66a73fac8 Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Fri, 17 Jan 2020 20:15:20 +0100 Subject: Batch image draw calls into one with multiple instances --- wgpu/src/image.rs | 336 ++++++++++++++++++++++------------------------- wgpu/src/image/raster.rs | 12 +- wgpu/src/image/vector.rs | 15 +-- 3 files changed, 160 insertions(+), 203 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index f8faa543..9443b876 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -28,7 +28,6 @@ pub struct Pipeline { uniforms: wgpu::Buffer, vertices: wgpu::Buffer, indices: wgpu::Buffer, - instances: wgpu::Buffer, constants: wgpu::BindGroup, texture_layout: wgpu::BindGroupLayout, texture_array: TextureArray, @@ -212,11 +211,6 @@ impl Pipeline { .create_buffer_mapped(QUAD_INDICES.len(), wgpu::BufferUsage::INDEX) .fill_from_slice(&QUAD_INDICES); - let instances = device.create_buffer(&wgpu::BufferDescriptor { - size: mem::size_of::() as u64, - usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, - }); - let texture_array = TextureArray::new(device); Pipeline { @@ -230,7 +224,6 @@ impl Pipeline { uniforms: uniforms_buffer, vertices, indices, - instances, constants: constant_bind_group, texture_layout, texture_array, @@ -257,10 +250,10 @@ impl Pipeline { &mut self, device: &mut wgpu::Device, encoder: &mut wgpu::CommandEncoder, - instances: &[Image], + images: &[Image], transformation: Transformation, - _bounds: Rectangle, - _target: &wgpu::TextureView, + bounds: Rectangle, + target: &wgpu::TextureView, _scale: f32, ) { let uniforms_buffer = device @@ -277,7 +270,9 @@ impl Pipeline { std::mem::size_of::() as u64, ); - for image in instances { + let mut instances: Vec = Vec::new(); + + for image in images { match &image.handle { Handle::Raster(_handle) => { #[cfg(feature = "image")] @@ -290,13 +285,10 @@ impl Pipeline { encoder, &mut self.texture_array, ) { - self.draw_image( - device, - encoder, + add_instances( image, allocation, - _bounds, - _target, + &mut instances, ); } } @@ -315,38 +307,17 @@ impl Pipeline { encoder, &mut self.texture_array, ) { - self.draw_image( - device, - encoder, + add_instances( image, allocation, - _bounds, - _target, + &mut instances, ); } } } } } - } - - pub fn trim_cache(&mut self) { - #[cfg(feature = "image")] - self.raster_cache.borrow_mut().trim(&mut self.texture_array); - - #[cfg(feature = "svg")] - self.vector_cache.borrow_mut().trim(&mut self.texture_array); - } - fn draw_image( - &self, - device: &mut wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - image: &Image, - allocation: &ImageAllocation, - bounds: Rectangle, - target: &wgpu::TextureView, - ) { let texture = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &self.texture_layout, bindings: &[wgpu::Binding { @@ -357,82 +328,10 @@ impl Pipeline { }], }); - match allocation { - ImageAllocation::SingleAllocation(allocation) => { - self.draw_allocation( - device, - encoder, - image.position, - image.scale, - allocation, - &texture, - bounds, - target, - ) - } - ImageAllocation::MultipleAllocations { mappings, size } => { - let scaling_x = image.scale[0] / size.0 as f32; - let scaling_y = image.scale[1] / size.1 as f32; - - for mapping in mappings { - let mut position = image.position; - let mut scale = image.scale; - - position[0] += mapping.src_pos.0 as f32 * scaling_x; - position[1] += mapping.src_pos.1 as f32 * scaling_y; - scale[0] = mapping.allocation.size().0 as f32 * scaling_x; - scale[1] = mapping.allocation.size().1 as f32 * scaling_y; - - self.draw_allocation( - device, - encoder, - position, - scale, - &mapping.allocation, - &texture, - bounds, - target, - ) - } - } - _ => {} - } - } - - fn draw_allocation( - &self, - device: &mut wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - position: [f32; 2], - scale: [f32; 2], - allocation: &ArrayAllocation, - texture: &wgpu::BindGroup, - bounds: Rectangle, - target: &wgpu::TextureView, - ) { - let x = (allocation.position().0 as f32 + 0.5) / (ATLAS_SIZE as f32); - let y = (allocation.position().1 as f32 + 0.5) / (ATLAS_SIZE as f32); - let w = (allocation.size().0 as f32 - 0.5) / (ATLAS_SIZE as f32); - let h = (allocation.size().1 as f32 - 0.5) / (ATLAS_SIZE as f32); - let layer = allocation.layer() as f32; - - let instance_buffer = device - .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) - .fill_from_slice(&[Instance { - _position: position, - _scale: scale, - _position_in_atlas: [x, y], - _scale_in_atlas: [w, h], - _layer: layer, - }]); - - encoder.copy_buffer_to_buffer( - &instance_buffer, - 0, - &self.instances, - 0, - mem::size_of::() as u64, - ); + let instances_buffer = device.create_buffer_mapped( + instances.len(), + wgpu::BufferUsage::VERTEX, + ).fill_from_slice(&instances); let mut render_pass = encoder.begin_render_pass( &wgpu::RenderPassDescriptor { @@ -460,8 +359,9 @@ impl Pipeline { render_pass.set_index_buffer(&self.indices, 0); render_pass.set_vertex_buffers( 0, - &[(&self.vertices, 0), (&self.instances, 0)], + &[(&self.vertices, 0), (&instances_buffer, 0)], ); + render_pass.set_scissor_rect( bounds.x, bounds.y, @@ -472,9 +372,69 @@ impl Pipeline { render_pass.draw_indexed( 0..QUAD_INDICES.len() as u32, 0, - 0..1 as u32, + 0..instances.len() as u32, ); } + + pub fn trim_cache(&mut self) { + #[cfg(feature = "image")] + self.raster_cache.borrow_mut().trim(&mut self.texture_array); + + #[cfg(feature = "svg")] + self.vector_cache.borrow_mut().trim(&mut self.texture_array); + } +} + +fn add_instances( + image: &Image, + allocation: &ImageAllocation, + instances: &mut Vec, +) { + match allocation { + ImageAllocation::SingleAllocation(allocation) => { + add_instance(image.position, image.scale, allocation, instances); + } + ImageAllocation::MultipleAllocations { mappings, size } => { + let scaling_x = image.scale[0] / size.0 as f32; + let scaling_y = image.scale[1] / size.1 as f32; + + for mapping in mappings { + let allocation = &mapping.allocation; + let mut position = image.position; + let mut scale = image.scale; + + position[0] += mapping.src_pos.0 as f32 * scaling_x; + position[1] += mapping.src_pos.1 as f32 * scaling_y; + scale[0] = allocation.size().0 as f32 * scaling_x; + scale[1] = allocation.size().1 as f32 * scaling_y; + + add_instance(position, scale, allocation, instances); + } + } + } +} + +fn add_instance( + position: [f32; 2], + scale: [f32; 2], + allocation: &ArrayAllocation, + instances: &mut Vec, +) { + let x = (allocation.position().0 as f32 + 0.5) / (ATLAS_SIZE as f32); + let y = (allocation.position().1 as f32 + 0.5) / (ATLAS_SIZE as f32); + let w = (allocation.size().0 as f32 - 0.5) / (ATLAS_SIZE as f32); + let h = (allocation.size().1 as f32 - 0.5) / (ATLAS_SIZE as f32); + let layer = allocation.layer() as f32; + + let instance = Instance { + _position: position, + _scale: scale, + _position_in_atlas: [x, y], + _scale_in_atlas: [w, h], + _layer: layer, + }; + + instances.push(instance); } pub struct Image { @@ -501,10 +461,10 @@ pub enum ImageAllocation { mappings: Vec, size: (u32, u32), }, - Error, } impl ImageAllocation { + #[cfg(feature = "image")] pub fn size(&self) -> (u32, u32) { match self { ImageAllocation::SingleAllocation(allocation) => { @@ -513,7 +473,6 @@ impl ImageAllocation { ImageAllocation::MultipleAllocations { size, .. } => { *size } - _ => (0, 0) } } } @@ -522,7 +481,7 @@ impl ImageAllocation { pub enum ArrayAllocation { AtlasAllocation { layer: usize, - #[debug_stub = "ReplacementValue"] + #[debug_stub = "Allocation"] allocation: Allocation, }, WholeLayer { @@ -563,7 +522,7 @@ impl ArrayAllocation { pub enum TextureLayer { Whole, Atlas( - #[debug_stub="ReplacementValue"] + #[debug_stub="AtlasAllocator"] AtlasAllocator ), Empty, @@ -577,7 +536,7 @@ pub struct TextureArray { } impl TextureArray { - pub fn new(device: &wgpu::Device) -> Self { + fn new(device: &wgpu::Device) -> Self { let (width, height) = (ATLAS_SIZE, ATLAS_SIZE); let extent = wgpu::Extent3d { @@ -598,32 +557,30 @@ impl TextureArray { | wgpu::TextureUsage::SAMPLED, }); - let size = Size::new(ATLAS_SIZE as i32, ATLAS_SIZE as i32); - TextureArray { texture, texture_array_size: 1, - layers: vec!(TextureLayer::Atlas(AtlasAllocator::new(size))), + layers: vec!(TextureLayer::Empty), } } - pub fn allocate(&mut self, size: Size) -> ImageAllocation { + fn allocate(&mut self, size: Size) -> Option { // Allocate one layer if allocation fits perfectly if size.width == ATLAS_SIZE as i32 && size.height == ATLAS_SIZE as i32 { - for (i, layer) in &mut self.layers.iter_mut().enumerate() { + for (i, layer) in self.layers.iter_mut().enumerate() { if let TextureLayer::Empty = layer { *layer = TextureLayer::Whole; - return ImageAllocation::SingleAllocation( + return Some(ImageAllocation::SingleAllocation( ArrayAllocation::WholeLayer { layer: i } - ); + )); } } self.layers.push(TextureLayer::Whole); - return ImageAllocation::SingleAllocation( + return Some(ImageAllocation::SingleAllocation( ArrayAllocation::WholeLayer { layer: self.layers.len() - 1 } - ); + )); } // Split big allocations across multiple layers @@ -637,7 +594,11 @@ impl TextureArray { while x < size.width { let width = std::cmp::min(size.width - x, ATLAS_SIZE as i32); - if let ImageAllocation::SingleAllocation(allocation) = self.allocate(Size::new(width, height)) { + let allocation = self + .allocate(Size::new(width, height)) + .expect("Allocating texture space"); + + if let ImageAllocation::SingleAllocation(allocation) = allocation { let src_pos = (x as u32, y as u32); mappings.push(ArrayAllocationMapping { src_pos, allocation }); } @@ -647,10 +608,10 @@ impl TextureArray { y += height; } - return ImageAllocation::MultipleAllocations { + return Some(ImageAllocation::MultipleAllocations { mappings, size: (size.width as u32, size.height as u32), - }; + }); } // Try allocating on an existing layer @@ -658,7 +619,7 @@ impl TextureArray { if let TextureLayer::Atlas(allocator) = layer { if let Some(allocation) = allocator.allocate(size.clone()) { let array_allocation = ArrayAllocation::AtlasAllocation { layer: i, allocation }; - return ImageAllocation::SingleAllocation(array_allocation); + return Some(ImageAllocation::SingleAllocation(array_allocation)); } } } @@ -668,19 +629,19 @@ impl TextureArray { if let Some(allocation) = allocator.allocate(size) { self.layers.push(TextureLayer::Atlas(allocator)); - return ImageAllocation::SingleAllocation( + return Some(ImageAllocation::SingleAllocation( ArrayAllocation::AtlasAllocation { layer: self.layers.len() - 1, allocation, } - ); + )); } // One of the above should have worked - ImageAllocation::Error + None } - pub fn deallocate(&mut self, allocation: &ImageAllocation) { + fn deallocate(&mut self, allocation: &ImageAllocation) { match allocation { ImageAllocation::SingleAllocation(allocation) => { if let Some(layer) = self.layers.get_mut(allocation.layer()) { @@ -712,7 +673,6 @@ impl TextureArray { } } } - _ => {} } } @@ -720,15 +680,17 @@ impl TextureArray { fn upload( &mut self, image: &I, - allocation: &ImageAllocation, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, - ) + ) -> ImageAllocation where I: RawImageData, C: Copy + 'static, { - match allocation { + let size = Size::new(image.width() as i32, image.height() as i32); + let allocation = self.allocate(size).expect("Allocating texture space"); + + match &allocation { ImageAllocation::SingleAllocation(allocation) => { let data = image.data(); let buffer = device @@ -744,7 +706,7 @@ impl TextureArray { self.upload_texture( &buffer, - allocation, + &allocation, encoder, ); } @@ -752,6 +714,17 @@ impl TextureArray { let chunks_per_pixel = 4 / std::mem::size_of::(); let chunks_per_line = chunks_per_pixel * image.width() as usize; + let highest_layer = mappings + .iter() + .map(|m| m.allocation.layer() as u32) + .max() + .unwrap_or(0); + + if highest_layer >= self.texture_array_size { + let grow_by = 1 + highest_layer - self.texture_array_size; + self.grow(grow_by, device, encoder); + } + for mapping in mappings { let sub_width = mapping.allocation.size().0 as usize; let sub_height = mapping.allocation.size().1 as usize; @@ -777,17 +750,6 @@ impl TextureArray { buffer_line.copy_from_slice(sub_line); } - let highest_layer = mappings - .iter() - .map(|m| m.allocation.layer() as u32) - .max() - .unwrap_or(0); - - if highest_layer >= self.texture_array_size { - let grow_by = 1 + highest_layer - self.texture_array_size; - self.grow(grow_by, device, encoder); - } - self.upload_texture( &buffer.finish(), &mapping.allocation, @@ -795,9 +757,9 @@ impl TextureArray { ); } } - _ => {} } + allocation } fn upload_texture( @@ -867,33 +829,43 @@ impl TextureArray { | wgpu::TextureUsage::SAMPLED, }); - encoder.copy_texture_to_texture( - wgpu::TextureCopyView { - texture: &self.texture, - array_layer: 0, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, - z: 0.0, + for (i, layer) in self.layers.iter().enumerate() { + if i >= old_texture_array_size as usize { + break; + } + + if let TextureLayer::Empty = layer { + continue; + } + + encoder.copy_texture_to_texture( + wgpu::TextureCopyView { + texture: &self.texture, + array_layer: i as u32, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, }, - }, - wgpu::TextureCopyView { - texture: &new_texture, - array_layer: 0, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, - z: 0.0, + wgpu::TextureCopyView { + texture: &new_texture, + array_layer: i as u32, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, }, - }, - wgpu::Extent3d { - width: ATLAS_SIZE, - height: ATLAS_SIZE, - depth: self.texture_array_size, - } - ); + wgpu::Extent3d { + width: ATLAS_SIZE, + height: ATLAS_SIZE, + depth: 1, + } + ); + } self.texture_array_size += grow_by; self.texture = new_texture; @@ -968,7 +940,7 @@ const QUAD_VERTS: [Vertex; 4] = [ const ATLAS_SIZE: u32 = 4096; #[repr(C)] -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] struct Instance { _position: [f32; 2], _scale: [f32; 2], diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 387bd23a..bca2ebda 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -3,7 +3,6 @@ use iced_native::image; use std::{ collections::{HashMap, HashSet}, }; -use guillotiere::Size; use debug_stub_derive::*; #[derive(DebugStub)] @@ -72,17 +71,10 @@ impl Cache { encoder: &mut wgpu::CommandEncoder, atlas_array: &mut TextureArray, ) -> &Memory { - let _ = self.load(handle); - - let memory = self.map.get_mut(&handle.id()).unwrap(); + let memory = self.load(handle); if let Memory::Host(image) = memory { - let (width, height) = image.dimensions(); - let size = Size::new(width as i32, height as i32); - - let allocation = atlas_array.allocate(size); - - atlas_array.upload(image, &allocation, device, encoder); + let allocation = atlas_array.upload(image, device, encoder); *memory = Memory::Device(allocation); } diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index a83d1a0a..9bddcc2b 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -3,7 +3,6 @@ use iced_native::svg; use std::{ collections::{HashMap, HashSet}, }; -use guillotiere::Size; use debug_stub_derive::*; #[derive(DebugStub)] @@ -83,25 +82,19 @@ impl Cache { // We currently rerasterize the SVG when its size changes. This is slow // as heck. A GPU rasterizer like `pathfinder` may perform better. // It would be cool to be able to smooth resize the `svg` example. - if self.rasterized.get(&(id, width, height)).is_some() { + if self.rasterized.contains_key(&(id, width, height)) { let _ = self.svg_hits.insert(id); let _ = self.rasterized_hits.insert((id, width, height)); return self.rasterized.get(&(id, width, height)); } - let _ = self.load(handle); - - match self.svgs.get(&handle.id()).unwrap() { + match self.load(handle) { Svg::Loaded(tree) => { if width == 0 || height == 0 { return None; } - let size = Size::new(width as i32, height as i32); - - let array_allocation = texture_array.allocate(size); - // TODO: Optimize! // We currently rerasterize the SVG when its size changes. This is slow // as heck. A GPU rasterizer like `pathfinder` may perform better. @@ -121,13 +114,13 @@ impl Cache { &mut canvas, ); - texture_array.upload(&canvas, &array_allocation, device, encoder); + let allocation = texture_array.upload(&canvas, device, encoder); let _ = self.svg_hits.insert(id); let _ = self.rasterized_hits.insert((id, width, height)); let _ = self .rasterized - .insert((id, width, height), array_allocation); + .insert((id, width, height), allocation); self.rasterized.get(&(id, width, height)) } -- cgit From 2f695ef9803c9c08f64961f1b9902a661a385160 Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Fri, 17 Jan 2020 20:48:49 +0100 Subject: Updated shaders and removed debug_stub_derive dependency --- wgpu/Cargo.toml | 1 - wgpu/src/image.rs | 32 ++++++++++++++++++++++++-------- wgpu/src/image/raster.rs | 13 +++++++++++-- wgpu/src/image/vector.rs | 24 ++++++++++++++++-------- wgpu/src/shader/image.frag.spv | Bin 684 -> 584 bytes wgpu/src/shader/image.vert.spv | Bin 2324 -> 1596 bytes 6 files changed, 51 insertions(+), 19 deletions(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index f8b5bb8c..56839cf0 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -22,7 +22,6 @@ glam = "0.8" font-kit = "0.4" log = "0.4" guillotiere = "0.4" -debug_stub_derive = "0.3" [dependencies.image] version = "0.22" diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 9443b876..2fd73b54 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -15,7 +15,6 @@ use std::mem; use std::cell::RefCell; use guillotiere::{Allocation, AtlasAllocator, Size}; -use debug_stub_derive::*; #[derive(Debug)] pub struct Pipeline { @@ -477,11 +476,9 @@ impl ImageAllocation { } } -#[derive(DebugStub)] pub enum ArrayAllocation { AtlasAllocation { layer: usize, - #[debug_stub = "Allocation"] allocation: Allocation, }, WholeLayer { @@ -518,16 +515,35 @@ impl ArrayAllocation { } } -#[derive(DebugStub)] +impl std::fmt::Debug for ArrayAllocation { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ArrayAllocation::AtlasAllocation { layer, .. } => { + write!(f, "ArrayAllocation::AtlasAllocation {{ layer: {} }}", layer) + }, + ArrayAllocation::WholeLayer { layer } => { + write!(f, "ArrayAllocation::WholeLayer {{ layer: {} }}", layer) + } + } + } +} + pub enum TextureLayer { Whole, - Atlas( - #[debug_stub="AtlasAllocator"] - AtlasAllocator - ), + Atlas(AtlasAllocator), Empty, } +impl std::fmt::Debug for TextureLayer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TextureLayer::Whole => write!(f, "TextureLayer::Whole"), + TextureLayer::Atlas(_) => write!(f, "TextureLayer::Atlas"), + TextureLayer::Empty => write!(f, "TextureLayer::Empty"), + } + } +} + #[derive(Debug)] pub struct TextureArray { texture: wgpu::Texture, diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index bca2ebda..648df0ff 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -3,9 +3,7 @@ use iced_native::image; use std::{ collections::{HashMap, HashSet}, }; -use debug_stub_derive::*; -#[derive(DebugStub)] pub enum Memory { Host(::image::ImageBuffer<::image::Bgra, Vec>), Device(ImageAllocation), @@ -24,6 +22,17 @@ impl Memory { } } +impl std::fmt::Debug for Memory { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Memory::Host(_) => write!(f, "Memory::Host"), + Memory::Device(_) => write!(f, "Memory::Device"), + Memory::NotFound => write!(f, "Memory::NotFound"), + Memory::Invalid => write!(f, "Memory::Invalid"), + } + } +} + #[derive(Debug)] pub struct Cache { map: HashMap, diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 9bddcc2b..a8746566 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -3,14 +3,9 @@ use iced_native::svg; use std::{ collections::{HashMap, HashSet}, }; -use debug_stub_derive::*; -#[derive(DebugStub)] pub enum Svg { - Loaded( - #[debug_stub="ReplacementValue"] - resvg::usvg::Tree - ), + Loaded(resvg::usvg::Tree), NotFound, } @@ -27,10 +22,17 @@ impl Svg { } } -#[derive(DebugStub)] +impl std::fmt::Debug for Svg { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Svg::Loaded(_) => write!(f, "Svg::Loaded"), + Svg::NotFound => write!(f, "Svg::NotFound"), + } + } +} + pub struct Cache { svgs: HashMap, - #[debug_stub="ReplacementValue"] rasterized: HashMap<(u64, u32, u32), ImageAllocation>, svg_hits: HashSet, rasterized_hits: HashSet<(u64, u32, u32)>, @@ -144,3 +146,9 @@ impl Cache { self.rasterized_hits.clear(); } } + +impl std::fmt::Debug for Cache { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "vector::Cache") + } +} \ No newline at end of file diff --git a/wgpu/src/shader/image.frag.spv b/wgpu/src/shader/image.frag.spv index ebee82ac..6a6445b4 100644 Binary files a/wgpu/src/shader/image.frag.spv and b/wgpu/src/shader/image.frag.spv differ diff --git a/wgpu/src/shader/image.vert.spv b/wgpu/src/shader/image.vert.spv index da76eca0..cceff73a 100644 Binary files a/wgpu/src/shader/image.vert.spv and b/wgpu/src/shader/image.vert.spv differ -- cgit From 8f9f44b9e8ff1f1629d2b19edd2ecdad79e80836 Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Sat, 18 Jan 2020 12:49:11 +0100 Subject: When deallocating the last allocation in an allocator mark its layer as empty --- wgpu/src/image.rs | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 2fd73b54..97e30403 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -660,37 +660,36 @@ impl TextureArray { fn deallocate(&mut self, allocation: &ImageAllocation) { match allocation { ImageAllocation::SingleAllocation(allocation) => { - if let Some(layer) = self.layers.get_mut(allocation.layer()) { - match allocation { - ArrayAllocation::WholeLayer { .. } => { - *layer = TextureLayer::Empty; - } - ArrayAllocation::AtlasAllocation { allocation, .. } => { - if let TextureLayer::Atlas(allocator) = layer { - allocator.deallocate(allocation.id); - } - } - } - } + self.deallocate_single_allocation(allocation); } ImageAllocation::MultipleAllocations { mappings, .. } => { for mapping in mappings { - if let Some(layer) = self.layers.get_mut(mapping.allocation.layer()) { - match &mapping.allocation { - ArrayAllocation::WholeLayer { .. } => { - *layer = TextureLayer::Empty; - } - ArrayAllocation::AtlasAllocation { allocation, .. } => { - if let TextureLayer::Atlas(allocator) = layer { - allocator.deallocate(allocation.id); - } - } + self.deallocate_single_allocation(&mapping.allocation); + } + } + } + } + + fn deallocate_single_allocation(&mut self, allocation: &ArrayAllocation) { + if let Some(layer) = self.layers.get_mut(allocation.layer()) { + match allocation { + ArrayAllocation::WholeLayer { .. } => { + *layer = TextureLayer::Empty; + } + ArrayAllocation::AtlasAllocation { allocation, .. } => { + if let TextureLayer::Atlas(allocator) = layer { + allocator.deallocate(allocation.id); + + let mut empty_allocator = true; + allocator.for_each_allocated_rectangle(|_, _| empty_allocator = false); + + if empty_allocator { + *layer = TextureLayer::Empty; } } } } } - } fn upload( @@ -953,7 +952,7 @@ const QUAD_VERTS: [Vertex; 4] = [ }, ]; -const ATLAS_SIZE: u32 = 4096; +const ATLAS_SIZE: u32 = 256; #[repr(C)] #[derive(Debug, Clone, Copy)] -- cgit From 4617da2818eb3ecc17b1da9571b7baa15056c026 Mon Sep 17 00:00:00 2001 From: Malte Veerman Date: Sun, 19 Jan 2020 18:06:46 +0100 Subject: Implemented automatic deallocation of texture space for dropped allocations --- wgpu/src/image.rs | 155 ++++++++++++++++++++++++++--------------------- wgpu/src/image/raster.rs | 10 +-- wgpu/src/image/vector.rs | 8 +-- 3 files changed, 88 insertions(+), 85 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 97e30403..7eaa1ae8 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -9,10 +9,7 @@ use crate::image::raster::Memory; use crate::Transformation; use iced_native::{image, svg, Rectangle}; -use std::mem; - -#[cfg(any(feature = "image", feature = "svg"))] -use std::cell::RefCell; +use std::{cell::RefCell, mem, rc::Rc}; use guillotiere::{Allocation, AtlasAllocator, Size}; @@ -377,10 +374,10 @@ impl Pipeline { pub fn trim_cache(&mut self) { #[cfg(feature = "image")] - self.raster_cache.borrow_mut().trim(&mut self.texture_array); + self.raster_cache.borrow_mut().trim(); #[cfg(feature = "svg")] - self.vector_cache.borrow_mut().trim(&mut self.texture_array); + self.vector_cache.borrow_mut().trim(); } } @@ -423,14 +420,14 @@ fn add_instance( let y = (allocation.position().1 as f32 + 0.5) / (ATLAS_SIZE as f32); let w = (allocation.size().0 as f32 - 0.5) / (ATLAS_SIZE as f32); let h = (allocation.size().1 as f32 - 0.5) / (ATLAS_SIZE as f32); - let layer = allocation.layer() as f32; + let layer_index = allocation.layer_index() as f32; let instance = Instance { _position: position, _scale: scale, _position_in_atlas: [x, y], _scale_in_atlas: [w, h], - _layer: layer, + _layer: layer_index, }; instances.push(instance); @@ -478,11 +475,13 @@ impl ImageAllocation { pub enum ArrayAllocation { AtlasAllocation { - layer: usize, + layer_index: usize, + layer: Rc>, allocation: Allocation, }, WholeLayer { - layer: usize, + layer_index: usize, + layer: Rc>, } } @@ -507,10 +506,10 @@ impl ArrayAllocation { } } - pub fn layer(&self) -> usize { + pub fn layer_index(&self) -> usize { match self { - ArrayAllocation::AtlasAllocation { layer, .. } => *layer, - ArrayAllocation::WholeLayer { layer } => *layer, + ArrayAllocation::AtlasAllocation { layer_index, .. } => *layer_index, + ArrayAllocation::WholeLayer { layer_index, .. } => *layer_index, } } } @@ -518,11 +517,34 @@ impl ArrayAllocation { impl std::fmt::Debug for ArrayAllocation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - ArrayAllocation::AtlasAllocation { layer, .. } => { - write!(f, "ArrayAllocation::AtlasAllocation {{ layer: {} }}", layer) + ArrayAllocation::AtlasAllocation { layer_index, .. } => { + write!(f, "ArrayAllocation::AtlasAllocation {{ layer_index: {:} }}", layer_index) }, - ArrayAllocation::WholeLayer { layer } => { - write!(f, "ArrayAllocation::WholeLayer {{ layer: {} }}", layer) + ArrayAllocation::WholeLayer { layer_index, .. } => { + write!(f, "ArrayAllocation::WholeLayer {{ layer_index: {} }}", layer_index) + } + } + } +} + +impl Drop for ArrayAllocation { + fn drop(&mut self) { + match self { + ArrayAllocation::WholeLayer { layer, .. } => { + let _ = layer.replace(TextureLayer::Whole); + } + ArrayAllocation::AtlasAllocation { allocation, layer, .. } => { + let mut layer = layer.borrow_mut(); + if let Some(allocator) = layer.allocator_mut() { + allocator.deallocate(allocation.id); + + let mut empty_allocator = true; + allocator.for_each_allocated_rectangle(|_, _| empty_allocator = false); + + if empty_allocator { + *layer = TextureLayer::Empty; + } + } } } } @@ -534,6 +556,23 @@ pub enum TextureLayer { Empty, } +impl TextureLayer { + pub fn is_empty(&self) -> bool { + if let TextureLayer::Empty = self { + true + } else { + false + } + } + + pub fn allocator_mut(&mut self) -> Option<&mut AtlasAllocator> { + match self { + TextureLayer::Atlas(allocator) => Some(allocator), + _ => None + } + } +} + impl std::fmt::Debug for TextureLayer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -544,11 +583,17 @@ impl std::fmt::Debug for TextureLayer { } } +impl From for TextureLayer { + fn from(allocator: AtlasAllocator) -> Self { + TextureLayer::Atlas(allocator) + } +} + #[derive(Debug)] pub struct TextureArray { texture: wgpu::Texture, texture_array_size: u32, - layers: Vec, + layers: Vec>>, } impl TextureArray { @@ -576,7 +621,7 @@ impl TextureArray { TextureArray { texture, texture_array_size: 1, - layers: vec!(TextureLayer::Empty), + layers: vec!(Rc::new(RefCell::new(TextureLayer::Empty))), } } @@ -584,18 +629,19 @@ impl TextureArray { // Allocate one layer if allocation fits perfectly if size.width == ATLAS_SIZE as i32 && size.height == ATLAS_SIZE as i32 { for (i, layer) in self.layers.iter_mut().enumerate() { - if let TextureLayer::Empty = layer + if layer.borrow().is_empty() { - *layer = TextureLayer::Whole; + let _ = layer.replace(TextureLayer::Whole); return Some(ImageAllocation::SingleAllocation( - ArrayAllocation::WholeLayer { layer: i } + ArrayAllocation::WholeLayer { layer: layer.clone(), layer_index: i } )); } } - self.layers.push(TextureLayer::Whole); + let layer = Rc::new(RefCell::new(TextureLayer::Whole)); + self.layers.push(layer.clone()); return Some(ImageAllocation::SingleAllocation( - ArrayAllocation::WholeLayer { layer: self.layers.len() - 1 } + ArrayAllocation::WholeLayer { layer, layer_index: self.layers.len() - 1 } )); } @@ -632,9 +678,13 @@ impl TextureArray { // Try allocating on an existing layer for (i, layer) in self.layers.iter_mut().enumerate() { - if let TextureLayer::Atlas(allocator) = layer { + if let Some(allocator) = layer.borrow_mut().allocator_mut() { if let Some(allocation) = allocator.allocate(size.clone()) { - let array_allocation = ArrayAllocation::AtlasAllocation { layer: i, allocation }; + let array_allocation = ArrayAllocation::AtlasAllocation { + layer: layer.clone(), + layer_index: i, + allocation + }; return Some(ImageAllocation::SingleAllocation(array_allocation)); } } @@ -643,11 +693,13 @@ impl TextureArray { // Create new layer with atlas allocator let mut allocator = AtlasAllocator::new(Size::new(ATLAS_SIZE as i32, ATLAS_SIZE as i32)); if let Some(allocation) = allocator.allocate(size) { - self.layers.push(TextureLayer::Atlas(allocator)); + let layer = Rc::new(RefCell::new(allocator.into())); + self.layers.push(layer.clone()); return Some(ImageAllocation::SingleAllocation( ArrayAllocation::AtlasAllocation { - layer: self.layers.len() - 1, + layer, + layer_index: self.layers.len() - 1, allocation, } )); @@ -657,41 +709,6 @@ impl TextureArray { None } - fn deallocate(&mut self, allocation: &ImageAllocation) { - match allocation { - ImageAllocation::SingleAllocation(allocation) => { - self.deallocate_single_allocation(allocation); - } - ImageAllocation::MultipleAllocations { mappings, .. } => { - for mapping in mappings { - self.deallocate_single_allocation(&mapping.allocation); - } - } - } - } - - fn deallocate_single_allocation(&mut self, allocation: &ArrayAllocation) { - if let Some(layer) = self.layers.get_mut(allocation.layer()) { - match allocation { - ArrayAllocation::WholeLayer { .. } => { - *layer = TextureLayer::Empty; - } - ArrayAllocation::AtlasAllocation { allocation, .. } => { - if let TextureLayer::Atlas(allocator) = layer { - allocator.deallocate(allocation.id); - - let mut empty_allocator = true; - allocator.for_each_allocated_rectangle(|_, _| empty_allocator = false); - - if empty_allocator { - *layer = TextureLayer::Empty; - } - } - } - } - } - } - fn upload( &mut self, image: &I, @@ -715,7 +732,7 @@ impl TextureArray { ) .fill_from_slice(data); - if allocation.layer() >= self.texture_array_size as usize { + if allocation.layer_index() >= self.texture_array_size as usize { self.grow(1, device, encoder); } @@ -731,7 +748,7 @@ impl TextureArray { let highest_layer = mappings .iter() - .map(|m| m.allocation.layer() as u32) + .map(|m| m.allocation.layer_index() as u32) .max() .unwrap_or(0); @@ -783,7 +800,7 @@ impl TextureArray { allocation: &ArrayAllocation, encoder: &mut wgpu::CommandEncoder, ) { - let array_layer = allocation.layer() as u32; + let array_layer = allocation.layer_index() as u32; let (width, height) = allocation.size(); @@ -844,12 +861,12 @@ impl TextureArray { | wgpu::TextureUsage::SAMPLED, }); - for (i, layer) in self.layers.iter().enumerate() { + for (i, layer) in self.layers.iter_mut().enumerate() { if i >= old_texture_array_size as usize { break; } - if let TextureLayer::Empty = layer { + if layer.borrow().is_empty() { continue; } @@ -952,7 +969,7 @@ const QUAD_VERTS: [Vertex; 4] = [ }, ]; -const ATLAS_SIZE: u32 = 256; +const ATLAS_SIZE: u32 = 4096; #[repr(C)] #[derive(Debug, Clone, Copy)] diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 648df0ff..884dd65a 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -91,17 +91,9 @@ impl Cache { memory } - pub fn trim(&mut self, texture_array: &mut TextureArray) { + pub fn trim(&mut self) { let hits = &self.hits; - for (id, mem) in &self.map { - if let Memory::Device(allocation) = mem { - if !hits.contains(&id) { - texture_array.deallocate(allocation); - } - } - } - self.map.retain(|k, _| hits.contains(k)); self.hits.clear(); } diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index a8746566..98588e5c 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -130,16 +130,10 @@ impl Cache { } } - pub fn trim(&mut self, texture_array: &mut TextureArray) { + pub fn trim(&mut self) { let svg_hits = &self.svg_hits; let rasterized_hits = &self.rasterized_hits; - for (k, allocation) in &self.rasterized { - if !rasterized_hits.contains(k) { - texture_array.deallocate(allocation); - } - } - self.svgs.retain(|k, _| svg_hits.contains(k)); self.rasterized.retain(|k, _| rasterized_hits.contains(k)); self.svg_hits.clear(); -- cgit From 82f0a49062d10f3cbf202a5379c061a2509ec97b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 25 Feb 2020 13:27:43 +0100 Subject: Recompile `image` shaders --- wgpu/src/shader/image.frag.spv | Bin 584 -> 684 bytes wgpu/src/shader/image.vert.spv | Bin 1596 -> 2472 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/wgpu/src/shader/image.frag.spv b/wgpu/src/shader/image.frag.spv index 6a6445b4..65b08aa3 100644 Binary files a/wgpu/src/shader/image.frag.spv and b/wgpu/src/shader/image.frag.spv differ diff --git a/wgpu/src/shader/image.vert.spv b/wgpu/src/shader/image.vert.spv index cceff73a..192bf6c3 100644 Binary files a/wgpu/src/shader/image.vert.spv and b/wgpu/src/shader/image.vert.spv differ -- cgit From 59d45a5440aaa46c7dc8f3dc70c8518167c10418 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Feb 2020 12:34:34 +0100 Subject: Refactor texture atlas - Split into multiple modules - Rename some concepts - Change API details --- wgpu/src/image.rs | 768 +++++++---------------------------- wgpu/src/image/raster.rs | 27 +- wgpu/src/image/vector.rs | 34 +- wgpu/src/lib.rs | 3 +- wgpu/src/renderer.rs | 4 +- wgpu/src/texture.rs | 3 + wgpu/src/texture/atlas.rs | 313 ++++++++++++++ wgpu/src/texture/atlas/allocation.rs | 35 ++ wgpu/src/texture/atlas/allocator.rs | 57 +++ wgpu/src/texture/atlas/entry.rs | 25 ++ wgpu/src/texture/atlas/layer.rs | 17 + 11 files changed, 633 insertions(+), 653 deletions(-) create mode 100644 wgpu/src/texture.rs create mode 100644 wgpu/src/texture/atlas.rs create mode 100644 wgpu/src/texture/atlas/allocation.rs create mode 100644 wgpu/src/texture/atlas/allocator.rs create mode 100644 wgpu/src/texture/atlas/entry.rs create mode 100644 wgpu/src/texture/atlas/layer.rs diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 7eaa1ae8..e14b3024 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -3,15 +3,13 @@ mod raster; #[cfg(feature = "svg")] mod vector; -#[cfg(feature = "image")] -use crate::image::raster::Memory; +use crate::{ + texture::{self, atlas}, + Transformation, +}; -use crate::Transformation; use iced_native::{image, svg, Rectangle}; - -use std::{cell::RefCell, mem, rc::Rc}; - -use guillotiere::{Allocation, AtlasAllocator, Size}; +use std::{cell::RefCell, mem}; #[derive(Debug)] pub struct Pipeline { @@ -25,8 +23,10 @@ pub struct Pipeline { vertices: wgpu::Buffer, indices: wgpu::Buffer, constants: wgpu::BindGroup, + texture: wgpu::BindGroup, + texture_version: usize, texture_layout: wgpu::BindGroupLayout, - texture_array: TextureArray, + texture_atlas: texture::Atlas, } impl Pipeline { @@ -207,7 +207,17 @@ impl Pipeline { .create_buffer_mapped(QUAD_INDICES.len(), wgpu::BufferUsage::INDEX) .fill_from_slice(&QUAD_INDICES); - let texture_array = TextureArray::new(device); + let texture_atlas = texture::Atlas::new(device); + + let texture = device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &texture_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::TextureView( + &texture_atlas.view(), + ), + }], + }); Pipeline { #[cfg(feature = "image")] @@ -221,8 +231,10 @@ impl Pipeline { vertices, indices, constants: constant_bind_group, + texture, + texture_version: texture_atlas.layer_count(), texture_layout, - texture_array, + texture_atlas, } } @@ -252,85 +264,88 @@ impl Pipeline { target: &wgpu::TextureView, _scale: f32, ) { - let uniforms_buffer = device - .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) - .fill_from_slice(&[Uniforms { - transform: transformation.into(), - }]); + let mut instances: Vec = Vec::new(); - encoder.copy_buffer_to_buffer( - &uniforms_buffer, - 0, - &self.uniforms, - 0, - std::mem::size_of::() as u64, - ); + #[cfg(feature = "image")] + let mut raster_cache = self.raster_cache.borrow_mut(); - let mut instances: Vec = Vec::new(); + #[cfg(feature = "svg")] + let mut vector_cache = self.vector_cache.borrow_mut(); for image in images { match &image.handle { Handle::Raster(_handle) => { #[cfg(feature = "image")] { - let mut raster_cache = self.raster_cache.borrow_mut(); - - if let Memory::Device(allocation) = raster_cache.upload( + if let Some(atlas_entry) = raster_cache.upload( _handle, device, encoder, - &mut self.texture_array, + &mut self.texture_atlas, ) { - add_instances( - image, - allocation, - &mut instances, - ); + add_instances(image, atlas_entry, &mut instances); } - } + }; } Handle::Vector(_handle) => { #[cfg(feature = "svg")] { - let mut vector_cache = self.vector_cache.borrow_mut(); - - // Upload rasterized svg to texture atlas - if let Some(allocation) = vector_cache.upload( + if let Some(atlas_entry) = vector_cache.upload( _handle, - image.scale, + image.size, _scale, device, encoder, - &mut self.texture_array, + &mut self.texture_atlas, ) { - add_instances( - image, - allocation, - &mut instances, - ); + add_instances(image, atlas_entry, &mut instances); } - } + }; } } } - let texture = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &self.texture_layout, - bindings: &[wgpu::Binding { - binding: 0, - resource: wgpu::BindingResource::TextureView( - &self.texture_array.texture.create_default_view(), - ), - }], - }); + if instances.is_empty() { + return; + } + + let texture_version = self.texture_atlas.layer_count(); + + if self.texture_version != texture_version { + self.texture = + device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &self.texture_layout, + bindings: &[wgpu::Binding { + binding: 0, + resource: wgpu::BindingResource::TextureView( + &self.texture_atlas.view(), + ), + }], + }); - let instances_buffer = device.create_buffer_mapped( - instances.len(), - wgpu::BufferUsage::VERTEX, - ).fill_from_slice(&instances); + self.texture_version = texture_version; + } - let mut render_pass = encoder.begin_render_pass( - &wgpu::RenderPassDescriptor { + let uniforms_buffer = device + .create_buffer_mapped(1, wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(&[Uniforms { + transform: transformation.into(), + }]); + + encoder.copy_buffer_to_buffer( + &uniforms_buffer, + 0, + &self.uniforms, + 0, + std::mem::size_of::() as u64, + ); + + let instances_buffer = device + .create_buffer_mapped(instances.len(), wgpu::BufferUsage::VERTEX) + .fill_from_slice(&instances); + + let mut render_pass = + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { color_attachments: &[ wgpu::RenderPassColorAttachmentDescriptor { attachment: target, @@ -346,12 +361,11 @@ impl Pipeline { }, ], depth_stencil_attachment: None, - }, - ); + }); render_pass.set_pipeline(&self.pipeline); render_pass.set_bind_group(0, &self.constants, &[]); - render_pass.set_bind_group(1, &texture, &[]); + render_pass.set_bind_group(1, &self.texture, &[]); render_pass.set_index_buffer(&self.indices, 0); render_pass.set_vertex_buffers( 0, @@ -381,62 +395,10 @@ impl Pipeline { } } -fn add_instances( - image: &Image, - allocation: &ImageAllocation, - instances: &mut Vec, -) { - match allocation { - ImageAllocation::SingleAllocation(allocation) => { - add_instance(image.position, image.scale, allocation, instances); - } - ImageAllocation::MultipleAllocations { mappings, size } => { - let scaling_x = image.scale[0] / size.0 as f32; - let scaling_y = image.scale[1] / size.1 as f32; - - for mapping in mappings { - let allocation = &mapping.allocation; - let mut position = image.position; - let mut scale = image.scale; - - position[0] += mapping.src_pos.0 as f32 * scaling_x; - position[1] += mapping.src_pos.1 as f32 * scaling_y; - scale[0] = allocation.size().0 as f32 * scaling_x; - scale[1] = allocation.size().1 as f32 * scaling_y; - - add_instance(position, scale, allocation, instances); - } - } - } -} - -fn add_instance( - position: [f32; 2], - scale: [f32; 2], - allocation: &ArrayAllocation, - instances: &mut Vec, -) { - let x = (allocation.position().0 as f32 + 0.5) / (ATLAS_SIZE as f32); - let y = (allocation.position().1 as f32 + 0.5) / (ATLAS_SIZE as f32); - let w = (allocation.size().0 as f32 - 0.5) / (ATLAS_SIZE as f32); - let h = (allocation.size().1 as f32 - 0.5) / (ATLAS_SIZE as f32); - let layer_index = allocation.layer_index() as f32; - - let instance = Instance { - _position: position, - _scale: scale, - _position_in_atlas: [x, y], - _scale_in_atlas: [w, h], - _layer: layer_index, - }; - - instances.push(instance); -} - pub struct Image { pub handle: Handle, pub position: [f32; 2], - pub scale: [f32; 2], + pub size: [f32; 2], } pub enum Handle { @@ -444,508 +406,6 @@ pub enum Handle { Vector(svg::Handle), } -#[derive(Debug)] -pub struct ArrayAllocationMapping { - src_pos: (u32, u32), - allocation: ArrayAllocation, -} - -#[derive(Debug)] -pub enum ImageAllocation { - SingleAllocation(ArrayAllocation), - MultipleAllocations { - mappings: Vec, - size: (u32, u32), - }, -} - -impl ImageAllocation { - #[cfg(feature = "image")] - pub fn size(&self) -> (u32, u32) { - match self { - ImageAllocation::SingleAllocation(allocation) => { - allocation.size() - } - ImageAllocation::MultipleAllocations { size, .. } => { - *size - } - } - } -} - -pub enum ArrayAllocation { - AtlasAllocation { - layer_index: usize, - layer: Rc>, - allocation: Allocation, - }, - WholeLayer { - layer_index: usize, - layer: Rc>, - } -} - -impl ArrayAllocation { - pub fn size(&self) -> (u32, u32) { - match self { - ArrayAllocation::AtlasAllocation { allocation, .. } => { - let size = allocation.rectangle.size(); - (size.width as u32, size.height as u32) - } - ArrayAllocation::WholeLayer { .. } => (ATLAS_SIZE, ATLAS_SIZE) - } - } - - pub fn position(&self) -> (u32, u32) { - match self { - ArrayAllocation::AtlasAllocation { allocation, .. } => { - let min = &allocation.rectangle.min; - (min.x as u32, min.y as u32) - } - ArrayAllocation::WholeLayer { .. } => (0, 0) - } - } - - pub fn layer_index(&self) -> usize { - match self { - ArrayAllocation::AtlasAllocation { layer_index, .. } => *layer_index, - ArrayAllocation::WholeLayer { layer_index, .. } => *layer_index, - } - } -} - -impl std::fmt::Debug for ArrayAllocation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ArrayAllocation::AtlasAllocation { layer_index, .. } => { - write!(f, "ArrayAllocation::AtlasAllocation {{ layer_index: {:} }}", layer_index) - }, - ArrayAllocation::WholeLayer { layer_index, .. } => { - write!(f, "ArrayAllocation::WholeLayer {{ layer_index: {} }}", layer_index) - } - } - } -} - -impl Drop for ArrayAllocation { - fn drop(&mut self) { - match self { - ArrayAllocation::WholeLayer { layer, .. } => { - let _ = layer.replace(TextureLayer::Whole); - } - ArrayAllocation::AtlasAllocation { allocation, layer, .. } => { - let mut layer = layer.borrow_mut(); - if let Some(allocator) = layer.allocator_mut() { - allocator.deallocate(allocation.id); - - let mut empty_allocator = true; - allocator.for_each_allocated_rectangle(|_, _| empty_allocator = false); - - if empty_allocator { - *layer = TextureLayer::Empty; - } - } - } - } - } -} - -pub enum TextureLayer { - Whole, - Atlas(AtlasAllocator), - Empty, -} - -impl TextureLayer { - pub fn is_empty(&self) -> bool { - if let TextureLayer::Empty = self { - true - } else { - false - } - } - - pub fn allocator_mut(&mut self) -> Option<&mut AtlasAllocator> { - match self { - TextureLayer::Atlas(allocator) => Some(allocator), - _ => None - } - } -} - -impl std::fmt::Debug for TextureLayer { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TextureLayer::Whole => write!(f, "TextureLayer::Whole"), - TextureLayer::Atlas(_) => write!(f, "TextureLayer::Atlas"), - TextureLayer::Empty => write!(f, "TextureLayer::Empty"), - } - } -} - -impl From for TextureLayer { - fn from(allocator: AtlasAllocator) -> Self { - TextureLayer::Atlas(allocator) - } -} - -#[derive(Debug)] -pub struct TextureArray { - texture: wgpu::Texture, - texture_array_size: u32, - layers: Vec>>, -} - -impl TextureArray { - fn new(device: &wgpu::Device) -> Self { - let (width, height) = (ATLAS_SIZE, ATLAS_SIZE); - - let extent = wgpu::Extent3d { - width, - height, - depth: 1, - }; - - let texture = 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::COPY_DST - | wgpu::TextureUsage::COPY_SRC - | wgpu::TextureUsage::SAMPLED, - }); - - TextureArray { - texture, - texture_array_size: 1, - layers: vec!(Rc::new(RefCell::new(TextureLayer::Empty))), - } - } - - fn allocate(&mut self, size: Size) -> Option { - // Allocate one layer if allocation fits perfectly - if size.width == ATLAS_SIZE as i32 && size.height == ATLAS_SIZE as i32 { - for (i, layer) in self.layers.iter_mut().enumerate() { - if layer.borrow().is_empty() - { - let _ = layer.replace(TextureLayer::Whole); - return Some(ImageAllocation::SingleAllocation( - ArrayAllocation::WholeLayer { layer: layer.clone(), layer_index: i } - )); - } - } - - let layer = Rc::new(RefCell::new(TextureLayer::Whole)); - self.layers.push(layer.clone()); - return Some(ImageAllocation::SingleAllocation( - ArrayAllocation::WholeLayer { layer, layer_index: self.layers.len() - 1 } - )); - } - - // Split big allocations across multiple layers - if size.width > ATLAS_SIZE as i32 || size.height > ATLAS_SIZE as i32 { - let mut mappings = Vec::new(); - - let mut y = 0; - while y < size.height { - let height = std::cmp::min(size.height - y, ATLAS_SIZE as i32); - let mut x = 0; - - while x < size.width { - let width = std::cmp::min(size.width - x, ATLAS_SIZE as i32); - let allocation = self - .allocate(Size::new(width, height)) - .expect("Allocating texture space"); - - if let ImageAllocation::SingleAllocation(allocation) = allocation { - let src_pos = (x as u32, y as u32); - mappings.push(ArrayAllocationMapping { src_pos, allocation }); - } - - x += width; - } - y += height; - } - - return Some(ImageAllocation::MultipleAllocations { - mappings, - size: (size.width as u32, size.height as u32), - }); - } - - // Try allocating on an existing layer - for (i, layer) in self.layers.iter_mut().enumerate() { - if let Some(allocator) = layer.borrow_mut().allocator_mut() { - if let Some(allocation) = allocator.allocate(size.clone()) { - let array_allocation = ArrayAllocation::AtlasAllocation { - layer: layer.clone(), - layer_index: i, - allocation - }; - return Some(ImageAllocation::SingleAllocation(array_allocation)); - } - } - } - - // Create new layer with atlas allocator - let mut allocator = AtlasAllocator::new(Size::new(ATLAS_SIZE as i32, ATLAS_SIZE as i32)); - if let Some(allocation) = allocator.allocate(size) { - let layer = Rc::new(RefCell::new(allocator.into())); - self.layers.push(layer.clone()); - - return Some(ImageAllocation::SingleAllocation( - ArrayAllocation::AtlasAllocation { - layer, - layer_index: self.layers.len() - 1, - allocation, - } - )); - } - - // One of the above should have worked - None - } - - fn upload( - &mut self, - image: &I, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - ) -> ImageAllocation - where - I: RawImageData, - C: Copy + 'static, - { - let size = Size::new(image.width() as i32, image.height() as i32); - let allocation = self.allocate(size).expect("Allocating texture space"); - - match &allocation { - ImageAllocation::SingleAllocation(allocation) => { - let data = image.data(); - let buffer = device - .create_buffer_mapped( - data.len(), - wgpu::BufferUsage::COPY_SRC, - ) - .fill_from_slice(data); - - if allocation.layer_index() >= self.texture_array_size as usize { - self.grow(1, device, encoder); - } - - self.upload_texture( - &buffer, - &allocation, - encoder, - ); - } - ImageAllocation::MultipleAllocations { mappings, .. } => { - let chunks_per_pixel = 4 / std::mem::size_of::(); - let chunks_per_line = chunks_per_pixel * image.width() as usize; - - let highest_layer = mappings - .iter() - .map(|m| m.allocation.layer_index() as u32) - .max() - .unwrap_or(0); - - if highest_layer >= self.texture_array_size { - let grow_by = 1 + highest_layer - self.texture_array_size; - self.grow(grow_by, device, encoder); - } - - for mapping in mappings { - let sub_width = mapping.allocation.size().0 as usize; - let sub_height = mapping.allocation.size().1 as usize; - let sub_line_start = mapping.src_pos.0 as usize * chunks_per_pixel; - let sub_line_end = (mapping.src_pos.0 as usize + sub_width) * chunks_per_pixel; - - let mut sub_lines = image - .data() - .chunks(chunks_per_line) - .skip(mapping.src_pos.1 as usize) - .take(sub_height) - .map(|line| &line[sub_line_start..sub_line_end]); - - let buffer = device - .create_buffer_mapped( - chunks_per_pixel * sub_width * sub_height, - wgpu::BufferUsage::COPY_SRC, - ); - - let mut buffer_lines = buffer.data.chunks_mut(sub_width * chunks_per_pixel); - - while let (Some(buffer_line), Some(sub_line)) = (buffer_lines.next(), sub_lines.next()) { - buffer_line.copy_from_slice(sub_line); - } - - self.upload_texture( - &buffer.finish(), - &mapping.allocation, - encoder, - ); - } - } - } - - allocation - } - - fn upload_texture( - &mut self, - buffer: &wgpu::Buffer, - allocation: &ArrayAllocation, - encoder: &mut wgpu::CommandEncoder, - ) { - let array_layer = allocation.layer_index() as u32; - - let (width, height) = allocation.size(); - - let extent = wgpu::Extent3d { - width, - height, - depth: 1, - }; - - let (x, y) = allocation.position(); - - encoder.copy_buffer_to_texture( - wgpu::BufferCopyView { - buffer, - offset: 0, - row_pitch: 4 * width, - image_height: height, - }, - wgpu::TextureCopyView { - texture: &self.texture, - array_layer, - mip_level: 0, - origin: wgpu::Origin3d { - x: x as f32, - y: y as f32, - z: 0.0, - }, - }, - extent, - ); - } - - fn grow( - &mut self, - grow_by: u32, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - ) { - if grow_by == 0 { - return; - } - - let old_texture_array_size = self.texture_array_size; - - let new_texture = device.create_texture(&wgpu::TextureDescriptor { - size: wgpu::Extent3d { - width: ATLAS_SIZE, - height: ATLAS_SIZE, - depth: 1, - }, - array_layer_count: old_texture_array_size + grow_by, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bgra8UnormSrgb, - usage: wgpu::TextureUsage::COPY_DST - | wgpu::TextureUsage::COPY_SRC - | wgpu::TextureUsage::SAMPLED, - }); - - for (i, layer) in self.layers.iter_mut().enumerate() { - if i >= old_texture_array_size as usize { - break; - } - - if layer.borrow().is_empty() { - continue; - } - - encoder.copy_texture_to_texture( - wgpu::TextureCopyView { - texture: &self.texture, - array_layer: i as u32, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, - z: 0.0, - }, - }, - wgpu::TextureCopyView { - texture: &new_texture, - array_layer: i as u32, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, - z: 0.0, - }, - }, - wgpu::Extent3d { - width: ATLAS_SIZE, - height: ATLAS_SIZE, - depth: 1, - } - ); - } - - self.texture_array_size += grow_by; - self.texture = new_texture; - } -} - -trait RawImageData { - type Chunk; - - fn data(&self) -> &[Self::Chunk]; - fn width(&self) -> u32; - fn height(&self) -> u32; -} - -#[cfg(feature = "image")] -impl RawImageData for ::image::ImageBuffer<::image::Bgra, Vec> { - type Chunk = u8; - - fn data(&self) -> &[Self::Chunk] { - &self - } - - fn width(&self) -> u32 { - self.dimensions().0 - } - - fn height(&self) -> u32 { - self.dimensions().1 - } -} - -#[cfg(feature = "svg")] -impl RawImageData for resvg::raqote::DrawTarget { - type Chunk = u32; - - fn data(&self) -> &[Self::Chunk] { - self.get_data() - } - - fn width(&self) -> u32 { - self.width() as u32 - } - - fn height(&self) -> u32 { - self.height() as u32 - } -} - #[repr(C)] #[derive(Clone, Copy)] pub struct Vertex { @@ -969,16 +429,14 @@ const QUAD_VERTS: [Vertex; 4] = [ }, ]; -const ATLAS_SIZE: u32 = 4096; - #[repr(C)] #[derive(Debug, Clone, Copy)] struct Instance { _position: [f32; 2], - _scale: [f32; 2], + _size: [f32; 2], _position_in_atlas: [f32; 2], - _scale_in_atlas: [f32; 2], - _layer: f32, + _size_in_atlas: [f32; 2], + _layer: u32, } #[repr(C)] @@ -986,3 +444,67 @@ struct Instance { struct Uniforms { transform: [f32; 16], } + +fn add_instances( + image: &Image, + entry: &atlas::Entry, + instances: &mut Vec, +) { + match entry { + atlas::Entry::Contiguous(allocation) => { + add_instance(image.position, image.size, allocation, instances); + } + atlas::Entry::Fragmented { fragments, size } => { + let scaling_x = image.size[0] / size.0 as f32; + let scaling_y = image.size[1] / size.1 as f32; + + for fragment in fragments { + let allocation = &fragment.allocation; + + let [x, y] = image.position; + let (fragment_x, fragment_y) = fragment.position; + let (fragment_width, fragment_height) = allocation.size(); + + let position = [ + x + fragment_x as f32 * scaling_x, + y + fragment_y as f32 * scaling_y, + ]; + + let size = [ + fragment_width as f32 * scaling_x, + fragment_height as f32 * scaling_y, + ]; + + add_instance(position, size, allocation, instances); + } + } + } +} + +#[inline] +fn add_instance( + position: [f32; 2], + size: [f32; 2], + allocation: &atlas::Allocation, + instances: &mut Vec, +) { + let (x, y) = allocation.position(); + let (width, height) = allocation.size(); + let layer = allocation.layer(); + + let instance = Instance { + _position: position, + _size: size, + _position_in_atlas: [ + x as f32 / atlas::SIZE as f32, + y as f32 / atlas::SIZE as f32, + ], + _size_in_atlas: [ + width as f32 / atlas::SIZE as f32, + height as f32 / atlas::SIZE as f32, + ], + _layer: layer as u32, + }; + + instances.push(instance); +} diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 884dd65a..071d53c8 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -1,12 +1,10 @@ -use crate::image::{TextureArray, ImageAllocation}; +use crate::texture::atlas::{self, Atlas}; use iced_native::image; -use std::{ - collections::{HashMap, HashSet}, -}; +use std::collections::{HashMap, HashSet}; pub enum Memory { Host(::image::ImageBuffer<::image::Bgra, Vec>), - Device(ImageAllocation), + Device(atlas::Entry), NotFound, Invalid, } @@ -15,7 +13,7 @@ impl Memory { pub fn dimensions(&self) -> (u32, u32) { match self { Memory::Host(image) => image.dimensions(), - Memory::Device(allocation) => allocation.size(), + Memory::Device(entry) => entry.size(), Memory::NotFound => (1, 1), Memory::Invalid => (1, 1), } @@ -78,17 +76,26 @@ impl Cache { handle: &image::Handle, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, - atlas_array: &mut TextureArray, - ) -> &Memory { + atlas: &mut Atlas, + ) -> Option<&atlas::Entry> { let memory = self.load(handle); if let Memory::Host(image) = memory { - let allocation = atlas_array.upload(image, device, encoder); + let (width, height) = image.dimensions(); + + let allocation = + atlas.upload(width, height, &image, device, encoder)?; + + dbg!("Uploaded"); *memory = Memory::Device(allocation); } - memory + if let Memory::Device(allocation) = memory { + Some(allocation) + } else { + None + } } pub fn trim(&mut self) { diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 98588e5c..0dabc9ca 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -1,8 +1,6 @@ -use crate::image::{TextureArray, ImageAllocation}; +use crate::texture::atlas::{self, Atlas}; use iced_native::svg; -use std::{ - collections::{HashMap, HashSet}, -}; +use std::collections::{HashMap, HashSet}; pub enum Svg { Loaded(resvg::usvg::Tree), @@ -33,7 +31,7 @@ impl std::fmt::Debug for Svg { pub struct Cache { svgs: HashMap, - rasterized: HashMap<(u64, u32, u32), ImageAllocation>, + rasterized: HashMap<(u64, u32, u32), atlas::Entry>, svg_hits: HashSet, rasterized_hits: HashSet<(u64, u32, u32)>, } @@ -71,8 +69,8 @@ impl Cache { scale: f32, device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, - texture_array: &mut TextureArray, - ) -> Option<&ImageAllocation> { + texture_atlas: &mut Atlas, + ) -> Option<&atlas::Entry> { let id = handle.id(); let (width, height) = ( @@ -104,10 +102,8 @@ impl Cache { let screen_size = resvg::ScreenSize::new(width, height).unwrap(); - let mut canvas = resvg::raqote::DrawTarget::new( - width as i32, - height as i32, - ); + let mut canvas = + resvg::raqote::DrawTarget::new(width as i32, height as i32); resvg::backend_raqote::render_to_canvas( tree, @@ -116,17 +112,21 @@ impl Cache { &mut canvas, ); - let allocation = texture_array.upload(&canvas, device, encoder); + let allocation = texture_atlas.upload( + width, + height, + canvas.get_data(), + device, + encoder, + )?; let _ = self.svg_hits.insert(id); let _ = self.rasterized_hits.insert((id, width, height)); - let _ = self - .rasterized - .insert((id, width, height), allocation); + let _ = self.rasterized.insert((id, width, height), allocation); self.rasterized.get(&(id, width, height)) } - Svg::NotFound => None + Svg::NotFound => None, } } @@ -145,4 +145,4 @@ impl std::fmt::Debug for Cache { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "vector::Cache") } -} \ No newline at end of file +} diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 4f2b732d..832da31d 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -19,13 +19,14 @@ //! [`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)] #![forbid(rust_2018_idioms)] pub mod defaults; pub mod settings; +pub mod texture; pub mod triangle; pub mod widget; pub mod window; diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index f67dd1eb..b5dce480 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -227,14 +227,14 @@ impl Renderer { layer.images.push(Image { handle: image::Handle::Raster(handle.clone()), position: [bounds.x, bounds.y], - scale: [bounds.width, bounds.height], + size: [bounds.width, bounds.height], }); } Primitive::Svg { handle, bounds } => { layer.images.push(Image { handle: image::Handle::Vector(handle.clone()), position: [bounds.x, bounds.y], - scale: [bounds.width, bounds.height], + size: [bounds.width, bounds.height], }); } Primitive::Mesh2D { origin, buffers } => { diff --git a/wgpu/src/texture.rs b/wgpu/src/texture.rs new file mode 100644 index 00000000..00b60bfa --- /dev/null +++ b/wgpu/src/texture.rs @@ -0,0 +1,3 @@ +pub mod atlas; + +pub use atlas::Atlas; diff --git a/wgpu/src/texture/atlas.rs b/wgpu/src/texture/atlas.rs new file mode 100644 index 00000000..b950e59b --- /dev/null +++ b/wgpu/src/texture/atlas.rs @@ -0,0 +1,313 @@ +pub mod entry; + +mod allocation; +mod allocator; +mod layer; + +pub use allocation::Allocation; +pub use entry::Entry; +pub use layer::Layer; + +use allocator::Allocator; + +pub const SIZE: u32 = 4096; + +#[derive(Debug)] +pub struct Atlas { + texture: wgpu::Texture, + texture_view: wgpu::TextureView, + layers: Vec, +} + +impl Atlas { + pub fn new(device: &wgpu::Device) -> Self { + let extent = wgpu::Extent3d { + width: SIZE, + height: SIZE, + depth: 1, + }; + + let texture = device.create_texture(&wgpu::TextureDescriptor { + size: extent, + array_layer_count: 2, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsage::COPY_DST + | wgpu::TextureUsage::COPY_SRC + | wgpu::TextureUsage::SAMPLED, + }); + + let texture_view = texture.create_default_view(); + + Atlas { + texture, + texture_view, + layers: vec![Layer::Empty, Layer::Empty], + } + } + + pub fn view(&self) -> &wgpu::TextureView { + &self.texture_view + } + + pub fn layer_count(&self) -> usize { + self.layers.len() + } + + pub fn upload( + &mut self, + width: u32, + height: u32, + data: &[C], + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + ) -> Option + where + C: Copy + 'static, + { + let memory = { + let current_size = self.layers.len(); + let memory = self.allocate(width, height)?; + + // We grow the internal texture after allocating if necessary + let new_layers = self.layers.len() - current_size; + self.grow(new_layers, device, encoder); + + memory + }; + + dbg!(&memory); + + let buffer = device + .create_buffer_mapped(data.len(), wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(data); + + match &memory { + Entry::Contiguous(allocation) => { + self.upload_texture(&buffer, 0, &allocation, encoder); + } + Entry::Fragmented { fragments, .. } => { + for fragment in fragments { + let (x, y) = fragment.allocation.position(); + + let offset = + (y * height + x) as usize * std::mem::size_of::(); + + self.upload_texture( + &buffer, + offset as u64, + &fragment.allocation, + encoder, + ); + } + } + } + + Some(memory) + } + + fn allocate(&mut self, width: u32, height: u32) -> Option { + // Allocate one layer if texture fits perfectly + if width == SIZE && height == SIZE { + let mut empty_layers = self + .layers + .iter_mut() + .enumerate() + .filter(|(_, layer)| layer.is_empty()); + + if let Some((i, layer)) = empty_layers.next() { + *layer = Layer::Full; + + return Some(Entry::Contiguous(Allocation::Full { layer: i })); + } + + self.layers.push(Layer::Full); + + return Some(Entry::Contiguous(Allocation::Full { + layer: self.layers.len() - 1, + })); + } + + // Split big textures across multiple layers + if width > SIZE || height > SIZE { + let mut fragments = Vec::new(); + let mut y = 0; + + while y < height { + let height = std::cmp::min(height - y, SIZE); + let mut x = 0; + + while x < width { + let width = std::cmp::min(width - x, SIZE); + + let allocation = self.allocate(width, height)?; + + if let Entry::Contiguous(allocation) = allocation { + fragments.push(entry::Fragment { + position: (x, y), + allocation, + }); + } + + x += width; + } + + y += height; + } + + return Some(Entry::Fragmented { + size: (width, height), + fragments, + }); + } + + // Try allocating on an existing layer + for (i, layer) in self.layers.iter_mut().enumerate() { + match layer { + Layer::Empty => { + let mut allocator = Allocator::new(SIZE); + + if let Some(region) = allocator.allocate(width, height) { + *layer = Layer::Busy(allocator); + + return Some(Entry::Contiguous(Allocation::Partial { + region, + layer: i, + })); + } + } + Layer::Busy(allocator) => { + if let Some(region) = allocator.allocate(width, height) { + return Some(Entry::Contiguous(Allocation::Partial { + region, + layer: i, + })); + } + } + _ => {} + } + } + + // Create new layer with atlas allocator + let mut allocator = Allocator::new(SIZE); + + if let Some(region) = allocator.allocate(width, height) { + self.layers.push(Layer::Busy(allocator)); + + return Some(Entry::Contiguous(Allocation::Partial { + region, + layer: self.layers.len() - 1, + })); + } + + // We ran out of memory (?) + None + } + + fn upload_texture( + &mut self, + buffer: &wgpu::Buffer, + offset: u64, + allocation: &Allocation, + encoder: &mut wgpu::CommandEncoder, + ) { + let (x, y) = allocation.position(); + let (width, height) = allocation.size(); + let layer = allocation.layer(); + + let extent = wgpu::Extent3d { + width, + height, + depth: 1, + }; + + encoder.copy_buffer_to_texture( + wgpu::BufferCopyView { + buffer, + offset, + row_pitch: 4 * width, + image_height: height, + }, + wgpu::TextureCopyView { + texture: &self.texture, + array_layer: layer as u32, + mip_level: 0, + origin: wgpu::Origin3d { + x: x as f32, + y: y as f32, + z: 0.0, + }, + }, + extent, + ); + } + + fn grow( + &mut self, + amount: usize, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + ) { + if amount == 0 { + return; + } + + let new_texture = device.create_texture(&wgpu::TextureDescriptor { + size: wgpu::Extent3d { + width: SIZE, + height: SIZE, + depth: 1, + }, + array_layer_count: (self.layers.len() + amount) as u32, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsage::COPY_DST + | wgpu::TextureUsage::COPY_SRC + | wgpu::TextureUsage::SAMPLED, + }); + + for (i, layer) in self.layers.iter_mut().enumerate() { + if layer.is_empty() { + continue; + } + + encoder.copy_texture_to_texture( + wgpu::TextureCopyView { + texture: &self.texture, + array_layer: i as u32, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + wgpu::TextureCopyView { + texture: &new_texture, + array_layer: i as u32, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + wgpu::Extent3d { + width: SIZE, + height: SIZE, + depth: 1, + }, + ); + } + + for _ in 0..amount { + self.layers.push(Layer::Empty); + } + + self.texture = new_texture; + } +} diff --git a/wgpu/src/texture/atlas/allocation.rs b/wgpu/src/texture/atlas/allocation.rs new file mode 100644 index 00000000..e17b3f8c --- /dev/null +++ b/wgpu/src/texture/atlas/allocation.rs @@ -0,0 +1,35 @@ +use crate::texture::atlas::{self, allocator}; + +#[derive(Debug)] +pub enum Allocation { + Partial { + layer: usize, + region: allocator::Region, + }, + Full { + layer: usize, + }, +} + +impl Allocation { + pub fn position(&self) -> (u32, u32) { + match self { + Allocation::Partial { region, .. } => region.position(), + Allocation::Full { .. } => (0, 0), + } + } + + pub fn size(&self) -> (u32, u32) { + match self { + Allocation::Partial { region, .. } => region.size(), + Allocation::Full { .. } => (atlas::SIZE, atlas::SIZE), + } + } + + pub fn layer(&self) -> usize { + match self { + Allocation::Partial { layer, .. } => *layer, + Allocation::Full { layer } => *layer, + } + } +} diff --git a/wgpu/src/texture/atlas/allocator.rs b/wgpu/src/texture/atlas/allocator.rs new file mode 100644 index 00000000..cd710522 --- /dev/null +++ b/wgpu/src/texture/atlas/allocator.rs @@ -0,0 +1,57 @@ +use guillotiere::{AtlasAllocator, Size}; + +pub struct Allocator { + raw: AtlasAllocator, +} + +impl Allocator { + pub fn new(size: u32) -> Allocator { + let raw = AtlasAllocator::new(Size::new(size as i32, size as i32)); + + Allocator { raw } + } + + pub fn allocate(&mut self, width: u32, height: u32) -> Option { + let allocation = self + .raw + .allocate(Size::new(width as i32 + 2, height as i32 + 2))?; + + Some(Region(allocation)) + } + + pub fn deallocate(&mut self, region: Region) { + self.raw.deallocate(region.0.id); + } +} + +pub struct Region(guillotiere::Allocation); + +impl Region { + pub fn position(&self) -> (u32, u32) { + let rectangle = &self.0.rectangle; + + (rectangle.min.x as u32 + 1, rectangle.min.y as u32 + 1) + } + + pub fn size(&self) -> (u32, u32) { + let size = self.0.rectangle.size(); + + (size.width as u32 - 2, size.height as u32 - 2) + } +} + +impl std::fmt::Debug for Allocator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Allocator") + } +} + +impl std::fmt::Debug for Region { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Region {{ id: {:?}, rectangle: {:?} }}", + self.0.id, self.0.rectangle + ) + } +} diff --git a/wgpu/src/texture/atlas/entry.rs b/wgpu/src/texture/atlas/entry.rs new file mode 100644 index 00000000..2c064665 --- /dev/null +++ b/wgpu/src/texture/atlas/entry.rs @@ -0,0 +1,25 @@ +use crate::texture::atlas; + +#[derive(Debug)] +pub enum Entry { + Contiguous(atlas::Allocation), + Fragmented { + size: (u32, u32), + fragments: Vec, + }, +} + +impl Entry { + pub fn size(&self) -> (u32, u32) { + match self { + Entry::Contiguous(allocation) => allocation.size(), + Entry::Fragmented { size, .. } => *size, + } + } +} + +#[derive(Debug)] +pub struct Fragment { + pub position: (u32, u32), + pub allocation: atlas::Allocation, +} diff --git a/wgpu/src/texture/atlas/layer.rs b/wgpu/src/texture/atlas/layer.rs new file mode 100644 index 00000000..b025d8a1 --- /dev/null +++ b/wgpu/src/texture/atlas/layer.rs @@ -0,0 +1,17 @@ +use crate::texture::atlas::Allocator; + +#[derive(Debug)] +pub enum Layer { + Empty, + Busy(Allocator), + Full, +} + +impl Layer { + pub fn is_empty(&self) -> bool { + match self { + Layer::Empty => true, + _ => false, + } + } +} -- cgit From c58d94f3fda40f215254008ec105aeab56085b0e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Feb 2020 12:52:30 +0100 Subject: Avoid creating a vertex buffer every frame --- wgpu/src/image.rs | 109 ++++++++++++++++++++++++++--------------- wgpu/src/image/raster.rs | 2 - wgpu/src/shader/image.vert | 2 +- wgpu/src/shader/image.vert.spv | Bin 2472 -> 2504 bytes wgpu/src/texture/atlas.rs | 12 ++--- 5 files changed, 75 insertions(+), 50 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index e14b3024..afff52a6 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -22,6 +22,7 @@ pub struct Pipeline { uniforms: wgpu::Buffer, vertices: wgpu::Buffer, indices: wgpu::Buffer, + instances: wgpu::Buffer, constants: wgpu::BindGroup, texture: wgpu::BindGroup, texture_version: usize, @@ -188,7 +189,7 @@ impl Pipeline { }, wgpu::VertexAttributeDescriptor { shader_location: 5, - format: wgpu::VertexFormat::Float, + format: wgpu::VertexFormat::Uint, offset: 4 * 8, }, ], @@ -207,6 +208,11 @@ impl Pipeline { .create_buffer_mapped(QUAD_INDICES.len(), wgpu::BufferUsage::INDEX) .fill_from_slice(&QUAD_INDICES); + let instances = device.create_buffer(&wgpu::BufferDescriptor { + size: mem::size_of::() as u64 * Instance::MAX as u64, + usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, + }); + let texture_atlas = texture::Atlas::new(device); let texture = device.create_bind_group(&wgpu::BindGroupDescriptor { @@ -230,6 +236,7 @@ impl Pipeline { uniforms: uniforms_buffer, vertices, indices, + instances, constants: constant_bind_group, texture, texture_version: texture_atlas.layer_count(), @@ -341,49 +348,67 @@ impl Pipeline { ); let instances_buffer = device - .create_buffer_mapped(instances.len(), wgpu::BufferUsage::VERTEX) + .create_buffer_mapped(instances.len(), wgpu::BufferUsage::COPY_SRC) .fill_from_slice(&instances); - 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, + let mut i = 0; + let total = instances.len(); + + while i < total { + let end = (i + Instance::MAX).min(total); + let amount = end - i; + + encoder.copy_buffer_to_buffer( + &instances_buffer, + (i * std::mem::size_of::()) as u64, + &self.instances, + 0, + (amount * 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, - }); - - render_pass.set_pipeline(&self.pipeline); - render_pass.set_bind_group(0, &self.constants, &[]); - render_pass.set_bind_group(1, &self.texture, &[]); - render_pass.set_index_buffer(&self.indices, 0); - render_pass.set_vertex_buffers( - 0, - &[(&self.vertices, 0), (&instances_buffer, 0)], - ); - - render_pass.set_scissor_rect( - bounds.x, - bounds.y, - bounds.width, - bounds.height, - ); + ], + depth_stencil_attachment: None, + }); - render_pass.draw_indexed( - 0..QUAD_INDICES.len() as u32, - 0, - 0..instances.len() as u32, - ); + render_pass.set_pipeline(&self.pipeline); + render_pass.set_bind_group(0, &self.constants, &[]); + render_pass.set_bind_group(1, &self.texture, &[]); + render_pass.set_index_buffer(&self.indices, 0); + render_pass.set_vertex_buffers( + 0, + &[(&self.vertices, 0), (&self.instances, 0)], + ); + + render_pass.set_scissor_rect( + bounds.x, + bounds.y, + bounds.width, + bounds.height, + ); + + render_pass.draw_indexed( + 0..QUAD_INDICES.len() as u32, + 0, + 0..amount as u32, + ); + + i += Instance::MAX; + } } pub fn trim_cache(&mut self) { @@ -439,6 +464,10 @@ struct Instance { _layer: u32, } +impl Instance { + pub const MAX: usize = 1_000; +} + #[repr(C)] #[derive(Debug, Clone, Copy)] struct Uniforms { diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 071d53c8..8d2f342e 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -86,8 +86,6 @@ impl Cache { let allocation = atlas.upload(width, height, &image, device, encoder)?; - dbg!("Uploaded"); - *memory = Memory::Device(allocation); } diff --git a/wgpu/src/shader/image.vert b/wgpu/src/shader/image.vert index 0ce7dd6b..dab53cfe 100644 --- a/wgpu/src/shader/image.vert +++ b/wgpu/src/shader/image.vert @@ -5,7 +5,7 @@ layout(location = 1) in vec2 i_Pos; layout(location = 2) in vec2 i_Scale; layout(location = 3) in vec2 i_Atlas_Pos; layout(location = 4) in vec2 i_Atlas_Scale; -layout(location = 5) in float i_Layer; +layout(location = 5) in uint i_Layer; layout (set = 0, binding = 0) uniform Globals { mat4 u_Transform; diff --git a/wgpu/src/shader/image.vert.spv b/wgpu/src/shader/image.vert.spv index 192bf6c3..21f5db2d 100644 Binary files a/wgpu/src/shader/image.vert.spv and b/wgpu/src/shader/image.vert.spv differ diff --git a/wgpu/src/texture/atlas.rs b/wgpu/src/texture/atlas.rs index b950e59b..bf528dc9 100644 --- a/wgpu/src/texture/atlas.rs +++ b/wgpu/src/texture/atlas.rs @@ -67,24 +67,22 @@ impl Atlas { where C: Copy + 'static, { - let memory = { + let entry = { let current_size = self.layers.len(); - let memory = self.allocate(width, height)?; + let entry = self.allocate(width, height)?; // We grow the internal texture after allocating if necessary let new_layers = self.layers.len() - current_size; self.grow(new_layers, device, encoder); - memory + entry }; - dbg!(&memory); - let buffer = device .create_buffer_mapped(data.len(), wgpu::BufferUsage::COPY_SRC) .fill_from_slice(data); - match &memory { + match &entry { Entry::Contiguous(allocation) => { self.upload_texture(&buffer, 0, &allocation, encoder); } @@ -105,7 +103,7 @@ impl Atlas { } } - Some(memory) + Some(entry) } fn allocate(&mut self, width: u32, height: u32) -> Option { -- cgit From 48d70280eb4f5908f1c9339bebdfbab856d55ae1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Feb 2020 18:49:46 +0100 Subject: Fix multiple issues from the refactoring - Update texture view on grow - Fix atlas texture coordinates - Fix fragmented uploads --- wgpu/src/image.rs | 10 +++--- wgpu/src/image/raster.rs | 5 ++- wgpu/src/texture/atlas.rs | 52 +++++++++++++++++++------------ wgpu/src/texture/atlas/allocator.rs | 61 +++++++++++++++++++++++++++---------- 4 files changed, 86 insertions(+), 42 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index afff52a6..1ffa50d2 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -319,6 +319,8 @@ impl Pipeline { let texture_version = self.texture_atlas.layer_count(); if self.texture_version != texture_version { + log::info!("Atlas has grown. Recreating bind group..."); + self.texture = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &self.texture_layout, @@ -525,12 +527,12 @@ fn add_instance( _position: position, _size: size, _position_in_atlas: [ - x as f32 / atlas::SIZE as f32, - y as f32 / atlas::SIZE as f32, + (x as f32 + 0.5) / atlas::SIZE as f32, + (y as f32 + 0.5) / atlas::SIZE as f32, ], _size_in_atlas: [ - width as f32 / atlas::SIZE as f32, - height as f32 / atlas::SIZE as f32, + (width as f32 - 0.5) / atlas::SIZE as f32, + (height as f32 - 0.5) / atlas::SIZE as f32, ], _layer: layer as u32, }; diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 8d2f342e..b19da582 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -83,10 +83,9 @@ impl Cache { if let Memory::Host(image) = memory { let (width, height) = image.dimensions(); - let allocation = - atlas.upload(width, height, &image, device, encoder)?; + let entry = atlas.upload(width, height, &image, device, encoder)?; - *memory = Memory::Device(allocation); + *memory = Memory::Device(entry); } if let Memory::Device(allocation) = memory { diff --git a/wgpu/src/texture/atlas.rs b/wgpu/src/texture/atlas.rs index bf528dc9..3d4e81c1 100644 --- a/wgpu/src/texture/atlas.rs +++ b/wgpu/src/texture/atlas.rs @@ -10,7 +10,7 @@ pub use layer::Layer; use allocator::Allocator; -pub const SIZE: u32 = 4096; +pub const SIZE: u32 = 2048; #[derive(Debug)] pub struct Atlas { @@ -78,24 +78,33 @@ impl Atlas { entry }; + log::info!("Allocated atlas entry: {:?}", entry); + let buffer = device .create_buffer_mapped(data.len(), wgpu::BufferUsage::COPY_SRC) .fill_from_slice(data); match &entry { Entry::Contiguous(allocation) => { - self.upload_texture(&buffer, 0, &allocation, encoder); + self.upload_allocation( + &buffer, + width, + height, + 0, + &allocation, + encoder, + ); } Entry::Fragmented { fragments, .. } => { for fragment in fragments { - let (x, y) = fragment.allocation.position(); - - let offset = - (y * height + x) as usize * std::mem::size_of::(); + let (x, y) = fragment.position; + let offset = (y * width + x) as usize * 4; - self.upload_texture( + self.upload_allocation( &buffer, - offset as u64, + width, + height, + offset, &fragment.allocation, encoder, ); @@ -103,6 +112,8 @@ impl Atlas { } } + log::info!("Current atlas: {:?}", &self); + Some(entry) } @@ -204,10 +215,12 @@ impl Atlas { None } - fn upload_texture( + fn upload_allocation( &mut self, buffer: &wgpu::Buffer, - offset: u64, + image_width: u32, + image_height: u32, + offset: usize, allocation: &Allocation, encoder: &mut wgpu::CommandEncoder, ) { @@ -224,9 +237,9 @@ impl Atlas { encoder.copy_buffer_to_texture( wgpu::BufferCopyView { buffer, - offset, - row_pitch: 4 * width, - image_height: height, + offset: offset as u64, + row_pitch: 4 * image_width, + image_height, }, wgpu::TextureCopyView { texture: &self.texture, @@ -258,7 +271,7 @@ impl Atlas { height: SIZE, depth: 1, }, - array_layer_count: (self.layers.len() + amount) as u32, + array_layer_count: self.layers.len() as u32, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, @@ -268,7 +281,11 @@ impl Atlas { | wgpu::TextureUsage::SAMPLED, }); - for (i, layer) in self.layers.iter_mut().enumerate() { + let amount_to_copy = self.layers.len() - amount; + + for (i, layer) in + self.layers.iter_mut().take(amount_to_copy).enumerate() + { if layer.is_empty() { continue; } @@ -302,10 +319,7 @@ impl Atlas { ); } - for _ in 0..amount { - self.layers.push(Layer::Empty); - } - self.texture = new_texture; + self.texture_view = self.texture.create_default_view(); } } diff --git a/wgpu/src/texture/atlas/allocator.rs b/wgpu/src/texture/atlas/allocator.rs index cd710522..ad111212 100644 --- a/wgpu/src/texture/atlas/allocator.rs +++ b/wgpu/src/texture/atlas/allocator.rs @@ -2,41 +2,70 @@ use guillotiere::{AtlasAllocator, Size}; pub struct Allocator { raw: AtlasAllocator, + size: u32, } impl Allocator { + const PADDING: u32 = 1; + pub fn new(size: u32) -> Allocator { let raw = AtlasAllocator::new(Size::new(size as i32, size as i32)); - Allocator { raw } + Allocator { raw, size } } pub fn allocate(&mut self, width: u32, height: u32) -> Option { - let allocation = self - .raw - .allocate(Size::new(width as i32 + 2, height as i32 + 2))?; + let padding = ( + if width + Self::PADDING * 2 < self.size { + Self::PADDING + } else { + 0 + }, + if height + Self::PADDING * 2 < self.size { + Self::PADDING + } else { + 0 + }, + ); + + let allocation = self.raw.allocate(Size::new( + (width + padding.0 * 2) as i32, + (height + padding.1 * 2) as i32, + ))?; - Some(Region(allocation)) + Some(Region { + allocation, + padding, + }) } pub fn deallocate(&mut self, region: Region) { - self.raw.deallocate(region.0.id); + self.raw.deallocate(region.allocation.id); } } -pub struct Region(guillotiere::Allocation); +pub struct Region { + allocation: guillotiere::Allocation, + padding: (u32, u32), +} impl Region { pub fn position(&self) -> (u32, u32) { - let rectangle = &self.0.rectangle; + let rectangle = &self.allocation.rectangle; - (rectangle.min.x as u32 + 1, rectangle.min.y as u32 + 1) + ( + rectangle.min.x as u32 + self.padding.0, + rectangle.min.y as u32 + self.padding.1, + ) } pub fn size(&self) -> (u32, u32) { - let size = self.0.rectangle.size(); + let size = self.allocation.rectangle.size(); - (size.width as u32 - 2, size.height as u32 - 2) + ( + size.width as u32 - self.padding.0 * 2, + size.height as u32 - self.padding.1 * 2, + ) } } @@ -48,10 +77,10 @@ impl std::fmt::Debug for Allocator { impl std::fmt::Debug for Region { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Region {{ id: {:?}, rectangle: {:?} }}", - self.0.id, self.0.rectangle - ) + f.debug_struct("Region") + .field("id", &self.allocation.id) + .field("rectangle", &self.allocation.rectangle) + .field("padding", &self.padding) + .finish() } } -- cgit From d06d06e05096e0145be74fd02d67eada0a1665a1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Feb 2020 20:10:19 +0100 Subject: Deallocate atlas entries and remove padding --- wgpu/src/image.rs | 8 +++--- wgpu/src/image/raster.rs | 15 ++++++++-- wgpu/src/image/vector.rs | 12 ++++++-- wgpu/src/texture/atlas.rs | 38 ++++++++++++++++++++++++- wgpu/src/texture/atlas/allocator.rs | 57 +++++++++++++------------------------ 5 files changed, 84 insertions(+), 46 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 1ffa50d2..7155b540 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -415,10 +415,10 @@ impl Pipeline { pub fn trim_cache(&mut self) { #[cfg(feature = "image")] - self.raster_cache.borrow_mut().trim(); + self.raster_cache.borrow_mut().trim(&mut self.texture_atlas); #[cfg(feature = "svg")] - self.vector_cache.borrow_mut().trim(); + self.vector_cache.borrow_mut().trim(&mut self.texture_atlas); } } @@ -531,8 +531,8 @@ fn add_instance( (y as f32 + 0.5) / atlas::SIZE as f32, ], _size_in_atlas: [ - (width as f32 - 0.5) / atlas::SIZE as f32, - (height as f32 - 0.5) / atlas::SIZE as f32, + (width as f32 - 1.0) / atlas::SIZE as f32, + (height as f32 - 1.0) / atlas::SIZE as f32, ], _layer: layer as u32, }; diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index b19da582..cae8e065 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -95,10 +95,21 @@ impl Cache { } } - pub fn trim(&mut self) { + pub fn trim(&mut self, atlas: &mut Atlas) { let hits = &self.hits; - self.map.retain(|k, _| hits.contains(k)); + self.map.retain(|k, memory| { + let retain = hits.contains(k); + + if !retain { + if let Memory::Device(entry) = memory { + atlas.remove(entry); + } + } + + retain + }); + self.hits.clear(); } diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 0dabc9ca..e7eb4906 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -130,12 +130,20 @@ impl Cache { } } - pub fn trim(&mut self) { + pub fn trim(&mut self, atlas: &mut Atlas) { let svg_hits = &self.svg_hits; let rasterized_hits = &self.rasterized_hits; self.svgs.retain(|k, _| svg_hits.contains(k)); - self.rasterized.retain(|k, _| rasterized_hits.contains(k)); + self.rasterized.retain(|k, entry| { + let retain = rasterized_hits.contains(k); + + if !retain { + atlas.remove(entry); + } + + retain + }); self.svg_hits.clear(); self.rasterized_hits.clear(); } diff --git a/wgpu/src/texture/atlas.rs b/wgpu/src/texture/atlas.rs index 3d4e81c1..a76c035b 100644 --- a/wgpu/src/texture/atlas.rs +++ b/wgpu/src/texture/atlas.rs @@ -112,11 +112,47 @@ impl Atlas { } } - log::info!("Current atlas: {:?}", &self); + log::info!("Current atlas: {:?}", self); Some(entry) } + pub fn remove(&mut self, entry: &Entry) { + log::info!("Removing atlas entry: {:?}", entry); + + match entry { + Entry::Contiguous(allocation) => { + self.deallocate(allocation); + } + Entry::Fragmented { fragments, .. } => { + for fragment in fragments { + self.deallocate(&fragment.allocation); + } + } + } + } + + fn deallocate(&mut self, allocation: &Allocation) { + log::info!("Deallocating atlas: {:?}", allocation); + + match allocation { + Allocation::Full { layer } => { + self.layers[*layer] = Layer::Empty; + } + Allocation::Partial { layer, region } => { + let layer = &mut self.layers[*layer]; + + if let Layer::Busy(allocator) = layer { + allocator.deallocate(region); + + if allocator.is_empty() { + *layer = Layer::Empty; + } + } + } + } + } + fn allocate(&mut self, width: u32, height: u32) -> Option { // Allocate one layer if texture fits perfectly if width == SIZE && height == SIZE { diff --git a/wgpu/src/texture/atlas/allocator.rs b/wgpu/src/texture/atlas/allocator.rs index ad111212..7a4ff5b1 100644 --- a/wgpu/src/texture/atlas/allocator.rs +++ b/wgpu/src/texture/atlas/allocator.rs @@ -2,70 +2,54 @@ use guillotiere::{AtlasAllocator, Size}; pub struct Allocator { raw: AtlasAllocator, - size: u32, + allocations: usize, } impl Allocator { - const PADDING: u32 = 1; - pub fn new(size: u32) -> Allocator { let raw = AtlasAllocator::new(Size::new(size as i32, size as i32)); - Allocator { raw, size } + Allocator { + raw, + allocations: 0, + } } pub fn allocate(&mut self, width: u32, height: u32) -> Option { - let padding = ( - if width + Self::PADDING * 2 < self.size { - Self::PADDING - } else { - 0 - }, - if height + Self::PADDING * 2 < self.size { - Self::PADDING - } else { - 0 - }, - ); - - let allocation = self.raw.allocate(Size::new( - (width + padding.0 * 2) as i32, - (height + padding.1 * 2) as i32, - ))?; - - Some(Region { - allocation, - padding, - }) + let allocation = + self.raw.allocate(Size::new(width as i32, height as i32))?; + + self.allocations += 1; + + Some(Region { allocation }) } - pub fn deallocate(&mut self, region: Region) { + pub fn deallocate(&mut self, region: &Region) { self.raw.deallocate(region.allocation.id); + + self.allocations = self.allocations.saturating_sub(1); + } + + pub fn is_empty(&self) -> bool { + self.allocations == 0 } } pub struct Region { allocation: guillotiere::Allocation, - padding: (u32, u32), } impl Region { pub fn position(&self) -> (u32, u32) { let rectangle = &self.allocation.rectangle; - ( - rectangle.min.x as u32 + self.padding.0, - rectangle.min.y as u32 + self.padding.1, - ) + (rectangle.min.x as u32, rectangle.min.y as u32) } pub fn size(&self) -> (u32, u32) { let size = self.allocation.rectangle.size(); - ( - size.width as u32 - self.padding.0 * 2, - size.height as u32 - self.padding.1 * 2, - ) + (size.width as u32, size.height as u32) } } @@ -80,7 +64,6 @@ impl std::fmt::Debug for Region { f.debug_struct("Region") .field("id", &self.allocation.id) .field("rectangle", &self.allocation.rectangle) - .field("padding", &self.padding) .finish() } } -- cgit From 883a9f22e2e868fa0f85b1ac251392c12bf83696 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Feb 2020 20:11:01 +0100 Subject: Add `env_logger` to `svg` example --- examples/svg/Cargo.toml | 1 + examples/svg/src/main.rs | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/examples/svg/Cargo.toml b/examples/svg/Cargo.toml index d8f83ac2..161ee6a8 100644 --- a/examples/svg/Cargo.toml +++ b/examples/svg/Cargo.toml @@ -7,3 +7,4 @@ publish = false [dependencies] iced = { path = "../..", features = ["svg"] } +env_logger = "0.7" diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs index 1fb80534..811fdfb5 100644 --- a/examples/svg/src/main.rs +++ b/examples/svg/src/main.rs @@ -1,6 +1,8 @@ use iced::{Column, Container, Element, Length, Sandbox, Settings, Svg}; pub fn main() { + env_logger::init(); + Tiger::run(Settings::default()) } @@ -22,9 +24,12 @@ impl Sandbox for Tiger { fn view(&mut self) -> Element<()> { let content = Column::new().padding(20).push( - Svg::new(format!("{}/resources/tiger.svg", env!("CARGO_MANIFEST_DIR"))) - .width(Length::Fill) - .height(Length::Fill), + Svg::new(format!( + "{}/resources/tiger.svg", + env!("CARGO_MANIFEST_DIR") + )) + .width(Length::Fill) + .height(Length::Fill), ); Container::new(content) -- cgit From 6cb7fb6d52a25dc69f44c0ed1bd8d0254b6b213f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Feb 2020 20:35:39 +0100 Subject: Remove unused code warnings in `iced_wgpu::image` --- wgpu/src/image.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index 7155b540..dc19cfbf 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -3,13 +3,16 @@ mod raster; #[cfg(feature = "svg")] mod vector; -use crate::{ - texture::{self, atlas}, - Transformation, -}; +use crate::{texture, Transformation}; use iced_native::{image, svg, Rectangle}; -use std::{cell::RefCell, mem}; +use std::mem; + +#[cfg(any(feature = "image", feature = "svg"))] +use std::cell::RefCell; + +#[cfg(any(feature = "image", feature = "svg"))] +use crate::texture::atlas; #[derive(Debug)] pub struct Pipeline { @@ -271,7 +274,7 @@ impl Pipeline { target: &wgpu::TextureView, _scale: f32, ) { - let mut instances: Vec = Vec::new(); + let instances: &mut Vec = &mut Vec::new(); #[cfg(feature = "image")] let mut raster_cache = self.raster_cache.borrow_mut(); @@ -290,7 +293,7 @@ impl Pipeline { encoder, &mut self.texture_atlas, ) { - add_instances(image, atlas_entry, &mut instances); + add_instances(image, atlas_entry, instances); } }; } @@ -305,7 +308,7 @@ impl Pipeline { encoder, &mut self.texture_atlas, ) { - add_instances(image, atlas_entry, &mut instances); + add_instances(image, atlas_entry, instances); } }; } @@ -476,6 +479,7 @@ struct Uniforms { transform: [f32; 16], } +#[cfg(any(feature = "image", feature = "svg"))] fn add_instances( image: &Image, entry: &atlas::Entry, @@ -512,6 +516,7 @@ fn add_instances( } } +#[cfg(any(feature = "image", feature = "svg"))] #[inline] fn add_instance( position: [f32; 2], -- cgit From deedf6e8b66c5564c020dd61b72fdc71d8dd1eae Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Feb 2020 20:36:52 +0100 Subject: Make new `texture` module private for now --- wgpu/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 832da31d..a807b44d 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -19,14 +19,13 @@ //! [`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)] #![forbid(rust_2018_idioms)] pub mod defaults; pub mod settings; -pub mod texture; pub mod triangle; pub mod widget; pub mod window; @@ -37,6 +36,7 @@ mod quad; mod renderer; mod target; mod text; +mod texture; mod transformation; mod viewport; -- cgit From 271725faa585ba14207a9bb27ab95fe27473279d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Feb 2020 20:47:27 +0100 Subject: Derive `Debug` for `raster::Memory` --- wgpu/src/image/raster.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index cae8e065..883c32f7 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -2,6 +2,7 @@ use crate::texture::atlas::{self, Atlas}; use iced_native::image; use std::collections::{HashMap, HashSet}; +#[derive(Debug)] pub enum Memory { Host(::image::ImageBuffer<::image::Bgra, Vec>), Device(atlas::Entry), @@ -20,17 +21,6 @@ impl Memory { } } -impl std::fmt::Debug for Memory { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Memory::Host(_) => write!(f, "Memory::Host"), - Memory::Device(_) => write!(f, "Memory::Device"), - Memory::NotFound => write!(f, "Memory::NotFound"), - Memory::Invalid => write!(f, "Memory::Invalid"), - } - } -} - #[derive(Debug)] pub struct Cache { map: HashMap, -- cgit From bb397cc66808dad14eace4fa40edf2e9bebf42bb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Feb 2020 20:47:37 +0100 Subject: Move `Debug` implementation for `vector::Svg` --- wgpu/src/image/vector.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index e7eb4906..6b12df54 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -20,15 +20,7 @@ impl Svg { } } -impl std::fmt::Debug for Svg { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Svg::Loaded(_) => write!(f, "Svg::Loaded"), - Svg::NotFound => write!(f, "Svg::NotFound"), - } - } -} - +#[derive(Debug)] pub struct Cache { svgs: HashMap, rasterized: HashMap<(u64, u32, u32), atlas::Entry>, @@ -149,8 +141,11 @@ impl Cache { } } -impl std::fmt::Debug for Cache { +impl std::fmt::Debug for Svg { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "vector::Cache") + match self { + Svg::Loaded(_) => write!(f, "Svg::Loaded"), + Svg::NotFound => write!(f, "Svg::NotFound"), + } } } -- cgit From fc55e3a3df9e08d361985b01528d2a6095d98aba Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 26 Feb 2020 20:50:32 +0100 Subject: Move `Atlas::deallocate` after `allocate` --- wgpu/src/texture/atlas.rs | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/wgpu/src/texture/atlas.rs b/wgpu/src/texture/atlas.rs index a76c035b..86a5ff49 100644 --- a/wgpu/src/texture/atlas.rs +++ b/wgpu/src/texture/atlas.rs @@ -132,27 +132,6 @@ impl Atlas { } } - fn deallocate(&mut self, allocation: &Allocation) { - log::info!("Deallocating atlas: {:?}", allocation); - - match allocation { - Allocation::Full { layer } => { - self.layers[*layer] = Layer::Empty; - } - Allocation::Partial { layer, region } => { - let layer = &mut self.layers[*layer]; - - if let Layer::Busy(allocator) = layer { - allocator.deallocate(region); - - if allocator.is_empty() { - *layer = Layer::Empty; - } - } - } - } - } - fn allocate(&mut self, width: u32, height: u32) -> Option { // Allocate one layer if texture fits perfectly if width == SIZE && height == SIZE { @@ -251,6 +230,27 @@ impl Atlas { None } + fn deallocate(&mut self, allocation: &Allocation) { + log::info!("Deallocating atlas: {:?}", allocation); + + match allocation { + Allocation::Full { layer } => { + self.layers[*layer] = Layer::Empty; + } + Allocation::Partial { layer, region } => { + let layer = &mut self.layers[*layer]; + + if let Layer::Busy(allocator) = layer { + allocator.deallocate(region); + + if allocator.is_empty() { + *layer = Layer::Empty; + } + } + } + } + } + fn upload_allocation( &mut self, buffer: &wgpu::Buffer, -- cgit From 4e7159c22c6be90f61aa715d5eb6811f805cb597 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 28 Feb 2020 14:38:42 +0100 Subject: Stop creating image pipeline when unnecessary --- wgpu/src/image.rs | 74 +++---- wgpu/src/image/atlas.rs | 361 +++++++++++++++++++++++++++++++++++ wgpu/src/image/atlas/allocation.rs | 35 ++++ wgpu/src/image/atlas/allocator.rs | 69 +++++++ wgpu/src/image/atlas/entry.rs | 26 +++ wgpu/src/image/atlas/layer.rs | 17 ++ wgpu/src/image/raster.rs | 2 +- wgpu/src/image/vector.rs | 2 +- wgpu/src/lib.rs | 9 +- wgpu/src/renderer.rs | 103 ++++++---- wgpu/src/texture.rs | 3 - wgpu/src/texture/atlas.rs | 361 ----------------------------------- wgpu/src/texture/atlas/allocation.rs | 35 ---- wgpu/src/texture/atlas/allocator.rs | 69 ------- wgpu/src/texture/atlas/entry.rs | 25 --- wgpu/src/texture/atlas/layer.rs | 17 -- 16 files changed, 618 insertions(+), 590 deletions(-) create mode 100644 wgpu/src/image/atlas.rs create mode 100644 wgpu/src/image/atlas/allocation.rs create mode 100644 wgpu/src/image/atlas/allocator.rs create mode 100644 wgpu/src/image/atlas/entry.rs create mode 100644 wgpu/src/image/atlas/layer.rs delete mode 100644 wgpu/src/texture.rs delete mode 100644 wgpu/src/texture/atlas.rs delete mode 100644 wgpu/src/texture/atlas/allocation.rs delete mode 100644 wgpu/src/texture/atlas/allocator.rs delete mode 100644 wgpu/src/texture/atlas/entry.rs delete mode 100644 wgpu/src/texture/atlas/layer.rs diff --git a/wgpu/src/image.rs b/wgpu/src/image.rs index dc19cfbf..d3603676 100644 --- a/wgpu/src/image.rs +++ b/wgpu/src/image.rs @@ -1,18 +1,23 @@ +mod atlas; + #[cfg(feature = "image")] mod raster; + #[cfg(feature = "svg")] mod vector; -use crate::{texture, Transformation}; +use crate::Transformation; +use atlas::Atlas; -use iced_native::{image, svg, Rectangle}; +use iced_native::Rectangle; +use std::cell::RefCell; use std::mem; -#[cfg(any(feature = "image", feature = "svg"))] -use std::cell::RefCell; +#[cfg(feature = "image")] +use iced_native::image; -#[cfg(any(feature = "image", feature = "svg"))] -use crate::texture::atlas; +#[cfg(feature = "svg")] +use iced_native::svg; #[derive(Debug)] pub struct Pipeline { @@ -30,7 +35,7 @@ pub struct Pipeline { texture: wgpu::BindGroup, texture_version: usize, texture_layout: wgpu::BindGroupLayout, - texture_atlas: texture::Atlas, + texture_atlas: Atlas, } impl Pipeline { @@ -216,7 +221,7 @@ impl Pipeline { usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, }); - let texture_atlas = texture::Atlas::new(device); + let texture_atlas = Atlas::new(device); let texture = device.create_bind_group(&wgpu::BindGroupDescriptor { layout: &texture_layout, @@ -284,33 +289,29 @@ impl Pipeline { for image in images { match &image.handle { - Handle::Raster(_handle) => { - #[cfg(feature = "image")] - { - if let Some(atlas_entry) = raster_cache.upload( - _handle, - device, - encoder, - &mut self.texture_atlas, - ) { - add_instances(image, atlas_entry, instances); - } - }; + #[cfg(feature = "image")] + Handle::Raster(handle) => { + if let Some(atlas_entry) = raster_cache.upload( + handle, + device, + encoder, + &mut self.texture_atlas, + ) { + add_instances(image, atlas_entry, instances); + } } - Handle::Vector(_handle) => { - #[cfg(feature = "svg")] - { - if let Some(atlas_entry) = vector_cache.upload( - _handle, - image.size, - _scale, - device, - encoder, - &mut self.texture_atlas, - ) { - add_instances(image, atlas_entry, instances); - } - }; + #[cfg(feature = "svg")] + Handle::Vector(handle) => { + if let Some(atlas_entry) = vector_cache.upload( + handle, + image.size, + _scale, + device, + encoder, + &mut self.texture_atlas, + ) { + add_instances(image, atlas_entry, instances); + } } } } @@ -432,7 +433,10 @@ pub struct Image { } pub enum Handle { + #[cfg(feature = "image")] Raster(image::Handle), + + #[cfg(feature = "svg")] Vector(svg::Handle), } @@ -479,7 +483,6 @@ struct Uniforms { transform: [f32; 16], } -#[cfg(any(feature = "image", feature = "svg"))] fn add_instances( image: &Image, entry: &atlas::Entry, @@ -516,7 +519,6 @@ fn add_instances( } } -#[cfg(any(feature = "image", feature = "svg"))] #[inline] fn add_instance( position: [f32; 2], diff --git a/wgpu/src/image/atlas.rs b/wgpu/src/image/atlas.rs new file mode 100644 index 00000000..86a5ff49 --- /dev/null +++ b/wgpu/src/image/atlas.rs @@ -0,0 +1,361 @@ +pub mod entry; + +mod allocation; +mod allocator; +mod layer; + +pub use allocation::Allocation; +pub use entry::Entry; +pub use layer::Layer; + +use allocator::Allocator; + +pub const SIZE: u32 = 2048; + +#[derive(Debug)] +pub struct Atlas { + texture: wgpu::Texture, + texture_view: wgpu::TextureView, + layers: Vec, +} + +impl Atlas { + pub fn new(device: &wgpu::Device) -> Self { + let extent = wgpu::Extent3d { + width: SIZE, + height: SIZE, + depth: 1, + }; + + let texture = device.create_texture(&wgpu::TextureDescriptor { + size: extent, + array_layer_count: 2, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsage::COPY_DST + | wgpu::TextureUsage::COPY_SRC + | wgpu::TextureUsage::SAMPLED, + }); + + let texture_view = texture.create_default_view(); + + Atlas { + texture, + texture_view, + layers: vec![Layer::Empty, Layer::Empty], + } + } + + pub fn view(&self) -> &wgpu::TextureView { + &self.texture_view + } + + pub fn layer_count(&self) -> usize { + self.layers.len() + } + + pub fn upload( + &mut self, + width: u32, + height: u32, + data: &[C], + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + ) -> Option + where + C: Copy + 'static, + { + let entry = { + let current_size = self.layers.len(); + let entry = self.allocate(width, height)?; + + // We grow the internal texture after allocating if necessary + let new_layers = self.layers.len() - current_size; + self.grow(new_layers, device, encoder); + + entry + }; + + log::info!("Allocated atlas entry: {:?}", entry); + + let buffer = device + .create_buffer_mapped(data.len(), wgpu::BufferUsage::COPY_SRC) + .fill_from_slice(data); + + match &entry { + Entry::Contiguous(allocation) => { + self.upload_allocation( + &buffer, + width, + height, + 0, + &allocation, + encoder, + ); + } + Entry::Fragmented { fragments, .. } => { + for fragment in fragments { + let (x, y) = fragment.position; + let offset = (y * width + x) as usize * 4; + + self.upload_allocation( + &buffer, + width, + height, + offset, + &fragment.allocation, + encoder, + ); + } + } + } + + log::info!("Current atlas: {:?}", self); + + Some(entry) + } + + pub fn remove(&mut self, entry: &Entry) { + log::info!("Removing atlas entry: {:?}", entry); + + match entry { + Entry::Contiguous(allocation) => { + self.deallocate(allocation); + } + Entry::Fragmented { fragments, .. } => { + for fragment in fragments { + self.deallocate(&fragment.allocation); + } + } + } + } + + fn allocate(&mut self, width: u32, height: u32) -> Option { + // Allocate one layer if texture fits perfectly + if width == SIZE && height == SIZE { + let mut empty_layers = self + .layers + .iter_mut() + .enumerate() + .filter(|(_, layer)| layer.is_empty()); + + if let Some((i, layer)) = empty_layers.next() { + *layer = Layer::Full; + + return Some(Entry::Contiguous(Allocation::Full { layer: i })); + } + + self.layers.push(Layer::Full); + + return Some(Entry::Contiguous(Allocation::Full { + layer: self.layers.len() - 1, + })); + } + + // Split big textures across multiple layers + if width > SIZE || height > SIZE { + let mut fragments = Vec::new(); + let mut y = 0; + + while y < height { + let height = std::cmp::min(height - y, SIZE); + let mut x = 0; + + while x < width { + let width = std::cmp::min(width - x, SIZE); + + let allocation = self.allocate(width, height)?; + + if let Entry::Contiguous(allocation) = allocation { + fragments.push(entry::Fragment { + position: (x, y), + allocation, + }); + } + + x += width; + } + + y += height; + } + + return Some(Entry::Fragmented { + size: (width, height), + fragments, + }); + } + + // Try allocating on an existing layer + for (i, layer) in self.layers.iter_mut().enumerate() { + match layer { + Layer::Empty => { + let mut allocator = Allocator::new(SIZE); + + if let Some(region) = allocator.allocate(width, height) { + *layer = Layer::Busy(allocator); + + return Some(Entry::Contiguous(Allocation::Partial { + region, + layer: i, + })); + } + } + Layer::Busy(allocator) => { + if let Some(region) = allocator.allocate(width, height) { + return Some(Entry::Contiguous(Allocation::Partial { + region, + layer: i, + })); + } + } + _ => {} + } + } + + // Create new layer with atlas allocator + let mut allocator = Allocator::new(SIZE); + + if let Some(region) = allocator.allocate(width, height) { + self.layers.push(Layer::Busy(allocator)); + + return Some(Entry::Contiguous(Allocation::Partial { + region, + layer: self.layers.len() - 1, + })); + } + + // We ran out of memory (?) + None + } + + fn deallocate(&mut self, allocation: &Allocation) { + log::info!("Deallocating atlas: {:?}", allocation); + + match allocation { + Allocation::Full { layer } => { + self.layers[*layer] = Layer::Empty; + } + Allocation::Partial { layer, region } => { + let layer = &mut self.layers[*layer]; + + if let Layer::Busy(allocator) = layer { + allocator.deallocate(region); + + if allocator.is_empty() { + *layer = Layer::Empty; + } + } + } + } + } + + fn upload_allocation( + &mut self, + buffer: &wgpu::Buffer, + image_width: u32, + image_height: u32, + offset: usize, + allocation: &Allocation, + encoder: &mut wgpu::CommandEncoder, + ) { + let (x, y) = allocation.position(); + let (width, height) = allocation.size(); + let layer = allocation.layer(); + + let extent = wgpu::Extent3d { + width, + height, + depth: 1, + }; + + encoder.copy_buffer_to_texture( + wgpu::BufferCopyView { + buffer, + offset: offset as u64, + row_pitch: 4 * image_width, + image_height, + }, + wgpu::TextureCopyView { + texture: &self.texture, + array_layer: layer as u32, + mip_level: 0, + origin: wgpu::Origin3d { + x: x as f32, + y: y as f32, + z: 0.0, + }, + }, + extent, + ); + } + + fn grow( + &mut self, + amount: usize, + device: &wgpu::Device, + encoder: &mut wgpu::CommandEncoder, + ) { + if amount == 0 { + return; + } + + let new_texture = device.create_texture(&wgpu::TextureDescriptor { + size: wgpu::Extent3d { + width: SIZE, + height: SIZE, + depth: 1, + }, + array_layer_count: self.layers.len() as u32, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + usage: wgpu::TextureUsage::COPY_DST + | wgpu::TextureUsage::COPY_SRC + | wgpu::TextureUsage::SAMPLED, + }); + + let amount_to_copy = self.layers.len() - amount; + + for (i, layer) in + self.layers.iter_mut().take(amount_to_copy).enumerate() + { + if layer.is_empty() { + continue; + } + + encoder.copy_texture_to_texture( + wgpu::TextureCopyView { + texture: &self.texture, + array_layer: i as u32, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + wgpu::TextureCopyView { + texture: &new_texture, + array_layer: i as u32, + mip_level: 0, + origin: wgpu::Origin3d { + x: 0.0, + y: 0.0, + z: 0.0, + }, + }, + wgpu::Extent3d { + width: SIZE, + height: SIZE, + depth: 1, + }, + ); + } + + self.texture = new_texture; + self.texture_view = self.texture.create_default_view(); + } +} diff --git a/wgpu/src/image/atlas/allocation.rs b/wgpu/src/image/atlas/allocation.rs new file mode 100644 index 00000000..59b7239f --- /dev/null +++ b/wgpu/src/image/atlas/allocation.rs @@ -0,0 +1,35 @@ +use crate::image::atlas::{self, allocator}; + +#[derive(Debug)] +pub enum Allocation { + Partial { + layer: usize, + region: allocator::Region, + }, + Full { + layer: usize, + }, +} + +impl Allocation { + pub fn position(&self) -> (u32, u32) { + match self { + Allocation::Partial { region, .. } => region.position(), + Allocation::Full { .. } => (0, 0), + } + } + + pub fn size(&self) -> (u32, u32) { + match self { + Allocation::Partial { region, .. } => region.size(), + Allocation::Full { .. } => (atlas::SIZE, atlas::SIZE), + } + } + + pub fn layer(&self) -> usize { + match self { + Allocation::Partial { layer, .. } => *layer, + Allocation::Full { layer } => *layer, + } + } +} diff --git a/wgpu/src/image/atlas/allocator.rs b/wgpu/src/image/atlas/allocator.rs new file mode 100644 index 00000000..7a4ff5b1 --- /dev/null +++ b/wgpu/src/image/atlas/allocator.rs @@ -0,0 +1,69 @@ +use guillotiere::{AtlasAllocator, Size}; + +pub struct Allocator { + raw: AtlasAllocator, + allocations: usize, +} + +impl Allocator { + pub fn new(size: u32) -> Allocator { + let raw = AtlasAllocator::new(Size::new(size as i32, size as i32)); + + Allocator { + raw, + allocations: 0, + } + } + + pub fn allocate(&mut self, width: u32, height: u32) -> Option { + let allocation = + self.raw.allocate(Size::new(width as i32, height as i32))?; + + self.allocations += 1; + + Some(Region { allocation }) + } + + pub fn deallocate(&mut self, region: &Region) { + self.raw.deallocate(region.allocation.id); + + self.allocations = self.allocations.saturating_sub(1); + } + + pub fn is_empty(&self) -> bool { + self.allocations == 0 + } +} + +pub struct Region { + allocation: guillotiere::Allocation, +} + +impl Region { + pub fn position(&self) -> (u32, u32) { + let rectangle = &self.allocation.rectangle; + + (rectangle.min.x as u32, rectangle.min.y as u32) + } + + pub fn size(&self) -> (u32, u32) { + let size = self.allocation.rectangle.size(); + + (size.width as u32, size.height as u32) + } +} + +impl std::fmt::Debug for Allocator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Allocator") + } +} + +impl std::fmt::Debug for Region { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Region") + .field("id", &self.allocation.id) + .field("rectangle", &self.allocation.rectangle) + .finish() + } +} diff --git a/wgpu/src/image/atlas/entry.rs b/wgpu/src/image/atlas/entry.rs new file mode 100644 index 00000000..0310fc54 --- /dev/null +++ b/wgpu/src/image/atlas/entry.rs @@ -0,0 +1,26 @@ +use crate::image::atlas; + +#[derive(Debug)] +pub enum Entry { + Contiguous(atlas::Allocation), + Fragmented { + size: (u32, u32), + fragments: Vec, + }, +} + +impl Entry { + #[cfg(feature = "image")] + pub fn size(&self) -> (u32, u32) { + match self { + Entry::Contiguous(allocation) => allocation.size(), + Entry::Fragmented { size, .. } => *size, + } + } +} + +#[derive(Debug)] +pub struct Fragment { + pub position: (u32, u32), + pub allocation: atlas::Allocation, +} diff --git a/wgpu/src/image/atlas/layer.rs b/wgpu/src/image/atlas/layer.rs new file mode 100644 index 00000000..b1084ed9 --- /dev/null +++ b/wgpu/src/image/atlas/layer.rs @@ -0,0 +1,17 @@ +use crate::image::atlas::Allocator; + +#[derive(Debug)] +pub enum Layer { + Empty, + Busy(Allocator), + Full, +} + +impl Layer { + pub fn is_empty(&self) -> bool { + match self { + Layer::Empty => true, + _ => false, + } + } +} diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 883c32f7..3edec57e 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -1,4 +1,4 @@ -use crate::texture::atlas::{self, Atlas}; +use crate::image::atlas::{self, Atlas}; use iced_native::image; use std::collections::{HashMap, HashSet}; diff --git a/wgpu/src/image/vector.rs b/wgpu/src/image/vector.rs index 6b12df54..bae0f82f 100644 --- a/wgpu/src/image/vector.rs +++ b/wgpu/src/image/vector.rs @@ -1,4 +1,4 @@ -use crate::texture::atlas::{self, Atlas}; +use crate::image::atlas::{self, Atlas}; use iced_native::svg; use std::collections::{HashMap, HashSet}; diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index a807b44d..1d63abbf 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -30,13 +30,11 @@ pub mod triangle; pub mod widget; pub mod window; -mod image; mod primitive; mod quad; mod renderer; mod target; mod text; -mod texture; mod transformation; mod viewport; @@ -52,6 +50,11 @@ pub use viewport::Viewport; #[doc(no_inline)] pub use widget::*; -pub(crate) use self::image::Image; pub(crate) use quad::Quad; pub(crate) use transformation::Transformation; + +#[cfg(any(feature = "image", feature = "svg"))] +mod image; + +#[cfg(any(feature = "image", feature = "svg"))] +pub(crate) use self::image::Image; diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index b5dce480..d9ef9fc4 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -1,7 +1,11 @@ use crate::{ - image, quad, text, triangle, Defaults, Image, Primitive, Quad, Settings, - Target, Transformation, + quad, text, triangle, Defaults, Primitive, Quad, Settings, Target, + Transformation, }; + +#[cfg(any(feature = "image", feature = "svg"))] +use crate::{image, Image}; + use iced_native::{ layout, Background, Color, Layout, MouseCursor, Point, Rectangle, Vector, Widget, @@ -16,18 +20,22 @@ mod widget; #[derive(Debug)] pub struct Renderer { quad_pipeline: quad::Pipeline, - image_pipeline: image::Pipeline, text_pipeline: text::Pipeline, - triangle_pipeline: crate::triangle::Pipeline, + triangle_pipeline: triangle::Pipeline, + + #[cfg(any(feature = "image", feature = "svg"))] + image_pipeline: image::Pipeline, } struct Layer<'a> { bounds: Rectangle, offset: Vector, quads: Vec, - images: Vec, meshes: Vec<(Point, Arc)>, text: Vec>, + + #[cfg(any(feature = "image", feature = "svg"))] + images: Vec, } impl<'a> Layer<'a> { @@ -36,9 +44,11 @@ impl<'a> Layer<'a> { bounds, offset, quads: Vec::new(), - images: Vec::new(), text: Vec::new(), meshes: Vec::new(), + + #[cfg(any(feature = "image", feature = "svg"))] + images: Vec::new(), } } } @@ -51,19 +61,22 @@ impl Renderer { let text_pipeline = text::Pipeline::new(device, settings.format, settings.default_font); let quad_pipeline = quad::Pipeline::new(device, settings.format); - let image_pipeline = - crate::image::Pipeline::new(device, settings.format); let triangle_pipeline = triangle::Pipeline::new( device, settings.format, settings.antialiasing, ); + #[cfg(any(feature = "image", feature = "svg"))] + let image_pipeline = image::Pipeline::new(device, settings.format); + Self { quad_pipeline, - image_pipeline, text_pipeline, triangle_pipeline, + + #[cfg(any(feature = "image", feature = "svg"))] + image_pipeline, } } @@ -116,6 +129,7 @@ impl Renderer { ); } + #[cfg(any(feature = "image", feature = "svg"))] self.image_pipeline.trim_cache(); *mouse_cursor @@ -223,20 +237,6 @@ impl Renderer { border_color: border_color.into_linear(), }); } - Primitive::Image { handle, bounds } => { - layer.images.push(Image { - handle: image::Handle::Raster(handle.clone()), - position: [bounds.x, bounds.y], - size: [bounds.width, bounds.height], - }); - } - Primitive::Svg { handle, bounds } => { - layer.images.push(Image { - handle: image::Handle::Vector(handle.clone()), - position: [bounds.x, bounds.y], - size: [bounds.width, bounds.height], - }); - } Primitive::Mesh2D { origin, buffers } => { layer.meshes.push((*origin, buffers.clone())); } @@ -264,6 +264,28 @@ impl Renderer { layers.push(new_layer); } } + + #[cfg(feature = "image")] + Primitive::Image { handle, bounds } => { + layer.images.push(Image { + handle: image::Handle::Raster(handle.clone()), + position: [bounds.x, bounds.y], + size: [bounds.width, bounds.height], + }); + } + #[cfg(not(feature = "image"))] + Primitive::Image { .. } => {} + + #[cfg(feature = "svg")] + Primitive::Svg { handle, bounds } => { + layer.images.push(Image { + handle: image::Handle::Vector(handle.clone()), + position: [bounds.x, bounds.y], + size: [bounds.width, bounds.height], + }); + } + #[cfg(not(feature = "svg"))] + Primitive::Svg { .. } => {} } } @@ -346,23 +368,26 @@ impl Renderer { ); } - if layer.images.len() > 0 { - let translated_and_scaled = transformation - * Transformation::scale(scale_factor, scale_factor) - * Transformation::translate( - -(layer.offset.x as f32), - -(layer.offset.y as f32), + #[cfg(any(feature = "image", feature = "svg"))] + { + if layer.images.len() > 0 { + let translated_and_scaled = transformation + * Transformation::scale(scale_factor, scale_factor) + * Transformation::translate( + -(layer.offset.x as f32), + -(layer.offset.y as f32), + ); + + self.image_pipeline.draw( + device, + encoder, + &layer.images, + translated_and_scaled, + bounds, + target, + scale_factor, ); - - self.image_pipeline.draw( - device, - encoder, - &layer.images, - translated_and_scaled, - bounds, - target, - scale_factor, - ); + } } if layer.text.len() > 0 { diff --git a/wgpu/src/texture.rs b/wgpu/src/texture.rs deleted file mode 100644 index 00b60bfa..00000000 --- a/wgpu/src/texture.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod atlas; - -pub use atlas::Atlas; diff --git a/wgpu/src/texture/atlas.rs b/wgpu/src/texture/atlas.rs deleted file mode 100644 index 86a5ff49..00000000 --- a/wgpu/src/texture/atlas.rs +++ /dev/null @@ -1,361 +0,0 @@ -pub mod entry; - -mod allocation; -mod allocator; -mod layer; - -pub use allocation::Allocation; -pub use entry::Entry; -pub use layer::Layer; - -use allocator::Allocator; - -pub const SIZE: u32 = 2048; - -#[derive(Debug)] -pub struct Atlas { - texture: wgpu::Texture, - texture_view: wgpu::TextureView, - layers: Vec, -} - -impl Atlas { - pub fn new(device: &wgpu::Device) -> Self { - let extent = wgpu::Extent3d { - width: SIZE, - height: SIZE, - depth: 1, - }; - - let texture = device.create_texture(&wgpu::TextureDescriptor { - size: extent, - array_layer_count: 2, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bgra8UnormSrgb, - usage: wgpu::TextureUsage::COPY_DST - | wgpu::TextureUsage::COPY_SRC - | wgpu::TextureUsage::SAMPLED, - }); - - let texture_view = texture.create_default_view(); - - Atlas { - texture, - texture_view, - layers: vec![Layer::Empty, Layer::Empty], - } - } - - pub fn view(&self) -> &wgpu::TextureView { - &self.texture_view - } - - pub fn layer_count(&self) -> usize { - self.layers.len() - } - - pub fn upload( - &mut self, - width: u32, - height: u32, - data: &[C], - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - ) -> Option - where - C: Copy + 'static, - { - let entry = { - let current_size = self.layers.len(); - let entry = self.allocate(width, height)?; - - // We grow the internal texture after allocating if necessary - let new_layers = self.layers.len() - current_size; - self.grow(new_layers, device, encoder); - - entry - }; - - log::info!("Allocated atlas entry: {:?}", entry); - - let buffer = device - .create_buffer_mapped(data.len(), wgpu::BufferUsage::COPY_SRC) - .fill_from_slice(data); - - match &entry { - Entry::Contiguous(allocation) => { - self.upload_allocation( - &buffer, - width, - height, - 0, - &allocation, - encoder, - ); - } - Entry::Fragmented { fragments, .. } => { - for fragment in fragments { - let (x, y) = fragment.position; - let offset = (y * width + x) as usize * 4; - - self.upload_allocation( - &buffer, - width, - height, - offset, - &fragment.allocation, - encoder, - ); - } - } - } - - log::info!("Current atlas: {:?}", self); - - Some(entry) - } - - pub fn remove(&mut self, entry: &Entry) { - log::info!("Removing atlas entry: {:?}", entry); - - match entry { - Entry::Contiguous(allocation) => { - self.deallocate(allocation); - } - Entry::Fragmented { fragments, .. } => { - for fragment in fragments { - self.deallocate(&fragment.allocation); - } - } - } - } - - fn allocate(&mut self, width: u32, height: u32) -> Option { - // Allocate one layer if texture fits perfectly - if width == SIZE && height == SIZE { - let mut empty_layers = self - .layers - .iter_mut() - .enumerate() - .filter(|(_, layer)| layer.is_empty()); - - if let Some((i, layer)) = empty_layers.next() { - *layer = Layer::Full; - - return Some(Entry::Contiguous(Allocation::Full { layer: i })); - } - - self.layers.push(Layer::Full); - - return Some(Entry::Contiguous(Allocation::Full { - layer: self.layers.len() - 1, - })); - } - - // Split big textures across multiple layers - if width > SIZE || height > SIZE { - let mut fragments = Vec::new(); - let mut y = 0; - - while y < height { - let height = std::cmp::min(height - y, SIZE); - let mut x = 0; - - while x < width { - let width = std::cmp::min(width - x, SIZE); - - let allocation = self.allocate(width, height)?; - - if let Entry::Contiguous(allocation) = allocation { - fragments.push(entry::Fragment { - position: (x, y), - allocation, - }); - } - - x += width; - } - - y += height; - } - - return Some(Entry::Fragmented { - size: (width, height), - fragments, - }); - } - - // Try allocating on an existing layer - for (i, layer) in self.layers.iter_mut().enumerate() { - match layer { - Layer::Empty => { - let mut allocator = Allocator::new(SIZE); - - if let Some(region) = allocator.allocate(width, height) { - *layer = Layer::Busy(allocator); - - return Some(Entry::Contiguous(Allocation::Partial { - region, - layer: i, - })); - } - } - Layer::Busy(allocator) => { - if let Some(region) = allocator.allocate(width, height) { - return Some(Entry::Contiguous(Allocation::Partial { - region, - layer: i, - })); - } - } - _ => {} - } - } - - // Create new layer with atlas allocator - let mut allocator = Allocator::new(SIZE); - - if let Some(region) = allocator.allocate(width, height) { - self.layers.push(Layer::Busy(allocator)); - - return Some(Entry::Contiguous(Allocation::Partial { - region, - layer: self.layers.len() - 1, - })); - } - - // We ran out of memory (?) - None - } - - fn deallocate(&mut self, allocation: &Allocation) { - log::info!("Deallocating atlas: {:?}", allocation); - - match allocation { - Allocation::Full { layer } => { - self.layers[*layer] = Layer::Empty; - } - Allocation::Partial { layer, region } => { - let layer = &mut self.layers[*layer]; - - if let Layer::Busy(allocator) = layer { - allocator.deallocate(region); - - if allocator.is_empty() { - *layer = Layer::Empty; - } - } - } - } - } - - fn upload_allocation( - &mut self, - buffer: &wgpu::Buffer, - image_width: u32, - image_height: u32, - offset: usize, - allocation: &Allocation, - encoder: &mut wgpu::CommandEncoder, - ) { - let (x, y) = allocation.position(); - let (width, height) = allocation.size(); - let layer = allocation.layer(); - - let extent = wgpu::Extent3d { - width, - height, - depth: 1, - }; - - encoder.copy_buffer_to_texture( - wgpu::BufferCopyView { - buffer, - offset: offset as u64, - row_pitch: 4 * image_width, - image_height, - }, - wgpu::TextureCopyView { - texture: &self.texture, - array_layer: layer as u32, - mip_level: 0, - origin: wgpu::Origin3d { - x: x as f32, - y: y as f32, - z: 0.0, - }, - }, - extent, - ); - } - - fn grow( - &mut self, - amount: usize, - device: &wgpu::Device, - encoder: &mut wgpu::CommandEncoder, - ) { - if amount == 0 { - return; - } - - let new_texture = device.create_texture(&wgpu::TextureDescriptor { - size: wgpu::Extent3d { - width: SIZE, - height: SIZE, - depth: 1, - }, - array_layer_count: self.layers.len() as u32, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Bgra8UnormSrgb, - usage: wgpu::TextureUsage::COPY_DST - | wgpu::TextureUsage::COPY_SRC - | wgpu::TextureUsage::SAMPLED, - }); - - let amount_to_copy = self.layers.len() - amount; - - for (i, layer) in - self.layers.iter_mut().take(amount_to_copy).enumerate() - { - if layer.is_empty() { - continue; - } - - encoder.copy_texture_to_texture( - wgpu::TextureCopyView { - texture: &self.texture, - array_layer: i as u32, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, - z: 0.0, - }, - }, - wgpu::TextureCopyView { - texture: &new_texture, - array_layer: i as u32, - mip_level: 0, - origin: wgpu::Origin3d { - x: 0.0, - y: 0.0, - z: 0.0, - }, - }, - wgpu::Extent3d { - width: SIZE, - height: SIZE, - depth: 1, - }, - ); - } - - self.texture = new_texture; - self.texture_view = self.texture.create_default_view(); - } -} diff --git a/wgpu/src/texture/atlas/allocation.rs b/wgpu/src/texture/atlas/allocation.rs deleted file mode 100644 index e17b3f8c..00000000 --- a/wgpu/src/texture/atlas/allocation.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::texture::atlas::{self, allocator}; - -#[derive(Debug)] -pub enum Allocation { - Partial { - layer: usize, - region: allocator::Region, - }, - Full { - layer: usize, - }, -} - -impl Allocation { - pub fn position(&self) -> (u32, u32) { - match self { - Allocation::Partial { region, .. } => region.position(), - Allocation::Full { .. } => (0, 0), - } - } - - pub fn size(&self) -> (u32, u32) { - match self { - Allocation::Partial { region, .. } => region.size(), - Allocation::Full { .. } => (atlas::SIZE, atlas::SIZE), - } - } - - pub fn layer(&self) -> usize { - match self { - Allocation::Partial { layer, .. } => *layer, - Allocation::Full { layer } => *layer, - } - } -} diff --git a/wgpu/src/texture/atlas/allocator.rs b/wgpu/src/texture/atlas/allocator.rs deleted file mode 100644 index 7a4ff5b1..00000000 --- a/wgpu/src/texture/atlas/allocator.rs +++ /dev/null @@ -1,69 +0,0 @@ -use guillotiere::{AtlasAllocator, Size}; - -pub struct Allocator { - raw: AtlasAllocator, - allocations: usize, -} - -impl Allocator { - pub fn new(size: u32) -> Allocator { - let raw = AtlasAllocator::new(Size::new(size as i32, size as i32)); - - Allocator { - raw, - allocations: 0, - } - } - - pub fn allocate(&mut self, width: u32, height: u32) -> Option { - let allocation = - self.raw.allocate(Size::new(width as i32, height as i32))?; - - self.allocations += 1; - - Some(Region { allocation }) - } - - pub fn deallocate(&mut self, region: &Region) { - self.raw.deallocate(region.allocation.id); - - self.allocations = self.allocations.saturating_sub(1); - } - - pub fn is_empty(&self) -> bool { - self.allocations == 0 - } -} - -pub struct Region { - allocation: guillotiere::Allocation, -} - -impl Region { - pub fn position(&self) -> (u32, u32) { - let rectangle = &self.allocation.rectangle; - - (rectangle.min.x as u32, rectangle.min.y as u32) - } - - pub fn size(&self) -> (u32, u32) { - let size = self.allocation.rectangle.size(); - - (size.width as u32, size.height as u32) - } -} - -impl std::fmt::Debug for Allocator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Allocator") - } -} - -impl std::fmt::Debug for Region { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Region") - .field("id", &self.allocation.id) - .field("rectangle", &self.allocation.rectangle) - .finish() - } -} diff --git a/wgpu/src/texture/atlas/entry.rs b/wgpu/src/texture/atlas/entry.rs deleted file mode 100644 index 2c064665..00000000 --- a/wgpu/src/texture/atlas/entry.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::texture::atlas; - -#[derive(Debug)] -pub enum Entry { - Contiguous(atlas::Allocation), - Fragmented { - size: (u32, u32), - fragments: Vec, - }, -} - -impl Entry { - pub fn size(&self) -> (u32, u32) { - match self { - Entry::Contiguous(allocation) => allocation.size(), - Entry::Fragmented { size, .. } => *size, - } - } -} - -#[derive(Debug)] -pub struct Fragment { - pub position: (u32, u32), - pub allocation: atlas::Allocation, -} diff --git a/wgpu/src/texture/atlas/layer.rs b/wgpu/src/texture/atlas/layer.rs deleted file mode 100644 index b025d8a1..00000000 --- a/wgpu/src/texture/atlas/layer.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::texture::atlas::Allocator; - -#[derive(Debug)] -pub enum Layer { - Empty, - Busy(Allocator), - Full, -} - -impl Layer { - pub fn is_empty(&self) -> bool { - match self { - Layer::Empty => true, - _ => false, - } - } -} -- cgit From 88d4cd097044077127e1b3aa8fcb04afed185491 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 28 Feb 2020 14:41:07 +0100 Subject: Remove unnecessary `pub(crate) use` --- wgpu/src/lib.rs | 3 --- wgpu/src/renderer.rs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 1d63abbf..4e0cbc60 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -55,6 +55,3 @@ pub(crate) use transformation::Transformation; #[cfg(any(feature = "image", feature = "svg"))] mod image; - -#[cfg(any(feature = "image", feature = "svg"))] -pub(crate) use self::image::Image; diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index d9ef9fc4..1da19b1a 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -4,7 +4,7 @@ use crate::{ }; #[cfg(any(feature = "image", feature = "svg"))] -use crate::{image, Image}; +use crate::image::{self, Image}; use iced_native::{ layout, Background, Color, Layout, MouseCursor, Point, Rectangle, Vector, -- cgit From 96f75eae4d4d19eeff8e55201822388d34445ec6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 28 Feb 2020 19:38:17 +0100 Subject: Fix offsets of buffer uploads in triangle pipeline --- wgpu/src/triangle.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index fe34040e..0a118bd2 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -248,7 +248,7 @@ impl Pipeline { &vertex_buffer, 0, &self.vertex_buffer.raw, - last_vertex as u64, + (std::mem::size_of::() * last_vertex) as u64, (std::mem::size_of::() * mesh.vertices.len()) as u64, ); @@ -256,7 +256,7 @@ impl Pipeline { &index_buffer, 0, &self.index_buffer.raw, - last_index as u64, + (std::mem::size_of::() * last_index) as u64, (std::mem::size_of::() * mesh.indices.len()) as u64, ); @@ -313,27 +313,30 @@ impl Pipeline { depth_stencil_attachment: None, }); + 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.drain(..).enumerate() + offsets.into_iter().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..indices as u32, 0, 0..1); } -- cgit From 18410154289fa3262403bb2c9de3dd741fd7dda2 Mon Sep 17 00:00:00 2001 From: Soham Chowdhury Date: Sat, 29 Feb 2020 07:32:42 +0530 Subject: Add support for loading already-decoded image pixels --- native/src/widget/image.rs | 26 ++++++++++++++++++++++++++ wgpu/src/image/raster.rs | 15 +++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 9b92c7f1..a1743744 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -125,6 +125,19 @@ impl Handle { Self::from_data(Data::Path(path.into())) } + /// Creates an image [`Handle`] containing the image pixels directly. + /// + /// This is useful if you have already decoded your image. + /// + /// [`Handle`]: struct.Handle.html + pub fn from_pixels(width: u32, height: u32, pixels: Vec) -> Handle { + Self::from_data(Data::Pixels { + width, + height, + pixels, + }) + } + /// Creates an image [`Handle`] containing the image data directly. /// /// This is useful if you already have your image loaded in-memory, maybe @@ -188,6 +201,16 @@ pub enum Data { /// In-memory data Bytes(Vec), + + /// Decoded image pixels in BGRA format. + Pixels { + /// The width of the image. + width: u32, + /// The height of the image. + height: u32, + /// The pixels. + pixels: Vec, + }, } impl std::fmt::Debug for Data { @@ -195,6 +218,9 @@ impl std::fmt::Debug for Data { match self { Data::Path(path) => write!(f, "Path({:?})", path), Data::Bytes(_) => write!(f, "Bytes(...)"), + Data::Pixels { width, height, .. } => { + write!(f, "Pixels({} * {})", width, height) + } } } } diff --git a/wgpu/src/image/raster.rs b/wgpu/src/image/raster.rs index 3edec57e..4f69df8c 100644 --- a/wgpu/src/image/raster.rs +++ b/wgpu/src/image/raster.rs @@ -55,6 +55,21 @@ impl Cache { Memory::Invalid } } + image::Data::Pixels { + width, + height, + pixels, + } => { + if let Some(image) = ::image::ImageBuffer::from_vec( + *width, + *height, + pixels.to_vec(), + ) { + Memory::Host(image) + } else { + Memory::Invalid + } + } }; self.insert(handle, memory); -- cgit From eb7e3250d3da495f46480360c99540a8f643d2e6 Mon Sep 17 00:00:00 2001 From: Soham Chowdhury Date: Sun, 1 Mar 2020 06:44:06 +0530 Subject: Note BGRA requirement in Handle::from_pixels docs --- native/src/widget/image.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index a1743744..fbe38bfc 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -125,7 +125,9 @@ impl Handle { Self::from_data(Data::Path(path.into())) } - /// Creates an image [`Handle`] containing the image pixels directly. + /// Creates an image [`Handle`] containing the image pixels directly. This + /// function expects the input data to be provided as a `Vec` of BGRA + /// pixels. /// /// This is useful if you have already decoded your image. /// -- cgit From 02091267bf38d4da5efab57e9b63504804b3a468 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 3 Mar 2020 00:06:40 +0100 Subject: Use new `wgpu_glyph` release in `iced_wgpu` --- wgpu/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 56839cf0..6c75af7b 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -15,8 +15,8 @@ canvas = ["lyon"] iced_native = { version = "0.1.0", path = "../native" } iced_style = { version = "0.1.0-alpha", path = "../style" } wgpu = "0.4" +wgpu_glyph = "0.7" glyph_brush = "0.6" -wgpu_glyph = { version = "0.7", git = "https://github.com/hecrj/wgpu_glyph", branch = "fix/font-load-panic" } raw-window-handle = "0.3" glam = "0.8" font-kit = "0.4" -- cgit From 012b4adec7a87331b2d75f6bc5d2a0189dcd7ec5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 4 Mar 2020 04:10:26 +0100 Subject: Draft `Panes` widget and `panes` example --- Cargo.toml | 1 + examples/clock/Cargo.toml | 3 - examples/clock/README.md | 4 +- examples/clock/src/lib.rs | 187 ++++++++++++++++++++++++++++++ examples/clock/src/main.rs | 188 +----------------------------- examples/panes/Cargo.toml | 11 ++ examples/panes/README.md | 18 +++ examples/panes/src/main.rs | 95 +++++++++++++++ examples/stopwatch/src/lib.rs | 204 ++++++++++++++++++++++++++++++++ examples/stopwatch/src/main.rs | 204 +------------------------------- native/src/element.rs | 2 +- native/src/lib.rs | 2 +- native/src/widget.rs | 3 + native/src/widget/panes.rs | 238 ++++++++++++++++++++++++++++++++++++++ src/widget.rs | 2 +- wgpu/src/renderer/widget.rs | 1 + wgpu/src/renderer/widget/panes.rs | 34 ++++++ 17 files changed, 801 insertions(+), 396 deletions(-) create mode 100644 examples/clock/src/lib.rs create mode 100644 examples/panes/Cargo.toml create mode 100644 examples/panes/README.md create mode 100644 examples/panes/src/main.rs create mode 100644 examples/stopwatch/src/lib.rs create mode 100644 native/src/widget/panes.rs create mode 100644 wgpu/src/renderer/widget/panes.rs diff --git a/Cargo.toml b/Cargo.toml index 01231b70..3b79abdb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ members = [ "examples/events", "examples/geometry", "examples/integration", + "examples/panes", "examples/pokedex", "examples/progress_bar", "examples/solar_system", diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml index 308cbfbb..ab771405 100644 --- a/examples/clock/Cargo.toml +++ b/examples/clock/Cargo.toml @@ -5,9 +5,6 @@ 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" } diff --git a/examples/clock/README.md b/examples/clock/README.md index 17509180..a87edad0 100644 --- a/examples/clock/README.md +++ b/examples/clock/README.md @@ -2,7 +2,7 @@ An application that uses the `Canvas` widget to draw a clock and its hands to display the current time. -The __[`main`]__ file contains all the code of the example. +The __[`lib`]__ file contains the relevant code of the example.
@@ -13,4 +13,4 @@ You can run it with `cargo run`: cargo run --package clock ``` -[`main`]: src/main.rs +[`lib`]: src/lib.rs diff --git a/examples/clock/src/lib.rs b/examples/clock/src/lib.rs new file mode 100644 index 00000000..229ba8fe --- /dev/null +++ b/examples/clock/src/lib.rs @@ -0,0 +1,187 @@ +use iced::{ + canvas, executor, Application, Canvas, Color, Command, Container, Element, + Length, Point, Subscription, Vector, +}; + +#[derive(Debug)] +pub struct Clock { + now: LocalTime, + clock: canvas::layer::Cache, +} + +#[derive(Debug, Clone, Copy)] +pub enum Message { + Tick(chrono::DateTime), +} + +impl Application for Clock { + type Executor = executor::Default; + type Message = Message; + + fn new() -> (Self, Command) { + ( + Clock { + now: chrono::Local::now().into(), + clock: canvas::layer::Cache::new(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Clock - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Tick(local_time) => { + let now = local_time.into(); + + if now != self.now { + self.now = now; + self.clock.clear(); + } + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription { + time::every(std::time::Duration::from_millis(500)).map(Message::Tick) + } + + fn view(&mut self) -> Element { + 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) + .center_x() + .center_y() + .into() + } +} + +#[derive(Debug, PartialEq, Eq)] +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::Drawable for LocalTime { + fn draw(&self, frame: &mut canvas::Frame) { + let center = frame.center(); + let radius = frame.width().min(frame.height()) / 2.0; + let offset = Vector::new(center.x, center.y); + + let clock = canvas::Path::new(|path| path.circle(center, radius)); + + frame.fill( + &clock, + canvas::Fill::Color(Color::from_rgb8(0x12, 0x93, 0xD8)), + ); + + fn draw_hand( + n: u32, + total: u32, + length: f32, + offset: Vector, + path: &mut canvas::path::Builder, + ) { + 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) + offset); + } + + let hour_and_minute_hands = canvas::Path::new(|path| { + path.move_to(center); + draw_hand(self.hour, 12, 0.5 * radius, offset, path); + + path.move_to(center); + draw_hand(self.minute, 60, 0.8 * radius, offset, path) + }); + + frame.stroke( + &hour_and_minute_hands, + canvas::Stroke { + width: 6.0, + color: Color::WHITE, + line_cap: canvas::LineCap::Round, + ..canvas::Stroke::default() + }, + ); + + let second_hand = canvas::Path::new(|path| { + path.move_to(center); + draw_hand(self.second, 60, 0.8 * radius, offset, path) + }); + + frame.stroke( + &second_hand, + canvas::Stroke { + width: 3.0, + color: Color::WHITE, + line_cap: canvas::LineCap::Round, + ..canvas::Stroke::default() + }, + ); + } +} + +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/examples/clock/src/main.rs b/examples/clock/src/main.rs index d8266f06..454ff4f0 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,7 +1,5 @@ -use iced::{ - canvas, executor, Application, Canvas, Color, Command, Container, Element, - Length, Point, Settings, Subscription, Vector, -}; +use clock::Clock; +use iced::{Application, Settings}; pub fn main() { Clock::run(Settings { @@ -9,185 +7,3 @@ pub fn main() { ..Settings::default() }) } - -struct Clock { - now: LocalTime, - clock: canvas::layer::Cache, -} - -#[derive(Debug, Clone, Copy)] -enum Message { - Tick(chrono::DateTime), -} - -impl Application for Clock { - type Executor = executor::Default; - type Message = Message; - - fn new() -> (Self, Command) { - ( - Clock { - now: chrono::Local::now().into(), - clock: canvas::layer::Cache::new(), - }, - Command::none(), - ) - } - - fn title(&self) -> String { - String::from("Clock - Iced") - } - - fn update(&mut self, message: Message) -> Command { - match message { - Message::Tick(local_time) => { - let now = local_time.into(); - - if now != self.now { - self.now = now; - self.clock.clear(); - } - } - } - - Command::none() - } - - fn subscription(&self) -> Subscription { - time::every(std::time::Duration::from_millis(500)).map(Message::Tick) - } - - fn view(&mut self) -> Element { - 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) - .center_x() - .center_y() - .into() - } -} - -#[derive(Debug, PartialEq, Eq)] -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::Drawable for LocalTime { - fn draw(&self, frame: &mut canvas::Frame) { - let center = frame.center(); - let radius = frame.width().min(frame.height()) / 2.0; - let offset = Vector::new(center.x, center.y); - - let clock = canvas::Path::new(|path| path.circle(center, radius)); - - frame.fill( - &clock, - canvas::Fill::Color(Color::from_rgb8(0x12, 0x93, 0xD8)), - ); - - fn draw_hand( - n: u32, - total: u32, - length: f32, - offset: Vector, - path: &mut canvas::path::Builder, - ) { - 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) + offset); - } - - let hour_and_minute_hands = canvas::Path::new(|path| { - path.move_to(center); - draw_hand(self.hour, 12, 0.5 * radius, offset, path); - - path.move_to(center); - draw_hand(self.minute, 60, 0.8 * radius, offset, path) - }); - - frame.stroke( - &hour_and_minute_hands, - canvas::Stroke { - width: 6.0, - color: Color::WHITE, - line_cap: canvas::LineCap::Round, - ..canvas::Stroke::default() - }, - ); - - let second_hand = canvas::Path::new(|path| { - path.move_to(center); - draw_hand(self.second, 60, 0.8 * radius, offset, path) - }); - - frame.stroke( - &second_hand, - canvas::Stroke { - width: 3.0, - color: Color::WHITE, - line_cap: canvas::LineCap::Round, - ..canvas::Stroke::default() - }, - ); - } -} - -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/examples/panes/Cargo.toml b/examples/panes/Cargo.toml new file mode 100644 index 00000000..174d2cde --- /dev/null +++ b/examples/panes/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "panes" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../..", features = ["async-std"] } +clock = { path = "../clock" } +stopwatch = { path = "../stopwatch" } diff --git a/examples/panes/README.md b/examples/panes/README.md new file mode 100644 index 00000000..4d9fc5b9 --- /dev/null +++ b/examples/panes/README.md @@ -0,0 +1,18 @@ +## Counter + +The classic counter example explained in the [`README`](../../README.md). + +The __[`main`]__ file contains all the code of the example. + + + +You can run it with `cargo run`: +``` +cargo run --package counter +``` + +[`main`]: src/main.rs diff --git a/examples/panes/src/main.rs b/examples/panes/src/main.rs new file mode 100644 index 00000000..c1bf991a --- /dev/null +++ b/examples/panes/src/main.rs @@ -0,0 +1,95 @@ +use iced::{ + panes, Application, Command, Element, Panes, Settings, Subscription, +}; + +use clock::{self, Clock}; +use stopwatch::{self, Stopwatch}; + +pub fn main() { + Launcher::run(Settings { + antialiasing: true, + ..Settings::default() + }) +} + +struct Launcher { + panes: panes::State, +} + +#[derive(Debug)] +enum Example { + Clock(Clock), + Stopwatch(Stopwatch), +} + +#[derive(Debug, Clone)] +enum Message { + Clock(panes::Pane, clock::Message), + Stopwatch(panes::Pane, stopwatch::Message), +} + +impl Application for Launcher { + type Executor = iced::executor::Default; + type Message = Message; + + fn new() -> (Self, Command) { + let (clock, _) = Clock::new(); + let (panes, _) = panes::State::new(Example::Clock(clock)); + + dbg!(&panes); + + (Self { panes }, Command::none()) + } + + fn title(&self) -> String { + String::from("Panes - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Clock(pane, message) => { + if let Some(Example::Clock(clock)) = self.panes.get_mut(&pane) { + let _ = clock.update(message); + } + } + Message::Stopwatch(pane, message) => { + if let Some(Example::Stopwatch(stopwatch)) = + self.panes.get_mut(&pane) + { + let _ = stopwatch.update(message); + } + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription { + Subscription::batch(self.panes.iter().map(|(pane, example)| { + match example { + Example::Clock(clock) => clock + .subscription() + .map(move |message| Message::Clock(pane, message)), + + Example::Stopwatch(stopwatch) => stopwatch + .subscription() + .map(move |message| Message::Stopwatch(pane, message)), + } + })) + } + + fn view(&mut self) -> Element { + let Self { panes } = self; + + Panes::new(panes, |pane, example| match example { + Example::Clock(clock) => clock + .view() + .map(move |message| Message::Clock(pane, message)), + + Example::Stopwatch(stopwatch) => stopwatch + .view() + .map(move |message| Message::Stopwatch(pane, message)), + }) + .into() + } +} diff --git a/examples/stopwatch/src/lib.rs b/examples/stopwatch/src/lib.rs new file mode 100644 index 00000000..0219470b --- /dev/null +++ b/examples/stopwatch/src/lib.rs @@ -0,0 +1,204 @@ +use iced::{ + button, Align, Application, Button, Column, Command, Container, Element, + HorizontalAlignment, Length, Row, Subscription, Text, +}; +use std::time::{Duration, Instant}; + +#[derive(Debug)] +pub struct Stopwatch { + duration: Duration, + state: State, + toggle: button::State, + reset: button::State, +} + +#[derive(Debug)] +enum State { + Idle, + Ticking { last_tick: Instant }, +} + +#[derive(Debug, Clone)] +pub enum Message { + Toggle, + Reset, + Tick(Instant), +} + +impl Application for Stopwatch { + type Executor = iced_futures::executor::AsyncStd; + type Message = Message; + + fn new() -> (Stopwatch, Command) { + ( + Stopwatch { + duration: Duration::default(), + state: State::Idle, + toggle: button::State::new(), + reset: button::State::new(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Stopwatch - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Toggle => match self.state { + State::Idle => { + self.state = State::Ticking { + last_tick: Instant::now(), + }; + } + State::Ticking { .. } => { + self.state = State::Idle; + } + }, + Message::Tick(now) => match &mut self.state { + State::Ticking { last_tick } => { + self.duration += now - *last_tick; + *last_tick = now; + } + _ => {} + }, + Message::Reset => { + self.duration = Duration::default(); + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription { + match self.state { + State::Idle => Subscription::none(), + State::Ticking { .. } => { + time::every(Duration::from_millis(10)).map(Message::Tick) + } + } + } + + fn view(&mut self) -> Element { + const MINUTE: u64 = 60; + const HOUR: u64 = 60 * MINUTE; + + let seconds = self.duration.as_secs(); + + let duration = Text::new(format!( + "{:0>2}:{:0>2}:{:0>2}.{:0>2}", + seconds / HOUR, + (seconds % HOUR) / MINUTE, + seconds % MINUTE, + self.duration.subsec_millis() / 10, + )) + .size(40); + + let button = |state, label, style| { + Button::new( + state, + Text::new(label) + .horizontal_alignment(HorizontalAlignment::Center), + ) + .min_width(80) + .padding(10) + .style(style) + }; + + let toggle_button = { + let (label, color) = match self.state { + State::Idle => ("Start", style::Button::Primary), + State::Ticking { .. } => ("Stop", style::Button::Destructive), + }; + + button(&mut self.toggle, label, color).on_press(Message::Toggle) + }; + + let reset_button = + button(&mut self.reset, "Reset", style::Button::Secondary) + .on_press(Message::Reset); + + let controls = Row::new() + .spacing(20) + .push(toggle_button) + .push(reset_button); + + let content = Column::new() + .align_items(Align::Center) + .spacing(20) + .push(duration) + .push(controls); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +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 = std::time::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(|_| std::time::Instant::now()) + .boxed() + } + } +} + +mod style { + use iced::{button, Background, Color, Vector}; + + pub enum Button { + Primary, + Secondary, + Destructive, + } + + impl button::StyleSheet for Button { + fn active(&self) -> button::Style { + button::Style { + background: Some(Background::Color(match self { + Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), + Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), + Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2), + })), + border_radius: 12, + shadow_offset: Vector::new(1.0, 1.0), + text_color: Color::WHITE, + ..button::Style::default() + } + } + } +} diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index d84c4817..adcdffe4 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -1,206 +1,6 @@ -use iced::{ - button, Align, Application, Button, Column, Command, Container, Element, - HorizontalAlignment, Length, Row, Settings, Subscription, Text, -}; -use std::time::{Duration, Instant}; +use iced::{Application, Settings}; +use stopwatch::Stopwatch; pub fn main() { Stopwatch::run(Settings::default()) } - -struct Stopwatch { - duration: Duration, - state: State, - toggle: button::State, - reset: button::State, -} - -enum State { - Idle, - Ticking { last_tick: Instant }, -} - -#[derive(Debug, Clone)] -enum Message { - Toggle, - Reset, - Tick(Instant), -} - -impl Application for Stopwatch { - type Executor = iced_futures::executor::AsyncStd; - type Message = Message; - - fn new() -> (Stopwatch, Command) { - ( - Stopwatch { - duration: Duration::default(), - state: State::Idle, - toggle: button::State::new(), - reset: button::State::new(), - }, - Command::none(), - ) - } - - fn title(&self) -> String { - String::from("Stopwatch - Iced") - } - - fn update(&mut self, message: Message) -> Command { - match message { - Message::Toggle => match self.state { - State::Idle => { - self.state = State::Ticking { - last_tick: Instant::now(), - }; - } - State::Ticking { .. } => { - self.state = State::Idle; - } - }, - Message::Tick(now) => match &mut self.state { - State::Ticking { last_tick } => { - self.duration += now - *last_tick; - *last_tick = now; - } - _ => {} - }, - Message::Reset => { - self.duration = Duration::default(); - } - } - - Command::none() - } - - fn subscription(&self) -> Subscription { - match self.state { - State::Idle => Subscription::none(), - State::Ticking { .. } => { - time::every(Duration::from_millis(10)).map(Message::Tick) - } - } - } - - fn view(&mut self) -> Element { - const MINUTE: u64 = 60; - const HOUR: u64 = 60 * MINUTE; - - let seconds = self.duration.as_secs(); - - let duration = Text::new(format!( - "{:0>2}:{:0>2}:{:0>2}.{:0>2}", - seconds / HOUR, - (seconds % HOUR) / MINUTE, - seconds % MINUTE, - self.duration.subsec_millis() / 10, - )) - .size(40); - - let button = |state, label, style| { - Button::new( - state, - Text::new(label) - .horizontal_alignment(HorizontalAlignment::Center), - ) - .min_width(80) - .padding(10) - .style(style) - }; - - let toggle_button = { - let (label, color) = match self.state { - State::Idle => ("Start", style::Button::Primary), - State::Ticking { .. } => ("Stop", style::Button::Destructive), - }; - - button(&mut self.toggle, label, color).on_press(Message::Toggle) - }; - - let reset_button = - button(&mut self.reset, "Reset", style::Button::Secondary) - .on_press(Message::Reset); - - let controls = Row::new() - .spacing(20) - .push(toggle_button) - .push(reset_button); - - let content = Column::new() - .align_items(Align::Center) - .spacing(20) - .push(duration) - .push(controls); - - Container::new(content) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .into() - } -} - -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 = std::time::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(|_| std::time::Instant::now()) - .boxed() - } - } -} - -mod style { - use iced::{button, Background, Color, Vector}; - - pub enum Button { - Primary, - Secondary, - Destructive, - } - - impl button::StyleSheet for Button { - fn active(&self) -> button::Style { - button::Style { - background: Some(Background::Color(match self { - Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), - Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), - Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2), - })), - border_radius: 12, - shadow_offset: Vector::new(1.0, 1.0), - text_color: Color::WHITE, - ..button::Style::default() - } - } - } -} diff --git a/native/src/element.rs b/native/src/element.rs index 276f7614..4e7c7fc6 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -243,7 +243,7 @@ where } /// Computes the _layout_ hash of the [`Element`]. - /// + /// /// [`Element`]: struct.Element.html pub fn hash_layout(&self, state: &mut Hasher) { self.widget.hash_layout(state); diff --git a/native/src/lib.rs b/native/src/lib.rs index e4e7baee..4551a982 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -34,7 +34,7 @@ //! [`window::Renderer`]: window/trait.Renderer.html //! [`UserInterface`]: struct.UserInterface.html //! [renderer]: renderer/index.html -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] diff --git a/native/src/widget.rs b/native/src/widget.rs index f9424b02..d97e836c 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -25,6 +25,7 @@ pub mod checkbox; pub mod column; pub mod container; pub mod image; +pub mod panes; pub mod progress_bar; pub mod radio; pub mod row; @@ -46,6 +47,8 @@ pub use container::Container; #[doc(no_inline)] pub use image::Image; #[doc(no_inline)] +pub use panes::Panes; +#[doc(no_inline)] pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; diff --git a/native/src/widget/panes.rs b/native/src/widget/panes.rs new file mode 100644 index 00000000..d69d251e --- /dev/null +++ b/native/src/widget/panes.rs @@ -0,0 +1,238 @@ +use crate::{ + layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Size, + Widget, +}; + +use std::collections::HashMap; + +#[allow(missing_debug_implementations)] +pub struct Panes<'a, Message, Renderer> { + state: &'a mut Internal, + elements: Vec>, + width: Length, + height: Length, +} + +impl<'a, Message, Renderer> Panes<'a, Message, Renderer> { + pub fn new( + state: &'a mut State, + view: impl Fn(Pane, &'a mut T) -> Element<'a, Message, Renderer>, + ) -> Self { + let elements = state + .panes + .iter_mut() + .map(|(pane, state)| view(*pane, state)) + .collect(); + + Self { + state: &mut state.internal, + elements, + width: Length::Fill, + height: Length::Fill, + } + } + + /// Sets the width of the [`Panes`]. + /// + /// [`Panes`]: struct.Column.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Panes`]. + /// + /// [`Panes`]: struct.Column.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } +} + +impl<'a, Message, Renderer> Widget + for Panes<'a, Message, Renderer> +where + Renderer: self::Renderer + 'static, + Message: 'static, +{ + 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); + + let children = self + .elements + .iter() + .map(|element| element.layout(renderer, &limits)) + .collect(); + + layout::Node::with_children(size, children) + } + + fn draw( + &self, + renderer: &mut Renderer, + defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output { + renderer.draw(defaults, &self.elements, layout, cursor_position) + } + + fn hash_layout(&self, state: &mut Hasher) { + use std::hash::Hash; + + std::any::TypeId::of::>().hash(state); + self.width.hash(state); + self.height.hash(state); + self.state.layout.hash(state); + + for element in &self.elements { + element.hash_layout(state); + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Pane(usize); + +#[derive(Debug)] +pub struct State { + panes: HashMap, + internal: Internal, +} + +#[derive(Debug)] +struct Internal { + layout: Node, + last_pane: usize, + focused_pane: Option, +} + +impl State { + pub fn new(first_pane_state: T) -> (Self, Pane) { + let first_pane = Pane(0); + + let mut panes = HashMap::new(); + let _ = panes.insert(first_pane, first_pane_state); + + ( + State { + panes, + internal: Internal { + layout: Node::Pane(first_pane), + last_pane: 0, + focused_pane: None, + }, + }, + first_pane, + ) + } + + pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> { + self.panes.get_mut(pane) + } + + pub fn iter(&self) -> impl Iterator { + self.panes.iter().map(|(pane, state)| (*pane, state)) + } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.panes.iter_mut().map(|(pane, state)| (*pane, state)) + } + + pub fn focused_pane(&self) -> Option { + self.internal.focused_pane + } + + pub fn focus(&mut self, pane: Pane) { + self.internal.focused_pane = Some(pane); + } + + pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { + let new_pane = Pane(self.internal.last_pane.checked_add(1)?); + + // TODO + + Some(new_pane) + } + + pub fn split_horizontally( + &mut self, + pane: &Pane, + state: T, + ) -> Option { + let new_pane = Pane(self.internal.last_pane.checked_add(1)?); + + // TODO + + Some(new_pane) + } +} + +#[derive(Debug, Clone, Hash)] +enum Node { + Split { + kind: Split, + ratio: u32, + a: Box, + b: Box, + }, + Pane(Pane), +} + +#[derive(Debug, Clone, Copy, Hash)] +enum Split { + Horizontal, + Vertical, +} + +/// The renderer of some [`Panes`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use [`Panes`] in your user interface. +/// +/// [`Panes`]: struct.Panes.html +/// [renderer]: ../../renderer/index.html +pub trait Renderer: crate::Renderer + Sized { + /// Draws some [`Panes`]. + /// + /// It receives: + /// - the children of the [`Column`] + /// - the [`Layout`] of the [`Column`] and its children + /// - the cursor position + /// + /// [`Column`]: struct.Row.html + /// [`Layout`]: ../layout/struct.Layout.html + fn draw( + &mut self, + defaults: &Self::Defaults, + content: &[Element<'_, Message, Self>], + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output; +} + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Renderer: self::Renderer + 'static, + Message: 'static, +{ + fn from( + panes: Panes<'a, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { + Element::new(panes) + } +} diff --git a/src/widget.rs b/src/widget.rs index 7d3a1cef..de3301fa 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -30,7 +30,7 @@ mod platform { pub use iced_winit::svg::{Handle, Svg}; } - pub use iced_winit::Text; + pub use iced_winit::{panes, Panes, Text}; #[doc(no_inline)] pub use { diff --git a/wgpu/src/renderer/widget.rs b/wgpu/src/renderer/widget.rs index 84f908e7..9a46552e 100644 --- a/wgpu/src/renderer/widget.rs +++ b/wgpu/src/renderer/widget.rs @@ -2,6 +2,7 @@ mod button; mod checkbox; mod column; mod container; +mod panes; mod progress_bar; mod radio; mod row; diff --git a/wgpu/src/renderer/widget/panes.rs b/wgpu/src/renderer/widget/panes.rs new file mode 100644 index 00000000..67f92f52 --- /dev/null +++ b/wgpu/src/renderer/widget/panes.rs @@ -0,0 +1,34 @@ +use crate::{Primitive, Renderer}; +use iced_native::{panes, Element, Layout, MouseCursor, Point}; + +impl panes::Renderer for Renderer { + fn draw( + &mut self, + defaults: &Self::Defaults, + content: &[Element<'_, Message, Self>], + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let mut mouse_cursor = MouseCursor::OutOfBounds; + + ( + Primitive::Group { + primitives: content + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + let (primitive, new_mouse_cursor) = + child.draw(self, defaults, layout, cursor_position); + + if new_mouse_cursor > mouse_cursor { + mouse_cursor = new_mouse_cursor; + } + + primitive + }) + .collect(), + }, + mouse_cursor, + ) + } +} -- cgit From b6926d9ab4ae0c45049c3a8c19616939cbe9db95 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 4 Mar 2020 21:56:59 +0100 Subject: Improve `Debug` implementation of `cache::State` --- wgpu/src/widget/canvas/layer/cache.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/wgpu/src/widget/canvas/layer/cache.rs b/wgpu/src/widget/canvas/layer/cache.rs index 3071cce0..8f265142 100644 --- a/wgpu/src/widget/canvas/layer/cache.rs +++ b/wgpu/src/widget/canvas/layer/cache.rs @@ -21,7 +21,6 @@ pub struct Cache { state: RefCell, } -#[derive(Debug)] enum State { Empty, Filled { @@ -99,3 +98,17 @@ where mesh } } + +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 { mesh, bounds } => f + .debug_struct("Filled") + .field("vertices", &mesh.vertices.len()) + .field("indices", &mesh.indices.len()) + .field("bounds", bounds) + .finish(), + } + } +} -- cgit From d7f32d47ba352616328f72323cad18351b326ae8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 4 Mar 2020 22:01:57 +0100 Subject: Compute `panes` regions and focus on click --- examples/panes/src/main.rs | 5 +- native/src/widget/panes.rs | 198 +++++++++++++++++++++++++++++++++++--- wgpu/src/renderer/widget/panes.rs | 6 +- 3 files changed, 190 insertions(+), 19 deletions(-) diff --git a/examples/panes/src/main.rs b/examples/panes/src/main.rs index c1bf991a..65db2b40 100644 --- a/examples/panes/src/main.rs +++ b/examples/panes/src/main.rs @@ -12,6 +12,7 @@ pub fn main() { }) } +#[derive(Debug)] struct Launcher { panes: panes::State, } @@ -36,8 +37,6 @@ impl Application for Launcher { let (clock, _) = Clock::new(); let (panes, _) = panes::State::new(Example::Clock(clock)); - dbg!(&panes); - (Self { panes }, Command::none()) } @@ -61,6 +60,8 @@ impl Application for Launcher { } } + dbg!(self); + Command::none() } diff --git a/native/src/widget/panes.rs b/native/src/widget/panes.rs index d69d251e..69b54b47 100644 --- a/native/src/widget/panes.rs +++ b/native/src/widget/panes.rs @@ -1,6 +1,7 @@ use crate::{ - layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Size, - Widget, + input::{mouse, ButtonState}, + layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, + Rectangle, Size, Widget, }; use std::collections::HashMap; @@ -8,7 +9,7 @@ use std::collections::HashMap; #[allow(missing_debug_implementations)] pub struct Panes<'a, Message, Renderer> { state: &'a mut Internal, - elements: Vec>, + elements: Vec<(Pane, Element<'a, Message, Renderer>)>, width: Length, height: Length, } @@ -21,7 +22,7 @@ impl<'a, Message, Renderer> Panes<'a, Message, Renderer> { let elements = state .panes .iter_mut() - .map(|(pane, state)| view(*pane, state)) + .map(|(pane, state)| (*pane, view(*pane, state))) .collect(); Self { @@ -71,15 +72,67 @@ where let limits = limits.width(self.width).height(self.height); let size = limits.resolve(Size::ZERO); + let regions = self.state.layout.regions(size); + let children = self .elements .iter() - .map(|element| element.layout(renderer, &limits)) + .filter_map(|(pane, element)| { + let region = regions.get(pane)?; + let size = Size::new(region.width, region.height); + + let mut node = + element.layout(renderer, &layout::Limits::new(size, size)); + + node.move_to(Point::new(region.x, region.y)); + + Some(node) + }) .collect(); layout::Node::with_children(size, children) } + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + renderer: &Renderer, + clipboard: Option<&dyn Clipboard>, + ) { + match event { + Event::Mouse(mouse::Event::Input { + button: mouse::Button::Left, + state: ButtonState::Pressed, + }) => { + let mut clicked_region = + self.elements.iter().zip(layout.children()).filter( + |(_, layout)| layout.bounds().contains(cursor_position), + ); + + if let Some(((pane, _), _)) = clicked_region.next() { + self.state.focused_pane = Some(*pane); + } + } + _ => {} + } + + self.elements.iter_mut().zip(layout.children()).for_each( + |((_, pane), layout)| { + pane.widget.on_event( + event.clone(), + layout, + cursor_position, + messages, + renderer, + clipboard, + ) + }, + ); + } + fn draw( &self, renderer: &mut Renderer, @@ -98,7 +151,7 @@ where self.height.hash(state); self.state.layout.hash(state); - for element in &self.elements { + for (_, element) in &self.elements { element.hash_layout(state); } } @@ -161,11 +214,7 @@ impl State { } pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { - let new_pane = Pane(self.internal.last_pane.checked_add(1)?); - - // TODO - - Some(new_pane) + self.split(Split::Vertical, pane, state) } pub fn split_horizontally( @@ -173,9 +222,22 @@ impl State { pane: &Pane, state: T, ) -> Option { - let new_pane = Pane(self.internal.last_pane.checked_add(1)?); + self.split(Split::Horizontal, pane, state) + } + + fn split(&mut self, kind: Split, pane: &Pane, state: T) -> Option { + let node = self.internal.layout.find(pane)?; + + let new_pane = { + self.internal.last_pane = self.internal.last_pane.checked_add(1)?; + + Pane(self.internal.last_pane) + }; + + node.split(kind, new_pane); - // TODO + let _ = self.panes.insert(new_pane, state); + self.internal.focused_pane = Some(new_pane); Some(new_pane) } @@ -192,12 +254,120 @@ enum Node { Pane(Pane), } +impl Node { + pub fn find(&mut self, pane: &Pane) -> Option<&mut Node> { + match self { + Node::Split { a, b, .. } => { + if let Some(node) = a.find(pane) { + Some(node) + } else { + b.find(pane) + } + } + Node::Pane(p) => { + if p == pane { + Some(self) + } else { + None + } + } + } + } + + pub fn split(&mut self, kind: Split, new_pane: Pane) { + *self = Node::Split { + kind, + ratio: 500_000, + a: Box::new(self.clone()), + b: Box::new(Node::Pane(new_pane)), + }; + } + + pub fn regions(&self, size: Size) -> HashMap { + let mut regions = HashMap::new(); + + self.compute_regions( + &Rectangle { + x: 0.0, + y: 0.0, + width: size.width, + height: size.height, + }, + &mut regions, + ); + + regions + } + + fn compute_regions( + &self, + current: &Rectangle, + regions: &mut HashMap, + ) { + match self { + Node::Split { kind, ratio, a, b } => { + let ratio = *ratio as f32 / 1_000_000.0; + let (region_a, region_b) = kind.apply(current, ratio); + + a.compute_regions(®ion_a, regions); + b.compute_regions(®ion_b, regions); + } + Node::Pane(pane) => { + let _ = regions.insert(*pane, *current); + } + } + } +} + #[derive(Debug, Clone, Copy, Hash)] enum Split { Horizontal, Vertical, } +impl Split { + pub fn apply( + &self, + rectangle: &Rectangle, + ratio: f32, + ) -> (Rectangle, Rectangle) { + match self { + Split::Horizontal => { + let width_left = rectangle.width * ratio; + let width_right = rectangle.width - width_left; + + ( + Rectangle { + width: width_left, + ..*rectangle + }, + Rectangle { + x: rectangle.x + width_left, + width: width_right, + ..*rectangle + }, + ) + } + Split::Vertical => { + let height_top = rectangle.height * ratio; + let height_bottom = rectangle.height - height_top; + + ( + Rectangle { + height: height_top, + ..*rectangle + }, + Rectangle { + x: rectangle.x + height_top, + height: height_bottom, + ..*rectangle + }, + ) + } + } + } +} + /// The renderer of some [`Panes`]. /// /// Your [renderer] will need to implement this trait before being @@ -218,7 +388,7 @@ pub trait Renderer: crate::Renderer + Sized { fn draw( &mut self, defaults: &Self::Defaults, - content: &[Element<'_, Message, Self>], + content: &[(Pane, Element<'_, Message, Self>)], layout: Layout<'_>, cursor_position: Point, ) -> Self::Output; diff --git a/wgpu/src/renderer/widget/panes.rs b/wgpu/src/renderer/widget/panes.rs index 67f92f52..74e58895 100644 --- a/wgpu/src/renderer/widget/panes.rs +++ b/wgpu/src/renderer/widget/panes.rs @@ -5,7 +5,7 @@ impl panes::Renderer for Renderer { fn draw( &mut self, defaults: &Self::Defaults, - content: &[Element<'_, Message, Self>], + content: &[(panes::Pane, Element<'_, Message, Self>)], layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { @@ -16,9 +16,9 @@ impl panes::Renderer for Renderer { primitives: content .iter() .zip(layout.children()) - .map(|(child, layout)| { + .map(|((_, pane), layout)| { let (primitive, new_mouse_cursor) = - child.draw(self, defaults, layout, cursor_position); + pane.draw(self, defaults, layout, cursor_position); if new_mouse_cursor > mouse_cursor { mouse_cursor = new_mouse_cursor; -- cgit From 58adfcd5145d571739eda8cc655aa2f95814e541 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 4 Mar 2020 22:31:37 +0100 Subject: Fix `Split::apply` on vertical splits --- native/src/widget/panes.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/native/src/widget/panes.rs b/native/src/widget/panes.rs index 69b54b47..22fa2b5a 100644 --- a/native/src/widget/panes.rs +++ b/native/src/widget/panes.rs @@ -160,6 +160,12 @@ where #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Pane(usize); +impl Pane { + pub fn index(&self) -> usize { + self.0 + } +} + #[derive(Debug)] pub struct State { panes: HashMap, @@ -225,7 +231,12 @@ impl State { self.split(Split::Horizontal, pane, state) } - fn split(&mut self, kind: Split, pane: &Pane, state: T) -> Option { + pub fn split( + &mut self, + kind: Split, + pane: &Pane, + state: T, + ) -> Option { let node = self.internal.layout.find(pane)?; let new_pane = { @@ -320,13 +331,13 @@ impl Node { } #[derive(Debug, Clone, Copy, Hash)] -enum Split { +pub enum Split { Horizontal, Vertical, } impl Split { - pub fn apply( + fn apply( &self, rectangle: &Rectangle, ratio: f32, @@ -358,7 +369,7 @@ impl Split { ..*rectangle }, Rectangle { - x: rectangle.x + height_top, + y: rectangle.y + height_top, height: height_bottom, ..*rectangle }, -- cgit From cc310f71cc11dca8fb0d144181b75a68ed3eb82a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 4 Mar 2020 22:31:59 +0100 Subject: Add split hotkeys to `panes` example --- examples/panes/Cargo.toml | 1 + examples/panes/src/main.rs | 97 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 87 insertions(+), 11 deletions(-) diff --git a/examples/panes/Cargo.toml b/examples/panes/Cargo.toml index 174d2cde..dc94cc2c 100644 --- a/examples/panes/Cargo.toml +++ b/examples/panes/Cargo.toml @@ -7,5 +7,6 @@ publish = false [dependencies] iced = { path = "../..", features = ["async-std"] } +iced_native = { path = "../../native" } clock = { path = "../clock" } stopwatch = { path = "../stopwatch" } diff --git a/examples/panes/src/main.rs b/examples/panes/src/main.rs index 65db2b40..50b21fc5 100644 --- a/examples/panes/src/main.rs +++ b/examples/panes/src/main.rs @@ -1,6 +1,7 @@ use iced::{ panes, Application, Command, Element, Panes, Settings, Subscription, }; +use iced_native::input::keyboard; use clock::{self, Clock}; use stopwatch::{self, Stopwatch}; @@ -27,6 +28,7 @@ enum Example { enum Message { Clock(panes::Pane, clock::Message), Stopwatch(panes::Pane, stopwatch::Message), + Split(panes::Split), } impl Application for Launcher { @@ -58,6 +60,21 @@ impl Application for Launcher { let _ = stopwatch.update(message); } } + Message::Split(kind) => { + if let Some(pane) = self.panes.focused_pane() { + let state = if pane.index() % 2 == 0 { + let (stopwatch, _) = Stopwatch::new(); + + Example::Stopwatch(stopwatch) + } else { + let (clock, _) = Clock::new(); + + Example::Clock(clock) + }; + + self.panes.split(kind, &pane, state); + } + } } dbg!(self); @@ -66,17 +83,26 @@ impl Application for Launcher { } fn subscription(&self) -> Subscription { - Subscription::batch(self.panes.iter().map(|(pane, example)| { - match example { - Example::Clock(clock) => clock - .subscription() - .map(move |message| Message::Clock(pane, message)), - - Example::Stopwatch(stopwatch) => stopwatch - .subscription() - .map(move |message| Message::Stopwatch(pane, message)), - } - })) + let panes_subscriptions = + Subscription::batch(self.panes.iter().map(|(pane, example)| { + match example { + Example::Clock(clock) => clock + .subscription() + .map(move |message| Message::Clock(pane, message)), + + Example::Stopwatch(stopwatch) => stopwatch + .subscription() + .map(move |message| Message::Stopwatch(pane, message)), + } + })); + + Subscription::batch(vec![ + events::key_released(keyboard::KeyCode::H) + .map(|_| Message::Split(panes::Split::Horizontal)), + events::key_released(keyboard::KeyCode::V) + .map(|_| Message::Split(panes::Split::Vertical)), + panes_subscriptions, + ]) } fn view(&mut self) -> Element { @@ -94,3 +120,52 @@ impl Application for Launcher { .into() } } + +mod events { + use iced_native::{ + futures::{ + self, + stream::{BoxStream, StreamExt}, + }, + input::{keyboard, ButtonState}, + subscription, Event, Hasher, Subscription, + }; + + pub fn key_released(key_code: keyboard::KeyCode) -> Subscription<()> { + Subscription::from_recipe(KeyReleased { key_code }) + } + + struct KeyReleased { + key_code: keyboard::KeyCode, + } + + impl subscription::Recipe for KeyReleased { + type Output = (); + + fn hash(&self, state: &mut Hasher) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); + self.key_code.hash(state); + } + + fn stream( + self: Box, + events: subscription::EventStream, + ) -> BoxStream<'static, Self::Output> { + events + .filter(move |event| match event { + Event::Keyboard(keyboard::Event::Input { + key_code, + state: ButtonState::Released, + .. + }) if *key_code == self.key_code => { + futures::future::ready(true) + } + _ => futures::future::ready(false), + }) + .map(|_| ()) + .boxed() + } + } +} -- cgit From 3062c190bbcc82f30eaabc086a4e5014e320c8b6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 4 Mar 2020 22:32:23 +0100 Subject: Fix offsets in `triangle` pipeline Yes, again... --- wgpu/src/triangle.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 0a118bd2..85ed4bd5 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -330,12 +330,17 @@ impl Pipeline { &[(std::mem::size_of::() * i) as u64], ); - render_pass - .set_index_buffer(&self.index_buffer.raw, index_offset); + render_pass.set_index_buffer( + &self.index_buffer.raw, + index_offset * std::mem::size_of::() as u64, + ); render_pass.set_vertex_buffers( 0, - &[(&self.vertex_buffer.raw, vertex_offset)], + &[( + &self.vertex_buffer.raw, + vertex_offset * std::mem::size_of::() as u64, + )], ); render_pass.draw_indexed(0..indices as u32, 0, 0..1); -- cgit From a6531c840b97b1d30af5153c01fda69d09f43a08 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 5 Mar 2020 02:08:53 +0100 Subject: Implement `Subscription::with` --- examples/panes/src/main.rs | 13 +++++---- futures/src/subscription.rs | 70 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/examples/panes/src/main.rs b/examples/panes/src/main.rs index 50b21fc5..b34ce205 100644 --- a/examples/panes/src/main.rs +++ b/examples/panes/src/main.rs @@ -77,8 +77,6 @@ impl Application for Launcher { } } - dbg!(self); - Command::none() } @@ -88,11 +86,14 @@ impl Application for Launcher { match example { Example::Clock(clock) => clock .subscription() - .map(move |message| Message::Clock(pane, message)), + .with(pane) + .map(|(pane, message)| Message::Clock(pane, message)), - Example::Stopwatch(stopwatch) => stopwatch - .subscription() - .map(move |message| Message::Stopwatch(pane, message)), + Example::Stopwatch(stopwatch) => { + stopwatch.subscription().with(pane).map( + |(pane, message)| Message::Stopwatch(pane, message), + ) + } } })); diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index b68444cd..8eccb7be 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -72,6 +72,34 @@ where self.recipes } + /// Adds a value to the [`Subscription`] context. + /// + /// The value will be part of the identity of a [`Subscription`]. + /// + /// This is necessary if you want to use multiple instances of the same + /// [`Subscription`] to produce different kinds of messages based on some + /// external data. + /// + /// [`Subscription`]: struct.Subscription.html + pub fn with(mut self, value: T) -> Subscription + where + H: 'static, + E: 'static, + O: 'static, + T: std::hash::Hash + Clone + Send + Sync + 'static, + { + Subscription { + recipes: self + .recipes + .drain(..) + .map(|recipe| { + Box::new(With::new(recipe, value.clone())) + as Box> + }) + .collect(), + } + } + /// Transforms the [`Subscription`] output with the given function. /// /// [`Subscription`]: struct.Subscription.html @@ -187,3 +215,45 @@ where .boxed() } } + +struct With { + recipe: Box>, + value: B, +} + +impl With { + fn new(recipe: Box>, value: B) -> Self { + With { recipe, value } + } +} + +impl Recipe for With +where + A: 'static, + B: 'static + std::hash::Hash + Clone + Send + Sync, + H: std::hash::Hasher, +{ + type Output = (B, A); + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); + self.value.hash(state); + self.recipe.hash(state); + } + + fn stream( + self: Box, + input: BoxStream<'static, E>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::StreamExt; + + let value = self.value; + + self.recipe + .stream(input) + .map(move |element| (value.clone(), element)) + .boxed() + } +} -- cgit From 15fad17f373c0aeb023a879f5e38440fdd944eca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 5 Mar 2020 03:12:45 +0100 Subject: Implement `panes::State::close` --- examples/panes/src/main.rs | 7 +++++++ native/src/widget/panes.rs | 50 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/examples/panes/src/main.rs b/examples/panes/src/main.rs index b34ce205..34206a2c 100644 --- a/examples/panes/src/main.rs +++ b/examples/panes/src/main.rs @@ -29,6 +29,7 @@ enum Message { Clock(panes::Pane, clock::Message), Stopwatch(panes::Pane, stopwatch::Message), Split(panes::Split), + Close, } impl Application for Launcher { @@ -75,6 +76,11 @@ impl Application for Launcher { self.panes.split(kind, &pane, state); } } + Message::Close => { + if let Some(pane) = self.panes.focused_pane() { + self.panes.close(&pane); + } + } } Command::none() @@ -102,6 +108,7 @@ impl Application for Launcher { .map(|_| Message::Split(panes::Split::Horizontal)), events::key_released(keyboard::KeyCode::V) .map(|_| Message::Split(panes::Split::Vertical)), + events::key_released(keyboard::KeyCode::Q).map(|_| Message::Close), panes_subscriptions, ]) } diff --git a/native/src/widget/panes.rs b/native/src/widget/panes.rs index 22fa2b5a..2ffb2226 100644 --- a/native/src/widget/panes.rs +++ b/native/src/widget/panes.rs @@ -199,6 +199,10 @@ impl State { ) } + pub fn len(&self) -> usize { + self.panes.len() + } + pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> { self.panes.get_mut(pane) } @@ -252,6 +256,15 @@ impl State { Some(new_pane) } + + pub fn close(&mut self, pane: &Pane) -> Option { + if let Some(sibling) = self.internal.layout.remove(pane) { + self.internal.focused_pane = Some(sibling); + self.panes.remove(pane) + } else { + None + } + } } #[derive(Debug, Clone, Hash)] @@ -266,7 +279,7 @@ enum Node { } impl Node { - pub fn find(&mut self, pane: &Pane) -> Option<&mut Node> { + fn find(&mut self, pane: &Pane) -> Option<&mut Node> { match self { Node::Split { a, b, .. } => { if let Some(node) = a.find(pane) { @@ -285,7 +298,7 @@ impl Node { } } - pub fn split(&mut self, kind: Split, new_pane: Pane) { + fn split(&mut self, kind: Split, new_pane: Pane) { *self = Node::Split { kind, ratio: 500_000, @@ -294,6 +307,23 @@ impl Node { }; } + fn remove(&mut self, pane: &Pane) -> Option { + match self { + Node::Split { a, b, .. } => { + if a.pane() == Some(*pane) { + *self = *b.clone(); + Some(self.first_pane()) + } else if b.pane() == Some(*pane) { + *self = *a.clone(); + Some(self.first_pane()) + } else { + a.remove(pane).or_else(|| b.remove(pane)) + } + } + Node::Pane(_) => None, + } + } + pub fn regions(&self, size: Size) -> HashMap { let mut regions = HashMap::new(); @@ -310,6 +340,20 @@ impl Node { regions } + fn pane(&self) -> Option { + match self { + Node::Split { .. } => None, + Node::Pane(pane) => Some(*pane), + } + } + + fn first_pane(&self) -> Pane { + match self { + Node::Split { a, .. } => a.first_pane(), + Node::Pane(pane) => *pane, + } + } + fn compute_regions( &self, current: &Rectangle, @@ -330,7 +374,7 @@ impl Node { } } -#[derive(Debug, Clone, Copy, Hash)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum Split { Horizontal, Vertical, -- cgit From f81827c151eba868ab17f35d21a654d48125d0bf Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 6 Mar 2020 03:30:48 +0100 Subject: Remove counterintuitive constant functions --- native/src/layout/limits.rs | 4 ++-- native/src/layout/node.rs | 4 ++-- native/src/widget/column.rs | 16 ++++++++-------- native/src/widget/image.rs | 4 ++-- native/src/widget/row.rs | 16 ++++++++-------- native/src/widget/slider.rs | 4 ++-- native/src/widget/text_input.rs | 11 ++++------- web/src/widget/button.rs | 4 ++-- web/src/widget/column.rs | 16 ++++++++-------- web/src/widget/row.rs | 16 ++++++++-------- web/src/widget/scrollable.rs | 4 ++-- web/src/widget/slider.rs | 2 +- web/src/widget/text_input.rs | 4 ++-- wgpu/src/renderer.rs | 2 +- 14 files changed, 52 insertions(+), 55 deletions(-) diff --git a/native/src/layout/limits.rs b/native/src/layout/limits.rs index 5f456871..740417d4 100644 --- a/native/src/layout/limits.rs +++ b/native/src/layout/limits.rs @@ -32,7 +32,7 @@ impl Limits { /// /// [`Limits`]: struct.Limits.html /// [`Size`]: ../struct.Size.html - pub const fn min(&self) -> Size { + pub fn min(&self) -> Size { self.min } @@ -40,7 +40,7 @@ impl Limits { /// /// [`Limits`]: struct.Limits.html /// [`Size`]: ../struct.Size.html - pub const fn max(&self) -> Size { + pub fn max(&self) -> Size { self.max } diff --git a/native/src/layout/node.rs b/native/src/layout/node.rs index 3b63914e..acfd33bd 100644 --- a/native/src/layout/node.rs +++ b/native/src/layout/node.rs @@ -36,14 +36,14 @@ impl Node { /// /// [`Node`]: struct.Node.html /// [`Size`]: ../struct.Size.html - pub const fn size(&self) -> Size { + pub fn size(&self) -> Size { Size::new(self.bounds.width, self.bounds.height) } /// Returns the bounds of the [`Node`]. /// /// [`Node`]: struct.Node.html - pub const fn bounds(&self) -> Rectangle { + pub fn bounds(&self) -> Rectangle { self.bounds } diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 104fdb94..cdcf25af 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -28,7 +28,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Creates an empty [`Column`]. /// /// [`Column`]: struct.Column.html - pub const fn new() -> Self { + pub fn new() -> Self { Column { spacing: 0, padding: 0, @@ -46,7 +46,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Custom margins per element do not exist in Iced. You should use this /// method instead! While less flexible, it helps you keep spacing between /// elements consistent. - pub const fn spacing(mut self, units: u16) -> Self { + pub fn spacing(mut self, units: u16) -> Self { self.spacing = units; self } @@ -54,7 +54,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Sets the padding of the [`Column`]. /// /// [`Column`]: struct.Column.html - pub const fn padding(mut self, units: u16) -> Self { + pub fn padding(mut self, units: u16) -> Self { self.padding = units; self } @@ -62,7 +62,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Sets the width of the [`Column`]. /// /// [`Column`]: struct.Column.html - pub const fn width(mut self, width: Length) -> Self { + pub fn width(mut self, width: Length) -> Self { self.width = width; self } @@ -70,7 +70,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Sets the height of the [`Column`]. /// /// [`Column`]: struct.Column.html - pub const fn height(mut self, height: Length) -> Self { + pub fn height(mut self, height: Length) -> Self { self.height = height; self } @@ -78,7 +78,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Sets the maximum width of the [`Column`]. /// /// [`Column`]: struct.Column.html - pub const fn max_width(mut self, max_width: u32) -> Self { + pub fn max_width(mut self, max_width: u32) -> Self { self.max_width = max_width; self } @@ -86,7 +86,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Sets the maximum height of the [`Column`] in pixels. /// /// [`Column`]: struct.Column.html - pub const fn max_height(mut self, max_height: u32) -> Self { + pub fn max_height(mut self, max_height: u32) -> Self { self.max_height = max_height; self } @@ -94,7 +94,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// Sets the horizontal alignment of the contents of the [`Column`] . /// /// [`Column`]: struct.Column.html - pub const fn align_items(mut self, align: Align) -> Self { + pub fn align_items(mut self, align: Align) -> Self { self.align_items = align; self } diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index 5cfe074f..4c588c9d 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -37,7 +37,7 @@ impl Image { /// Sets the width of the [`Image`] boundaries. /// /// [`Image`]: struct.Image.html - pub const fn width(mut self, width: Length) -> Self { + pub fn width(mut self, width: Length) -> Self { self.width = width; self } @@ -45,7 +45,7 @@ impl Image { /// Sets the height of the [`Image`] boundaries. /// /// [`Image`]: struct.Image.html - pub const fn height(mut self, height: Length) -> Self { + pub fn height(mut self, height: Length) -> Self { self.height = height; self } diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index e9b8654c..c854aff7 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -28,7 +28,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Creates an empty [`Row`]. /// /// [`Row`]: struct.Row.html - pub const fn new() -> Self { + pub fn new() -> Self { Row { spacing: 0, padding: 0, @@ -46,7 +46,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Custom margins per element do not exist in Iced. You should use this /// method instead! While less flexible, it helps you keep spacing between /// elements consistent. - pub const fn spacing(mut self, units: u16) -> Self { + pub fn spacing(mut self, units: u16) -> Self { self.spacing = units; self } @@ -54,7 +54,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Sets the padding of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub const fn padding(mut self, units: u16) -> Self { + pub fn padding(mut self, units: u16) -> Self { self.padding = units; self } @@ -62,7 +62,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Sets the width of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub const fn width(mut self, width: Length) -> Self { + pub fn width(mut self, width: Length) -> Self { self.width = width; self } @@ -70,7 +70,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Sets the height of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub const fn height(mut self, height: Length) -> Self { + pub fn height(mut self, height: Length) -> Self { self.height = height; self } @@ -78,7 +78,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Sets the maximum width of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub const fn max_width(mut self, max_width: u32) -> Self { + pub fn max_width(mut self, max_width: u32) -> Self { self.max_width = max_width; self } @@ -86,7 +86,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Sets the maximum height of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub const fn max_height(mut self, max_height: u32) -> Self { + pub fn max_height(mut self, max_height: u32) -> Self { self.max_height = max_height; self } @@ -94,7 +94,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// Sets the vertical alignment of the contents of the [`Row`] . /// /// [`Row`]: struct.Row.html - pub const fn align_items(mut self, align: Align) -> Self { + pub fn align_items(mut self, align: Align) -> Self { self.align_items = align; self } diff --git a/native/src/widget/slider.rs b/native/src/widget/slider.rs index a8915da1..f07ea7cd 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -95,8 +95,8 @@ impl State { /// Creates a new [`State`]. /// /// [`State`]: struct.State.html - pub const fn new() -> State { - State { is_dragging: false } + pub fn new() -> State { + State::default() } } diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 0246f0d5..f97ed424 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -326,17 +326,14 @@ impl State { /// Creates a new [`State`], representing an unfocused [`TextInput`]. /// /// [`State`]: struct.State.html - pub const fn new() -> Self { - Self { - is_focused: false, - cursor_position: 0, - } + pub fn new() -> Self { + Self::default() } /// Creates a new [`State`], representing a focused [`TextInput`]. /// /// [`State`]: struct.State.html - pub const fn focused() -> Self { + pub fn focused() -> Self { use std::usize; Self { @@ -348,7 +345,7 @@ impl State { /// Returns whether the [`TextInput`] is currently focused or not. /// /// [`TextInput`]: struct.TextInput.html - pub const fn is_focused(&self) -> bool { + pub fn is_focused(&self) -> bool { self.is_focused } diff --git a/web/src/widget/button.rs b/web/src/widget/button.rs index 13b47077..889c0ab1 100644 --- a/web/src/widget/button.rs +++ b/web/src/widget/button.rs @@ -113,8 +113,8 @@ impl State { /// Creates a new [`State`]. /// /// [`State`]: struct.State.html - pub const fn new() -> State { - State + pub fn new() -> State { + State::default() } } diff --git a/web/src/widget/column.rs b/web/src/widget/column.rs index 82b533dd..cc850f5f 100644 --- a/web/src/widget/column.rs +++ b/web/src/widget/column.rs @@ -24,7 +24,7 @@ impl<'a, Message> Column<'a, Message> { /// Creates an empty [`Column`]. /// /// [`Column`]: struct.Column.html - pub const fn new() -> Self { + pub fn new() -> Self { Column { spacing: 0, padding: 0, @@ -42,7 +42,7 @@ impl<'a, Message> Column<'a, Message> { /// Custom margins per element do not exist in Iced. You should use this /// method instead! While less flexible, it helps you keep spacing between /// elements consistent. - pub const fn spacing(mut self, units: u16) -> Self { + pub fn spacing(mut self, units: u16) -> Self { self.spacing = units; self } @@ -50,7 +50,7 @@ impl<'a, Message> Column<'a, Message> { /// Sets the padding of the [`Column`]. /// /// [`Column`]: struct.Column.html - pub const fn padding(mut self, units: u16) -> Self { + pub fn padding(mut self, units: u16) -> Self { self.padding = units; self } @@ -58,7 +58,7 @@ impl<'a, Message> Column<'a, Message> { /// Sets the width of the [`Column`]. /// /// [`Column`]: struct.Column.html - pub const fn width(mut self, width: Length) -> Self { + pub fn width(mut self, width: Length) -> Self { self.width = width; self } @@ -66,7 +66,7 @@ impl<'a, Message> Column<'a, Message> { /// Sets the height of the [`Column`]. /// /// [`Column`]: struct.Column.html - pub const fn height(mut self, height: Length) -> Self { + pub fn height(mut self, height: Length) -> Self { self.height = height; self } @@ -74,7 +74,7 @@ impl<'a, Message> Column<'a, Message> { /// Sets the maximum width of the [`Column`]. /// /// [`Column`]: struct.Column.html - pub const fn max_width(mut self, max_width: u32) -> Self { + pub fn max_width(mut self, max_width: u32) -> Self { self.max_width = max_width; self } @@ -82,7 +82,7 @@ impl<'a, Message> Column<'a, Message> { /// Sets the maximum height of the [`Column`] in pixels. /// /// [`Column`]: struct.Column.html - pub const fn max_height(mut self, max_height: u32) -> Self { + pub fn max_height(mut self, max_height: u32) -> Self { self.max_height = max_height; self } @@ -90,7 +90,7 @@ impl<'a, Message> Column<'a, Message> { /// Sets the horizontal alignment of the contents of the [`Column`] . /// /// [`Column`]: struct.Column.html - pub const fn align_items(mut self, align: Align) -> Self { + pub fn align_items(mut self, align: Align) -> Self { self.align_items = align; self } diff --git a/web/src/widget/row.rs b/web/src/widget/row.rs index 0b4cec54..e47478be 100644 --- a/web/src/widget/row.rs +++ b/web/src/widget/row.rs @@ -24,7 +24,7 @@ impl<'a, Message> Row<'a, Message> { /// Creates an empty [`Row`]. /// /// [`Row`]: struct.Row.html - pub const fn new() -> Self { + pub fn new() -> Self { Row { spacing: 0, padding: 0, @@ -42,7 +42,7 @@ impl<'a, Message> Row<'a, Message> { /// Custom margins per element do not exist in Iced. You should use this /// method instead! While less flexible, it helps you keep spacing between /// elements consistent. - pub const fn spacing(mut self, units: u16) -> Self { + pub fn spacing(mut self, units: u16) -> Self { self.spacing = units; self } @@ -50,7 +50,7 @@ impl<'a, Message> Row<'a, Message> { /// Sets the padding of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub const fn padding(mut self, units: u16) -> Self { + pub fn padding(mut self, units: u16) -> Self { self.padding = units; self } @@ -58,7 +58,7 @@ impl<'a, Message> Row<'a, Message> { /// Sets the width of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub const fn width(mut self, width: Length) -> Self { + pub fn width(mut self, width: Length) -> Self { self.width = width; self } @@ -66,7 +66,7 @@ impl<'a, Message> Row<'a, Message> { /// Sets the height of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub const fn height(mut self, height: Length) -> Self { + pub fn height(mut self, height: Length) -> Self { self.height = height; self } @@ -74,7 +74,7 @@ impl<'a, Message> Row<'a, Message> { /// Sets the maximum width of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub const fn max_width(mut self, max_width: u32) -> Self { + pub fn max_width(mut self, max_width: u32) -> Self { self.max_width = max_width; self } @@ -82,7 +82,7 @@ impl<'a, Message> Row<'a, Message> { /// Sets the maximum height of the [`Row`]. /// /// [`Row`]: struct.Row.html - pub const fn max_height(mut self, max_height: u32) -> Self { + pub fn max_height(mut self, max_height: u32) -> Self { self.max_height = max_height; self } @@ -90,7 +90,7 @@ impl<'a, Message> Row<'a, Message> { /// Sets the vertical alignment of the contents of the [`Row`] . /// /// [`Row`]: struct.Row.html - pub const fn align_items(mut self, align: Align) -> Self { + pub fn align_items(mut self, align: Align) -> Self { self.align_items = align; self } diff --git a/web/src/widget/scrollable.rs b/web/src/widget/scrollable.rs index 65766d74..710bb70a 100644 --- a/web/src/widget/scrollable.rs +++ b/web/src/widget/scrollable.rs @@ -151,7 +151,7 @@ impl State { /// Creates a new [`State`] with the scrollbar located at the top. /// /// [`State`]: struct.State.html - pub const fn new() -> Self { - State + pub fn new() -> Self { + State::default() } } diff --git a/web/src/widget/slider.rs b/web/src/widget/slider.rs index 55848084..5b203e07 100644 --- a/web/src/widget/slider.rs +++ b/web/src/widget/slider.rs @@ -147,7 +147,7 @@ impl State { /// Creates a new [`State`]. /// /// [`State`]: struct.State.html - pub const fn new() -> Self { + pub fn new() -> Self { Self } } diff --git a/web/src/widget/text_input.rs b/web/src/widget/text_input.rs index 04c3b287..d6357512 100644 --- a/web/src/widget/text_input.rs +++ b/web/src/widget/text_input.rs @@ -191,7 +191,7 @@ impl State { /// Creates a new [`State`], representing an unfocused [`TextInput`]. /// /// [`State`]: struct.State.html - pub const fn new() -> Self { - Self + pub fn new() -> Self { + Self::default() } } diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index da2c0d8c..f27a4b8a 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -35,7 +35,7 @@ struct Layer<'a> { } impl<'a> Layer<'a> { - pub const fn new(bounds: Rectangle, offset: Vector) -> Self { + pub fn new(bounds: Rectangle, offset: Vector) -> Self { Self { bounds, offset, -- cgit From 29219500b7144f31dbf50fcc64653f7d2ce806d0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 6 Mar 2020 03:58:38 +0100 Subject: Update `reqwest` in `pokedex` example --- examples/pokedex/Cargo.toml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/pokedex/Cargo.toml b/examples/pokedex/Cargo.toml index c1e3edb5..94320086 100644 --- a/examples/pokedex/Cargo.toml +++ b/examples/pokedex/Cargo.toml @@ -7,12 +7,16 @@ publish = false [dependencies] iced = { path = "../..", features = ["image", "debug", "tokio"] } -serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -rand = { version = "0.7", features = ["wasm-bindgen"] } + +[dependencies.serde] +version = "1.0" +features = ["derive"] [dependencies.reqwest] -version = "0.10" -git = "https://github.com/hecrj/reqwest.git" -branch = "feature/wasm-deserialize-json" +version = "0.10.2" features = ["json"] + +[dependencies.rand] +version = "0.7" +features = ["wasm-bindgen"] -- cgit From 1bb85556915bb00057ef2ee66a596592c292b15b Mon Sep 17 00:00:00 2001 From: Artur Sapek Date: Thu, 5 Mar 2020 22:05:05 -0700 Subject: implement text support in canvas widget --- wgpu/src/widget/canvas.rs | 7 ++--- wgpu/src/widget/canvas/frame.rs | 52 +++++++++++++++++++++++++++++------ wgpu/src/widget/canvas/layer.rs | 7 ++--- wgpu/src/widget/canvas/layer/cache.rs | 25 +++++++---------- wgpu/src/widget/canvas/text.rs | 20 ++++++++++++++ 5 files changed, 80 insertions(+), 31 deletions(-) create mode 100644 wgpu/src/widget/canvas/text.rs diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index 38c1ce62..7d0c5bf3 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -20,6 +20,7 @@ mod drawable; mod fill; mod frame; mod stroke; +mod text; pub use drawable::Drawable; pub use fill::Fill; @@ -27,6 +28,7 @@ pub use frame::Frame; pub use layer::Layer; pub use path::Path; pub use stroke::{LineCap, LineJoin, Stroke}; +pub use text::TextNode; /// A widget capable of drawing 2D graphics. /// @@ -121,10 +123,7 @@ impl<'a, Message> Widget for Canvas<'a> { primitives: self .layers .iter() - .map(|layer| Primitive::Mesh2D { - origin, - buffers: layer.draw(size), - }) + .map(|layer| layer.draw(origin, size)) .collect(), }, MouseCursor::Idle, diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index fa6d8c0a..cdb9d0e1 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -1,10 +1,12 @@ use iced_native::{Point, Size, Vector}; use crate::{ - canvas::{Fill, Path, Stroke}, - triangle, + canvas::{Fill, Path, Stroke, TextNode}, + triangle, Primitive, }; +use std::sync::Arc; + /// The frame of a [`Canvas`]. /// /// [`Canvas`]: struct.Canvas.html @@ -14,6 +16,7 @@ pub struct Frame { height: f32, buffers: lyon::tessellation::VertexBuffers, transforms: Transforms, + texts: Vec, } #[derive(Debug)] @@ -40,6 +43,7 @@ impl Frame { width, height, buffers: lyon::tessellation::VertexBuffers::new(), + texts: Vec::new(), transforms: Transforms { previous: Vec::new(), current: Transform { @@ -154,6 +158,14 @@ impl Frame { let _ = result.expect("Stroke path"); } + /// Draws the text of the given [`TextNode`] on the [`Frame`] + /// + /// [`TextNode`]: struct.TextNode.html + /// [`Frame`]: struct.Frame.html + pub fn text(&mut self, text: TextNode) { + self.texts.push(text); + } + /// Stores the current transform of the [`Frame`] and executes the given /// drawing operations, restoring the transform afterwards. /// @@ -209,14 +221,38 @@ impl Frame { self.transforms.current.is_identity = false; } - /// Produces the geometry that has been drawn on the [`Frame`]. + /// Produces the primitive representing everything drawn on the [`Frame`]. /// /// [`Frame`]: struct.Frame.html - pub fn into_mesh(self) -> triangle::Mesh2D { - triangle::Mesh2D { - vertices: self.buffers.vertices, - indices: self.buffers.indices, - } + pub fn into_primitive(self, origin: Point) -> Primitive { + let mut primitives: Vec = self + .texts + .into_iter() + .map(|mut t| { + t.bounds.x += origin.x; + t.bounds.y += origin.y; + + Primitive::Text { + content: t.content, + bounds: t.bounds, + color: t.color, + size: t.size, + font: t.font, + horizontal_alignment: t.horizontal_alignment, + vertical_alignment: t.vertical_alignment, + } + }) + .collect(); + + primitives.push(Primitive::Mesh2D { + origin, + buffers: Arc::new(triangle::Mesh2D { + vertices: self.buffers.vertices, + indices: self.buffers.indices, + }), + }); + + Primitive::Group { primitives } } } diff --git a/wgpu/src/widget/canvas/layer.rs b/wgpu/src/widget/canvas/layer.rs index 82d647bb..95e2d0ee 100644 --- a/wgpu/src/widget/canvas/layer.rs +++ b/wgpu/src/widget/canvas/layer.rs @@ -3,10 +3,9 @@ mod cache; pub use cache::Cache; -use crate::triangle; +use crate::Primitive; -use iced_native::Size; -use std::sync::Arc; +use iced_native::{Point, Size}; /// A layer that can be presented at a [`Canvas`]. /// @@ -21,5 +20,5 @@ pub trait Layer: std::fmt::Debug { /// /// [`Layer`]: trait.Layer.html /// [`Mesh2D`]: ../../../triangle/struct.Mesh2D.html - fn draw(&self, bounds: Size) -> Arc; + fn draw(&self, origin: Point, bounds: Size) -> Primitive; } diff --git a/wgpu/src/widget/canvas/layer/cache.rs b/wgpu/src/widget/canvas/layer/cache.rs index 3071cce0..f05028da 100644 --- a/wgpu/src/widget/canvas/layer/cache.rs +++ b/wgpu/src/widget/canvas/layer/cache.rs @@ -1,12 +1,10 @@ use crate::{ canvas::{Drawable, Frame, Layer}, - triangle, + Primitive, }; -use iced_native::Size; -use std::cell::RefCell; -use std::marker::PhantomData; -use std::sync::Arc; +use iced_native::{Point, Size}; +use std::{cell::RefCell, marker::PhantomData}; /// A simple cache that stores generated geometry to avoid recomputation. /// @@ -24,10 +22,7 @@ pub struct Cache { #[derive(Debug)] enum State { Empty, - Filled { - mesh: Arc, - bounds: Size, - }, + Filled { bounds: Size, primitive: Primitive }, } impl Cache @@ -75,27 +70,27 @@ impl<'a, T> Layer for Bind<'a, T> where T: Drawable + std::fmt::Debug, { - fn draw(&self, current_bounds: Size) -> Arc { + fn draw(&self, origin: Point, current_bounds: Size) -> Primitive { use std::ops::Deref; - if let State::Filled { mesh, bounds } = + if let State::Filled { bounds, primitive } = self.cache.state.borrow().deref() { if *bounds == current_bounds { - return mesh.clone(); + return primitive.clone(); } } let mut frame = Frame::new(current_bounds.width, current_bounds.height); self.input.draw(&mut frame); - let mesh = Arc::new(frame.into_mesh()); + let primitive = frame.into_primitive(origin); *self.cache.state.borrow_mut() = State::Filled { - mesh: mesh.clone(), bounds: current_bounds, + primitive: primitive.clone(), }; - mesh + primitive } } diff --git a/wgpu/src/widget/canvas/text.rs b/wgpu/src/widget/canvas/text.rs new file mode 100644 index 00000000..5f6abe58 --- /dev/null +++ b/wgpu/src/widget/canvas/text.rs @@ -0,0 +1,20 @@ +use iced_native::{Color, Font, HorizontalAlignment, Rectangle, VerticalAlignment}; + +/// A text node to be drawn to a canvas +#[derive(Debug, Clone)] +pub struct TextNode { + /// The contents of the text + pub content: String, + /// The bounds of the text + pub bounds: Rectangle, + /// The color of the text + pub color: Color, + /// The size of the text + pub size: f32, + /// The font of the text + pub font: Font, + /// The horizontal alignment of the text + pub horizontal_alignment: HorizontalAlignment, + /// The vertical alignment of the text + pub vertical_alignment: VerticalAlignment, +} -- cgit From f35c9f25f03976e058e892662454b1143fd172cd Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 7 Mar 2020 22:27:02 +0100 Subject: Rename `canvas::TextNode` to `canvas::Text` --- wgpu/src/widget/canvas.rs | 2 +- wgpu/src/widget/canvas/frame.rs | 10 +++++----- wgpu/src/widget/canvas/text.rs | 8 +++++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index 7d0c5bf3..9741ee9d 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -28,7 +28,7 @@ pub use frame::Frame; pub use layer::Layer; pub use path::Path; pub use stroke::{LineCap, LineJoin, Stroke}; -pub use text::TextNode; +pub use text::Text; /// A widget capable of drawing 2D graphics. /// diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index cdb9d0e1..44a8d9aa 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -1,7 +1,7 @@ use iced_native::{Point, Size, Vector}; use crate::{ - canvas::{Fill, Path, Stroke, TextNode}, + canvas::{Fill, Path, Stroke, Text}, triangle, Primitive, }; @@ -16,7 +16,7 @@ pub struct Frame { height: f32, buffers: lyon::tessellation::VertexBuffers, transforms: Transforms, - texts: Vec, + texts: Vec, } #[derive(Debug)] @@ -158,11 +158,11 @@ impl Frame { let _ = result.expect("Stroke path"); } - /// Draws the text of the given [`TextNode`] on the [`Frame`] + /// Draws the text of the given [`Text`] on the [`Frame`] /// - /// [`TextNode`]: struct.TextNode.html + /// [`Text`]: struct.Text.html /// [`Frame`]: struct.Frame.html - pub fn text(&mut self, text: TextNode) { + pub fn text(&mut self, text: Text) { self.texts.push(text); } diff --git a/wgpu/src/widget/canvas/text.rs b/wgpu/src/widget/canvas/text.rs index 5f6abe58..241f8503 100644 --- a/wgpu/src/widget/canvas/text.rs +++ b/wgpu/src/widget/canvas/text.rs @@ -1,8 +1,10 @@ -use iced_native::{Color, Font, HorizontalAlignment, Rectangle, VerticalAlignment}; +use iced_native::{ + Color, Font, HorizontalAlignment, Rectangle, VerticalAlignment, +}; -/// A text node to be drawn to a canvas +/// A bunch of text that can be drawn to a canvas #[derive(Debug, Clone)] -pub struct TextNode { +pub struct Text { /// The contents of the text pub content: String, /// The bounds of the text -- cgit From 37f0d97159d81dbd4801e287a06f4e243e483269 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 7 Mar 2020 22:28:57 +0100 Subject: Rename `Frame::text` to `Frame::fill_text` This keeps the API similar to the Web Canvas API. --- wgpu/src/widget/canvas/frame.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index 44a8d9aa..5b82c951 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -158,11 +158,13 @@ impl Frame { let _ = result.expect("Stroke path"); } - /// Draws the text of the given [`Text`] on the [`Frame`] + /// Draws the characters of the given [`Text`] on the [`Frame`], filling + /// them with the given color. /// /// [`Text`]: struct.Text.html /// [`Frame`]: struct.Frame.html - pub fn text(&mut self, text: Text) { + #[inline] + pub fn fill_text(&mut self, text: Text) { self.texts.push(text); } -- cgit From b74e7e7353d69ffb54cf0c0f0574ea7abf0f3a68 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 7 Mar 2020 23:45:54 +0100 Subject: Implement `Primitive::Cached` --- core/src/point.rs | 11 ++++ core/src/vector.rs | 11 ++++ examples/bezier_tool/src/main.rs | 5 +- examples/geometry/src/main.rs | 4 +- wgpu/src/primitive.rs | 13 +++- wgpu/src/renderer.rs | 112 +++++++++++++++++++--------------- wgpu/src/triangle.rs | 4 +- wgpu/src/widget/canvas.rs | 5 +- wgpu/src/widget/canvas/frame.rs | 59 +++++++++--------- wgpu/src/widget/canvas/layer.rs | 13 ++-- wgpu/src/widget/canvas/layer/cache.rs | 13 ++-- wgpu/src/widget/canvas/text.rs | 8 +-- 12 files changed, 152 insertions(+), 106 deletions(-) diff --git a/core/src/point.rs b/core/src/point.rs index b9a8149c..b55f5099 100644 --- a/core/src/point.rs +++ b/core/src/point.rs @@ -46,3 +46,14 @@ impl std::ops::Add for Point { } } } + +impl std::ops::Sub for Point { + type Output = Self; + + fn sub(self, vector: Vector) -> Self { + Self { + x: self.x - vector.x, + y: self.y - vector.y, + } + } +} diff --git a/core/src/vector.rs b/core/src/vector.rs index 4c1cbfab..a75053a0 100644 --- a/core/src/vector.rs +++ b/core/src/vector.rs @@ -32,6 +32,17 @@ where } } +impl std::ops::Sub for Vector +where + T: std::ops::Sub, +{ + type Output = Self; + + fn sub(self, b: Self) -> Self { + Self::new(self.x - b.x, self.y - b.y) + } +} + impl Default for Vector where T: Default, diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index 023eb0f7..c3fbf276 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -23,7 +23,6 @@ mod bezier { basic_shapes, BuffersBuilder, StrokeAttributes, StrokeOptions, StrokeTessellator, VertexBuffers, }; - use std::sync::Arc; pub struct Bezier<'a, Message> { state: &'a mut State, @@ -175,10 +174,10 @@ mod bezier { let mesh = Primitive::Mesh2D { origin: Point::new(bounds.x, bounds.y), - buffers: Arc::new(Mesh2D { + buffers: Mesh2D { vertices: buffer.vertices, indices: buffer.indices, - }), + }, }; ( diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index 795c6a71..13a687ab 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -87,7 +87,7 @@ mod rainbow { ( Primitive::Mesh2D { origin: Point::new(b.x, b.y), - buffers: std::sync::Arc::new(Mesh2D { + buffers: Mesh2D { vertices: vec![ Vertex2D { position: posn_center, @@ -136,7 +136,7 @@ mod rainbow { 0, 7, 8, // BL 0, 8, 1, // L ], - }), + }, }, MouseCursor::OutOfBounds, ) diff --git a/wgpu/src/primitive.rs b/wgpu/src/primitive.rs index 823b4b72..46d9e624 100644 --- a/wgpu/src/primitive.rs +++ b/wgpu/src/primitive.rs @@ -78,7 +78,18 @@ pub enum Primitive { origin: Point, /// The vertex and index buffers of the mesh - buffers: Arc, + buffers: triangle::Mesh2D, + }, + /// A cached 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, }, } diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 1da19b1a..c06af339 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -10,7 +10,6 @@ use iced_native::{ layout, Background, Color, Layout, MouseCursor, Point, Rectangle, Vector, Widget, }; -use std::sync::Arc; mod widget; @@ -29,9 +28,8 @@ pub struct Renderer { struct Layer<'a> { bounds: Rectangle, - offset: Vector, quads: Vec, - meshes: Vec<(Point, Arc)>, + meshes: Vec<(Point, &'a triangle::Mesh2D)>, text: Vec>, #[cfg(any(feature = "image", feature = "svg"))] @@ -39,10 +37,9 @@ struct Layer<'a> { } impl<'a> Layer<'a> { - pub fn new(bounds: Rectangle, offset: Vector) -> Self { + pub fn new(bounds: Rectangle) -> Self { Self { bounds, - offset, quads: Vec::new(), text: Vec::new(), meshes: Vec::new(), @@ -103,17 +100,14 @@ impl Renderer { let mut layers = Vec::new(); - layers.push(Layer::new( - Rectangle { - x: 0, - y: 0, - width: u32::from(width), - height: u32::from(height), - }, - Vector::new(0, 0), - )); - - self.draw_primitive(primitive, &mut layers); + layers.push(Layer::new(Rectangle { + x: 0, + y: 0, + width: u32::from(width), + height: u32::from(height), + })); + + self.draw_primitive(Vector::new(0.0, 0.0), primitive, &mut layers); self.draw_overlay(overlay, &mut layers); for layer in layers { @@ -137,17 +131,16 @@ impl Renderer { fn draw_primitive<'a>( &mut self, + translation: Vector, primitive: &'a Primitive, layers: &mut Vec>, ) { - let layer = layers.last_mut().unwrap(); - match primitive { Primitive::None => {} Primitive::Group { primitives } => { // TODO: Inspect a bit and regroup (?) for primitive in primitives { - self.draw_primitive(primitive, layers) + self.draw_primitive(translation, primitive, layers) } } Primitive::Text { @@ -179,12 +172,11 @@ impl Renderer { } }; + let layer = layers.last_mut().unwrap(); + layer.text.push(wgpu_glyph::Section { text: &content, - screen_position: ( - x - layer.offset.x as f32, - y - layer.offset.y as f32, - ), + screen_position: (x + translation.x, y + translation.y), bounds: (bounds.width, bounds.height), scale: wgpu_glyph::Scale { x: *size, y: *size }, color: color.into_linear(), @@ -222,11 +214,13 @@ impl Renderer { border_width, border_color, } => { - // TODO: Move some of this computations to the GPU (?) + let layer = layers.last_mut().unwrap(); + + // TODO: Move some of these computations to the GPU (?) layer.quads.push(Quad { position: [ - bounds.x - layer.offset.x as f32, - bounds.y - layer.offset.y as f32, + bounds.x + translation.x, + bounds.y + translation.y, ], scale: [bounds.width, bounds.height], color: match background { @@ -238,38 +232,59 @@ impl Renderer { }); } Primitive::Mesh2D { origin, buffers } => { - layer.meshes.push((*origin, buffers.clone())); + let layer = layers.last_mut().unwrap(); + + layer.meshes.push((*origin + translation, buffers)); } Primitive::Clip { bounds, offset, content, } => { + let layer = layers.last_mut().unwrap(); + let layer_bounds: Rectangle = layer.bounds.into(); let clip = Rectangle { - x: bounds.x - layer.offset.x as f32, - y: bounds.y - layer.offset.y as f32, + x: bounds.x + translation.x, + y: bounds.y + translation.y, ..*bounds }; // Only draw visible content if let Some(clip_bounds) = layer_bounds.intersection(&clip) { - let clip_layer = - Layer::new(clip_bounds.into(), layer.offset + *offset); - let new_layer = Layer::new(layer.bounds, layer.offset); + let clip_layer = Layer::new(clip_bounds.into()); + let new_layer = Layer::new(layer.bounds); layers.push(clip_layer); - self.draw_primitive(content, layers); + self.draw_primitive( + translation + - Vector::new(offset.x as f32, offset.y as f32), + content, + layers, + ); layers.push(new_layer); } } + Primitive::Cached { origin, cache } => { + self.draw_primitive( + translation + Vector::new(origin.x, origin.y), + &cache, + layers, + ); + } + #[cfg(feature = "image")] Primitive::Image { handle, bounds } => { + let layer = layers.last_mut().unwrap(); + layer.images.push(Image { handle: image::Handle::Raster(handle.clone()), - position: [bounds.x, bounds.y], + position: [ + bounds.x + translation.x, + bounds.y + translation.y, + ], size: [bounds.width, bounds.height], }); } @@ -278,9 +293,14 @@ impl Renderer { #[cfg(feature = "svg")] Primitive::Svg { handle, bounds } => { + let layer = layers.last_mut().unwrap(); + layer.images.push(Image { handle: image::Handle::Vector(handle.clone()), - position: [bounds.x, bounds.y], + position: [ + bounds.x + translation.x, + bounds.y + translation.y, + ], size: [bounds.width, bounds.height], }); } @@ -295,7 +315,7 @@ impl Renderer { layers: &mut Vec>, ) { let first = layers.first().unwrap(); - let mut overlay = Layer::new(first.bounds, Vector::new(0, 0)); + let mut overlay = Layer::new(first.bounds); let font_id = self.text_pipeline.overlay_font(); let scale = wgpu_glyph::Scale { x: 20.0, y: 20.0 }; @@ -337,12 +357,8 @@ impl Renderer { let bounds = layer.bounds * scale_factor; if layer.meshes.len() > 0 { - let translated = transformation - * Transformation::scale(scale_factor, scale_factor) - * Transformation::translate( - -(layer.offset.x as f32), - -(layer.offset.y as f32), - ); + let scaled = transformation + * Transformation::scale(scale_factor, scale_factor); self.triangle_pipeline.draw( device, @@ -350,7 +366,7 @@ impl Renderer { target, target_width, target_height, - translated, + scaled, &layer.meshes, bounds, ); @@ -371,18 +387,14 @@ impl Renderer { #[cfg(any(feature = "image", feature = "svg"))] { if layer.images.len() > 0 { - let translated_and_scaled = transformation - * Transformation::scale(scale_factor, scale_factor) - * Transformation::translate( - -(layer.offset.x as f32), - -(layer.offset.y as f32), - ); + let scaled = transformation + * Transformation::scale(scale_factor, scale_factor); self.image_pipeline.draw( device, encoder, &layer.images, - translated_and_scaled, + scaled, bounds, target, scale_factor, diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index 85ed4bd5..be61cc15 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -1,7 +1,7 @@ //! Draw meshes of triangles. use crate::{settings, Transformation}; use iced_native::{Point, Rectangle}; -use std::{mem, sync::Arc}; +use std::mem; mod msaa; @@ -194,7 +194,7 @@ impl Pipeline { target_width: u32, target_height: u32, transformation: Transformation, - meshes: &Vec<(Point, Arc)>, + meshes: &Vec<(Point, &Mesh2D)>, bounds: Rectangle, ) { // This looks a bit crazy, but we are just counting how many vertices diff --git a/wgpu/src/widget/canvas.rs b/wgpu/src/widget/canvas.rs index 9741ee9d..3a9605c9 100644 --- a/wgpu/src/widget/canvas.rs +++ b/wgpu/src/widget/canvas.rs @@ -123,7 +123,10 @@ impl<'a, Message> Widget for Canvas<'a> { primitives: self .layers .iter() - .map(|layer| layer.draw(origin, size)) + .map(|layer| Primitive::Cached { + origin, + cache: layer.draw(size), + }) .collect(), }, MouseCursor::Idle, diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index 5b82c951..0bf58320 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -1,12 +1,10 @@ -use iced_native::{Point, Size, Vector}; +use iced_native::{Point, Rectangle, Size, Vector}; use crate::{ canvas::{Fill, Path, Stroke, Text}, triangle, Primitive, }; -use std::sync::Arc; - /// The frame of a [`Canvas`]. /// /// [`Canvas`]: struct.Canvas.html @@ -15,8 +13,8 @@ pub struct Frame { width: f32, height: f32, buffers: lyon::tessellation::VertexBuffers, + primitives: Vec, transforms: Transforms, - texts: Vec, } #[derive(Debug)] @@ -43,7 +41,7 @@ impl Frame { width, height, buffers: lyon::tessellation::VertexBuffers::new(), - texts: Vec::new(), + primitives: Vec::new(), transforms: Transforms { previous: Vec::new(), current: Transform { @@ -165,7 +163,23 @@ impl Frame { /// [`Frame`]: struct.Frame.html #[inline] pub fn fill_text(&mut self, text: Text) { - self.texts.push(text); + use std::f32; + + // TODO: Use vectorial text instead of primitive + self.primitives.push(Primitive::Text { + content: text.content, + bounds: Rectangle { + x: text.position.x, + y: text.position.y, + width: f32::INFINITY, + height: f32::INFINITY, + }, + color: text.color, + size: text.size, + font: text.font, + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + }); } /// Stores the current transform of the [`Frame`] and executes the given @@ -226,35 +240,18 @@ impl Frame { /// Produces the primitive representing everything drawn on the [`Frame`]. /// /// [`Frame`]: struct.Frame.html - pub fn into_primitive(self, origin: Point) -> Primitive { - let mut primitives: Vec = self - .texts - .into_iter() - .map(|mut t| { - t.bounds.x += origin.x; - t.bounds.y += origin.y; - - Primitive::Text { - content: t.content, - bounds: t.bounds, - color: t.color, - size: t.size, - font: t.font, - horizontal_alignment: t.horizontal_alignment, - vertical_alignment: t.vertical_alignment, - } - }) - .collect(); - - primitives.push(Primitive::Mesh2D { - origin, - buffers: Arc::new(triangle::Mesh2D { + pub fn into_primitive(mut self) -> Primitive { + self.primitives.push(Primitive::Mesh2D { + origin: Point::ORIGIN, + buffers: triangle::Mesh2D { vertices: self.buffers.vertices, indices: self.buffers.indices, - }), + }, }); - Primitive::Group { primitives } + Primitive::Group { + primitives: self.primitives, + } } } diff --git a/wgpu/src/widget/canvas/layer.rs b/wgpu/src/widget/canvas/layer.rs index 95e2d0ee..a46b7fb1 100644 --- a/wgpu/src/widget/canvas/layer.rs +++ b/wgpu/src/widget/canvas/layer.rs @@ -4,21 +4,22 @@ mod cache; pub use cache::Cache; use crate::Primitive; +use iced_native::Size; -use iced_native::{Point, 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. + /// Draws the [`Layer`] in the given bounds and produces a [`Primitive`] as + /// a result. /// - /// The [`Layer`] may choose to store the produced [`Mesh2D`] locally and + /// 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 - /// [`Mesh2D`]: ../../../triangle/struct.Mesh2D.html - fn draw(&self, origin: Point, bounds: Size) -> Primitive; + /// [`Primitive`]: ../../../enum.Primitive.html + fn draw(&self, bounds: Size) -> Arc; } diff --git a/wgpu/src/widget/canvas/layer/cache.rs b/wgpu/src/widget/canvas/layer/cache.rs index f05028da..6b69f01e 100644 --- a/wgpu/src/widget/canvas/layer/cache.rs +++ b/wgpu/src/widget/canvas/layer/cache.rs @@ -3,8 +3,8 @@ use crate::{ Primitive, }; -use iced_native::{Point, Size}; -use std::{cell::RefCell, marker::PhantomData}; +use iced_native::Size; +use std::{cell::RefCell, marker::PhantomData, sync::Arc}; /// A simple cache that stores generated geometry to avoid recomputation. /// @@ -22,7 +22,10 @@ pub struct Cache { #[derive(Debug)] enum State { Empty, - Filled { bounds: Size, primitive: Primitive }, + Filled { + bounds: Size, + primitive: Arc, + }, } impl Cache @@ -70,7 +73,7 @@ impl<'a, T> Layer for Bind<'a, T> where T: Drawable + std::fmt::Debug, { - fn draw(&self, origin: Point, current_bounds: Size) -> Primitive { + fn draw(&self, current_bounds: Size) -> Arc { use std::ops::Deref; if let State::Filled { bounds, primitive } = @@ -84,7 +87,7 @@ where let mut frame = Frame::new(current_bounds.width, current_bounds.height); self.input.draw(&mut frame); - let primitive = frame.into_primitive(origin); + let primitive = Arc::new(frame.into_primitive()); *self.cache.state.borrow_mut() = State::Filled { bounds: current_bounds, diff --git a/wgpu/src/widget/canvas/text.rs b/wgpu/src/widget/canvas/text.rs index 241f8503..1f9bdc19 100644 --- a/wgpu/src/widget/canvas/text.rs +++ b/wgpu/src/widget/canvas/text.rs @@ -1,14 +1,12 @@ -use iced_native::{ - Color, Font, HorizontalAlignment, Rectangle, VerticalAlignment, -}; +use iced_native::{Color, Font, HorizontalAlignment, Point, VerticalAlignment}; /// A bunch of text that can be drawn to a canvas #[derive(Debug, Clone)] pub struct Text { /// The contents of the text pub content: String, - /// The bounds of the text - pub bounds: Rectangle, + /// The position where to begin drawing the text (top-left corner coordinates) + pub position: Point, /// The color of the text pub color: Color, /// The size of the text -- cgit From 38d967c414af2187b112c654082df7083e0ee7e5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 7 Mar 2020 23:47:55 +0100 Subject: Take a slice in `iced_wgpu::triangle` pipeline --- wgpu/src/triangle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index be61cc15..51a6f954 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -194,7 +194,7 @@ impl Pipeline { target_width: u32, target_height: u32, transformation: Transformation, - meshes: &Vec<(Point, &Mesh2D)>, + meshes: &[(Point, &Mesh2D)], bounds: Rectangle, ) { // This looks a bit crazy, but we are just counting how many vertices -- cgit From e9194cbf4a95ad743e16864f949716701d984a0d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 8 Mar 2020 00:06:04 +0100 Subject: Transform text position in `Frame::fill_text` Also add a warning explaining its current limitations. --- wgpu/src/widget/canvas/frame.rs | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/wgpu/src/widget/canvas/frame.rs b/wgpu/src/widget/canvas/frame.rs index 0bf58320..7d7ce06a 100644 --- a/wgpu/src/widget/canvas/frame.rs +++ b/wgpu/src/widget/canvas/frame.rs @@ -159,18 +159,38 @@ impl Frame { /// Draws the characters of the given [`Text`] on the [`Frame`], filling /// them with the given color. /// + /// __Warning:__ Text currently does not work well with rotations and scale + /// transforms! The position will be correctly transformed, but the + /// resulting glyphs will not be rotated or scaled properly. + /// + /// Additionally, all text will be rendered on top of all the layers of + /// a [`Canvas`]. Therefore, it is currently only meant to be used for + /// overlays, which is the most common use case. + /// + /// Support for vectorial text is planned, and should address all these + /// limitations. + /// /// [`Text`]: struct.Text.html /// [`Frame`]: struct.Frame.html - #[inline] pub fn fill_text(&mut self, text: Text) { use std::f32; + let position = if self.transforms.current.is_identity { + text.position + } else { + let transformed = self.transforms.current.raw.transform_point( + lyon::math::Point::new(text.position.x, text.position.y), + ); + + Point::new(transformed.x, transformed.y) + }; + // TODO: Use vectorial text instead of primitive self.primitives.push(Primitive::Text { content: text.content, bounds: Rectangle { - x: text.position.x, - y: text.position.y, + x: position.x, + y: position.y, width: f32::INFINITY, height: f32::INFINITY, }, -- cgit From b4f970ee7317297615848cd12f422c0cd2889f60 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 8 Mar 2020 00:06:48 +0100 Subject: Implement `Default` for `canvas::Text` --- wgpu/src/widget/canvas/text.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/wgpu/src/widget/canvas/text.rs b/wgpu/src/widget/canvas/text.rs index 1f9bdc19..d1cf1a0f 100644 --- a/wgpu/src/widget/canvas/text.rs +++ b/wgpu/src/widget/canvas/text.rs @@ -18,3 +18,17 @@ pub struct Text { /// The vertical alignment of the text pub vertical_alignment: VerticalAlignment, } + +impl Default for Text { + fn default() -> Text { + Text { + content: String::new(), + position: Point::ORIGIN, + color: Color::BLACK, + size: 16.0, + font: Font::Default, + horizontal_alignment: HorizontalAlignment::Left, + vertical_alignment: VerticalAlignment::Top, + } + } +} -- cgit From aac7ad3e14aaa9faf7d52fe9b91bc64cb037d576 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 9 Mar 2020 22:35:25 +0100 Subject: Update `winit` to `0.22` --- examples/integration/src/main.rs | 12 +++++------- winit/Cargo.toml | 2 +- winit/src/application.rs | 9 +++------ 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 4be913c1..2cb89ffc 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -10,7 +10,7 @@ use iced_wgpu::{ use iced_winit::{winit, Cache, Clipboard, MouseCursor, Size, UserInterface}; use winit::{ - event::{DeviceEvent, Event, ModifiersState, WindowEvent}, + event::{Event, ModifiersState, WindowEvent}, event_loop::{ControlFlow, EventLoop}, }; @@ -66,14 +66,11 @@ pub fn main() { *control_flow = ControlFlow::Wait; match event { - Event::DeviceEvent { - event: DeviceEvent::ModifiersChanged(new_modifiers), - .. - } => { - modifiers = new_modifiers; - } Event::WindowEvent { event, .. } => { match event { + WindowEvent::ModifiersChanged(new_modifiers) => { + modifiers = new_modifiers; + } WindowEvent::Resized(new_size) => { logical_size = new_size.to_logical(window.scale_factor()); @@ -82,6 +79,7 @@ pub fn main() { WindowEvent::CloseRequested => { *control_flow = ControlFlow::Exit; } + _ => {} } diff --git a/winit/Cargo.toml b/winit/Cargo.toml index 63df1d63..a70ecb9d 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -14,7 +14,7 @@ categories = ["gui"] debug = [] [dependencies] -winit = "0.21" +winit = "0.22" log = "0.4" [dependencies.iced_native] diff --git a/winit/src/application.rs b/winit/src/application.rs index f5aa799c..891b8f12 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -347,6 +347,9 @@ pub trait Application: Sized { WindowEvent::CloseRequested => { *control_flow = ControlFlow::Exit; } + WindowEvent::ModifiersChanged(new_modifiers) => { + modifiers = new_modifiers; + } #[cfg(target_os = "macos")] WindowEvent::KeyboardInput { input: @@ -382,12 +385,6 @@ pub trait Application: Sized { events.push(event); } } - event::Event::DeviceEvent { - event: event::DeviceEvent::ModifiersChanged(new_modifiers), - .. - } => { - modifiers = new_modifiers; - } _ => { *control_flow = ControlFlow::Wait; } -- cgit From 6151c528241d0a6ece88e6e664df1b50f8174ecb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 10 Mar 2020 02:57:13 +0100 Subject: Rename `Panes` widget to `PaneGrid` --- Cargo.toml | 2 +- examples/pane_grid/Cargo.toml | 12 + examples/pane_grid/README.md | 18 ++ examples/pane_grid/src/main.rs | 179 +++++++++++++ examples/panes/Cargo.toml | 12 - examples/panes/README.md | 18 -- examples/panes/src/main.rs | 179 ------------- native/src/widget.rs | 4 +- native/src/widget/pane_grid.rs | 463 ++++++++++++++++++++++++++++++++++ native/src/widget/panes.rs | 463 ---------------------------------- src/widget.rs | 2 +- wgpu/src/renderer/widget.rs | 2 +- wgpu/src/renderer/widget/pane_grid.rs | 34 +++ wgpu/src/renderer/widget/panes.rs | 34 --- 14 files changed, 711 insertions(+), 711 deletions(-) create mode 100644 examples/pane_grid/Cargo.toml create mode 100644 examples/pane_grid/README.md create mode 100644 examples/pane_grid/src/main.rs delete mode 100644 examples/panes/Cargo.toml delete mode 100644 examples/panes/README.md delete mode 100644 examples/panes/src/main.rs create mode 100644 native/src/widget/pane_grid.rs delete mode 100644 native/src/widget/panes.rs create mode 100644 wgpu/src/renderer/widget/pane_grid.rs delete mode 100644 wgpu/src/renderer/widget/panes.rs diff --git a/Cargo.toml b/Cargo.toml index 3b79abdb..37b20ec4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ members = [ "examples/events", "examples/geometry", "examples/integration", - "examples/panes", + "examples/pane_grid", "examples/pokedex", "examples/progress_bar", "examples/solar_system", diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml new file mode 100644 index 00000000..6d8573bd --- /dev/null +++ b/examples/pane_grid/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "pane_grid" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../..", features = ["async-std"] } +iced_native = { path = "../../native" } +clock = { path = "../clock" } +stopwatch = { path = "../stopwatch" } diff --git a/examples/pane_grid/README.md b/examples/pane_grid/README.md new file mode 100644 index 00000000..4d9fc5b9 --- /dev/null +++ b/examples/pane_grid/README.md @@ -0,0 +1,18 @@ +## Counter + +The classic counter example explained in the [`README`](../../README.md). + +The __[`main`]__ file contains all the code of the example. + + + +You can run it with `cargo run`: +``` +cargo run --package counter +``` + +[`main`]: src/main.rs diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs new file mode 100644 index 00000000..b103afc8 --- /dev/null +++ b/examples/pane_grid/src/main.rs @@ -0,0 +1,179 @@ +use iced::{ + pane_grid, Application, Command, Element, PaneGrid, Settings, Subscription, +}; +use iced_native::input::keyboard; + +use clock::{self, Clock}; +use stopwatch::{self, Stopwatch}; + +pub fn main() { + Launcher::run(Settings { + antialiasing: true, + ..Settings::default() + }) +} + +#[derive(Debug)] +struct Launcher { + panes: pane_grid::State, +} + +#[derive(Debug)] +enum Example { + Clock(Clock), + Stopwatch(Stopwatch), +} + +#[derive(Debug, Clone)] +enum Message { + Clock(pane_grid::Pane, clock::Message), + Stopwatch(pane_grid::Pane, stopwatch::Message), + Split(pane_grid::Split), + Close, +} + +impl Application for Launcher { + type Executor = iced::executor::Default; + type Message = Message; + + fn new() -> (Self, Command) { + let (clock, _) = Clock::new(); + let (panes, _) = pane_grid::State::new(Example::Clock(clock)); + + (Self { panes }, Command::none()) + } + + fn title(&self) -> String { + String::from("Panes - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Clock(pane, message) => { + if let Some(Example::Clock(clock)) = self.panes.get_mut(&pane) { + let _ = clock.update(message); + } + } + Message::Stopwatch(pane, message) => { + if let Some(Example::Stopwatch(stopwatch)) = + self.panes.get_mut(&pane) + { + let _ = stopwatch.update(message); + } + } + Message::Split(kind) => { + if let Some(pane) = self.panes.focused_pane() { + let state = if pane.index() % 2 == 0 { + let (stopwatch, _) = Stopwatch::new(); + + Example::Stopwatch(stopwatch) + } else { + let (clock, _) = Clock::new(); + + Example::Clock(clock) + }; + + self.panes.split(kind, &pane, state); + } + } + Message::Close => { + if let Some(pane) = self.panes.focused_pane() { + self.panes.close(&pane); + } + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription { + let panes_subscriptions = + Subscription::batch(self.panes.iter().map(|(pane, example)| { + match example { + Example::Clock(clock) => clock + .subscription() + .with(pane) + .map(|(pane, message)| Message::Clock(pane, message)), + + Example::Stopwatch(stopwatch) => { + stopwatch.subscription().with(pane).map( + |(pane, message)| Message::Stopwatch(pane, message), + ) + } + } + })); + + Subscription::batch(vec![ + events::key_released(keyboard::KeyCode::H) + .map(|_| Message::Split(pane_grid::Split::Horizontal)), + events::key_released(keyboard::KeyCode::V) + .map(|_| Message::Split(pane_grid::Split::Vertical)), + events::key_released(keyboard::KeyCode::Q).map(|_| Message::Close), + panes_subscriptions, + ]) + } + + fn view(&mut self) -> Element { + let Self { panes } = self; + + PaneGrid::new(panes, |pane, example| match example { + Example::Clock(clock) => clock + .view() + .map(move |message| Message::Clock(pane, message)), + + Example::Stopwatch(stopwatch) => stopwatch + .view() + .map(move |message| Message::Stopwatch(pane, message)), + }) + .into() + } +} + +mod events { + use iced_native::{ + futures::{ + self, + stream::{BoxStream, StreamExt}, + }, + input::{keyboard, ButtonState}, + subscription, Event, Hasher, Subscription, + }; + + pub fn key_released(key_code: keyboard::KeyCode) -> Subscription<()> { + Subscription::from_recipe(KeyReleased { key_code }) + } + + struct KeyReleased { + key_code: keyboard::KeyCode, + } + + impl subscription::Recipe for KeyReleased { + type Output = (); + + fn hash(&self, state: &mut Hasher) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); + self.key_code.hash(state); + } + + fn stream( + self: Box, + events: subscription::EventStream, + ) -> BoxStream<'static, Self::Output> { + events + .filter(move |event| match event { + Event::Keyboard(keyboard::Event::Input { + key_code, + state: ButtonState::Released, + .. + }) if *key_code == self.key_code => { + futures::future::ready(true) + } + _ => futures::future::ready(false), + }) + .map(|_| ()) + .boxed() + } + } +} diff --git a/examples/panes/Cargo.toml b/examples/panes/Cargo.toml deleted file mode 100644 index dc94cc2c..00000000 --- a/examples/panes/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "panes" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2018" -publish = false - -[dependencies] -iced = { path = "../..", features = ["async-std"] } -iced_native = { path = "../../native" } -clock = { path = "../clock" } -stopwatch = { path = "../stopwatch" } diff --git a/examples/panes/README.md b/examples/panes/README.md deleted file mode 100644 index 4d9fc5b9..00000000 --- a/examples/panes/README.md +++ /dev/null @@ -1,18 +0,0 @@ -## Counter - -The classic counter example explained in the [`README`](../../README.md). - -The __[`main`]__ file contains all the code of the example. - - - -You can run it with `cargo run`: -``` -cargo run --package counter -``` - -[`main`]: src/main.rs diff --git a/examples/panes/src/main.rs b/examples/panes/src/main.rs deleted file mode 100644 index 34206a2c..00000000 --- a/examples/panes/src/main.rs +++ /dev/null @@ -1,179 +0,0 @@ -use iced::{ - panes, Application, Command, Element, Panes, Settings, Subscription, -}; -use iced_native::input::keyboard; - -use clock::{self, Clock}; -use stopwatch::{self, Stopwatch}; - -pub fn main() { - Launcher::run(Settings { - antialiasing: true, - ..Settings::default() - }) -} - -#[derive(Debug)] -struct Launcher { - panes: panes::State, -} - -#[derive(Debug)] -enum Example { - Clock(Clock), - Stopwatch(Stopwatch), -} - -#[derive(Debug, Clone)] -enum Message { - Clock(panes::Pane, clock::Message), - Stopwatch(panes::Pane, stopwatch::Message), - Split(panes::Split), - Close, -} - -impl Application for Launcher { - type Executor = iced::executor::Default; - type Message = Message; - - fn new() -> (Self, Command) { - let (clock, _) = Clock::new(); - let (panes, _) = panes::State::new(Example::Clock(clock)); - - (Self { panes }, Command::none()) - } - - fn title(&self) -> String { - String::from("Panes - Iced") - } - - fn update(&mut self, message: Message) -> Command { - match message { - Message::Clock(pane, message) => { - if let Some(Example::Clock(clock)) = self.panes.get_mut(&pane) { - let _ = clock.update(message); - } - } - Message::Stopwatch(pane, message) => { - if let Some(Example::Stopwatch(stopwatch)) = - self.panes.get_mut(&pane) - { - let _ = stopwatch.update(message); - } - } - Message::Split(kind) => { - if let Some(pane) = self.panes.focused_pane() { - let state = if pane.index() % 2 == 0 { - let (stopwatch, _) = Stopwatch::new(); - - Example::Stopwatch(stopwatch) - } else { - let (clock, _) = Clock::new(); - - Example::Clock(clock) - }; - - self.panes.split(kind, &pane, state); - } - } - Message::Close => { - if let Some(pane) = self.panes.focused_pane() { - self.panes.close(&pane); - } - } - } - - Command::none() - } - - fn subscription(&self) -> Subscription { - let panes_subscriptions = - Subscription::batch(self.panes.iter().map(|(pane, example)| { - match example { - Example::Clock(clock) => clock - .subscription() - .with(pane) - .map(|(pane, message)| Message::Clock(pane, message)), - - Example::Stopwatch(stopwatch) => { - stopwatch.subscription().with(pane).map( - |(pane, message)| Message::Stopwatch(pane, message), - ) - } - } - })); - - Subscription::batch(vec![ - events::key_released(keyboard::KeyCode::H) - .map(|_| Message::Split(panes::Split::Horizontal)), - events::key_released(keyboard::KeyCode::V) - .map(|_| Message::Split(panes::Split::Vertical)), - events::key_released(keyboard::KeyCode::Q).map(|_| Message::Close), - panes_subscriptions, - ]) - } - - fn view(&mut self) -> Element { - let Self { panes } = self; - - Panes::new(panes, |pane, example| match example { - Example::Clock(clock) => clock - .view() - .map(move |message| Message::Clock(pane, message)), - - Example::Stopwatch(stopwatch) => stopwatch - .view() - .map(move |message| Message::Stopwatch(pane, message)), - }) - .into() - } -} - -mod events { - use iced_native::{ - futures::{ - self, - stream::{BoxStream, StreamExt}, - }, - input::{keyboard, ButtonState}, - subscription, Event, Hasher, Subscription, - }; - - pub fn key_released(key_code: keyboard::KeyCode) -> Subscription<()> { - Subscription::from_recipe(KeyReleased { key_code }) - } - - struct KeyReleased { - key_code: keyboard::KeyCode, - } - - impl subscription::Recipe for KeyReleased { - type Output = (); - - fn hash(&self, state: &mut Hasher) { - use std::hash::Hash; - - std::any::TypeId::of::().hash(state); - self.key_code.hash(state); - } - - fn stream( - self: Box, - events: subscription::EventStream, - ) -> BoxStream<'static, Self::Output> { - events - .filter(move |event| match event { - Event::Keyboard(keyboard::Event::Input { - key_code, - state: ButtonState::Released, - .. - }) if *key_code == self.key_code => { - futures::future::ready(true) - } - _ => futures::future::ready(false), - }) - .map(|_| ()) - .boxed() - } - } -} diff --git a/native/src/widget.rs b/native/src/widget.rs index d97e836c..88f819c9 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -25,7 +25,7 @@ pub mod checkbox; pub mod column; pub mod container; pub mod image; -pub mod panes; +pub mod pane_grid; pub mod progress_bar; pub mod radio; pub mod row; @@ -47,7 +47,7 @@ pub use container::Container; #[doc(no_inline)] pub use image::Image; #[doc(no_inline)] -pub use panes::Panes; +pub use pane_grid::PaneGrid; #[doc(no_inline)] pub use progress_bar::ProgressBar; #[doc(no_inline)] diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs new file mode 100644 index 00000000..a03b7fcf --- /dev/null +++ b/native/src/widget/pane_grid.rs @@ -0,0 +1,463 @@ +use crate::{ + input::{mouse, ButtonState}, + layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, + Rectangle, Size, Widget, +}; + +use std::collections::HashMap; + +#[allow(missing_debug_implementations)] +pub struct PaneGrid<'a, Message, Renderer> { + state: &'a mut Internal, + elements: Vec<(Pane, Element<'a, Message, Renderer>)>, + width: Length, + height: Length, +} + +impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { + pub fn new( + state: &'a mut State, + view: impl Fn(Pane, &'a mut T) -> Element<'a, Message, Renderer>, + ) -> Self { + let elements = state + .panes + .iter_mut() + .map(|(pane, state)| (*pane, view(*pane, state))) + .collect(); + + Self { + state: &mut state.internal, + elements, + width: Length::Fill, + height: Length::Fill, + } + } + + /// Sets the width of the [`Panes`]. + /// + /// [`Panes`]: struct.Column.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Panes`]. + /// + /// [`Panes`]: struct.Column.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } +} + +impl<'a, Message, Renderer> Widget + for PaneGrid<'a, Message, Renderer> +where + Renderer: self::Renderer + 'static, + Message: 'static, +{ + 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); + + let regions = self.state.layout.regions(size); + + let children = self + .elements + .iter() + .filter_map(|(pane, element)| { + let region = regions.get(pane)?; + let size = Size::new(region.width, region.height); + + let mut node = + element.layout(renderer, &layout::Limits::new(size, size)); + + node.move_to(Point::new(region.x, region.y)); + + Some(node) + }) + .collect(); + + layout::Node::with_children(size, children) + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + renderer: &Renderer, + clipboard: Option<&dyn Clipboard>, + ) { + match event { + Event::Mouse(mouse::Event::Input { + button: mouse::Button::Left, + state: ButtonState::Pressed, + }) => { + let mut clicked_region = + self.elements.iter().zip(layout.children()).filter( + |(_, layout)| layout.bounds().contains(cursor_position), + ); + + if let Some(((pane, _), _)) = clicked_region.next() { + self.state.focused_pane = Some(*pane); + } + } + _ => {} + } + + self.elements.iter_mut().zip(layout.children()).for_each( + |((_, pane), layout)| { + pane.widget.on_event( + event.clone(), + layout, + cursor_position, + messages, + renderer, + clipboard, + ) + }, + ); + } + + fn draw( + &self, + renderer: &mut Renderer, + defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output { + renderer.draw(defaults, &self.elements, layout, cursor_position) + } + + fn hash_layout(&self, state: &mut Hasher) { + use std::hash::Hash; + + std::any::TypeId::of::>().hash(state); + self.width.hash(state); + self.height.hash(state); + self.state.layout.hash(state); + + for (_, element) in &self.elements { + element.hash_layout(state); + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Pane(usize); + +impl Pane { + pub fn index(&self) -> usize { + self.0 + } +} + +#[derive(Debug)] +pub struct State { + panes: HashMap, + internal: Internal, +} + +#[derive(Debug)] +struct Internal { + layout: Node, + last_pane: usize, + focused_pane: Option, +} + +impl State { + pub fn new(first_pane_state: T) -> (Self, Pane) { + let first_pane = Pane(0); + + let mut panes = HashMap::new(); + let _ = panes.insert(first_pane, first_pane_state); + + ( + State { + panes, + internal: Internal { + layout: Node::Pane(first_pane), + last_pane: 0, + focused_pane: None, + }, + }, + first_pane, + ) + } + + pub fn len(&self) -> usize { + self.panes.len() + } + + pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> { + self.panes.get_mut(pane) + } + + pub fn iter(&self) -> impl Iterator { + self.panes.iter().map(|(pane, state)| (*pane, state)) + } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.panes.iter_mut().map(|(pane, state)| (*pane, state)) + } + + pub fn focused_pane(&self) -> Option { + self.internal.focused_pane + } + + pub fn focus(&mut self, pane: Pane) { + self.internal.focused_pane = Some(pane); + } + + pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { + self.split(Split::Vertical, pane, state) + } + + pub fn split_horizontally( + &mut self, + pane: &Pane, + state: T, + ) -> Option { + self.split(Split::Horizontal, pane, state) + } + + pub fn split( + &mut self, + kind: Split, + pane: &Pane, + state: T, + ) -> Option { + let node = self.internal.layout.find(pane)?; + + let new_pane = { + self.internal.last_pane = self.internal.last_pane.checked_add(1)?; + + Pane(self.internal.last_pane) + }; + + node.split(kind, new_pane); + + let _ = self.panes.insert(new_pane, state); + self.internal.focused_pane = Some(new_pane); + + Some(new_pane) + } + + pub fn close(&mut self, pane: &Pane) -> Option { + if let Some(sibling) = self.internal.layout.remove(pane) { + self.internal.focused_pane = Some(sibling); + self.panes.remove(pane) + } else { + None + } + } +} + +#[derive(Debug, Clone, Hash)] +enum Node { + Split { + kind: Split, + ratio: u32, + a: Box, + b: Box, + }, + Pane(Pane), +} + +impl Node { + fn find(&mut self, pane: &Pane) -> Option<&mut Node> { + match self { + Node::Split { a, b, .. } => { + if let Some(node) = a.find(pane) { + Some(node) + } else { + b.find(pane) + } + } + Node::Pane(p) => { + if p == pane { + Some(self) + } else { + None + } + } + } + } + + fn split(&mut self, kind: Split, new_pane: Pane) { + *self = Node::Split { + kind, + ratio: 500_000, + a: Box::new(self.clone()), + b: Box::new(Node::Pane(new_pane)), + }; + } + + fn remove(&mut self, pane: &Pane) -> Option { + match self { + Node::Split { a, b, .. } => { + if a.pane() == Some(*pane) { + *self = *b.clone(); + Some(self.first_pane()) + } else if b.pane() == Some(*pane) { + *self = *a.clone(); + Some(self.first_pane()) + } else { + a.remove(pane).or_else(|| b.remove(pane)) + } + } + Node::Pane(_) => None, + } + } + + pub fn regions(&self, size: Size) -> HashMap { + let mut regions = HashMap::new(); + + self.compute_regions( + &Rectangle { + x: 0.0, + y: 0.0, + width: size.width, + height: size.height, + }, + &mut regions, + ); + + regions + } + + fn pane(&self) -> Option { + match self { + Node::Split { .. } => None, + Node::Pane(pane) => Some(*pane), + } + } + + fn first_pane(&self) -> Pane { + match self { + Node::Split { a, .. } => a.first_pane(), + Node::Pane(pane) => *pane, + } + } + + fn compute_regions( + &self, + current: &Rectangle, + regions: &mut HashMap, + ) { + match self { + Node::Split { kind, ratio, a, b } => { + let ratio = *ratio as f32 / 1_000_000.0; + let (region_a, region_b) = kind.apply(current, ratio); + + a.compute_regions(®ion_a, regions); + b.compute_regions(®ion_b, regions); + } + Node::Pane(pane) => { + let _ = regions.insert(*pane, *current); + } + } + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq)] +pub enum Split { + Horizontal, + Vertical, +} + +impl Split { + fn apply( + &self, + rectangle: &Rectangle, + ratio: f32, + ) -> (Rectangle, Rectangle) { + match self { + Split::Horizontal => { + let width_left = rectangle.width * ratio; + let width_right = rectangle.width - width_left; + + ( + Rectangle { + width: width_left, + ..*rectangle + }, + Rectangle { + x: rectangle.x + width_left, + width: width_right, + ..*rectangle + }, + ) + } + Split::Vertical => { + let height_top = rectangle.height * ratio; + let height_bottom = rectangle.height - height_top; + + ( + Rectangle { + height: height_top, + ..*rectangle + }, + Rectangle { + y: rectangle.y + height_top, + height: height_bottom, + ..*rectangle + }, + ) + } + } + } +} + +/// The renderer of some [`Panes`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use [`Panes`] in your user interface. +/// +/// [`Panes`]: struct.Panes.html +/// [renderer]: ../../renderer/index.html +pub trait Renderer: crate::Renderer + Sized { + /// Draws some [`Panes`]. + /// + /// It receives: + /// - the children of the [`Column`] + /// - the [`Layout`] of the [`Column`] and its children + /// - the cursor position + /// + /// [`Column`]: struct.Row.html + /// [`Layout`]: ../layout/struct.Layout.html + fn draw( + &mut self, + defaults: &Self::Defaults, + content: &[(Pane, Element<'_, Message, Self>)], + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output; +} + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Renderer: self::Renderer + 'static, + Message: 'static, +{ + fn from( + panes: PaneGrid<'a, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { + Element::new(panes) + } +} diff --git a/native/src/widget/panes.rs b/native/src/widget/panes.rs deleted file mode 100644 index 2ffb2226..00000000 --- a/native/src/widget/panes.rs +++ /dev/null @@ -1,463 +0,0 @@ -use crate::{ - input::{mouse, ButtonState}, - layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Rectangle, Size, Widget, -}; - -use std::collections::HashMap; - -#[allow(missing_debug_implementations)] -pub struct Panes<'a, Message, Renderer> { - state: &'a mut Internal, - elements: Vec<(Pane, Element<'a, Message, Renderer>)>, - width: Length, - height: Length, -} - -impl<'a, Message, Renderer> Panes<'a, Message, Renderer> { - pub fn new( - state: &'a mut State, - view: impl Fn(Pane, &'a mut T) -> Element<'a, Message, Renderer>, - ) -> Self { - let elements = state - .panes - .iter_mut() - .map(|(pane, state)| (*pane, view(*pane, state))) - .collect(); - - Self { - state: &mut state.internal, - elements, - width: Length::Fill, - height: Length::Fill, - } - } - - /// Sets the width of the [`Panes`]. - /// - /// [`Panes`]: struct.Column.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the height of the [`Panes`]. - /// - /// [`Panes`]: struct.Column.html - pub fn height(mut self, height: Length) -> Self { - self.height = height; - self - } -} - -impl<'a, Message, Renderer> Widget - for Panes<'a, Message, Renderer> -where - Renderer: self::Renderer + 'static, - Message: 'static, -{ - 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); - - let regions = self.state.layout.regions(size); - - let children = self - .elements - .iter() - .filter_map(|(pane, element)| { - let region = regions.get(pane)?; - let size = Size::new(region.width, region.height); - - let mut node = - element.layout(renderer, &layout::Limits::new(size, size)); - - node.move_to(Point::new(region.x, region.y)); - - Some(node) - }) - .collect(); - - layout::Node::with_children(size, children) - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - messages: &mut Vec, - renderer: &Renderer, - clipboard: Option<&dyn Clipboard>, - ) { - match event { - Event::Mouse(mouse::Event::Input { - button: mouse::Button::Left, - state: ButtonState::Pressed, - }) => { - let mut clicked_region = - self.elements.iter().zip(layout.children()).filter( - |(_, layout)| layout.bounds().contains(cursor_position), - ); - - if let Some(((pane, _), _)) = clicked_region.next() { - self.state.focused_pane = Some(*pane); - } - } - _ => {} - } - - self.elements.iter_mut().zip(layout.children()).for_each( - |((_, pane), layout)| { - pane.widget.on_event( - event.clone(), - layout, - cursor_position, - messages, - renderer, - clipboard, - ) - }, - ); - } - - fn draw( - &self, - renderer: &mut Renderer, - defaults: &Renderer::Defaults, - layout: Layout<'_>, - cursor_position: Point, - ) -> Renderer::Output { - renderer.draw(defaults, &self.elements, layout, cursor_position) - } - - fn hash_layout(&self, state: &mut Hasher) { - use std::hash::Hash; - - std::any::TypeId::of::>().hash(state); - self.width.hash(state); - self.height.hash(state); - self.state.layout.hash(state); - - for (_, element) in &self.elements { - element.hash_layout(state); - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Pane(usize); - -impl Pane { - pub fn index(&self) -> usize { - self.0 - } -} - -#[derive(Debug)] -pub struct State { - panes: HashMap, - internal: Internal, -} - -#[derive(Debug)] -struct Internal { - layout: Node, - last_pane: usize, - focused_pane: Option, -} - -impl State { - pub fn new(first_pane_state: T) -> (Self, Pane) { - let first_pane = Pane(0); - - let mut panes = HashMap::new(); - let _ = panes.insert(first_pane, first_pane_state); - - ( - State { - panes, - internal: Internal { - layout: Node::Pane(first_pane), - last_pane: 0, - focused_pane: None, - }, - }, - first_pane, - ) - } - - pub fn len(&self) -> usize { - self.panes.len() - } - - pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> { - self.panes.get_mut(pane) - } - - pub fn iter(&self) -> impl Iterator { - self.panes.iter().map(|(pane, state)| (*pane, state)) - } - - pub fn iter_mut(&mut self) -> impl Iterator { - self.panes.iter_mut().map(|(pane, state)| (*pane, state)) - } - - pub fn focused_pane(&self) -> Option { - self.internal.focused_pane - } - - pub fn focus(&mut self, pane: Pane) { - self.internal.focused_pane = Some(pane); - } - - pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { - self.split(Split::Vertical, pane, state) - } - - pub fn split_horizontally( - &mut self, - pane: &Pane, - state: T, - ) -> Option { - self.split(Split::Horizontal, pane, state) - } - - pub fn split( - &mut self, - kind: Split, - pane: &Pane, - state: T, - ) -> Option { - let node = self.internal.layout.find(pane)?; - - let new_pane = { - self.internal.last_pane = self.internal.last_pane.checked_add(1)?; - - Pane(self.internal.last_pane) - }; - - node.split(kind, new_pane); - - let _ = self.panes.insert(new_pane, state); - self.internal.focused_pane = Some(new_pane); - - Some(new_pane) - } - - pub fn close(&mut self, pane: &Pane) -> Option { - if let Some(sibling) = self.internal.layout.remove(pane) { - self.internal.focused_pane = Some(sibling); - self.panes.remove(pane) - } else { - None - } - } -} - -#[derive(Debug, Clone, Hash)] -enum Node { - Split { - kind: Split, - ratio: u32, - a: Box, - b: Box, - }, - Pane(Pane), -} - -impl Node { - fn find(&mut self, pane: &Pane) -> Option<&mut Node> { - match self { - Node::Split { a, b, .. } => { - if let Some(node) = a.find(pane) { - Some(node) - } else { - b.find(pane) - } - } - Node::Pane(p) => { - if p == pane { - Some(self) - } else { - None - } - } - } - } - - fn split(&mut self, kind: Split, new_pane: Pane) { - *self = Node::Split { - kind, - ratio: 500_000, - a: Box::new(self.clone()), - b: Box::new(Node::Pane(new_pane)), - }; - } - - fn remove(&mut self, pane: &Pane) -> Option { - match self { - Node::Split { a, b, .. } => { - if a.pane() == Some(*pane) { - *self = *b.clone(); - Some(self.first_pane()) - } else if b.pane() == Some(*pane) { - *self = *a.clone(); - Some(self.first_pane()) - } else { - a.remove(pane).or_else(|| b.remove(pane)) - } - } - Node::Pane(_) => None, - } - } - - pub fn regions(&self, size: Size) -> HashMap { - let mut regions = HashMap::new(); - - self.compute_regions( - &Rectangle { - x: 0.0, - y: 0.0, - width: size.width, - height: size.height, - }, - &mut regions, - ); - - regions - } - - fn pane(&self) -> Option { - match self { - Node::Split { .. } => None, - Node::Pane(pane) => Some(*pane), - } - } - - fn first_pane(&self) -> Pane { - match self { - Node::Split { a, .. } => a.first_pane(), - Node::Pane(pane) => *pane, - } - } - - fn compute_regions( - &self, - current: &Rectangle, - regions: &mut HashMap, - ) { - match self { - Node::Split { kind, ratio, a, b } => { - let ratio = *ratio as f32 / 1_000_000.0; - let (region_a, region_b) = kind.apply(current, ratio); - - a.compute_regions(®ion_a, regions); - b.compute_regions(®ion_b, regions); - } - Node::Pane(pane) => { - let _ = regions.insert(*pane, *current); - } - } - } -} - -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub enum Split { - Horizontal, - Vertical, -} - -impl Split { - fn apply( - &self, - rectangle: &Rectangle, - ratio: f32, - ) -> (Rectangle, Rectangle) { - match self { - Split::Horizontal => { - let width_left = rectangle.width * ratio; - let width_right = rectangle.width - width_left; - - ( - Rectangle { - width: width_left, - ..*rectangle - }, - Rectangle { - x: rectangle.x + width_left, - width: width_right, - ..*rectangle - }, - ) - } - Split::Vertical => { - let height_top = rectangle.height * ratio; - let height_bottom = rectangle.height - height_top; - - ( - Rectangle { - height: height_top, - ..*rectangle - }, - Rectangle { - y: rectangle.y + height_top, - height: height_bottom, - ..*rectangle - }, - ) - } - } - } -} - -/// The renderer of some [`Panes`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use [`Panes`] in your user interface. -/// -/// [`Panes`]: struct.Panes.html -/// [renderer]: ../../renderer/index.html -pub trait Renderer: crate::Renderer + Sized { - /// Draws some [`Panes`]. - /// - /// It receives: - /// - the children of the [`Column`] - /// - the [`Layout`] of the [`Column`] and its children - /// - the cursor position - /// - /// [`Column`]: struct.Row.html - /// [`Layout`]: ../layout/struct.Layout.html - fn draw( - &mut self, - defaults: &Self::Defaults, - content: &[(Pane, Element<'_, Message, Self>)], - layout: Layout<'_>, - cursor_position: Point, - ) -> Self::Output; -} - -impl<'a, Message, Renderer> From> - for Element<'a, Message, Renderer> -where - Renderer: self::Renderer + 'static, - Message: 'static, -{ - fn from( - panes: Panes<'a, Message, Renderer>, - ) -> Element<'a, Message, Renderer> { - Element::new(panes) - } -} diff --git a/src/widget.rs b/src/widget.rs index de3301fa..cb099a65 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -30,7 +30,7 @@ mod platform { pub use iced_winit::svg::{Handle, Svg}; } - pub use iced_winit::{panes, Panes, Text}; + pub use iced_winit::{pane_grid, PaneGrid, Text}; #[doc(no_inline)] pub use { diff --git a/wgpu/src/renderer/widget.rs b/wgpu/src/renderer/widget.rs index 9a46552e..37421fbe 100644 --- a/wgpu/src/renderer/widget.rs +++ b/wgpu/src/renderer/widget.rs @@ -2,7 +2,7 @@ mod button; mod checkbox; mod column; mod container; -mod panes; +mod pane_grid; mod progress_bar; mod radio; mod row; diff --git a/wgpu/src/renderer/widget/pane_grid.rs b/wgpu/src/renderer/widget/pane_grid.rs new file mode 100644 index 00000000..022fea70 --- /dev/null +++ b/wgpu/src/renderer/widget/pane_grid.rs @@ -0,0 +1,34 @@ +use crate::{Primitive, Renderer}; +use iced_native::{pane_grid, Element, Layout, MouseCursor, Point}; + +impl pane_grid::Renderer for Renderer { + fn draw( + &mut self, + defaults: &Self::Defaults, + content: &[(pane_grid::Pane, Element<'_, Message, Self>)], + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let mut mouse_cursor = MouseCursor::OutOfBounds; + + ( + Primitive::Group { + primitives: content + .iter() + .zip(layout.children()) + .map(|((_, pane), layout)| { + let (primitive, new_mouse_cursor) = + pane.draw(self, defaults, layout, cursor_position); + + if new_mouse_cursor > mouse_cursor { + mouse_cursor = new_mouse_cursor; + } + + primitive + }) + .collect(), + }, + mouse_cursor, + ) + } +} diff --git a/wgpu/src/renderer/widget/panes.rs b/wgpu/src/renderer/widget/panes.rs deleted file mode 100644 index 74e58895..00000000 --- a/wgpu/src/renderer/widget/panes.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{panes, Element, Layout, MouseCursor, Point}; - -impl panes::Renderer for Renderer { - fn draw( - &mut self, - defaults: &Self::Defaults, - content: &[(panes::Pane, Element<'_, Message, Self>)], - layout: Layout<'_>, - cursor_position: Point, - ) -> Self::Output { - let mut mouse_cursor = MouseCursor::OutOfBounds; - - ( - Primitive::Group { - primitives: content - .iter() - .zip(layout.children()) - .map(|((_, pane), layout)| { - let (primitive, new_mouse_cursor) = - pane.draw(self, defaults, layout, cursor_position); - - if new_mouse_cursor > mouse_cursor { - mouse_cursor = new_mouse_cursor; - } - - primitive - }) - .collect(), - }, - mouse_cursor, - ) - } -} -- cgit From 4e0e50ae273c24c8cfba7f190c2b69b2f9f91c54 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 10 Mar 2020 06:46:11 +0100 Subject: Fix `Debug` implementation of `layer::cache::State` --- wgpu/src/widget/canvas/layer/cache.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wgpu/src/widget/canvas/layer/cache.rs b/wgpu/src/widget/canvas/layer/cache.rs index be08d48d..f7002459 100644 --- a/wgpu/src/widget/canvas/layer/cache.rs +++ b/wgpu/src/widget/canvas/layer/cache.rs @@ -101,10 +101,9 @@ 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 { mesh, bounds } => f + State::Filled { primitive, bounds } => f .debug_struct("Filled") - .field("vertices", &mesh.vertices.len()) - .field("indices", &mesh.indices.len()) + .field("primitive", primitive) .field("bounds", bounds) .finish(), } -- cgit From ed7c327b488da471dab6df210254d45983890cdf Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 10 Mar 2020 06:46:58 +0100 Subject: Implement `Default` for `keyboard::ModifiersState` --- native/src/input/keyboard/modifiers_state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/src/input/keyboard/modifiers_state.rs b/native/src/input/keyboard/modifiers_state.rs index 4e3794b3..3058c065 100644 --- a/native/src/input/keyboard/modifiers_state.rs +++ b/native/src/input/keyboard/modifiers_state.rs @@ -1,5 +1,5 @@ /// The current state of the keyboard modifiers. -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct ModifiersState { /// Whether a shift key is pressed pub shift: bool, -- cgit From eb070b965294707100b16472980f4794162cbc36 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 10 Mar 2020 06:47:32 +0100 Subject: Draft drag and drop support for `PaneGrid` --- native/src/widget/pane_grid.rs | 164 ++++++++++++++++++++++++++++------ wgpu/src/renderer/widget/pane_grid.rs | 80 +++++++++++++---- 2 files changed, 197 insertions(+), 47 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index a03b7fcf..08ec046a 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -1,5 +1,5 @@ use crate::{ - input::{mouse, ButtonState}, + input::{keyboard, mouse, ButtonState}, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; @@ -12,6 +12,7 @@ pub struct PaneGrid<'a, Message, Renderer> { elements: Vec<(Pane, Element<'a, Message, Renderer>)>, width: Length, height: Length, + on_drop: Option Message>>, } impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { @@ -30,6 +31,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { elements, width: Length::Fill, height: Length::Fill, + on_drop: None, } } @@ -48,6 +50,17 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self.height = height; self } + + pub fn on_drop(mut self, f: impl Fn(Drop) -> Message + 'static) -> Self { + self.on_drop = Some(Box::new(f)); + self + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Drop { + pub pane: Pane, + pub target: Pane, } impl<'a, Message, Renderer> Widget @@ -105,32 +118,76 @@ where match event { Event::Mouse(mouse::Event::Input { button: mouse::Button::Left, - state: ButtonState::Pressed, - }) => { - let mut clicked_region = - self.elements.iter().zip(layout.children()).filter( - |(_, layout)| layout.bounds().contains(cursor_position), - ); - - if let Some(((pane, _), _)) = clicked_region.next() { - self.state.focused_pane = Some(*pane); + state, + }) => match state { + ButtonState::Pressed => { + let mut clicked_region = + self.elements.iter().zip(layout.children()).filter( + |(_, layout)| { + layout.bounds().contains(cursor_position) + }, + ); + + if let Some(((pane, _), _)) = clicked_region.next() { + self.state.focus = if self.on_drop.is_some() + && self.state.modifiers.alt + { + Some(Focus::Dragging(*pane)) + } else { + Some(Focus::Idle(*pane)) + } + } } + ButtonState::Released => { + if let Some(on_drop) = &self.on_drop { + if let Some(Focus::Dragging(pane)) = self.state.focus { + let mut dropped_region = self + .elements + .iter() + .zip(layout.children()) + .filter(|(_, layout)| { + layout.bounds().contains(cursor_position) + }); + + if let Some(((target, _), _)) = + dropped_region.next() + { + if pane != *target { + messages.push(on_drop(Drop { + pane, + target: *target, + })); + } + } + + self.state.focus = Some(Focus::Idle(pane)); + } + } + } + }, + Event::Keyboard(keyboard::Event::Input { modifiers, .. }) => { + self.state.modifiers = modifiers; } _ => {} } - self.elements.iter_mut().zip(layout.children()).for_each( - |((_, pane), layout)| { - pane.widget.on_event( - event.clone(), - layout, - cursor_position, - messages, - renderer, - clipboard, - ) - }, - ); + match self.state.focus { + Some(Focus::Dragging(_)) => {} + _ => { + self.elements.iter_mut().zip(layout.children()).for_each( + |((_, pane), layout)| { + pane.widget.on_event( + event.clone(), + layout, + cursor_position, + messages, + renderer, + clipboard, + ) + }, + ); + } + } } fn draw( @@ -140,7 +197,18 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - renderer.draw(defaults, &self.elements, layout, cursor_position) + let dragging = match self.state.focus { + Some(Focus::Dragging(pane)) => Some(pane), + _ => None, + }; + + renderer.draw( + defaults, + &self.elements, + dragging, + layout, + cursor_position, + ) } fn hash_layout(&self, state: &mut Hasher) { @@ -176,7 +244,14 @@ pub struct State { struct Internal { layout: Node, last_pane: usize, - focused_pane: Option, + focus: Option, + modifiers: keyboard::ModifiersState, +} + +#[derive(Debug)] +enum Focus { + Idle(Pane), + Dragging(Pane), } impl State { @@ -192,7 +267,8 @@ impl State { internal: Internal { layout: Node::Pane(first_pane), last_pane: 0, - focused_pane: None, + focus: None, + modifiers: keyboard::ModifiersState::default(), }, }, first_pane, @@ -216,11 +292,15 @@ impl State { } pub fn focused_pane(&self) -> Option { - self.internal.focused_pane + match self.internal.focus { + Some(Focus::Idle(pane)) => Some(pane), + Some(Focus::Dragging(_)) => None, + None => None, + } } pub fn focus(&mut self, pane: Pane) { - self.internal.focused_pane = Some(pane); + self.internal.focus = Some(Focus::Idle(pane)); } pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { @@ -252,14 +332,27 @@ impl State { node.split(kind, new_pane); let _ = self.panes.insert(new_pane, state); - self.internal.focused_pane = Some(new_pane); + self.internal.focus = Some(Focus::Idle(new_pane)); Some(new_pane) } + pub fn swap(&mut self, a: &Pane, b: &Pane) { + self.internal.layout.update(&|node| match node { + Node::Split { .. } => {} + Node::Pane(pane) => { + if pane == a { + *node = Node::Pane(*b); + } else if pane == b { + *node = Node::Pane(*a); + } + } + }); + } + pub fn close(&mut self, pane: &Pane) -> Option { if let Some(sibling) = self.internal.layout.remove(pane) { - self.internal.focused_pane = Some(sibling); + self.internal.focus = Some(Focus::Idle(sibling)); self.panes.remove(pane) } else { None @@ -307,6 +400,18 @@ impl Node { }; } + fn update(&mut self, f: &impl Fn(&mut Node)) { + match self { + Node::Split { a, b, .. } => { + a.update(f); + b.update(f); + } + _ => {} + } + + f(self); + } + fn remove(&mut self, pane: &Pane) -> Option { match self { Node::Split { a, b, .. } => { @@ -444,6 +549,7 @@ pub trait Renderer: crate::Renderer + Sized { &mut self, defaults: &Self::Defaults, content: &[(Pane, Element<'_, Message, Self>)], + dragging: Option, layout: Layout<'_>, cursor_position: Point, ) -> Self::Output; diff --git a/wgpu/src/renderer/widget/pane_grid.rs b/wgpu/src/renderer/widget/pane_grid.rs index 022fea70..8fb4a1a9 100644 --- a/wgpu/src/renderer/widget/pane_grid.rs +++ b/wgpu/src/renderer/widget/pane_grid.rs @@ -1,34 +1,78 @@ use crate::{Primitive, Renderer}; -use iced_native::{pane_grid, Element, Layout, MouseCursor, Point}; +use iced_native::{ + pane_grid::{self, Pane}, + Element, Layout, MouseCursor, Point, Rectangle, Vector, +}; impl pane_grid::Renderer for Renderer { fn draw( &mut self, defaults: &Self::Defaults, - content: &[(pane_grid::Pane, Element<'_, Message, Self>)], + content: &[(Pane, Element<'_, Message, Self>)], + dragging: Option, layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { let mut mouse_cursor = MouseCursor::OutOfBounds; + let mut dragged_pane = None; + + let mut panes: Vec<_> = content + .iter() + .zip(layout.children()) + .enumerate() + .map(|(i, ((id, pane), layout))| { + let (primitive, new_mouse_cursor) = + pane.draw(self, defaults, layout, cursor_position); + + if new_mouse_cursor > mouse_cursor { + mouse_cursor = new_mouse_cursor; + } + + if Some(*id) == dragging { + dragged_pane = Some((i, layout)); + } + + primitive + }) + .collect(); + + let primitives = if let Some((index, layout)) = dragged_pane { + let pane = panes.remove(index); + let bounds = layout.bounds(); + + // TODO: Fix once proper layering is implemented. + // This is a pretty hacky way to achieve layering. + let clip = Primitive::Clip { + bounds: Rectangle { + x: cursor_position.x - bounds.width / 2.0, + y: cursor_position.y - bounds.height / 2.0, + width: bounds.width + 0.5, + height: bounds.height + 0.5, + }, + offset: Vector::new(0, 0), + content: Box::new(Primitive::Cached { + origin: Point::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), + }), + }; + + panes.push(clip); + + panes + } else { + panes + }; ( - Primitive::Group { - primitives: content - .iter() - .zip(layout.children()) - .map(|((_, pane), layout)| { - let (primitive, new_mouse_cursor) = - pane.draw(self, defaults, layout, cursor_position); - - if new_mouse_cursor > mouse_cursor { - mouse_cursor = new_mouse_cursor; - } - - primitive - }) - .collect(), + Primitive::Group { primitives }, + if dragging.is_some() { + MouseCursor::Grabbing + } else { + mouse_cursor }, - mouse_cursor, ) } } -- cgit From f11397c31a4139fedb90671a5d154c17749f2a7f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 10 Mar 2020 06:49:25 +0100 Subject: Clip `scrollable` primitives only when necessary --- wgpu/src/renderer/widget/scrollable.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/wgpu/src/renderer/widget/scrollable.rs b/wgpu/src/renderer/widget/scrollable.rs index bfee7411..732523e3 100644 --- a/wgpu/src/renderer/widget/scrollable.rs +++ b/wgpu/src/renderer/widget/scrollable.rs @@ -58,14 +58,14 @@ impl scrollable::Renderer for Renderer { style_sheet: &Self::Style, (content, mouse_cursor): Self::Output, ) -> Self::Output { - let clip = Primitive::Clip { - bounds, - offset: Vector::new(0, offset), - content: Box::new(content), - }; - ( if let Some(scrollbar) = scrollbar { + let clip = Primitive::Clip { + bounds, + offset: Vector::new(0, offset), + content: Box::new(content), + }; + let style = if state.is_scroller_grabbed() { style_sheet.dragging() } else if is_mouse_over_scrollbar { @@ -115,7 +115,7 @@ impl scrollable::Renderer for Renderer { primitives: vec![clip, scrollbar, scroller], } } else { - clip + content }, if is_mouse_over_scrollbar || state.is_scroller_grabbed() { MouseCursor::Idle -- cgit From df6e3f8da921a98c9a1e75a1790885a07485ee45 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 11 Mar 2020 23:25:00 +0100 Subject: Expose `pane_grid::Focus` for state-based styling --- examples/pane_grid/src/main.rs | 2 +- native/src/widget/pane_grid.rs | 115 +++++++++++++++++++++++++++++++---------- 2 files changed, 88 insertions(+), 29 deletions(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index b103afc8..eb8aaa51 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -116,7 +116,7 @@ impl Application for Launcher { fn view(&mut self) -> Element { let Self { panes } = self; - PaneGrid::new(panes, |pane, example| match example { + PaneGrid::new(panes, |pane, example, _| match example { Example::Clock(clock) => clock .view() .map(move |message| Message::Clock(pane, message)), diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 08ec046a..7fc12176 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -18,13 +18,31 @@ pub struct PaneGrid<'a, Message, Renderer> { impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { pub fn new( state: &'a mut State, - view: impl Fn(Pane, &'a mut T) -> Element<'a, Message, Renderer>, + view: impl Fn( + Pane, + &'a mut T, + Option, + ) -> Element<'a, Message, Renderer>, ) -> Self { - let elements = state - .panes - .iter_mut() - .map(|(pane, state)| (*pane, view(*pane, state))) - .collect(); + let elements = { + let focused_pane = state.internal.focused_pane; + + state + .panes + .iter_mut() + .map(move |(pane, pane_state)| { + let focus = match focused_pane { + FocusedPane::Some { + pane: focused_pane, + focus, + } if *pane == focused_pane => Some(focus), + _ => None, + }; + + (*pane, view(*pane, pane_state, focus)) + }) + .collect() + }; Self { state: &mut state.internal, @@ -129,18 +147,28 @@ where ); if let Some(((pane, _), _)) = clicked_region.next() { - self.state.focus = if self.on_drop.is_some() + self.state.focused_pane = if self.on_drop.is_some() && self.state.modifiers.alt { - Some(Focus::Dragging(*pane)) + FocusedPane::Some { + pane: *pane, + focus: Focus::Dragging, + } } else { - Some(Focus::Idle(*pane)) + FocusedPane::Some { + pane: *pane, + focus: Focus::Idle, + } } } } ButtonState::Released => { if let Some(on_drop) = &self.on_drop { - if let Some(Focus::Dragging(pane)) = self.state.focus { + if let FocusedPane::Some { + pane, + focus: Focus::Dragging, + } = self.state.focused_pane + { let mut dropped_region = self .elements .iter() @@ -160,7 +188,10 @@ where } } - self.state.focus = Some(Focus::Idle(pane)); + self.state.focused_pane = FocusedPane::Some { + pane, + focus: Focus::Idle, + }; } } } @@ -171,8 +202,11 @@ where _ => {} } - match self.state.focus { - Some(Focus::Dragging(_)) => {} + match self.state.focused_pane { + FocusedPane::Some { + focus: Focus::Dragging, + .. + } => {} _ => { self.elements.iter_mut().zip(layout.children()).for_each( |((_, pane), layout)| { @@ -197,8 +231,11 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - let dragging = match self.state.focus { - Some(Focus::Dragging(pane)) => Some(pane), + let dragging = match self.state.focused_pane { + FocusedPane::Some { + pane, + focus: Focus::Dragging, + } => Some(pane), _ => None, }; @@ -244,14 +281,20 @@ pub struct State { struct Internal { layout: Node, last_pane: usize, - focus: Option, + focused_pane: FocusedPane, modifiers: keyboard::ModifiersState, } -#[derive(Debug)] -enum Focus { - Idle(Pane), - Dragging(Pane), +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Focus { + Idle, + Dragging, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum FocusedPane { + None, + Some { pane: Pane, focus: Focus }, } impl State { @@ -267,7 +310,7 @@ impl State { internal: Internal { layout: Node::Pane(first_pane), last_pane: 0, - focus: None, + focused_pane: FocusedPane::None, modifiers: keyboard::ModifiersState::default(), }, }, @@ -292,15 +335,24 @@ impl State { } pub fn focused_pane(&self) -> Option { - match self.internal.focus { - Some(Focus::Idle(pane)) => Some(pane), - Some(Focus::Dragging(_)) => None, - None => None, + match self.internal.focused_pane { + FocusedPane::Some { + pane, + focus: Focus::Idle, + } => Some(pane), + FocusedPane::Some { + focus: Focus::Dragging, + .. + } => None, + FocusedPane::None => None, } } pub fn focus(&mut self, pane: Pane) { - self.internal.focus = Some(Focus::Idle(pane)); + self.internal.focused_pane = FocusedPane::Some { + pane, + focus: Focus::Idle, + }; } pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { @@ -332,7 +384,10 @@ impl State { node.split(kind, new_pane); let _ = self.panes.insert(new_pane, state); - self.internal.focus = Some(Focus::Idle(new_pane)); + self.internal.focused_pane = FocusedPane::Some { + pane: new_pane, + focus: Focus::Idle, + }; Some(new_pane) } @@ -352,7 +407,11 @@ impl State { pub fn close(&mut self, pane: &Pane) -> Option { if let Some(sibling) = self.internal.layout.remove(pane) { - self.internal.focus = Some(Focus::Idle(sibling)); + self.internal.focused_pane = FocusedPane::Some { + pane: sibling, + focus: Focus::Idle, + }; + self.panes.remove(pane) } else { None -- cgit From f09b4bd4f4113872eeac67320e79f81eac0a948f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 11 Mar 2020 23:26:45 +0100 Subject: Round region dimensions of panes to avoid overlaps --- native/src/widget/pane_grid.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 7fc12176..3b68b99e 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -552,7 +552,7 @@ impl Split { ) -> (Rectangle, Rectangle) { match self { Split::Horizontal => { - let width_left = rectangle.width * ratio; + let width_left = (rectangle.width * ratio).round(); let width_right = rectangle.width - width_left; ( @@ -568,7 +568,7 @@ impl Split { ) } Split::Vertical => { - let height_top = rectangle.height * ratio; + let height_top = (rectangle.height * ratio).round(); let height_bottom = rectangle.height - height_top; ( -- cgit From 2d8d420949fc3b5c4390e32295cda64fb191488f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 11 Mar 2020 23:34:51 +0100 Subject: Replace `Panes` with `PaneGrid` in documentation --- native/src/widget/pane_grid.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 3b68b99e..31148168 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -53,17 +53,17 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { } } - /// Sets the width of the [`Panes`]. + /// Sets the width of the [`PaneGrid`]. /// - /// [`Panes`]: struct.Column.html + /// [`PaneGrid`]: struct.Column.html pub fn width(mut self, width: Length) -> Self { self.width = width; self } - /// Sets the height of the [`Panes`]. + /// Sets the height of the [`PaneGrid`]. /// - /// [`Panes`]: struct.Column.html + /// [`PaneGrid`]: struct.Column.html pub fn height(mut self, height: Length) -> Self { self.height = height; self @@ -587,19 +587,20 @@ impl Split { } } -/// The renderer of some [`Panes`]. +/// The renderer of a [`PaneGrid`]. /// /// Your [renderer] will need to implement this trait before being -/// able to use [`Panes`] in your user interface. +/// able to use a [`PaneGrid`] in your user interface. /// -/// [`Panes`]: struct.Panes.html +/// [`PaneGrid`]: struct.PaneGrid.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer + Sized { - /// Draws some [`Panes`]. + /// Draws a [`PaneGrid`]. /// /// It receives: - /// - the children of the [`Column`] - /// - the [`Layout`] of the [`Column`] and its children + /// - the elements of the [`PaneGrid`] + /// - the [`Pane`] that is currently being dragged + /// - the [`Layout`] of the [`PaneGrid`] and its elements /// - the cursor position /// /// [`Column`]: struct.Row.html @@ -621,8 +622,8 @@ where Message: 'static, { fn from( - panes: PaneGrid<'a, Message, Renderer>, + pane_grid: PaneGrid<'a, Message, Renderer>, ) -> Element<'a, Message, Renderer> { - Element::new(panes) + Element::new(pane_grid) } } -- cgit From f1168187c917f84130e97be81e2ccd4181fe9539 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 12 Mar 2020 07:32:04 +0100 Subject: Update `window_clipboard` in `iced_winit` --- winit/Cargo.toml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/winit/Cargo.toml b/winit/Cargo.toml index a70ecb9d..ca2018c7 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -15,15 +15,12 @@ debug = [] [dependencies] winit = "0.22" +window_clipboard = "0.1" log = "0.4" [dependencies.iced_native] version = "0.1.0-alpha" path = "../native" -[dependencies.window_clipboard] -git = "https://github.com/hecrj/window_clipboard" -rev = "22c6dd6c04cd05d528029b50a30c56417cd4bebf" - [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3.6" -- cgit From c2ced4cd59fcc4cc46c2030897382bc443b9ccd8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 13 Mar 2020 07:35:44 +0100 Subject: Improve `PaneGrid` API by introducing `DragEvent` --- native/src/widget/pane_grid.rs | 76 ++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 31148168..7999998b 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -12,7 +12,7 @@ pub struct PaneGrid<'a, Message, Renderer> { elements: Vec<(Pane, Element<'a, Message, Renderer>)>, width: Length, height: Length, - on_drop: Option Message>>, + on_drag: Option Message>>, } impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { @@ -49,7 +49,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { elements, width: Length::Fill, height: Length::Fill, - on_drop: None, + on_drag: None, } } @@ -69,16 +69,20 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self } - pub fn on_drop(mut self, f: impl Fn(Drop) -> Message + 'static) -> Self { - self.on_drop = Some(Box::new(f)); + pub fn on_drag( + mut self, + f: impl Fn(DragEvent) -> Message + 'static, + ) -> Self { + self.on_drag = Some(Box::new(f)); self } } #[derive(Debug, Clone, Copy)] -pub struct Drop { - pub pane: Pane, - pub target: Pane, +pub enum DragEvent { + Picked { pane: Pane }, + Dropped { pane: Pane, target: Pane }, + Canceled { pane: Pane }, } impl<'a, Message, Renderer> Widget @@ -147,28 +151,38 @@ where ); if let Some(((pane, _), _)) = clicked_region.next() { - self.state.focused_pane = if self.on_drop.is_some() - && self.state.modifiers.alt - { - FocusedPane::Some { - pane: *pane, - focus: Focus::Dragging, + match &self.on_drag { + Some(on_drag) if self.state.modifiers.alt => { + self.state.focused_pane = FocusedPane::Some { + pane: *pane, + focus: Focus::Dragging, + }; + + messages.push(on_drag(DragEvent::Picked { + pane: *pane, + })); } - } else { - FocusedPane::Some { - pane: *pane, - focus: Focus::Idle, + _ => { + self.state.focused_pane = FocusedPane::Some { + pane: *pane, + focus: Focus::Idle, + }; } } } } ButtonState::Released => { - if let Some(on_drop) = &self.on_drop { - if let FocusedPane::Some { + if let FocusedPane::Some { + pane, + focus: Focus::Dragging, + } = self.state.focused_pane + { + self.state.focused_pane = FocusedPane::Some { pane, - focus: Focus::Dragging, - } = self.state.focused_pane - { + focus: Focus::Idle, + }; + + if let Some(on_drag) = &self.on_drag { let mut dropped_region = self .elements .iter() @@ -177,21 +191,17 @@ where layout.bounds().contains(cursor_position) }); - if let Some(((target, _), _)) = - dropped_region.next() - { - if pane != *target { - messages.push(on_drop(Drop { + let event = match dropped_region.next() { + Some(((target, _), _)) if pane != *target => { + DragEvent::Dropped { pane, target: *target, - })); + } } - } - - self.state.focused_pane = FocusedPane::Some { - pane, - focus: Focus::Idle, + _ => DragEvent::Canceled { pane }, }; + + messages.push(on_drag(event)); } } } -- cgit From 29bf51d25afdb07734700207dcc25b37357c04b1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 13 Mar 2020 07:51:41 +0100 Subject: Implement `spacing` support for `PaneGrid` --- native/src/widget/pane_grid.rs | 43 +++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 7999998b..92ddc6e0 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -12,6 +12,7 @@ pub struct PaneGrid<'a, Message, Renderer> { elements: Vec<(Pane, Element<'a, Message, Renderer>)>, width: Length, height: Length, + spacing: u16, on_drag: Option Message>>, } @@ -49,6 +50,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { elements, width: Length::Fill, height: Length::Fill, + spacing: 0, on_drag: None, } } @@ -69,6 +71,14 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self } + /// Sets the spacing _between_ the panes of the [`PaneGrid`]. + /// + /// [`PaneGrid`]: struct.Column.html + pub fn spacing(mut self, units: u16) -> Self { + self.spacing = units; + self + } + pub fn on_drag( mut self, f: impl Fn(DragEvent) -> Message + 'static, @@ -107,7 +117,7 @@ where let limits = limits.width(self.width).height(self.height); let size = limits.resolve(Size::ZERO); - let regions = self.state.layout.regions(size); + let regions = self.state.layout.regions(f32::from(self.spacing), size); let children = self .elements @@ -498,10 +508,15 @@ impl Node { } } - pub fn regions(&self, size: Size) -> HashMap { + pub fn regions( + &self, + spacing: f32, + size: Size, + ) -> HashMap { let mut regions = HashMap::new(); self.compute_regions( + spacing / 2.0, &Rectangle { x: 0.0, y: 0.0, @@ -530,16 +545,18 @@ impl Node { fn compute_regions( &self, + halved_spacing: f32, current: &Rectangle, regions: &mut HashMap, ) { match self { Node::Split { kind, ratio, a, b } => { let ratio = *ratio as f32 / 1_000_000.0; - let (region_a, region_b) = kind.apply(current, ratio); + let (region_a, region_b) = + kind.apply(current, ratio, halved_spacing); - a.compute_regions(®ion_a, regions); - b.compute_regions(®ion_b, regions); + a.compute_regions(halved_spacing, ®ion_a, regions); + b.compute_regions(halved_spacing, ®ion_b, regions); } Node::Pane(pane) => { let _ = regions.insert(*pane, *current); @@ -559,11 +576,13 @@ impl Split { &self, rectangle: &Rectangle, ratio: f32, + halved_spacing: f32, ) -> (Rectangle, Rectangle) { match self { Split::Horizontal => { - let width_left = (rectangle.width * ratio).round(); - let width_right = rectangle.width - width_left; + let width_left = + (rectangle.width * ratio).round() - halved_spacing; + let width_right = rectangle.width - width_left - halved_spacing; ( Rectangle { @@ -571,15 +590,17 @@ impl Split { ..*rectangle }, Rectangle { - x: rectangle.x + width_left, + x: rectangle.x + width_left + halved_spacing, width: width_right, ..*rectangle }, ) } Split::Vertical => { - let height_top = (rectangle.height * ratio).round(); - let height_bottom = rectangle.height - height_top; + let height_top = + (rectangle.height * ratio).round() - halved_spacing; + let height_bottom = + rectangle.height - height_top - halved_spacing; ( Rectangle { @@ -587,7 +608,7 @@ impl Split { ..*rectangle }, Rectangle { - y: rectangle.y + height_top, + y: rectangle.y + height_top + halved_spacing, height: height_bottom, ..*rectangle }, -- cgit From 0b12d706e3767c04b89d4d2692c31ff9fa715cc9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 13 Mar 2020 07:54:14 +0100 Subject: Unfocus pane in `PaneGrid` on out-of-bounds click --- native/src/widget/pane_grid.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 92ddc6e0..78057bda 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -179,6 +179,8 @@ where }; } } + } else { + self.state.focused_pane = FocusedPane::None; } } ButtonState::Released => { -- cgit From b9f184fda4e5cd60b20b9f35cfbb81a6cf1fbd65 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 13 Mar 2020 08:57:52 +0100 Subject: Draft `PaneGrid::focus_adjacent` --- native/src/widget/pane_grid.rs | 91 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 8 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 78057bda..d93f8738 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -319,6 +319,14 @@ enum FocusedPane { Some { pane: Pane, focus: Focus }, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Direction { + Top, + Bottom, + Left, + Right, +} + impl State { pub fn new(first_pane_state: T) -> (Self, Pane) { let first_pane = Pane(0); @@ -370,13 +378,20 @@ impl State { } } - pub fn focus(&mut self, pane: Pane) { + pub fn focus(&mut self, pane: &Pane) { self.internal.focused_pane = FocusedPane::Some { - pane, + pane: *pane, focus: Focus::Idle, }; } + pub fn focus_adjacent(&mut self, pane: &Pane, direction: Direction) { + if let Some(pane) = self.internal.layout.find_adjacent(pane, direction) + { + self.focus(&pane); + } + } + pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { self.split(Split::Vertical, pane, state) } @@ -452,15 +467,17 @@ enum Node { Pane(Pane), } +#[derive(Debug)] +enum Branch { + First, + Second, +} + impl Node { fn find(&mut self, pane: &Pane) -> Option<&mut Node> { match self { Node::Split { a, b, .. } => { - if let Some(node) = a.find(pane) { - Some(node) - } else { - b.find(pane) - } + a.find(pane).or_else(move || b.find(pane)) } Node::Pane(p) => { if p == pane { @@ -472,6 +489,64 @@ impl Node { } } + fn find_adjacent( + &mut self, + pane: &Pane, + direction: Direction, + ) -> Option { + let (pane, _) = self.find_split(pane, &|kind, branch, a, b| match ( + direction, kind, branch, + ) { + (Direction::Top, Split::Vertical, Branch::Second) + | (Direction::Left, Split::Horizontal, Branch::Second) => { + Some(a.first_pane()) + } + (Direction::Bottom, Split::Vertical, Branch::First) + | (Direction::Right, Split::Horizontal, Branch::First) => { + Some(b.first_pane()) + } + _ => None, + }); + + pane + } + + fn find_split( + &mut self, + pane: &Pane, + callback: &impl Fn(Split, Branch, &Node, &Node) -> Option, + ) -> (Option, bool) { + match self { + Node::Split { a, b, kind, .. } => { + let kind = *kind; + let (result, found) = a.find_split(pane, callback); + + if result.is_some() { + (result, found) + } else if found { + (callback(kind, Branch::First, a, b), true) + } else { + let (result, found) = b.find_split(pane, callback); + + if result.is_some() { + (result, found) + } else if found { + (callback(kind, Branch::Second, a, b), true) + } else { + (None, false) + } + } + } + Node::Pane(p) => { + if p == pane { + (None, true) + } else { + (None, false) + } + } + } + } + fn split(&mut self, kind: Split, new_pane: Pane) { *self = Node::Split { kind, @@ -567,7 +642,7 @@ impl Node { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub enum Split { Horizontal, Vertical, -- cgit From 26b9541bcab99bba66f2cf9bcae62a3adc95283c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 13 Mar 2020 09:55:59 +0100 Subject: Improve `PaneGrid::focus_adjacent` intuitiveness --- native/src/widget/pane_grid.rs | 94 ++++++++++++------------------------------ 1 file changed, 27 insertions(+), 67 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index d93f8738..1ba1b417 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -386,9 +386,33 @@ impl State { } pub fn focus_adjacent(&mut self, pane: &Pane, direction: Direction) { - if let Some(pane) = self.internal.layout.find_adjacent(pane, direction) - { - self.focus(&pane); + let regions = + self.internal.layout.regions(0.0, Size::new(4096.0, 4096.0)); + + if let Some(current_region) = regions.get(pane) { + let target = match direction { + Direction::Left => { + Point::new(current_region.x - 1.0, current_region.y + 1.0) + } + Direction::Right => Point::new( + current_region.x + current_region.width + 1.0, + current_region.y + 1.0, + ), + Direction::Top => { + Point::new(current_region.x + 1.0, current_region.y - 1.0) + } + Direction::Bottom => Point::new( + current_region.x + 1.0, + current_region.y + current_region.height + 1.0, + ), + }; + + let mut colliding_regions = + regions.iter().filter(|(_, region)| region.contains(target)); + + if let Some((pane, _)) = colliding_regions.next() { + self.focus(&pane); + } } } @@ -467,12 +491,6 @@ enum Node { Pane(Pane), } -#[derive(Debug)] -enum Branch { - First, - Second, -} - impl Node { fn find(&mut self, pane: &Pane) -> Option<&mut Node> { match self { @@ -489,64 +507,6 @@ impl Node { } } - fn find_adjacent( - &mut self, - pane: &Pane, - direction: Direction, - ) -> Option { - let (pane, _) = self.find_split(pane, &|kind, branch, a, b| match ( - direction, kind, branch, - ) { - (Direction::Top, Split::Vertical, Branch::Second) - | (Direction::Left, Split::Horizontal, Branch::Second) => { - Some(a.first_pane()) - } - (Direction::Bottom, Split::Vertical, Branch::First) - | (Direction::Right, Split::Horizontal, Branch::First) => { - Some(b.first_pane()) - } - _ => None, - }); - - pane - } - - fn find_split( - &mut self, - pane: &Pane, - callback: &impl Fn(Split, Branch, &Node, &Node) -> Option, - ) -> (Option, bool) { - match self { - Node::Split { a, b, kind, .. } => { - let kind = *kind; - let (result, found) = a.find_split(pane, callback); - - if result.is_some() { - (result, found) - } else if found { - (callback(kind, Branch::First, a, b), true) - } else { - let (result, found) = b.find_split(pane, callback); - - if result.is_some() { - (result, found) - } else if found { - (callback(kind, Branch::Second, a, b), true) - } else { - (None, false) - } - } - } - Node::Pane(p) => { - if p == pane { - (None, true) - } else { - (None, false) - } - } - } - } - fn split(&mut self, kind: Split, new_pane: Pane) { *self = Node::Split { kind, -- cgit From 858c086eeee4284a5a7b3e49cb3c112d204e53c3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Mar 2020 03:39:20 +0100 Subject: Remove `pane_grid` example for now It's too contrived. I will work on something simpler. --- Cargo.toml | 1 - examples/clock/Cargo.toml | 3 + examples/clock/README.md | 4 +- examples/clock/src/lib.rs | 187 ------------------------------------- examples/clock/src/main.rs | 188 ++++++++++++++++++++++++++++++++++++- examples/pane_grid/Cargo.toml | 12 --- examples/pane_grid/README.md | 18 ---- examples/pane_grid/src/main.rs | 179 ------------------------------------ examples/stopwatch/src/lib.rs | 204 ----------------------------------------- examples/stopwatch/src/main.rs | 204 ++++++++++++++++++++++++++++++++++++++++- 10 files changed, 393 insertions(+), 607 deletions(-) delete mode 100644 examples/clock/src/lib.rs delete mode 100644 examples/pane_grid/Cargo.toml delete mode 100644 examples/pane_grid/README.md delete mode 100644 examples/pane_grid/src/main.rs delete mode 100644 examples/stopwatch/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 37b20ec4..01231b70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,6 @@ members = [ "examples/events", "examples/geometry", "examples/integration", - "examples/pane_grid", "examples/pokedex", "examples/progress_bar", "examples/solar_system", diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml index ab771405..308cbfbb 100644 --- a/examples/clock/Cargo.toml +++ b/examples/clock/Cargo.toml @@ -5,6 +5,9 @@ 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" } diff --git a/examples/clock/README.md b/examples/clock/README.md index a87edad0..17509180 100644 --- a/examples/clock/README.md +++ b/examples/clock/README.md @@ -2,7 +2,7 @@ An application that uses the `Canvas` widget to draw a clock and its hands to display the current time. -The __[`lib`]__ file contains the relevant code of the example. +The __[`main`]__ file contains all the code of the example.
@@ -13,4 +13,4 @@ You can run it with `cargo run`: cargo run --package clock ``` -[`lib`]: src/lib.rs +[`main`]: src/main.rs diff --git a/examples/clock/src/lib.rs b/examples/clock/src/lib.rs deleted file mode 100644 index 229ba8fe..00000000 --- a/examples/clock/src/lib.rs +++ /dev/null @@ -1,187 +0,0 @@ -use iced::{ - canvas, executor, Application, Canvas, Color, Command, Container, Element, - Length, Point, Subscription, Vector, -}; - -#[derive(Debug)] -pub struct Clock { - now: LocalTime, - clock: canvas::layer::Cache, -} - -#[derive(Debug, Clone, Copy)] -pub enum Message { - Tick(chrono::DateTime), -} - -impl Application for Clock { - type Executor = executor::Default; - type Message = Message; - - fn new() -> (Self, Command) { - ( - Clock { - now: chrono::Local::now().into(), - clock: canvas::layer::Cache::new(), - }, - Command::none(), - ) - } - - fn title(&self) -> String { - String::from("Clock - Iced") - } - - fn update(&mut self, message: Message) -> Command { - match message { - Message::Tick(local_time) => { - let now = local_time.into(); - - if now != self.now { - self.now = now; - self.clock.clear(); - } - } - } - - Command::none() - } - - fn subscription(&self) -> Subscription { - time::every(std::time::Duration::from_millis(500)).map(Message::Tick) - } - - fn view(&mut self) -> Element { - 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) - .center_x() - .center_y() - .into() - } -} - -#[derive(Debug, PartialEq, Eq)] -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::Drawable for LocalTime { - fn draw(&self, frame: &mut canvas::Frame) { - let center = frame.center(); - let radius = frame.width().min(frame.height()) / 2.0; - let offset = Vector::new(center.x, center.y); - - let clock = canvas::Path::new(|path| path.circle(center, radius)); - - frame.fill( - &clock, - canvas::Fill::Color(Color::from_rgb8(0x12, 0x93, 0xD8)), - ); - - fn draw_hand( - n: u32, - total: u32, - length: f32, - offset: Vector, - path: &mut canvas::path::Builder, - ) { - 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) + offset); - } - - let hour_and_minute_hands = canvas::Path::new(|path| { - path.move_to(center); - draw_hand(self.hour, 12, 0.5 * radius, offset, path); - - path.move_to(center); - draw_hand(self.minute, 60, 0.8 * radius, offset, path) - }); - - frame.stroke( - &hour_and_minute_hands, - canvas::Stroke { - width: 6.0, - color: Color::WHITE, - line_cap: canvas::LineCap::Round, - ..canvas::Stroke::default() - }, - ); - - let second_hand = canvas::Path::new(|path| { - path.move_to(center); - draw_hand(self.second, 60, 0.8 * radius, offset, path) - }); - - frame.stroke( - &second_hand, - canvas::Stroke { - width: 3.0, - color: Color::WHITE, - line_cap: canvas::LineCap::Round, - ..canvas::Stroke::default() - }, - ); - } -} - -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/examples/clock/src/main.rs b/examples/clock/src/main.rs index 454ff4f0..d8266f06 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,5 +1,7 @@ -use clock::Clock; -use iced::{Application, Settings}; +use iced::{ + canvas, executor, Application, Canvas, Color, Command, Container, Element, + Length, Point, Settings, Subscription, Vector, +}; pub fn main() { Clock::run(Settings { @@ -7,3 +9,185 @@ pub fn main() { ..Settings::default() }) } + +struct Clock { + now: LocalTime, + clock: canvas::layer::Cache, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + Tick(chrono::DateTime), +} + +impl Application for Clock { + type Executor = executor::Default; + type Message = Message; + + fn new() -> (Self, Command) { + ( + Clock { + now: chrono::Local::now().into(), + clock: canvas::layer::Cache::new(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Clock - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Tick(local_time) => { + let now = local_time.into(); + + if now != self.now { + self.now = now; + self.clock.clear(); + } + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription { + time::every(std::time::Duration::from_millis(500)).map(Message::Tick) + } + + fn view(&mut self) -> Element { + 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) + .center_x() + .center_y() + .into() + } +} + +#[derive(Debug, PartialEq, Eq)] +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::Drawable for LocalTime { + fn draw(&self, frame: &mut canvas::Frame) { + let center = frame.center(); + let radius = frame.width().min(frame.height()) / 2.0; + let offset = Vector::new(center.x, center.y); + + let clock = canvas::Path::new(|path| path.circle(center, radius)); + + frame.fill( + &clock, + canvas::Fill::Color(Color::from_rgb8(0x12, 0x93, 0xD8)), + ); + + fn draw_hand( + n: u32, + total: u32, + length: f32, + offset: Vector, + path: &mut canvas::path::Builder, + ) { + 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) + offset); + } + + let hour_and_minute_hands = canvas::Path::new(|path| { + path.move_to(center); + draw_hand(self.hour, 12, 0.5 * radius, offset, path); + + path.move_to(center); + draw_hand(self.minute, 60, 0.8 * radius, offset, path) + }); + + frame.stroke( + &hour_and_minute_hands, + canvas::Stroke { + width: 6.0, + color: Color::WHITE, + line_cap: canvas::LineCap::Round, + ..canvas::Stroke::default() + }, + ); + + let second_hand = canvas::Path::new(|path| { + path.move_to(center); + draw_hand(self.second, 60, 0.8 * radius, offset, path) + }); + + frame.stroke( + &second_hand, + canvas::Stroke { + width: 3.0, + color: Color::WHITE, + line_cap: canvas::LineCap::Round, + ..canvas::Stroke::default() + }, + ); + } +} + +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/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml deleted file mode 100644 index 6d8573bd..00000000 --- a/examples/pane_grid/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "pane_grid" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2018" -publish = false - -[dependencies] -iced = { path = "../..", features = ["async-std"] } -iced_native = { path = "../../native" } -clock = { path = "../clock" } -stopwatch = { path = "../stopwatch" } diff --git a/examples/pane_grid/README.md b/examples/pane_grid/README.md deleted file mode 100644 index 4d9fc5b9..00000000 --- a/examples/pane_grid/README.md +++ /dev/null @@ -1,18 +0,0 @@ -## Counter - -The classic counter example explained in the [`README`](../../README.md). - -The __[`main`]__ file contains all the code of the example. - - - -You can run it with `cargo run`: -``` -cargo run --package counter -``` - -[`main`]: src/main.rs diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs deleted file mode 100644 index eb8aaa51..00000000 --- a/examples/pane_grid/src/main.rs +++ /dev/null @@ -1,179 +0,0 @@ -use iced::{ - pane_grid, Application, Command, Element, PaneGrid, Settings, Subscription, -}; -use iced_native::input::keyboard; - -use clock::{self, Clock}; -use stopwatch::{self, Stopwatch}; - -pub fn main() { - Launcher::run(Settings { - antialiasing: true, - ..Settings::default() - }) -} - -#[derive(Debug)] -struct Launcher { - panes: pane_grid::State, -} - -#[derive(Debug)] -enum Example { - Clock(Clock), - Stopwatch(Stopwatch), -} - -#[derive(Debug, Clone)] -enum Message { - Clock(pane_grid::Pane, clock::Message), - Stopwatch(pane_grid::Pane, stopwatch::Message), - Split(pane_grid::Split), - Close, -} - -impl Application for Launcher { - type Executor = iced::executor::Default; - type Message = Message; - - fn new() -> (Self, Command) { - let (clock, _) = Clock::new(); - let (panes, _) = pane_grid::State::new(Example::Clock(clock)); - - (Self { panes }, Command::none()) - } - - fn title(&self) -> String { - String::from("Panes - Iced") - } - - fn update(&mut self, message: Message) -> Command { - match message { - Message::Clock(pane, message) => { - if let Some(Example::Clock(clock)) = self.panes.get_mut(&pane) { - let _ = clock.update(message); - } - } - Message::Stopwatch(pane, message) => { - if let Some(Example::Stopwatch(stopwatch)) = - self.panes.get_mut(&pane) - { - let _ = stopwatch.update(message); - } - } - Message::Split(kind) => { - if let Some(pane) = self.panes.focused_pane() { - let state = if pane.index() % 2 == 0 { - let (stopwatch, _) = Stopwatch::new(); - - Example::Stopwatch(stopwatch) - } else { - let (clock, _) = Clock::new(); - - Example::Clock(clock) - }; - - self.panes.split(kind, &pane, state); - } - } - Message::Close => { - if let Some(pane) = self.panes.focused_pane() { - self.panes.close(&pane); - } - } - } - - Command::none() - } - - fn subscription(&self) -> Subscription { - let panes_subscriptions = - Subscription::batch(self.panes.iter().map(|(pane, example)| { - match example { - Example::Clock(clock) => clock - .subscription() - .with(pane) - .map(|(pane, message)| Message::Clock(pane, message)), - - Example::Stopwatch(stopwatch) => { - stopwatch.subscription().with(pane).map( - |(pane, message)| Message::Stopwatch(pane, message), - ) - } - } - })); - - Subscription::batch(vec![ - events::key_released(keyboard::KeyCode::H) - .map(|_| Message::Split(pane_grid::Split::Horizontal)), - events::key_released(keyboard::KeyCode::V) - .map(|_| Message::Split(pane_grid::Split::Vertical)), - events::key_released(keyboard::KeyCode::Q).map(|_| Message::Close), - panes_subscriptions, - ]) - } - - fn view(&mut self) -> Element { - let Self { panes } = self; - - PaneGrid::new(panes, |pane, example, _| match example { - Example::Clock(clock) => clock - .view() - .map(move |message| Message::Clock(pane, message)), - - Example::Stopwatch(stopwatch) => stopwatch - .view() - .map(move |message| Message::Stopwatch(pane, message)), - }) - .into() - } -} - -mod events { - use iced_native::{ - futures::{ - self, - stream::{BoxStream, StreamExt}, - }, - input::{keyboard, ButtonState}, - subscription, Event, Hasher, Subscription, - }; - - pub fn key_released(key_code: keyboard::KeyCode) -> Subscription<()> { - Subscription::from_recipe(KeyReleased { key_code }) - } - - struct KeyReleased { - key_code: keyboard::KeyCode, - } - - impl subscription::Recipe for KeyReleased { - type Output = (); - - fn hash(&self, state: &mut Hasher) { - use std::hash::Hash; - - std::any::TypeId::of::().hash(state); - self.key_code.hash(state); - } - - fn stream( - self: Box, - events: subscription::EventStream, - ) -> BoxStream<'static, Self::Output> { - events - .filter(move |event| match event { - Event::Keyboard(keyboard::Event::Input { - key_code, - state: ButtonState::Released, - .. - }) if *key_code == self.key_code => { - futures::future::ready(true) - } - _ => futures::future::ready(false), - }) - .map(|_| ()) - .boxed() - } - } -} diff --git a/examples/stopwatch/src/lib.rs b/examples/stopwatch/src/lib.rs deleted file mode 100644 index 0219470b..00000000 --- a/examples/stopwatch/src/lib.rs +++ /dev/null @@ -1,204 +0,0 @@ -use iced::{ - button, Align, Application, Button, Column, Command, Container, Element, - HorizontalAlignment, Length, Row, Subscription, Text, -}; -use std::time::{Duration, Instant}; - -#[derive(Debug)] -pub struct Stopwatch { - duration: Duration, - state: State, - toggle: button::State, - reset: button::State, -} - -#[derive(Debug)] -enum State { - Idle, - Ticking { last_tick: Instant }, -} - -#[derive(Debug, Clone)] -pub enum Message { - Toggle, - Reset, - Tick(Instant), -} - -impl Application for Stopwatch { - type Executor = iced_futures::executor::AsyncStd; - type Message = Message; - - fn new() -> (Stopwatch, Command) { - ( - Stopwatch { - duration: Duration::default(), - state: State::Idle, - toggle: button::State::new(), - reset: button::State::new(), - }, - Command::none(), - ) - } - - fn title(&self) -> String { - String::from("Stopwatch - Iced") - } - - fn update(&mut self, message: Message) -> Command { - match message { - Message::Toggle => match self.state { - State::Idle => { - self.state = State::Ticking { - last_tick: Instant::now(), - }; - } - State::Ticking { .. } => { - self.state = State::Idle; - } - }, - Message::Tick(now) => match &mut self.state { - State::Ticking { last_tick } => { - self.duration += now - *last_tick; - *last_tick = now; - } - _ => {} - }, - Message::Reset => { - self.duration = Duration::default(); - } - } - - Command::none() - } - - fn subscription(&self) -> Subscription { - match self.state { - State::Idle => Subscription::none(), - State::Ticking { .. } => { - time::every(Duration::from_millis(10)).map(Message::Tick) - } - } - } - - fn view(&mut self) -> Element { - const MINUTE: u64 = 60; - const HOUR: u64 = 60 * MINUTE; - - let seconds = self.duration.as_secs(); - - let duration = Text::new(format!( - "{:0>2}:{:0>2}:{:0>2}.{:0>2}", - seconds / HOUR, - (seconds % HOUR) / MINUTE, - seconds % MINUTE, - self.duration.subsec_millis() / 10, - )) - .size(40); - - let button = |state, label, style| { - Button::new( - state, - Text::new(label) - .horizontal_alignment(HorizontalAlignment::Center), - ) - .min_width(80) - .padding(10) - .style(style) - }; - - let toggle_button = { - let (label, color) = match self.state { - State::Idle => ("Start", style::Button::Primary), - State::Ticking { .. } => ("Stop", style::Button::Destructive), - }; - - button(&mut self.toggle, label, color).on_press(Message::Toggle) - }; - - let reset_button = - button(&mut self.reset, "Reset", style::Button::Secondary) - .on_press(Message::Reset); - - let controls = Row::new() - .spacing(20) - .push(toggle_button) - .push(reset_button); - - let content = Column::new() - .align_items(Align::Center) - .spacing(20) - .push(duration) - .push(controls); - - Container::new(content) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .into() - } -} - -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 = std::time::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(|_| std::time::Instant::now()) - .boxed() - } - } -} - -mod style { - use iced::{button, Background, Color, Vector}; - - pub enum Button { - Primary, - Secondary, - Destructive, - } - - impl button::StyleSheet for Button { - fn active(&self) -> button::Style { - button::Style { - background: Some(Background::Color(match self { - Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), - Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), - Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2), - })), - border_radius: 12, - shadow_offset: Vector::new(1.0, 1.0), - text_color: Color::WHITE, - ..button::Style::default() - } - } - } -} diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index adcdffe4..d84c4817 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -1,6 +1,206 @@ -use iced::{Application, Settings}; -use stopwatch::Stopwatch; +use iced::{ + button, Align, Application, Button, Column, Command, Container, Element, + HorizontalAlignment, Length, Row, Settings, Subscription, Text, +}; +use std::time::{Duration, Instant}; pub fn main() { Stopwatch::run(Settings::default()) } + +struct Stopwatch { + duration: Duration, + state: State, + toggle: button::State, + reset: button::State, +} + +enum State { + Idle, + Ticking { last_tick: Instant }, +} + +#[derive(Debug, Clone)] +enum Message { + Toggle, + Reset, + Tick(Instant), +} + +impl Application for Stopwatch { + type Executor = iced_futures::executor::AsyncStd; + type Message = Message; + + fn new() -> (Stopwatch, Command) { + ( + Stopwatch { + duration: Duration::default(), + state: State::Idle, + toggle: button::State::new(), + reset: button::State::new(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Stopwatch - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Toggle => match self.state { + State::Idle => { + self.state = State::Ticking { + last_tick: Instant::now(), + }; + } + State::Ticking { .. } => { + self.state = State::Idle; + } + }, + Message::Tick(now) => match &mut self.state { + State::Ticking { last_tick } => { + self.duration += now - *last_tick; + *last_tick = now; + } + _ => {} + }, + Message::Reset => { + self.duration = Duration::default(); + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription { + match self.state { + State::Idle => Subscription::none(), + State::Ticking { .. } => { + time::every(Duration::from_millis(10)).map(Message::Tick) + } + } + } + + fn view(&mut self) -> Element { + const MINUTE: u64 = 60; + const HOUR: u64 = 60 * MINUTE; + + let seconds = self.duration.as_secs(); + + let duration = Text::new(format!( + "{:0>2}:{:0>2}:{:0>2}.{:0>2}", + seconds / HOUR, + (seconds % HOUR) / MINUTE, + seconds % MINUTE, + self.duration.subsec_millis() / 10, + )) + .size(40); + + let button = |state, label, style| { + Button::new( + state, + Text::new(label) + .horizontal_alignment(HorizontalAlignment::Center), + ) + .min_width(80) + .padding(10) + .style(style) + }; + + let toggle_button = { + let (label, color) = match self.state { + State::Idle => ("Start", style::Button::Primary), + State::Ticking { .. } => ("Stop", style::Button::Destructive), + }; + + button(&mut self.toggle, label, color).on_press(Message::Toggle) + }; + + let reset_button = + button(&mut self.reset, "Reset", style::Button::Secondary) + .on_press(Message::Reset); + + let controls = Row::new() + .spacing(20) + .push(toggle_button) + .push(reset_button); + + let content = Column::new() + .align_items(Align::Center) + .spacing(20) + .push(duration) + .push(controls); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +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 = std::time::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(|_| std::time::Instant::now()) + .boxed() + } + } +} + +mod style { + use iced::{button, Background, Color, Vector}; + + pub enum Button { + Primary, + Secondary, + Destructive, + } + + impl button::StyleSheet for Button { + fn active(&self) -> button::Style { + button::Style { + background: Some(Background::Color(match self { + Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), + Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), + Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2), + })), + border_radius: 12, + shadow_offset: Vector::new(1.0, 1.0), + text_color: Color::WHITE, + ..button::Style::default() + } + } + } +} -- cgit From 6e8585e88c89c9e3e18e49765d4d954000bd4674 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Mar 2020 04:06:32 +0100 Subject: Expose `adjacent_pane` instead of `focus_adjacent` --- native/src/widget/pane_grid.rs | 70 ++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 1ba1b417..ddbc7bfd 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -321,8 +321,8 @@ enum FocusedPane { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Direction { - Top, - Bottom, + Up, + Down, Left, Right, } @@ -378,42 +378,46 @@ impl State { } } - pub fn focus(&mut self, pane: &Pane) { - self.internal.focused_pane = FocusedPane::Some { - pane: *pane, - focus: Focus::Idle, - }; - } - - pub fn focus_adjacent(&mut self, pane: &Pane, direction: Direction) { + pub fn adjacent_pane( + &self, + pane: &Pane, + direction: Direction, + ) -> Option { let regions = self.internal.layout.regions(0.0, Size::new(4096.0, 4096.0)); - if let Some(current_region) = regions.get(pane) { - let target = match direction { - Direction::Left => { - Point::new(current_region.x - 1.0, current_region.y + 1.0) - } - Direction::Right => Point::new( - current_region.x + current_region.width + 1.0, - current_region.y + 1.0, - ), - Direction::Top => { - Point::new(current_region.x + 1.0, current_region.y - 1.0) - } - Direction::Bottom => Point::new( - current_region.x + 1.0, - current_region.y + current_region.height + 1.0, - ), - }; - - let mut colliding_regions = - regions.iter().filter(|(_, region)| region.contains(target)); + let current_region = regions.get(pane)?; - if let Some((pane, _)) = colliding_regions.next() { - self.focus(&pane); + let target = match direction { + Direction::Left => { + Point::new(current_region.x - 1.0, current_region.y + 1.0) } - } + Direction::Right => Point::new( + current_region.x + current_region.width + 1.0, + current_region.y + 1.0, + ), + Direction::Up => { + Point::new(current_region.x + 1.0, current_region.y - 1.0) + } + Direction::Down => Point::new( + current_region.x + 1.0, + current_region.y + current_region.height + 1.0, + ), + }; + + let mut colliding_regions = + regions.iter().filter(|(_, region)| region.contains(target)); + + let (pane, _) = colliding_regions.next()?; + + Some(*pane) + } + + pub fn focus(&mut self, pane: &Pane) { + self.internal.focused_pane = FocusedPane::Some { + pane: *pane, + focus: Focus::Idle, + }; } pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { -- cgit From 2459648574abc11161390b24d8b0b621b5d39ab9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Mar 2020 04:47:14 +0100 Subject: Simplify `iter` and `iter_mut` in `pane_grid` --- native/src/widget/pane_grid.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index ddbc7bfd..a594832a 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -356,12 +356,12 @@ impl State { self.panes.get_mut(pane) } - pub fn iter(&self) -> impl Iterator { - self.panes.iter().map(|(pane, state)| (*pane, state)) + pub fn iter(&self) -> impl Iterator { + self.panes.iter() } - pub fn iter_mut(&mut self) -> impl Iterator { - self.panes.iter_mut().map(|(pane, state)| (*pane, state)) + pub fn iter_mut(&mut self) -> impl Iterator { + self.panes.iter_mut() } pub fn focused_pane(&self) -> Option { -- cgit From 460565056e6787d5cc7cb0d1d49c9e8ff1f77850 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Mar 2020 04:53:57 +0100 Subject: Reuse `PaneGrid::focus` to remove some duplication --- native/src/widget/pane_grid.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index a594832a..32dc4236 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -449,10 +449,7 @@ impl State { node.split(kind, new_pane); let _ = self.panes.insert(new_pane, state); - self.internal.focused_pane = FocusedPane::Some { - pane: new_pane, - focus: Focus::Idle, - }; + self.focus(&new_pane); Some(new_pane) } @@ -472,11 +469,7 @@ impl State { pub fn close(&mut self, pane: &Pane) -> Option { if let Some(sibling) = self.internal.layout.remove(pane) { - self.internal.focused_pane = FocusedPane::Some { - pane: sibling, - focus: Focus::Idle, - }; - + self.focus(&sibling); self.panes.remove(pane) } else { None -- cgit From 5c8ec4504b6541cdc588b91a6b2c7100b4a7cc77 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Mar 2020 05:26:59 +0100 Subject: Create module boundaries for `pane_grid` logic --- native/src/widget/pane_grid.rs | 442 +++---------------------------- native/src/widget/pane_grid/direction.rs | 7 + native/src/widget/pane_grid/node.rs | 128 +++++++++ native/src/widget/pane_grid/pane.rs | 2 + native/src/widget/pane_grid/split.rs | 54 ++++ native/src/widget/pane_grid/state.rs | 227 ++++++++++++++++ 6 files changed, 448 insertions(+), 412 deletions(-) create mode 100644 native/src/widget/pane_grid/direction.rs create mode 100644 native/src/widget/pane_grid/node.rs create mode 100644 native/src/widget/pane_grid/pane.rs create mode 100644 native/src/widget/pane_grid/split.rs create mode 100644 native/src/widget/pane_grid/state.rs diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 32dc4236..2272d32c 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -1,14 +1,24 @@ +mod direction; +mod node; +mod pane; +mod split; +mod state; + +pub use direction::Direction; +pub use pane::Pane; +pub use split::Split; +pub use state::{Focus, State}; + use crate::{ input::{keyboard, mouse, ButtonState}, - layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Rectangle, Size, Widget, + layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Size, + Widget, }; -use std::collections::HashMap; - #[allow(missing_debug_implementations)] pub struct PaneGrid<'a, Message, Renderer> { - state: &'a mut Internal, + state: &'a mut state::Internal, + modifiers: &'a mut keyboard::ModifiersState, elements: Vec<(Pane, Element<'a, Message, Renderer>)>, width: Length, height: Length, @@ -26,14 +36,14 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { ) -> Element<'a, Message, Renderer>, ) -> Self { let elements = { - let focused_pane = state.internal.focused_pane; + let focused_pane = state.internal.focused(); state .panes .iter_mut() .map(move |(pane, pane_state)| { let focus = match focused_pane { - FocusedPane::Some { + state::FocusedPane::Some { pane: focused_pane, focus, } if *pane == focused_pane => Some(focus), @@ -47,6 +57,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { Self { state: &mut state.internal, + modifiers: &mut state.modifiers, elements, width: Length::Fill, height: Length::Fill, @@ -117,7 +128,7 @@ where let limits = limits.width(self.width).height(self.height); let size = limits.resolve(Size::ZERO); - let regions = self.state.layout.regions(f32::from(self.spacing), size); + let regions = self.state.regions(f32::from(self.spacing), size); let children = self .elements @@ -162,37 +173,24 @@ where if let Some(((pane, _), _)) = clicked_region.next() { match &self.on_drag { - Some(on_drag) if self.state.modifiers.alt => { - self.state.focused_pane = FocusedPane::Some { - pane: *pane, - focus: Focus::Dragging, - }; + Some(on_drag) if self.modifiers.alt => { + self.state.drag(pane); messages.push(on_drag(DragEvent::Picked { pane: *pane, })); } _ => { - self.state.focused_pane = FocusedPane::Some { - pane: *pane, - focus: Focus::Idle, - }; + self.state.focus(pane); } } } else { - self.state.focused_pane = FocusedPane::None; + self.state.unfocus(); } } ButtonState::Released => { - if let FocusedPane::Some { - pane, - focus: Focus::Dragging, - } = self.state.focused_pane - { - self.state.focused_pane = FocusedPane::Some { - pane, - focus: Focus::Idle, - }; + if let Some(pane) = self.state.dragged() { + self.state.focus(&pane); if let Some(on_drag) = &self.on_drag { let mut dropped_region = self @@ -219,17 +217,13 @@ where } }, Event::Keyboard(keyboard::Event::Input { modifiers, .. }) => { - self.state.modifiers = modifiers; + *self.modifiers = modifiers; } _ => {} } - match self.state.focused_pane { - FocusedPane::Some { - focus: Focus::Dragging, - .. - } => {} - _ => { + if self.state.dragged().is_none() { + { self.elements.iter_mut().zip(layout.children()).for_each( |((_, pane), layout)| { pane.widget.on_event( @@ -253,18 +247,10 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - let dragging = match self.state.focused_pane { - FocusedPane::Some { - pane, - focus: Focus::Dragging, - } => Some(pane), - _ => None, - }; - renderer.draw( defaults, &self.elements, - dragging, + self.state.dragged(), layout, cursor_position, ) @@ -276,7 +262,7 @@ where std::any::TypeId::of::>().hash(state); self.width.hash(state); self.height.hash(state); - self.state.layout.hash(state); + self.state.hash_layout(state); for (_, element) in &self.elements { element.hash_layout(state); @@ -284,374 +270,6 @@ where } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Pane(usize); - -impl Pane { - pub fn index(&self) -> usize { - self.0 - } -} - -#[derive(Debug)] -pub struct State { - panes: HashMap, - internal: Internal, -} - -#[derive(Debug)] -struct Internal { - layout: Node, - last_pane: usize, - focused_pane: FocusedPane, - modifiers: keyboard::ModifiersState, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Focus { - Idle, - Dragging, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum FocusedPane { - None, - Some { pane: Pane, focus: Focus }, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Direction { - Up, - Down, - Left, - Right, -} - -impl State { - pub fn new(first_pane_state: T) -> (Self, Pane) { - let first_pane = Pane(0); - - let mut panes = HashMap::new(); - let _ = panes.insert(first_pane, first_pane_state); - - ( - State { - panes, - internal: Internal { - layout: Node::Pane(first_pane), - last_pane: 0, - focused_pane: FocusedPane::None, - modifiers: keyboard::ModifiersState::default(), - }, - }, - first_pane, - ) - } - - pub fn len(&self) -> usize { - self.panes.len() - } - - pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> { - self.panes.get_mut(pane) - } - - pub fn iter(&self) -> impl Iterator { - self.panes.iter() - } - - pub fn iter_mut(&mut self) -> impl Iterator { - self.panes.iter_mut() - } - - pub fn focused_pane(&self) -> Option { - match self.internal.focused_pane { - FocusedPane::Some { - pane, - focus: Focus::Idle, - } => Some(pane), - FocusedPane::Some { - focus: Focus::Dragging, - .. - } => None, - FocusedPane::None => None, - } - } - - pub fn adjacent_pane( - &self, - pane: &Pane, - direction: Direction, - ) -> Option { - let regions = - self.internal.layout.regions(0.0, Size::new(4096.0, 4096.0)); - - let current_region = regions.get(pane)?; - - let target = match direction { - Direction::Left => { - Point::new(current_region.x - 1.0, current_region.y + 1.0) - } - Direction::Right => Point::new( - current_region.x + current_region.width + 1.0, - current_region.y + 1.0, - ), - Direction::Up => { - Point::new(current_region.x + 1.0, current_region.y - 1.0) - } - Direction::Down => Point::new( - current_region.x + 1.0, - current_region.y + current_region.height + 1.0, - ), - }; - - let mut colliding_regions = - regions.iter().filter(|(_, region)| region.contains(target)); - - let (pane, _) = colliding_regions.next()?; - - Some(*pane) - } - - pub fn focus(&mut self, pane: &Pane) { - self.internal.focused_pane = FocusedPane::Some { - pane: *pane, - focus: Focus::Idle, - }; - } - - pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { - self.split(Split::Vertical, pane, state) - } - - pub fn split_horizontally( - &mut self, - pane: &Pane, - state: T, - ) -> Option { - self.split(Split::Horizontal, pane, state) - } - - pub fn split( - &mut self, - kind: Split, - pane: &Pane, - state: T, - ) -> Option { - let node = self.internal.layout.find(pane)?; - - let new_pane = { - self.internal.last_pane = self.internal.last_pane.checked_add(1)?; - - Pane(self.internal.last_pane) - }; - - node.split(kind, new_pane); - - let _ = self.panes.insert(new_pane, state); - self.focus(&new_pane); - - Some(new_pane) - } - - pub fn swap(&mut self, a: &Pane, b: &Pane) { - self.internal.layout.update(&|node| match node { - Node::Split { .. } => {} - Node::Pane(pane) => { - if pane == a { - *node = Node::Pane(*b); - } else if pane == b { - *node = Node::Pane(*a); - } - } - }); - } - - pub fn close(&mut self, pane: &Pane) -> Option { - if let Some(sibling) = self.internal.layout.remove(pane) { - self.focus(&sibling); - self.panes.remove(pane) - } else { - None - } - } -} - -#[derive(Debug, Clone, Hash)] -enum Node { - Split { - kind: Split, - ratio: u32, - a: Box, - b: Box, - }, - Pane(Pane), -} - -impl Node { - fn find(&mut self, pane: &Pane) -> Option<&mut Node> { - match self { - Node::Split { a, b, .. } => { - a.find(pane).or_else(move || b.find(pane)) - } - Node::Pane(p) => { - if p == pane { - Some(self) - } else { - None - } - } - } - } - - fn split(&mut self, kind: Split, new_pane: Pane) { - *self = Node::Split { - kind, - ratio: 500_000, - a: Box::new(self.clone()), - b: Box::new(Node::Pane(new_pane)), - }; - } - - fn update(&mut self, f: &impl Fn(&mut Node)) { - match self { - Node::Split { a, b, .. } => { - a.update(f); - b.update(f); - } - _ => {} - } - - f(self); - } - - fn remove(&mut self, pane: &Pane) -> Option { - match self { - Node::Split { a, b, .. } => { - if a.pane() == Some(*pane) { - *self = *b.clone(); - Some(self.first_pane()) - } else if b.pane() == Some(*pane) { - *self = *a.clone(); - Some(self.first_pane()) - } else { - a.remove(pane).or_else(|| b.remove(pane)) - } - } - Node::Pane(_) => None, - } - } - - pub fn regions( - &self, - spacing: f32, - size: Size, - ) -> HashMap { - let mut regions = HashMap::new(); - - self.compute_regions( - spacing / 2.0, - &Rectangle { - x: 0.0, - y: 0.0, - width: size.width, - height: size.height, - }, - &mut regions, - ); - - regions - } - - fn pane(&self) -> Option { - match self { - Node::Split { .. } => None, - Node::Pane(pane) => Some(*pane), - } - } - - fn first_pane(&self) -> Pane { - match self { - Node::Split { a, .. } => a.first_pane(), - Node::Pane(pane) => *pane, - } - } - - fn compute_regions( - &self, - halved_spacing: f32, - current: &Rectangle, - regions: &mut HashMap, - ) { - match self { - Node::Split { kind, ratio, a, b } => { - let ratio = *ratio as f32 / 1_000_000.0; - let (region_a, region_b) = - kind.apply(current, ratio, halved_spacing); - - a.compute_regions(halved_spacing, ®ion_a, regions); - b.compute_regions(halved_spacing, ®ion_b, regions); - } - Node::Pane(pane) => { - let _ = regions.insert(*pane, *current); - } - } - } -} - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub enum Split { - Horizontal, - Vertical, -} - -impl Split { - fn apply( - &self, - rectangle: &Rectangle, - ratio: f32, - halved_spacing: f32, - ) -> (Rectangle, Rectangle) { - match self { - Split::Horizontal => { - let width_left = - (rectangle.width * ratio).round() - halved_spacing; - let width_right = rectangle.width - width_left - halved_spacing; - - ( - Rectangle { - width: width_left, - ..*rectangle - }, - Rectangle { - x: rectangle.x + width_left + halved_spacing, - width: width_right, - ..*rectangle - }, - ) - } - Split::Vertical => { - let height_top = - (rectangle.height * ratio).round() - halved_spacing; - let height_bottom = - rectangle.height - height_top - halved_spacing; - - ( - Rectangle { - height: height_top, - ..*rectangle - }, - Rectangle { - y: rectangle.y + height_top + halved_spacing, - height: height_bottom, - ..*rectangle - }, - ) - } - } - } -} - /// The renderer of a [`PaneGrid`]. /// /// Your [renderer] will need to implement this trait before being diff --git a/native/src/widget/pane_grid/direction.rs b/native/src/widget/pane_grid/direction.rs new file mode 100644 index 00000000..0ee90557 --- /dev/null +++ b/native/src/widget/pane_grid/direction.rs @@ -0,0 +1,7 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Direction { + Up, + Down, + Left, + Right, +} diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs new file mode 100644 index 00000000..a9aa7fdc --- /dev/null +++ b/native/src/widget/pane_grid/node.rs @@ -0,0 +1,128 @@ +use crate::{ + pane_grid::{Pane, Split}, + Rectangle, Size, +}; + +use std::collections::HashMap; + +#[derive(Debug, Clone, Hash)] +pub enum Node { + Split { + kind: Split, + ratio: u32, + a: Box, + b: Box, + }, + Pane(Pane), +} + +impl Node { + pub fn find(&mut self, pane: &Pane) -> Option<&mut Node> { + match self { + Node::Split { a, b, .. } => { + a.find(pane).or_else(move || b.find(pane)) + } + Node::Pane(p) => { + if p == pane { + Some(self) + } else { + None + } + } + } + } + + pub fn split(&mut self, kind: Split, new_pane: Pane) { + *self = Node::Split { + kind, + ratio: 500_000, + a: Box::new(self.clone()), + b: Box::new(Node::Pane(new_pane)), + }; + } + + pub fn update(&mut self, f: &impl Fn(&mut Node)) { + match self { + Node::Split { a, b, .. } => { + a.update(f); + b.update(f); + } + _ => {} + } + + f(self); + } + + pub fn remove(&mut self, pane: &Pane) -> Option { + match self { + Node::Split { a, b, .. } => { + if a.pane() == Some(*pane) { + *self = *b.clone(); + Some(self.first_pane()) + } else if b.pane() == Some(*pane) { + *self = *a.clone(); + Some(self.first_pane()) + } else { + a.remove(pane).or_else(|| b.remove(pane)) + } + } + Node::Pane(_) => None, + } + } + + pub fn regions( + &self, + spacing: f32, + size: Size, + ) -> HashMap { + let mut regions = HashMap::new(); + + self.compute_regions( + spacing / 2.0, + &Rectangle { + x: 0.0, + y: 0.0, + width: size.width, + height: size.height, + }, + &mut regions, + ); + + regions + } + + pub fn pane(&self) -> Option { + match self { + Node::Split { .. } => None, + Node::Pane(pane) => Some(*pane), + } + } + + pub fn first_pane(&self) -> Pane { + match self { + Node::Split { a, .. } => a.first_pane(), + Node::Pane(pane) => *pane, + } + } + + fn compute_regions( + &self, + halved_spacing: f32, + current: &Rectangle, + regions: &mut HashMap, + ) { + match self { + Node::Split { kind, ratio, a, b } => { + let ratio = *ratio as f32 / 1_000_000.0; + let (region_a, region_b) = + kind.apply(current, ratio, halved_spacing); + + a.compute_regions(halved_spacing, ®ion_a, regions); + b.compute_regions(halved_spacing, ®ion_b, regions); + } + Node::Pane(pane) => { + let _ = regions.insert(*pane, *current); + } + } + } +} diff --git a/native/src/widget/pane_grid/pane.rs b/native/src/widget/pane_grid/pane.rs new file mode 100644 index 00000000..cfca3b03 --- /dev/null +++ b/native/src/widget/pane_grid/pane.rs @@ -0,0 +1,2 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Pane(pub(super) usize); diff --git a/native/src/widget/pane_grid/split.rs b/native/src/widget/pane_grid/split.rs new file mode 100644 index 00000000..ca9ed5e1 --- /dev/null +++ b/native/src/widget/pane_grid/split.rs @@ -0,0 +1,54 @@ +use crate::Rectangle; + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub enum Split { + Horizontal, + Vertical, +} + +impl Split { + pub(super) fn apply( + &self, + rectangle: &Rectangle, + ratio: f32, + halved_spacing: f32, + ) -> (Rectangle, Rectangle) { + match self { + Split::Horizontal => { + let width_left = + (rectangle.width * ratio).round() - halved_spacing; + let width_right = rectangle.width - width_left - halved_spacing; + + ( + Rectangle { + width: width_left, + ..*rectangle + }, + Rectangle { + x: rectangle.x + width_left + halved_spacing, + width: width_right, + ..*rectangle + }, + ) + } + Split::Vertical => { + let height_top = + (rectangle.height * ratio).round() - halved_spacing; + let height_bottom = + rectangle.height - height_top - halved_spacing; + + ( + Rectangle { + height: height_top, + ..*rectangle + }, + Rectangle { + y: rectangle.y + height_top + halved_spacing, + height: height_bottom, + ..*rectangle + }, + ) + } + } + } +} diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs new file mode 100644 index 00000000..130c7e34 --- /dev/null +++ b/native/src/widget/pane_grid/state.rs @@ -0,0 +1,227 @@ +use crate::{ + input::keyboard, + pane_grid::{node::Node, Direction, Pane, Split}, + Hasher, Point, Rectangle, Size, +}; + +use std::collections::HashMap; + +#[derive(Debug)] +pub struct State { + pub(super) panes: HashMap, + pub(super) internal: Internal, + pub(super) modifiers: keyboard::ModifiersState, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Focus { + Idle, + Dragging, +} + +impl State { + pub fn new(first_pane_state: T) -> (Self, Pane) { + let first_pane = Pane(0); + + let mut panes = HashMap::new(); + let _ = panes.insert(first_pane, first_pane_state); + + ( + State { + panes, + internal: Internal { + layout: Node::Pane(first_pane), + last_pane: 0, + focused_pane: FocusedPane::None, + }, + modifiers: keyboard::ModifiersState::default(), + }, + first_pane, + ) + } + + pub fn len(&self) -> usize { + self.panes.len() + } + + pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> { + self.panes.get_mut(pane) + } + + pub fn iter(&self) -> impl Iterator { + self.panes.iter() + } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.panes.iter_mut() + } + + pub fn focused_pane(&self) -> Option { + match self.internal.focused_pane { + FocusedPane::Some { + pane, + focus: Focus::Idle, + } => Some(pane), + FocusedPane::Some { + focus: Focus::Dragging, + .. + } => None, + FocusedPane::None => None, + } + } + + pub fn adjacent_pane( + &self, + pane: &Pane, + direction: Direction, + ) -> Option { + let regions = + self.internal.layout.regions(0.0, Size::new(4096.0, 4096.0)); + + let current_region = regions.get(pane)?; + + let target = match direction { + Direction::Left => { + Point::new(current_region.x - 1.0, current_region.y + 1.0) + } + Direction::Right => Point::new( + current_region.x + current_region.width + 1.0, + current_region.y + 1.0, + ), + Direction::Up => { + Point::new(current_region.x + 1.0, current_region.y - 1.0) + } + Direction::Down => Point::new( + current_region.x + 1.0, + current_region.y + current_region.height + 1.0, + ), + }; + + let mut colliding_regions = + regions.iter().filter(|(_, region)| region.contains(target)); + + let (pane, _) = colliding_regions.next()?; + + Some(*pane) + } + + pub fn focus(&mut self, pane: &Pane) { + self.internal.focus(pane); + } + + pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { + self.split(Split::Vertical, pane, state) + } + + pub fn split_horizontally( + &mut self, + pane: &Pane, + state: T, + ) -> Option { + self.split(Split::Horizontal, pane, state) + } + + pub fn split( + &mut self, + kind: Split, + pane: &Pane, + state: T, + ) -> Option { + let node = self.internal.layout.find(pane)?; + + let new_pane = { + self.internal.last_pane = self.internal.last_pane.checked_add(1)?; + + Pane(self.internal.last_pane) + }; + + node.split(kind, new_pane); + + let _ = self.panes.insert(new_pane, state); + self.focus(&new_pane); + + Some(new_pane) + } + + pub fn swap(&mut self, a: &Pane, b: &Pane) { + self.internal.layout.update(&|node| match node { + Node::Split { .. } => {} + Node::Pane(pane) => { + if pane == a { + *node = Node::Pane(*b); + } else if pane == b { + *node = Node::Pane(*a); + } + } + }); + } + + pub fn close(&mut self, pane: &Pane) -> Option { + if let Some(sibling) = self.internal.layout.remove(pane) { + self.focus(&sibling); + self.panes.remove(pane) + } else { + None + } + } +} + +#[derive(Debug)] +pub struct Internal { + layout: Node, + last_pane: usize, + focused_pane: FocusedPane, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FocusedPane { + None, + Some { pane: Pane, focus: Focus }, +} + +impl Internal { + pub fn focused(&self) -> FocusedPane { + self.focused_pane + } + pub fn dragged(&self) -> Option { + match self.focused_pane { + FocusedPane::Some { + pane, + focus: Focus::Dragging, + } => Some(pane), + _ => None, + } + } + + pub fn regions( + &self, + spacing: f32, + size: Size, + ) -> HashMap { + self.layout.regions(spacing, size) + } + + pub fn focus(&mut self, pane: &Pane) { + self.focused_pane = FocusedPane::Some { + pane: *pane, + focus: Focus::Idle, + }; + } + + pub fn drag(&mut self, pane: &Pane) { + self.focused_pane = FocusedPane::Some { + pane: *pane, + focus: Focus::Dragging, + }; + } + + pub fn unfocus(&mut self) { + self.focused_pane = FocusedPane::None; + } + + pub fn hash_layout(&self, hasher: &mut Hasher) { + use std::hash::Hash; + + self.layout.hash(hasher); + } +} -- cgit From 00c2b55b569ea2ff2fc9de9bbf02475c6ede7e42 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Mar 2020 06:26:09 +0100 Subject: Replace `FocusedPane` with `Action` in `pane_grid` --- native/src/widget/pane_grid.rs | 14 ++++--- native/src/widget/pane_grid/node.rs | 8 +++- native/src/widget/pane_grid/state.rs | 80 +++++++++++++++++------------------- 3 files changed, 52 insertions(+), 50 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 2272d32c..e446b235 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -36,17 +36,19 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { ) -> Element<'a, Message, Renderer>, ) -> Self { let elements = { - let focused_pane = state.internal.focused(); + let action = state.internal.action(); + let current_focus = action.focus(); state .panes .iter_mut() .map(move |(pane, pane_state)| { - let focus = match focused_pane { - state::FocusedPane::Some { - pane: focused_pane, - focus, - } if *pane == focused_pane => Some(focus), + let focus = match current_focus { + Some((focused_pane, focus)) + if *pane == focused_pane => + { + Some(focus) + } _ => None, }; diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs index a9aa7fdc..744e3e17 100644 --- a/native/src/widget/pane_grid/node.rs +++ b/native/src/widget/pane_grid/node.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; #[derive(Debug, Clone, Hash)] pub enum Node { Split { + id: usize, kind: Split, ratio: u32, a: Box, @@ -32,8 +33,9 @@ impl Node { } } - pub fn split(&mut self, kind: Split, new_pane: Pane) { + pub fn split(&mut self, id: usize, kind: Split, new_pane: Pane) { *self = Node::Split { + id, kind, ratio: 500_000, a: Box::new(self.clone()), @@ -112,7 +114,9 @@ impl Node { regions: &mut HashMap, ) { match self { - Node::Split { kind, ratio, a, b } => { + Node::Split { + kind, ratio, a, b, .. + } => { let ratio = *ratio as f32 / 1_000_000.0; let (region_a, region_b) = kind.apply(current, ratio, halved_spacing); diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 130c7e34..61576a29 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -31,8 +31,8 @@ impl State { panes, internal: Internal { layout: Node::Pane(first_pane), - last_pane: 0, - focused_pane: FocusedPane::None, + last_id: 0, + action: Action::Idle { focus: None }, }, modifiers: keyboard::ModifiersState::default(), }, @@ -56,25 +56,14 @@ impl State { self.panes.iter_mut() } - pub fn focused_pane(&self) -> Option { - match self.internal.focused_pane { - FocusedPane::Some { - pane, - focus: Focus::Idle, - } => Some(pane), - FocusedPane::Some { - focus: Focus::Dragging, - .. - } => None, - FocusedPane::None => None, + pub fn active(&self) -> Option { + match self.internal.action { + Action::Idle { focus } => focus, + _ => None, } } - pub fn adjacent_pane( - &self, - pane: &Pane, - direction: Direction, - ) -> Option { + pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option { let regions = self.internal.layout.regions(0.0, Size::new(4096.0, 4096.0)); @@ -130,12 +119,18 @@ impl State { let node = self.internal.layout.find(pane)?; let new_pane = { - self.internal.last_pane = self.internal.last_pane.checked_add(1)?; + self.internal.last_id = self.internal.last_id.checked_add(1)?; + + Pane(self.internal.last_id) + }; + + let split_id = { + self.internal.last_id = self.internal.last_id.checked_add(1)?; - Pane(self.internal.last_pane) + self.internal.last_id }; - node.split(kind, new_pane); + node.split(split_id, kind, new_pane); let _ = self.panes.insert(new_pane, state); self.focus(&new_pane); @@ -169,26 +164,33 @@ impl State { #[derive(Debug)] pub struct Internal { layout: Node, - last_pane: usize, - focused_pane: FocusedPane, + last_id: usize, + action: Action, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum FocusedPane { - None, - Some { pane: Pane, focus: Focus }, +pub enum Action { + Idle { focus: Option }, + Dragging { pane: Pane }, +} + +impl Action { + pub fn focus(&self) -> Option<(Pane, Focus)> { + match self { + Action::Idle { focus } => focus.map(|pane| (pane, Focus::Idle)), + Action::Dragging { pane } => Some((*pane, Focus::Dragging)), + } + } } impl Internal { - pub fn focused(&self) -> FocusedPane { - self.focused_pane + pub fn action(&self) -> Action { + self.action } + pub fn dragged(&self) -> Option { - match self.focused_pane { - FocusedPane::Some { - pane, - focus: Focus::Dragging, - } => Some(pane), + match self.action { + Action::Dragging { pane } => Some(pane), _ => None, } } @@ -202,21 +204,15 @@ impl Internal { } pub fn focus(&mut self, pane: &Pane) { - self.focused_pane = FocusedPane::Some { - pane: *pane, - focus: Focus::Idle, - }; + self.action = Action::Idle { focus: Some(*pane) }; } pub fn drag(&mut self, pane: &Pane) { - self.focused_pane = FocusedPane::Some { - pane: *pane, - focus: Focus::Dragging, - }; + self.action = Action::Dragging { pane: *pane }; } pub fn unfocus(&mut self) { - self.focused_pane = FocusedPane::None; + self.action = Action::Idle { focus: None }; } pub fn hash_layout(&self, hasher: &mut Hasher) { -- cgit From a79603e4ca5e0cee46a737ef0b1af5c69ddb49b6 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Mar 2020 06:32:56 +0100 Subject: Rename `Split` to `Axis` --- native/src/widget/pane_grid.rs | 4 +-- native/src/widget/pane_grid/axis.rs | 54 ++++++++++++++++++++++++++++++++++++ native/src/widget/pane_grid/node.rs | 12 ++++---- native/src/widget/pane_grid/split.rs | 54 ------------------------------------ native/src/widget/pane_grid/state.rs | 15 ++++------ 5 files changed, 67 insertions(+), 72 deletions(-) create mode 100644 native/src/widget/pane_grid/axis.rs delete mode 100644 native/src/widget/pane_grid/split.rs diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index e446b235..12d0b09d 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -1,12 +1,12 @@ +mod axis; mod direction; mod node; mod pane; -mod split; mod state; +pub use axis::Axis; pub use direction::Direction; pub use pane::Pane; -pub use split::Split; pub use state::{Focus, State}; use crate::{ diff --git a/native/src/widget/pane_grid/axis.rs b/native/src/widget/pane_grid/axis.rs new file mode 100644 index 00000000..375509b7 --- /dev/null +++ b/native/src/widget/pane_grid/axis.rs @@ -0,0 +1,54 @@ +use crate::Rectangle; + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub enum Axis { + Horizontal, + Vertical, +} + +impl Axis { + pub(super) fn split( + &self, + rectangle: &Rectangle, + ratio: f32, + halved_spacing: f32, + ) -> (Rectangle, Rectangle) { + match self { + Axis::Horizontal => { + let width_left = + (rectangle.width * ratio).round() - halved_spacing; + let width_right = rectangle.width - width_left - halved_spacing; + + ( + Rectangle { + width: width_left, + ..*rectangle + }, + Rectangle { + x: rectangle.x + width_left + halved_spacing, + width: width_right, + ..*rectangle + }, + ) + } + Axis::Vertical => { + let height_top = + (rectangle.height * ratio).round() - halved_spacing; + let height_bottom = + rectangle.height - height_top - halved_spacing; + + ( + Rectangle { + height: height_top, + ..*rectangle + }, + Rectangle { + y: rectangle.y + height_top + halved_spacing, + height: height_bottom, + ..*rectangle + }, + ) + } + } + } +} diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs index 744e3e17..aaf775d8 100644 --- a/native/src/widget/pane_grid/node.rs +++ b/native/src/widget/pane_grid/node.rs @@ -1,5 +1,5 @@ use crate::{ - pane_grid::{Pane, Split}, + pane_grid::{Axis, Pane}, Rectangle, Size, }; @@ -9,7 +9,7 @@ use std::collections::HashMap; pub enum Node { Split { id: usize, - kind: Split, + axis: Axis, ratio: u32, a: Box, b: Box, @@ -33,10 +33,10 @@ impl Node { } } - pub fn split(&mut self, id: usize, kind: Split, new_pane: Pane) { + pub fn split(&mut self, id: usize, axis: Axis, new_pane: Pane) { *self = Node::Split { id, - kind, + axis, ratio: 500_000, a: Box::new(self.clone()), b: Box::new(Node::Pane(new_pane)), @@ -115,11 +115,11 @@ impl Node { ) { match self { Node::Split { - kind, ratio, a, b, .. + axis, ratio, a, b, .. } => { let ratio = *ratio as f32 / 1_000_000.0; let (region_a, region_b) = - kind.apply(current, ratio, halved_spacing); + axis.split(current, ratio, halved_spacing); a.compute_regions(halved_spacing, ®ion_a, regions); b.compute_regions(halved_spacing, ®ion_b, regions); diff --git a/native/src/widget/pane_grid/split.rs b/native/src/widget/pane_grid/split.rs deleted file mode 100644 index ca9ed5e1..00000000 --- a/native/src/widget/pane_grid/split.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::Rectangle; - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub enum Split { - Horizontal, - Vertical, -} - -impl Split { - pub(super) fn apply( - &self, - rectangle: &Rectangle, - ratio: f32, - halved_spacing: f32, - ) -> (Rectangle, Rectangle) { - match self { - Split::Horizontal => { - let width_left = - (rectangle.width * ratio).round() - halved_spacing; - let width_right = rectangle.width - width_left - halved_spacing; - - ( - Rectangle { - width: width_left, - ..*rectangle - }, - Rectangle { - x: rectangle.x + width_left + halved_spacing, - width: width_right, - ..*rectangle - }, - ) - } - Split::Vertical => { - let height_top = - (rectangle.height * ratio).round() - halved_spacing; - let height_bottom = - rectangle.height - height_top - halved_spacing; - - ( - Rectangle { - height: height_top, - ..*rectangle - }, - Rectangle { - y: rectangle.y + height_top + halved_spacing, - height: height_bottom, - ..*rectangle - }, - ) - } - } - } -} diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 61576a29..dc66e32a 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -1,6 +1,6 @@ use crate::{ input::keyboard, - pane_grid::{node::Node, Direction, Pane, Split}, + pane_grid::{node::Node, Axis, Direction, Pane}, Hasher, Point, Rectangle, Size, }; @@ -99,7 +99,7 @@ impl State { } pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { - self.split(Split::Vertical, pane, state) + self.split(Axis::Vertical, pane, state) } pub fn split_horizontally( @@ -107,15 +107,10 @@ impl State { pane: &Pane, state: T, ) -> Option { - self.split(Split::Horizontal, pane, state) + self.split(Axis::Horizontal, pane, state) } - pub fn split( - &mut self, - kind: Split, - pane: &Pane, - state: T, - ) -> Option { + pub fn split(&mut self, axis: Axis, pane: &Pane, state: T) -> Option { let node = self.internal.layout.find(pane)?; let new_pane = { @@ -130,7 +125,7 @@ impl State { self.internal.last_id }; - node.split(split_id, kind, new_pane); + node.split(split_id, axis, new_pane); let _ = self.panes.insert(new_pane, state); self.focus(&new_pane); -- cgit From b55746b1e1d8a5ff759c86f52063fa6ce0c02a29 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Mar 2020 06:33:17 +0100 Subject: Remove `PaneGrid::split_*` helpers We can use the `split` method directly instead. --- native/src/widget/pane_grid/state.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index dc66e32a..f46252c7 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -98,18 +98,6 @@ impl State { self.internal.focus(pane); } - pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { - self.split(Axis::Vertical, pane, state) - } - - pub fn split_horizontally( - &mut self, - pane: &Pane, - state: T, - ) -> Option { - self.split(Axis::Horizontal, pane, state) - } - pub fn split(&mut self, axis: Axis, pane: &Pane, state: T) -> Option { let node = self.internal.layout.find(pane)?; -- cgit From db441a64b18487f3f64bb4f99192548d7fac6893 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Mar 2020 06:35:43 +0100 Subject: Reintroduce `pane_grid::Split` as an identifier --- native/src/widget/pane_grid.rs | 2 ++ native/src/widget/pane_grid/node.rs | 6 +++--- native/src/widget/pane_grid/split.rs | 2 ++ native/src/widget/pane_grid/state.rs | 8 ++++---- 4 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 native/src/widget/pane_grid/split.rs diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 12d0b09d..68f32bc0 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -2,11 +2,13 @@ mod axis; mod direction; mod node; mod pane; +mod split; mod state; pub use axis::Axis; pub use direction::Direction; pub use pane::Pane; +pub use split::Split; pub use state::{Focus, State}; use crate::{ diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs index aaf775d8..08046956 100644 --- a/native/src/widget/pane_grid/node.rs +++ b/native/src/widget/pane_grid/node.rs @@ -1,5 +1,5 @@ use crate::{ - pane_grid::{Axis, Pane}, + pane_grid::{Axis, Pane, Split}, Rectangle, Size, }; @@ -8,7 +8,7 @@ use std::collections::HashMap; #[derive(Debug, Clone, Hash)] pub enum Node { Split { - id: usize, + id: Split, axis: Axis, ratio: u32, a: Box, @@ -33,7 +33,7 @@ impl Node { } } - pub fn split(&mut self, id: usize, axis: Axis, new_pane: Pane) { + pub fn split(&mut self, id: Split, axis: Axis, new_pane: Pane) { *self = Node::Split { id, axis, diff --git a/native/src/widget/pane_grid/split.rs b/native/src/widget/pane_grid/split.rs new file mode 100644 index 00000000..c2dad980 --- /dev/null +++ b/native/src/widget/pane_grid/split.rs @@ -0,0 +1,2 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Split(pub(super) usize); diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index f46252c7..b0e571f0 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -1,6 +1,6 @@ use crate::{ input::keyboard, - pane_grid::{node::Node, Axis, Direction, Pane}, + pane_grid::{node::Node, Axis, Direction, Pane, Split}, Hasher, Point, Rectangle, Size, }; @@ -107,13 +107,13 @@ impl State { Pane(self.internal.last_id) }; - let split_id = { + let new_split = { self.internal.last_id = self.internal.last_id.checked_add(1)?; - self.internal.last_id + Split(self.internal.last_id) }; - node.split(split_id, axis, new_pane); + node.split(new_split, axis, new_pane); let _ = self.panes.insert(new_pane, state); self.focus(&new_pane); -- cgit From f08cb4ad565799689d07bacc190fbe0436a63648 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Mar 2020 08:10:50 +0100 Subject: Implement mouse-based pane resizing for `PaneGrid` --- core/src/point.rs | 10 +++ native/src/mouse_cursor.rs | 6 ++ native/src/widget/pane_grid.rs | 116 ++++++++++++++++++++++++++++++++-- native/src/widget/pane_grid/node.rs | 67 ++++++++++++++++++++ native/src/widget/pane_grid/state.rs | 65 +++++++++++++++++-- wgpu/src/renderer/widget/pane_grid.rs | 8 ++- winit/src/conversion.rs | 4 ++ 7 files changed, 265 insertions(+), 11 deletions(-) diff --git a/core/src/point.rs b/core/src/point.rs index b55f5099..b855cd91 100644 --- a/core/src/point.rs +++ b/core/src/point.rs @@ -22,6 +22,16 @@ impl Point { pub const fn new(x: f32, y: f32) -> Self { Self { x, y } } + + /// Computes the distance to another [`Point`]. + /// + /// [`Point`]: struct.Point.html + pub fn distance(&self, to: Point) -> f32 { + let a = self.x - to.x; + let b = self.y - to.y; + + f32::sqrt(a * a + b * b) + } } impl From<[f32; 2]> for Point { diff --git a/native/src/mouse_cursor.rs b/native/src/mouse_cursor.rs index c7297e0e..0dad3edc 100644 --- a/native/src/mouse_cursor.rs +++ b/native/src/mouse_cursor.rs @@ -21,6 +21,12 @@ pub enum MouseCursor { /// The cursor is over a text widget. Text, + + /// The cursor is resizing a widget horizontally. + ResizingHorizontally, + + /// The cursor is resizing a widget vertically. + ResizingVertically, } impl Default for MouseCursor { diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 68f32bc0..5229962d 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -14,7 +14,7 @@ pub use state::{Focus, State}; use crate::{ input::{keyboard, mouse, ButtonState}, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Size, - Widget, + Vector, Widget, }; #[allow(missing_debug_implementations)] @@ -26,6 +26,7 @@ pub struct PaneGrid<'a, Message, Renderer> { height: Length, spacing: u16, on_drag: Option Message>>, + on_resize: Option Message>>, } impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { @@ -67,6 +68,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { height: Length::Fill, spacing: 0, on_drag: None, + on_resize: None, } } @@ -101,6 +103,14 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self.on_drag = Some(Box::new(f)); self } + + pub fn on_resize( + mut self, + f: impl Fn(ResizeEvent) -> Message + 'static, + ) -> Self { + self.on_resize = Some(Box::new(f)); + self + } } #[derive(Debug, Clone, Copy)] @@ -110,6 +120,12 @@ pub enum DragEvent { Canceled { pane: Pane }, } +#[derive(Debug, Clone, Copy)] +pub struct ResizeEvent { + pub split: Split, + pub ratio: f32, +} + impl<'a, Message, Renderer> Widget for PaneGrid<'a, Message, Renderer> where @@ -178,7 +194,7 @@ where if let Some(((pane, _), _)) = clicked_region.next() { match &self.on_drag { Some(on_drag) if self.modifiers.alt => { - self.state.drag(pane); + self.state.pick_pane(pane); messages.push(on_drag(DragEvent::Picked { pane: *pane, @@ -193,7 +209,7 @@ where } } ButtonState::Released => { - if let Some(pane) = self.state.dragged() { + if let Some(pane) = self.state.picked_pane() { self.state.focus(&pane); if let Some(on_drag) = &self.on_drag { @@ -220,13 +236,101 @@ where } } }, + Event::Mouse(mouse::Event::Input { + button: mouse::Button::Right, + state, + }) if self.on_resize.is_some() + && self.state.picked_pane().is_none() + && self.modifiers.alt => + { + match state { + ButtonState::Pressed => { + let bounds = layout.bounds(); + + let splits = self.state.splits( + f32::from(self.spacing), + Size::new(bounds.width, bounds.height), + ); + + let mut sorted_splits: Vec<_> = splits.iter().collect(); + let offset = Vector::new(bounds.x, bounds.y); + + sorted_splits.sort_by_key( + |(_, (axis, rectangle, ratio))| { + let center = match axis { + Axis::Horizontal => Point::new( + rectangle.x + rectangle.width / 2.0, + rectangle.y + rectangle.height * ratio, + ), + + Axis::Vertical => Point::new( + rectangle.x + rectangle.width * ratio, + rectangle.y + rectangle.height / 2.0, + ), + }; + + cursor_position + .distance(center + offset) + .round() + as u32 + }, + ); + + if let Some((split, (axis, _, _))) = + sorted_splits.first() + { + self.state.pick_split(split, *axis); + } + } + ButtonState::Released => { + self.state.drop_split(); + } + } + } + Event::Mouse(mouse::Event::CursorMoved { .. }) => { + if let Some(on_resize) = &self.on_resize { + if let Some((split, _)) = self.state.picked_split() { + let bounds = layout.bounds(); + + let splits = self.state.splits( + f32::from(self.spacing), + Size::new(bounds.width, bounds.height), + ); + + if let Some((axis, rectangle, _)) = splits.get(&split) { + let ratio = match axis { + Axis::Horizontal => { + let position = cursor_position.x - bounds.x + + rectangle.x; + + (position / (rectangle.x + rectangle.width)) + .max(0.1) + .min(0.9) + } + Axis::Vertical => { + let position = cursor_position.y - bounds.y + + rectangle.y; + + (position + / (rectangle.y + rectangle.height)) + .max(0.1) + .min(0.9) + } + }; + + messages + .push(on_resize(ResizeEvent { split, ratio })); + } + } + } + } Event::Keyboard(keyboard::Event::Input { modifiers, .. }) => { *self.modifiers = modifiers; } _ => {} } - if self.state.dragged().is_none() { + if self.state.picked_pane().is_none() { { self.elements.iter_mut().zip(layout.children()).for_each( |((_, pane), layout)| { @@ -254,7 +358,8 @@ where renderer.draw( defaults, &self.elements, - self.state.dragged(), + self.state.picked_pane(), + self.state.picked_split().map(|(_, axis)| axis), layout, cursor_position, ) @@ -297,6 +402,7 @@ pub trait Renderer: crate::Renderer + Sized { defaults: &Self::Defaults, content: &[(Pane, Element<'_, Message, Self>)], dragging: Option, + resizing: Option, layout: Layout<'_>, cursor_position: Point, ) -> Self::Output; diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs index 08046956..4d5970b8 100644 --- a/native/src/widget/pane_grid/node.rs +++ b/native/src/widget/pane_grid/node.rs @@ -55,6 +55,25 @@ impl Node { f(self); } + pub fn resize(&mut self, split: &Split, percentage: f32) -> bool { + match self { + Node::Split { + id, ratio, a, b, .. + } => { + if id == split { + *ratio = (percentage * 1_000_000.0).round() as u32; + + true + } else if a.resize(split, percentage) { + true + } else { + b.resize(split, percentage) + } + } + Node::Pane(_) => false, + } + } + pub fn remove(&mut self, pane: &Pane) -> Option { match self { Node::Split { a, b, .. } => { @@ -93,6 +112,27 @@ impl Node { regions } + pub fn splits( + &self, + spacing: f32, + size: Size, + ) -> HashMap { + let mut splits = HashMap::new(); + + self.compute_splits( + spacing / 2.0, + &Rectangle { + x: 0.0, + y: 0.0, + width: size.width, + height: size.height, + }, + &mut splits, + ); + + splits + } + pub fn pane(&self) -> Option { match self { Node::Split { .. } => None, @@ -129,4 +169,31 @@ impl Node { } } } + + fn compute_splits( + &self, + halved_spacing: f32, + current: &Rectangle, + splits: &mut HashMap, + ) { + match self { + Node::Split { + axis, + ratio, + a, + b, + id, + } => { + let ratio = *ratio as f32 / 1_000_000.0; + let (region_a, region_b) = + axis.split(current, ratio, halved_spacing); + + let _ = splits.insert(*id, (*axis, *current, ratio)); + + a.compute_splits(halved_spacing, ®ion_a, splits); + b.compute_splits(halved_spacing, ®ion_b, splits); + } + Node::Pane(_) => {} + } + } } diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index b0e571f0..456ad78a 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -134,6 +134,10 @@ impl State { }); } + pub fn resize(&mut self, split: &Split, percentage: f32) { + let _ = self.internal.layout.resize(split, percentage); + } + pub fn close(&mut self, pane: &Pane) -> Option { if let Some(sibling) = self.internal.layout.remove(pane) { self.focus(&sibling); @@ -153,14 +157,25 @@ pub struct Internal { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Action { - Idle { focus: Option }, - Dragging { pane: Pane }, + Idle { + focus: Option, + }, + Dragging { + pane: Pane, + }, + Resizing { + split: Split, + axis: Axis, + focus: Option, + }, } impl Action { pub fn focus(&self) -> Option<(Pane, Focus)> { match self { - Action::Idle { focus } => focus.map(|pane| (pane, Focus::Idle)), + Action::Idle { focus } | Action::Resizing { focus, .. } => { + focus.map(|pane| (pane, Focus::Idle)) + } Action::Dragging { pane } => Some((*pane, Focus::Dragging)), } } @@ -171,13 +186,20 @@ impl Internal { self.action } - pub fn dragged(&self) -> Option { + pub fn picked_pane(&self) -> Option { match self.action { Action::Dragging { pane } => Some(pane), _ => None, } } + pub fn picked_split(&self) -> Option<(Split, Axis)> { + match self.action { + Action::Resizing { split, axis, .. } => Some((split, axis)), + _ => None, + } + } + pub fn regions( &self, spacing: f32, @@ -186,14 +208,47 @@ impl Internal { self.layout.regions(spacing, size) } + pub fn splits( + &self, + spacing: f32, + size: Size, + ) -> HashMap { + self.layout.splits(spacing, size) + } + pub fn focus(&mut self, pane: &Pane) { self.action = Action::Idle { focus: Some(*pane) }; } - pub fn drag(&mut self, pane: &Pane) { + pub fn pick_pane(&mut self, pane: &Pane) { self.action = Action::Dragging { pane: *pane }; } + pub fn pick_split(&mut self, split: &Split, axis: Axis) { + // TODO: Obtain `axis` from layout itself. Maybe we should implement + // `Node::find_split` + if self.picked_pane().is_some() { + return; + } + + let focus = self.action.focus().map(|(pane, _)| pane); + + self.action = Action::Resizing { + split: *split, + axis, + focus, + }; + } + + pub fn drop_split(&mut self) { + match self.action { + Action::Resizing { focus, .. } => { + self.action = Action::Idle { focus }; + } + _ => {} + } + } + pub fn unfocus(&mut self) { self.action = Action::Idle { focus: None }; } diff --git a/wgpu/src/renderer/widget/pane_grid.rs b/wgpu/src/renderer/widget/pane_grid.rs index 8fb4a1a9..a00b49ea 100644 --- a/wgpu/src/renderer/widget/pane_grid.rs +++ b/wgpu/src/renderer/widget/pane_grid.rs @@ -1,6 +1,6 @@ use crate::{Primitive, Renderer}; use iced_native::{ - pane_grid::{self, Pane}, + pane_grid::{self, Axis, Pane}, Element, Layout, MouseCursor, Point, Rectangle, Vector, }; @@ -10,6 +10,7 @@ impl pane_grid::Renderer for Renderer { defaults: &Self::Defaults, content: &[(Pane, Element<'_, Message, Self>)], dragging: Option, + resizing: Option, layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { @@ -70,6 +71,11 @@ impl pane_grid::Renderer for Renderer { Primitive::Group { primitives }, if dragging.is_some() { MouseCursor::Grabbing + } else if let Some(axis) = resizing { + match axis { + Axis::Horizontal => MouseCursor::ResizingHorizontally, + Axis::Vertical => MouseCursor::ResizingVertically, + } } else { mouse_cursor }, diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index b6a0b64b..74852876 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -116,6 +116,10 @@ pub fn mouse_cursor(mouse_cursor: MouseCursor) -> winit::window::CursorIcon { MouseCursor::Grab => winit::window::CursorIcon::Grab, MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, MouseCursor::Text => winit::window::CursorIcon::Text, + MouseCursor::ResizingHorizontally => { + winit::window::CursorIcon::EwResize + } + MouseCursor::ResizingVertically => winit::window::CursorIcon::NsResize, } } -- cgit From eb5e2251bdb71c75e1da86b0f575cd0e13cafa6a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Mar 2020 08:16:07 +0100 Subject: Trigger `PaneGrid` resize on click --- native/src/widget/pane_grid.rs | 82 ++++++++++++++++++++++++------------------ 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 5229962d..62148764 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -111,6 +111,47 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self.on_resize = Some(Box::new(f)); self } + + fn trigger_resize( + &mut self, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + ) { + if let Some(on_resize) = &self.on_resize { + if let Some((split, _)) = self.state.picked_split() { + let bounds = layout.bounds(); + + let splits = self.state.splits( + f32::from(self.spacing), + Size::new(bounds.width, bounds.height), + ); + + if let Some((axis, rectangle, _)) = splits.get(&split) { + let ratio = match axis { + Axis::Horizontal => { + let position = + cursor_position.x - bounds.x + rectangle.x; + + (position / (rectangle.x + rectangle.width)) + .max(0.1) + .min(0.9) + } + Axis::Vertical => { + let position = + cursor_position.y - bounds.y + rectangle.y; + + (position / (rectangle.y + rectangle.height)) + .max(0.1) + .min(0.9) + } + }; + + messages.push(on_resize(ResizeEvent { split, ratio })); + } + } + } + } } #[derive(Debug, Clone, Copy)] @@ -280,6 +321,11 @@ where sorted_splits.first() { self.state.pick_split(split, *axis); + self.trigger_resize( + layout, + cursor_position, + messages, + ); } } ButtonState::Released => { @@ -288,41 +334,7 @@ where } } Event::Mouse(mouse::Event::CursorMoved { .. }) => { - if let Some(on_resize) = &self.on_resize { - if let Some((split, _)) = self.state.picked_split() { - let bounds = layout.bounds(); - - let splits = self.state.splits( - f32::from(self.spacing), - Size::new(bounds.width, bounds.height), - ); - - if let Some((axis, rectangle, _)) = splits.get(&split) { - let ratio = match axis { - Axis::Horizontal => { - let position = cursor_position.x - bounds.x - + rectangle.x; - - (position / (rectangle.x + rectangle.width)) - .max(0.1) - .min(0.9) - } - Axis::Vertical => { - let position = cursor_position.y - bounds.y - + rectangle.y; - - (position - / (rectangle.y + rectangle.height)) - .max(0.1) - .min(0.9) - } - }; - - messages - .push(on_resize(ResizeEvent { split, ratio })); - } - } - } + self.trigger_resize(layout, cursor_position, messages); } Event::Keyboard(keyboard::Event::Input { modifiers, .. }) => { *self.modifiers = modifiers; -- cgit From ec334bdd362243a49237c1f07b601dd6b28ddc3a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Mar 2020 09:00:57 +0100 Subject: Improve pane selection when resizing a `PaneGrid` --- native/src/widget/pane_grid.rs | 102 +++++++++++++++++----------------- native/src/widget/pane_grid/axis.rs | 26 ++++----- wgpu/src/renderer/widget/pane_grid.rs | 4 +- 3 files changed, 67 insertions(+), 65 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 62148764..0d4a4404 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -14,7 +14,7 @@ pub use state::{Focus, State}; use crate::{ input::{keyboard, mouse, ButtonState}, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Size, - Vector, Widget, + Widget, }; #[allow(missing_debug_implementations)] @@ -131,17 +131,17 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { let ratio = match axis { Axis::Horizontal => { let position = - cursor_position.x - bounds.x + rectangle.x; + cursor_position.y - bounds.y + rectangle.y; - (position / (rectangle.x + rectangle.width)) + (position / (rectangle.y + rectangle.height)) .max(0.1) .min(0.9) } Axis::Vertical => { let position = - cursor_position.y - bounds.y + rectangle.y; + cursor_position.x - bounds.x + rectangle.x; - (position / (rectangle.y + rectangle.height)) + (position / (rectangle.x + rectangle.width)) .max(0.1) .min(0.9) } @@ -279,60 +279,62 @@ where }, Event::Mouse(mouse::Event::Input { button: mouse::Button::Right, - state, + state: ButtonState::Pressed, }) if self.on_resize.is_some() && self.state.picked_pane().is_none() && self.modifiers.alt => { - match state { - ButtonState::Pressed => { - let bounds = layout.bounds(); - - let splits = self.state.splits( - f32::from(self.spacing), - Size::new(bounds.width, bounds.height), - ); + let bounds = layout.bounds(); + let relative_cursor = Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ); - let mut sorted_splits: Vec<_> = splits.iter().collect(); - let offset = Vector::new(bounds.x, bounds.y); - - sorted_splits.sort_by_key( - |(_, (axis, rectangle, ratio))| { - let center = match axis { - Axis::Horizontal => Point::new( - rectangle.x + rectangle.width / 2.0, - rectangle.y + rectangle.height * ratio, - ), - - Axis::Vertical => Point::new( - rectangle.x + rectangle.width * ratio, - rectangle.y + rectangle.height / 2.0, - ), - }; - - cursor_position - .distance(center + offset) - .round() - as u32 - }, - ); + let splits = self.state.splits( + f32::from(self.spacing), + Size::new(bounds.width, bounds.height), + ); - if let Some((split, (axis, _, _))) = - sorted_splits.first() - { - self.state.pick_split(split, *axis); - self.trigger_resize( - layout, - cursor_position, - messages, - ); + let mut sorted_splits: Vec<_> = splits + .iter() + .filter(|(_, (axis, rectangle, _))| match axis { + Axis::Horizontal => { + relative_cursor.x > rectangle.x + && relative_cursor.x + < rectangle.x + rectangle.width } - } - ButtonState::Released => { - self.state.drop_split(); - } + Axis::Vertical => { + relative_cursor.y > rectangle.y + && relative_cursor.y + < rectangle.y + rectangle.height + } + }) + .collect(); + + sorted_splits.sort_by_key(|(_, (axis, rectangle, ratio))| { + let distance = match axis { + Axis::Horizontal => (relative_cursor.y + - (rectangle.y + rectangle.height * ratio)) + .abs(), + Axis::Vertical => (relative_cursor.x + - (rectangle.x + rectangle.width * ratio)) + .abs(), + }; + + distance.round() as u32 + }); + + if let Some((split, (axis, _, _))) = sorted_splits.first() { + self.state.pick_split(split, *axis); + self.trigger_resize(layout, cursor_position, messages); } } + Event::Mouse(mouse::Event::Input { + button: mouse::Button::Right, + state: ButtonState::Released, + }) if self.state.picked_split().is_some() => { + self.state.drop_split(); + } Event::Mouse(mouse::Event::CursorMoved { .. }) => { self.trigger_resize(layout, cursor_position, messages); } diff --git a/native/src/widget/pane_grid/axis.rs b/native/src/widget/pane_grid/axis.rs index 375509b7..f8d53e09 100644 --- a/native/src/widget/pane_grid/axis.rs +++ b/native/src/widget/pane_grid/axis.rs @@ -15,36 +15,36 @@ impl Axis { ) -> (Rectangle, Rectangle) { match self { Axis::Horizontal => { - let width_left = - (rectangle.width * ratio).round() - halved_spacing; - let width_right = rectangle.width - width_left - halved_spacing; + let height_top = + (rectangle.height * ratio).round() - halved_spacing; + let height_bottom = + rectangle.height - height_top - halved_spacing; ( Rectangle { - width: width_left, + height: height_top, ..*rectangle }, Rectangle { - x: rectangle.x + width_left + halved_spacing, - width: width_right, + y: rectangle.y + height_top + halved_spacing, + height: height_bottom, ..*rectangle }, ) } Axis::Vertical => { - let height_top = - (rectangle.height * ratio).round() - halved_spacing; - let height_bottom = - rectangle.height - height_top - halved_spacing; + let width_left = + (rectangle.width * ratio).round() - halved_spacing; + let width_right = rectangle.width - width_left - halved_spacing; ( Rectangle { - height: height_top, + width: width_left, ..*rectangle }, Rectangle { - y: rectangle.y + height_top + halved_spacing, - height: height_bottom, + x: rectangle.x + width_left + halved_spacing, + width: width_right, ..*rectangle }, ) diff --git a/wgpu/src/renderer/widget/pane_grid.rs b/wgpu/src/renderer/widget/pane_grid.rs index a00b49ea..741fe814 100644 --- a/wgpu/src/renderer/widget/pane_grid.rs +++ b/wgpu/src/renderer/widget/pane_grid.rs @@ -73,8 +73,8 @@ impl pane_grid::Renderer for Renderer { MouseCursor::Grabbing } else if let Some(axis) = resizing { match axis { - Axis::Horizontal => MouseCursor::ResizingHorizontally, - Axis::Vertical => MouseCursor::ResizingVertically, + Axis::Horizontal => MouseCursor::ResizingVertically, + Axis::Vertical => MouseCursor::ResizingHorizontally, } } else { mouse_cursor -- cgit From a373682fa4e8d57d66707faef1fb6b373f4297eb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Mar 2020 09:36:20 +0100 Subject: Fix ratio calculation on resize in `PaneGrid` --- native/src/widget/pane_grid.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 0d4a4404..7135efe4 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -131,19 +131,15 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { let ratio = match axis { Axis::Horizontal => { let position = - cursor_position.y - bounds.y + rectangle.y; + cursor_position.y - bounds.y - rectangle.y; - (position / (rectangle.y + rectangle.height)) - .max(0.1) - .min(0.9) + (position / rectangle.height).max(0.1).min(0.9) } Axis::Vertical => { let position = - cursor_position.x - bounds.x + rectangle.x; + cursor_position.x - bounds.x - rectangle.x; - (position / (rectangle.x + rectangle.width)) - .max(0.1) - .min(0.9) + (position / rectangle.width).max(0.1).min(0.9) } }; -- cgit From e1438774af809c2951c4c7446638500446c81111 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Mar 2020 23:25:19 +0100 Subject: Fix `Scrollable` width consistency --- examples/styling/src/main.rs | 1 + native/src/widget/scrollable.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index 47408624..d6f41b04 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -93,6 +93,7 @@ impl Sandbox for Styling { ProgressBar::new(0.0..=100.0, self.slider_value).style(self.theme); let scrollable = Scrollable::new(&mut self.scroll) + .width(Length::Fill) .height(Length::Units(100)) .style(self.theme) .push(Text::new("Scroll me!")) diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index e83f25af..ec9746d4 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -118,7 +118,7 @@ where Renderer: 'static + self::Renderer + column::Renderer, { fn width(&self) -> Length { - Length::Fill + Widget::::width(&self.content) } fn height(&self) -> Length { @@ -132,7 +132,7 @@ where ) -> layout::Node { let limits = limits .max_height(self.max_height) - .width(Length::Fill) + .width(Widget::::width(&self.content)) .height(self.height); let child_limits = layout::Limits::new( -- cgit From ae123d8f14c14a2c393bcf00dc364844a32cc0c8 Mon Sep 17 00:00:00 2001 From: Rowun Giles <1868220+rowungiles@users.noreply.github.com> Date: Mon, 16 Mar 2020 10:08:37 +0000 Subject: Instantiate Column and Row with children --- native/src/widget/column.rs | 16 ++++++++++++++++ native/src/widget/row.rs | 16 ++++++++++++++++ web/src/widget/column.rs | 16 ++++++++++++++++ web/src/widget/row.rs | 16 ++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index 104790d4..e6c795e5 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -42,6 +42,22 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { } } + /// Creates a [`Column`] with children. + /// + /// [`Column`]: struct.Column.html + pub fn new_with_children(children: Vec>) -> Self { + Column { + spacing: 0, + padding: 0, + width: Length::Shrink, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + align_items: Align::Start, + children, + } + } + /// Sets the vertical spacing _between_ elements. /// /// Custom margins per element do not exist in Iced. You should use this diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 775b953e..3d803fa2 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -42,6 +42,22 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { } } + /// Creates a [`Row`] with children. + /// + /// [`Row`]: struct.Row.html + pub fn new_with_children(children: Vec>) -> Self { + Row { + spacing: 0, + padding: 0, + width: Length::Shrink, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + align_items: Align::Start, + children, + } + } + /// Sets the horizontal spacing _between_ elements. /// /// Custom margins per element do not exist in Iced. You should use this diff --git a/web/src/widget/column.rs b/web/src/widget/column.rs index 6454ffba..f1d9f9a2 100644 --- a/web/src/widget/column.rs +++ b/web/src/widget/column.rs @@ -37,6 +37,22 @@ impl<'a, Message> Column<'a, Message> { } } + /// Creates a [`Column`] with children. + /// + /// [`Column`]: struct.Column.html + pub fn new_with_children(children: Vec>) -> Self { + Column { + spacing: 0, + padding: 0, + width: Length::Fill, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + align_items: Align::Start, + children, + } + } + /// Sets the vertical spacing _between_ elements. /// /// Custom margins per element do not exist in Iced. You should use this diff --git a/web/src/widget/row.rs b/web/src/widget/row.rs index 02035113..55077d52 100644 --- a/web/src/widget/row.rs +++ b/web/src/widget/row.rs @@ -37,6 +37,22 @@ impl<'a, Message> Row<'a, Message> { } } + /// Creates a [`Row`] with children. + /// + /// [`Row`]: struct.Row.html + pub fn new_with_children(children: Vec>) -> Self { + Row { + spacing: 0, + padding: 0, + width: Length::Fill, + height: Length::Shrink, + max_width: u32::MAX, + max_height: u32::MAX, + align_items: Align::Start, + children, + } + } + /// Sets the horizontal spacing _between_ elements. /// /// Custom margins per element do not exist in Iced. You should use this -- cgit From a146e53eb0f9bf2714f7f2a4227fe445f4905ff1 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 17 Mar 2020 00:50:23 +0100 Subject: Rename `new_with_children` to `with_children` --- native/src/widget/column.rs | 6 ++++-- native/src/widget/row.rs | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index e6c795e5..b38b1ef1 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -42,10 +42,12 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { } } - /// Creates a [`Column`] with children. + /// Creates a [`Column`] with the given elements. /// /// [`Column`]: struct.Column.html - pub fn new_with_children(children: Vec>) -> Self { + pub fn with_children( + children: Vec>, + ) -> Self { Column { spacing: 0, padding: 0, diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index 3d803fa2..b71d6480 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -42,10 +42,12 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { } } - /// Creates a [`Row`] with children. + /// Creates a [`Row`] with the given elements. /// /// [`Row`]: struct.Row.html - pub fn new_with_children(children: Vec>) -> Self { + pub fn with_children( + children: Vec>, + ) -> Self { Row { spacing: 0, padding: 0, -- cgit From 95c8031f3d2800ade28b593c17c138c6d389b1ae Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 17 Mar 2020 00:51:01 +0100 Subject: Reuse `with_children` to remove some duplication --- native/src/widget/column.rs | 11 +---------- native/src/widget/row.rs | 11 +---------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index b38b1ef1..a7a6f242 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -30,16 +30,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> { /// /// [`Column`]: struct.Column.html pub fn new() -> Self { - Column { - spacing: 0, - padding: 0, - width: Length::Shrink, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - align_items: Align::Start, - children: Vec::new(), - } + Self::with_children(Vec::new()) } /// Creates a [`Column`] with the given elements. diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs index b71d6480..c8812ea2 100644 --- a/native/src/widget/row.rs +++ b/native/src/widget/row.rs @@ -30,16 +30,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> { /// /// [`Row`]: struct.Row.html pub fn new() -> Self { - Row { - spacing: 0, - padding: 0, - width: Length::Shrink, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - align_items: Align::Start, - children: Vec::new(), - } + Self::with_children(Vec::new()) } /// Creates a [`Row`] with the given elements. -- cgit From 99b5212550f225a21e1d1144c6fb9ebb8f4e6ed2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 17 Mar 2020 00:53:51 +0100 Subject: Implement `Column` and `Row` changes in `iced_web` --- web/src/widget/column.rs | 15 +++------------ web/src/widget/row.rs | 15 +++------------ 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/web/src/widget/column.rs b/web/src/widget/column.rs index f1d9f9a2..25b88b0e 100644 --- a/web/src/widget/column.rs +++ b/web/src/widget/column.rs @@ -25,22 +25,13 @@ impl<'a, Message> Column<'a, Message> { /// /// [`Column`]: struct.Column.html pub fn new() -> Self { - Column { - spacing: 0, - padding: 0, - width: Length::Fill, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - align_items: Align::Start, - children: Vec::new(), - } + Self::with_children(Vec::new()) } - /// Creates a [`Column`] with children. + /// Creates a [`Column`] with the given elements. /// /// [`Column`]: struct.Column.html - pub fn new_with_children(children: Vec>) -> Self { + pub fn with_children(children: Vec>) -> Self { Column { spacing: 0, padding: 0, diff --git a/web/src/widget/row.rs b/web/src/widget/row.rs index 55077d52..cfa10fdf 100644 --- a/web/src/widget/row.rs +++ b/web/src/widget/row.rs @@ -25,22 +25,13 @@ impl<'a, Message> Row<'a, Message> { /// /// [`Row`]: struct.Row.html pub fn new() -> Self { - Row { - spacing: 0, - padding: 0, - width: Length::Fill, - height: Length::Shrink, - max_width: u32::MAX, - max_height: u32::MAX, - align_items: Align::Start, - children: Vec::new(), - } + Self::with_children(Vec::new()) } - /// Creates a [`Row`] with children. + /// Creates a [`Row`] with the given elements. /// /// [`Row`]: struct.Row.html - pub fn new_with_children(children: Vec>) -> Self { + pub fn with_children(children: Vec>) -> Self { Row { spacing: 0, padding: 0, -- cgit From 21a4095a99e23d7302cb689c73970c886b0278b8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 17 Mar 2020 04:15:17 +0100 Subject: Fix spacing calculation in `Axis::split` --- native/src/widget/pane_grid/axis.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/native/src/widget/pane_grid/axis.rs b/native/src/widget/pane_grid/axis.rs index f8d53e09..a17d0c12 100644 --- a/native/src/widget/pane_grid/axis.rs +++ b/native/src/widget/pane_grid/axis.rs @@ -15,36 +15,33 @@ impl Axis { ) -> (Rectangle, Rectangle) { match self { Axis::Horizontal => { - let height_top = - (rectangle.height * ratio).round() - halved_spacing; - let height_bottom = - rectangle.height - height_top - halved_spacing; + let height_top = (rectangle.height * ratio).round(); + let height_bottom = rectangle.height - height_top; ( Rectangle { - height: height_top, + height: height_top - halved_spacing, ..*rectangle }, Rectangle { y: rectangle.y + height_top + halved_spacing, - height: height_bottom, + height: height_bottom - halved_spacing, ..*rectangle }, ) } Axis::Vertical => { - let width_left = - (rectangle.width * ratio).round() - halved_spacing; - let width_right = rectangle.width - width_left - halved_spacing; + let width_left = (rectangle.width * ratio).round(); + let width_right = rectangle.width - width_left; ( Rectangle { - width: width_left, + width: width_left - halved_spacing, ..*rectangle }, Rectangle { x: rectangle.x + width_left + halved_spacing, - width: width_right, + width: width_right - halved_spacing, ..*rectangle }, ) -- cgit From 56ba6215a25fe90a50be8feebeb74031967e92b0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 17 Mar 2020 04:23:28 +0100 Subject: Add simple `pane_grid` example --- Cargo.toml | 1 + examples/pane_grid/Cargo.toml | 9 ++ examples/pane_grid/README.md | 18 ++++ examples/pane_grid/src/main.rs | 202 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 230 insertions(+) create mode 100644 examples/pane_grid/Cargo.toml create mode 100644 examples/pane_grid/README.md create mode 100644 examples/pane_grid/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 01231b70..12b75aed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ members = [ "examples/geometry", "examples/integration", "examples/pokedex", + "examples/pane_grid", "examples/progress_bar", "examples/solar_system", "examples/stopwatch", diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml new file mode 100644 index 00000000..3ed912ac --- /dev/null +++ b/examples/pane_grid/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "pane_grid" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } diff --git a/examples/pane_grid/README.md b/examples/pane_grid/README.md new file mode 100644 index 00000000..4d9fc5b9 --- /dev/null +++ b/examples/pane_grid/README.md @@ -0,0 +1,18 @@ +## Counter + +The classic counter example explained in the [`README`](../../README.md). + +The __[`main`]__ file contains all the code of the example. + + + +You can run it with `cargo run`: +``` +cargo run --package counter +``` + +[`main`]: src/main.rs diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs new file mode 100644 index 00000000..09969630 --- /dev/null +++ b/examples/pane_grid/src/main.rs @@ -0,0 +1,202 @@ +use iced::{ + button, pane_grid, scrollable, Align, Button, Column, Container, Element, + HorizontalAlignment, Length, PaneGrid, Sandbox, Scrollable, Settings, Text, +}; + +pub fn main() { + Example::run(Settings::default()) +} + +struct Example { + panes: pane_grid::State, + panes_created: usize, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + Split(pane_grid::Axis, pane_grid::Pane), + SplitFocused(pane_grid::Axis), + Dragged(pane_grid::DragEvent), + Resized(pane_grid::ResizeEvent), + Close(pane_grid::Pane), +} + +impl Sandbox for Example { + type Message = Message; + + fn new() -> Self { + let (panes, _) = pane_grid::State::new(Content::new(0)); + + Example { + panes, + panes_created: 1, + } + } + + fn title(&self) -> String { + String::from("Pane grid - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::Split(axis, pane) => { + let _ = self.panes.split( + axis, + &pane, + Content::new(self.panes_created), + ); + + self.panes_created += 1; + } + Message::SplitFocused(axis) => { + if let Some(pane) = self.panes.active() { + let _ = self.panes.split( + axis, + &pane, + Content::new(self.panes_created), + ); + + self.panes_created += 1; + } + } + Message::Resized(pane_grid::ResizeEvent { split, ratio }) => { + self.panes.resize(&split, ratio); + } + Message::Dragged(pane_grid::DragEvent::Dropped { + pane, + target, + }) => { + self.panes.swap(&pane, &target); + } + Message::Dragged(_) => {} + Message::Close(pane) => { + let _ = self.panes.close(&pane); + } + } + } + + fn view(&mut self) -> Element { + let total_panes = self.panes.len(); + + let pane_grid = + PaneGrid::new(&mut self.panes, |pane, content, focus| { + content.view(pane, focus, total_panes) + }) + .width(Length::Fill) + .height(Length::Fill) + .spacing(5) + .on_drag(Message::Dragged) + .on_resize(Message::Resized); + + Column::new() + .width(Length::Fill) + .height(Length::Fill) + .padding(10) + .push(pane_grid) + .into() + } +} + +struct Content { + id: usize, + scroll: scrollable::State, + split_horizontally: button::State, + split_vertically: button::State, + close: button::State, +} + +impl Content { + fn new(id: usize) -> Self { + Content { + id, + scroll: scrollable::State::new(), + split_horizontally: button::State::new(), + split_vertically: button::State::new(), + close: button::State::new(), + } + } + fn view( + &mut self, + pane: pane_grid::Pane, + focus: Option, + total_panes: usize, + ) -> Element { + let Content { + id, + scroll, + split_horizontally, + split_vertically, + close, + } = self; + + let button = |state, label, message| { + Button::new( + state, + Text::new(label) + .width(Length::Fill) + .horizontal_alignment(HorizontalAlignment::Center) + .size(16), + ) + .width(Length::Fill) + .on_press(message) + }; + + let mut controls = Column::new() + .spacing(5) + .max_width(150) + .push(button( + split_horizontally, + "Split horizontally", + Message::Split(pane_grid::Axis::Horizontal, pane), + )) + .push(button( + split_vertically, + "Split vertically", + Message::Split(pane_grid::Axis::Vertical, pane), + )); + + if total_panes > 1 { + controls = + controls.push(button(close, "Close", Message::Close(pane))); + } + + let content = Scrollable::new(scroll) + .width(Length::Fill) + .spacing(10) + .align_items(Align::Center) + .push(Text::new(format!("Pane {}", id)).size(30)) + .push(controls); + + Container::new(Column::new().padding(10).push(content)) + .width(Length::Fill) + .height(Length::Fill) + .center_y() + .style(style::Pane { + is_focused: focus.is_some(), + }) + .into() + } +} + +mod style { + use iced::{container, Background, Color}; + + pub struct Pane { + pub is_focused: bool, + } + + impl container::StyleSheet for Pane { + fn style(&self) -> container::Style { + container::Style { + background: Some(Background::Color(Color::WHITE)), + border_width: 1, + border_color: if self.is_focused { + Color::from_rgb8(0x25, 0x7A, 0xFD) + } else { + Color::BLACK + }, + ..Default::default() + } + } + } +} -- cgit From a280dcda23c3c3432f12776b2fe69c4ed39cd99a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 17 Mar 2020 06:53:57 +0100 Subject: Add `PaneGrid::on_key_press` for hotkey logic --- native/src/widget/pane_grid.rs | 48 +++++++++++++++++++++++++++++++++--- native/src/widget/pane_grid/state.rs | 7 ++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 7135efe4..8410f95c 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -25,8 +25,10 @@ pub struct PaneGrid<'a, Message, Renderer> { width: Length, height: Length, spacing: u16, + modifier_keys: keyboard::ModifiersState, on_drag: Option Message>>, on_resize: Option Message>>, + on_key_press: Option Option>>, } impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { @@ -67,8 +69,13 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { width: Length::Fill, height: Length::Fill, spacing: 0, + modifier_keys: keyboard::ModifiersState { + control: true, + ..Default::default() + }, on_drag: None, on_resize: None, + on_key_press: None, } } @@ -96,6 +103,14 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self } + pub fn modifier_keys( + mut self, + modifier_keys: keyboard::ModifiersState, + ) -> Self { + self.modifier_keys = modifier_keys; + self + } + pub fn on_drag( mut self, f: impl Fn(DragEvent) -> Message + 'static, @@ -112,6 +127,14 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self } + pub fn on_key_press( + mut self, + f: impl Fn(keyboard::KeyCode) -> Option + 'static, + ) -> Self { + self.on_key_press = Some(Box::new(f)); + self + } + fn trigger_resize( &mut self, layout: Layout<'_>, @@ -230,7 +253,9 @@ where if let Some(((pane, _), _)) = clicked_region.next() { match &self.on_drag { - Some(on_drag) if self.modifiers.alt => { + Some(on_drag) + if *self.modifiers == self.modifier_keys => + { self.state.pick_pane(pane); messages.push(on_drag(DragEvent::Picked { @@ -278,7 +303,7 @@ where state: ButtonState::Pressed, }) if self.on_resize.is_some() && self.state.picked_pane().is_none() - && self.modifiers.alt => + && *self.modifiers == self.modifier_keys => { let bounds = layout.bounds(); let relative_cursor = Point::new( @@ -334,7 +359,24 @@ where Event::Mouse(mouse::Event::CursorMoved { .. }) => { self.trigger_resize(layout, cursor_position, messages); } - Event::Keyboard(keyboard::Event::Input { modifiers, .. }) => { + Event::Keyboard(keyboard::Event::Input { + modifiers, + key_code, + state, + }) => { + if let Some(on_key_press) = &self.on_key_press { + // TODO: Discard when event is captured + if state == ButtonState::Pressed { + if let Some(_) = self.state.idle_pane() { + if modifiers == self.modifier_keys { + if let Some(message) = on_key_press(key_code) { + messages.push(message); + } + } + } + } + } + *self.modifiers = modifiers; } _ => {} diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 456ad78a..9103dcd0 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -186,6 +186,13 @@ impl Internal { self.action } + pub fn idle_pane(&self) -> Option { + match self.action { + Action::Idle { focus } => focus, + _ => None, + } + } + pub fn picked_pane(&self) -> Option { match self.action { Action::Dragging { pane } => Some(pane), -- cgit From 6f9cf6c70d8ef01446dae4d093c6e8ff2c7e7708 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 17 Mar 2020 06:54:25 +0100 Subject: Implement hotkey logic in `pane_grid` example --- examples/pane_grid/Cargo.toml | 1 + examples/pane_grid/src/main.rs | 42 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index 3ed912ac..fb160a8d 100644 --- a/examples/pane_grid/Cargo.toml +++ b/examples/pane_grid/Cargo.toml @@ -7,3 +7,4 @@ publish = false [dependencies] iced = { path = "../.." } +iced_native = { path = "../../native" } diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 09969630..7ab68393 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -2,6 +2,7 @@ use iced::{ button, pane_grid, scrollable, Align, Button, Column, Container, Element, HorizontalAlignment, Length, PaneGrid, Sandbox, Scrollable, Settings, Text, }; +use iced_native::input::keyboard; pub fn main() { Example::run(Settings::default()) @@ -16,9 +17,11 @@ struct Example { enum Message { Split(pane_grid::Axis, pane_grid::Pane), SplitFocused(pane_grid::Axis), + FocusAdjacent(pane_grid::Direction), Dragged(pane_grid::DragEvent), Resized(pane_grid::ResizeEvent), Close(pane_grid::Pane), + CloseFocused, } impl Sandbox for Example { @@ -59,6 +62,15 @@ impl Sandbox for Example { self.panes_created += 1; } } + Message::FocusAdjacent(direction) => { + if let Some(pane) = self.panes.active() { + if let Some(adjacent) = + self.panes.adjacent(&pane, direction) + { + self.panes.focus(&adjacent); + } + } + } Message::Resized(pane_grid::ResizeEvent { split, ratio }) => { self.panes.resize(&split, ratio); } @@ -72,6 +84,11 @@ impl Sandbox for Example { Message::Close(pane) => { let _ = self.panes.close(&pane); } + Message::CloseFocused => { + if let Some(pane) = self.panes.active() { + let _ = self.panes.close(&pane); + } + } } } @@ -86,7 +103,8 @@ impl Sandbox for Example { .height(Length::Fill) .spacing(5) .on_drag(Message::Dragged) - .on_resize(Message::Resized); + .on_resize(Message::Resized) + .on_key_press(handle_hotkey); Column::new() .width(Length::Fill) @@ -97,6 +115,26 @@ impl Sandbox for Example { } } +fn handle_hotkey(key_code: keyboard::KeyCode) -> Option { + use keyboard::KeyCode; + use pane_grid::{Axis, Direction}; + + let direction = match key_code { + KeyCode::Up => Some(Direction::Up), + KeyCode::Down => Some(Direction::Down), + KeyCode::Left => Some(Direction::Left), + KeyCode::Right => Some(Direction::Right), + _ => None, + }; + + match key_code { + KeyCode::V => Some(Message::SplitFocused(Axis::Vertical)), + KeyCode::H => Some(Message::SplitFocused(Axis::Horizontal)), + KeyCode::W => Some(Message::CloseFocused), + _ => direction.map(Message::FocusAdjacent), + } +} + struct Content { id: usize, scroll: scrollable::State, @@ -189,7 +227,7 @@ mod style { fn style(&self) -> container::Style { container::Style { background: Some(Background::Color(Color::WHITE)), - border_width: 1, + border_width: if self.is_focused { 2 } else { 1 }, border_color: if self.is_focused { Color::from_rgb8(0x25, 0x7A, 0xFD) } else { -- cgit From 1cd1582506810255394d2f9019597e9252bd8daa Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 17 Mar 2020 07:16:54 +0100 Subject: Add `modifiers` to `KeyPressEvent` in `pane_grid` --- examples/pane_grid/src/main.rs | 6 +++--- native/src/widget/pane_grid.rs | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 7ab68393..461ffc30 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -115,11 +115,11 @@ impl Sandbox for Example { } } -fn handle_hotkey(key_code: keyboard::KeyCode) -> Option { +fn handle_hotkey(event: pane_grid::KeyPressEvent) -> Option { use keyboard::KeyCode; use pane_grid::{Axis, Direction}; - let direction = match key_code { + let direction = match event.key_code { KeyCode::Up => Some(Direction::Up), KeyCode::Down => Some(Direction::Down), KeyCode::Left => Some(Direction::Left), @@ -127,7 +127,7 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option { _ => None, }; - match key_code { + match event.key_code { KeyCode::V => Some(Message::SplitFocused(Axis::Vertical)), KeyCode::H => Some(Message::SplitFocused(Axis::Horizontal)), KeyCode::W => Some(Message::CloseFocused), diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 8410f95c..5212a147 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -28,7 +28,7 @@ pub struct PaneGrid<'a, Message, Renderer> { modifier_keys: keyboard::ModifiersState, on_drag: Option Message>>, on_resize: Option Message>>, - on_key_press: Option Option>>, + on_key_press: Option Option>>, } impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { @@ -129,7 +129,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { pub fn on_key_press( mut self, - f: impl Fn(keyboard::KeyCode) -> Option + 'static, + f: impl Fn(KeyPressEvent) -> Option + 'static, ) -> Self { self.on_key_press = Some(Box::new(f)); self @@ -186,6 +186,12 @@ pub struct ResizeEvent { pub ratio: f32, } +#[derive(Debug, Clone, Copy)] +pub struct KeyPressEvent { + pub key_code: keyboard::KeyCode, + pub modifiers: keyboard::ModifiersState, +} + impl<'a, Message, Renderer> Widget for PaneGrid<'a, Message, Renderer> where @@ -369,7 +375,12 @@ where if state == ButtonState::Pressed { if let Some(_) = self.state.idle_pane() { if modifiers == self.modifier_keys { - if let Some(message) = on_key_press(key_code) { + if let Some(message) = + on_key_press(KeyPressEvent { + key_code, + modifiers, + }) + { messages.push(message); } } -- cgit From 05beb878527b4d4e3141ca5ba09337d6ada858be Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 17 Mar 2020 07:28:28 +0100 Subject: Move common keyboard types to `iced_core` Also expose them in `iced` through `iced_native` and `iced_web`. --- core/src/keyboard.rs | 6 + core/src/keyboard/key_code.rs | 198 +++++++++++++++++++++++++++ core/src/keyboard/modifiers_state.rs | 15 ++ core/src/lib.rs | 1 + examples/pane_grid/Cargo.toml | 1 - examples/pane_grid/src/main.rs | 6 +- native/src/input/keyboard.rs | 5 +- native/src/input/keyboard/key_code.rs | 198 --------------------------- native/src/input/keyboard/modifiers_state.rs | 15 -- src/keyboard.rs | 6 + src/lib.rs | 1 + web/src/lib.rs | 4 +- 12 files changed, 233 insertions(+), 223 deletions(-) create mode 100644 core/src/keyboard.rs create mode 100644 core/src/keyboard/key_code.rs create mode 100644 core/src/keyboard/modifiers_state.rs delete mode 100644 native/src/input/keyboard/key_code.rs delete mode 100644 native/src/input/keyboard/modifiers_state.rs create mode 100644 src/keyboard.rs diff --git a/core/src/keyboard.rs b/core/src/keyboard.rs new file mode 100644 index 00000000..d98b2989 --- /dev/null +++ b/core/src/keyboard.rs @@ -0,0 +1,6 @@ +//! Reuse basic keyboard types. +mod key_code; +mod modifiers_state; + +pub use key_code::KeyCode; +pub use modifiers_state::ModifiersState; diff --git a/core/src/keyboard/key_code.rs b/core/src/keyboard/key_code.rs new file mode 100644 index 00000000..26020a57 --- /dev/null +++ b/core/src/keyboard/key_code.rs @@ -0,0 +1,198 @@ +/// The symbolic name of a keyboard key. +/// +/// This is mostly the `KeyCode` type found in [`winit`]. +/// +/// [`winit`]: https://docs.rs/winit/0.20.0-alpha3/winit/ +#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)] +#[repr(u32)] +#[allow(missing_docs)] +pub enum KeyCode { + /// The '1' key over the letters. + Key1, + /// The '2' key over the letters. + Key2, + /// The '3' key over the letters. + Key3, + /// The '4' key over the letters. + Key4, + /// The '5' key over the letters. + Key5, + /// The '6' key over the letters. + Key6, + /// The '7' key over the letters. + Key7, + /// The '8' key over the letters. + Key8, + /// The '9' key over the letters. + Key9, + /// The '0' key over the 'O' and 'P' keys. + Key0, + + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + + /// The Escape key, next to F1 + Escape, + + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + + /// Print Screen/SysRq + Snapshot, + /// Scroll Lock + Scroll, + /// Pause/Break key, next to Scroll lock + Pause, + + /// `Insert`, next to Backspace + Insert, + Home, + Delete, + End, + PageDown, + PageUp, + + Left, + Up, + Right, + Down, + + Backspace, + Enter, + Space, + + /// The "Compose" key on Linux + Compose, + + Caret, + + Numlock, + Numpad0, + Numpad1, + Numpad2, + Numpad3, + Numpad4, + Numpad5, + Numpad6, + Numpad7, + Numpad8, + Numpad9, + + AbntC1, + AbntC2, + Add, + Apostrophe, + Apps, + At, + Ax, + Backslash, + Calculator, + Capital, + Colon, + Comma, + Convert, + Decimal, + Divide, + Equals, + Grave, + Kana, + Kanji, + LAlt, + LBracket, + LControl, + LShift, + LWin, + Mail, + MediaSelect, + MediaStop, + Minus, + Multiply, + Mute, + MyComputer, + NavigateForward, // also called "Prior" + NavigateBackward, // also called "Next" + NextTrack, + NoConvert, + NumpadComma, + NumpadEnter, + NumpadEquals, + OEM102, + Period, + PlayPause, + Power, + PrevTrack, + RAlt, + RBracket, + RControl, + RShift, + RWin, + Semicolon, + Slash, + Sleep, + Stop, + Subtract, + Sysrq, + Tab, + Underline, + Unlabeled, + VolumeDown, + VolumeUp, + Wake, + WebBack, + WebFavorites, + WebForward, + WebHome, + WebRefresh, + WebSearch, + WebStop, + Yen, + Copy, + Paste, + Cut, +} diff --git a/core/src/keyboard/modifiers_state.rs b/core/src/keyboard/modifiers_state.rs new file mode 100644 index 00000000..3058c065 --- /dev/null +++ b/core/src/keyboard/modifiers_state.rs @@ -0,0 +1,15 @@ +/// The current state of the keyboard modifiers. +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub struct ModifiersState { + /// Whether a shift key is pressed + pub shift: bool, + + /// Whether a control key is pressed + pub control: bool, + + /// Whether an alt key is pressed + pub alt: bool, + + /// Whether a logo key is pressed (e.g. windows key, command key...) + pub logo: bool, +} diff --git a/core/src/lib.rs b/core/src/lib.rs index ea5e8b43..c2887a0b 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -14,6 +14,7 @@ #![deny(unused_results)] #![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] +pub mod keyboard; mod align; mod background; diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index fb160a8d..3ed912ac 100644 --- a/examples/pane_grid/Cargo.toml +++ b/examples/pane_grid/Cargo.toml @@ -7,4 +7,3 @@ publish = false [dependencies] iced = { path = "../.." } -iced_native = { path = "../../native" } diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 461ffc30..9e6283ab 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,8 +1,8 @@ use iced::{ - button, pane_grid, scrollable, Align, Button, Column, Container, Element, - HorizontalAlignment, Length, PaneGrid, Sandbox, Scrollable, Settings, Text, + button, keyboard, pane_grid, scrollable, Align, Button, Column, Container, + Element, HorizontalAlignment, Length, PaneGrid, Sandbox, Scrollable, + Settings, Text, }; -use iced_native::input::keyboard; pub fn main() { Example::run(Settings::default()) diff --git a/native/src/input/keyboard.rs b/native/src/input/keyboard.rs index 432e75ba..928bf492 100644 --- a/native/src/input/keyboard.rs +++ b/native/src/input/keyboard.rs @@ -1,8 +1,5 @@ //! Build keyboard events. mod event; -mod key_code; -mod modifiers_state; pub use event::Event; -pub use key_code::KeyCode; -pub use modifiers_state::ModifiersState; +pub use iced_core::keyboard::{KeyCode, ModifiersState}; diff --git a/native/src/input/keyboard/key_code.rs b/native/src/input/keyboard/key_code.rs deleted file mode 100644 index 26020a57..00000000 --- a/native/src/input/keyboard/key_code.rs +++ /dev/null @@ -1,198 +0,0 @@ -/// The symbolic name of a keyboard key. -/// -/// This is mostly the `KeyCode` type found in [`winit`]. -/// -/// [`winit`]: https://docs.rs/winit/0.20.0-alpha3/winit/ -#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)] -#[repr(u32)] -#[allow(missing_docs)] -pub enum KeyCode { - /// The '1' key over the letters. - Key1, - /// The '2' key over the letters. - Key2, - /// The '3' key over the letters. - Key3, - /// The '4' key over the letters. - Key4, - /// The '5' key over the letters. - Key5, - /// The '6' key over the letters. - Key6, - /// The '7' key over the letters. - Key7, - /// The '8' key over the letters. - Key8, - /// The '9' key over the letters. - Key9, - /// The '0' key over the 'O' and 'P' keys. - Key0, - - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - - /// The Escape key, next to F1 - Escape, - - F1, - F2, - F3, - F4, - F5, - F6, - F7, - F8, - F9, - F10, - F11, - F12, - F13, - F14, - F15, - F16, - F17, - F18, - F19, - F20, - F21, - F22, - F23, - F24, - - /// Print Screen/SysRq - Snapshot, - /// Scroll Lock - Scroll, - /// Pause/Break key, next to Scroll lock - Pause, - - /// `Insert`, next to Backspace - Insert, - Home, - Delete, - End, - PageDown, - PageUp, - - Left, - Up, - Right, - Down, - - Backspace, - Enter, - Space, - - /// The "Compose" key on Linux - Compose, - - Caret, - - Numlock, - Numpad0, - Numpad1, - Numpad2, - Numpad3, - Numpad4, - Numpad5, - Numpad6, - Numpad7, - Numpad8, - Numpad9, - - AbntC1, - AbntC2, - Add, - Apostrophe, - Apps, - At, - Ax, - Backslash, - Calculator, - Capital, - Colon, - Comma, - Convert, - Decimal, - Divide, - Equals, - Grave, - Kana, - Kanji, - LAlt, - LBracket, - LControl, - LShift, - LWin, - Mail, - MediaSelect, - MediaStop, - Minus, - Multiply, - Mute, - MyComputer, - NavigateForward, // also called "Prior" - NavigateBackward, // also called "Next" - NextTrack, - NoConvert, - NumpadComma, - NumpadEnter, - NumpadEquals, - OEM102, - Period, - PlayPause, - Power, - PrevTrack, - RAlt, - RBracket, - RControl, - RShift, - RWin, - Semicolon, - Slash, - Sleep, - Stop, - Subtract, - Sysrq, - Tab, - Underline, - Unlabeled, - VolumeDown, - VolumeUp, - Wake, - WebBack, - WebFavorites, - WebForward, - WebHome, - WebRefresh, - WebSearch, - WebStop, - Yen, - Copy, - Paste, - Cut, -} diff --git a/native/src/input/keyboard/modifiers_state.rs b/native/src/input/keyboard/modifiers_state.rs deleted file mode 100644 index 3058c065..00000000 --- a/native/src/input/keyboard/modifiers_state.rs +++ /dev/null @@ -1,15 +0,0 @@ -/// The current state of the keyboard modifiers. -#[derive(Debug, Clone, Copy, PartialEq, Default)] -pub struct ModifiersState { - /// Whether a shift key is pressed - pub shift: bool, - - /// Whether a control key is pressed - pub control: bool, - - /// Whether an alt key is pressed - pub alt: bool, - - /// Whether a logo key is pressed (e.g. windows key, command key...) - pub logo: bool, -} diff --git a/src/keyboard.rs b/src/keyboard.rs new file mode 100644 index 00000000..181dd974 --- /dev/null +++ b/src/keyboard.rs @@ -0,0 +1,6 @@ +//! Listen and react to keyboard events. +#[cfg(not(target_arch = "wasm32"))] +pub use iced_winit::input::keyboard::{KeyCode, ModifiersState}; + +#[cfg(target_arch = "wasm32")] +pub use iced_web::keyboard::{KeyCode, ModifiersState}; diff --git a/src/lib.rs b/src/lib.rs index d492db02..aeec24c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -183,6 +183,7 @@ mod element; mod sandbox; pub mod executor; +pub mod keyboard; pub mod settings; pub mod widget; pub mod window; diff --git a/web/src/lib.rs b/web/src/lib.rs index 258ad9e7..1de00545 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, Size, - Vector, VerticalAlignment, + keyboard, Align, Background, Color, Font, HorizontalAlignment, Length, + Point, Size, Vector, VerticalAlignment, }; pub use iced_futures::{executor, futures, Command}; pub use subscription::Subscription; -- cgit From 20b142e8e3a674f27f9c9429449002086708ce11 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 18 Mar 2020 01:26:13 +0100 Subject: Make cursor unavailable when dragging panes --- wgpu/src/renderer/widget/pane_grid.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/wgpu/src/renderer/widget/pane_grid.rs b/wgpu/src/renderer/widget/pane_grid.rs index 741fe814..2d201fec 100644 --- a/wgpu/src/renderer/widget/pane_grid.rs +++ b/wgpu/src/renderer/widget/pane_grid.rs @@ -14,6 +14,14 @@ impl pane_grid::Renderer for Renderer { layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { + let pane_cursor_position = if dragging.is_some() { + // TODO: Remove once cursor availability is encoded in the type + // system + Point::new(-1.0, -1.0) + } else { + cursor_position + }; + let mut mouse_cursor = MouseCursor::OutOfBounds; let mut dragged_pane = None; @@ -23,7 +31,7 @@ impl pane_grid::Renderer for Renderer { .enumerate() .map(|(i, ((id, pane), layout))| { let (primitive, new_mouse_cursor) = - pane.draw(self, defaults, layout, cursor_position); + pane.draw(self, defaults, layout, pane_cursor_position); if new_mouse_cursor > mouse_cursor { mouse_cursor = new_mouse_cursor; -- cgit From b8a035d2dae43f590f681686d856b8b22630141b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 18 Mar 2020 01:27:23 +0100 Subject: Add some styling to `pane_grid` buttons --- examples/pane_grid/src/main.rs | 51 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 9e6283ab..c5dae016 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -167,7 +167,7 @@ impl Content { close, } = self; - let button = |state, label, message| { + let button = |state, label, message, style| { Button::new( state, Text::new(label) @@ -176,7 +176,9 @@ impl Content { .size(16), ) .width(Length::Fill) + .padding(8) .on_press(message) + .style(style) }; let mut controls = Column::new() @@ -186,16 +188,22 @@ impl Content { split_horizontally, "Split horizontally", Message::Split(pane_grid::Axis::Horizontal, pane), + style::Button::Primary, )) .push(button( split_vertically, "Split vertically", Message::Split(pane_grid::Axis::Vertical, pane), + style::Button::Primary, )); if total_panes > 1 { - controls = - controls.push(button(close, "Close", Message::Close(pane))); + controls = controls.push(button( + close, + "Close", + Message::Close(pane), + style::Button::Destructive, + )); } let content = Scrollable::new(scroll) @@ -217,7 +225,7 @@ impl Content { } mod style { - use iced::{container, Background, Color}; + use iced::{button, container, Background, Color, Vector}; pub struct Pane { pub is_focused: bool, @@ -237,4 +245,39 @@ mod style { } } } + + pub enum Button { + Primary, + Destructive, + } + + impl button::StyleSheet for Button { + fn active(&self) -> button::Style { + let color = match self { + Button::Primary => Color::from_rgb8(0x25, 0x7A, 0xFD), + Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2), + }; + + button::Style { + background: None, + border_color: color, + border_radius: 5, + border_width: 1, + shadow_offset: Vector::new(0.0, 0.0), + text_color: color, + ..button::Style::default() + } + } + + fn hovered(&self) -> button::Style { + let active = self.active(); + + button::Style { + background: Some(Background::Color(active.border_color)), + text_color: Color::WHITE, + border_width: 0, + ..active + } + } + } } -- cgit From 36abf7457fdc5a50b53f7e9ae63b978f07fbcda4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 18 Mar 2020 05:53:41 +0100 Subject: Improve styling of `pane_grid` example --- examples/pane_grid/src/main.rs | 59 +++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index c5dae016..dafc396c 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -101,7 +101,7 @@ impl Sandbox for Example { }) .width(Length::Fill) .height(Length::Fill) - .spacing(5) + .spacing(10) .on_drag(Message::Dragged) .on_resize(Message::Resized) .on_key_press(handle_hotkey); @@ -213,7 +213,7 @@ impl Content { .push(Text::new(format!("Pane {}", id)).size(30)) .push(controls); - Container::new(Column::new().padding(10).push(content)) + Container::new(Column::new().padding(5).push(content)) .width(Length::Fill) .height(Length::Fill) .center_y() @@ -227,6 +227,24 @@ impl Content { mod style { use iced::{button, container, Background, Color, Vector}; + const SURFACE: Color = Color::from_rgb( + 0xF2 as f32 / 255.0, + 0xF3 as f32 / 255.0, + 0xF5 as f32 / 255.0, + ); + + const ACTIVE: Color = Color::from_rgb( + 0x72 as f32 / 255.0, + 0x89 as f32 / 255.0, + 0xDA as f32 / 255.0, + ); + + const HOVERED: Color = Color::from_rgb( + 0x67 as f32 / 255.0, + 0x7B as f32 / 255.0, + 0xC4 as f32 / 255.0, + ); + pub struct Pane { pub is_focused: bool, } @@ -234,12 +252,11 @@ mod style { impl container::StyleSheet for Pane { fn style(&self) -> container::Style { container::Style { - background: Some(Background::Color(Color::WHITE)), - border_width: if self.is_focused { 2 } else { 1 }, - border_color: if self.is_focused { - Color::from_rgb8(0x25, 0x7A, 0xFD) - } else { - Color::BLACK + background: Some(Background::Color(SURFACE)), + border_width: 2, + border_color: Color { + a: if self.is_focused { 1.0 } else { 0.3 }, + ..Color::BLACK }, ..Default::default() } @@ -253,18 +270,18 @@ mod style { impl button::StyleSheet for Button { fn active(&self) -> button::Style { - let color = match self { - Button::Primary => Color::from_rgb8(0x25, 0x7A, 0xFD), - Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2), + let (background, text_color) = match self { + Button::Primary => (Some(ACTIVE), Color::WHITE), + Button::Destructive => { + (None, Color::from_rgb8(0xFF, 0x47, 0x47)) + } }; button::Style { - background: None, - border_color: color, + text_color, + background: background.map(Background::Color), border_radius: 5, - border_width: 1, shadow_offset: Vector::new(0.0, 0.0), - text_color: color, ..button::Style::default() } } @@ -272,10 +289,16 @@ mod style { fn hovered(&self) -> button::Style { let active = self.active(); + let background = match self { + Button::Primary => Some(HOVERED), + Button::Destructive => Some(Color { + a: 0.2, + ..active.text_color + }), + }; + button::Style { - background: Some(Background::Color(active.border_color)), - text_color: Color::WHITE, - border_width: 0, + background: background.map(Background::Color), ..active } } -- cgit From eba2ded88a92479bee93b727b31f5f84899339cf Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 18 Mar 2020 06:35:55 +0100 Subject: Update `README` of examples --- examples/README.md | 1 + examples/pane_grid/README.md | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/examples/README.md b/examples/README.md index 04399b93..a7673705 100644 --- a/examples/README.md +++ b/examples/README.md @@ -76,6 +76,7 @@ A bunch of simpler examples exist: - [`events`](events), a log of native events displayed using a conditional `Subscription`. - [`geometry`](geometry), a custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../wgpu). - [`integration`](integration), a demonstration of how to integrate Iced in an existing graphical application. +- [`pane_grid`](pane_grid), a grid of panes that can be split, resized, and reorganized. - [`pokedex`](pokedex), an application that displays a random Pokédex entry (sprite included!) by using the [PokéAPI]. - [`progress_bar`](progress_bar), a simple progress bar that can be filled by using a slider. - [`solar_system`](solar_system), an animated solar system drawn using the `Canvas` widget and showcasing how to compose different transforms. diff --git a/examples/pane_grid/README.md b/examples/pane_grid/README.md index 4d9fc5b9..3653fc5b 100644 --- a/examples/pane_grid/README.md +++ b/examples/pane_grid/README.md @@ -1,18 +1,28 @@ -## Counter +## Pane grid -The classic counter example explained in the [`README`](../../README.md). +A grid of panes that can be split, resized, and reorganized. + +This example showcases the `PaneGrid` widget, which features: + +* Vertical and horizontal splits +* Tracking of the last active pane +* Mouse-based resizing +* Drag and drop to reorganize panes +* Hotkey support +* Configurable modifier keys +* API to perform actions programmatically (`split`, `swap`, `resize`, etc.) The __[`main`]__ file contains all the code of the example. You can run it with `cargo run`: ``` -cargo run --package counter +cargo run --package pane_grid ``` [`main`]: src/main.rs -- cgit From 50b02d41a01ad66e08045b320a30a0f5d76ee2f9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 18 Mar 2020 07:10:36 +0100 Subject: Check only for partial match of modifier keys --- core/src/keyboard/modifiers_state.rs | 17 ++++++++++++++++- native/src/widget/pane_grid.rs | 8 +++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/core/src/keyboard/modifiers_state.rs b/core/src/keyboard/modifiers_state.rs index 3058c065..0cfc6d69 100644 --- a/core/src/keyboard/modifiers_state.rs +++ b/core/src/keyboard/modifiers_state.rs @@ -1,5 +1,5 @@ /// The current state of the keyboard modifiers. -#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct ModifiersState { /// Whether a shift key is pressed pub shift: bool, @@ -13,3 +13,18 @@ pub struct ModifiersState { /// Whether a logo key is pressed (e.g. windows key, command key...) pub logo: bool, } + +impl ModifiersState { + /// Returns true if the current [`ModifiersState`] has at least the same + /// modifiers enabled as the given value, and false otherwise. + /// + /// [`ModifiersState`]: struct.ModifiersState.html + pub fn matches(&self, modifiers: ModifiersState) -> bool { + let shift = !modifiers.shift || modifiers.shift && self.shift; + let control = !modifiers.control || modifiers.control && self.control; + let alt = !modifiers.alt || modifiers.alt && self.alt; + let logo = !modifiers.logo || modifiers.logo && self.logo; + + shift && control && alt && logo + } +} diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 5212a147..a2e4ebaa 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -260,7 +260,9 @@ where if let Some(((pane, _), _)) = clicked_region.next() { match &self.on_drag { Some(on_drag) - if *self.modifiers == self.modifier_keys => + if self + .modifiers + .matches(self.modifier_keys) => { self.state.pick_pane(pane); @@ -309,7 +311,7 @@ where state: ButtonState::Pressed, }) if self.on_resize.is_some() && self.state.picked_pane().is_none() - && *self.modifiers == self.modifier_keys => + && self.modifiers.matches(self.modifier_keys) => { let bounds = layout.bounds(); let relative_cursor = Point::new( @@ -374,7 +376,7 @@ where // TODO: Discard when event is captured if state == ButtonState::Pressed { if let Some(_) = self.state.idle_pane() { - if modifiers == self.modifier_keys { + if modifiers.matches(self.modifier_keys) { if let Some(message) = on_key_press(KeyPressEvent { key_code, -- cgit From a820b8ce7b192a496a1679a43d6fe4603dfc954b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 19 Mar 2020 08:21:23 +0100 Subject: Rename `PaneGrid::modifiers` to `pressed_modifiers` --- native/src/widget/pane_grid.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index a2e4ebaa..3e61642e 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -20,7 +20,7 @@ use crate::{ #[allow(missing_debug_implementations)] pub struct PaneGrid<'a, Message, Renderer> { state: &'a mut state::Internal, - modifiers: &'a mut keyboard::ModifiersState, + pressed_modifiers: &'a mut keyboard::ModifiersState, elements: Vec<(Pane, Element<'a, Message, Renderer>)>, width: Length, height: Length, @@ -64,7 +64,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { Self { state: &mut state.internal, - modifiers: &mut state.modifiers, + pressed_modifiers: &mut state.modifiers, elements, width: Length::Fill, height: Length::Fill, @@ -261,7 +261,7 @@ where match &self.on_drag { Some(on_drag) if self - .modifiers + .pressed_modifiers .matches(self.modifier_keys) => { self.state.pick_pane(pane); @@ -311,7 +311,7 @@ where state: ButtonState::Pressed, }) if self.on_resize.is_some() && self.state.picked_pane().is_none() - && self.modifiers.matches(self.modifier_keys) => + && self.pressed_modifiers.matches(self.modifier_keys) => { let bounds = layout.bounds(); let relative_cursor = Point::new( @@ -390,7 +390,7 @@ where } } - *self.modifiers = modifiers; + *self.pressed_modifiers = modifiers; } _ => {} } -- cgit From bd74c4e577de01b48064c7a01541ca2ad6d9ae16 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 19 Mar 2020 09:30:54 +0100 Subject: Write documentation for `pane_grid` --- native/src/lib.rs | 6 +- native/src/widget/pane_grid.rs | 160 +++++++++++++++++++++++++++++-- native/src/widget/pane_grid/axis.rs | 3 + native/src/widget/pane_grid/direction.rs | 5 + native/src/widget/pane_grid/pane.rs | 3 + native/src/widget/pane_grid/split.rs | 3 + native/src/widget/pane_grid/state.rs | 104 +++++++++++++++++++- 7 files changed, 273 insertions(+), 11 deletions(-) diff --git a/native/src/lib.rs b/native/src/lib.rs index 4551a982..d17dd918 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -21,8 +21,8 @@ //! # Usage //! The strategy to use this crate depends on your particular use case. If you //! want to: -//! - Implement a custom shell or integrate it in your own system, you should -//! check out the [`UserInterface`] type. +//! - Implement a custom shell or integrate it in your own system, check out the +//! [`UserInterface`] type. //! - Build a new renderer, see the [renderer] module. //! - Build a custom widget, start at the [`Widget`] trait. //! @@ -34,7 +34,7 @@ //! [`window::Renderer`]: window/trait.Renderer.html //! [`UserInterface`]: struct.UserInterface.html //! [renderer]: renderer/index.html -//#![deny(missing_docs)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 3e61642e..d33573ca 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -1,3 +1,6 @@ +//! Let your users split regions of your application and organize layout dynamically. +//! +//! [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) mod axis; mod direction; mod node; @@ -17,6 +20,57 @@ use crate::{ Widget, }; +/// A collection of panes distributed using either vertical or horizontal splits +/// to completely fill the space available. +/// +/// [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) +/// +/// This distribution of space is common in tiling window managers (like +/// [`awesome`](https://awesomewm.org/), [`i3`](https://i3wm.org/), or even +/// [`tmux`](https://github.com/tmux/tmux)). +/// +/// A [`PaneGrid`] supports: +/// +/// * Vertical and horizontal splits +/// * Tracking of the last active pane +/// * Mouse-based resizing +/// * Drag and drop to reorganize panes +/// * Hotkey support +/// * Configurable modifier keys +/// * [`State`] API to perform actions programmatically (`split`, `swap`, `resize`, etc.) +/// +/// ## Example +/// +/// ``` +/// # use iced_native::{pane_grid, Text}; +/// # +/// # type PaneGrid<'a, Message> = +/// # iced_native::PaneGrid<'a, Message, iced_native::renderer::Null>; +/// # +/// enum PaneState { +/// SomePane, +/// AnotherKindOfPane, +/// } +/// +/// enum Message { +/// PaneDragged(pane_grid::DragEvent), +/// PaneResized(pane_grid::ResizeEvent), +/// } +/// +/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane); +/// +/// let pane_grid = PaneGrid::new(&mut state, |pane, state, focus| { +/// match state { +/// PaneState::SomePane => Text::new("This is some pane"), +/// PaneState::AnotherKindOfPane => Text::new("This is another kind of pane"), +/// }.into() +/// }) +/// .on_drag(Message::PaneDragged) +/// .on_resize(Message::PaneResized); +/// ``` +/// +/// [`PaneGrid`]: struct.PaneGrid.html +/// [`State`]: struct.State.html #[allow(missing_debug_implementations)] pub struct PaneGrid<'a, Message, Renderer> { state: &'a mut state::Internal, @@ -32,6 +86,13 @@ pub struct PaneGrid<'a, Message, Renderer> { } impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { + /// Creates a [`PaneGrid`] with the given [`State`] and view function. + /// + /// The view function will be called to display each [`Pane`] present in the + /// [`State`]. + /// + /// [`PaneGrid`]: struct.PaneGrid.html + /// [`State`]: struct.State.html pub fn new( state: &'a mut State, view: impl Fn( @@ -81,7 +142,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { /// Sets the width of the [`PaneGrid`]. /// - /// [`PaneGrid`]: struct.Column.html + /// [`PaneGrid`]: struct.PaneGrid.html pub fn width(mut self, width: Length) -> Self { self.width = width; self @@ -89,7 +150,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { /// Sets the height of the [`PaneGrid`]. /// - /// [`PaneGrid`]: struct.Column.html + /// [`PaneGrid`]: struct.PaneGrid.html pub fn height(mut self, height: Length) -> Self { self.height = height; self @@ -97,12 +158,20 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { /// Sets the spacing _between_ the panes of the [`PaneGrid`]. /// - /// [`PaneGrid`]: struct.Column.html + /// [`PaneGrid`]: struct.PaneGrid.html pub fn spacing(mut self, units: u16) -> Self { self.spacing = units; self } + /// Sets the modifier keys of the [`PaneGrid`]. + /// + /// The modifier keys will need to be pressed to trigger dragging, resizing, + /// and key events. + /// + /// The default modifier key is `Ctrl`. + /// + /// [`PaneGrid`]: struct.PaneGrid.html pub fn modifier_keys( mut self, modifier_keys: keyboard::ModifiersState, @@ -111,6 +180,12 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self } + /// Enables the drag and drop interactions of the [`PaneGrid`], which will + /// use the provided function to produce messages. + /// + /// Panes can be dragged using `Modifier keys + Left click`. + /// + /// [`PaneGrid`]: struct.PaneGrid.html pub fn on_drag( mut self, f: impl Fn(DragEvent) -> Message + 'static, @@ -119,6 +194,12 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self } + /// Enables the resize interactions of the [`PaneGrid`], which will + /// use the provided function to produce messages. + /// + /// Panes can be resized using `Modifier keys + Right click`. + /// + /// [`PaneGrid`]: struct.PaneGrid.html pub fn on_resize( mut self, f: impl Fn(ResizeEvent) -> Message + 'static, @@ -127,6 +208,23 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self } + /// Captures hotkey interactions with the [`PaneGrid`], using the provided + /// function to produce messages. + /// + /// The function will be called when: + /// - a [`Pane`] is focused + /// - a key is pressed + /// - all the modifier keys are pressed + /// + /// If the function returns `None`, the key press event will be discarded + /// without producing any message. + /// + /// This function is particularly useful to implement hotkey interactions. + /// For instance, you can use it to enable splitting, swapping, or resizing + /// panes by pressing combinations of keys. + /// + /// [`PaneGrid`]: struct.PaneGrid.html + /// [`Pane`]: struct.Pane.html pub fn on_key_press( mut self, f: impl Fn(KeyPressEvent) -> Option + 'static, @@ -173,22 +271,72 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { } } +/// An event produced during a drag and drop interaction of a [`PaneGrid`]. +/// +/// [`PaneGrid`]: struct.PaneGrid.html #[derive(Debug, Clone, Copy)] pub enum DragEvent { - Picked { pane: Pane }, - Dropped { pane: Pane, target: Pane }, - Canceled { pane: Pane }, + /// A [`Pane`] was picked for dragging. + /// + /// [`Pane`]: struct.Pane.html + Picked { + /// The picked [`Pane`]. + /// + /// [`Pane`]: struct.Pane.html + pane: Pane, + }, + + /// A [`Pane`] was dropped on top of another [`Pane`]. + /// + /// [`Pane`]: struct.Pane.html + Dropped { + /// The picked [`Pane`]. + /// + /// [`Pane`]: struct.Pane.html + pane: Pane, + + /// The [`Pane`] where the picked one was dropped on. + /// + /// [`Pane`]: struct.Pane.html + target: Pane, + }, + + /// A [`Pane`] was picked and then dropped outside of other [`Pane`] + /// boundaries. + /// + /// [`Pane`]: struct.Pane.html + Canceled { + /// The picked [`Pane`]. + /// + /// [`Pane`]: struct.Pane.html + pane: Pane, + }, } +/// An event produced during a resize interaction of a [`PaneGrid`]. +/// +/// [`PaneGrid`]: struct.PaneGrid.html #[derive(Debug, Clone, Copy)] pub struct ResizeEvent { + /// The [`Split`] that is being dragged for resizing. pub split: Split, + + /// The new ratio of the [`Split`]. + /// + /// The ratio is a value in [0, 1], representing the exact position of a + /// [`Split`] between two panes. pub ratio: f32, } +/// An event produced during a key press interaction of a [`PaneGrid`]. +/// +/// [`PaneGrid`]: struct.PaneGrid.html #[derive(Debug, Clone, Copy)] pub struct KeyPressEvent { + /// The key that was pressed. pub key_code: keyboard::KeyCode, + + /// The state of the modifier keys when the key was pressed. pub modifiers: keyboard::ModifiersState, } diff --git a/native/src/widget/pane_grid/axis.rs b/native/src/widget/pane_grid/axis.rs index a17d0c12..f0e3f362 100644 --- a/native/src/widget/pane_grid/axis.rs +++ b/native/src/widget/pane_grid/axis.rs @@ -1,8 +1,11 @@ use crate::Rectangle; +/// A fixed reference line for the measurement of coordinates. #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub enum Axis { + /// The horizontal axis: — Horizontal, + /// The vertical axis: | Vertical, } diff --git a/native/src/widget/pane_grid/direction.rs b/native/src/widget/pane_grid/direction.rs index 0ee90557..b31a8737 100644 --- a/native/src/widget/pane_grid/direction.rs +++ b/native/src/widget/pane_grid/direction.rs @@ -1,7 +1,12 @@ +/// A four cardinal direction. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Direction { + /// ↑ Up, + /// ↓ Down, + /// ← Left, + /// → Right, } diff --git a/native/src/widget/pane_grid/pane.rs b/native/src/widget/pane_grid/pane.rs index cfca3b03..f9866407 100644 --- a/native/src/widget/pane_grid/pane.rs +++ b/native/src/widget/pane_grid/pane.rs @@ -1,2 +1,5 @@ +/// A rectangular region in a [`PaneGrid`] used to display widgets. +/// +/// [`PaneGrid`]: struct.PaneGrid.html #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Pane(pub(super) usize); diff --git a/native/src/widget/pane_grid/split.rs b/native/src/widget/pane_grid/split.rs index c2dad980..d020c510 100644 --- a/native/src/widget/pane_grid/split.rs +++ b/native/src/widget/pane_grid/split.rs @@ -1,2 +1,5 @@ +/// A divider that splits a region in a [`PaneGrid`] into two different panes. +/// +/// [`PaneGrid`]: struct.PaneGrid.html #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Split(pub(super) usize); diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 9103dcd0..6c80cacc 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -6,6 +6,20 @@ use crate::{ use std::collections::HashMap; +/// The state of a [`PaneGrid`]. +/// +/// It keeps track of the state of each [`Pane`] and the position of each +/// [`Split`]. +/// +/// The [`State`] needs to own any mutable contents a [`Pane`] may need. This is +/// why this struct is generic over the type `T`. Values of this type are +/// provided to the view function of [`PaneGrid::new`] for displaying each +/// [`Pane`]. +/// +/// [`PaneGrid`]: struct.PaneGrid.html +/// [`PaneGrid::new`]: struct.PaneGrid.html#method.new +/// [`State`]: struct.State.html +/// [`Pane`]: struct.Pane.html #[derive(Debug)] pub struct State { pub(super) panes: HashMap, @@ -13,13 +27,28 @@ pub struct State { pub(super) modifiers: keyboard::ModifiersState, } +/// The current focus of a [`Pane`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Focus { + /// The [`Pane`] is just focused. + /// + /// [`Pane`]: struct.Pane.html Idle, + + /// The [`Pane`] is being dragged. + /// + /// [`Pane`]: struct.Pane.html Dragging, } impl State { + /// Creates a new [`State`], initializing the first pane with the provided + /// state. + /// + /// Alongside the [`State`], it returns the first [`Pane`] identifier. + /// + /// [`State`]: struct.State.html + /// [`Pane`]: struct.Pane.html pub fn new(first_pane_state: T) -> (Self, Pane) { let first_pane = Pane(0); @@ -40,22 +69,42 @@ impl State { ) } + /// Returns the total amount of panes in the [`State`]. + /// + /// [`State`]: struct.State.html pub fn len(&self) -> usize { self.panes.len() } + /// Returns the internal state of the given [`Pane`], if it exists. + /// + /// [`Pane`]: struct.Pane.html pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> { self.panes.get_mut(pane) } + /// Returns an iterator over all the panes of the [`State`], alongside its + /// internal state. + /// + /// [`State`]: struct.State.html pub fn iter(&self) -> impl Iterator { self.panes.iter() } + /// Returns a mutable iterator over all the panes of the [`State`], + /// alongside its internal state. + /// + /// [`State`]: struct.State.html pub fn iter_mut(&mut self) -> impl Iterator { self.panes.iter_mut() } + /// Returns the active [`Pane`] of the [`State`], if there is one. + /// + /// A [`Pane`] is active if it is focused and is __not__ being dragged. + /// + /// [`Pane`]: struct.Pane.html + /// [`State`]: struct.State.html pub fn active(&self) -> Option { match self.internal.action { Action::Idle { focus } => focus, @@ -63,6 +112,27 @@ impl State { } } + /// Returns the adjacent [`Pane`] of another [`Pane`] in the given + /// direction, if there is one. + /// + /// ## Example + /// You can combine this with [`State::active`] to find the pane that is + /// adjacent to the current active one, and then swap them. For instance: + /// + /// ``` + /// # use iced_native::pane_grid; + /// # + /// # let (mut state, _) = pane_grid::State::new(()); + /// # + /// if let Some(active) = state.active() { + /// if let Some(adjacent) = state.adjacent(&active, pane_grid::Direction::Right) { + /// state.swap(&active, &adjacent); + /// } + /// } + /// ``` + /// + /// [`Pane`]: struct.Pane.html + /// [`State::active`]: struct.State.html#method.active pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option { let regions = self.internal.layout.regions(0.0, Size::new(4096.0, 4096.0)); @@ -94,10 +164,18 @@ impl State { Some(*pane) } + /// Focuses the given [`Pane`]. + /// + /// [`Pane`]: struct.Pane.html pub fn focus(&mut self, pane: &Pane) { self.internal.focus(pane); } + /// Splits the given [`Pane`] into two in the given [`Axis`] and + /// initializing the new [`Pane`] with the provided internal state. + /// + /// [`Pane`]: struct.Pane.html + /// [`Axis`]: enum.Axis.html pub fn split(&mut self, axis: Axis, pane: &Pane, state: T) -> Option { let node = self.internal.layout.find(pane)?; @@ -121,6 +199,14 @@ impl State { Some(new_pane) } + /// Swaps the position of the provided panes in the [`State`]. + /// + /// If you want to swap panes on drag and drop in your [`PaneGrid`], you + /// will need to call this method when handling a [`DragEvent`]. + /// + /// [`State`]: struct.State.html + /// [`PaneGrid`]: struct.PaneGrid.html + /// [`DragEvent`]: struct.DragEvent.html pub fn swap(&mut self, a: &Pane, b: &Pane) { self.internal.layout.update(&|node| match node { Node::Split { .. } => {} @@ -134,10 +220,24 @@ impl State { }); } - pub fn resize(&mut self, split: &Split, percentage: f32) { - let _ = self.internal.layout.resize(split, percentage); + /// Resizes two panes by setting the position of the provided [`Split`]. + /// + /// The ratio is a value in [0, 1], representing the exact position of a + /// [`Split`] between two panes. + /// + /// If you want to enable resize interactions in your [`PaneGrid`], you will + /// need to call this method when handling a [`ResizeEvent`]. + /// + /// [`Split`]: struct.Split.html + /// [`PaneGrid`]: struct.PaneGrid.html + /// [`ResizeEvent`]: struct.ResizeEvent.html + pub fn resize(&mut self, split: &Split, ratio: f32) { + let _ = self.internal.layout.resize(split, ratio); } + /// Closes the given [`Pane`] and returns its internal state, if it exists. + /// + /// [`Pane`]: struct.Pane.html pub fn close(&mut self, pane: &Pane) -> Option { if let Some(sibling) = self.internal.layout.remove(pane) { self.focus(&sibling); -- cgit From bb898fa2e277d46642feec397efd753f133a0aae Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 19 Mar 2020 09:37:13 +0100 Subject: Create `PaneGrid` alias in `iced_wgpu` --- src/widget.rs | 7 ++++--- wgpu/src/widget.rs | 3 +++ wgpu/src/widget/pane_grid.rs | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 wgpu/src/widget/pane_grid.rs diff --git a/src/widget.rs b/src/widget.rs index cb099a65..91ea1ed4 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -30,13 +30,14 @@ mod platform { pub use iced_winit::svg::{Handle, Svg}; } - pub use iced_winit::{pane_grid, PaneGrid, Text}; + pub use iced_winit::Text; #[doc(no_inline)] pub use { button::Button, checkbox::Checkbox, container::Container, image::Image, - progress_bar::ProgressBar, radio::Radio, scrollable::Scrollable, - slider::Slider, svg::Svg, text_input::TextInput, + pane_grid::PaneGrid, progress_bar::ProgressBar, radio::Radio, + scrollable::Scrollable, slider::Slider, svg::Svg, + text_input::TextInput, }; /// A container that distributes its contents vertically. diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index 73cce7e2..b39f2d91 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -10,6 +10,7 @@ pub mod button; pub mod checkbox; pub mod container; +pub mod pane_grid; pub mod progress_bar; pub mod radio; pub mod scrollable; @@ -23,6 +24,8 @@ pub use checkbox::Checkbox; #[doc(no_inline)] pub use container::Container; #[doc(no_inline)] +pub use pane_grid::PaneGrid; +#[doc(no_inline)] pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; diff --git a/wgpu/src/widget/pane_grid.rs b/wgpu/src/widget/pane_grid.rs new file mode 100644 index 00000000..7bc2f7c5 --- /dev/null +++ b/wgpu/src/widget/pane_grid.rs @@ -0,0 +1,17 @@ +//! Let your users split regions of your application and organize layout dynamically. +//! +//! [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) +use crate::Renderer; + +pub use iced_native::pane_grid::{ + Axis, Direction, DragEvent, Focus, KeyPressEvent, Pane, ResizeEvent, Split, + State, +}; + +/// A collection of panes distributed using either vertical or horizontal splits +/// to completely fill the space available. +/// +/// [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish) +/// +/// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`. +pub type PaneGrid<'a, Message> = iced_native::PaneGrid<'a, Message, Renderer>; -- cgit From 420275793e04b41254bacdaedd8ca60fb2ffe63f Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 19 Mar 2020 09:43:36 +0100 Subject: Fix minor documentation issues in `pane_grid` --- native/src/widget/pane_grid.rs | 7 ++++++- native/src/widget/pane_grid/state.rs | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index d33573ca..5ced6610 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -93,6 +93,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { /// /// [`PaneGrid`]: struct.PaneGrid.html /// [`State`]: struct.State.html + /// [`Pane`]: struct.Pane.html pub fn new( state: &'a mut State, view: impl Fn( @@ -219,7 +220,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { /// If the function returns `None`, the key press event will be discarded /// without producing any message. /// - /// This function is particularly useful to implement hotkey interactions. + /// This method is particularly useful to implement hotkey interactions. /// For instance, you can use it to enable splitting, swapping, or resizing /// panes by pressing combinations of keys. /// @@ -319,12 +320,16 @@ pub enum DragEvent { #[derive(Debug, Clone, Copy)] pub struct ResizeEvent { /// The [`Split`] that is being dragged for resizing. + /// + /// [`Split`]: struct.Split.html pub split: Split, /// The new ratio of the [`Split`]. /// /// The ratio is a value in [0, 1], representing the exact position of a /// [`Split`] between two panes. + /// + /// [`Split`]: struct.Split.html pub ratio: f32, } diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 6c80cacc..0e528d90 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -18,8 +18,9 @@ use std::collections::HashMap; /// /// [`PaneGrid`]: struct.PaneGrid.html /// [`PaneGrid::new`]: struct.PaneGrid.html#method.new -/// [`State`]: struct.State.html /// [`Pane`]: struct.Pane.html +/// [`Split`]: struct.Split.html +/// [`State`]: struct.State.html #[derive(Debug)] pub struct State { pub(super) panes: HashMap, @@ -28,6 +29,8 @@ pub struct State { } /// The current focus of a [`Pane`]. +/// +/// [`Pane`]: struct.Pane.html #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Focus { /// The [`Pane`] is just focused. -- cgit From 18f016cba70bf59095ae65ce0e289d80a548ae58 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 20 Mar 2020 04:08:18 +0100 Subject: Use `f32::hypot` in `Point::distance` --- core/src/point.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/point.rs b/core/src/point.rs index b855cd91..43ee2143 100644 --- a/core/src/point.rs +++ b/core/src/point.rs @@ -30,7 +30,7 @@ impl Point { let a = self.x - to.x; let b = self.y - to.y; - f32::sqrt(a * a + b * b) + a.hypot(b) } } -- cgit From 31aaf207d6772e2ef332bf523cde262cac118d1a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 20 Mar 2020 04:10:58 +0100 Subject: Remove redundant check in `ModifiersState::matches` --- core/src/keyboard/modifiers_state.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/keyboard/modifiers_state.rs b/core/src/keyboard/modifiers_state.rs index 0cfc6d69..4d24266f 100644 --- a/core/src/keyboard/modifiers_state.rs +++ b/core/src/keyboard/modifiers_state.rs @@ -20,10 +20,10 @@ impl ModifiersState { /// /// [`ModifiersState`]: struct.ModifiersState.html pub fn matches(&self, modifiers: ModifiersState) -> bool { - let shift = !modifiers.shift || modifiers.shift && self.shift; - let control = !modifiers.control || modifiers.control && self.control; - let alt = !modifiers.alt || modifiers.alt && self.alt; - let logo = !modifiers.logo || modifiers.logo && self.logo; + let shift = !modifiers.shift || self.shift; + let control = !modifiers.control || self.control; + let alt = !modifiers.alt || self.alt; + let logo = !modifiers.logo || self.logo; shift && control && alt && logo } -- cgit From 93f5640a2dc06d8f1bf2b0d033d45c62f8985380 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 20 Mar 2020 05:21:46 +0100 Subject: Fix `wasm-bindgen` command in `README` of `iced_web` --- web/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/README.md b/web/README.md index aceb4a97..83d1500f 100644 --- a/web/README.md +++ b/web/README.md @@ -36,7 +36,7 @@ For instance, let's say we want to build the [`tour` example]: ``` cd examples cargo build --package tour --target wasm32-unknown-unknown -wasm-bindgen ../target/wasm32-unknown-unknown/debug/examples/tour.wasm --out-dir tour --web +wasm-bindgen ../target/wasm32-unknown-unknown/debug/tour.wasm --out-dir tour --web ``` *__Note:__ Keep in mind that Iced is still in early exploration stages and most of the work needs to happen on the native side of the ecosystem. At this stage, it is important to be able to batch work without having to constantly jump back and forth. Because of this, there is currently no requirement for the `master` branch to contain a cross-platform API at all times. If you hit an issue when building an example and want to help, it may be a good way to [start contributing]!* -- cgit From 33f33ed4e32932175f1a77a0d8b59b81380ccf74 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 20 Mar 2020 11:53:08 +0100 Subject: Check cursor is in-bounds before resizing panes --- native/src/widget/pane_grid.rs | 83 ++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 5ced6610..5b609b56 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -467,48 +467,53 @@ where && self.pressed_modifiers.matches(self.modifier_keys) => { let bounds = layout.bounds(); - let relative_cursor = Point::new( - cursor_position.x - bounds.x, - cursor_position.y - bounds.y, - ); - - let splits = self.state.splits( - f32::from(self.spacing), - Size::new(bounds.width, bounds.height), - ); - let mut sorted_splits: Vec<_> = splits - .iter() - .filter(|(_, (axis, rectangle, _))| match axis { - Axis::Horizontal => { - relative_cursor.x > rectangle.x - && relative_cursor.x - < rectangle.x + rectangle.width - } - Axis::Vertical => { - relative_cursor.y > rectangle.y - && relative_cursor.y - < rectangle.y + rectangle.height - } - }) - .collect(); - - sorted_splits.sort_by_key(|(_, (axis, rectangle, ratio))| { - let distance = match axis { - Axis::Horizontal => (relative_cursor.y - - (rectangle.y + rectangle.height * ratio)) - .abs(), - Axis::Vertical => (relative_cursor.x - - (rectangle.x + rectangle.width * ratio)) - .abs(), - }; + if bounds.contains(cursor_position) { + let relative_cursor = Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ); + + let splits = self.state.splits( + f32::from(self.spacing), + Size::new(bounds.width, bounds.height), + ); + + let mut sorted_splits: Vec<_> = splits + .iter() + .filter(|(_, (axis, rectangle, _))| match axis { + Axis::Horizontal => { + relative_cursor.x > rectangle.x + && relative_cursor.x + < rectangle.x + rectangle.width + } + Axis::Vertical => { + relative_cursor.y > rectangle.y + && relative_cursor.y + < rectangle.y + rectangle.height + } + }) + .collect(); + + sorted_splits.sort_by_key( + |(_, (axis, rectangle, ratio))| { + let distance = match axis { + Axis::Horizontal => (relative_cursor.y + - (rectangle.y + rectangle.height * ratio)) + .abs(), + Axis::Vertical => (relative_cursor.x + - (rectangle.x + rectangle.width * ratio)) + .abs(), + }; - distance.round() as u32 - }); + distance.round() as u32 + }, + ); - if let Some((split, (axis, _, _))) = sorted_splits.first() { - self.state.pick_split(split, *axis); - self.trigger_resize(layout, cursor_position, messages); + if let Some((split, (axis, _, _))) = sorted_splits.first() { + self.state.pick_split(split, *axis); + self.trigger_resize(layout, cursor_position, messages); + } } } Event::Mouse(mouse::Event::Input { -- cgit From cfc2b55e05a3dc20eae71088d0475f82e34ea36b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 20 Mar 2020 11:54:42 +0100 Subject: Rename `Internal::idle_pane` to `active_pane` --- native/src/widget/pane_grid.rs | 2 +- native/src/widget/pane_grid/state.rs | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 5b609b56..7e547ccb 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -533,7 +533,7 @@ where if let Some(on_key_press) = &self.on_key_press { // TODO: Discard when event is captured if state == ButtonState::Pressed { - if let Some(_) = self.state.idle_pane() { + if let Some(_) = self.state.active_pane() { if modifiers.matches(self.modifier_keys) { if let Some(message) = on_key_press(KeyPressEvent { diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 0e528d90..0a8b8419 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -109,10 +109,7 @@ impl State { /// [`Pane`]: struct.Pane.html /// [`State`]: struct.State.html pub fn active(&self) -> Option { - match self.internal.action { - Action::Idle { focus } => focus, - _ => None, - } + self.internal.active_pane() } /// Returns the adjacent [`Pane`] of another [`Pane`] in the given @@ -289,7 +286,7 @@ impl Internal { self.action } - pub fn idle_pane(&self) -> Option { + pub fn active_pane(&self) -> Option { match self.action { Action::Idle { focus } => focus, _ => None, -- cgit From fb744a338c1b7566a3db9a3d24c03729b4858217 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 20 Mar 2020 11:56:39 +0100 Subject: Fix links in `pane_grid` documentation --- native/src/widget/column.rs | 2 +- native/src/widget/pane_grid.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index a7a6f242..b1adc6e3 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -219,7 +219,7 @@ pub trait Renderer: crate::Renderer + Sized { /// - the [`Layout`] of the [`Column`] and its children /// - the cursor position /// - /// [`Column`]: struct.Row.html + /// [`Column`]: struct.Column.html /// [`Layout`]: ../layout/struct.Layout.html fn draw( &mut self, diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 7e547ccb..a88f591a 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -615,10 +615,12 @@ pub trait Renderer: crate::Renderer + Sized { /// It receives: /// - the elements of the [`PaneGrid`] /// - the [`Pane`] that is currently being dragged + /// - the [`Axis`] that is currently being resized /// - the [`Layout`] of the [`PaneGrid`] and its elements /// - the cursor position /// - /// [`Column`]: struct.Row.html + /// [`PaneGrid`]: struct.PaneGrid.html + /// [`Pane`]: struct.Pane.html /// [`Layout`]: ../layout/struct.Layout.html fn draw( &mut self, -- cgit From e5d264caf05b9d3ed71cf690d754e74d68d3103b Mon Sep 17 00:00:00 2001 From: Mark Friedenbach Date: Fri, 20 Mar 2020 15:51:05 -0700 Subject: Remove excess whitespace from end of line to comply with `cargo fmt`. --- native/src/widget/container.rs | 2 +- native/src/widget/image.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs index 3459a832..d1cbb32e 100644 --- a/native/src/widget/container.rs +++ b/native/src/widget/container.rs @@ -77,7 +77,7 @@ where self.max_height = max_height; self } - + /// Sets the content alignment for the horizontal axis of the [`Container`]. /// /// [`Container`]: struct.Container.html diff --git a/native/src/widget/image.rs b/native/src/widget/image.rs index fbe38bfc..5b067687 100644 --- a/native/src/widget/image.rs +++ b/native/src/widget/image.rs @@ -126,7 +126,7 @@ impl Handle { } /// Creates an image [`Handle`] containing the image pixels directly. This - /// function expects the input data to be provided as a `Vec` of BGRA + /// function expects the input data to be provided as a `Vec` of BGRA /// pixels. /// /// This is useful if you have already decoded your image. -- cgit From e9ac53bb9a64682c4c32f86cf2ec674e2a609e19 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 21 Mar 2020 05:36:51 +0100 Subject: Add workflow to check format in CI --- .github/workflows/format.yml | 12 ++++++++++++ rustfmt.toml | 2 -- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/format.yml diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 00000000..55bc79d9 --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,12 @@ +name: Format +on: [push, pull_request] +jobs: + native: + runs-on: ubuntu-latest + steps: + - uses: hecrj/setup-rust-action@v1 + with: + components: rustfmt + - uses: actions/checkout@master + - name: Check format + run: cargo fmt --all -- --check diff --git a/rustfmt.toml b/rustfmt.toml index 7e5dded7..d979d317 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,3 +1 @@ max_width=80 -wrap_comments=true -merge_imports=true -- cgit From b2242c571fe2e809e4f471bdac500e21dd6584e9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 21 Mar 2020 05:39:07 +0100 Subject: Rename job in format workflow to `all` --- .github/workflows/format.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 55bc79d9..92caff79 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -1,7 +1,7 @@ name: Format on: [push, pull_request] jobs: - native: + all: runs-on: ubuntu-latest steps: - uses: hecrj/setup-rust-action@v1 -- cgit From fff333f89ba99f32171641f0e8d78c9cdfe291b4 Mon Sep 17 00:00:00 2001 From: Songtronix Date: Mon, 23 Mar 2020 15:54:23 +0100 Subject: Add example for download with progress tracking --- Cargo.toml | 1 + examples/download_progress/Cargo.toml | 13 +++ examples/download_progress/README.md | 15 ++++ examples/download_progress/src/downloader.rs | 99 +++++++++++++++++++++++ examples/download_progress/src/main.rs | 116 +++++++++++++++++++++++++++ 5 files changed, 244 insertions(+) create mode 100644 examples/download_progress/Cargo.toml create mode 100644 examples/download_progress/README.md create mode 100644 examples/download_progress/src/downloader.rs create mode 100644 examples/download_progress/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 12b75aed..d2444486 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ members = [ "web", "wgpu", "winit", + "examples/download_progress", "examples/bezier_tool", "examples/clock", "examples/counter", diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml new file mode 100644 index 00000000..ce0435fd --- /dev/null +++ b/examples/download_progress/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "download_progress" +version = "0.1.0" +authors = ["Songtronix "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } +iced_native = { path = "../../native" } +iced_futures = { path = "../../futures" } +async-std = { version = "1.0", features = ["unstable"] } +isahc = "0.9.1" diff --git a/examples/download_progress/README.md b/examples/download_progress/README.md new file mode 100644 index 00000000..c6311163 --- /dev/null +++ b/examples/download_progress/README.md @@ -0,0 +1,15 @@ +## Download Progress + +Downloading a file asynchronously with a `Subscription` while displaying the progress with a `ProgressBar`. + + + +You can run it with `cargo run`: + +``` +cargo run --package download_progress +``` diff --git a/examples/download_progress/src/downloader.rs b/examples/download_progress/src/downloader.rs new file mode 100644 index 00000000..62f943fd --- /dev/null +++ b/examples/download_progress/src/downloader.rs @@ -0,0 +1,99 @@ +use iced_futures::futures; + +// Just a little utility function +pub fn file(url: T) -> iced::Subscription { + iced::Subscription::from_recipe(Downloader { + url: url.to_string(), + }) +} + +pub struct Downloader { + url: String, +} + +// Make sure iced can use our download stream +impl iced_native::subscription::Recipe for Downloader +where + H: std::hash::Hasher, +{ + type Output = DownloadMessage; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + std::any::TypeId::of::().hash(state); + } + + fn stream( + self: Box, + _input: futures::stream::BoxStream<'static, I>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use isahc::prelude::*; + + Box::pin(futures::stream::unfold( + DownloadState::Ready(self.url), + |state| async move { + match state { + DownloadState::Ready(url) => { + let resp = Request::get(&url) + .metrics(true) + .body(()) + .unwrap() + .send_async() + .await + .unwrap(); + let metrics = resp.metrics().unwrap().clone(); + // If you actually want to download: + /*let file = async_std::fs::File::create("download.bin") + .await + .unwrap();*/ + + async_std::task::spawn(async_std::io::copy( + resp.into_body(), + async_std::io::sink(), //file + )); + + Some(( + DownloadMessage::DownloadStarted, + DownloadState::Downloading(metrics), + )) + } + DownloadState::Downloading(metrics) => { + async_std::task::sleep( + std::time::Duration::from_millis(100), + ) + .await; + + let percentage = metrics.download_progress().0 * 100 + / metrics.download_progress().1; + + if percentage == 100 { + Some(( + DownloadMessage::Done, + DownloadState::Finished, + )) + } else { + Some(( + DownloadMessage::Downloading(percentage), + DownloadState::Downloading(metrics), + )) + } + } + DownloadState::Finished => None, + } + }, + )) + } +} + +#[derive(Debug)] +pub enum DownloadMessage { + DownloadStarted, + Downloading(u64), + Done, +} + +pub enum DownloadState { + Ready(String), + Downloading(isahc::Metrics), + Finished, +} diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs new file mode 100644 index 00000000..936144d5 --- /dev/null +++ b/examples/download_progress/src/main.rs @@ -0,0 +1,116 @@ +use iced::{ + button, executor, Align, Application, Button, Column, Command, Container, + Element, Length, ProgressBar, Settings, Subscription, Text, +}; + +mod downloader; + +pub fn main() { + Downloader::run(Settings::default()) +} + +#[derive(Debug, Default)] +struct Downloader { + // Whether to start the download or not. + enabled: bool, + // The current percentage of the download + current_progress: u64, + + btn_state: button::State, +} + +#[derive(Debug)] +pub enum Message { + DownloadUpdate(downloader::DownloadMessage), + Interaction(Interaction), +} + +// For explanation of why we use an Interaction enum see here: +// https://github.com/hecrj/iced/pull/155#issuecomment-573523405 +#[derive(Debug, Clone)] +pub enum Interaction { + // User pressed the button to start the download + StartDownload, +} + +impl Application for Downloader { + type Executor = executor::Default; + type Message = Message; + + fn new() -> (Downloader, Command) { + (Downloader::default(), Command::none()) + } + + fn title(&self) -> String { + String::from("Download Progress - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Interaction(action) => match action { + Interaction::StartDownload => { + self.enabled = true; + } + }, + Message::DownloadUpdate(update) => match update { + downloader::DownloadMessage::Downloading(percentage) => { + self.current_progress = percentage; + } + downloader::DownloadMessage::Done => { + self.current_progress = 100; + self.enabled = false; + } + _ => {} + }, + }; + + Command::none() + } + + fn subscription(&self) -> Subscription { + if self.enabled { + downloader::file("https://speed.hetzner.de/100MB.bin") + .map(Message::DownloadUpdate) + } else { + Subscription::none() + } + } + + fn view(&mut self) -> Element { + // Construct widgets + + let toggle_text = match self.enabled { + true => "Downloading...", + false => "Start the download!", + }; + + let toggle: Element = + Button::new(&mut self.btn_state, Text::new(toggle_text)) + .on_press(Interaction::StartDownload) + .into(); + + let progress_bar = + ProgressBar::new(0.0..=100.0, self.current_progress as f32); + + let progress_text = &match self.enabled { + true => format!("Downloading {}%", self.current_progress), + false => "Ready to rock!".into(), + }; + + // Construct layout + let content = Column::new() + .align_items(Align::Center) + .spacing(20) + .padding(20) + .push(Text::new(progress_text)) + .push(progress_bar) + .push(toggle.map(Message::Interaction)); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} -- cgit From 30c7db3f25d12461f2dec493f92c3f3282bd264d Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 23 Mar 2020 20:34:16 +0100 Subject: Improve `download_progress` example - Use `reqwest` with `Response::chunk` to notify progress. - Turn example state into an enum --- examples/download_progress/Cargo.toml | 5 +- examples/download_progress/src/downloader.rs | 97 ++++++++++---------- examples/download_progress/src/main.rs | 129 ++++++++++++++------------- 3 files changed, 117 insertions(+), 114 deletions(-) diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml index ce0435fd..34e6a132 100644 --- a/examples/download_progress/Cargo.toml +++ b/examples/download_progress/Cargo.toml @@ -6,8 +6,7 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../.." } +iced = { path = "../..", features = ["tokio"] } iced_native = { path = "../../native" } iced_futures = { path = "../../futures" } -async-std = { version = "1.0", features = ["unstable"] } -isahc = "0.9.1" +reqwest = "0.10" diff --git a/examples/download_progress/src/downloader.rs b/examples/download_progress/src/downloader.rs index 62f943fd..3b54341e 100644 --- a/examples/download_progress/src/downloader.rs +++ b/examples/download_progress/src/downloader.rs @@ -1,7 +1,7 @@ use iced_futures::futures; // Just a little utility function -pub fn file(url: T) -> iced::Subscription { +pub fn file(url: T) -> iced::Subscription { iced::Subscription::from_recipe(Downloader { url: url.to_string(), }) @@ -16,7 +16,7 @@ impl iced_native::subscription::Recipe for Downloader where H: std::hash::Hasher, { - type Output = DownloadMessage; + type Output = Progress; fn hash(&self, state: &mut H) { use std::hash::Hash; @@ -27,73 +27,68 @@ where self: Box, _input: futures::stream::BoxStream<'static, I>, ) -> futures::stream::BoxStream<'static, Self::Output> { - use isahc::prelude::*; - Box::pin(futures::stream::unfold( - DownloadState::Ready(self.url), + State::Ready(self.url), |state| async move { match state { - DownloadState::Ready(url) => { - let resp = Request::get(&url) - .metrics(true) - .body(()) - .unwrap() - .send_async() - .await - .unwrap(); - let metrics = resp.metrics().unwrap().clone(); - // If you actually want to download: - /*let file = async_std::fs::File::create("download.bin") - .await - .unwrap();*/ - - async_std::task::spawn(async_std::io::copy( - resp.into_body(), - async_std::io::sink(), //file - )); + State::Ready(url) => { + let response = reqwest::get(&url).await; - Some(( - DownloadMessage::DownloadStarted, - DownloadState::Downloading(metrics), - )) + match response { + Ok(response) => Some(( + Progress::Started, + State::Downloading { + total: response.content_length().unwrap(), + downloaded: 0, + response, + }, + )), + Err(_) => None, + } } - DownloadState::Downloading(metrics) => { - async_std::task::sleep( - std::time::Duration::from_millis(100), - ) - .await; + State::Downloading { + mut response, + total, + downloaded, + } => match response.chunk().await { + Ok(Some(chunk)) => { + let downloaded = downloaded + chunk.len() as u64; - let percentage = metrics.download_progress().0 * 100 - / metrics.download_progress().1; + let percentage = + (downloaded as f32 / total as f32) * 100.0; - if percentage == 100 { Some(( - DownloadMessage::Done, - DownloadState::Finished, - )) - } else { - Some(( - DownloadMessage::Downloading(percentage), - DownloadState::Downloading(metrics), + Progress::Advanced(percentage), + State::Downloading { + response, + total, + downloaded, + }, )) } - } - DownloadState::Finished => None, + Ok(None) => Some((Progress::Finished, State::Finished)), + Err(_) => None, + }, + State::Finished => None, } }, )) } } -#[derive(Debug)] -pub enum DownloadMessage { - DownloadStarted, - Downloading(u64), - Done, +#[derive(Debug, Clone)] +pub enum Progress { + Started, + Advanced(f32), + Finished, } -pub enum DownloadState { +pub enum State { Ready(String), - Downloading(isahc::Metrics), + Downloading { + response: reqwest::Response, + total: u64, + downloaded: u64, + }, Finished, } diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index 936144d5..75e3bee0 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -6,60 +6,61 @@ use iced::{ mod downloader; pub fn main() { - Downloader::run(Settings::default()) -} - -#[derive(Debug, Default)] -struct Downloader { - // Whether to start the download or not. - enabled: bool, - // The current percentage of the download - current_progress: u64, - - btn_state: button::State, + Example::run(Settings::default()) } #[derive(Debug)] -pub enum Message { - DownloadUpdate(downloader::DownloadMessage), - Interaction(Interaction), +enum Example { + Idle { button: button::State }, + Downloading { progress: f32 }, + Finished { button: button::State }, } -// For explanation of why we use an Interaction enum see here: -// https://github.com/hecrj/iced/pull/155#issuecomment-573523405 #[derive(Debug, Clone)] -pub enum Interaction { - // User pressed the button to start the download - StartDownload, +pub enum Message { + DownloadProgressed(downloader::Progress), + Download, } -impl Application for Downloader { +impl Application for Example { type Executor = executor::Default; type Message = Message; - fn new() -> (Downloader, Command) { - (Downloader::default(), Command::none()) + fn new() -> (Example, Command) { + ( + Example::Idle { + button: button::State::new(), + }, + Command::none(), + ) } fn title(&self) -> String { - String::from("Download Progress - Iced") + String::from("Download progress - Iced") } fn update(&mut self, message: Message) -> Command { match message { - Message::Interaction(action) => match action { - Interaction::StartDownload => { - self.enabled = true; + Message::Download => match self { + Example::Idle { .. } | Example::Finished { .. } => { + *self = Example::Downloading { progress: 0.0 }; } + _ => {} }, - Message::DownloadUpdate(update) => match update { - downloader::DownloadMessage::Downloading(percentage) => { - self.current_progress = percentage; - } - downloader::DownloadMessage::Done => { - self.current_progress = 100; - self.enabled = false; - } + Message::DownloadProgressed(message) => match self { + Example::Downloading { progress } => match message { + downloader::Progress::Started => { + *progress = 0.0; + } + downloader::Progress::Advanced(percentage) => { + *progress = percentage; + } + downloader::Progress::Finished => { + *self = Example::Finished { + button: button::State::new(), + } + } + }, _ => {} }, }; @@ -68,43 +69,51 @@ impl Application for Downloader { } fn subscription(&self) -> Subscription { - if self.enabled { - downloader::file("https://speed.hetzner.de/100MB.bin") - .map(Message::DownloadUpdate) - } else { - Subscription::none() + match self { + Example::Downloading { .. } => { + downloader::file("https://speed.hetzner.de/100MB.bin") + .map(Message::DownloadProgressed) + } + _ => Subscription::none(), } } fn view(&mut self) -> Element { - // Construct widgets - - let toggle_text = match self.enabled { - true => "Downloading...", - false => "Start the download!", + let current_progress = match self { + Example::Idle { .. } => 0.0, + Example::Downloading { progress } => *progress, + Example::Finished { .. } => 100.0, }; - let toggle: Element = - Button::new(&mut self.btn_state, Text::new(toggle_text)) - .on_press(Interaction::StartDownload) - .into(); - - let progress_bar = - ProgressBar::new(0.0..=100.0, self.current_progress as f32); - - let progress_text = &match self.enabled { - true => format!("Downloading {}%", self.current_progress), - false => "Ready to rock!".into(), + let progress_bar = ProgressBar::new(0.0..=100.0, current_progress); + + let control: Element<_> = match self { + Example::Idle { button } => { + Button::new(button, Text::new("Start the download!")) + .on_press(Message::Download) + .into() + } + Example::Finished { button } => Column::new() + .spacing(10) + .align_items(Align::Center) + .push(Text::new("Download finished!")) + .push( + Button::new(button, Text::new("Start again")) + .on_press(Message::Download), + ) + .into(), + Example::Downloading { .. } => { + Text::new(format!("Downloading... {:.2}%", current_progress)) + .into() + } }; - // Construct layout let content = Column::new() + .spacing(10) + .padding(10) .align_items(Align::Center) - .spacing(20) - .padding(20) - .push(Text::new(progress_text)) .push(progress_bar) - .push(toggle.map(Message::Interaction)); + .push(control); Container::new(content) .width(Length::Fill) -- cgit From b92e1f957408e3254e5fe0da389808474de6c4a9 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 23 Mar 2020 20:37:30 +0100 Subject: Rename `downloader` module to `download` --- examples/download_progress/src/download.rs | 94 ++++++++++++++++++++++++++++ examples/download_progress/src/downloader.rs | 94 ---------------------------- examples/download_progress/src/main.rs | 12 ++-- 3 files changed, 100 insertions(+), 100 deletions(-) create mode 100644 examples/download_progress/src/download.rs delete mode 100644 examples/download_progress/src/downloader.rs diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs new file mode 100644 index 00000000..0562f54d --- /dev/null +++ b/examples/download_progress/src/download.rs @@ -0,0 +1,94 @@ +use iced_futures::futures; + +// Just a little utility function +pub fn file(url: T) -> iced::Subscription { + iced::Subscription::from_recipe(Download { + url: url.to_string(), + }) +} + +pub struct Download { + url: String, +} + +// Make sure iced can use our download stream +impl iced_native::subscription::Recipe for Download +where + H: std::hash::Hasher, +{ + type Output = Progress; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + std::any::TypeId::of::().hash(state); + } + + fn stream( + self: Box, + _input: futures::stream::BoxStream<'static, I>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + Box::pin(futures::stream::unfold( + State::Ready(self.url), + |state| async move { + match state { + State::Ready(url) => { + let response = reqwest::get(&url).await; + + match response { + Ok(response) => Some(( + Progress::Started, + State::Downloading { + total: response.content_length().unwrap(), + downloaded: 0, + response, + }, + )), + Err(_) => None, + } + } + State::Downloading { + mut response, + total, + downloaded, + } => match response.chunk().await { + Ok(Some(chunk)) => { + let downloaded = downloaded + chunk.len() as u64; + + let percentage = + (downloaded as f32 / total as f32) * 100.0; + + Some(( + Progress::Advanced(percentage), + State::Downloading { + response, + total, + downloaded, + }, + )) + } + Ok(None) => Some((Progress::Finished, State::Finished)), + Err(_) => None, + }, + State::Finished => None, + } + }, + )) + } +} + +#[derive(Debug, Clone)] +pub enum Progress { + Started, + Advanced(f32), + Finished, +} + +pub enum State { + Ready(String), + Downloading { + response: reqwest::Response, + total: u64, + downloaded: u64, + }, + Finished, +} diff --git a/examples/download_progress/src/downloader.rs b/examples/download_progress/src/downloader.rs deleted file mode 100644 index 3b54341e..00000000 --- a/examples/download_progress/src/downloader.rs +++ /dev/null @@ -1,94 +0,0 @@ -use iced_futures::futures; - -// Just a little utility function -pub fn file(url: T) -> iced::Subscription { - iced::Subscription::from_recipe(Downloader { - url: url.to_string(), - }) -} - -pub struct Downloader { - url: String, -} - -// Make sure iced can use our download stream -impl iced_native::subscription::Recipe for Downloader -where - H: std::hash::Hasher, -{ - type Output = Progress; - - fn hash(&self, state: &mut H) { - use std::hash::Hash; - std::any::TypeId::of::().hash(state); - } - - fn stream( - self: Box, - _input: futures::stream::BoxStream<'static, I>, - ) -> futures::stream::BoxStream<'static, Self::Output> { - Box::pin(futures::stream::unfold( - State::Ready(self.url), - |state| async move { - match state { - State::Ready(url) => { - let response = reqwest::get(&url).await; - - match response { - Ok(response) => Some(( - Progress::Started, - State::Downloading { - total: response.content_length().unwrap(), - downloaded: 0, - response, - }, - )), - Err(_) => None, - } - } - State::Downloading { - mut response, - total, - downloaded, - } => match response.chunk().await { - Ok(Some(chunk)) => { - let downloaded = downloaded + chunk.len() as u64; - - let percentage = - (downloaded as f32 / total as f32) * 100.0; - - Some(( - Progress::Advanced(percentage), - State::Downloading { - response, - total, - downloaded, - }, - )) - } - Ok(None) => Some((Progress::Finished, State::Finished)), - Err(_) => None, - }, - State::Finished => None, - } - }, - )) - } -} - -#[derive(Debug, Clone)] -pub enum Progress { - Started, - Advanced(f32), - Finished, -} - -pub enum State { - Ready(String), - Downloading { - response: reqwest::Response, - total: u64, - downloaded: u64, - }, - Finished, -} diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index 75e3bee0..f3da3d7b 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -3,7 +3,7 @@ use iced::{ Element, Length, ProgressBar, Settings, Subscription, Text, }; -mod downloader; +mod download; pub fn main() { Example::run(Settings::default()) @@ -18,7 +18,7 @@ enum Example { #[derive(Debug, Clone)] pub enum Message { - DownloadProgressed(downloader::Progress), + DownloadProgressed(download::Progress), Download, } @@ -49,13 +49,13 @@ impl Application for Example { }, Message::DownloadProgressed(message) => match self { Example::Downloading { progress } => match message { - downloader::Progress::Started => { + download::Progress::Started => { *progress = 0.0; } - downloader::Progress::Advanced(percentage) => { + download::Progress::Advanced(percentage) => { *progress = percentage; } - downloader::Progress::Finished => { + download::Progress::Finished => { *self = Example::Finished { button: button::State::new(), } @@ -71,7 +71,7 @@ impl Application for Example { fn subscription(&self) -> Subscription { match self { Example::Downloading { .. } => { - downloader::file("https://speed.hetzner.de/100MB.bin") + download::file("https://speed.hetzner.de/100MB.bin") .map(Message::DownloadProgressed) } _ => Subscription::none(), -- cgit From 0d719bbdf336a022c073986e1e5a91cf632a270c Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 23 Mar 2020 20:43:55 +0100 Subject: Handle errors in `download_progress` example --- examples/download_progress/src/download.rs | 38 +++++++++++++++++++++--------- examples/download_progress/src/main.rs | 16 +++++++++++++ 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs index 0562f54d..96e1dc28 100644 --- a/examples/download_progress/src/download.rs +++ b/examples/download_progress/src/download.rs @@ -35,15 +35,23 @@ where let response = reqwest::get(&url).await; match response { - Ok(response) => Some(( - Progress::Started, - State::Downloading { - total: response.content_length().unwrap(), - downloaded: 0, - response, - }, - )), - Err(_) => None, + Ok(response) => { + if let Some(total) = response.content_length() { + Some(( + Progress::Started, + State::Downloading { + response, + total, + downloaded: 0, + }, + )) + } else { + Some((Progress::Errored, State::Finished)) + } + } + Err(_) => { + Some((Progress::Errored, State::Finished)) + } } } State::Downloading { @@ -67,9 +75,16 @@ where )) } Ok(None) => Some((Progress::Finished, State::Finished)), - Err(_) => None, + Err(_) => Some((Progress::Errored, State::Finished)), }, - State::Finished => None, + State::Finished => { + // We do not let the stream die, as it would start a + // new download repeatedly if the user is not careful + // in case of errors. + let _: () = iced::futures::future::pending().await; + + None + } } }, )) @@ -81,6 +96,7 @@ pub enum Progress { Started, Advanced(f32), Finished, + Errored, } pub enum State { diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index f3da3d7b..817a45ac 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -14,6 +14,7 @@ enum Example { Idle { button: button::State }, Downloading { progress: f32 }, Finished { button: button::State }, + Errored { button: button::State }, } #[derive(Debug, Clone)] @@ -60,6 +61,11 @@ impl Application for Example { button: button::State::new(), } } + download::Progress::Errored => { + *self = Example::Errored { + button: button::State::new(), + }; + } }, _ => {} }, @@ -83,6 +89,7 @@ impl Application for Example { Example::Idle { .. } => 0.0, Example::Downloading { progress } => *progress, Example::Finished { .. } => 100.0, + Example::Errored { .. } => 0.0, }; let progress_bar = ProgressBar::new(0.0..=100.0, current_progress); @@ -106,6 +113,15 @@ impl Application for Example { Text::new(format!("Downloading... {:.2}%", current_progress)) .into() } + Example::Errored { button } => Column::new() + .spacing(10) + .align_items(Align::Center) + .push(Text::new("Something went wrong :(")) + .push( + Button::new(button, Text::new("Try again")) + .on_press(Message::Download), + ) + .into(), }; let content = Column::new() -- cgit From 8e073d10d71f80077b843c49013a8bdfeb5a7f44 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 23 Mar 2020 21:04:20 +0100 Subject: Update `README` of examples --- examples/README.md | 1 + examples/download_progress/README.md | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/README.md b/examples/README.md index a7673705..5aea51eb 100644 --- a/examples/README.md +++ b/examples/README.md @@ -73,6 +73,7 @@ A bunch of simpler examples exist: - [`clock`](clock), an application that uses the `Canvas` widget to draw a clock and its hands to display the current time. - [`counter`](counter), the classic counter example explained in the [`README`](../README.md). - [`custom_widget`](custom_widget), a demonstration of how to build a custom widget that draws a circle. +- [`download_progress`](download_progress), a basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress. - [`events`](events), a log of native events displayed using a conditional `Subscription`. - [`geometry`](geometry), a custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../wgpu). - [`integration`](integration), a demonstration of how to integrate Iced in an existing graphical application. diff --git a/examples/download_progress/README.md b/examples/download_progress/README.md index c6311163..c606c5f9 100644 --- a/examples/download_progress/README.md +++ b/examples/download_progress/README.md @@ -1,10 +1,12 @@ -## Download Progress +## Download progress -Downloading a file asynchronously with a `Subscription` while displaying the progress with a `ProgressBar`. +A basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress. + +The example implements a custom `Subscription` in the __[`download`](src/download.rs)__ module. This subscription downloads and produces messages that can be used to keep track of its progress. -- cgit From 8e0dcd212d71ff334aa590ee3b565da7b8d24713 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Mon, 23 Mar 2020 21:08:03 +0100 Subject: Fix retry button on `download_progress` example --- examples/download_progress/src/main.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index 817a45ac..6c3094f7 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -19,8 +19,8 @@ enum Example { #[derive(Debug, Clone)] pub enum Message { - DownloadProgressed(download::Progress), Download, + DownloadProgressed(download::Progress), } impl Application for Example { @@ -43,7 +43,9 @@ impl Application for Example { fn update(&mut self, message: Message) -> Command { match message { Message::Download => match self { - Example::Idle { .. } | Example::Finished { .. } => { + Example::Idle { .. } + | Example::Finished { .. } + | Example::Errored { .. } => { *self = Example::Downloading { progress: 0.0 }; } _ => {} -- cgit From fd7d9622e333a0a2cd5c2e8e6cc38cc09d7981e4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 24 Mar 2020 14:07:34 +0100 Subject: Fix `Recipe::hash` in `download_progress` example --- examples/download_progress/src/download.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/download_progress/src/download.rs b/examples/download_progress/src/download.rs index 96e1dc28..f46a01f7 100644 --- a/examples/download_progress/src/download.rs +++ b/examples/download_progress/src/download.rs @@ -20,7 +20,9 @@ where fn hash(&self, state: &mut H) { use std::hash::Hash; + std::any::TypeId::of::().hash(state); + self.url.hash(state); } fn stream( -- cgit