use std::{ collections::{HashMap, VecDeque}, ops::Sub, time::Duration, }; use dioxus::{ html::{ geometry::{ElementSpace, euclid::Point2D}, input_data::MouseButton, }, logger::tracing, prelude::*, }; use dioxus_html::geometry::euclid::Vector2D; use crate::{components::controls::ClientEvent, utils::mouse_filter_buffer::MouseFilterBuffer}; const FLING_START_THRESHOLD_VELOCITY: f64 = 500.0; const FLING_STOP_THRESHOLD_VELOCITY: f64 = 100.0; const FLING_DAMPENING: f64 = 0.98; const CURSOR_SPEED_MULTIPLIER: f64 = 1.5; pub struct PointerRegistryData { initial_position: Point2D, last_positions: MouseFilterBuffer, } pub struct FlingerData { velocity: Vector2D, } #[derive(Default)] pub struct PointerRegistry { pointers: HashMap, } #[derive(Default)] pub struct FlingerRegistry { flinger: Option, } #[component] pub fn MouseArea(onevent: EventHandler) -> Element { #[css_module("/assets/styling/mouse_area.module.css")] struct Styles; let mut registry = use_signal::(PointerRegistry::default); let mut flingers = use_signal::(FlingerRegistry::default); let pointer_move_handler = use_callback(move |evt: Event| { let mut registry = registry.write(); if let Some(data) = registry.pointers.get_mut(&evt.pointer_id()) { evt.prevent_default(); let point = evt.element_coordinates(); let last_position = data.last_positions.back(); let delta = point - last_position.position; data.last_positions .push(point, wasmtimer::std::SystemTime::now()); if registry.pointers.len() == 1 { onevent.call(ClientEvent::MouseMove { dx: delta.x * CURSOR_SPEED_MULTIPLIER, dy: delta.y * CURSOR_SPEED_MULTIPLIER, }); } else if registry.pointers.len() == 2 { onevent.call(ClientEvent::MouseScroll { dx: -delta.x, dy: -delta.y, }); } } }); let pointer_down_handler = use_callback(move |evt: Event| { //If any pointer is down, we cancel the flingers flingers.write().flinger.take(); let point = evt.element_coordinates(); registry.write().pointers.insert( evt.pointer_id(), PointerRegistryData { last_positions: MouseFilterBuffer::new( 10, point, wasmtimer::std::SystemTime::now(), ), initial_position: point, }, ); }); let pointer_up_handler = use_callback(move |evt: Event| { let point = evt.element_coordinates(); let mut registry = registry.write(); let data = registry.pointers.remove(&evt.pointer_id()); if let Some(data) = data { let distance_moved = data.initial_position - point; let release_velocity = data.last_positions.average_velocity_since( wasmtimer::std::SystemTime::now().sub(Duration::from_millis(100)), ); tracing::info!("Release Velocity: {:?}", release_velocity.length()); if distance_moved.length() <= 1.0 { match registry.pointers.len() { 0 => { onevent.call(ClientEvent::Click { button: MouseButton::Primary, }); }, 1 => { onevent.call(ClientEvent::Click { button: MouseButton::Secondary, }); }, _ => {}, } } else if release_velocity.length() > FLING_START_THRESHOLD_VELOCITY { //We only fling if there are no other pointers if registry.pointers.is_empty() { flingers.write().flinger = Some(FlingerData { velocity: release_velocity, }); } } } }); use_future(move || async move { let mut last_frame_time = wasmtimer::std::SystemTime::now(); loop { wasmtimer::tokio::sleep(Duration::from_millis(16)).await; let new_frame_time = wasmtimer::std::SystemTime::now(); let delta_seconds = new_frame_time .duration_since(last_frame_time) .unwrap() .as_secs_f64(); last_frame_time = new_frame_time; let mut flinger = flingers.write(); let new_flinger = flinger.flinger.as_ref().and_then(|flinger| { if flinger.velocity.length() < FLING_STOP_THRESHOLD_VELOCITY { None } else { onevent.call(ClientEvent::MouseMove { dx: flinger.velocity.x * delta_seconds * CURSOR_SPEED_MULTIPLIER, dy: flinger.velocity.y * delta_seconds * CURSOR_SPEED_MULTIPLIER, }); //tracing::info!("Fling: {:?}", flinger.velocity); Some(FlingerData { velocity: flinger.velocity * FLING_DAMPENING, }) } }); flinger.flinger = new_flinger; } }); rsx! { div { class: Styles::mouse_area, onpointermove: pointer_move_handler, onpointerdown: pointer_down_handler, onpointerup: pointer_up_handler, } } }