dioxus_use_gesture/
lib.rs

1use dioxus::prelude::*;
2use std::{mem, rc::Rc};
3use wasm_bindgen::{prelude::Closure, JsCast};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum DragState {
7    Move,
8    End,
9}
10
11#[derive(Default)]
12struct State {
13    mounted: Option<Rc<MountedData>>,
14    on_pointer_down: Option<Closure<dyn FnMut(web_sys::PointerEvent)>>,
15    on_pointer_move: Option<Closure<dyn FnMut(web_sys::PointerEvent)>>,
16    on_pointer_up: Option<Closure<dyn FnMut(web_sys::PointerEvent)>>,
17    start: Option<(f32, f32)>,
18}
19
20pub fn use_drag<'a, T>(cx: Scope<'a, T>, f: impl FnMut(DragState, f32, f32) + 'a) -> UseDrag {
21    let state_ref = use_ref(cx, || State::default());
22
23    let handler_cell: Rc<RefCell<dyn FnMut(DragState, f32, f32) + 'a>> = Rc::new(RefCell::new(f));
24    let handler_cell: Rc<RefCell<dyn FnMut(DragState, f32, f32) + 'static>> =
25        unsafe { mem::transmute(handler_cell) };
26
27    let state_ref_clone = state_ref.clone();
28    use_effect(
29        cx,
30        &state_ref.read().mounted.is_some(),
31        move |_| async move {
32            let mut state = state_ref_clone.write();
33            if let Some(mounted) = state.mounted.clone() {
34                let element = mounted
35                    .get_raw_element()
36                    .unwrap()
37                    .downcast_ref::<web_sys::Element>()
38                    .unwrap();
39
40                let callback_state_ref = state_ref_clone.clone();
41                let handler_cell_clone = handler_cell.clone();
42                state.on_pointer_move = Some(Closure::new(move |event: web_sys::PointerEvent| {
43                    if let Some((start_x, start_y)) = callback_state_ref.read().start {
44                        handler_cell_clone.borrow_mut()(
45                            DragState::Move,
46                            event.client_x() as f32 - start_x,
47                            event.client_y() as f32 - start_y,
48                        );
49                    }
50                }));
51                element
52                    .add_event_listener_with_callback(
53                        "pointermove",
54                        state
55                            .on_pointer_move
56                            .as_ref()
57                            .unwrap()
58                            .as_ref()
59                            .unchecked_ref(),
60                    )
61                    .unwrap();
62
63                let callback_state_ref = state_ref_clone.clone();
64                let callback_mounted = mounted.clone();
65                state.on_pointer_down = Some(Closure::new(move |event: web_sys::PointerEvent| {
66                    let element = callback_mounted
67                        .get_raw_element()
68                        .unwrap()
69                        .downcast_ref::<web_sys::Element>()
70                        .unwrap();
71                    let rect = element.get_bounding_client_rect();
72
73                    callback_state_ref.write().start = Some((
74                        event.client_x() as f32 - rect.left() as f32,
75                        event.client_y() as f32 - rect.top() as f32,
76                    ));
77                }));
78                element
79                    .add_event_listener_with_callback(
80                        "pointerdown",
81                        state
82                            .on_pointer_down
83                            .as_ref()
84                            .unwrap()
85                            .as_ref()
86                            .unchecked_ref(),
87                    )
88                    .unwrap();
89
90                let callback_state_ref = state_ref_clone.clone();
91                state.on_pointer_up = Some(Closure::new(move |event: web_sys::PointerEvent| {
92                    handler_cell.borrow_mut()(
93                        DragState::End,
94                        event.client_x() as _,
95                        event.client_y() as _,
96                    );
97                    callback_state_ref.write().start.take();
98                }));
99                element
100                    .add_event_listener_with_callback(
101                        "pointerup",
102                        state
103                            .on_pointer_up
104                            .as_ref()
105                            .unwrap()
106                            .as_ref()
107                            .unchecked_ref(),
108                    )
109                    .unwrap();
110            }
111        },
112    );
113
114    let state_ref_clone = state_ref.clone();
115    use_on_unmount(cx, move || {
116        let mut state = state_ref_clone.write();
117        if let Some(mounted) = state.mounted.clone() {
118            let element = mounted
119                .get_raw_element()
120                .unwrap()
121                .downcast_ref::<web_sys::Element>()
122                .unwrap();
123
124            remove_listener(element, "pointerdown", &mut state.on_pointer_down);
125            remove_listener(element, "pointermove", &mut state.on_pointer_move);
126            remove_listener(element, "pointerup", &mut state.on_pointer_up);
127        }
128    });
129
130    UseDrag {
131        element_ref: state_ref.clone(),
132    }
133}
134
135fn remove_listener(
136    element: &web_sys::Element,
137    name: &str,
138    cell: &mut Option<Closure<dyn FnMut(web_sys::PointerEvent)>>,
139) {
140    if let Some(f) = cell.take() {
141        element
142            .remove_event_listener_with_callback(name, f.as_ref().unchecked_ref())
143            .unwrap();
144    }
145}
146
147pub struct UseDrag {
148    element_ref: UseRef<State>,
149}
150
151impl UseDrag {
152    pub fn mount(&self, data: Rc<MountedData>) {
153        self.element_ref.write().mounted = Some(data);
154    }
155}