blob: f57de0ae53122623419adec88842eac9c4ac1659 [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 "third_party/blink/renderer/core/scroll/programmatic_scroll_animator.h"
#include <memory>
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/core/scroll/scrollable_area.h"
#include "third_party/blink/renderer/core/scroll/smooth_scroll_sequencer.h"
#include "third_party/blink/renderer/platform/animation/compositor_keyframe_model.h"
#include "third_party/blink/renderer/platform/animation/compositor_scroll_offset_animation_curve.h"
#include "third_party/blink/renderer/platform/geometry/int_size.h"
#include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
namespace blink {
ProgrammaticScrollAnimator::ProgrammaticScrollAnimator(
ScrollableArea* scrollable_area)
: scrollable_area_(scrollable_area), start_time_(0.0) {}
ProgrammaticScrollAnimator::~ProgrammaticScrollAnimator() = default;
void ProgrammaticScrollAnimator::ResetAnimationState() {
ScrollAnimatorCompositorCoordinator::ResetAnimationState();
animation_curve_.reset();
start_time_ = 0.0;
}
void ProgrammaticScrollAnimator::NotifyOffsetChanged(
const ScrollOffset& offset) {
ScrollType scroll_type =
is_sequenced_scroll_ ? kSequencedScroll : kProgrammaticScroll;
ScrollOffsetChanged(offset, scroll_type);
}
void ProgrammaticScrollAnimator::ScrollToOffsetWithoutAnimation(
const ScrollOffset& offset,
bool is_sequenced_scroll) {
CancelAnimation();
is_sequenced_scroll_ = is_sequenced_scroll;
NotifyOffsetChanged(offset);
is_sequenced_scroll_ = false;
if (SmoothScrollSequencer* sequencer =
GetScrollableArea()->GetSmoothScrollSequencer())
sequencer->RunQueuedAnimations();
}
void ProgrammaticScrollAnimator::AnimateToOffset(const ScrollOffset& offset,
bool is_sequenced_scroll) {
if (run_state_ == RunState::kPostAnimationCleanup)
ResetAnimationState();
start_time_ = 0.0;
target_offset_ = offset;
is_sequenced_scroll_ = is_sequenced_scroll;
animation_curve_ = CompositorScrollOffsetAnimationCurve::Create(
CompositorOffsetFromBlinkOffset(target_offset_),
CompositorScrollOffsetAnimationCurve::kScrollDurationDeltaBased);
scrollable_area_->RegisterForAnimation();
if (!scrollable_area_->ScheduleAnimation()) {
ResetAnimationState();
NotifyOffsetChanged(offset);
}
run_state_ = RunState::kWaitingToSendToCompositor;
}
void ProgrammaticScrollAnimator::CancelAnimation() {
DCHECK_NE(run_state_, RunState::kRunningOnCompositorButNeedsUpdate);
ScrollAnimatorCompositorCoordinator::CancelAnimation();
}
void ProgrammaticScrollAnimator::TickAnimation(double monotonic_time) {
if (run_state_ != RunState::kRunningOnMainThread)
return;
if (!start_time_)
start_time_ = monotonic_time;
double elapsed_time = monotonic_time - start_time_;
bool is_finished = (elapsed_time > animation_curve_->Duration());
ScrollOffset offset =
BlinkOffsetFromCompositorOffset(animation_curve_->GetValue(elapsed_time));
NotifyOffsetChanged(offset);
if (is_finished) {
run_state_ = RunState::kPostAnimationCleanup;
AnimationFinished();
} else if (!scrollable_area_->ScheduleAnimation()) {
NotifyOffsetChanged(offset);
ResetAnimationState();
}
}
void ProgrammaticScrollAnimator::UpdateCompositorAnimations() {
if (run_state_ == RunState::kPostAnimationCleanup) {
// No special cleanup, simply reset animation state. We have this state
// here because the state machine is shared with ScrollAnimator which
// has to do some cleanup that requires the compositing state to be clean.
return ResetAnimationState();
}
if (compositor_animation_id_ &&
run_state_ != RunState::kRunningOnCompositor) {
// 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.
DCHECK(run_state_ == RunState::kWaitingToCancelOnCompositor ||
run_state_ == RunState::kWaitingToSendToCompositor);
RemoveAnimation();
compositor_animation_id_ = 0;
compositor_animation_group_id_ = 0;
if (run_state_ == RunState::kWaitingToCancelOnCompositor) {
ResetAnimationState();
return;
}
}
if (run_state_ == RunState::kWaitingToSendToCompositor) {
if (!element_id_)
ReattachCompositorAnimationIfNeeded(
GetScrollableArea()->GetCompositorAnimationTimeline());
bool sent_to_compositor = false;
// TODO(sunyunjia): Sequenced Smooth Scroll should also be able to
// scroll on the compositor thread. We should send the ScrollType
// information to the compositor thread.
// crbug.com/730705
if (!scrollable_area_->ShouldScrollOnMainThread() &&
!is_sequenced_scroll_) {
std::unique_ptr<CompositorKeyframeModel> animation =
CompositorKeyframeModel::Create(
*animation_curve_, compositor_target_property::SCROLL_OFFSET, 0,
0);
int animation_id = animation->Id();
int animation_group_id = animation->Group();
if (AddAnimation(std::move(animation))) {
sent_to_compositor = true;
run_state_ = RunState::kRunningOnCompositor;
compositor_animation_id_ = animation_id;
compositor_animation_group_id_ = animation_group_id;
}
}
if (!sent_to_compositor) {
run_state_ = RunState::kRunningOnMainThread;
animation_curve_->SetInitialValue(
CompositorOffsetFromBlinkOffset(scrollable_area_->GetScrollOffset()));
if (!scrollable_area_->ScheduleAnimation()) {
NotifyOffsetChanged(target_offset_);
ResetAnimationState();
}
}
}
}
void ProgrammaticScrollAnimator::LayerForCompositedScrollingDidChange(
CompositorAnimationTimeline* timeline) {
ReattachCompositorAnimationIfNeeded(timeline);
// If the composited scrolling layer is lost during a composited animation,
// continue the animation on the main thread.
if (run_state_ == RunState::kRunningOnCompositor &&
!scrollable_area_->LayerForScrolling()) {
run_state_ = RunState::kRunningOnMainThread;
compositor_animation_id_ = 0;
compositor_animation_group_id_ = 0;
animation_curve_->SetInitialValue(
CompositorOffsetFromBlinkOffset(scrollable_area_->GetScrollOffset()));
scrollable_area_->RegisterForAnimation();
if (!scrollable_area_->ScheduleAnimation()) {
ResetAnimationState();
NotifyOffsetChanged(target_offset_);
}
}
}
void ProgrammaticScrollAnimator::NotifyCompositorAnimationFinished(
int group_id) {
DCHECK_NE(run_state_, RunState::kRunningOnCompositorButNeedsUpdate);
ScrollAnimatorCompositorCoordinator::CompositorAnimationFinished(group_id);
AnimationFinished();
}
void ProgrammaticScrollAnimator::AnimationFinished() {
if (is_sequenced_scroll_) {
is_sequenced_scroll_ = false;
if (SmoothScrollSequencer* sequencer =
GetScrollableArea()->GetSmoothScrollSequencer())
sequencer->RunQueuedAnimations();
}
}
void ProgrammaticScrollAnimator::Trace(blink::Visitor* visitor) {
visitor->Trace(scrollable_area_);
ScrollAnimatorCompositorCoordinator::Trace(visitor);
}
} // namespace blink