add cursor flinging
This commit is contained in:
parent
5f89601ef2
commit
d0a458b450
16 changed files with 511 additions and 1692 deletions
|
|
@ -1,5 +1,10 @@
|
|||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
ops::Sub,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use dioxus::{
|
||||
fullstack::{CborEncoding, WebSocketOptions, Websocket, extract::State, use_websocket},
|
||||
html::{
|
||||
geometry::{ElementSpace, euclid::Point2D},
|
||||
input_data::MouseButton,
|
||||
|
|
@ -7,184 +12,160 @@ use dioxus::{
|
|||
logger::tracing,
|
||||
prelude::*,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
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;
|
||||
|
||||
pub struct PointerRegistryData {
|
||||
initial_position: Point2D<f64, ElementSpace>,
|
||||
|
||||
last_positions: MouseFilterBuffer,
|
||||
}
|
||||
|
||||
pub struct FlingerData {
|
||||
velocity: Vector2D<f64, ElementSpace>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PointerRegistry {
|
||||
pointers: HashMap<i32, PointerRegistryData>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FlingerRegistry {
|
||||
flinger: Option<FlingerData>,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn MouseArea() -> Element {
|
||||
pub fn MouseArea(onevent: EventHandler<ClientEvent>) -> Element {
|
||||
#[css_module("/assets/styling/mouse_area.module.css")]
|
||||
struct Styles;
|
||||
|
||||
let mut last_cursor_position = use_signal::<Option<Point2D<f64, ElementSpace>>>(|| None);
|
||||
|
||||
let mut socket = use_websocket(move || mouse_move(WebSocketOptions::new()));
|
||||
|
||||
use_future(move || async move {
|
||||
loop {
|
||||
// Wait for the socket to connect
|
||||
_ = socket.connect().await;
|
||||
|
||||
// Loop poll with recv. Throws an error when the connection closes, making it possible
|
||||
// to run code before the socket re-connects when the name input changes
|
||||
while let Ok(message) = socket.recv().await {
|
||||
tracing::info!("Received message: {:?}", message);
|
||||
}
|
||||
}
|
||||
});
|
||||
let mut registry = use_signal::<PointerRegistry>(PointerRegistry::default);
|
||||
let mut flingers = use_signal::<FlingerRegistry>(FlingerRegistry::default);
|
||||
|
||||
let pointer_move_handler = use_callback(move |evt: Event<PointerData>| {
|
||||
if evt.held_buttons().contains(MouseButton::Primary) {
|
||||
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 = last_cursor_position.write().replace(point);
|
||||
let last_position = data.last_positions.back();
|
||||
let delta = point - last_position.position;
|
||||
|
||||
if let Some(last_position) = last_position {
|
||||
let delta = point - last_position;
|
||||
data.last_positions
|
||||
.push(point, wasmtimer::std::SystemTime::now());
|
||||
|
||||
spawn(async move {
|
||||
_ = socket
|
||||
.send(ClientEvent::MouseMove {
|
||||
dx: delta.x,
|
||||
dy: delta.y,
|
||||
})
|
||||
.await;
|
||||
if registry.pointers.len() == 1 {
|
||||
onevent.call(ClientEvent::MouseMove {
|
||||
dx: delta.x,
|
||||
dy: delta.y,
|
||||
});
|
||||
} 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<PointerData>| {
|
||||
//If any pointer is down, we cancel the flingers
|
||||
flingers.write().flinger.take();
|
||||
|
||||
let point = evt.element_coordinates();
|
||||
*last_cursor_position.write() = Some(point);
|
||||
|
||||
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<PointerData>| {
|
||||
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());
|
||||
|
||||
let pointer_click_handler = use_callback(move |evt: Event<MouseData>| {
|
||||
spawn(async move {
|
||||
_ = socket.send(ClientEvent::Click).await;
|
||||
});
|
||||
});
|
||||
|
||||
let key_down_handler = use_callback(move |evt: Event<KeyboardData>| {
|
||||
spawn(async move {
|
||||
_ = socket
|
||||
.send(ClientEvent::KeyEvent {
|
||||
key: evt.key().to_string(),
|
||||
is_pressed: true,
|
||||
})
|
||||
.await;
|
||||
});
|
||||
});
|
||||
|
||||
// let key_up_handler = use_callback(move |evt: Event<KeyboardData>| {
|
||||
// spawn(async move {
|
||||
// _ = socket
|
||||
// .send(ClientEvent::KeyEvent {
|
||||
// key: evt.key().to_string(),
|
||||
// is_pressed: false,
|
||||
// })
|
||||
// .await;
|
||||
// });
|
||||
// });
|
||||
|
||||
let mut input_state = use_signal(String::new);
|
||||
let input_handler = use_callback(move |evt: Event<FormData>| {
|
||||
let v = evt.value();
|
||||
input_state.set(v.clone());
|
||||
|
||||
spawn(async move {
|
||||
_ = socket.send(ClientEvent::TextInputEvent { text: v }).await;
|
||||
});
|
||||
});
|
||||
let key_press_handler = use_callback(move |evt: Event<KeyboardData>| {
|
||||
if evt.key() == Key::Enter {
|
||||
spawn(async move {
|
||||
_ = socket
|
||||
.send(ClientEvent::TextInputDoneEvent {
|
||||
text: input_state.replace(String::new()),
|
||||
})
|
||||
.await;
|
||||
});
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
let input_focus_handler = use_callback(move |evt: Event<FocusData>| {
|
||||
input_state.set(String::new());
|
||||
spawn(async move {
|
||||
_ = socket.send(ClientEvent::TextInputStartEvent).await;
|
||||
});
|
||||
});
|
||||
|
||||
let input_blur_handler = use_callback(move |evt: Event<FocusData>| {
|
||||
spawn(async move {
|
||||
_ = socket
|
||||
.send(ClientEvent::TextInputDoneEvent {
|
||||
text: input_state.replace(String::new()),
|
||||
})
|
||||
.await;
|
||||
});
|
||||
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,
|
||||
dy: flinger.velocity.y * delta_seconds,
|
||||
});
|
||||
|
||||
//tracing::info!("Fling: {:?}", flinger.velocity);
|
||||
Some(FlingerData {
|
||||
velocity: flinger.velocity * FLING_DAMPENING,
|
||||
})
|
||||
}
|
||||
});
|
||||
flinger.flinger = new_flinger;
|
||||
}
|
||||
});
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
input {
|
||||
oninput: input_handler,
|
||||
value: input_state,
|
||||
onkeypress: key_press_handler,
|
||||
onfocus: input_focus_handler,
|
||||
onblur: input_blur_handler }
|
||||
div {
|
||||
class: Styles::mouse_area,
|
||||
class: Styles::mouse_area,
|
||||
|
||||
onpointermove: pointer_move_handler,
|
||||
onpointerdown: pointer_down_handler,
|
||||
onclick: pointer_click_handler
|
||||
}
|
||||
onpointermove: pointer_move_handler,
|
||||
onpointerdown: pointer_down_handler,
|
||||
onpointerup: pointer_up_handler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
enum ClientEvent {
|
||||
MouseMove { dx: f64, dy: f64 },
|
||||
Click,
|
||||
KeyEvent { key: String, is_pressed: bool },
|
||||
TextInputStartEvent,
|
||||
TextInputEvent { text: String },
|
||||
TextInputDoneEvent { text: String },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
enum ServerEvent {
|
||||
Ping,
|
||||
}
|
||||
|
||||
#[expect(clippy::unused_async)]
|
||||
#[get("/api/mouse_move_ws", mouse_service: State<crate::server::input_proxy_service::InputProxyService>)]
|
||||
async fn mouse_move(
|
||||
options: WebSocketOptions
|
||||
) -> Result<Websocket<ClientEvent, ServerEvent, CborEncoding>> {
|
||||
Ok(options.on_upgrade(move |mut socket| async move {
|
||||
_ = socket.send(ServerEvent::Ping).await;
|
||||
|
||||
while let Ok(event) = socket.recv().await {
|
||||
match event {
|
||||
ClientEvent::MouseMove { dx, dy } => {
|
||||
mouse_service.move_mouse(dx, dy).await;
|
||||
},
|
||||
ClientEvent::Click => {
|
||||
mouse_service.click().await;
|
||||
},
|
||||
ClientEvent::KeyEvent { key, is_pressed } => {
|
||||
mouse_service.key_event(key, is_pressed).await;
|
||||
},
|
||||
ClientEvent::TextInputEvent { text } => {
|
||||
mouse_service.text_input(text).await;
|
||||
},
|
||||
ClientEvent::TextInputStartEvent => {
|
||||
mouse_service.text_input_start().await;
|
||||
},
|
||||
ClientEvent::TextInputDoneEvent { text } => {
|
||||
mouse_service.text_input_end(text).await;
|
||||
},
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue