Drag-and-Drop in Flash CS3

Some applications require that the user only drop a dragged object to a few predefined points or areas of the screen. This example takes the basic drag-and-drop interface we created on page 1 and adds a grid of valid points. This gives a "snap to grid" effect that is familiar to those who use graphics programs.

The first part of our code simply sets up the drawing board and the point to be dragged as we did on page 1. We add a block of code to draw tiny points so that the lattice of grid points is obvious. It is very easy to comment out this block (using /* on the line before the block and */ on the line after) if you would rather see the snapping effect without the grid being visible.

//Part I -- Set up the board

var bWidth:Number = 400;

var bHeight:Number = 240;

var grid:Number = 40; // Size of the grid and number of lattice points in each direction

var dotsWide:Number = Math.ceil(bWidth/grid) - 1;

var dotsHigh:Number = Math.ceil(bHeight/grid) - 1;

var board:Sprite = new Sprite();

var myPoint:Sprite = new Sprite();

this.addChild(board);

board.addChild(myPoint);

board.graphics.lineStyle(1,0);

board.graphics.beginFill(0xCCCCCC);

board.graphics.drawRect(0,0,bWidth,bHeight);

board.graphics.endFill();

// Add a bunch of circles to represent lattice points

board.graphics.beginFill(0x000000);

for (var i=1; i<=dotsHigh; i++) {

for (var j=1; j<=dotsWide; j++) {

board.graphics.drawCircle(j*grid,i*grid,0.5);

}

}

board.graphics.endFill();

board.x = 10;

board.y = 10;

myPoint.graphics.lineStyle(1,0);

myPoint.graphics.beginFill(0xFF5555);

myPoint.graphics.drawCircle(0,0,8);

myPoint.graphics.endFill();

myPoint.x = goodX(50);

myPoint.y = goodY(50);

The drag-and-drop functionality is handled exactly the way it was in our previous example, with the checking for legal drop values relegated to the helper functions goodX and goodY. We will take care of the snapping to grid in these functions as well in Part III.

// Part II -- Add drag and drop functionality

myPoint.addEventListener(MouseEvent.MOUSE_DOWN, startMove);

function startMove(evt:MouseEvent):void {

stage.addEventListener(MouseEvent.MOUSE_MOVE, pointMove);

}

function pointMove(e:MouseEvent):void {

myPoint.x = goodX(board.mouseX);

myPoint.y = goodY(board.mouseY);

e.updateAfterEvent();

}

stage.addEventListener(MouseEvent.MOUSE_UP, stopAll);

function stopAll(e:MouseEvent):void {

stage.removeEventListener(MouseEvent.MOUSE_MOVE, pointMove);

}

In the functions below, we think of the input parameters inX and inY as x and y coordinates of the mouse. As before, we first test to see if these are bigger or smaller than we would like to allow, and if so we return instead the biggest (resp. smallest) we would like these to be. Notice that in this case, we consider the first/last lattice dot to be the allowable extremes rather than the edge of the board. This creates consistency in cases where the edges of the board do not contain grid points.

The snapping action is embarrassingly simple using a little mathematics. To see the idea, consider the following operations on the number N: calculate N/10, round the result to the nearest integer, and multiply this result by 10. The overall result is that we have found the integer multiple of 10 to nearest to N. (For example, 87/10 = 8.7, which rounds to 9, which multiplied by 10 is 90, the integer multiple of 10 closest to 87.) We do precisely this to get the x and y coordinates for the grid point nearest to (inX, inY).

// Part III -- Helper functions to stay within boundary AND snap to grid

function goodX(inX:Number):Number {

if (inX > grid*dotsWide) {

return grid*dotsWide;

}

if (inX < grid) {

return grid;

}

return grid*Math.round(inX/grid);

}

function goodY(inY:Number):Number {

if (inY > grid*dotsHigh) {

return grid*dotsHigh;

}

if (inY < grid) {

return grid;

}

return grid*Math.round(inY/grid);

}

Download

Notes

It is easy to change the interface so that the dragging is smooth but the snapping happens only when the mouse is released. We will use this approach in the example on the next page, since there the final position of the object is all that matters. It is a worthwhile exercise to try implementing the idea here using the following outline:

In the goodX and goodY functions, return just inX (inY, respectively) instead of the snapped version grid*Math.ceil(bWidth/grid). In other words, change it back to the way it was at the end of Page 1. Then use the following new version of the function stopAll: