1292 lines
45 KiB
JavaScript
1292 lines
45 KiB
JavaScript
let ElementReferenceTable = new Array();
|
|
|
|
let ElementCategory_Inputs = new ElementCatalog_Category("Inputs","");
|
|
let ElementCategory_LOGIC = new ElementCatalog_Category("Logic" ,"");
|
|
let ElementCategory_Timing = new ElementCatalog_Category("Timing" ,"");
|
|
let elementCatalog = new ElementCatalog([ElementCategory_Inputs,
|
|
ElementCategory_LOGIC,
|
|
ElementCategory_Timing]);
|
|
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) {
|
|
this.Container = elementContainer;
|
|
this.Element = element;
|
|
this.Input = input;
|
|
}
|
|
toJSON(key) {
|
|
return {
|
|
Element: this.Element.Designator,
|
|
Input: parseInt(this.Input)
|
|
};
|
|
}
|
|
}
|
|
|
|
class Element extends CanvasTools {
|
|
|
|
constructor(RestoreData = null,logicengine,Inputs) {
|
|
super();
|
|
this.Name = "Element";
|
|
this.Designator = "";
|
|
this.Inputs = new Array(Inputs);
|
|
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;
|
|
|
|
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 (this.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;
|
|
}
|
|
}
|
|
|
|
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
|
|
for (let a = 0; a < this.OutputConnections.length;a++) {
|
|
this.LogicEngine.RecursionCount = 0;
|
|
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input,false);
|
|
}
|
|
}
|
|
|
|
MouseDown(mousePos) {
|
|
return;
|
|
}
|
|
|
|
MouseUp(mousePos) {
|
|
return;
|
|
}
|
|
|
|
MouseClick(mousePos) {
|
|
|
|
let mouseDistOutput = length2D(this.X+(this.Width-10),
|
|
this.Y+(this.Height/2),
|
|
this.MousePosition.x,
|
|
this.MousePosition.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),
|
|
this.MousePosition.x,
|
|
this.MousePosition.y);
|
|
if (mouseDist <= (this.inputCircleRadius)) {
|
|
this.LogicEngine.Link(a);
|
|
foundInput = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
} else {
|
|
if (mouseDistOutput <= (this.outputCircleRadius)) {
|
|
// Clicked on output, let us start a link
|
|
this.LogicEngine.Link();
|
|
}
|
|
}
|
|
}
|
|
|
|
mouseInside(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) {
|
|
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
|
|
this.LogicEngine.RecursionCount = 0;
|
|
element.setInput(input,false);
|
|
this.OutputConnections.splice(a,1);
|
|
return;
|
|
}
|
|
}
|
|
let newConnection = new ElementConnection(container,element,input);
|
|
this.OutputConnections.push(newConnection);
|
|
this.LogicEngine.RecursionCount = 0;
|
|
element.setInput(input,this.getOutput());
|
|
}
|
|
|
|
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();
|
|
}
|
|
ctx.restore();
|
|
}
|
|
|
|
drawOutputs(ctx,x,y,borderColor = "#000",circleColorFalse = "#ff0000",circleColorTrue="#00ff00",circleColorHover="#00ffff") {
|
|
let old_strokeStyle = ctx.strokeStyle;
|
|
let old_fillStyle = ctx.fillStyle;
|
|
let mouseDist = length2D(x+(this.Width-10),y+(this.Height/2),this.MousePosition.x,this.MousePosition.y);
|
|
ctx.beginPath();
|
|
ctx.arc(x+(this.Width-10),y+(this.Height/2),this.outputCircleRadius,0,2*Math.PI);
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.fillStyle = circleColorFalse;
|
|
if (this.getOutput()) ctx.fillStyle = circleColorTrue;
|
|
if ((mouseDist <= (this.outputCircleRadius)) && !this.LogicEngine.ActiveLink) ctx.fillStyle = circleColorHover;
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
ctx.strokeStyle = old_strokeStyle;
|
|
ctx.fillStyle = old_fillStyle;
|
|
}
|
|
|
|
drawConnections(ctx,settings) {
|
|
ctx.save();
|
|
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 = this.Y+(this.Height/2);
|
|
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.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()) ctx.strokeStyle = settings.InactiveConnectionColor;
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
ctx.restore();
|
|
}
|
|
|
|
setInput(Input,Value) {
|
|
let oldOutput = this.getOutput();
|
|
if (Value) {
|
|
Value = true;
|
|
} else {
|
|
Value = false;
|
|
}
|
|
if (Input < this.totalInputs()) {
|
|
this.Inputs[Input] = Value;
|
|
} else {
|
|
return;
|
|
}
|
|
if (this.getOutput() != oldOutput) {
|
|
// The output changed, we need to notify connected elements
|
|
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.LogicEngine.RecursionCount--;
|
|
}
|
|
}
|
|
}
|
|
|
|
getOutput() {
|
|
/*
|
|
Should return true or false
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
drawElement(x,y,ctx) {
|
|
/*
|
|
Draw routine for the element
|
|
*/
|
|
this.drawBorderBox(ctx,x+10,y,drawWidth-20,drawHeight);
|
|
this.drawTextCentered(ctx,x,y,this.Width,this.Height,"LOGIC");
|
|
this.drawInputs(ctx,x,y);
|
|
this.drawOutputs(ctx,x,y);
|
|
}
|
|
|
|
}
|
|
|
|
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() {
|
|
return this.Output;
|
|
}
|
|
|
|
setInput(Input, 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(RestoreData = null, logicengine) {
|
|
super(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() {
|
|
return this.Output;
|
|
}
|
|
|
|
setInput(Input, Value) {
|
|
if (Input > 0) return;
|
|
//super.setInput(Input, Value);
|
|
this.Inputs[Input] = Value;
|
|
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(RestoreData = null, logicengine) {
|
|
super(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() {
|
|
return this.Output;
|
|
}
|
|
|
|
setInput(Input, Value) {
|
|
if (Input > 0) return;
|
|
if (this.Inputs[Input] == Value) 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(RestoreData = null, logicengine) {
|
|
super(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 inputElement extends Element {
|
|
constructor(RestoreData = null,logicengine) {
|
|
super(RestoreData,logicengine,0);
|
|
this.Name = "InputElement";
|
|
this.Output = false;
|
|
this.Width = 100;
|
|
this.removeProperty("Inputs");
|
|
}
|
|
|
|
getOutput() {
|
|
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(RestoreData = null, logicengine) {
|
|
super(RestoreData,logicengine);
|
|
this.Name = "Switch";
|
|
this.Height = 70;
|
|
if (RestoreData) this.Output = RestoreData.Output;
|
|
}
|
|
|
|
MouseClick(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(RestoreData = null, logicengine) {
|
|
super(RestoreData,logicengine);
|
|
this.Name = "Button";
|
|
this.Height = 70;
|
|
}
|
|
|
|
MouseDown(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 LogicAND extends Element {
|
|
constructor(RestoreData = null, logicengine,Inputs) {
|
|
super(RestoreData,logicengine,Inputs);
|
|
this.Name = "AND";
|
|
}
|
|
|
|
getOutput() {
|
|
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(RestoreData = null, logicengine,Inputs) {
|
|
super(RestoreData,logicengine,Inputs);
|
|
this.Name = "NAND";
|
|
this.Width = this.Width + 10;
|
|
}
|
|
getOutput() {
|
|
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(RestoreData = null, logicengine,Inputs) {
|
|
super(RestoreData,logicengine,Inputs);
|
|
this.Name = "OR";
|
|
}
|
|
|
|
getOutput() {
|
|
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(RestoreData = null, logicengine,Inputs) {
|
|
super(RestoreData,logicengine,Inputs);
|
|
this.Name = "NOR";
|
|
this.Width = this.Width + 10;
|
|
}
|
|
getOutput() {
|
|
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(RestoreData = null, logicengine) {
|
|
super(RestoreData,logicengine,2); // Only 2 inputs on XOR
|
|
this.Name = "XOR";
|
|
this.removeProperty("Inputs");
|
|
}
|
|
|
|
getOutput() {
|
|
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(RestoreData = null, logicengine) {
|
|
super(RestoreData,logicengine,2); // Only 2 inputs on XOR
|
|
this.Name = "XNOR";
|
|
this.removeProperty("Inputs");
|
|
this.Width += 10;
|
|
}
|
|
|
|
getOutput() {
|
|
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(RestoreData = null, logicengine) {
|
|
super(RestoreData,logicengine,1); // Only 1 inputs on NOT
|
|
this.Name = "NOT";
|
|
this.removeProperty("Inputs");
|
|
this.Width += 10;
|
|
}
|
|
|
|
getOutput() {
|
|
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() {
|
|
return this.Output;
|
|
}
|
|
|
|
setInput(Input, Value) {
|
|
if (Input > 0) return;
|
|
//super.setInput(Input, Value);
|
|
this.Inputs[Input] = Value;
|
|
if (!this.Task.Enabled) {
|
|
this.Task.LastCall = 0;
|
|
this.Task.Enabled = true;
|
|
}
|
|
}
|
|
|
|
constructor(RestoreData = null, logicengine) {
|
|
super(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);
|
|
|