leptos-use/src/use_mouse.rs

329 lines
10 KiB
Rust
Raw Normal View History

2023-07-14 22:43:19 +01:00
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
2023-06-02 13:38:01 +01:00
use crate::core::{ElementMaybeSignal, Position};
use crate::{use_event_listener_with_options, use_window, UseEventListenerOptions, UseWindow};
2023-07-14 22:43:19 +01:00
use cfg_if::cfg_if;
2023-06-02 13:38:01 +01:00
use default_struct_builder::DefaultBuilder;
use leptos::ev::{dragover, mousemove, touchend, touchmove, touchstart};
use leptos::*;
use std::marker::PhantomData;
use wasm_bindgen::{JsCast, JsValue};
/// Reactive mouse position
///
/// ## Demo
///
/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_mouse)
///
/// ## Basic Usage
///
/// ```
/// # use leptos::*;
/// # use leptos_use::{use_mouse, UseMouseReturn};
/// #
/// # #[component]
2023-07-27 18:06:36 +01:00
/// # fn Demo() -> impl IntoView {
2023-06-02 13:38:01 +01:00
/// let UseMouseReturn {
/// x, y, source_type, ..
2023-07-27 18:06:36 +01:00
/// } = use_mouse();
/// # view! { }
2023-06-02 13:38:01 +01:00
/// # }
/// ```
///
/// Touch is enabled by default. To only detect mouse changes, set `touch` to `false`.
/// The `dragover` event is used to track mouse position while dragging.
///
/// ```
/// # use leptos::*;
/// # use leptos_use::{use_mouse_with_options, UseMouseOptions, UseMouseReturn};
/// #
/// # #[component]
2023-07-27 18:06:36 +01:00
/// # fn Demo() -> impl IntoView {
2023-06-02 13:38:01 +01:00
/// let UseMouseReturn {
/// x, y, ..
/// } = use_mouse_with_options(
/// UseMouseOptions::default().touch(false)
/// );
2023-07-27 18:06:36 +01:00
/// # view! { }
2023-06-02 13:38:01 +01:00
/// # }
/// ```
///
/// ## Custom Extractor
///
/// It's also possible to provide a custom extractor to get the position from the events.
///
/// ```
/// # use leptos::*;
2023-07-03 15:16:22 +01:00
/// # use leptos::html::Div;
2023-06-02 13:38:01 +01:00
/// use web_sys::MouseEvent;
/// use leptos_use::{use_mouse_with_options, UseMouseOptions, UseMouseReturn, UseMouseEventExtractor, UseMouseCoordType};
///
/// #[derive(Clone)]
/// struct MyExtractor;
///
/// impl UseMouseEventExtractor for MyExtractor {
/// fn extract_mouse_coords(&self, event: &MouseEvent) -> Option<(f64, f64)> {
/// Some((event.offset_x() as f64, event.offset_y() as f64))
/// }
///
/// // don't implement fn extract_touch_coords to ignore touch events
/// }
///
/// #[component]
2023-07-27 18:06:36 +01:00
/// fn Demo() -> impl IntoView {
/// let element = create_node_ref::<Div>();
2023-06-02 13:38:01 +01:00
///
/// let UseMouseReturn {
/// x, y, source_type, ..
/// } = use_mouse_with_options(
/// UseMouseOptions::default()
/// .target(element)
/// .coord_type(UseMouseCoordType::Custom(MyExtractor))
/// );
2023-07-27 18:06:36 +01:00
/// view! { <div node_ref=element></div> }
2023-06-02 13:38:01 +01:00
/// }
/// ```
2023-07-14 22:43:19 +01:00
///
/// ## Server-Side Rendering
///
/// On the server this returns simple `Signal`s with the `initial_value`s.
2023-07-27 18:06:36 +01:00
pub fn use_mouse() -> UseMouseReturn {
use_mouse_with_options(Default::default())
2023-06-02 13:38:01 +01:00
}
/// Variant of [`use_mouse`] that accepts options. Please see [`use_mouse`] for how to use.
2023-07-27 18:06:36 +01:00
pub fn use_mouse_with_options<El, T, Ex>(options: UseMouseOptions<El, T, Ex>) -> UseMouseReturn
2023-06-02 13:38:01 +01:00
where
2024-01-03 22:52:59 +00:00
El: Into<ElementMaybeSignal<T, web_sys::EventTarget>> + Clone,
2023-06-02 13:38:01 +01:00
T: Into<web_sys::EventTarget> + Clone + 'static,
Ex: UseMouseEventExtractor + Clone + 'static,
{
2023-07-27 18:06:36 +01:00
let (x, set_x) = create_signal(options.initial_value.x);
let (y, set_y) = create_signal(options.initial_value.y);
let (source_type, set_source_type) = create_signal(UseMouseSourceType::Unset);
2023-06-02 13:38:01 +01:00
2023-06-13 17:48:32 +01:00
let mouse_handler = {
let coord_type = options.coord_type.clone();
2023-06-02 13:38:01 +01:00
2023-06-13 17:48:32 +01:00
move |event: web_sys::MouseEvent| {
let result = coord_type.extract_mouse_coords(&event);
if let Some((x, y)) = result {
set_x.set(x);
set_y.set(y);
set_source_type.set(UseMouseSourceType::Mouse);
2023-06-13 17:48:32 +01:00
}
2023-06-02 13:38:01 +01:00
}
};
2023-06-13 17:48:32 +01:00
let drag_handler = {
let mouse_handler = mouse_handler.clone();
move |event: web_sys::DragEvent| {
let js_value: &JsValue = event.as_ref();
mouse_handler(js_value.clone().unchecked_into::<web_sys::MouseEvent>());
}
2023-06-02 13:38:01 +01:00
};
2023-06-13 17:48:32 +01:00
let touch_handler = {
let coord_type = options.coord_type.clone();
2023-06-13 17:50:39 +01:00
2023-06-13 17:48:32 +01:00
move |event: web_sys::TouchEvent| {
let touches = event.touches();
if touches.length() > 0 {
let result = coord_type.extract_touch_coords(
&touches
.get(0)
.expect("Just checked that there's at least on touch"),
);
2023-06-02 13:38:01 +01:00
2023-06-13 17:48:32 +01:00
if let Some((x, y)) = result {
set_x.set(x);
set_y.set(y);
set_source_type.set(UseMouseSourceType::Touch);
2023-06-13 17:48:32 +01:00
}
2023-06-02 13:38:01 +01:00
}
}
};
let initial_value = options.initial_value;
let reset = move || {
set_x.set(initial_value.x);
set_y.set(initial_value.y);
2023-06-02 13:38:01 +01:00
};
// TODO : event filters?
2023-07-14 22:43:19 +01:00
cfg_if! { if #[cfg(not(feature = "ssr"))] {
let target = options.target;
let event_listener_options = UseEventListenerOptions::default().passive(true);
2023-07-14 22:43:19 +01:00
2023-06-02 13:38:01 +01:00
let _ = use_event_listener_with_options(
2023-07-27 18:06:36 +01:00
target.clone(),
2023-07-14 22:43:19 +01:00
mousemove,
mouse_handler,
event_listener_options,
2023-06-02 13:38:01 +01:00
);
let _ = use_event_listener_with_options(
2023-07-27 18:06:36 +01:00
target.clone(),
2023-07-14 22:43:19 +01:00
dragover,
drag_handler,
event_listener_options,
2023-06-02 13:38:01 +01:00
);
2023-07-14 22:43:19 +01:00
if options.touch && !matches!(options.coord_type, UseMouseCoordType::Movement) {
let _ = use_event_listener_with_options(
2023-07-27 18:06:36 +01:00
target.clone(),
2023-07-14 22:43:19 +01:00
touchstart,
touch_handler.clone(),
event_listener_options,
2023-07-14 22:43:19 +01:00
);
2023-06-02 13:38:01 +01:00
let _ = use_event_listener_with_options(
2023-07-27 18:06:36 +01:00
target.clone(),
2023-07-14 22:43:19 +01:00
touchmove,
touch_handler,
event_listener_options,
2023-06-02 13:38:01 +01:00
);
2023-07-14 22:43:19 +01:00
if options.reset_on_touch_ends {
let _ = use_event_listener_with_options(
2023-07-27 18:06:36 +01:00
target,
2023-07-14 22:43:19 +01:00
touchend,
move |_| reset(),
event_listener_options,
2023-07-14 22:43:19 +01:00
);
}
2023-06-02 13:38:01 +01:00
}
2023-07-14 22:43:19 +01:00
}}
2023-06-02 13:38:01 +01:00
UseMouseReturn {
2024-01-03 22:52:59 +00:00
x: x.into(),
y: y.into(),
2023-06-02 13:38:01 +01:00
set_x,
set_y,
2024-01-03 22:52:59 +00:00
source_type: source_type.into(),
2023-06-02 13:38:01 +01:00
}
}
#[derive(DefaultBuilder)]
2023-06-05 00:02:13 +01:00
/// Options for [`use_mouse_with_options`].
2023-06-02 13:38:01 +01:00
pub struct UseMouseOptions<El, T, Ex>
where
El: Clone + Into<ElementMaybeSignal<T, web_sys::EventTarget>>,
2023-06-02 13:38:01 +01:00
T: Into<web_sys::EventTarget> + Clone + 'static,
Ex: UseMouseEventExtractor + Clone,
{
/// How to extract the x, y coordinates from mouse events or touches
coord_type: UseMouseCoordType<Ex>,
/// Listen events on `target` element. Defaults to `window`
target: El,
/// Listen to `touchmove` events. Defaults to `true`.
touch: bool,
/// Reset to initial value when `touchend` event fired. Defaults to `false`
reset_on_touch_ends: bool,
/// Initial values. Defaults to `{x: 0.0, y: 0.0}`.
initial_value: Position,
#[builder(skip)]
_marker: PhantomData<T>,
}
impl Default for UseMouseOptions<UseWindow, web_sys::Window, UseMouseEventExtractorDefault> {
fn default() -> Self {
Self {
coord_type: UseMouseCoordType::<UseMouseEventExtractorDefault>::default(),
target: use_window(),
touch: true,
reset_on_touch_ends: false,
initial_value: Position { x: 0.0, y: 0.0 },
_marker: PhantomData,
2023-06-02 13:38:01 +01:00
}
}
}
2023-06-02 13:38:01 +01:00
/// Defines how to get the coordinates from the event.
#[derive(Clone)]
pub enum UseMouseCoordType<E: UseMouseEventExtractor + Clone> {
Page,
Client,
Screen,
Movement,
Custom(E),
}
impl Default for UseMouseCoordType<UseMouseEventExtractorDefault> {
fn default() -> Self {
Self::Page
}
}
/// Trait to implement if you want to specify a custom extractor
#[allow(unused_variables)]
pub trait UseMouseEventExtractor {
/// Return the coordinates from mouse events (`Some(x, y)`) or `None`
fn extract_mouse_coords(&self, event: &web_sys::MouseEvent) -> Option<(f64, f64)> {
None
}
/// Return the coordinates from touches (`Some(x, y)`) or `None`
fn extract_touch_coords(&self, touch: &web_sys::Touch) -> Option<(f64, f64)> {
None
}
}
impl<E: UseMouseEventExtractor + Clone> UseMouseEventExtractor for UseMouseCoordType<E> {
fn extract_mouse_coords(&self, event: &web_sys::MouseEvent) -> Option<(f64, f64)> {
match self {
UseMouseCoordType::Page => Some((event.page_x() as f64, event.page_y() as f64)),
UseMouseCoordType::Client => Some((event.client_x() as f64, event.client_y() as f64)),
UseMouseCoordType::Screen => Some((event.screen_x() as f64, event.client_y() as f64)),
UseMouseCoordType::Movement => {
Some((event.movement_x() as f64, event.movement_y() as f64))
}
UseMouseCoordType::Custom(ref extractor) => extractor.extract_mouse_coords(event),
}
}
fn extract_touch_coords(&self, touch: &web_sys::Touch) -> Option<(f64, f64)> {
match self {
UseMouseCoordType::Page => Some((touch.page_x() as f64, touch.page_y() as f64)),
UseMouseCoordType::Client => Some((touch.client_x() as f64, touch.client_y() as f64)),
UseMouseCoordType::Screen => Some((touch.screen_x() as f64, touch.client_y() as f64)),
UseMouseCoordType::Movement => None,
UseMouseCoordType::Custom(ref extractor) => extractor.extract_touch_coords(touch),
}
}
}
#[derive(Clone)]
pub struct UseMouseEventExtractorDefault;
impl UseMouseEventExtractor for UseMouseEventExtractorDefault {}
/// Return type of [`use_mouse`].
pub struct UseMouseReturn {
/// X coordinate of the mouse pointer / touch
2024-01-03 22:52:59 +00:00
pub x: Signal<f64>,
2023-06-02 13:38:01 +01:00
/// Y coordinate of the mouse pointer / touch
2024-01-03 22:52:59 +00:00
pub y: Signal<f64>,
2023-06-02 13:38:01 +01:00
/// Sets the value of `x`. This does not actually move the mouse cursor.
pub set_x: WriteSignal<f64>,
/// Sets the value of `y`. This does not actually move the mouse cursor.
pub set_y: WriteSignal<f64>,
/// Identifies the source of the reported coordinates
2024-01-03 22:52:59 +00:00
pub source_type: Signal<UseMouseSourceType>,
2023-06-02 13:38:01 +01:00
}
/// Identifies the source of the reported coordinates
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum UseMouseSourceType {
/// coordinates come from mouse movement
Mouse,
/// coordinates come from touch
Touch,
/// Initially before any event has been recorded the source type is unset
Unset,
}