void TouchUi::touchesMoved( app::TouchEvent& event ) { if ( !isEventValid( event ) ) { return; } mTouches.clear(); if ( mEnabledTap ) { bool tapped = false; for ( const TouchEvent::Touch& touch : event.getTouches() ) { const vec2& tap = touch.getPos(); if ( mMask.contains( tap ) && glm::distance( tap, mTapPosition ) < mTapThreshold ) { tapped = true; pushTouch( touch ); break; } } if ( !tapped ) { resetTap(); } } const vec2 panSpeed( mPanSpeed.x * pow( ( mScaleMax.x + mScaleMin.x ) - mScale.x, 0.0002f ), mPanSpeed.y * pow( ( mScaleMax.y + mScaleMin.y ) - mScale.y, 0.0002f ) ); bool applyPan = false; bool applyRotation = false; bool applyScale = false; float panX = 0.0f; float panY = 0.0f; float scaleX = 0.0f; float scaleY = 0.0f; float rotation = 0.0f; // Pan if ( mEnabledPan ) { for ( const TouchEvent::Touch& touch : event.getTouches() ) { const vec2 a( touch.getPos() ); const vec2 b( touch.getPrevPos() ); if ( mMask.contains( b ) ) { panX = a.x - b.x; panY = a.y - b.y; pushTouch( touch ); break; } } } // Multi-touch TouchEvent::Touch a; TouchEvent::Touch b; size_t numTouches = event.getTouches().size(); if ( numTouches > 1 && mNumTouchPointsMax > 1 ) { a = *event.getTouches().begin(); b = *( event.getTouches().begin() + 1 ); const vec2 ap0( a.getPos() ); const vec2 ap1( a.getPrevPos() ); const vec2 bp0( b.getPos() ); const vec2 bp1( b.getPrevPos() ); if ( mMask.contains( bp0 ) && mMask.contains( bp1 ) ) { // Scale if ( mEnabledScale ) { float dx0 = glm::distance( ap0.x, bp0.x ); float dx1 = glm::distance( ap1.x, bp1.x ); scaleX = dx0 - dx1; float dy0 = glm::distance( ap0.y, bp0.y ); float dy1 = glm::distance( ap1.y, bp1.y ); scaleY = dy0 - dy1; } // Rotation if ( mEnabledRotation ) { float a0 = atan2( ap0.y - bp0.y, ap0.x - bp0.x ); float a1 = atan2( ap1.y - bp1.y, ap1.x - bp1.x ); rotation = wrapAngle( a0 - a1 ); } } } vector<Motion> motions = { { MotionType_PanX, abs( panX ) / mPanThreshold.x }, { MotionType_PanY, abs( panY ) / mPanThreshold.y }, { MotionType_Rotation, abs( rotation ) / mRotationThreshold }, { MotionType_ScaleX, abs( scaleX ) / mScaleThreshold.x }, { MotionType_ScaleY, abs( scaleY ) / mScaleThreshold.y }, }; auto evaluateMotion = [ &applyPan, &applyRotation, &applyScale ]( const Motion& motion ) { MotionType t = motion.first; if ( motion.second > 1.0f ) { if ( t == MotionType_PanX || t == MotionType_PanY ) { applyPan = true; } else if ( t == MotionType_Rotation ) { applyRotation = true; } else if ( t == MotionType_ScaleX || t == MotionType_ScaleY ) { applyScale = true; } } }; if ( mEnabledConstrain ) { sort( motions.begin(), motions.end(), []( const Motion& a, const Motion& b ) -> bool { return a.second > b.second; } ); evaluateMotion( *motions.begin() ); } else { for ( const Motion& motion : motions ) { evaluateMotion( motion ); } } if ( numTouches > 1 && ( applyPan || applyRotation || applyScale ) ) { pushTouch( a ); pushTouch( b ); } if ( applyPan ) { mPanTarget.x += panX * panSpeed.x; mPanTarget.y += panY * panSpeed.y; } if ( applyRotation ) { mRotationTarget.z += rotation * mRotationSpeed; } if ( applyScale ) { if ( mScaleSymmetry ) { mScaleTarget += vec2( ( scaleX * mScaleSpeed.x + scaleY * mScaleSpeed.y ) * 0.5f ); } else { mScaleTarget += vec2( scaleX * mScaleSpeed.x, scaleY * mScaleSpeed.y ); } } }