scrcpy-mask/src/components/keyboard/KeySetting.vue
2024-05-05 15:48:34 +08:00

481 lines
14 KiB
Vue

<script setup lang="ts">
import { Settings, CloseCircle } from "@vicons/ionicons5";
import {
NButton,
NIcon,
NH4,
NSelect,
NFlex,
NP,
NModal,
NCard,
NInput,
useMessage,
} from "naive-ui";
import { computed, onActivated, onMounted, ref, watch } from "vue";
import { useGlobalStore } from "../../store/global";
import { Store } from "@tauri-apps/plugin-store";
import { loadDefaultKeyconfig } from "../../invoke";
import { KeyMappingConfig } from "../../keyMappingConfig";
import { useKeyboardStore } from "../../store/keyboard";
const store = useGlobalStore();
const keyboardStore = useKeyboardStore();
const localStore = new Store("store.bin");
const message = useMessage();
const showImportModal = ref(false);
const showRenameModal = ref(false);
const importModalInputValue = ref("");
const renameModalInputValue = ref("");
const keyMappingNameOptions = computed(() => {
return store.keyMappingConfigList.map((item, index) => {
return {
label: item.title,
value: index,
};
});
});
const curRelativeSize = computed(() => {
if (store.keyMappingConfigList.length === 0) {
return { w: 800, h: 600 };
}
return store.keyMappingConfigList[store.curKeyMappingIndex].relativeSize;
});
const keySettingPos = ref({ x: 100, y: 100 });
onMounted(async () => {
// loading keySettingPos from local store
let storedPos = await localStore.get<{ x: number; y: number }>(
"keySettingPos"
);
if (storedPos === null) {
await localStore.set("keySettingPos", keySettingPos.value);
storedPos = { x: 100, y: 100 };
}
// apply keySettingPos
const keyboardElement = document.getElementById(
"keyboardElement"
) as HTMLElement;
const maxWidth = keyboardElement.clientWidth - 40;
const maxHeight = keyboardElement.clientHeight - 40;
keySettingPos.value.x = Math.max(0, Math.min(storedPos.x, maxWidth));
keySettingPos.value.y = Math.max(0, Math.min(storedPos.y, maxHeight));
});
onActivated(() => {
// reset editKeyMappingList as the same as keyMappingList
resetKeyMappingConfig();
// check config relative size
checkConfigSize();
});
watch(
() => store.curKeyMappingIndex,
() => {
// check config relative size
checkConfigSize();
}
);
function dragHandler(downEvent: MouseEvent) {
const target = document.getElementById("keySettingBtn") as HTMLElement;
const keyboardElement = document.getElementById(
"keyboardElement"
) as HTMLElement;
const maxWidth = keyboardElement.clientWidth - 40;
const maxHeight = keyboardElement.clientHeight - 40;
const oldX = keySettingPos.value.x;
const oldY = keySettingPos.value.y;
const x = downEvent.clientX;
const y = downEvent.clientY;
let moveFlag = false;
const moveHandler = (moveEvent: MouseEvent) => {
const newX = oldX + moveEvent.clientX - x;
const newY = oldY + moveEvent.clientY - y;
keySettingPos.value.x = Math.max(0, Math.min(newX, maxWidth));
keySettingPos.value.y = Math.max(0, Math.min(newY, maxHeight));
};
const timer = setTimeout(() => {
moveFlag = true;
target.style.setProperty("cursor", "grabbing");
window.addEventListener("mousemove", moveHandler);
}, 1000);
const upHandler = () => {
clearTimeout(timer);
window.removeEventListener("mousemove", moveHandler);
window.removeEventListener("mouseup", upHandler);
if (moveFlag) {
// move up
target.style.setProperty("cursor", "pointer");
localStore.set("keySettingPos", keySettingPos.value);
} else {
// click up
keyboardStore.activeButtonIndex = -1;
keyboardStore.activeSteeringWheelButtonKeyIndex = -1;
keyboardStore.showSettingFlag = !keyboardStore.showSettingFlag;
}
};
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.setKeyMappingIndex(store.keyMappingConfigList.length - 1);
showImportModal.value = false;
localStore.set("keyMappingConfigList", store.keyMappingConfigList);
message.success("按键方案已导入");
}
async function importDefaultKeyMappingConfig() {
const data = await loadDefaultKeyconfig();
let defaultConfigs: KeyMappingConfig[];
let count = 0;
try {
defaultConfigs = JSON.parse(data);
for (const config of defaultConfigs) {
store.keyMappingConfigList.push(config);
count++;
}
} catch (e) {
console.error(e);
message.error("默认按键方案导入失败");
return;
}
localStore.set("keyMappingConfigList", store.keyMappingConfigList);
message.success(`已导入${count}个默认方案`);
}
function createKeyMappingConfig() {
if (keyboardStore.edited) {
message.error("请先保存或还原当前方案");
return;
}
const keyboardElement = document.getElementById(
"keyboardElement"
) as HTMLElement;
const newConfig: KeyMappingConfig = {
title: "新方案",
relativeSize: {
w: keyboardElement.clientWidth,
h: keyboardElement.clientHeight,
},
list: [],
};
store.keyMappingConfigList.push(newConfig);
store.setKeyMappingIndex(store.keyMappingConfigList.length - 1);
localStore.set("keyMappingConfigList", store.keyMappingConfigList);
message.success("新方案已创建");
}
function copyCurKeyMappingConfig() {
if (keyboardStore.edited) {
message.error("请先保存或还原当前方案");
return;
}
const curConfig = store.keyMappingConfigList[store.curKeyMappingIndex];
const newConfig: KeyMappingConfig = {
title: curConfig.title + "-副本",
relativeSize: curConfig.relativeSize,
list: curConfig.list,
};
store.keyMappingConfigList.push(newConfig);
keyboardStore.activeButtonIndex = -1;
keyboardStore.activeSteeringWheelButtonKeyIndex = -1;
store.setKeyMappingIndex(store.keyMappingConfigList.length - 1);
localStore.set("keyMappingConfigList", store.keyMappingConfigList);
message.success("方案已复制为:" + curConfig.title + "-副本");
}
function delCurKeyMappingConfig() {
if (store.keyMappingConfigList.length <= 1) {
message.error("至少保留一个方案");
return;
}
const title = store.keyMappingConfigList[store.curKeyMappingIndex].title;
store.keyMappingConfigList.splice(store.curKeyMappingIndex, 1);
// reset active and edit status
keyboardStore.activeButtonIndex = -1;
keyboardStore.activeSteeringWheelButtonKeyIndex = -1;
keyboardStore.edited = false;
store.setKeyMappingIndex(
store.curKeyMappingIndex > 0 ? store.curKeyMappingIndex - 1 : 0
);
localStore.set("keyMappingConfigList", store.keyMappingConfigList);
message.success("方案已删除:" + title);
}
function renameKeyMappingConfig() {
const newTitle = renameModalInputValue.value;
showRenameModal.value = false;
if (newTitle !== "") {
store.keyMappingConfigList[store.curKeyMappingIndex].title = newTitle;
localStore.set("keyMappingConfigList", store.keyMappingConfigList);
message.success("方案已重命名为:" + newTitle);
} else {
message.error("方案名不能为空");
}
}
function exportKeyMappingConfig() {
const config = store.keyMappingConfigList[store.curKeyMappingIndex];
const data = JSON.stringify(config, null, 2);
navigator.clipboard
.writeText(data)
.then(() => {
message.success("当前按键方案已导出到剪切板");
})
.catch((e) => {
console.error(e);
message.error("按键方案导出失败");
});
}
function saveKeyMappingConfig() {
if (store.applyEditKeyMappingList()) {
keyboardStore.edited = false;
} else {
message.error("存在重复按键,无法保存");
}
}
function checkConfigSize() {
const keyboardElement = document.getElementById(
"keyboardElement"
) as HTMLElement;
const curKeyMappingConfig =
store.keyMappingConfigList[store.curKeyMappingIndex];
const relativeSize = curKeyMappingConfig.relativeSize;
if (
keyboardElement.clientWidth !== relativeSize.w ||
keyboardElement.clientHeight !== relativeSize.h
) {
message.warning(
`请注意当前按键方案"${curKeyMappingConfig.title}"与蒙版尺寸不一致,若有需要可进行迁移`
);
}
}
function migrateKeyMappingConfig() {
if (keyboardStore.edited) {
message.error("请先保存或还原当前按键方案");
return;
}
const keyboardElement = document.getElementById(
"keyboardElement"
) as HTMLElement;
const curKeyMappingConfig =
store.keyMappingConfigList[store.curKeyMappingIndex];
const relativeSize = curKeyMappingConfig.relativeSize;
const sizeW = keyboardElement.clientWidth;
const sizeH = keyboardElement.clientHeight;
if (sizeW !== relativeSize.w || sizeH !== relativeSize.h) {
// deep clone
const newConfig = JSON.parse(JSON.stringify(curKeyMappingConfig));
// migrate relativeSize
newConfig.relativeSize = {
w: sizeW,
h: sizeH,
};
// migrate key pos
for (const keyMapping of newConfig.list) {
keyMapping.posX = Math.round((keyMapping.posX / relativeSize.w) * sizeW);
keyMapping.posY = Math.round((keyMapping.posY / relativeSize.h) * sizeH);
}
// migrate title
newConfig.title += "-迁移";
store.keyMappingConfigList.splice(
store.curKeyMappingIndex + 1,
0,
newConfig
);
message.success("已迁移到新方案:" + newConfig.title);
keyboardStore.activeButtonIndex = -1;
keyboardStore.activeSteeringWheelButtonKeyIndex = -1;
store.setKeyMappingIndex(store.curKeyMappingIndex + 1);
} else {
message.info("当前方案符合蒙版尺寸,无需迁移");
}
}
function selectKeyMappingConfig(index: number) {
if (keyboardStore.edited) {
message.error("请先保存或还原当前按键方案");
return;
}
keyboardStore.activeButtonIndex = -1;
keyboardStore.activeSteeringWheelButtonKeyIndex = -1;
store.setKeyMappingIndex(index);
}
function resetKeyMappingConfig() {
keyboardStore.activeButtonIndex = -1;
keyboardStore.activeSteeringWheelButtonKeyIndex = -1;
store.resetEditKeyMappingList();
keyboardStore.edited = false;
}
</script>
<template>
<NButton
circle
type="info"
size="large"
class="key-setting-btn"
id="keySettingBtn"
title="长按可拖动"
@mousedown="dragHandler"
:style="{
left: keySettingPos.x + 'px',
top: keySettingPos.y + 'px',
}"
>
<template #icon>
<NIcon><Settings /></NIcon>
</template>
</NButton>
<div
class="key-setting"
v-show="keyboardStore.showSettingFlag"
@mousedown="
keyboardStore.activeButtonIndex = -1;
keyboardStore.activeSteeringWheelButtonKeyIndex = -1;
"
>
<NButton
text
class="key-setting-close"
@click="keyboardStore.showSettingFlag = false"
>
<NIcon><CloseCircle></CloseCircle></NIcon>
</NButton>
<NH4 prefix="bar">按键方案</NH4>
<NSelect
:value="store.curKeyMappingIndex"
@update:value="selectKeyMappingConfig"
:options="keyMappingNameOptions"
/>
<NP style="margin-top: 20px">
Relative Size:{{ curRelativeSize.w }}x{{ curRelativeSize.h }}
</NP>
<NFlex style="margin-top: 20px">
<template v-if="keyboardStore.edited">
<NButton type="success" @click="saveKeyMappingConfig">保存方案</NButton>
<NButton type="error" @click="resetKeyMappingConfig">还原方案</NButton>
</template>
<NButton @click="createKeyMappingConfig">新建方案</NButton>
<NButton @click="copyCurKeyMappingConfig">复制方案</NButton>
<NButton @click="migrateKeyMappingConfig">迁移方案</NButton>
<NButton @click="delCurKeyMappingConfig">删除方案</NButton>
<NButton
@click="
showRenameModal = true;
renameModalInputValue =
store.keyMappingConfigList[store.curKeyMappingIndex].title;
"
>重命名</NButton
>
</NFlex>
<NH4 prefix="bar">其他</NH4>
<NFlex>
<NButton
@click="
showImportModal = true;
importModalInputValue = '';
"
>导入方案</NButton
>
<NButton @click="exportKeyMappingConfig">导出方案</NButton>
<NButton @click="importDefaultKeyMappingConfig">导入默认</NButton>
<NButton
@click="keyboardStore.showKeyInfoFlag = !keyboardStore.showKeyInfoFlag"
>按键信息</NButton
>
</NFlex>
<NP style="margin-top: 40px">提示:右键空白区域可添加按键</NP>
</div>
<NModal v-model:show="showImportModal">
<NCard style="width: 40%; height: 50%">
<NFlex vertical style="height: 100%">
<NInput
type="textarea"
style="flex-grow: 1"
placeholder="粘贴单个按键方案的JSON文本 (此处无法对按键方案的合法性进行判断, 请确保JSON内容正确)"
v-model:value="importModalInputValue"
round
clearable
/>
<NButton type="success" round @click="importKeyMappingConfig"
>导入</NButton
>
</NFlex>
</NCard>
</NModal>
<NModal v-model:show="showRenameModal">
<NCard style="width: 40%" title="重命名按键方案">
<NFlex vertical>
<NInput v-model:value="renameModalInputValue" clearable />
<NButton type="success" round @click="renameKeyMappingConfig"
>重命名</NButton
>
</NFlex>
</NCard>
</NModal>
</template>
<style scoped lang="scss">
.key-setting-btn {
position: absolute;
z-index: 9;
}
.key-setting {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 70%;
height: 70%;
margin: auto;
background-color: var(--content-bg-color);
padding: 0 50px;
border: 1px solid var(--gray-color);
border-radius: 10px;
z-index: 10;
.key-setting-close {
font-size: 24px;
position: absolute;
right: 10px;
top: 10px;
}
}
</style>