11. Special Effect: Dragging and Dropping

In these notes you will learn:

  • How to implement drag-and-drop
  • How to drag and 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’re all familiar with dragging files into and out of folders, moving windows around, etc. In this note you will see how to implement this in Processing.

11.2. Planning the Program

Let’s 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 release the mouse button, the circle detaches from the pointer and stays where it is.

With a little bit of work, it is possible to implement dragging using the mousePressed variable. But dragging objects around a screen is such a common activity that Processing provides us with a function to handle it: mouseDragged(). As the name suggests, this function is automatically called when the user drags the mouse, i.e. “clicks and holds” the mouse button while moving the mouse.

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 point is in the circle, then we’ll set the circle’s centre 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

Here is a first attempt at the program

// circle centre and diameter.
float x, 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;
    }
}

// Return true if the point (a, b) is in the circle with centre (x, y)
// and radius r.
boolean pointInCircle(float a, float b, float x, float y, float r) {
    return dist(a, b, x, y) <= r;
}

The program makes the circle feel as if it were a solid object that you can pick and move around. Sort of. Play with it a bit before continuing to the next section.

11.4. Choppy dragging

You may have noticed that moving the mouse cursor fast while dragging might cause the circle to stay behind, as if it were dropped mid drag. Why is this happening? Try printing out the values of mouseX and mouseY at each call to draw while moving the mouse from, say, left to right fast. Are the values contiguous? That is, are all of the values between your start and end points printed to the screen?

What happens here is that the mouse motion is actually more frequent than the execution of the draw() function. Hence, a quick motion of the mouse will result in the mouse pointer moving farther than the circle (between calls to draw()). At some point, even though we are still dragging, the mouse won’t be in the circle, and the test pointInCircle() will return false.

What’s to be done? We can employ boolean variables and the mousePressed() function. Instead of always tracking whether the mouse pointer is in the circle, we’ll assume the user wants to drag whenever they click inside the circle, and when the click terminates, we’ll cancel our drag operation.

In pseudo-code:
  • we’ll have a global boolean variable shouldMoveCircle.
  • When the mouse is pressed, if pointInCircle() is true, then shouldMoveCircle will be set to true.
  • In mouseDragged(), instead of checking if the point is the circle, we’ll check shouldMoveCircle and if true, we’ll update x and y.
  • Finally we need to set shouldMoveCircle to false somewhere (why?). The best time to do this is when the mouse button is released, since then we can confidently say that dragging has ended. Kindly, Processing provides with the way to do this: the mouseReleased() function, which is called once, after the user has released the mouse button.

Specifically, we add make the following changes:

boolean shouldMoveCircle;

void setup() {
    // ... previous statements .../

    shouldMoveCircle = false;
}

// draw() is unchanged


void mousePressed() {
    shouldMoveCircle = pointInCircle(mouseX, mouseY, x, y);
}

void mouseDragged() {
    if (shouldMoveCircle) {
        x = mouseX;
        y = mouseY;
    }
}

void mouseReleased() {
    shouldMoveCircle = false;
}

That fixed the problem, so we’re done! Right?

11.5. A Small Problem

You may have noticed another issue while playing with the program. 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 centre immediately jumps to the mouse pointer location. It seems unnatural and is the sort of small blemish that makes a program looks 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 the circle is moving. This is much more natural: if you pick something up by its edge and move it, you’d be pretty surprised if it tried to centre itself in your hand!

So how can we get that to work? Let’s think about what we’d like to have happen.

11.6. Fixing the Problem

It’s always a good approach, when solving a programming problem, to ask why is this problem happening? In this case the answer is in the mouseDragged() function:

void mouseDragged() {
    if (shouldMoveCircle) {
        x = mouseX;
        y = mouseY;
    }
}

Whenever the mouse is dragged, and the pointer is in the circle, we move the centre of the circle to the pointer location, regardless of where on the circle the pointer is. Imagine we do this when the pointer is at and edge of the circle (which is of diameter 50). Then either the x value or the y value (depending on which edge our pointer is at), suddenly “jumps” by 25 pixels in that direction.

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 the mouse 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. Really what we want to have happen is that the centre of the ball move from its original position 3 pixels right and 2 pixels down. That is, all we need to do is add 3 pixels to x and 2 pixels to y.

But, our program doesn’t know directly how many pixels left/right or up/down the mouse has moved. So we need to calculate these. The trick is to realize that the distance moved along an axis, say the x-axis, is the difference between the current current mouseX value, and the mouseX value from the previous time draw() was called.

We could keep track of these value our selves (try!) but Processing, as friendly as ever, already keeps track of them for us, in the global variables pmouseX and pmouseY (notice the p at the beginning).

In code this gives:

void mouseDragged() {
    if (shouldMoveCircle()) {
        x += mouseX - pmouseY;
        y += mouseY - pmouseY;
    }
}

Notice that we are adding the difference to mouseX and mouseY.

11.7. Was it Really a Bug?

It is not always clear if 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 - 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 position on the circle as it moves. The original behaviour — the centre of the circle jumping to the mouse pointer — does not meet this specification, and so is a bug.

However, we could have specified things differently. We could specified that when you pick up the circle bu the edge, then the centre 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 when the circle moved.

11.8. 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.9. 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.