Bug 839911 - Separate animation out from layer code; r=kats

This commit is contained in:
Anthony Jones 2013-12-06 16:21:39 +13:00
parent a42a4aed9c
commit dc434fe6a9
3 changed files with 159 additions and 85 deletions

View File

@ -278,6 +278,57 @@ GetFrameTime() {
return sFrameTime;
}
class FlingAnimation: public AsyncPanZoomAnimation {
public:
FlingAnimation(AxisX aX, AxisY aY)
: AsyncPanZoomAnimation(TimeDuration::FromMilliseconds(gFlingRepaintInterval))
, mX(aX)
, mY(aY)
{}
/**
* Advances a fling by an interpolated amount based on the passed in |aDelta|.
* 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.
*/
virtual bool Sample(FrameMetrics& aFrameMetrics,
const TimeDuration& aDelta);
private:
AxisX mX;
AxisY mY;
};
class ZoomAnimation: public AsyncPanZoomAnimation {
public:
ZoomAnimation(CSSPoint aStartOffset, CSSToScreenScale aStartZoom,
CSSPoint aEndOffset, CSSToScreenScale aEndZoom)
: mStartOffset(aStartOffset)
, mStartZoom(aStartZoom)
, mEndOffset(aEndOffset)
, mEndZoom(aEndZoom)
{}
virtual bool Sample(FrameMetrics& aFrameMetrics,
const TimeDuration& aDelta);
private:
TimeDuration mDuration;
// Old metrics from before we started a zoom animation. This is only valid
// when we are in the "ANIMATED_ZOOM" state. This is used so that we can
// interpolate between the start and end frames. We only use the
// |mViewportScrollOffset| and |mResolution| fields on this.
CSSPoint mStartOffset;
CSSToScreenScale mStartZoom;
// Target metrics for a zoom to animation. This is only valid when we are in
// the "ANIMATED_ZOOM" state. We only use the |mViewportScrollOffset| and
// |mResolution| fields on this.
CSSPoint mEndOffset;
CSSToScreenScale mEndZoom;
};
void
AsyncPanZoomController::SetFrameTime(const TimeStamp& aTime) {
sFrameTime = aTime;
@ -597,12 +648,12 @@ nsEventStatus AsyncPanZoomController::OnTouchEnd(const MultiTouchInput& aEvent)
case PANNING_LOCKED_Y:
{
ReentrantMonitorAutoEnter lock(mMonitor);
ScheduleComposite();
RequestContentRepaint();
}
mX.EndTouch();
mY.EndTouch();
SetState(FLING);
StartAnimation(new FlingAnimation(mX, mY));
return nsEventStatus_eConsumeNoDefault;
case PINCHING:
@ -999,18 +1050,12 @@ ScreenIntPoint& AsyncPanZoomController::GetFirstTouchScreenPoint(const MultiTouc
return ((SingleTouchData&)aEvent.mTouches[0]).mScreenPoint;
}
bool AsyncPanZoomController::DoFling(const TimeDuration& aDelta) {
if (mState != FLING) {
return false;
}
bool FlingAnimation::Sample(FrameMetrics& aFrameMetrics,
const TimeDuration& aDelta) {
bool shouldContinueFlingX = mX.FlingApplyFrictionOrCancel(aDelta),
shouldContinueFlingY = mY.FlingApplyFrictionOrCancel(aDelta);
// If we shouldn't continue the fling, let's just stop and repaint.
if (!shouldContinueFlingX && !shouldContinueFlingY) {
SendAsyncScrollEvent();
RequestContentRepaint();
SetState(NOTHING);
return false;
}
@ -1020,21 +1065,26 @@ bool AsyncPanZoomController::DoFling(const TimeDuration& aDelta) {
// Inversely scale the offset by the resolution (when you're zoomed further in,
// a larger swipe should move you a shorter distance).
CSSPoint cssOffset = offset / mFrameMetrics.mZoom;
ScrollBy(CSSPoint::FromUnknownPoint(gfx::Point(
CSSPoint cssOffset = offset / aFrameMetrics.mZoom;
aFrameMetrics.mScrollOffset += CSSPoint::FromUnknownPoint(gfx::Point(
mX.AdjustDisplacement(cssOffset.x, overscroll.x),
mY.AdjustDisplacement(cssOffset.y, overscroll.y)
)));
TimeDuration timePaintDelta = mPaintThrottler.TimeSinceLastRequest(GetFrameTime());
if (timePaintDelta.ToMilliseconds() > gFlingRepaintInterval) {
RequestContentRepaint();
}
));
return true;
}
void AsyncPanZoomController::StartAnimation(AsyncPanZoomAnimation* aAnimation)
{
ReentrantMonitorAutoEnter lock(mMonitor);
mAnimation = aAnimation;
mLastSampleTime = GetFrameTime();
ScheduleComposite();
}
void AsyncPanZoomController::CancelAnimation() {
SetState(NOTHING);
mAnimation = nullptr;
}
void AsyncPanZoomController::SetCompositorParent(CompositorParent* aCompositorParent) {
@ -1241,6 +1291,55 @@ AsyncPanZoomController::FireAsyncScrollOnTimeout()
mAsyncScrollTimeoutTask = nullptr;
}
bool ZoomAnimation::Sample(FrameMetrics& aFrameMetrics,
const TimeDuration& aDelta) {
mDuration += aDelta;
double animPosition = mDuration / ZOOM_TO_DURATION;
if (animPosition >= 1.0) {
aFrameMetrics.mZoom = mEndZoom;
aFrameMetrics.mScrollOffset = mEndOffset;
return false;
}
// Sample the zoom at the current time point. The sampled zoom
// will affect the final computed resolution.
double sampledPosition = gComputedTimingFunction->GetValue(animPosition);
// We scale the scrollOffset linearly with sampledPosition, so the zoom
// needs to scale inversely to match.
aFrameMetrics.mZoom = CSSToScreenScale(1 /
(sampledPosition / mEndZoom.scale +
(1 - sampledPosition) / mStartZoom.scale));
aFrameMetrics.mScrollOffset = CSSPoint::FromUnknownPoint(gfx::Point(
mEndOffset.x * sampledPosition + mStartOffset.x * (1 - sampledPosition),
mEndOffset.y * sampledPosition + mStartOffset.y * (1 - sampledPosition)
));
return true;
}
bool AsyncPanZoomController::UpdateAnimation(const TimeStamp& aSampleTime)
{
if (mAnimation) {
if (mAnimation->Sample(mFrameMetrics, aSampleTime - mLastSampleTime)) {
if (mPaintThrottler.TimeSinceLastRequest(aSampleTime) >
mAnimation->mRepaintInterval) {
RequestContentRepaint();
}
} else {
mAnimation = nullptr;
SetState(NOTHING);
SendAsyncScrollEvent();
RequestContentRepaint();
}
mLastSampleTime = aSampleTime;
return true;
}
return false;
}
bool AsyncPanZoomController::SampleContentTransformForFrame(const TimeStamp& aSampleTime,
ViewTransform* aNewTransform,
ScreenPoint& aScrollOffset) {
@ -1254,47 +1353,7 @@ bool AsyncPanZoomController::SampleContentTransformForFrame(const TimeStamp& aSa
{
ReentrantMonitorAutoEnter lock(mMonitor);
switch (mState) {
case FLING:
// If a fling is currently happening, apply it now. We can pull
// the updated metrics afterwards.
requestAnimationFrame |= DoFling(aSampleTime - mLastSampleTime);
break;
case ANIMATING_ZOOM: {
double animPosition = (aSampleTime - mAnimationStartTime) / ZOOM_TO_DURATION;
if (animPosition > 1.0) {
animPosition = 1.0;
}
// Sample the zoom at the current time point. The sampled zoom
// will affect the final computed resolution.
double sampledPosition = gComputedTimingFunction->GetValue(animPosition);
// We scale the scrollOffset linearly with sampledPosition, so the zoom
// needs to scale inversely to match.
mFrameMetrics.mZoom = CSSToScreenScale(1 /
(sampledPosition / mEndZoomToMetrics.mZoom.scale +
(1 - sampledPosition) / mStartZoomToMetrics.mZoom.scale));
mFrameMetrics.mScrollOffset = CSSPoint::FromUnknownPoint(gfx::Point(
mEndZoomToMetrics.mScrollOffset.x * sampledPosition +
mStartZoomToMetrics.mScrollOffset.x * (1 - sampledPosition),
mEndZoomToMetrics.mScrollOffset.y * sampledPosition +
mStartZoomToMetrics.mScrollOffset.y * (1 - sampledPosition)
));
requestAnimationFrame = true;
if (aSampleTime - mAnimationStartTime >= ZOOM_TO_DURATION) {
SetState(NOTHING);
SendAsyncScrollEvent();
RequestContentRepaint();
}
break;
}
default:
break;
}
requestAnimationFrame = UpdateAnimation(aSampleTime);
aScrollOffset = mFrameMetrics.mScrollOffset * mFrameMetrics.mZoom;
*aNewTransform = GetCurrentAsyncTransform();
@ -1333,8 +1392,6 @@ bool AsyncPanZoomController::SampleContentTransformForFrame(const TimeStamp& aSa
gAsyncScrollTimeout);
}
mLastSampleTime = aSampleTime;
return requestAnimationFrame;
}
@ -1490,11 +1547,11 @@ void AsyncPanZoomController::ZoomToRect(CSSRect aRect) {
}
targetZoom.scale = clamped(targetZoom.scale, localMinZoom.scale, localMaxZoom.scale);
mEndZoomToMetrics = mFrameMetrics;
mEndZoomToMetrics.mZoom = targetZoom;
FrameMetrics endZoomToMetrics = mFrameMetrics;
endZoomToMetrics.mZoom = targetZoom;
// Adjust the zoomToRect to a sensible position to prevent overscrolling.
CSSRect rectAfterZoom = mEndZoomToMetrics.CalculateCompositedRectInCssPixels();
CSSRect rectAfterZoom = endZoomToMetrics.CalculateCompositedRectInCssPixels();
// If either of these conditions are met, the page will be
// overscrolled after zoomed
@ -1507,21 +1564,22 @@ void AsyncPanZoomController::ZoomToRect(CSSRect aRect) {
aRect.x = aRect.x > 0 ? aRect.x : 0;
}
mStartZoomToMetrics = mFrameMetrics;
mEndZoomToMetrics.mScrollOffset = aRect.TopLeft();
mEndZoomToMetrics.mDisplayPort =
CalculatePendingDisplayPort(mEndZoomToMetrics,
endZoomToMetrics.mScrollOffset = aRect.TopLeft();
endZoomToMetrics.mDisplayPort =
CalculatePendingDisplayPort(endZoomToMetrics,
gfx::Point(0,0),
gfx::Point(0,0),
0);
mAnimationStartTime = GetFrameTime();
ScheduleComposite();
StartAnimation(new ZoomAnimation(
mFrameMetrics.mScrollOffset,
mFrameMetrics.mZoom,
endZoomToMetrics.mScrollOffset,
endZoomToMetrics.mZoom));
// Schedule a repaint now, so the new displayport will be painted before the
// animation finishes.
ScheduleContentRepaint(mEndZoomToMetrics);
ScheduleContentRepaint(endZoomToMetrics);
}
}

View File

@ -30,6 +30,7 @@ class GestureEventListener;
class ContainerLayer;
class ViewTransform;
class APZCTreeManager;
class AsyncPanZoomAnimation;
/**
* Controller for all panning and zooming logic. Any time a user input is
@ -154,6 +155,8 @@ public:
// These methods must only be called on the compositor thread.
//
bool UpdateAnimation(const TimeStamp& aSampleTime);
/**
* The compositor calls this when it's about to draw pannable/zoomable content
* and is setting up transforms for compositing the layer tree. This is not
@ -264,6 +267,8 @@ public:
*/
void UpdateScrollOffset(const CSSPoint& aScrollOffset);
void StartAnimation(AsyncPanZoomAnimation* aAnimation);
/**
* Cancels any currently running animation. Note that all this does is set the
* state of the AsyncPanZoomController back to NOTHING, but it is the
@ -594,16 +599,6 @@ private:
// If we don't do this check, we don't get a ShadowLayersUpdated back.
FrameMetrics mLastPaintRequestMetrics;
// Old metrics from before we started a zoom animation. This is only valid
// when we are in the "ANIMATED_ZOOM" state. This is used so that we can
// interpolate between the start and end frames. We only use the
// |mViewportScrollOffset| and |mResolution| fields on this.
FrameMetrics mStartZoomToMetrics;
// Target metrics for a zoom to animation. This is only valid when we are in
// the "ANIMATED_ZOOM" state. We only use the |mViewportScrollOffset| and
// |mResolution| fields on this.
FrameMetrics mEndZoomToMetrics;
nsTArray<MultiTouchInput> mTouchQueue;
CancelableTask* mTouchListenerTimeoutTask;
@ -624,10 +619,6 @@ private:
// The last time a touch event came through on the UI thread.
uint32_t mLastEventTime;
// Start time of an animation. This is used for a zoom to animation to mark
// the beginning.
TimeStamp mAnimationStartTime;
// Stores the previous focus point if there is a pinch gesture happening. Used
// to allow panning by moving multiple fingers (thus moving the focus point).
ScreenPoint mLastZoomFocus;
@ -655,6 +646,8 @@ private:
// and we don't want to queue the events back up again.
bool mHandlingTouchQueue;
RefPtr<AsyncPanZoomAnimation> mAnimation;
friend class Axis;
/* The functions and members in this section are used to build a tree
@ -740,6 +733,29 @@ private:
gfx3DMatrix mCSSTransform;
};
class AsyncPanZoomAnimation {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPanZoomAnimation)
public:
AsyncPanZoomAnimation(const TimeDuration& aRepaintInterval =
TimeDuration::Forever())
: mRepaintInterval(aRepaintInterval)
{ }
virtual ~AsyncPanZoomAnimation()
{ }
virtual bool Sample(FrameMetrics& aFrameMetrics,
const TimeDuration& aDelta) = 0;
/**
* Specifies how frequently (at most) we want to do repaints during the
* animation sequence. TimeDuration::Forever() will cause it to only repaint
* at the end of the animation.
*/
TimeDuration mRepaintInterval;
};
}
}

View File

@ -416,7 +416,7 @@ TEST(AsyncPanZoomController, OverScrollPanning) {
apzc->SetFrameMetrics(TestFrameMetrics());
apzc->NotifyLayersUpdated(TestFrameMetrics(), true);
EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(3);
EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(4);
EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
// Pan sufficiently to hit overscroll behavior