BrowserLogic/js/elements/BaseElementClasses.js

520 lines
18 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++) {
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);
}
}