1233 lines
39 KiB
JavaScript
1233 lines
39 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];
|
|
this.ShadowColor = "#222";
|
|
}
|
|
}
|
|
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",shadowColor = "transparent") {
|
|
ctx.save();
|
|
ctx.beginPath();
|
|
ctx.fillStyle = borderColor;
|
|
if (shadowColor != "transparent") {
|
|
ctx.shadowBlur = "6";
|
|
ctx.shadowColor = shadowColor;
|
|
ctx.shadowOffsetX = 2;
|
|
ctx.shadowOffsetY = 2;
|
|
ctx.stroke();
|
|
}
|
|
ctx.fillRect(x,y,drawWidth,drawHeight);
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fillRect(x+borderWidth,y+borderWidth,drawWidth-(borderWidth*2),drawHeight-(borderWidth*2));
|
|
ctx.restore();
|
|
}
|
|
|
|
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 = 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);
|
|
}
|
|
|
|
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() {
|
|
// 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);
|
|
}
|
|
}
|
|
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) {
|
|
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(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+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");
|
|
this.drawTextCentered(ctx,x,y,this.Width,this.Height,this.Designator,"12px Console","#000");
|
|
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;
|
|
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(logicengine) {
|
|
super(logicengine);
|
|
this.Name = "Switch";
|
|
this.Height = 70;
|
|
}
|
|
|
|
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.drawTextCentered(ctx,x,y+(this.Height-14),this.Width-(this.outputCircleRadius*2),12,this.Designator,"12px Console","#000");
|
|
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+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);
|
|
}
|
|
}
|
|
|
|
class LogicNAND extends LogicAND {
|
|
constructor(logicengine,Inputs) {
|
|
super(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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
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);
|
|
}
|
|
}
|
|
|
|
class LogicNOR extends LogicOR {
|
|
constructor(logicengine,Inputs) {
|
|
super(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);
|
|
}
|
|
}
|
|
|
|
class LogicXOR extends Element {
|
|
constructor(logicengine) {
|
|
super(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);
|
|
}
|
|
}
|
|
|
|
class LogicXNOR extends Element {
|
|
constructor(logicengine) {
|
|
super(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);
|
|
}
|
|
}
|
|
|
|
class LogicNOT extends Element {
|
|
constructor(logicengine) {
|
|
super(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);
|
|
}
|
|
}
|
|
|
|
|
|
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++) {
|
|
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, 1, "rgba(100,200,255,0.25)", "rgba(100,200,255,0.25)");
|
|
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";
|
|
}
|
|
|
|
for (let a = 0; a < this.Elements.length; a++) {
|
|
// Not ideal to loop twice but we need the connections drawn all at once to prevent layer issues
|
|
this.Elements[a].drawConnections(ctx, settings);
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
let gridPlane = document.getElementById("GridPlane");
|
|
gridPlane.width = this.Canvas.width;
|
|
gridPlane.height = this.Canvas.height;
|
|
let Ctx = gridPlane.getContext("2d");
|
|
Ctx.save();
|
|
let gridWidth = 20;
|
|
for (let x = gridWidth;x < (this.Canvas.width); x+= gridWidth) {
|
|
Ctx.beginPath();
|
|
Ctx.moveTo(x,0);
|
|
Ctx.lineTo(x,this.Canvas.height);
|
|
Ctx.strokeStyle = "#777";
|
|
Ctx.lineWidth = "1";
|
|
Ctx.stroke();
|
|
}
|
|
for (let y = gridWidth;y < (this.Canvas.width); y+= gridWidth) {
|
|
Ctx.beginPath();
|
|
Ctx.moveTo(0,y);
|
|
Ctx.lineTo(this.Canvas.width,y);
|
|
Ctx.lineWidth = "1";
|
|
Ctx.strokeStyle = "#777";
|
|
Ctx.stroke();
|
|
}
|
|
Ctx.restore();
|
|
|
|
}
|
|
|
|
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) {
|
|
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.RecursionCount = 0;
|
|
this.RecursionError = false;
|
|
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() {
|
|
if (this.RecursionError) {
|
|
this.RecursionError = false;
|
|
alert("Recursion Error! Whatever you last did is causing an oscillating loop, please check your connections and try again!");
|
|
}
|
|
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();
|
|
}
|
|
}
|
|
|
|
|
|
|