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`. --- 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 +++++++---- 7 files changed, 34 insertions(+), 31 deletions(-) (limited to 'native/src') 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 } -- 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 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'native/src') 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, -- 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 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'native/src') 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) + } } } } -- 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(-) (limited to 'native/src') 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 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 --- native/src/element.rs | 2 +- native/src/lib.rs | 2 +- native/src/widget.rs | 3 + native/src/widget/panes.rs | 238 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 243 insertions(+), 2 deletions(-) create mode 100644 native/src/widget/panes.rs (limited to 'native/src') 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) + } +} -- 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 --- native/src/widget/panes.rs | 198 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 184 insertions(+), 14 deletions(-) (limited to 'native/src') 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; -- 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(-) (limited to 'native/src') 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 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` --- native/src/widget/panes.rs | 50 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) (limited to 'native/src') 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 ++++------- 7 files changed, 28 insertions(+), 31 deletions(-) (limited to 'native/src') 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 } -- 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` --- native/src/widget.rs | 4 +- native/src/widget/pane_grid.rs | 463 +++++++++++++++++++++++++++++++++++++++++ native/src/widget/panes.rs | 463 ----------------------------------------- 3 files changed, 465 insertions(+), 465 deletions(-) create mode 100644 native/src/widget/pane_grid.rs delete mode 100644 native/src/widget/panes.rs (limited to 'native/src') 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) - } -} -- 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(-) (limited to 'native/src') 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 +++++++++++++++++++++++++++++++++-------- 1 file changed, 135 insertions(+), 29 deletions(-) (limited to 'native/src') 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; -- 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 --- native/src/widget/pane_grid.rs | 115 +++++++++++++++++++++++++++++++---------- 1 file changed, 87 insertions(+), 28 deletions(-) (limited to 'native/src') 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(-) (limited to 'native/src') 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(-) (limited to 'native/src') 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 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(-) (limited to 'native/src') 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(-) (limited to 'native/src') 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(+) (limited to 'native/src') 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(-) (limited to 'native/src') 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(-) (limited to 'native/src') 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 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(-) (limited to 'native/src') 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(-) (limited to 'native/src') 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(-) (limited to 'native/src') 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 (limited to 'native/src') 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(-) (limited to 'native/src') 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 (limited to 'native/src') 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(-) (limited to 'native/src') 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 (limited to 'native/src') 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` --- 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 ++++++++++++++++++-- 4 files changed, 244 insertions(+), 10 deletions(-) (limited to 'native/src') 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 }; } -- 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(-) (limited to 'native/src') 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 ++++----- 2 files changed, 65 insertions(+), 63 deletions(-) (limited to 'native/src') 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 }, ) -- 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(-) (limited to 'native/src') 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 --- native/src/widget/scrollable.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'native/src') 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 ++++++++++++++++ 2 files changed, 32 insertions(+) (limited to 'native/src') 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 -- 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(-) (limited to 'native/src') 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(-) (limited to 'native/src') 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 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(-) (limited to 'native/src') 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 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(-) (limited to 'native/src') 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 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` --- native/src/widget/pane_grid.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'native/src') 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`. --- native/src/input/keyboard.rs | 5 +- native/src/input/keyboard/key_code.rs | 198 --------------------------- native/src/input/keyboard/modifiers_state.rs | 15 -- 3 files changed, 1 insertion(+), 217 deletions(-) delete mode 100644 native/src/input/keyboard/key_code.rs delete mode 100644 native/src/input/keyboard/modifiers_state.rs (limited to 'native/src') 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, -} -- 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 --- native/src/widget/pane_grid.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'native/src') 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(-) (limited to 'native/src') 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(-) (limited to 'native/src') 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 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(-) (limited to 'native/src') 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 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(-) (limited to 'native/src') 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(-) (limited to 'native/src') 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(-) (limited to 'native/src') 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(-) (limited to 'native/src') 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