From a50c655670be0b86c89281968003c0a1d13ed67f Mon Sep 17 00:00:00 2001 From: MatCat Date: Wed, 24 Feb 2021 18:56:49 -0800 Subject: [PATCH] 0.3.0: Loading and Saving, new buffer element, fixed bug on grid draw --- README.md | 6 + index.html | 10 +- js/baseclasses.js | 225 +++++++ js/elements.js | 1291 +++++++++++++++++++++++++++++++++++++++ js/globalfunctions.js | 163 +++++ js/logicengine.js | 1325 +---------------------------------------- js/main.js | 71 ++- js/scheduler.js | 60 ++ 8 files changed, 1801 insertions(+), 1350 deletions(-) create mode 100644 js/baseclasses.js create mode 100644 js/elements.js create mode 100644 js/globalfunctions.js create mode 100644 js/scheduler.js diff --git a/README.md b/README.md index 67f2013..6874a4d 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,12 @@ To be decided, but at this moment this code is open source and free to use for n ## Changelog +### 0.3.0 +* Saving / Loading of designs +* New Element: Buffer, allows for construction of circuits that would otherwise be recursive +* Added pulse count to the delay block printout +* Fixed grid draw bug + ### 0.2.12 * Fixed floating active link when deleting an element while linking * Fixed the delay element so that it properly buffers all input pulses diff --git a/index.html b/index.html index 3fe89fa..5416f28 100644 --- a/index.html +++ b/index.html @@ -42,12 +42,16 @@


+





- +

