webengine/webkitutils/rt_gesturehelper/src/gesture.cpp
changeset 0 dd21522fd290
child 1 7c90e6132015
equal deleted inserted replaced
-1:000000000000 0:dd21522fd290
       
     1 /*
       
     2 * Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
       
     3 * All rights reserved.
       
     4 * This component and the accompanying materials are made available
       
     5 * under the terms of the License "Eclipse Public License v1.0"
       
     6 * which accompanies this distribution, and is available
       
     7 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     8 *
       
     9 * Initial Contributors:
       
    10 * Nokia Corporation - initial contribution.
       
    11 *
       
    12 * Contributors:
       
    13 *
       
    14 * Description:  Gesture class
       
    15 *
       
    16 */
       
    17 
       
    18 
       
    19 #include "gesture.h"
       
    20 
       
    21 #include <e32math.h>
       
    22 #include <coemain.h>
       
    23 
       
    24 #include "gesturedefs.h"
       
    25 #include "utils.h"
       
    26 
       
    27 using namespace RT_GestureHelper;
       
    28 
       
    29 /** 
       
    30  * Point array for which only x axis is relevant
       
    31  */
       
    32 class TXAxisPointArray : public TPointArray
       
    33     {
       
    34 public:
       
    35     TXAxisPointArray( const RArray< TPointEntry >& aPoints )
       
    36             : TPointArray( aPoints ) {}
       
    37         
       
    38     // from TPointArray
       
    39     TPoint operator[]( TInt aIndex ) const 
       
    40         {
       
    41         return TPoint( Raw( aIndex ).iX, 0 );
       
    42         }
       
    43     };
       
    44 
       
    45 /** 
       
    46  * Point array for which only y axis is relevant
       
    47  */
       
    48 class TYAxisPointArray : public TPointArray
       
    49     {
       
    50 public:
       
    51     TYAxisPointArray( const RArray< TPointEntry >& aPoints )
       
    52             : TPointArray( aPoints ) {}
       
    53     
       
    54     // from TPointArray
       
    55     TPoint operator[]( TInt aIndex ) const 
       
    56         {
       
    57         return TPoint( 0, Raw( aIndex ).iY );
       
    58         }
       
    59     };
       
    60 
       
    61 namespace 
       
    62     {
       
    63     /** @return the current time */
       
    64     TTime CurrentTime()
       
    65         {
       
    66         TTime time;
       
    67         time.HomeTime();
       
    68         return time;
       
    69         }
       
    70 
       
    71     /**
       
    72      * @param aRelevantAxis See @ref MGestureEvent::Code
       
    73      * @return gesture code by analysing the sequence of points
       
    74      */
       
    75     TGestureCode CodeFromPoints( const RArray< TPointEntry >& aPoints, 
       
    76             MGestureEvent::TAxis aRelevantAxis ) 
       
    77         {
       
    78         // select the correct filter based on aRelevantAxis
       
    79         // these filter_ objects are array decorators that will eliminate either 
       
    80         // x, y or neither coordinate of each point
       
    81         TXAxisPointArray filterY( aPoints );
       
    82         TYAxisPointArray filterX( aPoints );
       
    83         TPointArray filterNone( aPoints );
       
    84         TPointArray& filter = 
       
    85             aRelevantAxis == MGestureEvent::EAxisHorizontal ? static_cast< TPointArray& >( filterY ) : 
       
    86             aRelevantAxis == MGestureEvent::EAxisVertical   ? static_cast< TPointArray& >( filterX ) :
       
    87             /* otherwise EAxisBoth */                         filterNone;
       
    88                             
       
    89         // currently the gesture recogniser does not have any state, so it is fast
       
    90         // to instantiate. The call is not static however, to allow the recogniser
       
    91         // to be replaced by a more complicated implementation that has state.
       
    92         // then it may make sense to make the recogniser a member variable.
       
    93         return TGestureRecogniser().GestureCode( filter );
       
    94         }
       
    95     } // unnamed namespace
       
    96 
       
    97 // ----------------------------------------------------------------------------
       
    98 // destructor
       
    99 // ----------------------------------------------------------------------------
       
   100 //
       
   101 CGesture::~CGesture()
       
   102     {
       
   103     iPoints.Close();
       
   104     }
       
   105 
       
   106 // ----------------------------------------------------------------------------
       
   107 // AsStartEventL
       
   108 // ----------------------------------------------------------------------------
       
   109 //
       
   110 CGesture* CGesture::AsStartEventLC() const
       
   111     {
       
   112     __ASSERT_DEBUG( 0 < iPoints.Count(), Panic( EGesturePanicIllegalLogic ) );
       
   113     CGesture* gesture = new ( ELeave ) CGesture;
       
   114     CleanupStack::PushL( gesture );
       
   115     User::LeaveIfError( gesture->AddPoint( iPoints[0].iPos, CCoeEnv::Static()->LastEvent().Time()));
       
   116     return gesture;
       
   117     }
       
   118 
       
   119 // ----------------------------------------------------------------------------
       
   120 // Reset
       
   121 // ----------------------------------------------------------------------------
       
   122 //
       
   123 void CGesture::Reset()
       
   124     {
       
   125     iPoints.Reset();
       
   126     iHoldingState = ENotHolding;
       
   127     iState = ENotComplete;
       
   128     iHoldingPointIndex = 0;
       
   129     iVisual = NULL;
       
   130     iIsDoubleTap = EFalse;
       
   131     iIsLongTap = EFalse;
       
   132     }
       
   133     
       
   134 // ----------------------------------------------------------------------------
       
   135 // Reset
       
   136 // ----------------------------------------------------------------------------
       
   137 //
       
   138 TBool CGesture::IsEmpty() const
       
   139     {
       
   140     return iPoints.Count() == 0;
       
   141     }
       
   142 
       
   143 // ----------------------------------------------------------------------------
       
   144 // Add a point to the sequence of points that together make up the gesture
       
   145 // ----------------------------------------------------------------------------
       
   146 //
       
   147 TInt CGesture::AddPoint( const TPoint& aPoint, const TTime& aEventTime )
       
   148     {
       
   149     if ( !IsLatestPoint( aPoint ) )
       
   150         {
       
   151         return iPoints.Append( TPointEntry( aPoint, aEventTime ) );
       
   152         }
       
   153     return KErrNone;
       
   154     }
       
   155 
       
   156 // ----------------------------------------------------------------------------
       
   157 // SetVisual
       
   158 // ----------------------------------------------------------------------------
       
   159 //
       
   160 void CGesture::SetVisual( CAlfVisual* aVisual )
       
   161     {
       
   162     iVisual = aVisual;
       
   163     }
       
   164 
       
   165 // ----------------------------------------------------------------------------
       
   166 // IsNearHoldingPoint
       
   167 // ----------------------------------------------------------------------------
       
   168 //
       
   169 TBool CGesture::IsNearHoldingPoint( const TPoint& aPoint ) const
       
   170     {
       
   171     return ToleranceRect( iPoints[ iHoldingPointIndex ].iPos ).Contains( aPoint );
       
   172     }
       
   173 
       
   174 // ----------------------------------------------------------------------------
       
   175 // IsLatestPoint
       
   176 // ----------------------------------------------------------------------------
       
   177 //
       
   178 TBool CGesture::IsLatestPoint( const TPoint& aPoint ) const
       
   179     {
       
   180     if ( iPoints.Count() > 0 )
       
   181         {
       
   182         return aPoint == CurrentPos();
       
   183         }
       
   184     return EFalse;
       
   185     }
       
   186 
       
   187 // ----------------------------------------------------------------------------
       
   188 // StartHolding
       
   189 // ----------------------------------------------------------------------------
       
   190 //
       
   191 void CGesture::StartHolding()
       
   192     {
       
   193     iHoldingState = EHoldStarting;
       
   194     
       
   195     // remove all points that were introduced after holding started
       
   196     for ( TInt i = iPoints.Count() - 1; i > iHoldingPointIndex; i-- )
       
   197         {
       
   198         iPoints.Remove( i );
       
   199         }
       
   200     }
       
   201 
       
   202 // ----------------------------------------------------------------------------
       
   203 // SetHoldingPoint
       
   204 // ----------------------------------------------------------------------------
       
   205 //
       
   206 void CGesture::SetHoldingPoint()
       
   207     {
       
   208     iHoldingPointIndex = iPoints.Count() - 1;
       
   209     }
       
   210 
       
   211 // ----------------------------------------------------------------------------
       
   212 // ContinueHolding
       
   213 // ----------------------------------------------------------------------------
       
   214 //
       
   215 void CGesture::ContinueHolding()
       
   216     {
       
   217     iHoldingState = EHolding;
       
   218     }
       
   219 
       
   220 // ----------------------------------------------------------------------------
       
   221 // SetReleased
       
   222 // ----------------------------------------------------------------------------
       
   223 //
       
   224 void CGesture::SetReleased()
       
   225     {
       
   226     // IsMovementStopped expects SetComplete to be called before SetRelea
       
   227     __ASSERT_DEBUG( EComplete == iState, Panic( EGesturePanicIllegalLogic ) );
       
   228     iState = EReleased;
       
   229     }
       
   230 
       
   231 /**
       
   232  * @return elapsed time between aStartTime and aEndTime
       
   233  */
       
   234 inline TTimeIntervalMicroSeconds32 Elapsed( const TTime& aStartTime, 
       
   235                                             const TTime& aEndTime )
       
   236     {
       
   237     return aEndTime.MicroSecondsFrom( aStartTime ).Int64();
       
   238     }
       
   239 
       
   240 // ----------------------------------------------------------------------------
       
   241 // SetComplete
       
   242 // ----------------------------------------------------------------------------
       
   243 //
       
   244 void CGesture::SetComplete()
       
   245     {
       
   246     __ASSERT_DEBUG( iPoints.Count() > 0, Panic( EGesturePanicIllegalLogic ) );
       
   247     iState = EComplete;
       
   248     iCompletionTime = CurrentTime();
       
   249     }
       
   250 
       
   251 // ----------------------------------------------------------------------------
       
   252 // SetComplete
       
   253 // ----------------------------------------------------------------------------
       
   254 //
       
   255 void CGesture::SetCancelled()
       
   256     {
       
   257     iState = ECancelled;
       
   258     }
       
   259 
       
   260 void CGesture::SetDoubleTap() 
       
   261     { 
       
   262     iIsDoubleTap = ETrue; 
       
   263     }
       
   264 
       
   265 void CGesture::SetLongTap(TBool aLongTap) 
       
   266     { 
       
   267     iIsLongTap = aLongTap; 
       
   268     }
       
   269 
       
   270 // ----------------------------------------------------------------------------
       
   271 // IsTap
       
   272 // ----------------------------------------------------------------------------
       
   273 //
       
   274 TBool CGesture::IsTap() const
       
   275     {
       
   276     return CodeFromPoints( iPoints, MGestureEvent::EAxisBoth ) == EGestureTap;
       
   277     }
       
   278 
       
   279 /**
       
   280  * Translates a non-holding code into a holding code
       
   281  * @param aCode original gesture code
       
   282  * @return a gesture code with hold flag applied
       
   283  */
       
   284 inline TGestureCode Hold( TGestureCode aCode )
       
   285     {
       
   286     if ( aCode != EGestureStart && 
       
   287          aCode != EGestureDrag && 
       
   288          aCode != EGestureReleased && 
       
   289          aCode != EGestureUnknown )
       
   290         {
       
   291         return static_cast< TGestureCode >( aCode | EFlagHold );
       
   292         }
       
   293     return aCode;
       
   294     }
       
   295     
       
   296 // ----------------------------------------------------------------------------
       
   297 // Code
       
   298 // ----------------------------------------------------------------------------
       
   299 //
       
   300 TGestureCode CGesture::Code( MGestureEvent::TAxis aRelevantAxis ) /* const */
       
   301     {
       
   302     TGestureCode code;
       
   303     
       
   304     switch ( iState )
       
   305         {
       
   306         case ENotComplete:
       
   307             {
       
   308             // "start" event if only first point received
       
   309             // need to check that not holding, in case user pressed stylus
       
   310             // down, and activated holding without moving the stylus
       
   311             if ( iPoints.Count() == 1 && !IsHolding() )
       
   312                 {
       
   313                 code = EGestureStart;
       
   314                 }
       
   315             // "drag" event if holding not started or holding started earlier
       
   316             else if ( iHoldingState != EHoldStarting )
       
   317                 {
       
   318                 code = EGestureDrag; 
       
   319                 }
       
   320             // holding was just started
       
   321             else 
       
   322                 {
       
   323                 code = Hold( CodeFromPoints( iPoints, aRelevantAxis ) );
       
   324                 }
       
   325             iPrevGestureCode = code;
       
   326             break;
       
   327             }
       
   328         case EComplete:
       
   329             {
       
   330             if ( iIsDoubleTap )
       
   331                 {
       
   332                 code = EGestureDoubleTap;
       
   333                 }
       
   334             else if ( iIsLongTap )
       
   335                 {
       
   336                 code = EGestureLongTap;
       
   337                 }
       
   338             
       
   339             else if (iPrevGestureCode == EGestureDrag)
       
   340                 {
       
   341                 //code = IsFlick() ? EGestureFlick : EGestureDrop;
       
   342                 if (IsFlick()) 
       
   343                     {
       
   344                     code = EGestureFlick ;
       
   345                     }
       
   346                   else
       
   347                     {
       
   348                     // Check if it is a swipe.  In this case a swipe is a gesture where 
       
   349                     // - the direction is close to the axes (up, down, left, right)
       
   350                     // - speed is slower than flick 
       
   351                     code = CodeFromPoints( iPoints, aRelevantAxis );
       
   352                     if (code == EGestureUnknown)    
       
   353                       {
       
   354                       code = EGestureDrop ; // It was not a swipe, so then it is the drop gesture 
       
   355                       }
       
   356                     }
       
   357                 }
       
   358             else
       
   359                 {
       
   360                 code = CodeFromPoints( iPoints, aRelevantAxis );
       
   361                 }
       
   362             iPrevGestureCode = code;
       
   363             break;
       
   364             }
       
   365             
       
   366         case EReleased:
       
   367             {
       
   368             code = EGestureReleased;
       
   369             break;
       
   370             }
       
   371             
       
   372         case ECancelled: // fallthrough
       
   373         default: 
       
   374             code = EGestureUnknown;
       
   375         }
       
   376     return code;
       
   377     }
       
   378 
       
   379 
       
   380 TBool CGesture::IsFlick() const
       
   381     {
       
   382     bool flick = EFalse;
       
   383     TRealPoint speed = Speed();
       
   384     TReal32 xSpeed = speed.iX;
       
   385     TReal32 ySpeed = speed.iY;
       
   386             
       
   387     flick = (Abs(xSpeed) > KFlickSpeed || 
       
   388              Abs(ySpeed) > KFlickSpeed);
       
   389         
       
   390     return flick;
       
   391     }
       
   392 
       
   393 // ----------------------------------------------------------------------------
       
   394 // IsHolding
       
   395 // ----------------------------------------------------------------------------
       
   396 //
       
   397 TBool CGesture::IsHolding() const
       
   398     {
       
   399     return iHoldingState >= EHoldStarting;
       
   400     }
       
   401 
       
   402 // ----------------------------------------------------------------------------
       
   403 // StartPos
       
   404 // ----------------------------------------------------------------------------
       
   405 //
       
   406 TPoint CGesture::StartPos() const
       
   407     {
       
   408     // at least one point will be in the array during callback (pointer down pos)
       
   409     return iPoints[ 0 ].iPos;
       
   410     }
       
   411 
       
   412 // ----------------------------------------------------------------------------
       
   413 // CurrentPos
       
   414 // ----------------------------------------------------------------------------
       
   415 //
       
   416 TPoint CGesture::CurrentPos() const
       
   417     {
       
   418     // at least on point will be in the array during callback (pointer down pos)
       
   419     return iPoints.Count() > 0 ? iPoints[ iPoints.Count() - 1 ].iPos : TPoint(-1, -1);
       
   420     }
       
   421 
       
   422 // ----------------------------------------------------------------------------
       
   423 // IsMovementStopped
       
   424 // ----------------------------------------------------------------------------
       
   425 //
       
   426 inline TBool CGesture::IsMovementStopped() const
       
   427     {
       
   428     // iCompletionTime is only only valid if client has called SetComplete 
       
   429     if ( iState >= EComplete )
       
   430         {
       
   431         TInt el = Elapsed( NthLastEntry( 1 ).iTime, iCompletionTime ).Int(); 
       
   432         return el > KSpeedStopTime;
       
   433         }
       
   434     return EFalse;
       
   435     }
       
   436 
       
   437 namespace 
       
   438     {
       
   439     const TInt KFloatingPointAccuracy = 0.000001;
       
   440     
       
   441     /** @return percentage (0.0-1.0) how far aPos is from aEdge1 towards aEdge2 */
       
   442     inline TReal32 Proportion( TReal32 aPos, TReal32 aEdge1, TReal32 aEdge2 )
       
   443         {
       
   444         if ( Abs( aEdge2 - aEdge1 ) > KFloatingPointAccuracy )
       
   445             {
       
   446             return ( aPos - aEdge1 ) / ( aEdge2 - aEdge1 );
       
   447             }
       
   448         return 0; // avoid division by zero 
       
   449         }
       
   450     
       
   451     /** Edges (pixels) at which speed should be -100% or 100% */
       
   452     NONSHARABLE_STRUCT( TEdges )
       
   453         {
       
   454         TReal32 iMin;
       
   455         TReal32 iMax;
       
   456         };
       
   457         
       
   458     /** 
       
   459      * scale which allows different (coordinate -> percentage) mapping
       
   460      * between -100% to 0% and 0 and 100%
       
   461      */
       
   462     NONSHARABLE_STRUCT( TScale )
       
   463         {
       
   464         TScale( TInt aZero, const TEdges& aEdges )
       
   465                 : iMin( aEdges.iMin ), iZero( aZero ), iMax( aEdges.iMax )
       
   466             {
       
   467             }
       
   468         
       
   469         /** @return aPos as a percentage between -100% and 100% in aScale */
       
   470         TReal32 Percent( TReal32 aPos ) const;
       
   471 
       
   472         /// coordinate where speed is -100%
       
   473         TReal32 iMin;
       
   474         /// coordinate where speed is 0%
       
   475         TReal32 iZero;
       
   476         /// coordinate where speed is 100%
       
   477         TReal32 iMax;
       
   478         };
       
   479         
       
   480     /** @convert aPos into a percentage between -100% and 100% in aScale */
       
   481     TReal32 TScale::Percent( TReal32 aPos ) const
       
   482         {
       
   483         TReal32 percent;
       
   484         if ( aPos < iZero )
       
   485             {
       
   486             // return negative percentages on the lower side of zero point
       
   487             percent = -1 * Proportion( aPos, iZero, iMin );
       
   488             }
       
   489         else 
       
   490             {
       
   491             percent = Proportion( aPos, iZero, iMax );
       
   492             }
       
   493         // constrain between -100% and 100%
       
   494         return Min( Max( percent, -1.0F ), 1.0F );
       
   495         }
       
   496     
       
   497     /** Scale in x and y dimensions */
       
   498     NONSHARABLE_STRUCT( TScale2D )
       
   499         {
       
   500         TRealPoint Percent( const TPoint& aPos ) const
       
   501             {
       
   502             return TRealPoint( iX.Percent( aPos.iX ),
       
   503                                iY.Percent( aPos.iY ) );
       
   504             }
       
   505                 
       
   506         TScale iX;
       
   507         TScale iY;
       
   508         };
       
   509             
       
   510     enum TDirection { ESmaller, ELarger };
       
   511     
       
   512     /** @return the direction of pos compared to the previous pos */
       
   513     inline TDirection Direction( TInt aPos, TInt aPreviousPos )
       
   514         {
       
   515         return aPos < aPreviousPos ? ESmaller : ELarger;    
       
   516         }
       
   517 
       
   518     /** Direction in x and y dimensions */
       
   519     NONSHARABLE_STRUCT( TDirection2D )
       
   520         {
       
   521         TDirection iX;
       
   522         TDirection iY;
       
   523         };
       
   524 
       
   525     /** Return the direction (up/down) of signal at aIndex */
       
   526     inline TDirection2D Direction( TInt aIndex, const RArray< TPointEntry >& aPoints )
       
   527         {
       
   528         const TPoint& pos = aPoints[ aIndex ].iPos;
       
   529         const TPoint& prevPos = aPoints[ aIndex - 1 ].iPos;
       
   530         TDirection2D dir = { Direction( pos.iX, prevPos.iX ),
       
   531                              Direction( pos.iY, prevPos.iY ) };
       
   532         return dir;
       
   533         }   
       
   534     /** 
       
   535      * @return a position in the aLow and aHigh, so that it aProportion of
       
   536      *         of length is above the pos 
       
   537      */
       
   538     TReal32 ProportionalLength( TReal32 aLow, TReal32 aHigh, TReal32 aProportion )
       
   539         {
       
   540         return ( aHigh - aLow ) * aProportion / ( 1 + aProportion );
       
   541         }
       
   542     
       
   543     /** 
       
   544      * @return aVariableEdge scaled to new position, when the other edge changes
       
   545      *         from aOldEdge to aNewEdge, so that aOrigin maintains the *same relative
       
   546      *         position* between aVariableEdge and the other edge 
       
   547      */
       
   548     inline TReal32 ScaledEdge( TReal32 aOrigin, TReal32 aVariableEdge, 
       
   549             TReal32 aOldEdge, TReal aNewEdge )
       
   550         {
       
   551         TReal32 proportion = Proportion( aOrigin, aVariableEdge, aOldEdge );
       
   552         return ( proportion * aNewEdge - aOrigin ) / ( proportion - 1 );
       
   553         }
       
   554     
       
   555     TScale Rescale( TReal32 aPos, TDirection aDir, TDirection aPrevDir, 
       
   556             const TScale& aPrevScale, const TEdges& aEdges )
       
   557         {
       
   558         TScale scale( aPrevScale );
       
   559         if ( aPrevDir != aDir )
       
   560             {
       
   561             // the code duplication is accepted here, since it is difficult to factor out
       
   562             // while maintaining the understandability of this anyway complex algorithm
       
   563             if ( aDir == ESmaller )
       
   564                 {
       
   565                 scale.iMin = aEdges.iMin;
       
   566                 if ( aPrevScale.iZero < aPos )
       
   567                     {
       
   568                     TReal32 proportionAboveZero = Proportion( aPos, aPrevScale.iZero, aPrevScale.iMax );
       
   569                     scale.iZero = aPos - ProportionalLength( aEdges.iMin, aPos, proportionAboveZero );
       
   570                     }
       
   571                 else 
       
   572                     {
       
   573                     // adjust zero pos so that proportion between aPos, Min, and Zero pos 
       
   574                     // stay the same (Min will move to 0, aPos stays the same)
       
   575                     scale.iZero = ScaledEdge( aPos, aPrevScale.iZero, 
       
   576                         aPrevScale.iMin, aEdges.iMin );
       
   577                     }
       
   578                 // adjust the upper edge to take into account the movement of zero pos
       
   579                 scale.iMax = ScaledEdge( aPos, aPrevScale.iMax, 
       
   580                     aPrevScale.iZero, scale.iZero );
       
   581                 }
       
   582             else // ELarger
       
   583                 {
       
   584                 scale.iMax = aEdges.iMax;
       
   585                 if ( aPos < aPrevScale.iZero )
       
   586                     {
       
   587                     TReal32 proportionBelowZero = Proportion( aPos, aPrevScale.iZero, aPrevScale.iMin );
       
   588                     scale.iZero = aPos + ProportionalLength( aPos, aEdges.iMax, proportionBelowZero );
       
   589                     }
       
   590                 else
       
   591                     {
       
   592                     // adjust zero pos so that proportion between aPos, Max, and Zero pos 
       
   593                     // stay the same (Max will move edge, aPos stays the same)
       
   594                     scale.iZero = ScaledEdge( aPos, aPrevScale.iZero, 
       
   595                         aPrevScale.iMax, aEdges.iMax );
       
   596                     }
       
   597                 // adjust the lower edge to take into account the movement of zero pos
       
   598                 scale.iMin = ScaledEdge( aPos, aPrevScale.iMin, 
       
   599                     aPrevScale.iZero, scale.iZero );
       
   600                 }
       
   601             }
       
   602         return scale;
       
   603         }
       
   604      
       
   605     /** Edges in x and y dimensions */
       
   606     NONSHARABLE_STRUCT( TEdges2D )
       
   607         {
       
   608         TEdges iX;
       
   609         TEdges iY;
       
   610         };
       
   611     
       
   612     /** 
       
   613      * @param aEdges edges of the area in which gesture points are accepted
       
   614      * @return the scale of latest point in the list of points 
       
   615      */
       
   616     TScale2D Scale( const RArray< TPointEntry >& aPoints, const TEdges2D& aEdges )
       
   617         {
       
   618         TScale2D scale = { TScale( aPoints[0].iPos.iX, aEdges.iX ),
       
   619                            TScale( aPoints[0].iPos.iY, aEdges.iY ) };
       
   620         TInt count = aPoints.Count();
       
   621         if ( count > 1 )
       
   622             {
       
   623             // iterate the whole point list to arrive to the current scale
       
   624             TDirection2D dir( Direction( 1, aPoints ) );
       
   625             for ( TInt i = 1; i < count; i++ )
       
   626                 {
       
   627                 // get direction at i
       
   628                 TDirection2D newDir( Direction( i, aPoints ) );
       
   629                 // get new scale at i
       
   630                 scale.iX = Rescale( aPoints[i - 1].iPos.iX, newDir.iX, dir.iX, scale.iX, aEdges.iX );
       
   631                 scale.iY = Rescale( aPoints[i - 1].iPos.iY, newDir.iY, dir.iY, scale.iY, aEdges.iY );
       
   632                 dir = newDir;
       
   633                 }
       
   634             }
       
   635         return scale;
       
   636         }
       
   637     } // unnamed namespace
       
   638 
       
   639 TRealPoint CGesture::SpeedPercent( const TRect& aEdges ) const
       
   640     {
       
   641     // x and y coordinates are easier to handle separately, extract from TRect:
       
   642     // ((iMinX, iMinY), (iMaxX, iMaxY)) -> ((iMinX, iMaxX), (iMinY, iMaxY))
       
   643     TEdges2D edges = { { aEdges.iTl.iX, aEdges.iBr.iX },
       
   644                        { aEdges.iTl.iY, aEdges.iBr.iY } };
       
   645     // work out the current scale (coordinate -> percentage mapping) from 
       
   646     // the history of points (i.e., points of current gesture). Then
       
   647     // calculate the percentage of the current position.
       
   648     return Scale( iPoints, edges ).Percent( CurrentPos() );
       
   649     }
       
   650     
       
   651 // ----------------------------------------------------------------------------
       
   652 // Speed
       
   653 // ----------------------------------------------------------------------------
       
   654 //
       
   655 TRealPoint CGesture::Speed() const
       
   656     {
       
   657     const TReal32 KMicroSecondsInSecond = 1000000;
       
   658     
       
   659     // Speed is only evaluated at the end of the swipe
       
   660     // if user stops at the end of the swipe before lifting stylus,
       
   661     // speed is zero. If time is zero, return 0 speed (infinite does 
       
   662     // not make sense either). Will need to consider also earlier points 
       
   663     // and their times or start time, if this zero-speed behavior is a problem
       
   664     TRealPoint speed;
       
   665     TReal32 time = static_cast<TReal32>( TimeFromPreviousPoint().Int() ) 
       
   666         / KMicroSecondsInSecond;
       
   667     if ( !IsMovementStopped() && time > 0 )
       
   668         {
       
   669         TPoint distance = CurrentPos() - PreviousPos();
       
   670         speed.iX = static_cast<TReal32>( distance.iX ) / time;
       
   671         speed.iY = static_cast<TReal32>( distance.iY ) / time;
       
   672         }
       
   673     return speed;
       
   674     }
       
   675 
       
   676 // ----------------------------------------------------------------------------
       
   677 // Distance
       
   678 // ----------------------------------------------------------------------------
       
   679 //
       
   680 TPoint CGesture::Distance() const
       
   681     {
       
   682     return CurrentPos() - StartPos();
       
   683     }
       
   684 
       
   685 // ----------------------------------------------------------------------------
       
   686 // Visual
       
   687 // ----------------------------------------------------------------------------
       
   688 //
       
   689 CAlfVisual* CGesture::Visual() const
       
   690     {
       
   691     return iVisual;
       
   692     }
       
   693 
       
   694 // ----------------------------------------------------------------------------
       
   695 // TimeFromPreviousPoint
       
   696 // ----------------------------------------------------------------------------
       
   697 //
       
   698 inline TTimeIntervalMicroSeconds32 CGesture::TimeFromPreviousPoint() const
       
   699     {
       
   700     const TInt KLatestEntryOffset = 1;
       
   701     return Elapsed( PreviousEntry().iTime, NthLastEntry( KLatestEntryOffset ).iTime );
       
   702     }
       
   703 
       
   704 // ----------------------------------------------------------------------------
       
   705 // return nth point from the end of the points array
       
   706 // ----------------------------------------------------------------------------
       
   707 //
       
   708 inline const TPointEntry& CGesture::NthLastEntry( TInt aOffset ) const
       
   709     {
       
   710     return iPoints[ Max( iPoints.Count() - aOffset, 0 ) ];
       
   711     }
       
   712 
       
   713 // ----------------------------------------------------------------------------
       
   714 // PreviousEntry
       
   715 // ----------------------------------------------------------------------------
       
   716 //
       
   717 inline const TPointEntry& CGesture::PreviousEntry() const
       
   718     {
       
   719     return NthLastEntry( KPreviousPointOffset );
       
   720     }
       
   721 
       
   722 // ----------------------------------------------------------------------------
       
   723 // PreviousPos
       
   724 // ----------------------------------------------------------------------------
       
   725 //
       
   726 inline TPoint CGesture::PreviousPos() const
       
   727     {
       
   728     return PreviousEntry().iPos;
       
   729     }
       
   730 
       
   731 inline TTime CGesture::TimeOfLastEntry() const
       
   732     {
       
   733     return NthLastEntry( 1 ).iTime;
       
   734     }