From f4a5828ea7677346ab6286998d2eb980896c09a0 Mon Sep 17 00:00:00 2001 From: Sline Date: Sun, 21 Sep 2025 13:46:42 +0800 Subject: [PATCH] fix: Implement DPI Scaling Support (#601) --- .../stage-tamagotchi/src/composables/tauri.ts | 27 ++++++++++++++ .../src/composables/use-rdev-mouse.ts | 37 ++++++++++++++++++- apps/stage-tamagotchi/src/pages/index.vue | 4 +- 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/apps/stage-tamagotchi/src/composables/tauri.ts b/apps/stage-tamagotchi/src/composables/tauri.ts index 6c7252600..7d48101f2 100644 --- a/apps/stage-tamagotchi/src/composables/tauri.ts +++ b/apps/stage-tamagotchi/src/composables/tauri.ts @@ -216,8 +216,35 @@ export function useTauriDpi() { return new imported.LogicalPosition(x, y) } + async function getScaleFactor(): Promise { + const imported = await tauriDpiApi.value + if (!imported) { + throw new Error('Tauri DPI API not available') + } + + const monitor = await imported.currentMonitor() + return monitor?.scaleFactor ?? 1 + } + + function convertPhysicalToLogical(physicalX: number, physicalY: number, scaleFactor: number): { x: number, y: number } { + return { + x: physicalX / scaleFactor, + y: physicalY / scaleFactor, + } + } + + function convertLogicalToPhysical(logicalX: number, logicalY: number, scaleFactor: number): { x: number, y: number } { + return { + x: logicalX * scaleFactor, + y: logicalY * scaleFactor, + } + } + return { createLogicalPosition, + getScaleFactor, + convertPhysicalToLogical, + convertLogicalToPhysical, } } diff --git a/apps/stage-tamagotchi/src/composables/use-rdev-mouse.ts b/apps/stage-tamagotchi/src/composables/use-rdev-mouse.ts index 1123fdbed..6568b5a28 100644 --- a/apps/stage-tamagotchi/src/composables/use-rdev-mouse.ts +++ b/apps/stage-tamagotchi/src/composables/use-rdev-mouse.ts @@ -1,17 +1,42 @@ import type { AiriTamagotchiEvents } from './tauri' import { createSharedComposable } from '@vueuse/core' -import { ref } from 'vue' +import { ref, watch } from 'vue' -import { useTauriEvent } from './tauri' +import { useTauriDpi, useTauriEvent } from './tauri' export const useRdevMouse = createSharedComposable(() => { const mouseX = ref(0) const mouseY = ref(0) + const logicalMouseX = ref(0) + const logicalMouseY = ref(0) + const scaleFactor = ref(1) const { listen } = useTauriEvent() + const { getScaleFactor } = useTauriDpi() + + // Update scale factor when DPI changes + async function updateScaleFactor() { + try { + const newScaleFactor = await getScaleFactor() + scaleFactor.value = newScaleFactor + } + catch (error) { + console.warn('Failed to get scale factor, using default 1.0:', error) + scaleFactor.value = 1 + } + } + + // Convert physical coordinates to logical coordinates + watch([mouseX, mouseY, scaleFactor], ([x, y, scale]) => { + const logical = convertPhysicalToLogical(x, y, scale) + logicalMouseX.value = logical.x + logicalMouseY.value = logical.y + }) async function setup() { + await updateScaleFactor() + await listen('tauri-plugins:tauri-plugin-rdev:mousemove', (event) => { if (event.payload.event_type.MouseMove) { const { x, y } = event.payload.event_type.MouseMove @@ -19,6 +44,11 @@ export const useRdevMouse = createSharedComposable(() => { mouseY.value = y } }) + + // Listen for scale changes (when window moves between monitors with different DPI) + await listen('tauri://scale-change', () => { + updateScaleFactor() + }) } setup() @@ -26,5 +56,8 @@ export const useRdevMouse = createSharedComposable(() => { return { mouseX, mouseY, + logicalMouseX, + logicalMouseY, + scaleFactor, } }) diff --git a/apps/stage-tamagotchi/src/pages/index.vue b/apps/stage-tamagotchi/src/pages/index.vue index c7e5ece8d..66f3e8365 100644 --- a/apps/stage-tamagotchi/src/pages/index.vue +++ b/apps/stage-tamagotchi/src/pages/index.vue @@ -30,7 +30,7 @@ const windowControlStore = useWindowControlStore() const resourcesStore = useResourcesStore() const mcpStore = useMcpStore() const { getPosition } = useTauriWindow() -const { mouseX, mouseY } = useRdevMouse() +const { logicalMouseX, logicalMouseY } = useRdevMouse() const { listen } = useTauriEvent() const { invoke } = useTauriCore() @@ -55,7 +55,7 @@ watch(shouldShowSetup, () => { }, { immediate: true }) // Handle mouse pass-through based on WebGL pixel alpha. -watchThrottled([mouseX, mouseY], ([x, y]) => { +watchThrottled([logicalMouseX, logicalMouseY], ([x, y]) => { requestAnimationFrame(() => { const canvas = widgetStageRef.value?.canvasElement() if (!canvas)