完成 toolbar

This commit is contained in:
锦恢 2024-08-18 21:15:57 +08:00
parent a3f6d7a49d
commit a5ae341b4e
47 changed files with 2648 additions and 282 deletions

BIN
public/CascadiaCode.woff2 Normal file

Binary file not shown.

105
public/animation.css Normal file
View File

@ -0,0 +1,105 @@
:root {
--main-during: 0.35s;
--fade-during: .5s;
}
.fade-animation-effect {
transition: var(--animation-5s);
-webkit-transition: var(--animation-5s);
-ms-transition: var(--animation-5s);
}
.easy-hidden {
visibility: hidden;
}
.main-fade-enter-from,
.main-fade-leave-to {
opacity: 0;
}
.main-fade-enter-to,
.main-fade-leave-from {
opacity: 1;
}
.main-fade-enter-active,
.main-fade-leave-active {
transition: opacity var(--main-during);
-moz-transition: opacity var(--main-during);
-webkit-transition: opacity var(--main-during);
}
.slide-enter-active,
.slide-leave-active {
transition: all .5s ease-out;
-moz-transition: all .5s ease-out;
-webkit-transition: all .5s ease-out;
}
.slide-enter-from {
position: relative;
transform: translateY(-100px);
opacity: 0%;
}
.slide-leave-to {
transform: translateY(100px);
opacity: 0%;
}
.slide-down-enter-active,
.slide-down-leave-active {
transition: all .5s ease-out;
-moz-transition: all .5s ease-out;
-webkit-transition: all .5s ease-out;
}
.slide-down-enter-from {
position: relative;
transform: translateY(100px);
opacity: 0%;
}
.slide-down-leave-to {
transform: translateY(100px);
opacity: 0%;
}
.slide-up-enter-active,
.slide-up-leave-active {
transition: all .5s ease-out;
-moz-transition: all .5s ease-out;
-webkit-transition: all .5s ease-out;
}
.slide-up-enter-from {
position: relative;
transform: translateY(-100px);
opacity: 0%;
}
.slide-up-leave-to {
transform: translateY(-100px);
opacity: 0%;
}
.collapse-from-top-enter-active,
.collapse-from-top-leave-active {
transition: var(--animation-3s);
-moz-transition: var(--animation-3s);
-webkit-transition: var(--animation-3s);
}
.collapse-from-top-enter-from {
transform: scaleY(0);
transform-origin: center top;
opacity: 0%;
}
.collapse-from-top-leave-to {
transform: scaleY(0);
transform-origin: center top;
opacity: 0%;
}
@keyframes loading-mask {
0% {
background-position: 100% 50%;
}
100% {
background-position: 0 50%;
}
}

View File

