From 45619460cf4a70c243327cb1d072b855b47b7bd9 Mon Sep 17 00:00:00 2001 From: Kirigaya <1193466151@qq.com> Date: Tue, 24 Dec 2024 21:40:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E7=BC=A9=E6=94=BE=E5=92=8C?= =?UTF-8?q?=E5=B9=B3=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/netlist.css | 2 +- src/components/render/index.vue | 17 ++- src/components/setting/index.vue | 14 +++ src/hook/css.js | 1 + src/hook/global.js | 20 +++- src/hook/render/drag.js | 14 +++ src/hook/render/index.js | 186 +++++++++++++++++++++++-------- src/hook/render/layout.js | 22 +--- src/hook/wheel-event.js | 104 +++++++++++++++++ src/i18n/ar.json | 3 +- src/i18n/de.json | 3 +- src/i18n/en.json | 5 +- src/i18n/fr.json | 3 +- src/i18n/ja.json | 3 +- src/i18n/ko.json | 3 +- src/i18n/ru.json | 3 +- src/i18n/zh-cn.json | 3 +- src/i18n/zh-tw.json | 3 +- 18 files changed, 328 insertions(+), 81 deletions(-) create mode 100644 src/hook/render/drag.js create mode 100644 src/hook/wheel-event.js diff --git a/public/netlist.css b/public/netlist.css index 4b6155e..ae10844 100644 --- a/public/netlist.css +++ b/public/netlist.css @@ -125,4 +125,4 @@ a { .el-select__wrapper.is-disabled { opacity: 0.6; box-shadow: unset !important; -} \ No newline at end of file +} diff --git a/src/components/render/index.vue b/src/components/render/index.vue index 410222d..449b502 100644 --- a/src/components/render/index.vue +++ b/src/components/render/index.vue @@ -1,14 +1,26 @@ @@ -20,5 +32,6 @@ defineComponent({ name: 'netlist-render' }); display: flex; justify-content: center; align-items: center; + transition: var(--animation-5s); } \ No newline at end of file diff --git a/src/components/setting/index.vue b/src/components/setting/index.vue index 889bc83..ce715e5 100644 --- a/src/components/setting/index.vue +++ b/src/components/setting/index.vue @@ -40,6 +40,20 @@
+ + +
+ + {{ t('render-animation') }} + + +
+ diff --git a/src/hook/css.js b/src/hook/css.js index 2ad9ef1..95a5c14 100644 --- a/src/hook/css.js +++ b/src/hook/css.js @@ -19,6 +19,7 @@ export function setDefaultCss() { document.body.style.setProperty('--el-color-info-light-9', 'var(--vscode-focusBorder)'); document.body.style.setProperty('--el-color-info', 'var(--foreground)'); document.body.style.setProperty('--el-color-info-light-8', 'var(--vscode-focusBorder)'); + // document.body.style.setProperty('--el-color-white', 'var(--background)'); // 设置全局宏 diff --git a/src/hook/global.js b/src/hook/global.js index 7cd65c5..46f3044 100644 --- a/src/hook/global.js +++ b/src/hook/global.js @@ -7,7 +7,8 @@ import { NetlistRender } from './render'; export const emitter = mitt(); export const globalSetting = reactive({ - language: 'zh' + language: 'zh', + renderAnimation: true }); export const globalLookup = { @@ -19,7 +20,22 @@ export const globalLookup = { /** * @type {NetlistRender} */ - netlistRender: new NetlistRender() + netlistRender: new NetlistRender(), + + /** + * @type {number} + */ + svgScale: 1, + + /** + * @type {number} + */ + svgTranslateX: 0, + + /** + * @type {number} + */ + svgTranslateY: 0 }; function loadSetting() { diff --git a/src/hook/render/drag.js b/src/hook/render/drag.js new file mode 100644 index 0000000..d253758 --- /dev/null +++ b/src/hook/render/drag.js @@ -0,0 +1,14 @@ +import * as d3 from 'd3'; +import ELK from 'elkjs'; + +/** + * @description 注册关于 器件 的拖动事件 + * + * 需要提取最小拓扑子图,然后重新调整各个区域的尺寸 + * @param {d3.Transition} renderSvg + */ +export function registerCellDragEvent(renderSvg) { + // renderSvg.call(d3.drag().on('drag', (event, g) => { + // console.log(event); + // })); +} \ No newline at end of file diff --git a/src/hook/render/index.js b/src/hook/render/index.js index d323385..5d34ddf 100644 --- a/src/hook/render/index.js +++ b/src/hook/render/index.js @@ -3,7 +3,8 @@ import ELK from 'elkjs'; import { Module } from './layout'; -import { globalLookup } from '../global'; +import { globalLookup, globalSetting } from '../global'; +import { registerCellDragEvent } from './drag'; export class NetlistRender { /** @@ -77,7 +78,6 @@ export class NetlistRender { 'org.eclipse.elk.layered.layering.strategy': 'LONGEST_PATH' } }); - console.log(layoutGraph); return layoutGraph; } @@ -96,12 +96,15 @@ export class NetlistRender { const ratio = this.renderHeight / virtualHeight; // 遍历计算布局进行创建 - const svg = d3.select(container).append('svg') + const svg = d3.select(container) + .selectAll('svg') .attr('width', virtualWidth) .attr('height', virtualHeight); - - await this.renderEntity(svg, computedLayout, ratio); + await this.renderLine(svg, computedLayout, ratio); + await this.renderEntity(svg, computedLayout, ratio); + + this.selection = svg; return svg; } @@ -121,12 +124,15 @@ export class NetlistRender { // 生成用于绘制的 d3 数据结构 // 默认需要渲染成矩形的(缺失样式的器件、例化模块等等) const squares = []; + + const connections = []; const svgElements = []; const skinManager = globalLookup.skinManager; for (const node of computedLayout.children) { const skin = skinManager.querySkin(node.renderName); if (skin) { + // 具有 skin 的器件 svgElements.push({ element: skin.meta.svgDoc.documentElement, x: node.x, @@ -136,6 +142,7 @@ export class NetlistRender { fill: 'var(--main-dark-color)', }); } else { + // 没有 skin 的器件 squares.push({ x: node.x, y: node.y, @@ -150,54 +157,126 @@ export class NetlistRender { // 如果存在 port,绘制 port for (const cellPort of node.ports || []) { - squares.push({ + connections.push({ x: cellPort.x + node.x, - y: cellPort.y + node.y, + y: cellPort.y + node.y + 0.5, // 0.5 是为了线宽 width: cellPort.width, height: cellPort.height, fill: 'var(--main-color)', text: '', - rx: 0, - ry: 0 + r: 3.5 }); } } - svg.selectAll('rect') - .data(squares) - .enter() - .append('rect') - .attr('x', data => data.x) - .attr('y', data => data.y) - .attr('width', data => data.width) - .attr('height', data => data.height) - .attr('fill', d => d.fill) - .attr('stroke', 'var(--main-color)') - .attr('stroke-width', 2) - .attr('rx', d => d.rx) - .attr('ry', d => d.ry); - - svg.selectAll('g') - .data(svgElements) - .enter() - .append(data => { - const element = data.element; - element.setAttribute('x', data.x); - element.setAttribute('y', data.y); - return element; - }); + if (globalSetting.renderAnimation) { + svg.selectAll('rect') + .data(squares) + .enter() + .append('rect') + .attr('x', data => data.x) + .attr('y', data => data.y) + .attr('width', data => data.width) + .attr('height', data => data.height) + .attr('fill', d => d.fill) + .transition() + .duration(1000) + .attr('stroke', 'var(--main-color)') + .attr('stroke-width', 2) + .attr('rx', d => d.rx) + .attr('ry', d => d.ry); + + const renderSvg = svg.selectAll('g') + .data(svgElements) + .enter() + .append(data => { + const element = data.element; + element.setAttribute('x', data.x); + element.setAttribute('y', data.y); + element.setAttribute('stroke-opacity', 0); + return element; + }) + .transition() + .duration(1000) + .attr('stroke-opacity', 1); + + registerCellDragEvent(renderSvg); - svg.selectAll('text') - .data(squares) - .enter() - .append('text') - .attr('x', data => data.x + data.width / 2) // 文本的 x 坐标(居中) - .attr('y', data => data.y + data.height / 2) // 文本的 y 坐标(居中) - .attr('dominant-baseline', 'middle') // 文本垂直居中 - .attr('text-anchor', 'middle') // 文本水平居中 - .attr('fill', 'white') // 文本颜色 - .attr('font-size', '12px') - .text(data => data.text); // 设置文本内容 + svg.selectAll('circle') + .data(connections) + .enter() + .append('circle') + .attr('cx', data => data.x) + .attr('cy', data => data.y) + .attr('width', data => data.width) + .attr('height', data => data.height) + .transition() + .duration(1000) + .attr('fill', d => d.fill) + .attr('r', d => d.r); + + svg.selectAll('text') + .data(squares) + .enter() + .append('text') + .attr('x', data => data.x + data.width / 2) // 文本的 x 坐标(居中) + .attr('y', data => data.y + data.height / 2) // 文本的 y 坐标(居中) + .attr('dominant-baseline', 'middle') // 文本垂直居中 + .attr('text-anchor', 'middle') // 文本水平居中 + .attr('fill', 'white') // 文本颜色 + .attr('font-size', '0') + .transition() + .duration(1000) + .attr('font-size', '12px') + .text(data => data.text); // 设置文本内容 + } else { + svg.selectAll('rect') + .data(squares) + .enter() + .append('rect') + .attr('x', data => data.x) + .attr('y', data => data.y) + .attr('width', data => data.width) + .attr('height', data => data.height) + .attr('fill', d => d.fill) + .attr('stroke', 'var(--main-color)') + .attr('stroke-width', 2) + .attr('rx', d => d.rx) + .attr('ry', d => d.ry); + + svg.selectAll('g') + .data(svgElements) + .enter() + .append(data => { + const element = data.element; + element.setAttribute('x', data.x); + element.setAttribute('y', data.y); + return element; + }); + + svg.selectAll('circle') + .data(connections) + .enter() + .append('circle') + .attr('cx', data => data.x) + .attr('cy', data => data.y) + .attr('width', data => data.width) + .attr('height', data => data.height) + .attr('fill', d => d.fill) + .attr('r', d => d.r); + + svg.selectAll('text') + .data(squares) + .enter() + .append('text') + .attr('x', data => data.x + data.width / 2) // 文本的 x 坐标(居中) + .attr('y', data => data.y + data.height / 2) // 文本的 y 坐标(居中) + .attr('dominant-baseline', 'middle') // 文本垂直居中 + .attr('text-anchor', 'middle') // 文本水平居中 + .attr('fill', 'white') // 文本颜色 + .attr('font-size', '12px') + .text(data => data.text); // 设置文本内容 + } } /** @@ -230,7 +309,7 @@ export class NetlistRender { } } - svg.selectAll('line') + let lineSelection = svg.selectAll('line') .data(lines) .enter() .append('line') @@ -238,7 +317,24 @@ export class NetlistRender { .attr('y1', data => data.y1) .attr('x2', data => data.x2) .attr('y2', data => data.y2) - .attr('stroke-width', data => data.strokeWidth) .attr('stroke', data => data.color); + + if (globalSetting.renderAnimation) { + lineSelection = lineSelection.transition().duration(1000); + } + + lineSelection.attr('stroke-width', data => data.strokeWidth); + } + + /** + * @description 从 globalLookup 中更新 svg 的方位 + */ + updateLocationFromGlobal() { + const svg = globalLookup.netlistRender.selection; + if (!svg) { + return; + } + + svg.attr('transform', `translate(${globalLookup.svgTranslateX}, ${globalLookup.svgTranslateY}) scale(${globalLookup.svgScale})`); } } \ No newline at end of file diff --git a/src/hook/render/layout.js b/src/hook/render/layout.js index d9f3ee1..3aa740d 100644 --- a/src/hook/render/layout.js +++ b/src/hook/render/layout.js @@ -134,16 +134,6 @@ export class Module { for (let i = 0; i < leftSideConnections.length; ++ i) { const connection = leftSideConnections[i]; const yOffset = meta.getPortYOffset(connection.name) * SKIN_SCALE; - const targetY = yOffset; - - // 默认采用 JUSTIFIED,均分一侧的 height - // 我们绘制的 svg 的 port offset y 未必和 elk 分配得到的一样,因此需要进行修正 - const layoutY = height / (leftSideConnections.length + 1) * (i + 1); - const extraOffset = LINE_WIDTH * leftSideConnections.length; - const anchorY = layoutY - targetY; - - console.log(yOffset); - ports.push({ id: connection.id, @@ -161,17 +151,7 @@ export class Module { // 计算右侧的 for (let i = 0; i < rightSideConnections.length; ++ i) { const connection = rightSideConnections[i]; - const yOffset = meta.getPortYOffset(connection.name) * SKIN_SCALE; - const targetY = yOffset; - - // 默认采用 JUSTIFIED,均分一侧的 height - // 我们绘制的 svg 的 port offset y 未必和 elk 分配得到的一样,因此需要进行修正 - const layoutY = height / (rightSideConnections.length + 1) * (i + 1); - const extraOffset = LINE_WIDTH * rightSideConnections.length / 2; - const anchorY = targetY - layoutY + extraOffset; - - console.log(yOffset); - + const yOffset = meta.getPortYOffset(connection.name) * SKIN_SCALE; ports.push({ id: connection.id, diff --git a/src/hook/wheel-event.js b/src/hook/wheel-event.js new file mode 100644 index 0000000..5035b35 --- /dev/null +++ b/src/hook/wheel-event.js @@ -0,0 +1,104 @@ +import { globalLookup } from "./global"; + +const MAX_SCALE = 10.0; +const MIN_SCALE = 0.0; + +/** + * + * @param {HTMLElement} element + * @param {WheelEvent} event + */ +function windowsKeydown(element, event) { + const svg = globalLookup.netlistRender.selection; + if (!svg) { + return; + } + + const { deltaX, deltaY } = event; + if (event.ctrlKey) { + if (deltaY < 0) { + // scale up + const nextScale = globalLookup.svgScale + 0.1; + if (nextScale <= MAX_SCALE) { + globalLookup.svgScale = nextScale; + globalLookup.netlistRender.updateLocationFromGlobal(); + } + + } else if (deltaY > 0) { + // scale down + const nextScale = globalLookup.svgScale - 0.1; + if (nextScale >= MIN_SCALE) { + globalLookup.svgScale = nextScale; + globalLookup.netlistRender.updateLocationFromGlobal(); + } + } + + event.preventDefault(); + } else if (event.shiftKey) { + event.preventDefault(); + } else if (deltaX !== 0 && deltaY === 0) { + if (deltaX > 0) { + // scroll left + const translateX = globalLookup.svgTranslateX - 50; + globalLookup.svgTranslateX = translateX; + globalLookup.netlistRender.updateLocationFromGlobal(); + } else { + // scroll right + const translateX = globalLookup.svgTranslateX + 50; + globalLookup.svgTranslateX = translateX; + globalLookup.netlistRender.updateLocationFromGlobal(); + } + + event.preventDefault(); + } else if (deltaX === 0 && deltaY !== 0) { + if (deltaY > 0) { + // scroll up + const translateY = globalLookup.svgTranslateY - 50; + globalLookup.svgTranslateY = translateY; + globalLookup.netlistRender.updateLocationFromGlobal(); + } else { + // scroll down + const translateY = globalLookup.svgTranslateY + 50; + globalLookup.svgTranslateY = translateY; + globalLookup.netlistRender.updateLocationFromGlobal(); + } + + event.preventDefault(); + } +} + +/** + * + * @param {HTMLElement} element + * @param {WheelEvent} event + */ +function linuxKeydown(element, event) { + +} + +/** + * + * @param {HTMLElement} element + * @param {WheelEvent} event + */ +function macOsKeydown(element, event) { + +} + +/** + * @description 注册滚轮事件 + * @param {HTMLElement} element + * @returns {(event: WheelEvent) => void} + */ +export function registerWheelEvent(element) { + const platform = navigator.platform; + if (platform.startsWith('Win')) { + return event => windowsKeydown(element, event); + } else if (platform.startsWith('Linux')) { + return event => linuxKeydown(element, event); + } else if (platform.startsWith('Mac')) { + return event => macOsKeydown(element, event); + } else { + throw Error('不支持的操作系统!'); + } +} diff --git a/src/i18n/ar.json b/src/i18n/ar.json index 1a5ecdb..8b71648 100644 --- a/src/i18n/ar.json +++ b/src/i18n/ar.json @@ -8,5 +8,6 @@ "cancel": "إلغاء", "tips": "نصائح", "language-setting": "اللغة", - "setting.language.change-dialog": "لقد قمت بتغيير اللغة إلى {0} ، ونوصي بإعادة تشغيل Netlist Viewer" + "setting.language.change-dialog": "لقد قمت بتغيير اللغة إلى {0} ، ونوصي بإعادة تشغيل Netlist Viewer", + "render-animation": "تفعيل الرسوم المتحركة للعرض" } \ No newline at end of file diff --git a/src/i18n/de.json b/src/i18n/de.json index 2819da6..8e966a4 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -8,5 +8,6 @@ "cancel": "Abbrechen", "tips": "Tipps", "language-setting": "Sprache", - "setting.language.change-dialog": "Sie haben die Sprache auf {0} geändert. Wir empfehlen Ihnen, Netlist Viewer neu zu starten." + "setting.language.change-dialog": "Sie haben die Sprache auf {0} geändert. Wir empfehlen Ihnen, Netlist Viewer neu zu starten.", + "render-animation": "Rendering-Animation aktivieren" } \ No newline at end of file diff --git a/src/i18n/en.json b/src/i18n/en.json index 927d7c5..c4b136b 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -3,10 +3,11 @@ "general-setting": "General", "appearance-setting": "Appearance", "current-version": "current version", - "copyright": "The copyright of this software belongs to Digital-IDE project team. Welcome to Star.", + "copyright": "The copyright of this software belongs to Digital-IDE project team. Welcome to Star.", "confirm": "confirm", "cancel": "cancel", "tips": "Tips", "language-setting": "Language", - "setting.language.change-dialog": "You have changed the language to {0}, we recommend restarting Netlist Viewer." + "setting.language.change-dialog": "You have changed the language to {0}, we recommend restarting Netlist Viewer.", + "render-animation": "enable rendering animation" } \ No newline at end of file diff --git a/src/i18n/fr.json b/src/i18n/fr.json index bbf7f63..d466186 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -8,5 +8,6 @@ "cancel": "Annuler", "tips": "Conseils", "language-setting": "Langue", - "setting.language.change-dialog": "Vous avez changé la langue en {0}, nous vous recommandons de redémarrer Netlist Viewer." + "setting.language.change-dialog": "Vous avez changé la langue en {0}, nous vous recommandons de redémarrer Netlist Viewer.", + "render-animation": "Activer l'animation de rendu" } \ No newline at end of file diff --git a/src/i18n/ja.json b/src/i18n/ja.json index 3073cda..3f59426 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -8,5 +8,6 @@ "cancel": "キャンセル", "tips": "ヒント", "language-setting": "言語", - "setting.language.change-dialog": "言語を {0} に変更しました。Netlist Viewer を再起動することをお勧めします。" + "setting.language.change-dialog": "言語を {0} に変更しました。Netlist Viewer を再起動することをお勧めします。", + "render-animation": "レンダリングアニメーションを有効にする" } \ No newline at end of file diff --git a/src/i18n/ko.json b/src/i18n/ko.json index fba49ec..53d8422 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -8,5 +8,6 @@ "cancel": "취소", "tips": "팁", "language-setting": "언어", - "setting.language.change-dialog": "언어를 {0} 으로 변경했습니다. Netlist Viewer 를 다시 시작하는 것을 권장합니다." + "setting.language.change-dialog": "언어를 {0} 으로 변경했습니다. Netlist Viewer 를 다시 시작하는 것을 권장합니다.", + "render-animation": "렌더링 애니메이션 활성화" } \ No newline at end of file diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 8eb884e..aa6712a 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -8,5 +8,6 @@ "cancel": "Отменить", "tips": "Советы", "language-setting": "Язык", - "setting.language.change-dialog": "Вы изменили язык на {0}, мы рекомендуем перезапустить Netlist Viewer." + "setting.language.change-dialog": "Вы изменили язык на {0}, мы рекомендуем перезапустить Netlist Viewer.", + "render-animation": "Включить анимацию рендеринга" } \ No newline at end of file diff --git a/src/i18n/zh-cn.json b/src/i18n/zh-cn.json index 10ce1c8..140795f 100644 --- a/src/i18n/zh-cn.json +++ b/src/i18n/zh-cn.json @@ -8,5 +8,6 @@ "cancel": "取消", "tips": "提示", "language-setting": "语言", - "setting.language.change-dialog": "您已经更换语言为 {0} ,我们建议您重启 Netlist Viewer" + "setting.language.change-dialog": "您已经更换语言为 {0} ,我们建议您重启 Netlist Viewer", + "render-animation": "开启渲染动画" } \ No newline at end of file diff --git a/src/i18n/zh-tw.json b/src/i18n/zh-tw.json index e1c5f5d..2b254d1 100644 --- a/src/i18n/zh-tw.json +++ b/src/i18n/zh-tw.json @@ -8,5 +8,6 @@ "cancel": "取消", "tips": "提示", "language-setting": "語言", - "setting.language.change-dialog": "您已將語言更改為 {0} ,我們建議您重新啟動 Netlist Viewer。" + "setting.language.change-dialog": "您已將語言更改為 {0} ,我們建議您重新啟動 Netlist Viewer。", + "render-animation": "開啟渲染動畫" } \ No newline at end of file