diff options
Diffstat (limited to '')
| -rw-r--r-- | native/src/widget/scrollable.rs | 323 | 
1 files changed, 139 insertions, 184 deletions
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 374dcf76..28d695ba 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -10,7 +10,7 @@ use crate::{      Rectangle, Size, Vector, Widget,  }; -use std::{cell::RefCell, f32, hash::Hash, u32}; +use std::{f32, hash::Hash, u32};  /// A widget that can vertically display an infinite amount of content with a  /// scrollbar. @@ -24,8 +24,6 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> {      scroller_width: u16,      content: Column<'a, Message, Renderer>,      style: Renderer::Style, -    on_scroll: Option<Box<dyn Fn(f32, f32) -> Message>>, -    snap_to_bottom: bool,  }  impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> { @@ -40,36 +38,9 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {              scroller_width: 10,              content: Column::new(),              style: Renderer::Style::default(), -            on_scroll: None, -            snap_to_bottom: false,          }      } -    /// Whether to set the [`Scrollable`] to snap to bottom when the user -    /// scrolls to bottom or not. This will keep the scrollable at the bottom -    /// even if new content is added to the scrollable. -    /// -    /// [`Scrollable`]: struct.Scrollable.html -    pub fn snap_to_bottom(mut self, snap: bool) -> Self { -        self.snap_to_bottom = snap; -        self -    } - -    /// Sets a function to call when the [`Scrollable`] is scrolled. -    /// -    /// The function takes two `f32` as arguments. First is the percentage of -    /// where the scrollable is at right now. Second is the percentage of where -    /// the scrollable was *before*. `0.0` means top and `1.0` means bottom. -    /// -    /// [`Scrollable`]: struct.Scrollable.html -    pub fn on_scroll<F>(mut self, message_constructor: F) -> Self -    where -        F: 'static + Fn(f32, f32) -> Message, -    { -        self.on_scroll = Some(Box::new(message_constructor)); -        self -    } -      /// Sets the vertical spacing _between_ elements.      ///      /// Custom margins per element do not exist in Iced. You should use this @@ -215,7 +186,7 @@ where              .map(|scrollbar| scrollbar.is_mouse_over(cursor_position))              .unwrap_or(false); -        let mut event_status = { +        let event_status = {              let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {                  Point::new(                      cursor_position.x, @@ -240,78 +211,99 @@ where              )          }; -        if let event::Status::Ignored = event_status { -            self.state.prev_offset = self.state.offset(bounds, content_bounds); +        if let event::Status::Captured = event_status { +            return event::Status::Captured; +        } + +        if is_mouse_over { +            match event { +                Event::Mouse(mouse::Event::WheelScrolled { delta }) => { +                    match delta { +                        mouse::ScrollDelta::Lines { y, .. } => { +                            // TODO: Configurable speed (?) +                            self.state.scroll(y * 60.0, bounds, content_bounds); +                        } +                        mouse::ScrollDelta::Pixels { y, .. } => { +                            self.state.scroll(y, bounds, content_bounds); +                        } +                    } + +                    return event::Status::Captured; +                } +                Event::Touch(event) => { +                    match event { +                        touch::Event::FingerPressed { .. } => { +                            self.state.scroll_box_touched_at = +                                Some(cursor_position); +                        } +                        touch::Event::FingerMoved { .. } => { +                            if let Some(scroll_box_touched_at) = +                                self.state.scroll_box_touched_at +                            { +                                let delta = +                                    cursor_position.y - scroll_box_touched_at.y; -            if is_mouse_over { -                match event { -                    Event::Mouse(mouse::Event::WheelScrolled { delta }) => { -                        match delta { -                            mouse::ScrollDelta::Lines { y, .. } => { -                                // TODO: Configurable speed (?)                                  self.state.scroll( -                                    y * 60.0, +                                    delta,                                      bounds,                                      content_bounds,                                  ); -                            } -                            mouse::ScrollDelta::Pixels { y, .. } => { -                                self.state.scroll(y, bounds, content_bounds); -                            } -                        } -                        event_status = event::Status::Captured; -                    } -                    Event::Touch(event) => { -                        match event { -                            touch::Event::FingerPressed { .. } => {                                  self.state.scroll_box_touched_at =                                      Some(cursor_position);                              } -                            touch::Event::FingerMoved { .. } => { -                                if let Some(scroll_box_touched_at) = -                                    self.state.scroll_box_touched_at -                                { -                                    let delta = cursor_position.y -                                        - scroll_box_touched_at.y; - -                                    self.state.scroll( -                                        delta, -                                        bounds, -                                        content_bounds, -                                    ); - -                                    self.state.scroll_box_touched_at = -                                        Some(cursor_position); -                                } -                            } -                            touch::Event::FingerLifted { .. } -                            | touch::Event::FingerLost { .. } => { -                                self.state.scroll_box_touched_at = None; -                            }                          } - -                        event_status = event::Status::Captured; +                        touch::Event::FingerLifted { .. } +                        | touch::Event::FingerLost { .. } => { +                            self.state.scroll_box_touched_at = None; +                        }                      } -                    _ => {} + +                    return event::Status::Captured;                  } +                _ => {}              } +        } -            if self.state.is_scroller_grabbed() { -                match event { -                    Event::Mouse(mouse::Event::ButtonReleased( -                        mouse::Button::Left, -                    )) -                    | Event::Touch(touch::Event::FingerLifted { .. }) -                    | Event::Touch(touch::Event::FingerLost { .. }) => { -                        self.state.scroller_grabbed_at = None; +        if self.state.is_scroller_grabbed() { +            match event { +                Event::Mouse(mouse::Event::ButtonReleased( +                    mouse::Button::Left, +                )) +                | Event::Touch(touch::Event::FingerLifted { .. }) +                | Event::Touch(touch::Event::FingerLost { .. }) => { +                    self.state.scroller_grabbed_at = None; -                        event_status = event::Status::Captured; +                    return event::Status::Captured; +                } +                Event::Mouse(mouse::Event::CursorMoved { .. }) +                | Event::Touch(touch::Event::FingerMoved { .. }) => { +                    if let (Some(scrollbar), Some(scroller_grabbed_at)) = +                        (scrollbar, self.state.scroller_grabbed_at) +                    { +                        self.state.scroll_to( +                            scrollbar.scroll_percentage( +                                scroller_grabbed_at, +                                cursor_position, +                            ), +                            bounds, +                            content_bounds, +                        ); + +                        return event::Status::Captured;                      } -                    Event::Mouse(mouse::Event::CursorMoved { .. }) -                    | Event::Touch(touch::Event::FingerMoved { .. }) => { -                        if let (Some(scrollbar), Some(scroller_grabbed_at)) = -                            (scrollbar, self.state.scroller_grabbed_at) +                } +                _ => {} +            } +        } else if is_mouse_over_scrollbar { +            match event { +                Event::Mouse(mouse::Event::ButtonPressed( +                    mouse::Button::Left, +                )) +                | Event::Touch(touch::Event::FingerPressed { .. }) => { +                    if let Some(scrollbar) = scrollbar { +                        if let Some(scroller_grabbed_at) = +                            scrollbar.grab_scroller(cursor_position)                          {                              self.state.scroll_to(                                  scrollbar.scroll_percentage( @@ -322,71 +314,18 @@ where                                  content_bounds,                              ); -                            event_status = event::Status::Captured; -                        } -                    } -                    _ => {} -                } -            } else if is_mouse_over_scrollbar { -                match event { -                    Event::Mouse(mouse::Event::ButtonPressed( -                        mouse::Button::Left, -                    )) -                    | Event::Touch(touch::Event::FingerPressed { .. }) => { -                        if let Some(scrollbar) = scrollbar { -                            if let Some(scroller_grabbed_at) = -                                scrollbar.grab_scroller(cursor_position) -                            { -                                self.state.scroll_to( -                                    scrollbar.scroll_percentage( -                                        scroller_grabbed_at, -                                        cursor_position, -                                    ), -                                    bounds, -                                    content_bounds, -                                ); - -                                self.state.scroller_grabbed_at = -                                    Some(scroller_grabbed_at); +                            self.state.scroller_grabbed_at = +                                Some(scroller_grabbed_at); -                                event_status = event::Status::Captured; -                            } +                            return event::Status::Captured;                          }                      } -                    _ => {}                  } +                _ => {}              }          } -        if let event::Status::Captured = event_status { -            if self.snap_to_bottom { -                let new_offset = self.state.offset(bounds, content_bounds); - -                if new_offset < self.state.prev_offset { -                    self.state.snap_to_bottom = false; -                } else { -                    let scroll_perc = new_offset as f32 -                        / (content_bounds.height - bounds.height); - -                    if scroll_perc >= 1.0 - f32::EPSILON { -                        self.state.snap_to_bottom = true; -                    } -                } -            } - -            if let Some(on_scroll) = &self.on_scroll { -                messages.push(on_scroll( -                    self.state.offset(bounds, content_bounds) as f32 -                        / (content_bounds.height - bounds.height), -                    self.state.prev_offset as f32 -                        / (content_bounds.height - bounds.height), -                )); -            } - -            event::Status::Captured -        } else { -            event::Status::Ignored -        } +        event::Status::Ignored      }      fn draw( @@ -400,15 +339,6 @@ where          let bounds = layout.bounds();          let content_layout = layout.children().next().unwrap();          let content_bounds = content_layout.bounds(); - -        if self.state.snap_to_bottom { -            self.state.scroll_to(1.0, bounds, content_bounds); -        } - -        if let Some(scroll_to) = self.state.scroll_to.borrow_mut().take() { -            self.state.scroll_to(scroll_to, bounds, content_bounds); -        } -          let offset = self.state.offset(bounds, content_bounds);          let scrollbar = renderer.scrollbar(              bounds, @@ -488,14 +418,44 @@ where  }  /// The local state of a [`Scrollable`]. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Copy)]  pub struct State {      scroller_grabbed_at: Option<f32>,      scroll_box_touched_at: Option<Point>, -    prev_offset: u32, -    snap_to_bottom: bool, -    offset: RefCell<f32>, -    scroll_to: RefCell<Option<f32>>, +    offset: Offset, +} + +impl Default for State { +    fn default() -> Self { +        Self { +            scroller_grabbed_at: None, +            scroll_box_touched_at: None, +            offset: Offset::Absolute(0.0), +        } +    } +} + +/// The local state of a [`Scrollable`]. +#[derive(Debug, Clone, Copy)] +enum Offset { +    Absolute(f32), +    Relative(f32), +} + +impl Offset { +    fn absolute(self, bounds: Rectangle, content_bounds: Rectangle) -> f32 { +        match self { +            Self::Absolute(absolute) => { +                let hidden_content = +                    (content_bounds.height - bounds.height).max(0.0); + +                absolute.min(hidden_content) +            } +            Self::Relative(percentage) => { +                ((content_bounds.height - bounds.height) * percentage).max(0.0) +            } +        } +    }  }  impl State { @@ -516,51 +476,46 @@ impl State {              return;          } -        let offset_val = *self.offset.borrow(); -        *self.offset.borrow_mut() = (offset_val - delta_y) -            .max(0.0) -            .min((content_bounds.height - bounds.height) as f32); +        self.offset = Offset::Absolute( +            (self.offset.absolute(bounds, content_bounds) - delta_y) +                .max(0.0) +                .min((content_bounds.height - bounds.height) as f32), +        );      } -    /// Moves the scroll position to a relative amount, given the bounds of -    /// the [`Scrollable`] and its contents. +    /// Scrolls the [`Scrollable`] to a relative amount.      ///      /// `0` represents scrollbar at the top, while `1` represents scrollbar at      /// the bottom.      pub fn scroll_to( -        &self, +        &mut self,          percentage: f32,          bounds: Rectangle,          content_bounds: Rectangle,      ) { -        *self.offset.borrow_mut() = -            ((content_bounds.height - bounds.height) * percentage).max(0.0); +        self.snap_to(percentage); +        self.unsnap(bounds, content_bounds);      } -    /// Marks the scrollable to scroll to `perc` percentage (between 0.0 and 1.0) -    /// in the next `draw` call. +    /// Snaps the scroll position to a relative amount.      /// -    /// [`Scrollable`]: struct.Scrollable.html -    /// [`State`]: struct.State.html -    pub fn scroll_to_percentage(&mut self, perc: f32) { -        *self.scroll_to.borrow_mut() = Some(perc.max(0.0).min(1.0)); +    /// `0` represents scrollbar at the top, while `1` represents scrollbar at +    /// the bottom. +    pub fn snap_to(&mut self, percentage: f32) { +        self.offset = Offset::Relative(percentage.max(0.0).min(1.0));      } -    /// Marks the scrollable to scroll to bottom in the next `draw` call. -    /// -    /// [`Scrollable`]: struct.Scrollable.html -    /// [`State`]: struct.State.html -    pub fn scroll_to_bottom(&mut self) { -        self.scroll_to_percentage(1.0); +    /// Unsnaps the current scroll position, if snapped, given the bounds of the +    /// [`Scrollable`] and its contents. +    pub fn unsnap(&mut self, bounds: Rectangle, content_bounds: Rectangle) { +        self.offset = +            Offset::Absolute(self.offset.absolute(bounds, content_bounds));      }      /// Returns the current scrolling offset of the [`State`], given the bounds      /// of the [`Scrollable`] and its contents.      pub fn offset(&self, bounds: Rectangle, content_bounds: Rectangle) -> u32 { -        let hidden_content = -            (content_bounds.height - bounds.height).max(0.0).round() as u32; - -        self.offset.borrow().min(hidden_content as f32) as u32 +        self.offset.absolute(bounds, content_bounds) as u32      }      /// Returns whether the scroller is currently grabbed or not.  | 
