feat: 新增「猫咪设置 > 窗口设置 > 鼠标移入隐藏」配置项 (#727)

Co-authored-by: ayang <473033518@qq.com>
This commit is contained in:
Thexvoilone
2025-12-01 20:27:24 +08:00
committed by GitHub
parent 1e24c04da2
commit 7e7c1aded0
10 changed files with 65 additions and 44 deletions

View File

@@ -3,6 +3,10 @@ html {
color-scheme: light;
body {
--uno: transition-opacity-300;
}
&.dark {
color-scheme: dark;
}

View File

@@ -1,8 +1,6 @@
import type { CursorPoint } from '@/utils/monitor'
import { invoke } from '@tauri-apps/api/core'
import { isEqual, mapValues } from 'es-toolkit'
import { ref } from 'vue'
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
import { cursorPosition } from '@tauri-apps/api/window'
import { INVOKE_KEY, LISTEN_KEY } from '../constants'
@@ -11,6 +9,7 @@ import { useTauriListen } from './useTauriListen'
import { useCatStore } from '@/stores/cat'
import { useModelStore } from '@/stores/model'
import { inBetween } from '@/utils/is'
import { isWindows } from '@/utils/platform'
interface MouseButtonEvent {
@@ -18,6 +17,11 @@ interface MouseButtonEvent {
value: string
}
export interface CursorPoint {
x: number
y: number
}
interface MouseMoveEvent {
kind: 'MouseMove'
value: CursorPoint
@@ -32,7 +36,6 @@ type DeviceEvent = MouseButtonEvent | MouseMoveEvent | KeyboardEvent
export function useDevice() {
const modelStore = useModelStore()
const lastCursorPoint = ref<CursorPoint>({ x: 0, y: 0 })
const releaseTimers = new Map<string, NodeJS.Timeout>()
const catStore = useCatStore()
const { handlePress, handleRelease, handleMouseChange, handleMouseMove } = useModel()
@@ -60,14 +63,25 @@ export function useDevice() {
return nextKey
}
const processMouseMove = (point: CursorPoint) => {
const roundedValue = mapValues(point, Math.round)
const handleCursorMove = async () => {
const cursorPoint = await cursorPosition()
if (isEqual(lastCursorPoint.value, roundedValue)) return
handleMouseMove(cursorPoint)
lastCursorPoint.value = roundedValue
if (catStore.window.hideOnHover) {
const appWindow = getCurrentWebviewWindow()
const position = await appWindow.outerPosition()
const { width, height } = await appWindow.innerSize()
return handleMouseMove(point)
const isInWindow = inBetween(cursorPoint.x, position.x, position.x + width)
&& inBetween(cursorPoint.y, position.y, position.y + height)
document.body.style.setProperty('opacity', isInWindow ? '0' : 'unset')
if (!catStore.window.passThrough) {
appWindow.setIgnoreCursorEvents(isInWindow)
}
}
}
const handleAutoRelease = (key: string, delay = 100) => {
@@ -117,7 +131,7 @@ export function useDevice() {
case 'MouseRelease':
return handleMouseChange(value, false)
case 'MouseMove':
return processMouseMove(value)
return handleCursorMove()
}
})

View File

@@ -1,4 +1,4 @@
import type { CursorPoint } from '@/utils/monitor'
import type { PhysicalPosition } from '@tauri-apps/api/dpi'
import { LogicalSize } from '@tauri-apps/api/dpi'
import { resolveResource, sep } from '@tauri-apps/api/path'
@@ -107,12 +107,12 @@ export function useModel() {
live2d.setParameterValue(id, pressed)
}
async function handleMouseMove(point: CursorPoint) {
const monitor = await getCursorMonitor(point)
async function handleMouseMove(cursorPoint: PhysicalPosition) {
const monitor = await getCursorMonitor(cursorPoint)
if (!monitor) return
const { size, position, cursorPoint } = monitor
const { size, position } = monitor
const xRatio = (cursorPoint.x - position.x) / size.width
const yRatio = (cursorPoint.y - position.y) / size.height

View File

@@ -20,7 +20,8 @@
"windowSize": "Window Size",
"windowRadius": "Window Radius",
"opacity": "Opacity",
"autoReleaseDelay": "Auto Release Delay"
"autoReleaseDelay": "Auto Release Delay",
"hideOnHover": "Hide on Hover"
},
"hints": {
"mirrorMode": "When enabled, the model will be horizontally flipped.",
@@ -29,7 +30,8 @@
"passThrough": "When enabled, the window will not affect operations on other applications.",
"alwaysOnTop": "When enabled, the window will always stay above other applications.",
"windowSize": "Move the mouse to the edge of the window or hold Shift and right-drag to resize.",
"autoReleaseDelay": "Due to Windows not capturing the release events of certain system-level keys, they will be automatically treated as released after a timeout."
"autoReleaseDelay": "Due to Windows not capturing the release events of certain system-level keys, they will be automatically treated as released after a timeout.",
"hideOnHover": "When enabled, the window will hide when the mouse hovers over it."
}
},
"general": {

View File

@@ -20,7 +20,8 @@
"windowSize": "Kích thước",
"windowRadius": "Độ bo tròn cửa sổ",
"opacity": "Độ mờ",
"autoReleaseDelay": "Độ trễ tự động nhả phím"
"autoReleaseDelay": "Độ trễ tự động nhả phím",
"hideOnHover": "Ẩn khi di chuột"
},
"hints": {
"mirrorMode": "Bật để lật ngang mô hình.",
@@ -29,7 +30,8 @@
"passThrough": "Bật để cửa sổ không ảnh hưởng đến thao tác trên ứng dụng khác.",
"alwaysOnTop": "Bật để cửa sổ luôn nằm trên ứng dụng khác.",
"windowSize": "Di chuyển chuột đến mép cửa sổ hoặc giữ Shift và kéo chuột phải để thay đổi kích thước.",
"autoReleaseDelay": "Do Windows không bắt được sự kiện nhả của một số phím hệ thống, các phím đó sẽ được tự động xem như đã nhả sau khi hết thời gian chờ."
"autoReleaseDelay": "Do Windows không bắt được sự kiện nhả của một số phím hệ thống, các phím đó sẽ được tự động xem như đã nhả sau khi hết thời gian chờ.",
"hideOnHover": "Khi bật, cửa sổ sẽ ẩn khi chuột di chuyển vào."
}
},
"general": {

View File

@@ -20,7 +20,8 @@
"windowSize": "窗口尺寸",
"windowRadius": "窗口圆角",
"opacity": "不透明度",
"autoReleaseDelay": "按键自动释放延迟"
"autoReleaseDelay": "按键自动释放延迟",
"hideOnHover": "鼠标移入隐藏"
},
"hints": {
"mirrorMode": "启用后,模型将水平镜像翻转。",
@@ -29,7 +30,8 @@
"passThrough": "启用后,窗口不影响对其他应用程序的操作。",
"alwaysOnTop": "启用后,窗口始终显示在其他应用程序上方。",
"windowSize": "将鼠标移至窗口边缘,或按住 Shift 并右键拖动,也可以调整窗口大小。",
"autoReleaseDelay": "由于 Windows 下部分系统级按键无法捕获释放事件,超时后将自动视为已释放。"
"autoReleaseDelay": "由于 Windows 下部分系统级按键无法捕获释放事件,超时后将自动视为已释放。",
"hideOnHover": "启用后,鼠标悬停在窗口上时,窗口会隐藏。"
}
},
"general": {

View File

@@ -60,6 +60,13 @@ const catStore = useCatStore()
<Switch v-model:checked="catStore.window.alwaysOnTop" />
</ProListItem>
<ProListItem
:description="$t('pages.preference.cat.hints.hideOnHover')"
:title="$t('pages.preference.cat.labels.hideOnHover')"
>
<Switch v-model:checked="catStore.window.hideOnHover" />
</ProListItem>
<ProListItem
:description="$t('pages.preference.cat.hints.windowSize')"
:title="$t('pages.preference.cat.labels.windowSize')"

View File

@@ -15,6 +15,7 @@ export interface CatStore {
scale: number
opacity: number
radius: number
hideOnHover: boolean
}
}
@@ -59,6 +60,7 @@ export const useCatStore = defineStore('cat', () => {
scale: 100,
opacity: 100,
radius: 0,
hideOnHover: false,
})
const init = () => {

View File

@@ -3,3 +3,7 @@ export function isImage(value: string) {
return regex.test(value)
}
export function inBetween(value: number, minimum: number, maximum: number) {
return value >= minimum && value <= maximum
}

View File

@@ -1,33 +1,17 @@
import type { PhysicalPosition } from '@tauri-apps/api/window'
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
import { monitorFromPoint } from '@tauri-apps/api/window'
import { mapValues } from 'es-toolkit'
import { isMac } from './platform'
export async function getCursorMonitor(cursorPoint: PhysicalPosition) {
const appWindow = getCurrentWebviewWindow()
const scaleFactor = await appWindow.scaleFactor()
export interface CursorPoint {
x: number
y: number
}
export async function getCursorMonitor(point: CursorPoint) {
let cursorPoint = point
if (isMac) {
const appWindow = getCurrentWebviewWindow()
const scaleFactor = await appWindow.scaleFactor()
cursorPoint = mapValues(cursorPoint, value => value * scaleFactor)
}
const { x, y } = point
const { x, y } = cursorPoint.toLogical(scaleFactor)
const monitor = await monitorFromPoint(x, y)
if (!monitor) return
return {
...monitor,
cursorPoint,
}
return monitor
}