resolve friends' grid to absolute
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
use device_query::{DeviceEvents, DeviceEventsHandler};
|
use device_query::{DeviceEvents, DeviceEventsHandler};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
use serde::Serialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@@ -10,7 +10,7 @@ use ts_rs::TS;
|
|||||||
|
|
||||||
use crate::{get_app_handle, lock_r, state::FDOLL};
|
use crate::{get_app_handle, lock_r, state::FDOLL};
|
||||||
|
|
||||||
#[derive(Clone, Serialize, TS)]
|
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct CursorPosition {
|
pub struct CursorPosition {
|
||||||
@@ -18,7 +18,7 @@ pub struct CursorPosition {
|
|||||||
pub y: i32,
|
pub y: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, TS)]
|
#[derive(Debug, Clone, Serialize, TS)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct CursorPositions {
|
pub struct CursorPositions {
|
||||||
|
|||||||
@@ -5,12 +5,28 @@ use tracing::{error, info};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
get_app_handle, lock_r, lock_w, models::app_config::AppConfig,
|
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
|
#[allow(non_camel_case_types)] // pretend to be a const like in js
|
||||||
pub struct WS_EVENT;
|
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 {
|
impl WS_EVENT {
|
||||||
pub const CURSOR_REPORT_POSITION: &str = "cursor-report-position";
|
pub const CURSOR_REPORT_POSITION: &str = "cursor-report-position";
|
||||||
pub const FRIEND_REQUEST_RECEIVED: &str = "friend-request-received";
|
pub const FRIEND_REQUEST_RECEIVED: &str = "friend-request-received";
|
||||||
@@ -69,11 +85,39 @@ fn on_unfriended(payload: Payload, _socket: RawClient) {
|
|||||||
|
|
||||||
fn on_friend_cursor_position(payload: Payload, _socket: RawClient) {
|
fn on_friend_cursor_position(payload: Payload, _socket: RawClient) {
|
||||||
match payload {
|
match payload {
|
||||||
Payload::Text(str) => {
|
Payload::Text(values) => {
|
||||||
|
// values is Vec<serde_json::Value>
|
||||||
|
if let Some(first_value) = values.first() {
|
||||||
|
let incoming_data: Result<IncomingFriendCursorPayload, _> = 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()
|
get_app_handle()
|
||||||
.emit(WS_EVENT::FRIEND_CURSOR_POSITION, str)
|
.emit(WS_EVENT::FRIEND_CURSOR_POSITION, outgoing_payload)
|
||||||
.unwrap();
|
.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"),
|
_ => error!("Received unexpected payload format for friend cursor position"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,20 +11,20 @@ export let cursorPositionOnScreen = writable<CursorPositions>({
|
|||||||
|
|
||||||
export type FriendCursorPosition = {
|
export type FriendCursorPosition = {
|
||||||
userId: string;
|
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
|
// We store the timestamp to detect stale cursors
|
||||||
type FriendCursorData = {
|
type FriendCursorData = {
|
||||||
position: CursorPosition;
|
position: CursorPositions;
|
||||||
lastUpdated: number;
|
lastUpdated: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
// The exported store will only expose the position part to consumers,
|
// The exported store will only expose the position part to consumers,
|
||||||
// but internally we manage the full data.
|
// but internally we manage the full data.
|
||||||
// Actually, it's easier if we just export the positions and manage state internally.
|
// Actually, it's easier if we just export the positions and manage state internally.
|
||||||
export let friendsCursorPositions = writable<Record<string, CursorPosition>>(
|
export let friendsCursorPositions = writable<Record<string, CursorPositions>>(
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -59,27 +59,11 @@ export async function initCursorTracking() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Listen to friend cursor position events
|
// Listen to friend cursor position events
|
||||||
unlistenFriendCursor = await listen<[FriendCursorPosition]>(
|
unlistenFriendCursor = await listen<FriendCursorPosition>(
|
||||||
"friend-cursor-position",
|
"friend-cursor-position",
|
||||||
(event) => {
|
(event) => {
|
||||||
// payload might be a string JSON if it comes directly from rust_socketio Payload::Text
|
// We now receive a clean object from Rust
|
||||||
let payload = event.payload;
|
const data = 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;
|
|
||||||
|
|
||||||
// Update internal state with timestamp
|
// Update internal state with timestamp
|
||||||
friendCursorState[data.userId] = {
|
friendCursorState[data.userId] = {
|
||||||
|
|||||||
@@ -34,9 +34,18 @@
|
|||||||
<p class="text-sm font-semibold opacity-75">Friends Online</p>
|
<p class="text-sm font-semibold opacity-75">Friends Online</p>
|
||||||
<div>
|
<div>
|
||||||
{#each Object.entries($friendsCursorPositions) as [userId, position]}
|
{#each Object.entries($friendsCursorPositions) as [userId, position]}
|
||||||
<div class="bg-base-200/50 p-2 rounded text-xs text-left">
|
<div
|
||||||
|
class="bg-base-200/50 p-2 rounded text-xs text-left flex gap-2 flex-col"
|
||||||
|
>
|
||||||
<span class="font-bold">{getFriendName(userId)}</span>
|
<span class="font-bold">{getFriendName(userId)}</span>
|
||||||
<span class="font-mono ml-2">({position.x}, {position.y})</span>
|
<div class="flex flex-col font-mono">
|
||||||
|
<span>
|
||||||
|
Raw: ({position.raw.x}, {position.raw.y})
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
Mapped: ({position.mapped.x}, {position.mapped.y})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user