/* First a few needed global functions */ function setCookie(cname, cvalue, exdays) { let d = new Date(); d.setTime(d.getTime() + (exdays*24*60*60*1000)); let expires = "expires="+ d.toUTCString(); document.cookie = cname + '="' + cvalue + '";' + expires; localStorage.setItem(cname,cvalue); } function getCookie(cname) { let name = cname + "="; let decodedCookie = decodeURIComponent(document.cookie); let ca = decodedCookie.split(';'); for(var i = 0; i (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); } } 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(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"); 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); } } 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(logicengine) { super(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); this.Properties.push(periodProperty); } 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); } } 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.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); } } class InputButton extends inputElement { constructor(logicengine) { super(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); } } 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 = ""; for (let a = 0; a < this.Selected.Properties.length;a++) { contentString += ""; } 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.MouseDownTime = performance.now(); let element = this.ActiveContainer.checkMouseBounds(mousePos); if (element) { this.MouseDown = true; this.ActiveContainer.Select(element); this.MovingElement = element; this.MovingElementStartX = element.X; this.MovingElementStartY = element.Y; this.MovingElementMouseStartX = mousePos.x; this.MovingElementMouseStartY = mousePos.y; element.MouseDown(mousePos); } else { this.ActiveLink = false; } } Mouse_Up(evt) { let mousePos = getMousePos(this.Canvas, evt); if (this.MovingElement) this.MovingElement.MouseUp(mousePos); 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.MovingElement.MouseClick(mousePos); } //console.log("Mouse Up"); } if (!this.MovingElement) this.ActiveContainer.Selected = false; this.MovingElement = false; this.MouseDown = 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) { 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); this.ActiveContainer.DrawAll(this.Ctx,this.Settings); if (this.ActiveLink) { let startX = this.ActiveLink.X + this.ActiveLink.Width; let startY = this.ActiveLink.Y+(this.ActiveLink.Height/2); let endX = this.Mouse.x; let endY = this.Mouse.y; let startMidX = startX + ((endX - startX)/2); let startMidY = startY; let midX = startMidX; let midY = startY + ((endY - startY)/2); let endMidX = startMidX; let endMidY = endY; 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(startX, startY); this.Ctx.quadraticCurveTo(startMidX,startMidY,midX,midY); this.Ctx.quadraticCurveTo(endMidX,endMidY,endX,endY); this.Ctx.stroke(); this.Ctx.restore(); } 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(); } }
" + this.Selected.Properties[a].Name + ""; switch (this.Selected.Properties[a].Type) { case "int": contentString += ""; break; } contentString += "