11. Special Effect: Dragging and Dropping

In these notes you will learn:

  • How to implement drag-and-drop using the mouseDragged function.
  • How to drag an object while keeping the mouse pointer fixed on the drag point.

11.1. Introduction

A standard user interface effect is “dragging” an object around the screen by clicking and holding it. For example, we are all familiar with deleting a file by dragging its icon into a trash bin. In this note we will see how to implement basic drag-and-drop in Processing.

11.2. Planning the Program

Lets write a demo program that will display a circle on the screen that can be moved using the standard drag-and-drop action. The user clicks anywhere on the circle, which causes the circle to become attached to the mouse pointer. As long as the user holds down the mouse button, the circle follows the mouse pointer wherever it goes. When the user releases the mouse button, the circle detaches from the pointer and stays where it is.

Dragging and dropping is such a common operation that Processing provides the special mouseDragged() function to handle it. This function is automatically called when the user “clicks and holds” the mouse, i.e. when they do a drag-and-drop action.

So our program will work as follows. We draw the circle, and then in mouseDragged() check to see if the mouse pointer is inside the circle; we’ll re-use the pointInCircle function we’ve already written to do this. If the pointer is in the circle, then we’ll set the circle’s center to (mouseX, mouseY). As long as the user clicks-and-holds the mouse button, mouseDragged() will be called, which has the effect of making the circle follow the mouse pointer as it moves.

11.3. The Program

With that in mind, here is a program that gives us a drag-and-drop circle:

float x;
float y;
float diam;

void setup() {
  size(500, 500);
  smooth();
  x = 250;
  y = 250;
  diam = 125;
}

void draw() {
  background(255);
  noStroke();
  fill(200, 0, 0);
  ellipse(x, y, diam, diam);
}

void mouseDragged() {
  if (pointInCircle(mouseX, mouseY, x, y, diam / 2)) {
    x = mouseX;
    y = mouseY;
  }
}

boolean pointInCircle(float x, float y, float a, float b, float r) {
  if (dist(x, y, a, b) <= r) {
    return true;
  } else {
    return false;
  }
}

This program makes the circle feel as if it were a solid object that you can pick up and move around. Play with it a bit before reading the next section; it has, perhaps, some imperfections.

11.4. A Small Problem

You may have noticed when playing with the previous program that sometimes the drag-and-drop doesn’t look right. If you click near the edge of the circle, then when you start moving the mouse the circle center immediately jumps to the mouse pointer location. It seems unnatural and is the sort of small blemish that makes a program look unpolished.

It might look better if, when the user clicks on the circle, the mouse pointer stays at the same point on the circle while it is moving. This is much more natural: if you pick up something by the edge and move it, you hold it by the edge the entire time.

So how can we get that to work? Think about this! It’s a good example of the sort of problem a programmer might have to solve in real life, i.e. we have a working program whose behaviour we want to change.

As we did when we created the program, we should first try to come up with the basic solution before we implement it.

11.5. Fixing the Problem

A good way to approach a problem is to understand why it is a problem. So, why does the circle center jump to the location of the mouse pointer when we pick it up near an edge? Look at the mouseDragged() function

void mouseDragged() {
  if (pointInCircle(mouseX, mouseY, x, y, diam/2)) {
    x = mouseX;
    y = mouseY;
  }
}

Whenever the mouse is dragged, and the pointer is in the circle, we set the center of the circle, (x, y), to be the location of the mouse pointer. Think about what happens when you start a drag-and-drop near the edge of the circle. When mouseDragged() is first called the center of the circle is set to the mouse position. Thus the circle is re-drawn at the mouse pointer, which causes it to jump.

How can we fix this? Imagine what we want to have happen: if we click near the edge of the circle and start dragging, then the circle should move but the pointer should stay at the same point on the circle. For this to work we can’t just set x and y to be mouseX and mouseY.

Again imagine that we have just started our drag-and-drop with the mouse pointer near the circle’s edge. Then suppose we move the mouse 3 pixels right, and 2 pixels down; assume this is the beginning of a fluid mouse movement. What we want is for the center of the circle to move 3 pixels right, and 2 pixels down. We can do this by adding 3 pixels to x and add 2 pixels to y.

However, our program does not directly know how many pixels left/right or up/down it should move the circle’s center. We will need to calculate those values. It’s actually not too tough when you think about it the right way: the distance moved along, say, the x-axis is the difference between current value of mouseX and the previous value of mouseX. To get the new x-coordinate of the circle’s center we add this difference to its current center x-coordinate.

In code we get this:

void mouseDragged() {
  if (pointInCircle(mouseX, mouseY, x, y, diam/2)) {
    x += mouseX - pmouseX;
    y += mouseY - pmouseY;
  }
}

Happily, Processing keeps track of the previous mouse location values in the pmouseX and pmouseY variables. So the final solution is short and simple.

Be sure to give this new version of the program a try: the difference is quite noticeable!

11.6. Was it Really a Bug?

It is not always clear if a particular behaviour of a program is a feature or a bug. In this case, how the circle ought to behave when dragged depends on our program’s specification, i.e. the description of how it ought to work.

We simply specified that if we drag the circle by the edge, then the pointer should stay at the same position on the circle as it moves. The original behaviour — the center of the circle jumping to the mouse pointer — does not meet this specification, and so must be a bug.

But we could have specified things differently. We could have specified that when you pick up the circle by the edge, then the center should jump to the pointer location. In this case, it would be a bug if the pointer stayed at the same place on the circle if it moved.

What is the right behaviour for dragging? There is no one best answer. It always depends on what makes sense for your program.

11.7. Questions

  1. Write an explanation of dragging-and-dropping that a smart but computer illiterate grandma could understand.
  2. When is the mouseDragged() function called?
  3. Explain the variables pmouseX and pmouseY.
  4. Give a concrete example of a program’s behaviour that could be described as either a feature or a bug depending upon the program’s specification. Choose your feature from any software that you’ve used other than Processing.

11.8. Programming Questions

  1. Modify the drag-and-drop program so that two differently-colored circles can be dragged and dropped around the screen. Set the transparency of the circles so that it is impossible for one of the circles to completely hide the other.
  2. Modify the drag-and-drop program so that it is impossible for any portion of the circle to go off the screen. However, edges of the circle should be able to touch the edges of the screen. Hint: Read about the Processing constrain function.
  3. Write a program that lets a user drag-and-drop a rectangle around the screen. Make sure that the mouse pointer stays on the point that is first clicked while the rectangle is being dragged.