CircuitVerse/CircuitVerse

View on GitHub
simulator/src/layoutMode.js

Summary

Maintainability
F
1 wk
Test Coverage
/* eslint-disable import/no-cycle */
/* eslint-disable no-continue */
import {
    dots, correctWidth, fillText, rect2,
} from './canvasApi';
import LayoutBuffer from './layout/layoutBuffer';
import simulationArea from './simulationArea';
import { hideProperties, fillSubcircuitElements, prevPropertyObjGet, prevPropertyObjSet, showProperties } from './ux';
import {
    update, scheduleUpdate, willBeUpdatedSet, gridUpdateSet, gridUpdateGet
} from './engine';
import miniMapArea from './minimap';
import { showMessage } from './utils';
import * as metadata from './metadata.json';
import { verilogModeGet, verilogModeSet } from './Verilog2CV';

/**
 * Layout.js - all subcircuit layout related code is here
 * You can edit how your subcircuit for a circuit will look by
 * clicking edit layout in properties for a ciruit
 * @category layoutMode
 */

var layoutMode = false;

export function layoutModeSet(param) {
    layoutMode = param;
}

export function layoutModeGet(param) {
    return layoutMode;
}

/**
 * @type {LayoutBuffer} - used to temporartily store all changes.
 * @category layoutMode
 */
export var tempBuffer;

/**
 * Helper function to determine alignment and position of nodes for rendering
 * @param {number} x - width of label
 * @param {number} y - height of label
 * @category layoutMode
 */
export function determineLabel(x, y) {
    if (x === 0) return ['left', 5, 5];
    if (x === tempBuffer.layout.width) return ['right', -5, 5];
    if (y === 0) return ['center', 0, 13];
    return ['center', 0, -6];
}

/**
 * Used to move the grid in the layout mode
 * @param {Scope} scope - the circuit whose subcircuit we are editing
 * @category layoutMode
 */
export function paneLayout(scope = globalScope) {
    if (!simulationArea.selected && simulationArea.mouseDown) {
        simulationArea.selected = true;
        simulationArea.lastSelected = scope.root;
        simulationArea.hover = scope.root;
    } else if (simulationArea.lastSelected === scope.root && simulationArea.mouseDown) {
        // pane canvas
        if (true) {
            globalScope.ox = (simulationArea.mouseRawX - simulationArea.mouseDownRawX) + simulationArea.oldx;
            globalScope.oy = (simulationArea.mouseRawY - simulationArea.mouseDownRawY) + simulationArea.oldy;
            globalScope.ox = Math.round(globalScope.ox);
            globalScope.oy = Math.round(globalScope.oy);
            gridUpdateSet(true);
            if (!embed && !lightMode) miniMapArea.setup();
        }
    } else if (simulationArea.lastSelected === scope.root) {
        // Select multiple objects

        simulationArea.lastSelected = undefined;
        simulationArea.selected = false;
        simulationArea.hover = undefined;
    }
}


/**
 * Function to render layout on screen in layoutMode
 * @param {Scope=} scope
 * @category layoutMode
 */
