Tutorial 15: Interaction 1 - Mouse

Introduction

This tutorial will introduce you to the basics of interaction with the mouse. Processing provides a number of standard variables and functions that make it easy to access the state of the mouse and the keyboard.

In this tutorial we will focus on interactions based on the mouse position, movement and the state of the mouse buttons. Processing provides variables to access the current and previous mouse position as well as the state of the mouse button(s). This chapter will introduce these variables and how you can use in the draw() function to provide interaction.

Mouse Position

The mouseX and mouseY variables are automatically updated by Processing every frame, just before the draw() function is called. These variables contain the mouse cursor's position relative to the sketch's canvas. This makes it easy to use the mouse to draw to the canvas. The following sketch shows how we can use the mouse to continously draw an ellipse at the mouse position every time draw() is called.

  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
}
 
void draw() {
  background(0);
  ellipse(mouseX, mouseY, 20, 20);
}

Reacting to the Mouse Position

The values contained in mouseX and mouseY are numbers, just like any other. So we can use these values to calculate the values of other variables.

  • Show Code
/** @peep sketch */
 
color lightBlue = color(66, 168, 237);
color darkBlue = color(0, 102, 153);
color orange = color(204, 102, 0);
 
void setup() {
  size(200, 200);
  noStroke();
}
 
void draw() {
  background(255-16);
  float radius = 20 + mouseX / 2;
  fill(orange, 128);
  ellipse(mouseX, height/2, radius, radius);
}

We can use the value of mouseX and mouseY to calculate the value of multiple variables at once.

  • Show Code
/** @peep sketch */
 
color lightBlue = color(66, 168, 237);
color darkBlue = color(0, 102, 153);
color orange = color(204, 102, 0);
 
void setup() {
  size(200, 200);
  noStroke();
}
 
void draw() {
  background(255-16);
  float radius1 = 20 + mouseX / 2;
  float radius2 = 20 + (width - mouseX) / 2;
  fill(orange, 128);
  ellipse(mouseX, height/2, radius1, radius1);
  fill(lightBlue, 128);
  ellipse(mouseX, height/2, radius2, radius2);
}

Write a sketch that uses the mouseX and mouseY to change how and where a shape is drawn to the canvas.

Comparing the Mouse Position

The dist() function can be very helpful when trying to react to the mouse cursors position. Using the dist() function we can easily calculate whether the mouse cursor is within a certain radius of a point. In the following code the dist() function is used to calculate the distance of the mouse cursor from the center of the canvas and a conditional statement is used to determine whether that distance is less than the radius of the ellipse that is being drawn. If it is then the ellipse is filled with orange otherwise it is filled with blue.

  • Show Code
/** @peep sketch */
 
color lightBlue = color(66, 168, 237);
color darkBlue = color(0, 102, 153);
color orange = color(204, 102, 0);
 
int radius = 50;
 
void setup() {
  size(200, 200);
}
 
void draw() {
  background(255-16);
  strokeWeight(4);
  float distance = dist(mouseX, mouseY, width/2, height/2);
  if (distance < radius) {
    fill(orange);
  } else {
    fill(lightBlue);
  }
  ellipse(width/2, height/2, radius*2, radius*2);
}

We are, or course, not limited to comparing the mouse pointer to just one position on the canvas.

  • Show Code
/** @peep sketch */
 
color lightBlue = color(66, 168, 237);
color darkBlue = color(0, 102, 153);
color orange = color(204, 102, 0);
 
int spacing = 20;
int radius = 8;
 
void setup() {
  size(200, 200);
  noStroke();
}
 
void draw() {
  background(255-16);
  for (int x = 10; x < width; x += spacing) {
    for (int y = 10; y < width; y += spacing) {
      float distance = dist(mouseX, mouseY, x, y);
      if (distance < radius) {
        fill(darkBlue);
      } else {
        fill(lightBlue);
      }
      pushMatrix();
      translate(x, y);
      ellipse(0, 0, radius*2, radius*2);
      popMatrix();
    }
  }
}

We can also combine a conditional statement with calculation based on mouseX and mouseY.

  • Show Code
/** @peep sketch */
 
color lightBlue = color(66, 168, 237);
color darkBlue = color(0, 102, 153);
color orange = color(204, 102, 0);
 
int spacing = 20;
int radius = 8;
 
void setup() {
  size(200, 200);
  noStroke();
}
 
void draw() {
  background(255-16);
  for (int x = 10; x < width; x += spacing) {
    for (int y = 10; y < width; y += spacing) {
      float distance = dist(mouseX, mouseY, x, y);
      if (distance < radius) {
        fill(darkBlue);
      } else {
        fill(lightBlue);
      }
      float factor = map(distance, 0, width, 1.3, 0.3);
      pushMatrix();
      translate(x, y);
      scale(factor);
      ellipse(0, 0, radius*2, radius*2);
      popMatrix();
    }
  }
}

