#![expect(non_snake_case)]
use std::{
cell::{Cell, RefCell},
rc::Rc,
};
use cranpose_core::{MutableState, State, remember};
pub type DragDeltaHandler = Rc<dyn Fn(f32)>;
struct DraggableStateInner {
on_delta: RefCell<Rc<dyn Fn(f32) -> f32>>,
dragging: MutableState<bool>,
offset: Cell<f32>,
}
#[derive(Clone)]
pub struct DraggableState {
inner: Rc<DraggableStateInner>,
}
impl PartialEq for DraggableState {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.inner, &other.inner)
}
}
impl DraggableState {
pub fn new(on_delta: impl Fn(f32) + 'static) -> Self {
Self::new_consuming(move |delta| {
on_delta(delta);
delta
})
}
fn new_consuming(on_delta: impl Fn(f32) -> f32 + 'static) -> Self {
let runtime = cranpose_core::current_runtime_handle()
.expect("DraggableState::new requires an active runtime");
Self {
inner: Rc::new(DraggableStateInner {
on_delta: RefCell::new(Rc::new(on_delta)),
dragging: MutableState::with_runtime(false, runtime),
offset: Cell::new(0.0),
}),
}
}
pub fn update_handler(&self, on_delta: impl Fn(f32) + 'static) {
self.update_consuming_handler(move |delta| {
on_delta(delta);
delta
});
}
fn update_consuming_handler(&self, on_delta: impl Fn(f32) -> f32 + 'static) {
*self.inner.on_delta.borrow_mut() = Rc::new(on_delta);
}
pub fn is_dragging(&self) -> bool {
self.inner.dragging.value()
}
pub fn dragging(&self) -> State<bool> {
self.inner.dragging.as_state()
}
pub fn offset(&self) -> f32 {
self.inner.offset.get()
}
pub fn drag_by(&self, delta: f32) {
self.dispatch_delta(delta);
}
fn dispatch_delta(&self, delta: f32) -> f32 {
if !delta.is_finite() || delta == 0.0 {
return 0.0;
}
let handler = Rc::clone(&self.inner.on_delta.borrow());
let consumed = handler(delta);
self.inner.offset.set(self.inner.offset.get() + consumed);
consumed
}
pub(crate) fn identity(&self) -> usize {
Rc::as_ptr(&self.inner) as usize
}
pub(crate) fn set_dragging(&self, dragging: bool) {
if self.inner.dragging.get_non_reactive() != dragging {
self.inner.dragging.set(dragging);
}
}
}
#[track_caller]
pub fn rememberDraggableState(on_delta: impl Fn(f32) + 'static) -> DraggableState {
let state = remember(|| DraggableState::new(|_| {})).with(Clone::clone);
state.update_handler(on_delta);
state
}
#[derive(Clone, PartialEq)]
pub struct ScrollableState {
drag: DraggableState,
}
impl ScrollableState {
pub fn new(on_delta: impl Fn(f32) -> f32 + 'static) -> Self {
Self {
drag: DraggableState::new_consuming(on_delta),
}
}
pub fn dispatch_raw_delta(&self, delta: f32) -> f32 {
self.drag.dispatch_delta(delta)
}
pub fn is_dragging(&self) -> bool {
self.drag.is_dragging()
}
pub(crate) fn identity(&self) -> usize {
self.drag.identity()
}
pub(crate) fn offset(&self) -> f32 {
self.drag.offset()
}
pub(crate) fn set_dragging(&self, dragging: bool) {
self.drag.set_dragging(dragging);
}
}
#[track_caller]
pub fn rememberScrollableState(on_delta: impl Fn(f32) -> f32 + 'static) -> ScrollableState {
let state = remember(|| ScrollableState::new(|_| 0.0)).with(Clone::clone);
state.drag.update_consuming_handler(on_delta);
state
}
#[cfg(test)]
#[path = "tests/draggable_tests.rs"]
mod draggable_tests;
#[cfg(test)]
#[path = "tests/scrollable_tests.rs"]
mod scrollable_tests;
#[cfg(test)]
#[path = "tests/draggable_drag_state_tests.rs"]
mod tests;