Part IV: Event-Driven Programming
In Figure 21-3, I compare clicks inside the Pizza buttons TextField when all objects mouseChildren properties are set to true, when pizzaButtons mouseChildren property is set to false, and when windows mouseChildren property is set to false. In all cases, a click is made over the same place. The click is considered to have occurred on the innermost reachable object, which changes due to the attening effect of mouseChildren.
FIGURE 21-3
Effects of mouseChildren with nested display objects
default pizzaButton.mouseChildren = false window.mouseChildren = false
target = textfield
target = pizzaButton
target = window
Of course, these effects apply to keyboard events as well as mouse events, and with the two sets of properties, you can control them differently.
Listening for All Events
Because input events that arent canceled nd themselves eventually at the stage, you can capture all events of a given kind by subscribing to the stage. Listening to the stage is especially useful for games, where the players key and mouse input should be interpreted to affect her character regardless of the position of the cursor or focus.
Mouse Interactions
Mouse input is translated by Flash Player into events of type MouseEvent. Mouse input comes in many types, differentiated by the events type property. A quick summary of these events is shown in Table 21-1. Although these events may be self-explanatory, Ill take some time in the following sections to show exactly how the various mouse events work in context. As you may have noticed, some of these events overlap each other. For example, a click (MouseEvent.CLICK) is the aggregate of the user depressing the mouse (MOUSE_DOWN) and releasing it (MOUSE_UP) in rapid succession, possibly with a small tolerance of mouse motion (MOUSE_MOVE) as well. If you listen for all these events, you will receive all of them. This allows you to make your mouse input handling quick and simple or as nuanced as you like. The MouseEvent class denes a few properties that apply to mouse input. Some of these properties are related to specic types of mouse input and will remain undened for other types of event that
432
Chapter 21: Interactivity with the Mouse and Keyboard
use MouseEvent. Among the more important properties are four Numbers that describe the cursors location when the event took place:
localX, localY The cursors x and y position relative to the target of the mouse
activity
stageX, stageY The cursors global x and y position
Note that when handling a mouse event called event, event.localX should be equal to event.target.mouseX, and event.stageX should be equal to stage.mouseX (and likewise with the y properties). Recall from Chapter 14, Visual Programming with the Display List, that its easy to translate between coordinate spaces; having the position dened for both spaces is a convenience.
TABLE 21-1
Mouse Events
Event Name Description
MouseEvent.CLICK
The main button of the mouse was clicked.
MouseEvent.DOUBLE_CLICK The main button of the mouse was double-clicked. MouseEvent.MOUSE_DOWN MouseEvent.MOUSE_UP MouseEvent.MOUSE_MOVE MouseEvent.MOUSE_OVER MouseEvent.MOUSE_OUT MouseEvent.ROLL_OVER MouseEvent.ROLL_OUT MouseEvent.MOUSE_WHEEL The main button of the mouse was depressed. The main button of the mouse was released. The position of the mouse cursor moved. The cursor moved over the display object. The cursor moved off the display object. The cursor moved over the display object from outside it and its children. The cursor moved off the display object and any of its children. The mouse wheel was rotated.
Tip
On rare occasions, Ive experienced that the event objects location properties are incorrect. If your mouse position code seems to be error-prone in mouse event handlers, try using the mouseX and mouseY properties of the Stage instead; just ensure that the code always has access to the stage object, or you might run into an error.
Clicking
Flash Player makes it easy to detect clicks on objects. The users operating system interprets the time between the mouse button being depressed and released to determine whether these two events constitute a click. You dont have to worry about just how the click occurred. Remember that this and other mouse events are affected by the mouseEnabled and mouseChildren properties, not just for the objects you click but their ancestors on the display list.
433
Part IV: Event-Driven Programming
Detecting a click on any InteractiveObject is trivial, as Example 21-1 shows. EXAMPLE 21-1
http://actionscriptbible.com/ch21/ex1
Detecting a Click
package { import com.actionscriptbible.Example; import flash.events.MouseEvent; public class ch21ex1 extends Example { public function ch21ex1() { var circ:ClickableCircle = new ClickableCircle(); circ.name = "Circle"; circ.x = circ.y = 100; addChild(circ); circ.addEventListener(MouseEvent.CLICK, onClick); } protected function onClick(event:MouseEvent):void { trace(event.target.name + " clicked at " + event.localX + "," + event.localY); } } } import flash.display.Sprite; class ClickableCircle extends Sprite { public function ClickableCircle(color:uint = 0, size:Number = 50) { graphics.beginFill(color, 0.25); graphics.drawCircle(0, 0, size); graphics.endFill(); } }
Running this example and clicking the circle prints the name of the display object, along with the coordinates where you clicked.
Button Mode and the Hand Cursor
If you run Example 21-1, you will notice that although the ClickableCircle is indeed clickable, it doesnt indicate as much. It doesnt react to your mouse moving over it. If you want a hand cursor to appear in place of the normal pointer cursor, you can do so by indicating to Flash Player that the display object is a button. You can do this in two ways: make it a subclass of SimpleButton, or set its buttonMode property. As far as Flash Player is concerned, being a button means a few important things. One, a button is included in the tabbing order, so you can press the Tab key to put focus on the button. Two, if you are focused on the button, pressing the spacebar simulates a click. These two effects ensure that the user interface is accessible to the keyboard and assistive devices. You can always customize
434
Chapter 21: Interactivity with the Mouse and Keyboard
the focus and tabbing effects in more detail with the display objects tabEnabled, tabOrder, and focusRect properties. Three, buttons change your cursor to the systems hand cursor when hovered over. These effects are shown in Example 21-2.
EXAMPLE 21-2
http://actionscriptbible.com/ch21/ex2
Button Mode
package { import com.actionscriptbible.Example; import flash.events.MouseEvent; public class ch21ex2 extends Example { public function ch21ex2() { var circ:ClickableCircle = new ClickableCircle(); circ.name = "Circle"; circ.x = circ.y = 100; addChild(circ); circ.addEventListener(MouseEvent.CLICK, onClick); } protected function onClick(event:MouseEvent):void { trace(event.target.name + " clicked at " + event.localX + "," + event.localY); } } } import flash.display.Sprite; class ClickableCircle extends Sprite { public function ClickableCircle(color:uint = 0, size:Number = 50) { graphics.beginFill(color, 0.25); graphics.drawCircle(0, 0, size); graphics.endFill(); buttonMode = true; } }
Here youve added one line to Example 21-1 to enable button mode. In Sprite and other InteractiveObject subclasses, buttonMode defaults to false, except of course for SimpleButton, where it defaults to true. If you run this code, you see all three effects mentioned in action. You might also notice that, as promised, where there is no ll in a display object, it does not react to clicks. If you put your mouse just outside the edge of the circle, even though you may be inside the display objects bounding box (which you can see by tabbing to it), you wont trigger the hand cursor or any click events.
Complex Clicking
The MouseEvent.CLICK event is triggered when the users primary mouse button is clicked. Whether the device is an actual mouse or something like a tablet or a gamepad, how many buttons
435
Part IV: Event-Driven Programming
the pointing device has and so on is of no consequence. Which button is considered the primary button is also up to the user and his operating system. The secondary mouse button usually the right button does not trigger a mouse event in the Flash Player API. The secondary mouse button is reserved for displaying a standard context menu, which Flash Player retains some control over but you can indeed customize. Further mouse buttons, like a middle button, are not supported in Flash Player.
Note
In the AIR runtimes, you will have much more control over and access to mouse input. For example, the middle and right mouse buttons are supported. If you are developing for AIR, check the AS3LR for these additional events.
Keyboard Modiers
You can detect whether the user holds down certain keyboard modier keys while clicking. The MouseEvent object denes several Boolean properties you can use to detect these keys:
shiftKey Whether the Shift key is held down ctrlKey On PCs, whether the Control key is held down; on Macs, whether either of the Control or Command keys is held down altKey On PCs, whether the Alt key is held down; on Macs, ignored
Youll use modier keys in Example 21-6 to create and drag a copy of a display object instead of dragging the original object.
Double-Clicking
Flash Player even tells you if the user double-clicks a display object. Although this is off by default, simply set the display objects doubleClickEnabled to enable it, as shown in Example 21-3.
EXAMPLE 21-3
http://actionscriptbible.com/ch21/ex3
Detecting a Double-Click
package { import com.actionscriptbible.Example; import flash.events.MouseEvent; public class ch21ex3 extends Example { public function ch21ex3() { var circ:ClickableCircle = new ClickableCircle(); circ.name = "Circle"; circ.x = circ.y = 100; addChild(circ); circ.addEventListener(MouseEvent.CLICK, onClick); }
436
Chapter 21: Interactivity with the Mouse and Keyboard
protected function onClick(event:MouseEvent):void { trace(event.target.name + " clicked at " + event.localX + "," + event.localY); } } } import flash.display.Sprite; import flash.events.MouseEvent; class ClickableCircle extends Sprite { public function ClickableCircle(color:uint = 0, size:Number = 50) { graphics.beginFill(color, 0.25); graphics.drawCircle(0, 0, size); graphics.endFill(); buttonMode = true; doubleClickEnabled = true; addEventListener(MouseEvent.DOUBLE_CLICK, onDoubleClick); } protected function onDoubleClick(event:MouseEvent):void { alpha *= 0.5; if (alpha < 0.1) alpha = 1; } }
In this example, youve elected to have the ClickableCircle handle its own double-click event because you want this to be an effect of all ClickableCircles. If you try running the example, youll see that the click event is still broadcast. In fact, when you double-click an object, the events are dispatched in order:
mouseDown mouseUp click mouseDown mouseUp doubleClick
So you can still react to behavior on a lower level while using high-level events like MouseEvent .CLICK and MouseEvent.DOUBLE_CLICK.
Rollovers
Flash Player makes it easy to react to rollovers or hovering. You can use rollovers to change the appearance of a display object as the mouse hovers over it to emphasize the fact that its active. The SimpleButton class has this behavior built in. It switches between its upState, overState, and downState display objects automatically as the mouse leaves, enters, and depresses the button. You can easily add this behavior to any InteractiveObject by listening to the MouseEvent .ROLL_OVER and MouseEvent.ROLL_OUT events, as Example 21-4 shows.
437
Part IV: Event-Driven Programming
EXAMPLE 21-4
http://actionscriptbible.com/ch21/ex4
Reacting to Mouse Hover
package { import flash.display.Sprite; public class ch21ex4 extends Sprite { public function ch21ex4() { var circ:ClickableCircle = new ClickableCircle(); circ.x = circ.y = 100; addChild(circ); } } } import flash.display.Sprite; import flash.events.MouseEvent; class ClickableCircle extends Sprite { public function ClickableCircle(color:uint = 0, size:Number = 50) { graphics.beginFill(color, 0.25); graphics.drawCircle(0, 0, size); graphics.endFill(); addEventListener(MouseEvent.ROLL_OVER, onRollOver); addEventListener(MouseEvent.ROLL_OUT, onRollOut); onRollOut(null); //start in the "up"/not hovered state. } protected function onRollOver(event:MouseEvent):void { alpha = 1; } protected function onRollOut(event:MouseEvent):void { alpha = 0.5; } }
Two sets of events deal with hovering: MOUSE_OVER, MOUSE_OUT, ROLL_OVER, and ROLL_OUT. The difference between these is subtle. The ROLL_OVER and ROLL_OUT events apply to a display object and its children, but the MOUSE_OVER and MOUSE_OUT events only apply to the display object. When moving your mouse from an inner display object to an outer display object, MOUSE_OUT is dispatched on the inner object as MOUSE_OVER is dispatched on the outer object. The difference might not be noticeable, however, because these events bubble, and the MOUSE_OUT event is followed up closely by a MOUSE_OVER event, although their targets are different. In Example 21-5, you cancel bubbling to see just how the events differ. Youll notice that when you use MOUSE_OVER and MOUSE_OUT, as the blue circles on the right do, only the one specic display object under the mouse receives events, so the parent circles stroke disappears when you mouse onto its child. Its best experimented with interactively, so run the example if you have a chance.
438
Chapter 21: Interactivity with the Mouse and Keyboard
EXAMPLE 21-5
http://actionscriptbible.com/ch21/ex5
MOUSE OVER versus ROLL OVER
package { import flash.display.Sprite; import flash.events.MouseEvent; public class ch21ex5 extends Sprite { public function ch21ex5() { //use ROLL_OVER and ROLL_OUT on the left (red) var a:NestedCircles = new NestedCircles(true, 0xff0000); //use MOUSE_OVER and MOUSE_OUT on the right (blue) var b:NestedCircles = new NestedCircles(false, 0x0000ff); a.x = 100; b.x = 250; a.y = b.y = 150; addChild(a); addChild(b); } } } import flash.display.*; import flash.events.MouseEvent; class NestedCircles extends Sprite { public var child:NestedCircles; protected var stroke:Shape; public function NestedCircles(useRoll:Boolean, color:uint = 0, size:Number = 60, isChild:Boolean = false) { graphics.beginFill(color, 0.25); graphics.drawCircle(0, 0, size); graphics.endFill(); stroke = new Shape(); addChild(stroke); stroke.graphics.lineStyle(5, 0xffff00); stroke.graphics.drawCircle(0, 0, size); stroke.visible = false; if (useRoll) { addEventListener(MouseEvent.ROLL_OVER, handler); addEventListener(MouseEvent.ROLL_OUT, handler); } else { addEventListener(MouseEvent.MOUSE_OVER, handler); addEventListener(MouseEvent.MOUSE_OUT, handler); } if (!isChild) { child = new NestedCircles(useRoll, color, size/2, true); addChild(child); child.y = -size; } } protected function handler(event:MouseEvent):void { switch (event.type) {
continued
439
Part IV: Event-Driven Programming
EXAMPLE 21-5
(continued)
case MouseEvent.MOUSE_OUT: case MouseEvent.ROLL_OUT: stroke.visible = false; event.stopPropagation(); break; case MouseEvent.MOUSE_OVER: case MouseEvent.ROLL_OVER: stroke.visible = true; event.stopPropagation(); break; } } }
In most cases, youll probably want to stick to MouseEvent.ROLL_OVER and MouseEvent.ROLL_OUT. And dont forget the time-saving SimpleButton option. When using hover-related events, youre always moving the pointer from one object to another (even if one of these objects is the stage). Whereas the target property of the event object tells you the subject of this action the target of a MouseEvent.ROLL_OUT event tells you where youve rolled off of the relatedObject property tells you what the other party is where you rolled off onto. Another example: you MOUSE_OVER a target, but your mouse was previously over the relatedObject.
Dragging
A Sprites startDrag() and stopDrag() methods are perfect for implementing drag-and-drop behavior, but youll still need some event dispatching glue to put the whole thing together. Simply start dragging when the mouse button is pressed, and stop dragging when the mouse button is released. Example 21-6 shows how this works. EXAMPLE 21-6
http://actionscriptbible.com/ch21/ex6
Drag-and-Drop
package { import flash.display.Sprite; public class ch21ex6 extends Sprite { public function ch21ex6() { var circ:DraggableCircle = new DraggableCircle(); circ.x = circ.y = 100; addChild(circ); } } }
440
Chapter 21: Interactivity with the Mouse and Keyboard
import flash.display.Sprite; import flash.events.MouseEvent; class DraggableCircle extends Sprite { public function DraggableCircle() { graphics.beginFill(0, 0.5); graphics.drawCircle(0, 0, 50); graphics.endFill(); addEventListener(MouseEvent.MOUSE_DOWN, onStartDrag); buttonMode = true; } protected function onStartDrag(event:MouseEvent):void { if (event && event.shiftKey) { cloneAndDrag(); } else { startDrag(); stage.addEventListener(MouseEvent.MOUSE_UP, onStopDrag); } } protected function onStopDrag(event:MouseEvent):void { stage.removeEventListener(MouseEvent.MOUSE_UP, onStopDrag); stopDrag(); } protected function cloneAndDrag():void { var copy:DraggableCircle = new DraggableCircle(); copy.x = this.x; copy.y = this.y; this.parent.addChild(copy); copy.onStartDrag(null); } }
Ive done one or two tricky things here, though. Ive listened for MouseEvent.MOUSE_UP on the stage, so that Ill receive that event no matter what display object the mouse was over when the event occurred (provided nobody cancels the event prior to it bubbling up to the stage). I did this just in case you managed to have your mouse cursor escape the boundaries of the object in question. Because its dragging, it should follow under your mouse cursor no matter what, but in its dragging travels it may pass under other display objects, or it might change shape or size slightly due to rollover effects, although it doesnt here. I just want to make sure that, no matter what, the next time you release the mouse button, the currently dragging object stops dragging. To complete this cycle, of course, I have to remove the event listener from the stage immediately when the mouse is released. Additionally, Ive looked at the state of the Shift key when the mouse is depressed. If you have the Shift key down, instead of dragging the circle, it makes a clone of the circle and drags that. Event handler methods are still normal methods, and they may be called directly as I have done here. However, if you pass an event object, it had better be valid. Because I sometimes pass null to the event handler, in its body I have to make sure I dont assume event is non-null.
441
Part IV: Event-Driven Programming
Position Tracking and Cursors
You already have the tools in your arsenal to follow the mouse around. Simply listen for MouseEvent.MOUSE_MOVE on the stage, and youll be updated with the mouses movement. You can use this to display custom cursors and mouse trails, control a game character, or even just to stare at you creepily, as a big pair of eyes do in Example 21-7. EXAMPLE 21-7
http://actionscriptbible.com/ch21/ex7
Following Mouse Movement
package { import flash.display.Sprite; public class ch21ex7 extends Sprite { public function ch21ex7() { var leftEye:Eye = new Eye(60); var rightEye:Eye = new Eye(60); leftEye.y = rightEye.y = stage.stageHeight/2; leftEye.x = stage.stageWidth * 1/3 rightEye.x = stage.stageWidth * 2/3; addChild(leftEye); addChild(rightEye); } } } import flash.display.*; import flash.events.*; class Eye extends Sprite { protected var pupil:Shape; public function Eye(size:Number) { graphics.lineStyle(3); graphics.beginFill(0xffffff); graphics.drawCircle(0, 0, size); graphics.endFill(); pupil = new Shape(); addChild(pupil); pupil.graphics.lineStyle(); pupil.graphics.beginFill(0x603030); pupil.graphics.drawCircle(3/4 * size, 0, size/4); scaleY = 2; addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); } protected function onAddedToStage(event:Event):void { stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove); } protected function onMouseMove(event:MouseEvent):void { pupil.rotation = Math.atan2(this.mouseY, this.mouseX) / Math.PI * 180; } }
442