From 041cab0fa499a54d21d2742ba821583f55b2a8fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 8 Jun 2020 18:11:29 +0200 Subject: [PATCH 1/5] Resize `PaneGrid` without modifier keys --- native/src/widget/pane_grid.rs | 209 ++++++++++++++------------- native/src/widget/pane_grid/axis.rs | 24 +++ native/src/widget/pane_grid/state.rs | 3 + 3 files changed, 139 insertions(+), 97 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 076ae76f..c7467962 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -26,7 +26,7 @@ pub use state::{Focus, State}; use crate::{ keyboard, layout, mouse, Clipboard, Element, Event, Hasher, Layout, Length, - Point, Size, Widget, + Point, Rectangle, Size, Widget, }; /// A collection of panes distributed using either vertical or horizontal splits @@ -177,8 +177,8 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { /// Sets the modifier keys of the [`PaneGrid`]. /// - /// The modifier keys will need to be pressed to trigger dragging, resizing, - /// and key events. + /// The modifier keys will need to be pressed to trigger dragging, and key + /// events. /// /// The default modifier key is `Ctrl`. /// @@ -208,8 +208,6 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { /// 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: F) -> Self where @@ -244,6 +242,35 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self } + fn click_pane( + &mut self, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + ) { + let mut clicked_region = + self.elements.iter().zip(layout.children()).filter( + |(_, layout)| layout.bounds().contains(cursor_position), + ); + + if let Some(((pane, _), _)) = clicked_region.next() { + match &self.on_drag { + Some(on_drag) + if self.pressed_modifiers.matches(self.modifier_keys) => + { + self.state.pick_pane(pane); + + messages.push(on_drag(DragEvent::Picked { pane: *pane })); + } + _ => { + self.state.focus(pane); + } + } + } else { + self.state.unfocus(); + } + } + fn trigger_resize( &mut self, layout: Layout<'_>, @@ -409,32 +436,45 @@ where match event { Event::Mouse(mouse_event) => match mouse_event { mouse::Event::ButtonPressed(mouse::Button::Left) => { - let mut clicked_region = - self.elements.iter().zip(layout.children()).filter( - |(_, layout)| { - layout.bounds().contains(cursor_position) - }, - ); + let bounds = layout.bounds(); - if let Some(((pane, _), _)) = clicked_region.next() { - match &self.on_drag { - Some(on_drag) - if self - .pressed_modifiers - .matches(self.modifier_keys) => - { - self.state.pick_pane(pane); + if bounds.contains(cursor_position) { + match self.on_resize { + Some(_) => { + let relative_cursor = Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ); - messages.push(on_drag(DragEvent::Picked { - pane: *pane, - })); + let splits = self.state.splits( + f32::from(self.spacing), + Size::new(bounds.width, bounds.height), + ); + + let clicked_split = hovered_split( + splits.iter(), + f32::from(self.spacing), + relative_cursor, + ); + + if let Some((split, axis)) = clicked_split { + self.state.pick_split(&split, axis); + } else { + self.click_pane( + layout, + cursor_position, + messages, + ); + } } - _ => { - self.state.focus(pane); + None => { + self.click_pane( + layout, + cursor_position, + messages, + ); } } - } else { - self.state.unfocus(); } } mouse::Event::ButtonReleased(mouse::Button::Left) => { @@ -462,78 +502,10 @@ where messages.push(on_drag(event)); } + } else if self.state.picked_split().is_some() { + self.state.drop_split(); } } - mouse::Event::ButtonPressed(mouse::Button::Right) - if self.on_resize.is_some() - && self.state.picked_pane().is_none() - && self - .pressed_modifiers - .matches(self.modifier_keys) => - { - let bounds = layout.bounds(); - - 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 - }, - ); - - if let Some((split, (axis, _, _))) = - sorted_splits.first() - { - self.state.pick_split(split, *axis); - self.trigger_resize( - layout, - cursor_position, - messages, - ); - } - } - } - mouse::Event::ButtonReleased(mouse::Button::Right) - if self.state.picked_split().is_some() => - { - self.state.drop_split(); - } mouse::Event::CursorMoved { .. } => { self.trigger_resize(layout, cursor_position, messages); } @@ -597,11 +569,32 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { + let picked_split = self + .state + .picked_split() + .or_else(|| match self.on_resize { + Some(_) => { + let bounds = layout.bounds(); + let spacing = f32::from(self.spacing); + + let relative_cursor = Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ); + + let splits = self.state.splits(spacing, bounds.size()); + + hovered_split(splits.iter(), spacing, relative_cursor) + } + None => None, + }) + .map(|(_, axis)| axis); + renderer.draw( defaults, &self.elements, self.state.picked_pane(), - self.state.picked_split().map(|(_, axis)| axis), + picked_split, layout, cursor_position, ) @@ -665,3 +658,25 @@ where Element::new(pane_grid) } } + +/* + * Helpers + */ +fn hovered_split<'a>( + splits: impl Iterator, + spacing: f32, + cursor_position: Point, +) -> Option<(Split, Axis)> { + splits + .filter_map(|(split, (axis, region, ratio))| { + let bounds = + axis.split_line_bounds(*region, *ratio, f32::from(spacing)); + + if bounds.contains(cursor_position) { + Some((*split, *axis)) + } else { + None + } + }) + .next() +} diff --git a/native/src/widget/pane_grid/axis.rs b/native/src/widget/pane_grid/axis.rs index b3a306d5..2320cb7c 100644 --- a/native/src/widget/pane_grid/axis.rs +++ b/native/src/widget/pane_grid/axis.rs @@ -53,6 +53,30 @@ impl Axis { } } } + + pub(super) fn split_line_bounds( + &self, + rectangle: Rectangle, + ratio: f32, + spacing: f32, + ) -> Rectangle { + match self { + Axis::Horizontal => Rectangle { + x: rectangle.x, + y: (rectangle.y + rectangle.height * ratio - spacing / 2.0) + .round(), + width: rectangle.width, + height: spacing, + }, + Axis::Vertical => Rectangle { + x: (rectangle.x + rectangle.width * ratio - spacing / 2.0) + .round(), + y: rectangle.y, + width: spacing, + height: rectangle.height, + }, + } + } } #[cfg(test)] diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 4b13fb8e..7a53d6c7 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -4,6 +4,7 @@ use crate::{ Hasher, Point, Rectangle, Size, }; +use std::cell::RefCell; use std::collections::HashMap; /// The state of a [`PaneGrid`]. @@ -71,6 +72,7 @@ impl State { internal: Internal { layout, last_id, + split_cache: RefCell::new(None), action: Action::Idle { focus: None }, }, modifiers: keyboard::ModifiersState::default(), @@ -308,6 +310,7 @@ pub struct Internal { layout: Node, last_id: usize, action: Action, + split_cache: RefCell>>, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] From be0cc2c780256ba776e52e0dd14f384553d6a332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 8 Jun 2020 18:25:23 +0200 Subject: [PATCH 2/5] Add `leeway` support to `PaneGrid::on_resize` --- examples/pane_grid/src/main.rs | 2 +- native/src/widget/pane_grid.rs | 28 ++++++++++++++++++---------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index b4bbd68f..a821072f 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -103,7 +103,7 @@ impl Sandbox for Example { .height(Length::Fill) .spacing(10) .on_drag(Message::Dragged) - .on_resize(Message::Resized) + .on_resize(10, Message::Resized) .on_key_press(handle_hotkey); Container::new(pane_grid) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index c7467962..c114fa13 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -91,7 +91,7 @@ pub struct PaneGrid<'a, Message, Renderer> { spacing: u16, modifier_keys: keyboard::ModifiersState, on_drag: Option Message + 'a>>, - on_resize: Option Message + 'a>>, + on_resize: Option<(u16, Box Message + 'a>)>, on_key_press: Option Option + 'a>>, } @@ -208,12 +208,15 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { /// Enables the resize interactions of the [`PaneGrid`], which will /// use the provided function to produce messages. /// + /// The `leeway` describes the amount of space around a split that can be + /// used to grab it. + /// /// [`PaneGrid`]: struct.PaneGrid.html - pub fn on_resize(mut self, f: F) -> Self + pub fn on_resize(mut self, leeway: u16, f: F) -> Self where F: 'a + Fn(ResizeEvent) -> Message, { - self.on_resize = Some(Box::new(f)); + self.on_resize = Some((leeway, Box::new(f))); self } @@ -277,7 +280,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { cursor_position: Point, messages: &mut Vec, ) { - if let Some(on_resize) = &self.on_resize { + if let Some((_, on_resize)) = &self.on_resize { if let Some((split, _)) = self.state.picked_split() { let bounds = layout.bounds(); @@ -440,7 +443,7 @@ where if bounds.contains(cursor_position) { match self.on_resize { - Some(_) => { + Some((leeway, _)) => { let relative_cursor = Point::new( cursor_position.x - bounds.x, cursor_position.y - bounds.y, @@ -453,7 +456,7 @@ where let clicked_split = hovered_split( splits.iter(), - f32::from(self.spacing), + f32::from(self.spacing + leeway), relative_cursor, ); @@ -573,18 +576,23 @@ where .state .picked_split() .or_else(|| match self.on_resize { - Some(_) => { + Some((leeway, _)) => { let bounds = layout.bounds(); - let spacing = f32::from(self.spacing); let relative_cursor = Point::new( cursor_position.x - bounds.x, cursor_position.y - bounds.y, ); - let splits = self.state.splits(spacing, bounds.size()); + let splits = self + .state + .splits(f32::from(self.spacing), bounds.size()); - hovered_split(splits.iter(), spacing, relative_cursor) + hovered_split( + splits.iter(), + f32::from(self.spacing + leeway), + relative_cursor, + ) } None => None, }) From dcc4bb77e942f8550b0d7ee08fa5a2882fd22ecd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 8 Jun 2020 18:25:46 +0200 Subject: [PATCH 3/5] Remove unused `split_cache` in `pane_grid` --- native/src/widget/pane_grid/state.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 7a53d6c7..4b13fb8e 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -4,7 +4,6 @@ use crate::{ Hasher, Point, Rectangle, Size, }; -use std::cell::RefCell; use std::collections::HashMap; /// The state of a [`PaneGrid`]. @@ -72,7 +71,6 @@ impl State { internal: Internal { layout, last_id, - split_cache: RefCell::new(None), action: Action::Idle { focus: None }, }, modifiers: keyboard::ModifiersState::default(), @@ -310,7 +308,6 @@ pub struct Internal { layout: Node, last_id: usize, action: Action, - split_cache: RefCell>>, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] From 172bd78a91ccc17b3516484d33d36616758b8d4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 8 Jun 2020 18:34:41 +0200 Subject: [PATCH 4/5] Fix `PaneGrid` documentation example --- native/src/widget/pane_grid.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index c114fa13..f8788932 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -76,7 +76,7 @@ use crate::{ /// }.into() /// }) /// .on_drag(Message::PaneDragged) -/// .on_resize(Message::PaneResized); +/// .on_resize(10, Message::PaneResized); /// ``` /// /// [`PaneGrid`]: struct.PaneGrid.html From 8b93c9cb6a623d8f936e7e7135a2598e3ce4e4ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 8 Jun 2020 19:41:33 +0200 Subject: [PATCH 5/5] Clarify `leeway` meaning in `PaneGrid` --- native/src/widget/pane_grid.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index f8788932..2d21a968 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -211,6 +211,10 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { /// The `leeway` describes the amount of space around a split that can be /// used to grab it. /// + /// The grabbable area of a split will have a length of `spacing + leeway`, + /// properly centered. In other words, a length of + /// `(spacing + leeway) / 2.0` on either side of the split line. + /// /// [`PaneGrid`]: struct.PaneGrid.html pub fn on_resize(mut self, leeway: u16, f: F) -> Self where