<DOCTYPE! html>
<html>
<head>
<script src = "js/CanvasInput.js"></script>
<script>
const TEXTBOX_MARGIN_WIDTH = 10; //Total left-right margin on textbox
const TEXTBOX_MARGIN_HEIGHT = 10;//Total up-down margin on textbox;
const TEXT_OFFSET_DOWN = 0; //Total amount text has to be offset down to fit in textbox
const TEXT_OFFSET_RIGHT = -0.5;//Total amount text has to be offset right to be centered in textbox
const MINT_GREEN = "#99FFAD";
const DARK_MINT_GREEN = "#89E598"
const PINK = '#FFDBF2'
const DARK_PINK = '#D3B6C9'
const EXPBOX_MARGIN_EXT = 8; //Total left-right margin around outside outside of expression boxes
const EXPBOX_MARGIN_INT = 3; //Total margin around operation in box
const EXPBOX_MARGIN_HEIGHT = 10;//Total margin
const INPUT_OFFSET = 2; //This is difference between top of blinky thing and top of text in a text field
BORDER_SIZE = 2;
class CanvasShape
{
constructor(x = 0, y = 0, color = "white")
{
this.x = x;
this.y = y;
this.color = color;
this.container = null;
}
setContainer(container)
{
this.container = container;
}
absX()
{
if(this.container)
{
return this.container.absX() + this.x;
}
else
{
console.log("Can't read container");
}
}
absY()
{
if(this.container)
{
return this.container.absY() + this.y;
}
else
{
console.log("Can't read container");
}
}
changeColor(color)
{
this.color = color;
}
changeX(x)
{
this.x = x;
}
changeY(y)
{
this.y = y;
}
changeInvisible(invisible)
{
this.invisible = invisible;
}
get2DContext()
{
if(this.getCanvas())//makes sure container exist, and if so if it can get a 2DContext
{
let context = this.container.get2DContext();
context.fillStyle = this.color;
return context;
; }
else
{
console.log("Can't read container");
}
}
getCanvas()
{
if(this.container)
{
return this.container.getCanvas();
}
else
{
console.log("Can't read container");
}
}
getWorkingCanvas()
{
if(this.container)
{
return this.container.getWorkingCanvas();
}
else
{
console.log("Can't read container");
}
}
adjustToEnviro()
{
//most shapes will do nothing, some will adjust to new environment
}
adjustSize()
{
//adjusts size based on situation the object itself can see
}
adjust()
{
if(this.getCanvas())
{
this.adjustSize();
if(this.container)
{
this.container.adjust();
}
else
{
console.log("Can't read container in adjust action");
}
}
}
//x is x-coord of click, y is y-coord of click, according to reference this shape is in (0, 0 is top left corner of container). Idea is that onClick will return shape that is clicked (lowest level shape if applicable)
onClick(x, y)
{
return null
}
draw()
{
throw new Error("Method 'draw()' must be implemented");
}
}
class CanvasRect extends CanvasShape
{
constructor(x, y, width, height, color)
{
super(x, y, color);
this.width = width;
this.height = height;
this.invisible = false;
}
changeWidth(width)
{
this.width = width;
}
changeHeight(height)
{
this.height = height;
}
getWidth()
{
return this.width;
}
getHeight()
{
return this.height;
}
getTop()
{
return this.y;
}
getBottom()
{
return this.y + this.getHeight();
}
getLeft()
{
return this.x;
}
getRight()
{
return this.x + this.getWidth();
}
onClick(x, y)
{
if(x > this.x && x < this.getRight() && y > this.y && y < this.getBottom())
{
return this;
}
return null;
}
//draws rectangle on canvas
draw()
{
if(this.getCanvas())
{
this.get2DContext().fillRect(this.absX(), this.absY(), this.width, this.height);
}
else
{
console.log("Can't get context!");
}
}
}
class CanvasBorderRect extends CanvasRect
{
constructor(x, y, width, height, color, border = true, borderColor = "black", borderSize = BORDER_SIZE)
{
super(x, y, width, height, color);
this.border = border;
this.borderColor = borderColor;
this.borderSize = borderSize;
}
changeBorder(border)
{
this.border = border;
}
changeBorderSize(borderSize = BORDER_SIZE)
{
this.borderSize = borderSize;
}
changeBorderColor(borderColor)
{
this.borderColor = borderColor;
}
draw()
{
super.draw();
if(this.border)
{
let context = this.get2DContext();
context.fillStyle = this.borderColor;
//draws all 4 sides of the border starting in order of top, right, bottom, left
context.fillRect(this.absX(), this.absY(), this.getWidth(), this.borderSize);
context.fillRect(this.absX(), this.absY(), this.borderSize, this.getHeight());
context.fillRect(this.absX(), this.absY() + this.getHeight() - this.borderSize, this.getWidth(), this.borderSize);
context.fillRect(this.absX() + this.getWidth() - this.borderSize, this.absY(), this.borderSize, this.getHeight());
}
}
}
class CanvasText extends CanvasShape
{
constructor(x, y, text, textColor = "black", size = 18, font = 'Computer Modern')
{
super(x, y, null);
this.textColor = textColor;
this.text = text;
this.font = font;
this.size = size;
this.italics = false;
this.bold = false;
}
getText()
{
return this.text;
}
changeText(text)
{
this.text = text;
}
//changes font to whatever name is passed in
changeFont(font)
{
this.font = font;
}
//changes size to whatever size is passted in
changeSize(size)
{
this.size = parseInt(size);
}
getSize()
{
return this.size;
}
changeTextColor(textColor)
{
this.textColor = textColor;
}
makeItalics(italics)
{
this.italics = italics;
}
makeBold(bold)
{
this.bold = bold;
}
get2DContext()
{
if(this.getCanvas())
{
let context = this.container.get2DContext();
context.font = this.size + "pt " + this.font;
if(this.italics)
{
context.font = "italic " + context.font;
}
context.fillStyle = this.textColor;
return context;
}
else
{
console.log("Can't read container or contanier context of CanvasText");
}
}
getWidth()
{
if(this.getCanvas())
{
let textDim = this.get2DContext().measureText(this.text);
return parseInt(textDim.width);
}
else
{
console.log("Can't read context");
}
}
getHeight() //height not including things below line
{
if(this.getCanvas())
{
let textDim = this.get2DContext().measureText(this.text);
let actualHeight = textDim.actualBoundingBoxAscent;// + textDim.actualBoundingBoxDescent;
return parseInt(actualHeight);
}
}
getFullHeight() //height including things below the line
{
if(this.getCanvas())
{
let textDim = this.get2DContext().measureText(this.text);
let actualFullHeight = textDim.actualBoundingBoxAscent + textDim.actualBoundingBoxDescent;
return parseInt(actualFullHeight);
}
}
getLeft()
{
return this.x;
}
getRight()
{
return this.x + this.getWidth();
}
getBottom()
{
return this.y + this.getHeight();
}
getTop()
{
return this.y;
}
draw()
{
let adjustedY = this.absY() + this.getHeight() + TEXT_OFFSET_DOWN*(this.size/18);
this.get2DContext().fillText(this.text, this.absX(), adjustedY);
}
}
class CanvasTextField extends CanvasShape
{
constructor(x, y, color = "white")
{
super(x, y, color);
this.value = null;
this.onsubmit = null;
this.onkeydown = null;
this.onkeyup = null;
this.input = null;
this.borderWidth = 1;
this.border = true;
this.font = "Computer Modern";
this.fontSize = 18;
this.startingWidth = 10;
let self = this;
function onDown()
{
self.adjust();
if(self.getCanvas())
{
self.getWorkingCanvas().draw();
}
}
this.changeKeyDown(onDown);
}
setInput()
{
if(this.getCanvas())
{
this.input = new CanvasInput({canvas: this.getCanvas(),
value: this.value,
placeHolder: "",
fontFamily: this.font,
fontSize: this.fontSize,
x: this.absX(),
y: this.absY(),
width: this.startingWidth,
onsubmit: this.onsubmit,
onkeydown: this.onkeydown,
onkeyup: this.onkeyup,
boxShadow: 'none',
backgroundColor: this.color,
borderWidth: Number(this.border)*this.borderWidth
});
}
else
{
console.log("Can't read canvas of TextField");
}
}
setContainer(container)
{
super.setContainer(container);
this.setInput();
}
adjustToEnviro()
{
this.setInput();
}
changeX(x)
{
super.changeX(x);
this.input.x(this.absX());
}
changeY(y)
{
super.changeY(y);
this.input.y(this.absY());
}
changeFunc(func)
{
this.onsubmit = func;
if(this.input != null)
{
this.input.onsubmit(func);
}
}
changeKeyDown(func)
{
this.onkeydown = func;
if(this.input !== null)
{
this.input.onkeydown(func);
}
}
changeKeyUp(func)
{
this.onkeyup = func;
if(this.input !== null)
{
this.input.onkeyup(func);
}
}
getText()
{
return this.input.value();
}
getRight()
{
return this.x + this.getWidth();
}
getHeight()
{
return this.input.height() + 2*this.input.borderWidth() + 2*this.input.padding();
}
getWidth()
{
return this.input.width() + 2*this.input.borderWidth() + 2*this.input.padding();
}
getLevel()
{
return this.input.height() + this.input.borderWidth() + this.input.padding() - INPUT_OFFSET;
}
setText(text)
{
this.input.value(text);
}
changeColor(color)
{
this.color = color;
if(this.input !== null)
{
this.input.backgroundColor(color);
}
}
colorAll(color)
{
this.changeColor(color);
}
changeBorder(border)
{
this.border = border;
if(this.input !== null && !this.border)
{
this.input.borderWidth(0);
}
else if(this.input !== null)
{
this.input.borderWidth(this.borderWidth);
}
}
changeInvisible(invisible)
{
this.invisible = invisible;
}
adjustSize()
{
if(this.input !== null)
{
let textWidth = this.input._textWidth(this.input.value() + "M");
this.input.width(textWidth);
}
}
draw()
{
if(!this.invisible)
{
this.input.render();
}
}
}
class CanvasContainer extends CanvasBorderRect
{
constructor(x, y, width = 0, height = 0, color = "white", border = false)
{
super(x, y, width, height, color, border);
this.shapes = [];
}
add(shape)
{
shape.setContainer(this);
this.shapes.push(shape);
}
setContainer(container)
{
super.setContainer(container);
if(container.getCanvas())//only tries to adjust if container has environment to begin with
{
this.adjustToEnviro();
}
}
adjustToEnviro()
{
for(let i = 0; i < this.shapes.length; i++)
{
this.shapes[i].adjustToEnviro();
}
this.adjustSize();
}
colorAll(color)
{
this.changeColor(color);
for(let i = 0; i < this.shapes.length; i++)
{
if(this.shapes[i].colorAll)
{
this.shapes[i].colorAll(color);
}
}
}
colorBordersAll(color)
{
this.changeBorderColor(color);
for(let i = 0; i < this.shapes.length; i++)
{
if(this.shapes[i].colorBordersAll)
{
this.shapes[i].colorBordersAll(color);
}
}
}
onClick(x, y)
{
if(super.onClick(x, y) !== null)//if this shape was clicked (meaning rectangle onClick is not null), we check its interior shapes to see which is the lowest item clicked
{
for(let i = 0; i < this.shapes.length; i++) //ONLY works if interior shapes don't overlap. Goes through each one and finds the most internal shape in that shape. Since they don't overlap should get just one or none, in which case this container is the lowest element clicked
{
let clickedShape = this.shapes[i].onClick(x - this.x, y - this.y);
if(clickedShape !== null)
{
return clickedShape;
}
}
return this;
}
return null; //If this shape isn't even clicked return null;
}
draw()
{
//first draws container if not transparent
super.draw();
//then draws all the shapes
for(let i = 0; i < this.shapes.length; i++)
{
if(!this.shapes[i].invisible)
{
this.shapes[i].draw();
}
}
}
}
//textbox class capable of making a textbox anywhere!
class CanvasTextBox extends CanvasContainer
{
constructor(x, y, text = "", color = MINT_GREEN, size = 18, textColor = "black", font = "Computer Modern")
{
//first we create CanvasContainer that is our box, and then we put a text object in it
super(x, y, 0, 0, color, false);//starts with border off
this.text = new CanvasText(TEXTBOX_MARGIN_WIDTH, TEXTBOX_MARGIN_HEIGHT, text, textColor, size, font);
this.add(this.text);
}
adjustToEnviro()
{
this.adjustSize();
}
adjustSize()
{
if(this.getCanvas())
{
this.changeWidth(this.text.getWidth() + 2*TEXTBOX_MARGIN_WIDTH);
this.changeHeight(this.text.getHeight() + 2*TEXTBOX_MARGIN_HEIGHT);
}
else
{
console.log("Can't read context");
}
}
getText()
{
return this.text.getText();
}
changeText(text)
{
this.text.changeText(text);
this.adjust();
}
colorAll(color)
{
this.changeColor(color);
}
colorBordersAll(color)
{
this.changeBorderColor(color);
}
onClick(x, y)
{
let clickedShape = super.onClick(x, y); //Since textbox just has one interior shape canvasText, which should return null as it is not a rectangle, this function will return this here textbox itself if clicked at all.
if(clickedShape)//if textbox clicked, let's indicate which one
{
//console.log(this.text.text + " was clicked!!!");
}
return clickedShape;
}
}
class CanvasExpressionBox extends CanvasContainer
{
constructor(x, y, exps, color = MINT_GREEN, borderColor = DARK_MINT_GREEN, opColor = "black")
{
super(x, y, 0, 0, color, true);//the true turns the border on
this.changeBorderColor(borderColor);
this.exps = exps;
this.ops = [];
this.op = null;
this.opColor = opColor;
this.parensInvisible = true;//whether or not to include parenthesis around everything
this.parens = this.makeParens();
this.inputBox = this.makeInputBox();
this.focus = false;
this.addExpsAndSet();
}
add(shape)
{
this.shapes.push(shape);
}
//Adds all shapes first and THEN sets the containers, since when shapes adjust it might depend on other shapes. Also avoids unneccesary adjusting all the time
addExpsAndSet()
{
this.add(this.parens[0]);
this.addExps();
this.add(this.parens[1]);
this.add(this.inputBox);
for(let i = 0; i < this.shapes.length; i++)
{
this.shapes[i].setContainer(this);
}
}
addExps()
{
//Need to add expressions here (may have different set up depending on unary, binary, etc)
}
makeOp()
{
return null; //Should return however to make your operation
}
changeParens(parensInvisible)
{
this.parensInvisible = parensInvisible;
this.parens[0].changeInvisible(this.parensInvisible);
this.parens[1].changeInvisible(this.parensInvisible);
this.adjust();
}
makeParens()
{
let leftParen = new CanvasText(0, 0, "(");
let rightParen = new CanvasText(0, 0, ")");
leftParen.changeInvisible(this.parensInvisible);
rightParen.changeInvisible(this.parensInvisible);
return [leftParen, rightParen];
}
getLevel() //finds level in y-coordinate of this object of baseline of writing
{
return this.exps[0].getTop() + this.exps[0].getLevel();
}
getMaxLetterHeight()
{
let maxLetterHeight = 0;
for(let i = 0; i < this.exps.length; i++)
{
if(this.exps[i].getMaxLetterHeight() > maxLetterHeight)
{
maxLetterHeight = this.exps[i].getMaxLetterHeight();
}
}
return maxLetterHeight;
}
makeInputBox()
{
let inputBox = new CanvasTextField(0, 0);
inputBox.changeColor(this.color);
inputBox.changeInvisible(true);
inputBox.changeBorder(false);
return inputBox;
}
changeFocus(focus)
{
this.focus = focus;
this.inputBox.changeInvisible(!focus);
this.adjust();
}
}
class CanvasObjBox extends CanvasExpressionBox
{
constructor(x, y, text, color, size = 18, textColor = "black", font = "Computer Modern")
{
let expObj = new CanvasText(0, 0, text, textColor, size, font);
super(x, y, [expObj], color);
}
addExps()
{
this.add(this.exps[0]);
}
adjustSize()
{
let maxHeight = this.exps[0].getHeight();
let maxDepth = 0;
if(!this.parensInvisible)
{
if(this.parens[0].getHeight() > maxHeight)
{
maxHeight = this.parens[0].getHeight();
}
}
if(this.focus && this.inputBox.getHeight() > maxHeight)
{
maxHeight = this.inputBox.getHeight();
}
this.height = maxHeight + maxDepth + 2*EXPBOX_MARGIN_EXT;
if(!this.parensInvisible && this.parens[0].getHeight() == maxHeight)
{
let expP = (this.height - this.parens[0].getHeight())/2;
this.parens[0].changeY(expP);
this.parens[1].changeY(expP);
this.exps[0].changeY(expP + this.parens[0].getHeight() - this.exps[0].getHeight());
this.inputBox.changeY(expP + this.parens[0].getHeight() - this.inputBox.getHeight());
}
else if(this.focus && this.inputBox.getHeight() == maxHeight)
{
let inpY = (this.height - this.inputBox.getHeight())/2;
this.exps[0].changeY(inpY + this.inputBox.getLevel() - this.exps[0].getHeight());
//this.exps[0].changeY(inpY + this.inputBox.getHeight() - this.exps[0].getHeight());
this.inputBox.changeY(inpY);
this.parens[0].changeY(inpY + this.inputBox.getHeight() - this.parens[0].getHeight());
this.parens[1].changeY(inpY + this.inputBox.getHeight() - this.parens[0].getHeight());
}
else
{
let expY = (this.height - this.exps[0].getHeight())/2;
this.exps[0].changeY(expY);
this.inputBox.changeY(expY + this.exps[0].getHeight() - this.inputBox.getHeight());
this.parens[0].changeY(expY + this.exps[0].getHeight() - this.parens[0].getHeight());
this.parens[1].changeY(expY + this.exps[0].getHeight() - this.parens[0].getHeight());
}
///////First we do y's then x's
let start = EXPBOX_MARGIN_EXT;
if(!this.parensInvisible)
{
this.parens[0].changeX(start);
start = this.parens[0].getRight() + EXPBOX_MARGIN_INT;
}
this.exps[0].changeX(start);
let end = this.exps[0].getRight();
if(this.focus)
{
this.inputBox.changeX(end + EXPBOX_MARGIN_INT);
end = this.inputBox.getRight();
}
if(!this.parensInvisible)
{
this.parens[1].changeX(end + EXPBOX_MARGIN_INT);
end = this.parens[1].getRight();
}
this.width = end + EXPBOX_MARGIN_EXT;
}
//adjustToEnviro()
//{
// this.adjustSize();
//}
getText()
{
return this.exps[0].getText();
}
changeText(text)
{
this.exps[0].changeText(text);
this.adjust();
}
colorBordersAll(color)
{
this.changeBorderColor(color);
}
onClick(x, y)
{
let clickedShape = super.onClick(x, y); //Since textbox just has one interior shape canvasText, which should return null as it is not a rectangle, this function will return this here textbox itself if clicked at all.
if(clickedShape)//if textbox clicked, let's indicate which one
{
//console.log(this.text.text + " was clicked!!!");
}
return clickedShape;
}
getLevel() //in this case the bottom just is the level as we're dealing with just variables and numbers here
{
return this.getHeight();
}
getMaxLetterHeight()
{
return this.getHeight();
}
makeItalics(italics)
{
this.exps[0].makeItalics(italics);
}
}
class CanvasUniBox extends CanvasExpressionBox
{
constructor(x, y, exps, color)
{
if(exps.length != 1)
{
throw new Error("Can only seat one expression in a UniBox");
}
super(x, y, exps, color);
}
addExps()
{
let op = this.makeOp();
this.ops.push(op);
this.add(op);
this.add(this.exps[0]);
}
adjustSize()
{
let maxHeight = this.exps[0].getLevel();
let maxDepth = this.exps[0].getHeight() - this.exps[0].getLevel();
this.height = maxHeight + maxDepth + 2*EXPBOX_MARGIN_EXT;
this.ops[0].changeY((this.height - this.ops[0].getHeight())/2);
this.exps[0].changeY((this.height - this.exps[0].getHeight())/2);
let currentHeight = this.parens[0].getFullHeight();
let newSize = ((maxHeight + maxDepth)/currentHeight)*this.parens[0].getSize();
this.parens[0].changeSize(newSize);
this.parens[1].changeSize(newSize);
this.parens[0].changeY(this.getLevel() + maxDepth - this.parens[0].getFullHeight());
this.parens[1].changeY(this.getLevel() + maxDepth - this.parens[1].getFullHeight());
///////////////First we handle the y's then we do the x's
let start = EXPBOX_MARGIN_EXT;
if(!this.parensInvisible)
{
this.parens[0].changeX(start);
start = this.parens[0].getRight() + EXPBOX_MARGIN_INT;
}
this.ops[0].changeX(start);
this.exps[0].changeX(this.ops[0].getRight() + EXPBOX_MARGIN_INT);
let end = this.exps[0].getRight();
if(!this.parensInvisible)
{
this.parens[1].changeX(end + EXPBOX_MARGIN_INT);
end = this.parens[1].getRight();;
}
this.width = end + EXPBOX_MARGIN_EXT;
}
}
class CanvasNotBox extends CanvasUniBox
{
constructor(x, y, exps, color)
{
super(x, y, exps, color);
this.op = "~";
}
makeOp()
{
return new CanvasText(0, 0, "\u223C", this.opColor);
}
}
class CanvasAssBox extends CanvasExpressionBox
{
constructor(x, y, exps, color = MINT_GREEN)
{
super(x, y, exps, color);
this.op = null;
}
addExps()
{
this.add(this.exps[0]); //adds first expression
for(let i = 1; i < this.exps.length; i++)
{
let currentOp = this.makeOp();
this.ops.push(currentOp); //addsd op to list of ops
this.add(currentOp);//adds op inbetween i - 1th and ith expression
this.add(this.exps[i]);//adds ith expression
}
}
//Readjusts all expression to be in the right place and resizes everything
adjustSize()
{
let maxHeight = this.exps[0].getLevel();
let maxDepth = this.exps[0].getHeight() - this.exps[0].getLevel();
for(let i = 1; i < this.exps.length; i++)
{
if(this.exps[i].getLevel() > maxHeight)
{
maxHeight = this.exps[i].getLevel();
}
if(this.exps[i].getHeight() - this.exps[i].getLevel() > maxDepth)
{
maxDepth = this.exps[i].getHeight() - this.exps[i].getLevel();
}
}
this.height = maxHeight + maxDepth + 2*EXPBOX_MARGIN_EXT;
for(let i = 0; i < this.exps.length; i++)
{
this.exps[i].changeY(EXPBOX_MARGIN_EXT + maxHeight - this.exps[i].getLevel());
}
for(let i = 0; i < this.ops.length; i++)
{
this.ops[i].changeY(this.getLevel() - this.getMaxLetterHeight()/2 - this.ops[i].getHeight()/2);
}
let currentHeight = this.parens[0].getFullHeight();
let newSize = ((maxHeight + maxDepth)/currentHeight)*this.parens[0].getSize();
this.parens[0].changeSize(newSize);
this.parens[1].changeSize(newSize);
this.parens[0].changeY(this.getLevel() + maxDepth - this.parens[0].getFullHeight());
this.parens[1].changeY(this.getLevel() + maxDepth - this.parens[1].getFullHeight());
///////////////First we do the heights as above and then we do the widths. This is for sake of parenthesis getting bolder
let start = EXPBOX_MARGIN_EXT;
if(!this.parensInvisible)
{
this.parens[0].changeX(start);
start = this.parens[0].getRight() + EXPBOX_MARGIN_INT;
}
this.exps[0].changeX(start); //adds first expression and makes it right at the margin
//Here we go and add all expressions to our shape and make their x values accordinly based on the widths and margins
for(let i = 1; i < this.exps.length; i++)
{
this.ops[i - 1].changeX(this.exps[i - 1].getRight()+ EXPBOX_MARGIN_INT);
//adds next inner expression box a margin away from the operation
//this.add(this.exps[i]);
this.exps[i].changeX(this.ops[i - 1].getRight() + EXPBOX_MARGIN_INT); //changes x to current x;
}
let end = this.exps[this.exps.length - 1].getRight();
if(!this.parensInvisible)
{
this.parens[1].changeX(end + EXPBOX_MARGIN_INT);
end = this.parens[1].getRight();;
}
this.width = end + EXPBOX_MARGIN_EXT;
}
makeOp()
{
return null; //Need actual instance of Associative Box to make operation here
}
}
class CanvasAndBox extends CanvasAssBox
{
constructor(x, y, exps, color)
{
super(x, y, exps, color);
this.op = "^";
}
makeOp()
{
return new CanvasText(0, 0, "\u2227", this.opColor);
}
}
class CanvasOrBox extends CanvasAssBox
{
constructor(x, y, exps, color)
{
super(x, y, exps, color);
this.op = "v";
}
makeOp()
{
return new CanvasText(0, 0, "\u2228", this.opColor);
}
}
class CanvasXORBox extends CanvasAssBox
{
constructor(x, y, exps, color)
{
super(x, y, exps, color);
this.op = "xor";
}
makeOp()
{
return new CanvasText(0, 0, "\u2295", this.opColor);
}
}
class CanvasImpliesBox extends CanvasAssBox
{
constructor(x, y, exps, color)
{
super(x, y, exps, color);
this.op = "implies";
}
makeOp()
{
return new CanvasText(0, 0, "\u2192", this.opColor);
}
}
class WorkingCanvas extends CanvasContainer
{
constructor(canvas, color = "white", clickFunc = null)
{
super(0, 0, canvas.width, canvas.height, color);
this.canvas = canvas;
this.clickFunc = clickFunc;
this.clear();
//this adds event listener to canvas, where all click functions cascade down from
let myCanvas = this;
this.canvas.addEventListener('click', function(event){
let canvasLeft = myCanvas.canvas.offsetLeft + myCanvas.canvas.clientLeft; //this is left side of canvas on page
let canvasTop = myCanvas.canvas.offsetTop + myCanvas.canvas.clientTop; //this is right side of canvas on page
myCanvas.onClick(event.pageX - canvasLeft, event.pageY - canvasTop); //passes click duties to onClick function
}, false);
}
onClick(x, y)
{
//console.log("Clicked at..." + x + ", " + y);
let clickedShape = super.onClick(x, y);
if(this.clickFunc !== null)
{
this.clickFunc(clickedShape);
}
}
changeClickFunc(func)
{
this.clickFunc = func;
}
absX()
{
return 0;
}
absY()
{
return 0;
}
get2DContext()
{
if(this.canvas)
{
let context = this.canvas.getContext('2d');
context.fillStyle = this.color;
return context;
}
}
getWorkingCanvas()
{
return this;
}
getCanvas()
{
return this.canvas;
}
clear()
{
if(this.getCanvas())
{
this.get2DContext().fillRect(0, 0, this.width, this.height);
}
else
{
console.log("Can't read context");
}
}
adjust()
{
//do nothing
}
draw()
{
console.log("...drawing full canvas...")
super.draw();
}
}
function start()
{
var myCanvas = new WorkingCanvas(document.getElementById('canvas'));
var inputBox = new CanvasTextField(10, 10);
myCanvas.add(inputBox);
var textBox2 = new CanvasTextBox(10, 50, "UPPER CASE", MINT_GREEN);
myCanvas.add(textBox2);
inputBox.changeFunc(test);
inputBox.changeKeyUp(onUp);
inputBox.changeKeyDown(onDown);
var objBox = new CanvasObjBox(10, 300, "p");
objBox.changeBorder(true);
objBox.changeBorderColor(DARK_MINT_GREEN);
myCanvas.add(objBox);
objBox.changeFocus(true);
var hingBox = new CanvasObjBox(0, 0, "hing");
hingBox.changeBorder(true);
hingBox.changeBorderColor(DARK_MINT_GREEN);
var notBox = new CanvasNotBox(10, 500, [hingBox]);
myCanvas.add(notBox);
function test()
{
leftBox.changeText(inputBox.getText());
myCanvas.shapes.splice(myCanvas.shapes.length - 1, 1);
textBox2 = parseExpression(inputBox.getText());
textBox2.changeX(10);
textBox2.changeY(50);
myCanvas.add(textBox2);
//console.log("From test I get..." + inputBox.getText());
myCanvas.draw();
}
function onDown()
{
inputBox.adjustSize();
myCanvas.draw();
}
function onUp()
{
/*
let text = inputBox.getText();
let index = findNaughtyString(text);
if(index >= 0)
{
inputBox.setText(text.substring(0, index));
myCanvas.draw();
console.log("throw new Error(May not input'#' or '@'");
}
*/
//inputBox.adjustSize();
//myCanvas.draw();
}
function clickFunc(shape)
{
if(shape === myCanvas)
{
return;
}
let changed = false;
if(shape.colorAll)
{
shape.colorAll(PINK);
changed = true;
}
if(shape.colorBordersAll)
{
shape.colorBordersAll(DARK_PINK);
changed = true;
}
if(changed)
{
myCanvas.draw();
}
}
myCanvas.changeClickFunc(clickFunc);
myCanvas.draw();
}
function parseExpression(rawStr, blocks = [], phrases = [])
{
console.log("Raw Str:" + rawStr);
let str = removeSpaces(rawStr);
if(phrases.length == 0)
{
phrases = findQuotes(str);
for(let i = 0; i < phrases.length; i++)
{
let phrase = phrases[i];
str = str.replaceAll(phrase, "@" + i + "@");
}
}
if(blocks.length == 0)
{
blocks = findParens(str);
for(let i = 0; i < blocks.length; i++)
{
let parenStr = "(" + blocks[i] + ")";
str = str.replaceAll(parenStr, "#" + i + "#");
}
}
if(str.match(/->/) !== null)
{
let index = str.lastIndexOf(str.match(/->/))
let part1 = str.substring(0, index);
let part2 = str.substring(index + 2, str.length);
if(part1.length == 0 || part2.length == 0)
{
throw new Error("Invalid Expression");
}
else
{
let box1 = parseExpression(part1, blocks, phrases);
let box2 = parseExpression(part2, blocks, phrases);
return new CanvasImpliesBox(0, 0, [box1, box2]);
}
}
else if(str.match(/[+v\^]/) !== null)
{
let index = str.lastIndexOf(str.match(/[+v\^]/g).pop());
let op = str.charAt(index);
let part1 = str.substring(0, index);
let part2 = str.substring(index + 1, str.length);
let box1 = parseExpression(part1, blocks, phrases);
let box2 = parseExpression(part2, blocks, phrases);
if(part1.length == 0 || part2.length == 0)
{
throw new Error("Invalid Expression");
}
else if(op == "+")
{
return new CanvasXORBox(0, 0, [box1, box2]);
}
else if(op == "v")
{
return new CanvasOrBox(0, 0, [box1, box2]);
}
else if(op == "^")
{
return new CanvasAndBox(0, 0, [box1, box2]);
}
}
else if(str.match(/~/g) !== null)
{
let index = str.search(/~/g);
let part1 = str.substring(0, index);
let part2 = str.substring(index + 1, str.length);
if(part1.length > 0)
{
throw new Error("Invalid Expression");
}
let box = parseExpression(part2, blocks, phrases);
return new CanvasNotBox(0, 0, [box]);
}
else if(str.match(/#\d#/g) !== null)
{
let firstMatch = str.match(/#\d#/)[0];
let start = str.search(/#\d#/);
let end = start + firstMatch.length - 1;
let part1 = str.substring(0, start);
let part2 = str.substring(end + 1, str.length);
if(part1.length > 0 || part2.length > 0)
{
throw new Error("Invalid Expression: No operation between objects");
}
let blockNum = parseInt(firstMatch.replaceAll("#", ""));
let box = parseExpression(blocks[blockNum], [], phrases);
box.changeParens(false);
return box;
}
else if(str.match(/@\d@/g) !== null)
{
let firstMatch = str.match(/@\d@/g)[0];
let start = str.search(/@\d@/g);
let end = start + firstMatch.length - 1;
let part1 = str.substring(0, start);
let part2 = str.substring(end + 1, str.length);
if(part1.length > 0 || part2.length > 0)
{
throw new Error("Invalid Expression: No operation between objects");
}
let phraseNum = parseInt(firstMatch.replaceAll("@", ""));
str = str.replaceAll("@" + phraseNum + "@", phrases[phraseNum]);
//then we let the rest take of it
}
//let textBox = new CanvasTextBox(0, 0, str);
let objBox = new CanvasObjBox(0, 0, convertToMath(str));
objBox.changeBorder(true);
objBox.changeBorderColor(DARK_MINT_GREEN);
if(str == "h")
{
objBox.makeItalics(true);
}
return objBox;
}
//finds parts between most outer parenthesis and stores them in an array of strings
function findParens(str)
{
let blocks = []; //where we will store blocks between parenthesis
let open = 0; //counts open parenthesis vs closed parenthesis
let start = 0; //will store location outer parenthesis starts when we get there
let started = false; //says that we are not in parenthesis yet
for(let i = 0; i < str.length; i++)
{
if(str[i] == "(")
{
open++; //one more open parenthesis, so it goes up
}
else if(str[i] == ")")
{
open-- //one less open parenthesis so it goes down
}
if(open < 0)
{
throw new Error("Invalid Parenthesis");
}
if(open > 0 && !started) //if we are unbalnced and haven't started yet, that means this must be the first parenthesis to start a new block.
{
started = true;
start = i;
}
if(open == 0 && started) //once we get back to balanced, if we've already started, this must mean this is the end of a block
{
let block = str.substring(start + 1, i);
blocks.push(block);
started = false;
}
}
if(open != 0)
{
throw new Error("Unbalanced Parenthesis");
}
return blocks;
}
function findQuotes(str)
{
let blocks = []; //where we will store blocks between parenthesis
let open = 0; //counts number of parenthesis total
let start = -1; //will store location outer parenthesis starts when we get there
let started = false; //says that we are not in parenthesis yet
for(let i = 0; i < str.length; i++)
{
if(str[i] == "\"")
{
if(started)
{
let block = str.substring(start, i + 1);
blocks.push(block);
started = false;
}
else
{
started = true;
start = i;
}
}
}
if(started)
{
let block = str.substring(start);
blocks.push(block);
}
if(open % 2 != 0)
{
throw new Error("Unbalanced Quotes");
}
return blocks;
}
function findUnquotedParts(str)
{
let phrases = findQuotes(str);
let parts = []; //non quoted portions
let end = 0; //variable which will keep index after "" block
for(let i = 0; i < phrases.length; i++) //finds all parts between "" blocks
{
let start = str.indexOf(phrases[i]); //start of this new "" block
let part = str.substring(end, start);//This is place between end of last "" and beginning of new ""
parts.push(part);
//parts.push(part.replaceAll(/\s+/g, "")); //pushes new part without parenthesis
end = start + phrases[i].length; //finds new end
}
let lastPart = str.substring(end); //part after last "" block to the end of str (Note if no "" blocks end = 0, so it still works)
parts.push(lastPart);
return parts;
}
function findNaughtyString(str)//finds first place of some #num# or @num@ that could mess things up in the parseExpression phase
{
let phrases = findQuotes(str);
let parts = findUnquotedParts(str); //finds unquoted sections where something could mess us up
for(let i = 0; i < parts.length; i++)//goes through all these parts
{
let index = parts[i].search(/#\d#/g); //finds where possible this part has #num#
let index2 = parts[i].search(/@\d@/g); //finds where possible this part has @num@
if(index2 >= 0 && (index2 < index || index == -1)) //if index2 = -1, then index1 will be index we want. If index2 > 0 it must be less than index to be index we want
{
index = index2;
}
//now index is -1 if no matches, or smallest index where there is a match
if(index >= 0)
{
for(let j = 0; j < i; j++)
{
index = index + phrases[j].length + parts[j].length; //add to index sizes of all lists before
}
return index; //now this is index that the string first breaks the rules
}
}
return -1; //if none found return -1
}
function removeSpaces(str)
{
let phrases = findQuotes(str);
let parts = findUnquotedParts(str);
let fixedStr = parts[0].replaceAll(/\s+/g, "");
for(let i = 0; i < phrases.length; i++)
{
fixedStr = fixedStr + phrases[i]; //complies all "" blocks and fixed parts
fixedStr = fixedStr + parts[i + 1].replaceAll(/\s+/g, "");
}
return fixedStr;
}
function convertToMath(str)
{
if(str.length > 1)
{
if(str.charAt(0) == "\"" && str.charAt(str.length - 1) == "\"")
{
return str.substring(1, str.length - 1);
}
throw new Error("Phrases should be in double quotes : " + str);
}
if(str == "h")
{
return "h";
}
if(str.match(/[a-z]/g) !== null)
{
return String.fromCodePoint(parseInt(str.charCodeAt(0)) + parseInt("1D44E", 16) - 97);
}
else if(str.match(/[A-Z]/g))
{
return String.fromCodePoint(parseInt(str.charCodeAt(0)) + parseInt("1D434", 16) - 65);
}
return str;
}
function getCanvas()
{
return document.getElementById('canvas');
}
function newTextBox(text, x, y)//text is text of textbox, x is how far right, y is how far down from start of Canvas
{
var canvas = getCanvas();//gets canvas
var context = canvas.getContext('2d');//gets 2D context
context.font = "18pt Computer Modern";//sets font;
let textDim = context.measureText(text);//finds width of this text
context.fillStyle = "#99FFAD";
context.fillRect(x,y, parseInt(textDim.width) + 2*TEXTBOX_MARGIN_WIDTH, TEXTBOX_HEIGHT);
context.fillStyle = "black";
context.fillText(text, x + TEXTBOX_MARGIN_WIDTH + TEXT_OFFSET_RIGHT, y + TEXT_OFFSET_DOWN);
}
</script>
<meta charset=utf-8 />
<title>Draw a rectangular shape</title>
</head>
<body onload="start();">
<p>Hello!</p>
<canvas id="canvas" width="1000" height="1000"></canvas>
</body>
</html>