pet menu persistence
This commit is contained in:
@@ -14,7 +14,7 @@ use crate::{
|
||||
},
|
||||
cursor::start_cursor_tracking,
|
||||
doll_editor::open_doll_editor_window,
|
||||
scene::{open_splash_window, set_scene_interactive},
|
||||
scene::{open_splash_window, set_pet_menu_state, set_scene_interactive},
|
||||
},
|
||||
state::{init_app_data, init_app_data_scoped, AppDataRefreshScope, FDOLL},
|
||||
};
|
||||
@@ -425,6 +425,7 @@ pub fn run() {
|
||||
open_client_config_manager,
|
||||
open_doll_editor_window,
|
||||
set_scene_interactive,
|
||||
set_pet_menu_state,
|
||||
start_auth_flow,
|
||||
logout_and_restart
|
||||
])
|
||||
|
||||
@@ -16,6 +16,16 @@ pub static SPLASH_WINDOW_LABEL: &str = "splash";
|
||||
static SCENE_INTERACTIVE_STATE: OnceCell<Arc<AtomicBool>> = OnceCell::new();
|
||||
static MODIFIER_LISTENER_INIT: OnceCell<()> = OnceCell::new();
|
||||
|
||||
// New: Track which pets have open menus
|
||||
static OPEN_PET_MENUS: OnceCell<Arc<std::sync::Mutex<std::collections::HashSet<String>>>> =
|
||||
OnceCell::new();
|
||||
|
||||
fn get_open_pet_menus() -> Arc<std::sync::Mutex<std::collections::HashSet<String>>> {
|
||||
OPEN_PET_MENUS
|
||||
.get_or_init(|| Arc::new(std::sync::Mutex::new(std::collections::HashSet::new())))
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn scene_interactive_state() -> Arc<AtomicBool> {
|
||||
SCENE_INTERACTIVE_STATE
|
||||
.get_or_init(|| Arc::new(AtomicBool::new(false)))
|
||||
@@ -25,6 +35,14 @@ fn scene_interactive_state() -> Arc<AtomicBool> {
|
||||
pub fn update_scene_interactive(interactive: bool) {
|
||||
let app_handle = get_app_handle();
|
||||
|
||||
// If we are forcing interactive to false (e.g. background click), clear any open menus
|
||||
// This prevents the loop from immediately re-enabling it if the frontend hasn't updated yet
|
||||
if !interactive {
|
||||
if let Ok(mut menus) = get_open_pet_menus().lock() {
|
||||
menus.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(window) = app_handle.get_window(SCENE_WINDOW_LABEL) {
|
||||
if let Err(e) = window.set_ignore_cursor_events(!interactive) {
|
||||
error!("Failed to toggle scene cursor events: {}", e);
|
||||
@@ -43,6 +61,33 @@ pub fn set_scene_interactive(interactive: bool) {
|
||||
update_scene_interactive(interactive);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_pet_menu_state(id: String, open: bool) {
|
||||
let menus_arc = get_open_pet_menus();
|
||||
let should_update = {
|
||||
if let Ok(mut menus) = menus_arc.lock() {
|
||||
if open {
|
||||
menus.insert(id);
|
||||
} else {
|
||||
menus.remove(&id);
|
||||
}
|
||||
!menus.is_empty()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
// After updating state, re-evaluate interactivity immediately
|
||||
// We don't have direct access to key state here easily without recalculating everything,
|
||||
// but the loop will pick it up shortly.
|
||||
// HOWEVER, if we just closed the last menu and keys aren't held, we might want to ensure it closes fast.
|
||||
// For now, let the loop handle it to avoid race conditions with key states.
|
||||
// But if we just OPENED a menu, we definitely want to ensure interactive is TRUE.
|
||||
if should_update {
|
||||
update_scene_interactive(true);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[link(name = "ApplicationServices", kind = "framework")]
|
||||
extern "C" {
|
||||
@@ -96,11 +141,22 @@ fn start_scene_modifier_listener() {
|
||||
loop {
|
||||
let keys = device_state.get_keys();
|
||||
// Check for Alt key (Option on Mac)
|
||||
let interactive = (keys.contains(&Keycode::LAlt) || keys.contains(&Keycode::RAlt)) || keys.contains(&Keycode::Command);
|
||||
let keys_interactive = (keys.contains(&Keycode::LAlt) || keys.contains(&Keycode::RAlt)) || keys.contains(&Keycode::Command);
|
||||
|
||||
// Check if any pet menus are open
|
||||
let menus_open = {
|
||||
if let Ok(menus) = get_open_pet_menus().lock() {
|
||||
!menus.is_empty()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
let interactive = keys_interactive || menus_open;
|
||||
|
||||
if interactive != last_interactive {
|
||||
// State changed
|
||||
info!("Key down state chanegd!");
|
||||
info!("Interactive state changed to: {}", interactive);
|
||||
let previous = state.swap(interactive, Ordering::SeqCst);
|
||||
if previous != interactive {
|
||||
update_scene_interactive(interactive);
|
||||
|
||||
@@ -88,6 +88,7 @@
|
||||
{@const config = getFriendDollConfig(userId)}
|
||||
{#if config}
|
||||
<DesktopPet
|
||||
id={userId}
|
||||
targetX={position.mapped.x * innerWidth}
|
||||
targetY={position.mapped.y * innerHeight}
|
||||
name={getFriendName(userId)}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from "svelte";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { usePetState } from "$lib/composables/usePetState";
|
||||
import { getSpriteSheetUrl } from "$lib/utils/sprite-utils";
|
||||
import PetSprite from "$lib/components/PetSprite.svelte";
|
||||
import onekoGif from "../../assets/oneko/oneko.gif";
|
||||
|
||||
export let id = "";
|
||||
export let targetX = 0;
|
||||
export let targetY = 0;
|
||||
export let name = "";
|
||||
@@ -21,9 +23,19 @@
|
||||
let lastFrameTimestamp: number;
|
||||
let spriteSheetUrl = onekoGif;
|
||||
|
||||
let isPetMenuOpen = false;
|
||||
|
||||
// Watch for color changes to regenerate sprite
|
||||
$: updateSprite(bodyColor, outlineColor);
|
||||
|
||||
$: (isInteractive, (isPetMenuOpen = false));
|
||||
|
||||
$: {
|
||||
if (id) {
|
||||
invoke("set_pet_menu_state", { id, open: isPetMenuOpen });
|
||||
}
|
||||
}
|
||||
|
||||
async function updateSprite(
|
||||
body: string | undefined,
|
||||
outline: string | undefined,
|
||||
@@ -65,23 +77,32 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<button
|
||||
onclick={() => {
|
||||
console.log("clicked on neko");
|
||||
}}
|
||||
<div
|
||||
class="desktop-pet flex flex-col items-center relative"
|
||||
style="
|
||||
transform: translate({$position.x - 16}px, {$position.y - 16}px);
|
||||
z-index: 50;
|
||||
"
|
||||
>
|
||||
<div class="hover:scale-110 active:scale-95 transition-transform">
|
||||
{#if isPetMenuOpen}
|
||||
<div
|
||||
class="absolute -translate-y-44 w-30 h-40 bg-neutral-500 rounded-md"
|
||||
role="menu"
|
||||
tabindex="0"
|
||||
aria-label="Pet Menu"
|
||||
></div>
|
||||
{/if}
|
||||
<button
|
||||
onclick={() => {
|
||||
isPetMenuOpen = !isPetMenuOpen;
|
||||
}}
|
||||
>
|
||||
<PetSprite
|
||||
{spriteSheetUrl}
|
||||
spriteX={$currentSprite.x}
|
||||
spriteY={$currentSprite.y}
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<span
|
||||
class="absolute -bottom-5 width-full text-[10px] bg-black/50 text-white px-1 rounded backdrop-blur-sm mt-1 whitespace-nowrap opacity-0 transition-opacity"
|
||||
@@ -89,7 +110,7 @@
|
||||
>
|
||||
{name}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.desktop-pet {
|
||||
|
||||
Reference in New Issue
Block a user