use dioxus::{ fullstack::{CborEncoding, WebSocketOptions, Websocket, extract::State, use_websocket}, html::{ geometry::{ElementSpace, euclid::Point2D}, input_data::MouseButton, }, logger::tracing, prelude::*, }; use serde::{Deserialize, Serialize}; #[component] pub fn MouseArea() -> Element { #[css_module("/assets/styling/mouse_area.module.css")] struct Styles; let mut last_cursor_position = use_signal::>>(|| 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 pointer_move_handler = use_callback(move |evt: Event| { if evt.held_buttons().contains(MouseButton::Primary) { evt.prevent_default(); let point = evt.element_coordinates(); let last_position = last_cursor_position.write().replace(point); if let Some(last_position) = last_position { let delta = point - last_position; spawn(async move { _ = socket .send(ClientEvent::MouseMove { dx: delta.x, dy: delta.y, }) .await; }); } } }); let pointer_down_handler = use_callback(move |evt: Event| { let point = evt.element_coordinates(); *last_cursor_position.write() = Some(point); }); let pointer_click_handler = use_callback(move |evt: Event| { spawn(async move { _ = socket.send(ClientEvent::Click).await; }); }); let key_down_handler = use_callback(move |evt: Event| { tracing::info!("Keydown"); 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| { spawn(async move { _ = socket .send(ClientEvent::KeyEvent { key: evt.key().to_string(), is_pressed: false, }) .await; }); }); rsx! { div { input { onkeydown: key_down_handler, onkeyup: key_up_handler} div { class: Styles::mouse_area, onpointermove: pointer_move_handler, onpointerdown: pointer_down_handler, onclick: pointer_click_handler } } } } #[derive(Serialize, Deserialize, Debug)] enum ClientEvent { MouseMove { dx: f64, dy: f64 }, Click, KeyEvent { key: String, is_pressed: bool }, } #[derive(Serialize, Deserialize, Debug)] enum ServerEvent { Ping, } #[expect(clippy::unused_async)] #[get("/api/mouse_move_ws", mouse_service: State)] async fn mouse_move( options: WebSocketOptions ) -> Result> { 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; }, } } })) }