blob: 243b0809bcf9d933c14a70b41451f4aee65b173b [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "config.h"
#include "platform/scroll/ProgrammaticScrollAnimator.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/geometry/IntPoint.h"
#include "platform/graphics/GraphicsLayer.h"
#include "platform/scroll/ScrollableArea.h"
#include "public/platform/Platform.h"
#include "public/platform/WebCompositorAnimation.h"
#include "public/platform/WebCompositorAnimationPlayer.h"
#include "public/platform/WebCompositorAnimationTimeline.h"
#include "public/platform/WebCompositorSupport.h"
#include "public/platform/WebScrollOffsetAnimationCurve.h"
namespace blink {
PassOwnPtr<ProgrammaticScrollAnimator> ProgrammaticScrollAnimator::create(ScrollableArea* scrollableArea)
{
return adoptPtr(new ProgrammaticScrollAnimator(scrollableArea));
}
ProgrammaticScrollAnimator::ProgrammaticScrollAnimator(ScrollableArea* scrollableArea)
: m_compositorAnimationAttachedToLayerId(0)
, m_scrollableArea(scrollableArea)
, m_startTime(0.0)
, m_runState(RunState::Idle)
, m_compositorAnimationId(0)
, m_compositorAnimationGroupId(0)
{
if (RuntimeEnabledFeatures::compositorAnimationTimelinesEnabled()) {
ASSERT(Platform::current()->compositorSupport());
m_compositorPlayer = adoptPtr(Platform::current()->compositorSupport()->createAnimationPlayer());
ASSERT(m_compositorPlayer);
m_compositorPlayer->setAnimationDelegate(this);
}
}
ProgrammaticScrollAnimator::~ProgrammaticScrollAnimator()
{
if (m_compositorPlayer) {
m_compositorPlayer->setAnimationDelegate(nullptr);
m_compositorPlayer.clear();
}
}
void ProgrammaticScrollAnimator::resetAnimationState()
{
m_animationCurve.clear();
m_startTime = 0.0;
m_runState = RunState::Idle;
m_compositorAnimationId = 0;
m_compositorAnimationGroupId = 0;
}
void ProgrammaticScrollAnimator::notifyPositionChanged(const DoublePoint& offset)
{
m_scrollableArea->scrollPositionChanged(offset, ProgrammaticScroll);
}
void ProgrammaticScrollAnimator::scrollToOffsetWithoutAnimation(const FloatPoint& offset)
{
cancelAnimation();
notifyPositionChanged(offset);
}
void ProgrammaticScrollAnimator::animateToOffset(FloatPoint offset)
{
m_startTime = 0.0;
m_targetOffset = offset;
m_animationCurve = adoptPtr(Platform::current()->compositorSupport()->createScrollOffsetAnimationCurve(m_targetOffset, WebCompositorAnimationCurve::TimingFunctionTypeEaseInOut));
m_animationCurve->setInitialValue(FloatPoint(m_scrollableArea->scrollPosition()));
m_scrollableArea->registerForAnimation();
if (!m_scrollableArea->scheduleAnimation()) {
resetAnimationState();
notifyPositionChanged(IntPoint(offset.x(), offset.y()));
}
m_runState = RunState::WaitingToSendToCompositor;
}
void ProgrammaticScrollAnimator::cancelAnimation()
{
switch (m_runState) {
case RunState::Idle:
case RunState::WaitingToCancelOnCompositor:
break;
case RunState::WaitingToSendToCompositor:
if (m_compositorAnimationId) {
// We still have a previous animation running on the compositor.
m_runState = RunState::WaitingToCancelOnCompositor;
} else {
resetAnimationState();
}
break;
case RunState::RunningOnMainThread:
resetAnimationState();
break;
case RunState::RunningOnCompositor:
m_runState = RunState::WaitingToCancelOnCompositor;
// Get serviced the next time compositor updates are allowed.
m_scrollableArea->registerForAnimation();
}
}
void ProgrammaticScrollAnimator::tickAnimation(double monotonicTime)
{
if (m_runState != RunState::RunningOnMainThread)
return;
if (!m_startTime)
m_startTime = monotonicTime;
double elapsedTime = monotonicTime - m_startTime;
bool isFinished = (elapsedTime > m_animationCurve->duration());
FloatPoint offset = m_animationCurve->getValue(elapsedTime);
notifyPositionChanged(IntPoint(offset.x(), offset.y()));
if (isFinished) {
resetAnimationState();
} else if (!m_scrollableArea->scheduleAnimation()) {
notifyPositionChanged(IntPoint(m_targetOffset.x(), m_targetOffset.y()));
resetAnimationState();
}
}
bool ProgrammaticScrollAnimator::hasAnimationThatRequiresService() const
{
switch (m_runState) {
case RunState::Idle:
case RunState::RunningOnCompositor:
return false;
case RunState::WaitingToSendToCompositor:
case RunState::RunningOnMainThread:
case RunState::WaitingToCancelOnCompositor:
return true;
}
ASSERT_NOT_REACHED();
return false;
}
void ProgrammaticScrollAnimator::updateCompositorAnimations()
{
if (m_compositorAnimationId && m_runState != RunState::RunningOnCompositor) {
// If the current run state is WaitingToSendToCompositor but we have a
// non-zero compositor animation id, there's a currently running
// compositor animation that needs to be removed here before the new
// animation is added below.
ASSERT(m_runState == RunState::WaitingToCancelOnCompositor || m_runState == RunState::WaitingToSendToCompositor);
if (m_compositorPlayer) {
if (m_compositorPlayer->isLayerAttached())
m_compositorPlayer->removeAnimation(m_compositorAnimationId);
} else {
if (GraphicsLayer* layer = m_scrollableArea->layerForScrolling())
layer->removeAnimation(m_compositorAnimationId);
}
m_compositorAnimationId = 0;
m_compositorAnimationGroupId = 0;
if (m_runState == RunState::WaitingToCancelOnCompositor) {
resetAnimationState();
return;
}
}
if (m_runState == RunState::WaitingToSendToCompositor) {
bool sentToCompositor = false;
if (GraphicsLayer* layer = m_scrollableArea->layerForScrolling()) {
if (!layer->platformLayer()->shouldScrollOnMainThread()) {
OwnPtr<WebCompositorAnimation> animation = adoptPtr(Platform::current()->compositorSupport()->createAnimation(*m_animationCurve, WebCompositorAnimation::TargetPropertyScrollOffset));
int animationId = animation->id();
int animationGroupId = animation->group();
bool animationAdded = false;
if (m_compositorPlayer) {
if (m_compositorPlayer->isLayerAttached()) {
m_compositorPlayer->addAnimation(animation.leakPtr());
animationAdded = true;
}
} else {
animationAdded = m_scrollableArea->layerForScrolling()->addAnimation(animation.release());
}
if (animationAdded) {
sentToCompositor = true;
m_runState = RunState::RunningOnCompositor;
m_compositorAnimationId = animationId;
m_compositorAnimationGroupId = animationGroupId;
}
}
}
if (!sentToCompositor) {
m_runState = RunState::RunningOnMainThread;
if (!m_scrollableArea->scheduleAnimation()) {
notifyPositionChanged(IntPoint(m_targetOffset.x(), m_targetOffset.y()));
resetAnimationState();
}
}
}
}
void ProgrammaticScrollAnimator::reattachCompositorPlayerIfNeeded(WebCompositorAnimationTimeline* timeline)
{
int compositorAnimationAttachedToLayerId = 0;
if (m_scrollableArea->layerForScrolling())
compositorAnimationAttachedToLayerId = m_scrollableArea->layerForScrolling()->platformLayer()->id();
if (compositorAnimationAttachedToLayerId != m_compositorAnimationAttachedToLayerId) {
if (m_compositorPlayer && timeline) {
// Detach from old layer (if any).
if (m_compositorAnimationAttachedToLayerId) {
if (m_compositorPlayer->isLayerAttached())
m_compositorPlayer->detachLayer();
timeline->playerDestroyed(*this);
}
// Attach to new layer (if any).
if (compositorAnimationAttachedToLayerId) {
ASSERT(m_scrollableArea->layerForScrolling());
ASSERT(!m_compositorPlayer->isLayerAttached());
timeline->playerAttached(*this);
m_compositorPlayer->attachLayer(m_scrollableArea->layerForScrolling()->platformLayer());
}
m_compositorAnimationAttachedToLayerId = compositorAnimationAttachedToLayerId;
}
}
}
void ProgrammaticScrollAnimator::layerForCompositedScrollingDidChange(WebCompositorAnimationTimeline* timeline)
{
reattachCompositorPlayerIfNeeded(timeline);
// If the composited scrolling layer is lost during a composited animation,
// continue the animation on the main thread.
if (m_runState == RunState::RunningOnCompositor && !m_scrollableArea->layerForScrolling()) {
m_runState = RunState::RunningOnMainThread;
m_compositorAnimationId = 0;
m_compositorAnimationGroupId = 0;
m_animationCurve->setInitialValue(FloatPoint(m_scrollableArea->scrollPosition()));
m_scrollableArea->registerForAnimation();
if (!m_scrollableArea->scheduleAnimation()) {
resetAnimationState();
notifyPositionChanged(IntPoint(m_targetOffset.x(), m_targetOffset.y()));
}
}
}
void ProgrammaticScrollAnimator::notifyCompositorAnimationFinished(int groupId)
{
if (m_compositorAnimationGroupId != groupId)
return;
m_compositorAnimationId = 0;
m_compositorAnimationGroupId = 0;
switch (m_runState) {
case RunState::Idle:
case RunState::RunningOnMainThread:
ASSERT_NOT_REACHED();
break;
case RunState::WaitingToSendToCompositor:
break;
case RunState::RunningOnCompositor:
case RunState::WaitingToCancelOnCompositor:
resetAnimationState();
}
}
void ProgrammaticScrollAnimator::notifyAnimationStarted(double monotonicTime, int group)
{
}
void ProgrammaticScrollAnimator::notifyAnimationFinished(double monotonicTime, int group)
{
notifyCompositorAnimationFinished(group);
}
WebCompositorAnimationPlayer* ProgrammaticScrollAnimator::compositorPlayer() const
{
return m_compositorPlayer.get();
}
} // namespace blink