export function renderLayout(scope = globalScope) {
    if (!layoutModeGet()) return;
    var ctx = simulationArea.context;
    simulationArea.clear();
    ctx.strokeStyle = 'black';
    ctx.fillStyle = 'white';
    ctx.lineWidth = correctWidth(3);
    // Draw base rectangle
    ctx.beginPath();
    rect2(ctx, 0, 0, tempBuffer.layout.width, tempBuffer.layout.height, 0, 0, "RIGHT");
    ctx.fill();
    ctx.stroke();
    ctx.beginPath();
    ctx.textAlign = 'center';
    ctx.fillStyle = 'black';
    if (tempBuffer.layout.titleEnabled) {
        fillText(ctx, scope.name, tempBuffer.layout.title_x, tempBuffer.layout.title_y, 11);
    }

    // Draw labels
    var info;
    for (let i = 0; i < tempBuffer.Input.length; i++) {
        if (!tempBuffer.Input[i].label) continue;
        info = determineLabel(tempBuffer.Input[i].x, tempBuffer.Input[i].y, scope);
        [ctx.textAlign] = info;
        fillText(ctx, tempBuffer.Input[i].label, tempBuffer.Input[i].x + info[1], tempBuffer.Input[i].y + info[2], 12);
    }
    for (let i = 0; i < tempBuffer.Output.length; i++) {
        if (!tempBuffer.Output[i].label) continue;
        info = determineLabel(tempBuffer.Output[i].x, tempBuffer.Output[i].y, scope);
        [ctx.textAlign] = info;
        fillText(ctx, tempBuffer.Output[i].label, tempBuffer.Output[i].x + info[1], tempBuffer.Output[i].y + info[2], 12);
    }
    ctx.fill();

    // Draw points
    for (let i = 0; i < tempBuffer.Input.length; i++) {
        tempBuffer.Input[i].draw();
    }
    for (let i = 0; i < tempBuffer.Output.length; i++) {
        tempBuffer.Output[i].draw();
    }

    if (gridUpdateGet()) {
        dots();
    }

     // Update UI position
    for(let i = 0; i < tempBuffer.subElements.length; i++){
        tempBuffer.subElements[i].update();

        // element nodes
        for(let j = 0; j < tempBuffer.subElements[i].nodeList.length; j++)
            tempBuffer.subElements[i].nodeList[j].update();
    }

    // Show properties of selected element
    if (!embed && prevPropertyObjGet() != simulationArea.lastSelected) {
        if (simulationArea.lastSelected) {
            showProperties(simulationArea.lastSelected);
        }
    }
    // Render objects
    for(let i = 0; i < circuitElementList.length; i++){
        if(globalScope[circuitElementList[i]].length === 0) continue;
        if (!globalScope[circuitElementList[i]][0].canShowInSubcircuit) continue;

        let elementName = circuitElementList[i];

        for(let j = 0; j < globalScope[elementName].length; j++){
            if (globalScope[elementName][j].subcircuitMetadata.showInSubcircuit) {
                globalScope[elementName][j].drawLayoutMode();
            }
        }
    }
}

/**
 * Update UI, positions of inputs and outputs
 * @param {Scope} scope - the circuit whose subcircuit we are editing
 * @category layoutMode
 */
export function layoutUpdate(scope = globalScope) {
    if (!layoutModeGet()) return;
    willBeUpdatedSet(false);
    for (let i = 0; i < tempBuffer.Input.length; i++) {
        tempBuffer.Input[i].update();
    }
    for (let i = 0; i < tempBuffer.Output.length; i++) {
        tempBuffer.Output[i].update();
    }

    for(let i = 0; i < circuitElementList.length; i++){
        if(globalScope[circuitElementList[i]].length === 0) continue;
        if(!globalScope[circuitElementList[i]][0].canShowInSubcircuit) continue;
        let elementName = circuitElementList[i];

        for(let j = 0; j < globalScope[elementName].length; j++){
            if (globalScope[elementName][j].subcircuitMetadata.showInSubcircuit) {
                globalScope[elementName][j].layoutUpdate();
            }
        }
    }
    paneLayout(scope);
    renderLayout(scope);
}

/**
 * Helper function to reset all nodes to original default positions
 * @category layoutMode
 */
export function layoutResetNodes() {
    tempBuffer.layout.width = 100;
    tempBuffer.layout.height = Math.max(tempBuffer.Input.length, tempBuffer.Output.length) * 20 + 20;
    for (let i = 0; i < tempBuffer.Input.length; i++) {
        tempBuffer.Input[i].x = 0;
        tempBuffer.Input[i].y = i * 20 + 20;
    }
    for (let i = 0; i < tempBuffer.Output.length; i++) {
        tempBuffer.Output[i].x = tempBuffer.layout.width;
        tempBuffer.Output[i].y = i * 20 + 20;
    }
}

/**
 * Increase width, and move all nodes
 * @category layoutMode
 */
