diff options
Diffstat (limited to '')
| -rw-r--r-- | widget/src/pane_grid.rs | 275 | ||||
| -rw-r--r-- | widget/src/pane_grid/node.rs | 10 | ||||
| -rw-r--r-- | widget/src/pane_grid/state.rs | 108 | 
3 files changed, 283 insertions, 110 deletions
| diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 040d6bb3..31bb0e86 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -581,39 +581,46 @@ pub fn update<'a, Message, T: Draggable>(          | Event::Touch(touch::Event::FingerLost { .. }) => {              if let Some((pane, _)) = action.picked_pane() {                  if let Some(on_drag) = on_drag { -                    let dropped_region = -                        cursor.position().and_then(|cursor_position| { -                            contents +                    if let Some(cursor_position) = cursor.position() { +                        let event = if let Some(edge) = +                            in_edge(layout, cursor_position) +                        { +                            DragEvent::Dropped { +                                pane, +                                target: Target::Edge(edge), +                            } +                        } else { +                            let dropped_region = contents                                  .zip(layout.children())                                  .filter_map(|(target, layout)| {                                      layout_region(layout, cursor_position)                                          .map(|region| (target, region))                                  }) -                                .next() -                        }); - -                    let event = match dropped_region { -                        Some(((target, _), region)) if pane != target => { -                            DragEvent::Dropped { -                                pane, -                                target, -                                region, +                                .next(); + +                            match dropped_region { +                                Some(((target, _), region)) +                                    if pane != target => +                                { +                                    DragEvent::Dropped { +                                        pane, +                                        target: Target::Pane(target, region), +                                    } +                                } +                                _ => DragEvent::Canceled { pane },                              } -                        } -                        _ => DragEvent::Canceled { pane }, -                    }; +                        }; -                    shell.publish(on_drag(event)); +                        shell.publish(on_drag(event)); +                    }                  } -                *action = state::Action::Idle; -                  event_status = event::Status::Captured;              } else if action.picked_split().is_some() { -                *action = state::Action::Idle; -                  event_status = event::Status::Captured;              } + +            *action = state::Action::Idle;          }          Event::Mouse(mouse::Event::CursorMoved { .. })          | Event::Touch(touch::Event::FingerMoved { .. }) => { @@ -671,13 +678,13 @@ fn layout_region(layout: Layout<'_>, cursor_position: Point) -> Option<Region> {      }      let region = if cursor_position.x < (bounds.x + bounds.width / 3.0) { -        Region::Left +        Region::Edge(Edge::Left)      } else if cursor_position.x > (bounds.x + 2.0 * bounds.width / 3.0) { -        Region::Right +        Region::Edge(Edge::Right)      } else if cursor_position.y < (bounds.y + bounds.height / 3.0) { -        Region::Top +        Region::Edge(Edge::Top)      } else if cursor_position.y > (bounds.y + 2.0 * bounds.height / 3.0) { -        Region::Bottom +        Region::Edge(Edge::Bottom)      } else {          Region::Center      }; @@ -833,28 +840,36 @@ pub fn draw<Renderer, T>(      let mut render_picked_pane = None; -    for ((id, pane), layout) in contents.zip(layout.children()) { +    let pane_in_edge = if picked_pane.is_some() { +        cursor +            .position() +            .and_then(|cursor_position| in_edge(layout, cursor_position)) +    } else { +        None +    }; + +    for ((id, pane), pane_layout) in contents.zip(layout.children()) {          match picked_pane {              Some((dragging, origin)) if id == dragging => { -                render_picked_pane = Some((pane, origin, layout)); +                render_picked_pane = Some((pane, origin, pane_layout));              }              Some((dragging, _)) if id != dragging => {                  draw_pane(                      pane,                      renderer,                      default_style, -                    layout, +                    pane_layout,                      pane_cursor,                      viewport,                  ); -                if picked_pane.is_some() { +                if picked_pane.is_some() && pane_in_edge.is_none() {                      if let Some(region) =                          cursor.position().and_then(|cursor_position| { -                            layout_region(layout, cursor_position) +                            layout_region(pane_layout, cursor_position)                          })                      { -                        let bounds = layout_region_bounds(layout, region); +                        let bounds = layout_region_bounds(pane_layout, region);                          let hovered_region_style = theme.hovered_region(style);                          renderer.fill_quad( @@ -875,7 +890,7 @@ pub fn draw<Renderer, T>(                      pane,                      renderer,                      default_style, -                    layout, +                    pane_layout,                      pane_cursor,                      viewport,                  ); @@ -883,6 +898,21 @@ pub fn draw<Renderer, T>(          }      } +    if let Some(edge) = pane_in_edge { +        let hovered_region_style = theme.hovered_region(style); +        let bounds = edge_bounds(layout, edge); + +        renderer.fill_quad( +            renderer::Quad { +                bounds, +                border_radius: hovered_region_style.border_radius, +                border_width: hovered_region_style.border_width, +                border_color: hovered_region_style.border_color, +            }, +            theme.hovered_region(style).background, +        ); +    } +      // Render picked pane last      if let Some((pane, origin, layout)) = render_picked_pane {          if let Some(cursor_position) = cursor.position() { @@ -907,71 +937,131 @@ pub fn draw<Renderer, T>(          }      } -    if let Some((axis, split_region, is_picked)) = picked_split { -        let highlight = if is_picked { -            theme.picked_split(style) -        } else { -            theme.hovered_split(style) -        }; - -        if let Some(highlight) = highlight { -            renderer.fill_quad( -                renderer::Quad { -                    bounds: match axis { -                        Axis::Horizontal => Rectangle { -                            x: split_region.x, -                            y: (split_region.y -                                + (split_region.height - highlight.width) -                                    / 2.0) -                                .round(), -                            width: split_region.width, -                            height: highlight.width, -                        }, -                        Axis::Vertical => Rectangle { -                            x: (split_region.x -                                + (split_region.width - highlight.width) / 2.0) -                                .round(), -                            y: split_region.y, -                            width: highlight.width, -                            height: split_region.height, +    if picked_pane.is_none() { +        if let Some((axis, split_region, is_picked)) = picked_split { +            let highlight = if is_picked { +                theme.picked_split(style) +            } else { +                theme.hovered_split(style) +            }; + +            if let Some(highlight) = highlight { +                renderer.fill_quad( +                    renderer::Quad { +                        bounds: match axis { +                            Axis::Horizontal => Rectangle { +                                x: split_region.x, +                                y: (split_region.y +                                    + (split_region.height - highlight.width) +                                        / 2.0) +                                    .round(), +                                width: split_region.width, +                                height: highlight.width, +                            }, +                            Axis::Vertical => Rectangle { +                                x: (split_region.x +                                    + (split_region.width - highlight.width) +                                        / 2.0) +                                    .round(), +                                y: split_region.y, +                                width: highlight.width, +                                height: split_region.height, +                            },                          }, +                        border_radius: 0.0.into(), +                        border_width: 0.0, +                        border_color: Color::TRANSPARENT,                      }, -                    border_radius: 0.0.into(), -                    border_width: 0.0, -                    border_color: Color::TRANSPARENT, -                }, -                highlight.color, -            ); +                    highlight.color, +                ); +            }          }      }  } -fn layout_region_bounds(layout: Layout<'_>, region: Region) -> Rectangle { +const THICKNESS_RATIO: f32 = 25.0; + +fn in_edge(layout: Layout<'_>, cursor: Point) -> Option<Edge> {      let bounds = layout.bounds(); -    match region { -        Region::Center => bounds, -        Region::Top => Rectangle { -            height: bounds.height / 2.0, +    let height_thickness = bounds.height / THICKNESS_RATIO; +    let width_thickness = bounds.width / THICKNESS_RATIO; +    let thickness = height_thickness.min(width_thickness); + +    if cursor.x > bounds.x && cursor.x < bounds.x + thickness { +        Some(Edge::Left) +    } else if cursor.x > bounds.x + bounds.width - thickness +        && cursor.x < bounds.x + bounds.width +    { +        Some(Edge::Right) +    } else if cursor.y > bounds.y && cursor.y < bounds.y + thickness { +        Some(Edge::Top) +    } else if cursor.y > bounds.y + bounds.height - thickness +        && cursor.y < bounds.y + bounds.height +    { +        Some(Edge::Bottom) +    } else { +        None +    } +} + +fn edge_bounds(layout: Layout<'_>, edge: Edge) -> Rectangle { +    let bounds = layout.bounds(); + +    let height_thickness = bounds.height / THICKNESS_RATIO; +    let width_thickness = bounds.width / THICKNESS_RATIO; +    let thickness = height_thickness.min(width_thickness); + +    match edge { +        Edge::Top => Rectangle { +            height: thickness,              ..bounds          }, -        Region::Left => Rectangle { -            width: bounds.width / 2.0, +        Edge::Left => Rectangle { +            width: thickness,              ..bounds          }, -        Region::Right => Rectangle { -            x: bounds.x + bounds.width / 2.0, -            width: bounds.width / 2.0, +        Edge::Right => Rectangle { +            x: bounds.x + bounds.width - thickness, +            width: thickness,              ..bounds          }, -        Region::Bottom => Rectangle { -            y: bounds.y + bounds.height / 2.0, -            height: bounds.height / 2.0, +        Edge::Bottom => Rectangle { +            y: bounds.y + bounds.height - thickness, +            height: thickness,              ..bounds          },      }  } +fn layout_region_bounds(layout: Layout<'_>, region: Region) -> Rectangle { +    let bounds = layout.bounds(); + +    match region { +        Region::Center => bounds, +        Region::Edge(edge) => match edge { +            Edge::Top => Rectangle { +                height: bounds.height / 2.0, +                ..bounds +            }, +            Edge::Left => Rectangle { +                width: bounds.width / 2.0, +                ..bounds +            }, +            Edge::Right => Rectangle { +                x: bounds.x + bounds.width / 2.0, +                width: bounds.width / 2.0, +                ..bounds +            }, +            Edge::Bottom => Rectangle { +                y: bounds.y + bounds.height / 2.0, +                height: bounds.height / 2.0, +                ..bounds +            }, +        }, +    } +} +  /// An event produced during a drag and drop interaction of a [`PaneGrid`].  #[derive(Debug, Clone, Copy)]  pub enum DragEvent { @@ -986,11 +1076,8 @@ pub enum DragEvent {          /// The picked [`Pane`].          pane: Pane, -        /// The [`Pane`] where the picked one was dropped on. -        target: Pane, - -        /// The [`Region`] of the target [`Pane`] where the picked one was dropped on. -        region: Region, +        /// The [`Target`] where the picked [`Pane`] was dropped on. +        target: Target,      },      /// A [`Pane`] was picked and then dropped outside of other [`Pane`] @@ -1001,19 +1088,35 @@ pub enum DragEvent {      },  } +/// The [`Target`] area a pane can be dropped on. +#[derive(Debug, Clone, Copy)] +pub enum Target { +    /// An [`Edge`] of the full [`PaneGrid`]. +    Edge(Edge), +    /// A single [`Pane`] of the [`PaneGrid`]. +    Pane(Pane, Region), +} +  /// The region of a [`Pane`].  #[derive(Debug, Clone, Copy, Default)]  pub enum Region {      /// Center region.      #[default]      Center, -    /// Top region. +    /// Edge region. +    Edge(Edge), +} + +/// The edges of an area. +#[derive(Debug, Clone, Copy)] +pub enum Edge { +    /// Top edge.      Top, -    /// Left region. +    /// Left edge.      Left, -    /// Right region. +    /// Right edge.      Right, -    /// Bottom region. +    /// Bottom edge.      Bottom,  } diff --git a/widget/src/pane_grid/node.rs b/widget/src/pane_grid/node.rs index 3976acd8..6de5920f 100644 --- a/widget/src/pane_grid/node.rs +++ b/widget/src/pane_grid/node.rs @@ -120,6 +120,16 @@ impl Node {          };      } +    pub(crate) fn split_inverse(&mut self, id: Split, axis: Axis, pane: Pane) { +        *self = Node::Split { +            id, +            axis, +            ratio: 0.5, +            a: Box::new(Node::Pane(pane)), +            b: Box::new(self.clone()), +        }; +    } +      pub(crate) fn update(&mut self, f: &impl Fn(&mut Node)) {          if let Node::Split { a, b, .. } = self {              a.update(f); diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs index 1f034ca3..6fd15890 100644 --- a/widget/src/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -3,7 +3,7 @@  //! [`PaneGrid`]: crate::widget::PaneGrid  use crate::core::{Point, Size};  use crate::pane_grid::{ -    Axis, Configuration, Direction, Node, Pane, Region, Split, +    Axis, Configuration, Direction, Edge, Node, Pane, Region, Split, Target,  };  use std::collections::HashMap; @@ -145,7 +145,55 @@ impl<T> State<T> {          pane: &Pane,          state: T,      ) -> Option<(Pane, Split)> { -        let node = self.internal.layout.find(pane)?; +        self.split_node(axis, Some(pane), state, false) +    } + +    /// Split a target [`Pane`] with a given [`Pane`] on a given [`Region`]. +    /// +    /// Panes will be swapped by default for [`Region::Center`]. +    pub fn split_with(&mut self, target: &Pane, pane: &Pane, region: Region) { +        match region { +            Region::Center => self.swap(pane, target), +            Region::Edge(edge) => match edge { +                Edge::Top => { +                    self.split_and_swap(Axis::Horizontal, target, pane, true) +                } +                Edge::Bottom => { +                    self.split_and_swap(Axis::Horizontal, target, pane, false) +                } +                Edge::Left => { +                    self.split_and_swap(Axis::Vertical, target, pane, true) +                } +                Edge::Right => { +                    self.split_and_swap(Axis::Vertical, target, pane, false) +                } +            }, +        } +    } + +    /// Drops the given [`Pane`] into the provided [`Target`]. +    pub fn drop(&mut self, pane: &Pane, target: Target) { +        match target { +            Target::Edge(edge) => self.move_to_edge(pane, edge), +            Target::Pane(target, region) => { +                self.split_with(&target, pane, region) +            } +        } +    } + +    fn split_node( +        &mut self, +        axis: Axis, +        pane: Option<&Pane>, +        state: T, +        inverse: bool, +    ) -> Option<(Pane, Split)> { +        let node = if let Some(pane) = pane { +            self.internal.layout.find(pane)? +        } else { +            // Major node +            &mut self.internal.layout +        };          let new_pane = {              self.internal.last_id = self.internal.last_id.checked_add(1)?; @@ -159,7 +207,11 @@ impl<T> State<T> {              Split(self.internal.last_id)          }; -        node.split(new_split, axis, new_pane); +        if inverse { +            node.split_inverse(new_split, axis, new_pane); +        } else { +            node.split(new_split, axis, new_pane); +        }          let _ = self.panes.insert(new_pane, state);          let _ = self.maximized.take(); @@ -167,27 +219,6 @@ impl<T> State<T> {          Some((new_pane, new_split))      } -    /// Split a target [`Pane`] with a given [`Pane`] on a given [`Region`]. -    /// -    /// Panes will be swapped by default for [`Region::Center`]. -    pub fn split_with(&mut self, target: &Pane, pane: &Pane, region: Region) { -        match region { -            Region::Center => self.swap(pane, target), -            Region::Top => { -                self.split_and_swap(Axis::Horizontal, target, pane, true) -            } -            Region::Bottom => { -                self.split_and_swap(Axis::Horizontal, target, pane, false) -            } -            Region::Left => { -                self.split_and_swap(Axis::Vertical, target, pane, true) -            } -            Region::Right => { -                self.split_and_swap(Axis::Vertical, target, pane, false) -            } -        } -    } -      fn split_and_swap(          &mut self,          axis: Axis, @@ -204,6 +235,35 @@ impl<T> State<T> {          }      } +    /// Move [`Pane`] to an [`Edge`] of the [`PaneGrid`]. +    pub fn move_to_edge(&mut self, pane: &Pane, edge: Edge) { +        match edge { +            Edge::Top => { +                self.split_major_node_and_swap(Axis::Horizontal, pane, true) +            } +            Edge::Bottom => { +                self.split_major_node_and_swap(Axis::Horizontal, pane, false) +            } +            Edge::Left => { +                self.split_major_node_and_swap(Axis::Vertical, pane, true) +            } +            Edge::Right => { +                self.split_major_node_and_swap(Axis::Vertical, pane, false) +            } +        } +    } + +    fn split_major_node_and_swap( +        &mut self, +        axis: Axis, +        pane: &Pane, +        swap: bool, +    ) { +        if let Some((state, _)) = self.close(pane) { +            let _ = self.split_node(axis, None, state, swap); +        } +    } +      /// Swaps the position of the provided panes in the [`State`].      ///      /// If you want to swap panes on drag and drop in your [`PaneGrid`], you | 
