michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set sw=2 ts=8 et tw=80 : */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "Axis.h" michael@0: #include // for fabsf, pow, powf michael@0: #include // for max michael@0: #include "AsyncPanZoomController.h" // for AsyncPanZoomController michael@0: #include "mozilla/layers/APZCTreeManager.h" // for APZCTreeManager michael@0: #include "FrameMetrics.h" // for FrameMetrics michael@0: #include "mozilla/Attributes.h" // for MOZ_FINAL michael@0: #include "mozilla/Preferences.h" // for Preferences michael@0: #include "mozilla/gfx/Rect.h" // for RoundedIn michael@0: #include "mozilla/mozalloc.h" // for operator new michael@0: #include "nsMathUtils.h" // for NS_lround michael@0: #include "nsThreadUtils.h" // for NS_DispatchToMainThread, etc michael@0: #include "nscore.h" // for NS_IMETHOD michael@0: #include "gfxPrefs.h" // for the preferences michael@0: michael@0: namespace mozilla { michael@0: namespace layers { michael@0: michael@0: Axis::Axis(AsyncPanZoomController* aAsyncPanZoomController) michael@0: : mPos(0), michael@0: mVelocity(0.0f), michael@0: mAxisLocked(false), michael@0: mAsyncPanZoomController(aAsyncPanZoomController) michael@0: { michael@0: } michael@0: michael@0: void Axis::UpdateWithTouchAtDevicePoint(int32_t aPos, const TimeDuration& aTimeDelta) { michael@0: float newVelocity = mAxisLocked ? 0 : (mPos - aPos) / aTimeDelta.ToMilliseconds(); michael@0: if (gfxPrefs::APZMaxVelocity() > 0.0f) { michael@0: newVelocity = std::min(newVelocity, gfxPrefs::APZMaxVelocity() * APZCTreeManager::GetDPI()); michael@0: } michael@0: michael@0: mVelocity = newVelocity; michael@0: mPos = aPos; michael@0: michael@0: // Limit queue size pased on pref michael@0: mVelocityQueue.AppendElement(mVelocity); michael@0: if (mVelocityQueue.Length() > gfxPrefs::APZMaxVelocityQueueSize()) { michael@0: mVelocityQueue.RemoveElementAt(0); michael@0: } michael@0: } michael@0: michael@0: void Axis::StartTouch(int32_t aPos) { michael@0: mStartPos = aPos; michael@0: mPos = aPos; michael@0: mAxisLocked = false; michael@0: } michael@0: michael@0: float Axis::AdjustDisplacement(float aDisplacement, float& aOverscrollAmountOut) { michael@0: if (mAxisLocked) { michael@0: aOverscrollAmountOut = 0; michael@0: return 0; michael@0: } michael@0: michael@0: float displacement = aDisplacement; michael@0: michael@0: // If this displacement will cause an overscroll, throttle it. Can potentially michael@0: // bring it to 0 even if the velocity is high. michael@0: if (DisplacementWillOverscroll(displacement) != OVERSCROLL_NONE) { michael@0: // No need to have a velocity along this axis anymore; it won't take us michael@0: // anywhere, so we're just spinning needlessly. michael@0: mVelocity = 0.0f; michael@0: aOverscrollAmountOut = DisplacementWillOverscrollAmount(displacement); michael@0: displacement -= aOverscrollAmountOut; michael@0: } michael@0: return displacement; michael@0: } michael@0: michael@0: float Axis::PanDistance() { michael@0: return fabsf(mPos - mStartPos); michael@0: } michael@0: michael@0: float Axis::PanDistance(float aPos) { michael@0: return fabsf(aPos - mStartPos); michael@0: } michael@0: michael@0: void Axis::EndTouch() { michael@0: // Calculate the mean velocity and empty the queue. michael@0: int count = mVelocityQueue.Length(); michael@0: if (count) { michael@0: mVelocity = 0; michael@0: while (!mVelocityQueue.IsEmpty()) { michael@0: mVelocity += mVelocityQueue[0]; michael@0: mVelocityQueue.RemoveElementAt(0); michael@0: } michael@0: mVelocity /= count; michael@0: } michael@0: } michael@0: michael@0: void Axis::CancelTouch() { michael@0: mVelocity = 0.0f; michael@0: while (!mVelocityQueue.IsEmpty()) { michael@0: mVelocityQueue.RemoveElementAt(0); michael@0: } michael@0: } michael@0: michael@0: bool Axis::Scrollable() { michael@0: if (mAxisLocked) { michael@0: return false; michael@0: } michael@0: return GetCompositionLength() < GetPageLength(); michael@0: } michael@0: michael@0: bool Axis::FlingApplyFrictionOrCancel(const TimeDuration& aDelta) { michael@0: if (fabsf(mVelocity) <= gfxPrefs::APZFlingStoppedThreshold()) { michael@0: // If the velocity is very low, just set it to 0 and stop the fling, michael@0: // otherwise we'll just asymptotically approach 0 and the user won't michael@0: // actually see any changes. michael@0: mVelocity = 0.0f; michael@0: return false; michael@0: } else { michael@0: mVelocity *= pow(1.0f - gfxPrefs::APZFlingFriction(), float(aDelta.ToMilliseconds())); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: Axis::Overscroll Axis::GetOverscroll() { michael@0: // If the current pan takes the window to the left of or above the current michael@0: // page rect. michael@0: bool minus = GetOrigin() < GetPageStart(); michael@0: // If the current pan takes the window to the right of or below the current michael@0: // page rect. michael@0: bool plus = GetCompositionEnd() > GetPageEnd(); michael@0: if (minus && plus) { michael@0: return OVERSCROLL_BOTH; michael@0: } michael@0: if (minus) { michael@0: return OVERSCROLL_MINUS; michael@0: } michael@0: if (plus) { michael@0: return OVERSCROLL_PLUS; michael@0: } michael@0: return OVERSCROLL_NONE; michael@0: } michael@0: michael@0: float Axis::GetExcess() { michael@0: switch (GetOverscroll()) { michael@0: case OVERSCROLL_MINUS: return GetOrigin() - GetPageStart(); michael@0: case OVERSCROLL_PLUS: return GetCompositionEnd() - GetPageEnd(); michael@0: case OVERSCROLL_BOTH: return (GetCompositionEnd() - GetPageEnd()) + michael@0: (GetPageStart() - GetOrigin()); michael@0: default: return 0; michael@0: } michael@0: } michael@0: michael@0: Axis::Overscroll Axis::DisplacementWillOverscroll(float aDisplacement) { michael@0: // If the current pan plus a displacement takes the window to the left of or michael@0: // above the current page rect. michael@0: bool minus = GetOrigin() + aDisplacement < GetPageStart(); michael@0: // If the current pan plus a displacement takes the window to the right of or michael@0: // below the current page rect. michael@0: bool plus = GetCompositionEnd() + aDisplacement > GetPageEnd(); michael@0: if (minus && plus) { michael@0: return OVERSCROLL_BOTH; michael@0: } michael@0: if (minus) { michael@0: return OVERSCROLL_MINUS; michael@0: } michael@0: if (plus) { michael@0: return OVERSCROLL_PLUS; michael@0: } michael@0: return OVERSCROLL_NONE; michael@0: } michael@0: michael@0: float Axis::DisplacementWillOverscrollAmount(float aDisplacement) { michael@0: switch (DisplacementWillOverscroll(aDisplacement)) { michael@0: case OVERSCROLL_MINUS: return (GetOrigin() + aDisplacement) - GetPageStart(); michael@0: case OVERSCROLL_PLUS: return (GetCompositionEnd() + aDisplacement) - GetPageEnd(); michael@0: // Don't handle overscrolled in both directions; a displacement can't cause michael@0: // this, it must have already been zoomed out too far. michael@0: default: return 0; michael@0: } michael@0: } michael@0: michael@0: float Axis::ScaleWillOverscrollAmount(float aScale, float aFocus) { michael@0: float originAfterScale = (GetOrigin() + aFocus) - (aFocus / aScale); michael@0: michael@0: bool both = ScaleWillOverscrollBothSides(aScale); michael@0: bool minus = originAfterScale < GetPageStart(); michael@0: bool plus = (originAfterScale + (GetCompositionLength() / aScale)) > GetPageEnd(); michael@0: michael@0: if ((minus && plus) || both) { michael@0: // If we ever reach here it's a bug in the client code. michael@0: MOZ_ASSERT(false, "In an OVERSCROLL_BOTH condition in ScaleWillOverscrollAmount"); michael@0: return 0; michael@0: } michael@0: if (minus) { michael@0: return originAfterScale - GetPageStart(); michael@0: } michael@0: if (plus) { michael@0: return originAfterScale + (GetCompositionLength() / aScale) - GetPageEnd(); michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: float Axis::GetVelocity() { michael@0: return mAxisLocked ? 0 : mVelocity; michael@0: } michael@0: michael@0: void Axis::SetVelocity(float aVelocity) { michael@0: mVelocity = aVelocity; michael@0: } michael@0: michael@0: float Axis::GetCompositionEnd() const { michael@0: return GetOrigin() + GetCompositionLength(); michael@0: } michael@0: michael@0: float Axis::GetPageEnd() const { michael@0: return GetPageStart() + GetPageLength(); michael@0: } michael@0: michael@0: float Axis::GetOrigin() const { michael@0: CSSPoint origin = mAsyncPanZoomController->GetFrameMetrics().GetScrollOffset(); michael@0: return GetPointOffset(origin); michael@0: } michael@0: michael@0: float Axis::GetCompositionLength() const { michael@0: const FrameMetrics& metrics = mAsyncPanZoomController->GetFrameMetrics(); michael@0: return GetRectLength(metrics.CalculateCompositedRectInCssPixels()); michael@0: } michael@0: michael@0: float Axis::GetPageStart() const { michael@0: CSSRect pageRect = mAsyncPanZoomController->GetFrameMetrics().mScrollableRect; michael@0: return GetRectOffset(pageRect); michael@0: } michael@0: michael@0: float Axis::GetPageLength() const { michael@0: CSSRect pageRect = mAsyncPanZoomController->GetFrameMetrics().mScrollableRect; michael@0: return GetRectLength(pageRect); michael@0: } michael@0: michael@0: bool Axis::ScaleWillOverscrollBothSides(float aScale) { michael@0: const FrameMetrics& metrics = mAsyncPanZoomController->GetFrameMetrics(); michael@0: michael@0: CSSToParentLayerScale scale(metrics.GetZoomToParent().scale * aScale); michael@0: CSSRect cssCompositionBounds = metrics.mCompositionBounds / scale; michael@0: michael@0: return GetRectLength(metrics.mScrollableRect) < GetRectLength(cssCompositionBounds); michael@0: } michael@0: michael@0: bool Axis::HasRoomToPan() const { michael@0: return GetOrigin() > GetPageStart() michael@0: || GetCompositionEnd() < GetPageEnd(); michael@0: } michael@0: michael@0: michael@0: AxisX::AxisX(AsyncPanZoomController* aAsyncPanZoomController) michael@0: : Axis(aAsyncPanZoomController) michael@0: { michael@0: michael@0: } michael@0: michael@0: float AxisX::GetPointOffset(const CSSPoint& aPoint) const michael@0: { michael@0: return aPoint.x; michael@0: } michael@0: michael@0: float AxisX::GetRectLength(const CSSRect& aRect) const michael@0: { michael@0: return aRect.width; michael@0: } michael@0: michael@0: float AxisX::GetRectOffset(const CSSRect& aRect) const michael@0: { michael@0: return aRect.x; michael@0: } michael@0: michael@0: AxisY::AxisY(AsyncPanZoomController* aAsyncPanZoomController) michael@0: : Axis(aAsyncPanZoomController) michael@0: { michael@0: michael@0: } michael@0: michael@0: float AxisY::GetPointOffset(const CSSPoint& aPoint) const michael@0: { michael@0: return aPoint.y; michael@0: } michael@0: michael@0: float AxisY::GetRectLength(const CSSRect& aRect) const michael@0: { michael@0: return aRect.height; michael@0: } michael@0: michael@0: float AxisY::GetRectOffset(const CSSRect& aRect) const michael@0: { michael@0: return aRect.y; michael@0: } michael@0: michael@0: } michael@0: }