910 lines
28 KiB
JavaScript
910 lines
28 KiB
JavaScript
/*
|
|
First a few needed global functions
|
|
*/
|
|
function getMousePos(canvas, evt) {
|
|
let rect = canvas.getBoundingClientRect();
|
|
return {
|
|
x: evt.clientX - rect.left,
|
|
y: evt.clientY - rect.top
|
|
};
|
|
}
|
|
|
|
function averageArray(arr) {
|
|
return arr.reduce((a, b) => (a + b)) / arr.length;
|
|
}
|
|
|
|
function length2D(x1,y1,x2,y2) {
|
|
let xDist = x1 - x2;
|
|
let yDist = y1 - y2;
|
|
return Math.sqrt(xDist*xDist + yDist * yDist);
|
|
}
|
|
|
|
/*
|
|
Now we can get into the classes
|
|
*/
|
|
|
|
class LogicEngineSettings {
|
|
constructor() {
|
|
this.ActiveConnectionColor = "#aabbaa";
|
|
this.InactiveConnectionColor = "#bbaaaa";
|
|
this.LinkWidth = "2";
|
|
this.LinkDash = [];
|
|
this.LinkingConnectionColor = "#aabbbb";
|
|
this.LinkingWidth = "3";
|
|
this.LinkingDash = [2,2];
|
|
}
|
|
}
|
|
class Task {
|
|
constructor(taskname,taskdescription,tasktype,tasktime,callback,deleteonrun = false) {
|
|
// tasktype: 0: interval, 1: fixed time
|
|
this.Name = taskname;
|
|
this.Description = taskdescription;
|
|
this.Type = tasktype;
|
|
this.Enabled = true;
|
|
this.Time = tasktime;
|
|
this.LastCall = Date.now();
|
|
this.CallCount = 0;
|
|
this.DeleteOnRun = false;
|
|
if (deleteonrun) this.DeleteOnRun = true;
|
|
this.Callback = callback;
|
|
if (!(tasktype >= 0 && tasktype <= 1)) this.Type = 0;
|
|
}
|
|
CheckTime() {
|
|
let time = this.Time;
|
|
if (this.Type == 0) time = this.LastCall + this.Time;
|
|
if (this.Enabled && (Date.now() >= time)) {
|
|
this.LastCall = Date.now();
|
|
this.CallCount++;
|
|
if (this.Type == 1 || this.DeleteOnRun == true) this.Enabled = false;
|
|
this.Callback();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
class ScheduleEngine {
|
|
constructor() {
|
|
this.Tasks = new Array();
|
|
}
|
|
|
|
addTask(task) {
|
|
this.Tasks.push(task);
|
|
}
|
|
|
|
deleteTask(task) {
|
|
for (let a = 0; a < this.Tasks.length; a++) {
|
|
if (this.Tasks[a] == task) {
|
|
this.Tasks.splice(a,1);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Tick() {
|
|
for (let a = 0; a < this.Tasks.length; a++) {
|
|
this.Tasks[a].CheckTime();
|
|
if (!this.Tasks[a].Enabled && this.Tasks[a].DeleteOnRun) {
|
|
this.Tasks.splice(a,1);
|
|
a--;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
class CanvasTools {
|
|
|
|
constructor() {
|
|
}
|
|
|
|
textSize(ctx,text,fontStyle) {
|
|
ctx.save();
|
|
ctx.font = fontStyle;
|
|
let tHeight = Math.round(ctx.measureText(text).actualBoundingBoxAscent + ctx.measureText(text).actualBoundingBoxDescent);
|
|
let tWidth = Math.round(ctx.measureText(text).width);
|
|
ctx.restore();
|
|
return {
|
|
width: tWidth,
|
|
height: tHeight
|
|
};
|
|
}
|
|
|
|
drawBorderBox(ctx,x,y,drawWidth,drawHeight,borderWidth=1,borderColor="#000",fillColor="#f7e979") {
|
|
let old_fillStyle = ctx.fillStyle;
|
|
ctx.fillStyle = borderColor;
|
|
ctx.fillRect(x,y,drawWidth,drawHeight);
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fillRect(x+borderWidth,y+borderWidth,drawWidth-(borderWidth*2),drawHeight-(borderWidth*2));
|
|
ctx.fillStyle = old_fillStyle;
|
|
}
|
|
|
|
drawTextCentered(ctx,x,y,x2,y2,text,fontStyle="24px Console",fontColor = "#555") {
|
|
let old_fillStyle = ctx.fillStyle;
|
|
let old_font = ctx.font;
|
|
ctx.font = fontStyle;
|
|
ctx.fillStyle = fontColor;
|
|
let tHeight = ctx.measureText(text).actualBoundingBoxAscent + ctx.measureText(text).actualBoundingBoxDescent;
|
|
let tX = x+((x2/2)-(ctx.measureText(text).width/2));
|
|
let tY = y+tHeight+((y2/2)-(tHeight/2));
|
|
ctx.fillText(text,tX,tY);
|
|
ctx.fillStyle = old_fillStyle;
|
|
ctx.font = old_font;
|
|
}
|
|
|
|
drawText(ctx,x,y,text,fontStyle="24px Console",fontColor = "#555") {
|
|
let old_fillStyle = ctx.fillStyle;
|
|
let old_font = ctx.font;
|
|
ctx.font = fontStyle;
|
|
ctx.fillStyle = fontColor;
|
|
ctx.fillText(text,x,y);
|
|
ctx.fillStyle = old_fillStyle;
|
|
ctx.font = old_font;
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
}
|
|
Call(value) {
|
|
this.Callback.CBObject[this.Callback.CBFunction](value);
|
|
}
|
|
}
|
|
|
|
class ElementConnection {
|
|
constructor(elementContainer,element,input) {
|
|
this.Container = elementContainer;
|
|
this.Element = element;
|
|
this.Input = input;
|
|
}
|
|
}
|
|
|
|
class Element extends CanvasTools {
|
|
|
|
constructor(logicengine,Inputs) {
|
|
super();
|
|
this.Name = "Element";
|
|
this.Designator = "";
|
|
this.Inputs = new Array(Inputs);
|
|
this.Width = 150;
|
|
this.Height = 100;
|
|
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);
|
|
this.Properties.push(inputProperty);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
Delete() {
|
|
// Just to clean up connections
|
|
for (let a = 0; a < this.OutputConnections.length;a++) {
|
|
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input,false);
|
|
}
|
|
}
|
|
MouseClick(mousePos) {
|
|
|
|
let mouseDistOutput = length2D(this.X+(this.Width-20),
|
|
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 mouseDist = length2D(this.X+20,
|
|
this.Y+(this.inputCircleRadius + 2)+(((a*(4+(this.inputCircleRadius*2))))-2)+(this.inputCircleRadius/2),
|
|
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) {
|
|
let newConnection = new ElementConnection(container,element,input);
|
|
this.OutputConnections.push(newConnection);
|
|
element.setInput(input,this.getOutput());
|
|
}
|
|
|
|
drawInputs(ctx,x,y,borderColor = "#000",circleColorFalse = "#ff0000",circleColorTrue="#00ff00",circleColorHover = "#00ffff") {
|
|
let old_strokeStyle = ctx.strokeStyle;
|
|
let old_fillStyle = ctx.fillStyle;
|
|
if ((this.totalInputs() * ((this.inputCircleRadius*2)+4)) > (this.Height-2)) {
|
|
this.inputCircleRadius = ((this.Height-(2 + this.totalInputs() * 4)) / this.totalInputs())/2;
|
|
} else {
|
|
this.inputCircleRadius = 10;
|
|
}
|
|
|
|
for (let a = 0; a < this.totalInputs();a++) {
|
|
let mouseDist = length2D(x+20,y+(this.inputCircleRadius + 2)+(((a*(4+(this.inputCircleRadius*2))))-2)+(this.inputCircleRadius/2),this.MousePosition.x,this.MousePosition.y);
|
|
ctx.beginPath();
|
|
ctx.arc(x+20,y+(this.inputCircleRadius + 2)+(((a*(4+(this.inputCircleRadius*2))))-2)+(this.inputCircleRadius/2),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.strokeStyle = old_strokeStyle;
|
|
ctx.fillStyle = old_fillStyle;
|
|
}
|
|
|
|
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-20),y+(this.Height/2),this.MousePosition.x,this.MousePosition.y);
|
|
ctx.beginPath();
|
|
ctx.arc(x+(this.Width-20),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 {
|
|
ctx.beginPath();
|
|
ctx.lineWidth = settings.LinkWidth;
|
|
ctx.setLineDash(settings.LinkDash);
|
|
ctx.moveTo((this.X + this.Width) - 20, this.Y + (this.Height / 2));
|
|
ctx.lineTo(this.OutputConnections[a].Element.X + 20, this.OutputConnections[a].Element.Y + 20);
|
|
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;
|
|
}
|
|
if (this.getOutput() != oldOutput) {
|
|
// The output changed, we need to notify connected elements
|
|
for (let a = 0; a < this.OutputConnections.length;a++) {
|
|
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input,this.getOutput());
|
|
}
|
|
}
|
|
}
|
|
|
|
getOutput() {
|
|
/*
|
|
Should return true or false
|
|
*/
|
|
return false;
|
|
}
|
|
|
|
drawElement(x,y,ctx) {
|
|
/*
|
|
Draw routine for the element
|
|
*/
|
|
this.drawBorderBox(ctx,x,y,drawWidth,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.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput());
|
|
}
|
|
}
|
|
}
|
|
|
|
getOutput() {
|
|
return this.Output;
|
|
}
|
|
|
|
setInput(Input, Value) {
|
|
super.setInput(Input, Value);
|
|
if (!this.Inputs[0]) {
|
|
this.Output = false;
|
|
for (let a = 0; a < this.OutputConnections.length;a++) {
|
|
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input,this.getOutput());
|
|
}
|
|
}
|
|
}
|
|
|
|
constructor(logicengine) {
|
|
super(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);
|
|
this.Properties.push(periodProperty);
|
|
this.Properties.push(dutyProperty);
|
|
}
|
|
|
|
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,y,this.Width,this.Height);
|
|
this.drawTextCentered(ctx,x,y,this.Width,(this.Height+(this.Height/2)),this.Period + "ms " + (this.Duty * 100) + "%","10px Console");
|
|
this.drawInputs(ctx,x,y);
|
|
this.drawOutputs(ctx,x,y);
|
|
}
|
|
}
|
|
|
|
class inputElement extends Element {
|
|
constructor(logicengine) {
|
|
super(logicengine,0);
|
|
this.Name = "InputElement";
|
|
this.Output = false;
|
|
this.Width = 100;
|
|
}
|
|
|
|
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(logicengine) {
|
|
super(logicengine);
|
|
this.Name = "Switch";
|
|
}
|
|
|
|
MouseClick(mousePos) {
|
|
super.MouseClick(mousePos);
|
|
if ((mousePos.x >= (this.X + 10)) && (mousePos.x <= (this.X + 60)) && (mousePos.y >= (this.Y + 25)) && (mousePos.y <= (this.Y + 75))) {
|
|
this.Output = ~this.Output;
|
|
for (let a = 0; a < this.OutputConnections.length; a++) {
|
|
this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput());
|
|
}
|
|
}
|
|
}
|
|
|
|
drawElement(x, y, ctx) {
|
|
this.drawBorderBox(ctx, x,y,this.Width,this.Height);
|
|
this.drawBorderBox(ctx,x+10,y+25,50,50,1,"#ccc","#777");
|
|
this.drawOutputs(ctx,x,y);
|
|
}
|
|
}
|
|
|
|
class LogicAND extends Element {
|
|
constructor(logicengine,Inputs) {
|
|
super(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,y,this.Width,this.Height);
|
|
this.drawTextCentered(ctx,x,y,this.Width,this.Height,"|⊃");
|
|
this.drawInputs(ctx,x,y);
|
|
this.drawOutputs(ctx,x,y);
|
|
}
|
|
}
|
|
|
|
class LogicNAND extends LogicAND {
|
|
constructor(logicengine,Inputs) {
|
|
super(logicengine,Inputs);
|
|
this.Name = "NAND";
|
|
}
|
|
getOutput() {
|
|
if (super.getOutput()) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
drawElement(x,y,ctx) {
|
|
this.drawBorderBox(ctx, x,y,this.Width,this.Height);
|
|
this.drawTextCentered(ctx,x,y,this.Width,this.Height,"|⊃🞄");
|
|
this.drawInputs(ctx,x,y);
|
|
this.drawOutputs(ctx,x,y);
|
|
}
|
|
}
|
|
|
|
class LogicOR extends Element {
|
|
constructor(logicengine,Inputs) {
|
|
super(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;
|
|
|
|
this.drawBorderBox(ctx, x,y,drawWidth,drawHeight);
|
|
this.drawTextCentered(ctx,x,y,drawWidth,drawHeight,")⊃");
|
|
this.drawInputs(ctx,x,y);
|
|
this.drawOutputs(ctx,x,y);
|
|
}
|
|
}
|
|
|
|
class LogicNOR extends LogicOR {
|
|
constructor(logicengine,Inputs) {
|
|
super(logicengine,Inputs);
|
|
this.Name = "NOR";
|
|
}
|
|
getOutput() {
|
|
if (super.getOutput()) {
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
drawElement(x,y,ctx) {
|
|
this.drawBorderBox(ctx, x,y,this.Width,this.Height);
|
|
this.drawTextCentered(ctx,x,y,this.Width,this.Height,")⊃🞄");
|
|
this.drawInputs(ctx,x,y);
|
|
this.drawOutputs(ctx,x,y);
|
|
}
|
|
}
|
|
|
|
class LogicXOR extends Element {
|
|
constructor(logicengine) {
|
|
super(logicengine,2); // Only 2 inputs on XOR
|
|
this.Name = "XOR";
|
|
}
|
|
|
|
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;
|
|
|
|
this.drawBorderBox(ctx, x,y,drawWidth,drawHeight);
|
|
this.drawTextCentered(ctx,x,y,drawWidth,drawHeight,"))⊃");
|
|
this.drawInputs(ctx,x,y);
|
|
this.drawOutputs(ctx,x,y);
|
|
}
|
|
}
|
|
|
|
class LogicXNOR extends Element {
|
|
constructor(logicengine) {
|
|
super(logicengine,2); // Only 2 inputs on XOR
|
|
this.Name = "XNOR";
|
|
}
|
|
|
|
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;
|
|
|
|
this.drawBorderBox(ctx, x,y,drawWidth,drawHeight);
|
|
this.drawTextCentered(ctx,x,y,drawWidth,drawHeight,"))⊃🞄");
|
|
this.drawInputs(ctx,x,y);
|
|
this.drawOutputs(ctx,x,y);
|
|
}
|
|
}
|
|
|
|
|
|
class elementContainer {
|
|
constructor() {
|
|
this.Elements = new Array();
|
|
this.Selected = false;
|
|
}
|
|
|
|
AddElement(element) {
|
|
let designatorNumber = 1;
|
|
let designatorTest = element.Name + designatorNumber;
|
|
let unused = false;
|
|
|
|
while (!unused) {
|
|
let foundMatch = false;
|
|
for (let a=0;a < this.Elements.length;a++) {
|
|
if (this.Elements[a].Designator == designatorTest) foundMatch = true;
|
|
}
|
|
if (foundMatch) {
|
|
designatorNumber++;
|
|
designatorTest = element.Name + designatorNumber;
|
|
} else {
|
|
unused = true;
|
|
element.Designator = designatorTest;
|
|
this.Elements.push(element);
|
|
}
|
|
}
|
|
}
|
|
|
|
DeleteElement(element) {
|
|
// Can pass object or Designator
|
|
for (let a = 0; a < this.Elements.length; a++) {
|
|
if ((this.Elements[a] == element) || (this.Elements[a].Designator == element)) {
|
|
this.Elements[a].Delete();
|
|
this.Elements.splice(a,1);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
HasElement(element) {
|
|
// Can pass object or Designator
|
|
for (let a = 0; a < this.Elements.length; a++) {
|
|
if ((this.Elements[a] == element) || (this.Elements[a].Designator == element)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
DrawAll(ctx,settings) {
|
|
for (let a = 0; a < this.Elements.length; a++) {
|
|
// Not ideal to loop twice but we need the connections drawn first
|
|
this.Elements[a].drawConnections(ctx, settings);
|
|
}
|
|
|
|
for (let a = 0; a < this.Elements.length; a++) {
|
|
if (this.Elements[a] == this.Selected) this.Elements[a].drawBorderBox(ctx, this.Elements[a].X - 2, this.Elements[a].Y - 2, this.Elements[a].Width + 4, this.Elements[a].Height + 4, 2, "#5555FF", "#5555ff");
|
|
this.Elements[a].drawElement(this.Elements[a].X, this.Elements[a].Y, ctx);
|
|
let old_font = ctx.font;
|
|
let old_fillStyle = ctx.fillStyle;
|
|
ctx.font = "10px Console";
|
|
let x = this.Elements[a].X;
|
|
let y = this.Elements[a].Y + (this.Elements[a].Height - 12);
|
|
let x2 = this.Elements[a].Width;
|
|
let y2 = 10;
|
|
this.Elements[a].drawTextCentered(ctx, x, y, x2, y2, this.Elements[a].Designator, ctx.font, "#000");
|
|
ctx.font = old_font;
|
|
ctx.fillStyle = old_fillStyle;
|
|
|
|
}
|
|
if (!this.Selected) {
|
|
let PropertiesBox = document.getElementById("PropertiesBox");
|
|
if (PropertiesBox.style.display != "none") PropertiesBox.style.display = "none";
|
|
}
|
|
}
|
|
|
|
Select(element) {
|
|
this.Selected = element;
|
|
let PropertiesBox = document.getElementById("PropertiesBox");
|
|
let PropertiesBoxTitle = document.getElementById("PropertiesBoxTitle");
|
|
let PropertiesBoxContent = document.getElementById("PropertiesBoxContent");
|
|
PropertiesBoxTitle.innerText = this.Selected.Designator + " Properties";
|
|
let contentString = "<table id='propertiesTable'>";
|
|
for (let a = 0; a < this.Selected.Properties.length;a++) {
|
|
contentString += "<tr><td>" + this.Selected.Properties[a].Name + "</td><td>";
|
|
switch (this.Selected.Properties[a].Type) {
|
|
case "int":
|
|
contentString += "<input type='number' id='prop_" + this.Selected.Properties[a].Name + "' min='" + this.Selected.Properties[a].Minimium + "' max='" + this.Selected.Properties[a].Maximium + "' value='" + this.Selected.Properties[a].CurrentValue + "' onchange='logicEngine.PropertyChange(" + String.fromCharCode(34) + this.Selected.Properties[a].Name + String.fromCharCode(34) +");'>";
|
|
break;
|
|
}
|
|
contentString += "</td></tr>";
|
|
}
|
|
PropertiesBoxContent.innerHTML = contentString;
|
|
PropertiesBox.style.display = "block";
|
|
}
|
|
|
|
checkMouseBounds(mousePos) {
|
|
// We go backwards so that the newest (highest drawn) element is clicked before one lower.
|
|
for (let a = (this.Elements.length - 1); a >= 0; a--) {
|
|
if (this.Elements[a].mouseInside(mousePos)) return this.Elements[a];
|
|
}
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
class LogicEngine {
|
|
|
|
Resize(evt) {
|
|
let leftmenu = document.getElementById("left-menu");
|
|
leftmenu.style.height = (window.innerHeight - 52) + "px";
|
|
this.Canvas.width = window.innerWidth - 205;
|
|
this.Canvas.height = window.innerHeight - 50;
|
|
this.Mouse = false;
|
|
}
|
|
|
|
PropertyChange(property) {
|
|
if (!this.ActiveContainer.Selected.getProperty(property)) return false;
|
|
let propElement = document.getElementById("prop_" + property);
|
|
this.ActiveContainer.Selected.getProperty(property).Call(propElement.value);
|
|
}
|
|
|
|
Mouse_Down(evt) {
|
|
let mousePos = getMousePos(this.Canvas, evt);
|
|
this.MouseDown = true;
|
|
this.MouseDownTime = performance.now();
|
|
let element = this.ActiveContainer.checkMouseBounds(mousePos);
|
|
if (element) {
|
|
console.log("Moused down on " + element.Designator);
|
|
this.MovingElement = element;
|
|
this.MovingElementStartX = element.X;
|
|
this.MovingElementStartY = element.Y;
|
|
this.MovingElementMouseStartX = mousePos.x;
|
|
this.MovingElementMouseStartY = mousePos.y;
|
|
} else {
|
|
this.ActiveLink = false;
|
|
}
|
|
}
|
|
|
|
Mouse_Up(evt) {
|
|
let mousePos = getMousePos(this.Canvas, evt);
|
|
this.MouseDown = false;
|
|
if (this.MovingElement && (this.MovingElement.X == this.MovingElementStartX) && (this.MovingElement.Y == this.MovingElementStartY)) {
|
|
if ((performance.now() - this.MouseDownTime) < 3000) {
|
|
// Presume this was a click
|
|
this.ActiveContainer.Select(this.MovingElement);
|
|
this.MovingElement.MouseClick(mousePos);
|
|
}
|
|
//console.log("Mouse Up");
|
|
}
|
|
if (!this.MovingElement) this.ActiveContainer.Selected = false;
|
|
this.MovingElement = false;
|
|
}
|
|
|
|
Mouse_Move(evt) {
|
|
this.Canvas.focus();
|
|
let mousePos = getMousePos(this.Canvas, evt);
|
|
this.Mouse = mousePos;
|
|
if(this.MouseDown) {
|
|
//console.log('Mouse at position: ' + mousePos.x + ',' + mousePos.y);
|
|
if (this.MovingElement) {
|
|
if ((performance.now() - this.MouseDownTime) > 100) {
|
|
this.ActiveContainer.Select(this.MovingElement);
|
|
let xOffset = mousePos.x - this.MovingElementMouseStartX;
|
|
let yOffset = mousePos.y - this.MovingElementMouseStartY;
|
|
let diffxOffset = this.MovingElementMouseStartX - this.MovingElementStartX;
|
|
let diffyOffset = this.MovingElementMouseStartY - this.MovingElementStartY;
|
|
this.MovingElement.X = (this.MovingElementMouseStartX + xOffset) - diffxOffset;
|
|
this.MovingElement.Y = (this.MovingElementMouseStartY + yOffset) - diffyOffset;
|
|
}
|
|
}
|
|
} else {
|
|
this.ActiveContainer.checkMouseBounds(mousePos);
|
|
}
|
|
}
|
|
|
|
Key_Press(evt) {
|
|
if (evt.key == "Escape") {
|
|
if (this.MovingElement && this.MouseDown) {
|
|
this.MovingElement.X = this.MovingElementStartX;
|
|
this.MovingElement.Y = this.MovingElementStartY;
|
|
this.MovingElement = false;
|
|
}
|
|
}
|
|
if (evt.key == "Delete") {
|
|
if (this.ActiveContainer.Selected) {
|
|
this.ActiveContainer.DeleteElement(this.ActiveContainer.Selected);
|
|
this.ActiveContainer.Selected = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
constructor(canvas) {
|
|
this.Canvas = canvas;
|
|
this.Ctx = canvas.getContext("2d");
|
|
|
|
this.Settings = new LogicEngineSettings;
|
|
this.FPSCounter = 0;
|
|
this.FPS = 0;
|
|
this.PotentialFPS = 0;
|
|
this.PotentialFPSAVGs = new Array(20);
|
|
this.PotentialFPSAVGLoc = 0;
|
|
this.LastFPSCheck = performance.now();
|
|
this.MouseDown = false;
|
|
this.MouseDownTime = 0;
|
|
this.MovingElementContainer = false;
|
|
this.MovingElement = false;
|
|
this.MovingElementStartX = 0;
|
|
this.MovingElementStartY = 0;
|
|
this.MovingElementMouseStartX = 0;
|
|
this.MovingElementMouseStartY = 0;
|
|
this.ActiveContainer = new elementContainer();
|
|
this.ActiveLink = false;
|
|
this.Scheduler = new ScheduleEngine();
|
|
|
|
this.Canvas.setAttribute('tabindex','0');
|
|
|
|
}
|
|
|
|
Link(input = 0) {
|
|
if (this.ActiveLink) {
|
|
if (this.ActiveContainer.Selected && (this.ActiveContainer.Selected != this.ActiveLink)) {
|
|
this.ActiveLink.addConnection(this.ActiveContainer,this.ActiveContainer.Selected,input);
|
|
this.ActiveLink = false;
|
|
} else {
|
|
this.ActiveLink = false;
|
|
}
|
|
} else {
|
|
if (this.ActiveContainer.Selected) {
|
|
this.ActiveLink = this.ActiveContainer.Selected;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
DrawLoop() {
|
|
let startLoop = performance.now();
|
|
this.Ctx.clearRect(0,0,this.Canvas.width,this.Canvas.height);
|
|
if (this.ActiveLink) {
|
|
this.Ctx.save();
|
|
this.Ctx.strokeStyle = this.Settings.LinkingConnectionColor;
|
|
this.Ctx.lineWidth = this.Settings.LinkingWidth;
|
|
this.Ctx.setLineDash(this.Settings.LinkingDash);
|
|
this.Ctx.beginPath();
|
|
this.Ctx.moveTo(this.ActiveLink.X + (this.ActiveLink.Width/2), this.ActiveLink.Y + (this.ActiveLink.Height/2));
|
|
this.Ctx.lineTo(this.Mouse.x, this.Mouse.y);
|
|
this.Ctx.stroke();
|
|
this.Ctx.restore();
|
|
}
|
|
this.ActiveContainer.DrawAll(this.Ctx,this.Settings);
|
|
let ct = new CanvasTools();
|
|
let FPSOffset = this.Canvas.width - 150;
|
|
ct.drawText(this.Ctx,FPSOffset,650,"FPS: " + this.FPS,"12px console", "#00ff00");
|
|
ct.drawText(this.Ctx,FPSOffset,670,"Potential FPS: " + this.PotentialFPS,"12px console", "#00ff00");
|
|
let timeCheck = performance.now();
|
|
this.FPSCounter++;
|
|
|
|
if (!(Math.round(timeCheck - this.LastFPSCheck) % 50)) {
|
|
let frameTimeUS = (performance.now() - startLoop) * 1000;
|
|
let potentialFPS = 1000000 / frameTimeUS;
|
|
this.PotentialFPSAVGs[this.PotentialFPSAVGLoc] = Math.round(potentialFPS);
|
|
this.PotentialFPSAVGLoc++;
|
|
if (this.PotentialFPSAVGLoc == this.PotentialFPSAVGs.length) this.PotentialFPSAVGLoc = 0;
|
|
this.PotentialFPS = averageArray(this.PotentialFPSAVGs);
|
|
}
|
|
|
|
if ((timeCheck - this.LastFPSCheck) >= 1000) {
|
|
this.FPS = this.FPSCounter;
|
|
this.FPSCounter = 0;
|
|
this.LastFPSCheck = performance.now();
|
|
//console.log("Frame Time: " + frameTimeUS + "uS" + ", Potential FPS: " + potentialFPS);
|
|
//console.log("FPS: " + FPS);
|
|
}
|
|
|
|
requestAnimationFrame(this.DrawLoop.bind(this));
|
|
}
|
|
|
|
StartEngine() {
|
|
this.Resize("");
|
|
this.DrawLoop();
|
|
}
|
|
}
|
|
|
|
|
|
|