feat: refactoring the project structure and adding content to the preferences window (#26)

This commit is contained in:
ayangweb
2025-04-10 22:30:15 +08:00
committed by GitHub
parent 5b8628cd39
commit 0f26e1608d
39 changed files with 1589 additions and 725 deletions

116
Cargo.lock generated
View File

@@ -1230,9 +1230,9 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.1.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
dependencies = [
"crc32fast",
"miniz_oxide",
@@ -1537,7 +1537,7 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed7131e57abbde63513e0e6636f76668a1ca9798dcae2df4e283cae9ee83859e"
dependencies = [
"rustix 1.0.3",
"rustix 1.0.5",
"windows-targets 0.52.6",
]
@@ -1822,6 +1822,12 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "http-range"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
[[package]]
name = "httparse"
version = "1.10.1"
@@ -1849,9 +1855,9 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.10"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2"
dependencies = [
"bytes",
"futures-channel",
@@ -1859,6 +1865,7 @@ dependencies = [
"http",
"http-body",
"hyper",
"libc",
"pin-project-lite",
"socket2",
"tokio",
@@ -1868,9 +1875,9 @@ dependencies = [
[[package]]
name = "iana-time-zone"
version = "0.1.62"
version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127"
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
dependencies = [
"android_system_properties",
"core-foundation-sys 0.8.7",
@@ -1878,7 +1885,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.52.0",
"windows-core 0.61.0",
]
[[package]]
@@ -3513,9 +3520,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "1.0.3"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96"
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
dependencies = [
"bitflags 2.9.0",
"errno",
@@ -3828,9 +3835,9 @@ checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
[[package]]
name = "socket2"
version = "0.5.8"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
dependencies = [
"libc",
"windows-sys 0.52.0",
@@ -4066,9 +4073,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
version = "2.4.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "511dd38065a5d3b36c33cdba4362b99a40a5103bebcd4aebb930717e7c8ba292"
checksum = "4d08db1ff9e011e04014e737ec022610d756c0eae0b3b3a9037bccaf3003173a"
dependencies = [
"anyhow",
"bytes",
@@ -4081,6 +4088,7 @@ dependencies = [
"gtk",
"heck 0.5.0",
"http",
"http-range",
"image",
"jni",
"libc",
@@ -4117,9 +4125,9 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.1.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffa8732a66f90903f5a585215f3cf1e87988d0359bc88c18a502efe7572c1de"
checksum = "0fd20e4661c2cce65343319e6e8da256958f5af958cafc47c0d0af66a55dcd17"
dependencies = [
"anyhow",
"cargo_toml",
@@ -4139,9 +4147,9 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "2.1.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c266a247f14d63f40c6282c2653a8bac5cc3d482ca562a003a88513653ea817a"
checksum = "458258b19032450ccf975840116ecf013e539eadbb74420bd890e8c56ab2b1a4"
dependencies = [
"base64 0.22.1",
"brotli",
@@ -4166,9 +4174,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.1.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f47a1cf94b3bd6c4dc37dce1a43fc96120ff29a91757f0ab3cf713c7ad846e7c"
checksum = "d402813d3b9c773a0fa58697c457c771f10e735498fdcb7b343264d18e5a601f"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -4196,9 +4204,9 @@ dependencies = [
[[package]]
name = "tauri-plugin"
version = "2.1.0"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9972871fcbddf16618f70412d965d4d845cd4b76d03fff168709961ef71e5cdf"
checksum = "a4190775d6ff73fe66d9af44c012739a2659720efd9c0e1e56a918678038699d"
dependencies = [
"anyhow",
"glob",
@@ -4308,9 +4316,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "2.5.0"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e9c7bce5153f1ca7bc45eba37349b31ba50e975e28edc8b5766c5ec02b0b63a"
checksum = "00ada7ac2f9276f09b8c3afffd3215fd5d9bff23c22df8a7c70e7ef67cacd532"
dependencies = [
"cookie",
"dpi",
@@ -4328,9 +4336,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "2.5.0"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "087188020fd6facb8578fe9b38e81fa0fe5fb85744c73da51a299f94a530a1e3"
checksum = "cf2e5842c57e154af43a20a49c7efee0ce2578c20b4c2bdf266852b422d2e421"
dependencies = [
"gtk",
"http",
@@ -4401,9 +4409,9 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82dcced4014e59af9790cc22f5d271df3be09ecd6728ec68861642553c8d01b7"
checksum = "1f037e66c7638cc0a2213f61566932b9a06882b8346486579c90e4b019bac447"
dependencies = [
"anyhow",
"brotli",
@@ -4456,7 +4464,7 @@ dependencies = [
"fastrand",
"getrandom 0.3.2",
"once_cell",
"rustix 1.0.3",
"rustix 1.0.5",
"windows-sys 0.59.0",
]
@@ -5117,7 +5125,7 @@ dependencies = [
"webview2-com-sys",
"windows",
"windows-core 0.60.1",
"windows-implement",
"windows-implement 0.59.0",
"windows-interface",
]
@@ -5211,26 +5219,30 @@ dependencies = [
"windows-core 0.60.1",
]
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-core"
version = "0.60.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247"
dependencies = [
"windows-implement",
"windows-implement 0.59.0",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
"windows-strings 0.3.1",
]
[[package]]
name = "windows-core"
version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
dependencies = [
"windows-implement 0.60.0",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings 0.4.0",
]
[[package]]
@@ -5254,6 +5266,17 @@ dependencies = [
"syn 2.0.100",
]
[[package]]
name = "windows-implement"
version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "windows-interface"
version = "0.59.1"
@@ -5288,7 +5311,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
dependencies = [
"windows-result",
"windows-strings",
"windows-strings 0.3.1",
"windows-targets 0.53.0",
]
@@ -5310,6 +5333,15 @@ dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.45.0"

View File

@@ -1,6 +1,7 @@
import antfu from '@antfu/eslint-config'
export default antfu({
formatters: true,
unocss: true,
rules: {
'antfu/if-newline': 'off',
@@ -19,6 +20,7 @@ export default antfu({
},
},
],
'vue/attributes-order': ['error', { alphabetical: true }],
},
ignores: ['**/*.toml'],
})

View File

@@ -21,14 +21,14 @@
"release": "release-it"
},
"dependencies": {
"@tauri-apps/api": "^2.4.0",
"@tauri-apps/api": "^2.4.1",
"@tauri-apps/plugin-log": "~2.3.1",
"@tauri-apps/plugin-opener": "~2.2.6",
"@tauri-apps/plugin-os": "^2.2.1",
"@tauri-apps/plugin-process": "^2.2.0",
"@tauri-store/pinia": "^3.2.0",
"@unocss/reset": "66.1.0-beta.7",
"@vueuse/core": "^13.0.0",
"ant-design-vue": "^4.2.6",
"pinia": "^3.0.1",
"pixi-live2d-display": "^0.4.0",
"pixi.js": "^6.5.10",
@@ -40,8 +40,9 @@
"@antfu/eslint-config": "^4.11.0",
"@commitlint/cli": "^19.8.0",
"@commitlint/config-conventional": "^19.8.0",
"@tauri-apps/cli": "^2.4.0",
"@types/node": "^22.13.14",
"@iconify-json/solar": "^1.2.2",
"@tauri-apps/cli": "^2.4.1",
"@types/node": "^22.13.17",
"@unocss/eslint-plugin": "^66.0.0",
"@vitejs/plugin-vue": "^5.2.3",
"eslint": "^9.23.0",
@@ -49,11 +50,12 @@
"lint-staged": "^15.5.0",
"npm-run-all": "^4.1.5",
"release-it": "^18.1.2",
"sass": "^1.86.3",
"simple-git-hooks": "^2.12.1",
"tsx": "^4.19.3",
"typescript": "~5.6.3",
"unocss": "66.1.0-beta.7",
"vite": "^6.2.3"
"vite": "^6.2.4"
},
"pnpm": {
"patchedDependencies": {

1586
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

BIN
public/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -10,40 +10,34 @@
"DisplayInfo": "demomodel.cdi3.json",
"Expressions": [
{
"Name": "live2d_expression0.exp3.json",
"Key": "",
"Name": "去掉表情",
"File": "live2d_expression0.exp3.json"
},
{
"Name": "live2d_expression1.exp3.json",
"Key": "",
"Name": "戴上墨镜",
"File": "live2d_expression1.exp3.json"
},
{
"Name": "live2d_expression2.exp3.json",
"Key": "",
"Name": "升天",
"File": "live2d_expression2.exp3.json"
}
],
"Motions": {
"CAT_motion": [
{
"Key": "",
"Name": "打雷",
"File": "live2d_motion1.motion3.json",
"Sound": "live2d_motion1.flac",
"FadeInTime": 0,
"FadeOutTime": 0
},
{
"File": "live2d_motion2.motion3.json",
"FadeInTime": 0,
"FadeOutTime": 0
}
],
"CAT_motion_lock": [
{
"File": "live2d_motion1.motion3.json",
"Sound": "live2d_motion1.flac",
"FadeInTime": 0,
"FadeOutTime": 0
},
{
"Key": "",
"Name": "左手摇摆",
"File": "live2d_motion2.motion3.json",
"FadeInTime": 0,
"FadeOutTime": 0

View File

@@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { workspace = true, features = ["macos-private-api", "tray-icon", "image-png"] }
tauri = { workspace = true, features = ["tray-icon", "protocol-asset", "macos-private-api", "image-png"] }
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
tauri-plugin-custom-window.workspace = true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -9,6 +9,10 @@
"core:default",
"core:window:allow-start-dragging",
"core:window:allow-set-size",
"core:window:deny-internal-toggle-maximize",
"core:window:allow-set-always-on-top",
"core:window:allow-set-ignore-cursor-events",
"core:window:allow-set-decorations",
"custom-window:default",
"os:default",
"process:default",

View File

@@ -22,17 +22,31 @@
"alwaysOnTop": true,
"transparent": true,
"decorations": false,
"acceptFirstMouse": true
"acceptFirstMouse": true,
"skipTaskbar": true
},
{
"label": "preference",
"title": "偏好设置",
"url": "index.html/#/preference",
"visible": false
"width": 600,
"height": 450,
"resizable": false,
"maximizable": false,
"titleBarStyle": "Overlay",
"hiddenTitle": true
}
],
"security": {
"csp": null
"csp": null,
"dangerousDisableAssetCspModification": true,
"assetProtocol": {
"enable": true,
"scope": {
"allow": ["**/*"],
"requireLiteralLeadingDot": false
}
}
}
},
"bundle": {
@@ -47,7 +61,7 @@
"icons/icon.icns",
"icons/icon.ico"
],
"resources": ["assets/tray.png"]
"resources": ["assets/tray.png", "assets/logo.png"]
},
"plugins": {
"updater": {

View File

@@ -1,6 +1,6 @@
{
"identifier": "com.ayangweb.BongoCat",
"bundle": {
"resources": ["assets/tray-mac.png"]
"resources": ["assets/tray-mac.png", "assets/logo.png"]
}
}

View File

@@ -7,16 +7,21 @@ import { onMounted } from 'vue'
import { RouterView } from 'vue-router'
import { useTauriListen } from './composables/useTauriListen'
import { useThemeVars } from './composables/useThemeVars'
import { LISTEN_KEY } from './constants'
import { hideWindow, showWindow } from './plugins/window'
import { useCatStore } from './stores/cat'
import { useModelStore } from './stores/model'
const { generateColorVars } = useThemeVars()
const modelStore = useModelStore()
const catStore = useCatStore()
const appWindow = getCurrentWebviewWindow()
onMounted(() => {
generateColorVars()
modelStore.$tauri.start()
catStore.$tauri.start()
})
useTauriListen(LISTEN_KEY.SHOW_WINDOW, ({ payload }) => {

View File

@@ -1,18 +0,0 @@
@font-face {
font-family: 'cat';
src: url('../fonts/cat.woff2') format('woff2');
}
html {
--uno: select-none overscroll-none font-[cat] antialiased;
color-scheme: light;
&.dark {
color-scheme: dark;
}
}
img {
-webkit-user-drag: none;
}

View File

@@ -0,0 +1,17 @@
html {
--uno: select-none overscroll-none antialiased;
color-scheme: light;
&.dark {
color-scheme: dark;
}
img {
-webkit-user-drag: none;
}
button {
outline: none !important;
}
}

Binary file not shown.

View File

@@ -0,0 +1,43 @@
<script setup lang="ts">
import { Flex } from 'ant-design-vue'
import { computed, useSlots } from 'vue'
const { title, icon, description } = defineProps<{
title: string
icon?: string
description?: string
}>()
const slots = useSlots()
const hasIcon = computed(() => {
return icon || slots.icon
})
const hasDescription = computed(() => {
return description || slots.description
})
</script>
<template>
<Flex align="center" class="b b-color-2 rounded-lg b-solid p-4" justify="space-between">
<Flex align="center">
<slot name="icon">
<div class="text-4" :class="icon" />
</slot>
<Flex :class="{ 'ml-4': hasIcon }" vertical>
<div class="text-sm font-medium">
{{ title }}
</div>
<div class="text-xs [&_a]:(active:text-color-primary-7 hover:text-color-primary-5 text-color-3) text-color-3" :class="{ 'mt-2': hasDescription }">
<slot name="description">
{{ description }}
</slot>
</div>
</Flex>
</Flex>
<slot />
</Flex>
</template>

View File

@@ -0,0 +1,19 @@
<script setup lang="ts">
import { Flex } from 'ant-design-vue'
const { title } = defineProps<{
title: string
}>()
</script>
<template>
<Flex class="mb-4" gap="small" vertical>
<div class="text-base font-medium" data-tauri-drag-region>
{{ title }}
</div>
<Flex gap="middle" vertical>
<slot />
</Flex>
</FLex>
</template>

View File

@@ -1,23 +1,23 @@
import { LogicalSize } from '@tauri-apps/api/dpi'
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
import { computed, watch } from 'vue'
import { watch } from 'vue'
import { MODEL_BACKGROUND } from '../constants'
import { useModelStore } from '../stores/model'
import live2d from '../utils/live2d'
import { getCursorMonitor } from '../utils/monitor'
import { useCatStore } from '@/stores/cat'
export function useModel() {
const modelState = useModelStore()
const carStore = useCatStore()
const background = computed(() => MODEL_BACKGROUND[modelState.mode])
watch(() => modelState.mode, handleLoad)
watch(() => carStore.mode, handleLoad)
async function handleLoad() {
await live2d.load(modelState.mode)
const data = await live2d.load(`/models/${carStore.mode}/cat.model3.json`)
handleResize()
Object.assign(carStore, data)
}
function handleDestroy() {
@@ -25,7 +25,7 @@ export function useModel() {
}
async function handleResize() {
if (!live2d.currentModel) return
if (!live2d.model) return
const appWindow = getCurrentWebviewWindow()
const { innerWidth } = window
@@ -37,7 +37,7 @@ export function useModel() {
}),
)
live2d.currentModel?.scale.set(innerWidth / 612)
live2d.model?.scale.set(innerWidth / 612)
}
function handleKeyDown(value: string[]) {
@@ -49,7 +49,7 @@ export function useModel() {
}
async function handleMouseMove() {
if (modelState.mode !== 'STANDARD' || !live2d.currentModel) return
if (carStore.mode !== 'standard' || !live2d.model) return
const monitor = await getCursorMonitor()
@@ -79,9 +79,6 @@ export function useModel() {
}
return {
background,
motions: live2d.currentMotions,
expressions: live2d.currentExpressions,
handleLoad,
handleDestroy,
handleResize,

View File

@@ -0,0 +1,36 @@
import { theme } from 'ant-design-vue'
import { dash } from 'radash'
export function useThemeVars() {
const { defaultAlgorithm, darkAlgorithm, defaultConfig } = theme
const generateColorVars = () => {
const { token } = defaultConfig
const colors = [
defaultAlgorithm(token),
darkAlgorithm(token),
]
for (const [index, item] of colors.entries()) {
const isDark = index !== 0
const vars: Record<string, any> = {}
for (const [key, value] of Object.entries(item)) {
vars[`--ant-${dash(key)}`] = value
}
const style = document.createElement('style')
style.dataset.theme = isDark ? 'dark' : 'light'
const selector = isDark ? 'html.dark' : ':root'
const values = Object.entries(vars).map(([key, value]) => `${key}: ${value};`)
style.innerHTML = `${selector}{\n${values.join('\n')}\n}`
document.head.appendChild(style)
}
}
return {
generateColorVars,
}
}

View File

@@ -10,16 +10,17 @@ import { ref, watch } from 'vue'
import { GITHUB_LINK } from '../constants'
import { hideWindow, showWindow } from '../plugins/window'
import { useModelStore } from '../stores/model'
import { isMac } from '../utils/platform'
import { useCatStore } from '@/stores/cat'
const TRAY_ID = 'BONGO_CAT_TRAY'
export function useTray() {
const visible = ref(true)
const modelStore = useModelStore()
const catStore = useCatStore()
watch([visible, () => modelStore.mode], () => {
watch([visible, () => catStore.mode], () => {
updateTrayMenu()
})
@@ -78,16 +79,16 @@ export function useTray() {
items: await Promise.all([
CheckMenuItem.new({
text: '标准模式',
checked: modelStore.mode === 'STANDARD',
checked: catStore.mode === 'standard',
action: () => {
modelStore.mode = 'STANDARD'
catStore.mode = 'standard'
},
}),
CheckMenuItem.new({
text: '键盘模式',
checked: modelStore.mode === 'KEYBOARD',
checked: catStore.mode === 'keyboard',
action: () => {
modelStore.mode = 'KEYBOARD'
catStore.mode = 'keyboard'
},
}),
]),

View File

@@ -4,14 +4,4 @@ export const LISTEN_KEY = {
SHOW_WINDOW: 'show-window',
HIDE_WINDOW: 'hide-window',
DEVICE_CHANGED: 'device-changed',
} as const
export const MODEL_PATH = {
STANDARD: '/models/standard/cat.model3.json',
KEYBOARD: '/models/keyboard/cat.model3.json',
} as const
export const MODEL_BACKGROUND = {
STANDARD: '/images/backgrounds/standard.png',
KEYBOARD: '/images/backgrounds/keyboard.png',
} as const
}

View File

@@ -0,0 +1,32 @@
<script setup lang="ts">
import { Flex } from 'ant-design-vue'
import { onMounted } from 'vue'
import { useTray } from '@/composables/useTray'
import { preferenceRoutes } from '@/router'
import { isMac } from '@/utils/platform'
const { createTray } = useTray()
onMounted(async () => {
createTray()
})
</script>
<template>
<Flex class="h-screen">
<div class="h-full w-40 bg-color-8" :class="[isMac ? 'pt-8' : 'pt-4']" data-tauri-drag-region>
<Flex class="px-2" gap="small" vertical>
<RouterLink v-for="item in preferenceRoutes" :key="item.path" active-class="bg-primary! text-white! font-bold" class="h-10 flex items-center gap-2 rounded-lg hover:bg-color-6 px-4 text-color-1! transition" :to="item.path">
<div class="size-5" :class="item.meta?.icon" />
<span>{{ item.meta?.title }}</span>
</RouterLink>
</Flex>
</div>
<div class="flex-1 p-4" data-tauri-drag-region>
<RouterView />
</div>
</Flex>
</template>

View File

@@ -5,8 +5,8 @@ import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import 'virtual:uno.css'
import '@unocss/reset/tailwind-compat.css'
import './assets/css/global.css'
import 'ant-design-vue/dist/reset.css'
import './assets/css/global.scss'
const pinia = createPinia()
pinia.use(createPlugin({ saveOnChange: true }))

36
src/pages/about/index.vue Normal file
View File

@@ -0,0 +1,36 @@
<script setup lang="ts">
import { Button } from 'ant-design-vue'
import ProList from '@/components/pro-list/index.vue'
import ProListItem from '@/components/pro-list-item/index.vue'
import { GITHUB_LINK } from '@/constants'
import { useAppStore } from '@/stores/app'
const appStore = useAppStore()
</script>
<template>
<ProList title="关于软件">
<ProListItem :description="`版本v${appStore.version}`" :title="appStore.name">
<Button type="primary">
检查更新
</Button>
<template #icon>
<img class="size-12 drop-shadow" src="/images/logo.png">
</template>
</ProListItem>
<ProListItem title="开源地址">
<Button danger>
反馈问题
</Button>
<template #description>
<a :href="GITHUB_LINK" target="_blank">
{{ GITHUB_LINK }}
</a>
</template>
</ProListItem>
</ProList>
</template>

36
src/pages/cat/index.vue Normal file
View File

@@ -0,0 +1,36 @@
<script setup lang="ts">
import type { SelectProps } from 'ant-design-vue'
import { Select, Switch } from 'ant-design-vue'
import ProList from '@/components/pro-list/index.vue'
import ProListItem from '@/components/pro-list-item/index.vue'
import { useCatStore } from '@/stores/cat'
const catStore = useCatStore()
const modeList: SelectProps['options'] = [
{
label: '标准模式',
value: 'standard',
},
{
label: '键盘模式',
value: 'keyboard',
},
]
</script>
<template>
<ProList title="模式设置">
<ProListItem title="选择模式">
<Select v-model:value="catStore.mode" :options="modeList" title="选择模式" />
</ProListItem>
</ProList>
<ProList title="窗口设置">
<ProListItem description="支持窗口穿透" title="窗口穿透">
<Switch v-model:checked="catStore.penetrable" />
</ProListItem>
</ProList>
</template>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
import { Switch } from 'ant-design-vue'
import ProList from '@/components/pro-list/index.vue'
import ProListItem from '@/components/pro-list-item/index.vue'
</script>
<template>
<ProList title="更新设置">
<ProListItem title="自动检查更新">
<Switch />
</ProListItem>
</ProList>
</template>

View File

@@ -3,11 +3,14 @@ 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 { useDevice } from '@/composables/useDevice'
import { useModel } from '@/composables/useModel'
import { useCatStore } from '@/stores/cat'
const appWindow = getCurrentWebviewWindow()
const { pressedMouses, mousePosition, pressedKeys } = useDevice()
const { background, handleLoad, handleDestroy, handleResize, handleMouseDown, handleMouseMove, handleKeyDown } = useModel()
const { handleLoad, handleDestroy, handleResize, handleMouseDown, handleMouseMove, handleKeyDown } = useModel()
const catStore = useCatStore()
const resizing = ref(false)
@@ -33,14 +36,16 @@ watch(mousePosition, handleMouseMove)
watch(pressedKeys, handleKeyDown)
function handleWindowDrag() {
const appWindow = getCurrentWebviewWindow()
watch(() => catStore.penetrable, (value) => {
appWindow.setIgnoreCursorEvents(value)
})
function handleWindowDrag() {
appWindow.startDragging()
}
function resolveImageURL(key: string) {
return new URL(`../assets/images/keys/${key}.png`, import.meta.url).href
return new URL(`../../assets/images/keys/${key}.png`, import.meta.url).href
}
</script>
@@ -49,7 +54,7 @@ function resolveImageURL(key: string) {
class="relative children:(absolute h-screen w-screen)"
@mousedown="handleWindowDrag"
>
<img :src="background">
<img :src="`/images/backgrounds/${catStore.mode}.png`">
<canvas id="live2dCanvas" />

View File

@@ -0,0 +1,6 @@
<script setup lang="ts">
</script>
<template>
model
</template>

View File

@@ -1,17 +0,0 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import { useTray } from '../composables/useTray'
const { createTray } = useTray()
onMounted(() => {
createTray()
})
</script>
<template>
<div class="">
Preference
</div>
</template>

View File

@@ -1,9 +1,48 @@
// @unocss-include
import type { RouteRecordRaw } from 'vue-router'
import { createRouter, createWebHashHistory } from 'vue-router'
import Main from '../pages/main.vue'
import Preference from '../pages/preference.vue'
import Preference from '../layouts/preference/index.vue'
import General from '../pages/general/index.vue'
import Main from '../pages/main/index.vue'
const routes = [
export const preferenceRoutes: RouteRecordRaw[] = [
{
path: 'cat',
component: () => import('../pages/cat/index.vue'),
meta: {
title: '猫咪设置',
icon: 'i-solar:cat-outline',
},
},
{
path: 'general',
component: General,
meta: {
title: '通用设置',
icon: 'i-solar:settings-outline',
},
},
{
path: 'model',
component: () => import('../pages/model/index.vue'),
meta: {
title: '模型管理',
icon: 'i-solar:magic-stick-3-outline',
},
},
{
path: 'about',
component: () => import('../pages/about/index.vue'),
meta: {
title: '关于',
icon: 'i-solar:info-circle-outline',
},
},
]
const routes: Readonly<RouteRecordRaw[]> = [
{
path: '/',
component: Main,
@@ -11,6 +50,8 @@ const routes = [
{
path: '/preference',
component: Preference,
redirect: '/preference/cat',
children: preferenceRoutes,
},
]

18
src/stores/app.ts Normal file
View File

@@ -0,0 +1,18 @@
import { getName, getVersion } from '@tauri-apps/api/app'
import { defineStore } from 'pinia'
import { onMounted, ref } from 'vue'
export const useAppStore = defineStore('app', () => {
const name = ref('')
const version = ref('')
onMounted(async () => {
name.value = await getName()
version.value = await getVersion()
})
return {
name,
version,
}
})

12
src/stores/cat.ts Normal file
View File

@@ -0,0 +1,12 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useCatStore = defineStore('cat', () => {
const mode = ref<'standard' | 'keyboard'>('standard')
const penetrable = ref(false)
return {
mode,
penetrable,
}
})

6
src/stores/general.ts Normal file
View File

@@ -0,0 +1,6 @@
import { defineStore } from 'pinia'
export const useCatStore = defineStore('cat', () => {
return {
}
})

View File

@@ -1,12 +1,27 @@
import type { ModelType } from '../types/model'
import { defineStore } from 'pinia'
import { ref } from 'vue'
interface Motion {
Key: string
Name: string
File: string
Sound?: string
FadeInTime: number
FadeOutTime: number
}
interface Expression {
Key: string
Name: string
File: string
}
export const useModelStore = defineStore('model', () => {
const mode = ref<ModelType>('STANDARD')
const motions = ref<Motion[]>([])
const expressions = ref<Expression[]>([])
return {
mode,
motions,
expressions,
}
})

View File

@@ -1,3 +0,0 @@
import type { MODEL_PATH } from '../constants'
export type ModelType = keyof typeof MODEL_PATH

View File

@@ -1,30 +1,11 @@
import type { ModelType } from '../types/model'
import { Live2DModel } from 'pixi-live2d-display'
import { Application, Ticker } from 'pixi.js'
import { MODEL_PATH } from '../constants'
Live2DModel.registerTicker(Ticker)
interface Motion {
Name: string
File: string
Sound?: string
FadeInTime: number
FadeOutTime: number
}
interface Expression {
Name: string
File: string
}
class ModelManager {
class Live2d {
private app: Application | null = null
public currentModel: Live2DModel | null = null
public currentMotions = new Map<string, Motion[]>()
public currentExpressions = new Map<string, Expression>()
public model: Live2DModel | null = null
constructor() { }
@@ -40,14 +21,12 @@ class ModelManager {
})
}
public async load(type: ModelType) {
const modelPath = MODEL_PATH[type]
public async load(url: string) {
if (!this.app) {
this.mount()
}
const model = await Live2DModel.from(modelPath)
const model = await Live2DModel.from(url)
if (this.app?.stage.children.length) {
this.app.stage.removeChildren()
@@ -57,24 +36,23 @@ class ModelManager {
const { definitions, expressionManager } = model.internalModel.motionManager
this.currentModel = model
this.currentMotions = new Map(
Object.entries(definitions),
) as Map<string, Motion[]>
this.currentExpressions = new Map(
Object.entries(expressionManager?.definitions || {}),
) as Map<string, Expression>
this.model = model
return {
motions: Object.values(definitions).flat(),
expressions: expressionManager?.definitions ?? [],
}
}
public destroy() {
this.currentModel?.destroy()
this.model?.destroy()
}
public setParameterValue(id: string, value: number | boolean) {
return this.currentModel?.internalModel.coreModel.setParameterValueById(id, Number(value))
return this.model?.internalModel.coreModel.setParameterValueById(id, Number(value))
}
}
const live2d = new ModelManager()
const live2d = new Live2d()
export default live2d

2
src/utils/tauri.ts Normal file
View File

@@ -0,0 +1,2 @@
export function getMainWebviewWindow() {
}

View File

@@ -7,7 +7,6 @@
"baseUrl": ".",
"module": "ESNext",
/* Bundler mode */
"moduleResolution": "bundler",
"paths": {
@@ -24,7 +23,6 @@
"noEmit": true,
"isolatedModules": true,
"skipLibCheck": true
},
"references": [{ "path": "./tsconfig.node.json" }],
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]

View File

@@ -17,4 +17,29 @@ export default defineConfig({
applyVariable: ['--uno'],
}),
],
shortcuts: [
[/^bg-color-(\d+)$/, ([, d]) => `bg-bg-${d}`],
[/^text-color-(\d+)$/, ([, d]) => `text-text-${d}`],
[/^b-color-(\d+)$/, ([, d]) => `b-border-${d}`],
[/^(.*)-primary-(\d+)$/, ([, s, d]) => `${s}-[var(--ant-blue-${d})]`],
],
theme: {
colors: {
'bg-1': 'var(--ant-color-bg-layout)',
'bg-2': 'var(--ant-color-bg-container)',
'bg-3': 'var(--ant-color-bg-elevated)',
'bg-4': 'var(--ant-color-bg-spotlight)',
'bg-5': 'var(--ant-color-fill)',
'bg-6': 'var(--ant-color-fill-secondary)',
'bg-7': 'var(--ant-color-fill-tertiary)',
'bg-8': 'var(--ant-color-fill-quaternary)',
'text-1': 'var(--ant-color-text)',
'text-2': 'var(--ant-color-text-secondary)',
'text-3': 'var(--ant-color-text-tertiary)',
'text-4': 'var(--ant-color-text-quaternary)',
'border-1': 'var(--ant-color-border)',
'border-2': 'var(--ant-color-border-secondary)',
'primary': 'var(--ant-blue)',
},
},
})