move retrieval of sprite url from svelte to rust

This commit is contained in:
2026-03-09 19:34:57 +08:00
parent 02f1119254
commit ceaa1257bf
11 changed files with 116 additions and 50 deletions

View File

@@ -1,7 +1,7 @@
use crate::{ use crate::{
lock_r, lock_r,
models::{app_data::UserData, dolls::DollColorSchemeDto}, models::app_data::UserData,
services::presence_modules::models::ModuleMetadata, services::{presence_modules::models::ModuleMetadata, sprite},
state::{init_app_data_scoped, AppDataRefreshScope, FDOLL}, state::{init_app_data_scoped, AppDataRefreshScope, FDOLL},
}; };
@@ -29,24 +29,6 @@ pub fn get_modules() -> Result<Vec<ModuleMetadata>, String> {
#[tauri::command] #[tauri::command]
#[specta::specta] #[specta::specta]
pub fn get_active_doll_color_scheme() -> Result<Option<DollColorSchemeDto>, String> { pub fn get_active_doll_sprite_base64() -> Result<Option<String>, String> {
let guard = lock_r!(FDOLL); sprite::get_active_doll_sprite_base64()
let active_doll_id = guard
.user_data
.user
.as_ref()
.and_then(|u| u.active_doll_id.as_deref());
match active_doll_id {
Some(active_doll_id) => {
let color_scheme = guard
.user_data
.dolls
.as_ref()
.and_then(|dolls| dolls.iter().find(|d| d.id == active_doll_id))
.map(|d| d.configuration.color_scheme.clone());
Ok(color_scheme)
}
None => Ok(None),
}
} }

View File

@@ -5,8 +5,8 @@ pub mod config;
pub mod dolls; pub mod dolls;
pub mod friends; pub mod friends;
pub mod interaction; pub mod interaction;
pub mod sprite;
pub mod petpet; pub mod petpet;
pub mod sprite;
use crate::lock_r; use crate::lock_r;
use crate::state::{init_app_data_scoped, AppDataRefreshScope, FDOLL}; use crate::state::{init_app_data_scoped, AppDataRefreshScope, FDOLL};

View File

@@ -6,7 +6,7 @@ use crate::{
}, },
}; };
use commands::app::{quit_app, restart_app, retry_connection}; use commands::app::{quit_app, restart_app, retry_connection};
use commands::app_state::{get_app_data, get_active_doll_color_scheme, refresh_app_data}; use commands::app_state::{get_active_doll_sprite_base64, get_app_data, refresh_app_data};
use commands::auth::{change_password, login, logout_and_restart, register, reset_password}; use commands::auth::{change_password, login, logout_and_restart, register, reset_password};
use commands::config::{get_client_config, open_client_config_manager, save_client_config}; use commands::config::{get_client_config, open_client_config_manager, save_client_config};
use commands::dolls::{ use commands::dolls::{
@@ -17,18 +17,18 @@ use commands::friends::{
search_users, send_friend_request, sent_friend_requests, unfriend, search_users, send_friend_request, sent_friend_requests, unfriend,
}; };
use commands::interaction::send_interaction_cmd; use commands::interaction::send_interaction_cmd;
use commands::sprite::recolor_gif_base64;
use commands::petpet::encode_pet_doll_gif_base64; use commands::petpet::encode_pet_doll_gif_base64;
use commands::sprite::recolor_gif_base64;
use specta_typescript::Typescript; use specta_typescript::Typescript;
use tauri::async_runtime; use tauri::async_runtime;
use tauri_specta::{Builder as SpectaBuilder, ErrorHandlingMode, collect_commands, collect_events}; use tauri_specta::{collect_commands, collect_events, Builder as SpectaBuilder, ErrorHandlingMode};
use crate::services::app_events::{ use crate::services::app_events::{
AppDataRefreshed, CreateDoll, CursorMoved, EditDoll, FriendActiveDollChanged, ActiveDollSpriteChanged, AppDataRefreshed, CreateDoll, CursorMoved, EditDoll,
FriendCursorPositionsUpdated, FriendDisconnected, FriendActiveDollChanged, FriendCursorPositionsUpdated, FriendDisconnected,
FriendRequestAccepted, FriendRequestDenied, FriendRequestReceived, FriendRequestAccepted, FriendRequestDenied, FriendRequestReceived, FriendUserStatusChanged,
FriendUserStatusChanged, InteractionDeliveryFailed, InteractionReceived, InteractionDeliveryFailed, InteractionReceived, SceneInteractiveChanged, SetInteractionOverlay,
SceneInteractiveChanged, SetInteractionOverlay, Unfriended, UserStatusChanged, Unfriended, UserStatusChanged,
}; };
static APP_HANDLE: std::sync::OnceLock<tauri::AppHandle<tauri::Wry>> = std::sync::OnceLock::new(); static APP_HANDLE: std::sync::OnceLock<tauri::AppHandle<tauri::Wry>> = std::sync::OnceLock::new();
@@ -65,7 +65,7 @@ pub fn run() {
.error_handling(ErrorHandlingMode::Throw) .error_handling(ErrorHandlingMode::Throw)
.commands(collect_commands![ .commands(collect_commands![
get_app_data, get_app_data,
get_active_doll_color_scheme, get_active_doll_sprite_base64,
refresh_app_data, refresh_app_data,
list_friends, list_friends,
search_users, search_users,
@@ -106,6 +106,7 @@ pub fn run() {
CursorMoved, CursorMoved,
SceneInteractiveChanged, SceneInteractiveChanged,
AppDataRefreshed, AppDataRefreshed,
ActiveDollSpriteChanged,
SetInteractionOverlay, SetInteractionOverlay,
EditDoll, EditDoll,
CreateDoll, CreateDoll,

View File

@@ -30,6 +30,10 @@ pub struct SceneInteractiveChanged(pub bool);
#[tauri_specta(event_name = "app-data-refreshed")] #[tauri_specta(event_name = "app-data-refreshed")]
pub struct AppDataRefreshed(pub UserData); pub struct AppDataRefreshed(pub UserData);
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
#[tauri_specta(event_name = "active-doll-sprite-changed")]
pub struct ActiveDollSpriteChanged(pub Option<String>);
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)] #[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
#[tauri_specta(event_name = "set-interaction-overlay")] #[tauri_specta(event_name = "set-interaction-overlay")]
pub struct SetInteractionOverlay(pub bool); pub struct SetInteractionOverlay(pub bool);

View File

@@ -15,6 +15,7 @@ pub mod interaction;
pub mod petpet; pub mod petpet;
pub mod presence_modules; pub mod presence_modules;
pub mod scene; pub mod scene;
pub mod sprite;
pub mod sprite_recolor; pub mod sprite_recolor;
pub mod welcome; pub mod welcome;
pub mod ws; pub mod ws;

View File

@@ -0,0 +1,33 @@
use crate::{lock_r, models::dolls::DollDto, state::FDOLL};
const APPLY_TEXTURE: bool = true;
pub fn get_active_doll() -> Option<DollDto> {
let guard = lock_r!(FDOLL);
let active_doll_id = guard
.user_data
.user
.as_ref()
.and_then(|user| user.active_doll_id.as_deref())?;
guard
.user_data
.dolls
.as_ref()
.and_then(|dolls| dolls.iter().find(|doll| doll.id == active_doll_id))
.cloned()
}
pub fn get_active_doll_sprite_base64() -> Result<Option<String>, String> {
get_active_doll()
.map(|doll| {
let color_scheme = doll.configuration.color_scheme;
super::sprite_recolor::recolor_gif_base64(
&color_scheme.body,
&color_scheme.outline,
APPLY_TEXTURE,
)
.map_err(|err| err.to_string())
})
.transpose()
}

View File

@@ -1,12 +1,15 @@
use crate::{ use crate::{
get_app_handle, lock_r, lock_w, get_app_handle, lock_r, lock_w,
remotes::{dolls::DollsRemote, friends::FriendRemote, user::UserRemote}, remotes::{dolls::DollsRemote, friends::FriendRemote, user::UserRemote},
services::{app_events::AppDataRefreshed, friend_cursor}, services::{
app_events::{ActiveDollSpriteChanged, AppDataRefreshed},
friend_cursor, sprite,
},
state::FDOLL, state::FDOLL,
}; };
use std::{collections::HashSet, sync::LazyLock}; use std::{collections::HashSet, sync::LazyLock};
use tokio::sync::Mutex;
use tauri_specta::Event as _; use tauri_specta::Event as _;
use tokio::sync::Mutex;
use tracing::{info, warn}; use tracing::{info, warn};
pub fn update_display_dimensions_for_scene_state() { pub fn update_display_dimensions_for_scene_state() {
@@ -227,6 +230,26 @@ pub async fn init_app_data_scoped(scope: AppDataRefreshScope) {
.kind(MessageDialogKind::Error) .kind(MessageDialogKind::Error)
.show(|_| {}); .show(|_| {});
} }
if matches!(
scope,
AppDataRefreshScope::All
| AppDataRefreshScope::User
| AppDataRefreshScope::Dolls
) {
match sprite::get_active_doll_sprite_base64() {
Ok(sprite_b64) => {
if let Err(e) =
ActiveDollSpriteChanged(sprite_b64).emit(get_app_handle())
{
warn!("Failed to emit active-doll-sprite-changed event: {}", e);
}
}
Err(e) => {
warn!("Failed to generate active doll sprite: {}", e);
}
}
}
} }
Ok(()) Ok(())

View File

@@ -0,0 +1,25 @@
import { writable } from "svelte/store";
import onekoGif from "../assets/oneko/oneko.gif";
import { commands, events } from "$lib/bindings";
import { createEventSource } from "./listener-utils";
export const activeDollSpriteUrl = writable(onekoGif);
function toSpriteUrl(spriteBase64: string | null): string {
return spriteBase64 ? `data:image/gif;base64,${spriteBase64}` : onekoGif;
}
export const {
start: startActiveDollSprite,
stop: stopActiveDollSprite,
} = createEventSource(async (addEventListener) => {
activeDollSpriteUrl.set(
toSpriteUrl(await commands.getActiveDollSpriteBase64()),
);
addEventListener(
await events.activeDollSpriteChanged.listen((event) => {
activeDollSpriteUrl.set(toSpriteUrl(event.payload));
}),
);
});

View File

@@ -8,8 +8,8 @@ export const commands = {
async getAppData() : Promise<UserData> { async getAppData() : Promise<UserData> {
return await TAURI_INVOKE("get_app_data"); return await TAURI_INVOKE("get_app_data");
}, },
async getActiveDollColorScheme() : Promise<DollColorSchemeDto | null> { async getActiveDollSpriteBase64() : Promise<string | null> {
return await TAURI_INVOKE("get_active_doll_color_scheme"); return await TAURI_INVOKE("get_active_doll_sprite_base64");
}, },
async refreshAppData() : Promise<UserData> { async refreshAppData() : Promise<UserData> {
return await TAURI_INVOKE("refresh_app_data"); return await TAURI_INVOKE("refresh_app_data");
@@ -128,6 +128,7 @@ async getModules() : Promise<ModuleMetadata[]> {
export const events = __makeEvents__<{ export const events = __makeEvents__<{
activeDollSpriteChanged: ActiveDollSpriteChanged,
appDataRefreshed: AppDataRefreshed, appDataRefreshed: AppDataRefreshed,
createDoll: CreateDoll, createDoll: CreateDoll,
cursorMoved: CursorMoved, cursorMoved: CursorMoved,
@@ -146,6 +147,7 @@ setInteractionOverlay: SetInteractionOverlay,
unfriended: Unfriended, unfriended: Unfriended,
userStatusChanged: UserStatusChanged userStatusChanged: UserStatusChanged
}>({ }>({
activeDollSpriteChanged: "active-doll-sprite-changed",
appDataRefreshed: "app-data-refreshed", appDataRefreshed: "app-data-refreshed",
createDoll: "create-doll", createDoll: "create-doll",
cursorMoved: "cursor-moved", cursorMoved: "cursor-moved",
@@ -171,6 +173,7 @@ userStatusChanged: "user-status-changed"
/** user-defined types **/ /** user-defined types **/
export type ActiveDollSpriteChanged = string | null
export type AppConfig = { api_base_url: string | null } export type AppConfig = { api_base_url: string | null }
export type AppDataRefreshed = UserData export type AppDataRefreshed = UserData
export type CreateDoll = null export type CreateDoll = null

View File

@@ -6,6 +6,10 @@
startFriendCursorTracking, startFriendCursorTracking,
stopFriendCursorTracking, stopFriendCursorTracking,
} from "../events/friend-cursor"; } from "../events/friend-cursor";
import {
startActiveDollSprite,
stopActiveDollSprite,
} from "../events/active-doll-sprite";
import { startAppData } from "../events/app-data"; import { startAppData } from "../events/app-data";
import { startInteraction, stopInteraction } from "../events/interaction"; import { startInteraction, stopInteraction } from "../events/interaction";
import { import {
@@ -19,6 +23,7 @@
onMount(async () => { onMount(async () => {
try { try {
await startAppData(); await startAppData();
await startActiveDollSprite();
await startCursorTracking(); await startCursorTracking();
await startFriendCursorTracking(); await startFriendCursorTracking();
await startSceneInteractive(); await startSceneInteractive();
@@ -32,6 +37,7 @@
onDestroy(() => { onDestroy(() => {
stopCursorTracking(); stopCursorTracking();
stopFriendCursorTracking(); stopFriendCursorTracking();
stopActiveDollSprite();
stopSceneInteractive(); stopSceneInteractive();
stopInteraction(); stopInteraction();
stopUserStatus(); stopUserStatus();

View File

@@ -2,27 +2,15 @@
import { cursorPositionOnScreen } from "../../events/cursor"; import { cursorPositionOnScreen } from "../../events/cursor";
import { friendsCursorPositions } from "../../events/friend-cursor"; import { friendsCursorPositions } from "../../events/friend-cursor";
import { appData } from "../../events/app-data"; import { appData } from "../../events/app-data";
import { activeDollSpriteUrl } from "../../events/active-doll-sprite";
import { sceneInteractive } from "../../events/scene-interactive"; import { sceneInteractive } from "../../events/scene-interactive";
import { import {
friendsPresenceStates, friendsPresenceStates,
currentPresenceState, currentPresenceState,
} from "../../events/user-status"; } from "../../events/user-status";
import { commands } from "$lib/bindings"; import { commands } from "$lib/bindings";
import { getSpriteSheetUrl } from "$lib/utils/sprite-utils";
import DebugBar from "./components/debug-bar.svelte"; import DebugBar from "./components/debug-bar.svelte";
import Neko from "./components/neko/neko.svelte"; import Neko from "./components/neko/neko.svelte";
let spriteUrl = $state("");
$effect(() => {
$appData;
if (!$appData) return;
commands.getActiveDollColorScheme().then((colorScheme) => {
getSpriteSheetUrl(colorScheme ?? undefined).then((url) => {
spriteUrl = url;
});
});
});
</script> </script>
<div class="w-svw h-svh p-4 relative overflow-hidden"> <div class="w-svw h-svh p-4 relative overflow-hidden">
@@ -36,7 +24,7 @@
<Neko <Neko
targetX={$cursorPositionOnScreen.raw.x} targetX={$cursorPositionOnScreen.raw.x}
targetY={$cursorPositionOnScreen.raw.y} targetY={$cursorPositionOnScreen.raw.y}
{spriteUrl} spriteUrl={$activeDollSpriteUrl}
/> />
<div id="debug-bar"> <div id="debug-bar">
<DebugBar <DebugBar