diff options
Diffstat (limited to 'native/src/widget/scrollable.rs')
-rw-r--r-- | native/src/widget/scrollable.rs | 217 |
1 files changed, 200 insertions, 17 deletions
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 091dac47..d4568412 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -1,20 +1,104 @@ use crate::{ column, input::{mouse, ButtonState}, - layout, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, - Widget, + layout, Align, Column, Element, Event, Hasher, Layout, Length, Point, + Rectangle, Size, Widget, }; -pub use iced_core::scrollable::State; +use std::{f32, hash::Hash, u32}; -use std::f32; -use std::hash::Hash; +/// A widget that can vertically display an infinite amount of content with a +/// scrollbar. +pub struct Scrollable<'a, Message, Renderer> { + state: &'a mut State, + height: Length, + max_height: u32, + content: Column<'a, Message, Renderer>, +} -/// A scrollable [`Column`]. -/// -/// [`Column`]: ../column/struct.Column.html -pub type Scrollable<'a, Message, Renderer> = - iced_core::Scrollable<'a, Element<'a, Message, Renderer>>; +impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> { + /// Creates a new [`Scrollable`] with the given [`State`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + /// [`State`]: struct.State.html + pub fn new(state: &'a mut State) -> Self { + Scrollable { + state, + height: Length::Shrink, + max_height: u32::MAX, + content: Column::new(), + } + } + + /// Sets the vertical spacing _between_ elements. + /// + /// Custom margins per element do not exist in Iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, units: u16) -> Self { + self.content = self.content.spacing(units); + self + } + + /// Sets the padding of the [`Scrollable`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn padding(mut self, units: u16) -> Self { + self.content = self.content.padding(units); + self + } + + /// Sets the width of the [`Scrollable`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn width(mut self, width: Length) -> Self { + self.content = self.content.width(width); + self + } + + /// Sets the height of the [`Scrollable`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } + + /// Sets the maximum width of the [`Scrollable`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn max_width(mut self, max_width: u32) -> Self { + self.content = self.content.max_width(max_width); + self + } + + /// Sets the maximum height of the [`Scrollable`] in pixels. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn max_height(mut self, max_height: u32) -> Self { + self.max_height = max_height; + self + } + + /// Sets the horizontal alignment of the contents of the [`Scrollable`] . + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn align_items(mut self, align_items: Align) -> Self { + self.content = self.content.align_items(align_items); + self + } + + /// Adds an element to the [`Scrollable`]. + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn push<E>(mut self, child: E) -> Self + where + E: Into<Element<'a, Message, Renderer>>, + { + self.content = self.content.push(child); + self + } +} impl<'a, Message, Renderer> Widget<Message, Renderer> for Scrollable<'a, Message, Renderer> @@ -153,13 +237,35 @@ where ) -> Renderer::Output { let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); + let content_bounds = content_layout.bounds(); + let offset = self.state.offset(bounds, content_bounds); + + let is_mouse_over = bounds.contains(cursor_position); + let is_mouse_over_scrollbar = renderer.is_mouse_over_scrollbar( + bounds, + content_bounds, + cursor_position, + ); + + let content = { + let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { + Point::new(cursor_position.x, cursor_position.y + offset as f32) + } else { + Point::new(cursor_position.x, -1.0) + }; + + self.content.draw(renderer, content_layout, cursor_position) + }; self::Renderer::draw( renderer, - &self, + &self.state, bounds, - content_layout, - cursor_position, + content_layout.bounds(), + is_mouse_over, + is_mouse_over_scrollbar, + offset, + content, ) } @@ -173,6 +279,80 @@ where } } +/// The local state of a [`Scrollable`]. +/// +/// [`Scrollable`]: struct.Scrollable.html +#[derive(Debug, Clone, Copy, Default)] +pub struct State { + scrollbar_grabbed_at: Option<Point>, + offset: u32, +} + +impl State { + /// Creates a new [`State`] with the scrollbar located at the top. + /// + /// [`State`]: struct.State.html + pub fn new() -> Self { + State::default() + } + + /// Apply a scrolling offset to the current [`State`], given the bounds of + /// the [`Scrollable`] and its contents. + /// + /// [`Scrollable`]: struct.Scrollable.html + /// [`State`]: struct.State.html + pub fn scroll( + &mut self, + delta_y: f32, + bounds: Rectangle, + content_bounds: Rectangle, + ) { + if bounds.height >= content_bounds.height { + return; + } + + self.offset = (self.offset as i32 - delta_y.round() as i32) + .max(0) + .min((content_bounds.height - bounds.height) as i32) + as u32; + } + + /// Moves the scroll position to a relative amount, given the bounds of + /// the [`Scrollable`] and its contents. + /// + /// `0` represents scrollbar at the top, while `1` represents scrollbar at + /// the bottom. + /// + /// [`Scrollable`]: struct.Scrollable.html + /// [`State`]: struct.State.html + pub fn scroll_to( + &mut self, + percentage: f32, + bounds: Rectangle, + content_bounds: Rectangle, + ) { + self.offset = ((content_bounds.height - bounds.height) * percentage) + .max(0.0) as u32; + } + + /// Returns the current scrolling offset of the [`State`], given the bounds + /// of the [`Scrollable`] and its contents. + /// + /// [`Scrollable`]: struct.Scrollable.html + /// [`State`]: struct.State.html + 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.min(hidden_content) + } + + /// Returns whether the scrollbar is currently grabbed or not. + pub fn is_scrollbar_grabbed(&self) -> bool { + self.scrollbar_grabbed_at.is_some() + } +} + pub trait Renderer: crate::Renderer + Sized { fn is_mouse_over_scrollbar( &self, @@ -181,12 +361,15 @@ pub trait Renderer: crate::Renderer + Sized { cursor_position: Point, ) -> bool; - fn draw<Message>( + fn draw( &mut self, - scrollable: &Scrollable<'_, Message, Self>, + scrollable: &State, bounds: Rectangle, - content_layout: Layout<'_>, - cursor_position: Point, + content_bounds: Rectangle, + is_mouse_over: bool, + is_mouse_over_scrollbar: bool, + offset: u32, + content: Self::Output, ) -> Self::Output; } |