float GetCurrentSpan(const MultiTouchInput& aEvent) { const ParentLayerPoint& firstTouch = aEvent.mTouches[0].mLocalScreenPoint; const ParentLayerPoint& secondTouch = aEvent.mTouches[1].mLocalScreenPoint; ParentLayerPoint delta = secondTouch - firstTouch; return delta.Length(); }
float Axis::ToLocalVelocity(float aVelocityInchesPerMs) const { ScreenPoint velocity = MakePoint(aVelocityInchesPerMs * mAsyncPanZoomController->GetDPI()); // Use ToScreenCoordinates() to convert a point rather than a vector by // treating the point as a vector, and using (0, 0) as the anchor. ScreenPoint panStart = mAsyncPanZoomController->ToScreenCoordinates( mAsyncPanZoomController->PanStart(), ParentLayerPoint()); ParentLayerPoint localVelocity = mAsyncPanZoomController->ToParentLayerCoordinates(velocity, panStart); return localVelocity.Length(); }
AndroidFlingAnimation::AndroidFlingAnimation(AsyncPanZoomController& aApzc, PlatformSpecificStateBase* aPlatformSpecificState, const RefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain, bool aFlingIsHandoff, const RefPtr<const AsyncPanZoomController>& aScrolledApzc) : mApzc(aApzc) , mOverscrollHandoffChain(aOverscrollHandoffChain) , mScrolledApzc(aScrolledApzc) , mSentBounceX(false) , mSentBounceY(false) { MOZ_ASSERT(mOverscrollHandoffChain); MOZ_ASSERT(aPlatformSpecificState->AsAndroidSpecificState()); mOverScroller = aPlatformSpecificState->AsAndroidSpecificState()->mOverScroller; MOZ_ASSERT(mOverScroller); // Drop any velocity on axes where we don't have room to scroll anyways // (in this APZC, or an APZC further in the handoff chain). // This ensures that we don't take the 'overscroll' path in Sample() // on account of one axis which can't scroll having a velocity. if (!mOverscrollHandoffChain->CanScrollInDirection(&mApzc, Layer::HORIZONTAL)) { ReentrantMonitorAutoEnter lock(mApzc.mMonitor); mApzc.mX.SetVelocity(0); } if (!mOverscrollHandoffChain->CanScrollInDirection(&mApzc, Layer::VERTICAL)) { ReentrantMonitorAutoEnter lock(mApzc.mMonitor); mApzc.mY.SetVelocity(0); } ParentLayerPoint velocity = mApzc.GetVelocityVector(); mPreviousVelocity = velocity; float scrollRangeStartX = mApzc.mX.GetPageStart().value; float scrollRangeEndX = mApzc.mX.GetScrollRangeEnd().value; float scrollRangeStartY = mApzc.mY.GetPageStart().value; float scrollRangeEndY = mApzc.mY.GetScrollRangeEnd().value; mStartOffset.x = mPreviousOffset.x = mApzc.mX.GetOrigin().value; mStartOffset.y = mPreviousOffset.y = mApzc.mY.GetOrigin().value; float length = velocity.Length(); if (length > 0.0f) { mFlingDirection = velocity / length; } int32_t originX = ClampStart(mStartOffset.x, scrollRangeStartX, scrollRangeEndX); int32_t originY = ClampStart(mStartOffset.y, scrollRangeStartY, scrollRangeEndY); mOverScroller->Fling(originX, originY, // Android needs the velocity in pixels per second and it is in pixels per ms. (int32_t)(velocity.x * 1000.0f), (int32_t)(velocity.y * 1000.0f), (int32_t)floor(scrollRangeStartX), (int32_t)ceil(scrollRangeEndX), (int32_t)floor(scrollRangeStartY), (int32_t)ceil(scrollRangeEndY), 0, 0); }
Maybe<LayerPoint> HitTestingTreeNode::Untransform(const ParentLayerPoint& aPoint) const { // convert into Layer coordinate space gfx::Matrix4x4 localTransform = mTransform; if (mApzc) { localTransform = localTransform * mApzc->GetCurrentAsyncTransformWithOverscroll(); } gfx::Point4D point = localTransform.Inverse().ProjectPoint(aPoint.ToUnknownPoint()); return point.HasPositiveWCoord() ? Some(ViewAs<LayerPixel>(point.As2DPoint())) : Nothing(); }
void AndroidFlingPhysics::Init(const ParentLayerPoint& aStartingVelocity, float aPLPPI) { mVelocity = aStartingVelocity.Length(); // We should not have created a fling animation if there is no velocity. MOZ_ASSERT(mVelocity != 0.0f); const double tuningCoeff = ComputeDeceleration(aPLPPI); mTargetDuration = ComputeFlingDuration(mVelocity, tuningCoeff); MOZ_ASSERT(!mTargetDuration.IsZero()); mDurationSoFar = TimeDuration(); mLastPos = ParentLayerPoint(); mCurrentPos = ParentLayerPoint(); float coeffX = mVelocity == 0 ? 1.0f : aStartingVelocity.x / mVelocity; float coeffY = mVelocity == 0 ? 1.0f : aStartingVelocity.y / mVelocity; mTargetDistance = ComputeFlingDistance(mVelocity, tuningCoeff); mTargetPos = ParentLayerPoint(mTargetDistance * coeffX, mTargetDistance * coeffY); const float hyp = mTargetPos.Length(); if (FuzzyEqualsAdditive(hyp, 0.0f)) { mDeltaNorm = ParentLayerPoint(1, 1); } else { mDeltaNorm = ParentLayerPoint(mTargetPos.x / hyp, mTargetPos.y / hyp); } }
AndroidFlingAnimation::AndroidFlingAnimation(AsyncPanZoomController& aApzc, PlatformSpecificStateBase* aPlatformSpecificState, const RefPtr<const OverscrollHandoffChain>& aOverscrollHandoffChain, bool aFlingIsHandoff, const RefPtr<const AsyncPanZoomController>& aScrolledApzc) : mApzc(aApzc) , mOverscrollHandoffChain(aOverscrollHandoffChain) , mScrolledApzc(aScrolledApzc) , mSentBounceX(false) , mSentBounceY(false) , mFlingDuration(0) { MOZ_ASSERT(mOverscrollHandoffChain); AndroidSpecificState* state = aPlatformSpecificState->AsAndroidSpecificState(); MOZ_ASSERT(state); mOverScroller = state->mOverScroller; MOZ_ASSERT(mOverScroller); // Drop any velocity on axes where we don't have room to scroll anyways // (in this APZC, or an APZC further in the handoff chain). // This ensures that we don't take the 'overscroll' path in Sample() // on account of one axis which can't scroll having a velocity. if (!mOverscrollHandoffChain->CanScrollInDirection(&mApzc, ScrollDirection::HORIZONTAL)) { ReentrantMonitorAutoEnter lock(mApzc.mMonitor); mApzc.mX.SetVelocity(0); } if (!mOverscrollHandoffChain->CanScrollInDirection(&mApzc, ScrollDirection::VERTICAL)) { ReentrantMonitorAutoEnter lock(mApzc.mMonitor); mApzc.mY.SetVelocity(0); } ParentLayerPoint velocity = mApzc.GetVelocityVector(); float scrollRangeStartX = mApzc.mX.GetPageStart().value; float scrollRangeEndX = mApzc.mX.GetScrollRangeEnd().value; float scrollRangeStartY = mApzc.mY.GetPageStart().value; float scrollRangeEndY = mApzc.mY.GetScrollRangeEnd().value; mStartOffset.x = mPreviousOffset.x = mApzc.mX.GetOrigin().value; mStartOffset.y = mPreviousOffset.y = mApzc.mY.GetOrigin().value; float length = velocity.Length(); if (length > 0.0f) { mFlingDirection = velocity / length; if ((sMaxFlingSpeed > 0.0f) && (length > sMaxFlingSpeed)) { velocity = mFlingDirection * sMaxFlingSpeed; } } mPreviousVelocity = velocity; int32_t originX = ClampStart(mStartOffset.x, scrollRangeStartX, scrollRangeEndX); int32_t originY = ClampStart(mStartOffset.y, scrollRangeStartY, scrollRangeEndY); if (!state->mLastFling.IsNull()) { // If it's been too long since the previous fling, or if the new fling's // velocity is too low, don't allow flywheel to kick in. If we do allow // flywheel to kick in, then we need to update the timestamp on the // StackScroller because otherwise it might use a stale velocity. TimeDuration flingDuration = TimeStamp::Now() - state->mLastFling; if (flingDuration.ToMilliseconds() < gfxPrefs::APZFlingAccelInterval() && velocity.Length() >= gfxPrefs::APZFlingAccelMinVelocity()) { bool unused = false; mOverScroller->ComputeScrollOffset(flingDuration.ToMilliseconds(), &unused); } else { mOverScroller->ForceFinished(true); } } mOverScroller->Fling(originX, originY, // Android needs the velocity in pixels per second and it is in pixels per ms. (int32_t)(velocity.x * 1000.0f), (int32_t)(velocity.y * 1000.0f), (int32_t)floor(scrollRangeStartX), (int32_t)ceil(scrollRangeEndX), (int32_t)floor(scrollRangeStartY), (int32_t)ceil(scrollRangeEndY), 0, 0, 0); state->mLastFling = TimeStamp::Now(); }
/** * Advances a fling by an interpolated amount based on the Android OverScroller. * This should be called whenever sampling the content transform for this * frame. Returns true if the fling animation should be advanced by one frame, * or false if there is no fling or the fling has ended. */ bool AndroidFlingAnimation::DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) { bool shouldContinueFling = true; mFlingDuration += aDelta.ToMilliseconds(); mOverScroller->ComputeScrollOffset(mFlingDuration, &shouldContinueFling); int32_t currentX = 0; int32_t currentY = 0; mOverScroller->GetCurrX(¤tX); mOverScroller->GetCurrY(¤tY); ParentLayerPoint offset((float)currentX, (float)currentY); ParentLayerPoint preCheckedOffset(offset); bool hitBoundX = CheckBounds(mApzc.mX, offset.x, mFlingDirection.x, &(offset.x)); bool hitBoundY = CheckBounds(mApzc.mY, offset.y, mFlingDirection.y, &(offset.y)); ParentLayerPoint velocity = mPreviousVelocity; // Sometimes the OverScroller fails to update the offset for a frame. // If the frame can still scroll we just use the velocity from the previous // frame. However, if the frame can no longer scroll in the direction // of the fling, then end the animation. if (offset != mPreviousOffset) { if (aDelta.ToMilliseconds() > 0) { mOverScroller->GetCurrSpeedX(&velocity.x); mOverScroller->GetCurrSpeedY(&velocity.y); velocity.x /= 1000; velocity.y /= 1000; mPreviousVelocity = velocity; } } else if ((fabsf(offset.x - preCheckedOffset.x) > BOUNDS_EPSILON) || (fabsf(offset.y - preCheckedOffset.y) > BOUNDS_EPSILON)) { // The page is no longer scrolling but the fling animation is still animating beyond the page bounds. If it goes // beyond the BOUNDS_EPSILON then it has overflowed and will never stop. In that case, stop the fling animation. shouldContinueFling = false; } else if (hitBoundX && hitBoundY) { // We can't scroll any farther along either axis. shouldContinueFling = false; } float speed = velocity.Length(); // gfxPrefs::APZFlingStoppedThreshold is only used in tests. if (!shouldContinueFling || (speed < gfxPrefs::APZFlingStoppedThreshold())) { if (shouldContinueFling) { // The OverScroller thinks it should continue but the speed is below // the stopping threshold so abort the animation. mOverScroller->AbortAnimation(); } // This animation is going to end. If DeferHandleFlingOverscroll // has not been called and there is still some velocity left, // call it so that fling hand off may occur if applicable. if (!mSentBounceX && !mSentBounceY && (speed > 0.0f)) { DeferHandleFlingOverscroll(velocity); } return false; } mPreviousOffset = offset; mApzc.SetVelocityVector(velocity); aFrameMetrics.SetScrollOffset(offset / aFrameMetrics.GetZoom()); // If we hit a bounds while flinging, send the velocity so that the bounce // animation can play. if (hitBoundX || hitBoundY) { ParentLayerPoint bounceVelocity = velocity; if (!mSentBounceX && hitBoundX && fabsf(offset.x - mStartOffset.x) > BOUNDS_EPSILON) { mSentBounceX = true; } else { bounceVelocity.x = 0.0f; } if (!mSentBounceY && hitBoundY && fabsf(offset.y - mStartOffset.y) > BOUNDS_EPSILON) { mSentBounceY = true; } else { bounceVelocity.y = 0.0f; } if (!IsZero(bounceVelocity)) { DeferHandleFlingOverscroll(bounceVelocity); } } return true; }
/** * Advances a fling by an interpolated amount based on the Android OverScroller. * This should be called whenever sampling the content transform for this * frame. Returns true if the fling animation should be advanced by one frame, * or false if there is no fling or the fling has ended. */ bool AndroidFlingAnimation::DoSample(FrameMetrics& aFrameMetrics, const TimeDuration& aDelta) { bool shouldContinueFling = true; mOverScroller->ComputeScrollOffset(&shouldContinueFling); // OverScroller::GetCurrVelocity will sometimes return NaN. So need to externally // calculate current velocity and not rely on what the OverScroller calculates. // mOverScroller->GetCurrVelocity(&speed); int32_t currentX = 0; int32_t currentY = 0; mOverScroller->GetCurrX(¤tX); mOverScroller->GetCurrY(¤tY); ParentLayerPoint offset((float)currentX, (float)currentY); bool hitBoundX = CheckBounds(mApzc.mX, offset.x, mFlingDirection.x, &(offset.x)); bool hitBoundY = CheckBounds(mApzc.mY, offset.y, mFlingDirection.y, &(offset.y)); ParentLayerPoint velocity = mPreviousVelocity; // Sometimes the OverScroller fails to update the offset for a frame. // If the frame can still scroll we just use the velocity from the previous // frame. However, if the frame can no longer scroll in the direction // of the fling, then end the animation. if (offset != mPreviousOffset) { if (aDelta.ToMilliseconds() > 0) { velocity = (offset - mPreviousOffset) / (float)aDelta.ToMilliseconds(); mPreviousVelocity = velocity; } } else if (hitBoundX || hitBoundY) { // We have reached the end of the scroll in one of the directions being scrolled and the offset has not // changed so end animation. shouldContinueFling = false; } float speed = velocity.Length(); // gfxPrefs::APZFlingStoppedThreshold is only used in tests. if (!shouldContinueFling || (speed < gfxPrefs::APZFlingStoppedThreshold())) { if (shouldContinueFling) { // The OverScroller thinks it should continue but the speed is below // the stopping threshold so abort the animation. mOverScroller->AbortAnimation(); } // This animation is going to end. If DeferHandleFlingOverscroll // has not been called and there is still some velocity left, // call it so that fling hand off may occur if applicable. if (!mSentBounceX && !mSentBounceY && (speed > 0.0f)) { DeferHandleFlingOverscroll(velocity); } return false; } mPreviousOffset = offset; mApzc.SetVelocityVector(velocity); aFrameMetrics.SetScrollOffset(offset / aFrameMetrics.GetZoom()); // If we hit a bounds while flinging, send the velocity so that the bounce // animation can play. if (hitBoundX || hitBoundY) { ParentLayerPoint bounceVelocity = velocity; if (!mSentBounceX && hitBoundX && fabsf(offset.x - mStartOffset.x) > BOUNDS_EPSILON) { mSentBounceX = true; } else { bounceVelocity.x = 0.0f; } if (!mSentBounceY && hitBoundY && fabsf(offset.y - mStartOffset.y) > BOUNDS_EPSILON) { mSentBounceY = true; } else { bounceVelocity.y = 0.0f; } if (!IsZero(bounceVelocity)) { DeferHandleFlingOverscroll(bounceVelocity); } } return true; }