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.rs217
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;
}