diff options
Diffstat (limited to 'native')
| -rw-r--r-- | native/src/widget/pane_grid.rs | 781 | ||||
| -rw-r--r-- | native/src/widget/pane_grid/axis.rs | 7 | ||||
| -rw-r--r-- | native/src/widget/pane_grid/content.rs | 39 | ||||
| -rw-r--r-- | native/src/widget/pane_grid/draggable.rs | 12 | ||||
| -rw-r--r-- | native/src/widget/pane_grid/state.rs | 114 | 
5 files changed, 547 insertions, 406 deletions
| diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 8ad63cf1..2093886e 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -11,16 +11,19 @@ mod axis;  mod configuration;  mod content;  mod direction; +mod draggable;  mod node;  mod pane;  mod split; -mod state;  mod title_bar; +pub mod state; +  pub use axis::Axis;  pub use configuration::Configuration;  pub use content::Content;  pub use direction::Direction; +pub use draggable::Draggable;  pub use node::Node;  pub use pane::Pane;  pub use split::Split; @@ -92,6 +95,7 @@ pub use iced_style::pane_grid::{Line, StyleSheet};  #[allow(missing_debug_implementations)]  pub struct PaneGrid<'a, Message, Renderer> {      state: &'a mut state::Internal, +    action: &'a mut state::Action,      elements: Vec<(Pane, Content<'a, Message, Renderer>)>,      width: Length,      height: Length, @@ -124,6 +128,7 @@ where          Self {              state: &mut state.internal, +            action: &mut state.action,              elements,              width: Length::Fill,              height: Length::Fill, @@ -197,80 +202,407 @@ where      }  } -impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> -where -    Renderer: crate::Renderer, -{ -    fn click_pane( -        &mut self, -        layout: Layout<'_>, -        cursor_position: Point, -        shell: &mut Shell<'_, Message>, -    ) { -        let mut clicked_region = -            self.elements.iter().zip(layout.children()).filter( -                |(_, layout)| layout.bounds().contains(cursor_position), +/// Calculates the [`Layout`] of a [`PaneGrid`]. +pub fn layout<Renderer, T>( +    renderer: &Renderer, +    limits: &layout::Limits, +    state: &state::Internal, +    width: Length, +    height: Length, +    spacing: u16, +    elements: impl Iterator<Item = (Pane, T)>, +    layout_element: impl Fn(T, &Renderer, &layout::Limits) -> layout::Node, +) -> layout::Node { +    let limits = limits.width(width).height(height); +    let size = limits.resolve(Size::ZERO); + +    let regions = state.pane_regions(f32::from(spacing), size); +    let children = elements +        .filter_map(|(pane, element)| { +            let region = regions.get(&pane)?; +            let size = Size::new(region.width, region.height); + +            let mut node = layout_element( +                element, +                renderer, +                &layout::Limits::new(size, size),              ); -        if let Some(((pane, content), layout)) = clicked_region.next() { -            if let Some(on_click) = &self.on_click { -                shell.publish(on_click(*pane)); +            node.move_to(Point::new(region.x, region.y)); + +            Some(node) +        }) +        .collect(); + +    layout::Node::with_children(size, children) +} + +/// Processes an [`Event`] and updates the [`state`] of a [`PaneGrid`] +/// accordingly. +pub fn update<'a, Message, T: Draggable>( +    action: &mut state::Action, +    state: &state::Internal, +    event: &Event, +    layout: Layout<'_>, +    cursor_position: Point, +    shell: &mut Shell<'_, Message>, +    spacing: u16, +    elements: impl Iterator<Item = (Pane, T)>, +    on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>, +    on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, +    on_resize: &Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>, +) -> event::Status { +    let mut event_status = event::Status::Ignored; + +    match event { +        Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) +        | Event::Touch(touch::Event::FingerPressed { .. }) => { +            let bounds = layout.bounds(); + +            if bounds.contains(cursor_position) { +                event_status = event::Status::Captured; + +                match on_resize { +                    Some((leeway, _)) => { +                        let relative_cursor = Point::new( +                            cursor_position.x - bounds.x, +                            cursor_position.y - bounds.y, +                        ); + +                        let splits = state.split_regions( +                            f32::from(spacing), +                            Size::new(bounds.width, bounds.height), +                        ); + +                        let clicked_split = hovered_split( +                            splits.iter(), +                            f32::from(spacing + leeway), +                            relative_cursor, +                        ); + +                        if let Some((split, axis, _)) = clicked_split { +                            if action.picked_pane().is_none() { +                                *action = +                                    state::Action::Resizing { split, axis }; +                            } +                        } else { +                            click_pane( +                                action, +                                layout, +                                cursor_position, +                                shell, +                                elements, +                                on_click, +                                on_drag, +                            ); +                        } +                    } +                    None => { +                        click_pane( +                            action, +                            layout, +                            cursor_position, +                            shell, +                            elements, +                            on_click, +                            on_drag, +                        ); +                    } +                }              } +        } +        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) +        | Event::Touch(touch::Event::FingerLifted { .. }) +        | Event::Touch(touch::Event::FingerLost { .. }) => { +            if let Some((pane, _)) = action.picked_pane() { +                if let Some(on_drag) = on_drag { +                    let mut dropped_region = elements +                        .zip(layout.children()) +                        .filter(|(_, layout)| { +                            layout.bounds().contains(cursor_position) +                        }); + +                    let event = match dropped_region.next() { +                        Some(((target, _), _)) if pane != target => { +                            DragEvent::Dropped { pane, target } +                        } +                        _ => DragEvent::Canceled { pane }, +                    }; + +                    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; +            } +        } +        Event::Mouse(mouse::Event::CursorMoved { .. }) +        | Event::Touch(touch::Event::FingerMoved { .. }) => { +            if let Some((_, on_resize)) = on_resize { +                if let Some((split, _)) = action.picked_split() { +                    let bounds = layout.bounds(); + +                    let splits = state.split_regions( +                        f32::from(spacing), +                        Size::new(bounds.width, bounds.height), +                    ); -            if let Some(on_drag) = &self.on_drag { -                if content.can_be_picked_at(layout, cursor_position) { -                    let pane_position = layout.position(); +                    if let Some((axis, rectangle, _)) = splits.get(&split) { +                        let ratio = match axis { +                            Axis::Horizontal => { +                                let position = +                                    cursor_position.y - bounds.y - rectangle.y; -                    let origin = cursor_position -                        - Vector::new(pane_position.x, pane_position.y); +                                (position / rectangle.height).max(0.1).min(0.9) +                            } +                            Axis::Vertical => { +                                let position = +                                    cursor_position.x - bounds.x - rectangle.x; -                    self.state.pick_pane(pane, origin); +                                (position / rectangle.width).max(0.1).min(0.9) +                            } +                        }; -                    shell.publish(on_drag(DragEvent::Picked { pane: *pane })); +                        shell.publish(on_resize(ResizeEvent { split, ratio })); + +                        event_status = event::Status::Captured; +                    }                  }              }          } +        _ => {}      } -    fn trigger_resize( -        &mut self, -        layout: Layout<'_>, -        cursor_position: Point, -        shell: &mut Shell<'_, Message>, -    ) -> event::Status { -        if let Some((_, on_resize)) = &self.on_resize { -            if let Some((split, _)) = self.state.picked_split() { +    event_status +} + +fn click_pane<'a, Message, T>( +    action: &mut state::Action, +    layout: Layout<'_>, +    cursor_position: Point, +    shell: &mut Shell<'_, Message>, +    elements: impl Iterator<Item = (Pane, T)>, +    on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>, +    on_drag: &Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, +) where +    T: Draggable, +{ +    let mut clicked_region = elements +        .zip(layout.children()) +        .filter(|(_, layout)| layout.bounds().contains(cursor_position)); + +    if let Some(((pane, content), layout)) = clicked_region.next() { +        if let Some(on_click) = &on_click { +            shell.publish(on_click(pane)); +        } + +        if let Some(on_drag) = &on_drag { +            if content.can_be_dragged_at(layout, cursor_position) { +                let pane_position = layout.position(); + +                let origin = cursor_position +                    - Vector::new(pane_position.x, pane_position.y); + +                *action = state::Action::Dragging { pane, origin }; + +                shell.publish(on_drag(DragEvent::Picked { pane })); +            } +        } +    } +} + +/// Returns the current [`mouse::Interaction`] of a [`PaneGrid`]. +pub fn mouse_interaction( +    action: &state::Action, +    state: &state::Internal, +    layout: Layout<'_>, +    cursor_position: Point, +    spacing: u16, +    resize_leeway: Option<u16>, +) -> Option<mouse::Interaction> { +    if action.picked_pane().is_some() { +        return Some(mouse::Interaction::Grab); +    } + +    let resize_axis = +        action.picked_split().map(|(_, axis)| axis).or_else(|| { +            resize_leeway.and_then(|leeway| {                  let bounds = layout.bounds(); -                let splits = self.state.split_regions( -                    f32::from(self.spacing), -                    Size::new(bounds.width, bounds.height), +                let splits = +                    state.split_regions(f32::from(spacing), bounds.size()); + +                let relative_cursor = Point::new( +                    cursor_position.x - bounds.x, +                    cursor_position.y - bounds.y,                  ); -                if let Some((axis, rectangle, _)) = splits.get(&split) { -                    let ratio = match axis { -                        Axis::Horizontal => { -                            let position = -                                cursor_position.y - bounds.y - rectangle.y; +                hovered_split( +                    splits.iter(), +                    f32::from(spacing + leeway), +                    relative_cursor, +                ) +                .map(|(_, axis, _)| axis) +            }) +        }); -                            (position / rectangle.height).max(0.1).min(0.9) -                        } -                        Axis::Vertical => { -                            let position = -                                cursor_position.x - bounds.x - rectangle.x; +    if let Some(resize_axis) = resize_axis { +        return Some(match resize_axis { +            Axis::Horizontal => mouse::Interaction::ResizingVertically, +            Axis::Vertical => mouse::Interaction::ResizingHorizontally, +        }); +    } -                            (position / rectangle.width).max(0.1).min(0.9) -                        } -                    }; +    None +} + +/// Draws a [`PaneGrid`]. +pub fn draw<Renderer, T>( +    action: &state::Action, +    state: &state::Internal, +    layout: Layout<'_>, +    cursor_position: Point, +    renderer: &mut Renderer, +    style: &renderer::Style, +    viewport: &Rectangle, +    spacing: u16, +    resize_leeway: Option<u16>, +    style_sheet: &dyn StyleSheet, +    elements: impl Iterator<Item = (Pane, T)>, +    draw_pane: impl Fn( +        T, +        &mut Renderer, +        &renderer::Style, +        Layout<'_>, +        Point, +        &Rectangle, +    ), +) where +    Renderer: crate::Renderer, +{ +    let picked_pane = action.picked_pane(); -                    shell.publish(on_resize(ResizeEvent { split, ratio })); +    let picked_split = action +        .picked_split() +        .and_then(|(split, axis)| { +            let bounds = layout.bounds(); -                    return event::Status::Captured; -                } +            let splits = state.split_regions(f32::from(spacing), bounds.size()); + +            let (_axis, region, ratio) = splits.get(&split)?; + +            let region = +                axis.split_line_bounds(*region, *ratio, f32::from(spacing)); + +            Some((axis, region + Vector::new(bounds.x, bounds.y), true)) +        }) +        .or_else(|| match resize_leeway { +            Some(leeway) => { +                let bounds = layout.bounds(); + +                let relative_cursor = Point::new( +                    cursor_position.x - bounds.x, +                    cursor_position.y - bounds.y, +                ); + +                let splits = +                    state.split_regions(f32::from(spacing), bounds.size()); + +                let (_split, axis, region) = hovered_split( +                    splits.iter(), +                    f32::from(spacing + leeway), +                    relative_cursor, +                )?; + +                Some((axis, region + Vector::new(bounds.x, bounds.y), false)) +            } +            None => None, +        }); + +    let pane_cursor_position = if picked_pane.is_some() { +        // TODO: Remove once cursor availability is encoded in the type +        // system +        Point::new(-1.0, -1.0) +    } else { +        cursor_position +    }; + +    for ((id, pane), layout) in elements.zip(layout.children()) { +        match picked_pane { +            Some((dragging, origin)) if id == dragging => { +                let bounds = layout.bounds(); + +                renderer.with_translation( +                    cursor_position +                        - Point::new(bounds.x + origin.x, bounds.y + origin.y), +                    |renderer| { +                        renderer.with_layer(bounds, |renderer| { +                            draw_pane( +                                pane, +                                renderer, +                                style, +                                layout, +                                pane_cursor_position, +                                viewport, +                            ); +                        }); +                    }, +                ); +            } +            _ => { +                draw_pane( +                    pane, +                    renderer, +                    style, +                    layout, +                    pane_cursor_position, +                    viewport, +                );              }          } +    } -        event::Status::Ignored +    if let Some((axis, split_region, is_picked)) = picked_split { +        let highlight = if is_picked { +            style_sheet.picked_split() +        } else { +            style_sheet.hovered_split() +        }; + +        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, +                    border_width: 0.0, +                    border_color: Color::TRANSPARENT, +                }, +                highlight.color, +            ); +        }      }  } @@ -331,28 +663,16 @@ where          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.pane_regions(f32::from(self.spacing), 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) +        layout( +            renderer, +            limits, +            self.state, +            self.width, +            self.height, +            self.spacing, +            self.elements.iter().map(|(pane, content)| (*pane, content)), +            |element, renderer, limits| element.layout(renderer, limits), +        )      }      fn on_event( @@ -364,89 +684,21 @@ where          clipboard: &mut dyn Clipboard,          shell: &mut Shell<'_, Message>,      ) -> event::Status { -        let mut event_status = event::Status::Ignored; - -        match event { -            Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) -            | Event::Touch(touch::Event::FingerPressed { .. }) => { -                let bounds = layout.bounds(); - -                if bounds.contains(cursor_position) { -                    event_status = event::Status::Captured; - -                    match self.on_resize { -                        Some((leeway, _)) => { -                            let relative_cursor = Point::new( -                                cursor_position.x - bounds.x, -                                cursor_position.y - bounds.y, -                            ); - -                            let splits = self.state.split_regions( -                                f32::from(self.spacing), -                                Size::new(bounds.width, bounds.height), -                            ); - -                            let clicked_split = hovered_split( -                                splits.iter(), -                                f32::from(self.spacing + leeway), -                                relative_cursor, -                            ); - -                            if let Some((split, axis, _)) = clicked_split { -                                self.state.pick_split(&split, axis); -                            } else { -                                self.click_pane(layout, cursor_position, shell); -                            } -                        } -                        None => { -                            self.click_pane(layout, cursor_position, shell); -                        } -                    } -                } -            } -            Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) -            | Event::Touch(touch::Event::FingerLifted { .. }) -            | Event::Touch(touch::Event::FingerLost { .. }) => { -                if let Some((pane, _)) = self.state.picked_pane() { -                    if let Some(on_drag) = &self.on_drag { -                        let mut dropped_region = -                            self.elements.iter().zip(layout.children()).filter( -                                |(_, layout)| { -                                    layout.bounds().contains(cursor_position) -                                }, -                            ); - -                        let event = match dropped_region.next() { -                            Some(((target, _), _)) if pane != *target => { -                                DragEvent::Dropped { -                                    pane, -                                    target: *target, -                                } -                            } -                            _ => DragEvent::Canceled { pane }, -                        }; - -                        shell.publish(on_drag(event)); -                    } - -                    self.state.idle(); - -                    event_status = event::Status::Captured; -                } else if self.state.picked_split().is_some() { -                    self.state.idle(); - -                    event_status = event::Status::Captured; -                } -            } -            Event::Mouse(mouse::Event::CursorMoved { .. }) -            | Event::Touch(touch::Event::FingerMoved { .. }) => { -                event_status = -                    self.trigger_resize(layout, cursor_position, shell); -            } -            _ => {} -        } - -        let picked_pane = self.state.picked_pane().map(|(pane, _)| pane); +        let event_status = update( +            self.action, +            self.state, +            &event, +            layout, +            cursor_position, +            shell, +            self.spacing, +            self.elements.iter().map(|(pane, content)| (*pane, content)), +            &self.on_click, +            &self.on_drag, +            &self.on_resize, +        ); + +        let picked_pane = self.action.picked_pane().map(|(pane, _)| pane);          self.elements              .iter_mut() @@ -474,53 +726,29 @@ where          viewport: &Rectangle,          renderer: &Renderer,      ) -> mouse::Interaction { -        if self.state.picked_pane().is_some() { -            return mouse::Interaction::Grab; -        } - -        let resize_axis = -            self.state.picked_split().map(|(_, axis)| axis).or_else(|| { -                self.on_resize.as_ref().and_then(|(leeway, _)| { -                    let bounds = layout.bounds(); - -                    let splits = self -                        .state -                        .split_regions(f32::from(self.spacing), bounds.size()); - -                    let relative_cursor = Point::new( -                        cursor_position.x - bounds.x, -                        cursor_position.y - bounds.y, -                    ); - -                    hovered_split( -                        splits.iter(), -                        f32::from(self.spacing + leeway), -                        relative_cursor, +        mouse_interaction( +            self.action, +            self.state, +            layout, +            cursor_position, +            self.spacing, +            self.on_resize.as_ref().map(|(leeway, _)| *leeway), +        ) +        .unwrap_or_else(|| { +            self.elements +                .iter() +                .zip(layout.children()) +                .map(|((_pane, content), layout)| { +                    content.mouse_interaction( +                        layout, +                        cursor_position, +                        viewport, +                        renderer,                      ) -                    .map(|(_, axis, _)| axis)                  }) -            }); - -        if let Some(resize_axis) = resize_axis { -            return match resize_axis { -                Axis::Horizontal => mouse::Interaction::ResizingVertically, -                Axis::Vertical => mouse::Interaction::ResizingHorizontally, -            }; -        } - -        self.elements -            .iter() -            .zip(layout.children()) -            .map(|((_pane, content), layout)| { -                content.mouse_interaction( -                    layout, -                    cursor_position, -                    viewport, -                    renderer, -                ) -            }) -            .max() -            .unwrap_or_default() +                .max() +                .unwrap_or_default() +        })      }      fn draw( @@ -531,139 +759,22 @@ where          cursor_position: Point,          viewport: &Rectangle,      ) { -        let picked_pane = self.state.picked_pane(); - -        let picked_split = self -            .state -            .picked_split() -            .and_then(|(split, axis)| { -                let bounds = layout.bounds(); - -                let splits = self -                    .state -                    .split_regions(f32::from(self.spacing), bounds.size()); - -                let (_axis, region, ratio) = splits.get(&split)?; - -                let region = axis.split_line_bounds( -                    *region, -                    *ratio, -                    f32::from(self.spacing), -                ); - -                Some((axis, region + Vector::new(bounds.x, bounds.y), true)) -            }) -            .or_else(|| match self.on_resize { -                Some((leeway, _)) => { -                    let bounds = layout.bounds(); - -                    let relative_cursor = Point::new( -                        cursor_position.x - bounds.x, -                        cursor_position.y - bounds.y, -                    ); - -                    let splits = self -                        .state -                        .split_regions(f32::from(self.spacing), bounds.size()); - -                    let (_split, axis, region) = hovered_split( -                        splits.iter(), -                        f32::from(self.spacing + leeway), -                        relative_cursor, -                    )?; - -                    Some(( -                        axis, -                        region + Vector::new(bounds.x, bounds.y), -                        false, -                    )) -                } -                None => None, -            }); - -        let pane_cursor_position = if picked_pane.is_some() { -            // TODO: Remove once cursor availability is encoded in the type -            // system -            Point::new(-1.0, -1.0) -        } else { -            cursor_position -        }; - -        for ((id, pane), layout) in self.elements.iter().zip(layout.children()) -        { -            match picked_pane { -                Some((dragging, origin)) if *id == dragging => { -                    let bounds = layout.bounds(); - -                    renderer.with_translation( -                        cursor_position -                            - Point::new( -                                bounds.x + origin.x, -                                bounds.y + origin.y, -                            ), -                        |renderer| { -                            renderer.with_layer(bounds, |renderer| { -                                pane.draw( -                                    renderer, -                                    style, -                                    layout, -                                    pane_cursor_position, -                                    viewport, -                                ); -                            }); -                        }, -                    ); -                } -                _ => { -                    pane.draw( -                        renderer, -                        style, -                        layout, -                        pane_cursor_position, -                        viewport, -                    ); -                } -            } -        } - -        if let Some((axis, split_region, is_picked)) = picked_split { -            let highlight = if is_picked { -                self.style_sheet.picked_split() -            } else { -                self.style_sheet.hovered_split() -            }; - -            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, -                        border_width: 0.0, -                        border_color: Color::TRANSPARENT, -                    }, -                    highlight.color, -                ); -            } -        } +        draw( +            self.action, +            self.state, +            layout, +            cursor_position, +            renderer, +            style, +            viewport, +            self.spacing, +            self.on_resize.as_ref().map(|(leeway, _)| *leeway), +            self.style_sheet.as_ref(), +            self.elements.iter().map(|(pane, content)| (*pane, content)), +            |pane, renderer, style, layout, cursor_position, rectangle| { +                pane.draw(renderer, style, layout, cursor_position, rectangle); +            }, +        )      }      fn overlay( diff --git a/native/src/widget/pane_grid/axis.rs b/native/src/widget/pane_grid/axis.rs index 2320cb7c..02bde064 100644 --- a/native/src/widget/pane_grid/axis.rs +++ b/native/src/widget/pane_grid/axis.rs @@ -10,7 +10,9 @@ pub enum Axis {  }  impl Axis { -    pub(super) fn split( +    /// Splits the provided [`Rectangle`] on the current [`Axis`] with the +    /// given `ratio` and `spacing`. +    pub fn split(          &self,          rectangle: &Rectangle,          ratio: f32, @@ -54,7 +56,8 @@ impl Axis {          }      } -    pub(super) fn split_line_bounds( +    /// Calculates the bounds of the split line in a [`Rectangle`] region. +    pub fn split_line_bounds(          &self,          rectangle: Rectangle,          ratio: f32, diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs index 8b0e8d2a..f0ed0426 100644 --- a/native/src/widget/pane_grid/content.rs +++ b/native/src/widget/pane_grid/content.rs @@ -4,7 +4,7 @@ use crate::mouse;  use crate::overlay;  use crate::renderer;  use crate::widget::container; -use crate::widget::pane_grid::TitleBar; +use crate::widget::pane_grid::{Draggable, TitleBar};  use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};  /// The content of a [`Pane`]. @@ -101,23 +101,6 @@ where          }      } -    /// Returns whether the [`Content`] with the given [`Layout`] can be picked -    /// at the provided cursor position. -    pub fn can_be_picked_at( -        &self, -        layout: Layout<'_>, -        cursor_position: Point, -    ) -> bool { -        if let Some(title_bar) = &self.title_bar { -            let mut children = layout.children(); -            let title_bar_layout = children.next().unwrap(); - -            title_bar.is_over_pick_area(title_bar_layout, cursor_position) -        } else { -            false -        } -    } -      pub(crate) fn layout(          &self,          renderer: &Renderer, @@ -253,6 +236,26 @@ where      }  } +impl<'a, Message, Renderer> Draggable for &Content<'a, Message, Renderer> +where +    Renderer: crate::Renderer, +{ +    fn can_be_dragged_at( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +    ) -> bool { +        if let Some(title_bar) = &self.title_bar { +            let mut children = layout.children(); +            let title_bar_layout = children.next().unwrap(); + +            title_bar.is_over_pick_area(title_bar_layout, cursor_position) +        } else { +            false +        } +    } +} +  impl<'a, T, Message, Renderer> From<T> for Content<'a, Message, Renderer>  where      T: Into<Element<'a, Message, Renderer>>, diff --git a/native/src/widget/pane_grid/draggable.rs b/native/src/widget/pane_grid/draggable.rs new file mode 100644 index 00000000..6044871d --- /dev/null +++ b/native/src/widget/pane_grid/draggable.rs @@ -0,0 +1,12 @@ +use crate::{Layout, Point}; + +/// A pane that can be dragged. +pub trait Draggable { +    /// Returns whether the [`Draggable`] with the given [`Layout`] can be picked +    /// at the provided cursor position. +    fn can_be_dragged_at( +        &self, +        layout: Layout<'_>, +        cursor_position: Point, +    ) -> bool; +} diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index feea0dec..f9ea21f4 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -1,3 +1,4 @@ +//! The state of a [`PaneGrid`].  use crate::widget::pane_grid::{      Axis, Configuration, Direction, Node, Pane, Split,  }; @@ -19,8 +20,13 @@ use std::collections::{BTreeMap, HashMap};  /// [`PaneGrid::new`]: crate::widget::PaneGrid::new  #[derive(Debug, Clone)]  pub struct State<T> { -    pub(super) panes: HashMap<Pane, T>, -    pub(super) internal: Internal, +    /// The panes of the [`PaneGrid`]. +    pub panes: HashMap<Pane, T>, + +    /// The internal state of the [`PaneGrid`]. +    pub internal: Internal, + +    pub(super) action: Action,  }  impl<T> State<T> { @@ -39,16 +45,13 @@ impl<T> State<T> {      pub fn with_configuration(config: impl Into<Configuration<T>>) -> Self {          let mut panes = HashMap::new(); -        let (layout, last_id) = -            Self::distribute_content(&mut panes, config.into(), 0); +        let internal = +            Internal::from_configuration(&mut panes, config.into(), 0);          State {              panes, -            internal: Internal { -                layout, -                last_id, -                action: Action::Idle, -            }, +            internal, +            action: Action::Idle,          }      } @@ -192,16 +195,34 @@ impl<T> State<T> {              None          }      } +} + +/// The internal state of a [`PaneGrid`]. +#[derive(Debug, Clone)] +pub struct Internal { +    layout: Node, +    last_id: usize, +} -    fn distribute_content( +impl Internal { +    /// Initializes the [`Internal`] state of a [`PaneGrid`] from a +    /// [`Configuration`]. +    pub fn from_configuration<T>(          panes: &mut HashMap<Pane, T>,          content: Configuration<T>,          next_id: usize, -    ) -> (Node, usize) { -        match content { +    ) -> Self { +        let (layout, last_id) = match content {              Configuration::Split { axis, ratio, a, b } => { -                let (a, next_id) = Self::distribute_content(panes, *a, next_id); -                let (b, next_id) = Self::distribute_content(panes, *b, next_id); +                let Internal { +                    layout: a, +                    last_id: next_id, +                } = Self::from_configuration(panes, *a, next_id); + +                let Internal { +                    layout: b, +                    last_id: next_id, +                } = Self::from_configuration(panes, *b, next_id);                  (                      Node::Split { @@ -220,39 +241,53 @@ impl<T> State<T> {                  (Node::Pane(id), next_id + 1)              } -        } -    } -} +        }; -#[derive(Debug, Clone)] -pub struct Internal { -    layout: Node, -    last_id: usize, -    action: Action, +        Self { layout, last_id } +    }  } +/// The current action of a [`PaneGrid`].  #[derive(Debug, Clone, Copy, PartialEq)]  pub enum Action { +    /// The [`PaneGrid`] is idle.      Idle, -    Dragging { pane: Pane, origin: Point }, -    Resizing { split: Split, axis: Axis }, +    /// A [`Pane`] in the [`PaneGrid`] is being dragged. +    Dragging { +        /// The [`Pane`] being dragged. +        pane: Pane, +        /// The starting [`Point`] of the drag interaction. +        origin: Point, +    }, +    /// A [`Split`] in the [`PaneGrid`] is being dragged. +    Resizing { +        /// The [`Split`] being dragged. +        split: Split, +        /// The [`Axis`] of the [`Split`]. +        axis: Axis, +    },  } -impl Internal { +impl Action { +    /// Returns the current [`Pane`] that is being dragged, if any.      pub fn picked_pane(&self) -> Option<(Pane, Point)> { -        match self.action { +        match *self {              Action::Dragging { pane, origin, .. } => Some((pane, origin)),              _ => None,          }      } +    /// Returns the current [`Split`] that is being dragged, if any.      pub fn picked_split(&self) -> Option<(Split, Axis)> { -        match self.action { +        match *self {              Action::Resizing { split, axis, .. } => Some((split, axis)),              _ => None,          }      } +} +impl Internal { +    /// Calculates the current [`Pane`] regions from the [`PaneGrid`] layout.      pub fn pane_regions(          &self,          spacing: f32, @@ -261,6 +296,7 @@ impl Internal {          self.layout.pane_regions(spacing, size)      } +    /// Calculates the current [`Split`] regions from the [`PaneGrid`] layout.      pub fn split_regions(          &self,          spacing: f32, @@ -268,28 +304,4 @@ impl Internal {      ) -> BTreeMap<Split, (Axis, Rectangle, f32)> {          self.layout.split_regions(spacing, size)      } - -    pub fn pick_pane(&mut self, pane: &Pane, origin: Point) { -        self.action = Action::Dragging { -            pane: *pane, -            origin, -        }; -    } - -    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; -        } - -        self.action = Action::Resizing { -            split: *split, -            axis, -        }; -    } - -    pub fn idle(&mut self) { -        self.action = Action::Idle; -    }  } | 
