feat(KeyBoard+Mask+hotkey): load local key mapping config

This commit is contained in:
AkiChase 2024-04-26 23:07:26 +08:00
parent 8730e47672
commit f1f3b2862c
7 changed files with 287 additions and 287 deletions

View File

@ -7,6 +7,45 @@ import {
NMessageProvider, NMessageProvider,
NDialogProvider, NDialogProvider,
} from "naive-ui"; } from "naive-ui";
import { Store } from "@tauri-apps/plugin-store";
import { getCurrent } from "@tauri-apps/api/window";
import { KeyMappingConfig } from "./keyMappingConfig";
import { onMounted } from "vue";
import { useGlobalStore } from "./store/global";
const store = useGlobalStore();
onMounted(async () => {
// loading keyMappingConfigList from local store
const localStore = new Store("store.bin");
let keyMappingConfigList: KeyMappingConfig[] | null = await localStore.get(
"keyMappingConfigList"
);
if (keyMappingConfigList === null || keyMappingConfigList.length === 0) {
keyMappingConfigList = [
{
relativeSize: await calMaskSize(),
title: "空白方案",
list: [],
},
];
}
store.keyMappingConfigList = keyMappingConfigList;
});
async function calMaskSize() {
const appWindow = getCurrent();
const ml = 70;
const mt = 30;
let size = (await appWindow.outerSize()).toLogical(
await appWindow.scaleFactor()
);
return {
w: Math.round(size.width) - ml,
h: Math.round(size.height) - mt,
};
}
</script> </script>
<template> <template>
@ -37,7 +76,7 @@ import {
"sidebar content"; "sidebar content";
} }
.n-scrollbar-container{ .n-scrollbar-container {
background-color: var(--bg-color); background-color: var(--bg-color);
} }
@ -45,3 +84,6 @@ import {
height: 100%; height: 100%;
} }
</style> </style>
import { getCurrent } from "@tauri-apps/api/webview"; import { Store } from
"@tauri-apps/plugin-store"; import { KeyMappingConfig } from
"./keyMappingConfig";

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { Ref, onActivated, ref } from "vue"; import { Ref, onActivated, ref } from "vue";
import { NDialog } from "naive-ui"; import { NDialog, useMessage } from "naive-ui";
import { useGlobalStore } from "../store/global"; import { useGlobalStore } from "../store/global";
import { onBeforeRouteLeave, useRouter } from "vue-router"; import { onBeforeRouteLeave, useRouter } from "vue-router";
import { import {
@ -16,13 +16,14 @@ const maskRef = ref<HTMLElement | null>(null);
const store = useGlobalStore(); const store = useGlobalStore();
const router = useRouter(); const router = useRouter();
const message = useMessage();
const renderedButtons: Ref<any[]> = ref([]); const renderedButtons: Ref<any[]> = ref([]);
onBeforeRouteLeave(() => { onBeforeRouteLeave(() => {
if (maskRef.value && store.controledDevice) { if (maskRef.value && store.controledDevice) {
unlistenToKeyEvent(); unlistenToKeyEvent();
clearShortcuts(); clearShortcuts(maskRef.value);
} }
}); });
@ -40,8 +41,16 @@ onActivated(async () => {
); );
refreshKeyMappingButton(); refreshKeyMappingButton();
applyShortcuts(maskRef.value, store.curKeyMappingConfig); if (
listenToKeyEvent(); applyShortcuts(
maskRef.value,
store.keyMappingConfigList[store.curKeyMappingIndex]
)
) {
listenToKeyEvent();
}else{
message.error("")
}
} }
}); });
@ -50,7 +59,8 @@ function toStartServer() {
} }
function refreshKeyMappingButton() { function refreshKeyMappingButton() {
const curKeyMappingConfig = store.curKeyMappingConfig; const curKeyMappingConfig =
store.keyMappingConfigList[store.curKeyMappingIndex];
const relativeSize = curKeyMappingConfig.relativeSize; const relativeSize = curKeyMappingConfig.relativeSize;
const maskSizeW = maskRef?.value?.clientWidth; const maskSizeW = maskRef?.value?.clientWidth;
const maskSizeH = maskRef?.value?.clientHeight; const maskSizeH = maskRef?.value?.clientHeight;
@ -61,12 +71,14 @@ function refreshKeyMappingButton() {
y: Math.round((y / relativeSize.h) * maskSizeH), y: Math.round((y / relativeSize.h) * maskSizeH),
}; };
}; };
const buttons: any = []; const buttons = [];
for (let keyObject of curKeyMappingConfig.list) { for (let keyObject of curKeyMappingConfig.list) {
const { x, y } = relativePosToMaskPos(keyObject.posX, keyObject.posY); const { x, y } = relativePosToMaskPos(keyObject.posX, keyObject.posY);
keyObject.x = x; buttons.push({
keyObject.y = y; ...keyObject,
buttons.push(keyObject); x,
y,
});
} }
renderedButtons.value = buttons; renderedButtons.value = buttons;
} }

