完成多个器件桥接类型的渲染

This commit is contained in:
锦恢 2024-12-25 19:39:36 +08:00
parent 425c513635
commit 2f1d16c303
11 changed files with 308 additions and 277 deletions

View File

@ -23,4 +23,3 @@ resources
``` ```
---

View File

@ -1,125 +1,137 @@
{ {
"creator": "Yosys 0.48+5 (git sha1 7a362f1f7, clang++ 18.1.2-wasi-sdk -Oz)", "creator": "Yosys 0.48+5 (git sha1 7a362f1f7, clang++ 18.1.2-wasi-sdk -Oz)",
"modules": { "modules": {
"half_adder": { "half_adder": {
"attributes": {
"top": "00000000000000000000000000000001",
"src": "/root/half_adder.v:1.1-9.10"
},
"ports": {
"a": {
"direction": "input",
"bits": [ 2 ]
},
"b": {
"direction": "input",
"bits": [ 3 ]
},
"c": {
"direction": "input",
"bits": [ 4 ]
},
"sum": {
"direction": "output",
"bits": [ 5 ]
},
"carry": {
"direction": "output",
"bits": [ 6 ]
}
},
"cells": {
"$auto$simplemap.cc:75:simplemap_bitop$81": {
"hide_name": 1,
"type": "$_AND_",
"parameters": {
},
"attributes": { "attributes": {
"top": "00000000000000000000000000000001", "src": "/root/half_adder.v:7.18-7.23"
"src": "/root/half_adder.v:1.1-8.10"
}, },
"ports": { "port_directions": {
"a": { "A": "input",
"direction": "input", "B": "input",
"bits": [ "Y": "output"
2
]
},
"b": {
"direction": "input",
"bits": [
3
]
},
"sum": {
"direction": "output",
"bits": [
4
]
},
"carry": {
"direction": "output",
"bits": [
5
]
}
}, },
"cells": { "connections": {
"$auto$simplemap.cc:75:simplemap_bitop$80": { "A": [ 2 ],
"hide_name": 1, "B": [ 3 ],
"type": "$_AND_", "Y": [ 7 ]
"parameters": {},
"attributes": {
"src": "/root/half_adder.v:6.18-6.23"
},
"port_directions": {
"A": "input",
"B": "input",
"Y": "output"
},
"connections": {
"A": [
2
],
"B": [
3
],
"Y": [
5
]
}
},
"$auto$simplemap.cc:75:simplemap_bitop$81": {
"hide_name": 1,
"type": "$_XOR_",
"parameters": {},
"attributes": {
"src": "/root/half_adder.v:7.16-7.21"
},
"port_directions": {
"A": "input",
"B": "input",
"Y": "output"
},
"connections": {
"A": [
2
],
"B": [
3
],
"Y": [
4
]
}
}
},
"netnames": {
"a": {
"hide_name": 0,
"bits": [
2
],
"attributes": {
"src": "/root/half_adder.v:2.9-2.10"
}
},
"b": {
"hide_name": 0,
"bits": [
3
],
"attributes": {
"src": "/root/half_adder.v:3.9-3.10"
}
},
"carry": {
"hide_name": 0,
"bits": [
5
],
"attributes": {
"src": "/root/half_adder.v:4.10-4.15"
}
},
"sum": {
"hide_name": 0,
"bits": [
4
],
"attributes": {
"src": "/root/half_adder.v:5.10-5.13"
}
}
} }
},
"$auto$simplemap.cc:75:simplemap_bitop$82": {
"hide_name": 1,
"type": "$_OR_",
"parameters": {
},
"attributes": {
"src": "/root/half_adder.v:7.18-7.27"
},
"port_directions": {
"A": "input",
"B": "input",
"Y": "output"
},
"connections": {
"A": [ 7 ],
"B": [ 4 ],
"Y": [ 6 ]
}
},
"$auto$simplemap.cc:75:simplemap_bitop$83": {
"hide_name": 1,
"type": "$_XOR_",
"parameters": {
},
"attributes": {
"src": "/root/half_adder.v:8.16-8.21"
},
"port_directions": {
"A": "input",
"B": "input",
"Y": "output"
},
"connections": {
"A": [ 2 ],
"B": [ 3 ],
"Y": [ 5 ]
}
}
},
"netnames": {
"$and$/root/half_adder.v:7$1_Y": {
"hide_name": 1,
"bits": [ 7 ],
"attributes": {
"src": "/root/half_adder.v:7.18-7.23"
}
},
"a": {
"hide_name": 0,
"bits": [ 2 ],
"attributes": {
"src": "/root/half_adder.v:2.9-2.10"
}
},
"b": {
"hide_name": 0,
"bits": [ 3 ],
"attributes": {
"src": "/root/half_adder.v:3.9-3.10"
}
},
"c": {
"hide_name": 0,
"bits": [ 4 ],
"attributes": {
"src": "/root/half_adder.v:4.9-4.10"
}
},
"carry": {
"hide_name": 0,
"bits": [ 6 ],
"attributes": {
"src": "/root/half_adder.v:5.10-5.15"
}
},
"sum": {
"hide_name": 0,
"bits": [ 5 ],
"attributes": {
"src": "/root/half_adder.v:6.10-6.13"
}
}
} }
}
} }
} }