@ -1,15 +1,92 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 4440655 */ font-family: "iconfont"; /* Project id 4440655 */
src: url('iconfont.woff2?t=1715948037690') format('woff2'); src: url('iconfont.woff2?t=1723382481292') format('woff2');
} }
.iconfont { .iconfont {
font-family: "iconfont" !important; font-family: "iconfont" !important;
font-style: normal; font-style: normal;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-add-line:before {
content: "\e7b0";
}
.icon-arrow-to-previous:before {
content: "\e616";
}
.icon-arrow-to-next:before {
content: "\e615";
}
.icon-beginning:before {
content: "\e60e";
}
.icon-ending:before {
content: "\e60f";
}
.icon-line-analog:before {
content: "\e662";
}
.icon-ladder-analog:before {
content: "\e87b";
}
.icon-common-digital:before {
content: "\e606";
}
.icon-delete:before {
content: "\e684";
}
.icon-empty:before {
content: "\e617";
}
.icon-expand:before {
content: "\e658";
}
.icon-collapse:before {
content: "\e676";
}
.icon-group:before {
content: "\e70c";
}
.icon-cancel-group:before {
content: "\e65f";
}
.icon-delete-group:before {
content: "\e675";
}
.icon-choose:before {
content: "\e63b";
}
.icon-thin-arrow-right:before {
content: "\e622";
}
.icon-change-color:before {
content: "\e6fb";
}
.icon-join-group:before {
content: "\e914";
}
.icon-arrow-right:before { .icon-arrow-right:before {
content: "\eb08"; content: "\eb08";
} }

Binary file not shown.

View File

@ -11,6 +11,7 @@
<link rel="stylesheet" href="vscode.css"> <link rel="stylesheet" href="vscode.css">
<link rel="stylesheet" href="vcd.css"> <link rel="stylesheet" href="vcd.css">
<link rel="stylesheet" href="iconfont.css"> <link rel="stylesheet" href="iconfont.css">
<link rel="stylesheet" href="animation.css">
<!-- <script src="vcd.js"></script> --> <!-- <script src="vcd.js"></script> -->
<script></script> <script></script>

View File

@ -9,7 +9,9 @@
--time-scale-height: 50px; --time-scale-height: 50px;
--sidebar-padding: 10px; --sidebar-padding: 10px;
--sidebar-item-margin: 5px; --sidebar-item-margin: 5px;
--toolbar-height: 50px;
--vcd-render-padding: 30px; --vcd-render-padding: 30px;
--vcd-value-font-family: "Cascadia code", monospace;
/* 需要满足如下公式 --time-scale-height + --sidebar-padding = --vcd-render-padding + canvas预留 高度 */ /* 需要满足如下公式 --time-scale-height + --sidebar-padding = --vcd-render-padding + canvas预留 高度 */
--render-scale-x: 1; --render-scale-x: 1;
@ -20,6 +22,11 @@
--gray-box-shadow-0: 0 0 8px 3px rgba(182, 181, 182, 0.9); --gray-box-shadow-0: 0 0 8px 3px rgba(182, 181, 182, 0.9);
} }
@font-face {
font-family: "Cascadia code";
src: url("./CascadiaCode.woff2");
}
html, body { html, body {
background-color: var(--background); background-color: var(--background);
color: var(--foreground); color: var(--foreground);
@ -36,7 +43,7 @@ body::-webkit-scrollbar {
height: 2px; height: 2px;
width: 95%; width: 95%;
} }
/*
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 12px; width: 12px;
} }
@ -68,15 +75,17 @@ body::-webkit-scrollbar {
::-webkit-scrollbar-corner { ::-webkit-scrollbar-corner {
background: none; background: none;
display: none; display: none;
} } */
.el-select__wrapper { .el-select__wrapper {
background-color: transparent !important; min-width: 100px;
width: 50% !important; padding: 13px;
min-width: 200px !important; font-size: 16px;
padding: 13px !important; color: var(--sidebar-item-text);
font-size: 16px !important; }
color: var(--sidebar-item-text) !important;
.el-select-group__title {
color: var(--main-color) !important;
} }
.el-select__placeholder { .el-select__placeholder {
@ -84,19 +93,14 @@ body::-webkit-scrollbar {
} }
.el-select-dropdown { .el-select-dropdown {
min-width: 300px !important;
background-color: var(--sidebar); background-color: var(--sidebar);
border: 1.0px solid var(--main-color); border: 1.0px solid var(--main-color);
outline: none;
} }
.el-checkbox-button__inner { .el-checkbox-button__inner {
font-size: 16px !important; font-size: 16px !important;
} }
.el-select {
width: fit-content !important;
}
a { a {
color: var(--main-color); color: var(--main-color);

40
scripts/update_icon.py Normal file
View File

@ -0,0 +1,40 @@
import requests as r
import os
import shutil
import zipfile
# 下载 压缩包
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36',
'Cookie': 'cna=FeNcHjgPWA8CAXU/P9lD8IsF; EGG_SESS_ICONFONT=Hu68kBY7XO7C6Udp3T99M1asKmUZ0gxjps8xjTrjx4ZtNCIR_nFu9Li15nxoPAWLmGlcEMN2KEQyAvgBfASR3cSsmd2lhqg89lUmApzbWgBgCWjMwMzjawMqh2KNT8kCICxit3iWC7YLdUuCdUfXg_cGkRjPNvDohqyeHF27gTb5CloBSvLjqN45PcUvcUig; ctoken=Ku-GfnHTFQU6ObMjjX4rrwYn; u=10114852; u.sig=mv5vi-TPPlhvQJi2PMIC4VoPpD03Wc9UykMTMiG6ElA; xlly_s=1; tfstk=fSrIwBNqyBACtg2sZ2BZ5BUbpIn7PWsVNLM8n8KeeDnpwQex_JrEY887N5wxykyrvui717NU8DUUPYN4p7glKbl-N7y88OSV0J2nq0K5giSVfSIC9W9pv3B-6YMoT58eCJ2nqdvwwZzTKU6xnQBS27ntXvkj2HHKwO9tnfKKeHHJ6OMo6bn-9v39XxMD9e3Kpbdy1Y7IeJ69Kr6y7I-gLftJcKDKJZyez3-yav0nMVh62Arsd2GYpk3XwyMTblgq7L5ZX-4a9AifbenYCzFbk7b2SDw8J70_t1Ys_PE3eb3wenysPXaTvV9J2RmsN447O6Tn9yPsoA39FiDagfe3vP6k6JFqODHt7iBbB4UaxqqF6HiYoJoUy7b2SDw8JcszunlfpXTWCqxSCjW1CUYrE4jk1CVx8l3KIvIVCOO6r2HiCjW1CUYoJADLuO661Uf..; isg=BCwsfWsQZki1QXEWw0jCCc4h_Qpe5dCP-aVamIZsF1d6kc6br_ZyHmFnsVkpGQjn',
'Pragma': 'no-cache',
'sec-fetch-mode': 'navigate'
}
url = 'https://www.iconfont.cn/api/project/download.zip?spm=a313x.manage_type_myprojects.i1.d7543c303.125e3a81q6VMOU&pid=4440655&ctoken=Ku-GfnHTFQU6ObMjjX4rrwYn'
res = r.get(url, headers=headers)
if res.status_code:
with open('./scripts/tmp.zip', 'wb') as fp:
fp.write(res.content)
# 解压文件
with zipfile.ZipFile('./scripts/tmp.zip', 'r') as zipf:
zipf.extractall('./scripts/tmp')
# 将文件搬运至工作区,我的 css 全放在 public 下面了,你的视情况而定
for parent, _, files in os.walk('./scripts/tmp'):
for file in files:
filepath = os.path.join(parent, file)
if file.startswith('demo'):
continue
if file.endswith('.css'):
content = open(filepath, 'r', encoding='utf-8').read().replace('font-size: 16px;', '')
open(filepath, 'w', encoding='utf-8').write(content)
shutil.move(filepath, os.path.join('./public', file))
elif file.endswith('.woff2'):
shutil.move(filepath, os.path.join('./public', file))
# 删除压缩包和解压区域
os.remove('./scripts/tmp.zip')
shutil.rmtree('./scripts/tmp')

View File

@ -0,0 +1,27 @@
import requests as r
import os
import shutil
import zipfile
res = r.get('https://kirigaya.cn/files/links/tmp.zip')
# 解压文件
with zipfile.ZipFile('./scripts/tmp.zip', 'r') as zipf:
zipf.extractall('./scripts/tmp')
# 将文件搬运至工作区,我的 css 全放在 public 下面了,你的视情况而定
for parent, _, files in os.walk('./scripts/tmp'):
for file in files:
filepath = os.path.join(parent, file)
if file.startswith('demo'):
continue
if file.endswith('.css'):
content = open(filepath, 'r', encoding='utf-8').read().replace('font-size: 16px;', '')
open(filepath, 'w', encoding='utf-8').write(content)
shutil.move(filepath, os.path.join('./public', file))
elif file.endswith('.woff2'):
shutil.move(filepath, os.path.join('./public', file))
# 删除压缩包和解压区域
os.remove('./scripts/tmp.zip')
shutil.rmtree('./scripts/tmp')

View File

@ -1,14 +1,13 @@
<template> <template>
<!-- 上方的 toolbar -->
<ToolBar></ToolBar>
<!-- 主渲染区 --> <!-- 主渲染区 -->
<MainRender></MainRender> <MainRender></MainRender>
<!-- 左上角的 sidebar用于显示 --> <!-- 左上角的 sidebar用于显示 -->
<Sidebar></Sidebar> <Sidebar></Sidebar>
<!-- 工具栏 -->
<div class="vcd-toolbar">
</div>
<!-- 显示当前信号树形关系 --> <!-- 显示当前信号树形关系 -->
<!-- 右侧工具合集 --> <!-- 右侧工具合集 -->
<RightNav :topModules="VcdInfo.topModules"></RightNav> <RightNav :topModules="VcdInfo.topModules"></RightNav>
@ -24,6 +23,8 @@ import { emitter, globalLookup, globalSetting } from '@/hook/global';
import { makeWaveView } from '@/hook/render'; import { makeWaveView } from '@/hook/render';
import { getCrossOriginWorkerURL } from '@/hook/network'; import { getCrossOriginWorkerURL } from '@/hook/network';
import ToolBar from '@/components/toolbar';
import Sidebar from '@/components/sidebar'; import Sidebar from '@/components/sidebar';
import RightNav from '@/components/right-nav.vue'; import RightNav from '@/components/right-nav.vue';
import MainRender from '@/components/render'; import MainRender from '@/components/render';
@ -73,6 +74,7 @@ onMounted(async () => {
document.body.style.setProperty('--time-scale-height', '50px'); document.body.style.setProperty('--time-scale-height', '50px');
document.body.style.setProperty('--vcd-render-padding', '30px'); document.body.style.setProperty('--vcd-render-padding', '30px');
document.body.style.setProperty('--sidebar-width', '230px'); document.body.style.setProperty('--sidebar-width', '230px');
document.body.style.setProperty('--toolbar-height', '60px');
// signal height // signal height
document.body.style.setProperty('--display-signal-info-height', globalSetting.displaySignalHeight + 'px'); document.body.style.setProperty('--display-signal-info-height', globalSetting.displaySignalHeight + 'px');
@ -113,7 +115,7 @@ onMounted(async () => {
globalLookup.chango = signalValues; globalLookup.chango = signalValues;
makeWaveView(); makeWaveView();
console.log(signalValues['*']); // console.log(signalValues['*']);
// console.log('duration time', globalLookup.time); // console.log('duration time', globalLookup.time);
emitter.emit('meta-ready', null); emitter.emit('meta-ready', null);
@ -144,4 +146,5 @@ onMounted(async () => {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
</style> </style>

View File

@ -52,6 +52,7 @@ export default {
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
transform: translateY(var(--toolbar-height));
} }
.vcd-vline { .vcd-vline {
@ -92,6 +93,7 @@ export default {
.vcd-values text { .vcd-values text {
font-size: 14px; font-size: 14px;
font-family: var(--vcd-value-font-family);
text-anchor: middle; text-anchor: middle;
fill: #fff; fill: #fff;
} }

View File

@ -1,11 +1,16 @@
<template> <template>
<div class="setting-wrapper"> <div class="setting-wrapper">
<div class="setting-container"> <el-scrollbar height="98vh">
<div class="setting-section"> <div class="setting-section">
<h2>{{ t('general-setting') }}</h2> <h2>{{ t('general-setting') }}</h2>
<div class="setting-option" style="width: 300px;"> <div class="setting-option" style="width: 220px;">
<span class="option-title">{{ t('language-setting') }}</span> <span class="option-title">{{ t('language-setting') }}</span>
<el-select name="language-setting" class="language-setting" v-model="locale"> <div style="width: 100px;">
<el-select
name="language-setting"
class="language-setting"
v-model="locale"
>
<el-option <el-option
v-for="option in languageSetting.options" v-for="option in languageSetting.options"
:value="option.value" :value="option.value"
@ -14,8 +19,9 @@
</el-option> </el-option>
</el-select> </el-select>
</div> </div>
</div>
<br> <br>
<div class="setting-option"> <!-- <div class="setting-option">
<span class="option-title"> <span class="option-title">
{{ t('prerender') }} {{ t('prerender') }}
<help-icon placement="left" <help-icon placement="left"
@ -25,7 +31,7 @@
<el-switch v-model="globalSetting.prerender" size="default"/> <el-switch v-model="globalSetting.prerender" size="default"/>
</div> </div>
<br> <br> -->
<div class="setting-option"> <div class="setting-option">
<span class="option-title"> <span class="option-title">
@ -89,7 +95,8 @@
<br> <br>
<div class="setting-option"> <div class="setting-option">
<span class="option-title">{{ t('wavecolor') }}</span> <span class="option-title" style="width: 100px;">{{ t('wavecolor') }}</span>
<div style="width: 120px">
<el-select <el-select
v-model="wavecolor.currentOptionIndex" v-model="wavecolor.currentOptionIndex"
collapse-tags collapse-tags
@ -99,6 +106,7 @@
<el-option v-for="option in wavecolor.options" :key="option.value" <el-option v-for="option in wavecolor.options" :key="option.value"
:label="option.label" :value="option.value" /> :label="option.label" :value="option.value" />
</el-select> </el-select>
</div>
<div style="height: 20px; width: 20px;"></div> <div style="height: 20px; width: 20px;"></div>
<el-color-picker <el-color-picker
v-model="wavecolor.colors[wavecolor.currentOptionIndex]" v-model="wavecolor.colors[wavecolor.currentOptionIndex]"
@ -129,8 +137,10 @@
<div class="setting-option"> <div class="setting-option">
<span class="option-title">{{ t('search-scope') }}</span> <span class="option-title">{{ t('search-scope') }}</span>
<div style="width: 150px;">
<el-select <el-select
v-model="globalSetting.searchScope" v-model="globalSetting.searchScope"
size="large"
multiple multiple
collapse-tags collapse-tags
collapse-tags-tooltip collapse-tags-tooltip
@ -143,8 +153,8 @@
</el-select> </el-select>
</div> </div>
</div> </div>
</div> </div>
</el-scrollbar>
</div> </div>
</template> </template>
@ -204,6 +214,7 @@ watch(() => wavecolor.currentOptionIndex, () => {
console.log(wavecolor.currentOptionIndex); console.log(wavecolor.currentOptionIndex);
}); });
//
watch(() => wavecolor.colors, () => { watch(() => wavecolor.colors, () => {
const colorString = wavecolor.colors[wavecolor.currentOptionIndex]; const colorString = wavecolor.colors[wavecolor.currentOptionIndex];
const rgba = rgba2WebglColor(colorString); const rgba = rgba2WebglColor(colorString);
@ -212,7 +223,8 @@ watch(() => wavecolor.colors, () => {
const waveRender = globalLookup.getWaveRender(); const waveRender = globalLookup.getWaveRender();
const glColorIndex = wavecolor.glColorMap[currentOption.value]; const glColorIndex = wavecolor.glColorMap[currentOption.value];
const colorUpdaters = [{ index: glColorIndex, rgba }]; const colorUpdaters = [{ index: glColorIndex, rgba }];
if (glColorIndex === 2) { // value = 0 value = 1 if (glColorIndex === 2) {
// value = 0 value = 1
colorUpdaters.push({ index: 3, rgba }); colorUpdaters.push({ index: 3, rgba });
} }
waveRender.updateGLColor(colorUpdaters, { updateMask: true }); waveRender.updateGLColor(colorUpdaters, { updateMask: true });

View File

@ -0,0 +1,30 @@
import { gl_Colors } from '@/hook/wave-view/render-utils';
import { reactive } from 'vue';
// 用于管理当前信号 link 和对应颜色索引的映射关系
// 如果 link 不在下表中,则使用默认的颜色索引
// 默认颜色请参考 render-utils.js 中有关 gl_Colors_template 的定义
export const userSignalColorMap = new Map();
// 当前选中波形目前的颜色
export const colorPickerManage = reactive({
currentSelecteIndex: -1
});
/**
*
* @param {WireItem} signal
*/
export function updateColorPickerManage(signal) {
if (userSignalColorMap.has(signal.link)) {
colorPickerManage.currentSelecteIndex = userSignalColorMap.get(signal.link);
} else {
// 找到默认的配色
if (signal.size === 1) {
colorPickerManage.currentSelecteIndex = 2;
} else {
colorPickerManage.currentSelecteIndex = 5;
}
}
}

View File

@ -0,0 +1,115 @@
<template>
<div class="color-picker" :style="colorPickerStyle">
<div
class="color-option"
v-for="item in colorPickerStyles"
:key="item.index"
>
<span
:style="item.style"
class="color-option-inner"
@click="handleColorSelector(item.index)"
>
<transition name="main-fade">
<span
v-show="colorPickerManage.currentSelecteIndex === item.index"
class="choose iconfont icon-choose"
></span>
</transition>
</span>
</div>
</div>
</template>
<script setup>
import { gl_Colors, gl_Colors_template } from '@/hook/wave-view/render-utils';
import { reactive, computed, defineComponent } from 'vue';
import { contextmenu } from './handle-contextmenu';
import { colorPickerManage, userSignalColorMap } from './color-picker';
import { globalLookup } from '@/hook/global';
defineComponent({ name: 'color-picker' });
/**
* @typedef {Object} ColorPickerOption
* @property {number} index
* @property {string} style
*/
/**
* @description gl_Colors_template 11 开始就是自定义颜色了
* @type {ColorPickerOption[]}
*/
const colorPickerStyles = [];
for (let i = 1; i < gl_Colors_template.length; ++ i) {
const rgba = gl_Colors_template[i];
const styleString = `background-color: rgba(${rgba[0]},${rgba[1]},${rgba[2]},${rgba[3]})`;
colorPickerStyles.push({
index: i,
style: styleString
});
}
function handleColorSelector(index) {
colorPickerManage.currentSelecteIndex = index;
if (contextmenu.currentWire) {
const link = contextmenu.currentWire.link;
userSignalColorMap.set(link, index);
// VAO
globalLookup.waveRender.renderPipeHook.userDefinedColor = index;
globalLookup.waveRender.resetVertice(link);
globalLookup.render();
}
}
const colorPickerStyle = computed(() => ({
top: '0px',
left: contextmenu.width + 'px'
}));
</script>
<style>
.color-picker {
width: 280px;
display: flex;
flex-wrap: wrap;
flex-direction: row;
position: absolute;
padding: 10px;
box-shadow: 0 0 10px 1px rgb(16, 16, 16);
background-color: var(--sidebar);
border: solid 1px var(--sidebar-border);
transition: var(--animation-3s);
cursor: default;
}
.color-picker .color-option {
display: flex;
border-radius: .7em;
width: 30px;
height: 30px;
margin: 5px;
cursor: pointer;
border: 2px solid var(--sidebar);
}
.color-picker .color-option > span {
border-radius: .5em;
width: 100%;
height: 100%;
}
.color-option-inner {
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
color: var(--sidebar);
}
</style>

View File

@ -0,0 +1,18 @@
import { globalLookup } from "@/hook/global";
import { reactive } from "vue";
// 从 globalLookup.currentWiresRenderView 中可以获取当前的分组情况
export function getCurrentGroups() {
const groups = globalLookup.currentWiresRenderView.filter(view => view.renderType === 1);
return groups;
}
export const existGroup = reactive({
display: false,
show() {
this.display = true;
},
hide() {
this.display = false;
}
});

View File

@ -0,0 +1,130 @@
<template>
<div class="exist-group" :style="existGroupStyle">
<div class="wrapper">
<div
v-if="currentGroups.length > 0"
v-for="(item, index) in currentGroups"
:key="index"
class="group-item"
:style="getItemStyle(item)"
>
<span class="iconfont icon-group"></span>
&ensp;
<span>
{{ getItemName(item) }}
</span>
</div>
<div v-else
class="empty-status"
>
<span class="iconfont icon-empty"></span>
<span>{{ t('context-menu.group.empty') }}</span>
</div>
</div>
</div>
</template>
<script setup>
import { computed, defineComponent } from 'vue';
import { contextmenu } from './handle-contextmenu';
import { currentGroups } from './manage-group';
import { globalLookup } from '@/hook/global';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
defineComponent({ name: 'exist-group' });
const existGroupStyle = computed(() => ({
top: '0px',
left: contextmenu.width + 'px'
}));
/**
* @param {WaveRenderSidebarItem} view
*/
function getItemStyle(view) {
return `background-color: ${view.groupInfo.color}`;
}
/**
* @param {WaveRenderSidebarItem} view
*/
function getItemName(view) {
let name = t('context-menu.group.uname-group');
if (view.groupInfo.name) {
name = view.groupInfo.name;
}
return name;
}
</script>
<style scoped>
.exist-group {
color: var(--foreground);
width: 260px;
position: absolute;
padding: 10px;
box-shadow: 0 0 10px 1px rgb(16, 16, 16);
background-color: var(--sidebar);
border: solid 1px var(--sidebar-border);
transition: var(--animation-3s);
cursor: default;
padding: 10px;
}
.group-item {
width: 150px;
display: flex;
align-items: center;
color: var(--sidebar);
border-radius: .5em;
height: 20px;
padding: 5px;
margin: 3px;
cursor: pointer;
transition: var(--animation-5s);
}
.group-item > span {
max-width: 80%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.group-item:hover {
width: 200px;
transition: var(--animation-5s);
}
.group-item .iconfont {
font-size: 1.1rem;
font-weight: 700;
}
.wrapper {
}
.empty-status {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.empty-status .iconfont {
font-size: 70px;
display: flex;
flex-direction: column;
}
.empty-status > span {
margin: 5px;
}
</style>

View File

@ -0,0 +1,168 @@
<template>
<div class="group-context-menu">
<div class="top-region">
<div class="group-name-input">
<el-input
:ref="el => groupcontextmenu.groupNameInput = el"
v-model="value"
size="default"
></el-input>
</div>
<div class="group-color-selector">
<span
v-for="(item, index) in groupColors"
:key="index"
:style="makeColorStyle(item.color)"
class="item"
@click="onClick(item)"
>
<span
class="focus-circle"
:class="{'active': item.color === currentGroupColor}"
></span>
</span>
</div>
</div>
<hr>
<div class="group-function-list">
<div class="item"
@click="cancelGroup()"
>
<span class="iconfont icon-cancel-group"></span>
<span>{{ t('context-menu.group.cancel') }}</span>
</div>
<div class="item"
@click="deleteGroup()"
>
<span class="iconfont icon-delete-group"></span>
<span>{{ t('context-menu.group.delete') }}</span>
</div>
</div>
</div>
</template>
<script setup>
import { computed, ref } from 'vue';
import { cancelGroup, deleteGroup, groupColors, makeColorStyle } from './manage-group';
import { useI18n } from 'vue-i18n';
import { contextmenu, groupcontextmenu } from './handle-contextmenu';
const { t } = useI18n();
const currentGroupColor = computed(() => {
if (groupcontextmenu.currentGroupView) {
return groupcontextmenu.currentGroupView.groupInfo.color;
}
return '';
})
const value = computed({
get() {
if (groupcontextmenu.currentGroupView) {
return groupcontextmenu.currentGroupView.groupInfo.name;
} else {
return '';
}
},
set(v) {
if (groupcontextmenu.currentGroupView) {
groupcontextmenu.currentGroupView.groupInfo.name = v;
}
}
});
function onClick(item) {
groupcontextmenu.currentGroupView.groupInfo.color = item.color;
}
</script>
<style>
.group-context-menu {
z-index: 200;
border-radius: .5em;
padding: 10px;
position: fixed;
box-shadow: 0 0 10px 1px rgb(16, 16, 16);
background-color: var(--sidebar);
border: solid 1px var(--sidebar-border);
}
.group-context-menu .top-region {
display: flex;
flex-direction: column;
align-items: center;
}
.group-name-input {
width: 95%;
}
.group-color-selector {
margin-top: 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.group-color-selector .item {
border-radius: 99em;
width: 20px;
height: 20px;
margin: 5px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.group-color-selector .item .focus-circle {
border-radius: 99em;
width: 12px;
height: 12px;
}
.group-color-selector .item .focus-circle.active {
border: 2px solid var(--sidebar);
}
.group-function-list {
display: flex;
flex-direction: column;
}
.group-function-list .item {
font-size: 0.9rem;
display: flex;
align-items: center;
margin: 5px;
padding: 3px;
padding-left: 10px;
border-radius: .5em;
transition: var(--animation-3s);
cursor: pointer;
width: 90%;
position: relative;
}
.group-function-list .item .iconfont {
font-size: 1.1rem;
width: 35px;
}
.group-function-list .item:hover {
color: var(--sidebar);
background: linear-gradient(
90deg,
#7CA532 25%,
rgba(200, 200, 200, 1) 37%,
rgba(255, 255, 255, 0) 63%
);
background-size: 400% 100%;
animation: loading-mask 1.5s cubic-bezier(0.23,1,0.32,1);
-webkit-animation: loading-mask 1.5s cubic-bezier(0.23,1,0.32,1);
transition: var(--animation-3s);
}
</style>

View File

@ -0,0 +1,131 @@
<template>
<div class="group-item" :style="groupItemStyle">
<div
class="group-card"
:style="groupCardStyle"
@click="handleGroupClick(view)"
@contextmenu.prevent="handleGroupContextmenu($event, view, index)"
>
<span class="iconfont icon-group"></span>
&ensp;
<span>
{{ props.view.groupInfo.name }}
</span>
</div>
<VueDraggable
v-show="!props.view.groupInfo.collapse"
class="group-children"
v-model="props.view.children"
group="vcd-render-item"
:animation="150"
:on-update="onUpdate()"
ghost-class="signal-drag-ghost-class"
drag-class="signal-drag-drag-class"
>
<div
v-for="view in props.view.children"
:key="view.signalInfo.link"
:style="groupChildStyle"
>
<SignalItem
:view="view"
@click="handleItemClick(view.signalInfo.link)"
@contextmenu.prevent="handleContextmenu($event, view, null)"
></SignalItem>
</div>
</VueDraggable>
</div>
</template>
<script setup>
import { globalLookup, globalStyle } from '@/hook/global';
import { defineComponent, computed, onMounted, reactive } from 'vue';
import { onUpdate } from './handle-drag';
import { VueDraggable } from 'vue-draggable-plus';
import SignalItem from './signal-item.vue';
import GroupContextMenu from './group-context-menu.vue';
import { groupcontextmenu, handleContextmenu, handleGroupClick, handleGroupContextmenu } from './handle-contextmenu';
defineComponent({ name: 'group-item' });
const props = defineProps({
view: {
type: Object,
required: true
}
});
const groupCardStyle = computed(() => ({
backgroundColor: props.view.groupInfo.color,
marginBottom: globalStyle.sideBarItemMargin + 'px',
borderBottomRightRadius: props.view.groupInfo.collapse ? '.5em' : '0em',
borderLeft: props.view.groupInfo.collapse ? '3px solid ' + props.view.groupInfo.color : '0'
}));
const groupItemStyle = computed(() => ({
borderLeft: props.view.groupInfo.collapse ? '0' : '3px solid ' + props.view.groupInfo.color
}));
const groupChildStyle = computed(() => ({
marginBottom: globalStyle.sideBarItemMargin + 'px'
}));
// context menu
function handleItemClick(link) {
if (globalLookup.sidebarSelectedWireLinks.has(link)) {
globalLookup.sidebarSelectedWireLinks.delete(link);
} else {
globalLookup.sidebarSelectedWireLinks.add(link);
}
}
</script>
<style scoped>
.group-item {
display: flex;
flex-direction: column;
width: 100%;
border-top-left-radius: .7em;
}
.group-card {
max-width: calc(var(--sidebar-width) - 36px);
width: fit-content;
color: var(--sidebar);
padding-left: 10px;
padding-right: 10px;
height: var(--display-signal-info-height);
border-top-left-radius: .4em;
border-top-right-radius: .5em;
font-size: 1.0rem;
display: flex;
align-items: center;
user-select: none;
cursor: pointer;
}
.group-card > span {
max-width: 83%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.group-children {
display: flex;
flex-direction: column;
margin-left: 5px;
position: relative;
}
.group-card .iconfont {
font-size: 1.1rem;
font-weight: 600;
}
</style>

View File

@ -0,0 +1,255 @@
import { globalLookup } from "@/hook/global";
import { computed, nextTick, reactive } from "vue";
import { updateColorPickerManage } from "./color-picker";
import { WaveContainerView } from "@/hook/wave-container-view";
/**
* @namespace contextmenu
*/
export const contextmenu = reactive({
show: false,
width: 300,
x: 0,
y: 0,
/**
* @type {WaveRenderSidebarItem | undefined}
*/
currentView: undefined,
/**
* @type {WireItem | undefined}
*/
currentWire: undefined,
/**
* @type {number}
*/
currentWireIndex: -1,
isSameSignal(view) {
if (this.currentWire) {
return this.currentWire.link === view.signalInfo.link;
}
return false;
}
});
export const contextmenuStyle = computed(() => ({
left: contextmenu.x + 'px',
top: contextmenu.y + 'px'
}));
/**
*
* @param {MouseEvent} event
* @param {WaveRenderSidebarItem} view
* @param {number | null} index null 说明 handleContextmenu group 内部被调用
*/
export function handleContextmenu(event, view, index) {
groupcontextmenu.show = false;
contextmenu.x = event.x + 10 + window.scrollX;
contextmenu.y = event.clientY - 10 + window.scrollY;
const realSignal = globalLookup.link2CurrentWires.get(view.signalInfo.link);
contextmenu.currentView = view;
contextmenu.currentWire = realSignal;
contextmenu.show = true;
updateColorPickerManage(realSignal);
if (index !== null) {
contextmenu.currentWireIndex = index;
groupcontextmenu.updateHook = updateCurrentGroupContextMenu(event, view);
}
// 延时 300 ms 是因为这里 menu 出现会使用 一个 0.35 秒的入场动画
setTimeout(() => {
getContextMenuWidth();
}, 200);
}
/**
* @description 将当前 signal-item 相连的 group-item 更新进 groupcontextmenu.currentGroupCardEl
* @param {MouseEvent} event
* @param {WaveRenderSidebarItem} view
* @param {Promise<boolean>}
*/
async function updateCurrentGroupContextMenu(event, view) {
const node = event.target;
const container = findSignalItemEl(node);
if (container) {
const groupItem = container.nextSibling;
groupcontextmenu.currentGroupView = view;
groupcontextmenu.currentGroupCardEl = groupItem.childNodes[0];
return true;
}
return false;
}
/**
*
* @param {HTMLElement | undefined} node
* @returns {HTMLElement | undefined}
*/
function findSignalItemEl(node) {
const maxFindTimes = 10;
for (let i = 0; ; ++ i) {
if (i >= maxFindTimes || node === undefined) {
return undefined;
}
if (node.className.startsWith('signal-item')) {
return node;
}
node = node.parentNode;
}
}
function getContextMenuWidth() {
let width = 300;
const menus = document.querySelectorAll('.sidebar-context-menu');
if (menus) {
const menu = menus[0];
width = menu.offsetWidth || menu.clientWidth;
}
contextmenu.width = width;
return width;
}
// 用于魔改取色器的
export const colorPicker = reactive({
display: false,
show() {
this.display = true;
},
hide() {
this.display = false;
}
});
export const groupcontextmenu = reactive({
show: false,
x: 0,
y: 0,
/**
* @type {WaveRenderSidebarItem | undefined}
*/
currentGroupView: undefined,
/**
* @type
*/
currentGroupCardEl: null,
groupNameInput: null,
updateHook: null
});
export const groupcontextmenuStyle = computed(() => ({
left: groupcontextmenu.x + 'px',
top: groupcontextmenu.y + 'px'
}));
/**
*
* @param {WaveRenderSidebarItem} view
*/
export function handleGroupClick(view) {
view.groupInfo.collapse = !view.groupInfo.collapse;
}
/**
*
* @param {HTMLElement | undefined} node
* @returns {HTMLElement | undefined}
*/
function findGroupCardEl(node) {
const maxSearchTime = 3;
for (let i = 0;; ++ i) {
// 往外搜索最多三次,找到 group-card 这个类的 span
if (i >= maxSearchTime || node === undefined || node === null) {
return undefined;
}
if (node.className.includes('group-card')) {
return node;
}
node = node.parentNode;
}
}
function getScreenPosition(element) {
let x = 0;
let y = 0;
// 遍历元素及其所有祖先元素
while (element) {
x += element.offsetLeft; // 累加 offsetLeft
y += element.offsetTop; // 累加 offsetTop
element = element.offsetParent; // 移动到下一个祖先元素
}
// 考虑到页面滚动
x -= window.pageXOffset;
y -= window.pageYOffset;
return { x: x, y: y };
}
/**
*
* @param {MouseEvent} event
* @param {WaveRenderSidebarItem} view
* @param {number} index
*/
export function handleGroupContextmenu(event, view, index) {
contextmenu.show = false;
const node = event.target;
const groupCard = findGroupCardEl(node);
if (groupCard) {
const {x, y} = getScreenPosition(groupCard);
groupcontextmenu.x = x + groupCard.offsetWidth + 10;
groupcontextmenu.y = y;
groupcontextmenu.currentGroupView = view;
groupcontextmenu.show = true;
if (groupcontextmenu.groupNameInput) {
groupcontextmenu.groupNameInput.focus();
}
}
}
export async function handleGroupContextmenuFromSignalItem() {
contextmenu.show = false;
if (groupcontextmenu.updateHook) {
// const ok = await groupcontextmenu.updateHook;
groupcontextmenu.x = contextmenu.x + 10 + window.scrollX;
groupcontextmenu.y = contextmenu.y - 5 + window.scrollY;
groupcontextmenu.show = true;
if (groupcontextmenu.groupNameInput) {
groupcontextmenu.groupNameInput.focus();
}
// if (ok && groupcontextmenu.currentGroupCardEl) {
// // const groupCard = groupcontextmenu.currentGroupCardEl;
// // const {x, y} = getScreenPosition(groupCard);
// }
}
}
export function deleteSignalByView() {
const signal = contextmenu.currentWire;
if (signal) {
WaveContainerView.delete(signal);
}
contextmenu.show = false;
}

View File

@ -0,0 +1,7 @@
import { globalLookup } from "@/hook/global";
import { updateCurrentGroups } from "./manage-group";
export function onUpdate() {
globalLookup.render();
updateCurrentGroups();
}

View File

@ -1,85 +1,84 @@
<template> <template>
<div class="vcd-sidebar-wrapper"> <div class="vcd-sidebar-wrapper"
@click="handleSidebarGlobalClick"
>
<div class="display-signal-wrapper" :style="sideBarWrapperStyle"> <div class="display-signal-wrapper" :style="sideBarWrapperStyle">
<div class="display-signal-container" :style="sidedBarContainerStyle"> <div class="display-signal-container" :style="sidedBarContainerStyle">
<div :style="timeScaleStyle"></div> <div :style="timeScaleStyle"></div>
<VueDraggable <VueDraggable
v-model="globalLookup.currentWires" v-model="globalLookup.currentWiresRenderView"
group="vcd-render-item" :group="{ name: 'vcd-render-item' }"
:animation="150" :animation="150"
ghost-class="signal-drag-ghost" :on-update="onUpdate()"
ghost-class="signal-drag-ghost-class"
drag-class="signal-drag-drag-class"
> >
<div <div
class="display-signal-item-container" class="display-signal-item-container"
:style="sideBarItemStyle" :style="sideBarItemStyle"
v-for="(signal, index) in globalLookup.currentWires" v-for="(view, index) in globalLookup.currentWiresRenderView"
:key="signal.link" :key="view.signalInfo.link"
> >
<div class="display-signal-item">
<div class="signal-color-vendor">
<span :class="makeSignalIconClass(signal)"></span>
</div>
<div class="signal-info"> <SignalItem
<div class="signal-info"> v-show="view.renderType === 0"
<el-tooltip :view="view"
effect="dark" @click="handleItemClick(view.signalInfo.link)"
:content="makeFullSignalNameDeps(signal)" @contextmenu.prevent="handleContextmenu($event, view, index)"
placement="top" />
raw-content
>
<div class="signal-info-name">
<span class="signal-parent-info" v-show="showParent">{{ signal.parent.name }}</span>
<span>{{ signal.name }}</span>
</div>
</el-tooltip>
<div class="signal-info-width" v-show="showWidth"> <GroupItem
<div :class="signal.size > 1 ? 'signal-info-caption' : ''"> v-show="view.renderType === 1"
{{ makeSignalCaption(signal) }} :view="view"
</div> />
</div>
</div>
</div>
</div>
<div class="signal-info-current-value-wrapper">
<span></span>
<el-tooltip
effect="dark"
:content="globalLookup.currentSignalValues[signal.link] + ''"
placement="top"
raw-content
><div class="signal-info-current-value">
{{ globalLookup.currentSignalValues[signal.link] }}
</div></el-tooltip>
</div>
</div>
</div>
</VueDraggable> </VueDraggable>
</div> </div>
</div> </div>
<div class="add-signal-button" @click="addSignal"> <!-- <div class="add-signal-button" @click="addSignal">
<span class="iconfont icon-collections"></span> <span class="iconfont icon-collections"></span>
</div> -->
<teleport to='body'>
<transition name="collapse-from-top" mode="out-in">
<SignalContextMenu
v-show="contextmenu.show"
:style="contextmenuStyle"
></SignalContextMenu>
</transition>
</teleport>
<teleport to='body'>
<transition name="collapse-from-top" mode="out-in">
<GroupContextMenu
v-show="groupcontextmenu.show"
:style="groupcontextmenuStyle"
></GroupContextMenu>
</transition>
</teleport>
</div> </div>
</div>
</template> </template>
<script setup> <script setup>
import { reactive, computed, defineComponent, ref } from 'vue'; import { reactive, computed, defineComponent } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { VueDraggable } from 'vue-draggable-plus'; import { VueDraggable } from 'vue-draggable-plus';
import { emitter, globalLookup, globalSetting, globalStyle } from '@/hook/global'; import { emitter, globalLookup, globalSetting, globalStyle } from '@/hook/global';
import { makeIconClass } from '@/hook/utils'; import { makeIconClass } from '@/hook/utils';
import { onUpdate } from './handle-drag';
import SignalContextMenu from './signal-context-menu.vue';
import { colorPicker, contextmenu, contextmenuStyle, groupcontextmenu, groupcontextmenuStyle, handleContextmenu } from './handle-contextmenu';
import SignalItem from './signal-item.vue';
import GroupItem from './group-item.vue';
import GroupContextMenu from './group-context-menu.vue';
const { t } = useI18n(); const { t } = useI18n();
defineComponent({ name: 'side-bar' }); defineComponent({ name: 'side-bar' });
const showWidth = computed(() => globalSetting.displaySignalInfoScope.includes('width'));
const showParent = computed(() => globalSetting.displaySignalInfoScope.includes('parent'));
const timeScaleStyle = computed(() => ({ const timeScaleStyle = computed(() => ({
height: `${globalStyle.timeScaleHeight}px` height: `${globalStyle.timeScaleHeight}px`
})); }));
@ -101,46 +100,35 @@ function addSignal() {
emitter.emit('right-nav', 0); emitter.emit('right-nav', 0);
} }
function makeSignalIconClass(signal) { /**
return 'iconfont ' + makeIconClass(signal); *
* @param {WaveRenderSidebarItem} view
*/
function getVForKey(view) {
return view.signalInfo.link + '-' + view.renderType;
} }
function makeSignalCaption(signal) {
return signal.size === 1 ? '' : `${signal.size - 1}:0`; function handleItemClick(link) {
if (globalLookup.sidebarSelectedWireLinks.has(link)) {
globalLookup.sidebarSelectedWireLinks.delete(link);
} else {
globalLookup.sidebarSelectedWireLinks.add(link);
}
} }
function makeFullSignalNameDeps(signal) {
const deps = [];
while (signal) {
if (signal.name && signal.type) {
deps.push(signal);
}
signal = signal.parent;
}
let htmlString = '';
for (let i = deps.length - 1; i >= 0; -- i) {
const mod = deps[i];
// const displayName = mod.name.length > 6 ? mod.name.substring(0, 6) + '...' : mod.name;
const iconClass = makeIconClass(mod);
const iconText = `<span class="iconfont ${iconClass}"></span>&ensp;${mod.name}`;
htmlString += iconText; function handleSidebarGlobalClick() {
contextmenu.show = false;
if (i > 0) { groupcontextmenu.show = false;
htmlString += '<div class="dep-arrow"></div>';
}
}
htmlString = '<div class="signal-info-tooltip-wrapper">' + htmlString + '</div>';
return htmlString;
} }
</script> </script>
<style> <style>
.vcd-sidebar-wrapper { .vcd-sidebar-wrapper {
position: absolute; position: absolute;
top: 0px; top: var(--toolbar-height);
left: 0px; left: 0px;
z-index: 60; z-index: 60;
} }
@ -162,9 +150,10 @@ function makeFullSignalNameDeps(signal) {
.display-signal-wrapper { .display-signal-wrapper {
background-color: var(--sidebar); background-color: var(--sidebar);
border-radius: 0 .8em .8em 0;
border: solid 1px var(--sidebar-border); border: solid 1px var(--sidebar-border);
width: var(--sidebar-width); width: var(--sidebar-width);
height: calc(100vh - 90px); height: calc(100vh - var(--toolbar-height));
box-shadow: 0 0 15px 1px rgb(16, 16, 16); box-shadow: 0 0 15px 1px rgb(16, 16, 16);
overflow: hidden; overflow: hidden;
} }
@ -176,6 +165,7 @@ function makeFullSignalNameDeps(signal) {
display: flex; display: flex;
width: 200px; width: 200px;
cursor: pointer; cursor: pointer;
transition: var(--animation-5s);
} }
.display-signal-item-container { .display-signal-item-container {
@ -184,7 +174,7 @@ function makeFullSignalNameDeps(signal) {
align-items: center; align-items: center;
} }
.display-signal-item::selection { .signal-info-current-value-wrapper::selection {
background-color: none; background-color: none;
} }
@ -223,7 +213,7 @@ function makeFullSignalNameDeps(signal) {
} }
.signal-info-tooltip-wrapper { .signal-info-tooltip-wrapper {
display: flex; display: inline-block;
align-items: center; align-items: center;
justify-content: space-around; justify-content: space-around;
} }
@ -276,8 +266,28 @@ function makeFullSignalNameDeps(signal) {
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.signal-drag-ghost { .signal-drag-ghost-class .display-signal-item,
opacity: 0.5; .signal-drag-ghost-class .signal-info-current-value-wrapper {
background: #c8ebfb; color: var(--sidebar);
background-color: #7CA532 !important;
transition: var(--animation-5s);
}
.active .display-signal-item,
.active .signal-info-current-value-wrapper {
background-color: var(--button-active);
transition: var(--animation-5s);
}
.right-active .display-signal-item,
.right-active .signal-info-current-value-wrapper {
color: var(--sidebar);
background-color: #7CA532 !important;
transition: var(--animation-5s);
}
.signal-drag-drag-class {
opacity: 0;
border: none;
} }
</style> </style>

View File

@ -0,0 +1,190 @@
import { globalLookup } from "@/hook/global";
import { colorPicker, contextmenu, groupcontextmenu, handleGroupContextmenuFromSignalItem } from "./handle-contextmenu";
import { reactive, ref } from "vue";
import { findViewIndexByLink } from "@/hook/wave-container-view";
export const groupColors = [
{
color: '#dadce0',
used: false
},
{
color: '#7ca0d9',
used: false
},
{
color: '#e3847d',
used: false
},
{
color: '#fdd663',
used: false
},
{
color: '#7bbe8f',
used: false
},
{
color: '#ff8bcb',
used: false
},
{
color: '#c58af9',
used: false
},
{
color: '#73ccdf',
used: false
},
{
color: '#fcad70',
used: false
}
];
const color2Index = {};
for (let i = 0; i < groupColors.length; ++ i) {
const color = groupColors[i].color;
color2Index[color] = i;
}
/**
* @description 当前的组
* @type {WaveRenderSidebarItem[]}
*/
export const currentGroups = reactive([]);
export async function updateCurrentGroups() {
currentGroups.length = 0;
for (const view of globalLookup.currentWiresRenderView) {
if (view.renderType === 1) {
// 1 代表当前为 group
currentGroups.push(view);
}
}
}
export const groupColorDispatcher = {
/**
* @description 获取颜色
* @returns {string}
*/
get() {
let color = groupColors[0];
for (const c of groupColors) {
if (!c.used) {
c.used = true;
return c.color;
}
}
return color;
},
/**
* @description 归还颜色
* @param {string} color
*/
put(color) {
const index = color2Index[color];
if (index !== undefined && index < groupColors.length) {
groupColors[index].used = false;
}
}
};
export function makeColorStyle(color) {
return `background-color: ${color};`
}
// 创建一个组,并且从 group 颜色调度器拿走一个颜色
export function createGroup() {
const signal = contextmenu.currentWire;
const wireIndex = contextmenu.currentWireIndex;
if (signal) {
const renderView = globalLookup.currentWiresRenderView;
if (wireIndex < renderView.length) {
const view = renderView[wireIndex];
view.renderType = 1;
const groupLink = view.signalInfo.link + '-group';
view.signalInfo.link = groupLink;
view.groupInfo = {
name: '',
color: groupColorDispatcher.get(),
collapse: false
};
view.children.push({
signalInfo: {
name: signal.name,
link: signal.link,
size: signal.size,
parentLink: groupLink
},
groupInfo: {
name: '',
color: '',
collapse: false
},
renderType: 0,
children: []
});
handleGroupContextmenuFromSignalItem();
}
}
contextmenu.show = false;
globalLookup.render();
updateCurrentGroups();
}
export function cancelGroup() {
const currentGroupView = groupcontextmenu.currentGroupView;
if (currentGroupView) {
const link = currentGroupView.signalInfo.link;
const renderView = globalLookup.currentWiresRenderView;
const i = findViewIndexByLink(link);
if (i > 0) {
// 此时 renderView[i] 就是 currentGroupView我们需要替换它
// 把 i 之后的元素保存起来
const tailElements = renderView.slice(i + 1);
renderView.length = i;
// 把 i 的 children 先加进来,然后再把保存的元素加进来
currentGroupView.children.forEach(view => renderView.push(view));
tailElements.forEach(view => renderView.push(view));
// 归换颜色
groupColorDispatcher.put(currentGroupView.groupInfo.color);
}
}
groupcontextmenu.show = false;
updateCurrentGroups();
}
export function deleteGroup() {
const currentGroupView = groupcontextmenu.currentGroupView;
if (currentGroupView) {
const link = currentGroupView.signalInfo.link;
const renderView = globalLookup.currentWiresRenderView;
const i = findViewIndexByLink(link);
if (i > 0) {
const tailElements = renderView.slice(i + 1);
renderView.length = i;
tailElements.forEach(view => renderView.push(view));
currentGroupView.children.forEach(view => {
const signal = globalLookup.link2CurrentWires.get(view.signalInfo.link);
if (signal) {
globalLookup.currentWires.delete(signal);
globalLookup.link2CurrentWires.delete(signal.link);
delete globalLookup.currentSignalValues[signal.link];
}
});
groupColorDispatcher.put(currentGroupView.groupInfo.color);
}
}
groupcontextmenu.show = false;
updateCurrentGroups();
}

View File

@ -0,0 +1,227 @@
<template>
<div class="sidebar-context-menu">
<div class="menu-container">
<div
class="menu-item-container"
@click="deleteSignalByView()"
>
<span class="menu-item-icon iconfont icon-delete"></span>
<span class="menu-item-name">{{ t('context-menu.delete') }}</span>
</div>
<div
class="menu-item-container"
@click="createGroup()"
>
<span class="menu-item-icon iconfont icon-group"></span>
<span class="menu-item-name">{{ t('context-menu.create-group') }}</span>
</div>
<div
class="menu-item-container"
@mouseenter="existGroup.show()"
@mouseleave="existGroup.hide()"
>
<span class="menu-item-icon iconfont icon-join-group"></span>
<span class="menu-item-name">{{ t('context-menu.join-group') }}</span>
<transition name="collapse-from-top">
<ExistGroup v-show="existGroup.display"></ExistGroup>
</transition>
</div>
<div
class="menu-item-container"
@mouseenter="colorPicker.show()"
@mouseleave="colorPicker.hide()"
>
<span class="menu-item-icon iconfont icon-change-color"></span>
<span class="menu-item-name">{{ t('context-menu.change-color') }}</span>
<transition name="collapse-from-top">
<ColorPicker v-show="colorPicker.display"></ColorPicker>
</transition>
</div>
</div>
<hr>
<div
v-if="contextmenu.currentWire"
class="basic-info"
>
<div class="info-item">
<span class="info-item-title">{{ t('context-menu.signal.name') }}</span>
<span>
{{ contextmenu.currentWire.name }} (<code>{{contextmenu.currentWire.link}}</code>)
</span>
</div>
<div class="info-item">
<span class="info-item-title">{{ t('context-menu.signal.type') }}</span>
<span>
<span :class="makeSignalIconClass(contextmenu.currentWire)"></span>
<span>{{ contextmenu.currentWire.type }}</span>
</span>
</div>
<div class="info-item">
<span class="info-item-title">{{ t('context-menu.signal.width') }}</span>
<span>{{ contextmenu.currentWire.size }}</span>
</div>
<div class="info-item">
<span class="info-item-title">{{ t('context-menu.signal.dep') }}</span>
<span v-html="makeFullSignalNameDeps(contextmenu.currentWire)"></span>
</div>
</div>
</div>
</template>
<script setup>
import { defineComponent, reactive, ref } from 'vue';
import { globalLookup } from '@/hook/global';
import { useI18n } from 'vue-i18n';
import { colorPicker, contextmenu, deleteSignalByView } from './handle-contextmenu';
import { makeIconClass } from '@/hook/utils';
import ColorPicker from './color-picker.vue';
import ExistGroup from './exist-group.vue';
import { existGroup } from './exist-group';
import { createGroup } from './manage-group';
const { t } = useI18n();
defineComponent({ name: 'signal-context-menu' });
const props = defineProps({
});
const emits = defineEmits([]);
const predefineColors = reactive([
'#FF0000',
'#1AD834',
'#33E633',
'#FF4D7C',
'#ff4500',
'#ff8c00',
'#ffd700',
'#90ee90',
'#00ced1',
'#1e90ff',
'#c71585',
'#801fff',
'#7ca532',
]);
function makeSignalIconClass(signal) {
return 'iconfont ' + makeIconClass(signal);
}
/**
* @param {WireItemBaseInfo} signal
*/
function makeFullSignalNameDeps(signal) {
const deps = [];
while (signal) {
if (signal.name && signal.type) {
deps.push(signal);
}
signal = signal.parent;
}
let htmlString = '';
for (let i = deps.length - 1; i >= 0; -- i) {
const mod = deps[i];
// const displayName = mod.name.length > 6 ? mod.name.substring(0, 6) + '...' : mod.name;
const iconClass = makeIconClass(mod);
const iconText = `<span class="iconfont ${iconClass}"></span>${mod.name}`;
htmlString += iconText;
if (i > 0) {
htmlString += '<div class="dep-arrow"></div>';
}
}
htmlString = '<div class="signal-info-tooltip-wrapper">' + htmlString + '</div>';
return htmlString;
}
</script>
<style>
.sidebar-context-menu {
z-index: 200;
border-radius: .5em;
padding: 10px;
position: fixed;
box-shadow: 0 0 10px 1px rgb(16, 16, 16);
background-color: var(--sidebar);
border: solid 1px var(--sidebar-border);
}
.menu-container {
display: flex;
flex-direction: column;
align-items: center;
}
.menu-item-container {
font-size: 0.9rem;
display: flex;
align-items: center;
margin: 5px;
padding: 3px;
padding-left: 10px;
border-radius: .5em;
transition: var(--animation-3s);
cursor: pointer;
width: 95%;
position: relative;
}
.menu-item-container:hover {
color: var(--sidebar);
background: linear-gradient(
90deg,
#7CA532 25%,
rgba(200, 200, 200, 1) 37%,
rgba(255, 255, 255, 0) 63%
);
background-size: 400% 100%;
animation: loading-mask 1.5s cubic-bezier(0.23,1,0.32,1);
-webkit-animation: loading-mask 1.5s cubic-bezier(0.23,1,0.32,1);
transition: var(--animation-3s);
}
.menu-item-icon {
width: 42px;
font-size: 1.0rem;
}
.basic-info {
font-size: .8rem;
padding: 7px;
}
.info-item {
display: flex;
margin-top: 12px;
}
.info-item-title {
width: 75px;
}
.info-item code {
background-color: var(--button);
border-radius: .45em;
padding: 1px 5px;
margin: 1px 3px;
}
.info-item .iconfont {
margin-right: 10px;
}
.menu-item-container .el-color-picker__panel {
box-shadow: 0 0 10px 1px rgb(16, 16, 16);
}
.el-color-picker__panel button {
display: none;
}
</style>

View File

@ -0,0 +1,107 @@
<template>
<div class="signal-item"
:class="{
'active': globalLookup.sidebarSelectedWireLinks.has(props.view.signalInfo.link),
'right-active': contextmenu.isSameSignal(props.view) && contextmenu.show
}"
>
<div class="display-signal-item">
<div class="signal-color-vendor">
<span :class="makeSignalIconClass(signalInfo)"></span>
</div>
<div class="signal-info">
<div class="signal-info">
<div class="signal-info-name">
<span class="signal-parent-info" v-show="showParent">{{ getParentName(signalInfo) }}</span>
<span>{{ signalInfo.name }}</span>
</div>
<div class="signal-info-width" v-show="showWidth">
<div :class="signalInfo.size > 1 ? 'signal-info-caption' : ''">
{{ makeSignalCaption(signalInfo) }}
</div>
</div>
</div>
</div>
</div>
<div class="signal-info-current-value-wrapper">
<span></span>
<el-tooltip
effect="dark"
:content="globalLookup.currentSignalValues[signalInfo.link] + ''"
placement="top"
raw-content
><div class="signal-info-current-value">
{{ globalLookup.currentSignalValues[signalInfo.link] }}
</div></el-tooltip>
</div>
</div>
</template>
<script setup>
import { globalLookup, globalSetting } from '@/hook/global';
import { makeIconClass } from '@/hook/utils';
import { defineComponent, computed } from 'vue';
import { contextmenu } from './handle-contextmenu';
defineComponent({ name: 'signal-item' });
const showWidth = computed(() => globalSetting.displaySignalInfoScope.includes('width'));
const showParent = computed(() => globalSetting.displaySignalInfoScope.includes('parent'));
const props = defineProps({
view: {
type: Object,
required: false
},
signalInfo: {
type: Object,
required: false
}
});
const signalInfo = computed(() => {
const info = (props.view || {}).signalInfo || props.signalInfo;
return info;
});
/**
* @param {WireItemBaseInfo} signal
*/
function makeSignalIconClass(signal) {
const realSignal = globalLookup.link2CurrentWires.get(signal.link);
return 'iconfont ' + makeIconClass(realSignal);
}
function makeSignalCaption(signal) {
if (signal === undefined) {
return '';
}
return signal.size === 1 ? '' : `${signal.size - 1}:0`;
}
/**
* @param {WireItemBaseInfo} signal
*/
function getParentName(signal) {
if (signal === undefined) {
return '';
}
const realSignal = globalLookup.link2CurrentWires.get(signal.link);
if (realSignal) {
return realSignal.parent.name;
} else {
return '';
}
}
</script>
<style scoped>
.signal-item {
display: flex;
width: 100%;
}
</style>

View File

@ -0,0 +1,41 @@
<template>
<div>
<!-- 当前选择的波形的颜色选择的数量等等 -->
<div class="status">
+ {{ selectedSignalNumber }}
</div>
</div>
</template>
<script setup>
import { globalLookup } from '@/hook/global';
import { computed, defineComponent } from 'vue';
defineComponent({ name: 'current-status' });
const selectedSignalNumber = computed(() => {
return globalLookup.sidebarSelectedWireLinks.size;
});
const selectedColors = computed(() => {
return;
});
</script>
<style scoped>
.status {
color: var(--sidebar);
border-radius: .5em;
cursor: pointer;
height: 32px;
width: 32px;
background-color: var(--main-color);
display: flex;
align-items: center;
justify-content: center;
}
</style>

View File

@ -0,0 +1,65 @@
<template>
<div class="location">
<div
class="item iconfont icon-beginning"
/>
<div
class="item iconfont icon-ending"
/>
<el-divider direction="vertical"></el-divider>
<div
class="item iconfont icon-arrow-to-previous"
/>
<div
class="item iconfont icon-arrow-to-next"
/>
<el-divider direction="vertical"></el-divider>
<div
class="item iconfont icon-add-line"
/>
</div>
</template>
<script setup>
import { defineComponent } from 'vue';
defineComponent({ name: 'cursor-location' });
</script>
<style scoped>
.location {
display: flex;
align-items: center;
height: 32px;
padding: 1px 5px;
background-color: var(--sidebar);
border-radius: .5em;
}
.location .item {
border-radius: .5em;
display: flex;
align-items: center;
justify-content: center;
margin-left: 3px;
height: 32px;
font-weight: 800;
width: 32px;
cursor: pointer;
transition: var(--animation-3s);
}
.location .item:hover {
background-color: var(--main-color);
color: var(--sidebar);
transition: var(--animation-3s);
}
</style>

View File

@ -1,29 +1,49 @@
<template> <template>
<div class="toolbar-container"> <div class="toolbar-container">
<div class="toolbar-body"> <div class="toolbar-body">
<div> <CurrentStatus></CurrentStatus>
&emsp;
</div> <SignalModal></SignalModal>
&emsp;
<SignalValueFormat></SignalValueFormat>
&emsp;
<CursorLocation></CursorLocation>
&emsp;
<ValueSearch></ValueSearch>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup>
import { defineComponent } from 'vue';
import CurrentStatus from './current-status.vue';
import SignalModal from './signal-modal.vue';
import SignalValueFormat from './signal-value-format.vue';
import CursorLocation from './cursor-location.vue';
import ValueSearch from './value-search.vue';
defineComponent({ name: 'toolbar' });
</script> </script>
<style> <style>
.toolbar-container { .toolbar-container {
position: fixed; position: fixed;
bottom: 10px; top: 0;
left: 0;
width: 100%; width: 100%;
display: flex; height: var(--toolbar-height);
justify-content: center; background-color: var(--background);
align-items: center;
} }
.toolbar-body { .toolbar-body {
display: flex;
height: 100%;
align-items: top;
padding-left: 20px;
padding-top: 8px;
} }
</style> </style>

View File

@ -0,0 +1,3 @@
import { reactive } from "vue";
export const signalModal = reactive();

View File

@ -0,0 +1,59 @@
<template>
<div class="">
<el-radio-group v-model="signalModal">
<!-- 数字模式 -->
<el-radio-button :label="0">
<el-tooltip
effect="dark"
:content="t('toolbar.modal.common-digital')"
placement="bottom"
raw-content
>
<span class="iconfont icon-common-digital"></span>
</el-tooltip>
</el-radio-button>
<!-- 折现模拟 -->
<el-radio-button :label="1">
<el-tooltip
effect="dark"
:content="t('toolbar.modal.ladder-analog')"
placement="bottom"
raw-content
><span class="iconfont icon-ladder-analog"></span></el-tooltip>
</el-radio-button>
<!-- 阶梯模拟 -->
<el-radio-button :label="2">
<el-tooltip
effect="dark"
:content="t('toolbar.modal.line-analog')"
placement="bottom"
raw-content
><span class="iconfont icon-line-analog"></span></el-tooltip>
</el-radio-button>
</el-radio-group>
</div>
</template>
<script setup>
import { defineComponent, ref } from 'vue';
import { useI18n } from 'vue-i18n';
// 线
defineComponent({ name: 'signal-modal' });
const { t } = useI18n();
const signalModal = ref(0);
</script>
<style scoped>
.iconfont {
font-size: 1.10em;
font-weight: 800;
}
</style>

View File

@ -0,0 +1,90 @@
<template>
<div class="signal-format">
<el-select
v-model="currentOption"
>
<el-option-group
v-for="group in formatOptions"
:key="group.label"
:label="group.label"
>
<el-option
v-for="item in group.options"
:key="item.value"
:label="item.label"
:value="item.value"
>
</el-option>
</el-option-group>
</el-select>
</div>
</template>
<script setup>
import { defineComponent, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
//
defineComponent({ name: 'signal-value-format' });
const { t } = useI18n();
const currentOption = ref(0);
const formatOptions = reactive([
{
label: t('toolbar.format.category.base'),
options: [
{
value: 0,
label: 'Bin'
},
{
value: 1,
label: 'Oct'
},
{
value: 2,
label: 'Hex'
},
]
},
{
label: t('toolbar.format.category.dec'),
options: [
{
value: 3,
label: t('toolbar.format.signed')
},
{
value: 4,
label: t('toolbar.format.unsigned')
},
]
},
{
label: t('toolbar.format.category.float'),
options: [
{
value: 5,
label: t('toolbar.format.half')
},
{
value: 6,
label: t('toolbar.format.float')
},
{
value: 7,
label: t('toolbar.format.double')
},
]
},
]);
</script>
<style scoped>
.signal-format {
width: 100px;
}
</style>

View File

@ -0,0 +1,48 @@
<template>
<div class="value-search">
<div>
<el-radio-group
v-model="searchMode"
style="border-radius: 1.2em;"
>
<el-radio-button :label="0">
<span>{{ t('toolbar.search.value') }}</span>
</el-radio-button>
<el-radio-button :label="1">
<span>{{ t('toolbar.search.name') }}</span>
</el-radio-button>
</el-radio-group>
</div>
<div class="value-input-wrapper">
<el-input></el-input>
</div>
</div>
</template>
<script setup>
import { defineComponent, ref } from 'vue';
import { useI18n } from 'vue-i18n';
defineComponent({ name: 'value-search' })
const { t } = useI18n();
const searchMode = ref(0);
</script>
<style scoped>
.value-search {
margin-left: 5px;
padding-top: 2px;
display: flex;
height: 32px;
align-items: center;
}
.value-input-wrapper {
margin-left: 5px;
width: 120px;
}
</style>

View File

@ -2,18 +2,16 @@
<!-- treeview --> <!-- treeview -->
<div class="vcd-treeview"> <div class="vcd-treeview">
<TreeViewSearch></TreeViewSearch> <TreeViewSearch></TreeViewSearch>
<br>
<div class="vcd-module-wrapper"> <div class="vcd-module-wrapper">
<div class="vcd-module-info"> <div class="vcd-module-info">
<div class="vcd-signal-title">{{ t('module') }}</div> <div class="vcd-signal-title">{{ t('module') }}</div>
<hr> <hr>
<div class="vcd-module-display-wrapper"> <el-scrollbar height="86vh" style="padding-right: 7px;">
<Modules v-for="mod of props.topModules" <Modules v-for="mod of props.topModules"
:key="mod.name" :key="mod.name"
:module="mod" :module="mod"
></Modules> ></Modules>
</div> </el-scrollbar>
</div> </div>
<div class="vcd-module-wires"> <div class="vcd-module-wires">
<Signals></Signals> <Signals></Signals>
@ -63,10 +61,6 @@ export default {
width: 220px; width: 220px;
} }
.vcd-module-display-wrapper {
height: 90vh;
overflow: scroll;
}
.vcd-treeview { .vcd-treeview {
background-color: var(--sidebar); background-color: var(--sidebar);

View File

@ -2,24 +2,26 @@
<div <div
class="tree-view-search-wrapper" class="tree-view-search-wrapper"
> >
<div> <div style="height: 5vh;">
<el-input <el-input
:placeholder="t('search-signal')" :placeholder="t('search-signal')"
size="large"
v-model="searchManage.content" v-model="searchManage.content"
input-style="font-size: 16px;"
@input="safeSearch" @input="safeSearch"
@focus="searchManage.displayResult = true" @focus="searchManage.displayResult = true"
@blur="searchManage.displayResult = false" @blur="searchManage.displayResult = false"
/> />
</div> </div>
<transition name="collapse-from-top">
<div <div
v-if="searchManage.displayResult | searchManage.mouseOnResult" v-show="searchManage.displayResult | searchManage.mouseOnResult"
:style="searchResultWrapper" :style="searchResultWrapper"
class="search-result-wrapper" class="search-result-wrapper"
id="search-result-wrapper" id="search-result-wrapper"
> >
<div class="search-result" <el-scrollbar
height="50vh"
width="600px"
class="search-result"
@mouseenter="searchManage.mouseOnResult = true" @mouseenter="searchManage.mouseOnResult = true"
@mouseleave="searchManage.mouseOnResult = false" @mouseleave="searchManage.mouseOnResult = false"
> >
@ -31,13 +33,15 @@
<div v-html="searchResult.htmlString" class="search-result-item"></div> <div v-html="searchResult.htmlString" class="search-result-item"></div>
</div> </div>
</div> </div>
<div v-else> <div v-else class="search-nothing">
{{ t('search-nothing') }} <span class="iconfont icon-empty"></span>
<span>{{ t('search-nothing') }}</span>
</div> </div>
</el-scrollbar>
</div> </div>
</transition>
</div> </div>
</div>
</template> </template>
<script setup> <script setup>
@ -70,6 +74,7 @@ function search() {
const searchString = searchManage.content.trim(); const searchString = searchManage.content.trim();
if (searchString.length === 0) { if (searchString.length === 0) {
searchManage.displayResult = false; searchManage.displayResult = false;
searchManage.searchResult = []
return; return;
} }
@ -111,7 +116,6 @@ const safeSearch = debounceWrapper(search, 500);
<style> <style>
.tree-view-search-wrapper { .tree-view-search-wrapper {
min-height: 30px;
padding: 10px; padding: 10px;
transition: flex .5s ease-in-out; transition: flex .5s ease-in-out;
} }
@ -129,11 +133,8 @@ const safeSearch = debounceWrapper(search, 500);
} }
.search-result { .search-result {
overflow-x: scroll;
overflow-y: scroll;
max-height: 80vh; max-height: 80vh;
max-width: 600px; max-width: 600px;
padding-right: 5px; padding-right: 5px;
} }
@ -157,8 +158,30 @@ const safeSearch = debounceWrapper(search, 500);
width: 7px; width: 7px;
border-top: solid 1.7px var(--sidebar-item-text); border-top: solid 1.7px var(--sidebar-item-text);
border-left: solid 1.7px var(--sidebar-item-text); border-left: solid 1.7px var(--sidebar-item-text);
transform: rotate(135deg); transform: rotate(225deg);
margin-right: 8px; margin-top: 6px;
margin-bottom: 8px;
margin-left: 3px; margin-left: 3px;
} }
.search-result-item .dep-arrow {
transform: rotate(135deg);
margin-left: 2px;
margin-right: 7px;
}
.search-nothing {
height: 40vh;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 1.1rem;
}
.search-nothing .iconfont {
font-size: 120px;
}
</style> </style>

View File

@ -2,11 +2,12 @@
<div> <div>
<div class="vcd-signal-title">{{ t('signal') }}({{signals.content.length}})</div> <div class="vcd-signal-title">{{ t('signal') }}({{signals.content.length}})</div>
<hr> <hr>
<div class="vcd-signal-signals-display"> <el-scrollbar height="86vh" class="vcd-signal-signals-display">
<div v-for="(signal, index) in signals.content" :key="index" <div v-for="(signal, index) in signals.content" :key="index"
@click="toggleRender(signal)" @click="toggleRender(signal)"
class="vcd-signal-signal-item" class="vcd-signal-signal-item"
:class="globalLookup.currentWires.has(signal) ? 'vcd-treeview-selected' : ''"> :class="globalLookup.currentWires.has(signal) ? 'vcd-treeview-selected' : ''"
>
<div class="vcd-signal-signal-item-text"><span :class="`iconfont ${makeIconClass(signal)}`"></span>&ensp;{{ signal.name }}</div> <div class="vcd-signal-signal-item-text"><span :class="`iconfont ${makeIconClass(signal)}`"></span>&ensp;{{ signal.name }}</div>
<div> <div>
<div :class="signal.size > 1 ? 'vcd-signal-signal-caption' : ''"> <div :class="signal.size > 1 ? 'vcd-signal-signal-caption' : ''">
@ -14,7 +15,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </el-scrollbar>
</div> </div>
</template> </template>
@ -62,6 +63,7 @@ export default {
<style> <style>
.vcd-signal-title { .vcd-signal-title {
margin-left: 5px; margin-left: 5px;
height: 2.5vh;
} }
.icon-wave-square { .icon-wave-square {
@ -95,10 +97,9 @@ export default {
.vcd-signal-signals-display { .vcd-signal-signals-display {
color: var(--sidebar-item-text); color: var(--sidebar-item-text);
padding: 0px 8px; padding: 0px 8px;
height: 90vh;
overflow-x: scroll;
} }
.vcd-signal-signal-item { .vcd-signal-signal-item {
margin: 3px; margin: 3px;
display: flex; display: flex;

View File

@ -10,17 +10,30 @@ export const emitter = mitt();
* *
*/ */
export const globalLookup = reactive({ export const globalLookup = reactive({
// 所有的顶层文件 /**
* @description 所有的顶层文件
* @type {TopModWireItem[]}
*/
topModules: [], topModules: [],
// 当前选中的 信号,也就是 tree-view 左列的,默认是第一个。不可复选。
/**
* @description 当前选中的 信号也就是 tree-view 左列的默认是第一个不可复选
* @type {WireItem | undefined}
*/
currentModule: undefined, currentModule: undefined,
/** /**
* @description 当前被选中的波形用于进行高效的增删改查 * @description 当前被选中的波形用于进行高效的增删改查
* @type {Set<WaveRenderItem>} * @type {Set<WireItem>}
*/ */
currentWires: new Set(), currentWires: new Set(),
/**
* @description 当前被选中的波形 currentWires 的字典版本
* @type {Map<string, WireItem>}
*/
link2CurrentWires: new Map(),
/** /**
* @description 当前被选中的波形用于 sidebar 的渲染视图 * @description 当前被选中的波形用于 sidebar 的渲染视图
* 详细请见设计文档https://nc-ai-lab.feishu.cn/wiki/Fy3ZwtbYbiatmxkhOp2cyHSFnlw * 详细请见设计文档https://nc-ai-lab.feishu.cn/wiki/Fy3ZwtbYbiatmxkhOp2cyHSFnlw
@ -34,6 +47,19 @@ export const globalLookup = reactive({
*/ */
currentSignalValues: {}, currentSignalValues: {},
/**
* @description 侧边栏中当前被选中的波形一定是 currentWires 的子集
* 内部存储的是 wire link所以是 string
* @type {Set<string>}
*/
sidebarSelectedWireLinks: new Set(),
/**
* @description 当前渲染的信号的基本选项比如高度颜色formatmodal 等等
* @type {Map<string, >}
*/
currentSignalRenderOptions: new Map(),
// 当前 ns 数(或者 ps // 当前 ns 数(或者 ps
currentTime: 0, currentTime: 0,
@ -62,7 +88,14 @@ export const globalLookup = reactive({
// 初始化时会被定义 // 初始化时会被定义
render: () => {}, render: () => {},
/**
* @description 非常核心的用于操控 webgl 进行渲染和动画的核心类
* @type {WebGL2WaveRender}
*/
waveRender: undefined, waveRender: undefined,
pstate: undefined, pstate: undefined,
xScale: 1, xScale: 1,
@ -88,9 +121,16 @@ export const globalLookup = reactive({
}); });
export const globalStyle = reactive({ export const globalStyle = reactive({
timeScaleHeight: 30, /**
* @description 这个值代表 sidebar 的上侧的空间但是 sidebar 本身有一个 10px padding所以真实的 sidebar 上侧
* 空出来的空间等于 timeScaleHeight + 10需要注意 timeScaleHeight 需要等于 pstate.yOffset 的初始值的相反数
* pstate.yOffset 的初始值请参考 dom-container.js 这个文件
* @type {number}
*/
timeScaleHeight: 20,
sideBarPadding: 10, sideBarPadding: 10,
sideBarItemMargin: 5, sideBarItemMargin: 2,
vcdRenderPadding: 24, vcdRenderPadding: 24,
yOffset: 0, yOffset: 0,
}); });

View File

@ -25,6 +25,9 @@ function debounceWrapper(fn, delay) {
function makeIconClass(mod) { function makeIconClass(mod) {
if (mod === undefined) {
return '';
}
switch (mod.type) { switch (mod.type) {
case 'module': return 'icon-memory-chip'; case 'module': return 'icon-memory-chip';
case 'begin': return 'icon-brackets'; case 'begin': return 'icon-brackets';

View File

@ -19,10 +19,22 @@ export const WaveContainerView = {
// 增加高效 CRUD 视图 // 增加高效 CRUD 视图
globalLookup.currentWires.add(signal); globalLookup.currentWires.add(signal);
// 上面的 视图 的 string, signal 字典版本
globalLookup.link2CurrentWires.set(signal.link, signal);
// 增加 sidebar 渲染视图 // 增加 sidebar 渲染视图
// TODO : 支持更加复杂的视图加入 // TODO : 支持更加复杂的视图加入
globalLookup.currentWiresRenderView.push({ globalLookup.currentWiresRenderView.push({
signal, signalInfo: {
name: signal.name,
link: signal.link,
size: signal.size,
},
groupInfo: {
name: '',
color: '',
collapse: false
},
renderType: 0, renderType: 0,
children: [] children: []
}); });
@ -38,8 +50,18 @@ export const WaveContainerView = {
* @param {WireItem} signal * @param {WireItem} signal
*/ */
delete(signal) { delete(signal) {
const renderView = globalLookup.currentWiresRenderView;
globalLookup.currentWires.delete(signal); globalLookup.currentWires.delete(signal);
globalLookup.link2CurrentWires.delete(signal.link);
delete globalLookup.currentSignalValues[signal.link]; delete globalLookup.currentSignalValues[signal.link];
// 从 currentWiresRenderView 中删除
const i = findViewIndexByLink(signal.link);
const tailElements = renderView.slice(i + 1);
renderView.length = i;
tailElements.forEach(view => renderView.push(view));
}, },
/** /**
@ -51,3 +73,45 @@ export const WaveContainerView = {
return globalLookup.currentWires.has(signal); return globalLookup.currentWires.has(signal);
} }
}; };
export function findViewIndexByLink(link) {
const renderView = globalLookup.currentWiresRenderView;
let i = 0;
while (renderView[i].signalInfo.link !== link && i < renderView.length) {
++ i;
}
if (i < renderView.length) {
return i;
} else {
return -1;
}
}
function makeFullSignalNameDeps(signal) {
const deps = [];
while (signal) {
if (signal.name && signal.type) {
deps.push(signal);
}
signal = signal.parent;
}
let htmlString = '';
for (let i = deps.length - 1; i >= 0; -- i) {
const mod = deps[i];
// const displayName = mod.name.length > 6 ? mod.name.substring(0, 6) + '...' : mod.name;
const iconClass = makeIconClass(mod);
const iconText = `<span class="iconfont ${iconClass}"></span>&ensp;${mod.name}`;
htmlString += iconText;
if (i > 0) {
htmlString += '<div class="dep-arrow"></div>';
}
}
htmlString = '<div class="signal-info-tooltip-wrapper">' + htmlString + '</div>';
return htmlString;
}

View File

@ -117,13 +117,13 @@ const domContainer = (obj) => {
topBarHeight: fontHeight * 1.5, // [px] topBarHeight: fontHeight * 1.5, // [px]
botBarHeight: fontHeight * 1.5, // [px] botBarHeight: fontHeight * 1.5, // [px]
xOffset: sidebarWidth, xOffset: sidebarWidth,
yOffset: -40, // [px] yOffset: -20, // [px]
yStep: 54, // = 24 // [px] wave lane height yStep: 54, // = 24 // [px] wave lane height
yDuty: 0.6, yDuty: 0.6,
// for animation // for animation
oldXOffset: sidebarWidth, oldXOffset: sidebarWidth,
oldYOffset: -40, oldYOffset: -20,
oldYStep: 54, oldYStep: 54,
oldYDuty: 0.6, oldYDuty: 0.6,
@ -155,6 +155,8 @@ const domContainer = (obj) => {
time: deso.time time: deso.time
}); });
// TODO : 增加已有波形的保存和读取
try { try {
const str = localStorage.getItem('dide'); const str = localStorage.getItem('dide');
const obj = JSON.parse(str); const obj = JSON.parse(str);

View File

@ -4,7 +4,11 @@ const genResizeHandler = pstate =>
(width, height) => { (width, height) => {
let { xOffset, yOffset, xScale, yStep, time, sidebarWidth, numLanes } = pstate; let { xOffset, yOffset, xScale, yStep, time, sidebarWidth, numLanes } = pstate;
pstate.width = width; pstate.width = width;
pstate.height = height;
// TODO: 此处用于管理 波形渲染区域 的整体方位
// 真实高度还要减去 上方 toolbar 的高度
// 此处的 60 为 --toolbar-height
pstate.height = height - 55;
// Y // Y
const yOffsetMax = (numLanes + 2) * 2 * yStep; const yOffsetMax = (numLanes + 2) * 2 * yStep;

View File

@ -23,7 +23,11 @@ const getLabel = (lane) => {
value = BigInt(value); value = BigInt(value);
const txtShort = formatter(value, pos, width); let txtShort = formatter(value, pos, width);
// TODO: 重构这里
if (txtShort.startsWith('...')) {
txtShort = txtShort.replace('...', '');
}
return ['text', { x, class: 'common' }, txtShort]; return ['text', { x, class: 'common' }, txtShort];
}; };

View File

@ -1,3 +1,4 @@
import { gl_Colors_template } from "./render-utils";
class ShaderMaker { class ShaderMaker {
/** /**
@ -26,12 +27,14 @@ class ShaderMaker {
} }
} }
const colorsLength = gl_Colors_template.length << 1;
const vertexShader = new ShaderMaker('VERTEX_SHADER', `#version 300 es const vertexShader = new ShaderMaker('VERTEX_SHADER', `#version 300 es
in uvec4 pos; in uvec4 pos;
out vec4 v_color; out vec4 v_color;
uniform vec2 scale; uniform vec2 scale;
uniform vec2 offset; uniform vec2 offset;
uniform vec4 colors[16]; uniform vec4 colors[${colorsLength}];
uniform vec2 shifts[7]; // 基础八位图偏移量为了性能pos 只传入整数,需要的坐标负数由该值提供 uniform vec2 shifts[7]; // 基础八位图偏移量为了性能pos 只传入整数,需要的坐标负数由该值提供
uniform vec2 widthShifts[8]; // 用于构造线宽的偏移 uniform vec2 widthShifts[8]; // 用于构造线宽的偏移

View File

@ -24,29 +24,56 @@ function getRatio() {
const screenHeightPixel = window.screen.height * getRatio() / 100; const screenHeightPixel = window.screen.height * getRatio() / 100;
const screenWidthPixel = window.screen.width * getRatio() / 100; const screenWidthPixel = window.screen.width * getRatio() / 100;
// 控制颜色 // rgba 颜色通道,都是预设的颜色
const gl_Colors = new Float32Array([ export const gl_Colors_template = [
0, 0, 0, 0, // 0: 空 [0, 0, 0, 0 ], // 0: 空
[0, 0, 255, 1], // 1: 未知态 X 默认颜色
[51, 230, 26, 1], // 2: value = 0 用于 width = 1 的信号 默认颜色
[51, 230, 26, 1], // 3: value = 1 用于 width = 1 的信号 默认颜色
[230, 51, 51, 1], // 4: 高阻态 Z 默认颜色
[124, 77, 255, 1], // 5: vec 用于 width > 1 的信号
[255, 0, 255, 1], // 6: yellow
[255, 0, 255, 1], // 7: strange purple
[0, 255, 0, 0.5], // 8: (l L) weak 0
[255, 0, 255, 0.5], // 9: (h H) weak 1
[255, 0, 0, 0.5], // 10: (w W) weak unknown
0, 0, 1, 1, // 1: 未知态 X // >= 11 自定义颜色
0.2, 0.847, 0.1, 1, // 2: value = 0 用于 width = 1 的信号 [255, 140, 0, 1],
0.2, 0.847, 0.1, 1, // 3: value = 1 用于 width = 1 的信号 [255, 140, 0, 1],
0.9, 0.2, 0.2, 1, // 4: 高阻态 Z [144, 238, 144, 1],
0.486, 0.302, 1., 1, // 5: vec 用于 width > 1 的信号 [0, 206, 209, 1],
[30, 144, 255, 1],
[199, 21, 133, 1],
[128, 31, 255, 1],
[124, 165, 50, 1]
];
1, 1, 0, 1, // 6: yellow function makeGlColors() {
1, 0, 1, 1, // 7: strange purple const glcolors = [];
for (const rgba of gl_Colors_template) {
const r = rgba[0] / 255;
const g = rgba[1] / 255;
const b = rgba[2] / 255;
const a = rgba[3];
glcolors.push(r, g, b, a);
}
// 制作 mask
for (const rgba of gl_Colors_template) {
const r = rgba[0] / 255;
const g = rgba[1] / 255;
const b = rgba[2] / 255;
glcolors.push(r, g, b, 0.1);
}
return glcolors;
}
0, 1, 0, .5, // 8: (l L) weak 0
0, 1, 1, .5, // 9: (h H) weak 1
1, 0, 0, .5, // 10: (w W) weak unknown
0, 0, 1, 0.1, // 11: 未知态 X 遮罩 // 根据 gl_Colors_template 计算出 gl_Colors
0.2, 0.847, 0.1, 0.1, // 12: value = 0 遮罩 // 控制颜色 0 - 1 归一化的 bgr
0.2, 0.847, 0.1, 0.1, // 13: value = 1 遮罩 const gl_Colors = new Float32Array(makeGlColors());
0.9, 0.2, 0.2, 0.1, // 14: 高阻态 Z 遮罩
0.486, 0.302, 1., 0.1 // 15: vec 遮罩 export let maskColorIndexOffset = gl_Colors_template.length;
]);
// 特殊偏移量 // 特殊偏移量

View File

@ -8,6 +8,7 @@ import vline from './vline';
import getX from './get-x'; import getX from './get-x';
import vlineStylo from './vline-stylo'; import vlineStylo from './vline-stylo';
import getLabel from './get-label'; import getLabel from './get-label';
import { globalLookup } from '../global.js';
const defs = ['defs', const defs = ['defs',
['linearGradient', { id: 'valid' }, ['linearGradient', { id: 'valid' },
@ -32,18 +33,56 @@ const defs = ['defs',
}) })
]; ];
/**
*
* @param {GlobalLookup} desc
* @param {*} pstate
* @returns
*/
function* renderValues(desc, pstate) { function* renderValues(desc, pstate) {
const { width, height, sidebarWidth, yOffset, yStep, topBarHeight, botBarHeight } = pstate; const { width, height, yOffset, yStep, topBarHeight, botBarHeight } = pstate;
// TODO: 对齐参数
const sidebarWidth = 230;
// 根据 currentWiresRenderView 视图渲染
// 此处应该和 render-wave 的相同注释的地方保持逻辑上的一致render-wave.js 约 646 行)
const currentWires = desc.currentWires; const currentWires = desc.currentWires;
const view = []; const renderSignals = [];
for (const signal of currentWires) { const link2CurrentWires = globalLookup.link2CurrentWires;
view.push({ for (const view of globalLookup.currentWiresRenderView) {
kind: signal.kind, if (view.renderType === 0) {
name: signal.name, const realSignal = link2CurrentWires.get(view.signalInfo.link);
ref: signal.link renderSignals.push({
name: realSignal.name,
kind: realSignal.kind,
ref: realSignal.link
});
} else {
// 如果是组,需要渲染空白的一行
renderSignals.push({
name: '',
kind: '',
ref: ''
});
// 如果没有关闭,把所有子节点加入其中
if (view.groupInfo.collapse === false) {
for (const child of view.children) {
const realSignal = link2CurrentWires.get(child.signalInfo.link);
renderSignals.push({
name: realSignal.name,
kind: realSignal.kind,
ref: realSignal.link
}); });
} }
}
}
}
const ilen = height / yStep; const ilen = height / yStep;
const iskip = yOffset / yStep; const iskip = yOffset / yStep;
@ -52,7 +91,7 @@ function* renderValues(desc, pstate) {
let ifirst = 0; let ifirst = 0;
for (let i = 0; i < ilen; ++ i) { for (let i = 0; i < ilen; ++ i) {
const lane = view[i]; const lane = renderSignals[i];
if (lane && (lane.name || lane.kind)) { if (lane && (lane.name || lane.kind)) {
if (i > iskip) { if (i > iskip) {
break; break;
@ -67,7 +106,7 @@ function* renderValues(desc, pstate) {
ml.push(markers); ml.push(markers);
for (let i = 0; i < (iskip + ilen); i++) { for (let i = 0; i < (iskip + ilen); i++) {
const lane = view[i + (ifirst | 0)]; const lane = renderSignals[i + (ifirst | 0)];
if (lane && lane.kind === 'DIZ') { if (lane && lane.kind === 'DIZ') {
markers.push(['g', tt(0, Math.round((i - (iskip - ifirst) + 1.18) * yStep))].concat(water(lane, desc, pstate))); markers.push(['g', tt(0, Math.round((i - (iskip - ifirst) + 1.18) * yStep))].concat(water(lane, desc, pstate)));
@ -76,7 +115,9 @@ function* renderValues(desc, pstate) {
} else if (lane && lane.ref) { } else if (lane && lane.ref) {
const chango = desc.chango[lane.ref]; const chango = desc.chango[lane.ref];
if (chango && chango.kind === 'vec') { if (chango && chango.kind === 'vec') {
const mLane = ['g', tt(0, Math.round((i - (iskip - ifirst) + 0.1) * yStep))]; // tt: 计算出当前这一行的所有 值 svg 在 Y 方向的偏移
const mLane = ['g', tt(0, Math.round((i - (iskip - ifirst) + 0.15) * yStep))];
const { wave } = chango; const { wave } = chango;
const jlen = wave.length; const jlen = wave.length;
@ -103,6 +144,7 @@ function* renderValues(desc, pstate) {
// 宽度太小不画了 // 宽度太小不画了
if (w > 8) { if (w > 8) {
const x = Math.round((xPreNorm + xCurNorm) / 2); const x = Math.round((xPreNorm + xCurNorm) / 2);
// 计算 当前这一个 值 在 X 方向上的偏移
mLane.push(labeler(vPre, mPre, x, w)); mLane.push(labeler(vPre, mPre, x, w));
} }
} }
@ -119,8 +161,8 @@ function* renderValues(desc, pstate) {
} }
// console.log(view); // console.log(view);
for (let i = 0; i < view.length; i++) { for (let i = 0; i < renderSignals.length; i++) {
const lane = view[i]; const lane = renderSignals[i];
if (lane && lane.vlines) { if (lane && lane.vlines) {
markers.push(...vline(lane, pstate, i)); markers.push(...vline(lane, pstate, i));
} }

View File

@ -1,6 +1,6 @@
import { globalSetting, globalStyle } from '../global'; import { globalSetting, globalStyle } from '../global';
import { gl_Colors, gl_Shifts, gl_Shifts_for_bar, gl_Shifts_map, gl_WidthShifts, barShift, getRatio, screenHeightPixel } from './render-utils.js'; import { gl_Colors, gl_Shifts, gl_Shifts_for_bar, gl_Shifts_map, gl_WidthShifts, barShift, getRatio, screenHeightPixel, maskColorIndexOffset } from './render-utils.js';
import { vertexShader, fragmentShader } from './render-shader.js'; import { vertexShader, fragmentShader } from './render-shader.js';
// const { ChangoItem } = require('./types.d.ts'); // const { ChangoItem } = require('./types.d.ts');
@ -23,6 +23,12 @@ class WebGL2WaveRender {
this.pstate = pstate; this.pstate = pstate;
this.plugins = plugins; this.plugins = plugins;
// 用于在外部进行额外特性更新使用的一个类,未来需要往渲染管线中添加量可以随时往里面加东西,然后
// 在管线相关的函数中去使用它
this.renderPipeHook = {
userDefinedColor: undefined
};
const gl = canvas.getContext('webgl2', { const gl = canvas.getContext('webgl2', {
premultipliedAlpha: false, premultipliedAlpha: false,
alpha: true, alpha: true,
@ -95,7 +101,7 @@ class WebGL2WaveRender {
/** /**
* *
* @param {string} id * @param {string} id 波形的 link
* @returns {{ * @returns {{
* lineVertices: Uint32Array * lineVertices: Uint32Array
* maskVertices: Uint32Array * maskVertices: Uint32Array
@ -130,13 +136,23 @@ class WebGL2WaveRender {
* }} * }}
*/ */
translateValue2RenderParameter(value) { translateValue2RenderParameter(value) {
let colorParam;
switch (value) { switch (value) {
case 0: return { y: -1, color: 2 }; // 0 value case 0: colorParam = { y: -1, color: 2 }; break; // 0 value
case 1: return { y: 1, color: 3 }; // 1 value case 1: colorParam = { y: 1, color: 3 }; break; // 1 value
case 2: case 3: return { y: -1, color: 4 }; // 不定态 x case 2: case 3: colorParam = { y: -1, color: 4 }; break; // 不定态 x
case 4: case 5: return { y: 0, color: 2 }; // 高阻态 z case 4: case 5: colorParam = { y: 0, color: 2 }; break; // 高阻态 z
default: return { y: -1, color: 7 }; // 其他,我也不知道还有啥 default: colorParam = { y: -1, color: 7 }; break; // 其他,我也不知道还有啥
} }
if (value === 0 || value === 1) {
const renderPipeHook = this.renderPipeHook;
if (renderPipeHook.userDefinedColor !== undefined) {
colorParam.color = renderPipeHook.userDefinedColor;
}
}
return colorParam;
} }
/** /**
@ -265,17 +281,6 @@ class WebGL2WaveRender {
const lineVertices = []; const lineVertices = [];
const maskVertices = []; const maskVertices = [];
// const lineVertices = [
// 0, 0, 2, 0,
// 10, 0, 2, 0,
// 10, 4, 2, 0,
// 20, 4, 2, 0,
// 20, 0, 2, 0,
// 30, 0, 2, 0,
// 30, 4, 2, 0
// ];
// return new Uint32Array(lineVertices);
// 制作 lineVertices // 制作 lineVertices
for (let i = 0; i < pointNum; ++ i) { for (let i = 0; i < pointNum; ++ i) {
@ -318,7 +323,7 @@ class WebGL2WaveRender {
// 回退 // 回退
if (-- i > 0) { if (-- i > 0) {
// 四元组: (x, yshift_index, color_index, width_shift_index) // 四元组: (x, yshift_index, color_index, width_shift_index)
const rectangleVertices = this.makeRectangleVertices(p1.x, 1, perspectivePoints[i].x, -1, p1.color + 10, 4); const rectangleVertices = this.makeRectangleVertices(p1.x, 1, perspectivePoints[i].x, -1, p1.color + maskColorIndexOffset, 4);
// 三角图元画矩形 // 三角图元画矩形
maskVertices.push(...rectangleVertices); maskVertices.push(...rectangleVertices);
continue; continue;
@ -335,7 +340,7 @@ class WebGL2WaveRender {
if (p2.y > p1.y) { if (p2.y > p1.y) {
// 矩形的四个点 // 矩形的四个点
// 四元组: (x, yshift_index, color_index, width_shift_index) // 四元组: (x, yshift_index, color_index, width_shift_index)
const rectangleVertices = this.makeRectangleVertices(p2.x, p2.y, p3.x, p1.y, p2.color + 10, 4); const rectangleVertices = this.makeRectangleVertices(p2.x, p2.y, p3.x, p1.y, p2.color + maskColorIndexOffset, 4);
// 三角图元画矩形 // 三角图元画矩形
maskVertices.push(...rectangleVertices); maskVertices.push(...rectangleVertices);
} }
@ -354,8 +359,6 @@ class WebGL2WaveRender {
} }
/** /**
* *
* @param {Array<string | number>} wave * @param {Array<string | number>} wave
@ -369,6 +372,7 @@ class WebGL2WaveRender {
const lineVertices = []; const lineVertices = [];
const maskVertices = []; const maskVertices = [];
const length = wave.length; const length = wave.length;
const renderPipeHook = this.renderPipeHook;
for (let i = 0; i < length; ++ i) { for (let i = 0; i < length; ++ i) {
const [t1, val, mask] = wave[i]; const [t1, val, mask] = wave[i];
@ -384,7 +388,14 @@ class WebGL2WaveRender {
const a2 = {x: t1, y: 1}; const a2 = {x: t1, y: 1};
const a3 = {x: t2, y: 2}; const a3 = {x: t2, y: 2};
const color = mask ? 4 : 5; let color;
if (mask) {
color = 4;
} else if (renderPipeHook.userDefinedColor !== undefined){
color = renderPipeHook.userDefinedColor;
} else {
color = 5;
}
const points = [ a1, p1, a3, a2, p0, a0 ]; const points = [ a1, p1, a3, a2, p0, a0 ];
const wsIndice = [ 1, 2, 3, 5, 6, 7 ]; const wsIndice = [ 1, 2, 3, 5, 6, 7 ];
@ -415,9 +426,9 @@ class WebGL2WaveRender {
const point2 = points[i2]; const point2 = points[i2];
const point3 = points[i3]; const point3 = points[i3];
maskVertices.push( maskVertices.push(
point1.x, point1.y, color + 10, wsIndice[i1], point1.x, point1.y, color + maskColorIndexOffset, wsIndice[i1],
point2.x, point2.y, color + 10, wsIndice[i2], point2.x, point2.y, color + maskColorIndexOffset, wsIndice[i2],
point3.x, point3.y, color + 10, wsIndice[i3] point3.x, point3.y, color + maskColorIndexOffset, wsIndice[i3]
); );
} }
} }
@ -470,6 +481,41 @@ class WebGL2WaveRender {
gl.enableVertexAttribArray(webglLocation.pos); gl.enableVertexAttribArray(webglLocation.pos);
} }
/**
* @description 用于重置 VAO 的函数
* @param {string} id 波形的 link
*/
resetVertice(id) {
const webglLocation = this.webglLocation;
const gl = webglLocation.gl;
const signalItem = this.globalLookup.chango[id];
const { lineVertices, maskVertices } = this.makeVertexByID(id);
this.lineVerticesMap.set(id, lineVertices);
this.maskVerticesMap.set(id, maskVertices);
// 清除自定义标志位
this.renderPipeHook.userDefinedColor = undefined;
// 创建并设置 绘制wave轮廓 主体轮廓的 缓冲区、vao、顶点设置
const lineVertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, lineVertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, lineVertices, gl.STATIC_DRAW);
signalItem.lineVao = gl.createVertexArray();
gl.bindVertexArray(signalItem.lineVao);
gl.vertexAttribIPointer(webglLocation.pos, 4, gl.UNSIGNED_INT, 0, 0);
gl.enableVertexAttribArray(webglLocation.pos);
// 创建并设置 绘制wave半透明遮罩层 主体轮廓的 缓冲区、vao、顶点设置
const maskVertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, maskVertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, maskVertices, gl.STATIC_DRAW);
signalItem.maskVao = gl.createVertexArray();
gl.bindVertexArray(signalItem.maskVao);
gl.vertexAttribIPointer(webglLocation.pos, 4, gl.UNSIGNED_INT, 0, 0);
gl.enableVertexAttribArray(webglLocation.pos);
}
initData() { initData() {
if (globalSetting.prerender) { if (globalSetting.prerender) {
for (const id of Reflect.ownKeys(this.globalLookup.chango)) { for (const id of Reflect.ownKeys(this.globalLookup.chango)) {
@ -485,7 +531,7 @@ class WebGL2WaveRender {
} }
/** /**
* * @description 更新默认波形的颜色
* @param {Array<Updater>} updaters * @param {Array<Updater>} updaters
* @param {{ * @param {{
* updateMask: boolean * updateMask: boolean
@ -504,7 +550,7 @@ class WebGL2WaveRender {
gl_Colors[startIndex + 3] = rgba.alpha; gl_Colors[startIndex + 3] = rgba.alpha;
if (config.updateMask) { if (config.updateMask) {
const maskIndex = (updater.index + 10) * 4; const maskIndex = (updater.index + maskColorIndexOffset) * 4;
gl_Colors[maskIndex] = rgba.red; gl_Colors[maskIndex] = rgba.red;
gl_Colors[maskIndex + 1] = rgba.green; gl_Colors[maskIndex + 1] = rgba.green;
gl_Colors[maskIndex + 2] = rgba.blue; gl_Colors[maskIndex + 2] = rgba.blue;
@ -544,7 +590,7 @@ class WebGL2WaveRender {
const plugins = this.plugins; const plugins = this.plugins;
pstate.yStep = globalSetting.displaySignalHeight + globalStyle.sideBarItemMargin; pstate.yStep = globalSetting.displaySignalHeight + globalStyle.sideBarItemMargin;
pstate.yDuty = (1 - 2 * globalStyle.sideBarItemMargin / this.pstate.yStep) * 0.9; pstate.yDuty = (1 - 2 * globalStyle.sideBarItemMargin / this.pstate.yStep) * 0.95;
document.body.style.setProperty('--vcd-render-padding', pstate.topBarHeight + 'px'); document.body.style.setProperty('--vcd-render-padding', pstate.topBarHeight + 'px');
const canvasHeight = pstate.height - pstate.topBarHeight - pstate.botBarHeight; const canvasHeight = pstate.height - pstate.topBarHeight - pstate.botBarHeight;
@ -595,15 +641,35 @@ class WebGL2WaveRender {
// 清楚颜色缓冲区,也就是删除上一次的渲染结果 // 清楚颜色缓冲区,也就是删除上一次的渲染结果
gl.clear(gl.COLOR_BUFFER_BIT); gl.clear(gl.COLOR_BUFFER_BIT);
// 根据 globalLookup 当前激活的需要渲染的信号进行渲染 // 根据 currentWiresRenderView 视图渲染
let index = 0; const renderSignals = [];
for (const signal of globalLookup.currentWires) { const link2CurrentWires = globalLookup.link2CurrentWires;
for (const view of globalLookup.currentWiresRenderView) {
if (view.renderType === 0) {
const realSignal = link2CurrentWires.get(view.signalInfo.link);
renderSignals.push(realSignal);
} else {
// 如果是组,需要渲染空白的一行
renderSignals.push(0);
// 如果没有关闭,把所有子节点加入其中
if (view.groupInfo.collapse === false) {
for (const child of view.children) {
const realSignal = link2CurrentWires.get(child.signalInfo.link);
renderSignals.push(realSignal);
}
}
}
}
for (let index = 0; index < renderSignals.length; ++ index) {
const signal = renderSignals[index];
if (signal === 0) {
continue;
}
const id = signal.link; const id = signal.link;
const wave = globalLookup.chango[id].wave; const wave = globalLookup.chango[id].wave;
// _this.makeBitVertex(wave, globalLookup.time, true);
// _this.makeVecVertex(wave, globalLookup.time, true);
const signalItem = globalLookup.chango[id]; const signalItem = globalLookup.chango[id];
if (!signalItem) { if (!signalItem) {
return; return;
@ -638,7 +704,6 @@ class WebGL2WaveRender {
gl.drawArrays(gl.TRIANGLES, 0, maskVertices.length / 4); gl.drawArrays(gl.TRIANGLES, 0, maskVertices.length / 4);
} }
index ++;
} }
plugins.map(fn => fn(globalLookup, pstate, elements)); plugins.map(fn => fn(globalLookup, pstate, elements));

View File

@ -7,23 +7,21 @@ const yOffsetUpdate = (pstate, nextOffsetYFn) => {
const currentRenderHeight = globalLookup.currentWires.size * pstate.yStep; const currentRenderHeight = globalLookup.currentWires.size * pstate.yStep;
const canvasHeight = pstate.height - pstate.topBarHeight - pstate.botBarHeight; const canvasHeight = pstate.height - pstate.topBarHeight - pstate.botBarHeight;
// console.log(currentRenderHeight, canvasHeight); // console.log(currentRenderHeight, canvasHeight);
const maxOffsetX = Math.max(-40, currentRenderHeight - canvasHeight); // maximum offset const maxOffsetX = Math.max(-20, currentRenderHeight - canvasHeight); // maximum offset
nextOffsetY = Math.min(nextOffsetY, maxOffsetX); nextOffsetY = Math.min(nextOffsetY, maxOffsetX);
const minOffsetX = -40; // minimum offset const minOffsetX = -20; // minimum offset
nextOffsetY = Math.max(nextOffsetY, minOffsetX); nextOffsetY = Math.max(nextOffsetY, minOffsetX);
if (nextOffsetY === xOffset) { if (nextOffsetY === xOffset) {
return false; // exit without scroll return false; // exit without scroll
} }
console.log('next offset y', nextOffsetY);
pstate.oldYOffset = pstate.yOffset; pstate.oldYOffset = pstate.yOffset;
pstate.yOffset = nextOffsetY; pstate.yOffset = nextOffsetY;
// 这玩意儿初始设置 -40 的偏移,当作 padding-top // 这玩意儿初始设置 -20 的偏移,当作 padding-top
globalStyle.yOffset = nextOffsetY + 40; globalStyle.yOffset = nextOffsetY + 20;
return true; return true;
}; };

View File

@ -30,7 +30,7 @@
"display-signal-info-scope.width": "width", "display-signal-info-scope.width": "width",
"display-signal-info-scope.parent": "parent", "display-signal-info-scope.parent": "parent",
"wavecolor": "color of wave", "wavecolor": "default color of wave",
"wavecolor.normal-bit": "wave of one width", "wavecolor.normal-bit": "wave of one width",
"wavecolor.normal-vec": "wave of more than one width", "wavecolor.normal-vec": "wave of more than one width",
"wavecolor.high-impedance": "wave of high impedance", "wavecolor.high-impedance": "wave of high impedance",
@ -54,6 +54,39 @@
"loading": "loading", "loading": "loading",
"context-menu.create-group": "create group",
"context-menu.join-group": "join created group",
"context-menu.change-color": "change color",
"context-menu.delete": "delete signal",
"context-menu.signal.name": "signal name",
"context-menu.signal.type": "signal type",
"context-menu.signal.width": "signal width",
"context-menu.signal.dep": "signal dependency",
"context-menu.group.cancel": "cancel group",
"context-menu.group.delete": "delete group",
"context-menu.group.empty": "No groups are currently available",
"context-menu.group.uname-group": "unamed group",
"toolbar.modal.common-digital": "Digital",
"toolbar.modal.ladder-analog": "Analog (Ladder)",
"toolbar.modal.line-analog": "Analog (Line)",
"toolbar.search.name": "Name",
"toolbar.search.value": "Value",
"toolbar.format.category.base": "Base",
"toolbar.format.category.dec": "Decimal",
"toolbar.format.category.float": "Float",
"toolbar.format.signed": "Signed",
"toolbar.format.unsigned": "Unsigned",
"toolbar.format.half": "Half (16bit)",
"toolbar.format.float": "Float (32bit)",
"toolbar.format.double": "Double (64bit)",
"current-version": "current version", "current-version": "current version",
"copyright": "The copyright of this software belongs to <a href=\"https://github.com/Digital-EDA\" target=\"_blank\">Digital-IDE</a> project team. Welcome to <a href=\"https://github.com/Digital-EDA/Digital-IDE\">Star</a>." "copyright": "The copyright of this software belongs to <a href=\"https://github.com/Digital-EDA\" target=\"_blank\">Digital-IDE</a> project team. Welcome to <a href=\"https://github.com/Digital-EDA/Digital-IDE\">Star</a>."
} }

View File

@ -16,7 +16,7 @@
"search-mode": "搜索模式", "search-mode": "搜索模式",
"search-scope": "搜索范围", "search-scope": "搜索范围",
"search-display-parent-only": "只展示父模块", "search-display-parent-only": "只展示父模块",
"search-nothing": "没有找到任何号", "search-nothing": "没有找到任何号",
"signal-only": "信号", "signal-only": "信号",
"module-only": "模块", "module-only": "模块",
@ -29,7 +29,7 @@
"display-signal-info-scope.width": "位宽", "display-signal-info-scope.width": "位宽",
"display-signal-info-scope.parent": "所属模块名", "display-signal-info-scope.parent": "所属模块名",
"wavecolor": "波形颜色", "wavecolor": "默认波形颜色",
"wavecolor.normal-bit": "单位宽波形", "wavecolor.normal-bit": "单位宽波形",
"wavecolor.normal-vec": "多位宽波形", "wavecolor.normal-vec": "多位宽波形",
"wavecolor.high-impedance": "高阻态波形", "wavecolor.high-impedance": "高阻态波形",
@ -52,6 +52,41 @@
"loading": "加载中", "loading": "加载中",
"context-menu.create-group": "新建组",
"context-menu.join-group": "加入已有分组",
"context-menu.change-color": "修改颜色",
"context-menu.delete": "删除信号",
"context-menu.signal.name": "信号名称",
"context-menu.signal.type": "信号类型",
"context-menu.signal.width": "信号宽度",
"context-menu.signal.dep": "依赖关系",
"context-menu.group.cancel": "取消分组",
"context-menu.group.delete": "删除分组",
"context-menu.group.empty": "当前没有可用的分组",
"context-menu.group.uname-group": "未命名分组",
"toolbar.modal.common-digital": "数字",
"toolbar.modal.ladder-analog": "模拟(阶梯)",
"toolbar.modal.line-analog": "模拟(折线)",
"toolbar.search.name": "名称",
"toolbar.search.value": "值",
"toolbar.format.category.base": "基础",
"toolbar.format.category.dec": "十进制",
"toolbar.format.category.float": "浮点数",
"toolbar.format.signed": "有符号",
"toolbar.format.unsigned": "无符号",
"toolbar.format.half": "半精度16bit",
"toolbar.format.float": "单精度32bit",
"toolbar.format.double": "双精度64bit",
"current-version": "当前版本", "current-version": "当前版本",
"copyright": "本软件版权归 <a href=\"https://github.com/Digital-EDA\" target=\"_blank\">Digital-IDE</a> 项目组所有,欢迎 <a href=\"https://github.com/Digital-EDA/Digital-IDE\">Star</a>。" "copyright": "本软件版权归 <a href=\"https://github.com/Digital-EDA\" target=\"_blank\">Digital-IDE</a> 项目组所有,欢迎 <a href=\"https://github.com/Digital-EDA/Digital-IDE\">Star</a>。"
} }

View File

@ -20,13 +20,23 @@
* @typedef {Record<string, ChangoItem>} Chango * @typedef {Record<string, ChangoItem>} Chango
*/ */
/**
* @description
* @typedef {Object} WireItemBaseInfo
* @property {string} link
* @property {string} name
* @property {number} size
* @property {string | undefined} parentLink 如果当前的波形被加入了一个 group bus 该值则不会为 undefined
*/
/** /**
* @description * @description
* @typedef {object} WaveRenderSidebarItem * @typedef {Object} WaveRenderSidebarItem
* @property {WireItem} signal * @property {WireItemBaseInfo} signalInfo
* @property {GroupBaseInfo} groupInfo
* @property {0 | 1} renderType 0 代表单条波形1 代表 分组 * @property {0 | 1} renderType 0 代表单条波形1 代表 分组
* @property {WireItem[]} children 分组内的波形只有当 renderType 1 时才不为空数组 * @property {WaveRenderSidebarItem[]} children 分组内的波形只有当 renderType 1 时才不为空数组
*/ */
/** /**
@ -40,6 +50,25 @@
* @property {string} type * @property {string} type
*/ */
/**
* @description
* @typedef {Object} GroupBaseInfo
* @property {string} name
* @property {string} color
* @property {boolean} collapse
*/
/**
* @description
* @typedef {Object} TopModWireItem
* @property {string} kind
* @property {string} link
* @property {string} name
* @property {number} size
* @property {string} type
*/
/** /**
* @description * @description
* @typedef {Array<{ ref: string }>} View * @typedef {Array<{ ref: string }>} View
@ -56,6 +85,10 @@
* @property {Chango} chango * @property {Chango} chango
* @property {View} view * @property {View} view
* @property {CurrentWires} currentWires * @property {CurrentWires} currentWires
* @property {Map<string, WireItem>} link2CurrentWires
* @property {WaveRenderSidebarItem[]} currentWiresRenderView
* @property {Record<string, BigInt | string>} currentSignalValues
* @property {Set<string>} sidebarSelectedWireLinks
* @property {number} time * @property {number} time
*/ */
@ -108,3 +141,13 @@
/** /**
* @typedef {'scrollLeft' | 'scrollRight' | 'scrollUp' | 'scrollDown' | 'scaleUp' | 'scaleDown'} EventHandlerKind * @typedef {'scrollLeft' | 'scrollRight' | 'scrollUp' | 'scrollDown' | 'scaleUp' | 'scaleDown'} EventHandlerKind
*/ */
/**
* @typedef {Object} IRenderOption
* @property {number | undefined} height 波形的高度默认为 30可以在右侧的栏目中设置
* @property {string | undefined} color 波形的颜色可以在右击菜单栏中设置
* @property {number | undefined} valueFormat 波形数字的展示形式可以在上侧的下拉菜单中设置vec 类型的波形可以设置为0二进制1八进制2十六进制3有符号整型4无符号整型5浮点数半精度6单精度7双精度
* @property {number | undefined} renderModal 波形的渲染模式默认为 bit vecvec 类型的波形可以设置为0数字形式1折线模拟形式2阶梯模拟形式
*/