diff --git a/README.md b/README.md index dc22e1d..24e739f 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,10 @@ To be decided, but at this moment this code is open source and free to use for n ## Changelog +### 0.3.4 + +* Added Flip-Flops! There are 4 to choose from, JK, SR, D, and T! + ### 0.3.3 * Fixed a bug that crashed the engine when attempting to place an IC with no inputs diff --git a/js/baseclasses.js b/js/baseclasses.js index b9abd56..1122b7c 100644 --- a/js/baseclasses.js +++ b/js/baseclasses.js @@ -33,26 +33,22 @@ class CanvasTools { } drawTextCentered(ctx,x,y,x2,y2,text,fontStyle="24px Console",fontColor = "#555") { - let old_fillStyle = ctx.fillStyle; - let old_font = ctx.font; + ctx.save(); 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; + ctx.restore(); } drawText(ctx,x,y,text,fontStyle="24px Console",fontColor = "#555") { - let old_fillStyle = ctx.fillStyle; - let old_font = ctx.font; + ctx.save(); ctx.font = fontStyle; ctx.fillStyle = fontColor; ctx.fillText(text,x,y); - ctx.fillStyle = old_fillStyle; - ctx.font = old_font; + ctx.restore(); } } @@ -138,21 +134,20 @@ class elementContainer { DrawAll(ctx,settings) { let ICOuts = 0; for (let a = 0; a < this.Elements.length; a++) { + ctx.save(); if (this.Elements[a] instanceof ICOutput) ICOuts++; 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; + ctx.restore(); } + this.ICOutputs = ICOuts; if (!this.Selected) { let PropertiesBox = document.getElementById("PropertiesBox"); diff --git a/js/elements.js b/js/elements.js index 74b6fd6..c9d25ab 100644 --- a/js/elements.js +++ b/js/elements.js @@ -2,10 +2,12 @@ let ElementReferenceTable = new Array(); let ElementCategory_Inputs = new ElementCatalog_Category("Inputs",""); let ElementCategory_LOGIC = new ElementCatalog_Category("Logic" ,""); +let ElementCategory_FlipFlop = new ElementCatalog_Category("Flip-Flops" ,""); let ElementCategory_Timing = new ElementCatalog_Category("Timing" ,""); let ElementCategory_ICs = new ElementCatalog_Category("ICs" ,""); let elementCatalog = new ElementCatalog([ElementCategory_Inputs, ElementCategory_LOGIC, + ElementCategory_FlipFlop, ElementCategory_Timing, ElementCategory_ICs]); class ElementProperty { @@ -75,6 +77,7 @@ class Element extends CanvasTools { this.Name = "Element"; this.Designator = ""; this.Inputs = new Array(Inputs); + this.InputLabels = new Array(1); this.Width = 100; this.Height = 60; this.inputCircleRadius = 10; @@ -87,6 +90,7 @@ class Element extends CanvasTools { this.Properties = new Array(); this.LogicEngine = logicengine; this.Outputs = new Array(1); + this.OutputLabels = new Array(1); this.NoOutput = false; this.OutputLink = 0; @@ -267,28 +271,36 @@ class Element extends CanvasTools { if ((mouseDist <= (this.inputCircleRadius)) && this.LogicEngine.ActiveLink) ctx.fillStyle = circleColorHover; ctx.fill(); ctx.stroke(); + if (this.InputLabels[a]) this.drawText(ctx,x+(this.inputCircleRadius*2)+ 5,(firstY + (a*24)) + 5,this.InputLabels[a],"10px Console","#000"); } 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; + drawOutputs(ctx,x,y,borderColor = "#000",circleColorFalse = "#ff0000",circleColorTrue="#00ff00",circleColorHover = "#00ffff") { + ctx.save(); + let centerY = y + Math.round(this.Height / 2); + let totalHeight = this.Outputs.length * ((this.outputCircleRadius*2)+4); + let firstY = (centerY - (totalHeight/2)) + 12; + + for (let a = 0; a < this.Outputs.length;a++) { + let mouseDist = length2D(x+(this.Width - 10), firstY + (a*24),this.MousePosition.x,this.MousePosition.y); + ctx.beginPath(); + ctx.arc(x+(this.Width-10),firstY + (a*24),this.outputCircleRadius,0,2*Math.PI); + ctx.strokeStyle = borderColor; + ctx.fillStyle = circleColorFalse; + if (this.getOutput(a)) ctx.fillStyle = circleColorTrue; + if ((mouseDist <= (this.outputCircleRadius)) && !this.LogicEngine.ActiveLink) ctx.fillStyle = circleColorHover; + ctx.fill(); + ctx.stroke(); + let textSize = false; + + if (this.OutputLabels[a]) textSize = this.textSize(ctx,this.OutputLabels[a],"10px Console"); + if (this.OutputLabels[a]) this.drawText(ctx,(x+(this.Width)) - (textSize.width + 5 + (this.outputCircleRadius*2)),(firstY + (a*24)) + 5,this.OutputLabels[a],"10px Console","#000"); + } + ctx.restore(); } drawConnections(ctx,settings) { - ctx.save(); let centerY = this.Y + Math.round(this.Height / 2); let totalHeight = this.Outputs.length * ((this.outputCircleRadius*2)+4); let firstY = (centerY - (totalHeight/2)) + 12; @@ -315,6 +327,7 @@ class Element extends CanvasTools { let endMidX = startMidX; let endMidY = endY; + ctx.save(); ctx.beginPath(); ctx.lineWidth = settings.LinkWidth; ctx.setLineDash(settings.LinkDash); @@ -325,11 +338,28 @@ class Element extends CanvasTools { ctx.strokeStyle = settings.ActiveConnectionColor; if (!this.getOutput(this.OutputConnections[a].Output)) ctx.strokeStyle = settings.InactiveConnectionColor; ctx.stroke(); + ctx.restore(); + } } - ctx.restore(); } + setConnections() { + 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.OutputConnections[a].Output)); + this.LogicEngine.RecursionCount--; + } + } setInput(Input,Value) { let oldOutput = this.getOutput(); if (Value) { @@ -342,23 +372,7 @@ class Element extends CanvasTools { } 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--; - } - } + this.setConnections(); } getOutput(Output=0) { @@ -1084,7 +1098,7 @@ class InputSwitch extends inputElement { 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; + 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()); @@ -1154,6 +1168,226 @@ let ElementCatalog_BUTTON = new ElementCatalog_Element("Button","The button only ElementReferenceTable.push(ElementCatalog_BUTTON); ElementCategory_Inputs.addElement(ElementCatalog_BUTTON); +class FlipFlopJK extends Element { + constructor(RestoreData = null, logicengine) { + super(RestoreData,logicengine,3); + this.Name = "JK-FF"; + this.Outputs = new Array(2); + this.InputLabels = new Array("J","CLK","K"); + this.OutputLabels = new Array("Q","~Q"); + this.removeProperty("Inputs"); + this.Height = 80; + + if (RestoreData) { + this.Outputs = RestoreData.Outputs; + } + } + + toJSON(key) { + let $superjson = super.toJSON(key); + + $superjson.Outputs = this.Outputs; + return $superjson; + } + + setInput(Input, Value) { + if (Input >= this.Inputs.length) return false; + let oldOutput = this.Outputs[0]; + this.Inputs[Input] = Value; + if (this.Inputs[1]) { + if (!this.Inputs[0] && this.Inputs[2]) { + // set Q low + this.Outputs[0] = false; + this.Outputs[1] = true; + } else if (this.Inputs[0] && !this.Inputs[2]) { + // set Q low + this.Outputs[0] = true; + this.Outputs[1] = false; + } else if (this.Inputs[0] && this.Inputs[2]) { + // set Q low + this.Outputs[0] = !this.Outputs[0]; + this.Outputs[1] = !this.Outputs[0]; + } + } + if (oldOutput != this.getOutput(0)) { + this.setConnections(); + } + } + + getOutput(Output=0) { + return this.Outputs[Output]; + } + drawElement(x,y,ctx) { + let xOffset = 20; + this.drawBorderBox(ctx, x+20,y,this.Width-40,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor); + this.drawTextCentered(ctx,x,y+(this.Height-14),this.Width,14,this.Designator,"10px Console","#000"); + this.drawInputs(ctx,x,y); + this.drawOutputs(ctx,x,y); + } +} +let ElementCatalog_JKFlipFlop = new ElementCatalog_Element("JK-FF","The JK Flip-Flop is a common type of flip-flop that allows for either setting in a specific state, or toggling state.","JK",FlipFlopJK,[]); +ElementReferenceTable.push(ElementCatalog_JKFlipFlop); +ElementCategory_FlipFlop.addElement(ElementCatalog_JKFlipFlop); + +class FlipFlopSR extends Element { + constructor(RestoreData = null, logicengine) { + super(RestoreData,logicengine,3); + this.Name = "SR-FF"; + this.Outputs = new Array(2); + this.InputLabels = new Array("S","CLK","R"); + this.OutputLabels = new Array("Q","~Q"); + this.removeProperty("Inputs"); + this.Height = 80; + + if (RestoreData) { + this.Outputs = RestoreData.Outputs; + } + } + + toJSON(key) { + let $superjson = super.toJSON(key); + + $superjson.Outputs = this.Outputs; + return $superjson; + } + + setInput(Input, Value) { + if (Input >= this.Inputs.length) return false; + let oldOutput = this.Outputs[0]; + this.Inputs[Input] = Value; + if (this.Inputs[1]) { + if (!this.Inputs[0] && this.Inputs[2]) { + // set Q low + this.Outputs[0] = false; + this.Outputs[1] = true; + } else if (this.Inputs[0] && !this.Inputs[2]) { + // set Q low + this.Outputs[0] = true; + this.Outputs[1] = false; + } + } + if (oldOutput != this.getOutput(0)) { + this.setConnections(); + } + } + + getOutput(Output=0) { + return this.Outputs[Output]; + } + drawElement(x,y,ctx) { + let xOffset = 20; + this.drawBorderBox(ctx, x+20,y,this.Width-40,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor); + this.drawTextCentered(ctx,x,y+(this.Height-14),this.Width,14,this.Designator,"10px Console","#000"); + this.drawInputs(ctx,x,y); + this.drawOutputs(ctx,x,y); + } +} +let ElementCatalog_SRFlipFlop = new ElementCatalog_Element("SR-FF","The SR Flip-Flop is a common type of flip-flop that allows for either setting, or resetting the output state.","SR",FlipFlopSR,[]); +ElementReferenceTable.push(ElementCatalog_SRFlipFlop); +ElementCategory_FlipFlop.addElement(ElementCatalog_SRFlipFlop); + +class FlipFlopT extends Element { + constructor(RestoreData = null, logicengine) { + super(RestoreData,logicengine,2); + this.Name = "T-FF"; + this.Outputs = new Array(2); + this.InputLabels = new Array("T","CLK"); + this.OutputLabels = new Array("Q","~Q"); + this.removeProperty("Inputs"); + this.Height = 80; + + if (RestoreData) { + this.Outputs = RestoreData.Outputs; + } + } + + toJSON(key) { + let $superjson = super.toJSON(key); + + $superjson.Outputs = this.Outputs; + return $superjson; + } + + setInput(Input, Value) { + if (Input >= this.Inputs.length) return false; + let oldOutput = this.Outputs[0]; + this.Inputs[Input] = Value; + if (this.Inputs[0] && this.Inputs[1]) { + this.Outputs[0] = !this.Outputs[0]; + this.Outputs[1] = !this.Outputs[0]; + } + if (oldOutput != this.getOutput(0)) { + this.setConnections(); + } + } + + getOutput(Output=0) { + return this.Outputs[Output]; + } + drawElement(x,y,ctx) { + let xOffset = 20; + this.drawBorderBox(ctx, x+20,y,this.Width-40,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor); + this.drawTextCentered(ctx,x,y+(this.Height-14),this.Width,14,this.Designator,"10px Console","#000"); + this.drawInputs(ctx,x,y); + this.drawOutputs(ctx,x,y); + } +} +let ElementCatalog_TFlipFlop = new ElementCatalog_Element("T-FF","The T Flip-Flop is a common type of flip-flop that toggles the output when T is high and CLK goes high.","T",FlipFlopT,[]); +ElementReferenceTable.push(ElementCatalog_TFlipFlop); +ElementCategory_FlipFlop.addElement(ElementCatalog_TFlipFlop); + +class FlipFlopD extends Element { + constructor(RestoreData = null, logicengine) { + super(RestoreData,logicengine,2); + this.Name = "D-FF"; + this.Outputs = new Array(2); + this.InputLabels = new Array("D","CLK"); + this.OutputLabels = new Array("Q","~Q"); + this.removeProperty("Inputs"); + this.Height = 80; + + if (RestoreData) { + this.Outputs = RestoreData.Outputs; + } + } + + toJSON(key) { + let $superjson = super.toJSON(key); + + $superjson.Outputs = this.Outputs; + return $superjson; + } + + setInput(Input, Value) { + if (Input >= this.Inputs.length) return false; + let oldOutput = this.Outputs[0]; + let oldInput = this.Inputs[1]; + this.Inputs[Input] = Value; + if (this.Inputs[1] && !oldInput) { + this.Outputs[0] = this.Inputs[0]; + this.Outputs[1] = !this.Outputs[0]; + } + if (oldOutput != this.getOutput(0)) { + this.setConnections(); + } + } + + getOutput(Output=0) { + return this.Outputs[Output]; + } + drawElement(x,y,ctx) { + let xOffset = 20; + this.drawBorderBox(ctx, x+20,y,this.Width-40,this.Height,1,"#000","#f7e979",this.LogicEngine.Settings.ShadowColor); + this.drawTextCentered(ctx,x,y+(this.Height-14),this.Width,14,this.Designator,"10px Console","#000"); + this.drawInputs(ctx,x,y); + this.drawOutputs(ctx,x,y); + } +} +let ElementCatalog_DFlipFlop = new ElementCatalog_Element("D-FF","The D Flip-Flop is a common type of flip-flop that sets the output to equal D if the clock goes high","D",FlipFlopD,[]); +ElementReferenceTable.push(ElementCatalog_DFlipFlop); +ElementCategory_FlipFlop.addElement(ElementCatalog_DFlipFlop); + + class LogicAND extends Element { constructor(RestoreData = null, logicengine,Inputs) { super(RestoreData,logicengine,Inputs); diff --git a/js/main.js b/js/main.js index ceb2297..1b2550a 100644 --- a/js/main.js +++ b/js/main.js @@ -2,7 +2,7 @@ MatCat BrowserLogic Simulator */ -let Version = "0.3.3"; +let Version = "0.3.4"; let spanVersion = document.getElementById("version"); spanVersion.innerText = Version; // get the canvas and get the engine object going