feat: 支持通过右键菜单快捷操作猫猫 (#136)

This commit is contained in:
ayangweb
2025-04-28 16:13:02 +08:00
committed by GitHub
parent 53f62d3daa
commit 11747e0cee
5 changed files with 137 additions and 51 deletions

View File

@@ -0,0 +1,103 @@
import type { CatMode } from '@/stores/cat'
import { CheckMenuItem, MenuItem, PredefinedMenuItem, Submenu } from '@tauri-apps/api/menu'
import { hideWindow, showWindow } from '@/plugins/window'
import { useCatStore } from '@/stores/cat'
import { isMac } from '@/utils/platform'
interface ModeOption {
label: string
value: CatMode
}
export function useSharedMenu() {
const catStore = useCatStore()
const modeOptions: ModeOption[] = [
{ label: '标准模式', value: 'standard' },
{ label: '键盘模式', value: 'keyboard' },
]
const getOpacityMenuItems = async () => {
const options = [25, 50, 75, 100]
const items = options.map((item) => {
return CheckMenuItem.new({
text: `${item}%`,
checked: catStore.opacity === item,
action: () => {
catStore.opacity = item
},
})
})
if (!options.includes(catStore.opacity)) {
items.unshift(CheckMenuItem.new({
text: `${catStore.opacity}%`,
checked: true,
enabled: false,
}))
}
return Promise.all(items)
}
const getSharedMenu = async () => {
return await Promise.all([
MenuItem.new({
text: '偏好设置...',
accelerator: isMac ? 'Cmd+,' : '',
action: () => showWindow('preference'),
}),
PredefinedMenuItem.new({ item: 'Separator' }),
Submenu.new({
text: '猫咪模式',
items: await Promise.all(
modeOptions.map((item) => {
return CheckMenuItem.new({
text: item.label,
checked: catStore.mode === item.value,
action: () => {
catStore.mode = item.value
},
})
}),
),
}),
MenuItem.new({
text: catStore.visible ? '隐藏猫咪' : '显示猫咪',
action: () => {
if (catStore.visible) {
hideWindow('main')
} else {
showWindow('main')
}
catStore.visible = !catStore.visible
},
}),
CheckMenuItem.new({
text: '窗口穿透',
checked: catStore.penetrable,
action: () => {
catStore.penetrable = !catStore.penetrable
},
}),
Submenu.new({
text: '不透明度',
items: await getOpacityMenuItems(),
}),
CheckMenuItem.new({
text: '镜像模式',
checked: catStore.mirrorMode,
action: () => {
catStore.mirrorMode = !catStore.mirrorMode
},
}),
])
}
return {
getSharedMenu,
}
}

View File

@@ -2,29 +2,36 @@ import type { TrayIconOptions } from '@tauri-apps/api/tray'
import { getName, getVersion } from '@tauri-apps/api/app'
import { emit } from '@tauri-apps/api/event'
import { CheckMenuItem, Menu, MenuItem, PredefinedMenuItem, Submenu } from '@tauri-apps/api/menu'
import { Menu, MenuItem, PredefinedMenuItem } from '@tauri-apps/api/menu'
import { resolveResource } from '@tauri-apps/api/path'
import { TrayIcon } from '@tauri-apps/api/tray'
import { openUrl } from '@tauri-apps/plugin-opener'
import { exit, relaunch } from '@tauri-apps/plugin-process'
import { ref, watch } from 'vue'
import { useDebounceFn } from '@vueuse/core'
import { watch } from 'vue'
import { GITHUB_LINK, LISTEN_KEY } from '../constants'
import { hideWindow, showWindow } from '../plugins/window'
import { showWindow } from '../plugins/window'
import { isMac } from '../utils/platform'
import { useSharedMenu } from './useSharedMenu'
import { useCatStore } from '@/stores/cat'
const TRAY_ID = 'BONGO_CAT_TRAY'
export function useTray() {
const visible = ref(true)
const catStore = useCatStore()
const { getSharedMenu } = useSharedMenu()
watch([visible, () => catStore.mode, () => catStore.penetrable], () => {
const debouncedUpdateTrayMenu = useDebounceFn(() => {
updateTrayMenu()
})
watch(() => catStore, () => {
debouncedUpdateTrayMenu()
}, { deep: true })
const createTray = async () => {
const tray = await getTrayById()
@@ -57,49 +64,7 @@ export function useTray() {
const appVersion = await getVersion()
const items = await Promise.all([
MenuItem.new({
text: '偏好设置...',
accelerator: isMac ? 'Cmd+,' : '',
action: () => showWindow(),
}),
MenuItem.new({
text: visible.value ? '隐藏猫咪' : '显示猫咪',
action: () => {
if (visible.value) {
hideWindow('main')
} else {
showWindow('main')
}
visible.value = !visible.value
},
}),
Submenu.new({
text: '猫咪模式',
items: await Promise.all([
CheckMenuItem.new({
text: '标准模式',
checked: catStore.mode === 'standard',
action: () => {
catStore.mode = 'standard'
},
}),
CheckMenuItem.new({
text: '键盘模式',
checked: catStore.mode === 'keyboard',
action: () => {
catStore.mode = 'keyboard'
},
}),
]),
}),
CheckMenuItem.new({
text: '窗口穿透',
checked: catStore.penetrable,
action: () => {
catStore.penetrable = !catStore.penetrable
},
}),
...await getSharedMenu(),
PredefinedMenuItem.new({ item: 'Separator' }),
MenuItem.new({
text: '检查更新',

View File

@@ -1,16 +1,19 @@
<script setup lang="ts">
import { Menu } from '@tauri-apps/api/menu'
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
import { useDebounceFn, useEventListener } from '@vueuse/core'
import { onMounted, onUnmounted, ref, watch } from 'vue'
import { useDevice } from '@/composables/useDevice'
import { useModel } from '@/composables/useModel'
import { useSharedMenu } from '@/composables/useSharedMenu'
import { useCatStore } from '@/stores/cat'
const appWindow = getCurrentWebviewWindow()
const { pressedMouses, mousePosition, pressedKeys } = useDevice()
const { handleLoad, handleDestroy, handleResize, handleMouseDown, handleMouseMove, handleKeyDown } = useModel()
const catStore = useCatStore()
const { getSharedMenu } = useSharedMenu()
const resizing = ref(false)
@@ -38,12 +41,22 @@ watch(pressedKeys, handleKeyDown)
watch(() => catStore.penetrable, (value) => {
appWindow.setIgnoreCursorEvents(value)
}, { immediate: true })
})
function handleWindowDrag() {
appWindow.startDragging()
}
async function handleContextmenu(event: MouseEvent) {
event.preventDefault()
const menu = await Menu.new({
items: await getSharedMenu(),
})
menu.popup()
}
function resolveImageURL(key: string) {
return new URL(`../../assets/images/keys/${key}.png`, import.meta.url).href
}
@@ -54,6 +67,7 @@ function resolveImageURL(key: string) {
class="relative children:(absolute h-screen w-screen)"
:class="[catStore.mirrorMode ? '-scale-x-100' : 'scale-x-100']"
:style="{ opacity: catStore.opacity / 100 }"
@contextmenu="handleContextmenu"
@mousedown="handleWindowDrag"
>
<img :src="`/images/backgrounds/${catStore.mode}.png`">

View File

@@ -41,7 +41,7 @@ const modeList: SelectProps['options'] = [
</ProListItem>
<ProListItem
title="透明度"
title="透明度"
vertical
>
<Slider

View File

@@ -1,14 +1,18 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export type CatMode = 'standard' | 'keyboard'
export const useCatStore = defineStore('cat', () => {
const mode = ref<'standard' | 'keyboard'>('standard')
const mode = ref<CatMode>('standard')
const visible = ref(true)
const penetrable = ref(false)
const opacity = ref(100)
const mirrorMode = ref(false)
return {
mode,
visible,
penetrable,
opacity,
mirrorMode,