+
+ + @@ -61,6 +65,10 @@
+ + + + diff --git a/js/baseclasses.js b/js/baseclasses.js new file mode 100644 index 0000000..ebe1df9 --- /dev/null +++ b/js/baseclasses.js @@ -0,0 +1,225 @@ +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 elementContainer { + constructor() { + this.Elements = new Array(); + this.Selected = false; + } + + toJSON(key) { + let elements = new Array(); + for (let a = 0; a < this.Elements.length; a++) { + elements.push(this.Elements[a].toJSON()); + } + return elements; + } + + 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 this.Elements[a]; + } + } + 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; + } + + checkOverlayBounds(x,y,width,height) { + for (let a = 0; a < this.Elements.length; a++) { + if ((x >= this.Elements[a].X) && (x <= (this.Elements[a].X + this.Elements[a].Width)) && (y >= this.Elements[a].Y) && (y <= (this.Elements[a].Y + this.Elements[a].Height))) return this.Elements[a]; + if (((x + width) >= this.Elements[a].X) && ((x + width) <= (this.Elements[a].X + this.Elements[a].Width)) && (y >= this.Elements[a].Y) && (y <= (this.Elements[a].Y + this.Elements[a].Height))) return this.Elements[a]; + if ((x >= this.Elements[a].X) && (x <= (this.Elements[a].X + this.Elements[a].Width)) && ((y + height) >= this.Elements[a].Y) && ((y + height) <= (this.Elements[a].Y + this.Elements[a].Height))) return this.Elements[a]; + if (((x + width) >= this.Elements[a].X) && ((x + width) <= (this.Elements[a].X + this.Elements[a].Width)) && ((y + height) >= this.Elements[a].Y) && ((y + height) <= (this.Elements[a].Y + this.Elements[a].Height))) return this.Elements[a]; + } + return false; + } + +} + +class ElementCatalog_Category { + constructor(name,icon) { + this.Name = name; + this.Icon = icon; + this.Elements = new Array(); + } + addElement(element) { + if (element instanceof ElementCatalog_Element) { + this.Elements.push(element); + } + } +} + +class ElementCatalog_Element { + constructor(name,description,icon,classref,args) { + this.Name = name; + this.Description = description; + this.Icon = icon; + this.Class = classref; + this.Args = args; + } +} + +class ElementCatalog { + constructor(categories = new Array()) { + this.Categories = categories; + for (let a = 0; a < this.Categories.length; a++) { + if (!this.Categories[a] instanceof ElementCatalog_Category) { + this.Categories.splice(a,1); + a--; + } + } + } + addCategory(category) { + if (category instanceof ElementCatalog_Category) { + this.Categories.push(category); + } + } +} diff --git a/js/elements.js b/js/elements.js new file mode 100644 index 0000000..6e99ca5 --- /dev/null +++ b/js/elements.js @@ -0,0 +1,1291 @@ +let ElementReferenceTable = new Array(); + +let ElementCategory_Inputs = new ElementCatalog_Category("Inputs",""); +let ElementCategory_LOGIC = new ElementCatalog_Category("Logic" ,""); +let ElementCategory_Timing = new ElementCatalog_Category("Timing" ,""); +let elementCatalog = new ElementCatalog([ElementCategory_Inputs, + ElementCategory_LOGIC, + ElementCategory_Timing]); +class ElementProperty { + constructor(name,type,callback,defaultValue,currentValue = false,values=false,min=0,max=12) { + /* + Types + --------------------------------------- + bool Boolean Values + int Integer Value + string String Value + list Dropdown box of values + + Callback is an object of: + --------------------------------------- + CBObject Object to call function on + CBFunction The function + + */ + this.Name = name; + this.Type = type; + this.Callback = callback; + this.DefaultValue = defaultValue; + if (!currentValue) currentValue = defaultValue; + this.CurrentValue = currentValue; + this.Values = values; + if (!values) this.Values = new Array(); + this.Minimium = min; + this.Maximium = max; + } + + toJSON(key) { + return { + Name: this.Name, + DefaultValue: this.DefaultValue, + CurrentValue: this.CurrentValue, + Values: this.Values, + Minimium: this.Minimium, + Maximium: this.Maximium + }; + } + + Call(value) { + this.Callback.CBObject[this.Callback.CBFunction](value); + } +} + +class ElementConnection { + constructor(elementContainer,element,input) { + this.Container = elementContainer; + this.Element = element; + this.Input = input; + } + toJSON(key) { + return { + Element: this.Element.Designator, + Input: parseInt(this.Input) + }; + } +} + +class Element extends CanvasTools { + + constructor(RestoreData = null,logicengine,Inputs) { + super(); + this.Name = "Element"; + this.Designator = ""; + this.Inputs = new Array(Inputs); + this.Width = 100; + this.Height = 60; + this.inputCircleRadius = 10; + this.outputCircleRadius = 10; + this.X = 0; + this.Y = 0; + this.OutputConnections = new Array(); + this.MouseOver = false; + this.MousePosition = {x: 0, y: 0}; + this.Properties = new Array(); + this.LogicEngine = logicengine; + + let inputProperty = new ElementProperty("Inputs","int",{CBObject: this,CBFunction: "ChangeInputs"},2,Inputs,false,2); + this.Properties.push(inputProperty); + if (RestoreData) { + this.Designator = RestoreData.Designator; + this.Width = RestoreData.Width; + this.Height = RestoreData.Height; + this.X = RestoreData.X; + this.Y = RestoreData.Y; + if (RestoreData.Properties.length > 0) { + if (this.Properties[0].Name == "Inputs") { + this.ChangeInputs(RestoreData.Properties[0].CurrentValue); + this.Properties[0].DefaultValue = RestoreData.Properties[0].DefaultValue; + this.Properties[0].CurrentValue = RestoreData.Properties[0].CurrentValue; + this.Properties[0].Values = RestoreData.Properties[0].Values; + this.Properties[0].Minimium = RestoreData.Properties[0].Minimium; + this.Properties[0].Maximium = RestoreData.Properties[0].Maximium; + } + } + this.Inputs = RestoreData.Inputs; + } + } + + toJSON(key) { + return { + Name: this.Name, + Designator: this.Designator, + Inputs: this.Inputs, + Outputs: this.OutputConnections, + Output: this.getOutput(), + Width: this.Width, + Height: this.Height, + X: this.X, + Y: this.Y, + Properties: this.Properties + }; + } + + getProperty(property) { + for (let a = 0; a < this.Properties.length;a++) { + if (this.Properties[a].Name == property) return this.Properties[a]; + } + return false; + } + + removeProperty(property) { + for (let a = 0; a < this.Properties.length;a++) { + if (this.Properties[a].Name == property) { + this.Properties.splice(a,1); + return true; + } + } + return false; + } + + totalInputs() { + return this.Inputs.length; + } + + ChangeInputs(inputs) { + inputs = parseInt(inputs,10); + this.Inputs = new Array(inputs); + this.getProperty("Inputs").CurrentValue = inputs; + this.Height = inputs*25; + if (this.Height < 60) this.Height = 60; + } + + Delete() { + // if we are active linking stop doing so + if (this.LogicEngine.ActiveLink == this) this.LogicEngine.ActiveLink = false; + // Just to clean up connections + for (let a = 0; a < this.OutputConnections.length;a++) { + this.LogicEngine.RecursionCount = 0; + this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input,false); + } + } + + MouseDown(mousePos) { + return; + } + + MouseUp(mousePos) { + return; + } + + MouseClick(mousePos) { + + let mouseDistOutput = length2D(this.X+(this.Width-10), + this.Y+(this.Height/2), + this.MousePosition.x, + this.MousePosition.y); + if (this.LogicEngine.ActiveLink) { + // We need to see if an input is being clicked on to be linked to + let foundInput = false; + for (let a = 0; a < this.Inputs.length;a++) { + let centerY = this.Y + Math.round(this.Height / 2); + let totalHeight = this.totalInputs() * ((this.inputCircleRadius*2)+4); + let firstY = (centerY - (totalHeight/2)) + 12; + + let mouseDist = length2D(this.X+10, + firstY+ (a*24), + this.MousePosition.x, + this.MousePosition.y); + if (mouseDist <= (this.inputCircleRadius)) { + this.LogicEngine.Link(a); + foundInput = true; + break; + } + } + + } else { + if (mouseDistOutput <= (this.outputCircleRadius)) { + // Clicked on output, let us start a link + this.LogicEngine.Link(); + } + } + } + + mouseInside(mousePos) { + this.MouseOver = false; + if (((mousePos.x >= this.X ) && (mousePos.x <= (this.X + this.Width))) & ((mousePos.y >= this.Y ) && (mousePos.y <= (this.Y + this.Height)))) this.MouseOver = true; + this.MousePosition = mousePos; + return this.MouseOver; + } + + addConnection(container, element, input) { + for (let a = 0; a < this.OutputConnections.length; a++) { + if (this.OutputConnections[a].Element == element && this.OutputConnections[a].Input == input) { + // Already existing link, we will remove it instead + this.LogicEngine.RecursionCount = 0; + element.setInput(input,false); + this.OutputConnections.splice(a,1); + return; + } + } + let newConnection = new ElementConnection(container,element,input); + this.OutputConnections.push(newConnection); + this.LogicEngine.RecursionCount = 0; + element.setInput(input,this.getOutput()); + } + + drawInputs(ctx,x,y,borderColor = "#000",circleColorFalse = "#ff0000",circleColorTrue="#00ff00",circleColorHover = "#00ffff") { + ctx.save(); + //this.inputCircleRadius = 10; + let centerY = y + Math.round(this.Height / 2); + let totalHeight = this.totalInputs() * ((this.inputCircleRadius*2)+4); + let firstY = (centerY - (totalHeight/2)) + 12; + + for (let a = 0; a < this.totalInputs();a++) { + let mouseDist = length2D(x+10, firstY + (a*24),this.MousePosition.x,this.MousePosition.y); + ctx.beginPath(); + ctx.arc(x+10,firstY + (a*24),this.inputCircleRadius,0,2*Math.PI); + ctx.strokeStyle = borderColor; + ctx.fillStyle = circleColorFalse; + if (this.Inputs[a]) ctx.fillStyle = circleColorTrue; + if ((mouseDist <= (this.inputCircleRadius)) && this.LogicEngine.ActiveLink) ctx.fillStyle = circleColorHover; + ctx.fill(); + ctx.stroke(); + } + ctx.restore(); + } + + drawOutputs(ctx,x,y,borderColor = "#000",circleColorFalse = "#ff0000",circleColorTrue="#00ff00",circleColorHover="#00ffff") { + let old_strokeStyle = ctx.strokeStyle; + let old_fillStyle = ctx.fillStyle; + let mouseDist = length2D(x+(this.Width-10),y+(this.Height/2),this.MousePosition.x,this.MousePosition.y); + ctx.beginPath(); + ctx.arc(x+(this.Width-10),y+(this.Height/2),this.outputCircleRadius,0,2*Math.PI); + ctx.strokeStyle = borderColor; + ctx.fillStyle = circleColorFalse; + if (this.getOutput()) ctx.fillStyle = circleColorTrue; + if ((mouseDist <= (this.outputCircleRadius)) && !this.LogicEngine.ActiveLink) ctx.fillStyle = circleColorHover; + ctx.fill(); + ctx.stroke(); + ctx.strokeStyle = old_strokeStyle; + ctx.fillStyle = old_fillStyle; + } + + drawConnections(ctx,settings) { + ctx.save(); + for (let a = 0; a < this.OutputConnections.length;a++) { + if (!this.OutputConnections[a].Container.HasElement(this.OutputConnections[a].Element)) { + // This is a ghosted connection, lets get rid of it + this.OutputConnections.splice(a,1); + a--; + } else { + let endCenterY = this.OutputConnections[a].Element.Y + Math.round(this.OutputConnections[a].Element.Height / 2); + let endTotalHeight = this.OutputConnections[a].Element.totalInputs() * ((this.OutputConnections[a].Element.inputCircleRadius*2)+4); + let endFirstY = (endCenterY - (endTotalHeight/2)) + 12; + + let startX = this.X + this.Width; + let startY = this.Y+(this.Height/2); + let endX = this.OutputConnections[a].Element.X; + //let endY = this.OutputConnections[a].Element.Y+(this.OutputConnections[a].Element.inputCircleRadius + 2)+(((this.OutputConnections[a].Input*(4+(this.OutputConnections[a].Element.inputCircleRadius*2))))-2)+(this.OutputConnections[a].Element.inputCircleRadius/2); + let endY = endFirstY + (this.OutputConnections[a].Input*24); + let startMidX = startX + ((endX - startX)/2); + let startMidY = startY; + let midX = startMidX; + let midY = startY + ((endY - startY)/2); + let endMidX = startMidX; + let endMidY = endY; + + ctx.beginPath(); + ctx.lineWidth = settings.LinkWidth; + ctx.setLineDash(settings.LinkDash); + ctx.moveTo(startX, startY); + //ctx.lineTo(endX, endY); + ctx.quadraticCurveTo(startMidX,startMidY,midX,midY); + ctx.quadraticCurveTo(endMidX,endMidY,endX,endY); + ctx.strokeStyle = settings.ActiveConnectionColor; + if (!this.getOutput()) ctx.strokeStyle = settings.InactiveConnectionColor; + ctx.stroke(); + } + } + ctx.restore(); + } + + setInput(Input,Value) { + let oldOutput = this.getOutput(); + if (Value) { + Value = true; + } else { + Value = false; + } + if (Input < this.totalInputs()) { + this.Inputs[Input] = Value; + } else { + return; + } + if (this.getOutput() != oldOutput) { + // The output changed, we need to notify connected elements + for (let a = 0; a < this.OutputConnections.length;a++) { + //console.log(this.Designator + " sending " + this.getOutput() + " to " + this.OutputConnections[a].Element.Designator + " I" + this.OutputConnections[a].Input); + this.LogicEngine.RecursionCount++; + //console.log("Recursion: " + this.LogicEngine.RecursionCount); + if (this.LogicEngine.RecursionCount > 1000) { + if (!this.LogicEngine.RecursionError) { + console.log("RECURSION ERROR"); + this.LogicEngine.RecursionError = true; + } + return; + } + this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input,this.getOutput()); + this.LogicEngine.RecursionCount--; + } + } + } + + getOutput() { + /* + Should return true or false + */ + return false; + } + + drawElement(x,y,ctx) { + /* + Draw routine for the element + */ + this.drawBorderBox(ctx,x+10,y,drawWidth-20,drawHeight); + this.drawTextCentered(ctx,x,y,this.Width,this.Height,"LOGIC"); + this.drawInputs(ctx,x,y); + this.drawOutputs(ctx,x,y); + } + +} + +class ClockElement extends Element { + ClockTick() { + if (this.Inputs[0]) { + this.Output = ~this.Output; + if (this.Output) { + this.Task.Time = Math.round(this.Period * this.Duty); + } else { + this.Task.Time = this.Period - Math.round(this.Period * this.Duty); + } + for (let a = 0; a < this.OutputConnections.length; a++) { + this.LogicEngine.RecursionCount = 0; + this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput()); + } + } + } + + Delete() { + super.Delete(); + this.LogicEngine.Scheduler.deleteTask(this.Task); + } + + getOutput() { + return this.Output; + } + + setInput(Input, Value) { + super.setInput(Input, Value); + if (!this.Inputs[0]) { + this.Output = false; + this.Task.LastCall = 0; + this.Task.Enabled = false; + for (let a = 0; a < this.OutputConnections.length;a++) { + this.LogicEngine.RecursionCount = 0; + this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input,this.getOutput()); + } + } else { + this.Task.Enabled = true; + } + } + + constructor(RestoreData = null, logicengine) { + super(RestoreData,logicengine,1); + this.removeProperty("Inputs"); + this.Name = "Clock"; + this.Period = 1000; + this.Duty = 0.5; + this.Output = false; + this.Width = 100; + this.Task = new Task("ClockTask","CLOCK",0,Math.round(this.Period * this.Duty),this.ClockTick.bind(this)); + this.setInput(0,true); + this.removeProperty("Inputs"); + let periodProperty = new ElementProperty("Period","int",{CBObject: this,CBFunction: "setPeriod"},1000,false,false,4,999999); + let dutyProperty = new ElementProperty("Duty","int",{CBObject: this,CBFunction: "setDuty"},50,false,false,0,100); + if (RestoreData) { + if (this.Height != RestoreData.Height) { + // Oddly this height value gets set to 25000 on return from the above super(), if anyone has any ideas why let me know! + this.Height = RestoreData.Height; + } + periodProperty.DefaultValue = RestoreData.Properties[0].DefaultValue; + periodProperty.CurrentValue = RestoreData.Properties[0].CurrentValue; + periodProperty.Values = RestoreData.Properties[0].Values; + periodProperty.Minimium = RestoreData.Properties[0].Minimium; + periodProperty.Maximium = RestoreData.Properties[0].Maximium; + dutyProperty.DefaultValue = RestoreData.Properties[1].DefaultValue; + dutyProperty.CurrentValue = RestoreData.Properties[1].CurrentValue; + dutyProperty.Values = RestoreData.Properties[1].Values; + dutyProperty.Minimium = RestoreData.Properties[1].Minimium; + dutyProperty.Maximium = RestoreData.Properties[1].Maximium; + + this.Period = periodProperty.CurrentValue; + this.Duty = dutyProperty.CurrentValue/100; + this.Task.Enabled = RestoreData.Task.Enabled; + this.Task.Time = RestoreData.Task.Time; + this.Task.LastCall = RestoreData.Task.LastCall + Date.now(); + this.Task.CallCount = RestoreData.Task.CallCount; + } + this.Properties.push(periodProperty); + this.Properties.push(dutyProperty); + this.LogicEngine.Scheduler.addTask(this.Task); + } + + toJSON(key) { + let superjson = super.toJSON(key); + superjson.Task = { + Enabled: this.Task.Enabled, + Time: this.Task.Time, + LastCall: Date.now() - this.Task.LastCall, + CallCount: this.Task.CallCount + }; + return superjson; + } + + setPeriod(period) { + this.Period = period; + this.Task.LastCall = 0; + this.getProperty("Period").CurrentValue = period; + } + + setDuty(duty) { + this.Duty = duty/100; + this.Task.LastCall = 0; + this.getProperty("Duty").CurrentValue = duty; + } + + drawElement(x, y, ctx) { + this.drawBorderBox(ctx, x+10,y,this.Width-20,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor); + this.drawTextCentered(ctx,x,y+5,this.Width,12,this.Period + "ms " + (this.Duty * 100) + "%","10px Console"); + if (this.Task.Enabled) this.drawTextCentered(ctx,x,y+this.Height-16,this.Width,12,(this.Task.Time - (Date.now() - this.Task.LastCall)) + "ms","10px Console"); + this.drawTextCentered(ctx,x,y,this.Width,this.Height,this.Designator,"12px Console","#000"); + this.drawInputs(ctx,x,y); + this.drawOutputs(ctx,x,y); + } +} +let ElementCatalog_CLOCK = new ElementCatalog_Element("Clock","The clock will output a pulse for the length of the duty cycle for a given period. I.E. if a 1000ms period with 50% duty cycle is given, the output will be high for 500ms and low for 500ms. The clock can be disabled with a low input, this also resets the clocks start time to allow for syncing.","🕑",ClockElement,[]); +ElementReferenceTable.push(ElementCatalog_CLOCK); +ElementCategory_Timing.addElement(ElementCatalog_CLOCK); + +class PulseElement extends Element { + ClockTick() { + this.Output = false; + this.Task.Enabled = false; + this.Task.LastCall = Date.now(); + for (let a = 0; a < this.OutputConnections.length; a++) { + this.LogicEngine.RecursionCount = 0; + this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput()); + } + } + + Delete() { + super.Delete(); + this.LogicEngine.Scheduler.deleteTask(this.Task); + } + + getOutput() { + return this.Output; + } + + setInput(Input, Value) { + if (Input > 0) return; + //super.setInput(Input, Value); + this.Inputs[Input] = Value; + if (this.Inputs[0] && !this.Task.Enabled) { + this.Output = true; + for (let a = 0; a < this.OutputConnections.length;a++) { + this.LogicEngine.RecursionCount = 0; + this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input,this.getOutput()); + } + this.Task.LastCall = Date.now(); + this.Task.Enabled = true; + } + } + + constructor(RestoreData = null, logicengine) { + super(RestoreData,logicengine,1); + this.removeProperty("Inputs"); + this.Name = "Pulse"; + this.Period = 100; + this.Output = false; + this.Width = 100; + this.Task = new Task("PulseTask","CLOCK",0,this.Period,this.ClockTick.bind(this)); + this.removeProperty("Inputs"); + let periodProperty = new ElementProperty("Period","int",{CBObject: this,CBFunction: "setPeriod"},100,false,false,4,999999); + if (RestoreData) { + if (this.Height != RestoreData.Height) { + // Oddly this height value gets set to 25000 on return from the above super(), if anyone has any ideas why let me know! + this.Height = RestoreData.Height; + } + periodProperty.DefaultValue = RestoreData.Properties[0].DefaultValue; + periodProperty.CurrentValue = RestoreData.Properties[0].CurrentValue; + periodProperty.Values = RestoreData.Properties[0].Values; + periodProperty.Minimium = RestoreData.Properties[0].Minimium; + periodProperty.Maximium = RestoreData.Properties[0].Maximium; + + this.Period = periodProperty.CurrentValue; + + this.Task.Enabled = RestoreData.Task.Enabled; + this.Task.Time = RestoreData.Task.Time; + this.Task.LastCall = RestoreData.Task.LastCall + Date.now(); + this.Task.CallCount = RestoreData.Task.CallCount; + } + + + this.Properties.push(periodProperty); + this.LogicEngine.Scheduler.addTask(this.Task); + } + + toJSON(key) { + let superjson = super.toJSON(key); + superjson.Task = { + Enabled: this.Task.Enabled, + Time: this.Task.Time, + LastCall: Date.now() - this.Task.LastCall, + CallCount: this.Task.CallCount + }; + return superjson; + } + + + setPeriod(period) { + this.Period = parseInt(period); + this.Task.Time = parseInt(period); + this.getProperty("Period").CurrentValue = parseInt(period); + } + + drawElement(x, y, ctx) { + this.drawBorderBox(ctx, x+10,y,this.Width-20,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor); + this.drawTextCentered(ctx,x,y+5,this.Width,12,this.Period + "ms","10px Console"); + if (this.Task.Enabled) this.drawTextCentered(ctx,x,y+this.Height-16,this.Width,12,(this.Task.Time - (Date.now() - this.Task.LastCall)) + "ms","10px Console"); + this.drawTextCentered(ctx,x,y,this.Width,this.Height,this.Designator,"12px Console","#000"); + this.drawInputs(ctx,x,y); + this.drawOutputs(ctx,x,y); + } +} +let ElementCatalog_PULSE = new ElementCatalog_Element("Pulse","The pulse upon input will output a high only for as long as the period set, I.E. if a period of 1000ms is set then any input no matter how long, will trigger a pulse 1000ms long.","|\\__",PulseElement,[]); +ElementReferenceTable.push(ElementCatalog_PULSE); +ElementCategory_Timing.addElement(ElementCatalog_PULSE); + +class DelayElement extends Element { + ClockTick() { + if (this.Output) { + this.Output = false; + this.Task.Enabled = false; + this.Task.LastCall = Date.now(); + this.Task.Time = this.Period; + if (this.InputPulses.length > 0) { + this.Task.Enabled = true; + } + } else { + this.Output = true; + this.Task.Enabled = false; + if (this.InputPulses.length > 0) { + this.Task.Time = this.InputPulses[0]; + this.Task.Enabled = true; + this.InputPulses.splice(0,1); + } else { + if (this.InputEnd > 0) { + this.Task.Time = this.InputEnd - this.InputStart; + this.Task.Enabled = true; + } + } + this.Task.LastCall = Date.now(); + } + + for (let a = 0; a < this.OutputConnections.length; a++) { + this.LogicEngine.RecursionCount = 0; + this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput()); + } + } + + Delete() { + super.Delete(); + this.LogicEngine.Scheduler.deleteTask(this.Task); + } + + getOutput() { + return this.Output; + } + + setInput(Input, Value) { + if (Input > 0) return; + if (this.Inputs[Input] == Value) return; + //super.setInput(Input, Value); + this.Inputs[Input] = Value; + if (this.Inputs[0] && !this.Task.Enabled) { + this.InputStart = Date.now(); + this.InputEnd = 0; + this.Output = false; + for (let a = 0; a < this.OutputConnections.length;a++) { + this.LogicEngine.RecursionCount = 0; + this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input,this.getOutput()); + } + this.Task.LastCall = Date.now(); + this.Task.Enabled = true; + } else { + if (!this.Inputs[Input]) { + this.InputEnd = Date.now(); + if (this.InputPulses.length > 0) { + this.InputPulses.push(this.InputEnd - this.InputStart); + this.InputStart = 0; + this.InputEnd = 0; + } else { + if (!this.Task.Enabled) { + this.Task.LastCall = Date.now(); + this.Task.Time = (this.InputEnd - this.InputStart) - (Date.now() - this.InputEnd); + this.Task.Enabled = true; + } else { + this.InputPulses.push(this.InputEnd - this.InputStart); + } + } + } else { + // We are in a condition where input went true, but the task is already enabled + this.InputStart = Date.now(); + } + } + } + + constructor(RestoreData = null, logicengine) { + super(RestoreData,logicengine,1); + this.removeProperty("Inputs"); + this.Name = "Delay"; + this.Period = 100; + this.Output = false; + this.Width = 100; + this.Task = new Task("DelayTask","CLOCK",0,this.Period,this.ClockTick.bind(this)); + this.Task.Enabled = false; + this.removeProperty("Inputs"); + let periodProperty = new ElementProperty("Period","int",{CBObject: this,CBFunction: "setPeriod"},100,false,false,4,999999); + if (RestoreData) { + if (this.Height != RestoreData.Height) { + // Oddly this height value gets set to 25000 on return from the above super(), if anyone has any ideas why let me know! + this.Height = RestoreData.Height; + } + periodProperty.DefaultValue = RestoreData.Properties[0].DefaultValue; + periodProperty.CurrentValue = RestoreData.Properties[0].CurrentValue; + periodProperty.Values = RestoreData.Properties[0].Values; + periodProperty.Minimium = RestoreData.Properties[0].Minimium; + periodProperty.Maximium = RestoreData.Properties[0].Maximium; + + this.Period = periodProperty.CurrentValue; + + this.Task.Enabled = RestoreData.Task.Enabled; + this.Task.Time = RestoreData.Task.Time; + this.Task.LastCall = RestoreData.Task.LastCall + Date.now(); + this.Task.CallCount = RestoreData.Task.CallCount; + } + this.Properties.push(periodProperty); + this.InputStart = 0; + this.InputEnd = 0; + this.InputPulses = new Array(); + this.Inputs[0] = false; + this.LogicEngine.Scheduler.addTask(this.Task); + } + toJSON(key) { + let superjson = super.toJSON(key); + superjson.InputStart = this.InputStart; + superjson.InputEnd = this.InputEnd; + superjson.InputPulses = this.InputPulses; + superjson.Task = { + Enabled: this.Task.Enabled, + Time: this.Task.Time, + LastCall: Date.now() - this.Task.LastCall, + CallCount: this.Task.CallCount + }; + return superjson; + } + + setPeriod(period) { + this.Period = parseInt(period); + this.Task.Time = parseInt(period); + this.getProperty("Period").CurrentValue = parseInt(period); + } + + drawElement(x, y, ctx) { + this.drawBorderBox(ctx, x+10,y,this.Width-20,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor); + this.drawTextCentered(ctx,x,y+5,this.Width,12,this.Period + "ms","10px Console"); + if (this.Task.Enabled) this.drawTextCentered(ctx,x,y+this.Height-16,this.Width,12,(this.Task.Time - (Date.now() - this.Task.LastCall)) + "ms " + "(" + this.InputPulses.length + ")","10px Console"); + this.drawTextCentered(ctx,x,y,this.Width,this.Height,this.Designator,"12px Console","#000"); + this.drawInputs(ctx,x,y); + this.drawOutputs(ctx,x,y); + } +} +let ElementCatalog_DELAY = new ElementCatalog_Element("Delay","The delay element will delay any input signal for the given period then output it for the same length of time it came in. Inputs will be buffered and played back out at the period rate.","__|\\",DelayElement,[]); +ElementReferenceTable.push(ElementCatalog_DELAY); +ElementCategory_Timing.addElement(ElementCatalog_DELAY); + +class inputElement extends Element { + constructor(RestoreData = null,logicengine) { + super(RestoreData,logicengine,0); + this.Name = "InputElement"; + this.Output = false; + this.Width = 100; + this.removeProperty("Inputs"); + } + + getOutput() { + return this.Output; + } + + drawElement(x, y, ctx) { + this.drawBorderBox(ctx, x,y,this.Width,this.Height); + this.drawTextCentered(ctx,x,y,this.Width,this.Height,"IN"); + this.drawOutputs(ctx,x,y); + } +} + +class InputSwitch extends inputElement { + constructor(RestoreData = null, logicengine) { + super(RestoreData,logicengine); + this.Name = "Switch"; + this.Height = 70; + if (RestoreData) this.Output = RestoreData.Output; + } + + MouseClick(mousePos) { + super.MouseClick(mousePos); + if ((mousePos.x >= (this.X + 5)) && (mousePos.x <= (this.X + 55)) && (mousePos.y >= (this.Y + 5)) && (mousePos.y <= (this.Y + 55))) { + this.Output = ~this.Output; + for (let a = 0; a < this.OutputConnections.length; a++) { + this.LogicEngine.RecursionCount = 0; + this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput()); + } + } + } + + drawElement(x, y, ctx) { + this.drawBorderBox(ctx, x,y,this.Width-20,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor); + //this.drawBorderBox(ctx,x+5,y+5,50,50,1,"#ccc","#777"); + this.drawBorderBox(ctx,x+5,y+25,50,10,1,"#ccc","#777"); + if (this.getOutput()) { + this.drawBorderBox(ctx,x+15,y+5,30,25,1,"#ccc","#777"); + this.drawTextCentered(ctx,x,y+this.Height - 30,this.Width-(this.outputCircleRadius*2)-20,12,"OFF","12px Console","#000"); + } else { + this.drawBorderBox(ctx,x+15,y+30,30,25,1,"#ccc","#777"); + this.drawTextCentered(ctx,x,y+7,this.Width-(this.outputCircleRadius*2)-20,12,"ON","12px Console","#000"); + } + this.drawTextCentered(ctx,x,y+(this.Height-14),this.Width-(this.outputCircleRadius*2),12,this.Designator,"12px Console","#000"); + this.drawOutputs(ctx,x,y); + } +} +let ElementCatalog_SWITCH = new ElementCatalog_Element("Switch","The switch allows for toggling an output low or high.","|-",InputSwitch,[]); +ElementReferenceTable.push(ElementCatalog_SWITCH); +ElementCategory_Inputs.addElement(ElementCatalog_SWITCH); + +class InputButton extends inputElement { + constructor(RestoreData = null, logicengine) { + super(RestoreData,logicengine); + this.Name = "Button"; + this.Height = 70; + } + + MouseDown(mousePos) { + if ((mousePos.x >= (this.X + 5)) && (mousePos.x <= (this.X + 55)) && (mousePos.y >= (this.Y + 5)) && (mousePos.y <= (this.Y + 55))) { + let old_output = this.Output; + this.Output = true; + this.LogicEngine.MouseDown = false; // Prevent movement on the button when its being pushed + if (old_output != this.Output) { + for (let a = 0; a < this.OutputConnections.length; a++) { + this.LogicEngine.RecursionCount = 0; + this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput()); + } + } + } + } + + MouseUp(mousePos) { + let old_output = this.Output; + this.Output = false; + if (old_output != this.Output) { + for (let a = 0; a < this.OutputConnections.length; a++) { + this.LogicEngine.RecursionCount = 0; + this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput()); + } + } + } + + drawElement(x, y, ctx) { + this.drawBorderBox(ctx, x,y,this.Width-20,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor); + this.drawBorderBox(ctx,x+5,y+5,50,50,1,"#ccc","#777"); + this.drawTextCentered(ctx,x,y+(this.Height-14),this.Width-(this.outputCircleRadius*2),12,this.Designator,"12px Console","#000"); + this.drawOutputs(ctx,x,y); + } +} +let ElementCatalog_BUTTON = new ElementCatalog_Element("Button","The button only outputs high when the button is pressed.","[o]",InputButton,[]); +ElementReferenceTable.push(ElementCatalog_BUTTON); +ElementCategory_Inputs.addElement(ElementCatalog_BUTTON); + +class LogicAND extends Element { + constructor(RestoreData = null, logicengine,Inputs) { + super(RestoreData,logicengine,Inputs); + this.Name = "AND"; + } + + getOutput() { + let ANDResult = true; + for (let a = 0; a < this.totalInputs();a++) { + if (!this.Inputs[a]) ANDResult = false; + } + return ANDResult; + } + drawElement(x,y,ctx) { + //this.drawBorderBox(ctx, x+10,y,this.Width-20,this.Height); + let xOffset = 20; + let shadowColor = this.LogicEngine.Settings.ShadowColor; + ctx.save(); + ctx.beginPath(); + ctx.moveTo(x+xOffset,y); + ctx.lineTo((x+xOffset)+ this.Width/4,y); + ctx.quadraticCurveTo(x+(this.Width - xOffset),y,x+(this.Width-xOffset),y+(this.Height/2)); + ctx.quadraticCurveTo(x+(this.Width - xOffset),y+this.Height,(x+xOffset)+ this.Width/4,y+this.Height); + ctx.lineTo(x+xOffset,y+this.Height); + ctx.lineTo(x+xOffset,y); + ctx.lineWidth = "3"; + ctx.fillStyle = "#f7e979"; + ctx.shadowColor = shadowColor; + ctx.shadowBlur = "6"; + ctx.shadowOffsetX = 2; + ctx.shadowOffsetY = 2; + ctx.stroke(); + ctx.fill(); + ctx.restore(); + this.drawTextCentered(ctx,x,y,this.Width,this.Height,this.Designator,"12px Console","#000"); + this.drawInputs(ctx,x,y); + this.drawOutputs(ctx,x,y); + } +} +let ElementCatalog_AND = new ElementCatalog_Element("AND","The AND gate only outputs high when all of the inputs are high.","&",LogicAND,[2]); +ElementReferenceTable.push(ElementCatalog_AND); +ElementCategory_LOGIC.addElement(ElementCatalog_AND); + +class LogicNAND extends LogicAND { + constructor(RestoreData = null, logicengine,Inputs) { + super(RestoreData,logicengine,Inputs); + this.Name = "NAND"; + this.Width = this.Width + 10; + } + getOutput() { + if (super.getOutput()) { + return false; + } else { + return true; + } + } + drawElement(x,y,ctx) { + let xOffset = 20; + let shadowColor = this.LogicEngine.Settings.ShadowColor; + ctx.save(); + ctx.beginPath(); + ctx.moveTo(x+xOffset,y); + ctx.lineTo((x+xOffset)+ this.Width/4,y); + ctx.quadraticCurveTo(x+(this.Width - (xOffset*1.5)),y,x+(this.Width-(xOffset*1.5)),y+(this.Height/2)); + ctx.quadraticCurveTo(x+(this.Width - (xOffset*1.5)),y+this.Height,(x+xOffset)+ this.Width/4,y+this.Height); + ctx.lineTo(x+xOffset,y+this.Height); + ctx.lineTo(x+xOffset,y); + ctx.lineWidth = "3"; + ctx.fillStyle = "#f7e979"; + ctx.strokeStyle = "#000000"; + ctx.shadowColor = shadowColor; + ctx.shadowBlur = "6"; + ctx.shadowOffsetX = 2; + ctx.shadowOffsetY = 2; + ctx.stroke(); + ctx.fill(); + + ctx.beginPath(); + ctx.fillStyle = "#000000"; + ctx.strokeStyle = "#000000"; + ctx.lineWidth = "1"; + ctx.arc(x + (this.Width - 25),y + (this.Height/2),5,0,2*Math.PI); + ctx.stroke(); + ctx.fill(); + + ctx.restore(); + this.drawTextCentered(ctx,x,y,this.Width-xOffset,this.Height,this.Designator,"12px Console","#000"); + this.drawInputs(ctx,x,y); + this.drawOutputs(ctx,x,y); + } +} +let ElementCatalog_NAND = new ElementCatalog_Element("NAND","The NAND gate always outputs a high signal unless all the inputs are high then it goes low.","!&",LogicNAND,[2]); +ElementReferenceTable.push(ElementCatalog_NAND); +ElementCategory_LOGIC.addElement(ElementCatalog_NAND); + +class LogicOR extends Element { + constructor(RestoreData = null, logicengine,Inputs) { + super(RestoreData,logicengine,Inputs); + this.Name = "OR"; + } + + getOutput() { + let ORResult = false; + for (let a = 0; a < this.totalInputs();a++) { + if (this.Inputs[a]) ORResult = true; + } + return ORResult; + } + drawElement(x,y,ctx) { + let drawWidth = this.Width; + let drawHeight = this.Height; + let xOffset = 20; + let shadowColor = this.LogicEngine.Settings.ShadowColor; + + ctx.save(); + ctx.beginPath(); + ctx.moveTo(x+(xOffset/4),y); + ctx.lineTo((x+xOffset)+ this.Width/4,y); + ctx.quadraticCurveTo(x+(this.Width - (xOffset*2)),y,x+(this.Width-xOffset),y+(this.Height/2)); + ctx.quadraticCurveTo(x+(this.Width - (xOffset*2)),y+this.Height,(x+xOffset)+ this.Width/4,y+this.Height); + ctx.lineTo(x+(xOffset/4),y+this.Height); + ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height - (this.Height/32)),x+xOffset+5,y+(this.Height/2)); + ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height/32),x+(xOffset/4),y); + ctx.lineWidth = "3"; + ctx.fillStyle = "#f7e979"; + ctx.shadowColor = shadowColor; + ctx.shadowBlur = "6"; + ctx.shadowOffsetX = 2; + ctx.shadowOffsetY = 2; + ctx.stroke(); + ctx.fill(); + ctx.restore(); + + this.drawTextCentered(ctx,x,y,this.Width,this.Height,this.Designator,"12px Console","#000"); + this.drawInputs(ctx,x,y); + this.drawOutputs(ctx,x,y); + } +} +let ElementCatalog_OR = new ElementCatalog_Element("OR","The OR gate outputs a high when any input is HIGH.","|",LogicOR,[2]); +ElementReferenceTable.push(ElementCatalog_OR); +ElementCategory_LOGIC.addElement(ElementCatalog_OR); + +class LogicNOR extends LogicOR { + constructor(RestoreData = null, logicengine,Inputs) { + super(RestoreData,logicengine,Inputs); + this.Name = "NOR"; + this.Width = this.Width + 10; + } + getOutput() { + if (super.getOutput()) { + return false; + } else { + return true; + } + } + drawElement(x,y,ctx) { + let xOffset = 20; + let shadowColor = this.LogicEngine.Settings.ShadowColor; + + ctx.save(); + ctx.beginPath(); + ctx.moveTo(x+(xOffset/4),y); + ctx.lineTo((x+xOffset)+ this.Width/4,y); + ctx.quadraticCurveTo(x+(this.Width - (xOffset*2.5)),y,x+(this.Width-(xOffset*1.5)),y+(this.Height/2)); + ctx.quadraticCurveTo(x+(this.Width - (xOffset*2.5)),y+this.Height,(x+xOffset)+ this.Width/4,y+this.Height); + ctx.lineTo(x+(xOffset/4),y+this.Height); + ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height - (this.Height/32)),x+xOffset+5,y+(this.Height/2)); + ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height/32),x+(xOffset/4),y); + ctx.lineWidth = "3"; + ctx.fillStyle = "#f7e979"; + ctx.shadowColor = shadowColor; + ctx.shadowBlur = "6"; + ctx.shadowOffsetX = 2; + ctx.shadowOffsetY = 2; + ctx.stroke(); + ctx.fill(); + + ctx.beginPath(); + ctx.fillStyle = "#000000"; + ctx.strokeStyle = "#000000"; + ctx.lineWidth = "1"; + ctx.arc(x + (this.Width - 25),y + (this.Height/2),5,0,2*Math.PI); + ctx.stroke(); + ctx.fill(); + + ctx.restore(); + + this.drawTextCentered(ctx,x,y,this.Width-xOffset,this.Height,this.Designator,"12px Console","#000"); + this.drawInputs(ctx,x,y); + this.drawOutputs(ctx,x,y); + } +} +let ElementCatalog_NOR = new ElementCatalog_Element("NOR","The NOR gate outputs a high only when all inputs are low.","!|",LogicNOR,[2]); +ElementReferenceTable.push(ElementCatalog_NOR); +ElementCategory_LOGIC.addElement(ElementCatalog_NOR); + +class LogicXOR extends Element { + constructor(RestoreData = null, logicengine) { + super(RestoreData,logicengine,2); // Only 2 inputs on XOR + this.Name = "XOR"; + this.removeProperty("Inputs"); + } + + getOutput() { + let ORResult = false; + if ( (this.Inputs[0] && !this.Inputs[1]) || (!this.Inputs[0] && this.Inputs[1]) ) ORResult = true; + return ORResult; + } + + drawElement(x,y,ctx) { + let drawWidth = this.Width; + let drawHeight = this.Height; + + let xOffset = 20; + let shadowColor = this.LogicEngine.Settings.ShadowColor; + + ctx.save(); + ctx.beginPath(); + ctx.moveTo(x+(xOffset/4),y); + ctx.lineTo((x+xOffset)+ this.Width/4,y); + ctx.quadraticCurveTo(x+(this.Width - (xOffset*2)),y,x+(this.Width-xOffset),y+(this.Height/2)); + ctx.quadraticCurveTo(x+(this.Width - (xOffset*2)),y+this.Height,(x+xOffset)+ this.Width/4,y+this.Height); + ctx.lineTo(x+(xOffset/4),y+this.Height); + ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height - (this.Height/32)),x+xOffset+5,y+(this.Height/2)); + ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height/32),x+(xOffset/4),y); + ctx.lineWidth = "3"; + ctx.fillStyle = "#f7e979"; + ctx.shadowColor = shadowColor; + ctx.shadowBlur = "6"; + ctx.shadowOffsetX = 2; + ctx.shadowOffsetY = 2; + ctx.stroke(); + ctx.fill(); + + ctx.lineWidth = "2"; + ctx.beginPath(); + ctx.shadowColor = "transparent"; + ctx.moveTo(x+(xOffset/4)+10,y+this.Height); + ctx.quadraticCurveTo(x+(xOffset+10),y+(this.Height - (this.Height/32)),x+xOffset+10,y+(this.Height/2)); + ctx.quadraticCurveTo(x+(xOffset+10),y+(this.Height/32),x+(xOffset/4)+10,y); + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(x+(xOffset/4),y+this.Height); + ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height - (this.Height/32)),x+xOffset+5,y+(this.Height/2)); + ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height/32),x+(xOffset/4),y); + ctx.stroke(); + ctx.restore(); + + this.drawTextCentered(ctx,x,y,this.Width,this.Height,this.Designator,"12px Console","#000"); + this.drawInputs(ctx,x,y); + this.drawOutputs(ctx,x,y); + } +} +let ElementCatalog_XOR = new ElementCatalog_Element("XOR","The XOR gate outputs a high when only 1 input is high.","^",LogicXOR,[]); +ElementReferenceTable.push(ElementCatalog_XOR); +ElementCategory_LOGIC.addElement(ElementCatalog_XOR); + +class LogicXNOR extends Element { + constructor(RestoreData = null, logicengine) { + super(RestoreData,logicengine,2); // Only 2 inputs on XOR + this.Name = "XNOR"; + this.removeProperty("Inputs"); + this.Width += 10; + } + + getOutput() { + let ORResult = false; + if ( (this.Inputs[0] && !this.Inputs[1]) || (!this.Inputs[0] && this.Inputs[1]) ) ORResult = true; + return !ORResult; + } + + drawElement(x,y,ctx) { + let drawWidth = this.Width; + let drawHeight = this.Height; + + let xOffset = 20; + let shadowColor = this.LogicEngine.Settings.ShadowColor; + + ctx.save(); + ctx.beginPath(); + ctx.save(); + ctx.beginPath(); + ctx.moveTo(x+(xOffset/4),y); + ctx.lineTo((x+xOffset)+ this.Width/4,y); + ctx.quadraticCurveTo(x+(this.Width - (xOffset*2.5)),y,x+(this.Width-(xOffset*1.5)),y+(this.Height/2)); + ctx.quadraticCurveTo(x+(this.Width - (xOffset*2.5)),y+this.Height,(x+xOffset)+ this.Width/4,y+this.Height); + ctx.lineTo(x+(xOffset/4),y+this.Height); + ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height - (this.Height/32)),x+xOffset+5,y+(this.Height/2)); + ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height/32),x+(xOffset/4),y); + ctx.lineWidth = "3"; + ctx.fillStyle = "#f7e979"; + ctx.shadowColor = shadowColor; + ctx.shadowBlur = "6"; + ctx.shadowOffsetX = 2; + ctx.shadowOffsetY = 2; + ctx.stroke(); + ctx.fill(); + + ctx.beginPath(); + ctx.fillStyle = "#000000"; + ctx.strokeStyle = "#000000"; + ctx.lineWidth = "1"; + ctx.arc(x + (this.Width - 25),y + (this.Height/2),5,0,2*Math.PI); + ctx.stroke(); + ctx.fill(); + + ctx.lineWidth = "2"; + ctx.beginPath(); + ctx.shadowColor = "transparent"; + ctx.moveTo(x+(xOffset/4)+10,y+this.Height); + ctx.quadraticCurveTo(x+(xOffset+10),y+(this.Height - (this.Height/32)),x+xOffset+10,y+(this.Height/2)); + ctx.quadraticCurveTo(x+(xOffset+10),y+(this.Height/32),x+(xOffset/4)+10,y); + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(x+(xOffset/4),y+this.Height); + ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height - (this.Height/32)),x+xOffset+5,y+(this.Height/2)); + ctx.quadraticCurveTo(x+(xOffset+5),y+(this.Height/32),x+(xOffset/4),y); + ctx.stroke(); + ctx.restore(); + + this.drawTextCentered(ctx,x,y,this.Width-(xOffset/2),this.Height,this.Designator,"10px Console","#000"); + this.drawInputs(ctx,x,y); + this.drawOutputs(ctx,x,y); + } +} + +let ElementCatalog_XNOR = new ElementCatalog_Element("XNOR","The XNOR gate outputs a high only when both inputs are either low or high.","!^",LogicXNOR,[]); +ElementReferenceTable.push(ElementCatalog_XNOR); +ElementCategory_LOGIC.addElement(ElementCatalog_XNOR); + +class LogicNOT extends Element { + constructor(RestoreData = null, logicengine) { + super(RestoreData,logicengine,1); // Only 1 inputs on NOT + this.Name = "NOT"; + this.removeProperty("Inputs"); + this.Width += 10; + } + + getOutput() { + if (this.Inputs[0]) return false; + return true; + } + + drawElement(x,y,ctx) { + let drawWidth = this.Width; + let drawHeight = this.Height; + + let xOffset = 20; + let shadowColor = this.LogicEngine.Settings.ShadowColor; + + ctx.save(); + ctx.beginPath(); + ctx.moveTo(x+xOffset,y); + ctx.lineTo(x+(this.Width-(xOffset+10)),y+(this.Height/2)); + ctx.lineTo(x+xOffset,y+this.Height); + ctx.lineTo(x+xOffset,y); + ctx.lineWidth = "3"; + ctx.fillStyle = "#f7e979"; + ctx.shadowColor = shadowColor; + ctx.shadowBlur = "6"; + ctx.shadowOffsetX = 2; + ctx.shadowOffsetY = 2; + ctx.stroke(); + ctx.fill(); + + ctx.beginPath(); + ctx.fillStyle = "#000000"; + ctx.strokeStyle = "#000000"; + ctx.lineWidth = "1"; + ctx.arc(x + (this.Width - 25),y + (this.Height/2),5,0,2*Math.PI); + ctx.stroke(); + ctx.fill(); + + ctx.restore(); + this.drawTextCentered(ctx,x,y,this.Width-(xOffset),this.Height,this.Designator,"12px Console","#000"); + this.drawInputs(ctx,x,y); + this.drawOutputs(ctx,x,y); + } +} +let ElementCatalog_NOT = new ElementCatalog_Element("NOT","The NOT gate outputs the opposite of the input.","!",LogicNOT,[]); +ElementReferenceTable.push(ElementCatalog_NOT); +ElementCategory_LOGIC.addElement(ElementCatalog_NOT); + +class LogicBuffer extends Element { + ClockTick() { + this.Output = this.Inputs[0]; + this.Task.Enabled = false; + this.Task.LastCall = 0; + + for (let a = 0; a < this.OutputConnections.length; a++) { + this.LogicEngine.RecursionCount = 0; + this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput()); + } + } + + Delete() { + super.Delete(); + this.LogicEngine.Scheduler.deleteTask(this.Task); + } + + getOutput() { + return this.Output; + } + + setInput(Input, Value) { + if (Input > 0) return; + //super.setInput(Input, Value); + this.Inputs[Input] = Value; + if (!this.Task.Enabled) { + this.Task.LastCall = 0; + this.Task.Enabled = true; + } + } + + constructor(RestoreData = null, logicengine) { + super(RestoreData,logicengine,1); + this.removeProperty("Inputs"); + this.Output = false; + this.Name = "Buffer"; + this.Width = 100; + this.Task = new Task("BufferTask","Buffer",0,9999999,this.ClockTick.bind(this)); + this.Task.Time = 4; + this.Task.Enabled = false; + this.Task.LastCall = 0; + this.removeProperty("Inputs"); + if (RestoreData) this.Output = RestoreData.Output; + this.LogicEngine.Scheduler.addTask(this.Task); + } + + toJSON(key) { + let superjson = super.toJSON(key); + superjson.Task = { + Enabled: this.Task.Enabled, + Time: this.Task.Time, + LastCall: Date.now() - this.Task.LastCall, + CallCount: this.Task.CallCount + }; + return superjson; + } + + + drawElement(x, y, ctx) { + let drawWidth = this.Width; + let drawHeight = this.Height; + + let xOffset = 20; + let shadowColor = this.LogicEngine.Settings.ShadowColor; + + ctx.save(); + ctx.beginPath(); + ctx.moveTo(x+xOffset,y); + ctx.lineTo(x+(this.Width-(xOffset)),y+(this.Height/2)); + ctx.lineTo(x+xOffset,y+this.Height); + ctx.lineTo(x+xOffset,y); + ctx.lineWidth = "3"; + ctx.fillStyle = "#f7e979"; + ctx.shadowColor = shadowColor; + ctx.shadowBlur = "6"; + ctx.shadowOffsetX = 2; + ctx.shadowOffsetY = 2; + ctx.stroke(); + ctx.fill(); + ctx.restore(); + this.drawTextCentered(ctx,x,y,this.Width-(xOffset),this.Height,this.Designator,"12px Console","#000"); + this.drawInputs(ctx,x,y); + this.drawOutputs(ctx,x,y); + } +} +let ElementCatalog_BUFFER = new ElementCatalog_Element("Buffer","The buffer is a special gate to allow the prevention of recursion loops.","|>",LogicBuffer,[]); +ElementReferenceTable.push(ElementCatalog_BUFFER); +ElementCategory_LOGIC.addElement(ElementCatalog_BUFFER); + diff --git a/js/globalfunctions.js b/js/globalfunctions.js new file mode 100644 index 0000000..5a1d17c --- /dev/null +++ b/js/globalfunctions.js @@ -0,0 +1,163 @@ +function addElement(RestoreData = null,refClass,props) { + let newElement = new refClass(RestoreData,logicEngine,...props); + + if (!RestoreData) { + let x = Math.round(logicEngine.Canvas.width / 2); + let y = Math.round(logicEngine.Canvas.height / 2); + let width = newElement.Width; + let height = newElement.Height; + + let noclearspot = true; + while (noclearspot) { + if (!logicEngine.ActiveContainer.checkOverlayBounds(x, y, width, height)) { + noclearspot = false; + } else { + x += Math.floor(Math.random() * (width - (width - (width * 2)))) + (width - (width * 2)); + y += Math.floor(Math.random() * (height - (height - (height * 2)))) + (height - (height * 2)); + if (x < 0) x = 0; + if (y < 0) y = 0; + if (x + width > logicEngine.Canvas.width) x = logicEngine.Canvas.width - width; + if (y + height > logicEngine.Canvas.height) y = logicEngine.Canvas.height - height; + } + } + newElement.X = x; + newElement.Y = y; + } + logicEngine.ActiveContainer.AddElement(newElement); + logicEngine.ActiveContainer.Select(newElement); + return newElement; +} + +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); +} + +function getElementInfo(element) { + for (let a = 0; a < ElementReferenceTable.length; a++) { + if (ElementReferenceTable[a].Name == element) return ElementReferenceTable[a]; + } + return false; +} + +function isVersionNewer(version1,version2,orEqual = true) { + let v1 = version1.split("."); + let v2 = version1.split("."); + if (v1.length != 3) return false; + if (v2.length != 3) return false; + for (let a = 0; a < 3; a++) { + v1[a] = parseInt(v1[a]); + v2[a] = parseInt(v2[a]); + } + if (v1[0] > v2[0]) return true; + if (v1[0] == v2[0] && v1[1] > v2[1]) return true; + if (v1[0] == v2[0] && v1[1] == v2[1] && v1[2] > v2[2]) return true; + if ((v1[0] == v2[0] && v1[1] == v2[1]) && (orEqual && v1[2] == v2[2])) return true; + return false; +} + +function createSaveState(container = false) { + let saveState = { + Name: "LogicDesign", + Version: Version, + Timestamp: Date.now(), + Elements: new Array() + }; + if (container.Elements.length > 0) saveState.Elements = container.Elements; + return saveState; +} + +function download(filename, savestate) { + let text = JSON.stringify(savestate); + let element = document.createElement('a'); + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); + element.setAttribute('download', filename); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); +} + +function loadsave(savedata) { + if (!savedata) return false; + if (!savedata.Version) return false; // TODO: Let the person know invalid save file + if (!isVersionNewer(savedata.Version,"0.3.0")) return false; // TODO: Let the person know the version is too old + let newContainer = new elementContainer(); + let elementConnections = new Array(); + for (let a = 0; a < savedata.Elements.length; a++) { + let classRef = getElementInfo(savedata.Elements[a].Name).Class; + let newElement = new classRef(savedata.Elements[a],logicEngine,savedata.Elements[a].Args); + newContainer.AddElement(newElement); + newElement.Designator = savedata.Elements[a].Designator; + if (savedata.Elements[a].Outputs) { + if (savedata.Elements[a].Outputs.length > 0) { + for (let b=0; b < savedata.Elements[a].Outputs.length; b++) { + elementConnections.push({ + FromElement: newElement, + Input: savedata.Elements[a].Outputs[b].Input, + ToElement: savedata.Elements[a].Outputs[b].Element + }); + } + } + } + } + // Now we need to make all of the connections + for (let a = 0; a < elementConnections.length; a++) { + let toElement = newContainer.HasElement(elementConnections[a].ToElement); + if (toElement) { + let newConnection = new ElementConnection(newContainer,toElement,elementConnections[a].Input); + elementConnections[a].FromElement.OutputConnections.push(newConnection); + } + } + logicEngine.ActiveContainer = newContainer; + return true; +} diff --git a/js/logicengine.js b/js/logicengine.js index 353bbcc..88a0d9d 100644 --- a/js/logicengine.js +++ b/js/logicengine.js @@ -1,82 +1,3 @@ -/* - First a few needed global functions - */ - -function addElement(refClass,props) { - let newElement = new refClass(logicEngine,...props); - - let x = Math.round(logicEngine.Canvas.width/2); - let y = Math.round(logicEngine.Canvas.height/2); - let width = newElement.Width; - let height = newElement.Height; - - let noclearspot = true; - while (noclearspot) { - if (!logicEngine.ActiveContainer.checkOverlayBounds(x,y,width,height)) { - noclearspot = false; - } else { - x += Math.floor(Math.random() * (width - (width - (width*2))) ) + (width - (width*2)); - y += Math.floor(Math.random() * (height - (height - (height*2))) ) + (height - (height*2)); - if (x < 0) x = 0; - if (y < 0) y = 0; - if (x + width > logicEngine.Canvas.width) x = logicEngine.Canvas.width - width; - if (y + height > logicEngine.Canvas.height) y = logicEngine.Canvas.height - height; - } - } - newElement.X = x; - newElement.Y = y; - - logicEngine.ActiveContainer.AddElement(newElement); - logicEngine.ActiveContainer.Select(newElement); - return newElement; -} - -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"; @@ -89,1252 +10,8 @@ class LogicEngineSettings { 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() { - // if we are active linking stop doing so - if (this.LogicEngine.ActiveLink == this) this.LogicEngine.ActiveLink = false; - // Just to clean up connections - for (let a = 0; a < this.OutputConnections.length;a++) { - this.LogicEngine.RecursionCount = 0; - this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input,false); - } - } - - MouseDown(mousePos) { - return; - } - - MouseUp(mousePos) { - return; - } - - MouseClick(mousePos) { - - let mouseDistOutput = length2D(this.X+(this.Width-10), - this.Y+(this.Height/2), - this.MousePosition.x, - this.MousePosition.y); - if (this.LogicEngine.ActiveLink) { - // We need to see if an input is being clicked on to be linked to - let foundInput = false; - for (let a = 0; a < this.Inputs.length;a++) { - let centerY = this.Y + Math.round(this.Height / 2); - let totalHeight = this.totalInputs() * ((this.inputCircleRadius*2)+4); - let firstY = (centerY - (totalHeight/2)) + 12; - - let mouseDist = length2D(this.X+10, - firstY+ (a*24), - this.MousePosition.x, - this.MousePosition.y); - if (mouseDist <= (this.inputCircleRadius)) { - this.LogicEngine.Link(a); - foundInput = true; - break; - } - } - - } else { - if (mouseDistOutput <= (this.outputCircleRadius)) { - // Clicked on output, let us start a link - this.LogicEngine.Link(); - } - } - } - - mouseInside(mousePos) { - this.MouseOver = false; - if (((mousePos.x >= this.X ) && (mousePos.x <= (this.X + this.Width))) & ((mousePos.y >= this.Y ) && (mousePos.y <= (this.Y + this.Height)))) this.MouseOver = true; - this.MousePosition = mousePos; - return this.MouseOver; - } - - addConnection(container, element, input) { - for (let a = 0; a < this.OutputConnections.length; a++) { - if (this.OutputConnections[a].Element == element && this.OutputConnections[a].Input == input) { - // Already existing link, we will remove it instead - this.LogicEngine.RecursionCount = 0; - element.setInput(input,false); - this.OutputConnections.splice(a,1); - return; - } - } - let newConnection = new ElementConnection(container,element,input); - this.OutputConnections.push(newConnection); - this.LogicEngine.RecursionCount = 0; - element.setInput(input,this.getOutput()); - } - - drawInputs(ctx,x,y,borderColor = "#000",circleColorFalse = "#ff0000",circleColorTrue="#00ff00",circleColorHover = "#00ffff") { - ctx.save(); - //this.inputCircleRadius = 10; - let centerY = y + Math.round(this.Height / 2); - let totalHeight = this.totalInputs() * ((this.inputCircleRadius*2)+4); - let firstY = (centerY - (totalHeight/2)) + 12; - - for (let a = 0; a < this.totalInputs();a++) { - let mouseDist = length2D(x+10, firstY + (a*24),this.MousePosition.x,this.MousePosition.y); - ctx.beginPath(); - ctx.arc(x+10,firstY + (a*24),this.inputCircleRadius,0,2*Math.PI); - ctx.strokeStyle = borderColor; - ctx.fillStyle = circleColorFalse; - if (this.Inputs[a]) ctx.fillStyle = circleColorTrue; - if ((mouseDist <= (this.inputCircleRadius)) && this.LogicEngine.ActiveLink) ctx.fillStyle = circleColorHover; - ctx.fill(); - ctx.stroke(); - } - ctx.restore(); - } - - drawOutputs(ctx,x,y,borderColor = "#000",circleColorFalse = "#ff0000",circleColorTrue="#00ff00",circleColorHover="#00ffff") { - let old_strokeStyle = ctx.strokeStyle; - let old_fillStyle = ctx.fillStyle; - let mouseDist = length2D(x+(this.Width-10),y+(this.Height/2),this.MousePosition.x,this.MousePosition.y); - ctx.beginPath(); - ctx.arc(x+(this.Width-10),y+(this.Height/2),this.outputCircleRadius,0,2*Math.PI); - ctx.strokeStyle = borderColor; - ctx.fillStyle = circleColorFalse; - if (this.getOutput()) ctx.fillStyle = circleColorTrue; - if ((mouseDist <= (this.outputCircleRadius)) && !this.LogicEngine.ActiveLink) ctx.fillStyle = circleColorHover; - ctx.fill(); - ctx.stroke(); - ctx.strokeStyle = old_strokeStyle; - ctx.fillStyle = old_fillStyle; - } - - drawConnections(ctx,settings) { - ctx.save(); - for (let a = 0; a < this.OutputConnections.length;a++) { - if (!this.OutputConnections[a].Container.HasElement(this.OutputConnections[a].Element)) { - // This is a ghosted connection, lets get rid of it - this.OutputConnections.splice(a,1); - a--; - } else { - let endCenterY = this.OutputConnections[a].Element.Y + Math.round(this.OutputConnections[a].Element.Height / 2); - let endTotalHeight = this.OutputConnections[a].Element.totalInputs() * ((this.OutputConnections[a].Element.inputCircleRadius*2)+4); - let endFirstY = (endCenterY - (endTotalHeight/2)) + 12; - - let startX = this.X + this.Width; - let startY = this.Y+(this.Height/2); - let endX = this.OutputConnections[a].Element.X; - //let endY = this.OutputConnections[a].Element.Y+(this.OutputConnections[a].Element.inputCircleRadius + 2)+(((this.OutputConnections[a].Input*(4+(this.OutputConnections[a].Element.inputCircleRadius*2))))-2)+(this.OutputConnections[a].Element.inputCircleRadius/2); - let endY = endFirstY + (this.OutputConnections[a].Input*24); - let startMidX = startX + ((endX - startX)/2); - let startMidY = startY; - let midX = startMidX; - let midY = startY + ((endY - startY)/2); - let endMidX = startMidX; - let endMidY = endY; - - ctx.beginPath(); - ctx.lineWidth = settings.LinkWidth; - ctx.setLineDash(settings.LinkDash); - ctx.moveTo(startX, startY); - //ctx.lineTo(endX, endY); - ctx.quadraticCurveTo(startMidX,startMidY,midX,midY); - ctx.quadraticCurveTo(endMidX,endMidY,endX,endY); - ctx.strokeStyle = settings.ActiveConnectionColor; - if (!this.getOutput()) ctx.strokeStyle = settings.InactiveConnectionColor; - ctx.stroke(); - } - } - ctx.restore(); - } - - setInput(Input,Value) { - let oldOutput = this.getOutput(); - if (Value) { - Value = true; - } else { - Value = false; - } - if (Input < this.totalInputs()) { - this.Inputs[Input] = Value; - } else { - return; - } - if (this.getOutput() != oldOutput) { - // The output changed, we need to notify connected elements - for (let a = 0; a < this.OutputConnections.length;a++) { - //console.log(this.Designator + " sending " + this.getOutput() + " to " + this.OutputConnections[a].Element.Designator + " I" + this.OutputConnections[a].Input); - this.LogicEngine.RecursionCount++; - //console.log("Recursion: " + this.LogicEngine.RecursionCount); - if (this.LogicEngine.RecursionCount > 1000) { - if (!this.LogicEngine.RecursionError) { - console.log("RECURSION ERROR"); - this.LogicEngine.RecursionError = true; - } - return; - } - this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input,this.getOutput()); - this.LogicEngine.RecursionCount--; - } - } - } - - getOutput() { - /* - Should return true or false - */ - return false; - } - - drawElement(x,y,ctx) { - /* - Draw routine for the element - */ - this.drawBorderBox(ctx,x+10,y,drawWidth-20,drawHeight); - this.drawTextCentered(ctx,x,y,this.Width,this.Height,"LOGIC"); - this.drawInputs(ctx,x,y); - this.drawOutputs(ctx,x,y); - } - -} - -class ClockElement extends Element { - ClockTick() { - if (this.Inputs[0]) { - this.Output = ~this.Output; - if (this.Output) { - this.Task.Time = Math.round(this.Period * this.Duty); - } else { - this.Task.Time = this.Period - Math.round(this.Period * this.Duty); - } - for (let a = 0; a < this.OutputConnections.length; a++) { - this.LogicEngine.RecursionCount = 0; - this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput()); - } - } - } - - Delete() { - super.Delete(); - this.LogicEngine.Scheduler.deleteTask(this.Task); - } - - getOutput() { - return this.Output; - } - - setInput(Input, Value) { - super.setInput(Input, Value); - if (!this.Inputs[0]) { - this.Output = false; - this.Task.LastCall = 0; - this.Task.Enabled = false; - for (let a = 0; a < this.OutputConnections.length;a++) { - this.LogicEngine.RecursionCount = 0; - this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input,this.getOutput()); - } - } else { - this.Task.Enabled = true; - } - } - - constructor(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 DelayElement extends Element { - ClockTick() { - if (this.Output) { - this.Output = false; - this.Task.Enabled = false; - this.Task.LastCall = Date.now(); - this.Task.Time = this.Period; - if (this.InputPulses.length > 0) { - this.Task.Enabled = true; - } - } else { - this.Output = true; - this.Task.Enabled = false; - if (this.InputPulses.length > 0) { - this.Task.Time = this.InputPulses[0]; - this.Task.Enabled = true; - this.InputPulses.splice(0,1); - } else { - if (this.InputEnd > 0) { - this.Task.Time = this.InputEnd - this.InputStart; - this.Task.Enabled = true; - } - } - this.Task.LastCall = Date.now(); - } - - for (let a = 0; a < this.OutputConnections.length; a++) { - this.LogicEngine.RecursionCount = 0; - this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, this.getOutput()); - } - } - - Delete() { - super.Delete(); - this.LogicEngine.Scheduler.deleteTask(this.Task); - } - - getOutput() { - return this.Output; - } - - setInput(Input, Value) { - if (Input > 0) return; - if (this.Inputs[Input] == Value) return; - //super.setInput(Input, Value); - this.Inputs[Input] = Value; - if (this.Inputs[0] && !this.Task.Enabled) { - this.InputStart = Date.now(); - this.InputEnd = 0; - this.Output = false; - for (let a = 0; a < this.OutputConnections.length;a++) { - this.LogicEngine.RecursionCount = 0; - this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input,this.getOutput()); - } - this.Task.LastCall = Date.now(); - this.Task.Enabled = true; - } else { - if (!this.Inputs[Input]) { - this.InputEnd = Date.now(); - if (this.InputPulses.length > 0) { - this.InputPulses.push(this.InputEnd - this.InputStart); - this.InputStart = 0; - this.InputEnd = 0; - } else { - if (!this.Task.Enabled) { - this.Task.LastCall = Date.now(); - this.Task.Time = (this.InputEnd - this.InputStart) - (Date.now() - this.InputEnd); - this.Task.Enabled = true; - } else { - this.InputPulses.push(this.InputEnd - this.InputStart); - } - } - } else { - // We are in a condition where input went true, but the task is already enabled - this.InputStart = Date.now(); - } - } - } - - constructor(logicengine) { - super(logicengine,1); - this.removeProperty("Inputs"); - this.Name = "Delay"; - this.Period = 100; - this.Output = false; - this.Width = 100; - this.Task = new Task("DelayTask","CLOCK",0,this.Period,this.ClockTick.bind(this)); - this.Task.Enabled = false; - this.removeProperty("Inputs"); - let periodProperty = new ElementProperty("Period","int",{CBObject: this,CBFunction: "setPeriod"},100,false,false,4,999999); - this.Properties.push(periodProperty); - this.InputStart = 0; - this.InputEnd = 0; - this.InputPulses = new Array(); - this.Inputs[0] = false; - } - - 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 = "
" + this.Selected.Properties[a].Name + ""; + switch (this.Selected.Properties[a].Type) { + case "int": + contentString += ""; + break; + } + 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; - } - - checkOverlayBounds(x,y,width,height) { - for (let a = 0; a < this.Elements.length; a++) { - if ((x >= this.Elements[a].X) && (x <= (this.Elements[a].X + this.Elements[a].Width)) && (y >= this.Elements[a].Y) && (y <= (this.Elements[a].Y + this.Elements[a].Height))) return this.Elements[a]; - if (((x + width) >= this.Elements[a].X) && ((x + width) <= (this.Elements[a].X + this.Elements[a].Width)) && (y >= this.Elements[a].Y) && (y <= (this.Elements[a].Y + this.Elements[a].Height))) return this.Elements[a]; - if ((x >= this.Elements[a].X) && (x <= (this.Elements[a].X + this.Elements[a].Width)) && ((y + height) >= this.Elements[a].Y) && ((y + height) <= (this.Elements[a].Y + this.Elements[a].Height))) return this.Elements[a]; - if (((x + width) >= this.Elements[a].X) && ((x + width) <= (this.Elements[a].X + this.Elements[a].Width)) && ((y + height) >= this.Elements[a].Y) && ((y + height) <= (this.Elements[a].Y + this.Elements[a].Height))) return this.Elements[a]; - } - return false; - } - -} class LogicEngine { - Resize(evt) { let leftmenu = document.getElementById("left-menu"); leftmenu.style.height = (window.innerHeight - 52) + "px"; @@ -1355,7 +32,7 @@ class LogicEngine { Ctx.lineWidth = "1"; Ctx.stroke(); } - for (let y = gridWidth;y < (this.Canvas.width); y+= gridWidth) { + for (let y = gridWidth;y < (this.Canvas.height); y+= gridWidth) { Ctx.beginPath(); Ctx.moveTo(0,y); Ctx.lineTo(this.Canvas.width,y); diff --git a/js/main.js b/js/main.js index b7960d2..e908647 100644 --- a/js/main.js +++ b/js/main.js @@ -2,7 +2,7 @@ MatCat BrowserLogic Simulator */ -let Version = "0.2.12"; +let Version = "0.3.0"; let spanVersion = document.getElementById("version"); spanVersion.innerText = Version; // get the canvas and get the engine object going @@ -13,7 +13,6 @@ let logicEngine = new LogicEngine(lCanvasElement); // by the HTML5 spec! setInterval(logicEngine.Scheduler.Tick.bind(logicEngine.Scheduler), 4); -// Sadly this doesn't work well inside of the class so we will do it here real fast window.addEventListener('resize', function(evt) { logicEngine.Resize(evt); }, false); @@ -57,73 +56,95 @@ btn_Delete.addEventListener('click', function(evt) { let btn_AddAND = document.getElementById("btn_AddAND"); btn_AddAND.addEventListener('click', function(evt) { - addElement(LogicAND, [2]); + addElement(null,LogicAND, [2]); }, false); let btn_AddNAND = document.getElementById("btn_AddNAND"); btn_AddNAND.addEventListener('click', function(evt) { - addElement(LogicNAND, [2]); + addElement(null,LogicNAND, [2]); }, false); let btn_AddOR = document.getElementById("btn_AddOR"); btn_AddOR.addEventListener('click', function(evt) { - addElement(LogicOR, [2]); + addElement(null,LogicOR, [2]); }, false); let btn_AddNOR = document.getElementById("btn_AddNOR"); btn_AddNOR.addEventListener('click', function(evt) { - addElement(LogicNOR, [2]); + addElement(null,LogicNOR, [2]); }, false); let btn_AddXOR = document.getElementById("btn_AddXOR"); btn_AddXOR.addEventListener('click', function(evt) { - addElement(LogicXOR,[]); + addElement(null,LogicXOR,[]); }, false); let btn_AddXNOR = document.getElementById("btn_AddXNOR"); btn_AddXNOR.addEventListener('click', function(evt) { - addElement(LogicXNOR,[]); + addElement(null,LogicXNOR,[]); }, false); +let btn_AddNOT = document.getElementById("btn_AddNOT"); btn_AddNOT.addEventListener('click', function(evt) { - addElement(LogicNOT,[]); + addElement(null,LogicNOT,[]); +}, false); + +let btn_AddBUFFER = document.getElementById("btn_AddBUFFER"); +btn_AddBUFFER.addEventListener('click', function(evt) { + let logicBuffer = addElement(null,LogicBuffer,[]); }, false); let btn_AddSWITCH = document.getElementById("btn_AddSWITCH"); btn_AddSWITCH.addEventListener('click', function(evt) { - addElement(InputSwitch,[]); + addElement(null,InputSwitch,[]); }, false); let btn_AddBTN = document.getElementById("btn_AddBTN"); btn_AddBTN.addEventListener('click', function(evt) { - addElement(InputButton,[]); + addElement(null,InputButton,[]); }, false); let btn_AddCLK = document.getElementById("btn_AddCLK"); btn_AddCLK.addEventListener('click', function(evt) { - let clk = addElement(ClockElement,[]); - logicEngine.Scheduler.addTask(clk.Task); + let clk = addElement(null,ClockElement,[]); }, false); let btn_AddPulse = document.getElementById("btn_AddPULSE"); btn_AddPulse.addEventListener('click', function(evt) { - let pulse = addElement(PulseElement,[]); - logicEngine.Scheduler.addTask(pulse.Task); + let pulse = addElement(null,PulseElement,[]); }, false); let btn_AddDelay = document.getElementById("btn_AddDELAY"); btn_AddDelay.addEventListener('click', function(evt) { - let delay = addElement(DelayElement,[]); - logicEngine.Scheduler.addTask(delay.Task); + let delay = addElement(null,DelayElement,[]); +}, false); + +let btn_Save = document.getElementById("btn_Save"); +btn_Save.addEventListener('click', function(evt) { + download("mydeign.LogicParts",createSaveState(logicEngine.ActiveContainer)); +}); + +let file_Load = document.getElementById("file_Load"); +let btn_Load = document.getElementById("btn_Load"); +btn_Load.addEventListener('click', function(evt) { + file_Load.click(); +}); +file_Load.addEventListener('change', function(evt) { + let fread = new FileReader(); + fread.onload = (function (theFile) { + return function (e) { + try { + let restoredata = JSON.parse(e.target.result); + if (!loadsave(restoredata)) { + alert("Bad file!"); + } + } catch (ex) { + alert("Bad file!"); + } + } + })(evt.target.files[0]); + fread.readAsText(evt.target.files[0]); }, false); -function CheckForWelcomeCookie() { - if (getCookie("hidewelcomescreen")) { - let WelcomeScreen = document.getElementById("WelcomeWindow"); - let DarkOverlay = document.getElementById("darkout-overlay"); - WelcomeScreen.style.display = "none"; - DarkOverlay.style.display = "none"; - } -} CheckForWelcomeCookie(); diff --git a/js/scheduler.js b/js/scheduler.js new file mode 100644 index 0000000..31c8cca --- /dev/null +++ b/js/scheduler.js @@ -0,0 +1,60 @@ +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--; + } + } + } + + +}
" + this.Selected.Properties[a].Name + ""; - switch (this.Selected.Properties[a].Type) { - case "int": - contentString += ""; - break; - } - contentString += "