diff --git a/src-tauri/src/services/cursor.rs b/src-tauri/src/services/cursor.rs index 2df841e..f9ad2a1 100644 --- a/src-tauri/src/services/cursor.rs +++ b/src-tauri/src/services/cursor.rs @@ -1,6 +1,6 @@ use device_query::{DeviceEvents, DeviceEventsHandler}; use once_cell::sync::OnceCell; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use std::time::Duration; @@ -10,7 +10,7 @@ use ts_rs::TS; use crate::{get_app_handle, lock_r, state::FDOLL}; -#[derive(Clone, Serialize, TS)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] pub struct CursorPosition { @@ -18,7 +18,7 @@ pub struct CursorPosition { pub y: i32, } -#[derive(Clone, Serialize, TS)] +#[derive(Debug, Clone, Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] pub struct CursorPositions { diff --git a/src-tauri/src/services/ws.rs b/src-tauri/src/services/ws.rs index df261bf..ddf8f2d 100644 --- a/src-tauri/src/services/ws.rs +++ b/src-tauri/src/services/ws.rs @@ -5,12 +5,28 @@ use tracing::{error, info}; use crate::{ get_app_handle, lock_r, lock_w, models::app_config::AppConfig, - services::cursor::CursorPosition, state::FDOLL, + services::cursor::{grid_to_absolute_position, CursorPosition, CursorPositions}, + state::FDOLL, }; +use serde::{Deserialize, Serialize}; #[allow(non_camel_case_types)] // pretend to be a const like in js pub struct WS_EVENT; +#[derive(Debug, Deserialize)] +struct IncomingFriendCursorPayload { + #[serde(rename = "userId")] + user_id: String, + position: CursorPosition, +} + +#[derive(Clone, Serialize)] +#[serde(rename_all = "camelCase")] +struct OutgoingFriendCursorPayload { + user_id: String, + position: CursorPositions, +} + impl WS_EVENT { pub const CURSOR_REPORT_POSITION: &str = "cursor-report-position"; pub const FRIEND_REQUEST_RECEIVED: &str = "friend-request-received"; @@ -69,10 +85,38 @@ fn on_unfriended(payload: Payload, _socket: RawClient) { fn on_friend_cursor_position(payload: Payload, _socket: RawClient) { match payload { - Payload::Text(str) => { - get_app_handle() - .emit(WS_EVENT::FRIEND_CURSOR_POSITION, str) - .unwrap(); + Payload::Text(values) => { + // values is Vec + if let Some(first_value) = values.first() { + let incoming_data: Result = serde_json::from_value(first_value.clone()); + + match incoming_data { + Ok(friend_data) => { + // We received grid coordinates (mapped) + let mapped_pos = &friend_data.position; + + // Convert grid coordinates back to absolute screen coordinates (raw) + let raw_pos = grid_to_absolute_position(mapped_pos); + + let outgoing_payload = OutgoingFriendCursorPayload { + user_id: friend_data.user_id.clone(), + position: CursorPositions { + raw: raw_pos, + mapped: mapped_pos.clone(), + }, + }; + + get_app_handle() + .emit(WS_EVENT::FRIEND_CURSOR_POSITION, outgoing_payload) + .unwrap(); + } + Err(e) => { + error!("Failed to parse friend cursor position data: {}", e); + } + } + } else { + error!("Received empty text payload for friend cursor position"); + } } _ => error!("Received unexpected payload format for friend cursor position"), } diff --git a/src/events/cursor.ts b/src/events/cursor.ts index 6145d11..2fc04df 100644 --- a/src/events/cursor.ts +++ b/src/events/cursor.ts @@ -11,20 +11,20 @@ export let cursorPositionOnScreen = writable({ export type FriendCursorPosition = { userId: string; - position: CursorPosition; + position: CursorPositions; }; -// Map of userId -> { position: CursorPosition, lastUpdated: number } +// Map of userId -> { position: CursorPositions, lastUpdated: number } // We store the timestamp to detect stale cursors type FriendCursorData = { - position: CursorPosition; + position: CursorPositions; lastUpdated: number; }; // The exported store will only expose the position part to consumers, // but internally we manage the full data. // Actually, it's easier if we just export the positions and manage state internally. -export let friendsCursorPositions = writable>( +export let friendsCursorPositions = writable>( {}, ); @@ -59,27 +59,11 @@ export async function initCursorTracking() { ); // Listen to friend cursor position events - unlistenFriendCursor = await listen<[FriendCursorPosition]>( + unlistenFriendCursor = await listen( "friend-cursor-position", (event) => { - // payload might be a string JSON if it comes directly from rust_socketio Payload::Text - let payload = event.payload; - - if (typeof payload === "string") { - try { - payload = JSON.parse(payload); - } catch (e) { - console.error( - "[Cursor] Failed to parse friend cursor position payload:", - e, - ); - return; - } - } - - // Rust socket.io client returns payload as an array of arguments - // Since we only send one argument { userId, position }, it's the first element - const data = Array.isArray(payload) ? payload[0] : payload; + // We now receive a clean object from Rust + const data = event.payload; // Update internal state with timestamp friendCursorState[data.userId] = { diff --git a/src/routes/scene/+page.svelte b/src/routes/scene/+page.svelte index 5a42c58..929a486 100644 --- a/src/routes/scene/+page.svelte +++ b/src/routes/scene/+page.svelte @@ -34,9 +34,18 @@

Friends Online

{#each Object.entries($friendsCursorPositions) as [userId, position]} -
+
{getFriendName(userId)} - ({position.x}, {position.y}) +
+ + Raw: ({position.raw.x}, {position.raw.y}) + + + Mapped: ({position.mapped.x}, {position.mapped.y}) + +
{/each}