Write a sketch based on the one above that uses the mouse position to change how multiple squares are drawn. For example, see if you can you make a sketch that produces interactive patterns like this:

Twisted Squares

Hint: experiment with the rotate() function.

Mouse Movement

As well as the current mouse position, Processing stores the value of the mouse position from the previous time draw() was called. These values are stored in the variables pmouseX and pmouseY. The following sketch shows how we can draw a line from the previous mouse position to the current mouse position. We can use this to build a very simple "scribble" sketch:

  • Show Code
/** @peep sketch */
void setup() {
  size(200, 200);
  background(255-16);
}
 
void draw() {
  line(pmouseX, pmouseY, mouseX, mouseY);
}

Note: Unfortunately, depending on your browser, this sketch may not work very well because of a bug in the current version of Processing.js.

Calculating the amount of movement

We can use the values of mouseX, mouseY, pmouseX and pmouseY to calculate the distance that the mouse cursor has moved since the last frame was drawn, i.e., since draw() was last called. We can use this distance calculation to change how we draw a shape or line.

Here's an example of drawing shapes with different sizes depending on how far the mouse has travelled since the previous shape was drawn:

  • Show Code
/** @peep sketch */
 
color orange = color(204, 102, 0);
 
void setup() {
  size(200, 200);
  background(255-16);
  noStroke();
  fill(orange);
}
 
void draw() {
  float distance = dist(pmouseX, pmouseY, mouseX, mouseY);
  if (distance < 20) {
    ellipse(mouseX, mouseY, distance, distance);
  }
}

Here's an example of a sketch that draws the thickness of the line based on the inverse of the distance travelled by the mouse, i.e., so that the line gets thinner when the mouse is moved faster. Notice how the code uses the constrain() function to ensure that the strokeWeight() function call always gets a value between 2 and 10.

  • Show Code
/** @peep sketch */
 
color darkBlue = color(0, 102, 153);
 
void setup() {
  size(200, 200);
  background(255-16);
  stroke(darkBlue);
}
 
void draw() {
  float d = dist(pmouseX, pmouseY, mouseX, mouseY);
  if (d <= 40) {
    strokeWeight(constrain(10-d, 2, 10));
    line(pmouseX, pmouseY, mouseX, mouseY);
  }
}

Develop you own sketch that draws differently depending on how fast the mouse is moving. For example, you might use the random() function to draw small ellipses closer or further away from the mouse, depending on how fast it is moving. This might allow you to produce a simple drawing tool that can support more subtle drawing than simple lines:

Mouse Position and Mouse Movement

Finally, here's a sketch that combines the calculation of the thickness of the line to draw, based on the movement of the mouse, with the colour of the line, based on the position of the mouse.

  • Show Code
/** @peep sketch */
void setup() {
  size(255, 255);
  background(255-16);
  stroke(0);
}
 
void draw() {
  float d = dist(pmouseX, pmouseY, mouseX, mouseY);
  if (d <= 40) {
    float r = (mouseX + mouseY)/2;
    float g = max(mouseX, mouseY);
    float b = 255 - max(mouseX, mouseY);
    float s = constrain(10-d, 2, 10);
    stroke(r, g, b, 255-16);
    strokeWeight(s);
    line(pmouseX, pmouseY, mouseX, mouseY);
  }
}

Develop your own sketch that changes the drawing of a line or shape based on the movement and position of the mouse.

Mouse Buttons

Processing provides the variables mousePressed and mouseButton that allow a sketch to monitor the status of the mouse buttons.

The mousePressed variable is a Boolean value, which indicates whether a mouse button is currently being pressed.

  • Show Code
/** @peep sketch */
 
void setup() {
  size(200, 200);
  background(255-16);
}
 
void draw() {
  if (mousePressed) {
    line(pmouseX, pmouseY, mouseX, mouseY);
  }
}

The mouseButton variable contains the value of the most recently pressed mouse button. This can be either LEFT, RIGHT or CENTER.

Adapt the code you developed to draw variable sized shapes based on mouse movement to only draw if a mouse button is pressed.

  • Show Code
/** @peep sketch */
 
color lightBlue = color(66, 168, 237);
color darkBlue = color(0, 102, 153);
color orange = color(204, 102, 0);
 
void setup() {
  size(200, 200);
  background(255-16);
}
 
void draw() {
  if (mousePressed) {
    if (mouseButton == LEFT) {
      stroke(darkBlue);
    } else if (mouseButton == RIGHT) {
      stroke(orange);
    }
    line(pmouseX, pmouseY, mouseX, mouseY);
  }
}

