2026-02-28 22:24:42 +01:00
|
|
|
use dioxus::{
|
|
|
|
|
fullstack::{CborEncoding, WebSocketOptions, Websocket, extract::State, use_websocket},
|
2026-03-02 22:06:15 +01:00
|
|
|
html::{
|
|
|
|
|
geometry::{ElementSpace, euclid::Point2D},
|
|
|
|
|
input_data::MouseButton,
|
|
|
|
|
},
|
2026-02-28 22:24:42 +01:00
|
|
|
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::<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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-02 22:06:15 +01:00
|
|
|
let pointer_move_handler = use_callback(move |evt: Event<PointerData>| {
|
|
|
|
|
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<PointerData>| {
|
|
|
|
|
let point = evt.element_coordinates();
|
|
|
|
|
*last_cursor_position.write() = Some(point);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let pointer_click_handler = use_callback(move |evt: Event<MouseData>| {
|
|
|
|
|
spawn(async move {
|
|
|
|
|
_ = socket.send(ClientEvent::Click).await;
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-02-28 22:24:42 +01:00
|
|
|
|
2026-03-02 22:06:15 +01:00
|
|
|
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;
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-02-28 22:24:42 +01:00
|
|
|
|
2026-03-03 14:11:50 +01:00
|
|
|
// 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;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
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>| {
|
2026-03-02 22:06:15 +01:00
|
|
|
spawn(async move {
|
|
|
|
|
_ = socket
|
2026-03-03 14:11:50 +01:00
|
|
|
.send(ClientEvent::TextInputDoneEvent {
|
|
|
|
|
text: input_state.replace(String::new()),
|
2026-03-02 22:06:15 +01:00
|
|
|
})
|
|
|
|
|
.await;
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-02-28 22:24:42 +01:00
|
|
|
|
2026-03-02 22:06:15 +01:00
|
|
|
rsx! {
|
|
|
|
|
div {
|
2026-03-03 14:11:50 +01:00
|
|
|
input {
|
|
|
|
|
oninput: input_handler,
|
|
|
|
|
value: input_state,
|
|
|
|
|
onkeypress: key_press_handler,
|
|
|
|
|
onfocus: input_focus_handler,
|
|
|
|
|
onblur: input_blur_handler }
|
2026-03-02 22:06:15 +01:00
|
|
|
div {
|
|
|
|
|
class: Styles::mouse_area,
|
2026-02-28 22:24:42 +01:00
|
|
|
|
2026-03-02 22:06:15 +01:00
|
|
|
onpointermove: pointer_move_handler,
|
|
|
|
|
onpointerdown: pointer_down_handler,
|
|
|
|
|
onclick: pointer_click_handler
|
2026-02-28 22:24:42 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
|
enum ClientEvent {
|
|
|
|
|
MouseMove { dx: f64, dy: f64 },
|
2026-03-02 22:06:15 +01:00
|
|
|
Click,
|
|
|
|
|
KeyEvent { key: String, is_pressed: bool },
|
2026-03-03 14:11:50 +01:00
|
|
|
TextInputStartEvent,
|
|
|
|
|
TextInputEvent { text: String },
|
|
|
|
|
TextInputDoneEvent { text: String },
|
2026-02-28 22:24:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
|
enum ServerEvent {
|
|
|
|
|
Ping,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[expect(clippy::unused_async)]
|
2026-03-02 22:06:15 +01:00
|
|
|
#[get("/api/mouse_move_ws", mouse_service: State<crate::server::input_proxy_service::InputProxyService>)]
|
2026-02-28 22:24:42 +01:00
|
|
|
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;
|
|
|
|
|
|
2026-03-02 22:06:15 +01:00
|
|
|
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;
|
|
|
|
|
},
|
2026-03-03 14:11:50 +01:00
|
|
|
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;
|
|
|
|
|
},
|
2026-03-02 22:06:15 +01:00
|
|
|
}
|
2026-02-28 22:24:42 +01:00
|
|
|
}
|
|
|
|
|
}))
|
|
|
|
|
}
|