export function increaseLayoutWidth() {
    for (let i = 0; i < tempBuffer.Input.length; i++) {
        if (tempBuffer.Input[i].x === tempBuffer.layout.width) { tempBuffer.Input[i].x += 10; }
    }
    for (let i = 0; i < tempBuffer.Output.length; i++) {
        if (tempBuffer.Output[i].x === tempBuffer.layout.width) { tempBuffer.Output[i].x += 10; }
    }
    tempBuffer.layout.width += 10;
}

/**
 * Increase Height, and move all nodes
 * @category layoutMode
 */
export function increaseLayoutHeight() {
    for (let i = 0; i < tempBuffer.Input.length; i++) {
        if (tempBuffer.Input[i].y === tempBuffer.layout.height) { tempBuffer.Input[i].y += 10; }
    }
    for (let i = 0; i < tempBuffer.Output.length; i++) {
        if (tempBuffer.Output[i].y === tempBuffer.layout.height) { tempBuffer.Output[i].y += 10; }
    }
    tempBuffer.layout.height += 10;
}

/**
 * Decrease Width, and move all nodes, check if space is there
 * @category layoutMode
 */
export function decreaseLayoutWidth() {
    if (tempBuffer.layout.width < 30) return;
    for (let i = 0; i < tempBuffer.Input.length; i++) {
        if (tempBuffer.Input[i].x === tempBuffer.layout.width - 10) {
            showMessage('No space. Move or delete some nodes to make space.');
            return;
        }
    }
    for (let i = 0; i < tempBuffer.Output.length; i++) {
        if (tempBuffer.Output[i].x === tempBuffer.layout.width - 10) {
            showMessage('No space. Move or delete some nodes to make space.');
            return;
        }
    }

    for (let i = 0; i < tempBuffer.Input.length; i++) {
        if (tempBuffer.Input[i].x === tempBuffer.layout.width) { tempBuffer.Input[i].x -= 10; }
    }
    for (let i = 0; i < tempBuffer.Output.length; i++) {
        if (tempBuffer.Output[i].x === tempBuffer.layout.width) { tempBuffer.Output[i].x -= 10; }
    }
    tempBuffer.layout.width -= 10;
}

/**
 * Decrease Height, and move all nodes, check if space is there
 * @category layoutMode
 */
export function decreaseLayoutHeight() {
    if (tempBuffer.layout.height < 30) return;
    for (let i = 0; i < tempBuffer.Input.length; i++) {
        if (tempBuffer.Input[i].y === tempBuffer.layout.height - 10) {
            showMessage('No space. Move or delete some nodes to make space.');
            return;
        }
    }
    for (let i = 0; i < tempBuffer.Output.length; i++) {
        if (tempBuffer.Output[i].y === tempBuffer.layout.height - 10) {
            showMessage('No space. Move or delete some nodes to make space.');
            return;
        }
    }

    for (let i = 0; i < tempBuffer.Input.length; i++) {
        if (tempBuffer.Input[i].y === tempBuffer.layout.height) { tempBuffer.Input[i].y -= 10; }
    }
    for (let i = 0; i < tempBuffer.Output.length; i++) {
        if (tempBuffer.Output[i].y === tempBuffer.layout.height) { tempBuffer.Output[i].y -= 10; }
    }
    tempBuffer.layout.height -= 10;
}

/**
 * Helper functions to move the titles
 * @category layoutMode
 */
export function layoutTitleUp() {
    tempBuffer.layout.title_y -= 5;
}

/**
 * Helper functions to move the titles
 * @category layoutMode
 */
export function layoutTitleDown() {
    tempBuffer.layout.title_y += 5;
}

/**
 * Helper functions to move the titles
 * @category layoutMode
 */
export function layoutTitleRight() {
    tempBuffer.layout.title_x += 5;
}

/**
 * Helper functions to move the titles
 * @category layoutMode
 */
export function layoutTitleLeft() {
    tempBuffer.layout.title_x -= 5;
}

/**
 * Helper functions to move the titles
 * @category layoutMode
 */
export function toggleLayoutTitle() {
    tempBuffer.layout.titleEnabled = !tempBuffer.layout.titleEnabled;
}

/**
 * just toggles back to normal mode
 * @category layoutMode
 */
