diff options
Diffstat (limited to 'native/src/widget/scrollable.rs')
| -rw-r--r-- | native/src/widget/scrollable.rs | 215 | 
1 files changed, 156 insertions, 59 deletions
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 678d837a..e83f25af 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -2,8 +2,8 @@  use crate::{      column,      input::{mouse, ButtonState}, -    layout, Align, Column, Element, Event, Hasher, Layout, Length, Point, -    Rectangle, Size, Widget, +    layout, Align, Clipboard, Column, Element, Event, Hasher, Layout, Length, +    Point, Rectangle, Size, Widget,  };  use std::{f32, hash::Hash, u32}; @@ -11,14 +11,15 @@ use std::{f32, hash::Hash, u32};  /// A widget that can vertically display an infinite amount of content with a  /// scrollbar.  #[allow(missing_debug_implementations)] -pub struct Scrollable<'a, Message, Renderer> { +pub struct Scrollable<'a, Message, Renderer: self::Renderer> {      state: &'a mut State,      height: Length,      max_height: u32,      content: Column<'a, Message, Renderer>, +    style: Renderer::Style,  } -impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> { +impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {      /// Creates a new [`Scrollable`] with the given [`State`].      ///      /// [`Scrollable`]: struct.Scrollable.html @@ -29,6 +30,7 @@ impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> {              height: Length::Shrink,              max_height: u32::MAX,              content: Column::new(), +            style: Renderer::Style::default(),          }      } @@ -90,6 +92,14 @@ impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> {          self      } +    /// Sets the style of the [`Scrollable`] . +    /// +    /// [`Scrollable`]: struct.Scrollable.html +    pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self { +        self.style = style.into(); +        self +    } +      /// Adds an element to the [`Scrollable`].      ///      /// [`Scrollable`]: struct.Scrollable.html @@ -105,7 +115,7 @@ impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> {  impl<'a, Message, Renderer> Widget<Message, Renderer>      for Scrollable<'a, Message, Renderer>  where -    Renderer: self::Renderer + column::Renderer, +    Renderer: 'static + self::Renderer + column::Renderer,  {      fn width(&self) -> Length {          Length::Fill @@ -143,6 +153,7 @@ where          cursor_position: Point,          messages: &mut Vec<Message>,          renderer: &Renderer, +        clipboard: Option<&dyn Clipboard>,      ) {          let bounds = layout.bounds();          let is_mouse_over = bounds.contains(cursor_position); @@ -150,12 +161,6 @@ where          let content = layout.children().next().unwrap();          let content_bounds = content.bounds(); -        let is_mouse_over_scrollbar = renderer.is_mouse_over_scrollbar( -            bounds, -            content_bounds, -            cursor_position, -        ); -          // TODO: Event capture. Nested scrollables should capture scroll events.          if is_mouse_over {              match event { @@ -174,49 +179,66 @@ where              }          } -        if self.state.is_scrollbar_grabbed() || is_mouse_over_scrollbar { +        let offset = self.state.offset(bounds, content_bounds); +        let scrollbar = renderer.scrollbar(bounds, content_bounds, offset); +        let is_mouse_over_scrollbar = scrollbar +            .as_ref() +            .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) +            .unwrap_or(false); + +        if self.state.is_scroller_grabbed() {              match event {                  Event::Mouse(mouse::Event::Input {                      button: mouse::Button::Left, -                    state, -                }) => match state { -                    ButtonState::Pressed => { -                        self.state.scroll_to( -                            cursor_position.y / (bounds.y + bounds.height), -                            bounds, -                            content_bounds, -                        ); - -                        self.state.scrollbar_grabbed_at = Some(cursor_position); -                    } -                    ButtonState::Released => { -                        self.state.scrollbar_grabbed_at = None; -                    } -                }, +                    state: ButtonState::Released, +                }) => { +                    self.state.scroller_grabbed_at = None; +                }                  Event::Mouse(mouse::Event::CursorMoved { .. }) => { -                    if let Some(scrollbar_grabbed_at) = -                        self.state.scrollbar_grabbed_at +                    if let (Some(scrollbar), Some(scroller_grabbed_at)) = +                        (scrollbar, self.state.scroller_grabbed_at)                      { -                        let ratio = content_bounds.height / bounds.height; -                        let delta = scrollbar_grabbed_at.y - cursor_position.y; - -                        self.state.scroll( -                            delta * ratio, +                        self.state.scroll_to( +                            scrollbar.scroll_percentage( +                                scroller_grabbed_at, +                                cursor_position, +                            ),                              bounds,                              content_bounds,                          ); - -                        self.state.scrollbar_grabbed_at = Some(cursor_position); +                    } +                } +                _ => {} +            } +        } else if is_mouse_over_scrollbar { +            match event { +                Event::Mouse(mouse::Event::Input { +                    button: mouse::Button::Left, +                    state: ButtonState::Pressed, +                }) => { +                    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); +                        }                      }                  }                  _ => {}              }          } -        let cursor_position = if is_mouse_over -            && !(is_mouse_over_scrollbar -                || self.state.scrollbar_grabbed_at.is_some()) -        { +        let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar {              Point::new(                  cursor_position.x,                  cursor_position.y @@ -236,12 +258,14 @@ where              cursor_position,              messages,              renderer, +            clipboard,          )      }      fn draw(          &self,          renderer: &mut Renderer, +        defaults: &Renderer::Defaults,          layout: Layout<'_>,          cursor_position: Point,      ) -> Renderer::Output { @@ -249,13 +273,13 @@ where          let content_layout = layout.children().next().unwrap();          let content_bounds = content_layout.bounds();          let offset = self.state.offset(bounds, content_bounds); +        let scrollbar = renderer.scrollbar(bounds, content_bounds, offset);          let is_mouse_over = bounds.contains(cursor_position); -        let is_mouse_over_scrollbar = renderer.is_mouse_over_scrollbar( -            bounds, -            content_bounds, -            cursor_position, -        ); +        let is_mouse_over_scrollbar = scrollbar +            .as_ref() +            .map(|scrollbar| scrollbar.is_mouse_over(cursor_position)) +            .unwrap_or(false);          let content = {              let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { @@ -264,7 +288,12 @@ where                  Point::new(cursor_position.x, -1.0)              }; -            self.content.draw(renderer, content_layout, cursor_position) +            self.content.draw( +                renderer, +                defaults, +                content_layout, +                cursor_position, +            )          };          self::Renderer::draw( @@ -274,13 +303,15 @@ where              content_layout.bounds(),              is_mouse_over,              is_mouse_over_scrollbar, +            scrollbar,              offset, +            &self.style,              content,          )      }      fn hash_layout(&self, state: &mut Hasher) { -        std::any::TypeId::of::<Scrollable<'static, (), ()>>().hash(state); +        std::any::TypeId::of::<Scrollable<'static, (), Renderer>>().hash(state);          self.height.hash(state);          self.max_height.hash(state); @@ -294,7 +325,7 @@ where  /// [`Scrollable`]: struct.Scrollable.html  #[derive(Debug, Clone, Copy, Default)]  pub struct State { -    scrollbar_grabbed_at: Option<Point>, +    scroller_grabbed_at: Option<f32>,      offset: f32,  } @@ -356,12 +387,69 @@ impl State {          self.offset.min(hidden_content as f32) as u32      } -    /// Returns whether the scrollbar is currently grabbed or not. -    pub fn is_scrollbar_grabbed(&self) -> bool { -        self.scrollbar_grabbed_at.is_some() +    /// Returns whether the scroller is currently grabbed or not. +    pub fn is_scroller_grabbed(&self) -> bool { +        self.scroller_grabbed_at.is_some() +    } +} + +/// The scrollbar of a [`Scrollable`]. +/// +/// [`Scrollable`]: struct.Scrollable.html +#[derive(Debug)] +pub struct Scrollbar { +    /// The bounds of the [`Scrollbar`]. +    /// +    /// [`Scrollbar`]: struct.Scrollbar.html +    pub bounds: Rectangle, + +    /// The bounds of the [`Scroller`]. +    /// +    /// [`Scroller`]: struct.Scroller.html +    pub scroller: Scroller, +} + +impl Scrollbar { +    fn is_mouse_over(&self, cursor_position: Point) -> bool { +        self.bounds.contains(cursor_position) +    } + +    fn grab_scroller(&self, cursor_position: Point) -> Option<f32> { +        if self.bounds.contains(cursor_position) { +            Some(if self.scroller.bounds.contains(cursor_position) { +                (cursor_position.y - self.scroller.bounds.y) +                    / self.scroller.bounds.height +            } else { +                0.5 +            }) +        } else { +            None +        } +    } + +    fn scroll_percentage( +        &self, +        grabbed_at: f32, +        cursor_position: Point, +    ) -> f32 { +        (cursor_position.y +            - self.bounds.y +            - self.scroller.bounds.height * grabbed_at) +            / (self.bounds.height - self.scroller.bounds.height)      }  } +/// The handle of a [`Scrollbar`]. +/// +/// [`Scrollbar`]: struct.Scrollbar.html +#[derive(Debug, Clone, Copy)] +pub struct Scroller { +    /// The bounds of the [`Scroller`]. +    /// +    /// [`Scroller`]: struct.Scrollbar.html +    pub bounds: Rectangle, +} +  /// The renderer of a [`Scrollable`].  ///  /// Your [renderer] will need to implement this trait before being @@ -370,27 +458,34 @@ impl State {  /// [`Scrollable`]: struct.Scrollable.html  /// [renderer]: ../../renderer/index.html  pub trait Renderer: crate::Renderer + Sized { -    /// Returns whether the mouse is over the scrollbar given the bounds of -    /// the [`Scrollable`] and its contents. +    /// The style supported by this renderer. +    type Style: Default; + +    /// Returns the [`Scrollbar`] given the bounds and content bounds of a +    /// [`Scrollable`].      /// +    /// [`Scrollbar`]: struct.Scrollbar.html      /// [`Scrollable`]: struct.Scrollable.html -    fn is_mouse_over_scrollbar( +    fn scrollbar(          &self,          bounds: Rectangle,          content_bounds: Rectangle, -        cursor_position: Point, -    ) -> bool; +        offset: u32, +    ) -> Option<Scrollbar>;      /// Draws the [`Scrollable`].      ///      /// It receives:      /// - the [`State`] of the [`Scrollable`] -    /// - the bounds of the [`Scrollable`] +    /// - the bounds of the [`Scrollable`] widget +    /// - the bounds of the [`Scrollable`] content      /// - whether the mouse is over the [`Scrollable`] or not -    /// - whether the mouse is over the scrollbar or not +    /// - whether the mouse is over the [`Scrollbar`] or not +    /// - a optional [`Scrollbar`] to be rendered      /// - the scrolling offset      /// - the drawn content      /// +    /// [`Scrollbar`]: struct.Scrollbar.html      /// [`Scrollable`]: struct.Scrollable.html      /// [`State`]: struct.State.html      fn draw( @@ -400,7 +495,9 @@ pub trait Renderer: crate::Renderer + Sized {          content_bounds: Rectangle,          is_mouse_over: bool,          is_mouse_over_scrollbar: bool, +        scrollbar: Option<Scrollbar>,          offset: u32, +        style: &Self::Style,          content: Self::Output,      ) -> Self::Output;  } @@ -408,7 +505,7 @@ pub trait Renderer: crate::Renderer + Sized {  impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>      for Element<'a, Message, Renderer>  where -    Renderer: 'a + self::Renderer + column::Renderer, +    Renderer: 'static + self::Renderer + column::Renderer,      Message: 'static,  {      fn from(  | 
