cranpose_ui/
nested_scroll.rs1use std::rc::Rc;
14
15use cranpose_core::{CompositionLocal, CompositionLocalProvider, compositionLocalOf};
16use cranpose_macros::composable;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum NestedScrollSource {
21 Drag,
23 Fling,
25 Wheel,
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum ScrollAxis {
32 Vertical,
34 Horizontal,
36}
37
38pub trait NestedScrollConnection {
45 fn on_pre_scroll(
47 &self,
48 _axis: ScrollAxis,
49 _available: f32,
50 _source: NestedScrollSource,
51 ) -> f32 {
52 0.0
53 }
54
55 fn on_post_scroll(
58 &self,
59 _axis: ScrollAxis,
60 _consumed: f32,
61 _available: f32,
62 _source: NestedScrollSource,
63 ) -> f32 {
64 0.0
65 }
66
67 fn on_pre_fling(&self, _axis: ScrollAxis, _velocity: f32) -> f32 {
70 0.0
71 }
72}
73
74#[derive(Clone)]
76pub(crate) struct NestedScrollParent {
77 connection: Rc<dyn NestedScrollConnection>,
78 outer: Option<Rc<NestedScrollParent>>,
79}
80
81impl PartialEq for NestedScrollParent {
84 fn eq(&self, other: &Self) -> bool {
85 Rc::ptr_eq(&self.connection, &other.connection) && self.outer == other.outer
86 }
87}
88
89impl NestedScrollParent {
90 pub(crate) fn identity(&self) -> usize {
93 Rc::as_ptr(&self.connection).cast::<()>() as usize
94 }
95
96 pub(crate) fn pre_scroll(
98 &self,
99 axis: ScrollAxis,
100 available: f32,
101 source: NestedScrollSource,
102 ) -> f32 {
103 let outer = self
104 .outer
105 .as_ref()
106 .map_or(0.0, |outer| outer.pre_scroll(axis, available, source));
107 outer
108 + self
109 .connection
110 .on_pre_scroll(axis, available - outer, source)
111 }
112
113 pub(crate) fn post_scroll(
115 &self,
116 axis: ScrollAxis,
117 consumed: f32,
118 available: f32,
119 source: NestedScrollSource,
120 ) -> f32 {
121 let own = self
122 .connection
123 .on_post_scroll(axis, consumed, available, source);
124 own + self.outer.as_ref().map_or(0.0, |outer| {
125 outer.post_scroll(axis, consumed + own, available - own, source)
126 })
127 }
128
129 pub(crate) fn pre_fling(&self, axis: ScrollAxis, velocity: f32) -> f32 {
131 let outer = self
132 .outer
133 .as_ref()
134 .map_or(0.0, |outer| outer.pre_fling(axis, velocity));
135 outer + self.connection.on_pre_fling(axis, velocity - outer)
136 }
137
138 pub(crate) fn dispatch(
142 &self,
143 axis: ScrollAxis,
144 available: f32,
145 source: NestedScrollSource,
146 apply: impl FnOnce(f32) -> f32,
147 ) -> f32 {
148 let before = self.pre_scroll(axis, available, source);
149 let consumed = apply(available - before);
150 let after = self.post_scroll(axis, consumed, available - before - consumed, source);
151 before + consumed + after
152 }
153}
154
155fn local_nested_scroll() -> CompositionLocal<Option<NestedScrollParent>> {
156 crate::thread_state::with_environment(|locals| {
157 locals
158 .nested_scroll
159 .get_or_init(|| compositionLocalOf(|| None))
160 .clone()
161 })
162}
163
164pub(crate) fn current_nested_scroll_parent() -> Option<NestedScrollParent> {
167 cranpose_core::composer_context::current_composer()?;
168 local_nested_scroll().current()
169}
170
171#[composable]
174pub fn NestedScroll(connection: Rc<dyn NestedScrollConnection>, content: impl FnOnce()) {
175 let local = local_nested_scroll();
176 let parent = NestedScrollParent {
177 connection,
178 outer: local.current().map(Rc::new),
179 };
180 CompositionLocalProvider(vec![local.provides(Some(parent))], content);
181}