diff options
Diffstat (limited to 'native/src/widget')
| -rw-r--r-- | native/src/widget/pane_grid.rs | 116 | ||||
| -rw-r--r-- | native/src/widget/pane_grid/node.rs | 67 | ||||
| -rw-r--r-- | native/src/widget/pane_grid/state.rs | 65 | 
3 files changed, 238 insertions, 10 deletions
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<Box<dyn Fn(DragEvent) -> Message>>, +    on_resize: Option<Box<dyn Fn(ResizeEvent) -> 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<Message, Renderer>      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<Pane>, +        resizing: Option<Axis>,          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<Pane> {          match self {              Node::Split { a, b, .. } => { @@ -93,6 +112,27 @@ impl Node {          regions      } +    pub fn splits( +        &self, +        spacing: f32, +        size: Size, +    ) -> HashMap<Split, (Axis, Rectangle, f32)> { +        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<Pane> {          match self {              Node::Split { .. } => None, @@ -129,4 +169,31 @@ impl Node {              }          }      } + +    fn compute_splits( +        &self, +        halved_spacing: f32, +        current: &Rectangle, +        splits: &mut HashMap<Split, (Axis, Rectangle, f32)>, +    ) { +        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<T> State<T> {          });      } +    pub fn resize(&mut self, split: &Split, percentage: f32) { +        let _ = self.internal.layout.resize(split, percentage); +    } +      pub fn close(&mut self, pane: &Pane) -> Option<T> {          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<Pane> }, -    Dragging { pane: Pane }, +    Idle { +        focus: Option<Pane>, +    }, +    Dragging { +        pane: Pane, +    }, +    Resizing { +        split: Split, +        axis: Axis, +        focus: Option<Pane>, +    },  }  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<Pane> { +    pub fn picked_pane(&self) -> Option<Pane> {          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<Split, (Axis, Rectangle, f32)> { +        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 };      }  | 
