From 041cab0fa499a54d21d2742ba821583f55b2a8fc Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector0193@gmail.com>
Date: Mon, 8 Jun 2020 18:11:29 +0200
Subject: Resize `PaneGrid` without modifier keys

---
 native/src/widget/pane_grid.rs       | 213 +++++++++++++++++++----------------
 native/src/widget/pane_grid/axis.rs  |  24 ++++
 native/src/widget/pane_grid/state.rs |   3 +
 3 files changed, 141 insertions(+), 99 deletions(-)

(limited to 'native/src')

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<F>(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<Message>,
+    ) {
+        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)
-                            },
-                        );
-
-                    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,
-                                }));
+                    let bounds = layout.bounds();
+
+                    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,
+                                );
+
+                                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<Item = (&'a Split, &'a (Axis, Rectangle, f32))>,
+    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<T> State<T> {
             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<Option<HashMap<Split, (Axis, f32, Rectangle)>>>,
 }
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
-- 
cgit