Jan 14: Responding conditionally
Testing the mouse and key
We saw last time how to do something exactly at the "event" when the mouse is pressed, by defining a mousePressed() function. But what if we want to make a scribbling program, where ellipses are drawn wherever the mouse is, as long as it is held down? That is, each frame we want to check if the mouse is pressed, and if so, draw an ellipse. Here's the translation of that English sentence into Processing.
void setup() { size(400,400); smooth(); noStroke(); } void draw() { if (mousePressed) { // It is pressed, so draw a white ellipse fill(255); ellipse(mouseX,mouseY,25,25); } }
[applet]
The variable mousePressed (yes, the same name as the function) tells us whether or not the mouse is currently pressed. While we've mostly dealt with numbers (coordinates, sizes), this isn't a number, but rather a boolean, one that can be either true (the mouse is pressed) or false (it isn't).
To do something (or not) depending on whether or not a boolean is true, we use a construct called a conditional, or if-else statement. (As usual, see the Programming notes section for more discussion of syntax and so forth.) Conditionals are very useful, and there are many things that we can test. For example, we can set the fill based on whether the mouse is on the left or right half of the window:
void setup() { size(400,400); smooth(); noStroke(); } void draw() { if (mouseX < width/2) { // Left half of screen; draw black fill(0); } else { // Right half of screen; draw white fill(255); } if (mousePressed) { // Only draw if button is pressed ellipse(mouseX,mouseY,25,25); } }
[applet]
Here the boolean expression is not a single variable (like we saw with mousePressed), but a comparison. It does pretty much what you might expect -- if the x coordinate is less than half the width, the boolean is true; otherwise it is false. Other comparisons (below) include the various inequalities, along with equals (==) and not equals (!=). Try changing the code to use another one.
The variable mouseButton tells us which button is pressed (if one is); its value can be LEFT, MIDDLE, or RIGHT. Here's an example that uses an equality test to draw in black or white for left or right button, and doesn't draw when there's no button.
void setup() { size(400,400); smooth(); noStroke(); } void draw() { if (mousePressed) { if (mouseButton == LEFT) { fill(0); } else if (mouseButton == RIGHT) { fill(255); } ellipse(mouseX,mouseY,25,25); } }
[applet]
Note that here when the mousePressed test passes, the body of the "if" is itself an "if". That's totally acceptable, and called nested. In fact, it would make sense to nest the fill color in the previous sketch -- no need to set it unless we know the button is pressed. Note also that the "else" part of the mouseButton test is itself an if statement, also totally fine, and the way to do things when there are multiple possibilities. The outcome of one question leads to the next question (kind of like in the game of 20 questions). Proper indendation is a must for this to be readable!
There are plenty of other variations on this theme. For example, rather than testing the mouse press, we could set the fill color either black or white at random:
if (random(1) < 0.5) { fill(0); } else { fill(255); }
Key presses are handled much the same way as mouse clicks. Just as the keyPressed() function handles key press events, the keyPressed variable (also a boolean) tells us whether or not a key has been pressed, and we can test it inside the draw body. I can't think of too many reasons to do that, though. However, if we want to see which key was pressed inside the keyPressed() function, we check the key variable, which works much like the mouseButton variable. However, rather than telling us that a key was pressed, it tells us which one. We can compare it against different letters (enclosed in single quotes) with equality tests, and do different things depending on which test passes.
Key presses are useful to control aspects of a sketch. In the following example, keys set the color to be drawn. Note that if there's only one statement following the "if", we can omit the curly braces.
void setup() { size(400,400); fill(0,50); // default to transparent black noStroke(); background(0); } void draw() { ellipse(mouseX,mouseY,10,10); } void keyPressed() { // Set fill color to be red, green, blue, or black // according to key press if (key=='r') fill(255,0,0,50); else if (key=='g') fill(0,255,0,50); else if (key=='b') fill(0,0,255,50); else fill(0,50); }
[applet]
Conditional + state
Remember the random wanderer from last time? Let's get a bit fancier, and introduce another variable holding the current shade of the ball, along with one telling us whether we should increase or decrease the shade. Once the shade gets too low, we'll increase until it gets too high, and then we'll decrease. We'll do the same type of thing with the size, but take smaller steps.
// The current coordinates of the ball, starting at the center (in setup) float x, y; // The current color of the ball, and whether it's increasing or decreasing int gray=255, dgray=-1; // The current size of the ball, and how much it's increasing or decreasing float sz=5, dsz=0.1; void setup() { smooth(); noStroke(); background(0); x=width/2; y=height/2; } void draw() { fill(0,3); rect(0,0,width,height); fill(gray); ellipse(x,y,sz,sz); // Move the position by random steps in x and y x += random(-2,2); y += random(-2,2); // Increase/decrease the color, changing direction at the extremes gray += dgray; if (gray == 128) { // Decreased too far; start increasing dgray = 1; } else if (gray == 255) { // Increased too far; start decreasing dgray = -1; } // Increase/decrease the size, changing direction at the extremes sz += dsz; if (sz <= 2 || sz >= 15) dsz = -dsz; }
[applet]
A few details. The shorthand x += random(-2,2) is equivalent to x = x+random(-2,2); similarly, while the example doesn't use it, x++ is equivalent to x = x+1. The two ways of changing direction here (explicitly set the variable vs. negate it) are equivalent. Finally, note that when we're using floating point numbers, it's best not to test for exact equality but for the appropriate inequality.
If we wanted the keep the wanderer from going off-screen, we could add code like the following, just after giving x its new value:
if (x < 0) x = 0; else if (x > width) x = width;
Since it's common to want to constrain something to be within a range, Processing provides the constrain() function, which returns the value (first parameter) if it's between the minimum (second) and maximum (third), or else the minimum or maximum. So we could instead write the following, which is exactly equivalent to the "if" construct above:
x = constrain(x,0,width);
Graphical user interfaces
Ever wonder how buttons work in graphical user interfaces? We can build them ourselves, by performing the proper tests. We saw already how to change the color for left half vs. right half. We can test that for any desired x coordinate, including the boundary of a rectangle. We need to make sure that the mouse is more than the left side, less than the right side, more than the top side, and less than the bottom side. The logical AND operator, written &&, combines booleans. Only if both are true does it evaluate to true.
void draw() { if (mouseX>25 && mouseX<75 && mouseY>40 && mouseY<60) // Inside rectangle, so make it yellow fill(255,255,0); else // Outside rectangle, so make it green fill(0,255,0); rect(25,40,50,20); }
[applet]
That gives us a "mouseover" effect. To make it into a button, either "on" or "off", we set a boolean variable accordingly, each time the mouse is pressed in the rectangle. We draw the rectangle differently depending on the state of the variable.
boolean buttonOn = false; void draw() { if (buttonOn) // On, so make it red fill(255,0,0); else if (mouseX>25 && mouseX<75 && mouseY>40 && mouseY<60) // Inside rectangle, so make it yellow fill(255,255,0); else // Outside rectangle, so make it green fill(0,255,0); rect(25,40,50,20); } void mousePressed() { if (mouseX>25 && mouseX<75 && mouseY>40 && mouseY<60) // Button inside rectangle, so invert button buttonOn = !buttonOn; }
[applet]
Note the use of the boolean operator "!" (NOT), which gives the opposite value (true for false, and vice versa).
Programming notes
- Boolean
- A boolean is something that is either true or false.
- Boolean operators
- Just like we add and subtract numbers, we can combine booleans with
logical AND (&&), and logical OR (||). There's also logical NOT (!), which inverts a single boolean.
a !a false true true false a b a && b false false false false true false true false false true true true a b a || b false false false false true true true false true true true true - Comparison
- We can compare numbers to see which is larger than the other. The result of a test is a boolean, indicating whether that statement is true.
We can also test for equality:
- == (equality) Note that this is a double equals. Single equals means something else entirely, which we'll discuss later; you have to keep these straight or you'll get strange (and frustrating) errors.
- != (inequality)
- Conditional
- A conditional executes code only if some boolean expression is true.
The basic conditional has the following form:
If there's only one statement in the body of the "if" or the "else", the curly braces around it can be omitted. The "else" part is optional -- if we leave it out, then nothing happens if the boolean is false. There can be as many "else if" parts as we like (including none).
if (boolean expression) { statements to do if the boolean is true } else if (another boolean expression) { statements to do if the first boolean is false and the second is true } ... else { statements to do if the boolean is false }