cirsim
Version:
Cirsim Circuit Simulator
355 lines (289 loc) • 9.42 kB
JavaScript
import {Util} from './Utility/Util';
import {Vector} from './Utility/Vector';
import {Rect} from "./Utility/Rect";
/**
* Base object for connectors (In or Out)
*
* Both In and Out are derived from Connector
* @param component Component this connector is for
* @param x Relative x on the component
* @param y Relative y on the component
* @param len Length in pixels to draw the connector
* @param name Name to draw next to the connector
* @param inv True (optional) if connector has a circle (inverse)
* @constructor
*/
export const Connector = function(component, x, y, len, name, inv) {
this.component = component;
this.x = x;
this.y = y;
this.len = len !== undefined ? len : 16;
this.index = undefined;
this.name = name;
this.inv = inv;
this.value = undefined;
this.touchRange = 8;
this.bus = false;
// If this is a CircuitRef connector, this will be set
// to the ID of the referenced component
this.reference = null;
/// Is this connector a clock? This applies to
/// inputs only. Clocks draw as a triangle instead of a label
this.clock = false;
};
/// Orientation of the input (n, s, e, or w)
/// "w" means the connection is to the left of x,y
Connector.prototype.orientation = "w";
Connector.prototype.single = function() {
return true;
};
Connector.prototype.copyFrom = function(other) {
this.x = other.x;
this.y = other.y;
this.len = other.len;
this.index = other.index;
this.name = other.name;
this.inv = other.inv;
this.value = other.value;
this.touchRange = other.touchRange;
this.bus = other.bus;
this.reference = other.reference;
}
Connector.prototype.get = function() {
return this.value;
}
Connector.prototype.getAsString = function() {
function toValueString(v) {
if(Array.isArray(v)) {
var str = '';
v.forEach(function(v1) {
str = toValueString(v1) + str;
})
return str;
} else {
return v === undefined ? "?" : (v ? "1" : "0")
}
}
return toValueString(this.value);
}
/**
* Parse a string in the bus value format.
* @param str String to parse
* @returns {*} Array as value or null if invalid
*/
Connector.parseBusValue = function(str) {
var value = [];
for(var i=str.length-1; i>=0; i--) {
var char = str.substr(i, 1);
switch(char) {
case '0':
value.push(false);
break;
case '1':
value.push(true);
break;
case '?':
value.push(undefined);
break;
default:
return null;
}
}
return value;
}
/**
* Convert a bus value to a number.
* @param bus The Bus value
* @param limit If set, any computed value is modulus the limit value
*/
Connector.busValueToDecimal = function(bus, limit) {
if(bus === undefined) {
return null;
}
let val = 0;
let pow = 1;
for(let i in bus) {
if(bus[i] === undefined) {
return null;
}
if(bus[i]) {
val += pow;
}
pow *= 2;
}
if(limit !== undefined) {
val %= limit;
}
return val;
}
/**
* Test to see if we have touched this connector
* @param x Mouse X
* @param y Mouse Y
* @return true if touched
*/
Connector.prototype.touch = function(x, y) {
var loc = this.getLoc();
var touchRange = this.touchRange;
if(x >= loc.x - touchRange && x <= loc.x + touchRange &&
y >= loc.y - touchRange && y <= loc.y + touchRange) {
return true;
}
return false;
};
/**
* Get the x,y location of the connection
* @returns Object with x,y
*/
Connector.prototype.getLoc = function() {
switch(this.orientation) {
case 'w':
return new Vector(this.component.x + this.x - this.len, this.component.y + this.y);
case 'n':
return new Vector(this.component.x + this.x, this.component.y + this.y - this.len);
case 's':
return new Vector(this.component.x + this.x, this.component.y + this.y + this.len);
case 'e':
return new Vector(this.component.x + this.x + this.len, this.component.y + this.y);
}
};
/**
* Draw the connector
* @param context The display context
* @param view View object we are drawing in
*/
Connector.prototype.draw = function(context, view) {
let x = this.component.x + this.x;
let y = this.component.y + this.y;
if(this.bus) {
context.lineWidth = 2;
} else {
context.lineWidth = 1;
}
switch(this.orientation) {
case 'e':
context.beginPath();
context.moveTo(x, y + 0.5);
context.lineTo(x + this.len, y + 0.5);
context.fillRect(x + this.len - 1, y - 1, 3, 3);
if(this.component.circuit.circuits.model.main.options.showOutputStates) {
context.font = "11px Times";
context.textAlign = "left";
let value = this.getAsString();
if(value.length > 8) {
value = parseInt(value, 2);
if(isNaN(value)) {
value = "?";
} else {
value = Util.toHex(value, 4);
}
}
context.fillText(value, x+5, y-2);
}
if(this.name !== undefined) {
context.font = "12px Times";
context.textAlign = "right";
context.fillText(this.name, x-3, y+3);
}
context.stroke();
break;
case 'w':
// Left side - to west
context.beginPath();
context.moveTo(x, y + 0.5);
context.lineTo(x - this.len, y + 0.5);
context.fillRect(x - this.len - 1, y - 1, 3, 3);
if(this.clock) {
var clockSize = 7;
context.moveTo(x, y - clockSize);
context.lineTo(x + clockSize, y);
context.lineTo(x, y + clockSize);
}
if(this.name !== undefined) {
context.font = "12px Times";
context.textAlign = "left";
context.fillText(this.name, x+2, y+3);
}
context.stroke();
break;
case 'n':
// Top side, to north
context.beginPath();
context.moveTo(x + 0.5, y);
context.lineTo(x + 0.5, y - this.len);
context.fillRect(x - 1, y - this.len - 1, 3, 3);
if(this.clock) {
var clockSize = 7;
context.moveTo(x - clockSize, y);
context.lineTo(x, y + clockSize);
context.lineTo(x + clockSize, y);
}
context.stroke();
if(this.name !== undefined) {
y += this.clock ? 12 + clockSize : 12;
context.font = "12px Times";
context.textAlign = "center";
context.fillText(this.name, x, y);
}
break;
case 's':
// Bottom side, to sound
context.beginPath();
context.moveTo(x + 0.5, y);
context.lineTo(x + 0.5, y + this.len);
context.fillRect(x - 1, y + this.len - 1, 3, 3);
if(this.clock) {
var clockSize = 7;
context.moveTo(x - clockSize, y);
context.lineTo(x, y - clockSize);
context.lineTo(x + clockSize, y);
}
context.stroke();
if(this.name !== undefined) {
y -= this.clock ? 6 + clockSize : 6;
context.font = "12px Times";
context.textAlign = "center";
context.fillText(this.name, x, y);
}
break;
}
context.lineWidth = 1;
}
/**
* Determine a length automatically so that the end of the
* connector will be on the grid, but be at least 12 pixels long
*/
Connector.prototype.autoLen = function() {
switch(this.orientation) {
case 's':
this.len = Math.floor(this.y / 8) * 8 + 8 - this.y;
while(this.len < 12) {
this.len += 8;
}
break;
}
}
/**
* Get a bounding box that encloses this connector.
* @returns {Rect}
*/
Connector.prototype.bounds = function() {
let x = this.component.x + this.x;
let y = this.component.y + this.y;
const bounds = new Rect(x, y, x, y);
switch(this.orientation) {
case 'e':
bounds.expandXY(x + this.len, y);
break;
case 'w':
bounds.expandXY(x - this.len, y);
break;
case 'n':
bounds.expandXY(x, y - this.len);
break;
case 's':
bounds.expandXY(x, y + this.len);
break;
}
return bounds;
}