//! Navigate an endless amount of content with a scrollbar. use crate::overlay; use crate::widget::tree::{self, Tree}; use crate::{Element, Widget}; use iced_native::event::{self, Event}; use iced_native::layout::{self, Layout}; use iced_native::mouse; use iced_native::renderer; use iced_native::widget::scrollable; use iced_native::{Clipboard, Length, Point, Rectangle, Shell, Vector}; pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; /// A widget that can vertically display an infinite amount of content with a /// scrollbar. #[allow(missing_debug_implementations)] pub struct Scrollable<'a, Message, Renderer> { height: Length, scrollbar_width: u16, scrollbar_margin: u16, scroller_width: u16, on_scroll: Option Message + 'a>>, style_sheet: Box, content: Element<'a, Message, Renderer>, } impl<'a, Message, Renderer: iced_native::Renderer> Scrollable<'a, Message, Renderer> { /// Creates a new [`Scrollable`]. pub fn new(content: impl Into>) -> Self { Scrollable { height: Length::Shrink, scrollbar_width: 10, scrollbar_margin: 0, scroller_width: 10, on_scroll: None, style_sheet: Default::default(), content: content.into(), } } /// Sets the height of the [`Scrollable`]. pub fn height(mut self, height: Length) -> Self { self.height = height; self } /// Sets the scrollbar width of the [`Scrollable`] . /// Silently enforces a minimum value of 1. pub fn scrollbar_width(mut self, scrollbar_width: u16) -> Self { self.scrollbar_width = scrollbar_width.max(1); self } /// Sets the scrollbar margin of the [`Scrollable`] . pub fn scrollbar_margin(mut self, scrollbar_margin: u16) -> Self { self.scrollbar_margin = scrollbar_margin; self } /// Sets the scroller width of the [`Scrollable`] . /// /// It silently enforces a minimum value of 1. pub fn scroller_width(mut self, scroller_width: u16) -> Self { self.scroller_width = scroller_width.max(1); self } /// Sets a function to call when the [`Scrollable`] is scrolled. /// /// The function takes the new relative offset of the [`Scrollable`] /// (e.g. `0` means top, while `1` means bottom). pub fn on_scroll(mut self, f: impl Fn(f32) -> Message + 'a) -> Self { self.on_scroll = Some(Box::new(f)); self } /// Sets the style of the [`Scrollable`] . pub fn style( mut self, style_sheet: impl Into>, ) -> Self { self.style_sheet = style_sheet.into(); self } } impl<'a, Message, Renderer> Widget for Scrollable<'a, Message, Renderer> where Renderer: iced_native::Renderer, { fn tag(&self) -> tree::Tag { tree::Tag::of::() } fn state(&self) -> tree::State { tree::State::new(scrollable::State::new()) } fn children(&self) -> Vec { vec![Tree::new(&self.content)] } fn diff(&self, tree: &mut Tree) { tree.diff_children(std::slice::from_ref(&self.content)) } fn width(&self) -> Length { self.content.as_widget().width() } fn height(&self) -> Length { self.height } fn layout( &self, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { scrollable::layout( renderer, limits, Widget::::width(self), self.height, |renderer, limits| { self.content.as_widget().layout(renderer, limits) }, ) } fn on_event( &mut self, tree: &mut Tree, event: Event, layout: Layout<'_>, cursor_position: Point, renderer: &Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { scrollable::update( tree.state.downcast_mut::(), event, layout, cursor_position, clipboard, shell, self.scrollbar_width, self.scrollbar_margin, self.scroller_width, &self.on_scroll, |event, layout, cursor_position, clipboard, shell| { self.content.as_widget_mut().on_event( &mut tree.children[0], event, layout, cursor_position, renderer, clipboard, shell, ) }, ) } fn draw( &self, tree: &Tree, renderer: &mut Renderer, style: &renderer::Style, layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, ) { scrollable::draw( tree.state.downcast_ref::(), renderer, layout, cursor_position, self.scrollbar_width, self.scrollbar_margin, self.scroller_width, self.style_sheet.as_ref(), |renderer, layout, cursor_position, viewport| { self.content.as_widget().draw( &tree.children[0], renderer, style, layout, cursor_position, viewport, ) }, ) } fn mouse_interaction( &self, tree: &Tree, layout: Layout<'_>, cursor_position: Point, _viewport: &Rectangle, renderer: &Renderer, ) -> mouse::Interaction { scrollable::mouse_interaction( tree.state.downcast_ref::(), layout, cursor_position, self.scrollbar_width, self.scrollbar_margin, self.scroller_width, |layout, cursor_position, viewport| { self.content.as_widget().mouse_interaction( &tree.children[0], layout, cursor_position, viewport, renderer, ) }, ) } fn overlay<'b>( &'b self, tree: &'b mut Tree, layout: Layout<'_>, renderer: &Renderer, ) -> Option> { self.content .as_widget() .overlay( &mut tree.children[0], layout.children().next().unwrap(), renderer, ) .map(|overlay| { let bounds = layout.bounds(); let content_layout = layout.children().next().unwrap(); let content_bounds = content_layout.bounds(); let offset = tree .state .downcast_ref::() .offset(bounds, content_bounds); overlay.translate(Vector::new(0.0, -(offset as f32))) }) } } impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where Message: 'a + Clone, Renderer: 'a + iced_native::Renderer, { fn from( text_input: Scrollable<'a, Message, Renderer>, ) -> Element<'a, Message, Renderer> { Element::new(text_input) } }