mulwidgets/gesturehelper/src/gesture.cpp
changeset 0 e83bab7cf002
child 23 514d98f21c43
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mulwidgets/gesturehelper/src/gesture.cpp	Thu Dec 17 08:56:02 2009 +0200
@@ -0,0 +1,931 @@
+/*
+* 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 "gesture.h"
+
+#include <e32math.h>
+
+#include "gesturedefs.h"
+#include "utils.h"
+
+using namespace GestureHelper;
+
+/** 
+ * Point array for which only x axis is relevant
+ */
+class TXAxisPointArray : public TPointArray
+    {
+public:
+    TXAxisPointArray( const RArray< TPointEntry >& aPoints )
+            : TPointArray( aPoints ) {}
+        
+    // from TPointArray
+    TPoint operator[]( TInt aIndex ) const 
+        {
+        return TPoint( Raw( aIndex ).iX, 0 );
+        }
+    };
+
+/** 
+ * Point array for which only y axis is relevant
+ */
+class TYAxisPointArray : public TPointArray
+    {
+public:
+    TYAxisPointArray( const RArray< TPointEntry >& aPoints )
+            : TPointArray( aPoints ) {}
+    
+    // from TPointArray
+    TPoint operator[]( TInt aIndex ) const 
+        {
+        return TPoint( 0, Raw( aIndex ).iY );
+        }
+    };
+
+namespace 
+    {
+    /** @return the current time */
+    TTime CurrentTime()
+        {
+        TTime time;
+        time.UniversalTime();
+        return time;
+        }
+
+    /**
+     * @param aRelevantAxis See @ref MGestureEvent::Code
+     * @return gesture code by analysing the sequence of points
+     */
+    TGestureCode CodeFromPoints( const RArray< TPointEntry >& aPoints, 
+            MGestureEvent::TAxis aRelevantAxis ) 
+        {
+        // 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( aPoints );
+        TYAxisPointArray filterX( aPoints );
+        TPointArray filterNone( aPoints );
+        TPointArray& filter = 
+            aRelevantAxis == MGestureEvent::EAxisHorizontal ? static_cast< TPointArray& >( filterY ) : 
+            aRelevantAxis == MGestureEvent::EAxisVertical   ? static_cast< TPointArray& >( 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 TGestureRecogniser().GestureCode( filter, aRelevantAxis );
+        }
+
+    /**
+     * @param aPoints Sequence of points representing 1st pointer movement
+     * @param aSecondaryPoints Sequence of points representing 2nd pointer movement
+     * @param aPreviousDistance contains the previous distance aftre this function execution 
+     * @return gesture code by analysing the sequence of points
+     */
+    TGestureCode MultiTouchCode( const RArray< TPointEntry >& aPoints, 
+            const RArray< TPointEntry >& aSecondaryPoints, TInt aPreviousDistance, TBool aIsFirstPinch ) 
+        {
+        TPointArray filter(aPoints);
+        TPointArray secondaryFilter(aSecondaryPoints);
+        return TGestureRecogniser().MultiTouchGestureCode( filter, secondaryFilter, aPreviousDistance, aIsFirstPinch );
+        }
+    } // unnamed namespace
+
+// ----------------------------------------------------------------------------
+// constructor
+// ----------------------------------------------------------------------------
+//
+CGesture::CGesture()
+    {
+    iPinchStartDistance = -1;
+    iPinchEndDistance = -1;
+    iPinchDetected = EFalse ; 
+    }
+
+// ----------------------------------------------------------------------------
+// destructor
+// ----------------------------------------------------------------------------
+//
+CGesture::~CGesture()
+    {
+    iPoints.Close();
+    iSecondaryPoints.Close();
+    }
+
+// ----------------------------------------------------------------------------
+// AsStartEventL
+// ----------------------------------------------------------------------------
+//
+CGesture* CGesture::AsStartEventLC() const
+    {
+    __ASSERT_DEBUG( 0 < iPoints.Count(), Panic( EGesturePanicIllegalLogic ) );
+    CGesture* gesture = new ( ELeave ) CGesture;
+    CleanupStack::PushL( gesture );
+    User::LeaveIfError( gesture->AddPoint( iPoints[0].iPos ) );
+    return gesture;
+    }
+
+// ----------------------------------------------------------------------------
+// ResetToLastPoint
+// ----------------------------------------------------------------------------
+//
+void CGesture::ResetToLastPoint(TBool aSetPointerZero,TBool aSetToZero)
+    {
+    TPointEntry lastEntry(TPoint(0,0),TTime());
+    if( aSetToZero )
+        {
+        __ASSERT_DEBUG( 0 < iPoints.Count(), Panic( EGesturePanicIllegalLogic ) );
+        lastEntry = LastPoint( iPoints);
+        }
+    else
+        {
+        __ASSERT_DEBUG( 0 < iSecondaryPoints.Count(), Panic( EGesturePanicIllegalLogic ) );
+        lastEntry = LastPoint( iSecondaryPoints);
+        }
+    Reset();
+    if( aSetPointerZero )
+        {
+        iPoints.Append(lastEntry);
+        }
+    else
+        {
+        iSecondaryPoints.Append(lastEntry);  
+        }     
+    }
+
+// ----------------------------------------------------------------------------
+// LastPoint
+// ----------------------------------------------------------------------------
+//
+inline const TPointEntry CGesture::LastPoint( const RArray< TPointEntry >& aPoints ) const
+    {
+    return aPoints[aPoints.Count() - 1];
+    }
+
+// ----------------------------------------------------------------------------
+// IsPinchToleranceHigh
+// ----------------------------------------------------------------------------
+//
+inline TBool CGesture::IsHighPinchTolerance() const
+    {
+  
+    if( iPinchEndDistance == -1 )
+        {
+        return ETrue;
+        }
+    else
+        {
+        TInt currentDistance = TGestureRecogniser().Length( LastPoint(iPoints).iPos, LastPoint(iSecondaryPoints).iPos );
+        // if previously zooming out and current distance corresponds to zooming in or viceversa
+        if( ( iZoomState == EZoomOut && currentDistance < iPinchStartDistance) || 
+                ( iZoomState == EZoomIn && currentDistance > iPinchStartDistance))
+            {
+            return ETrue;
+            }
+        }
+    return EFalse;
+    }
+// ----------------------------------------------------------------------------
+// Reset
+// ----------------------------------------------------------------------------
+//
+void CGesture::Reset()
+    {
+    iPoints.Reset();
+    iSecondaryPoints.Reset();
+    iHoldingState = ENotHolding;
+    iState = ENotActive;
+    iHoldingPointIndex = 0;
+    iVisual = NULL;
+    iIsDoubleTap = EFalse;
+    iPinchStartDistance = -1;
+    iPinchEndDistance = -1;
+    iPinchDetected = EFalse;
+    iZoomState = ENoZoom;
+    }
+    
+// ----------------------------------------------------------------------------
+// IsEmpty
+// ----------------------------------------------------------------------------
+//
+TBool CGesture::IsEmpty() const
+    {
+    return iPoints.Count() == 0;
+    }
+
+// ----------------------------------------------------------------------------
+// IsMultiTouch
+// ----------------------------------------------------------------------------
+//
+TBool CGesture::IsMultiTouch() const
+    {
+    return iSecondaryPoints.Count() == 0;
+    }
+
+// ----------------------------------------------------------------------------
+// Add a point to the sequence of points that together make up the gesture
+// ----------------------------------------------------------------------------
+//
+TInt CGesture::AddPoint( const TPoint& aPoint )
+    {
+    if ( !IsLatestPoint( aPoint ) )
+        {
+        return iPoints.Append( TPointEntry( aPoint, CurrentTime() ) );
+        }
+    return KErrNone;
+    }
+
+// ----------------------------------------------------------------------------
+// AddSecondaryPoint
+// ----------------------------------------------------------------------------
+//
+TInt CGesture::AddSecondaryPoint( const TPoint& aPoint )
+    {
+    if ( !IsLatestSecondaryPoint( aPoint ) )
+        {
+        return iSecondaryPoints.Append( TPointEntry( aPoint, CurrentTime() ) );
+        }
+    return KErrNone;
+    }
+
+// ----------------------------------------------------------------------------
+// SetVisual
+// ----------------------------------------------------------------------------
+//
+void CGesture::SetVisual( CAlfVisual* aVisual )
+    {
+    iVisual = aVisual;
+    }
+
+// ----------------------------------------------------------------------------
+// IsNearHoldingPoint
+// ----------------------------------------------------------------------------
+//
+TBool CGesture::IsNearHoldingPoint( const TPoint& aPoint ) const
+    {
+    return ToleranceRect( iPoints[ iHoldingPointIndex ].iPos ).Contains( aPoint );
+    }
+
+// ----------------------------------------------------------------------------
+// IsLatestPoint
+// ----------------------------------------------------------------------------
+//
+TBool CGesture::IsLatestPoint( const TPoint& aPoint ) const
+    {
+    if ( iPoints.Count() > 0 )
+        {
+        return aPoint == CurrentPos();
+        }
+    return EFalse;
+    }
+// ----------------------------------------------------------------------------
+// IsLatestSecondaryPoint
+// ----------------------------------------------------------------------------
+//
+TBool CGesture::IsLatestSecondaryPoint( const TPoint& aPoint ) const
+    {
+    if ( iSecondaryPoints.Count() > 0 )
+        {
+        return aPoint == iSecondaryPoints[ iSecondaryPoints.Count() - 1 ].iPos;
+        }
+    return EFalse;
+    }
+
+// ----------------------------------------------------------------------------
+// StartHolding
+// ----------------------------------------------------------------------------
+//
+void CGesture::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 CGesture::SetHoldingPoint()
+    {
+    iHoldingPointIndex = iPoints.Count() - 1;
+    }
+
+// ----------------------------------------------------------------------------
+// ContinueHolding
+// ----------------------------------------------------------------------------
+//
+void CGesture::ContinueHolding()
+    {
+    iHoldingState = EHolding;
+    }
+
+// ----------------------------------------------------------------------------
+// SetSingleTouchActive
+// ----------------------------------------------------------------------------
+//
+void CGesture::SetSingleTouchActive()
+    {
+    __ASSERT_DEBUG( ENotActive == iState, Panic( EGesturePanicIllegalLogic ) );
+    iState = ESingleTouchActive;
+    }
+// ----------------------------------------------------------------------------
+// SetMultiTouchActive
+// ----------------------------------------------------------------------------
+//
+void CGesture::SetMultiTouchActive()
+    {
+    iState = EMultiTouchActive;
+    iIsMultiTouched = ETrue;
+    }
+// ----------------------------------------------------------------------------
+// SetSingleTouchReleased
+// ----------------------------------------------------------------------------
+//
+void CGesture::SetSingleTouchReleased()
+    {
+    // IsMovementStopped expects corresponding SetComplete to be called before SetRelease
+    __ASSERT_DEBUG( ESingleTouchComplete == iState, Panic( EGesturePanicIllegalLogic ) );
+    iState = ESingleTouchReleased;
+    iIsMultiTouched = EFalse;
+    }
+// ----------------------------------------------------------------------------
+// SetMultiTouchReleased
+// ----------------------------------------------------------------------------
+//
+void CGesture::SetMultiTouchReleased()
+    {
+    // IsMovementStopped expects corresponding SetComplete to be called before SetRelease
+    __ASSERT_DEBUG( EMultiTouchComplete == iState, Panic( EGesturePanicIllegalLogic ) );
+    iState = EMultiTouchReleased;
+    }
+
+/**
+ * @return elapsed time between aStartTime and aEndTime
+ */
+inline TTimeIntervalMicroSeconds32 Elapsed( const TTime& aStartTime, 
+                                            const TTime& aEndTime )
+    {
+    return aEndTime.MicroSecondsFrom( aStartTime ).Int64();
+    }
+
+// ----------------------------------------------------------------------------
+// SetSingleTouchComplete
+// ----------------------------------------------------------------------------
+//
+void CGesture::SetSingleTouchComplete()
+    {
+    __ASSERT_DEBUG( iPoints.Count() > 0, Panic( EGesturePanicIllegalLogic ) );
+    iState = ESingleTouchComplete;
+    iCompletionTime = CurrentTime();
+    }
+
+// ----------------------------------------------------------------------------
+// SetMultiTouchComplete
+// ----------------------------------------------------------------------------
+//
+void CGesture::SetMultiTouchComplete()
+    {
+    __ASSERT_DEBUG( iSecondaryPoints.Count() > 0, Panic( EGesturePanicIllegalLogic ) );
+    iState = EMultiTouchComplete;
+    }
+
+// ----------------------------------------------------------------------------
+// SetCancelled
+// ----------------------------------------------------------------------------
+//
+void CGesture::SetCancelled()
+    {
+    iState = ECancelled;
+    }
+
+// ----------------------------------------------------------------------------
+// SetDoubleTap
+// ----------------------------------------------------------------------------
+//
+void CGesture::SetDoubleTap() 
+    { 
+    iIsDoubleTap = ETrue; 
+    }
+
+// ----------------------------------------------------------------------------
+// IsTap
+// ----------------------------------------------------------------------------
+//
+TBool CGesture::IsTap() const
+    {
+    if(iIsMultiTouched)
+        {
+        return EFalse;
+        }
+    return CodeFromPoints( iPoints, EAxisBoth ) == EGestureTap;
+    }
+
+// ----------------------------------------------------------------------------
+// IsPinch
+// ----------------------------------------------------------------------------
+//
+TBool CGesture::IsPinch()
+    {
+    if (iPinchStartDistance  == -1 )
+        {
+        iPinchStartDistance = TGestureRecogniser().Length( iPoints[0].iPos, iSecondaryPoints[0].iPos ) ;
+        }
+    // if there is a pinch detected in the last attempt then update the start distance.
+    // henceforth this new distance is used as reference for calculating the pinch.
+    
+    if( iPinchDetected )
+        {
+        iPinchStartDistance = iPinchEndDistance;
+        }
+    iPinchDetected = MultiTouchCode( iPoints, iSecondaryPoints, iPinchStartDistance,IsHighPinchTolerance()) == EGesturePinch;
+    if( iPinchDetected )
+        {
+        // This end distance is updated the first time the pinch is detected.
+        // This is done the save the value of pinch end distnce for further refernce to
+        // update the pinch start distance next time any pointer position changes.
+        iPinchEndDistance = TGestureRecogniser().Length( 
+                LastPoint(iPoints).iPos,LastPoint(iSecondaryPoints).iPos );
+        iZoomState = iPinchEndDistance > iPinchStartDistance ? EZoomOut : EZoomIn;
+        }
+    return iPinchDetected;
+    }
+
+/**
+ * Translates a non-holding code into a holding code
+ * @param aCode original gesture code
+ * @return a gesture code with hold flag applied
+ */
+inline TGestureCode Hold( TGestureCode aCode )
+    {
+    if ( aCode != EGestureStart && 
+         aCode != EGestureDrag && 
+         aCode != EGestureReleased && 
+         aCode != EGestureUnknown )
+        {
+        return static_cast< TGestureCode >( aCode | EFlagHold );
+        }
+    return aCode;
+    }
+    
+// ----------------------------------------------------------------------------
+// Code
+// ----------------------------------------------------------------------------
+//
+TGestureCode CGesture::Code( TAxis aRelevantAxis ) const
+    {
+    switch ( iState )
+        {
+        case ESingleTouchActive:           
+            // "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 )
+                {
+                // 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 );
+                TPointArray filterNone( iPoints );
+                TPointArray& filter = 
+                    aRelevantAxis == MGestureEvent::EAxisHorizontal ? static_cast< TPointArray& >( filterY ) : 
+                    aRelevantAxis == MGestureEvent::EAxisVertical   ? static_cast< TPointArray& >( filterX ) :
+                    /* otherwise EAxisBoth */                         filterNone;
+            
+                return TGestureRecogniser().ValidateDrag( filter, aRelevantAxis );
+                }
+            // holding was just started
+            else 
+                {
+                return Hold( CodeFromPoints( iPoints, aRelevantAxis ) );
+                }
+            
+        case EMultiTouchActive:
+            // Only if there are some points in secondary array
+            if ( iSecondaryPoints.Count() == 1 && iPoints.Count() == 1 )
+                {
+                return EGestureMultiTouchStart;
+                }
+            else
+                {
+                return MultiTouchCode( iPoints, iSecondaryPoints,  iPinchStartDistance,IsHighPinchTolerance() );
+                }
+
+        case ESingleTouchComplete:
+			{
+            if ( iIsDoubleTap )
+                {
+                return EGestureDoubleTap;
+                }
+            
+            // If there was a mulitouch within the last gesture then ignore the tap
+			TGestureCode gestureCode = CodeFromPoints( iPoints, aRelevantAxis );
+            if( gestureCode == EGestureTap && iIsMultiTouched)
+                {
+                return EGestureUnknown;
+                }
+            return gestureCode;
+            }
+        case EMultiTouchComplete:
+            return MultiTouchCode( iPoints, iSecondaryPoints, iPinchStartDistance,IsHighPinchTolerance());
+            
+        case ESingleTouchReleased:
+            return EGestureReleased;
+        case EMultiTouchReleased:
+            return EGestureMultiTouchReleased;         
+            
+        case ECancelled: // fallthrough
+        case ENotActive:
+        default: 
+            return EGestureUnknown;
+        }
+    }
+
+// ----------------------------------------------------------------------------
+// IsHolding
+// ----------------------------------------------------------------------------
+//
+TBool CGesture::IsHolding() const
+    {
+    return iHoldingState >= EHoldStarting;
+    }
+
+// ----------------------------------------------------------------------------
+// StartPos
+// ----------------------------------------------------------------------------
+//
+TPoint CGesture::StartPos() const
+    {
+    // at least one point will be in the array during callback (pointer down pos)
+    return iPoints[ 0 ].iPos;
+    }
+
+// ----------------------------------------------------------------------------
+// CurrentPos
+// ----------------------------------------------------------------------------
+//
+TPoint CGesture::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 CGesture::IsMovementStopped() const
+    {
+    // iCompletionTime is only only valid if client has called SetComplete 
+    if ( iState >= ESingleTouchComplete )
+        {
+        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< TPointEntry >& 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< TPointEntry >& 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 CGesture::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 CGesture::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 CGesture::Distance() const
+    {
+    return CurrentPos() - StartPos();
+    }
+
+// ----------------------------------------------------------------------------
+// Visual
+// ----------------------------------------------------------------------------
+//
+CAlfVisual* CGesture::Visual() const
+    {
+    return iVisual;
+    }
+
+// ----------------------------------------------------------------------------
+// TimeFromPreviousPoint
+// ----------------------------------------------------------------------------
+//
+inline TTimeIntervalMicroSeconds32 CGesture::TimeFromPreviousPoint() const
+    {
+    const TInt KLatestEntryOffset = 1;
+    return Elapsed( PreviousEntry().iTime, NthLastEntry( KLatestEntryOffset ).iTime );
+    }
+
+// ----------------------------------------------------------------------------
+// return nth point from the end of the points array
+// ----------------------------------------------------------------------------
+//
+inline const TPointEntry& CGesture::NthLastEntry( TInt aOffset ) const
+    {
+    return iPoints[ Max( iPoints.Count() - aOffset, 0 ) ];
+    }
+
+// ----------------------------------------------------------------------------
+// PreviousEntry
+// ----------------------------------------------------------------------------
+//
+inline const TPointEntry& CGesture::PreviousEntry() const
+    {
+    return NthLastEntry( KPreviousPointOffset );
+    }
+
+// ----------------------------------------------------------------------------
+// PreviousPos
+// ----------------------------------------------------------------------------
+//
+inline TPoint CGesture::PreviousPos() const
+    {
+    return PreviousEntry().iPos;
+    }
+
+// ----------------------------------------------------------------------------
+// PinchPercent
+// ----------------------------------------------------------------------------
+//
+TInt CGesture::PinchPercent() const
+    {
+    return (iPinchEndDistance*100/iPinchStartDistance);
+    }
+
+// ----------------------------------------------------------------------------
+// PinchCentrePoint
+// ----------------------------------------------------------------------------
+//
+TPoint CGesture::PinchCentrePoint() const
+    {
+    if( iPoints.Count() <= 0 || iSecondaryPoints.Count() <= 0 )
+        {
+        return TPoint(0,0);
+        }
+    TInt greaterX = iPoints[0].iPos.iX > iSecondaryPoints[0].iPos.iX ? iPoints[0].iPos.iX : iSecondaryPoints[0].iPos.iX;
+    TInt greaterY = iPoints[0].iPos.iY > iSecondaryPoints[0].iPos.iY ? iPoints[0].iPos.iY : iSecondaryPoints[0].iPos.iY; 
+    return TPoint( (Abs(iPoints[0].iPos.iX - iSecondaryPoints[0].iPos.iX)/2 + greaterX), (Abs(iPoints[0].iPos.iY - iSecondaryPoints[0].iPos.iY)/2 + greaterY));
+    }
+
+// end of file
+