Mouse Drawing with an Eraser - The Code

Below is the commented code for the applet from the previous page. We discuss all the new elements: the Shape 'shDrawing', in which the user's drawing resides, is now a child of the MainTimeline and not of the graphing board, spBoard. 'shDrawing' is masked by shMask. We created a custom cursor (a simple disk filled with a gradient) at authoring time, linked it from the Library to AS3. Then, we can instantiate it at runtime. To create a custom cursor, we use Mouse.hide(), Mouse.show() methods. We make the new cursor follow the mouse with an event listener attached to spBoard that listens to the MOUSE_MOVE event. We also use DisplayObjectContainer.contains(DisplayObjectContainer) method.

The Code

We are creating a Sprite, spBoard, that will serve as a board for the user to draw on. We add spBoard as a child of 'this', that is, the MainTimeline and position spBoard within the main movie. Then, we call a function (defined later in the script) that draws a white square in spBoard.

var spBoard:Sprite=new Sprite();

this.addChild(spBoard);

spBoard.x=280;

spBoard.y=23;

drawBoard();

shDrawing is a Shape that will contain the user's drawing. Unlike in Part 1, shDrawing is now a child of the MainTimeline and not a child of spBoard. We set the position of shDrawing to be the same as the position of spBoard. Thus, local coordinate systems in spBoard and shDrawing are going to coincide.

var shDrawing:Shape=new Shape();

this.addChild(shDrawing);

shDrawing.x=280;

shDrawing.y=23;

Having shDrawing as a child of the MainTimeline and not of spBoard will make it easier to stop drawing when ROLL_OUT event for spBoard occurs. (If shDrawing is a child of spBoard, ROLL_OUT does not occur when the drawing goes out of the white square. spBoard just grows bigger.) However, in order to avoid altogether having to limit coordinates within which drawing takes place, we have to mask shDrawing. Because of the way Flash draws lines, thick lines may fall slightly outside of the white square even if the mouse cursor is within the square. To see that, comment out the lines below: shDrawing.mask=shMask, and: drawMask(); We create a shape, shMask, and call a function that draws a square in it. After we assign shMask as a mask to shDrawing, the only portions of shDrawing that will be visible are those visible through the square. The square in shMask, once assigned as a mask, will not be visible.

var shMask:Shape=new Shape();

this.addChild(shMask);

shMask.x=280;

shMask.y=23;

drawMask();

shDrawing.mask=shMask;

We set variables that will determine the thickness and the color of the line for the user drawing. We define two Boolean variables. One will remember if drawing should or should not proceed, the other Boolean variable remembers if the eraser is or is not active.

var doDraw:Boolean=false;

var eraserOn:Boolean=false;

var lineSize:Number=7;

var currentColor:Number;

The dynamic text field, sizeBox, displays the current thickness of the line.

sizeBox.text=String(lineSize);

When the eraser is active, the mouse pointer will be replaced by our custom pointer -- in our case a disk with a gradient fill. In order to avoid drawing gradient at runtime (a bit of a pain), we drew our disk by hand and converted it to a MovieClip, eraserClip. We deleted the clip from the Stage; the clip remained in the Library. From the menu in the upper right corner of the Library window, we chose 'Linkage' and checked 'Export to ActionScript'. After the linkage is established (and only after that), we can create instances of our clip at runtime. We do it below.

var mcEraser:eraserClip= new eraserClip();

Without the next line, erasing would not take place. Why? The portion of spBoard hidden under the eraser (which is a MovieClip and thus an InteractiveObject) would not receive mouse events; mcEraser would receive them. Try commenting the line out, and decyphering the havoc that happens.

mcEraser.mouseEnabled=false;

The eraser is scaled based on the current thickness of the line chosen. Note that we haven't added mcEraser as a child of anything. That will be done by the clip (acting as a button) that activates the eraser.

mcEraser.scaleX=lineSize/20;

mcEraser.scaleY=lineSize/20;

 

function drawBoard():void {

spBoard.graphics.lineStyle(1,0x000000);

spBoard.graphics.beginFill(0xFFFFFF);

spBoard.graphics.drawRect(0,0,250,250);

spBoard.graphics.endFill();

spBoard.filters = [ new DropShadowFilter() ];

}

 

function drawMask():void {

shMask.graphics.lineStyle(1,0x000000);

shMask.graphics.beginFill(0xFFFFFF);

shMask.graphics.drawRect(1,1,249,249);

shMask.graphics.endFill();

}

We attach the appropriate listeners to spBoard. The listeners will cause drawing to take place only after the user presses the mouse button over spBoard (MOUSE_DOWN), and moves the mouse over spBoard (MOUSE_MOVE). When the user moves the mouse outside spBoard (ROLL_OUT), drawing stops. When the eraser is active, the pointer changes when the user moves the mouse over (ROLL_OVER) or out (ROLL_OUT) of spBoard. All the code responsible for that is placed in event handlers 'boardOut', boardOver', etc.. The key is in setting the value of the Boolean variable, doDraw, and reading the value of the Boolean variable, eraserOn.

