1537 lines
50 KiB
JavaScript
1537 lines
50 KiB
JavaScript
|
// Size of canvas
|
||
|
var width;
|
||
|
var height;
|
||
|
var listenToSimulator=true; //enables key down listener on the simulator
|
||
|
|
||
|
var createNode=false //Flag to create node when its value ==true
|
||
|
var stopWire=true //flag for stopoing making Nodes when the second terminal reaches a Node (closed path)
|
||
|
|
||
|
uniqueIdCounter = 0; // To be deprecated
|
||
|
unit = 10; // size of each division/ not used everywhere, to be deprecated
|
||
|
|
||
|
wireToBeChecked = 0; // when node disconnects from another node
|
||
|
willBeUpdated = false; // scheduleUpdate() will be called if true
|
||
|
objectSelection = false; // Flag for object selection
|
||
|
errorDetected = false; // Flag for error detection
|
||
|
|
||
|
prevErrorMessage = undefined; // Global variable for error messages
|
||
|
prevShowMessage = undefined; // Global variable for error messages
|
||
|
|
||
|
updatePosition = true; // Flag for updating position
|
||
|
updateSimulation = true; // Flag for updating simulation
|
||
|
updateCanvas = true; // Flag for rendering
|
||
|
|
||
|
gridUpdate = true; // Flag for updating grid
|
||
|
updateSubcircuit = true; // Flag for updating subCircuits
|
||
|
|
||
|
loading = false; // Flag - all assets are loaded
|
||
|
|
||
|
DPR = 1; // devicePixelRatio, 2 for retina displays, 1 for low resolution displays
|
||
|
|
||
|
projectSaved = true; // Flag for project saved or not
|
||
|
|
||
|
lightMode = false; // To be deprecated
|
||
|
|
||
|
layoutMode = false; // Flag for mode
|
||
|
|
||
|
forceResetNodes = true; // FLag to reset all Nodes
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
//Exact same name as object constructor
|
||
|
//This list needs to be updated when new circuitselements are created
|
||
|
|
||
|
|
||
|
function setupElementLists() {
|
||
|
|
||
|
$('#menu').empty();
|
||
|
|
||
|
window.circuitElementList = metadata.circuitElementList;
|
||
|
window.annotationList = metadata.annotationList;
|
||
|
window.inputList = metadata.inputList;
|
||
|
window.subCircuitInputList = metadata.subCircuitInputList;
|
||
|
window.moduleList = [...circuitElementList, ...annotationList]
|
||
|
window.updateOrder = ["wires", ...circuitElementList, "nodes", ...annotationList]; // Order of update
|
||
|
window.renderOrder = [...(moduleList.slice().reverse()), "wires", "allNodes"]; // Order of render
|
||
|
|
||
|
|
||
|
function createIcon(element) {
|
||
|
return `<div class="icon logixModules pointerCursor" id="${element}" >
|
||
|
<img src= "/img/${element}.svg" >
|
||
|
<p class="img__description">${element}</p>
|
||
|
</div>`;
|
||
|
}
|
||
|
|
||
|
let elementHierarchy = metadata.elementHierarchy;
|
||
|
for (category in elementHierarchy) {
|
||
|
let htmlIcons = '';
|
||
|
|
||
|
let categoryData = elementHierarchy[category];
|
||
|
|
||
|
for (let i = 0; i < categoryData.length; i++) {
|
||
|
let element = categoryData[i];
|
||
|
htmlIcons += createIcon(element);
|
||
|
}
|
||
|
|
||
|
let accordionData = `<div class="panelHeader">${category}</div>
|
||
|
<div class="panel" style="overflow-y:hidden;">
|
||
|
${htmlIcons}
|
||
|
</div>`;
|
||
|
|
||
|
$('#menu').append(accordionData);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
// setupElementLists()
|
||
|
|
||
|
|
||
|
// circuitElementList = [
|
||
|
// "Input", "Output", "NotGate", "OrGate", "AndGate", "NorGate", "NandGate", "XorGate", "XnorGate", "SevenSegDisplay", "SixteenSegDisplay", "HexDisplay",
|
||
|
// "Multiplexer", "BitSelector", "Splitter", "Power", "Ground", "ConstantVal", "ControlledInverter", "TriState", "Adder", "Rom", "RAM", "EEPROM", "TflipFlop",
|
||
|
// "JKflipFlop", "SRflipFlop", "DflipFlop", "TTY", "Keyboard", "Clock", "DigitalLed", "Stepper", "VariableLed", "RGBLed", "SquareRGBLed", "RGBLedMatrix", "Button", "Demultiplexer",
|
||
|
// "Buffer", "SubCircuit", "Flag", "MSB", "LSB", "PriorityEncoder", "Tunnel", "ALU", "Decoder", "Random", "Counter", "Dlatch", "TB_Input", "TB_Output", "ForceGate",
|
||
|
// ];
|
||
|
|
||
|
// annotationList = ["Text", "Rectangle", "Arrow"]
|
||
|
|
||
|
// moduleList = [...circuitElementList, ...annotationList]
|
||
|
// updateOrder = ["wires", ...circuitElementList, "nodes", ...annotationList]; // Order of update
|
||
|
// renderOrder = [...(moduleList.slice().reverse()), "wires", "allNodes"]; // Order of render
|
||
|
|
||
|
// // Exact same name as object constructor
|
||
|
// // All the combinational modules which give rise to an value(independently)
|
||
|
|
||
|
// inputList = ["Random","Buffer", "Stepper", "Ground", "Power", "ConstantVal", "Input", "Clock", "Button","Dlatch","JKflipFlop","TflipFlop","SRflipFlop","DflipFlop"];
|
||
|
// subCircuitInputList=["Clock", "Button","Buffer", "Stepper", "Ground", "Power", "ConstantVal","Dlatch","JKflipFlop","TflipFlop","SRflipFlop","DflipFlop"]
|
||
|
|
||
|
// inputList = ["Random", "Dlatch", "JKflipFlop", "TflipFlop", "SRflipFlop", "DflipFlop", "Buffer", "Stepper", "Ground", "Power", "ConstantVal", "Input", "Clock", "Button", "Counter"];
|
||
|
// subCircuitInputList = ["Random", "Dlatch", "JKflipFlop", "TflipFlop", "SRflipFlop", "DflipFlop", "Buffer", "Stepper", "Ground", "Power", "ConstantVal", "Clock", "Button", "Counter"];
|
||
|
|
||
|
//Scope object for each circuit level, globalScope for outer level
|
||
|
scopeList = {};
|
||
|
|
||
|
|
||
|
// Helper function to show error
|
||
|
function showError(error) {
|
||
|
errorDetected = true;
|
||
|
if (error == prevErrorMessage) return;
|
||
|
prevErrorMessage = error;
|
||
|
var id = Math.floor(Math.random() * 10000);
|
||
|
$('#MessageDiv').append("<div class='alert alert-danger' role='alert' id='" + id + "'> " + error + "</div>");
|
||
|
setTimeout(function() {
|
||
|
prevErrorMessage = undefined;
|
||
|
$('#' + id).fadeOut();
|
||
|
}, 1500);
|
||
|
}
|
||
|
|
||
|
function showRestricted() {
|
||
|
$('#restrictedDiv').removeClass("display--none");
|
||
|
// Show no help text for restricted elements
|
||
|
$("#Help").removeClass("show");
|
||
|
$('#restrictedDiv').html("The element has been restricted by mentor. Usage might lead to deduction in marks");
|
||
|
}
|
||
|
|
||
|
function hideRestricted() {
|
||
|
$('#restrictedDiv').addClass("display--none");
|
||
|
}
|
||
|
|
||
|
function updateRestrictedElementsList() {
|
||
|
if (restrictedElements.length === 0) return;
|
||
|
|
||
|
const restrictedCircuitElementsUsed = globalScope.restrictedCircuitElementsUsed;
|
||
|
let restrictedStr = "";
|
||
|
|
||
|
restrictedCircuitElementsUsed.forEach((element) => {
|
||
|
restrictedStr += `${element}, `;
|
||
|
});
|
||
|
|
||
|
if (restrictedStr === "") {
|
||
|
restrictedStr = "None";
|
||
|
} else {
|
||
|
restrictedStr = restrictedStr.slice(0, -2);
|
||
|
}
|
||
|
|
||
|
$("#restrictedElementsDiv--list").html(restrictedStr);
|
||
|
}
|
||
|
|
||
|
|
||
|
function updateRestrictedElementsInScope(scope = globalScope) {
|
||
|
// Do nothing if no restricted elements
|
||
|
if (restrictedElements.length === 0) return;
|
||
|
|
||
|
let restrictedElementsUsed = [];
|
||
|
restrictedElements.forEach((element) => {
|
||
|
if (scope[element].length > 0) {
|
||
|
restrictedElementsUsed.push(element);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
scope.restrictedCircuitElementsUsed = restrictedElementsUsed;
|
||
|
updateRestrictedElementsList();
|
||
|
}
|
||
|
|
||
|
// Helper function to show message
|
||
|
function showMessage(mes) {
|
||
|
if (mes == prevShowMessage) return;
|
||
|
prevShowMessage = mes
|
||
|
var id = Math.floor(Math.random() * 10000);
|
||
|
$('#MessageDiv').append("<div class='alert alert-success' role='alert' id='" + id + "'> " + mes + "</div>");
|
||
|
setTimeout(function() {
|
||
|
prevShowMessage = undefined;
|
||
|
$('#' + id).fadeOut()
|
||
|
}, 2500);
|
||
|
}
|
||
|
|
||
|
// Helper function to open a new tab
|
||
|
function openInNewTab(url) {
|
||
|
var win = window.open(url, '_blank');
|
||
|
win.focus();
|
||
|
}
|
||
|
|
||
|
// Following function need to be improved - remove mutability etc
|
||
|
//fn to remove elem in array
|
||
|
Array.prototype.clean = function(deleteValue) {
|
||
|
for (var i = 0; i < this.length; i++) {
|
||
|
if (this[i] == deleteValue) {
|
||
|
this.splice(i, 1);
|
||
|
i--;
|
||
|
}
|
||
|
}
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
// Following function need to be improved
|
||
|
Array.prototype.extend = function(other_array) {
|
||
|
/* you should include a test to check whether other_array really is an array */
|
||
|
other_array.forEach(function(v) {
|
||
|
this.push(v)
|
||
|
}, this);
|
||
|
}
|
||
|
|
||
|
// Following function need to be improved
|
||
|
//fn to check if an elem is in an array
|
||
|
Array.prototype.contains = function(value) {
|
||
|
return this.indexOf(value) > -1
|
||
|
};
|
||
|
|
||
|
// Helper function to return unique list
|
||
|
function uniq(a) {
|
||
|
var seen = {};
|
||
|
return a.filter(function(item) {
|
||
|
return seen.hasOwnProperty(item) ? false : (seen[item] = true);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
// Currently Focussed circuit/scope
|
||
|
globalScope = undefined;
|
||
|
|
||
|
// Base circuit class
|
||
|
// All circuits are stored in a scope
|
||
|
|
||
|
function Scope(name = "localScope", id = undefined) {
|
||
|
this.restrictedCircuitElementsUsed = [];
|
||
|
this.id = id || Math.floor((Math.random() * 100000000000) + 1);
|
||
|
this.CircuitElement = [];
|
||
|
|
||
|
//root object for referring to main canvas - intermediate node uses this
|
||
|
this.root = new CircuitElement(0, 0, this, "RIGHT", 1);
|
||
|
this.backups = [];
|
||
|
this.timeStamp = new Date().getTime();
|
||
|
|
||
|
this.ox = 0;
|
||
|
this.oy = 0;
|
||
|
this.scale = DPR;
|
||
|
this.tunnelList = {};
|
||
|
this.stack = []
|
||
|
|
||
|
this.name = name;
|
||
|
this.pending = []
|
||
|
this.nodes = []; //intermediate nodes only
|
||
|
this.allNodes = [];
|
||
|
this.wires = [];
|
||
|
|
||
|
// Creating arrays for other module elements
|
||
|
for (var i = 0; i < moduleList.length; i++) {
|
||
|
this[moduleList[i]] = [];
|
||
|
}
|
||
|
|
||
|
// Setting default layout
|
||
|
this.layout = { // default position
|
||
|
width: 100,
|
||
|
height: 40,
|
||
|
title_x: 50,
|
||
|
title_y: 13,
|
||
|
titleEnabled: true,
|
||
|
}
|
||
|
|
||
|
|
||
|
// FOR SOME UNKNOWN REASON, MAKING THE COPY OF THE LIST COMMON
|
||
|
// TO ALL SCOPES EITHER BY PROTOTYPE OR JUST BY REFERNCE IS CAUSING ISSUES
|
||
|
// The issue comes regarding copy/paste operation, after 5-6 operations it becomes slow for unknown reasons
|
||
|
// CHANGE/ REMOVE WITH CAUTION
|
||
|
// this.objects = ["wires", ...circuitElementList, "nodes", ...annotationList];
|
||
|
// this.renderObjectOrder = [ ...(moduleList.slice().reverse()), "wires", "allNodes"];
|
||
|
}
|
||
|
|
||
|
// Resets all nodes recursively
|
||
|
Scope.prototype.reset = function() {
|
||
|
for (var i = 0; i < this.allNodes.length; i++)
|
||
|
this.allNodes[i].reset();
|
||
|
for (var i = 0; i < this.Splitter.length; i++) {
|
||
|
this.Splitter[i].reset();
|
||
|
}
|
||
|
for (var i = 0; i < this.SubCircuit.length; i++) {
|
||
|
this.SubCircuit[i].reset();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// Adds all inputs to simulationQueue
|
||
|
Scope.prototype.addInputs = function() {
|
||
|
for (var i = 0; i < inputList.length; i++) {
|
||
|
for (var j = 0; j < this[inputList[i]].length; j++) {
|
||
|
simulationArea.simulationQueue.add(this[inputList[i]][j], 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (let j = 0; j < this.SubCircuit.length; j++)
|
||
|
this.SubCircuit[j].addInputs();
|
||
|
|
||
|
}
|
||
|
|
||
|
// Ticks clocks recursively -- needs to be deprecated and synchronize all clocks with a global clock
|
||
|
Scope.prototype.clockTick = function() {
|
||
|
for (var i = 0; i < this.Clock.length; i++)
|
||
|
this.Clock[i].toggleState(); //tick clock!
|
||
|
for (var i = 0; i < this.SubCircuit.length; i++)
|
||
|
this.SubCircuit[i].localScope.clockTick(); //tick clock!
|
||
|
}
|
||
|
|
||
|
// Checks if this circuit contains directly or indirectly scope with id
|
||
|
// Recursive nature
|
||
|
Scope.prototype.checkDependency = function(id) {
|
||
|
if (id == this.id) return true;
|
||
|
for (var i = 0; i < this.SubCircuit.length; i++)
|
||
|
if (this.SubCircuit[i].id == id) return true;
|
||
|
|
||
|
for (var i = 0; i < this.SubCircuit.length; i++)
|
||
|
if (scopeList[this.SubCircuit[i].id].checkDependency(id)) return true;
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// Get dependency list - list of all circuits, this circuit depends on
|
||
|
Scope.prototype.getDependencies = function() {
|
||
|
var list = []
|
||
|
for (var i = 0; i < this.SubCircuit.length; i++) {
|
||
|
list.push(this.SubCircuit[i].id);
|
||
|
list.extend(scopeList[this.SubCircuit[i].id].getDependencies());
|
||
|
}
|
||
|
return uniq(list);
|
||
|
}
|
||
|
|
||
|
// helper function to reduce layout size
|
||
|
Scope.prototype.fixLayout = function() {
|
||
|
var max_y = 20;
|
||
|
for (var i = 0; i < this.Input.length; i++)
|
||
|
max_y = Math.max(this.Input[i].layoutProperties.y, max_y)
|
||
|
for (var i = 0; i < this.Output.length; i++)
|
||
|
max_y = Math.max(this.Output[i].layoutProperties.y, max_y)
|
||
|
if (max_y != this.layout.height)
|
||
|
this.layout.height = max_y + 10;
|
||
|
}
|
||
|
|
||
|
// Function which centers the circuit to the correct zoom level
|
||
|
Scope.prototype.centerFocus = function(zoomIn = true) {
|
||
|
if (layoutMode) return;
|
||
|
findDimensions(this);
|
||
|
var minX = simulationArea.minWidth || 0;
|
||
|
var minY = simulationArea.minHeight || 0;
|
||
|
var maxX = simulationArea.maxWidth || 0;
|
||
|
var maxY = simulationArea.maxHeight || 0;
|
||
|
|
||
|
var reqWidth = maxX - minX + 150;
|
||
|
var reqHeight = maxY - minY + 150;
|
||
|
|
||
|
this.scale = Math.min(width / reqWidth, height / reqHeight)
|
||
|
|
||
|
if (!zoomIn)
|
||
|
this.scale = Math.min(this.scale, DPR);
|
||
|
this.scale = Math.max(this.scale, DPR / 10);
|
||
|
|
||
|
this.ox = (-minX) * this.scale + (width - (maxX - minX) * this.scale) / 2;
|
||
|
this.oy = (-minY) * this.scale + (height - (maxY - minY) * this.scale) / 2;
|
||
|
}
|
||
|
|
||
|
//fn to setup environment
|
||
|
function setupEnvironment() {
|
||
|
|
||
|
projectId = generateId();
|
||
|
updateSimulation = true;
|
||
|
DPR = window.devicePixelRatio || 1;
|
||
|
newCircuit("Main");
|
||
|
|
||
|
data = {}
|
||
|
resetup();
|
||
|
}
|
||
|
|
||
|
|
||
|
function setup() {
|
||
|
|
||
|
setupElementLists();
|
||
|
setupEnvironment();
|
||
|
if (!embed)
|
||
|
setupUI();
|
||
|
startListeners();
|
||
|
|
||
|
// Load project data after 1 second - needs to be improved, delay needs to be eliminated
|
||
|
setTimeout(function() {
|
||
|
if (logix_project_id != 0) {
|
||
|
$('.loadingIcon').fadeIn();
|
||
|
$.ajax({
|
||
|
url: '/simulator/get_data',
|
||
|
type: 'POST',
|
||
|
beforeSend: function(xhr) {
|
||
|
xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))
|
||
|
},
|
||
|
data: {
|
||
|
"id": logix_project_id
|
||
|
},
|
||
|
success: function(response) {
|
||
|
data = (response);
|
||
|
|
||
|
if (data) {
|
||
|
load(data);
|
||
|
simulationArea.changeClockTime(data["timePeriod"] || 500);
|
||
|
}
|
||
|
$('.loadingIcon').fadeOut();
|
||
|
},
|
||
|
failure: function() {
|
||
|
alert("Error: could not load ");
|
||
|
$('.loadingIcon').fadeOut();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
}
|
||
|
|
||
|
// Restore unsaved data and save
|
||
|
else if (localStorage.getItem("recover_login") && userSignedIn) {
|
||
|
var data = JSON.parse(localStorage.getItem("recover_login"));
|
||
|
load(data);
|
||
|
localStorage.removeItem("recover");
|
||
|
localStorage.removeItem("recover_login");
|
||
|
save();
|
||
|
}
|
||
|
|
||
|
// Restore unsaved data which didn't get saved due to error
|
||
|
else if (localStorage.getItem("recover")) {
|
||
|
showMessage("We have detected that you did not save your last work. Don't worry we have recovered them. Access them using Project->Recover")
|
||
|
}
|
||
|
}, 1000);
|
||
|
|
||
|
|
||
|
}
|
||
|
|
||
|
// Helper function to recover unsaved data
|
||
|
function recoverProject() {
|
||
|
if (localStorage.getItem("recover")) {
|
||
|
var data = JSON.parse(localStorage.getItem("recover"));
|
||
|
if (confirm("Would you like to recover: " + data.name)) {
|
||
|
load(data);
|
||
|
}
|
||
|
localStorage.removeItem("recover");
|
||
|
} else {
|
||
|
showError("No recover project found")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Toggle light mode
|
||
|
function changeLightMode(val) {
|
||
|
if (!val && lightMode) {
|
||
|
lightMode = false;
|
||
|
DPR = window.devicePixelRatio || 1;
|
||
|
globalScope.scale *= DPR;
|
||
|
} else if (val && !lightMode) {
|
||
|
lightMode = true;
|
||
|
globalScope.scale /= DPR;
|
||
|
DPR = 1
|
||
|
$('#miniMap').fadeOut('fast');
|
||
|
}
|
||
|
resetup();
|
||
|
}
|
||
|
|
||
|
//to resize window and setup things
|
||
|
function resetup() {
|
||
|
|
||
|
DPR = window.devicePixelRatio || 1;
|
||
|
if (lightMode)
|
||
|
DPR = 1;
|
||
|
width = document.getElementById("simulationArea").clientWidth * DPR;
|
||
|
if (!embed) {
|
||
|
height = (document.getElementById("simulation").clientHeight - document.getElementById("toolbar").clientHeight) * DPR;
|
||
|
} else {
|
||
|
height = (document.getElementById("simulation").clientHeight) * DPR;
|
||
|
}
|
||
|
|
||
|
//setup simulationArea
|
||
|
backgroundArea.setup();
|
||
|
if (!embed) plotArea.setup();
|
||
|
simulationArea.setup();
|
||
|
|
||
|
// update();
|
||
|
dots();
|
||
|
|
||
|
document.getElementById("backgroundArea").style.height = height / DPR + 100;
|
||
|
document.getElementById("backgroundArea").style.width = width / DPR + 100;
|
||
|
document.getElementById("canvasArea").style.height = height / DPR;
|
||
|
simulationArea.canvas.width = width;
|
||
|
simulationArea.canvas.height = height;
|
||
|
backgroundArea.canvas.width = width + 100 * DPR;
|
||
|
backgroundArea.canvas.height = height + 100 * DPR;
|
||
|
if (!embed) {
|
||
|
plotArea.c.width = document.getElementById("plot").clientWidth;
|
||
|
plotArea.c.height = document.getElementById("plot").clientHeight
|
||
|
}
|
||
|
|
||
|
updateCanvas = true;
|
||
|
update(); // INEFFICIENT, needs to be deprecated
|
||
|
simulationArea.prevScale = 0;
|
||
|
dots(true, false);
|
||
|
}
|
||
|
|
||
|
window.onresize = resetup; // listener
|
||
|
window.onorientationchange = resetup; // listener
|
||
|
|
||
|
//for mobiles
|
||
|
window.addEventListener('orientationchange', resetup); // listener
|
||
|
|
||
|
// Object that holds data and objects of grid canvas
|
||
|
var backgroundArea = {
|
||
|
canvas: document.getElementById("backgroundArea"),
|
||
|
setup: function() {
|
||
|
this.canvas.width = width;
|
||
|
this.canvas.height = height;
|
||
|
this.context = this.canvas.getContext("2d");
|
||
|
dots(true, false);
|
||
|
},
|
||
|
clear: function() {
|
||
|
if (!this.context) return;
|
||
|
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//simulation environment object - holds simulation canvas
|
||
|
var simulationArea = {
|
||
|
canvas: document.getElementById("simulationArea"),
|
||
|
selected: false,
|
||
|
hover: false,
|
||
|
clockState: 0,
|
||
|
clockEnabled: true,
|
||
|
lastSelected: undefined,
|
||
|
stack: [],
|
||
|
prevScale: 0,
|
||
|
oldx: 0,
|
||
|
oldy: 0,
|
||
|
objectList: [],
|
||
|
maxHeight: 0,
|
||
|
maxWidth: 0,
|
||
|
minHeight: 0,
|
||
|
minWidth: 0,
|
||
|
multipleObjectSelections: [],
|
||
|
copyList: [],
|
||
|
shiftDown: false,
|
||
|
controlDown: false,
|
||
|
timePeriod: 500,
|
||
|
mouseX: 0,
|
||
|
mouseY: 0,
|
||
|
mouseDownX: 0,
|
||
|
mouseDownY: 0,
|
||
|
simulationQueue: undefined,
|
||
|
|
||
|
clickCount: 0, //double click
|
||
|
lock: "unlocked",
|
||
|
timer: function() {
|
||
|
ckickTimer = setTimeout(function() {
|
||
|
simulationArea.clickCount = 0;
|
||
|
}, 600);
|
||
|
},
|
||
|
|
||
|
setup: function() {
|
||
|
this.canvas.width = width;
|
||
|
this.canvas.height = height;
|
||
|
this.simulationQueue = new EventQueue(10000);
|
||
|
this.context = this.canvas.getContext("2d");
|
||
|
simulationArea.changeClockTime(simulationArea.timePeriod)
|
||
|
this.mouseDown = false;
|
||
|
},
|
||
|
changeClockTime: function(t) {
|
||
|
if (t < 50) return;
|
||
|
clearInterval(simulationArea.ClockInterval);
|
||
|
t = t || prompt("Enter Time Period:");
|
||
|
simulationArea.timePeriod = t;
|
||
|
simulationArea.ClockInterval = setInterval(clockTick, t);
|
||
|
},
|
||
|
clear: function() {
|
||
|
if (!this.context) return;
|
||
|
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||
|
}
|
||
|
}
|
||
|
changeClockTime = simulationArea.changeClockTime
|
||
|
|
||
|
// Kept for archival purposes - needs to be removed
|
||
|
//
|
||
|
// function copyPaste(copyList) {
|
||
|
// if(copyList.length==0)return;
|
||
|
// tempScope = new Scope(globalScope.name,globalScope.id);
|
||
|
// var oldOx=globalScope.ox;
|
||
|
// var oldOy=globalScope.oy;
|
||
|
// var oldScale=globalScope.scale;
|
||
|
// d = backUp(globalScope);
|
||
|
// loadScope(tempScope, d);
|
||
|
// scopeList[tempScope.id]=tempScope;
|
||
|
// tempScope.backups=globalScope.backups;
|
||
|
// for (var i = 0; i < updateOrder.length; i++){
|
||
|
// var prevLength=globalScope[updateOrder[i]].length; // LOL length of list will reduce automatically when deletion starts
|
||
|
// // if(globalScope[updateOrder[i]].length)////console.log("deleting, ",globalScope[updateOrder[i]]);
|
||
|
// for (var j = 0; j < globalScope[updateOrder[i]].length; j++) {
|
||
|
// var obj = globalScope[updateOrder[i]][j];
|
||
|
// if (obj.objectType != 'Wire') { //}&&obj.objectType!='CircuitElement'){//}&&(obj.objectType!='Node'||obj.type==2)){
|
||
|
// if (!copyList.contains(globalScope[updateOrder[i]][j])) {
|
||
|
// ////console.log("DELETE:", globalScope[updateOrder[i]][j]);
|
||
|
// globalScope[updateOrder[i]][j].cleanDelete();
|
||
|
// }
|
||
|
// }
|
||
|
//
|
||
|
// if(globalScope[updateOrder[i]].length!=prevLength){
|
||
|
// prevLength--;
|
||
|
// j--;
|
||
|
// }
|
||
|
// }
|
||
|
// }
|
||
|
//
|
||
|
// // updateSimulation = true;
|
||
|
// // update(globalScope);
|
||
|
// ////console.log("DEBUG1",globalScope.wires.length)
|
||
|
// var prevLength=globalScope.wires.length;
|
||
|
// for (var i = 0; i < globalScope.wires.length; i++) {
|
||
|
// globalScope.wires[i].checkConnections();
|
||
|
// if(globalScope.wires.length!=prevLength){
|
||
|
// prevLength--;
|
||
|
// i--;
|
||
|
// }
|
||
|
// }
|
||
|
// ////console.log(globalScope.wires,globalScope.allNodes)
|
||
|
// ////console.log("DEBUG2",globalScope.wires.length)
|
||
|
// // update(globalScope);
|
||
|
// // ////console.log(globalScope.wires.length)
|
||
|
//
|
||
|
// var approxX=0;
|
||
|
// var approxY=0;
|
||
|
// for (var i = 0; i < copyList.length; i++) {
|
||
|
// approxX+=copyList[i].x;
|
||
|
// approxY+=copyList[i].y;
|
||
|
// }
|
||
|
// approxX/=copyList.length;
|
||
|
// approxY/=copyList.length;
|
||
|
//
|
||
|
// approxX=Math.round(approxX/10)*10
|
||
|
// approxY=Math.round(approxY/10)*10
|
||
|
// for (var i = 0; i < updateOrder.length; i++)
|
||
|
// for (var j = 0; j < globalScope[updateOrder[i]].length; j++) {
|
||
|
// var obj = globalScope[updateOrder[i]][j];
|
||
|
// obj.updateScope(tempScope);
|
||
|
// }
|
||
|
//
|
||
|
//
|
||
|
// for (var i = 0; i < copyList.length; i++) {
|
||
|
// // ////console.log(copyList[i]);
|
||
|
// copyList[i].x += simulationArea.mouseX-approxX;
|
||
|
// copyList[i].y += simulationArea.mouseY-approxY;
|
||
|
//
|
||
|
// }
|
||
|
//
|
||
|
//
|
||
|
// // for (var i = 0; i < globalScope.wires.length; i++) {
|
||
|
// // globalScope.wires[i].updateScope(tempScope);
|
||
|
// // }
|
||
|
//
|
||
|
// for (l in globalScope) {
|
||
|
// if (globalScope[l] instanceof Array && l != "objects") {
|
||
|
// tempScope[l].extend(globalScope[l]);
|
||
|
// // ////console.log("Copying , ",l);
|
||
|
// }
|
||
|
// }
|
||
|
//
|
||
|
// // update(tempScope);
|
||
|
//
|
||
|
//
|
||
|
// simulationArea.multipleObjectSelections = [];//copyList.slice();
|
||
|
// simulationArea.copyList = [];//copyList.slice();
|
||
|
// canvasUpdate=true;
|
||
|
// updateSimulation = true;
|
||
|
// globalScope = tempScope;
|
||
|
// scheduleUpdate();
|
||
|
// globalScope.ox=oldOx;
|
||
|
// globalScope.oy=oldOy;
|
||
|
// globalScope.scale=oldScale;
|
||
|
//
|
||
|
// // }
|
||
|
|
||
|
// paste function
|
||
|
function paste(copyData) {
|
||
|
|
||
|
if (copyData == undefined) return;
|
||
|
var data = JSON.parse(copyData);
|
||
|
if (!data["logixClipBoardData"]) return;
|
||
|
|
||
|
var currentScopeId = globalScope.id;
|
||
|
for (var i = 0; i < data.scopes.length; i++) {
|
||
|
if (scopeList[data.scopes[i].id] == undefined) {
|
||
|
var scope = newCircuit(data.scopes[i].name, data.scopes[i].id);
|
||
|
loadScope(scope, data.scopes[i]);
|
||
|
scopeList[data.scopes[i].id] = scope;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
switchCircuit(currentScopeId);
|
||
|
var tempScope = new Scope(globalScope.name, globalScope.id);
|
||
|
var oldOx = globalScope.ox;
|
||
|
var oldOy = globalScope.oy;
|
||
|
var oldScale = globalScope.scale;
|
||
|
loadScope(tempScope, data);
|
||
|
|
||
|
var prevLength = tempScope.allNodes.length
|
||
|
for (var i = 0; i < tempScope.allNodes.length; i++) {
|
||
|
tempScope.allNodes[i].checkDeleted();
|
||
|
if (tempScope.allNodes.length != prevLength) {
|
||
|
prevLength--;
|
||
|
i--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var approxX = 0;
|
||
|
var approxY = 0;
|
||
|
var count = 0
|
||
|
|
||
|
for (var i = 0; i < updateOrder.length; i++) {
|
||
|
for (var j = 0; j < tempScope[updateOrder[i]].length; j++) {
|
||
|
var obj = tempScope[updateOrder[i]][j];
|
||
|
obj.updateScope(globalScope);
|
||
|
if (obj.objectType != "Wire") {
|
||
|
approxX += obj.x;
|
||
|
approxY += obj.y;
|
||
|
count++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (var j = 0; j < tempScope.CircuitElement.length; j++) {
|
||
|
var obj = tempScope.CircuitElement[j]
|
||
|
obj.updateScope(globalScope);
|
||
|
}
|
||
|
|
||
|
approxX /= count
|
||
|
approxY /= count
|
||
|
|
||
|
approxX = Math.round(approxX / 10) * 10
|
||
|
approxY = Math.round(approxY / 10) * 10
|
||
|
|
||
|
|
||
|
for (var i = 0; i < updateOrder.length; i++) {
|
||
|
for (var j = 0; j < tempScope[updateOrder[i]].length; j++) {
|
||
|
var obj = tempScope[updateOrder[i]][j];
|
||
|
if (obj.objectType != "Wire") {
|
||
|
obj.x += simulationArea.mouseX - approxX;
|
||
|
obj.y += simulationArea.mouseY - approxY;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (l in tempScope) {
|
||
|
if (tempScope[l] instanceof Array && l != "objects" && l != "CircuitElement") {
|
||
|
globalScope[l].extend(tempScope[l]);
|
||
|
}
|
||
|
}
|
||
|
for (var i = 0; i < tempScope.Input.length; i++) {
|
||
|
tempScope.Input[i].layoutProperties.y = get_next_position(0, globalScope);
|
||
|
tempScope.Input[i].layoutProperties.id = generateId();
|
||
|
}
|
||
|
for (var i = 0; i < tempScope.Output.length; i++) {
|
||
|
tempScope.Output[i].layoutProperties.x = globalScope.layout.width;
|
||
|
tempScope.Output[i].layoutProperties.id = generateId();
|
||
|
tempScope.Output[i].layoutProperties.y = get_next_position(globalScope.layout.width, globalScope);
|
||
|
}
|
||
|
|
||
|
canvasUpdate = true;
|
||
|
updateSimulation = true;
|
||
|
updateSubcircuit = true;
|
||
|
scheduleUpdate();
|
||
|
globalScope.ox = oldOx;
|
||
|
globalScope.oy = oldOy;
|
||
|
globalScope.scale = oldScale;
|
||
|
|
||
|
|
||
|
forceResetNodes = true
|
||
|
}
|
||
|
|
||
|
function cut(copyList) {
|
||
|
if (copyList.length == 0) return;
|
||
|
var tempScope = new Scope(globalScope.name, globalScope.id);
|
||
|
var oldOx = globalScope.ox;
|
||
|
var oldOy = globalScope.oy;
|
||
|
var oldScale = globalScope.scale;
|
||
|
d = backUp(globalScope);
|
||
|
loadScope(tempScope, d);
|
||
|
scopeList[tempScope.id] = tempScope;
|
||
|
|
||
|
for (var i = 0; i < copyList.length; i++) {
|
||
|
var obj = copyList[i];
|
||
|
if (obj.objectType == "Node") obj.objectType = "allNodes"
|
||
|
for (var j = 0; j < tempScope[obj.objectType].length; j++) {
|
||
|
if (tempScope[obj.objectType][j].x == obj.x && tempScope[obj.objectType][j].y == obj.y && (obj.objectType != "Node" || obj.type == 2)) {
|
||
|
tempScope[obj.objectType][j].delete();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
tempScope.backups = globalScope.backups;
|
||
|
for (var i = 0; i < updateOrder.length; i++) {
|
||
|
var prevLength = globalScope[updateOrder[i]].length; // LOL length of list will reduce automatically when deletion starts
|
||
|
for (var j = 0; j < globalScope[updateOrder[i]].length; j++) {
|
||
|
var obj = globalScope[updateOrder[i]][j];
|
||
|
if (obj.objectType != 'Wire') { //}&&obj.objectType!='CircuitElement'){//}&&(obj.objectType!='Node'||obj.type==2)){
|
||
|
if (!copyList.contains(globalScope[updateOrder[i]][j])) {
|
||
|
globalScope[updateOrder[i]][j].cleanDelete();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (globalScope[updateOrder[i]].length != prevLength) {
|
||
|
prevLength--;
|
||
|
j--;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var prevLength = globalScope.wires.length;
|
||
|
for (var i = 0; i < globalScope.wires.length; i++) {
|
||
|
globalScope.wires[i].checkConnections();
|
||
|
if (globalScope.wires.length != prevLength) {
|
||
|
prevLength--;
|
||
|
i--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
updateSimulation = true;
|
||
|
|
||
|
var data = backUp(globalScope);
|
||
|
data['logixClipBoardData'] = true;
|
||
|
var dependencyList = globalScope.getDependencies();
|
||
|
data["dependencies"] = {};
|
||
|
for (dependency in dependencyList)
|
||
|
data.dependencies[dependency] = backUp(scopeList[dependency]);
|
||
|
data['logixClipBoardData'] = true;
|
||
|
data = JSON.stringify(data);
|
||
|
|
||
|
simulationArea.multipleObjectSelections = []; //copyList.slice();
|
||
|
simulationArea.copyList = []; //copyList.slice();
|
||
|
canvasUpdate = true;
|
||
|
updateSimulation = true;
|
||
|
globalScope = tempScope;
|
||
|
scheduleUpdate();
|
||
|
globalScope.ox = oldOx;
|
||
|
globalScope.oy = oldOy;
|
||
|
globalScope.scale = oldScale;
|
||
|
forceResetNodes = true
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
function copy(copyList, cut = false) {
|
||
|
|
||
|
if (copyList.length == 0) return;
|
||
|
var tempScope = new Scope(globalScope.name, globalScope.id);
|
||
|
var oldOx = globalScope.ox;
|
||
|
var oldOy = globalScope.oy;
|
||
|
var oldScale = globalScope.scale;
|
||
|
var d = backUp(globalScope);
|
||
|
|
||
|
loadScope(tempScope, d);
|
||
|
scopeList[tempScope.id] = tempScope;
|
||
|
|
||
|
if (cut) {
|
||
|
for (var i = 0; i < copyList.length; i++) {
|
||
|
var obj = copyList[i];
|
||
|
if (obj.objectType == "Node") obj.objectType = "allNodes"
|
||
|
for (var j = 0; j < tempScope[obj.objectType].length; j++) {
|
||
|
if (tempScope[obj.objectType][j].x == obj.x && tempScope[obj.objectType][j].y == obj.y && (obj.objectType != "Node" || obj.type == 2)) {
|
||
|
tempScope[obj.objectType][j].delete();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
tempScope.backups = globalScope.backups;
|
||
|
for (var i = 0; i < updateOrder.length; i++) {
|
||
|
var prevLength = globalScope[updateOrder[i]].length; // LOL length of list will reduce automatically when deletion starts
|
||
|
for (var j = 0; j < globalScope[updateOrder[i]].length; j++) {
|
||
|
var obj = globalScope[updateOrder[i]][j];
|
||
|
if (obj.objectType != 'Wire') { //}&&obj.objectType!='CircuitElement'){//}&&(obj.objectType!='Node'||obj.type==2)){
|
||
|
if (!copyList.contains(globalScope[updateOrder[i]][j])) {
|
||
|
////console.log("DELETE:", globalScope[updateOrder[i]][j]);
|
||
|
globalScope[updateOrder[i]][j].cleanDelete();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (globalScope[updateOrder[i]].length != prevLength) {
|
||
|
prevLength--;
|
||
|
j--;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var prevLength = globalScope.wires.length;
|
||
|
for (var i = 0; i < globalScope.wires.length; i++) {
|
||
|
globalScope.wires[i].checkConnections();
|
||
|
if (globalScope.wires.length != prevLength) {
|
||
|
prevLength--;
|
||
|
i--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
updateSimulation = true;
|
||
|
|
||
|
var data = backUp(globalScope);
|
||
|
data.scopes = []
|
||
|
var dependencyList = {};
|
||
|
var requiredDependencies = globalScope.getDependencies();
|
||
|
var completed = {};
|
||
|
for (id in scopeList)
|
||
|
dependencyList[id] = scopeList[id].getDependencies();
|
||
|
|
||
|
function saveScope(id) {
|
||
|
|
||
|
if (completed[id]) return;
|
||
|
for (var i = 0; i < dependencyList[id].length; i++)
|
||
|
saveScope(dependencyList[id][i]);
|
||
|
completed[id] = true;
|
||
|
data.scopes.push(backUp(scopeList[id]));
|
||
|
|
||
|
}
|
||
|
|
||
|
for (var i = 0; i < requiredDependencies.length; i++)
|
||
|
saveScope(requiredDependencies[i]);
|
||
|
|
||
|
data['logixClipBoardData'] = true;
|
||
|
data = JSON.stringify(data);
|
||
|
|
||
|
simulationArea.multipleObjectSelections = []; //copyList.slice();
|
||
|
simulationArea.copyList = []; //copyList.slice();
|
||
|
canvasUpdate = true;
|
||
|
updateSimulation = true;
|
||
|
globalScope = tempScope;
|
||
|
scheduleUpdate();
|
||
|
globalScope.ox = oldOx;
|
||
|
globalScope.oy = oldOy;
|
||
|
globalScope.scale = oldScale;
|
||
|
forceResetNodes = true
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
// Function selects all the elements from the scope
|
||
|
function selectAll(scope = globalScope) {
|
||
|
circuitElementList.forEach((val, _, __) => {
|
||
|
if (scope.hasOwnProperty(val)) {
|
||
|
simulationArea.multipleObjectSelections.push(...scope[val]);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
if (scope.nodes) {
|
||
|
simulationArea.multipleObjectSelections.push(...scope.nodes);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// The Circuit element class serves as the abstract class for all circuit elements.
|
||
|
// Data Members: /* Insert Description */
|
||
|
// Prototype Methods:
|
||
|
// - update: Used to update the state of object on mouse applicationCache
|
||
|
// - isHover: Used to check if mouse is hovering over object
|
||
|
|
||
|
|
||
|
function CircuitElement(x, y, scope, dir, bitWidth) {
|
||
|
// Data member initializations
|
||
|
this.objectType = this.constructor.name;
|
||
|
this.x = x;
|
||
|
this.y = y;
|
||
|
|
||
|
this.hover = false;
|
||
|
if (this.x == undefined || this.y == undefined) {
|
||
|
this.x = simulationArea.mouseX;
|
||
|
this.y = simulationArea.mouseY;
|
||
|
this.newElement = true;
|
||
|
this.hover = true;
|
||
|
}
|
||
|
|
||
|
this.deleteNodesWhenDeleted = true; // FOR NOW - TO CHECK LATER
|
||
|
|
||
|
this.nodeList = []
|
||
|
this.clicked = false;
|
||
|
|
||
|
this.oldx = x;
|
||
|
this.oldy = y;
|
||
|
|
||
|
/**
|
||
|
The following attributes help in setting the touch area bound. They are the distances from the center.
|
||
|
Note they are all positive distances from center. They will automatically be rotated when direction is changed.
|
||
|
To stop the rotation when direction is changed, check overrideDirectionRotation attribute.
|
||
|
**/
|
||
|
this.leftDimensionX = 10;
|
||
|
this.rightDimensionX = 10;
|
||
|
this.upDimensionY = 10;
|
||
|
this.downDimensionY = 10;
|
||
|
|
||
|
this.rectangleObject = true;
|
||
|
this.label = "";
|
||
|
this.scope = scope;
|
||
|
|
||
|
this.scope[this.objectType].push(this);
|
||
|
|
||
|
this.bitWidth = bitWidth || parseInt(prompt("Enter bitWidth"), 10) || 1;
|
||
|
this.direction = dir;
|
||
|
this.directionFixed = false;
|
||
|
this.labelDirectionFixed = false;
|
||
|
this.labelDirection = oppositeDirection[dir];
|
||
|
this.orientationFixed = true;
|
||
|
this.fixedBitWidth = false;
|
||
|
|
||
|
scheduleUpdate();
|
||
|
|
||
|
this.queueProperties = {
|
||
|
inQueue: false,
|
||
|
time: undefined,
|
||
|
index: undefined,
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
CircuitElement.prototype.alwaysResolve = false
|
||
|
CircuitElement.prototype.propagationDelay = 100
|
||
|
CircuitElement.prototype.flipBits = function(val) {
|
||
|
return ((~val >>> 0) << (32 - this.bitWidth)) >>> (32 - this.bitWidth);
|
||
|
}
|
||
|
CircuitElement.prototype.absX = function() {
|
||
|
return this.x;
|
||
|
}
|
||
|
CircuitElement.prototype.absY = function() {
|
||
|
return this.y;
|
||
|
}
|
||
|
CircuitElement.prototype.copyFrom = function(obj) {
|
||
|
var properties = ["label", "labelDirection"];
|
||
|
for (var i = 0; i < properties.length; i++) {
|
||
|
if (obj[properties[i]] !== undefined)
|
||
|
this[properties[i]] = obj[properties[i]];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Methods to be Implemented for derivedClass
|
||
|
saveObject(); //To generate JSON-safe data that can be loaded
|
||
|
customDraw(); //This is to draw the custom design of the circuit(Optional)
|
||
|
resolve(); // To execute digital logic(Optional)
|
||
|
override isResolvable(); // custom logic for checking if module is ready
|
||
|
override newDirection(dir) //To implement custom direction logic(Optional)
|
||
|
newOrientation(dir) //To implement custom orientation logic(Optional)
|
||
|
*/
|
||
|
|
||
|
// Method definitions
|
||
|
|
||
|
CircuitElement.prototype.updateScope = function(scope) {
|
||
|
this.scope = scope;
|
||
|
for (var i = 0; i < this.nodeList.length; i++)
|
||
|
this.nodeList[i].scope = scope;
|
||
|
}
|
||
|
|
||
|
CircuitElement.prototype.saveObject = function() {
|
||
|
var data = {
|
||
|
x: this.x,
|
||
|
y: this.y,
|
||
|
objectType: this.objectType,
|
||
|
label: this.label,
|
||
|
direction: this.direction,
|
||
|
labelDirection: this.labelDirection,
|
||
|
propagationDelay: this.propagationDelay,
|
||
|
customData: this.customSave()
|
||
|
}
|
||
|
return data;
|
||
|
|
||
|
}
|
||
|
CircuitElement.prototype.customSave = function() {
|
||
|
return {
|
||
|
values: {},
|
||
|
nodes: {},
|
||
|
constructorParamaters: [],
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CircuitElement.prototype.checkHover = function() {
|
||
|
|
||
|
if (simulationArea.mouseDown) return;
|
||
|
for (var i = 0; i < this.nodeList.length; i++) {
|
||
|
this.nodeList[i].checkHover();
|
||
|
}
|
||
|
if (!simulationArea.mouseDown) {
|
||
|
if (simulationArea.hover == this) {
|
||
|
this.hover = this.isHover();
|
||
|
if (!this.hover) simulationArea.hover = undefined;
|
||
|
} else if (!simulationArea.hover) {
|
||
|
this.hover = this.isHover();
|
||
|
if (this.hover) simulationArea.hover = this;
|
||
|
} else {
|
||
|
this.hover = false
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//This sets the width and height of the element if its rectangular
|
||
|
// and the reference point is at the center of the object.
|
||
|
//width and height define the X and Y distance from the center.
|
||
|
//Effectively HALF the actual width and height.
|
||
|
// NOT OVERRIDABLE
|
||
|
CircuitElement.prototype.setDimensions = function(width, height) {
|
||
|
this.leftDimensionX = this.rightDimensionX = width;
|
||
|
this.downDimensionY = this.upDimensionY = height;
|
||
|
}
|
||
|
CircuitElement.prototype.setWidth = function(width) {
|
||
|
this.leftDimensionX = this.rightDimensionX = width;
|
||
|
}
|
||
|
CircuitElement.prototype.setHeight = function(height) {
|
||
|
this.downDimensionY = this.upDimensionY = height;
|
||
|
}
|
||
|
|
||
|
// The update method is used to change the parameters of the object on mouse click and hover.
|
||
|
// Return Value: true if state has changed else false
|
||
|
// NOT OVERRIDABLE
|
||
|
|
||
|
// When true this.isHover() will not rotate bounds. To be used when bounds are set manually.
|
||
|
CircuitElement.prototype.overrideDirectionRotation = false;
|
||
|
|
||
|
CircuitElement.prototype.startDragging = function() {
|
||
|
this.oldx = this.x;
|
||
|
this.oldy = this.y;
|
||
|
}
|
||
|
CircuitElement.prototype.drag = function() {
|
||
|
this.x = this.oldx + simulationArea.mouseX - simulationArea.mouseDownX;
|
||
|
this.y = this.oldy + simulationArea.mouseY - simulationArea.mouseDownY;
|
||
|
}
|
||
|
CircuitElement.prototype.update = function() {
|
||
|
|
||
|
var update = false;
|
||
|
|
||
|
update |= this.newElement;
|
||
|
if (this.newElement) {
|
||
|
if (this.centerElement) {
|
||
|
this.x = Math.round((simulationArea.mouseX - (this.rightDimensionX - this.leftDimensionX) / 2) / 10) * 10;
|
||
|
this.y = Math.round((simulationArea.mouseY - (this.downDimensionY - this.upDimensionY) / 2) / 10) * 10;
|
||
|
} else {
|
||
|
this.x = simulationArea.mouseX;
|
||
|
this.y = simulationArea.mouseY;
|
||
|
}
|
||
|
|
||
|
if (simulationArea.mouseDown) {
|
||
|
this.newElement = false;
|
||
|
simulationArea.lastSelected = this;
|
||
|
} else return;
|
||
|
}
|
||
|
|
||
|
for (var i = 0; i < this.nodeList.length; i++) {
|
||
|
update |= this.nodeList[i].update();
|
||
|
}
|
||
|
|
||
|
if (!simulationArea.hover || simulationArea.hover == this)
|
||
|
this.hover = this.isHover();
|
||
|
|
||
|
if (!simulationArea.mouseDown) this.hover = false;
|
||
|
|
||
|
|
||
|
if ((this.clicked || !simulationArea.hover) && this.isHover()) {
|
||
|
this.hover = true;
|
||
|
simulationArea.hover = this;
|
||
|
} else if (!simulationArea.mouseDown && this.hover && this.isHover() == false) {
|
||
|
if (this.hover) simulationArea.hover = undefined;
|
||
|
this.hover = false;
|
||
|
}
|
||
|
|
||
|
if (simulationArea.mouseDown && (this.clicked)) {
|
||
|
|
||
|
this.drag();
|
||
|
if (!simulationArea.shiftDown && simulationArea.multipleObjectSelections.contains(this)) {
|
||
|
for (var i = 0; i < simulationArea.multipleObjectSelections.length; i++) {
|
||
|
simulationArea.multipleObjectSelections[i].drag();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
update |= true;
|
||
|
} else if (simulationArea.mouseDown && !simulationArea.selected) {
|
||
|
|
||
|
this.startDragging();
|
||
|
if (!simulationArea.shiftDown && simulationArea.multipleObjectSelections.contains(this)) {
|
||
|
for (var i = 0; i < simulationArea.multipleObjectSelections.length; i++) {
|
||
|
simulationArea.multipleObjectSelections[i].startDragging();
|
||
|
}
|
||
|
}
|
||
|
simulationArea.selected = this.clicked = this.hover;
|
||
|
|
||
|
update |= this.clicked;
|
||
|
} else {
|
||
|
if (this.clicked) simulationArea.selected = false;
|
||
|
this.clicked = false;
|
||
|
this.wasClicked = false;
|
||
|
}
|
||
|
|
||
|
if (simulationArea.mouseDown && !this.wasClicked) {
|
||
|
if (this.clicked) {
|
||
|
this.wasClicked = true;
|
||
|
if (this.click) this.click();
|
||
|
if (simulationArea.shiftDown) {
|
||
|
simulationArea.lastSelected = undefined;
|
||
|
if (simulationArea.multipleObjectSelections.contains(this)) {
|
||
|
simulationArea.multipleObjectSelections.clean(this);
|
||
|
} else {
|
||
|
simulationArea.multipleObjectSelections.push(this);
|
||
|
}
|
||
|
} else {
|
||
|
simulationArea.lastSelected = this;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return update;
|
||
|
}
|
||
|
|
||
|
CircuitElement.prototype.fixDirection = function() {
|
||
|
this.direction = fixDirection[this.direction] || this.direction;
|
||
|
this.labelDirection = fixDirection[this.labelDirection] || this.labelDirection;
|
||
|
}
|
||
|
|
||
|
// The isHover method is used to check if the mouse is hovering over the object.
|
||
|
// Return Value: true if mouse is hovering over object else false
|
||
|
// NOT OVERRIDABLE
|
||
|
CircuitElement.prototype.isHover = function() {
|
||
|
|
||
|
var mX = simulationArea.mouseXf - this.x;
|
||
|
var mY = this.y - simulationArea.mouseYf;
|
||
|
|
||
|
var rX = this.rightDimensionX;
|
||
|
var lX = this.leftDimensionX;
|
||
|
var uY = this.upDimensionY;
|
||
|
var dY = this.downDimensionY;
|
||
|
|
||
|
if (!this.directionFixed && !this.overrideDirectionRotation) {
|
||
|
if (this.direction == "LEFT") {
|
||
|
lX = this.rightDimensionX;
|
||
|
rX = this.leftDimensionX
|
||
|
} else if (this.direction == "DOWN") {
|
||
|
lX = this.downDimensionY;
|
||
|
rX = this.upDimensionY;
|
||
|
uY = this.leftDimensionX;
|
||
|
dY = this.rightDimensionX;
|
||
|
} else if (this.direction == "UP") {
|
||
|
lX = this.downDimensionY;
|
||
|
rX = this.upDimensionY;
|
||
|
dY = this.leftDimensionX;
|
||
|
uY = this.rightDimensionX;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return -lX <= mX && mX <= rX && -dY <= mY && mY <= uY;
|
||
|
};
|
||
|
|
||
|
CircuitElement.prototype.setLabel = function(label) {
|
||
|
this.label = label || ""
|
||
|
}
|
||
|
|
||
|
CircuitElement.prototype.propagationDelayFixed = false;
|
||
|
|
||
|
//Method that draws the outline of the module and calls draw function on module Nodes.
|
||
|
//NOT OVERRIDABLE
|
||
|
CircuitElement.prototype.draw = function() {
|
||
|
var ctx = simulationArea.context;
|
||
|
this.checkHover();
|
||
|
if (this.x * this.scope.scale + this.scope.ox < -this.rightDimensionX * this.scope.scale - 00 || this.x * this.scope.scale + this.scope.ox > width + this.leftDimensionX * this.scope.scale + 00 || this.y * this.scope.scale + this.scope.oy < -this.downDimensionY * this.scope.scale - 00 || this.y * this.scope.scale + this.scope.oy > height + 00 + this.upDimensionY * this.scope.scale) return;
|
||
|
|
||
|
// Draws rectangle and highlights
|
||
|
if (this.rectangleObject) {
|
||
|
ctx.strokeStyle = "black";
|
||
|
ctx.fillStyle = "white";
|
||
|
ctx.lineWidth = correctWidth(3);
|
||
|
ctx.beginPath();
|
||
|
rect2(ctx, -this.leftDimensionX, -this.upDimensionY, this.leftDimensionX + this.rightDimensionX, this.upDimensionY + this.downDimensionY, this.x, this.y, [this.direction, "RIGHT"][+this.directionFixed]);
|
||
|
if ((this.hover && !simulationArea.shiftDown) || simulationArea.lastSelected == this || simulationArea.multipleObjectSelections.contains(this)) ctx.fillStyle = "rgba(255, 255, 32,0.8)";
|
||
|
ctx.fill();
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
if (this.label != "") {
|
||
|
var rX = this.rightDimensionX;
|
||
|
var lX = this.leftDimensionX;
|
||
|
var uY = this.upDimensionY;
|
||
|
var dY = this.downDimensionY;
|
||
|
if (!this.directionFixed) {
|
||
|
if (this.direction == "LEFT") {
|
||
|
lX = this.rightDimensionX;
|
||
|
rX = this.leftDimensionX
|
||
|
} else if (this.direction == "DOWN") {
|
||
|
lX = this.downDimensionY;
|
||
|
rX = this.upDimensionY;
|
||
|
uY = this.leftDimensionX;
|
||
|
dY = this.rightDimensionX;
|
||
|
} else if (this.direction == "UP") {
|
||
|
lX = this.downDimensionY;
|
||
|
rX = this.upDimensionY;
|
||
|
dY = this.leftDimensionX;
|
||
|
uY = this.rightDimensionX;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (this.labelDirection == "LEFT") {
|
||
|
ctx.beginPath();
|
||
|
ctx.textAlign = "right";
|
||
|
ctx.fillStyle = "black";
|
||
|
fillText(ctx, this.label, this.x - lX - 10, this.y + 5, 14);
|
||
|
ctx.fill();
|
||
|
} else if (this.labelDirection == "RIGHT") {
|
||
|
ctx.beginPath();
|
||
|
ctx.textAlign = "left";
|
||
|
ctx.fillStyle = "black";
|
||
|
fillText(ctx, this.label, this.x + rX + 10, this.y + 5, 14);
|
||
|
ctx.fill();
|
||
|
} else if (this.labelDirection == "UP") {
|
||
|
ctx.beginPath();
|
||
|
ctx.textAlign = "center";
|
||
|
ctx.fillStyle = "black";
|
||
|
fillText(ctx, this.label, this.x, this.y + 5 - uY - 10, 14);
|
||
|
ctx.fill();
|
||
|
} else if (this.labelDirection == "DOWN") {
|
||
|
ctx.beginPath();
|
||
|
ctx.textAlign = "center";
|
||
|
ctx.fillStyle = "black";
|
||
|
fillText(ctx, this.label, this.x, this.y + 5 + dY + 10, 14);
|
||
|
ctx.fill();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// calls the custom circuit design
|
||
|
if (this.customDraw)
|
||
|
this.customDraw();
|
||
|
|
||
|
//draws nodes - Moved to renderCanvas
|
||
|
// for (var i = 0; i < this.nodeList.length; i++)
|
||
|
// this.nodeList[i].draw();
|
||
|
}
|
||
|
|
||
|
//method to delete object
|
||
|
//OVERRIDE WITH CAUTION
|
||
|
CircuitElement.prototype.delete = function() {
|
||
|
simulationArea.lastSelected = undefined;
|
||
|
this.scope[this.objectType].clean(this); // CHECK IF THIS IS VALID
|
||
|
if (this.deleteNodesWhenDeleted)
|
||
|
this.deleteNodes();
|
||
|
else
|
||
|
for (var i = 0; i < this.nodeList.length; i++)
|
||
|
if (this.nodeList[i].connections.length)
|
||
|
this.nodeList[i].converToIntermediate();
|
||
|
else
|
||
|
this.nodeList[i].delete();
|
||
|
this.deleted = true;
|
||
|
}
|
||
|
|
||
|
CircuitElement.prototype.cleanDelete = function() {
|
||
|
this.deleteNodesWhenDeleted = true;
|
||
|
this.delete();
|
||
|
}
|
||
|
|
||
|
CircuitElement.prototype.deleteNodes = function() {
|
||
|
for (var i = 0; i < this.nodeList.length; i++)
|
||
|
this.nodeList[i].delete();
|
||
|
}
|
||
|
|
||
|
//method to change direction
|
||
|
//OVERRIDE WITH CAUTION
|
||
|
CircuitElement.prototype.newDirection = function(dir) {
|
||
|
if (this.direction == dir) return;
|
||
|
// Leave this for now
|
||
|
if (this.directionFixed && this.orientationFixed) return;
|
||
|
else if (this.directionFixed) {
|
||
|
this.newOrientation(dir);
|
||
|
return; // Should it return ?
|
||
|
}
|
||
|
|
||
|
// if (obj.direction == undefined) return;
|
||
|
this.direction = dir;
|
||
|
for (var i = 0; i < this.nodeList.length; i++) {
|
||
|
this.nodeList[i].refresh();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
CircuitElement.prototype.newLabelDirection = function(dir) {
|
||
|
this.labelDirection = dir;
|
||
|
}
|
||
|
|
||
|
//Method to check if object can be resolved
|
||
|
//OVERRIDE if necessary
|
||
|
CircuitElement.prototype.isResolvable = function() {
|
||
|
if (this.alwaysResolve) return true;
|
||
|
for (var i = 0; i < this.nodeList.length; i++)
|
||
|
if (this.nodeList[i].type == 0 && this.nodeList[i].value == undefined) return false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//Method to change object Bitwidth
|
||
|
//OVERRIDE if necessary
|
||
|
CircuitElement.prototype.newBitWidth = function(bitWidth) {
|
||
|
if (this.fixedBitWidth) return;
|
||
|
if (this.bitWidth == undefined) return;
|
||
|
if (this.bitWidth < 1) return;
|
||
|
this.bitWidth = bitWidth;
|
||
|
for (var i = 0; i < this.nodeList.length; i++)
|
||
|
this.nodeList[i].bitWidth = bitWidth;
|
||
|
}
|
||
|
|
||
|
//Method to change object delay
|
||
|
//OVERRIDE if necessary
|
||
|
CircuitElement.prototype.changePropagationDelay = function(delay) {
|
||
|
if (this.propagationDelayFixed) return;
|
||
|
if (delay == undefined) return;
|
||
|
if (delay == "") return;
|
||
|
delay = parseInt(delay, 10)
|
||
|
if (delay < 0) return;
|
||
|
this.propagationDelay = delay;
|
||
|
}
|
||
|
|
||
|
//Dummy resolve function
|
||
|
//OVERRIDE if necessary
|
||
|
CircuitElement.prototype.resolve = function() {
|
||
|
|
||
|
}
|
||
|
|
||
|
// Graph algorithm to resolve verilog wire labels
|
||
|
CircuitElement.prototype.processVerilog = function() {
|
||
|
// Output count used to sanitize output
|
||
|
var output_total = 0;
|
||
|
for (var i = 0; i < this.nodeList.length; i++) {
|
||
|
if (this.nodeList[i].type == NODE_OUTPUT && this.nodeList[i].connections.length > 0)
|
||
|
output_total++;
|
||
|
}
|
||
|
|
||
|
var output_count = 0;
|
||
|
for (var i = 0; i < this.nodeList.length; i++) {
|
||
|
if (this.nodeList[i].type == NODE_OUTPUT) {
|
||
|
if (this.objectType != "Input" && this.objectType != "Clock" && this.nodeList[i].connections.length > 0) {
|
||
|
this.nodeList[i].verilogLabel =
|
||
|
verilog.generateNodeName(this.nodeList[i], output_count, output_total);
|
||
|
|
||
|
if (!this.scope.verilogWireList[this.nodeList[i].bitWidth].contains(this.nodeList[i].verilogLabel))
|
||
|
this.scope.verilogWireList[this.nodeList[i].bitWidth].push(this.nodeList[i].verilogLabel);
|
||
|
output_count++;
|
||
|
}
|
||
|
this.scope.stack.push(this.nodeList[i]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CircuitElement.prototype.removePropagation = function() {
|
||
|
for (var i = 0; i < this.nodeList.length; i++) {
|
||
|
if (this.nodeList[i].type == NODE_OUTPUT) {
|
||
|
if (this.nodeList[i].value !== undefined) {
|
||
|
this.nodeList[i].value = undefined;
|
||
|
simulationArea.simulationQueue.add(this.nodeList[i]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CircuitElement.prototype.verilogName = function() {
|
||
|
return this.verilogType || this.objectType;
|
||
|
}
|
||
|
|
||
|
CircuitElement.prototype.verilogBaseType = function() {
|
||
|
return this.verilogName();
|
||
|
}
|
||
|
|
||
|
CircuitElement.prototype.verilogParametrizedType = function() {
|
||
|
var type = this.verilogBaseType();
|
||
|
// Suffix bitwidth for multi-bit inputs
|
||
|
// Example: DflipFlop #(2) DflipFlop_0
|
||
|
if (this.bitWidth != undefined && this.bitWidth > 1)
|
||
|
type += " #(" + this.bitWidth + ")";
|
||
|
return type
|
||
|
}
|
||
|
|
||
|
// Generates final verilog code for each element
|
||
|
CircuitElement.prototype.generateVerilog = function() {
|
||
|
// Example: and and_1(_out, _out, _Q[0]);
|
||
|
var inputs = [];
|
||
|
var outputs = [];
|
||
|
|
||
|
for (var i = 0; i < this.nodeList.length; i++) {
|
||
|
if (this.nodeList[i].type == NODE_INPUT) {
|
||
|
inputs.push(this.nodeList[i]);
|
||
|
} else {
|
||
|
if (this.nodeList[i].connections.length > 0)
|
||
|
outputs.push(this.nodeList[i]);
|
||
|
else
|
||
|
outputs.push(""); // Don't create a wire
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var list = outputs.concat(inputs);
|
||
|
var res = this.verilogParametrizedType();
|
||
|
var moduleParams = list.map(x => x.verilogLabel).join(", ");
|
||
|
res += ` ${this.verilogLabel}(${moduleParams});`;
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
// Generates final verilog code for each element
|
||
|
// Gate = &/|/^
|
||
|
// Invert is true for xNor, Nor, Nand
|
||
|
function gateGenerateVerilog(gate, invert = false) {
|
||
|
var inputs = [];
|
||
|
var outputs = [];
|
||
|
|
||
|
for (var i = 0; i < this.nodeList.length; i++) {
|
||
|
if (this.nodeList[i].type == NODE_INPUT) {
|
||
|
inputs.push(this.nodeList[i]);
|
||
|
} else {
|
||
|
if (this.nodeList[i].connections.length > 0)
|
||
|
outputs.push(this.nodeList[i]);
|
||
|
else
|
||
|
outputs.push(""); // Don't create a wire
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var res = "assign ";
|
||
|
if (outputs.length == 1)
|
||
|
res += outputs[0].verilogLabel;
|
||
|
else
|
||
|
res += `{${outputs.map(x => x.verilogLabel).join(", ")}}`;
|
||
|
|
||
|
res += " = ";
|
||
|
|
||
|
var inputParams = inputs.map(x => x.verilogLabel).join(` ${gate} `);
|
||
|
if(invert) {
|
||
|
res += `~(${inputParams});`;
|
||
|
}
|
||
|
else {
|
||
|
res += inputParams + ';';
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
function distance(x1, y1, x2, y2) {
|
||
|
return Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2));
|
||
|
}
|