1#![expect(non_snake_case)]
24
25use std::{
26 cell::{Cell, RefCell},
27 rc::Rc,
28};
29
30use cranpose_core::{MutableState, State, remember};
31
32pub type DragDeltaHandler = Rc<dyn Fn(f32)>;
34
35struct DraggableStateInner {
36 on_delta: RefCell<Rc<dyn Fn(f32) -> f32>>,
37 dragging: MutableState<bool>,
38 offset: Cell<f32>,
39}
40
41#[derive(Clone)]
46pub struct DraggableState {
47 inner: Rc<DraggableStateInner>,
48}
49
50impl PartialEq for DraggableState {
51 fn eq(&self, other: &Self) -> bool {
52 Rc::ptr_eq(&self.inner, &other.inner)
53 }
54}
55
56impl DraggableState {
57 pub fn new(on_delta: impl Fn(f32) + 'static) -> Self {
62 Self::new_consuming(move |delta| {
63 on_delta(delta);
64 delta
65 })
66 }
67
68 fn new_consuming(on_delta: impl Fn(f32) -> f32 + 'static) -> Self {
69 let runtime = cranpose_core::current_runtime_handle()
70 .expect("DraggableState::new requires an active runtime");
71 Self {
72 inner: Rc::new(DraggableStateInner {
73 on_delta: RefCell::new(Rc::new(on_delta)),
74 dragging: MutableState::with_runtime(false, runtime),
75 offset: Cell::new(0.0),
76 }),
77 }
78 }
79
80 pub fn update_handler(&self, on_delta: impl Fn(f32) + 'static) {
85 self.update_consuming_handler(move |delta| {
86 on_delta(delta);
87 delta
88 });
89 }
90
91 fn update_consuming_handler(&self, on_delta: impl Fn(f32) -> f32 + 'static) {
92 *self.inner.on_delta.borrow_mut() = Rc::new(on_delta);
93 }
94
95 pub fn is_dragging(&self) -> bool {
99 self.inner.dragging.value()
100 }
101
102 pub fn dragging(&self) -> State<bool> {
104 self.inner.dragging.as_state()
105 }
106
107 pub fn offset(&self) -> f32 {
109 self.inner.offset.get()
110 }
111
112 pub fn drag_by(&self, delta: f32) {
117 self.dispatch_delta(delta);
118 }
119
120 fn dispatch_delta(&self, delta: f32) -> f32 {
121 if !delta.is_finite() || delta == 0.0 {
122 return 0.0;
123 }
124 let handler = Rc::clone(&self.inner.on_delta.borrow());
125 let consumed = handler(delta);
126 self.inner.offset.set(self.inner.offset.get() + consumed);
127 consumed
128 }
129
130 pub(crate) fn identity(&self) -> usize {
131 Rc::as_ptr(&self.inner) as usize
132 }
133
134 pub(crate) fn set_dragging(&self, dragging: bool) {
135 if self.inner.dragging.get_non_reactive() != dragging {
136 self.inner.dragging.set(dragging);
137 }
138 }
139}
140
141#[track_caller]
144pub fn rememberDraggableState(on_delta: impl Fn(f32) + 'static) -> DraggableState {
145 let state = remember(|| DraggableState::new(|_| {})).with(Clone::clone);
146 state.update_handler(on_delta);
147 state
148}
149
150#[derive(Clone, PartialEq)]
157pub struct ScrollableState {
158 drag: DraggableState,
159}
160
161impl ScrollableState {
162 pub fn new(on_delta: impl Fn(f32) -> f32 + 'static) -> Self {
164 Self {
165 drag: DraggableState::new_consuming(on_delta),
166 }
167 }
168
169 pub fn dispatch_raw_delta(&self, delta: f32) -> f32 {
171 self.drag.dispatch_delta(delta)
172 }
173
174 pub fn is_dragging(&self) -> bool {
176 self.drag.is_dragging()
177 }
178
179 pub(crate) fn identity(&self) -> usize {
180 self.drag.identity()
181 }
182
183 pub(crate) fn offset(&self) -> f32 {
184 self.drag.offset()
185 }
186
187 pub(crate) fn set_dragging(&self, dragging: bool) {
188 self.drag.set_dragging(dragging);
189 }
190}
191
192#[track_caller]
194pub fn rememberScrollableState(on_delta: impl Fn(f32) -> f32 + 'static) -> ScrollableState {
195 let state = remember(|| ScrollableState::new(|_| 0.0)).with(Clone::clone);
196 state.drag.update_consuming_handler(on_delta);
197 state
198}
199
200#[cfg(test)]
201#[path = "tests/draggable_tests.rs"]
202mod draggable_tests;
203
204#[cfg(test)]
205#[path = "tests/scrollable_tests.rs"]
206mod scrollable_tests;
207
208#[cfg(test)]
209#[path = "tests/draggable_drag_state_tests.rs"]
210mod tests;