diff --git a/public/vcd.css b/public/vcd.css index 42e4510..ada7fd3 100644 --- a/public/vcd.css +++ b/public/vcd.css @@ -6,7 +6,7 @@ --main-light-color: var(--main-color); --sidebar-width: 330px; --right-nav-width: 60px; - --time-scale-height: 50px; + --time-scale-height: 30px; --sidebar-padding: 10px; --sidebar-item-margin: 5px; --toolbar-height: 50px; diff --git a/src/App.vue b/src/App.vue index fb4b45e..9c40918 100644 --- a/src/App.vue +++ b/src/App.vue @@ -71,7 +71,7 @@ onMounted(async () => { // document.body.style.setProperty('--el-color-white', 'var(--background)'); // 设置全局宏 - document.body.style.setProperty('--time-scale-height', '50px'); + document.body.style.setProperty('--time-scale-height', '30px'); document.body.style.setProperty('--vcd-render-padding', '30px'); document.body.style.setProperty('--sidebar-width', '330px'); document.body.style.setProperty('--toolbar-height', '60px'); diff --git a/src/components/render/cursor.js b/src/components/render/cursor.js new file mode 100644 index 0000000..794c79f --- /dev/null +++ b/src/components/render/cursor.js @@ -0,0 +1,47 @@ +import { globalLookup } from '@/hook/global'; +import formatTime from '@/hook/wave-view/format-time'; +import { reactive } from 'vue'; + +export const StaticCursor = reactive({ + label: '', + show: false, + currentTime: 0, + left: 0, + updateLabel(timescale) { + this.label = formatTime(this.currentTime, timescale); + }, + updateLeft() { + this.left = calcCursorLeft(this.currentTime); + } +}); + +export function changeCursorLocation() { + StaticCursor.show = true; + const pstate = globalLookup.pstate; + if (pstate) { + const { xCursor, xOffset, xScale, tgcd, timescale } = pstate; + const currentTime = Math.round((xCursor - xOffset) / xScale) * tgcd; + StaticCursor.currentTime = currentTime; + StaticCursor.updateLabel(timescale); + StaticCursor.updateLeft(); + } +} + +/** + * @description 给出当前的时间,比如 23ns,计算当前这个点应该相对于左侧偏移多少 + * @param {number} currentTime + * @returns {number} + */ +export function calcCursorLeft(currentTime) { + const pstate = globalLookup.pstate; + if (pstate) { + const { xOffset, xScale, tgcd, timescale } = pstate; + const xCursor = (currentTime / tgcd) * xScale + xOffset; + return xCursor; + } + return 0; +} + +export const MovingCursor = reactive({ + +}); \ No newline at end of file diff --git a/src/components/render/cursor.vue b/src/components/render/cursor.vue index a00050f..9089329 100644 --- a/src/components/render/cursor.vue +++ b/src/components/render/cursor.vue @@ -1,94 +1,62 @@ - - \ No newline at end of file diff --git a/src/components/render/index.vue b/src/components/render/index.vue index 5aab3a9..b3c54c5 100644 --- a/src/components/render/index.vue +++ b/src/components/render/index.vue @@ -1,35 +1,27 @@ - @@ -89,6 +81,7 @@ export default { .vcd-cursor { position: absolute; pointer-events: none; + z-index: 60; } .vcd-values text { diff --git a/src/components/sidebar/index.vue b/src/components/sidebar/index.vue index 9826358..6dee06c 100644 --- a/src/components/sidebar/index.vue +++ b/src/components/sidebar/index.vue @@ -150,7 +150,7 @@ function handleSidebarGlobalClick() { border-radius: 0 .8em .8em 0; border: solid 1px var(--sidebar-border); width: var(--sidebar-width); - height: calc(100vh - var(--toolbar-height)); + height: calc(100vh - var(--toolbar-height) - 50px); box-shadow: 0 0 15px 1px rgb(16, 16, 16); overflow: hidden; } @@ -165,6 +165,14 @@ function handleSidebarGlobalClick() { transition: var(--animation-5s); } +.group-children .display-signal-item { + width: calc(200px - 8px - 10px) !important; +} + +.group-children .signal-color-vendor > span { + font-size: 12px; +} + .display-signal-item-container { display: flex; justify-content: space-between; diff --git a/src/components/sidebar/signal-item.vue b/src/components/sidebar/signal-item.vue index 0cefccc..60be409 100644 --- a/src/components/sidebar/signal-item.vue +++ b/src/components/sidebar/signal-item.vue @@ -17,11 +17,13 @@ {{ signalInfo.name }}
-
+
{{ makeSignalCaption(signalInfo) }} -
+
- +
@@ -70,7 +72,7 @@ const signalInfo = computed(() => { /** * @param {WireItemBaseInfo} signal */ - function makeSignalIconClass(signal) { +function makeSignalIconClass(signal) { const realSignal = globalLookup.link2CurrentWires.get(signal.link); return 'iconfont ' + makeIconClass(realSignal); } diff --git a/src/components/toolbar/cursor-location.js b/src/components/toolbar/cursor-location.js new file mode 100644 index 0000000..4da14cd --- /dev/null +++ b/src/components/toolbar/cursor-location.js @@ -0,0 +1,160 @@ +import { globalLookup } from "@/hook/global"; +import { calcCursorLeft, StaticCursor } from "../render/cursor"; +import { sidebarSelectedWires } from "@/hook/sidebar-select-wire"; + + +/** + * @description 将 某时间点 移动到指定位置 + * @param {number} time 需要移动到的时间点 + * @param {number} leftMargin 移动后的该时间点距离左侧多少px + * @param {Pstate} pstate + */ +function moveto(time, leftMargin, pstate) { + StaticCursor.show = true; + StaticCursor.currentTime = time; + pstate.oldXOffset = pstate.xOffset; + const { width, xOffset, xScale, tgcd, timescale } = pstate; + pstate.xOffset = leftMargin - (time / tgcd) * xScale; + + StaticCursor.updateLeft(); + StaticCursor.updateLabel(pstate.timescale); + globalLookup.render(); +} + +export function toBegin() { + const pstate = globalLookup.pstate; + if (pstate) { + moveto(0, pstate.sidebarWidth - 20, pstate); + } +} + +export function toEnd() { + const pstate = globalLookup.pstate; + if (pstate) { + moveto(globalLookup.time, pstate.width - pstate.sidebarWidth, pstate); + } +} + +function clip(v, min, max) { + if (v < min) { + v = min; + } + if (v > max) { + v = max; + } + return v; +} + +export function toPrevChange() { + const link = sidebarSelectedWires.lastLink; + const pstate = globalLookup.pstate; + + if (link === undefined || pstate === undefined) { + return; + } + + const chango = globalLookup.chango[link]; + if (chango && chango.wave && chango.wave.length > 0) { + const wave = chango.wave; + + // 如果长度为 1,直接到前面或者后面即可 + if (wave.length === 1) { + toBegin(); + } else { + const currentT = StaticCursor.currentTime; + let pivot = bisearch(wave, currentT); + + const pwave = wave[pivot]; + if (pwave[0] === currentT) { + pivot = clip(pivot - 1, 0, wave.length - 1); + } else { + pivot = clip(pivot, 0, wave.length - 1); + } + + const targetT = wave[pivot][0]; + moveto(targetT, (pstate.width + pstate.sidebarWidth) / 2, pstate); + } + } +} + + +export function toNextChange() { + const link = sidebarSelectedWires.lastLink; + const pstate = globalLookup.pstate; + + if (link === undefined || pstate === undefined) { + return; + } + + const chango = globalLookup.chango[link]; + if (chango && chango.wave && chango.wave.length > 0) { + const wave = chango.wave; + + // 如果长度为 1,直接到前面或者后面即可 + if (wave.length === 1) { + toEnd(); + } else { + const currentT = StaticCursor.currentTime; + let pivot = bisearch(wave, currentT); + let targetT; + if (pivot === wave.length - 1) { + pivot = clip(pivot + 1, 0, wave.length - 1); + targetT = globalLookup.time; + } else { + pivot = clip(pivot + 1, 0, wave.length - 1); + targetT = wave[pivot][0]; + } + + moveto(targetT, (pstate.width + pstate.sidebarWidth) / 2, pstate); + } + } +} + +const locationManager = { + /** + * @description 记录所有的信标 + * @type {Map} 时间点到元素的映射 + */ + pivots: new Map(), + +}; + + +export function makeLocation() { + const currentT = StaticCursor.currentTime; + // 在 currentT 创建新的信标 + +} + +/** + * @description 二分查找找到最近的索引下标 + * @param {number[][]} wave + * @param {number} time + * @return {number} + */ +function bisearch(wave, time) { + const times = wave.map(p => p[0]); + + // 二分查找,并将结果存入 i + let i = 0, j = wave.length - 1; + while (i < j) { + if (times[i] === time) { + break; + } + if (times[j] <= time) { + i = j; + break; + } + if (j - i === 1) { + break; + } + const mid = (i + j) >> 1; + if (times[mid] > time) { + j = mid; + } else { + i = mid; + } + } + + return i; +} \ No newline at end of file diff --git a/src/components/toolbar/cursor-location.vue b/src/components/toolbar/cursor-location.vue index 72e423c..de06212 100644 --- a/src/components/toolbar/cursor-location.vue +++ b/src/components/toolbar/cursor-location.vue @@ -1,35 +1,86 @@ @@ -62,4 +113,15 @@ defineComponent({ name: 'cursor-location' }); color: var(--sidebar); transition: var(--animation-3s); } + +.disable { + opacity: 0.5; + cursor: not-allowed !important; +} + +.location .item.disable:hover { + background-color: unset !important; + color: unset !important; +} + \ No newline at end of file diff --git a/src/components/toolbar/index.vue b/src/components/toolbar/index.vue index fd170e4..847fa5e 100644 --- a/src/components/toolbar/index.vue +++ b/src/components/toolbar/index.vue @@ -37,6 +37,7 @@ defineComponent({ name: 'toolbar' }); width: 100%; height: var(--toolbar-height); background-color: var(--background); + z-index: 60; } .toolbar-body { diff --git a/src/components/toolbar/pivot-view.js b/src/components/toolbar/pivot-view.js new file mode 100644 index 0000000..991aa1a --- /dev/null +++ b/src/components/toolbar/pivot-view.js @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/toolbar/signal-modal.vue b/src/components/toolbar/signal-modal.vue index 2a90ac6..708403e 100644 --- a/src/components/toolbar/signal-modal.vue +++ b/src/components/toolbar/signal-modal.vue @@ -54,14 +54,15 @@ const signalModal = ref(0); const disabled = ref(true); function onSignalModalUpdate() { - console.log('enter change'); const links = globalLookup.sidebarSelectedWireLinks; if (links.size > 0) { const modal = signalModal.value; // 更新渲染选项 for (const link of links) { - globalLookup.setRenderOption(link, 'renderModal', modal); - globalLookup.waveRender.resetVertice(link); + const realChange = globalLookup.setRenderOption(link, 'renderModal', modal); + if (realChange) { + globalLookup.waveRender.resetVertice(link); + } } // 重新渲染 diff --git a/src/components/toolbar/signal-value-format.vue b/src/components/toolbar/signal-value-format.vue index 1342d14..861fa24 100644 --- a/src/components/toolbar/signal-value-format.vue +++ b/src/components/toolbar/signal-value-format.vue @@ -37,9 +37,6 @@ const { t } = useI18n(); const currentOption = ref(2); const disabled = ref(true); -function onChange() { - console.log(currentOption.value); -} onMounted(() => { formatOptions.push({ @@ -95,6 +92,23 @@ onMounted(() => { const formatOptions = reactive([]); +function onChange() { + const links = globalLookup.sidebarSelectedWireLinks; + if (links.size > 0) { + const valueFormat = currentOption.value; + // 更新渲染选项 + for (const link of links) { + const realChange = globalLookup.setRenderOption(link, 'valueFormat', valueFormat); + if (realChange) { + globalLookup.waveRender.resetVertice(link); + } + } + + // 重新渲染,不过只需要渲染数值即可 + globalLookup.render({ type: 'value' }); + } +} + sidebarSelectedWires.addToPipe(lastLink => { if (lastLink) { const option = globalLookup.currentSignalRenderOptions.get(lastLink); diff --git a/src/components/toolbar/value-search.vue b/src/components/toolbar/value-search.vue index 5a61622..6ea2aa9 100644 --- a/src/components/toolbar/value-search.vue +++ b/src/components/toolbar/value-search.vue @@ -14,20 +14,132 @@
- + + + +
+ +
+ +
+
+ + {{ t('search-nothing') }} +
+
+
+
+ @@ -41,8 +153,106 @@ const searchMode = ref(0); } .value-input-wrapper { + position: relative; margin-left: 5px; width: 120px; } +.toolbar-search-container { + position: absolute; + top: 50px; + left: -10px; + transition: var(--animation-3s); + padding: 10px; + margin: 10px; + background-color: var(--sidebar); + border: 1.5px solid var(--main-color); + color: var(--sidebar-item-text); + border-radius: .5em; + width: 420px; +} + +.toolbar-search-nothing { + height: 260px; + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: 1.1rem; +} + +.toolbar-search-nothing .iconfont { + font-size: 80px; +} + +.toolbar-search .signal-name { + width: 100px; + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + height: 100%; + align-items: center; + padding: 5px; +} + +.toolbar-search .iconfont { + font-size: 13px; + color: var(--sidebar); + background-color: #7ca532; + border-radius: 99em; + height: 20px; + width: 20px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 5px; +} + +.toolbar-search { + display: flex; + margin: 3px; + align-items: center; + cursor: pointer; + transition: var(--animation-3s); + border-radius: .5em; + padding-left: 5px; +} + +.toolbar-search:hover { + transition: var(--animation-3s); + background-color: var(--sidebar-item-selected); +} + +.toolbar-search .signal-width { + width: 80px; + height: 100%; + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + align-items: center; + padding: 5px; +} + +.toolbar-search .vec { + color: var(--sidebar); + background-color: #7ca532; + padding: 2px 3px; + border-radius: .5em; + font-size: 0.9rem; +} + +.toolbar-search .signal-value { + width: 150px; + height: 100%; + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + align-items: center; + padding: 5px; +} + \ No newline at end of file diff --git a/src/components/treeview/search.vue b/src/components/treeview/search.vue index bc3fa82..a8a98b4 100644 --- a/src/components/treeview/search.vue +++ b/src/components/treeview/search.vue @@ -13,7 +13,7 @@
{ searchManage.searchResult.push(searchResultItem); }); -const safeSearch = debounceWrapper(search, 500); +const safeSearch = debounceWrapper(search, 200); diff --git a/src/hook/global.js b/src/hook/global.js index 9d6addd..846652e 100644 --- a/src/hook/global.js +++ b/src/hook/global.js @@ -86,10 +86,11 @@ export const globalLookup = reactive({ */ chango: {}, - // 初始化时会被定义 - render: () => {}, - - + /** + * @description 【核心】接入渲染管线,重新渲染,该函数必须在 domContainer 初始化完成后再去调用 + * @param {RenderConfig} renderConfig 渲染的类型,默认为重新渲染所有可视项 + */ + render: (renderConfig) => {}, /** * @description 非常核心的用于操控 webgl 进行渲染和动画的核心类 @@ -98,6 +99,10 @@ export const globalLookup = reactive({ waveRender: undefined, + /** + * @description 描述描述当前渲染区的一些基本几何状态 + * @type {Pstate | undefined} + */ pstate: undefined, xScale: 1, @@ -126,13 +131,18 @@ export const globalLookup = reactive({ * @param {string} link * @param {'height' | 'color' | 'valueFormat' | 'renderModal'} option * @param {any} value + * @returns {boolean} true 代表改变了值,否则则没有,可以用来判断是否需要重复塑造 Vertices */ setRenderOption(link, option, value) { if (!this.currentSignalRenderOptions.has(link)) { this.currentSignalRenderOptions.set(link, {}); } const renderOption = this.currentSignalRenderOptions.get(link); + if (renderOption[option] === value) { + return false; + } renderOption[option] = value; + return true; } }); diff --git a/src/hook/render.js b/src/hook/render.js index e315a5c..b35d389 100644 --- a/src/hook/render.js +++ b/src/hook/render.js @@ -65,8 +65,6 @@ function makeWaveView(parentElement) { console.log('updater'); }; - globalLookup - // 注册基本的响应事件 container.elo.container.addEventListener('wheel', registerWheelEvent(parentElement, container.pstate, globalLookup, eventHandler) diff --git a/src/hook/wave-view/dom-container.js b/src/hook/wave-view/dom-container.js index 33e516b..67df750 100644 --- a/src/hook/wave-view/dom-container.js +++ b/src/hook/wave-view/dom-container.js @@ -21,14 +21,20 @@ const mouseMoveHandler = (cursor, content, pstate /* , render */) => { // 这是上面展示当前 time 的圆角矩形内的字体大小 const fontHeight = 20; const fontWidth = fontHeight / 2; + const sidebarWidth = pstate.sidebarWidth; + const handler = event => { const x = pstate.xCursor = event.clientX; - cursor.style.left = (x - xmargin) + 'px'; - cursor.innerHTML = renderCursor({ xmargin, fontWidth, fontHeight }, pstate); + const left = x - xmargin; + // 不能进入左侧,也不能进入右侧 + if (left > sidebarWidth - 195) { + cursor.style.left = (x - xmargin) + 'px'; + cursor.innerHTML = renderCursor({ xmargin, fontWidth, fontHeight }, pstate); + } }; // 添加初始鼠标位置 handler({ clientX: pstate.width / 2 }); - content.addEventListener('mousemove', handler); + document.addEventListener('mousemove', handler); }; @@ -171,7 +177,6 @@ const domContainer = (obj) => { setTime(pstate, deso.timeOpt.value); } - const waveRender = new WebGL2WaveRender(elo, deso, pstate, obj.renderPlugins); deso.render = waveRender.render.bind(waveRender); deso.waveRender = waveRender; diff --git a/src/hook/wave-view/render-values.js b/src/hook/wave-view/render-values.js index 12ff07a..2614ba9 100644 --- a/src/hook/wave-view/render-values.js +++ b/src/hook/wave-view/render-values.js @@ -81,7 +81,7 @@ function addVecRenderItem(link) { /** * * @param {GlobalLookup} desc - * @param {*} pstate + * @param {Pstate} pstate * @returns */ function* renderValues(desc, pstate) { @@ -89,9 +89,9 @@ function* renderValues(desc, pstate) { // 根据 currentWiresRenderView 视图渲染 // 此处应该和 render-wave 的相同注释的地方保持逻辑上的一致(render-wave.js 约 646 行) - const currentWires = desc.currentWires; const renderSignals = []; + for (const view of globalLookup.currentWiresRenderView) { if (view.renderType === 0) { const realSignal = addVecRenderItem(view.signalInfo.link); @@ -115,48 +115,57 @@ function* renderValues(desc, pstate) { const lineHeightExtra = (globalSetting.displaySignalHeight - 30) / 2; - const ilen = height / yStep; - const iskip = (yOffset - lineHeightExtra) / yStep; + const canvaseHeight = height - topBarHeight - botBarHeight; - const ml = genSVG(width, height - topBarHeight - botBarHeight); + // 当前一页最多渲染这么多项目 + const validRenderNum = Math.ceil(canvaseHeight / yStep); + const ml = genSVG(width, canvaseHeight); + + // 需要跳过前面几项 + const renderStartNo = (yOffset - lineHeightExtra) / yStep; let ifirst = 0; - for (let i = 0; i < ilen; ++ i) { + for (let i = 0; i < validRenderNum; ++ i) { const lane = renderSignals[i]; if (lane && (lane.name || lane.kind)) { - if (i > iskip) { + if (i > renderStartNo) { break; } ifirst = i; } } + ml.push(defs); yield; const markers = ['g']; ml.push(markers); - for (let i = 0; i < (iskip + ilen); i++) { + for (let i = 0; i < (renderStartNo + validRenderNum); i++) { const lane = renderSignals[i + (ifirst | 0)]; const link = (lane || {}).ref; + + if (link === '') { + continue; + } + 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 - (renderStartNo - ifirst) + 1.18) * yStep))].concat(water(lane, desc, pstate))); } else if (lane && lane.kind === 'brace') { - markers.push(['g', tt(0, Math.round((i - (iskip - ifirst) + 1.18) * yStep))].concat(bracer(lane, desc, pstate))); + markers.push(['g', tt(0, Math.round((i - (renderStartNo - ifirst) + 1.18) * yStep))].concat(bracer(lane, desc, pstate))); } else if (lane && link) { const chango = desc.chango[link]; if (chango && chango.kind === 'vec') { // tt: 计算出当前这一行的所有 值 svg 在 Y 方向的偏移 - const mLane = ['g', tt(0, Math.round((i - (iskip - ifirst) + 0.15) * yStep))]; + const mLane = ['g', tt(0, Math.round((i - (renderStartNo - ifirst) + 0.15) * yStep))]; const { wave } = chango; - const jlen = wave.length; perLane: { let [tPre, vPre, mPre] = wave[0]; let xPre = getX(pstate, tPre); const labeler = getLabel(lane); - for (let j = 1; j <= jlen; j++) { + for (let j = 1; j <= wave.length; j++) { const mark = wave[j]; const [tCur, vCur, mCur] = (mark || [desc.time, 0, 0]); @@ -175,7 +184,7 @@ function* renderValues(desc, pstate) { if (w > 8) { const x = Math.round((xPreNorm + xCurNorm) / 2); // 计算 当前这一个 值 在 X 方向上的偏移 - const renderLabel = labeler(vPre, mPre, x, w, link); + const renderLabel = labeler(vPre, mPre, x, w, link); mLane.push(renderLabel); } } @@ -191,7 +200,6 @@ function* renderValues(desc, pstate) { } } - // console.log(view); for (let i = 0; i < renderSignals.length; i++) { const lane = renderSignals[i]; if (lane && lane.vlines) { diff --git a/src/hook/wave-view/render-wave.js b/src/hook/wave-view/render-wave.js index 3bda206..e15e354 100644 --- a/src/hook/wave-view/render-wave.js +++ b/src/hook/wave-view/render-wave.js @@ -3,6 +3,7 @@ import { globalSetting, globalStyle } from '../global'; import { gl_Colors, gl_Shifts, gl_Shifts_for_bar, gl_WidthShifts, barShift, getRatio, screenHeightPixel, maskColorIndexOffset, posYFactor, glslInputLength, prettyPrint, ladderAnalog_GL_WidthShifts, lineAnlog_GL_WidthShifts } from './render-utils.js'; import { vertexShader, fragmentShader } from './render-shader.js'; import { renderAsBit, renderAsCommonDigital, renderAsLadderAnalog, renderAsLineAnalog } from './toolbar/renderModal'; +import { StaticCursor } from '@/components/render/cursor'; /** * @description @@ -392,6 +393,17 @@ class WebGL2WaveRender { const _this = this; + if (renderConfig.type === 'value') { + const renderValue = plugins.filter(plugin => plugin.name === 'pluginRenderValues')[0]; + if (renderValue) { + renderValue(globalLookup, pstate, elements); + } + return; + } + + // 更新静态 cursor + StaticCursor.updateLeft(); + function linearAnimation(delta, oldVal, newVal) { return (1 - delta) * oldVal + delta * newVal; } @@ -537,10 +549,8 @@ class WebGL2WaveRender { pstate.oldYStep = pstate.yStep; pstate.oldYDuty = pstate.yDuty; - if (needHiddenAnimation) { - _this.elements.grid.classList.remove('vcd-hidden'); - _this.elements.values.classList.remove('vcd-hidden'); - } + _this.elements.grid.classList.remove('vcd-hidden'); + _this.elements.values.classList.remove('vcd-hidden'); return; } } diff --git a/src/hook/wave-view/toolbar/renderFormat.js b/src/hook/wave-view/toolbar/renderFormat.js index a4073c5..a823eb8 100644 --- a/src/hook/wave-view/toolbar/renderFormat.js +++ b/src/hook/wave-view/toolbar/renderFormat.js @@ -124,7 +124,7 @@ export class FormatValueRender { if (valueString.length <= pos) { return valueString; } - const valSign = (value < 0) ? '-' : '+'; + const valSign = (value < 0) ? '-' : ''; if (pos === 1) { return valSign; } @@ -155,6 +155,8 @@ export class FormatValueRender { * @param {number} pos 位置 */ floatTransform(fWidth, value, pos) { + const replacer = this.replacer; + if (fWidth === 16) { value = this.calcIEEEFloat(value, 5, 10); } else if (fWidth === 32) { @@ -167,7 +169,7 @@ export class FormatValueRender { if (valueString.length <= pos) { return valueString; } - const valSign = (value < 0) ? '-' : '+'; + const valSign = (value < 0) ? '-' : ''; if (pos === 1) { return valSign; } @@ -177,6 +179,144 @@ export class FormatValueRender { return valSign + replacer + valueString.slice(2 - pos); } + /** + * @description 根据 e 和 m 得到计算 IEEE 浮点数的函数,默认第一个位置代表符号 + * @param {number} value 值 + * @param {number} eWidth 代表指数部分的位数 + * @param {number} mWidth 代表有效尾数的位数 + * @returns {(value: number) => number} + */ + calcIEEEFloat(value, eWidth, mWidth) { + const offset = (1 << (eWidth - 1)) - 1; + const mFrac = 1 << mWidth; + + // 计算参考博客:https://blog.csdn.net/leo0308/article/details/117398166 + let bits = value.toString(2); + const width = 1 + eWidth + mWidth; + // 应对 bits 长度和 预定长度不一致的情况 + // 不足的地方补上 0,否则进行截断 + if (bits.length < width) { + bits = '0'.repeat(width - bits.length) + bits; + } + if (bits.length > width) { + bits = bits.substring(bits.length - width); + } + + const s = parseInt(bits[0], 2); + const e = parseInt(bits.substring(1, 1 + eWidth), 2); + const m = parseInt(bits.substring(2 + eWidth), 2); + + const sign = (s === 1) ? -1 : 1; + // 全 0 + if (e === 0) { + return sign * efficentPow2(-offset + 1 - mWidth) * m; + } + + // 全 1 + if (e === ((1 << eWidth) - 1)) { + if (m === 0) { + return sign * Infinity; + } + return NaN; + } + + return sign * efficentPow2(e - offset) * (1 + m / mFrac); + } +} + + +export class JSValueRender { + /** + * + * @param {string} link 信号的ID + * @param {number} width 信号的位置宽度 + * @param {string} replacer 位置不足的地方会用 replacer 补足,比如 ... + */ + constructor(link, width) { + this.formatCode = getValueFormatCode(link); + this.width = BigInt(width); + } + + /** + * @description 根据当前的设置进行数值的渲染 + * @param {number} value 波形当前的数值 + * @returns {string} 转换后需要渲染的字符串 + */ + explainAsNumber(value) { + const formatCode = this.formatCode; + switch (formatCode) { + // 二进制 + case 0: return this.radixTransform(2, value); + // 八进制 + case 1: return this.radixTransform(8, value); + // 十六进制 + case 2: return this.radixTransform(16, value); + // 有符号整型 + case 3: return this.radixTransform(10, value, true); + // 无符号整型 + case 4: return this.radixTransform(10, value); + // 半精度浮点数 + case 5: return this.floatTransform(16, value); + // 单精度浮点数 + case 6: return this.floatTransform(32, value); + // 双精度浮点数 + case 7: return this.floatTransform(64, value); + // 未知 + default: return '?'; + } + + } + + /** + * @description 整数的进制转换 + * @param {number} radix 进制 + * @param {number} value 值 + * @param {number} pos 位置宽度 + * @param {boolean} sign 是否是有符号数,默认为 false + */ + radixTransform(radix, value, sign = false) { + const width = this.width; + const replacer = this.replacer; + + if (value === 'x') { + return -1; + } + value = BigInt(value); + + // 如果是有符号数 + if (sign) { + if ((value >> (width - 1n)) & 1n) { + value = value - (2n ** width); + } + + return parseInt(value); + } + + // 无符号的各类进制数字 + return parseInt(value); + } + + /** + * @description IEEE 浮点数的转换 + * 为什么没有 sign 参数?因为 IEEE 定义下的 float 第一个位置就是符号 + * @param {number} fWidth 16, 32 或者 64 + * @param {number} value 值 + * @param {number} pos 位置 + */ + floatTransform(fWidth, value) { + const replacer = this.replacer; + + if (fWidth === 16) { + value = this.calcIEEEFloat(value, 5, 10); + } else if (fWidth === 32) { + value = this.calcIEEEFloat(value, 8, 23); + } else if (fWidth === 64) { + value = this.calcIEEEFloat(value, 11, 52); + } + + return value; + } + /** * @description 根据 e 和 m 得到计算 IEEE 浮点数的函数,默认第一个位置代表符号 * @param {number} value 值 diff --git a/src/hook/wave-view/toolbar/renderModal.js b/src/hook/wave-view/toolbar/renderModal.js index 6e77059..d76e979 100644 --- a/src/hook/wave-view/toolbar/renderModal.js +++ b/src/hook/wave-view/toolbar/renderModal.js @@ -30,7 +30,7 @@ import { globalLookup } from "@/hook/global"; import { lineAnalog_WidthShift, lineAnlog_GL_WidthShifts, maskColorIndexOffset, posYFactor, prettyPrint } from "../render-utils"; -import { hexToSignedInt } from "./renderFormat"; +import { FormatValueRender, hexToSignedInt, JSValueRender } from "./renderFormat"; function makeQuadVertices(p1, p2, p3, p4) { return [ @@ -413,11 +413,13 @@ function getMaxMinByFormat(link, wave, time, formatCode, width) { let minVal = undefined; const length = wave.length; + const valueRender = new JSValueRender(link, width); + // TODO : 优化这段代码 for (let i = 0; i < length; ++ i) { const [t1, val, mask] = wave[i]; const t2 = (i === (length - 1)) ? time : wave[i + 1][0]; - const numVal = explainAsJSNumber(formatCode, val, width); + const numVal = valueRender.explainAsNumber(val); if (maxVal === undefined) { maxVal = numVal; @@ -479,6 +481,7 @@ export function renderAsLadderAnalog(lookup, link, wave, time) { const { maxVal, minVal } = getMaxMinByFormat(link, wave, time, formatCode, width); const coordinateTransform = getMappingFunc(formatCode, maxVal, minVal); + const valueRender = new JSValueRender(link, width); function makeLadderAnalogRenderParam(link, wave, time) { const [t1, val, mask] = wave; @@ -489,7 +492,7 @@ export function renderAsLadderAnalog(lookup, link, wave, time) { } // 根据当前格式进行转换 - const numVal = explainAsJSNumber(formatCode, val, width); + const numVal = valueRender.explainAsNumber(val); // 此时的 y 是一个 -1 到 1 的浮点数,因为 VAO 我设置了 Int const y = coordinateTransform(numVal); const colorParam = { y, color: 5 }; @@ -648,6 +651,7 @@ export function renderAsLineAnalog(lookup, link, wave, time) { const length = wave.length; const lineVertices = []; const maskVertices = []; + const valueRender = new JSValueRender(link, width); function makeLineAnalogRenderParam(link, wave, time) { const [t1, val, mask] = wave; @@ -658,7 +662,7 @@ export function renderAsLineAnalog(lookup, link, wave, time) { } // 根据当前格式进行转换 - const numVal = explainAsJSNumber(formatCode, val, width); + const numVal = valueRender.explainAsNumber(val); // 此时的 y 是一个 -1 到 1 的浮点数,因为 VAO 我设置了 Int const y = coordinateTransform(numVal); const colorParam = { y, color: 5 }; diff --git a/src/hook/wave-view/x-offset-update.js b/src/hook/wave-view/x-offset-update.js index ad7163e..db0f839 100644 --- a/src/hook/wave-view/x-offset-update.js +++ b/src/hook/wave-view/x-offset-update.js @@ -21,6 +21,7 @@ const xOffsetUpdate = (pstate, nextOffsetXFn) => { pstate.oldXOffset = pstate.xOffset; pstate.xOffset = nextOffsetX; + return true; }; diff --git a/src/hook/wave-view/y-offset-update.js b/src/hook/wave-view/y-offset-update.js index 841b25f..222fd14 100644 --- a/src/hook/wave-view/y-offset-update.js +++ b/src/hook/wave-view/y-offset-update.js @@ -6,19 +6,25 @@ const yOffsetUpdate = (pstate, nextOffsetYFn) => { nextOffsetY = nextOffsetY; + // 当前所有信号的高度之和 const currentRenderHeight = globalLookup.currentWires.size * pstate.yStep; + + // 当前有效的画布高度 const canvasHeight = pstate.height - pstate.topBarHeight - pstate.botBarHeight; - // console.log(currentRenderHeight, canvasHeight); - const maxOffsetY = Math.max(-20, - currentRenderHeight - canvasHeight); // maximum offset + + if (currentRenderHeight < canvasHeight) { + // 如果当前的信号高度小于画布的高度,则取消本次渲染 + return false; + } + + const maxOffsetY = Math.max(-20, currentRenderHeight - canvasHeight); nextOffsetY = Math.min(nextOffsetY, maxOffsetY); - const minOffsetY = -20; // minimum offset + const minOffsetY = -20; nextOffsetY = Math.max(nextOffsetY, minOffsetY); - // if (nextOffsetY === xOffset) { - // return false; // exit without scroll - // } + console.log(nextOffsetY); + pstate.oldYOffset = pstate.yOffset; pstate.yOffset = nextOffsetY; diff --git a/src/i18n/en.json b/src/i18n/en.json index 4f69aae..5ae1c77 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -87,6 +87,12 @@ "toolbar.format.float": "Float (32bit)", "toolbar.format.double": "Double (64bit)", + "toolbar.location.to-begin": "Move to Beginning", + "toolbar.location.to-end": "Move to End", + "toolbar.location.to-next-change": "Go to Next Change Edge", + "toolbar.location.to-prev-change": "Go to Previous Change Edge", + "toolbar.location.make-location": "Create New Pivot", + "current-version": "current version", "copyright": "The copyright of this software belongs to Digital-IDE project team. Welcome to Star." } \ No newline at end of file diff --git a/src/i18n/zh.json b/src/i18n/zh.json index 688d7ed..68d4975 100644 --- a/src/i18n/zh.json +++ b/src/i18n/zh.json @@ -85,7 +85,11 @@ "toolbar.format.float": "单精度(32bit)", "toolbar.format.double": "双精度(64bit)", - + "toolbar.location.to-begin": "移动至开头", + "toolbar.location.to-end": "移动至结尾", + "toolbar.location.to-next-change": "前往下一个变化的边沿", + "toolbar.location.to-prev-change": "前往上一个变化的边沿", + "toolbar.location.make-location": "创建新的信标", "current-version": "当前版本", "copyright": "本软件版权归 Digital-IDE 项目组所有,欢迎 Star。" diff --git a/src/types/jsdoc.js b/src/types/jsdoc.js index e85fb15..67bf8a4 100644 --- a/src/types/jsdoc.js +++ b/src/types/jsdoc.js @@ -96,13 +96,18 @@ /** * @description * @typedef {Object} Pstate + * @property {number} tgcd * @property {number} width * @property {number} height * @property {number} xScale + * @property {number} xCursor * @property {number} xOffset * @property {number} yOffset * @property {number} yStep * @property {number} yDuty + * @property {number} sidebarWidth + * @property {number} topBarHeight + * @property {number} botBarHeight */ /** @@ -124,7 +129,7 @@ /** * @description * @typedef {Object} RenderConfig - * @property {'common' | 'action' | undefined} type + * @property {'common' | 'action' | 'value' | undefined} type */ /**