diff --git a/src/composables/useDevice.ts b/src/composables/useDevice.ts index 785f61ba..ee465efb 100644 --- a/src/composables/useDevice.ts +++ b/src/composables/useDevice.ts @@ -1,14 +1,15 @@ import { invoke } from '@tauri-apps/api/core' import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow' import { cursorPosition } from '@tauri-apps/api/window' - -import { INVOKE_KEY, LISTEN_KEY } from '../constants' +import { ref } from 'vue' import { useModel } from './useModel' import { useTauriListen } from './useTauriListen' +import { INVOKE_KEY, LISTEN_KEY } from '@/constants' import { useCatStore } from '@/stores/cat' import { useModelStore } from '@/stores/model' +import { useStatisticsStore } from '@/stores/statistics' import { inBetween } from '@/utils/is' import { isWindows } from '@/utils/platform' @@ -35,9 +36,11 @@ interface KeyboardEvent { type DeviceEvent = MouseButtonEvent | MouseMoveEvent | KeyboardEvent export function useDevice() { + const isHovering = ref(false) const modelStore = useModelStore() const releaseTimers = new Map() const catStore = useCatStore() + const statisticsStore = useStatisticsStore() const { handlePress, handleRelease, handleMouseChange, handleMouseMove } = useModel() const startListening = () => { @@ -68,14 +71,16 @@ export function useDevice() { handleMouseMove(cursorPoint) - if (catStore.window.hideOnHover) { - const appWindow = getCurrentWebviewWindow() - const position = await appWindow.outerPosition() - const { width, height } = await appWindow.innerSize() + const appWindow = getCurrentWebviewWindow() + const position = await appWindow.outerPosition() + const { width, height } = await appWindow.innerSize() - const isInWindow = inBetween(cursorPoint.x, position.x, position.x + width) - && inBetween(cursorPoint.y, position.y, position.y + height) + const isInWindow = inBetween(cursorPoint.x, position.x, position.x + width) + && inBetween(cursorPoint.y, position.y, position.y + height) + isHovering.value = isInWindow + + if (catStore.window.hideOnHover) { document.body.style.setProperty('opacity', isInWindow ? '0' : 'unset') if (!catStore.window.passThrough) { @@ -108,6 +113,10 @@ export function useDevice() { if (!nextValue) return + if (kind === 'KeyboardPress') { + statisticsStore.recordKeyPress(nextValue) + } + if (nextValue === 'CapsLock') { return handleAutoRelease(nextValue) } @@ -127,6 +136,7 @@ export function useDevice() { switch (kind) { case 'MousePress': + statisticsStore.recordMouseClick(value) return handleMouseChange(value) case 'MouseRelease': return handleMouseChange(value, false) @@ -137,5 +147,6 @@ export function useDevice() { return { startListening, + isHovering, } } diff --git a/src/locales/en-US.json b/src/locales/en-US.json index d8fa78a6..cc0260be 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -22,7 +22,11 @@ "opacity": "Opacity", "autoReleaseDelay": "Auto Release Delay", "hideOnHover": "Hide on Hover", - "position": "Window Position" + "position": "Window Position", + "statisticsSettings": "Statistics Settings", + "enableStatistics": "Enable Statistics", + "mouseClickStatistics": "Track Mouse Clicks", + "resetStatistics": "Reset Statistics" }, "hints": { "mirrorMode": "When enabled, the model will be mirrored horizontally.", @@ -33,7 +37,15 @@ "windowSize": "Move mouse to window edge, or hold Shift and right-drag to resize.", "autoReleaseDelay": "On Windows, some system keys cannot capture release events and will auto-release after timeout.", "hideOnHover": "When enabled, the window hides when mouse hovers over it.", - "position": "Takes effect after the app starts, or when this parameter, window size, model, or screen resolution changes." + "position": "Takes effect after the app starts, or when this parameter, window size, model, or screen resolution changes.", + "enableStatistics": "When enabled, keyboard and mouse usage data will be tracked.", + "mouseClickStatistics": "When enabled, mouse clicks will be counted.", + "resetStatisticsConfirm": "Are you sure you want to reset all statistics?" + }, + "buttons": { + "reset": "Reset", + "cancel": "Cancel", + "confirm": "Confirm" }, "options": { "topLeft": "Top Left", diff --git a/src/locales/pt-BR.json b/src/locales/pt-BR.json index ee4f716b..1ea8a081 100644 --- a/src/locales/pt-BR.json +++ b/src/locales/pt-BR.json @@ -22,7 +22,11 @@ "opacity": "Opacidade", "autoReleaseDelay": "Atraso de Liberação Automática", "hideOnHover": "Ocultar ao Passar o Mouse", - "position": "Posição da Janela" + "position": "Posição da Janela", + "statisticsSettings": "Configurações de Estatísticas", + "enableStatistics": "Ativar Estatísticas", + "mouseClickStatistics": "Rastrear Cliques do Mouse", + "resetStatistics": "Redefinir Estatísticas" }, "hints": { "mirrorMode": "Quando ativado, o modelo será invertido horizontalmente.", @@ -33,7 +37,15 @@ "windowSize": "Mova o mouse para a borda da janela ou segure Shift e arraste com o botão direito para redimensionar.", "autoReleaseDelay": "Devido ao Windows não capturar eventos de liberação de certas teclas de nível do sistema, elas serão automaticamente tratadas como liberadas após um tempo limite.", "hideOnHover": "Quando ativado, a janela será ocultada quando o mouse passar sobre ela.", - "position": "Entra em vigor após inicializar o aplicativo ou quando este parâmetro, o tamanho da janela, o modelo ou a resolução de tela é alterado." + "position": "Entra em vigor após inicializar o aplicativo ou quando este parâmetro, o tamanho da janela, o modelo ou a resolução de tela é alterado.", + "enableStatistics": "Quando ativado, os dados de uso do teclado e mouse serão rastreados.", + "mouseClickStatistics": "Quando ativado, os cliques do mouse serão contados.", + "resetStatisticsConfirm": "Tem certeza de que deseja redefinir todas as estatísticas?" + }, + "buttons": { + "reset": "Redefinir", + "cancel": "Cancelar", + "confirm": "Confirmar" }, "options": { "topLeft": "Canto Superior Esquerdo", diff --git a/src/locales/vi-VN.json b/src/locales/vi-VN.json index fc8818d7..2b8014f9 100644 --- a/src/locales/vi-VN.json +++ b/src/locales/vi-VN.json @@ -22,7 +22,11 @@ "opacity": "Độ mờ", "autoReleaseDelay": "Độ trễ tự động nhả phím", "hideOnHover": "Ẩn khi di chuột", - "position": "Vị trí cửa sổ" + "position": "Vị trí cửa sổ", + "statisticsSettings": "Cài đặt Thống kê", + "enableStatistics": "Bật Thống kê", + "mouseClickStatistics": "Thống kê Click chuột", + "resetStatistics": "Đặt lại Thống kê" }, "hints": { "mirrorMode": "Bật để lật ngang mô hình.", @@ -33,7 +37,15 @@ "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ờ.", "hideOnHover": "Khi bật, cửa sổ sẽ ẩn khi chuột di chuyển vào.", - "position": "Có hiệu lực sau khi khởi động ứng dụng hoặc khi tham số này, kích thước cửa sổ, mô hình hoặc độ phân giải màn hình thay đổi." + "position": "Có hiệu lực sau khi khởi động ứng dụng hoặc khi tham số này, kích thước cửa sổ, mô hình hoặc độ phân giải màn hình thay đổi.", + "enableStatistics": "Khi bật, dữ liệu sử dụng bàn phím và chuột sẽ được thống kê.", + "mouseClickStatistics": "Khi bật, số lần click chuột sẽ được thống kê.", + "resetStatisticsConfirm": "Bạn có chắc muốn đặt lại tất cả thống kê không?" + }, + "buttons": { + "reset": "Đặt lại", + "cancel": "Hủy", + "confirm": "Xác nhận" }, "options": { "topLeft": "Góc trên cùng bên trái", diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json index 1489747a..9e9383f1 100644 --- a/src/locales/zh-CN.json +++ b/src/locales/zh-CN.json @@ -22,7 +22,11 @@ "opacity": "不透明度", "autoReleaseDelay": "按键自动释放延迟", "hideOnHover": "鼠标移入隐藏", - "position": "窗口位置" + "position": "窗口位置", + "statisticsSettings": "统计设置", + "enableStatistics": "启用统计", + "mouseClickStatistics": "统计鼠标点击", + "resetStatistics": "重置统计" }, "hints": { "mirrorMode": "启用后,模型将水平镜像翻转。", @@ -33,7 +37,15 @@ "windowSize": "将鼠标移至窗口边缘,或按住 Shift 并右键拖动,也可以调整窗口大小。", "autoReleaseDelay": "由于 Windows 下部分系统级按键无法捕获释放事件,超时后将自动视为已释放。", "hideOnHover": "启用后,鼠标悬停在窗口上时,窗口会隐藏。", - "position": "应用启动后,或当此参数、窗口尺寸、模型、电脑分辨率发生变化时生效。" + "position": "应用启动后,或当此参数、窗口尺寸、模型、电脑分辨率发生变化时生效。", + "enableStatistics": "启用后,将统计键盘和鼠标使用数据。", + "mouseClickStatistics": "启用后,将统计鼠标点击次数。", + "resetStatisticsConfirm": "确定要重置所有统计数据吗?" + }, + "buttons": { + "reset": "重置", + "cancel": "取消", + "confirm": "确定" }, "options": { "topLeft": "左上角", diff --git a/src/pages/main/index.vue b/src/pages/main/index.vue index 5cf11db1..2060bdf4 100644 --- a/src/pages/main/index.vue +++ b/src/pages/main/index.vue @@ -8,7 +8,7 @@ import { exists, readDir } from '@tauri-apps/plugin-fs' import { useDebounceFn, useEventListener } from '@vueuse/core' import { round } from 'es-toolkit' import { nth } from 'es-toolkit/compat' -import { onMounted, onUnmounted, ref, watch } from 'vue' +import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import { useDevice } from '@/composables/useDevice' import { useGamepad } from '@/composables/useGamepad' @@ -19,11 +19,13 @@ import { hideWindow, setAlwaysOnTop, setTaskbarVisibility, showWindow } from '@/ import { useCatStore } from '@/stores/cat' import { useGeneralStore } from '@/stores/general.ts' import { useModelStore } from '@/stores/model' +import { useStatisticsStore } from '@/stores/statistics' import { isImage } from '@/utils/is' import { join } from '@/utils/path' import { clearObject } from '@/utils/shared' -const { startListening } = useDevice() +const { startListening, isHovering } = useDevice() +const statisticsStore = useStatisticsStore() const appWindow = getCurrentWebviewWindow() const { modelSize, handleLoad, handleDestroy, handleResize, handleKeyChange } = useModel() const catStore = useCatStore() @@ -147,6 +149,10 @@ function handleMouseMove(event: MouseEvent) { catStore.window.scale = round(nextScale) } + +const totalCount = computed(() => { + return statisticsStore.keyboard.total + statisticsStore.mouse.total +}) diff --git a/src/pages/preference/components/cat/index.vue b/src/pages/preference/components/cat/index.vue index 0feb324b..8670a7fe 100644 --- a/src/pages/preference/components/cat/index.vue +++ b/src/pages/preference/components/cat/index.vue @@ -1,14 +1,20 @@ diff --git a/src/stores/statistics.ts b/src/stores/statistics.ts new file mode 100644 index 00000000..456a5496 --- /dev/null +++ b/src/stores/statistics.ts @@ -0,0 +1,77 @@ +import { defineStore } from 'pinia' +import { reactive } from 'vue' + +export interface StatisticsStore { + keyboard: { + total: number + keys: Record + } + mouse: { + total: number + left: number + right: number + } + settings: { + enabled: boolean + mouseClickEnabled: boolean + } +} + +export const useStatisticsStore = defineStore('statistics', () => { + const keyboard = reactive({ + total: 0, + keys: {}, + }) + + const mouse = reactive({ + total: 0, + left: 0, + right: 0, + }) + + const settings = reactive({ + enabled: true, + mouseClickEnabled: true, + }) + + const recordKeyPress = (key: string) => { + if (!settings.enabled) return + keyboard.total++ + keyboard.keys[key] = (keyboard.keys[key] || 0) + 1 + } + + const recordMouseClick = (button: string) => { + if (!settings.enabled || !settings.mouseClickEnabled) return + if (button === 'Left') { + mouse.left++ + mouse.total++ + } else if (button === 'Right') { + mouse.right++ + mouse.total++ + } + } + + const reset = () => { + keyboard.total = 0 + keyboard.keys = {} + mouse.total = 0 + mouse.left = 0 + mouse.right = 0 + } + + return { + keyboard, + mouse, + settings, + // actions + recordKeyPress, + recordMouseClick, + reset, + } +}, { + tauri: { + autoStart: true, + saveOnChange: true, + filterKeys: ['settings'], + }, +})