1#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
2
3use crate::core::{IntoElementMaybeSignal, Position};
4use crate::{UseEventListenerOptions, UseWindow, use_event_listener_with_options, use_window};
5use default_struct_builder::DefaultBuilder;
6use leptos::ev::{dragover, mousemove, touchend, touchmove, touchstart};
7use leptos::prelude::*;
8use std::convert::Infallible;
9use std::marker::PhantomData;
10use wasm_bindgen::{JsCast, JsValue};
11
12pub fn use_mouse() -> UseMouseReturn {
93 use_mouse_with_options(Default::default())
94}
95
96pub fn use_mouse_with_options<El, M, Ex>(options: UseMouseOptions<El, M, Ex>) -> UseMouseReturn
98where
99 El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
100 Ex: UseMouseEventExtractor + Clone + 'static,
101{
102 let (x, set_x) = signal(options.initial_value.x);
103 let (y, set_y) = signal(options.initial_value.y);
104 let (source_type, set_source_type) = signal(UseMouseSourceType::Unset);
105
106 let mouse_handler = {
107 let coord_type = options.coord_type.clone();
108
109 move |event: web_sys::MouseEvent| {
110 let result = coord_type.extract_mouse_coords(&event);
111
112 if let Some((x, y)) = result {
113 set_x.set(x);
114 set_y.set(y);
115 set_source_type.set(UseMouseSourceType::Mouse);
116 }
117 }
118 };
119
120 let drag_handler = {
121 let mouse_handler = mouse_handler.clone();
122
123 move |event: web_sys::DragEvent| {
124 let js_value: &JsValue = event.as_ref();
125 mouse_handler(js_value.clone().unchecked_into::<web_sys::MouseEvent>());
126 }
127 };
128
129 let touch_handler = {
130 let coord_type = options.coord_type.clone();
131
132 move |event: web_sys::TouchEvent| {
133 let touches = event.touches();
134 if touches.length() > 0 {
135 let result = coord_type.extract_touch_coords(
136 &touches
137 .get(0)
138 .expect("Just checked that there's at least on touch"),
139 );
140
141 if let Some((x, y)) = result {
142 set_x.set(x);
143 set_y.set(y);
144 set_source_type.set(UseMouseSourceType::Touch);
145 }
146 }
147 }
148 };
149
150 let initial_value = options.initial_value;
151 let reset = move || {
152 set_x.set(initial_value.x);
153 set_y.set(initial_value.y);
154 };
155
156 #[cfg(not(feature = "ssr"))]
159 {
160 let target = options.target.into_element_maybe_signal();
161 let event_listener_options = UseEventListenerOptions::default().passive(true);
162
163 let _ = use_event_listener_with_options(
164 target,
165 mousemove,
166 mouse_handler,
167 event_listener_options,
168 );
169 let _ =
170 use_event_listener_with_options(target, dragover, drag_handler, event_listener_options);
171
172 if options.touch && !matches!(options.coord_type, UseMouseCoordType::Movement) {
173 let _ = use_event_listener_with_options(
174 target,
175 touchstart,
176 touch_handler.clone(),
177 event_listener_options,
178 );
179 let _ = use_event_listener_with_options(
180 target,
181 touchmove,
182 touch_handler,
183 event_listener_options,
184 );
185 if options.reset_on_touch_ends {
186 let _ = use_event_listener_with_options(
187 target,
188 touchend,
189 move |_| reset(),
190 event_listener_options,
191 );
192 }
193 }
194 }
195
196 UseMouseReturn {
197 x: x.into(),
198 y: y.into(),
199 set_x,
200 set_y,
201 source_type: source_type.into(),
202 }
203}
204
205#[derive(DefaultBuilder)]
206pub struct UseMouseOptions<El, M, Ex>
208where
209 El: IntoElementMaybeSignal<web_sys::EventTarget, M>,
210 Ex: UseMouseEventExtractor + Clone,
211{
212 coord_type: UseMouseCoordType<Ex>,
214
215 target: El,
217
218 touch: bool,
220
221 reset_on_touch_ends: bool,
223
224 initial_value: Position,
226
227 #[builder(skip)]
228 _marker: PhantomData<M>,
229}
230
231impl<M> Default for UseMouseOptions<UseWindow, M, Infallible>
232where
233 UseWindow: IntoElementMaybeSignal<web_sys::EventTarget, M>,
234{
235 fn default() -> Self {
236 Self {
237 coord_type: UseMouseCoordType::default(),
238 target: use_window(),
239 touch: true,
240 reset_on_touch_ends: false,
241 initial_value: Position { x: 0.0, y: 0.0 },
242 _marker: PhantomData,
243 }
244 }
245}
246
247#[derive(Clone, Default)]
249pub enum UseMouseCoordType<E: UseMouseEventExtractor + Clone> {
250 #[default]
251 Page,
252 Client,
253 Screen,
254 Movement,
255 Custom(E),
256}
257
258#[allow(unused_variables)]
260pub trait UseMouseEventExtractor {
261 fn extract_mouse_coords(&self, event: &web_sys::MouseEvent) -> Option<(f64, f64)> {
263 None
264 }
265
266 fn extract_touch_coords(&self, touch: &web_sys::Touch) -> Option<(f64, f64)> {
268 None
269 }
270}
271
272impl<E: UseMouseEventExtractor + Clone> UseMouseEventExtractor for UseMouseCoordType<E> {
273 fn extract_mouse_coords(&self, event: &web_sys::MouseEvent) -> Option<(f64, f64)> {
274 match self {
275 UseMouseCoordType::Page => Some((event.page_x() as f64, event.page_y() as f64)),
276 UseMouseCoordType::Client => Some((event.client_x() as f64, event.client_y() as f64)),
277 UseMouseCoordType::Screen => Some((event.screen_x() as f64, event.client_y() as f64)),
278 UseMouseCoordType::Movement => {
279 Some((event.movement_x() as f64, event.movement_y() as f64))
280 }
281 UseMouseCoordType::Custom(extractor) => extractor.extract_mouse_coords(event),
282 }
283 }
284
285 fn extract_touch_coords(&self, touch: &web_sys::Touch) -> Option<(f64, f64)> {
286 match self {
287 UseMouseCoordType::Page => Some((touch.page_x() as f64, touch.page_y() as f64)),
288 UseMouseCoordType::Client => Some((touch.client_x() as f64, touch.client_y() as f64)),
289 UseMouseCoordType::Screen => Some((touch.screen_x() as f64, touch.client_y() as f64)),
290 UseMouseCoordType::Movement => None,
291 UseMouseCoordType::Custom(extractor) => extractor.extract_touch_coords(touch),
292 }
293 }
294}
295
296impl UseMouseEventExtractor for Infallible {
297 fn extract_mouse_coords(&self, _: &web_sys::MouseEvent) -> Option<(f64, f64)> {
298 unreachable!()
299 }
300
301 fn extract_touch_coords(&self, _: &web_sys::Touch) -> Option<(f64, f64)> {
302 unreachable!()
303 }
304}
305
306pub struct UseMouseReturn {
308 pub x: Signal<f64>,
310 pub y: Signal<f64>,
312 pub set_x: WriteSignal<f64>,
314 pub set_y: WriteSignal<f64>,
316 pub source_type: Signal<UseMouseSourceType>,
318}
319
320#[derive(Copy, Clone, Debug, PartialEq, Eq)]
322pub enum UseMouseSourceType {
323 Mouse,
325 Touch,
327 Unset,
329}