View File

@ -6,17 +6,22 @@
</template> </template>
<script setup> <script setup>
import { useI18n } from 'vue-i18n';
import Render from '@/components/render'; import Render from '@/components/render';
import RightNav from '@/components/right-nav.vue'; import RightNav from '@/components/right-nav.vue';
import { onMounted } from 'vue'; import { onMounted, watch } from 'vue';
import { setDefaultCss } from './hook/css'; import { setDefaultCss } from './hook/css';
import { NetlistRender } from './hook/render';
import * as d3 from 'd3'; import { globalLookup, globalSetting } from './hook/global';
import { globalLookup } from './hook/global';
// globalSetting localStorage
watch(
() => globalSetting,
() => {
localStorage.setItem('setting', JSON.stringify(globalSetting));
},
{ deep: true }
);
const { t } = useI18n();
onMounted(async () => { onMounted(async () => {
// css // css
@ -29,7 +34,7 @@ onMounted(async () => {
const skinManager = globalLookup.skinManager; const skinManager = globalLookup.skinManager;
skinManager.load(skinBinary); skinManager.load(skinBinary);
render.load(netJson); render.load(netJson);
const layout = await render.createLayout(); const layout = await render.createLayout();
const svg = await render.render(layout, '#netlist'); const svg = await render.render(layout, '#netlist');

View File

@ -48,7 +48,6 @@
</span> </span>
<el-switch <el-switch
v-model="globalSetting.renderAnimation" v-model="globalSetting.renderAnimation"
size="large"
active-text="ON" active-text="ON"
inactive-text="OFF" inactive-text="OFF"
/> />
@ -66,7 +65,7 @@ import { useI18n } from 'vue-i18n';
defineComponent({ name: "dide-setting" }); defineComponent({ name: "dide-setting" });
const { t, locale } = useI18n(); const { t, locale } = useI18n();
locale.value = globalSetting.language;
watch( watch(
() => locale.value, () => locale.value,
@ -85,7 +84,7 @@ function confirmLanguageDialog() {
function onlanguagechange(code) { function onlanguagechange(code) {
const option = languageSetting.options.find(item => item.value === code); const option = languageSetting.options.find(item => item.value === code);
currentLanguage.value = option.text; currentLanguage.value = option.text;
languageDialogShow.value = true; languageDialogShow.value = true;
} }

View File

@ -22,20 +22,6 @@ export const globalLookup = {
*/ */
netlistRender: new NetlistRender(), netlistRender: new NetlistRender(),
/**
* @type {number}
*/
svgScale: 1,
/**
* @type {number}
*/
svgTranslateX: 0,
/**
* @type {number}
*/
svgTranslateY: 0
}; };
function loadSetting() { function loadSetting() {

View File

@ -1,13 +1,16 @@
import * as d3 from 'd3'; import * as d3 from 'd3';
import { globalSetting } from '../global'; import { globalSetting } from '../global';
import { NetlistRender } from '.';
export class CellRender { export class CellRender {
/** /**
* *
* @param {d3.Selection} selection * @param {d3.Selection} selection
* @param {NetlistRender} rootRender
*/ */
constructor(selection, cells) { constructor(selection, rootRender) {
this.parentSelection = selection; this.parentSelection = selection;
this.rootRender = rootRender;
/** /**
* @type {BasicD3DataItem[]} * @type {BasicD3DataItem[]}
@ -33,6 +36,7 @@ export class CellRender {
render() { render() {
const data = this.data; const data = this.data;
const rootRender = this.rootRender;
let cellSelections = this.parentSelection.selectAll('g') let cellSelections = this.parentSelection.selectAll('g')
.data(data) .data(data)
@ -53,19 +57,22 @@ export class CellRender {
.duration(1000) .duration(1000)
.attr('stroke-opacity', 1) .attr('stroke-opacity', 1)
.attr('class', 'grab') .attr('class', 'grab')
.on('end', function (data) { .on('end', function (data) {
console.log('enter end');
const cellSelection = d3.select(this); const cellSelection = d3.select(this);
registerDragEvent(cellSelection, data);
registerDragEvent(cellSelection, data, rootRender);
}); });
} else { } else {
cellSelections = cellSelections cellSelections = cellSelections
.attr('class', 'grab') .attr('class', 'grab')
.each(function (data) { .each(function (data) {
console.log('enter end'); // 在这里执行你需要的逻辑
const cellSelection = d3.select(this); const cellSelection = d3.select(this);
registerDragEvent(cellSelection, data);
// 计算最小拓扑图
const mintoGraph = undefined;
registerDragEvent(cellSelection, data, rootRender);
}); });
} }
@ -80,14 +87,15 @@ export class CellRender {
* 需要提取最小拓扑子图然后重新调整各个区域的尺寸 * 需要提取最小拓扑子图然后重新调整各个区域的尺寸
* @param {d3.Selection} selection * @param {d3.Selection} selection
* @param {any} data * @param {any} data
* @param {NetlistRender} rootRender
*/ */
export function registerDragEvent(selection, data) { export function registerDragEvent(selection, data, rootRender) {
// 创建拖拽行为 // 创建拖拽行为
const drag = d3.drag(); const drag = d3.drag();
drag.on('start', event => dragStart(event, selection, data)); drag.on('start', async event => dragStart(event, selection, data));
drag.on('drag', event => dragged(event, selection, data)); drag.on('drag', async event => dragged(event, selection, data, rootRender));
drag.on('end', event => dragEnd(event, selection, data)) drag.on('end', async event => dragEnd(event, selection, data))
selection.call(drag); selection.call(drag);
} }
@ -105,14 +113,20 @@ function dragStart(event, selection, data) {
* *
* @param {d3.D3DragEvent} event * @param {d3.D3DragEvent} event
* @param {d3.Selection} selection * @param {d3.Selection} selection
* @param {NetlistRender} rootRender
*/ */
function dragged(event, selection, data) { function dragged(event, selection, data, rootRender) {
// 当拖动结束时D3 会根据绑定的数据data.x 和 data.y重新渲染元素导致元素回到初始位置。
// 所以需要 手动更新 data.x 和 data.y
data.x = event.x; data.x = event.x;
data.y = event.y; data.y = event.y;
selection selection
.attr('x', event.x) .attr('x', event.x)
.attr('y', event.y); .attr('y', event.y);
// 根据最小拓扑图,提取出关键点,重新计算布局
} }
/** /**

View File

@ -31,11 +31,31 @@ export class NetlistRender {
edges: [] edges: []
}; };
/**
* @type {import('elkjs').ELK}
*/
this.elk = new ELK();
/** /**
* @type {Map<string, Module>} * @type {Map<string, Module>}
*/ */
this.nameToModule = new Map(); this.nameToModule = new Map();
this.maxX = -1;
this.minX = 1e12;
this.maxY = -1;
this.minY = 1e12;
/**
* @type {number} 一开始绘制完成后整体的横向偏移量默认一开始从左上角开始画不居中
*/
this.startOffsetX = 0;
/**
* @type {number} 一开始绘制完成后整体的纵向偏移量默认一开始从左上角开始画不居中
*/
this.startOffsetY = 0;
} }
/** /**
@ -48,16 +68,19 @@ export class NetlistRender {
*/ */
this.rawNet = rawNet; this.rawNet = rawNet;
// 转换为 elkjs 格式的 graph // 构造模块树
for (const [moduleName, rawModule] of Object.entries(rawNet.modules)) { for (const [moduleName, rawModule] of Object.entries(rawNet.modules)) {
const top = parseInt(rawModule.attributes.top); const top = parseInt(rawModule.attributes.top);
// 一开始只渲染 top 模块 // 一开始只渲染 top 模块,或者告诉我当前的 top 是什么
if (top) { if (top) {
const module = new Module(moduleName, rawModule); const module = new Module(moduleName, rawModule);
// 构造符合 elk 格式的节点数据
const portNodes = module.makeNetsElkNodes(); const portNodes = module.makeNetsElkNodes();
const cellNodes = module.makeCellsElkNodes(); const cellNodes = module.makeCellsElkNodes();
const [constantNodes, connectionEdges] = module.makeConnectionElkNodes(); const [constantNodes, connectionEdges] = module.makeConnectionElkNodes();
// 挂载到渲染图中
this.elkGraph.children.push(...portNodes); this.elkGraph.children.push(...portNodes);
this.elkGraph.children.push(...cellNodes); this.elkGraph.children.push(...cellNodes);
this.elkGraph.children.push(...constantNodes); this.elkGraph.children.push(...constantNodes);
@ -73,15 +96,21 @@ export class NetlistRender {
* @returns {Promise<ElkNode>} * @returns {Promise<ElkNode>}
*/ */
async createLayout() { async createLayout() {
const elk = new ELK();
const graph = this.elkGraph; const graph = this.elkGraph;
const layoutGraph = await elk.layout(graph, {
const start = performance.now();
const layoutGraph = await this.elk.layout(graph, {
layoutOptions: { layoutOptions: {
'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 35, 'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 35,
'org.eclipse.elk.spacing.nodeNode': 35, 'org.eclipse.elk.spacing.nodeNode': 35,
'org.eclipse.elk.layered.layering.strategy': 'LONGEST_PATH' 'org.eclipse.elk.layered.layering.strategy': 'LONGEST_PATH'
} }
}); });
const timecost = (performance.now() - start).toFixed(3);
console.log(
`%c 布局计算耗时 ${timecost} ms`,
'background-color: #CB81DA; color: white; padding: 3px; border-radius: 3px;'
);
return layoutGraph; return layoutGraph;
} }
@ -132,6 +161,8 @@ export class NetlistRender {
// 将分组作为一个后续操作的 parent selection // 将分组作为一个后续操作的 parent selection
const g = svg.append('g'); const g = svg.append('g');
console.log(computedLayout);
await this.renderLine(g, computedLayout, ratio); await this.renderLine(g, computedLayout, ratio);
await this.renderEntity(g, computedLayout, ratio); await this.renderEntity(g, computedLayout, ratio);
@ -139,7 +170,10 @@ export class NetlistRender {
this.selection = svg; this.selection = svg;
// 注册平移和缩放 // 注册平移和缩放
// this.registerRenderTransform(g); this.registerRenderTransform(g);
// 根据最大最小尺寸微调全局方位
this.adjustLocation(g);
return svg; return svg;
} }
@ -164,6 +198,9 @@ export class NetlistRender {
this.connectionRender = new ConnectionRender(parentSelection); this.connectionRender = new ConnectionRender(parentSelection);
for (const node of computedLayout.children) { for (const node of computedLayout.children) {
// 只计算形体的,因为 连接点 非常小,几乎不影响布局
this.updateMaxMinSize(node.x, node.y, node.width, node.height);
const skin = skinManager.querySkin(node.renderName); const skin = skinManager.querySkin(node.renderName);
if (skin) { if (skin) {
// 具有 skin 的器件 // 具有 skin 的器件
@ -223,15 +260,16 @@ export class NetlistRender {
// 创建缩放行为 // 创建缩放行为
const zoom = d3.zoom() const zoom = d3.zoom()
// 设置缩放范围(最小缩放比例 0.5,最大缩放比例 5 // 设置缩放范围(最小缩放比例 0.5,最大缩放比例 5
.scaleExtent([0.5, 5]) .scaleExtent([0.1, 10])
.on("zoom", zoomed); .on("zoom", zoomed);
// 将缩放行为应用到 SVG // 将缩放行为应用到 SVG
this.selection.call(zoom); this.selection.call(zoom);
this.zoom = zoom;
// 缩放事件处理函数 // 缩放事件处理函数
function zoomed(event) { function zoomed(event) {
const { transform, sourceEvent } = event; const { transform, sourceEvent } = event;
if (sourceEvent && sourceEvent.type === "wheel") { if (sourceEvent && sourceEvent.type === "wheel") {
if (globalSetting.renderAnimation) { if (globalSetting.renderAnimation) {
@ -248,54 +286,64 @@ export class NetlistRender {
} else { } else {
parentSelection.attr('transform', transform); parentSelection.attr('transform', transform);
} }
} else {
// 系统调用的变换,慢一点变换
if (globalSetting.renderAnimation) {
parentSelection
.transition()
.duration(1000)
.attr('transform', transform);
} else {
parentSelection.attr('transform', transform);
}
} }
} }
} }
updateMaxMinSize(x, y, width, height) {
let x1 = x;
let y1 = y;
let x2 = x1 + width;
let y2 = y1 + height;
this.minX = Math.min(this.minX, x1);
this.maxX = Math.max(this.maxX, x2);
this.minY = Math.min(this.minY, y1);
this.maxY = Math.max(this.maxY, y2);
}
/** /**
* @description globalLookup 中更新 svg 的方位 * @description 调整位姿
* @param {d3.Selection} parentSelection
*/ */
updateLocationFromGlobal() { adjustLocation(parentSelection) {
// const svg = globalLookup.netlistRender.selection; const renderHeight = this.renderHeight;
// if (!svg) { const renderWidth = this.renderWidth;
// return; // TODO: 更加智能的方位调整
const cx = (this.minX + this.maxX) / 2;
const cy = (this.minY + this.maxY) / 2;
this.startOffsetX = renderWidth / 2 - cx;
this.startOffsetY = renderHeight / 2 - cy;
const transform = d3.zoomIdentity.translate(
this.startOffsetX, this.startOffsetY
);
this.selection.call(
this.zoom.transform,
transform
);
// if (globalSetting.renderAnimation) {
// parentSelection
// .transition()
// .duration(1000)
// .attr('transform', transform);
// } else {
// parentSelection.attr('transform', transform);
// } // }
// svg.attr('transform', `translate(${globalLookup.svgTranslateX}, ${globalLookup.svgTranslateY}) scale(${globalLookup.svgScale})`);
} }
/**
* @description 渲染 module 中的 port
*/
renderPorts() {
}
/**
* @description 渲染 module 中的例化模块
*/
renderInstantiations() {
}
/**
* @description 渲染 module 中的基础器件
*/
renderCells() {
}
/**
* @description 渲染每一个 例化模块/基础器件 的接入端口
*/
renderConnections() {
}
/**
* @description 渲染连线
*/
renderWires() {
}
} }

View File

@ -3,7 +3,7 @@
*/ */
import { globalLookup } from "../global"; import { globalLookup } from "../global";
import { Cell, CELL_LIBS, ModuleTree } from "./yosys"; import { Cell, ModuleTree } from "./yosys";
export const SKIN_SCALE = 1; export const SKIN_SCALE = 1;
export const LINE_WIDTH = 2; export const LINE_WIDTH = 2;
@ -33,16 +33,9 @@ export const ELK_DIRECTION = {
BOTTOM: 'SOUTH' BOTTOM: 'SOUTH'
} }
/**
*
* @param {Cell} cell
*/
function getInstanceSize(cell) {
// 根据 port 尺寸来进行计算
}
function makeEdgeId(fromId, toId) { export function makeEdgeId(fromId, toId) {
return 'edge-' + fromId + '-' + toId; return fromId + '-' + toId;
} }
export class Module { export class Module {
@ -229,8 +222,6 @@ export class Module {
for (const cellName of tree.nameToCell.keys()) { for (const cellName of tree.nameToCell.keys()) {
const cell = tree.nameToCell.get(cellName); const cell = tree.nameToCell.get(cellName);
// 当前器件的 ID
const cellId = cell.id;
for (const connectionName of cell.nameToConnection.keys()) { for (const connectionName of cell.nameToConnection.keys()) {
const connection = cell.nameToConnection.get(connectionName); const connection = cell.nameToConnection.get(connectionName);
@ -257,7 +248,7 @@ export class Module {
// 创建常数到器件的连线 // 创建常数到器件的连线
const edge = { const edge = {
id: makeEdgeId(cellId, id), id: makeEdgeId(cell.id, id),
sources: [connection.id], sources: [connection.id],
targets: [id] targets: [id]
}; };
@ -268,11 +259,14 @@ export class Module {
// 1. 当前的器件的这个端口和某一个 port 连接 // 1. 当前的器件的这个端口和某一个 port 连接
// 2. 当前的器件的这个端口和另一个器件的一个端口连接 // 2. 当前的器件的这个端口和另一个器件的一个端口连接
if (tree.wireIdToPort.has(wireId)) { if (tree.wireIdToPort.has(wireId)) {
// 当前的器件的这个端口和某一个 port 连接
const port = tree.wireIdToPort.get(wireId); const port = tree.wireIdToPort.get(wireId);
if (port.direction === 'input') { if (port.direction === 'input') {
const edge = { const edge = {
id: makeEdgeId(cellId, port.id), // id 遵循 sourcePort-targetPort
id: makeEdgeId(port.id, cell.id),
source: port.id, source: port.id,
sourcePort: port.id, sourcePort: port.id,
target: cell.id, target: cell.id,
@ -282,7 +276,7 @@ export class Module {
edges.push(edge); edges.push(edge);
} else { } else {
const edge = { const edge = {
id: makeEdgeId(cellId, port.id), id: makeEdgeId(cell.id, port.id),
source: cell.id, source: cell.id,
sourcePort: connection.id, sourcePort: connection.id,
target: port.id, target: port.id,
@ -292,14 +286,32 @@ export class Module {
edges.push(edge); edges.push(edge);
} }
} else if (tree.wireIdToConnection.has(wireId)) { } else if (tree.wireIdToConnection.has(wireId)) {
const connection = tree.wireIdToConnection.get(wireId);
const edge = { // 当前的器件的这个端口和另一个器件的一个端口连接
id: makeEdgeId(cellId, connection.id), const conn = tree.wireIdToConnection.get(wireId);
sources: [cellId],
targets: [connection.id]
};
edges.push(edge); if (conn.direction === 'input') {
const edge = {
// id 遵循 sourcePort-targetPort
id: makeEdgeId(conn.id, cell.id),
source: conn.cell.id,
sourcePort: conn.id,
target: cell.id,
targetPort: connection.id
};
edges.push(edge);
} else {
const edge = {
id: makeEdgeId(cell.id, conn.id),
source: cell.id,
sourcePort: connection.id,
target: conn.id,
targetPort: conn.cell.id
};
edges.push(edge);
}
} }
} }
} }

View File

@ -47,12 +47,10 @@ export class PortRender {
portSelections.append("text") portSelections.append("text")
.attr("x", d => d.width / 2) // 文本的 x 坐标(居中) .attr("x", d => d.width / 2)
.attr("y", d => d.height / 2) // 文本的 y 坐标(居中) .attr("y", d => d.height / 2)
.attr("text-anchor", "middle") // 文本水平居中 .text(d => d.text)
.attr("dominant-baseline", "middle") // 文本垂直居中 .attr('font-size', '12px');
.text(d => d.text) // 设置文本内容
.attr("fill", "black"); // 文本颜色
if (globalSetting.renderAnimation) { if (globalSetting.renderAnimation) {
portSelections = portSelections portSelections = portSelections

View File

@ -1,29 +1,3 @@
/**
* @type {Record<string, string>} yosys 器件映射
* - key: 器件的 type比如 $_AND_
* - value: 器件对应的我们内部的名称用于检索皮肤系统的
*/
export const CELL_LIBS = {
"$_AND_": "and",
"$_XOR_": "xor",
"$_NOT_": "not",
"$_OR_": "or",
"$_NAND_": "nand",
"$_NOR_": "nor",
"$_XNOR_": "xnor",
"$_MUX_": "mux",
"$_AOI3_": "aoi3",
"$_OAI3_": "oai3",
"$_AOI4_": "aoi4",
"$_OAI4_": "oai4",
"$_DFF_P_": "dff_pos",
"$_DFF_N_": "dff_neg",
"$_SR_NN_": "sr_nn",
"$_SR_NP_": "sr_np",
"$_SR_PN_": "sr_pn",
"$_SR_PP_": "sr_pp"
};
/** /**
* @description 模块树对象直接作为 treeview 的渲染视图加入运算 * @description 模块树对象直接作为 treeview 的渲染视图加入运算
* 相比于 YosysNetModule, ModuleTree 只关心和渲染有关的那部分变量且不可被序列化 * 相比于 YosysNetModule, ModuleTree 只关心和渲染有关的那部分变量且不可被序列化

View File

@ -18,19 +18,11 @@ function windowsKeydown(element, event) {
if (event.ctrlKey) { if (event.ctrlKey) {
if (deltaY < 0) { if (deltaY < 0) {
// scale up // scale up
const nextScale = globalLookup.svgScale + 0.1;
if (nextScale <= MAX_SCALE) {
globalLookup.svgScale = nextScale;
globalLookup.netlistRender.updateLocationFromGlobal();
}
} else if (deltaY > 0) { } else if (deltaY > 0) {
// scale down // scale down
const nextScale = globalLookup.svgScale - 0.1;
if (nextScale >= MIN_SCALE) {
globalLookup.svgScale = nextScale;
globalLookup.netlistRender.updateLocationFromGlobal();
}
} }
event.preventDefault(); event.preventDefault();
@ -39,28 +31,20 @@ function windowsKeydown(element, event) {
} else if (deltaX !== 0 && deltaY === 0) { } else if (deltaX !== 0 && deltaY === 0) {
if (deltaX > 0) { if (deltaX > 0) {
// scroll left // scroll left
const translateX = globalLookup.svgTranslateX - 50;
globalLookup.svgTranslateX = translateX;
globalLookup.netlistRender.updateLocationFromGlobal();
} else { } else {
// scroll right // scroll right
const translateX = globalLookup.svgTranslateX + 50;
globalLookup.svgTranslateX = translateX;
globalLookup.netlistRender.updateLocationFromGlobal();
} }
event.preventDefault(); event.preventDefault();
} else if (deltaX === 0 && deltaY !== 0) { } else if (deltaX === 0 && deltaY !== 0) {
if (deltaY > 0) { if (deltaY > 0) {
// scroll up // scroll up
const translateY = globalLookup.svgTranslateY - 50;
globalLookup.svgTranslateY = translateY;
globalLookup.netlistRender.updateLocationFromGlobal();
} else { } else {
// scroll down // scroll down
const translateY = globalLookup.svgTranslateY + 50;
globalLookup.svgTranslateY = translateY;
globalLookup.netlistRender.updateLocationFromGlobal();
} }
event.preventDefault(); event.preventDefault();