diff -r 4ea6f81c838a -r 0e9bb658ef58 mulwidgets/gesturehelper/src/gesture.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mulwidgets/gesturehelper/src/gesture.cpp Wed Sep 01 12:23:18 2010 +0100 @@ -0,0 +1,930 @@ +/* +* 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 + +#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( TimeFromPreviousPoint().Int() ) + / KMicroSecondsInSecond; + if ( !IsMovementStopped() && time > 0 ) + { + TPoint distance = CurrentPos() - PreviousPos(); + speed.iX = static_cast( distance.iX ) / time; + speed.iY = static_cast( 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 + { + // Added 0.5 to avoid 5.7 getting rounded off to 5. + return (iPinchEndDistance*100/iPinchStartDistance) + 0.5; + } + +// ---------------------------------------------------------------------------- +// PinchCentrePoint +// ---------------------------------------------------------------------------- +// +TPoint CGesture::PinchCentrePoint() const + { + if( iPoints.Count() <= 0 || iSecondaryPoints.Count() <= 0 ) + { + return TPoint(0,0); + } + return TPoint( (iPoints[0].iPos.iX + iSecondaryPoints[0].iPos.iX)/2, (iPoints[0].iPos.iY + iSecondaryPoints[0].iPos.iY)/2); + } + +// end of file +