Detecting Transitions
Detecting Transitions
This tutorial shows how to detect when the mouse enters a rectangle — a state transition rather than a state. It progresses from simple boolean tracking to classes and arrays, building up to detecting transitions across an arbitrary number of buttons.
Use the ◀ ▶ buttons or arrow keys to step through the stages. Move the mouse over the canvas to interact. Drag any number in the code to adjust it live.
We want to tell when the mouse enters a rectangle. This involves looking for a transition. We want to detect when it goes from not being inside the rectangle, to being inside the rectangle.
Here’s the setup. Draw a green rectangle. This is the button. We’re going to detect whether the mouse is inside of it.
Draw crosshairs around the mouse position. This isn’t strictly necessary (the user can just look at the mouse cursor), but I find it helpful to have something on the canvas that reflects the variables that I’m working with, just to check that the basic “plumbing” is working.
let buttonX = 100;
let buttonY = 100;
let buttonWidth = 200;
let buttonHeight = 50;
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(100);
// draw a big button
noStroke();
fill("lightgreen");
rect(buttonX, buttonY, buttonWidth, buttonHeight);
// draw crosshairs to highlight the mouse position
noFill();
stroke("blue");
strokeWeight(4);
line(mouseX - 10, mouseY, mouseX + 10, mouseY);
line(mouseX, mouseY - 10, mouseX, mouseY + 10);
} Before we get started on mouse detection, refactor the code that draws the big button, and the code that draws the crosshairs, into their own functions. This makes draw() very simple, so that it will be easier to work on, and to see what happens next.
(We are about to make draw() more complicated again by adding code that detects when it is touching the button, so it is helpful to simplify it first.)
Note that the comments in the previous step are no longer necessary, because the function names do the same work of documenting the intent of the “low-level” noStroke(), fill(), rect() etc. functions.
let buttonX = 100;
let buttonY = 100;
let buttonWidth = 200;
let buttonHeight = 50;
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(100);
bigButton();
crosshairs(mouseX, mouseY);
}
function bigButton() {
noStroke();
fill("lightgreen");
rect(buttonX, buttonY, buttonWidth, buttonHeight);
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} Display “Touched!” above the rectangle, when the rectangle is being touched.
This isn’t what we want – we want to know when the rectangle is first touched, not when it is still being touched – but it is a step along the way.
let buttonX = 100;
let buttonY = 100;
let buttonWidth = 200;
let buttonHeight = 50;
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(100);
bigButton();
crosshairs(mouseX, mouseY);
if (buttonX <= mouseX && mouseX < buttonX + buttonWidth &&
buttonY <= mouseY && mouseY < buttonY + buttonHeight) {
fill("white");
noStroke();
textAlign(LEFT, BOTTOM);
textSize(20);
text("Touching!", buttonX, buttonY);
}
}
function bigButton() {
noStroke();
fill("lightgreen");
rect(buttonX, buttonY, buttonWidth, buttonHeight);
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} Factor a couple more functions out of draw(), to keep draw() simple.
let buttonX = 100;
let buttonY = 100;
let buttonWidth = 200;
let buttonHeight = 50;
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(100);
bigButton();
crosshairs(mouseX, mouseY);
if (testPointInRect(mouseX, mouseY, buttonX, buttonY, buttonWidth, buttonHeight)) {
labelAboveRect("Touching!");
}
}
function testPointInRect(x, y, left, top, width, height) {
if (left <= x && x < left + width &&
top <= y && y < top + height) {
return true;
}
return false;
}
function labelAboveRect(str) {
fill("white");
noStroke();
textAlign(LEFT, BOTTOM);
textSize(20);
text(str, buttonX, buttonY);
}
function bigButton() {
noStroke();
fill("lightgreen");
rect(buttonX, buttonY, buttonWidth, buttonHeight);
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} This step adds the specified functionality. It detects when the mouse is first moved into the rectangle. It does this by using a global variable (a variable whose lifespan is greater than any particular call to draw()) to record whether the mouse had previously entered the rectangle.
Move the mouse into the rectangle. You can see a flash of text “Touched!” during the single animation frame when the mouse first enters the rectangle. This text then disappears, because the mouse is no longer entering the rectangle for the first time.
This code only detects the first time that the mouse enters the rectangle. To get it to detect another entry, you need to run the program again.
let buttonX = 100;
let buttonY = 100;
let buttonWidth = 200;
let buttonHeight = 50;
let wasInRect = false;
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(100);
bigButton();
crosshairs(mouseX, mouseY);
if (testPointInRect(mouseX, mouseY, buttonX, buttonY, buttonWidth, buttonHeight) &&
wasInRect === false) {
labelAboveRect("Touched!");
wasInRect = true;
}
}
function testPointInRect(x, y, left, top, width, height) {
if (left <= x && x < left + width &&
top <= y && y < top + height) {
return true;
}
return false;
}
function labelAboveRect(str) {
fill("white");
noStroke();
textAlign(LEFT, BOTTOM);
textSize(20);
text(str, buttonX, buttonY);
}
function bigButton() {
noStroke();
fill("lightgreen");
rect(buttonX, buttonY, buttonWidth, buttonHeight);
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} The previous step code only detected the first time that the mouse enters the rectangle. To get it to detect another entry, you needed to run the program again.
To fix this, we can reset wasInRect when the mouse is not in the rectangle. Now, the code detects each time that the mouse moves from outside the rectangle, to inside.
let buttonX = 100;
let buttonY = 100;
let buttonWidth = 200;
let buttonHeight = 50;
let wasInRect = false;
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(100);
bigButton();
crosshairs(mouseX, mouseY);
if (testPointInRect(mouseX, mouseY, buttonX, buttonY, buttonWidth, buttonHeight)) {
if (wasInRect === false) {
labelAboveRect("Touched!");
}
wasInRect = true;
} else {
wasInRect = false;
}
}
function testPointInRect(x, y, left, top, width, height) {
if (left <= x && x < left + width &&
top <= y && y < top + height) {
return true;
}
return false;
}
function labelAboveRect(str) {
fill("white");
noStroke();
textAlign(LEFT, BOTTOM);
textSize(20);
text(str, buttonX, buttonY);
}
function bigButton() {
noStroke();
fill("lightgreen");
rect(buttonX, buttonY, buttonWidth, buttonHeight);
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} The code in the previous step works, but the nested if statements (the if statement inside the if statement) made it difficult to read.
This step is the first of two steps that demonstrates another way to write this. This step introduces another variable isInRect to record whether the mouse is inside the rectangle on this animation frame. It then compares isInRect to wasInRect. In the next step, we will see how this pays off…
let buttonX = 100;
let buttonY = 100;
let buttonWidth = 200;
let buttonHeight = 50;
let wasInRect = false;
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(100);
bigButton();
crosshairs(mouseX, mouseY);
let isInRect = testPointInRect(mouseX, mouseY, buttonX, buttonY, buttonWidth, buttonHeight);
if (isInRect !== wasInRect) {
if (wasInRect === false) {
labelAboveRect("Touched!");
}
wasInRect = true;
} else {
wasInRect = false;
}
}
function testPointInRect(x, y, left, top, width, height) {
if (left <= x && x < left + width &&
top <= y && y < top + height) {
return true;
}
return false;
}
function labelAboveRect(str) {
fill("white");
noStroke();
textAlign(LEFT, BOTTOM);
textSize(20);
text(str, buttonX, buttonY);
}
function bigButton() {
noStroke();
fill("lightgreen");
rect(buttonX, buttonY, buttonWidth, buttonHeight);
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} Now we can simply copy the current state of “mouse is in the rectangle” into the variable that records the previous state.
This technique also extends better to more sophisticated varieties of detection. We will see the first of these later in this tutorial.
- Detecting changes in a state that has more than two values – for example, which of several rectangles contains the mouse position; or, whether the mouse is in the left, center, or right side of the canvas
- Detecting rate of change
- Detecting changes that have to do with several previous states
This step also replaces “if (wasInRect === false)” by “if (isInRect === true)”. This is true under the same conditions (since this line is only executed when isInRect has the opposite value from wasInRect), but is a clearer statement of the intent. We could not do this before because we did not have access to a variable that told us directly whether the mouse is in the rectangle during this frame.
let buttonX = 100;
let buttonY = 100;
let buttonWidth = 200;
let buttonHeight = 50;
let wasInRect = false;
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(100);
bigButton();
crosshairs(mouseX, mouseY);
let isInRect = testPointInRect(mouseX, mouseY, buttonX, buttonY, buttonWidth, buttonHeight);
if (isInRect !== wasInRect) {
if (isInRect === true) {
labelAboveRect("Touched!");
}
}
wasInRect = isInRect;
}
function testPointInRect(x, y, left, top, width, height) {
if (left <= x && x < left + width &&
top <= y && y < top + height) {
return true;
}
return false;
}
function labelAboveRect(str) {
fill("white");
noStroke();
textAlign(LEFT, BOTTOM);
textSize(20);
text(str, buttonX, buttonY);
}
function bigButton() {
noStroke();
fill("lightgreen");
rect(buttonX, buttonY, buttonWidth, buttonHeight);
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} Here’s a couple of changes that demonstrate more “idiomatic” ways of working with boolean-valued variables. Even if you do not use these, you are likely to encounter code that does (such as this code!), so it is useful to have reading familiarity with them.
- Replace the nested if statement by the logical and operator (
&&). “if (condition1) { if (condition2) { … }}” can be replaced by “if (condition1 && condition2) { … }” - “
expression === true” can be replaced by “expression”, if you know that expression has one of the valuestrueandfalse. - “
if (expression) { return true; } else { return false; }” can be replaced by “return expression”, if you know that expression has one of the valuestrueandfalse.
let buttonX = 100;
let buttonY = 100;
let buttonWidth = 200;
let buttonHeight = 50;
let wasInRect = false;
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(100);
bigButton();
crosshairs(mouseX, mouseY);
let isInRect = testPointInRect(mouseX, mouseY, buttonX, buttonY, buttonWidth, buttonHeight);
if (isInRect !== wasInRect && isInRect) {
labelAboveRect("Touched!");
}
wasInRect = isInRect;
}
function testPointInRect(x, y, left, top, width, height) {
return left <= x && x < left + width &&
top <= y && y < top + height;
}
function labelAboveRect(str) {
fill("white");
noStroke();
textAlign(LEFT, BOTTOM);
textSize(20);
text(str, buttonX, buttonY);
}
function bigButton() {
noStroke();
fill("lightgreen");
rect(buttonX, buttonY, buttonWidth, buttonHeight);
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} In the next set of steps, we will add another rectangle. The mouse can relate to the two rectangles in one of three ways:
- It can be touching the first rectangle
- It can be touching the second rectangle
- It can be touching neither rectangle
We want to extend the sketch so that it detects when the mouse position changes between these three states.
First, rename buttonX etc. to make room in the variable namespace for the second rectangle.
let firstButtonX = 100;
let firstButtonY = 100;
let buttonWidth = 200;
let buttonHeight = 50;
let wasInRect = false;
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(100);
firstButton();
crosshairs(mouseX, mouseY);
let isInRect = testPointInRect(mouseX, mouseY, firstButtonX, firstButtonY, buttonWidth, buttonHeight);
if (isInRect !== wasInRect && isInRect) {
labelAboveRect("Touched!");
}
wasInRect = isInRect;
}
function testPointInRect(x, y, left, top, width, height) {
return left <= x && x < left + width &&
top <= y && y < top + height;
}
function labelAboveRect(str) {
fill("white");
noStroke();
textAlign(LEFT, BOTTOM);
textSize(20);
text(str, firstButtonX, firstButtonY);
}
function firstButton() {
noStroke();
fill("lightgreen");
rect(firstButtonX, firstButtonY, buttonWidth, buttonHeight);
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} Now draw the second rectangle.
We are not yet detecting when the mouse interacts with this rectangle.
let firstButtonX = 100;
let firstButtonY = 100;
let secondButtonX = 150;
let secondButtonY = 200;
let buttonWidth = 200;
let buttonHeight = 50;
let wasInRect = false;
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(100);
firstButton();
secondButton();
crosshairs(mouseX, mouseY);
let isInRect = testPointInRect(mouseX, mouseY, firstButtonX, firstButtonY, buttonWidth, buttonHeight);
if (isInRect !== wasInRect && isInRect) {
labelAboveRect("Touched!");
}
wasInRect = isInRect;
}
function testPointInRect(x, y, left, top, width, height) {
return left <= x && x < left + width &&
top <= y && y < top + height;
}
function labelAboveRect(str) {
fill("white");
noStroke();
textAlign(LEFT, BOTTOM);
textSize(20);
text(str, firstButtonX, firstButtonY);
}
function firstButton() {
noStroke();
fill("lightgreen");
rect(firstButtonX, firstButtonY, buttonWidth, buttonHeight);
}
function secondButton() {
noStroke();
fill("lightblue");
rect(secondButtonX, secondButtonY, buttonWidth, buttonHeight);
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} In preparation for recognizing the second rectangle, change wasInRect and isInRect from boolean values, to an optional numeric value.
In the previous steps, wasInRect and isInRect could take on the values false – to indicate the mouse is not in the rectangle – or true – to indicate that it is.
In this step, the (renamed) variables previousButtonIndex and buttonIndex take on the values null, to indicate that the mouse is not in the first rectangle, and 1, to indicate that the mouse is in the first rectangle.
I am thinking of each rectangle as having its own (1-based) index: 1, and 2.
The special JavaScript value null represents no value or none of the above. In this case we could have also used 0 to represent that no rectangle is being touched, but in general null is a better choice.
let firstButtonX = 100;
let firstButtonY = 100;
let secondButtonX = 150;
let secondButtonY = 200;
let buttonWidth = 200;
let buttonHeight = 50;
let previousButtonIndex = null;
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(100);
firstButton();
secondButton();
crosshairs(mouseX, mouseY);
let buttonIndex = null;
if (testPointInRect(mouseX, mouseY, firstButtonX, firstButtonY, buttonWidth, buttonHeight)) {
buttonIndex = 1;
}
if (buttonIndex !== previousButtonIndex && buttonIndex !== null) {
labelAboveRect("Touched!");
}
previousButtonIndex = buttonIndex;
}
function testPointInRect(x, y, left, top, width, height) {
return left <= x && x < left + width &&
top <= y && y < top + height;
}
function labelAboveRect(str) {
fill("white");
noStroke();
textAlign(LEFT, BOTTOM);
textSize(20);
text(str, firstButtonX, firstButtonY);
}
function firstButton() {
noStroke();
fill("lightgreen");
rect(firstButtonX, firstButtonY, buttonWidth, buttonHeight);
}
function secondButton() {
noStroke();
fill("lightblue");
rect(secondButtonX, secondButtonY, buttonWidth, buttonHeight);
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} Now add detection for the second rectangle.
After all the previous setup, this doesn’t require many changes:
- Lines 24–26 are a copy of lines 21–23, with
firstButtonXetc. replaced bysecondButtonX. This handles detection. - Line 28 has been modified to display which rectangle has been touched.
let firstButtonX = 100;
let firstButtonY = 100;
let secondButtonX = 150;
let secondButtonY = 200;
let buttonWidth = 200;
let buttonHeight = 50;
let previousButtonIndex = null;
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(100);
firstButton();
secondButton();
crosshairs(mouseX, mouseY);
let buttonIndex = null;
if (testPointInRect(mouseX, mouseY, firstButtonX, firstButtonY, buttonWidth, buttonHeight)) {
buttonIndex = 1;
}
if (testPointInRect(mouseX, mouseY, secondButtonX, secondButtonY, buttonWidth, buttonHeight)) {
buttonIndex = 2;
}
if (buttonIndex !== previousButtonIndex && buttonIndex !== null) {
labelAboveRect(`Touched button #${buttonIndex}!`);
}
previousButtonIndex = buttonIndex;
}
function testPointInRect(x, y, left, top, width, height) {
return left <= x && x < left + width &&
top <= y && y < top + height;
}
function labelAboveRect(str) {
fill("white");
noStroke();
textAlign(LEFT, BOTTOM);
textSize(20);
text(str, firstButtonX, firstButtonY);
}
function firstButton() {
noStroke();
fill("lightgreen");
rect(firstButtonX, firstButtonY, buttonWidth, buttonHeight);
}
function secondButton() {
noStroke();
fill("lightblue");
rect(secondButtonX, secondButtonY, buttonWidth, buttonHeight);
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} This step moves the rectangles so that they overlap. This allows us to test the case where the mouse transitions directly from one rectangle to another.
Something to watch out for: if we draw the second rectangle after the first rectangle (lines 16–17), we need to test the second rectangle after the first rectangle (lines 21–26). This ensures that when the mouse is over a region that includes both rectangles, the last value assigned to buttonIndex – and therefore the value that is used in line 27 – is the index of the last rectangle.
let firstButtonX = 100;
let firstButtonY = 100;
let secondButtonX = 150;
let secondButtonY = 125;
let buttonWidth = 200;
let buttonHeight = 50;
let previousButtonIndex = null;
function setup() {
createCanvas(windowWidth, windowHeight);
}
function draw() {
background(100);
firstButton();
secondButton();
crosshairs(mouseX, mouseY);
let buttonIndex = null;
if (testPointInRect(mouseX, mouseY, firstButtonX, firstButtonY, buttonWidth, buttonHeight)) {
buttonIndex = 1;
}
if (testPointInRect(mouseX, mouseY, secondButtonX, secondButtonY, buttonWidth, buttonHeight)) {
buttonIndex = 2;
}
if (buttonIndex !== previousButtonIndex && buttonIndex !== null) {
labelAboveRect(`Touched button #${buttonIndex}!`);
}
previousButtonIndex = buttonIndex;
}
function testPointInRect(x, y, left, top, width, height) {
return left <= x && x < left + width &&
top <= y && y < top + height;
}
function labelAboveRect(str) {
fill("white");
noStroke();
textAlign(LEFT, BOTTOM);
textSize(20);
text(str, firstButtonX, firstButtonY);
}
function firstButton() {
noStroke();
fill("lightgreen");
rect(firstButtonX, firstButtonY, buttonWidth, buttonHeight);
}
function secondButton() {
noStroke();
fill("lightblue");
rect(secondButtonX, secondButtonY, buttonWidth, buttonHeight);
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} This step moves the code that draws and tests the two rectangles into a RectButton class.
Note: These rectangle “buttons” are not related to values returned by the p5.js createButton() function.
let firstButtonX = 100;
let firstButtonY = 100;
let secondButtonX = 150;
let secondButtonY = 125;
let buttonWidth = 200;
let buttonHeight = 50;
let button1, button2;
let previousButtonIndex = null;
function setup() {
createCanvas(windowWidth, windowHeight);
button1 = new RectButton(firstButtonX, firstButtonY, buttonWidth, buttonHeight, "lightgreen");
button2 = new RectButton(secondButtonX, secondButtonY, buttonWidth, buttonHeight, "lightblue");
}
function draw() {
background(100);
button1.draw();
button2.draw();
crosshairs(mouseX, mouseY);
let buttonIndex = null;
if (button1.containsPoint(mouseX, mouseY)) {
buttonIndex = 1;
}
if (button2.containsPoint(mouseX, mouseY)) {
buttonIndex = 2;
}
if (buttonIndex !== previousButtonIndex && buttonIndex !== null) {
labelAboveRect(`Touched button #${buttonIndex}!`);
}
previousButtonIndex = buttonIndex;
}
function testPointInRect(x, y, left, top, width, height) {
return left <= x && x < left + width &&
top <= y && y < top + height;
}
function labelAboveRect(str) {
fill("white");
noStroke();
textAlign(LEFT, BOTTOM);
textSize(20);
text(str, firstButtonX, firstButtonY);
}
class RectButton {
constructor(x, y, width, height, fillColor) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.fillColor = fillColor;
}
draw() {
noStroke();
fill(this.fillColor);
rect(this.x, this.y, this.width, this.height);
}
containsPoint(x, y) {
return testPointInRect(x, y, this.x, this.y, this.width, this.height);
}
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} Each rectangle is now represented by a distinct value. These are the values of button1 and button2 – they are instances of RectButton.
The code can use these values instead of the button indices 1 and 2. This is a more direct representation of the meaning of those variables. Instead of using 1 to represent the first rectangle, the detection code uses the value of button1 to represent the first rectangle. This is the same value that represents the first rectangle elsewhere in the sketch (when it is drawn, and when its geometry is compared to the mouse position).
This step also renames labelAboveRect() to message(), and changes it to display the message at the top of the screen.
let firstButtonX = 100;
let firstButtonY = 100;
let secondButtonX = 150;
let secondButtonY = 125;
let buttonWidth = 200;
let buttonHeight = 50;
let button1, button2;
let previousButton = null;
function setup() {
createCanvas(windowWidth, windowHeight);
button1 = new RectButton(firstButtonX, firstButtonY, buttonWidth, buttonHeight, "lightgreen");
button2 = new RectButton(secondButtonX, secondButtonY, buttonWidth, buttonHeight, "lightblue");
button1.name = "button #1";
button2.name = "button #2";
}
function draw() {
background(100);
button1.draw();
button2.draw();
crosshairs(mouseX, mouseY);
let touchedButton = null;
if (button1.containsPoint(mouseX, mouseY)) {
touchedButton = button1;
}
if (button2.containsPoint(mouseX, mouseY)) {
touchedButton = button2;
}
if (touchedButton !== previousButton && touchedButton !== null) {
message(`Touched ${touchedButton.name}!`);
}
previousButton = touchedButton;
}
function testPointInRect(x, y, left, top, width, height) {
return left <= x && x < left + width &&
top <= y && y < top + height;
}
function message(str) {
fill("white");
noStroke();
textAlign(LEFT, TOP);
textSize(20);
text(str, 5, 5);
}
class RectButton {
constructor(x, y, width, height, fillColor) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.fillColor = fillColor;
}
draw() {
noStroke();
fill(this.fillColor);
rect(this.x, this.y, this.width, this.height);
}
containsPoint(x, y) {
return testPointInRect(x, y, this.x, this.y, this.width, this.height);
}
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} This also lets us replace the separate variables button1 and button2 by an Array of instances of RectButton.
This step also removes firstButtonX etc. – since their values are only used once – and uses their former values directly in the calls to new RectButton().
let buttonWidth = 200;
let buttonHeight = 50;
let buttons;
let previousButton = null;
function setup() {
createCanvas(windowWidth, windowHeight);
let button1 = new RectButton(100, 100, buttonWidth, buttonHeight, "lightgreen");
let button2 = new RectButton(150, 125, buttonWidth, buttonHeight, "lightblue");
button1.name = "button #1";
button2.name = "button #2";
buttons = [button1, button2];
}
function draw() {
background(100);
for (let button of buttons) {
button.draw();
}
crosshairs(mouseX, mouseY);
let touchedButton = null;
for (let button of buttons) {
if (button.containsPoint(mouseX, mouseY)) {
touchedButton = button;
}
}
if (touchedButton !== previousButton && touchedButton !== null) {
message(`Touched ${touchedButton.name}!`);
}
previousButton = touchedButton;
}
function testPointInRect(x, y, left, top, width, height) {
return left <= x && x < left + width &&
top <= y && y < top + height;
}
function message(str) {
fill("white");
noStroke();
textAlign(LEFT, TOP);
textSize(20);
text(str, 5, 5);
}
class RectButton {
constructor(x, y, width, height, fillColor) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.fillColor = fillColor;
}
draw() {
noStroke();
fill(this.fillColor);
rect(this.x, this.y, this.width, this.height);
}
containsPoint(x, y) {
return testPointInRect(x, y, this.x, this.y, this.width, this.height);
}
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} Now we can create an arbitrary number of rectangles.
let buttonWidth = 200;
let buttonHeight = 50;
let buttons = [];
let previousButton = null;
function setup() {
createCanvas(windowWidth, windowHeight);
for (let i = 0; i < 20; i++) {
let x = random(width - buttonWidth);
let y = random(height - buttonHeight);
let fillColor = color(random(255), random(255), random(255));
let button = new RectButton(x, y, buttonWidth, buttonHeight, fillColor);
button.name = `button #${i + 1}`;
buttons.push(button);
}
}
function draw() {
background(100);
for (let button of buttons) {
button.draw();
}
crosshairs(mouseX, mouseY);
let touchedButton = null;
for (let button of buttons) {
if (button.containsPoint(mouseX, mouseY)) {
touchedButton = button;
}
}
if (touchedButton !== previousButton && touchedButton !== null) {
message(`Touched #${touchedButton.name}!`);
}
previousButton = touchedButton;
}
function testPointInRect(x, y, left, top, width, height) {
return left <= x && x < left + width &&
top <= y && y < top + height;
}
function message(str) {
fill("white");
noStroke();
textAlign(LEFT, TOP);
textSize(20);
text(str, 5, 5);
}
class RectButton {
constructor(x, y, width, height, fillColor) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.fillColor = fillColor;
}
draw() {
noStroke();
fill(this.fillColor);
rect(this.x, this.y, this.width, this.height);
}
containsPoint(x, y) {
return testPointInRect(x, y, this.x, this.y, this.width, this.height);
}
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} Advanced feature: replace the for loops by the Array methods Array.forEach and Array.find().
let buttonWidth = 200;
let buttonHeight = 50;
let buttons = [];
let previousButton = null;
function setup() {
createCanvas(windowWidth, windowHeight);
for (let i = 0; i < 20; i++) {
let x = random(width - buttonWidth);
let y = random(30, height - buttonHeight);
let fillColor = color(random(255), random(255), random(255));
let button = new RectButton(x, y, buttonWidth, buttonHeight, fillColor);
button.name = `button ${i + 1}`;
buttons.push(button);
}
}
function draw() {
background(100);
buttons.forEach(button => button.draw());
crosshairs(mouseX, mouseY);
let touchedButton = buttons.find(
button => button.containsPoint(mouseX, mouseY));
if (touchedButton !== previousButton && touchedButton) {
message(`Touched #${touchedButton.name}!`);
}
previousButton = touchedButton;
}
function testPointInRect(x, y, left, top, width, height) {
return left <= x && x < left + width &&
top <= y && y < top + height;
}
function message(str) {
fill("white");
noStroke();
textAlign(LEFT, TOP);
textSize(20);
text(str, 5, 5);
}
class RectButton {
constructor(x, y, width, height, fillColor) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.fillColor = fillColor;
}
draw() {
noStroke();
fill(this.fillColor);
rect(this.x, this.y, this.width, this.height);
fill("white");
textAlign(CENTER, CENTER);
text(this.name, this.x + this.width / 2, this.y + this.height / 2);
}
containsPoint(x, y) {
return testPointInRect(x, y, this.x, this.y, this.width, this.height);
}
}
function crosshairs(x, y) {
noFill();
stroke("blue");
strokeWeight(4);
line(x - 10, y, x + 10, y);
line(x, y - 10, x, y + 10);
} Try it on OpenProcessing: Detecting Transitions