scene configuration, neko opacity & scale
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
use crate::{
|
||||
lock_r,
|
||||
models::app_data::UserData,
|
||||
models::{app_data::UserData, app_state::{AppState, NekoPosition}},
|
||||
services::{
|
||||
app_data::{init_app_data_scoped, AppDataRefreshScope},
|
||||
app_state,
|
||||
friends,
|
||||
presence_modules::models::ModuleMetadata,
|
||||
sprite,
|
||||
@@ -45,3 +46,27 @@ pub fn get_friend_active_doll_sprites_base64() -> Result<friends::FriendActiveDo
|
||||
friends::sync_active_doll_sprites_from_app_data();
|
||||
Ok(friends::get_active_doll_sprites_snapshot())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn get_app_state() -> Result<AppState, String> {
|
||||
Ok(app_state::get_snapshot())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn set_scene_setup_nekos_position(nekos_position: Option<NekoPosition>) {
|
||||
app_state::set_scene_setup_nekos_position(nekos_position);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn set_scene_setup_nekos_opacity(nekos_opacity: f32) {
|
||||
app_state::set_scene_setup_nekos_opacity(nekos_opacity);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub fn set_scene_setup_nekos_scale(nekos_scale: f32) {
|
||||
app_state::set_scene_setup_nekos_scale(nekos_scale);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
use crate::{
|
||||
commands::app_state::get_modules,
|
||||
services::{
|
||||
doll_editor::open_doll_editor_window,
|
||||
scene::{get_scene_interactive, set_pet_menu_state, set_scene_interactive},
|
||||
},
|
||||
use crate::services::{
|
||||
doll_editor::open_doll_editor_window,
|
||||
scene::{get_scene_interactive, set_pet_menu_state, set_scene_interactive},
|
||||
};
|
||||
use commands::app::{quit_app, restart_app, retry_connection};
|
||||
use commands::app_state::{
|
||||
get_active_doll_sprite_base64, get_app_data, get_friend_active_doll_sprites_base64,
|
||||
refresh_app_data,
|
||||
get_active_doll_sprite_base64, get_app_data, get_app_state,
|
||||
get_friend_active_doll_sprites_base64, get_modules, refresh_app_data,
|
||||
set_scene_setup_nekos_opacity, set_scene_setup_nekos_position, set_scene_setup_nekos_scale,
|
||||
};
|
||||
use commands::auth::{logout_and_restart, start_discord_auth, start_google_auth};
|
||||
use commands::config::{get_client_config, open_client_config, save_client_config};
|
||||
@@ -27,11 +25,12 @@ use tauri::async_runtime;
|
||||
use tauri_specta::{collect_commands, collect_events, Builder as SpectaBuilder, ErrorHandlingMode};
|
||||
|
||||
use crate::services::app_events::{
|
||||
ActiveDollSpriteChanged, AppDataRefreshed, AuthFlowUpdated, CreateDoll, CursorMoved, EditDoll,
|
||||
FriendActiveDollChanged, FriendActiveDollSpritesUpdated, FriendCursorPositionsUpdated,
|
||||
FriendDisconnected, FriendRequestAccepted, FriendRequestDenied, FriendRequestReceived,
|
||||
FriendUserStatusChanged, InteractionDeliveryFailed, InteractionReceived,
|
||||
SceneInteractiveChanged, SetInteractionOverlay, Unfriended, UserStatusChanged,
|
||||
ActiveDollSpriteChanged, AppDataRefreshed, AppStateChanged, AuthFlowUpdated, CreateDoll,
|
||||
CursorMoved, EditDoll, FriendActiveDollChanged, FriendActiveDollSpritesUpdated,
|
||||
FriendCursorPositionsUpdated, FriendDisconnected, FriendRequestAccepted,
|
||||
FriendRequestDenied, FriendRequestReceived, FriendUserStatusChanged,
|
||||
InteractionDeliveryFailed, InteractionReceived, SceneInteractiveChanged,
|
||||
SetInteractionOverlay, Unfriended, UserStatusChanged,
|
||||
};
|
||||
|
||||
static APP_HANDLE: std::sync::OnceLock<tauri::AppHandle<tauri::Wry>> = std::sync::OnceLock::new();
|
||||
@@ -68,6 +67,7 @@ pub fn run() {
|
||||
.error_handling(ErrorHandlingMode::Throw)
|
||||
.commands(collect_commands![
|
||||
get_app_data,
|
||||
get_app_state,
|
||||
get_active_doll_sprite_base64,
|
||||
get_friend_active_doll_sprites_base64,
|
||||
refresh_app_data,
|
||||
@@ -102,12 +102,16 @@ pub fn run() {
|
||||
start_discord_auth,
|
||||
logout_and_restart,
|
||||
send_interaction_cmd,
|
||||
get_modules
|
||||
get_modules,
|
||||
set_scene_setup_nekos_position,
|
||||
set_scene_setup_nekos_opacity,
|
||||
set_scene_setup_nekos_scale
|
||||
])
|
||||
.events(collect_events![
|
||||
CursorMoved,
|
||||
SceneInteractiveChanged,
|
||||
AppDataRefreshed,
|
||||
AppStateChanged,
|
||||
ActiveDollSpriteChanged,
|
||||
SetInteractionOverlay,
|
||||
EditDoll,
|
||||
|
||||
39
src-tauri/src/models/app_state.rs
Normal file
39
src-tauri/src/models/app_state.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specta::Type;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Type)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum NekoPosition {
|
||||
TopLeft,
|
||||
Top,
|
||||
TopRight,
|
||||
Left,
|
||||
Right,
|
||||
BottomLeft,
|
||||
Bottom,
|
||||
BottomRight,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Type)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SceneSetup {
|
||||
pub nekos_position: Option<NekoPosition>,
|
||||
pub nekos_opacity: f32,
|
||||
pub nekos_scale: f32,
|
||||
}
|
||||
|
||||
impl Default for SceneSetup {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
nekos_position: None,
|
||||
nekos_opacity: 1.0,
|
||||
nekos_scale: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Type, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AppState {
|
||||
pub scene_setup: SceneSetup,
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod app_data;
|
||||
pub mod app_state;
|
||||
pub mod dolls;
|
||||
pub mod event_payloads;
|
||||
pub mod friends;
|
||||
|
||||
@@ -5,6 +5,7 @@ use tauri_specta::Event;
|
||||
use crate::{
|
||||
models::{
|
||||
app_data::UserData,
|
||||
app_state::AppState,
|
||||
event_payloads::{
|
||||
FriendActiveDollChangedPayload, FriendDisconnectedPayload,
|
||||
FriendRequestAcceptedPayload, FriendRequestDeniedPayload, FriendRequestReceivedPayload,
|
||||
@@ -47,6 +48,10 @@ pub struct SceneInteractiveChanged(pub bool);
|
||||
#[tauri_specta(event_name = "app-data-refreshed")]
|
||||
pub struct AppDataRefreshed(pub UserData);
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||
#[tauri_specta(event_name = "app-state-changed")]
|
||||
pub struct AppStateChanged(pub AppState);
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Type, Event)]
|
||||
#[tauri_specta(event_name = "active-doll-sprite-changed")]
|
||||
pub struct ActiveDollSpriteChanged(pub Option<String>);
|
||||
|
||||
42
src-tauri/src/services/app_state.rs
Normal file
42
src-tauri/src/services/app_state.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use std::sync::{Arc, LazyLock, RwLock};
|
||||
|
||||
use tauri_specta::Event as _;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::{
|
||||
get_app_handle,
|
||||
models::app_state::{AppState, NekoPosition},
|
||||
services::app_events::AppStateChanged,
|
||||
};
|
||||
|
||||
static APP_STATE: LazyLock<Arc<RwLock<AppState>>> =
|
||||
LazyLock::new(|| Arc::new(RwLock::new(AppState::default())));
|
||||
|
||||
pub fn get_snapshot() -> AppState {
|
||||
let guard = APP_STATE.read().expect("app state lock poisoned");
|
||||
guard.clone()
|
||||
}
|
||||
|
||||
pub fn set_scene_setup_nekos_position(nekos_position: Option<NekoPosition>) {
|
||||
let mut guard = APP_STATE.write().expect("app state lock poisoned");
|
||||
guard.scene_setup.nekos_position = nekos_position;
|
||||
emit_snapshot(&guard);
|
||||
}
|
||||
|
||||
pub fn set_scene_setup_nekos_opacity(nekos_opacity: f32) {
|
||||
let mut guard = APP_STATE.write().expect("app state lock poisoned");
|
||||
guard.scene_setup.nekos_opacity = nekos_opacity.clamp(0.1, 1.0);
|
||||
emit_snapshot(&guard);
|
||||
}
|
||||
|
||||
pub fn set_scene_setup_nekos_scale(nekos_scale: f32) {
|
||||
let mut guard = APP_STATE.write().expect("app state lock poisoned");
|
||||
guard.scene_setup.nekos_scale = nekos_scale.clamp(0.5, 2.0);
|
||||
emit_snapshot(&guard);
|
||||
}
|
||||
|
||||
fn emit_snapshot(app_state: &AppState) {
|
||||
if let Err(error) = AppStateChanged(app_state.clone()).emit(get_app_handle()) {
|
||||
warn!("Failed to emit app-state-changed event: {}", error);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod app_data;
|
||||
pub mod app_events;
|
||||
pub mod app_state;
|
||||
pub mod app_menu;
|
||||
pub mod app_update;
|
||||
pub mod accelerators;
|
||||
|
||||
17
src/assets/icons/image.svelte
Normal file
17
src/assets/icons/image.svelte
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
class="lucide lucide-image-icon lucide-image"
|
||||
><rect width="18" height="18" x="3" y="3" rx="2" ry="2" /><circle
|
||||
cx="9"
|
||||
cy="9"
|
||||
r="2"
|
||||
/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" /></svg
|
||||
>
|
||||
|
After Width: | Height: | Size: 413 B |
32
src/events/app-state.ts
Normal file
32
src/events/app-state.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { writable } from "svelte/store";
|
||||
import {
|
||||
commands,
|
||||
events,
|
||||
type AppState,
|
||||
type NekoPosition,
|
||||
} from "$lib/bindings";
|
||||
import { createEventSource } from "./listener-utils";
|
||||
|
||||
export type NeksPosition = NekoPosition;
|
||||
export type { AppState };
|
||||
|
||||
const initialState: AppState = {
|
||||
sceneSetup: {
|
||||
nekosPosition: null,
|
||||
nekosOpacity: 1,
|
||||
nekosScale: 1,
|
||||
},
|
||||
};
|
||||
|
||||
export const appState = writable<AppState>(initialState);
|
||||
|
||||
export const { start: startAppState, stop: stopAppState } = createEventSource(
|
||||
async (addEventListener) => {
|
||||
appState.set(await commands.getAppState());
|
||||
addEventListener(
|
||||
await events.appStateChanged.listen((event) => {
|
||||
appState.set(event.payload);
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -8,6 +8,9 @@ export const commands = {
|
||||
async getAppData() : Promise<UserData> {
|
||||
return await TAURI_INVOKE("get_app_data");
|
||||
},
|
||||
async getAppState() : Promise<AppState> {
|
||||
return await TAURI_INVOKE("get_app_state");
|
||||
},
|
||||
async getActiveDollSpriteBase64() : Promise<string | null> {
|
||||
return await TAURI_INVOKE("get_active_doll_sprite_base64");
|
||||
},
|
||||
@@ -118,6 +121,15 @@ async sendInteractionCmd(dto: SendInteractionDto) : Promise<null> {
|
||||
},
|
||||
async getModules() : Promise<ModuleMetadata[]> {
|
||||
return await TAURI_INVOKE("get_modules");
|
||||
},
|
||||
async setSceneSetupNekosPosition(nekosPosition: NekoPosition | null) : Promise<void> {
|
||||
await TAURI_INVOKE("set_scene_setup_nekos_position", { nekosPosition });
|
||||
},
|
||||
async setSceneSetupNekosOpacity(nekosOpacity: number) : Promise<void> {
|
||||
await TAURI_INVOKE("set_scene_setup_nekos_opacity", { nekosOpacity });
|
||||
},
|
||||
async setSceneSetupNekosScale(nekosScale: number) : Promise<void> {
|
||||
await TAURI_INVOKE("set_scene_setup_nekos_scale", { nekosScale });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +139,7 @@ async getModules() : Promise<ModuleMetadata[]> {
|
||||
export const events = __makeEvents__<{
|
||||
activeDollSpriteChanged: ActiveDollSpriteChanged,
|
||||
appDataRefreshed: AppDataRefreshed,
|
||||
appStateChanged: AppStateChanged,
|
||||
authFlowUpdated: AuthFlowUpdated,
|
||||
createDoll: CreateDoll,
|
||||
cursorMoved: CursorMoved,
|
||||
@@ -148,6 +161,7 @@ userStatusChanged: UserStatusChanged
|
||||
}>({
|
||||
activeDollSpriteChanged: "active-doll-sprite-changed",
|
||||
appDataRefreshed: "app-data-refreshed",
|
||||
appStateChanged: "app-state-changed",
|
||||
authFlowUpdated: "auth-flow-updated",
|
||||
createDoll: "create-doll",
|
||||
cursorMoved: "cursor-moved",
|
||||
@@ -180,6 +194,8 @@ export type AcceleratorModifier = "cmd" | "alt" | "ctrl" | "shift"
|
||||
export type ActiveDollSpriteChanged = string | null
|
||||
export type AppConfig = { api_base_url: string | null; debug_mode: boolean; accelerators?: Partial<{ [key in AcceleratorAction]: KeyboardAccelerator }> }
|
||||
export type AppDataRefreshed = UserData
|
||||
export type AppState = { sceneSetup: SceneSetup }
|
||||
export type AppStateChanged = AppState
|
||||
export type AuthFlowStatus = "started" | "succeeded" | "failed" | "cancelled"
|
||||
export type AuthFlowUpdated = AuthFlowUpdatedPayload
|
||||
export type AuthFlowUpdatedPayload = { provider: string; status: AuthFlowStatus; message: string | null }
|
||||
@@ -217,9 +233,11 @@ export type InteractionPayloadDto = { senderUserId: string; senderName: string;
|
||||
export type InteractionReceived = InteractionPayloadDto
|
||||
export type KeyboardAccelerator = { modifiers?: AcceleratorModifier[]; key?: AcceleratorKey | null }
|
||||
export type ModuleMetadata = { id: string; name: string; version: string; description: string | null }
|
||||
export type NekoPosition = "top-left" | "top" | "top-right" | "left" | "right" | "bottom-left" | "bottom" | "bottom-right"
|
||||
export type PresenceStatus = { title: string | null; subtitle: string | null; graphicsB64: string | null }
|
||||
export type SceneData = { display: DisplayData; grid_size: number }
|
||||
export type SceneInteractiveChanged = boolean
|
||||
export type SceneSetup = { nekosPosition: NekoPosition | null; nekosOpacity: number; nekosScale: number }
|
||||
export type SendFriendRequestDto = { receiverId: string }
|
||||
export type SendInteractionDto = { recipientUserId: string; content: string; type: string }
|
||||
export type SetInteractionOverlay = boolean
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
stopFriendActiveDollSprite,
|
||||
} from "../events/friend-active-doll-sprite";
|
||||
import { startAppData } from "../events/app-data";
|
||||
import { startAppState, stopAppState } from "../events/app-state";
|
||||
import { startInteraction, stopInteraction } from "../events/interaction";
|
||||
import {
|
||||
startSceneInteractive,
|
||||
@@ -27,6 +28,7 @@
|
||||
onMount(async () => {
|
||||
try {
|
||||
await startAppData();
|
||||
await startAppState();
|
||||
await startActiveDollSprite();
|
||||
await startFriendActiveDollSprite();
|
||||
await startCursorTracking();
|
||||
@@ -47,6 +49,7 @@
|
||||
stopSceneInteractive();
|
||||
stopInteraction();
|
||||
stopUserStatus();
|
||||
stopAppState();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
import Users from "../../assets/icons/users.svelte";
|
||||
import Settings from "../../assets/icons/settings.svelte";
|
||||
import Blocks from "../../assets/icons/blocks.svelte";
|
||||
import Image from "../../assets/icons/image.svelte";
|
||||
import Scene from "./tabs/scene/scene.svelte";
|
||||
|
||||
let showInteractionOverlay = false;
|
||||
|
||||
@@ -46,9 +48,19 @@
|
||||
<input
|
||||
type="radio"
|
||||
name="app_menu_tabs"
|
||||
aria-label="Your Nekos"
|
||||
aria-label="Scene Configuration"
|
||||
checked
|
||||
/>
|
||||
<div class="*:size-4">
|
||||
<Image />
|
||||
</div>
|
||||
</label>
|
||||
<div class="tab-content bg-base-100 border-base-300 p-4">
|
||||
<Scene />
|
||||
</div>
|
||||
|
||||
<label class="tab">
|
||||
<input type="radio" name="app_menu_tabs" aria-label="Your Nekos" />
|
||||
<div class="*:size-4">
|
||||
<PawPrint />
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
import type { DollColorSchemeDto } from "$lib/bindings";
|
||||
|
||||
export let dollColorScheme: DollColorSchemeDto;
|
||||
export let spriteScale = 2;
|
||||
export let spriteOpacity = 1;
|
||||
|
||||
let previewBase64: string | null = null;
|
||||
let error: string | null = null;
|
||||
@@ -80,7 +82,10 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="scale-200 p-4">
|
||||
<div
|
||||
style="transform: scale({spriteScale}); padding: {spriteScale *
|
||||
10}px; opacity: {spriteOpacity};"
|
||||
>
|
||||
<div class="size-8">
|
||||
{#if error}
|
||||
<div
|
||||
|
||||
74
src/routes/app-menu/tabs/scene/neko-reposition.svelte
Normal file
74
src/routes/app-menu/tabs/scene/neko-reposition.svelte
Normal file
@@ -0,0 +1,74 @@
|
||||
<script lang="ts">
|
||||
import { commands } from "$lib/bindings";
|
||||
import { appState, type NeksPosition } from "../../../../events/app-state";
|
||||
|
||||
const positions: { value: NeksPosition | null; label: string }[] = [
|
||||
{ value: "top-left", label: "Top Left" },
|
||||
{ value: "top", label: "Top" },
|
||||
{ value: "top-right", label: "Top Right" },
|
||||
{ value: "left", label: "Left" },
|
||||
{ value: null, label: "" },
|
||||
{ value: "right", label: "Right" },
|
||||
{ value: "bottom-left", label: "Bottom Left" },
|
||||
{ value: "bottom", label: "Bottom" },
|
||||
{ value: "bottom-right", label: "Bottom Right" },
|
||||
];
|
||||
|
||||
async function selectPosition(position: NeksPosition | null) {
|
||||
await commands.setSceneSetupNekosPosition(
|
||||
$appState.sceneSetup.nekosPosition === position ? null : position,
|
||||
);
|
||||
}
|
||||
|
||||
let selectedLabel = $derived(
|
||||
positions.find((p) => p.value === $appState.sceneSetup.nekosPosition)
|
||||
?.label ?? "",
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="collapse bg-base-100 border-base-300 border">
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title py-2 text-sm opacity-70">Neko Reposition</div>
|
||||
<div class="collapse-content">
|
||||
<div class="flex flex-row gap-4 h-full pt-4 border-t border-base-300">
|
||||
<div class="h-full flex flex-col justify-between">
|
||||
<div>
|
||||
<p class="text-sm opacity-50">
|
||||
Choose a corner to gather nekos into a cluster
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={$appState.sceneSetup.nekosPosition !== null}
|
||||
onclick={() =>
|
||||
selectPosition(
|
||||
$appState.sceneSetup.nekosPosition ? null : "bottom-left",
|
||||
)}
|
||||
class="toggle toggle-xl {$appState.sceneSetup.nekosPosition
|
||||
? 'bg-primary/10 toggle-primary'
|
||||
: 'bg-base-200'}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card bg-base-200/50 p-1 w-max border border-base-300">
|
||||
<div class="grid grid-cols-3 gap-6 items-center w-max">
|
||||
{#each positions as pos}
|
||||
{#if pos.value === null}
|
||||
<div></div>
|
||||
{:else}
|
||||
<button
|
||||
class={"btn-xs btn btn-square " +
|
||||
($appState.sceneSetup.nekosPosition === pos.value
|
||||
? "btn-primary"
|
||||
: "")}
|
||||
aria-label={pos.label}
|
||||
onclick={() => selectPosition(pos.value)}
|
||||
></button>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
85
src/routes/app-menu/tabs/scene/neko-view.svelte
Normal file
85
src/routes/app-menu/tabs/scene/neko-view.svelte
Normal file
@@ -0,0 +1,85 @@
|
||||
<script lang="ts">
|
||||
import { commands } from "$lib/bindings";
|
||||
import { appState } from "../../../../events/app-state";
|
||||
import DollPreview from "../../components/doll-preview.svelte";
|
||||
|
||||
async function updateOpacity(value: number) {
|
||||
await commands.setSceneSetupNekosOpacity(value);
|
||||
}
|
||||
|
||||
async function updateScale(value: number) {
|
||||
await commands.setSceneSetupNekosScale(value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="collapse bg-base-100 border-base-300 border">
|
||||
<input type="checkbox" checked />
|
||||
<div class="collapse-title py-2 text-sm opacity-70">Neko View</div>
|
||||
<div class="collapse-content">
|
||||
<div class="pt-4 border-t border-base-300">
|
||||
<div class="flex flex-row gap-4">
|
||||
<div class="border border-primary bg-base-200/50 w-40 card relative">
|
||||
<div class="size-full absolute">
|
||||
<div
|
||||
class="flex flex-row size-full items-end justify-between text-[8px] opacity-50 p-1 font-mono"
|
||||
>
|
||||
<div class="text-start flex flex-col">
|
||||
<p>Scale</p>
|
||||
<p>Opacity</p>
|
||||
</div>
|
||||
<div class="text-end flex flex-col">
|
||||
<p>{($appState.sceneSetup.nekosScale * 100).toFixed(0)}%</p>
|
||||
<p>{($appState.sceneSetup.nekosOpacity * 100).toFixed(0)}%</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="size-full flex flex-row -translate-y-2 justify-center items-center"
|
||||
>
|
||||
<DollPreview
|
||||
dollColorScheme={{ body: "b7f2ff", outline: "496065" }}
|
||||
spriteScale={$appState.sceneSetup.nekosScale}
|
||||
spriteOpacity={$appState.sceneSetup.nekosOpacity}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4 w-full">
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-xs opacity-70">Opacity</p>
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<input
|
||||
type="range"
|
||||
class="range flex-1"
|
||||
min="0.1"
|
||||
max="1"
|
||||
step="0.01"
|
||||
value={$appState.sceneSetup.nekosOpacity}
|
||||
oninput={(event) =>
|
||||
updateOpacity(
|
||||
Number((event.currentTarget as HTMLInputElement).value),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-xs opacity-70">Scale</p>
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<input
|
||||
type="range"
|
||||
class="range flex-1"
|
||||
min="0.5"
|
||||
max="2"
|
||||
step="0.25"
|
||||
value={$appState.sceneSetup.nekosScale}
|
||||
oninput={(event) =>
|
||||
updateScale(
|
||||
Number((event.currentTarget as HTMLInputElement).value),
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
10
src/routes/app-menu/tabs/scene/scene.svelte
Normal file
10
src/routes/app-menu/tabs/scene/scene.svelte
Normal file
@@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
import NekoView from "./neko-view.svelte";
|
||||
import NekoReposition from "./neko-reposition.svelte";
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 w-full h-full">
|
||||
<p class="text-lg font-bold">Scene Configuration</p>
|
||||
<NekoView />
|
||||
<NekoReposition />
|
||||
</div>
|
||||
@@ -1,9 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import {
|
||||
commands,
|
||||
type AppConfig,
|
||||
} from "$lib/bindings";
|
||||
import { commands, type AppConfig } from "$lib/bindings";
|
||||
|
||||
let form: AppConfig = {
|
||||
api_base_url: "",
|
||||
@@ -109,7 +106,6 @@
|
||||
bind:checked={form.debug_mode}
|
||||
/>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
|
||||
{#if errorMessage}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import PetMessagePop from "./components/pet-message-pop.svelte";
|
||||
import PetMessageSend from "./components/pet-message-send.svelte";
|
||||
import type { UserBasicDto } from "$lib/bindings";
|
||||
import { appState } from "../../events/app-state";
|
||||
|
||||
let debugMode = false;
|
||||
|
||||
@@ -46,6 +47,8 @@
|
||||
targetX={$cursorPositionOnScreen.raw.x}
|
||||
targetY={$cursorPositionOnScreen.raw.y}
|
||||
spriteUrl={$activeDollSpriteUrl}
|
||||
scale={$appState.sceneSetup.nekosScale}
|
||||
opacity={$appState.sceneSetup.nekosOpacity}
|
||||
/>
|
||||
{/if}
|
||||
{#each Object.entries($friendsCursorPositions) as [friendId, position] (friendId)}
|
||||
@@ -57,6 +60,8 @@
|
||||
spriteUrl={$friendActiveDollSpriteUrls[friendId]}
|
||||
initialX={position.raw.x}
|
||||
initialY={position.raw.y}
|
||||
scale={$appState.sceneSetup.nekosScale}
|
||||
opacity={$appState.sceneSetup.nekosOpacity}
|
||||
>
|
||||
<PetMenu user={friend!} ariaLabel={`Open ${friend?.name} actions`} />
|
||||
<PetMessagePop userId={friendId} />
|
||||
@@ -67,12 +72,12 @@
|
||||
{#if debugMode}
|
||||
<div id="debug-bar">
|
||||
<DebugBar
|
||||
isInteractive={$sceneInteractive}
|
||||
cursorPosition={$cursorPositionOnScreen}
|
||||
presenceStatus={$currentPresenceState?.presenceStatus ?? null}
|
||||
friendsCursorPositions={$friendsCursorPositions}
|
||||
friends={$appData?.friends ?? []}
|
||||
friendsPresenceStates={$friendsPresenceStates}
|
||||
isInteractive={$sceneInteractive}
|
||||
cursorPosition={$cursorPositionOnScreen}
|
||||
presenceStatus={$currentPresenceState?.presenceStatus ?? null}
|
||||
friendsCursorPositions={$friendsCursorPositions}
|
||||
friends={$appData?.friends ?? []}
|
||||
friendsPresenceStates={$friendsPresenceStates}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { setSprite } from "./sprites";
|
||||
import { calculateDirection, moveTowards, clampPosition } from "./physics";
|
||||
import { updateIdle } from "./idle";
|
||||
import { appState } from "../../../../events/app-state";
|
||||
|
||||
interface Props {
|
||||
targetX: number;
|
||||
@@ -11,6 +12,8 @@
|
||||
spriteUrl: string;
|
||||
initialX?: number;
|
||||
initialY?: number;
|
||||
scale?: number;
|
||||
opacity?: number;
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
@@ -20,10 +23,13 @@
|
||||
spriteUrl,
|
||||
initialX = 32,
|
||||
initialY = 32,
|
||||
scale = 1.0,
|
||||
opacity = 1.0,
|
||||
children,
|
||||
}: Props = $props();
|
||||
|
||||
let nekoEl: HTMLDivElement;
|
||||
let wrapperEl: HTMLDivElement;
|
||||
let animationFrameId: number;
|
||||
|
||||
let nekoPos = $state({ x: initialX, y: initialY });
|
||||
@@ -86,11 +92,15 @@
|
||||
const newPos = moveTowards(nekoPos.x, nekoPos.y, targetPos.x, targetPos.y);
|
||||
nekoPos = newPos;
|
||||
|
||||
nekoEl.style.transform = `translate(${nekoPos.x - 16}px, ${nekoPos.y - 16}px)`;
|
||||
nekoEl.style.transform = `scale(${scale ?? 1.0})`;
|
||||
nekoEl.style.opacity = `${opacity ?? 1.0}`;
|
||||
wrapperEl.style.transform = `translate(${nekoPos.x - 16}px, ${nekoPos.y - 16}px)`;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
nekoEl.style.backgroundImage = `url(${spriteUrl})`;
|
||||
nekoEl.style.opacity = `${opacity ?? 1.0}`;
|
||||
wrapperEl.style.transform = `translate(${nekoPos.x - 16}px, ${nekoPos.y - 16}px)`;
|
||||
animationFrameId = requestAnimationFrame(frame);
|
||||
});
|
||||
|
||||
@@ -101,18 +111,27 @@
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (nekoEl && spriteUrl) {
|
||||
if (nekoEl && spriteUrl && $appState) {
|
||||
nekoEl.style.transform = `scale(${scale ?? 1.0})`;
|
||||
nekoEl.style.backgroundImage = `url(${spriteUrl})`;
|
||||
nekoEl.style.opacity = `${opacity ?? 1.0}`;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={nekoEl}
|
||||
bind:this={wrapperEl}
|
||||
class="pointer-events-none fixed z-999 size-8 select-none"
|
||||
style="width: 32px; height: 32px; position: fixed; image-rendering: pixelated;"
|
||||
style="position: fixed; width: 32px; height: 32px;"
|
||||
>
|
||||
<div class="relative size-full">
|
||||
{@render children?.()}
|
||||
<div class="relative">
|
||||
<div
|
||||
bind:this={nekoEl}
|
||||
class="size-8"
|
||||
style="position: absolute; image-rendering: pixelated;"
|
||||
></div>
|
||||
<div class="absolute size-8">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user