Files
2013-04-25 13:56:18 -07:00

270 lines
11 KiB
ActionScript

// =================================================================================================
//
// Starling Framework
// Copyright 2011 Gamua OG. All Rights Reserved.
//
// This program is free software. You can redistribute and/or modify it
// in accordance with the terms of the accompanying license agreement.
//
// =================================================================================================
package starling.events
{
import flash.geom.Point;
import starling.display.DisplayObject;
import starling.display.Stage;
/** @private
* The TouchProcessor is used internally to convert mouse and touch events of the conventional
* Flash stage to Starling's TouchEvents. */
public class TouchProcessor
{
private static const MULTITAP_TIME:Number = 0.3;
private static const MULTITAP_DISTANCE:Number = 25;
private var mStage:Stage;
private var mElapsedTime:Number;
private var mTouchMarker:TouchMarker;
private var mCurrentTouches:Vector.<Touch>;
private var mQueue:Vector.<Array>;
private var mLastTaps:Vector.<Touch>;
private var mShiftDown:Boolean = false;
private var mCtrlDown:Boolean = false;
/** Helper objects. */
private static var sProcessedTouchIDs:Vector.<int> = new <int>[];
private static var sHoveringTouchData:Vector.<Object> = new <Object>[];
public function TouchProcessor(stage:Stage)
{
mStage = stage;
mElapsedTime = 0;
mCurrentTouches = new <Touch>[];
mQueue = new <Array>[];
mLastTaps = new <Touch>[];
mStage.addEventListener(KeyboardEvent.KEY_DOWN, onKey);
mStage.addEventListener(KeyboardEvent.KEY_UP, onKey);
}
public function dispose():void
{
mStage.removeEventListener(KeyboardEvent.KEY_DOWN, onKey);
mStage.removeEventListener(KeyboardEvent.KEY_UP, onKey);
if (mTouchMarker) mTouchMarker.dispose();
}
public function advanceTime(passedTime:Number):void
{
var i:int;
var touchID:int;
var touch:Touch;
mElapsedTime += passedTime;
// remove old taps
if (mLastTaps.length > 0)
{
for (i=mLastTaps.length-1; i>=0; --i)
if (mElapsedTime - mLastTaps[i].timestamp > MULTITAP_TIME)
mLastTaps.splice(i, 1);
}
while (mQueue.length > 0)
{
sProcessedTouchIDs.length = sHoveringTouchData.length = 0;
// update existing touches
for each (var currentTouch:Touch in mCurrentTouches)
{
// set touches that were new or moving to phase 'stationary'
if (currentTouch.phase == TouchPhase.BEGAN || currentTouch.phase == TouchPhase.MOVED)
currentTouch.setPhase(TouchPhase.STATIONARY);
// check if target is still connected to stage, otherwise find new target
if (currentTouch.target.stage == null)
currentTouch.setTarget(mStage.hitTest(
new Point(currentTouch.globalX, currentTouch.globalY), true));
}
// process new touches, but each ID only once
while (mQueue.length > 0 &&
sProcessedTouchIDs.indexOf(mQueue[mQueue.length-1][0]) == -1)
{
var touchArgs:Array = mQueue.pop();
touchID = touchArgs[0] as int;
touch = getCurrentTouch(touchID);
// hovering touches need special handling (see below)
if (touch && touch.phase == TouchPhase.HOVER && touch.target)
sHoveringTouchData.push({ touch: touch, target: touch.target });
processTouch.apply(this, touchArgs);
sProcessedTouchIDs.push(touchID);
}
// if the target of a hovering touch changed, we dispatch an event to the previous
// target to notify it that it's no longer being hovered over.
for each (var touchData:Object in sHoveringTouchData)
if (touchData.touch.target != touchData.target)
touchData.target.dispatchEvent(new TouchEvent(
TouchEvent.TOUCH, mCurrentTouches, mShiftDown, mCtrlDown));
// dispatch events
for each (touchID in sProcessedTouchIDs)
{
touch = getCurrentTouch(touchID);
if (touch.target)
touch.target.dispatchEvent(new TouchEvent(TouchEvent.TOUCH, mCurrentTouches,
mShiftDown, mCtrlDown));
}
// remove ended touches
for (i=mCurrentTouches.length-1; i>=0; --i)
if (mCurrentTouches[i].phase == TouchPhase.ENDED)
mCurrentTouches.splice(i, 1);
// timestamps must differ for remaining touches
if (mQueue.length != 0) mElapsedTime += 0.00001;
}
}
public function enqueue(touchID:int, phase:String, globalX:Number, globalY:Number):void
{
mQueue.unshift(arguments);
// multitouch simulation (only with mouse)
if (mCtrlDown && simulateMultitouch && touchID == 0)
{
mTouchMarker.moveMarker(globalX, globalY, mShiftDown);
mQueue.unshift([1, phase, mTouchMarker.mockX, mTouchMarker.mockY]);
}
}
private function processTouch(touchID:int, phase:String, globalX:Number, globalY:Number):void
{
var position:Point = new Point(globalX, globalY);
var touch:Touch = getCurrentTouch(touchID);
if (touch == null)
{
touch = new Touch(touchID, globalX, globalY, phase, null);
addCurrentTouch(touch);
}
touch.setPosition(globalX, globalY);
touch.setPhase(phase);
touch.setTimestamp(mElapsedTime);
if (phase == TouchPhase.HOVER || phase == TouchPhase.BEGAN)
touch.setTarget(mStage.hitTest(position, true));
if (phase == TouchPhase.BEGAN)
processTap(touch);
}
private function onKey(event:KeyboardEvent):void
{
if (event.keyCode == 17 || event.keyCode == 15) // ctrl or cmd key
{
var wasCtrlDown:Boolean = mCtrlDown;
mCtrlDown = event.type == KeyboardEvent.KEY_DOWN;
if (simulateMultitouch && wasCtrlDown != mCtrlDown)
{
mTouchMarker.visible = mCtrlDown;
mTouchMarker.moveCenter(mStage.stageWidth/2, mStage.stageHeight/2);
var mouseTouch:Touch = getCurrentTouch(0);
var mockedTouch:Touch = getCurrentTouch(1);
if (mouseTouch)
mTouchMarker.moveMarker(mouseTouch.globalX, mouseTouch.globalY);
// end active touch ...
if (wasCtrlDown && mockedTouch && mockedTouch.phase != TouchPhase.ENDED)
mQueue.unshift([1, TouchPhase.ENDED, mockedTouch.globalX, mockedTouch.globalY]);
// ... or start new one
else if (mCtrlDown && mouseTouch)
{
if (mouseTouch.phase == TouchPhase.BEGAN || mouseTouch.phase == TouchPhase.MOVED)
mQueue.unshift([1, TouchPhase.BEGAN, mTouchMarker.mockX, mTouchMarker.mockY]);
else
mQueue.unshift([1, TouchPhase.HOVER, mTouchMarker.mockX, mTouchMarker.mockY]);
}
}
}
else if (event.keyCode == 16) // shift key
{
mShiftDown = event.type == KeyboardEvent.KEY_DOWN;
}
}
private function processTap(touch:Touch):void
{
var nearbyTap:Touch = null;
var minSqDist:Number = MULTITAP_DISTANCE * MULTITAP_DISTANCE;
for each (var tap:Touch in mLastTaps)
{
var sqDist:Number = Math.pow(tap.globalX - touch.globalX, 2) +
Math.pow(tap.globalY - touch.globalY, 2);
if (sqDist <= minSqDist)
{
nearbyTap = tap;
break;
}
}
if (nearbyTap)
{
touch.setTapCount(nearbyTap.tapCount + 1);
mLastTaps.splice(mLastTaps.indexOf(nearbyTap), 1);
}
else
{
touch.setTapCount(1);
}
mLastTaps.push(touch.clone());
}
private function addCurrentTouch(touch:Touch):void
{
for (var i:int=mCurrentTouches.length-1; i>=0; --i)
if (mCurrentTouches[i].id == touch.id)
mCurrentTouches.splice(i, 1);
mCurrentTouches.push(touch);
}
private function getCurrentTouch(touchID:int):Touch
{
for each (var touch:Touch in mCurrentTouches)
if (touch.id == touchID) return touch;
return null;
}
public function get simulateMultitouch():Boolean { return mTouchMarker != null; }
public function set simulateMultitouch(value:Boolean):void
{
if (simulateMultitouch == value) return; // no change
if (value)
{
mTouchMarker = new TouchMarker();
mTouchMarker.visible = false;
mStage.addChild(mTouchMarker);
}
else
{
mTouchMarker.removeFromParent(true);
mTouchMarker = null;
}
}
}
}