pet menu persistence
This commit is contained in:
@@ -14,7 +14,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
cursor::start_cursor_tracking,
|
cursor::start_cursor_tracking,
|
||||||
doll_editor::open_doll_editor_window,
|
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},
|
state::{init_app_data, init_app_data_scoped, AppDataRefreshScope, FDOLL},
|
||||||
};
|
};
|
||||||
@@ -425,6 +425,7 @@ pub fn run() {
|
|||||||
open_client_config_manager,
|
open_client_config_manager,
|
||||||
open_doll_editor_window,
|
open_doll_editor_window,
|
||||||
set_scene_interactive,
|
set_scene_interactive,
|
||||||
|
set_pet_menu_state,
|
||||||
start_auth_flow,
|
start_auth_flow,
|
||||||
logout_and_restart
|
logout_and_restart
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -16,6 +16,16 @@ pub static SPLASH_WINDOW_LABEL: &str = "splash";
|
|||||||
static SCENE_INTERACTIVE_STATE: OnceCell<Arc<AtomicBool>> = OnceCell::new();
|
static SCENE_INTERACTIVE_STATE: OnceCell<Arc<AtomicBool>> = OnceCell::new();
|
||||||
static MODIFIER_LISTENER_INIT: OnceCell<()> = 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> {
|
fn scene_interactive_state() -> Arc<AtomicBool> {
|
||||||
SCENE_INTERACTIVE_STATE
|
SCENE_INTERACTIVE_STATE
|
||||||
.get_or_init(|| Arc::new(AtomicBool::new(false)))
|
.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) {
|
pub fn update_scene_interactive(interactive: bool) {
|
||||||
let app_handle = get_app_handle();
|
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 Some(window) = app_handle.get_window(SCENE_WINDOW_LABEL) {
|
||||||
if let Err(e) = window.set_ignore_cursor_events(!interactive) {
|
if let Err(e) = window.set_ignore_cursor_events(!interactive) {
|
||||||
error!("Failed to toggle scene cursor events: {}", e);
|
error!("Failed to toggle scene cursor events: {}", e);
|
||||||
@@ -43,6 +61,33 @@ pub fn set_scene_interactive(interactive: bool) {
|
|||||||
update_scene_interactive(interactive);
|
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")]
|
#[cfg(target_os = "macos")]
|
||||||
#[link(name = "ApplicationServices", kind = "framework")]
|
#[link(name = "ApplicationServices", kind = "framework")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -96,11 +141,22 @@ fn start_scene_modifier_listener() {
|
|||||||
loop {
|
loop {
|
||||||
let keys = device_state.get_keys();
|
let keys = device_state.get_keys();
|
||||||
// Check for Alt key (Option on Mac)
|
// 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 {
|
if interactive != last_interactive {
|
||||||
// State changed
|
// State changed
|
||||||
info!("Key down state chanegd!");
|
info!("Interactive state changed to: {}", interactive);
|
||||||
let previous = state.swap(interactive, Ordering::SeqCst);
|
let previous = state.swap(interactive, Ordering::SeqCst);
|
||||||
if previous != interactive {
|
if previous != interactive {
|
||||||
update_scene_interactive(interactive);
|
update_scene_interactive(interactive);
|
||||||
|
|||||||
@@ -88,6 +88,7 @@
|
|||||||
{@const config = getFriendDollConfig(userId)}
|
{@const config = getFriendDollConfig(userId)}
|
||||||
{#if config}
|
{#if config}
|
||||||
<DesktopPet
|
<DesktopPet
|
||||||
|
id={userId}
|
||||||
targetX={position.mapped.x * innerWidth}
|
targetX={position.mapped.x * innerWidth}
|
||||||
targetY={position.mapped.y * innerHeight}
|
targetY={position.mapped.y * innerHeight}
|
||||||
name={getFriendName(userId)}
|
name={getFriendName(userId)}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, onDestroy } from "svelte";
|
import { onMount, onDestroy } from "svelte";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { usePetState } from "$lib/composables/usePetState";
|
import { usePetState } from "$lib/composables/usePetState";
|
||||||
import { getSpriteSheetUrl } from "$lib/utils/sprite-utils";
|
import { getSpriteSheetUrl } from "$lib/utils/sprite-utils";
|
||||||
import PetSprite from "$lib/components/PetSprite.svelte";
|
import PetSprite from "$lib/components/PetSprite.svelte";
|
||||||
import onekoGif from "../../assets/oneko/oneko.gif";
|
import onekoGif from "../../assets/oneko/oneko.gif";
|
||||||
|
|
||||||
|
export let id = "";
|
||||||
export let targetX = 0;
|
export let targetX = 0;
|
||||||
export let targetY = 0;
|
export let targetY = 0;
|
||||||
export let name = "";
|
export let name = "";
|
||||||
@@ -21,9 +23,19 @@
|
|||||||
let lastFrameTimestamp: number;
|
let lastFrameTimestamp: number;
|
||||||
let spriteSheetUrl = onekoGif;
|
let spriteSheetUrl = onekoGif;
|
||||||
|
|
||||||
|
let isPetMenuOpen = false;
|
||||||
|
|
||||||
// Watch for color changes to regenerate sprite
|
// Watch for color changes to regenerate sprite
|
||||||
$: updateSprite(bodyColor, outlineColor);
|
$: updateSprite(bodyColor, outlineColor);
|
||||||
|
|
||||||
|
$: (isInteractive, (isPetMenuOpen = false));
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (id) {
|
||||||
|
invoke("set_pet_menu_state", { id, open: isPetMenuOpen });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function updateSprite(
|
async function updateSprite(
|
||||||
body: string | undefined,
|
body: string | undefined,
|
||||||
outline: string | undefined,
|
outline: string | undefined,
|
||||||
@@ -65,23 +77,32 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<div
|
||||||
onclick={() => {
|
|
||||||
console.log("clicked on neko");
|
|
||||||
}}
|
|
||||||
class="desktop-pet flex flex-col items-center relative"
|
class="desktop-pet flex flex-col items-center relative"
|
||||||
style="
|
style="
|
||||||
transform: translate({$position.x - 16}px, {$position.y - 16}px);
|
transform: translate({$position.x - 16}px, {$position.y - 16}px);
|
||||||
z-index: 50;
|
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
|
<PetSprite
|
||||||
{spriteSheetUrl}
|
{spriteSheetUrl}
|
||||||
spriteX={$currentSprite.x}
|
spriteX={$currentSprite.x}
|
||||||
spriteY={$currentSprite.y}
|
spriteY={$currentSprite.y}
|
||||||
/>
|
/>
|
||||||
</div>
|
</button>
|
||||||
|
|
||||||
<span
|
<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"
|
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}
|
{name}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.desktop-pet {
|
.desktop-pet {
|
||||||
|
|||||||
Reference in New Issue
Block a user