function cancelLayout() {
    if (layoutModeGet()) {
        // eslint-disable-next-line no-use-before-define
        toggleLayoutMode();
    }
}


/**
 * Store all data into layout and exit
 * @category layoutMode
 */
function saveLayout() {
    if (layoutModeGet()) {
        for (let i = 0; i < tempBuffer.Input.length; i++) {
            tempBuffer.Input[i].parent.layoutProperties.x = tempBuffer.Input[i].x;
            tempBuffer.Input[i].parent.layoutProperties.y = tempBuffer.Input[i].y;
        }
        for (let i = 0; i < tempBuffer.Output.length; i++) {
            tempBuffer.Output[i].parent.layoutProperties.x = tempBuffer.Output[i].x;
            tempBuffer.Output[i].parent.layoutProperties.y = tempBuffer.Output[i].y;
        }
        globalScope.layout = { ...tempBuffer.layout };
        // eslint-disable-next-line no-use-before-define
        toggleLayoutMode();
    }
}

/**
 * Function to toggle between layoutMode and normal Mode
 * the sidebar is disabled and n properties are shown.
 * @category layoutMode
 */
export function toggleLayoutMode() {
    hideProperties();
    if (layoutModeGet()) {
        layoutModeSet(false);
        $('#layoutDialog').fadeOut();
        $('.layoutElementPanel').fadeOut();
        $('.elementPanel').fadeIn();
        $('.testbench-manual-panel').fadeIn();
        if (!(window.screen.width > 1000)) {
            $('.ce-panel').fadeOut();
            $('#touchMenu').fadeIn();
            $(' #liveMenu').fadeIn();
            $('#touchElement-property').fadeIn();
        }
        if ((window.screen.width > 1000)) {
            $('.timing-diagram-panel').fadeIn();
        }
        globalScope.centerFocus(false);
        if(globalScope.verilogMetadata.isVerilogCircuit)
            verilogModeSet(true);
        dots();
    } else {
        layoutModeSet(true);
        verilogModeSet(false);
        $('#layoutDialog').fadeIn();
        $('.layoutElementPanel').fadeIn();
        if (!(window.screen.width > 1000)) {
            $('#touchMenu').fadeOut();
            $(' #liveMenu').fadeOut();
            $('#touchElement-property').fadeOut();
        }
        $('.elementPanel').fadeOut();
        $('.timing-diagram-panel').fadeOut();
        $('.testbench-manual-panel').fadeOut();
        fillSubcircuitElements();

        globalScope.ox = 0;
        globalScope.oy = 0;
        globalScope.scale = DPR * 1.3;
        dots();
        tempBuffer = new LayoutBuffer();
        $('#toggleLayoutTitle')[0].checked = tempBuffer.layout.titleEnabled;
    }
    update(globalScope, true);
    scheduleUpdate();
}

export function setupLayoutModePanelListeners() {
    $('#decreaseLayoutWidth').on('click',() => {
        decreaseLayoutWidth();
    });
    $('#increaseLayoutWidth').on('click',() => {
        increaseLayoutWidth();
    });
    $('#decreaseLayoutHeight').on('click',() => {
        decreaseLayoutHeight();
    });
    $('#increaseLayoutHeight').on('click',() => {
        increaseLayoutHeight();
    });
    $('#layoutResetNodes').on('click',() => {
        layoutResetNodes();
    });
    $('#layoutTitleUp').on('click',() => {
        layoutTitleUp();
    });
    $('#layoutTitleDown').on('click',() => {
        layoutTitleDown();
    });
    $('#layoutTitleLeft').on('click',() => {
        layoutTitleLeft();
    });
    $('#layoutTitleRight').on('click',() => {
        layoutTitleRight();
    });
    $('#toggleLayoutTitle').on('click',() => {
        toggleLayoutTitle();
    });
    $('#saveLayout').on('click',() => {
        saveLayout();
    });
    $('#cancelLayout').on('click',() => {
        cancelLayout();
    });
    $('#layoutDialog button').on('click', () => {
        scheduleUpdate();
    });
    $('#layoutDialog input').on('click', () => {
        scheduleUpdate();
    });
}