426 lines
14 KiB
JavaScript
426 lines
14 KiB
JavaScript
|
var canvas, ctx
|
||
|
var selectedItem
|
||
|
var mouseisdown
|
||
|
var chip = new Component("main");
|
||
|
var pinImages = [];
|
||
|
var wireVisuals = [];
|
||
|
var wireBeingDrawn = [];
|
||
|
images = {};
|
||
|
var chipoffset=0;
|
||
|
var numUpdates = 0;
|
||
|
var x;
|
||
|
document.addEventListener("DOMContentLoaded", function(event){
|
||
|
canvas = document.getElementById('logicsim');
|
||
|
canvas.addEventListener("mousedown",selectItem);
|
||
|
canvas.addEventListener("mouseup", function(){mouseisdown=false});
|
||
|
canvas.addEventListener("mousemove",mousemove);
|
||
|
document.addEventListener("keydown",checkKey);
|
||
|
document.addEventListener("keyup",function(e){shifted=e.shiftKey});
|
||
|
ctx = canvas.getContext('2d');
|
||
|
preloadAssets();
|
||
|
requestAnimationFrame(drawCanvas);
|
||
|
//x=setInterval(function(){var st = performance.now(); chip.process(console.log(performance.now()-st))},1.1);
|
||
|
y=0
|
||
|
x=setInterval(function(){chip.process();numUpdates+=1},0.3);
|
||
|
storedChips = localStorage.getItem('chips');
|
||
|
if(storedChips) chips = JSON.parse(storedChips);
|
||
|
});
|
||
|
function getMousePos(c,e){
|
||
|
var rect = c.getBoundingClientRect();
|
||
|
return {
|
||
|
x: e.clientX - rect.left,
|
||
|
y: e.clientY - rect.top
|
||
|
}
|
||
|
}
|
||
|
function checkKey(e){
|
||
|
e=e||window.event;
|
||
|
if(e.keyCode==27){
|
||
|
selectedPin={component:-1,pin:-1};
|
||
|
wireBeingDrawn=[];
|
||
|
} else if(e.key=="n"){
|
||
|
var name = prompt('name new chip or cancel');
|
||
|
if (name.length){
|
||
|
chips[name]=JSON.parse(chip.getJSONandName(name));
|
||
|
localStorage.setItem('chips',JSON.stringify(chips));
|
||
|
chip = new Component();
|
||
|
close();
|
||
|
}
|
||
|
} else if(e.keyCode==8 || e.keyCode==46){if(selectedItem>=0){chip.removeComponent(selectedItem);
|
||
|
wireVisuals.forEach(wv=>{
|
||
|
wv.path.forEach(p=>{if(p.component>selectedItem){p.component-=1}});
|
||
|
});}} else if (e.keyCode==37) {
|
||
|
chipoffset+=10;
|
||
|
} else if (e.keyCode==39) { chipoffset-=10};
|
||
|
shifted=e.shiftKey;
|
||
|
}
|
||
|
function isInside(c,point){
|
||
|
return ( point.x > c.position.x && point.x < c.position.x + images[c.type].width) &&
|
||
|
( point.y > c.position.y && point.y < c.position.y + images[c.type].height);
|
||
|
}
|
||
|
function isInsidePin(p,point){
|
||
|
return Math.sqrt(Math.pow((p.position.x-point.x),2)+Math.pow((p.position.y-point.y),2)) < p.radius;
|
||
|
}
|
||
|
var clickpos;
|
||
|
var shifted = 0;
|
||
|
var nWireAttempts=0;
|
||
|
var selectedPin = {component:-1,pin:-1};
|
||
|
var hoveredPin = {component:-1,pin:-1,type:-1};
|
||
|
function selectItem(e){
|
||
|
pos = getMousePos(canvas, e)
|
||
|
|
||
|
if(pos.y>canvas.height-60){
|
||
|
chipMenu.forEach(cm=>{
|
||
|
if((pos.x>cm.topLeft.x && pos.x < (cm.topLeft.x+cm.width))&&(pos.y>cm.topLeft.y&&pos.y<(cm.topLeft.y+40))){
|
||
|
console.log(cm.type);
|
||
|
chip.addComponent(cm.type);
|
||
|
//.position = {x:pos.x,y:pos.y}
|
||
|
|
||
|
} else {
|
||
|
}
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(hoveredPin.component>=0){
|
||
|
if(hoveredPin.type==1){
|
||
|
if(hoveredPin.pin<chip.inputs)
|
||
|
chip.pins[hoveredPin.pin]=1-chip.pins[hoveredPin.pin];
|
||
|
} else {
|
||
|
if(wireBeingDrawn.length>0){
|
||
|
if(chip.connect(selectedPin, hoveredPin, nWireAttempts)){
|
||
|
wireBeingDrawn[wireBeingDrawn.length-1]={component:hoveredPin.component,pin:hoveredPin.pin,shift:shifted,offset:wireBeingDrawn[1].offset};
|
||
|
wireVisuals.push({wireId:nWireAttempts,path:wireBeingDrawn.slice()});
|
||
|
wireBeingDrawn=[];
|
||
|
selectedPin={component:-1,pin:-1};
|
||
|
nWireAttempts+=1;
|
||
|
} else {
|
||
|
console.log("noooo");
|
||
|
}
|
||
|
} else {
|
||
|
inp = chip.subComponents[hoveredPin.component].inputs;
|
||
|
defaultOffset = 10;
|
||
|
if(hoveredPin.component == 0 && hoveredPin.pin < inp){
|
||
|
o = 7*hoveredPin.pin;
|
||
|
rev = 1
|
||
|
} else if(hoveredPin.component == 0) {
|
||
|
o = 7*(hoveredPin.pin-inp);
|
||
|
rev = -1
|
||
|
} else if (hoveredPin.component>0 && hoveredPin.pin < inp) {
|
||
|
o = 7*hoveredPin.pin;
|
||
|
rev = -1
|
||
|
} else {
|
||
|
o = 7*(hoveredPin.pin-inp)
|
||
|
rev = 1
|
||
|
}
|
||
|
wireBeingDrawn.push({component:hoveredPin.component,pin:hoveredPin.pin,shift:shifted,rev:rev});
|
||
|
wireBeingDrawn.push({x:pos.x,y:pos.y,shift:shifted,offset:o});
|
||
|
selectedPin = {component:hoveredPin.component,pin:hoveredPin.pin};
|
||
|
}
|
||
|
}
|
||
|
} else if(pos.x<20){
|
||
|
if(typeof editDisabled == 'undefined')
|
||
|
chip.addPin(1);
|
||
|
|
||
|
//wireVisuals.forEach(wv=>{if(wv.path[0].component==0){wv.path[wv.path.length-1].pin+=1;}});
|
||
|
return true;
|
||
|
} else if(pos.x>canvas.width-20){
|
||
|
if(typeof editDisabled == 'undefined')
|
||
|
chip.addPin();
|
||
|
return true;
|
||
|
} else {
|
||
|
if(wireBeingDrawn.length>0){
|
||
|
wireBeingDrawn[wireBeingDrawn.length-1]={x:pos.x,y:pos.y,shift:shifted,offset:wireBeingDrawn[1].offset};
|
||
|
wireBeingDrawn.push({x:pos.x,y:pos.y,shift:shifted,offset:wireBeingDrawn[1].offset})//slice());
|
||
|
}
|
||
|
}
|
||
|
selectedItem=-1;
|
||
|
chip.zIndex.forEach(function(c,i){
|
||
|
if (isInside(c, pos) && ctx.getImageData(pos.x,pos.y,1,1).data[3]) {
|
||
|
mouseisdown = true;
|
||
|
clickpos = pos;
|
||
|
chip.sendToFront(chip.getProperIndex(c));
|
||
|
selectedItem = chip.getProperIndex(c);
|
||
|
console.log(selectedItem);
|
||
|
// DELETE THIS LATER //
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
});
|
||
|
}
|
||
|
var pos;
|
||
|
function mousemove(e){
|
||
|
pos = getMousePos(canvas,e);
|
||
|
if(wireBeingDrawn.length>0){
|
||
|
o = wireBeingDrawn[wireBeingDrawn.length-1].offset;
|
||
|
wireBeingDrawn[wireBeingDrawn.length-1]={x:pos.x,y:pos.y,shift:shifted,offset:o};
|
||
|
}
|
||
|
if(mouseisdown){
|
||
|
last = chip.zIndex.length-1;
|
||
|
chip.zIndex[last].position.x -= (clickpos.x - pos.x);
|
||
|
chip.zIndex[last].position.y -= (clickpos.y - pos.y);
|
||
|
clickpos = pos;
|
||
|
}
|
||
|
if(!pinImages.some(pi=>{
|
||
|
if(isInsidePin(pi,pos)){
|
||
|
hoveredPin = {component:pi.component,pin:pi.pin,type:pi.type}
|
||
|
return true;
|
||
|
}
|
||
|
})){
|
||
|
hoveredPin={component:-1,pin:-1};
|
||
|
}
|
||
|
}
|
||
|
function preloadAssets(){
|
||
|
Object.entries(chips).forEach(function(c){
|
||
|
if (!(c[1].image in images)){
|
||
|
images[c[0]] = new Image();
|
||
|
images[c[0]].src = c[1].image;
|
||
|
}
|
||
|
});
|
||
|
if(typeof loadChip == 'function'){
|
||
|
loadChip();
|
||
|
}
|
||
|
}
|
||
|
function makeArr(startValue, stopValue, cardinality) {
|
||
|
var arr = [];
|
||
|
var step = (stopValue - startValue) / (cardinality - 1);
|
||
|
for (var i = 0; i < cardinality; i++) {
|
||
|
arr.push(startValue + (step * i));
|
||
|
}
|
||
|
return arr;
|
||
|
}
|
||
|
function drawCircle(x,y,radius,colour){
|
||
|
ctx.beginPath();
|
||
|
ctx.arc(Math.floor(x),Math.floor(y),radius,0,2*Math.PI,false);
|
||
|
ctx.fillStyle=colour;
|
||
|
ctx.fill();
|
||
|
}
|
||
|
function drawLine(origin,destination,width,colour,taxi,offset,rev){
|
||
|
if(!taxi||destination.shift){
|
||
|
ctx.beginPath();
|
||
|
ctx.moveTo(origin.x,origin.y);
|
||
|
ctx.lineTo(destination.x,destination.y);
|
||
|
ctx.strokeStyle=colour;
|
||
|
ctx.lineWidth=width;
|
||
|
ctx.stroke();
|
||
|
} else {
|
||
|
ctx.beginPath();
|
||
|
ctx.moveTo(origin.x,origin.y);
|
||
|
if(destination.y>origin.y){
|
||
|
offset = offset*-rev;
|
||
|
} else {
|
||
|
offset = offset * rev;
|
||
|
}
|
||
|
multip = 0.4;
|
||
|
if(rev<0) {
|
||
|
multip = 1-multip;
|
||
|
}
|
||
|
ctx.lineTo(origin.x+(destination.x-origin.x)*multip+offset,origin.y);
|
||
|
ctx.lineTo(origin.x+(destination.x-origin.x)*multip+offset,destination.y);
|
||
|
ctx.lineTo(destination.x,destination.y);
|
||
|
ctx.strokeStyle=colour;
|
||
|
ctx.lineWidth=width;
|
||
|
ctx.stroke();
|
||
|
}
|
||
|
}
|
||
|
chipMenu = [];
|
||
|
tt = null;
|
||
|
class Tooltip {
|
||
|
constructor(x,y,message,persist=false){
|
||
|
this.x=x;
|
||
|
this.y=y;
|
||
|
this.message=message;
|
||
|
this.persist=persist;
|
||
|
}
|
||
|
draw(offset){
|
||
|
//ctx.fillStyle='pink';
|
||
|
//ctx.fillRect(0,0,canvas.width,canvas.height);
|
||
|
//return;
|
||
|
ctx.fillStyle="rgba(0,0,0,0.65)";
|
||
|
ctx.font="20px vcr_osd_monoregular";
|
||
|
ctx.textBaseline="bottom";
|
||
|
ctx.textAlign="left";
|
||
|
var width=ctx.measureText(this.message).width+6;
|
||
|
offset=0
|
||
|
ctx.fillRect(this.x<canvas.width/2?this.x:this.x-width,this.y-25-(26*offset),width,25);
|
||
|
ctx.fillStyle="white";
|
||
|
ctx.fillText(this.message,this.x<canvas.width/2?(this.x+3):(this.x-width+3),this.y-(26*offset));
|
||
|
}
|
||
|
}
|
||
|
function drawTooltip(message){
|
||
|
if(!message) return
|
||
|
if(!tt){
|
||
|
tt = new Tooltip(pos.x,pos.y,message);
|
||
|
}
|
||
|
}
|
||
|
function drawCanvas() {
|
||
|
//console.log(`Processed ${numUpdates} times`);
|
||
|
numUpdates=0;
|
||
|
// loadChip();
|
||
|
|
||
|
pI = [];
|
||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||
|
ctx.canvas.width = window.innerWidth;
|
||
|
ctx.canvas.height = window.innerHeight;
|
||
|
ctx.fillStyle="darkgray";
|
||
|
ctx.fillRect(0,0,20,canvas.height-60);
|
||
|
ctx.fillRect(canvas.width-20,0,canvas.width,canvas.height-60);
|
||
|
ctx.fillStyle="#303030";
|
||
|
ctx.fillRect(0,canvas.height-60,canvas.width,canvas.height);
|
||
|
var cm=[];
|
||
|
var curX = 5;
|
||
|
Object.entries(chips).forEach(chip=>{
|
||
|
ctx.font = "20px vcr_osd_monoregular"
|
||
|
ctx.fillStyle="#1A3060";
|
||
|
width=ctx.measureText(chip[0]).width+8;
|
||
|
ctx.fillRect(curX+chipoffset,canvas.height-50,width,40);
|
||
|
ctx.fillStyle="white";
|
||
|
ctx.fillText(chip[0],curX+chipoffset+4,canvas.height-25);
|
||
|
cm.push({type:chip[0],topLeft:{x: curX+chipoffset, y: canvas.height-50},width:width});
|
||
|
curX+=width+5;
|
||
|
});
|
||
|
chipMenu=cm;
|
||
|
|
||
|
//components.slice().reverse().forEach(function(c){
|
||
|
for(var i=0;i<wireBeingDrawn.length-1;i++){
|
||
|
if(i==0)
|
||
|
p1=pinImages.find(p=>p.component==wireBeingDrawn[0].component && p.pin==wireBeingDrawn[0].pin && p.type==0).position;
|
||
|
else
|
||
|
p1=wireBeingDrawn[i];
|
||
|
//if(i==wireBeingDrawn.length-2)
|
||
|
// p2=pinImages.find(p=>p.component==wireBeingDrawn[i+1].component&&p.pin==wireBeingDrawn[i+1].pin&&p.type==0).position;
|
||
|
//else
|
||
|
p2=wireBeingDrawn[i+1]
|
||
|
o=0.5;
|
||
|
if(wireBeingDrawn[i+1].offset != undefined){
|
||
|
o=wireBeingDrawn[i+1].offset;
|
||
|
}
|
||
|
drawLine(p1,p2,3,["lightgray","red"][chip.subComponents[wireBeingDrawn[0].component].pins[wireBeingDrawn[0].pin]],true,o,wireBeingDrawn[0].rev);
|
||
|
}
|
||
|
wireVisuals=wireVisuals.filter(wv=> {return chip.wires.findIndex(w=>w.wireId==wv.wireId)>=0});
|
||
|
wireVisuals.forEach(wv=>{
|
||
|
for(var i=0;i<wv.path.length-1;i++){
|
||
|
if(i==0)
|
||
|
p1=pinImages.find(p=>p.component==wv.path[0].component && p.pin==wv.path[0].pin && p.type==0).position;
|
||
|
else
|
||
|
p1=wv.path[i];
|
||
|
if(i==wv.path.length-2)
|
||
|
p2=pinImages.find(p=>p.component==wv.path[i+1].component&&p.pin==wv.path[i+1].pin&&p.type==0).position;
|
||
|
else
|
||
|
p2=wv.path[i+1];
|
||
|
drawLine(p1,p2,3,["lightgray","red"][chip.subComponents[wv.path[0].component].pins[wv.path[0].pin]],true,wv.path[1].offset,wv.path[0].rev);
|
||
|
}
|
||
|
});
|
||
|
currentGroup=-1;
|
||
|
groupn=1;
|
||
|
chip.pins.forEach((p,i)=>{
|
||
|
if(i<chip.inputs){
|
||
|
centerX=10;
|
||
|
otherpins = chip.inputs;
|
||
|
pindex=i;
|
||
|
} else {
|
||
|
centerX = canvas.width - 10;
|
||
|
otherpins = chip.pins.length - chip.inputs;
|
||
|
pindex = i - chip.inputs;
|
||
|
}
|
||
|
ys = makeArr(0-(20/otherpins),0+(canvas.height-60)+(20/otherpins),otherpins+2);
|
||
|
ys.shift();
|
||
|
ys.pop();
|
||
|
centerY = ys[pindex];
|
||
|
//ctx.beginPath();
|
||
|
//ctx.arc(Math.floor(centerX), Math.floor(centerY), 10, 0, 2*Math.PI, false)
|
||
|
(hoveredPin.component==0&&hoveredPin.pin==i&&hoveredPin.type==0) ? offset=2 : offset=0;
|
||
|
newGroup = -1;
|
||
|
chip.groups.forEach((group,n)=>{
|
||
|
if(group.includes(i)) newGroup = n;
|
||
|
});
|
||
|
if(newGroup>-1 && newGroup==currentGroup) {
|
||
|
centerY -=20*groupn;
|
||
|
groupn++;
|
||
|
} else {groupn=1};
|
||
|
pI.push({component:0,pin:i,position:{x:centerX,y:centerY},radius:7,type:1});
|
||
|
secondaryX=(i<chip.inputs?centerX+20:centerX-20);
|
||
|
pI.push({component:0,pin:i,position:{x:secondaryX,y:centerY},radius:5,type:0});
|
||
|
fillStyle1=["black","red","grey","pink"][chip.pins[i]+offset];
|
||
|
fillStyle2=(i<chip.inputs?["black","red","grey","pink"]:["black","red","black","red"])[chip.pins[i]+((hoveredPin.component==0&&hoveredPin.pin==i&&hoveredPin.type==1)?2:0)];
|
||
|
//ctx.fill();
|
||
|
drawLine({x:centerX,y:centerY},{x:secondaryX,y:centerY},1,["black","red"][chip.pins[i]],false);
|
||
|
drawCircle(centerX,centerY,10,fillStyle2);
|
||
|
drawCircle(secondaryX,centerY,5,fillStyle1);
|
||
|
if(newGroup>-1){
|
||
|
ctx.fillStyle="white";
|
||
|
old=[ctx.textAlign, ctx.textBaseline];
|
||
|
ctx.textAlign="center";
|
||
|
ctx.textBaseline="middle";
|
||
|
ctx.font='11px monospace'
|
||
|
if(!chip.no)
|
||
|
ctx.fillText(Math.pow(2,groupn-1), centerX, centerY);
|
||
|
else if(!chip.no.includes(i)) ctx.fillText(Math.pow(2,groupn-1), centerX, centerY);
|
||
|
ctx.textAlign=old[0];
|
||
|
ctx.textBaseline=old[1];
|
||
|
}
|
||
|
currentGroup=newGroup;
|
||
|
})
|
||
|
chip.zIndex.forEach(c=>{
|
||
|
img = images[c.type];
|
||
|
if(img!==undefined&&img.dummy!=1)
|
||
|
ctx.drawImage(img,c.position.x,c.position.y);
|
||
|
else {
|
||
|
ctx.font = "20px vcr_osd_monoregular"
|
||
|
ctx.fillStyle="#1A3060";
|
||
|
h = parseInt(md5(c.type).substring(1,10),16);
|
||
|
poss_colors = ["white","red","lightblue","pink","yellow","lightgreen","orange","violet"];
|
||
|
ctx.fillStyle = poss_colors[h%poss_colors.length];
|
||
|
width = ctx.measureText(c.type).width + 30;
|
||
|
if (c.inputs>c.pins.length/2){
|
||
|
height = c.inputs * 15
|
||
|
} else {
|
||
|
height = (c.pins.length-c.inputs)*15;
|
||
|
}
|
||
|
ctx.fillRect(c.position.x,c.position.y,width,height);
|
||
|
ctx.fillStyle="black";
|
||
|
ctx.fillText(c.type,c.position.x+15,c.position.y+(height/2)+4);
|
||
|
img = {width,height,dummy:1};
|
||
|
images[c.type]=img;
|
||
|
}
|
||
|
|
||
|
c.pins.forEach((p,i)=>{
|
||
|
//trueCenterY = (c.position.y+(c.position.y + c.height))/2
|
||
|
centerX = c.position.x+img.width+5;
|
||
|
otherpins = c.pins.length - c.inputs;
|
||
|
pindex = i - c.inputs;
|
||
|
if(i<c.inputs){
|
||
|
centerX = c.position.x-5;
|
||
|
if(["xor","nor","or"].includes(c.type)){
|
||
|
centerX += 9;
|
||
|
}
|
||
|
otherpins = c.inputs;
|
||
|
pindex=i;
|
||
|
}
|
||
|
ys = makeArr(c.position.y-(20/otherpins),c.position.y+img.height+(20/otherpins),otherpins+2);
|
||
|
ys.shift();
|
||
|
ys.pop();
|
||
|
centerY = ys[pindex];
|
||
|
pI.push({component:chip.subComponents.indexOf(c),pin:i,position:{x:centerX,y:centerY},radius:10,type:0});
|
||
|
ctx.beginPath();
|
||
|
ctx.arc(Math.floor(centerX), Math.floor(centerY), 5, 0, 2*Math.PI, false);
|
||
|
(hoveredPin.component==chip.subComponents.indexOf(c)&&hoveredPin.pin==i) ? offset=2 : offset=0;
|
||
|
ctx.fillStyle=["black","red","grey","pink"][c.pins[i]+offset];
|
||
|
ctx.fill();
|
||
|
|
||
|
|
||
|
});
|
||
|
});
|
||
|
pinImages=pI;
|
||
|
|
||
|
tt = null;
|
||
|
if(hoveredPin.component>=0){
|
||
|
try{
|
||
|
drawTooltip(chip.subComponents[hoveredPin.component].tooltips[hoveredPin.pin]);
|
||
|
} catch(ex){console.log(ex)}
|
||
|
} else {
|
||
|
}
|
||
|
if(tt) tt.draw();
|
||
|
|
||
|
requestAnimationFrame(drawCanvas);
|
||
|
}
|