--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/idlehomescreen/xmluirendering/uiengine/src/xngesture.cpp Thu Dec 17 08:40:49 2009 +0200
@@ -0,0 +1,752 @@
+/*
+* Copyright (c) 2008-2008 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description: Gesture class
+*
+*/
+
+#include <e32math.h>
+
+#include "xngesture.h"
+#include "xngesturedefs.h"
+
+using namespace XnGestureHelper;
+
+// ======== LOCAL FUNCTIONS ===================================================
+
+/**
+ * Point array for which only x axis is relevant
+ */
+class TXAxisPointArray : public TXnPointArray
+ {
+public:
+ TXAxisPointArray( const RArray< TXnPointEntry >& aPoints )
+ : TXnPointArray( aPoints )
+ {
+ }
+
+ // from TXnPointArray
+ TPoint operator[]( TInt aIndex ) const
+ {
+ return TPoint( Raw( aIndex ).iX, 0 );
+ }
+ };
+
+/**
+ * Point array for which only y axis is relevant
+ */
+class TYAxisPointArray : public TXnPointArray
+ {
+public:
+ TYAxisPointArray( const RArray< TXnPointEntry >& aPoints )
+ : TXnPointArray( aPoints )
+ {
+ }
+
+ // from TXnPointArray
+ TPoint operator[]( TInt aIndex ) const
+ {
+ return TPoint( 0, Raw( aIndex ).iY );
+ }
+ };
+
+/** @return the current time */
+TTime CurrentTime()
+ {
+ TTime time;
+ time.UniversalTime();
+ return time;
+ }
+
+// ----------------------------------------------------------------------------
+// destructor
+// ----------------------------------------------------------------------------
+//
+CXnGesture::~CXnGesture()
+ {
+ iPoints.Close();
+ }
+
+// ----------------------------------------------------------------------------
+// Reset
+// ----------------------------------------------------------------------------
+//
+void CXnGesture::Reset()
+ {
+ // store previous gesture data before resetting the state
+ if ( iPoints.Count() > 0 )
+ {
+ iPreviousGesture = TGestureRecord( Type(), iCompletionTime,
+ iPoints[iPoints.Count() - 1].iPos );
+ }
+ else
+ {
+ iPreviousGesture = TGestureRecord();
+ }
+
+ iPoints.Reset();
+ iHoldingState = ENotHolding;
+ iState = ENotComplete;
+ iHoldingPointIndex = 0;
+ }
+
+// ----------------------------------------------------------------------------
+// Reset
+// ----------------------------------------------------------------------------
+//
+TBool CXnGesture::IsEmpty() const
+ {
+ return iPoints.Count() == 0;
+ }
+
+// ----------------------------------------------------------------------------
+// Add a point to the sequence of points that together make up the gesture
+// ----------------------------------------------------------------------------
+//
+TInt CXnGesture::AddPoint( const TPoint& aPoint )
+ {
+ if ( !IsLatestPoint( aPoint ) )
+ {
+ return iPoints.Append( TXnPointEntry( aPoint, CurrentTime() ) );
+ }
+ return KErrNone;
+ }
+
+/**
+ * @return ETrue if the point is within a specified distance of the other point
+ */
+inline TBool IsNear( const TPoint& aPointUnderTest, const TPoint& aPoint,
+ TInt aMargin )
+ {
+ TRect rect(
+ aPoint.iX - aMargin, aPoint.iY - aMargin,
+ aPoint.iX + aMargin, aPoint.iY + aMargin );
+ return rect.Contains( aPointUnderTest );
+ }
+
+// ----------------------------------------------------------------------------
+// IsNearHoldingPoint
+// ----------------------------------------------------------------------------
+//
+TBool CXnGesture::IsNearHoldingPoint( const TPoint& aPoint ) const
+ {
+ return IsNear( aPoint, iPoints[iHoldingPointIndex].iPos,
+ KSamePointTolerance );
+ }
+
+// ----------------------------------------------------------------------------
+// IsLatestPoint
+// ----------------------------------------------------------------------------
+//
+TBool CXnGesture::IsLatestPoint( const TPoint& aPoint ) const
+ {
+ if ( iPoints.Count() > 0 )
+ {
+ return aPoint == CurrentPos();
+ }
+ return EFalse;
+ }
+
+// ----------------------------------------------------------------------------
+// StartHolding
+// ----------------------------------------------------------------------------
+//
+void CXnGesture::StartHolding()
+ {
+ iHoldingState = EHoldStarting;
+
+ // remove all points that were introduced after holding started
+ for ( TInt i = iPoints.Count() - 1; i > iHoldingPointIndex; i-- )
+ {
+ iPoints.Remove( i );
+ }
+ }
+
+// ----------------------------------------------------------------------------
+// SetHoldingPoint
+// ----------------------------------------------------------------------------
+//
+void CXnGesture::SetHoldingPoint()
+ {
+ iHoldingPointIndex = iPoints.Count() - 1;
+ }
+
+// ----------------------------------------------------------------------------
+// ContinueHolding
+// ----------------------------------------------------------------------------
+//
+void CXnGesture::ContinueHolding()
+ {
+ iHoldingState = EHolding;
+ }
+
+// ----------------------------------------------------------------------------
+// SetReleased
+// ----------------------------------------------------------------------------
+//
+void CXnGesture::SetReleased()
+ {
+ // IsMovementStopped expects SetComplete to be called before SetRelea
+ __ASSERT_DEBUG( EComplete == iState, Panic( EGesturePanicIllegalLogic ) );
+ iState = EReleased;
+ }
+
+/**
+ * @return elapsed time between aStartTime and aEndTime
+ */
+inline TTimeIntervalMicroSeconds32 Elapsed(
+ const TTime& aStartTime,
+ const TTime& aEndTime )
+ {
+ return aEndTime.MicroSecondsFrom( aStartTime ).Int64();
+ }
+
+// ----------------------------------------------------------------------------
+// SetComplete
+// ----------------------------------------------------------------------------
+//
+void CXnGesture::SetComplete()
+ {
+ __ASSERT_DEBUG( iPoints.Count() > 0, Panic( EGesturePanicIllegalLogic ) );
+ iState = EComplete;
+ iCompletionTime = CurrentTime();
+ }
+
+// ----------------------------------------------------------------------------
+// SetComplete
+// ----------------------------------------------------------------------------
+//
+void CXnGesture::SetCancelled()
+ {
+ iState = ECancelled;
+ }
+
+// ----------------------------------------------------------------------------
+// IsTap
+// ----------------------------------------------------------------------------
+//
+TBool CXnGesture::IsTap() const
+ {
+ return CodeFromPoints( EAxisBoth ) == EGestureTap;
+ }
+
+/**
+ * Translates a non-holding code into a holding code
+ * @param aCode original gesture code
+ * @return a gesture code with hold flag applied
+ */
+inline TXnGestureCode Hold( TXnGestureCode aCode )
+ {
+ if ( aCode != EGestureStart &&
+ aCode != EGestureDrag &&
+ aCode != EGestureReleased &&
+ aCode != EGestureUnknown )
+ {
+ return static_cast< TXnGestureCode >( aCode | EFlagHold );
+ }
+ return aCode;
+ }
+
+// ----------------------------------------------------------------------------
+// Code
+// ----------------------------------------------------------------------------
+//
+TXnGestureCode CXnGesture::Code( TAxis aRelevantAxis ) const
+ {
+ switch ( iState )
+ {
+ case ENotComplete:
+ // "start" event if only first point received
+ // need to check that not holding, in case user pressed stylus
+ // down, and activated holding without moving the stylus
+ if ( iPoints.Count() == 1 && !IsHolding() )
+ {
+ return EGestureStart;
+ }
+ // "drag" event if holding not started or holding started earlier
+ else if ( iHoldingState != EHoldStarting )
+ {
+ return EGestureDrag;
+ }
+ // holding was just started
+ else
+ {
+ return Hold( CodeFromPoints( aRelevantAxis ) );
+ }
+
+ case EComplete:
+ {
+ TXnGestureCode code = CodeFromPoints( aRelevantAxis );
+
+#ifdef _GESTURE_DOUBLE_TAP_SUPPORT
+ if ( EGestureTap == code && IsTapDoubleTap() )
+ {
+ code = EGestureDoubleTap;
+ }
+#endif // _GESTURE_DOUBLE_TAP_SUPPORT
+
+ return code;
+ }
+
+ case EReleased:
+ return EGestureReleased;
+
+ case ECancelled: // fallthrough
+ default:
+ return EGestureUnknown;
+ }
+ }
+
+// ----------------------------------------------------------------------------
+// IsHolding
+// ----------------------------------------------------------------------------
+//
+TBool CXnGesture::IsHolding() const
+ {
+ return iHoldingState >= EHoldStarting;
+ }
+
+// ----------------------------------------------------------------------------
+// StartPos
+// ----------------------------------------------------------------------------
+//
+TPoint CXnGesture::StartPos() const
+ {
+ // at least one point will be in the array during callback (pointer down pos)
+ return iPoints[0].iPos;
+ }
+
+// ----------------------------------------------------------------------------
+// CurrentPos
+// ----------------------------------------------------------------------------
+//
+TPoint CXnGesture::CurrentPos() const
+ {
+ // at least on point will be in the array during callback (pointer down pos)
+ return iPoints[iPoints.Count() - 1].iPos;
+ }
+
+// ----------------------------------------------------------------------------
+// IsMovementStopped
+// ----------------------------------------------------------------------------
+//
+inline TBool CXnGesture::IsMovementStopped() const
+ {
+ // iCompletionTime is only only valid if client has called SetComplete
+ if ( iState >= EComplete )
+ {
+ return Elapsed( NthLastEntry( 1 ).iTime, iCompletionTime )
+ .Int() > KSpeedStopTime;
+ }
+ return EFalse;
+ }
+
+namespace
+ {
+ const TInt KFloatingPointAccuracy = 0.000001;
+
+ /** @return percentage (0.0-1.0) how far aPos is from aEdge1 towards aEdge2 */
+ inline TReal32 Proportion( TReal32 aPos, TReal32 aEdge1, TReal32 aEdge2 )
+ {
+ if ( Abs( aEdge2 - aEdge1 ) > KFloatingPointAccuracy )
+ {
+ return ( aPos - aEdge1 ) / ( aEdge2 - aEdge1 );
+ }
+ return 0; // avoid division by zero
+ }
+
+ /** Edges (pixels) at which speed should be -100% or 100% */
+ NONSHARABLE_STRUCT( TEdges )
+ {
+ TReal32 iMin;
+ TReal32 iMax;
+ };
+
+ /**
+ * scale which allows different (coordinate -> percentage) mapping
+ * between -100% to 0% and 0 and 100%
+ */
+ NONSHARABLE_STRUCT( TScale )
+ {
+ TScale( TInt aZero, const TEdges& aEdges )
+ : iMin( aEdges.iMin ), iZero( aZero ), iMax( aEdges.iMax )
+ {
+ }
+
+ /** @return aPos as a percentage between -100% and 100% in aScale */
+ TReal32 Percent( TReal32 aPos ) const;
+
+ /// coordinate where speed is -100%
+ TReal32 iMin;
+ /// coordinate where speed is 0%
+ TReal32 iZero;
+ /// coordinate where speed is 100%
+ TReal32 iMax;
+ };
+
+ /** @convert aPos into a percentage between -100% and 100% in aScale */
+ TReal32 TScale::Percent( TReal32 aPos ) const
+ {
+ TReal32 percent;
+ if ( aPos < iZero )
+ {
+ // return negative percentages on the lower side of zero point
+ percent = -1 * Proportion( aPos, iZero, iMin );
+ }
+ else
+ {
+ percent = Proportion( aPos, iZero, iMax );
+ }
+ // constrain between -100% and 100%
+ return Min( Max( percent, -1.0F ), 1.0F );
+ }
+
+ /** Scale in x and y dimensions */
+ NONSHARABLE_STRUCT( TScale2D )
+ {
+ TRealPoint Percent( const TPoint& aPos ) const
+ {
+ return TRealPoint(
+ iX.Percent( aPos.iX ),
+ iY.Percent( aPos.iY ) );
+ }
+
+ TScale iX;
+ TScale iY;
+ };
+
+ enum TDirection { ESmaller, ELarger };
+
+ /** @return the direction of pos compared to the previous pos */
+ inline TDirection Direction( TInt aPos, TInt aPreviousPos )
+ {
+ return aPos < aPreviousPos ? ESmaller : ELarger;
+ }
+
+ /** Direction in x and y dimensions */
+ NONSHARABLE_STRUCT( TDirection2D )
+ {
+ TDirection iX;
+ TDirection iY;
+ };
+
+ /** Return the direction (up/down) of signal at aIndex */
+ inline TDirection2D Direction(
+ TInt aIndex,
+ const RArray< TXnPointEntry >& aPoints )
+ {
+ const TPoint& pos = aPoints[aIndex].iPos;
+ const TPoint& prevPos = aPoints[aIndex - 1].iPos;
+ TDirection2D dir = {
+ Direction( pos.iX, prevPos.iX ),
+ Direction( pos.iY, prevPos.iY ) };
+ return dir;
+ }
+
+ /**
+ * @return a position in the aLow and aHigh, so that it aProportion of
+ * of length is above the pos
+ */
+ TReal32 ProportionalLength( TReal32 aLow, TReal32 aHigh, TReal32 aProportion )
+ {
+ return ( aHigh - aLow ) * aProportion / ( 1 + aProportion );
+ }
+
+ /**
+ * @return aVariableEdge scaled to new position, when the other edge changes
+ * from aOldEdge to aNewEdge, so that aOrigin maintains the *same
+ * relative position* between aVariableEdge and the other edge
+ */
+ inline TReal32 ScaledEdge(
+ TReal32 aOrigin,
+ TReal32 aVariableEdge,
+ TReal32 aOldEdge,
+ TReal aNewEdge )
+ {
+ TReal32 proportion = Proportion( aOrigin, aVariableEdge, aOldEdge );
+ return ( proportion * aNewEdge - aOrigin ) / ( proportion - 1 );
+ }
+
+ TScale Rescale(
+ TReal32 aPos,
+ TDirection aDir,
+ TDirection aPrevDir,
+ const TScale& aPrevScale,
+ const TEdges& aEdges )
+ {
+ TScale scale( aPrevScale );
+ if ( aPrevDir != aDir )
+ {
+ // the code duplication is accepted here, since it is difficult
+ // to factor out while maintaining the understandability of this
+ // anyway complex algorithm
+ if ( aDir == ESmaller )
+ {
+ scale.iMin = aEdges.iMin;
+ if ( aPrevScale.iZero < aPos )
+ {
+ TReal32 proportionAboveZero = Proportion(
+ aPos, aPrevScale.iZero, aPrevScale.iMax );
+ scale.iZero = aPos - ProportionalLength(
+ aEdges.iMin, aPos, proportionAboveZero );
+ }
+ else
+ {
+ // adjust zero pos so that proportion between aPos, Min,
+ // and Zero pos stay the same (Min will move to 0,
+ // aPos stays the same)
+ scale.iZero = ScaledEdge( aPos, aPrevScale.iZero,
+ aPrevScale.iMin, aEdges.iMin );
+ }
+
+ // adjust the upper edge to take into account the movement of
+ // zero pos
+ scale.iMax = ScaledEdge( aPos, aPrevScale.iMax,
+ aPrevScale.iZero, scale.iZero );
+ }
+ else // ELarger
+ {
+ scale.iMax = aEdges.iMax;
+ if ( aPos < aPrevScale.iZero )
+ {
+ TReal32 proportionBelowZero = Proportion(
+ aPos, aPrevScale.iZero, aPrevScale.iMin );
+ scale.iZero = aPos + ProportionalLength(
+ aPos, aEdges.iMax, proportionBelowZero );
+ }
+ else
+ {
+ // adjust zero pos so that proportion between aPos, Max, and
+ // Zero pos stay the same (Max will move edge, aPos stays
+ // the same)
+ scale.iZero = ScaledEdge( aPos, aPrevScale.iZero,
+ aPrevScale.iMax, aEdges.iMax );
+ }
+
+ // adjust the lower edge to take into account the movement of
+ // zero pos
+ scale.iMin = ScaledEdge( aPos, aPrevScale.iMin,
+ aPrevScale.iZero, scale.iZero );
+ }
+ }
+ return scale;
+ }
+
+ /** Edges in x and y dimensions */
+ NONSHARABLE_STRUCT( TEdges2D )
+ {
+ TEdges iX;
+ TEdges iY;
+ };
+
+ /**
+ * @param aEdges edges of the area in which gesture points are accepted
+ * @return the scale of latest point in the list of points
+ */
+ TScale2D Scale( const RArray< TXnPointEntry >& aPoints, const TEdges2D& aEdges )
+ {
+ TScale2D scale = { TScale( aPoints[0].iPos.iX, aEdges.iX ),
+ TScale( aPoints[0].iPos.iY, aEdges.iY ) };
+ TInt count = aPoints.Count();
+ if ( count > 1 )
+ {
+ // iterate the whole point list to arrive to the current scale
+ TDirection2D dir( Direction( 1, aPoints ) );
+ for ( TInt i = 1; i < count; i++ )
+ {
+ // get direction at i
+ TDirection2D newDir( Direction( i, aPoints ) );
+ // get new scale at i
+ scale.iX = Rescale(
+ aPoints[i - 1].iPos.iX,
+ newDir.iX,
+ dir.iX,
+ scale.iX,
+ aEdges.iX );
+ scale.iY = Rescale(
+ aPoints[i - 1].iPos.iY,
+ newDir.iY,
+ dir.iY,
+ scale.iY,
+ aEdges.iY );
+ dir = newDir;
+ }
+ }
+ return scale;
+ }
+ } // unnamed namespace
+
+TRealPoint CXnGesture::SpeedPercent( const TRect& aEdges ) const
+ {
+ // x and y coordinates are easier to handle separately, extract from TRect:
+ // ((iMinX, iMinY), (iMaxX, iMaxY)) -> ((iMinX, iMaxX), (iMinY, iMaxY))
+ TEdges2D edges = {
+ { aEdges.iTl.iX, aEdges.iBr.iX },
+ { aEdges.iTl.iY, aEdges.iBr.iY } };
+ // work out the current scale (coordinate -> percentage mapping) from
+ // the history of points (i.e., points of current gesture). Then
+ // calculate the percentage of the current position.
+ return Scale( iPoints, edges ).Percent( CurrentPos() );
+ }
+
+// ----------------------------------------------------------------------------
+// Speed
+// ----------------------------------------------------------------------------
+//
+TRealPoint CXnGesture::Speed() const
+ {
+ const TReal32 KMicroSecondsInSecond = 1000000;
+
+ // Speed is only evaluated at the end of the swipe
+ // if user stops at the end of the swipe before lifting stylus,
+ // speed is zero. If time is zero, return 0 speed (infinite does
+ // not make sense either). Will need to consider also earlier points
+ // and their times or start time, if this zero-speed behavior is a problem
+ TRealPoint speed;
+ TReal32 time = static_cast< TReal32 >( TimeFromPreviousPoint().Int() )
+ / KMicroSecondsInSecond;
+ if ( !IsMovementStopped() && time > 0 )
+ {
+ TPoint distance = CurrentPos() - PreviousPos();
+ speed.iX = static_cast< TReal32 >( distance.iX ) / time;
+ speed.iY = static_cast< TReal32 >( distance.iY ) / time;
+ }
+ return speed;
+ }
+
+// ----------------------------------------------------------------------------
+// Distance
+// ----------------------------------------------------------------------------
+//
+TPoint CXnGesture::Distance() const
+ {
+ return CurrentPos() - StartPos();
+ }
+
+// ----------------------------------------------------------------------------
+// TimeFromPreviousPoint
+// ----------------------------------------------------------------------------
+//
+inline TTimeIntervalMicroSeconds32 CXnGesture::TimeFromPreviousPoint() const
+ {
+ const TInt KLatestEntryOffset = 1;
+ return Elapsed( PreviousEntry().iTime, NthLastEntry( KLatestEntryOffset ).iTime );
+ }
+
+// ----------------------------------------------------------------------------
+// CodeFromPoints
+// ----------------------------------------------------------------------------
+//
+TXnGestureCode CXnGesture::CodeFromPoints( TAxis aRelevantAxis ) const
+ {
+ // select the correct filter based on aRelevantAxis
+ // these filter_ objects are array decorators that will eliminate either
+ // x, y or neither coordinate of each point
+ TXAxisPointArray filterY( iPoints );
+ TYAxisPointArray filterX( iPoints );
+ TXnPointArray filterNone( iPoints );
+ TXnPointArray& filter =
+ aRelevantAxis == EAxisHorizontal ? static_cast< TXnPointArray& >( filterY ) :
+ aRelevantAxis == EAxisVertical ? static_cast< TXnPointArray& >( filterX ) :
+ /* otherwise EAxisBoth */ filterNone;
+
+ // currently the gesture recogniser does not have any state, so it is fast
+ // to instantiate. The call is not static however, to allow the recogniser
+ // to be replaced by a more complicated implementation that has state.
+ // then it may make sense to make the recogniser a member variable.
+ return TXnGestureRecogniser().GestureCode( filter );
+ }
+
+// ----------------------------------------------------------------------------
+// return nth point from the end of the points array
+// ----------------------------------------------------------------------------
+//
+inline const TXnPointEntry& CXnGesture::NthLastEntry( TInt aOffset ) const
+ {
+ return iPoints[Max( iPoints.Count() - aOffset, 0 )];
+ }
+
+// ----------------------------------------------------------------------------
+// PreviousEntry
+// ----------------------------------------------------------------------------
+//
+inline const TXnPointEntry& CXnGesture::PreviousEntry() const
+ {
+ return NthLastEntry( KPreviousPointOffset );
+ }
+
+// ----------------------------------------------------------------------------
+// PreviousPos
+// ----------------------------------------------------------------------------
+//
+inline TPoint CXnGesture::PreviousPos() const
+ {
+ return PreviousEntry().iPos;
+ }
+
+// ----------------------------------------------------------------------------
+// SetComplete
+// ----------------------------------------------------------------------------
+//
+TBool CXnGesture::IsTapDoubleTap() const
+ {
+ return iPreviousGesture.iType == TGestureRecord::ETypeTap &&
+ Elapsed( iPreviousGesture.iCompletionTime, iCompletionTime ).Int() <=
+ KMaxDoubleTapDuration &&
+ IsNear( iPreviousGesture.iPos, iPoints[iPoints.Count() - 1].iPos,
+ KSamePointTolerance );
+ }
+
+// ----------------------------------------------------------------------------
+// Type
+// ----------------------------------------------------------------------------
+//
+CXnGesture::TGestureRecord::TType CXnGesture::Type() const
+ {
+ if ( CodeFromPoints( EAxisBoth ) == EGestureTap && !IsHolding() )
+ {
+ if ( IsTapDoubleTap() )
+ {
+ return CXnGesture::TGestureRecord::ETypeDoubleTap;
+ }
+ else
+ {
+ return CXnGesture::TGestureRecord::ETypeTap;
+ }
+ }
+ else
+ {
+ return CXnGesture::TGestureRecord::ETypeOther;
+ }
+ }
+
+// ----------------------------------------------------------------------------
+// TGestureRecord constructor
+// ----------------------------------------------------------------------------
+//
+CXnGesture::TGestureRecord::TGestureRecord()
+ {
+ iType = ETypeOther;
+ }
+
+// ----------------------------------------------------------------------------
+// TGestureRecord constructor
+// ----------------------------------------------------------------------------
+//
+CXnGesture::TGestureRecord::TGestureRecord(
+ CXnGesture::TGestureRecord::TType aType,
+ TTime aCompletionTime,
+ TPoint aPos )
+ : iType( aType ), iCompletionTime( aCompletionTime ), iPos( aPos )
+ {
+ }