summaryrefslogtreecommitdiffstats
path: root/native/src/widget/scrollable.rs
diff options
context:
space:
mode:
Diffstat (limited to 'native/src/widget/scrollable.rs')
-rw-r--r--native/src/widget/scrollable.rs215
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(