yew_hooks/hooks/
use_swipe.rs

1use std::cmp::max;
2use std::rc::Rc;
3
4use yew::prelude::*;
5
6use super::{use_event, use_mut_latest};
7
8/// Swipe direction.
9#[derive(PartialEq, Eq, Clone, Debug)]
10pub enum UseSwipeDirection {
11    Up,
12    Right,
13    Down,
14    Left,
15    None,
16}
17
18/// Options for swipe.
19#[derive(Default)]
20pub struct UseSwipeOptions {
21    pub threshold: Option<u32>,
22    pub onswipestart: Option<Box<dyn FnMut(TouchEvent)>>,
23    pub onswipe: Option<Box<dyn FnMut(TouchEvent)>>,
24    pub onswipeend: Option<Box<dyn FnMut(TouchEvent, UseSwipeDirection)>>,
25}
26
27/// State handle for the [`use_swipe`] hook.
28pub struct UseSwipeHandle {
29    pub swiping: UseStateHandle<bool>,
30    pub direction: UseStateHandle<UseSwipeDirection>,
31    pub coords_start: UseStateHandle<(i32, i32)>,
32    pub coords_end: UseStateHandle<(i32, i32)>,
33    pub length_x: UseStateHandle<i32>,
34    pub length_y: UseStateHandle<i32>,
35}
36
37impl Clone for UseSwipeHandle {
38    fn clone(&self) -> Self {
39        Self {
40            swiping: self.swiping.clone(),
41            direction: self.direction.clone(),
42            coords_start: self.coords_start.clone(),
43            coords_end: self.coords_end.clone(),
44            length_x: self.length_x.clone(),
45            length_y: self.length_y.clone(),
46        }
47    }
48}
49
50/// A sensor hook that detects swipe based on TouchEvent.
51///
52/// # Example
53///
54/// ```rust
55/// # use yew::prelude::*;
56/// #
57/// use yew_hooks::prelude::*;
58///
59/// #[function_component(UseSwipe)]
60/// fn swipe() -> Html {
61///     let node =  use_node_ref();
62///     let state = use_swipe(node.clone());
63///
64///     // You can depend on direction/swiping etc.
65///     {
66///         let state = state.clone();
67///         use_effect_with(state.direction, move |direction| {
68///             // Do something based on direction.
69///             match **direction {
70///                 UseSwipeDirection::Left => (),
71///                 UseSwipeDirection::Right => (),
72///                 UseSwipeDirection::Up => (),
73///                 UseSwipeDirection::Down => (),
74///                 _ => (),
75///             }
76///             || ()
77///         });
78///     }
79///     
80///     html! {
81///         <div ref={node}>
82///             <b>{ " swiping: " }</b>
83///             { *state.swiping }
84///             <b>{ " direction: " }</b>
85///             { format!("{:?}", *state.direction) }
86///             <b>{ " coords_start: " }</b>
87///             { format!("{:?}", *state.coords_start) }
88///             <b>{ " coords_end: " }</b>
89///             { format!("{:?}", *state.coords_end) }
90///             <b>{ " length_x: " }</b>
91///             { *state.length_x }
92///             <b>{ " length_y: " }</b>
93///             { *state.length_y }
94///         </div>
95///     }
96/// }
97/// ```
98#[hook]
99pub fn use_swipe(node: NodeRef) -> UseSwipeHandle {
100    use_swipe_with_options(node, UseSwipeOptions::default())
101}
102
103/// A sensor hook that detects swipe based on TouchEvent for window.
104/// See [`use_swipe`].
105#[hook]
106pub fn use_swipe_with_window() -> UseSwipeHandle {
107    use_swipe_with_options(NodeRef::default(), UseSwipeOptions::default())
108}
109
110/// A sensor hook that detects swipe based on TouchEvent with options.
111/// If you want to detect for window, pass `NodeRef::default()` to param `node`.
112///
113/// # Example
114///
115/// ```rust
116/// # use yew::prelude::*;
117/// #
118/// use yew_hooks::prelude::*;
119///
120/// #[function_component(UseSwipe)]
121/// fn swipe() -> Html {
122///     let node =  use_node_ref();
123///     let state = use_swipe_with_options(node.clone(), UseSwipeOptions {
124///         onswipeend: Some(Box::new(move |_e, direction| {
125///             // Deal with TouchEvent.
126///             log::debug!("Swipe direction: {:?}", direction);
127///         })),
128///         ..Default::default()
129///     });
130///     
131///     html! {
132///         <div ref={node}>
133///             <b>{ " swiping: " }</b>
134///             { *state.swiping }
135///             <b>{ " direction: " }</b>
136///             { format!("{:?}", *state.direction) }
137///             <b>{ " coords_start: " }</b>
138///             { format!("{:?}", *state.coords_start) }
139///             <b>{ " coords_end: " }</b>
140///             { format!("{:?}", *state.coords_end) }
141///             <b>{ " length_x: " }</b>
142///             { *state.length_x }
143///             <b>{ " length_y: " }</b>
144///             { *state.length_y }
145///         </div>
146///     }
147/// }
148/// ```
149#[hook]
150pub fn use_swipe_with_options(node: NodeRef, options: UseSwipeOptions) -> UseSwipeHandle {
151    let swiping = use_state_eq(|| false);
152    let direction = use_state_eq(|| UseSwipeDirection::None);
153    let coords_start = use_state(|| (0, 0));
154    let coords_end = use_state(|| (0, 0));
155    let length_x = use_state(|| 0);
156    let length_y = use_state(|| 0);
157
158    let threshold = options.threshold.unwrap_or(50);
159    let onswipestart = use_mut_latest(options.onswipestart);
160    let onswipe = use_mut_latest(options.onswipe);
161    let onswipeend = use_mut_latest(options.onswipeend);
162
163    let diff_x = {
164        let coords_start = coords_start.clone();
165        let coords_end = coords_end.clone();
166        #[allow(clippy::unnecessary_cast)]
167        Rc::new(move || (coords_start.0 - coords_end.0) as i32)
168    };
169
170    let diff_y = {
171        let coords_start = coords_start.clone();
172        let coords_end = coords_end.clone();
173        #[allow(clippy::unnecessary_cast)]
174        Rc::new(move || (coords_start.1 - coords_end.1) as i32)
175    };
176
177    let ontouchend = {
178        let swiping = swiping.clone();
179        let direction = direction.clone();
180        Rc::new(move |e: TouchEvent| {
181            if *swiping {
182                let onswipeend = onswipeend.current();
183                let onswipeend = &mut *onswipeend.borrow_mut();
184                if let Some(onswipeend) = onswipeend {
185                    onswipeend(e, (*direction).clone());
186                }
187            }
188
189            swiping.set(false);
190            direction.set(UseSwipeDirection::None);
191        })
192    };
193
194    let threshold_exceeded = {
195        let diff_x = diff_x.clone();
196        let diff_y = diff_y.clone();
197        Rc::new(move || max(diff_x().abs(), diff_y().abs()) >= (threshold as i32))
198    };
199
200    {
201        let node = node.clone();
202        let coords_start = coords_start.clone();
203        let coords_end = coords_end.clone();
204        use_event(node, "touchstart", move |e: TouchEvent| {
205            let x = e.touches().get(0).map_or(0, |t| t.client_x());
206            let y = e.touches().get(0).map_or(0, |t| t.client_y());
207            coords_start.set((x, y));
208            coords_end.set((x, y));
209
210            let onswipestart = onswipestart.current();
211            let onswipestart = &mut *onswipestart.borrow_mut();
212            if let Some(onswipestart) = onswipestart {
213                onswipestart(e);
214            }
215        });
216    }
217
218    {
219        let node = node.clone();
220        let coords_end = coords_end.clone();
221        let swiping = swiping.clone();
222        let length_x = length_x.clone();
223        let length_y = length_y.clone();
224        let direction = direction.clone();
225        use_event(node, "touchmove", move |e: TouchEvent| {
226            let x = e.touches().get(0).map_or(0, |t| t.client_x());
227            let y = e.touches().get(0).map_or(0, |t| t.client_y());
228            coords_end.set((x, y));
229            length_x.set(diff_x());
230            length_y.set(diff_y());
231
232            if !*swiping && threshold_exceeded() {
233                swiping.set(true);
234            }
235
236            if !threshold_exceeded() {
237                direction.set(UseSwipeDirection::None);
238            } else if diff_x().abs() > diff_y().abs() {
239                if diff_x() > 0 {
240                    direction.set(UseSwipeDirection::Left);
241                } else {
242                    direction.set(UseSwipeDirection::Right);
243                }
244            } else if diff_y() > 0 {
245                direction.set(UseSwipeDirection::Up);
246            } else {
247                direction.set(UseSwipeDirection::Down);
248            }
249
250            if *swiping {
251                let onswipe = onswipe.current();
252                let onswipe = &mut *onswipe.borrow_mut();
253                if let Some(onswipe) = onswipe {
254                    onswipe(e);
255                }
256            }
257        });
258    }
259
260    {
261        let node = node.clone();
262        let ontouchend = ontouchend.clone();
263        use_event(node, "touchend", move |e: TouchEvent| {
264            ontouchend(e);
265        });
266    }
267
268    {
269        let ontouchend = ontouchend.clone();
270        use_event(node, "touchcancel", move |e: TouchEvent| {
271            ontouchend(e);
272        });
273    }
274
275    UseSwipeHandle {
276        swiping,
277        direction,
278        coords_start,
279        coords_end,
280        length_x,
281        length_y,
282    }
283}