/**
* @author Trevor McCauley
* @link www.senocular.com
*/
package org.papervision3d.core.utils.virtualmouse
{
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.InteractiveObject;
import flash.display.SimpleButton;
import flash.display.Sprite;
import flash.display.Stage;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.utils.Dictionary;
import org.papervision3d.core.log.PaperLogger;
/**
* Dispatched when the virtual mouse state is updated.
* @eventType flash.events.Event
*/
[Event(name="update", type="flash.events.Event")]
/**
* Dispatched when the virtual mouse fires an
* Event.MOUSE_LEAVE event.
* @eventType flash.events.Event
*/
[Event(name="mouseLeave", type="flash.events.Event")]
/**
* Dispatched when the virtual mouse fires an
* MouseEvent.MOUSE_MOVE event.
* @eventType flash.events.MouseEvent
*/
[Event(name="mouseMove", type="flash.events.MouseEvent")]
/**
* Dispatched when the virtual mouse fires an
* MouseEvent.MOUSE_OUT event.
* @eventType flash.events.MouseEvent
*/
[Event(name="mouseOut", type="flash.events.MouseEvent")]
/**
* Dispatched when the virtual mouse fires an
* MouseEvent.ROLL_OUT event.
* @eventType flash.events.MouseEvent
*/
[Event(name="rollOut", type="flash.events.MouseEvent")]
/**
* Dispatched when the virtual mouse fires an
* MouseEvent.MOUSE_OVER event.
* @eventType flash.events.MouseEvent
*/
[Event(name="mouseOver", type="flash.events.MouseEvent")]
/**
* Dispatched when the virtual mouse fires an
* MouseEvent.ROLL_OVER event.
* @eventType flash.events.MouseEvent
*/
[Event(name="rollOver", type="flash.events.MouseEvent")]
/**
* Dispatched when the virtual mouse fires an
* MouseEvent.MOUSE_DOWN event.
* @eventType flash.events.MouseEvent
*/
[Event(name="mouseDown", type="flash.events.MouseEvent")]
/**
* Dispatched when the virtual mouse fires an
* MouseEvent.MOUSE_UP event.
* @eventType flash.events.MouseEvent
*/
[Event(name="mouseUp", type="flash.events.MouseEvent")]
/**
* Dispatched when the virtual mouse fires an
* MouseEvent.CLICK event.
* @eventType flash.events.MouseEvent
*/
[Event(name="click", type="flash.events.MouseEvent")]
/**
* Dispatched when the virtual mouse fires an
* MouseEvent.DOUBLE_CLICK event.
* @eventType flash.events.MouseEvent
*/
[Event(name="doubleClick", type="flash.events.MouseEvent")]
/**
* The VirtualMouse class is used to create a programmatic
* version of the users mouse that can be moved about the
* Flash player stage firing off mouse events of the display
* objects it interacts with. This can allow you to simulate
* interaction with buttons and movie clips through ActionScript.
*
* Handled events include:
* Event.MOUSE_LEAVE,
* MouseEvent.MOUSE_MOVE,
* MouseEvent.MOUSE_OUT,
* MouseEvent.ROLL_OUT,
* MouseEvent.MOUSE_OVER,
* MouseEvent.ROLL_OVER,
* MouseEvent.MOUSE_DOWN,
* MouseEvent.MOUSE_UP.
* MouseEvent.CLICK, and,
* MouseEvent.DOUBLE_CLICK.
* Along with dispatching those events for their respective
* targets, the VirtualMouse instance will also dispatch the
* event on itself allowing to capture which events are being
* fired by the virtual mouse. The last event fired can also
* be referenced in the lastEvent property.
*
* VirtualMouse mouse cannot:
* activate states of SimpleButton instances,
* change object focus,
* handle mouseWheel related events,
* change the system's cursor location, or
* spoof the location of the mouseX and mouseY properties
* (which some components rely on).
*/
public class VirtualMouse extends EventDispatcher {
public static const UPDATE:String = "update";
private var altKey:Boolean = false;
private var ctrlKey:Boolean = false;
private var shiftKey:Boolean = false;
private var delta:int = 0; // mouseWheel unsupported
private var _stage:Stage;
private var _container:Sprite;
private var target:InteractiveObject;
private var location:Point;
private var isLocked:Boolean = false;
private var isDoubleClickEvent:Boolean = false;
private static var _mouseIsDown:Boolean = false;
private var disabledEvents:Object = new Object();
private var ignoredInstances:Dictionary = new Dictionary(true);
private var _lastEvent:Event;
private var lastMouseDown:Boolean = false;
private var updateMouseDown:Boolean = false;
private var lastLocation:Point;
private var lastDownTarget:DisplayObject;
private var lastWithinStage:Boolean = true;
private var _useNativeEvents:Boolean = false;
private var eventEvent:Class = VirtualMouseEvent;
private var mouseEventEvent:Class = VirtualMouseMouseEvent;
/**
* A reference to the Stage instance. This
* reference needs to be passed to the
* VirtualMouse instance either in its constructor
* or through assigning it's stage property.
* Without a valid reference to the stage, the
* virtual mouse will not function.
* @see VirtualMouse()
*/
public function get stage():Stage {
return _stage;
}
public function set stage(s:Stage):void {
var hadStage:Boolean;
if (_stage){
hadStage = true;
_stage.removeEventListener(KeyboardEvent.KEY_DOWN, keyHandler);
_stage.removeEventListener(KeyboardEvent.KEY_UP, keyHandler);
}else{
hadStage = false;
}
_stage = s;
if (_stage) {
_stage.addEventListener(KeyboardEvent.KEY_DOWN, keyHandler);
_stage.addEventListener(KeyboardEvent.KEY_UP, keyHandler);
target = _stage;
if (!hadStage) update();
}
}
/**
*
* @param value Sprite container you want VirtualMouse to use with its testing of sub containers
* @return
*/
public function set container(value:Sprite):void
{
_container = value;
}
public function get container():Sprite { return _container; }
/**
* The last event dispatched by the VirtualMouse
* instance. This can be useful for preventing
* event recursion if performing VirtualMouse
* operations within MouseEvent handlers.
*/
public function get lastEvent():Event {
return _lastEvent;
}
/**
* True if the virtual mouse is being
* pressed, false if not. The mouse is
* down for the virtual mouse if press()
* was called.
* @see press()
* @see release()
*/
public function get mouseIsDown():Boolean {
return _mouseIsDown;
}
/**
* The x location of the virtual mouse. If you are
* setting both the x and y properties of the
* virtual mouse at the same time, you would probably
* want to lock the VirtualMouse instance to prevent
* additional events from firing.
* @see lock
* @see unlock
* @see y
* @see setLocation()
* @see getLocation()
*/
public function get x():Number {
return location.x;
}
public function set x(n:Number):void {
location.x = n;
if (!isLocked) update();
}
/**
* The y location of the virtual mouse. If you are
* setting both the x and y properties of the
* virtual mouse at the same time, you would probably
* want to lock the VirtualMouse instance to prevent
* additional events from firing.
* @see lock
* @see unlock
* @see x
* @see setLocation()
* @see getLocation()
*/
public function get y():Number {
return location.y;
}
public function set y(n:Number):void {
location.y = n;
if (!isLocked) update();
}
/**
* Determines if the events dispatched by the
* VirtualMouse instance are IVirualMouseEvent
* Events (wrapping Event and MouseEvent) or events
* of the native Event and MouseEvent type. When using
* non-native events, you can check to see if the
* events originated from VirtualMouse by seeing if
* the events are of the type IVirualMouseEvent.
* @see lastEvent
*/
public function get useNativeEvents():Boolean {
return _useNativeEvents;
}
public function set useNativeEvents(b:Boolean):void {
if (b == _useNativeEvents) return;
_useNativeEvents = b;
if (_useNativeEvents){
eventEvent = VirtualMouseEvent;
mouseEventEvent = VirtualMouseMouseEvent;
}else{
eventEvent = Event;
mouseEventEvent = MouseEvent;
}
}
/**
* Initializes a new VirtualMouse instance.
* @param stage A reference to the stage instance.
* @param startX The initial x location of
* the virtual mouse.
* @param startY The initial y location of
* the virtual mouse.
*/
public function VirtualMouse(stage:Stage = null, container:Sprite = null, startX:Number = 0, startY:Number = 0) {
this.stage = stage;
this.container = container;
location = new Point(startX, startY);
lastLocation = location.clone();
addEventListener(UPDATE, handleUpdate);
update();
}
/**
* Returns the location (x and y) of the current
* VirtualMouse instance. The location of the
* virtual mouse is based in the global
* coordinate space.
* @return A Point instance representing the
* location of the virtual mouse in
* global coordinate space.
* @see x
* @see y
* @see setLocation()
*/
public function getLocation():Point {
return location.clone();
}
/**
* Sets the location (x and y) of the current
* VirtualMouse instance. There are two ways to
* call setLocation, either passing in a single
* Point instance, or by passing in two Number
* instances representing x and y coordinates.
* The location of the virtual mouse is based in
* the global coordinate space.
* @param a A Point instance or x Number value.
* @param b A y Number value if a is a Number.
* @see x
* @see y
* @see getLocation()
*/
public function setLocation(a:*, b:* = null):void
{
//log.debug("VM setLocation", a, b);
if (a is Point) {
var loc:Point = a as Point;
location.x = loc.x;
location.y = loc.y;
}else{
location.x = Number(a);
location.y = Number(b);
}
if (!isLocked) update();
}
/**
* Locks the current VirtualMouse instance
* preventing updates from being made as
* properties change within the instance.
* To release and allow an update, call unlock().
* @see lock()
* @see update()
*/
public function lock():void {
isLocked = true;
}
/**
* Unlocks the current VirtualMouse instance
* allowing updates to be made for the
* dispatching of virtual mouse events. After
* unlocking the instance, it will update and
* additional calls to press(), release(), or
* changing the location of the virtual mouse
* will also invoke updates.
* @see lock()
* @see update()
*/
public function unlock():void {
isLocked = false;
update();
}
/**
* Allows you to disable an event by type
* preventing the virtual mouse from
* dispatching that event during an update.
* @param type The type for the event to
* disable, e.g. MouseEvent.CLICK
* @see enableEvent()
*/
public function disableEvent(type:String):void {
disabledEvents[type] = true;
}
/**
* Re-enables an event disabled with
* disableEvent.
* @param type The type for the event to
* enable, e.g. MouseEvent.CLICK
* @see disableEvent()
*/
public function enableEvent(type:String):void {
if (type in disabledEvents) {
delete disabledEvents[type];
}
}
/**
* Ignores a display object preventing that
* object from recieving events from the
* virtual mouse. This is useful for instances
* used for cursors which may always be under
* the virtual mouse's location.
* @param instance A reference to the
* DisplayObject instance to ignore.
* @see unignore()
*/
public function ignore(instance:DisplayObject):void {
ignoredInstances[instance] = true;
}
/**
* Removes an instance from the ignore list
* defined by ignore(). When an ingored
* object is passed into unignore(), it will
* be able to receive events from the virtual
* mouse.
* @param instance A reference to the
* DisplayObject instance to unignore.
* @see ignore()
*/
public function unignore(instance:DisplayObject):void {
if (instance in ignoredInstances){
delete ignoredInstances[instance];
}
}
/**
* Simulates the pressing of the left
* mouse button. To release the mouse
* button, use release().
* @see release()
* @see click()
*/
public function press():void {
//if (_mouseIsDown) return;
updateMouseDown = true;
_mouseIsDown = true;
if (!isLocked) update();
}
/**
* Simulates the release of the left
* mouse button. This method has no
* effect unless press() was called first.
* @see press()
* @see click()
*/
public function release():void {
//if (!_mouseIsDown) return;
updateMouseDown = true;
_mouseIsDown = false;
if (!isLocked) update();
}
/**
* Simulates a click of the left
* mouse button (press and release)
* @see press()
* @see release()
* @see click()
* @see doubleClick()
*/
public function click():void {
press();
release();
}
/**
* Simulates a double-click of the left
* mouse button (press and release twice).
* Calling this command is the only way to
* simulate a double-click for the virtual
* mouse. Calling press() and release() or
* click() is rapid succession will not
* invoke a double-click event. The double-click
* event will also only fire for an instance
* if it's doubleClickEnabled property is
* set to true.
* @see click()
*/
public function doubleClick():void {
// if locked, doubleClick will
// not fire but the mouse will
// be released if not already
if (isLocked) {
release();
}else{
// call update with a click, press, then release
// and double-click notification for release
click();
press();
isDoubleClickEvent = true;
release();
isDoubleClickEvent = false;
}
}
/*Added by Jim Kremens kremens@gmail.com 08/16/07 */
public function exitContainer():void {
if( !container ) return;
var targetLocal:Point = target.globalToLocal(location);
//log.debug("Targetlocal", target != container);
if (!disabledEvents[MouseEvent.MOUSE_OUT]) {
_lastEvent = new mouseEventEvent(MouseEvent.MOUSE_OUT, true, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta);
container.dispatchEvent(new mouseEventEvent(MouseEvent.MOUSE_OUT, true, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta));
dispatchEvent(new mouseEventEvent(MouseEvent.MOUSE_OUT, true, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta));
}
if (!disabledEvents[MouseEvent.ROLL_OUT]) { // rolls do not propagate
_lastEvent = new mouseEventEvent(MouseEvent.ROLL_OUT, false, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta);
container.dispatchEvent(new mouseEventEvent(MouseEvent.ROLL_OUT, false, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta));
dispatchEvent(new mouseEventEvent(MouseEvent.ROLL_OUT, false, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta));
}
if (target != container) {
if (!disabledEvents[MouseEvent.MOUSE_OUT]) {
_lastEvent = new mouseEventEvent(MouseEvent.MOUSE_OUT, true, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta);
target.dispatchEvent(new mouseEventEvent(MouseEvent.MOUSE_OUT, true, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta));
dispatchEvent(new mouseEventEvent(MouseEvent.MOUSE_OUT, true, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta));
}
if (!disabledEvents[MouseEvent.ROLL_OUT]) { // rolls do not propagate
_lastEvent = new mouseEventEvent(MouseEvent.ROLL_OUT, false, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta);
target.dispatchEvent(new mouseEventEvent(MouseEvent.ROLL_OUT, false, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta));
dispatchEvent(new mouseEventEvent(MouseEvent.ROLL_OUT, false, false, targetLocal.x, targetLocal.y, container, ctrlKey, altKey, shiftKey, _mouseIsDown, delta));
}
}
//reset the target to the stage, which is the value it starts at.
target = _stage;
}
/**
* Updates the VirtualMouse instance's state
* to reflect a change in the virtual mouse.
* Within this method all events will be dispatched.
* update() is called any time a VirtualMouse
* property is changed unless lock() was used to
* lock the instance. update() will then not be
* called until unlock() is used to unlock
* the instance. Typically you would never call
* update() directly; it is called automatically
* by the VirtualMouse class. Calling update()
* manually will override lock(). Whenever update()
* is called, the UPDATE event is dispatched.
* @see lock()
* @see unlock()
*/
public function update():void {
// dispatch an update event indicating that
// an update has occured
dispatchEvent(new Event(UPDATE, false, false));
}
private function handleUpdate(event:Event):void
{
if (!container) return;
// go through each objectsUnderPoint checking:
// 1) is not ignored
// 2) is InteractiveObject
// 3) mouseEnabled
// 4) all parents have mouseChildren
// if not interactive object, defer interaction to next object in list
// if is interactive and enabled, give interaction and ignore rest
// var p:Point = CoordinateTools.localToLocal(container, stage, location);
//var objectsUnderPoint:Array = container.getObjectsUnderPoint(p);
if( container.scrollRect ){
PaperLogger.warning("The container that virtualMouse is trying to test against has a scrollRect defined, and may cause an issue with finding objects under a defined point. Use MovieMaterial.rect to set a rectangle area instead");
}
var originalPoint:Point = new Point();
originalPoint.x = container.x;
originalPoint.y = container.y;
container.x = container.y = 0;
var objectsUnderPoint:Array = container.getObjectsUnderPoint(location);
container.x = originalPoint.x;
container.y = originalPoint.y;
var currentTarget:InteractiveObject;
var currentParent:DisplayObject;
var i:int = objectsUnderPoint.length;
while (i--) {
currentParent = objectsUnderPoint[i];
// go through parent hierarchy
while (currentParent) {
// don't use ignored instances as the target
if (ignoredInstances[currentParent]
) {
currentTarget = null;
break;
}
// invalid target if in a SimpleButton
if (currentTarget && currentParent is SimpleButton)
{
//log.debug("found SimpleButton - setting currentTarget to null");
currentTarget = null;
// invalid target if a parent has a
// false mouseChildren
} else if (currentTarget && !DisplayObjectContainer(currentParent).mouseChildren)
{
//log.debug("parent false mouseChildren - setting currentTarget to null");
currentTarget = null;
}
// define target if an InteractiveObject
// and mouseEnabled is true
if (!currentTarget && currentParent is InteractiveObject && InteractiveObject(currentParent).mouseEnabled)
{
//log.debug("found InteractiveObject && mouseEnabled = true - setting currentTarget");
currentTarget = InteractiveObject(currentParent);
}
// next parent in hierarchy
currentParent = currentParent.parent;
}
// if a currentTarget was found
// ignore all other objectsUnderPoint
if (currentTarget){
break;
}
}
// if a currentTarget was not found
// the currentTarget is the stage
if (!currentTarget)
{
//trace("no CurrentTarget", container.hitTestPoint(location.x, location.y));
currentTarget = container;
//currentTarget = _stage;
//log.debug("no new target found, using stage");
}
// get local coordinate locations
var targetLocal:Point = target.globalToLocal(location);
var currentTargetLocal:Point = currentTarget.globalToLocal(location);
// move event
if (lastLocation.x != location.x || lastLocation.y != location.y)
{
var withinStage:Boolean = false;
if(stage) withinStage = (location.x >= 0 && location.y >= 0 && location.x <= stage.stageWidth && location.y <= stage.stageHeight);
// mouse leave if left stage
if (!withinStage && lastWithinStage && !disabledEvents[Event.MOUSE_LEAVE]){
_lastEvent = new eventEvent(Event.MOUSE_LEAVE, false, false);
stage.dispatchEvent(_lastEvent);
dispatchEvent(_lastEvent);
}
// only mouse move if within stage
if (withinStage && !disabledEvents[MouseEvent.MOUSE_MOVE]){
_lastEvent = new mouseEventEvent(MouseEvent.MOUSE_MOVE, true, false, currentTargetLocal.x, currentTargetLocal.y, currentTarget, ctrlKey, altKey, shiftKey, _mouseIsDown, delta);
currentTarget.dispatchEvent(_lastEvent);
dispatchEvent(_lastEvent);
}
// remember if within stage
lastWithinStage = withinStage;
}
// roll/mouse (out and over) events
if (currentTarget != target)
{
// off of last target
if (!disabledEvents[MouseEvent.MOUSE_OUT]){
_lastEvent = new mouseEventEvent(MouseEvent.MOUSE_OUT, true, false, targetLocal.x, targetLocal.y, currentTarget, ctrlKey, altKey, shiftKey, _mouseIsDown, delta);
target.dispatchEvent(_lastEvent);
dispatchEvent(_lastEvent);
}
if (!disabledEvents[MouseEvent.ROLL_OUT]){ // rolls do not propagate
_lastEvent = new mouseEventEvent(MouseEvent.ROLL_OUT, false, false, targetLocal.x, targetLocal.y, currentTarget, ctrlKey, altKey, shiftKey, _mouseIsDown, delta);
target.dispatchEvent(_lastEvent);
dispatchEvent(_lastEvent);
}
// on to current target
if (!disabledEvents[MouseEvent.MOUSE_OVER]){
//log.debug("*** MOUSEOVER****");
_lastEvent = new mouseEventEvent(MouseEvent.MOUSE_OVER, true, false, currentTargetLocal.x, currentTargetLocal.y, target, ctrlKey, altKey, shiftKey, _mouseIsDown, delta);
currentTarget.dispatchEvent(_lastEvent);
dispatchEvent(_lastEvent);
}
if (!disabledEvents[MouseEvent.ROLL_OVER]){ // rolls do not propagate
//log.debug("*** ROLLOVER****");
_lastEvent = new mouseEventEvent(MouseEvent.ROLL_OVER, false, false, currentTargetLocal.x, currentTargetLocal.y, target, ctrlKey, altKey, shiftKey, _mouseIsDown, delta);
currentTarget.dispatchEvent(_lastEvent);
dispatchEvent(_lastEvent);
}
}
// click/up/down events
if (updateMouseDown) {
if (_mouseIsDown) {
if (!disabledEvents[MouseEvent.MOUSE_DOWN]){
_lastEvent = new mouseEventEvent(MouseEvent.MOUSE_DOWN, true, false, currentTargetLocal.x, currentTargetLocal.y, currentTarget, ctrlKey, altKey, shiftKey, _mouseIsDown, delta);
currentTarget.dispatchEvent(_lastEvent);
dispatchEvent(_lastEvent);
}
// remember last down
lastDownTarget = currentTarget;
updateMouseDown = false;
// mouse is up
}else{
if (!disabledEvents[MouseEvent.MOUSE_UP]){
_lastEvent = new mouseEventEvent(MouseEvent.MOUSE_UP, true, false, currentTargetLocal.x, currentTargetLocal.y, currentTarget, ctrlKey, altKey, shiftKey, _mouseIsDown, delta);
currentTarget.dispatchEvent(_lastEvent);
dispatchEvent(_lastEvent);
}
if (!disabledEvents[MouseEvent.CLICK] && currentTarget == lastDownTarget) {
_lastEvent = new mouseEventEvent(MouseEvent.CLICK, true, false, currentTargetLocal.x, currentTargetLocal.y, currentTarget, ctrlKey, altKey, shiftKey, _mouseIsDown, delta);
currentTarget.dispatchEvent(_lastEvent);
dispatchEvent(_lastEvent);
}
// clear last down
lastDownTarget = null;
updateMouseDown = false;
}
}
// explicit call to doubleClick()
if (isDoubleClickEvent && !disabledEvents[MouseEvent.DOUBLE_CLICK] && currentTarget.doubleClickEnabled) {
_lastEvent = new mouseEventEvent(MouseEvent.DOUBLE_CLICK, true, false, currentTargetLocal.x, currentTargetLocal.y, currentTarget, ctrlKey, altKey, shiftKey, _mouseIsDown, delta);
currentTarget.dispatchEvent(_lastEvent);
dispatchEvent(_lastEvent);
}
// remember last values
lastLocation = location.clone();
lastMouseDown = _mouseIsDown;
target = currentTarget;
}
private function keyHandler(event:KeyboardEvent):void {
// update properties used in MouseEvents
altKey = event.altKey;
ctrlKey = event.ctrlKey;
shiftKey = event.shiftKey;
}
}
}