In my last post, I explained how to create a simple component: a shape inside the blackboard. As we want to enable the user to draw diferent shapes, we will have to learn how to create the blackboard component, in wich we will be able to draw shapes using the mouse. So the blackboard component will hold some instances of the BlackboardShape component we built in the previous part of this tutorial.
Let’s go with the next part of this article
You can see the whole example working here. Press right button -> View Source to see the full source code.
MOUSE EVENTS
I assume you have already worked with Actionscript events. However, let’s explain it just in case you don’t remember.
You can listen to any events dispatched by an Actionscript object just by writing this code:
object.addEventListener (eventType, eventHandler)
The eventHandler is a function that will be executed everytime the event is dispatched.
For example:
object.addEventListener (MouseEvent.MOUSE_DOWN, mouseDownEventHandler);
private function mouseDownHandler (event : MouseEvent) : void {
…
}
The event object we receive as a parameter contains useful information to handle the event and perform some actions.
In our example, we will use these events:
- MouseEvent.MOUSE_DOWN: it happens when we press the left button of the mouse
- MouseEvent.MOUSE_UP: it happens when we release the left mouse button
- MouseEvent.MOUSE_OUT: it is dispatched when we move the cursor out of the component
- MouseEvent.MOUSE_MOVE: it is dispatched when we move the cursor along the component.
If we create an empty component and try to listen to mouse events, we will see that no event is dispatched. This happens because they will only be dispatched if we have drawn something inside the component. So, in order to catch the mouse events, I have drawn a transparent sprite on top of the component that will listen to the mouse events (cristal).
COMPONENT STATES
When creating our component, there are some rules that we should keep in mind:
- Update the visualization of the component only in the updateDisplayList function.
- Never call directly to the updateDisplayList function. We should call the invalidateDisplayList function instead.
This is a very simple component. If we create more complex components showing complex data, it will be necessary to use the commitProperties function.
The easiest way to handle the diferent states of our component is to store the current state in a variable (blackboardState).
We will use three states:
- BASE
- STARTING_PICTURE: the user is starting a new shape inside the blackboard
- DRAWING: the user is drawing
Whenever we want to make a change to the component, we will change the state variable and call the invalidateDisplayList function if the visualization of the component must be refreshed.
THE CODE
Enough explanation. Let’s see the code:
package comp
{
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.geom.Point;
import mx.controls.Alert;
import mx.controls.Button;
import mx.core.UIComponent;
public class Blackboard extends UIComponent
{
/* BLACKBOARD AVAILABLE STATES */
private static const BASE : String = "";
private static const STARTING_PICTURE : String = "startingPicture";
private static const DRAWING : String = "drawing";
// The state variable will store the current state of the variable
private var blackboardState : String = BASE;
// We will use a transparent Sprite that will be displayed on top of the blackboard and will
// capture mouse events
private var cristal : Sprite;
// The background
private var background : Shape;
// Reference to the shape we are working with
private var currentShape : BlackboardShape;
// The location of the mouse cursor
private var cursorPosition : Point;
public function Blackboard()
{
super();
}
// In this method we create the children of this component and make sure everything is correctly initialized
override protected function createChildren():void {
cristal = new Sprite ();
cristal.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
cristal.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
cristal.addEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);
cristal.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
background = new Shape ();
}
// When we detect a mouseDown event, it means the user is starting a new drawing
private function mouseDownHandler (event : MouseEvent) : void {
blackboardState = STARTING_PICTURE;
cursorPosition = new Point (event.localX, event.localY);
currentShape = new BlackboardShape ();
currentShape.x = cursorPosition.x;
currentShape.y = cursorPosition.y;
currentShape.addPoint (cursorPosition.x - currentShape.x, cursorPosition.y - currentShape.y);
invalidateDisplayList();
}
// When we capture a mouseUp, it means the user stops drawing, so my component will return to the base state
private function mouseUpHandler (event : MouseEvent) : void {
blackboardState = BASE;
}
// If the user moves the mouse out of this component, the component will return to the base state
// and the shape the user was drawing is finished
private function mouseOutHandler (event : MouseEvent) : void {
blackboardState = BASE;
}
// If the user was drawing a shape and moves the mouse, we must register a new point in the shape
private function mouseMoveHandler (event : MouseEvent) : void {
if (blackboardState==DRAWING) {
cursorPosition = new Point (event.localX, event.localY);
currentShape.addPoint (cursorPosition.x - currentShape.x, cursorPosition.y - currentShape.y);
invalidateDisplayList();
}
}
// This method configures the minimum and default size of the component
override protected function measure():void {
minHeight = measuredMinHeight = 300;
minWidth = measuredMinWidth = 400;
}
// This method renders changes in the visualization of the component when necessary
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
background.graphics.clear();
background.graphics.beginFill(0xFFFFFF, 1);
background.graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
background.graphics.endFill();
if (!this.contains(background)) {
this.addChild(background);
}
if (blackboardState==STARTING_PICTURE) {
this.addChildAt(currentShape, this.numChildren - 1); // We make sure not to add the shape on top of the cristal that dispatches mouseEvents
blackboardState = DRAWING;
}
// Update the cristal size to capture events. Just in case the size of the component has changed, I redraw it
cristal.graphics.clear();
cristal.graphics.beginFill(0xFFFFFF, 0);
cristal.graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
cristal.graphics.endFill();
if (!this.contains(cristal)) {
this.addChild(cristal);
}
}
}
}
You can see the whole example working here. Press right button -> View Source to see the full source code.
Recent Comments