webengine/webkitutils/rt_gesturehelper/src/gesture.cpp
changeset 28 d39add9822e2
parent 27 6297cdf66332
child 32 ea4b2e4f7cac
equal deleted inserted replaced
27:6297cdf66332 28:d39add9822e2
     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                                  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 == EAxisHorizontal ? static_cast< TPointArray& >( filterY ) : 
       
    86             aRelevantAxis == 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     iIsDoubleTap = EFalse;
       
   130     iIsLongTap = EFalse;
       
   131     }
       
   132     
       
   133 // ----------------------------------------------------------------------------
       
   134 // Reset
       
   135 // ----------------------------------------------------------------------------
       
   136 //
       
   137 TBool CGesture::IsEmpty() const
       
   138     {
       
   139     return iPoints.Count() == 0;
       
   140     }
       
   141 
       
   142 // ----------------------------------------------------------------------------
       
   143 // Add a point to the sequence of points that together make up the gesture
       
   144 // ----------------------------------------------------------------------------
       
   145 //
       
   146 TInt CGesture::AddPoint( const TPoint& aPoint, const TTime& aEventTime )
       
   147     {
       
   148     if ( !IsLatestPoint( aPoint ) )
       
   149         {
       
   150         return iPoints.Append( TPointEntry( aPoint, aEventTime ) );
       
   151         }
       
   152     return KErrNone;
       
   153     }
       
   154 
       
   155 // ----------------------------------------------------------------------------
       
   156 // SetVisual
       
   157 // ----------------------------------------------------------------------------
       
   158 //
       
   159 
       
   160 
       
   161 // ----------------------------------------------------------------------------
       
   162 // IsNearHoldingPoint
       
   163 // ----------------------------------------------------------------------------
       
   164 //
       
   165 TBool CGesture::IsNearHoldingPoint( const TPoint& aPoint ) const
       
   166     {
       
   167     return ToleranceRect( iPoints[ iHoldingPointIndex ].iPos ).Contains( aPoint );
       
   168     }
       
   169 
       
   170 // ----------------------------------------------------------------------------
       
   171 // IsLatestPoint
       
   172 // ----------------------------------------------------------------------------
       
   173 //
       
   174 TBool CGesture::IsLatestPoint( const TPoint& aPoint ) const
       
   175     {
       
   176     if ( iPoints.Count() > 0 )
       
   177         {
       
   178         return aPoint == CurrentPos();
       
   179         }
       
   180     return EFalse;
       
   181     }
       
   182 
       
   183 // ----------------------------------------------------------------------------
       
   184 // StartHolding
       
   185 // ----------------------------------------------------------------------------
       
   186 //
       
   187 void CGesture::StartHolding()
       
   188     {
       
   189     iHoldingState = EHoldStarting;
       
   190     
       
   191     // remove all points that were introduced after holding started
       
   192     for ( TInt i = iPoints.Count() - 1; i > iHoldingPointIndex; i-- )
       
   193         {
       
   194         iPoints.Remove( i );
       
   195         }
       
   196     }
       
   197 
       
   198 // ----------------------------------------------------------------------------
       
   199 // SetHoldingPoint
       
   200 // ----------------------------------------------------------------------------
       
   201 //
       
   202 void CGesture::SetHoldingPoint()
       
   203     {
       
   204     iHoldingPointIndex = iPoints.Count() - 1;
       
   205     }
       
   206 
       
   207 // ----------------------------------------------------------------------------
       
   208 // ContinueHolding
       
   209 // ----------------------------------------------------------------------------
       
   210 //
       
   211 void CGesture::ContinueHolding()
       
   212     {
       
   213     iHoldingState = EHolding;
       
   214     }
       
   215 
       
   216 // ----------------------------------------------------------------------------
       
   217 // SetReleased
       
   218 // ----------------------------------------------------------------------------
       
   219 //
       
   220 void CGesture::SetReleased()
       
   221     {
       
   222     // IsMovementStopped expects SetComplete to be called before SetRelea
       
   223     __ASSERT_DEBUG( EComplete == iState, Panic( EGesturePanicIllegalLogic ) );
       
   224     iState = EReleased;
       
   225     }
       
   226 
       
   227 /**
       
   228  * @return elapsed time between aStartTime and aEndTime
       
   229  */
       
   230 inline TTimeIntervalMicroSeconds32 Elapsed( const TTime& aStartTime, 
       
   231                                             const TTime& aEndTime )
       
   232     {
       
   233     return aEndTime.MicroSecondsFrom( aStartTime ).Int64();
       
   234     }
       
   235 
       
   236 // ----------------------------------------------------------------------------
       
   237 // SetComplete
       
   238 // ----------------------------------------------------------------------------
       
   239 //
       
   240 void CGesture::SetComplete()
       
   241     {
       
   242     __ASSERT_DEBUG( iPoints.Count() > 0, Panic( EGesturePanicIllegalLogic ) );
       
   243     iState = EComplete;
       
   244     iCompletionTime = CurrentTime();
       
   245     }
       
   246 
       
   247 // ----------------------------------------------------------------------------
       
   248 // SetComplete
       
   249 // ----------------------------------------------------------------------------
       
   250 //
       
   251 void CGesture::SetCancelled()
       
   252     {
       
   253     iState = ECancelled;
       
   254     }
       
   255 
       
   256 void CGesture::SetDoubleTap() 
       
   257     { 
       
   258     iIsDoubleTap = ETrue; 
       
   259     }
       
   260 
       
   261 void CGesture::SetLongTap(TBool aLongTap) 
       
   262     { 
       
   263     iIsLongTap = aLongTap; 
       
   264     }
       
   265 
       
   266 // ----------------------------------------------------------------------------
       
   267 // IsTap
       
   268 // ----------------------------------------------------------------------------
       
   269 //
       
   270 TBool CGesture::IsTap() const
       
   271     {
       
   272     return CodeFromPoints( iPoints, EAxisBoth ) == EGestureTap;
       
   273     }
       
   274 
       
   275 /**
       
   276  * Translates a non-holding code into a holding code
       
   277  * @param aCode original gesture code
       
   278  * @return a gesture code with hold flag applied
       
   279  */
       
   280 inline TGestureCode Hold( TGestureCode aCode )
       
   281     {
       
   282     if ( aCode != EGestureStart && 
       
   283          aCode != EGestureDrag && 
       
   284          aCode != EGestureReleased && 
       
   285          aCode != EGestureUnknown )
       
   286         {
       
   287         return static_cast< TGestureCode >( aCode | EFlagHold );
       
   288         }
       
   289     return aCode;
       
   290     }
       
   291     
       
   292 // ----------------------------------------------------------------------------
       
   293 // Code
       
   294 // ----------------------------------------------------------------------------
       
   295 //
       
   296 TGestureCode CGesture::Code( TAxis aRelevantAxis ) /* const */
       
   297     {
       
   298     TGestureCode code;
       
   299     
       
   300     switch ( iState )
       
   301         {
       
   302         case ENotComplete:
       
   303             {
       
   304             // "start" event if only first point received
       
   305             // need to check that not holding, in case user pressed stylus
       
   306             // down, and activated holding without moving the stylus
       
   307             if ( iPoints.Count() == 1 && !IsHolding() )
       
   308                 {
       
   309                 code = EGestureStart;
       
   310                 }
       
   311             // "drag" event if holding not started or holding started earlier
       
   312             else if ( iHoldingState != EHoldStarting )
       
   313                 {
       
   314                 code = EGestureDrag; 
       
   315                 }
       
   316             // holding was just started
       
   317             else 
       
   318                 {
       
   319                 code = Hold( CodeFromPoints( iPoints, aRelevantAxis ) );
       
   320                 }
       
   321             iPrevGestureCode = code;
       
   322             break;
       
   323             }
       
   324         case EComplete:
       
   325             {
       
   326             if ( iIsDoubleTap )
       
   327                 {
       
   328                 code = EGestureDoubleTap;
       
   329                 }
       
   330             else if ( iIsLongTap )
       
   331                 {
       
   332                 code = EGestureLongTap;
       
   333                 }
       
   334             
       
   335             else if (iPrevGestureCode == EGestureDrag)
       
   336                 {
       
   337                 //code = IsFlick() ? EGestureFlick : EGestureDrop;
       
   338                 if (IsFlick()) 
       
   339                     {
       
   340                     code = EGestureFlick ;
       
   341                     }
       
   342                   else
       
   343                     {
       
   344                     // Check if it is a swipe.  In this case a swipe is a gesture where 
       
   345                     // - the direction is close to the axes (up, down, left, right)
       
   346                     // - speed is slower than flick 
       
   347                     code = CodeFromPoints( iPoints, aRelevantAxis );
       
   348                     if (code == EGestureUnknown)    
       
   349                       {
       
   350                       code = EGestureDrop ; // It was not a swipe, so then it is the drop gesture 
       
   351                       }
       
   352                     }
       
   353                 }
       
   354             else
       
   355                 {
       
   356                 code = CodeFromPoints( iPoints, aRelevantAxis );
       
   357                 }
       
   358             iPrevGestureCode = code;
       
   359             break;
       
   360             }
       
   361             
       
   362         case EReleased:
       
   363             {
       
   364             code = EGestureReleased;
       
   365             break;
       
   366             }
       
   367             
       
   368         case ECancelled: // fallthrough
       
   369         default: 
       
   370             code = EGestureUnknown;
       
   371         }
       
   372     return code;
       
   373     }
       
   374 
       
   375 
       
   376 TBool CGesture::IsFlick() const
       
   377     {
       
   378     bool flick = EFalse;
       
   379     TRealPoint speed = Speed();
       
   380     TReal32 xSpeed = speed.iX;
       
   381     TReal32 ySpeed = speed.iY;
       
   382             
       
   383     flick = (Abs(xSpeed) > KFlickSpeed || 
       
   384              Abs(ySpeed) > KFlickSpeed);
       
   385         
       
   386     return flick;
       
   387     }
       
   388 
       
   389 // ----------------------------------------------------------------------------
       
   390 // IsHolding
       
   391 // ----------------------------------------------------------------------------
       
   392 //
       
   393 TBool CGesture::IsHolding() const
       
   394     {
       
   395     return iHoldingState >= EHoldStarting;
       
   396     }
       
   397 
       
   398 // ----------------------------------------------------------------------------
       
   399 // StartPos
       
   400 // ----------------------------------------------------------------------------
       
   401 //
       
   402 TPoint CGesture::StartPos() const
       
   403     {
       
   404     // at least one point will be in the array during callback (pointer down pos)
       
   405     return iPoints[ 0 ].iPos;
       
   406     }
       
   407 
       
   408 // ----------------------------------------------------------------------------
       
   409 // CurrentPos
       
   410 // ----------------------------------------------------------------------------
       
   411 //
       
   412 TPoint CGesture::CurrentPos() const
       
   413     {
       
   414     // at least on point will be in the array during callback (pointer down pos)
       
   415     return iPoints.Count() > 0 ? iPoints[ iPoints.Count() - 1 ].iPos : TPoint(-1, -1);
       
   416     }
       
   417 
       
   418 // ----------------------------------------------------------------------------
       
   419 // IsMovementStopped
       
   420 // ----------------------------------------------------------------------------
       
   421 //
       
   422 inline TBool CGesture::IsMovementStopped() const
       
   423     {
       
   424     // iCompletionTime is only only valid if client has called SetComplete 
       
   425     if ( iState >= EComplete )
       
   426         {
       
   427         TInt el = Elapsed( NthLastEntry( 1 ).iTime, iCompletionTime ).Int(); 
       
   428         return el > KSpeedStopTime;
       
   429         }
       
   430     return EFalse;
       
   431     }
       
   432 
       
   433 namespace 
       
   434     {
       
   435     const TInt KFloatingPointAccuracy = 0.000001;
       
   436     
       
   437     /** @return percentage (0.0-1.0) how far aPos is from aEdge1 towards aEdge2 */
       
   438     inline TReal32 Proportion( TReal32 aPos, TReal32 aEdge1, TReal32 aEdge2 )
       
   439         {
       
   440         if ( Abs( aEdge2 - aEdge1 ) > KFloatingPointAccuracy )
       
   441             {
       
   442             return ( aPos - aEdge1 ) / ( aEdge2 - aEdge1 );
       
   443             }
       
   444         return 0; // avoid division by zero 
       
   445         }
       
   446     
       
   447     /** Edges (pixels) at which speed should be -100% or 100% */
       
   448     NONSHARABLE_STRUCT( TEdges )
       
   449         {
       
   450         TReal32 iMin;
       
   451         TReal32 iMax;
       
   452         };
       
   453         
       
   454     /** 
       
   455      * scale which allows different (coordinate -> percentage) mapping
       
   456      * between -100% to 0% and 0 and 100%
       
   457      */
       
   458     NONSHARABLE_STRUCT( TScale )
       
   459         {
       
   460         TScale( TInt aZero, const TEdges& aEdges )
       
   461                 : iMin( aEdges.iMin ), iZero( aZero ), iMax( aEdges.iMax )
       
   462             {
       
   463             }
       
   464         
       
   465         /** @return aPos as a percentage between -100% and 100% in aScale */
       
   466         TReal32 Percent( TReal32 aPos ) const;
       
   467 
       
   468         /// coordinate where speed is -100%
       
   469         TReal32 iMin;
       
   470         /// coordinate where speed is 0%
       
   471         TReal32 iZero;
       
   472         /// coordinate where speed is 100%
       
   473         TReal32 iMax;
       
   474         };
       
   475         
       
   476     /** @convert aPos into a percentage between -100% and 100% in aScale */
       
   477     TReal32 TScale::Percent( TReal32 aPos ) const
       
   478         {
       
   479         TReal32 percent;
       
   480         if ( aPos < iZero )
       
   481             {
       
   482             // return negative percentages on the lower side of zero point
       
   483             percent = -1 * Proportion( aPos, iZero, iMin );
       
   484             }
       
   485         else 
       
   486             {
       
   487             percent = Proportion( aPos, iZero, iMax );
       
   488             }
       
   489         // constrain between -100% and 100%
       
   490         return Min( Max( percent, -1.0F ), 1.0F );
       
   491         }
       
   492     
       
   493     /** Scale in x and y dimensions */
       
   494     NONSHARABLE_STRUCT( TScale2D )
       
   495         {
       
   496         TRealPoint Percent( const TPoint& aPos ) const
       
   497             {
       
   498             return TRealPoint( iX.Percent( aPos.iX ),
       
   499                                iY.Percent( aPos.iY ) );
       
   500             }
       
   501                 
       
   502         TScale iX;
       
   503         TScale iY;
       
   504         };
       
   505             
       
   506     enum TDirection { ESmaller, ELarger };
       
   507     
       
   508     /** @return the direction of pos compared to the previous pos */
       
   509     inline TDirection Direction( TInt aPos, TInt aPreviousPos )
       
   510         {
       
   511         return aPos < aPreviousPos ? ESmaller : ELarger;    
       
   512         }
       
   513 
       
   514     /** Direction in x and y dimensions */
       
   515     NONSHARABLE_STRUCT( TDirection2D )
       
   516         {
       
   517         TDirection iX;
       
   518         TDirection iY;
       
   519         };
       
   520 
       
   521     /** Return the direction (up/down) of signal at aIndex */
       
   522     inline TDirection2D Direction( TInt aIndex, const RArray< TPointEntry >& aPoints )
       
   523         {
       
   524         const TPoint& pos = aPoints[ aIndex ].iPos;
       
   525         const TPoint& prevPos = aPoints[ aIndex - 1 ].iPos;
       
   526         TDirection2D dir = { Direction( pos.iX, prevPos.iX ),
       
   527                              Direction( pos.iY, prevPos.iY ) };
       
   528         return dir;
       
   529         }   
       
   530     /** 
       
   531      * @return a position in the aLow and aHigh, so that it aProportion of
       
   532      *         of length is above the pos 
       
   533      */
       
   534     TReal32 ProportionalLength( TReal32 aLow, TReal32 aHigh, TReal32 aProportion )
       
   535         {
       
   536         return ( aHigh - aLow ) * aProportion / ( 1 + aProportion );
       
   537         }
       
   538     
       
   539     /** 
       
   540      * @return aVariableEdge scaled to new position, when the other edge changes
       
   541      *         from aOldEdge to aNewEdge, so that aOrigin maintains the *same relative
       
   542      *         position* between aVariableEdge and the other edge 
       
   543      */
       
   544     inline TReal32 ScaledEdge( TReal32 aOrigin, TReal32 aVariableEdge, 
       
   545             TReal32 aOldEdge, TReal aNewEdge )
       
   546         {
       
   547         TReal32 proportion = Proportion( aOrigin, aVariableEdge, aOldEdge );
       
   548         return ( proportion * aNewEdge - aOrigin ) / ( proportion - 1 );
       
   549         }
       
   550     
       
   551     TScale Rescale( TReal32 aPos, TDirection aDir, TDirection aPrevDir, 
       
   552             const TScale& aPrevScale, const TEdges& aEdges )
       
   553         {
       
   554         TScale scale( aPrevScale );
       
   555         if ( aPrevDir != aDir )
       
   556             {
       
   557             // the code duplication is accepted here, since it is difficult to factor out
       
   558             // while maintaining the understandability of this anyway complex algorithm
       
   559             if ( aDir == ESmaller )
       
   560                 {
       
   561                 scale.iMin = aEdges.iMin;
       
   562                 if ( aPrevScale.iZero < aPos )
       
   563                     {
       
   564                     TReal32 proportionAboveZero = Proportion( aPos, aPrevScale.iZero, aPrevScale.iMax );
       
   565                     scale.iZero = aPos - ProportionalLength( aEdges.iMin, aPos, proportionAboveZero );
       
   566                     }
       
   567                 else 
       
   568                     {
       
   569                     // adjust zero pos so that proportion between aPos, Min, and Zero pos 
       
   570                     // stay the same (Min will move to 0, aPos stays the same)
       
   571                     scale.iZero = ScaledEdge( aPos, aPrevScale.iZero, 
       
   572                         aPrevScale.iMin, aEdges.iMin );
       
   573                     }
       
   574                 // adjust the upper edge to take into account the movement of zero pos
       
   575                 scale.iMax = ScaledEdge( aPos, aPrevScale.iMax, 
       
   576                     aPrevScale.iZero, scale.iZero );
       
   577                 }
       
   578             else // ELarger
       
   579                 {
       
   580                 scale.iMax = aEdges.iMax;
       
   581                 if ( aPos < aPrevScale.iZero )
       
   582                     {
       
   583                     TReal32 proportionBelowZero = Proportion( aPos, aPrevScale.iZero, aPrevScale.iMin );
       
   584                     scale.iZero = aPos + ProportionalLength( aPos, aEdges.iMax, proportionBelowZero );
       
   585                     }
       
   586                 else
       
   587                     {
       
   588                     // adjust zero pos so that proportion between aPos, Max, and Zero pos 
       
   589                     // stay the same (Max will move edge, aPos stays the same)
       
   590                     scale.iZero = ScaledEdge( aPos, aPrevScale.iZero, 
       
   591                         aPrevScale.iMax, aEdges.iMax );
       
   592                     }
       
   593                 // adjust the lower edge to take into account the movement of zero pos
       
   594                 scale.iMin = ScaledEdge( aPos, aPrevScale.iMin, 
       
   595                     aPrevScale.iZero, scale.iZero );
       
   596                 }
       
   597             }
       
   598         return scale;
       
   599         }
       
   600      
       
   601     /** Edges in x and y dimensions */
       
   602     NONSHARABLE_STRUCT( TEdges2D )
       
   603         {
       
   604         TEdges iX;
       
   605         TEdges iY;
       
   606         };
       
   607     
       
   608     /** 
       
   609      * @param aEdges edges of the area in which gesture points are accepted
       
   610      * @return the scale of latest point in the list of points 
       
   611      */
       
   612     TScale2D Scale( const RArray< TPointEntry >& aPoints, const TEdges2D& aEdges )
       
   613         {
       
   614         TScale2D scale = { TScale( aPoints[0].iPos.iX, aEdges.iX ),
       
   615                            TScale( aPoints[0].iPos.iY, aEdges.iY ) };
       
   616         TInt count = aPoints.Count();
       
   617         if ( count > 1 )
       
   618             {
       
   619             // iterate the whole point list to arrive to the current scale
       
   620             TDirection2D dir( Direction( 1, aPoints ) );
       
   621             for ( TInt i = 1; i < count; i++ )
       
   622                 {
       
   623                 // get direction at i
       
   624                 TDirection2D newDir( Direction( i, aPoints ) );
       
   625                 // get new scale at i
       
   626                 scale.iX = Rescale( aPoints[i - 1].iPos.iX, newDir.iX, dir.iX, scale.iX, aEdges.iX );
       
   627                 scale.iY = Rescale( aPoints[i - 1].iPos.iY, newDir.iY, dir.iY, scale.iY, aEdges.iY );
       
   628                 dir = newDir;
       
   629                 }
       
   630             }
       
   631         return scale;
       
   632         }
       
   633     } // unnamed namespace
       
   634 
       
   635 TRealPoint CGesture::SpeedPercent( const TRect& aEdges ) const
       
   636     {
       
   637     // x and y coordinates are easier to handle separately, extract from TRect:
       
   638     // ((iMinX, iMinY), (iMaxX, iMaxY)) -> ((iMinX, iMaxX), (iMinY, iMaxY))
       
   639     TEdges2D edges = { { aEdges.iTl.iX, aEdges.iBr.iX },
       
   640                        { aEdges.iTl.iY, aEdges.iBr.iY } };
       
   641     // work out the current scale (coordinate -> percentage mapping) from 
       
   642     // the history of points (i.e., points of current gesture). Then
       
   643     // calculate the percentage of the current position.
       
   644     return Scale( iPoints, edges ).Percent( CurrentPos() );
       
   645     }
       
   646     
       
   647 // ----------------------------------------------------------------------------
       
   648 // Speed
       
   649 // ----------------------------------------------------------------------------
       
   650 //
       
   651 TRealPoint CGesture::Speed() const
       
   652     {
       
   653     const TReal32 KMicroSecondsInSecond = 1000000;
       
   654     
       
   655     // Speed is only evaluated at the end of the swipe
       
   656     // if user stops at the end of the swipe before lifting stylus,
       
   657     // speed is zero. If time is zero, return 0 speed (infinite does 
       
   658     // not make sense either). Will need to consider also earlier points 
       
   659     // and their times or start time, if this zero-speed behavior is a problem
       
   660     TRealPoint speed;
       
   661     TReal32 time = static_cast<TReal32>( TimeFromPreviousPoint().Int() ) 
       
   662         / KMicroSecondsInSecond;
       
   663     if ( !IsMovementStopped() && time > 0 )
       
   664         {
       
   665         TPoint distance = CurrentPos() - PreviousPos();
       
   666         speed.iX = static_cast<TReal32>( distance.iX ) / time;
       
   667         speed.iY = static_cast<TReal32>( distance.iY ) / time;
       
   668         }
       
   669     return speed;
       
   670     }
       
   671 
       
   672 // ----------------------------------------------------------------------------
       
   673 // Distance
       
   674 // ----------------------------------------------------------------------------
       
   675 //
       
   676 TPoint CGesture::Distance() const
       
   677     {
       
   678     return CurrentPos() - StartPos();
       
   679     }
       
   680 
       
   681 // ----------------------------------------------------------------------------
       
   682 // Visual
       
   683 // ----------------------------------------------------------------------------
       
   684 //
       
   685 
       
   686 // ----------------------------------------------------------------------------
       
   687 // TimeFromPreviousPoint
       
   688 // ----------------------------------------------------------------------------
       
   689 //
       
   690 inline TTimeIntervalMicroSeconds32 CGesture::TimeFromPreviousPoint() const
       
   691     {
       
   692     const TInt KLatestEntryOffset = 1;
       
   693     return Elapsed( PreviousEntry().iTime, NthLastEntry( KLatestEntryOffset ).iTime );
       
   694     }
       
   695 
       
   696 // ----------------------------------------------------------------------------
       
   697 // return nth point from the end of the points array
       
   698 // ----------------------------------------------------------------------------
       
   699 //
       
   700 inline const TPointEntry& CGesture::NthLastEntry( TInt aOffset ) const
       
   701     {
       
   702     return iPoints[ Max( iPoints.Count() - aOffset, 0 ) ];
       
   703     }
       
   704 
       
   705 // ----------------------------------------------------------------------------
       
   706 // PreviousEntry
       
   707 // ----------------------------------------------------------------------------
       
   708 //
       
   709 inline const TPointEntry& CGesture::PreviousEntry() const
       
   710     {
       
   711     return NthLastEntry( KPreviousPointOffset );
       
   712     }
       
   713 
       
   714 // ----------------------------------------------------------------------------
       
   715 // PreviousPos
       
   716 // ----------------------------------------------------------------------------
       
   717 //
       
   718 inline TPoint CGesture::PreviousPos() const
       
   719     {
       
   720     return PreviousEntry().iPos;
       
   721     }
       
   722 
       
   723 inline TTime CGesture::TimeOfLastEntry() const
       
   724     {
       
   725     return NthLastEntry( 1 ).iTime;
       
   726     }