iced_pure/widget/
scrollable.rs

1//! Navigate an endless amount of content with a scrollbar.
2use crate::overlay;
3use crate::widget::tree::{self, Tree};
4use crate::{Element, Widget};
5
6use iced_native::event::{self, Event};
7use iced_native::layout::{self, Layout};
8use iced_native::mouse;
9use iced_native::renderer;
10use iced_native::widget::scrollable;
11use iced_native::{Clipboard, Length, Point, Rectangle, Shell, Vector};
12
13pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet};
14
15/// A widget that can vertically display an infinite amount of content with a
16/// scrollbar.
17#[allow(missing_debug_implementations)]
18pub struct Scrollable<'a, Message, Renderer> {
19    height: Length,
20    scrollbar_width: u16,
21    scrollbar_margin: u16,
22    scroller_width: u16,
23    on_scroll: Option<Box<dyn Fn(f32) -> Message + 'a>>,
24    style_sheet: Box<dyn StyleSheet + 'a>,
25    content: Element<'a, Message, Renderer>,
26}
27
28impl<'a, Message, Renderer: iced_native::Renderer>
29    Scrollable<'a, Message, Renderer>
30{
31    /// Creates a new [`Scrollable`].
32    pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
33        Scrollable {
34            height: Length::Shrink,
35            scrollbar_width: 10,
36            scrollbar_margin: 0,
37            scroller_width: 10,
38            on_scroll: None,
39            style_sheet: Default::default(),
40            content: content.into(),
41        }
42    }
43
44    /// Sets the height of the [`Scrollable`].
45    pub fn height(mut self, height: Length) -> Self {
46        self.height = height;
47        self
48    }
49
50    /// Sets the scrollbar width of the [`Scrollable`] .
51    /// Silently enforces a minimum value of 1.
52    pub fn scrollbar_width(mut self, scrollbar_width: u16) -> Self {
53        self.scrollbar_width = scrollbar_width.max(1);
54        self
55    }
56
57    /// Sets the scrollbar margin of the [`Scrollable`] .
58    pub fn scrollbar_margin(mut self, scrollbar_margin: u16) -> Self {
59        self.scrollbar_margin = scrollbar_margin;
60        self
61    }
62
63    /// Sets the scroller width of the [`Scrollable`] .
64    ///
65    /// It silently enforces a minimum value of 1.
66    pub fn scroller_width(mut self, scroller_width: u16) -> Self {
67        self.scroller_width = scroller_width.max(1);
68        self
69    }
70
71    /// Sets a function to call when the [`Scrollable`] is scrolled.
72    ///
73    /// The function takes the new relative offset of the [`Scrollable`]
74    /// (e.g. `0` means top, while `1` means bottom).
75    pub fn on_scroll(mut self, f: impl Fn(f32) -> Message + 'a) -> Self {
76        self.on_scroll = Some(Box::new(f));
77        self
78    }
79
80    /// Sets the style of the [`Scrollable`] .
81    pub fn style(
82        mut self,
83        style_sheet: impl Into<Box<dyn StyleSheet + 'a>>,
84    ) -> Self {
85        self.style_sheet = style_sheet.into();
86        self
87    }
88}
89
90impl<'a, Message, Renderer> Widget<Message, Renderer>
91    for Scrollable<'a, Message, Renderer>
92where
93    Renderer: iced_native::Renderer,
94{
95    fn tag(&self) -> tree::Tag {
96        tree::Tag::of::<scrollable::State>()
97    }
98
99    fn state(&self) -> tree::State {
100        tree::State::new(scrollable::State::new())
101    }
102
103    fn children(&self) -> Vec<Tree> {
104        vec![Tree::new(&self.content)]
105    }
106
107    fn diff(&self, tree: &mut Tree) {
108        tree.diff_children(std::slice::from_ref(&self.content))
109    }
110
111    fn width(&self) -> Length {
112        self.content.as_widget().width()
113    }
114
115    fn height(&self) -> Length {
116        self.height
117    }
118
119    fn layout(
120        &self,
121        renderer: &Renderer,
122        limits: &layout::Limits,
123    ) -> layout::Node {
124        scrollable::layout(
125            renderer,
126            limits,
127            Widget::<Message, Renderer>::width(self),
128            self.height,
129            |renderer, limits| {
130                self.content.as_widget().layout(renderer, limits)
131            },
132        )
133    }
134
135    fn on_event(
136        &mut self,
137        tree: &mut Tree,
138        event: Event,
139        layout: Layout<'_>,
140        cursor_position: Point,
141        renderer: &Renderer,
142        clipboard: &mut dyn Clipboard,
143        shell: &mut Shell<'_, Message>,
144    ) -> event::Status {
145        scrollable::update(
146            tree.state.downcast_mut::<scrollable::State>(),
147            event,
148            layout,
149            cursor_position,
150            clipboard,
151            shell,
152            self.scrollbar_width,
153            self.scrollbar_margin,
154            self.scroller_width,
155            &self.on_scroll,
156            |event, layout, cursor_position, clipboard, shell| {
157                self.content.as_widget_mut().on_event(
158                    &mut tree.children[0],
159                    event,
160                    layout,
161                    cursor_position,
162                    renderer,
163                    clipboard,
164                    shell,
165                )
166            },
167        )
168    }
169
170    fn draw(
171        &self,
172        tree: &Tree,
173        renderer: &mut Renderer,
174        style: &renderer::Style,
175        layout: Layout<'_>,
176        cursor_position: Point,
177        _viewport: &Rectangle,
178    ) {
179        scrollable::draw(
180            tree.state.downcast_ref::<scrollable::State>(),
181            renderer,
182            layout,
183            cursor_position,
184            self.scrollbar_width,
185            self.scrollbar_margin,
186            self.scroller_width,
187            self.style_sheet.as_ref(),
188            |renderer, layout, cursor_position, viewport| {
189                self.content.as_widget().draw(
190                    &tree.children[0],
191                    renderer,
192                    style,
193                    layout,
194                    cursor_position,
195                    viewport,
196                )
197            },
198        )
199    }
200
201    fn mouse_interaction(
202        &self,
203        tree: &Tree,
204        layout: Layout<'_>,
205        cursor_position: Point,
206        _viewport: &Rectangle,
207        renderer: &Renderer,
208    ) -> mouse::Interaction {
209        scrollable::mouse_interaction(
210            tree.state.downcast_ref::<scrollable::State>(),
211            layout,
212            cursor_position,
213            self.scrollbar_width,
214            self.scrollbar_margin,
215            self.scroller_width,
216            |layout, cursor_position, viewport| {
217                self.content.as_widget().mouse_interaction(
218                    &tree.children[0],
219                    layout,
220                    cursor_position,
221                    viewport,
222                    renderer,
223                )
224            },
225        )
226    }
227
228    fn overlay<'b>(
229        &'b self,
230        tree: &'b mut Tree,
231        layout: Layout<'_>,
232        renderer: &Renderer,
233    ) -> Option<overlay::Element<'b, Message, Renderer>> {
234        self.content
235            .as_widget()
236            .overlay(
237                &mut tree.children[0],
238                layout.children().next().unwrap(),
239                renderer,
240            )
241            .map(|overlay| {
242                let bounds = layout.bounds();
243                let content_layout = layout.children().next().unwrap();
244                let content_bounds = content_layout.bounds();
245                let offset = tree
246                    .state
247                    .downcast_ref::<scrollable::State>()
248                    .offset(bounds, content_bounds);
249
250                overlay.translate(Vector::new(0.0, -(offset as f32)))
251            })
252    }
253}
254
255impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
256    for Element<'a, Message, Renderer>
257where
258    Message: 'a + Clone,
259    Renderer: 'a + iced_native::Renderer,
260{
261    fn from(
262        text_input: Scrollable<'a, Message, Renderer>,
263    ) -> Element<'a, Message, Renderer> {
264        Element::new(text_input)
265    }
266}