526 lines
19 KiB
JavaScript
526 lines
19 KiB
JavaScript
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.Disconnecting = false;
|
|
}
|
|
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();
|
|
let drawOverline = false;
|
|
let label = this.InputLabels[a];
|
|
|
|
if (this.InputLabels[a] && drawFresh) {
|
|
if (this.InputLabels[a].charAt(0) == "!" || this.InputLabels[a].charAt(0) == "~") {
|
|
// Draw a NOT line
|
|
drawOverline = true;
|
|
label = this.InputLabels[a].substring(1);
|
|
}
|
|
this.drawText(ctx,x+(this.inputCircleRadius*2)+ 5,(firstY + (a*24)) + 5,label,"10px Console","#000",drawOverline);
|
|
}
|
|
}
|
|
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;
|
|
let drawOverline = false;
|
|
let label = this.OutputLabels[a];
|
|
if (this?.OutputLabels[a]?.charAt(0) == "!" || this?.OutputLabels[a]?.charAt(0) == "~") {
|
|
// Draw a NOT line
|
|
drawOverline = true;
|
|
label = this.OutputLabels[a].substring(1);
|
|
}
|
|
|
|
if (this.OutputLabels[a]) textSize = this.textSize(ctx,label,"10px Console");
|
|
if (this.OutputLabels[a] && drawFresh) {
|
|
this.drawText(ctx,(x+(this.Width)) - (textSize.width + 5 + (this.outputCircleRadius*2)),(firstY + (a*24)) + 5,label,"10px Console","#000",drawOverline);
|
|
}
|
|
}
|
|
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++) {
|
|
if (this.isVisible() || this.OutputConnections[a].Element.isVisible()) {
|
|
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 - this.outputCircleRadius);
|
|
let startY = firstY + (this.OutputConnections[a].Output * 24);
|
|
let endX = this.OutputConnections[a].Element.X + this.OutputConnections[a].Element.inputCircleRadius;
|
|
//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);
|
|
if (this instanceof LogicWireNode) {
|
|
ctx.lineTo(endX, endY);
|
|
} else {
|
|
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);
|
|
}
|
|
}
|