spBoard.addEventListener(MouseEvent.ROLL_OUT,boardOut);

spBoard.addEventListener(MouseEvent.ROLL_OVER,boardOver);

spBoard.addEventListener(MouseEvent.MOUSE_MOVE,boardMove);

spBoard.addEventListener(MouseEvent.MOUSE_DOWN,boardDown);

spBoard.addEventListener(MouseEvent.MOUSE_UP,boardUp);

When the user moves the mouse over spBoard and the eraser is active (eraserOn==true; the value set by clicking mcEraserActivate clip), mcEraser is added as a child of the MainTimeline so it can now be visible. Then, mcEraser is scaled based on the current thickness of the line. The regular mouse pointer is hidden and mcEraser is placed at the mouse position. In boardMove handler, mcEraser will be made to follow the mouse.

function boardOver(e:MouseEvent):void {

if(eraserOn){

this.addChild(mcEraser);

mcEraser.scaleX=lineSize/20;

mcEraser.scaleY=lineSize/20;

mcEraser.x=stage.mouseX;

mcEraser.y=stage.mouseY;

Mouse.hide();

}

}

When the mouse moves away from spBoard, doDraw is set to false. If the MainTimline contains mcEraser as a child (this.contains(mcEraser)) the child is removed and the regular mouse pointer shows.

function boardOut(e:MouseEvent):void {

doDraw=false;

if(this.contains(mcEraser)){

this.removeChild(mcEraser);

Mouse.show();

}

}

When the user presses the mouse over spBoard, drawing begins at the point where the mouse is. If eraserOn is true, the drawing will be in white. That will produce the erasing effect. Otherwise, the color is determined by the current color chosen in our ColorPicker, cpColor.

function boardDown(e:MouseEvent):void {

var curX:Number=shDrawing.mouseX;

var curY:Number=shDrawing.mouseY;

doDraw=true;

if(eraserOn){

currentColor=0xFFFFFF;

} else {

currentColor=cpColor.selectedColor;

}

shDrawing.graphics.lineStyle(lineSize,currentColor);

shDrawing.graphics.moveTo(curX,curY);

}

When the user releases the mouse button, all erasing or drawing stops as doDraw is set to false.

function boardUp(e:MouseEvent):void {

doDraw=false;

}

The next function is executed when the user moves the mouse over spBoard. if eraserOn==true, the mcEraser coordinates are set to those of the mouse. So mcEraser follows the mouse. When doDraw==true, drawing happens. If eraserOn==true, drawing is in white (as set in boardDown).

function boardMove(e:MouseEvent):void {

var curX:Number=shDrawing.mouseX;

var curY:Number=shDrawing.mouseY;

if(eraserOn){

mcEraser.x=stage.mouseX;

mcEraser.y=stage.mouseY;

}

if(doDraw){

shDrawing.graphics.lineTo(curX,curY);

}

e.updateAfterEvent();

}

mcEraserActivate clip acts as a button that activates the eraser. First it changes the current value of eraserOn. (eraserOn=!eraserOn). Then if eraserOn==true, it moves to frame 2 (disk red) and scales the eraser clip. Otherwise, it moves to frame 1 (disk gray).

mcEraserActivate.addEventListener(MouseEvent.CLICK, bigActivate);

 

function bigActivate(e:MouseEvent):void {

eraserOn=!eraserOn;

if(eraserOn){

mcEraserActivate.gotoAndStop(2);

mcEraser.scaleX=lineSize/20;

mcEraser.scaleY=lineSize/20;

} else {

mcEraserActivate.gotoAndStop(1);

}

}

We attach self-explanatory listeners to our three buttons: ERASE button and the two arrow buttons that change the thickness of the line.

btnErase.addEventListener(MouseEvent.CLICK, eraseClicked);

 

function eraseClicked(e:MouseEvent):void {

shDrawing.graphics.clear();

}

 

btnUp.addEventListener(MouseEvent.CLICK, upClicked);

 

function upClicked(e:MouseEvent):void {

if(lineSize<20){

lineSize+=1;

} else {

lineSize=20;

}

sizeBox.text=String(lineSize);

}

 

btnDown.addEventListener(MouseEvent.CLICK, downClicked);

 

function downClicked(e:MouseEvent):void {

if(lineSize>1){

lineSize+=-1;

} else {

lineSize=1;

}

sizeBox.text=String(lineSize);

}

 

On the next page of this tutorial, we extract the code for creating a custom mouse pointer and present the corresponding simple applet.

Back to Basic Tutorials              Back to Flash and Math Home

The site www.flashandmath.com is maintained by Doug Ensley (doug@flashandmath.com) and Barbara Kaskosz (barbara@flashandmath.com).
It has been developed with partial funding from the National Science Foundation and the Mathematical Association of America.