diff options
Diffstat (limited to 'native/src/widget')
| -rw-r--r-- | native/src/widget/pane_grid.rs | 233 | ||||
| -rw-r--r-- | native/src/widget/pane_grid/axis.rs | 24 | ||||
| -rw-r--r-- | native/src/widget/slider.rs | 25 | 
3 files changed, 177 insertions, 105 deletions
| diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 076ae76f..2d21a968 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 @@ -76,7 +76,7 @@ use crate::{  ///         }.into()  ///     })  ///     .on_drag(Message::PaneDragged) -///     .on_resize(Message::PaneResized); +///     .on_resize(10, Message::PaneResized);  /// ```  ///  /// [`PaneGrid`]: struct.PaneGrid.html @@ -91,7 +91,7 @@ pub struct PaneGrid<'a, Message, Renderer> {      spacing: u16,      modifier_keys: keyboard::ModifiersState,      on_drag: Option<Box<dyn Fn(DragEvent) -> Message + 'a>>, -    on_resize: Option<Box<dyn Fn(ResizeEvent) -> Message + 'a>>, +    on_resize: Option<(u16, Box<dyn Fn(ResizeEvent) -> Message + 'a>)>,      on_key_press: Option<Box<dyn Fn(KeyPressEvent) -> Option<Message> + 'a>>,  } @@ -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,14 +208,19 @@ 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`. +    /// 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<F>(mut self, f: F) -> Self +    pub fn on_resize<F>(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      } @@ -244,13 +249,42 @@ 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<'_>,          cursor_position: Point,          messages: &mut Vec<Message>,      ) { -        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(); @@ -409,32 +443,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((leeway, _)) => { +                                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 + leeway), +                                    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 +509,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 +576,37 @@ where          layout: Layout<'_>,          cursor_position: Point,      ) -> Renderer::Output { +        let picked_split = self +            .state +            .picked_split() +            .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 +                        .splits(f32::from(self.spacing), bounds.size()); + +                    hovered_split( +                        splits.iter(), +                        f32::from(self.spacing + leeway), +                        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 +670,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/slider.rs b/native/src/widget/slider.rs index 8cdfc3de..753a49fe 100644 --- a/native/src/widget/slider.rs +++ b/native/src/widget/slider.rs @@ -40,6 +40,7 @@ pub struct Slider<'a, Message, Renderer: self::Renderer> {      range: RangeInclusive<f32>,      value: f32,      on_change: Box<dyn Fn(f32) -> Message>, +    on_release: Option<Message>,      width: Length,      style: Renderer::Style,  } @@ -71,11 +72,25 @@ impl<'a, Message, Renderer: self::Renderer> Slider<'a, Message, Renderer> {              value: value.max(*range.start()).min(*range.end()),              range,              on_change: Box::new(on_change), +            on_release: None,              width: Length::Fill,              style: Renderer::Style::default(),          }      } +    /// Sets the release message of the [`Slider`]. +    /// This is called when the mouse is released from the slider. +    /// +    /// Typically, the user's interaction with the slider is finished when this message is produced. +    /// This is useful if you need to spawn a long-running task from the slider's result, where +    /// the default on_change message could create too many events. +    /// +    /// [`Slider`]: struct.Slider.html +    pub fn on_release(mut self, on_release: Message) -> Self { +        self.on_release = Some(on_release); +        self +    } +      /// Sets the width of the [`Slider`].      ///      /// [`Slider`]: struct.Slider.html @@ -114,6 +129,7 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>      for Slider<'a, Message, Renderer>  where      Renderer: self::Renderer, +    Message: Clone,  {      fn width(&self) -> Length {          self.width @@ -171,7 +187,12 @@ where                      }                  }                  mouse::Event::ButtonReleased(mouse::Button::Left) => { -                    self.state.is_dragging = false; +                    if self.state.is_dragging { +                        if let Some(on_release) = self.on_release.clone() { +                            messages.push(on_release); +                        } +                        self.state.is_dragging = false; +                    }                  }                  mouse::Event::CursorMoved { .. } => {                      if self.state.is_dragging { @@ -252,7 +273,7 @@ impl<'a, Message, Renderer> From<Slider<'a, Message, Renderer>>      for Element<'a, Message, Renderer>  where      Renderer: 'a + self::Renderer, -    Message: 'a, +    Message: 'a + Clone,  {      fn from(          slider: Slider<'a, Message, Renderer>, | 
