mulwidgets/gesturehelper/src/gesture.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Thu, 17 Dec 2009 08:56:02 +0200
changeset 0 e83bab7cf002
child 23 514d98f21c43
permissions -rw-r--r--
Revision: 200949 Kit: 200951

/*
* 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