cranpose_ui/
nested_scroll.rs1use std::rc::Rc;
14
15use cranpose_core::{CompositionLocal, CompositionLocalProvider, compositionLocalOf};
16use cranpose_macros::composable;
17use cranpose_ui_layout::Axis;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub enum NestedScrollSource {
22 Drag,
24 Fling,
26 Wheel,
28}
29
30pub trait NestedScrollConnection {
37 fn on_pre_scroll(&self, _axis: Axis, _available: f32, _source: NestedScrollSource) -> f32 {
39 0.0
40 }
41
42 fn on_post_scroll(
45 &self,
46 _axis: Axis,
47 _consumed: f32,
48 _available: f32,
49 _source: NestedScrollSource,
50 ) -> f32 {
51 0.0
52 }
53
54 fn on_pre_fling(&self, _axis: Axis, _velocity: f32) -> f32 {
57 0.0
58 }
59}
60
61#[derive(Clone)]
63pub(crate) struct NestedScrollParent {
64 connection: Rc<dyn NestedScrollConnection>,
65 outer: Option<Rc<Self>>,
66}
67
68impl PartialEq for NestedScrollParent {
71 fn eq(&self, other: &Self) -> bool {
72 Rc::ptr_eq(&self.connection, &other.connection) && self.outer == other.outer
73 }
74}
75
76impl NestedScrollParent {
77 pub(crate) fn identity(&self) -> usize {
80 Rc::as_ptr(&self.connection).cast::<()>() as usize
81 }
82
83 pub(crate) fn pre_scroll(&self, axis: Axis, available: f32, source: NestedScrollSource) -> f32 {
85 let outer = self
86 .outer
87 .as_ref()
88 .map_or(0.0, |outer| outer.pre_scroll(axis, available, source));
89 outer
90 + self
91 .connection
92 .on_pre_scroll(axis, available - outer, source)
93 }
94
95 pub(crate) fn post_scroll(
97 &self,
98 axis: Axis,
99 consumed: f32,
100 available: f32,
101 source: NestedScrollSource,
102 ) -> f32 {
103 let own = self
104 .connection
105 .on_post_scroll(axis, consumed, available, source);
106 own + self.outer.as_ref().map_or(0.0, |outer| {
107 outer.post_scroll(axis, consumed + own, available - own, source)
108 })
109 }
110
111 pub(crate) fn pre_fling(&self, axis: Axis, velocity: f32) -> f32 {
113 let outer = self
114 .outer
115 .as_ref()
116 .map_or(0.0, |outer| outer.pre_fling(axis, velocity));
117 outer + self.connection.on_pre_fling(axis, velocity - outer)
118 }
119
120 pub(crate) fn dispatch(
124 &self,
125 axis: Axis,
126 available: f32,
127 source: NestedScrollSource,
128 apply: impl FnOnce(f32) -> f32,
129 ) -> f32 {
130 let before = self.pre_scroll(axis, available, source);
131 let consumed = apply(available - before);
132 let after = self.post_scroll(axis, consumed, available - before - consumed, source);
133 before + consumed + after
134 }
135}
136
137fn local_nested_scroll() -> CompositionLocal<Option<NestedScrollParent>> {
138 crate::thread_state::with_environment(|locals| {
139 locals
140 .nested_scroll
141 .get_or_init(|| compositionLocalOf(|| None))
142 .clone()
143 })
144}
145
146pub(crate) fn current_nested_scroll_parent() -> Option<NestedScrollParent> {
149 cranpose_core::composer_context::current_composer()?;
150 local_nested_scroll().current()
151}
152
153#[composable]
156pub fn NestedScroll(connection: Rc<dyn NestedScrollConnection>, content: impl FnOnce()) {
157 let local = local_nested_scroll();
158 let parent = NestedScrollParent {
159 connection,
160 outer: local.current().map(Rc::new),
161 };
162 CompositionLocalProvider(vec![local.provides(Some(parent))], content);
163}