BrowserLogic/js/elements.js
2021-03-07 01:01:37 -08:00

2119 lines
74 KiB
JavaScript

let ElementReferenceTable = new Array();
let ElementCategory_Inputs = new ElementCatalog_Category("Inputs","");
let ElementCategory_Outputs = new ElementCatalog_Category("Outputs","");
let ElementCategory_LOGIC = new ElementCatalog_Category("Logic" ,"");
let ElementCategory_FlipFlop = new ElementCatalog_Category("Flip-Flops" ,"");
let ElementCategory_Timing = new ElementCatalog_Category("Timing" ,"");
let ElementCategory_ICs = new ElementCatalog_Category("ICs" ,"");
let ElementCategory_Other = new ElementCatalog_Category("Other" ,"");
let elementCatalog = new ElementCatalog([ElementCategory_Inputs,
ElementCategory_Outputs,
ElementCategory_LOGIC,
ElementCategory_FlipFlop,
ElementCategory_Timing,
ElementCategory_ICs,
ElementCategory_Other]);
class ElementProperty {
constructor(name,type,callback,defaultValue,currentValue = false,values=false,min=0,max=12) {
/*
Types
---------------------------------------
bool Boolean Values
int Integer Value
string String Value
list Dropdown box of values
Callback is an object of:
---------------------------------------
CBObject Object to call function on
CBFunction The function
*/
this.Name = name;
this.Type = type;
this.Callback = callback;
this.DefaultValue = defaultValue;
if (!currentValue) currentValue = defaultValue;
this.CurrentValue = currentValue;
this.Values = values;
if (!values) this.Values = new Array();
this.Minimium = min;
this.Maximium = max;
}
toJSON(key) {
return {
Name: this.Name,
DefaultValue: this.DefaultValue,
CurrentValue: this.CurrentValue,
Values: this.Values,
Minimium: this.Minimium,
Maximium: this.Maximium
};
}
Call(value) {
this.Callback.CBObject[this.Callback.CBFunction](value);
}
}
class ElementConnection {
constructor(elementContainer,element,input,output = 0) {
this.Container = elementContainer;
this.Element = element;
this.Input = input;
this.Output = output;
}
toJSON(key) {
return {
Element: this.Element.Designator,
Input: parseInt(this.Input),
Output: this.Output
};
}
}
class Element extends CanvasTools {
constructor(_Container,RestoreData = null,logicengine,Inputs) {
super();
this.Name = "Element";
this.Designator = "";
this.Inputs = new Array(Inputs);
this.InputLabels = new Array(1);
this.Width = 100;
this.Height = 60;
this.inputCircleRadius = 10;
this.outputCircleRadius = 10;
this.X = 0;
this.Y = 0;
this.OutputConnections = new Array();
this.MouseOver = false;
this.MousePosition = {x: 0, y: 0};
this.Properties = new Array();
this.LogicEngine = logicengine;
this.Outputs = new Array(1);
this.OutputLabels = new Array(1);
this.NoOutput = false;
this.OutputLink = 0;
this._Container = _Container;
let inputProperty = new ElementProperty("Inputs","int",{CBObject: this,CBFunction: "ChangeInputs"},2,Inputs,false,2);
this.Properties.push(inputProperty);
if (RestoreData) {
this.Designator = RestoreData.Designator;
this.Width = RestoreData.Width;
this.Height = RestoreData.Height;
this.X = RestoreData.X;
this.Y = RestoreData.Y;
if (RestoreData.Properties.length > 0) {
if (RestoreData.Properties[0].Name == "Inputs") {
this.ChangeInputs(RestoreData.Properties[0].CurrentValue);
this.Properties[0].DefaultValue = RestoreData.Properties[0].DefaultValue;
this.Properties[0].CurrentValue = RestoreData.Properties[0].CurrentValue;
this.Properties[0].Values = RestoreData.Properties[0].Values;
this.Properties[0].Minimium = RestoreData.Properties[0].Minimium;
this.Properties[0].Maximium = RestoreData.Properties[0].Maximium;
}
}
this.Inputs = RestoreData.Inputs;
}
}
ConnectsTo(element,input = false) {
for (let a = 0; a < this.OutputConnections.length; a++) {
if (this.OutputConnections[a].Element == element || this.OutputConnections[a].Element.Designator == element) {
if (input !== false) {
if (this.OutputConnections[a].Input == input) return this.OutputConnections[a].Output;
} else {
return 0;
}
}
}
return false;
}
isVisible() {
let isvisible = false;
if (this.LogicEngine) {
let LeftX = this.LogicEngine.Panning.OffsetX;
if (LeftX < 0) {
LeftX = Math.abs(LeftX);
} else {
LeftX = -Math.abs(LeftX);
}
let RightX = LeftX + this.LogicEngine.Canvas.width;
let TopY = this.LogicEngine.Panning.OffsetY;
if (TopY < 0) {
TopY = Math.abs(TopY);
} else {
TopY = -Math.abs(TopY);
}
let BottomY = TopY + this.LogicEngine.Canvas.height;
if (((this.X + this.Width) >= LeftX) && ((this.X) <= RightX) && ((this.Y + this.Height) >= TopY) && ((this.Y) <= BottomY)) isvisible = true;
}
return isvisible;
}
toJSON(key) {
return {
Name: this.Name,
Designator: this.Designator,
Inputs: this.Inputs,
Outputs: this.OutputConnections,
Output: this.getOutput(),
Width: this.Width,
Height: this.Height,
X: this.X,
Y: this.Y,
Properties: this.Properties
};
}
getProperty(property) {
for (let a = 0; a < this.Properties.length;a++) {
if (this.Properties[a].Name == property) return this.Properties[a];
}
return false;
}
removeProperty(property) {
for (let a = 0; a < this.Properties.length;a++) {
if (this.Properties[a].Name == property) {
this.Properties.splice(a,1);
return true;
}
}
return false;
}
totalInputs() {
return this.Inputs.length;
}
ChangeInputs(inputs) {
inputs = parseInt(inputs,10);
this.Inputs = new Array(inputs);
this.getProperty("Inputs").CurrentValue = inputs;
this.Height = inputs*25;
if (this.Height < 60) this.Height = 60;
}
Delete() {
// if we are active linking stop doing so
if (this.LogicEngine.ActiveLink == this) this.LogicEngine.ActiveLink = false;
// Just to clean up connections
this.Disconnect();
}
Disconnect(element = false) {
if (element) {
for (let a = 0; a < this.OutputConnections.length; a++) {
if (this.OutputConnections[a].Element == element) {
this.LogicEngine.RecursionCount = 0;
let element = this.OutputConnections[a].Element;
let input = this.OutputConnections[a].Input;
this.OutputConnections.splice(a,1);
element.setInput(input, false);
a--;
}
}
} else {
for (let a = 0; a < this.OutputConnections.length; a++) {
this.LogicEngine.RecursionCount = 0;
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, false);
this.OutputConnections.splice(a,1);
a--;
}
}
}
MouseDown(mousePos) {
return;
}
MouseUp(mousePos) {
return;
}
LinkOutLocation() {
return this.OutputLink;
}
MouseClick(mousePos) {
let ctxMousePos = this.MousePosition;
let mouseDistOutput = length2D(this.X+(this.Width-10),
this.Y+(this.Height/2),
ctxMousePos.x,
ctxMousePos.y);
if (this.LogicEngine.ActiveLink) {
// We need to see if an input is being clicked on to be linked to
let foundInput = false;
for (let a = 0; a < this.Inputs.length;a++) {
let centerY = this.Y + Math.round(this.Height / 2);
let totalHeight = this.totalInputs() * ((this.inputCircleRadius*2)+4);
let firstY = (centerY - (totalHeight/2)) + 12;
let mouseDist = length2D(this.X+10,
firstY+ (a*24),
ctxMousePos.x,
ctxMousePos.y);
if (mouseDist <= (this.inputCircleRadius)) {
this.LogicEngine.Link(a);
foundInput = true;
break;
}
}
} else {
let foundOutput = false;
for (let a = 0; a < this.Outputs.length;a++) {
let centerY = this.Y + Math.round(this.Height / 2);
let totalHeight = this.Outputs.length * ((this.outputCircleRadius*2)+4);
let firstY = (centerY - (totalHeight/2)) + 12;
let mouseDist = length2D(this.X+(this.Width-10),
firstY+ (a*24),
ctxMousePos.x,
ctxMousePos.y);
if (mouseDist <= (this.outputCircleRadius)) {
this.OutputLink = {Output: a,x: this.X+(this.Width-10), y: firstY+ (a*24)};
this.LogicEngine.Link();
foundOutput = true;
break;
}
}
}
}
mouseInside(mousePos) {
mousePos = this.LogicEngine.getCanvasMousePos(mousePos);
this.MouseOver = false;
if (((mousePos.x >= this.X ) && (mousePos.x <= (this.X + this.Width))) && ((mousePos.y >= this.Y ) && (mousePos.y <= (this.Y + this.Height)))) this.MouseOver = true;
this.MousePosition = mousePos;
return this.MouseOver;
}
addConnection(container, element, input,output=0) {
for (let a = 0; a < this.OutputConnections.length; a++) {
if (this.OutputConnections[a].Element == element && this.OutputConnections[a].Input == input) {
// Already existing link, we will remove it instead
console.log("Removing link");
this.LogicEngine.RecursionCount = 0;
element.setInput(input,false);
this.OutputConnections.splice(a,1);
return;
}
}
let newConnection = new ElementConnection(container,element,input,output);
this.OutputConnections.push(newConnection);
this.LogicEngine.RecursionCount = 0;
element.setInput(input,this.getOutput(output));
return newConnection;
}
drawInputs(ctx,x,y,borderColor = "#000",circleColorFalse = "#ff0000",circleColorTrue="#00ff00",circleColorHover = "#00ffff") {
ctx.save();
//this.inputCircleRadius = 10;
let centerY = y + Math.round(this.Height / 2);
let totalHeight = this.totalInputs() * ((this.inputCircleRadius*2)+4);
let firstY = (centerY - (totalHeight/2)) + 12;
for (let a = 0; a < this.totalInputs();a++) {
let mouseDist = length2D(x+10, firstY + (a*24),this.MousePosition.x,this.MousePosition.y);
ctx.beginPath();
ctx.arc(x+10,firstY + (a*24),this.inputCircleRadius,0,2*Math.PI);
ctx.strokeStyle = borderColor;
ctx.fillStyle = circleColorFalse;
if (this.Inputs[a]) ctx.fillStyle = circleColorTrue;
if ((mouseDist <= (this.inputCircleRadius)) && this.LogicEngine.ActiveLink) ctx.fillStyle = circleColorHover;
ctx.fill();
ctx.stroke();
if (this.InputLabels[a]) this.drawText(ctx,x+(this.inputCircleRadius*2)+ 5,(firstY + (a*24)) + 5,this.InputLabels[a],"10px Console","#000");
}
ctx.restore();
}
drawOutputs(ctx,x,y,borderColor = "#000",circleColorFalse = "#ff0000",circleColorTrue="#00ff00",circleColorHover = "#00ffff") {
ctx.save();
let centerY = y + Math.round(this.Height / 2);
let totalHeight = this.Outputs.length * ((this.outputCircleRadius*2)+4);
let firstY = (centerY - (totalHeight/2)) + 12;
for (let a = 0; a < this.Outputs.length;a++) {
let mouseDist = length2D(x+(this.Width - 10), firstY + (a*24),this.MousePosition.x,this.MousePosition.y);
ctx.beginPath();
ctx.arc(x+(this.Width-10),firstY + (a*24),this.outputCircleRadius,0,2*Math.PI);
ctx.strokeStyle = borderColor;
ctx.fillStyle = circleColorFalse;
if (this.getOutput(a)) ctx.fillStyle = circleColorTrue;
if ((mouseDist <= (this.outputCircleRadius)) && !this.LogicEngine.ActiveLink) ctx.fillStyle = circleColorHover;
ctx.fill();
ctx.stroke();
let textSize = false;
if (this.OutputLabels[a]) textSize = this.textSize(ctx,this.OutputLabels[a],"10px Console");
if (this.OutputLabels[a]) this.drawText(ctx,(x+(this.Width)) - (textSize.width + 5 + (this.outputCircleRadius*2)),(firstY + (a*24)) + 5,this.OutputLabels[a],"10px Console","#000");
}
ctx.restore();
}
drawConnections(ctx,settings) {
let centerY = this.Y + Math.round(this.Height / 2);
let totalHeight = this.Outputs.length * ((this.outputCircleRadius*2)+4);
let firstY = (centerY - (totalHeight/2)) + 12;
for (let a = 0; a < this.OutputConnections.length;a++) {
if (!this.OutputConnections[a].Container.HasElement(this.OutputConnections[a].Element)) {
// This is a ghosted connection, lets get rid of it
this.OutputConnections.splice(a,1);
a--;
} else {
let endCenterY = this.OutputConnections[a].Element.Y + Math.round(this.OutputConnections[a].Element.Height / 2);
let endTotalHeight = this.OutputConnections[a].Element.totalInputs() * ((this.OutputConnections[a].Element.inputCircleRadius*2)+4);
let endFirstY = (endCenterY - (endTotalHeight/2)) + 12;
let startX = this.X + this.Width;
let startY = firstY+ (this.OutputConnections[a].Output*24);
let endX = this.OutputConnections[a].Element.X;
//let endY = this.OutputConnections[a].Element.Y+(this.OutputConnections[a].Element.inputCircleRadius + 2)+(((this.OutputConnections[a].Input*(4+(this.OutputConnections[a].Element.inputCircleRadius*2))))-2)+(this.OutputConnections[a].Element.inputCircleRadius/2);
let endY = endFirstY + (this.OutputConnections[a].Input*24);
let startMidX = startX + ((endX - startX)/2);
let startMidY = startY;
let midX = startMidX;
let midY = startY + ((endY - startY)/2);
let endMidX = startMidX;
let endMidY = endY;
ctx.save();
ctx.beginPath();
ctx.lineWidth = settings.LinkWidth;
ctx.setLineDash(settings.LinkDash);
ctx.moveTo(startX, startY);
//ctx.lineTo(endX, endY);
ctx.quadraticCurveTo(startMidX,startMidY,midX,midY);
ctx.quadraticCurveTo(endMidX,endMidY,endX,endY);
ctx.strokeStyle = settings.ActiveConnectionColor;
if (!this.getOutput(this.OutputConnections[a].Output)) ctx.strokeStyle = settings.InactiveConnectionColor;
ctx.stroke();
ctx.restore();
}
}
}
setConnections() {
for (let a = 0; a < this.OutputConnections.length;a++) {
//console.log(this.Designator + " sending " + this.getOutput() + " to " + this.OutputConnections[a].Element.Designator + " I" + this.OutputConnections[a].Input);
this.LogicEngine.RecursionCount++;
//console.log("Recursion: " + this.LogicEngine.RecursionCount);
if (this.LogicEngine.RecursionCount > 1000) {
if (!this.LogicEngine.RecursionError) {
console.log("RECURSION ERROR");
this.LogicEngine.RecursionError = true;
}
return;
}
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input,this.getOutput(this.OutputConnections[a].Output));
this.LogicEngine.RecursionCount--;
}
}
setInput(Input,Value) {
let oldOutput = this.getOutput();
if (Value) {
Value = true;
} else {
Value = false;
}
if (Input < this.totalInputs()) {
this.Inputs[Input] = Value;
} else {
return;
}
let isHigh = this._Container.isHigh(this,Input);
if (isHigh !== false) this.Inputs[Input] = true;
if (isHigh === false) this.Inputs[Input] = false;
this.setConnections();
}
getOutput(Output=0) {
/*
Should return true or false
*/
return false;
}
drawElement(x,y,ctx) {
/*
Draw routine for the element
*/
this.drawBorderBox(ctx,x+10,y,this.Width-20,this.Height);
this.drawTextCentered(ctx,x,y,this.Width,this.Height,"LOGIC");
this.drawInputs(ctx,x,y);
this.drawOutputs(ctx,x,y);
}
}
class LabelElement extends Element {
constructor(_Container, RestoreData = null,logicengine) {
super(_Container, RestoreData,logicengine,0);
this.removeProperty("Inputs");
this.Name = "Label";
this.Font = "48px Console";
this.FontStyle = "black";
this.Label = this.Name;
let LabelProperty = new ElementProperty("Label","string",{CBObject: this,CBFunction: "setLabel"},"Label","Label",false,1,255);
this.Properties.push(LabelProperty);
let LabelSize = new ElementProperty("Size","int",{CBObject: this,CBFunction: "setSize"},"48","48",false,6,99999);
this.Properties.push(LabelSize);
let LabelColor = new ElementProperty("Color","color",{CBObject: this,CBFunction: "setColor"},"#000000","#000000",false,0,0);
this.Properties.push(LabelColor);
if (RestoreData) {
this.Label = RestoreData.Properties[0].CurrentValue;
this.Properties[0].CurrentValue = this.Label;
this.Font = RestoreData.Properties[1].CurrentValue + "px Console";
this.Properties[1].CurrentValue = RestoreData.Properties[1].CurrentValue;
this.FontStyle = RestoreData.Properties[2].CurrentValue;
this.Properties[2].CurrentValue = this.FontStyle;
}
let textWidth = this.textSize(logicengine.Ctx,this.Label,this.Font);
this.Width = textWidth.width;
this.Height = textWidth.height;
}
setLabel(label) {
this.Label = label;
this.Properties[0].CurrentValue = label;
}
setSize(size) {
this.Font = size + "px Console";
this.Properties[1].CurrentValue = size;
}
setColor(color) {
this.FontStyle = color;
this.Properties[2].CurrentValue = color;
}
drawElement(x, y, ctx) {
let textWidth = this.textSize(ctx,this.Label,this.Font);
this.Width = textWidth.width;
this.Height = textWidth.height;
this.drawTextCentered(ctx,x,y,textWidth.width,textWidth.height,this.Label,this.Font,this.FontStyle);
}
}
let ElementCatalog_Label = new ElementCatalog_Element("Label","Allows you to place a label","🏷",LabelElement,[]);
ElementReferenceTable.push(ElementCatalog_Label);
ElementCategory_Other.addElement(ElementCatalog_Label);
class ICInput extends Element {
setPinName(pinname) {
// Dont actually need it to do anything
this.Properties[0].CurrentValue = pinname;
this.Output = false;
}
constructor(_Container, RestoreData = null,logicengine) {
super(_Container, RestoreData ,logicengine,0);
this.removeProperty("Inputs");
this.Task = new Task("ICInputTask","CLOCK",0,1,this.ClockTick.bind(this));
this.Name = "ICInput";
this.Input = false;
let pinNameProperty = new ElementProperty("Pin Name","string",{CBObject: this,CBFunction: "setPinName"},"Pin","Pin",false,1,32);
this.Properties.push(pinNameProperty);
this.LogicEngine.Scheduler.addTask(this.Task);
if (RestoreData) {
pinNameProperty.CurrentValue = RestoreData.Properties[0].CurrentValue;
}
}
toJSON(key) {
let superjson = super.toJSON(key);
superjson.Task = {
Enabled: this.Task.Enabled,
Time: this.Task.Time,
LastCall: Date.now() - this.Task.LastCall,
CallCount: this.Task.CallCount
};
return superjson;
}
setInput(notused = 0,value) {
if (notused > 0) return;
if (value === false) this.Input = false;
if (value !== false) this.Input = true;
if (!this.Task.Enabled) {
this.Task.LastCall = 0;
this.Task.Enabled = true;
}
}
ClockTick() {
this.Output = this.Input;
this.Task.Enabled = false;
this.Task.LastCall = 0;
for (let a = 0; a < this.OutputConnections.length; a++) {
this.LogicEngine.RecursionCount = 0;
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput());
}
}
Delete() {
super.Delete();
this.LogicEngine.Scheduler.deleteTask(this.Task);
}
getOutput(Output=0) {
return this.Output;
}
drawElement(x,y,ctx) {
/*
Draw routine for the element
*/
this.drawBorderBox(ctx,x+10,y,this.Width-30,this.Height);
this.drawTextCentered(ctx,x,y+2,this.Width-10,14,this.Designator,"12px Console");
this.drawTextCentered(ctx,x,y,this.Width-10,this.Height,this.Properties[0].CurrentValue,"12px Console");
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_ICInput = new ElementCatalog_Element("ICInput","Allows for external signal to be applied to the design when used as an IC.","-<|",ICInput,[]);
ElementReferenceTable.push(ElementCatalog_ICInput);
ElementCategory_ICs.addElement(ElementCatalog_ICInput);
class ICOutput extends Element {
setPinName(pinname) {
// Dont actually need it to do anything
this.Properties[0].CurrentValue = pinname;
this.Output = false;
}
constructor(_Container, RestoreData = null,logicengine) {
super(_Container, RestoreData ,logicengine,1);
this.removeProperty("Inputs");
this.Task = new Task("ICOutputTask","CLOCK",0,1,this.ClockTick.bind(this));
this.Name = "ICOutput";
this.Input = false;
this.NoOutput = true;
let pinNameProperty = new ElementProperty("Pin Name","string",{CBObject: this,CBFunction: "setPinName"},"Pin","Pin",false,1,32);
this.Properties.push(pinNameProperty);
this.LogicEngine.Scheduler.addTask(this.Task);
if (RestoreData) {
pinNameProperty.CurrentValue = RestoreData.Properties[0].CurrentValue;
}
}
toJSON(key) {
let superjson = super.toJSON(key);
superjson.Task = {
Enabled: this.Task.Enabled,
Time: this.Task.Time,
LastCall: Date.now() - this.Task.LastCall,
CallCount: this.Task.CallCount
};
return superjson;
}
setInput(notused = 0,value) {
if (notused > 0) return;
value = this._Container.isHigh(this,0);
if (value === false) this.Input = false;
if (value !== false) this.Input = true;
this.Inputs[0] = this.Input;
if (!this.Task.Enabled) {
this.Task.LastCall = 0;
this.Task.Enabled = true;
}
}
ClockTick() {
this.Output = this.Input;
this.Task.Enabled = false;
this.Task.LastCall = 0;
//console.log(this);
for (let a = 0; a < this.OutputConnections.length; a++) {
this.LogicEngine.RecursionCount = 0;
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput());
}
}
Delete() {
super.Delete();
this.LogicEngine.Scheduler.deleteTask(this.Task);
}
getOutput(Output=0) {
return this.Output;
}
drawConnections(ctx, settings) {
// Don't draw connections from IC Outputs
}
drawElement(x,y,ctx) {
/*
Draw routine for the element
*/
this.drawBorderBox(ctx,x+20,y,this.Width-10,this.Height);
this.drawTextCentered(ctx,x+20,y+2,this.Width-10,14,this.Designator,"12px Console");
this.drawTextCentered(ctx,x+20,y,this.Width-10,this.Height,this.Properties[0].CurrentValue,"12px Console");
this.drawInputs(ctx,x,y);
}
}
let ElementCatalog_ICOutput = new ElementCatalog_Element("ICOutput","Allows for routing a signal externally when the design is used as an IC.","|>-",ICOutput,[]);
ElementReferenceTable.push(ElementCatalog_ICOutput);
ElementCategory_ICs.addElement(ElementCatalog_ICOutput);
class ICElement extends Element {
constructor(_Container, RestoreData = null, logicengine,ICSettings = null) {
let newContainer = false;
let icContainer = false;
try {
newContainer = loadContainer(JSON.parse(JSON.parse(ICSettings.Container)));
icContainer = loadContainer(JSON.parse(JSON.parse(ICSettings.Container)));
} catch (ex) {
newContainer = loadContainer(JSON.parse(ICSettings.Container));
icContainer = loadContainer(JSON.parse(ICSettings.Container));
}
ICSettings.Inputs = new Array();
ICSettings.Outputs = new Array();
for (let a = 0; a < newContainer.Elements.length; a++) {
if (newContainer.Elements[a] instanceof ICInput) {
let cConn = new ContainerConnection(null,newContainer,null,newContainer.Elements[a],0);
ICSettings.Inputs.push(cConn);
}
if (newContainer.Elements[a] instanceof ICOutput) {
let cConn = new ContainerConnection(newContainer,null,newContainer.Elements[a],null,0);
ICSettings.Outputs.push(cConn);
}
}
let totalInputs = ICSettings.Inputs.length;
let totalOutputs = ICSettings.Outputs.length;
super(_Container, RestoreData, logicengine, totalInputs);
this.removeProperty("Inputs");
this.ICBlueprint = icContainer;
this.Outputs = ICSettings.Outputs;
this.InputConnections = ICSettings.Inputs;
this.Container = newContainer;
this.Name = ICSettings.Name;
this.Icon = "≡[°]≡";
this.Width = 140;
this.Height = (totalInputs >= totalOutputs) ? totalInputs*25 : totalOutputs*25;
if (this.Height < 60) this.Height = 60;
if (RestoreData) {
//console.log(RestoreData);
}
}
toJSON(key) {
let superjson = super.toJSON(key);
superjson.IsIC = true;
let eCat = getElementInfo(this.Name);
superjson.Description = eCat.Description;
superjson.ICOutputs = this.Outputs;
superjson.InputConnections = this.InputConnections;
superjson.ICBlueprint = this.ICBlueprint;
superjson.ICContainer = this.Container;
return superjson;
}
Disconnect(element = false) {
super.Disconnect(element);
if (element) {
for (let a = 0; a < this.Outputs.length; a++) {
if (this.Outputs[a].toElement == element || this.Outputs[a].fromElement == element) {
if (this.Outputs[a].toElement == element) {
// It's a too element, we need to tell it we are not high anymore
} else {
// Its a from element, we need to tell it's too element
}
}
}
} else {
// We are disconnecting all of our own connections
for (let a = 0; a < this.Outputs.length; a++) {
}
}
}
addConnection(container, element, input, output = 0) {
super.addConnection(container, element, input, output);
this.Outputs[output].toElementContainer = container;
this.Outputs[output].toElement = element;
this.Outputs[output].Input = input;
this.Outputs[output].fromElement.addConnection(container,element,input,0);
}
setInput(Input, Value) {
if (this.InputConnections.length >= Input) {
// No need to worry about recursion as this goes to an input element which is buffered
this.Inputs[Input] = (Value === false) ? false : true;
//console.log("Calling " + this.InputConnections[Input].toElement.Designator);
this.InputConnections[Input].toElement.setInput(this.InputConnections[Input].Input,Value);
}
return false;
}
getOutput(Output = 0) {
if (this.Outputs.length >= Output) {
return this.Outputs[Output].fromElement.getOutput(this.Outputs[Output].Input);
}
return false;
}
drawElement(x,y,ctx) {
/*
Draw routine for the element
*/
this.drawBorderBox(ctx,x+20,y,this.Width-40,this.Height);
this.drawTextCentered(ctx,x,y,this.Width,this.Height,this.Icon,"12px Console");
this.drawTextCentered(ctx,x,(y+this.Height)-14,this.Width,14,this.Designator,"10px Console","#000");
this.drawInputs(ctx,x,y);
this.drawOutputs(ctx,x,y);
}
drawInputs(ctx,x,y,borderColor = "#000",circleColorFalse = "#ff0000",circleColorTrue="#00ff00",circleColorHover = "#00ffff") {
ctx.save();
let centerY = y + Math.round(this.Height / 2);
let totalHeight = this.totalInputs() * ((this.inputCircleRadius*2)+4);
let firstY = (centerY - (totalHeight/2)) + 12;
for (let a = 0; a < this.totalInputs();a++) {
let mouseDist = length2D(x+10, firstY + (a*24),this.MousePosition.x,this.MousePosition.y);
ctx.beginPath();
ctx.arc(x+10,firstY + (a*24),this.inputCircleRadius,0,2*Math.PI);
ctx.strokeStyle = borderColor;
ctx.fillStyle = circleColorFalse;
if (this.Inputs[a]) ctx.fillStyle = circleColorTrue;
if ((mouseDist <= (this.inputCircleRadius)) && this.LogicEngine.ActiveLink) ctx.fillStyle = circleColorHover;
ctx.fill();
ctx.stroke();
this.drawText(ctx,x+(this.inputCircleRadius*2)+ 5,(firstY + (a*24)) + 5,this.InputConnections[a].toElement.Properties[0].CurrentValue,"10px Console","#000");
}
ctx.restore();
}
drawOutputs(ctx,x,y,borderColor = "#000",circleColorFalse = "#ff0000",circleColorTrue="#00ff00",circleColorHover = "#00ffff") {
ctx.save();
let centerY = y + Math.round(this.Height / 2);
let totalHeight = this.Outputs.length * ((this.outputCircleRadius*2)+4);
let firstY = (centerY - (totalHeight/2)) + 12;
for (let a = 0; a < this.Outputs.length;a++) {
let mouseDist = length2D(x+(this.Width - 10), firstY + (a*24),this.MousePosition.x,this.MousePosition.y);
ctx.beginPath();
ctx.arc(x+(this.Width-10),firstY + (a*24),this.outputCircleRadius,0,2*Math.PI);
ctx.strokeStyle = borderColor;
ctx.fillStyle = circleColorFalse;
if (this.Outputs[a].fromElement.getOutput(0)) ctx.fillStyle = circleColorTrue;
if ((mouseDist <= (this.outputCircleRadius)) && !this.LogicEngine.ActiveLink) ctx.fillStyle = circleColorHover;
ctx.fill();
ctx.stroke();
let textSize = false;
if (ctx,this.Outputs[a]) textSize = this.textSize(ctx,this.Outputs[a].fromElement.Properties[0].CurrentValue,"10px Console");
this.drawText(ctx,(x+(this.Width-10)) - (textSize.width + 5 + (this.outputCircleRadius*2)),(firstY + (a*24)) + 5,this.Outputs[a].fromElement.Properties[0].CurrentValue,"10px Console","#000");
}
ctx.restore();
}
}
class ClockElement extends Element {
ClockTick() {
if (this.Inputs[0]) {
this.Output = ~this.Output;
if (this.Output) {
this.Task.Time = Math.round(this.Period * this.Duty);
} else {
this.Task.Time = this.Period - Math.round(this.Period * this.Duty);
}
for (let a = 0; a < this.OutputConnections.length; a++) {
this.LogicEngine.RecursionCount = 0;
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput());
}
}
}
Delete() {
super.Delete();
this.LogicEngine.Scheduler.deleteTask(this.Task);
}
getOutput(Output=0) {
return this.Output;
}
setInput(Input, Value) {
console.log(this.Designator + " got told to set " + Input + " to value " + Value);
super.setInput(Input, Value);
if (!this.Inputs[0]) {
this.Output = false;
this.Task.LastCall = 0;
this.Task.Enabled = false;
for (let a = 0; a < this.OutputConnections.length;a++) {
this.LogicEngine.RecursionCount = 0;
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input,this.getOutput());
}
} else {
this.Task.Enabled = true;
}
}
constructor(_Container, RestoreData = null, logicengine) {
super(_Container, RestoreData,logicengine,1);
this.removeProperty("Inputs");
this.Name = "Clock";
this.Period = 1000;
this.Duty = 0.5;
this.Output = false;
this.Width = 100;
this.Task = new Task("ClockTask","CLOCK",0,Math.round(this.Period * this.Duty),this.ClockTick.bind(this));
this.setInput(0,true);
this.removeProperty("Inputs");
let periodProperty = new ElementProperty("Period","int",{CBObject: this,CBFunction: "setPeriod"},1000,false,false,4,999999);
let dutyProperty = new ElementProperty("Duty","int",{CBObject: this,CBFunction: "setDuty"},50,false,false,0,100);
if (RestoreData) {
if (this.Height != RestoreData.Height) {
// Oddly this height value gets set to 25000 on return from the above super(), if anyone has any ideas why let me know!
this.Height = RestoreData.Height;
}
periodProperty.DefaultValue = RestoreData.Properties[0].DefaultValue;
periodProperty.CurrentValue = RestoreData.Properties[0].CurrentValue;
periodProperty.Values = RestoreData.Properties[0].Values;
periodProperty.Minimium = RestoreData.Properties[0].Minimium;
periodProperty.Maximium = RestoreData.Properties[0].Maximium;
dutyProperty.DefaultValue = RestoreData.Properties[1].DefaultValue;
dutyProperty.CurrentValue = RestoreData.Properties[1].CurrentValue;
dutyProperty.Values = RestoreData.Properties[1].Values;
dutyProperty.Minimium = RestoreData.Properties[1].Minimium;
dutyProperty.Maximium = RestoreData.Properties[1].Maximium;
this.Period = periodProperty.CurrentValue;
this.Duty = dutyProperty.CurrentValue/100;
this.Task.Enabled = RestoreData.Task.Enabled;
this.Task.Time = RestoreData.Task.Time;
this.Task.LastCall = RestoreData.Task.LastCall + Date.now();
this.Task.CallCount = RestoreData.Task.CallCount;
}
this.Properties.push(periodProperty);
this.Properties.push(dutyProperty);
this.LogicEngine.Scheduler.addTask(this.Task);
}
toJSON(key) {
let superjson = super.toJSON(key);
superjson.Task = {
Enabled: this.Task.Enabled,
Time: this.Task.Time,
LastCall: Date.now() - this.Task.LastCall,
CallCount: this.Task.CallCount
};
return superjson;
}
setPeriod(period) {
this.Period = period;
this.Task.LastCall = 0;
this.getProperty("Period").CurrentValue = period;
}
setDuty(duty) {
this.Duty = duty/100;
this.Task.LastCall = 0;
this.getProperty("Duty").CurrentValue = duty;
}
drawElement(x, y, ctx) {
this.drawBorderBox(ctx, x+10,y,this.Width-20,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor);
this.drawTextCentered(ctx,x,y+5,this.Width,12,this.Period + "ms " + (this.Duty * 100) + "%","10px Console");
if (this.Task.Enabled) this.drawTextCentered(ctx,x,y+this.Height-16,this.Width,12,(this.Task.Time - (Date.now() - this.Task.LastCall)) + "ms","10px Console");
this.drawTextCentered(ctx,x,y,this.Width,this.Height,this.Designator,"12px Console","#000");
this.drawInputs(ctx,x,y);
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_CLOCK = new ElementCatalog_Element("Clock","The clock will output a pulse for the length of the duty cycle for a given period. I.E. if a 1000ms period with 50% duty cycle is given, the output will be high for 500ms and low for 500ms. The clock can be disabled with a low input, this also resets the clocks start time to allow for syncing.","🕑",ClockElement,[]);
ElementReferenceTable.push(ElementCatalog_CLOCK);
ElementCategory_Timing.addElement(ElementCatalog_CLOCK);
class PulseElement extends Element {
ClockTick() {
this.Output = false;
this.Task.Enabled = false;
this.Task.LastCall = Date.now();
for (let a = 0; a < this.OutputConnections.length; a++) {
this.LogicEngine.RecursionCount = 0;
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput());
}
}
Delete() {
super.Delete();
this.LogicEngine.Scheduler.deleteTask(this.Task);
}
getOutput(Output=0) {
return this.Output;
}
setInput(Input, Value) {
if (Input > 0) return;
Value = this._Container.isHigh(this,Input);
//super.setInput(Input, Value);
this.Inputs[Input] = (Value === false) ? false : true;
if (this.Inputs[0] && !this.Task.Enabled) {
this.Output = true;
for (let a = 0; a < this.OutputConnections.length;a++) {
this.LogicEngine.RecursionCount = 0;
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input,this.getOutput());
}
this.Task.LastCall = Date.now();
this.Task.Enabled = true;
}
}
constructor(_Container, RestoreData = null, logicengine) {
super(_Container, RestoreData,logicengine,1);
this.removeProperty("Inputs");
this.Name = "Pulse";
this.Period = 100;
this.Output = false;
this.Width = 100;
this.Task = new Task("PulseTask","CLOCK",0,this.Period,this.ClockTick.bind(this));
this.removeProperty("Inputs");
let periodProperty = new ElementProperty("Period","int",{CBObject: this,CBFunction: "setPeriod"},100,false,false,4,999999);
if (RestoreData) {
if (this.Height != RestoreData.Height) {
// Oddly this height value gets set to 25000 on return from the above super(), if anyone has any ideas why let me know!
this.Height = RestoreData.Height;
}
periodProperty.DefaultValue = RestoreData.Properties[0].DefaultValue;
periodProperty.CurrentValue = RestoreData.Properties[0].CurrentValue;
periodProperty.Values = RestoreData.Properties[0].Values;
periodProperty.Minimium = RestoreData.Properties[0].Minimium;
periodProperty.Maximium = RestoreData.Properties[0].Maximium;
this.Period = periodProperty.CurrentValue;
this.Task.Enabled = RestoreData.Task.Enabled;
this.Task.Time = RestoreData.Task.Time;
this.Task.LastCall = RestoreData.Task.LastCall + Date.now();
this.Task.CallCount = RestoreData.Task.CallCount;
}
this.Properties.push(periodProperty);
this.LogicEngine.Scheduler.addTask(this.Task);
}
toJSON(key) {
let superjson = super.toJSON(key);
superjson.Task = {
Enabled: this.Task.Enabled,
Time: this.Task.Time,
LastCall: Date.now() - this.Task.LastCall,
CallCount: this.Task.CallCount
};
return superjson;
}
setPeriod(period) {
this.Period = parseInt(period);
this.Task.Time = parseInt(period);
this.getProperty("Period").CurrentValue = parseInt(period);
}
drawElement(x, y, ctx) {
this.drawBorderBox(ctx, x+10,y,this.Width-20,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor);
this.drawTextCentered(ctx,x,y+5,this.Width,12,this.Period + "ms","10px Console");
if (this.Task.Enabled) this.drawTextCentered(ctx,x,y+this.Height-16,this.Width,12,(this.Task.Time - (Date.now() - this.Task.LastCall)) + "ms","10px Console");
this.drawTextCentered(ctx,x,y,this.Width,this.Height,this.Designator,"12px Console","#000");
this.drawInputs(ctx,x,y);
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_PULSE = new ElementCatalog_Element("Pulse","The pulse upon input will output a high only for as long as the period set, I.E. if a period of 1000ms is set then any input no matter how long, will trigger a pulse 1000ms long.","|\\__",PulseElement,[]);
ElementReferenceTable.push(ElementCatalog_PULSE);
ElementCategory_Timing.addElement(ElementCatalog_PULSE);
class DelayElement extends Element {
ClockTick() {
if (this.Output) {
this.Output = false;
this.Task.Enabled = false;
this.Task.LastCall = Date.now();
this.Task.Time = this.Period;
if (this.InputPulses.length > 0) {
this.Task.Enabled = true;
}
} else {
this.Output = true;
this.Task.Enabled = false;
if (this.InputPulses.length > 0) {
this.Task.Time = this.InputPulses[0];
this.Task.Enabled = true;
this.InputPulses.splice(0,1);
} else {
if (this.InputEnd > 0) {
this.Task.Time = this.InputEnd - this.InputStart;
this.Task.Enabled = true;
}
}
this.Task.LastCall = Date.now();
}
for (let a = 0; a < this.OutputConnections.length; a++) {
this.LogicEngine.RecursionCount = 0;
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput());
}
}
Delete() {
super.Delete();
this.LogicEngine.Scheduler.deleteTask(this.Task);
}
getOutput(Output=0) {
return this.Output;
}
setInput(Input, Value) {
if (Input > 0) return;
Value = this._Container.isHigh(this,Input);
if (this.Inputs[Input] == (Value === false) ? false : true) return;
//super.setInput(Input, Value);
this.Inputs[Input] = Value;
if (this.Inputs[0] && !this.Task.Enabled) {
this.InputStart = Date.now();
this.InputEnd = 0;
this.Output = false;
for (let a = 0; a < this.OutputConnections.length;a++) {
this.LogicEngine.RecursionCount = 0;
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input,this.getOutput());
}
this.Task.LastCall = Date.now();
this.Task.Enabled = true;
} else {
if (!this.Inputs[Input]) {
this.InputEnd = Date.now();
if (this.InputPulses.length > 0) {
this.InputPulses.push(this.InputEnd - this.InputStart);
this.InputStart = 0;
this.InputEnd = 0;
} else {
if (!this.Task.Enabled) {
this.Task.LastCall = Date.now();
this.Task.Time = (this.InputEnd - this.InputStart) - (Date.now() - this.InputEnd);
this.Task.Enabled = true;
} else {
this.InputPulses.push(this.InputEnd - this.InputStart);
}
}
} else {
// We are in a condition where input went true, but the task is already enabled
this.InputStart = Date.now();
}
}
}
constructor(_Container, RestoreData = null, logicengine) {
super(_Container, RestoreData,logicengine,1);
this.removeProperty("Inputs");
this.Name = "Delay";
this.Period = 100;
this.Output = false;
this.Width = 100;
this.Task = new Task("DelayTask","CLOCK",0,this.Period,this.ClockTick.bind(this));
this.Task.Enabled = false;
this.removeProperty("Inputs");
let periodProperty = new ElementProperty("Period","int",{CBObject: this,CBFunction: "setPeriod"},100,false,false,4,999999);
if (RestoreData) {
if (this.Height != RestoreData.Height) {
// Oddly this height value gets set to 25000 on return from the above super(), if anyone has any ideas why let me know!
this.Height = RestoreData.Height;
}
periodProperty.DefaultValue = RestoreData.Properties[0].DefaultValue;
periodProperty.CurrentValue = RestoreData.Properties[0].CurrentValue;
periodProperty.Values = RestoreData.Properties[0].Values;
periodProperty.Minimium = RestoreData.Properties[0].Minimium;
periodProperty.Maximium = RestoreData.Properties[0].Maximium;
this.Period = periodProperty.CurrentValue;
this.Task.Enabled = RestoreData.Task.Enabled;
this.Task.Time = RestoreData.Task.Time;
this.Task.LastCall = RestoreData.Task.LastCall + Date.now();
this.Task.CallCount = RestoreData.Task.CallCount;
}
this.Properties.push(periodProperty);
this.InputStart = 0;
this.InputEnd = 0;
this.InputPulses = new Array();
this.Inputs[0] = false;
this.LogicEngine.Scheduler.addTask(this.Task);
}
toJSON(key) {
let superjson = super.toJSON(key);
superjson.InputStart = this.InputStart;
superjson.InputEnd = this.InputEnd;
superjson.InputPulses = this.InputPulses;
superjson.Task = {
Enabled: this.Task.Enabled,
Time: this.Task.Time,
LastCall: Date.now() - this.Task.LastCall,
CallCount: this.Task.CallCount
};
return superjson;
}
setPeriod(period) {
this.Period = parseInt(period);
this.Task.Time = parseInt(period);
this.getProperty("Period").CurrentValue = parseInt(period);
}
drawElement(x, y, ctx) {
this.drawBorderBox(ctx, x+10,y,this.Width-20,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor);
this.drawTextCentered(ctx,x,y+5,this.Width,12,this.Period + "ms","10px Console");
if (this.Task.Enabled) this.drawTextCentered(ctx,x,y+this.Height-16,this.Width,12,(this.Task.Time - (Date.now() - this.Task.LastCall)) + "ms " + "(" + this.InputPulses.length + ")","10px Console");
this.drawTextCentered(ctx,x,y,this.Width,this.Height,this.Designator,"12px Console","#000");
this.drawInputs(ctx,x,y);
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_DELAY = new ElementCatalog_Element("Delay","The delay element will delay any input signal for the given period then output it for the same length of time it came in. Inputs will be buffered and played back out at the period rate.","__|\\",DelayElement,[]);
ElementReferenceTable.push(ElementCatalog_DELAY);
ElementCategory_Timing.addElement(ElementCatalog_DELAY);
class output4bitDisplay extends Element {
constructor(_Container, RestoreData = null,logicengine) {
super(_Container, RestoreData,logicengine,4);
this.Name = "4B Display";
this.Output = false;
this.OutputChar = "0";
this.NoOutput = true;
this.Width = 100;
this.Height = 100;
this.Inputs = [0,0,0,0];
this.InputLabels = ["3","2","1","0"];
this.Type = 0;
this.removeProperty("Inputs");
let typeProperty = new ElementProperty("Type","list",{CBObject: this,CBFunction: "setType"},0,0,[{Value: "0", String: "Hex"},{Value: "1", String: "Decimal"},{Value: "2", String: "BCD"}],0,0);
this.Properties.push(typeProperty);
if (RestoreData) {
if (RestoreData.Properties.length > 0) {
console.log(RestoreData);
this.Properties[0].CurrentValue = RestoreData.Properties[0].CurrentValue;
this.Properties[0].Values = RestoreData.Properties[0].Values;
this.setType(this.Properties[0].CurrentValue);
}
}
}
setType(type) {
this.Properties[0].CurrentValue = type;
switch (type) {
case "0":
this.Type = 0;
break;
case "1":
this.Type = 1;
break;
case "2":
this.Type = 2;
break;
}
this.setInput(0,this.Inputs[0]); // Trigger a proper recalc of the display.
}
setInput(Input, Value) {
Value = this._Container.isHigh(this,Input);
this.Inputs[Input] = (Value !== false) ? 1 : 0;
let outchar = (this.Inputs[0] << 3) + (this.Inputs[1] << 2) + (this.Inputs[2] << 1) + (this.Inputs[3]);
this.OutputChar = (outchar);
switch (outchar) {
case 10:
if (this.Type == 0) this.OutputChar = 'A';
if (this.Type == 1) this.OutputChar = '10';
if (this.Type == 2) this.OutputChar = '0';
break;
case 11:
if (this.Type == 0) this.OutputChar = 'B';
if (this.Type == 1) this.OutputChar = '11';
if (this.Type == 2) this.OutputChar = '1';
break;
case 12:
if (this.Type == 0) this.OutputChar = 'C';
if (this.Type == 1) this.OutputChar = '12';
if (this.Type == 2) this.OutputChar = '2';
break;
case 13:
if (this.Type == 0) this.OutputChar = 'D';
if (this.Type == 1) this.OutputChar = '13';
if (this.Type == 2) this.OutputChar = '3';
break;
case 14:
if (this.Type == 0) this.OutputChar = 'E';
if (this.Type == 1) this.OutputChar = '14';
if (this.Type == 2) this.OutputChar = '4';
break;
case 15:
if (this.Type == 0) this.OutputChar = 'F';
if (this.Type == 1) this.OutputChar = '15';
if (this.Type == 2) this.OutputChar = '5';
break;
}
}
drawElement(x, y, ctx) {
this.drawBorderBox(ctx, x+20,y,this.Width-40,this.Height);
let fontStyle = "72px Console";
if (this.Type == 1) fontStyle = "36px Console";
this.drawTextCentered(ctx,x,y,this.Width,this.Height,this.OutputChar,fontStyle);
this.drawInputs(ctx,x,y);
}
}
let ElementCatalog_Output_4BDisplay = new ElementCatalog_Element("4B Display","A display! Takes a 4 bit input and shows hex value of the input.","[8]",output4bitDisplay,[]);
ElementReferenceTable.push(ElementCatalog_Output_4BDisplay);
ElementCategory_Outputs.addElement(ElementCatalog_Output_4BDisplay);
class inputElement extends Element {
constructor(_Container, RestoreData = null,logicengine) {
super(_Container, RestoreData,logicengine,0);
this.Name = "InputElement";
this.Output = false;
this.Width = 100;
this.removeProperty("Inputs");
}
getOutput(Output=0) {
return this.Output;
}
drawElement(x, y, ctx) {
this.drawBorderBox(ctx, x,y,this.Width,this.Height);
this.drawTextCentered(ctx,x,y,this.Width,this.Height,"IN");
this.drawOutputs(ctx,x,y);
}
}
class InputSwitch extends inputElement {
constructor(_Container, RestoreData = null, logicengine) {
super(_Container, RestoreData,logicengine);
this.Name = "Switch";
this.Height = 70;
if (RestoreData) this.Output = RestoreData.Output;
}
MouseClick(mousePos) {
mousePos = this.LogicEngine.getCanvasMousePos(mousePos);
super.MouseClick(mousePos);
if ((mousePos.x >= (this.X + 5)) && (mousePos.x <= (this.X + 55)) && (mousePos.y >= (this.Y + 5)) && (mousePos.y <= (this.Y + 55))) {
this.Output = !this.Output;
for (let a = 0; a < this.OutputConnections.length; a++) {
this.LogicEngine.RecursionCount = 0;
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput());
}
}
}
drawElement(x, y, ctx) {
this.drawBorderBox(ctx, x,y,this.Width-20,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor);
//this.drawBorderBox(ctx,x+5,y+5,50,50,1,"#ccc","#777");
this.drawBorderBox(ctx,x+5,y+25,50,10,1,"#ccc","#777");
if (this.getOutput()) {
this.drawBorderBox(ctx,x+15,y+5,30,25,1,"#ccc","#777");
this.drawTextCentered(ctx,x,y+this.Height - 30,this.Width-(this.outputCircleRadius*2)-20,12,"OFF","12px Console","#000");
} else {
this.drawBorderBox(ctx,x+15,y+30,30,25,1,"#ccc","#777");
this.drawTextCentered(ctx,x,y+7,this.Width-(this.outputCircleRadius*2)-20,12,"ON","12px Console","#000");
}
this.drawTextCentered(ctx,x,y+(this.Height-14),this.Width-(this.outputCircleRadius*2),12,this.Designator,"12px Console","#000");
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_SWITCH = new ElementCatalog_Element("Switch","The switch allows for toggling an output low or high.","|-",InputSwitch,[]);
ElementReferenceTable.push(ElementCatalog_SWITCH);
ElementCategory_Inputs.addElement(ElementCatalog_SWITCH);
class InputButton extends inputElement {
constructor(_Container, RestoreData = null, logicengine) {
super(_Container, RestoreData,logicengine);
this.Name = "Button";
this.Height = 70;
}
MouseDown(mousePos) {
mousePos = this.LogicEngine.getCanvasMousePos(mousePos);
if ((mousePos.x >= (this.X + 5)) && (mousePos.x <= (this.X + 55)) && (mousePos.y >= (this.Y + 5)) && (mousePos.y <= (this.Y + 55))) {
let old_output = this.Output;
this.Output = true;
this.LogicEngine.MouseDown = false; // Prevent movement on the button when its being pushed
if (old_output != this.Output) {
for (let a = 0; a < this.OutputConnections.length; a++) {
this.LogicEngine.RecursionCount = 0;
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput());
}
}
}
}
MouseUp(mousePos) {
let old_output = this.Output;
this.Output = false;
if (old_output != this.Output) {
for (let a = 0; a < this.OutputConnections.length; a++) {
this.LogicEngine.RecursionCount = 0;
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput());
}
}
}
drawElement(x, y, ctx) {
this.drawBorderBox(ctx, x,y,this.Width-20,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor);
this.drawBorderBox(ctx,x+5,y+5,50,50,1,"#ccc","#777");
this.drawTextCentered(ctx,x,y+(this.Height-14),this.Width-(this.outputCircleRadius*2),12,this.Designator,"12px Console","#000");
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_BUTTON = new ElementCatalog_Element("Button","The button only outputs high when the button is pressed.","[o]",InputButton,[]);
ElementReferenceTable.push(ElementCatalog_BUTTON);
ElementCategory_Inputs.addElement(ElementCatalog_BUTTON);
class FlipFlopJK extends Element {
constructor(_Container, RestoreData = null, logicengine) {
super(_Container, RestoreData,logicengine,3);
this.Name = "JK-FF";
this.Outputs = new Array(2);
this.InputLabels = new Array("J","CLK","K");
this.OutputLabels = new Array("Q","~Q");
this.removeProperty("Inputs");
this.Height = 80;
if (RestoreData) {
this.Outputs = RestoreData.OutputStates;
}
}
toJSON(key) {
let $superjson = super.toJSON(key);
$superjson.OutputStates = this.Outputs;
return $superjson;
}
setInput(Input, Value) {
if (Input >= this.Inputs.length) return false;
Value = this._Container.isHigh(this,Input);
if (Value !== false) Value = true;
let oldOutput = this.Outputs[0];
let oldOutput2 = this.Outputs[1];
this.Inputs[Input] = Value;
if (this.Inputs[1]) {
if (!this.Inputs[0] && this.Inputs[2]) {
// set Q low
this.Outputs[0] = false;
this.Outputs[1] = true;
} else if (this.Inputs[0] && !this.Inputs[2]) {
// set Q low
this.Outputs[0] = true;
this.Outputs[1] = false;
} else if (this.Inputs[0] && this.Inputs[2]) {
// set Q low
this.Outputs[0] = !this.Outputs[0];
this.Outputs[1] = !this.Outputs[0];
}
}
if (oldOutput != this.getOutput(0) || oldOutput2 != this.getOutput(1)) {
this.setConnections();
}
}
getOutput(Output=0) {
return this.Outputs[Output];
}
drawElement(x,y,ctx) {
let xOffset = 20;
this.drawBorderBox(ctx, x+20,y,this.Width-40,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor);
this.drawTextCentered(ctx,x,y+(this.Height-14),this.Width,14,this.Designator,"10px Console","#000");
this.drawInputs(ctx,x,y);
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_JKFlipFlop = new ElementCatalog_Element("JK-FF","The JK Flip-Flop is a common type of flip-flop that allows for either setting in a specific state, or toggling state.","",FlipFlopJK,[]);
ElementReferenceTable.push(ElementCatalog_JKFlipFlop);
ElementCategory_FlipFlop.addElement(ElementCatalog_JKFlipFlop);
class FlipFlopSR extends Element {
constructor(_Container, RestoreData = null, logicengine) {
super(_Container, RestoreData,logicengine,3);
this.Name = "SR-FF";
this.Outputs = new Array(2);
this.InputLabels = new Array("S","CLK","R");
this.OutputLabels = new Array("Q","~Q");
this.removeProperty("Inputs");
this.Height = 80;
if (RestoreData) {
this.Outputs = RestoreData.OutputStates;
}
}
toJSON(key) {
let $superjson = super.toJSON(key);
$superjson.OutputStates = this.Outputs;
return $superjson;
}
setInput(Input, Value) {
if (Input >= this.Inputs.length) return false;
Value = this._Container.isHigh(this,Input);
if (Value !== false) Value = true;
let oldOutput = this.Outputs[0];
let oldOutput2 = this.Outputs[1];
this.Inputs[Input] = Value;
if (this.Inputs[1]) {
if (!this.Inputs[0] && this.Inputs[2]) {
// set Q low
this.Outputs[0] = false;
this.Outputs[1] = true;
} else if (this.Inputs[0] && !this.Inputs[2]) {
// set Q low
this.Outputs[0] = true;
this.Outputs[1] = false;
}
}
if (oldOutput != this.getOutput(0) || oldOutput2 != this.getOutput(1)) {
this.setConnections();
}
}
getOutput(Output=0) {
return this.Outputs[Output];
}
drawElement(x,y,ctx) {
let xOffset = 20;
this.drawBorderBox(ctx, x+20,y,this.Width-40,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor);
this.drawTextCentered(ctx,x,y+(this.Height-14),this.Width,14,this.Designator,"10px Console","#000");
this.drawInputs(ctx,x,y);
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_SRFlipFlop = new ElementCatalog_Element("SR-FF","The SR Flip-Flop is a common type of flip-flop that allows for either setting, or resetting the output state.","",FlipFlopSR,[]);
ElementReferenceTable.push(ElementCatalog_SRFlipFlop);
ElementCategory_FlipFlop.addElement(ElementCatalog_SRFlipFlop);
class FlipFlopT extends Element {
constructor(_Container, RestoreData = null, logicengine) {
super(_Container, RestoreData,logicengine,2);
this.Name = "T-FF";
this.Outputs = new Array(2);
this.InputLabels = new Array("T","CLK");
this.OutputLabels = new Array("Q","~Q");
this.removeProperty("Inputs");
this.Height = 80;
if (RestoreData) {
this.Outputs = RestoreData.OutputStates;
}
}
toJSON(key) {
let $superjson = super.toJSON(key);
$superjson.OutputStates = this.Outputs;
return $superjson;
}
setInput(Input, Value) {
if (Input >= this.Inputs.length) return false;
Value = this._Container.isHigh(this,Input);
if (Value !== false) Value = true;
let oldOutput = this.Outputs[0];
let oldOutput2 = this.Outputs[1];
this.Inputs[Input] = Value;
if (this.Inputs[0] && this.Inputs[1]) {
this.Outputs[0] = !this.Outputs[0];
this.Outputs[1] = !this.Outputs[0];
}
if (oldOutput != this.getOutput(0) || oldOutput2 != this.getOutput(1)) {
this.setConnections();
}
}
getOutput(Output=0) {
return this.Outputs[Output];
}
drawElement(x,y,ctx) {
let xOffset = 20;
this.drawBorderBox(ctx, x+20,y,this.Width-40,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor);
this.drawTextCentered(ctx,x,y+(this.Height-14),this.Width,14,this.Designator,"10px Console","#000");
this.drawInputs(ctx,x,y);
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_TFlipFlop = new ElementCatalog_Element("T-FF","The T Flip-Flop is a common type of flip-flop that toggles the output when T is high and CLK goes high.","",FlipFlopT,[]);
ElementReferenceTable.push(ElementCatalog_TFlipFlop);
ElementCategory_FlipFlop.addElement(ElementCatalog_TFlipFlop);
class FlipFlopD extends Element {
constructor(_Container, RestoreData = null, logicengine) {
super(_Container, RestoreData,logicengine,2);
this.Name = "D-FF";
this.Outputs = new Array(2);
this.InputLabels = new Array("D","CLK");
this.OutputLabels = new Array("Q","~Q");
this.removeProperty("Inputs");
this.Height = 80;
if (RestoreData) {
this.Outputs = RestoreData.OutputStates;
}
}
toJSON(key) {
let $superjson = super.toJSON(key);
$superjson.OutputStates = this.Outputs;
return $superjson;
}
setInput(Input, Value) {
if (Input >= this.Inputs.length) return false;
Value = this._Container.isHigh(this,Input);
if (Value !== false) Value = true;
let oldOutput = this.Outputs[0];
let oldOutput2 = this.Outputs[1];
let oldInput = this.Inputs[1];
this.Inputs[Input] = Value;
if (this.Inputs[1] && !oldInput) {
this.Outputs[0] = this.Inputs[0];
this.Outputs[1] = !this.Outputs[0];
}
if (oldOutput != this.getOutput(0) || oldOutput2 != this.getOutput(1)) {
this.setConnections();
}
}
getOutput(Output=0) {
return this.Outputs[Output];
}
drawElement(x,y,ctx) {
let xOffset = 20;
this.drawBorderBox(ctx, x+20,y,this.Width-40,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor);
this.drawTextCentered(ctx,x,y+(this.Height-14),this.Width,14,this.Designator,"10px Console","#000");
this.drawInputs(ctx,x,y);
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_DFlipFlop = new ElementCatalog_Element("D-FF","The D Flip-Flop is a common type of flip-flop that sets the output to equal D if the clock goes high","",FlipFlopD,[]);
ElementReferenceTable.push(ElementCatalog_DFlipFlop);
ElementCategory_FlipFlop.addElement(ElementCatalog_DFlipFlop);
class LogicAND extends Element {
constructor(_Container, RestoreData = null, logicengine,Inputs) {
super(_Container, RestoreData,logicengine,Inputs);
this.Name = "AND";
}
getOutput(Output=0) {
let ANDResult = true;
for (let a = 0; a < this.totalInputs();a++) {
if (!this.Inputs[a]) ANDResult = false;
}
return ANDResult;
}
drawElement(x,y,ctx) {
//this.drawBorderBox(ctx, x+10,y,this.Width-20,this.Height);
let xOffset = 20;
let shadowColor = this.LogicEngine.Settings.ShadowColor;
ctx.save();
ctx.beginPath();
ctx.moveTo(x+xOffset,y);
ctx.lineTo((x+xOffset)+ this.Width/4,y);
ctx.quadraticCurveTo(x+(this.Width - xOffset),y,x+(this.Width-xOffset),y+(this.Height/2));
ctx.quadraticCurveTo(x+(this.Width - xOffset),y+this.Height,(x+xOffset)+ this.Width/4,y+this.Height);
ctx.lineTo(x+xOffset,y+this.Height);
ctx.lineTo(x+xOffset,y);
ctx.lineWidth = "3";
ctx.fillStyle = "#f7e979";
ctx.shadowColor = shadowColor;
ctx.shadowBlur = "6";
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.stroke();
ctx.fill();
ctx.restore();
this.drawTextCentered(ctx,x,y,this.Width,this.Height,this.Designator,"12px Console","#000");
this.drawInputs(ctx,x,y);
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_AND = new ElementCatalog_Element("AND","The AND gate only outputs high when all of the inputs are high.","&",LogicAND,[2]);
ElementReferenceTable.push(ElementCatalog_AND);
ElementCategory_LOGIC.addElement(ElementCatalog_AND);
class LogicNAND extends LogicAND {
constructor(_Container, RestoreData = null, logicengine,Inputs) {
super(_Container, RestoreData,logicengine,Inputs);
this.Name = "NAND";
this.Width = this.Width + 10;
}
getOutput(Output=0) {
if (super.getOutput()) {
return false;
} else {
return true;
}
}
drawElement(x,y,ctx) {
let xOffset = 20;
let shadowColor = this.LogicEngine.Settings.ShadowColor;
ctx.save();
ctx.beginPath();
ctx.moveTo(x+xOffset,y);
ctx.lineTo((x+xOffset)+ this.Width/4,y);
ctx.quadraticCurveTo(x+(this.Width - (xOffset*1.5)),y,x+(this.Width-(xOffset*1.5)),y+(this.Height/2));
ctx.quadraticCurveTo(x+(this.Width - (xOffset*1.5)),y+this.Height,(x+xOffset)+ this.Width/4,y+this.Height);
ctx.lineTo(x+xOffset,y+this.Height);
ctx.lineTo(x+xOffset,y);
ctx.lineWidth = "3";
ctx.fillStyle = "#f7e979";
ctx.strokeStyle = "#000000";
ctx.shadowColor = shadowColor;
ctx.shadowBlur = "6";
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.stroke();
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "#000000";
ctx.strokeStyle = "#000000";
ctx.lineWidth = "1";
ctx.arc(x + (this.Width - 25),y + (this.Height/2),5,0,2*Math.PI);
ctx.stroke();
ctx.fill();
ctx.restore();
this.drawTextCentered(ctx,x,y,this.Width-xOffset,this.Height,this.Designator,"12px Console","#000");
this.drawInputs(ctx,x,y);
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_NAND = new ElementCatalog_Element("NAND","The NAND gate always outputs a high signal unless all the inputs are high then it goes low.","!&",LogicNAND,[2]);
ElementReferenceTable.push(ElementCatalog_NAND);
ElementCategory_LOGIC.addElement(ElementCatalog_NAND);
class LogicOR extends Element {
constructor(_Container, RestoreData = null, logicengine,Inputs) {
super(_Container, RestoreData,logicengine,Inputs);
this.Name = "OR";
}
getOutput(Output=0) {
let ORResult = false;
for (let a = 0; a < this.totalInputs();a++) {
if (this.Inputs[a]) ORResult = true;
}
return ORResult;
}
drawElement(x,y,ctx) {
let drawWidth = this.Width;
let drawHeight = this.Height;
let xOffset = 20;
let shadowColor = this.LogicEngine.Settings.ShadowColor;
ctx.save();
ctx.beginPath();
ctx.moveTo(x+(xOffset/4),y);
ctx.lineTo((x+xOffset)+ this.Width/4,y);
ctx.quadraticCurveTo(x+(this.Width - (xOffset*2)),y,x+(this.Width-xOffset),y+(this.Height/2));
ctx.quadraticCurveTo(x+(this.Width - (xOffset*2)),y+this.Height,(x+xOffset)+ this.Width/4,y+this.Height);
ctx.lineTo(x+(xOffset/4),y+this.Height);
ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height - (this.Height/32)),x+xOffset+5,y+(this.Height/2));
ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height/32),x+(xOffset/4),y);
ctx.lineWidth = "3";
ctx.fillStyle = "#f7e979";
ctx.shadowColor = shadowColor;
ctx.shadowBlur = "6";
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.stroke();
ctx.fill();
ctx.restore();
this.drawTextCentered(ctx,x,y,this.Width,this.Height,this.Designator,"12px Console","#000");
this.drawInputs(ctx,x,y);
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_OR = new ElementCatalog_Element("OR","The OR gate outputs a high when any input is HIGH.","|",LogicOR,[2]);
ElementReferenceTable.push(ElementCatalog_OR);
ElementCategory_LOGIC.addElement(ElementCatalog_OR);
class LogicNOR extends LogicOR {
constructor(_Container, RestoreData = null, logicengine,Inputs) {
super(_Container, RestoreData,logicengine,Inputs);
this.Name = "NOR";
this.Width = this.Width + 10;
}
getOutput(Output=0) {
if (super.getOutput()) {
return false;
} else {
return true;
}
}
drawElement(x,y,ctx) {
let xOffset = 20;
let shadowColor = this.LogicEngine.Settings.ShadowColor;
ctx.save();
ctx.beginPath();
ctx.moveTo(x+(xOffset/4),y);
ctx.lineTo((x+xOffset)+ this.Width/4,y);
ctx.quadraticCurveTo(x+(this.Width - (xOffset*2.5)),y,x+(this.Width-(xOffset*1.5)),y+(this.Height/2));
ctx.quadraticCurveTo(x+(this.Width - (xOffset*2.5)),y+this.Height,(x+xOffset)+ this.Width/4,y+this.Height);
ctx.lineTo(x+(xOffset/4),y+this.Height);
ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height - (this.Height/32)),x+xOffset+5,y+(this.Height/2));
ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height/32),x+(xOffset/4),y);
ctx.lineWidth = "3";
ctx.fillStyle = "#f7e979";
ctx.shadowColor = shadowColor;
ctx.shadowBlur = "6";
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.stroke();
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "#000000";
ctx.strokeStyle = "#000000";
ctx.lineWidth = "1";
ctx.arc(x + (this.Width - 25),y + (this.Height/2),5,0,2*Math.PI);
ctx.stroke();
ctx.fill();
ctx.restore();
this.drawTextCentered(ctx,x,y,this.Width-xOffset,this.Height,this.Designator,"12px Console","#000");
this.drawInputs(ctx,x,y);
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_NOR = new ElementCatalog_Element("NOR","The NOR gate outputs a high only when all inputs are low.","!|",LogicNOR,[2]);
ElementReferenceTable.push(ElementCatalog_NOR);
ElementCategory_LOGIC.addElement(ElementCatalog_NOR);
class LogicXOR extends Element {
constructor(_Container, RestoreData = null, logicengine) {
super(_Container, RestoreData,logicengine,2); // Only 2 inputs on XOR
this.Name = "XOR";
this.removeProperty("Inputs");
}
getOutput(Output=0) {
let ORResult = false;
if ( (this.Inputs[0] && !this.Inputs[1]) || (!this.Inputs[0] && this.Inputs[1]) ) ORResult = true;
return ORResult;
}
drawElement(x,y,ctx) {
let drawWidth = this.Width;
let drawHeight = this.Height;
let xOffset = 20;
let shadowColor = this.LogicEngine.Settings.ShadowColor;
ctx.save();
ctx.beginPath();
ctx.moveTo(x+(xOffset/4),y);
ctx.lineTo((x+xOffset)+ this.Width/4,y);
ctx.quadraticCurveTo(x+(this.Width - (xOffset*2)),y,x+(this.Width-xOffset),y+(this.Height/2));
ctx.quadraticCurveTo(x+(this.Width - (xOffset*2)),y+this.Height,(x+xOffset)+ this.Width/4,y+this.Height);
ctx.lineTo(x+(xOffset/4),y+this.Height);
ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height - (this.Height/32)),x+xOffset+5,y+(this.Height/2));
ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height/32),x+(xOffset/4),y);
ctx.lineWidth = "3";
ctx.fillStyle = "#f7e979";
ctx.shadowColor = shadowColor;
ctx.shadowBlur = "6";
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.stroke();
ctx.fill();
ctx.lineWidth = "2";
ctx.beginPath();
ctx.shadowColor = "transparent";
ctx.moveTo(x+(xOffset/4)+10,y+this.Height);
ctx.quadraticCurveTo(x+(xOffset+10),y+(this.Height - (this.Height/32)),x+xOffset+10,y+(this.Height/2));
ctx.quadraticCurveTo(x+(xOffset+10),y+(this.Height/32),x+(xOffset/4)+10,y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x+(xOffset/4),y+this.Height);
ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height - (this.Height/32)),x+xOffset+5,y+(this.Height/2));
ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height/32),x+(xOffset/4),y);
ctx.stroke();
ctx.restore();
this.drawTextCentered(ctx,x,y,this.Width,this.Height,this.Designator,"12px Console","#000");
this.drawInputs(ctx,x,y);
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_XOR = new ElementCatalog_Element("XOR","The XOR gate outputs a high when only 1 input is high.","^",LogicXOR,[]);
ElementReferenceTable.push(ElementCatalog_XOR);
ElementCategory_LOGIC.addElement(ElementCatalog_XOR);
class LogicXNOR extends Element {
constructor(_Container, RestoreData = null, logicengine) {
super(_Container, RestoreData,logicengine,2); // Only 2 inputs on XOR
this.Name = "XNOR";
this.removeProperty("Inputs");
this.Width += 10;
}
getOutput(Output=0) {
let ORResult = false;
if ( (this.Inputs[0] && !this.Inputs[1]) || (!this.Inputs[0] && this.Inputs[1]) ) ORResult = true;
return !ORResult;
}
drawElement(x,y,ctx) {
let drawWidth = this.Width;
let drawHeight = this.Height;
let xOffset = 20;
let shadowColor = this.LogicEngine.Settings.ShadowColor;
ctx.save();
ctx.beginPath();
ctx.save();
ctx.beginPath();
ctx.moveTo(x+(xOffset/4),y);
ctx.lineTo((x+xOffset)+ this.Width/4,y);
ctx.quadraticCurveTo(x+(this.Width - (xOffset*2.5)),y,x+(this.Width-(xOffset*1.5)),y+(this.Height/2));
ctx.quadraticCurveTo(x+(this.Width - (xOffset*2.5)),y+this.Height,(x+xOffset)+ this.Width/4,y+this.Height);
ctx.lineTo(x+(xOffset/4),y+this.Height);
ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height - (this.Height/32)),x+xOffset+5,y+(this.Height/2));
ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height/32),x+(xOffset/4),y);
ctx.lineWidth = "3";
ctx.fillStyle = "#f7e979";
ctx.shadowColor = shadowColor;
ctx.shadowBlur = "6";
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.stroke();
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "#000000";
ctx.strokeStyle = "#000000";
ctx.lineWidth = "1";
ctx.arc(x + (this.Width - 25),y + (this.Height/2),5,0,2*Math.PI);
ctx.stroke();
ctx.fill();
ctx.lineWidth = "2";
ctx.beginPath();
ctx.shadowColor = "transparent";
ctx.moveTo(x+(xOffset/4)+10,y+this.Height);
ctx.quadraticCurveTo(x+(xOffset+10),y+(this.Height - (this.Height/32)),x+xOffset+10,y+(this.Height/2));
ctx.quadraticCurveTo(x+(xOffset+10),y+(this.Height/32),x+(xOffset/4)+10,y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x+(xOffset/4),y+this.Height);
ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height - (this.Height/32)),x+xOffset+5,y+(this.Height/2));
ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height/32),x+(xOffset/4),y);
ctx.stroke();
ctx.restore();
this.drawTextCentered(ctx,x,y,this.Width-(xOffset/2),this.Height,this.Designator,"10px Console","#000");
this.drawInputs(ctx,x,y);
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_XNOR = new ElementCatalog_Element("XNOR","The XNOR gate outputs a high only when both inputs are either low or high.","!^",LogicXNOR,[]);
ElementReferenceTable.push(ElementCatalog_XNOR);
ElementCategory_LOGIC.addElement(ElementCatalog_XNOR);
class LogicNOT extends Element {
constructor(_Container, RestoreData = null, logicengine) {
super(_Container, RestoreData,logicengine,1); // Only 1 inputs on NOT
this.Name = "NOT";
this.removeProperty("Inputs");
this.Width += 10;
}
getOutput(Output=0) {
if (this.Inputs[0]) return false;
return true;
}
drawElement(x,y,ctx) {
let drawWidth = this.Width;
let drawHeight = this.Height;
let xOffset = 20;
let shadowColor = this.LogicEngine.Settings.ShadowColor;
ctx.save();
ctx.beginPath();
ctx.moveTo(x+xOffset,y);
ctx.lineTo(x+(this.Width-(xOffset+10)),y+(this.Height/2));
ctx.lineTo(x+xOffset,y+this.Height);
ctx.lineTo(x+xOffset,y);
ctx.lineWidth = "3";
ctx.fillStyle = "#f7e979";
ctx.shadowColor = shadowColor;
ctx.shadowBlur = "6";
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.stroke();
ctx.fill();
ctx.beginPath();
ctx.fillStyle = "#000000";
ctx.strokeStyle = "#000000";
ctx.lineWidth = "1";
ctx.arc(x + (this.Width - 25),y + (this.Height/2),5,0,2*Math.PI);
ctx.stroke();
ctx.fill();
ctx.restore();
this.drawTextCentered(ctx,x,y,this.Width-(xOffset),this.Height,this.Designator,"12px Console","#000");
this.drawInputs(ctx,x,y);
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_NOT = new ElementCatalog_Element("NOT","The NOT gate outputs the opposite of the input.","!",LogicNOT,[]);
ElementReferenceTable.push(ElementCatalog_NOT);
ElementCategory_LOGIC.addElement(ElementCatalog_NOT);
class LogicBuffer extends Element {
ClockTick() {
this.Output = this.Inputs[0];
this.Task.Enabled = false;
this.Task.LastCall = 0;
for (let a = 0; a < this.OutputConnections.length; a++) {
this.LogicEngine.RecursionCount = 0;
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput());
}
}
Delete() {
super.Delete();
this.LogicEngine.Scheduler.deleteTask(this.Task);
}
getOutput(Output=0) {
return this.Output;
}
setInput(Input, Value) {
if (Input > 0) return;
//super.setInput(Input, Value);
Value = this._Container.isHigh(this,Input);
if (Value !== false) Value = true;
this.Inputs[Input] = Value;
if (!this.Task.Enabled) {
this.Task.LastCall = 0;
this.Task.Enabled = true;
}
}
constructor(_Container, RestoreData = null, logicengine) {
super(_Container, RestoreData,logicengine,1);
this.removeProperty("Inputs");
this.Output = false;
this.Name = "Buffer";
this.Width = 100;
this.Task = new Task("BufferTask","Buffer",0,9999999,this.ClockTick.bind(this));
this.Task.Time = 4;
this.Task.Enabled = false;
this.Task.LastCall = 0;
this.removeProperty("Inputs");
if (RestoreData) this.Output = RestoreData.Output;
this.LogicEngine.Scheduler.addTask(this.Task);
}
toJSON(key) {
let superjson = super.toJSON(key);
superjson.Task = {
Enabled: this.Task.Enabled,
Time: this.Task.Time,
LastCall: Date.now() - this.Task.LastCall,
CallCount: this.Task.CallCount
};
return superjson;
}
drawElement(x, y, ctx) {
let drawWidth = this.Width;
let drawHeight = this.Height;
let xOffset = 20;
let shadowColor = this.LogicEngine.Settings.ShadowColor;
ctx.save();
ctx.beginPath();
ctx.moveTo(x+xOffset,y);
ctx.lineTo(x+(this.Width-(xOffset)),y+(this.Height/2));
ctx.lineTo(x+xOffset,y+this.Height);
ctx.lineTo(x+xOffset,y);
ctx.lineWidth = "3";
ctx.fillStyle = "#f7e979";
ctx.shadowColor = shadowColor;
ctx.shadowBlur = "6";
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.stroke();
ctx.fill();
ctx.restore();
this.drawTextCentered(ctx,x,y,this.Width-(xOffset),this.Height,this.Designator,"12px Console","#000");
this.drawInputs(ctx,x,y);
this.drawOutputs(ctx,x,y);
}
}
let ElementCatalog_BUFFER = new ElementCatalog_Element("Buffer","The buffer is a special gate to allow the prevention of recursion loops.","|>",LogicBuffer,[]);
ElementReferenceTable.push(ElementCatalog_BUFFER);
ElementCategory_LOGIC.addElement(ElementCatalog_BUFFER);