Skip to main content

repose_ui/
scroll.rs

1//! # Scroll model
2//!
3//! Repose separates visual scroll containers from scroll state.
4//!
5//! This file implements scroll composables (wrappers that attach scroll bindings
6//! as modifiers). The actual scroll state types live in `repose_core::scroll`.
7//!
8//! Velocities are expressed in px/sec and integrated with dt,
9//! so behavior is frame-rate independent.
10
11pub use repose_core::scroll::{HorizontalScrollState, ScrollPhysics, ScrollState, ScrollStateXY};
12use crate::ViewExt;
13use repose_core::*;
14use std::rc::Rc;
15
16/// Remembered ScrollState (requires unique key).
17pub fn remember_scroll_state(key: impl Into<String>) -> Rc<ScrollState> {
18    repose_core::remember_with_key(key.into(), ScrollState::new)
19}
20
21pub fn remember_horizontal_scroll_state(key: impl Into<String>) -> Rc<HorizontalScrollState> {
22    repose_core::remember_with_key(key.into(), HorizontalScrollState::new)
23}
24
25pub fn remember_scroll_state_xy(key: impl Into<String>) -> Rc<ScrollStateXY> {
26    repose_core::remember_with_key(key.into(), ScrollStateXY::new)
27}
28
29/// Scroll container with inertia, like verticalScroll.
30///
31/// Content is wrapped so it:
32/// - does not flex-shrink,
33/// - is at least as wide/tall as the viewport,
34/// - grows with content on the scroll axis.
35///
36/// Prefer a stable `remember_scroll_state(key)` and put `scope!` / `repaint_boundary`
37/// around expensive list bodies.
38pub fn ScrollArea(modifier: Modifier, state: Rc<ScrollState>, content: View) -> View {
39    let binding = state.to_binding();
40    let content = crate::Box(
41        Modifier::new()
42            .flex_shrink(0.0)
43            // Fill viewport width so short content still spans the scroller;
44            // height stays auto so content grows vertically.
45            .fill_max_width(),
46    )
47    .child(content);
48
49    View::new(0, ViewKind::Box)
50        .modifier(modifier.vertical_scroll(match binding {
51            ScrollBinding::Vertical(b) => b,
52            _ => unreachable!(),
53        }))
54        .with_children(vec![content])
55}
56
57/// Horizontal scroll container with inertia.
58///
59/// Content is wrapped so it does not flex-shrink, is at least as tall as the
60/// viewport, and grows with content horizontally.
61pub fn HorizontalScrollArea(
62    modifier: Modifier,
63    state: Rc<HorizontalScrollState>,
64    content: View,
65) -> View {
66    let binding = state.to_binding();
67    let content = crate::Box(
68        Modifier::new()
69            .flex_shrink(0.0)
70            // Fill viewport height so short content still spans the scroller;
71            // width stays auto so content grows horizontally.
72            .fill_max_height(),
73    )
74    .child(content);
75
76    View::new(0, ViewKind::Box)
77        .modifier(modifier.horizontal_scroll(match binding {
78            ScrollBinding::Horizontal(b) => b,
79            _ => unreachable!(),
80        }))
81        .with_children(vec![content])
82}
83
84/// Two-axis scroll container with inertia.
85///
86/// Content is wrapped so it does not flex-shrink and grows with content on
87/// both axes.
88pub fn ScrollAreaXY(modifier: Modifier, state: Rc<ScrollStateXY>, content: View) -> View {
89    let binding = state.to_binding();
90    let content = crate::Box(Modifier::new().flex_shrink(0.0)).child(content);
91
92    View::new(0, ViewKind::Box)
93        .modifier(modifier.scrollable(match binding {
94            ScrollBinding::Both(b) => b,
95            _ => unreachable!(),
96        }))
97        .with_children(vec![content])
98}