View File

@ -3,7 +3,7 @@ import { ref } from "vue";
import KeyInfo from "./KeyInfo.vue"; import KeyInfo from "./KeyInfo.vue";
import KeySetting from "./KeySetting.vue"; import KeySetting from "./KeySetting.vue";
// TODO 4-. global storecurKeyMappingConfig // TODO 3. 退storesetlocalStore
// TODO 4. // TODO 4.
// TODO 5. // TODO 5.
@ -11,10 +11,10 @@ const keyInfoShowFlag = ref(false);
</script> </script>
<template> <template>
<div id="keyboardElement" class="keyboard"> <div id="keyboardElement" class="keyboard">
<KeySetting v-model="keyInfoShowFlag"/> <KeySetting v-model="keyInfoShowFlag" />
<KeyInfo v-model="keyInfoShowFlag" /> <KeyInfo v-model="keyInfoShowFlag" />
</div> </div>
</template> </template>
<style scoped> <style scoped>

View File

@ -1,18 +1,41 @@
<script setup lang="ts"> <script setup lang="ts">
import { Settings, CloseCircle } from "@vicons/ionicons5"; import { Settings, CloseCircle } from "@vicons/ionicons5";
import { NButton, NIcon, NH4, NSelect, NFlex } from "naive-ui"; import {
import { ref } from "vue"; NButton,
NIcon,
NH4,
NSelect,
NFlex,
NP,
NModal,
NCard,
NInput,
useMessage,
} from "naive-ui";
import { computed, ref } from "vue";
import { useGlobalStore } from "../../store/global";
const store = useGlobalStore();
const message = useMessage();
const showKeyInfoFlag = defineModel({ required: true }); const showKeyInfoFlag = defineModel({ required: true });
const showSetting = ref(false); const showSetting = ref(false);
const selectedKeyMappingName = ref("王者荣耀-暃"); const showImportModal = ref(false);
const KeyMappingNameOptions = ref([ const importModalInputValue = ref("");
{
label: "王者荣耀-暃", const keyMappingNameOptions = computed(() => {
value: "王者荣耀-暃", return store.keyMappingConfigList.map((item, index) => {
}, return {
]); label: item.title,
value: index,
};
});
});
const curKeyMappingrelativeSize = computed(() => {
return store.keyMappingConfigList[store.curKeyMappingIndex].relativeSize;
});
function dragHandler(downEvent: MouseEvent) { function dragHandler(downEvent: MouseEvent) {
const target = document.querySelector( const target = document.querySelector(
@ -52,6 +75,20 @@ function dragHandler(downEvent: MouseEvent) {
}; };
window.addEventListener("mouseup", upHandler); window.addEventListener("mouseup", upHandler);
} }
function importKeyMappingConfig() {
let keyMappingConfig;
try {
keyMappingConfig = JSON.parse(importModalInputValue.value);
} catch (e) {
console.error(e);
message.error("导入失败");
return;
}
store.keyMappingConfigList.push(keyMappingConfig);
store.curKeyMappingIndex = store.keyMappingConfigList.length - 1;
showImportModal.value = false;
}
</script> </script>
<template> <template>
@ -72,9 +109,13 @@ function dragHandler(downEvent: MouseEvent) {
</NButton> </NButton>
<NH4 prefix="bar">按键方案</NH4> <NH4 prefix="bar">按键方案</NH4>
<NSelect <NSelect
v-model:value="selectedKeyMappingName" v-model:value="store.curKeyMappingIndex"
:options="KeyMappingNameOptions" :options="keyMappingNameOptions"
/> />
<NP>
Relative Size: {{ curKeyMappingrelativeSize.w }} x
{{ curKeyMappingrelativeSize.h }}
</NP>
<NFlex style="margin-top: 20px"> <NFlex style="margin-top: 20px">
<NButton>新建方案</NButton> <NButton>新建方案</NButton>
<NButton>复制方案</NButton> <NButton>复制方案</NButton>
@ -83,11 +124,26 @@ function dragHandler(downEvent: MouseEvent) {
</NFlex> </NFlex>
<NH4 prefix="bar">其他</NH4> <NH4 prefix="bar">其他</NH4>
<NFlex> <NFlex>
<NButton>导入</NButton> <NButton @click="showImportModal = true">导入</NButton>
<NButton>导出</NButton> <NButton>导出</NButton>
<NButton @click="showKeyInfoFlag = !showKeyInfoFlag">按键信息</NButton> <NButton @click="showKeyInfoFlag = !showKeyInfoFlag">按键信息</NButton>
</NFlex> </NFlex>
</div> </div>
<NModal v-model:show="showImportModal">
<NCard style="width: 40%; height: 50%" title="导入按键方案">
<NFlex vertical style="height: 100%">
<NInput
type="textarea"
style="flex-grow: 1"
placeholder="粘贴单个按键方案的JSON文本 (此处无法对按键方案的合法性进行判断, 请确保JSON内容正确)"
v-model:value="importModalInputValue"
round
clearable
/>
<NButton round @click="importKeyMappingConfig">导入</NButton>
</NFlex>
</NCard>
</NModal>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -5,6 +5,18 @@ import {
swipe, swipe,
touch, touch,
} from "./frontcommand/scrcpyMaskCmd"; } from "./frontcommand/scrcpyMaskCmd";
import {
KeyCancelSkill,
KeyDirectionalSkill,
KeyDirectionlessSkill,
KeyMacro,
KeyMacroList,
KeyMappingConfig,
KeyObservation,
KeySteeringWheel,
KeyTap,
KeyTriggerWhenPressedSkill,
} from "./keyMappingConfig";
function clientxToPosx(clientx: number) { function clientxToPosx(clientx: number) {
return clientx < 70 return clientx < 70
@ -780,7 +792,11 @@ function addShortcut(
* ]); * ]);
*/ */
async function execMacro(relativeSize: { w: number; h: number }, macro: any[]) { async function execMacro(
relativeSize: { w: number; h: number },
macro: KeyMacroList
) {
if (macro === null) return;
for (const cmd of macro) { for (const cmd of macro) {
if (!cmd.hasOwnProperty("type") || !cmd.hasOwnProperty("args")) { if (!cmd.hasOwnProperty("type") || !cmd.hasOwnProperty("args")) {
console.error("Invalid command: ", cmd); console.error("Invalid command: ", cmd);
@ -870,12 +886,19 @@ function execLoopCB() {
if (loopFlag) requestAnimationFrame(execLoopCB); if (loopFlag) requestAnimationFrame(execLoopCB);
} }
function applyKeyMappingConfigShortcuts(keyMappingConfig: any): boolean { // change ts type
function asType<T>(_val: any): asserts _val is T {}
function applyKeyMappingConfigShortcuts(
keyMappingConfig: KeyMappingConfig,
element: HTMLElement
): boolean {
try { try {
const relativeSize = keyMappingConfig.relativeSize; const relativeSize = keyMappingConfig.relativeSize;
for (const item of keyMappingConfig.list) { for (const item of keyMappingConfig.list) {
switch (item.type) { switch (item.type) {
case "SteeringWheel": case "SteeringWheel":
asType<KeySteeringWheel>(item);
addSteeringWheelKeyboardShortcuts( addSteeringWheelKeyboardShortcuts(
item.key, item.key,
relativeSize, relativeSize,
@ -886,6 +909,7 @@ function applyKeyMappingConfigShortcuts(keyMappingConfig: any): boolean {
); );
break; break;
case "DirectionalSkill": case "DirectionalSkill":
asType<KeyDirectionalSkill>(item);
addDirectionalSkillShortcuts( addDirectionalSkillShortcuts(
item.key, item.key,
relativeSize, relativeSize,
@ -896,6 +920,7 @@ function applyKeyMappingConfigShortcuts(keyMappingConfig: any): boolean {
); );
break; break;
case "DirectionlessSkill": case "DirectionlessSkill":
asType<KeyDirectionlessSkill>(item);
addDirectionlessSkillShortcuts( addDirectionlessSkillShortcuts(
item.key, item.key,
relativeSize, relativeSize,
@ -905,6 +930,7 @@ function applyKeyMappingConfigShortcuts(keyMappingConfig: any): boolean {
); );
break; break;
case "CancelSkill": case "CancelSkill":
asType<KeyCancelSkill>(item);
addCancelSkillShortcuts( addCancelSkillShortcuts(
item.key, item.key,
relativeSize, relativeSize,
@ -914,6 +940,7 @@ function applyKeyMappingConfigShortcuts(keyMappingConfig: any): boolean {
); );
break; break;
case "Tap": case "Tap":
asType<KeyTap>(item);
addTapShortcuts( addTapShortcuts(
item.key, item.key,
relativeSize, relativeSize,
@ -924,6 +951,7 @@ function applyKeyMappingConfigShortcuts(keyMappingConfig: any): boolean {
); );
break; break;
case "TriggerWhenPressedSkill": case "TriggerWhenPressedSkill":
asType<KeyTriggerWhenPressedSkill>(item);
addTriggerWhenPressedSkillShortcuts( addTriggerWhenPressedSkillShortcuts(
item.key, item.key,
relativeSize, relativeSize,
@ -935,6 +963,7 @@ function applyKeyMappingConfigShortcuts(keyMappingConfig: any): boolean {
); );
break; break;
case "Observation": case "Observation":
asType<KeyObservation>(item);
addObservationShortcuts( addObservationShortcuts(
item.key, item.key,
relativeSize, relativeSize,
@ -945,6 +974,7 @@ function applyKeyMappingConfigShortcuts(keyMappingConfig: any): boolean {
); );
break; break;
case "Macro": case "Macro":
asType<KeyMacro>(item);
addShortcut( addShortcut(
item.key, item.key,
item.macro.down === null item.macro.down === null
@ -972,7 +1002,7 @@ function applyKeyMappingConfigShortcuts(keyMappingConfig: any): boolean {
return true; return true;
} catch (e) { } catch (e) {
console.error("Invalid keyMappingConfig: ", keyMappingConfig, e); console.error("Invalid keyMappingConfig: ", keyMappingConfig, e);
clearShortcuts(); clearShortcuts(element);
return false; return false;
} }
} }
@ -982,7 +1012,6 @@ export function listenToKeyEvent() {
document.addEventListener("keyup", keyupHandler); document.addEventListener("keyup", keyupHandler);
loopFlag = true; loopFlag = true;
execLoopCB(); execLoopCB();
// setInterval(()=>console.log(loopDownKeyCBMap), 3000);
} }
export function unlistenToKeyEvent() { export function unlistenToKeyEvent() {
@ -991,7 +1020,13 @@ export function unlistenToKeyEvent() {
loopFlag = false; loopFlag = false;
} }
export function clearShortcuts() { export function clearShortcuts(element: HTMLElement) {
element.removeEventListener("mousedown", handleMouseDown);
element.removeEventListener("mousemove", handleMouseMove);
element.removeEventListener("mouseup", handleMouseUp);
element.removeEventListener("wheel", handleMouseWheel);
element.removeEventListener("mouseout", handleMouseUp);
downKeyMap.clear(); downKeyMap.clear();
downKeyCBMap.clear(); downKeyCBMap.clear();
loopDownKeyCBMap.clear(); loopDownKeyCBMap.clear();
@ -1009,7 +1044,10 @@ export function updateScreenSizeAndMaskArea(
maskSizeH = maskArea[1]; maskSizeH = maskArea[1];
} }
export function applyShortcuts(element: HTMLElement, keyMappingConfig: any) { export function applyShortcuts(
element: HTMLElement,
keyMappingConfig: KeyMappingConfig
) {
element.addEventListener("mousedown", handleMouseDown); element.addEventListener("mousedown", handleMouseDown);
element.addEventListener("mousemove", handleMouseMove); element.addEventListener("mousemove", handleMouseMove);
element.addEventListener("mouseup", handleMouseUp); element.addEventListener("mouseup", handleMouseUp);
@ -1017,5 +1055,5 @@ export function applyShortcuts(element: HTMLElement, keyMappingConfig: any) {
element.addEventListener("mouseout", handleMouseUp); // mouse out of the element as mouse up element.addEventListener("mouseout", handleMouseUp); // mouse out of the element as mouse up
addClickShortcuts("M0", 0); addClickShortcuts("M0", 0);
applyKeyMappingConfigShortcuts(keyMappingConfig); return applyKeyMappingConfigShortcuts(keyMappingConfig, element);
} }

100
src/keyMappingConfig.ts Normal file
View File

@ -0,0 +1,100 @@
interface Key {
type:
| "SteeringWheel"
| "DirectionalSkill"
| "DirectionlessSkill"
| "CancelSkill"
| "Tap"
| "TriggerWhenPressedSkill"
| "Observation"
| "Macro";
note: string;
posX: number;
posY: number;
pointerId: number;
}
interface KeySteeringWheel extends Key {
key: {
left: string;
right: string;
up: string;
down: string;
};
offset: number;
}
interface KeyDirectionalSkill extends Key {
key: string;
range: number;
}
interface KeyDirectionlessSkill extends Key {
key: string;
}
interface KeyCancelSkill extends Key {
key: string;
}
interface KeyTap extends Key {
key: string;
time: number;
}
interface KeyTriggerWhenPressedSkill extends Key {
key: string;
directional: boolean;
rangeOrTime: number;
}
interface KeyObservation extends Key {
key: string;
scale: number;
}
type KeyMacroType = "touch" | "sleep" | "swipe";
type KeyMacroArgs = any[];
type KeyMacroList = Array<{
type: KeyMacroType;
args: KeyMacroArgs;
}> | null;
interface KeyMacro extends Key {
key: string;
macro: {
down: KeyMacroList;
loop: KeyMacroList;
up: KeyMacroList;
};
}
type KeyMapping =
| KeySteeringWheel
| KeyDirectionalSkill
| KeyDirectionlessSkill
| KeyCancelSkill
| KeyTap
| KeyTriggerWhenPressedSkill
| KeyObservation
| KeyMacro;
interface KeyMappingConfig {
relativeSize: { w: number; h: number };
title: string;
list: KeyMapping[];
}
export type {
KeyMacroList,
KeySteeringWheel,
KeyDirectionalSkill,
KeyDirectionlessSkill,
KeyCancelSkill,
KeyTap,
KeyTriggerWhenPressedSkill,
KeyObservation,
KeyMacro,
KeyMapping,
KeyMappingConfig,
};

View File

@ -1,6 +1,7 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { Ref, ref } from "vue"; import { Ref, ref } from "vue";
import { Device } from "../invoke"; import { Device } from "../invoke";
import { KeyMappingConfig } from "../keyMappingConfig";
export const useGlobalStore = defineStore("counter", () => { export const useGlobalStore = defineStore("counter", () => {
const showLoadingRef = ref(false); const showLoadingRef = ref(false);
@ -22,258 +23,8 @@ export const useGlobalStore = defineStore("counter", () => {
const controledDevice: Ref<ControledDevice | null> = ref(null); const controledDevice: Ref<ControledDevice | null> = ref(null);
const curKeyMappingConfig: any = { const keyMappingConfigList: KeyMappingConfig[] = [];
relativeSize: { w: 1280, h: 720 }, const curKeyMappingIndex = ref(0);
title: "王者荣耀-暃",
list: [
{
type: "SteeringWheel",
note: "方向轮盘",
key: {
left: "KeyA",
right: "KeyD",
up: "KeyW",
down: "KeyS",
},
posX: 180,
posY: 560,
offset: 100,
pointerId: 1,
},
{
type: "DirectionalSkill",
note: "技能1",
key: "KeyQ",
posX: 950,
posY: 610,
range: 200,
pointerId: 2,
},
{
type: "DirectionalSkill",
note: "技能2",
key: "AltLeft",
posX: 1025,
posY: 500,
range: 200,
pointerId: 2,
},
{
type: "DirectionalSkill",
note: "技能3",
key: "KeyE",
posX: 1160,
posY: 420,
range: 200,
pointerId: 2,
},
{
type: "TriggerWhenPressedSkill",
note: "技能3",
key: "M4",
posX: 1160,
posY: 420,
directional: true,
rangeOrTime: 0,
pointerId: 2,
},
{
type: "DirectionlessSkill",
note: "无方向装备技能",
key: "M1",
posX: 1150,
posY: 280,
pointerId: 2,
},
{
type: "CancelSkill",
note: "取消技能",
key: "Space",
posX: 1160,
posY: 140,
pointerId: 2,
},
{
type: "Tap",
note: "回城",
key: "KeyB",
time: 80,
posX: 650,
posY: 650,
pointerId: 3,
},
{
type: "Tap",
note: "回复",
key: "KeyC",
time: 80,
posX: 740,
posY: 650,
pointerId: 3,
},
{
type: "DirectionalSkill",
note: "召唤师技能",
key: "KeyF",
posX: 840,
posY: 650,
range: 200,
pointerId: 2,
},
{
type: "TriggerWhenPressedSkill",
note: "无方向召唤师技能",
key: "ControlLeft",
posX: 840,
posY: 650,
directional: false,
rangeOrTime: 80,
pointerId: 3,
},
{
type: "Tap",
note: "攻击",
key: "M2",
time: 80,
posX: 1165,
posY: 620,
pointerId: 3,
},
{
type: "Tap",
note: "技能1升级",
key: "Digit1",
time: 80,
posX: 880,
posY: 560,
pointerId: 3,
},
{
type: "Tap",
note: "技能2升级",
key: "Digit2",
time: 80,
posX: 960,
posY: 430,
pointerId: 3,
},
{
type: "Tap",
note: "技能3升级",
key: "Digit3",
time: 80,
posX: 1090,
posY: 350,
pointerId: 3,
},
{
type: "Tap",
note: "快速购买1",
key: "Digit5",
time: 80,
posX: 130,
posY: 300,
pointerId: 3,
},
{
type: "Tap",
note: "快速购买2",
key: "Digit6",
time: 80,
posX: 130,
posY: 370,
pointerId: 3,
},
{
type: "TriggerWhenPressedSkill",
note: "装备技能",
key: "WheelDown",
posX: 1150,
posY: 280,
directional: false,
rangeOrTime: 80,
pointerId: 3,
},
{
type: "Observation",
note: "观察",
key: "M3",
posX: 1000,
posY: 200,
scale: 0.5,
pointerId: 4,
},
{
type: "Macro",
note: "战绩面板",
key: "Tab",
macro: {
down: [
{
type: "touch",
args: ["default", 5, 1185, 40, 80],
},
],
loop: null,
up: [
{
type: "touch",
args: ["default", 5, 1220, 100, 80],
},
],
},
posX: 1185,
posY: 40,
pointerId: 5,
},
{
type: "Macro",
note: "商店",
key: "ShiftLeft",
macro: {
down: [
{
type: "touch",
args: ["default", 5, 40, 300, 80],
},
],
loop: null,
up: [
{
type: "touch",
args: ["default", 5, 1200, 60, 80],
},
],
},
posX: 40,
posY: 300,
pointerId: 5,
},
{
type: "Macro",
note: "地图",
key: "KeyZ",
macro: {
down: [
{
type: "touch",
args: ["default", 5, 250, 230, 80],
},
],
loop: null,
up: [
{
type: "touch",
args: ["default", 5, 640, 150, 80],
},
],
},
posX: 250,
posY: 230,
pointerId: 5,
},
],
};
return { return {
showLoading, showLoading,
@ -282,6 +33,7 @@ export const useGlobalStore = defineStore("counter", () => {
controledDevice, controledDevice,
screenSizeW, screenSizeW,
screenSizeH, screenSizeH,
curKeyMappingConfig, keyMappingConfigList,
curKeyMappingIndex,
}; };
}); });