xxxxxxxxxx
// need to be able to delete ingredient
// when creating a new input structure, disable number boxes until name box has been updated
let products = []
let inputs = []
let arrows = []
let plus = []
let deletes = []
let buttonstroke, buttonfill, arrowcol, needcol, mincol, namecol, inputbackcol, buttonhigh
let jrow = 45, imarg = 150, joff = 50
let imatch = -1
let debug = false
let shaded = false
let ordinputs = []
let canvas, buffer, oldname, activename = -1
function setup() {
canvas = createCanvas(windowWidth, windowHeight);
buttonstroke = color(120,120,120)
buttonfill = color(30,90,30)
buttonhigh = color(50,110,50)
inputbackcol = color(40,40,40)
arrowcol = 255
needcol = color(170,50,170)
mincol = color(170,170,50)
namecol = color(120,120,120)
// Create structure for root parent input boxes. Each structure will have the following fields:
// name: an input box for the name of the product
// need: an input box for the number of products needed
// min: an input box for the minimum number of the product that can be produced by its recipe
// level: integer specifying what level in the input tree this set of input boxes belongs.
// product: index into the products array to which this set of input boxes belong.
// parent: if the product is an ingredient in a recipe, 'parent' corresponds to the parent input structure, i.e.
// an index into the inputs array that refers to the item that the current product is helping to make.
// children: array of numbers representing the indices into the inputs array that correspond to this element's
// ingredients.
// arrow: index into the arrow button array corresponding to this set of input boxes.
// An arrow button appears below the product name if the product has a recipe, and the user clicks it
// to expand the recipe. A value of -1 indicates that no recipe exists, so no arrow button is present.
// plus: index into the plus button array corresponding to this set of input boxes.
// A plus button appears below every product name. It allows the user to add ingredients for making the
// product.
// visible: boolean indicating if input boxes and buttons are shown or hidden.
inputs.push({name: createInput(''), need: createInput(''), minnum: createInput(''), level: 0,
product: 0, parent: -1, children: [],
arrow: -1, plus: 0, delete: -1,
visible: true})
// Make the first plus button:
plus.push(new SymbolButton(imarg+70,joff+jrow+2,30,15,'+',arrowcol,buttonstroke,buttonfill,false,0))
// Format root product input boxes:
inputs[0].need.size(30)
inputs[0].need.position(imarg,joff+jrow-20)
inputs[0].need.input(updateNumNeeded)
inputs[0].need.name = '0' // The name of the input box will be its index into the inputs array
inputs[0].need.value(1)
inputs[0].need.style('background-color',inputbackcol)
inputs[0].need.style('color',color(255,255,255))
inputs[0].need.style('border-style','solid')
inputs[0].need.style('border-color',needcol)
inputs[0].need.attribute('disabled', '')
inputs[0].name.size(100)
inputs[0].name.position(imarg+50,jrow-20)
inputs[0].name.input(updateName)
inputs[0].name.name = '0' // The name of the input box will be its index into the inputs array
inputs[0].name.style('background-color',inputbackcol)
inputs[0].name.style('color',color(255,255,255))
inputs[0].name.style('border-style','solid')
inputs[0].name.style('border-color',namecol)
inputs[0].minnum.size(30)
inputs[0].minnum.position(imarg+170,jrow-20)
inputs[0].minnum.input(updateMinNum)
inputs[0].minnum.name = '0' // The name of the input box will be its index into the inputs array
inputs[0].minnum.value(1)
inputs[0].minnum.style('background-color',inputbackcol)
inputs[0].minnum.style('color',color(255,255,255))
inputs[0].minnum.style('border-style','solid')
inputs[0].minnum.style('border-color',mincol)
inputs[0].minnum.attribute('disabled', '')
} // setup
function draw() {
background(20)
// Write app name:
textAlign(LEFT)
textSize(20)
fill(120)
noStroke()
textStyle(ITALIC)
text("Crafting Calculator",imarg+35,windowHeight-40)
textStyle(NORMAL)
strokeWeight(8)
stroke(120)
line(-5,windowHeight-46,imarg+20,windowHeight-46)
line(367,windowHeight-46,windowWidth+5,windowHeight-46)
strokeCap(SQUARE)
stroke(needcol)
line(-5,windowHeight-46,25,windowHeight-46)
stroke(mincol)
line(25,windowHeight-46,50,windowHeight-46)
strokeCap(ROUND)
strokeWeight(1)
noStroke()
fill(200)
textAlign(RIGHT)
textSize(13)
text("number",imarg+35,45)
text("needed",imarg+35,63)
textAlign(CENTER)
text("product",imarg+100,45)
text("name",imarg+100,63)
textAlign(LEFT)
text("minimum number",imarg+170,45)
text("from one recipe",imarg+170,63)
// Draw all the input structures, buttons, and connecting lines:
drawTree()
// Check for button presses:
if (imatch != -1) {
// User has pressed an arrow button.
if (imatch < arrows.length) {
// Find the input structure belonging to the clicked arrow button:
i0 = arrows[imatch].input
// Retrieve the direction of the arrow on the button:
let dir = arrows[imatch].type
if (dir == 'u') {
// Clicking on an up arrow means we need to hide the ingredients belonging to the arrow's structure:
for (let i=0; i<inputs[i0].children.length; i++) {inputs[inputs[i0].children[i]].visible = false}
// Switch the arrow button to a down arrow:
arrows[imatch].type = 'd'
} else {
// Clicking on a down arrow means we need to show the ingredients belonging to the arrow's structure:
for (let i=0; i<inputs[i0].children.length; i++) {inputs[inputs[i0].children[i]].visible = true}
// Switch the arrow button to an up arrow:
arrows[imatch].type = 'u'
}
} else if (imatch < arrows.length + plus.length) {
// User has pressed a plus button.
// Determine what input structure the button belongs to. This is the parent product. Note that we don't
// have to search inputs for the entry with the correct pointer to the plus array, because every input
// structure has a plus sign (even if it's hidden), so the indices should always match.
let p = plus[imatch-arrows.length].input
// Determine the level of the new inputs structure that needs to be created:
let lev = inputs[p].level + 1
// Make the new structure:
inputs.push({name: createInput(''), need: createInput(''), minnum: createInput(''), level: lev,
product: -1, parent: p, children: [],
arrow: -1, plus: 0, delete: 0,
visible: true})
let newi = inputs.length - 1
inputs[newi].need.size(30)
inputs[newi].need.position(imarg*(1+lev),joff+jrow*(1+newi)-20)
inputs[newi].need.input(updateNumNeeded)
inputs[newi].need.name = str(newi) // The name of the input box will be its index into the inputs array
inputs[newi].need.value(1)
inputs[newi].need.style('background-color',inputbackcol)
inputs[newi].need.style('color',color(255,255,255))
inputs[newi].need.style('border-style','solid')
inputs[newi].need.style('border-color',needcol)
inputs[newi].need.attribute('disabled', '')
inputs[newi].name.size(100)
inputs[newi].name.position(imarg*(1+lev)+50,joff+jrow*(1+newi)-20)
inputs[newi].name.input(updateName)
inputs[newi].name.name = str(newi) // The name of the input box will be its index into the inputs array
inputs[newi].name.elt.focus()
inputs[newi].name.style('background-color',inputbackcol)
inputs[newi].name.style('color',color(255,255,255))
inputs[newi].name.style('border-style','solid')
inputs[newi].name.style('border-color',namecol)
inputs[newi].minnum.size(30)
inputs[newi].minnum.position(imarg*(1+lev)+200,joff+jrow*(1+newi)-20)
inputs[newi].minnum.input(updateMinNum)
inputs[newi].minnum.name = str(newi) // The name of the input box will be its index into the inputs array
inputs[newi].minnum.value(1)
inputs[newi].minnum.style('background-color',inputbackcol)
inputs[newi].minnum.style('color',color(255,255,255))
inputs[newi].minnum.style('border-style','solid')
inputs[newi].minnum.style('border-color',mincol)
inputs[newi].minnum.attribute('disabled', '')
// Make new plus button:
plus.push(new SymbolButton(imarg*(1+lev)+70,joff+jrow*(1+newi)+2,30,15,
'+',arrowcol,buttonstroke,buttonfill,false,newi))
inputs[newi].plus = plus.length - 1
// Make new delete button:
deletes.push(new SymbolButton(imarg*(1+lev)+220,joff+jrow*(1+newi)-17,15,15,'x',160,-1,buttonfill,true,newi))
inputs[newi].delete = deletes.length - 1
// Determine if the parent product has an arrow button. If not, make one, since the parent now has ingredients:
if (inputs[p].arrow == -1) {
arrows.push(new SymbolButton(imarg*lev+100,joff+jrow*(1+p)+2,30,15,'u',arrowcol,buttonstroke,buttonfill,true,p))
inputs[p].arrow = arrows.length - 1
}
// Add the new input structure's index to the parent's children list:
inputs[p].children.push(newi)
} else {
// User has clicked a delete button.
let d = deletes[imatch-arrows.length-plus.length].input
deleteInput(d)
} // if (imatch < ...
// Switch off the trigger for creating a new input structure.
imatch = -1
} // if (imatch != -1...
// Calculate how much of each product will be needed:
calculateProducts()
if (debug) printProducts()
} // draw()
function drawTree() {
// Draw all input boxes and associated buttons.
// Determine the correct position of each input structure based on parent-child relationships and
// whether subtrees are hidden.
orderInputs(0)
// Set input structure positions based on the ordered list:
for (let i=0; i<ordinputs.length; i++) {
let lev = inputs[ordinputs[i]].level
inputs[ordinputs[i]].need.position(imarg*(1+lev),joff+jrow*(1+i)-20)
inputs[ordinputs[i]].name.position(imarg*(1+lev)+50,joff+jrow*(1+i)-20)
inputs[ordinputs[i]].minnum.position(imarg*(1+lev)+170,joff+jrow*(1+i)-20)
let p = inputs[ordinputs[i]].plus
plus[p].x = imarg*(1+lev)+70
plus[p].y = joff+jrow*(1+i)+2
let d = inputs[ordinputs[i]].delete
if (d >= 0) {
deletes[d].x = imarg*(1+lev)+220
deletes[d].y = joff+jrow*(1+i)-17
}
let a = inputs[ordinputs[i]].arrow
if (a >= 0) {
arrows[a].x = plus[p].x+plus[p].dx
arrows[a].y = joff+jrow*(1+i)+2
}
}
// Do the actual drawing of all visible stuff, in the right order:
// First, for later, make a list that will record which input structures should NOT be plotted.
// This will start out as an array of the same length as inputs, with all elements set to -1,
// indicating they should be hidden. As we work through the list of visible elements, we'll set
// their entries in hiddenlist to 1.
let hiddenlist = []
for (let i=0; i<inputs.length; i++) hiddenlist.push(-1)
for (let i=0; i<ordinputs.length; i++) {
let ii = ordinputs[i]
// Draw a background line connecting the three input boxes. First, figure out where the line
// has to start. If the input box has a parent, the line must start at the x value corresponding
// to the parent's arrow button.
let x0 = inputs[ii].need.x+5
let yv0 = inputs[ii].minnum.y+0.5*21
if (inputs[ii].parent != -1) {
let par = inputs[ii].parent
x0 = arrows[inputs[par].arrow].x+0.5*arrows[inputs[par].arrow].dx
yv0 = inputs[par].name.y+5
}
stroke(namecol)
strokeWeight(8)
// Horizontal line:
line(x0,inputs[ii].need.y+0.5*21,
inputs[ii].minnum.x+inputs[ii].minnum.width-5,inputs[ii].minnum.y+0.5*21)
// Vertical line:
line(x0,yv0,x0,inputs[ii].minnum.y+0.5*21)
strokeWeight(1)
hiddenlist[ii] = 1
inputs[ii].name.show()
inputs[ii].need.show()
inputs[ii].minnum.show()
plus[inputs[ii].plus].draw()
if (inputs[ii].delete != -1) deletes[inputs[ii].delete].draw()
}
// Draw all arrow buttons last, so that they cover the lines:
for (let i=0; i<ordinputs.length; i++) {
let ii = ordinputs[i]
if (inputs[ii].arrow != -1) {
if (arrows[inputs[ii].arrow].visible) arrows[inputs[ii].arrow].draw()
}
}
// Hide any input box that is not in the ordinputs list, i.e. still has a value of -1 in the hiddenlist array:
for (let i=0; i<hiddenlist.length; i++) {
if (hiddenlist[i] == -1) {
inputs[i].name.hide()
inputs[i].need.hide()
inputs[i].minnum.hide()
}
}
}
function orderInputs(i0) {
// Make an ordered list of input structures for drawing, based on parent-child relationships.
if (i0 == 0) ordinputs = []
if (inputs[i0].visible) {
ordinputs.push(i0)
for (let i=0; i<inputs[i0].children.length; i++) {
orderInputs(inputs[i0].children[i])
}
}
}
function updateProduct() {
// Gets called when user has clicked updated a product name then clicked away from it.
// Retrieve the product name:
pname = inputs[activename].name.value()
// Determine if the updated product name already exists in the products list:
let ihit = -1
for (let i=0; i<products.length; i++) {
if (products[i].name == pname) ihit = i
}
if (ihit != -1) {
// If we found a match, update the product field for the inputs array entry:
inputs[activename].product = ihit
// Set the minimum number of products made in its recipe to the correct value:
inputs[activename].minnum.value(products[ihit].num)
// See if this product node needs an arrow button, i.e. if it has a list of ingredients:
if (products[ihit].ingred.length > 0) {
// Yes, it does. See if an arrow button already exists at this node:
if (inputs[activename].arrow == -1) {
// No, an arrow button does not exist, so make a new one:
arrows.push(new SymbolButton(imarg*(1+inputs[activename].level)+100,joff+jrow*(1+activename)+2,30,15,
'd',arrowcol,buttonstroke,buttonfill,activename))
// Update the input's structure to point to the new button:
inputs[activename].arrow = arrows.length - 1
}
}
} else {
// We have not found a match, so this is a new product. Add it to the products list. We assume
// the user has not yet typed the min number made by the product's recipe, so we set it provisionally
// to 1. Likewise, the ingredients, if any, have not been typed yet, so we keep the list empty.
products.push(new Product(pname,inputs[activename].minnum.value(),[]))
// Update the input structure's pointer to product index:
inputs[activename].product = products.length-1
}
// Updating a name means an ingredient in a recipe has likely changed. The easiest thing to do is just
// reconstruct the entire ingredient list from scratch. We know which input structures contain the ingredients'
// information: they're the input structures listed in the parent input structure's children array. All we have
// to do is work through this array, looking up what product each ingredient is.
//
// Find the index of the parent's input structure entry:
let ipar = inputs[activename].parent
if (ipar != -1 ) {
// Find the product associated with the parent input structure:
let iparprd = inputs[ipar].product
// Empty the parent product's ingredient list:
products[iparprd].ingred = []
// Reconstruct the list using the parent's children array:
for (let i=0; i<inputs[ipar].children.length; i++) {
// Child inputs id:
let ich = inputs[ipar].children[i]
// Product associated with child:
let ichprd = inputs[ich].product
// Add the ingredient:
products[iparprd].ingred.push({id: ichprd, need: inputs[ich].need.value()})
}
}
} // updateProduct
function deleteInput(idel) {
// Delete an input structure from the inputs array. This really just means setting its visibility
// flag to false and setting its product index to -1.
inputs[idel].visible = false
// Hide the input's buttons to avoid wrong index matches in button press searches:
plus[inputs[idel].plus].visible = false
let a = inputs[idel].arrow
if (a != -1) arrows[a].visible = false
let d = inputs[idel].delete
if (d != -1) deletes[d].visible = false
// Set the number needed to zero, so that the input does not feature in the calculations:
inputs[idel].need.value(0)
// Delete the ingredient from the ingredients list in the products array.
// First, find the parent for the deleted ingredient:
let ipar = inputs[idel].parent
// Now find the products index for the parent input structure:
let prod = inputs[ipar].product
// Search through the ingredients list and delete the deleted ingredient:
for (let i=0; i<products[prod].ingred.length; i++) {
if (products[prod].ingred[i].id == inputs[idel].product) products[prod].ingred.splice(i,1)
}
// Delete the ingredient from its parent input structure's children array:
for (let i=0; i<inputs[ipar].children.length; i++) {
if (inputs[ipar].children[i] == idel) inputs[ipar].children.splice(i,1)
}
}
function calculateProducts() {
// Calculate how much of each basic product is needed for the root recipe.
// Draw dividing line:
stroke(70)
line(0.75*windowWidth,0.1*windowHeight,0.75*windowWidth,0.85*windowHeight)
// Compute how many runs of each recipe are needed, and store in an array of equal size to inputs. Each
// input structure can be regarded as having a recipe: Either it has a genuine recipe with a list of
// ingredients specified by its children list, or it is a "recipe of one": it is it's own ingredient.
//textSize(15)
//noStroke()
//fill(200)
//let i0=0
//text("=== numrecip:",500,80)
if (products.length > 0) {
let numrecip = []
// Fill the array with zeroes:
for (let i=0; i<inputs.length; i++) {numrecip.push(0)}
for (let i=0; i<inputs.length; i++) {
// Don't include brand new input structures whose product fields have not been set, or input
// structures whose minnum is currently blank (zero):
if ((inputs[i].product != -1)&&(inputs[i].minnum.value()>0)) {
numrecip[i] = ceil(inputs[i].need.value()/inputs[i].minnum.value())
//text(products[inputs[i].product].name+": "+numrecip[i],500,100+i0*18)
//i0++
}
}
// Determine how many times each recipe needs to be run. This is the product of the main product's numrecip
// value and its parent's numrecip value and that parent's parent's numrecip value, etc.
let totn = []
for (let i=0; i<inputs.length; i++) {
totn.push(numrecip[i])
let par = inputs[i].parent
let safety = 0
while ((par != -1)&&(safety < 100)) {
totn[i] *= numrecip[par]
par = inputs[par].parent
safety++
}
if (safety == 100) print("ERROR: Infinite loop in calculateProducts")
}
// Next, multiply the total number of recipes run with the minimum number of items produced per run:
let subtot = []
for (let i=0; i<inputs.length; i++) {
// Slight iffy thing happening here, which I think is harmless for now: A brand new input structure will
// be a member of the inputs array, but its name will not have been defined yet, nor its numbers correctly
// set. As a result, it's subtot value will be meaningless. That's OK, since such a value will not be
// extracted for use in the next and final part of the calculation.
subtot.push(totn[i]*inputs[i].minnum.value())
}
// Now link all the input structure subtotals with actual products:
let tottot = [] // This time, the array will have the same length as the products array.
// Fill the array with zeroes:
for (let i=0; i<products.length; i++) {tottot.push(0)}
// Go through each input and determine which product's tally it should contribute to:
for (let i=0; i<inputs.length; i++) {
let prod = inputs[i].product
if (prod != -1) {
tottot[prod] += subtot[i] // Add the subtotal from the current input structure to its parent product total.
}
}
// Print the results.
// Determine number of non-zero entries, for text positioning purposes:
let nonzero = 0
for (let i=0; i<products.length; i++) {
if (tottot[i] > 0) nonzero++
}
let dj = 18
noStroke()
fill(175)
textSize(13)
// Write prompt:
textAlign(CENTER)
text("R E S U L T S",0.875*windowWidth,40)
textAlign(LEFT)
text("Basic ingredients are ",0.875*windowWidth-90,40+1.1*dj)
fill(255)
text("highlighted ",0.875*windowWidth+37,40+1.1*dj)
textSize(15)
let ii=0
for (let i=0; i<products.length; i++) {
if (tottot[i] > 0) {
// Determine if this is a basic product, so we can emphasize it:
let basic = false
if (products[i].ingred.length == 0) basic = true
if (basic == true) {
fill(255)
} else fill(175)
textAlign(RIGHT)
let jval = 0.5*windowHeight-(0.5*nonzero-ii)*dj+0.5*dj
text(products[i].name,0.875*windowWidth-10,jval)
textAlign(CENTER)
text(":",0.875*windowWidth,jval)
textAlign(LEFT)
text(tottot[i],0.875*windowWidth+10,jval)
ii++
}
}
}
}
function printProducts() {
// For debugging purposes. Prints out each product with its list of ingredients and associated numbers.
let iind = 50
let jr = 27
let jj = 0
let j0 = 300
textSize(15)
textAlign(LEFT)
fill(255)
noStroke()
for (let i=0; i<products.length; i++) {
text(products[i].name,700,j0+jj*jr)
text(" can make no fewer than: "+products[i].num,700+iind,j0+jj*jr)
jj++
for (let j=0; j<products[i].ingred.length; j++) {
let id = products[i].ingred[j].id
if (id != -1) text(products[id].name,700+iind,j0+jj*jr)
text(" need "+products[i].ingred[j].need,700+2*iind,j0+jj*jr)
if (id != -1) text(" can make no fewer than: "+products[id].num,700+2*iind+100,j0+jj*jr)
jj++
}
}
j0 += jj*jr
for (let j=0; j<inputs.length; j++) {
let p = inputs[j].product
if ((p > -1)&&(products.length>0)) {
text("input for "+products[p].name+" has visible = "+inputs[j].visible,700,j0+j*jr)
}
}
} // printProducts
// ========== START: MOUSE LISTENERS
function mouseReleased() {
// Check through each plus, arrow, and delete button for a click:
imatch = -1
let count = 0
let alength = arrows.length
let plength = plus.length
let dlength = deletes.length
while ((count < alength + plength + dlength) && (imatch == -1)) {
if (count < alength) {
// Look for arrow button presses:
if (arrows[count].mouseHovering()) imatch = count
} else if (count < alength + plength) {
// Look for plus button presses:
if (plus[count-alength].mouseHovering()) {
imatch = count
}
} else {
if (deletes[count-alength-plength].mouseHovering()) {
imatch = count
}
}
count++
} // while
// Check if the user has clicked outside a product name input box that was being edited: This
// is a signal to update the product information.
if (activename != -1) {
// The following statement compares the currently active HTML element to the most recently edited name
// input box. If they're not the same thing, the user has clicked away:
if (document.activeElement != inputs[activename].name.elt) {
updateProduct()
activename = -1 // Reset the activename flag
} // if (document.activeElement...
} // if (activename...
} // mouse
// ========== END: MOUSE LISTENERS
// ========== START: INPUT BOX LISTENERS
function updateName() {
// Update the input box structure information to reflect the name typed into a product name input box.
// Get the index of this input box into the inputs array and assign it to the activename variable:
activename = int(this.name)
// Get the plus button for this input structure:
let ip = inputs[activename].plus
// Show the plus button for this input structure:
plus[ip].visible = true
// Activate the input structure's need and minnum input boxes:
inputs[activename].need.removeAttribute('disabled')
inputs[activename].minnum.removeAttribute('disabled')
} // updateName()
function updateMinNum() {
// Update the input box structure information to reflect the number typed into a product's minimum-number-produced
// input box.
// Get the index of this input box into the inputs array:
let ii = int(this.name)
// Find the product for this input box:
let p = inputs[ii].product
// Update the products array:
if (products.length > 0) {
products[p].num = this.value()
// Update other input boxes that represent this product (the product may be an ingredient in multiple recipes):
for (let i=0; i<inputs.length; i++) {
if ((i != ii)&&(inputs[i].product == p)) inputs[i].minnum.value(this.value())
}
}
} // updateMinNum()
function updateNumNeeded() {
// Called when the number of items needed for a product is updated.
// Get the index of this input box into the inputs array:
let ii = int(this.name)
// Find the product for this input box:
let p = inputs[ii].product
// Find the parent input structure of this product:
let par = inputs[ii].parent
if (par != -1) {
// Find the product associated with the parent:
let parprod = inputs[par].product
// Look for the ingredient in the parent's ingredient list:
ihit = -1
for (let i=0; i<products[parprod].ingred.length; i++) {
if (products[parprod].ingred[i].id == p) ihit = i
}
if (ihit == -1) {
print("ERROR in updateNumNeeded: input box's product is not an ingredient in its parent's recipe.")
} else {
// Add the product's needed value to its parent's ingredient list:
products[parprod].ingred[ihit].need = this.value()
}
}
} // updateNumNeeded()
// ========== END: INPUT BOX LISTENERS
// ========== START: CLASS DEFINITIONS
class Product {
constructor(newname, newnum, newingred) {
// newname: text string describing the product
// newnum: the minimum number of the product that is made with its recipe. If this is a base product,
// newnum will be 1.
// newingred: a list of structures specifying the ingredients needed to make newnum of the product.
// Each element in the list should have two fields: 'id', which is the id of the ingredient, namely
// the index into the products array that points to the ingredient, and 'need', which is the number
// of the ingredient required to make newnum of the product.
this.name = newname
this.num = newnum
this.ingred = newingred
}
}
class SymbolButton {
constructor(newx,newy,newdx,newdy,newtype,newarrowcol,newbordercol,newfillcol,newvisib,newinput) {
// newtype can be 'u', 'd', '+', or 'x' for an up arrow, down arrow, plus sign, or deletion cross, respectively.
this.x = newx // x-coordinate of top left corner
this.y = newy // y-coordinate of top left corner
this.dx = newdx
this.dy = newdy
this.type = newtype
this.arrowcol = newarrowcol
this.bordercol = newbordercol // button border color
this.fillcol = newfillcol // button fill color
this.visible = newvisib
this.input = newinput
}
draw() { // SymbolButton class
if (this.visible == true) {
if (this.mouseHovering()) {
this.fillcol = 60
} else {
this.fillcol = 30
}
if (this.bordercol != -1) {
stroke(this.bordercol)
} else noStroke()
fill(this.fillcol)
rect(this.x,this.y,this.dx,this.dy)
stroke(this.arrowcol)
noFill()
let halflen
switch (this.type) {
case 'u':
line(this.x+0.2*this.dx,this.y+0.8*this.dy,this.x+0.5*this.dx,this.y+0.2*this.dy)
line(this.x+0.5*this.dx,this.y+0.2*this.dy,this.x+0.8*this.dx,this.y+0.8*this.dy)
break
case 'd':
line(this.x+0.2*this.dx,this.y+0.2*this.dy,this.x+0.5*this.dx,this.y+0.8*this.dy)
line(this.x+0.5*this.dx,this.y+0.8*this.dy,this.x+0.8*this.dx,this.y+0.2*this.dy)
break
case '+':
halflen = 0.4*min(this.dx,this.dy)
line(this.x+0.5*this.dx-halflen,this.y+0.5*this.dy, this.x+0.5*this.dx+halflen,this.y+0.5*this.dy)
line(this.x+0.5*this.dx, this.y+0.5*this.dy-halflen,this.x+0.5*this.dx, this.y+0.5*this.dy+halflen)
break
case 'x':
halflen = 0.4*min(this.dx,this.dy)
line(this.x+0.5*this.dx-halflen,this.y+0.5*this.dy-halflen,this.x+0.5*this.dx+halflen,this.y+0.5*this.dy+halflen)
line(this.x+0.5*this.dx-halflen,this.y+0.5*this.dy+halflen,this.x+0.5*this.dx+halflen,this.y+0.5*this.dy-halflen)
break
default: // do nothing
} // switch
} // if (this.visible...
} // draw()
mouseHovering() {
// Return true if the mouse is currently hovering over the button:
if (this.visible) {
if ((mouseX > this.x) && (mouseY > this.y) &&
(mouseX < this.x + this.dx) && (mouseY < this.y + this.dy)) return true
}
return false
}
} // SymbolButton
class TextButton {
constructor(newx,newy,newdx,newdy,newtext,newtextcol,newbordercol,newfillcol,newvisib) {
this.x = newx // x-coordinate of top left corner
this.y = newy // y-coordinate of top left corner
this.dx = newdx
this.dy = newdy
this.text = newtext
this.textcol = newtextcol
this.bordercol = newbordercol // button border color
this.fillcol = newfillcol // button fill color
this.visible = newvisib
}
draw() { // TextButton class
if (this.visible == true) {
if (this.mouseHovering()) {
this.fillcol = buttonhigh
} else {
this.fillcol = buttonfill
}
if (this.bordercol != -1) {
stroke(this.bordercol)
} else noStroke()
fill(this.fillcol)
rect(this.x,this.y,this.dx,this.dy)
noStroke()
fill(this.textcol)
textSize(0.6*this.dy)
textAlign(CENTER)
text(this.text,this.x + 0.5*this.dx,this.y + 0.8*this.dy)
} // if (this.visible...
} // draw()
mouseHovering() {
// Return true if the mouse is currently hovering over the button:
if ((mouseX > this.x) && (mouseY > this.y) &&
(mouseX < this.x + this.dx) && (mouseY < this.y + this.dy)) return true
return false
}
} // TextButton
// ========== END: CLASS DEFINITIONS