Change the above code to create an "eraser" when the right mouse button is pressed. (Hint: To create an eraser, you'll just need to draw with the same colour as the background.)

A Simple Button UI

We can use a conditional statement and mousePressed to create a simple button for a user interface.

  • Show Code
/** @peep sketch */
 
color lightBlue = color(66, 168, 237);
color darkBlue = color(0, 102, 153);
color orange = color(204, 102, 0);
 
int radius = 100;
color fillColor = lightBlue;
 
void setup() {
  size(200, 200);
}
 
void draw() {
  background(255-16);
  strokeWeight(4);
  float distance = dist(mouseX, mouseY, width/2, height/2);
  if (mousePressed && distance < radius/2) {
    fill(orange);
    println('The button has been pressed.');
  } else {
    fill(lightBlue);
  }
  ellipse(width/2, height/2, radius, radius);
}

Change the above sketch to add a different reaction to the button being pressed, e.g., change the background colour. (Hint: To change the background colour you wouldn't call background() in the conditional, this would just wipe everything out. You'll need to create a variable to store the background colour and use this variable to call background() colour each time draw() is called and then change the value of this variable depending on whether the button is being pressed or not.)

The Mouse Cursor

Processing supports the changing of the mouse cursor, to one of a few standard types supported by the operating system on which a sketch is running through the cursor() function. This function accepts a few standard values that correspond with commonly supported mouse cursor shapes: ARROW (the default mouse cursor), CROSS (used for accurate positioning), HAND (used to indicate that the user can interact with something), MOVE (typically used when the user is dragging something), TEXT (used to indicate that the mouse can be used to change the text cursor position), and WAIT (used to indicate that a long operation is taking place that cannot be interrupted). These values do not explicitly define the graphic used for the mouse cursor, the exact shape of the cursor will depend on the operating system.

NOTE: Changing the mouse cursor requires support from the underlying operating system, so while Processing and Processing.js have support for these functions, you should always test on the platforms you want your sketch to run on. Currently, the version of Processing.js running on Peep does not support changing the mouse cursor, and so the following code will not run in your browser. To see the effect copy and paste the code into a copy of Processing in your desktop environment.

  • Show Sketch
/** @peep sketchcode */
 
void setup() {
  size(300, 200);
  background(255);
  strokeWeight(2);
  for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
      rect(i*100 + 5, j*100 + 5, 100 - 10, 100 - 10);
    }
  }
}
 
void draw() {
  int i = (mouseX / 100) + 1; // values 1..3
  int j = (mouseY / 100) + 1; // values 1..2
  int m = i * j; // values 1..6
  if (m == 1) cursor(ARROW);
  else if (m == 2) cursor(CROSS);
  else if (m == 3) cursor(HAND);
  else if (m == 4) cursor(MOVE);
  else if (m == 5) cursor(TEXT);
  else if (m == 6) cursor(WAIT);
}

The cursor() function can also take an instance of a PImage together with an (x, y) coordinate that indicates where in an image the "tip" of the mouse cursor should be considered to be. Here's an example of a custom mouse cursor using this function.

  • Show Code
/* @pjs preload="/uploads/1/pencil-red-small.png"; */
/** @peep sketch */
 
void setup() {
  size(300, 300);
  PImage pencil = loadImage("/uploads/1/pencil-red-small.png");
  cursor(pencil, 0, pencil.height - 1);
  background(255);
  strokeWeight(2);
}
 
void draw() {
  if (mousePressed) {
    line(pmouseX, pmouseY, mouseX, mouseY);
  }
}

NOTE: The above sketch demonstrates that this version of cursor() works in the version of Processing.js used on Peep but you may notice that when a mouse button is pressed the mouse cursor switches to the standard TEXT mouse cursor. The latest version of Processing.js attempts to fix this problem. The desktop version of Processing does not have the same issue.

There is one other way to change the appearance of the mouse cursor. The noCursor() function makes the mouse cursor disappear. This can be helpful when we want to implement a custom draw function in place of the mouse cursor:

  • Show Code
/** @peep sketch */
 
void setup() {
  size(300, 300);
  noCursor();
}
 
void draw() {
  background(255);
  line(mouseX, 0, mouseX, height);
  line(0, mouseY, width, mouseY);
}

NOTE: If you want to do this kind of custom drawing over the top of a permanent image, such as a drawing canvas, you'll need to use a PGraphics object to store the canvas image, so that the custom drawing actions don't get drawn to the canvas. See the Overlays portfolio post for an example implementation.

Comments

    Laura Bee about a year ago

    The Overlays portfolio post isn't available.