feat: frontend add virtual matrix key
This commit is contained in:
42
src/components/equipments/BaseBoard.vue
Normal file
42
src/components/equipments/BaseBoard.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" :width="width" :height="height" :class="$attrs">
|
||||
<rect :width="width" :height="height" :rx="props.roundCorner" fill="#222222" />
|
||||
</svg>
|
||||
<Teleport to="#ComponentCapabilities" v-if="selectecComponentID === props.componentId && !!slot.default">
|
||||
<slot></slot>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, inject } from "vue";
|
||||
import { CanvasCurrentSelectedComponentID } from "../InjectKeys";
|
||||
|
||||
export interface Props {
|
||||
size?: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
roundCorner?: number;
|
||||
componentId?: string;
|
||||
}
|
||||
|
||||
const slot = defineSlots();
|
||||
const props = withDefaults(defineProps<Props>(), getDefaultProps());
|
||||
const selectecComponentID = inject(CanvasCurrentSelectedComponentID, ref(null));
|
||||
|
||||
// 计算实际宽高
|
||||
const width = computed(() => props.width * props.size);
|
||||
const height = computed(() => props.height * props.size);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export function getDefaultProps(): Props {
|
||||
return {
|
||||
size: 1,
|
||||
width: 200,
|
||||
height: 200,
|
||||
roundCorner: 20,
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss"></style>
|
||||
@@ -1,16 +1,28 @@
|
||||
<template>
|
||||
<div class="button-container" :style="{
|
||||
width: width + 'px',
|
||||
height: height + 'px',
|
||||
position: 'relative',
|
||||
}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" :width="width" :height="height" viewBox="400 400 800 800"
|
||||
class="mechanical-button">
|
||||
<div
|
||||
class="button-container"
|
||||
:style="{
|
||||
width: width + 'px',
|
||||
height: height + 'px',
|
||||
position: 'relative',
|
||||
}"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
:width="width"
|
||||
:height="height"
|
||||
viewBox="400 400 800 800"
|
||||
class="mechanical-button"
|
||||
>
|
||||
<!-- defs 和按钮底座保持不变 -->
|
||||
<defs>
|
||||
<filter id="btn-shadow">
|
||||
<feGaussianBlur in="SourceAlpha" stdDeviation="20" result="blur" />
|
||||
<feColorMatrix result="bluralpha" type="matrix" :values="colorMatrix" />
|
||||
<feColorMatrix
|
||||
result="bluralpha"
|
||||
type="matrix"
|
||||
:values="colorMatrix"
|
||||
/>
|
||||
<feOffset in="bluralpha" dx="20" dy="20" result="offsetBlur" />
|
||||
<feMerge>
|
||||
<feMergeNode in="offsetBlur" />
|
||||
@@ -37,42 +49,81 @@
|
||||
<circle r="20" cx="525" cy="1075" fill="#171717" />
|
||||
|
||||
<!-- 按钮主体 -->
|
||||
<circle r="220" cx="800" cy="800" fill="black" filter="url(#btn-shadow)" />
|
||||
<circle :r="btnHeight" cx="800" cy="800" :fill="isKeyPressed ? 'url(#pressed)' : 'url(#normal)'"
|
||||
fill-opacity="0.9" @mousedown="toggleButtonState(true)" @mouseup="toggleButtonState(false)"
|
||||
@mouseleave="toggleButtonState(false)" style="
|
||||
<circle
|
||||
r="220"
|
||||
cx="800"
|
||||
cy="800"
|
||||
fill="black"
|
||||
filter="url(#btn-shadow)"
|
||||
/>
|
||||
<circle
|
||||
:r="btnHeight"
|
||||
cx="800"
|
||||
cy="800"
|
||||
:fill="isKeyPressed ? 'url(#pressed)' : 'url(#normal)'"
|
||||
fill-opacity="0.9"
|
||||
@mousedown="toggleButtonState(true)"
|
||||
@mouseup="toggleButtonState(false)"
|
||||
@mouseleave="toggleButtonState(false)"
|
||||
style="
|
||||
pointer-events: auto;
|
||||
transition: all 20ms ease-in-out;
|
||||
cursor: pointer;
|
||||
" />
|
||||
"
|
||||
/>
|
||||
<!-- 按键文字 - 仅显示绑定的按键 -->
|
||||
<text v-if="bindKeyDisplay" x="800" y="800" font-size="310" text-anchor="middle" dominant-baseline="central"
|
||||
fill="#ccc" style="
|
||||
<text
|
||||
v-if="bindKeyDisplay"
|
||||
x="800"
|
||||
y="800"
|
||||
font-size="310"
|
||||
text-anchor="middle"
|
||||
dominant-baseline="central"
|
||||
fill="#ccc"
|
||||
style="
|
||||
font-family: Arial;
|
||||
filter: url(#btn-shadow);
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
mix-blend-mode: overlay;
|
||||
">
|
||||
"
|
||||
>
|
||||
{{ bindKeyDisplay }}
|
||||
</text>
|
||||
</svg>
|
||||
|
||||
<!-- 渲染自定义引脚数组 -->
|
||||
<div v-for="pin in props.pins" :key="pin.pinId" :style="{
|
||||
position: 'absolute',
|
||||
left: `${pin.x * props.size}px`,
|
||||
top: `${pin.y * props.size}px`,
|
||||
transform: 'translate(-50%, -50%)',
|
||||
zIndex: 3,
|
||||
pointerEvents: 'auto',
|
||||
}" :data-pin-wrapper="`${pin.pinId}`" :data-pin-x="`${pin.x * props.size}`"
|
||||
:data-pin-y="`${pin.y * props.size}`">
|
||||
<Pin :ref="(el) => {
|
||||
if (el) pinRefs[pin.pinId] = el;
|
||||
}
|
||||
" direction="output" type="digital" :label="pin.pinId" :constraint="pin.constraint" :pinId="pin.pinId"
|
||||
:size="0.8" :componentId="props.componentId" @value-change="handlePinValueChange" @pin-click="handlePinClick" />
|
||||
<div
|
||||
v-for="pin in props.pins"
|
||||
:key="pin.pinId"
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
left: `${pin.x * props.size}px`,
|
||||
top: `${pin.y * props.size}px`,
|
||||
transform: 'translate(-50%, -50%)',
|
||||
zIndex: 3,
|
||||
pointerEvents: 'auto',
|
||||
}"
|
||||
:data-pin-wrapper="`${pin.pinId}`"
|
||||
:data-pin-x="`${pin.x * props.size}`"
|
||||
:data-pin-y="`${pin.y * props.size}`"
|
||||
>
|
||||
<Pin
|
||||
:ref="
|
||||
(el) => {
|
||||
if (el) pinRefs[pin.pinId] = el;
|
||||
}
|
||||
"
|
||||
direction="output"
|
||||
type="digital"
|
||||
:label="pin.pinId"
|
||||
:constraint="pin.constraint"
|
||||
:pinId="pin.pinId"
|
||||
:size="0.8"
|
||||
:componentId="props.componentId"
|
||||
@value-change="handlePinValueChange"
|
||||
@pin-click="handlePinClick"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -80,16 +131,16 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, computed } from "vue";
|
||||
import Pin from "./Pin.vue";
|
||||
import { useEquipments } from "@/stores/equipments";
|
||||
import { useDialogStore } from "@/stores/dialog";
|
||||
import { useConstraintsStore } from "../../stores/constraints";
|
||||
const { notifyConstraintChange } = useConstraintsStore();
|
||||
|
||||
// 存储多个Pin引用
|
||||
const pinRefs = ref<Record<string, any>>({});
|
||||
import { isNull, isUndefined } from "mathjs";
|
||||
import z from "zod";
|
||||
import { toNumber } from "lodash";
|
||||
|
||||
// 按钮特有属性
|
||||
interface ButtonProps {
|
||||
size?: number;
|
||||
bindKey?: string;
|
||||
export interface ButtonProps {
|
||||
size: number;
|
||||
componentId?: string;
|
||||
pins?: {
|
||||
pinId: string;
|
||||
@@ -97,21 +148,20 @@ interface ButtonProps {
|
||||
x: number;
|
||||
y: number;
|
||||
}[];
|
||||
|
||||
bindKey?: string;
|
||||
bindMatrixKey?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<ButtonProps>(), {
|
||||
size: 1,
|
||||
bindKey: "",
|
||||
componentId: "button-default",
|
||||
pins: () => [
|
||||
{
|
||||
pinId: "BTN",
|
||||
constraint: "",
|
||||
x: 80,
|
||||
y: 140,
|
||||
},
|
||||
],
|
||||
});
|
||||
const props = defineProps<ButtonProps>();
|
||||
|
||||
// Global Stores
|
||||
const constrainsts = useConstraintsStore();
|
||||
const dialog = useDialogStore();
|
||||
const eqps = useEquipments();
|
||||
|
||||
// 存储多个Pin引用
|
||||
const pinRefs = ref<Record<string, any>>({});
|
||||
|
||||
// 计算实际宽高
|
||||
const width = computed(() => 160 * props.size);
|
||||
@@ -153,15 +203,25 @@ function toggleButtonState(isPressed: boolean) {
|
||||
isKeyPressed.value = isPressed;
|
||||
btnHeight.value = isPressed ? 180 : 200;
|
||||
|
||||
// 矩阵键盘
|
||||
if (eqps.enableMatrixKey) {
|
||||
const ret = eqps.setMatrixKey(props.bindMatrixKey, isPressed);
|
||||
if (!ret)
|
||||
dialog.error(
|
||||
`绑定的矩阵键盘值只能是0 ~ 15,而不是: ${props.bindMatrixKey}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 发出事件通知父组件
|
||||
if (isPressed) {
|
||||
emit("press");
|
||||
// 如果有约束,通知约束状态变化为高电平
|
||||
// 对所有引脚应用相同的状态
|
||||
|
||||
if (props.pins) {
|
||||
// 如果有约束,通知约束状态变化为高电平
|
||||
// 对所有引脚应用相同的状态
|
||||
props.pins.forEach((pin) => {
|
||||
if (pin.constraint) {
|
||||
notifyConstraintChange(pin.constraint, "high");
|
||||
constrainsts.notifyConstraintChange(pin.constraint, "high");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -172,7 +232,7 @@ function toggleButtonState(isPressed: boolean) {
|
||||
if (props.pins) {
|
||||
props.pins.forEach((pin) => {
|
||||
if (pin.constraint) {
|
||||
notifyConstraintChange(pin.constraint, "low");
|
||||
constrainsts.notifyConstraintChange(pin.constraint, "low");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -200,35 +260,37 @@ onUnmounted(() => {
|
||||
defineExpose({
|
||||
toggleButtonState,
|
||||
getInfo: () => ({
|
||||
// 按钮特有属性
|
||||
bindKey: props.bindKey,
|
||||
componentId: props.componentId,
|
||||
pins: props.pins,
|
||||
}),
|
||||
// 获取引脚位置
|
||||
getPinPosition: (pinId: string) => {
|
||||
console.log(`[MechanicalButton] 调用getPinPosition,寻找pinId: ${pinId}`);
|
||||
console.log(
|
||||
console.debug(`[MechanicalButton] 调用getPinPosition,寻找pinId: ${pinId}`);
|
||||
console.debug(
|
||||
`[MechanicalButton] 组件ID: ${props.componentId}, 当前尺寸: ${props.size}, 组件宽高: ${width.value}x${height.value}`,
|
||||
);
|
||||
console.log(`[MechanicalButton] 当前存在的pins:`, props.pins);
|
||||
console.debug(`[MechanicalButton] 当前存在的pins:`, props.pins);
|
||||
|
||||
// 如果是自定义的引脚ID
|
||||
if (props.pins && props.pins.length > 0) {
|
||||
const customPin = props.pins.find((p) => p.pinId === pinId);
|
||||
|
||||
if (customPin) {
|
||||
console.log(`[MechanicalButton] 找到自定义引脚: ${pinId},配置位置:`, {
|
||||
x: customPin.x,
|
||||
y: customPin.y,
|
||||
});
|
||||
console.debug(
|
||||
`[MechanicalButton] 找到自定义引脚: ${pinId},配置位置:`,
|
||||
{
|
||||
x: customPin.x,
|
||||
y: customPin.y,
|
||||
},
|
||||
);
|
||||
|
||||
// 考虑组件尺寸的缩放
|
||||
// 这里的x和y是针对标准尺寸(size=1)的坐标,需要根据实际size调整
|
||||
const scaledX = customPin.x * props.size;
|
||||
const scaledY = customPin.y * props.size;
|
||||
|
||||
console.log(`[MechanicalButton] 返回缩放后的坐标:`, {
|
||||
console.debug(`[MechanicalButton] 返回缩放后的坐标:`, {
|
||||
x: scaledX,
|
||||
y: scaledY,
|
||||
});
|
||||
@@ -237,12 +299,12 @@ defineExpose({
|
||||
y: scaledY,
|
||||
};
|
||||
} else {
|
||||
console.log(`[MechanicalButton] 未找到pinId: ${pinId}的引脚配置`);
|
||||
console.debug(`[MechanicalButton] 未找到pinId: ${pinId}的引脚配置`);
|
||||
}
|
||||
} else {
|
||||
console.log(`[MechanicalButton] 没有配置任何引脚`);
|
||||
console.debug(`[MechanicalButton] 没有配置任何引脚`);
|
||||
}
|
||||
console.log(`[MechanicalButton] 返回null,未找到引脚`);
|
||||
console.debug(`[MechanicalButton] 返回null,未找到引脚`);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
@@ -253,7 +315,6 @@ defineExpose({
|
||||
export function getDefaultProps() {
|
||||
return {
|
||||
size: 1,
|
||||
bindKey: "",
|
||||
pins: [
|
||||
{
|
||||
pinId: "BTN",
|
||||
@@ -262,6 +323,8 @@ export function getDefaultProps() {
|
||||
y: 140,
|
||||
},
|
||||
],
|
||||
bindKey: "",
|
||||
bindMatrixKey: "",
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -23,7 +23,7 @@ import { toNumber } from "lodash";
|
||||
|
||||
// 主板特有属性
|
||||
export interface MotherBoardProps {
|
||||
size?: number;
|
||||
size: number;
|
||||
boardAddr?: string;
|
||||
boardPort?: string;
|
||||
componentId?: string;
|
||||
@@ -63,7 +63,6 @@ export function getDefaultProps(): MotherBoardProps {
|
||||
size: 1,
|
||||
boardAddr: "127.0.0.1",
|
||||
boardPort: "1234",
|
||||
componentId: "DefaultMotherBoardID",
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -44,6 +44,12 @@
|
||||
{{ eqps.enableJtagBoundaryScan ? "关闭边界扫描" : "启动边界扫描" }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<h1 class="font-bold text-center text-2xl">外设</h1>
|
||||
<div class="flex flex-row">
|
||||
<input type="checkbox" class="checkbox" :checked="eqps.enableMatrixKey" @change="handleMatrixkeyCheckboxChange" />
|
||||
<p class="mx-2">启用矩阵键盘</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -52,7 +58,7 @@ import z from "zod";
|
||||
import UploadCard from "@/components/UploadCard.vue";
|
||||
import { useDialogStore } from "@/stores/dialog";
|
||||
import { useEquipments } from "@/stores/equipments";
|
||||
import { computed, ref, watchEffect } from "vue";
|
||||
import { computed, ref, watchEffect, watchPostEffect } from "vue";
|
||||
|
||||
interface CapsProps {
|
||||
jtagAddr?: string;
|
||||
@@ -100,6 +106,19 @@ function handleSelectJtagSpeed(event: Event) {
|
||||
emits("changeJtagFreq", target.value);
|
||||
}
|
||||
|
||||
async function handleMatrixkeyCheckboxChange(event: Event) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (target.checked) {
|
||||
const ret = await eqps.matrixKeypadEnable(true);
|
||||
if (!ret) {
|
||||
}
|
||||
} else {
|
||||
const ret = await eqps.matrixKeypadEnable(false);
|
||||
if (!ret) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleJtagBoundaryScan() {
|
||||
if (eqps.jtagClientMutex.isLocked()) {
|
||||
dialog.warn("Jtag正在被占用");
|
||||
|
||||
Reference in New Issue
Block a user