setup()
setup() runs once when the sketch loads. Here it builds the entire calculator UI by creating p5 DOM elements and attaching event listeners to each button. The touch debounce pattern is crucial for mobile—without it, a single tap would trigger both the touch event AND a simulated mouse event, causing calculations to run twice.
🔬 This array defines the first row of buttons. What happens if you remove one line, like the division operator? Or add a new operator like '%' for modulo (remainder)?
const buttonValues = [
{ value: '+', class: 'operator' },
{ value: '-', class: 'operator' },
{ value: '*', class: 'operator', html: '×' },
{ value: '/', class: 'operator', html: '÷' },
function setup() {
createCanvas(0, 0);
// Create the main calculator container
calculatorDiv = createDiv();
calculatorDiv.addClass('calculator');
// Create the custom title
titleElement = createDiv('corbuns calculator');
titleElement.addClass('calculator-title');
calculatorDiv.child(titleElement);
// Create the display element
displayElement = createDiv(displayValue);
displayElement.addClass('calculator-display');
calculatorDiv.child(displayElement);
// Create the keys container
let keysDiv = createDiv();
keysDiv.addClass('calculator-keys');
calculatorDiv.child(keysDiv);
// Define the button layout and their values
const buttonValues = [
{ value: '+', class: 'operator' },
{ value: '-', class: 'operator' },
{ value: '*', class: 'operator', html: '×' },
{ value: '/', class: 'operator', html: '÷' },
{ value: '7', class: '' },
{ value: '8', class: '' },
{ value: '9', class: '' },
{ value: '4', class: '' },
{ value: '5', class: '' },
{ value: '6', class: '' },
{ value: '1', class: '' },
{ value: '2', class: '' },
{ value: '3', class: '' },
{ value: '0', class: '' },
{ value: '.', class: 'decimal' },
{ value: 'clear', class: 'all-clear', html: 'AC' },
{ value: '=', class: 'equal-sign' }
];
// Create buttons and add them to the keys container
buttonValues.forEach(btnData => {
let button = createButton(btnData.html || btnData.value);
button.addClass(btnData.class);
button.elt.value = btnData.value;
keysDiv.child(button);
// Attach event listeners
// Handle mouse presses, but ignore if a touch has just occurred
button.mousePressed(() => {
if (touchHandled) {
touchHandled = false; // Reset flag for next interaction
return; // Ignore this mouse event, it's likely a simulated one
}
handleButtonPress(button.elt.value);
});
// Handle touch starts and set a flag
button.touchStarted(() => {
touchHandled = true; // Set flag to indicate a touch has occurred
handleButtonPress(button.elt.value);
// Reset the flag after a short delay to allow subsequent mouse events
// but still block the immediate simulated mouse event.
setTimeout(() => {
touchHandled = false;
}, 300); // 300ms is a common debounce time
});
// Store references for specific button types if needed
if (btnData.value === 'clear') allClearButton = button;
else if (btnData.value === '=') equalSignButton = button;
else if (btnData.value === '.') decimalButton = button;
else if (!isNaN(btnData.value)) numberButtons.push(button);
else if (['+', '-', '*', '/'].includes(btnData.value)) operatorButtons.push(button);
});
// Initial display update
updateDisplay();
}
Line-by-line explanation (17 lines)
🔧 Subcomponents:
buttonValues.forEach(btnData => {
Iterates through all 16 button definitions and creates corresponding DOM elements with event listeners
if (touchHandled) {
Prevents double-firing when a touch event has just occurred by ignoring the simulated mouse event
setTimeout(() => {
touchHandled = false;
}, 300);
Sets a 300ms delay before allowing the next mouse event to fire, blocking the browser's simulated click
createCanvas(0, 0);- Creates a hidden canvas with 0×0 size—p5.js requires a canvas even though we're using DOM elements instead
calculatorDiv = createDiv();- Creates the main container div that will hold all calculator elements
calculatorDiv.addClass('calculator');- Adds the CSS class 'calculator' to style the container with grid layout and dark background
const buttonValues = [- Defines an array of objects containing button data: value (what gets calculated), CSS class, and optional HTML (like × for multiply)
buttonValues.forEach(btnData => {- Loops through each button definition and creates a p5 button element for it
let button = createButton(btnData.html || btnData.value);- Creates a p5 button; uses the html property if it exists (for display symbols like ×), otherwise uses the value
button.elt.value = btnData.value;- Stores the actual button value in the DOM element's value property so we know what operation to perform when clicked
button.mousePressed(() => {- Attaches a mouse event listener that fires when the button is clicked
if (touchHandled) {- Checks if a touch event just fired; if so, ignores this mouse event to prevent double-calculation
handleButtonPress(button.elt.value);- Calls the main button handler with the button's value, routing to the correct calculator function
button.touchStarted(() => {- Attaches a touch event listener that fires when the button is touched on mobile
touchHandled = true;- Sets the flag to true so the next simulated mouse event will be blocked
setTimeout(() => {- Schedules a function to run after a delay; used here to reset the touch flag after 300ms
}, 300);- The delay in milliseconds—300ms is long enough to block the simulated mouse event but short enough that real mouse clicks work normally
if (btnData.value === 'clear') allClearButton = button;- Stores a reference to the clear button in a global variable for potential future use
else if (!isNaN(btnData.value)) numberButtons.push(button);- Uses isNaN() to check if the value is a number; if so, adds the button to the numberButtons array
updateDisplay();- Syncs the initial displayValue ('0') to the DOM, showing it on the calculator screen