let ElementReferenceTable = new Array(); let ElementCategory_Inputs = new ElementCatalog_Category("Inputs",""); let ElementCategory_Outputs = new ElementCatalog_Category("Outputs",""); 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 ElementCategory_Other = new ElementCatalog_Category("Other" ,""); let elementCatalog = new ElementCatalog([ElementCategory_Inputs, ElementCategory_Outputs, ElementCategory_LOGIC, ElementCategory_FlipFlop, ElementCategory_Timing, ElementCategory_ICs, ElementCategory_Other]); class ElementProperty { constructor(name,type,callback,defaultValue,currentValue = false,values=false,min=0,max=64) { /* 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,output = 0) { this.Container = elementContainer; this.Element = element; this.Input = input; this.Output = output; } toJSON(key) { return { Element: this.Element.Designator, Input: parseInt(this.Input), Output: this.Output }; } } class Element extends CanvasTools { constructor(_Container,RestoreData = null,logicengine,Inputs) { super(); this.Name = "Element"; this.Designator = ""; this.Inputs = new Array(Inputs); this.InputLabels = new Array(1); 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; this.Outputs = new Array(1); this.OutputLabels = new Array(1); this.NoOutput = false; this.OutputLink = 0; this._Container = _Container; this.Disconnecting = false; this.redraw = true; this.StaticCanvas = document.createElement("canvas"); this.StaticCtx = this.StaticCanvas.getContext('2d'); 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 (RestoreData.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; } this.drawElement(0,0,this.StaticCtx); } clearStatic() { this.StaticCtx.clearRect(0, 0, this.StaticCanvas.width,this.StaticCanvas.height); } ConnectsTo(element,input = false) { for (let a = 0; a < this.OutputConnections.length; a++) { if (this.OutputConnections[a].Element == element || this.OutputConnections[a].Element.Designator == element) { if (input !== false) { if (this.OutputConnections[a].Input == input) return this.OutputConnections[a].Output; } else { return 0; } } } return false; } isVisible() { let isvisible = false; if (this.LogicEngine) { let LeftX = this.LogicEngine.Panning.OffsetX; if (LeftX < 0) { LeftX = Math.abs(LeftX); } else { LeftX = -Math.abs(LeftX); } let RightX = LeftX + this.LogicEngine.Canvas.width; let TopY = this.LogicEngine.Panning.OffsetY; if (TopY < 0) { TopY = Math.abs(TopY); } else { TopY = -Math.abs(TopY); } let BottomY = TopY + this.LogicEngine.Canvas.height; if (((this.X + this.Width) >= LeftX) && ((this.X) <= RightX) && ((this.Y + this.Height) >= TopY) && ((this.Y) <= BottomY)) isvisible = true; } return isvisible; } 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; this.drawElement(0,0,this.StaticCtx); } Delete() { // if we are active linking stop doing so if (this.LogicEngine.ActiveLink == this) this.LogicEngine.ActiveLink = false; // Just to clean up connections this.Disconnect(); } Disconnect(element = false) { if (element) { for (let a = 0; a < this.OutputConnections.length; a++) { if (this.OutputConnections[a].Element == element) { this.LogicEngine.RecursionCount = 0; let element = this.OutputConnections[a].Element; let input = this.OutputConnections[a].Input; this.OutputConnections.splice(a,1); element.setInput(input, false); a--; } } } else { this.Disconnecting = true; for (let a = 0; a < this.OutputConnections.length; a++) { this.LogicEngine.RecursionCount = 0; this.OutputConnections[a].Element.setInput(this.OutputConnections[a].Input, false); this.OutputConnections.splice(a,1); a--; } } this.drawElement(0,0,this.StaticCtx); } MouseDown(mousePos) { return; } MouseUp(mousePos) { return; } LinkOutLocation() { return this.OutputLink; } MouseClick(mousePos) { let ctxMousePos = this.MousePosition; let mouseDistOutput = length2D(this.X+(this.Width-10), this.Y+(this.Height/2), ctxMousePos.x, ctxMousePos.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), ctxMousePos.x, ctxMousePos.y); if (mouseDist <= (this.inputCircleRadius)) { this.LogicEngine.Link(a); foundInput = true; break; } } } else { let foundOutput = false; for (let a = 0; a < this.Outputs.length;a++) { let centerY = this.Y + Math.round(this.Height / 2); let totalHeight = this.Outputs.length * ((this.outputCircleRadius*2)+4); let firstY = (centerY - (totalHeight/2)) + 12; let mouseDist = length2D(this.X+(this.Width-10), firstY+ (a*24), ctxMousePos.x, ctxMousePos.y); if (mouseDist <= (this.outputCircleRadius)) { this.OutputLink = {Output: a,x: this.X+(this.Width-10), y: firstY+ (a*24)}; this.LogicEngine.Link(); foundOutput = true; break; } } } } mouseInside(mousePos) { mousePos = this.LogicEngine.getCanvasMousePos(mousePos); let oldMouseOver = this.MouseOver; 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; if (this.MouseOver) this.drawInputs(this.StaticCtx, 0,0); if (this.MouseOver) this.drawOutputs(this.StaticCtx, 0,0); if (oldMouseOver && !this.MouseOver) this.drawInputs(this.StaticCtx, 0,0); // Make sure we clear hover if (oldMouseOver && !this.MouseOver) this.drawOutputs(this.StaticCtx, 0,0); return this.MouseOver; } addConnection(container, element, input,output=0) { 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; this.OutputConnections.splice(a,1); element.setInput(input,false); this.drawElement(0,0,this.StaticCtx); return; } } let newConnection = new ElementConnection(container,element,input,output); this.OutputConnections.push(newConnection); this.LogicEngine.RecursionCount = 0; element.setInput(input,this.getOutput(output)); this.drawElement(0,0,this.StaticCtx); return newConnection; } drawInputs(ctx,x,y,borderColor = "#000",circleColorFalse = "#ff0000",circleColorTrue="#00ff00",circleColorHover = "#00ffff",drawFresh = false) { if (this.LogicEngine.ActiveContainer !== this._Container) return; // No point if we aren't visible 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; let centerYReal = this.Y + Math.round(this.Height / 2); let firstYReal = (centerYReal - (totalHeight/2)) + 12; for (let a = 0; a < this.totalInputs();a++) { let mouseDist = length2D(this.X+10, firstYReal + (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(); if (this.InputLabels[a] && drawFresh) 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",drawFresh = false) { if (this.LogicEngine.ActiveContainer !== this._Container) return; // No point if we aren't visible 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; let centerYReal = this.Y + Math.round(this.Height / 2); let firstYReal = (centerYReal - (totalHeight/2)) + 12; for (let a = 0; a < this.Outputs.length;a++) { let mouseDist = length2D(this.X+(this.Width - 10), firstYReal + (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] && drawFresh) 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) { if (this.LogicEngine.ActiveContainer !== this._Container) return; // No point if we aren't visible let centerY = this.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.OutputConnections.length;a++) { let mouseDist = length2D(this.X+(this.Width - 10), firstY + (this.OutputConnections[a].Output*24),this.MousePosition.x,this.MousePosition.y); 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 = firstY+ (this.OutputConnections[a].Output*24); 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.save(); ctx.beginPath(); ctx.lineWidth = settings.LinkWidth; if ((mouseDist <= (this.outputCircleRadius)) && !this.LogicEngine.ActiveLink) ctx.lineWidth = (settings.LinkWidth * 2); 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 ((mouseDist <= (this.outputCircleRadius)) && !this.LogicEngine.ActiveLink) ctx.strokeStyle = settings.ActiveConnectionHoverColor; if (!this.getOutput(this.OutputConnections[a].Output)) { ctx.strokeStyle = settings.InactiveConnectionColor; if ((mouseDist <= (this.outputCircleRadius)) && !this.LogicEngine.ActiveLink) ctx.strokeStyle = settings.InactiveConnectionHoverColor; } ctx.stroke(); ctx.restore(); } } } setConnections() { for (let a = 0; a < this.OutputConnections.length;a++) { 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) { if (Value) { Value = true; } else { Value = false; } let oldInput = this.Inputs[Input]; if (Input < this.totalInputs()) { this.Inputs[Input] = Value; } else { return; } let isHigh = this._Container.isHigh(this,Input); if (isHigh !== false) this.Inputs[Input] = true; if (isHigh === false) this.Inputs[Input] = false; if (oldInput != this.Inputs[Input]) { this.setConnections(); this.drawElement(0,0,this.StaticCtx); } } getOutput(Output=0) { /* Should return true or false */ if (this.Disconnecting) return 0; return false; } drawElement(x,y,ctx) { /* Draw routine for the element */ if (this.LogicEngine.ActiveContainer !== this._Container) return; // No point if we aren't visible this.StaticCanvas.width = this.Width; this.StaticCanvas.height = this.Height; this.drawBorderBox(ctx,x+10,y,this.Width-20,this.Height); this.drawTextCentered(ctx,x,y,this.Width,this.Height,"LOGIC"); this.drawInputs(ctx,x,y,undefined,undefined,undefined,undefined,true); this.drawOutputs(ctx,x,y,undefined,undefined,undefined,undefined,true); } }